##// END OF EJS Templates
replace extra (str) -> extras (iterable)
yuji96 -
Show More
@@ -1,668 +1,671 b''
1 1 """Various display related classes.
2 2
3 3 Authors : MinRK, gregcaporaso, dannystaple
4 4 """
5 5 from html import escape as html_escape
6 6 from os.path import exists, isfile, splitext, abspath, join, isdir
7 7 from os import walk, sep, fsdecode
8 8
9 9 from IPython.core.display import DisplayObject, TextDisplayObject
10 10
11 from typing import Tuple
11 from typing import Tuple, Iterable
12 12
13 13 __all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument',
14 14 'FileLink', 'FileLinks', 'Code']
15 15
16 16
17 17 class Audio(DisplayObject):
18 18 """Create an audio object.
19 19
20 20 When this object is returned by an input cell or passed to the
21 21 display function, it will result in Audio controls being displayed
22 22 in the frontend (only works in the notebook).
23 23
24 24 Parameters
25 25 ----------
26 26 data : numpy array, list, unicode, str or bytes
27 27 Can be one of
28 28
29 29 * Numpy 1d array containing the desired waveform (mono)
30 30 * Numpy 2d array containing waveforms for each channel.
31 31 Shape=(NCHAN, NSAMPLES). For the standard channel order, see
32 32 http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
33 33 * List of float or integer representing the waveform (mono)
34 34 * String containing the filename
35 35 * Bytestring containing raw PCM data or
36 36 * URL pointing to a file on the web.
37 37
38 38 If the array option is used, the waveform will be normalized.
39 39
40 40 If a filename or url is used, the format support will be browser
41 41 dependent.
42 42 url : unicode
43 43 A URL to download the data from.
44 44 filename : unicode
45 45 Path to a local file to load the data from.
46 46 embed : boolean
47 47 Should the audio data be embedded using a data URI (True) or should
48 48 the original source be referenced. Set this to True if you want the
49 49 audio to playable later with no internet connection in the notebook.
50 50
51 51 Default is `True`, unless the keyword argument `url` is set, then
52 52 default value is `False`.
53 53 rate : integer
54 54 The sampling rate of the raw data.
55 55 Only required when data parameter is being used as an array
56 56 autoplay : bool
57 57 Set to True if the audio should immediately start playing.
58 58 Default is `False`.
59 59 normalize : bool
60 60 Whether audio should be normalized (rescaled) to the maximum possible
61 61 range. Default is `True`. When set to `False`, `data` must be between
62 62 -1 and 1 (inclusive), otherwise an error is raised.
63 63 Applies only when `data` is a list or array of samples; other types of
64 64 audio are never normalized.
65 65
66 66 Examples
67 67 --------
68 68
69 69 Generate a sound
70 70
71 71 >>> import numpy as np
72 72 ... framerate = 44100
73 73 ... t = np.linspace(0,5,framerate*5)
74 74 ... data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t)
75 75 ... Audio(data, rate=framerate)
76 76
77 77 Can also do stereo or more channels
78 78
79 79 >>> dataleft = np.sin(2*np.pi*220*t)
80 80 ... dataright = np.sin(2*np.pi*224*t)
81 81 ... Audio([dataleft, dataright], rate=framerate)
82 82
83 83 From URL:
84 84
85 85 >>> Audio("http://www.nch.com.au/acm/8k16bitpcm.wav")
86 86 >>> Audio(url="http://www.w3schools.com/html/horse.ogg")
87 87
88 88 From a File:
89 89
90 90 >>> Audio('/path/to/sound.wav')
91 91 >>> Audio(filename='/path/to/sound.ogg')
92 92
93 93 From Bytes:
94 94
95 95 >>> Audio(b'RAW_WAV_DATA..')
96 96 >>> Audio(data=b'RAW_WAV_DATA..')
97 97
98 98 See Also
99 99 --------
100 100 ipywidgets.Audio
101 101
102 102 AUdio widget with more more flexibility and options.
103 103
104 104 """
105 105 _read_flags = 'rb'
106 106
107 107 def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False, normalize=True, *,
108 108 element_id=None):
109 109 if filename is None and url is None and data is None:
110 110 raise ValueError("No audio data found. Expecting filename, url, or data.")
111 111 if embed is False and url is None:
112 112 raise ValueError("No url found. Expecting url when embed=False")
113 113
114 114 if url is not None and embed is not True:
115 115 self.embed = False
116 116 else:
117 117 self.embed = True
118 118 self.autoplay = autoplay
119 119 self.element_id = element_id
120 120 super(Audio, self).__init__(data=data, url=url, filename=filename)
121 121
122 122 if self.data is not None and not isinstance(self.data, bytes):
123 123 if rate is None:
124 124 raise ValueError("rate must be specified when data is a numpy array or list of audio samples.")
125 125 self.data = Audio._make_wav(data, rate, normalize)
126 126
127 127 def reload(self):
128 128 """Reload the raw data from file or URL."""
129 129 import mimetypes
130 130 if self.embed:
131 131 super(Audio, self).reload()
132 132
133 133 if self.filename is not None:
134 134 self.mimetype = mimetypes.guess_type(self.filename)[0]
135 135 elif self.url is not None:
136 136 self.mimetype = mimetypes.guess_type(self.url)[0]
137 137 else:
138 138 self.mimetype = "audio/wav"
139 139
140 140 @staticmethod
141 141 def _make_wav(data, rate, normalize):
142 142 """ Transform a numpy array to a PCM bytestring """
143 143 from io import BytesIO
144 144 import wave
145 145
146 146 try:
147 147 scaled, nchan = Audio._validate_and_normalize_with_numpy(data, normalize)
148 148 except ImportError:
149 149 scaled, nchan = Audio._validate_and_normalize_without_numpy(data, normalize)
150 150
151 151 fp = BytesIO()
152 152 waveobj = wave.open(fp,mode='wb')
153 153 waveobj.setnchannels(nchan)
154 154 waveobj.setframerate(rate)
155 155 waveobj.setsampwidth(2)
156 156 waveobj.setcomptype('NONE','NONE')
157 157 waveobj.writeframes(scaled)
158 158 val = fp.getvalue()
159 159 waveobj.close()
160 160
161 161 return val
162 162
163 163 @staticmethod
164 164 def _validate_and_normalize_with_numpy(data, normalize) -> Tuple[bytes, int]:
165 165 import numpy as np
166 166
167 167 data = np.array(data, dtype=float)
168 168 if len(data.shape) == 1:
169 169 nchan = 1
170 170 elif len(data.shape) == 2:
171 171 # In wave files,channels are interleaved. E.g.,
172 172 # "L1R1L2R2..." for stereo. See
173 173 # http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
174 174 # for channel ordering
175 175 nchan = data.shape[0]
176 176 data = data.T.ravel()
177 177 else:
178 178 raise ValueError('Array audio input must be a 1D or 2D array')
179 179
180 180 max_abs_value = np.max(np.abs(data))
181 181 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
182 182 scaled = data / normalization_factor * 32767
183 183 return scaled.astype("<h").tobytes(), nchan
184 184
185 185 @staticmethod
186 186 def _validate_and_normalize_without_numpy(data, normalize):
187 187 import array
188 188 import sys
189 189
190 190 data = array.array('f', data)
191 191
192 192 try:
193 193 max_abs_value = float(max([abs(x) for x in data]))
194 194 except TypeError as e:
195 195 raise TypeError('Only lists of mono audio are '
196 196 'supported if numpy is not installed') from e
197 197
198 198 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
199 199 scaled = array.array('h', [int(x / normalization_factor * 32767) for x in data])
200 200 if sys.byteorder == 'big':
201 201 scaled.byteswap()
202 202 nchan = 1
203 203 return scaled.tobytes(), nchan
204 204
205 205 @staticmethod
206 206 def _get_normalization_factor(max_abs_value, normalize):
207 207 if not normalize and max_abs_value > 1:
208 208 raise ValueError('Audio data must be between -1 and 1 when normalize=False.')
209 209 return max_abs_value if normalize else 1
210 210
211 211 def _data_and_metadata(self):
212 212 """shortcut for returning metadata with url information, if defined"""
213 213 md = {}
214 214 if self.url:
215 215 md['url'] = self.url
216 216 if md:
217 217 return self.data, md
218 218 else:
219 219 return self.data
220 220
221 221 def _repr_html_(self):
222 222 src = """
223 223 <audio {element_id} controls="controls" {autoplay}>
224 224 <source src="{src}" type="{type}" />
225 225 Your browser does not support the audio element.
226 226 </audio>
227 227 """
228 228 return src.format(src=self.src_attr(), type=self.mimetype, autoplay=self.autoplay_attr(),
229 229 element_id=self.element_id_attr())
230 230
231 231 def src_attr(self):
232 232 import base64
233 233 if self.embed and (self.data is not None):
234 234 data = base64=base64.b64encode(self.data).decode('ascii')
235 235 return """data:{type};base64,{base64}""".format(type=self.mimetype,
236 236 base64=data)
237 237 elif self.url is not None:
238 238 return self.url
239 239 else:
240 240 return ""
241 241
242 242 def autoplay_attr(self):
243 243 if(self.autoplay):
244 244 return 'autoplay="autoplay"'
245 245 else:
246 246 return ''
247 247
248 248 def element_id_attr(self):
249 249 if (self.element_id):
250 250 return 'id="{element_id}"'.format(element_id=self.element_id)
251 251 else:
252 252 return ''
253 253
254 254 class IFrame(object):
255 255 """
256 256 Generic class to embed an iframe in an IPython notebook
257 257 """
258 258
259 259 iframe = """
260 260 <iframe
261 261 width="{width}"
262 262 height="{height}"
263 263 src="{src}{params}"
264 264 frameborder="0"
265 265 allowfullscreen
266 {extra}
266 {extras}
267 267 ></iframe>
268 268 """
269 269
270 def __init__(self, src, width, height, extra="", **kwargs):
270 def __init__(self, src, width, height, extras: Iterable[str] = None, **kwargs):
271 if extras is None:
272 extras = []
273
271 274 self.src = src
272 275 self.width = width
273 276 self.height = height
274 self.extra = extra
277 self.extras = extras
275 278 self.params = kwargs
276 279
277 280 def _repr_html_(self):
278 281 """return the embed iframe"""
279 282 if self.params:
280 283 from urllib.parse import urlencode
281 284 params = "?" + urlencode(self.params)
282 285 else:
283 286 params = ""
284 287 return self.iframe.format(
285 288 src=self.src,
286 289 width=self.width,
287 290 height=self.height,
288 291 params=params,
289 extra=self.extra,
292 extras=" ".join(self.extras),
290 293 )
291 294
292 295
293 296 class YouTubeVideo(IFrame):
294 297 """Class for embedding a YouTube Video in an IPython session, based on its video id.
295 298
296 299 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
297 300 do::
298 301
299 302 vid = YouTubeVideo("foo")
300 303 display(vid)
301 304
302 305 To start from 30 seconds::
303 306
304 307 vid = YouTubeVideo("abc", start=30)
305 308 display(vid)
306 309
307 310 To calculate seconds from time as hours, minutes, seconds use
308 311 :class:`datetime.timedelta`::
309 312
310 313 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
311 314
312 315 Other parameters can be provided as documented at
313 316 https://developers.google.com/youtube/player_parameters#Parameters
314 317
315 318 When converting the notebook using nbconvert, a jpeg representation of the video
316 319 will be inserted in the document.
317 320 """
318 321
319 322 def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs):
320 323 self.id=id
321 324 src = "https://www.youtube.com/embed/{0}".format(id)
322 325 if allow_autoplay:
323 kwargs.update(autoplay=1, extra='allow="autoplay"')
326 kwargs.update(autoplay=1, extras=['allow="autoplay"'])
324 327 super(YouTubeVideo, self).__init__(src, width, height, **kwargs)
325 328
326 329 def _repr_jpeg_(self):
327 330 # Deferred import
328 331 from urllib.request import urlopen
329 332
330 333 try:
331 334 return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
332 335 except IOError:
333 336 return None
334 337
335 338 class VimeoVideo(IFrame):
336 339 """
337 340 Class for embedding a Vimeo video in an IPython session, based on its video id.
338 341 """
339 342
340 343 def __init__(self, id, width=400, height=300, **kwargs):
341 344 src="https://player.vimeo.com/video/{0}".format(id)
342 345 super(VimeoVideo, self).__init__(src, width, height, **kwargs)
343 346
344 347 class ScribdDocument(IFrame):
345 348 """
346 349 Class for embedding a Scribd document in an IPython session
347 350
348 351 Use the start_page params to specify a starting point in the document
349 352 Use the view_mode params to specify display type one off scroll | slideshow | book
350 353
351 354 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
352 355
353 356 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
354 357 """
355 358
356 359 def __init__(self, id, width=400, height=300, **kwargs):
357 360 src="https://www.scribd.com/embeds/{0}/content".format(id)
358 361 super(ScribdDocument, self).__init__(src, width, height, **kwargs)
359 362
360 363 class FileLink(object):
361 364 """Class for embedding a local file link in an IPython session, based on path
362 365
363 366 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
364 367
365 368 you would do::
366 369
367 370 local_file = FileLink("my/data.txt")
368 371 display(local_file)
369 372
370 373 or in the HTML notebook, just::
371 374
372 375 FileLink("my/data.txt")
373 376 """
374 377
375 378 html_link_str = "<a href='%s' target='_blank'>%s</a>"
376 379
377 380 def __init__(self,
378 381 path,
379 382 url_prefix='',
380 383 result_html_prefix='',
381 384 result_html_suffix='<br>'):
382 385 """
383 386 Parameters
384 387 ----------
385 388 path : str
386 389 path to the file or directory that should be formatted
387 390 url_prefix : str
388 391 prefix to be prepended to all files to form a working link [default:
389 392 '']
390 393 result_html_prefix : str
391 394 text to append to beginning to link [default: '']
392 395 result_html_suffix : str
393 396 text to append at the end of link [default: '<br>']
394 397 """
395 398 if isdir(path):
396 399 raise ValueError("Cannot display a directory using FileLink. "
397 400 "Use FileLinks to display '%s'." % path)
398 401 self.path = fsdecode(path)
399 402 self.url_prefix = url_prefix
400 403 self.result_html_prefix = result_html_prefix
401 404 self.result_html_suffix = result_html_suffix
402 405
403 406 def _format_path(self):
404 407 fp = ''.join([self.url_prefix, html_escape(self.path)])
405 408 return ''.join([self.result_html_prefix,
406 409 self.html_link_str % \
407 410 (fp, html_escape(self.path, quote=False)),
408 411 self.result_html_suffix])
409 412
410 413 def _repr_html_(self):
411 414 """return html link to file
412 415 """
413 416 if not exists(self.path):
414 417 return ("Path (<tt>%s</tt>) doesn't exist. "
415 418 "It may still be in the process of "
416 419 "being generated, or you may have the "
417 420 "incorrect path." % self.path)
418 421
419 422 return self._format_path()
420 423
421 424 def __repr__(self):
422 425 """return absolute path to file
423 426 """
424 427 return abspath(self.path)
425 428
426 429 class FileLinks(FileLink):
427 430 """Class for embedding local file links in an IPython session, based on path
428 431
429 432 e.g. to embed links to files that were generated in the IPython notebook
430 433 under ``my/data``, you would do::
431 434
432 435 local_files = FileLinks("my/data")
433 436 display(local_files)
434 437
435 438 or in the HTML notebook, just::
436 439
437 440 FileLinks("my/data")
438 441 """
439 442 def __init__(self,
440 443 path,
441 444 url_prefix='',
442 445 included_suffixes=None,
443 446 result_html_prefix='',
444 447 result_html_suffix='<br>',
445 448 notebook_display_formatter=None,
446 449 terminal_display_formatter=None,
447 450 recursive=True):
448 451 """
449 452 See :class:`FileLink` for the ``path``, ``url_prefix``,
450 453 ``result_html_prefix`` and ``result_html_suffix`` parameters.
451 454
452 455 included_suffixes : list
453 456 Filename suffixes to include when formatting output [default: include
454 457 all files]
455 458
456 459 notebook_display_formatter : function
457 460 Used to format links for display in the notebook. See discussion of
458 461 formatter functions below.
459 462
460 463 terminal_display_formatter : function
461 464 Used to format links for display in the terminal. See discussion of
462 465 formatter functions below.
463 466
464 467 Formatter functions must be of the form::
465 468
466 469 f(dirname, fnames, included_suffixes)
467 470
468 471 dirname : str
469 472 The name of a directory
470 473 fnames : list
471 474 The files in that directory
472 475 included_suffixes : list
473 476 The file suffixes that should be included in the output (passing None
474 477 meansto include all suffixes in the output in the built-in formatters)
475 478 recursive : boolean
476 479 Whether to recurse into subdirectories. Default is True.
477 480
478 481 The function should return a list of lines that will be printed in the
479 482 notebook (if passing notebook_display_formatter) or the terminal (if
480 483 passing terminal_display_formatter). This function is iterated over for
481 484 each directory in self.path. Default formatters are in place, can be
482 485 passed here to support alternative formatting.
483 486
484 487 """
485 488 if isfile(path):
486 489 raise ValueError("Cannot display a file using FileLinks. "
487 490 "Use FileLink to display '%s'." % path)
488 491 self.included_suffixes = included_suffixes
489 492 # remove trailing slashes for more consistent output formatting
490 493 path = path.rstrip('/')
491 494
492 495 self.path = path
493 496 self.url_prefix = url_prefix
494 497 self.result_html_prefix = result_html_prefix
495 498 self.result_html_suffix = result_html_suffix
496 499
497 500 self.notebook_display_formatter = \
498 501 notebook_display_formatter or self._get_notebook_display_formatter()
499 502 self.terminal_display_formatter = \
500 503 terminal_display_formatter or self._get_terminal_display_formatter()
501 504
502 505 self.recursive = recursive
503 506
504 507 def _get_display_formatter(self,
505 508 dirname_output_format,
506 509 fname_output_format,
507 510 fp_format,
508 511 fp_cleaner=None):
509 512 """ generate built-in formatter function
510 513
511 514 this is used to define both the notebook and terminal built-in
512 515 formatters as they only differ by some wrapper text for each entry
513 516
514 517 dirname_output_format: string to use for formatting directory
515 518 names, dirname will be substituted for a single "%s" which
516 519 must appear in this string
517 520 fname_output_format: string to use for formatting file names,
518 521 if a single "%s" appears in the string, fname will be substituted
519 522 if two "%s" appear in the string, the path to fname will be
520 523 substituted for the first and fname will be substituted for the
521 524 second
522 525 fp_format: string to use for formatting filepaths, must contain
523 526 exactly two "%s" and the dirname will be substituted for the first
524 527 and fname will be substituted for the second
525 528 """
526 529 def f(dirname, fnames, included_suffixes=None):
527 530 result = []
528 531 # begin by figuring out which filenames, if any,
529 532 # are going to be displayed
530 533 display_fnames = []
531 534 for fname in fnames:
532 535 if (isfile(join(dirname,fname)) and
533 536 (included_suffixes is None or
534 537 splitext(fname)[1] in included_suffixes)):
535 538 display_fnames.append(fname)
536 539
537 540 if len(display_fnames) == 0:
538 541 # if there are no filenames to display, don't print anything
539 542 # (not even the directory name)
540 543 pass
541 544 else:
542 545 # otherwise print the formatted directory name followed by
543 546 # the formatted filenames
544 547 dirname_output_line = dirname_output_format % dirname
545 548 result.append(dirname_output_line)
546 549 for fname in display_fnames:
547 550 fp = fp_format % (dirname,fname)
548 551 if fp_cleaner is not None:
549 552 fp = fp_cleaner(fp)
550 553 try:
551 554 # output can include both a filepath and a filename...
552 555 fname_output_line = fname_output_format % (fp, fname)
553 556 except TypeError:
554 557 # ... or just a single filepath
555 558 fname_output_line = fname_output_format % fname
556 559 result.append(fname_output_line)
557 560 return result
558 561 return f
559 562
560 563 def _get_notebook_display_formatter(self,
561 564 spacer="&nbsp;&nbsp;"):
562 565 """ generate function to use for notebook formatting
563 566 """
564 567 dirname_output_format = \
565 568 self.result_html_prefix + "%s/" + self.result_html_suffix
566 569 fname_output_format = \
567 570 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
568 571 fp_format = self.url_prefix + '%s/%s'
569 572 if sep == "\\":
570 573 # Working on a platform where the path separator is "\", so
571 574 # must convert these to "/" for generating a URI
572 575 def fp_cleaner(fp):
573 576 # Replace all occurrences of backslash ("\") with a forward
574 577 # slash ("/") - this is necessary on windows when a path is
575 578 # provided as input, but we must link to a URI
576 579 return fp.replace('\\','/')
577 580 else:
578 581 fp_cleaner = None
579 582
580 583 return self._get_display_formatter(dirname_output_format,
581 584 fname_output_format,
582 585 fp_format,
583 586 fp_cleaner)
584 587
585 588 def _get_terminal_display_formatter(self,
586 589 spacer=" "):
587 590 """ generate function to use for terminal formatting
588 591 """
589 592 dirname_output_format = "%s/"
590 593 fname_output_format = spacer + "%s"
591 594 fp_format = '%s/%s'
592 595
593 596 return self._get_display_formatter(dirname_output_format,
594 597 fname_output_format,
595 598 fp_format)
596 599
597 600 def _format_path(self):
598 601 result_lines = []
599 602 if self.recursive:
600 603 walked_dir = list(walk(self.path))
601 604 else:
602 605 walked_dir = [next(walk(self.path))]
603 606 walked_dir.sort()
604 607 for dirname, subdirs, fnames in walked_dir:
605 608 result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes)
606 609 return '\n'.join(result_lines)
607 610
608 611 def __repr__(self):
609 612 """return newline-separated absolute paths
610 613 """
611 614 result_lines = []
612 615 if self.recursive:
613 616 walked_dir = list(walk(self.path))
614 617 else:
615 618 walked_dir = [next(walk(self.path))]
616 619 walked_dir.sort()
617 620 for dirname, subdirs, fnames in walked_dir:
618 621 result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
619 622 return '\n'.join(result_lines)
620 623
621 624
622 625 class Code(TextDisplayObject):
623 626 """Display syntax-highlighted source code.
624 627
625 628 This uses Pygments to highlight the code for HTML and Latex output.
626 629
627 630 Parameters
628 631 ----------
629 632 data : str
630 633 The code as a string
631 634 url : str
632 635 A URL to fetch the code from
633 636 filename : str
634 637 A local filename to load the code from
635 638 language : str
636 639 The short name of a Pygments lexer to use for highlighting.
637 640 If not specified, it will guess the lexer based on the filename
638 641 or the code. Available lexers: http://pygments.org/docs/lexers/
639 642 """
640 643 def __init__(self, data=None, url=None, filename=None, language=None):
641 644 self.language = language
642 645 super().__init__(data=data, url=url, filename=filename)
643 646
644 647 def _get_lexer(self):
645 648 if self.language:
646 649 from pygments.lexers import get_lexer_by_name
647 650 return get_lexer_by_name(self.language)
648 651 elif self.filename:
649 652 from pygments.lexers import get_lexer_for_filename
650 653 return get_lexer_for_filename(self.filename)
651 654 else:
652 655 from pygments.lexers import guess_lexer
653 656 return guess_lexer(self.data)
654 657
655 658 def __repr__(self):
656 659 return self.data
657 660
658 661 def _repr_html_(self):
659 662 from pygments import highlight
660 663 from pygments.formatters import HtmlFormatter
661 664 fmt = HtmlFormatter()
662 665 style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html'))
663 666 return style + highlight(self.data, self._get_lexer(), fmt)
664 667
665 668 def _repr_latex_(self):
666 669 from pygments import highlight
667 670 from pygments.formatters import LatexFormatter
668 671 return highlight(self.data, self._get_lexer(), LatexFormatter())
General Comments 0
You need to be logged in to leave comments. Login now