summaryrefslogtreecommitdiff
path: root/mammoth
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2025-06-08 00:22:01 +0100
committerMichael Williamson <mike@zwobble.org>2025-06-08 00:22:01 +0100
commit80e9fd089494996def1e2529d1fa127d3ee796dd (patch)
treeb54aa0b0959e5c83c43a78958669070e95e3fae8 /mammoth
parentcc1ad1d283264284e10beeff33b140abc16156d3 (diff)
Handle SDTs representing checkboxes wrapped in other elements
Diffstat (limited to 'mammoth')
-rw-r--r--mammoth/documents.py3
-rw-r--r--mammoth/docx/body_xml.py43
-rw-r--r--mammoth/transforms.py8
3 files changed, 45 insertions, 9 deletions
diff --git a/mammoth/documents.py b/mammoth/documents.py
index f9c0665..e874775 100644
--- a/mammoth/documents.py
+++ b/mammoth/documents.py
@@ -92,6 +92,9 @@ class TableCellUnmerged:
rowspan = cobble.field()
vmerge = cobble.field()
+ def copy(self, **kwargs):
+ return cobble.copy(self, **kwargs)
+
@cobble.data
class Break(Element):
break_type = cobble.field()
diff --git a/mammoth/docx/body_xml.py b/mammoth/docx/body_xml.py
index 3de2c60..5cef312 100644
--- a/mammoth/docx/body_xml.py
+++ b/mammoth/docx/body_xml.py
@@ -5,6 +5,7 @@ import sys
from .. import documents
from .. import results
from .. import lists
+from .. import transforms
from . import complex_fields
from .dingbats import dingbats
from .xmlparser import node_types, XmlElement, null_xml_element
@@ -577,17 +578,49 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
return read_child_elements(element.find_child_or_null("mc:Fallback"))
def read_sdt(element):
- checkbox = element.find_child_or_null("w:sdtPr").find_child("wordml:checkbox")
+ content_result = read_child_elements(element.find_child_or_null("w:sdtContent"))
+
+ def handle_content(content):
+ # From the WordML standard: https://learn.microsoft.com/en-us/openspecs/office_standards/ms-docx/3350cb64-931f-41f7-8824-f18b2568ce66
+ #
+ # > A CT_SdtCheckbox element that specifies that the parent
+ # > structured document tag is a checkbox when displayed in the
+ # > document. The parent structured document tag contents MUST
+ # > contain a single character and optionally an additional
+ # > character in a deleted run.
+ checkbox = element.find_child_or_null("w:sdtPr").find_child("wordml:checkbox")
+
+ if checkbox is None:
+ return content
- if checkbox is not None:
checked_element = checkbox.find_child("wordml:checked")
is_checked = (
checked_element is not None and
read_boolean_attribute_value(checked_element.attributes.get("wordml:val"))
)
- return _success(documents.checkbox(checked=is_checked))
- else:
- return read_child_elements(element.find_child_or_null("w:sdtContent"))
+ document_checkbox = documents.checkbox(checked=is_checked)
+
+ has_checkbox = False
+
+ def transform_text(text):
+ nonlocal has_checkbox
+ if len(text.value) > 0 and not has_checkbox:
+ has_checkbox = True
+ return document_checkbox
+ else:
+ return text
+
+ replaced_content = list(map(
+ transforms.element_of_type(documents.Text, transform_text),
+ content,
+ ))
+
+ if has_checkbox:
+ return replaced_content
+ else:
+ return document_checkbox
+
+ return content_result.map(handle_content)
handlers = {
"w:t": text,
diff --git a/mammoth/transforms.py b/mammoth/transforms.py
index 62bfc1f..cf96cd3 100644
--- a/mammoth/transforms.py
+++ b/mammoth/transforms.py
@@ -15,18 +15,18 @@ def element_of_type(element_type, transform):
return transform(element)
else:
return element
-
+
return _each_element(transform_element)
def _each_element(transform_element):
def transform_element_and_children(element):
- if isinstance(element, documents.HasChildren):
+ if isinstance(element, (documents.HasChildren, documents.TableCellUnmerged)):
children = list(map(transform_element_and_children, element.children))
element = element.copy(children=children)
-
+
return transform_element(element)
-
+
return transform_element_and_children