aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2019-03-17 16:50:46 +0000
committerMichael Williamson <mike@zwobble.org>2019-03-17 16:51:10 +0000
commit20c1fea1a37ac39f3114775facad74644435253e (patch)
tree061cb9cc4dee8c5be07b0068300af2d9c6c10b92
parent49618f540107bf3084e93fda426746818e74418d (diff)
Read numbering styles
-rw-r--r--mammoth/docx/styles_xml.py61
-rw-r--r--tests/docx/body_xml_tests.py234
-rw-r--r--tests/docx/styles_xml_tests.py31
3 files changed, 196 insertions, 130 deletions
diff --git a/mammoth/docx/styles_xml.py b/mammoth/docx/styles_xml.py
index 3339bb6..937f852 100644
--- a/mammoth/docx/styles_xml.py
+++ b/mammoth/docx/styles_xml.py
@@ -2,25 +2,48 @@ import collections
class Styles(object):
- def __init__(self, paragraph_styles, character_styles, table_styles):
+ @staticmethod
+ def create(paragraph_styles=None, character_styles=None, table_styles=None, numbering_styles=None):
+ if paragraph_styles is None:
+ paragraph_styles = {}
+ if character_styles is None:
+ character_styles = {}
+ if table_styles is None:
+ table_styles = {}
+ if numbering_styles is None:
+ numbering_styles = {}
+
+ return Styles(
+ paragraph_styles=paragraph_styles,
+ character_styles=character_styles,
+ table_styles=table_styles,
+ numbering_styles=numbering_styles,
+ )
+
+ def __init__(self, paragraph_styles, character_styles, table_styles, numbering_styles):
self._paragraph_styles = paragraph_styles
self._character_styles = character_styles
self._table_styles = table_styles
-
+ self._numbering_styles = numbering_styles
+
def find_paragraph_style_by_id(self, style_id):
return self._paragraph_styles.get(style_id)
-
+
def find_character_style_by_id(self, style_id):
return self._character_styles.get(style_id)
-
+
def find_table_style_by_id(self, style_id):
return self._table_styles.get(style_id)
+ def find_numbering_style_by_id(self, style_id):
+ return self._numbering_styles.get(style_id)
+
Styles.EMPTY = Styles(
paragraph_styles={},
character_styles={},
table_styles={},
+ numbering_styles={},
)
@@ -28,30 +51,48 @@ def read_styles_xml_element(element):
paragraph_styles = {}
character_styles = {}
table_styles = {}
+ numbering_styles = {}
styles = {
"paragraph": paragraph_styles,
"character": character_styles,
"table": table_styles,
}
-
+
for style_element in element.find_children("w:style"):
style = _read_style_element(style_element)
element_type = style_element.attributes["w:type"]
- style_set = styles.get(element_type)
- if style_set is not None:
- style_set[style.style_id] = style
-
+ if element_type == "numbering":
+ numbering_styles[style.style_id] = _read_numbering_style_element(style_element)
+ else:
+ style_set = styles.get(element_type)
+ if style_set is not None:
+ style_set[style.style_id] = style
+
return Styles(
paragraph_styles=paragraph_styles,
character_styles=character_styles,
table_styles=table_styles,
+ numbering_styles=numbering_styles,
)
+Style = collections.namedtuple("Style", ["style_id", "name"])
+
+
def _read_style_element(element):
style_id = element.attributes["w:styleId"]
name = element.find_child_or_null("w:name").attributes.get("w:val")
return Style(style_id=style_id, name=name)
-Style = collections.namedtuple("Style", ["style_id", "name"])
+NumberingStyle = collections.namedtuple("NumberingStyle", ["num_id"])
+
+
+def _read_numbering_style_element(element):
+ num_id = element \
+ .find_child_or_null("w:pPr") \
+ .find_child_or_null("w:numPr") \
+ .find_child_or_null("w:numId") \
+ .attributes.get("w:val")
+
+ return NumberingStyle(num_id=num_id)
diff --git a/tests/docx/body_xml_tests.py b/tests/docx/body_xml_tests.py
index b259d59..7ccd9b4 100644
--- a/tests/docx/body_xml_tests.py
+++ b/tests/docx/body_xml_tests.py
@@ -55,41 +55,39 @@ class ParagraphTests(object):
def paragraph_has_no_style_if_it_has_no_properties(self):
element = xml_element("w:p")
assert_equal(None, _read_and_get_document_xml_element(element).style_id)
-
+
@istest
def paragraph_has_style_id_and_name_read_from_paragraph_properties_if_present(self):
style_xml = xml_element("w:pStyle", {"w:val": "Heading1"})
properties_xml = xml_element("w:pPr", {}, [style_xml])
paragraph_xml = xml_element("w:p", {}, [properties_xml])
-
- styles = Styles(
+
+ styles = Styles.create(
paragraph_styles={"Heading1": Style(style_id="Heading1", name="Heading 1")},
- character_styles={},
- table_styles={},
)
-
+
paragraph = _read_and_get_document_xml_element(paragraph_xml, styles=styles)
assert_equal("Heading1", paragraph.style_id)
assert_equal("Heading 1", paragraph.style_name)
-
+
@istest
def warning_is_emitted_when_paragraph_style_cannot_be_found(self):
style_xml = xml_element("w:pStyle", {"w:val": "Heading1"})
properties_xml = xml_element("w:pPr", {}, [style_xml])
paragraph_xml = xml_element("w:p", {}, [properties_xml])
-
+
result = _read_document_xml_element(paragraph_xml, styles=Styles.EMPTY)
paragraph = result.value
assert_equal("Heading1", paragraph.style_id)
assert_equal(None, paragraph.style_name)
assert_equal([results.warning("Paragraph style with ID Heading1 was referenced but not defined in the document")], result.messages)
-
+
@istest
def paragraph_has_no_justification_if_it_has_no_justificiation_properties(self):
paragraph_xml = xml_element("w:p")
paragraph = _read_and_get_document_xml_element(paragraph_xml)
assert_equal(None, paragraph.alignment)
-
+
@istest
def paragraph_has_justification_read_from_paragraph_properties_if_present(self):
justification_xml = xml_element("w:jc", {"w:val": "center"})
@@ -97,47 +95,47 @@ class ParagraphTests(object):
paragraph_xml = xml_element("w:p", {}, [properties_xml])
paragraph = _read_and_get_document_xml_element(paragraph_xml)
assert_equal("center", paragraph.alignment)
-
+
@istest
def paragraph_has_no_numbering_if_it_has_no_numbering_properties(self):
element = xml_element("w:p")
assert_equal(None, _read_and_get_document_xml_element(element).numbering)
-
+
@istest
def paragraph_has_numbering_properties_from_paragraph_properties_if_present(self):
paragraph_xml = self._paragraph_with_numbering_properties([
xml_element("w:ilvl", {"w:val": "1"}),
xml_element("w:numId", {"w:val": "42"}),
])
-
+
numbering = Numbering({"42": {"1": documents.numbering_level("1", True)}})
paragraph = _read_and_get_document_xml_element(paragraph_xml, numbering=numbering)
-
+
assert_equal("1", paragraph.numbering.level_index)
assert_equal(True, paragraph.numbering.is_ordered)
-
+
@istest
def numbering_properties_are_ignored_if_lvl_is_missing(self):
paragraph_xml = self._paragraph_with_numbering_properties([
xml_element("w:numId", {"w:val": "42"}),
])
-
+
numbering = Numbering({"42": {"1": documents.numbering_level("1", True)}})
paragraph = _read_and_get_document_xml_element(paragraph_xml, numbering=numbering)
-
+
assert_equal(None, paragraph.numbering)
-
+
@istest
def numbering_properties_are_ignored_if_num_id_is_missing(self):
paragraph_xml = self._paragraph_with_numbering_properties([
xml_element("w:ilvl", {"w:val": "1"}),
])
-
+
numbering = Numbering({"42": {"1": documents.numbering_level("1", True)}})
paragraph = _read_and_get_document_xml_element(paragraph_xml, numbering=numbering)
-
+
assert_equal(None, paragraph.numbering)
-
+
def _paragraph_with_numbering_properties(self, children):
numbering_properties_xml = xml_element("w:numPr", {}, children)
properties_xml = xml_element("w:pPr", {}, [numbering_properties_xml])
@@ -151,7 +149,7 @@ class ParagraphIndentTests(object):
paragraph_xml = self._paragraph_with_indent({"w:start": "720", "w:left": "40"})
paragraph = _read_and_get_document_xml_element(paragraph_xml)
assert_equal("720", paragraph.indent.start)
-
+
@istest
def when_w_start_is_not_set_then_start_indent_is_read_from_w_left(self):
paragraph_xml = self._paragraph_with_indent({"w:left": "720"})
@@ -190,7 +188,7 @@ class ParagraphIndentTests(object):
assert_equal(None, paragraph.indent.end)
assert_equal(None, paragraph.indent.first_line)
assert_equal(None, paragraph.indent.hanging)
-
+
def _paragraph_with_indent(self, attributes):
indent_xml = xml_element("w:ind", attributes)
properties_xml = xml_element("w:pPr", {}, [indent_xml])
@@ -203,79 +201,77 @@ class RunTests(object):
def run_has_no_style_if_it_has_no_properties(self):
element = xml_element("w:r")
assert_equal(None, _read_and_get_document_xml_element(element).style_id)
-
+
@istest
def run_has_style_id_and_name_read_from_run_properties_if_present(self):
style_xml = xml_element("w:rStyle", {"w:val": "Heading1Char"})
-
- styles = Styles(
- paragraph_styles={},
+
+ styles = Styles.create(
character_styles={"Heading1Char": Style(style_id="Heading1Char", name="Heading 1 Char")},
- table_styles={},
)
-
+
run = self._read_run_with_properties([style_xml], styles=styles)
assert_equal("Heading1Char", run.style_id)
assert_equal("Heading 1 Char", run.style_name)
-
+
@istest
def warning_is_emitted_when_run_style_cannot_be_found(self):
style_xml = xml_element("w:rStyle", {"w:val": "Heading1Char"})
properties_xml = xml_element("w:rPr", {}, [style_xml])
run_xml = xml_element("w:r", {}, [properties_xml])
-
+
result = _read_document_xml_element(run_xml, styles=Styles.EMPTY)
run = result.value
assert_equal("Heading1Char", run.style_id)
assert_equal(None, run.style_name)
assert_equal([results.warning("Run style with ID Heading1Char was referenced but not defined in the document")], result.messages)
-
-
+
+
@istest
def run_is_not_bold_if_bold_element_is_not_present(self):
run = self._read_run_with_properties([])
assert_equal(False, run.is_bold)
-
+
@istest
def run_is_bold_if_bold_element_is_present(self):
run = self._read_run_with_properties([xml_element("w:b")])
assert_equal(True, run.is_bold)
-
+
@istest
def run_is_not_italic_if_italic_element_is_not_present(self):
run = self._read_run_with_properties([])
assert_equal(False, run.is_italic)
-
+
@istest
def run_is_italic_if_italic_element_is_present(self):
run = self._read_run_with_properties([xml_element("w:i")])
assert_equal(True, run.is_italic)
-
+
@istest
def run_is_not_underlined_if_underline_element_is_not_present(self):
run = self._read_run_with_properties([])
assert_equal(False, run.is_underline)
-
+
@istest
def run_is_underlined_if_underline_element_is_present(self):
run = self._read_run_with_properties([xml_element("w:u")])
assert_equal(True, run.is_underline)
-
+
@istest
def run_is_not_struckthrough_if_strikethrough_element_is_not_present(self):
run = self._read_run_with_properties([])
assert_equal(False, run.is_strikethrough)
-
+
@istest
def run_is_struckthrough_if_strikethrough_element_is_present(self):
run = self._read_run_with_properties([xml_element("w:strike")])
assert_equal(True, run.is_strikethrough)
-
+
@istest
def run_is_not_small_caps_if_small_caps_element_is_not_present(self):
run = self._read_run_with_properties([])
assert_equal(False, run.is_small_caps)
-
+
@istest
def run_is_small_caps_if_small_caps_element_is_present(self):
run = self._read_run_with_properties([xml_element("w:smallCaps")])
@@ -308,12 +304,12 @@ class RunTests(object):
def run_boolean_property_is_true_if_element_is_present_and_val_is_1(self, attr_name, tag_name):
run = self._read_run_with_properties([xml_element(tag_name, {"w:val": "1"})])
assert_equal(True, getattr(run, attr_name))
-
+
@istest
def run_has_baseline_vertical_alignment_if_vertical_alignment_element_is_not_present(self):
run = self._read_run_with_properties([])
assert_equal(documents.VerticalAlignment.baseline, run.vertical_alignment)
-
+
@istest
def run_has_vertical_alignment_read_from_vertical_alignment_element(self):
run = self._read_run_with_properties([xml_element("w:vertAlign", {"w:val": "superscript"})])
@@ -329,7 +325,7 @@ class RunTests(object):
font_xml = xml_element("w:rFonts", {"w:ascii": "Arial"})
run = self._read_run_with_properties([font_xml])
assert_equal("Arial", run.font)
-
+
def _read_run_with_properties(self, properties, styles=None):
properties_xml = xml_element("w:rPr", {}, properties)
run_xml = xml_element("w:r", {}, [properties_xml])
@@ -351,7 +347,7 @@ class ComplexFieldTests(object):
_HYPERLINK_INSTRTEXT = xml_element("w:instrText", {}, [
xml_text(' HYPERLINK "{0}"'.format(_URI))
])
-
+
def _is_hyperlinked_run(self, **kwargs):
return is_run(children=is_sequence(
is_hyperlink(
@@ -359,11 +355,11 @@ class ComplexFieldTests(object):
**kwargs
),
))
-
+
@property
def _is_empty_hyperlinked_run(self):
return self._is_hyperlinked_run(children=[])
-
+
@istest
def runs_in_a_complex_field_for_hyperlinks_are_read_as_hyperlinks(self):
element = xml_element("w:p", {}, [
@@ -383,7 +379,7 @@ class ComplexFieldTests(object):
)),
is_empty_run,
)))
-
+
@istest
def runs_after_a_complex_field_for_hyperlinks_are_not_read_as_hyperlinks(self):
element = xml_element("w:p", {}, [
@@ -403,7 +399,7 @@ class ComplexFieldTests(object):
is_text("this will not be a hyperlink"),
)),
)))
-
+
@istest
def can_handle_split_instr_text_elements(self):
element = xml_element("w:p", {}, [
@@ -428,7 +424,7 @@ class ComplexFieldTests(object):
)),
is_empty_run,
)))
-
+
@istest
def hyperlink_is_not_ended_by_end_of_nested_complex_field(self):
element = xml_element("w:p", {}, [
@@ -457,7 +453,7 @@ class ComplexFieldTests(object):
)),
is_empty_run,
)))
-
+
@istest
def complex_field_nested_within_a_hyperlink_complex_field_is_wrapped_with_the_hyperlink(self):
element = xml_element("w:p", {}, [
@@ -486,7 +482,7 @@ class ComplexFieldTests(object):
self._is_empty_hyperlinked_run,
is_empty_run,
)))
-
+
@istest
def field_without_separate_fld_char_is_ignored(self):
element = xml_element("w:p", {}, [
@@ -524,7 +520,7 @@ def no_break_hyphen_element_is_read_as_non_breaking_hyphen_character():
element = xml_element("w:noBreakHyphen")
tab = _read_and_get_document_xml_element(element)
assert_equal(documents.text(unichr(0x2011)), tab)
-
+
@istest
class TableTests(object):
@@ -546,40 +542,38 @@ class TableTests(object):
])
])
assert_equal(expected_result, table)
-
+
@istest
def table_has_no_style_if_it_has_no_properties(self):
element = xml_element("w:tbl")
assert_equal(None, _read_and_get_document_xml_element(element).style_id)
-
+
@istest
def table_has_style_id_and_name_read_from_paragraph_properties_if_present(self):
style_xml = xml_element("w:tblStyle", {"w:val": "TableNormal"})
properties_xml = xml_element("w:tblPr", {}, [style_xml])
table_xml = xml_element("w:tbl", {}, [properties_xml])
-
- styles = Styles(
- paragraph_styles={},
- character_styles={},
+
+ styles = Styles.create(
table_styles={"TableNormal": Style(style_id="TableNormal", name="Normal Table")},
)
-
+
paragraph = _read_and_get_document_xml_element(table_xml, styles=styles)
assert_equal("TableNormal", paragraph.style_id)
assert_equal("Normal Table", paragraph.style_name)
-
+
@istest
def warning_is_emitted_when_table_style_cannot_be_found(self):
style_xml = xml_element("w:tblStyle", {"w:val": "TableNormal"})
properties_xml = xml_element("w:tblPr", {}, [style_xml])
table_xml = xml_element("w:tbl", {}, [properties_xml])
-
+
result = _read_document_xml_element(table_xml, styles=Styles.EMPTY)
table = result.value
assert_equal("TableNormal", table.style_id)
assert_equal(None, table.style_name)
assert_equal([results.warning("Table style with ID TableNormal was referenced but not defined in the document")], result.messages)
-
+
@istest
def tbl_header_marks_table_row_as_header(self):
element = xml_element("w:tbl", {}, [
@@ -597,8 +591,8 @@ class TableTests(object):
is_row(is_header=False),
),
))
-
-
+
+
@istest
def gridspan_is_read_as_colspan_for_table_cell(self):
element = xml_element("w:tbl", {}, [
@@ -707,7 +701,7 @@ class TableTests(object):
@istest
def children_of_w_ins_are_converted_normally():
_assert_children_are_converted_normally("w:ins")
-
+
@istest
def children_of_w_object_are_converted_normally():
_assert_children_are_converted_normally("w:object")
@@ -752,7 +746,7 @@ class HyperlinkTests(object):
documents.hyperlink(href="http://example.com", children=[documents.run([])]),
_read_and_get_document_xml_element(element, relationships=relationships)
)
-
+
@istest
def hyperlink_is_read_as_external_hyperlink_if_it_has_a_relationship_id_and_an_anchor(self):
relationships = Relationships([
@@ -764,7 +758,7 @@ class HyperlinkTests(object):
documents.hyperlink(href="http://example.com/#fragment", children=[documents.run([])]),
_read_and_get_document_xml_element(element, relationships=relationships)
)
-
+
@istest
def existing_fragment_is_replaced_when_anchor_is_set_on_external_link(self):
relationships = Relationships([
@@ -776,7 +770,7 @@ class HyperlinkTests(object):
documents.hyperlink(href="http://example.com/#fragment", children=[documents.run([])]),
_read_and_get_document_xml_element(element, relationships=relationships)
)
-
+
@istest
def hyperlink_is_read_as_internal_hyperlink_if_it_has_an_anchor_attribute(self):
run_element = xml_element("w:r")
@@ -785,7 +779,7 @@ class HyperlinkTests(object):
documents.hyperlink(anchor="start", children=[documents.run([])]),
_read_and_get_document_xml_element(element)
)
-
+
@istest
def hyperlink_is_ignored_if_it_does_not_have_a_relationship_id_nor_anchor(self):
run_element = xml_element("w:r")
@@ -794,7 +788,7 @@ class HyperlinkTests(object):
documents.run([]),
_read_and_get_document_xml_element(element)
)
-
+
@istest
def target_frame_is_read(self):
element = xml_element("w:hyperlink", {
@@ -805,7 +799,7 @@ class HyperlinkTests(object):
_read_and_get_document_xml_element(element),
is_hyperlink(target_frame="_blank"),
)
-
+
@istest
def empty_target_frame_is_ignored(self):
element = xml_element("w:hyperlink", {
@@ -824,7 +818,7 @@ class BookmarkTests(object):
def go_back_bookmark_is_ignored(self):
element = xml_element("w:bookmarkStart", {"w:name": "_GoBack"})
assert_equal(None, _read_and_get_document_xml_element(element))
-
+
@istest
def bookmark_start_is_read_if_name_is_not_go_back(self):
element = xml_element("w:bookmarkStart", {"w:name": "start"})
@@ -873,67 +867,67 @@ class BreakTests(object):
class ImageTests(object):
IMAGE_BYTES = b"Not an image at all!"
IMAGE_RELATIONSHIP_ID = "rId5"
-
+
def _read_embedded_image(self, element):
relationships = Relationships([
_image_relationship(self.IMAGE_RELATIONSHIP_ID, "media/hat.png"),
])
-
+
mocks = funk.Mocks()
docx_file = mocks.mock()
funk.allows(docx_file).open("word/media/hat.png").returns(io.BytesIO(self.IMAGE_BYTES))
-
+
content_types = mocks.mock()
funk.allows(content_types).find_content_type("word/media/hat.png").returns("image/png")
-
+
return _read_and_get_document_xml_element(
element,
content_types=content_types,
relationships=relationships,
docx_file=docx_file,
)
-
+
@istest
def can_read_imagedata_elements_with_rid_attribute(self):
imagedata_element = xml_element("v:imagedata", {
"r:id": self.IMAGE_RELATIONSHIP_ID,
"o:title": "It's a hat"
})
-
+
image = self._read_embedded_image(imagedata_element)
-
+
assert_equal(documents.Image, type(image))
assert_equal("It's a hat", image.alt_text)
assert_equal("image/png", image.content_type)
with image.open() as image_file:
assert_equal(self.IMAGE_BYTES, image_file.read())
-
+
@istest
def when_imagedata_element_has_no_relationship_id_then_it_is_ignored_with_warning(self):
imagedata_element = xml_element("v:imagedata")
-
+
result = _read_document_xml_element(imagedata_element)
expected_warning = results.warning("A v:imagedata element without a relationship ID was ignored")
assert_equal([expected_warning], result.messages)
assert_equal(None, result.value)
-
-
+
+
@istest
def can_read_inline_pictures(self):
drawing_element = _create_inline_image(
blip=_embedded_blip(self.IMAGE_RELATIONSHIP_ID),
description="It's a hat",
)
-
+
image = self._read_embedded_image(drawing_element)
-
+
assert_equal(documents.Image, type(image))
assert_equal("It's a hat", image.alt_text)
assert_equal("image/png", image.content_type)
with image.open() as image_file:
assert_equal(self.IMAGE_BYTES, image_file.read())
-
-
+
+
@istest
def alt_text_title_is_used_if_alt_text_description_is_blank(self):
drawing_element = _create_inline_image(
@@ -941,32 +935,32 @@ class ImageTests(object):
description=" ",
title="It's a hat",
)
-
+
image = self._read_embedded_image(drawing_element)
-
+
assert_equal(documents.Image, type(image))
assert_equal("It's a hat", image.alt_text)
assert_equal("image/png", image.content_type)
with image.open() as image_file:
assert_equal(self.IMAGE_BYTES, image_file.read())
-
-
+
+
@istest
def alt_text_title_is_used_if_alt_text_description_is_missing(self):
drawing_element = _create_inline_image(
blip=_embedded_blip(self.IMAGE_RELATIONSHIP_ID),
title="It's a hat",
)
-
+
image = self._read_embedded_image(drawing_element)
-
+
assert_equal(documents.Image, type(image))
assert_equal("It's a hat", image.alt_text)
assert_equal("image/png", image.content_type)
with image.open() as image_file:
assert_equal(self.IMAGE_BYTES, image_file.read())
-
-
+
+
@istest
def alt_text_description_is_preferred_to_alt_text_title(self):
drawing_element = _create_inline_image(
@@ -974,31 +968,31 @@ class ImageTests(object):
description="It's a hat",
title="hat",
)
-
+
image = self._read_embedded_image(drawing_element)
-
+
assert_equal(documents.Image, type(image))
assert_equal("It's a hat", image.alt_text)
assert_equal("image/png", image.content_type)
with image.open() as image_file:
assert_equal(self.IMAGE_BYTES, image_file.read())
-
+
@istest
def can_read_anchored_pictures(self):
drawing_element = _create_anchored_image(
blip=_embedded_blip(self.IMAGE_RELATIONSHIP_ID),
description="It's a hat",
)
-
+
image = self._read_embedded_image(drawing_element)
-
+
assert_equal(documents.Image, type(image))
assert_equal("It's a hat", image.alt_text)
assert_equal("image/png", image.content_type)
with image.open() as image_file:
assert_equal(self.IMAGE_BYTES, image_file.read())
-
-
+
+
@istest
@funk.with_mocks
def warning_if_unsupported_image_type(self, mocks):
@@ -1006,17 +1000,17 @@ class ImageTests(object):
blip=_embedded_blip("rId5"),
description="It's a hat",
)
-
+
relationships = Relationships([
_image_relationship("rId5", "media/hat.emf"),
])
-
+
docx_file = mocks.mock()
funk.allows(docx_file).open("word/media/hat.emf").returns(io.BytesIO(self.IMAGE_BYTES))
-
+
content_types = mocks.mock()
funk.allows(content_types).find_content_type("word/media/hat.emf").returns("image/x-emf")
-
+
result = _read_document_xml_element(
drawing_element,
content_types=content_types,
@@ -1026,8 +1020,8 @@ class ImageTests(object):
assert_equal("image/x-emf", result.value.content_type)
expected_warning = results.warning("Image of type image/x-emf is unlikely to display in web browsers")
assert_equal([expected_warning], result.messages)
-
-
+
+
@istest
@funk.with_mocks
def can_read_linked_pictures(self, mocks):
@@ -1035,18 +1029,18 @@ class ImageTests(object):
blip=_linked_blip("rId5"),
description="It's a hat",
)
-
+
relationships = Relationships([
_image_relationship("rId5", "file:///media/hat.png"),
])
-
+
files = mocks.mock()
funk.allows(files).verify("file:///media/hat.png")
funk.allows(files).open("file:///media/hat.png").returns(io.BytesIO(self.IMAGE_BYTES))
-
+
content_types = mocks.mock()
funk.allows(content_types).find_content_type("file:///media/hat.png").returns("image/png")
-
+
image = _read_and_get_document_xml_element(
drawing_element,
content_types=content_types,
@@ -1098,7 +1092,7 @@ def unrecognised_children_are_ignored():
documents.run([documents.Text("Hello!")]),
_read_document_xml_element(element).value
)
-
+
@istest
def text_boxes_have_content_appended_after_containing_paragraph():
text_box = xml_element("w:pict", {}, [
@@ -1190,10 +1184,10 @@ def _read_document_xml(func, element, *args, **kwargs):
class FakeStyles(object):
def find_paragraph_style_by_id(self, style_id):
return Style(style_id, style_id)
-
+
def find_character_style_by_id(self, style_id):
return None
-
+
def find_table_style_by_id(self, style_id):
return None
@@ -1217,7 +1211,7 @@ def _run_element_with_text(text):
def _text_element(value):
return xml_element("w:t", {}, [xml_text(value)])
-
+
def _create_inline_image(blip, description=None, title=None):
return xml_element("w:drawing", {}, [
@@ -1230,14 +1224,14 @@ def _create_anchored_image(description, blip):
xml_element("wp:anchor", {}, _create_image_elements(blip, description=description, ))
])
-
+
def _create_image_elements(blip, description=None, title=None):
properties = {}
if description is not None:
properties["descr"] = description
if title is not None:
properties["title"] = title
-
+
return [
xml_element("wp:docPr", properties),
xml_element("a:graphic", {}, [
diff --git a/tests/docx/styles_xml_tests.py b/tests/docx/styles_xml_tests.py
index d70e068..580d91d 100644
--- a/tests/docx/styles_xml_tests.py
+++ b/tests/docx/styles_xml_tests.py
@@ -81,6 +81,37 @@ def style_name_is_none_if_name_element_does_not_exist():
assert_equal(None, styles.find_character_style_by_id("Heading1Char").name)
+@istest
+def numbering_style_is_none_if_no_style_with_that_id_exists():
+ element = xml_element("w:styles", {}, [])
+ styles = read_styles_xml_element(element)
+ assert_equal(None, styles.find_numbering_style_by_id("List1"))
+
+
+@istest
+def numbering_style_has_none_num_id_if_style_has_no_paragraph_properties():
+ element = xml_element("w:styles", {}, [
+ xml_element("w:style", {"w:type": "numbering", "w:styleId": "List1"}),
+ ])
+ styles = read_styles_xml_element(element)
+ assert_equal(None, styles.find_numbering_style_by_id("List1").num_id)
+
+
+@istest
+def numbering_style_has_num_id_read_from_paragraph_properties():
+ element = xml_element("w:styles", {}, [
+ xml_element("w:style", {"w:type": "numbering", "w:styleId": "List1"}, [
+ xml_element("w:pPr", {}, [
+ xml_element("w:numPr", {}, [
+ xml_element("w:numId", {"w:val": "42"})
+ ]),
+ ]),
+ ]),
+ ])
+ styles = read_styles_xml_element(element)
+ assert_equal("42", styles.find_numbering_style_by_id("List1").num_id)
+
+
def _paragraph_style_element(style_id, name):
return _style_element("paragraph", style_id, name)