209 lines
5.7 KiB
Vue
209 lines
5.7 KiB
Vue
|
|
<template>
|
||
|
|
<div class="xml-importer">
|
||
|
|
<input type="file" @change="handleFileUpload" accept=".xml" />
|
||
|
|
<button @click="importToEditor" :disabled="!xmlData">导入到编辑器</button>
|
||
|
|
<div ref="editor"></div>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
import E from 'wangeditor'
|
||
|
|
|
||
|
|
export default {
|
||
|
|
name: 'XmlImporter',
|
||
|
|
data() {
|
||
|
|
return {
|
||
|
|
editor: null,
|
||
|
|
xmlData: null,
|
||
|
|
parsedContent: ''
|
||
|
|
}
|
||
|
|
},
|
||
|
|
mounted() {
|
||
|
|
this.initEditor()
|
||
|
|
},
|
||
|
|
methods: {
|
||
|
|
initEditor() {
|
||
|
|
this.editor = new E(this.$refs.editor)
|
||
|
|
this.editor.create()
|
||
|
|
},
|
||
|
|
|
||
|
|
// 处理XML文件上传
|
||
|
|
handleFileUpload(event) {
|
||
|
|
const file = event.target.files[0]
|
||
|
|
if (!file) return
|
||
|
|
|
||
|
|
const reader = new FileReader()
|
||
|
|
reader.onload = (e) => {
|
||
|
|
this.xmlData = e.target.result
|
||
|
|
this.parseS1000DXml(this.xmlData)
|
||
|
|
}
|
||
|
|
reader.readAsText(file)
|
||
|
|
},
|
||
|
|
|
||
|
|
// 解析S1000D XML
|
||
|
|
parseS1000DXml(xmlString) {
|
||
|
|
try {
|
||
|
|
const parser = new DOMParser()
|
||
|
|
const xmlDoc = parser.parseFromString(xmlString, "text/xml")
|
||
|
|
|
||
|
|
// 检查是否是有效的S1000D文档
|
||
|
|
if (xmlDoc.getElementsByTagName('parsererror').length > 0) {
|
||
|
|
throw new Error('XML解析错误: ' + xmlDoc.getElementsByTagName('parsererror')[0].textContent)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 提取内容部分
|
||
|
|
const contentElement = xmlDoc.querySelector('content description')
|
||
|
|
if (!contentElement) {
|
||
|
|
throw new Error('未找到content/description元素')
|
||
|
|
}
|
||
|
|
|
||
|
|
// 将S1000D XML转换为HTML
|
||
|
|
this.parsedContent = this.s1000dToHtml(contentElement)
|
||
|
|
} catch (error) {
|
||
|
|
console.error('XML解析失败:', error)
|
||
|
|
alert(`解析失败: ${error.message}`)
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
// 将S1000D元素转换为HTML
|
||
|
|
s1000dToHtml(s1000dElement) {
|
||
|
|
let html = ''
|
||
|
|
|
||
|
|
// 递归处理子节点
|
||
|
|
const processNode = (node) => {
|
||
|
|
if (node.nodeType === Node.TEXT_NODE) {
|
||
|
|
return node.textContent
|
||
|
|
}
|
||
|
|
|
||
|
|
if (node.nodeType !== Node.ELEMENT_NODE) return ''
|
||
|
|
|
||
|
|
const tag = node.tagName.toLowerCase()
|
||
|
|
const children = Array.from(node.childNodes).map(processNode).join('')
|
||
|
|
const attrs = Array.from(node.attributes)
|
||
|
|
.map(attr => ` ${attr.name}="${this.escapeHtml(attr.value)}"`)
|
||
|
|
.join('')
|
||
|
|
|
||
|
|
// 将S1000D标签映射回HTML
|
||
|
|
switch(tag) {
|
||
|
|
case 'para': {
|
||
|
|
return `<p${attrs}>${children}</p>`
|
||
|
|
}
|
||
|
|
case 'emphasis': {
|
||
|
|
const role = node.getAttribute('role') || 'bold'
|
||
|
|
switch(role) {
|
||
|
|
case 'bold': return `<strong${attrs}>${children}</strong>`
|
||
|
|
case 'italic': return `<em${attrs}>${children}</em>`
|
||
|
|
case 'underline': return `<u${attrs}>${children}</u>`
|
||
|
|
default: return `<span class="emphasis-${role}"${attrs}>${children}</span>`
|
||
|
|
}
|
||
|
|
}
|
||
|
|
case 'orderedlist': {
|
||
|
|
return `<ol${attrs}>${children}</ol>`
|
||
|
|
}
|
||
|
|
case 'itemizedlist': {
|
||
|
|
return `<ul${attrs}>${children}</ul>`
|
||
|
|
}
|
||
|
|
case 'listitem': {
|
||
|
|
return `<li${attrs}>${children}</li>`
|
||
|
|
}
|
||
|
|
case 'figure': {
|
||
|
|
const graphic = node.querySelector('graphic')
|
||
|
|
if (graphic) {
|
||
|
|
const imgAttrs = Array.from(graphic.attributes)
|
||
|
|
.filter(attr => attr.name !== 'infoentityid')
|
||
|
|
.map(attr => ` ${attr.name}="${this.escapeHtml(attr.value)}"`)
|
||
|
|
.join('')
|
||
|
|
return `<div class="figure"><img${imgAttrs}>${children}</div>`
|
||
|
|
}
|
||
|
|
return children
|
||
|
|
}
|
||
|
|
case 'table': {
|
||
|
|
return `<table${attrs}>${children}</table>`
|
||
|
|
}
|
||
|
|
case 'row': {
|
||
|
|
return `<tr${attrs}>${children}</tr>`
|
||
|
|
}
|
||
|
|
case 'entry': {
|
||
|
|
const isHeader = node.getAttribute('thead') === 'yes'
|
||
|
|
return isHeader
|
||
|
|
? `<th${attrs}>${children}</th>`
|
||
|
|
: `<td${attrs}>${children}</td>`
|
||
|
|
}
|
||
|
|
case 'title': {
|
||
|
|
const level = node.getAttribute('level') || '1'
|
||
|
|
return `<h${level}${attrs}>${children}</h${level}>`
|
||
|
|
}
|
||
|
|
case 'xref': {
|
||
|
|
const href = node.getAttribute('xrefid') || '#'
|
||
|
|
return `<a href="${href}"${attrs}>${children}</a>`
|
||
|
|
}
|
||
|
|
default: {
|
||
|
|
// 未知标签作为div处理
|
||
|
|
return `<div class="s1000d-${tag}"${attrs}>${children}</div>`
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 处理所有子节点
|
||
|
|
html = Array.from(s1000dElement.childNodes).map(processNode).join('')
|
||
|
|
|
||
|
|
return html
|
||
|
|
},
|
||
|
|
|
||
|
|
// HTML特殊字符转义
|
||
|
|
escapeHtml(text) {
|
||
|
|
return text
|
||
|
|
.replace(/&/g, '&')
|
||
|
|
.replace(/</g, '<')
|
||
|
|
.replace(/>/g, '>')
|
||
|
|
.replace(/"/g, '"')
|
||
|
|
.replace(/'/g, ''')
|
||
|
|
},
|
||
|
|
|
||
|
|
// 将解析后的内容导入编辑器
|
||
|
|
importToEditor() {
|
||
|
|
if (this.parsedContent && this.editor) {
|
||
|
|
this.editor.txt.html(this.parsedContent)
|
||
|
|
alert('内容已成功导入编辑器')
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
beforeDestroy() {
|
||
|
|
if (this.editor) {
|
||
|
|
this.editor.destroy()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style>
|
||
|
|
.xml-importer {
|
||
|
|
max-width: 800px;
|
||
|
|
margin: 0 auto;
|
||
|
|
padding: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.xml-importer input[type="file"] {
|
||
|
|
margin-bottom: 15px;
|
||
|
|
display: block;
|
||
|
|
}
|
||
|
|
|
||
|
|
.xml-importer button {
|
||
|
|
padding: 8px 16px;
|
||
|
|
background: #42b983;
|
||
|
|
color: white;
|
||
|
|
border: none;
|
||
|
|
border-radius: 4px;
|
||
|
|
cursor: pointer;
|
||
|
|
}
|
||
|
|
|
||
|
|
.xml-importer button:disabled {
|
||
|
|
background: #cccccc;
|
||
|
|
cursor: not-allowed;
|
||
|
|
}
|
||
|
|
|
||
|
|
.figure {
|
||
|
|
margin: 15px 0;
|
||
|
|
text-align: center;
|
||
|
|
}
|
||
|
|
</style>
|