A law firm sent me a 60-page contract to convert, and by section 7 every cross-reference was off by one. The text said "as set out in clause 6.3" while the heading it pointed to read "6.4". I assumed a typo in the source document — until the same drift showed up in three other files that week.
The root cause changed how I think about Word documents: the numbers you see in a DOCX are not stored anywhere. Open document.xml and search for "6.3" — you'll find cross-reference fields, maybe, but the list numbers themselves simply don't exist. A numbered paragraph carries only <w:numPr> with a numId and a nesting level. The visible number is computed at render time from numbering.xml: an abstract numbering definition holding formats like "%1.%2", start values, restart rules, and occasionally a startOverride when someone right-clicked "Restart at 1".
So Word runs a small numbering engine on every render. My first converter just counted paragraphs and incremented digits, which works until it doesn't: legal formats like (a) and (iv), decimal-within-upper-level numbering, restart-on-higher-level semantics, and those per-instance overrides. Any oddity that Word silently heals, my code re-broke.
The fix was implementing the sequence resolution rules from ECMA-376 properly: one counter per level, reset deeper levels when a higher one increments, apply startOverride per numbering instance, format every number through the level's pattern. Not fun, but now heading numbers match Word's display exactly. My regression test is cheap: convert the file, export the same file to PDF from Word, and diff just the clause headings. Any drift is a bug.
The lesson for anyone feeding contracts or policies into a pipeline: list numbers in DOCX are a virtual layer, computed, not stored. If a converter renders lists as bullets or renumbers with its own logic, quoted clause references in the body text will eventually point at the wrong clause. That numbering engine is now baked into the DOCX-to-Markdown API I operate (https://x402.freeq.one/tools/docx_to_markdown.html), and it's the reason cross-references survive conversion intact.
Top comments (0)