Summary
ResolveNumPrFromStyle (src/officecli/Handlers/Word/WordHandler.StyleList.cs) resolves a paragraph's effective numbering by walking the basedOn style chain, but it treats numId and ilvl as a single unit. When a child style declares only w:ilvl and inherits w:numId from its basedOn parent, the walk skips the child (it has no numId) and returns the parent's (numId, ilvl) — silently discarding the child's ilvl.
Repro
A common Word-generated multilevel list linked to heading styles:
- style
2 (heading 1): numPr = <w:ilvl val="0"/><w:numId val="1"/>
- style
3 (heading 2): basedOn=2, numPr = <w:ilvl val="1"/> — no numId, inherited from style 2
- style
4 (heading 3): numPr = <w:ilvl val="2"/><w:numId val="1"/>
A paragraph with pStyle=3 should resolve to (numId=1, ilvl=1).
But officecli get <doc> /body --json reports numLevel=0 for it (the parent's ilvl), not 1.
Impact
Everything relying on ResolveNumPrFromStyle: paragraph numLevel emission (get /body), HtmlPreview rendering, Navigation counters. A downstream consumer that computes numbering values from numId+numLevel gets the wrong level, so all H1/H2 collapse onto one counter and values come out wrong.
Root cause
In the basedOn walk, the first style with a non-null numId is returned wholesale as (numId, ilvl). A child style that declares ilvl without numId is never consulted for its ilvl.
Confirmed still present on main (v1.0.131).
Suggested fix
Track numId and ilvl independently while walking the chain: remember the first-seen numId and the first-seen ilvl (they may come from different styles in the chain), break once numId is resolved, and return (resolvedNumId, resolvedIlvl ?? 0). The direct-numPr branch (step 1) has the same latent issue when a paragraph's own numPr carries ilvl but not numId.
Summary
ResolveNumPrFromStyle(src/officecli/Handlers/Word/WordHandler.StyleList.cs) resolves a paragraph's effective numbering by walking thebasedOnstyle chain, but it treatsnumIdandilvlas a single unit. When a child style declares onlyw:ilvland inheritsw:numIdfrom itsbasedOnparent, the walk skips the child (it has nonumId) and returns the parent's(numId, ilvl)— silently discarding the child'silvl.Repro
A common Word-generated multilevel list linked to heading styles:
2(heading 1):numPr=<w:ilvl val="0"/><w:numId val="1"/>3(heading 2):basedOn=2,numPr=<w:ilvl val="1"/>— no numId, inherited from style 24(heading 3):numPr=<w:ilvl val="2"/><w:numId val="1"/>A paragraph with
pStyle=3should resolve to(numId=1, ilvl=1).But
officecli get <doc> /body --jsonreportsnumLevel=0for it (the parent's ilvl), not1.Impact
Everything relying on
ResolveNumPrFromStyle: paragraphnumLevelemission (get /body), HtmlPreview rendering, Navigation counters. A downstream consumer that computes numbering values fromnumId+numLevelgets the wrong level, so all H1/H2 collapse onto one counter and values come out wrong.Root cause
In the basedOn walk, the first style with a non-null
numIdis returned wholesale as(numId, ilvl). A child style that declaresilvlwithoutnumIdis never consulted for itsilvl.Confirmed still present on
main(v1.0.131).Suggested fix
Track
numIdandilvlindependently while walking the chain: remember the first-seennumIdand the first-seenilvl(they may come from different styles in the chain), break oncenumIdis resolved, and return(resolvedNumId, resolvedIlvl ?? 0). The direct-numPr branch (step 1) has the same latent issue when a paragraph's ownnumPrcarriesilvlbut notnumId.