From da0700f95953a1bb35c2e7aa8f7f02869b8dd325 Mon Sep 17 00:00:00 2001 From: Maxim Lysak Date: Wed, 9 Oct 2024 16:47:23 +0200 Subject: [PATCH] Fixes for docx backend Signed-off-by: Maxim Lysak --- docling/backend/msword_backend.py | 54 +++++++++++++++++++++++++----- tests/data/word_sample.docx | Bin 102752 -> 103051 bytes 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/docling/backend/msword_backend.py b/docling/backend/msword_backend.py index 7bd99fc0..fbde6869 100644 --- a/docling/backend/msword_backend.py +++ b/docling/backend/msword_backend.py @@ -27,6 +27,9 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend): self.XML_KEY = ( "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}val" ) + self.xml_namespaces = { + "w": "http://schemas.microsoft.com/office/word/2003/wordml" + } super().__init__(path_or_stream, document_hash) # self.initialise(path_or_stream) # Word file: @@ -107,11 +110,22 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend): for element in body: tag_name = etree.QName(element).localname + found_drawing = etree.ElementBase.xpath( + element, ".//w:drawing", namespaces=self.xml_namespaces + ) + found_pict = etree.ElementBase.xpath( + element, ".//w:pict", namespaces=self.xml_namespaces + ) + # Check for Tables if element.tag.endswith("tbl"): - self.handle_tables(element, docx_obj, doc) + try: + self.handle_tables(element, docx_obj, doc) + except Exception: + _log.error("could not parse a table, broken docx table") # Check for Inline Images (drawings or blip elements) - elif element.xpath(".//w:drawing") or element.xpath(".//w:pict"): + # elif element.xpath(".//w:drawing", namespaces = self.xml_namespaces) or element.xpath(".//w:pict", namespaces = self.xml_namespaces): + elif found_drawing or found_pict: self.handle_pictures(element, docx_obj, doc) # Check for Text elif tag_name in ["p"]: @@ -120,6 +134,14 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend): _log.warn(f"Ignoring element in DOCX with tag: {tag_name}") return doc + def str_to_int(self, s, default=0): + if s is None: + return None + try: + return int(s) + except ValueError: + return default + def get_numId_and_ilvl(self, paragraph): # Access the XML element of the paragraph numPr = paragraph._element.find( @@ -134,7 +156,9 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend): numId = numId_elem.get(self.XML_KEY) if numId_elem is not None else None ilvl = ilvl_elem.get(self.XML_KEY) if ilvl_elem is not None else None - return int(numId), int(ilvl) + return self.str_to_int(numId, default=None), self.str_to_int( + ilvl, default=None + ) return None, None # If the paragraph is not part of a list @@ -142,6 +166,8 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend): if paragraph.style is None: return "Normal", None label = paragraph.style.name + if label is None: + return "Normal", None if ":" in label: parts = label.split(":") @@ -151,12 +177,23 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend): parts = label.split(" ") if "Heading" in label and len(parts) == 2: - return parts[0], int(parts[1]) + parts.sort() + label_str = "" + label_level = 0 + if parts[0] == "Heading": + # print("{} - {}".format(parts[0], parts[1])) + label_str = parts[0] + label_level = self.str_to_int(parts[1], default=None) + if parts[1] == "Heading": + label_str = parts[1] + label_level = self.str_to_int(parts[0], default=None) + return label_str, label_level else: return label, None def handle_text_elements(self, element, docx_obj, doc): paragraph = docx.text.paragraph.Paragraph(element, docx_obj) + if paragraph.text is None: # _log.warn(f"paragraph has text==None") return @@ -167,6 +204,8 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend): p_style_name, p_level = self.get_label_and_level(paragraph) numid, ilevel = self.get_numId_and_ilvl(paragraph) + # print("paragraph.text: {} | numid: {} | ilevel: {}".format(paragraph.text, numid, ilevel)) + # Handle lists if numid is not None and ilevel is not None: self.add_listitem( @@ -209,6 +248,7 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend): else: # Text style names can, and will have, not only default values but user values too # hence we treat all other labels as pure text + level = self.get_level() doc.add_text( label=DocItemLabel.PARAGRAPH, parent=self.parents[level - 1], text=text ) @@ -262,7 +302,6 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend): ): level = self.get_level() if self.prev_numid() is None: # Open new list - self.level_at_new_list = level self.parents[level] = doc.add_group( @@ -276,7 +315,6 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend): elif ( self.prev_numid() == numid and self.prev_indent() < ilevel ): # Open indented list - for i in range( self.level_at_new_list + self.prev_indent() + 1, self.level_at_new_list + ilevel + 1, @@ -292,7 +330,6 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend): ) elif self.prev_numid() == numid and ilevel < self.prev_indent(): # Close list - for k, v in self.parents.items(): if k > self.level_at_new_list + ilevel: self.parents[k] = None @@ -304,7 +341,6 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend): ) elif self.prev_numid() == numid or self.prev_indent() == ilevel: - doc.add_text( label=DocItemLabel.LIST_ITEM, parent=self.parents[level - 1], text=text ) @@ -335,6 +371,8 @@ class MsWordDocumentBackend(DeclarativeDocumentBackend): for row in table.rows: # Calculate the max number of columns num_cols = max(num_cols, sum(get_colspan(cell) for cell in row.cells)) + # if row.cells: + # num_cols = max(num_cols, len(row.cells)) # Initialize the table grid table_grid = [[None for _ in range(num_cols)] for _ in range(num_rows)] diff --git a/tests/data/word_sample.docx b/tests/data/word_sample.docx index f57734b4f4cf9f23b9f66597369ec6e5377fa836..70ef7375ec93deb062e369988a88a9676e96c3b4 100644 GIT binary patch delta 11389 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