Passed
Branch master (0442a2)
by P.R.
02:00
created

NodeStore._adjust_hierarchy()   F

Complexity

Conditions 9

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 34
rs 3
cc 9
1
"""
2
SDoc
3
4
Copyright 2016 Set Based IT Consultancy
5
6
Licence MIT
7
"""
8
9
inline_creators = {}
0 ignored issues
show
Coding Style Naming introduced by
The name inline_creators does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
10
"""
11
Map from inline commands to node creators.
12
13
:type: dict[str,callable]
14
"""
15
16
block_creators = {}
0 ignored issues
show
Coding Style Naming introduced by
The name block_creators does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
17
"""
18
Map from block commands to object creators.
19
20
:type: dict[str,callable]
21
"""
22
23
24
class NodeStore:
25
    """
26
    Class for creating, storing, and retrieving nodes.
27
28
    @todo Make abstract and implement other document store classes.
29
    """
30
31
    # ------------------------------------------------------------------------------------------------------------------
32
    def __init__(self):
33
        """
34
        Object constructor.
35
        """
36
37
        self.format = 'html'
38
        """
39
        The output format.
40
41
        :type: str
42
        """
43
44
        self._formatters = {}
45
        """
46
        Map from format name to map from inline and block commands to format creators.
47
48
        :type: dict[str,dict[str,callable]]
49
        """
50
51
        self.nested_nodes = []
52
        """
53
        The stack of nested nodes (only filled when creating all nodes).
54
55
        :type: list[sdoc.sdoc2.node.Node.Node]
56
        """
57
58
        self.nodes = {}
59
        """
60
        The actual node store. Map from node ID to node.
61
62
        :type: dict[int,sdoc.sdoc2.node.Node.Node]
63
        """
64
65
        self._enumerable_numbers = {}
66
        """
67
        The current numbers of enumerable nodes (e.g. headings, figures).
68
69
        :type: dict[str,str]
70
        """
71
72
        self.labels = {}
73
        """
74
        The identifiers of labels which refers on each heading node.
75
76
        :type: dict[str,str]
77
        """
78
79
    # ------------------------------------------------------------------------------------------------------------------
80
    def end_block_node(self, command):
81
        """
82
        Signals the end of a block command.
83
84
        :param string command: The name of the inline command.
85
        """
86
        # Pop none block command nodes from the stack.
87
        while self.nested_nodes and not self.nested_nodes[-1].is_block_command():
88
            self.nested_nodes.pop()
89
90
        if not self.nested_nodes:
91
            # @todo position
92
            raise RuntimeError("Unexpected \\end{{{0!s}}}.".format(command))
93
94
        # Get the last node on the block stack.
95
        node = self.nested_nodes[-1]
96
97
        if node.name != command:
98
            # @todo position \end
99
            # @todo position \begin
100
            raise RuntimeError("\\begin{{{0!s}}} and \\end{{{1!s}}} do not match.".format(node.name, command))
101
102
        # Pop the last node of the block stack.
103
        self.nested_nodes.pop()
104
105
    # ------------------------------------------------------------------------------------------------------------------
106
    def in_scope(self, node_id):
107
        """
108
        Retrieves a node based on its ID.
109
110
        :param int node_id: The node ID.
111
112
        :rtype: sdoc.sdoc2.node.Node.Node
113
        """
114
        return self.nodes[node_id]
115
116
    # ------------------------------------------------------------------------------------------------------------------
117
    def out_scope(self, node):
118
        """
119
        Marks a node as not longer in scope.
120
121
        :param sdoc.sdoc2.node.Node.Node node: The node.
122
        """
123
        pass
124
125
    # ------------------------------------------------------------------------------------------------------------------
126
    @staticmethod
127
    def register_inline_command(command, constructor):
128
        """
129
        Registers a node constructor for an inline command.
130
131
        :param str command: The name of the inline command.
132
        :param callable constructor: The node constructor.
133
        """
134
        inline_creators[command] = constructor
135
136
    # ------------------------------------------------------------------------------------------------------------------
137
    def register_formatter(self, command, output_format, formatter):
138
        """
139
        Registers a output formatter constructor for a command.
140
141
        :param str command: The name of the command.
142
        :param str output_format: The output format the formatter generates.
143
        :param callable formatter: The formatter for generating the content of the node in the output format.
144
        """
145
        if output_format not in self._formatters:
146
            self._formatters[output_format] = {}
147
148
        self._formatters[output_format][command] = formatter
149
150
    # ------------------------------------------------------------------------------------------------------------------
151
    @staticmethod
152
    def register_block_command(command, constructor):
153
        """
154
        Registers a node constructor for a block command.
155
156
        :param string command: The name of the inline command.
157
        :param callable constructor: The node constructor.
158
        """
159
        block_creators[command] = constructor
160
161
    # ------------------------------------------------------------------------------------------------------------------
162
    def create_inline_node(self, command, options=None, argument='', position=None):
163
        """
164
        Creates a node based an inline command.
165
166
        Note: The node is not stored nor appended to the content tree.
167
168
        :param str command: The inline command.
169
        :param dict options: The options.
170
        :param str argument: The argument of the inline command.
171
        :param None|sdoc.sdoc2.Position.Position position: The position of the node definition.
172
173
        :rtype: sdoc.sdoc2.node.Node.Node
174
        """
175
        if command not in inline_creators:
176
            # @todo set error status
177
            constructor = inline_creators['unknown']
178
            node = constructor(options, argument)
179
            node.name = command
180
181
        else:
182
            # Create the new node.
183
            constructor = inline_creators[command]
184
            node = constructor(options, argument)
185
186
        node.position = position
187
188
        # Store the node and assign ID.
189
        self.store_node(node)
190
191
        return node
192
193
    # ------------------------------------------------------------------------------------------------------------------
194
    def create_block_node(self, command, options, position=None):
195
        """
196
        Creates a node based on a block command.
197
198
        Note: The node is not appended to the content tree.
199
200
        :param str command: The inline command.
201
        :param dict[str,str] options: The options.
202
        :param None|sdoc.sdoc2.Position.Position position: The position of the node definition.
203
204
        :rtype: sdoc.sdoc2.node.Node.Node
205
        """
206
        if command not in block_creators:
207
            constructor = block_creators['unknown']
208
            # @todo set error status
209
210
        else:
211
            # Create the new node.
212
            constructor = block_creators[command]
213
214
        node = constructor(options)
215
        node.position = position
216
217
        # Store the node and assign ID.
218
        self.store_node(node)
219
220
        return node
221
222
    # ------------------------------------------------------------------------------------------------------------------
223
    def append_inline_node(self, command, options, argument, position):
224
        """
225
        Creates a node based an inline command and appends it to the end of the content tree.
226
227
        :param str command: The inline command.
228
        :param dict options: The options.
229
        :param str argument: The argument of the inline command.
230
        :param sdoc.sdoc2.Position.Position position: The position of the node definition.
231
232
        :rtype: sdoc.sdoc2.node.Node.Node
233
        """
234
        # Create the inline node.
235
        node = self.create_inline_node(command, options, argument, position)
236
237
        # Add the node to the node store.
238
        self._append_to_content_tree(node)
239
240
        return node
241
242
    # ------------------------------------------------------------------------------------------------------------------
243
    def append_block_node(self, command, options, position):
244
        """
245
        Creates a node based on a block command and appends it to the end of the content tree.
246
247
        :param str command: The inline command.
248
        :param dict options: The options.
249
        :param sdoc.sdoc2.Position.Position position: The position of the node definition.
250
251
        :rtype: sdoc.sdoc2.node.Node.Node
252
        """
253
        # Create the block node.
254
        node = self.create_block_node(command, options, position)
255
256
        # Add the node to the node store.
257
        self._append_to_content_tree(node)
258
259
        return node
260
261
    # ------------------------------------------------------------------------------------------------------------------
262
    def create_formatter(self, command, parent=None):
263
        """
264
        Creates a formatter for generating the output of nodes in the requested output format.
265
266
        :param str command: The inline of block command.
267
        :param sdoc.sdoc2.formatter.Formatter.Formatter parent: The parent formatter.
268
269
        :rtype: sdoc.sdoc2.formatter.Formatter.Formatter
270
        """
271
        if self.format not in self._formatters:
272
            raise RuntimeError("Unknown output format '{0!s}'.".format(self.format))
273
274
        if command not in self._formatters[self.format]:
275
            # @todo use default none decorator with warning
276
            raise RuntimeError("Unknown formatter '{0!s}' for format '{1!s}'.".format(command, self.format))
277
278
        constructor = self._formatters[self.format][command]
279
        formatter = constructor(parent)
280
281
        return formatter
282
283
    # ------------------------------------------------------------------------------------------------------------------
284
    def _adjust_hierarchy(self, node):
285
        """
286
        Adjust the hierarchy based on the hierarchy of a new node.
287
288
        :param sdoc.sdoc2.node.Node.Node node: The new node.
289
        """
290
        node_hierarchy_name = node.get_hierarchy_name()
291
        parent_found = False
292
        while self.nested_nodes and not parent_found:
293
            parent_node = self.nested_nodes[-1]
294
            parent_hierarchy_name = parent_node.get_hierarchy_name()
295
            if parent_hierarchy_name != node_hierarchy_name:
296
                if node.is_hierarchy_root():
297
                    parent_found = True
298
                else:
299
                    raise RuntimeError("Improper nesting of node '{0!s}' at {1!s} and node '{2!s}' at {3!s}."
300
                                       .format(parent_node.name, parent_node.position, node.name, node.position))
301
302
            if not parent_found:
303
                parent_hierarchy_level = parent_node.get_hierarchy_level()
304
                node_hierarchy_level = node.get_hierarchy_level(parent_hierarchy_level)
305
                if parent_hierarchy_level >= node_hierarchy_level and len(self.nested_nodes) > 1:
306
                    self.nested_nodes.pop()
307
                else:
308
                    parent_found = True
309
310
        parent_node = self.nested_nodes[-1]
311
        parent_hierarchy_level = parent_node.get_hierarchy_level()
312
        node_hierarchy_level = node.get_hierarchy_level(parent_hierarchy_level)
313
314
        if node_hierarchy_level - parent_hierarchy_level > 1:
315
            # @todo position
316
            print("Warning improper nesting of levels: {0:d} at {1!s} and {2:d} at {3!s}."
317
                  .format(parent_hierarchy_level, parent_node.position, node_hierarchy_level, node.position))
318
319
    # ------------------------------------------------------------------------------------------------------------------
320
    def store_node(self, node):
321
        """
322
        Stores a node. If the node was not stored before assigns an ID to this node, otherwise the node replaces the
323
        node stored under the same ID. Returns the ID if the node.
324
325
        :param sdoc.sdoc2.node.Node.Node node: The node.
326
327
        :rtype: int
328
        """
329
        if not node.id:
330
            # Add the node to the node store.
331
            node_id = len(self.nodes) + 1
332
            node.id = node_id
333
334
        self.nodes[node.id] = node
335
336
        return node.id
337
338
    # ------------------------------------------------------------------------------------------------------------------
339
    def _append_to_content_tree(self, node):
340
        """
341
        Appends the node at the proper nesting level at the end of the content tree.
342
343
        :param sdoc.sdoc2.node.Node.Node node: The node.
344
        """
345
        if node.id == 1:
346
            # The first node must be a document root.
347
            if not node.is_document_root():
348
                # @todo position of block node.
349
                raise RuntimeError("Node {0!s} is not a document root".format(node.name))
350
351
            self.nested_nodes.append(node)
352
353
        else:
354
            # All other nodes must not be a document root.
355
            if node.is_document_root():
356
                # @todo position of block node.
357
                raise RuntimeError("Unexpected {0!s}. Node is document root".format(node.name))
358
359
            # If the node is a part of a hierarchy adjust the nested nodes stack.
360
            if node.get_hierarchy_name():
361
                self._adjust_hierarchy(node)
362
363
            # Add the node to the list of child nodes of its parent node.
364
            if self.nested_nodes:
365
                parent_node = self.nested_nodes[-1]
366
367
                # Pop from stack if we have two list element nodes (e.g. item nodes) in a row.
368
                if node.is_list_element() and type(parent_node) == type(node):
369
                    self.nested_nodes.pop()
370
                    parent_node = self.nested_nodes[-1]
371
372
                parent_node.child_nodes.append(node.id)
373
374
            # Block commands and hierarchical nodes must be appended to the nested nodes.
375
            if node.is_block_command() or node.get_hierarchy_name():
376
                self.nested_nodes.append(node)
377
378
    # ------------------------------------------------------------------------------------------------------------------
379
    def prepare_content_tree(self):
380
        """
381
        Prepares after parsing at SDoc2 level the content tree for further processing.
382
        """
383
        # Currently, node with ID 1 is the document node. @todo Improve getting the document node.
384
        self.nodes[1].prepare_content_tree()
385
386
    # ------------------------------------------------------------------------------------------------------------------
387
    def number_numerable(self):
388
        """
389
        Numbers all numerable nodes such as chapters, sections, figures, and, items.
390
        """
391
        self.nodes[1].number(self._enumerable_numbers)
392
393
    # ------------------------------------------------------------------------------------------------------------------
394
    @staticmethod
395
    def generate(formatter):
396
        """
397
        Generates the document.
398
399
        :param sdoc.format.Format.Format formatter: The format which will generate file.
400
        """
401
        # Start generating file using specific formatter.
402
        formatter.generate()
403
404
    # ------------------------------------------------------------------------------------------------------------------
405
    def get_enumerated_items(self):
406
        """
407
        Returns a list with tuples with command and number of enumerated nodes.
408
409
        This method is intended for unit test only.
410
411
        :rtype: list[(str,str)]
412
        """
413
        return self.nodes[1].get_enumerated_items()
414
415
    # ------------------------------------------------------------------------------------------------------------------
416
    def parse_labels(self):
417
        """
418
        Method for parsing labels, setting additional arguments to nodes, and removing label nodes from tree.
419
        """
420
        self.nodes[1].parse_labels()
421
        self.nodes[1].change_ref_argument()
422
423
# ----------------------------------------------------------------------------------------------------------------------
424