大家在工作中经常需要通过思维导图来梳理自己的思路,比如通过xmind或processon等在线工具来拆解主题、问题、内容、方法等等,但思维导图软件默认形成的.xmind格式是二进制格式,如果后续需要在其他地方复用,这种格式文件复用起来特别不方便,一般情况下需要将其转换为opml(或xml)、md等格式。因此本次主要结合AI编写了一个小工具,实现xmind和opml格式这两种格式的互转,这样既可以在思维导图软件中查看编辑,也可以通过obsidian进行查看编辑,目前已经编写沉淀了50多款转换工具,其他转换工具可以参考之前的文章。本次主要内容如下,供大家参考。
一、主要功能
主要基于xml、zipfile、json等库,实现将xmind格式思维导图转换为opml或md格式功能,以及反过来的转换功能。
具体支持功能如下:
1.通过xml和json实现XMind转为OPML文件格式,详见xmind_to_opml()
2.通过xml和zipfile实现 .XMind转换为 .md 文件格式,详见xmind_to_markdown()
3.通过zipfile和json实现 .OPML转换为 .XMind文件格式,详见make_xmind()
二、实现效果
1.原始xmind导图

2.转换后的opml格式内容截图

3.转换后的markdown格式内容截图

三、核心代码
#!/root/anaconda3/envs/llm/bin/python
# -*- coding: utf-8 -*-
import xml.etree.ElementTree as ET
import zipfile
import json,sys
import uuid
from xml.sax.saxutils import escape
import os
"""
解析大纲内容
:param outline_node: 根目录大纲名称
:param topic: 输出 每一级大纲名称和id
"""
def parse_outline(outline_node):
title = outline_node.attrib.get('text', '')
topic = {
"id": str(uuid.uuid4()),
"title": title
}
children = []
for child in outline_node:
if child.tag == 'outline':
children.append(parse_outline(child))
if children:
topic["children"] = {
"attached": children
}
return topic
"""
把OPML转成XMind文件
:param opml_file: OPML文件路径 或者 OPML的XML文本字符串
:param output_file: 输出 .xmind 文件路径
"""
def make_xmind(opml_file, output_file):
# 判断是否为文件路径并读取
try:
with open(opml_file, "r", encoding="utf-8") as f:
opml_content = f.read()
except FileNotFoundError:
# 文件不存在,就当成xml字符串处理(原有逻辑)
opml_content = opml_file
root_elem = ET.fromstring(opml_content)
body_elem = root_elem.find('body')
first_outline = body_elem.find('outline')
root_topic_data = parse_outline(first_outline)
root_sheet_id = str(uuid.uuid4())
root_topic_data["id"] = root_sheet_id
content_data = [
{
"id": root_sheet_id,
"title": root_topic_data["title"],
"rootTopic": root_topic_data
}
]
manifest_data = {
"fileVersion": "v2",
"rootSheetId": root_sheet_id
}
metadata_data = {
"data": {
"creator": {
"name": "hhue",
"version": "v1.0"
}
}
}
# output_file = "咨询主题思维导图_转换自OPML.xmind"
with zipfile.ZipFile(output_file, 'w', zipfile.ZIP_DEFLATED) as zf:
zf.writestr('content.json', json.dumps(content_data, ensure_ascii=False))
zf.writestr('manifest.json', json.dumps(manifest_data, ensure_ascii=False))
zf.writestr('metadata.json', json.dumps(metadata_data, ensure_ascii=False))
print(f"Generated: {output_file}")
"""
读取 xmind 文件,返回根节点树 [(text, [children])]:param path: xmind文件路径 或者 xind文本字符串,
"""
def read_xmind_nodes(path):
z = zipfile.ZipFile(path)
names = z.namelist()
# 新版 XMind (JSON) if 'content.json' in names:
data = json.loads(z.read('content.json'))[0]
def conv(node):
text = node.get('title', '')
kids = [conv(c) for c in node.get('children', {}).get('attached', [])]
return (text, kids)
return conv(data['rootTopic'])
# 旧版 XMind (XML) elif 'content.xml' in names:
root = ET.fromstring(z.read('content.xml'))
topic = root.find('.//topic')
def conv(t):
text = t.find('title').text or ''
kids = [conv(c) for c in t.findall('topic')]
return (text, kids)
return conv(topic)
else:
raise ValueError('不是有效的 xmind 文件')
"""
组织输出opml文件的数据内容
:param node: 当前节点
"""
def to_opml(node, indent=0):
text, children = node
pad = ' ' * indent
out = [f'{pad}<outline text="{escape(text)}">']
for c in children:
out.append(to_opml(c, indent + 1))
out.append(f'{pad}</outline>')
return '\n'.join(out)
"""
把XMind转成OPML文件
:param xmind_file: .xmind 文件路径
:param opml_file: 输出,OPML文件路径 或者 OPML的XML文本字符串
"""
def xmind_to_opml(xmind_file,opml_file='output.opml'):
# xmind_file = '你的文件.xmind'
root = read_xmind_nodes(xmind_file)
opml = f'''<?xml version="1.0" encoding="UTF-8"?>
<opml version="1.0"> <head><title>{escape(root[0])}</title></head>
<body> {to_opml(root)}
</body> </opml>'''
with open(opml_file, 'w', encoding='utf-8') as f:
f.write(opml)
print('转换完成:',opml_file)
"""
递归解析 XMind 的 topic 节点,转换为 markdown 列表项
:param topic: 根主题
:param 输出: 具备层级的 markdown 列表
"""
def parse_topic(topic, level=0):
lines = []
title = topic.get("title", "").strip()
if title:
# 根据层级生成对应缩进的 Markdown 列表
indent = " " * level
lines.append(f"{indent}- {title}")
# 查找子主题 (适配不同版本的 XMind JSON 结构)
children_container = topic.get("children", {})
# 有些版本子主题在 attached 数组中
attached_children = children_container.get("attached", [])
for child in attached_children:
lines.extend(parse_topic(child, level + 1))
return lines
"""
将 .xmind 文件转换为 .md 文件
:param xmind_path: xmind文件路径
:param md_path: markdown文件路径
"""
def xmind_to_markdown(xmind_path, md_path):
if not os.path.exists(xmind_path):
print(f"错误: 找不到文件 {xmind_path}")
return
markdown_lines = []
# .xmind 文件是 zip 压缩包
with zipfile.ZipFile(xmind_path, 'r') as zf:
if "content.json" not in zf.namelist():
print("错误: 有效的 XMind 文件中未找到 content.json")
return
# 读取 content.json content_content = zf.read("content.json").decode("utf-8")
sheets = json.loads(content_content)
# 遍历每一个工作表 (Sheet) for sheet_idx, sheet in enumerate(sheets):
sheet_title = sheet.get("title", f"Sheet {sheet_idx + 1}")
markdown_lines.append(f"# {sheet_title}\n")
# 获取根主题 (rootTopic) root_topic = sheet.get("rootTopic")
if root_topic:
# 解析根主题及所有子孙节点
topic_lines = parse_topic(root_topic, level=0)
markdown_lines.extend(topic_lines)
markdown_lines.append("\n---\n")
# 写入 Markdown 文件
with open(md_path, "w", encoding="utf-8") as f:
f.write("\n".join(markdown_lines))
print(f"转换成功!Markdown 文件已保存至: {md_path}")
# 运行示例:
# python xml2Xmind.py E:\导图.xmind E:\导图.opml xmind2opml
# python xml2Xmind.py E:\导图.xmind E:\导图.md xmind2md
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法: python xml2Xmind.py <xml文件路径> [输出xind文件路径] [类型选项]")
print("\n示例:")
print(" python xml2Xmind.py book.xml book.xmind opml2xmind ")
sys.exit
in_path = sys.argv[1]
output_path = sys.argv[2]
type = sys.argv[3]
if type == 'opml2xmind':
# xml-->xmind
make_xmind(in_path, output_path)
print('xml-->xmind转换完成...')
elif type == 'xmind2opml':
# xmind-->xml
xmind_to_opml(in_path,output_path)
print('xmind-->xml转换完成...')
elif type == 'xmind2md':
# xmind-->md
xmind_to_markdown(in_path, output_path)
print('xmind-->md转换完成...')
else:
print("无效的类型选项,请使用 'opml2xmind'、'xmind2opml' 或 'xmind2md'")