##// END OF EJS Templates
Merge pull request #5648 from andrewjesaitis/ticket4756...
Min RK -
r16416:b257cbb2 merge
parent child Browse files
Show More
@@ -1,779 +1,803
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Top-level display functions for displaying object in different formats.
2 """Top-level display functions for displaying object in different formats.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
10 # Copyright (C) 2013 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 from __future__ import print_function
20 from __future__ import print_function
21
21
22 import os
22 import os
23 import struct
23 import struct
24
24
25 from IPython.core.formatters import _safe_get_formatter_method
25 from IPython.core.formatters import _safe_get_formatter_method
26 from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode,
26 from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode,
27 unicode_type)
27 unicode_type)
28 from IPython.testing.skipdoctest import skip_doctest
28 from IPython.testing.skipdoctest import skip_doctest
29 from .displaypub import publish_display_data
29 from .displaypub import publish_display_data
30
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # utility functions
32 # utility functions
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34
34
35 def _safe_exists(path):
35 def _safe_exists(path):
36 """Check path, but don't let exceptions raise"""
36 """Check path, but don't let exceptions raise"""
37 try:
37 try:
38 return os.path.exists(path)
38 return os.path.exists(path)
39 except Exception:
39 except Exception:
40 return False
40 return False
41
41
42 def _merge(d1, d2):
42 def _merge(d1, d2):
43 """Like update, but merges sub-dicts instead of clobbering at the top level.
43 """Like update, but merges sub-dicts instead of clobbering at the top level.
44
44
45 Updates d1 in-place
45 Updates d1 in-place
46 """
46 """
47
47
48 if not isinstance(d2, dict) or not isinstance(d1, dict):
48 if not isinstance(d2, dict) or not isinstance(d1, dict):
49 return d2
49 return d2
50 for key, value in d2.items():
50 for key, value in d2.items():
51 d1[key] = _merge(d1.get(key), value)
51 d1[key] = _merge(d1.get(key), value)
52 return d1
52 return d1
53
53
54 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
54 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
55 """internal implementation of all display_foo methods
55 """internal implementation of all display_foo methods
56
56
57 Parameters
57 Parameters
58 ----------
58 ----------
59 mimetype : str
59 mimetype : str
60 The mimetype to be published (e.g. 'image/png')
60 The mimetype to be published (e.g. 'image/png')
61 objs : tuple of objects
61 objs : tuple of objects
62 The Python objects to display, or if raw=True raw text data to
62 The Python objects to display, or if raw=True raw text data to
63 display.
63 display.
64 raw : bool
64 raw : bool
65 Are the data objects raw data or Python objects that need to be
65 Are the data objects raw data or Python objects that need to be
66 formatted before display? [default: False]
66 formatted before display? [default: False]
67 metadata : dict (optional)
67 metadata : dict (optional)
68 Metadata to be associated with the specific mimetype output.
68 Metadata to be associated with the specific mimetype output.
69 """
69 """
70 if metadata:
70 if metadata:
71 metadata = {mimetype: metadata}
71 metadata = {mimetype: metadata}
72 if raw:
72 if raw:
73 # turn list of pngdata into list of { 'image/png': pngdata }
73 # turn list of pngdata into list of { 'image/png': pngdata }
74 objs = [ {mimetype: obj} for obj in objs ]
74 objs = [ {mimetype: obj} for obj in objs ]
75 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
75 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
76
76
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78 # Main functions
78 # Main functions
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80
80
81 def display(*objs, **kwargs):
81 def display(*objs, **kwargs):
82 """Display a Python object in all frontends.
82 """Display a Python object in all frontends.
83
83
84 By default all representations will be computed and sent to the frontends.
84 By default all representations will be computed and sent to the frontends.
85 Frontends can decide which representation is used and how.
85 Frontends can decide which representation is used and how.
86
86
87 Parameters
87 Parameters
88 ----------
88 ----------
89 objs : tuple of objects
89 objs : tuple of objects
90 The Python objects to display.
90 The Python objects to display.
91 raw : bool, optional
91 raw : bool, optional
92 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
92 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
93 or Python objects that need to be formatted before display? [default: False]
93 or Python objects that need to be formatted before display? [default: False]
94 include : list or tuple, optional
94 include : list or tuple, optional
95 A list of format type strings (MIME types) to include in the
95 A list of format type strings (MIME types) to include in the
96 format data dict. If this is set *only* the format types included
96 format data dict. If this is set *only* the format types included
97 in this list will be computed.
97 in this list will be computed.
98 exclude : list or tuple, optional
98 exclude : list or tuple, optional
99 A list of format type strings (MIME types) to exclude in the format
99 A list of format type strings (MIME types) to exclude in the format
100 data dict. If this is set all format types will be computed,
100 data dict. If this is set all format types will be computed,
101 except for those included in this argument.
101 except for those included in this argument.
102 metadata : dict, optional
102 metadata : dict, optional
103 A dictionary of metadata to associate with the output.
103 A dictionary of metadata to associate with the output.
104 mime-type keys in this dictionary will be associated with the individual
104 mime-type keys in this dictionary will be associated with the individual
105 representation formats, if they exist.
105 representation formats, if they exist.
106 """
106 """
107 raw = kwargs.get('raw', False)
107 raw = kwargs.get('raw', False)
108 include = kwargs.get('include')
108 include = kwargs.get('include')
109 exclude = kwargs.get('exclude')
109 exclude = kwargs.get('exclude')
110 metadata = kwargs.get('metadata')
110 metadata = kwargs.get('metadata')
111
111
112 from IPython.core.interactiveshell import InteractiveShell
112 from IPython.core.interactiveshell import InteractiveShell
113
113
114 if not raw:
114 if not raw:
115 format = InteractiveShell.instance().display_formatter.format
115 format = InteractiveShell.instance().display_formatter.format
116
116
117 for obj in objs:
117 for obj in objs:
118
118
119 # If _ipython_display_ is defined, use that to display this object.
119 # If _ipython_display_ is defined, use that to display this object.
120 display_method = _safe_get_formatter_method(obj, '_ipython_display_')
120 display_method = _safe_get_formatter_method(obj, '_ipython_display_')
121 if display_method is not None:
121 if display_method is not None:
122 try:
122 try:
123 display_method(**kwargs)
123 display_method(**kwargs)
124 except NotImplementedError:
124 except NotImplementedError:
125 pass
125 pass
126 else:
126 else:
127 continue
127 continue
128 if raw:
128 if raw:
129 publish_display_data('display', obj, metadata)
129 publish_display_data('display', obj, metadata)
130 else:
130 else:
131 format_dict, md_dict = format(obj, include=include, exclude=exclude)
131 format_dict, md_dict = format(obj, include=include, exclude=exclude)
132 if metadata:
132 if metadata:
133 # kwarg-specified metadata gets precedence
133 # kwarg-specified metadata gets precedence
134 _merge(md_dict, metadata)
134 _merge(md_dict, metadata)
135 publish_display_data('display', format_dict, md_dict)
135 publish_display_data('display', format_dict, md_dict)
136
136
137
137
138 def display_pretty(*objs, **kwargs):
138 def display_pretty(*objs, **kwargs):
139 """Display the pretty (default) representation of an object.
139 """Display the pretty (default) representation of an object.
140
140
141 Parameters
141 Parameters
142 ----------
142 ----------
143 objs : tuple of objects
143 objs : tuple of objects
144 The Python objects to display, or if raw=True raw text data to
144 The Python objects to display, or if raw=True raw text data to
145 display.
145 display.
146 raw : bool
146 raw : bool
147 Are the data objects raw data or Python objects that need to be
147 Are the data objects raw data or Python objects that need to be
148 formatted before display? [default: False]
148 formatted before display? [default: False]
149 metadata : dict (optional)
149 metadata : dict (optional)
150 Metadata to be associated with the specific mimetype output.
150 Metadata to be associated with the specific mimetype output.
151 """
151 """
152 _display_mimetype('text/plain', objs, **kwargs)
152 _display_mimetype('text/plain', objs, **kwargs)
153
153
154
154
155 def display_html(*objs, **kwargs):
155 def display_html(*objs, **kwargs):
156 """Display the HTML representation of an object.
156 """Display the HTML representation of an object.
157
157
158 Parameters
158 Parameters
159 ----------
159 ----------
160 objs : tuple of objects
160 objs : tuple of objects
161 The Python objects to display, or if raw=True raw HTML data to
161 The Python objects to display, or if raw=True raw HTML data to
162 display.
162 display.
163 raw : bool
163 raw : bool
164 Are the data objects raw data or Python objects that need to be
164 Are the data objects raw data or Python objects that need to be
165 formatted before display? [default: False]
165 formatted before display? [default: False]
166 metadata : dict (optional)
166 metadata : dict (optional)
167 Metadata to be associated with the specific mimetype output.
167 Metadata to be associated with the specific mimetype output.
168 """
168 """
169 _display_mimetype('text/html', objs, **kwargs)
169 _display_mimetype('text/html', objs, **kwargs)
170
170
171
171
172 def display_markdown(*objs, **kwargs):
173 """Displays the Markdown representation of an object.
174
175 Parameters
176 ----------
177 objs : tuple of objects
178 The Python objects to display, or if raw=True raw markdown data to
179 display.
180 raw : bool
181 Are the data objects raw data or Python objects that need to be
182 formatted before display? [default: False]
183 metadata : dict (optional)
184 Metadata to be associated with the specific mimetype output.
185 """
186
187 _display_mimetype('text/markdown', objs, **kwargs)
188
189
172 def display_svg(*objs, **kwargs):
190 def display_svg(*objs, **kwargs):
173 """Display the SVG representation of an object.
191 """Display the SVG representation of an object.
174
192
175 Parameters
193 Parameters
176 ----------
194 ----------
177 objs : tuple of objects
195 objs : tuple of objects
178 The Python objects to display, or if raw=True raw svg data to
196 The Python objects to display, or if raw=True raw svg data to
179 display.
197 display.
180 raw : bool
198 raw : bool
181 Are the data objects raw data or Python objects that need to be
199 Are the data objects raw data or Python objects that need to be
182 formatted before display? [default: False]
200 formatted before display? [default: False]
183 metadata : dict (optional)
201 metadata : dict (optional)
184 Metadata to be associated with the specific mimetype output.
202 Metadata to be associated with the specific mimetype output.
185 """
203 """
186 _display_mimetype('image/svg+xml', objs, **kwargs)
204 _display_mimetype('image/svg+xml', objs, **kwargs)
187
205
188
206
189 def display_png(*objs, **kwargs):
207 def display_png(*objs, **kwargs):
190 """Display the PNG representation of an object.
208 """Display the PNG representation of an object.
191
209
192 Parameters
210 Parameters
193 ----------
211 ----------
194 objs : tuple of objects
212 objs : tuple of objects
195 The Python objects to display, or if raw=True raw png data to
213 The Python objects to display, or if raw=True raw png data to
196 display.
214 display.
197 raw : bool
215 raw : bool
198 Are the data objects raw data or Python objects that need to be
216 Are the data objects raw data or Python objects that need to be
199 formatted before display? [default: False]
217 formatted before display? [default: False]
200 metadata : dict (optional)
218 metadata : dict (optional)
201 Metadata to be associated with the specific mimetype output.
219 Metadata to be associated with the specific mimetype output.
202 """
220 """
203 _display_mimetype('image/png', objs, **kwargs)
221 _display_mimetype('image/png', objs, **kwargs)
204
222
205
223
206 def display_jpeg(*objs, **kwargs):
224 def display_jpeg(*objs, **kwargs):
207 """Display the JPEG representation of an object.
225 """Display the JPEG representation of an object.
208
226
209 Parameters
227 Parameters
210 ----------
228 ----------
211 objs : tuple of objects
229 objs : tuple of objects
212 The Python objects to display, or if raw=True raw JPEG data to
230 The Python objects to display, or if raw=True raw JPEG data to
213 display.
231 display.
214 raw : bool
232 raw : bool
215 Are the data objects raw data or Python objects that need to be
233 Are the data objects raw data or Python objects that need to be
216 formatted before display? [default: False]
234 formatted before display? [default: False]
217 metadata : dict (optional)
235 metadata : dict (optional)
218 Metadata to be associated with the specific mimetype output.
236 Metadata to be associated with the specific mimetype output.
219 """
237 """
220 _display_mimetype('image/jpeg', objs, **kwargs)
238 _display_mimetype('image/jpeg', objs, **kwargs)
221
239
222
240
223 def display_latex(*objs, **kwargs):
241 def display_latex(*objs, **kwargs):
224 """Display the LaTeX representation of an object.
242 """Display the LaTeX representation of an object.
225
243
226 Parameters
244 Parameters
227 ----------
245 ----------
228 objs : tuple of objects
246 objs : tuple of objects
229 The Python objects to display, or if raw=True raw latex data to
247 The Python objects to display, or if raw=True raw latex data to
230 display.
248 display.
231 raw : bool
249 raw : bool
232 Are the data objects raw data or Python objects that need to be
250 Are the data objects raw data or Python objects that need to be
233 formatted before display? [default: False]
251 formatted before display? [default: False]
234 metadata : dict (optional)
252 metadata : dict (optional)
235 Metadata to be associated with the specific mimetype output.
253 Metadata to be associated with the specific mimetype output.
236 """
254 """
237 _display_mimetype('text/latex', objs, **kwargs)
255 _display_mimetype('text/latex', objs, **kwargs)
238
256
239
257
240 def display_json(*objs, **kwargs):
258 def display_json(*objs, **kwargs):
241 """Display the JSON representation of an object.
259 """Display the JSON representation of an object.
242
260
243 Note that not many frontends support displaying JSON.
261 Note that not many frontends support displaying JSON.
244
262
245 Parameters
263 Parameters
246 ----------
264 ----------
247 objs : tuple of objects
265 objs : tuple of objects
248 The Python objects to display, or if raw=True raw json data to
266 The Python objects to display, or if raw=True raw json data to
249 display.
267 display.
250 raw : bool
268 raw : bool
251 Are the data objects raw data or Python objects that need to be
269 Are the data objects raw data or Python objects that need to be
252 formatted before display? [default: False]
270 formatted before display? [default: False]
253 metadata : dict (optional)
271 metadata : dict (optional)
254 Metadata to be associated with the specific mimetype output.
272 Metadata to be associated with the specific mimetype output.
255 """
273 """
256 _display_mimetype('application/json', objs, **kwargs)
274 _display_mimetype('application/json', objs, **kwargs)
257
275
258
276
259 def display_javascript(*objs, **kwargs):
277 def display_javascript(*objs, **kwargs):
260 """Display the Javascript representation of an object.
278 """Display the Javascript representation of an object.
261
279
262 Parameters
280 Parameters
263 ----------
281 ----------
264 objs : tuple of objects
282 objs : tuple of objects
265 The Python objects to display, or if raw=True raw javascript data to
283 The Python objects to display, or if raw=True raw javascript data to
266 display.
284 display.
267 raw : bool
285 raw : bool
268 Are the data objects raw data or Python objects that need to be
286 Are the data objects raw data or Python objects that need to be
269 formatted before display? [default: False]
287 formatted before display? [default: False]
270 metadata : dict (optional)
288 metadata : dict (optional)
271 Metadata to be associated with the specific mimetype output.
289 Metadata to be associated with the specific mimetype output.
272 """
290 """
273 _display_mimetype('application/javascript', objs, **kwargs)
291 _display_mimetype('application/javascript', objs, **kwargs)
274
292
275
293
276 def display_pdf(*objs, **kwargs):
294 def display_pdf(*objs, **kwargs):
277 """Display the PDF representation of an object.
295 """Display the PDF representation of an object.
278
296
279 Parameters
297 Parameters
280 ----------
298 ----------
281 objs : tuple of objects
299 objs : tuple of objects
282 The Python objects to display, or if raw=True raw javascript data to
300 The Python objects to display, or if raw=True raw javascript data to
283 display.
301 display.
284 raw : bool
302 raw : bool
285 Are the data objects raw data or Python objects that need to be
303 Are the data objects raw data or Python objects that need to be
286 formatted before display? [default: False]
304 formatted before display? [default: False]
287 metadata : dict (optional)
305 metadata : dict (optional)
288 Metadata to be associated with the specific mimetype output.
306 Metadata to be associated with the specific mimetype output.
289 """
307 """
290 _display_mimetype('application/pdf', objs, **kwargs)
308 _display_mimetype('application/pdf', objs, **kwargs)
291
309
292
310
293 #-----------------------------------------------------------------------------
311 #-----------------------------------------------------------------------------
294 # Smart classes
312 # Smart classes
295 #-----------------------------------------------------------------------------
313 #-----------------------------------------------------------------------------
296
314
297
315
298 class DisplayObject(object):
316 class DisplayObject(object):
299 """An object that wraps data to be displayed."""
317 """An object that wraps data to be displayed."""
300
318
301 _read_flags = 'r'
319 _read_flags = 'r'
302
320
303 def __init__(self, data=None, url=None, filename=None):
321 def __init__(self, data=None, url=None, filename=None):
304 """Create a display object given raw data.
322 """Create a display object given raw data.
305
323
306 When this object is returned by an expression or passed to the
324 When this object is returned by an expression or passed to the
307 display function, it will result in the data being displayed
325 display function, it will result in the data being displayed
308 in the frontend. The MIME type of the data should match the
326 in the frontend. The MIME type of the data should match the
309 subclasses used, so the Png subclass should be used for 'image/png'
327 subclasses used, so the Png subclass should be used for 'image/png'
310 data. If the data is a URL, the data will first be downloaded
328 data. If the data is a URL, the data will first be downloaded
311 and then displayed. If
329 and then displayed. If
312
330
313 Parameters
331 Parameters
314 ----------
332 ----------
315 data : unicode, str or bytes
333 data : unicode, str or bytes
316 The raw data or a URL or file to load the data from
334 The raw data or a URL or file to load the data from
317 url : unicode
335 url : unicode
318 A URL to download the data from.
336 A URL to download the data from.
319 filename : unicode
337 filename : unicode
320 Path to a local file to load the data from.
338 Path to a local file to load the data from.
321 """
339 """
322 if data is not None and isinstance(data, string_types):
340 if data is not None and isinstance(data, string_types):
323 if data.startswith('http') and url is None:
341 if data.startswith('http') and url is None:
324 url = data
342 url = data
325 filename = None
343 filename = None
326 data = None
344 data = None
327 elif _safe_exists(data) and filename is None:
345 elif _safe_exists(data) and filename is None:
328 url = None
346 url = None
329 filename = data
347 filename = data
330 data = None
348 data = None
331
349
332 self.data = data
350 self.data = data
333 self.url = url
351 self.url = url
334 self.filename = None if filename is None else unicode_type(filename)
352 self.filename = None if filename is None else unicode_type(filename)
335
353
336 self.reload()
354 self.reload()
337 self._check_data()
355 self._check_data()
338
356
339 def _check_data(self):
357 def _check_data(self):
340 """Override in subclasses if there's something to check."""
358 """Override in subclasses if there's something to check."""
341 pass
359 pass
342
360
343 def reload(self):
361 def reload(self):
344 """Reload the raw data from file or URL."""
362 """Reload the raw data from file or URL."""
345 if self.filename is not None:
363 if self.filename is not None:
346 with open(self.filename, self._read_flags) as f:
364 with open(self.filename, self._read_flags) as f:
347 self.data = f.read()
365 self.data = f.read()
348 elif self.url is not None:
366 elif self.url is not None:
349 try:
367 try:
350 try:
368 try:
351 from urllib.request import urlopen # Py3
369 from urllib.request import urlopen # Py3
352 except ImportError:
370 except ImportError:
353 from urllib2 import urlopen
371 from urllib2 import urlopen
354 response = urlopen(self.url)
372 response = urlopen(self.url)
355 self.data = response.read()
373 self.data = response.read()
356 # extract encoding from header, if there is one:
374 # extract encoding from header, if there is one:
357 encoding = None
375 encoding = None
358 for sub in response.headers['content-type'].split(';'):
376 for sub in response.headers['content-type'].split(';'):
359 sub = sub.strip()
377 sub = sub.strip()
360 if sub.startswith('charset'):
378 if sub.startswith('charset'):
361 encoding = sub.split('=')[-1].strip()
379 encoding = sub.split('=')[-1].strip()
362 break
380 break
363 # decode data, if an encoding was specified
381 # decode data, if an encoding was specified
364 if encoding:
382 if encoding:
365 self.data = self.data.decode(encoding, 'replace')
383 self.data = self.data.decode(encoding, 'replace')
366 except:
384 except:
367 self.data = None
385 self.data = None
368
386
369 class TextDisplayObject(DisplayObject):
387 class TextDisplayObject(DisplayObject):
370 """Validate that display data is text"""
388 """Validate that display data is text"""
371 def _check_data(self):
389 def _check_data(self):
372 if self.data is not None and not isinstance(self.data, string_types):
390 if self.data is not None and not isinstance(self.data, string_types):
373 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
391 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
374
392
375 class Pretty(TextDisplayObject):
393 class Pretty(TextDisplayObject):
376
394
377 def _repr_pretty_(self):
395 def _repr_pretty_(self):
378 return self.data
396 return self.data
379
397
380
398
381 class HTML(TextDisplayObject):
399 class HTML(TextDisplayObject):
382
400
383 def _repr_html_(self):
401 def _repr_html_(self):
384 return self.data
402 return self.data
385
403
386 def __html__(self):
404 def __html__(self):
387 """
405 """
388 This method exists to inform other HTML-using modules (e.g. Markupsafe,
406 This method exists to inform other HTML-using modules (e.g. Markupsafe,
389 htmltag, etc) that this object is HTML and does not need things like
407 htmltag, etc) that this object is HTML and does not need things like
390 special characters (<>&) escaped.
408 special characters (<>&) escaped.
391 """
409 """
392 return self._repr_html_()
410 return self._repr_html_()
393
411
394
412
413 class Markdown(TextDisplayObject):
414
415 def _repr_markdown_(self):
416 return self.data
417
418
395 class Math(TextDisplayObject):
419 class Math(TextDisplayObject):
396
420
397 def _repr_latex_(self):
421 def _repr_latex_(self):
398 s = self.data.strip('$')
422 s = self.data.strip('$')
399 return "$$%s$$" % s
423 return "$$%s$$" % s
400
424
401
425
402 class Latex(TextDisplayObject):
426 class Latex(TextDisplayObject):
403
427
404 def _repr_latex_(self):
428 def _repr_latex_(self):
405 return self.data
429 return self.data
406
430
407
431
408 class SVG(DisplayObject):
432 class SVG(DisplayObject):
409
433
410 # wrap data in a property, which extracts the <svg> tag, discarding
434 # wrap data in a property, which extracts the <svg> tag, discarding
411 # document headers
435 # document headers
412 _data = None
436 _data = None
413
437
414 @property
438 @property
415 def data(self):
439 def data(self):
416 return self._data
440 return self._data
417
441
418 @data.setter
442 @data.setter
419 def data(self, svg):
443 def data(self, svg):
420 if svg is None:
444 if svg is None:
421 self._data = None
445 self._data = None
422 return
446 return
423 # parse into dom object
447 # parse into dom object
424 from xml.dom import minidom
448 from xml.dom import minidom
425 svg = cast_bytes_py2(svg)
449 svg = cast_bytes_py2(svg)
426 x = minidom.parseString(svg)
450 x = minidom.parseString(svg)
427 # get svg tag (should be 1)
451 # get svg tag (should be 1)
428 found_svg = x.getElementsByTagName('svg')
452 found_svg = x.getElementsByTagName('svg')
429 if found_svg:
453 if found_svg:
430 svg = found_svg[0].toxml()
454 svg = found_svg[0].toxml()
431 else:
455 else:
432 # fallback on the input, trust the user
456 # fallback on the input, trust the user
433 # but this is probably an error.
457 # but this is probably an error.
434 pass
458 pass
435 svg = cast_unicode(svg)
459 svg = cast_unicode(svg)
436 self._data = svg
460 self._data = svg
437
461
438 def _repr_svg_(self):
462 def _repr_svg_(self):
439 return self.data
463 return self.data
440
464
441
465
442 class JSON(TextDisplayObject):
466 class JSON(TextDisplayObject):
443
467
444 def _repr_json_(self):
468 def _repr_json_(self):
445 return self.data
469 return self.data
446
470
447 css_t = """$("head").append($("<link/>").attr({
471 css_t = """$("head").append($("<link/>").attr({
448 rel: "stylesheet",
472 rel: "stylesheet",
449 type: "text/css",
473 type: "text/css",
450 href: "%s"
474 href: "%s"
451 }));
475 }));
452 """
476 """
453
477
454 lib_t1 = """$.getScript("%s", function () {
478 lib_t1 = """$.getScript("%s", function () {
455 """
479 """
456 lib_t2 = """});
480 lib_t2 = """});
457 """
481 """
458
482
459 class Javascript(TextDisplayObject):
483 class Javascript(TextDisplayObject):
460
484
461 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
485 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
462 """Create a Javascript display object given raw data.
486 """Create a Javascript display object given raw data.
463
487
464 When this object is returned by an expression or passed to the
488 When this object is returned by an expression or passed to the
465 display function, it will result in the data being displayed
489 display function, it will result in the data being displayed
466 in the frontend. If the data is a URL, the data will first be
490 in the frontend. If the data is a URL, the data will first be
467 downloaded and then displayed.
491 downloaded and then displayed.
468
492
469 In the Notebook, the containing element will be available as `element`,
493 In the Notebook, the containing element will be available as `element`,
470 and jQuery will be available. The output area starts hidden, so if
494 and jQuery will be available. The output area starts hidden, so if
471 the js appends content to `element` that should be visible, then
495 the js appends content to `element` that should be visible, then
472 it must call `container.show()` to unhide the area.
496 it must call `container.show()` to unhide the area.
473
497
474 Parameters
498 Parameters
475 ----------
499 ----------
476 data : unicode, str or bytes
500 data : unicode, str or bytes
477 The Javascript source code or a URL to download it from.
501 The Javascript source code or a URL to download it from.
478 url : unicode
502 url : unicode
479 A URL to download the data from.
503 A URL to download the data from.
480 filename : unicode
504 filename : unicode
481 Path to a local file to load the data from.
505 Path to a local file to load the data from.
482 lib : list or str
506 lib : list or str
483 A sequence of Javascript library URLs to load asynchronously before
507 A sequence of Javascript library URLs to load asynchronously before
484 running the source code. The full URLs of the libraries should
508 running the source code. The full URLs of the libraries should
485 be given. A single Javascript library URL can also be given as a
509 be given. A single Javascript library URL can also be given as a
486 string.
510 string.
487 css: : list or str
511 css: : list or str
488 A sequence of css files to load before running the source code.
512 A sequence of css files to load before running the source code.
489 The full URLs of the css files should be given. A single css URL
513 The full URLs of the css files should be given. A single css URL
490 can also be given as a string.
514 can also be given as a string.
491 """
515 """
492 if isinstance(lib, string_types):
516 if isinstance(lib, string_types):
493 lib = [lib]
517 lib = [lib]
494 elif lib is None:
518 elif lib is None:
495 lib = []
519 lib = []
496 if isinstance(css, string_types):
520 if isinstance(css, string_types):
497 css = [css]
521 css = [css]
498 elif css is None:
522 elif css is None:
499 css = []
523 css = []
500 if not isinstance(lib, (list,tuple)):
524 if not isinstance(lib, (list,tuple)):
501 raise TypeError('expected sequence, got: %r' % lib)
525 raise TypeError('expected sequence, got: %r' % lib)
502 if not isinstance(css, (list,tuple)):
526 if not isinstance(css, (list,tuple)):
503 raise TypeError('expected sequence, got: %r' % css)
527 raise TypeError('expected sequence, got: %r' % css)
504 self.lib = lib
528 self.lib = lib
505 self.css = css
529 self.css = css
506 super(Javascript, self).__init__(data=data, url=url, filename=filename)
530 super(Javascript, self).__init__(data=data, url=url, filename=filename)
507
531
508 def _repr_javascript_(self):
532 def _repr_javascript_(self):
509 r = ''
533 r = ''
510 for c in self.css:
534 for c in self.css:
511 r += css_t % c
535 r += css_t % c
512 for l in self.lib:
536 for l in self.lib:
513 r += lib_t1 % l
537 r += lib_t1 % l
514 r += self.data
538 r += self.data
515 r += lib_t2*len(self.lib)
539 r += lib_t2*len(self.lib)
516 return r
540 return r
517
541
518 # constants for identifying png/jpeg data
542 # constants for identifying png/jpeg data
519 _PNG = b'\x89PNG\r\n\x1a\n'
543 _PNG = b'\x89PNG\r\n\x1a\n'
520 _JPEG = b'\xff\xd8'
544 _JPEG = b'\xff\xd8'
521
545
522 def _pngxy(data):
546 def _pngxy(data):
523 """read the (width, height) from a PNG header"""
547 """read the (width, height) from a PNG header"""
524 ihdr = data.index(b'IHDR')
548 ihdr = data.index(b'IHDR')
525 # next 8 bytes are width/height
549 # next 8 bytes are width/height
526 w4h4 = data[ihdr+4:ihdr+12]
550 w4h4 = data[ihdr+4:ihdr+12]
527 return struct.unpack('>ii', w4h4)
551 return struct.unpack('>ii', w4h4)
528
552
529 def _jpegxy(data):
553 def _jpegxy(data):
530 """read the (width, height) from a JPEG header"""
554 """read the (width, height) from a JPEG header"""
531 # adapted from http://www.64lines.com/jpeg-width-height
555 # adapted from http://www.64lines.com/jpeg-width-height
532
556
533 idx = 4
557 idx = 4
534 while True:
558 while True:
535 block_size = struct.unpack('>H', data[idx:idx+2])[0]
559 block_size = struct.unpack('>H', data[idx:idx+2])[0]
536 idx = idx + block_size
560 idx = idx + block_size
537 if data[idx:idx+2] == b'\xFF\xC0':
561 if data[idx:idx+2] == b'\xFF\xC0':
538 # found Start of Frame
562 # found Start of Frame
539 iSOF = idx
563 iSOF = idx
540 break
564 break
541 else:
565 else:
542 # read another block
566 # read another block
543 idx += 2
567 idx += 2
544
568
545 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
569 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
546 return w, h
570 return w, h
547
571
548 class Image(DisplayObject):
572 class Image(DisplayObject):
549
573
550 _read_flags = 'rb'
574 _read_flags = 'rb'
551 _FMT_JPEG = u'jpeg'
575 _FMT_JPEG = u'jpeg'
552 _FMT_PNG = u'png'
576 _FMT_PNG = u'png'
553 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
577 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
554
578
555 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
579 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
556 """Create a PNG/JPEG image object given raw data.
580 """Create a PNG/JPEG image object given raw data.
557
581
558 When this object is returned by an input cell or passed to the
582 When this object is returned by an input cell or passed to the
559 display function, it will result in the image being displayed
583 display function, it will result in the image being displayed
560 in the frontend.
584 in the frontend.
561
585
562 Parameters
586 Parameters
563 ----------
587 ----------
564 data : unicode, str or bytes
588 data : unicode, str or bytes
565 The raw image data or a URL or filename to load the data from.
589 The raw image data or a URL or filename to load the data from.
566 This always results in embedded image data.
590 This always results in embedded image data.
567 url : unicode
591 url : unicode
568 A URL to download the data from. If you specify `url=`,
592 A URL to download the data from. If you specify `url=`,
569 the image data will not be embedded unless you also specify `embed=True`.
593 the image data will not be embedded unless you also specify `embed=True`.
570 filename : unicode
594 filename : unicode
571 Path to a local file to load the data from.
595 Path to a local file to load the data from.
572 Images from a file are always embedded.
596 Images from a file are always embedded.
573 format : unicode
597 format : unicode
574 The format of the image data (png/jpeg/jpg). If a filename or URL is given
598 The format of the image data (png/jpeg/jpg). If a filename or URL is given
575 for format will be inferred from the filename extension.
599 for format will be inferred from the filename extension.
576 embed : bool
600 embed : bool
577 Should the image data be embedded using a data URI (True) or be
601 Should the image data be embedded using a data URI (True) or be
578 loaded using an <img> tag. Set this to True if you want the image
602 loaded using an <img> tag. Set this to True if you want the image
579 to be viewable later with no internet connection in the notebook.
603 to be viewable later with no internet connection in the notebook.
580
604
581 Default is `True`, unless the keyword argument `url` is set, then
605 Default is `True`, unless the keyword argument `url` is set, then
582 default value is `False`.
606 default value is `False`.
583
607
584 Note that QtConsole is not able to display images if `embed` is set to `False`
608 Note that QtConsole is not able to display images if `embed` is set to `False`
585 width : int
609 width : int
586 Width to which to constrain the image in html
610 Width to which to constrain the image in html
587 height : int
611 height : int
588 Height to which to constrain the image in html
612 Height to which to constrain the image in html
589 retina : bool
613 retina : bool
590 Automatically set the width and height to half of the measured
614 Automatically set the width and height to half of the measured
591 width and height.
615 width and height.
592 This only works for embedded images because it reads the width/height
616 This only works for embedded images because it reads the width/height
593 from image data.
617 from image data.
594 For non-embedded images, you can just set the desired display width
618 For non-embedded images, you can just set the desired display width
595 and height directly.
619 and height directly.
596
620
597 Examples
621 Examples
598 --------
622 --------
599 # embedded image data, works in qtconsole and notebook
623 # embedded image data, works in qtconsole and notebook
600 # when passed positionally, the first arg can be any of raw image data,
624 # when passed positionally, the first arg can be any of raw image data,
601 # a URL, or a filename from which to load image data.
625 # a URL, or a filename from which to load image data.
602 # The result is always embedding image data for inline images.
626 # The result is always embedding image data for inline images.
603 Image('http://www.google.fr/images/srpr/logo3w.png')
627 Image('http://www.google.fr/images/srpr/logo3w.png')
604 Image('/path/to/image.jpg')
628 Image('/path/to/image.jpg')
605 Image(b'RAW_PNG_DATA...')
629 Image(b'RAW_PNG_DATA...')
606
630
607 # Specifying Image(url=...) does not embed the image data,
631 # Specifying Image(url=...) does not embed the image data,
608 # it only generates `<img>` tag with a link to the source.
632 # it only generates `<img>` tag with a link to the source.
609 # This will not work in the qtconsole or offline.
633 # This will not work in the qtconsole or offline.
610 Image(url='http://www.google.fr/images/srpr/logo3w.png')
634 Image(url='http://www.google.fr/images/srpr/logo3w.png')
611
635
612 """
636 """
613 if filename is not None:
637 if filename is not None:
614 ext = self._find_ext(filename)
638 ext = self._find_ext(filename)
615 elif url is not None:
639 elif url is not None:
616 ext = self._find_ext(url)
640 ext = self._find_ext(url)
617 elif data is None:
641 elif data is None:
618 raise ValueError("No image data found. Expecting filename, url, or data.")
642 raise ValueError("No image data found. Expecting filename, url, or data.")
619 elif isinstance(data, string_types) and (
643 elif isinstance(data, string_types) and (
620 data.startswith('http') or _safe_exists(data)
644 data.startswith('http') or _safe_exists(data)
621 ):
645 ):
622 ext = self._find_ext(data)
646 ext = self._find_ext(data)
623 else:
647 else:
624 ext = None
648 ext = None
625
649
626 if ext is not None:
650 if ext is not None:
627 format = ext.lower()
651 format = ext.lower()
628 if ext == u'jpg' or ext == u'jpeg':
652 if ext == u'jpg' or ext == u'jpeg':
629 format = self._FMT_JPEG
653 format = self._FMT_JPEG
630 if ext == u'png':
654 if ext == u'png':
631 format = self._FMT_PNG
655 format = self._FMT_PNG
632 elif isinstance(data, bytes) and format == 'png':
656 elif isinstance(data, bytes) and format == 'png':
633 # infer image type from image data header,
657 # infer image type from image data header,
634 # only if format might not have been specified.
658 # only if format might not have been specified.
635 if data[:2] == _JPEG:
659 if data[:2] == _JPEG:
636 format = 'jpeg'
660 format = 'jpeg'
637
661
638 self.format = unicode_type(format).lower()
662 self.format = unicode_type(format).lower()
639 self.embed = embed if embed is not None else (url is None)
663 self.embed = embed if embed is not None else (url is None)
640
664
641 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
665 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
642 raise ValueError("Cannot embed the '%s' image format" % (self.format))
666 raise ValueError("Cannot embed the '%s' image format" % (self.format))
643 self.width = width
667 self.width = width
644 self.height = height
668 self.height = height
645 self.retina = retina
669 self.retina = retina
646 super(Image, self).__init__(data=data, url=url, filename=filename)
670 super(Image, self).__init__(data=data, url=url, filename=filename)
647
671
648 if retina:
672 if retina:
649 self._retina_shape()
673 self._retina_shape()
650
674
651 def _retina_shape(self):
675 def _retina_shape(self):
652 """load pixel-doubled width and height from image data"""
676 """load pixel-doubled width and height from image data"""
653 if not self.embed:
677 if not self.embed:
654 return
678 return
655 if self.format == 'png':
679 if self.format == 'png':
656 w, h = _pngxy(self.data)
680 w, h = _pngxy(self.data)
657 elif self.format == 'jpeg':
681 elif self.format == 'jpeg':
658 w, h = _jpegxy(self.data)
682 w, h = _jpegxy(self.data)
659 else:
683 else:
660 # retina only supports png
684 # retina only supports png
661 return
685 return
662 self.width = w // 2
686 self.width = w // 2
663 self.height = h // 2
687 self.height = h // 2
664
688
665 def reload(self):
689 def reload(self):
666 """Reload the raw data from file or URL."""
690 """Reload the raw data from file or URL."""
667 if self.embed:
691 if self.embed:
668 super(Image,self).reload()
692 super(Image,self).reload()
669 if self.retina:
693 if self.retina:
670 self._retina_shape()
694 self._retina_shape()
671
695
672 def _repr_html_(self):
696 def _repr_html_(self):
673 if not self.embed:
697 if not self.embed:
674 width = height = ''
698 width = height = ''
675 if self.width:
699 if self.width:
676 width = ' width="%d"' % self.width
700 width = ' width="%d"' % self.width
677 if self.height:
701 if self.height:
678 height = ' height="%d"' % self.height
702 height = ' height="%d"' % self.height
679 return u'<img src="%s"%s%s/>' % (self.url, width, height)
703 return u'<img src="%s"%s%s/>' % (self.url, width, height)
680
704
681 def _data_and_metadata(self):
705 def _data_and_metadata(self):
682 """shortcut for returning metadata with shape information, if defined"""
706 """shortcut for returning metadata with shape information, if defined"""
683 md = {}
707 md = {}
684 if self.width:
708 if self.width:
685 md['width'] = self.width
709 md['width'] = self.width
686 if self.height:
710 if self.height:
687 md['height'] = self.height
711 md['height'] = self.height
688 if md:
712 if md:
689 return self.data, md
713 return self.data, md
690 else:
714 else:
691 return self.data
715 return self.data
692
716
693 def _repr_png_(self):
717 def _repr_png_(self):
694 if self.embed and self.format == u'png':
718 if self.embed and self.format == u'png':
695 return self._data_and_metadata()
719 return self._data_and_metadata()
696
720
697 def _repr_jpeg_(self):
721 def _repr_jpeg_(self):
698 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
722 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
699 return self._data_and_metadata()
723 return self._data_and_metadata()
700
724
701 def _find_ext(self, s):
725 def _find_ext(self, s):
702 return unicode_type(s.split('.')[-1].lower())
726 return unicode_type(s.split('.')[-1].lower())
703
727
704
728
705 def clear_output(wait=False):
729 def clear_output(wait=False):
706 """Clear the output of the current cell receiving output.
730 """Clear the output of the current cell receiving output.
707
731
708 Parameters
732 Parameters
709 ----------
733 ----------
710 wait : bool [default: false]
734 wait : bool [default: false]
711 Wait to clear the output until new output is available to replace it."""
735 Wait to clear the output until new output is available to replace it."""
712 from IPython.core.interactiveshell import InteractiveShell
736 from IPython.core.interactiveshell import InteractiveShell
713 if InteractiveShell.initialized():
737 if InteractiveShell.initialized():
714 InteractiveShell.instance().display_pub.clear_output(wait)
738 InteractiveShell.instance().display_pub.clear_output(wait)
715 else:
739 else:
716 from IPython.utils import io
740 from IPython.utils import io
717 print('\033[2K\r', file=io.stdout, end='')
741 print('\033[2K\r', file=io.stdout, end='')
718 io.stdout.flush()
742 io.stdout.flush()
719 print('\033[2K\r', file=io.stderr, end='')
743 print('\033[2K\r', file=io.stderr, end='')
720 io.stderr.flush()
744 io.stderr.flush()
721
745
722
746
723 @skip_doctest
747 @skip_doctest
724 def set_matplotlib_formats(*formats, **kwargs):
748 def set_matplotlib_formats(*formats, **kwargs):
725 """Select figure formats for the inline backend. Optionally pass quality for JPEG.
749 """Select figure formats for the inline backend. Optionally pass quality for JPEG.
726
750
727 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
751 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
728
752
729 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
753 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
730
754
731 To set this in your config files use the following::
755 To set this in your config files use the following::
732
756
733 c.InlineBackend.figure_formats = {'png', 'jpeg'}
757 c.InlineBackend.figure_formats = {'png', 'jpeg'}
734 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
758 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
735
759
736 Parameters
760 Parameters
737 ----------
761 ----------
738 *formats : strs
762 *formats : strs
739 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
763 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
740 **kwargs :
764 **kwargs :
741 Keyword args will be relayed to ``figure.canvas.print_figure``.
765 Keyword args will be relayed to ``figure.canvas.print_figure``.
742 """
766 """
743 from IPython.core.interactiveshell import InteractiveShell
767 from IPython.core.interactiveshell import InteractiveShell
744 from IPython.core.pylabtools import select_figure_formats
768 from IPython.core.pylabtools import select_figure_formats
745 from IPython.kernel.zmq.pylab.config import InlineBackend
769 from IPython.kernel.zmq.pylab.config import InlineBackend
746 # build kwargs, starting with InlineBackend config
770 # build kwargs, starting with InlineBackend config
747 kw = {}
771 kw = {}
748 cfg = InlineBackend.instance()
772 cfg = InlineBackend.instance()
749 kw.update(cfg.print_figure_kwargs)
773 kw.update(cfg.print_figure_kwargs)
750 kw.update(**kwargs)
774 kw.update(**kwargs)
751 shell = InteractiveShell.instance()
775 shell = InteractiveShell.instance()
752 select_figure_formats(shell, formats, **kw)
776 select_figure_formats(shell, formats, **kw)
753
777
754 @skip_doctest
778 @skip_doctest
755 def set_matplotlib_close(close=True):
779 def set_matplotlib_close(close=True):
756 """Set whether the inline backend closes all figures automatically or not.
780 """Set whether the inline backend closes all figures automatically or not.
757
781
758 By default, the inline backend used in the IPython Notebook will close all
782 By default, the inline backend used in the IPython Notebook will close all
759 matplotlib figures automatically after each cell is run. This means that
783 matplotlib figures automatically after each cell is run. This means that
760 plots in different cells won't interfere. Sometimes, you may want to make
784 plots in different cells won't interfere. Sometimes, you may want to make
761 a plot in one cell and then refine it in later cells. This can be accomplished
785 a plot in one cell and then refine it in later cells. This can be accomplished
762 by::
786 by::
763
787
764 In [1]: set_matplotlib_close(False)
788 In [1]: set_matplotlib_close(False)
765
789
766 To set this in your config files use the following::
790 To set this in your config files use the following::
767
791
768 c.InlineBackend.close_figures = False
792 c.InlineBackend.close_figures = False
769
793
770 Parameters
794 Parameters
771 ----------
795 ----------
772 close : bool
796 close : bool
773 Should all matplotlib figures be automatically closed after each cell is
797 Should all matplotlib figures be automatically closed after each cell is
774 run?
798 run?
775 """
799 """
776 from IPython.kernel.zmq.pylab.config import InlineBackend
800 from IPython.kernel.zmq.pylab.config import InlineBackend
777 cfg = InlineBackend.instance()
801 cfg = InlineBackend.instance()
778 cfg.close_figures = close
802 cfg.close_figures = close
779
803
@@ -1,177 +1,179
1 """An interface for publishing rich data to frontends.
1 """An interface for publishing rich data to frontends.
2
2
3 There are two components of the display system:
3 There are two components of the display system:
4
4
5 * Display formatters, which take a Python object and compute the
5 * Display formatters, which take a Python object and compute the
6 representation of the object in various formats (text, HTML, SVG, etc.).
6 representation of the object in various formats (text, HTML, SVG, etc.).
7 * The display publisher that is used to send the representation data to the
7 * The display publisher that is used to send the representation data to the
8 various frontends.
8 various frontends.
9
9
10 This module defines the logic display publishing. The display publisher uses
10 This module defines the logic display publishing. The display publisher uses
11 the ``display_data`` message type that is defined in the IPython messaging
11 the ``display_data`` message type that is defined in the IPython messaging
12 spec.
12 spec.
13
13
14 Authors:
14 Authors:
15
15
16 * Brian Granger
16 * Brian Granger
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Copyright (C) 2008-2011 The IPython Development Team
20 # Copyright (C) 2008-2011 The IPython Development Team
21 #
21 #
22 # Distributed under the terms of the BSD License. The full license is in
22 # Distributed under the terms of the BSD License. The full license is in
23 # the file COPYING, distributed as part of this software.
23 # the file COPYING, distributed as part of this software.
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Imports
27 # Imports
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 from __future__ import print_function
30 from __future__ import print_function
31
31
32 from IPython.config.configurable import Configurable
32 from IPython.config.configurable import Configurable
33 from IPython.utils import io
33 from IPython.utils import io
34 from IPython.utils.py3compat import string_types
34 from IPython.utils.py3compat import string_types
35 from IPython.utils.traitlets import List
35 from IPython.utils.traitlets import List
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Main payload class
38 # Main payload class
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40
40
41 class DisplayPublisher(Configurable):
41 class DisplayPublisher(Configurable):
42 """A traited class that publishes display data to frontends.
42 """A traited class that publishes display data to frontends.
43
43
44 Instances of this class are created by the main IPython object and should
44 Instances of this class are created by the main IPython object and should
45 be accessed there.
45 be accessed there.
46 """
46 """
47
47
48 def _validate_data(self, source, data, metadata=None):
48 def _validate_data(self, source, data, metadata=None):
49 """Validate the display data.
49 """Validate the display data.
50
50
51 Parameters
51 Parameters
52 ----------
52 ----------
53 source : str
53 source : str
54 The fully dotted name of the callable that created the data, like
54 The fully dotted name of the callable that created the data, like
55 :func:`foo.bar.my_formatter`.
55 :func:`foo.bar.my_formatter`.
56 data : dict
56 data : dict
57 The formata data dictionary.
57 The formata data dictionary.
58 metadata : dict
58 metadata : dict
59 Any metadata for the data.
59 Any metadata for the data.
60 """
60 """
61
61
62 if not isinstance(source, string_types):
62 if not isinstance(source, string_types):
63 raise TypeError('source must be a str, got: %r' % source)
63 raise TypeError('source must be a str, got: %r' % source)
64 if not isinstance(data, dict):
64 if not isinstance(data, dict):
65 raise TypeError('data must be a dict, got: %r' % data)
65 raise TypeError('data must be a dict, got: %r' % data)
66 if metadata is not None:
66 if metadata is not None:
67 if not isinstance(metadata, dict):
67 if not isinstance(metadata, dict):
68 raise TypeError('metadata must be a dict, got: %r' % data)
68 raise TypeError('metadata must be a dict, got: %r' % data)
69
69
70 def publish(self, source, data, metadata=None):
70 def publish(self, source, data, metadata=None):
71 """Publish data and metadata to all frontends.
71 """Publish data and metadata to all frontends.
72
72
73 See the ``display_data`` message in the messaging documentation for
73 See the ``display_data`` message in the messaging documentation for
74 more details about this message type.
74 more details about this message type.
75
75
76 The following MIME types are currently implemented:
76 The following MIME types are currently implemented:
77
77
78 * text/plain
78 * text/plain
79 * text/html
79 * text/html
80 * text/markdown
80 * text/latex
81 * text/latex
81 * application/json
82 * application/json
82 * application/javascript
83 * application/javascript
83 * image/png
84 * image/png
84 * image/jpeg
85 * image/jpeg
85 * image/svg+xml
86 * image/svg+xml
86
87
87 Parameters
88 Parameters
88 ----------
89 ----------
89 source : str
90 source : str
90 A string that give the function or method that created the data,
91 A string that give the function or method that created the data,
91 such as 'IPython.core.page'.
92 such as 'IPython.core.page'.
92 data : dict
93 data : dict
93 A dictionary having keys that are valid MIME types (like
94 A dictionary having keys that are valid MIME types (like
94 'text/plain' or 'image/svg+xml') and values that are the data for
95 'text/plain' or 'image/svg+xml') and values that are the data for
95 that MIME type. The data itself must be a JSON'able data
96 that MIME type. The data itself must be a JSON'able data
96 structure. Minimally all data should have the 'text/plain' data,
97 structure. Minimally all data should have the 'text/plain' data,
97 which can be displayed by all frontends. If more than the plain
98 which can be displayed by all frontends. If more than the plain
98 text is given, it is up to the frontend to decide which
99 text is given, it is up to the frontend to decide which
99 representation to use.
100 representation to use.
100 metadata : dict
101 metadata : dict
101 A dictionary for metadata related to the data. This can contain
102 A dictionary for metadata related to the data. This can contain
102 arbitrary key, value pairs that frontends can use to interpret
103 arbitrary key, value pairs that frontends can use to interpret
103 the data. Metadata specific to each mime-type can be specified
104 the data. Metadata specific to each mime-type can be specified
104 in the metadata dict with the same mime-type keys as
105 in the metadata dict with the same mime-type keys as
105 the data itself.
106 the data itself.
106 """
107 """
107
108
108 # The default is to simply write the plain text data using io.stdout.
109 # The default is to simply write the plain text data using io.stdout.
109 if 'text/plain' in data:
110 if 'text/plain' in data:
110 print(data['text/plain'], file=io.stdout)
111 print(data['text/plain'], file=io.stdout)
111
112
112 def clear_output(self, wait=False):
113 def clear_output(self, wait=False):
113 """Clear the output of the cell receiving output."""
114 """Clear the output of the cell receiving output."""
114 print('\033[2K\r', file=io.stdout, end='')
115 print('\033[2K\r', file=io.stdout, end='')
115 io.stdout.flush()
116 io.stdout.flush()
116 print('\033[2K\r', file=io.stderr, end='')
117 print('\033[2K\r', file=io.stderr, end='')
117 io.stderr.flush()
118 io.stderr.flush()
118
119
119
120
120 class CapturingDisplayPublisher(DisplayPublisher):
121 class CapturingDisplayPublisher(DisplayPublisher):
121 """A DisplayPublisher that stores"""
122 """A DisplayPublisher that stores"""
122 outputs = List()
123 outputs = List()
123
124
124 def publish(self, source, data, metadata=None):
125 def publish(self, source, data, metadata=None):
125 self.outputs.append((source, data, metadata))
126 self.outputs.append((source, data, metadata))
126
127
127 def clear_output(self, wait=False):
128 def clear_output(self, wait=False):
128 super(CapturingDisplayPublisher, self).clear_output(wait)
129 super(CapturingDisplayPublisher, self).clear_output(wait)
129
130
130 # empty the list, *do not* reassign a new list
131 # empty the list, *do not* reassign a new list
131 del self.outputs[:]
132 del self.outputs[:]
132
133
133
134
134 def publish_display_data(source, data, metadata=None):
135 def publish_display_data(source, data, metadata=None):
135 """Publish data and metadata to all frontends.
136 """Publish data and metadata to all frontends.
136
137
137 See the ``display_data`` message in the messaging documentation for
138 See the ``display_data`` message in the messaging documentation for
138 more details about this message type.
139 more details about this message type.
139
140
140 The following MIME types are currently implemented:
141 The following MIME types are currently implemented:
141
142
142 * text/plain
143 * text/plain
143 * text/html
144 * text/html
145 * text/markdown
144 * text/latex
146 * text/latex
145 * application/json
147 * application/json
146 * application/javascript
148 * application/javascript
147 * image/png
149 * image/png
148 * image/jpeg
150 * image/jpeg
149 * image/svg+xml
151 * image/svg+xml
150
152
151 Parameters
153 Parameters
152 ----------
154 ----------
153 source : str
155 source : str
154 A string that give the function or method that created the data,
156 A string that give the function or method that created the data,
155 such as 'IPython.core.page'.
157 such as 'IPython.core.page'.
156 data : dict
158 data : dict
157 A dictionary having keys that are valid MIME types (like
159 A dictionary having keys that are valid MIME types (like
158 'text/plain' or 'image/svg+xml') and values that are the data for
160 'text/plain' or 'image/svg+xml') and values that are the data for
159 that MIME type. The data itself must be a JSON'able data
161 that MIME type. The data itself must be a JSON'able data
160 structure. Minimally all data should have the 'text/plain' data,
162 structure. Minimally all data should have the 'text/plain' data,
161 which can be displayed by all frontends. If more than the plain
163 which can be displayed by all frontends. If more than the plain
162 text is given, it is up to the frontend to decide which
164 text is given, it is up to the frontend to decide which
163 representation to use.
165 representation to use.
164 metadata : dict
166 metadata : dict
165 A dictionary for metadata related to the data. This can contain
167 A dictionary for metadata related to the data. This can contain
166 arbitrary key, value pairs that frontends can use to interpret
168 arbitrary key, value pairs that frontends can use to interpret
167 the data. mime-type keys matching those in data can be used
169 the data. mime-type keys matching those in data can be used
168 to specify metadata about particular representations.
170 to specify metadata about particular representations.
169 """
171 """
170 from IPython.core.interactiveshell import InteractiveShell
172 from IPython.core.interactiveshell import InteractiveShell
171 InteractiveShell.instance().display_pub.publish(
173 InteractiveShell.instance().display_pub.publish(
172 source,
174 source,
173 data,
175 data,
174 metadata
176 metadata
175 )
177 )
176
178
177
179
@@ -1,884 +1,902
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Display formatters.
2 """Display formatters.
3
3
4 Inheritance diagram:
4 Inheritance diagram:
5
5
6 .. inheritance-diagram:: IPython.core.formatters
6 .. inheritance-diagram:: IPython.core.formatters
7 :parts: 3
7 :parts: 3
8
8
9 Authors:
9 Authors:
10
10
11 * Robert Kern
11 * Robert Kern
12 * Brian Granger
12 * Brian Granger
13 """
13 """
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Copyright (C) 2010-2011, IPython Development Team.
15 # Copyright (C) 2010-2011, IPython Development Team.
16 #
16 #
17 # Distributed under the terms of the Modified BSD License.
17 # Distributed under the terms of the Modified BSD License.
18 #
18 #
19 # The full license is in the file COPYING.txt, distributed with this software.
19 # The full license is in the file COPYING.txt, distributed with this software.
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 # Stdlib imports
26 # Stdlib imports
27 import abc
27 import abc
28 import inspect
28 import inspect
29 import sys
29 import sys
30 import types
30 import types
31 import warnings
31 import warnings
32
32
33 from IPython.external.decorator import decorator
33 from IPython.external.decorator import decorator
34
34
35 # Our own imports
35 # Our own imports
36 from IPython.config.configurable import Configurable
36 from IPython.config.configurable import Configurable
37 from IPython.lib import pretty
37 from IPython.lib import pretty
38 from IPython.utils import io
38 from IPython.utils import io
39 from IPython.utils.traitlets import (
39 from IPython.utils.traitlets import (
40 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
40 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
41 )
41 )
42 from IPython.utils.warn import warn
42 from IPython.utils.warn import warn
43 from IPython.utils.py3compat import (
43 from IPython.utils.py3compat import (
44 unicode_to_str, with_metaclass, PY3, string_types, unicode_type,
44 unicode_to_str, with_metaclass, PY3, string_types, unicode_type,
45 )
45 )
46
46
47 if PY3:
47 if PY3:
48 from io import StringIO
48 from io import StringIO
49 else:
49 else:
50 from StringIO import StringIO
50 from StringIO import StringIO
51
51
52
52
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54 # The main DisplayFormatter class
54 # The main DisplayFormatter class
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56
56
57
57
58 def _valid_formatter(f):
58 def _valid_formatter(f):
59 """Return whether an object is a valid formatter
59 """Return whether an object is a valid formatter
60
60
61 Cases checked:
61 Cases checked:
62
62
63 - bound methods OK
63 - bound methods OK
64 - unbound methods NO
64 - unbound methods NO
65 - callable with zero args OK
65 - callable with zero args OK
66 """
66 """
67 if f is None:
67 if f is None:
68 return False
68 return False
69 elif isinstance(f, type(str.find)):
69 elif isinstance(f, type(str.find)):
70 # unbound methods on compiled classes have type method_descriptor
70 # unbound methods on compiled classes have type method_descriptor
71 return False
71 return False
72 elif isinstance(f, types.BuiltinFunctionType):
72 elif isinstance(f, types.BuiltinFunctionType):
73 # bound methods on compiled classes have type builtin_function
73 # bound methods on compiled classes have type builtin_function
74 return True
74 return True
75 elif callable(f):
75 elif callable(f):
76 # anything that works with zero args should be okay
76 # anything that works with zero args should be okay
77 try:
77 try:
78 inspect.getcallargs(f)
78 inspect.getcallargs(f)
79 except TypeError:
79 except TypeError:
80 return False
80 return False
81 else:
81 else:
82 return True
82 return True
83 return False
83 return False
84
84
85 def _safe_get_formatter_method(obj, name):
85 def _safe_get_formatter_method(obj, name):
86 """Safely get a formatter method"""
86 """Safely get a formatter method"""
87 method = pretty._safe_getattr(obj, name, None)
87 method = pretty._safe_getattr(obj, name, None)
88 # formatter methods must be bound
88 # formatter methods must be bound
89 if _valid_formatter(method):
89 if _valid_formatter(method):
90 return method
90 return method
91
91
92
92
93 class DisplayFormatter(Configurable):
93 class DisplayFormatter(Configurable):
94
94
95 # When set to true only the default plain text formatter will be used.
95 # When set to true only the default plain text formatter will be used.
96 plain_text_only = Bool(False, config=True)
96 plain_text_only = Bool(False, config=True)
97 def _plain_text_only_changed(self, name, old, new):
97 def _plain_text_only_changed(self, name, old, new):
98 warnings.warn("""DisplayFormatter.plain_text_only is deprecated.
98 warnings.warn("""DisplayFormatter.plain_text_only is deprecated.
99
99
100 Use DisplayFormatter.active_types = ['text/plain']
100 Use DisplayFormatter.active_types = ['text/plain']
101 for the same effect.
101 for the same effect.
102 """, DeprecationWarning)
102 """, DeprecationWarning)
103 if new:
103 if new:
104 self.active_types = ['text/plain']
104 self.active_types = ['text/plain']
105 else:
105 else:
106 self.active_types = self.format_types
106 self.active_types = self.format_types
107
107
108 active_types = List(Unicode, config=True,
108 active_types = List(Unicode, config=True,
109 help="""List of currently active mime-types to display.
109 help="""List of currently active mime-types to display.
110 You can use this to set a white-list for formats to display.
110 You can use this to set a white-list for formats to display.
111
111
112 Most users will not need to change this value.
112 Most users will not need to change this value.
113 """)
113 """)
114 def _active_types_default(self):
114 def _active_types_default(self):
115 return self.format_types
115 return self.format_types
116
116
117 def _active_types_changed(self, name, old, new):
117 def _active_types_changed(self, name, old, new):
118 for key, formatter in self.formatters.items():
118 for key, formatter in self.formatters.items():
119 if key in new:
119 if key in new:
120 formatter.enabled = True
120 formatter.enabled = True
121 else:
121 else:
122 formatter.enabled = False
122 formatter.enabled = False
123
123
124 # A dict of formatter whose keys are format types (MIME types) and whose
124 # A dict of formatter whose keys are format types (MIME types) and whose
125 # values are subclasses of BaseFormatter.
125 # values are subclasses of BaseFormatter.
126 formatters = Dict()
126 formatters = Dict()
127 def _formatters_default(self):
127 def _formatters_default(self):
128 """Activate the default formatters."""
128 """Activate the default formatters."""
129 formatter_classes = [
129 formatter_classes = [
130 PlainTextFormatter,
130 PlainTextFormatter,
131 HTMLFormatter,
131 HTMLFormatter,
132 MarkdownFormatter,
132 SVGFormatter,
133 SVGFormatter,
133 PNGFormatter,
134 PNGFormatter,
134 PDFFormatter,
135 PDFFormatter,
135 JPEGFormatter,
136 JPEGFormatter,
136 LatexFormatter,
137 LatexFormatter,
137 JSONFormatter,
138 JSONFormatter,
138 JavascriptFormatter
139 JavascriptFormatter
139 ]
140 ]
140 d = {}
141 d = {}
141 for cls in formatter_classes:
142 for cls in formatter_classes:
142 f = cls(parent=self)
143 f = cls(parent=self)
143 d[f.format_type] = f
144 d[f.format_type] = f
144 return d
145 return d
145
146
146 def format(self, obj, include=None, exclude=None):
147 def format(self, obj, include=None, exclude=None):
147 """Return a format data dict for an object.
148 """Return a format data dict for an object.
148
149
149 By default all format types will be computed.
150 By default all format types will be computed.
150
151
151 The following MIME types are currently implemented:
152 The following MIME types are currently implemented:
152
153
153 * text/plain
154 * text/plain
154 * text/html
155 * text/html
156 * text/markdown
155 * text/latex
157 * text/latex
156 * application/json
158 * application/json
157 * application/javascript
159 * application/javascript
158 * application/pdf
160 * application/pdf
159 * image/png
161 * image/png
160 * image/jpeg
162 * image/jpeg
161 * image/svg+xml
163 * image/svg+xml
162
164
163 Parameters
165 Parameters
164 ----------
166 ----------
165 obj : object
167 obj : object
166 The Python object whose format data will be computed.
168 The Python object whose format data will be computed.
167 include : list or tuple, optional
169 include : list or tuple, optional
168 A list of format type strings (MIME types) to include in the
170 A list of format type strings (MIME types) to include in the
169 format data dict. If this is set *only* the format types included
171 format data dict. If this is set *only* the format types included
170 in this list will be computed.
172 in this list will be computed.
171 exclude : list or tuple, optional
173 exclude : list or tuple, optional
172 A list of format type string (MIME types) to exclude in the format
174 A list of format type string (MIME types) to exclude in the format
173 data dict. If this is set all format types will be computed,
175 data dict. If this is set all format types will be computed,
174 except for those included in this argument.
176 except for those included in this argument.
175
177
176 Returns
178 Returns
177 -------
179 -------
178 (format_dict, metadata_dict) : tuple of two dicts
180 (format_dict, metadata_dict) : tuple of two dicts
179
181
180 format_dict is a dictionary of key/value pairs, one of each format that was
182 format_dict is a dictionary of key/value pairs, one of each format that was
181 generated for the object. The keys are the format types, which
183 generated for the object. The keys are the format types, which
182 will usually be MIME type strings and the values and JSON'able
184 will usually be MIME type strings and the values and JSON'able
183 data structure containing the raw data for the representation in
185 data structure containing the raw data for the representation in
184 that format.
186 that format.
185
187
186 metadata_dict is a dictionary of metadata about each mime-type output.
188 metadata_dict is a dictionary of metadata about each mime-type output.
187 Its keys will be a strict subset of the keys in format_dict.
189 Its keys will be a strict subset of the keys in format_dict.
188 """
190 """
189 format_dict = {}
191 format_dict = {}
190 md_dict = {}
192 md_dict = {}
191
193
192 for format_type, formatter in self.formatters.items():
194 for format_type, formatter in self.formatters.items():
193 if include and format_type not in include:
195 if include and format_type not in include:
194 continue
196 continue
195 if exclude and format_type in exclude:
197 if exclude and format_type in exclude:
196 continue
198 continue
197
199
198 md = None
200 md = None
199 try:
201 try:
200 data = formatter(obj)
202 data = formatter(obj)
201 except:
203 except:
202 # FIXME: log the exception
204 # FIXME: log the exception
203 raise
205 raise
204
206
205 # formatters can return raw data or (data, metadata)
207 # formatters can return raw data or (data, metadata)
206 if isinstance(data, tuple) and len(data) == 2:
208 if isinstance(data, tuple) and len(data) == 2:
207 data, md = data
209 data, md = data
208
210
209 if data is not None:
211 if data is not None:
210 format_dict[format_type] = data
212 format_dict[format_type] = data
211 if md is not None:
213 if md is not None:
212 md_dict[format_type] = md
214 md_dict[format_type] = md
213
215
214 return format_dict, md_dict
216 return format_dict, md_dict
215
217
216 @property
218 @property
217 def format_types(self):
219 def format_types(self):
218 """Return the format types (MIME types) of the active formatters."""
220 """Return the format types (MIME types) of the active formatters."""
219 return list(self.formatters.keys())
221 return list(self.formatters.keys())
220
222
221
223
222 #-----------------------------------------------------------------------------
224 #-----------------------------------------------------------------------------
223 # Formatters for specific format types (text, html, svg, etc.)
225 # Formatters for specific format types (text, html, svg, etc.)
224 #-----------------------------------------------------------------------------
226 #-----------------------------------------------------------------------------
225
227
226 class FormatterWarning(UserWarning):
228 class FormatterWarning(UserWarning):
227 """Warning class for errors in formatters"""
229 """Warning class for errors in formatters"""
228
230
229 @decorator
231 @decorator
230 def warn_format_error(method, self, *args, **kwargs):
232 def warn_format_error(method, self, *args, **kwargs):
231 """decorator for warning on failed format call"""
233 """decorator for warning on failed format call"""
232 try:
234 try:
233 r = method(self, *args, **kwargs)
235 r = method(self, *args, **kwargs)
234 except NotImplementedError as e:
236 except NotImplementedError as e:
235 # don't warn on NotImplementedErrors
237 # don't warn on NotImplementedErrors
236 return None
238 return None
237 except Exception as e:
239 except Exception as e:
238 warnings.warn("Exception in %s formatter: %s" % (self.format_type, e),
240 warnings.warn("Exception in %s formatter: %s" % (self.format_type, e),
239 FormatterWarning,
241 FormatterWarning,
240 )
242 )
241 return None
243 return None
242 if r is None or isinstance(r, self._return_type) or \
244 if r is None or isinstance(r, self._return_type) or \
243 (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
245 (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
244 return r
246 return r
245 else:
247 else:
246 warnings.warn(
248 warnings.warn(
247 "%s formatter returned invalid type %s (expected %s) for object: %s" % \
249 "%s formatter returned invalid type %s (expected %s) for object: %s" % \
248 (self.format_type, type(r), self._return_type, pretty._safe_repr(args[0])),
250 (self.format_type, type(r), self._return_type, pretty._safe_repr(args[0])),
249 FormatterWarning
251 FormatterWarning
250 )
252 )
251
253
252
254
253 class FormatterABC(with_metaclass(abc.ABCMeta, object)):
255 class FormatterABC(with_metaclass(abc.ABCMeta, object)):
254 """ Abstract base class for Formatters.
256 """ Abstract base class for Formatters.
255
257
256 A formatter is a callable class that is responsible for computing the
258 A formatter is a callable class that is responsible for computing the
257 raw format data for a particular format type (MIME type). For example,
259 raw format data for a particular format type (MIME type). For example,
258 an HTML formatter would have a format type of `text/html` and would return
260 an HTML formatter would have a format type of `text/html` and would return
259 the HTML representation of the object when called.
261 the HTML representation of the object when called.
260 """
262 """
261
263
262 # The format type of the data returned, usually a MIME type.
264 # The format type of the data returned, usually a MIME type.
263 format_type = 'text/plain'
265 format_type = 'text/plain'
264
266
265 # Is the formatter enabled...
267 # Is the formatter enabled...
266 enabled = True
268 enabled = True
267
269
268 @abc.abstractmethod
270 @abc.abstractmethod
269 @warn_format_error
271 @warn_format_error
270 def __call__(self, obj):
272 def __call__(self, obj):
271 """Return a JSON'able representation of the object.
273 """Return a JSON'able representation of the object.
272
274
273 If the object cannot be formatted by this formatter,
275 If the object cannot be formatted by this formatter,
274 warn and return None.
276 warn and return None.
275 """
277 """
276 return repr(obj)
278 return repr(obj)
277
279
278
280
279 def _mod_name_key(typ):
281 def _mod_name_key(typ):
280 """Return a (__module__, __name__) tuple for a type.
282 """Return a (__module__, __name__) tuple for a type.
281
283
282 Used as key in Formatter.deferred_printers.
284 Used as key in Formatter.deferred_printers.
283 """
285 """
284 module = getattr(typ, '__module__', None)
286 module = getattr(typ, '__module__', None)
285 name = getattr(typ, '__name__', None)
287 name = getattr(typ, '__name__', None)
286 return (module, name)
288 return (module, name)
287
289
288
290
289 def _get_type(obj):
291 def _get_type(obj):
290 """Return the type of an instance (old and new-style)"""
292 """Return the type of an instance (old and new-style)"""
291 return getattr(obj, '__class__', None) or type(obj)
293 return getattr(obj, '__class__', None) or type(obj)
292
294
293 _raise_key_error = object()
295 _raise_key_error = object()
294
296
295
297
296 class BaseFormatter(Configurable):
298 class BaseFormatter(Configurable):
297 """A base formatter class that is configurable.
299 """A base formatter class that is configurable.
298
300
299 This formatter should usually be used as the base class of all formatters.
301 This formatter should usually be used as the base class of all formatters.
300 It is a traited :class:`Configurable` class and includes an extensible
302 It is a traited :class:`Configurable` class and includes an extensible
301 API for users to determine how their objects are formatted. The following
303 API for users to determine how their objects are formatted. The following
302 logic is used to find a function to format an given object.
304 logic is used to find a function to format an given object.
303
305
304 1. The object is introspected to see if it has a method with the name
306 1. The object is introspected to see if it has a method with the name
305 :attr:`print_method`. If is does, that object is passed to that method
307 :attr:`print_method`. If is does, that object is passed to that method
306 for formatting.
308 for formatting.
307 2. If no print method is found, three internal dictionaries are consulted
309 2. If no print method is found, three internal dictionaries are consulted
308 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
310 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
309 and :attr:`deferred_printers`.
311 and :attr:`deferred_printers`.
310
312
311 Users should use these dictionaries to register functions that will be
313 Users should use these dictionaries to register functions that will be
312 used to compute the format data for their objects (if those objects don't
314 used to compute the format data for their objects (if those objects don't
313 have the special print methods). The easiest way of using these
315 have the special print methods). The easiest way of using these
314 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
316 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
315 methods.
317 methods.
316
318
317 If no function/callable is found to compute the format data, ``None`` is
319 If no function/callable is found to compute the format data, ``None`` is
318 returned and this format type is not used.
320 returned and this format type is not used.
319 """
321 """
320
322
321 format_type = Unicode('text/plain')
323 format_type = Unicode('text/plain')
322 _return_type = string_types
324 _return_type = string_types
323
325
324 enabled = Bool(True, config=True)
326 enabled = Bool(True, config=True)
325
327
326 print_method = ObjectName('__repr__')
328 print_method = ObjectName('__repr__')
327
329
328 # The singleton printers.
330 # The singleton printers.
329 # Maps the IDs of the builtin singleton objects to the format functions.
331 # Maps the IDs of the builtin singleton objects to the format functions.
330 singleton_printers = Dict(config=True)
332 singleton_printers = Dict(config=True)
331
333
332 # The type-specific printers.
334 # The type-specific printers.
333 # Map type objects to the format functions.
335 # Map type objects to the format functions.
334 type_printers = Dict(config=True)
336 type_printers = Dict(config=True)
335
337
336 # The deferred-import type-specific printers.
338 # The deferred-import type-specific printers.
337 # Map (modulename, classname) pairs to the format functions.
339 # Map (modulename, classname) pairs to the format functions.
338 deferred_printers = Dict(config=True)
340 deferred_printers = Dict(config=True)
339
341
340 @warn_format_error
342 @warn_format_error
341 def __call__(self, obj):
343 def __call__(self, obj):
342 """Compute the format for an object."""
344 """Compute the format for an object."""
343 if self.enabled:
345 if self.enabled:
344 # lookup registered printer
346 # lookup registered printer
345 try:
347 try:
346 printer = self.lookup(obj)
348 printer = self.lookup(obj)
347 except KeyError:
349 except KeyError:
348 pass
350 pass
349 else:
351 else:
350 return printer(obj)
352 return printer(obj)
351 # Finally look for special method names
353 # Finally look for special method names
352 method = _safe_get_formatter_method(obj, self.print_method)
354 method = _safe_get_formatter_method(obj, self.print_method)
353 if method is not None:
355 if method is not None:
354 return method()
356 return method()
355 return None
357 return None
356 else:
358 else:
357 return None
359 return None
358
360
359 def __contains__(self, typ):
361 def __contains__(self, typ):
360 """map in to lookup_by_type"""
362 """map in to lookup_by_type"""
361 try:
363 try:
362 self.lookup_by_type(typ)
364 self.lookup_by_type(typ)
363 except KeyError:
365 except KeyError:
364 return False
366 return False
365 else:
367 else:
366 return True
368 return True
367
369
368 def lookup(self, obj):
370 def lookup(self, obj):
369 """Look up the formatter for a given instance.
371 """Look up the formatter for a given instance.
370
372
371 Parameters
373 Parameters
372 ----------
374 ----------
373 obj : object instance
375 obj : object instance
374
376
375 Returns
377 Returns
376 -------
378 -------
377 f : callable
379 f : callable
378 The registered formatting callable for the type.
380 The registered formatting callable for the type.
379
381
380 Raises
382 Raises
381 ------
383 ------
382 KeyError if the type has not been registered.
384 KeyError if the type has not been registered.
383 """
385 """
384 # look for singleton first
386 # look for singleton first
385 obj_id = id(obj)
387 obj_id = id(obj)
386 if obj_id in self.singleton_printers:
388 if obj_id in self.singleton_printers:
387 return self.singleton_printers[obj_id]
389 return self.singleton_printers[obj_id]
388 # then lookup by type
390 # then lookup by type
389 return self.lookup_by_type(_get_type(obj))
391 return self.lookup_by_type(_get_type(obj))
390
392
391 def lookup_by_type(self, typ):
393 def lookup_by_type(self, typ):
392 """Look up the registered formatter for a type.
394 """Look up the registered formatter for a type.
393
395
394 Parameters
396 Parameters
395 ----------
397 ----------
396 typ : type or '__module__.__name__' string for a type
398 typ : type or '__module__.__name__' string for a type
397
399
398 Returns
400 Returns
399 -------
401 -------
400 f : callable
402 f : callable
401 The registered formatting callable for the type.
403 The registered formatting callable for the type.
402
404
403 Raises
405 Raises
404 ------
406 ------
405 KeyError if the type has not been registered.
407 KeyError if the type has not been registered.
406 """
408 """
407 if isinstance(typ, string_types):
409 if isinstance(typ, string_types):
408 typ_key = tuple(typ.rsplit('.',1))
410 typ_key = tuple(typ.rsplit('.',1))
409 if typ_key not in self.deferred_printers:
411 if typ_key not in self.deferred_printers:
410 # We may have it cached in the type map. We will have to
412 # We may have it cached in the type map. We will have to
411 # iterate over all of the types to check.
413 # iterate over all of the types to check.
412 for cls in self.type_printers:
414 for cls in self.type_printers:
413 if _mod_name_key(cls) == typ_key:
415 if _mod_name_key(cls) == typ_key:
414 return self.type_printers[cls]
416 return self.type_printers[cls]
415 else:
417 else:
416 return self.deferred_printers[typ_key]
418 return self.deferred_printers[typ_key]
417 else:
419 else:
418 for cls in pretty._get_mro(typ):
420 for cls in pretty._get_mro(typ):
419 if cls in self.type_printers or self._in_deferred_types(cls):
421 if cls in self.type_printers or self._in_deferred_types(cls):
420 return self.type_printers[cls]
422 return self.type_printers[cls]
421
423
422 # If we have reached here, the lookup failed.
424 # If we have reached here, the lookup failed.
423 raise KeyError("No registered printer for {0!r}".format(typ))
425 raise KeyError("No registered printer for {0!r}".format(typ))
424
426
425 def for_type(self, typ, func=None):
427 def for_type(self, typ, func=None):
426 """Add a format function for a given type.
428 """Add a format function for a given type.
427
429
428 Parameters
430 Parameters
429 -----------
431 -----------
430 typ : type or '__module__.__name__' string for a type
432 typ : type or '__module__.__name__' string for a type
431 The class of the object that will be formatted using `func`.
433 The class of the object that will be formatted using `func`.
432 func : callable
434 func : callable
433 A callable for computing the format data.
435 A callable for computing the format data.
434 `func` will be called with the object to be formatted,
436 `func` will be called with the object to be formatted,
435 and will return the raw data in this formatter's format.
437 and will return the raw data in this formatter's format.
436 Subclasses may use a different call signature for the
438 Subclasses may use a different call signature for the
437 `func` argument.
439 `func` argument.
438
440
439 If `func` is None or not specified, there will be no change,
441 If `func` is None or not specified, there will be no change,
440 only returning the current value.
442 only returning the current value.
441
443
442 Returns
444 Returns
443 -------
445 -------
444 oldfunc : callable
446 oldfunc : callable
445 The currently registered callable.
447 The currently registered callable.
446 If you are registering a new formatter,
448 If you are registering a new formatter,
447 this will be the previous value (to enable restoring later).
449 this will be the previous value (to enable restoring later).
448 """
450 """
449 # if string given, interpret as 'pkg.module.class_name'
451 # if string given, interpret as 'pkg.module.class_name'
450 if isinstance(typ, string_types):
452 if isinstance(typ, string_types):
451 type_module, type_name = typ.rsplit('.', 1)
453 type_module, type_name = typ.rsplit('.', 1)
452 return self.for_type_by_name(type_module, type_name, func)
454 return self.for_type_by_name(type_module, type_name, func)
453
455
454 try:
456 try:
455 oldfunc = self.lookup_by_type(typ)
457 oldfunc = self.lookup_by_type(typ)
456 except KeyError:
458 except KeyError:
457 oldfunc = None
459 oldfunc = None
458
460
459 if func is not None:
461 if func is not None:
460 self.type_printers[typ] = func
462 self.type_printers[typ] = func
461
463
462 return oldfunc
464 return oldfunc
463
465
464 def for_type_by_name(self, type_module, type_name, func=None):
466 def for_type_by_name(self, type_module, type_name, func=None):
465 """Add a format function for a type specified by the full dotted
467 """Add a format function for a type specified by the full dotted
466 module and name of the type, rather than the type of the object.
468 module and name of the type, rather than the type of the object.
467
469
468 Parameters
470 Parameters
469 ----------
471 ----------
470 type_module : str
472 type_module : str
471 The full dotted name of the module the type is defined in, like
473 The full dotted name of the module the type is defined in, like
472 ``numpy``.
474 ``numpy``.
473 type_name : str
475 type_name : str
474 The name of the type (the class name), like ``dtype``
476 The name of the type (the class name), like ``dtype``
475 func : callable
477 func : callable
476 A callable for computing the format data.
478 A callable for computing the format data.
477 `func` will be called with the object to be formatted,
479 `func` will be called with the object to be formatted,
478 and will return the raw data in this formatter's format.
480 and will return the raw data in this formatter's format.
479 Subclasses may use a different call signature for the
481 Subclasses may use a different call signature for the
480 `func` argument.
482 `func` argument.
481
483
482 If `func` is None or unspecified, there will be no change,
484 If `func` is None or unspecified, there will be no change,
483 only returning the current value.
485 only returning the current value.
484
486
485 Returns
487 Returns
486 -------
488 -------
487 oldfunc : callable
489 oldfunc : callable
488 The currently registered callable.
490 The currently registered callable.
489 If you are registering a new formatter,
491 If you are registering a new formatter,
490 this will be the previous value (to enable restoring later).
492 this will be the previous value (to enable restoring later).
491 """
493 """
492 key = (type_module, type_name)
494 key = (type_module, type_name)
493
495
494 try:
496 try:
495 oldfunc = self.lookup_by_type("%s.%s" % key)
497 oldfunc = self.lookup_by_type("%s.%s" % key)
496 except KeyError:
498 except KeyError:
497 oldfunc = None
499 oldfunc = None
498
500
499 if func is not None:
501 if func is not None:
500 self.deferred_printers[key] = func
502 self.deferred_printers[key] = func
501 return oldfunc
503 return oldfunc
502
504
503 def pop(self, typ, default=_raise_key_error):
505 def pop(self, typ, default=_raise_key_error):
504 """Pop a formatter for the given type.
506 """Pop a formatter for the given type.
505
507
506 Parameters
508 Parameters
507 ----------
509 ----------
508 typ : type or '__module__.__name__' string for a type
510 typ : type or '__module__.__name__' string for a type
509 default : object
511 default : object
510 value to be returned if no formatter is registered for typ.
512 value to be returned if no formatter is registered for typ.
511
513
512 Returns
514 Returns
513 -------
515 -------
514 obj : object
516 obj : object
515 The last registered object for the type.
517 The last registered object for the type.
516
518
517 Raises
519 Raises
518 ------
520 ------
519 KeyError if the type is not registered and default is not specified.
521 KeyError if the type is not registered and default is not specified.
520 """
522 """
521
523
522 if isinstance(typ, string_types):
524 if isinstance(typ, string_types):
523 typ_key = tuple(typ.rsplit('.',1))
525 typ_key = tuple(typ.rsplit('.',1))
524 if typ_key not in self.deferred_printers:
526 if typ_key not in self.deferred_printers:
525 # We may have it cached in the type map. We will have to
527 # We may have it cached in the type map. We will have to
526 # iterate over all of the types to check.
528 # iterate over all of the types to check.
527 for cls in self.type_printers:
529 for cls in self.type_printers:
528 if _mod_name_key(cls) == typ_key:
530 if _mod_name_key(cls) == typ_key:
529 old = self.type_printers.pop(cls)
531 old = self.type_printers.pop(cls)
530 break
532 break
531 else:
533 else:
532 old = default
534 old = default
533 else:
535 else:
534 old = self.deferred_printers.pop(typ_key)
536 old = self.deferred_printers.pop(typ_key)
535 else:
537 else:
536 if typ in self.type_printers:
538 if typ in self.type_printers:
537 old = self.type_printers.pop(typ)
539 old = self.type_printers.pop(typ)
538 else:
540 else:
539 old = self.deferred_printers.pop(_mod_name_key(typ), default)
541 old = self.deferred_printers.pop(_mod_name_key(typ), default)
540 if old is _raise_key_error:
542 if old is _raise_key_error:
541 raise KeyError("No registered value for {0!r}".format(typ))
543 raise KeyError("No registered value for {0!r}".format(typ))
542 return old
544 return old
543
545
544 def _in_deferred_types(self, cls):
546 def _in_deferred_types(self, cls):
545 """
547 """
546 Check if the given class is specified in the deferred type registry.
548 Check if the given class is specified in the deferred type registry.
547
549
548 Successful matches will be moved to the regular type registry for future use.
550 Successful matches will be moved to the regular type registry for future use.
549 """
551 """
550 mod = getattr(cls, '__module__', None)
552 mod = getattr(cls, '__module__', None)
551 name = getattr(cls, '__name__', None)
553 name = getattr(cls, '__name__', None)
552 key = (mod, name)
554 key = (mod, name)
553 if key in self.deferred_printers:
555 if key in self.deferred_printers:
554 # Move the printer over to the regular registry.
556 # Move the printer over to the regular registry.
555 printer = self.deferred_printers.pop(key)
557 printer = self.deferred_printers.pop(key)
556 self.type_printers[cls] = printer
558 self.type_printers[cls] = printer
557 return True
559 return True
558 return False
560 return False
559
561
560
562
561 class PlainTextFormatter(BaseFormatter):
563 class PlainTextFormatter(BaseFormatter):
562 """The default pretty-printer.
564 """The default pretty-printer.
563
565
564 This uses :mod:`IPython.lib.pretty` to compute the format data of
566 This uses :mod:`IPython.lib.pretty` to compute the format data of
565 the object. If the object cannot be pretty printed, :func:`repr` is used.
567 the object. If the object cannot be pretty printed, :func:`repr` is used.
566 See the documentation of :mod:`IPython.lib.pretty` for details on
568 See the documentation of :mod:`IPython.lib.pretty` for details on
567 how to write pretty printers. Here is a simple example::
569 how to write pretty printers. Here is a simple example::
568
570
569 def dtype_pprinter(obj, p, cycle):
571 def dtype_pprinter(obj, p, cycle):
570 if cycle:
572 if cycle:
571 return p.text('dtype(...)')
573 return p.text('dtype(...)')
572 if hasattr(obj, 'fields'):
574 if hasattr(obj, 'fields'):
573 if obj.fields is None:
575 if obj.fields is None:
574 p.text(repr(obj))
576 p.text(repr(obj))
575 else:
577 else:
576 p.begin_group(7, 'dtype([')
578 p.begin_group(7, 'dtype([')
577 for i, field in enumerate(obj.descr):
579 for i, field in enumerate(obj.descr):
578 if i > 0:
580 if i > 0:
579 p.text(',')
581 p.text(',')
580 p.breakable()
582 p.breakable()
581 p.pretty(field)
583 p.pretty(field)
582 p.end_group(7, '])')
584 p.end_group(7, '])')
583 """
585 """
584
586
585 # The format type of data returned.
587 # The format type of data returned.
586 format_type = Unicode('text/plain')
588 format_type = Unicode('text/plain')
587
589
588 # This subclass ignores this attribute as it always need to return
590 # This subclass ignores this attribute as it always need to return
589 # something.
591 # something.
590 enabled = Bool(True, config=False)
592 enabled = Bool(True, config=False)
591
593
592 # Look for a _repr_pretty_ methods to use for pretty printing.
594 # Look for a _repr_pretty_ methods to use for pretty printing.
593 print_method = ObjectName('_repr_pretty_')
595 print_method = ObjectName('_repr_pretty_')
594
596
595 # Whether to pretty-print or not.
597 # Whether to pretty-print or not.
596 pprint = Bool(True, config=True)
598 pprint = Bool(True, config=True)
597
599
598 # Whether to be verbose or not.
600 # Whether to be verbose or not.
599 verbose = Bool(False, config=True)
601 verbose = Bool(False, config=True)
600
602
601 # The maximum width.
603 # The maximum width.
602 max_width = Integer(79, config=True)
604 max_width = Integer(79, config=True)
603
605
604 # The newline character.
606 # The newline character.
605 newline = Unicode('\n', config=True)
607 newline = Unicode('\n', config=True)
606
608
607 # format-string for pprinting floats
609 # format-string for pprinting floats
608 float_format = Unicode('%r')
610 float_format = Unicode('%r')
609 # setter for float precision, either int or direct format-string
611 # setter for float precision, either int or direct format-string
610 float_precision = CUnicode('', config=True)
612 float_precision = CUnicode('', config=True)
611
613
612 def _float_precision_changed(self, name, old, new):
614 def _float_precision_changed(self, name, old, new):
613 """float_precision changed, set float_format accordingly.
615 """float_precision changed, set float_format accordingly.
614
616
615 float_precision can be set by int or str.
617 float_precision can be set by int or str.
616 This will set float_format, after interpreting input.
618 This will set float_format, after interpreting input.
617 If numpy has been imported, numpy print precision will also be set.
619 If numpy has been imported, numpy print precision will also be set.
618
620
619 integer `n` sets format to '%.nf', otherwise, format set directly.
621 integer `n` sets format to '%.nf', otherwise, format set directly.
620
622
621 An empty string returns to defaults (repr for float, 8 for numpy).
623 An empty string returns to defaults (repr for float, 8 for numpy).
622
624
623 This parameter can be set via the '%precision' magic.
625 This parameter can be set via the '%precision' magic.
624 """
626 """
625
627
626 if '%' in new:
628 if '%' in new:
627 # got explicit format string
629 # got explicit format string
628 fmt = new
630 fmt = new
629 try:
631 try:
630 fmt%3.14159
632 fmt%3.14159
631 except Exception:
633 except Exception:
632 raise ValueError("Precision must be int or format string, not %r"%new)
634 raise ValueError("Precision must be int or format string, not %r"%new)
633 elif new:
635 elif new:
634 # otherwise, should be an int
636 # otherwise, should be an int
635 try:
637 try:
636 i = int(new)
638 i = int(new)
637 assert i >= 0
639 assert i >= 0
638 except ValueError:
640 except ValueError:
639 raise ValueError("Precision must be int or format string, not %r"%new)
641 raise ValueError("Precision must be int or format string, not %r"%new)
640 except AssertionError:
642 except AssertionError:
641 raise ValueError("int precision must be non-negative, not %r"%i)
643 raise ValueError("int precision must be non-negative, not %r"%i)
642
644
643 fmt = '%%.%if'%i
645 fmt = '%%.%if'%i
644 if 'numpy' in sys.modules:
646 if 'numpy' in sys.modules:
645 # set numpy precision if it has been imported
647 # set numpy precision if it has been imported
646 import numpy
648 import numpy
647 numpy.set_printoptions(precision=i)
649 numpy.set_printoptions(precision=i)
648 else:
650 else:
649 # default back to repr
651 # default back to repr
650 fmt = '%r'
652 fmt = '%r'
651 if 'numpy' in sys.modules:
653 if 'numpy' in sys.modules:
652 import numpy
654 import numpy
653 # numpy default is 8
655 # numpy default is 8
654 numpy.set_printoptions(precision=8)
656 numpy.set_printoptions(precision=8)
655 self.float_format = fmt
657 self.float_format = fmt
656
658
657 # Use the default pretty printers from IPython.lib.pretty.
659 # Use the default pretty printers from IPython.lib.pretty.
658 def _singleton_printers_default(self):
660 def _singleton_printers_default(self):
659 return pretty._singleton_pprinters.copy()
661 return pretty._singleton_pprinters.copy()
660
662
661 def _type_printers_default(self):
663 def _type_printers_default(self):
662 d = pretty._type_pprinters.copy()
664 d = pretty._type_pprinters.copy()
663 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
665 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
664 return d
666 return d
665
667
666 def _deferred_printers_default(self):
668 def _deferred_printers_default(self):
667 return pretty._deferred_type_pprinters.copy()
669 return pretty._deferred_type_pprinters.copy()
668
670
669 #### FormatterABC interface ####
671 #### FormatterABC interface ####
670
672
671 @warn_format_error
673 @warn_format_error
672 def __call__(self, obj):
674 def __call__(self, obj):
673 """Compute the pretty representation of the object."""
675 """Compute the pretty representation of the object."""
674 if not self.pprint:
676 if not self.pprint:
675 return pretty._safe_repr(obj)
677 return pretty._safe_repr(obj)
676 else:
678 else:
677 # This uses use StringIO, as cStringIO doesn't handle unicode.
679 # This uses use StringIO, as cStringIO doesn't handle unicode.
678 stream = StringIO()
680 stream = StringIO()
679 # self.newline.encode() is a quick fix for issue gh-597. We need to
681 # self.newline.encode() is a quick fix for issue gh-597. We need to
680 # ensure that stream does not get a mix of unicode and bytestrings,
682 # ensure that stream does not get a mix of unicode and bytestrings,
681 # or it will cause trouble.
683 # or it will cause trouble.
682 printer = pretty.RepresentationPrinter(stream, self.verbose,
684 printer = pretty.RepresentationPrinter(stream, self.verbose,
683 self.max_width, unicode_to_str(self.newline),
685 self.max_width, unicode_to_str(self.newline),
684 singleton_pprinters=self.singleton_printers,
686 singleton_pprinters=self.singleton_printers,
685 type_pprinters=self.type_printers,
687 type_pprinters=self.type_printers,
686 deferred_pprinters=self.deferred_printers)
688 deferred_pprinters=self.deferred_printers)
687 printer.pretty(obj)
689 printer.pretty(obj)
688 printer.flush()
690 printer.flush()
689 return stream.getvalue()
691 return stream.getvalue()
690
692
691
693
692 class HTMLFormatter(BaseFormatter):
694 class HTMLFormatter(BaseFormatter):
693 """An HTML formatter.
695 """An HTML formatter.
694
696
695 To define the callables that compute the HTML representation of your
697 To define the callables that compute the HTML representation of your
696 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
698 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
697 or :meth:`for_type_by_name` methods to register functions that handle
699 or :meth:`for_type_by_name` methods to register functions that handle
698 this.
700 this.
699
701
700 The return value of this formatter should be a valid HTML snippet that
702 The return value of this formatter should be a valid HTML snippet that
701 could be injected into an existing DOM. It should *not* include the
703 could be injected into an existing DOM. It should *not* include the
702 ```<html>`` or ```<body>`` tags.
704 ```<html>`` or ```<body>`` tags.
703 """
705 """
704 format_type = Unicode('text/html')
706 format_type = Unicode('text/html')
705
707
706 print_method = ObjectName('_repr_html_')
708 print_method = ObjectName('_repr_html_')
707
709
708
710
711 class MarkdownFormatter(BaseFormatter):
712 """A Markdown formatter.
713
714 To define the callables that compute the Markdown representation of your
715 objects, define a :meth:`_repr_markdown_` method or use the :meth:`for_type`
716 or :meth:`for_type_by_name` methods to register functions that handle
717 this.
718
719 The return value of this formatter should be a valid Markdown.
720 """
721 format_type = Unicode('text/markdown')
722
723 print_method = ObjectName('_repr_markdown_')
724
709 class SVGFormatter(BaseFormatter):
725 class SVGFormatter(BaseFormatter):
710 """An SVG formatter.
726 """An SVG formatter.
711
727
712 To define the callables that compute the SVG representation of your
728 To define the callables that compute the SVG representation of your
713 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
729 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
714 or :meth:`for_type_by_name` methods to register functions that handle
730 or :meth:`for_type_by_name` methods to register functions that handle
715 this.
731 this.
716
732
717 The return value of this formatter should be valid SVG enclosed in
733 The return value of this formatter should be valid SVG enclosed in
718 ```<svg>``` tags, that could be injected into an existing DOM. It should
734 ```<svg>``` tags, that could be injected into an existing DOM. It should
719 *not* include the ```<html>`` or ```<body>`` tags.
735 *not* include the ```<html>`` or ```<body>`` tags.
720 """
736 """
721 format_type = Unicode('image/svg+xml')
737 format_type = Unicode('image/svg+xml')
722
738
723 print_method = ObjectName('_repr_svg_')
739 print_method = ObjectName('_repr_svg_')
724
740
725
741
726 class PNGFormatter(BaseFormatter):
742 class PNGFormatter(BaseFormatter):
727 """A PNG formatter.
743 """A PNG formatter.
728
744
729 To define the callables that compute the PNG representation of your
745 To define the callables that compute the PNG representation of your
730 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
746 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
731 or :meth:`for_type_by_name` methods to register functions that handle
747 or :meth:`for_type_by_name` methods to register functions that handle
732 this.
748 this.
733
749
734 The return value of this formatter should be raw PNG data, *not*
750 The return value of this formatter should be raw PNG data, *not*
735 base64 encoded.
751 base64 encoded.
736 """
752 """
737 format_type = Unicode('image/png')
753 format_type = Unicode('image/png')
738
754
739 print_method = ObjectName('_repr_png_')
755 print_method = ObjectName('_repr_png_')
740
756
741 _return_type = (bytes, unicode_type)
757 _return_type = (bytes, unicode_type)
742
758
743
759
744 class JPEGFormatter(BaseFormatter):
760 class JPEGFormatter(BaseFormatter):
745 """A JPEG formatter.
761 """A JPEG formatter.
746
762
747 To define the callables that compute the JPEG representation of your
763 To define the callables that compute the JPEG representation of your
748 objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
764 objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
749 or :meth:`for_type_by_name` methods to register functions that handle
765 or :meth:`for_type_by_name` methods to register functions that handle
750 this.
766 this.
751
767
752 The return value of this formatter should be raw JPEG data, *not*
768 The return value of this formatter should be raw JPEG data, *not*
753 base64 encoded.
769 base64 encoded.
754 """
770 """
755 format_type = Unicode('image/jpeg')
771 format_type = Unicode('image/jpeg')
756
772
757 print_method = ObjectName('_repr_jpeg_')
773 print_method = ObjectName('_repr_jpeg_')
758
774
759 _return_type = (bytes, unicode_type)
775 _return_type = (bytes, unicode_type)
760
776
761
777
762 class LatexFormatter(BaseFormatter):
778 class LatexFormatter(BaseFormatter):
763 """A LaTeX formatter.
779 """A LaTeX formatter.
764
780
765 To define the callables that compute the LaTeX representation of your
781 To define the callables that compute the LaTeX representation of your
766 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
782 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
767 or :meth:`for_type_by_name` methods to register functions that handle
783 or :meth:`for_type_by_name` methods to register functions that handle
768 this.
784 this.
769
785
770 The return value of this formatter should be a valid LaTeX equation,
786 The return value of this formatter should be a valid LaTeX equation,
771 enclosed in either ```$```, ```$$``` or another LaTeX equation
787 enclosed in either ```$```, ```$$``` or another LaTeX equation
772 environment.
788 environment.
773 """
789 """
774 format_type = Unicode('text/latex')
790 format_type = Unicode('text/latex')
775
791
776 print_method = ObjectName('_repr_latex_')
792 print_method = ObjectName('_repr_latex_')
777
793
778
794
779 class JSONFormatter(BaseFormatter):
795 class JSONFormatter(BaseFormatter):
780 """A JSON string formatter.
796 """A JSON string formatter.
781
797
782 To define the callables that compute the JSON string representation of
798 To define the callables that compute the JSON string representation of
783 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
799 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
784 or :meth:`for_type_by_name` methods to register functions that handle
800 or :meth:`for_type_by_name` methods to register functions that handle
785 this.
801 this.
786
802
787 The return value of this formatter should be a valid JSON string.
803 The return value of this formatter should be a valid JSON string.
788 """
804 """
789 format_type = Unicode('application/json')
805 format_type = Unicode('application/json')
790
806
791 print_method = ObjectName('_repr_json_')
807 print_method = ObjectName('_repr_json_')
792
808
793
809
794 class JavascriptFormatter(BaseFormatter):
810 class JavascriptFormatter(BaseFormatter):
795 """A Javascript formatter.
811 """A Javascript formatter.
796
812
797 To define the callables that compute the Javascript representation of
813 To define the callables that compute the Javascript representation of
798 your objects, define a :meth:`_repr_javascript_` method or use the
814 your objects, define a :meth:`_repr_javascript_` method or use the
799 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
815 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
800 that handle this.
816 that handle this.
801
817
802 The return value of this formatter should be valid Javascript code and
818 The return value of this formatter should be valid Javascript code and
803 should *not* be enclosed in ```<script>``` tags.
819 should *not* be enclosed in ```<script>``` tags.
804 """
820 """
805 format_type = Unicode('application/javascript')
821 format_type = Unicode('application/javascript')
806
822
807 print_method = ObjectName('_repr_javascript_')
823 print_method = ObjectName('_repr_javascript_')
808
824
809
825
810 class PDFFormatter(BaseFormatter):
826 class PDFFormatter(BaseFormatter):
811 """A PDF formatter.
827 """A PDF formatter.
812
828
813 To define the callables that compute the PDF representation of your
829 To define the callables that compute the PDF representation of your
814 objects, define a :meth:`_repr_pdf_` method or use the :meth:`for_type`
830 objects, define a :meth:`_repr_pdf_` method or use the :meth:`for_type`
815 or :meth:`for_type_by_name` methods to register functions that handle
831 or :meth:`for_type_by_name` methods to register functions that handle
816 this.
832 this.
817
833
818 The return value of this formatter should be raw PDF data, *not*
834 The return value of this formatter should be raw PDF data, *not*
819 base64 encoded.
835 base64 encoded.
820 """
836 """
821 format_type = Unicode('application/pdf')
837 format_type = Unicode('application/pdf')
822
838
823 print_method = ObjectName('_repr_pdf_')
839 print_method = ObjectName('_repr_pdf_')
824
840
825
841
826 FormatterABC.register(BaseFormatter)
842 FormatterABC.register(BaseFormatter)
827 FormatterABC.register(PlainTextFormatter)
843 FormatterABC.register(PlainTextFormatter)
828 FormatterABC.register(HTMLFormatter)
844 FormatterABC.register(HTMLFormatter)
845 FormatterABC.register(MarkdownFormatter)
829 FormatterABC.register(SVGFormatter)
846 FormatterABC.register(SVGFormatter)
830 FormatterABC.register(PNGFormatter)
847 FormatterABC.register(PNGFormatter)
831 FormatterABC.register(PDFFormatter)
848 FormatterABC.register(PDFFormatter)
832 FormatterABC.register(JPEGFormatter)
849 FormatterABC.register(JPEGFormatter)
833 FormatterABC.register(LatexFormatter)
850 FormatterABC.register(LatexFormatter)
834 FormatterABC.register(JSONFormatter)
851 FormatterABC.register(JSONFormatter)
835 FormatterABC.register(JavascriptFormatter)
852 FormatterABC.register(JavascriptFormatter)
836
853
837
854
838 def format_display_data(obj, include=None, exclude=None):
855 def format_display_data(obj, include=None, exclude=None):
839 """Return a format data dict for an object.
856 """Return a format data dict for an object.
840
857
841 By default all format types will be computed.
858 By default all format types will be computed.
842
859
843 The following MIME types are currently implemented:
860 The following MIME types are currently implemented:
844
861
845 * text/plain
862 * text/plain
846 * text/html
863 * text/html
864 * text/markdown
847 * text/latex
865 * text/latex
848 * application/json
866 * application/json
849 * application/javascript
867 * application/javascript
850 * application/pdf
868 * application/pdf
851 * image/png
869 * image/png
852 * image/jpeg
870 * image/jpeg
853 * image/svg+xml
871 * image/svg+xml
854
872
855 Parameters
873 Parameters
856 ----------
874 ----------
857 obj : object
875 obj : object
858 The Python object whose format data will be computed.
876 The Python object whose format data will be computed.
859
877
860 Returns
878 Returns
861 -------
879 -------
862 format_dict : dict
880 format_dict : dict
863 A dictionary of key/value pairs, one or each format that was
881 A dictionary of key/value pairs, one or each format that was
864 generated for the object. The keys are the format types, which
882 generated for the object. The keys are the format types, which
865 will usually be MIME type strings and the values and JSON'able
883 will usually be MIME type strings and the values and JSON'able
866 data structure containing the raw data for the representation in
884 data structure containing the raw data for the representation in
867 that format.
885 that format.
868 include : list or tuple, optional
886 include : list or tuple, optional
869 A list of format type strings (MIME types) to include in the
887 A list of format type strings (MIME types) to include in the
870 format data dict. If this is set *only* the format types included
888 format data dict. If this is set *only* the format types included
871 in this list will be computed.
889 in this list will be computed.
872 exclude : list or tuple, optional
890 exclude : list or tuple, optional
873 A list of format type string (MIME types) to exclue in the format
891 A list of format type string (MIME types) to exclue in the format
874 data dict. If this is set all format types will be computed,
892 data dict. If this is set all format types will be computed,
875 except for those included in this argument.
893 except for those included in this argument.
876 """
894 """
877 from IPython.core.interactiveshell import InteractiveShell
895 from IPython.core.interactiveshell import InteractiveShell
878
896
879 InteractiveShell.instance().display_formatter.format(
897 InteractiveShell.instance().display_formatter.format(
880 obj,
898 obj,
881 include,
899 include,
882 exclude
900 exclude
883 )
901 )
884
902
@@ -1,942 +1,963
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008 The IPython Development Team
2 // Copyright (C) 2008 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // OutputArea
9 // OutputArea
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule OutputArea
15 * @submodule OutputArea
16 */
16 */
17 var IPython = (function (IPython) {
17 var IPython = (function (IPython) {
18 "use strict";
18 "use strict";
19
19
20 var utils = IPython.utils;
20 var utils = IPython.utils;
21
21
22 /**
22 /**
23 * @class OutputArea
23 * @class OutputArea
24 *
24 *
25 * @constructor
25 * @constructor
26 */
26 */
27
27
28 var OutputArea = function (selector, prompt_area) {
28 var OutputArea = function (selector, prompt_area) {
29 this.selector = selector;
29 this.selector = selector;
30 this.wrapper = $(selector);
30 this.wrapper = $(selector);
31 this.outputs = [];
31 this.outputs = [];
32 this.collapsed = false;
32 this.collapsed = false;
33 this.scrolled = false;
33 this.scrolled = false;
34 this.trusted = true;
34 this.trusted = true;
35 this.clear_queued = null;
35 this.clear_queued = null;
36 if (prompt_area === undefined) {
36 if (prompt_area === undefined) {
37 this.prompt_area = true;
37 this.prompt_area = true;
38 } else {
38 } else {
39 this.prompt_area = prompt_area;
39 this.prompt_area = prompt_area;
40 }
40 }
41 this.create_elements();
41 this.create_elements();
42 this.style();
42 this.style();
43 this.bind_events();
43 this.bind_events();
44 };
44 };
45
45
46
46
47 /**
47 /**
48 * Class prototypes
48 * Class prototypes
49 **/
49 **/
50
50
51 OutputArea.prototype.create_elements = function () {
51 OutputArea.prototype.create_elements = function () {
52 this.element = $("<div/>");
52 this.element = $("<div/>");
53 this.collapse_button = $("<div/>");
53 this.collapse_button = $("<div/>");
54 this.prompt_overlay = $("<div/>");
54 this.prompt_overlay = $("<div/>");
55 this.wrapper.append(this.prompt_overlay);
55 this.wrapper.append(this.prompt_overlay);
56 this.wrapper.append(this.element);
56 this.wrapper.append(this.element);
57 this.wrapper.append(this.collapse_button);
57 this.wrapper.append(this.collapse_button);
58 };
58 };
59
59
60
60
61 OutputArea.prototype.style = function () {
61 OutputArea.prototype.style = function () {
62 this.collapse_button.hide();
62 this.collapse_button.hide();
63 this.prompt_overlay.hide();
63 this.prompt_overlay.hide();
64
64
65 this.wrapper.addClass('output_wrapper');
65 this.wrapper.addClass('output_wrapper');
66 this.element.addClass('output');
66 this.element.addClass('output');
67
67
68 this.collapse_button.addClass("btn output_collapsed");
68 this.collapse_button.addClass("btn output_collapsed");
69 this.collapse_button.attr('title', 'click to expand output');
69 this.collapse_button.attr('title', 'click to expand output');
70 this.collapse_button.text('. . .');
70 this.collapse_button.text('. . .');
71
71
72 this.prompt_overlay.addClass('out_prompt_overlay prompt');
72 this.prompt_overlay.addClass('out_prompt_overlay prompt');
73 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
73 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
74
74
75 this.collapse();
75 this.collapse();
76 };
76 };
77
77
78 /**
78 /**
79 * Should the OutputArea scroll?
79 * Should the OutputArea scroll?
80 * Returns whether the height (in lines) exceeds a threshold.
80 * Returns whether the height (in lines) exceeds a threshold.
81 *
81 *
82 * @private
82 * @private
83 * @method _should_scroll
83 * @method _should_scroll
84 * @param [lines=100]{Integer}
84 * @param [lines=100]{Integer}
85 * @return {Bool}
85 * @return {Bool}
86 *
86 *
87 */
87 */
88 OutputArea.prototype._should_scroll = function (lines) {
88 OutputArea.prototype._should_scroll = function (lines) {
89 if (lines <=0 ){ return }
89 if (lines <=0 ){ return }
90 if (!lines) {
90 if (!lines) {
91 lines = 100;
91 lines = 100;
92 }
92 }
93 // line-height from http://stackoverflow.com/questions/1185151
93 // line-height from http://stackoverflow.com/questions/1185151
94 var fontSize = this.element.css('font-size');
94 var fontSize = this.element.css('font-size');
95 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
95 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
96
96
97 return (this.element.height() > lines * lineHeight);
97 return (this.element.height() > lines * lineHeight);
98 };
98 };
99
99
100
100
101 OutputArea.prototype.bind_events = function () {
101 OutputArea.prototype.bind_events = function () {
102 var that = this;
102 var that = this;
103 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
103 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
104 this.prompt_overlay.click(function () { that.toggle_scroll(); });
104 this.prompt_overlay.click(function () { that.toggle_scroll(); });
105
105
106 this.element.resize(function () {
106 this.element.resize(function () {
107 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
107 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
108 if ( IPython.utils.browser[0] === "Firefox" ) {
108 if ( IPython.utils.browser[0] === "Firefox" ) {
109 return;
109 return;
110 }
110 }
111 // maybe scroll output,
111 // maybe scroll output,
112 // if it's grown large enough and hasn't already been scrolled.
112 // if it's grown large enough and hasn't already been scrolled.
113 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
113 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
114 that.scroll_area();
114 that.scroll_area();
115 }
115 }
116 });
116 });
117 this.collapse_button.click(function () {
117 this.collapse_button.click(function () {
118 that.expand();
118 that.expand();
119 });
119 });
120 };
120 };
121
121
122
122
123 OutputArea.prototype.collapse = function () {
123 OutputArea.prototype.collapse = function () {
124 if (!this.collapsed) {
124 if (!this.collapsed) {
125 this.element.hide();
125 this.element.hide();
126 this.prompt_overlay.hide();
126 this.prompt_overlay.hide();
127 if (this.element.html()){
127 if (this.element.html()){
128 this.collapse_button.show();
128 this.collapse_button.show();
129 }
129 }
130 this.collapsed = true;
130 this.collapsed = true;
131 }
131 }
132 };
132 };
133
133
134
134
135 OutputArea.prototype.expand = function () {
135 OutputArea.prototype.expand = function () {
136 if (this.collapsed) {
136 if (this.collapsed) {
137 this.collapse_button.hide();
137 this.collapse_button.hide();
138 this.element.show();
138 this.element.show();
139 this.prompt_overlay.show();
139 this.prompt_overlay.show();
140 this.collapsed = false;
140 this.collapsed = false;
141 }
141 }
142 };
142 };
143
143
144
144
145 OutputArea.prototype.toggle_output = function () {
145 OutputArea.prototype.toggle_output = function () {
146 if (this.collapsed) {
146 if (this.collapsed) {
147 this.expand();
147 this.expand();
148 } else {
148 } else {
149 this.collapse();
149 this.collapse();
150 }
150 }
151 };
151 };
152
152
153
153
154 OutputArea.prototype.scroll_area = function () {
154 OutputArea.prototype.scroll_area = function () {
155 this.element.addClass('output_scroll');
155 this.element.addClass('output_scroll');
156 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
156 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
157 this.scrolled = true;
157 this.scrolled = true;
158 };
158 };
159
159
160
160
161 OutputArea.prototype.unscroll_area = function () {
161 OutputArea.prototype.unscroll_area = function () {
162 this.element.removeClass('output_scroll');
162 this.element.removeClass('output_scroll');
163 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
163 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
164 this.scrolled = false;
164 this.scrolled = false;
165 };
165 };
166
166
167 /**
167 /**
168 *
168 *
169 * Scroll OutputArea if height supperior than a threshold (in lines).
169 * Scroll OutputArea if height supperior than a threshold (in lines).
170 *
170 *
171 * Threshold is a maximum number of lines. If unspecified, defaults to
171 * Threshold is a maximum number of lines. If unspecified, defaults to
172 * OutputArea.minimum_scroll_threshold.
172 * OutputArea.minimum_scroll_threshold.
173 *
173 *
174 * Negative threshold will prevent the OutputArea from ever scrolling.
174 * Negative threshold will prevent the OutputArea from ever scrolling.
175 *
175 *
176 * @method scroll_if_long
176 * @method scroll_if_long
177 *
177 *
178 * @param [lines=20]{Number} Default to 20 if not set,
178 * @param [lines=20]{Number} Default to 20 if not set,
179 * behavior undefined for value of `0`.
179 * behavior undefined for value of `0`.
180 *
180 *
181 **/
181 **/
182 OutputArea.prototype.scroll_if_long = function (lines) {
182 OutputArea.prototype.scroll_if_long = function (lines) {
183 var n = lines | OutputArea.minimum_scroll_threshold;
183 var n = lines | OutputArea.minimum_scroll_threshold;
184 if(n <= 0){
184 if(n <= 0){
185 return
185 return
186 }
186 }
187
187
188 if (this._should_scroll(n)) {
188 if (this._should_scroll(n)) {
189 // only allow scrolling long-enough output
189 // only allow scrolling long-enough output
190 this.scroll_area();
190 this.scroll_area();
191 }
191 }
192 };
192 };
193
193
194
194
195 OutputArea.prototype.toggle_scroll = function () {
195 OutputArea.prototype.toggle_scroll = function () {
196 if (this.scrolled) {
196 if (this.scrolled) {
197 this.unscroll_area();
197 this.unscroll_area();
198 } else {
198 } else {
199 // only allow scrolling long-enough output
199 // only allow scrolling long-enough output
200 this.scroll_if_long();
200 this.scroll_if_long();
201 }
201 }
202 };
202 };
203
203
204
204
205 // typeset with MathJax if MathJax is available
205 // typeset with MathJax if MathJax is available
206 OutputArea.prototype.typeset = function () {
206 OutputArea.prototype.typeset = function () {
207 if (window.MathJax){
207 if (window.MathJax){
208 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
208 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
209 }
209 }
210 };
210 };
211
211
212
212
213 OutputArea.prototype.handle_output = function (msg) {
213 OutputArea.prototype.handle_output = function (msg) {
214 var json = {};
214 var json = {};
215 var msg_type = json.output_type = msg.header.msg_type;
215 var msg_type = json.output_type = msg.header.msg_type;
216 var content = msg.content;
216 var content = msg.content;
217 if (msg_type === "stream") {
217 if (msg_type === "stream") {
218 json.text = content.data;
218 json.text = content.data;
219 json.stream = content.name;
219 json.stream = content.name;
220 } else if (msg_type === "display_data") {
220 } else if (msg_type === "display_data") {
221 json = content.data;
221 json = content.data;
222 json.output_type = msg_type;
222 json.output_type = msg_type;
223 json.metadata = content.metadata;
223 json.metadata = content.metadata;
224 } else if (msg_type === "pyout") {
224 } else if (msg_type === "pyout") {
225 json = content.data;
225 json = content.data;
226 json.output_type = msg_type;
226 json.output_type = msg_type;
227 json.metadata = content.metadata;
227 json.metadata = content.metadata;
228 json.prompt_number = content.execution_count;
228 json.prompt_number = content.execution_count;
229 } else if (msg_type === "pyerr") {
229 } else if (msg_type === "pyerr") {
230 json.ename = content.ename;
230 json.ename = content.ename;
231 json.evalue = content.evalue;
231 json.evalue = content.evalue;
232 json.traceback = content.traceback;
232 json.traceback = content.traceback;
233 }
233 }
234 this.append_output(json);
234 this.append_output(json);
235 };
235 };
236
236
237
237
238 OutputArea.prototype.rename_keys = function (data, key_map) {
238 OutputArea.prototype.rename_keys = function (data, key_map) {
239 var remapped = {};
239 var remapped = {};
240 for (var key in data) {
240 for (var key in data) {
241 var new_key = key_map[key] || key;
241 var new_key = key_map[key] || key;
242 remapped[new_key] = data[key];
242 remapped[new_key] = data[key];
243 }
243 }
244 return remapped;
244 return remapped;
245 };
245 };
246
246
247
247
248 OutputArea.output_types = [
248 OutputArea.output_types = [
249 'application/javascript',
249 'application/javascript',
250 'text/html',
250 'text/html',
251 'text/markdown',
251 'text/latex',
252 'text/latex',
252 'image/svg+xml',
253 'image/svg+xml',
253 'image/png',
254 'image/png',
254 'image/jpeg',
255 'image/jpeg',
255 'application/pdf',
256 'application/pdf',
256 'text/plain'
257 'text/plain'
257 ];
258 ];
258
259
259 OutputArea.prototype.validate_output = function (json) {
260 OutputArea.prototype.validate_output = function (json) {
260 // scrub invalid outputs
261 // scrub invalid outputs
261 // TODO: right now everything is a string, but JSON really shouldn't be.
262 // TODO: right now everything is a string, but JSON really shouldn't be.
262 // nbformat 4 will fix that.
263 // nbformat 4 will fix that.
263 $.map(OutputArea.output_types, function(key){
264 $.map(OutputArea.output_types, function(key){
264 if (json[key] !== undefined && typeof json[key] !== 'string') {
265 if (json[key] !== undefined && typeof json[key] !== 'string') {
265 console.log("Invalid type for " + key, json[key]);
266 console.log("Invalid type for " + key, json[key]);
266 delete json[key];
267 delete json[key];
267 }
268 }
268 });
269 });
269 return json;
270 return json;
270 };
271 };
271
272
272 OutputArea.prototype.append_output = function (json) {
273 OutputArea.prototype.append_output = function (json) {
273 this.expand();
274 this.expand();
274
275
275 // validate output data types
276 // validate output data types
276 json = this.validate_output(json);
277 json = this.validate_output(json);
277
278
278 // Clear the output if clear is queued.
279 // Clear the output if clear is queued.
279 var needs_height_reset = false;
280 var needs_height_reset = false;
280 if (this.clear_queued) {
281 if (this.clear_queued) {
281 this.clear_output(false);
282 this.clear_output(false);
282 needs_height_reset = true;
283 needs_height_reset = true;
283 }
284 }
284
285
285 if (json.output_type === 'pyout') {
286 if (json.output_type === 'pyout') {
286 this.append_pyout(json);
287 this.append_pyout(json);
287 } else if (json.output_type === 'pyerr') {
288 } else if (json.output_type === 'pyerr') {
288 this.append_pyerr(json);
289 this.append_pyerr(json);
289 } else if (json.output_type === 'stream') {
290 } else if (json.output_type === 'stream') {
290 this.append_stream(json);
291 this.append_stream(json);
291 }
292 }
292
293
293 // We must release the animation fixed height in a callback since Gecko
294 // We must release the animation fixed height in a callback since Gecko
294 // (FireFox) doesn't render the image immediately as the data is
295 // (FireFox) doesn't render the image immediately as the data is
295 // available.
296 // available.
296 var that = this;
297 var that = this;
297 var handle_appended = function ($el) {
298 var handle_appended = function ($el) {
298 // Only reset the height to automatic if the height is currently
299 // Only reset the height to automatic if the height is currently
299 // fixed (done by wait=True flag on clear_output).
300 // fixed (done by wait=True flag on clear_output).
300 if (needs_height_reset) {
301 if (needs_height_reset) {
301 that.element.height('');
302 that.element.height('');
302 }
303 }
303 that.element.trigger('resize');
304 that.element.trigger('resize');
304 };
305 };
305 if (json.output_type === 'display_data') {
306 if (json.output_type === 'display_data') {
306 this.append_display_data(json, handle_appended);
307 this.append_display_data(json, handle_appended);
307 } else {
308 } else {
308 handle_appended();
309 handle_appended();
309 }
310 }
310
311
311 this.outputs.push(json);
312 this.outputs.push(json);
312 };
313 };
313
314
314
315
315 OutputArea.prototype.create_output_area = function () {
316 OutputArea.prototype.create_output_area = function () {
316 var oa = $("<div/>").addClass("output_area");
317 var oa = $("<div/>").addClass("output_area");
317 if (this.prompt_area) {
318 if (this.prompt_area) {
318 oa.append($('<div/>').addClass('prompt'));
319 oa.append($('<div/>').addClass('prompt'));
319 }
320 }
320 return oa;
321 return oa;
321 };
322 };
322
323
323
324
324 function _get_metadata_key(metadata, key, mime) {
325 function _get_metadata_key(metadata, key, mime) {
325 var mime_md = metadata[mime];
326 var mime_md = metadata[mime];
326 // mime-specific higher priority
327 // mime-specific higher priority
327 if (mime_md && mime_md[key] !== undefined) {
328 if (mime_md && mime_md[key] !== undefined) {
328 return mime_md[key];
329 return mime_md[key];
329 }
330 }
330 // fallback on global
331 // fallback on global
331 return metadata[key];
332 return metadata[key];
332 }
333 }
333
334
334 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
335 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
335 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
336 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
336 if (_get_metadata_key(md, 'isolated', mime)) {
337 if (_get_metadata_key(md, 'isolated', mime)) {
337 // Create an iframe to isolate the subarea from the rest of the
338 // Create an iframe to isolate the subarea from the rest of the
338 // document
339 // document
339 var iframe = $('<iframe/>').addClass('box-flex1');
340 var iframe = $('<iframe/>').addClass('box-flex1');
340 iframe.css({'height':1, 'width':'100%', 'display':'block'});
341 iframe.css({'height':1, 'width':'100%', 'display':'block'});
341 iframe.attr('frameborder', 0);
342 iframe.attr('frameborder', 0);
342 iframe.attr('scrolling', 'auto');
343 iframe.attr('scrolling', 'auto');
343
344
344 // Once the iframe is loaded, the subarea is dynamically inserted
345 // Once the iframe is loaded, the subarea is dynamically inserted
345 iframe.on('load', function() {
346 iframe.on('load', function() {
346 // Workaround needed by Firefox, to properly render svg inside
347 // Workaround needed by Firefox, to properly render svg inside
347 // iframes, see http://stackoverflow.com/questions/10177190/
348 // iframes, see http://stackoverflow.com/questions/10177190/
348 // svg-dynamically-added-to-iframe-does-not-render-correctly
349 // svg-dynamically-added-to-iframe-does-not-render-correctly
349 this.contentDocument.open();
350 this.contentDocument.open();
350
351
351 // Insert the subarea into the iframe
352 // Insert the subarea into the iframe
352 // We must directly write the html. When using Jquery's append
353 // We must directly write the html. When using Jquery's append
353 // method, javascript is evaluated in the parent document and
354 // method, javascript is evaluated in the parent document and
354 // not in the iframe document. At this point, subarea doesn't
355 // not in the iframe document. At this point, subarea doesn't
355 // contain any user content.
356 // contain any user content.
356 this.contentDocument.write(subarea.html());
357 this.contentDocument.write(subarea.html());
357
358
358 this.contentDocument.close();
359 this.contentDocument.close();
359
360
360 var body = this.contentDocument.body;
361 var body = this.contentDocument.body;
361 // Adjust the iframe height automatically
362 // Adjust the iframe height automatically
362 iframe.height(body.scrollHeight + 'px');
363 iframe.height(body.scrollHeight + 'px');
363 });
364 });
364
365
365 // Elements should be appended to the inner subarea and not to the
366 // Elements should be appended to the inner subarea and not to the
366 // iframe
367 // iframe
367 iframe.append = function(that) {
368 iframe.append = function(that) {
368 subarea.append(that);
369 subarea.append(that);
369 };
370 };
370
371
371 return iframe;
372 return iframe;
372 } else {
373 } else {
373 return subarea;
374 return subarea;
374 }
375 }
375 }
376 }
376
377
377
378
378 OutputArea.prototype._append_javascript_error = function (err, element) {
379 OutputArea.prototype._append_javascript_error = function (err, element) {
379 // display a message when a javascript error occurs in display output
380 // display a message when a javascript error occurs in display output
380 var msg = "Javascript error adding output!"
381 var msg = "Javascript error adding output!"
381 if ( element === undefined ) return;
382 if ( element === undefined ) return;
382 element
383 element
383 .append($('<div/>').text(msg).addClass('js-error'))
384 .append($('<div/>').text(msg).addClass('js-error'))
384 .append($('<div/>').text(err.toString()).addClass('js-error'))
385 .append($('<div/>').text(err.toString()).addClass('js-error'))
385 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
386 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
386 };
387 };
387
388
388 OutputArea.prototype._safe_append = function (toinsert) {
389 OutputArea.prototype._safe_append = function (toinsert) {
389 // safely append an item to the document
390 // safely append an item to the document
390 // this is an object created by user code,
391 // this is an object created by user code,
391 // and may have errors, which should not be raised
392 // and may have errors, which should not be raised
392 // under any circumstances.
393 // under any circumstances.
393 try {
394 try {
394 this.element.append(toinsert);
395 this.element.append(toinsert);
395 } catch(err) {
396 } catch(err) {
396 console.log(err);
397 console.log(err);
397 // Create an actual output_area and output_subarea, which creates
398 // Create an actual output_area and output_subarea, which creates
398 // the prompt area and the proper indentation.
399 // the prompt area and the proper indentation.
399 var toinsert = this.create_output_area();
400 var toinsert = this.create_output_area();
400 var subarea = $('<div/>').addClass('output_subarea');
401 var subarea = $('<div/>').addClass('output_subarea');
401 toinsert.append(subarea);
402 toinsert.append(subarea);
402 this._append_javascript_error(err, subarea);
403 this._append_javascript_error(err, subarea);
403 this.element.append(toinsert);
404 this.element.append(toinsert);
404 }
405 }
405 };
406 };
406
407
407
408
408 OutputArea.prototype.append_pyout = function (json) {
409 OutputArea.prototype.append_pyout = function (json) {
409 var n = json.prompt_number || ' ';
410 var n = json.prompt_number || ' ';
410 var toinsert = this.create_output_area();
411 var toinsert = this.create_output_area();
411 if (this.prompt_area) {
412 if (this.prompt_area) {
412 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
413 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
413 }
414 }
414 var inserted = this.append_mime_type(json, toinsert);
415 var inserted = this.append_mime_type(json, toinsert);
415 if (inserted) {
416 if (inserted) {
416 inserted.addClass('output_pyout');
417 inserted.addClass('output_pyout');
417 }
418 }
418 this._safe_append(toinsert);
419 this._safe_append(toinsert);
419 // If we just output latex, typeset it.
420 // If we just output latex, typeset it.
420 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
421 if ((json['text/latex'] !== undefined) ||
422 (json['text/html'] !== undefined) ||
423 (json['text/markdown'] !== undefined)) {
421 this.typeset();
424 this.typeset();
422 }
425 }
423 };
426 };
424
427
425
428
426 OutputArea.prototype.append_pyerr = function (json) {
429 OutputArea.prototype.append_pyerr = function (json) {
427 var tb = json.traceback;
430 var tb = json.traceback;
428 if (tb !== undefined && tb.length > 0) {
431 if (tb !== undefined && tb.length > 0) {
429 var s = '';
432 var s = '';
430 var len = tb.length;
433 var len = tb.length;
431 for (var i=0; i<len; i++) {
434 for (var i=0; i<len; i++) {
432 s = s + tb[i] + '\n';
435 s = s + tb[i] + '\n';
433 }
436 }
434 s = s + '\n';
437 s = s + '\n';
435 var toinsert = this.create_output_area();
438 var toinsert = this.create_output_area();
436 var append_text = OutputArea.append_map['text/plain'];
439 var append_text = OutputArea.append_map['text/plain'];
437 if (append_text) {
440 if (append_text) {
438 append_text.apply(this, [s, {}, toinsert]).addClass('output_pyerr');
441 append_text.apply(this, [s, {}, toinsert]).addClass('output_pyerr');
439 }
442 }
440 this._safe_append(toinsert);
443 this._safe_append(toinsert);
441 }
444 }
442 };
445 };
443
446
444
447
445 OutputArea.prototype.append_stream = function (json) {
448 OutputArea.prototype.append_stream = function (json) {
446 // temporary fix: if stream undefined (json file written prior to this patch),
449 // temporary fix: if stream undefined (json file written prior to this patch),
447 // default to most likely stdout:
450 // default to most likely stdout:
448 if (json.stream === undefined){
451 if (json.stream === undefined){
449 json.stream = 'stdout';
452 json.stream = 'stdout';
450 }
453 }
451 var text = json.text;
454 var text = json.text;
452 var subclass = "output_"+json.stream;
455 var subclass = "output_"+json.stream;
453 if (this.outputs.length > 0){
456 if (this.outputs.length > 0){
454 // have at least one output to consider
457 // have at least one output to consider
455 var last = this.outputs[this.outputs.length-1];
458 var last = this.outputs[this.outputs.length-1];
456 if (last.output_type == 'stream' && json.stream == last.stream){
459 if (last.output_type == 'stream' && json.stream == last.stream){
457 // latest output was in the same stream,
460 // latest output was in the same stream,
458 // so append directly into its pre tag
461 // so append directly into its pre tag
459 // escape ANSI & HTML specials:
462 // escape ANSI & HTML specials:
460 var pre = this.element.find('div.'+subclass).last().find('pre');
463 var pre = this.element.find('div.'+subclass).last().find('pre');
461 var html = utils.fixCarriageReturn(
464 var html = utils.fixCarriageReturn(
462 pre.html() + utils.fixConsole(text));
465 pre.html() + utils.fixConsole(text));
463 // The only user content injected with this HTML call is
466 // The only user content injected with this HTML call is
464 // escaped by the fixConsole() method.
467 // escaped by the fixConsole() method.
465 pre.html(html);
468 pre.html(html);
466 return;
469 return;
467 }
470 }
468 }
471 }
469
472
470 if (!text.replace("\r", "")) {
473 if (!text.replace("\r", "")) {
471 // text is nothing (empty string, \r, etc.)
474 // text is nothing (empty string, \r, etc.)
472 // so don't append any elements, which might add undesirable space
475 // so don't append any elements, which might add undesirable space
473 return;
476 return;
474 }
477 }
475
478
476 // If we got here, attach a new div
479 // If we got here, attach a new div
477 var toinsert = this.create_output_area();
480 var toinsert = this.create_output_area();
478 var append_text = OutputArea.append_map['text/plain'];
481 var append_text = OutputArea.append_map['text/plain'];
479 if (append_text) {
482 if (append_text) {
480 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
483 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
481 }
484 }
482 this._safe_append(toinsert);
485 this._safe_append(toinsert);
483 };
486 };
484
487
485
488
486 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
489 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
487 var toinsert = this.create_output_area();
490 var toinsert = this.create_output_area();
488 if (this.append_mime_type(json, toinsert, handle_inserted)) {
491 if (this.append_mime_type(json, toinsert, handle_inserted)) {
489 this._safe_append(toinsert);
492 this._safe_append(toinsert);
490 // If we just output latex, typeset it.
493 // If we just output latex, typeset it.
491 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
494 if ((json['text/latex'] !== undefined) ||
495 (json['text/html'] !== undefined) ||
496 (json['text/markdown'] !== undefined)) {
492 this.typeset();
497 this.typeset();
493 }
498 }
494 }
499 }
495 };
500 };
496
501
497
502
498 OutputArea.safe_outputs = {
503 OutputArea.safe_outputs = {
499 'text/plain' : true,
504 'text/plain' : true,
500 'text/latex' : true,
505 'text/latex' : true,
501 'image/png' : true,
506 'image/png' : true,
502 'image/jpeg' : true
507 'image/jpeg' : true
503 };
508 };
504
509
505 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
510 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
506 for (var i=0; i < OutputArea.display_order.length; i++) {
511 for (var i=0; i < OutputArea.display_order.length; i++) {
507 var type = OutputArea.display_order[i];
512 var type = OutputArea.display_order[i];
508 var append = OutputArea.append_map[type];
513 var append = OutputArea.append_map[type];
509 if ((json[type] !== undefined) && append) {
514 if ((json[type] !== undefined) && append) {
510 var value = json[type];
515 var value = json[type];
511 if (!this.trusted && !OutputArea.safe_outputs[type]) {
516 if (!this.trusted && !OutputArea.safe_outputs[type]) {
512 // not trusted, sanitize HTML
517 // not trusted, sanitize HTML
513 if (type==='text/html' || type==='text/svg') {
518 if (type==='text/html' || type==='text/svg') {
514 value = IPython.security.sanitize_html(value);
519 value = IPython.security.sanitize_html(value);
515 } else {
520 } else {
516 // don't display if we don't know how to sanitize it
521 // don't display if we don't know how to sanitize it
517 console.log("Ignoring untrusted " + type + " output.");
522 console.log("Ignoring untrusted " + type + " output.");
518 continue;
523 continue;
519 }
524 }
520 }
525 }
521 var md = json.metadata || {};
526 var md = json.metadata || {};
522 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
527 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
523 // Since only the png and jpeg mime types call the inserted
528 // Since only the png and jpeg mime types call the inserted
524 // callback, if the mime type is something other we must call the
529 // callback, if the mime type is something other we must call the
525 // inserted callback only when the element is actually inserted
530 // inserted callback only when the element is actually inserted
526 // into the DOM. Use a timeout of 0 to do this.
531 // into the DOM. Use a timeout of 0 to do this.
527 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
532 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
528 setTimeout(handle_inserted, 0);
533 setTimeout(handle_inserted, 0);
529 }
534 }
530 $([IPython.events]).trigger('output_appended.OutputArea', [type, value, md, toinsert]);
535 $([IPython.events]).trigger('output_appended.OutputArea', [type, value, md, toinsert]);
531 return toinsert;
536 return toinsert;
532 }
537 }
533 }
538 }
534 return null;
539 return null;
535 };
540 };
536
541
537
542
538 var append_html = function (html, md, element) {
543 var append_html = function (html, md, element) {
539 var type = 'text/html';
544 var type = 'text/html';
540 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
545 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
541 IPython.keyboard_manager.register_events(toinsert);
546 IPython.keyboard_manager.register_events(toinsert);
542 toinsert.append(html);
547 toinsert.append(html);
543 element.append(toinsert);
548 element.append(toinsert);
544 return toinsert;
549 return toinsert;
545 };
550 };
546
551
547
552
553 var append_markdown = function(markdown, md, element) {
554 var type = 'text/markdown';
555 var toinsert = this.create_output_subarea(md, "output_markdown", type);
556 var text_and_math = IPython.mathjaxutils.remove_math(markdown);
557 var text = text_and_math[0];
558 var math = text_and_math[1];
559 var html = marked.parser(marked.lexer(text));
560 html = IPython.mathjaxutils.replace_math(html, math);
561 toinsert.append(html);
562 element.append(toinsert);
563 return toinsert;
564 };
565
566
548 var append_javascript = function (js, md, element) {
567 var append_javascript = function (js, md, element) {
549 // We just eval the JS code, element appears in the local scope.
568 // We just eval the JS code, element appears in the local scope.
550 var type = 'application/javascript';
569 var type = 'application/javascript';
551 var toinsert = this.create_output_subarea(md, "output_javascript", type);
570 var toinsert = this.create_output_subarea(md, "output_javascript", type);
552 IPython.keyboard_manager.register_events(toinsert);
571 IPython.keyboard_manager.register_events(toinsert);
553 element.append(toinsert);
572 element.append(toinsert);
554 // FIXME TODO : remove `container element for 3.0`
573 // FIXME TODO : remove `container element for 3.0`
555 //backward compat, js should be eval'ed in a context where `container` is defined.
574 //backward compat, js should be eval'ed in a context where `container` is defined.
556 var container = element;
575 var container = element;
557 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
576 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
558 // end backward compat
577 // end backward compat
559
578
560 // Fix for ipython/issues/5293, make sure `element` is the area which
579 // Fix for ipython/issues/5293, make sure `element` is the area which
561 // output can be inserted into at the time of JS execution.
580 // output can be inserted into at the time of JS execution.
562 element = toinsert;
581 element = toinsert;
563 try {
582 try {
564 eval(js);
583 eval(js);
565 } catch(err) {
584 } catch(err) {
566 console.log(err);
585 console.log(err);
567 this._append_javascript_error(err, toinsert);
586 this._append_javascript_error(err, toinsert);
568 }
587 }
569 return toinsert;
588 return toinsert;
570 };
589 };
571
590
572
591
573 var append_text = function (data, md, element) {
592 var append_text = function (data, md, element) {
574 var type = 'text/plain';
593 var type = 'text/plain';
575 var toinsert = this.create_output_subarea(md, "output_text", type);
594 var toinsert = this.create_output_subarea(md, "output_text", type);
576 // escape ANSI & HTML specials in plaintext:
595 // escape ANSI & HTML specials in plaintext:
577 data = utils.fixConsole(data);
596 data = utils.fixConsole(data);
578 data = utils.fixCarriageReturn(data);
597 data = utils.fixCarriageReturn(data);
579 data = utils.autoLinkUrls(data);
598 data = utils.autoLinkUrls(data);
580 // The only user content injected with this HTML call is
599 // The only user content injected with this HTML call is
581 // escaped by the fixConsole() method.
600 // escaped by the fixConsole() method.
582 toinsert.append($("<pre/>").html(data));
601 toinsert.append($("<pre/>").html(data));
583 element.append(toinsert);
602 element.append(toinsert);
584 return toinsert;
603 return toinsert;
585 };
604 };
586
605
587
606
588 var append_svg = function (svg_html, md, element) {
607 var append_svg = function (svg_html, md, element) {
589 var type = 'image/svg+xml';
608 var type = 'image/svg+xml';
590 var toinsert = this.create_output_subarea(md, "output_svg", type);
609 var toinsert = this.create_output_subarea(md, "output_svg", type);
591
610
592 // Get the svg element from within the HTML.
611 // Get the svg element from within the HTML.
593 var svg = $('<div />').html(svg_html).find('svg');
612 var svg = $('<div />').html(svg_html).find('svg');
594 var svg_area = $('<div />');
613 var svg_area = $('<div />');
595 var width = svg.attr('width');
614 var width = svg.attr('width');
596 var height = svg.attr('height');
615 var height = svg.attr('height');
597 svg
616 svg
598 .width('100%')
617 .width('100%')
599 .height('100%');
618 .height('100%');
600 svg_area
619 svg_area
601 .width(width)
620 .width(width)
602 .height(height);
621 .height(height);
603
622
604 // The jQuery resize handlers don't seem to work on the svg element.
623 // The jQuery resize handlers don't seem to work on the svg element.
605 // When the svg renders completely, measure it's size and set the parent
624 // When the svg renders completely, measure it's size and set the parent
606 // div to that size. Then set the svg to 100% the size of the parent
625 // div to that size. Then set the svg to 100% the size of the parent
607 // div and make the parent div resizable.
626 // div and make the parent div resizable.
608 this._dblclick_to_reset_size(svg_area, true, false);
627 this._dblclick_to_reset_size(svg_area, true, false);
609
628
610 svg_area.append(svg);
629 svg_area.append(svg);
611 toinsert.append(svg_area);
630 toinsert.append(svg_area);
612 element.append(toinsert);
631 element.append(toinsert);
613
632
614 return toinsert;
633 return toinsert;
615 };
634 };
616
635
617 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
636 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
618 // Add a resize handler to an element
637 // Add a resize handler to an element
619 //
638 //
620 // img: jQuery element
639 // img: jQuery element
621 // immediately: bool=False
640 // immediately: bool=False
622 // Wait for the element to load before creating the handle.
641 // Wait for the element to load before creating the handle.
623 // resize_parent: bool=True
642 // resize_parent: bool=True
624 // Should the parent of the element be resized when the element is
643 // Should the parent of the element be resized when the element is
625 // reset (by double click).
644 // reset (by double click).
626 var callback = function (){
645 var callback = function (){
627 var h0 = img.height();
646 var h0 = img.height();
628 var w0 = img.width();
647 var w0 = img.width();
629 if (!(h0 && w0)) {
648 if (!(h0 && w0)) {
630 // zero size, don't make it resizable
649 // zero size, don't make it resizable
631 return;
650 return;
632 }
651 }
633 img.resizable({
652 img.resizable({
634 aspectRatio: true,
653 aspectRatio: true,
635 autoHide: true
654 autoHide: true
636 });
655 });
637 img.dblclick(function () {
656 img.dblclick(function () {
638 // resize wrapper & image together for some reason:
657 // resize wrapper & image together for some reason:
639 img.height(h0);
658 img.height(h0);
640 img.width(w0);
659 img.width(w0);
641 if (resize_parent === undefined || resize_parent) {
660 if (resize_parent === undefined || resize_parent) {
642 img.parent().height(h0);
661 img.parent().height(h0);
643 img.parent().width(w0);
662 img.parent().width(w0);
644 }
663 }
645 });
664 });
646 };
665 };
647
666
648 if (immediately) {
667 if (immediately) {
649 callback();
668 callback();
650 } else {
669 } else {
651 img.on("load", callback);
670 img.on("load", callback);
652 }
671 }
653 };
672 };
654
673
655 var set_width_height = function (img, md, mime) {
674 var set_width_height = function (img, md, mime) {
656 // set width and height of an img element from metadata
675 // set width and height of an img element from metadata
657 var height = _get_metadata_key(md, 'height', mime);
676 var height = _get_metadata_key(md, 'height', mime);
658 if (height !== undefined) img.attr('height', height);
677 if (height !== undefined) img.attr('height', height);
659 var width = _get_metadata_key(md, 'width', mime);
678 var width = _get_metadata_key(md, 'width', mime);
660 if (width !== undefined) img.attr('width', width);
679 if (width !== undefined) img.attr('width', width);
661 };
680 };
662
681
663 var append_png = function (png, md, element, handle_inserted) {
682 var append_png = function (png, md, element, handle_inserted) {
664 var type = 'image/png';
683 var type = 'image/png';
665 var toinsert = this.create_output_subarea(md, "output_png", type);
684 var toinsert = this.create_output_subarea(md, "output_png", type);
666 var img = $("<img/>");
685 var img = $("<img/>");
667 if (handle_inserted !== undefined) {
686 if (handle_inserted !== undefined) {
668 img.on('load', function(){
687 img.on('load', function(){
669 handle_inserted(img);
688 handle_inserted(img);
670 });
689 });
671 }
690 }
672 img[0].src = 'data:image/png;base64,'+ png;
691 img[0].src = 'data:image/png;base64,'+ png;
673 set_width_height(img, md, 'image/png');
692 set_width_height(img, md, 'image/png');
674 this._dblclick_to_reset_size(img);
693 this._dblclick_to_reset_size(img);
675 toinsert.append(img);
694 toinsert.append(img);
676 element.append(toinsert);
695 element.append(toinsert);
677 return toinsert;
696 return toinsert;
678 };
697 };
679
698
680
699
681 var append_jpeg = function (jpeg, md, element, handle_inserted) {
700 var append_jpeg = function (jpeg, md, element, handle_inserted) {
682 var type = 'image/jpeg';
701 var type = 'image/jpeg';
683 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
702 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
684 var img = $("<img/>");
703 var img = $("<img/>");
685 if (handle_inserted !== undefined) {
704 if (handle_inserted !== undefined) {
686 img.on('load', function(){
705 img.on('load', function(){
687 handle_inserted(img);
706 handle_inserted(img);
688 });
707 });
689 }
708 }
690 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
709 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
691 set_width_height(img, md, 'image/jpeg');
710 set_width_height(img, md, 'image/jpeg');
692 this._dblclick_to_reset_size(img);
711 this._dblclick_to_reset_size(img);
693 toinsert.append(img);
712 toinsert.append(img);
694 element.append(toinsert);
713 element.append(toinsert);
695 return toinsert;
714 return toinsert;
696 };
715 };
697
716
698
717
699 var append_pdf = function (pdf, md, element) {
718 var append_pdf = function (pdf, md, element) {
700 var type = 'application/pdf';
719 var type = 'application/pdf';
701 var toinsert = this.create_output_subarea(md, "output_pdf", type);
720 var toinsert = this.create_output_subarea(md, "output_pdf", type);
702 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
721 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
703 a.attr('target', '_blank');
722 a.attr('target', '_blank');
704 a.text('View PDF')
723 a.text('View PDF')
705 toinsert.append(a);
724 toinsert.append(a);
706 element.append(toinsert);
725 element.append(toinsert);
707 return toinsert;
726 return toinsert;
708 }
727 }
709
728
710 var append_latex = function (latex, md, element) {
729 var append_latex = function (latex, md, element) {
711 // This method cannot do the typesetting because the latex first has to
730 // This method cannot do the typesetting because the latex first has to
712 // be on the page.
731 // be on the page.
713 var type = 'text/latex';
732 var type = 'text/latex';
714 var toinsert = this.create_output_subarea(md, "output_latex", type);
733 var toinsert = this.create_output_subarea(md, "output_latex", type);
715 toinsert.append(latex);
734 toinsert.append(latex);
716 element.append(toinsert);
735 element.append(toinsert);
717 return toinsert;
736 return toinsert;
718 };
737 };
719
738
720
739
721 OutputArea.prototype.append_raw_input = function (msg) {
740 OutputArea.prototype.append_raw_input = function (msg) {
722 var that = this;
741 var that = this;
723 this.expand();
742 this.expand();
724 var content = msg.content;
743 var content = msg.content;
725 var area = this.create_output_area();
744 var area = this.create_output_area();
726
745
727 // disable any other raw_inputs, if they are left around
746 // disable any other raw_inputs, if they are left around
728 $("div.output_subarea.raw_input_container").remove();
747 $("div.output_subarea.raw_input_container").remove();
729
748
730 area.append(
749 area.append(
731 $("<div/>")
750 $("<div/>")
732 .addClass("box-flex1 output_subarea raw_input_container")
751 .addClass("box-flex1 output_subarea raw_input_container")
733 .append(
752 .append(
734 $("<span/>")
753 $("<span/>")
735 .addClass("raw_input_prompt")
754 .addClass("raw_input_prompt")
736 .text(content.prompt)
755 .text(content.prompt)
737 )
756 )
738 .append(
757 .append(
739 $("<input/>")
758 $("<input/>")
740 .addClass("raw_input")
759 .addClass("raw_input")
741 .attr('type', 'text')
760 .attr('type', 'text')
742 .attr("size", 47)
761 .attr("size", 47)
743 .keydown(function (event, ui) {
762 .keydown(function (event, ui) {
744 // make sure we submit on enter,
763 // make sure we submit on enter,
745 // and don't re-execute the *cell* on shift-enter
764 // and don't re-execute the *cell* on shift-enter
746 if (event.which === IPython.keyboard.keycodes.enter) {
765 if (event.which === IPython.keyboard.keycodes.enter) {
747 that._submit_raw_input();
766 that._submit_raw_input();
748 return false;
767 return false;
749 }
768 }
750 })
769 })
751 )
770 )
752 );
771 );
753
772
754 this.element.append(area);
773 this.element.append(area);
755 var raw_input = area.find('input.raw_input');
774 var raw_input = area.find('input.raw_input');
756 // Register events that enable/disable the keyboard manager while raw
775 // Register events that enable/disable the keyboard manager while raw
757 // input is focused.
776 // input is focused.
758 IPython.keyboard_manager.register_events(raw_input);
777 IPython.keyboard_manager.register_events(raw_input);
759 // Note, the following line used to read raw_input.focus().focus().
778 // Note, the following line used to read raw_input.focus().focus().
760 // This seemed to be needed otherwise only the cell would be focused.
779 // This seemed to be needed otherwise only the cell would be focused.
761 // But with the modal UI, this seems to work fine with one call to focus().
780 // But with the modal UI, this seems to work fine with one call to focus().
762 raw_input.focus();
781 raw_input.focus();
763 }
782 }
764
783
765 OutputArea.prototype._submit_raw_input = function (evt) {
784 OutputArea.prototype._submit_raw_input = function (evt) {
766 var container = this.element.find("div.raw_input_container");
785 var container = this.element.find("div.raw_input_container");
767 var theprompt = container.find("span.raw_input_prompt");
786 var theprompt = container.find("span.raw_input_prompt");
768 var theinput = container.find("input.raw_input");
787 var theinput = container.find("input.raw_input");
769 var value = theinput.val();
788 var value = theinput.val();
770 var content = {
789 var content = {
771 output_type : 'stream',
790 output_type : 'stream',
772 name : 'stdout',
791 name : 'stdout',
773 text : theprompt.text() + value + '\n'
792 text : theprompt.text() + value + '\n'
774 }
793 }
775 // remove form container
794 // remove form container
776 container.parent().remove();
795 container.parent().remove();
777 // replace with plaintext version in stdout
796 // replace with plaintext version in stdout
778 this.append_output(content, false);
797 this.append_output(content, false);
779 $([IPython.events]).trigger('send_input_reply.Kernel', value);
798 $([IPython.events]).trigger('send_input_reply.Kernel', value);
780 }
799 }
781
800
782
801
783 OutputArea.prototype.handle_clear_output = function (msg) {
802 OutputArea.prototype.handle_clear_output = function (msg) {
784 // msg spec v4 had stdout, stderr, display keys
803 // msg spec v4 had stdout, stderr, display keys
785 // v4.1 replaced these with just wait
804 // v4.1 replaced these with just wait
786 // The default behavior is the same (stdout=stderr=display=True, wait=False),
805 // The default behavior is the same (stdout=stderr=display=True, wait=False),
787 // so v4 messages will still be properly handled,
806 // so v4 messages will still be properly handled,
788 // except for the rarely used clearing less than all output.
807 // except for the rarely used clearing less than all output.
789 this.clear_output(msg.content.wait || false);
808 this.clear_output(msg.content.wait || false);
790 };
809 };
791
810
792
811
793 OutputArea.prototype.clear_output = function(wait) {
812 OutputArea.prototype.clear_output = function(wait) {
794 if (wait) {
813 if (wait) {
795
814
796 // If a clear is queued, clear before adding another to the queue.
815 // If a clear is queued, clear before adding another to the queue.
797 if (this.clear_queued) {
816 if (this.clear_queued) {
798 this.clear_output(false);
817 this.clear_output(false);
799 };
818 };
800
819
801 this.clear_queued = true;
820 this.clear_queued = true;
802 } else {
821 } else {
803
822
804 // Fix the output div's height if the clear_output is waiting for
823 // Fix the output div's height if the clear_output is waiting for
805 // new output (it is being used in an animation).
824 // new output (it is being used in an animation).
806 if (this.clear_queued) {
825 if (this.clear_queued) {
807 var height = this.element.height();
826 var height = this.element.height();
808 this.element.height(height);
827 this.element.height(height);
809 this.clear_queued = false;
828 this.clear_queued = false;
810 }
829 }
811
830
812 // Clear all
831 // Clear all
813 // Remove load event handlers from img tags because we don't want
832 // Remove load event handlers from img tags because we don't want
814 // them to fire if the image is never added to the page.
833 // them to fire if the image is never added to the page.
815 this.element.find('img').off('load');
834 this.element.find('img').off('load');
816 this.element.html("");
835 this.element.html("");
817 this.outputs = [];
836 this.outputs = [];
818 this.trusted = true;
837 this.trusted = true;
819 this.unscroll_area();
838 this.unscroll_area();
820 return;
839 return;
821 };
840 };
822 };
841 };
823
842
824
843
825 // JSON serialization
844 // JSON serialization
826
845
827 OutputArea.prototype.fromJSON = function (outputs) {
846 OutputArea.prototype.fromJSON = function (outputs) {
828 var len = outputs.length;
847 var len = outputs.length;
829 var data;
848 var data;
830
849
831 for (var i=0; i<len; i++) {
850 for (var i=0; i<len; i++) {
832 data = outputs[i];
851 data = outputs[i];
833 var msg_type = data.output_type;
852 var msg_type = data.output_type;
834 if (msg_type === "display_data" || msg_type === "pyout") {
853 if (msg_type === "display_data" || msg_type === "pyout") {
835 // convert short keys to mime keys
854 // convert short keys to mime keys
836 // TODO: remove mapping of short keys when we update to nbformat 4
855 // TODO: remove mapping of short keys when we update to nbformat 4
837 data = this.rename_keys(data, OutputArea.mime_map_r);
856 data = this.rename_keys(data, OutputArea.mime_map_r);
838 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
857 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
839 }
858 }
840
859
841 this.append_output(data);
860 this.append_output(data);
842 }
861 }
843 };
862 };
844
863
845
864
846 OutputArea.prototype.toJSON = function () {
865 OutputArea.prototype.toJSON = function () {
847 var outputs = [];
866 var outputs = [];
848 var len = this.outputs.length;
867 var len = this.outputs.length;
849 var data;
868 var data;
850 for (var i=0; i<len; i++) {
869 for (var i=0; i<len; i++) {
851 data = this.outputs[i];
870 data = this.outputs[i];
852 var msg_type = data.output_type;
871 var msg_type = data.output_type;
853 if (msg_type === "display_data" || msg_type === "pyout") {
872 if (msg_type === "display_data" || msg_type === "pyout") {
854 // convert mime keys to short keys
873 // convert mime keys to short keys
855 data = this.rename_keys(data, OutputArea.mime_map);
874 data = this.rename_keys(data, OutputArea.mime_map);
856 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
875 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
857 }
876 }
858 outputs[i] = data;
877 outputs[i] = data;
859 }
878 }
860 return outputs;
879 return outputs;
861 };
880 };
862
881
863 /**
882 /**
864 * Class properties
883 * Class properties
865 **/
884 **/
866
885
867 /**
886 /**
868 * Threshold to trigger autoscroll when the OutputArea is resized,
887 * Threshold to trigger autoscroll when the OutputArea is resized,
869 * typically when new outputs are added.
888 * typically when new outputs are added.
870 *
889 *
871 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
890 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
872 * unless it is < 0, in which case autoscroll will never be triggered
891 * unless it is < 0, in which case autoscroll will never be triggered
873 *
892 *
874 * @property auto_scroll_threshold
893 * @property auto_scroll_threshold
875 * @type Number
894 * @type Number
876 * @default 100
895 * @default 100
877 *
896 *
878 **/
897 **/
879 OutputArea.auto_scroll_threshold = 100;
898 OutputArea.auto_scroll_threshold = 100;
880
899
881 /**
900 /**
882 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
901 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
883 * shorter than this are never scrolled.
902 * shorter than this are never scrolled.
884 *
903 *
885 * @property minimum_scroll_threshold
904 * @property minimum_scroll_threshold
886 * @type Number
905 * @type Number
887 * @default 20
906 * @default 20
888 *
907 *
889 **/
908 **/
890 OutputArea.minimum_scroll_threshold = 20;
909 OutputArea.minimum_scroll_threshold = 20;
891
910
892
911
893
912
894 OutputArea.mime_map = {
913 OutputArea.mime_map = {
895 "text/plain" : "text",
914 "text/plain" : "text",
896 "text/html" : "html",
915 "text/html" : "html",
897 "image/svg+xml" : "svg",
916 "image/svg+xml" : "svg",
898 "image/png" : "png",
917 "image/png" : "png",
899 "image/jpeg" : "jpeg",
918 "image/jpeg" : "jpeg",
900 "text/latex" : "latex",
919 "text/latex" : "latex",
901 "application/json" : "json",
920 "application/json" : "json",
902 "application/javascript" : "javascript",
921 "application/javascript" : "javascript",
903 };
922 };
904
923
905 OutputArea.mime_map_r = {
924 OutputArea.mime_map_r = {
906 "text" : "text/plain",
925 "text" : "text/plain",
907 "html" : "text/html",
926 "html" : "text/html",
908 "svg" : "image/svg+xml",
927 "svg" : "image/svg+xml",
909 "png" : "image/png",
928 "png" : "image/png",
910 "jpeg" : "image/jpeg",
929 "jpeg" : "image/jpeg",
911 "latex" : "text/latex",
930 "latex" : "text/latex",
912 "json" : "application/json",
931 "json" : "application/json",
913 "javascript" : "application/javascript",
932 "javascript" : "application/javascript",
914 };
933 };
915
934
916 OutputArea.display_order = [
935 OutputArea.display_order = [
917 'application/javascript',
936 'application/javascript',
918 'text/html',
937 'text/html',
938 'text/markdown',
919 'text/latex',
939 'text/latex',
920 'image/svg+xml',
940 'image/svg+xml',
921 'image/png',
941 'image/png',
922 'image/jpeg',
942 'image/jpeg',
923 'application/pdf',
943 'application/pdf',
924 'text/plain'
944 'text/plain'
925 ];
945 ];
926
946
927 OutputArea.append_map = {
947 OutputArea.append_map = {
928 "text/plain" : append_text,
948 "text/plain" : append_text,
929 "text/html" : append_html,
949 "text/html" : append_html,
950 "text/markdown": append_markdown,
930 "image/svg+xml" : append_svg,
951 "image/svg+xml" : append_svg,
931 "image/png" : append_png,
952 "image/png" : append_png,
932 "image/jpeg" : append_jpeg,
953 "image/jpeg" : append_jpeg,
933 "text/latex" : append_latex,
954 "text/latex" : append_latex,
934 "application/javascript" : append_javascript,
955 "application/javascript" : append_javascript,
935 "application/pdf" : append_pdf
956 "application/pdf" : append_pdf
936 };
957 };
937
958
938 IPython.OutputArea = OutputArea;
959 IPython.OutputArea = OutputArea;
939
960
940 return IPython;
961 return IPython;
941
962
942 }(IPython));
963 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now