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-2014 by the Pygments team, see AUTHORS. |
8 :copyright: Copyright 2006-2015 by the Pygments team, see AUTHORS. |
9 :license: BSD, see LICENSE for details. |
9 :license: BSD, see LICENSE for details. |
10 """ |
10 """ |
11 |
11 |
12 from __future__ import print_function |
12 from __future__ import print_function |
13 |
13 |
34 ord('>'): u'>', |
34 ord('>'): u'>', |
35 ord('"'): u'"', |
35 ord('"'): u'"', |
36 ord("'"): u''', |
36 ord("'"): u''', |
37 } |
37 } |
38 |
38 |
|
39 |
39 def escape_html(text, table=_escape_html_table): |
40 def escape_html(text, table=_escape_html_table): |
40 """Escape &, <, > as well as single and double quotes for HTML.""" |
41 """Escape &, <, > as well as single and double quotes for HTML.""" |
41 return text.translate(table) |
42 return text.translate(table) |
42 |
|
43 def get_random_id(): |
|
44 """Return a random id for javascript fields.""" |
|
45 from random import random |
|
46 from time import time |
|
47 try: |
|
48 from hashlib import sha1 as sha |
|
49 except ImportError: |
|
50 import sha |
|
51 sha = sha.new |
|
52 return sha('%s|%s' % (random(), time())).hexdigest() |
|
53 |
43 |
54 |
44 |
55 def _get_ttype_class(ttype): |
45 def _get_ttype_class(ttype): |
56 fname = STANDARD_TYPES.get(ttype) |
46 fname = STANDARD_TYPES.get(ttype) |
57 if fname: |
47 if fname: |
148 the style definitions inside a ``<style>`` tag, or in a separate file if |
138 the style definitions inside a ``<style>`` tag, or in a separate file if |
149 the `cssfile` option is given. |
139 the `cssfile` option is given. |
150 |
140 |
151 When `tagsfile` is set to the path of a ctags index file, it is used to |
141 When `tagsfile` is set to the path of a ctags index file, it is used to |
152 generate hyperlinks from names to their definition. You must enable |
142 generate hyperlinks from names to their definition. You must enable |
153 `anchorlines` and run ctags with the `-n` option for this to work. The |
143 `lineanchors` and run ctags with the `-n` option for this to work. The |
154 `python-ctags` module from PyPI must be installed to use this feature; |
144 `python-ctags` module from PyPI must be installed to use this feature; |
155 otherwise a `RuntimeError` will be raised. |
145 otherwise a `RuntimeError` will be raised. |
156 |
146 |
157 The `get_style_defs(arg='')` method of a `HtmlFormatter` returns a string |
147 The `get_style_defs(arg='')` method of a `HtmlFormatter` returns a string |
158 containing CSS rules for the CSS classes used by the formatter. The |
148 containing CSS rules for the CSS classes used by the formatter. The |
328 A string formatting pattern used to generate links to ctags definitions. |
318 A string formatting pattern used to generate links to ctags definitions. |
329 Available variables are `%(path)s`, `%(fname)s` and `%(fext)s`. |
319 Available variables are `%(path)s`, `%(fname)s` and `%(fext)s`. |
330 Defaults to an empty string, resulting in just `#prefix-number` links. |
320 Defaults to an empty string, resulting in just `#prefix-number` links. |
331 |
321 |
332 .. versionadded:: 1.6 |
322 .. versionadded:: 1.6 |
|
323 |
|
324 `filename` |
|
325 A string used to generate a filename when rendering <pre> blocks, |
|
326 for example if displaying source code. |
|
327 |
|
328 .. versionadded:: 2.1 |
333 |
329 |
334 |
330 |
335 **Subclassing the HTML formatter** |
331 **Subclassing the HTML formatter** |
336 |
332 |
337 .. versionadded:: 0.7 |
333 .. versionadded:: 0.7 |
396 self.prestyles = self._decodeifneeded(options.get('prestyles', '')) |
392 self.prestyles = self._decodeifneeded(options.get('prestyles', '')) |
397 self.cssfile = self._decodeifneeded(options.get('cssfile', '')) |
393 self.cssfile = self._decodeifneeded(options.get('cssfile', '')) |
398 self.noclobber_cssfile = get_bool_opt(options, 'noclobber_cssfile', False) |
394 self.noclobber_cssfile = get_bool_opt(options, 'noclobber_cssfile', False) |
399 self.tagsfile = self._decodeifneeded(options.get('tagsfile', '')) |
395 self.tagsfile = self._decodeifneeded(options.get('tagsfile', '')) |
400 self.tagurlformat = self._decodeifneeded(options.get('tagurlformat', '')) |
396 self.tagurlformat = self._decodeifneeded(options.get('tagurlformat', '')) |
|
397 self.filename = self._decodeifneeded(options.get('filename', '')) |
401 |
398 |
402 if self.tagsfile: |
399 if self.tagsfile: |
403 if not ctags: |
400 if not ctags: |
404 raise RuntimeError('The "ctags" package must to be installed ' |
401 raise RuntimeError('The "ctags" package must to be installed ' |
405 'to be able to use the "tagsfile" feature.') |
402 'to be able to use the "tagsfile" feature.') |
435 the classprefix option.""" |
432 the classprefix option.""" |
436 ttypeclass = _get_ttype_class(ttype) |
433 ttypeclass = _get_ttype_class(ttype) |
437 if ttypeclass: |
434 if ttypeclass: |
438 return self.classprefix + ttypeclass |
435 return self.classprefix + ttypeclass |
439 return '' |
436 return '' |
|
437 |
|
438 def _get_css_classes(self, ttype): |
|
439 """Return the css classes of this token type prefixed with |
|
440 the classprefix option.""" |
|
441 cls = self._get_css_class(ttype) |
|
442 while ttype not in STANDARD_TYPES: |
|
443 ttype = ttype.parent |
|
444 cls = self._get_css_class(ttype) + ' ' + cls |
|
445 return cls |
440 |
446 |
441 def _create_stylesheet(self): |
447 def _create_stylesheet(self): |
442 t2c = self.ttype2class = {Token: ''} |
448 t2c = self.ttype2class = {Token: ''} |
443 c2s = self.class2style = {} |
449 c2s = self.class2style = {} |
444 for ttype, ndef in self.style: |
450 for ttype, ndef in self.style: |
520 # pseudo files, e.g. name == '<fdopen>' |
526 # pseudo files, e.g. name == '<fdopen>' |
521 raise AttributeError |
527 raise AttributeError |
522 cssfilename = os.path.join(os.path.dirname(filename), |
528 cssfilename = os.path.join(os.path.dirname(filename), |
523 self.cssfile) |
529 self.cssfile) |
524 except AttributeError: |
530 except AttributeError: |
525 print('Note: Cannot determine output file name, ' \ |
531 print('Note: Cannot determine output file name, ' |
526 'using current directory as base for the CSS file name', |
532 'using current directory as base for the CSS file name', |
527 file=sys.stderr) |
533 file=sys.stderr) |
528 cssfilename = self.cssfile |
534 cssfilename = self.cssfile |
529 # write CSS file only if noclobber_cssfile isn't given as an option. |
535 # write CSS file only if noclobber_cssfile isn't given as an option. |
530 try: |
536 try: |
531 if not os.path.exists(cssfilename) or not self.noclobber_cssfile: |
537 if not os.path.exists(cssfilename) or not self.noclobber_cssfile: |
532 cf = open(cssfilename, "w") |
538 cf = open(cssfilename, "w") |
533 cf.write(CSSFILE_TEMPLATE % |
539 cf.write(CSSFILE_TEMPLATE % |
534 {'styledefs': self.get_style_defs('body')}) |
540 {'styledefs': self.get_style_defs('body')}) |
535 cf.close() |
541 cf.close() |
536 except IOError as err: |
542 except IOError as err: |
537 err.strerror = 'Error writing CSS file: ' + err.strerror |
543 err.strerror = 'Error writing CSS file: ' + err.strerror |
538 raise |
544 raise |
539 |
545 |
540 yield 0, (DOC_HEADER_EXTERNALCSS % |
546 yield 0, (DOC_HEADER_EXTERNALCSS % |
541 dict(title = self.title, |
547 dict(title=self.title, |
542 cssfile = self.cssfile, |
548 cssfile=self.cssfile, |
543 encoding = self.encoding)) |
549 encoding=self.encoding)) |
544 else: |
550 else: |
545 yield 0, (DOC_HEADER % |
551 yield 0, (DOC_HEADER % |
546 dict(title = self.title, |
552 dict(title=self.title, |
547 styledefs = self.get_style_defs('body'), |
553 styledefs=self.get_style_defs('body'), |
548 encoding = self.encoding)) |
554 encoding=self.encoding)) |
549 |
555 |
550 for t, line in inner: |
556 for t, line in inner: |
551 yield t, line |
557 yield t, line |
552 yield 0, DOC_FOOTER |
558 yield 0, DOC_FOOTER |
553 |
559 |
622 mw = len(str(len(lines) + num - 1)) |
628 mw = len(str(len(lines) + num - 1)) |
623 |
629 |
624 if self.noclasses: |
630 if self.noclasses: |
625 if sp: |
631 if sp: |
626 for t, line in lines: |
632 for t, line in lines: |
627 if num%sp == 0: |
633 if num % sp == 0: |
628 style = 'background-color: #ffffc0; padding: 0 5px 0 5px' |
634 style = 'background-color: #ffffc0; padding: 0 5px 0 5px' |
629 else: |
635 else: |
630 style = 'background-color: #f0f0f0; padding: 0 5px 0 5px' |
636 style = 'background-color: #f0f0f0; padding: 0 5px 0 5px' |
631 yield 1, '<span style="%s">%*s </span>' % ( |
637 yield 1, '<span style="%s">%*s </span>' % ( |
632 style, mw, (num%st and ' ' or num)) + line |
638 style, mw, (num % st and ' ' or num)) + line |
633 num += 1 |
639 num += 1 |
634 else: |
640 else: |
635 for t, line in lines: |
641 for t, line in lines: |
636 yield 1, ('<span style="background-color: #f0f0f0; ' |
642 yield 1, ('<span style="background-color: #f0f0f0; ' |
637 'padding: 0 5px 0 5px">%*s </span>' % ( |
643 'padding: 0 5px 0 5px">%*s </span>' % ( |
638 mw, (num%st and ' ' or num)) + line) |
644 mw, (num % st and ' ' or num)) + line) |
639 num += 1 |
645 num += 1 |
640 elif sp: |
646 elif sp: |
641 for t, line in lines: |
647 for t, line in lines: |
642 yield 1, '<span class="lineno%s">%*s </span>' % ( |
648 yield 1, '<span class="lineno%s">%*s </span>' % ( |
643 num%sp == 0 and ' special' or '', mw, |
649 num % sp == 0 and ' special' or '', mw, |
644 (num%st and ' ' or num)) + line |
650 (num % st and ' ' or num)) + line |
645 num += 1 |
651 num += 1 |
646 else: |
652 else: |
647 for t, line in lines: |
653 for t, line in lines: |
648 yield 1, '<span class="lineno">%*s </span>' % ( |
654 yield 1, '<span class="lineno">%*s </span>' % ( |
649 mw, (num%st and ' ' or num)) + line |
655 mw, (num % st and ' ' or num)) + line |
650 num += 1 |
656 num += 1 |
651 |
657 |
652 def _wrap_lineanchors(self, inner): |
658 def _wrap_lineanchors(self, inner): |
653 s = self.lineanchors |
659 s = self.lineanchors |
654 i = self.linenostart - 1 # subtract 1 since we have to increment i |
660 # subtract 1 since we have to increment i *before* yielding |
655 # *before* yielding |
661 i = self.linenostart - 1 |
656 for t, line in inner: |
662 for t, line in inner: |
657 if t: |
663 if t: |
658 i += 1 |
664 i += 1 |
659 yield 1, '<a name="%s-%d"></a>' % (s, i) + line |
665 yield 1, '<a name="%s-%d"></a>' % (s, i) + line |
660 else: |
666 else: |
671 yield 0, line |
677 yield 0, line |
672 |
678 |
673 def _wrap_div(self, inner): |
679 def _wrap_div(self, inner): |
674 style = [] |
680 style = [] |
675 if (self.noclasses and not self.nobackground and |
681 if (self.noclasses and not self.nobackground and |
676 self.style.background_color is not None): |
682 self.style.background_color is not None): |
677 style.append('background: %s' % (self.style.background_color,)) |
683 style.append('background: %s' % (self.style.background_color,)) |
678 if self.cssstyles: |
684 if self.cssstyles: |
679 style.append(self.cssstyles) |
685 style.append(self.cssstyles) |
680 style = '; '.join(style) |
686 style = '; '.join(style) |
681 |
687 |
682 yield 0, ('<div' + (self.cssclass and ' class="%s"' % self.cssclass) |
688 yield 0, ('<div' + (self.cssclass and ' class="%s"' % self.cssclass) + |
683 + (style and (' style="%s"' % style)) + '>') |
689 (style and (' style="%s"' % style)) + '>') |
684 for tup in inner: |
690 for tup in inner: |
685 yield tup |
691 yield tup |
686 yield 0, '</div>\n' |
692 yield 0, '</div>\n' |
687 |
693 |
688 def _wrap_pre(self, inner): |
694 def _wrap_pre(self, inner): |
710 c2s = self.class2style |
719 c2s = self.class2style |
711 escape_table = _escape_html_table |
720 escape_table = _escape_html_table |
712 tagsfile = self.tagsfile |
721 tagsfile = self.tagsfile |
713 |
722 |
714 lspan = '' |
723 lspan = '' |
715 line = '' |
724 line = [] |
716 for ttype, value in tokensource: |
725 for ttype, value in tokensource: |
717 if nocls: |
726 if nocls: |
718 cclass = getcls(ttype) |
727 cclass = getcls(ttype) |
719 while cclass is None: |
728 while cclass is None: |
720 ttype = ttype.parent |
729 ttype = ttype.parent |
721 cclass = getcls(ttype) |
730 cclass = getcls(ttype) |
722 cspan = cclass and '<span style="%s">' % c2s[cclass][0] or '' |
731 cspan = cclass and '<span style="%s">' % c2s[cclass][0] or '' |
723 else: |
732 else: |
724 cls = self._get_css_class(ttype) |
733 cls = self._get_css_classes(ttype) |
725 cspan = cls and '<span class="%s">' % cls or '' |
734 cspan = cls and '<span class="%s">' % cls or '' |
726 |
735 |
727 parts = value.translate(escape_table).split('\n') |
736 parts = value.translate(escape_table).split('\n') |
728 |
737 |
729 if tagsfile and ttype in Token.Name: |
738 if tagsfile and ttype in Token.Name: |
741 |
750 |
742 # for all but the last line |
751 # for all but the last line |
743 for part in parts[:-1]: |
752 for part in parts[:-1]: |
744 if line: |
753 if line: |
745 if lspan != cspan: |
754 if lspan != cspan: |
746 line += (lspan and '</span>') + cspan + part + \ |
755 line.extend(((lspan and '</span>'), cspan, part, |
747 (cspan and '</span>') + lsep |
756 (cspan and '</span>'), lsep)) |
748 else: # both are the same |
757 else: # both are the same |
749 line += part + (lspan and '</span>') + lsep |
758 line.extend((part, (lspan and '</span>'), lsep)) |
750 yield 1, line |
759 yield 1, ''.join(line) |
751 line = '' |
760 line = [] |
752 elif part: |
761 elif part: |
753 yield 1, cspan + part + (cspan and '</span>') + lsep |
762 yield 1, ''.join((cspan, part, (cspan and '</span>'), lsep)) |
754 else: |
763 else: |
755 yield 1, lsep |
764 yield 1, lsep |
756 # for the last line |
765 # for the last line |
757 if line and parts[-1]: |
766 if line and parts[-1]: |
758 if lspan != cspan: |
767 if lspan != cspan: |
759 line += (lspan and '</span>') + cspan + parts[-1] |
768 line.extend(((lspan and '</span>'), cspan, parts[-1])) |
760 lspan = cspan |
769 lspan = cspan |
761 else: |
770 else: |
762 line += parts[-1] |
771 line.append(parts[-1]) |
763 elif parts[-1]: |
772 elif parts[-1]: |
764 line = cspan + parts[-1] |
773 line = [cspan, parts[-1]] |
765 lspan = cspan |
774 lspan = cspan |
766 # else we neither have to open a new span nor set lspan |
775 # else we neither have to open a new span nor set lspan |
767 |
776 |
768 if line: |
777 if line: |
769 yield 1, line + (lspan and '</span>') + lsep |
778 line.extend(((lspan and '</span>'), lsep)) |
|
779 yield 1, ''.join(line) |
770 |
780 |
771 def _lookup_ctag(self, token): |
781 def _lookup_ctag(self, token): |
772 entry = ctags.TagEntry() |
782 entry = ctags.TagEntry() |
773 if self._ctags.find(entry, token, 0): |
783 if self._ctags.find(entry, token, 0): |
774 return entry['file'], entry['lineNumber'] |
784 return entry['file'], entry['lineNumber'] |
783 hls = self.hl_lines |
793 hls = self.hl_lines |
784 |
794 |
785 for i, (t, value) in enumerate(tokensource): |
795 for i, (t, value) in enumerate(tokensource): |
786 if t != 1: |
796 if t != 1: |
787 yield t, value |
797 yield t, value |
788 if i + 1 in hls: # i + 1 because Python indexes start at 0 |
798 if i + 1 in hls: # i + 1 because Python indexes start at 0 |
789 if self.noclasses: |
799 if self.noclasses: |
790 style = '' |
800 style = '' |
791 if self.style.highlight_color is not None: |
801 if self.style.highlight_color is not None: |
792 style = (' style="background-color: %s"' % |
802 style = (' style="background-color: %s"' % |
793 (self.style.highlight_color,)) |
803 (self.style.highlight_color,)) |