# Strip numPr When Adding Manual Numbering to Auto-Numbered Paragraphs ## Problem (2026-07-01 反委托代发工资协议) Original contract paragraphs have `` with actual auto-numbering (e.g., `numId=3 → abstractNum decimal "%1." start=1`). When you insert manual "第X条" numbering as `w:ins` at paragraph start, OnlyOffice renders BOTH: ``` 1. 第一条 乙方应严格按照... ← "1." is auto-numbering, "第一条" is your INS ``` This looks broken — two different numbering systems stacked. ## Root Cause The paragraph's `pPr/numPr` tells the rendering engine to prepend an automatic decimal number. Your INS adds a second, manual number. They coexist independently. Additionally, if the paragraph has `pPr/pPrChange` (tracking the old paragraph formatting), the old `numPr` inside `pPrChange` can ALSO render in markup view. ## Fix (two-step) After inserting manual numbering INS elements, strip auto-numbering from ALL affected paragraphs: ```python for idx in target_paragraph_indices: p = paras[idx] ppr = p.find(f'{WNS}pPr') if ppr is not None: # Step 1: Remove direct numPr num_pr = ppr.find(f'{WNS}numPr') if num_pr is not None: ppr.remove(num_pr) # Step 2: Remove numPr inside pPrChange (old formatting record) ppr_change = ppr.find(f'{WNS}pPrChange') if ppr_change is not None: inner_ppr = ppr_change.find(f'{WNS}pPr') if inner_ppr is not None: inner_num = inner_ppr.find(f'{WNS}numPr') if inner_num is not None: inner_ppr.remove(inner_num) ``` ## When This Applies - You're converting a contract from auto-numbered clauses to manual "第X条" heading-style numbering - The original .doc/.docx used Word's list numbering for clause structure - You're adding "第一条 " etc. as INS at paragraph start ## Verification After fix: 1. `pdftotext -layout` of OnlyOffice render should show NO stray "1." / "2." / "3." before your "第X条" 2. Accept-revisions preview should also be clean (no residual auto-numbers) ## Scenario B: Auto-Numbering Resets Across Tracked-Deleted Paragraphs (2026-07-01 反委托代发工资协议) ### Problem When paragraphs with `numPr` auto-numbering are interspersed with **entirely deleted paragraphs** (all content in `w:del`), OnlyOffice's auto-number counter **resets to 1** after the deleted block. This makes continuous numbering impossible with `numPr` alone. Example structure: ``` P6: numPr=1 INS content (clause 1) → renders "1." P7: numPr=1 continuation → renders "2." (wrong if P7 shouldn't be numbered) P8: numPr=1 ALL w:del → renders "3." with strikethrough P9: numPr=1 ALL w:del → renders "4." with strikethrough P10: numPr=1 INS content (clause 2) → renders "1." ← RESETS! Should be "2." ``` The auto-numbering engine counts visible (non-deleted) items in the `numId` sequence, but deleted paragraphs **break the continuity** in OnlyOffice's rendering. ### Solution: Convert to Manual Text Numbering Strip `numPr` from ALL paragraphs and insert "N. " as `w:ins` text at paragraph start. This gives identical visual output ("1. 2. 3. ...") without depending on the broken auto-number counter. ```python # Step 1: Strip ALL numPr (including inside pPrChange) for i, p in enumerate(paragraphs): pPr = p.find(f'{{{W}}}pPr') if pPr is not None: numPr = pPr.find(f'{{{W}}}numPr') if numPr is not None: pPr.remove(numPr) for pPrChange in pPr.findall(f'{{{W}}}pPrChange'): old_pPr = pPrChange.find(f'{{{W}}}pPr') if old_pPr is not None: old_numPr = old_pPr.find(f'{{{W}}}numPr') if old_numPr is not None: old_pPr.remove(old_numPr) # Step 2: Insert "N. " as w:ins text for each clause paragraph # Only number paragraphs that have VISIBLE content (not entirely w:del) clause_map = {6: 1, 10: 2, 11: 3, ...} # para_index: clause_number for para_idx, clause_num in clause_map.items(): p = paragraphs[para_idx] # Build INS element with "N. " text ins_elem = ET.Element(f'{{{W}}}ins') ins_elem.set(f'{{{W}}}id', str(next_rev_id())) ins_elem.set(f'{{{W}}}author', rev_author) # from existing INS in doc ins_elem.set(f'{{{W}}}date', rev_date) r_elem = ET.SubElement(ins_elem, f'{{{W}}}r') # Clone rPr from existing runs for font consistency rPr = get_run_rPr_from_paragraph(p) if rPr is not None: r_elem.append(copy.deepcopy(rPr)) t_elem = ET.SubElement(r_elem, f'{{{W}}}t') t_elem.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve') t_elem.text = f"{clause_num}. " # Insert after pPr pPr = p.find(f'{{{W}}}pPr') if pPr is not None: p.insert(list(p).index(pPr) + 1, ins_elem) else: p.insert(0, ins_elem) ``` ### When to Use This (vs Scenario A) - **Scenario A** (above): You're CHANGING the numbering scheme (auto "1." → manual "第一条") - **Scenario B** (this): You're KEEPING the same format ("1. 2. 3.") but converting from auto to manual because auto-numbering resets across w:del paragraphs - **Trigger**: Original uses numPr auto-numbering + your edits create entirely-deleted paragraphs between numbered items → auto counter resets → switch to manual text ### Key Decision: Which Paragraphs to Number Only number paragraphs that will be visible after accepting revisions: - Paragraphs with ONLY `w:del` content → skip (they're deleted) - Paragraphs that are continuations of the previous clause (no independent number) → skip - New INS-only paragraphs (new clauses) → number them - Rewritten paragraphs (mixed INS+DEL, first clause in sequence) → number them ### Verification After conversion: 1. OnlyOffice render (x2t → PDF) should show continuous "1. 2. 3. ... 11." without resets 2. No stray auto-numbers from numPr remnants 3. Deleted paragraphs (entirely w:del) should NOT show any number ## Distinction from Existing Rules - Rule 5 (A類 vs B類) talks about NEW clauses inheriting/stripping numPr - Scenario A is EXISTING paragraphs where you're REPLACING their numbering scheme with a different format via INS - Scenario B is EXISTING paragraphs where auto-numbering BREAKS due to tracked-deleted paragraphs, requiring conversion to same-format manual text - numId=0 trap (Rule 5 sub-note) is about fake auto-numbering; BOTH scenarios here are about REAL auto-numbering that renders visible numbers