aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mammoth/docx/numbering_xml.py62
-rw-r--r--mammoth/docx/xmlparser.py3
-rw-r--r--tests/docx/numbering_xml_tests.py43
3 files changed, 108 insertions, 0 deletions
diff --git a/mammoth/docx/numbering_xml.py b/mammoth/docx/numbering_xml.py
new file mode 100644
index 0000000..77400c1
--- /dev/null
+++ b/mammoth/docx/numbering_xml.py
@@ -0,0 +1,62 @@
+def read_numbering_xml_element(element):
+ abstract_nums = _read_abstract_nums(element)
+ nums = _read_nums(element, abstract_nums)
+ return Numbering(nums)
+
+
+def _read_abstract_nums(element):
+ abstract_num_elements = element.find_children("w:abstractNum")
+ return dict(map(_read_abstract_num, abstract_num_elements))
+
+
+def _read_abstract_num(element):
+ abstract_num_id = element.attributes.get("w:abstractNumId")
+ levels = _read_abstract_num_levels(element)
+ return abstract_num_id, levels
+
+
+def _read_abstract_num_levels(element):
+ levels = map(_read_abstract_num_level, element.find_children("w:lvl"))
+ return dict(
+ (level.level_index, level)
+ for level in levels
+ )
+
+
+def _read_abstract_num_level(element):
+ level_index = element.attributes["w:ilvl"]
+ num_fmt = element.find_child_or_null("w:numFmt").attributes.get("w:val")
+ is_ordered = num_fmt != "bullet"
+ return NumberingLevel(level_index, is_ordered)
+
+
+def _read_nums(element, abstract_nums):
+ num_elements = element.find_children("w:num")
+ return dict(
+ _read_num(num_element, abstract_nums)
+ for num_element in num_elements
+ )
+
+
+def _read_num(element, abstract_nums):
+ num_id = element.attributes.get("w:numId")
+ abstract_num_id = element.find_child_or_null("w:abstractNumId").attributes["w:val"]
+ return num_id, abstract_nums[abstract_num_id]
+
+
+class Numbering(object):
+ def __init__(self, nums):
+ self._nums = nums
+
+ def find_level(self, num_id, level):
+ num = self._nums.get(num_id)
+ if num is None:
+ return None
+ else:
+ return num[level]
+
+
+class NumberingLevel(object):
+ def __init__(self, level_index, is_ordered):
+ self.level_index = level_index
+ self.is_ordered = is_ordered
diff --git a/mammoth/docx/xmlparser.py b/mammoth/docx/xmlparser.py
index d58ea75..efeafcf 100644
--- a/mammoth/docx/xmlparser.py
+++ b/mammoth/docx/xmlparser.py
@@ -9,6 +9,9 @@ class XmlElement(XmlElementBase):
if child.name == name:
return child
return _null_xml_element
+
+ def find_children(self, name):
+ return filter(lambda child: child.name == name, self.children)
class NullXmlElement(object):
diff --git a/tests/docx/numbering_xml_tests.py b/tests/docx/numbering_xml_tests.py
new file mode 100644
index 0000000..6f4e9ac
--- /dev/null
+++ b/tests/docx/numbering_xml_tests.py
@@ -0,0 +1,43 @@
+from nose.tools import istest, assert_equal
+
+from mammoth.docx.xmlparser import element as xml_element
+from mammoth.docx.numbering_xml import read_numbering_xml_element
+
+
+@istest
+def find_level_returns_none_if_num_with_id_cannot_be_found():
+ numbering = read_numbering_xml_element(xml_element("w:numbering"))
+ assert_equal(None, numbering.find_level("47", "0"))
+
+
+_sample_numbering_xml = xml_element("w:numbering", {}, [
+ xml_element("w:abstractNum", {"w:abstractNumId": "42"}, [
+ xml_element("w:lvl", {"w:ilvl": "0"}, [
+ xml_element("w:numFmt", {"w:val": "bullet"})
+ ]),
+ xml_element("w:lvl", {"w:ilvl": "1"}, [
+ xml_element("w:numFmt", {"w:val": "decimal"})
+ ])
+ ]),
+ xml_element("w:num", {"w:numId": "47"}, [
+ xml_element("w:abstractNumId", {"w:val": "42"})
+ ])
+])
+
+
+@istest
+def level_includes_level_index():
+ numbering = read_numbering_xml_element(_sample_numbering_xml)
+ assert_equal("0", numbering.find_level("47", "0").level_index)
+ assert_equal("1", numbering.find_level("47", "1").level_index)
+
+
+@istest
+def list_is_not_ordered_if_formatted_as_bullet():
+ numbering = read_numbering_xml_element(_sample_numbering_xml)
+ assert_equal(False, numbering.find_level("47", "0").is_ordered)
+
+@istest
+def list_is_ordered_if_formatted_as_decimal():
+ numbering = read_numbering_xml_element(_sample_numbering_xml)
+ assert_equal(True, numbering.find_level("47", "1").is_ordered)