diff options
Diffstat (limited to 'src')
5 files changed, 110 insertions, 8 deletions
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( |
