Completed
Push — master ( 4c8e7d...ffbd85 )
by P.R.
01:53
created

NodeStore.out_scope()   A

Complexity

Conditions 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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