Completed
Pull Request — master (#32)
by Oleg
01:51
created

HeadingNode.set_part_numeration()   A

Complexity

Conditions 2

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 18
rs 9.4285
cc 2
1
"""
2
SDoc
3
4
Copyright 2016 Set Based IT Consultancy
5
6
Licence MIT
7
"""
8
# ----------------------------------------------------------------------------------------------------------------------
9
import sdoc
10
from sdoc.sdoc2 import in_scope, out_scope
11
from sdoc.sdoc2.node.Node import Node
12
from sdoc.sdoc2.node.TextNode import TextNode
13
from sdoc.sdoc2.node.EndParagraphNode import EndParagraphNode
14
15
16
class HeadingNode(Node):
0 ignored issues
show
Bug introduced by
The method get_command which was declared abstract in the super-class Node
was not overridden.

Methods which raise NotImplementedError should be overridden in concrete child classes.

Loading history...
17
    """
18
    Abstract class for heading nodes.
19
    """
20
    # ------------------------------------------------------------------------------------------------------------------
21
    def __init__(self, name, options, argument):
22
        """
23
        Object constructor.
24
25
        :param str name: The (command) name of this heading.
26
        :param dict[str,str] options: The options of this heading.
27
        :param str argument: The title of this heading.
28
        """
29
        super().__init__(name, options, argument)
30
31
    # ------------------------------------------------------------------------------------------------------------------
32
    def get_hierarchy_name(self):
33
        """
34
        Returns 'sectioning'.
35
36
        :rtype: str
37
        """
38
        return 'sectioning'
39
40
    # ------------------------------------------------------------------------------------------------------------------
41
    def is_block_command(self):
42
        """
43
        Returns False.
44
45
        :rtype: bool
46
        """
47
        return False
48
49
    # ------------------------------------------------------------------------------------------------------------------
50
    def is_inline_command(self):
51
        """
52
        Returns True.
53
54
        :rtype: bool
55
        """
56
        return True
57
58
    # ------------------------------------------------------------------------------------------------------------------
59
    @staticmethod
60
    def _trim_levels(string_of_numbers):
61
        """
62
        Strips all starting '0's and add one starting '0' symbol if number starts from '0'.
63
64
        :param str string_of_numbers: String with header node number.
65
66
        :rtype: str
67
        """
68
        if string_of_numbers.startswith('0'):
69
            string_of_numbers = string_of_numbers.lstrip('0.')
70
            string_of_numbers = '0.{0!s}'.format(string_of_numbers)
71
72
        return string_of_numbers
73
74
    # ------------------------------------------------------------------------------------------------------------------
75
    @staticmethod
76
    def get_numeration(enumerable_numbers, level):
77
        """
78
        Returns the current enumeration of headings at a heading level.
79
80
        :param dict[str,str] enumerable_numbers: The current numbers of enumerable nodes.
81
        :param int level: The level of nested heading.
82
83
        :rtype: str
84
        """
85
        if 'heading' not in enumerable_numbers:
86
            heading_numbers = []
87
88
            for _ in range(level):
89
                heading_numbers.append('0')
90
        else:
91
            heading_numbers = enumerable_numbers['heading'].split('.')
92
93
            if level > len(heading_numbers):
94
                for _ in range(level-len(heading_numbers)):
95
                    heading_numbers.append('0')
96
97
        return '.'.join(heading_numbers[:level])
98
99
    # ------------------------------------------------------------------------------------------------------------------
100
    @staticmethod
101
    def set_part_numeration(enumerable_numbers):
102
        """
103
        Sets and returns the part number. If we already haven't got a part, we set it to 1 (i.e. first part).
104
        If we already have part, we increment part number, and reset heading nodes numbering.
105
106
        This method changes original list with values!
107
108
        :param dict[str,str] enumerable_numbers: The current numbers of enumerable nodes.
109
110
        :rtype: str
111
        """
112
        if 'part' not in enumerable_numbers:
113
            enumerable_numbers['part'] = '1'
114
115
        else:
116
            del enumerable_numbers['heading']
117
            enumerable_numbers['part'] = str(int(enumerable_numbers['part']) + 1)
118
119
    # ------------------------------------------------------------------------------------------------------------------
120
    @staticmethod
121
    def _increment_last_level(enumerable_numbers):
122
        """
123
        Increments the last level in number of a heading number.
124
125
        :param dict[str,str] enumerable_numbers: The current numbers of enumerable nodes.
126
127
        :rtype: str
128
        """
129
        heading_numbers = enumerable_numbers['heading'].split('.')
130
        heading_numbers[-1] = str(int(heading_numbers[-1]) + 1)
131
132
        return '.'.join(heading_numbers)
133
134
    # ------------------------------------------------------------------------------------------------------------------
135
    def number(self, enumerable_numbers):
136
        """
137
        Sets number of heading nodes.
138
139
        :param dict[str,str] enumerable_numbers: The current numbers of enumerable nodes.
140
        """
141
        if self.get_command() == 'part':
142
            self.set_part_numeration(enumerable_numbers)
143
            self._options['number'] = self._trim_levels(enumerable_numbers['part'])
144
145
        else:
146
            enumerable_numbers['heading'] = self.get_numeration(enumerable_numbers, self.get_hierarchy_level())
147
            enumerable_numbers['heading'] = self._increment_last_level(enumerable_numbers)
148
149
            self._options['number'] = self._trim_levels(enumerable_numbers['heading'])
150
151
        super().number(enumerable_numbers)
152
153
    # ------------------------------------------------------------------------------------------------------------------
154
    def prepare_content_tree(self):
155
        """
156
        Prepares the content tree. Create paragraph nodes.
157
        """
158
159
        super().prepare_content_tree()
160
161
        # Adding the id's of splitted text in 'new_child_nodes1' list.
162
        self.split_text_nodes()
163
164
        # Creating paragraphs and add all id's in 'new_child_nodes2' list.
165
        self.create_paragraphs()
166
167
    # ------------------------------------------------------------------------------------------------------------------
168
    def split_text_nodes(self):
169
        """
170
        Replaces single text nodes that contains a paragraph separator (i.e. a double new line) with multiple text nodes
171
        without paragraph separator.
172
        """
173
        new_child_nodes = []
174
175
        for node_id in self.child_nodes:
176
            node = in_scope(node_id)
177
178
            if isinstance(node, TextNode):
179
                list_ids = node.split_by_paragraph()
180
                for ids in list_ids:
181
                    new_child_nodes.append(ids)
182
            else:
183
                new_child_nodes.append(node.id)
184
185
            out_scope(node)
186
187
        self.child_nodes = new_child_nodes
188
189
    # ------------------------------------------------------------------------------------------------------------------
190
    def create_paragraphs(self):
191
        """
192
        Create paragraph nodes.
193
194
        A paragraph consists of phrasing nodes only. Each continuous slice of phrasing child nodes is move to a
195
        paragraph node.
196
        """
197
        new_child_nodes = []
198
        paragraph_node = None
199
200
        for node_id in self.child_nodes:
201
            node = in_scope(node_id)
202
203
            if node.is_phrasing():
204
                if not paragraph_node:
205
                    paragraph_node = sdoc.sdoc2.node_store.create_inline_node('paragraph')
206
                    new_child_nodes.append(paragraph_node.id)
207
208
                paragraph_node.append_child_node(node)
209
            else:
210
                if paragraph_node:
211
                    paragraph_node.prune_whitespace()
212
                    sdoc.sdoc2.node_store.store_node(paragraph_node)
213
                    paragraph_node = None
214
215
                # End paragraph nodes are created temporary to separate paragraphs in a flat list of (text) node. There
216
                # role ae replaced by the content hierarchy now. So, we must no store end paragraph nodes.
217
                if not isinstance(node, EndParagraphNode):
218
                    new_child_nodes.append(node.id)
219
220
            out_scope(node)
221
222
        if paragraph_node:
223
            paragraph_node.prune_whitespace()
224
            sdoc.sdoc2.node_store.store_node(paragraph_node)
225
            # paragraph_node = None
226
227
        # Setting child nodes.
228
        self.child_nodes = new_child_nodes
229
230
# ----------------------------------------------------------------------------------------------------------------------
231