aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/zwobble/mammoth/internal/docx/NumberingXml.java
blob: 012d548f3b50ef041fe5c9625a6782a3ef30775a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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;

import static org.zwobble.mammoth.internal.util.Maps.entry;
import static org.zwobble.mammoth.internal.util.Maps.toMap;

public class NumberingXml {
    public static Numbering readNumberingXmlElement(XmlElement element, Styles styles) {
        Map<String, Numbering.AbstractNum> abstractNums = readAbstractNums(element.findChildren("w:abstractNum"));
        Map<String, Numbering.Num> nums = readNums(element.findChildren("w:num"));
        return new Numbering(abstractNums, nums, styles);
    }

    private static Map<String, Numbering.AbstractNum> readAbstractNums(XmlElementList children) {
        return toMap(children, NumberingXml::readAbstractNum);
    }

    private static Map.Entry<String, Numbering.AbstractNum> readAbstractNum(XmlElement element) {
        // TODO: in python-mammoth, we allow None here. Check whether that's actually possible or not
        String abstractNumId = element.getAttribute("w:abstractNumId");
        Numbering.AbstractNum abstractNum = new Numbering.AbstractNum(
            readAbstractNumLevels(element),
            element.findChildOrEmpty("w:numStyleLink").getAttributeOrNone("w:val")
        );
        return entry(abstractNumId, abstractNum);
    }

    private static Map<String, Numbering.AbstractNumLevel> readAbstractNumLevels(XmlElement element) {
        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<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");
        Numbering.AbstractNumLevel abstractNumLevel = new Numbering.AbstractNumLevel(
            levelIndex.orElse("0"),
            isOrdered,
            paragraphStyleId
        );
        return entry(levelIndex, abstractNumLevel);
    }

    private static Map<String, Numbering.Num> readNums(XmlElementList numElements) {
        return toMap(numElements, NumberingXml::readNum);
    }

    private static Map.Entry<String, Numbering.Num> readNum(XmlElement numElement) {
        // TODO: in python-mammoth, we allow None here. Check whether that's actually possible or not
        String numId = numElement.getAttribute("w:numId");
        Optional<String> abstractNumId = numElement.findChildOrEmpty("w:abstractNumId").getAttributeOrNone("w:val");
        return entry(numId, new Numbering.Num(abstractNumId));
    }
}