425 lines
14 KiB
Python
Executable File
425 lines
14 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
读书笔记写入 XMind 脚本(五行模板格式 v2)
|
||
中心红色 + 五行橙色浮动节点 + 五方位布局
|
||
|
||
用法:
|
||
python write_to_xmind.py "书名" "作者" "分类" [--test]
|
||
|
||
示例:
|
||
python write_to_xmind.py "厚黑学" "李宗吾" "商业思维"
|
||
"""
|
||
|
||
import json
|
||
import os
|
||
import shutil
|
||
import zipfile
|
||
import uuid
|
||
import sys
|
||
import random
|
||
import string
|
||
from datetime import datetime
|
||
|
||
XMIND_PATH = "/Users/karuo/Documents/我的脑图/5 学习/读书笔记.xmind"
|
||
|
||
CATEGORIES = {
|
||
"个人提升": "一、个人提升",
|
||
"人际关系": "二、人际关系",
|
||
"创业": "三、创业",
|
||
"商业思维": "四、商业思维",
|
||
"投资": "五、投资"
|
||
}
|
||
|
||
WUXING_THEME = {
|
||
"subTopic": {
|
||
"id": str(uuid.uuid4()),
|
||
"properties": {
|
||
"fo:font-size": "11pt",
|
||
"fo:color": "#434B54",
|
||
"fo:text-align": "left"
|
||
}
|
||
},
|
||
"summary": {
|
||
"id": str(uuid.uuid4()),
|
||
"properties": {"line-color": "#F0B67F"}
|
||
},
|
||
"boundary": {
|
||
"id": str(uuid.uuid4()),
|
||
"properties": {
|
||
"fo:font-size": "14pt", "fo:font-weight": "700",
|
||
"fo:font-style": "normal", "fo:color": "#F0B67F",
|
||
"svg:fill": "#FEF1E4", "line-color": "#F0B67F"
|
||
}
|
||
},
|
||
"importantTopic": {
|
||
"id": str(uuid.uuid4()),
|
||
"properties": {
|
||
"fo:font-weight": "bold", "fo:color": "#FFFFFF", "svg:fill": "#FF4600"
|
||
}
|
||
},
|
||
"calloutTopic": {
|
||
"id": str(uuid.uuid4()),
|
||
"properties": {
|
||
"fo:font-size": "14pt", "fo:font-weight": "600",
|
||
"fo:font-style": "normal", "fo:color": "#775D44",
|
||
"svg:fill": "#F0B67F", "border-line-width": "0"
|
||
}
|
||
},
|
||
"centralTopic": {
|
||
"id": str(uuid.uuid4()),
|
||
"properties": {
|
||
"fo:font-size": "20pt", "fo:font-weight": "600",
|
||
"fo:font-style": "normal", "svg:fill": "#e4705c",
|
||
"line-color": "#434B54", "border-line-width": "0"
|
||
}
|
||
},
|
||
"mainTopic": {
|
||
"id": str(uuid.uuid4()),
|
||
"properties": {
|
||
"fo:font-size": "14pt", "fo:color": "#FFFFFF",
|
||
"svg:fill": "#434B54", "line-width": "1pt",
|
||
"border-line-width": "0",
|
||
"line-class": "org.xmind.branchConnection.curve"
|
||
}
|
||
},
|
||
"floatingTopic": {
|
||
"id": str(uuid.uuid4()),
|
||
"properties": {
|
||
"fo:font-weight": "600", "fo:font-style": "normal",
|
||
"fo:color": "#775D44", "svg:fill": "#F0B67F",
|
||
"line-width": "1pt", "line-color": "#F0B67F",
|
||
"border-line-color": "#F0B67F", "border-line-width": "0",
|
||
"line-class": "org.xmind.branchConnection.curve"
|
||
}
|
||
},
|
||
"summaryTopic": {
|
||
"id": str(uuid.uuid4()),
|
||
"properties": {
|
||
"fo:font-weight": "600", "fo:font-style": "normal",
|
||
"fo:color": "#775D44", "svg:fill": "#F0B67F",
|
||
"line-width": "1pt", "line-color": "#F0B67F",
|
||
"border-line-color": "#F0B67F", "border-line-width": "2pt",
|
||
"line-class": "org.xmind.branchConnection.curve"
|
||
}
|
||
},
|
||
"relationship": {
|
||
"id": str(uuid.uuid4()),
|
||
"properties": {
|
||
"fo:font-weight": "600", "fo:font-style": "normal",
|
||
"fo:color": "#F0B67F", "line-width": "3pt",
|
||
"line-color": "#F0B67F", "line-pattern": "solid"
|
||
}
|
||
}
|
||
}
|
||
|
||
WUXING_POSITIONS = {
|
||
"金": {"x": -8, "y": -252},
|
||
"水": {"x": 271, "y": -113},
|
||
"木": {"x": 196, "y": 191},
|
||
"火": {"x": -175, "y": 197},
|
||
"土": {"x": -260, "y": -83},
|
||
}
|
||
|
||
WUXING_DESCRIPTIONS = {
|
||
"金": "定位与角色:是谁、给谁、站在什么位置上",
|
||
"水": "经历与路径:事情是怎么发生的",
|
||
"木": "方法与产出:具体怎么干、能产出什么",
|
||
"火": "认知与判断:为什么这么想、怎么判断对错",
|
||
"土": "系统与沉淀:如何长期稳定、不崩盘",
|
||
}
|
||
|
||
ELEMENT_KEY_MAP = {
|
||
"金": "gold", "水": "water", "木": "wood", "火": "fire", "土": "earth"
|
||
}
|
||
|
||
|
||
def gen_id():
|
||
chars = string.ascii_lowercase + string.digits
|
||
return ''.join(random.choice(chars) for _ in range(26))
|
||
|
||
|
||
def _make_element_node(name, items, note_text=""):
|
||
"""创建五行浮动节点(detached 定位模式)"""
|
||
children = []
|
||
if WUXING_DESCRIPTIONS.get(name):
|
||
children.append({"id": gen_id(), "title": WUXING_DESCRIPTIONS[name]})
|
||
for item in items:
|
||
children.append({"id": gen_id(), "title": item})
|
||
|
||
node = {
|
||
"id": gen_id(),
|
||
"title": name,
|
||
"position": WUXING_POSITIONS[name],
|
||
"children": {"attached": children} if children else {}
|
||
}
|
||
if note_text:
|
||
node["notes"] = {"plain": {"content": note_text}}
|
||
return node
|
||
|
||
|
||
def create_book_sheet(book_name, author, note_data=None):
|
||
"""
|
||
创建书籍标签页(五行模板格式 v2)
|
||
中心红色节点 + 五行橙色浮动节点(detached)+ 补充信息(attached)
|
||
"""
|
||
sheet_id = str(uuid.uuid4())
|
||
root_id = gen_id()
|
||
|
||
if note_data is None:
|
||
note_data = {
|
||
"summary": "待填写一句话总结",
|
||
"gold": ["金-1:待填写", "金-2:待填写", "金-3:待填写", "金-4:待填写"],
|
||
"water": ["水-1:待填写", "水-2:待填写", "水-3:待填写", "水-4:待填写"],
|
||
"wood": ["木-1:待填写", "木-2:待填写", "木-3:待填写", "木-4:待填写"],
|
||
"fire": ["火-1:待填写", "火-2:待填写", "火-3:待填写", "火-4:待填写"],
|
||
"earth": ["土-1:待填写", "土-2:待填写", "土-3:待填写", "土-4:待填写"],
|
||
"questions": [], "characters": [], "quotes": [],
|
||
"keywords": [], "process": "", "rules": ""
|
||
}
|
||
|
||
detached_nodes = []
|
||
for name in ["金", "水", "木", "火", "土"]:
|
||
key = ELEMENT_KEY_MAP[name]
|
||
items = note_data.get(key, [])
|
||
detached_nodes.append(_make_element_node(name, items))
|
||
|
||
attached_nodes = [
|
||
{
|
||
"id": gen_id(), "title": "一句话总结",
|
||
"children": {"attached": [
|
||
{"id": gen_id(), "title": note_data.get("summary", "待填写")}
|
||
]}
|
||
},
|
||
{
|
||
"id": gen_id(), "title": "问题与解答",
|
||
"children": {"attached": [
|
||
{"id": gen_id(), "title": q}
|
||
for q in (note_data.get("questions") or ["待填写"])
|
||
]}
|
||
},
|
||
{
|
||
"id": gen_id(), "title": "人物分析",
|
||
"children": {"attached": [
|
||
{"id": gen_id(), "title": c}
|
||
for c in (note_data.get("characters") or ["待填写"])
|
||
]}
|
||
},
|
||
{
|
||
"id": gen_id(), "title": "金句与关键词",
|
||
"children": {"attached": [
|
||
{"id": gen_id(), "title": "金句", "children": {"attached": [
|
||
{"id": gen_id(), "title": q}
|
||
for q in (note_data.get("quotes") or ["待填写"])
|
||
]}},
|
||
{"id": gen_id(), "title": "关键词", "children": {"attached": [
|
||
{"id": gen_id(), "title": k}
|
||
for k in (note_data.get("keywords") or ["待填写"])
|
||
]}}
|
||
]}
|
||
},
|
||
{
|
||
"id": gen_id(), "title": "流程图示",
|
||
"children": {"attached": [
|
||
{"id": gen_id(), "title": note_data.get("process") or "待填写"}
|
||
]}
|
||
},
|
||
{
|
||
"id": gen_id(), "title": "使用规则",
|
||
"children": {"attached": [
|
||
{"id": gen_id(), "title": note_data.get("rules") or "待填写"}
|
||
]}
|
||
},
|
||
]
|
||
|
||
sheet = {
|
||
"id": sheet_id,
|
||
"class": "sheet",
|
||
"title": f"《{book_name}》- {author}",
|
||
"theme": WUXING_THEME,
|
||
"rootTopic": {
|
||
"id": root_id,
|
||
"class": "topic",
|
||
"title": f"《{book_name}》\n{author}",
|
||
"structureClass": "org.xmind.ui.map.clockwise",
|
||
"extensions": [
|
||
{
|
||
"provider": "org.xmind.ui.map.unbalanced",
|
||
"content": [{"name": "right-number", "content": "-1"}]
|
||
}
|
||
],
|
||
"children": {
|
||
"attached": attached_nodes,
|
||
"detached": detached_nodes,
|
||
}
|
||
}
|
||
}
|
||
|
||
return sheet, sheet_id, root_id
|
||
|
||
def add_link_to_category(topics, category_title, book_name, author, sheet_id):
|
||
"""
|
||
在主图的指定分类下添加书籍链接
|
||
|
||
Args:
|
||
topics: XMind topics 列表
|
||
category_title: 分类标题(如"三、创业")
|
||
book_name: 书名
|
||
author: 作者
|
||
sheet_id: 目标标签页 ID
|
||
|
||
Returns:
|
||
bool: 是否成功添加
|
||
"""
|
||
for topic in topics:
|
||
if topic.get('title') == category_title:
|
||
if 'children' not in topic:
|
||
topic['children'] = {'attached': []}
|
||
if 'attached' not in topic['children']:
|
||
topic['children']['attached'] = []
|
||
|
||
# 检查是否已存在
|
||
link_title = f"《{book_name}》- {author}"
|
||
for child in topic['children']['attached']:
|
||
if child.get('title') == link_title:
|
||
print(f"⚠️ 链接已存在: {link_title}")
|
||
return True
|
||
|
||
# 添加链接
|
||
topic['children']['attached'].append({
|
||
"id": gen_id(),
|
||
"title": link_title,
|
||
"href": f"xmind:#{sheet_id}"
|
||
})
|
||
print(f"✅ 已添加链接: {link_title} → {category_title}")
|
||
return True
|
||
|
||
# 递归搜索子节点
|
||
children = topic.get('children', {}).get('attached', [])
|
||
if add_link_to_category(children, category_title, book_name, author, sheet_id):
|
||
return True
|
||
|
||
return False
|
||
|
||
def write_book_to_xmind(book_name, author, category, note_data=None, test_mode=False):
|
||
"""
|
||
将书籍笔记写入 XMind 文件
|
||
|
||
Args:
|
||
book_name: 书名
|
||
author: 作者
|
||
category: 分类(个人提升/人际关系/创业/商业思维/投资)
|
||
note_data: 笔记数据字典(可选)
|
||
test_mode: 测试模式,不实际写入文件
|
||
|
||
Returns:
|
||
bool: 是否成功
|
||
"""
|
||
# 验证分类
|
||
if category not in CATEGORIES:
|
||
print(f"❌ 无效分类: {category}")
|
||
print(f" 可用分类: {', '.join(CATEGORIES.keys())}")
|
||
return False
|
||
|
||
category_title = CATEGORIES[category]
|
||
|
||
# 检查 XMind 文件
|
||
if not os.path.exists(XMIND_PATH):
|
||
print(f"❌ XMind 文件不存在: {XMIND_PATH}")
|
||
return False
|
||
|
||
# 创建临时目录
|
||
tmp_dir = f"/tmp/xmind_edit_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||
os.makedirs(tmp_dir, exist_ok=True)
|
||
|
||
try:
|
||
# 解压 XMind 文件
|
||
print(f"📦 解压 XMind 文件...")
|
||
with zipfile.ZipFile(XMIND_PATH, 'r') as zf:
|
||
zf.extractall(tmp_dir)
|
||
|
||
# 读取 content.json
|
||
content_path = os.path.join(tmp_dir, 'content.json')
|
||
with open(content_path, 'r', encoding='utf-8') as f:
|
||
content = json.load(f)
|
||
|
||
# 创建书籍标签页
|
||
print(f"📝 创建标签页: 《{book_name}》- {author}")
|
||
book_sheet, sheet_id, root_id = create_book_sheet(book_name, author, note_data)
|
||
|
||
# 检查是否已存在同名标签页
|
||
sheet_title = f"《{book_name}》- {author}"
|
||
for sheet in content:
|
||
if sheet.get('title') == sheet_title:
|
||
print(f"⚠️ 标签页已存在: {sheet_title}")
|
||
return False
|
||
|
||
# 添加标签页
|
||
content.append(book_sheet)
|
||
|
||
# 在主图添加链接
|
||
main_sheet = content[0]
|
||
main_topics = main_sheet.get('rootTopic', {}).get('children', {}).get('attached', [])
|
||
|
||
# 链接应该指向 rootTopic.id 而不是 sheet.id
|
||
if not add_link_to_category(main_topics, category_title, book_name, author, root_id):
|
||
print(f"⚠️ 未找到分类: {category_title}")
|
||
|
||
if test_mode:
|
||
print(f"\n🧪 测试模式 - 不写入文件")
|
||
print(f" 将添加标签页: {sheet_title}")
|
||
print(f" 将添加到分类: {category_title}")
|
||
return True
|
||
|
||
# 写回 content.json
|
||
with open(content_path, 'w', encoding='utf-8') as f:
|
||
json.dump(content, f, ensure_ascii=False, indent=2)
|
||
|
||
# 重新打包 XMind 文件
|
||
print(f"📦 重新打包 XMind 文件...")
|
||
|
||
# 创建新的 XMind 文件(不再备份)
|
||
with zipfile.ZipFile(XMIND_PATH, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||
for root, dirs, files in os.walk(tmp_dir):
|
||
for file in files:
|
||
file_path = os.path.join(root, file)
|
||
arcname = os.path.relpath(file_path, tmp_dir)
|
||
zf.write(file_path, arcname)
|
||
|
||
print(f"\n✅ 成功写入 XMind!")
|
||
print(f" 标签页: {sheet_title}")
|
||
print(f" 分类: {category_title}")
|
||
print(f" 文件: {XMIND_PATH}")
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"❌ 错误: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
finally:
|
||
# 清理临时目录
|
||
if os.path.exists(tmp_dir):
|
||
shutil.rmtree(tmp_dir)
|
||
|
||
def main():
|
||
"""命令行入口"""
|
||
if len(sys.argv) < 4:
|
||
print("用法: python write_to_xmind.py <书名> <作者> <分类> [--test]")
|
||
print("分类: 个人提升 | 人际关系 | 创业 | 商业思维 | 投资")
|
||
print("\n示例:")
|
||
print(' python write_to_xmind.py "厚黑学" "李宗吾" "商业思维"')
|
||
print(' python write_to_xmind.py "原则" "瑞·达利欧" "投资" --test')
|
||
sys.exit(1)
|
||
|
||
book_name = sys.argv[1]
|
||
author = sys.argv[2]
|
||
category = sys.argv[3]
|
||
test_mode = "--test" in sys.argv
|
||
|
||
write_book_to_xmind(book_name, author, category, test_mode=test_mode)
|
||
|
||
if __name__ == '__main__':
|
||
main()
|