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