Skip to content

Bug: createNumbering crashes when lvlText is null - "Cannot read properties of null (reading 'replace')" #1675

@rccoe

Description

@rccoe

Description

When processing documents with incomplete or corrupted list numbering definitions (where lvlText is null), SuperDoc throws an unhandled error:

TypeError: Cannot read properties of null (reading 'replace')
    at createNumbering (SuperConverter-D7wt-eeZ.cjs:19862)
    at Array.reduce (<anonymous>)
    at createNumbering (SuperConverter-D7wt-eeZ.cjs:19861)
    at generateNumbering (SuperConverter-D7wt-eeZ.cjs:19867)
    at handleDecimal (SuperConverter-D7wt-eeZ.cjs:19827)
    at Object.generateOrderedListIndex (SuperConverter-D7wt-eeZ.cjs:19858)

Root Cause

In shared/common/list-numbering/index.ts, the createNumbering function does not handle null lvlText:

const createNumbering = (values, lvlText) => {
  return values.reduce((acc, value, index2) => {
    return Number(value) > 9 
      ? acc.replace(/^0/, "").replace(`%${index2 + 1}`, value) 
      : acc.replace(`%${index2 + 1}`, value);
  }, lvlText);  // <-- lvlText can be null here
};

The call chain is:

  1. numberingPlugin.appendTransaction() calls ListHelpers.getListDefinitionDetails()
  2. lvlText is destructured from the result (may be null or undefined)
  3. For non-bullet lists, generateOrderedListIndex({ listLevel, lvlText, ... }) is called
  4. This calls handleDecimal(path, lvlText)generateNumbering(path, lvlText, formatter)createNumbering(formattedValues, lvlText)
  5. createNumbering attempts lvlText.replace(...) which throws when lvlText is null

Suggested Fix

Option 1: Add null check in createNumbering:

const createNumbering = (values: number[], lvlText: string | null): string | null => {
  if (!lvlText) return null;
  return values.reduce((acc, value, index) => {
    return Number(value) > 9 
      ? acc.replace(/^0/, "").replace(`%${index + 1}`, String(value)) 
      : acc.replace(`%${index + 1}`, String(value));
  }, lvlText);
};

Option 2: Add null check in generateOrderedListIndex:

const generateOrderedListIndex = ({ listLevel, lvlText, listNumberingType, customFormat }) => {
  if (!lvlText) return null;  // Add early return
  const handler = listIndexMap[listNumberingType];
  return handler ? handler(listLevel, lvlText, customFormat) : null;
};

Option 3: Guard in numberingPlugin.appendTransaction() before calling generateOrderedListIndex:

if (listNumberingType !== "bullet") {
  if (lvlText) {  // Add guard
    markerText = generateOrderedListIndex({ listLevel: path, lvlText, listNumberingType, customFormat });
  }
}

Environment

  • SuperDoc version: 1.2.1
  • Usage context: Headless Node.js environment with JSDOM
  • Trigger: Documents with list paragraphs that have numbering properties but incomplete/missing lvlText in the numbering definitions

Reproduction

The issue occurs when a document has:

  1. A paragraph with numberingProperties (numId, ilvl)
  2. The corresponding abstract numbering definition is missing or has a null lvlText for that level

This can happen with:

  • Corrupted DOCX files
  • Documents with partial numbering definitions
  • Edge cases in numbering inheritance

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions