##// END OF EJS Templates
validate that text display data is text...
MinRK -
Show More
@@ -1,679 +1,690 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Top-level display functions for displaying object in different formats.
3 3
4 4 Authors:
5 5
6 6 * Brian Granger
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2013 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 20 from __future__ import print_function
21 21
22 22 import os
23 23 import struct
24 24
25 25 from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode,
26 26 unicode_type)
27 27
28 28 from .displaypub import publish_display_data
29 29
30 30 #-----------------------------------------------------------------------------
31 31 # utility functions
32 32 #-----------------------------------------------------------------------------
33 33
34 34 def _safe_exists(path):
35 35 """Check path, but don't let exceptions raise"""
36 36 try:
37 37 return os.path.exists(path)
38 38 except Exception:
39 39 return False
40 40
41 41 def _merge(d1, d2):
42 42 """Like update, but merges sub-dicts instead of clobbering at the top level.
43 43
44 44 Updates d1 in-place
45 45 """
46 46
47 47 if not isinstance(d2, dict) or not isinstance(d1, dict):
48 48 return d2
49 49 for key, value in d2.items():
50 50 d1[key] = _merge(d1.get(key), value)
51 51 return d1
52 52
53 53 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
54 54 """internal implementation of all display_foo methods
55 55
56 56 Parameters
57 57 ----------
58 58 mimetype : str
59 59 The mimetype to be published (e.g. 'image/png')
60 60 objs : tuple of objects
61 61 The Python objects to display, or if raw=True raw text data to
62 62 display.
63 63 raw : bool
64 64 Are the data objects raw data or Python objects that need to be
65 65 formatted before display? [default: False]
66 66 metadata : dict (optional)
67 67 Metadata to be associated with the specific mimetype output.
68 68 """
69 69 if metadata:
70 70 metadata = {mimetype: metadata}
71 71 if raw:
72 72 # turn list of pngdata into list of { 'image/png': pngdata }
73 73 objs = [ {mimetype: obj} for obj in objs ]
74 74 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
75 75
76 76 #-----------------------------------------------------------------------------
77 77 # Main functions
78 78 #-----------------------------------------------------------------------------
79 79
80 80 def display(*objs, **kwargs):
81 81 """Display a Python object in all frontends.
82 82
83 83 By default all representations will be computed and sent to the frontends.
84 84 Frontends can decide which representation is used and how.
85 85
86 86 Parameters
87 87 ----------
88 88 objs : tuple of objects
89 89 The Python objects to display.
90 90 raw : bool, optional
91 91 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
92 92 or Python objects that need to be formatted before display? [default: False]
93 93 include : list or tuple, optional
94 94 A list of format type strings (MIME types) to include in the
95 95 format data dict. If this is set *only* the format types included
96 96 in this list will be computed.
97 97 exclude : list or tuple, optional
98 98 A list of format type strings (MIME types) to exclude in the format
99 99 data dict. If this is set all format types will be computed,
100 100 except for those included in this argument.
101 101 metadata : dict, optional
102 102 A dictionary of metadata to associate with the output.
103 103 mime-type keys in this dictionary will be associated with the individual
104 104 representation formats, if they exist.
105 105 """
106 106 raw = kwargs.get('raw', False)
107 107 include = kwargs.get('include')
108 108 exclude = kwargs.get('exclude')
109 109 metadata = kwargs.get('metadata')
110 110
111 111 from IPython.core.interactiveshell import InteractiveShell
112 112
113 113 if raw:
114 114 for obj in objs:
115 115 publish_display_data('display', obj, metadata)
116 116 else:
117 117 format = InteractiveShell.instance().display_formatter.format
118 118 for obj in objs:
119 119 format_dict, md_dict = format(obj, include=include, exclude=exclude)
120 120 if metadata:
121 121 # kwarg-specified metadata gets precedence
122 122 _merge(md_dict, metadata)
123 123 publish_display_data('display', format_dict, md_dict)
124 124
125 125
126 126 def display_pretty(*objs, **kwargs):
127 127 """Display the pretty (default) representation of an object.
128 128
129 129 Parameters
130 130 ----------
131 131 objs : tuple of objects
132 132 The Python objects to display, or if raw=True raw text data to
133 133 display.
134 134 raw : bool
135 135 Are the data objects raw data or Python objects that need to be
136 136 formatted before display? [default: False]
137 137 metadata : dict (optional)
138 138 Metadata to be associated with the specific mimetype output.
139 139 """
140 140 _display_mimetype('text/plain', objs, **kwargs)
141 141
142 142
143 143 def display_html(*objs, **kwargs):
144 144 """Display the HTML representation of an object.
145 145
146 146 Parameters
147 147 ----------
148 148 objs : tuple of objects
149 149 The Python objects to display, or if raw=True raw HTML data to
150 150 display.
151 151 raw : bool
152 152 Are the data objects raw data or Python objects that need to be
153 153 formatted before display? [default: False]
154 154 metadata : dict (optional)
155 155 Metadata to be associated with the specific mimetype output.
156 156 """
157 157 _display_mimetype('text/html', objs, **kwargs)
158 158
159 159
160 160 def display_svg(*objs, **kwargs):
161 161 """Display the SVG representation of an object.
162 162
163 163 Parameters
164 164 ----------
165 165 objs : tuple of objects
166 166 The Python objects to display, or if raw=True raw svg data to
167 167 display.
168 168 raw : bool
169 169 Are the data objects raw data or Python objects that need to be
170 170 formatted before display? [default: False]
171 171 metadata : dict (optional)
172 172 Metadata to be associated with the specific mimetype output.
173 173 """
174 174 _display_mimetype('image/svg+xml', objs, **kwargs)
175 175
176 176
177 177 def display_png(*objs, **kwargs):
178 178 """Display the PNG representation of an object.
179 179
180 180 Parameters
181 181 ----------
182 182 objs : tuple of objects
183 183 The Python objects to display, or if raw=True raw png data to
184 184 display.
185 185 raw : bool
186 186 Are the data objects raw data or Python objects that need to be
187 187 formatted before display? [default: False]
188 188 metadata : dict (optional)
189 189 Metadata to be associated with the specific mimetype output.
190 190 """
191 191 _display_mimetype('image/png', objs, **kwargs)
192 192
193 193
194 194 def display_jpeg(*objs, **kwargs):
195 195 """Display the JPEG representation of an object.
196 196
197 197 Parameters
198 198 ----------
199 199 objs : tuple of objects
200 200 The Python objects to display, or if raw=True raw JPEG data to
201 201 display.
202 202 raw : bool
203 203 Are the data objects raw data or Python objects that need to be
204 204 formatted before display? [default: False]
205 205 metadata : dict (optional)
206 206 Metadata to be associated with the specific mimetype output.
207 207 """
208 208 _display_mimetype('image/jpeg', objs, **kwargs)
209 209
210 210
211 211 def display_latex(*objs, **kwargs):
212 212 """Display the LaTeX representation of an object.
213 213
214 214 Parameters
215 215 ----------
216 216 objs : tuple of objects
217 217 The Python objects to display, or if raw=True raw latex data to
218 218 display.
219 219 raw : bool
220 220 Are the data objects raw data or Python objects that need to be
221 221 formatted before display? [default: False]
222 222 metadata : dict (optional)
223 223 Metadata to be associated with the specific mimetype output.
224 224 """
225 225 _display_mimetype('text/latex', objs, **kwargs)
226 226
227 227
228 228 def display_json(*objs, **kwargs):
229 229 """Display the JSON representation of an object.
230 230
231 231 Note that not many frontends support displaying JSON.
232 232
233 233 Parameters
234 234 ----------
235 235 objs : tuple of objects
236 236 The Python objects to display, or if raw=True raw json data to
237 237 display.
238 238 raw : bool
239 239 Are the data objects raw data or Python objects that need to be
240 240 formatted before display? [default: False]
241 241 metadata : dict (optional)
242 242 Metadata to be associated with the specific mimetype output.
243 243 """
244 244 _display_mimetype('application/json', objs, **kwargs)
245 245
246 246
247 247 def display_javascript(*objs, **kwargs):
248 248 """Display the Javascript representation of an object.
249 249
250 250 Parameters
251 251 ----------
252 252 objs : tuple of objects
253 253 The Python objects to display, or if raw=True raw javascript data to
254 254 display.
255 255 raw : bool
256 256 Are the data objects raw data or Python objects that need to be
257 257 formatted before display? [default: False]
258 258 metadata : dict (optional)
259 259 Metadata to be associated with the specific mimetype output.
260 260 """
261 261 _display_mimetype('application/javascript', objs, **kwargs)
262 262
263 263 #-----------------------------------------------------------------------------
264 264 # Smart classes
265 265 #-----------------------------------------------------------------------------
266 266
267 267
268 268 class DisplayObject(object):
269 269 """An object that wraps data to be displayed."""
270 270
271 271 _read_flags = 'r'
272 272
273 273 def __init__(self, data=None, url=None, filename=None):
274 274 """Create a display object given raw data.
275 275
276 276 When this object is returned by an expression or passed to the
277 277 display function, it will result in the data being displayed
278 278 in the frontend. The MIME type of the data should match the
279 279 subclasses used, so the Png subclass should be used for 'image/png'
280 280 data. If the data is a URL, the data will first be downloaded
281 281 and then displayed. If
282 282
283 283 Parameters
284 284 ----------
285 285 data : unicode, str or bytes
286 286 The raw data or a URL or file to load the data from
287 287 url : unicode
288 288 A URL to download the data from.
289 289 filename : unicode
290 290 Path to a local file to load the data from.
291 291 """
292 292 if data is not None and isinstance(data, string_types):
293 293 if data.startswith('http') and url is None:
294 294 url = data
295 295 filename = None
296 296 data = None
297 297 elif _safe_exists(data) and filename is None:
298 298 url = None
299 299 filename = data
300 300 data = None
301 301
302 302 self.data = data
303 303 self.url = url
304 304 self.filename = None if filename is None else unicode_type(filename)
305 305
306 306 self.reload()
307 self._check_data()
308
309 def _check_data(self):
310 """Override in subclasses if there's something to check."""
311 pass
307 312
308 313 def reload(self):
309 314 """Reload the raw data from file or URL."""
310 315 if self.filename is not None:
311 316 with open(self.filename, self._read_flags) as f:
312 317 self.data = f.read()
313 318 elif self.url is not None:
314 319 try:
315 320 try:
316 321 from urllib.request import urlopen # Py3
317 322 except ImportError:
318 323 from urllib2 import urlopen
319 324 response = urlopen(self.url)
320 325 self.data = response.read()
321 326 # extract encoding from header, if there is one:
322 327 encoding = None
323 328 for sub in response.headers['content-type'].split(';'):
324 329 sub = sub.strip()
325 330 if sub.startswith('charset'):
326 331 encoding = sub.split('=')[-1].strip()
327 332 break
328 333 # decode data, if an encoding was specified
329 334 if encoding:
330 335 self.data = self.data.decode(encoding, 'replace')
331 336 except:
332 337 self.data = None
333 338
334 class Pretty(DisplayObject):
339 class TextDisplayObject(DisplayObject):
340 """Validate that display data is text"""
341 def _check_data(self):
342 if self.data is not None and not isinstance(self.data, string_types):
343 raise TypeError("%s xpects text, not %r" % (self.__class__.__name__, self.data))
344
345 class Pretty(TextDisplayObject):
335 346
336 347 def _repr_pretty_(self):
337 348 return self.data
338 349
339 350
340 class HTML(DisplayObject):
351 class HTML(TextDisplayObject):
341 352
342 353 def _repr_html_(self):
343 354 return self.data
344 355
345 356 def __html__(self):
346 357 """
347 358 This method exists to inform other HTML-using modules (e.g. Markupsafe,
348 359 htmltag, etc) that this object is HTML and does not need things like
349 360 special characters (<>&) escaped.
350 361 """
351 362 return self._repr_html_()
352 363
353 364
354 class Math(DisplayObject):
365 class Math(TextDisplayObject):
355 366
356 367 def _repr_latex_(self):
357 368 s = self.data.strip('$')
358 369 return "$$%s$$" % s
359 370
360 371
361 class Latex(DisplayObject):
372 class Latex(TextDisplayObject):
362 373
363 374 def _repr_latex_(self):
364 375 return self.data
365 376
366 377
367 378 class SVG(DisplayObject):
368 379
369 380 # wrap data in a property, which extracts the <svg> tag, discarding
370 381 # document headers
371 382 _data = None
372 383
373 384 @property
374 385 def data(self):
375 386 return self._data
376 387
377 388 @data.setter
378 389 def data(self, svg):
379 390 if svg is None:
380 391 self._data = None
381 392 return
382 393 # parse into dom object
383 394 from xml.dom import minidom
384 395 svg = cast_bytes_py2(svg)
385 396 x = minidom.parseString(svg)
386 397 # get svg tag (should be 1)
387 398 found_svg = x.getElementsByTagName('svg')
388 399 if found_svg:
389 400 svg = found_svg[0].toxml()
390 401 else:
391 402 # fallback on the input, trust the user
392 403 # but this is probably an error.
393 404 pass
394 405 svg = cast_unicode(svg)
395 406 self._data = svg
396 407
397 408 def _repr_svg_(self):
398 409 return self.data
399 410
400 411
401 class JSON(DisplayObject):
412 class JSON(TextDisplayObject):
402 413
403 414 def _repr_json_(self):
404 415 return self.data
405 416
406 417 css_t = """$("head").append($("<link/>").attr({
407 418 rel: "stylesheet",
408 419 type: "text/css",
409 420 href: "%s"
410 421 }));
411 422 """
412 423
413 424 lib_t1 = """$.getScript("%s", function () {
414 425 """
415 426 lib_t2 = """});
416 427 """
417 428
418 class Javascript(DisplayObject):
429 class Javascript(TextDisplayObject):
419 430
420 431 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
421 432 """Create a Javascript display object given raw data.
422 433
423 434 When this object is returned by an expression or passed to the
424 435 display function, it will result in the data being displayed
425 436 in the frontend. If the data is a URL, the data will first be
426 437 downloaded and then displayed.
427 438
428 439 In the Notebook, the containing element will be available as `element`,
429 440 and jQuery will be available. The output area starts hidden, so if
430 441 the js appends content to `element` that should be visible, then
431 442 it must call `container.show()` to unhide the area.
432 443
433 444 Parameters
434 445 ----------
435 446 data : unicode, str or bytes
436 447 The Javascript source code or a URL to download it from.
437 448 url : unicode
438 449 A URL to download the data from.
439 450 filename : unicode
440 451 Path to a local file to load the data from.
441 452 lib : list or str
442 453 A sequence of Javascript library URLs to load asynchronously before
443 454 running the source code. The full URLs of the libraries should
444 455 be given. A single Javascript library URL can also be given as a
445 456 string.
446 457 css: : list or str
447 458 A sequence of css files to load before running the source code.
448 459 The full URLs of the css files should be given. A single css URL
449 460 can also be given as a string.
450 461 """
451 462 if isinstance(lib, string_types):
452 463 lib = [lib]
453 464 elif lib is None:
454 465 lib = []
455 466 if isinstance(css, string_types):
456 467 css = [css]
457 468 elif css is None:
458 469 css = []
459 470 if not isinstance(lib, (list,tuple)):
460 471 raise TypeError('expected sequence, got: %r' % lib)
461 472 if not isinstance(css, (list,tuple)):
462 473 raise TypeError('expected sequence, got: %r' % css)
463 474 self.lib = lib
464 475 self.css = css
465 476 super(Javascript, self).__init__(data=data, url=url, filename=filename)
466 477
467 478 def _repr_javascript_(self):
468 479 r = ''
469 480 for c in self.css:
470 481 r += css_t % c
471 482 for l in self.lib:
472 483 r += lib_t1 % l
473 484 r += self.data
474 485 r += lib_t2*len(self.lib)
475 486 return r
476 487
477 488 # constants for identifying png/jpeg data
478 489 _PNG = b'\x89PNG\r\n\x1a\n'
479 490 _JPEG = b'\xff\xd8'
480 491
481 492 def _pngxy(data):
482 493 """read the (width, height) from a PNG header"""
483 494 ihdr = data.index(b'IHDR')
484 495 # next 8 bytes are width/height
485 496 w4h4 = data[ihdr+4:ihdr+12]
486 497 return struct.unpack('>ii', w4h4)
487 498
488 499 def _jpegxy(data):
489 500 """read the (width, height) from a JPEG header"""
490 501 # adapted from http://www.64lines.com/jpeg-width-height
491 502
492 503 idx = 4
493 504 while True:
494 505 block_size = struct.unpack('>H', data[idx:idx+2])[0]
495 506 idx = idx + block_size
496 507 if data[idx:idx+2] == b'\xFF\xC0':
497 508 # found Start of Frame
498 509 iSOF = idx
499 510 break
500 511 else:
501 512 # read another block
502 513 idx += 2
503 514
504 515 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
505 516 return w, h
506 517
507 518 class Image(DisplayObject):
508 519
509 520 _read_flags = 'rb'
510 521 _FMT_JPEG = u'jpeg'
511 522 _FMT_PNG = u'png'
512 523 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
513 524
514 525 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
515 526 """Create a PNG/JPEG image object given raw data.
516 527
517 528 When this object is returned by an input cell or passed to the
518 529 display function, it will result in the image being displayed
519 530 in the frontend.
520 531
521 532 Parameters
522 533 ----------
523 534 data : unicode, str or bytes
524 535 The raw image data or a URL or filename to load the data from.
525 536 This always results in embedded image data.
526 537 url : unicode
527 538 A URL to download the data from. If you specify `url=`,
528 539 the image data will not be embedded unless you also specify `embed=True`.
529 540 filename : unicode
530 541 Path to a local file to load the data from.
531 542 Images from a file are always embedded.
532 543 format : unicode
533 544 The format of the image data (png/jpeg/jpg). If a filename or URL is given
534 545 for format will be inferred from the filename extension.
535 546 embed : bool
536 547 Should the image data be embedded using a data URI (True) or be
537 548 loaded using an <img> tag. Set this to True if you want the image
538 549 to be viewable later with no internet connection in the notebook.
539 550
540 551 Default is `True`, unless the keyword argument `url` is set, then
541 552 default value is `False`.
542 553
543 554 Note that QtConsole is not able to display images if `embed` is set to `False`
544 555 width : int
545 556 Width to which to constrain the image in html
546 557 height : int
547 558 Height to which to constrain the image in html
548 559 retina : bool
549 560 Automatically set the width and height to half of the measured
550 561 width and height.
551 562 This only works for embedded images because it reads the width/height
552 563 from image data.
553 564 For non-embedded images, you can just set the desired display width
554 565 and height directly.
555 566
556 567 Examples
557 568 --------
558 569 # embedded image data, works in qtconsole and notebook
559 570 # when passed positionally, the first arg can be any of raw image data,
560 571 # a URL, or a filename from which to load image data.
561 572 # The result is always embedding image data for inline images.
562 573 Image('http://www.google.fr/images/srpr/logo3w.png')
563 574 Image('/path/to/image.jpg')
564 575 Image(b'RAW_PNG_DATA...')
565 576
566 577 # Specifying Image(url=...) does not embed the image data,
567 578 # it only generates `<img>` tag with a link to the source.
568 579 # This will not work in the qtconsole or offline.
569 580 Image(url='http://www.google.fr/images/srpr/logo3w.png')
570 581
571 582 """
572 583 if filename is not None:
573 584 ext = self._find_ext(filename)
574 585 elif url is not None:
575 586 ext = self._find_ext(url)
576 587 elif data is None:
577 588 raise ValueError("No image data found. Expecting filename, url, or data.")
578 589 elif isinstance(data, string_types) and (
579 590 data.startswith('http') or _safe_exists(data)
580 591 ):
581 592 ext = self._find_ext(data)
582 593 else:
583 594 ext = None
584 595
585 596 if ext is not None:
586 597 format = ext.lower()
587 598 if ext == u'jpg' or ext == u'jpeg':
588 599 format = self._FMT_JPEG
589 600 if ext == u'png':
590 601 format = self._FMT_PNG
591 602 elif isinstance(data, bytes) and format == 'png':
592 603 # infer image type from image data header,
593 604 # only if format might not have been specified.
594 605 if data[:2] == _JPEG:
595 606 format = 'jpeg'
596 607
597 608 self.format = unicode_type(format).lower()
598 609 self.embed = embed if embed is not None else (url is None)
599 610
600 611 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
601 612 raise ValueError("Cannot embed the '%s' image format" % (self.format))
602 613 self.width = width
603 614 self.height = height
604 615 self.retina = retina
605 616 super(Image, self).__init__(data=data, url=url, filename=filename)
606 617
607 618 if retina:
608 619 self._retina_shape()
609 620
610 621 def _retina_shape(self):
611 622 """load pixel-doubled width and height from image data"""
612 623 if not self.embed:
613 624 return
614 625 if self.format == 'png':
615 626 w, h = _pngxy(self.data)
616 627 elif self.format == 'jpeg':
617 628 w, h = _jpegxy(self.data)
618 629 else:
619 630 # retina only supports png
620 631 return
621 632 self.width = w // 2
622 633 self.height = h // 2
623 634
624 635 def reload(self):
625 636 """Reload the raw data from file or URL."""
626 637 if self.embed:
627 638 super(Image,self).reload()
628 639 if self.retina:
629 640 self._retina_shape()
630 641
631 642 def _repr_html_(self):
632 643 if not self.embed:
633 644 width = height = ''
634 645 if self.width:
635 646 width = ' width="%d"' % self.width
636 647 if self.height:
637 648 height = ' height="%d"' % self.height
638 649 return u'<img src="%s"%s%s/>' % (self.url, width, height)
639 650
640 651 def _data_and_metadata(self):
641 652 """shortcut for returning metadata with shape information, if defined"""
642 653 md = {}
643 654 if self.width:
644 655 md['width'] = self.width
645 656 if self.height:
646 657 md['height'] = self.height
647 658 if md:
648 659 return self.data, md
649 660 else:
650 661 return self.data
651 662
652 663 def _repr_png_(self):
653 664 if self.embed and self.format == u'png':
654 665 return self._data_and_metadata()
655 666
656 667 def _repr_jpeg_(self):
657 668 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
658 669 return self._data_and_metadata()
659 670
660 671 def _find_ext(self, s):
661 672 return unicode_type(s.split('.')[-1].lower())
662 673
663 674
664 675 def clear_output(wait=False):
665 676 """Clear the output of the current cell receiving output.
666 677
667 678 Parameters
668 679 ----------
669 680 wait : bool [default: false]
670 681 Wait to clear the output until new output is available to replace it."""
671 682 from IPython.core.interactiveshell import InteractiveShell
672 683 if InteractiveShell.initialized():
673 684 InteractiveShell.instance().display_pub.clear_output(wait)
674 685 else:
675 686 from IPython.utils import io
676 687 print('\033[2K\r', file=io.stdout, end='')
677 688 io.stdout.flush()
678 689 print('\033[2K\r', file=io.stderr, end='')
679 690 io.stderr.flush()
General Comments 0
You need to be logged in to leave comments. Login now