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