Skip to content

Commit fc1810c

Browse files
committed
🐛 fix(numbering_resolver): correct Word's paragraph numbering semantics
- remove incorrect runtime style override caching (style_numpr_overrides) - ensure direct numPr only applies to its own paragraph, not subsequent style-based paragraphs - update docstring to clarify Word's actual rendering behavior - clean up test fixture to match removed attribute
1 parent a6b0f16 commit fc1810c

2 files changed

Lines changed: 16 additions & 32 deletions

File tree

skills/doc-audit/scripts/numbering_resolver.py

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ def __init__(self, docx_path: str):
4242
self.counters: Dict[str, Dict[int, int]] = {} # numId -> {ilvl -> current_count}
4343
self.start_overrides: Dict[str, Dict[int, int]] = {} # numId -> {ilvl -> start_value}
4444
self.style_numpr: Dict[str, dict] = {} # styleId -> {numId, ilvl} from styles.xml
45-
self.style_numpr_overrides: Dict[str, dict] = {} # Runtime overrides when direct numPr + pStyle
4645
self.style_based_on: Dict[str, str] = {} # styleId -> basedOn styleId
4746
# Smart numbering merge state (Word's rendering behavior)
4847
self.last_numId: str = None # Previous paragraph's numId
@@ -201,61 +200,47 @@ def reset_tracking_state(self):
201200
def get_label(self, para_element) -> str:
202201
"""
203202
Get rendered numbering label for a paragraph.
204-
205-
Checks both direct numPr and style-inherited numbering.
206-
When a paragraph has both pStyle and direct numPr, the direct numPr
207-
becomes the runtime default for that style (overriding styles.xml).
208-
203+
204+
Direct numPr applies only to its own paragraph (per Word's semantics).
205+
Subsequent paragraphs that carry only pStyle fall back to the numPr
206+
declared by the style in styles.xml.
207+
209208
Args:
210209
para_element: lxml Element for <w:p>
211-
210+
212211
Returns:
213212
Rendered label string (e.g., "1.1", "a)", "第一章") or empty string
214213
"""
215214
try:
216215
pPr = para_element.find(f'{{{NSMAP["w"]}}}pPr')
217216
if pPr is None:
218217
return ""
219-
218+
220219
num_id = None
221220
ilvl = 0
222221
style_id = None
223-
222+
224223
# Get pStyle (if present)
225224
pStyle = pPr.find(f'{{{NSMAP["w"]}}}pStyle')
226225
if pStyle is not None:
227226
style_id = pStyle.get(f'{{{NSMAP["w"]}}}val')
228-
227+
229228
# Check for direct numPr in paragraph
230229
numPr = pPr.find(f'{{{NSMAP["w"]}}}numPr')
231230
if numPr is not None:
232231
num_id_elem = numPr.find(f'{{{NSMAP["w"]}}}numId')
233232
ilvl_elem = numPr.find(f'{{{NSMAP["w"]}}}ilvl')
234-
233+
235234
if num_id_elem is not None:
236235
num_id = num_id_elem.get(f'{{{NSMAP["w"]}}}val')
237236
ilvl = int(ilvl_elem.get(f'{{{NSMAP["w"]}}}val')) if ilvl_elem is not None else 0
238-
239-
# If paragraph has both pStyle and direct numPr, record the override
240-
if style_id:
241-
self.style_numpr_overrides[style_id] = {
242-
'numId': num_id,
243-
'ilvl': ilvl
244-
}
245-
246-
# If no direct numPr, check style-inherited numbering
237+
238+
# If no direct numPr, fall back to style-inherited numbering from styles.xml
247239
if num_id is None and style_id:
248-
# First check runtime overrides (from previous direct numPr)
249-
if style_id in self.style_numpr_overrides:
250-
override = self.style_numpr_overrides[style_id]
251-
num_id = override['numId']
252-
ilvl = override['ilvl']
253-
else:
254-
# Fall back to original style definition from styles.xml
255-
style_num = self._get_numbering_from_style(style_id)
256-
if style_num:
257-
num_id = style_num['numId']
258-
ilvl = style_num['ilvl']
240+
style_num = self._get_numbering_from_style(style_id)
241+
if style_num:
242+
num_id = style_num['numId']
243+
ilvl = style_num['ilvl']
259244

260245
# If still no numbering found, clear state and return empty
261246
if num_id is None:

tests/test_numbering_resolver.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ def _make_resolver_with_state(levels: dict, counters: dict, num_id: str = '1'):
2626
resolver.counters = {num_id: counters}
2727
resolver.start_overrides = {}
2828
resolver.style_numpr = {}
29-
resolver.style_numpr_overrides = {}
3029
resolver.style_based_on = {}
3130
resolver.last_numId = None
3231
resolver.last_abstract_id = None

0 commit comments

Comments
 (0)