Excel Backend Processing#
MsExcelDocumentBackend (in docling/backend/msexcel_backend.py) converts XLSX workbooks into DoclingDocument objects. It is used by SimplePipeline and paired with ExcelFormatOption . The backend requires openpyxl (pip install 'docling-slim[format-xlsx]'); it raises ImportError if the package is absent . The workbook is loaded with data_only=True so all formula cells are read as their last computed values.
Each worksheet maps to one page in the output document; chart sheets (Chartsheet) are also supported. For each sheet, the backend runs three extraction passes in order:
- Tables — via flood-fill BFS
- Images — PIL-readable images + EMF/WMF fallback
- Charts — native openpyxl chart objects (both
WorksheetandChartsheet)
After all items are added, children are sorted by top-row position so visual order matches document order.
Table Detection: Flood-Fill BFS#
_find_table_bounds() implements a two-phase algorithm:
Phase 1 — Flood Fill: Starting from an unvisited non-empty cell, BFS explores neighbors in 4 directions. A neighbor is included if it has a cell value or falls within a merged cell range. The gap_tolerance option (MsExcelBackendOptions.gap_tolerance, default 0) extends the search radius by that many empty cells in each direction — non-zero values merge nearby disconnected clusters into one table .
Phase 2 — Grid Extraction: The bounding box of all connected cells is materialized into an ExcelTable. Gaps inside the bounding box become empty cells. Merged cells are tracked to avoid double-counting "shadow" cells .
The outer loop in _find_data_tables() scans the sheet within true data bounds (computed by _find_true_data_bounds(), which checks sheet._cells and merged ranges), marks visited cells, and calls _find_table_bounds() for each unvisited non-empty cell — yielding zero or more ExcelTable objects per sheet.
treat_singleton_as_text (default False): when True, any 1×1 table is emitted as a TextItem instead of a TableItem .
Sheet Filtering#
MsExcelBackendOptions lives in docling/datamodel/backend_options.py. The sheet_names field (default None) accepts a list of sheet names to process; all others are skipped. Matching is case-sensitive. If the filter list contains names not present in the workbook, a warning is logged .
Page numbers are assigned densely across the accepted sheets and respect the page_range limit from InputDocument .
from docling.datamodel.backend_options import MsExcelBackendOptions
from docling.document_converter import DocumentConverter, ExcelFormatOption
from docling.datamodel.base_models import InputFormat
converter = DocumentConverter(
format_options={
InputFormat.XLSX: ExcelFormatOption(
backend_options=MsExcelBackendOptions(
sheet_names=["Summary", "Q4 Results"],
gap_tolerance=1,
treat_singleton_as_text=True,
)
)
}
)
Image Extraction#
_find_images_in_sheet() iterates sheet._images (populated by openpyxl) and calls ImageRef.from_pil() for each. Bounding boxes use cell-index coordinates derived from the anchor object (TwoCellAnchor → exact span; OneCellAnchor → 1×1 cell) .
EMF/WMF fallback: openpyxl silently drops Windows Metafile images. MsExcelDocumentBackend suppresses the openpyxl warning during workbook load , re-reads the raw bytes from the XLSX zip archive, and converts them via LibreOffice → PDF → pypdfium2 render through _convert_emf_to_pil(). If LibreOffice is absent, affected images are skipped and a warning is logged .
Chart Extraction#
_find_chart_in_sheet() is called for both Worksheet and Chartsheet objects. Controlled by MsExcelBackendOptions.parse_charts (default True) .
For each chart in sheet._charts:
- Classification: The chart's
tagnameis looked up in_CHART_TAGNAME_TO_CLASSIFICATION, mapping DrawingML element names (barChart,lineChart,pieChart,scatterChart, etc.) toPictureClassificationLabelvalues. Unknown types fall back toOTHER_CHART. - Title:
_chart_title_text()extracts the plain-text title from the DrawingML rich-text structure. - Data reconstruction:
_chart_to_table_data()resolves each series' cell-range references (e.g.'Sheet1'!$B$2:$B$7) into cached values (the workbook is loaded withdata_only=True) and reconstructs the data as aTableDatagrid: categories in the first column, one column per series. Scatter charts usexVal/yValreferences .
Each chart is emitted as a PictureItem with PictureMeta carrying both the PictureClassificationMetaField and a TabularChartMetaField — the same data shape produced by the VLM-based chart extraction path for PDF/PPTX .
Comments#
Cell comments are extracted during the table scan . Old-style comments are read via cell.comment. Threaded comments (Excel 365+) are parsed directly from the XLSX zip XML (xl/threadedComments/threadedComment{n}.xml) via _parse_threaded_comments() — this only works when the input is a Path, not a BytesIO stream. Comments are emitted as DocItem objects in ContentLayer.NOTES and linked to their parent cell item.
Key Source Files#
| File | Purpose |
|---|---|
docling/backend/msexcel_backend.py | Full XLSX conversion pipeline |
docling/datamodel/backend_options.py | MsExcelBackendOptions definition |
docling/document_converter.py | ExcelFormatOption wiring |
| Chart Extraction KB article | VLM chart path and PictureItem output shape |
| LibreOffice Integration KB article | EMF/WMF conversion details |