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