summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS4
-rw-r--r--mammoth/docx/body_xml.py57
-rw-r--r--tests/docx/body_xml_tests.py55
3 files changed, 96 insertions, 20 deletions
diff --git a/NEWS b/NEWS
index a060285..8302447 100644
--- a/NEWS
+++ b/NEWS
@@ -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])