diff options
Diffstat (limited to 'pygments/formatters/html.py')
| -rw-r--r-- | pygments/formatters/html.py | 851 | 
1 files changed, 851 insertions, 0 deletions
diff --git a/pygments/formatters/html.py b/pygments/formatters/html.py new file mode 100644 index 00000000..2c6bb19e --- /dev/null +++ b/pygments/formatters/html.py @@ -0,0 +1,851 @@ +# -*- coding: utf-8 -*- +""" +    pygments.formatters.html +    ~~~~~~~~~~~~~~~~~~~~~~~~ + +    Formatter for HTML output. + +    :copyright: Copyright 2006-2015 by the Pygments team, see AUTHORS. +    :license: BSD, see LICENSE for details. +""" + +from __future__ import print_function + +import os +import sys +import os.path + +from pygments.formatter import Formatter +from pygments.token import Token, Text, STANDARD_TYPES +from pygments.util import get_bool_opt, get_int_opt, get_list_opt, \ +    StringIO, string_types, iteritems + +try: +    import ctags +except ImportError: +    ctags = None + +__all__ = ['HtmlFormatter'] + + +_escape_html_table = { +    ord('&'): u'&', +    ord('<'): u'<', +    ord('>'): u'>', +    ord('"'): u'"', +    ord("'"): u''', +} + + +def escape_html(text, table=_escape_html_table): +    """Escape &, <, > as well as single and double quotes for HTML.""" +    return text.translate(table) + + +def _get_ttype_class(ttype): +    fname = STANDARD_TYPES.get(ttype) +    if fname: +        return fname +    aname = '' +    while fname is None: +        aname = '-' + ttype[-1] + aname +        ttype = ttype.parent +        fname = STANDARD_TYPES.get(ttype) +    return fname + aname + + +CSSFILE_TEMPLATE = '''\ +td.linenos { background-color: #f0f0f0; padding-right: 10px; } +span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } +pre { line-height: 125%%; } +%(styledefs)s +''' + +DOC_HEADER = '''\ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" +   "http://www.w3.org/TR/html4/strict.dtd"> + +<html> +<head> +  <title>%(title)s</title> +  <meta http-equiv="content-type" content="text/html; charset=%(encoding)s"> +  <style type="text/css"> +''' + CSSFILE_TEMPLATE + ''' +  </style> +</head> +<body> +<h2>%(title)s</h2> + +''' + +DOC_HEADER_EXTERNALCSS = '''\ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" +   "http://www.w3.org/TR/html4/strict.dtd"> + +<html> +<head> +  <title>%(title)s</title> +  <meta http-equiv="content-type" content="text/html; charset=%(encoding)s"> +  <link rel="stylesheet" href="%(cssfile)s" type="text/css"> +</head> +<body> +<h2>%(title)s</h2> + +''' + +DOC_FOOTER = '''\ +</body> +</html> +''' + + +class HtmlFormatter(Formatter): +    r""" +    Format tokens as HTML 4 ``<span>`` tags within a ``<pre>`` tag, wrapped +    in a ``<div>`` tag. The ``<div>``'s CSS class can be set by the `cssclass` +    option. + +    If the `linenos` option is set to ``"table"``, the ``<pre>`` is +    additionally wrapped inside a ``<table>`` which has one row and two +    cells: one containing the line numbers and one containing the code. +    Example: + +    .. sourcecode:: html + +        <div class="highlight" > +        <table><tr> +          <td class="linenos" title="click to toggle" +            onclick="with (this.firstChild.style) +                     { display = (display == '') ? 'none' : '' }"> +            <pre>1 +            2</pre> +          </td> +          <td class="code"> +            <pre><span class="Ke">def </span><span class="NaFu">foo</span>(bar): +              <span class="Ke">pass</span> +            </pre> +          </td> +        </tr></table></div> + +    (whitespace added to improve clarity). + +    Wrapping can be disabled using the `nowrap` option. + +    A list of lines can be specified using the `hl_lines` option to make these +    lines highlighted (as of Pygments 0.11). + +    With the `full` option, a complete HTML 4 document is output, including +    the style definitions inside a ``<style>`` tag, or in a separate file if +    the `cssfile` option is given. + +    When `tagsfile` is set to the path of a ctags index file, it is used to +    generate hyperlinks from names to their definition.  You must enable +    `lineanchors` and run ctags with the `-n` option for this to work.  The +    `python-ctags` module from PyPI must be installed to use this feature; +    otherwise a `RuntimeError` will be raised. + +    The `get_style_defs(arg='')` method of a `HtmlFormatter` returns a string +    containing CSS rules for the CSS classes used by the formatter. The +    argument `arg` can be used to specify additional CSS selectors that +    are prepended to the classes. A call `fmter.get_style_defs('td .code')` +    would result in the following CSS classes: + +    .. sourcecode:: css + +        td .code .kw { font-weight: bold; color: #00FF00 } +        td .code .cm { color: #999999 } +        ... + +    If you have Pygments 0.6 or higher, you can also pass a list or tuple to the +    `get_style_defs()` method to request multiple prefixes for the tokens: + +    .. sourcecode:: python + +        formatter.get_style_defs(['div.syntax pre', 'pre.syntax']) + +    The output would then look like this: + +    .. sourcecode:: css + +        div.syntax pre .kw, +        pre.syntax .kw { font-weight: bold; color: #00FF00 } +        div.syntax pre .cm, +        pre.syntax .cm { color: #999999 } +        ... + +    Additional options accepted: + +    `nowrap` +        If set to ``True``, don't wrap the tokens at all, not even inside a ``<pre>`` +        tag. This disables most other options (default: ``False``). + +    `full` +        Tells the formatter to output a "full" document, i.e. a complete +        self-contained document (default: ``False``). + +    `title` +        If `full` is true, the title that should be used to caption the +        document (default: ``''``). + +    `style` +        The style to use, can be a string or a Style subclass (default: +        ``'default'``). This option has no effect if the `cssfile` +        and `noclobber_cssfile` option are given and the file specified in +        `cssfile` exists. + +    `noclasses` +        If set to true, token ``<span>`` tags will not use CSS classes, but +        inline styles. This is not recommended for larger pieces of code since +        it increases output size by quite a bit (default: ``False``). + +    `classprefix` +        Since the token types use relatively short class names, they may clash +        with some of your own class names. In this case you can use the +        `classprefix` option to give a string to prepend to all Pygments-generated +        CSS class names for token types. +        Note that this option also affects the output of `get_style_defs()`. + +    `cssclass` +        CSS class for the wrapping ``<div>`` tag (default: ``'highlight'``). +        If you set this option, the default selector for `get_style_defs()` +        will be this class. + +        .. versionadded:: 0.9 +           If you select the ``'table'`` line numbers, the wrapping table will +           have a CSS class of this string plus ``'table'``, the default is +           accordingly ``'highlighttable'``. + +    `cssstyles` +        Inline CSS styles for the wrapping ``<div>`` tag (default: ``''``). + +    `prestyles` +        Inline CSS styles for the ``<pre>`` tag (default: ``''``). + +        .. versionadded:: 0.11 + +    `cssfile` +        If the `full` option is true and this option is given, it must be the +        name of an external file. If the filename does not include an absolute +        path, the file's path will be assumed to be relative to the main output +        file's path, if the latter can be found. The stylesheet is then written +        to this file instead of the HTML file. + +        .. versionadded:: 0.6 + +    `noclobber_cssfile` +        If `cssfile` is given and the specified file exists, the css file will +        not be overwritten. This allows the use of the `full` option in +        combination with a user specified css file. Default is ``False``. + +        .. versionadded:: 1.1 + +    `linenos` +        If set to ``'table'``, output line numbers as a table with two cells, +        one containing the line numbers, the other the whole code.  This is +        copy-and-paste-friendly, but may cause alignment problems with some +        browsers or fonts.  If set to ``'inline'``, the line numbers will be +        integrated in the ``<pre>`` tag that contains the code (that setting +        is *new in Pygments 0.8*). + +        For compatibility with Pygments 0.7 and earlier, every true value +        except ``'inline'`` means the same as ``'table'`` (in particular, that +        means also ``True``). + +        The default value is ``False``, which means no line numbers at all. + +        **Note:** with the default ("table") line number mechanism, the line +        numbers and code can have different line heights in Internet Explorer +        unless you give the enclosing ``<pre>`` tags an explicit ``line-height`` +        CSS property (you get the default line spacing with ``line-height: +        125%``). + +    `hl_lines` +        Specify a list of lines to be highlighted. + +        .. versionadded:: 0.11 + +    `linenostart` +        The line number for the first line (default: ``1``). + +    `linenostep` +        If set to a number n > 1, only every nth line number is printed. + +    `linenospecial` +        If set to a number n > 0, every nth line number is given the CSS +        class ``"special"`` (default: ``0``). + +    `nobackground` +        If set to ``True``, the formatter won't output the background color +        for the wrapping element (this automatically defaults to ``False`` +        when there is no wrapping element [eg: no argument for the +        `get_syntax_defs` method given]) (default: ``False``). + +        .. versionadded:: 0.6 + +    `lineseparator` +        This string is output between lines of code. It defaults to ``"\n"``, +        which is enough to break a line inside ``<pre>`` tags, but you can +        e.g. set it to ``"<br>"`` to get HTML line breaks. + +        .. versionadded:: 0.7 + +    `lineanchors` +        If set to a nonempty string, e.g. ``foo``, the formatter will wrap each +        output line in an anchor tag with a ``name`` of ``foo-linenumber``. +        This allows easy linking to certain lines. + +        .. versionadded:: 0.9 + +    `linespans` +        If set to a nonempty string, e.g. ``foo``, the formatter will wrap each +        output line in a span tag with an ``id`` of ``foo-linenumber``. +        This allows easy access to lines via javascript. + +        .. versionadded:: 1.6 + +    `anchorlinenos` +        If set to `True`, will wrap line numbers in <a> tags. Used in +        combination with `linenos` and `lineanchors`. + +    `tagsfile` +        If set to the path of a ctags file, wrap names in anchor tags that +        link to their definitions. `lineanchors` should be used, and the +        tags file should specify line numbers (see the `-n` option to ctags). + +        .. versionadded:: 1.6 + +    `tagurlformat` +        A string formatting pattern used to generate links to ctags definitions. +        Available variables are `%(path)s`, `%(fname)s` and `%(fext)s`. +        Defaults to an empty string, resulting in just `#prefix-number` links. + +        .. versionadded:: 1.6 + +    `filename` +        A string used to generate a filename when rendering <pre> blocks, +        for example if displaying source code. + +        .. versionadded:: 2.1 + + +    **Subclassing the HTML formatter** + +    .. versionadded:: 0.7 + +    The HTML formatter is now built in a way that allows easy subclassing, thus +    customizing the output HTML code. The `format()` method calls +    `self._format_lines()` which returns a generator that yields tuples of ``(1, +    line)``, where the ``1`` indicates that the ``line`` is a line of the +    formatted source code. + +    If the `nowrap` option is set, the generator is the iterated over and the +    resulting HTML is output. + +    Otherwise, `format()` calls `self.wrap()`, which wraps the generator with +    other generators. These may add some HTML code to the one generated by +    `_format_lines()`, either by modifying the lines generated by the latter, +    then yielding them again with ``(1, line)``, and/or by yielding other HTML +    code before or after the lines, with ``(0, html)``. The distinction between +    source lines and other code makes it possible to wrap the generator multiple +    times. + +    The default `wrap()` implementation adds a ``<div>`` and a ``<pre>`` tag. + +    A custom `HtmlFormatter` subclass could look like this: + +    .. sourcecode:: python + +        class CodeHtmlFormatter(HtmlFormatter): + +            def wrap(self, source, outfile): +                return self._wrap_code(source) + +            def _wrap_code(self, source): +                yield 0, '<code>' +                for i, t in source: +                    if i == 1: +                        # it's a line of formatted code +                        t += '<br>' +                    yield i, t +                yield 0, '</code>' + +    This results in wrapping the formatted lines with a ``<code>`` tag, where the +    source lines are broken using ``<br>`` tags. + +    After calling `wrap()`, the `format()` method also adds the "line numbers" +    and/or "full document" wrappers if the respective options are set. Then, all +    HTML yielded by the wrapped generator is output. +    """ + +    name = 'HTML' +    aliases = ['html'] +    filenames = ['*.html', '*.htm'] + +    def __init__(self, **options): +        Formatter.__init__(self, **options) +        self.title = self._decodeifneeded(self.title) +        self.nowrap = get_bool_opt(options, 'nowrap', False) +        self.noclasses = get_bool_opt(options, 'noclasses', False) +        self.classprefix = options.get('classprefix', '') +        self.cssclass = self._decodeifneeded(options.get('cssclass', 'highlight')) +        self.cssstyles = self._decodeifneeded(options.get('cssstyles', '')) +        self.prestyles = self._decodeifneeded(options.get('prestyles', '')) +        self.cssfile = self._decodeifneeded(options.get('cssfile', '')) +        self.noclobber_cssfile = get_bool_opt(options, 'noclobber_cssfile', False) +        self.tagsfile = self._decodeifneeded(options.get('tagsfile', '')) +        self.tagurlformat = self._decodeifneeded(options.get('tagurlformat', '')) +        self.filename = self._decodeifneeded(options.get('filename', '')) + +        if self.tagsfile: +            if not ctags: +                raise RuntimeError('The "ctags" package must to be installed ' +                                   'to be able to use the "tagsfile" feature.') +            self._ctags = ctags.CTags(self.tagsfile) + +        linenos = options.get('linenos', False) +        if linenos == 'inline': +            self.linenos = 2 +        elif linenos: +            # compatibility with <= 0.7 +            self.linenos = 1 +        else: +            self.linenos = 0 +        self.linenostart = abs(get_int_opt(options, 'linenostart', 1)) +        self.linenostep = abs(get_int_opt(options, 'linenostep', 1)) +        self.linenospecial = abs(get_int_opt(options, 'linenospecial', 0)) +        self.nobackground = get_bool_opt(options, 'nobackground', False) +        self.lineseparator = options.get('lineseparator', '\n') +        self.lineanchors = options.get('lineanchors', '') +        self.linespans = options.get('linespans', '') +        self.anchorlinenos = options.get('anchorlinenos', False) +        self.hl_lines = set() +        for lineno in get_list_opt(options, 'hl_lines', []): +            try: +                self.hl_lines.add(int(lineno)) +            except ValueError: +                pass + +        self._create_stylesheet() + +    def _get_css_class(self, ttype): +        """Return the css class of this token type prefixed with +        the classprefix option.""" +        ttypeclass = _get_ttype_class(ttype) +        if ttypeclass: +            return self.classprefix + ttypeclass +        return '' + +    def _get_css_classes(self, ttype): +        """Return the css classes of this token type prefixed with +        the classprefix option.""" +        cls = self._get_css_class(ttype) +        while ttype not in STANDARD_TYPES: +            ttype = ttype.parent +            cls = self._get_css_class(ttype) + ' ' + cls +        return cls + +    def _create_stylesheet(self): +        t2c = self.ttype2class = {Token: ''} +        c2s = self.class2style = {} +        for ttype, ndef in self.style: +            name = self._get_css_class(ttype) +            style = '' +            if ndef['color']: +                style += 'color: #%s; ' % ndef['color'] +            if ndef['bold']: +                style += 'font-weight: bold; ' +            if ndef['italic']: +                style += 'font-style: italic; ' +            if ndef['underline']: +                style += 'text-decoration: underline; ' +            if ndef['bgcolor']: +                style += 'background-color: #%s; ' % ndef['bgcolor'] +            if ndef['border']: +                style += 'border: 1px solid #%s; ' % ndef['border'] +            if style: +                t2c[ttype] = name +                # save len(ttype) to enable ordering the styles by +                # hierarchy (necessary for CSS cascading rules!) +                c2s[name] = (style[:-2], ttype, len(ttype)) + +    def get_style_defs(self, arg=None): +        """ +        Return CSS style definitions for the classes produced by the current +        highlighting style. ``arg`` can be a string or list of selectors to +        insert before the token type classes. +        """ +        if arg is None: +            arg = ('cssclass' in self.options and '.'+self.cssclass or '') +        if isinstance(arg, string_types): +            args = [arg] +        else: +            args = list(arg) + +        def prefix(cls): +            if cls: +                cls = '.' + cls +            tmp = [] +            for arg in args: +                tmp.append((arg and arg + ' ' or '') + cls) +            return ', '.join(tmp) + +        styles = [(level, ttype, cls, style) +                  for cls, (style, ttype, level) in iteritems(self.class2style) +                  if cls and style] +        styles.sort() +        lines = ['%s { %s } /* %s */' % (prefix(cls), style, repr(ttype)[6:]) +                 for (level, ttype, cls, style) in styles] +        if arg and not self.nobackground and \ +           self.style.background_color is not None: +            text_style = '' +            if Text in self.ttype2class: +                text_style = ' ' + self.class2style[self.ttype2class[Text]][0] +            lines.insert(0, '%s { background: %s;%s }' % +                         (prefix(''), self.style.background_color, text_style)) +        if self.style.highlight_color is not None: +            lines.insert(0, '%s.hll { background-color: %s }' % +                         (prefix(''), self.style.highlight_color)) +        return '\n'.join(lines) + +    def _decodeifneeded(self, value): +        if isinstance(value, bytes): +            if self.encoding: +                return value.decode(self.encoding) +            return value.decode() +        return value + +    def _wrap_full(self, inner, outfile): +        if self.cssfile: +            if os.path.isabs(self.cssfile): +                # it's an absolute filename +                cssfilename = self.cssfile +            else: +                try: +                    filename = outfile.name +                    if not filename or filename[0] == '<': +                        # pseudo files, e.g. name == '<fdopen>' +                        raise AttributeError +                    cssfilename = os.path.join(os.path.dirname(filename), +                                               self.cssfile) +                except AttributeError: +                    print('Note: Cannot determine output file name, ' +                          'using current directory as base for the CSS file name', +                          file=sys.stderr) +                    cssfilename = self.cssfile +            # write CSS file only if noclobber_cssfile isn't given as an option. +            try: +                if not os.path.exists(cssfilename) or not self.noclobber_cssfile: +                    cf = open(cssfilename, "w") +                    cf.write(CSSFILE_TEMPLATE % +                             {'styledefs': self.get_style_defs('body')}) +                    cf.close() +            except IOError as err: +                err.strerror = 'Error writing CSS file: ' + err.strerror +                raise + +            yield 0, (DOC_HEADER_EXTERNALCSS % +                      dict(title=self.title, +                           cssfile=self.cssfile, +                           encoding=self.encoding)) +        else: +            yield 0, (DOC_HEADER % +                      dict(title=self.title, +                           styledefs=self.get_style_defs('body'), +                           encoding=self.encoding)) + +        for t, line in inner: +            yield t, line +        yield 0, DOC_FOOTER + +    def _wrap_tablelinenos(self, inner): +        dummyoutfile = StringIO() +        lncount = 0 +        for t, line in inner: +            if t: +                lncount += 1 +            dummyoutfile.write(line) + +        fl = self.linenostart +        mw = len(str(lncount + fl - 1)) +        sp = self.linenospecial +        st = self.linenostep +        la = self.lineanchors +        aln = self.anchorlinenos +        nocls = self.noclasses +        if sp: +            lines = [] + +            for i in range(fl, fl+lncount): +                if i % st == 0: +                    if i % sp == 0: +                        if aln: +                            lines.append('<a href="#%s-%d" class="special">%*d</a>' % +                                         (la, i, mw, i)) +                        else: +                            lines.append('<span class="special">%*d</span>' % (mw, i)) +                    else: +                        if aln: +                            lines.append('<a href="#%s-%d">%*d</a>' % (la, i, mw, i)) +                        else: +                            lines.append('%*d' % (mw, i)) +                else: +                    lines.append('') +            ls = '\n'.join(lines) +        else: +            lines = [] +            for i in range(fl, fl+lncount): +                if i % st == 0: +                    if aln: +                        lines.append('<a href="#%s-%d">%*d</a>' % (la, i, mw, i)) +                    else: +                        lines.append('%*d' % (mw, i)) +                else: +                    lines.append('') +            ls = '\n'.join(lines) + +        # in case you wonder about the seemingly redundant <div> here: since the +        # content in the other cell also is wrapped in a div, some browsers in +        # some configurations seem to mess up the formatting... +        if nocls: +            yield 0, ('<table class="%stable">' % self.cssclass + +                      '<tr><td><div class="linenodiv" ' +                      'style="background-color: #f0f0f0; padding-right: 10px">' +                      '<pre style="line-height: 125%">' + +                      ls + '</pre></div></td><td class="code">') +        else: +            yield 0, ('<table class="%stable">' % self.cssclass + +                      '<tr><td class="linenos"><div class="linenodiv"><pre>' + +                      ls + '</pre></div></td><td class="code">') +        yield 0, dummyoutfile.getvalue() +        yield 0, '</td></tr></table>' + +    def _wrap_inlinelinenos(self, inner): +        # need a list of lines since we need the width of a single number :( +        lines = list(inner) +        sp = self.linenospecial +        st = self.linenostep +        num = self.linenostart +        mw = len(str(len(lines) + num - 1)) + +        if self.noclasses: +            if sp: +                for t, line in lines: +                    if num % sp == 0: +                        style = 'background-color: #ffffc0; padding: 0 5px 0 5px' +                    else: +                        style = 'background-color: #f0f0f0; padding: 0 5px 0 5px' +                    yield 1, '<span style="%s">%*s </span>' % ( +                        style, mw, (num % st and ' ' or num)) + line +                    num += 1 +            else: +                for t, line in lines: +                    yield 1, ('<span style="background-color: #f0f0f0; ' +                              'padding: 0 5px 0 5px">%*s </span>' % ( +                                  mw, (num % st and ' ' or num)) + line) +                    num += 1 +        elif sp: +            for t, line in lines: +                yield 1, '<span class="lineno%s">%*s </span>' % ( +                    num % sp == 0 and ' special' or '', mw, +                    (num % st and ' ' or num)) + line +                num += 1 +        else: +            for t, line in lines: +                yield 1, '<span class="lineno">%*s </span>' % ( +                    mw, (num % st and ' ' or num)) + line +                num += 1 + +    def _wrap_lineanchors(self, inner): +        s = self.lineanchors +        # subtract 1 since we have to increment i *before* yielding +        i = self.linenostart - 1 +        for t, line in inner: +            if t: +                i += 1 +                yield 1, '<a name="%s-%d"></a>' % (s, i) + line +            else: +                yield 0, line + +    def _wrap_linespans(self, inner): +        s = self.linespans +        i = self.linenostart - 1 +        for t, line in inner: +            if t: +                i += 1 +                yield 1, '<span id="%s-%d">%s</span>' % (s, i, line) +            else: +                yield 0, line + +    def _wrap_div(self, inner): +        style = [] +        if (self.noclasses and not self.nobackground and +                self.style.background_color is not None): +            style.append('background: %s' % (self.style.background_color,)) +        if self.cssstyles: +            style.append(self.cssstyles) +        style = '; '.join(style) + +        yield 0, ('<div' + (self.cssclass and ' class="%s"' % self.cssclass) + +                  (style and (' style="%s"' % style)) + '>') +        for tup in inner: +            yield tup +        yield 0, '</div>\n' + +    def _wrap_pre(self, inner): +        style = [] +        if self.prestyles: +            style.append(self.prestyles) +        if self.noclasses: +            style.append('line-height: 125%') +        style = '; '.join(style) + +        if self.filename: +            yield 0, ('<span class="filename">' + self.filename + '</span>') + +        # the empty span here is to keep leading empty lines from being +        # ignored by HTML parsers +        yield 0, ('<pre' + (style and ' style="%s"' % style) + '><span></span>') +        for tup in inner: +            yield tup +        yield 0, '</pre>' + +    def _format_lines(self, tokensource): +        """ +        Just format the tokens, without any wrapping tags. +        Yield individual lines. +        """ +        nocls = self.noclasses +        lsep = self.lineseparator +        # for <span style=""> lookup only +        getcls = self.ttype2class.get +        c2s = self.class2style +        escape_table = _escape_html_table +        tagsfile = self.tagsfile + +        lspan = '' +        line = [] +        for ttype, value in tokensource: +            if nocls: +                cclass = getcls(ttype) +                while cclass is None: +                    ttype = ttype.parent +                    cclass = getcls(ttype) +                cspan = cclass and '<span style="%s">' % c2s[cclass][0] or '' +            else: +                cls = self._get_css_classes(ttype) +                cspan = cls and '<span class="%s">' % cls or '' + +            parts = value.translate(escape_table).split('\n') + +            if tagsfile and ttype in Token.Name: +                filename, linenumber = self._lookup_ctag(value) +                if linenumber: +                    base, filename = os.path.split(filename) +                    if base: +                        base += '/' +                    filename, extension = os.path.splitext(filename) +                    url = self.tagurlformat % {'path': base, 'fname': filename, +                                               'fext': extension} +                    parts[0] = "<a href=\"%s#%s-%d\">%s" % \ +                        (url, self.lineanchors, linenumber, parts[0]) +                    parts[-1] = parts[-1] + "</a>" + +            # for all but the last line +            for part in parts[:-1]: +                if line: +                    if lspan != cspan: +                        line.extend(((lspan and '</span>'), cspan, part, +                                     (cspan and '</span>'), lsep)) +                    else:  # both are the same +                        line.extend((part, (lspan and '</span>'), lsep)) +                    yield 1, ''.join(line) +                    line = [] +                elif part: +                    yield 1, ''.join((cspan, part, (cspan and '</span>'), lsep)) +                else: +                    yield 1, lsep +            # for the last line +            if line and parts[-1]: +                if lspan != cspan: +                    line.extend(((lspan and '</span>'), cspan, parts[-1])) +                    lspan = cspan +                else: +                    line.append(parts[-1]) +            elif parts[-1]: +                line = [cspan, parts[-1]] +                lspan = cspan +            # else we neither have to open a new span nor set lspan + +        if line: +            line.extend(((lspan and '</span>'), lsep)) +            yield 1, ''.join(line) + +    def _lookup_ctag(self, token): +        entry = ctags.TagEntry() +        if self._ctags.find(entry, token, 0): +            return entry['file'], entry['lineNumber'] +        else: +            return None, None + +    def _highlight_lines(self, tokensource): +        """ +        Highlighted the lines specified in the `hl_lines` option by +        post-processing the token stream coming from `_format_lines`. +        """ +        hls = self.hl_lines + +        for i, (t, value) in enumerate(tokensource): +            if t != 1: +                yield t, value +            if i + 1 in hls:  # i + 1 because Python indexes start at 0 +                if self.noclasses: +                    style = '' +                    if self.style.highlight_color is not None: +                        style = (' style="background-color: %s"' % +                                 (self.style.highlight_color,)) +                    yield 1, '<span%s>%s</span>' % (style, value) +                else: +                    yield 1, '<span class="hll">%s</span>' % value +            else: +                yield 1, value + +    def wrap(self, source, outfile): +        """ +        Wrap the ``source``, which is a generator yielding +        individual lines, in custom generators. See docstring +        for `format`. Can be overridden. +        """ +        return self._wrap_div(self._wrap_pre(source)) + +    def format_unencoded(self, tokensource, outfile): +        """ +        The formatting process uses several nested generators; which of +        them are used is determined by the user's options. + +        Each generator should take at least one argument, ``inner``, +        and wrap the pieces of text generated by this. + +        Always yield 2-tuples: (code, text). If "code" is 1, the text +        is part of the original tokensource being highlighted, if it's +        0, the text is some piece of wrapping. This makes it possible to +        use several different wrappers that process the original source +        linewise, e.g. line number generators. +        """ +        source = self._format_lines(tokensource) +        if self.hl_lines: +            source = self._highlight_lines(source) +        if not self.nowrap: +            if self.linenos == 2: +                source = self._wrap_inlinelinenos(source) +            if self.lineanchors: +                source = self._wrap_lineanchors(source) +            if self.linespans: +                source = self._wrap_linespans(source) +            source = self.wrap(source, outfile) +            if self.linenos == 1: +                source = self._wrap_tablelinenos(source) +            if self.full: +                source = self._wrap_full(source, outfile) + +        for t, piece in source: +            outfile.write(piece)  | 
