This commit is contained in:
2025-07-28 17:01:13 +08:00
parent f88caa57e2
commit 05b849470f
5 changed files with 508 additions and 198 deletions

View File

@@ -1,209 +1,95 @@
<template>
<div class="xml-importer">
<input type="file" @change="handleFileUpload" accept=".xml" />
<button @click="importToEditor" :disabled="!xmlData">导入到编辑器</button>
<div ref="editor"></div>
<div>
<div ref="editor" style="text-align:left"></div>
<button @click="exportToXml">导出为XML</button>
<input type="file" accept=".xml" @change="handleXmlUpload">
</div>
</template>
<script>
import E from 'wangeditor'
import E from 'wangeditor';
import { js2xml, xml2js } from 'xml-js';
export default {
name: 'XmlImporter',
data() {
return {
editor: null,
xmlData: null,
parsedContent: ''
}
editor: null
};
},
mounted() {
this.initEditor()
this.editor = new E(this.$refs.editor);
this.editor.create();
},
methods: {
initEditor() {
this.editor = new E(this.$refs.editor)
this.editor.create()
},
// 处理XML文件上传
handleFileUpload(event) {
const file = event.target.files[0]
if (!file) return
convertHtmlToXml(htmlContent) {
const xmlObject = {
declaration: {
attributes: {
version: '1.0',
encoding: 'UTF-8'
}
},
elements: [{
type: 'element',
name: 'richText',
elements: [{
type: 'text',
text: htmlContent
}]
}]
};
const reader = new FileReader()
reader.onload = (e) => {
this.xmlData = e.target.result
this.parseS1000DXml(this.xmlData)
}
reader.readAsText(file)
return js2xml(xmlObject, { compact: false, spaces: 4 });
},
// 解析S1000D XML
parseS1000DXml(xmlString) {
downloadXml(content, fileName = 'content.xml') {
const blob = new Blob([content], { type: 'application/xml' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = fileName;
link.click();
URL.revokeObjectURL(link.href);
},
exportToXml() {
const html = this.editor.txt.html();
const xml = this.convertHtmlToXml(html);
this.downloadXml(xml);
},
parseXmlToHtml(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 result = xml2js(xmlString, { compact: false });
const richTextElement = result.elements.find(el => el.name === 'richText');
if (richTextElement && richTextElement.elements && richTextElement.elements.length > 0) {
return richTextElement.elements[0].text;
}
// 提取内容部分
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}`)
return '';
} catch (e) {
console.error('XML解析错误:', e);
return '';
}
},
// 将S1000D元素转换为HTML
s1000dToHtml(s1000dElement) {
let html = ''
handleXmlUpload(event) {
const file = event.target.files[0];
if (!file) return;
// 递归处理子节点
const processNode = (node) => {
if (node.nodeType === Node.TEXT_NODE) {
return node.textContent
const reader = new FileReader();
reader.onload = (e) => {
const xmlString = e.target.result;
const html = this.parseXmlToHtml(xmlString);
if (html) {
this.editor.txt.html(html);
}
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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
},
// 将解析后的内容导入编辑器
importToEditor() {
if (this.parsedContent && this.editor) {
this.editor.txt.html(this.parsedContent)
alert('内容已成功导入编辑器')
}
};
reader.readAsText(file);
}
},
beforeDestroy() {
if (this.editor) {
this.editor.destroy()
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>
};
</script>