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

421 statements  

1""" 

2 pygments.formatters.html 

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

4 

5 Formatter for HTML output. 

6 

7 :copyright: Copyright 2006-present by the Pygments team, see AUTHORS. 

8 :license: BSD, see LICENSE for details. 

9""" 

10 

11import functools 

12import os 

13import sys 

14import os.path 

15from io import StringIO 

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 

21import html 

22 

23try: 

24 import ctags 

25except ImportError: 

26 ctags = None 

27 

28__all__ = ['HtmlFormatter'] 

29 

30 

31_escape_html_table = { 

32 ord('&'): '&', 

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

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

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

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

37} 

38 

39 

40def 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 

45def webify(color): 

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

47 return color 

48 else: 

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

50 color = color.upper() 

51 if (len(color) == 6 and 

52 ( color[0] == color[1] 

53 and color[2] == color[3] 

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

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

56 else: 

57 return f'#{color}' 

58 

59 

60def _get_ttype_class(ttype): 

61 fname = STANDARD_TYPES.get(ttype) 

62 if fname: 

63 return fname 

64 aname = '' 

65 while fname is None: 

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

67 ttype = ttype.parent 

68 fname = STANDARD_TYPES.get(ttype) 

69 return fname + aname 

70 

71 

72CSSFILE_TEMPLATE = '''\ 

73/* 

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

75Copyright 2006-present by the Pygments team. 

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

77*/ 

78%(styledefs)s 

79''' 

80 

81DOC_HEADER = '''\ 

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

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

84<!-- 

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

86Copyright 2006-present by the Pygments team. 

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

88--> 

89<html> 

90<head> 

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

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

93 <style type="text/css"> 

94''' + CSSFILE_TEMPLATE + ''' 

95 </style> 

96</head> 

97<body> 

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

99 

100''' 

101 

102DOC_HEADER_EXTERNALCSS = '''\ 

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

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

105 

106<html> 

107<head> 

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

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

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

111</head> 

112<body> 

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

114 

115''' 

116 

117DOC_FOOTER = '''\ 

118</body> 

119</html> 

120''' 

121 

122 

123class HtmlFormatter(Formatter): 

124 r""" 

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

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

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

128 

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

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

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

132 Example: 

133 

134 .. sourcecode:: html 

135 

136 <div class="highlight" > 

137 <table><tr> 

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

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

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

141 <pre>1 

142 2</pre> 

143 </td> 

144 <td class="code"> 

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

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

147 </pre> 

148 </td> 

149 </tr></table></div> 

150 

151 (whitespace added to improve clarity). 

152 

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

154 lines highlighted (as of Pygments 0.11). 

155 

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

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

158 the `cssfile` option is given. 

159 

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

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

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

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

164 otherwise a `RuntimeError` will be raised. 

165 

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

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

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

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

170 would result in the following CSS classes: 

171 

172 .. sourcecode:: css 

173 

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

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

176 ... 

177 

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

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

180 

181 .. sourcecode:: python 

182 

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

184 

185 The output would then look like this: 

186 

187 .. sourcecode:: css 

188 

189 div.syntax pre .kw, 

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

191 div.syntax pre .cm, 

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

193 ... 

194 

195 Additional options accepted: 

196 

197 `nowrap` 

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

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

200 

201 `full` 

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

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

204 

205 `title` 

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

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

208 

209 `style` 

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

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

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

213 `cssfile` exists. 

214 

215 `noclasses` 

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

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

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

219 (default: ``False``). 

220 

221 `classprefix` 

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

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

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

225 CSS class names for token types. 

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

227 

228 `cssclass` 

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

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

231 will be this class. 

232 

233 .. versionadded:: 0.9 

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

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

236 accordingly ``'highlighttable'``. 

237 

238 `cssstyles` 

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

240 

241 `prestyles` 

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

243 

244 .. versionadded:: 0.11 

245 

246 `cssfile` 

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

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

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

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

251 to this file instead of the HTML file. 

252 

253 .. versionadded:: 0.6 

254 

255 `noclobber_cssfile` 

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

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

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

259 

260 .. versionadded:: 1.1 

261 

262 `linenos` 

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

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

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

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

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

268 is *new in Pygments 0.8*). 

269 

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

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

272 means also ``True``). 

273 

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

275 

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

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

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

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

280 125%``). 

281 

282 `hl_lines` 

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

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

285 independent of `linenostart`. 

286 

287 .. versionadded:: 0.11 

288 

289 `linenostart` 

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

291 

292 `linenostep` 

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

294 

295 `linenospecial` 

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

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

298 

299 `nobackground` 

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

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

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

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

304 

305 .. versionadded:: 0.6 

306 

307 `lineseparator` 

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

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

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

311 

312 .. versionadded:: 0.7 

313 

314 `lineanchors` 

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

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

317 This allows easy linking to certain lines. 

318 

319 .. versionadded:: 0.9 

320 

321 `linespans` 

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

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

324 This allows easy access to lines via javascript. 

325 

326 .. versionadded:: 1.6 

327 

328 `anchorlinenos` 

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

330 combination with `linenos` and `lineanchors`. 

331 

332 `tagsfile` 

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

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

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

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

337 

338 .. versionadded:: 1.6 

339 

340 `tagurlformat` 

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

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

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

344 

345 .. versionadded:: 1.6 

346 

347 `filename` 

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

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

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

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

352 

353 .. versionadded:: 2.1 

354 

355 `wrapcode` 

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

357 by the HTML5 specification. 

358 

359 .. versionadded:: 2.4 

360 

361 `debug_token_types` 

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

363 name of the token. 

364 

365 .. versionadded:: 2.10 

366 

367 

368 **Subclassing the HTML formatter** 

369 

370 .. versionadded:: 0.7 

371 

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

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

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

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

376 formatted source code. 

377 

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

379 resulting HTML is output. 

380 

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

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

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

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

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

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

387 times. 

388 

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

390 

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

392 

393 .. sourcecode:: python 

394 

395 class CodeHtmlFormatter(HtmlFormatter): 

396 

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

398 return self._wrap_code(source) 

399 

400 def _wrap_code(self, source): 

401 yield 0, '<code>' 

402 for i, t in source: 

403 if i == 1: 

404 # it's a line of formatted code 

405 t += '<br>' 

406 yield i, t 

407 yield 0, '</code>' 

408 

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

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

411 

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

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

414 HTML yielded by the wrapped generator is output. 

415 """ 

416 

417 name = 'HTML' 

418 aliases = ['html'] 

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

420 

421 def __init__(self, **options): 

422 Formatter.__init__(self, **options) 

423 self.title = self._decodeifneeded(self.title) 

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

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

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

427 self.cssclass = html.escape(self._decodeifneeded(options.get('cssclass', 'highlight'))) 

428 self.cssstyles = html.escape(self._decodeifneeded(options.get('cssstyles', ''))) 

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

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

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

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

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

434 self.filename = html.escape(self._decodeifneeded(options.get('filename', ''))) 

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

436 self.span_element_openers = {} 

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

438 

439 if self.tagsfile: 

440 if not ctags: 

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

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

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

444 

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

446 if linenos == 'inline': 

447 self.linenos = 2 

448 elif linenos: 

449 # compatibility with <= 0.7 

450 self.linenos = 1 

451 else: 

452 self.linenos = 0 

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

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

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

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

457 self.lineseparator = html.escape(options.get('lineseparator', '\n')) 

458 self.lineanchors = html.escape(options.get('lineanchors', '')) 

459 self.linespans = html.escape(options.get('linespans', '')) 

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

461 self.hl_lines = set() 

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

463 try: 

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

465 except ValueError: 

466 pass 

467 

468 self._create_stylesheet() 

469 

470 def _get_css_class(self, ttype): 

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

472 the classprefix option.""" 

473 ttypeclass = _get_ttype_class(ttype) 

474 if ttypeclass: 

475 return self.classprefix + ttypeclass 

476 return '' 

477 

478 def _get_css_classes(self, ttype): 

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

480 cls = self._get_css_class(ttype) 

481 while ttype not in STANDARD_TYPES: 

482 ttype = ttype.parent 

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

484 return cls or '' 

485 

486 def _get_css_inline_styles(self, ttype): 

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

488 cclass = self.ttype2class.get(ttype) 

489 while cclass is None: 

490 ttype = ttype.parent 

491 cclass = self.ttype2class.get(ttype) 

492 return cclass or '' 

493 

494 def _create_stylesheet(self): 

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

496 c2s = self.class2style = {} 

497 for ttype, ndef in self.style: 

498 name = self._get_css_class(ttype) 

499 style = '' 

500 if ndef['color']: 

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

502 if ndef['bold']: 

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

504 if ndef['italic']: 

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

506 if ndef['underline']: 

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

508 if ndef['bgcolor']: 

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

510 if ndef['border']: 

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

512 if style: 

513 t2c[ttype] = name 

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

515 # hierarchy (necessary for CSS cascading rules!) 

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

517 

518 def get_style_defs(self, arg=None): 

519 """ 

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

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

522 insert before the token type classes. 

523 """ 

524 style_lines = [] 

525 

526 style_lines.extend(self.get_linenos_style_defs()) 

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

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

529 

530 return '\n'.join(style_lines) 

531 

532 def get_token_style_defs(self, arg=None): 

533 prefix = self.get_css_prefix(arg) 

534 

535 styles = [ 

536 (level, ttype, cls, style) 

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

538 if cls and style 

539 ] 

540 styles.sort() 

541 

542 lines = [ 

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

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

545 ] 

546 

547 return lines 

548 

549 def get_background_style_defs(self, arg=None): 

550 prefix = self.get_css_prefix(arg) 

551 bg_color = self.style.background_color 

552 hl_color = self.style.highlight_color 

553 

554 lines = [] 

555 

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

557 text_style = '' 

558 if Text in self.ttype2class: 

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

560 lines.insert( 

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

562 prefix(''), bg_color, text_style 

563 ) 

564 ) 

565 if hl_color is not None: 

566 lines.insert( 

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

568 ) 

569 

570 return lines 

571 

572 def get_linenos_style_defs(self): 

573 lines = [ 

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

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

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

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

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

579 ] 

580 

581 return lines 

582 

583 def get_css_prefix(self, arg): 

584 if arg is None: 

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

586 if isinstance(arg, str): 

587 args = [arg] 

588 else: 

589 args = list(arg) 

590 

591 def prefix(cls): 

592 if cls: 

593 cls = '.' + cls 

594 tmp = [] 

595 for arg in args: 

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

597 return ', '.join(tmp) 

598 

599 return prefix 

600 

601 @property 

602 def _pre_style(self): 

603 return 'line-height: 125%;' 

604 

605 @property 

606 def _linenos_style(self): 

607 color = self.style.line_number_color 

608 background_color = self.style.line_number_background_color 

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

610 

611 @property 

612 def _linenos_special_style(self): 

613 color = self.style.line_number_special_color 

614 background_color = self.style.line_number_special_background_color 

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

616 

617 def _decodeifneeded(self, value): 

618 if isinstance(value, bytes): 

619 if self.encoding: 

620 return value.decode(self.encoding) 

621 return value.decode() 

622 return value 

623 

624 def _wrap_full(self, inner, outfile): 

625 if self.cssfile: 

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

627 # it's an absolute filename 

628 cssfilename = self.cssfile 

629 else: 

630 try: 

631 filename = outfile.name 

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

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

634 raise AttributeError 

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

636 self.cssfile) 

637 except AttributeError: 

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

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

640 file=sys.stderr) 

641 cssfilename = self.cssfile 

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

643 try: 

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

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

646 cf.write(CSSFILE_TEMPLATE % 

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

648 except OSError as err: 

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

650 raise 

651 

652 yield 0, (DOC_HEADER_EXTERNALCSS % 

653 dict(title=self.title, 

654 cssfile=self.cssfile, 

655 encoding=self.encoding)) 

656 else: 

657 yield 0, (DOC_HEADER % 

658 dict(title=self.title, 

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

660 encoding=self.encoding)) 

661 

662 yield from inner 

663 yield 0, DOC_FOOTER 

664 

665 def _wrap_tablelinenos(self, inner): 

666 dummyoutfile = StringIO() 

667 lncount = 0 

668 for t, line in inner: 

669 if t: 

670 lncount += 1 

671 dummyoutfile.write(line) 

672 

673 fl = self.linenostart 

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

675 sp = self.linenospecial 

676 st = self.linenostep 

677 anchor_name = self.lineanchors or self.linespans 

678 aln = self.anchorlinenos 

679 nocls = self.noclasses 

680 

681 lines = [] 

682 

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

684 print_line = i % st == 0 

685 special_line = sp and i % sp == 0 

686 

687 if print_line: 

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

689 if aln: 

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

691 else: 

692 line = ' ' * mw 

693 

694 if nocls: 

695 if special_line: 

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

697 else: 

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

699 else: 

700 if special_line: 

701 style = ' class="special"' 

702 else: 

703 style = ' class="normal"' 

704 

705 if style: 

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

707 

708 lines.append(line) 

709 

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

711 

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

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

714 filename_tr = "" 

715 if self.filename: 

716 filename_tr = ( 

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

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

719 '</th></tr>') 

720 

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

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

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

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

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

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

727 yield 0, '<div>' 

728 yield 0, dummyoutfile.getvalue() 

729 yield 0, '</div>' 

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

731 

732 

733 def _wrap_inlinelinenos(self, inner): 

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

735 inner_lines = list(inner) 

736 sp = self.linenospecial 

737 st = self.linenostep 

738 num = self.linenostart 

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

740 anchor_name = self.lineanchors or self.linespans 

741 aln = self.anchorlinenos 

742 nocls = self.noclasses 

743 

744 for _, inner_line in inner_lines: 

745 print_line = num % st == 0 

746 special_line = sp and num % sp == 0 

747 

748 if print_line: 

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

750 else: 

751 line = ' ' * mw 

752 

753 if nocls: 

754 if special_line: 

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

756 else: 

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

758 else: 

759 if special_line: 

760 style = ' class="linenos special"' 

761 else: 

762 style = ' class="linenos"' 

763 

764 if style: 

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

766 else: 

767 linenos = line 

768 

769 if aln: 

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

771 inner_line) 

772 else: 

773 yield 1, linenos + inner_line 

774 num += 1 

775 

776 def _wrap_lineanchors(self, inner): 

777 s = self.lineanchors 

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

779 i = self.linenostart - 1 

780 for t, line in inner: 

781 if t: 

782 i += 1 

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

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

785 else: 

786 yield 0, line 

787 

788 def _wrap_linespans(self, inner): 

789 s = self.linespans 

790 i = self.linenostart - 1 

791 for t, line in inner: 

792 if t: 

793 i += 1 

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

795 else: 

796 yield 0, line 

797 

798 def _wrap_div(self, inner): 

799 style = [] 

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

801 self.style.background_color is not None): 

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

803 if self.cssstyles: 

804 style.append(self.cssstyles) 

805 style = '; '.join(style) 

806 

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

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

809 yield from inner 

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

811 

812 def _wrap_pre(self, inner): 

813 style = [] 

814 if self.prestyles: 

815 style.append(self.prestyles) 

816 if self.noclasses: 

817 style.append(self._pre_style) 

818 style = '; '.join(style) 

819 

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

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

822 

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

824 # ignored by HTML parsers 

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

826 yield from inner 

827 yield 0, '</pre>' 

828 

829 def _wrap_code(self, inner): 

830 yield 0, '<code>' 

831 yield from inner 

832 yield 0, '</code>' 

833 

834 @functools.lru_cache(maxsize=100) 

835 def _translate_parts(self, value): 

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

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

838 

839 def _format_lines(self, tokensource): 

840 """ 

841 Just format the tokens, without any wrapping tags. 

842 Yield individual lines. 

843 """ 

844 nocls = self.noclasses 

845 lsep = self.lineseparator 

846 tagsfile = self.tagsfile 

847 

848 lspan = '' 

849 line = [] 

850 for ttype, value in tokensource: 

851 try: 

852 cspan = self.span_element_openers[ttype] 

853 except KeyError: 

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

855 if nocls: 

856 css_style = self._get_css_inline_styles(ttype) 

857 if css_style: 

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

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

860 else: 

861 cspan = '' 

862 else: 

863 css_class = self._get_css_classes(ttype) 

864 if css_class: 

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

866 else: 

867 cspan = '' 

868 self.span_element_openers[ttype] = cspan 

869 

870 parts = self._translate_parts(value) 

871 

872 if tagsfile and ttype in Token.Name: 

873 filename, linenumber = self._lookup_ctag(value) 

874 if linenumber: 

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

876 if base: 

877 base += '/' 

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

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

880 'fext': extension} 

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

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

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

884 

885 # for all but the last line 

886 for part in parts[:-1]: 

887 if line: 

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

889 # empty <span> tags 

890 if lspan != cspan and part: 

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

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

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

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

895 yield 1, ''.join(line) 

896 line = [] 

897 elif part: 

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

899 else: 

900 yield 1, lsep 

901 # for the last line 

902 if line and parts[-1]: 

903 if lspan != cspan: 

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

905 lspan = cspan 

906 else: 

907 line.append(parts[-1]) 

908 elif parts[-1]: 

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

910 lspan = cspan 

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

912 

913 if line: 

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

915 yield 1, ''.join(line) 

916 

917 def _lookup_ctag(self, token): 

918 entry = ctags.TagEntry() 

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

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

921 else: 

922 return None, None 

923 

924 def _highlight_lines(self, tokensource): 

925 """ 

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

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

928 """ 

929 hls = self.hl_lines 

930 

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

932 if t != 1: 

933 yield t, value 

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

935 if self.noclasses: 

936 style = '' 

937 if self.style.highlight_color is not None: 

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

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

940 else: 

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

942 else: 

943 yield 1, value 

944 

945 def wrap(self, source): 

946 """ 

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

948 individual lines, in custom generators. See docstring 

949 for `format`. Can be overridden. 

950 """ 

951 

952 output = source 

953 if self.wrapcode: 

954 output = self._wrap_code(output) 

955 

956 output = self._wrap_pre(output) 

957 

958 return output 

959 

960 def format_unencoded(self, tokensource, outfile): 

961 """ 

962 The formatting process uses several nested generators; which of 

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

964 

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

966 and wrap the pieces of text generated by this. 

967 

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

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

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

971 use several different wrappers that process the original source 

972 linewise, e.g. line number generators. 

973 """ 

974 source = self._format_lines(tokensource) 

975 

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

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

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

979 source = self._wrap_inlinelinenos(source) 

980 

981 if self.hl_lines: 

982 source = self._highlight_lines(source) 

983 

984 if not self.nowrap: 

985 if self.lineanchors: 

986 source = self._wrap_lineanchors(source) 

987 if self.linespans: 

988 source = self._wrap_linespans(source) 

989 source = self.wrap(source) 

990 if self.linenos == 1: 

991 source = self._wrap_tablelinenos(source) 

992 source = self._wrap_div(source) 

993 if self.full: 

994 source = self._wrap_full(source, outfile) 

995 

996 for t, piece in source: 

997 outfile.write(piece)