Commits

Takayuki Shimizukawa committed 5328f9e

Fix multiple cross references (term, ref, doc) in the same line return the same link with i18n. refs #1090, #1193

  • Participants
  • Parent commits 5c620a0

Comments (0)

Files changed (6)

   characters to "Project name" on quickstart.
 * #1190: Output TeX/texinfo/man filename has no basename (only extention)
   when using multibyte characters to "Project name" on quickstart.
+* #1090: Fix multiple cross references (term, ref, doc) in the same line
+  return the same link with i18n.
 
 
 Release 1.2 (beta1 released Mar 31, 2013)

File sphinx/domains/std.py

         return title, target
 
 
+def make_termnodes_from_paragraph_node(env, node):
+    gloss_entries = env.temp_data.setdefault('gloss_entries', set())
+    objects = env.domaindata['std']['objects']
+
+    termtext = node.astext()
+    new_id = 'term-' + nodes.make_id(termtext)
+    if new_id in gloss_entries:
+        new_id = 'term-' + str(len(gloss_entries))
+    gloss_entries.add(new_id)
+    objects['term', termtext.lower()] = env.docname, new_id
+
+    # add an index entry too
+    indexnode = addnodes.index()
+    indexnode['entries'] = [('single', termtext, new_id, 'main')]
+    new_termnodes = []
+    new_termnodes.append(indexnode)
+    new_termnodes.extend(node.children)
+    new_termnodes.append(addnodes.termsep())
+    for termnode in new_termnodes:
+        termnode.source, termnode.line = node.source, node.line
+
+    return new_id, termtext, new_termnodes
+
+
+def make_term_from_paragraph_node(termnodes, ids):
+    # make a single "term" node with all the terms, separated by termsep
+    # nodes (remove the dangling trailing separator)
+    term = nodes.term('', '', *termnodes[:-1])
+    term.source, term.line = termnodes[0].source, termnodes[0].line
+    term.rawsource = term.astext()
+    term['ids'].extend(ids)
+    term['names'].extend(ids)
+    return term
+
+
 class Glossary(Directive):
     """
     Directive to create a glossary with cross-reference targets for :term:
 
     def run(self):
         env = self.state.document.settings.env
-        objects = env.domaindata['std']['objects']
-        gloss_entries = env.temp_data.setdefault('gloss_entries', set())
         node = addnodes.glossary()
         node.document = self.state.document
 
                 # get a text-only representation of the term and register it
                 # as a cross-reference target
                 tmp = nodes.paragraph('', '', *res[0])
-                termtext = tmp.astext()
-                new_id = 'term-' + nodes.make_id(termtext)
-                if new_id in gloss_entries:
-                    new_id = 'term-' + str(len(gloss_entries))
-                gloss_entries.add(new_id)
+                tmp.source = source
+                tmp.line = lineno
+                new_id, termtext, new_termnodes = \
+                        make_termnodes_from_paragraph_node(env, tmp)
                 ids.append(new_id)
-                objects['term', termtext.lower()] = env.docname, new_id
                 termtexts.append(termtext)
-                # add an index entry too
-                indexnode = addnodes.index()
-                indexnode['entries'] = [('single', termtext, new_id, 'main')]
-                new_termnodes = []
-                new_termnodes.append(indexnode)
-                new_termnodes.extend(res[0])
-                new_termnodes.append(addnodes.termsep())
-                for termnode in new_termnodes:
-                    termnode.source, termnode.line = source, lineno
                 termnodes.extend(new_termnodes)
-            # make a single "term" node with all the terms, separated by termsep
-            # nodes (remove the dangling trailing separator)
-            term = nodes.term('', '', *termnodes[:-1])
-            term.source, term.line = termnodes[0].source, termnodes[0].line
-            term.rawsource = term.astext()
-            term['ids'].extend(ids)
-            term['names'].extend(ids)
+
+            term = make_term_from_paragraph_node(termnodes, ids)
             term += system_messages
 
             defnode = nodes.definition()

File sphinx/transforms.py

 from sphinx.util.osutil import ustrftime, find_catalog
 from sphinx.util.compat import docutils_version
 from sphinx.util.pycompat import all
+from sphinx.domains.std import (
+    make_term_from_paragraph_node,
+    make_termnodes_from_paragraph_node,
+)
 
 
 default_substitutions = set([
                 if refname in refname_ids_map:
                     new["ids"] = refname_ids_map[refname]
 
+            # glossary terms update refid
+            if isinstance(node, nodes.term):
+                new_id, _, termnodes = \
+                    make_termnodes_from_paragraph_node(env, patch)
+                term = make_term_from_paragraph_node(
+                        termnodes, [new_id])
+                patch = term
+                node['ids'] = patch['ids']
+                node['names'] = patch['names']
+
             # Original pending_xref['reftarget'] contain not-translated
             # target name, new pending_xref must use original one.
             # This code restricts to change ref-targets in the translation.
             if len(old_refs) != len(new_refs):
                 env.warn_node('inconsistent term references in '
                               'translated message', node)
+            def get_key(node):
+                key = node["refdomain"], node["reftype"]
+                if key == ('std', 'term'):
+                    key = None
+                elif key == ('std', 'ref'):
+                    key += (node['reftarget'],)
+                elif key == ('', 'doc'):
+                    key += (node['reftarget'],)
+                else:
+                    pass
+                return key
+
             for old in old_refs:
-                key = old["reftype"], old["refdomain"]
-                xref_reftarget_map[key] = old["reftarget"]
+                key = get_key(old)
+                if key:
+                    xref_reftarget_map[key] = old["reftarget"]
             for new in new_refs:
-                key = new["reftype"], new["refdomain"]
+                key = get_key(new)
                 if key in xref_reftarget_map:
                     new['reftarget'] = xref_reftarget_map[key]
 

File tests/roots/test-intl/role_xref.po

 
 msgid "link to :term:`Some term`, :ref:`i18n-role-xref`, :doc:`contents`."
 msgstr "LINK TO :ref:`i18n-role-xref`, :doc:`contents`, :term:`SOME NEW TERM`."
+
+msgid "same type links"
+msgstr "SAME TYPE LINKS"
+
+msgid "link to :term:`Some term` and :term:`Some other term`."
+msgstr "LINK TO :term:`SOME OTHER NEW TERM` AND :term:`SOME NEW TERM`."
+
+msgid "link to :ref:`i18n-role-xref` and :ref:`same-type-links`."
+msgstr "LINK TO :ref:`same-type-links` AND :ref:`i18n-role-xref`."
+
+msgid "link to :doc:`contents` and :doc:`glossary_terms`."
+msgstr "LINK TO :doc:`glossary_terms` AND :doc:`contents`."
+

File tests/roots/test-intl/role_xref.txt

 
 link to :term:`Some term`, :ref:`i18n-role-xref`, :doc:`contents`.
 
+.. _same-type-links:
+
+same type links
+=================
+
+link to :term:`Some term` and :term:`Some other term`.
+
+link to :ref:`i18n-role-xref` and :ref:`same-type-links`.
+
+link to :doc:`contents` and :doc:`glossary_terms`.

File tests/test_intl.py

 import re
 from StringIO import StringIO
 from subprocess import Popen, PIPE
+from xml.etree import ElementTree
 
 from sphinx.util.pycompat import relpath
 
     assert 'term not in glossary' not in warnings
 
 
-@with_intl_app(buildername='text', warning=warnfile)
+@with_intl_app(buildername='xml', warning=warnfile)
 def test_i18n_role_xref(app):
     # regression test for #1090
+
+    def gettexts(elem):
+        def itertext(self):
+            # this function copied from Python-2.7 'ElementTree.itertext'.
+            # for compatibility to Python-2.5, 2.6, 3.1
+            tag = self.tag
+            if not isinstance(tag, basestring) and tag is not None:
+                return
+            if self.text:
+                yield self.text
+            for e in self:
+                for s in itertext(e):
+                    yield s
+                if e.tail:
+                    yield e.tail
+        return filter(None, [s.strip() for s in itertext(elem)])
+
+    def getref(elem):
+        return elem.attrib.get('refid') or elem.attrib.get('refuri')
+
+    def assert_text_refs(elem, text, refs):
+        _text = gettexts(elem)
+        assert _text == text
+        _refs = map(getref, elem.findall('reference'))
+        assert _refs == refs
+
     app.builddir.rmtree(True)  #for warnings acceleration
     app.builder.build(['role_xref'])
-    result = (app.outdir / 'role_xref.txt').text(encoding='utf-8')
-    expect = (
-        u"\nI18N ROCK'N ROLE XREF"
-        u"\n*********************\n"
-        u"\nLINK TO *I18N ROCK'N ROLE XREF*, *CONTENTS*, *SOME NEW TERM*.\n"
-    )
+    et = ElementTree.parse(app.outdir / 'role_xref.xml')
+    sec1, sec2 = et.findall('section')
 
+    para1, = sec1.findall('paragraph')
+    assert_text_refs(
+            para1,
+            ['LINK TO', "I18N ROCK'N ROLE XREF", ',', 'CONTENTS', ',',
+             'SOME NEW TERM', '.'],
+            ['i18n-role-xref',
+             'contents',
+             'glossary_terms#term-some-new-term'])
+
+    para21, para22, para23 = sec2.findall('paragraph')
+    assert_text_refs(
+            para21,
+            ['LINK TO', 'SOME OTHER NEW TERM', 'AND', 'SOME NEW TERM', '.'],
+            ['glossary_terms#term-some-other-new-term',
+             'glossary_terms#term-some-new-term'])
+    assert_text_refs(
+            para22,
+            ['LINK TO', 'SAME TYPE LINKS', 'AND', "I18N ROCK'N ROLE XREF", '.'],
+            ['same-type-links', 'i18n-role-xref'])
+    assert_text_refs(
+            para23,
+            ['LINK TO', 'I18N WITH GLOSSARY TERMS', 'AND', 'CONTENTS', '.'],
+            ['glossary_terms', 'contents'])
+
+    #warnings
     warnings = warnfile.getvalue().replace(os.sep, '/')
     assert 'term not in glossary' not in warnings
     assert 'undefined label' not in warnings
     assert 'unknown document' not in warnings
 
-    assert result == expect
-
 
 @with_intl_app(buildername='text', warning=warnfile)
 def test_i18n_glossary_terms_inconsistency(app):