docling-project/docling · error · DocumentLoadError

Legacy Box Notes (the pre-August-2022 atext/pool format) are

Error message

Legacy Box Notes (the pre-August-2022 atext/pool format) are not supported yet; only the current Box Note format can be converted.

What it means

The second post-conversion assertion in _run_benchmark(): after confirming exactly one table, it checks that the table's data grid is merge_count rows by 4 columns, matching the generated workbook (one merged A:C range plus a value in D per row, no header row). It fires when Docling's XLSX parsing returns a different grid: extra/missing rows (e.g. a header row added, empty trailing rows included) or different column count (merged A:C collapsed to one column instead of expanded to three, or an extra index column). It signals that the merged-cell expansion semantics assumed by the benchmark no longer hold.

Source

Thrown at docling/backend/boxnote_backend.py:69

        self.data: dict[str, Any] = {}
        try:
            raw = ""
            if isinstance(self.path_or_stream, BytesIO):
                raw = self.path_or_stream.getvalue().decode("utf-8")
            elif isinstance(self.path_or_stream, Path):
                raw = self.path_or_stream.read_text(encoding="utf-8")
            if raw.strip():
                loaded = json.loads(raw)
                if isinstance(loaded, dict):
                    self.data = loaded
        except (json.JSONDecodeError, UnicodeDecodeError, OSError) as e:
            raise DocumentLoadError(
                f"Could not load Box Note document with hash {self.document_hash}."
            ) from e

        if "atext" in self.data and not self.is_valid():
            raise DocumentLoadError(
                "Legacy Box Notes (the pre-August-2022 atext/pool format) are not "
                "supported yet; only the current Box Note format can be converted."
            )

    @override
    def is_valid(self) -> bool:
        return isinstance(self.data.get("doc"), dict)

    @classmethod
    @override
    def supports_pagination(cls) -> bool:
        return False

    @classmethod
    @override
    def supported_formats(cls) -> set[InputFormat]:
        return {InputFormat.BOXNOTE}

View on GitHub (pinned to 61d76f1ff3)

Solutions

  1. Inspect table.data.grid (or export the document to HTML/markdown) to see the actual shape: num_cols == 2 means merged ranges are collapsed rather than expanded; num_rows == merge_count + 1 means a header row was added.
  2. Update the assertion to the current backend contract, e.g. `if table.data.num_rows != merge_count + 1` if headers are now emitted, or `num_cols != 2` if merges are collapsed — and update non_empty_cells / rectangle_area bookkeeping in BenchmarkResult to match.
  3. Pin the benchmark to the docling version whose merged-cell expansion (A:C expanded to 3 columns, 4 total) you intend to measure, e.g. run inside the repo checkout with `uv run python perfs/xlsx_merged_cells.py`.
  4. If the change is unintended (a regression in merged-cell expansion), fix the XLSX backend so num_cols stays 4 and num_rows equals merge_count, keeping the benchmark as the regression guard.

Example fix

// before (perfs/xlsx_merged_cells.py:153-156)
if table.data.num_rows != merge_count or table.data.num_cols != 4:
    raise RuntimeError(
        f"unexpected table dimensions: {table.data.num_rows}x{table.data.num_cols}"
    )

// after - encode the backend's actual contract explicitly
EXPECTED_COLS = 4  # A:C merged range expanded to 3 columns + column D
header_offset = 1 if table.data.num_rows == merge_count + 1 else 0
if table.data.num_cols != EXPECTED_COLS:
    raise RuntimeError(
        f"unexpected column count: {table.data.num_cols} != {EXPECTED_COLS}"
    )
if table.data.num_rows - header_offset != merge_count:
    raise RuntimeError(
        f"unexpected row count: {table.data.num_rows} != {merge_count}"
    )
Defensive patterns

Strategy: validation

Validate before calling

table = document.tables[0]
num_rows, num_cols = table.data.num_rows, table.data.num_cols
# generated workbook: merge_count rows (A:C merged per row) + column D, no header
if num_cols not in (4,):  # extend if the backend collapses merges (2) or adds columns
    print(f"column count changed: {num_cols}", file=sys.stderr)
row_delta = num_rows - merge_count
if row_delta not in (0, 1):  # 1 == backend now emits a header row
    print(f"row count off by {row_delta}", file=sys.stderr)

Type guard

def table_matches_workbook(table, merge_count: int, num_cols: int = 4) -> bool:
    """True when the parsed table grid matches the generated merged-range workbook."""
    return (
        table is not None
        and table.data is not None
        and table.data.num_rows == merge_count
        and table.data.num_cols == num_cols
    )

Try / catch

if not table_matches_workbook(table, merge_count):
    # keep the sweep alive but flag the row/col deviation for triage
    tqdm.write(
        f"dimension drift for merge_count={merge_count}: "
        f"{table.data.num_rows}x{table.data.num_cols}",
        file=sys.stderr,
    )

Prevention

When it happens

Trigger: A Docling version change in how the XLSX backend expands merged ranges into the table grid (collapsing merged cells to a single column yields num_cols == 2 instead of 4); the backend adding a header row or omitting all-empty rows (num_rows becomes merge_count + 1 or fewer); openpyxl/docling-core TableData changes to num_rows/num_cols accounting; benchmarking a branch where cell-level dedup or row-span handling changed.

Common situations: Running the perf harness across a Docling upgrade to compare performance without updating the structural expectations first; a PR that changes MsDocumentBackend merged-cell handling; benchmark results invalidated because the assertion encodes the exact grid contract of one specific docling-core version; running against an installed docling older/newer than the checkout.

Related errors


AI-assisted analysis of docling-project/docling@61d76f1ff3 (2026-08-14). Data as JSON: /api/errors/e1186e0bed7e0c86. Report an issue: GitHub.