Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pygments/formatters/html.py: 49%
412 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-01 06:54 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-01 06:54 +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).
327 .. versionadded:: 1.6
329 `tagurlformat`
330 A string formatting pattern used to generate links to ctags definitions.
331 Available variables are `%(path)s`, `%(fname)s` and `%(fext)s`.
332 Defaults to an empty string, resulting in just `#prefix-number` links.
334 .. versionadded:: 1.6
336 `filename`
337 A string used to generate a filename when rendering ``<pre>`` blocks,
338 for example if displaying source code. If `linenos` is set to
339 ``'table'`` then the filename will be rendered in an initial row
340 containing a single `<th>` which spans both columns.
342 .. versionadded:: 2.1
344 `wrapcode`
345 Wrap the code inside ``<pre>`` blocks using ``<code>``, as recommended
346 by the HTML5 specification.
348 .. versionadded:: 2.4
350 `debug_token_types`
351 Add ``title`` attributes to all token ``<span>`` tags that show the
352 name of the token.
354 .. versionadded:: 2.10
357 **Subclassing the HTML formatter**
359 .. versionadded:: 0.7
361 The HTML formatter is now built in a way that allows easy subclassing, thus
362 customizing the output HTML code. The `format()` method calls
363 `self._format_lines()` which returns a generator that yields tuples of ``(1,
364 line)``, where the ``1`` indicates that the ``line`` is a line of the
365 formatted source code.
367 If the `nowrap` option is set, the generator is the iterated over and the
368 resulting HTML is output.
370 Otherwise, `format()` calls `self.wrap()`, which wraps the generator with
371 other generators. These may add some HTML code to the one generated by
372 `_format_lines()`, either by modifying the lines generated by the latter,
373 then yielding them again with ``(1, line)``, and/or by yielding other HTML
374 code before or after the lines, with ``(0, html)``. The distinction between
375 source lines and other code makes it possible to wrap the generator multiple
376 times.
378 The default `wrap()` implementation adds a ``<div>`` and a ``<pre>`` tag.
380 A custom `HtmlFormatter` subclass could look like this:
382 .. sourcecode:: python
384 class CodeHtmlFormatter(HtmlFormatter):
386 def wrap(self, source, *, include_div):
387 return self._wrap_code(source)
389 def _wrap_code(self, source):
390 yield 0, '<code>'
391 for i, t in source:
392 if i == 1:
393 # it's a line of formatted code
394 t += '<br>'
395 yield i, t
396 yield 0, '</code>'
398 This results in wrapping the formatted lines with a ``<code>`` tag, where the
399 source lines are broken using ``<br>`` tags.
401 After calling `wrap()`, the `format()` method also adds the "line numbers"
402 and/or "full document" wrappers if the respective options are set. Then, all
403 HTML yielded by the wrapped generator is output.
404 """
406 name = 'HTML'
407 aliases = ['html']
408 filenames = ['*.html', '*.htm']
410 def __init__(self, **options):
411 Formatter.__init__(self, **options)
412 self.title = self._decodeifneeded(self.title)
413 self.nowrap = get_bool_opt(options, 'nowrap', False)
414 self.noclasses = get_bool_opt(options, 'noclasses', False)
415 self.classprefix = options.get('classprefix', '')
416 self.cssclass = self._decodeifneeded(options.get('cssclass', 'highlight'))
417 self.cssstyles = self._decodeifneeded(options.get('cssstyles', ''))
418 self.prestyles = self._decodeifneeded(options.get('prestyles', ''))
419 self.cssfile = self._decodeifneeded(options.get('cssfile', ''))
420 self.noclobber_cssfile = get_bool_opt(options, 'noclobber_cssfile', False)
421 self.tagsfile = self._decodeifneeded(options.get('tagsfile', ''))
422 self.tagurlformat = self._decodeifneeded(options.get('tagurlformat', ''))
423 self.filename = self._decodeifneeded(options.get('filename', ''))
424 self.wrapcode = get_bool_opt(options, 'wrapcode', False)
425 self.span_element_openers = {}
426 self.debug_token_types = get_bool_opt(options, 'debug_token_types', False)
428 if self.tagsfile:
429 if not ctags:
430 raise RuntimeError('The "ctags" package must to be installed '
431 'to be able to use the "tagsfile" feature.')
432 self._ctags = ctags.CTags(self.tagsfile)
434 linenos = options.get('linenos', False)
435 if linenos == 'inline':
436 self.linenos = 2
437 elif linenos:
438 # compatibility with <= 0.7
439 self.linenos = 1
440 else:
441 self.linenos = 0
442 self.linenostart = abs(get_int_opt(options, 'linenostart', 1))
443 self.linenostep = abs(get_int_opt(options, 'linenostep', 1))
444 self.linenospecial = abs(get_int_opt(options, 'linenospecial', 0))
445 self.nobackground = get_bool_opt(options, 'nobackground', False)
446 self.lineseparator = options.get('lineseparator', '\n')
447 self.lineanchors = options.get('lineanchors', '')
448 self.linespans = options.get('linespans', '')
449 self.anchorlinenos = get_bool_opt(options, 'anchorlinenos', False)
450 self.hl_lines = set()
451 for lineno in get_list_opt(options, 'hl_lines', []):
452 try:
453 self.hl_lines.add(int(lineno))
454 except ValueError:
455 pass
457 self._create_stylesheet()
459 def _get_css_class(self, ttype):
460 """Return the css class of this token type prefixed with
461 the classprefix option."""
462 ttypeclass = _get_ttype_class(ttype)
463 if ttypeclass:
464 return self.classprefix + ttypeclass
465 return ''
467 def _get_css_classes(self, ttype):
468 """Return the CSS classes of this token type prefixed with the classprefix option."""
469 cls = self._get_css_class(ttype)
470 while ttype not in STANDARD_TYPES:
471 ttype = ttype.parent
472 cls = self._get_css_class(ttype) + ' ' + cls
473 return cls or ''
475 def _get_css_inline_styles(self, ttype):
476 """Return the inline CSS styles for this token type."""
477 cclass = self.ttype2class.get(ttype)
478 while cclass is None:
479 ttype = ttype.parent
480 cclass = self.ttype2class.get(ttype)
481 return cclass or ''
483 def _create_stylesheet(self):
484 t2c = self.ttype2class = {Token: ''}
485 c2s = self.class2style = {}
486 for ttype, ndef in self.style:
487 name = self._get_css_class(ttype)
488 style = ''
489 if ndef['color']:
490 style += 'color: %s; ' % webify(ndef['color'])
491 if ndef['bold']:
492 style += 'font-weight: bold; '
493 if ndef['italic']:
494 style += 'font-style: italic; '
495 if ndef['underline']:
496 style += 'text-decoration: underline; '
497 if ndef['bgcolor']:
498 style += 'background-color: %s; ' % webify(ndef['bgcolor'])
499 if ndef['border']:
500 style += 'border: 1px solid %s; ' % webify(ndef['border'])
501 if style:
502 t2c[ttype] = name
503 # save len(ttype) to enable ordering the styles by
504 # hierarchy (necessary for CSS cascading rules!)
505 c2s[name] = (style[:-2], ttype, len(ttype))
507 def get_style_defs(self, arg=None):
508 """
509 Return CSS style definitions for the classes produced by the current
510 highlighting style. ``arg`` can be a string or list of selectors to
511 insert before the token type classes.
512 """
513 style_lines = []
515 style_lines.extend(self.get_linenos_style_defs())
516 style_lines.extend(self.get_background_style_defs(arg))
517 style_lines.extend(self.get_token_style_defs(arg))
519 return '\n'.join(style_lines)
521 def get_token_style_defs(self, arg=None):
522 prefix = self.get_css_prefix(arg)
524 styles = [
525 (level, ttype, cls, style)
526 for cls, (style, ttype, level) in self.class2style.items()
527 if cls and style
528 ]
529 styles.sort()
531 lines = [
532 '%s { %s } /* %s */' % (prefix(cls), style, repr(ttype)[6:])
533 for (level, ttype, cls, style) in styles
534 ]
536 return lines
538 def get_background_style_defs(self, arg=None):
539 prefix = self.get_css_prefix(arg)
540 bg_color = self.style.background_color
541 hl_color = self.style.highlight_color
543 lines = []
545 if arg and not self.nobackground and bg_color is not None:
546 text_style = ''
547 if Text in self.ttype2class:
548 text_style = ' ' + self.class2style[self.ttype2class[Text]][0]
549 lines.insert(
550 0, '%s{ background: %s;%s }' % (
551 prefix(''), bg_color, text_style
552 )
553 )
554 if hl_color is not None:
555 lines.insert(
556 0, '%s { background-color: %s }' % (prefix('hll'), hl_color)
557 )
559 return lines
561 def get_linenos_style_defs(self):
562 lines = [
563 'pre { %s }' % self._pre_style,
564 'td.linenos .normal { %s }' % self._linenos_style,
565 'span.linenos { %s }' % self._linenos_style,
566 'td.linenos .special { %s }' % self._linenos_special_style,
567 'span.linenos.special { %s }' % self._linenos_special_style,
568 ]
570 return lines
572 def get_css_prefix(self, arg):
573 if arg is None:
574 arg = ('cssclass' in self.options and '.'+self.cssclass or '')
575 if isinstance(arg, str):
576 args = [arg]
577 else:
578 args = list(arg)
580 def prefix(cls):
581 if cls:
582 cls = '.' + cls
583 tmp = []
584 for arg in args:
585 tmp.append((arg and arg + ' ' or '') + cls)
586 return ', '.join(tmp)
588 return prefix
590 @property
591 def _pre_style(self):
592 return 'line-height: 125%;'
594 @property
595 def _linenos_style(self):
596 return 'color: %s; background-color: %s; padding-left: 5px; padding-right: 5px;' % (
597 self.style.line_number_color,
598 self.style.line_number_background_color
599 )
601 @property
602 def _linenos_special_style(self):
603 return 'color: %s; background-color: %s; padding-left: 5px; padding-right: 5px;' % (
604 self.style.line_number_special_color,
605 self.style.line_number_special_background_color
606 )
608 def _decodeifneeded(self, value):
609 if isinstance(value, bytes):
610 if self.encoding:
611 return value.decode(self.encoding)
612 return value.decode()
613 return value
615 def _wrap_full(self, inner, outfile):
616 if self.cssfile:
617 if os.path.isabs(self.cssfile):
618 # it's an absolute filename
619 cssfilename = self.cssfile
620 else:
621 try:
622 filename = outfile.name
623 if not filename or filename[0] == '<':
624 # pseudo files, e.g. name == '<fdopen>'
625 raise AttributeError
626 cssfilename = os.path.join(os.path.dirname(filename),
627 self.cssfile)
628 except AttributeError:
629 print('Note: Cannot determine output file name, '
630 'using current directory as base for the CSS file name',
631 file=sys.stderr)
632 cssfilename = self.cssfile
633 # write CSS file only if noclobber_cssfile isn't given as an option.
634 try:
635 if not os.path.exists(cssfilename) or not self.noclobber_cssfile:
636 with open(cssfilename, "w", encoding="utf-8") as cf:
637 cf.write(CSSFILE_TEMPLATE %
638 {'styledefs': self.get_style_defs('body')})
639 except OSError as err:
640 err.strerror = 'Error writing CSS file: ' + err.strerror
641 raise
643 yield 0, (DOC_HEADER_EXTERNALCSS %
644 dict(title=self.title,
645 cssfile=self.cssfile,
646 encoding=self.encoding))
647 else:
648 yield 0, (DOC_HEADER %
649 dict(title=self.title,
650 styledefs=self.get_style_defs('body'),
651 encoding=self.encoding))
653 yield from inner
654 yield 0, DOC_FOOTER
656 def _wrap_tablelinenos(self, inner):
657 dummyoutfile = StringIO()
658 lncount = 0
659 for t, line in inner:
660 if t:
661 lncount += 1
662 dummyoutfile.write(line)
664 fl = self.linenostart
665 mw = len(str(lncount + fl - 1))
666 sp = self.linenospecial
667 st = self.linenostep
668 anchor_name = self.lineanchors or self.linespans
669 aln = self.anchorlinenos
670 nocls = self.noclasses
672 lines = []
674 for i in range(fl, fl+lncount):
675 print_line = i % st == 0
676 special_line = sp and i % sp == 0
678 if print_line:
679 line = '%*d' % (mw, i)
680 if aln:
681 line = '<a href="#%s-%d">%s</a>' % (anchor_name, i, line)
682 else:
683 line = ' ' * mw
685 if nocls:
686 if special_line:
687 style = ' style="%s"' % self._linenos_special_style
688 else:
689 style = ' style="%s"' % self._linenos_style
690 else:
691 if special_line:
692 style = ' class="special"'
693 else:
694 style = ' class="normal"'
696 if style:
697 line = '<span%s>%s</span>' % (style, line)
699 lines.append(line)
701 ls = '\n'.join(lines)
703 # If a filename was specified, we can't put it into the code table as it
704 # would misalign the line numbers. Hence we emit a separate row for it.
705 filename_tr = ""
706 if self.filename:
707 filename_tr = (
708 '<tr><th colspan="2" class="filename">'
709 '<span class="filename">' + self.filename + '</span>'
710 '</th></tr>')
712 # in case you wonder about the seemingly redundant <div> here: since the
713 # content in the other cell also is wrapped in a div, some browsers in
714 # some configurations seem to mess up the formatting...
715 yield 0, (f'<table class="{self.cssclass}table">' + filename_tr +
716 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
717 ls + '</pre></div></td><td class="code">')
718 yield 0, '<div>'
719 yield 0, dummyoutfile.getvalue()
720 yield 0, '</div>'
721 yield 0, '</td></tr></table>'
724 def _wrap_inlinelinenos(self, inner):
725 # need a list of lines since we need the width of a single number :(
726 inner_lines = list(inner)
727 sp = self.linenospecial
728 st = self.linenostep
729 num = self.linenostart
730 mw = len(str(len(inner_lines) + num - 1))
731 anchor_name = self.lineanchors or self.linespans
732 aln = self.anchorlinenos
733 nocls = self.noclasses
735 for _, inner_line in inner_lines:
736 print_line = num % st == 0
737 special_line = sp and num % sp == 0
739 if print_line:
740 line = '%*d' % (mw, num)
741 else:
742 line = ' ' * mw
744 if nocls:
745 if special_line:
746 style = ' style="%s"' % self._linenos_special_style
747 else:
748 style = ' style="%s"' % self._linenos_style
749 else:
750 if special_line:
751 style = ' class="linenos special"'
752 else:
753 style = ' class="linenos"'
755 if style:
756 linenos = '<span%s>%s</span>' % (style, line)
757 else:
758 linenos = line
760 if aln:
761 yield 1, ('<a href="#%s-%d">%s</a>' % (anchor_name, num, linenos) +
762 inner_line)
763 else:
764 yield 1, linenos + inner_line
765 num += 1
767 def _wrap_lineanchors(self, inner):
768 s = self.lineanchors
769 # subtract 1 since we have to increment i *before* yielding
770 i = self.linenostart - 1
771 for t, line in inner:
772 if t:
773 i += 1
774 href = "" if self.linenos else ' href="#%s-%d"' % (s, i)
775 yield 1, '<a id="%s-%d" name="%s-%d"%s></a>' % (s, i, s, i, href) + line
776 else:
777 yield 0, line
779 def _wrap_linespans(self, inner):
780 s = self.linespans
781 i = self.linenostart - 1
782 for t, line in inner:
783 if t:
784 i += 1
785 yield 1, '<span id="%s-%d">%s</span>' % (s, i, line)
786 else:
787 yield 0, line
789 def _wrap_div(self, inner):
790 style = []
791 if (self.noclasses and not self.nobackground and
792 self.style.background_color is not None):
793 style.append('background: %s' % (self.style.background_color,))
794 if self.cssstyles:
795 style.append(self.cssstyles)
796 style = '; '.join(style)
798 yield 0, ('<div' + (self.cssclass and ' class="%s"' % self.cssclass) +
799 (style and (' style="%s"' % style)) + '>')
800 yield from inner
801 yield 0, '</div>\n'
803 def _wrap_pre(self, inner):
804 style = []
805 if self.prestyles:
806 style.append(self.prestyles)
807 if self.noclasses:
808 style.append(self._pre_style)
809 style = '; '.join(style)
811 if self.filename and self.linenos != 1:
812 yield 0, ('<span class="filename">' + self.filename + '</span>')
814 # the empty span here is to keep leading empty lines from being
815 # ignored by HTML parsers
816 yield 0, ('<pre' + (style and ' style="%s"' % style) + '><span></span>')
817 yield from inner
818 yield 0, '</pre>'
820 def _wrap_code(self, inner):
821 yield 0, '<code>'
822 yield from inner
823 yield 0, '</code>'
825 @functools.lru_cache(maxsize=100)
826 def _translate_parts(self, value):
827 """HTML-escape a value and split it by newlines."""
828 return value.translate(_escape_html_table).split('\n')
830 def _format_lines(self, tokensource):
831 """
832 Just format the tokens, without any wrapping tags.
833 Yield individual lines.
834 """
835 nocls = self.noclasses
836 lsep = self.lineseparator
837 tagsfile = self.tagsfile
839 lspan = ''
840 line = []
841 for ttype, value in tokensource:
842 try:
843 cspan = self.span_element_openers[ttype]
844 except KeyError:
845 title = ' title="%s"' % '.'.join(ttype) if self.debug_token_types else ''
846 if nocls:
847 css_style = self._get_css_inline_styles(ttype)
848 if css_style:
849 css_style = self.class2style[css_style][0]
850 cspan = '<span style="%s"%s>' % (css_style, title)
851 else:
852 cspan = ''
853 else:
854 css_class = self._get_css_classes(ttype)
855 if css_class:
856 cspan = '<span class="%s"%s>' % (css_class, title)
857 else:
858 cspan = ''
859 self.span_element_openers[ttype] = cspan
861 parts = self._translate_parts(value)
863 if tagsfile and ttype in Token.Name:
864 filename, linenumber = self._lookup_ctag(value)
865 if linenumber:
866 base, filename = os.path.split(filename)
867 if base:
868 base += '/'
869 filename, extension = os.path.splitext(filename)
870 url = self.tagurlformat % {'path': base, 'fname': filename,
871 'fext': extension}
872 parts[0] = "<a href=\"%s#%s-%d\">%s" % \
873 (url, self.lineanchors, linenumber, parts[0])
874 parts[-1] = parts[-1] + "</a>"
876 # for all but the last line
877 for part in parts[:-1]:
878 if line:
879 # Also check for part being non-empty, so we avoid creating
880 # empty <span> tags
881 if lspan != cspan and part:
882 line.extend(((lspan and '</span>'), cspan, part,
883 (cspan and '</span>'), lsep))
884 else: # both are the same, or the current part was empty
885 line.extend((part, (lspan and '</span>'), lsep))
886 yield 1, ''.join(line)
887 line = []
888 elif part:
889 yield 1, ''.join((cspan, part, (cspan and '</span>'), lsep))
890 else:
891 yield 1, lsep
892 # for the last line
893 if line and parts[-1]:
894 if lspan != cspan:
895 line.extend(((lspan and '</span>'), cspan, parts[-1]))
896 lspan = cspan
897 else:
898 line.append(parts[-1])
899 elif parts[-1]:
900 line = [cspan, parts[-1]]
901 lspan = cspan
902 # else we neither have to open a new span nor set lspan
904 if line:
905 line.extend(((lspan and '</span>'), lsep))
906 yield 1, ''.join(line)
908 def _lookup_ctag(self, token):
909 entry = ctags.TagEntry()
910 if self._ctags.find(entry, token.encode(), 0):
911 return entry['file'], entry['lineNumber']
912 else:
913 return None, None
915 def _highlight_lines(self, tokensource):
916 """
917 Highlighted the lines specified in the `hl_lines` option by
918 post-processing the token stream coming from `_format_lines`.
919 """
920 hls = self.hl_lines
922 for i, (t, value) in enumerate(tokensource):
923 if t != 1:
924 yield t, value
925 if i + 1 in hls: # i + 1 because Python indexes start at 0
926 if self.noclasses:
927 style = ''
928 if self.style.highlight_color is not None:
929 style = (' style="background-color: %s"' %
930 (self.style.highlight_color,))
931 yield 1, '<span%s>%s</span>' % (style, value)
932 else:
933 yield 1, '<span class="hll">%s</span>' % value
934 else:
935 yield 1, value
937 def wrap(self, source):
938 """
939 Wrap the ``source``, which is a generator yielding
940 individual lines, in custom generators. See docstring
941 for `format`. Can be overridden.
942 """
944 output = source
945 if self.wrapcode:
946 output = self._wrap_code(output)
948 output = self._wrap_pre(output)
950 return output
952 def format_unencoded(self, tokensource, outfile):
953 """
954 The formatting process uses several nested generators; which of
955 them are used is determined by the user's options.
957 Each generator should take at least one argument, ``inner``,
958 and wrap the pieces of text generated by this.
960 Always yield 2-tuples: (code, text). If "code" is 1, the text
961 is part of the original tokensource being highlighted, if it's
962 0, the text is some piece of wrapping. This makes it possible to
963 use several different wrappers that process the original source
964 linewise, e.g. line number generators.
965 """
966 source = self._format_lines(tokensource)
968 # As a special case, we wrap line numbers before line highlighting
969 # so the line numbers get wrapped in the highlighting tag.
970 if not self.nowrap and self.linenos == 2:
971 source = self._wrap_inlinelinenos(source)
973 if self.hl_lines:
974 source = self._highlight_lines(source)
976 if not self.nowrap:
977 if self.lineanchors:
978 source = self._wrap_lineanchors(source)
979 if self.linespans:
980 source = self._wrap_linespans(source)
981 source = self.wrap(source)
982 if self.linenos == 1:
983 source = self._wrap_tablelinenos(source)
984 source = self._wrap_div(source)
985 if self.full:
986 source = self._wrap_full(source, outfile)
988 for t, piece in source:
989 outfile.write(piece)