feat: WordTemplateParser 章构成/占位符/样式名提取

This commit is contained in:
lhl
2026-08-10 09:38:37 +08:00
parent e8771aa797
commit 00fcbf18c3
4 changed files with 176 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
"""Word 解析共享小工具(WordTemplateParser / RuleDocParser 复用)。"""
from __future__ import annotations
def heading_level(style_name: str) -> int:
"""从 Heading N 样式名解析大纲级别;非数字/无后缀兜底 1。"""
try:
return int(style_name.split()[-1])
except (ValueError, IndexError):
return 1
@@ -0,0 +1,57 @@
from __future__ import annotations
import re
from pathlib import Path
from docx import Document
from docx.oxml.ns import qn
from genesis.data_models import ChapterMarker, ParsedTemplate
from genesis.parsers._word_common import heading_level
# 统一占位符正则:{{键名}} 或 {{键名:章节名}}spec §3.2
PLACEHOLDER_RE = re.compile(r"\{\{([a-z][a-z0-9_]*)(?::([a-z][a-z0-9_]*))?\}\}")
class WordTemplateParser:
"""概要设计模板 docx 解析:章构成 / 占位符 / 样式名提取。"""
def parse(self, path: str | Path) -> ParsedTemplate:
doc = Document(str(path))
sections: list[ChapterMarker] = []
placeholders: dict[str, str] = {}
used_styles: set[str] = set()
# 文档命名样式(定义集合)
defined = {s.name for s in doc.styles if s.name}
for para in doc.paragraphs:
style_name = para.style.name if para.style else "Normal"
used_styles.add(style_name)
text = para.text
if style_name.startswith("Heading"):
sections.append(ChapterMarker(
type="heading", name=text, level=heading_level(style_name)
))
for m in PLACEHOLDER_RE.finditer(text):
if m.group(2):
key = f"{m.group(1)}:{m.group(2)}"
else:
key = m.group(1)
placeholders[key] = text
sections.append(ChapterMarker(type="placeholder", name=key, level=0))
# 书签:遍历 body 中全部 bookmarkStart
for bm in doc.element.body.iter(qn("w:bookmarkStart")):
name = bm.get(qn("w:name"))
if name:
sections.append(ChapterMarker(type="bookmark", name=name, level=0))
return ParsedTemplate(
file_name=Path(path).name,
sections=sections,
placeholders=placeholders,
styles={"defined": sorted(defined), "used": sorted(used_styles)},
)