feat(doc-codec): read a paragraph's own numbering definitions from PlfLst/PlfLfo - #913
Merged
Conversation
Mearman
marked this pull request as ready for review
September 4, 2026 13:01
…fLst/PlfLfo A paragraph's listId/listLevel (sprmPIlfo/sprmPIlvl) says which list it belongs to and at what depth, but nothing about what that list actually looks like -- the glyph/format, level-text template, and start-at value live in PlfLst (the list definitions: LSTF plus each one's appended array of LVLs) and PlfLfo (which list a paragraph's own ilfo actually refers to), neither of which this reader touched. list/numbering.ts's readNumberingDefinitions resolves both into NumberingDefinitions, keyed by the same listId string ContentListMembership.numId already carries. The shape and field values deliberately mirror ooxml.js's own docx numbering reader: NumberingLevel.format is the identical ECMA-376 ST_NumberFormat string MSONFC's own values are documented as mapping to ([MS-OSHARED] 2.2.1.3), and NumberingLevel.text is the identical '%1.'-style placeholder convention, decoded from Xst's own raw-level-index character encoding via rgbxchNums. NumberingDefinitions sits outside document-schema.js for the same reason ooxml.js's own numbering definitions do: ContentListMembership is shared verbatim across every codec, and a document-level resource keyed by id has no business being copied onto every paragraph that shares it. read.ts's readDocContent now returns DocContent, a ContentDocument widened by one further field (numbering) -- an intersection type, so every existing caller expecting a plain ContentDocument is unaffected. Read-only, matching ooxml.js's own docx writer exactly: writeDocContent does not attempt to write PlfLst/PlfLfo back out, and LFOLVL overrides, grpprlPapx/grpprlChpx, and legal numbering (fLegal) are deliberately not resolved -- each a genuine further layer of the format, not an oversight.
…lfLfo fixtures Bytes assembled directly from [MS-DOC] 2.9.226 (PlfLst)/2.9.191 (LSTF)/2.9.196 (LVL)/2.9.148 (LVLF)/2.9.343 (Xst)/2.9.225 (PlfLfo)/2.9.181 (LFO)'s own field tables, independently of numbering.ts's own reader -- so a test asserting against these bytes checks the reader's understanding of the spec, not agreement with a second copy of the same layout, the identical convention table/decoration.test.ts's own hand-built Brc80/Shd80 fixtures state. Covers: the common case of no PlfLst/PlfLfo at all, a simple one-level bulleted list, a decimal list with a real '%1.' placeholder template, a nine-level multi-level list with mixed formats and placeholder levels, ilvlRestartLim resolving only when fNoRestart is set, the nfc=0xFF "no number sequence" sentinel, resolution keyed by the one-based ilfo (not by lsid, proven by an LFO array whose order deliberately does not match its LSTF array), and a thrown DocFormatError for an unrecognised MSONFC value. Also adds list/numbering.js to the deep-import smoke test's own module list, matching the family convention of covering every new src module there.
Adds a Numbering definitions section explaining what readNumberingDefinitions resolves, why it deliberately mirrors ooxml.js's own numbering shape and vocabulary rather than document-schema.js, and what it deliberately does not resolve (LFOLVL overrides, grpprlPapx/grpprlChpx, legal numbering). Removes the "Numbering definitions" row from the "not built on either side" table now that the read side genuinely resolves it, and adds a read-side status bullet. Verified against real LibreOffice 26.2.5.2: a .doc built from a hand-authored .fodt declaring a real numbered list and a separate bulleted list reads back with the exact ODF-authored decimal template and the real Private Use Area bullet glyph (U+F0B7) LibreOffice wrote for it, confirmed against the raw PlfLst/LVL bytes directly.
…c round trip
readDocContent's return type widened by one field (numbering,
doc-codec's own read-only list-formatting definitions keyed by
listId) -- this fixture declares no lists, so the round trip's own
numbering now resolves to {} rather than being absent from the result
entirely, which the strict content equality check needs to expect.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Mearman
force-pushed
the
feat/doc-numbering-definitions
branch
from
September 4, 2026 13:02
36cecd6 to
8f09463
Compare
Contributor
|
🎉 This PR is included in version 6.5.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A paragraph's listId/listLevel (sprmPIlfo/sprmPIlvl) says which list it belongs to and at what depth, but nothing about what that list actually looks like -- the glyph/format, level-text template, and start-at value live in PlfLst (the list definitions: LSTF plus each one's appended array of LVLs) and PlfLfo (which list a paragraph's own ilfo actually refers to), neither of which this reader touched.
list/numbering.ts's readNumberingDefinitions resolves both into NumberingDefinitions, keyed by the same listId string ContentListMembership.numId already carries. The shape and field values deliberately mirror ooxml.js's own docx numbering reader: NumberingLevel.format is the identical ECMA-376 ST_NumberFormat string MSONFC's own values are documented as mapping to ([MS-OSHARED] 2.2.1.3), and NumberingLevel.text is the identical '%1.'-style placeholder convention. NumberingDefinitions sits outside document-schema.js for the same reason ooxml.js's own numbering definitions do -- a document-level resource keyed by id has no business being copied onto every paragraph that shares it.
readDocContent now returns DocContent, a ContentDocument widened by one further field (numbering) -- an intersection type, so every existing caller expecting a plain ContentDocument is unaffected (confirmed: full workspace typecheck/lint/test/test:workers green, one documents.js fixture updated to expect the new field).
Read-only, matching ooxml.js's own docx writer exactly: writeDocContent does not attempt to write PlfLst/PlfLfo back out. LFOLVL overrides, grpprlPapx/grpprlChpx, and legal numbering (fLegal) are deliberately not resolved -- each a genuine further layer of the format, not an oversight.
Verified against real LibreOffice 26.2.5.2: a .doc built from a hand-authored .fodt declaring a real numbered list and a separate bulleted list reads back with the exact ODF-authored decimal template and the real Private Use Area bullet glyph (U+F0B7) LibreOffice wrote for it, confirmed against the raw PlfLst/LVL bytes directly.
Part of #816