aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS2
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/docx/Numbering.java4
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/docx/NumberingXml.java36
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/docx/StatefulBodyXmlReader.java7
-rw-r--r--src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java11
-rw-r--r--src/test/java/org/zwobble/mammoth/tests/docx/NumberingXmlTests.java60
6 files changed, 112 insertions, 8 deletions
diff --git a/NEWS b/NEWS
index e1c50f8..3711c10 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,8 @@
* Support disabling external file accesses using the external_file_access argument.
+* Handle numbering levels defined without an index.
+
# 1.10.0
* Add "Heading" and "Body" styles, as found in documents created by Apple Pages,
diff --git a/src/main/java/org/zwobble/mammoth/internal/docx/Numbering.java b/src/main/java/org/zwobble/mammoth/internal/docx/Numbering.java
index 764bb87..1a39047 100644
--- a/src/main/java/org/zwobble/mammoth/internal/docx/Numbering.java
+++ b/src/main/java/org/zwobble/mammoth/internal/docx/Numbering.java
@@ -41,6 +41,10 @@ public class Numbering {
this.paragraphStyleId = paragraphStyleId;
}
+ public String levelIndex() {
+ return levelIndex;
+ }
+
public NumberingLevel toNumberingLevel() {
return new NumberingLevel(levelIndex, isOrdered);
}
diff --git a/src/main/java/org/zwobble/mammoth/internal/docx/NumberingXml.java b/src/main/java/org/zwobble/mammoth/internal/docx/NumberingXml.java
index 5f811db..012d548 100644
--- a/src/main/java/org/zwobble/mammoth/internal/docx/NumberingXml.java
+++ b/src/main/java/org/zwobble/mammoth/internal/docx/NumberingXml.java
@@ -3,6 +3,7 @@ package org.zwobble.mammoth.internal.docx;
import org.zwobble.mammoth.internal.xml.XmlElement;
import org.zwobble.mammoth.internal.xml.XmlElementList;
+import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@@ -31,15 +32,42 @@ public class NumberingXml {
}
private static Map<String, Numbering.AbstractNumLevel> readAbstractNumLevels(XmlElement element) {
- return toMap(element.findChildren("w:lvl"), NumberingXml::readAbstractNumLevel);
+ Map<String, Numbering.AbstractNumLevel> levels = new HashMap<>();
+
+ // Some malformed documents define numbering levels without an index, and
+ // reference the numbering using a w:numPr element without a w:ilvl child.
+ // To handle such cases, we assume a level of 0 as a fallback.
+ Optional<Numbering.AbstractNumLevel> levelWithoutIndex = Optional.empty();
+
+ for (XmlElement levelElement : element.findChildren("w:lvl")) {
+ Map.Entry<Optional<String>, Numbering.AbstractNumLevel> entry =
+ readAbstractNumLevel(levelElement);
+
+ if (entry.getKey().isPresent()) {
+ levels.put(entry.getKey().get(), entry.getValue());
+ } else {
+ levelWithoutIndex = Optional.of(entry.getValue());
+ }
+ }
+
+ if (levelWithoutIndex.isPresent() && !levels.containsKey(levelWithoutIndex.get().levelIndex())) {
+ levels.put(levelWithoutIndex.get().levelIndex(), levelWithoutIndex.get());
+ }
+
+ return levels;
}
- private static Map.Entry<String, Numbering.AbstractNumLevel> readAbstractNumLevel(XmlElement element) {
- String levelIndex = element.getAttribute("w:ilvl");
+ private static Map.Entry<Optional<String>, Numbering.AbstractNumLevel> readAbstractNumLevel(XmlElement element) {
+ Optional<String> levelIndex = element.getAttributeOrNone("w:ilvl");
Optional<String> numFmt = element.findChildOrEmpty("w:numFmt").getAttributeOrNone("w:val");
boolean isOrdered = !numFmt.equals(Optional.of("bullet"));
Optional<String> paragraphStyleId = element.findChildOrEmpty("w:pStyle").getAttributeOrNone("w:val");
- return entry(levelIndex, new Numbering.AbstractNumLevel(levelIndex, isOrdered, paragraphStyleId));
+ Numbering.AbstractNumLevel abstractNumLevel = new Numbering.AbstractNumLevel(
+ levelIndex.orElse("0"),
+ isOrdered,
+ paragraphStyleId
+ );
+ return entry(levelIndex, abstractNumLevel);
}
private static Map<String, Numbering.Num> readNums(XmlElementList numElements) {
diff --git a/src/main/java/org/zwobble/mammoth/internal/docx/StatefulBodyXmlReader.java b/src/main/java/org/zwobble/mammoth/internal/docx/StatefulBodyXmlReader.java
index 649a47b..1f5aff4 100644
--- a/src/main/java/org/zwobble/mammoth/internal/docx/StatefulBodyXmlReader.java
+++ b/src/main/java/org/zwobble/mammoth/internal/docx/StatefulBodyXmlReader.java
@@ -429,6 +429,13 @@ class StatefulBodyXmlReader {
}
}
+ // Some malformed documents define numbering levels without an index, and
+ // reference the numbering using a w:numPr element without a w:ilvl child.
+ // To handle such cases, we assume a level of 0 as a fallback.
+ if (numId.isPresent()) {
+ return numbering.findLevel(numId.get(), "0");
+ }
+
return Optional.empty();
}
diff --git a/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java b/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java
index ef5262a..ea3e3f3 100644
--- a/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java
+++ b/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java
@@ -259,18 +259,21 @@ public class BodyXmlTests {
}
@Test
- public void numberingPropertiesAreIgnoredIfLevelIsMissing() {
+ public void whenNumberingPropertiesAreMissingLevelThenLevelOf0IsAssumed() {
// TODO: emit warning
XmlElement element = paragraphXml(list(
element("w:pPr", list(
element("w:numPr", map(), list(
- element("w:numId", map("w:val", "42"))))))));
+ element("w:numId", map("w:val", "42"))
+ ))
+ ))
+ ));
- Numbering numbering = numberingMap(map("42", map("1", Numbering.AbstractNumLevel.ordered("1"))));
+ Numbering numbering = numberingMap(map("42", map("0", Numbering.AbstractNumLevel.ordered("0"))));
assertThat(
readSuccess(bodyReader(numbering), element),
- hasNumbering(Optional.empty()));
+ hasNumbering(NumberingLevel.ordered("0")));
}
@Test
diff --git a/src/test/java/org/zwobble/mammoth/tests/docx/NumberingXmlTests.java b/src/test/java/org/zwobble/mammoth/tests/docx/NumberingXmlTests.java
index 4853b54..676b654 100644
--- a/src/test/java/org/zwobble/mammoth/tests/docx/NumberingXmlTests.java
+++ b/src/test/java/org/zwobble/mammoth/tests/docx/NumberingXmlTests.java
@@ -79,6 +79,66 @@ public class NumberingXmlTests {
}
@Test
+ public void givenNoOtherLevelsWithIndexOf0WhenLevelIsMissingIlvlThenLevelIndexIs0() {
+ XmlElement element = element("w:numbering", list(
+ element("w:abstractNum", map("w:abstractNumId", "42"), list(
+ element("w:lvl", map(), list(
+ element("w:numFmt", map("w:val", "decimal"))
+ ))
+ )),
+ element("w:num", map("w:numId", "47"), list(
+ element("w:abstractNumId", map("w:val", "42"))
+ ))
+ ));
+
+ Numbering numbering = readNumbering(element);
+
+ assertEquals(true, numbering.findLevel("47", "0").get().isOrdered());
+ }
+
+ @Test
+ public void givenPreviousOtherLevelWithIndexOf0WhenLevelIsMissingIlvlThenLevelIsIgnored() {
+ XmlElement element = element("w:numbering", list(
+ element("w:abstractNum", map("w:abstractNumId", "42"), list(
+ element("w:lvl", map("w:ilvl", "0"), list(
+ element("w:numFmt", map("w:val", "bullet"))
+ )),
+ element("w:lvl", map(), list(
+ element("w:numFmt", map("w:val", "decimal"))
+ ))
+ )),
+ element("w:num", map("w:numId", "47"), list(
+ element("w:abstractNumId", map("w:val", "42"))
+ ))
+ ));
+
+ Numbering numbering = readNumbering(element);
+
+ assertEquals(false, numbering.findLevel("47", "0").get().isOrdered());
+ }
+
+ @Test
+ public void givenSubsequentOtherLevelWithIndexOf0WhenLevelIsMissingIlvlThenLevelIsIgnored() {
+ XmlElement element = element("w:numbering", list(
+ element("w:abstractNum", map("w:abstractNumId", "42"), list(
+ element("w:lvl", map(), list(
+ element("w:numFmt", map("w:val", "decimal"))
+ )),
+ element("w:lvl", map("w:ilvl", "0"), list(
+ element("w:numFmt", map("w:val", "bullet"))
+ ))
+ )),
+ element("w:num", map("w:numId", "47"), list(
+ element("w:abstractNumId", map("w:val", "42"))
+ ))
+ ));
+
+ Numbering numbering = readNumbering(element);
+
+ assertEquals(false, numbering.findLevel("47", "0").get().isOrdered());
+ }
+
+ @Test
public void whenAbstractNumHasNumStyleLinkThenStyleIsUsedToFindNum() {
Numbering numbering = readNumberingXmlElement(
element("w:numbering", list(