docling 2.1.0__py3-none-any.whl → 2.2.1__py3-none-any.whl
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/backend/abstract_backend.py +1 -0
- docling/backend/asciidoc_backend.py +435 -0
- docling/backend/docling_parse_backend.py +3 -3
- docling/backend/docling_parse_v2_backend.py +11 -3
- docling/backend/html_backend.py +43 -39
- docling/backend/md_backend.py +346 -0
- docling/backend/mspowerpoint_backend.py +62 -39
- docling/backend/msword_backend.py +12 -25
- docling/datamodel/base_models.py +15 -9
- docling/datamodel/document.py +33 -5
- docling/document_converter.py +18 -0
- {docling-2.1.0.dist-info → docling-2.2.1.dist-info}/METADATA +8 -7
- {docling-2.1.0.dist-info → docling-2.2.1.dist-info}/RECORD +16 -14
- {docling-2.1.0.dist-info → docling-2.2.1.dist-info}/LICENSE +0 -0
- {docling-2.1.0.dist-info → docling-2.2.1.dist-info}/WHEEL +0 -0
- {docling-2.1.0.dist-info → docling-2.2.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,346 @@
|
|
1
|
+
import logging
|
2
|
+
import re
|
3
|
+
import warnings
|
4
|
+
from io import BytesIO
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import Set, Union
|
7
|
+
|
8
|
+
import marko
|
9
|
+
import marko.ext
|
10
|
+
import marko.ext.gfm
|
11
|
+
import marko.inline
|
12
|
+
from docling_core.types.doc import (
|
13
|
+
DocItemLabel,
|
14
|
+
DoclingDocument,
|
15
|
+
DocumentOrigin,
|
16
|
+
GroupLabel,
|
17
|
+
TableCell,
|
18
|
+
TableData,
|
19
|
+
)
|
20
|
+
from marko import Markdown
|
21
|
+
|
22
|
+
from docling.backend.abstract_backend import DeclarativeDocumentBackend
|
23
|
+
from docling.datamodel.base_models import InputFormat
|
24
|
+
from docling.datamodel.document import InputDocument
|
25
|
+
|
26
|
+
_log = logging.getLogger(__name__)
|
27
|
+
|
28
|
+
|
29
|
+
class MarkdownDocumentBackend(DeclarativeDocumentBackend):
|
30
|
+
|
31
|
+
def shorten_underscore_sequences(self, markdown_text, max_length=10):
|
32
|
+
# This regex will match any sequence of underscores
|
33
|
+
pattern = r"_+"
|
34
|
+
|
35
|
+
def replace_match(match):
|
36
|
+
underscore_sequence = match.group(
|
37
|
+
0
|
38
|
+
) # Get the full match (sequence of underscores)
|
39
|
+
|
40
|
+
# Shorten the sequence if it exceeds max_length
|
41
|
+
if len(underscore_sequence) > max_length:
|
42
|
+
return "_" * max_length
|
43
|
+
else:
|
44
|
+
return underscore_sequence # Leave it unchanged if it is shorter or equal to max_length
|
45
|
+
|
46
|
+
# Use re.sub to replace long underscore sequences
|
47
|
+
shortened_text = re.sub(pattern, replace_match, markdown_text)
|
48
|
+
|
49
|
+
if len(shortened_text) != len(markdown_text):
|
50
|
+
warnings.warn("Detected potentially incorrect Markdown, correcting...")
|
51
|
+
|
52
|
+
return shortened_text
|
53
|
+
|
54
|
+
def __init__(self, in_doc: "InputDocument", path_or_stream: Union[BytesIO, Path]):
|
55
|
+
super().__init__(in_doc, path_or_stream)
|
56
|
+
|
57
|
+
_log.debug("MD INIT!!!")
|
58
|
+
|
59
|
+
# Markdown file:
|
60
|
+
self.path_or_stream = path_or_stream
|
61
|
+
self.valid = True
|
62
|
+
self.markdown = "" # To store original Markdown string
|
63
|
+
|
64
|
+
self.in_table = False
|
65
|
+
self.md_table_buffer: list[str] = []
|
66
|
+
self.inline_text_buffer = ""
|
67
|
+
|
68
|
+
try:
|
69
|
+
if isinstance(self.path_or_stream, BytesIO):
|
70
|
+
text_stream = self.path_or_stream.getvalue().decode("utf-8")
|
71
|
+
# remove invalid sequences
|
72
|
+
# very long sequences of underscores will lead to unnecessary long processing times.
|
73
|
+
# In any proper Markdown files, underscores have to be escaped,
|
74
|
+
# otherwise they represent emphasis (bold or italic)
|
75
|
+
self.markdown = self.shorten_underscore_sequences(text_stream)
|
76
|
+
if isinstance(self.path_or_stream, Path):
|
77
|
+
with open(self.path_or_stream, "r", encoding="utf-8") as f:
|
78
|
+
md_content = f.read()
|
79
|
+
# remove invalid sequences
|
80
|
+
# very long sequences of underscores will lead to unnecessary long processing times.
|
81
|
+
# In any proper Markdown files, underscores have to be escaped,
|
82
|
+
# otherwise they represent emphasis (bold or italic)
|
83
|
+
self.markdown = self.shorten_underscore_sequences(md_content)
|
84
|
+
self.valid = True
|
85
|
+
|
86
|
+
_log.debug(self.markdown)
|
87
|
+
except Exception as e:
|
88
|
+
raise RuntimeError(
|
89
|
+
f"Could not initialize MD backend for file with hash {self.document_hash}."
|
90
|
+
) from e
|
91
|
+
return
|
92
|
+
|
93
|
+
def close_table(self, doc=None):
|
94
|
+
if self.in_table:
|
95
|
+
_log.debug("=== TABLE START ===")
|
96
|
+
for md_table_row in self.md_table_buffer:
|
97
|
+
_log.debug(md_table_row)
|
98
|
+
_log.debug("=== TABLE END ===")
|
99
|
+
tcells = []
|
100
|
+
result_table = []
|
101
|
+
for n, md_table_row in enumerate(self.md_table_buffer):
|
102
|
+
data = []
|
103
|
+
if n == 0:
|
104
|
+
header = [t.strip() for t in md_table_row.split("|")[1:-1]]
|
105
|
+
for value in header:
|
106
|
+
data.append(value)
|
107
|
+
result_table.append(data)
|
108
|
+
if n > 1:
|
109
|
+
values = [t.strip() for t in md_table_row.split("|")[1:-1]]
|
110
|
+
for value in values:
|
111
|
+
data.append(value)
|
112
|
+
result_table.append(data)
|
113
|
+
|
114
|
+
for trow_ind, trow in enumerate(result_table):
|
115
|
+
for tcol_ind, cellval in enumerate(trow):
|
116
|
+
row_span = (
|
117
|
+
1 # currently supporting just simple tables (without spans)
|
118
|
+
)
|
119
|
+
col_span = (
|
120
|
+
1 # currently supporting just simple tables (without spans)
|
121
|
+
)
|
122
|
+
icell = TableCell(
|
123
|
+
text=cellval.strip(),
|
124
|
+
row_span=row_span,
|
125
|
+
col_span=col_span,
|
126
|
+
start_row_offset_idx=trow_ind,
|
127
|
+
end_row_offset_idx=trow_ind + row_span,
|
128
|
+
start_col_offset_idx=tcol_ind,
|
129
|
+
end_col_offset_idx=tcol_ind + col_span,
|
130
|
+
col_header=False,
|
131
|
+
row_header=False,
|
132
|
+
)
|
133
|
+
tcells.append(icell)
|
134
|
+
|
135
|
+
num_rows = len(result_table)
|
136
|
+
num_cols = len(result_table[0])
|
137
|
+
self.in_table = False
|
138
|
+
self.md_table_buffer = [] # clean table markdown buffer
|
139
|
+
# Initialize Docling TableData
|
140
|
+
data = TableData(num_rows=num_rows, num_cols=num_cols, table_cells=tcells)
|
141
|
+
# Populate
|
142
|
+
for tcell in tcells:
|
143
|
+
data.table_cells.append(tcell)
|
144
|
+
if len(tcells) > 0:
|
145
|
+
doc.add_table(data=data)
|
146
|
+
return
|
147
|
+
|
148
|
+
def process_inline_text(self, parent_element, doc=None):
|
149
|
+
# self.inline_text_buffer += str(text_in)
|
150
|
+
txt = self.inline_text_buffer.strip()
|
151
|
+
if len(txt) > 0:
|
152
|
+
doc.add_text(
|
153
|
+
label=DocItemLabel.PARAGRAPH,
|
154
|
+
parent=parent_element,
|
155
|
+
text=txt,
|
156
|
+
)
|
157
|
+
self.inline_text_buffer = ""
|
158
|
+
|
159
|
+
def iterate_elements(self, element, depth=0, doc=None, parent_element=None):
|
160
|
+
# Iterates over all elements in the AST
|
161
|
+
# Check for different element types and process relevant details
|
162
|
+
if isinstance(element, marko.block.Heading):
|
163
|
+
self.close_table(doc)
|
164
|
+
self.process_inline_text(parent_element, doc)
|
165
|
+
_log.debug(
|
166
|
+
f" - Heading level {element.level}, content: {element.children[0].children}"
|
167
|
+
)
|
168
|
+
if element.level == 1:
|
169
|
+
doc_label = DocItemLabel.TITLE
|
170
|
+
else:
|
171
|
+
doc_label = DocItemLabel.SECTION_HEADER
|
172
|
+
|
173
|
+
# Header could have arbitrary inclusion of bold, italic or emphasis,
|
174
|
+
# hence we need to traverse the tree to get full text of a header
|
175
|
+
strings = []
|
176
|
+
|
177
|
+
# Define a recursive function to traverse the tree
|
178
|
+
def traverse(node):
|
179
|
+
# Check if the node has a "children" attribute
|
180
|
+
if hasattr(node, "children"):
|
181
|
+
# If "children" is a list, continue traversal
|
182
|
+
if isinstance(node.children, list):
|
183
|
+
for child in node.children:
|
184
|
+
traverse(child)
|
185
|
+
# If "children" is text, add it to header text
|
186
|
+
elif isinstance(node.children, str):
|
187
|
+
strings.append(node.children)
|
188
|
+
|
189
|
+
traverse(element)
|
190
|
+
snippet_text = "".join(strings)
|
191
|
+
if len(snippet_text) > 0:
|
192
|
+
parent_element = doc.add_text(
|
193
|
+
label=doc_label, parent=parent_element, text=snippet_text
|
194
|
+
)
|
195
|
+
|
196
|
+
elif isinstance(element, marko.block.List):
|
197
|
+
self.close_table(doc)
|
198
|
+
self.process_inline_text(parent_element, doc)
|
199
|
+
_log.debug(f" - List {'ordered' if element.ordered else 'unordered'}")
|
200
|
+
list_label = GroupLabel.LIST
|
201
|
+
if element.ordered:
|
202
|
+
list_label = GroupLabel.ORDERED_LIST
|
203
|
+
parent_element = doc.add_group(
|
204
|
+
label=list_label, name=f"list", parent=parent_element
|
205
|
+
)
|
206
|
+
|
207
|
+
elif isinstance(element, marko.block.ListItem):
|
208
|
+
self.close_table(doc)
|
209
|
+
self.process_inline_text(parent_element, doc)
|
210
|
+
_log.debug(" - List item")
|
211
|
+
|
212
|
+
snippet_text = str(element.children[0].children[0].children)
|
213
|
+
is_numbered = False
|
214
|
+
if parent_element.label == GroupLabel.ORDERED_LIST:
|
215
|
+
is_numbered = True
|
216
|
+
doc.add_list_item(
|
217
|
+
enumerated=is_numbered, parent=parent_element, text=snippet_text
|
218
|
+
)
|
219
|
+
|
220
|
+
elif isinstance(element, marko.inline.Image):
|
221
|
+
self.close_table(doc)
|
222
|
+
self.process_inline_text(parent_element, doc)
|
223
|
+
_log.debug(f" - Image with alt: {element.title}, url: {element.dest}")
|
224
|
+
doc.add_picture(parent=parent_element, caption=element.title)
|
225
|
+
|
226
|
+
elif isinstance(element, marko.block.Paragraph):
|
227
|
+
self.process_inline_text(parent_element, doc)
|
228
|
+
|
229
|
+
elif isinstance(element, marko.inline.RawText):
|
230
|
+
_log.debug(f" - Paragraph (raw text): {element.children}")
|
231
|
+
snippet_text = str(element.children).strip()
|
232
|
+
# Detect start of the table:
|
233
|
+
if "|" in snippet_text:
|
234
|
+
# most likely part of the markdown table
|
235
|
+
self.in_table = True
|
236
|
+
if len(self.md_table_buffer) > 0:
|
237
|
+
self.md_table_buffer[len(self.md_table_buffer) - 1] += str(
|
238
|
+
snippet_text
|
239
|
+
)
|
240
|
+
else:
|
241
|
+
self.md_table_buffer.append(snippet_text)
|
242
|
+
else:
|
243
|
+
self.close_table(doc)
|
244
|
+
self.in_table = False
|
245
|
+
# most likely just inline text
|
246
|
+
self.inline_text_buffer += str(
|
247
|
+
element.children
|
248
|
+
) # do not strip an inline text, as it may contain important spaces
|
249
|
+
|
250
|
+
elif isinstance(element, marko.inline.CodeSpan):
|
251
|
+
self.close_table(doc)
|
252
|
+
self.process_inline_text(parent_element, doc)
|
253
|
+
_log.debug(f" - Code Span: {element.children}")
|
254
|
+
snippet_text = str(element.children).strip()
|
255
|
+
doc.add_text(
|
256
|
+
label=DocItemLabel.CODE, parent=parent_element, text=snippet_text
|
257
|
+
)
|
258
|
+
|
259
|
+
elif isinstance(element, marko.block.CodeBlock):
|
260
|
+
self.close_table(doc)
|
261
|
+
self.process_inline_text(parent_element, doc)
|
262
|
+
_log.debug(f" - Code Block: {element.children}")
|
263
|
+
snippet_text = str(element.children[0].children).strip()
|
264
|
+
doc.add_text(
|
265
|
+
label=DocItemLabel.CODE, parent=parent_element, text=snippet_text
|
266
|
+
)
|
267
|
+
|
268
|
+
elif isinstance(element, marko.block.FencedCode):
|
269
|
+
self.close_table(doc)
|
270
|
+
self.process_inline_text(parent_element, doc)
|
271
|
+
_log.debug(f" - Code Block: {element.children}")
|
272
|
+
snippet_text = str(element.children[0].children).strip()
|
273
|
+
doc.add_text(
|
274
|
+
label=DocItemLabel.CODE, parent=parent_element, text=snippet_text
|
275
|
+
)
|
276
|
+
|
277
|
+
elif isinstance(element, marko.inline.LineBreak):
|
278
|
+
self.process_inline_text(parent_element, doc)
|
279
|
+
if self.in_table:
|
280
|
+
_log.debug("Line break in a table")
|
281
|
+
self.md_table_buffer.append("")
|
282
|
+
|
283
|
+
elif isinstance(element, marko.block.HTMLBlock):
|
284
|
+
self.process_inline_text(parent_element, doc)
|
285
|
+
self.close_table(doc)
|
286
|
+
_log.debug("HTML Block: {}".format(element))
|
287
|
+
if (
|
288
|
+
len(element.children) > 0
|
289
|
+
): # If Marko doesn't return any content for HTML block, skip it
|
290
|
+
snippet_text = str(element.children).strip()
|
291
|
+
doc.add_text(
|
292
|
+
label=DocItemLabel.CODE, parent=parent_element, text=snippet_text
|
293
|
+
)
|
294
|
+
else:
|
295
|
+
if not isinstance(element, str):
|
296
|
+
self.close_table(doc)
|
297
|
+
_log.debug("Some other element: {}".format(element))
|
298
|
+
|
299
|
+
# Iterate through the element's children (if any)
|
300
|
+
if not isinstance(element, marko.block.ListItem):
|
301
|
+
if not isinstance(element, marko.block.Heading):
|
302
|
+
if not isinstance(element, marko.block.FencedCode):
|
303
|
+
# if not isinstance(element, marko.block.Paragraph):
|
304
|
+
if hasattr(element, "children"):
|
305
|
+
for child in element.children:
|
306
|
+
self.iterate_elements(child, depth + 1, doc, parent_element)
|
307
|
+
|
308
|
+
def is_valid(self) -> bool:
|
309
|
+
return self.valid
|
310
|
+
|
311
|
+
def unload(self):
|
312
|
+
if isinstance(self.path_or_stream, BytesIO):
|
313
|
+
self.path_or_stream.close()
|
314
|
+
self.path_or_stream = None
|
315
|
+
|
316
|
+
@classmethod
|
317
|
+
def supports_pagination(cls) -> bool:
|
318
|
+
return False
|
319
|
+
|
320
|
+
@classmethod
|
321
|
+
def supported_formats(cls) -> Set[InputFormat]:
|
322
|
+
return {InputFormat.MD}
|
323
|
+
|
324
|
+
def convert(self) -> DoclingDocument:
|
325
|
+
_log.debug("converting Markdown...")
|
326
|
+
|
327
|
+
origin = DocumentOrigin(
|
328
|
+
filename=self.file.name or "file",
|
329
|
+
mimetype="text/markdown",
|
330
|
+
binary_hash=self.document_hash,
|
331
|
+
)
|
332
|
+
|
333
|
+
doc = DoclingDocument(name=self.file.stem or "file", origin=origin)
|
334
|
+
|
335
|
+
if self.is_valid():
|
336
|
+
# Parse the markdown into an abstract syntax tree (AST)
|
337
|
+
marko_parser = Markdown()
|
338
|
+
parsed_ast = marko_parser.parse(self.markdown)
|
339
|
+
# Start iterating from the root of the AST
|
340
|
+
self.iterate_elements(parsed_ast, 0, doc, None)
|
341
|
+
self.process_inline_text(None, doc) # handle last hanging inline text
|
342
|
+
else:
|
343
|
+
raise RuntimeError(
|
344
|
+
f"Cannot convert md with {self.document_hash} because the backend failed to init."
|
345
|
+
)
|
346
|
+
return doc
|
@@ -83,21 +83,14 @@ class MsPowerpointDocumentBackend(DeclarativeDocumentBackend, PaginatedDocumentB
|
|
83
83
|
# Parses the PPTX into a structured document model.
|
84
84
|
# origin = DocumentOrigin(filename=self.path_or_stream.name, mimetype=next(iter(FormatToMimeType.get(InputFormat.PPTX))), binary_hash=self.document_hash)
|
85
85
|
|
86
|
-
fname = ""
|
87
|
-
if isinstance(self.path_or_stream, Path):
|
88
|
-
fname = self.path_or_stream.name
|
89
|
-
|
90
86
|
origin = DocumentOrigin(
|
91
|
-
filename=
|
87
|
+
filename=self.file.name or "file",
|
92
88
|
mimetype="application/vnd.ms-powerpoint",
|
93
89
|
binary_hash=self.document_hash,
|
94
90
|
)
|
95
|
-
|
96
|
-
docname = Path(fname).stem
|
97
|
-
else:
|
98
|
-
docname = "stream"
|
91
|
+
|
99
92
|
doc = DoclingDocument(
|
100
|
-
name=
|
93
|
+
name=self.file.stem or "file", origin=origin
|
101
94
|
) # must add origin information
|
102
95
|
doc = self.walk_linear(self.pptx_obj, doc)
|
103
96
|
|
@@ -119,10 +112,16 @@ class MsPowerpointDocumentBackend(DeclarativeDocumentBackend, PaginatedDocumentB
|
|
119
112
|
|
120
113
|
def handle_text_elements(self, shape, parent_slide, slide_ind, doc):
|
121
114
|
is_a_list = False
|
115
|
+
is_list_group_created = False
|
122
116
|
enum_list_item_value = 0
|
117
|
+
new_list = None
|
118
|
+
bullet_type = "None"
|
119
|
+
list_text = ""
|
120
|
+
list_label = GroupLabel.LIST
|
121
|
+
prov = self.generate_prov(shape, slide_ind, shape.text.strip())
|
122
|
+
|
123
|
+
# Identify if shape contains lists
|
123
124
|
for paragraph in shape.text_frame.paragraphs:
|
124
|
-
enum_list_item_value += 1
|
125
|
-
bullet_type = "None"
|
126
125
|
# Check if paragraph is a bullet point using the `element` XML
|
127
126
|
p = paragraph._element
|
128
127
|
if (
|
@@ -143,29 +142,32 @@ class MsPowerpointDocumentBackend(DeclarativeDocumentBackend, PaginatedDocumentB
|
|
143
142
|
if paragraph.level > 0:
|
144
143
|
# Most likely a sub-list
|
145
144
|
is_a_list = True
|
146
|
-
list_text = paragraph.text.strip()
|
147
|
-
|
148
|
-
prov = self.generate_prov(shape, slide_ind, shape.text.strip())
|
149
145
|
|
150
146
|
if is_a_list:
|
151
147
|
# Determine if this is an unordered list or an ordered list.
|
152
148
|
# Set GroupLabel.ORDERED_LIST when it fits.
|
153
|
-
list_label = GroupLabel.LIST
|
154
149
|
if bullet_type == "Numbered":
|
155
150
|
list_label = GroupLabel.ORDERED_LIST
|
156
151
|
|
157
|
-
new_list = doc.add_group(
|
158
|
-
label=list_label, name=f"list", parent=parent_slide
|
159
|
-
)
|
160
|
-
else:
|
161
|
-
new_list = None
|
162
|
-
|
163
152
|
if is_a_list:
|
164
153
|
_log.debug("LIST DETECTED!")
|
165
154
|
else:
|
166
155
|
_log.debug("No List")
|
167
156
|
|
168
|
-
|
157
|
+
# If there is a list inside of the shape, create a new docling list to assign list items to
|
158
|
+
# if is_a_list:
|
159
|
+
# new_list = doc.add_group(
|
160
|
+
# label=list_label, name=f"list", parent=parent_slide
|
161
|
+
# )
|
162
|
+
|
163
|
+
# Iterate through paragraphs to build up text
|
164
|
+
for paragraph in shape.text_frame.paragraphs:
|
165
|
+
# p_text = paragraph.text.strip()
|
166
|
+
p = paragraph._element
|
167
|
+
enum_list_item_value += 1
|
168
|
+
inline_paragraph_text = ""
|
169
|
+
inline_list_item_text = ""
|
170
|
+
|
169
171
|
for e in p.iterfind(".//a:r", namespaces={"a": self.namespaces["a"]}):
|
170
172
|
if len(e.text.strip()) > 0:
|
171
173
|
e_is_a_list_item = False
|
@@ -187,15 +189,17 @@ class MsPowerpointDocumentBackend(DeclarativeDocumentBackend, PaginatedDocumentB
|
|
187
189
|
e_is_a_list_item = False
|
188
190
|
|
189
191
|
if e_is_a_list_item:
|
192
|
+
if len(inline_paragraph_text) > 0:
|
193
|
+
# output accumulated inline text:
|
194
|
+
doc.add_text(
|
195
|
+
label=doc_label,
|
196
|
+
parent=parent_slide,
|
197
|
+
text=inline_paragraph_text,
|
198
|
+
prov=prov,
|
199
|
+
)
|
190
200
|
# Set marker and enumerated arguments if this is an enumeration element.
|
191
|
-
|
192
|
-
|
193
|
-
marker=enum_marker,
|
194
|
-
enumerated=is_numbered,
|
195
|
-
parent=new_list,
|
196
|
-
text=list_text,
|
197
|
-
prov=prov,
|
198
|
-
)
|
201
|
+
inline_list_item_text += e.text
|
202
|
+
# print(e.text)
|
199
203
|
else:
|
200
204
|
# Assign proper label to the text, depending if it's a Title or Section Header
|
201
205
|
# For other types of text, assign - PARAGRAPH
|
@@ -210,15 +214,34 @@ class MsPowerpointDocumentBackend(DeclarativeDocumentBackend, PaginatedDocumentB
|
|
210
214
|
doc_label = DocItemLabel.TITLE
|
211
215
|
elif placeholder_type == PP_PLACEHOLDER.SUBTITLE:
|
212
216
|
DocItemLabel.SECTION_HEADER
|
213
|
-
|
214
217
|
enum_list_item_value = 0
|
218
|
+
inline_paragraph_text += e.text
|
215
219
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
220
|
+
if len(inline_paragraph_text) > 0:
|
221
|
+
# output accumulated inline text:
|
222
|
+
doc.add_text(
|
223
|
+
label=doc_label,
|
224
|
+
parent=parent_slide,
|
225
|
+
text=inline_paragraph_text,
|
226
|
+
prov=prov,
|
227
|
+
)
|
228
|
+
|
229
|
+
if len(inline_list_item_text) > 0:
|
230
|
+
enum_marker = ""
|
231
|
+
if is_numbered:
|
232
|
+
enum_marker = str(enum_list_item_value) + "."
|
233
|
+
if not is_list_group_created:
|
234
|
+
new_list = doc.add_group(
|
235
|
+
label=list_label, name=f"list", parent=parent_slide
|
236
|
+
)
|
237
|
+
is_list_group_created = True
|
238
|
+
doc.add_list_item(
|
239
|
+
marker=enum_marker,
|
240
|
+
enumerated=is_numbered,
|
241
|
+
parent=new_list,
|
242
|
+
text=inline_list_item_text,
|
243
|
+
prov=prov,
|
244
|
+
)
|
222
245
|
return
|
223
246
|
|
224
247
|
def handle_title(self, shape, parent_slide, slide_ind, doc):
|
@@ -311,7 +334,7 @@ class MsPowerpointDocumentBackend(DeclarativeDocumentBackend, PaginatedDocumentB
|
|
311
334
|
if len(tcells) > 0:
|
312
335
|
# If table is not fully empty...
|
313
336
|
# Create Docling table
|
314
|
-
doc.add_table(data=data, prov=prov)
|
337
|
+
doc.add_table(parent=parent_slide, data=data, prov=prov)
|
315
338
|
return
|
316
339
|
|
317
340
|
def walk_linear(self, pptx_obj, doc) -> DoclingDocument:
|
@@ -85,20 +85,13 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend):
|
|
85
85
|
def convert(self) -> DoclingDocument:
|
86
86
|
# Parses the DOCX into a structured document model.
|
87
87
|
|
88
|
-
fname = ""
|
89
|
-
if isinstance(self.path_or_stream, Path):
|
90
|
-
fname = self.path_or_stream.name
|
91
|
-
|
92
88
|
origin = DocumentOrigin(
|
93
|
-
filename=
|
89
|
+
filename=self.file.name or "file",
|
94
90
|
mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
95
91
|
binary_hash=self.document_hash,
|
96
92
|
)
|
97
|
-
|
98
|
-
|
99
|
-
else:
|
100
|
-
docname = "stream"
|
101
|
-
doc = DoclingDocument(name=docname, origin=origin)
|
93
|
+
|
94
|
+
doc = DoclingDocument(name=self.file.stem or "file", origin=origin)
|
102
95
|
if self.is_valid():
|
103
96
|
assert self.docx_obj is not None
|
104
97
|
doc = self.walk_linear(self.docx_obj.element.body, self.docx_obj, doc)
|
@@ -301,13 +294,7 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend):
|
|
301
294
|
level = self.get_level()
|
302
295
|
if isinstance(curr_level, int):
|
303
296
|
|
304
|
-
if curr_level
|
305
|
-
|
306
|
-
self.parents[level] = doc.add_heading(
|
307
|
-
parent=self.parents[level - 1], text=text
|
308
|
-
)
|
309
|
-
|
310
|
-
elif curr_level > level:
|
297
|
+
if curr_level > level:
|
311
298
|
|
312
299
|
# add invisible group
|
313
300
|
for i in range(level, curr_level):
|
@@ -317,10 +304,6 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend):
|
|
317
304
|
name=f"header-{i}",
|
318
305
|
)
|
319
306
|
|
320
|
-
self.parents[curr_level] = doc.add_heading(
|
321
|
-
parent=self.parents[curr_level - 1], text=text
|
322
|
-
)
|
323
|
-
|
324
307
|
elif curr_level < level:
|
325
308
|
|
326
309
|
# remove the tail
|
@@ -328,13 +311,17 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend):
|
|
328
311
|
if key >= curr_level:
|
329
312
|
self.parents[key] = None
|
330
313
|
|
331
|
-
|
332
|
-
|
333
|
-
|
314
|
+
self.parents[curr_level] = doc.add_heading(
|
315
|
+
parent=self.parents[curr_level - 1],
|
316
|
+
text=text,
|
317
|
+
level=curr_level,
|
318
|
+
)
|
334
319
|
|
335
320
|
else:
|
336
321
|
self.parents[self.level] = doc.add_heading(
|
337
|
-
parent=self.parents[self.level - 1],
|
322
|
+
parent=self.parents[self.level - 1],
|
323
|
+
text=text,
|
324
|
+
level=1,
|
338
325
|
)
|
339
326
|
return
|
340
327
|
|
docling/datamodel/base_models.py
CHANGED
@@ -30,6 +30,8 @@ class InputFormat(str, Enum):
|
|
30
30
|
HTML = "html"
|
31
31
|
IMAGE = "image"
|
32
32
|
PDF = "pdf"
|
33
|
+
ASCIIDOC = "asciidoc"
|
34
|
+
MD = "md"
|
33
35
|
|
34
36
|
|
35
37
|
class OutputFormat(str, Enum):
|
@@ -43,29 +45,33 @@ FormatToExtensions: Dict[InputFormat, List[str]] = {
|
|
43
45
|
InputFormat.DOCX: ["docx", "dotx", "docm", "dotm"],
|
44
46
|
InputFormat.PPTX: ["pptx", "potx", "ppsx", "pptm", "potm", "ppsm"],
|
45
47
|
InputFormat.PDF: ["pdf"],
|
48
|
+
InputFormat.MD: ["md"],
|
46
49
|
InputFormat.HTML: ["html", "htm", "xhtml"],
|
47
50
|
InputFormat.IMAGE: ["jpg", "jpeg", "png", "tif", "tiff", "bmp"],
|
51
|
+
InputFormat.ASCIIDOC: ["adoc", "asciidoc", "asc"],
|
48
52
|
}
|
49
53
|
|
50
|
-
FormatToMimeType: Dict[InputFormat,
|
51
|
-
InputFormat.DOCX:
|
54
|
+
FormatToMimeType: Dict[InputFormat, List[str]] = {
|
55
|
+
InputFormat.DOCX: [
|
52
56
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
53
57
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.template",
|
54
|
-
|
55
|
-
InputFormat.PPTX:
|
58
|
+
],
|
59
|
+
InputFormat.PPTX: [
|
56
60
|
"application/vnd.openxmlformats-officedocument.presentationml.template",
|
57
61
|
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
58
62
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
59
|
-
|
60
|
-
InputFormat.HTML:
|
61
|
-
InputFormat.IMAGE:
|
63
|
+
],
|
64
|
+
InputFormat.HTML: ["text/html", "application/xhtml+xml"],
|
65
|
+
InputFormat.IMAGE: [
|
62
66
|
"image/png",
|
63
67
|
"image/jpeg",
|
64
68
|
"image/tiff",
|
65
69
|
"image/gif",
|
66
70
|
"image/bmp",
|
67
|
-
|
68
|
-
InputFormat.PDF:
|
71
|
+
],
|
72
|
+
InputFormat.PDF: ["application/pdf"],
|
73
|
+
InputFormat.ASCIIDOC: ["text/asciidoc"],
|
74
|
+
InputFormat.MD: ["text/markdown", "text/x-markdown"],
|
69
75
|
}
|
70
76
|
MimeTypeToFormat = {
|
71
77
|
mime: fmt for fmt, mimes in FormatToMimeType.items() for mime in mimes
|