Fix #4803: latex: too slow in proportion to number of auto numbered footnotes

This commit is contained in:
Takeshi KOMIYA 2018-04-14 20:46:01 +09:00
parent 185987a517
commit 36a8781688
2 changed files with 80 additions and 43 deletions

View File

@ -26,6 +26,7 @@ Bugs fixed
* #4817: wrong URLs on warning messages * #4817: wrong URLs on warning messages
* #4784: latex: :confval:`latex_show_urls` assigns incorrect footnote numbers if * #4784: latex: :confval:`latex_show_urls` assigns incorrect footnote numbers if
hyperlinks exists inside substitutions hyperlinks exists inside substitutions
* #4803: latex: too slow in proportion to number of auto numbered footnotes
Testing Testing
-------- --------

View File

@ -10,20 +10,25 @@
""" """
from docutils import nodes from docutils import nodes
from six import iteritems
from sphinx import addnodes
from sphinx.transforms import SphinxTransform from sphinx.transforms import SphinxTransform
from sphinx.util.nodes import traverse_parent
if False:
# For type annotation
from typing import Dict, List, Set, Union # NOQA
URI_SCHEMES = ('mailto:', 'http:', 'https:', 'ftp:') URI_SCHEMES = ('mailto:', 'http:', 'https:', 'ftp:')
class ShowUrlsTransform(SphinxTransform, object): class ShowUrlsTransform(SphinxTransform):
def __init__(self, document, startnode=None): """Expand references to inline text or footnotes.
# type: (nodes.document, nodes.Node) -> None
super(ShowUrlsTransform, self).__init__(document, startnode) For more information, see :confval:`latex_show_urls`.
self.expanded = False """
default_priority = 400
# references are expanded to footnotes (or not)
expanded = False
def apply(self): def apply(self):
# type: () -> None # type: () -> None
@ -80,46 +85,77 @@ class ShowUrlsTransform(SphinxTransform, object):
def renumber_footnotes(self): def renumber_footnotes(self):
# type: () -> None # type: () -> None
def is_used_number(number): collector = FootnoteCollector(self.document)
# type: (unicode) -> bool self.document.walkabout(collector)
for node in self.document.traverse(nodes.footnote):
if not node.get('auto') and number in node['names']:
return True
return False num = 0
for document, footnote in collector.auto_footnotes:
def is_auto_footnote(node): # search unused footnote number
# type: (nodes.Node) -> bool
return isinstance(node, nodes.footnote) and node.get('auto')
def footnote_ref_by(node):
# type: (nodes.Node) -> Callable[[nodes.Node], bool]
ids = node['ids']
parent = list(traverse_parent(node, (nodes.document, addnodes.start_of_file)))[0]
def is_footnote_ref(node):
# type: (nodes.Node) -> bool
return (isinstance(node, nodes.footnote_reference) and
ids[0] == node['refid'] and
parent in list(traverse_parent(node)))
return is_footnote_ref
startnum = 1
for footnote in self.document.traverse(is_auto_footnote):
while True: while True:
label = str(startnum) num += 1
startnum += 1 if str(num) not in collector.used_footnote_numbers:
if not is_used_number(label):
break break
# assign new footnote number
old_label = footnote[0].astext() old_label = footnote[0].astext()
footnote.remove(footnote[0]) footnote[0].replace_self(nodes.label('', str(num)))
footnote.insert(0, nodes.label('', label))
if old_label in footnote['names']: if old_label in footnote['names']:
footnote['names'].remove(old_label) footnote['names'].remove(old_label)
footnote['names'].append(label) footnote['names'].append(str(num))
for footnote_ref in self.document.traverse(footnote_ref_by(footnote)): # update footnote_references by new footnote number
footnote_ref.remove(footnote_ref[0]) for ref in collector.footnote_refs.get(document, []):
footnote_ref += nodes.Text(label) if footnote['ids'][0] == ref['refid']:
ref.remove(ref[0])
ref += nodes.Text(str(num))
class FootnoteCollector(nodes.NodeVisitor):
"""Collect footnotes and footnote references on the document"""
def __init__(self, document):
# type: (nodes.document) -> None
self.auto_footnotes = [] # type: List[nodes.footnote]
self.used_footnote_numbers = set() # type: Set[unicode]
self.footnote_refs = {} # type: Dict[nodes.Node, List[nodes.footnote_reference]] # NOQA
self.current_document = [] # type: List[nodes.Node]
nodes.NodeVisitor.__init__(self, document)
def visit_document(self, node):
# type: (nodes.Node) -> None
self.current_document.append(node)
def depart_document(self, node):
# type: (nodes.Node) -> None
self.current_document.pop()
def visit_start_of_file(self, node):
# type: (nodes.Node) -> None
self.current_document.append(node)
def depart_start_of_file(self, node):
# type: (nodes.Node) -> None
self.current_document.pop()
def unknown_visit(self, node):
# type: (nodes.Node) -> None
pass
def visit_footnote(self, node):
# type: (nodes.footnote) -> None
document = self.current_document[-1]
if node.get('auto'):
self.auto_footnotes.append((document, node))
else:
for name in node['names']:
self.used_footnote_numbers.add(name)
def visit_footnote_reference(self, node):
# type: (nodes.footnote_reference) -> None
document = self.current_document[-1]
footnote_refs = self.footnote_refs.setdefault(document, [])
footnote_refs.append(node)
def unknown_departure(self, node):
# type: (nodes.Node) -> None
pass