From 0aff91cd31157a5938949c4b0c0708a3664879bf Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Mon, 17 Aug 2026 22:38:11 +0100 Subject: [PATCH 1/2] refactor: extract text:list numId minting and item walking into typed/shared/list.ts The odt reader's list machinery -- the per-encounter numId counter, the ordered:/bullet: kind-prefix resolution, and the text:list/text:list-item structural walk -- moves verbatim into a shared module so the odp reader can reuse it against the identical text:list construct inside slide text boxes. readOdfListParagraphs attaches list membership itself, so readParagraphOrHeading loses its list parameter. No behaviour change. --- src/index.ts | 3 ++ src/typed/odt/read.ts | 78 +++++++--------------------------------- src/typed/shared/list.ts | 73 +++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 66 deletions(-) create mode 100644 src/typed/shared/list.ts diff --git a/src/index.ts b/src/index.ts index 3ca1214..733419f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -101,6 +101,9 @@ export { readOdfMetadata, META_PART } from './typed/shared/metadata'; export { readOdfParagraph } from './typed/shared/paragraph'; +export { mintOdfListNumId, readOdfListParagraphs, resolveOdfListKind } from './typed/shared/list'; +export type { OdfListIdState, OdfListParagraphReader } from './typed/shared/list'; + export { readOdfTable } from './typed/shared/table'; export { parseOdfTransform, applyOdfTransform, netRotationDeg, resolveOdfShapeGeometry, composeOdfGroupTransform } from './typed/shared/transform'; diff --git a/src/typed/odt/read.ts b/src/typed/odt/read.ts index 676b67a..9ad2cb5 100644 --- a/src/typed/odt/read.ts +++ b/src/typed/odt/read.ts @@ -1,8 +1,9 @@ -import type { ContentBlock, ContentListMembership, ContentParagraph, ContentSection, LayoutMetadata, Margins, PageSize } from 'document-schema.js'; +import type { ContentBlock, ContentParagraph, ContentSection, LayoutMetadata, Margins, PageSize } from 'document-schema.js'; import { PAGE_SIZE_A4 } from 'document-schema.js'; import type { Package } from '../../model/package'; import type { XmlElement, XmlNode } from '../../model/node'; import { rootElement, findChildElement, childrenWithTag, attrValue } from '../../xml/query'; +import { mintOdfListNumId, readOdfListParagraphs, type OdfListIdState } from '../shared/list'; import { readOdfParagraph } from '../shared/paragraph'; import { readOdfTable } from '../shared/table'; import { readOdfMetadata } from '../shared/metadata'; @@ -11,18 +12,11 @@ import { parseOdfLength } from '../shared/units'; // Package -> OdtDocument: the first end-to-end ODF content reader, producing GENUINE ContentSection[] values (document-schema.js's own pivot type, the one documents.js's docx flow/pagination engine already consumes) from a real .odt package. This is the concrete proof of the whole odf.js architectural bet -- that odt and docx can share one pivot and one layout algorithm despite being completely unrelated XML formats -- so every mapping below is deliberately expressed in terms document-schema.js already defines, never a lookalike shape of its own. // -// This reader is deliberately thin: paragraph/run reading (readOdfParagraph) and table reading (readOdfTable) already live in typed/shared/ -- built for reuse across odt/ods/odp/odg, not odt-specific -- so this module's own job is the odt-SPECIFIC structure those shared readers have no opinion on: walking office:text's actual block sequence (paragraphs interleaved with lists and tables, in document order), turning a text:list's purely structural nesting into ContentParagraph.list (numId/level), mapping text:h's own text:outline-level onto a docx-equivalent styleId alongside document-schema.js's own headingLevel field, and resolving the document's own page geometry from its first master page. readOdfParagraph is tag-agnostic (it never inspects which tag its own caller found it at) and reads text:h exactly as it reads text:p, so this reader calls straight through to it for both, then overrides ONLY the resulting heading identity (styleId plus headingLevel) for a heading -- see readParagraphOrHeading below. +// This reader is deliberately thin: paragraph/run reading (readOdfParagraph) and table reading (readOdfTable) already live in typed/shared/ -- built for reuse across odt/ods/odp/odg, not odt-specific -- so this module's own job is the odt-SPECIFIC structure those shared readers have no opinion on: walking office:text's actual block sequence (paragraphs interleaved with lists and tables, in document order), mapping text:h's own text:outline-level onto a docx-equivalent styleId alongside document-schema.js's own headingLevel field, and resolving the document's own page geometry from its first master page. readOdfParagraph is tag-agnostic (it never inspects which tag its own caller found it at) and reads text:h exactly as it reads text:p, so this reader calls straight through to it for both, then overrides ONLY the resulting heading identity (styleId plus headingLevel) for a heading -- see readParagraphOrHeading below. // -// SCOPE, matching ooxml.js's own readDocx's already-established, deliberately narrower gaps (see that module's own top-of-file note for the identical reasoning applied to OOXML): footnotes/endnotes, annotations/comments, header/footer content, inline frames/images (draw:frame inside text flow -- odp/odg's job, not odt's), fields beyond their cached/last-computed text value, change tracking (text:change-*), cell borders, explicit page breaks (fo:break-before/fo:break-after -- not modelled by styles/properties.ts's StyleProperties, so the cascade this reader relies on can't surface it; a genuinely separate, bounded follow-on), and documents with more than one master page (only the first is read, in document order -- see readFirstMasterPageGeometry below). A text:h or a nested text:list/text:table inside a table cell is also out of scope here, inherited directly from readOdfTable's own cell reading (table:table-cell content there is read as text:p only) -- not a gap introduced by this module. src/typed/formula/read.ts does not exist yet at the time this reader was written, so there is no formula-embedding recursion to account for either. List marker GLYPHS (the exact bullet character or number format string) remain unread -- only the ordered-vs-bullet KIND is resolved (see resolveListKind below), since that is what downstream consumers need to render
    vs