Skip to content

Commit 82bc0fe

Browse files
committed
Handle numbering levels defined without an index
1 parent 8216e5a commit 82bc0fe

File tree

5 files changed

+88
-9
lines changed

5 files changed

+88
-9
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
* Support disabling external file accesses using the external_file_access argument.
88

9+
* Handle numbering levels defined without an index.
10+
911
# 1.10.0
1012

1113
* Add "Heading" and "Body" styles, as found in documents created by Apple Pages,

mammoth/docx/body_xml.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,12 @@ def _read_numbering_properties(paragraph_style_id, element):
288288
if level is not None:
289289
return level
290290

291+
# Some malformed documents define numbering levels without an index, and
292+
# reference the numbering using a w:numPr element without a w:ilvl child.
293+
# To handle such cases, we assume a level of 0 as a fallback.
294+
if num_id is not None:
295+
return numbering.find_level(num_id, "0")
296+
291297
return None
292298

293299
def _read_paragraph_indent(element):

mammoth/docx/numbering_xml.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,29 @@ class _AbstractNumLevel(object):
3636

3737

3838
def _read_abstract_num_levels(element):
39-
levels = map(_read_abstract_num_level, element.find_children("w:lvl"))
40-
return dict(
41-
(level.level_index, level)
42-
for level in levels
43-
)
39+
levels = {}
40+
41+
# Some malformed documents define numbering levels without an index, and
42+
# reference the numbering using a w:numPr element without a w:ilvl child.
43+
# To handle such cases, we assume a level of 0 as a fallback.
44+
level_without_index = None
45+
46+
for level_element in element.find_children("w:lvl"):
47+
level = _read_abstract_num_level(level_element)
48+
if level.level_index is None:
49+
level.level_index = "0"
50+
level_without_index = level
51+
else:
52+
levels[level.level_index] = level
53+
54+
if "0" not in levels and level_without_index is not None:
55+
levels[level_without_index.level_index] = level_without_index
56+
57+
return levels
4458

4559

4660
def _read_abstract_num_level(element):
47-
level_index = element.attributes["w:ilvl"]
61+
level_index = element.attributes.get("w:ilvl")
4862
num_fmt = element.find_child_or_null("w:numFmt").attributes.get("w:val")
4963
is_ordered = num_fmt != "bullet"
5064
paragraph_style_id = element.find_child_or_null("w:pStyle").attributes.get("w:val")

tests/docx/body_xml_tests.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,15 +138,15 @@ def test_numbering_properties_in_paragraph_properties_takes_precedence_over_numb
138138

139139
assert_equal("1", paragraph.numbering.level_index)
140140

141-
def test_numbering_properties_are_ignored_if_lvl_is_missing(self):
141+
def test_when_numbering_properties_are_missing_level_then_level_of_0_is_assumed(self):
142142
paragraph_xml = self._paragraph_with_numbering_properties([
143143
xml_element("w:numId", {"w:val": "42"}),
144144
])
145145

146-
numbering = _NumberingMap({"42": {"1": documents.numbering_level("1", True)}})
146+
numbering = _NumberingMap({"42": {"0": documents.numbering_level("0", True)}})
147147
paragraph = _read_and_get_document_xml_element(paragraph_xml, numbering=numbering)
148148

149-
assert_equal(None, paragraph.numbering)
149+
assert_equal(documents.numbering_level("0", True), paragraph.numbering)
150150

151151
def test_numbering_properties_are_ignored_if_num_id_is_missing(self):
152152
paragraph_xml = self._paragraph_with_numbering_properties([

tests/docx/numbering_xml_tests.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,63 @@ def test_num_referencing_non_existent_abstract_num_is_ignored():
7272
assert_equal(None, numbering.find_level("47", "0"))
7373

7474

75+
def test_given_no_other_levels_with_index_of_0_when_level_is_missing_ilvl_then_level_index_is_0():
76+
element = xml_element("w:numbering", {}, [
77+
xml_element("w:abstractNum", {"w:abstractNumId": "42"}, [
78+
xml_element("w:lvl", {}, [
79+
xml_element("w:numFmt", {"w:val": "decimal"}),
80+
]),
81+
]),
82+
xml_element("w:num", {"w:numId": "47"}, [
83+
xml_element("w:abstractNumId", {"w:val": "42"})
84+
])
85+
])
86+
87+
numbering = _read_numbering_xml_element(element)
88+
89+
assert_equal(True, numbering.find_level("47", "0").is_ordered)
90+
91+
92+
def test_given_previous_other_level_with_index_of_0_when_level_is_missing_ilvl_then_level_is_ignored():
93+
element = xml_element("w:numbering", {}, [
94+
xml_element("w:abstractNum", {"w:abstractNumId": "42"}, [
95+
xml_element("w:lvl", {"w:ilvl": "0"}, [
96+
xml_element("w:numFmt", {"w:val": "bullet"}),
97+
]),
98+
xml_element("w:lvl", {}, [
99+
xml_element("w:numFmt", {"w:val": "decimal"}),
100+
]),
101+
]),
102+
xml_element("w:num", {"w:numId": "47"}, [
103+
xml_element("w:abstractNumId", {"w:val": "42"})
104+
])
105+
])
106+
107+
numbering = _read_numbering_xml_element(element)
108+
109+
assert_equal(False, numbering.find_level("47", "0").is_ordered)
110+
111+
112+
def test_given_subsequent_other_level_with_index_of_0_when_level_is_missing_ilvl_then_level_is_ignored():
113+
element = xml_element("w:numbering", {}, [
114+
xml_element("w:abstractNum", {"w:abstractNumId": "42"}, [
115+
xml_element("w:lvl", {}, [
116+
xml_element("w:numFmt", {"w:val": "decimal"}),
117+
]),
118+
xml_element("w:lvl", {"w:ilvl": "0"}, [
119+
xml_element("w:numFmt", {"w:val": "bullet"}),
120+
]),
121+
]),
122+
xml_element("w:num", {"w:numId": "47"}, [
123+
xml_element("w:abstractNumId", {"w:val": "42"})
124+
])
125+
])
126+
127+
numbering = _read_numbering_xml_element(element)
128+
129+
assert_equal(False, numbering.find_level("47", "0").is_ordered)
130+
131+
75132
def test_when_abstract_num_has_num_style_link_then_style_is_used_to_find_num():
76133
numbering = _read_numbering_xml_element(
77134
xml_element("w:numbering", {}, [

0 commit comments

Comments
 (0)