diff options
| -rw-r--r-- | NEWS | 4 | ||||
| -rw-r--r-- | mammoth/docx/body_xml.py | 57 | ||||
| -rw-r--r-- | tests/docx/body_xml_tests.py | 55 |
3 files changed, 96 insertions, 20 deletions
@@ -1,3 +1,7 @@ +# 1.6.0 + +* Support merged paragraphs when revisions are tracked. + # 1.5.1 * Add a pyproject.toml to add an explicit build dependency on setuptools. diff --git a/mammoth/docx/body_xml.py b/mammoth/docx/body_xml.py index c11e9e5..09ad744 100644 --- a/mammoth/docx/body_xml.py +++ b/mammoth/docx/body_xml.py @@ -49,6 +49,14 @@ class _BodyReader(object): def _create_reader(numbering, content_types, relationships, styles, docx_file, files): + current_instr_text = [] + complex_field_stack = [] + + # When a paragraph is marked as deleted, its contents should be combined + # with the following paragraph. See 17.13.5.15 del (Deleted Paragraph) of + # ECMA-376 4th edition Part 1. + deleted_paragraph_contents = [] + _ignored_elements = set([ "office-word:wrap", "v:shadow", @@ -131,30 +139,41 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f def paragraph(element): properties = element.find_child_or_null("w:pPr") - alignment = properties.find_child_or_null("w:jc").attributes.get("w:val") - indent = _read_paragraph_indent(properties.find_child_or_null("w:ind")) - return _ReadResult.map_results( - _read_paragraph_style(properties), - _read_xml_elements(element.children), - lambda style, children: documents.paragraph( - children=children, - style_id=style[0], - style_name=style[1], - numbering=_read_numbering_properties( - paragraph_style_id=style[0], - element=properties.find_child_or_null("w:numPr"), - ), - alignment=alignment, - indent=indent, - )).append_extra() + is_deleted = properties.find_child_or_null("w:rPr").find_child("w:del") + + if is_deleted is not None: + for child in element.children: + deleted_paragraph_contents.append(child) + return _empty_result + + else: + alignment = properties.find_child_or_null("w:jc").attributes.get("w:val") + indent = _read_paragraph_indent(properties.find_child_or_null("w:ind")) + + children_xml = element.children + if deleted_paragraph_contents: + children_xml = deleted_paragraph_contents + children_xml + deleted_paragraph_contents.clear() + + return _ReadResult.map_results( + _read_paragraph_style(properties), + _read_xml_elements(children_xml), + lambda style, children: documents.paragraph( + children=children, + style_id=style[0], + style_name=style[1], + numbering=_read_numbering_properties( + paragraph_style_id=style[0], + element=properties.find_child_or_null("w:numPr"), + ), + alignment=alignment, + indent=indent, + )).append_extra() def _read_paragraph_style(properties): return _read_style(properties, "w:pStyle", "Paragraph", styles.find_paragraph_style_by_id) - current_instr_text = [] - complex_field_stack = [] - def current_hyperlink_kwargs(): for complex_field in reversed(complex_field_stack): if isinstance(complex_field, complex_fields.Hyperlink): diff --git a/tests/docx/body_xml_tests.py b/tests/docx/body_xml_tests.py index 1b02b97..32f6642 100644 --- a/tests/docx/body_xml_tests.py +++ b/tests/docx/body_xml_tests.py @@ -4,7 +4,7 @@ import io import sys import funk -from precisely import assert_that, is_sequence +from precisely import all_of, assert_that, has_attrs, instance_of, is_sequence import pytest from mammoth import documents, results @@ -144,6 +144,59 @@ class ParagraphTests(object): assert_equal(None, paragraph.numbering) + def test_content_of_deleted_paragraph_is_prepended_to_next_paragraph(self): + styles = Styles.create( + paragraph_styles={ + "Heading1": Style(style_id="Heading1", name="Heading 1"), + "Heading2": Style(style_id="Heading2", name="Heading 2"), + }, + ) + body_xml = [ + xml_element("w:p", {}, [ + xml_element("w:pPr", {}, [ + xml_element("w:pStyle", {"w:val": "Heading1"}, []), + xml_element("w:rPr", {}, [ + xml_element("w:del"), + ]), + ]), + _run_element_with_text("One"), + ]), + xml_element("w:p", {}, [ + xml_element("w:pPr", {}, [ + xml_element("w:pStyle", {"w:val": "Heading2"}, []), + ]), + _run_element_with_text("Two"), + ]), + # Include a second paragraph that isn't deleted to ensure we only add + # the deleted paragraph contents once. + xml_element("w:p", {}, [ + _run_element_with_text("Three"), + ]) + ] + + result = _read_and_get_document_xml_elements(body_xml, styles=styles); + + assert_that(result, is_sequence( + all_of( + instance_of(documents.Paragraph), + has_attrs( + style_id="Heading2", + children=is_sequence( + documents.run([documents.text("One")]), + documents.run([documents.text("Two")]) + ), + ), + ), + all_of( + instance_of(documents.Paragraph), + has_attrs( + children=is_sequence( + documents.run([documents.text("Three")]), + ) + ), + ), + )) + def _paragraph_with_numbering_properties(self, children): numbering_properties_xml = xml_element("w:numPr", {}, children) properties_xml = xml_element("w:pPr", {}, [numbering_properties_xml]) |
