3 pygments.formatters.html |
3 pygments.formatters.html |
4 ~~~~~~~~~~~~~~~~~~~~~~~~ |
4 ~~~~~~~~~~~~~~~~~~~~~~~~ |
5 |
5 |
6 Formatter for HTML output. |
6 Formatter for HTML output. |
7 |
7 |
8 :copyright: Copyright 2006-2017 by the Pygments team, see AUTHORS. |
8 :copyright: Copyright 2006-2019 by the Pygments team, see AUTHORS. |
9 :license: BSD, see LICENSE for details. |
9 :license: BSD, see LICENSE for details. |
10 """ |
10 """ |
11 |
|
12 from __future__ import print_function |
|
13 |
11 |
14 import os |
12 import os |
15 import sys |
13 import sys |
16 import os.path |
14 import os.path |
|
15 from io import StringIO |
17 |
16 |
18 from pygments.formatter import Formatter |
17 from pygments.formatter import Formatter |
19 from pygments.token import Token, Text, STANDARD_TYPES |
18 from pygments.token import Token, Text, STANDARD_TYPES |
20 from pygments.util import get_bool_opt, get_int_opt, get_list_opt, \ |
19 from pygments.util import get_bool_opt, get_int_opt, get_list_opt |
21 StringIO, string_types, iteritems |
|
22 |
20 |
23 try: |
21 try: |
24 import ctags |
22 import ctags |
25 except ImportError: |
23 except ImportError: |
26 ctags = None |
24 ctags = None |
38 |
36 |
39 |
37 |
40 def escape_html(text, table=_escape_html_table): |
38 def escape_html(text, table=_escape_html_table): |
41 """Escape &, <, > as well as single and double quotes for HTML.""" |
39 """Escape &, <, > as well as single and double quotes for HTML.""" |
42 return text.translate(table) |
40 return text.translate(table) |
|
41 |
|
42 |
|
43 def webify(color): |
|
44 if color.startswith('calc') or color.startswith('var'): |
|
45 return color |
|
46 else: |
|
47 return '#' + color |
43 |
48 |
44 |
49 |
45 def _get_ttype_class(ttype): |
50 def _get_ttype_class(ttype): |
46 fname = STANDARD_TYPES.get(ttype) |
51 fname = STANDARD_TYPES.get(ttype) |
47 if fname: |
52 if fname: |
53 fname = STANDARD_TYPES.get(ttype) |
58 fname = STANDARD_TYPES.get(ttype) |
54 return fname + aname |
59 return fname + aname |
55 |
60 |
56 |
61 |
57 CSSFILE_TEMPLATE = '''\ |
62 CSSFILE_TEMPLATE = '''\ |
|
63 /* |
|
64 generated by Pygments <https://pygments.org/> |
|
65 Copyright 2006-2019 by the Pygments team. |
|
66 Licensed under the BSD license, see LICENSE for details. |
|
67 */ |
58 td.linenos { background-color: #f0f0f0; padding-right: 10px; } |
68 td.linenos { background-color: #f0f0f0; padding-right: 10px; } |
59 span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } |
69 span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } |
60 pre { line-height: 125%%; } |
70 pre { line-height: 125%%; } |
61 %(styledefs)s |
71 %(styledefs)s |
62 ''' |
72 ''' |
63 |
73 |
64 DOC_HEADER = '''\ |
74 DOC_HEADER = '''\ |
65 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" |
75 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" |
66 "http://www.w3.org/TR/html4/strict.dtd"> |
76 "http://www.w3.org/TR/html4/strict.dtd"> |
67 |
77 <!-- |
|
78 generated by Pygments <https://pygments.org/> |
|
79 Copyright 2006-2019 by the Pygments team. |
|
80 Licensed under the BSD license, see LICENSE for details. |
|
81 --> |
68 <html> |
82 <html> |
69 <head> |
83 <head> |
70 <title>%(title)s</title> |
84 <title>%(title)s</title> |
71 <meta http-equiv="content-type" content="text/html; charset=%(encoding)s"> |
85 <meta http-equiv="content-type" content="text/html; charset=%(encoding)s"> |
72 <style type="text/css"> |
86 <style type="text/css"> |
320 Defaults to an empty string, resulting in just `#prefix-number` links. |
334 Defaults to an empty string, resulting in just `#prefix-number` links. |
321 |
335 |
322 .. versionadded:: 1.6 |
336 .. versionadded:: 1.6 |
323 |
337 |
324 `filename` |
338 `filename` |
325 A string used to generate a filename when rendering <pre> blocks, |
339 A string used to generate a filename when rendering ``<pre>`` blocks, |
326 for example if displaying source code. |
340 for example if displaying source code. |
327 |
341 |
328 .. versionadded:: 2.1 |
342 .. versionadded:: 2.1 |
|
343 |
|
344 `wrapcode` |
|
345 Wrap the code inside ``<pre>`` blocks using ``<code>``, as recommended |
|
346 by the HTML5 specification. |
|
347 |
|
348 .. versionadded:: 2.4 |
329 |
349 |
330 |
350 |
331 **Subclassing the HTML formatter** |
351 **Subclassing the HTML formatter** |
332 |
352 |
333 .. versionadded:: 0.7 |
353 .. versionadded:: 0.7 |
393 self.cssfile = self._decodeifneeded(options.get('cssfile', '')) |
413 self.cssfile = self._decodeifneeded(options.get('cssfile', '')) |
394 self.noclobber_cssfile = get_bool_opt(options, 'noclobber_cssfile', False) |
414 self.noclobber_cssfile = get_bool_opt(options, 'noclobber_cssfile', False) |
395 self.tagsfile = self._decodeifneeded(options.get('tagsfile', '')) |
415 self.tagsfile = self._decodeifneeded(options.get('tagsfile', '')) |
396 self.tagurlformat = self._decodeifneeded(options.get('tagurlformat', '')) |
416 self.tagurlformat = self._decodeifneeded(options.get('tagurlformat', '')) |
397 self.filename = self._decodeifneeded(options.get('filename', '')) |
417 self.filename = self._decodeifneeded(options.get('filename', '')) |
|
418 self.wrapcode = get_bool_opt(options, 'wrapcode', False) |
398 |
419 |
399 if self.tagsfile: |
420 if self.tagsfile: |
400 if not ctags: |
421 if not ctags: |
401 raise RuntimeError('The "ctags" package must to be installed ' |
422 raise RuntimeError('The "ctags" package must to be installed ' |
402 'to be able to use the "tagsfile" feature.') |
423 'to be able to use the "tagsfile" feature.') |
412 self.linenos = 0 |
433 self.linenos = 0 |
413 self.linenostart = abs(get_int_opt(options, 'linenostart', 1)) |
434 self.linenostart = abs(get_int_opt(options, 'linenostart', 1)) |
414 self.linenostep = abs(get_int_opt(options, 'linenostep', 1)) |
435 self.linenostep = abs(get_int_opt(options, 'linenostep', 1)) |
415 self.linenospecial = abs(get_int_opt(options, 'linenospecial', 0)) |
436 self.linenospecial = abs(get_int_opt(options, 'linenospecial', 0)) |
416 self.nobackground = get_bool_opt(options, 'nobackground', False) |
437 self.nobackground = get_bool_opt(options, 'nobackground', False) |
417 self.lineseparator = options.get('lineseparator', '\n') |
438 self.lineseparator = options.get('lineseparator', u'\n') |
418 self.lineanchors = options.get('lineanchors', '') |
439 self.lineanchors = options.get('lineanchors', '') |
419 self.linespans = options.get('linespans', '') |
440 self.linespans = options.get('linespans', '') |
420 self.anchorlinenos = options.get('anchorlinenos', False) |
441 self.anchorlinenos = options.get('anchorlinenos', False) |
421 self.hl_lines = set() |
442 self.hl_lines = set() |
422 for lineno in get_list_opt(options, 'hl_lines', []): |
443 for lineno in get_list_opt(options, 'hl_lines', []): |
449 c2s = self.class2style = {} |
470 c2s = self.class2style = {} |
450 for ttype, ndef in self.style: |
471 for ttype, ndef in self.style: |
451 name = self._get_css_class(ttype) |
472 name = self._get_css_class(ttype) |
452 style = '' |
473 style = '' |
453 if ndef['color']: |
474 if ndef['color']: |
454 style += 'color: #%s; ' % ndef['color'] |
475 style += 'color: %s; ' % webify(ndef['color']) |
455 if ndef['bold']: |
476 if ndef['bold']: |
456 style += 'font-weight: bold; ' |
477 style += 'font-weight: bold; ' |
457 if ndef['italic']: |
478 if ndef['italic']: |
458 style += 'font-style: italic; ' |
479 style += 'font-style: italic; ' |
459 if ndef['underline']: |
480 if ndef['underline']: |
460 style += 'text-decoration: underline; ' |
481 style += 'text-decoration: underline; ' |
461 if ndef['bgcolor']: |
482 if ndef['bgcolor']: |
462 style += 'background-color: #%s; ' % ndef['bgcolor'] |
483 style += 'background-color: %s; ' % webify(ndef['bgcolor']) |
463 if ndef['border']: |
484 if ndef['border']: |
464 style += 'border: 1px solid #%s; ' % ndef['border'] |
485 style += 'border: 1px solid %s; ' % webify(ndef['border']) |
465 if style: |
486 if style: |
466 t2c[ttype] = name |
487 t2c[ttype] = name |
467 # save len(ttype) to enable ordering the styles by |
488 # save len(ttype) to enable ordering the styles by |
468 # hierarchy (necessary for CSS cascading rules!) |
489 # hierarchy (necessary for CSS cascading rules!) |
469 c2s[name] = (style[:-2], ttype, len(ttype)) |
490 c2s[name] = (style[:-2], ttype, len(ttype)) |
488 for arg in args: |
509 for arg in args: |
489 tmp.append((arg and arg + ' ' or '') + cls) |
510 tmp.append((arg and arg + ' ' or '') + cls) |
490 return ', '.join(tmp) |
511 return ', '.join(tmp) |
491 |
512 |
492 styles = [(level, ttype, cls, style) |
513 styles = [(level, ttype, cls, style) |
493 for cls, (style, ttype, level) in iteritems(self.class2style) |
514 for cls, (style, ttype, level) in self.class2style.items() |
494 if cls and style] |
515 if cls and style] |
495 styles.sort() |
516 styles.sort() |
496 lines = ['%s { %s } /* %s */' % (prefix(cls), style, repr(ttype)[6:]) |
517 lines = ['%s { %s } /* %s */' % (prefix(cls), style, repr(ttype)[6:]) |
497 for (level, ttype, cls, style) in styles] |
518 for (level, ttype, cls, style) in styles] |
498 if arg and not self.nobackground and \ |
519 if arg and not self.nobackground and \ |
533 file=sys.stderr) |
554 file=sys.stderr) |
534 cssfilename = self.cssfile |
555 cssfilename = self.cssfile |
535 # write CSS file only if noclobber_cssfile isn't given as an option. |
556 # write CSS file only if noclobber_cssfile isn't given as an option. |
536 try: |
557 try: |
537 if not os.path.exists(cssfilename) or not self.noclobber_cssfile: |
558 if not os.path.exists(cssfilename) or not self.noclobber_cssfile: |
538 cf = open(cssfilename, "w") |
559 with open(cssfilename, "w") as cf: |
539 cf.write(CSSFILE_TEMPLATE % |
560 cf.write(CSSFILE_TEMPLATE % |
540 {'styledefs': self.get_style_defs('body')}) |
561 {'styledefs': self.get_style_defs('body')}) |
541 cf.close() |
|
542 except IOError as err: |
562 except IOError as err: |
543 err.strerror = 'Error writing CSS file: ' + err.strerror |
563 err.strerror = 'Error writing CSS file: ' + err.strerror |
544 raise |
564 raise |
545 |
565 |
546 yield 0, (DOC_HEADER_EXTERNALCSS % |
566 yield 0, (DOC_HEADER_EXTERNALCSS % |
707 yield 0, ('<pre' + (style and ' style="%s"' % style) + '><span></span>') |
727 yield 0, ('<pre' + (style and ' style="%s"' % style) + '><span></span>') |
708 for tup in inner: |
728 for tup in inner: |
709 yield tup |
729 yield tup |
710 yield 0, '</pre>' |
730 yield 0, '</pre>' |
711 |
731 |
|
732 def _wrap_code(self, inner): |
|
733 yield 0, '<code>' |
|
734 for tup in inner: |
|
735 yield tup |
|
736 yield 0, '</code>' |
|
737 |
712 def _format_lines(self, tokensource): |
738 def _format_lines(self, tokensource): |
713 """ |
739 """ |
714 Just format the tokens, without any wrapping tags. |
740 Just format the tokens, without any wrapping tags. |
715 Yield individual lines. |
741 Yield individual lines. |
716 """ |
742 """ |
813 """ |
839 """ |
814 Wrap the ``source``, which is a generator yielding |
840 Wrap the ``source``, which is a generator yielding |
815 individual lines, in custom generators. See docstring |
841 individual lines, in custom generators. See docstring |
816 for `format`. Can be overridden. |
842 for `format`. Can be overridden. |
817 """ |
843 """ |
818 return self._wrap_div(self._wrap_pre(source)) |
844 if self.wrapcode: |
|
845 return self._wrap_div(self._wrap_pre(self._wrap_code(source))) |
|
846 else: |
|
847 return self._wrap_div(self._wrap_pre(source)) |
819 |
848 |
820 def format_unencoded(self, tokensource, outfile): |
849 def format_unencoded(self, tokensource, outfile): |
821 """ |
850 """ |
822 The formatting process uses several nested generators; which of |
851 The formatting process uses several nested generators; which of |
823 them are used is determined by the user's options. |
852 them are used is determined by the user's options. |