aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2016-03-14 18:23:34 +0000
committerMichael Williamson <mike@zwobble.org>2016-03-14 20:09:13 +0000
commit6cd476e739583c4c370b47d274a87a312d9aed9e (patch)
treecf7fa2a2077abf9f7884e4815f1b6f36ee913fad
parent00cfd0d5bae2e7688754dfc458016d42abfcd037 (diff)
Always store list of elements in _ReadResult
-rw-r--r--mammoth/docx/body_xml.py175
-rw-r--r--tests/docx/body_xml_tests.py49
2 files changed, 116 insertions, 108 deletions
diff --git a/mammoth/docx/body_xml.py b/mammoth/docx/body_xml.py
index f19fa6a..30c2aa1 100644
--- a/mammoth/docx/body_xml.py
+++ b/mammoth/docx/body_xml.py
@@ -36,11 +36,15 @@ class _BodyReader(object):
def read(self, element):
result = self._read(element)
- return results.Result(result.value, result.messages)
+ if result.elements:
+ element = result.elements[0]
+ else:
+ element = None
+ return results.Result(element, result.messages)
def read_all(self, elements):
result = self._read_all(elements)
- return results.Result(result.value, result.messages)
+ return results.Result(result.elements, result.messages)
def _create_reader(numbering, content_types, relationships, styles, docx_file, files):
@@ -70,10 +74,7 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
def run(element):
- messages = []
properties = element.find_child_or_null("w:rPr")
- style_id, style_name = _read_run_style(properties, messages)
-
vertical_alignment = properties \
.find_child_or_null("w:vertAlign") \
.attributes.get("w:val")
@@ -83,46 +84,43 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
is_underline = properties.find_child("w:u")
is_strikethrough = properties.find_child("w:strike")
- return _read_xml_elements(element.children) \
- .bind(lambda children: _element_result_with_messages(
- documents.run(
- children=children,
- style_id=style_id,
- style_name=style_name,
- is_bold=is_bold,
- is_italic=is_italic,
- is_underline=is_underline,
- is_strikethrough=is_strikethrough,
- vertical_alignment=vertical_alignment,
- ),
- messages))
+ return _ReadResult.map_results(
+ _read_run_style(properties),
+ _read_xml_elements(element.children),
+ lambda style, children: documents.run(
+ children=children,
+ style_id=style[0],
+ style_name=style[1],
+ is_bold=is_bold,
+ is_italic=is_italic,
+ is_underline=is_underline,
+ is_strikethrough=is_strikethrough,
+ vertical_alignment=vertical_alignment,
+ ))
def paragraph(element):
- messages = []
properties = element.find_child_or_null("w:pPr")
-
- style_id, style_name = _read_paragraph_style(properties, messages)
numbering = _read_numbering_properties(properties.find_child_or_null("w:numPr"))
- return _read_xml_elements(element.children) \
- .bind(lambda children: _element_result_with_messages(
- documents.paragraph(
- children=children,
- style_id=style_id,
- style_name=style_name,
- numbering=numbering,
- ),
- messages)) \
- .append_extra()
-
- def _read_paragraph_style(properties, messages):
- return _read_style(properties, "w:pStyle", "Paragraph", styles.find_paragraph_style_by_id, messages)
-
- def _read_run_style(properties, messages):
- return _read_style(properties, "w:rStyle", "Run", styles.find_character_style_by_id, messages)
-
- def _read_style(properties, style_tag_name, style_type, find_style_by_id, messages):
+ 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=numbering,
+ )).append_extra()
+
+ def _read_paragraph_style(properties):
+ return _read_style(properties, "w:pStyle", "Paragraph", styles.find_paragraph_style_by_id)
+
+ def _read_run_style(properties):
+ return _read_style(properties, "w:rStyle", "Run", styles.find_character_style_by_id)
+
+ def _read_style(properties, style_tag_name, style_type, find_style_by_id):
+ messages = []
style_id = properties \
.find_child_or_null(style_tag_name) \
.attributes.get("w:val")
@@ -137,7 +135,7 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
else:
style_name = style.name
- return style_id, style_name
+ return _ReadResult([style_id, style_name], [], messages)
def _undefined_style_warning(style_type, style_id):
return results.warning("{0} style with ID {1} was referenced but not defined in the document".format(style_type, style_id))
@@ -202,7 +200,7 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
break_type = element.attributes.get("w:type")
if break_type:
warning = results.warning("Unsupported break type: {0}".format(break_type))
- return _empty_result_with_messages([warning])
+ return _empty_result_with_message(warning)
else:
return _success(documents.line_break())
@@ -216,7 +214,7 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
return _read_blips(blips, alt_text)
def _read_blips(blips, alt_text):
- return _combine_results(map(lambda blip: _read_blip(blip, alt_text), blips))
+ return _ReadResult.concat(map(lambda blip: _read_blip(blip, alt_text), blips))
def _read_blip(element, alt_text):
return _read_image(lambda: _find_blip_image(element), alt_text)
@@ -307,17 +305,16 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
if handler is None:
if element.name not in _ignored_elements:
warning = results.warning("An unrecognised element was ignored: {0}".format(element.name))
- return _empty_result_with_messages([warning])
+ return _empty_result_with_message(warning)
else:
- return _success(None)
+ return _empty_result
else:
return handler(element)
def _read_xml_elements(nodes):
elements = filter(lambda node: isinstance(node, XmlElement), nodes)
- return _combine_results(map(read, elements)) \
- .map(lambda values: lists.collect(values))
+ return _ReadResult.concat(map(read, elements))
return read, _read_xml_elements
@@ -331,68 +328,52 @@ def _inner_text(node):
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
+ @staticmethod
+ def concat(results):
+ return _ReadResult(
+ lists.flat_map(lambda result: result.elements, results),
+ lists.flat_map(lambda result: result.extra, results),
+ lists.flat_map(lambda result: result.messages, results))
+
+
+ @staticmethod
+ def map_results(first, second, func):
+ return _ReadResult(
+ [func(first.elements, second.elements)],
+ first.extra + second.extra,
+ first.messages + second.messages)
+
+ def __init__(self, elements, extra, messages):
+ self.elements = elements
+ self.extra = extra
+ self.messages = messages
def map(self, func):
- result = self._result.map(lambda value: func(value[0]))
- return _ReadResult(result.value, self.extra, result.messages)
-
- def bind(self, func):
- result = self._result.bind(lambda value: func(value[0]))
- return _ReadResult(result.value, self.extra, result.messages)
+ return _ReadResult(
+ [func(self.elements)],
+ self.extra,
+ self.messages)
def to_extra(self):
- return _ReadResult(None, _concat(self.extra, self.value), self.messages)
+ return _ReadResult([], _concat(self.extra, self.elements), self.messages)
def append_extra(self):
- return _ReadResult(_concat(self.value, self.extra), None, self.messages)
+ return _ReadResult(_concat(self.elements, self.extra), [], self.messages)
def _success(element):
- return _ReadResult(element, None, [])
+ return _ReadResult([element], [], [])
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)
+ return _ReadResult([element], [], messages)
-_empty_result = _success(None)
+_empty_result = _ReadResult([], [], [])
-def _empty_result_with_messages(messages):
- return _ReadResult(None, None, messages)
+def _empty_result_with_message(message):
+ return _ReadResult([], [], [message])
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]
+ result = []
+ for value in values:
+ for element in value:
+ result.append(element)
+ return result
diff --git a/tests/docx/body_xml_tests.py b/tests/docx/body_xml_tests.py
index 63ec33d..a268da6 100644
--- a/tests/docx/body_xml_tests.py
+++ b/tests/docx/body_xml_tests.py
@@ -273,7 +273,7 @@ class ReadXmlElementTests(object):
run_element = xml_element("w:r")
element = xml_element("w:hyperlink", {}, [run_element])
assert_equal(
- [documents.run([])],
+ documents.run([]),
_read_and_get_document_xml_element(element)
)
@@ -361,7 +361,7 @@ class ReadXmlElementTests(object):
content_types=content_types,
relationships=relationships,
docx_file=docx_file,
- )[0]
+ )
assert_equal(documents.Image, type(image))
assert_equal("It's a hat", image.alt_text)
assert_equal("image/png", image.content_type)
@@ -393,7 +393,7 @@ class ReadXmlElementTests(object):
content_types=content_types,
relationships=relationships,
docx_file=docx_file,
- )[0]
+ )
assert_equal(documents.Image, type(image))
assert_equal("It's a hat", image.alt_text)
assert_equal("image/png", image.content_type)
@@ -427,7 +427,7 @@ class ReadXmlElementTests(object):
relationships=relationships,
docx_file=docx_file,
)
- assert_equal("image/x-emf", result.value[0].content_type)
+ 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)
@@ -458,7 +458,7 @@ class ReadXmlElementTests(object):
content_types=content_types,
relationships=relationships,
files=files,
- )[0]
+ )
assert_equal(documents.Image, type(image))
assert_equal("It's a hat", image.alt_text)
assert_equal("image/png", image.content_type)
@@ -512,7 +512,7 @@ class ReadXmlElementTests(object):
paragraph = xml_element("w:p", {}, [
xml_element("w:r", {}, [text_box])
])
- result = _read_and_get_document_xml_element(paragraph)
+ result = _read_and_get_document_xml_elements(paragraph)
assert_equal(result[1].style_id, "textbox-content")
@istest
@@ -526,7 +526,7 @@ class ReadXmlElementTests(object):
])
])
result = _read_and_get_document_xml_element(element)
- assert_equal("second", result[0].style_id)
+ assert_equal("second", result.style_id)
@istest
def text_nodes_are_ignored_when_reading_children(self):
@@ -536,17 +536,44 @@ class ReadXmlElementTests(object):
_read_and_get_document_xml_element(element)
)
-
def _read_and_get_document_xml_element(*args, **kwargs):
+ return _read_and_get_document_xml(
+ lambda reader, element: reader.read(element),
+ *args,
+ **kwargs)
+
+
+def _read_and_get_document_xml_elements(*args, **kwargs):
+ return _read_and_get_document_xml(
+ lambda reader, element: reader.read_all([element]),
+ *args,
+ **kwargs)
+
+
+def _read_and_get_document_xml(func, *args, **kwargs):
styles = kwargs.pop("styles", FakeStyles())
- result = _read_document_xml_element(*args, styles=styles, **kwargs)
+ result = _read_document_xml(func, *args, styles=styles, **kwargs)
assert_equal([], result.messages)
return result.value
-def _read_document_xml_element(element, *args, **kwargs):
+def _read_document_xml_element(*args, **kwargs):
+ return _read_document_xml(
+ lambda reader, element: reader.read(element),
+ *args,
+ **kwargs)
+
+
+def _read_document_xml_elements(*args, **kwargs):
+ return _read_document_xml(
+ lambda reader, element: reader.read_all([element]),
+ *args,
+ **kwargs)
+
+
+def _read_document_xml(func, element, *args, **kwargs):
reader = body_xml.reader(*args, **kwargs)
- return reader.read(element)
+ return func(reader, element)
class FakeStyles(object):