Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pygments/formatters/html.py: 14%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

420 statements  

1""" 

2 pygments.formatters.html 

3 ~~~~~~~~~~~~~~~~~~~~~~~~ 

4 

5 Formatter for HTML output. 

6 

7 :copyright: Copyright 2006-2025 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 

21try: 

22 import ctags 

23except ImportError: 

24 ctags = None 

25 

26__all__ = ['HtmlFormatter'] 

27 

28 

29_escape_html_table = { 

30 ord('&'): '&', 

31 ord('<'): '&lt;', 

32 ord('>'): '&gt;', 

33 ord('"'): '&quot;', 

34 ord("'"): '&#39;', 

35} 

36 

37 

38def escape_html(text, table=_escape_html_table): 

39 """Escape &, <, > as well as single and double quotes for HTML.""" 

40 return text.translate(table) 

41 

42 

43def webify(color): 

44 if color.startswith('calc') or color.startswith('var'): 

45 return color 

46 else: 

47 # Check if the color can be shortened from 6 to 3 characters 

48 color = color.upper() 

49 if (len(color) == 6 and 

50 ( color[0] == color[1] 

51 and color[2] == color[3] 

52 and color[4] == color[5])): 

53 return f'#{color[0]}{color[2]}{color[4]}' 

54 else: 

55 return f'#{color}' 

56 

57 

58def _get_ttype_class(ttype): 

59 fname = STANDARD_TYPES.get(ttype) 

60 if fname: 

61 return fname 

62 aname = '' 

63 while fname is None: 

64 aname = '-' + ttype[-1] + aname 

65 ttype = ttype.parent 

66 fname = STANDARD_TYPES.get(ttype) 

67 return fname + aname 

68 

69 

70CSSFILE_TEMPLATE = '''\ 

71/* 

72generated by Pygments <https://pygments.org/> 

73Copyright 2006-2025 by the Pygments team. 

74Licensed under the BSD license, see LICENSE for details. 

75*/ 

76%(styledefs)s 

77''' 

78 

79DOC_HEADER = '''\ 

80<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" 

81 "http://www.w3.org/TR/html4/strict.dtd"> 

82<!-- 

83generated by Pygments <https://pygments.org/> 

84Copyright 2006-2025 by the Pygments team. 

85Licensed under the BSD license, see LICENSE for details. 

86--> 

87<html> 

88<head> 

89 <title>%(title)s</title> 

90 <meta http-equiv="content-type" content="text/html; charset=%(encoding)s"> 

91 <style type="text/css"> 

92''' + CSSFILE_TEMPLATE + ''' 

93 </style> 

94</head> 

95<body> 

96<h2>%(title)s</h2> 

97 

98''' 

99 

100DOC_HEADER_EXTERNALCSS = '''\ 

101<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" 

102 "http://www.w3.org/TR/html4/strict.dtd"> 

103 

104<html> 

105<head> 

106 <title>%(title)s</title> 

107 <meta http-equiv="content-type" content="text/html; charset=%(encoding)s"> 

108 <link rel="stylesheet" href="%(cssfile)s" type="text/css"> 

109</head> 

110<body> 

111<h2>%(title)s</h2> 

112 

113''' 

114 

115DOC_FOOTER = '''\ 

116</body> 

117</html> 

118''' 

119 

120 

121class HtmlFormatter(Formatter): 

122 r""" 

123 Format tokens as HTML 4 ``<span>`` tags. By default, the content is enclosed 

124 in a ``<pre>`` tag, itself wrapped in a ``<div>`` tag (but see the `nowrap` option). 

125 The ``<div>``'s CSS class can be set by the `cssclass` option. 

126 

127 If the `linenos` option is set to ``"table"``, the ``<pre>`` is 

128 additionally wrapped inside a ``<table>`` which has one row and two 

129 cells: one containing the line numbers and one containing the code. 

130 Example: 

131 

132 .. sourcecode:: html 

133 

134 <div class="highlight" > 

135 <table><tr> 

136 <td class="linenos" title="click to toggle" 

137 onclick="with (this.firstChild.style) 

138 { display = (display == '') ? 'none' : '' }"> 

139 <pre>1 

140 2</pre> 

141 </td> 

142 <td class="code"> 

143 <pre><span class="Ke">def </span><span class="NaFu">foo</span>(bar): 

144 <span class="Ke">pass</span> 

145 </pre> 

146 </td> 

147 </tr></table></div> 

148 

149 (whitespace added to improve clarity). 

150 

151 A list of lines can be specified using the `hl_lines` option to make these 

152 lines highlighted (as of Pygments 0.11). 

153 

154 With the `full` option, a complete HTML 4 document is output, including 

155 the style definitions inside a ``<style>`` tag, or in a separate file if 

156 the `cssfile` option is given. 

157 

158 When `tagsfile` is set to the path of a ctags index file, it is used to 

159 generate hyperlinks from names to their definition. You must enable 

160 `lineanchors` and run ctags with the `-n` option for this to work. The 

161 `python-ctags` module from PyPI must be installed to use this feature; 

162 otherwise a `RuntimeError` will be raised. 

163 

164 The `get_style_defs(arg='')` method of a `HtmlFormatter` returns a string 

165 containing CSS rules for the CSS classes used by the formatter. The 

166 argument `arg` can be used to specify additional CSS selectors that 

167 are prepended to the classes. A call `fmter.get_style_defs('td .code')` 

168 would result in the following CSS classes: 

169 

170 .. sourcecode:: css 

171 

172 td .code .kw { font-weight: bold; color: #00FF00 } 

173 td .code .cm { color: #999999 } 

174 ... 

175 

176 If you have Pygments 0.6 or higher, you can also pass a list or tuple to the 

177 `get_style_defs()` method to request multiple prefixes for the tokens: 

178 

179 .. sourcecode:: python 

180 

181 formatter.get_style_defs(['div.syntax pre', 'pre.syntax']) 

182 

183 The output would then look like this: 

184 

185 .. sourcecode:: css 

186 

187 div.syntax pre .kw, 

188 pre.syntax .kw { font-weight: bold; color: #00FF00 } 

189 div.syntax pre .cm, 

190 pre.syntax .cm { color: #999999 } 

191 ... 

192 

193 Additional options accepted: 

194 

195 `nowrap` 

196 If set to ``True``, don't add a ``<pre>`` and a ``<div>`` tag 

197 around the tokens. This disables most other options (default: ``False``). 

198 

199 `full` 

200 Tells the formatter to output a "full" document, i.e. a complete 

201 self-contained document (default: ``False``). 

202 

203 `title` 

204 If `full` is true, the title that should be used to caption the 

205 document (default: ``''``). 

206 

207 `style` 

208 The style to use, can be a string or a Style subclass (default: 

209 ``'default'``). This option has no effect if the `cssfile` 

210 and `noclobber_cssfile` option are given and the file specified in 

211 `cssfile` exists. 

212 

213 `noclasses` 

214 If set to true, token ``<span>`` tags (as well as line number elements) 

215 will not use CSS classes, but inline styles. This is not recommended 

216 for larger pieces of code since it increases output size by quite a bit 

217 (default: ``False``). 

218 

219 `classprefix` 

220 Since the token types use relatively short class names, they may clash 

221 with some of your own class names. In this case you can use the 

222 `classprefix` option to give a string to prepend to all Pygments-generated 

223 CSS class names for token types. 

224 Note that this option also affects the output of `get_style_defs()`. 

225 

226 `cssclass` 

227 CSS class for the wrapping ``<div>`` tag (default: ``'highlight'``). 

228 If you set this option, the default selector for `get_style_defs()` 

229 will be this class. 

230 

231 .. versionadded:: 0.9 

232 If you select the ``'table'`` line numbers, the wrapping table will 

233 have a CSS class of this string plus ``'table'``, the default is 

234 accordingly ``'highlighttable'``. 

235 

236 `cssstyles` 

237 Inline CSS styles for the wrapping ``<div>`` tag (default: ``''``). 

238 

239 `prestyles` 

240 Inline CSS styles for the ``<pre>`` tag (default: ``''``). 

241 

242 .. versionadded:: 0.11 

243 

244 `cssfile` 

245 If the `full` option is true and this option is given, it must be the 

246 name of an external file. If the filename does not include an absolute 

247 path, the file's path will be assumed to be relative to the main output 

248 file's path, if the latter can be found. The stylesheet is then written 

249 to this file instead of the HTML file. 

250 

251 .. versionadded:: 0.6 

252 

253 `noclobber_cssfile` 

254 If `cssfile` is given and the specified file exists, the css file will 

255 not be overwritten. This allows the use of the `full` option in 

256 combination with a user specified css file. Default is ``False``. 

257 

258 .. versionadded:: 1.1 

259 

260 `linenos` 

261 If set to ``'table'``, output line numbers as a table with two cells, 

262 one containing the line numbers, the other the whole code. This is 

263 copy-and-paste-friendly, but may cause alignment problems with some 

264 browsers or fonts. If set to ``'inline'``, the line numbers will be 

265 integrated in the ``<pre>`` tag that contains the code (that setting 

266 is *new in Pygments 0.8*). 

267 

268 For compatibility with Pygments 0.7 and earlier, every true value 

269 except ``'inline'`` means the same as ``'table'`` (in particular, that 

270 means also ``True``). 

271 

272 The default value is ``False``, which means no line numbers at all. 

273 

274 **Note:** with the default ("table") line number mechanism, the line 

275 numbers and code can have different line heights in Internet Explorer 

276 unless you give the enclosing ``<pre>`` tags an explicit ``line-height`` 

277 CSS property (you get the default line spacing with ``line-height: 

278 125%``). 

279 

280 `hl_lines` 

281 Specify a list of lines to be highlighted. The line numbers are always 

282 relative to the input (i.e. the first line is line 1) and are 

283 independent of `linenostart`. 

284 

285 .. versionadded:: 0.11 

286 

287 `linenostart` 

288 The line number for the first line (default: ``1``). 

289 

290 `linenostep` 

291 If set to a number n > 1, only every nth line number is printed. 

292 

293 `linenospecial` 

294 If set to a number n > 0, every nth line number is given the CSS 

295 class ``"special"`` (default: ``0``). 

296 

297 `nobackground` 

298 If set to ``True``, the formatter won't output the background color 

299 for the wrapping element (this automatically defaults to ``False`` 

300 when there is no wrapping element [eg: no argument for the 

301 `get_syntax_defs` method given]) (default: ``False``). 

302 

303 .. versionadded:: 0.6 

304 

305 `lineseparator` 

306 This string is output between lines of code. It defaults to ``"\n"``, 

307 which is enough to break a line inside ``<pre>`` tags, but you can 

308 e.g. set it to ``"<br>"`` to get HTML line breaks. 

309 

310 .. versionadded:: 0.7 

311 

312 `lineanchors` 

313 If set to a nonempty string, e.g. ``foo``, the formatter will wrap each 

314 output line in an anchor tag with an ``id`` (and `name`) of ``foo-linenumber``. 

315 This allows easy linking to certain lines. 

316 

317 .. versionadded:: 0.9 

318 

319 `linespans` 

320 If set to a nonempty string, e.g. ``foo``, the formatter will wrap each 

321 output line in a span tag with an ``id`` of ``foo-linenumber``. 

322 This allows easy access to lines via javascript. 

323 

324 .. versionadded:: 1.6 

325 

326 `anchorlinenos` 

327 If set to `True`, will wrap line numbers in <a> tags. Used in 

328 combination with `linenos` and `lineanchors`. 

329 

330 `tagsfile` 

331 If set to the path of a ctags file, wrap names in anchor tags that 

332 link to their definitions. `lineanchors` should be used, and the 

333 tags file should specify line numbers (see the `-n` option to ctags). 

334 The tags file is assumed to be encoded in UTF-8. 

335 

336 .. versionadded:: 1.6 

337 

338 `tagurlformat` 

339 A string formatting pattern used to generate links to ctags definitions. 

340 Available variables are `%(path)s`, `%(fname)s` and `%(fext)s`. 

341 Defaults to an empty string, resulting in just `#prefix-number` links. 

342 

343 .. versionadded:: 1.6 

344 

345 `filename` 

346 A string used to generate a filename when rendering ``<pre>`` blocks, 

347 for example if displaying source code. If `linenos` is set to 

348 ``'table'`` then the filename will be rendered in an initial row 

349 containing a single `<th>` which spans both columns. 

350 

351 .. versionadded:: 2.1 

352 

353 `wrapcode` 

354 Wrap the code inside ``<pre>`` blocks using ``<code>``, as recommended 

355 by the HTML5 specification. 

356 

357 .. versionadded:: 2.4 

358 

359 `debug_token_types` 

360 Add ``title`` attributes to all token ``<span>`` tags that show the 

361 name of the token. 

362 

363 .. versionadded:: 2.10 

364 

365 

366 **Subclassing the HTML formatter** 

367 

368 .. versionadded:: 0.7 

369 

370 The HTML formatter is now built in a way that allows easy subclassing, thus 

371 customizing the output HTML code. The `format()` method calls 

372 `self._format_lines()` which returns a generator that yields tuples of ``(1, 

373 line)``, where the ``1`` indicates that the ``line`` is a line of the 

374 formatted source code. 

375 

376 If the `nowrap` option is set, the generator is the iterated over and the 

377 resulting HTML is output. 

378 

379 Otherwise, `format()` calls `self.wrap()`, which wraps the generator with 

380 other generators. These may add some HTML code to the one generated by 

381 `_format_lines()`, either by modifying the lines generated by the latter, 

382 then yielding them again with ``(1, line)``, and/or by yielding other HTML 

383 code before or after the lines, with ``(0, html)``. The distinction between 

384 source lines and other code makes it possible to wrap the generator multiple 

385 times. 

386 

387 The default `wrap()` implementation adds a ``<div>`` and a ``<pre>`` tag. 

388 

389 A custom `HtmlFormatter` subclass could look like this: 

390 

391 .. sourcecode:: python 

392 

393 class CodeHtmlFormatter(HtmlFormatter): 

394 

395 def wrap(self, source, *, include_div): 

396 return self._wrap_code(source) 

397 

398 def _wrap_code(self, source): 

399 yield 0, '<code>' 

400 for i, t in source: 

401 if i == 1: 

402 # it's a line of formatted code 

403 t += '<br>' 

404 yield i, t 

405 yield 0, '</code>' 

406 

407 This results in wrapping the formatted lines with a ``<code>`` tag, where the 

408 source lines are broken using ``<br>`` tags. 

409 

410 After calling `wrap()`, the `format()` method also adds the "line numbers" 

411 and/or "full document" wrappers if the respective options are set. Then, all 

412 HTML yielded by the wrapped generator is output. 

413 """ 

414 

415 name = 'HTML' 

416 aliases = ['html'] 

417 filenames = ['*.html', '*.htm'] 

418 

419 def __init__(self, **options): 

420 Formatter.__init__(self, **options) 

421 self.title = self._decodeifneeded(self.title) 

422 self.nowrap = get_bool_opt(options, 'nowrap', False) 

423 self.noclasses = get_bool_opt(options, 'noclasses', False) 

424 self.classprefix = options.get('classprefix', '') 

425 self.cssclass = self._decodeifneeded(options.get('cssclass', 'highlight')) 

426 self.cssstyles = self._decodeifneeded(options.get('cssstyles', '')) 

427 self.prestyles = self._decodeifneeded(options.get('prestyles', '')) 

428 self.cssfile = self._decodeifneeded(options.get('cssfile', '')) 

429 self.noclobber_cssfile = get_bool_opt(options, 'noclobber_cssfile', False) 

430 self.tagsfile = self._decodeifneeded(options.get('tagsfile', '')) 

431 self.tagurlformat = self._decodeifneeded(options.get('tagurlformat', '')) 

432 self.filename = self._decodeifneeded(options.get('filename', '')) 

433 self.wrapcode = get_bool_opt(options, 'wrapcode', False) 

434 self.span_element_openers = {} 

435 self.debug_token_types = get_bool_opt(options, 'debug_token_types', False) 

436 

437 if self.tagsfile: 

438 if not ctags: 

439 raise RuntimeError('The "ctags" package must to be installed ' 

440 'to be able to use the "tagsfile" feature.') 

441 self._ctags = ctags.CTags(self.tagsfile) 

442 

443 linenos = options.get('linenos', False) 

444 if linenos == 'inline': 

445 self.linenos = 2 

446 elif linenos: 

447 # compatibility with <= 0.7 

448 self.linenos = 1 

449 else: 

450 self.linenos = 0 

451 self.linenostart = abs(get_int_opt(options, 'linenostart', 1)) 

452 self.linenostep = abs(get_int_opt(options, 'linenostep', 1)) 

453 self.linenospecial = abs(get_int_opt(options, 'linenospecial', 0)) 

454 self.nobackground = get_bool_opt(options, 'nobackground', False) 

455 self.lineseparator = options.get('lineseparator', '\n') 

456 self.lineanchors = options.get('lineanchors', '') 

457 self.linespans = options.get('linespans', '') 

458 self.anchorlinenos = get_bool_opt(options, 'anchorlinenos', False) 

459 self.hl_lines = set() 

460 for lineno in get_list_opt(options, 'hl_lines', []): 

461 try: 

462 self.hl_lines.add(int(lineno)) 

463 except ValueError: 

464 pass 

465 

466 self._create_stylesheet() 

467 

468 def _get_css_class(self, ttype): 

469 """Return the css class of this token type prefixed with 

470 the classprefix option.""" 

471 ttypeclass = _get_ttype_class(ttype) 

472 if ttypeclass: 

473 return self.classprefix + ttypeclass 

474 return '' 

475 

476 def _get_css_classes(self, ttype): 

477 """Return the CSS classes of this token type prefixed with the classprefix option.""" 

478 cls = self._get_css_class(ttype) 

479 while ttype not in STANDARD_TYPES: 

480 ttype = ttype.parent 

481 cls = self._get_css_class(ttype) + ' ' + cls 

482 return cls or '' 

483 

484 def _get_css_inline_styles(self, ttype): 

485 """Return the inline CSS styles for this token type.""" 

486 cclass = self.ttype2class.get(ttype) 

487 while cclass is None: 

488 ttype = ttype.parent 

489 cclass = self.ttype2class.get(ttype) 

490 return cclass or '' 

491 

492 def _create_stylesheet(self): 

493 t2c = self.ttype2class = {Token: ''} 

494 c2s = self.class2style = {} 

495 for ttype, ndef in self.style: 

496 name = self._get_css_class(ttype) 

497 style = '' 

498 if ndef['color']: 

499 style += 'color: {}; '.format(webify(ndef['color'])) 

500 if ndef['bold']: 

501 style += 'font-weight: bold; ' 

502 if ndef['italic']: 

503 style += 'font-style: italic; ' 

504 if ndef['underline']: 

505 style += 'text-decoration: underline; ' 

506 if ndef['bgcolor']: 

507 style += 'background-color: {}; '.format(webify(ndef['bgcolor'])) 

508 if ndef['border']: 

509 style += 'border: 1px solid {}; '.format(webify(ndef['border'])) 

510 if style: 

511 t2c[ttype] = name 

512 # save len(ttype) to enable ordering the styles by 

513 # hierarchy (necessary for CSS cascading rules!) 

514 c2s[name] = (style[:-2], ttype, len(ttype)) 

515 

516 def get_style_defs(self, arg=None): 

517 """ 

518 Return CSS style definitions for the classes produced by the current 

519 highlighting style. ``arg`` can be a string or list of selectors to 

520 insert before the token type classes. 

521 """ 

522 style_lines = [] 

523 

524 style_lines.extend(self.get_linenos_style_defs()) 

525 style_lines.extend(self.get_background_style_defs(arg)) 

526 style_lines.extend(self.get_token_style_defs(arg)) 

527 

528 return '\n'.join(style_lines) 

529 

530 def get_token_style_defs(self, arg=None): 

531 prefix = self.get_css_prefix(arg) 

532 

533 styles = [ 

534 (level, ttype, cls, style) 

535 for cls, (style, ttype, level) in self.class2style.items() 

536 if cls and style 

537 ] 

538 styles.sort() 

539 

540 lines = [ 

541 f'{prefix(cls)} {{ {style} }} /* {repr(ttype)[6:]} */' 

542 for (level, ttype, cls, style) in styles 

543 ] 

544 

545 return lines 

546 

547 def get_background_style_defs(self, arg=None): 

548 prefix = self.get_css_prefix(arg) 

549 bg_color = self.style.background_color 

550 hl_color = self.style.highlight_color 

551 

552 lines = [] 

553 

554 if arg and not self.nobackground and bg_color is not None: 

555 text_style = '' 

556 if Text in self.ttype2class: 

557 text_style = ' ' + self.class2style[self.ttype2class[Text]][0] 

558 lines.insert( 

559 0, '{}{{ background: {};{} }}'.format( 

560 prefix(''), bg_color, text_style 

561 ) 

562 ) 

563 if hl_color is not None: 

564 lines.insert( 

565 0, '{} {{ background-color: {} }}'.format(prefix('hll'), hl_color) 

566 ) 

567 

568 return lines 

569 

570 def get_linenos_style_defs(self): 

571 lines = [ 

572 f'pre {{ {self._pre_style} }}', 

573 f'td.linenos .normal {{ {self._linenos_style} }}', 

574 f'span.linenos {{ {self._linenos_style} }}', 

575 f'td.linenos .special {{ {self._linenos_special_style} }}', 

576 f'span.linenos.special {{ {self._linenos_special_style} }}', 

577 ] 

578 

579 return lines 

580 

581 def get_css_prefix(self, arg): 

582 if arg is None: 

583 arg = ('cssclass' in self.options and '.'+self.cssclass or '') 

584 if isinstance(arg, str): 

585 args = [arg] 

586 else: 

587 args = list(arg) 

588 

589 def prefix(cls): 

590 if cls: 

591 cls = '.' + cls 

592 tmp = [] 

593 for arg in args: 

594 tmp.append((arg and arg + ' ' or '') + cls) 

595 return ', '.join(tmp) 

596 

597 return prefix 

598 

599 @property 

600 def _pre_style(self): 

601 return 'line-height: 125%;' 

602 

603 @property 

604 def _linenos_style(self): 

605 color = self.style.line_number_color 

606 background_color = self.style.line_number_background_color 

607 return f'color: {color}; background-color: {background_color}; padding-left: 5px; padding-right: 5px;' 

608 

609 @property 

610 def _linenos_special_style(self): 

611 color = self.style.line_number_special_color 

612 background_color = self.style.line_number_special_background_color 

613 return f'color: {color}; background-color: {background_color}; padding-left: 5px; padding-right: 5px;' 

614 

615 def _decodeifneeded(self, value): 

616 if isinstance(value, bytes): 

617 if self.encoding: 

618 return value.decode(self.encoding) 

619 return value.decode() 

620 return value 

621 

622 def _wrap_full(self, inner, outfile): 

623 if self.cssfile: 

624 if os.path.isabs(self.cssfile): 

625 # it's an absolute filename 

626 cssfilename = self.cssfile 

627 else: 

628 try: 

629 filename = outfile.name 

630 if not filename or filename[0] == '<': 

631 # pseudo files, e.g. name == '<fdopen>' 

632 raise AttributeError 

633 cssfilename = os.path.join(os.path.dirname(filename), 

634 self.cssfile) 

635 except AttributeError: 

636 print('Note: Cannot determine output file name, ' 

637 'using current directory as base for the CSS file name', 

638 file=sys.stderr) 

639 cssfilename = self.cssfile 

640 # write CSS file only if noclobber_cssfile isn't given as an option. 

641 try: 

642 if not os.path.exists(cssfilename) or not self.noclobber_cssfile: 

643 with open(cssfilename, "w", encoding="utf-8") as cf: 

644 cf.write(CSSFILE_TEMPLATE % 

645 {'styledefs': self.get_style_defs('body')}) 

646 except OSError as err: 

647 err.strerror = 'Error writing CSS file: ' + err.strerror 

648 raise 

649 

650 yield 0, (DOC_HEADER_EXTERNALCSS % 

651 dict(title=self.title, 

652 cssfile=self.cssfile, 

653 encoding=self.encoding)) 

654 else: 

655 yield 0, (DOC_HEADER % 

656 dict(title=self.title, 

657 styledefs=self.get_style_defs('body'), 

658 encoding=self.encoding)) 

659 

660 yield from inner 

661 yield 0, DOC_FOOTER 

662 

663 def _wrap_tablelinenos(self, inner): 

664 dummyoutfile = StringIO() 

665 lncount = 0 

666 for t, line in inner: 

667 if t: 

668 lncount += 1 

669 dummyoutfile.write(line) 

670 

671 fl = self.linenostart 

672 mw = len(str(lncount + fl - 1)) 

673 sp = self.linenospecial 

674 st = self.linenostep 

675 anchor_name = self.lineanchors or self.linespans 

676 aln = self.anchorlinenos 

677 nocls = self.noclasses 

678 

679 lines = [] 

680 

681 for i in range(fl, fl+lncount): 

682 print_line = i % st == 0 

683 special_line = sp and i % sp == 0 

684 

685 if print_line: 

686 line = '%*d' % (mw, i) 

687 if aln: 

688 line = '<a href="#%s-%d">%s</a>' % (anchor_name, i, line) 

689 else: 

690 line = ' ' * mw 

691 

692 if nocls: 

693 if special_line: 

694 style = f' style="{self._linenos_special_style}"' 

695 else: 

696 style = f' style="{self._linenos_style}"' 

697 else: 

698 if special_line: 

699 style = ' class="special"' 

700 else: 

701 style = ' class="normal"' 

702 

703 if style: 

704 line = f'<span{style}>{line}</span>' 

705 

706 lines.append(line) 

707 

708 ls = '\n'.join(lines) 

709 

710 # If a filename was specified, we can't put it into the code table as it 

711 # would misalign the line numbers. Hence we emit a separate row for it. 

712 filename_tr = "" 

713 if self.filename: 

714 filename_tr = ( 

715 '<tr><th colspan="2" class="filename">' 

716 '<span class="filename">' + self.filename + '</span>' 

717 '</th></tr>') 

718 

719 # in case you wonder about the seemingly redundant <div> here: since the 

720 # content in the other cell also is wrapped in a div, some browsers in 

721 # some configurations seem to mess up the formatting... 

722 yield 0, (f'<table class="{self.cssclass}table">' + filename_tr + 

723 '<tr><td class="linenos"><div class="linenodiv"><pre>' + 

724 ls + '</pre></div></td><td class="code">') 

725 yield 0, '<div>' 

726 yield 0, dummyoutfile.getvalue() 

727 yield 0, '</div>' 

728 yield 0, '</td></tr></table>' 

729 

730 

731 def _wrap_inlinelinenos(self, inner): 

732 # need a list of lines since we need the width of a single number :( 

733 inner_lines = list(inner) 

734 sp = self.linenospecial 

735 st = self.linenostep 

736 num = self.linenostart 

737 mw = len(str(len(inner_lines) + num - 1)) 

738 anchor_name = self.lineanchors or self.linespans 

739 aln = self.anchorlinenos 

740 nocls = self.noclasses 

741 

742 for _, inner_line in inner_lines: 

743 print_line = num % st == 0 

744 special_line = sp and num % sp == 0 

745 

746 if print_line: 

747 line = '%*d' % (mw, num) 

748 else: 

749 line = ' ' * mw 

750 

751 if nocls: 

752 if special_line: 

753 style = f' style="{self._linenos_special_style}"' 

754 else: 

755 style = f' style="{self._linenos_style}"' 

756 else: 

757 if special_line: 

758 style = ' class="linenos special"' 

759 else: 

760 style = ' class="linenos"' 

761 

762 if style: 

763 linenos = f'<span{style}>{line}</span>' 

764 else: 

765 linenos = line 

766 

767 if aln: 

768 yield 1, ('<a href="#%s-%d">%s</a>' % (anchor_name, num, linenos) + 

769 inner_line) 

770 else: 

771 yield 1, linenos + inner_line 

772 num += 1 

773 

774 def _wrap_lineanchors(self, inner): 

775 s = self.lineanchors 

776 # subtract 1 since we have to increment i *before* yielding 

777 i = self.linenostart - 1 

778 for t, line in inner: 

779 if t: 

780 i += 1 

781 href = "" if self.linenos else ' href="#%s-%d"' % (s, i) 

782 yield 1, '<a id="%s-%d" name="%s-%d"%s></a>' % (s, i, s, i, href) + line 

783 else: 

784 yield 0, line 

785 

786 def _wrap_linespans(self, inner): 

787 s = self.linespans 

788 i = self.linenostart - 1 

789 for t, line in inner: 

790 if t: 

791 i += 1 

792 yield 1, '<span id="%s-%d">%s</span>' % (s, i, line) 

793 else: 

794 yield 0, line 

795 

796 def _wrap_div(self, inner): 

797 style = [] 

798 if (self.noclasses and not self.nobackground and 

799 self.style.background_color is not None): 

800 style.append(f'background: {self.style.background_color}') 

801 if self.cssstyles: 

802 style.append(self.cssstyles) 

803 style = '; '.join(style) 

804 

805 yield 0, ('<div' + (self.cssclass and f' class="{self.cssclass}"') + 

806 (style and (f' style="{style}"')) + '>') 

807 yield from inner 

808 yield 0, '</div>\n' 

809 

810 def _wrap_pre(self, inner): 

811 style = [] 

812 if self.prestyles: 

813 style.append(self.prestyles) 

814 if self.noclasses: 

815 style.append(self._pre_style) 

816 style = '; '.join(style) 

817 

818 if self.filename and self.linenos != 1: 

819 yield 0, ('<span class="filename">' + self.filename + '</span>') 

820 

821 # the empty span here is to keep leading empty lines from being 

822 # ignored by HTML parsers 

823 yield 0, ('<pre' + (style and f' style="{style}"') + '><span></span>') 

824 yield from inner 

825 yield 0, '</pre>' 

826 

827 def _wrap_code(self, inner): 

828 yield 0, '<code>' 

829 yield from inner 

830 yield 0, '</code>' 

831 

832 @functools.lru_cache(maxsize=100) 

833 def _translate_parts(self, value): 

834 """HTML-escape a value and split it by newlines.""" 

835 return value.translate(_escape_html_table).split('\n') 

836 

837 def _format_lines(self, tokensource): 

838 """ 

839 Just format the tokens, without any wrapping tags. 

840 Yield individual lines. 

841 """ 

842 nocls = self.noclasses 

843 lsep = self.lineseparator 

844 tagsfile = self.tagsfile 

845 

846 lspan = '' 

847 line = [] 

848 for ttype, value in tokensource: 

849 try: 

850 cspan = self.span_element_openers[ttype] 

851 except KeyError: 

852 title = ' title="{}"'.format('.'.join(ttype)) if self.debug_token_types else '' 

853 if nocls: 

854 css_style = self._get_css_inline_styles(ttype) 

855 if css_style: 

856 css_style = self.class2style[css_style][0] 

857 cspan = f'<span style="{css_style}"{title}>' 

858 else: 

859 cspan = '' 

860 else: 

861 css_class = self._get_css_classes(ttype) 

862 if css_class: 

863 cspan = f'<span class="{css_class}"{title}>' 

864 else: 

865 cspan = '' 

866 self.span_element_openers[ttype] = cspan 

867 

868 parts = self._translate_parts(value) 

869 

870 if tagsfile and ttype in Token.Name: 

871 filename, linenumber = self._lookup_ctag(value) 

872 if linenumber: 

873 base, filename = os.path.split(filename) 

874 if base: 

875 base += '/' 

876 filename, extension = os.path.splitext(filename) 

877 url = self.tagurlformat % {'path': base, 'fname': filename, 

878 'fext': extension} 

879 parts[0] = "<a href=\"%s#%s-%d\">%s" % \ 

880 (url, self.lineanchors, linenumber, parts[0]) 

881 parts[-1] = parts[-1] + "</a>" 

882 

883 # for all but the last line 

884 for part in parts[:-1]: 

885 if line: 

886 # Also check for part being non-empty, so we avoid creating 

887 # empty <span> tags 

888 if lspan != cspan and part: 

889 line.extend(((lspan and '</span>'), cspan, part, 

890 (cspan and '</span>'), lsep)) 

891 else: # both are the same, or the current part was empty 

892 line.extend((part, (lspan and '</span>'), lsep)) 

893 yield 1, ''.join(line) 

894 line = [] 

895 elif part: 

896 yield 1, ''.join((cspan, part, (cspan and '</span>'), lsep)) 

897 else: 

898 yield 1, lsep 

899 # for the last line 

900 if line and parts[-1]: 

901 if lspan != cspan: 

902 line.extend(((lspan and '</span>'), cspan, parts[-1])) 

903 lspan = cspan 

904 else: 

905 line.append(parts[-1]) 

906 elif parts[-1]: 

907 line = [cspan, parts[-1]] 

908 lspan = cspan 

909 # else we neither have to open a new span nor set lspan 

910 

911 if line: 

912 line.extend(((lspan and '</span>'), lsep)) 

913 yield 1, ''.join(line) 

914 

915 def _lookup_ctag(self, token): 

916 entry = ctags.TagEntry() 

917 if self._ctags.find(entry, token.encode(), 0): 

918 return entry['file'].decode(), entry['lineNumber'] 

919 else: 

920 return None, None 

921 

922 def _highlight_lines(self, tokensource): 

923 """ 

924 Highlighted the lines specified in the `hl_lines` option by 

925 post-processing the token stream coming from `_format_lines`. 

926 """ 

927 hls = self.hl_lines 

928 

929 for i, (t, value) in enumerate(tokensource): 

930 if t != 1: 

931 yield t, value 

932 if i + 1 in hls: # i + 1 because Python indexes start at 0 

933 if self.noclasses: 

934 style = '' 

935 if self.style.highlight_color is not None: 

936 style = (f' style="background-color: {self.style.highlight_color}"') 

937 yield 1, f'<span{style}>{value}</span>' 

938 else: 

939 yield 1, f'<span class="hll">{value}</span>' 

940 else: 

941 yield 1, value 

942 

943 def wrap(self, source): 

944 """ 

945 Wrap the ``source``, which is a generator yielding 

946 individual lines, in custom generators. See docstring 

947 for `format`. Can be overridden. 

948 """ 

949 

950 output = source 

951 if self.wrapcode: 

952 output = self._wrap_code(output) 

953 

954 output = self._wrap_pre(output) 

955 

956 return output 

957 

958 def format_unencoded(self, tokensource, outfile): 

959 """ 

960 The formatting process uses several nested generators; which of 

961 them are used is determined by the user's options. 

962 

963 Each generator should take at least one argument, ``inner``, 

964 and wrap the pieces of text generated by this. 

965 

966 Always yield 2-tuples: (code, text). If "code" is 1, the text 

967 is part of the original tokensource being highlighted, if it's 

968 0, the text is some piece of wrapping. This makes it possible to 

969 use several different wrappers that process the original source 

970 linewise, e.g. line number generators. 

971 """ 

972 source = self._format_lines(tokensource) 

973 

974 # As a special case, we wrap line numbers before line highlighting 

975 # so the line numbers get wrapped in the highlighting tag. 

976 if not self.nowrap and self.linenos == 2: 

977 source = self._wrap_inlinelinenos(source) 

978 

979 if self.hl_lines: 

980 source = self._highlight_lines(source) 

981 

982 if not self.nowrap: 

983 if self.lineanchors: 

984 source = self._wrap_lineanchors(source) 

985 if self.linespans: 

986 source = self._wrap_linespans(source) 

987 source = self.wrap(source) 

988 if self.linenos == 1: 

989 source = self._wrap_tablelinenos(source) 

990 source = self._wrap_div(source) 

991 if self.full: 

992 source = self._wrap_full(source, outfile) 

993 

994 for t, piece in source: 

995 outfile.write(piece)