sync: WIN10-I9-64G 2026-09-19

This commit is contained in:
Myfro9
2026-09-19 19:26:02 +08:00
parent 5359cf8d1f
commit da85f2bbfd
5 changed files with 985 additions and 0 deletions
+112
View File
@@ -0,0 +1,112 @@
"""drawio_read.py — 把任意 drawio 文件规整成纯 mxGraphModel XML 打到 stdout。
为什么需要它:drawio MCP 的 new_diagram 写出来的**不是**纯 mxfile XML,而是
SVG 外壳 + `content="<mxfile>..."`,里面的 diagram 段还是 base64+raw-deflate
压缩的。人手在 drawio 桌面版里存的则常常是纯 XML。两种都要能读,否则
n18(按原理图回写 drawio)和 d21(原理图与 drawio 一致性比对)根本没法比。
用法:
python drawio_read.py <drawio 文件> # 打印 mxGraphModel XML
python drawio_read.py <drawio 文件> --summary # 只打印模块/连线清单
"""
from __future__ import annotations
import argparse
import base64
import html
import re
import sys
import urllib.parse
import zlib
from pathlib import Path
for _s in (sys.stdout, sys.stderr):
try:
_s.reconfigure(encoding="utf-8", errors="replace")
except (AttributeError, ValueError):
pass
def _inflate(payload: str) -> str:
"""base64 + raw-deflate + urlencode 三层解开(drawio 的压缩 diagram 段)。"""
blob = zlib.decompress(base64.b64decode(payload), -15)
return urllib.parse.unquote(blob.decode("utf-8"))
def to_xml(path: Path) -> str:
"""返回纯 mxGraphModel/mxfile XML,无论源文件是哪种封装。"""
raw = path.read_text(encoding="utf-8", errors="replace")
# 形态 1:SVG 外壳,真内容在 content 属性里
if not raw.lstrip().startswith(("<mxfile", "<mxGraphModel")):
m = re.search(r'content="([^"]*)"', raw)
if not m:
raise SystemExit(f"{path} 既不是 mxfile,也找不到 SVG 的 content 属性")
raw = html.unescape(m.group(1))
# 形态 2:diagram 段被压缩
d = re.search(r"<diagram[^>]*>\s*([A-Za-z0-9+/=]+)\s*</diagram>", raw)
if d:
try:
return _inflate(d.group(1))
except (zlib.error, ValueError) as exc:
raise SystemExit(f"{path} 的 diagram 段解压失败:{exc}")
# 形态 3:已经是明文 XML
return raw
_CELL = re.compile(r'<mxCell\b[^>]*?id="([^"]*)"[^>]*?>', re.S)
_VAL = re.compile(r'value="([^"]*)"')
_SRC = re.compile(r'source="([^"]*)"')
_TGT = re.compile(r'target="([^"]*)"')
def summarize(xml: str) -> str:
"""列出模块与连线,供 d21 做原理图/框图一致性比对时逐条核。"""
names: dict[str, str] = {}
edges: list[tuple[str, str, str]] = []
for m in re.finditer(r"<mxCell\b.*?(?:/>|</mxCell>)", xml, re.S):
tag = m.group(0)
cid = re.search(r'id="([^"]*)"', tag)
if not cid:
continue
val = _VAL.search(tag)
label = html.unescape(val.group(1)) if val else ""
label = re.sub(r"<[^>]+>", " ", label) # 去掉 value 里的 HTML 标签
label = re.sub(r"\s*[\r\n]+\s*", " / ", label) # 多行 value 压成一行
label = re.sub(r"\s{2,}", " ", label).strip()
if 'vertex="1"' in tag:
if "edgeLabel" in tag: # 边上的 Yes/No 标签不是模块
continue
names[cid.group(1)] = label
elif 'edge="1"' in tag:
s, t = _SRC.search(tag), _TGT.search(tag)
edges.append((s.group(1) if s else "?", t.group(1) if t else "?", label))
def short(cid: str) -> str:
"""连线清单里只用模块名首段,否则整行被模块说明撑爆。"""
return names.get(cid, cid).split(" / ")[0]
lines = [f"模块 {len(names)} 个:"]
lines += [f" [{k}] {v}" for k, v in names.items()]
lines.append(f"连线 {len(edges)} 条:")
lines += [f" {short(s)} -> {short(t)}" + (f" ({lbl})" if lbl else "")
for s, t, lbl in edges]
return "\n".join(lines)
def main() -> int:
ap = argparse.ArgumentParser(description="把任意 drawio 文件规整成 mxGraphModel XML")
ap.add_argument("path", type=Path)
ap.add_argument("--summary", action="store_true", help="只打印模块/连线清单")
args = ap.parse_args()
if not args.path.is_file():
raise SystemExit(f"文件不存在:{args.path}")
xml = to_xml(args.path)
print(summarize(xml) if args.summary else xml)
return 0
if __name__ == "__main__":
sys.exit(main())