|
""" |
|
pygments.formatters.html |
|
~~~~~~~~~~~~~~~~~~~~~~~~ |
|
|
|
Formatter for HTML output. |
|
|
|
:copyright: Copyright 2006-2025 by the Pygments team, see AUTHORS. |
|
:license: BSD, see LICENSE for details. |
|
""" |
|
|
|
import functools |
|
import os |
|
import sys |
|
import os.path |
|
from io import StringIO |
|
|
|
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 |
|
|
|
try: |
|
import ctags |
|
except ImportError: |
|
ctags = None |
|
|
|
__all__ = ['HtmlFormatter'] |
|
|
|
|
|
_escape_html_table = { |
|
ord('&'): '&', |
|
ord('<'): '<', |
|
ord('>'): '>', |
|
ord('"'): '"', |
|
ord("'"): ''', |
|
} |
|
|
|
|
|
def escape_html(text, table=_escape_html_table): |
|
"""Escape &, <, > as well as single and double quotes for HTML.""" |
|
return text.translate(table) |
|
|
|
|
|
def webify(color): |
|
if color.startswith('calc') or color.startswith('var'): |
|
return color |
|
else: |
|
|
|
color = color.upper() |
|
if (len(color) == 6 and |
|
( color[0] == color[1] |
|
and color[2] == color[3] |
|
and color[4] == color[5])): |
|
return f'#{color[0]}{color[2]}{color[4]}' |
|
else: |
|
return f'#{color}' |
|
|
|
|
|
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 = '''\ |
|
/* |
|
generated by Pygments <https://pygments.org/> |
|
Copyright 2006-2025 by the Pygments team. |
|
Licensed under the BSD license, see LICENSE for details. |
|
*/ |
|
%(styledefs)s |
|
''' |
|
|
|
DOC_HEADER = '''\ |
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" |
|
"http://www.w3.org/TR/html4/strict.dtd"> |
|
<!-- |
|
generated by Pygments <https://pygments.org/> |
|
Copyright 2006-2025 by the Pygments team. |
|
Licensed under the BSD license, see LICENSE for details. |
|
--> |
|
<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. By default, the content is enclosed |
|
in a ``<pre>`` tag, itself wrapped in a ``<div>`` tag (but see the `nowrap` option). |
|
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). |
|
|
|
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 add a ``<pre>`` and a ``<div>`` tag |
|
around the tokens. 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 (as well as line number elements) |
|
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. The line numbers are always |
|
relative to the input (i.e. the first line is line 1) and are |
|
independent of `linenostart`. |
|
|
|
.. 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 an ``id`` (and `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). |
|
The tags file is assumed to be encoded in UTF-8. |
|
|
|
.. 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. If `linenos` is set to |
|
``'table'`` then the filename will be rendered in an initial row |
|
containing a single `<th>` which spans both columns. |
|
|
|
.. versionadded:: 2.1 |
|
|
|
`wrapcode` |
|
Wrap the code inside ``<pre>`` blocks using ``<code>``, as recommended |
|
by the HTML5 specification. |
|
|
|
.. versionadded:: 2.4 |
|
|
|
`debug_token_types` |
|
Add ``title`` attributes to all token ``<span>`` tags that show the |
|
name of the token. |
|
|
|
.. versionadded:: 2.10 |
|
|
|
|
|
**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, *, include_div): |
|
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', '')) |
|
self.wrapcode = get_bool_opt(options, 'wrapcode', False) |
|
self.span_element_openers = {} |
|
self.debug_token_types = get_bool_opt(options, 'debug_token_types', False) |
|
|
|
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: |
|
|
|
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 = get_bool_opt(options, '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 or '' |
|
|
|
def _get_css_inline_styles(self, ttype): |
|
"""Return the inline CSS styles for this token type.""" |
|
cclass = self.ttype2class.get(ttype) |
|
while cclass is None: |
|
ttype = ttype.parent |
|
cclass = self.ttype2class.get(ttype) |
|
return cclass or '' |
|
|
|
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: {}; '.format(webify(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: {}; '.format(webify(ndef['bgcolor'])) |
|
if ndef['border']: |
|
style += 'border: 1px solid {}; '.format(webify(ndef['border'])) |
|
if style: |
|
t2c[ttype] = name |
|
|
|
|
|
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. |
|
""" |
|
style_lines = [] |
|
|
|
style_lines.extend(self.get_linenos_style_defs()) |
|
style_lines.extend(self.get_background_style_defs(arg)) |
|
style_lines.extend(self.get_token_style_defs(arg)) |
|
|
|
return '\n'.join(style_lines) |
|
|
|
def get_token_style_defs(self, arg=None): |
|
prefix = self.get_css_prefix(arg) |
|
|
|
styles = [ |
|
(level, ttype, cls, style) |
|
for cls, (style, ttype, level) in self.class2style.items() |
|
if cls and style |
|
] |
|
styles.sort() |
|
|
|
lines = [ |
|
f'{prefix(cls)} {{ {style} }} /* {repr(ttype)[6:]} */' |
|
for (level, ttype, cls, style) in styles |
|
] |
|
|
|
return lines |
|
|
|
def get_background_style_defs(self, arg=None): |
|
prefix = self.get_css_prefix(arg) |
|
bg_color = self.style.background_color |
|
hl_color = self.style.highlight_color |
|
|
|
lines = [] |
|
|
|
if arg and not self.nobackground and bg_color is not None: |
|
text_style = '' |
|
if Text in self.ttype2class: |
|
text_style = ' ' + self.class2style[self.ttype2class[Text]][0] |
|
lines.insert( |
|
0, '{}{{ background: {};{} }}'.format( |
|
prefix(''), bg_color, text_style |
|
) |
|
) |
|
if hl_color is not None: |
|
lines.insert( |
|
0, '{} {{ background-color: {} }}'.format(prefix('hll'), hl_color) |
|
) |
|
|
|
return lines |
|
|
|
def get_linenos_style_defs(self): |
|
lines = [ |
|
f'pre {{ {self._pre_style} }}', |
|
f'td.linenos .normal {{ {self._linenos_style} }}', |
|
f'span.linenos {{ {self._linenos_style} }}', |
|
f'td.linenos .special {{ {self._linenos_special_style} }}', |
|
f'span.linenos.special {{ {self._linenos_special_style} }}', |
|
] |
|
|
|
return lines |
|
|
|
def get_css_prefix(self, arg): |
|
if arg is None: |
|
arg = ('cssclass' in self.options and '.'+self.cssclass or '') |
|
if isinstance(arg, str): |
|
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) |
|
|
|
return prefix |
|
|
|
@property |
|
def _pre_style(self): |
|
return 'line-height: 125%;' |
|
|
|
@property |
|
def _linenos_style(self): |
|
color = self.style.line_number_color |
|
background_color = self.style.line_number_background_color |
|
return f'color: {color}; background-color: {background_color}; padding-left: 5px; padding-right: 5px;' |
|
|
|
@property |
|
def _linenos_special_style(self): |
|
color = self.style.line_number_special_color |
|
background_color = self.style.line_number_special_background_color |
|
return f'color: {color}; background-color: {background_color}; padding-left: 5px; padding-right: 5px;' |
|
|
|
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): |
|
|
|
cssfilename = self.cssfile |
|
else: |
|
try: |
|
filename = outfile.name |
|
if not filename or filename[0] == '<': |
|
|
|
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 |
|
|
|
try: |
|
if not os.path.exists(cssfilename) or not self.noclobber_cssfile: |
|
with open(cssfilename, "w", encoding="utf-8") as cf: |
|
cf.write(CSSFILE_TEMPLATE % |
|
{'styledefs': self.get_style_defs('body')}) |
|
except OSError 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)) |
|
|
|
yield from inner |
|
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 |
|
anchor_name = self.lineanchors or self.linespans |
|
aln = self.anchorlinenos |
|
nocls = self.noclasses |
|
|
|
lines = [] |
|
|
|
for i in range(fl, fl+lncount): |
|
print_line = i % st == 0 |
|
special_line = sp and i % sp == 0 |
|
|
|
if print_line: |
|
line = '%*d' % (mw, i) |
|
if aln: |
|
line = '<a href="#%s-%d">%s</a>' % (anchor_name, i, line) |
|
else: |
|
line = ' ' * mw |
|
|
|
if nocls: |
|
if special_line: |
|
style = f' style="{self._linenos_special_style}"' |
|
else: |
|
style = f' style="{self._linenos_style}"' |
|
else: |
|
if special_line: |
|
style = ' class="special"' |
|
else: |
|
style = ' class="normal"' |
|
|
|
if style: |
|
line = f'<span{style}>{line}</span>' |
|
|
|
lines.append(line) |
|
|
|
ls = '\n'.join(lines) |
|
|
|
|
|
|
|
filename_tr = "" |
|
if self.filename: |
|
filename_tr = ( |
|
'<tr><th colspan="2" class="filename">' |
|
'<span class="filename">' + self.filename + '</span>' |
|
'</th></tr>') |
|
|
|
|
|
|
|
|
|
yield 0, (f'<table class="{self.cssclass}table">' + filename_tr + |
|
'<tr><td class="linenos"><div class="linenodiv"><pre>' + |
|
ls + '</pre></div></td><td class="code">') |
|
yield 0, '<div>' |
|
yield 0, dummyoutfile.getvalue() |
|
yield 0, '</div>' |
|
yield 0, '</td></tr></table>' |
|
|
|
|
|
def _wrap_inlinelinenos(self, inner): |
|
|
|
inner_lines = list(inner) |
|
sp = self.linenospecial |
|
st = self.linenostep |
|
num = self.linenostart |
|
mw = len(str(len(inner_lines) + num - 1)) |
|
anchor_name = self.lineanchors or self.linespans |
|
aln = self.anchorlinenos |
|
nocls = self.noclasses |
|
|
|
for _, inner_line in inner_lines: |
|
print_line = num % st == 0 |
|
special_line = sp and num % sp == 0 |
|
|
|
if print_line: |
|
line = '%*d' % (mw, num) |
|
else: |
|
line = ' ' * mw |
|
|
|
if nocls: |
|
if special_line: |
|
style = f' style="{self._linenos_special_style}"' |
|
else: |
|
style = f' style="{self._linenos_style}"' |
|
else: |
|
if special_line: |
|
style = ' class="linenos special"' |
|
else: |
|
style = ' class="linenos"' |
|
|
|
if style: |
|
linenos = f'<span{style}>{line}</span>' |
|
else: |
|
linenos = line |
|
|
|
if aln: |
|
yield 1, ('<a href="#%s-%d">%s</a>' % (anchor_name, num, linenos) + |
|
inner_line) |
|
else: |
|
yield 1, linenos + inner_line |
|
num += 1 |
|
|
|
def _wrap_lineanchors(self, inner): |
|
s = self.lineanchors |
|
|
|
i = self.linenostart - 1 |
|
for t, line in inner: |
|
if t: |
|
i += 1 |
|
href = "" if self.linenos else ' href="#%s-%d"' % (s, i) |
|
yield 1, '<a id="%s-%d" name="%s-%d"%s></a>' % (s, i, s, i, href) + 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(f'background: {self.style.background_color}') |
|
if self.cssstyles: |
|
style.append(self.cssstyles) |
|
style = '; '.join(style) |
|
|
|
yield 0, ('<div' + (self.cssclass and f' class="{self.cssclass}"') + |
|
(style and (f' style="{style}"')) + '>') |
|
yield from inner |
|
yield 0, '</div>\n' |
|
|
|
def _wrap_pre(self, inner): |
|
style = [] |
|
if self.prestyles: |
|
style.append(self.prestyles) |
|
if self.noclasses: |
|
style.append(self._pre_style) |
|
style = '; '.join(style) |
|
|
|
if self.filename and self.linenos != 1: |
|
yield 0, ('<span class="filename">' + self.filename + '</span>') |
|
|
|
|
|
|
|
yield 0, ('<pre' + (style and f' style="{style}"') + '><span></span>') |
|
yield from inner |
|
yield 0, '</pre>' |
|
|
|
def _wrap_code(self, inner): |
|
yield 0, '<code>' |
|
yield from inner |
|
yield 0, '</code>' |
|
|
|
@functools.lru_cache(maxsize=100) |
|
def _translate_parts(self, value): |
|
"""HTML-escape a value and split it by newlines.""" |
|
return value.translate(_escape_html_table).split('\n') |
|
|
|
def _format_lines(self, tokensource): |
|
""" |
|
Just format the tokens, without any wrapping tags. |
|
Yield individual lines. |
|
""" |
|
nocls = self.noclasses |
|
lsep = self.lineseparator |
|
tagsfile = self.tagsfile |
|
|
|
lspan = '' |
|
line = [] |
|
for ttype, value in tokensource: |
|
try: |
|
cspan = self.span_element_openers[ttype] |
|
except KeyError: |
|
title = ' title="{}"'.format('.'.join(ttype)) if self.debug_token_types else '' |
|
if nocls: |
|
css_style = self._get_css_inline_styles(ttype) |
|
if css_style: |
|
css_style = self.class2style[css_style][0] |
|
cspan = f'<span style="{css_style}"{title}>' |
|
else: |
|
cspan = '' |
|
else: |
|
css_class = self._get_css_classes(ttype) |
|
if css_class: |
|
cspan = f'<span class="{css_class}"{title}>' |
|
else: |
|
cspan = '' |
|
self.span_element_openers[ttype] = cspan |
|
|
|
parts = self._translate_parts(value) |
|
|
|
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 part in parts[:-1]: |
|
if line: |
|
|
|
|
|
if lspan != cspan and part: |
|
line.extend(((lspan and '</span>'), cspan, part, |
|
(cspan and '</span>'), lsep)) |
|
else: |
|
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 |
|
|
|
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 |
|
|
|
|
|
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.encode(), 0): |
|
return entry['file'].decode(), 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: |
|
if self.noclasses: |
|
style = '' |
|
if self.style.highlight_color is not None: |
|
style = (f' style="background-color: {self.style.highlight_color}"') |
|
yield 1, f'<span{style}>{value}</span>' |
|
else: |
|
yield 1, f'<span class="hll">{value}</span>' |
|
else: |
|
yield 1, value |
|
|
|
def wrap(self, source): |
|
""" |
|
Wrap the ``source``, which is a generator yielding |
|
individual lines, in custom generators. See docstring |
|
for `format`. Can be overridden. |
|
""" |
|
|
|
output = source |
|
if self.wrapcode: |
|
output = self._wrap_code(output) |
|
|
|
output = self._wrap_pre(output) |
|
|
|
return output |
|
|
|
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 not self.nowrap and self.linenos == 2: |
|
source = self._wrap_inlinelinenos(source) |
|
|
|
if self.hl_lines: |
|
source = self._highlight_lines(source) |
|
|
|
if not self.nowrap: |
|
if self.lineanchors: |
|
source = self._wrap_lineanchors(source) |
|
if self.linespans: |
|
source = self._wrap_linespans(source) |
|
source = self.wrap(source) |
|
if self.linenos == 1: |
|
source = self._wrap_tablelinenos(source) |
|
source = self._wrap_div(source) |
|
if self.full: |
|
source = self._wrap_full(source, outfile) |
|
|
|
for t, piece in source: |
|
outfile.write(piece) |
|
|