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: |