docling-core 2.77.0__tar.gz → 2.78.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {docling_core-2.77.0 → docling_core-2.78.0}/PKG-INFO +1 -1
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/hierarchical_chunker.py +54 -13
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/serializer/html.py +39 -10
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/serializer/html_styles.py +0 -6
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/doc/__init__.py +1 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/doc/document.py +103 -31
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core.egg-info/PKG-INFO +1 -1
- {docling_core-2.77.0 → docling_core-2.78.0}/pyproject.toml +2 -2
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_docling_doc.py +240 -4
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_hierarchical_chunker.py +92 -1
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_hybrid_chunker.py +20 -2
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_metadata.py +2 -2
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_search_meta.py +1 -1
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_serialization.py +161 -6
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_utils.py +22 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/LICENSE +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/README.md +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/cli/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/cli/serialize.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/cli/view.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/experimental/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/experimental/doclang.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/experimental/serializer/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/experimental/serializer/outline.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/py.typed +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/resources/schemas/doc/ANN.json +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/resources/schemas/doc/DOC.json +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/resources/schemas/doc/OCR-output.json +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/resources/schemas/doc/RAW.json +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/resources/schemas/generated/ccs_document_schema.json +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/resources/schemas/generated/minimal_document_schema_flat.json +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/resources/schemas/search/search_doc_mapping.json +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/resources/schemas/search/search_doc_mapping_v2.json +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/search/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/search/json_schema_to_search_mapper.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/search/mapping.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/search/meta.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/search/package.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/base.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/chunk_expander.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/code_chunking/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/code_chunking/_language_code_chunkers.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/code_chunking/_utils.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/code_chunking/base_code_chunking_strategy.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/code_chunking/code_chunk.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/code_chunking/standard_code_chunking_strategy.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/doc_chunk.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/hybrid_chunker.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/line_chunker.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/page_chunker.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/tokenizer/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/tokenizer/base.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/tokenizer/huggingface.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/tokenizer/openai.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/profiler/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/profiler/doc_profiler.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/serializer/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/serializer/azure.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/serializer/base.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/serializer/common.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/serializer/doctags.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/serializer/latex.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/serializer/markdown.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/serializer/markdown_excel.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/serializer/plain_text.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/serializer/webvtt.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/visualizer/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/visualizer/base.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/visualizer/key_value_visualizer.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/visualizer/layout_visualizer.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/visualizer/reading_order_visualizer.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/visualizer/table_visualizer.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/base.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/doc/base.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/doc/labels.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/doc/page.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/doc/tokens.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/doc/utils.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/doc/webvtt.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/gen/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/gen/generic.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/io/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/legacy_doc/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/legacy_doc/base.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/legacy_doc/doc_ann.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/legacy_doc/doc_ocr.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/legacy_doc/doc_raw.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/legacy_doc/document.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/legacy_doc/tokens.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/nlp/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/nlp/qa.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/nlp/qa_labels.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/rec/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/rec/attribute.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/rec/base.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/rec/predicate.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/rec/record.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/rec/statement.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/types/rec/subject.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/utils/__init__.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/utils/alias.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/utils/file.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/utils/generate_docs.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/utils/generate_jsonschema.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/utils/legacy.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/utils/settings.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/utils/validate.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core/utils/validators.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core.egg-info/SOURCES.txt +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core.egg-info/dependency_links.txt +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core.egg-info/entry_points.txt +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core.egg-info/requires.txt +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/docling_core.egg-info/top_level.txt +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/setup.cfg +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_azure_serializer.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_base.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_chunk_expander.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_code_chunker.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_code_chunking_strategy.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_collection.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_data_gen_flag.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_deserializer_doclang.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_doc_base.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_doc_legacy_convert.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_doc_schema.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_doc_schema_extractor.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_doctags_load.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_hierarchy.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_json_schema_to_search_mapper.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_latex_serialization.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_line_chunker.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_nlp_qa.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_otsl_table_export.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_page.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_page_chunker.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_plain_text_serialization.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_profiler.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_rec_schema.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_regions_to_table.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_serialization_doclang.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_serialization_doctag.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_serialization_outline.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_visualization.py +0 -0
- {docling_core-2.77.0 → docling_core-2.78.0}/test/test_webvtt.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docling-core
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.78.0
|
|
4
4
|
Summary: A python library to define and validate data types in Docling.
|
|
5
5
|
Author-email: Cesar Berrospi Ramis <ceb@zurich.ibm.com>, Panos Vagenas <pva@zurich.ibm.com>, Michele Dolfi <dol@zurich.ibm.com>, Christoph Auer <cau@zurich.ibm.com>, Peter Staar <taa@zurich.ibm.com>
|
|
6
6
|
Maintainer-email: Panos Vagenas <pva@zurich.ibm.com>, Michele Dolfi <dol@zurich.ibm.com>, Christoph Auer <cau@zurich.ibm.com>, Peter Staar <taa@zurich.ibm.com>, Cesar Berrospi Ramis <ceb@zurich.ibm.com>
|
{docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/chunker/hierarchical_chunker.py
RENAMED
|
@@ -6,6 +6,7 @@ import logging
|
|
|
6
6
|
from collections.abc import Iterator
|
|
7
7
|
from typing import Annotated, Any, Optional, Union
|
|
8
8
|
|
|
9
|
+
import pandas as pd
|
|
9
10
|
from pydantic import ConfigDict, Field
|
|
10
11
|
from typing_extensions import override
|
|
11
12
|
|
|
@@ -45,6 +46,25 @@ _logger = logging.getLogger(__name__)
|
|
|
45
46
|
class TripletTableSerializer(BaseTableSerializer):
|
|
46
47
|
"""Triplet-based table item serializer."""
|
|
47
48
|
|
|
49
|
+
@staticmethod
|
|
50
|
+
def _flatten_table_text(table_df: pd.DataFrame) -> str:
|
|
51
|
+
"""Last-resort fallback that turns a table into plain text.
|
|
52
|
+
|
|
53
|
+
The preferred output of this serializer is the triplet form
|
|
54
|
+
'row, column = value'. When that comes out empty - for example on
|
|
55
|
+
single-cell RichTableCell layout tables where the header and row
|
|
56
|
+
labels are blank - we fall back to this helper so the table still
|
|
57
|
+
contributes something chunkable and does not end up consuming its
|
|
58
|
+
sibling refs.
|
|
59
|
+
|
|
60
|
+
Cells are read row by row, blanks are skipped, and the remaining
|
|
61
|
+
values are joined with '. '. So a table like [['A', ''], ['B', 'C']]
|
|
62
|
+
becomes 'A. B. C'.
|
|
63
|
+
"""
|
|
64
|
+
return ". ".join(
|
|
65
|
+
text for row in table_df.itertuples(index=False, name=None) for value in row if (text := str(value).strip())
|
|
66
|
+
)
|
|
67
|
+
|
|
48
68
|
@override
|
|
49
69
|
def serialize(
|
|
50
70
|
self,
|
|
@@ -56,6 +76,7 @@ class TripletTableSerializer(BaseTableSerializer):
|
|
|
56
76
|
) -> SerializationResult:
|
|
57
77
|
"""Serializes the passed item."""
|
|
58
78
|
parts: list[SerializationResult] = []
|
|
79
|
+
shared_visited = kwargs.get("visited")
|
|
59
80
|
|
|
60
81
|
cap_res = doc_serializer.serialize_captions(
|
|
61
82
|
item=item,
|
|
@@ -65,40 +86,60 @@ class TripletTableSerializer(BaseTableSerializer):
|
|
|
65
86
|
parts.append(cap_res)
|
|
66
87
|
|
|
67
88
|
if item.self_ref not in doc_serializer.get_excluded_refs(**kwargs):
|
|
89
|
+
table_text = ""
|
|
90
|
+
local_kwargs = {**kwargs, "visited": set(shared_visited)} if shared_visited is not None else kwargs
|
|
68
91
|
table_df = item._export_to_dataframe_with_options(
|
|
69
92
|
doc,
|
|
70
93
|
doc_serializer=doc_serializer,
|
|
71
|
-
**
|
|
94
|
+
**local_kwargs,
|
|
72
95
|
)
|
|
96
|
+
|
|
97
|
+
# Header-only tables produce a dataframe with 0 rows. Emit the
|
|
98
|
+
# header text directly instead of dropping the table on the floor.
|
|
99
|
+
if table_df.shape[0] == 0 and len(table_df.columns) > 0:
|
|
100
|
+
table_text = ". ".join(text for col in table_df.columns if (text := str(col).strip()))
|
|
101
|
+
|
|
73
102
|
if table_df.shape[0] >= 1 and table_df.shape[1] >= 1:
|
|
103
|
+
fallback_df = table_df
|
|
74
104
|
# Handle single-column tables
|
|
75
105
|
if table_df.shape[1] == 1:
|
|
76
106
|
# For single-column tables, use first row as column name
|
|
77
107
|
# and remaining rows as values
|
|
78
108
|
col_name = str(table_df.iloc[0, 0]).strip()
|
|
79
109
|
values = [str(val).strip() for val in table_df.iloc[1:, 0].to_list()]
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
110
|
+
if values:
|
|
111
|
+
table_text_parts = [f"{col_name} = {val}" for val in values]
|
|
112
|
+
table_text = ". ".join(table_text_parts)
|
|
113
|
+
else:
|
|
114
|
+
# Single-row single-column table: emit the cell text
|
|
115
|
+
table_text = col_name
|
|
83
116
|
else:
|
|
84
117
|
# For multi-column tables
|
|
85
118
|
# copy header as first row and shift all rows by one
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
119
|
+
triplet_df = table_df.copy()
|
|
120
|
+
triplet_df.loc[-1] = triplet_df.columns # type: ignore[call-overload]
|
|
121
|
+
triplet_df.index = triplet_df.index + 1
|
|
122
|
+
triplet_df = triplet_df.sort_index()
|
|
89
123
|
|
|
90
|
-
rows = [str(item).strip() for item in
|
|
91
|
-
cols = [str(item).strip() for item in
|
|
124
|
+
rows = [str(item).strip() for item in triplet_df.iloc[:, 0].to_list()]
|
|
125
|
+
cols = [str(item).strip() for item in triplet_df.iloc[0, :].to_list()]
|
|
92
126
|
|
|
93
|
-
nrows =
|
|
94
|
-
ncols =
|
|
127
|
+
nrows = triplet_df.shape[0]
|
|
128
|
+
ncols = triplet_df.shape[1]
|
|
95
129
|
table_text_parts = [
|
|
96
|
-
f"{rows[i]}, {cols[j]} = {str(
|
|
130
|
+
f"{rows[i]}, {cols[j]} = {str(triplet_df.iloc[i, j]).strip()}"
|
|
97
131
|
for i in range(1, nrows)
|
|
98
132
|
for j in range(1, ncols)
|
|
99
133
|
]
|
|
100
134
|
table_text = ". ".join(table_text_parts)
|
|
101
|
-
|
|
135
|
+
|
|
136
|
+
if not table_text:
|
|
137
|
+
table_text = self._flatten_table_text(fallback_df)
|
|
138
|
+
|
|
139
|
+
if table_text:
|
|
140
|
+
if shared_visited is not None:
|
|
141
|
+
shared_visited.update(local_kwargs["visited"])
|
|
142
|
+
parts.append(create_ser_result(text=table_text, span_source=item))
|
|
102
143
|
|
|
103
144
|
text_res = "\n\n".join([r.text for r in parts])
|
|
104
145
|
|
|
@@ -230,7 +230,7 @@ class HTMLTextSerializer(BaseModel, BaseTextSerializer):
|
|
|
230
230
|
# Regular text item
|
|
231
231
|
text = get_html_tag_with_text_direction(html_tag="p", text=text)
|
|
232
232
|
|
|
233
|
-
# Apply formatting and hyperlinks
|
|
233
|
+
# Apply formatting and hyperlinks to the parent's own text+tag.
|
|
234
234
|
if not post_processed:
|
|
235
235
|
text = doc_serializer.post_process(
|
|
236
236
|
text=text,
|
|
@@ -238,6 +238,23 @@ class HTMLTextSerializer(BaseModel, BaseTextSerializer):
|
|
|
238
238
|
hyperlink=item.hyperlink,
|
|
239
239
|
)
|
|
240
240
|
|
|
241
|
+
# Recurse into children for branches that don't already consume them.
|
|
242
|
+
# has_inline_repr already consumed the single InlineGroup child; ListItem
|
|
243
|
+
# recursion is handled inline in its branch above.
|
|
244
|
+
if not has_inline_repr and not isinstance(item, ListItem) and item.children:
|
|
245
|
+
nested_text = "\n".join(
|
|
246
|
+
r.text
|
|
247
|
+
for r in doc_serializer.get_parts(
|
|
248
|
+
item=item,
|
|
249
|
+
is_inline_scope=is_inline_scope,
|
|
250
|
+
visited=my_visited,
|
|
251
|
+
**kwargs,
|
|
252
|
+
)
|
|
253
|
+
if r.text
|
|
254
|
+
)
|
|
255
|
+
if nested_text:
|
|
256
|
+
text = f"{text}\n{nested_text}" if text else nested_text
|
|
257
|
+
|
|
241
258
|
if text:
|
|
242
259
|
text_res = create_ser_result(text=text, span_source=item)
|
|
243
260
|
res_parts.append(text_res)
|
|
@@ -945,6 +962,8 @@ class HTMLMetaSerializer(BaseModel, BaseMetaSerializer):
|
|
|
945
962
|
|
|
946
963
|
def _serialize_meta_field(self, meta: BaseMeta, name: str) -> Optional[str]:
|
|
947
964
|
if (field_val := getattr(meta, name)) is not None:
|
|
965
|
+
is_html_markup = False
|
|
966
|
+
|
|
948
967
|
if isinstance(field_val, SummaryMetaField):
|
|
949
968
|
txt = field_val.text
|
|
950
969
|
elif isinstance(field_val, LanguageMetaField):
|
|
@@ -952,14 +971,13 @@ class HTMLMetaSerializer(BaseModel, BaseMetaSerializer):
|
|
|
952
971
|
elif isinstance(field_val, EntitiesMetaField):
|
|
953
972
|
txt = ", ".join(
|
|
954
973
|
(
|
|
955
|
-
f"{
|
|
956
|
-
f"({html.escape(mention.label)}, [{mention.charspan[0]},{mention.charspan[1]}])"
|
|
974
|
+
f"{mention.text} ({mention.label}, [{mention.charspan[0]},{mention.charspan[1]}])"
|
|
957
975
|
if mention.label is not None and mention.charspan
|
|
958
|
-
else f"{
|
|
976
|
+
else f"{mention.text} ({mention.label})"
|
|
959
977
|
if mention.label is not None
|
|
960
|
-
else f"{
|
|
978
|
+
else f"{mention.text} ([{mention.charspan[0]},{mention.charspan[1]}])"
|
|
961
979
|
if mention.charspan
|
|
962
|
-
else
|
|
980
|
+
else mention.text
|
|
963
981
|
)
|
|
964
982
|
for mention in field_val.mentions
|
|
965
983
|
)
|
|
@@ -975,19 +993,30 @@ class HTMLMetaSerializer(BaseModel, BaseMetaSerializer):
|
|
|
975
993
|
table_content = temp_table.export_to_html(temp_doc).strip()
|
|
976
994
|
if table_content:
|
|
977
995
|
txt = table_content
|
|
996
|
+
is_html_markup = True
|
|
978
997
|
else:
|
|
979
998
|
return None
|
|
980
999
|
elif isinstance(field_val, CodeMetaField):
|
|
981
1000
|
lang = field_val.language.value.lower() if field_val.language else ""
|
|
982
|
-
|
|
983
|
-
|
|
1001
|
+
escaped_lang = html.escape(lang)
|
|
1002
|
+
escaped_code = html.escape(field_val.text)
|
|
1003
|
+
code_class = f' class="language-{escaped_lang}"' if lang else ""
|
|
1004
|
+
txt = f'<pre class="docling-meta-code"><code{code_class}>{escaped_code}</code></pre>'
|
|
1005
|
+
is_html_markup = True
|
|
984
1006
|
elif tmp := str(field_val or ""):
|
|
985
1007
|
txt = tmp
|
|
986
1008
|
else:
|
|
987
1009
|
return None
|
|
1010
|
+
|
|
1011
|
+
# Escape plain text content for safe HTML output.
|
|
1012
|
+
# HTML markup fields (TabularChartMetaField, CodeMetaField) are already properly formatted.
|
|
1013
|
+
if not is_html_markup:
|
|
1014
|
+
txt = html.escape(txt, quote=False)
|
|
1015
|
+
|
|
1016
|
+
escaped_name = html.escape(name, quote=True)
|
|
988
1017
|
return (
|
|
989
|
-
f'<div class="docling-meta-field" data-meta-{
|
|
990
|
-
f'<span class="docling-meta-field-label">{
|
|
1018
|
+
f'<div class="docling-meta-field" data-meta-name="{escaped_name}">'
|
|
1019
|
+
f'<span class="docling-meta-field-label">{escaped_name}:</span> '
|
|
991
1020
|
f'<span class="docling-meta-field-value">{txt}</span>'
|
|
992
1021
|
f"</div>"
|
|
993
1022
|
)
|
{docling_core-2.77.0 → docling_core-2.78.0}/docling_core/transforms/serializer/html_styles.py
RENAMED
|
@@ -113,9 +113,6 @@ def _get_css_for_split_page() -> str:
|
|
|
113
113
|
font-weight: bold;
|
|
114
114
|
color: #444;
|
|
115
115
|
}
|
|
116
|
-
.docling-meta-field[data-meta-classification] {
|
|
117
|
-
text-align: left;
|
|
118
|
-
}
|
|
119
116
|
pre.docling-meta-code {
|
|
120
117
|
background-color: #1e1e1e;
|
|
121
118
|
color: #d4d4d4;
|
|
@@ -287,9 +284,6 @@ def _get_css_for_single_column() -> str:
|
|
|
287
284
|
font-weight: bold;
|
|
288
285
|
color: #444;
|
|
289
286
|
}
|
|
290
|
-
.docling-meta-field[data-meta-classification] {
|
|
291
|
-
text-align: left;
|
|
292
|
-
}
|
|
293
287
|
pre.docling-meta-code {
|
|
294
288
|
background-color: #1e1e1e;
|
|
295
289
|
color: #d4d4d4;
|
|
@@ -385,12 +385,28 @@ AnyTableCell = Annotated[
|
|
|
385
385
|
]
|
|
386
386
|
|
|
387
387
|
|
|
388
|
+
class Orientation(str, Enum):
|
|
389
|
+
"""Counter-clockwise rotation of a table on the page, in degrees.
|
|
390
|
+
|
|
391
|
+
Follows the convention used by PIL/Pillow's ``Image.rotate``: positive
|
|
392
|
+
angles rotate the table counter-clockwise. ``ROT_0`` / ``ROT_180`` keep
|
|
393
|
+
rows running horizontally on the page; ``ROT_90`` / ``ROT_270`` turn
|
|
394
|
+
rows into vertical stripes.
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
ROT_0 = "rot_0" # no rotation; row 0 at top, rows horizontal
|
|
398
|
+
ROT_90 = "rot_90" # 90° CCW; row 0 on the left, rows are vertical stripes
|
|
399
|
+
ROT_180 = "rot_180" # 180°; row 0 at bottom (upside-down), rows horizontal
|
|
400
|
+
ROT_270 = "rot_270" # 270° CCW (= 90° CW); row 0 on the right, rows are vertical stripes
|
|
401
|
+
|
|
402
|
+
|
|
388
403
|
class TableData(BaseModel): # TBD
|
|
389
404
|
"""BaseTableData."""
|
|
390
405
|
|
|
391
406
|
table_cells: list[AnyTableCell] = []
|
|
392
407
|
num_rows: int = 0
|
|
393
408
|
num_cols: int = 0
|
|
409
|
+
orientation: Orientation = Orientation.ROT_0
|
|
394
410
|
|
|
395
411
|
@computed_field # type: ignore
|
|
396
412
|
@property
|
|
@@ -577,15 +593,23 @@ class TableData(BaseModel): # TBD
|
|
|
577
593
|
def get_row_bounding_boxes(self, *, minimal: bool = True) -> dict[int, BoundingBox]:
|
|
578
594
|
"""Get the bounding box for each row in the table.
|
|
579
595
|
|
|
596
|
+
Layout follows the table's ``orientation`` field: ``ROT_0`` / ``ROT_180``
|
|
597
|
+
keep rows running left-to-right on the page; ``ROT_90`` / ``ROT_270``
|
|
598
|
+
turn rows into vertical stripes. This affects both the axis along which
|
|
599
|
+
span cells extend a row's bbox and, when ``minimal=False``, the axis
|
|
600
|
+
equalized across rows.
|
|
601
|
+
|
|
580
602
|
Args:
|
|
581
603
|
minimal: If True (default), returns the minimal bounding box for each
|
|
582
|
-
row based on its cells. If False, all rows will have uniform
|
|
583
|
-
|
|
604
|
+
row based on its cells. If False, all rows will have a uniform
|
|
605
|
+
extent perpendicular to the row direction (l/r for ROT_0/ROT_180,
|
|
606
|
+
t/b for ROT_90/ROT_270).
|
|
584
607
|
|
|
585
608
|
Returns:
|
|
586
609
|
dict[int, BoundingBox]: A dictionary mapping row indices to their
|
|
587
610
|
bounding boxes. Only rows with cells that have bounding boxes are included.
|
|
588
611
|
"""
|
|
612
|
+
horizontal = self.orientation in (Orientation.ROT_0, Orientation.ROT_180)
|
|
589
613
|
coords = []
|
|
590
614
|
for cell in self.table_cells:
|
|
591
615
|
if cell.bbox is not None:
|
|
@@ -616,35 +640,68 @@ class TableData(BaseModel): # TBD
|
|
|
616
640
|
min_row_span = min(row_cells_with_bbox.keys())
|
|
617
641
|
row_bbox: BoundingBox = BoundingBox.enclosing_bbox(row_cells_with_bbox[min_row_span])
|
|
618
642
|
|
|
643
|
+
# Spanning cells extend along the row's natural axis:
|
|
644
|
+
# horizontal table → row runs l/r; vertical table → row runs t/b.
|
|
619
645
|
for rspan, bboxs in row_cells_with_bbox.items():
|
|
620
646
|
for bbox in bboxs:
|
|
621
|
-
|
|
622
|
-
|
|
647
|
+
if horizontal:
|
|
648
|
+
row_bbox.l = min(row_bbox.l, bbox.l)
|
|
649
|
+
row_bbox.r = max(row_bbox.r, bbox.r)
|
|
650
|
+
else:
|
|
651
|
+
if bbox.coord_origin == CoordOrigin.TOPLEFT:
|
|
652
|
+
row_bbox.t = min(row_bbox.t, bbox.t)
|
|
653
|
+
row_bbox.b = max(row_bbox.b, bbox.b)
|
|
654
|
+
else: # BOTTOMLEFT
|
|
655
|
+
row_bbox.t = max(row_bbox.t, bbox.t)
|
|
656
|
+
row_bbox.b = min(row_bbox.b, bbox.b)
|
|
623
657
|
|
|
624
658
|
row_bboxes[row_idx] = row_bbox
|
|
625
659
|
|
|
626
|
-
# If not minimal, make all rows have uniform
|
|
660
|
+
# If not minimal, make all rows have uniform extent on the axis
|
|
661
|
+
# perpendicular to the row direction.
|
|
627
662
|
if not minimal and row_bboxes:
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
bbox.
|
|
632
|
-
bbox
|
|
663
|
+
if horizontal:
|
|
664
|
+
# Rows run left-to-right; equalize horizontal extent.
|
|
665
|
+
global_l = min(bbox.l for bbox in row_bboxes.values())
|
|
666
|
+
global_r = max(bbox.r for bbox in row_bboxes.values())
|
|
667
|
+
for bbox in row_bboxes.values():
|
|
668
|
+
bbox.l = global_l
|
|
669
|
+
bbox.r = global_r
|
|
670
|
+
else:
|
|
671
|
+
# Vertical table: rows are vertical stripes; equalize vertical extent.
|
|
672
|
+
first_bbox = next(iter(row_bboxes.values()))
|
|
673
|
+
if first_bbox.coord_origin == CoordOrigin.TOPLEFT:
|
|
674
|
+
global_t = min(bbox.t for bbox in row_bboxes.values())
|
|
675
|
+
global_b = max(bbox.b for bbox in row_bboxes.values())
|
|
676
|
+
else: # BOTTOMLEFT
|
|
677
|
+
global_t = max(bbox.t for bbox in row_bboxes.values())
|
|
678
|
+
global_b = min(bbox.b for bbox in row_bboxes.values())
|
|
679
|
+
for bbox in row_bboxes.values():
|
|
680
|
+
bbox.t = global_t
|
|
681
|
+
bbox.b = global_b
|
|
633
682
|
|
|
634
683
|
return row_bboxes
|
|
635
684
|
|
|
636
685
|
def get_column_bounding_boxes(self, *, minimal: bool = True) -> dict[int, BoundingBox]:
|
|
637
686
|
"""Get the bounding box for each column in the table.
|
|
638
687
|
|
|
688
|
+
Layout follows the table's ``orientation`` field: ``ROT_0`` / ``ROT_180``
|
|
689
|
+
keep columns running top-to-bottom on the page; ``ROT_90`` / ``ROT_270``
|
|
690
|
+
turn columns into horizontal stripes. This affects both the axis along
|
|
691
|
+
which span cells extend a column's bbox and, when ``minimal=False``, the
|
|
692
|
+
axis equalized across columns.
|
|
693
|
+
|
|
639
694
|
Args:
|
|
640
695
|
minimal: If True (default), returns the minimal bounding box for each
|
|
641
|
-
column based on its cells. If False, all columns will have
|
|
642
|
-
|
|
696
|
+
column based on its cells. If False, all columns will have a
|
|
697
|
+
uniform extent perpendicular to the column direction (t/b for
|
|
698
|
+
ROT_0/ROT_180, l/r for ROT_90/ROT_270).
|
|
643
699
|
|
|
644
700
|
Returns:
|
|
645
701
|
dict[int, BoundingBox]: A dictionary mapping column indices to their
|
|
646
702
|
bounding boxes. Only columns with cells that have bounding boxes are included.
|
|
647
703
|
"""
|
|
704
|
+
horizontal = self.orientation in (Orientation.ROT_0, Orientation.ROT_180)
|
|
648
705
|
coords = []
|
|
649
706
|
for cell in self.table_cells:
|
|
650
707
|
if cell.bbox is not None:
|
|
@@ -675,31 +732,46 @@ class TableData(BaseModel): # TBD
|
|
|
675
732
|
min_col_span = min(col_cells_with_bbox.keys())
|
|
676
733
|
col_bbox: BoundingBox = BoundingBox.enclosing_bbox(col_cells_with_bbox[min_col_span])
|
|
677
734
|
|
|
735
|
+
# Spanning cells extend along the column's natural axis:
|
|
736
|
+
# horizontal table → column runs t/b; vertical table → column runs l/r.
|
|
678
737
|
for rspan, bboxs in col_cells_with_bbox.items():
|
|
679
738
|
for bbox in bboxs:
|
|
680
|
-
if
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
739
|
+
if horizontal:
|
|
740
|
+
if bbox.coord_origin == CoordOrigin.TOPLEFT:
|
|
741
|
+
col_bbox.b = max(col_bbox.b, bbox.b)
|
|
742
|
+
col_bbox.t = min(col_bbox.t, bbox.t)
|
|
743
|
+
elif bbox.coord_origin == CoordOrigin.BOTTOMLEFT:
|
|
744
|
+
col_bbox.b = min(col_bbox.b, bbox.b)
|
|
745
|
+
col_bbox.t = max(col_bbox.t, bbox.t)
|
|
746
|
+
else:
|
|
747
|
+
col_bbox.l = min(col_bbox.l, bbox.l)
|
|
748
|
+
col_bbox.r = max(col_bbox.r, bbox.r)
|
|
687
749
|
|
|
688
750
|
col_bboxes[col_idx] = col_bbox
|
|
689
751
|
|
|
690
|
-
# If not minimal, make all columns have uniform
|
|
752
|
+
# If not minimal, make all columns have uniform extent on the axis
|
|
753
|
+
# perpendicular to the column direction.
|
|
691
754
|
if not minimal and col_bboxes:
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
bbox
|
|
755
|
+
if horizontal:
|
|
756
|
+
# Columns run top-to-bottom; equalize vertical extent.
|
|
757
|
+
# Get the coord_origin from the first bbox (they're all the same)
|
|
758
|
+
first_bbox = next(iter(col_bboxes.values()))
|
|
759
|
+
if first_bbox.coord_origin == CoordOrigin.TOPLEFT:
|
|
760
|
+
global_t = min(bbox.t for bbox in col_bboxes.values())
|
|
761
|
+
global_b = max(bbox.b for bbox in col_bboxes.values())
|
|
762
|
+
else: # BOTTOMLEFT
|
|
763
|
+
global_t = max(bbox.t for bbox in col_bboxes.values())
|
|
764
|
+
global_b = min(bbox.b for bbox in col_bboxes.values())
|
|
765
|
+
for bbox in col_bboxes.values():
|
|
766
|
+
bbox.t = global_t
|
|
767
|
+
bbox.b = global_b
|
|
768
|
+
else:
|
|
769
|
+
# Vertical table: columns are horizontal stripes; equalize horizontal extent.
|
|
770
|
+
global_l = min(bbox.l for bbox in col_bboxes.values())
|
|
771
|
+
global_r = max(bbox.r for bbox in col_bboxes.values())
|
|
772
|
+
for bbox in col_bboxes.values():
|
|
773
|
+
bbox.l = global_l
|
|
774
|
+
bbox.r = global_r
|
|
703
775
|
|
|
704
776
|
return col_bboxes
|
|
705
777
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docling-core
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.78.0
|
|
4
4
|
Summary: A python library to define and validate data types in Docling.
|
|
5
5
|
Author-email: Cesar Berrospi Ramis <ceb@zurich.ibm.com>, Panos Vagenas <pva@zurich.ibm.com>, Michele Dolfi <dol@zurich.ibm.com>, Christoph Auer <cau@zurich.ibm.com>, Peter Staar <taa@zurich.ibm.com>
|
|
6
6
|
Maintainer-email: Panos Vagenas <pva@zurich.ibm.com>, Michele Dolfi <dol@zurich.ibm.com>, Christoph Auer <cau@zurich.ibm.com>, Peter Staar <taa@zurich.ibm.com>, Cesar Berrospi Ramis <ceb@zurich.ibm.com>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "docling-core"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.78.0" # DO NOT EDIT, updated automatically
|
|
4
4
|
description = "A python library to define and validate data types in Docling."
|
|
5
5
|
license = "MIT"
|
|
6
6
|
license-files = ["LICENSE"]
|
|
@@ -113,7 +113,7 @@ dev = [
|
|
|
113
113
|
"pandas-stubs~=2.1",
|
|
114
114
|
"ipykernel~=6.29",
|
|
115
115
|
"coverage~=7.6",
|
|
116
|
-
"pytest~=
|
|
116
|
+
"pytest~=9.0.3",
|
|
117
117
|
"pytest-cov>=6.1.1",
|
|
118
118
|
"python-semantic-release~=7.32",
|
|
119
119
|
'tree-sitter-java-orchard (>=0.3.0,<1.0.0); python_version >= "3.10"',
|