1
0

parts.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. # $Id: parts.py 9038 2022-03-05 23:31:46Z milde $
  2. # Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer; Dmitry Jemerov
  3. # Copyright: This module has been placed in the public domain.
  4. """
  5. Transforms related to document parts.
  6. """
  7. __docformat__ = 'reStructuredText'
  8. import sys
  9. from docutils import nodes
  10. from docutils.transforms import Transform
  11. class SectNum(Transform):
  12. """
  13. Automatically assigns numbers to the titles of document sections.
  14. It is possible to limit the maximum section level for which the numbers
  15. are added. For those sections that are auto-numbered, the "autonum"
  16. attribute is set, informing the contents table generator that a different
  17. form of the TOC should be used.
  18. """
  19. default_priority = 710
  20. """Should be applied before `Contents`."""
  21. def apply(self):
  22. self.maxdepth = self.startnode.details.get('depth', None)
  23. self.startvalue = self.startnode.details.get('start', 1)
  24. self.prefix = self.startnode.details.get('prefix', '')
  25. self.suffix = self.startnode.details.get('suffix', '')
  26. self.startnode.parent.remove(self.startnode)
  27. if self.document.settings.sectnum_xform:
  28. if self.maxdepth is None:
  29. self.maxdepth = sys.maxsize
  30. self.update_section_numbers(self.document)
  31. else: # store details for eventual section numbering by the writer
  32. self.document.settings.sectnum_depth = self.maxdepth
  33. self.document.settings.sectnum_start = self.startvalue
  34. self.document.settings.sectnum_prefix = self.prefix
  35. self.document.settings.sectnum_suffix = self.suffix
  36. def update_section_numbers(self, node, prefix=(), depth=0):
  37. depth += 1
  38. if prefix:
  39. sectnum = 1
  40. else:
  41. sectnum = self.startvalue
  42. for child in node:
  43. if isinstance(child, nodes.section):
  44. numbers = prefix + (str(sectnum),)
  45. title = child[0]
  46. # Use &nbsp; for spacing:
  47. generated = nodes.generated(
  48. '', (self.prefix + '.'.join(numbers) + self.suffix
  49. + '\u00a0' * 3),
  50. classes=['sectnum'])
  51. title.insert(0, generated)
  52. title['auto'] = 1
  53. if depth < self.maxdepth:
  54. self.update_section_numbers(child, numbers, depth)
  55. sectnum += 1
  56. class Contents(Transform):
  57. """
  58. This transform generates a table of contents from the entire document tree
  59. or from a single branch. It locates "section" elements and builds them
  60. into a nested bullet list, which is placed within a "topic" created by the
  61. contents directive. A title is either explicitly specified, taken from
  62. the appropriate language module, or omitted (local table of contents).
  63. The depth may be specified. Two-way references between the table of
  64. contents and section titles are generated (requires Writer support).
  65. This transform requires a startnode, which contains generation
  66. options and provides the location for the generated table of contents (the
  67. startnode is replaced by the table of contents "topic").
  68. """
  69. default_priority = 720
  70. def apply(self):
  71. # let the writer (or output software) build the contents list?
  72. toc_by_writer = getattr(self.document.settings, 'use_latex_toc', False)
  73. details = self.startnode.details
  74. if 'local' in details:
  75. startnode = self.startnode.parent.parent
  76. while not (isinstance(startnode, nodes.section)
  77. or isinstance(startnode, nodes.document)):
  78. # find the ToC root: a direct ancestor of startnode
  79. startnode = startnode.parent
  80. else:
  81. startnode = self.document
  82. self.toc_id = self.startnode.parent['ids'][0]
  83. if 'backlinks' in details:
  84. self.backlinks = details['backlinks']
  85. else:
  86. self.backlinks = self.document.settings.toc_backlinks
  87. if toc_by_writer:
  88. # move customization settings to the parent node
  89. self.startnode.parent.attributes.update(details)
  90. self.startnode.parent.remove(self.startnode)
  91. else:
  92. contents = self.build_contents(startnode)
  93. if len(contents):
  94. self.startnode.replace_self(contents)
  95. else:
  96. self.startnode.parent.parent.remove(self.startnode.parent)
  97. def build_contents(self, node, level=0):
  98. level += 1
  99. sections = [sect for sect in node if isinstance(sect, nodes.section)]
  100. entries = []
  101. depth = self.startnode.details.get('depth', sys.maxsize)
  102. for section in sections:
  103. title = section[0]
  104. auto = title.get('auto') # May be set by SectNum.
  105. entrytext = self.copy_and_filter(title)
  106. reference = nodes.reference('', '', refid=section['ids'][0],
  107. *entrytext)
  108. ref_id = self.document.set_id(reference,
  109. suggested_prefix='toc-entry')
  110. entry = nodes.paragraph('', '', reference)
  111. item = nodes.list_item('', entry)
  112. if (self.backlinks in ('entry', 'top')
  113. and title.next_node(nodes.reference) is None):
  114. if self.backlinks == 'entry':
  115. title['refid'] = ref_id
  116. elif self.backlinks == 'top':
  117. title['refid'] = self.toc_id
  118. if level < depth:
  119. subsects = self.build_contents(section, level)
  120. item += subsects
  121. entries.append(item)
  122. if entries:
  123. contents = nodes.bullet_list('', *entries)
  124. if auto: # auto-numbered sections
  125. contents['classes'].append('auto-toc')
  126. return contents
  127. else:
  128. return []
  129. def copy_and_filter(self, node):
  130. """Return a copy of a title, with references, images, etc. removed."""
  131. visitor = ContentsFilter(self.document)
  132. node.walkabout(visitor)
  133. return visitor.get_entry_text()
  134. class ContentsFilter(nodes.TreeCopyVisitor):
  135. def get_entry_text(self):
  136. return self.get_tree_copy().children
  137. def visit_citation_reference(self, node):
  138. raise nodes.SkipNode
  139. def visit_footnote_reference(self, node):
  140. raise nodes.SkipNode
  141. def visit_image(self, node):
  142. if node.hasattr('alt'):
  143. self.parent.append(nodes.Text(node['alt']))
  144. raise nodes.SkipNode
  145. def ignore_node_but_process_children(self, node):
  146. raise nodes.SkipDeparture
  147. visit_problematic = ignore_node_but_process_children
  148. visit_reference = ignore_node_but_process_children
  149. visit_target = ignore_node_but_process_children