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