Package tdi :: Module nodetree
[frames] | no frames]

Source Code for Module tdi.nodetree

   1  # -*- coding: ascii -*- 
   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      # pylint: disable = redefined-builtin 
  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 
38 39 40 -class RawNode(object):
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 # pylint: disable = protected-access 51
52 - def __init__(self, node):
53 """ 54 Initialization 55 56 :Parameters: 57 `node` : `Node` 58 The original node 59 """ 60 self._udict = node._udict
61 62 @_util.Property
63 - def content():
64 """ 65 Raw content 66 67 :Type: ``str`` 68 """ 69 # pylint: disable = no-method-argument, unused-variable 70 # pylint: disable = missing-docstring 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
88 - def encoder():
89 """ 90 Output encoder 91 92 :Type: `EncoderInterface` 93 """ 94 # pylint: disable = no-method-argument, unused-variable 95 # pylint: disable = missing-docstring 96 97 def fget(self): 98 return self._udict['encoder']
99 return locals() 100 101 @_util.Property
102 - def decoder():
103 """ 104 Input decoder 105 106 :Type: `DecoderInterface` 107 """ 108 # pylint: disable = no-method-argument, unused-variable 109 # pylint: disable = missing-docstring 110 111 def fget(self): 112 return self._udict['decoder']
113 return locals() 114
115 - def __setitem__(self, name, value):
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
146 - def __getitem__(self, name):
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
165 - def __delitem__(self, name):
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 # Ignore, because this is not an error. 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 # pylint: disable = protected-access 210 211 @_util.Property
212 - def content():
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 # pylint: disable = no-method-argument, unused-variable 227 # pylint: disable = missing-docstring 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
243 - def hiddenelement():
244 """ 245 Hidden node markup? 246 247 :Type: ``bool`` 248 """ 249 # pylint: disable = no-method-argument, unused-variable 250 # pylint: disable = missing-docstring 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
260 - def closedelement():
261 """ 262 Self-closed element? (read-only) 263 264 :Type: ``bool`` 265 """ 266 # pylint: disable = no-method-argument, unused-variable 267 # pylint: disable = missing-docstring 268 269 def fget(self): 270 return self._udict['closed']
271 return locals() 272 273 @_util.Property
274 - def raw():
275 """ 276 Raw node 277 278 :Type: `RawNode` 279 """ 280 # pylint: disable = no-method-argument, unused-variable 281 # pylint: disable = missing-docstring 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
328 - def __call__(self, name):
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: # walk through transparent "nodes" 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
369 - def __getattr__(self, name):
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
390 - def __setitem__(self, name, value):
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
421 - def __getitem__(self, name):
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
444 - def __delitem__(self, name):
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 # Ignore, because this is not an error. 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
524 - def remove(self):
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 # This effectively indents the iterated nodeset by one level. 566 # The original node (which was copied from before) only acts as a 567 # container now. 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'] # name stays the same 610 udict['callback'] = callback 611 udict['complete'] = fixed 612 613 self._udict = udict 614 return self
615
616 - def copy(self):
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
693 694 -class TemplateNode(object):
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 # pylint: disable = protected-access 714 715 @_util.Property
716 - def endtag():
717 """ 718 End tag of the node 719 720 :Type: ``str`` 721 """ 722 # pylint: disable = no-method-argument, unused-variable 723 # pylint: disable = missing-docstring 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], # is_hidden 767 '>' in overlay[0], # is_target 768 '<' in overlay[0], # is_source 769 overlay[1], # name 770 ) 771 772 if scope is None: 773 scope = False, False, None 774 else: 775 scope = ( 776 ('-' in scope[0]), # is_hidden 777 ('=' in scope[0]), # is_absolute 778 scope[1], # name 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
817 - def append_escape(self, escaped, content):
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 # pylint: disable = protected-access 885 886 @_util.Property
887 - def encoder():
888 """ 889 Output encoder 890 891 :Type: `EncoderInterface` 892 """ 893 # pylint: disable = no-method-argument, unused-variable 894 # pylint: disable = missing-docstring 895 896 def fget(self): 897 return self._udict['encoder']
898 return locals()
899 900 @_util.Property
901 - def decoder():
902 """ 903 Input decoder 904 905 :Type: `DecoderInterface` 906 """ 907 # pylint: disable = no-method-argument, unused-variable 908 # pylint: disable = missing-docstring 909 910 def fget(self): 911 return self._udict['decoder']
912 return locals() 913 914 @_util.Property
915 - def source_overlay_names():
916 """ 917 Source overlay names 918 919 :Type: iterable 920 """ 921 # pylint: disable = no-method-argument, unused-variable 922 # pylint: disable = missing-docstring 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
931 - def target_overlay_names():
932 """ 933 Target overlay names 934 935 :Type: iterable 936 """ 937 # pylint: disable = no-method-argument, unused-variable 938 # pylint: disable = missing-docstring 939 940 def fget(self): 941 if self._targets is None: 942 return () 943 return self._targets.iterkeys()
944 return locals() 945
946 - def __init__(self):
947 """ Initialization """ 948 super(Root, self).__init__('', (), {}, False) 949 self.endtag = '' 950 self._udict['is_root'] = True
951
952 - def __str__(self):
953 """ String representation of the tree """ 954 return self.to_string(verbose=True)
955
956 - def to_string(self, verbose=False):
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
973 - def finalize(self, encoder, decoder):
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
995 - def overlay(self, other):
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 = ( # noqa 1050 c.Root, c.Node, c.RawNode, c.TemplateNode 1051 ) 1052 del c 1053