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