Mercurial > repos > bcclaywell > argo_navis
comparison venv/lib/python2.7/site-packages/docutils/nodes.py @ 0:d67268158946 draft
planemo upload commit a3f181f5f126803c654b3a66dd4e83a48f7e203b
| author | bcclaywell |
|---|---|
| date | Mon, 12 Oct 2015 17:43:33 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:d67268158946 |
|---|---|
| 1 # $Id: nodes.py 7595 2013-01-21 17:33:56Z milde $ | |
| 2 # Author: David Goodger <goodger@python.org> | |
| 3 # Maintainer: docutils-develop@lists.sourceforge.net | |
| 4 # Copyright: This module has been placed in the public domain. | |
| 5 | |
| 6 """ | |
| 7 Docutils document tree element class library. | |
| 8 | |
| 9 Classes in CamelCase are abstract base classes or auxiliary classes. The one | |
| 10 exception is `Text`, for a text (PCDATA) node; uppercase is used to | |
| 11 differentiate from element classes. Classes in lower_case_with_underscores | |
| 12 are element classes, matching the XML element generic identifiers in the DTD_. | |
| 13 | |
| 14 The position of each node (the level at which it can occur) is significant and | |
| 15 is represented by abstract base classes (`Root`, `Structural`, `Body`, | |
| 16 `Inline`, etc.). Certain transformations will be easier because we can use | |
| 17 ``isinstance(node, base_class)`` to determine the position of the node in the | |
| 18 hierarchy. | |
| 19 | |
| 20 .. _DTD: http://docutils.sourceforge.net/docs/ref/docutils.dtd | |
| 21 """ | |
| 22 | |
| 23 __docformat__ = 'reStructuredText' | |
| 24 | |
| 25 import sys | |
| 26 import os | |
| 27 import re | |
| 28 import warnings | |
| 29 import types | |
| 30 import unicodedata | |
| 31 | |
| 32 # ============================== | |
| 33 # Functional Node Base Classes | |
| 34 # ============================== | |
| 35 | |
| 36 class Node(object): | |
| 37 | |
| 38 """Abstract base class of nodes in a document tree.""" | |
| 39 | |
| 40 parent = None | |
| 41 """Back-reference to the Node immediately containing this Node.""" | |
| 42 | |
| 43 document = None | |
| 44 """The `document` node at the root of the tree containing this Node.""" | |
| 45 | |
| 46 source = None | |
| 47 """Path or description of the input source which generated this Node.""" | |
| 48 | |
| 49 line = None | |
| 50 """The line number (1-based) of the beginning of this Node in `source`.""" | |
| 51 | |
| 52 def __nonzero__(self): | |
| 53 """ | |
| 54 Node instances are always true, even if they're empty. A node is more | |
| 55 than a simple container. Its boolean "truth" does not depend on | |
| 56 having one or more subnodes in the doctree. | |
| 57 | |
| 58 Use `len()` to check node length. Use `None` to represent a boolean | |
| 59 false value. | |
| 60 """ | |
| 61 return True | |
| 62 | |
| 63 if sys.version_info < (3,): | |
| 64 # on 2.x, str(node) will be a byte string with Unicode | |
| 65 # characters > 255 escaped; on 3.x this is no longer necessary | |
| 66 def __str__(self): | |
| 67 return unicode(self).encode('raw_unicode_escape') | |
| 68 | |
| 69 def asdom(self, dom=None): | |
| 70 """Return a DOM **fragment** representation of this Node.""" | |
| 71 if dom is None: | |
| 72 import xml.dom.minidom as dom | |
| 73 domroot = dom.Document() | |
| 74 return self._dom_node(domroot) | |
| 75 | |
| 76 def pformat(self, indent=' ', level=0): | |
| 77 """ | |
| 78 Return an indented pseudo-XML representation, for test purposes. | |
| 79 | |
| 80 Override in subclasses. | |
| 81 """ | |
| 82 raise NotImplementedError | |
| 83 | |
| 84 def copy(self): | |
| 85 """Return a copy of self.""" | |
| 86 raise NotImplementedError | |
| 87 | |
| 88 def deepcopy(self): | |
| 89 """Return a deep copy of self (also copying children).""" | |
| 90 raise NotImplementedError | |
| 91 | |
| 92 def setup_child(self, child): | |
| 93 child.parent = self | |
| 94 if self.document: | |
| 95 child.document = self.document | |
| 96 if child.source is None: | |
| 97 child.source = self.document.current_source | |
| 98 if child.line is None: | |
| 99 child.line = self.document.current_line | |
| 100 | |
| 101 def walk(self, visitor): | |
| 102 """ | |
| 103 Traverse a tree of `Node` objects, calling the | |
| 104 `dispatch_visit()` method of `visitor` when entering each | |
| 105 node. (The `walkabout()` method is similar, except it also | |
| 106 calls the `dispatch_departure()` method before exiting each | |
| 107 node.) | |
| 108 | |
| 109 This tree traversal supports limited in-place tree | |
| 110 modifications. Replacing one node with one or more nodes is | |
| 111 OK, as is removing an element. However, if the node removed | |
| 112 or replaced occurs after the current node, the old node will | |
| 113 still be traversed, and any new nodes will not. | |
| 114 | |
| 115 Within ``visit`` methods (and ``depart`` methods for | |
| 116 `walkabout()`), `TreePruningException` subclasses may be raised | |
| 117 (`SkipChildren`, `SkipSiblings`, `SkipNode`, `SkipDeparture`). | |
| 118 | |
| 119 Parameter `visitor`: A `NodeVisitor` object, containing a | |
| 120 ``visit`` implementation for each `Node` subclass encountered. | |
| 121 | |
| 122 Return true if we should stop the traversal. | |
| 123 """ | |
| 124 stop = False | |
| 125 visitor.document.reporter.debug( | |
| 126 'docutils.nodes.Node.walk calling dispatch_visit for %s' | |
| 127 % self.__class__.__name__) | |
| 128 try: | |
| 129 try: | |
| 130 visitor.dispatch_visit(self) | |
| 131 except (SkipChildren, SkipNode): | |
| 132 return stop | |
| 133 except SkipDeparture: # not applicable; ignore | |
| 134 pass | |
| 135 children = self.children | |
| 136 try: | |
| 137 for child in children[:]: | |
| 138 if child.walk(visitor): | |
| 139 stop = True | |
| 140 break | |
| 141 except SkipSiblings: | |
| 142 pass | |
| 143 except StopTraversal: | |
| 144 stop = True | |
| 145 return stop | |
| 146 | |
| 147 def walkabout(self, visitor): | |
| 148 """ | |
| 149 Perform a tree traversal similarly to `Node.walk()` (which | |
| 150 see), except also call the `dispatch_departure()` method | |
| 151 before exiting each node. | |
| 152 | |
| 153 Parameter `visitor`: A `NodeVisitor` object, containing a | |
| 154 ``visit`` and ``depart`` implementation for each `Node` | |
| 155 subclass encountered. | |
| 156 | |
| 157 Return true if we should stop the traversal. | |
| 158 """ | |
| 159 call_depart = True | |
| 160 stop = False | |
| 161 visitor.document.reporter.debug( | |
| 162 'docutils.nodes.Node.walkabout calling dispatch_visit for %s' | |
| 163 % self.__class__.__name__) | |
| 164 try: | |
| 165 try: | |
| 166 visitor.dispatch_visit(self) | |
| 167 except SkipNode: | |
| 168 return stop | |
| 169 except SkipDeparture: | |
| 170 call_depart = False | |
| 171 children = self.children | |
| 172 try: | |
| 173 for child in children[:]: | |
| 174 if child.walkabout(visitor): | |
| 175 stop = True | |
| 176 break | |
| 177 except SkipSiblings: | |
| 178 pass | |
| 179 except SkipChildren: | |
| 180 pass | |
| 181 except StopTraversal: | |
| 182 stop = True | |
| 183 if call_depart: | |
| 184 visitor.document.reporter.debug( | |
| 185 'docutils.nodes.Node.walkabout calling dispatch_departure ' | |
| 186 'for %s' % self.__class__.__name__) | |
| 187 visitor.dispatch_departure(self) | |
| 188 return stop | |
| 189 | |
| 190 def _fast_traverse(self, cls): | |
| 191 """Specialized traverse() that only supports instance checks.""" | |
| 192 result = [] | |
| 193 if isinstance(self, cls): | |
| 194 result.append(self) | |
| 195 for child in self.children: | |
| 196 result.extend(child._fast_traverse(cls)) | |
| 197 return result | |
| 198 | |
| 199 def _all_traverse(self): | |
| 200 """Specialized traverse() that doesn't check for a condition.""" | |
| 201 result = [] | |
| 202 result.append(self) | |
| 203 for child in self.children: | |
| 204 result.extend(child._all_traverse()) | |
| 205 return result | |
| 206 | |
| 207 def traverse(self, condition=None, include_self=True, descend=True, | |
| 208 siblings=False, ascend=False): | |
| 209 """ | |
| 210 Return an iterable containing | |
| 211 | |
| 212 * self (if include_self is true) | |
| 213 * all descendants in tree traversal order (if descend is true) | |
| 214 * all siblings (if siblings is true) and their descendants (if | |
| 215 also descend is true) | |
| 216 * the siblings of the parent (if ascend is true) and their | |
| 217 descendants (if also descend is true), and so on | |
| 218 | |
| 219 If `condition` is not None, the iterable contains only nodes | |
| 220 for which ``condition(node)`` is true. If `condition` is a | |
| 221 node class ``cls``, it is equivalent to a function consisting | |
| 222 of ``return isinstance(node, cls)``. | |
| 223 | |
| 224 If ascend is true, assume siblings to be true as well. | |
| 225 | |
| 226 For example, given the following tree:: | |
| 227 | |
| 228 <paragraph> | |
| 229 <emphasis> <--- emphasis.traverse() and | |
| 230 <strong> <--- strong.traverse() are called. | |
| 231 Foo | |
| 232 Bar | |
| 233 <reference name="Baz" refid="baz"> | |
| 234 Baz | |
| 235 | |
| 236 Then list(emphasis.traverse()) equals :: | |
| 237 | |
| 238 [<emphasis>, <strong>, <#text: Foo>, <#text: Bar>] | |
| 239 | |
| 240 and list(strong.traverse(ascend=True)) equals :: | |
| 241 | |
| 242 [<strong>, <#text: Foo>, <#text: Bar>, <reference>, <#text: Baz>] | |
| 243 """ | |
| 244 if ascend: | |
| 245 siblings=True | |
| 246 # Check for special argument combinations that allow using an | |
| 247 # optimized version of traverse() | |
| 248 if include_self and descend and not siblings: | |
| 249 if condition is None: | |
| 250 return self._all_traverse() | |
| 251 elif isinstance(condition, (types.ClassType, type)): | |
| 252 return self._fast_traverse(condition) | |
| 253 # Check if `condition` is a class (check for TypeType for Python | |
| 254 # implementations that use only new-style classes, like PyPy). | |
| 255 if isinstance(condition, (types.ClassType, type)): | |
| 256 node_class = condition | |
| 257 def condition(node, node_class=node_class): | |
| 258 return isinstance(node, node_class) | |
| 259 r = [] | |
| 260 if include_self and (condition is None or condition(self)): | |
| 261 r.append(self) | |
| 262 if descend and len(self.children): | |
| 263 for child in self: | |
| 264 r.extend(child.traverse(include_self=True, descend=True, | |
| 265 siblings=False, ascend=False, | |
| 266 condition=condition)) | |
| 267 if siblings or ascend: | |
| 268 node = self | |
| 269 while node.parent: | |
| 270 index = node.parent.index(node) | |
| 271 for sibling in node.parent[index+1:]: | |
| 272 r.extend(sibling.traverse(include_self=True, | |
| 273 descend=descend, | |
| 274 siblings=False, ascend=False, | |
| 275 condition=condition)) | |
| 276 if not ascend: | |
| 277 break | |
| 278 else: | |
| 279 node = node.parent | |
| 280 return r | |
| 281 | |
| 282 def next_node(self, condition=None, include_self=False, descend=True, | |
| 283 siblings=False, ascend=False): | |
| 284 """ | |
| 285 Return the first node in the iterable returned by traverse(), | |
| 286 or None if the iterable is empty. | |
| 287 | |
| 288 Parameter list is the same as of traverse. Note that | |
| 289 include_self defaults to 0, though. | |
| 290 """ | |
| 291 iterable = self.traverse(condition=condition, | |
| 292 include_self=include_self, descend=descend, | |
| 293 siblings=siblings, ascend=ascend) | |
| 294 try: | |
| 295 return iterable[0] | |
| 296 except IndexError: | |
| 297 return None | |
| 298 | |
| 299 if sys.version_info < (3,): | |
| 300 class reprunicode(unicode): | |
| 301 """ | |
| 302 A unicode sub-class that removes the initial u from unicode's repr. | |
| 303 """ | |
| 304 | |
| 305 def __repr__(self): | |
| 306 return unicode.__repr__(self)[1:] | |
| 307 | |
| 308 | |
| 309 else: | |
| 310 reprunicode = unicode | |
| 311 | |
| 312 | |
| 313 def ensure_str(s): | |
| 314 """ | |
| 315 Failsave conversion of `unicode` to `str`. | |
| 316 """ | |
| 317 if sys.version_info < (3,) and isinstance(s, unicode): | |
| 318 return s.encode('ascii', 'backslashreplace') | |
| 319 return s | |
| 320 | |
| 321 | |
| 322 class Text(Node, reprunicode): | |
| 323 | |
| 324 """ | |
| 325 Instances are terminal nodes (leaves) containing text only; no child | |
| 326 nodes or attributes. Initialize by passing a string to the constructor. | |
| 327 Access the text itself with the `astext` method. | |
| 328 """ | |
| 329 | |
| 330 tagname = '#text' | |
| 331 | |
| 332 children = () | |
| 333 """Text nodes have no children, and cannot have children.""" | |
| 334 | |
| 335 if sys.version_info > (3,): | |
| 336 def __new__(cls, data, rawsource=None): | |
| 337 """Prevent the rawsource argument from propagating to str.""" | |
| 338 if isinstance(data, bytes): | |
| 339 raise TypeError('expecting str data, not bytes') | |
| 340 return reprunicode.__new__(cls, data) | |
| 341 else: | |
| 342 def __new__(cls, data, rawsource=None): | |
| 343 """Prevent the rawsource argument from propagating to str.""" | |
| 344 return reprunicode.__new__(cls, data) | |
| 345 | |
| 346 def __init__(self, data, rawsource=''): | |
| 347 | |
| 348 self.rawsource = rawsource | |
| 349 """The raw text from which this element was constructed.""" | |
| 350 | |
| 351 def shortrepr(self, maxlen=18): | |
| 352 data = self | |
| 353 if len(data) > maxlen: | |
| 354 data = data[:maxlen-4] + ' ...' | |
| 355 return '<%s: %r>' % (self.tagname, reprunicode(data)) | |
| 356 | |
| 357 def __repr__(self): | |
| 358 return self.shortrepr(maxlen=68) | |
| 359 | |
| 360 def _dom_node(self, domroot): | |
| 361 return domroot.createTextNode(unicode(self)) | |
| 362 | |
| 363 def astext(self): | |
| 364 return reprunicode(self) | |
| 365 | |
| 366 # Note about __unicode__: The implementation of __unicode__ here, | |
| 367 # and the one raising NotImplemented in the superclass Node had | |
| 368 # to be removed when changing Text to a subclass of unicode instead | |
| 369 # of UserString, since there is no way to delegate the __unicode__ | |
| 370 # call to the superclass unicode: | |
| 371 # unicode itself does not have __unicode__ method to delegate to | |
| 372 # and calling unicode(self) or unicode.__new__ directly creates | |
| 373 # an infinite loop | |
| 374 | |
| 375 def copy(self): | |
| 376 return self.__class__(reprunicode(self), rawsource=self.rawsource) | |
| 377 | |
| 378 def deepcopy(self): | |
| 379 return self.copy() | |
| 380 | |
| 381 def pformat(self, indent=' ', level=0): | |
| 382 result = [] | |
| 383 indent = indent * level | |
| 384 for line in self.splitlines(): | |
| 385 result.append(indent + line + '\n') | |
| 386 return ''.join(result) | |
| 387 | |
| 388 # rstrip and lstrip are used by substitution definitions where | |
| 389 # they are expected to return a Text instance, this was formerly | |
| 390 # taken care of by UserString. Note that then and now the | |
| 391 # rawsource member is lost. | |
| 392 | |
| 393 def rstrip(self, chars=None): | |
| 394 return self.__class__(reprunicode.rstrip(self, chars)) | |
| 395 def lstrip(self, chars=None): | |
| 396 return self.__class__(reprunicode.lstrip(self, chars)) | |
| 397 | |
| 398 class Element(Node): | |
| 399 | |
| 400 """ | |
| 401 `Element` is the superclass to all specific elements. | |
| 402 | |
| 403 Elements contain attributes and child nodes. Elements emulate | |
| 404 dictionaries for attributes, indexing by attribute name (a string). To | |
| 405 set the attribute 'att' to 'value', do:: | |
| 406 | |
| 407 element['att'] = 'value' | |
| 408 | |
| 409 There are two special attributes: 'ids' and 'names'. Both are | |
| 410 lists of unique identifiers, and names serve as human interfaces | |
| 411 to IDs. Names are case- and whitespace-normalized (see the | |
| 412 fully_normalize_name() function), and IDs conform to the regular | |
| 413 expression ``[a-z](-?[a-z0-9]+)*`` (see the make_id() function). | |
| 414 | |
| 415 Elements also emulate lists for child nodes (element nodes and/or text | |
| 416 nodes), indexing by integer. To get the first child node, use:: | |
| 417 | |
| 418 element[0] | |
| 419 | |
| 420 Elements may be constructed using the ``+=`` operator. To add one new | |
| 421 child node to element, do:: | |
| 422 | |
| 423 element += node | |
| 424 | |
| 425 This is equivalent to ``element.append(node)``. | |
| 426 | |
| 427 To add a list of multiple child nodes at once, use the same ``+=`` | |
| 428 operator:: | |
| 429 | |
| 430 element += [node1, node2] | |
| 431 | |
| 432 This is equivalent to ``element.extend([node1, node2])``. | |
| 433 """ | |
| 434 | |
| 435 basic_attributes = ('ids', 'classes', 'names', 'dupnames') | |
| 436 """List attributes which are defined for every Element-derived class | |
| 437 instance and can be safely transferred to a different node.""" | |
| 438 | |
| 439 local_attributes = ('backrefs',) | |
| 440 """A list of class-specific attributes that should not be copied with the | |
| 441 standard attributes when replacing a node. | |
| 442 | |
| 443 NOTE: Derived classes should override this value to prevent any of its | |
| 444 attributes being copied by adding to the value in its parent class.""" | |
| 445 | |
| 446 list_attributes = basic_attributes + local_attributes | |
| 447 """List attributes, automatically initialized to empty lists for | |
| 448 all nodes.""" | |
| 449 | |
| 450 known_attributes = list_attributes + ('source',) | |
| 451 """List attributes that are known to the Element base class.""" | |
| 452 | |
| 453 tagname = None | |
| 454 """The element generic identifier. If None, it is set as an instance | |
| 455 attribute to the name of the class.""" | |
| 456 | |
| 457 child_text_separator = '\n\n' | |
| 458 """Separator for child nodes, used by `astext()` method.""" | |
| 459 | |
| 460 def __init__(self, rawsource='', *children, **attributes): | |
| 461 self.rawsource = rawsource | |
| 462 """The raw text from which this element was constructed.""" | |
| 463 | |
| 464 self.children = [] | |
| 465 """List of child nodes (elements and/or `Text`).""" | |
| 466 | |
| 467 self.extend(children) # maintain parent info | |
| 468 | |
| 469 self.attributes = {} | |
| 470 """Dictionary of attribute {name: value}.""" | |
| 471 | |
| 472 # Initialize list attributes. | |
| 473 for att in self.list_attributes: | |
| 474 self.attributes[att] = [] | |
| 475 | |
| 476 for att, value in attributes.items(): | |
| 477 att = att.lower() | |
| 478 if att in self.list_attributes: | |
| 479 # mutable list; make a copy for this node | |
| 480 self.attributes[att] = value[:] | |
| 481 else: | |
| 482 self.attributes[att] = value | |
| 483 | |
| 484 if self.tagname is None: | |
| 485 self.tagname = self.__class__.__name__ | |
| 486 | |
| 487 def _dom_node(self, domroot): | |
| 488 element = domroot.createElement(self.tagname) | |
| 489 for attribute, value in self.attlist(): | |
| 490 if isinstance(value, list): | |
| 491 value = ' '.join([serial_escape('%s' % (v,)) for v in value]) | |
| 492 element.setAttribute(attribute, '%s' % value) | |
| 493 for child in self.children: | |
| 494 element.appendChild(child._dom_node(domroot)) | |
| 495 return element | |
| 496 | |
| 497 def __repr__(self): | |
| 498 data = '' | |
| 499 for c in self.children: | |
| 500 data += c.shortrepr() | |
| 501 if len(data) > 60: | |
| 502 data = data[:56] + ' ...' | |
| 503 break | |
| 504 if self['names']: | |
| 505 return '<%s "%s": %s>' % (self.__class__.__name__, | |
| 506 '; '.join([ensure_str(n) for n in self['names']]), data) | |
| 507 else: | |
| 508 return '<%s: %s>' % (self.__class__.__name__, data) | |
| 509 | |
| 510 def shortrepr(self): | |
| 511 if self['names']: | |
| 512 return '<%s "%s"...>' % (self.__class__.__name__, | |
| 513 '; '.join([ensure_str(n) for n in self['names']])) | |
| 514 else: | |
| 515 return '<%s...>' % self.tagname | |
| 516 | |
| 517 def __unicode__(self): | |
| 518 if self.children: | |
| 519 return u'%s%s%s' % (self.starttag(), | |
| 520 ''.join([unicode(c) for c in self.children]), | |
| 521 self.endtag()) | |
| 522 else: | |
| 523 return self.emptytag() | |
| 524 | |
| 525 if sys.version_info > (3,): | |
| 526 # 2to3 doesn't convert __unicode__ to __str__ | |
| 527 __str__ = __unicode__ | |
| 528 | |
| 529 def starttag(self, quoteattr=None): | |
| 530 # the optional arg is used by the docutils_xml writer | |
| 531 if quoteattr is None: | |
| 532 quoteattr = pseudo_quoteattr | |
| 533 parts = [self.tagname] | |
| 534 for name, value in self.attlist(): | |
| 535 if value is None: # boolean attribute | |
| 536 parts.append(name) | |
| 537 continue | |
| 538 if isinstance(value, list): | |
| 539 values = [serial_escape('%s' % (v,)) for v in value] | |
| 540 value = ' '.join(values) | |
| 541 else: | |
| 542 value = unicode(value) | |
| 543 value = quoteattr(value) | |
| 544 parts.append(u'%s=%s' % (name, value)) | |
| 545 return u'<%s>' % u' '.join(parts) | |
| 546 | |
| 547 def endtag(self): | |
| 548 return '</%s>' % self.tagname | |
| 549 | |
| 550 def emptytag(self): | |
| 551 return u'<%s/>' % u' '.join([self.tagname] + | |
| 552 ['%s="%s"' % (n, v) | |
| 553 for n, v in self.attlist()]) | |
| 554 | |
| 555 def __len__(self): | |
| 556 return len(self.children) | |
| 557 | |
| 558 def __contains__(self, key): | |
| 559 # support both membership test for children and attributes | |
| 560 # (has_key is translated to "in" by 2to3) | |
| 561 if isinstance(key, basestring): | |
| 562 return key in self.attributes | |
| 563 return key in self.children | |
| 564 | |
| 565 def __getitem__(self, key): | |
| 566 if isinstance(key, basestring): | |
| 567 return self.attributes[key] | |
| 568 elif isinstance(key, int): | |
| 569 return self.children[key] | |
| 570 elif isinstance(key, types.SliceType): | |
| 571 assert key.step in (None, 1), 'cannot handle slice with stride' | |
| 572 return self.children[key.start:key.stop] | |
| 573 else: | |
| 574 raise TypeError, ('element index must be an integer, a slice, or ' | |
| 575 'an attribute name string') | |
| 576 | |
| 577 def __setitem__(self, key, item): | |
| 578 if isinstance(key, basestring): | |
| 579 self.attributes[str(key)] = item | |
| 580 elif isinstance(key, int): | |
| 581 self.setup_child(item) | |
| 582 self.children[key] = item | |
| 583 elif isinstance(key, types.SliceType): | |
| 584 assert key.step in (None, 1), 'cannot handle slice with stride' | |
| 585 for node in item: | |
| 586 self.setup_child(node) | |
| 587 self.children[key.start:key.stop] = item | |
| 588 else: | |
| 589 raise TypeError, ('element index must be an integer, a slice, or ' | |
| 590 'an attribute name string') | |
| 591 | |
| 592 def __delitem__(self, key): | |
| 593 if isinstance(key, basestring): | |
| 594 del self.attributes[key] | |
| 595 elif isinstance(key, int): | |
| 596 del self.children[key] | |
| 597 elif isinstance(key, types.SliceType): | |
| 598 assert key.step in (None, 1), 'cannot handle slice with stride' | |
| 599 del self.children[key.start:key.stop] | |
| 600 else: | |
| 601 raise TypeError, ('element index must be an integer, a simple ' | |
| 602 'slice, or an attribute name string') | |
| 603 | |
| 604 def __add__(self, other): | |
| 605 return self.children + other | |
| 606 | |
| 607 def __radd__(self, other): | |
| 608 return other + self.children | |
| 609 | |
| 610 def __iadd__(self, other): | |
| 611 """Append a node or a list of nodes to `self.children`.""" | |
| 612 if isinstance(other, Node): | |
| 613 self.append(other) | |
| 614 elif other is not None: | |
| 615 self.extend(other) | |
| 616 return self | |
| 617 | |
| 618 def astext(self): | |
| 619 return self.child_text_separator.join( | |
| 620 [child.astext() for child in self.children]) | |
| 621 | |
| 622 def non_default_attributes(self): | |
| 623 atts = {} | |
| 624 for key, value in self.attributes.items(): | |
| 625 if self.is_not_default(key): | |
| 626 atts[key] = value | |
| 627 return atts | |
| 628 | |
| 629 def attlist(self): | |
| 630 attlist = self.non_default_attributes().items() | |
| 631 attlist.sort() | |
| 632 return attlist | |
| 633 | |
| 634 def get(self, key, failobj=None): | |
| 635 return self.attributes.get(key, failobj) | |
| 636 | |
| 637 def hasattr(self, attr): | |
| 638 return attr in self.attributes | |
| 639 | |
| 640 def delattr(self, attr): | |
| 641 if attr in self.attributes: | |
| 642 del self.attributes[attr] | |
| 643 | |
| 644 def setdefault(self, key, failobj=None): | |
| 645 return self.attributes.setdefault(key, failobj) | |
| 646 | |
| 647 has_key = hasattr | |
| 648 | |
| 649 # support operator ``in`` | |
| 650 __contains__ = hasattr | |
| 651 | |
| 652 def get_language_code(self, fallback=''): | |
| 653 """Return node's language tag. | |
| 654 | |
| 655 Look iteratively in self and parents for a class argument | |
| 656 starting with ``language-`` and return the remainder of it | |
| 657 (which should be a `BCP49` language tag) or the `fallback`. | |
| 658 """ | |
| 659 for cls in self.get('classes', []): | |
| 660 if cls.startswith('language-'): | |
| 661 return cls[9:] | |
| 662 try: | |
| 663 return self.parent.get_language(fallback) | |
| 664 except AttributeError: | |
| 665 return fallback | |
| 666 | |
| 667 def append(self, item): | |
| 668 self.setup_child(item) | |
| 669 self.children.append(item) | |
| 670 | |
| 671 def extend(self, item): | |
| 672 for node in item: | |
| 673 self.append(node) | |
| 674 | |
| 675 def insert(self, index, item): | |
| 676 if isinstance(item, Node): | |
| 677 self.setup_child(item) | |
| 678 self.children.insert(index, item) | |
| 679 elif item is not None: | |
| 680 self[index:index] = item | |
| 681 | |
| 682 def pop(self, i=-1): | |
| 683 return self.children.pop(i) | |
| 684 | |
| 685 def remove(self, item): | |
| 686 self.children.remove(item) | |
| 687 | |
| 688 def index(self, item): | |
| 689 return self.children.index(item) | |
| 690 | |
| 691 def is_not_default(self, key): | |
| 692 if self[key] == [] and key in self.list_attributes: | |
| 693 return 0 | |
| 694 else: | |
| 695 return 1 | |
| 696 | |
| 697 def update_basic_atts(self, dict_): | |
| 698 """ | |
| 699 Update basic attributes ('ids', 'names', 'classes', | |
| 700 'dupnames', but not 'source') from node or dictionary `dict_`. | |
| 701 """ | |
| 702 if isinstance(dict_, Node): | |
| 703 dict_ = dict_.attributes | |
| 704 for att in self.basic_attributes: | |
| 705 self.append_attr_list(att, dict_.get(att, [])) | |
| 706 | |
| 707 def append_attr_list(self, attr, values): | |
| 708 """ | |
| 709 For each element in values, if it does not exist in self[attr], append | |
| 710 it. | |
| 711 | |
| 712 NOTE: Requires self[attr] and values to be sequence type and the | |
| 713 former should specifically be a list. | |
| 714 """ | |
| 715 # List Concatenation | |
| 716 for value in values: | |
| 717 if not value in self[attr]: | |
| 718 self[attr].append(value) | |
| 719 | |
| 720 def coerce_append_attr_list(self, attr, value): | |
| 721 """ | |
| 722 First, convert both self[attr] and value to a non-string sequence | |
| 723 type; if either is not already a sequence, convert it to a list of one | |
| 724 element. Then call append_attr_list. | |
| 725 | |
| 726 NOTE: self[attr] and value both must not be None. | |
| 727 """ | |
| 728 # List Concatenation | |
| 729 if not isinstance(self.get(attr), list): | |
| 730 self[attr] = [self[attr]] | |
| 731 if not isinstance(value, list): | |
| 732 value = [value] | |
| 733 self.append_attr_list(attr, value) | |
| 734 | |
| 735 def replace_attr(self, attr, value, force = True): | |
| 736 """ | |
| 737 If self[attr] does not exist or force is True or omitted, set | |
| 738 self[attr] to value, otherwise do nothing. | |
| 739 """ | |
| 740 # One or the other | |
| 741 if force or self.get(attr) is None: | |
| 742 self[attr] = value | |
| 743 | |
| 744 def copy_attr_convert(self, attr, value, replace = True): | |
| 745 """ | |
| 746 If attr is an attribute of self, set self[attr] to | |
| 747 [self[attr], value], otherwise set self[attr] to value. | |
| 748 | |
| 749 NOTE: replace is not used by this function and is kept only for | |
| 750 compatibility with the other copy functions. | |
| 751 """ | |
| 752 if self.get(attr) is not value: | |
| 753 self.coerce_append_attr_list(attr, value) | |
| 754 | |
| 755 def copy_attr_coerce(self, attr, value, replace): | |
| 756 """ | |
| 757 If attr is an attribute of self and either self[attr] or value is a | |
| 758 list, convert all non-sequence values to a sequence of 1 element and | |
| 759 then concatenate the two sequence, setting the result to self[attr]. | |
| 760 If both self[attr] and value are non-sequences and replace is True or | |
| 761 self[attr] is None, replace self[attr] with value. Otherwise, do | |
| 762 nothing. | |
| 763 """ | |
| 764 if self.get(attr) is not value: | |
| 765 if isinstance(self.get(attr), list) or \ | |
| 766 isinstance(value, list): | |
| 767 self.coerce_append_attr_list(attr, value) | |
| 768 else: | |
| 769 self.replace_attr(attr, value, replace) | |
| 770 | |
| 771 def copy_attr_concatenate(self, attr, value, replace): | |
| 772 """ | |
| 773 If attr is an attribute of self and both self[attr] and value are | |
| 774 lists, concatenate the two sequences, setting the result to | |
| 775 self[attr]. If either self[attr] or value are non-sequences and | |
| 776 replace is True or self[attr] is None, replace self[attr] with value. | |
| 777 Otherwise, do nothing. | |
| 778 """ | |
| 779 if self.get(attr) is not value: | |
| 780 if isinstance(self.get(attr), list) and \ | |
| 781 isinstance(value, list): | |
| 782 self.append_attr_list(attr, value) | |
| 783 else: | |
| 784 self.replace_attr(attr, value, replace) | |
| 785 | |
| 786 def copy_attr_consistent(self, attr, value, replace): | |
| 787 """ | |
| 788 If replace is True or selfpattr] is None, replace self[attr] with | |
| 789 value. Otherwise, do nothing. | |
| 790 """ | |
| 791 if self.get(attr) is not value: | |
| 792 self.replace_attr(attr, value, replace) | |
| 793 | |
| 794 def update_all_atts(self, dict_, update_fun = copy_attr_consistent, | |
| 795 replace = True, and_source = False): | |
| 796 """ | |
| 797 Updates all attributes from node or dictionary `dict_`. | |
| 798 | |
| 799 Appends the basic attributes ('ids', 'names', 'classes', | |
| 800 'dupnames', but not 'source') and then, for all other attributes in | |
| 801 dict_, updates the same attribute in self. When attributes with the | |
| 802 same identifier appear in both self and dict_, the two values are | |
| 803 merged based on the value of update_fun. Generally, when replace is | |
| 804 True, the values in self are replaced or merged with the values in | |
| 805 dict_; otherwise, the values in self may be preserved or merged. When | |
| 806 and_source is True, the 'source' attribute is included in the copy. | |
| 807 | |
| 808 NOTE: When replace is False, and self contains a 'source' attribute, | |
| 809 'source' is not replaced even when dict_ has a 'source' | |
| 810 attribute, though it may still be merged into a list depending | |
| 811 on the value of update_fun. | |
| 812 NOTE: It is easier to call the update-specific methods then to pass | |
| 813 the update_fun method to this function. | |
| 814 """ | |
| 815 if isinstance(dict_, Node): | |
| 816 dict_ = dict_.attributes | |
| 817 | |
| 818 # Include the source attribute when copying? | |
| 819 if and_source: | |
| 820 filter_fun = self.is_not_list_attribute | |
| 821 else: | |
| 822 filter_fun = self.is_not_known_attribute | |
| 823 | |
| 824 # Copy the basic attributes | |
| 825 self.update_basic_atts(dict_) | |
| 826 | |
| 827 # Grab other attributes in dict_ not in self except the | |
| 828 # (All basic attributes should be copied already) | |
| 829 for att in filter(filter_fun, dict_): | |
| 830 update_fun(self, att, dict_[att], replace) | |
| 831 | |
| 832 def update_all_atts_consistantly(self, dict_, replace = True, | |
| 833 and_source = False): | |
| 834 """ | |
| 835 Updates all attributes from node or dictionary `dict_`. | |
| 836 | |
| 837 Appends the basic attributes ('ids', 'names', 'classes', | |
| 838 'dupnames', but not 'source') and then, for all other attributes in | |
| 839 dict_, updates the same attribute in self. When attributes with the | |
| 840 same identifier appear in both self and dict_ and replace is True, the | |
| 841 values in self are replaced with the values in dict_; otherwise, the | |
| 842 values in self are preserved. When and_source is True, the 'source' | |
| 843 attribute is included in the copy. | |
| 844 | |
| 845 NOTE: When replace is False, and self contains a 'source' attribute, | |
| 846 'source' is not replaced even when dict_ has a 'source' | |
| 847 attribute, though it may still be merged into a list depending | |
| 848 on the value of update_fun. | |
| 849 """ | |
| 850 self.update_all_atts(dict_, Element.copy_attr_consistent, replace, | |
| 851 and_source) | |
| 852 | |
| 853 def update_all_atts_concatenating(self, dict_, replace = True, | |
| 854 and_source = False): | |
| 855 """ | |
| 856 Updates all attributes from node or dictionary `dict_`. | |
| 857 | |
| 858 Appends the basic attributes ('ids', 'names', 'classes', | |
| 859 'dupnames', but not 'source') and then, for all other attributes in | |
| 860 dict_, updates the same attribute in self. When attributes with the | |
| 861 same identifier appear in both self and dict_ whose values aren't each | |
| 862 lists and replace is True, the values in self are replaced with the | |
| 863 values in dict_; if the values from self and dict_ for the given | |
| 864 identifier are both of list type, then the two lists are concatenated | |
| 865 and the result stored in self; otherwise, the values in self are | |
| 866 preserved. When and_source is True, the 'source' attribute is | |
| 867 included in the copy. | |
| 868 | |
| 869 NOTE: When replace is False, and self contains a 'source' attribute, | |
| 870 'source' is not replaced even when dict_ has a 'source' | |
| 871 attribute, though it may still be merged into a list depending | |
| 872 on the value of update_fun. | |
| 873 """ | |
| 874 self.update_all_atts(dict_, Element.copy_attr_concatenate, replace, | |
| 875 and_source) | |
| 876 | |
| 877 def update_all_atts_coercion(self, dict_, replace = True, | |
| 878 and_source = False): | |
| 879 """ | |
| 880 Updates all attributes from node or dictionary `dict_`. | |
| 881 | |
| 882 Appends the basic attributes ('ids', 'names', 'classes', | |
| 883 'dupnames', but not 'source') and then, for all other attributes in | |
| 884 dict_, updates the same attribute in self. When attributes with the | |
| 885 same identifier appear in both self and dict_ whose values are both | |
| 886 not lists and replace is True, the values in self are replaced with | |
| 887 the values in dict_; if either of the values from self and dict_ for | |
| 888 the given identifier are of list type, then first any non-lists are | |
| 889 converted to 1-element lists and then the two lists are concatenated | |
| 890 and the result stored in self; otherwise, the values in self are | |
| 891 preserved. When and_source is True, the 'source' attribute is | |
| 892 included in the copy. | |
| 893 | |
| 894 NOTE: When replace is False, and self contains a 'source' attribute, | |
| 895 'source' is not replaced even when dict_ has a 'source' | |
| 896 attribute, though it may still be merged into a list depending | |
| 897 on the value of update_fun. | |
| 898 """ | |
| 899 self.update_all_atts(dict_, Element.copy_attr_coerce, replace, | |
| 900 and_source) | |
| 901 | |
| 902 def update_all_atts_convert(self, dict_, and_source = False): | |
| 903 """ | |
| 904 Updates all attributes from node or dictionary `dict_`. | |
| 905 | |
| 906 Appends the basic attributes ('ids', 'names', 'classes', | |
| 907 'dupnames', but not 'source') and then, for all other attributes in | |
| 908 dict_, updates the same attribute in self. When attributes with the | |
| 909 same identifier appear in both self and dict_ then first any non-lists | |
| 910 are converted to 1-element lists and then the two lists are | |
| 911 concatenated and the result stored in self; otherwise, the values in | |
| 912 self are preserved. When and_source is True, the 'source' attribute | |
| 913 is included in the copy. | |
| 914 | |
| 915 NOTE: When replace is False, and self contains a 'source' attribute, | |
| 916 'source' is not replaced even when dict_ has a 'source' | |
| 917 attribute, though it may still be merged into a list depending | |
| 918 on the value of update_fun. | |
| 919 """ | |
| 920 self.update_all_atts(dict_, Element.copy_attr_convert, | |
| 921 and_source = and_source) | |
| 922 | |
| 923 def clear(self): | |
| 924 self.children = [] | |
| 925 | |
| 926 def replace(self, old, new): | |
| 927 """Replace one child `Node` with another child or children.""" | |
| 928 index = self.index(old) | |
| 929 if isinstance(new, Node): | |
| 930 self.setup_child(new) | |
| 931 self[index] = new | |
| 932 elif new is not None: | |
| 933 self[index:index+1] = new | |
| 934 | |
| 935 def replace_self(self, new): | |
| 936 """ | |
| 937 Replace `self` node with `new`, where `new` is a node or a | |
| 938 list of nodes. | |
| 939 """ | |
| 940 update = new | |
| 941 if not isinstance(new, Node): | |
| 942 # `new` is a list; update first child. | |
| 943 try: | |
| 944 update = new[0] | |
| 945 except IndexError: | |
| 946 update = None | |
| 947 if isinstance(update, Element): | |
| 948 update.update_basic_atts(self) | |
| 949 else: | |
| 950 # `update` is a Text node or `new` is an empty list. | |
| 951 # Assert that we aren't losing any attributes. | |
| 952 for att in self.basic_attributes: | |
| 953 assert not self[att], \ | |
| 954 'Losing "%s" attribute: %s' % (att, self[att]) | |
| 955 self.parent.replace(self, new) | |
| 956 | |
| 957 def first_child_matching_class(self, childclass, start=0, end=sys.maxint): | |
| 958 """ | |
| 959 Return the index of the first child whose class exactly matches. | |
| 960 | |
| 961 Parameters: | |
| 962 | |
| 963 - `childclass`: A `Node` subclass to search for, or a tuple of `Node` | |
| 964 classes. If a tuple, any of the classes may match. | |
| 965 - `start`: Initial index to check. | |
| 966 - `end`: Initial index to *not* check. | |
| 967 """ | |
| 968 if not isinstance(childclass, tuple): | |
| 969 childclass = (childclass,) | |
| 970 for index in range(start, min(len(self), end)): | |
| 971 for c in childclass: | |
| 972 if isinstance(self[index], c): | |
| 973 return index | |
| 974 return None | |
| 975 | |
| 976 def first_child_not_matching_class(self, childclass, start=0, | |
| 977 end=sys.maxint): | |
| 978 """ | |
| 979 Return the index of the first child whose class does *not* match. | |
| 980 | |
| 981 Parameters: | |
| 982 | |
| 983 - `childclass`: A `Node` subclass to skip, or a tuple of `Node` | |
| 984 classes. If a tuple, none of the classes may match. | |
| 985 - `start`: Initial index to check. | |
| 986 - `end`: Initial index to *not* check. | |
| 987 """ | |
| 988 if not isinstance(childclass, tuple): | |
| 989 childclass = (childclass,) | |
| 990 for index in range(start, min(len(self), end)): | |
| 991 for c in childclass: | |
| 992 if isinstance(self.children[index], c): | |
| 993 break | |
| 994 else: | |
| 995 return index | |
| 996 return None | |
| 997 | |
| 998 def pformat(self, indent=' ', level=0): | |
| 999 return ''.join(['%s%s\n' % (indent * level, self.starttag())] + | |
| 1000 [child.pformat(indent, level+1) | |
| 1001 for child in self.children]) | |
| 1002 | |
| 1003 def copy(self): | |
| 1004 return self.__class__(rawsource=self.rawsource, **self.attributes) | |
| 1005 | |
| 1006 def deepcopy(self): | |
| 1007 copy = self.copy() | |
| 1008 copy.extend([child.deepcopy() for child in self.children]) | |
| 1009 return copy | |
| 1010 | |
| 1011 def set_class(self, name): | |
| 1012 """Add a new class to the "classes" attribute.""" | |
| 1013 warnings.warn('docutils.nodes.Element.set_class deprecated; ' | |
| 1014 "append to Element['classes'] list attribute directly", | |
| 1015 DeprecationWarning, stacklevel=2) | |
| 1016 assert ' ' not in name | |
| 1017 self['classes'].append(name.lower()) | |
| 1018 | |
| 1019 def note_referenced_by(self, name=None, id=None): | |
| 1020 """Note that this Element has been referenced by its name | |
| 1021 `name` or id `id`.""" | |
| 1022 self.referenced = 1 | |
| 1023 # Element.expect_referenced_by_* dictionaries map names or ids | |
| 1024 # to nodes whose ``referenced`` attribute is set to true as | |
| 1025 # soon as this node is referenced by the given name or id. | |
| 1026 # Needed for target propagation. | |
| 1027 by_name = getattr(self, 'expect_referenced_by_name', {}).get(name) | |
| 1028 by_id = getattr(self, 'expect_referenced_by_id', {}).get(id) | |
| 1029 if by_name: | |
| 1030 assert name is not None | |
| 1031 by_name.referenced = 1 | |
| 1032 if by_id: | |
| 1033 assert id is not None | |
| 1034 by_id.referenced = 1 | |
| 1035 | |
| 1036 @classmethod | |
| 1037 def is_not_list_attribute(cls, attr): | |
| 1038 """ | |
| 1039 Returns True if and only if the given attribute is NOT one of the | |
| 1040 basic list attributes defined for all Elements. | |
| 1041 """ | |
| 1042 return attr not in cls.list_attributes | |
| 1043 | |
| 1044 @classmethod | |
| 1045 def is_not_known_attribute(cls, attr): | |
| 1046 """ | |
| 1047 Returns True if and only if the given attribute is NOT recognized by | |
| 1048 this class. | |
| 1049 """ | |
| 1050 return attr not in cls.known_attributes | |
| 1051 | |
| 1052 | |
| 1053 class TextElement(Element): | |
| 1054 | |
| 1055 """ | |
| 1056 An element which directly contains text. | |
| 1057 | |
| 1058 Its children are all `Text` or `Inline` subclass nodes. You can | |
| 1059 check whether an element's context is inline simply by checking whether | |
| 1060 its immediate parent is a `TextElement` instance (including subclasses). | |
| 1061 This is handy for nodes like `image` that can appear both inline and as | |
| 1062 standalone body elements. | |
| 1063 | |
| 1064 If passing children to `__init__()`, make sure to set `text` to | |
| 1065 ``''`` or some other suitable value. | |
| 1066 """ | |
| 1067 | |
| 1068 child_text_separator = '' | |
| 1069 """Separator for child nodes, used by `astext()` method.""" | |
| 1070 | |
| 1071 def __init__(self, rawsource='', text='', *children, **attributes): | |
| 1072 if text != '': | |
| 1073 textnode = Text(text) | |
| 1074 Element.__init__(self, rawsource, textnode, *children, | |
| 1075 **attributes) | |
| 1076 else: | |
| 1077 Element.__init__(self, rawsource, *children, **attributes) | |
| 1078 | |
| 1079 | |
| 1080 class FixedTextElement(TextElement): | |
| 1081 | |
| 1082 """An element which directly contains preformatted text.""" | |
| 1083 | |
| 1084 def __init__(self, rawsource='', text='', *children, **attributes): | |
| 1085 TextElement.__init__(self, rawsource, text, *children, **attributes) | |
| 1086 self.attributes['xml:space'] = 'preserve' | |
| 1087 | |
| 1088 | |
| 1089 # ======== | |
| 1090 # Mixins | |
| 1091 # ======== | |
| 1092 | |
| 1093 class Resolvable: | |
| 1094 | |
| 1095 resolved = 0 | |
| 1096 | |
| 1097 | |
| 1098 class BackLinkable: | |
| 1099 | |
| 1100 def add_backref(self, refid): | |
| 1101 self['backrefs'].append(refid) | |
| 1102 | |
| 1103 | |
| 1104 # ==================== | |
| 1105 # Element Categories | |
| 1106 # ==================== | |
| 1107 | |
| 1108 class Root: pass | |
| 1109 | |
| 1110 class Titular: pass | |
| 1111 | |
| 1112 class PreBibliographic: | |
| 1113 """Category of Node which may occur before Bibliographic Nodes.""" | |
| 1114 | |
| 1115 class Bibliographic: pass | |
| 1116 | |
| 1117 class Decorative(PreBibliographic): pass | |
| 1118 | |
| 1119 class Structural: pass | |
| 1120 | |
| 1121 class Body: pass | |
| 1122 | |
| 1123 class General(Body): pass | |
| 1124 | |
| 1125 class Sequential(Body): | |
| 1126 """List-like elements.""" | |
| 1127 | |
| 1128 class Admonition(Body): pass | |
| 1129 | |
| 1130 class Special(Body): | |
| 1131 """Special internal body elements.""" | |
| 1132 | |
| 1133 class Invisible(PreBibliographic): | |
| 1134 """Internal elements that don't appear in output.""" | |
| 1135 | |
| 1136 class Part: pass | |
| 1137 | |
| 1138 class Inline: pass | |
| 1139 | |
| 1140 class Referential(Resolvable): pass | |
| 1141 | |
| 1142 | |
| 1143 class Targetable(Resolvable): | |
| 1144 | |
| 1145 referenced = 0 | |
| 1146 | |
| 1147 indirect_reference_name = None | |
| 1148 """Holds the whitespace_normalized_name (contains mixed case) of a target. | |
| 1149 Required for MoinMoin/reST compatibility.""" | |
| 1150 | |
| 1151 | |
| 1152 class Labeled: | |
| 1153 """Contains a `label` as its first element.""" | |
| 1154 | |
| 1155 | |
| 1156 # ============== | |
| 1157 # Root Element | |
| 1158 # ============== | |
| 1159 | |
| 1160 class document(Root, Structural, Element): | |
| 1161 | |
| 1162 """ | |
| 1163 The document root element. | |
| 1164 | |
| 1165 Do not instantiate this class directly; use | |
| 1166 `docutils.utils.new_document()` instead. | |
| 1167 """ | |
| 1168 | |
| 1169 def __init__(self, settings, reporter, *args, **kwargs): | |
| 1170 Element.__init__(self, *args, **kwargs) | |
| 1171 | |
| 1172 self.current_source = None | |
| 1173 """Path to or description of the input source being processed.""" | |
| 1174 | |
| 1175 self.current_line = None | |
| 1176 """Line number (1-based) of `current_source`.""" | |
| 1177 | |
| 1178 self.settings = settings | |
| 1179 """Runtime settings data record.""" | |
| 1180 | |
| 1181 self.reporter = reporter | |
| 1182 """System message generator.""" | |
| 1183 | |
| 1184 self.indirect_targets = [] | |
| 1185 """List of indirect target nodes.""" | |
| 1186 | |
| 1187 self.substitution_defs = {} | |
| 1188 """Mapping of substitution names to substitution_definition nodes.""" | |
| 1189 | |
| 1190 self.substitution_names = {} | |
| 1191 """Mapping of case-normalized substitution names to case-sensitive | |
| 1192 names.""" | |
| 1193 | |
| 1194 self.refnames = {} | |
| 1195 """Mapping of names to lists of referencing nodes.""" | |
| 1196 | |
| 1197 self.refids = {} | |
| 1198 """Mapping of ids to lists of referencing nodes.""" | |
| 1199 | |
| 1200 self.nameids = {} | |
| 1201 """Mapping of names to unique id's.""" | |
| 1202 | |
| 1203 self.nametypes = {} | |
| 1204 """Mapping of names to hyperlink type (boolean: True => explicit, | |
| 1205 False => implicit.""" | |
| 1206 | |
| 1207 self.ids = {} | |
| 1208 """Mapping of ids to nodes.""" | |
| 1209 | |
| 1210 self.footnote_refs = {} | |
| 1211 """Mapping of footnote labels to lists of footnote_reference nodes.""" | |
| 1212 | |
| 1213 self.citation_refs = {} | |
| 1214 """Mapping of citation labels to lists of citation_reference nodes.""" | |
| 1215 | |
| 1216 self.autofootnotes = [] | |
| 1217 """List of auto-numbered footnote nodes.""" | |
| 1218 | |
| 1219 self.autofootnote_refs = [] | |
| 1220 """List of auto-numbered footnote_reference nodes.""" | |
| 1221 | |
| 1222 self.symbol_footnotes = [] | |
| 1223 """List of symbol footnote nodes.""" | |
| 1224 | |
| 1225 self.symbol_footnote_refs = [] | |
| 1226 """List of symbol footnote_reference nodes.""" | |
| 1227 | |
| 1228 self.footnotes = [] | |
| 1229 """List of manually-numbered footnote nodes.""" | |
| 1230 | |
| 1231 self.citations = [] | |
| 1232 """List of citation nodes.""" | |
| 1233 | |
| 1234 self.autofootnote_start = 1 | |
| 1235 """Initial auto-numbered footnote number.""" | |
| 1236 | |
| 1237 self.symbol_footnote_start = 0 | |
| 1238 """Initial symbol footnote symbol index.""" | |
| 1239 | |
| 1240 self.id_start = 1 | |
| 1241 """Initial ID number.""" | |
| 1242 | |
| 1243 self.parse_messages = [] | |
| 1244 """System messages generated while parsing.""" | |
| 1245 | |
| 1246 self.transform_messages = [] | |
| 1247 """System messages generated while applying transforms.""" | |
| 1248 | |
| 1249 import docutils.transforms | |
| 1250 self.transformer = docutils.transforms.Transformer(self) | |
| 1251 """Storage for transforms to be applied to this document.""" | |
| 1252 | |
| 1253 self.decoration = None | |
| 1254 """Document's `decoration` node.""" | |
| 1255 | |
| 1256 self.document = self | |
| 1257 | |
| 1258 def __getstate__(self): | |
| 1259 """ | |
| 1260 Return dict with unpicklable references removed. | |
| 1261 """ | |
| 1262 state = self.__dict__.copy() | |
| 1263 state['reporter'] = None | |
| 1264 state['transformer'] = None | |
| 1265 return state | |
| 1266 | |
| 1267 def asdom(self, dom=None): | |
| 1268 """Return a DOM representation of this document.""" | |
| 1269 if dom is None: | |
| 1270 import xml.dom.minidom as dom | |
| 1271 domroot = dom.Document() | |
| 1272 domroot.appendChild(self._dom_node(domroot)) | |
| 1273 return domroot | |
| 1274 | |
| 1275 def set_id(self, node, msgnode=None): | |
| 1276 for id in node['ids']: | |
| 1277 if id in self.ids and self.ids[id] is not node: | |
| 1278 msg = self.reporter.severe('Duplicate ID: "%s".' % id) | |
| 1279 if msgnode != None: | |
| 1280 msgnode += msg | |
| 1281 if not node['ids']: | |
| 1282 for name in node['names']: | |
| 1283 id = self.settings.id_prefix + make_id(name) | |
| 1284 if id and id not in self.ids: | |
| 1285 break | |
| 1286 else: | |
| 1287 id = '' | |
| 1288 while not id or id in self.ids: | |
| 1289 id = (self.settings.id_prefix + | |
| 1290 self.settings.auto_id_prefix + str(self.id_start)) | |
| 1291 self.id_start += 1 | |
| 1292 node['ids'].append(id) | |
| 1293 self.ids[id] = node | |
| 1294 return id | |
| 1295 | |
| 1296 def set_name_id_map(self, node, id, msgnode=None, explicit=None): | |
| 1297 """ | |
| 1298 `self.nameids` maps names to IDs, while `self.nametypes` maps names to | |
| 1299 booleans representing hyperlink type (True==explicit, | |
| 1300 False==implicit). This method updates the mappings. | |
| 1301 | |
| 1302 The following state transition table shows how `self.nameids` ("ids") | |
| 1303 and `self.nametypes` ("types") change with new input (a call to this | |
| 1304 method), and what actions are performed ("implicit"-type system | |
| 1305 messages are INFO/1, and "explicit"-type system messages are ERROR/3): | |
| 1306 | |
| 1307 ==== ===== ======== ======== ======= ==== ===== ===== | |
| 1308 Old State Input Action New State Notes | |
| 1309 ----------- -------- ----------------- ----------- ----- | |
| 1310 ids types new type sys.msg. dupname ids types | |
| 1311 ==== ===== ======== ======== ======= ==== ===== ===== | |
| 1312 - - explicit - - new True | |
| 1313 - - implicit - - new False | |
| 1314 None False explicit - - new True | |
| 1315 old False explicit implicit old new True | |
| 1316 None True explicit explicit new None True | |
| 1317 old True explicit explicit new,old None True [#]_ | |
| 1318 None False implicit implicit new None False | |
| 1319 old False implicit implicit new,old None False | |
| 1320 None True implicit implicit new None True | |
| 1321 old True implicit implicit new old True | |
| 1322 ==== ===== ======== ======== ======= ==== ===== ===== | |
| 1323 | |
| 1324 .. [#] Do not clear the name-to-id map or invalidate the old target if | |
| 1325 both old and new targets are external and refer to identical URIs. | |
| 1326 The new target is invalidated regardless. | |
| 1327 """ | |
| 1328 for name in node['names']: | |
| 1329 if name in self.nameids: | |
| 1330 self.set_duplicate_name_id(node, id, name, msgnode, explicit) | |
| 1331 else: | |
| 1332 self.nameids[name] = id | |
| 1333 self.nametypes[name] = explicit | |
| 1334 | |
| 1335 def set_duplicate_name_id(self, node, id, name, msgnode, explicit): | |
| 1336 old_id = self.nameids[name] | |
| 1337 old_explicit = self.nametypes[name] | |
| 1338 self.nametypes[name] = old_explicit or explicit | |
| 1339 if explicit: | |
| 1340 if old_explicit: | |
| 1341 level = 2 | |
| 1342 if old_id is not None: | |
| 1343 old_node = self.ids[old_id] | |
| 1344 if 'refuri' in node: | |
| 1345 refuri = node['refuri'] | |
| 1346 if old_node['names'] \ | |
| 1347 and 'refuri' in old_node \ | |
| 1348 and old_node['refuri'] == refuri: | |
| 1349 level = 1 # just inform if refuri's identical | |
| 1350 if level > 1: | |
| 1351 dupname(old_node, name) | |
| 1352 self.nameids[name] = None | |
| 1353 msg = self.reporter.system_message( | |
| 1354 level, 'Duplicate explicit target name: "%s".' % name, | |
| 1355 backrefs=[id], base_node=node) | |
| 1356 if msgnode != None: | |
| 1357 msgnode += msg | |
| 1358 dupname(node, name) | |
| 1359 else: | |
| 1360 self.nameids[name] = id | |
| 1361 if old_id is not None: | |
| 1362 old_node = self.ids[old_id] | |
| 1363 dupname(old_node, name) | |
| 1364 else: | |
| 1365 if old_id is not None and not old_explicit: | |
| 1366 self.nameids[name] = None | |
| 1367 old_node = self.ids[old_id] | |
| 1368 dupname(old_node, name) | |
| 1369 dupname(node, name) | |
| 1370 if not explicit or (not old_explicit and old_id is not None): | |
| 1371 msg = self.reporter.info( | |
| 1372 'Duplicate implicit target name: "%s".' % name, | |
| 1373 backrefs=[id], base_node=node) | |
| 1374 if msgnode != None: | |
| 1375 msgnode += msg | |
| 1376 | |
| 1377 def has_name(self, name): | |
| 1378 return name in self.nameids | |
| 1379 | |
| 1380 # "note" here is an imperative verb: "take note of". | |
| 1381 def note_implicit_target(self, target, msgnode=None): | |
| 1382 id = self.set_id(target, msgnode) | |
| 1383 self.set_name_id_map(target, id, msgnode, explicit=None) | |
| 1384 | |
| 1385 def note_explicit_target(self, target, msgnode=None): | |
| 1386 id = self.set_id(target, msgnode) | |
| 1387 self.set_name_id_map(target, id, msgnode, explicit=True) | |
| 1388 | |
| 1389 def note_refname(self, node): | |
| 1390 self.refnames.setdefault(node['refname'], []).append(node) | |
| 1391 | |
| 1392 def note_refid(self, node): | |
| 1393 self.refids.setdefault(node['refid'], []).append(node) | |
| 1394 | |
| 1395 def note_indirect_target(self, target): | |
| 1396 self.indirect_targets.append(target) | |
| 1397 if target['names']: | |
| 1398 self.note_refname(target) | |
| 1399 | |
| 1400 def note_anonymous_target(self, target): | |
| 1401 self.set_id(target) | |
| 1402 | |
| 1403 def note_autofootnote(self, footnote): | |
| 1404 self.set_id(footnote) | |
| 1405 self.autofootnotes.append(footnote) | |
| 1406 | |
| 1407 def note_autofootnote_ref(self, ref): | |
| 1408 self.set_id(ref) | |
| 1409 self.autofootnote_refs.append(ref) | |
| 1410 | |
| 1411 def note_symbol_footnote(self, footnote): | |
| 1412 self.set_id(footnote) | |
| 1413 self.symbol_footnotes.append(footnote) | |
| 1414 | |
| 1415 def note_symbol_footnote_ref(self, ref): | |
| 1416 self.set_id(ref) | |
| 1417 self.symbol_footnote_refs.append(ref) | |
| 1418 | |
| 1419 def note_footnote(self, footnote): | |
| 1420 self.set_id(footnote) | |
| 1421 self.footnotes.append(footnote) | |
| 1422 | |
| 1423 def note_footnote_ref(self, ref): | |
| 1424 self.set_id(ref) | |
| 1425 self.footnote_refs.setdefault(ref['refname'], []).append(ref) | |
| 1426 self.note_refname(ref) | |
| 1427 | |
| 1428 def note_citation(self, citation): | |
| 1429 self.citations.append(citation) | |
| 1430 | |
| 1431 def note_citation_ref(self, ref): | |
| 1432 self.set_id(ref) | |
| 1433 self.citation_refs.setdefault(ref['refname'], []).append(ref) | |
| 1434 self.note_refname(ref) | |
| 1435 | |
| 1436 def note_substitution_def(self, subdef, def_name, msgnode=None): | |
| 1437 name = whitespace_normalize_name(def_name) | |
| 1438 if name in self.substitution_defs: | |
| 1439 msg = self.reporter.error( | |
| 1440 'Duplicate substitution definition name: "%s".' % name, | |
| 1441 base_node=subdef) | |
| 1442 if msgnode != None: | |
| 1443 msgnode += msg | |
| 1444 oldnode = self.substitution_defs[name] | |
| 1445 dupname(oldnode, name) | |
| 1446 # keep only the last definition: | |
| 1447 self.substitution_defs[name] = subdef | |
| 1448 # case-insensitive mapping: | |
| 1449 self.substitution_names[fully_normalize_name(name)] = name | |
| 1450 | |
| 1451 def note_substitution_ref(self, subref, refname): | |
| 1452 subref['refname'] = whitespace_normalize_name(refname) | |
| 1453 | |
| 1454 def note_pending(self, pending, priority=None): | |
| 1455 self.transformer.add_pending(pending, priority) | |
| 1456 | |
| 1457 def note_parse_message(self, message): | |
| 1458 self.parse_messages.append(message) | |
| 1459 | |
| 1460 def note_transform_message(self, message): | |
| 1461 self.transform_messages.append(message) | |
| 1462 | |
| 1463 def note_source(self, source, offset): | |
| 1464 self.current_source = source | |
| 1465 if offset is None: | |
| 1466 self.current_line = offset | |
| 1467 else: | |
| 1468 self.current_line = offset + 1 | |
| 1469 | |
| 1470 def copy(self): | |
| 1471 return self.__class__(self.settings, self.reporter, | |
| 1472 **self.attributes) | |
| 1473 | |
| 1474 def get_decoration(self): | |
| 1475 if not self.decoration: | |
| 1476 self.decoration = decoration() | |
| 1477 index = self.first_child_not_matching_class(Titular) | |
| 1478 if index is None: | |
| 1479 self.append(self.decoration) | |
| 1480 else: | |
| 1481 self.insert(index, self.decoration) | |
| 1482 return self.decoration | |
| 1483 | |
| 1484 | |
| 1485 # ================ | |
| 1486 # Title Elements | |
| 1487 # ================ | |
| 1488 | |
| 1489 class title(Titular, PreBibliographic, TextElement): pass | |
| 1490 class subtitle(Titular, PreBibliographic, TextElement): pass | |
| 1491 class rubric(Titular, TextElement): pass | |
| 1492 | |
| 1493 | |
| 1494 # ======================== | |
| 1495 # Bibliographic Elements | |
| 1496 # ======================== | |
| 1497 | |
| 1498 class docinfo(Bibliographic, Element): pass | |
| 1499 class author(Bibliographic, TextElement): pass | |
| 1500 class authors(Bibliographic, Element): pass | |
| 1501 class organization(Bibliographic, TextElement): pass | |
| 1502 class address(Bibliographic, FixedTextElement): pass | |
| 1503 class contact(Bibliographic, TextElement): pass | |
| 1504 class version(Bibliographic, TextElement): pass | |
| 1505 class revision(Bibliographic, TextElement): pass | |
| 1506 class status(Bibliographic, TextElement): pass | |
| 1507 class date(Bibliographic, TextElement): pass | |
| 1508 class copyright(Bibliographic, TextElement): pass | |
| 1509 | |
| 1510 | |
| 1511 # ===================== | |
| 1512 # Decorative Elements | |
| 1513 # ===================== | |
| 1514 | |
| 1515 class decoration(Decorative, Element): | |
| 1516 | |
| 1517 def get_header(self): | |
| 1518 if not len(self.children) or not isinstance(self.children[0], header): | |
| 1519 self.insert(0, header()) | |
| 1520 return self.children[0] | |
| 1521 | |
| 1522 def get_footer(self): | |
| 1523 if not len(self.children) or not isinstance(self.children[-1], footer): | |
| 1524 self.append(footer()) | |
| 1525 return self.children[-1] | |
| 1526 | |
| 1527 | |
| 1528 class header(Decorative, Element): pass | |
| 1529 class footer(Decorative, Element): pass | |
| 1530 | |
| 1531 | |
| 1532 # ===================== | |
| 1533 # Structural Elements | |
| 1534 # ===================== | |
| 1535 | |
| 1536 class section(Structural, Element): pass | |
| 1537 | |
| 1538 | |
| 1539 class topic(Structural, Element): | |
| 1540 | |
| 1541 """ | |
| 1542 Topics are terminal, "leaf" mini-sections, like block quotes with titles, | |
| 1543 or textual figures. A topic is just like a section, except that it has no | |
| 1544 subsections, and it doesn't have to conform to section placement rules. | |
| 1545 | |
| 1546 Topics are allowed wherever body elements (list, table, etc.) are allowed, | |
| 1547 but only at the top level of a section or document. Topics cannot nest | |
| 1548 inside topics, sidebars, or body elements; you can't have a topic inside a | |
| 1549 table, list, block quote, etc. | |
| 1550 """ | |
| 1551 | |
| 1552 | |
| 1553 class sidebar(Structural, Element): | |
| 1554 | |
| 1555 """ | |
| 1556 Sidebars are like miniature, parallel documents that occur inside other | |
| 1557 documents, providing related or reference material. A sidebar is | |
| 1558 typically offset by a border and "floats" to the side of the page; the | |
| 1559 document's main text may flow around it. Sidebars can also be likened to | |
| 1560 super-footnotes; their content is outside of the flow of the document's | |
| 1561 main text. | |
| 1562 | |
| 1563 Sidebars are allowed wherever body elements (list, table, etc.) are | |
| 1564 allowed, but only at the top level of a section or document. Sidebars | |
| 1565 cannot nest inside sidebars, topics, or body elements; you can't have a | |
| 1566 sidebar inside a table, list, block quote, etc. | |
| 1567 """ | |
| 1568 | |
| 1569 | |
| 1570 class transition(Structural, Element): pass | |
| 1571 | |
| 1572 | |
| 1573 # =============== | |
| 1574 # Body Elements | |
| 1575 # =============== | |
| 1576 | |
| 1577 class paragraph(General, TextElement): pass | |
| 1578 class compound(General, Element): pass | |
| 1579 class container(General, Element): pass | |
| 1580 class bullet_list(Sequential, Element): pass | |
| 1581 class enumerated_list(Sequential, Element): pass | |
| 1582 class list_item(Part, Element): pass | |
| 1583 class definition_list(Sequential, Element): pass | |
| 1584 class definition_list_item(Part, Element): pass | |
| 1585 class term(Part, TextElement): pass | |
| 1586 class classifier(Part, TextElement): pass | |
| 1587 class definition(Part, Element): pass | |
| 1588 class field_list(Sequential, Element): pass | |
| 1589 class field(Part, Element): pass | |
| 1590 class field_name(Part, TextElement): pass | |
| 1591 class field_body(Part, Element): pass | |
| 1592 | |
| 1593 | |
| 1594 class option(Part, Element): | |
| 1595 | |
| 1596 child_text_separator = '' | |
| 1597 | |
| 1598 | |
| 1599 class option_argument(Part, TextElement): | |
| 1600 | |
| 1601 def astext(self): | |
| 1602 return self.get('delimiter', ' ') + TextElement.astext(self) | |
| 1603 | |
| 1604 | |
| 1605 class option_group(Part, Element): | |
| 1606 | |
| 1607 child_text_separator = ', ' | |
| 1608 | |
| 1609 | |
| 1610 class option_list(Sequential, Element): pass | |
| 1611 | |
| 1612 | |
| 1613 class option_list_item(Part, Element): | |
| 1614 | |
| 1615 child_text_separator = ' ' | |
| 1616 | |
| 1617 | |
| 1618 class option_string(Part, TextElement): pass | |
| 1619 class description(Part, Element): pass | |
| 1620 class literal_block(General, FixedTextElement): pass | |
| 1621 class doctest_block(General, FixedTextElement): pass | |
| 1622 class math_block(General, FixedTextElement): pass | |
| 1623 class line_block(General, Element): pass | |
| 1624 | |
| 1625 | |
| 1626 class line(Part, TextElement): | |
| 1627 | |
| 1628 indent = None | |
| 1629 | |
| 1630 | |
| 1631 class block_quote(General, Element): pass | |
| 1632 class attribution(Part, TextElement): pass | |
| 1633 class attention(Admonition, Element): pass | |
| 1634 class caution(Admonition, Element): pass | |
| 1635 class danger(Admonition, Element): pass | |
| 1636 class error(Admonition, Element): pass | |
| 1637 class important(Admonition, Element): pass | |
| 1638 class note(Admonition, Element): pass | |
| 1639 class tip(Admonition, Element): pass | |
| 1640 class hint(Admonition, Element): pass | |
| 1641 class warning(Admonition, Element): pass | |
| 1642 class admonition(Admonition, Element): pass | |
| 1643 class comment(Special, Invisible, FixedTextElement): pass | |
| 1644 class substitution_definition(Special, Invisible, TextElement): pass | |
| 1645 class target(Special, Invisible, Inline, TextElement, Targetable): pass | |
| 1646 class footnote(General, BackLinkable, Element, Labeled, Targetable): pass | |
| 1647 class citation(General, BackLinkable, Element, Labeled, Targetable): pass | |
| 1648 class label(Part, TextElement): pass | |
| 1649 class figure(General, Element): pass | |
| 1650 class caption(Part, TextElement): pass | |
| 1651 class legend(Part, Element): pass | |
| 1652 class table(General, Element): pass | |
| 1653 class tgroup(Part, Element): pass | |
| 1654 class colspec(Part, Element): pass | |
| 1655 class thead(Part, Element): pass | |
| 1656 class tbody(Part, Element): pass | |
| 1657 class row(Part, Element): pass | |
| 1658 class entry(Part, Element): pass | |
| 1659 | |
| 1660 | |
| 1661 class system_message(Special, BackLinkable, PreBibliographic, Element): | |
| 1662 | |
| 1663 """ | |
| 1664 System message element. | |
| 1665 | |
| 1666 Do not instantiate this class directly; use | |
| 1667 ``document.reporter.info/warning/error/severe()`` instead. | |
| 1668 """ | |
| 1669 | |
| 1670 def __init__(self, message=None, *children, **attributes): | |
| 1671 if message: | |
| 1672 p = paragraph('', message) | |
| 1673 children = (p,) + children | |
| 1674 try: | |
| 1675 Element.__init__(self, '', *children, **attributes) | |
| 1676 except: | |
| 1677 print 'system_message: children=%r' % (children,) | |
| 1678 raise | |
| 1679 | |
| 1680 def astext(self): | |
| 1681 line = self.get('line', '') | |
| 1682 return u'%s:%s: (%s/%s) %s' % (self['source'], line, self['type'], | |
| 1683 self['level'], Element.astext(self)) | |
| 1684 | |
| 1685 | |
| 1686 class pending(Special, Invisible, Element): | |
| 1687 | |
| 1688 """ | |
| 1689 The "pending" element is used to encapsulate a pending operation: the | |
| 1690 operation (transform), the point at which to apply it, and any data it | |
| 1691 requires. Only the pending operation's location within the document is | |
| 1692 stored in the public document tree (by the "pending" object itself); the | |
| 1693 operation and its data are stored in the "pending" object's internal | |
| 1694 instance attributes. | |
| 1695 | |
| 1696 For example, say you want a table of contents in your reStructuredText | |
| 1697 document. The easiest way to specify where to put it is from within the | |
| 1698 document, with a directive:: | |
| 1699 | |
| 1700 .. contents:: | |
| 1701 | |
| 1702 But the "contents" directive can't do its work until the entire document | |
| 1703 has been parsed and possibly transformed to some extent. So the directive | |
| 1704 code leaves a placeholder behind that will trigger the second phase of its | |
| 1705 processing, something like this:: | |
| 1706 | |
| 1707 <pending ...public attributes...> + internal attributes | |
| 1708 | |
| 1709 Use `document.note_pending()` so that the | |
| 1710 `docutils.transforms.Transformer` stage of processing can run all pending | |
| 1711 transforms. | |
| 1712 """ | |
| 1713 | |
| 1714 def __init__(self, transform, details=None, | |
| 1715 rawsource='', *children, **attributes): | |
| 1716 Element.__init__(self, rawsource, *children, **attributes) | |
| 1717 | |
| 1718 self.transform = transform | |
| 1719 """The `docutils.transforms.Transform` class implementing the pending | |
| 1720 operation.""" | |
| 1721 | |
| 1722 self.details = details or {} | |
| 1723 """Detail data (dictionary) required by the pending operation.""" | |
| 1724 | |
| 1725 def pformat(self, indent=' ', level=0): | |
| 1726 internals = [ | |
| 1727 '.. internal attributes:', | |
| 1728 ' .transform: %s.%s' % (self.transform.__module__, | |
| 1729 self.transform.__name__), | |
| 1730 ' .details:'] | |
| 1731 details = self.details.items() | |
| 1732 details.sort() | |
| 1733 for key, value in details: | |
| 1734 if isinstance(value, Node): | |
| 1735 internals.append('%7s%s:' % ('', key)) | |
| 1736 internals.extend(['%9s%s' % ('', line) | |
| 1737 for line in value.pformat().splitlines()]) | |
| 1738 elif value and isinstance(value, list) \ | |
| 1739 and isinstance(value[0], Node): | |
| 1740 internals.append('%7s%s:' % ('', key)) | |
| 1741 for v in value: | |
| 1742 internals.extend(['%9s%s' % ('', line) | |
| 1743 for line in v.pformat().splitlines()]) | |
| 1744 else: | |
| 1745 internals.append('%7s%s: %r' % ('', key, value)) | |
| 1746 return (Element.pformat(self, indent, level) | |
| 1747 + ''.join([(' %s%s\n' % (indent * level, line)) | |
| 1748 for line in internals])) | |
| 1749 | |
| 1750 def copy(self): | |
| 1751 return self.__class__(self.transform, self.details, self.rawsource, | |
| 1752 **self.attributes) | |
| 1753 | |
| 1754 | |
| 1755 class raw(Special, Inline, PreBibliographic, FixedTextElement): | |
| 1756 | |
| 1757 """ | |
| 1758 Raw data that is to be passed untouched to the Writer. | |
| 1759 """ | |
| 1760 | |
| 1761 pass | |
| 1762 | |
| 1763 | |
| 1764 # ================= | |
| 1765 # Inline Elements | |
| 1766 # ================= | |
| 1767 | |
| 1768 class emphasis(Inline, TextElement): pass | |
| 1769 class strong(Inline, TextElement): pass | |
| 1770 class literal(Inline, TextElement): pass | |
| 1771 class reference(General, Inline, Referential, TextElement): pass | |
| 1772 class footnote_reference(Inline, Referential, TextElement): pass | |
| 1773 class citation_reference(Inline, Referential, TextElement): pass | |
| 1774 class substitution_reference(Inline, TextElement): pass | |
| 1775 class title_reference(Inline, TextElement): pass | |
| 1776 class abbreviation(Inline, TextElement): pass | |
| 1777 class acronym(Inline, TextElement): pass | |
| 1778 class superscript(Inline, TextElement): pass | |
| 1779 class subscript(Inline, TextElement): pass | |
| 1780 class math(Inline, TextElement): pass | |
| 1781 | |
| 1782 | |
| 1783 class image(General, Inline, Element): | |
| 1784 | |
| 1785 def astext(self): | |
| 1786 return self.get('alt', '') | |
| 1787 | |
| 1788 | |
| 1789 class inline(Inline, TextElement): pass | |
| 1790 class problematic(Inline, TextElement): pass | |
| 1791 class generated(Inline, TextElement): pass | |
| 1792 | |
| 1793 | |
| 1794 # ======================================== | |
| 1795 # Auxiliary Classes, Functions, and Data | |
| 1796 # ======================================== | |
| 1797 | |
| 1798 node_class_names = """ | |
| 1799 Text | |
| 1800 abbreviation acronym address admonition attention attribution author | |
| 1801 authors | |
| 1802 block_quote bullet_list | |
| 1803 caption caution citation citation_reference classifier colspec comment | |
| 1804 compound contact container copyright | |
| 1805 danger date decoration definition definition_list definition_list_item | |
| 1806 description docinfo doctest_block document | |
| 1807 emphasis entry enumerated_list error | |
| 1808 field field_body field_list field_name figure footer | |
| 1809 footnote footnote_reference | |
| 1810 generated | |
| 1811 header hint | |
| 1812 image important inline | |
| 1813 label legend line line_block list_item literal literal_block | |
| 1814 math math_block | |
| 1815 note | |
| 1816 option option_argument option_group option_list option_list_item | |
| 1817 option_string organization | |
| 1818 paragraph pending problematic | |
| 1819 raw reference revision row rubric | |
| 1820 section sidebar status strong subscript substitution_definition | |
| 1821 substitution_reference subtitle superscript system_message | |
| 1822 table target tbody term tgroup thead tip title title_reference topic | |
| 1823 transition | |
| 1824 version | |
| 1825 warning""".split() | |
| 1826 """A list of names of all concrete Node subclasses.""" | |
| 1827 | |
| 1828 | |
| 1829 class NodeVisitor: | |
| 1830 | |
| 1831 """ | |
| 1832 "Visitor" pattern [GoF95]_ abstract superclass implementation for | |
| 1833 document tree traversals. | |
| 1834 | |
| 1835 Each node class has corresponding methods, doing nothing by | |
| 1836 default; override individual methods for specific and useful | |
| 1837 behaviour. The `dispatch_visit()` method is called by | |
| 1838 `Node.walk()` upon entering a node. `Node.walkabout()` also calls | |
| 1839 the `dispatch_departure()` method before exiting a node. | |
| 1840 | |
| 1841 The dispatch methods call "``visit_`` + node class name" or | |
| 1842 "``depart_`` + node class name", resp. | |
| 1843 | |
| 1844 This is a base class for visitors whose ``visit_...`` & ``depart_...`` | |
| 1845 methods should be implemented for *all* node types encountered (such as | |
| 1846 for `docutils.writers.Writer` subclasses). Unimplemented methods will | |
| 1847 raise exceptions. | |
| 1848 | |
| 1849 For sparse traversals, where only certain node types are of interest, | |
| 1850 subclass `SparseNodeVisitor` instead. When (mostly or entirely) uniform | |
| 1851 processing is desired, subclass `GenericNodeVisitor`. | |
| 1852 | |
| 1853 .. [GoF95] Gamma, Helm, Johnson, Vlissides. *Design Patterns: Elements of | |
| 1854 Reusable Object-Oriented Software*. Addison-Wesley, Reading, MA, USA, | |
| 1855 1995. | |
| 1856 """ | |
| 1857 | |
| 1858 optional = () | |
| 1859 """ | |
| 1860 Tuple containing node class names (as strings). | |
| 1861 | |
| 1862 No exception will be raised if writers do not implement visit | |
| 1863 or departure functions for these node classes. | |
| 1864 | |
| 1865 Used to ensure transitional compatibility with existing 3rd-party writers. | |
| 1866 """ | |
| 1867 | |
| 1868 def __init__(self, document): | |
| 1869 self.document = document | |
| 1870 | |
| 1871 def dispatch_visit(self, node): | |
| 1872 """ | |
| 1873 Call self."``visit_`` + node class name" with `node` as | |
| 1874 parameter. If the ``visit_...`` method does not exist, call | |
| 1875 self.unknown_visit. | |
| 1876 """ | |
| 1877 node_name = node.__class__.__name__ | |
| 1878 method = getattr(self, 'visit_' + node_name, self.unknown_visit) | |
| 1879 self.document.reporter.debug( | |
| 1880 'docutils.nodes.NodeVisitor.dispatch_visit calling %s for %s' | |
| 1881 % (method.__name__, node_name)) | |
| 1882 return method(node) | |
| 1883 | |
| 1884 def dispatch_departure(self, node): | |
| 1885 """ | |
| 1886 Call self."``depart_`` + node class name" with `node` as | |
| 1887 parameter. If the ``depart_...`` method does not exist, call | |
| 1888 self.unknown_departure. | |
| 1889 """ | |
| 1890 node_name = node.__class__.__name__ | |
| 1891 method = getattr(self, 'depart_' + node_name, self.unknown_departure) | |
| 1892 self.document.reporter.debug( | |
| 1893 'docutils.nodes.NodeVisitor.dispatch_departure calling %s for %s' | |
| 1894 % (method.__name__, node_name)) | |
| 1895 return method(node) | |
| 1896 | |
| 1897 def unknown_visit(self, node): | |
| 1898 """ | |
| 1899 Called when entering unknown `Node` types. | |
| 1900 | |
| 1901 Raise an exception unless overridden. | |
| 1902 """ | |
| 1903 if (self.document.settings.strict_visitor | |
| 1904 or node.__class__.__name__ not in self.optional): | |
| 1905 raise NotImplementedError( | |
| 1906 '%s visiting unknown node type: %s' | |
| 1907 % (self.__class__, node.__class__.__name__)) | |
| 1908 | |
| 1909 def unknown_departure(self, node): | |
| 1910 """ | |
| 1911 Called before exiting unknown `Node` types. | |
| 1912 | |
| 1913 Raise exception unless overridden. | |
| 1914 """ | |
| 1915 if (self.document.settings.strict_visitor | |
| 1916 or node.__class__.__name__ not in self.optional): | |
| 1917 raise NotImplementedError( | |
| 1918 '%s departing unknown node type: %s' | |
| 1919 % (self.__class__, node.__class__.__name__)) | |
| 1920 | |
| 1921 | |
| 1922 class SparseNodeVisitor(NodeVisitor): | |
| 1923 | |
| 1924 """ | |
| 1925 Base class for sparse traversals, where only certain node types are of | |
| 1926 interest. When ``visit_...`` & ``depart_...`` methods should be | |
| 1927 implemented for *all* node types (such as for `docutils.writers.Writer` | |
| 1928 subclasses), subclass `NodeVisitor` instead. | |
| 1929 """ | |
| 1930 | |
| 1931 | |
| 1932 class GenericNodeVisitor(NodeVisitor): | |
| 1933 | |
| 1934 """ | |
| 1935 Generic "Visitor" abstract superclass, for simple traversals. | |
| 1936 | |
| 1937 Unless overridden, each ``visit_...`` method calls `default_visit()`, and | |
| 1938 each ``depart_...`` method (when using `Node.walkabout()`) calls | |
| 1939 `default_departure()`. `default_visit()` (and `default_departure()`) must | |
| 1940 be overridden in subclasses. | |
| 1941 | |
| 1942 Define fully generic visitors by overriding `default_visit()` (and | |
| 1943 `default_departure()`) only. Define semi-generic visitors by overriding | |
| 1944 individual ``visit_...()`` (and ``depart_...()``) methods also. | |
| 1945 | |
| 1946 `NodeVisitor.unknown_visit()` (`NodeVisitor.unknown_departure()`) should | |
| 1947 be overridden for default behavior. | |
| 1948 """ | |
| 1949 | |
| 1950 def default_visit(self, node): | |
| 1951 """Override for generic, uniform traversals.""" | |
| 1952 raise NotImplementedError | |
| 1953 | |
| 1954 def default_departure(self, node): | |
| 1955 """Override for generic, uniform traversals.""" | |
| 1956 raise NotImplementedError | |
| 1957 | |
| 1958 def _call_default_visit(self, node): | |
| 1959 self.default_visit(node) | |
| 1960 | |
| 1961 def _call_default_departure(self, node): | |
| 1962 self.default_departure(node) | |
| 1963 | |
| 1964 def _nop(self, node): | |
| 1965 pass | |
| 1966 | |
| 1967 def _add_node_class_names(names): | |
| 1968 """Save typing with dynamic assignments:""" | |
| 1969 for _name in names: | |
| 1970 setattr(GenericNodeVisitor, "visit_" + _name, _call_default_visit) | |
| 1971 setattr(GenericNodeVisitor, "depart_" + _name, _call_default_departure) | |
| 1972 setattr(SparseNodeVisitor, 'visit_' + _name, _nop) | |
| 1973 setattr(SparseNodeVisitor, 'depart_' + _name, _nop) | |
| 1974 | |
| 1975 _add_node_class_names(node_class_names) | |
| 1976 | |
| 1977 | |
| 1978 class TreeCopyVisitor(GenericNodeVisitor): | |
| 1979 | |
| 1980 """ | |
| 1981 Make a complete copy of a tree or branch, including element attributes. | |
| 1982 """ | |
| 1983 | |
| 1984 def __init__(self, document): | |
| 1985 GenericNodeVisitor.__init__(self, document) | |
| 1986 self.parent_stack = [] | |
| 1987 self.parent = [] | |
| 1988 | |
| 1989 def get_tree_copy(self): | |
| 1990 return self.parent[0] | |
| 1991 | |
| 1992 def default_visit(self, node): | |
| 1993 """Copy the current node, and make it the new acting parent.""" | |
| 1994 newnode = node.copy() | |
| 1995 self.parent.append(newnode) | |
| 1996 self.parent_stack.append(self.parent) | |
| 1997 self.parent = newnode | |
| 1998 | |
| 1999 def default_departure(self, node): | |
| 2000 """Restore the previous acting parent.""" | |
| 2001 self.parent = self.parent_stack.pop() | |
| 2002 | |
| 2003 | |
| 2004 class TreePruningException(Exception): | |
| 2005 | |
| 2006 """ | |
| 2007 Base class for `NodeVisitor`-related tree pruning exceptions. | |
| 2008 | |
| 2009 Raise subclasses from within ``visit_...`` or ``depart_...`` methods | |
| 2010 called from `Node.walk()` and `Node.walkabout()` tree traversals to prune | |
| 2011 the tree traversed. | |
| 2012 """ | |
| 2013 | |
| 2014 pass | |
| 2015 | |
| 2016 | |
| 2017 class SkipChildren(TreePruningException): | |
| 2018 | |
| 2019 """ | |
| 2020 Do not visit any children of the current node. The current node's | |
| 2021 siblings and ``depart_...`` method are not affected. | |
| 2022 """ | |
| 2023 | |
| 2024 pass | |
| 2025 | |
| 2026 | |
| 2027 class SkipSiblings(TreePruningException): | |
| 2028 | |
| 2029 """ | |
| 2030 Do not visit any more siblings (to the right) of the current node. The | |
| 2031 current node's children and its ``depart_...`` method are not affected. | |
| 2032 """ | |
| 2033 | |
| 2034 pass | |
| 2035 | |
| 2036 | |
| 2037 class SkipNode(TreePruningException): | |
| 2038 | |
| 2039 """ | |
| 2040 Do not visit the current node's children, and do not call the current | |
| 2041 node's ``depart_...`` method. | |
| 2042 """ | |
| 2043 | |
| 2044 pass | |
| 2045 | |
| 2046 | |
| 2047 class SkipDeparture(TreePruningException): | |
| 2048 | |
| 2049 """ | |
| 2050 Do not call the current node's ``depart_...`` method. The current node's | |
| 2051 children and siblings are not affected. | |
| 2052 """ | |
| 2053 | |
| 2054 pass | |
| 2055 | |
| 2056 | |
| 2057 class NodeFound(TreePruningException): | |
| 2058 | |
| 2059 """ | |
| 2060 Raise to indicate that the target of a search has been found. This | |
| 2061 exception must be caught by the client; it is not caught by the traversal | |
| 2062 code. | |
| 2063 """ | |
| 2064 | |
| 2065 pass | |
| 2066 | |
| 2067 | |
| 2068 class StopTraversal(TreePruningException): | |
| 2069 | |
| 2070 """ | |
| 2071 Stop the traversal alltogether. The current node's ``depart_...`` method | |
| 2072 is not affected. The parent nodes ``depart_...`` methods are also called | |
| 2073 as usual. No other nodes are visited. This is an alternative to | |
| 2074 NodeFound that does not cause exception handling to trickle up to the | |
| 2075 caller. | |
| 2076 """ | |
| 2077 | |
| 2078 pass | |
| 2079 | |
| 2080 | |
| 2081 def make_id(string): | |
| 2082 """ | |
| 2083 Convert `string` into an identifier and return it. | |
| 2084 | |
| 2085 Docutils identifiers will conform to the regular expression | |
| 2086 ``[a-z](-?[a-z0-9]+)*``. For CSS compatibility, identifiers (the "class" | |
| 2087 and "id" attributes) should have no underscores, colons, or periods. | |
| 2088 Hyphens may be used. | |
| 2089 | |
| 2090 - The `HTML 4.01 spec`_ defines identifiers based on SGML tokens: | |
| 2091 | |
| 2092 ID and NAME tokens must begin with a letter ([A-Za-z]) and may be | |
| 2093 followed by any number of letters, digits ([0-9]), hyphens ("-"), | |
| 2094 underscores ("_"), colons (":"), and periods ("."). | |
| 2095 | |
| 2096 - However the `CSS1 spec`_ defines identifiers based on the "name" token, | |
| 2097 a tighter interpretation ("flex" tokenizer notation; "latin1" and | |
| 2098 "escape" 8-bit characters have been replaced with entities):: | |
| 2099 | |
| 2100 unicode \\[0-9a-f]{1,4} | |
| 2101 latin1 [¡-ÿ] | |
| 2102 escape {unicode}|\\[ -~¡-ÿ] | |
| 2103 nmchar [-a-z0-9]|{latin1}|{escape} | |
| 2104 name {nmchar}+ | |
| 2105 | |
| 2106 The CSS1 "nmchar" rule does not include underscores ("_"), colons (":"), | |
| 2107 or periods ("."), therefore "class" and "id" attributes should not contain | |
| 2108 these characters. They should be replaced with hyphens ("-"). Combined | |
| 2109 with HTML's requirements (the first character must be a letter; no | |
| 2110 "unicode", "latin1", or "escape" characters), this results in the | |
| 2111 ``[a-z](-?[a-z0-9]+)*`` pattern. | |
| 2112 | |
| 2113 .. _HTML 4.01 spec: http://www.w3.org/TR/html401 | |
| 2114 .. _CSS1 spec: http://www.w3.org/TR/REC-CSS1 | |
| 2115 """ | |
| 2116 id = string.lower() | |
| 2117 if not isinstance(id, unicode): | |
| 2118 id = id.decode() | |
| 2119 id = id.translate(_non_id_translate_digraphs) | |
| 2120 id = id.translate(_non_id_translate) | |
| 2121 # get rid of non-ascii characters. | |
| 2122 # 'ascii' lowercase to prevent problems with turkish locale. | |
| 2123 id = unicodedata.normalize('NFKD', id).\ | |
| 2124 encode('ascii', 'ignore').decode('ascii') | |
| 2125 # shrink runs of whitespace and replace by hyphen | |
| 2126 id = _non_id_chars.sub('-', ' '.join(id.split())) | |
| 2127 id = _non_id_at_ends.sub('', id) | |
| 2128 return str(id) | |
| 2129 | |
| 2130 _non_id_chars = re.compile('[^a-z0-9]+') | |
| 2131 _non_id_at_ends = re.compile('^[-0-9]+|-+$') | |
| 2132 _non_id_translate = { | |
| 2133 0x00f8: u'o', # o with stroke | |
| 2134 0x0111: u'd', # d with stroke | |
| 2135 0x0127: u'h', # h with stroke | |
| 2136 0x0131: u'i', # dotless i | |
| 2137 0x0142: u'l', # l with stroke | |
| 2138 0x0167: u't', # t with stroke | |
| 2139 0x0180: u'b', # b with stroke | |
| 2140 0x0183: u'b', # b with topbar | |
| 2141 0x0188: u'c', # c with hook | |
| 2142 0x018c: u'd', # d with topbar | |
| 2143 0x0192: u'f', # f with hook | |
| 2144 0x0199: u'k', # k with hook | |
| 2145 0x019a: u'l', # l with bar | |
| 2146 0x019e: u'n', # n with long right leg | |
| 2147 0x01a5: u'p', # p with hook | |
| 2148 0x01ab: u't', # t with palatal hook | |
| 2149 0x01ad: u't', # t with hook | |
| 2150 0x01b4: u'y', # y with hook | |
| 2151 0x01b6: u'z', # z with stroke | |
| 2152 0x01e5: u'g', # g with stroke | |
| 2153 0x0225: u'z', # z with hook | |
| 2154 0x0234: u'l', # l with curl | |
| 2155 0x0235: u'n', # n with curl | |
| 2156 0x0236: u't', # t with curl | |
| 2157 0x0237: u'j', # dotless j | |
| 2158 0x023c: u'c', # c with stroke | |
| 2159 0x023f: u's', # s with swash tail | |
| 2160 0x0240: u'z', # z with swash tail | |
| 2161 0x0247: u'e', # e with stroke | |
| 2162 0x0249: u'j', # j with stroke | |
| 2163 0x024b: u'q', # q with hook tail | |
| 2164 0x024d: u'r', # r with stroke | |
| 2165 0x024f: u'y', # y with stroke | |
| 2166 } | |
| 2167 _non_id_translate_digraphs = { | |
| 2168 0x00df: u'sz', # ligature sz | |
| 2169 0x00e6: u'ae', # ae | |
| 2170 0x0153: u'oe', # ligature oe | |
| 2171 0x0238: u'db', # db digraph | |
| 2172 0x0239: u'qp', # qp digraph | |
| 2173 } | |
| 2174 | |
| 2175 def dupname(node, name): | |
| 2176 node['dupnames'].append(name) | |
| 2177 node['names'].remove(name) | |
| 2178 # Assume that this method is referenced, even though it isn't; we | |
| 2179 # don't want to throw unnecessary system_messages. | |
| 2180 node.referenced = 1 | |
| 2181 | |
| 2182 def fully_normalize_name(name): | |
| 2183 """Return a case- and whitespace-normalized name.""" | |
| 2184 return ' '.join(name.lower().split()) | |
| 2185 | |
| 2186 def whitespace_normalize_name(name): | |
| 2187 """Return a whitespace-normalized name.""" | |
| 2188 return ' '.join(name.split()) | |
| 2189 | |
| 2190 def serial_escape(value): | |
| 2191 """Escape string values that are elements of a list, for serialization.""" | |
| 2192 return value.replace('\\', r'\\').replace(' ', r'\ ') | |
| 2193 | |
| 2194 def pseudo_quoteattr(value): | |
| 2195 """Quote attributes for pseudo-xml""" | |
| 2196 return '"%s"' % value | |
| 2197 | |
| 2198 # | |
| 2199 # | |
| 2200 # Local Variables: | |
| 2201 # indent-tabs-mode: nil | |
| 2202 # sentence-end-double-space: t | |
| 2203 # fill-column: 78 | |
| 2204 # End: |
