Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/docutils/nodes.py: 57%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1126 statements  

1# $Id$ 

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

7Docutils document tree element class library. 

8 

9Classes in CamelCase are abstract base classes or auxiliary classes. The one 

10exception is `Text`, for a text (PCDATA) node; uppercase is used to 

11differentiate from element classes. Classes in lower_case_with_underscores 

12are element classes, matching the XML element generic identifiers in the DTD_. 

13 

14The position of each node (the level at which it can occur) is significant and 

15is 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 

18hierarchy. 

19 

20.. _DTD: https://docutils.sourceforge.io/docs/ref/docutils.dtd 

21""" 

22 

23__docformat__ = 'reStructuredText' 

24 

25from collections import Counter 

26import re 

27import sys 

28import unicodedata 

29import warnings 

30# import xml.dom.minidom as dom # -> conditional import in Node.asdom() 

31# and document.asdom() 

32 

33# import docutils.transforms # -> conditional import in document.__init__() 

34 

35 

36# ============================== 

37# Functional Node Base Classes 

38# ============================== 

39 

40class Node: 

41 """Abstract base class of nodes in a document tree.""" 

42 

43 parent = None 

44 """Back-reference to the Node immediately containing this Node.""" 

45 

46 source = None 

47 """Path or description of the input source which generated this Node.""" 

48 

49 line = None 

50 """The line number (1-based) of the beginning of this Node in `source`.""" 

51 

52 _document = None 

53 

54 @property 

55 def document(self): 

56 """Return the `document` root node of the tree containing this Node. 

57 """ 

58 try: 

59 return self._document or self.parent.document 

60 except AttributeError: 

61 return None 

62 

63 @document.setter 

64 def document(self, value): 

65 self._document = value 

66 

67 def __bool__(self): 

68 """ 

69 Node instances are always true, even if they're empty. A node is more 

70 than a simple container. Its boolean "truth" does not depend on 

71 having one or more subnodes in the doctree. 

72 

73 Use `len()` to check node length. 

74 """ 

75 return True 

76 

77 def asdom(self, dom=None): 

78 """Return a DOM **fragment** representation of this Node.""" 

79 if dom is None: 

80 import xml.dom.minidom as dom 

81 domroot = dom.Document() 

82 return self._dom_node(domroot) 

83 

84 def pformat(self, indent=' ', level=0): 

85 """ 

86 Return an indented pseudo-XML representation, for test purposes. 

87 

88 Override in subclasses. 

89 """ 

90 raise NotImplementedError 

91 

92 def copy(self): 

93 """Return a copy of self.""" 

94 raise NotImplementedError 

95 

96 def deepcopy(self): 

97 """Return a deep copy of self (also copying children).""" 

98 raise NotImplementedError 

99 

100 def astext(self): 

101 """Return a string representation of this Node.""" 

102 raise NotImplementedError 

103 

104 def setup_child(self, child): 

105 child.parent = self 

106 if self.document: 

107 child.document = self.document 

108 if child.source is None: 

109 child.source = self.document.current_source 

110 if child.line is None: 

111 child.line = self.document.current_line 

112 

113 def walk(self, visitor): 

114 """ 

115 Traverse a tree of `Node` objects, calling the 

116 `dispatch_visit()` method of `visitor` when entering each 

117 node. (The `walkabout()` method is similar, except it also 

118 calls the `dispatch_departure()` method before exiting each 

119 node.) 

120 

121 This tree traversal supports limited in-place tree 

122 modifications. Replacing one node with one or more nodes is 

123 OK, as is removing an element. However, if the node removed 

124 or replaced occurs after the current node, the old node will 

125 still be traversed, and any new nodes will not. 

126 

127 Within ``visit`` methods (and ``depart`` methods for 

128 `walkabout()`), `TreePruningException` subclasses may be raised 

129 (`SkipChildren`, `SkipSiblings`, `SkipNode`, `SkipDeparture`). 

130 

131 Parameter `visitor`: A `NodeVisitor` object, containing a 

132 ``visit`` implementation for each `Node` subclass encountered. 

133 

134 Return true if we should stop the traversal. 

135 """ 

136 stop = False 

137 visitor.document.reporter.debug( 

138 'docutils.nodes.Node.walk calling dispatch_visit for %s' 

139 % self.__class__.__name__) 

140 try: 

141 try: 

142 visitor.dispatch_visit(self) 

143 except (SkipChildren, SkipNode): 

144 return stop 

145 except SkipDeparture: # not applicable; ignore 

146 pass 

147 children = self.children 

148 try: 

149 for child in children[:]: 

150 if child.walk(visitor): 

151 stop = True 

152 break 

153 except SkipSiblings: 

154 pass 

155 except StopTraversal: 

156 stop = True 

157 return stop 

158 

159 def walkabout(self, visitor): 

160 """ 

161 Perform a tree traversal similarly to `Node.walk()` (which 

162 see), except also call the `dispatch_departure()` method 

163 before exiting each node. 

164 

165 Parameter `visitor`: A `NodeVisitor` object, containing a 

166 ``visit`` and ``depart`` implementation for each `Node` 

167 subclass encountered. 

168 

169 Return true if we should stop the traversal. 

170 """ 

171 call_depart = True 

172 stop = False 

173 visitor.document.reporter.debug( 

174 'docutils.nodes.Node.walkabout calling dispatch_visit for %s' 

175 % self.__class__.__name__) 

176 try: 

177 try: 

178 visitor.dispatch_visit(self) 

179 except SkipNode: 

180 return stop 

181 except SkipDeparture: 

182 call_depart = False 

183 children = self.children 

184 try: 

185 for child in children[:]: 

186 if child.walkabout(visitor): 

187 stop = True 

188 break 

189 except SkipSiblings: 

190 pass 

191 except SkipChildren: 

192 pass 

193 except StopTraversal: 

194 stop = True 

195 if call_depart: 

196 visitor.document.reporter.debug( 

197 'docutils.nodes.Node.walkabout calling dispatch_departure ' 

198 'for %s' % self.__class__.__name__) 

199 visitor.dispatch_departure(self) 

200 return stop 

201 

202 def _fast_findall(self, cls): 

203 """Return iterator that only supports instance checks.""" 

204 if isinstance(self, cls): 

205 yield self 

206 for child in self.children: 

207 yield from child._fast_findall(cls) 

208 

209 def _superfast_findall(self): 

210 """Return iterator that doesn't check for a condition.""" 

211 # This is different from ``iter(self)`` implemented via 

212 # __getitem__() and __len__() in the Element subclass, 

213 # which yields only the direct children. 

214 yield self 

215 for child in self.children: 

216 yield from child._superfast_findall() 

217 

218 def findall(self, condition=None, include_self=True, descend=True, 

219 siblings=False, ascend=False): 

220 """ 

221 Return an iterator yielding nodes following `self`: 

222 

223 * self (if `include_self` is true) 

224 * all descendants in tree traversal order (if `descend` is true) 

225 * the following siblings (if `siblings` is true) and their 

226 descendants (if also `descend` is true) 

227 * the following siblings of the parent (if `ascend` is true) and 

228 their descendants (if also `descend` is true), and so on. 

229 

230 If `condition` is not None, the iterator yields only nodes 

231 for which ``condition(node)`` is true. If `condition` is a 

232 node class ``cls``, it is equivalent to a function consisting 

233 of ``return isinstance(node, cls)``. 

234 

235 If `ascend` is true, assume `siblings` to be true as well. 

236 

237 If the tree structure is modified during iteration, the result 

238 is undefined. 

239 

240 For example, given the following tree:: 

241 

242 <paragraph> 

243 <emphasis> <--- emphasis.traverse() and 

244 <strong> <--- strong.traverse() are called. 

245 Foo 

246 Bar 

247 <reference name="Baz" refid="baz"> 

248 Baz 

249 

250 Then tuple(emphasis.traverse()) equals :: 

251 

252 (<emphasis>, <strong>, <#text: Foo>, <#text: Bar>) 

253 

254 and list(strong.traverse(ascend=True) equals :: 

255 

256 [<strong>, <#text: Foo>, <#text: Bar>, <reference>, <#text: Baz>] 

257 """ 

258 if ascend: 

259 siblings = True 

260 # Check for special argument combinations that allow using an 

261 # optimized version of traverse() 

262 if include_self and descend and not siblings: 

263 if condition is None: 

264 yield from self._superfast_findall() 

265 return 

266 elif isinstance(condition, type): 

267 yield from self._fast_findall(condition) 

268 return 

269 # Check if `condition` is a class (check for TypeType for Python 

270 # implementations that use only new-style classes, like PyPy). 

271 if isinstance(condition, type): 

272 node_class = condition 

273 

274 def condition(node, node_class=node_class): 

275 return isinstance(node, node_class) 

276 

277 if include_self and (condition is None or condition(self)): 

278 yield self 

279 if descend and len(self.children): 

280 for child in self: 

281 yield from child.findall(condition=condition, 

282 include_self=True, descend=True, 

283 siblings=False, ascend=False) 

284 if siblings or ascend: 

285 node = self 

286 while node.parent: 

287 index = node.parent.index(node) 

288 # extra check since Text nodes have value-equality 

289 while node.parent[index] is not node: 

290 index = node.parent.index(node, index + 1) 

291 for sibling in node.parent[index+1:]: 

292 yield from sibling.findall( 

293 condition=condition, 

294 include_self=True, descend=descend, 

295 siblings=False, ascend=False) 

296 if not ascend: 

297 break 

298 else: 

299 node = node.parent 

300 

301 def traverse(self, condition=None, include_self=True, descend=True, 

302 siblings=False, ascend=False): 

303 """Return list of nodes following `self`. 

304 

305 For looping, Node.findall() is faster and more memory efficient. 

306 """ 

307 # traverse() may be eventually removed: 

308 warnings.warn('nodes.Node.traverse() is obsoleted by Node.findall().', 

309 PendingDeprecationWarning, stacklevel=2) 

310 return list(self.findall(condition, include_self, descend, 

311 siblings, ascend)) 

312 

313 def next_node(self, condition=None, include_self=False, descend=True, 

314 siblings=False, ascend=False): 

315 """ 

316 Return the first node in the iterator returned by findall(), 

317 or None if the iterable is empty. 

318 

319 Parameter list is the same as of `findall()`. Note that `include_self` 

320 defaults to False, though. 

321 """ 

322 try: 

323 return next(self.findall(condition, include_self, 

324 descend, siblings, ascend)) 

325 except StopIteration: 

326 return None 

327 

328 

329class Text(Node, str): 

330 """ 

331 Instances are terminal nodes (leaves) containing text only; no child 

332 nodes or attributes. Initialize by passing a string to the constructor. 

333 

334 Access the raw (null-escaped) text with ``str(<instance>)`` 

335 and unescaped text with ``<instance>.astext()``. 

336 """ 

337 

338 tagname = '#text' 

339 

340 children = () 

341 """Text nodes have no children, and cannot have children.""" 

342 

343 def __new__(cls, data, rawsource=None): 

344 """Assert that `data` is not an array of bytes 

345 and warn if the deprecated `rawsource` argument is used. 

346 """ 

347 if isinstance(data, bytes): 

348 raise TypeError('expecting str data, not bytes') 

349 if rawsource is not None: 

350 warnings.warn('nodes.Text: initialization argument "rawsource" ' 

351 'is ignored and will be removed in Docutils 2.0.', 

352 DeprecationWarning, stacklevel=2) 

353 return str.__new__(cls, data) 

354 

355 def shortrepr(self, maxlen=18): 

356 data = self 

357 if len(data) > maxlen: 

358 data = data[:maxlen-4] + ' ...' 

359 return '<%s: %r>' % (self.tagname, str(data)) 

360 

361 def __repr__(self): 

362 return self.shortrepr(maxlen=68) 

363 

364 def astext(self): 

365 return str(unescape(self)) 

366 

367 def _dom_node(self, domroot): 

368 return domroot.createTextNode(str(self)) 

369 

370 def copy(self): 

371 return self.__class__(str(self)) 

372 

373 def deepcopy(self): 

374 return self.copy() 

375 

376 def pformat(self, indent=' ', level=0): 

377 try: 

378 if self.document.settings.detailed: 

379 tag = '%s%s' % (indent*level, '<#text>') 

380 lines = (indent*(level+1) + repr(line) 

381 for line in self.splitlines(True)) 

382 return '\n'.join((tag, *lines)) + '\n' 

383 except AttributeError: 

384 pass 

385 indent = indent * level 

386 lines = [indent+line for line in self.astext().splitlines()] 

387 if not lines: 

388 return '' 

389 return '\n'.join(lines) + '\n' 

390 

391 # rstrip and lstrip are used by substitution definitions where 

392 # they are expected to return a Text instance, this was formerly 

393 # taken care of by UserString. 

394 

395 def rstrip(self, chars=None): 

396 return self.__class__(str.rstrip(self, chars)) 

397 

398 def lstrip(self, chars=None): 

399 return self.__class__(str.lstrip(self, chars)) 

400 

401 def validate(self): 

402 pass # Text nodes have no attributes and no children. 

403 

404 

405class Element(Node): 

406 """ 

407 `Element` is the superclass to all specific elements. 

408 

409 Elements contain attributes and child nodes. 

410 They can be described as a cross between a list and a dictionary. 

411 

412 Elements emulate dictionaries for external [#]_ attributes, indexing by 

413 attribute name (a string). To set the attribute 'att' to 'value', do:: 

414 

415 element['att'] = 'value' 

416 

417 .. [#] External attributes correspond to the XML element attributes. 

418 From its `Node` superclass, Element also inherits "internal" 

419 class attributes that are accessed using the standard syntax, e.g. 

420 ``element.parent``. 

421 

422 There are two special attributes: 'ids' and 'names'. Both are 

423 lists of unique identifiers: 'ids' conform to the regular expression 

424 ``[a-z](-?[a-z0-9]+)*`` (see the make_id() function for rationale and 

425 details). 'names' serve as user-friendly interfaces to IDs; they are 

426 case- and whitespace-normalized (see the fully_normalize_name() function). 

427 

428 Elements emulate lists for child nodes (element nodes and/or text 

429 nodes), indexing by integer. To get the first child node, use:: 

430 

431 element[0] 

432 

433 to iterate over the child nodes (without descending), use:: 

434 

435 for child in element: 

436 ... 

437 

438 Elements may be constructed using the ``+=`` operator. To add one new 

439 child node to element, do:: 

440 

441 element += node 

442 

443 This is equivalent to ``element.append(node)``. 

444 

445 To add a list of multiple child nodes at once, use the same ``+=`` 

446 operator:: 

447 

448 element += [node1, node2] 

449 

450 This is equivalent to ``element.extend([node1, node2])``. 

451 """ 

452 

453 list_attributes = ('ids', 'classes', 'names', 'dupnames') 

454 """Tuple of attributes that are initialized to empty lists. 

455 

456 NOTE: Derived classes should update this value when supporting 

457 additional list attributes. 

458 """ 

459 

460 valid_attributes = list_attributes + ('source',) 

461 """Tuple of attributes that are valid for elements of this class. 

462 

463 NOTE: Derived classes should update this value when supporting 

464 additional attributes. 

465 """ 

466 

467 common_attributes = valid_attributes 

468 """Tuple of `common attributes`__ known to all Doctree Element classes. 

469 

470 __ https://docutils.sourceforge.io/docs/ref/doctree.html#common-attributes 

471 """ 

472 

473 known_attributes = common_attributes 

474 """Alias for `common_attributes`. Will be removed in Docutils 2.0.""" 

475 

476 basic_attributes = list_attributes 

477 """Common list attributes. Deprecated. Will be removed in Docutils 2.0.""" 

478 

479 local_attributes = ('backrefs',) 

480 """Obsolete. Will be removed in Docutils 2.0.""" 

481 

482 valid_children = tuple() 

483 """Valid class or tuple of valid classes for child elements. 

484 

485 NOTE: Derived classes should update this value 

486 when supporting child elements. 

487 """ 

488 

489 valid_len = (1, None) 

490 """Tuple of minimal and maximal number of child elements. 

491 

492 A maximal value of None stands for "no upper limit". 

493 

494 Default: one or more child elements. 

495 

496 NOTE: Derived classes should update this value when there are different 

497 restrictions to the number of child elements. 

498 """ 

499 

500 tagname = None 

501 """The element generic identifier. 

502 

503 If None, it is set as an instance attribute to the name of the class. 

504 """ 

505 

506 child_text_separator = '\n\n' 

507 """Separator for child nodes, used by `astext()` method.""" 

508 

509 def __init__(self, rawsource='', *children, **attributes): 

510 self.rawsource = rawsource 

511 """The raw text from which this element was constructed. 

512 

513 For informative and debugging purposes. Don't rely on its value! 

514 

515 NOTE: some elements do not set this value (default ''). 

516 """ 

517 

518 self.children = [] 

519 """List of child nodes (elements and/or `Text`).""" 

520 

521 self.extend(children) # maintain parent info 

522 

523 self.attributes = {} 

524 """Dictionary of attribute {name: value}.""" 

525 

526 # Initialize list attributes. 

527 for att in self.list_attributes: 

528 self.attributes[att] = [] 

529 

530 for att, value in attributes.items(): 

531 att = att.lower() # normalize attribute name 

532 if att in self.list_attributes: 

533 # lists are mutable; make a copy for this node 

534 self.attributes[att] = value[:] 

535 else: 

536 self.attributes[att] = value 

537 

538 if self.tagname is None: 

539 self.tagname = self.__class__.__name__ 

540 

541 def _dom_node(self, domroot): 

542 element = domroot.createElement(self.tagname) 

543 for attribute, value in self.attlist(): 

544 if isinstance(value, list): 

545 value = ' '.join(serial_escape('%s' % (v,)) for v in value) 

546 element.setAttribute(attribute, '%s' % value) 

547 for child in self.children: 

548 element.appendChild(child._dom_node(domroot)) 

549 return element 

550 

551 def __repr__(self): 

552 data = '' 

553 for c in self.children: 

554 data += c.shortrepr() 

555 if len(data) > 60: 

556 data = data[:56] + ' ...' 

557 break 

558 if self['names']: 

559 return '<%s "%s": %s>' % (self.__class__.__name__, 

560 '; '.join(self['names']), data) 

561 else: 

562 return '<%s: %s>' % (self.__class__.__name__, data) 

563 

564 def shortrepr(self): 

565 if self['names']: 

566 return '<%s "%s"...>' % (self.__class__.__name__, 

567 '; '.join(self['names'])) 

568 else: 

569 return '<%s...>' % self.tagname 

570 

571 def __str__(self): 

572 if self.children: 

573 return '%s%s%s' % (self.starttag(), 

574 ''.join(str(c) for c in self.children), 

575 self.endtag()) 

576 else: 

577 return self.emptytag() 

578 

579 def starttag(self, quoteattr=None): 

580 # the optional arg is used by the docutils_xml writer 

581 if quoteattr is None: 

582 quoteattr = pseudo_quoteattr 

583 parts = [self.tagname] 

584 for name, value in self.attlist(): 

585 if value is None: # boolean attribute 

586 parts.append('%s="True"' % name) 

587 continue 

588 if isinstance(value, bool): 

589 value = str(int(value)) 

590 if isinstance(value, list): 

591 values = [serial_escape('%s' % (v,)) for v in value] 

592 value = ' '.join(values) 

593 else: 

594 value = str(value) 

595 value = quoteattr(value) 

596 parts.append('%s=%s' % (name, value)) 

597 return '<%s>' % ' '.join(parts) 

598 

599 def endtag(self): 

600 return '</%s>' % self.tagname 

601 

602 def emptytag(self): 

603 attributes = ('%s="%s"' % (n, v) for n, v in self.attlist()) 

604 return '<%s/>' % ' '.join((self.tagname, *attributes)) 

605 

606 def __len__(self): 

607 return len(self.children) 

608 

609 def __contains__(self, key): 

610 # Test for both, children and attributes with operator ``in``. 

611 if isinstance(key, str): 

612 return key in self.attributes 

613 return key in self.children 

614 

615 def __getitem__(self, key): 

616 if isinstance(key, str): 

617 return self.attributes[key] 

618 elif isinstance(key, int): 

619 return self.children[key] 

620 elif isinstance(key, slice): 

621 assert key.step in (None, 1), 'cannot handle slice with stride' 

622 return self.children[key.start:key.stop] 

623 else: 

624 raise TypeError('element index must be an integer, a slice, or ' 

625 'an attribute name string') 

626 

627 def __setitem__(self, key, item): 

628 if isinstance(key, str): 

629 self.attributes[str(key)] = item 

630 elif isinstance(key, int): 

631 self.setup_child(item) 

632 self.children[key] = item 

633 elif isinstance(key, slice): 

634 assert key.step in (None, 1), 'cannot handle slice with stride' 

635 for node in item: 

636 self.setup_child(node) 

637 self.children[key.start:key.stop] = item 

638 else: 

639 raise TypeError('element index must be an integer, a slice, or ' 

640 'an attribute name string') 

641 

642 def __delitem__(self, key): 

643 if isinstance(key, str): 

644 del self.attributes[key] 

645 elif isinstance(key, int): 

646 del self.children[key] 

647 elif isinstance(key, slice): 

648 assert key.step in (None, 1), 'cannot handle slice with stride' 

649 del self.children[key.start:key.stop] 

650 else: 

651 raise TypeError('element index must be an integer, a simple ' 

652 'slice, or an attribute name string') 

653 

654 def __add__(self, other): 

655 return self.children + other 

656 

657 def __radd__(self, other): 

658 return other + self.children 

659 

660 def __iadd__(self, other): 

661 """Append a node or a list of nodes to `self.children`.""" 

662 if isinstance(other, Node): 

663 self.append(other) 

664 elif other is not None: 

665 self.extend(other) 

666 return self 

667 

668 def astext(self): 

669 return self.child_text_separator.join( 

670 [child.astext() for child in self.children]) 

671 

672 def non_default_attributes(self): 

673 atts = {} 

674 for key, value in self.attributes.items(): 

675 if self.is_not_default(key): 

676 atts[key] = value 

677 return atts 

678 

679 def attlist(self): 

680 return sorted(self.non_default_attributes().items()) 

681 

682 def get(self, key, failobj=None): 

683 return self.attributes.get(key, failobj) 

684 

685 def hasattr(self, attr): 

686 return attr in self.attributes 

687 

688 def delattr(self, attr): 

689 if attr in self.attributes: 

690 del self.attributes[attr] 

691 

692 def setdefault(self, key, failobj=None): 

693 return self.attributes.setdefault(key, failobj) 

694 

695 has_key = hasattr 

696 

697 def get_language_code(self, fallback=''): 

698 """Return node's language tag. 

699 

700 Look iteratively in self and parents for a class argument 

701 starting with ``language-`` and return the remainder of it 

702 (which should be a `BCP49` language tag) or the `fallback`. 

703 """ 

704 for cls in self.get('classes', []): 

705 if cls.startswith('language-'): 

706 return cls[9:] 

707 try: 

708 return self.parent.get_language(fallback) 

709 except AttributeError: 

710 return fallback 

711 

712 def append(self, item): 

713 self.setup_child(item) 

714 self.children.append(item) 

715 

716 def extend(self, item): 

717 for node in item: 

718 self.append(node) 

719 

720 def insert(self, index, item): 

721 if isinstance(item, Node): 

722 self.setup_child(item) 

723 self.children.insert(index, item) 

724 elif item is not None: 

725 self[index:index] = item 

726 

727 def pop(self, i=-1): 

728 return self.children.pop(i) 

729 

730 def remove(self, item): 

731 self.children.remove(item) 

732 

733 def index(self, item, start=0, stop=sys.maxsize): 

734 return self.children.index(item, start, stop) 

735 

736 def previous_sibling(self): 

737 """Return preceding sibling node or ``None``.""" 

738 try: 

739 i = self.parent.index(self) 

740 except (AttributeError): 

741 return None 

742 return self.parent[i-1] if i > 0 else None 

743 

744 def is_not_default(self, key): 

745 if self[key] == [] and key in self.list_attributes: 

746 return 0 

747 else: 

748 return 1 

749 

750 def update_basic_atts(self, dict_): 

751 """ 

752 Update basic attributes ('ids', 'names', 'classes', 

753 'dupnames', but not 'source') from node or dictionary `dict_`. 

754 

755 Provisional. 

756 """ 

757 if isinstance(dict_, Node): 

758 dict_ = dict_.attributes 

759 for att in self.basic_attributes: 

760 self.append_attr_list(att, dict_.get(att, [])) 

761 

762 def append_attr_list(self, attr, values): 

763 """ 

764 For each element in values, if it does not exist in self[attr], append 

765 it. 

766 

767 NOTE: Requires self[attr] and values to be sequence type and the 

768 former should specifically be a list. 

769 """ 

770 # List Concatenation 

771 for value in values: 

772 if value not in self[attr]: 

773 self[attr].append(value) 

774 

775 def coerce_append_attr_list(self, attr, value): 

776 """ 

777 First, convert both self[attr] and value to a non-string sequence 

778 type; if either is not already a sequence, convert it to a list of one 

779 element. Then call append_attr_list. 

780 

781 NOTE: self[attr] and value both must not be None. 

782 """ 

783 # List Concatenation 

784 if not isinstance(self.get(attr), list): 

785 self[attr] = [self[attr]] 

786 if not isinstance(value, list): 

787 value = [value] 

788 self.append_attr_list(attr, value) 

789 

790 def replace_attr(self, attr, value, force=True): 

791 """ 

792 If self[attr] does not exist or force is True or omitted, set 

793 self[attr] to value, otherwise do nothing. 

794 """ 

795 # One or the other 

796 if force or self.get(attr) is None: 

797 self[attr] = value 

798 

799 def copy_attr_convert(self, attr, value, replace=True): 

800 """ 

801 If attr is an attribute of self, set self[attr] to 

802 [self[attr], value], otherwise set self[attr] to value. 

803 

804 NOTE: replace is not used by this function and is kept only for 

805 compatibility with the other copy functions. 

806 """ 

807 if self.get(attr) is not value: 

808 self.coerce_append_attr_list(attr, value) 

809 

810 def copy_attr_coerce(self, attr, value, replace): 

811 """ 

812 If attr is an attribute of self and either self[attr] or value is a 

813 list, convert all non-sequence values to a sequence of 1 element and 

814 then concatenate the two sequence, setting the result to self[attr]. 

815 If both self[attr] and value are non-sequences and replace is True or 

816 self[attr] is None, replace self[attr] with value. Otherwise, do 

817 nothing. 

818 """ 

819 if self.get(attr) is not value: 

820 if isinstance(self.get(attr), list) or \ 

821 isinstance(value, list): 

822 self.coerce_append_attr_list(attr, value) 

823 else: 

824 self.replace_attr(attr, value, replace) 

825 

826 def copy_attr_concatenate(self, attr, value, replace): 

827 """ 

828 If attr is an attribute of self and both self[attr] and value are 

829 lists, concatenate the two sequences, setting the result to 

830 self[attr]. If either self[attr] or value are non-sequences and 

831 replace is True or self[attr] is None, replace self[attr] with value. 

832 Otherwise, do nothing. 

833 """ 

834 if self.get(attr) is not value: 

835 if isinstance(self.get(attr), list) and \ 

836 isinstance(value, list): 

837 self.append_attr_list(attr, value) 

838 else: 

839 self.replace_attr(attr, value, replace) 

840 

841 def copy_attr_consistent(self, attr, value, replace): 

842 """ 

843 If replace is True or self[attr] is None, replace self[attr] with 

844 value. Otherwise, do nothing. 

845 """ 

846 if self.get(attr) is not value: 

847 self.replace_attr(attr, value, replace) 

848 

849 def update_all_atts(self, dict_, update_fun=copy_attr_consistent, 

850 replace=True, and_source=False): 

851 """ 

852 Updates all attributes from node or dictionary `dict_`. 

853 

854 Appends the basic attributes ('ids', 'names', 'classes', 

855 'dupnames', but not 'source') and then, for all other attributes in 

856 dict_, updates the same attribute in self. When attributes with the 

857 same identifier appear in both self and dict_, the two values are 

858 merged based on the value of update_fun. Generally, when replace is 

859 True, the values in self are replaced or merged with the values in 

860 dict_; otherwise, the values in self may be preserved or merged. When 

861 and_source is True, the 'source' attribute is included in the copy. 

862 

863 NOTE: When replace is False, and self contains a 'source' attribute, 

864 'source' is not replaced even when dict_ has a 'source' 

865 attribute, though it may still be merged into a list depending 

866 on the value of update_fun. 

867 NOTE: It is easier to call the update-specific methods then to pass 

868 the update_fun method to this function. 

869 """ 

870 if isinstance(dict_, Node): 

871 dict_ = dict_.attributes 

872 

873 # Include the source attribute when copying? 

874 if and_source: 

875 filter_fun = self.is_not_list_attribute 

876 else: 

877 filter_fun = self.is_not_known_attribute 

878 

879 # Copy the basic attributes 

880 self.update_basic_atts(dict_) 

881 

882 # Grab other attributes in dict_ not in self except the 

883 # (All basic attributes should be copied already) 

884 for att in filter(filter_fun, dict_): 

885 update_fun(self, att, dict_[att], replace) 

886 

887 def update_all_atts_consistantly(self, dict_, replace=True, 

888 and_source=False): 

889 """ 

890 Updates all attributes from node or dictionary `dict_`. 

891 

892 Appends the basic attributes ('ids', 'names', 'classes', 

893 'dupnames', but not 'source') and then, for all other attributes in 

894 dict_, updates the same attribute in self. When attributes with the 

895 same identifier appear in both self and dict_ and replace is True, the 

896 values in self are replaced with the values in dict_; otherwise, the 

897 values in self are preserved. When and_source is True, the 'source' 

898 attribute is included in the copy. 

899 

900 NOTE: When replace is False, and self contains a 'source' attribute, 

901 'source' is not replaced even when dict_ has a 'source' 

902 attribute, though it may still be merged into a list depending 

903 on the value of update_fun. 

904 """ 

905 self.update_all_atts(dict_, Element.copy_attr_consistent, replace, 

906 and_source) 

907 

908 def update_all_atts_concatenating(self, dict_, replace=True, 

909 and_source=False): 

910 """ 

911 Updates all attributes from node or dictionary `dict_`. 

912 

913 Appends the basic attributes ('ids', 'names', 'classes', 

914 'dupnames', but not 'source') and then, for all other attributes in 

915 dict_, updates the same attribute in self. When attributes with the 

916 same identifier appear in both self and dict_ whose values aren't each 

917 lists and replace is True, the values in self are replaced with the 

918 values in dict_; if the values from self and dict_ for the given 

919 identifier are both of list type, then the two lists are concatenated 

920 and the result stored in self; otherwise, the values in self are 

921 preserved. When and_source is True, the 'source' attribute is 

922 included in the copy. 

923 

924 NOTE: When replace is False, and self contains a 'source' attribute, 

925 'source' is not replaced even when dict_ has a 'source' 

926 attribute, though it may still be merged into a list depending 

927 on the value of update_fun. 

928 """ 

929 self.update_all_atts(dict_, Element.copy_attr_concatenate, replace, 

930 and_source) 

931 

932 def update_all_atts_coercion(self, dict_, replace=True, 

933 and_source=False): 

934 """ 

935 Updates all attributes from node or dictionary `dict_`. 

936 

937 Appends the basic attributes ('ids', 'names', 'classes', 

938 'dupnames', but not 'source') and then, for all other attributes in 

939 dict_, updates the same attribute in self. When attributes with the 

940 same identifier appear in both self and dict_ whose values are both 

941 not lists and replace is True, the values in self are replaced with 

942 the values in dict_; if either of the values from self and dict_ for 

943 the given identifier are of list type, then first any non-lists are 

944 converted to 1-element lists and then the two lists are concatenated 

945 and the result stored in self; otherwise, the values in self are 

946 preserved. When and_source is True, the 'source' attribute is 

947 included in the copy. 

948 

949 NOTE: When replace is False, and self contains a 'source' attribute, 

950 'source' is not replaced even when dict_ has a 'source' 

951 attribute, though it may still be merged into a list depending 

952 on the value of update_fun. 

953 """ 

954 self.update_all_atts(dict_, Element.copy_attr_coerce, replace, 

955 and_source) 

956 

957 def update_all_atts_convert(self, dict_, and_source=False): 

958 """ 

959 Updates all attributes from node or dictionary `dict_`. 

960 

961 Appends the basic attributes ('ids', 'names', 'classes', 

962 'dupnames', but not 'source') and then, for all other attributes in 

963 dict_, updates the same attribute in self. When attributes with the 

964 same identifier appear in both self and dict_ then first any non-lists 

965 are converted to 1-element lists and then the two lists are 

966 concatenated and the result stored in self; otherwise, the values in 

967 self are preserved. When and_source is True, the 'source' attribute 

968 is included in the copy. 

969 

970 NOTE: When replace is False, and self contains a 'source' attribute, 

971 'source' is not replaced even when dict_ has a 'source' 

972 attribute, though it may still be merged into a list depending 

973 on the value of update_fun. 

974 """ 

975 self.update_all_atts(dict_, Element.copy_attr_convert, 

976 and_source=and_source) 

977 

978 def clear(self): 

979 self.children = [] 

980 

981 def replace(self, old, new): 

982 """Replace one child `Node` with another child or children.""" 

983 index = self.index(old) 

984 if isinstance(new, Node): 

985 self.setup_child(new) 

986 self[index] = new 

987 elif new is not None: 

988 self[index:index+1] = new 

989 

990 def replace_self(self, new): 

991 """ 

992 Replace `self` node with `new`, where `new` is a node or a 

993 list of nodes. 

994 

995 Provisional: the handling of node attributes will be revised. 

996 """ 

997 update = new 

998 if not isinstance(new, Node): 

999 # `new` is a list; update first child. 

1000 try: 

1001 update = new[0] 

1002 except IndexError: 

1003 update = None 

1004 if isinstance(update, Element): 

1005 update.update_basic_atts(self) 

1006 else: 

1007 # `update` is a Text node or `new` is an empty list. 

1008 # Assert that we aren't losing any attributes. 

1009 for att in self.basic_attributes: 

1010 assert not self[att], \ 

1011 'Losing "%s" attribute: %s' % (att, self[att]) 

1012 self.parent.replace(self, new) 

1013 

1014 def first_child_matching_class(self, childclass, start=0, end=sys.maxsize): 

1015 """ 

1016 Return the index of the first child whose class exactly matches. 

1017 

1018 Parameters: 

1019 

1020 - `childclass`: A `Node` subclass to search for, or a tuple of `Node` 

1021 classes. If a tuple, any of the classes may match. 

1022 - `start`: Initial index to check. 

1023 - `end`: Initial index to *not* check. 

1024 """ 

1025 if not isinstance(childclass, tuple): 

1026 childclass = (childclass,) 

1027 for index in range(start, min(len(self), end)): 

1028 for c in childclass: 

1029 if isinstance(self[index], c): 

1030 return index 

1031 return None 

1032 

1033 def first_child_not_matching_class(self, childclass, start=0, 

1034 end=sys.maxsize): 

1035 """ 

1036 Return the index of the first child whose class does *not* match. 

1037 

1038 Parameters: 

1039 

1040 - `childclass`: A `Node` subclass to skip, or a tuple of `Node` 

1041 classes. If a tuple, none of the classes may match. 

1042 - `start`: Initial index to check. 

1043 - `end`: Initial index to *not* check. 

1044 """ 

1045 if not isinstance(childclass, tuple): 

1046 childclass = (childclass,) 

1047 for index in range(start, min(len(self), end)): 

1048 for c in childclass: 

1049 if isinstance(self.children[index], c): 

1050 break 

1051 else: 

1052 return index 

1053 return None 

1054 

1055 def pformat(self, indent=' ', level=0): 

1056 tagline = '%s%s\n' % (indent*level, self.starttag()) 

1057 childreps = (c.pformat(indent, level+1) for c in self.children) 

1058 return ''.join((tagline, *childreps)) 

1059 

1060 def copy(self): 

1061 obj = self.__class__(rawsource=self.rawsource, **self.attributes) 

1062 obj._document = self._document 

1063 obj.source = self.source 

1064 obj.line = self.line 

1065 return obj 

1066 

1067 def deepcopy(self): 

1068 copy = self.copy() 

1069 copy.extend([child.deepcopy() for child in self.children]) 

1070 return copy 

1071 

1072 def set_class(self, name): 

1073 """Add a new class to the "classes" attribute.""" 

1074 warnings.warn('docutils.nodes.Element.set_class() is deprecated; ' 

1075 ' and will be removed in Docutils 0.21 or later.' 

1076 "Append to Element['classes'] list attribute directly", 

1077 DeprecationWarning, stacklevel=2) 

1078 assert ' ' not in name 

1079 self['classes'].append(name.lower()) 

1080 

1081 def note_referenced_by(self, name=None, id=None): 

1082 """Note that this Element has been referenced by its name 

1083 `name` or id `id`.""" 

1084 self.referenced = True 

1085 # Element.expect_referenced_by_* dictionaries map names or ids 

1086 # to nodes whose ``referenced`` attribute is set to true as 

1087 # soon as this node is referenced by the given name or id. 

1088 # Needed for target propagation. 

1089 by_name = getattr(self, 'expect_referenced_by_name', {}).get(name) 

1090 by_id = getattr(self, 'expect_referenced_by_id', {}).get(id) 

1091 if by_name: 

1092 assert name is not None 

1093 by_name.referenced = True 

1094 if by_id: 

1095 assert id is not None 

1096 by_id.referenced = True 

1097 

1098 @classmethod 

1099 def is_not_list_attribute(cls, attr): 

1100 """ 

1101 Returns True if and only if the given attribute is NOT one of the 

1102 basic list attributes defined for all Elements. 

1103 """ 

1104 return attr not in cls.list_attributes 

1105 

1106 @classmethod 

1107 def is_not_known_attribute(cls, attr): 

1108 """ 

1109 Return True if `attr` is NOT defined for all Element instances. 

1110 

1111 Provisional. May be removed in Docutils 2.0. 

1112 """ 

1113 return attr not in cls.common_attributes 

1114 

1115 def validate_attributes(self): 

1116 """Normalize and validate element attributes. 

1117 

1118 Convert string values to expected datatype. 

1119 Normalize values. 

1120 

1121 Raise `ValueError` for invalid attributes or attribute values. 

1122 

1123 Provisional. 

1124 """ 

1125 messages = [] 

1126 for key, value in self.attributes.items(): 

1127 if key.startswith('internal:'): 

1128 continue # see docs/user/config.html#expose-internals 

1129 if key not in self.valid_attributes: 

1130 va = ' '.join(self.valid_attributes) 

1131 messages.append(f'Attribute "{key}" not one of "{va}".') 

1132 continue 

1133 try: 

1134 self.attributes[key] = ATTRIBUTE_VALIDATORS[key](value) 

1135 except (ValueError, TypeError, KeyError) as e: 

1136 messages.append( 

1137 f'Attribute "{key}" has invalid value "{value}".\n' 

1138 + e.args[0]) # message argument 

1139 if messages: 

1140 raise ValueError('\n'.join(messages)) 

1141 

1142 def validate(self): 

1143 messages = [] 

1144 try: 

1145 self.validate_attributes() 

1146 except ValueError as e: 

1147 messages.append(e.args[0]) # the message argument 

1148 # TODO: check number of children 

1149 n_min, n_max = self.valid_len 

1150 if len(self.children) < n_min: 

1151 messages.append(f'Expects at least {n_min} children, ' 

1152 f'not {len(self.children)}.') 

1153 if n_max is not None and len(self.children) > n_max: 

1154 messages.append(f'Expects at most {n_max} children, ' 

1155 f'not {len(self.children)}.') 

1156 for child in self.children: 

1157 if not isinstance(child, self.valid_children): 

1158 messages.append(f'May not contain "{child.tagname}" elements.') 

1159 child.validate() 

1160 if messages: 

1161 msg = f'Element <{self.tagname}> invalid:\n' + '\n'.join(messages) 

1162 try: 

1163 self.document.reporter.warning(msg) 

1164 except AttributeError: 

1165 raise ValueError(msg) 

1166 

1167 

1168# ======== 

1169# Mixins 

1170# ======== 

1171 

1172class Resolvable: 

1173 resolved = False 

1174 

1175 

1176class BackLinkable: 

1177 """Mixin for Elements that accept a "backrefs" attribute.""" 

1178 

1179 list_attributes = Element.list_attributes + ('backrefs',) 

1180 valid_attributes = Element.valid_attributes + ('backrefs',) 

1181 

1182 def add_backref(self, refid): 

1183 self['backrefs'].append(refid) 

1184 

1185 

1186# ==================== 

1187# Element Categories 

1188# ==================== 

1189 

1190class Root: 

1191 """Element at the root of a document tree.""" 

1192 

1193 

1194class Structural: 

1195 """`Structural elements`__. 

1196 

1197 __ https://docutils.sourceforge.io/docs/ref/doctree.html 

1198 #structural-elements 

1199 """ 

1200 

1201 

1202class SubRoot: 

1203 """Elements that may only be children of the root element.""" 

1204 

1205 

1206class SubStructural(SubRoot): 

1207 """`Structural subelements`__ are children of structural elements. 

1208 

1209 Most Structural elements accept only some of the SubStructural elements. 

1210 

1211 __ https://docutils.sourceforge.io/docs/ref/doctree.html 

1212 #structural-subelements 

1213 """ 

1214 

1215 

1216class Bibliographic: 

1217 """`Bibliographic Elements`__ (displayed document meta-data). 

1218 

1219 __ https://docutils.sourceforge.io/docs/ref/doctree.html 

1220 #bibliographic-elements 

1221 """ 

1222 

1223 

1224class Body: 

1225 """`Body elements`__. 

1226 

1227 __ https://docutils.sourceforge.io/docs/ref/doctree.html#body-elements 

1228 """ 

1229 

1230 

1231class Admonition(Body): 

1232 """Admonitions (distinctive and self-contained notices).""" 

1233 valid_children = Body # (%body.elements;) 

1234 

1235 

1236class Sequential(Body): 

1237 """List-like body elements.""" 

1238 

1239 

1240class General(Body): 

1241 """Miscellaneous body elements.""" 

1242 

1243 

1244class Special(Body): 

1245 """Special internal body elements.""" 

1246 

1247 

1248class Part: 

1249 """`Body Subelements`__ always occur within specific parent elements. 

1250 

1251 __ https://docutils.sourceforge.io/docs/ref/doctree.html#body-subelements 

1252 """ 

1253 

1254 

1255class Decorative: 

1256 """Decorative elements (`header` and `footer`). 

1257 

1258 Children of `decoration`. 

1259 """ 

1260 valid_children = Body # (%body.elements;) 

1261 

1262 

1263class Inline: 

1264 """Inline elements contain text data and possibly other inline elements. 

1265 """ 

1266 

1267 

1268# Orthogonal categories 

1269 

1270class PreBibliographic: 

1271 """Elements which may occur before Bibliographic Elements.""" 

1272 

1273 

1274class Invisible(Special, PreBibliographic): 

1275 """Internal elements that don't appear in output.""" 

1276 

1277 

1278class Labeled: 

1279 """Contains a `label` as its first element.""" 

1280 

1281 

1282class Referential(Resolvable): 

1283 """Elements holding a cross-reference (outgoing hyperlink).""" 

1284 

1285 

1286class Targetable(Resolvable): 

1287 """Cross-reference targets (incoming hyperlink).""" 

1288 referenced = 0 

1289 

1290 indirect_reference_name = None 

1291 """Holds the whitespace_normalized_name (contains mixed case) of a target. 

1292 Required for MoinMoin/reST compatibility. 

1293 

1294 Provisional. 

1295 """ 

1296 

1297 

1298class Titular: 

1299 """Title, sub-title, or informal heading (rubric).""" 

1300 

1301 

1302class TextElement(Element): 

1303 """ 

1304 An element which directly contains text. 

1305 

1306 Its children are all `Text` or `Inline` subclass nodes. You can 

1307 check whether an element's context is inline simply by checking whether 

1308 its immediate parent is a `TextElement` instance (including subclasses). 

1309 This is handy for nodes like `image` that can appear both inline and as 

1310 standalone body elements. 

1311 

1312 If passing children to `__init__()`, make sure to set `text` to 

1313 ``''`` or some other suitable value. 

1314 """ 

1315 valid_children = (Text, Inline) # (#PCDATA | %inline.elements;)* 

1316 valid_len = (0, None) 

1317 child_text_separator = '' 

1318 """Separator for child nodes, used by `astext()` method.""" 

1319 

1320 def __init__(self, rawsource='', text='', *children, **attributes): 

1321 if text != '': 

1322 textnode = Text(text) 

1323 Element.__init__(self, rawsource, textnode, *children, 

1324 **attributes) 

1325 else: 

1326 Element.__init__(self, rawsource, *children, **attributes) 

1327 

1328 

1329class FixedTextElement(TextElement): 

1330 """An element which directly contains preformatted text.""" 

1331 

1332 valid_attributes = Element.valid_attributes + ('xml:space',) 

1333 

1334 def __init__(self, rawsource='', text='', *children, **attributes): 

1335 super().__init__(rawsource, text, *children, **attributes) 

1336 self.attributes['xml:space'] = 'preserve' 

1337 

1338 

1339class PureTextElement(TextElement): 

1340 """An element which only contains text, no children.""" 

1341 valid_children = Text # (#PCDATA) 

1342 

1343 

1344# ============== 

1345# Root Element 

1346# ============== 

1347 

1348class document(Root, Element): 

1349 """ 

1350 The document root element. 

1351 

1352 Do not instantiate this class directly; use 

1353 `docutils.utils.new_document()` instead. 

1354 """ 

1355 valid_attributes = Element.valid_attributes + ('title',) 

1356 # content model: ( (title, subtitle?)?, 

1357 # meta*, 

1358 # decoration?, 

1359 # (docinfo, transition?)?, 

1360 # %structure.model; ) 

1361 valid_children = (Structural, SubRoot, Body) 

1362 valid_len = (0, None) # may be empty 

1363 

1364 def __init__(self, settings, reporter, *args, **kwargs): 

1365 Element.__init__(self, *args, **kwargs) 

1366 

1367 self.current_source = None 

1368 """Path to or description of the input source being processed.""" 

1369 

1370 self.current_line = None 

1371 """Line number (1-based) of `current_source`.""" 

1372 

1373 self.settings = settings 

1374 """Runtime settings data record.""" 

1375 

1376 self.reporter = reporter 

1377 """System message generator.""" 

1378 

1379 self.indirect_targets = [] 

1380 """List of indirect target nodes.""" 

1381 

1382 self.substitution_defs = {} 

1383 """Mapping of substitution names to substitution_definition nodes.""" 

1384 

1385 self.substitution_names = {} 

1386 """Mapping of case-normalized substitution names to case-sensitive 

1387 names.""" 

1388 

1389 self.refnames = {} 

1390 """Mapping of names to lists of referencing nodes.""" 

1391 

1392 self.refids = {} 

1393 """Mapping of ids to lists of referencing nodes.""" 

1394 

1395 self.nameids = {} 

1396 """Mapping of names to unique id's.""" 

1397 

1398 self.nametypes = {} 

1399 """Mapping of names to hyperlink type (boolean: True => explicit, 

1400 False => implicit.""" 

1401 

1402 self.ids = {} 

1403 """Mapping of ids to nodes.""" 

1404 

1405 self.footnote_refs = {} 

1406 """Mapping of footnote labels to lists of footnote_reference nodes.""" 

1407 

1408 self.citation_refs = {} 

1409 """Mapping of citation labels to lists of citation_reference nodes.""" 

1410 

1411 self.autofootnotes = [] 

1412 """List of auto-numbered footnote nodes.""" 

1413 

1414 self.autofootnote_refs = [] 

1415 """List of auto-numbered footnote_reference nodes.""" 

1416 

1417 self.symbol_footnotes = [] 

1418 """List of symbol footnote nodes.""" 

1419 

1420 self.symbol_footnote_refs = [] 

1421 """List of symbol footnote_reference nodes.""" 

1422 

1423 self.footnotes = [] 

1424 """List of manually-numbered footnote nodes.""" 

1425 

1426 self.citations = [] 

1427 """List of citation nodes.""" 

1428 

1429 self.autofootnote_start = 1 

1430 """Initial auto-numbered footnote number.""" 

1431 

1432 self.symbol_footnote_start = 0 

1433 """Initial symbol footnote symbol index.""" 

1434 

1435 self.id_counter = Counter() 

1436 """Numbers added to otherwise identical IDs.""" 

1437 

1438 self.parse_messages = [] 

1439 """System messages generated while parsing.""" 

1440 

1441 self.transform_messages = [] 

1442 """System messages generated while applying transforms.""" 

1443 

1444 import docutils.transforms 

1445 self.transformer = docutils.transforms.Transformer(self) 

1446 """Storage for transforms to be applied to this document.""" 

1447 

1448 self.include_log = [] 

1449 """The current source's parents (to detect inclusion loops).""" 

1450 

1451 self.decoration = None 

1452 """Document's `decoration` node.""" 

1453 

1454 self._document = self 

1455 

1456 def __getstate__(self): 

1457 """ 

1458 Return dict with unpicklable references removed. 

1459 """ 

1460 state = self.__dict__.copy() 

1461 state['reporter'] = None 

1462 state['transformer'] = None 

1463 return state 

1464 

1465 def asdom(self, dom=None): 

1466 """Return a DOM representation of this document.""" 

1467 if dom is None: 

1468 import xml.dom.minidom as dom 

1469 domroot = dom.Document() 

1470 domroot.appendChild(self._dom_node(domroot)) 

1471 return domroot 

1472 

1473 def set_id(self, node, msgnode=None, suggested_prefix=''): 

1474 if node['ids']: 

1475 # register and check for duplicates 

1476 for id in node['ids']: 

1477 self.ids.setdefault(id, node) 

1478 if self.ids[id] is not node: 

1479 msg = self.reporter.severe('Duplicate ID: "%s".' % id) 

1480 if msgnode is not None: 

1481 msgnode += msg 

1482 return id 

1483 # generate and set id 

1484 id_prefix = self.settings.id_prefix 

1485 auto_id_prefix = self.settings.auto_id_prefix 

1486 base_id = '' 

1487 id = '' 

1488 for name in node['names']: 

1489 if id_prefix: 

1490 # allow names starting with numbers if `id_prefix` 

1491 base_id = make_id('x'+name)[1:] 

1492 else: 

1493 base_id = make_id(name) 

1494 # TODO: normalize id-prefix? (would make code simpler) 

1495 id = id_prefix + base_id 

1496 if base_id and id not in self.ids: 

1497 break 

1498 else: 

1499 if base_id and auto_id_prefix.endswith('%'): 

1500 # disambiguate name-derived ID 

1501 # TODO: remove second condition after announcing change 

1502 prefix = id + '-' 

1503 else: 

1504 prefix = id_prefix + auto_id_prefix 

1505 if prefix.endswith('%'): 

1506 prefix = '%s%s-' % (prefix[:-1], 

1507 suggested_prefix 

1508 or make_id(node.tagname)) 

1509 while True: 

1510 self.id_counter[prefix] += 1 

1511 id = '%s%d' % (prefix, self.id_counter[prefix]) 

1512 if id not in self.ids: 

1513 break 

1514 node['ids'].append(id) 

1515 self.ids[id] = node 

1516 return id 

1517 

1518 def set_name_id_map(self, node, id, msgnode=None, explicit=None): 

1519 """ 

1520 `self.nameids` maps names to IDs, while `self.nametypes` maps names to 

1521 booleans representing hyperlink type (True==explicit, 

1522 False==implicit). This method updates the mappings. 

1523 

1524 The following state transition table shows how `self.nameids` items 

1525 ("id") and `self.nametypes` items ("type") change with new input 

1526 (a call to this method), and what actions are performed 

1527 ("implicit"-type system messages are INFO/1, and 

1528 "explicit"-type system messages are ERROR/3): 

1529 

1530 ==== ===== ======== ======== ======= ==== ===== ===== 

1531 Old State Input Action New State Notes 

1532 ----------- -------- ----------------- ----------- ----- 

1533 id type new type sys.msg. dupname id type 

1534 ==== ===== ======== ======== ======= ==== ===== ===== 

1535 - - explicit - - new True 

1536 - - implicit - - new False 

1537 - False explicit - - new True 

1538 old False explicit implicit old new True 

1539 - True explicit explicit new - True 

1540 old True explicit explicit new,old - True [#]_ 

1541 - False implicit implicit new - False 

1542 old False implicit implicit new,old - False 

1543 - True implicit implicit new - True 

1544 old True implicit implicit new old True 

1545 ==== ===== ======== ======== ======= ==== ===== ===== 

1546 

1547 .. [#] Do not clear the name-to-id map or invalidate the old target if 

1548 both old and new targets are external and refer to identical URIs. 

1549 The new target is invalidated regardless. 

1550 """ 

1551 for name in tuple(node['names']): 

1552 if name in self.nameids: 

1553 self.set_duplicate_name_id(node, id, name, msgnode, explicit) 

1554 # attention: modifies node['names'] 

1555 else: 

1556 self.nameids[name] = id 

1557 self.nametypes[name] = explicit 

1558 

1559 def set_duplicate_name_id(self, node, id, name, msgnode, explicit): 

1560 old_id = self.nameids[name] 

1561 old_explicit = self.nametypes[name] 

1562 self.nametypes[name] = old_explicit or explicit 

1563 if explicit: 

1564 if old_explicit: 

1565 level = 2 

1566 if old_id is not None: 

1567 old_node = self.ids[old_id] 

1568 if 'refuri' in node: 

1569 refuri = node['refuri'] 

1570 if (old_node['names'] 

1571 and 'refuri' in old_node 

1572 and old_node['refuri'] == refuri): 

1573 level = 1 # just inform if refuri's identical 

1574 if level > 1: 

1575 dupname(old_node, name) 

1576 self.nameids[name] = None 

1577 msg = self.reporter.system_message( 

1578 level, 'Duplicate explicit target name: "%s".' % name, 

1579 backrefs=[id], base_node=node) 

1580 if msgnode is not None: 

1581 msgnode += msg 

1582 dupname(node, name) 

1583 else: 

1584 self.nameids[name] = id 

1585 if old_id is not None: 

1586 old_node = self.ids[old_id] 

1587 dupname(old_node, name) 

1588 else: 

1589 if old_id is not None and not old_explicit: 

1590 self.nameids[name] = None 

1591 old_node = self.ids[old_id] 

1592 dupname(old_node, name) 

1593 dupname(node, name) 

1594 if not explicit or (not old_explicit and old_id is not None): 

1595 msg = self.reporter.info( 

1596 'Duplicate implicit target name: "%s".' % name, 

1597 backrefs=[id], base_node=node) 

1598 if msgnode is not None: 

1599 msgnode += msg 

1600 

1601 def has_name(self, name): 

1602 return name in self.nameids 

1603 

1604 # "note" here is an imperative verb: "take note of". 

1605 def note_implicit_target(self, target, msgnode=None): 

1606 id = self.set_id(target, msgnode) 

1607 self.set_name_id_map(target, id, msgnode, explicit=False) 

1608 

1609 def note_explicit_target(self, target, msgnode=None): 

1610 id = self.set_id(target, msgnode) 

1611 self.set_name_id_map(target, id, msgnode, explicit=True) 

1612 

1613 def note_refname(self, node): 

1614 self.refnames.setdefault(node['refname'], []).append(node) 

1615 

1616 def note_refid(self, node): 

1617 self.refids.setdefault(node['refid'], []).append(node) 

1618 

1619 def note_indirect_target(self, target): 

1620 self.indirect_targets.append(target) 

1621 if target['names']: 

1622 self.note_refname(target) 

1623 

1624 def note_anonymous_target(self, target): 

1625 self.set_id(target) 

1626 

1627 def note_autofootnote(self, footnote): 

1628 self.set_id(footnote) 

1629 self.autofootnotes.append(footnote) 

1630 

1631 def note_autofootnote_ref(self, ref): 

1632 self.set_id(ref) 

1633 self.autofootnote_refs.append(ref) 

1634 

1635 def note_symbol_footnote(self, footnote): 

1636 self.set_id(footnote) 

1637 self.symbol_footnotes.append(footnote) 

1638 

1639 def note_symbol_footnote_ref(self, ref): 

1640 self.set_id(ref) 

1641 self.symbol_footnote_refs.append(ref) 

1642 

1643 def note_footnote(self, footnote): 

1644 self.set_id(footnote) 

1645 self.footnotes.append(footnote) 

1646 

1647 def note_footnote_ref(self, ref): 

1648 self.set_id(ref) 

1649 self.footnote_refs.setdefault(ref['refname'], []).append(ref) 

1650 self.note_refname(ref) 

1651 

1652 def note_citation(self, citation): 

1653 self.citations.append(citation) 

1654 

1655 def note_citation_ref(self, ref): 

1656 self.set_id(ref) 

1657 self.citation_refs.setdefault(ref['refname'], []).append(ref) 

1658 self.note_refname(ref) 

1659 

1660 def note_substitution_def(self, subdef, def_name, msgnode=None): 

1661 name = whitespace_normalize_name(def_name) 

1662 if name in self.substitution_defs: 

1663 msg = self.reporter.error( 

1664 'Duplicate substitution definition name: "%s".' % name, 

1665 base_node=subdef) 

1666 if msgnode is not None: 

1667 msgnode += msg 

1668 oldnode = self.substitution_defs[name] 

1669 dupname(oldnode, name) 

1670 # keep only the last definition: 

1671 self.substitution_defs[name] = subdef 

1672 # case-insensitive mapping: 

1673 self.substitution_names[fully_normalize_name(name)] = name 

1674 

1675 def note_substitution_ref(self, subref, refname): 

1676 subref['refname'] = whitespace_normalize_name(refname) 

1677 

1678 def note_pending(self, pending, priority=None): 

1679 self.transformer.add_pending(pending, priority) 

1680 

1681 def note_parse_message(self, message): 

1682 self.parse_messages.append(message) 

1683 

1684 def note_transform_message(self, message): 

1685 self.transform_messages.append(message) 

1686 

1687 def note_source(self, source, offset): 

1688 self.current_source = source 

1689 if offset is None: 

1690 self.current_line = offset 

1691 else: 

1692 self.current_line = offset + 1 

1693 

1694 def copy(self): 

1695 obj = self.__class__(self.settings, self.reporter, 

1696 **self.attributes) 

1697 obj.source = self.source 

1698 obj.line = self.line 

1699 return obj 

1700 

1701 def get_decoration(self): 

1702 if not self.decoration: 

1703 self.decoration = decoration() 

1704 index = self.first_child_not_matching_class((Titular, meta)) 

1705 if index is None: 

1706 self.append(self.decoration) 

1707 else: 

1708 self.insert(index, self.decoration) 

1709 return self.decoration 

1710 

1711 

1712# ================ 

1713# Title Elements 

1714# ================ 

1715 

1716class title(Titular, PreBibliographic, SubStructural, TextElement): 

1717 valid_attributes = Element.valid_attributes + ('auto', 'refid') 

1718 

1719 

1720class subtitle(Titular, PreBibliographic, SubStructural, TextElement): pass 

1721class rubric(Titular, General, TextElement): pass 

1722 

1723 

1724# ================== 

1725# Meta-Data Element 

1726# ================== 

1727 

1728class meta(PreBibliographic, SubRoot, Element): 

1729 """Container for "invisible" bibliographic data, or meta-data.""" 

1730 valid_attributes = Element.valid_attributes + ( 

1731 'content', 'dir', 'http-equiv', 'lang', 'media', 'name', 'scheme') 

1732 valid_len = (0, 0) # The <meta> element has no content. 

1733 

1734 

1735# ======================== 

1736# Bibliographic Elements 

1737# ======================== 

1738 

1739class docinfo(SubRoot, Element): 

1740 """Container for displayed document meta-data.""" 

1741 valid_children = Bibliographic # (%bibliographic.elements;)+ 

1742 

1743 

1744class author(Bibliographic, TextElement): pass 

1745class organization(Bibliographic, TextElement): pass 

1746class address(Bibliographic, FixedTextElement): pass 

1747class contact(Bibliographic, TextElement): pass 

1748class version(Bibliographic, TextElement): pass 

1749class revision(Bibliographic, TextElement): pass 

1750class status(Bibliographic, TextElement): pass 

1751class date(Bibliographic, TextElement): pass 

1752class copyright(Bibliographic, TextElement): pass 

1753 

1754 

1755class authors(Bibliographic, Element): 

1756 """Container for author information for documents with multiple authors. 

1757 """ 

1758 # content model: (author, organization?, address?, contact?)+ 

1759 valid_children = (author, organization, address, contact) 

1760 

1761 

1762# ===================== 

1763# Decorative Elements 

1764# ===================== 

1765 

1766 

1767class decoration(PreBibliographic, SubRoot, Element): 

1768 """Container for `header` and `footer`.""" 

1769 valid_children = Decorative # (header?, footer?) 

1770 valid_len = (0, 2) # TODO: empty element does not make sense. 

1771 

1772 def get_header(self): 

1773 if not len(self.children) or not isinstance(self.children[0], header): 

1774 self.insert(0, header()) 

1775 return self.children[0] 

1776 

1777 def get_footer(self): 

1778 if not len(self.children) or not isinstance(self.children[-1], footer): 

1779 self.append(footer()) 

1780 return self.children[-1] 

1781 

1782 

1783class header(Decorative, Element): pass 

1784class footer(Decorative, Element): pass 

1785 

1786 

1787# ===================== 

1788# Structural Elements 

1789# ===================== 

1790 

1791class section(Structural, Element): 

1792 """Document section. The main unit of hierarchy.""" 

1793 # content model: (title, subtitle?, %structure.model;) 

1794 valid_children = (Structural, SubStructural, Body) 

1795 

1796 

1797class topic(Structural, Element): 

1798 """ 

1799 Topics are terminal, "leaf" mini-sections, like block quotes with titles, 

1800 or textual figures. A topic is just like a section, except that 

1801 it has no subsections, it does not get listed in the ToC, 

1802 and it doesn't have to conform to section placement rules. 

1803 

1804 Topics are allowed wherever body elements (list, table, etc.) are allowed, 

1805 but only at the top level of a sideber, section or document. 

1806 Topics cannot nest inside topics, or body elements; you can't have 

1807 a topic inside a table, list, block quote, etc. 

1808 """ 

1809 # "depth" and "local" attributes may be added by the "Contents" transform: 

1810 valid_attributes = Element.valid_attributes + ('depth', 'local') 

1811 valid_children = (title, Body) # (title?, (%body.elements;)+) 

1812 

1813 

1814class sidebar(Structural, Element): 

1815 """ 

1816 Sidebars are like miniature, parallel documents that occur inside other 

1817 documents, providing related or reference material. A sidebar is 

1818 typically offset by a border and "floats" to the side of the page; the 

1819 document's main text may flow around it. Sidebars can also be likened to 

1820 super-footnotes; their content is outside of the flow of the document's 

1821 main text. 

1822 

1823 Sidebars are allowed wherever body elements (list, table, etc.) are 

1824 allowed, but only at the top level of a section or document. Sidebars 

1825 cannot nest inside sidebars, topics, or body elements; you can't have a 

1826 sidebar inside a table, list, block quote, etc. 

1827 """ 

1828 # content model: ((title, subtitle?)?, (%body.elements; | topic)+) 

1829 valid_children = (title, subtitle, topic, Body) 

1830 

1831 

1832class transition(SubStructural, Element): 

1833 """Transitions are breaks between untitled text parts. 

1834 

1835 A transition may not begin or end a section or document, nor may two 

1836 transitions be immediately adjacent. 

1837 """ 

1838 valid_len = (0, 0) # empty element 

1839 

1840 

1841# =============== 

1842# Body Elements 

1843# =============== 

1844 

1845class paragraph(General, TextElement): pass 

1846 

1847 

1848class compound(General, Element): 

1849 valid_children = Body # (%body.elements;)+ 

1850 

1851 

1852class container(General, Element): 

1853 valid_children = Body # (%body.elements;)+ 

1854 

1855 

1856class attribution(Part, TextElement): 

1857 """Visible reference to the source of a `block_quote`.""" 

1858 

1859 

1860class block_quote(General, Element): 

1861 """An extended quotation, set off from the main text.""" 

1862 valid_children = (Body, attribution) # ((%body.elements;)+, attribution?) 

1863 

1864 

1865# Lists 

1866# ===== 

1867# 

1868# Lists (Sequential) and related Body Subelements (Part) 

1869 

1870class list_item(Part, Element): 

1871 valid_children = Body # (%body.elements;)* 

1872 valid_len = (0, None) 

1873 

1874 

1875class bullet_list(Sequential, Element): 

1876 valid_attributes = Element.valid_attributes + ('bullet',) 

1877 valid_children = list_item # (list_item+) 

1878 

1879 

1880class enumerated_list(Sequential, Element): 

1881 valid_attributes = Element.valid_attributes + ( 

1882 'enumtype', 'prefix', 'suffix', 'start') 

1883 valid_children = list_item # (list_item+) 

1884 

1885 

1886class term(Part, TextElement): pass 

1887class classifier(Part, TextElement): pass 

1888 

1889 

1890class definition(Part, Element): 

1891 """Definition of a `term` in a `definition_list`.""" 

1892 valid_children = Body # (%body.elements;)+ 

1893 

1894 

1895class definition_list_item(Part, Element): 

1896 valid_children = (term, classifier, definition) 

1897 valid_len = (2, None) # (term, classifier*, definition) 

1898 

1899 

1900class definition_list(Sequential, Element): 

1901 """List of terms and their definitions. 

1902 

1903 Can be used for glossaries or dictionaries, to describe or 

1904 classify things, for dialogues, or to itemize subtopics. 

1905 """ 

1906 valid_children = definition_list_item # (definition_list_item+) 

1907 

1908 

1909class field_name(Part, TextElement): pass 

1910 

1911 

1912class field_body(Part, Element): 

1913 valid_children = Body # (%body.elements;)* 

1914 valid_len = (0, None) 

1915 

1916 

1917class field(Part, Bibliographic, Element): 

1918 valid_children = (field_name, field_body) # (field_name, field_body) 

1919 valid_len = (2, 2) 

1920 

1921 

1922class field_list(Sequential, Element): 

1923 """List of label & data pairs. 

1924 

1925 Typically rendered as a two-column list. 

1926 Also used for extension syntax or special processing. 

1927 """ 

1928 valid_children = field # (field+) 

1929 

1930 

1931class option_string(Part, PureTextElement): 

1932 """A literal command-line option. Typically monospaced.""" 

1933 

1934 

1935class option_argument(Part, PureTextElement): 

1936 """Placeholder text for option arguments.""" 

1937 valid_attributes = Element.valid_attributes + ('delimiter',) 

1938 

1939 def astext(self): 

1940 return self.get('delimiter', ' ') + TextElement.astext(self) 

1941 

1942 

1943class option(Part, Element): 

1944 """Option element in an `option_list_item`. 

1945 

1946 Groups an option string with zero or more option argument placeholders. 

1947 """ 

1948 child_text_separator = '' 

1949 # content model: (option_string, option_argument*) 

1950 valid_children = (option_string, option_argument) 

1951 

1952 

1953class option_group(Part, Element): 

1954 """Groups together one or more `option` elements, all synonyms.""" 

1955 child_text_separator = ', ' 

1956 valid_children = option # (option+) 

1957 

1958 

1959class description(Part, Element): 

1960 """Describtion of a command-line option.""" 

1961 valid_children = Body # (%body.elements;)+ 

1962 

1963 

1964class option_list_item(Part, Element): 

1965 """Container for a pair of `option_group` and `description` elements. 

1966 """ 

1967 child_text_separator = ' ' 

1968 valid_children = (option_group, description) # (option_group, description) 

1969 valid_len = (2, 2) 

1970 

1971 

1972class option_list(Sequential, Element): 

1973 """Two-column list of command-line options and descriptions.""" 

1974 valid_children = option_list_item # (option_list_item+) 

1975 

1976 

1977# Pre-formatted text blocks 

1978# ========================= 

1979 

1980class literal_block(General, FixedTextElement): pass 

1981class doctest_block(General, FixedTextElement): pass 

1982 

1983 

1984class math_block(General, FixedTextElement, PureTextElement): 

1985 """Mathematical notation (display formula).""" 

1986 

1987 

1988class line(Part, TextElement): 

1989 """Single line of text in a `line_block`.""" 

1990 indent = None 

1991 

1992 

1993class line_block(General, Element): 

1994 """Sequence of lines and nested line blocks. 

1995 """ 

1996 # recursive content model: (line | line_block)+ 

1997 

1998 

1999line_block.valid_children = (line, line_block) 

2000 

2001 

2002# Admonitions 

2003# =========== 

2004# distinctive and self-contained notices 

2005 

2006class attention(Admonition, Element): pass 

2007class caution(Admonition, Element): pass 

2008class danger(Admonition, Element): pass 

2009class error(Admonition, Element): pass 

2010class important(Admonition, Element): pass 

2011class note(Admonition, Element): pass 

2012class tip(Admonition, Element): pass 

2013class hint(Admonition, Element): pass 

2014class warning(Admonition, Element): pass 

2015 

2016 

2017class admonition(Admonition, Element): 

2018 valid_children = (title, Body) # (title, (%body.elements;)+) 

2019 valid_len = (2, None) 

2020 

2021 

2022# Invisible elements 

2023# ================== 

2024 

2025class comment(Invisible, FixedTextElement, PureTextElement): 

2026 """Author notes, hidden from the output.""" 

2027 

2028 

2029class substitution_definition(Invisible, TextElement): 

2030 valid_attributes = Element.valid_attributes + ('ltrim', 'rtrim') 

2031 

2032 

2033class target(Invisible, Inline, TextElement, Targetable): 

2034 valid_attributes = Element.valid_attributes + ( 

2035 'anonymous', 'refid', 'refname', 'refuri') 

2036 

2037 

2038# Footnote and citation 

2039# ===================== 

2040 

2041class label(Part, PureTextElement): 

2042 """Visible identifier for footnotes and citations.""" 

2043 

2044 

2045class footnote(General, BackLinkable, Element, Labeled, Targetable): 

2046 """Labelled note providing additional context (footnote or endnote).""" 

2047 valid_attributes = Element.valid_attributes + ('auto', 'backrefs') 

2048 valid_children = (label, Body) # (label?, (%body.elements;)+) 

2049 

2050 

2051class citation(General, BackLinkable, Element, Labeled, Targetable): 

2052 valid_children = (label, Body) # (label, (%body.elements;)+) 

2053 valid_len = (2, None) 

2054 

2055 

2056# Graphical elements 

2057# ================== 

2058 

2059class image(General, Inline, Element): 

2060 """Reference to an image resource. 

2061 

2062 May be body element or inline element. 

2063 """ 

2064 valid_attributes = Element.valid_attributes + ( 

2065 'uri', 'alt', 'align', 'height', 'width', 'scale', 'loading') 

2066 valid_len = (0, 0) # emtpy element 

2067 

2068 def astext(self): 

2069 return self.get('alt', '') 

2070 

2071 

2072class caption(Part, TextElement): pass 

2073 

2074 

2075class legend(Part, Element): 

2076 """A wrapper for text accompanying a `figure` that is not the caption.""" 

2077 valid_children = Body # (%body.elements;) 

2078 

2079 

2080class figure(General, Element): 

2081 """A formal figure, generally an illustration, with a title.""" 

2082 valid_attributes = Element.valid_attributes + ('align', 'width') 

2083 # content model: (image, ((caption, legend?) | legend)) 

2084 valid_children = (image, caption, legend) 

2085 valid_len = (1, 3) 

2086 # TODO: According to the DTD, a caption or legend is required 

2087 # but rST allows "bare" figures which are formatted differently from 

2088 # images (floating in LaTeX, nested in a <figure> in HTML). 

2089 

2090 

2091# Tables 

2092# ====== 

2093 

2094class entry(Part, Element): 

2095 """An entry in a `row` (a table cell).""" 

2096 valid_attributes = Element.valid_attributes + ( 

2097 'align', 'char', 'charoff', 'colname', 'colsep', 'morecols', 

2098 'morerows', 'namest', 'nameend', 'rowsep', 'valign') 

2099 valid_children = Body # %tbl.entry.mdl -> (%body.elements;)* 

2100 valid_len = (0, None) # may be empty 

2101 

2102 

2103class row(Part, Element): 

2104 """Row of table cells.""" 

2105 valid_attributes = Element.valid_attributes + ('rowsep', 'valign') 

2106 valid_children = entry # (%tbl.row.mdl;) -> entry+ 

2107 

2108 

2109class colspec(Part, Element): 

2110 """Specifications for a column in a `tgroup`.""" 

2111 valid_attributes = Element.valid_attributes + ( 

2112 'align', 'char', 'charoff', 'colname', 'colnum', 

2113 'colsep', 'colwidth', 'rowsep', 'stub') 

2114 valid_len = (0, 0) # empty element 

2115 

2116 

2117class thead(Part, Element): 

2118 """Row(s) that form the head of a `tgroup`.""" 

2119 valid_attributes = Element.valid_attributes + ('valign',) 

2120 valid_children = row # (row+) 

2121 

2122 

2123class tbody(Part, Element): 

2124 """Body of a `tgroup`.""" 

2125 valid_attributes = Element.valid_attributes + ('valign',) 

2126 valid_children = row # (row+) 

2127 

2128 

2129class tgroup(Part, Element): 

2130 """A portion of a table. Most tables have just one `tgroup`.""" 

2131 valid_attributes = Element.valid_attributes + ( 

2132 'align', 'cols', 'colsep', 'rowsep') 

2133 valid_children = (colspec, thead, tbody) # (colspec*, thead?, tbody) 

2134 

2135 

2136class table(General, Element): 

2137 """A data arrangement with rows and columns.""" 

2138 valid_attributes = Element.valid_attributes + ( 

2139 'align', 'colsep', 'frame', 'pgwide', 'rowsep', 'width') 

2140 valid_children = (title, tgroup) # (title?, tgroup+) 

2141 

2142 

2143# Special purpose elements 

2144# ======================== 

2145 

2146class system_message(Special, BackLinkable, PreBibliographic, Element): 

2147 """ 

2148 System message element. 

2149 

2150 Do not instantiate this class directly; use 

2151 ``document.reporter.info/warning/error/severe()`` instead. 

2152 """ 

2153 valid_attributes = BackLinkable.valid_attributes + ( 

2154 'level', 'line', 'type') 

2155 valid_children = Body # (%body.elements;)+ 

2156 

2157 def __init__(self, message=None, *children, **attributes): 

2158 rawsource = attributes.pop('rawsource', '') 

2159 if message: 

2160 p = paragraph('', message) 

2161 children = (p,) + children 

2162 try: 

2163 Element.__init__(self, rawsource, *children, **attributes) 

2164 except: # noqa catchall 

2165 print('system_message: children=%r' % (children,)) 

2166 raise 

2167 

2168 def astext(self): 

2169 line = self.get('line', '') 

2170 return '%s:%s: (%s/%s) %s' % (self['source'], line, self['type'], 

2171 self['level'], Element.astext(self)) 

2172 

2173 

2174class pending(Invisible, Element): 

2175 """ 

2176 Placeholder for pending operations. 

2177 

2178 The "pending" element is used to encapsulate a pending operation: the 

2179 operation (transform), the point at which to apply it, and any data it 

2180 requires. Only the pending operation's location within the document is 

2181 stored in the public document tree (by the "pending" object itself); the 

2182 operation and its data are stored in the "pending" object's internal 

2183 instance attributes. 

2184 

2185 For example, say you want a table of contents in your reStructuredText 

2186 document. The easiest way to specify where to put it is from within the 

2187 document, with a directive:: 

2188 

2189 .. contents:: 

2190 

2191 But the "contents" directive can't do its work until the entire document 

2192 has been parsed and possibly transformed to some extent. So the directive 

2193 code leaves a placeholder behind that will trigger the second phase of its 

2194 processing, something like this:: 

2195 

2196 <pending ...public attributes...> + internal attributes 

2197 

2198 Use `document.note_pending()` so that the 

2199 `docutils.transforms.Transformer` stage of processing can run all pending 

2200 transforms. 

2201 """ 

2202 valid_len = (0, 0) # empty element 

2203 

2204 def __init__(self, transform, details=None, 

2205 rawsource='', *children, **attributes): 

2206 Element.__init__(self, rawsource, *children, **attributes) 

2207 

2208 self.transform = transform 

2209 """The `docutils.transforms.Transform` class implementing the pending 

2210 operation.""" 

2211 

2212 self.details = details or {} 

2213 """Detail data (dictionary) required by the pending operation.""" 

2214 

2215 def pformat(self, indent=' ', level=0): 

2216 internals = ['.. internal attributes:', 

2217 ' .transform: %s.%s' % (self.transform.__module__, 

2218 self.transform.__name__), 

2219 ' .details:'] 

2220 details = sorted(self.details.items()) 

2221 for key, value in details: 

2222 if isinstance(value, Node): 

2223 internals.append('%7s%s:' % ('', key)) 

2224 internals.extend(['%9s%s' % ('', line) 

2225 for line in value.pformat().splitlines()]) 

2226 elif (value 

2227 and isinstance(value, list) 

2228 and isinstance(value[0], Node)): 

2229 internals.append('%7s%s:' % ('', key)) 

2230 for v in value: 

2231 internals.extend(['%9s%s' % ('', line) 

2232 for line in v.pformat().splitlines()]) 

2233 else: 

2234 internals.append('%7s%s: %r' % ('', key, value)) 

2235 return (Element.pformat(self, indent, level) 

2236 + ''.join((' %s%s\n' % (indent * level, line)) 

2237 for line in internals)) 

2238 

2239 def copy(self): 

2240 obj = self.__class__(self.transform, self.details, self.rawsource, 

2241 **self.attributes) 

2242 obj._document = self._document 

2243 obj.source = self.source 

2244 obj.line = self.line 

2245 return obj 

2246 

2247 

2248class raw(Special, Inline, PreBibliographic, 

2249 FixedTextElement, PureTextElement): 

2250 """Raw data that is to be passed untouched to the Writer. 

2251 """ 

2252 valid_attributes = Element.valid_attributes + ('format', 'xml:space') 

2253 

2254 

2255# ================= 

2256# Inline Elements 

2257# ================= 

2258 

2259class emphasis(Inline, TextElement): pass 

2260class strong(Inline, TextElement): pass 

2261class literal(Inline, TextElement): pass 

2262 

2263 

2264class reference(General, Inline, Referential, TextElement): 

2265 valid_attributes = Element.valid_attributes + ( 

2266 'anonymous', 'name', 'refid', 'refname', 'refuri') 

2267 

2268 

2269class footnote_reference(Inline, Referential, PureTextElement): 

2270 valid_attributes = Element.valid_attributes + ('auto', 'refid', 'refname') 

2271 

2272 

2273class citation_reference(Inline, Referential, PureTextElement): 

2274 valid_attributes = Element.valid_attributes + ('refid', 'refname') 

2275 

2276 

2277class substitution_reference(Inline, TextElement): 

2278 valid_attributes = Element.valid_attributes + ('refname',) 

2279 

2280 

2281class title_reference(Inline, TextElement): pass 

2282class abbreviation(Inline, TextElement): pass 

2283class acronym(Inline, TextElement): pass 

2284class superscript(Inline, TextElement): pass 

2285class subscript(Inline, TextElement): pass 

2286 

2287 

2288class math(Inline, PureTextElement): 

2289 """Mathematical notation in running text.""" 

2290 

2291 

2292class inline(Inline, TextElement): pass 

2293 

2294 

2295class problematic(Inline, TextElement): 

2296 valid_attributes = Element.valid_attributes + ( 

2297 'refid', 'refname', 'refuri') 

2298 

2299 

2300class generated(Inline, TextElement): pass 

2301 

2302 

2303# ======================================== 

2304# Auxiliary Classes, Functions, and Data 

2305# ======================================== 

2306 

2307node_class_names = """ 

2308 Text 

2309 abbreviation acronym address admonition attention attribution author 

2310 authors 

2311 block_quote bullet_list 

2312 caption caution citation citation_reference classifier colspec comment 

2313 compound contact container copyright 

2314 danger date decoration definition definition_list definition_list_item 

2315 description docinfo doctest_block document 

2316 emphasis entry enumerated_list error 

2317 field field_body field_list field_name figure footer 

2318 footnote footnote_reference 

2319 generated 

2320 header hint 

2321 image important inline 

2322 label legend line line_block list_item literal literal_block 

2323 math math_block meta 

2324 note 

2325 option option_argument option_group option_list option_list_item 

2326 option_string organization 

2327 paragraph pending problematic 

2328 raw reference revision row rubric 

2329 section sidebar status strong subscript substitution_definition 

2330 substitution_reference subtitle superscript system_message 

2331 table target tbody term tgroup thead tip title title_reference topic 

2332 transition 

2333 version 

2334 warning""".split() 

2335"""A list of names of all concrete Node subclasses.""" 

2336 

2337 

2338class NodeVisitor: 

2339 """ 

2340 "Visitor" pattern [GoF95]_ abstract superclass implementation for 

2341 document tree traversals. 

2342 

2343 Each node class has corresponding methods, doing nothing by 

2344 default; override individual methods for specific and useful 

2345 behaviour. The `dispatch_visit()` method is called by 

2346 `Node.walk()` upon entering a node. `Node.walkabout()` also calls 

2347 the `dispatch_departure()` method before exiting a node. 

2348 

2349 The dispatch methods call "``visit_`` + node class name" or 

2350 "``depart_`` + node class name", resp. 

2351 

2352 This is a base class for visitors whose ``visit_...`` & ``depart_...`` 

2353 methods must be implemented for *all* compulsory node types encountered 

2354 (such as for `docutils.writers.Writer` subclasses). 

2355 Unimplemented methods will raise exceptions (except for optional nodes). 

2356 

2357 For sparse traversals, where only certain node types are of interest, use 

2358 subclass `SparseNodeVisitor` instead. When (mostly or entirely) uniform 

2359 processing is desired, subclass `GenericNodeVisitor`. 

2360 

2361 .. [GoF95] Gamma, Helm, Johnson, Vlissides. *Design Patterns: Elements of 

2362 Reusable Object-Oriented Software*. Addison-Wesley, Reading, MA, USA, 

2363 1995. 

2364 """ 

2365 

2366 optional = ('meta',) 

2367 """ 

2368 Tuple containing node class names (as strings). 

2369 

2370 No exception will be raised if writers do not implement visit 

2371 or departure functions for these node classes. 

2372 

2373 Used to ensure transitional compatibility with existing 3rd-party writers. 

2374 """ 

2375 

2376 def __init__(self, document): 

2377 self.document = document 

2378 

2379 def dispatch_visit(self, node): 

2380 """ 

2381 Call self."``visit_`` + node class name" with `node` as 

2382 parameter. If the ``visit_...`` method does not exist, call 

2383 self.unknown_visit. 

2384 """ 

2385 node_name = node.__class__.__name__ 

2386 method = getattr(self, 'visit_' + node_name, self.unknown_visit) 

2387 self.document.reporter.debug( 

2388 'docutils.nodes.NodeVisitor.dispatch_visit calling %s for %s' 

2389 % (method.__name__, node_name)) 

2390 return method(node) 

2391 

2392 def dispatch_departure(self, node): 

2393 """ 

2394 Call self."``depart_`` + node class name" with `node` as 

2395 parameter. If the ``depart_...`` method does not exist, call 

2396 self.unknown_departure. 

2397 """ 

2398 node_name = node.__class__.__name__ 

2399 method = getattr(self, 'depart_' + node_name, self.unknown_departure) 

2400 self.document.reporter.debug( 

2401 'docutils.nodes.NodeVisitor.dispatch_departure calling %s for %s' 

2402 % (method.__name__, node_name)) 

2403 return method(node) 

2404 

2405 def unknown_visit(self, node): 

2406 """ 

2407 Called when entering unknown `Node` types. 

2408 

2409 Raise an exception unless overridden. 

2410 """ 

2411 if (self.document.settings.strict_visitor 

2412 or node.__class__.__name__ not in self.optional): 

2413 raise NotImplementedError( 

2414 '%s visiting unknown node type: %s' 

2415 % (self.__class__, node.__class__.__name__)) 

2416 

2417 def unknown_departure(self, node): 

2418 """ 

2419 Called before exiting unknown `Node` types. 

2420 

2421 Raise exception unless overridden. 

2422 """ 

2423 if (self.document.settings.strict_visitor 

2424 or node.__class__.__name__ not in self.optional): 

2425 raise NotImplementedError( 

2426 '%s departing unknown node type: %s' 

2427 % (self.__class__, node.__class__.__name__)) 

2428 

2429 

2430class SparseNodeVisitor(NodeVisitor): 

2431 """ 

2432 Base class for sparse traversals, where only certain node types are of 

2433 interest. When ``visit_...`` & ``depart_...`` methods should be 

2434 implemented for *all* node types (such as for `docutils.writers.Writer` 

2435 subclasses), subclass `NodeVisitor` instead. 

2436 """ 

2437 

2438 

2439class GenericNodeVisitor(NodeVisitor): 

2440 """ 

2441 Generic "Visitor" abstract superclass, for simple traversals. 

2442 

2443 Unless overridden, each ``visit_...`` method calls `default_visit()`, and 

2444 each ``depart_...`` method (when using `Node.walkabout()`) calls 

2445 `default_departure()`. `default_visit()` (and `default_departure()`) must 

2446 be overridden in subclasses. 

2447 

2448 Define fully generic visitors by overriding `default_visit()` (and 

2449 `default_departure()`) only. Define semi-generic visitors by overriding 

2450 individual ``visit_...()`` (and ``depart_...()``) methods also. 

2451 

2452 `NodeVisitor.unknown_visit()` (`NodeVisitor.unknown_departure()`) should 

2453 be overridden for default behavior. 

2454 """ 

2455 

2456 def default_visit(self, node): 

2457 """Override for generic, uniform traversals.""" 

2458 raise NotImplementedError 

2459 

2460 def default_departure(self, node): 

2461 """Override for generic, uniform traversals.""" 

2462 raise NotImplementedError 

2463 

2464 

2465def _call_default_visit(self, node): 

2466 self.default_visit(node) 

2467 

2468 

2469def _call_default_departure(self, node): 

2470 self.default_departure(node) 

2471 

2472 

2473def _nop(self, node): 

2474 pass 

2475 

2476 

2477def _add_node_class_names(names): 

2478 """Save typing with dynamic assignments:""" 

2479 for _name in names: 

2480 setattr(GenericNodeVisitor, "visit_" + _name, _call_default_visit) 

2481 setattr(GenericNodeVisitor, "depart_" + _name, _call_default_departure) 

2482 setattr(SparseNodeVisitor, 'visit_' + _name, _nop) 

2483 setattr(SparseNodeVisitor, 'depart_' + _name, _nop) 

2484 

2485 

2486_add_node_class_names(node_class_names) 

2487 

2488 

2489class TreeCopyVisitor(GenericNodeVisitor): 

2490 """ 

2491 Make a complete copy of a tree or branch, including element attributes. 

2492 """ 

2493 

2494 def __init__(self, document): 

2495 GenericNodeVisitor.__init__(self, document) 

2496 self.parent_stack = [] 

2497 self.parent = [] 

2498 

2499 def get_tree_copy(self): 

2500 return self.parent[0] 

2501 

2502 def default_visit(self, node): 

2503 """Copy the current node, and make it the new acting parent.""" 

2504 newnode = node.copy() 

2505 self.parent.append(newnode) 

2506 self.parent_stack.append(self.parent) 

2507 self.parent = newnode 

2508 

2509 def default_departure(self, node): 

2510 """Restore the previous acting parent.""" 

2511 self.parent = self.parent_stack.pop() 

2512 

2513 

2514class TreePruningException(Exception): 

2515 """ 

2516 Base class for `NodeVisitor`-related tree pruning exceptions. 

2517 

2518 Raise subclasses from within ``visit_...`` or ``depart_...`` methods 

2519 called from `Node.walk()` and `Node.walkabout()` tree traversals to prune 

2520 the tree traversed. 

2521 """ 

2522 

2523 

2524class SkipChildren(TreePruningException): 

2525 """ 

2526 Do not visit any children of the current node. The current node's 

2527 siblings and ``depart_...`` method are not affected. 

2528 """ 

2529 

2530 

2531class SkipSiblings(TreePruningException): 

2532 """ 

2533 Do not visit any more siblings (to the right) of the current node. The 

2534 current node's children and its ``depart_...`` method are not affected. 

2535 """ 

2536 

2537 

2538class SkipNode(TreePruningException): 

2539 """ 

2540 Do not visit the current node's children, and do not call the current 

2541 node's ``depart_...`` method. 

2542 """ 

2543 

2544 

2545class SkipDeparture(TreePruningException): 

2546 """ 

2547 Do not call the current node's ``depart_...`` method. The current node's 

2548 children and siblings are not affected. 

2549 """ 

2550 

2551 

2552class NodeFound(TreePruningException): 

2553 """ 

2554 Raise to indicate that the target of a search has been found. This 

2555 exception must be caught by the client; it is not caught by the traversal 

2556 code. 

2557 """ 

2558 

2559 

2560class StopTraversal(TreePruningException): 

2561 """ 

2562 Stop the traversal altogether. The current node's ``depart_...`` method 

2563 is not affected. The parent nodes ``depart_...`` methods are also called 

2564 as usual. No other nodes are visited. This is an alternative to 

2565 NodeFound that does not cause exception handling to trickle up to the 

2566 caller. 

2567 """ 

2568 

2569 

2570# definition moved here from `utils` to avoid circular import dependency 

2571def unescape(text, restore_backslashes=False, respect_whitespace=False): 

2572 """ 

2573 Return a string with nulls removed or restored to backslashes. 

2574 Backslash-escaped spaces are also removed. 

2575 """ 

2576 # `respect_whitespace` is ignored (since introduction 2016-12-16) 

2577 if restore_backslashes: 

2578 return text.replace('\x00', '\\') 

2579 else: 

2580 for sep in ['\x00 ', '\x00\n', '\x00']: 

2581 text = ''.join(text.split(sep)) 

2582 return text 

2583 

2584 

2585def make_id(string): 

2586 """ 

2587 Convert `string` into an identifier and return it. 

2588 

2589 Docutils identifiers will conform to the regular expression 

2590 ``[a-z](-?[a-z0-9]+)*``. For CSS compatibility, identifiers (the "class" 

2591 and "id" attributes) should have no underscores, colons, or periods. 

2592 Hyphens may be used. 

2593 

2594 - The `HTML 4.01 spec`_ defines identifiers based on SGML tokens: 

2595 

2596 ID and NAME tokens must begin with a letter ([A-Za-z]) and may be 

2597 followed by any number of letters, digits ([0-9]), hyphens ("-"), 

2598 underscores ("_"), colons (":"), and periods ("."). 

2599 

2600 - However the `CSS1 spec`_ defines identifiers based on the "name" token, 

2601 a tighter interpretation ("flex" tokenizer notation; "latin1" and 

2602 "escape" 8-bit characters have been replaced with entities):: 

2603 

2604 unicode \\[0-9a-f]{1,4} 

2605 latin1 [&iexcl;-&yuml;] 

2606 escape {unicode}|\\[ -~&iexcl;-&yuml;] 

2607 nmchar [-a-z0-9]|{latin1}|{escape} 

2608 name {nmchar}+ 

2609 

2610 The CSS1 "nmchar" rule does not include underscores ("_"), colons (":"), 

2611 or periods ("."), therefore "class" and "id" attributes should not contain 

2612 these characters. They should be replaced with hyphens ("-"). Combined 

2613 with HTML's requirements (the first character must be a letter; no 

2614 "unicode", "latin1", or "escape" characters), this results in the 

2615 ``[a-z](-?[a-z0-9]+)*`` pattern. 

2616 

2617 .. _HTML 4.01 spec: https://www.w3.org/TR/html401 

2618 .. _CSS1 spec: https://www.w3.org/TR/REC-CSS1 

2619 """ 

2620 id = string.lower() 

2621 id = id.translate(_non_id_translate_digraphs) 

2622 id = id.translate(_non_id_translate) 

2623 # get rid of non-ascii characters. 

2624 # 'ascii' lowercase to prevent problems with turkish locale. 

2625 id = unicodedata.normalize( 

2626 'NFKD', id).encode('ascii', 'ignore').decode('ascii') 

2627 # shrink runs of whitespace and replace by hyphen 

2628 id = _non_id_chars.sub('-', ' '.join(id.split())) 

2629 id = _non_id_at_ends.sub('', id) 

2630 return str(id) 

2631 

2632 

2633_non_id_chars = re.compile('[^a-z0-9]+') 

2634_non_id_at_ends = re.compile('^[-0-9]+|-+$') 

2635_non_id_translate = { 

2636 0x00f8: 'o', # o with stroke 

2637 0x0111: 'd', # d with stroke 

2638 0x0127: 'h', # h with stroke 

2639 0x0131: 'i', # dotless i 

2640 0x0142: 'l', # l with stroke 

2641 0x0167: 't', # t with stroke 

2642 0x0180: 'b', # b with stroke 

2643 0x0183: 'b', # b with topbar 

2644 0x0188: 'c', # c with hook 

2645 0x018c: 'd', # d with topbar 

2646 0x0192: 'f', # f with hook 

2647 0x0199: 'k', # k with hook 

2648 0x019a: 'l', # l with bar 

2649 0x019e: 'n', # n with long right leg 

2650 0x01a5: 'p', # p with hook 

2651 0x01ab: 't', # t with palatal hook 

2652 0x01ad: 't', # t with hook 

2653 0x01b4: 'y', # y with hook 

2654 0x01b6: 'z', # z with stroke 

2655 0x01e5: 'g', # g with stroke 

2656 0x0225: 'z', # z with hook 

2657 0x0234: 'l', # l with curl 

2658 0x0235: 'n', # n with curl 

2659 0x0236: 't', # t with curl 

2660 0x0237: 'j', # dotless j 

2661 0x023c: 'c', # c with stroke 

2662 0x023f: 's', # s with swash tail 

2663 0x0240: 'z', # z with swash tail 

2664 0x0247: 'e', # e with stroke 

2665 0x0249: 'j', # j with stroke 

2666 0x024b: 'q', # q with hook tail 

2667 0x024d: 'r', # r with stroke 

2668 0x024f: 'y', # y with stroke 

2669} 

2670_non_id_translate_digraphs = { 

2671 0x00df: 'sz', # ligature sz 

2672 0x00e6: 'ae', # ae 

2673 0x0153: 'oe', # ligature oe 

2674 0x0238: 'db', # db digraph 

2675 0x0239: 'qp', # qp digraph 

2676} 

2677 

2678 

2679def dupname(node, name): 

2680 node['dupnames'].append(name) 

2681 node['names'].remove(name) 

2682 # Assume that `node` is referenced, even though it isn't; 

2683 # we don't want to throw unnecessary system_messages. 

2684 node.referenced = True 

2685 

2686 

2687def fully_normalize_name(name): 

2688 """Return a case- and whitespace-normalized name.""" 

2689 return ' '.join(name.lower().split()) 

2690 

2691 

2692def whitespace_normalize_name(name): 

2693 """Return a whitespace-normalized name.""" 

2694 return ' '.join(name.split()) 

2695 

2696 

2697def serial_escape(value): 

2698 """Escape string values that are elements of a list, for serialization.""" 

2699 return value.replace('\\', r'\\').replace(' ', r'\ ') 

2700 

2701 

2702def split_name_list(s): 

2703 r"""Split a string at non-escaped whitespace. 

2704 

2705 Backslashes escape internal whitespace (cf. `serial_escape()`). 

2706 Return list of "names" (after removing escaping backslashes). 

2707 

2708 >>> split_name_list(r'a\ n\ame two\\ n\\ames'), 

2709 ['a name', 'two\\', r'n\ames'] 

2710 

2711 Provisional. 

2712 """ 

2713 s = s.replace('\\', '\x00') # escape with NULL char 

2714 s = s.replace('\x00\x00', '\\') # unescape backslashes 

2715 s = s.replace('\x00 ', '\x00\x00') # escaped spaces -> NULL NULL 

2716 names = s.split(' ') 

2717 # restore internal spaces, drop other escaping characters 

2718 return [name.replace('\x00\x00', ' ').replace('\x00', '') 

2719 for name in names] 

2720 

2721 

2722def pseudo_quoteattr(value): 

2723 """Quote attributes for pseudo-xml""" 

2724 return '"%s"' % value 

2725 

2726 

2727# Methods to validate `Element attribute`__ values. 

2728 

2729# Ensure the expected Python `data type`__, normalize, and check for 

2730# restrictions. 

2731# 

2732# The methods can be used to convert `str` values (eg. from an XML 

2733# representation) or to validate an existing document tree or node. 

2734# 

2735# Cf. `Element.validate_attributes()`, `docutils.parsers.docutils_xml`, 

2736# and the `attribute_validating_functions` mapping below. 

2737# 

2738# __ https://docutils.sourceforge.io/docs/ref/doctree.html#attribute-reference 

2739# __ https://docutils.sourceforge.io/docs/ref/doctree.html#attribute-types 

2740 

2741def validate_enumerated_type(*keywords): 

2742 """ 

2743 Return a function that validates a `str` against given `keywords`. 

2744 

2745 Provisional. 

2746 """ 

2747 def validate_keywords(value): 

2748 if value not in keywords: 

2749 allowed = '", \"'.join(keywords) 

2750 raise ValueError(f'"{value}" is not one of "{allowed}".') 

2751 return value 

2752 return validate_keywords 

2753 

2754 

2755def validate_identifier(value): 

2756 """ 

2757 Validate identifier key or class name. 

2758 

2759 Used in `idref.type`__ and for the tokens in `validate_identifier_list()`. 

2760 

2761 __ https://docutils.sourceforge.io/docs/ref/doctree.html#idref-type 

2762 

2763 Provisional. 

2764 """ 

2765 if value != make_id(value): 

2766 raise ValueError(f'"{value}" is no valid id or class name.') 

2767 return value 

2768 

2769 

2770def validate_identifier_list(value): 

2771 """ 

2772 A (space-separated) list of ids or class names. 

2773 

2774 `value` may be a `list` or a `str` with space separated 

2775 ids or class names (cf. `validate_identifier()`). 

2776 

2777 Used in `classnames.type`__, `ids.type`__, and `idrefs.type`__. 

2778 

2779 __ https://docutils.sourceforge.io/docs/ref/doctree.html#classnames-type 

2780 __ https://docutils.sourceforge.io/docs/ref/doctree.html#ids-type 

2781 __ https://docutils.sourceforge.io/docs/ref/doctree.html#idrefs-type 

2782 

2783 Provisional. 

2784 """ 

2785 if isinstance(value, str): 

2786 value = value.split() 

2787 for token in value: 

2788 validate_identifier(token) 

2789 return value 

2790 

2791 

2792def validate_measure(value): 

2793 """ 

2794 Validate a length measure__ (number + recognized unit). 

2795 

2796 __ https://docutils.sourceforge.io/docs/ref/doctree.html#measure 

2797 

2798 Provisional. 

2799 """ 

2800 units = 'em|ex|px|in|cm|mm|pt|pc|%' 

2801 if not re.fullmatch(f'[-0-9.]+ *({units}?)', value): 

2802 raise ValueError(f'"{value}" is no valid measure. ' 

2803 f'Valid units: {units.replace("|", " ")}.') 

2804 return value.replace(' ', '').strip() 

2805 

2806 

2807def validate_NMTOKEN(value): 

2808 """ 

2809 Validate a "name token": a `str` of letters, digits, and [-._]. 

2810 

2811 Provisional. 

2812 """ 

2813 if not re.fullmatch('[-._A-Za-z0-9]+', value): 

2814 raise ValueError(f'"{value}" is no NMTOKEN.') 

2815 return value 

2816 

2817 

2818def validate_NMTOKENS(value): 

2819 """ 

2820 Validate a list of "name tokens". 

2821 

2822 Provisional. 

2823 """ 

2824 if isinstance(value, str): 

2825 value = value.split() 

2826 for token in value: 

2827 validate_NMTOKEN(token) 

2828 return value 

2829 

2830 

2831def validate_refname_list(value): 

2832 """ 

2833 Validate a list of `reference names`__. 

2834 

2835 Reference names may contain all characters; 

2836 whitespace is normalized (cf, `whitespace_normalize_name()`). 

2837 

2838 `value` may be either a `list` of names or a `str` with 

2839 space separated names (with internal spaces backslash escaped 

2840 and literal backslashes doubled cf. `serial_escape()`). 

2841 

2842 Return a list of whitespace-normalized, unescaped reference names. 

2843 

2844 Provisional. 

2845 

2846 __ https://docutils.sourceforge.io/docs/ref/doctree.html#reference-name 

2847 """ 

2848 if isinstance(value, str): 

2849 value = split_name_list(value) 

2850 return [whitespace_normalize_name(name) for name in value] 

2851 

2852 

2853def validate_yesorno(value): 

2854 if value == "0": 

2855 return False 

2856 return bool(value) 

2857 

2858 

2859ATTRIBUTE_VALIDATORS = { 

2860 'alt': str, # CDATA 

2861 'align': str, 

2862 'anonymous': validate_yesorno, 

2863 'auto': str, # CDATA (only '1' or '*' are used in rST) 

2864 'backrefs': validate_identifier_list, 

2865 'bullet': str, # CDATA (only '-', '+', or '*' are used in rST) 

2866 'classes': validate_identifier_list, 

2867 'char': str, # from Exchange Table Model (CALS), currently ignored 

2868 'charoff': validate_NMTOKEN, # from CALS, currently ignored 

2869 'colname': validate_NMTOKEN, # from CALS, currently ignored 

2870 'colnum': int, # from CALS, currently ignored 

2871 'cols': int, # from CALS: "NMTOKEN, […] must be an integer > 0". 

2872 'colsep': validate_yesorno, 

2873 'colwidth': int, # sic! CALS: CDATA (measure or number+'*') 

2874 'content': str, # <meta> 

2875 'delimiter': str, 

2876 'depth': int, 

2877 'dir': validate_enumerated_type('ltr', 'rtl', 'auto'), # <meta> 

2878 'dupnames': validate_refname_list, 

2879 'enumtype': validate_enumerated_type('arabic', 'loweralpha', 'lowerroman', 

2880 'upperalpha', 'upperroman'), 

2881 'format': str, # CDATA (space separated format names) 

2882 'frame': validate_enumerated_type('top', 'bottom', 'topbot', 'all', 

2883 'sides', 'none'), # from CALS, ignored 

2884 'height': validate_measure, 

2885 'http-equiv': str, # <meta> 

2886 'ids': validate_identifier_list, 

2887 'lang': str, # <meta> 

2888 'level': int, 

2889 'line': int, 

2890 'local': validate_yesorno, 

2891 'ltrim': validate_yesorno, 

2892 'loading': validate_enumerated_type('embed', 'link', 'lazy'), 

2893 'media': str, # <meta> 

2894 'morecols': int, 

2895 'morerows': int, 

2896 'name': whitespace_normalize_name, # in <reference> (deprecated) 

2897 # 'name': node_attributes.validate_NMTOKEN, # in <meta> 

2898 'names': validate_refname_list, 

2899 'namest': validate_NMTOKEN, # start of span, from CALS, currently ignored 

2900 'nameend': validate_NMTOKEN, # end of span, from CALS, currently ignored 

2901 'pgwide': validate_yesorno, # from CALS, currently ignored 

2902 'prefix': str, 

2903 'refid': validate_identifier, 

2904 'refname': whitespace_normalize_name, 

2905 'refuri': str, 

2906 'rowsep': validate_yesorno, 

2907 'rtrim': validate_yesorno, 

2908 'scale': int, 

2909 'scheme': str, 

2910 'source': str, 

2911 'start': int, 

2912 'stub': validate_yesorno, 

2913 'suffix': str, 

2914 'title': str, 

2915 'type': validate_NMTOKEN, 

2916 'uri': str, 

2917 'valign': validate_enumerated_type('top', 'middle', 'bottom'), # from CALS 

2918 'width': validate_measure, 

2919 'xml:space': validate_enumerated_type('default', 'preserve'), 

2920 } 

2921""" 

2922Mapping of `attribute names`__ to validating functions. 

2923 

2924Provisional. 

2925 

2926__ https://docutils.sourceforge.io/docs/ref/doctree.html#attribute-reference 

2927"""