summaryrefslogtreecommitdiff
path: root/mammoth/docx/numbering_xml.py
blob: e1ea0b47d53c3c0e9fa37688c0b1fb8d6aaac8d4 (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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import cobble

from ..documents import numbering_level
from .styles_xml import Styles


def read_numbering_xml_element(element, styles):
    abstract_nums = _read_abstract_nums(element)
    nums = _read_nums(element)
    return Numbering(abstract_nums=abstract_nums, nums=nums, styles=styles)


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)
    num_style_link = element.find_child_or_null("w:numStyleLink").attributes.get("w:val")
    return abstract_num_id, _AbstractNum(levels=levels, num_style_link=num_style_link)


@cobble.data
class _AbstractNum(object):
    levels = cobble.field()
    num_style_link = cobble.field()


@cobble.data
class _AbstractNumLevel(object):
    level_index = cobble.field()
    is_ordered = cobble.field()
    paragraph_style_id = cobble.field()


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"
    paragraph_style_id = element.find_child_or_null("w:pStyle").attributes.get("w:val")
    return _AbstractNumLevel(
        level_index=level_index,
        is_ordered=is_ordered,
        paragraph_style_id=paragraph_style_id,
    )


def _read_nums(element):
    num_elements = element.find_children("w:num")
    return dict(
        _read_num(num_element)
        for num_element in num_elements
    )


def _read_num(element):
    num_id = element.attributes.get("w:numId")
    abstract_num_id = element.find_child_or_null("w:abstractNumId").attributes["w:val"]
    return num_id, _Num(abstract_num_id=abstract_num_id)


@cobble.data
class _Num(object):
    abstract_num_id = cobble.field()


class Numbering(object):
    def __init__(self, abstract_nums, nums, styles):
        self._abstract_nums = abstract_nums
        self._levels_by_paragraph_style_id = dict(
            (level.paragraph_style_id, self._to_numbering_level(level))
            for abstract_num in abstract_nums.values()
            for level in abstract_num.levels.values()
            if level.paragraph_style_id is not None
        )
        self._nums = nums
        self._styles = styles

    def find_level(self, num_id, level):
        num = self._nums.get(num_id)
        if num is None:
            return None
        else:
            abstract_num = self._abstract_nums[num.abstract_num_id]
            if abstract_num.num_style_link is None:
                return self._to_numbering_level(abstract_num.levels.get(level))
            else:
                style = self._styles.find_numbering_style_by_id(abstract_num.num_style_link)
                return self.find_level(style.num_id, level)

    def find_level_by_paragraph_style_id(self, style_id):
        return self._levels_by_paragraph_style_id.get(style_id)

    def _to_numbering_level(self, abstract_num_level):
        if abstract_num_level is None:
            return None
        else:
            return numbering_level(
                level_index=abstract_num_level.level_index,
                is_ordered=abstract_num_level.is_ordered,
            )


Numbering.EMPTY = Numbering(abstract_nums={}, nums={}, styles=Styles.EMPTY)