Files
karuo-ai/04_卡火(火)/火炬_全栈消息/读书笔记/脚本/write_to_xmind.py

425 lines
14 KiB
Python
Executable File
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.

#!/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()