docling 2.0.0__tar.gz → 2.2.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.
Files changed (46) hide show
  1. {docling-2.0.0 → docling-2.2.0}/PKG-INFO +10 -9
  2. {docling-2.0.0 → docling-2.2.0}/README.md +5 -5
  3. {docling-2.0.0 → docling-2.2.0}/docling/backend/abstract_backend.py +1 -0
  4. docling-2.2.0/docling/backend/asciidoc_backend.py +435 -0
  5. {docling-2.0.0 → docling-2.2.0}/docling/backend/docling_parse_backend.py +3 -3
  6. {docling-2.0.0 → docling-2.2.0}/docling/backend/docling_parse_v2_backend.py +11 -3
  7. {docling-2.0.0 → docling-2.2.0}/docling/backend/html_backend.py +8 -1
  8. docling-2.2.0/docling/backend/md_backend.py +293 -0
  9. {docling-2.0.0 → docling-2.2.0}/docling/backend/mspowerpoint_backend.py +62 -39
  10. {docling-2.0.0 → docling-2.2.0}/docling/backend/msword_backend.py +3 -10
  11. {docling-2.0.0 → docling-2.2.0}/docling/datamodel/base_models.py +15 -9
  12. {docling-2.0.0 → docling-2.2.0}/docling/datamodel/document.py +49 -12
  13. {docling-2.0.0 → docling-2.2.0}/docling/datamodel/pipeline_options.py +3 -0
  14. {docling-2.0.0 → docling-2.2.0}/docling/document_converter.py +18 -0
  15. {docling-2.0.0 → docling-2.2.0}/docling/models/base_ocr_model.py +9 -1
  16. {docling-2.0.0 → docling-2.2.0}/docling/models/ds_glm_model.py +16 -7
  17. docling-2.2.0/docling/models/easyocr_model.py +90 -0
  18. {docling-2.0.0 → docling-2.2.0}/docling/models/layout_model.py +63 -59
  19. docling-2.2.0/docling/models/page_assemble_model.py +172 -0
  20. {docling-2.0.0 → docling-2.2.0}/docling/models/page_preprocessing_model.py +7 -3
  21. docling-2.2.0/docling/models/table_structure_model.py +171 -0
  22. {docling-2.0.0 → docling-2.2.0}/docling/models/tesseract_ocr_cli_model.py +56 -52
  23. {docling-2.0.0 → docling-2.2.0}/docling/models/tesseract_ocr_model.py +50 -45
  24. {docling-2.0.0 → docling-2.2.0}/docling/pipeline/standard_pdf_pipeline.py +7 -7
  25. {docling-2.0.0 → docling-2.2.0}/pyproject.toml +7 -4
  26. docling-2.0.0/docling/models/easyocr_model.py +0 -88
  27. docling-2.0.0/docling/models/page_assemble_model.py +0 -164
  28. docling-2.0.0/docling/models/table_structure_model.py +0 -162
  29. {docling-2.0.0 → docling-2.2.0}/LICENSE +0 -0
  30. {docling-2.0.0 → docling-2.2.0}/docling/__init__.py +0 -0
  31. {docling-2.0.0 → docling-2.2.0}/docling/backend/__init__.py +0 -0
  32. {docling-2.0.0 → docling-2.2.0}/docling/backend/pdf_backend.py +0 -0
  33. {docling-2.0.0 → docling-2.2.0}/docling/backend/pypdfium2_backend.py +0 -0
  34. {docling-2.0.0 → docling-2.2.0}/docling/cli/__init__.py +0 -0
  35. {docling-2.0.0 → docling-2.2.0}/docling/cli/main.py +0 -0
  36. {docling-2.0.0 → docling-2.2.0}/docling/datamodel/__init__.py +0 -0
  37. {docling-2.0.0 → docling-2.2.0}/docling/datamodel/settings.py +0 -0
  38. {docling-2.0.0 → docling-2.2.0}/docling/models/__init__.py +0 -0
  39. {docling-2.0.0 → docling-2.2.0}/docling/models/base_model.py +0 -0
  40. {docling-2.0.0 → docling-2.2.0}/docling/pipeline/__init__.py +0 -0
  41. {docling-2.0.0 → docling-2.2.0}/docling/pipeline/base_pipeline.py +0 -0
  42. {docling-2.0.0 → docling-2.2.0}/docling/pipeline/simple_pipeline.py +0 -0
  43. {docling-2.0.0 → docling-2.2.0}/docling/utils/__init__.py +0 -0
  44. {docling-2.0.0 → docling-2.2.0}/docling/utils/export.py +0 -0
  45. {docling-2.0.0 → docling-2.2.0}/docling/utils/layout_utils.py +0 -0
  46. {docling-2.0.0 → docling-2.2.0}/docling/utils/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: docling
3
- Version: 2.0.0
3
+ Version: 2.2.0
4
4
  Summary: Docling PDF conversion package
5
5
  Home-page: https://github.com/DS4SD/docling
6
6
  License: MIT
@@ -22,13 +22,14 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
22
  Provides-Extra: tesserocr
23
23
  Requires-Dist: beautifulsoup4 (>=4.12.3,<5.0.0)
24
24
  Requires-Dist: certifi (>=2024.7.4)
25
- Requires-Dist: deepsearch-glm (>=0.25.0,<0.26.0)
26
- Requires-Dist: docling-core (>=2.0.0,<3.0.0)
25
+ Requires-Dist: deepsearch-glm (>=0.26.1,<0.27.0)
26
+ Requires-Dist: docling-core (>=2.1.0,<3.0.0)
27
27
  Requires-Dist: docling-ibm-models (>=2.0.1,<3.0.0)
28
- Requires-Dist: docling-parse (>=1.6.0,<2.0.0)
28
+ Requires-Dist: docling-parse (>=2.0.0,<3.0.0)
29
29
  Requires-Dist: easyocr (>=1.7,<2.0)
30
30
  Requires-Dist: filetype (>=1.2.0,<2.0.0)
31
31
  Requires-Dist: huggingface_hub (>=0.23,<1)
32
+ Requires-Dist: marko (>=2.1.2,<3.0.0)
32
33
  Requires-Dist: pandas (>=2.1.4,<3.0.0)
33
34
  Requires-Dist: pyarrow (>=16.1.0,<17.0.0)
34
35
  Requires-Dist: pydantic (>=2.0.0,<3.0.0)
@@ -50,7 +51,7 @@ Description-Content-Type: text/markdown
50
51
 
51
52
  <p align="center">
52
53
  <a href="https://github.com/ds4sd/docling">
53
- <img loading="lazy" alt="Docling" src="https://github.com/DS4SD/docling/raw/main/docs/assets/logo.png" width="150" />
54
+ <img loading="lazy" alt="Docling" src="https://github.com/DS4SD/docling/raw/main/docs/assets/docling_processing.png" width="100%"/>
54
55
  </a>
55
56
  </p>
56
57
 
@@ -69,6 +70,7 @@ Description-Content-Type: text/markdown
69
70
 
70
71
  Docling parses documents and exports them to the desired format with ease and speed.
71
72
 
73
+
72
74
  ## Features
73
75
 
74
76
  * 🗂️ Multi-format support for input (PDF, DOCX etc.) & output (Markdown, JSON etc.)
@@ -94,16 +96,15 @@ More [detailed installation instructions](https://ds4sd.github.io/docling/instal
94
96
 
95
97
  ## Getting started
96
98
 
97
- To convert invidual documents, use `convert()`, for example:
99
+ To convert individual documents, use `convert()`, for example:
98
100
 
99
101
  ```python
100
102
  from docling.document_converter import DocumentConverter
101
103
 
102
- source = "https://arxiv.org/pdf/2408.09869" # PDF path or URL
104
+ source = "https://arxiv.org/pdf/2408.09869" # document per local path or URL
103
105
  converter = DocumentConverter()
104
106
  result = converter.convert(source)
105
107
  print(result.document.export_to_markdown()) # output: "## Docling Technical Report[...]"
106
- print(result.document.export_to_document_tokens()) # output: "<document><title><page_1><loc_20>..."
107
108
  ```
108
109
 
109
110
 
@@ -144,6 +145,6 @@ If you use Docling in your projects, please consider citing the following:
144
145
 
145
146
  ## License
146
147
 
147
- The Docling codebase is under MIT license.
148
+ The Docling codebase is under MIT license.
148
149
  For individual model usage, please refer to the model licenses found in the original packages.
149
150
 
@@ -1,6 +1,6 @@
1
1
  <p align="center">
2
2
  <a href="https://github.com/ds4sd/docling">
3
- <img loading="lazy" alt="Docling" src="https://github.com/DS4SD/docling/raw/main/docs/assets/logo.png" width="150" />
3
+ <img loading="lazy" alt="Docling" src="https://github.com/DS4SD/docling/raw/main/docs/assets/docling_processing.png" width="100%"/>
4
4
  </a>
5
5
  </p>
6
6
 
@@ -19,6 +19,7 @@
19
19
 
20
20
  Docling parses documents and exports them to the desired format with ease and speed.
21
21
 
22
+
22
23
  ## Features
23
24
 
24
25
  * 🗂️ Multi-format support for input (PDF, DOCX etc.) & output (Markdown, JSON etc.)
@@ -44,16 +45,15 @@ More [detailed installation instructions](https://ds4sd.github.io/docling/instal
44
45
 
45
46
  ## Getting started
46
47
 
47
- To convert invidual documents, use `convert()`, for example:
48
+ To convert individual documents, use `convert()`, for example:
48
49
 
49
50
  ```python
50
51
  from docling.document_converter import DocumentConverter
51
52
 
52
- source = "https://arxiv.org/pdf/2408.09869" # PDF path or URL
53
+ source = "https://arxiv.org/pdf/2408.09869" # document per local path or URL
53
54
  converter = DocumentConverter()
54
55
  result = converter.convert(source)
55
56
  print(result.document.export_to_markdown()) # output: "## Docling Technical Report[...]"
56
- print(result.document.export_to_document_tokens()) # output: "<document><title><page_1><loc_20>..."
57
57
  ```
58
58
 
59
59
 
@@ -94,5 +94,5 @@ If you use Docling in your projects, please consider citing the following:
94
94
 
95
95
  ## License
96
96
 
97
- The Docling codebase is under MIT license.
97
+ The Docling codebase is under MIT license.
98
98
  For individual model usage, please refer to the model licenses found in the original packages.
@@ -13,6 +13,7 @@ if TYPE_CHECKING:
13
13
  class AbstractDocumentBackend(ABC):
14
14
  @abstractmethod
15
15
  def __init__(self, in_doc: "InputDocument", path_or_stream: Union[BytesIO, Path]):
16
+ self.file = in_doc.file
16
17
  self.path_or_stream = path_or_stream
17
18
  self.document_hash = in_doc.document_hash
18
19
  self.input_format = in_doc.format
@@ -0,0 +1,435 @@
1
+ import logging
2
+ import os
3
+ import re
4
+ from io import BytesIO
5
+ from pathlib import Path
6
+ from typing import Set, Union
7
+
8
+ from docling_core.types.doc import (
9
+ DocItem,
10
+ DocItemLabel,
11
+ DoclingDocument,
12
+ DocumentOrigin,
13
+ GroupItem,
14
+ GroupLabel,
15
+ ImageRef,
16
+ NodeItem,
17
+ Size,
18
+ TableCell,
19
+ TableData,
20
+ )
21
+ from pydantic import AnyUrl
22
+
23
+ from docling.backend.abstract_backend import DeclarativeDocumentBackend
24
+ from docling.datamodel.base_models import InputFormat
25
+ from docling.datamodel.document import InputDocument
26
+
27
+ _log = logging.getLogger(__name__)
28
+
29
+
30
+ class AsciiDocBackend(DeclarativeDocumentBackend):
31
+
32
+ def __init__(self, in_doc: InputDocument, path_or_stream: Union[BytesIO, Path]):
33
+ super().__init__(in_doc, path_or_stream)
34
+
35
+ self.path_or_stream = path_or_stream
36
+
37
+ try:
38
+ if isinstance(self.path_or_stream, BytesIO):
39
+ text_stream = self.path_or_stream.getvalue().decode("utf-8")
40
+ self.lines = text_stream.split("\n")
41
+ if isinstance(self.path_or_stream, Path):
42
+ with open(self.path_or_stream, "r", encoding="utf-8") as f:
43
+ self.lines = f.readlines()
44
+ self.valid = True
45
+
46
+ except Exception as e:
47
+ raise RuntimeError(
48
+ f"Could not initialize AsciiDoc backend for file with hash {self.document_hash}."
49
+ ) from e
50
+ return
51
+
52
+ def is_valid(self) -> bool:
53
+ return self.valid
54
+
55
+ @classmethod
56
+ def supports_pagination(cls) -> bool:
57
+ return False
58
+
59
+ def unload(self):
60
+ return
61
+
62
+ @classmethod
63
+ def supported_formats(cls) -> Set[InputFormat]:
64
+ return {InputFormat.ASCIIDOC}
65
+
66
+ def convert(self) -> DoclingDocument:
67
+ """
68
+ Parses the ASCII into a structured document model.
69
+ """
70
+
71
+ origin = DocumentOrigin(
72
+ filename=self.file.name or "file",
73
+ mimetype="text/asciidoc",
74
+ binary_hash=self.document_hash,
75
+ )
76
+
77
+ doc = DoclingDocument(name=self.file.stem or "file", origin=origin)
78
+
79
+ doc = self._parse(doc)
80
+
81
+ return doc
82
+
83
+ def _parse(self, doc: DoclingDocument):
84
+ """
85
+ Main function that orchestrates the parsing by yielding components:
86
+ title, section headers, text, lists, and tables.
87
+ """
88
+
89
+ content = ""
90
+
91
+ in_list = False
92
+ in_table = False
93
+
94
+ text_data: list[str] = []
95
+ table_data: list[str] = []
96
+ caption_data: list[str] = []
97
+
98
+ # parents: dict[int, Union[DocItem, GroupItem, None]] = {}
99
+ parents: dict[int, Union[GroupItem, None]] = {}
100
+ # indents: dict[int, Union[DocItem, GroupItem, None]] = {}
101
+ indents: dict[int, Union[GroupItem, None]] = {}
102
+
103
+ for i in range(0, 10):
104
+ parents[i] = None
105
+ indents[i] = None
106
+
107
+ for line in self.lines:
108
+ # line = line.strip()
109
+
110
+ # Title
111
+ if self._is_title(line):
112
+ item = self._parse_title(line)
113
+ level = item["level"]
114
+
115
+ parents[level] = doc.add_text(
116
+ text=item["text"], label=DocItemLabel.TITLE
117
+ )
118
+
119
+ # Section headers
120
+ elif self._is_section_header(line):
121
+ item = self._parse_section_header(line)
122
+ level = item["level"]
123
+
124
+ parents[level] = doc.add_heading(
125
+ text=item["text"], level=item["level"], parent=parents[level - 1]
126
+ )
127
+ for k, v in parents.items():
128
+ if k > level:
129
+ parents[k] = None
130
+
131
+ # Lists
132
+ elif self._is_list_item(line):
133
+
134
+ _log.debug(f"line: {line}")
135
+ item = self._parse_list_item(line)
136
+ _log.debug(f"parsed list-item: {item}")
137
+
138
+ level = self._get_current_level(parents)
139
+
140
+ if not in_list:
141
+ in_list = True
142
+
143
+ parents[level + 1] = doc.add_group(
144
+ parent=parents[level], name="list", label=GroupLabel.LIST
145
+ )
146
+ indents[level + 1] = item["indent"]
147
+
148
+ elif in_list and item["indent"] > indents[level]:
149
+ parents[level + 1] = doc.add_group(
150
+ parent=parents[level], name="list", label=GroupLabel.LIST
151
+ )
152
+ indents[level + 1] = item["indent"]
153
+
154
+ elif in_list and item["indent"] < indents[level]:
155
+
156
+ # print(item["indent"], " => ", indents[level])
157
+ while item["indent"] < indents[level]:
158
+ # print(item["indent"], " => ", indents[level])
159
+ parents[level] = None
160
+ indents[level] = None
161
+ level -= 1
162
+
163
+ doc.add_list_item(
164
+ item["text"], parent=self._get_current_parent(parents)
165
+ )
166
+
167
+ elif in_list and not self._is_list_item(line):
168
+ in_list = False
169
+
170
+ level = self._get_current_level(parents)
171
+ parents[level] = None
172
+
173
+ # Tables
174
+ elif line.strip() == "|===" and not in_table: # start of table
175
+ in_table = True
176
+
177
+ elif self._is_table_line(line): # within a table
178
+ in_table = True
179
+ table_data.append(self._parse_table_line(line))
180
+
181
+ elif in_table and (
182
+ (not self._is_table_line(line)) or line.strip() == "|==="
183
+ ): # end of table
184
+
185
+ caption = None
186
+ if len(caption_data) > 0:
187
+ caption = doc.add_text(
188
+ text=" ".join(caption_data), label=DocItemLabel.CAPTION
189
+ )
190
+
191
+ caption_data = []
192
+
193
+ data = self._populate_table_as_grid(table_data)
194
+ doc.add_table(
195
+ data=data, parent=self._get_current_parent(parents), caption=caption
196
+ )
197
+
198
+ in_table = False
199
+ table_data = []
200
+
201
+ # Picture
202
+ elif self._is_picture(line):
203
+
204
+ caption = None
205
+ if len(caption_data) > 0:
206
+ caption = doc.add_text(
207
+ text=" ".join(caption_data), label=DocItemLabel.CAPTION
208
+ )
209
+
210
+ caption_data = []
211
+
212
+ item = self._parse_picture(line)
213
+
214
+ size = None
215
+ if "width" in item and "height" in item:
216
+ size = Size(width=int(item["width"]), height=int(item["height"]))
217
+
218
+ uri = None
219
+ if (
220
+ "uri" in item
221
+ and not item["uri"].startswith("http")
222
+ and item["uri"].startswith("//")
223
+ ):
224
+ uri = "file:" + item["uri"]
225
+ elif (
226
+ "uri" in item
227
+ and not item["uri"].startswith("http")
228
+ and item["uri"].startswith("/")
229
+ ):
230
+ uri = "file:/" + item["uri"]
231
+ elif "uri" in item and not item["uri"].startswith("http"):
232
+ uri = "file://" + item["uri"]
233
+
234
+ image = ImageRef(mimetype="image/png", size=size, dpi=70, uri=uri)
235
+ doc.add_picture(image=image, caption=caption)
236
+
237
+ # Caption
238
+ elif self._is_caption(line) and len(caption_data) == 0:
239
+ item = self._parse_caption(line)
240
+ caption_data.append(item["text"])
241
+
242
+ elif (
243
+ len(line.strip()) > 0 and len(caption_data) > 0
244
+ ): # allow multiline captions
245
+ item = self._parse_text(line)
246
+ caption_data.append(item["text"])
247
+
248
+ # Plain text
249
+ elif len(line.strip()) == 0 and len(text_data) > 0:
250
+ doc.add_text(
251
+ text=" ".join(text_data),
252
+ label=DocItemLabel.PARAGRAPH,
253
+ parent=self._get_current_parent(parents),
254
+ )
255
+ text_data = []
256
+
257
+ elif len(line.strip()) > 0: # allow multiline texts
258
+
259
+ item = self._parse_text(line)
260
+ text_data.append(item["text"])
261
+
262
+ if len(text_data) > 0:
263
+ doc.add_text(
264
+ text=" ".join(text_data),
265
+ label=DocItemLabel.PARAGRAPH,
266
+ parent=self._get_current_parent(parents),
267
+ )
268
+ text_data = []
269
+
270
+ if in_table and len(table_data) > 0:
271
+ data = self._populate_table_as_grid(table_data)
272
+ doc.add_table(data=data, parent=self._get_current_parent(parents))
273
+
274
+ in_table = False
275
+ table_data = []
276
+
277
+ return doc
278
+
279
+ def _get_current_level(self, parents):
280
+ for k, v in parents.items():
281
+ if v == None and k > 0:
282
+ return k - 1
283
+
284
+ return 0
285
+
286
+ def _get_current_parent(self, parents):
287
+ for k, v in parents.items():
288
+ if v == None and k > 0:
289
+ return parents[k - 1]
290
+
291
+ return None
292
+
293
+ # ========= Title
294
+ def _is_title(self, line):
295
+ return re.match(r"^= ", line)
296
+
297
+ def _parse_title(self, line):
298
+ return {"type": "title", "text": line[2:].strip(), "level": 0}
299
+
300
+ # ========= Section headers
301
+ def _is_section_header(self, line):
302
+ return re.match(r"^==+", line)
303
+
304
+ def _parse_section_header(self, line):
305
+ match = re.match(r"^(=+)\s+(.*)", line)
306
+
307
+ marker = match.group(1) # The list marker (e.g., "*", "-", "1.")
308
+ text = match.group(2) # The actual text of the list item
309
+
310
+ header_level = marker.count("=") # number of '=' represents level
311
+ return {
312
+ "type": "header",
313
+ "level": header_level - 1,
314
+ "text": text.strip(),
315
+ }
316
+
317
+ # ========= Lists
318
+ def _is_list_item(self, line):
319
+ return re.match(r"^(\s)*(\*|-|\d+\.|\w+\.) ", line)
320
+
321
+ def _parse_list_item(self, line):
322
+ """Extract the item marker (number or bullet symbol) and the text of the item."""
323
+
324
+ match = re.match(r"^(\s*)(\*|-|\d+\.)\s+(.*)", line)
325
+ if match:
326
+ indent = match.group(1)
327
+ marker = match.group(2) # The list marker (e.g., "*", "-", "1.")
328
+ text = match.group(3) # The actual text of the list item
329
+
330
+ if marker == "*" or marker == "-":
331
+ return {
332
+ "type": "list_item",
333
+ "marker": marker,
334
+ "text": text.strip(),
335
+ "numbered": False,
336
+ "indent": 0 if indent == None else len(indent),
337
+ }
338
+ else:
339
+ return {
340
+ "type": "list_item",
341
+ "marker": marker,
342
+ "text": text.strip(),
343
+ "numbered": True,
344
+ "indent": 0 if indent == None else len(indent),
345
+ }
346
+ else:
347
+ # Fallback if no match
348
+ return {
349
+ "type": "list_item",
350
+ "marker": "-",
351
+ "text": line,
352
+ "numbered": False,
353
+ "indent": 0,
354
+ }
355
+
356
+ # ========= Tables
357
+ def _is_table_line(self, line):
358
+ return re.match(r"^\|.*\|", line)
359
+
360
+ def _parse_table_line(self, line):
361
+ # Split table cells and trim extra spaces
362
+ return [cell.strip() for cell in line.split("|") if cell.strip()]
363
+
364
+ def _populate_table_as_grid(self, table_data):
365
+
366
+ num_rows = len(table_data)
367
+
368
+ # Adjust the table data into a grid format
369
+ num_cols = max(len(row) for row in table_data)
370
+
371
+ data = TableData(num_rows=num_rows, num_cols=num_cols, table_cells=[])
372
+ for row_idx, row in enumerate(table_data):
373
+ # Pad rows with empty strings to match column count
374
+ # grid.append(row + [''] * (max_cols - len(row)))
375
+
376
+ for col_idx, text in enumerate(row):
377
+ row_span = 1
378
+ col_span = 1
379
+
380
+ cell = TableCell(
381
+ text=text,
382
+ row_span=row_span,
383
+ col_span=col_span,
384
+ start_row_offset_idx=row_idx,
385
+ end_row_offset_idx=row_idx + row_span,
386
+ start_col_offset_idx=col_idx,
387
+ end_col_offset_idx=col_idx + col_span,
388
+ col_header=False,
389
+ row_header=False,
390
+ )
391
+ data.table_cells.append(cell)
392
+
393
+ return data
394
+
395
+ # ========= Pictures
396
+ def _is_picture(self, line):
397
+ return re.match(r"^image::", line)
398
+
399
+ def _parse_picture(self, line):
400
+ """
401
+ Parse an image macro, extracting its path and attributes.
402
+ Syntax: image::path/to/image.png[Alt Text, width=200, height=150, align=center]
403
+ """
404
+ mtch = re.match(r"^image::(.+)\[(.*)\]$", line)
405
+ if mtch:
406
+ picture_path = mtch.group(1).strip()
407
+ attributes = mtch.group(2).split(",")
408
+ picture_info = {"type": "picture", "uri": picture_path}
409
+
410
+ # Extract optional attributes (alt text, width, height, alignment)
411
+ if attributes:
412
+ picture_info["alt"] = attributes[0].strip() if attributes[0] else ""
413
+ for attr in attributes[1:]:
414
+ key, value = attr.split("=")
415
+ picture_info[key.strip()] = value.strip()
416
+
417
+ return picture_info
418
+
419
+ return {"type": "picture", "uri": line}
420
+
421
+ # ========= Captions
422
+ def _is_caption(self, line):
423
+ return re.match(r"^\.(.+)", line)
424
+
425
+ def _parse_caption(self, line):
426
+ mtch = re.match(r"^\.(.+)", line)
427
+ if mtch:
428
+ text = mtch.group(1)
429
+ return {"type": "caption", "text": text}
430
+
431
+ return {"type": "caption", "text": ""}
432
+
433
+ # ========= Plain text
434
+ def _parse_text(self, line):
435
+ return {"type": "text", "text": line.strip()}
@@ -6,7 +6,7 @@ from typing import Iterable, List, Optional, Union
6
6
 
7
7
  import pypdfium2 as pdfium
8
8
  from docling_core.types.doc import BoundingBox, CoordOrigin, Size
9
- from docling_parse.docling_parse import pdf_parser
9
+ from docling_parse.docling_parse import pdf_parser_v1
10
10
  from PIL import Image, ImageDraw
11
11
  from pypdfium2 import PdfPage
12
12
 
@@ -19,7 +19,7 @@ _log = logging.getLogger(__name__)
19
19
 
20
20
  class DoclingParsePageBackend(PdfPageBackend):
21
21
  def __init__(
22
- self, parser: pdf_parser, document_hash: str, page_no: int, page_obj: PdfPage
22
+ self, parser: pdf_parser_v1, document_hash: str, page_no: int, page_obj: PdfPage
23
23
  ):
24
24
  self._ppage = page_obj
25
25
  parsed_page = parser.parse_pdf_from_key_on_page(document_hash, page_no)
@@ -192,7 +192,7 @@ class DoclingParseDocumentBackend(PdfDocumentBackend):
192
192
  super().__init__(in_doc, path_or_stream)
193
193
 
194
194
  self._pdoc = pdfium.PdfDocument(self.path_or_stream)
195
- self.parser = pdf_parser()
195
+ self.parser = pdf_parser_v1()
196
196
 
197
197
  success = False
198
198
  if isinstance(self.path_or_stream, BytesIO):
@@ -26,9 +26,9 @@ class DoclingParseV2PageBackend(PdfPageBackend):
26
26
  self._ppage = page_obj
27
27
  parsed_page = parser.parse_pdf_from_key_on_page(document_hash, page_no)
28
28
 
29
- self.valid = "pages" in parsed_page
29
+ self.valid = "pages" in parsed_page and len(parsed_page["pages"]) == 1
30
30
  if self.valid:
31
- self._dpage = parsed_page["pages"][page_no]
31
+ self._dpage = parsed_page["pages"][0]
32
32
  else:
33
33
  _log.info(
34
34
  f"An error occured when loading page {page_no} of document {document_hash}."
@@ -223,7 +223,15 @@ class DoclingParseV2DocumentBackend(PdfDocumentBackend):
223
223
  )
224
224
 
225
225
  def page_count(self) -> int:
226
- return len(self._pdoc) # To be replaced with docling-parse API
226
+ # return len(self._pdoc) # To be replaced with docling-parse API
227
+
228
+ len_1 = len(self._pdoc)
229
+ len_2 = self.parser.number_of_pages(self.document_hash)
230
+
231
+ if len_1 != len_2:
232
+ _log.error(f"Inconsistent number of pages: {len_1}!={len_2}")
233
+
234
+ return len_2
227
235
 
228
236
  def load_page(self, page_no: int) -> DoclingParseV2PageBackend:
229
237
  return DoclingParseV2PageBackend(
@@ -7,6 +7,7 @@ from bs4 import BeautifulSoup
7
7
  from docling_core.types.doc import (
8
8
  DocItemLabel,
9
9
  DoclingDocument,
10
+ DocumentOrigin,
10
11
  GroupLabel,
11
12
  TableCell,
12
13
  TableData,
@@ -66,7 +67,13 @@ class HTMLDocumentBackend(DeclarativeDocumentBackend):
66
67
 
67
68
  def convert(self) -> DoclingDocument:
68
69
  # access self.path_or_stream to load stuff
69
- doc = DoclingDocument(name="dummy")
70
+ origin = DocumentOrigin(
71
+ filename=self.file.name or "file",
72
+ mimetype="text/html",
73
+ binary_hash=self.document_hash,
74
+ )
75
+
76
+ doc = DoclingDocument(name=self.file.stem or "file", origin=origin)
70
77
  _log.debug("Trying to convert HTML...")
71
78
 
72
79
  if self.is_valid():