Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pygments/formatters/html.py: 14%
412 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
1"""
2 pygments.formatters.html
3 ~~~~~~~~~~~~~~~~~~~~~~~~
5 Formatter for HTML output.
7 :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
8 :license: BSD, see LICENSE for details.
9"""
11import functools
12import os
13import sys
14import os.path
15from io import StringIO
17from pygments.formatter import Formatter
18from pygments.token import Token, Text, STANDARD_TYPES
19from pygments.util import get_bool_opt, get_int_opt, get_list_opt
21try:
22 import ctags
23except ImportError:
24 ctags = None
26__all__ = ['HtmlFormatter']
29_escape_html_table = {
30 ord('&'): '&',
31 ord('<'): '<',
32 ord('>'): '>',
33 ord('"'): '"',
34 ord("'"): ''',
35}
38def escape_html(text, table=_escape_html_table):
39 """Escape &, <, > as well as single and double quotes for HTML."""
40 return text.translate(table)
43def webify(color):
44 if color.startswith('calc') or color.startswith('var'):
45 return color
46 else:
47 return '#' + color
50def _get_ttype_class(ttype):
51 fname = STANDARD_TYPES.get(ttype)
52 if fname:
53 return fname
54 aname = ''
55 while fname is None:
56 aname = '-' + ttype[-1] + aname
57 ttype = ttype.parent
58 fname = STANDARD_TYPES.get(ttype)
59 return fname + aname
62CSSFILE_TEMPLATE = '''\
63/*
64generated by Pygments <https://pygments.org/>
65Copyright 2006-2023 by the Pygments team.
66Licensed under the BSD license, see LICENSE for details.
67*/
68%(styledefs)s
69'''
71DOC_HEADER = '''\
72<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
73 "http://www.w3.org/TR/html4/strict.dtd">
74<!--
75generated by Pygments <https://pygments.org/>
76Copyright 2006-2023 by the Pygments team.
77Licensed under the BSD license, see LICENSE for details.
78-->
79<html>
80<head>
81 <title>%(title)s</title>
82 <meta http-equiv="content-type" content="text/html; charset=%(encoding)s">
83 <style type="text/css">
84''' + CSSFILE_TEMPLATE + '''
85 </style>
86</head>
87<body>
88<h2>%(title)s</h2>
90'''
92DOC_HEADER_EXTERNALCSS = '''\
93<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
94 "http://www.w3.org/TR/html4/strict.dtd">
96<html>
97<head>
98 <title>%(title)s</title>
99 <meta http-equiv="content-type" content="text/html; charset=%(encoding)s">
100 <link rel="stylesheet" href="%(cssfile)s" type="text/css">
101</head>
102<body>
103<h2>%(title)s</h2>
105'''
107DOC_FOOTER = '''\
108</body>
109</html>
110'''
113class HtmlFormatter(Formatter):
114 r"""
115 Format tokens as HTML 4 ``<span>`` tags. By default, the content is enclosed
116 in a ``<pre>`` tag, itself wrapped in a ``<div>`` tag (but see the `nowrap` option).
117 The ``<div>``'s CSS class can be set by the `cssclass` option.
119 If the `linenos` option is set to ``"table"``, the ``<pre>`` is
120 additionally wrapped inside a ``<table>`` which has one row and two
121 cells: one containing the line numbers and one containing the code.
122 Example:
124 .. sourcecode:: html
126 <div class="highlight" >
127 <table><tr>
128 <td class="linenos" title="click to toggle"
129 onclick="with (this.firstChild.style)
130 { display = (display == '') ? 'none' : '' }">
131 <pre>1
132 2</pre>
133 </td>
134 <td class="code">
135 <pre><span class="Ke">def </span><span class="NaFu">foo</span>(bar):
136 <span class="Ke">pass</span>
137 </pre>
138 </td>
139 </tr></table></div>
141 (whitespace added to improve clarity).
143 A list of lines can be specified using the `hl_lines` option to make these
144 lines highlighted (as of Pygments 0.11).
146 With the `full` option, a complete HTML 4 document is output, including
147 the style definitions inside a ``<style>`` tag, or in a separate file if
148 the `cssfile` option is given.
150 When `tagsfile` is set to the path of a ctags index file, it is used to
151 generate hyperlinks from names to their definition. You must enable
152 `lineanchors` and run ctags with the `-n` option for this to work. The
153 `python-ctags` module from PyPI must be installed to use this feature;
154 otherwise a `RuntimeError` will be raised.
156 The `get_style_defs(arg='')` method of a `HtmlFormatter` returns a string
157 containing CSS rules for the CSS classes used by the formatter. The
158 argument `arg` can be used to specify additional CSS selectors that
159 are prepended to the classes. A call `fmter.get_style_defs('td .code')`
160 would result in the following CSS classes:
162 .. sourcecode:: css
164 td .code .kw { font-weight: bold; color: #00FF00 }
165 td .code .cm { color: #999999 }
166 ...
168 If you have Pygments 0.6 or higher, you can also pass a list or tuple to the
169 `get_style_defs()` method to request multiple prefixes for the tokens:
171 .. sourcecode:: python
173 formatter.get_style_defs(['div.syntax pre', 'pre.syntax'])
175 The output would then look like this:
177 .. sourcecode:: css
179 div.syntax pre .kw,
180 pre.syntax .kw { font-weight: bold; color: #00FF00 }
181 div.syntax pre .cm,
182 pre.syntax .cm { color: #999999 }
183 ...
185 Additional options accepted:
187 `nowrap`
188 If set to ``True``, don't add a ``<pre>`` and a ``<div>`` tag
189 around the tokens. This disables most other options (default: ``False``).
191 `full`
192 Tells the formatter to output a "full" document, i.e. a complete
193 self-contained document (default: ``False``).
195 `title`
196 If `full` is true, the title that should be used to caption the
197 document (default: ``''``).
199 `style`
200 The style to use, can be a string or a Style subclass (default:
201 ``'default'``). This option has no effect if the `cssfile`
202 and `noclobber_cssfile` option are given and the file specified in
203 `cssfile` exists.
205 `noclasses`
206 If set to true, token ``<span>`` tags (as well as line number elements)
207 will not use CSS classes, but inline styles. This is not recommended
208 for larger pieces of code since it increases output size by quite a bit
209 (default: ``False``).
211 `classprefix`
212 Since the token types use relatively short class names, they may clash
213 with some of your own class names. In this case you can use the
214 `classprefix` option to give a string to prepend to all Pygments-generated
215 CSS class names for token types.
216 Note that this option also affects the output of `get_style_defs()`.
218 `cssclass`
219 CSS class for the wrapping ``<div>`` tag (default: ``'highlight'``).
220 If you set this option, the default selector for `get_style_defs()`
221 will be this class.
223 .. versionadded:: 0.9
224 If you select the ``'table'`` line numbers, the wrapping table will
225 have a CSS class of this string plus ``'table'``, the default is
226 accordingly ``'highlighttable'``.
228 `cssstyles`
229 Inline CSS styles for the wrapping ``<div>`` tag (default: ``''``).
231 `prestyles`
232 Inline CSS styles for the ``<pre>`` tag (default: ``''``).
234 .. versionadded:: 0.11
236 `cssfile`
237 If the `full` option is true and this option is given, it must be the
238 name of an external file. If the filename does not include an absolute
239 path, the file's path will be assumed to be relative to the main output
240 file's path, if the latter can be found. The stylesheet is then written
241 to this file instead of the HTML file.
243 .. versionadded:: 0.6
245 `noclobber_cssfile`
246 If `cssfile` is given and the specified file exists, the css file will
247 not be overwritten. This allows the use of the `full` option in
248 combination with a user specified css file. Default is ``False``.
250 .. versionadded:: 1.1
252 `linenos`
253 If set to ``'table'``, output line numbers as a table with two cells,
254 one containing the line numbers, the other the whole code. This is
255 copy-and-paste-friendly, but may cause alignment problems with some
256 browsers or fonts. If set to ``'inline'``, the line numbers will be
257 integrated in the ``<pre>`` tag that contains the code (that setting
258 is *new in Pygments 0.8*).
260 For compatibility with Pygments 0.7 and earlier, every true value
261 except ``'inline'`` means the same as ``'table'`` (in particular, that
262 means also ``True``).
264 The default value is ``False``, which means no line numbers at all.
266 **Note:** with the default ("table") line number mechanism, the line
267 numbers and code can have different line heights in Internet Explorer
268 unless you give the enclosing ``<pre>`` tags an explicit ``line-height``
269 CSS property (you get the default line spacing with ``line-height:
270 125%``).
272 `hl_lines`
273 Specify a list of lines to be highlighted. The line numbers are always
274 relative to the input (i.e. the first line is line 1) and are
275 independent of `linenostart`.
277 .. versionadded:: 0.11
279 `linenostart`
280 The line number for the first line (default: ``1``).
282 `linenostep`
283 If set to a number n > 1, only every nth line number is printed.
285 `linenospecial`
286 If set to a number n > 0, every nth line number is given the CSS
287 class ``"special"`` (default: ``0``).
289 `nobackground`
290 If set to ``True``, the formatter won't output the background color
291 for the wrapping element (this automatically defaults to ``False``
292 when there is no wrapping element [eg: no argument for the
293 `get_syntax_defs` method given]) (default: ``False``).
295 .. versionadded:: 0.6
297 `lineseparator`
298 This string is output between lines of code. It defaults to ``"\n"``,
299 which is enough to break a line inside ``<pre>`` tags, but you can
300 e.g. set it to ``"<br>"`` to get HTML line breaks.
302 .. versionadded:: 0.7
304 `lineanchors`
305 If set to a nonempty string, e.g. ``foo``, the formatter will wrap each
306 output line in an anchor tag with an ``id`` (and `name`) of ``foo-linenumber``.
307 This allows easy linking to certain lines.
309 .. versionadded:: 0.9
311 `linespans`
312 If set to a nonempty string, e.g. ``foo``, the formatter will wrap each
313 output line in a span tag with an ``id`` of ``foo-linenumber``.
314 This allows easy access to lines via javascript.
316 .. versionadded:: 1.6
318 `anchorlinenos`
319 If set to `True`, will wrap line numbers in <a> tags. Used in
320 combination with `linenos` and `lineanchors`.
322 `tagsfile`
323 If set to the path of a ctags file, wrap names in anchor tags that
324 link to their definitions. `lineanchors` should be used, and the
325 tags file should specify line numbers (see the `-n` option to ctags).
326 The tags file is assumed to be encoded in UTF-8.
328 .. versionadded:: 1.6
330 `tagurlformat`
331 A string formatting pattern used to generate links to ctags definitions.
332 Available variables are `%(path)s`, `%(fname)s` and `%(fext)s`.
333 Defaults to an empty string, resulting in just `#prefix-number` links.
335 .. versionadded:: 1.6
337 `filename`
338 A string used to generate a filename when rendering ``<pre>`` blocks,
339 for example if displaying source code. If `linenos` is set to
340 ``'table'`` then the filename will be rendered in an initial row
341 containing a single `<th>` which spans both columns.
343 .. versionadded:: 2.1
345 `wrapcode`
346 Wrap the code inside ``<pre>`` blocks using ``<code>``, as recommended
347 by the HTML5 specification.
349 .. versionadded:: 2.4
351 `debug_token_types`
352 Add ``title`` attributes to all token ``<span>`` tags that show the
353 name of the token.
355 .. versionadded:: 2.10
358 **Subclassing the HTML formatter**
360 .. versionadded:: 0.7
362 The HTML formatter is now built in a way that allows easy subclassing, thus
363 customizing the output HTML code. The `format()` method calls
364 `self._format_lines()` which returns a generator that yields tuples of ``(1,
365 line)``, where the ``1`` indicates that the ``line`` is a line of the
366 formatted source code.
368 If the `nowrap` option is set, the generator is the iterated over and the
369 resulting HTML is output.
371 Otherwise, `format()` calls `self.wrap()`, which wraps the generator with
372 other generators. These may add some HTML code to the one generated by
373 `_format_lines()`, either by modifying the lines generated by the latter,
374 then yielding them again with ``(1, line)``, and/or by yielding other HTML
375 code before or after the lines, with ``(0, html)``. The distinction between
376 source lines and other code makes it possible to wrap the generator multiple
377 times.
379 The default `wrap()` implementation adds a ``<div>`` and a ``<pre>`` tag.
381 A custom `HtmlFormatter` subclass could look like this:
383 .. sourcecode:: python
385 class CodeHtmlFormatter(HtmlFormatter):
387 def wrap(self, source, *, include_div):
388 return self._wrap_code(source)
390 def _wrap_code(self, source):
391 yield 0, '<code>'
392 for i, t in source:
393 if i == 1:
394 # it's a line of formatted code
395 t += '<br>'
396 yield i, t
397 yield 0, '</code>'
399 This results in wrapping the formatted lines with a ``<code>`` tag, where the
400 source lines are broken using ``<br>`` tags.
402 After calling `wrap()`, the `format()` method also adds the "line numbers"
403 and/or "full document" wrappers if the respective options are set. Then, all
404 HTML yielded by the wrapped generator is output.
405 """
407 name = 'HTML'
408 aliases = ['html']
409 filenames = ['*.html', '*.htm']
411 def __init__(self, **options):
412 Formatter.__init__(self, **options)
413 self.title = self._decodeifneeded(self.title)
414 self.nowrap = get_bool_opt(options, 'nowrap', False)
415 self.noclasses = get_bool_opt(options, 'noclasses', False)
416 self.classprefix = options.get('classprefix', '')
417 self.cssclass = self._decodeifneeded(options.get('cssclass', 'highlight'))
418 self.cssstyles = self._decodeifneeded(options.get('cssstyles', ''))
419 self.prestyles = self._decodeifneeded(options.get('prestyles', ''))
420 self.cssfile = self._decodeifneeded(options.get('cssfile', ''))
421 self.noclobber_cssfile = get_bool_opt(options, 'noclobber_cssfile', False)
422 self.tagsfile = self._decodeifneeded(options.get('tagsfile', ''))
423 self.tagurlformat = self._decodeifneeded(options.get('tagurlformat', ''))
424 self.filename = self._decodeifneeded(options.get('filename', ''))
425 self.wrapcode = get_bool_opt(options, 'wrapcode', False)
426 self.span_element_openers = {}
427 self.debug_token_types = get_bool_opt(options, 'debug_token_types', False)
429 if self.tagsfile:
430 if not ctags:
431 raise RuntimeError('The "ctags" package must to be installed '
432 'to be able to use the "tagsfile" feature.')
433 self._ctags = ctags.CTags(self.tagsfile)
435 linenos = options.get('linenos', False)
436 if linenos == 'inline':
437 self.linenos = 2
438 elif linenos:
439 # compatibility with <= 0.7
440 self.linenos = 1
441 else:
442 self.linenos = 0
443 self.linenostart = abs(get_int_opt(options, 'linenostart', 1))
444 self.linenostep = abs(get_int_opt(options, 'linenostep', 1))
445 self.linenospecial = abs(get_int_opt(options, 'linenospecial', 0))
446 self.nobackground = get_bool_opt(options, 'nobackground', False)
447 self.lineseparator = options.get('lineseparator', '\n')
448 self.lineanchors = options.get('lineanchors', '')
449 self.linespans = options.get('linespans', '')
450 self.anchorlinenos = get_bool_opt(options, 'anchorlinenos', False)
451 self.hl_lines = set()
452 for lineno in get_list_opt(options, 'hl_lines', []):
453 try:
454 self.hl_lines.add(int(lineno))
455 except ValueError:
456 pass
458 self._create_stylesheet()
460 def _get_css_class(self, ttype):
461 """Return the css class of this token type prefixed with
462 the classprefix option."""
463 ttypeclass = _get_ttype_class(ttype)
464 if ttypeclass:
465 return self.classprefix + ttypeclass
466 return ''
468 def _get_css_classes(self, ttype):
469 """Return the CSS classes of this token type prefixed with the classprefix option."""
470 cls = self._get_css_class(ttype)
471 while ttype not in STANDARD_TYPES:
472 ttype = ttype.parent
473 cls = self._get_css_class(ttype) + ' ' + cls
474 return cls or ''
476 def _get_css_inline_styles(self, ttype):
477 """Return the inline CSS styles for this token type."""
478 cclass = self.ttype2class.get(ttype)
479 while cclass is None:
480 ttype = ttype.parent
481 cclass = self.ttype2class.get(ttype)
482 return cclass or ''
484 def _create_stylesheet(self):
485 t2c = self.ttype2class = {Token: ''}
486 c2s = self.class2style = {}
487 for ttype, ndef in self.style:
488 name = self._get_css_class(ttype)
489 style = ''
490 if ndef['color']:
491 style += 'color: %s; ' % webify(ndef['color'])
492 if ndef['bold']:
493 style += 'font-weight: bold; '
494 if ndef['italic']:
495 style += 'font-style: italic; '
496 if ndef['underline']:
497 style += 'text-decoration: underline; '
498 if ndef['bgcolor']:
499 style += 'background-color: %s; ' % webify(ndef['bgcolor'])
500 if ndef['border']:
501 style += 'border: 1px solid %s; ' % webify(ndef['border'])
502 if style:
503 t2c[ttype] = name
504 # save len(ttype) to enable ordering the styles by
505 # hierarchy (necessary for CSS cascading rules!)
506 c2s[name] = (style[:-2], ttype, len(ttype))
508 def get_style_defs(self, arg=None):
509 """
510 Return CSS style definitions for the classes produced by the current
511 highlighting style. ``arg`` can be a string or list of selectors to
512 insert before the token type classes.
513 """
514 style_lines = []
516 style_lines.extend(self.get_linenos_style_defs())
517 style_lines.extend(self.get_background_style_defs(arg))
518 style_lines.extend(self.get_token_style_defs(arg))
520 return '\n'.join(style_lines)
522 def get_token_style_defs(self, arg=None):
523 prefix = self.get_css_prefix(arg)
525 styles = [
526 (level, ttype, cls, style)
527 for cls, (style, ttype, level) in self.class2style.items()
528 if cls and style
529 ]
530 styles.sort()
532 lines = [
533 '%s { %s } /* %s */' % (prefix(cls), style, repr(ttype)[6:])
534 for (level, ttype, cls, style) in styles
535 ]
537 return lines
539 def get_background_style_defs(self, arg=None):
540 prefix = self.get_css_prefix(arg)
541 bg_color = self.style.background_color
542 hl_color = self.style.highlight_color
544 lines = []
546 if arg and not self.nobackground and bg_color is not None:
547 text_style = ''
548 if Text in self.ttype2class:
549 text_style = ' ' + self.class2style[self.ttype2class[Text]][0]
550 lines.insert(
551 0, '%s{ background: %s;%s }' % (
552 prefix(''), bg_color, text_style
553 )
554 )
555 if hl_color is not None:
556 lines.insert(
557 0, '%s { background-color: %s }' % (prefix('hll'), hl_color)
558 )
560 return lines
562 def get_linenos_style_defs(self):
563 lines = [
564 'pre { %s }' % self._pre_style,
565 'td.linenos .normal { %s }' % self._linenos_style,
566 'span.linenos { %s }' % self._linenos_style,
567 'td.linenos .special { %s }' % self._linenos_special_style,
568 'span.linenos.special { %s }' % self._linenos_special_style,
569 ]
571 return lines
573 def get_css_prefix(self, arg):
574 if arg is None:
575 arg = ('cssclass' in self.options and '.'+self.cssclass or '')
576 if isinstance(arg, str):
577 args = [arg]
578 else:
579 args = list(arg)
581 def prefix(cls):
582 if cls:
583 cls = '.' + cls
584 tmp = []
585 for arg in args:
586 tmp.append((arg and arg + ' ' or '') + cls)
587 return ', '.join(tmp)
589 return prefix
591 @property
592 def _pre_style(self):
593 return 'line-height: 125%;'
595 @property
596 def _linenos_style(self):
597 return 'color: %s; background-color: %s; padding-left: 5px; padding-right: 5px;' % (
598 self.style.line_number_color,
599 self.style.line_number_background_color
600 )
602 @property
603 def _linenos_special_style(self):
604 return 'color: %s; background-color: %s; padding-left: 5px; padding-right: 5px;' % (
605 self.style.line_number_special_color,
606 self.style.line_number_special_background_color
607 )
609 def _decodeifneeded(self, value):
610 if isinstance(value, bytes):
611 if self.encoding:
612 return value.decode(self.encoding)
613 return value.decode()
614 return value
616 def _wrap_full(self, inner, outfile):
617 if self.cssfile:
618 if os.path.isabs(self.cssfile):
619 # it's an absolute filename
620 cssfilename = self.cssfile
621 else:
622 try:
623 filename = outfile.name
624 if not filename or filename[0] == '<':
625 # pseudo files, e.g. name == '<fdopen>'
626 raise AttributeError
627 cssfilename = os.path.join(os.path.dirname(filename),
628 self.cssfile)
629 except AttributeError:
630 print('Note: Cannot determine output file name, '
631 'using current directory as base for the CSS file name',
632 file=sys.stderr)
633 cssfilename = self.cssfile
634 # write CSS file only if noclobber_cssfile isn't given as an option.
635 try:
636 if not os.path.exists(cssfilename) or not self.noclobber_cssfile:
637 with open(cssfilename, "w", encoding="utf-8") as cf:
638 cf.write(CSSFILE_TEMPLATE %
639 {'styledefs': self.get_style_defs('body')})
640 except OSError as err:
641 err.strerror = 'Error writing CSS file: ' + err.strerror
642 raise
644 yield 0, (DOC_HEADER_EXTERNALCSS %
645 dict(title=self.title,
646 cssfile=self.cssfile,
647 encoding=self.encoding))
648 else:
649 yield 0, (DOC_HEADER %
650 dict(title=self.title,
651 styledefs=self.get_style_defs('body'),
652 encoding=self.encoding))
654 yield from inner
655 yield 0, DOC_FOOTER
657 def _wrap_tablelinenos(self, inner):
658 dummyoutfile = StringIO()
659 lncount = 0
660 for t, line in inner:
661 if t:
662 lncount += 1
663 dummyoutfile.write(line)
665 fl = self.linenostart
666 mw = len(str(lncount + fl - 1))
667 sp = self.linenospecial
668 st = self.linenostep
669 anchor_name = self.lineanchors or self.linespans
670 aln = self.anchorlinenos
671 nocls = self.noclasses
673 lines = []
675 for i in range(fl, fl+lncount):
676 print_line = i % st == 0
677 special_line = sp and i % sp == 0
679 if print_line:
680 line = '%*d' % (mw, i)
681 if aln:
682 line = '<a href="#%s-%d">%s</a>' % (anchor_name, i, line)
683 else:
684 line = ' ' * mw
686 if nocls:
687 if special_line:
688 style = ' style="%s"' % self._linenos_special_style
689 else:
690 style = ' style="%s"' % self._linenos_style
691 else:
692 if special_line:
693 style = ' class="special"'
694 else:
695 style = ' class="normal"'
697 if style:
698 line = '<span%s>%s</span>' % (style, line)
700 lines.append(line)
702 ls = '\n'.join(lines)
704 # If a filename was specified, we can't put it into the code table as it
705 # would misalign the line numbers. Hence we emit a separate row for it.
706 filename_tr = ""
707 if self.filename:
708 filename_tr = (
709 '<tr><th colspan="2" class="filename">'
710 '<span class="filename">' + self.filename + '</span>'
711 '</th></tr>')
713 # in case you wonder about the seemingly redundant <div> here: since the
714 # content in the other cell also is wrapped in a div, some browsers in
715 # some configurations seem to mess up the formatting...
716 yield 0, (f'<table class="{self.cssclass}table">' + filename_tr +
717 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
718 ls + '</pre></div></td><td class="code">')
719 yield 0, '<div>'
720 yield 0, dummyoutfile.getvalue()
721 yield 0, '</div>'
722 yield 0, '</td></tr></table>'
725 def _wrap_inlinelinenos(self, inner):
726 # need a list of lines since we need the width of a single number :(
727 inner_lines = list(inner)
728 sp = self.linenospecial
729 st = self.linenostep
730 num = self.linenostart
731 mw = len(str(len(inner_lines) + num - 1))
732 anchor_name = self.lineanchors or self.linespans
733 aln = self.anchorlinenos
734 nocls = self.noclasses
736 for _, inner_line in inner_lines:
737 print_line = num % st == 0
738 special_line = sp and num % sp == 0
740 if print_line:
741 line = '%*d' % (mw, num)
742 else:
743 line = ' ' * mw
745 if nocls:
746 if special_line:
747 style = ' style="%s"' % self._linenos_special_style
748 else:
749 style = ' style="%s"' % self._linenos_style
750 else:
751 if special_line:
752 style = ' class="linenos special"'
753 else:
754 style = ' class="linenos"'
756 if style:
757 linenos = '<span%s>%s</span>' % (style, line)
758 else:
759 linenos = line
761 if aln:
762 yield 1, ('<a href="#%s-%d">%s</a>' % (anchor_name, num, linenos) +
763 inner_line)
764 else:
765 yield 1, linenos + inner_line
766 num += 1
768 def _wrap_lineanchors(self, inner):
769 s = self.lineanchors
770 # subtract 1 since we have to increment i *before* yielding
771 i = self.linenostart - 1
772 for t, line in inner:
773 if t:
774 i += 1
775 href = "" if self.linenos else ' href="#%s-%d"' % (s, i)
776 yield 1, '<a id="%s-%d" name="%s-%d"%s></a>' % (s, i, s, i, href) + line
777 else:
778 yield 0, line
780 def _wrap_linespans(self, inner):
781 s = self.linespans
782 i = self.linenostart - 1
783 for t, line in inner:
784 if t:
785 i += 1
786 yield 1, '<span id="%s-%d">%s</span>' % (s, i, line)
787 else:
788 yield 0, line
790 def _wrap_div(self, inner):
791 style = []
792 if (self.noclasses and not self.nobackground and
793 self.style.background_color is not None):
794 style.append('background: %s' % (self.style.background_color,))
795 if self.cssstyles:
796 style.append(self.cssstyles)
797 style = '; '.join(style)
799 yield 0, ('<div' + (self.cssclass and ' class="%s"' % self.cssclass) +
800 (style and (' style="%s"' % style)) + '>')
801 yield from inner
802 yield 0, '</div>\n'
804 def _wrap_pre(self, inner):
805 style = []
806 if self.prestyles:
807 style.append(self.prestyles)
808 if self.noclasses:
809 style.append(self._pre_style)
810 style = '; '.join(style)
812 if self.filename and self.linenos != 1:
813 yield 0, ('<span class="filename">' + self.filename + '</span>')
815 # the empty span here is to keep leading empty lines from being
816 # ignored by HTML parsers
817 yield 0, ('<pre' + (style and ' style="%s"' % style) + '><span></span>')
818 yield from inner
819 yield 0, '</pre>'
821 def _wrap_code(self, inner):
822 yield 0, '<code>'
823 yield from inner
824 yield 0, '</code>'
826 @functools.lru_cache(maxsize=100)
827 def _translate_parts(self, value):
828 """HTML-escape a value and split it by newlines."""
829 return value.translate(_escape_html_table).split('\n')
831 def _format_lines(self, tokensource):
832 """
833 Just format the tokens, without any wrapping tags.
834 Yield individual lines.
835 """
836 nocls = self.noclasses
837 lsep = self.lineseparator
838 tagsfile = self.tagsfile
840 lspan = ''
841 line = []
842 for ttype, value in tokensource:
843 try:
844 cspan = self.span_element_openers[ttype]
845 except KeyError:
846 title = ' title="%s"' % '.'.join(ttype) if self.debug_token_types else ''
847 if nocls:
848 css_style = self._get_css_inline_styles(ttype)
849 if css_style:
850 css_style = self.class2style[css_style][0]
851 cspan = '<span style="%s"%s>' % (css_style, title)
852 else:
853 cspan = ''
854 else:
855 css_class = self._get_css_classes(ttype)
856 if css_class:
857 cspan = '<span class="%s"%s>' % (css_class, title)
858 else:
859 cspan = ''
860 self.span_element_openers[ttype] = cspan
862 parts = self._translate_parts(value)
864 if tagsfile and ttype in Token.Name:
865 filename, linenumber = self._lookup_ctag(value)
866 if linenumber:
867 base, filename = os.path.split(filename)
868 if base:
869 base += '/'
870 filename, extension = os.path.splitext(filename)
871 url = self.tagurlformat % {'path': base, 'fname': filename,
872 'fext': extension}
873 parts[0] = "<a href=\"%s#%s-%d\">%s" % \
874 (url, self.lineanchors, linenumber, parts[0])
875 parts[-1] = parts[-1] + "</a>"
877 # for all but the last line
878 for part in parts[:-1]:
879 if line:
880 # Also check for part being non-empty, so we avoid creating
881 # empty <span> tags
882 if lspan != cspan and part:
883 line.extend(((lspan and '</span>'), cspan, part,
884 (cspan and '</span>'), lsep))
885 else: # both are the same, or the current part was empty
886 line.extend((part, (lspan and '</span>'), lsep))
887 yield 1, ''.join(line)
888 line = []
889 elif part:
890 yield 1, ''.join((cspan, part, (cspan and '</span>'), lsep))
891 else:
892 yield 1, lsep
893 # for the last line
894 if line and parts[-1]:
895 if lspan != cspan:
896 line.extend(((lspan and '</span>'), cspan, parts[-1]))
897 lspan = cspan
898 else:
899 line.append(parts[-1])
900 elif parts[-1]:
901 line = [cspan, parts[-1]]
902 lspan = cspan
903 # else we neither have to open a new span nor set lspan
905 if line:
906 line.extend(((lspan and '</span>'), lsep))
907 yield 1, ''.join(line)
909 def _lookup_ctag(self, token):
910 entry = ctags.TagEntry()
911 if self._ctags.find(entry, token.encode(), 0):
912 return entry['file'].decode(), entry['lineNumber']
913 else:
914 return None, None
916 def _highlight_lines(self, tokensource):
917 """
918 Highlighted the lines specified in the `hl_lines` option by
919 post-processing the token stream coming from `_format_lines`.
920 """
921 hls = self.hl_lines
923 for i, (t, value) in enumerate(tokensource):
924 if t != 1:
925 yield t, value
926 if i + 1 in hls: # i + 1 because Python indexes start at 0
927 if self.noclasses:
928 style = ''
929 if self.style.highlight_color is not None:
930 style = (' style="background-color: %s"' %
931 (self.style.highlight_color,))
932 yield 1, '<span%s>%s</span>' % (style, value)
933 else:
934 yield 1, '<span class="hll">%s</span>' % value
935 else:
936 yield 1, value
938 def wrap(self, source):
939 """
940 Wrap the ``source``, which is a generator yielding
941 individual lines, in custom generators. See docstring
942 for `format`. Can be overridden.
943 """
945 output = source
946 if self.wrapcode:
947 output = self._wrap_code(output)
949 output = self._wrap_pre(output)
951 return output
953 def format_unencoded(self, tokensource, outfile):
954 """
955 The formatting process uses several nested generators; which of
956 them are used is determined by the user's options.
958 Each generator should take at least one argument, ``inner``,
959 and wrap the pieces of text generated by this.
961 Always yield 2-tuples: (code, text). If "code" is 1, the text
962 is part of the original tokensource being highlighted, if it's
963 0, the text is some piece of wrapping. This makes it possible to
964 use several different wrappers that process the original source
965 linewise, e.g. line number generators.
966 """
967 source = self._format_lines(tokensource)
969 # As a special case, we wrap line numbers before line highlighting
970 # so the line numbers get wrapped in the highlighting tag.
971 if not self.nowrap and self.linenos == 2:
972 source = self._wrap_inlinelinenos(source)
974 if self.hl_lines:
975 source = self._highlight_lines(source)
977 if not self.nowrap:
978 if self.lineanchors:
979 source = self._wrap_lineanchors(source)
980 if self.linespans:
981 source = self._wrap_linespans(source)
982 source = self.wrap(source)
983 if self.linenos == 1:
984 source = self._wrap_tablelinenos(source)
985 source = self._wrap_div(source)
986 if self.full:
987 source = self._wrap_full(source, outfile)
989 for t, piece in source:
990 outfile.write(piece)