@ljheee/xmind-parser
v1.1.3
Published
Full-fidelity bidirectional converter between XMind (.xmind) and KityMinder (.km) JSON formats. Supports XMind 8 (XML) and XMind 2020+ (JSON), covering hyperlinks, notes, images, labels, markers, priority, progress, and styles.
Maintainers
Readme
xmind-parser
XMind ↔ KityMinder JSON 完整双向转换库。
支持 XMind 8(XML 格式)和 XMind 2020+(JSON 格式)两个大版本,字段覆盖超链接、备注、图片、标签、优先级、进度、样式等,反向转换生成合法 ZIP 结构,浏览器和 Node.js 双端兼容,零额外依赖(Node.js 环境需要 @xmldom/xmldom)。
安装
npm install @ljheee/xmind-parserNode.js 环境解析 XMind 8 XML 时需要:
npm install @xmldom/xmldom快速上手
Node.js
import { xmindToKm, kmToXmind } from '@ljheee/xmind-parser';
// XMind → KityMinder(自动检测 XMind 8 / 2020+)
const sheets = await xmindToKm('./my-map.xmind');
console.log(sheets[0].root.data.text); // 根节点文本
// KityMinder → XMind 2020+
await kmToXmind(sheets[0], './output.xmind', { format: 'xmind2020' });
// KityMinder → XMind 8
await kmToXmind(sheets[0], './output-v8.xmind', { format: 'xmind8' });浏览器
import { xmindBufferToKm, kmToXmindBuffer, downloadArrayBuffer } from '@ljheee/xmind-parser';
// 读取用户上传的文件
const file = document.querySelector('input[type=file]').files[0];
const buffer = await file.arrayBuffer();
// 解析
const sheets = await xmindBufferToKm(buffer);
console.log(sheets[0].root.data.text);
// 导出并下载
const outBuffer = kmToXmindBuffer(sheets[0], { format: 'xmind2020' });
downloadArrayBuffer(outBuffer, 'output.xmind');API
高层 API
| 函数 | 说明 | 环境 |
|------|------|------|
| xmindToKm(filePath, options?) | 从文件路径读取并解析 | Node.js |
| kmToXmind(kmData, outputPath, options?) | 写入 .xmind 文件 | Node.js |
| xmindBufferToKm(buffer, options?) | 从 ArrayBuffer 解析 | 双端 |
| kmToXmindBuffer(kmData, options?) | 转换为 ArrayBuffer | 双端 |
| downloadArrayBuffer(buffer, filename) | 触发浏览器下载 | 浏览器 |
options 参数:
format:'xmind2020'(默认)或'xmind8',控制输出格式firstSheetOnly:true时只返回第一个画布
低层 API
import {
parseXmind8Xml, // content.xml 字符串 → KM 数组
parseXmind2020Json, // content.json 字符串/数组 → KM 数组
kmToXmind8, // KM → XMind 8 ZIP ArrayBuffer
kmToXmind2020, // KM → XMind 2020+ ZIP ArrayBuffer
} from '@ljheee/xmind-parser';KityMinder JSON 数据结构
{
root: {
data: {
text: "节点文本", // 必填
hyperlink: "https://...", // 超链接
note: "备注内容", // 备注
label: ["标签1", "标签2"], // 标签数组
priority: 1, // 优先级 1-9
progress: 3, // 进度 1-10(见下表)
markers: ["flag-red"], // 其他标记 ID
image: "xap:resources/img.png", // 图片路径字符串
imageSize: { width: 200, height: 150 }, // 图片尺寸(可选)
style: {
"xmind-style-id": "xxx", // XMind 样式 ID(保留字段)
color: "#FF0000",
background: "#FFFF00",
fontSize: "14pt",
fontWeight: "bold",
fontStyle: "italic",
lineColor: "#0000FF",
lineWidth: "2",
shapeClass: "org.xmind.topicShape.roundedRect",
},
expandState: "collapse", // 折叠状态
// 以下为 XMind 保留字段,往返转换时保持原样
"xmind-structure": "org.xmind.ui.logic.right",
"xmind-detached": true, // 浮动节点(有损标记)
"xmind-summary": true, // 概要节点(有损标记)
},
children: [ /* 递归 KmNode */ ],
},
template: "default",
theme: "fresh-blue",
version: "1.4.43",
title: "画布名称",
}字段支持矩阵
XMind → KityMinder
| 字段 | XMind 8 (XML) | XMind 2020+ (JSON) | KityMinder 字段 | 说明 |
|------|:---:|:---:|------|------|
| 节点文本 | ✅ | ✅ | data.text | |
| 超链接 | ✅ | ✅ | data.hyperlink | 含 xmind:# 内部链接 |
| 备注 | ✅ | ✅ | data.note | 纯文本;HTML 备注自动去标签 |
| 标签 | ✅ | ✅ | data.label[] | |
| 优先级 | ✅ | ✅ | data.priority | priority-1~priority-9 → 1~9 |
| 进度 | ✅ | ✅ | data.progress | 见进度映射表 |
| 其他标记 | ✅ | ✅ | data.markers[] | 原始 markerId 字符串 |
| 图片 | ✅ | ✅ | data.image + data.imageSize | image 为路径字符串,imageSize 为 {width, height} |
| 样式 ID | ✅ | ✅ | data.style['xmind-style-id'] | |
| 样式属性 | ❌ | ✅ | data.style.* | XMind 8 样式在 styles.xml,暂不解析 |
| 折叠状态 | ✅ | ✅ | data.expandState | "collapse" |
| 结构类型 | ✅ | ✅ | data['xmind-structure'] | 保留字段 |
| 浮动节点 | ✅ | ✅ | data['xmind-detached'] | 有损:位置信息丢失 |
| 概要节点 | ❌ | ✅ | data['xmind-summary'] | 有损:范围信息丢失 |
| 多画布 | ✅ | ✅ | 返回数组 | |
| 关系连线 | ❌ | ❌ | — | 有损:暂不支持 |
KityMinder → XMind
| KityMinder 字段 | XMind 8 (XML) | XMind 2020+ (JSON) | 说明 |
|------|:---:|:---:|------|
| data.text | ✅ | ✅ | |
| data.hyperlink | ✅ | ✅ | |
| data.note | ✅ | ✅ | XMind 8 同时写 plain + xhtml |
| data.label[] | ✅ | ✅ | |
| data.priority | ✅ | ✅ | |
| data.progress | ✅ | ✅ | |
| data.markers[] | ✅ | ✅ | |
| data.image | ✅ | ✅ | |
| data.style | ✅ | ✅ | XMind 2020 同时写 id + properties |
| data.expandState | ✅ | ✅ | |
| data['xmind-structure'] | ✅ | ✅ | |
| data['xmind-detached'] | ✅ | ✅ | 生成 type="detached" 分组 |
| data['xmind-summary'] | ❌ | ✅ | XMind 8 不支持 summary 分组 |
进度标记映射
| XMind markerId | KityMinder progress | 含义 |
|---|:---:|---|
| task-start | 1 | 未开始 (0%) |
| task-oct | 2 | 12.5% |
| task-quarter | 3 | 25% |
| task-3oct | 4 | 37.5% |
| task-half | 5 | 50% |
| task-5oct | 6 | 62.5% |
| task-3quar | 7 | 75% |
| task-7oct | 8 | 87.5% |
| task-done | 9 | 完成 (100%) |
| task-pause | 10 | 暂停 |
有损转换说明
"有损"的含义:转换后再转回来,得到的结果与原始文件不完全一致。下面逐条说明每处丢失的具体内容和根本原因。
XMind → KityMinder
关系连线(relationships)—— 完全丢失
XMind 支持在任意两个节点之间画一条带标签的连线(<relationship>),这是独立于树形结构之外的图结构数据。KityMinder 的数据模型是纯树,没有"节点间连线"这个概念,因此无处存放,转换时直接丢弃。
浮动节点的坐标位置(detached topic position)—— 节点保留,位置丢失
XMind 的浮动主题不挂在树上,而是用绝对坐标定位(XMind 8 用 svg:x/svg:y 属性,XMind 2020+ 用 position.x/position.y)。KityMinder 是自动布局引擎,所有节点位置由算法计算,没有"绝对坐标"字段。节点的文本、备注、标签等内容字段会完整保留,并打上 xmind-detached: true 标记,但坐标信息无法映射,反向写回 XMind 时浮动节点会出现在默认位置而非原位。
概要节点的覆盖范围(summary range)—— 节点保留,范围丢失
XMind 的概要节点(summary topic)会记录它概括的是父节点下第几个到第几个子节点(startIndex/endIndex)。KityMinder 没有"概要"这种节点类型,也没有存储范围索引的字段。节点本身的文本内容会保留并打上 xmind-summary: true 标记,但"它概括哪几个兄弟节点"这一语义信息丢失,反向写回 XMind 2020+ 时会生成一个没有范围绑定的孤立概要节点。
XMind 8 的样式属性(styles.xml)—— 只保留 ID,不解析属性值
XMind 8 把所有样式(颜色、字体、线条粗细、节点形状等)集中存放在 ZIP 内的 styles.xml 文件中,节点本身只记录一个 style-id 引用。本库目前只读取并保留这个 style-id(存入 data.style['xmind-style-id']),不去解析 styles.xml 的具体属性值。原因是 styles.xml 的 XSL-FO 格式较复杂,且 KityMinder 的样式字段与之不完全对应,解析后也难以无损还原。XMind 2020+ 的样式是内联在节点 JSON 里的,不受此限制,属性值完整支持。
图片的二进制内容 —— 路径保留,文件不提取
XMind 把图片文件打包在 ZIP 内的 attachments/(XMind 8)或 resources/(XMind 2020+)目录下,节点里只存路径引用(如 xap:attachments/abc123.png)。本库只读取这个路径字符串,不从 ZIP 中提取图片的二进制数据。如果你只使用转换后的 KityMinder JSON,图片路径会指向一个不存在的本地文件,图片无法显示。如需图片,需要自行从原始 ZIP 中提取 attachments/ 目录的内容。
KityMinder → XMind
KityMinder 的布局与主题信息 —— 完全丢失
KityMinder JSON 顶层的 template(布局模板,如 "fish-bone"、"right")和 theme(配色主题,如 "fresh-blue")描述的是整张脑图的视觉风格。XMind 有自己独立的主题和布局体系,两者的枚举值和语义不对应,无法直接映射。生成的 XMind 文件打开后会使用 XMind 的默认布局和主题。
概要节点写入 XMind 8 —— 节点被跳过
XMind 8 的 ZIP 格式(content.xml)没有 type="summary" 的 topics 分组,概要节点在 XMind 8 的数据模型中是通过 <summaries> 元素挂在 sheet 级别实现的,结构与普通子节点完全不同,本库暂未实现这一写入逻辑。因此,标记了 xmind-summary: true 的节点在生成 XMind 8 文件时会被忽略。XMind 2020+ 格式支持 children.summary 分组,不受此限制。
与 xmindparser 的对比
| 特性 | xmindparser | xmind-parser | |------|:---:|:---:| | XMind 8 (XML) 解析 | ✅ | ✅ | | XMind 2020+ (JSON) 解析 | ✅ | ✅ | | 反向转换(KM → XMind) | ✅ | ✅ | | 超链接 | ❌ | ✅ | | 备注 | 部分 | ✅ | | 图片 | ❌ | ✅ | | 标签 | ❌ | ✅ | | 优先级标记 | ❌ | ✅ | | 进度标记 | ❌ | ✅ | | 其他标记 | ❌ | ✅ | | 样式属性 | ❌ | ✅(2020+) | | 折叠状态 | ❌ | ✅ | | 浮动节点 | ❌ | ✅(有损) | | 概要节点 | ❌ | ✅(有损) | | 多画布 | ✅ | ✅ | | 浏览器兼容 | ✅ | ✅ | | 生成合法 ZIP | ✅ | ✅ | | 零依赖(浏览器) | ✅ | ✅ |
项目结构
xmind-parser/
├── src/
│ ├── index.js # 统一入口,高层 API
│ ├── constants.js # 标记映射、命名空间常量
│ ├── utils.js # ZIP 读写、XML 工具、文件 I/O
│ ├── xmind8-to-km.js # XMind 8 (XML) → KityMinder
│ ├── xmind2020-to-km.js # XMind 2020+ (JSON) → KityMinder
│ ├── km-to-xmind8.js # KityMinder → XMind 8 (XML)
│ └── km-to-xmind2020.js # KityMinder → XMind 2020+ (JSON)
├── test/
│ ├── index.test.js # 测试套件(103 个测试用例)
│ └── fixtures/
│ ├── sample-xmind8.xml # XMind 8 测试数据
│ └── sample-xmind2020.json # XMind 2020+ 测试数据
└── package.json运行测试
npm test输出示例:
📋 XMind 8 (XML) → KityMinder JSON
✅ 解析成功,返回数组
✅ 超链接 (xlink:href)
...(20 个测试)
📋 XMind 2020+ (JSON) → KityMinder JSON
✅ 解析成功,返回数组
...(17 个测试)
📊 测试结果: 103 通过, 0 失败
🎉 所有测试通过!License
MIT
