Commits

georg.brandl  committed f1c885f

Merged revisions 65566-65567,65623,65625 via svnmerge from
svn+ssh://pythondev@svn.python.org/doctools/branches/0.4.x

........
r65566 | georg.brandl | 2008-08-07 09:11:11 +0000 (Thu, 07 Aug 2008) | 2 lines

Clarification for the ref role.
........
r65567 | georg.brandl | 2008-08-07 09:11:25 +0000 (Thu, 07 Aug 2008) | 2 lines

Rebuild everything if extensions change.
........
r65623 | georg.brandl | 2008-08-10 11:18:42 +0000 (Sun, 10 Aug 2008) | 2 lines

Unify handling of LaTeX escaping, and add some more replacements.
........
r65625 | georg.brandl | 2008-08-10 11:25:41 +0000 (Sun, 10 Aug 2008) | 2 lines

Make tex escapes a module.
........

  • Participants
  • Parent commits 75a0f59

Comments (0)

Files changed (8)

 
 .PHONY: all check clean clean-pyc clean-patchfiles pylint reindent test
 
-all: clean-pyc check
+all: clean-pyc check test
 
 check:
 	@$(PYTHON) utils/check_sources.py -i sphinx/style/jquery.js sphinx

File doc/markup/inline.rst

 Cross-referencing arbitrary locations
 -------------------------------------
 
-To support cross-referencing to arbitrary locations in the documentation, the
-standard reST labels used.  Of course, for this to work label names must be
-unique throughout the entire documentation.  There are two ways in which you can
-refer to labels:
+.. index:: pair: ref; role
+
+To support cross-referencing to arbitrary locations in any document, the
+standard reST labels are used.  For this to work label names must be unique
+throughout the entire documentation.  There are two ways in which you can refer
+to labels:
 
 * If you place a label directly before a section title, you can reference to it
   with ``:ref:`label-name```.  Example::
      It refers to the section itself, see :ref:`my-reference-label`.
 
   The ``:ref:`` role would then generate a link to the section, with the link
-  title being "Section to cross-reference".
+  title being "Section to cross-reference".  This works just as well when
+  section and reference are in different source files.
 
 * Labels that aren't placed before a section title can still be referenced to,
   but you must give the link an explicit title, using this syntax: ``:ref:`Link
   title <label-name>```.
 
+Using :role:`ref` is advised over standard reStructuredText links to sections
+(like ```Section title`_``) because it works across files, when section headings
+are changed, and for all builders that support cross-references.
+  
 
 Other semantic markup
 ---------------------

File sphinx/builder.py

 from docutils.readers.doctree import Reader as DoctreeReader
 
 from sphinx import addnodes, locale, __version__
-from sphinx.util import ensuredir, relative_uri, SEP, os_path, json
+from sphinx.util import ensuredir, relative_uri, SEP, os_path, json, texescape
 from sphinx.htmlhelp import build_hhx
 from sphinx.htmlwriter import HTMLWriter, HTMLTranslator, SmartyPantsHTMLTranslator
 from sphinx.textwriter import TextWriter
     def init(self):
         self.docnames = []
         self.document_data = []
+        texescape.init()
 
     def get_outdated_docs(self):
         return 'all documents' # for now

File sphinx/environment.py

                     break
             else:
                 msg = ''
+            # this value is not covered by the above loop because it is handled
+            # specially by the config class
+            if self.config.extensions != config.extensions:
+                msg = '[extensions changed] '
+                config_changed = True
         # the source and doctree directories may have been relocated
         self.srcdir = srcdir
         self.doctreedir = doctreedir

File sphinx/highlighting.py

 import re
 import parser
 
+from sphinx.util.texescape import tex_hl_escape_map
+
 try:
     import pygments
     from pygments import highlight
         _lexer.add_filter('raiseonerror')
 
 
-
-def escape_tex(text):
-    return text.replace('@', '\x00').    \
-                replace('[', '\x01').    \
-                replace(']', '\x02').    \
-                replace('\x00', '@at[]').\
-                replace('\x01', '@lb[]').\
-                replace('\x02', '@rb[]')
+escape_hl_chars = {ord(u'@'): u'@at[]',
+                   ord(u'['): u'@lb[]',
+                   ord(u']'): u'@rb[]'}
 
 # used if Pygments is not available
 _LATEX_STYLES = r'''
                        True: LatexFormatter(style=style, linenos=True,
                                             commandprefix='PYG')}
 
+    def unhighlighted(self, source):
+        if self.dest == 'html':
+            return '<pre>' + cgi.escape(source) + '</pre>\n'
+        else:
+            # first, escape highlighting characters like Pygments does
+            source = source.translate(escape_hl_chars)
+            # then, escape all characters nonrepresentable in LaTeX
+            source = source.translate(tex_hl_escape_map)
+            return '\\begin{Verbatim}[commandchars=@\\[\\]]\n' + \
+                   source + '\\end{Verbatim}\n'
+
     def highlight_block(self, source, lang, linenos=False):
-        def unhighlighted():
-            if self.dest == 'html':
-                return '<pre>' + cgi.escape(source) + '</pre>\n'
-            else:
-                return '\\begin{Verbatim}[commandchars=@\\[\\]]\n' + \
-                       escape_tex(source) + '\\end{Verbatim}\n'
         if not pygments:
-            return unhighlighted()
+            return self.unhighlighted(source)
         if lang == 'python':
             if source.startswith('>>>'):
                 # interactive session
                 try:
                     parser.suite(src)
                 except parsing_exceptions:
-                    return unhighlighted()
+                    return self.unhighlighted(source)
                 else:
                     lexer = lexers['python']
         else:
                 lexer = lexers[lang] = get_lexer_by_name(lang)
                 lexer.add_filter('raiseonerror')
         try:
-            fmter = (self.dest == 'html' and self.hfmter or self.lfmter)[bool(linenos)]
-            return highlight(source, lexer, fmter)
+            if self.dest == 'html':
+                return highlight(source, lexer, self.hfmter[bool(linenos)])
+            else:
+                hlsource = highlight(source, lexer, self.lfmter[bool(linenos)])
+                return hlsource.translate(tex_hl_escape_map)
         except ErrorToken:
             # this is most probably not the selected language,
             # so let it pass unhighlighted
-            return unhighlighted()
+            return self.unhighlighted(source)
 
     def get_stylesheet(self):
         if not pygments:

File sphinx/latexwriter.py

 from sphinx import addnodes
 from sphinx import highlighting
 from sphinx.locale import admonitionlabels, versionlabels
+from sphinx.util.texescape import tex_escape_map
 from sphinx.util.smartypants import educateQuotesLatex
 
 HEADER = r'''%% Generated by Sphinx.
 
 BEGIN_DOC = r'''
 \begin{document}
-\shorthandoff{"}
+%(shorthandoff)s
 \maketitle
 \tableofcontents
 '''
 
 # Helper classes
 
+class ExtBabel(Babel):
+    def get_shorthandoff(self):
+        if self.language == 'de':
+            return '\\shorthandoff{"}'
+        return ''
+
+
 class Table(object):
     def __init__(self):
         self.col = 0
             paper = 'letterpaper'
         date = time.strftime(builder.config.today_fmt or _('%B %d, %Y'))
         logo = (builder.config.latex_logo and
-                "\\includegraphics{%s}\\par" % path.basename(builder.config.latex_logo)
+                '\\includegraphics{%s}\\par' % path.basename(builder.config.latex_logo)
                 or '')
         self.options = {'docclass': docclass,
                         'papersize': paper,
                         'releasename': _('Release'),
                         'logo': logo,
                         'date': date,
-                        'classoptions': '',
+                        'classoptions': ',english',
+                        'shorthandoff': '',
                         }
         if builder.config.language:
-            babel = Babel(builder.config.language)
+            babel = ExtBabel(builder.config.language)
             self.options['classoptions'] += ',' + babel.get_language()
+            self.shorthandoff = babel.get_shorthandoff()
         self.highlighter = highlighting.PygmentsBridge(
             'latex', builder.config.pygments_style)
         self.context = []
 
     # text handling
 
-    replacements = [
-        (u"\\", u"\x00"),
-        (u"$", ur"\$"),
-        (r"%", ur"\%"),
-        (u"&", ur"\&"),
-        (u"#", ur"\#"),
-        (u"_", ur"\_"),
-        (u"{", ur"\{"),
-        (u"}", ur"\}"),
-        (u"[", ur"{[}"),
-        (u"]", ur"{]}"),
-        (u"¶", ur"\P{}"),
-        (u"§", ur"\S{}"),
-        (u"∞", ur"$\infty$"),
-        (u"±", ur"$\pm$"),
-        (u"‣", ur"$\rightarrow$"),
-        (u"Ω", ur"$\Omega$"),
-        (u"Ω", ur"$\Omega$"),
-        (u"φ", ur"$\phi$"),
-        (u"π", ur"$\pi$"),
-        (u"~", ur"\textasciitilde{}"),
-        (u"€", ur"\texteuro{}"),
-        (u"<", ur"\textless{}"),
-        (u">", ur"\textgreater{}"),
-        (u"^", ur"\textasciicircum{}"),
-        (u"\x00", ur"\textbackslash{}"),
-        (u"\N{RIGHTWARDS ARROW}", ur"$\rightarrow$"),
-    ]
-
     def encode(self, text):
-        for x, y in self.replacements:
-            text = text.replace(x, y)
+        text = unicode(text).translate(tex_escape_map)
         if self.literal_whitespace:
             # Insert a blank before the newline, to avoid
             # ! LaTeX Error: There's no line here to end.
-            text = text.replace("\n", '~\\\\\n').replace(" ", "~")
+            text = text.replace(u'\n', u'~\\\\\n').replace(u' ', u'~')
         return text
 
     def visit_Text(self, node):
         self.body.append('\n')
 
     def unknown_visit(self, node):
-        raise NotImplementedError("Unknown node: " + node.__class__.__name__)
+        raise NotImplementedError('Unknown node: ' + node.__class__.__name__)

File sphinx/util/texescape.py

+# -*- coding: utf-8 -*-
+"""
+    sphinx.util.texescape
+    ~~~~~~~~~~~~~~~~~~~~~
+
+    TeX escaping helper.
+
+    :copyright: 2008 by Georg Brandl.
+    :license: BSD.
+"""
+
+tex_replacements = [
+    # map TeX special chars
+    (u'$', ur'\$'),
+    (u'%', ur'\%'),
+    (u'&', ur'\&'),
+    (u'#', ur'\#'),
+    (u'_', ur'\_'),
+    (u'{', ur'\{'),
+    (u'}', ur'\}'),
+    (u'[', ur'{[}'),
+    (u']', ur'{]}'),
+    (u'\\',ur'\textbackslash{}'),
+    (u'~', ur'\textasciitilde{}'),
+    (u'<', ur'\textless{}'),
+    (u'>', ur'\textgreater{}'),
+    (u'^', ur'\textasciicircum{}'),
+    # map special Unicode characters to TeX commands
+    (u'¶', ur'\P{}'),
+    (u'§', ur'\S{}'),
+    (u'€', ur'\texteuro{}'),
+    (u'∞', ur'\(\infty\)'),
+    (u'±', ur'\(\pm\)'),
+    (u'→', ur'\(\rightarrow\)'),
+    (u'‣', ur'\(\rightarrow\)'),
+    # map some special Unicode characters to similar ASCII ones
+    (u'─', ur'-'),
+    (u'⎽', ur'\_'),
+    (u'╲', ur'\textbackslash{}'),
+    (u'│', ur'|'),
+    (u'ℯ', ur'e'),
+    (u'ⅈ', ur'i'),
+    (u'₁', ur'1'),
+    (u'₂', ur'2'),
+    # map Greek alphabet
+    (u'α', ur'\(\alpha\)'),
+    (u'β', ur'\(\beta\)'),
+    (u'γ', ur'\(\gamma\)'),
+    (u'δ', ur'\(\delta\)'),
+    (u'ε', ur'\(\epsilon\)'),
+    (u'ζ', ur'\(\zeta\)'),
+    (u'η', ur'\(\eta\)'),
+    (u'θ', ur'\(\theta\)'),
+    (u'ι', ur'\(\iota\)'),
+    (u'κ', ur'\(\kappa\)'),
+    (u'λ', ur'\(\lambda\)'),
+    (u'μ', ur'\(\mu\)'),
+    (u'ν', ur'\(\nu\)'),
+    (u'ξ', ur'\(\xi\)'),
+    (u'ο', ur'o'),
+    (u'π', ur'\(\pi\)'),
+    (u'ρ', ur'\(\rho\)'),
+    (u'σ', ur'\(\sigma\)'),
+    (u'τ', ur'\(\tau\)'),
+    (u'υ', u'\\(\\upsilon\\)'),
+    (u'φ', ur'\(\phi\)'),
+    (u'χ', ur'\(\chi\)'),
+    (u'ψ', ur'\(\psi\)'),
+    (u'ω', ur'\(\omega\)'),
+    (u'Α', ur'A'),
+    (u'Β', ur'B'),
+    (u'Γ', ur'\(\Gamma\)'),
+    (u'Δ', ur'\(\Delta\)'),
+    (u'Ε', ur'E'),
+    (u'Ζ', ur'Z'),
+    (u'Η', ur'H'),
+    (u'Θ', ur'\(\Theta\)'),
+    (u'Ι', ur'I'),
+    (u'Κ', ur'K'),
+    (u'Λ', ur'\(\Lambda\)'),
+    (u'Μ', ur'M'),
+    (u'Ν', ur'N'),
+    (u'Ξ', ur'\(\Xi\)'),
+    (u'Ο', ur'O'),
+    (u'Π', ur'\(\Pi\)'),
+    (u'Ρ', ur'P'),
+    (u'Σ', ur'\(\Sigma\)'),
+    (u'Τ', ur'T'),
+    (u'Υ', u'\\(\\Upsilon\\)'),
+    (u'Φ', ur'\(\Phi\)'),
+    (u'Χ', ur'X'),
+    (u'Ψ', ur'\(\Psi\)'),
+    (u'Ω', ur'\(\Omega\)'),
+    (u'Ω', ur'\(\Omega\)'),
+]
+
+tex_escape_map = {}
+tex_hl_escape_map = {}
+_new_cmd_chars = {ord(u'\\'): u'@', ord(u'{'): u'[', ord(u'}'): u']'}
+
+def init():
+    for a, b in tex_replacements:
+        tex_escape_map[ord(a)] = b
+
+    for a, b in tex_replacements:
+        if a in u'[]{}\\': continue
+        tex_hl_escape_map[ord(a)] = b.translate(_new_cmd_chars)

File tests/test_markup.py

         latex_translator.first_document = -1 # don't write \begin{document}
         document.walkabout(latex_translator)
         latex_translated = ''.join(latex_translator.body).strip()
-        assert re.match(latex_expected, latex_translated), 'from ' + rst
+        assert re.match(latex_expected, latex_translated), 'from ' + repr(rst)
 
 def verify(rst, html_expected, latex_expected):
-    verify_re(rst, re.escape(html_expected) + '$', re.escape(latex_expected) + '$')
+    if html_expected:
+        html_expected = re.escape(html_expected) + '$'
+    if latex_expected:
+        latex_expected = re.escape(latex_expected) + '$'
+    verify_re(rst, html_expected, latex_expected)
 
 
 def test_inline():
     # interpolation of arrows in menuselection
     verify(':menuselection:`a --> b`',
            u'<p><em>a \N{TRIANGULAR BULLET} b</em></p>',
-           '\\emph{a $\\rightarrow$ b}')
+           '\\emph{a \\(\\rightarrow\\) b}')
 
     # non-interpolation of dashes in option role
     verify_re(':option:`--with-option`',
            '<p><tt class="docutils literal"><span class="pre">'
            '&quot;John&quot;</span></tt></p>',
            '\\code{"John"}')
+
+def test_latex_escaping():
+    # correct escaping in normal mode
+    verify(u'Γ\\\\∞$', None, ur'\(\Gamma\)\textbackslash{}\(\infty\)\$')
+    # in verbatim code fragments
+    verify(u'::\n\n @Γ\\∞$[]', None,
+           u'\\begin{Verbatim}[commandchars=@\\[\\]]\n'
+           u'@at[]@(@Gamma@)\\@(@infty@)@$@lb[]@rb[]\n'
+           u'\\end{Verbatim}')