docs: Phase3 计划修订(Task1 前缀 test:、_heading_level 提取共享 helper、兜底分支覆盖)
This commit is contained in:
@@ -40,8 +40,6 @@
|
|||||||
- [ ] **Step 1: 新建 docx_helpers.py**
|
- [ ] **Step 1: 新建 docx_helpers.py**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from docx import Document
|
from docx import Document
|
||||||
|
|
||||||
|
|
||||||
@@ -90,7 +88,7 @@ Expected: `ok`(工作目录为项目根 `D:\00_project\Genesis`)
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add tests/docx_helpers.py
|
git add tests/docx_helpers.py
|
||||||
git commit -m "chore: docx 测试基建(新建/落盘/规则文档快捷构造)"
|
git commit -m "test: docx 测试基建(新建/落盘/规则文档快捷构造)"
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -102,7 +100,7 @@ git commit -m "chore: docx 测试基建(新建/落盘/规则文档快捷构造
|
|||||||
- Test: `tests/test_word_template_parser.py`
|
- Test: `tests/test_word_template_parser.py`
|
||||||
|
|
||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
- Consumes: `genesis.data_models.ChapterMarker` / `genesis.data_models.ParsedTemplate`
|
- Consumes: `genesis.data_models.ChapterMarker` / `genesis.data_models.ParsedTemplate`;`genesis.parsers._word_common.heading_level`(**本任务新建**共享 helper,Task 3 复用)
|
||||||
- Produces:
|
- Produces:
|
||||||
- `class WordTemplateParser`,方法 `parse(path: str | Path) -> ParsedTemplate`
|
- `class WordTemplateParser`,方法 `parse(path: str | Path) -> ParsedTemplate`
|
||||||
- 内部正则 `PLACEHOLDER_RE`:`r"\{\{([a-z][a-z0-9_]*)(?::([a-z][a-z0-9_]*))?\}\}"`(两组:group1=键名,group2=可选章节名)
|
- 内部正则 `PLACEHOLDER_RE`:`r"\{\{([a-z][a-z0-9_]*)(?::([a-z][a-z0-9_]*))?\}\}"`(两组:group1=键名,group2=可选章节名)
|
||||||
@@ -116,11 +114,24 @@ Create `tests/test_word_template_parser.py`:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from genesis.data_models import ChapterMarker, ParsedTemplate
|
from genesis.data_models import ChapterMarker, ParsedTemplate
|
||||||
|
from genesis.parsers._word_common import heading_level
|
||||||
from genesis.parsers.word_template_parser import WordTemplateParser
|
from genesis.parsers.word_template_parser import WordTemplateParser
|
||||||
|
|
||||||
from tests.docx_helpers import new_document, save_document
|
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):
|
def test_parse_extracts_heading_levels(tmp_path):
|
||||||
doc = new_document()
|
doc = new_document()
|
||||||
doc.add_heading("1. はじめに", level=1)
|
doc.add_heading("1. はじめに", level=1)
|
||||||
@@ -210,9 +221,25 @@ def test_parse_styles_collected(tmp_path):
|
|||||||
- [ ] **Step 2: 运行测试确认失败**
|
- [ ] **Step 2: 运行测试确认失败**
|
||||||
|
|
||||||
Run: `python -m pytest tests/test_word_template_parser.py -v`
|
Run: `python -m pytest tests/test_word_template_parser.py -v`
|
||||||
Expected: FAIL / ERROR(`ModuleNotFoundError: No module named 'genesis.parsers.word_template_parser'`)
|
Expected: FAIL / ERROR(`ModuleNotFoundError: No module named 'genesis.parsers.word_template_parser'` / `_word_common`)
|
||||||
|
|
||||||
- [ ] **Step 3: 写最小实现**
|
- [ ] **Step 3: 写最小实现(含共享 helper)**
|
||||||
|
|
||||||
|
Create `src/genesis/parsers/_word_common.py`(Task 2/3 共享,避免重复定义):
|
||||||
|
|
||||||
|
```python
|
||||||
|
"""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
|
||||||
|
```
|
||||||
|
|
||||||
Create `src/genesis/parsers/word_template_parser.py`:
|
Create `src/genesis/parsers/word_template_parser.py`:
|
||||||
|
|
||||||
@@ -226,19 +253,12 @@ from docx import Document
|
|||||||
from docx.oxml.ns import qn
|
from docx.oxml.ns import qn
|
||||||
|
|
||||||
from genesis.data_models import ChapterMarker, ParsedTemplate
|
from genesis.data_models import ChapterMarker, ParsedTemplate
|
||||||
|
from genesis.parsers._word_common import heading_level
|
||||||
|
|
||||||
# 统一占位符正则:{{键名}} 或 {{键名:章节名}}(spec §3.2)
|
# 统一占位符正则:{{键名}} 或 {{键名:章节名}}(spec §3.2)
|
||||||
PLACEHOLDER_RE = re.compile(r"\{\{([a-z][a-z0-9_]*)(?::([a-z][a-z0-9_]*))?\}\}")
|
PLACEHOLDER_RE = re.compile(r"\{\{([a-z][a-z0-9_]*)(?::([a-z][a-z0-9_]*))?\}\}")
|
||||||
|
|
||||||
|
|
||||||
def _heading_level(style_name: str) -> int:
|
|
||||||
"""从 Heading N 样式名解析大纲级别;非数字兜底 1。"""
|
|
||||||
try:
|
|
||||||
return int(style_name.split()[-1])
|
|
||||||
except (ValueError, IndexError):
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
class WordTemplateParser:
|
class WordTemplateParser:
|
||||||
"""概要设计模板 docx 解析:章构成 / 占位符 / 样式名提取。"""
|
"""概要设计模板 docx 解析:章构成 / 占位符 / 样式名提取。"""
|
||||||
|
|
||||||
@@ -258,7 +278,7 @@ class WordTemplateParser:
|
|||||||
|
|
||||||
if style_name.startswith("Heading"):
|
if style_name.startswith("Heading"):
|
||||||
sections.append(ChapterMarker(
|
sections.append(ChapterMarker(
|
||||||
type="heading", name=text, level=_heading_level(style_name)
|
type="heading", name=text, level=heading_level(style_name)
|
||||||
))
|
))
|
||||||
|
|
||||||
for m in PLACEHOLDER_RE.finditer(text):
|
for m in PLACEHOLDER_RE.finditer(text):
|
||||||
@@ -286,17 +306,17 @@ class WordTemplateParser:
|
|||||||
- [ ] **Step 4: 运行测试确认通过**
|
- [ ] **Step 4: 运行测试确认通过**
|
||||||
|
|
||||||
Run: `python -m pytest tests/test_word_template_parser.py -v`
|
Run: `python -m pytest tests/test_word_template_parser.py -v`
|
||||||
Expected: 6 passed
|
Expected: 8 passed(6 解析用例 + 2 共享 helper 兜底分支用例)
|
||||||
|
|
||||||
- [ ] **Step 5: 全量回归**
|
- [ ] **Step 5: 全量回归**
|
||||||
|
|
||||||
Run: `python -m pytest -q`
|
Run: `python -m pytest -q`
|
||||||
Expected: 138 passed / 100.00%(132 基线 + 6 新增),fail_under=99 达标
|
Expected: 140 passed / 100.00%(132 基线 + 8 新增),fail_under=99 达标(branch 模式)
|
||||||
|
|
||||||
- [ ] **Step 6: 提交**
|
- [ ] **Step 6: 提交**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add tests/test_word_template_parser.py src/genesis/parsers/word_template_parser.py
|
git add tests/test_word_template_parser.py src/genesis/parsers/_word_common.py src/genesis/parsers/word_template_parser.py
|
||||||
git commit -m "feat: WordTemplateParser 章构成/占位符/样式名提取"
|
git commit -m "feat: WordTemplateParser 章构成/占位符/样式名提取"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -309,7 +329,7 @@ git commit -m "feat: WordTemplateParser 章构成/占位符/样式名提取"
|
|||||||
- Test: `tests/test_rule_doc_parser.py`
|
- Test: `tests/test_rule_doc_parser.py`
|
||||||
|
|
||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
- Consumes: `genesis.data_models.RuleDocument`;`tests/docx_helpers.make_rule_doc`
|
- Consumes: `genesis.data_models.RuleDocument`;`genesis.parsers._word_common.heading_level`(Task 2 共享 helper);`tests.docx_helpers.make_rule_doc`
|
||||||
- Produces:
|
- Produces:
|
||||||
- `class RuleDocParser`,方法 `parse(path: str | Path, category: str = "write") -> RuleDocument`
|
- `class RuleDocParser`,方法 `parse(path: str | Path, category: str = "write") -> RuleDocument`
|
||||||
- Markdown 化:Heading N → `#`×N;`・`/`-`/`•` 前缀文本或 List Bullet/Number 样式 → `- ` 项;普通段落 → 原文;表格 → GFM(表头+分隔行+数据行);空段 → 空行
|
- Markdown 化:Heading N → `#`×N;`・`/`-`/`•` 前缀文本或 List Bullet/Number 样式 → `- ` 项;普通段落 → 原文;表格 → GFM(表头+分隔行+数据行);空段 → 空行
|
||||||
@@ -422,18 +442,12 @@ from docx import Document
|
|||||||
from docx.oxml.ns import qn
|
from docx.oxml.ns import qn
|
||||||
|
|
||||||
from genesis.data_models import RuleDocument
|
from genesis.data_models import RuleDocument
|
||||||
|
from genesis.parsers._word_common import heading_level
|
||||||
|
|
||||||
# 列表项前缀(真实样本为 Normal 样式 + ・ 前缀,样式名通道不足以命中)
|
# 列表项前缀(真实样本为 Normal 样式 + ・ 前缀,样式名通道不足以命中)
|
||||||
LIST_PREFIXES = ("・", "-", "•")
|
LIST_PREFIXES = ("・", "-", "•")
|
||||||
|
|
||||||
|
|
||||||
def _heading_level(style_name: str) -> int:
|
|
||||||
try:
|
|
||||||
return int(style_name.split()[-1])
|
|
||||||
except (ValueError, IndexError):
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
def _is_list_item(text: str, style_name: str) -> bool:
|
def _is_list_item(text: str, style_name: str) -> bool:
|
||||||
"""双通道列表检测:List 样式或文本前缀(spec §3.3)。"""
|
"""双通道列表检测:List 样式或文本前缀(spec §3.3)。"""
|
||||||
if "List" in style_name:
|
if "List" in style_name:
|
||||||
@@ -471,7 +485,7 @@ class RuleDocParser:
|
|||||||
if not stripped:
|
if not stripped:
|
||||||
md.append("")
|
md.append("")
|
||||||
elif style_name.startswith("Heading"):
|
elif style_name.startswith("Heading"):
|
||||||
md.append("#" * _heading_level(style_name) + " " + stripped)
|
md.append("#" * heading_level(style_name) + " " + stripped)
|
||||||
elif _is_list_item(stripped, style_name):
|
elif _is_list_item(stripped, style_name):
|
||||||
md.append("- " + stripped.lstrip("・-•").strip())
|
md.append("- " + stripped.lstrip("・-•").strip())
|
||||||
else:
|
else:
|
||||||
@@ -502,7 +516,7 @@ Expected: 6 passed
|
|||||||
- [ ] **Step 5: 全量回归**
|
- [ ] **Step 5: 全量回归**
|
||||||
|
|
||||||
Run: `python -m pytest -q`
|
Run: `python -m pytest -q`
|
||||||
Expected: 144 passed / 100.00%(138 + 6 新增),fail_under=99 达标
|
Expected: 146 passed / 100.00%(140 + 6 新增),fail_under=99 达标
|
||||||
|
|
||||||
- [ ] **Step 6: 提交**
|
- [ ] **Step 6: 提交**
|
||||||
|
|
||||||
@@ -699,7 +713,7 @@ Expected: 6 passed
|
|||||||
- [ ] **Step 5: 全量回归**
|
- [ ] **Step 5: 全量回归**
|
||||||
|
|
||||||
Run: `python -m pytest -q`
|
Run: `python -m pytest -q`
|
||||||
Expected: 150 passed / 100.00%(144 + 6 新增),fail_under=99 达标
|
Expected: 152 passed / 100.00%(146 + 6 新增),fail_under=99 达标
|
||||||
|
|
||||||
- [ ] **Step 6: 提交**
|
- [ ] **Step 6: 提交**
|
||||||
|
|
||||||
@@ -797,7 +811,7 @@ Expected: 8 passed(现有 4 Excel + 新增 4 Word)
|
|||||||
- [ ] **Step 3: 全量回归(红线验证)**
|
- [ ] **Step 3: 全量回归(红线验证)**
|
||||||
|
|
||||||
Run: `python -m pytest -q`
|
Run: `python -m pytest -q`
|
||||||
Expected: 154 passed / 100.00%(150 + 4 新增),fail_under=99 达标
|
Expected: 156 passed / 100.00%(152 + 4 新增),fail_under=99 达标
|
||||||
|
|
||||||
- [ ] **Step 4: 提交**
|
- [ ] **Step 4: 提交**
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user