codekingpro/portable-devtools
115k
1"""2 pygments.formatters.html3 ~~~~~~~~~~~~~~~~~~~~~~~~4 5 Formatter for HTML output.6 7 :copyright: Copyright 2006-present by the Pygments team, see AUTHORS.8 :license: BSD, see LICENSE for details.9"""10 11import functools12import os13import sys14import os.path15from io import StringIO16 17from pygments.formatter import Formatter18from pygments.token import Token, Text, STANDARD_TYPES19from pygments.util import get_bool_opt, get_int_opt, get_list_opt20 21import html22 23try:24 import ctags25except ImportError:26 ctags = None27 28__all__ = ['HtmlFormatter']29 30 31_escape_html_table = {32 ord('&'): '&',33 ord('<'): '<',34 ord('>'): '>',35 ord('"'): '"',36 ord("'"): ''',37}38 39 40def escape_html(text, table=_escape_html_table):41 """Escape &, <, > as well as single and double quotes for HTML."""42 return text.translate(table)43 44 45def webify(color):46 if color.startswith('calc') or color.startswith('var'):47 return color48 else:49 # Check if the color can be shortened from 6 to 3 characters50 color = color.upper()51 if (len(color) == 6 and52 ( color[0] == color[1]53 and color[2] == color[3]54 and color[4] == color[5])):55 return f'#{color[0]}{color[2]}{color[4]}'56 else:57 return f'#{color}'58 59 60def _get_ttype_class(ttype):61 fname = STANDARD_TYPES.get(ttype)62 if fname:63 return fname64 aname = ''65 while fname is None:66 aname = '-' + ttype[-1] + aname67 ttype = ttype.parent68 fname = STANDARD_TYPES.get(ttype)69 return fname + aname70 71 72CSSFILE_TEMPLATE = '''\73/*74generated by Pygments <https://pygments.org/>75Copyright 2006-present by the Pygments team.76Licensed under the BSD license, see LICENSE for details.77*/78%(styledefs)s79'''80 81DOC_HEADER = '''\82<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"83 "http://www.w3.org/TR/html4/strict.dtd">84<!--85generated by Pygments <https://pygments.org/>86Copyright 2006-present by the Pygments team.87Licensed under the BSD license, see LICENSE for details.88-->89<html>90<head>91 <title>%(title)s</title>92 <meta http-equiv="content-type" content="text/html; charset=%(encoding)s">93 <style type="text/css">94''' + CSSFILE_TEMPLATE + '''95 </style>96</head>97<body>98<h2>%(title)s</h2>99 100'''101 102DOC_HEADER_EXTERNALCSS = '''\103<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"104 "http://www.w3.org/TR/html4/strict.dtd">105 106<html>107<head>108 <title>%(title)s</title>109 <meta http-equiv="content-type" content="text/html; charset=%(encoding)s">110 <link rel="stylesheet" href="%(cssfile)s" type="text/css">111</head>112<body>113<h2>%(title)s</h2>114 115'''116 117DOC_FOOTER = '''\118</body>119</html>120'''121 122 123class HtmlFormatter(Formatter):124 r"""125 Format tokens as HTML 4 ``<span>`` tags. By default, the content is enclosed126 in a ``<pre>`` tag, itself wrapped in a ``<div>`` tag (but see the `nowrap` option).127 The ``<div>``'s CSS class can be set by the `cssclass` option.128 129 If the `linenos` option is set to ``"table"``, the ``<pre>`` is130 additionally wrapped inside a ``<table>`` which has one row and two131 cells: one containing the line numbers and one containing the code.132 Example:133 134 .. sourcecode:: html135 136 <div class="highlight" >137 <table><tr>138 <td class="linenos" title="click to toggle"139 onclick="with (this.firstChild.style)140 { display = (display == '') ? 'none' : '' }">141 <pre>1142 2</pre>143 </td>144 <td class="code">145 <pre><span class="Ke">def </span><span class="NaFu">foo</span>(bar):146 <span class="Ke">pass</span>147 </pre>148 </td>149 </tr></table></div>150 151 (whitespace added to improve clarity).152 153 A list of lines can be specified using the `hl_lines` option to make these154 lines highlighted (as of Pygments 0.11).155 156 With the `full` option, a complete HTML 4 document is output, including157 the style definitions inside a ``<style>`` tag, or in a separate file if158 the `cssfile` option is given.159 160 When `tagsfile` is set to the path of a ctags index file, it is used to161 generate hyperlinks from names to their definition. You must enable162 `lineanchors` and run ctags with the `-n` option for this to work. The163 `python-ctags` module from PyPI must be installed to use this feature;164 otherwise a `RuntimeError` will be raised.165 166 The `get_style_defs(arg='')` method of a `HtmlFormatter` returns a string167 containing CSS rules for the CSS classes used by the formatter. The168 argument `arg` can be used to specify additional CSS selectors that169 are prepended to the classes. A call `fmter.get_style_defs('td .code')`170 would result in the following CSS classes:171 172 .. sourcecode:: css173 174 td .code .kw { font-weight: bold; color: #00FF00 }175 td .code .cm { color: #999999 }176 ...177 178 If you have Pygments 0.6 or higher, you can also pass a list or tuple to the179 `get_style_defs()` method to request multiple prefixes for the tokens:180 181 .. sourcecode:: python182 183 formatter.get_style_defs(['div.syntax pre', 'pre.syntax'])184 185 The output would then look like this:186 187 .. sourcecode:: css188 189 div.syntax pre .kw,190 pre.syntax .kw { font-weight: bold; color: #00FF00 }191 div.syntax pre .cm,192 pre.syntax .cm { color: #999999 }193 ...194 195 Additional options accepted:196 197 `nowrap`198 If set to ``True``, don't add a ``<pre>`` and a ``<div>`` tag199 around the tokens. This disables most other options (default: ``False``).200 201 `full`202 Tells the formatter to output a "full" document, i.e. a complete203 self-contained document (default: ``False``).204 205 `title`206 If `full` is true, the title that should be used to caption the207 document (default: ``''``).208 209 `style`210 The style to use, can be a string or a Style subclass (default:211 ``'default'``). This option has no effect if the `cssfile`212 and `noclobber_cssfile` option are given and the file specified in213 `cssfile` exists.214 215 `noclasses`216 If set to true, token ``<span>`` tags (as well as line number elements)217 will not use CSS classes, but inline styles. This is not recommended218 for larger pieces of code since it increases output size by quite a bit219 (default: ``False``).220 221 `classprefix`222 Since the token types use relatively short class names, they may clash223 with some of your own class names. In this case you can use the224 `classprefix` option to give a string to prepend to all Pygments-generated225 CSS class names for token types.226 Note that this option also affects the output of `get_style_defs()`.227 228 `cssclass`229 CSS class for the wrapping ``<div>`` tag (default: ``'highlight'``).230 If you set this option, the default selector for `get_style_defs()`231 will be this class.232 233 .. versionadded:: 0.9234 If you select the ``'table'`` line numbers, the wrapping table will235 have a CSS class of this string plus ``'table'``, the default is236 accordingly ``'highlighttable'``.237 238 `cssstyles`239 Inline CSS styles for the wrapping ``<div>`` tag (default: ``''``).240 241 `prestyles`242 Inline CSS styles for the ``<pre>`` tag (default: ``''``).243 244 .. versionadded:: 0.11245 246 `cssfile`247 If the `full` option is true and this option is given, it must be the248 name of an external file. If the filename does not include an absolute249 path, the file's path will be assumed to be relative to the main output250 file's path, if the latter can be found. The stylesheet is then written251 to this file instead of the HTML file.252 253 .. versionadded:: 0.6254 255 `noclobber_cssfile`256 If `cssfile` is given and the specified file exists, the css file will257 not be overwritten. This allows the use of the `full` option in258 combination with a user specified css file. Default is ``False``.259 260 .. versionadded:: 1.1261 262 `linenos`263 If set to ``'table'``, output line numbers as a table with two cells,264 one containing the line numbers, the other the whole code. This is265 copy-and-paste-friendly, but may cause alignment problems with some266 browsers or fonts. If set to ``'inline'``, the line numbers will be267 integrated in the ``<pre>`` tag that contains the code (that setting268 is *new in Pygments 0.8*).269 270 For compatibility with Pygments 0.7 and earlier, every true value271 except ``'inline'`` means the same as ``'table'`` (in particular, that272 means also ``True``).273 274 The default value is ``False``, which means no line numbers at all.275 276 **Note:** with the default ("table") line number mechanism, the line277 numbers and code can have different line heights in Internet Explorer278 unless you give the enclosing ``<pre>`` tags an explicit ``line-height``279 CSS property (you get the default line spacing with ``line-height:280 125%``).281 282 `hl_lines`283 Specify a list of lines to be highlighted. The line numbers are always284 relative to the input (i.e. the first line is line 1) and are285 independent of `linenostart`.286 287 .. versionadded:: 0.11288 289 `linenostart`290 The line number for the first line (default: ``1``).291 292 `linenostep`293 If set to a number n > 1, only every nth line number is printed.294 295 `linenospecial`296 If set to a number n > 0, every nth line number is given the CSS297 class ``"special"`` (default: ``0``).298 299 `nobackground`300 If set to ``True``, the formatter won't output the background color301 for the wrapping element (this automatically defaults to ``False``302 when there is no wrapping element [eg: no argument for the303 `get_syntax_defs` method given]) (default: ``False``).304 305 .. versionadded:: 0.6306 307 `lineseparator`308 This string is output between lines of code. It defaults to ``"\n"``,309 which is enough to break a line inside ``<pre>`` tags, but you can310 e.g. set it to ``"<br>"`` to get HTML line breaks.311 312 .. versionadded:: 0.7313 314 `lineanchors`315 If set to a nonempty string, e.g. ``foo``, the formatter will wrap each316 output line in an anchor tag with an ``id`` (and `name`) of ``foo-linenumber``.317 This allows easy linking to certain lines.318 319 .. versionadded:: 0.9320 321 `linespans`322 If set to a nonempty string, e.g. ``foo``, the formatter will wrap each323 output line in a span tag with an ``id`` of ``foo-linenumber``.324 This allows easy access to lines via javascript.325 326 .. versionadded:: 1.6327 328 `anchorlinenos`329 If set to `True`, will wrap line numbers in <a> tags. Used in330 combination with `linenos` and `lineanchors`.331 332 `tagsfile`333 If set to the path of a ctags file, wrap names in anchor tags that334 link to their definitions. `lineanchors` should be used, and the335 tags file should specify line numbers (see the `-n` option to ctags).336 The tags file is assumed to be encoded in UTF-8.337 338 .. versionadded:: 1.6339 340 `tagurlformat`341 A string formatting pattern used to generate links to ctags definitions.342 Available variables are `%(path)s`, `%(fname)s` and `%(fext)s`.343 Defaults to an empty string, resulting in just `#prefix-number` links.344 345 .. versionadded:: 1.6346 347 `filename`348 A string used to generate a filename when rendering ``<pre>`` blocks,349 for example if displaying source code. If `linenos` is set to350 ``'table'`` then the filename will be rendered in an initial row351 containing a single `<th>` which spans both columns.352 353 .. versionadded:: 2.1354 355 `wrapcode`356 Wrap the code inside ``<pre>`` blocks using ``<code>``, as recommended357 by the HTML5 specification.358 359 .. versionadded:: 2.4360 361 `debug_token_types`362 Add ``title`` attributes to all token ``<span>`` tags that show the363 name of the token.364 365 .. versionadded:: 2.10366 367 368 **Subclassing the HTML formatter**369 370 .. versionadded:: 0.7371 372 The HTML formatter is now built in a way that allows easy subclassing, thus373 customizing the output HTML code. The `format()` method calls374 `self._format_lines()` which returns a generator that yields tuples of ``(1,375 line)``, where the ``1`` indicates that the ``line`` is a line of the376 formatted source code.377 378 If the `nowrap` option is set, the generator is the iterated over and the379 resulting HTML is output.380 381 Otherwise, `format()` calls `self.wrap()`, which wraps the generator with382 other generators. These may add some HTML code to the one generated by383 `_format_lines()`, either by modifying the lines generated by the latter,384 then yielding them again with ``(1, line)``, and/or by yielding other HTML385 code before or after the lines, with ``(0, html)``. The distinction between386 source lines and other code makes it possible to wrap the generator multiple387 times.388 389 The default `wrap()` implementation adds a ``<div>`` and a ``<pre>`` tag.390 391 A custom `HtmlFormatter` subclass could look like this:392 393 .. sourcecode:: python394 395 class CodeHtmlFormatter(HtmlFormatter):396 397 def wrap(self, source, *, include_div):398 return self._wrap_code(source)399 400 def _wrap_code(self, source):401 yield 0, '<code>'402 for i, t in source:403 if i == 1:404 # it's a line of formatted code405 t += '<br>'406 yield i, t407 yield 0, '</code>'408 409 This results in wrapping the formatted lines with a ``<code>`` tag, where the410 source lines are broken using ``<br>`` tags.411 412 After calling `wrap()`, the `format()` method also adds the "line numbers"413 and/or "full document" wrappers if the respective options are set. Then, all414 HTML yielded by the wrapped generator is output.415 """416 417 name = 'HTML'418 aliases = ['html']419 filenames = ['*.html', '*.htm']420 421 def __init__(self, **options):422 Formatter.__init__(self, **options)423 self.title = self._decodeifneeded(self.title)424 self.nowrap = get_bool_opt(options, 'nowrap', False)425 self.noclasses = get_bool_opt(options, 'noclasses', False)426 self.classprefix = options.get('classprefix', '')427 self.cssclass = html.escape(self._decodeifneeded(options.get('cssclass', 'highlight')))428 self.cssstyles = html.escape(self._decodeifneeded(options.get('cssstyles', '')))429 self.prestyles = self._decodeifneeded(options.get('prestyles', ''))430 self.cssfile = self._decodeifneeded(options.get('cssfile', ''))431 self.noclobber_cssfile = get_bool_opt(options, 'noclobber_cssfile', False)432 self.tagsfile = self._decodeifneeded(options.get('tagsfile', ''))433 self.tagurlformat = self._decodeifneeded(options.get('tagurlformat', ''))434 self.filename = html.escape(self._decodeifneeded(options.get('filename', '')))435 self.wrapcode = get_bool_opt(options, 'wrapcode', False)436 self.span_element_openers = {}437 self.debug_token_types = get_bool_opt(options, 'debug_token_types', False)438 439 if self.tagsfile:440 if not ctags:441 raise RuntimeError('The "ctags" package must to be installed '442 'to be able to use the "tagsfile" feature.')443 self._ctags = ctags.CTags(self.tagsfile)444 445 linenos = options.get('linenos', False)446 if linenos == 'inline':447 self.linenos = 2448 elif linenos:449 # compatibility with <= 0.7450 self.linenos = 1451 else:452 self.linenos = 0453 self.linenostart = abs(get_int_opt(options, 'linenostart', 1))454 self.linenostep = abs(get_int_opt(options, 'linenostep', 1))455 self.linenospecial = abs(get_int_opt(options, 'linenospecial', 0))456 self.nobackground = get_bool_opt(options, 'nobackground', False)457 self.lineseparator = html.escape(options.get('lineseparator', '\n'))458 self.lineanchors = html.escape(options.get('lineanchors', ''))459 self.linespans = html.escape(options.get('linespans', ''))460 self.anchorlinenos = get_bool_opt(options, 'anchorlinenos', False)461 self.hl_lines = set()462 for lineno in get_list_opt(options, 'hl_lines', []):463 try:464 self.hl_lines.add(int(lineno))465 except ValueError:466 pass467 468 self._create_stylesheet()469 470 def _get_css_class(self, ttype):471 """Return the css class of this token type prefixed with472 the classprefix option."""473 ttypeclass = _get_ttype_class(ttype)474 if ttypeclass:475 return self.classprefix + ttypeclass476 return ''477 478 def _get_css_classes(self, ttype):479 """Return the CSS classes of this token type prefixed with the classprefix option."""480 cls = self._get_css_class(ttype)481 while ttype not in STANDARD_TYPES:482 ttype = ttype.parent483 cls = self._get_css_class(ttype) + ' ' + cls484 return cls or ''485 486 def _get_css_inline_styles(self, ttype):487 """Return the inline CSS styles for this token type."""488 cclass = self.ttype2class.get(ttype)489 while cclass is None:490 ttype = ttype.parent491 cclass = self.ttype2class.get(ttype)492 return cclass or ''493 494 def _create_stylesheet(self):495 t2c = self.ttype2class = {Token: ''}496 c2s = self.class2style = {}497 for ttype, ndef in self.style:498 name = self._get_css_class(ttype)499 style = ''500 if ndef['color']:501 style += 'color: {}; '.format(webify(ndef['color']))502 if ndef['bold']:503 style += 'font-weight: bold; '504 if ndef['italic']:505 style += 'font-style: italic; '506 if ndef['underline']:507 style += 'text-decoration: underline; '508 if ndef['bgcolor']:509 style += 'background-color: {}; '.format(webify(ndef['bgcolor']))510 if ndef['border']:511 style += 'border: 1px solid {}; '.format(webify(ndef['border']))512 if style:513 t2c[ttype] = name514 # save len(ttype) to enable ordering the styles by515 # hierarchy (necessary for CSS cascading rules!)516 c2s[name] = (style[:-2], ttype, len(ttype))517 518 def get_style_defs(self, arg=None):519 """520 Return CSS style definitions for the classes produced by the current521 highlighting style. ``arg`` can be a string or list of selectors to522 insert before the token type classes.523 """524 style_lines = []525 526 style_lines.extend(self.get_linenos_style_defs())527 style_lines.extend(self.get_background_style_defs(arg))528 style_lines.extend(self.get_token_style_defs(arg))529 530 return '\n'.join(style_lines)531 532 def get_token_style_defs(self, arg=None):533 prefix = self.get_css_prefix(arg)534 535 styles = [536 (level, ttype, cls, style)537 for cls, (style, ttype, level) in self.class2style.items()538 if cls and style539 ]540 styles.sort()541 542 lines = [543 f'{prefix(cls)} {{ {style} }} /* {repr(ttype)[6:]} */'544 for (level, ttype, cls, style) in styles545 ]546 547 return lines548 549 def get_background_style_defs(self, arg=None):550 prefix = self.get_css_prefix(arg)551 bg_color = self.style.background_color552 hl_color = self.style.highlight_color553 554 lines = []555 556 if arg and not self.nobackground and bg_color is not None:557 text_style = ''558 if Text in self.ttype2class:559 text_style = ' ' + self.class2style[self.ttype2class[Text]][0]560 lines.insert(561 0, '{}{{ background: {};{} }}'.format(562 prefix(''), bg_color, text_style563 )564 )565 if hl_color is not None:566 lines.insert(567 0, '{} {{ background-color: {} }}'.format(prefix('hll'), hl_color)568 )569 570 return lines571 572 def get_linenos_style_defs(self):573 lines = [574 f'pre {{ {self._pre_style} }}',575 f'td.linenos .normal {{ {self._linenos_style} }}',576 f'span.linenos {{ {self._linenos_style} }}',577 f'td.linenos .special {{ {self._linenos_special_style} }}',578 f'span.linenos.special {{ {self._linenos_special_style} }}',579 ]580 581 return lines582 583 def get_css_prefix(self, arg):584 if arg is None:585 arg = ('cssclass' in self.options and '.'+self.cssclass or '')586 if isinstance(arg, str):587 args = [arg]588 else:589 args = list(arg)590 591 def prefix(cls):592 if cls:593 cls = '.' + cls594 tmp = []595 for arg in args:596 tmp.append((arg and arg + ' ' or '') + cls)597 return ', '.join(tmp)598 599 return prefix600 601 @property602 def _pre_style(self):603 return 'line-height: 125%;'604 605 @property606 def _linenos_style(self):607 color = self.style.line_number_color608 background_color = self.style.line_number_background_color609 return f'color: {color}; background-color: {background_color}; padding-left: 5px; padding-right: 5px;'610 611 @property612 def _linenos_special_style(self):613 color = self.style.line_number_special_color614 background_color = self.style.line_number_special_background_color615 return f'color: {color}; background-color: {background_color}; padding-left: 5px; padding-right: 5px;'616 617 def _decodeifneeded(self, value):618 if isinstance(value, bytes):619 if self.encoding:620 return value.decode(self.encoding)621 return value.decode()622 return value623 624 def _wrap_full(self, inner, outfile):625 if self.cssfile:626 if os.path.isabs(self.cssfile):627 # it's an absolute filename628 cssfilename = self.cssfile629 else:630 try:631 filename = outfile.name632 if not filename or filename[0] == '<':633 # pseudo files, e.g. name == '<fdopen>'634 raise AttributeError635 cssfilename = os.path.join(os.path.dirname(filename),636 self.cssfile)637 except AttributeError:638 print('Note: Cannot determine output file name, '639 'using current directory as base for the CSS file name',640 file=sys.stderr)641 cssfilename = self.cssfile642 # write CSS file only if noclobber_cssfile isn't given as an option.643 try:644 if not os.path.exists(cssfilename) or not self.noclobber_cssfile:645 with open(cssfilename, "w", encoding="utf-8") as cf:646 cf.write(CSSFILE_TEMPLATE %647 {'styledefs': self.get_style_defs('body')})648 except OSError as err:649 err.strerror = 'Error writing CSS file: ' + err.strerror650 raise651 652 yield 0, (DOC_HEADER_EXTERNALCSS %653 dict(title=self.title,654 cssfile=self.cssfile,655 encoding=self.encoding))656 else:657 yield 0, (DOC_HEADER %658 dict(title=self.title,659 styledefs=self.get_style_defs('body'),660 encoding=self.encoding))661 662 yield from inner663 yield 0, DOC_FOOTER664 665 def _wrap_tablelinenos(self, inner):666 dummyoutfile = StringIO()667 lncount = 0668 for t, line in inner:669 if t:670 lncount += 1671 dummyoutfile.write(line)672 673 fl = self.linenostart674 mw = len(str(lncount + fl - 1))675 sp = self.linenospecial676 st = self.linenostep677 anchor_name = self.lineanchors or self.linespans678 aln = self.anchorlinenos679 nocls = self.noclasses680 681 lines = []682 683 for i in range(fl, fl+lncount):684 print_line = i % st == 0685 special_line = sp and i % sp == 0686 687 if print_line:688 line = '%*d' % (mw, i)689 if aln:690 line = '<a href="#%s-%d">%s</a>' % (anchor_name, i, line)691 else:692 line = ' ' * mw693 694 if nocls:695 if special_line:696 style = f' style="{self._linenos_special_style}"'697 else:698 style = f' style="{self._linenos_style}"'699 else:700 if special_line:701 style = ' class="special"'702 else:703 style = ' class="normal"'704 705 if style:706 line = f'<span{style}>{line}</span>'707 708 lines.append(line)709 710 ls = '\n'.join(lines)711 712 # If a filename was specified, we can't put it into the code table as it713 # would misalign the line numbers. Hence we emit a separate row for it.714 filename_tr = ""715 if self.filename:716 filename_tr = (717 '<tr><th colspan="2" class="filename">'718 '<span class="filename">' + self.filename + '</span>'719 '</th></tr>')720 721 # in case you wonder about the seemingly redundant <div> here: since the722 # content in the other cell also is wrapped in a div, some browsers in723 # some configurations seem to mess up the formatting...724 yield 0, (f'<table class="{self.cssclass}table">' + filename_tr +725 '<tr><td class="linenos"><div class="linenodiv"><pre>' +726 ls + '</pre></div></td><td class="code">')727 yield 0, '<div>'728 yield 0, dummyoutfile.getvalue()729 yield 0, '</div>'730 yield 0, '</td></tr></table>'731 732 733 def _wrap_inlinelinenos(self, inner):734 # need a list of lines since we need the width of a single number :(735 inner_lines = list(inner)736 sp = self.linenospecial737 st = self.linenostep738 num = self.linenostart739 mw = len(str(len(inner_lines) + num - 1))740 anchor_name = self.lineanchors or self.linespans741 aln = self.anchorlinenos742 nocls = self.noclasses743 744 for _, inner_line in inner_lines:745 print_line = num % st == 0746 special_line = sp and num % sp == 0747 748 if print_line:749 line = '%*d' % (mw, num)750 else:751 line = ' ' * mw752 753 if nocls:754 if special_line:755 style = f' style="{self._linenos_special_style}"'756 else:757 style = f' style="{self._linenos_style}"'758 else:759 if special_line:760 style = ' class="linenos special"'761 else:762 style = ' class="linenos"'763 764 if style:765 linenos = f'<span{style}>{line}</span>'766 else:767 linenos = line768 769 if aln:770 yield 1, ('<a href="#%s-%d">%s</a>' % (anchor_name, num, linenos) +771 inner_line)772 else:773 yield 1, linenos + inner_line774 num += 1775 776 def _wrap_lineanchors(self, inner):777 s = self.lineanchors778 # subtract 1 since we have to increment i *before* yielding779 i = self.linenostart - 1780 for t, line in inner:781 if t:782 i += 1783 href = "" if self.linenos else ' href="#%s-%d"' % (s, i)784 yield 1, '<a id="%s-%d" name="%s-%d"%s></a>' % (s, i, s, i, href) + line785 else:786 yield 0, line787 788 def _wrap_linespans(self, inner):789 s = self.linespans790 i = self.linenostart - 1791 for t, line in inner:792 if t:793 i += 1794 yield 1, '<span id="%s-%d">%s</span>' % (s, i, line)795 else:796 yield 0, line797 798 def _wrap_div(self, inner):799 style = []800 if (self.noclasses and not self.nobackground and801 self.style.background_color is not None):802 style.append(f'background: {self.style.background_color}')803 if self.cssstyles:804 style.append(self.cssstyles)805 style = '; '.join(style)806 807 yield 0, ('<div' + (self.cssclass and f' class="{self.cssclass}"') +808 (style and (f' style="{style}"')) + '>')809 yield from inner810 yield 0, '</div>\n'811 812 def _wrap_pre(self, inner):813 style = []814 if self.prestyles:815 style.append(self.prestyles)816 if self.noclasses:817 style.append(self._pre_style)818 style = '; '.join(style)819 820 if self.filename and self.linenos != 1:821 yield 0, ('<span class="filename">' + self.filename + '</span>')822 823 # the empty span here is to keep leading empty lines from being824 # ignored by HTML parsers825 yield 0, ('<pre' + (style and f' style="{style}"') + '><span></span>')826 yield from inner827 yield 0, '</pre>'828 829 def _wrap_code(self, inner):830 yield 0, '<code>'831 yield from inner832 yield 0, '</code>'833 834 @functools.lru_cache(maxsize=100)835 def _translate_parts(self, value):836 """HTML-escape a value and split it by newlines."""837 return value.translate(_escape_html_table).split('\n')838 839 def _format_lines(self, tokensource):840 """841 Just format the tokens, without any wrapping tags.842 Yield individual lines.843 """844 nocls = self.noclasses845 lsep = self.lineseparator846 tagsfile = self.tagsfile847 848 lspan = ''849 line = []850 for ttype, value in tokensource:851 try:852 cspan = self.span_element_openers[ttype]853 except KeyError:854 title = ' title="{}"'.format('.'.join(ttype)) if self.debug_token_types else ''855 if nocls:856 css_style = self._get_css_inline_styles(ttype)857 if css_style:858 css_style = self.class2style[css_style][0]859 cspan = f'<span style="{css_style}"{title}>'860 else:861 cspan = ''862 else:863 css_class = self._get_css_classes(ttype)864 if css_class:865 cspan = f'<span class="{css_class}"{title}>'866 else:867 cspan = ''868 self.span_element_openers[ttype] = cspan869 870 parts = self._translate_parts(value)871 872 if tagsfile and ttype in Token.Name:873 filename, linenumber = self._lookup_ctag(value)874 if linenumber:875 base, filename = os.path.split(filename)876 if base:877 base += '/'878 filename, extension = os.path.splitext(filename)879 url = self.tagurlformat % {'path': base, 'fname': filename,880 'fext': extension}881 parts[0] = "<a href=\"%s#%s-%d\">%s" % \882 (url, self.lineanchors, linenumber, parts[0])883 parts[-1] = parts[-1] + "</a>"884 885 # for all but the last line886 for part in parts[:-1]:887 if line:888 # Also check for part being non-empty, so we avoid creating889 # empty <span> tags890 if lspan != cspan and part:891 line.extend(((lspan and '</span>'), cspan, part,892 (cspan and '</span>'), lsep))893 else: # both are the same, or the current part was empty894 line.extend((part, (lspan and '</span>'), lsep))895 yield 1, ''.join(line)896 line = []897 elif part:898 yield 1, ''.join((cspan, part, (cspan and '</span>'), lsep))899 else:900 yield 1, lsep901 # for the last line902 if line and parts[-1]:903 if lspan != cspan:904 line.extend(((lspan and '</span>'), cspan, parts[-1]))905 lspan = cspan906 else:907 line.append(parts[-1])908 elif parts[-1]:909 line = [cspan, parts[-1]]910 lspan = cspan911 # else we neither have to open a new span nor set lspan912 913 if line:914 line.extend(((lspan and '</span>'), lsep))915 yield 1, ''.join(line)916 917 def _lookup_ctag(self, token):918 entry = ctags.TagEntry()919 if self._ctags.find(entry, token.encode(), 0):920 return entry['file'].decode(), entry['lineNumber']921 else:922 return None, None923 924 def _highlight_lines(self, tokensource):925 """926 Highlighted the lines specified in the `hl_lines` option by927 post-processing the token stream coming from `_format_lines`.928 """929 hls = self.hl_lines930 931 for i, (t, value) in enumerate(tokensource):932 if t != 1:933 yield t, value934 if i + 1 in hls: # i + 1 because Python indexes start at 0935 if self.noclasses:936 style = ''937 if self.style.highlight_color is not None:938 style = (f' style="background-color: {self.style.highlight_color}"')939 yield 1, f'<span{style}>{value}</span>'940 else:941 yield 1, f'<span class="hll">{value}</span>'942 else:943 yield 1, value944 945 def wrap(self, source):946 """947 Wrap the ``source``, which is a generator yielding948 individual lines, in custom generators. See docstring949 for `format`. Can be overridden.950 """951 952 output = source953 if self.wrapcode:954 output = self._wrap_code(output)955 956 output = self._wrap_pre(output)957 958 return output959 960 def format_unencoded(self, tokensource, outfile):961 """962 The formatting process uses several nested generators; which of963 them are used is determined by the user's options.964 965 Each generator should take at least one argument, ``inner``,966 and wrap the pieces of text generated by this.967 968 Always yield 2-tuples: (code, text). If "code" is 1, the text969 is part of the original tokensource being highlighted, if it's970 0, the text is some piece of wrapping. This makes it possible to971 use several different wrappers that process the original source972 linewise, e.g. line number generators.973 """974 source = self._format_lines(tokensource)975 976 # As a special case, we wrap line numbers before line highlighting977 # so the line numbers get wrapped in the highlighting tag.978 if not self.nowrap and self.linenos == 2:979 source = self._wrap_inlinelinenos(source)980 981 if self.hl_lines:982 source = self._highlight_lines(source)983 984 if not self.nowrap:985 if self.lineanchors:986 source = self._wrap_lineanchors(source)987 if self.linespans:988 source = self._wrap_linespans(source)989 source = self.wrap(source)990 if self.linenos == 1:991 source = self._wrap_tablelinenos(source)992 source = self._wrap_div(source)993 if self.full:994 source = self._wrap_full(source, outfile)995 996 for t, piece in source:997 outfile.write(piece)998 