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