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

412 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1""" 

2 pygments.formatters.html 

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

4 

5 Formatter for HTML output. 

6 

7 :copyright: Copyright 2006-2023 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 return '#' + color 

48 

49 

50def _get_ttype_class(ttype): 

51 fname = STANDARD_TYPES.get(ttype) 

52 if fname: 

53 return fname 

54 aname = '' 

55 while fname is None: 

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

57 ttype = ttype.parent 

58 fname = STANDARD_TYPES.get(ttype) 

59 return fname + aname 

60 

61 

62CSSFILE_TEMPLATE = '''\ 

63/* 

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

65Copyright 2006-2023 by the Pygments team. 

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

67*/ 

68%(styledefs)s 

69''' 

70 

71DOC_HEADER = '''\ 

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

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

74<!-- 

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

76Copyright 2006-2023 by the Pygments team. 

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

78--> 

79<html> 

80<head> 

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

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

83 <style type="text/css"> 

84''' + CSSFILE_TEMPLATE + ''' 

85 </style> 

86</head> 

87<body> 

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

89 

90''' 

91 

92DOC_HEADER_EXTERNALCSS = '''\ 

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

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

95 

96<html> 

97<head> 

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

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

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

101</head> 

102<body> 

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

104 

105''' 

106 

107DOC_FOOTER = '''\ 

108</body> 

109</html> 

110''' 

111 

112 

113class HtmlFormatter(Formatter): 

114 r""" 

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

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

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

118 

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

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

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

122 Example: 

123 

124 .. sourcecode:: html 

125 

126 <div class="highlight" > 

127 <table><tr> 

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

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

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

131 <pre>1 

132 2</pre> 

133 </td> 

134 <td class="code"> 

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

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

137 </pre> 

138 </td> 

139 </tr></table></div> 

140 

141 (whitespace added to improve clarity). 

142 

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

144 lines highlighted (as of Pygments 0.11). 

145 

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

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

148 the `cssfile` option is given. 

149 

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

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

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

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

154 otherwise a `RuntimeError` will be raised. 

155 

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

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

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

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

160 would result in the following CSS classes: 

161 

162 .. sourcecode:: css 

163 

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

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

166 ... 

167 

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

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

170 

171 .. sourcecode:: python 

172 

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

174 

175 The output would then look like this: 

176 

177 .. sourcecode:: css 

178 

179 div.syntax pre .kw, 

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

181 div.syntax pre .cm, 

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

183 ... 

184 

185 Additional options accepted: 

186 

187 `nowrap` 

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

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

190 

191 `full` 

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

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

194 

195 `title` 

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

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

198 

199 `style` 

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

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

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

203 `cssfile` exists. 

204 

205 `noclasses` 

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

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

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

209 (default: ``False``). 

210 

211 `classprefix` 

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

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

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

215 CSS class names for token types. 

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

217 

218 `cssclass` 

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

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

221 will be this class. 

222 

223 .. versionadded:: 0.9 

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

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

226 accordingly ``'highlighttable'``. 

227 

228 `cssstyles` 

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

230 

231 `prestyles` 

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

233 

234 .. versionadded:: 0.11 

235 

236 `cssfile` 

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

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

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

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

241 to this file instead of the HTML file. 

242 

243 .. versionadded:: 0.6 

244 

245 `noclobber_cssfile` 

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

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

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

249 

250 .. versionadded:: 1.1 

251 

252 `linenos` 

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

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

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

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

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

258 is *new in Pygments 0.8*). 

259 

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

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

262 means also ``True``). 

263 

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

265 

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

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

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

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

270 125%``). 

271 

272 `hl_lines` 

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

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

275 independent of `linenostart`. 

276 

277 .. versionadded:: 0.11 

278 

279 `linenostart` 

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

281 

282 `linenostep` 

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

284 

285 `linenospecial` 

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

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

288 

289 `nobackground` 

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

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

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

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

294 

295 .. versionadded:: 0.6 

296 

297 `lineseparator` 

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

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

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

301 

302 .. versionadded:: 0.7 

303 

304 `lineanchors` 

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

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

307 This allows easy linking to certain lines. 

308 

309 .. versionadded:: 0.9 

310 

311 `linespans` 

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

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

314 This allows easy access to lines via javascript. 

315 

316 .. versionadded:: 1.6 

317 

318 `anchorlinenos` 

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

320 combination with `linenos` and `lineanchors`. 

321 

322 `tagsfile` 

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

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

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

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

327 

328 .. versionadded:: 1.6 

329 

330 `tagurlformat` 

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

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

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

334 

335 .. versionadded:: 1.6 

336 

337 `filename` 

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

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

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

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

342 

343 .. versionadded:: 2.1 

344 

345 `wrapcode` 

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

347 by the HTML5 specification. 

348 

349 .. versionadded:: 2.4 

350 

351 `debug_token_types` 

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

353 name of the token. 

354 

355 .. versionadded:: 2.10 

356 

357 

358 **Subclassing the HTML formatter** 

359 

360 .. versionadded:: 0.7 

361 

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

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

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

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

366 formatted source code. 

367 

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

369 resulting HTML is output. 

370 

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

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

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

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

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

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

377 times. 

378 

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

380 

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

382 

383 .. sourcecode:: python 

384 

385 class CodeHtmlFormatter(HtmlFormatter): 

386 

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

388 return self._wrap_code(source) 

389 

390 def _wrap_code(self, source): 

391 yield 0, '<code>' 

392 for i, t in source: 

393 if i == 1: 

394 # it's a line of formatted code 

395 t += '<br>' 

396 yield i, t 

397 yield 0, '</code>' 

398 

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

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

401 

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

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

404 HTML yielded by the wrapped generator is output. 

405 """ 

406 

407 name = 'HTML' 

408 aliases = ['html'] 

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

410 

411 def __init__(self, **options): 

412 Formatter.__init__(self, **options) 

413 self.title = self._decodeifneeded(self.title) 

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

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

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

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

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

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

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

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

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

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

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

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

426 self.span_element_openers = {} 

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

428 

429 if self.tagsfile: 

430 if not ctags: 

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

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

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

434 

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

436 if linenos == 'inline': 

437 self.linenos = 2 

438 elif linenos: 

439 # compatibility with <= 0.7 

440 self.linenos = 1 

441 else: 

442 self.linenos = 0 

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

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

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

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

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

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

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

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

451 self.hl_lines = set() 

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

453 try: 

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

455 except ValueError: 

456 pass 

457 

458 self._create_stylesheet() 

459 

460 def _get_css_class(self, ttype): 

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

462 the classprefix option.""" 

463 ttypeclass = _get_ttype_class(ttype) 

464 if ttypeclass: 

465 return self.classprefix + ttypeclass 

466 return '' 

467 

468 def _get_css_classes(self, ttype): 

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

470 cls = self._get_css_class(ttype) 

471 while ttype not in STANDARD_TYPES: 

472 ttype = ttype.parent 

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

474 return cls or '' 

475 

476 def _get_css_inline_styles(self, ttype): 

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

478 cclass = self.ttype2class.get(ttype) 

479 while cclass is None: 

480 ttype = ttype.parent 

481 cclass = self.ttype2class.get(ttype) 

482 return cclass or '' 

483 

484 def _create_stylesheet(self): 

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

486 c2s = self.class2style = {} 

487 for ttype, ndef in self.style: 

488 name = self._get_css_class(ttype) 

489 style = '' 

490 if ndef['color']: 

491 style += 'color: %s; ' % webify(ndef['color']) 

492 if ndef['bold']: 

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

494 if ndef['italic']: 

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

496 if ndef['underline']: 

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

498 if ndef['bgcolor']: 

499 style += 'background-color: %s; ' % webify(ndef['bgcolor']) 

500 if ndef['border']: 

501 style += 'border: 1px solid %s; ' % webify(ndef['border']) 

502 if style: 

503 t2c[ttype] = name 

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

505 # hierarchy (necessary for CSS cascading rules!) 

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

507 

508 def get_style_defs(self, arg=None): 

509 """ 

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

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

512 insert before the token type classes. 

513 """ 

514 style_lines = [] 

515 

516 style_lines.extend(self.get_linenos_style_defs()) 

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

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

519 

520 return '\n'.join(style_lines) 

521 

522 def get_token_style_defs(self, arg=None): 

523 prefix = self.get_css_prefix(arg) 

524 

525 styles = [ 

526 (level, ttype, cls, style) 

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

528 if cls and style 

529 ] 

530 styles.sort() 

531 

532 lines = [ 

533 '%s { %s } /* %s */' % (prefix(cls), style, repr(ttype)[6:]) 

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

535 ] 

536 

537 return lines 

538 

539 def get_background_style_defs(self, arg=None): 

540 prefix = self.get_css_prefix(arg) 

541 bg_color = self.style.background_color 

542 hl_color = self.style.highlight_color 

543 

544 lines = [] 

545 

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

547 text_style = '' 

548 if Text in self.ttype2class: 

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

550 lines.insert( 

551 0, '%s{ background: %s;%s }' % ( 

552 prefix(''), bg_color, text_style 

553 ) 

554 ) 

555 if hl_color is not None: 

556 lines.insert( 

557 0, '%s { background-color: %s }' % (prefix('hll'), hl_color) 

558 ) 

559 

560 return lines 

561 

562 def get_linenos_style_defs(self): 

563 lines = [ 

564 'pre { %s }' % self._pre_style, 

565 'td.linenos .normal { %s }' % self._linenos_style, 

566 'span.linenos { %s }' % self._linenos_style, 

567 'td.linenos .special { %s }' % self._linenos_special_style, 

568 'span.linenos.special { %s }' % self._linenos_special_style, 

569 ] 

570 

571 return lines 

572 

573 def get_css_prefix(self, arg): 

574 if arg is None: 

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

576 if isinstance(arg, str): 

577 args = [arg] 

578 else: 

579 args = list(arg) 

580 

581 def prefix(cls): 

582 if cls: 

583 cls = '.' + cls 

584 tmp = [] 

585 for arg in args: 

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

587 return ', '.join(tmp) 

588 

589 return prefix 

590 

591 @property 

592 def _pre_style(self): 

593 return 'line-height: 125%;' 

594 

595 @property 

596 def _linenos_style(self): 

597 return 'color: %s; background-color: %s; padding-left: 5px; padding-right: 5px;' % ( 

598 self.style.line_number_color, 

599 self.style.line_number_background_color 

600 ) 

601 

602 @property 

603 def _linenos_special_style(self): 

604 return 'color: %s; background-color: %s; padding-left: 5px; padding-right: 5px;' % ( 

605 self.style.line_number_special_color, 

606 self.style.line_number_special_background_color 

607 ) 

608 

609 def _decodeifneeded(self, value): 

610 if isinstance(value, bytes): 

611 if self.encoding: 

612 return value.decode(self.encoding) 

613 return value.decode() 

614 return value 

615 

616 def _wrap_full(self, inner, outfile): 

617 if self.cssfile: 

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

619 # it's an absolute filename 

620 cssfilename = self.cssfile 

621 else: 

622 try: 

623 filename = outfile.name 

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

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

626 raise AttributeError 

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

628 self.cssfile) 

629 except AttributeError: 

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

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

632 file=sys.stderr) 

633 cssfilename = self.cssfile 

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

635 try: 

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

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

638 cf.write(CSSFILE_TEMPLATE % 

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

640 except OSError as err: 

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

642 raise 

643 

644 yield 0, (DOC_HEADER_EXTERNALCSS % 

645 dict(title=self.title, 

646 cssfile=self.cssfile, 

647 encoding=self.encoding)) 

648 else: 

649 yield 0, (DOC_HEADER % 

650 dict(title=self.title, 

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

652 encoding=self.encoding)) 

653 

654 yield from inner 

655 yield 0, DOC_FOOTER 

656 

657 def _wrap_tablelinenos(self, inner): 

658 dummyoutfile = StringIO() 

659 lncount = 0 

660 for t, line in inner: 

661 if t: 

662 lncount += 1 

663 dummyoutfile.write(line) 

664 

665 fl = self.linenostart 

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

667 sp = self.linenospecial 

668 st = self.linenostep 

669 anchor_name = self.lineanchors or self.linespans 

670 aln = self.anchorlinenos 

671 nocls = self.noclasses 

672 

673 lines = [] 

674 

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

676 print_line = i % st == 0 

677 special_line = sp and i % sp == 0 

678 

679 if print_line: 

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

681 if aln: 

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

683 else: 

684 line = ' ' * mw 

685 

686 if nocls: 

687 if special_line: 

688 style = ' style="%s"' % self._linenos_special_style 

689 else: 

690 style = ' style="%s"' % self._linenos_style 

691 else: 

692 if special_line: 

693 style = ' class="special"' 

694 else: 

695 style = ' class="normal"' 

696 

697 if style: 

698 line = '<span%s>%s</span>' % (style, line) 

699 

700 lines.append(line) 

701 

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

703 

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

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

706 filename_tr = "" 

707 if self.filename: 

708 filename_tr = ( 

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

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

711 '</th></tr>') 

712 

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

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

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

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

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

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

719 yield 0, '<div>' 

720 yield 0, dummyoutfile.getvalue() 

721 yield 0, '</div>' 

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

723 

724 

725 def _wrap_inlinelinenos(self, inner): 

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

727 inner_lines = list(inner) 

728 sp = self.linenospecial 

729 st = self.linenostep 

730 num = self.linenostart 

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

732 anchor_name = self.lineanchors or self.linespans 

733 aln = self.anchorlinenos 

734 nocls = self.noclasses 

735 

736 for _, inner_line in inner_lines: 

737 print_line = num % st == 0 

738 special_line = sp and num % sp == 0 

739 

740 if print_line: 

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

742 else: 

743 line = ' ' * mw 

744 

745 if nocls: 

746 if special_line: 

747 style = ' style="%s"' % self._linenos_special_style 

748 else: 

749 style = ' style="%s"' % self._linenos_style 

750 else: 

751 if special_line: 

752 style = ' class="linenos special"' 

753 else: 

754 style = ' class="linenos"' 

755 

756 if style: 

757 linenos = '<span%s>%s</span>' % (style, line) 

758 else: 

759 linenos = line 

760 

761 if aln: 

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

763 inner_line) 

764 else: 

765 yield 1, linenos + inner_line 

766 num += 1 

767 

768 def _wrap_lineanchors(self, inner): 

769 s = self.lineanchors 

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

771 i = self.linenostart - 1 

772 for t, line in inner: 

773 if t: 

774 i += 1 

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

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

777 else: 

778 yield 0, line 

779 

780 def _wrap_linespans(self, inner): 

781 s = self.linespans 

782 i = self.linenostart - 1 

783 for t, line in inner: 

784 if t: 

785 i += 1 

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

787 else: 

788 yield 0, line 

789 

790 def _wrap_div(self, inner): 

791 style = [] 

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

793 self.style.background_color is not None): 

794 style.append('background: %s' % (self.style.background_color,)) 

795 if self.cssstyles: 

796 style.append(self.cssstyles) 

797 style = '; '.join(style) 

798 

799 yield 0, ('<div' + (self.cssclass and ' class="%s"' % self.cssclass) + 

800 (style and (' style="%s"' % style)) + '>') 

801 yield from inner 

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

803 

804 def _wrap_pre(self, inner): 

805 style = [] 

806 if self.prestyles: 

807 style.append(self.prestyles) 

808 if self.noclasses: 

809 style.append(self._pre_style) 

810 style = '; '.join(style) 

811 

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

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

814 

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

816 # ignored by HTML parsers 

817 yield 0, ('<pre' + (style and ' style="%s"' % style) + '><span></span>') 

818 yield from inner 

819 yield 0, '</pre>' 

820 

821 def _wrap_code(self, inner): 

822 yield 0, '<code>' 

823 yield from inner 

824 yield 0, '</code>' 

825 

826 @functools.lru_cache(maxsize=100) 

827 def _translate_parts(self, value): 

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

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

830 

831 def _format_lines(self, tokensource): 

832 """ 

833 Just format the tokens, without any wrapping tags. 

834 Yield individual lines. 

835 """ 

836 nocls = self.noclasses 

837 lsep = self.lineseparator 

838 tagsfile = self.tagsfile 

839 

840 lspan = '' 

841 line = [] 

842 for ttype, value in tokensource: 

843 try: 

844 cspan = self.span_element_openers[ttype] 

845 except KeyError: 

846 title = ' title="%s"' % '.'.join(ttype) if self.debug_token_types else '' 

847 if nocls: 

848 css_style = self._get_css_inline_styles(ttype) 

849 if css_style: 

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

851 cspan = '<span style="%s"%s>' % (css_style, title) 

852 else: 

853 cspan = '' 

854 else: 

855 css_class = self._get_css_classes(ttype) 

856 if css_class: 

857 cspan = '<span class="%s"%s>' % (css_class, title) 

858 else: 

859 cspan = '' 

860 self.span_element_openers[ttype] = cspan 

861 

862 parts = self._translate_parts(value) 

863 

864 if tagsfile and ttype in Token.Name: 

865 filename, linenumber = self._lookup_ctag(value) 

866 if linenumber: 

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

868 if base: 

869 base += '/' 

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

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

872 'fext': extension} 

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

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

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

876 

877 # for all but the last line 

878 for part in parts[:-1]: 

879 if line: 

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

881 # empty <span> tags 

882 if lspan != cspan and part: 

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

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

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

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

887 yield 1, ''.join(line) 

888 line = [] 

889 elif part: 

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

891 else: 

892 yield 1, lsep 

893 # for the last line 

894 if line and parts[-1]: 

895 if lspan != cspan: 

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

897 lspan = cspan 

898 else: 

899 line.append(parts[-1]) 

900 elif parts[-1]: 

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

902 lspan = cspan 

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

904 

905 if line: 

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

907 yield 1, ''.join(line) 

908 

909 def _lookup_ctag(self, token): 

910 entry = ctags.TagEntry() 

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

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

913 else: 

914 return None, None 

915 

916 def _highlight_lines(self, tokensource): 

917 """ 

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

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

920 """ 

921 hls = self.hl_lines 

922 

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

924 if t != 1: 

925 yield t, value 

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

927 if self.noclasses: 

928 style = '' 

929 if self.style.highlight_color is not None: 

930 style = (' style="background-color: %s"' % 

931 (self.style.highlight_color,)) 

932 yield 1, '<span%s>%s</span>' % (style, value) 

933 else: 

934 yield 1, '<span class="hll">%s</span>' % value 

935 else: 

936 yield 1, value 

937 

938 def wrap(self, source): 

939 """ 

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

941 individual lines, in custom generators. See docstring 

942 for `format`. Can be overridden. 

943 """ 

944 

945 output = source 

946 if self.wrapcode: 

947 output = self._wrap_code(output) 

948 

949 output = self._wrap_pre(output) 

950 

951 return output 

952 

953 def format_unencoded(self, tokensource, outfile): 

954 """ 

955 The formatting process uses several nested generators; which of 

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

957 

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

959 and wrap the pieces of text generated by this. 

960 

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

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

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

964 use several different wrappers that process the original source 

965 linewise, e.g. line number generators. 

966 """ 

967 source = self._format_lines(tokensource) 

968 

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

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

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

972 source = self._wrap_inlinelinenos(source) 

973 

974 if self.hl_lines: 

975 source = self._highlight_lines(source) 

976 

977 if not self.nowrap: 

978 if self.lineanchors: 

979 source = self._wrap_lineanchors(source) 

980 if self.linespans: 

981 source = self._wrap_linespans(source) 

982 source = self.wrap(source) 

983 if self.linenos == 1: 

984 source = self._wrap_tablelinenos(source) 

985 source = self._wrap_div(source) 

986 if self.full: 

987 source = self._wrap_full(source, outfile) 

988 

989 for t, piece in source: 

990 outfile.write(piece)