mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-21 18:37:43 +08:00
329 lines
8.7 KiB
JavaScript
329 lines
8.7 KiB
JavaScript
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
|
||
}
|