diff --git a/src/content-json-schema-defs.ts b/src/content-json-schema-defs.ts index 15ec6fb..a2e123d 100644 --- a/src/content-json-schema-defs.ts +++ b/src/content-json-schema-defs.ts @@ -105,10 +105,10 @@ export const CONTENT_DEFS: Record = { ContentListMembership: { type: 'object', properties: { - numId: { type: 'string' }, + numId: { type: 'string' }, // optional in the Zod source -- depth-only list membership (OOXML drawing paragraphs) carries no numbering identity level: { type: 'integer', minimum: 0, maximum: MAX_SAFE_INTEGER }, }, - required: ['numId', 'level'], + required: ['level'], additionalProperties: false, }, ContentRun: { diff --git a/src/content.test.ts b/src/content.test.ts index a09982f..623cb1d 100644 --- a/src/content.test.ts +++ b/src/content.test.ts @@ -659,6 +659,33 @@ describe('ContentParagraphSchema headingLevel', () => { }); }); +describe('ContentListMembership numId', () => { + it('parses a level-only membership, the shape a format with depth but no numbering identity produces (OOXML drawing paragraphs carry only a:pPr/@lvl)', () => { + const parsed = ContentParagraphSchema.parse({ + kind: 'paragraph', + runs: [{ text: 'Bullet text' }], + list: { level: 1 }, + }); + expect(parsed.list).toEqual({ level: 1 }); + }); + + it('still parses a numId+level membership, the shape a format with a shared numbering definition produces (docx w:numId, ODF minted identity)', () => { + const parsed = ContentParagraphSchema.parse({ + kind: 'paragraph', + runs: [{ text: 'Item one' }], + list: { numId: '1', level: 0 }, + }); + expect(parsed.list).toEqual({ numId: '1', level: 0 }); + }); + + it('keeps level required, so a membership without one does not parse', () => { + expect(ContentParagraphSchema.safeParse({ kind: 'paragraph', runs: [], list: { numId: '1' } }).success).toBe( + false, + ); + expect(ContentParagraphSchema.safeParse({ kind: 'paragraph', runs: [], list: {} }).success).toBe(false); + }); +}); + describe('clampHeadingLevel', () => { it('leaves a level already within 1-6 untouched', () => { expect(clampHeadingLevel(1)).toBe(1); diff --git a/src/content.ts b/src/content.ts index 69b28a9..14f059c 100644 --- a/src/content.ts +++ b/src/content.ts @@ -31,7 +31,7 @@ export const ContentRunSchema = z.object({ export type ContentRun = z.infer; export const ContentListMembershipSchema = z.object({ - numId: z.string(), // w:numId + numId: z.string().optional(), // identifies a shared numbering definition when the source format has one -- docx's w:numId, ODF's minted structural identity -- and is absent when the format carries only a depth (OOXML drawing paragraphs' a:pPr/@lvl), where fabricating one would invent numbering identity the source never had level: z.number().int().nonnegative(), // w:ilvl }); export type ContentListMembership = z.infer;