mirror of
https://github.com/DS4SD/docling.git
synced 2025-12-08 12:48:28 +00:00
fix: do not consider singleton cells in xlsx as TableItems but rather TextItems (#2589)
fix: do not handle 1x1 cell as a tableitem but as a textitem Signed-off-by: glypt <8trash-can8@protonmail.ch>
This commit is contained in:
@@ -8,6 +8,7 @@ from docling_core.types.doc import (
|
|||||||
ContentLayer,
|
ContentLayer,
|
||||||
CoordOrigin,
|
CoordOrigin,
|
||||||
DocItem,
|
DocItem,
|
||||||
|
DocItemLabel,
|
||||||
DoclingDocument,
|
DoclingDocument,
|
||||||
DocumentOrigin,
|
DocumentOrigin,
|
||||||
GroupLabel,
|
GroupLabel,
|
||||||
@@ -31,6 +32,7 @@ from docling.backend.abstract_backend import (
|
|||||||
DeclarativeDocumentBackend,
|
DeclarativeDocumentBackend,
|
||||||
PaginatedDocumentBackend,
|
PaginatedDocumentBackend,
|
||||||
)
|
)
|
||||||
|
from docling.datamodel.backend_options import MsExcelBackendOptions
|
||||||
from docling.datamodel.base_models import InputFormat
|
from docling.datamodel.base_models import InputFormat
|
||||||
from docling.datamodel.document import InputDocument
|
from docling.datamodel.document import InputDocument
|
||||||
|
|
||||||
@@ -116,18 +118,22 @@ class MsExcelDocumentBackend(DeclarativeDocumentBackend, PaginatedDocumentBacken
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
def __init__(
|
def __init__(
|
||||||
self, in_doc: "InputDocument", path_or_stream: Union[BytesIO, Path]
|
self,
|
||||||
|
in_doc: "InputDocument",
|
||||||
|
path_or_stream: Union[BytesIO, Path],
|
||||||
|
options: MsExcelBackendOptions = MsExcelBackendOptions(),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the MsExcelDocumentBackend object.
|
"""Initialize the MsExcelDocumentBackend object.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
in_doc: The input document object.
|
in_doc: The input document object.
|
||||||
path_or_stream: The path or stream to the Excel file.
|
path_or_stream: The path or stream to the Excel file.
|
||||||
|
options: Backend options for Excel parsing.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
RuntimeError: An error occurred parsing the file.
|
RuntimeError: An error occurred parsing the file.
|
||||||
"""
|
"""
|
||||||
super().__init__(in_doc, path_or_stream)
|
super().__init__(in_doc, path_or_stream, options)
|
||||||
|
|
||||||
# Initialise the parents for the hierarchy
|
# Initialise the parents for the hierarchy
|
||||||
self.max_levels = 10
|
self.max_levels = 10
|
||||||
@@ -277,51 +283,83 @@ class MsExcelDocumentBackend(DeclarativeDocumentBackend, PaginatedDocumentBacken
|
|||||||
content_layer = self._get_sheet_content_layer(sheet)
|
content_layer = self._get_sheet_content_layer(sheet)
|
||||||
tables = self._find_data_tables(sheet)
|
tables = self._find_data_tables(sheet)
|
||||||
|
|
||||||
|
treat_singleton_as_text = (
|
||||||
|
isinstance(self.options, MsExcelBackendOptions)
|
||||||
|
and self.options.treat_singleton_as_text
|
||||||
|
)
|
||||||
|
|
||||||
for excel_table in tables:
|
for excel_table in tables:
|
||||||
origin_col = excel_table.anchor[0]
|
origin_col = excel_table.anchor[0]
|
||||||
origin_row = excel_table.anchor[1]
|
origin_row = excel_table.anchor[1]
|
||||||
num_rows = excel_table.num_rows
|
num_rows = excel_table.num_rows
|
||||||
num_cols = excel_table.num_cols
|
num_cols = excel_table.num_cols
|
||||||
|
|
||||||
table_data = TableData(
|
if (
|
||||||
num_rows=num_rows,
|
treat_singleton_as_text
|
||||||
num_cols=num_cols,
|
and num_rows == 1
|
||||||
table_cells=[],
|
and num_cols == 1
|
||||||
)
|
and excel_table.data
|
||||||
|
):
|
||||||
for excel_cell in excel_table.data:
|
page_no = self.workbook.index(sheet) + 1
|
||||||
cell = TableCell(
|
doc.add_text(
|
||||||
text=excel_cell.text,
|
text=excel_table.data[0].text,
|
||||||
row_span=excel_cell.row_span,
|
label=DocItemLabel.TEXT,
|
||||||
col_span=excel_cell.col_span,
|
parent=self.parents[0],
|
||||||
start_row_offset_idx=excel_cell.row,
|
prov=ProvenanceItem(
|
||||||
end_row_offset_idx=excel_cell.row + excel_cell.row_span,
|
page_no=page_no,
|
||||||
start_col_offset_idx=excel_cell.col,
|
charspan=(0, 0),
|
||||||
end_col_offset_idx=excel_cell.col + excel_cell.col_span,
|
bbox=BoundingBox.from_tuple(
|
||||||
column_header=excel_cell.row == 0,
|
(
|
||||||
row_header=False,
|
origin_col,
|
||||||
)
|
origin_row,
|
||||||
table_data.table_cells.append(cell)
|
origin_col + num_cols,
|
||||||
|
origin_row + num_rows,
|
||||||
page_no = self.workbook.index(sheet) + 1
|
),
|
||||||
doc.add_table(
|
origin=CoordOrigin.TOPLEFT,
|
||||||
data=table_data,
|
|
||||||
parent=self.parents[0],
|
|
||||||
prov=ProvenanceItem(
|
|
||||||
page_no=page_no,
|
|
||||||
charspan=(0, 0),
|
|
||||||
bbox=BoundingBox.from_tuple(
|
|
||||||
(
|
|
||||||
origin_col,
|
|
||||||
origin_row,
|
|
||||||
origin_col + num_cols,
|
|
||||||
origin_row + num_rows,
|
|
||||||
),
|
),
|
||||||
origin=CoordOrigin.TOPLEFT,
|
|
||||||
),
|
),
|
||||||
),
|
content_layer=content_layer,
|
||||||
content_layer=content_layer,
|
)
|
||||||
)
|
else:
|
||||||
|
table_data = TableData(
|
||||||
|
num_rows=num_rows,
|
||||||
|
num_cols=num_cols,
|
||||||
|
table_cells=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
for excel_cell in excel_table.data:
|
||||||
|
cell = TableCell(
|
||||||
|
text=excel_cell.text,
|
||||||
|
row_span=excel_cell.row_span,
|
||||||
|
col_span=excel_cell.col_span,
|
||||||
|
start_row_offset_idx=excel_cell.row,
|
||||||
|
end_row_offset_idx=excel_cell.row + excel_cell.row_span,
|
||||||
|
start_col_offset_idx=excel_cell.col,
|
||||||
|
end_col_offset_idx=excel_cell.col + excel_cell.col_span,
|
||||||
|
column_header=excel_cell.row == 0,
|
||||||
|
row_header=False,
|
||||||
|
)
|
||||||
|
table_data.table_cells.append(cell)
|
||||||
|
|
||||||
|
page_no = self.workbook.index(sheet) + 1
|
||||||
|
doc.add_table(
|
||||||
|
data=table_data,
|
||||||
|
parent=self.parents[0],
|
||||||
|
prov=ProvenanceItem(
|
||||||
|
page_no=page_no,
|
||||||
|
charspan=(0, 0),
|
||||||
|
bbox=BoundingBox.from_tuple(
|
||||||
|
(
|
||||||
|
origin_col,
|
||||||
|
origin_row,
|
||||||
|
origin_col + num_cols,
|
||||||
|
origin_row + num_rows,
|
||||||
|
),
|
||||||
|
origin=CoordOrigin.TOPLEFT,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
content_layer=content_layer,
|
||||||
|
)
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|||||||
@@ -71,12 +71,26 @@ class PdfBackendOptions(BaseBackendOptions):
|
|||||||
password: Optional[SecretStr] = None
|
password: Optional[SecretStr] = None
|
||||||
|
|
||||||
|
|
||||||
|
class MsExcelBackendOptions(BaseBackendOptions):
|
||||||
|
"""Options specific to the MS Excel backend."""
|
||||||
|
|
||||||
|
kind: Literal["xlsx"] = Field("xlsx", exclude=True, repr=False)
|
||||||
|
treat_singleton_as_text: bool = Field(
|
||||||
|
False,
|
||||||
|
description=(
|
||||||
|
"Whether to treat singleton cells (1x1 tables with empty neighboring "
|
||||||
|
"cells) as TextItem instead of TableItem."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
BackendOptions = Annotated[
|
BackendOptions = Annotated[
|
||||||
Union[
|
Union[
|
||||||
DeclarativeBackendOptions,
|
DeclarativeBackendOptions,
|
||||||
HTMLBackendOptions,
|
HTMLBackendOptions,
|
||||||
MarkdownBackendOptions,
|
MarkdownBackendOptions,
|
||||||
PdfBackendOptions,
|
PdfBackendOptions,
|
||||||
|
MsExcelBackendOptions,
|
||||||
],
|
],
|
||||||
Field(discriminator="kind"),
|
Field(discriminator="kind"),
|
||||||
]
|
]
|
||||||
|
|||||||
4
tests/data/groundtruth/docling_v2/xlsx_05_table_with_title.xlsx.itxt
vendored
Normal file
4
tests/data/groundtruth/docling_v2/xlsx_05_table_with_title.xlsx.itxt
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
item-0 at level 0: unspecified: group _root_
|
||||||
|
item-1 at level 1: section: group sheet: Duck Observations
|
||||||
|
item-2 at level 2: table with [1x1]
|
||||||
|
item-3 at level 2: table with [7x2]
|
||||||
578
tests/data/groundtruth/docling_v2/xlsx_05_table_with_title.xlsx.json
vendored
Normal file
578
tests/data/groundtruth/docling_v2/xlsx_05_table_with_title.xlsx.json
vendored
Normal file
@@ -0,0 +1,578 @@
|
|||||||
|
{
|
||||||
|
"schema_name": "DoclingDocument",
|
||||||
|
"version": "1.7.0",
|
||||||
|
"name": "xlsx_05_table_with_title",
|
||||||
|
"origin": {
|
||||||
|
"mimetype": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
"binary_hash": 18126553641942797758,
|
||||||
|
"filename": "xlsx_05_table_with_title.xlsx",
|
||||||
|
"uri": null
|
||||||
|
},
|
||||||
|
"furniture": {
|
||||||
|
"self_ref": "#/furniture",
|
||||||
|
"parent": null,
|
||||||
|
"children": [],
|
||||||
|
"content_layer": "furniture",
|
||||||
|
"name": "_root_",
|
||||||
|
"label": "unspecified"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"self_ref": "#/body",
|
||||||
|
"parent": null,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"cref": "#/groups/0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"content_layer": "body",
|
||||||
|
"name": "_root_",
|
||||||
|
"label": "unspecified"
|
||||||
|
},
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"self_ref": "#/groups/0",
|
||||||
|
"parent": {
|
||||||
|
"cref": "#/body"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"cref": "#/tables/0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cref": "#/tables/1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"content_layer": "body",
|
||||||
|
"name": "sheet: Duck Observations",
|
||||||
|
"label": "section"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"texts": [],
|
||||||
|
"pictures": [],
|
||||||
|
"tables": [
|
||||||
|
{
|
||||||
|
"self_ref": "#/tables/0",
|
||||||
|
"parent": {
|
||||||
|
"cref": "#/groups/0"
|
||||||
|
},
|
||||||
|
"children": [],
|
||||||
|
"content_layer": "body",
|
||||||
|
"label": "table",
|
||||||
|
"prov": [
|
||||||
|
{
|
||||||
|
"page_no": 1,
|
||||||
|
"bbox": {
|
||||||
|
"l": 1.0,
|
||||||
|
"t": 1.0,
|
||||||
|
"r": 2.0,
|
||||||
|
"b": 2.0,
|
||||||
|
"coord_origin": "TOPLEFT"
|
||||||
|
},
|
||||||
|
"charspan": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"captions": [],
|
||||||
|
"references": [],
|
||||||
|
"footnotes": [],
|
||||||
|
"image": null,
|
||||||
|
"data": {
|
||||||
|
"table_cells": [
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 0,
|
||||||
|
"end_row_offset_idx": 1,
|
||||||
|
"start_col_offset_idx": 0,
|
||||||
|
"end_col_offset_idx": 1,
|
||||||
|
"text": "Number of freshwater ducks per year",
|
||||||
|
"column_header": true,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"num_rows": 1,
|
||||||
|
"num_cols": 1,
|
||||||
|
"grid": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 0,
|
||||||
|
"end_row_offset_idx": 1,
|
||||||
|
"start_col_offset_idx": 0,
|
||||||
|
"end_col_offset_idx": 1,
|
||||||
|
"text": "Number of freshwater ducks per year",
|
||||||
|
"column_header": true,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"annotations": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"self_ref": "#/tables/1",
|
||||||
|
"parent": {
|
||||||
|
"cref": "#/groups/0"
|
||||||
|
},
|
||||||
|
"children": [],
|
||||||
|
"content_layer": "body",
|
||||||
|
"label": "table",
|
||||||
|
"prov": [
|
||||||
|
{
|
||||||
|
"page_no": 1,
|
||||||
|
"bbox": {
|
||||||
|
"l": 1.0,
|
||||||
|
"t": 3.0,
|
||||||
|
"r": 3.0,
|
||||||
|
"b": 10.0,
|
||||||
|
"coord_origin": "TOPLEFT"
|
||||||
|
},
|
||||||
|
"charspan": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"captions": [],
|
||||||
|
"references": [],
|
||||||
|
"footnotes": [],
|
||||||
|
"image": null,
|
||||||
|
"data": {
|
||||||
|
"table_cells": [
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 0,
|
||||||
|
"end_row_offset_idx": 1,
|
||||||
|
"start_col_offset_idx": 0,
|
||||||
|
"end_col_offset_idx": 1,
|
||||||
|
"text": "Year",
|
||||||
|
"column_header": true,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 0,
|
||||||
|
"end_row_offset_idx": 1,
|
||||||
|
"start_col_offset_idx": 1,
|
||||||
|
"end_col_offset_idx": 2,
|
||||||
|
"text": "Freshwater Ducks",
|
||||||
|
"column_header": true,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 1,
|
||||||
|
"end_row_offset_idx": 2,
|
||||||
|
"start_col_offset_idx": 0,
|
||||||
|
"end_col_offset_idx": 1,
|
||||||
|
"text": "2019",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 1,
|
||||||
|
"end_row_offset_idx": 2,
|
||||||
|
"start_col_offset_idx": 1,
|
||||||
|
"end_col_offset_idx": 2,
|
||||||
|
"text": "120",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 2,
|
||||||
|
"end_row_offset_idx": 3,
|
||||||
|
"start_col_offset_idx": 0,
|
||||||
|
"end_col_offset_idx": 1,
|
||||||
|
"text": "2020",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 2,
|
||||||
|
"end_row_offset_idx": 3,
|
||||||
|
"start_col_offset_idx": 1,
|
||||||
|
"end_col_offset_idx": 2,
|
||||||
|
"text": "135",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 3,
|
||||||
|
"end_row_offset_idx": 4,
|
||||||
|
"start_col_offset_idx": 0,
|
||||||
|
"end_col_offset_idx": 1,
|
||||||
|
"text": "2021",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 3,
|
||||||
|
"end_row_offset_idx": 4,
|
||||||
|
"start_col_offset_idx": 1,
|
||||||
|
"end_col_offset_idx": 2,
|
||||||
|
"text": "150",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 4,
|
||||||
|
"end_row_offset_idx": 5,
|
||||||
|
"start_col_offset_idx": 0,
|
||||||
|
"end_col_offset_idx": 1,
|
||||||
|
"text": "2022",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 4,
|
||||||
|
"end_row_offset_idx": 5,
|
||||||
|
"start_col_offset_idx": 1,
|
||||||
|
"end_col_offset_idx": 2,
|
||||||
|
"text": "170",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 5,
|
||||||
|
"end_row_offset_idx": 6,
|
||||||
|
"start_col_offset_idx": 0,
|
||||||
|
"end_col_offset_idx": 1,
|
||||||
|
"text": "2023",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 5,
|
||||||
|
"end_row_offset_idx": 6,
|
||||||
|
"start_col_offset_idx": 1,
|
||||||
|
"end_col_offset_idx": 2,
|
||||||
|
"text": "160",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 6,
|
||||||
|
"end_row_offset_idx": 7,
|
||||||
|
"start_col_offset_idx": 0,
|
||||||
|
"end_col_offset_idx": 1,
|
||||||
|
"text": "2024",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 6,
|
||||||
|
"end_row_offset_idx": 7,
|
||||||
|
"start_col_offset_idx": 1,
|
||||||
|
"end_col_offset_idx": 2,
|
||||||
|
"text": "180",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"num_rows": 7,
|
||||||
|
"num_cols": 2,
|
||||||
|
"grid": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 0,
|
||||||
|
"end_row_offset_idx": 1,
|
||||||
|
"start_col_offset_idx": 0,
|
||||||
|
"end_col_offset_idx": 1,
|
||||||
|
"text": "Year",
|
||||||
|
"column_header": true,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 0,
|
||||||
|
"end_row_offset_idx": 1,
|
||||||
|
"start_col_offset_idx": 1,
|
||||||
|
"end_col_offset_idx": 2,
|
||||||
|
"text": "Freshwater Ducks",
|
||||||
|
"column_header": true,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 1,
|
||||||
|
"end_row_offset_idx": 2,
|
||||||
|
"start_col_offset_idx": 0,
|
||||||
|
"end_col_offset_idx": 1,
|
||||||
|
"text": "2019",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 1,
|
||||||
|
"end_row_offset_idx": 2,
|
||||||
|
"start_col_offset_idx": 1,
|
||||||
|
"end_col_offset_idx": 2,
|
||||||
|
"text": "120",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 2,
|
||||||
|
"end_row_offset_idx": 3,
|
||||||
|
"start_col_offset_idx": 0,
|
||||||
|
"end_col_offset_idx": 1,
|
||||||
|
"text": "2020",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 2,
|
||||||
|
"end_row_offset_idx": 3,
|
||||||
|
"start_col_offset_idx": 1,
|
||||||
|
"end_col_offset_idx": 2,
|
||||||
|
"text": "135",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 3,
|
||||||
|
"end_row_offset_idx": 4,
|
||||||
|
"start_col_offset_idx": 0,
|
||||||
|
"end_col_offset_idx": 1,
|
||||||
|
"text": "2021",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 3,
|
||||||
|
"end_row_offset_idx": 4,
|
||||||
|
"start_col_offset_idx": 1,
|
||||||
|
"end_col_offset_idx": 2,
|
||||||
|
"text": "150",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 4,
|
||||||
|
"end_row_offset_idx": 5,
|
||||||
|
"start_col_offset_idx": 0,
|
||||||
|
"end_col_offset_idx": 1,
|
||||||
|
"text": "2022",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 4,
|
||||||
|
"end_row_offset_idx": 5,
|
||||||
|
"start_col_offset_idx": 1,
|
||||||
|
"end_col_offset_idx": 2,
|
||||||
|
"text": "170",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 5,
|
||||||
|
"end_row_offset_idx": 6,
|
||||||
|
"start_col_offset_idx": 0,
|
||||||
|
"end_col_offset_idx": 1,
|
||||||
|
"text": "2023",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 5,
|
||||||
|
"end_row_offset_idx": 6,
|
||||||
|
"start_col_offset_idx": 1,
|
||||||
|
"end_col_offset_idx": 2,
|
||||||
|
"text": "160",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 6,
|
||||||
|
"end_row_offset_idx": 7,
|
||||||
|
"start_col_offset_idx": 0,
|
||||||
|
"end_col_offset_idx": 1,
|
||||||
|
"text": "2024",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bbox": null,
|
||||||
|
"row_span": 1,
|
||||||
|
"col_span": 1,
|
||||||
|
"start_row_offset_idx": 6,
|
||||||
|
"end_row_offset_idx": 7,
|
||||||
|
"start_col_offset_idx": 1,
|
||||||
|
"end_col_offset_idx": 2,
|
||||||
|
"text": "180",
|
||||||
|
"column_header": false,
|
||||||
|
"row_header": false,
|
||||||
|
"row_section": false,
|
||||||
|
"fillable": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"annotations": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"key_value_items": [],
|
||||||
|
"form_items": [],
|
||||||
|
"pages": {
|
||||||
|
"1": {
|
||||||
|
"size": {
|
||||||
|
"width": 2.0,
|
||||||
|
"height": 7.0
|
||||||
|
},
|
||||||
|
"image": null,
|
||||||
|
"page_no": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
tests/data/groundtruth/docling_v2/xlsx_05_table_with_title.xlsx.md
vendored
Normal file
11
tests/data/groundtruth/docling_v2/xlsx_05_table_with_title.xlsx.md
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
| Number of freshwater ducks per year |
|
||||||
|
|---------------------------------------|
|
||||||
|
|
||||||
|
| Year | Freshwater Ducks |
|
||||||
|
|--------|--------------------|
|
||||||
|
| 2019 | 120 |
|
||||||
|
| 2020 | 135 |
|
||||||
|
| 2021 | 150 |
|
||||||
|
| 2022 | 170 |
|
||||||
|
| 2023 | 160 |
|
||||||
|
| 2024 | 180 |
|
||||||
BIN
tests/data/xlsx/xlsx_05_table_with_title.xlsx
vendored
Normal file
BIN
tests/data/xlsx/xlsx_05_table_with_title.xlsx
vendored
Normal file
Binary file not shown.
@@ -6,9 +6,10 @@ import pytest
|
|||||||
from openpyxl import load_workbook
|
from openpyxl import load_workbook
|
||||||
|
|
||||||
from docling.backend.msexcel_backend import MsExcelDocumentBackend
|
from docling.backend.msexcel_backend import MsExcelDocumentBackend
|
||||||
|
from docling.datamodel.backend_options import MsExcelBackendOptions
|
||||||
from docling.datamodel.base_models import InputFormat
|
from docling.datamodel.base_models import InputFormat
|
||||||
from docling.datamodel.document import ConversionResult, DoclingDocument, InputDocument
|
from docling.datamodel.document import ConversionResult, DoclingDocument, InputDocument
|
||||||
from docling.document_converter import DocumentConverter
|
from docling.document_converter import DocumentConverter, ExcelFormatOption
|
||||||
|
|
||||||
from .test_data_gen_flag import GEN_TEST_DATA
|
from .test_data_gen_flag import GEN_TEST_DATA
|
||||||
from .verify_utils import verify_document, verify_export
|
from .verify_utils import verify_document, verify_export
|
||||||
@@ -227,6 +228,49 @@ def test_inflated_rows_handling(documents) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_table_with_title():
|
||||||
|
"""Test that singleton cells with non-numeric content are treated as TextItem.
|
||||||
|
|
||||||
|
When treat_singleton_as_text option is enabled, 1x1 tables containing non-numeric
|
||||||
|
text should be converted to TextItem instead of TableItem. This test verifies that
|
||||||
|
xlsx_05_table_with_title.xlsx is correctly parsed with this option.
|
||||||
|
"""
|
||||||
|
path = next(
|
||||||
|
item for item in get_excel_paths() if item.stem == "xlsx_05_table_with_title"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create converter with treat_singleton_as_text=True
|
||||||
|
options = MsExcelBackendOptions(treat_singleton_as_text=True)
|
||||||
|
format_options = {InputFormat.XLSX: ExcelFormatOption(backend_options=options)}
|
||||||
|
converter = DocumentConverter(
|
||||||
|
allowed_formats=[InputFormat.XLSX], format_options=format_options
|
||||||
|
)
|
||||||
|
|
||||||
|
conv_result: ConversionResult = converter.convert(path)
|
||||||
|
doc: DoclingDocument = conv_result.document
|
||||||
|
|
||||||
|
# With treat_singleton_as_text=True, the singleton title cell should be a TextItem
|
||||||
|
texts = list(doc.texts)
|
||||||
|
tables = list(doc.tables)
|
||||||
|
|
||||||
|
assert len(texts) == 1, f"Should have 1 text item (the title), got {len(texts)}"
|
||||||
|
assert len(tables) == 1, f"Should have 1 table, got {len(tables)}"
|
||||||
|
|
||||||
|
# Verify the text item contains the title
|
||||||
|
assert texts[0].text == "Number of freshwater ducks per year", (
|
||||||
|
f"Text should be 'Number of freshwater ducks per year', got '{texts[0].text}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify table dimensions
|
||||||
|
table = tables[0]
|
||||||
|
assert table.data.num_rows == 7, (
|
||||||
|
f"Table should have 7 rows, got {table.data.num_rows}"
|
||||||
|
)
|
||||||
|
assert table.data.num_cols == 2, (
|
||||||
|
f"Table should have 2 columns, got {table.data.num_cols}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_bytesio_stream():
|
def test_bytesio_stream():
|
||||||
"""Test that Excel files can be loaded from BytesIO streams.
|
"""Test that Excel files can be loaded from BytesIO streams.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user