mind-map/simple-mind-map/src/parse/xmind.js

329 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import JSZip from 'jszip'
import xmlConvert from 'xml-js'
import {
getTextFromHtml,
imgToDataUrl,
parseDataUrl,
getImageSize
} from '../utils/index'
// 解析.xmind文件
const parseXmindFile = file => {
return new Promise((resolve, reject) => {
JSZip.loadAsync(file).then(
async zip => {
try {
let content = ''
if (zip.files['content.json']) {
let json = await zip.files['content.json'].async('string')
content = await transformXmind(json, zip.files)
} else if (zip.files['content.xml']) {
let xml = await zip.files['content.xml'].async('string')
let json = xmlConvert.xml2json(xml)
content = transformOldXmind(json)
}
if (content) {
resolve(content)
} else {
reject(new Error('解析失败'))
}
} catch (error) {
reject(error)
}
},
e => {
reject(e)
}
)
})
}
// 转换xmind数据
const transformXmind = async (content, files) => {
let data = JSON.parse(content)[0]
let nodeTree = data.rootTopic
let newTree = {}
let waitLoadImageList = []
let walk = async (node, newNode) => {
newNode.data = {
// 节点内容
text: node.title
}
// 节点备注
if (node.notes) {
let notesData = node.notes.realHTML || node.notes.plain
newNode.data.note = notesData ? notesData.content || '' : ''
}
// 超链接
if (node.href && /^https?:\/\//.test(node.href)) {
newNode.data.hyperlink = node.href
}
// 标签
if (node.labels && node.labels.length > 0) {
newNode.data.tag = node.labels
}
// 图片
if (node.image && /\.(jpg|jpeg|png|gif|webp)$/.test(node.image.src)) {
try {
// 处理异步逻辑
let resolve = null
let promise = new Promise(_resolve => {
resolve = _resolve
})
waitLoadImageList.push(promise)
// 读取图片
let imageType = /\.([^.]+)$/.exec(node.image.src)[1]
let imageBase64 =
`data:image/${imageType};base64,` +
(await files['resources/' + node.image.src.split('/')[1]].async(
'base64'
))
newNode.data.image = imageBase64
// 如果图片尺寸不存在
if (!node.image.width && !node.image.height) {
let imageSize = await getImageSize(imageBase64)
newNode.data.imageSize = {
width: imageSize.width,
height: imageSize.height
}
} else {
newNode.data.imageSize = {
width: node.image.width,
height: node.image.height
}
}
resolve()
} catch (error) {
console.log(error)
resolve()
}
}
// 子节点
newNode.children = []
if (
node.children &&
node.children.attached &&
node.children.attached.length > 0
) {
node.children.attached.forEach(item => {
let newChild = {}
newNode.children.push(newChild)
walk(item, newChild)
})
}
}
walk(nodeTree, newTree)
await Promise.all(waitLoadImageList)
return newTree
}
// 转换旧版xmind数据xmind8
const transformOldXmind = content => {
let data = JSON.parse(content)
let elements = data.elements
let root = null
let getRoot = arr => {
for (let i = 0; i < arr.length; i++) {
if (!root && arr[i].name === 'topic') {
root = arr[i]
return
}
}
arr.forEach(item => {
getRoot(item.elements)
})
}
getRoot(elements)
let newTree = {}
let getItemByName = (arr, name) => {
return arr.find(item => {
return item.name === name
})
}
let walk = (node, newNode) => {
let nodeElements = node.elements
newNode.data = {
// 节点内容
text: getItemByName(nodeElements, 'title').elements[0].text
}
try {
// 节点备注
let notesElement = getItemByName(nodeElements, 'notes')
if (notesElement) {
newNode.data.note =
notesElement.elements[0].elements[0].elements[0].text
}
} catch (error) {
console.log(error)
}
try {
// 超链接
if (
node.attributes &&
node.attributes['xlink:href'] &&
/^https?:\/\//.test(node.attributes['xlink:href'])
) {
newNode.data.hyperlink = node.attributes['xlink:href']
}
} catch (error) {
console.log(error)
}
try {
// 标签
let labelsElement = getItemByName(nodeElements, 'labels')
if (labelsElement) {
newNode.data.tag = labelsElement.elements.map(item => {
return item.elements[0].text
})
}
} catch (error) {
console.log(error)
}
// 子节点
newNode.children = []
let _children = getItemByName(nodeElements, 'children')
if (_children && _children.elements && _children.elements.length > 0) {
_children.elements.forEach(item => {
if (item.name === 'topics') {
item.elements.forEach(item2 => {
let newChild = {}
newNode.children.push(newChild)
walk(item2, newChild)
})
} else {
let newChild = {}
newNode.children.push(newChild)
walk(item, newChild)
}
})
}
}
walk(root, newTree)
return newTree
}
// 数据转换为xmind文件
const transformToXmind = async (data, name) => {
const id = 'simpleMindMap_' + Date.now()
const imageList = []
// 转换核心数据
let newTree = {}
let waitLoadImageList = []
let walk = async (node, newNode, isRoot) => {
let newData = {
structureClass: 'org.xmind.ui.logic.right',
title: getTextFromHtml(node.data.text), // 节点文本
children: {
attached: []
}
}
// 备注
if (node.data.note !== undefined) {
newData.notes = {
realHTML: {
content: node.data.note
},
plain: {
content: node.data.note
}
}
}
// 超链接
if (node.data.hyperlink !== undefined) {
newData.href = node.data.hyperlink
}
// 标签
if (node.data.tag !== undefined) {
newData.labels = node.data.tag || []
}
// 图片
if (node.data.image) {
try {
// 处理异步逻辑
let resolve = null
let promise = new Promise(_resolve => {
resolve = _resolve
})
waitLoadImageList.push(promise)
let imgName = ''
let imgData = node.data.image
// 网络图片要先转换成data:url
if (/^https?:\/\//.test(node.data.image)) {
imgData = await imgToDataUrl(node.data.image)
}
// 从data:url中解析出图片类型和base64
let dataUrlRes = parseDataUrl(imgData)
imgName = 'image_' + imageList.length + '.' + dataUrlRes.type
imageList.push({
name: imgName,
data: dataUrlRes.base64
})
newData.image = {
src: 'xap:resources/' + imgName,
width: node.data.imageSize.width,
height: node.data.imageSize.height
}
resolve()
} catch (error) {
console.log(error)
resolve()
}
}
// 样式
// 暂时不考虑样式
if (isRoot) {
newData.class = 'topic'
newNode.id = id
newNode.class = 'sheet'
newNode.title = name
newNode.extensions = []
newNode.topicPositioning = 'fixed'
newNode.topicOverlapping = 'overlap'
newNode.coreVersion = '2.100.0'
newNode.rootTopic = newData
} else {
Object.keys(newData).forEach(key => {
newNode[key] = newData[key]
})
}
if (node.children && node.children.length > 0) {
node.children.forEach(child => {
let newChild = {}
walk(child, newChild)
newData.children.attached.push(newChild)
})
}
}
walk(data, newTree, true)
await Promise.all(waitLoadImageList)
const contentData = [newTree]
// 创建压缩包
const zip = new JSZip()
zip.file('content.json', JSON.stringify(contentData))
zip.file(
'metadata.json',
`{"modifier":"","dataStructureVersion":"1","layoutEngineVersion":"2","activeSheetId":"${id}"}`
)
const manifestData = {
'file-entries': { 'content.json': {}, 'metadata.json': {} }
}
// 图片
if (imageList.length > 0) {
imageList.forEach(item => {
manifestData['file-entries']['resources/' + item.name] = {}
const img = zip.folder('resources')
img.file(item.name, item.data, { base64: true })
})
}
zip.file('manifest.json', JSON.stringify(manifestData))
const zipData = await zip.generateAsync({ type: 'blob' })
return zipData
}
export default {
parseXmindFile,
transformXmind,
transformOldXmind,
transformToXmind
}