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