feat(xmind): 思维导图增加「系统名称」根层级,根主题为运八网络货运平台

- export_XMind.py 新增最外层系统名称根主题:默认从项目画像「项目名称」解析,可用 --system-name 覆盖;原需求主题下沉为第二层
- 三个需求(安徽运八/物流公司管理/运单资料文件包下载)的 XMind 全部按新结构重新生成
- .gitignore 恢复远程规范忽略项(.idea/、.vscode/、output/node_modules/、~$*),并增补 output/excel_reports/、.pytest_cache/、xst/
This commit is contained in:
xst
2026-09-29 10:03:24 +08:00
parent 901b6741ff
commit 1a53a1f537
5 changed files with 44 additions and 8 deletions
+37 -8
View File
@@ -26,6 +26,22 @@ MANIFEST_NS = "urn:xmind:xmap:xmlns:manifest:1.0"
SEPARATOR_RE = re.compile(r"^\|\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$")
# XMind 最外层根主题:系统名称(默认取项目画像,取不到则用默认值)
DEFAULT_SYSTEM_NAME = "运八网络货运平台"
PROJECT_PROFILE = REPO_ROOT / "knowledge_base" / "00_project" / "project_profile.md"
def resolve_system_name() -> str:
"""解析系统名称:优先取项目画像的「项目名称」,失败则回退默认值。"""
try:
text = PROJECT_PROFILE.read_text(encoding="utf-8")
except OSError:
return DEFAULT_SYSTEM_NAME
match = re.search(r"项目名称\*{0,2}[::]\s*([^((\n]+)", text)
if not match:
return DEFAULT_SYSTEM_NAME
return match.group(1).strip() or DEFAULT_SYSTEM_NAME
def _new_id():
return str(uuid.uuid4())
@@ -99,7 +115,8 @@ def normalize_text(value: str) -> str:
return re.sub(r"<br\s*/?>", "\n", value or "").replace("\\n", "\n").strip()
def build_xmind_content(headers: list[str], rows: list[list[str]], topic_name: str) -> bytes:
def build_xmind_content(headers: list[str], rows: list[list[str]], topic_name: str,
system_name: str = "") -> bytes:
ts = _now_ms()
nsmap = {None: XMAP_NS, "fo": FO_NS, "svg": SVG_NS, "xhtml": XHTML_NS, "xlink": XLINK_NS}
@@ -115,10 +132,16 @@ def build_xmind_content(headers: list[str], rows: list[list[str]], topic_name: s
root_topic = _el("topic", parent=sheet, id=_new_id(), timestamp=ts,
**{"structure-class": "org.xmind.ui.logic.right"})
_el("title", parent=root_topic).text = topic_name
_el("title", parent=root_topic).text = system_name or DEFAULT_SYSTEM_NAME
rc = _el("children", parent=root_topic)
rt = _el("topics", parent=rc, type="attached")
# 第二层:需求名称
need_topic = _el("topic", parent=rt, id=_new_id(), timestamp=ts)
_el("title", parent=need_topic).text = topic_name
nc = _el("children", parent=need_topic)
nt = _el("topics", parent=nc, type="attached")
# 按模块路径分组
grouped: dict[str, list[dict[str, str]]] = {}
for row in rows:
@@ -127,7 +150,7 @@ def build_xmind_content(headers: list[str], rows: list[list[str]], topic_name: s
grouped.setdefault(module, []).append(case)
for module_name, cases in grouped.items():
mtopic = _el("topic", parent=rt, id=_new_id(), timestamp=ts)
mtopic = _el("topic", parent=nt, id=_new_id(), timestamp=ts)
_el("title", parent=mtopic).text = module_name
mc = _el("children", parent=mtopic)
mtopics = _el("topics", parent=mc, type="attached")
@@ -207,15 +230,18 @@ def build_meta() -> bytes:
return etree.tostring(root, encoding="utf-8", xml_declaration=True, pretty_print=True)
def export_markdown_to_xmind(source_path: Path, output_dir: Path, topic_name: str = "") -> Path:
def export_markdown_to_xmind(source_path: Path, output_dir: Path, topic_name: str = "",
system_name: str = "") -> Path:
headers, rows = load_markdown_table(source_path)
if not topic_name:
topic_name = source_path.stem.replace("_测试用例", "").replace("_分析", "")
if not system_name:
system_name = resolve_system_name()
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / f"{topic_name}.xmind"
content_xml = build_xmind_content(headers, rows, topic_name)
content_xml = build_xmind_content(headers, rows, topic_name, system_name)
manifest_xml = build_manifest()
meta_xml = build_meta()
@@ -240,7 +266,9 @@ def main() -> None:
parser = argparse.ArgumentParser(description="将 Markdown 测试用例导出为 XMind 思维导图。")
parser.add_argument("--source-file", help="测试用例 Markdown 文件路径")
parser.add_argument("--base-name", help="按 BASE_NAME 查找 output/test_cases/{BASE_NAME}_测试用例.md")
parser.add_argument("--topic-name", help="XMind 根主题名称(默认从文件名推断)", default="")
parser.add_argument("--topic-name", help="需求主题名称(默认从文件名推断)", default="")
parser.add_argument("--system-name", default="",
help=f"XMind 最外层根主题:系统名称(默认取项目画像,回退 {DEFAULT_SYSTEM_NAME})")
parser.add_argument("--input-dir", default=str(DEFAULT_INPUT_DIR), help="Markdown 所在目录")
parser.add_argument("--output-dir", default=str(DEFAULT_OUTPUT_DIR), help="XMind 输出目录")
args = parser.parse_args()
@@ -259,11 +287,12 @@ def main() -> None:
if not source_path.exists():
raise FileNotFoundError(f"测试用例文件不存在:{source_path}")
output_path = export_markdown_to_xmind(source_path, output_dir, args.topic_name)
topic_name = args.topic_name or source_path.stem.replace("_测试用例", "").replace("_分析", "")
output_path = export_markdown_to_xmind(source_path, output_dir, args.topic_name, args.system_name)
headers, rows = load_markdown_table(source_path)
print(f"来源文件: {source_path}")
print(f"用例数量: {len(rows)}")
print(f"根主题: {args.topic_name or source_path.stem}")
print(f"根主题: {args.system_name or resolve_system_name()} → {topic_name}")
print(f"XMind 已生成: {output_path}")