comparison env/lib/python3.9/site-packages/docutils/nodes.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
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 [&iexcl;-&yuml;]
2215 escape {unicode}|\\[ -~&iexcl;-&yuml;]
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: