1"""
2 pygments.formatters.html
3 ~~~~~~~~~~~~~~~~~~~~~~~~
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 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
21import html
22
23try:
24 import ctags
25except ImportError:
26 ctags = None
27
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 color
48 else:
49 # Check if the color can be shortened from 6 to 3 characters
50 color = color.upper()
51 if (len(color) == 6 and
52 ( 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 fname
64 aname = ''
65 while fname is None:
66 aname = '-' + ttype[-1] + aname
67 ttype = ttype.parent
68 fname = STANDARD_TYPES.get(ttype)
69 return fname + aname
70
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)s
79'''
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 enclosed
126 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>`` is
130 additionally wrapped inside a ``<table>`` which has one row and two
131 cells: one containing the line numbers and one containing the code.
132 Example:
133
134 .. sourcecode:: html
135
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>1
142 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 these
154 lines highlighted (as of Pygments 0.11).
155
156 With the `full` option, a complete HTML 4 document is output, including
157 the style definitions inside a ``<style>`` tag, or in a separate file if
158 the `cssfile` option is given.
159
160 When `tagsfile` is set to the path of a ctags index file, it is used to
161 generate hyperlinks from names to their definition. You must enable
162 `lineanchors` and run ctags with the `-n` option for this to work. The
163 `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 string
167 containing CSS rules for the CSS classes used by the formatter. The
168 argument `arg` can be used to specify additional CSS selectors that
169 are prepended to the classes. A call `fmter.get_style_defs('td .code')`
170 would result in the following CSS classes:
171
172 .. sourcecode:: css
173
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 the
179 `get_style_defs()` method to request multiple prefixes for the tokens:
180
181 .. sourcecode:: python
182
183 formatter.get_style_defs(['div.syntax pre', 'pre.syntax'])
184
185 The output would then look like this:
186
187 .. sourcecode:: css
188
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>`` tag
199 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 complete
203 self-contained document (default: ``False``).
204
205 `title`
206 If `full` is true, the title that should be used to caption the
207 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 in
213 `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 recommended
218 for larger pieces of code since it increases output size by quite a bit
219 (default: ``False``).
220
221 `classprefix`
222 Since the token types use relatively short class names, they may clash
223 with some of your own class names. In this case you can use the
224 `classprefix` option to give a string to prepend to all Pygments-generated
225 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.9
234 If you select the ``'table'`` line numbers, the wrapping table will
235 have a CSS class of this string plus ``'table'``, the default is
236 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.11
245
246 `cssfile`
247 If the `full` option is true and this option is given, it must be the
248 name of an external file. If the filename does not include an absolute
249 path, the file's path will be assumed to be relative to the main output
250 file's path, if the latter can be found. The stylesheet is then written
251 to this file instead of the HTML file.
252
253 .. versionadded:: 0.6
254
255 `noclobber_cssfile`
256 If `cssfile` is given and the specified file exists, the css file will
257 not be overwritten. This allows the use of the `full` option in
258 combination with a user specified css file. Default is ``False``.
259
260 .. versionadded:: 1.1
261
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 is
265 copy-and-paste-friendly, but may cause alignment problems with some
266 browsers or fonts. If set to ``'inline'``, the line numbers will be
267 integrated in the ``<pre>`` tag that contains the code (that setting
268 is *new in Pygments 0.8*).
269
270 For compatibility with Pygments 0.7 and earlier, every true value
271 except ``'inline'`` means the same as ``'table'`` (in particular, that
272 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 line
277 numbers and code can have different line heights in Internet Explorer
278 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 always
284 relative to the input (i.e. the first line is line 1) and are
285 independent of `linenostart`.
286
287 .. versionadded:: 0.11
288
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 CSS
297 class ``"special"`` (default: ``0``).
298
299 `nobackground`
300 If set to ``True``, the formatter won't output the background color
301 for the wrapping element (this automatically defaults to ``False``
302 when there is no wrapping element [eg: no argument for the
303 `get_syntax_defs` method given]) (default: ``False``).
304
305 .. versionadded:: 0.6
306
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 can
310 e.g. set it to ``"<br>"`` to get HTML line breaks.
311
312 .. versionadded:: 0.7
313
314 `lineanchors`
315 If set to a nonempty string, e.g. ``foo``, the formatter will wrap each
316 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.9
320
321 `linespans`
322 If set to a nonempty string, e.g. ``foo``, the formatter will wrap each
323 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.6
327
328 `anchorlinenos`
329 If set to `True`, will wrap line numbers in <a> tags. Used in
330 combination with `linenos` and `lineanchors`.
331
332 `tagsfile`
333 If set to the path of a ctags file, wrap names in anchor tags that
334 link to their definitions. `lineanchors` should be used, and the
335 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.6
339
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.6
346
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 to
350 ``'table'`` then the filename will be rendered in an initial row
351 containing a single `<th>` which spans both columns.
352
353 .. versionadded:: 2.1
354
355 `wrapcode`
356 Wrap the code inside ``<pre>`` blocks using ``<code>``, as recommended
357 by the HTML5 specification.
358
359 .. versionadded:: 2.4
360
361 `debug_token_types`
362 Add ``title`` attributes to all token ``<span>`` tags that show the
363 name of the token.
364
365 .. versionadded:: 2.10
366
367
368 **Subclassing the HTML formatter**
369
370 .. versionadded:: 0.7
371
372 The HTML formatter is now built in a way that allows easy subclassing, thus
373 customizing the output HTML code. The `format()` method calls
374 `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 the
376 formatted source code.
377
378 If the `nowrap` option is set, the generator is the iterated over and the
379 resulting HTML is output.
380
381 Otherwise, `format()` calls `self.wrap()`, which wraps the generator with
382 other generators. These may add some HTML code to the one generated by
383 `_format_lines()`, either by modifying the lines generated by the latter,
384 then yielding them again with ``(1, line)``, and/or by yielding other HTML
385 code before or after the lines, with ``(0, html)``. The distinction between
386 source lines and other code makes it possible to wrap the generator multiple
387 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:: python
394
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 code
405 t += '<br>'
406 yield i, t
407 yield 0, '</code>'
408
409 This results in wrapping the formatted lines with a ``<code>`` tag, where the
410 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, all
414 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 = 2
448 elif linenos:
449 # compatibility with <= 0.7
450 self.linenos = 1
451 else:
452 self.linenos = 0
453 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 pass
467
468 self._create_stylesheet()
469
470 def _get_css_class(self, ttype):
471 """Return the css class of this token type prefixed with
472 the classprefix option."""
473 ttypeclass = _get_ttype_class(ttype)
474 if ttypeclass:
475 return self.classprefix + ttypeclass
476 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.parent
483 cls = self._get_css_class(ttype) + ' ' + cls
484 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.parent
491 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] = name
514 # save len(ttype) to enable ordering the styles by
515 # 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 current
521 highlighting style. ``arg`` can be a string or list of selectors to
522 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 style
539 ]
540 styles.sort()
541
542 lines = [
543 f'{prefix(cls)} {{ {style} }} /* {repr(ttype)[6:]} */'
544 for (level, ttype, cls, style) in styles
545 ]
546
547 return lines
548
549 def get_background_style_defs(self, arg=None):
550 prefix = self.get_css_prefix(arg)
551 bg_color = self.style.background_color
552 hl_color = self.style.highlight_color
553
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_style
563 )
564 )
565 if hl_color is not None:
566 lines.insert(
567 0, '{} {{ background-color: {} }}'.format(prefix('hll'), hl_color)
568 )
569
570 return lines
571
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 lines
582
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 = '.' + cls
594 tmp = []
595 for arg in args:
596 tmp.append((arg and arg + ' ' or '') + cls)
597 return ', '.join(tmp)
598
599 return prefix
600
601 @property
602 def _pre_style(self):
603 return 'line-height: 125%;'
604
605 @property
606 def _linenos_style(self):
607 color = self.style.line_number_color
608 background_color = self.style.line_number_background_color
609 return f'color: {color}; background-color: {background_color}; padding-left: 5px; padding-right: 5px;'
610
611 @property
612 def _linenos_special_style(self):
613 color = self.style.line_number_special_color
614 background_color = self.style.line_number_special_background_color
615 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 value
623
624 def _wrap_full(self, inner, outfile):
625 if self.cssfile:
626 if os.path.isabs(self.cssfile):
627 # it's an absolute filename
628 cssfilename = self.cssfile
629 else:
630 try:
631 filename = outfile.name
632 if not filename or filename[0] == '<':
633 # pseudo files, e.g. name == '<fdopen>'
634 raise AttributeError
635 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.cssfile
642 # 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.strerror
650 raise
651
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 inner
663 yield 0, DOC_FOOTER
664
665 def _wrap_tablelinenos(self, inner):
666 dummyoutfile = StringIO()
667 lncount = 0
668 for t, line in inner:
669 if t:
670 lncount += 1
671 dummyoutfile.write(line)
672
673 fl = self.linenostart
674 mw = len(str(lncount + fl - 1))
675 sp = self.linenospecial
676 st = self.linenostep
677 anchor_name = self.lineanchors or self.linespans
678 aln = self.anchorlinenos
679 nocls = self.noclasses
680
681 lines = []
682
683 for i in range(fl, fl+lncount):
684 print_line = i % st == 0
685 special_line = sp and i % sp == 0
686
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 = ' ' * mw
693
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 it
713 # 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 the
722 # content in the other cell also is wrapped in a div, some browsers in
723 # 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.linenospecial
737 st = self.linenostep
738 num = self.linenostart
739 mw = len(str(len(inner_lines) + num - 1))
740 anchor_name = self.lineanchors or self.linespans
741 aln = self.anchorlinenos
742 nocls = self.noclasses
743
744 for _, inner_line in inner_lines:
745 print_line = num % st == 0
746 special_line = sp and num % sp == 0
747
748 if print_line:
749 line = '%*d' % (mw, num)
750 else:
751 line = ' ' * mw
752
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 = line
768
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_line
774 num += 1
775
776 def _wrap_lineanchors(self, inner):
777 s = self.lineanchors
778 # subtract 1 since we have to increment i *before* yielding
779 i = self.linenostart - 1
780 for t, line in inner:
781 if t:
782 i += 1
783 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) + line
785 else:
786 yield 0, line
787
788 def _wrap_linespans(self, inner):
789 s = self.linespans
790 i = self.linenostart - 1
791 for t, line in inner:
792 if t:
793 i += 1
794 yield 1, '<span id="%s-%d">%s</span>' % (s, i, line)
795 else:
796 yield 0, line
797
798 def _wrap_div(self, inner):
799 style = []
800 if (self.noclasses and not self.nobackground and
801 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 inner
810 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 being
824 # ignored by HTML parsers
825 yield 0, ('<pre' + (style and f' style="{style}"') + '><span></span>')
826 yield from inner
827 yield 0, '</pre>'
828
829 def _wrap_code(self, inner):
830 yield 0, '<code>'
831 yield from inner
832 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.noclasses
845 lsep = self.lineseparator
846 tagsfile = self.tagsfile
847
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] = cspan
869
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 line
886 for part in parts[:-1]:
887 if line:
888 # Also check for part being non-empty, so we avoid creating
889 # empty <span> tags
890 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 empty
894 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, lsep
901 # for the last line
902 if line and parts[-1]:
903 if lspan != cspan:
904 line.extend(((lspan and '</span>'), cspan, parts[-1]))
905 lspan = cspan
906 else:
907 line.append(parts[-1])
908 elif parts[-1]:
909 line = [cspan, parts[-1]]
910 lspan = cspan
911 # else we neither have to open a new span nor set lspan
912
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, None
923
924 def _highlight_lines(self, tokensource):
925 """
926 Highlighted the lines specified in the `hl_lines` option by
927 post-processing the token stream coming from `_format_lines`.
928 """
929 hls = self.hl_lines
930
931 for i, (t, value) in enumerate(tokensource):
932 if t != 1:
933 yield t, value
934 if i + 1 in hls: # i + 1 because Python indexes start at 0
935 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, value
944
945 def wrap(self, source):
946 """
947 Wrap the ``source``, which is a generator yielding
948 individual lines, in custom generators. See docstring
949 for `format`. Can be overridden.
950 """
951
952 output = source
953 if self.wrapcode:
954 output = self._wrap_code(output)
955
956 output = self._wrap_pre(output)
957
958 return output
959
960 def format_unencoded(self, tokensource, outfile):
961 """
962 The formatting process uses several nested generators; which of
963 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 text
969 is part of the original tokensource being highlighted, if it's
970 0, the text is some piece of wrapping. This makes it possible to
971 use several different wrappers that process the original source
972 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 highlighting
977 # 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)