Files
skills-win/kicad-flow/scripts/drawio_read.py
T
2026-09-19 19:26:02 +08:00

113 lines
4.2 KiB
Python
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.
"""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())