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

Source Code for Module tdi.nodetree

   1  # -*- coding: ascii -*- 
   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   
37 -class RawNode(object):
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
47 - def __init__(self, node):
48 """ 49 Initialization 50 51 :Parameters: 52 `node` : `Node` 53 The original node 54 """ 55 self._udict = node._udict # pylint: disable = W0212
56
57 - def content():
58 """ 59 Raw content 60 61 :Type: ``str`` 62 """ 63 # pylint: disable = E0211, W0212, W0612, C0111 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
78 - def encoder():
79 """ 80 Output encoder 81 82 :Type: `EncoderInterface` 83 """ 84 # pylint: disable = E0211, W0212, W0612, C0111 85 def fget(self): 86 return self._udict['encoder']
87 return locals() 88 encoder = _util.Property(encoder) 89
90 - def decoder():
91 """ 92 Input decoder 93 94 :Type: `DecoderInterface` 95 """ 96 # pylint: disable = E0211, W0212, W0612, C0111 97 def fget(self): 98 return self._udict['decoder']
99 return locals() 100 decoder = _util.Property(decoder) 101
102 - def __setitem__(self, name, value):
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
133 - def __getitem__(self, name):
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
152 - def __delitem__(self, name):
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 # Ignore, because this is not an error. 169 pass
170 171
172 -class Node(object):
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
196 - def content():
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 # pylint: disable = E0211, W0212, W0612, C0111 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
223 - def hiddenelement():
224 """ 225 Hidden node markup? 226 227 :Type: ``bool`` 228 """ 229 # pylint: disable = E0211, W0212, W0612, C0111 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
237 - def closedelement():
238 """ 239 Self-closed element? (read-only) 240 241 :Type: ``bool`` 242 """ 243 # pylint: disable = E0211, W0212, W0612, C0111 244 def fget(self): 245 return self._udict['closed']
246 return locals() 247 closedelement = _util.Property(closedelement) 248
249 - def raw():
250 """ 251 Raw node 252 253 :Type: `RawNode` 254 """ 255 # pylint: disable = E0211, W0212, W0612, C0111 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 # pylint: disable = W0212 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
303 - def __call__(self, name):
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 # pylint: disable = W0212 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: # walk through transparent "nodes" 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
345 - def __getattr__(self, name):
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
366 - def __setitem__(self, name, value):
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
397 - def __getitem__(self, name):
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
420 - def __delitem__(self, name):
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 # Ignore, because this is not an error. 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
500 - def remove(self):
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 # This effectively indents the iterated nodeset by one level. 542 # The original node (which was copied from before) only acts as a 543 # container now. 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 # pylint: disable = E0203 575 # pylint: disable = W0201, W0212 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'] # name stays the same 586 udict['callback'] = callback 587 udict['complete'] = fixed 588 589 self._udict = udict 590 return self
591
592 - def copy(self):
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 # pylint: disable = W0212 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
671 -class TemplateNode(object):
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
690 - def endtag():
691 """ 692 End tag of the node 693 694 :Type: ``str`` 695 """ 696 # pylint: disable = E0211, W0212, W0612, C0111 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], # is_hidden 739 '>' in overlay[0], # is_target 740 '<' in overlay[0], # is_source 741 overlay[1], # name 742 ) 743 744 if scope is None: 745 scope = False, False, None 746 else: 747 scope = ( 748 ('-' in scope[0]), # is_hidden 749 ('=' in scope[0]), # is_absolute 750 scope[1], # name 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
789 - def append_escape(self, escaped, content):
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
856 - def encoder():
857 """ 858 Output encoder 859 860 :Type: `EncoderInterface` 861 """ 862 # pylint: disable = E0211, W0212, W0612, C0111 863 def fget(self): 864 return self._udict['encoder']
865 return locals()
866 encoder = _util.Property(encoder) 867
868 - def decoder():
869 """ 870 Input decoder 871 872 :Type: `DecoderInterface` 873 """ 874 # pylint: disable = E0211, W0212, W0612, C0111 875 def fget(self): 876 return self._udict['decoder']
877 return locals() 878 decoder = _util.Property(decoder) 879
880 - def source_overlay_names():
881 """ 882 Source overlay names 883 884 :Type: iterable 885 """ 886 # pylint: disable = E0211, W0212, W0612, C0111 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
894 - def target_overlay_names():
895 """ 896 Target overlay names 897 898 :Type: iterable 899 """ 900 # pylint: disable = E0211, W0212, W0612, C0111 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
908 - def __init__(self):
909 """ Initialization """ 910 super(Root, self).__init__('', (), {}, False) 911 self.endtag = '' 912 self._udict['is_root'] = True
913
914 - def __str__(self):
915 """ String representation of the tree """ 916 return self.to_string(verbose=True)
917
918 - def to_string(self, verbose=False):
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
935 - def finalize(self, encoder, decoder):
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
957 - def overlay(self, other):
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 # pylint: disable = W0212 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