Completed
Pull Request — master (#40)
by Oleg
01:53
created

NodeStore.store_node()   A

Complexity

Conditions 2

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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