docling-project/docling · error · DocumentLoadError

Could not load Box Note document with hash {self.document_ha

Error message

Could not load Box Note document with hash {self.document_hash}.

What it means

Raised by the benchmark's post-conversion assertion in _run_benchmark() after converting a generated single-sheet workbook. The script builds exactly one worksheet whose merged ranges should be parsed by Docling's XLSX pipeline into exactly one TableModel, and treats any other table count as a failure of the assumption being benchmarked. It fires when DocumentConverter.convert() on the temporary .xlsx yields zero tables (conversion produced no table structure) or more than one (the sheet was split into multiple tables, e.g. per merged range or per region).

Source

Thrown at docling/backend/boxnote_backend.py:64

    """

    @override
    def __init__(self, in_doc: InputDocument, path_or_stream: BytesIO | Path):
        super().__init__(in_doc, path_or_stream)

        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

View on GitHub (pinned to 61d76f1ff3)

Solutions

  1. Print or inspect document.tables (count, origins, and each table's data.grid) before the raise to see whether the sheet was split or dropped entirely — that tells you which side of the contract changed.
  2. If you intentionally changed the XLSX backend's segmentation (e.g. one table per merged range), update the benchmark's expectation: replace the strict `len(document.tables) != 1` check with the new expected count, or assert against the sum of all tables' rows.
  3. If tables is empty, verify the installed docling matches this checkout (`pip show docling`, run via `uv run` from the repo) and that InputFormat.XLSX conversion isn't being skipped or raising a swallowed error; check converter.convert() status/errors on the ConversionResult.
  4. Pin/upgrade the docling version whose XLSX backend is known to emit one table per worksheet for this workbook shape, since the assertion encodes that version's behavior.

Example fix

// before (perfs/xlsx_merged_cells.py:148-152)
if len(document.tables) != 1:
    raise RuntimeError(
        f"expected one table for {merge_count} merges, got {len(document.tables)}"
    )
table = document.tables[0]

// after - tolerate a backend that splits the sheet, assert on totals
table = document.tables[0] if document.tables else None
if table is None:
    raise RuntimeError(f"no tables produced for {merge_count} merges")
expected_rows = merge_count * len(document.tables)
if sum(t.data.num_rows for t in document.tables) != expected_rows:
    raise RuntimeError(
        f"expected {expected_rows} total rows for {merge_count} merges "
        f"across {len(document.tables)} tables"
    )
Defensive patterns

Strategy: validation

Validate before calling

document = converter.convert(workbook_path).document
table_count = len(document.tables)
if table_count == 0:
    raise SystemExit("XLSX conversion produced no tables; check backend/format detection")
# expected: one table per worksheet in the current backend contract
if table_count != 1:
    print(f"warning: sheet split into {table_count} tables", file=sys.stderr)

Type guard

def has_single_table(document) -> bool:
    """Narrow a DoclingDocument expected to hold exactly one XLSX worksheet table."""
    return len(document.tables) == 1 and document.tables[0].data is not None

Try / catch

try:
    document = converter.convert(workbook_path).document
except ConversionError as exc:
    raise SystemExit(f"conversion failed for {workbook_path}: {exc}") from exc
if len(document.tables) != 1:
    # record the deviation instead of aborting the whole benchmark sweep
    tqdm.write(f"table count {len(document.tables)} for merge_count={merge_count}", file=sys.stderr)

Prevention

When it happens

Trigger: Calling converter.convert(workbook_path).document on the generated workbook and inspecting len(document.tables) after a Docling upgrade that changes XLSX table segmentation (one table per worksheet vs. one per merged range/row group); an XLSX backend or openpyxl version change that stops emitting the sheet as a single table; the file being misdetected as a non-XLSX format so tables is empty; passing merge_count values that trigger different backend code paths (e.g. very large sheets hitting a splitting heuristic).

Common situations: Running the perf script on a Docling branch/PR that changes MsPowerpointDocumentBackend/XlsxDocumentBackend table emission; benchmarking after a dependency bump (docling-core, openpyxl, python-docx stack) where the XLSX backend's tables-per-sheet contract changed; running the script against an installed docling that differs from the checkout; adding multi-sheet or differently-shaped workbooks to the benchmark without updating the assertion.

Related errors


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