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

412 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-01 06:54 +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 

327 .. versionadded:: 1.6 

328 

329 `tagurlformat` 

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

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

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

333 

334 .. versionadded:: 1.6 

335 

336 `filename` 

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

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

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

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

341 

342 .. versionadded:: 2.1 

343 

344 `wrapcode` 

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

346 by the HTML5 specification. 

347 

348 .. versionadded:: 2.4 

349 

350 `debug_token_types` 

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

352 name of the token. 

353 

354 .. versionadded:: 2.10 

355 

356 

357 **Subclassing the HTML formatter** 

358 

359 .. versionadded:: 0.7 

360 

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

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

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

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

365 formatted source code. 

366 

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

368 resulting HTML is output. 

369 

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

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

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

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

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

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

376 times. 

377 

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

379 

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

381 

382 .. sourcecode:: python 

383 

384 class CodeHtmlFormatter(HtmlFormatter): 

385 

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

387 return self._wrap_code(source) 

388 

389 def _wrap_code(self, source): 

390 yield 0, '<code>' 

391 for i, t in source: 

392 if i == 1: 

393 # it's a line of formatted code 

394 t += '<br>' 

395 yield i, t 

396 yield 0, '</code>' 

397 

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

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

400 

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

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

403 HTML yielded by the wrapped generator is output. 

404 """ 

405 

406 name = 'HTML' 

407 aliases = ['html'] 

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

409 

410 def __init__(self, **options): 

411 Formatter.__init__(self, **options) 

412 self.title = self._decodeifneeded(self.title) 

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

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

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

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

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

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

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

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

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

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

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

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

425 self.span_element_openers = {} 

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

427 

428 if self.tagsfile: 

429 if not ctags: 

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

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

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

433 

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

435 if linenos == 'inline': 

436 self.linenos = 2 

437 elif linenos: 

438 # compatibility with <= 0.7 

439 self.linenos = 1 

440 else: 

441 self.linenos = 0 

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

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

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

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

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

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

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

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

450 self.hl_lines = set() 

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

452 try: 

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

454 except ValueError: 

455 pass 

456 

457 self._create_stylesheet() 

458 

459 def _get_css_class(self, ttype): 

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

461 the classprefix option.""" 

462 ttypeclass = _get_ttype_class(ttype) 

463 if ttypeclass: 

464 return self.classprefix + ttypeclass 

465 return '' 

466 

467 def _get_css_classes(self, ttype): 

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

469 cls = self._get_css_class(ttype) 

470 while ttype not in STANDARD_TYPES: 

471 ttype = ttype.parent 

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

473 return cls or '' 

474 

475 def _get_css_inline_styles(self, ttype): 

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

477 cclass = self.ttype2class.get(ttype) 

478 while cclass is None: 

479 ttype = ttype.parent 

480 cclass = self.ttype2class.get(ttype) 

481 return cclass or '' 

482 

483 def _create_stylesheet(self): 

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

485 c2s = self.class2style = {} 

486 for ttype, ndef in self.style: 

487 name = self._get_css_class(ttype) 

488 style = '' 

489 if ndef['color']: 

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

491 if ndef['bold']: 

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

493 if ndef['italic']: 

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

495 if ndef['underline']: 

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

497 if ndef['bgcolor']: 

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

499 if ndef['border']: 

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

501 if style: 

502 t2c[ttype] = name 

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

504 # hierarchy (necessary for CSS cascading rules!) 

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

506 

507 def get_style_defs(self, arg=None): 

508 """ 

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

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

511 insert before the token type classes. 

512 """ 

513 style_lines = [] 

514 

515 style_lines.extend(self.get_linenos_style_defs()) 

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

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

518 

519 return '\n'.join(style_lines) 

520 

521 def get_token_style_defs(self, arg=None): 

522 prefix = self.get_css_prefix(arg) 

523 

524 styles = [ 

525 (level, ttype, cls, style) 

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

527 if cls and style 

528 ] 

529 styles.sort() 

530 

531 lines = [ 

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

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

534 ] 

535 

536 return lines 

537 

538 def get_background_style_defs(self, arg=None): 

539 prefix = self.get_css_prefix(arg) 

540 bg_color = self.style.background_color 

541 hl_color = self.style.highlight_color 

542 

543 lines = [] 

544 

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

546 text_style = '' 

547 if Text in self.ttype2class: 

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

549 lines.insert( 

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

551 prefix(''), bg_color, text_style 

552 ) 

553 ) 

554 if hl_color is not None: 

555 lines.insert( 

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

557 ) 

558 

559 return lines 

560 

561 def get_linenos_style_defs(self): 

562 lines = [ 

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

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

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

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

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

568 ] 

569 

570 return lines 

571 

572 def get_css_prefix(self, arg): 

573 if arg is None: 

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

575 if isinstance(arg, str): 

576 args = [arg] 

577 else: 

578 args = list(arg) 

579 

580 def prefix(cls): 

581 if cls: 

582 cls = '.' + cls 

583 tmp = [] 

584 for arg in args: 

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

586 return ', '.join(tmp) 

587 

588 return prefix 

589 

590 @property 

591 def _pre_style(self): 

592 return 'line-height: 125%;' 

593 

594 @property 

595 def _linenos_style(self): 

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

597 self.style.line_number_color, 

598 self.style.line_number_background_color 

599 ) 

600 

601 @property 

602 def _linenos_special_style(self): 

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

604 self.style.line_number_special_color, 

605 self.style.line_number_special_background_color 

606 ) 

607 

608 def _decodeifneeded(self, value): 

609 if isinstance(value, bytes): 

610 if self.encoding: 

611 return value.decode(self.encoding) 

612 return value.decode() 

613 return value 

614 

615 def _wrap_full(self, inner, outfile): 

616 if self.cssfile: 

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

618 # it's an absolute filename 

619 cssfilename = self.cssfile 

620 else: 

621 try: 

622 filename = outfile.name 

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

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

625 raise AttributeError 

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

627 self.cssfile) 

628 except AttributeError: 

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

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

631 file=sys.stderr) 

632 cssfilename = self.cssfile 

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

634 try: 

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

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

637 cf.write(CSSFILE_TEMPLATE % 

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

639 except OSError as err: 

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

641 raise 

642 

643 yield 0, (DOC_HEADER_EXTERNALCSS % 

644 dict(title=self.title, 

645 cssfile=self.cssfile, 

646 encoding=self.encoding)) 

647 else: 

648 yield 0, (DOC_HEADER % 

649 dict(title=self.title, 

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

651 encoding=self.encoding)) 

652 

653 yield from inner 

654 yield 0, DOC_FOOTER 

655 

656 def _wrap_tablelinenos(self, inner): 

657 dummyoutfile = StringIO() 

658 lncount = 0 

659 for t, line in inner: 

660 if t: 

661 lncount += 1 

662 dummyoutfile.write(line) 

663 

664 fl = self.linenostart 

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

666 sp = self.linenospecial 

667 st = self.linenostep 

668 anchor_name = self.lineanchors or self.linespans 

669 aln = self.anchorlinenos 

670 nocls = self.noclasses 

671 

672 lines = [] 

673 

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

675 print_line = i % st == 0 

676 special_line = sp and i % sp == 0 

677 

678 if print_line: 

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

680 if aln: 

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

682 else: 

683 line = ' ' * mw 

684 

685 if nocls: 

686 if special_line: 

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

688 else: 

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

690 else: 

691 if special_line: 

692 style = ' class="special"' 

693 else: 

694 style = ' class="normal"' 

695 

696 if style: 

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

698 

699 lines.append(line) 

700 

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

702 

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

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

705 filename_tr = "" 

706 if self.filename: 

707 filename_tr = ( 

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

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

710 '</th></tr>') 

711 

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

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

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

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

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

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

718 yield 0, '<div>' 

719 yield 0, dummyoutfile.getvalue() 

720 yield 0, '</div>' 

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

722 

723 

724 def _wrap_inlinelinenos(self, inner): 

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

726 inner_lines = list(inner) 

727 sp = self.linenospecial 

728 st = self.linenostep 

729 num = self.linenostart 

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

731 anchor_name = self.lineanchors or self.linespans 

732 aln = self.anchorlinenos 

733 nocls = self.noclasses 

734 

735 for _, inner_line in inner_lines: 

736 print_line = num % st == 0 

737 special_line = sp and num % sp == 0 

738 

739 if print_line: 

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

741 else: 

742 line = ' ' * mw 

743 

744 if nocls: 

745 if special_line: 

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

747 else: 

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

749 else: 

750 if special_line: 

751 style = ' class="linenos special"' 

752 else: 

753 style = ' class="linenos"' 

754 

755 if style: 

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

757 else: 

758 linenos = line 

759 

760 if aln: 

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

762 inner_line) 

763 else: 

764 yield 1, linenos + inner_line 

765 num += 1 

766 

767 def _wrap_lineanchors(self, inner): 

768 s = self.lineanchors 

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

770 i = self.linenostart - 1 

771 for t, line in inner: 

772 if t: 

773 i += 1 

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

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

776 else: 

777 yield 0, line 

778 

779 def _wrap_linespans(self, inner): 

780 s = self.linespans 

781 i = self.linenostart - 1 

782 for t, line in inner: 

783 if t: 

784 i += 1 

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

786 else: 

787 yield 0, line 

788 

789 def _wrap_div(self, inner): 

790 style = [] 

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

792 self.style.background_color is not None): 

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

794 if self.cssstyles: 

795 style.append(self.cssstyles) 

796 style = '; '.join(style) 

797 

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

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

800 yield from inner 

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

802 

803 def _wrap_pre(self, inner): 

804 style = [] 

805 if self.prestyles: 

806 style.append(self.prestyles) 

807 if self.noclasses: 

808 style.append(self._pre_style) 

809 style = '; '.join(style) 

810 

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

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

813 

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

815 # ignored by HTML parsers 

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

817 yield from inner 

818 yield 0, '</pre>' 

819 

820 def _wrap_code(self, inner): 

821 yield 0, '<code>' 

822 yield from inner 

823 yield 0, '</code>' 

824 

825 @functools.lru_cache(maxsize=100) 

826 def _translate_parts(self, value): 

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

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

829 

830 def _format_lines(self, tokensource): 

831 """ 

832 Just format the tokens, without any wrapping tags. 

833 Yield individual lines. 

834 """ 

835 nocls = self.noclasses 

836 lsep = self.lineseparator 

837 tagsfile = self.tagsfile 

838 

839 lspan = '' 

840 line = [] 

841 for ttype, value in tokensource: 

842 try: 

843 cspan = self.span_element_openers[ttype] 

844 except KeyError: 

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

846 if nocls: 

847 css_style = self._get_css_inline_styles(ttype) 

848 if css_style: 

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

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

851 else: 

852 cspan = '' 

853 else: 

854 css_class = self._get_css_classes(ttype) 

855 if css_class: 

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

857 else: 

858 cspan = '' 

859 self.span_element_openers[ttype] = cspan 

860 

861 parts = self._translate_parts(value) 

862 

863 if tagsfile and ttype in Token.Name: 

864 filename, linenumber = self._lookup_ctag(value) 

865 if linenumber: 

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

867 if base: 

868 base += '/' 

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

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

871 'fext': extension} 

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

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

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

875 

876 # for all but the last line 

877 for part in parts[:-1]: 

878 if line: 

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

880 # empty <span> tags 

881 if lspan != cspan and part: 

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

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

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

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

886 yield 1, ''.join(line) 

887 line = [] 

888 elif part: 

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

890 else: 

891 yield 1, lsep 

892 # for the last line 

893 if line and parts[-1]: 

894 if lspan != cspan: 

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

896 lspan = cspan 

897 else: 

898 line.append(parts[-1]) 

899 elif parts[-1]: 

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

901 lspan = cspan 

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

903 

904 if line: 

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

906 yield 1, ''.join(line) 

907 

908 def _lookup_ctag(self, token): 

909 entry = ctags.TagEntry() 

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

911 return entry['file'], entry['lineNumber'] 

912 else: 

913 return None, None 

914 

915 def _highlight_lines(self, tokensource): 

916 """ 

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

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

919 """ 

920 hls = self.hl_lines 

921 

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

923 if t != 1: 

924 yield t, value 

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

926 if self.noclasses: 

927 style = '' 

928 if self.style.highlight_color is not None: 

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

930 (self.style.highlight_color,)) 

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

932 else: 

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

934 else: 

935 yield 1, value 

936 

937 def wrap(self, source): 

938 """ 

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

940 individual lines, in custom generators. See docstring 

941 for `format`. Can be overridden. 

942 """ 

943 

944 output = source 

945 if self.wrapcode: 

946 output = self._wrap_code(output) 

947 

948 output = self._wrap_pre(output) 

949 

950 return output 

951 

952 def format_unencoded(self, tokensource, outfile): 

953 """ 

954 The formatting process uses several nested generators; which of 

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

956 

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

958 and wrap the pieces of text generated by this. 

959 

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

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

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

963 use several different wrappers that process the original source 

964 linewise, e.g. line number generators. 

965 """ 

966 source = self._format_lines(tokensource) 

967 

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

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

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

971 source = self._wrap_inlinelinenos(source) 

972 

973 if self.hl_lines: 

974 source = self._highlight_lines(source) 

975 

976 if not self.nowrap: 

977 if self.lineanchors: 

978 source = self._wrap_lineanchors(source) 

979 if self.linespans: 

980 source = self._wrap_linespans(source) 

981 source = self.wrap(source) 

982 if self.linenos == 1: 

983 source = self._wrap_tablelinenos(source) 

984 source = self._wrap_div(source) 

985 if self.full: 

986 source = self._wrap_full(source, outfile) 

987 

988 for t, piece in source: 

989 outfile.write(piece)