133 lines
4.4 KiB
Python
133 lines
4.4 KiB
Python
from genesis.data_models import ChapterMarker, ParsedTemplate
|
||
from genesis.parsers._word_common import heading_level
|
||
from genesis.parsers.word_template_parser import WordTemplateParser
|
||
|
||
from tests.docx_helpers import new_document, save_document
|
||
|
||
|
||
def test_heading_level_parses_numeric_suffix():
|
||
assert heading_level("Heading 1") == 1
|
||
assert heading_level("Heading 2") == 2
|
||
assert heading_level("Heading 3") == 3
|
||
|
||
|
||
def test_heading_level_fallback_on_invalid():
|
||
# 兜底分支(非数字 / 无后缀)→ 1,分支覆盖必须命中
|
||
assert heading_level("Heading X") == 1
|
||
assert heading_level("Heading") == 1
|
||
|
||
|
||
def test_parse_extracts_heading_levels(tmp_path):
|
||
doc = new_document()
|
||
doc.add_heading("1. はじめに", level=1)
|
||
doc.add_heading("2.1 画面遷移図", level=2)
|
||
doc.add_heading("2.1.1 詳細", level=3)
|
||
path = save_document(tmp_path, doc)
|
||
|
||
result = WordTemplateParser().parse(path)
|
||
|
||
assert isinstance(result, ParsedTemplate)
|
||
headings = [s for s in result.sections if s.type == "heading"]
|
||
assert [(s.name, s.level) for s in headings] == [
|
||
("1. はじめに", 1),
|
||
("2.1 画面遷移図", 2),
|
||
("2.1.1 詳細", 3),
|
||
]
|
||
|
||
|
||
def test_parse_extracts_bookmark(tmp_path):
|
||
from docx.oxml.ns import qn
|
||
doc = new_document()
|
||
para = doc.add_paragraph("アンカー")
|
||
bm_start = para._p.makeelement(qn("w:bookmarkStart"), {qn("w:id"): "0", qn("w:name"): "template_start"})
|
||
para._p.insert(0, bm_start)
|
||
# 无 name 的书签:覆盖 if name 假分支,应被跳过
|
||
bm_anon = para._p.makeelement(qn("w:bookmarkStart"), {qn("w:id"): "1"})
|
||
para._p.insert(1, bm_anon)
|
||
path = save_document(tmp_path, doc)
|
||
|
||
result = WordTemplateParser().parse(path)
|
||
|
||
bookmarks = [s for s in result.sections if s.type == "bookmark"]
|
||
assert [s.name for s in bookmarks] == ["template_start"]
|
||
|
||
|
||
def test_parse_extracts_placeholders(tmp_path):
|
||
doc = new_document()
|
||
doc.add_paragraph("{{doc_title}}")
|
||
doc.add_paragraph("{{section:introduction}}")
|
||
doc.add_paragraph("{{section:function_list}}")
|
||
path = save_document(tmp_path, doc)
|
||
|
||
result = WordTemplateParser().parse(path)
|
||
|
||
assert result.placeholders == {
|
||
"doc_title": "{{doc_title}}",
|
||
"section:introduction": "{{section:introduction}}",
|
||
"section:function_list": "{{section:function_list}}",
|
||
}
|
||
ph = [s for s in result.sections if s.type == "placeholder"]
|
||
assert [s.name for s in ph] == ["doc_title", "section:introduction", "section:function_list"]
|
||
|
||
|
||
def test_parse_invalid_placeholder_kept_as_text(tmp_path):
|
||
doc = new_document()
|
||
doc.add_paragraph("{{ invalid }}")
|
||
doc.add_paragraph("ただの {text}")
|
||
path = save_document(tmp_path, doc)
|
||
|
||
result = WordTemplateParser().parse(path)
|
||
|
||
assert result.placeholders == {}
|
||
assert [s for s in result.sections if s.type == "placeholder"] == []
|
||
|
||
|
||
def test_parse_empty_document(tmp_path):
|
||
doc = new_document()
|
||
path = save_document(tmp_path, doc)
|
||
|
||
result = WordTemplateParser().parse(path)
|
||
|
||
assert result.sections == []
|
||
assert result.placeholders == {}
|
||
assert "Normal" in result.styles["defined"]
|
||
|
||
|
||
def test_parse_styles_collected(tmp_path):
|
||
doc = new_document()
|
||
doc.add_heading("章", level=1)
|
||
doc.add_paragraph("本文")
|
||
path = save_document(tmp_path, doc)
|
||
|
||
result = WordTemplateParser().parse(path)
|
||
|
||
assert "Heading 1" in result.styles["used"]
|
||
assert "Normal" in result.styles["used"]
|
||
assert "Heading 1" in result.styles["defined"]
|
||
|
||
|
||
def test_parse_extracts_placeholder_case_insensitive(tmp_path):
|
||
# 宽容:键名大小写不敏感 → 归一为小写 section:id
|
||
doc = new_document()
|
||
doc.add_paragraph("{{Section:2}}")
|
||
path = save_document(tmp_path, doc)
|
||
|
||
result = WordTemplateParser().parse(path)
|
||
|
||
assert result.placeholders == {"section:2": "{{Section:2}}"}
|
||
ph = [s for s in result.sections if s.type == "placeholder"]
|
||
assert [s.name for s in ph] == ["section:2"]
|
||
|
||
|
||
def test_parse_extracts_placeholder_fullwidth_colon(tmp_path):
|
||
# 宽容:全角冒号(:)也视为分隔符 → 归一为半角 section:id
|
||
doc = new_document()
|
||
doc.add_paragraph("{{section:2}}")
|
||
path = save_document(tmp_path, doc)
|
||
|
||
result = WordTemplateParser().parse(path)
|
||
|
||
assert result.placeholders == {"section:2": "{{section:2}}"}
|
||
ph = [s for s in result.sections if s.type == "placeholder"]
|
||
assert [s.name for s in ph] == ["section:2"]
|