diff --git a/CHANGES b/CHANGES index 5feea324b..996337a23 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,7 @@ Bugs fixed * #1833: Fix email addresses is showed again if latex_show_urls is not None * #2176: sphinx.ext.graphviz: use instead of to embed svg * #967: Fix SVG inheritance diagram is not hyperlinked (clickable) +* #1237: Fix footnotes not working in definition list in LaTeX Release 1.3.3 (released Dec 2, 2015) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index c1667666d..af1c6af7a 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -69,6 +69,8 @@ FOOTER = r''' \end{document} ''' +URI_SCHEMES = ('mailto:', 'http:', 'https:', 'ftp:') + class collected_footnote(nodes.footnote): """Footnotes that are collected are assigned this class.""" @@ -98,6 +100,8 @@ class LaTeXWriter(writers.Writer): self.builder.translator_class or LaTeXTranslator) def translate(self): + transform = ShowUrlsTransform(self.document) + transform.apply() visitor = self.translator_class(self.document, self.builder) self.document.walkabout(visitor) self.output = visitor.astext() @@ -126,6 +130,99 @@ if hasattr(Babel, '_ISO639_TO_BABEL'): Babel._ISO639_TO_BABEL['sl'] = 'slovene' +class ShowUrlsTransform(object): + expanded = False + + def __init__(self, document): + self.document = document + + def apply(self): + # replace id_prefix temporarily + id_prefix = self.document.settings.id_prefix + self.document.settings.id_prefix = 'show_urls' + + self.expand_show_urls() + if self.expanded: + self.renumber_footnotes() + + # restore id_prefix + self.document.settings.id_prefix = id_prefix + + def expand_show_urls(self): + show_urls = self.document.settings.env.config.latex_show_urls + if show_urls is False or show_urls == 'no': + return + + for node in self.document.traverse(nodes.reference): + uri = node.get('refuri', '') + if uri.startswith(URI_SCHEMES): + if uri.startswith('mailto:'): + uri = uri[7:] + if node.astext() != uri: + index = node.parent.index(node) + if show_urls == 'footnote': + footnote_nodes = self.create_footnote(uri) + for i, fn in enumerate(footnote_nodes): + node.parent.insert(index + i + 1, fn) + + self.expanded = True + else: # all other true values (b/w compat) + textnode = nodes.Text(" (%s)" % uri) + node.parent.insert(index + 1, textnode) + + def create_footnote(self, uri): + label = nodes.label('', '#') + para = nodes.paragraph() + para.append(nodes.Text(uri)) + footnote = nodes.footnote(uri, label, para, auto=1) + footnote['names'].append('#') + self.document.note_autofootnote(footnote) + + label = nodes.Text('#') + footnote_ref = nodes.footnote_reference('[#]_', label, auto=1, + refid=footnote['ids'][0]) + self.document.note_autofootnote_ref(footnote_ref) + footnote.add_backref(footnote_ref['ids'][0]) + + return [footnote, footnote_ref] + + def renumber_footnotes(self): + def is_used_number(number): + for node in self.document.traverse(nodes.footnote): + if not node.get('auto') and number in node['names']: + return True + + return False + + def is_auto_footnote(node): + return isinstance(node, nodes.footnote) and node.get('auto') + + def footnote_ref_by(ids): + def is_footnote_ref(node): + return isinstance(node, nodes.footnote_reference) and ids[0] == node['refid'] + + return is_footnote_ref + + startnum = 1 + for footnote in self.document.traverse(is_auto_footnote): + while True: + label = str(startnum) + startnum += 1 + if not is_used_number(label): + break + + old_label = footnote[0].astext() + footnote.remove(footnote[0]) + footnote.insert(0, nodes.label('', label)) + if old_label in footnote['names']: + footnote['names'].remove(old_label) + footnote['names'].append(label) + + for footnote_ref in self.document.traverse(footnote_ref_by(footnote['ids'])): + footnote_ref.remove(footnote_ref[0]) + footnote_ref += nodes.Text(label) + + class Table(object): def __init__(self): self.col = 0 @@ -278,6 +375,7 @@ class LaTeXTranslator(nodes.NodeVisitor): sys.maxsize]] self.bodystack = [] self.footnotestack = [] + self.termfootnotestack = [] self.curfilestack = [] self.handled_abbrs = set() if document.settings.docclass == 'howto': @@ -297,6 +395,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.in_footnote = 0 self.in_caption = 0 self.in_container_literal_block = 0 + self.in_term = 0 self.first_document = 1 self.this_is_the_title = 1 self.literal_whitespace = 0 @@ -761,7 +860,7 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_collected_footnote(self, node): self.in_footnote += 1 - if 'in_table' in node: + if 'footnotetext' in node: self.body.append('\\footnotetext[%s]{' % node['number']) else: self.body.append('\\footnote[%s]{' % node['number']) @@ -864,7 +963,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.body.append('\\end{threeparttable}\n\n') if self.table.footnotes: for footnode in self.table.footnotes: - footnode['in_table'] = True + footnode['footnotetext'] = True footnode.walkabout(self) self.table = None self.tablebody = None @@ -1031,14 +1130,22 @@ class LaTeXTranslator(nodes.NodeVisitor): pass def visit_term(self, node): + self.in_term += 1 ctx = '}] \\leavevmode' if node.get('ids'): ctx += self.hypertarget(node['ids'][0]) self.body.append('\\item[{') + self.termfootnotestack.append([]) self.context.append(ctx) def depart_term(self, node): self.body.append(self.context.pop()) + footnotes = self.termfootnotestack.pop() + for footnode in footnotes: + footnode['footnotetext'] = True + footnode.walkabout(self) + + self.in_term -= 1 def visit_termsep(self, node): self.body.append(', ') @@ -1399,23 +1506,9 @@ class LaTeXTranslator(nodes.NodeVisitor): uri = '%' + self.curfilestack[-1] + '#' + node['refid'] if self.in_title or not uri: self.context.append('') - elif uri.startswith('mailto:') or uri.startswith('http:') or \ - uri.startswith('https:') or uri.startswith('ftp:'): + elif uri.startswith(URI_SCHEMES): self.body.append('\\href{%s}{' % self.encode_uri(uri)) - # if configured, put the URL after the link - show_urls = self.builder.config.latex_show_urls - if uri.startswith('mailto:'): - uri = uri[7:] - if node.astext() != uri and show_urls and show_urls != 'no': - if show_urls == 'footnote' and not \ - (self.in_footnote or self.in_caption): - # obviously, footnotes in footnotes are not going to work - self.context.append( - r'}\footnote{%s}' % self.encode_uri(uri)) - else: # all other true values (b/w compat) - self.context.append('} (%s)' % self.encode_uri(uri)) - else: - self.context.append('}') + self.context.append('}') elif uri.startswith('#'): # references to labels in the same document id = self.curfilestack[-1] + ':' + uri[1:] @@ -1566,7 +1659,7 @@ class LaTeXTranslator(nodes.NodeVisitor): # if a footnote has been inserted once, it shouldn't be repeated # by the next reference if used: - if self.table: + if self.table or self.in_term: self.body.append('\\protect\\footnotemark[%s]' % num) else: self.body.append('\\footnotemark[%s]' % num) @@ -1574,6 +1667,9 @@ class LaTeXTranslator(nodes.NodeVisitor): self.footnotestack[-1][num][1] = True self.body.append('\\protect\\footnotemark[%s]' % num) self.table.footnotes.append(footnode) + elif self.in_term: + self.body.append('\\footnotemark[%s]' % num) + self.termfootnotestack[-1].append(footnode) else: if self.in_caption: raise UnsupportedError('%s:%s: footnotes in float captions ' diff --git a/tests/root/conf.py b/tests/root/conf.py index 6ce196c3d..bdf2f8c8d 100644 --- a/tests/root/conf.py +++ b/tests/root/conf.py @@ -51,7 +51,6 @@ latex_documents = [ 'Georg Brandl \\and someone else', 'manual'), ] -latex_show_urls = 'footnote' latex_additional_files = ['svgimg.svg'] texinfo_documents = [ diff --git a/tests/root/footnote.txt b/tests/root/footnote.txt index c30a5fe9c..36ad3fadc 100644 --- a/tests/root/footnote.txt +++ b/tests/root/footnote.txt @@ -35,18 +35,6 @@ footnotes in table * - VIDIOC_CROPCAP - Information about VIDIOC_CROPCAP -URLs as footnotes ------------------ - -`homepage `_ - -URLs should not be footnotes ----------------------------- - -GitHub Page: `https://github.com/sphinx-doc/sphinx `_ - -Mailing list: `sphinx-dev@googlegroups.com `_ - footenotes -------------------- diff --git a/tests/roots/test-references-in-caption/conf.py b/tests/roots/test-footnotes/conf.py similarity index 100% rename from tests/roots/test-references-in-caption/conf.py rename to tests/roots/test-footnotes/conf.py diff --git a/tests/roots/test-footnotes/index.rst b/tests/roots/test-footnotes/index.rst new file mode 100644 index 000000000..c7fe38ff9 --- /dev/null +++ b/tests/roots/test-footnotes/index.rst @@ -0,0 +1,45 @@ +=============== +test-footenotes +=============== + +The section with a reference to [AuthorYear]_ +============================================= + +.. figure:: rimg.png + + This is the figure caption with a reference to [AuthorYear]_. + +.. list-table:: The table title with a reference to [AuthorYear]_ + :header-rows: 1 + + * - Header1 + - Header2 + * - Content + - Content + +.. rubric:: The rubric title with a reference to [AuthorYear]_ + +.. [#] First + +* First footnote: [#]_ +* Second footnote: [1]_ +* `Sphinx `_ +* Third footnote: [#]_ +* `URL including tilde `_ +* GitHub Page: `https://github.com/sphinx-doc/sphinx `_ +* Mailing list: `sphinx-dev@googlegroups.com `_ + +.. [AuthorYear] Author, Title, Year +.. [1] Second +.. [#] Third + +`URL in term `_ + Description Description Description ... + +Footnote in term [#]_ + Description Description Description ... + + `Term in deflist `_ + Description2 + +.. [#] Footnote in term diff --git a/tests/roots/test-references-in-caption/rimg.png b/tests/roots/test-footnotes/rimg.png similarity index 100% rename from tests/roots/test-references-in-caption/rimg.png rename to tests/roots/test-footnotes/rimg.png diff --git a/tests/roots/test-references-in-caption/index.rst b/tests/roots/test-references-in-caption/index.rst deleted file mode 100644 index decec1bad..000000000 --- a/tests/roots/test-references-in-caption/index.rst +++ /dev/null @@ -1,22 +0,0 @@ -============== -test-reference -============== - -The section with a reference to [AuthorYear]_ -============================================= - -.. figure:: rimg.png - - This is the figure caption with a reference to [AuthorYear]_. - -.. list-table:: The table title with a reference to [AuthorYear]_ - :header-rows: 1 - - * - Header1 - - Header2 - * - Content - - Content - -.. rubric:: The rubric title with a reference to [AuthorYear]_ - -.. [AuthorYear] Author, Title, Year diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index 00d3b82c1..d4f9f8451 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -318,12 +318,9 @@ def test_footnote(app, status, warning): assert ('\\end{threeparttable}\n\n' '\\footnotetext[4]{\nfootnotes in table caption\n}' '\\footnotetext[5]{\nfootnotes in table\n}' in result) - assert r'\href{http://sphinx.org}{homepage}\footnote{http://sphinx.org}' in result - assert r'\footnote{https://github.com/sphinx-doc/sphinx}' not in result - assert r'\footnote{sphinx-dev@googlegroups.com}' not in result -@with_app(buildername='latex', testroot='references-in-caption') +@with_app(buildername='latex', testroot='footnotes') def test_reference_in_caption(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -335,3 +332,81 @@ def test_reference_in_caption(app, status, warning): assert '\\chapter{The section with a reference to {[}AuthorYear{]}}' in result assert '\\caption{The table title with a reference to {[}AuthorYear{]}}' in result assert '\\paragraph{The rubric title with a reference to {[}AuthorYear{]}}' in result + + +@with_app(buildername='latex', testroot='footnotes', + confoverrides={'latex_show_urls': 'inline'}) +def test_latex_show_urls_is_inline(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'Python.tex').text(encoding='utf8') + print(result) + print(status.getvalue()) + print(warning.getvalue()) + assert 'First footnote: \\footnote[2]{\nFirst\n}' in result + assert 'Second footnote: \\footnote[1]{\nSecond\n}' in result + assert '\\href{http://sphinx-doc.org/}{Sphinx} (http://sphinx-doc.org/)' in result + assert 'Third footnote: \\footnote[3]{\nThird\n}' in result + assert ('\\href{http://sphinx-doc.org/~test/}{URL including tilde} ' + '(http://sphinx-doc.org/\\textasciitilde{}test/)' in result) + assert ('\\item[{\\href{http://sphinx-doc.org/}{URL in term} (http://sphinx-doc.org/)}] ' + '\\leavevmode\nDescription' in result) + assert ('\\item[{Footnote in term \\footnotemark[4]}] ' + '\\leavevmode\\footnotetext[4]{\nFootnote in term\n}\nDescription' in result) + assert ('\\item[{\\href{http://sphinx-doc.org/}{Term in deflist} ' + '(http://sphinx-doc.org/)}] \\leavevmode\nDescription' in result) + assert ('\\href{https://github.com/sphinx-doc/sphinx}' + '{https://github.com/sphinx-doc/sphinx}\n' in result) + assert ('\\href{mailto:sphinx-dev@googlegroups.com}' + '{sphinx-dev@googlegroups.com}' in result) + + +@with_app(buildername='latex', testroot='footnotes', + confoverrides={'latex_show_urls': 'footnote'}) +def test_latex_show_urls_is_footnote(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'Python.tex').text(encoding='utf8') + print(result) + print(status.getvalue()) + print(warning.getvalue()) + assert 'First footnote: \\footnote[2]{\nFirst\n}' in result + assert 'Second footnote: \\footnote[1]{\nSecond\n}' in result + assert ('\\href{http://sphinx-doc.org/}{Sphinx}' + '\\footnote[3]{\nhttp://sphinx-doc.org/\n}' in result) + assert 'Third footnote: \\footnote[5]{\nThird\n}' in result + assert ('\\href{http://sphinx-doc.org/~test/}{URL including tilde}' + '\\footnote[4]{\nhttp://sphinx-doc.org/\\textasciitilde{}test/\n}' in result) + assert ('\\item[{\\href{http://sphinx-doc.org/}{URL in term}\\footnotemark[6]}] ' + '\\leavevmode\\footnotetext[6]{\nhttp://sphinx-doc.org/\n}\nDescription' in result) + assert ('\\item[{Footnote in term \\footnotemark[8]}] ' + '\\leavevmode\\footnotetext[8]{\nFootnote in term\n}\nDescription' in result) + assert ('\\item[{\\href{http://sphinx-doc.org/}{Term in deflist}\\footnotemark[7]}] ' + '\\leavevmode\\footnotetext[7]{\nhttp://sphinx-doc.org/\n}\nDescription' in result) + assert ('\\href{https://github.com/sphinx-doc/sphinx}' + '{https://github.com/sphinx-doc/sphinx}\n' in result) + assert ('\\href{mailto:sphinx-dev@googlegroups.com}' + '{sphinx-dev@googlegroups.com}\n' in result) + + +@with_app(buildername='latex', testroot='footnotes', + confoverrides={'latex_show_urls': 'no'}) +def test_latex_show_urls_is_no(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'Python.tex').text(encoding='utf8') + print(result) + print(status.getvalue()) + print(warning.getvalue()) + assert 'First footnote: \\footnote[2]{\nFirst\n}' in result + assert 'Second footnote: \\footnote[1]{\nSecond\n}' in result + assert '\\href{http://sphinx-doc.org/}{Sphinx}' in result + assert 'Third footnote: \\footnote[3]{\nThird\n}' in result + assert '\\href{http://sphinx-doc.org/~test/}{URL including tilde}' in result + assert ('\\item[{\\href{http://sphinx-doc.org/}{URL in term}}] ' + '\\leavevmode\nDescription' in result) + assert ('\\item[{Footnote in term \\footnotemark[4]}] ' + '\\leavevmode\\footnotetext[4]{\nFootnote in term\n}\nDescription' in result) + assert ('\\item[{\\href{http://sphinx-doc.org/}{Term in deflist}}] ' + '\\leavevmode\nDescription' in result) + assert ('\\href{https://github.com/sphinx-doc/sphinx}' + '{https://github.com/sphinx-doc/sphinx}\n' in result) + assert ('\\href{mailto:sphinx-dev@googlegroups.com}' + '{sphinx-dev@googlegroups.com}\n' in result)