diff options
| -rw-r--r-- | mammoth/docx/__init__.py | 2 | ||||
| -rw-r--r-- | mammoth/docx/document_xml.py | 105 | ||||
| -rw-r--r-- | tests/docx/document_xml_tests.py | 17 | ||||
| -rw-r--r-- | tests/mammoth_tests.py | 7 | ||||
| -rwxr-xr-x | tests/test-data/text-box.docx | bin | 0 -> 15850 bytes |
5 files changed, 116 insertions, 15 deletions
diff --git a/mammoth/docx/__init__.py b/mammoth/docx/__init__.py index 588ce2c..6e45882 100644 --- a/mammoth/docx/__init__.py +++ b/mammoth/docx/__init__.py @@ -18,6 +18,8 @@ _namespaces = [ ("content-types", "http://schemas.openxmlformats.org/package/2006/content-types"), ("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships"), ("relationships", "http://schemas.openxmlformats.org/package/2006/relationships"), + ("v", "urn:schemas-microsoft-com:vml"), + ("mc", "http://schemas.openxmlformats.org/markup-compatibility/2006"), ] def read(fileobj): diff --git a/mammoth/docx/document_xml.py b/mammoth/docx/document_xml.py index 70e25a5..84e2790 100644 --- a/mammoth/docx/document_xml.py +++ b/mammoth/docx/document_xml.py @@ -25,7 +25,8 @@ def read_document_xml_element(element, note_elements=note_elements, docx_file=docx_file, ) - return reader(element) + result = reader(element) + return results.Result(result.value, result.messages) def _create_reader(numbering, content_types, relationships, styles, note_elements, docx_file): _handlers = {} @@ -57,7 +58,7 @@ def _create_reader(numbering, content_types, relationships, styles, note_element @handler("w:t") def text(element): - return results.success(documents.Text(_inner_text(element))) + return _success(documents.Text(_inner_text(element))) @handler("w:r") @@ -116,7 +117,8 @@ def _create_reader(numbering, content_types, relationships, styles, note_element style_id=style_id, style_name=style_name, numbering=numbering, - )) + )) \ + .append_extra() def _read_numbering_properties(element): num_id = element.find_child("w:numId").attributes["w:val"] @@ -133,7 +135,7 @@ def _create_reader(numbering, content_types, relationships, styles, note_element def document(element): body_element = _find_child(element, "w:body") children_result = _read_xml_elements(body_element.children) - notes_result = results.combine(map(_read_note, note_elements)).map(documents.notes) + notes_result = _combine_results(map(_read_note, note_elements)).map(documents.notes) return results.map(documents.document, children_result, notes_result) @@ -144,7 +146,7 @@ def _create_reader(numbering, content_types, relationships, styles, note_element @handler("w:tab") def tab(element): - return results.success(documents.tab()) + return _success(documents.tab()) @handler("w:tbl") @@ -168,10 +170,18 @@ def _create_reader(numbering, content_types, relationships, styles, note_element @handler("w:ins") @handler("w:smartTag") @handler("w:drawing") + @handler("v:shape") + @handler("v:textbox") + @handler("w:txbxContent") def read_child_elements(element): return _read_xml_elements(element.children) + @handler("w:pict") + def pict(element): + return read_child_elements(element).to_extra() + + @handler("w:hyperlink") def hyperlink(element): relationship_id = element.attributes.get("r:id") @@ -189,9 +199,9 @@ def _create_reader(numbering, content_types, relationships, styles, note_element def bookmark_start(element): name = element.attributes.get("w:name") if name == "_GoBack": - return results.success(None) + return _empty_result else: - return results.success(documents.bookmark(name)) + return _success(documents.bookmark(name)) @handler("w:br") @@ -199,9 +209,9 @@ def _create_reader(numbering, content_types, relationships, styles, note_element break_type = element.attributes.get("w:type") if break_type: warning = results.warning("Unsupported break type: {0}".format(break_type)) - return results.Result(None, [warning]) + return _empty_result_with_messages([warning]) else: - return results.success(documents.line_break()) + return _success(documents.line_break()) @handler("wp:inline") @handler("wp:anchor") @@ -215,7 +225,7 @@ def _create_reader(numbering, content_types, relationships, styles, note_element return _read_blips(blips, alt_text) def _read_blips(blips, alt_text): - return results.combine(map(lambda blip: _read_blip(blip, alt_text), blips)) + return _combine_results(map(lambda blip: _read_blip(blip, alt_text), blips)) def _read_blip(element, alt_text): relationship_id = element.attributes["r:embed"] @@ -236,12 +246,12 @@ def _create_reader(numbering, content_types, relationships, styles, note_element else: messages = [results.warning("Image of type {0} is unlikely to display in web browsers".format(content_type))] - return results.Result(image, messages) + return _element_result_with_messages(image, messages) def note_reference_reader(note_type): @handler("w:{0}Reference".format(note_type)) def note_reference(element): - return results.success(documents.note_reference(note_type, element.attributes["w:id"])) + return _success(documents.note_reference(note_type, element.attributes["w:id"])) return note_reference @@ -257,15 +267,15 @@ def _create_reader(numbering, content_types, relationships, styles, note_element if handler is None: if element.name not in _ignored_elements: warning = results.warning("An unrecognised element was ignored: {0}".format(element.name)) - return results.Result(None, [warning]) + return _empty_result_with_messages([warning]) else: - return results.success(None) + return _success(None) else: return handler(element) def _read_xml_elements(elements): - return results.combine(map(read, elements)) \ + return _combine_results(map(read, elements)) \ .map(lambda values: lists.collect(values)) return read @@ -286,3 +296,68 @@ def _inner_text(node): return node.value else: return "".join(_inner_text(child) for child in node.children) + + + +class _ReadResult(object): + def __init__(self, value, extra, messages): + if extra is None: + extra = [] + self._result = results.Result((value, extra), messages) + + @property + def value(self): + return self._result.value[0] + + @property + def extra(self): + return self._result.value[1] + + @property + def messages(self): + return self._result.messages + + def map(self, func): + result = self._result.map(lambda value: func(value[0])) + return _ReadResult(result.value, self.extra, result.messages) + + def to_extra(self): + return _ReadResult(None, _concat(self.extra, self.value), self.messages) + + def append_extra(self): + return _ReadResult(_concat(self.value, self.extra), None, self.messages) + +def _success(element): + return _ReadResult(element, None, []) + +def _element_result_with_messages(element, messages): + return _ReadResult(element, None, messages) + +def _combine_results(read_results): + combined = results.combine(result._result for result in read_results) + if combined.value: + value, extras = map(list, zip(*combined.value)) + extra = sum(extras, []) + else: + value, extra = [], None + return _ReadResult(value, extra, combined.messages) + +_empty_result = _success(None) + +def _empty_result_with_messages(messages): + return _ReadResult(None, None, messages) + +def _concat(*values): + valid_values = list(filter(None, values)) + if not valid_values: + return None + elif len(valid_values) == 1: + return valid_values[0] + else: + return sum(list(map(_to_list, valid_values)), []) + +def _to_list(value): + if isinstance(value, list): + return value + else: + return [value] diff --git a/tests/docx/document_xml_tests.py b/tests/docx/document_xml_tests.py index 22b4c31..f0fa5ab 100644 --- a/tests/docx/document_xml_tests.py +++ b/tests/docx/document_xml_tests.py @@ -409,6 +409,23 @@ class ReadXmlElementTests(object): documents.run([documents.Text("Hello!")]), read_document_xml_element(element).value ) + + @istest + def text_boxes_have_content_appended_after_containing_paragraph(self): + text_box = xml_element("w:pict", {}, [ + xml_element("v:shape", {}, [ + xml_element("v:textbox", {}, [ + xml_element("w:txbxContent", {}, [ + _paragraph_with_style_id("textbox-content") + ]) + ]) + ]) + ]) + paragraph = xml_element("w:p", {}, [ + xml_element("w:r", {}, [text_box]) + ]) + result = _read_and_get_document_xml_element(paragraph) + assert_equal(result[1].style_id, "textbox-content") @istest def alternate_content_is_read_using_fallback(self): diff --git a/tests/mammoth_tests.py b/tests/mammoth_tests.py index d9f1093..18f7b48 100644 --- a/tests/mammoth_tests.py +++ b/tests/mammoth_tests.py @@ -103,6 +103,13 @@ def endnotes_are_appended_to_text(): @istest +def text_boxes_are_read(): + with open(test_path("text-box.docx"), "rb") as fileobj: + result = mammoth.convert_to_html(fileobj=fileobj) + assert_equal('<p>Datum plane</p>', result.value) + + +@istest def transform_document_is_applied_to_document_before_conversion(): def transform_document(document): document.children[0].style_id = "Heading1" diff --git a/tests/test-data/text-box.docx b/tests/test-data/text-box.docx Binary files differnew file mode 100755 index 0000000..2d53703 --- /dev/null +++ b/tests/test-data/text-box.docx |
