diff --git a/src/genesis/writer/template_mapper.py b/src/genesis/writer/template_mapper.py index 436879d..abee43f 100644 --- a/src/genesis/writer/template_mapper.py +++ b/src/genesis/writer/template_mapper.py @@ -1,22 +1,36 @@ -"""模板 → 有序章节规格映射(Phase 5)。""" +"""模板 → 有序章节规格映射(Phase 5)。 + +真实 ParsedTemplate.sections 为 ChapterMarker 列表;章节由 type=="heading" 起, +紧随其后的 type=="placeholder" 且形如 `section:` 的标记归属该章, +用于确定 chapter_id 与 section_placeholder(语言无关、按文档顺序)。 +""" from __future__ import annotations +import re + +from genesis.data_models import ParsedTemplate from genesis.writer.models import ChapterSpec +_SECTION_RE = re.compile(r"^section:(.+)$") -def map_template(parsed) -> list[ChapterSpec]: - """按模板 Heading 层级顺序产出有序章节列表。 - `parsed.chapters` 为 WordTemplateParser 产出的章节标记列表, - 每项含 chapter_id / title / section_placeholder 属性。 - """ +def map_template(parsed: ParsedTemplate) -> list[ChapterSpec]: specs: list[ChapterSpec] = [] - for ch in getattr(parsed, "chapters", []): - specs.append( - ChapterSpec( - chapter_id=getattr(ch, "chapter_id", ""), - title=getattr(ch, "title", ""), - section_placeholder=getattr(ch, "section_placeholder", None), + idx = 0 + current: ChapterSpec | None = None + for ch in getattr(parsed, "sections", []): + t = getattr(ch, "type", None) + if t == "heading": + idx += 1 + current = ChapterSpec( + chapter_id=f"chapter_{idx}", title=getattr(ch, "name", ""), section_placeholder=None ) - ) + specs.append(current) + elif t == "placeholder" and current is not None: + m = _SECTION_RE.match(getattr(ch, "name", "")) + if m: + sid = m.group(1) + current.section_placeholder = getattr(ch, "name", "") + if current.chapter_id.startswith("chapter_"): + current.chapter_id = sid return specs diff --git a/tests/test_phase5_template_mapper.py b/tests/test_phase5_template_mapper.py index 001e160..d40b38a 100644 --- a/tests/test_phase5_template_mapper.py +++ b/tests/test_phase5_template_mapper.py @@ -1,17 +1,32 @@ -from types import SimpleNamespace -from genesis.writer.template_mapper import map_template +from genesis.data_models import ChapterMarker, ParsedTemplate from genesis.writer.models import ChapterSpec +from genesis.writer.template_mapper import map_template -def _fake_parsed(): - ch1 = SimpleNamespace(chapter_id="intro", title="はじめに", section_placeholder="{{section:introduction}}") - ch2 = SimpleNamespace(chapter_id="db_design", title="DB 設計", section_placeholder=None) - return SimpleNamespace(chapters=[ch1, ch2]) +def _parsed(): + sections = [ + ChapterMarker(type="heading", name="はじめに", level=1), + ChapterMarker(type="placeholder", name="section:introduction", level=0), + ChapterMarker(type="heading", name="DB 設計", level=1), + ChapterMarker(type="placeholder", name="section:db_design", level=0), + ChapterMarker(type="bookmark", name="bm1", level=0), + ] + return ParsedTemplate(file_name="t.docx", sections=sections, placeholders={"section:introduction": "x"}, styles={}) -def test_map_template_ordered(): - specs = map_template(_fake_parsed()) - assert [s.chapter_id for s in specs] == ["intro", "db_design"] - assert specs[0].section_placeholder == "{{section:introduction}}" - assert specs[1].section_placeholder is None +def test_map_template_ordered_and_assigns_ids(): + specs = map_template(_parsed()) + assert [s.title for s in specs] == ["はじめに", "DB 設計"] + assert specs[0].chapter_id == "introduction" + assert specs[0].section_placeholder == "section:introduction" + assert specs[1].chapter_id == "db_design" + assert specs[1].section_placeholder == "section:db_design" assert all(isinstance(s, ChapterSpec) for s in specs) + + +def test_map_template_falls_back_without_placeholder(): + sections = [ChapterMarker(type="heading", name="附録", level=1)] + parsed = ParsedTemplate(file_name="t.docx", sections=sections, placeholders={}, styles={}) + specs = map_template(parsed) + assert specs[0].chapter_id == "chapter_1" + assert specs[0].section_placeholder is None