Skip to content

Commit b388fb0

Browse files
committed
Do not duplicate identical styles
1 parent 58f4e26 commit b388fb0

2 files changed

Lines changed: 45 additions & 9 deletions

File tree

docxcompose/composer.py

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def __init__(self, doc, preserve_styles=False):
5151

5252
self.restart_numbering = True
5353
self.preserve_styles = preserve_styles
54+
self._preserved_styles = {}
5455

5556
self.reset_reference_mapping()
5657

@@ -69,7 +70,7 @@ def append(self, doc, remove_property_fields=True):
6970
def insert(self, index, doc, remove_property_fields=True):
7071
"""Insert the given document at the given index."""
7172
self.reset_reference_mapping()
72-
self._preserved_styles = {}
73+
self._current_preserved_styles = {}
7374

7475
# Remove custom property fields but keep the values
7576
if remove_property_fields:
@@ -313,14 +314,29 @@ def add_styles(self, doc, element):
313314
# To preserve styles with the same id from added documents, we
314315
# create a copy and append a suffix to the id and name.
315316
if self.preserve_styles and our_style_id in our_style_ids:
316-
if our_style_id not in self._preserved_styles:
317+
if our_style_id not in self._current_preserved_styles:
317318
style_element = deepcopy(doc.styles.element.get_by_id(style_id))
318319
our_style_element = self.doc.styles.element.get_by_id(our_style_id)
319-
if not xml_elements_equal(
320-
style_element,
321-
our_style_element,
322-
ignored_tags=IGNORED_STYLE_TAGS,
323-
):
320+
321+
# Check if we already have an identical style
322+
preserved_style_ids = self._preserved_styles.get(
323+
our_style_id, [our_style_id]
324+
)
325+
matched_style_id = None
326+
for pstyle_id in preserved_style_ids:
327+
our_style_element = self.doc.styles.element.get_by_id(pstyle_id)
328+
if xml_elements_equal(
329+
style_element,
330+
our_style_element,
331+
ignored_tags=IGNORED_STYLE_TAGS,
332+
):
333+
matched_style_id = pstyle_id
334+
self._current_preserved_styles[our_style_id] = (
335+
style_element.styleId
336+
)
337+
break
338+
# No matching style found, insert style with a new name
339+
if matched_style_id is None:
324340
new_id = increment_name(our_style_id)
325341
new_name = None
326342
if style_element.name is not None:
@@ -335,9 +351,15 @@ def add_styles(self, doc, element):
335351
self.doc.styles.element.append(style_element)
336352
self.add_numberings(doc, style_element)
337353
self.add_linked_styles(doc, style_element)
338-
self._preserved_styles[our_style_id] = style_element.styleId
354+
self._current_preserved_styles[our_style_id] = new_id
355+
self._preserved_styles.setdefault(
356+
our_style_id, [our_style_id]
357+
).append(new_id)
358+
else:
359+
self._current_preserved_styles[our_style_id] = matched_style_id
360+
339361
for el in xpath(element, ".//w:tblStyle|.//w:pStyle|.//w:rStyle"):
340-
el.val = self._preserved_styles[our_style_id]
362+
el.val = self._current_preserved_styles[our_style_id]
341363
elif our_style_id not in our_style_ids:
342364
style_element = deepcopy(doc.styles.element.get_by_id(style_id))
343365
if style_element is not None:

tests/test_styles.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,20 @@ def test_ignore_styles_with_same_id():
8787
assert "MyCustomStyle_1" not in style_ids
8888

8989

90+
def test_preserve_styles_does_not_duplicate_identical_styles():
91+
composer = Composer(
92+
Document(docx_path("styles_preserve1.docx")), preserve_styles=True
93+
)
94+
composer.append(Document(docx_path("styles_preserve2.docx")))
95+
composer.append(Document(docx_path("styles_preserve2.docx")))
96+
composer.append(Document(docx_path("styles_preserve1.docx")))
97+
assert [
98+
s.style_id
99+
for s in composer.doc.styles
100+
if s.style_id.startswith("MyCustomStyle")
101+
] == ["MyCustomStyle", "MyCustomStyleZchn", "MyCustomStyle_1"]
102+
103+
90104
@pytest.fixture
91105
def merged_styles():
92106
composer = Composer(Document(docx_path("styles_en.docx")))

0 commit comments

Comments
 (0)