1
2 r"""
3 :Copyright:
4
5 Copyright 2006 - 2015
6 Andr\xe9 Malo or his licensors, as applicable
7
8 :License:
9
10 Licensed under the Apache License, Version 2.0 (the "License");
11 you may not use this file except in compliance with the License.
12 You may obtain a copy of the License at
13
14 http://www.apache.org/licenses/LICENSE-2.0
15
16 Unless required by applicable law or agreed to in writing, software
17 distributed under the License is distributed on an "AS IS" BASIS,
18 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 See the License for the specific language governing permissions and
20 limitations under the License.
21
22 ======================
23 Node Tree Structures
24 ======================
25
26 This module provides node tree management.
27 """
28 if __doc__:
29
30 __doc__ = __doc__.encode('ascii').decode('unicode_escape')
31 __author__ = r"Andr\xe9 Malo".encode('ascii').decode('unicode_escape')
32 __docformat__ = "restructuredtext en"
33
34 from ._exceptions import NodeNotFoundError, NodeTreeError
35 from . import _finalize
36 from . import _nodetree
37 from . import _util
41 """
42 Lightweight node for raw content and attribute assignment
43
44 :IVariables:
45 `_udict` : ``dict``
46 The dict containing node information
47 """
48 __slots__ = ['content', 'encoder', 'decoder', '_udict']
49
50
51
53 """
54 Initialization
55
56 :Parameters:
57 `node` : `Node`
58 The original node
59 """
60 self._udict = node._udict
61
62 @_util.Property
64 """
65 Raw content
66
67 :Type: ``str``
68 """
69
70
71
72 unicode_, str_, isinstance_ = unicode, str, isinstance
73
74 def fset(self, content):
75 udict = self._udict
76 if isinstance_(content, unicode_):
77 cont = udict['encoder'].encode(content)
78 else:
79 cont = str_(content)
80 udict['content'] = (cont, cont)
81 udict['namedict'] = {}
82
83 def fget(self):
84 return self._udict['content'][0]
85 return locals()
86
87 @_util.Property
89 """
90 Output encoder
91
92 :Type: `EncoderInterface`
93 """
94
95
96
97 def fget(self):
98 return self._udict['encoder']
99 return locals()
100
101 @_util.Property
103 """
104 Input decoder
105
106 :Type: `DecoderInterface`
107 """
108
109
110
111 def fget(self):
112 return self._udict['decoder']
113 return locals()
114
116 """
117 Set the attribute `name` to `value`
118
119 The value is *not* encoded according to the model.
120 The original case of `name` is preserved. If the attribute does not
121 occur in the original template, the case of the passed `name` is
122 taken over. Non-string values (including unicode, but not ``None``)
123 are converted to string using ``str()``.
124
125 :Parameters:
126 `name` : ``str``
127 The attribute name (case insensitive)
128
129 `value` : ``str``
130 The attribute value (may be ``None`` for short
131 attributes). Objects that are not ``None`` and and not
132 ``unicode`` are stored as their string representation.
133 """
134 udict = self._udict
135 if value is not None:
136 if isinstance(value, unicode):
137 value = udict['encoder'].encode(value)
138 else:
139 value = str(value)
140 attr = udict['attr']
141 name = udict['encoder'].name(name)
142 normname = udict['decoder'].normalize(name)
143 realname = attr.get(normname, (name,))[0]
144 attr[normname] = (realname, value)
145
147 """
148 Determine the value of attribute `name`
149
150 :Parameters:
151 `name` : ``str``
152 The attribute name
153
154 :Return: The attribute (``None`` for shorttags)
155 :Rtype: ``str``
156
157 :Exceptions:
158 - `KeyError` : The attribute does not exist
159 """
160 udict = self._udict
161 return udict['attr'][
162 udict['decoder'].normalize(udict['encoder'].name(name))
163 ][1]
164
166 """
167 Delete attribute `name`
168
169 If the attribute does not exist, no exception is raised.
170
171 :Parameters:
172 `name` : ``str``
173 The name of the attribute to delete (case insensitive)
174 """
175 udict = self._udict
176 try:
177 del udict['attr'][
178 udict['decoder'].normalize(udict['encoder'].name(name))
179 ]
180 except KeyError:
181
182 pass
183
184
185 -class Node(object):
186 """
187 User visible node object
188
189 :IVariables:
190 `ctx` : ``tuple``
191 The node context (``None`` if there isn't one). Node contexts
192 are created on repetitions for all (direct and no-direct) subnodes of
193 the repeated node. The context is a ``tuple``, which contains for
194 repeated nodes the position within the loop (starting with ``0``), the
195 actual item and a tuple of the fixed parameters. The last two are also
196 passed to the repeat callback function directly. For separator
197 nodes, ``ctx[1]`` is a tuple containing the items before the separator
198 and after it. Separator indices are starting with ``0``, too.
199
200 `_model` : `ModelAdapterInterface`
201 The template model object
202
203 `_udict` : ``dict``
204 The dict containing node information
205 """
206 _usernode = True
207 __slots__ = ['content', 'raw', 'ctx', '_model', '_udict']
208
209
210
211 @_util.Property
213 """
214 Node content
215
216 The property can be set to a unicode or str value, which will be
217 escaped and encoded (in case of unicode). It replaces the content or
218 child nodes of the node completely.
219
220 The property can be read and will either return the *raw* content of
221 the node (it may even contain markup) - or ``None`` if the node has
222 subnodes.
223
224 :Type: ``basestring`` or ``None``
225 """
226
227
228
229 basestring_, isinstance_, str_ = basestring, isinstance, str
230
231 def fset(self, content):
232 if not isinstance_(content, basestring_):
233 content = str_(content)
234 udict = self._udict
235 udict['content'] = (udict['encoder'].content(content), None)
236 udict['namedict'] = {}
237
238 def fget(self):
239 return self._udict['content'][0]
240 return locals()
241
242 @_util.Property
244 """
245 Hidden node markup?
246
247 :Type: ``bool``
248 """
249
250
251
252 def fset(self, value):
253 self._udict['noelement'] = value and True or False
254
255 def fget(self):
256 return self._udict['noelement']
257 return locals()
258
259 @_util.Property
261 """
262 Self-closed element? (read-only)
263
264 :Type: ``bool``
265 """
266
267
268
269 def fget(self):
270 return self._udict['closed']
271 return locals()
272
273 @_util.Property
275 """
276 Raw node
277
278 :Type: `RawNode`
279 """
280
281
282
283 def fget(self):
284 return RawNode(self)
285 return locals()
286
287 - def __new__(cls, node, model, ctx=None, light=False):
288 """
289 Construction
290
291 :Parameters:
292 `node` : `Node` or `TemplateNode`
293 The node to clone
294
295 `model` : `ModelAdapterInterface`
296 The template model instance
297
298 `ctx` : ``tuple``
299 The node context
300
301 `light` : ``bool``
302 Do a light copy? (In this case just the node context is
303 updated and the *original* node is returned). Do this only if
304 `node` is already a `Node` instance and you do not need another
305 copy!
306
307 :Return: The node instance
308 :Rtype: `Node`
309 """
310 if light:
311 if not node._udict.get('callback'):
312 node.ctx = ctx
313 return node
314
315 self = object.__new__(cls)
316 udict = node._udict.copy()
317 udict['attr'] = udict['attr'].copy()
318 udict['nodes'] = udict['nodes'][:]
319 self._udict = udict
320 self._model = model
321 if udict.get('callback'):
322 self.ctx = node.ctx
323 else:
324 self.ctx = ctx
325
326 return self
327
329 """
330 Determine direct subnodes by name
331
332 In contrast to `__getattr__` this works for all names. Also the
333 exception in case of a failed lookup is different.
334
335 :Parameters:
336 `name` : ``str``
337 The name looked for
338
339 :Return: The found node
340 :Rtype: `Node`
341
342 :Exceptions:
343 - `NodeNotFoundError` : The subnode was not found
344 """
345 udict = self._udict
346 try:
347 name = str(name)
348 idx = udict['namedict'][name]
349 except (UnicodeError, KeyError):
350 raise NodeNotFoundError(name)
351
352 while idx < 0:
353 kind, result = udict['nodes'][-1 - idx]
354 if not result._usernode:
355 result = Node(result, self._model, self.ctx)
356 udict['nodes'][-1 - idx] = (kind, result)
357 udict = result._udict
358 idx = udict['namedict'][name]
359
360 kind, result = udict['nodes'][idx]
361 if not result._usernode:
362 result = Node(result, self._model, self.ctx)
363 udict['nodes'][idx] = (kind, result)
364 else:
365 result.ctx = self.ctx
366
367 return result
368
370 """
371 Determine direct subnodes by name
372
373 :Parameters:
374 `name` : ``str``
375 The name looked for
376
377 :Return: The found subnode
378 :Rtype: `Node`
379
380 :Exceptions:
381 - `AttributeError` : The subnode was not found
382 """
383 try:
384 return self(name)
385 except NodeNotFoundError:
386 raise AttributeError("Attribute %s.%s not found" % (
387 self.__class__.__name__, name
388 ))
389
391 """
392 Set the attribute `name` to `value`
393
394 The value is encoded according to the model and the original case
395 of `name` is preserved. If the attribute does not occur in the
396 original template, the case of the passed `name` is taken over.
397 Non-string values are converted to string using ``str()``. Unicode
398 values are passed as-is to the model encoder.
399
400 :Parameters:
401 `name` : ``str``
402 The attribute name (case insensitive)
403
404 `value` : any
405 The attribute value (may be ``None`` for short
406 attributes). Objects that are not ``None`` and and not
407 ``unicode`` are stored as their string representation.
408 """
409 udict = self._udict
410 if value is not None:
411 if not isinstance(value, basestring):
412 value = str(value)
413 value = udict['encoder'].attribute(value)
414
415 attr = udict['attr']
416 name = udict['encoder'].name(name)
417 normname = udict['decoder'].normalize(name)
418 realname = attr.get(normname, [name])[0]
419 attr[normname] = (realname, value)
420
422 """
423 Determine the value of attribute `name`
424
425 :Parameters:
426 `name` : ``str``
427 The attribute name
428
429 :Return: The attribute (``None`` for shorttags)
430 :Rtype: ``str``
431
432 :Exceptions:
433 - `KeyError` : The attribute does not exist
434 """
435 udict = self._udict
436 value = udict['attr'][
437 udict['decoder'].normalize(udict['encoder'].name(name))
438 ][1]
439 if value and (value.startswith('"') or value.startswith("'")):
440 value = value[1:-1]
441
442 return value
443
445 """
446 Delete attribute `name`
447
448 If the attribute does not exist, no exception is raised.
449
450 :Parameters:
451 `name` : ``str``
452 The name of the attribute to delete (case insensitive)
453 """
454 udict = self._udict
455 try:
456 del udict['attr'][
457 udict['decoder'].normalize(udict['encoder'].name(name))
458 ]
459 except KeyError:
460
461 pass
462
463 - def repeat(self, callback, itemlist, *fixed, **kwargs):
464 """
465 Repeat the snippet ``len(list(itemlist))`` times
466
467 The actually supported signature is::
468
469 repeat(self, callback, itemlist, *fixed, separate=None)
470
471 Examples::
472
473 def render_foo(self, node):
474 def callback(node, item):
475 ...
476 node.repeat(callback, [1, 2, 3, 4])
477
478 def render_foo(self, node):
479 def callback(node, item):
480 ...
481 def sep(node):
482 ...
483 node.repeat(callback, [1, 2, 3, 4], separate=sep)
484
485 def render_foo(self, node):
486 def callback(node, item, foo, bar):
487 ...
488 node.repeat(callback, [1, 2, 3, 4], "foo", "bar")
489
490 def render_foo(self, node):
491 def callback(node, item, foo, bar):
492 ...
493 def sep(node):
494 ...
495 node.repeat(callback, [1, 2, 3, 4], "foo", "bar",
496 separate=sep)
497
498 :Parameters:
499 `callback` : ``callable``
500 The callback function
501
502 `itemlist` : iterable
503 The items to iterate over
504
505 `fixed` : ``tuple``
506 Fixed parameters to be passed to the repeat methods
507
508 :Keywords:
509 `separate` : ``callable``
510 Alternative callback function for separator nodes. If omitted or
511 ``None``, ``self.separate_name`` is looked up and called if it
512 exists.
513 """
514 if 'separate' in kwargs:
515 if len(kwargs) > 1:
516 raise TypeError("Unrecognized keyword parameters")
517 separate = kwargs['separate']
518 elif kwargs:
519 raise TypeError("Unrecognized keyword parameters")
520 else:
521 separate = None
522 self._udict['repeated'] = (callback, iter(itemlist), fixed, separate)
523
525 """
526 Remove the node from the tree
527
528 Tells the system, that the node (and all of its subnodes) should
529 not be rendered.
530 """
531 self._udict['removed'] = True
532 self._udict['namedict'] = {}
533
534 - def iterate(self, itemlist, separate=None):
535 """
536 Iterate over repeated nodes
537
538 Iteration works by repeating the original node
539 ``len(list(iteritems))`` times, turning the original node into a
540 container node and appending the generated nodeset to that container.
541 That way, the iterated nodes are virtually indented by one level, but
542 the container node is completely hidden, so it won't be visible.
543
544 All repeated nodes are marked as ``DONE``, so they (and their
545 subnodes) are not processed any further (except explicit callbacks).
546 If there is a separator node assigned, it's put between the
547 repetitions and *not* marked as ``DONE``. The callbacks to them
548 (if any) are executed when the template system gets back to control.
549
550 :Parameters:
551 `itemlist` : iterable
552 The items to iterate over
553
554 `separate` : ``callable``
555 Alternative callback function for separator nodes. If omitted or
556 ``None``, ``self.separate_name`` is looked up and called if it
557 exists.
558
559 :Return: The repeated nodes and items (``[(node, item), ...]``)
560 :Rtype: iterable
561 """
562 itemlist = iter(itemlist)
563 node, nodelist = self.copy(), []
564
565
566
567
568 self._udict['content'] = (None, None)
569 self._udict['nodes'] = nodelist
570 self._udict['namedict'] = {}
571 self._udict['masked'] = True
572
573 return _nodetree.iterate(
574 node, nodelist, itemlist, separate, Node
575 )
576
577 - def replace(self, callback, other, *fixed):
578 """
579 Replace the node (and all subnodes) with the copy of another one
580
581 The replacement node is deep-copied, so use it with care
582 (performance-wise).
583
584 :Parameters:
585 `callback` : ``callable``
586 callback function
587
588 `other` : `Node`
589 The replacement node
590
591 `fixed` : ``tuple``
592 Fixed parameters for the callback
593
594 :Return: The replaced node (actually the node itself, but with
595 updated parameters)
596 :Rtype: `Node`
597 """
598 udict = other._udict.copy()
599 udict['attr'] = udict['attr'].copy()
600 ctx, deep, TEXT = self.ctx, _nodetree.copydeep, _nodetree.TEXT_NODE
601 model = self._model
602
603 udict['nodes'] = [(
604 kind,
605 (kind != TEXT and node._usernode)
606 and deep(node, model, ctx, Node) or node
607 ) for kind, node in udict['nodes']]
608
609 udict['name'] = self._udict['name']
610 udict['callback'] = callback
611 udict['complete'] = fixed
612
613 self._udict = udict
614 return self
615
617 """
618 Deep copy this node
619
620 :Return: The node copy
621 :Rtype: `Node`
622 """
623 return _nodetree.copydeep(self, self._model, self.ctx, Node)
624
625 - def render(self, *callback, **kwargs):
626 """
627 render(self, callback, params, **kwargs)
628
629 Render this node only and return the result as string
630
631 Note that callback and params are optional positional parameters::
632
633 render(self, decode=True, decode_errors='strict')
634 # or
635 render(self, callback, decode=True, decode_errors='strict')
636 # or
637 render(self, callback, param1, paramx, ... decode=True, ...)
638
639 is also possible.
640
641 :Parameters:
642 `callback` : callable or ``None``
643 Optional callback function and additional parameters
644
645 `params` : ``tuple``
646 Optional extra parameters for `callback`
647
648 :Keywords:
649 `decode` : ``bool``
650 Decode the result back to unicode? This uses the encoding of the
651 template.
652
653 `decode_errors` : ``str``
654 Error handler if decode errors happen.
655
656 `model` : any
657 New render model, if omitted or ``None``, the current model is
658 applied.
659
660 `adapter` : ``callable``
661 Model adapter factory, takes the model and returns a
662 `ModelAdapterInterface`. If omitted or ``None``, the current
663 adapter is used. This parameter is ignored, if no ``model``
664 parameter is passed.
665
666 :Return: The rendered node, type depends on ``decode`` keyword
667 :Rtype: ``basestring``
668 """
669 decode = kwargs.pop('decode', True)
670 decode_errors = kwargs.pop('decode_errors', 'strict')
671 model = kwargs.pop('model', None)
672 adapter = kwargs.pop('adapter', None)
673 if kwargs:
674 raise TypeError("Unrecognized keyword parameters")
675
676 if model is None:
677 model = self._model
678 elif adapter is None:
679 model = self._model.new(model)
680 else:
681 model = adapter(model)
682
683 node = _nodetree.copydeep(self, model, self.ctx, Node)
684 if callback and callback[0] is not None:
685 node.replace(callback[0], node, *callback[1:])
686 else:
687 node.replace(None, node)
688 res = ''.join(_nodetree.render(node, model, Node))
689 if not decode:
690 return res
691 return res.decode(self._udict['decoder'].encoding, decode_errors)
692
695 """
696 Template node
697
698 This is kind of a proto node. During rendering each template node is
699 turned into a user visible `Node` object, which implements the user
700 interface. `TemplateNode` objects provide a tree building interface
701 instead.
702
703 :IVariables:
704 `_udict` : ``dict``
705 The dict containing node information
706
707 `_finalized` : ``bool``
708 Was the tree finalized?
709 """
710 ctx = None
711 _usernode = False
712
713
714
715 @_util.Property
717 """
718 End tag of the node
719
720 :Type: ``str``
721 """
722
723
724
725 def fset(self, data):
726 if self._finalized:
727 raise NodeTreeError("Tree was already finalized")
728 if self._udict['closed']:
729 raise NodeTreeError(
730 "Self-closing elements cannot have an endtag"
731 )
732 if not isinstance(data, str):
733 raise NodeTreeError("Endtag data must be a string")
734 self._udict['endtag'] = data
735
736 def fget(self):
737 return self._udict.get('endtag')
738 return locals()
739
740 - def __init__(self, tagname, attr, special, closed):
741 """
742 Initialization
743
744 :Parameters:
745 `tagname` : ``str``
746 The name of the accompanying tag
747
748 `attr` : iterable
749 The attribute list (``((name, value), ...)``)
750
751 `special` : ``dict``
752 Special node information
753 """
754 scope = special.get('scope')
755 overlay = special.get('overlay')
756 tdi = special.get('attribute')
757 if tdi is None:
758 flags, name = '', None
759 else:
760 flags, name = tdi
761
762 if overlay is None:
763 overlay = False, False, False, None
764 else:
765 overlay = (
766 '-' in overlay[0],
767 '>' in overlay[0],
768 '<' in overlay[0],
769 overlay[1],
770 )
771
772 if scope is None:
773 scope = False, False, None
774 else:
775 scope = (
776 ('-' in scope[0]),
777 ('=' in scope[0]),
778 scope[1],
779 )
780 if not scope[0] and not scope[1] and not scope[2]:
781 scope = False, False, None
782
783 self._udict = {
784 'sep': None,
785 'nodes': [],
786 'content': (None, None),
787 'attr_': tuple(attr),
788 'removed': False,
789 'repeated': None,
790 'name': name or None,
791 'closed': closed,
792 'tagname': tagname,
793 'noelement': '-' in flags or overlay[0] or scope[0],
794 'noauto': '*' in flags,
795 'masked': False,
796 'overlay': overlay,
797 'scope': scope,
798 }
799 self._finalized = False
800
801 - def append_text(self, content):
802 """
803 Append a text node
804
805 :Parameters:
806 `content` : ``str``
807 The text node content
808
809 :Exceptions:
810 - `NodeTreeError` : The tree was already finalized
811 """
812 if self._finalized:
813 raise NodeTreeError("Tree was already finalized")
814
815 self._udict['nodes'].append((_nodetree.TEXT_NODE, (content, content)))
816
818 """
819 Append an escaped node
820
821 :Parameters:
822 `escaped` : ``str``
823 The escaped string (in unescaped form, i.e. the final result)
824
825 `content` : ``str``
826 The escape string (the whole sequence)
827
828 :Exceptions:
829 - `NodeTreeError` : The tree was already finalized
830 """
831 if self._finalized:
832 raise NodeTreeError("Tree was already finalized")
833
834 self._udict['nodes'].append((_nodetree.TEXT_NODE, (escaped, content)))
835
836 - def append_node(self, tagname, attr, special, closed):
837 """
838 Append processable node
839
840 :Parameters:
841 `tagname` : ``str``
842 The name of the accompanying tag
843
844 `attr` : iterable
845 The attribute list (``((name, value), ...)``)
846
847 `special` : ``dict``
848 Special attributes. If it's empty, something's wrong.
849
850 `closed` : ``bool``
851 Closed tag?
852
853 :Return: new `TemplateNode` instance
854 :Rtype: `TemplateNode`
855
856 :Exceptions:
857 - `NodeTreeError` : The tree was already finalized
858 - `AssertionError` : nothing special
859 """
860 if self._finalized:
861 raise NodeTreeError("Tree was already finalized")
862
863 assert len(special), "Nothing special about this node."
864
865 node = TemplateNode(tagname, attr, special, bool(closed))
866 tdi = special.get('attribute')
867 if tdi is not None and ':' in tdi[0]:
868 kind = _nodetree.SEP_NODE
869 else:
870 kind = _nodetree.PROC_NODE
871 self._udict['nodes'].append((kind, node))
872
873 return node
874
875
876 -class Root(TemplateNode):
877 """
878 Root Node class
879
880 This class has to be used as the initial root of the tree.
881 """
882 _sources, _targets = None, None
883
884
885
886 @_util.Property
888 """
889 Output encoder
890
891 :Type: `EncoderInterface`
892 """
893
894
895
896 def fget(self):
897 return self._udict['encoder']
898 return locals()
899
900 @_util.Property
902 """
903 Input decoder
904
905 :Type: `DecoderInterface`
906 """
907
908
909
910 def fget(self):
911 return self._udict['decoder']
912 return locals()
913
914 @_util.Property
916 """
917 Source overlay names
918
919 :Type: iterable
920 """
921
922
923
924 def fget(self):
925 if self._sources is None:
926 return ()
927 return self._sources.iterkeys()
928 return locals()
929
930 @_util.Property
932 """
933 Target overlay names
934
935 :Type: iterable
936 """
937
938
939
940 def fget(self):
941 if self._targets is None:
942 return ()
943 return self._targets.iterkeys()
944 return locals()
945
947 """ Initialization """
948 super(Root, self).__init__('', (), {}, False)
949 self.endtag = ''
950 self._udict['is_root'] = True
951
953 """ String representation of the tree """
954 return self.to_string(verbose=True)
955
957 """
958 String representation of the tree
959
960 :Parameters:
961 `verbose` : ``bool``
962 Show (shortened) text node content and separator nodes?
963
964 :Return: The string representation
965 :Rtype: ``str``
966 """
967 if not self._finalized:
968 raise NodeTreeError("Tree was not finalized yet")
969 return '\n'.join(list(
970 _nodetree.represent(self._udict, bool(verbose))
971 )) + '\n'
972
974 """
975 Finalize the tree
976
977 This method assigns separator nodes to their accompanying content
978 nodes, concatenates adjacent text nodes and tries to optimize
979 the tree a bit.
980
981 :Parameters:
982 `encoder` : `EncoderInterface`
983 Encoder instance
984
985 :Exceptions:
986 - `NodeTreeError` : The tree was already finalized or endtag was not
987 set
988 """
989 if self._finalized:
990 raise NodeTreeError("Tree was already finalized")
991 self._sources, self._targets = \
992 _finalize.finalize(self._udict, encoder, decoder)
993 self._finalized = True
994
996 """
997 Overlay this tree with another one
998
999 :Parameters:
1000 `other` : `Root`
1001 The tree to lay over
1002
1003 :Exceptions:
1004 - `NodeTreeError` : Finalization error
1005 """
1006 if not self._finalized:
1007 raise NodeTreeError("Tree was not finalized yet.")
1008 if not other._finalized:
1009 raise NodeTreeError("Overlay tree was not finalized yet.")
1010 return _nodetree.overlay(
1011 self._udict, other._sources, TemplateNode, Root
1012 )
1013
1014 - def render(self, model, startnode=None):
1015 """
1016 Render the tree into chunks, calling `model` for input
1017
1018 :Parameters:
1019 `model` : `ModelAdapterInterface`
1020 The model object
1021
1022 `startnode` : ``str``
1023 Only render this node (and all its children). The node
1024 is addressed via a dotted string notation, like ``a.b.c`` (this
1025 would render the ``c`` node.) The notation does not describe a
1026 strict node chain, though. Between to parts of a node chain may
1027 be gaps in the tree. The algorithm looks out for the first
1028 matching node. It does no backtracking and so does not cover all
1029 branches (yet?), but that works fine for realistic cases :). A
1030 non-working example would be (searching for a.b.c)::
1031
1032 *
1033 +- a
1034 | `- b - d
1035 `- a
1036 `- b - c
1037
1038 :Return: Rendered chunks
1039 :Rtype: iterable
1040 """
1041 return _nodetree.render(
1042 _nodetree.findnode(self, startnode), model, Node
1043 )
1044
1045
1046 from . import c
1047 c = c.load('impl')
1048 if c is not None:
1049 Root, Node, RawNode, TemplateNode = (
1050 c.Root, c.Node, c.RawNode, c.TemplateNode
1051 )
1052 del c
1053