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:
@@ -9,3 +9,10 @@ __pycache__/
|
|||||||
# Node 依赖与 Office 锁文件不入库
|
# Node 依赖与 Office 锁文件不入库
|
||||||
output/node_modules/
|
output/node_modules/
|
||||||
~$*
|
~$*
|
||||||
|
|
||||||
|
# 本地导出产物与测试缓存
|
||||||
|
output/excel_reports/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# 运政网批量查询工作数据(截图与结果约 1.1G,不入库)
|
||||||
|
xst/
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+37
-8
@@ -26,6 +26,22 @@ MANIFEST_NS = "urn:xmind:xmap:xmlns:manifest:1.0"
|
|||||||
|
|
||||||
SEPARATOR_RE = re.compile(r"^\|\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$")
|
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():
|
def _new_id():
|
||||||
return str(uuid.uuid4())
|
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()
|
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()
|
ts = _now_ms()
|
||||||
nsmap = {None: XMAP_NS, "fo": FO_NS, "svg": SVG_NS, "xhtml": XHTML_NS, "xlink": XLINK_NS}
|
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,
|
root_topic = _el("topic", parent=sheet, id=_new_id(), timestamp=ts,
|
||||||
**{"structure-class": "org.xmind.ui.logic.right"})
|
**{"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)
|
rc = _el("children", parent=root_topic)
|
||||||
rt = _el("topics", parent=rc, type="attached")
|
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]]] = {}
|
grouped: dict[str, list[dict[str, str]]] = {}
|
||||||
for row in rows:
|
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)
|
grouped.setdefault(module, []).append(case)
|
||||||
|
|
||||||
for module_name, cases in grouped.items():
|
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
|
_el("title", parent=mtopic).text = module_name
|
||||||
mc = _el("children", parent=mtopic)
|
mc = _el("children", parent=mtopic)
|
||||||
mtopics = _el("topics", parent=mc, type="attached")
|
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)
|
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)
|
headers, rows = load_markdown_table(source_path)
|
||||||
if not topic_name:
|
if not topic_name:
|
||||||
topic_name = source_path.stem.replace("_测试用例", "").replace("_分析", "")
|
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_dir.mkdir(parents=True, exist_ok=True)
|
||||||
output_path = output_dir / f"{topic_name}.xmind"
|
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()
|
manifest_xml = build_manifest()
|
||||||
meta_xml = build_meta()
|
meta_xml = build_meta()
|
||||||
|
|
||||||
@@ -240,7 +266,9 @@ def main() -> None:
|
|||||||
parser = argparse.ArgumentParser(description="将 Markdown 测试用例导出为 XMind 思维导图。")
|
parser = argparse.ArgumentParser(description="将 Markdown 测试用例导出为 XMind 思维导图。")
|
||||||
parser.add_argument("--source-file", help="测试用例 Markdown 文件路径")
|
parser.add_argument("--source-file", help="测试用例 Markdown 文件路径")
|
||||||
parser.add_argument("--base-name", help="按 BASE_NAME 查找 output/test_cases/{BASE_NAME}_测试用例.md")
|
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("--input-dir", default=str(DEFAULT_INPUT_DIR), help="Markdown 所在目录")
|
||||||
parser.add_argument("--output-dir", default=str(DEFAULT_OUTPUT_DIR), help="XMind 输出目录")
|
parser.add_argument("--output-dir", default=str(DEFAULT_OUTPUT_DIR), help="XMind 输出目录")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -259,11 +287,12 @@ def main() -> None:
|
|||||||
if not source_path.exists():
|
if not source_path.exists():
|
||||||
raise FileNotFoundError(f"测试用例文件不存在:{source_path}")
|
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)
|
headers, rows = load_markdown_table(source_path)
|
||||||
print(f"来源文件: {source_path}")
|
print(f"来源文件: {source_path}")
|
||||||
print(f"用例数量: {len(rows)}")
|
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}")
|
print(f"XMind 已生成: {output_path}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user