feat(writer): docx 注入原型提前(T17 架构审查整改,OV8)
- T17 (OV8, P1): 新建 src/genesis/writer/ 包
- docx_injector.py: DocxInjector 用原生 python-docx 实现 §6.6 占位符注入
- 章节级 {{section:id}} → 内容块 docx 元素序列(heading/paragraph/table)
- 行内 {{meta}} → 元信息填充
- 残留检查: 未替换 {{...}} 抛 DocxInjectError
- 格式精度: 注入 heading 继承模板 Heading 样式,原内容不被破坏
- 新增 test_docx_injector.py(5 用例)
- 同步 design.md §6.7 渲染链路 T17 原型说明
- TDD: RED(模块缺失)→ GREEN(聚焦 5 passed)→ 全量 245 passed / 100.00%(1361 stmts/340 br)
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
"""docx 注入原型测试(T17,OV8)。
|
||||
|
||||
OV8 裁定:最难成功标准(格式精度)排关键路径末尾 → docx 注入原型提前验证。
|
||||
本文件测试 DocxInjector:模板占位符替换(章节级/行内)、残留检查、格式精度
|
||||
(注入 heading 继承模板 Heading 样式,原有内容样式不被破坏)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from docx import Document
|
||||
|
||||
from genesis.writer.docx_injector import (
|
||||
Block,
|
||||
DocxInjectError,
|
||||
DocxInjector,
|
||||
)
|
||||
|
||||
|
||||
def _make_template(tmp_path, body: str) -> str:
|
||||
doc = Document()
|
||||
doc.add_paragraph("{{doc_title}}") # 行内占位符
|
||||
doc.add_paragraph(body) # 章节占位符所在段落
|
||||
doc.add_paragraph("尾部固定内容")
|
||||
path = tmp_path / "template.docx"
|
||||
doc.save(str(path))
|
||||
return str(path)
|
||||
|
||||
|
||||
def _section_blocks() -> list[Block]:
|
||||
return [
|
||||
Block(kind="heading", text="3.1 テーブル一覧", level=2),
|
||||
Block(kind="paragraph", text="以下がDB表定义です。"),
|
||||
Block(kind="table", rows=[["テーブル", "説明"], ["TB001", "社員"]]),
|
||||
]
|
||||
|
||||
|
||||
# ---------- 章节级占位符替换 ----------
|
||||
|
||||
def test_section_placeholder_replaced(tmp_path):
|
||||
tpl = _make_template(tmp_path, "{{section:db_design}}")
|
||||
inj = DocxInjector(tpl)
|
||||
out = inj.inject({"db_design": _section_blocks()}, {"doc_title": "概要設計書"})
|
||||
|
||||
full_text = "\n".join(p.text for p in out.paragraphs)
|
||||
assert "{{section:db_design}}" not in full_text
|
||||
assert "3.1 テーブル一覧" in full_text
|
||||
assert "以下がDB表定义です。" in full_text
|
||||
|
||||
|
||||
# ---------- 行内占位符替换 ----------
|
||||
|
||||
def test_inline_meta_replaced(tmp_path):
|
||||
tpl = _make_template(tmp_path, "{{section:db_design}}")
|
||||
inj = DocxInjector(tpl)
|
||||
out = inj.inject({"db_design": []}, {"doc_title": "概要設計書"})
|
||||
|
||||
assert "{{doc_title}}" not in "\n".join(p.text for p in out.paragraphs)
|
||||
assert "概要設計書" in "\n".join(p.text for p in out.paragraphs)
|
||||
|
||||
|
||||
# ---------- 占位符残留检查 ----------
|
||||
|
||||
def test_residue_detection_raises(tmp_path):
|
||||
tpl = _make_template(tmp_path, "{{section:unknown_chapter}}")
|
||||
inj = DocxInjector(tpl)
|
||||
with pytest.raises(DocxInjectError, match="残留"):
|
||||
inj.inject({}, {"doc_title": "X"})
|
||||
|
||||
|
||||
# ---------- 格式精度:heading 继承模板样式 ----------
|
||||
|
||||
def test_heading_inherits_template_style(tmp_path):
|
||||
tpl = _make_template(tmp_path, "{{section:db_design}}")
|
||||
inj = DocxInjector(tpl)
|
||||
out = inj.inject({"db_design": _section_blocks()}, {"doc_title": "T"})
|
||||
|
||||
# 注入的 heading block(level=2)应渲染为模板中存在的 Heading 2 样式段落
|
||||
heading_paras = [p for p in out.paragraphs if p.style.name == "Heading 2"]
|
||||
assert any("3.1 テーブル一覧" in p.text for p in heading_paras)
|
||||
|
||||
|
||||
def test_original_content_style_preserved(tmp_path):
|
||||
tpl = _make_template(tmp_path, "{{section:db_design}}")
|
||||
inj = DocxInjector(tpl)
|
||||
out = inj.inject({"db_design": []}, {"doc_title": "T"})
|
||||
|
||||
# 模板原有段落(尾部固定内容)在注入后仍存在且未被破坏
|
||||
assert any("尾部固定内容" in p.text for p in out.paragraphs)
|
||||
Reference in New Issue
Block a user