Completed
Push — master ( ffbd85...1e6578 )
by P.R.
01:48
created

NodeStore.create_formatter()   A

Complexity

Conditions 3

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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