Merge pull request #6045 from minrk/nbformat4

nbformat v4
pull/37/head
Thomas Kluyver 12 years ago
commit a16a3d0270

@ -13,7 +13,7 @@ from ..base.handlers import (
IPythonHandler, FilesRedirectHandler,
notebook_path_regex, path_regex,
)
from IPython.nbformat.current import to_notebook_json
from IPython.nbformat import from_dict
from IPython.utils.py3compat import cast_bytes
@ -114,14 +114,15 @@ class NbconvertPostHandler(IPythonHandler):
exporter = get_exporter(format, config=self.config)
model = self.get_json_body()
nbnode = to_notebook_json(model['content'])
name = model.get('name', 'notebook.ipynb')
nbnode = from_dict(model['content'])
try:
output, resources = exporter.from_notebook_node(nbnode)
except Exception as e:
raise web.HTTPError(500, "nbconvert failed: %s" % e)
if respond_zip(self, nbnode.metadata.name, output, resources):
if respond_zip(self, name, output, resources):
return
# MIME type

@ -10,9 +10,10 @@ import requests
from IPython.html.utils import url_path_join
from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
from IPython.nbformat.current import (new_notebook, write, new_worksheet,
new_heading_cell, new_code_cell,
new_output)
from IPython.nbformat import write
from IPython.nbformat.v4 import (
new_notebook, new_markdown_cell, new_code_cell, new_output,
)
from IPython.testing.decorators import onlyif_cmds_exist
@ -43,7 +44,8 @@ class NbconvertAPI(object):
png_green_pixel = base64.encodestring(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00'
b'\x00\x00\x01\x00\x00x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDAT'
b'\x08\xd7c\x90\xfb\xcf\x00\x00\x02\\\x01\x1e.~d\x87\x00\x00\x00\x00IEND\xaeB`\x82')
b'\x08\xd7c\x90\xfb\xcf\x00\x00\x02\\\x01\x1e.~d\x87\x00\x00\x00\x00IEND\xaeB`\x82'
).decode('ascii')
class APITest(NotebookTestBase):
def setUp(self):
@ -52,19 +54,20 @@ class APITest(NotebookTestBase):
if not os.path.isdir(pjoin(nbdir, 'foo')):
os.mkdir(pjoin(nbdir, 'foo'))
nb = new_notebook(name='testnb')
nb = new_notebook()
ws = new_worksheet()
nb.worksheets = [ws]
ws.cells.append(new_heading_cell(u'Created by test ³'))
cc1 = new_code_cell(input=u'print(2*6)')
cc1.outputs.append(new_output(output_text=u'12', output_type='stream'))
cc1.outputs.append(new_output(output_png=png_green_pixel, output_type='pyout'))
ws.cells.append(cc1)
nb.cells.append(new_markdown_cell(u'Created by test ³'))
cc1 = new_code_cell(source=u'print(2*6)')
cc1.outputs.append(new_output(output_type="stream", text=u'12'))
cc1.outputs.append(new_output(output_type="execute_result",
data={'image/png' : png_green_pixel},
execution_count=1,
))
nb.cells.append(cc1)
with io.open(pjoin(nbdir, 'foo', 'testnb.ipynb'), 'w',
encoding='utf-8') as f:
write(nb, f, format='ipynb')
write(nb, f, version=4)
self.nbconvert_api = NbconvertAPI(self.base_url())

@ -12,7 +12,7 @@ import shutil
from tornado import web
from .manager import ContentsManager
from IPython.nbformat import current
from IPython import nbformat
from IPython.utils.io import atomic_writing
from IPython.utils.path import ensure_dir_exists
from IPython.utils.traitlets import Unicode, Bool, TraitError
@ -253,9 +253,9 @@ class FileContentsManager(ContentsManager):
os_path = self._get_os_path(name, path)
with io.open(os_path, 'r', encoding='utf-8') as f:
try:
nb = current.read(f, u'json')
nb = nbformat.read(f, as_version=4)
except Exception as e:
raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
raise web.HTTPError(400, u"Unreadable Notebook: %s %r" % (os_path, e))
self.mark_trusted_cells(nb, name, path)
model['content'] = nb
model['format'] = 'json'
@ -295,7 +295,7 @@ class FileContentsManager(ContentsManager):
def _save_notebook(self, os_path, model, name='', path=''):
"""save a notebook file"""
# Save the notebook file
nb = current.to_notebook_json(model['content'])
nb = nbformat.from_dict(model['content'])
self.check_and_sign(nb, name, path)
@ -303,7 +303,7 @@ class FileContentsManager(ContentsManager):
nb['metadata']['name'] = u''
with atomic_writing(os_path, encoding='utf-8') as f:
current.write(nb, f, version=nb.nbformat)
nbformat.write(nb, f, version=nbformat.NO_CONVERT)
def _save_file(self, os_path, model, name='', path=''):
"""save a non-notebook file"""
@ -522,7 +522,7 @@ class FileContentsManager(ContentsManager):
# ensure notebook is readable (never restore from an unreadable notebook)
if cp_path.endswith('.ipynb'):
with io.open(cp_path, 'r', encoding='utf-8') as f:
current.read(f, u'json')
nbformat.read(f, as_version=4)
self._copy(cp_path, nb_path)
self.log.debug("copying %s -> %s", cp_path, nb_path)

@ -11,7 +11,8 @@ import os
from tornado.web import HTTPError
from IPython.config.configurable import LoggingConfigurable
from IPython.nbformat import current, sign
from IPython.nbformat import sign, validate, ValidationError
from IPython.nbformat.v4 import new_notebook
from IPython.utils.traitlets import Instance, Unicode, List
@ -220,8 +221,8 @@ class ContentsManager(LoggingConfigurable):
def validate_notebook_model(self, model):
"""Add failed-validation message to model"""
try:
current.validate(model['content'])
except current.ValidationError as e:
validate(model['content'])
except ValidationError as e:
model['message'] = 'Notebook Validation failed: {}:\n{}'.format(
e.message, json.dumps(e.instance, indent=1, default=lambda obj: '<UNKNOWN>'),
)
@ -234,8 +235,7 @@ class ContentsManager(LoggingConfigurable):
model = {}
if 'content' not in model and model.get('type', None) != 'directory':
if ext == '.ipynb':
metadata = current.new_metadata(name=u'')
model['content'] = current.new_notebook(metadata=metadata)
model['content'] = new_notebook()
model['type'] = 'notebook'
model['format'] = 'json'
else:
@ -309,14 +309,12 @@ class ContentsManager(LoggingConfigurable):
Parameters
----------
nb : dict
The notebook object (in nbformat.current format)
The notebook dict
name : string
The filename of the notebook (for logging)
path : string
The notebook's directory (for logging)
"""
if nb['nbformat'] != current.nbformat:
return
if self.notary.check_cells(nb):
self.notary.sign(nb)
else:
@ -330,7 +328,7 @@ class ContentsManager(LoggingConfigurable):
Parameters
----------
nb : dict
The notebook object (in nbformat.current format)
The notebook object (in current nbformat)
name : string
The filename of the notebook (for logging)
path : string

@ -14,9 +14,10 @@ import requests
from IPython.html.utils import url_path_join, url_escape
from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
from IPython.nbformat import current
from IPython.nbformat.current import (new_notebook, write, read, new_worksheet,
new_heading_cell, to_notebook_json)
from IPython.nbformat import read, write, from_dict
from IPython.nbformat.v4 import (
new_notebook, new_markdown_cell,
)
from IPython.nbformat import v2
from IPython.utils import py3compat
from IPython.utils.data import uniq_stable
@ -142,8 +143,8 @@ class APITest(NotebookTestBase):
# create a notebook
with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w',
encoding='utf-8') as f:
nb = new_notebook(name=name)
write(nb, f, format='ipynb')
nb = new_notebook()
write(nb, f, version=4)
# create a text file
with io.open(pjoin(nbdir, d, '%s.txt' % name), 'w',
@ -286,14 +287,14 @@ class APITest(NotebookTestBase):
self.assertEqual(model['content'], '')
def test_upload_untitled(self):
nb = new_notebook(name='Upload test')
nb = new_notebook()
nbmodel = {'content': nb, 'type': 'notebook'}
resp = self.api.upload_untitled(path=u'å b',
body=json.dumps(nbmodel))
self._check_created(resp, 'Untitled0.ipynb', u'å b')
def test_upload(self):
nb = new_notebook(name=u'ignored')
nb = new_notebook()
nbmodel = {'content': nb, 'type': 'notebook'}
resp = self.api.upload(u'Upload tést.ipynb', path=u'å b',
body=json.dumps(nbmodel))
@ -354,8 +355,7 @@ class APITest(NotebookTestBase):
self._check_created(resp, u'Upload tést.ipynb', u'å b')
resp = self.api.read(u'Upload tést.ipynb', u'å b')
data = resp.json()
self.assertEqual(data['content']['nbformat'], current.nbformat)
self.assertEqual(data['content']['orig_nbformat'], 2)
self.assertEqual(data['content']['nbformat'], 4)
def test_copy_untitled(self):
resp = self.api.copy_untitled(u'ç d.ipynb', path=u'å b')
@ -415,22 +415,20 @@ class APITest(NotebookTestBase):
def test_save(self):
resp = self.api.read('a.ipynb', 'foo')
nbcontent = json.loads(resp.text)['content']
nb = to_notebook_json(nbcontent)
ws = new_worksheet()
nb.worksheets = [ws]
ws.cells.append(new_heading_cell(u'Created by test ³'))
nb = from_dict(nbcontent)
nb.cells.append(new_markdown_cell(u'Created by test ³'))
nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')
with io.open(nbfile, 'r', encoding='utf-8') as f:
newnb = read(f, format='ipynb')
self.assertEqual(newnb.worksheets[0].cells[0].source,
newnb = read(f, as_version=4)
self.assertEqual(newnb.cells[0].source,
u'Created by test ³')
nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
newnb = to_notebook_json(nbcontent)
self.assertEqual(newnb.worksheets[0].cells[0].source,
newnb = from_dict(nbcontent)
self.assertEqual(newnb.cells[0].source,
u'Created by test ³')
# Save and rename
@ -454,11 +452,9 @@ class APITest(NotebookTestBase):
# Modify it
nbcontent = json.loads(resp.text)['content']
nb = to_notebook_json(nbcontent)
ws = new_worksheet()
nb.worksheets = [ws]
hcell = new_heading_cell('Created by test')
ws.cells.append(hcell)
nb = from_dict(nbcontent)
hcell = new_markdown_cell('Created by test')
nb.cells.append(hcell)
# Save
nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
@ -468,15 +464,15 @@ class APITest(NotebookTestBase):
self.assertEqual(cps, [cp1])
nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
nb = to_notebook_json(nbcontent)
self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test')
nb = from_dict(nbcontent)
self.assertEqual(nb.cells[0].source, 'Created by test')
# Restore cp1
r = self.api.restore_checkpoint('a.ipynb', 'foo', cp1['id'])
self.assertEqual(r.status_code, 204)
nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
nb = to_notebook_json(nbcontent)
self.assertEqual(nb.worksheets, [])
nb = from_dict(nbcontent)
self.assertEqual(nb.cells, [])
# Delete cp1
r = self.api.delete_checkpoint('a.ipynb', 'foo', cp1['id'])

@ -9,7 +9,7 @@ from tornado.web import HTTPError
from unittest import TestCase
from tempfile import NamedTemporaryFile
from IPython.nbformat import current
from IPython.nbformat import v4 as nbformat
from IPython.utils.tempdir import TemporaryDirectory
from IPython.utils.traitlets import TraitError
@ -95,11 +95,9 @@ class TestContentsManager(TestCase):
return os_path
def add_code_cell(self, nb):
output = current.new_output("display_data", output_javascript="alert('hi');")
cell = current.new_code_cell("print('hi')", outputs=[output])
if not nb.worksheets:
nb.worksheets.append(current.new_worksheet())
nb.worksheets[0].cells.append(cell)
output = nbformat.new_output("display_data", {'application/javascript': "alert('hi');"})
cell = nbformat.new_code_cell("print('hi')", outputs=[output])
nb.cells.append(cell)
def new_notebook(self):
cm = self.contents_manager
@ -309,13 +307,13 @@ class TestContentsManager(TestCase):
nb, name, path = self.new_notebook()
cm.mark_trusted_cells(nb, name, path)
for cell in nb.worksheets[0].cells:
for cell in nb.cells:
if cell.cell_type == 'code':
assert not cell.metadata.trusted
cm.trust_notebook(name, path)
nb = cm.get_model(name, path)['content']
for cell in nb.worksheets[0].cells:
for cell in nb.cells:
if cell.cell_type == 'code':
assert cell.metadata.trusted

@ -11,7 +11,8 @@ pjoin = os.path.join
from IPython.html.utils import url_path_join
from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
from IPython.nbformat.current import new_notebook, write
from IPython.nbformat.v4 import new_notebook
from IPython.nbformat import write
class SessionAPI(object):
"""Wrapper for notebook API calls."""
@ -62,8 +63,8 @@ class SessionAPITest(NotebookTestBase):
with io.open(pjoin(nbdir, 'foo', 'nb1.ipynb'), 'w',
encoding='utf-8') as f:
nb = new_notebook(name='nb1')
write(nb, f, format='ipynb')
nb = new_notebook()
write(nb, f, version=4)
self.sess_api = SessionAPI(self.base_url())

@ -382,13 +382,11 @@ define([
CodeCell.prototype.collapse_output = function () {
this.collapsed = true;
this.output_area.collapse();
};
CodeCell.prototype.expand_output = function () {
this.collapsed = false;
this.output_area.expand();
this.output_area.unscroll_area();
};
@ -399,7 +397,6 @@ define([
};
CodeCell.prototype.toggle_output = function () {
this.collapsed = Boolean(1 - this.collapsed);
this.output_area.toggle_output();
};
@ -467,22 +464,18 @@ define([
CodeCell.prototype.fromJSON = function (data) {
Cell.prototype.fromJSON.apply(this, arguments);
if (data.cell_type === 'code') {
if (data.input !== undefined) {
this.set_text(data.input);
if (data.source !== undefined) {
this.set_text(data.source);
// make this value the starting point, so that we can only undo
// to this state, instead of a blank cell
this.code_mirror.clearHistory();
this.auto_highlight();
}
if (data.prompt_number !== undefined) {
this.set_input_prompt(data.prompt_number);
} else {
this.set_input_prompt();
}
this.set_input_prompt(data.execution_count);
this.output_area.trusted = data.metadata.trusted || false;
this.output_area.fromJSON(data.outputs);
if (data.collapsed !== undefined) {
if (data.collapsed) {
if (data.metadata.collapsed !== undefined) {
if (data.metadata.collapsed) {
this.collapse_output();
} else {
this.expand_output();
@ -494,16 +487,17 @@ define([
CodeCell.prototype.toJSON = function () {
var data = Cell.prototype.toJSON.apply(this);
data.input = this.get_text();
data.source = this.get_text();
// is finite protect against undefined and '*' value
if (isFinite(this.input_prompt_number)) {
data.prompt_number = this.input_prompt_number;
data.execution_count = this.input_prompt_number;
} else {
data.execution_count = null;
}
var outputs = this.output_area.toJSON();
data.outputs = outputs;
data.language = 'python';
data.metadata.trusted = this.output_area.trusted;
data.collapsed = this.output_area.collapsed;
data.metadata.collapsed = this.output_area.collapsed;
return data;
};

@ -139,12 +139,6 @@ define([
.append($('<option/>').attr('value','code').text('Code'))
.append($('<option/>').attr('value','markdown').text('Markdown'))
.append($('<option/>').attr('value','raw').text('Raw NBConvert'))
.append($('<option/>').attr('value','heading1').text('Heading 1'))
.append($('<option/>').attr('value','heading2').text('Heading 2'))
.append($('<option/>').attr('value','heading3').text('Heading 3'))
.append($('<option/>').attr('value','heading4').text('Heading 4'))
.append($('<option/>').attr('value','heading5').text('Heading 5'))
.append($('<option/>').attr('value','heading6').text('Heading 6'))
);
};
@ -190,24 +184,18 @@ define([
this.element.find('#cell_type').change(function () {
var cell_type = $(this).val();
if (cell_type === 'code') {
switch (cell_type) {
case 'code':
that.notebook.to_code();
} else if (cell_type === 'markdown') {
break;
case 'markdown':
that.notebook.to_markdown();
} else if (cell_type === 'raw') {
break;
case 'raw':
that.notebook.to_raw();
} else if (cell_type === 'heading1') {
that.notebook.to_heading(undefined, 1);
} else if (cell_type === 'heading2') {
that.notebook.to_heading(undefined, 2);
} else if (cell_type === 'heading3') {
that.notebook.to_heading(undefined, 3);
} else if (cell_type === 'heading4') {
that.notebook.to_heading(undefined, 4);
} else if (cell_type === 'heading5') {
that.notebook.to_heading(undefined, 5);
} else if (cell_type === 'heading6') {
that.notebook.to_heading(undefined, 6);
break;
default:
console.log("unrecognized cell type:", cell_type);
}
});
this.events.on('selected_cell_type_changed.Notebook', function (event, data) {

@ -121,10 +121,8 @@ define([
this.autosave_timer = null;
// autosave *at most* every two minutes
this.minimum_autosave_interval = 120000;
// single worksheet for now
this.worksheet_metadata = {};
this.notebook_name_blacklist_re = /[\/\\:]/;
this.nbformat = 3; // Increment this when changing the nbformat
this.nbformat = 4; // Increment this when changing the nbformat
this.nbformat_minor = 0; // Increment this when changing the nbformat
this.codemirror_mode = 'ipython';
this.create_elements();
@ -826,7 +824,7 @@ define([
* Index will be brought back into the accessible range [0,n]
*
* @method insert_cell_at_index
* @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
* @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
* @param [index] {int} a valid index where to insert cell
*
* @return cell {cell|null} created cell or null
@ -862,15 +860,19 @@ define([
notebook: this,
tooltip: this.tooltip,
};
if (type === 'code') {
switch(type) {
case 'code':
cell = new codecell.CodeCell(this.kernel, cell_options);
cell.set_input_prompt();
} else if (type === 'markdown') {
break;
case 'markdown':
cell = new textcell.MarkdownCell(cell_options);
} else if (type === 'raw') {
break;
case 'raw':
cell = new textcell.RawCell(cell_options);
} else if (type === 'heading') {
cell = new textcell.HeadingCell(cell_options);
break;
default:
console.log("invalid cell type: ", type);
}
if(this._insert_element_at_index(cell.element,index)) {
@ -1092,10 +1094,10 @@ define([
if (this.is_valid_cell_index(i)) {
var source_cell = this.get_cell(i);
var target_cell = null;
if (source_cell instanceof textcell.HeadingCell) {
source_cell.set_level(level);
if (source_cell instanceof textcell.MarkdownCell) {
source_cell.set_heading_level(level);
} else {
target_cell = this.insert_cell_below('heading',i);
target_cell = this.insert_cell_below('markdown',i);
var text = source_cell.get_text();
if (text === source_cell.placeholder) {
text = '';
@ -1103,9 +1105,9 @@ define([
//metadata
target_cell.metadata = source_cell.metadata;
// We must show the editor before setting its contents
target_cell.set_level(level);
target_cell.unrender();
target_cell.set_text(text);
target_cell.set_heading_level(level);
// make this value the starting point, so that we can only undo
// to this state, instead of a blank cell
target_cell.code_mirror.clearHistory();
@ -1119,7 +1121,7 @@ define([
}
this.set_dirty(true);
this.events.trigger('selected_cell_type_changed.Notebook',
{'cell_type':'heading',level:level}
{'cell_type':'markdown',level:level}
);
}
};
@ -1528,8 +1530,8 @@ define([
}
this.codemirror_mode = newmode;
codecell.CodeCell.options_default.cm_config.mode = newmode;
modename = newmode.mode || newmode.name || newmode
modename = newmode.mode || newmode.name || newmode;
that = this;
utils.requireCodeMirrorMode(modename, function () {
$.map(that.get_cells(), function(cell, i) {
@ -1541,7 +1543,7 @@ define([
cell.cm_config.mode = newmode;
}
});
})
});
};
// Session related things
@ -1785,8 +1787,6 @@ define([
/**
* Load a notebook from JSON (.ipynb).
*
* This currently handles one worksheet: others are deleted.
*
* @method fromJSON
* @param {Object} data JSON representation of a notebook
*/
@ -1818,50 +1818,22 @@ define([
this.set_codemirror_mode(cm_mode);
}
// Only handle 1 worksheet for now.
var worksheet = content.worksheets[0];
if (worksheet !== undefined) {
if (worksheet.metadata) {
this.worksheet_metadata = worksheet.metadata;
}
var new_cells = worksheet.cells;
ncells = new_cells.length;
var cell_data = null;
var new_cell = null;
for (i=0; i<ncells; i++) {
cell_data = new_cells[i];
// VERSIONHACK: plaintext -> raw
// handle never-released plaintext name for raw cells
if (cell_data.cell_type === 'plaintext'){
cell_data.cell_type = 'raw';
}
new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
new_cell.fromJSON(cell_data);
if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
trusted = false;
}
var new_cells = content.cells;
ncells = new_cells.length;
var cell_data = null;
var new_cell = null;
for (i=0; i<ncells; i++) {
cell_data = new_cells[i];
new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
new_cell.fromJSON(cell_data);
if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
trusted = false;
}
}
if (trusted !== this.trusted) {
this.trusted = trusted;
this.events.trigger("trust_changed.Notebook", trusted);
}
if (content.worksheets.length > 1) {
dialog.modal({
notebook: this,
keyboard_manager: this.keyboard_manager,
title : "Multiple worksheets",
body : "This notebook has " + data.worksheets.length + " worksheets, " +
"but this version of IPython can only handle the first. " +
"If you save this notebook, worksheets after the first will be lost.",
buttons : {
OK : {
class : "btn-danger"
}
}
});
}
};
/**
@ -1871,6 +1843,10 @@ define([
* @return {Object} A JSON-friendly representation of this notebook.
*/
Notebook.prototype.toJSON = function () {
// remove the conversion indicator, which only belongs in-memory
delete this.metadata.orig_nbformat;
delete this.metadata.orig_nbformat_minor;
var cells = this.get_cells();
var ncells = cells.length;
var cell_array = new Array(ncells);
@ -1883,11 +1859,7 @@ define([
cell_array[i] = cell.toJSON();
}
var data = {
// Only handle 1 worksheet for now.
worksheets : [{
cells: cell_array,
metadata: this.worksheet_metadata
}],
cells: cell_array,
metadata : this.metadata
};
if (trusted != this.trusted) {
@ -2337,10 +2309,13 @@ define([
}
this.set_dirty(false);
this.scroll_to_top();
if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
var nbmodel = data.content;
var orig_nbformat = nbmodel.metadata.orig_nbformat;
var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
var msg = "This notebook has been converted from an older " +
"notebook format (v"+data.orig_nbformat+") to the current notebook " +
"format (v"+data.nbformat+"). The next time you save this notebook, the " +
"notebook format (v"+orig_nbformat+") to the current notebook " +
"format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
"newer notebook format will be used and older versions of IPython " +
"may not be able to read it. To keep the older version, close the " +
"notebook without saving it.";
@ -2355,10 +2330,10 @@ define([
}
}
});
} else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
} else if (orig_nbformat_minor !== undefined && nbmodel.nbformat_minor !== orig_nbformat_minor) {
var that = this;
var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
var orig_vs = 'v' + nbmodel.nbformat + '.' + orig_nbformat_minor;
var this_vs = 'v' + nbmodel.nbformat + '.' + this.nbformat_minor;
var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
this_vs + ". You can still work with this notebook, but some features " +
"introduced in later notebook versions may not be available.";
@ -2412,13 +2387,13 @@ define([
Notebook.prototype.load_notebook_error = function (xhr, status, error) {
this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
utils.log_ajax_error(xhr, status, error);
var msg;
var msg = $("<div>");
if (xhr.status === 400) {
msg = escape(utils.ajax_error_msg(xhr));
msg.text(utils.ajax_error_msg(xhr));
} else if (xhr.status === 500) {
msg = "An unknown error occurred while loading this notebook. " +
msg.text("An unknown error occurred while loading this notebook. " +
"This version can load notebook formats " +
"v" + this.nbformat + " or earlier. See the server log for details.";
"v" + this.nbformat + " or earlier. See the server log for details.");
}
dialog.modal({
notebook: this,

@ -81,7 +81,7 @@ define([
*
*/
OutputArea.prototype._should_scroll = function (lines) {
if (lines <=0 ){ return }
if (lines <=0 ){ return; }
if (!lines) {
lines = 100;
}
@ -177,7 +177,7 @@ define([
OutputArea.prototype.scroll_if_long = function (lines) {
var n = lines | OutputArea.minimum_scroll_threshold;
if(n <= 0){
return
return;
}
if (this._should_scroll(n)) {
@ -211,16 +211,16 @@ define([
var content = msg.content;
if (msg_type === "stream") {
json.text = content.text;
json.stream = content.name;
json.name = content.name;
} else if (msg_type === "display_data") {
json = content.data;
json.data = content.data;
json.output_type = msg_type;
json.metadata = content.metadata;
} else if (msg_type === "execute_result") {
json = content.data;
json.data = content.data;
json.output_type = msg_type;
json.metadata = content.metadata;
json.prompt_number = content.execution_count;
json.execution_count = content.execution_count;
} else if (msg_type === "error") {
json.ename = content.ename;
json.evalue = content.evalue;
@ -233,16 +233,6 @@ define([
};
OutputArea.prototype.rename_keys = function (data, key_map) {
var remapped = {};
for (var key in data) {
var new_key = key_map[key] || key;
remapped[new_key] = data[key];
}
return remapped;
};
OutputArea.output_types = [
'application/javascript',
'text/html',
@ -257,12 +247,14 @@ define([
OutputArea.prototype.validate_output = function (json) {
// scrub invalid outputs
// TODO: right now everything is a string, but JSON really shouldn't be.
// nbformat 4 will fix that.
var data = json.data;
$.map(OutputArea.output_types, function(key){
if (json[key] !== undefined && typeof json[key] !== 'string') {
console.log("Invalid type for " + key, json[key]);
delete json[key];
if (key !== 'application/json' &&
data[key] !== undefined &&
typeof data[key] !== 'string'
) {
console.log("Invalid type for " + key, data[key]);
delete data[key];
}
});
return json;
@ -272,7 +264,9 @@ define([
this.expand();
// validate output data types
json = this.validate_output(json);
if (json.data) {
json = this.validate_output(json);
}
// Clear the output if clear is queued.
var needs_height_reset = false;
@ -376,12 +370,12 @@ define([
} else {
return subarea;
}
}
};
OutputArea.prototype._append_javascript_error = function (err, element) {
// display a message when a javascript error occurs in display output
var msg = "Javascript error adding output!"
var msg = "Javascript error adding output!";
if ( element === undefined ) return;
element
.append($('<div/>').text(msg).addClass('js-error'))
@ -410,7 +404,7 @@ define([
OutputArea.prototype.append_execute_result = function (json) {
var n = json.prompt_number || ' ';
var n = json.execution_count || ' ';
var toinsert = this.create_output_area();
if (this.prompt_area) {
toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
@ -449,17 +443,12 @@ define([
OutputArea.prototype.append_stream = function (json) {
// temporary fix: if stream undefined (json file written prior to this patch),
// default to most likely stdout:
if (json.stream === undefined){
json.stream = 'stdout';
}
var text = json.text;
var subclass = "output_"+json.stream;
var subclass = "output_"+json.name;
if (this.outputs.length > 0){
// have at least one output to consider
var last = this.outputs[this.outputs.length-1];
if (last.output_type == 'stream' && json.stream == last.stream){
if (last.output_type == 'stream' && json.name == last.name){
// latest output was in the same stream,
// so append directly into its pre tag
// escape ANSI & HTML specials:
@ -518,8 +507,8 @@ define([
for (var i=0; i < OutputArea.display_order.length; i++) {
var type = OutputArea.display_order[i];
var append = OutputArea.append_map[type];
if ((json[type] !== undefined) && append) {
var value = json[type];
if ((json.data[type] !== undefined) && append) {
var value = json.data[type];
if (!this.trusted && !OutputArea.safe_outputs[type]) {
// not trusted, sanitize HTML
if (type==='text/html' || type==='text/svg') {
@ -722,11 +711,11 @@ define([
var toinsert = this.create_output_subarea(md, "output_pdf", type);
var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
a.attr('target', '_blank');
a.text('View PDF')
a.text('View PDF');
toinsert.append(a);
element.append(toinsert);
return toinsert;
}
};
var append_latex = function (latex, md, element) {
// This method cannot do the typesetting because the latex first has to
@ -783,7 +772,7 @@ define([
// This seemed to be needed otherwise only the cell would be focused.
// But with the modal UI, this seems to work fine with one call to focus().
raw_input.focus();
}
};
OutputArea.prototype._submit_raw_input = function (evt) {
var container = this.element.find("div.raw_input_container");
@ -799,13 +788,13 @@ define([
output_type : 'stream',
stream : 'stdout',
text : theprompt.text() + echo + '\n'
}
};
// remove form container
container.parent().remove();
// replace with plaintext version in stdout
this.append_output(content, false);
this.events.trigger('send_input_reply.Kernel', value);
}
};
OutputArea.prototype.handle_clear_output = function (msg) {
@ -824,7 +813,7 @@ define([
// If a clear is queued, clear before adding another to the queue.
if (this.clear_queued) {
this.clear_output(false);
};
}
this.clear_queued = true;
} else {
@ -846,76 +835,39 @@ define([
this.trusted = true;
this.unscroll_area();
return;
};
}
};
// JSON serialization
OutputArea.prototype.fromJSON = function (outputs) {
OutputArea.prototype.fromJSON = function (outputs, metadata) {
var len = outputs.length;
var data;
metadata = metadata || {};
for (var i=0; i<len; i++) {
data = outputs[i];
var msg_type = data.output_type;
if (msg_type == "pyout") {
// pyout message has been renamed to execute_result,
// but the nbformat has not been updated,
// so transform back to pyout for json.
msg_type = data.output_type = "execute_result";
} else if (msg_type == "pyerr") {
// pyerr message has been renamed to error,
// but the nbformat has not been updated,
// so transform back to pyerr for json.
msg_type = data.output_type = "error";
this.append_output(outputs[i]);
}
if (metadata.collapsed !== undefined) {
this.collapsed = metadata.collapsed;
if (metadata.collapsed) {
this.collapse_output();
}
if (msg_type === "display_data" || msg_type === "execute_result") {
// convert short keys to mime keys
// TODO: remove mapping of short keys when we update to nbformat 4
data = this.rename_keys(data, OutputArea.mime_map_r);
data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
// msg spec JSON is an object, nbformat v3 JSON is a JSON string
if (data["application/json"] !== undefined && typeof data["application/json"] === 'string') {
data["application/json"] = JSON.parse(data["application/json"]);
}
}
if (metadata.autoscroll !== undefined) {
this.collapsed = metadata.collapsed;
if (metadata.collapsed) {
this.collapse_output();
} else {
this.expand_output();
}
this.append_output(data);
}
};
OutputArea.prototype.toJSON = function () {
var outputs = [];
var len = this.outputs.length;
var data;
for (var i=0; i<len; i++) {
data = this.outputs[i];
var msg_type = data.output_type;
if (msg_type === "display_data" || msg_type === "execute_result") {
// convert mime keys to short keys
data = this.rename_keys(data, OutputArea.mime_map);
data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
// msg spec JSON is an object, nbformat v3 JSON is a JSON string
if (data.json !== undefined && typeof data.json !== 'string') {
data.json = JSON.stringify(data.json);
}
}
if (msg_type == "execute_result") {
// pyout message has been renamed to execute_result,
// but the nbformat has not been updated,
// so transform back to pyout for json.
data.output_type = "pyout";
} else if (msg_type == "error") {
// pyerr message has been renamed to error,
// but the nbformat has not been updated,
// so transform back to pyerr for json.
data.output_type = "pyerr";
}
outputs[i] = data;
}
return outputs;
return this.outputs;
};
/**
@ -948,29 +900,6 @@ define([
OutputArea.minimum_scroll_threshold = 20;
OutputArea.mime_map = {
"text/plain" : "text",
"text/html" : "html",
"image/svg+xml" : "svg",
"image/png" : "png",
"image/jpeg" : "jpeg",
"text/latex" : "latex",
"application/json" : "json",
"application/javascript" : "javascript",
};
OutputArea.mime_map_r = {
"text" : "text/plain",
"html" : "text/html",
"svg" : "image/svg+xml",
"png" : "image/png",
"jpeg" : "image/jpeg",
"latex" : "text/latex",
"json" : "application/json",
"javascript" : "application/javascript",
};
OutputArea.display_order = [
'application/javascript',
'text/html',

@ -222,6 +222,19 @@ define([
MarkdownCell.prototype = Object.create(TextCell.prototype);
MarkdownCell.prototype.set_heading_level = function (level) {
// make a markdown cell a heading
level = level || 1;
var source = this.get_text();
source = source.replace(/^(#*)\s?/,
new Array(level + 1).join('#') + ' ');
this.set_text(source);
this.refresh();
if (this.rendered) {
this.render();
}
};
/**
* @method render
*/
@ -238,6 +251,19 @@ define([
html = mathjaxutils.replace_math(html, math);
html = security.sanitize_html(html);
html = $($.parseHTML(html));
// add anchors to headings
// console.log(html);
html.find(":header").addBack(":header").each(function (i, h) {
h = $(h);
var hash = h.text().replace(/ /g, '-');
h.attr('id', hash);
h.append(
$('<a/>')
.addClass('anchor-link')
.attr('href', '#' + hash)
.text('¶')
);
})
// links in markdown cells should open in new tabs
html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
this.set_rendered(html);
@ -305,121 +331,15 @@ define([
return cont;
};
var HeadingCell = function (options) {
// Constructor
//
// Parameters:
// options: dictionary
// Dictionary of keyword arguments.
// events: $(Events) instance
// config: dictionary
// keyboard_manager: KeyboardManager instance
// notebook: Notebook instance
options = options || {};
var config = utils.mergeopt(HeadingCell, options.config);
TextCell.apply(this, [$.extend({}, options, {config: config})]);
this.level = 1;
this.cell_type = 'heading';
};
HeadingCell.options_default = {
cm_config: {
theme: 'heading-1'
},
placeholder: "Type Heading Here"
};
HeadingCell.prototype = Object.create(TextCell.prototype);
/** @method fromJSON */
HeadingCell.prototype.fromJSON = function (data) {
if (data.level !== undefined){
this.level = data.level;
}
TextCell.prototype.fromJSON.apply(this, arguments);
this.code_mirror.setOption("theme", "heading-"+this.level);
};
/** @method toJSON */
HeadingCell.prototype.toJSON = function () {
var data = TextCell.prototype.toJSON.apply(this);
data.level = this.get_level();
return data;
};
/**
* Change heading level of cell, and re-render
* @method set_level
*/
HeadingCell.prototype.set_level = function (level) {
this.level = level;
this.code_mirror.setOption("theme", "heading-"+level);
if (this.rendered) {
this.rendered = false;
this.render();
}
};
/** The depth of header cell, based on html (h1 to h6)
* @method get_level
* @return {integer} level - for 1 to 6
*/
HeadingCell.prototype.get_level = function () {
return this.level;
};
HeadingCell.prototype.get_rendered = function () {
var r = this.element.find("div.text_cell_render");
return r.children().first().html();
};
HeadingCell.prototype.render = function () {
var cont = TextCell.prototype.render.apply(this);
if (cont) {
var text = this.get_text();
var math = null;
// Markdown headings must be a single line
text = text.replace(/\n/g, ' ');
if (text === "") { text = this.placeholder; }
text = new Array(this.level + 1).join("#") + " " + text;
var text_and_math = mathjaxutils.remove_math(text);
text = text_and_math[0];
math = text_and_math[1];
var html = marked.parser(marked.lexer(text));
html = mathjaxutils.replace_math(html, math);
html = security.sanitize_html(html);
var h = $($.parseHTML(html));
// add id and linkback anchor
var hash = h.text().trim().replace(/ /g, '-');
h.attr('id', hash);
h.append(
$('<a/>')
.addClass('anchor-link')
.attr('href', '#' + hash)
.text('¶')
);
this.set_rendered(h);
this.typeset();
}
return cont;
};
// Backwards compatability.
IPython.TextCell = TextCell;
IPython.MarkdownCell = MarkdownCell;
IPython.RawCell = RawCell;
IPython.HeadingCell = HeadingCell;
var textcell = {
'TextCell': TextCell,
'MarkdownCell': MarkdownCell,
'RawCell': RawCell,
'HeadingCell': HeadingCell,
TextCell: TextCell,
MarkdownCell: MarkdownCell,
RawCell: RawCell,
};
return textcell;
});

@ -36,7 +36,6 @@ div.cell.text_cell.rendered {
padding: 0px;
}
.text_cell.rendered .input_area {
display: none;
}
@ -45,25 +44,25 @@ div.cell.text_cell.rendered {
display:none;
}
.cm-s-heading-1,
.cm-s-heading-2,
.cm-s-heading-3,
.cm-s-heading-4,
.cm-s-heading-5,
.cm-s-heading-6 {
.cm-header-1,
.cm-header-2,
.cm-header-3,
.cm-header-4,
.cm-header-5,
.cm-header-6 {
font-weight: bold;
font-family: @font-family-sans-serif;
}
.cm-s-heading-1 { font-size:150%; }
.cm-s-heading-2 { font-size: 130%; }
.cm-s-heading-3 { font-size: 120%; }
.cm-s-heading-4 { font-size: 110%; }
.cm-s-heading-5 {
font-size: 100%;
.cm-header-1 { font-size: 185.7%; }
.cm-header-2 { font-size: 157.1%; }
.cm-header-3 { font-size: 128.6%; }
.cm-header-4 { font-size: 110%; }
.cm-header-5 {
font-size: 100%;
font-style: italic;
}
.cm-s-heading-6 {
font-size: 90%;
.cm-header-6 {
font-size: 100%;
font-style: italic;
}

@ -1116,33 +1116,33 @@ div.cell.text_cell.rendered {
.text_cell.unrendered .text_cell_render {
display: none;
}
.cm-s-heading-1,
.cm-s-heading-2,
.cm-s-heading-3,
.cm-s-heading-4,
.cm-s-heading-5,
.cm-s-heading-6 {
.cm-header-1,
.cm-header-2,
.cm-header-3,
.cm-header-4,
.cm-header-5,
.cm-header-6 {
font-weight: bold;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.cm-s-heading-1 {
font-size: 150%;
.cm-header-1 {
font-size: 185.7%;
}
.cm-s-heading-2 {
font-size: 130%;
.cm-header-2 {
font-size: 157.1%;
}
.cm-s-heading-3 {
font-size: 120%;
.cm-header-3 {
font-size: 128.6%;
}
.cm-s-heading-4 {
.cm-header-4 {
font-size: 110%;
}
.cm-s-heading-5 {
.cm-header-5 {
font-size: 100%;
font-style: italic;
}
.cm-s-heading-6 {
font-size: 90%;
.cm-header-6 {
font-size: 100%;
font-style: italic;
}
.widget-area {

@ -8985,33 +8985,33 @@ div.cell.text_cell.rendered {
.text_cell.unrendered .text_cell_render {
display: none;
}
.cm-s-heading-1,
.cm-s-heading-2,
.cm-s-heading-3,
.cm-s-heading-4,
.cm-s-heading-5,
.cm-s-heading-6 {
.cm-header-1,
.cm-header-2,
.cm-header-3,
.cm-header-4,
.cm-header-5,
.cm-header-6 {
font-weight: bold;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.cm-s-heading-1 {
font-size: 150%;
.cm-header-1 {
font-size: 185.7%;
}
.cm-s-heading-2 {
font-size: 130%;
.cm-header-2 {
font-size: 157.1%;
}
.cm-s-heading-3 {
font-size: 120%;
.cm-header-3 {
font-size: 128.6%;
}
.cm-s-heading-4 {
.cm-header-4 {
font-size: 110%;
}
.cm-s-heading-5 {
.cm-header-5 {
font-size: 100%;
font-style: italic;
}
.cm-s-heading-6 {
font-size: 90%;
.cm-header-6 {
font-size: 100%;
font-style: italic;
}
.widget-area {

@ -189,12 +189,6 @@ class="notebook_app"
<li id="to_raw"
title="Contents will pass through nbconvert unmodified">
<a href="#">Raw NBConvert</a></li>
<li id="to_heading1"><a href="#">Heading 1</a></li>
<li id="to_heading2"><a href="#">Heading 2</a></li>
<li id="to_heading3"><a href="#">Heading 3</a></li>
<li id="to_heading4"><a href="#">Heading 4</a></li>
<li id="to_heading5"><a href="#">Heading 5</a></li>
<li id="to_heading6"><a href="#">Heading 6</a></li>
</ul>
</li>
<li class="divider"></li>

@ -52,12 +52,12 @@ casper.notebook_test(function () {
this.then(function () {
this.select_cell(2);
this.trigger_keydown('1'); // switch it to heading for the next test
this.test.assertEquals(this.get_cell(2).cell_type, 'heading', 'test cell is heading');
this.trigger_keydown('y'); // switch it to code for the next test
this.test.assertEquals(this.get_cell(2).cell_type, 'code', 'test cell is code');
this.trigger_keydown('b'); // new cell below
this.test.assertEquals(this.get_cell(3).cell_type, 'heading', 'b; inserts a heading cell below heading cell');
this.test.assertEquals(this.get_cell(3).cell_type, 'code', 'b; inserts a code cell below code cell');
this.trigger_keydown('a'); // new cell above
this.test.assertEquals(this.get_cell(3).cell_type, 'heading', 'a; inserts a heading cell below heading cell');
this.test.assertEquals(this.get_cell(3).cell_type, 'code', 'a; inserts a code cell above code cell');
});
this.thenEvaluate(function() {
@ -72,6 +72,6 @@ casper.notebook_test(function () {
this.test.assertEquals(this.get_cell(2).cell_type, 'raw', 'a; inserts a raw cell above raw cell');
this.trigger_keydown('y'); // switch it to code for the next test
this.trigger_keydown('b'); // new cell below
this.test.assertEquals(this.get_cell(3).cell_type, 'raw', 'b; inserts a raw cell above raw cell');
this.test.assertEquals(this.get_cell(3).cell_type, 'raw', 'b; inserts a raw cell below raw cell');
});
});

@ -4,25 +4,38 @@
casper.notebook_test(function () {
this.then(function () {
// Cell mode change
this.select_cell(0);
var index = 0;
this.select_cell(index);
var a = 'hello\nmulti\nline';
this.set_cell_text(index, a);
this.trigger_keydown('esc','r');
this.test.assertEquals(this.get_cell(0).cell_type, 'raw', 'r; cell is raw');
this.test.assertEquals(this.get_cell(index).cell_type, 'raw', 'r; cell is raw');
this.trigger_keydown('1');
this.test.assertEquals(this.get_cell(0).cell_type, 'heading', '1; cell is heading');
this.test.assertEquals(this.get_cell(0).level, 1, '1; cell is level 1 heading');
this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '1; cell is markdown');
this.test.assertEquals(this.get_cell_text(index), '# ' + a, '1; markdown heading');
this.trigger_keydown('2');
this.test.assertEquals(this.get_cell(0).level, 2, '2; cell is level 2 heading');
this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '2; cell is markdown');
this.test.assertEquals(this.get_cell_text(index), '## ' + a, '2; markdown heading');
this.trigger_keydown('3');
this.test.assertEquals(this.get_cell(0).level, 3, '3; cell is level 3 heading');
this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '3; cell is markdown');
this.test.assertEquals(this.get_cell_text(index), '### ' + a, '3; markdown heading');
this.trigger_keydown('4');
this.test.assertEquals(this.get_cell(0).level, 4, '4; cell is level 4 heading');
this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '4; cell is markdown');
this.test.assertEquals(this.get_cell_text(index), '#### ' + a, '4; markdown heading');
this.trigger_keydown('5');
this.test.assertEquals(this.get_cell(0).level, 5, '5; cell is level 5 heading');
this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '5; cell is markdown');
this.test.assertEquals(this.get_cell_text(index), '##### ' + a, '5; markdown heading');
this.trigger_keydown('6');
this.test.assertEquals(this.get_cell(0).level, 6, '6; cell is level 6 heading');
this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '6; cell is markdown');
this.test.assertEquals(this.get_cell_text(index), '###### ' + a, '6; markdown heading');
this.trigger_keydown('m');
this.test.assertEquals(this.get_cell(0).cell_type, 'markdown', 'm; cell is markdown');
this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', 'm; cell is markdown');
this.test.assertEquals(this.get_cell_text(index), '###### ' + a, 'm; still markdown heading');
this.trigger_keydown('y');
this.test.assertEquals(this.get_cell(0).cell_type, 'code', 'y; cell is code');
this.test.assertEquals(this.get_cell(index).cell_type, 'code', 'y; cell is code');
this.test.assertEquals(this.get_cell_text(index), '###### ' + a, 'y; still has hashes');
this.trigger_keydown('1');
this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '1; cell is markdown');
this.test.assertEquals(this.get_cell_text(index), '# ' + a, '1; markdown heading');
});
});

@ -13,7 +13,7 @@ casper.notebook_test(function () {
this.evaluate(function () {
var cell = IPython.notebook.get_cell(0);
var json = cell.toJSON();
json.prompt_number = "<script> alert('hello from input prompts !')</script>";
json.execution_count = "<script> alert('hello from input prompts !')</script>";
cell.fromJSON(json);
});

@ -10,38 +10,53 @@ casper.notebook_test(function () {
cell.render();
return cell.get_rendered();
});
this.test.assertEquals(output.trim(), '<h1 id=\"foo\">Foo</h1>', 'Markdown JS API works.');
this.test.assertEquals(output.trim(), '<h1 id=\"Foo\">Foo<a class=\"anchor-link\" href=\"#Foo\">¶</a></h1>', 'Markdown JS API works.');
// Test menubar entries.
output = this.evaluate(function () {
$('#to_code').mouseenter().click();
$('#to_markdown').mouseenter().click();
var cell = IPython.notebook.get_selected_cell();
cell.set_text('# Bar');
cell.set_text('**Bar**');
$('#run_cell').mouseenter().click();
return cell.get_rendered();
});
this.test.assertEquals(output.trim(), '<h1 id=\"bar\">Bar</h1>', 'Markdown menubar items work.');
this.test.assertEquals(output.trim(), '<p><strong>Bar</strong></p>', 'Markdown menubar items work.');
// Test toolbar buttons.
output = this.evaluate(function () {
$('#cell_type').val('code').change();
$('#cell_type').val('markdown').change();
var cell = IPython.notebook.get_selected_cell();
cell.set_text('# Baz');
cell.set_text('*Baz*');
$('#run_b').click();
return cell.get_rendered();
});
this.test.assertEquals(output.trim(), '<h1 id=\"baz\">Baz</h1>', 'Markdown toolbar items work.');
this.test.assertEquals(output.trim(), '<p><em>Baz</em></p>', 'Markdown toolbar items work.');
// Test JavaScript models.
var output = this.evaluate(function () {
// Test markdown headings
var text = 'multi\nline';
this.evaluate(function (text) {
var cell = IPython.notebook.insert_cell_at_index('markdown', 0);
cell.set_text('# Qux');
cell.render();
return cell.get_rendered();
});
this.test.assertEquals(output.trim(), '<h1 id=\"qux\">Qux</h1>', 'Markdown JS API works.');
cell.set_text(text);
}, {text: text});
var set_level = function (level) {
return casper.evaluate(function (level) {
var cell = IPython.notebook.get_cell(0);
cell.set_heading_level(level);
return cell.get_text();
}, {level: level});
};
var level_text;
var levels = [ 1, 2, 3, 4, 5, 6, 2, 1 ];
for (var idx=0; idx < levels.length; idx++) {
var level = levels[idx];
level_text = set_level(level);
hashes = new Array(level + 1).join('#');
this.test.assertEquals(level_text, hashes + ' ' + text, 'markdown set_heading_level ' + level);
}
});

@ -29,7 +29,7 @@ casper.notebook_test(function () {
var ex = expected[i];
this.test.assertEquals(r.output_type, ex.output_type, "output " + i);
if (r.output_type === 'stream') {
this.test.assertEquals(r.stream, ex.stream, "stream " + i);
this.test.assertEquals(r.name, ex.name, "stream " + i);
this.test.assertEquals(r.text, ex.text, "content " + i);
}
}
@ -57,7 +57,7 @@ casper.notebook_test(function () {
"print(3)"
].join("\n"), [{
output_type: "stream",
stream: "stdout",
name: "stdout",
text: "1\n2\n3\n"
}]
);
@ -69,11 +69,11 @@ casper.notebook_test(function () {
"print(3, file=sys.stderr)"
].join("\n"), [{
output_type: "stream",
stream: "stdout",
name: "stdout",
text: "1\n2\n"
},{
output_type: "stream",
stream: "stderr",
name: "stderr",
text: "3\n"
}]
);
@ -85,13 +85,13 @@ casper.notebook_test(function () {
"print(3)"
].join("\n"), [{
output_type: "stream",
stream: "stdout",
name: "stdout",
text: "1\n"
},{
output_type: "display_data",
},{
output_type: "stream",
stream: "stdout",
name: "stdout",
text: "3\n"
}]
);

@ -14,7 +14,7 @@ mime = {
"json" : "application/json",
"javascript" : "application/javascript",
};
var black_dot_jpeg="u\"\"\"/9j/4AAQSkZJRgABAQEASABIAAD/2wBDACodICUgGiolIiUvLSoyP2lEPzo6P4FcYUxpmYagnpaG\nk5GovfLNqLPltZGT0v/V5fr/////o8v///////L/////2wBDAS0vLz83P3xERHz/rpOu////////\n////////////////////////////////////////////////////////////wgARCAABAAEDAREA\nAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAABP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEA\nAhADEAAAARn/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAEFAn//xAAUEQEAAAAAAAAAAAAA\nAAAAAAAA/9oACAEDAQE/AX//xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAECAQE/AX//xAAUEAEA\nAAAAAAAAAAAAAAAAAAAA/9oACAEBAAY/An//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAE/\nIX//2gAMAwEAAgADAAAAEB//xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAEDAQE/EH//xAAUEQEA\nAAAAAAAAAAAAAAAAAAAA/9oACAECAQE/EH//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAE/\nEH//2Q==\"\"\"";
var black_dot_png = 'u\"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAWJLR0QA\\niAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94BCRQnOqNu0b4AAAAKSURBVAjXY2AA\\nAAACAAHiIbwzAAAAAElFTkSuQmCC\"';
var svg = "\"<svg width='1cm' height='1cm' viewBox='0 0 1000 500'><defs><style>rect {fill:red;}; </style></defs><rect id='r1' x='200' y='100' width='600' height='300' /></svg>\"";
@ -24,18 +24,20 @@ var svg = "\"<svg width='1cm' height='1cm' viewBox='0 0 1000 500'><defs><style>r
// name, and that fromJSON also gets its long mimetype name
function assert_has(short_name, json, result, result2) {
long_name = mime[short_name];
this.test.assertTrue(json[0].hasOwnProperty(short_name),
'toJSON() representation uses ' + short_name);
this.test.assertTrue(result.hasOwnProperty(long_name),
this.test.assertFalse(json[0].data.hasOwnProperty(short_name),
"toJSON() representation doesn't use " + short_name);
this.test.assertTrue(json[0].data.hasOwnProperty(long_name),
'toJSON() representation uses ' + long_name);
this.test.assertTrue(result.data.hasOwnProperty(long_name),
'toJSON() original embedded JSON keeps ' + long_name);
this.test.assertTrue(result2.hasOwnProperty(long_name),
this.test.assertTrue(result2.data.hasOwnProperty(long_name),
'fromJSON() embedded ' + short_name + ' gets mime key ' + long_name);
}
// helper function for checkout that the first two cells have a particular
// output_type (either 'execute_result' or 'display_data'), and checks the to/fromJSON
// for a set of mimetype keys, using their short names ('javascript', 'text',
// 'png', etc).
// for a set of mimetype keys, ensuring the old short names ('javascript', 'text',
// 'png', etc) are not used.
function check_output_area(output_type, keys) {
this.wait_for_output(0);
json = this.evaluate(function() {
@ -69,7 +71,7 @@ function clear_and_execute(that, code) {
that.execute_cell(0);
that.wait_for_idle();
});
};
}
casper.notebook_test(function () {
this.evaluate(function () {
@ -86,7 +88,7 @@ casper.notebook_test(function () {
var result = this.get_output_cell(0);
var num_cells = this.get_cells_length();
this.test.assertEquals(num_cells, 2, '%%javascript magic works');
this.test.assertTrue(result.hasOwnProperty('application/javascript'),
this.test.assertTrue(result.data.hasOwnProperty('application/javascript'),
'testing JS embedded with mime key');
});
@ -234,10 +236,10 @@ casper.notebook_test(function () {
this.then(function () {
var long_name = 'text/superfancymimetype';
var result = this.get_output_cell(0);
this.test.assertTrue(result.hasOwnProperty(long_name),
this.test.assertTrue(result.data.hasOwnProperty(long_name),
'display_data custom mimetype ' + long_name);
var result = this.get_output_cell(0, 1);
this.test.assertTrue(result.hasOwnProperty(long_name),
result = this.get_output_cell(0, 1);
this.test.assertTrue(result.data.hasOwnProperty(long_name),
'execute_result custom mimetype ' + long_name);
});

@ -26,7 +26,7 @@ casper.notebook_test(function () {
var output = this.get_output_cell(0);
this.test.assert(messages.length > 0, "Captured log message");
this.test.assertEquals(messages[messages.length-1].substr(0,26), "Invalid type for image/png", "Logged Invalid type message");
this.test.assertEquals(output['image/png'], undefined, "Non-string png data was stripped");
this.test.assertEquals(output['text/plain'], '5', "text data is fine");
this.test.assertEquals(output.data['image/png'], undefined, "Non-string png data was stripped");
this.test.assertEquals(output.data['text/plain'], '5', "text data is fine");
});
});

@ -10,8 +10,9 @@ pjoin = os.path.join
import requests
import json
from IPython.nbformat.current import (new_notebook, write, new_worksheet,
new_heading_cell, new_code_cell,
from IPython.nbformat import write
from IPython.nbformat.v4 import (new_notebook,
new_markdown_cell, new_code_cell,
new_output)
from IPython.html.utils import url_path_join
@ -62,18 +63,18 @@ class FilesTest(NotebookTestBase):
nbdir = self.notebook_dir.name
base = self.base_url()
nb = new_notebook(name='testnb')
ws = new_worksheet()
nb.worksheets = [ws]
ws.cells.append(new_heading_cell(u'Created by test ³'))
cc1 = new_code_cell(input=u'print(2*6)')
cc1.outputs.append(new_output(output_text=u'12', output_type='stream'))
ws.cells.append(cc1)
nb = new_notebook(
cells=[
new_markdown_cell(u'Created by test ³'),
new_code_cell("print(2*6)", outputs=[
new_output("stream", text="12"),
])
]
)
with io.open(pjoin(nbdir, 'testnb.ipynb'), 'w',
encoding='utf-8') as f:
write(nb, f, format='ipynb')
write(nb, f, version=4)
with io.open(pjoin(nbdir, 'test.bin'), 'wb') as f:
f.write(b'\xff' + os.urandom(5))

@ -170,7 +170,7 @@ casper.notebook_test(function () {
this.test.assert(outputs.length <= 5, 'Messages throttled.');
// We also need to verify that the last state sent was correct.
var last_state = outputs[outputs.length-1]['text/plain'];
var last_state = outputs[outputs.length-1].data['text/plain'];
this.test.assertEquals(last_state, "20", "Last state sent when throttling.");
});
});

@ -37,7 +37,7 @@ casper.notebook_test(function () {
this.wait_for_output(button_index, 1);
this.then(function () {
this.test.assertEquals(this.get_output_cell(button_index, 1)['text/plain'], "'Clicked'",
this.test.assertEquals(this.get_output_cell(button_index, 1).data['text/plain'], "'Clicked'",
'Button click event fires.');
});
});

@ -6,6 +6,7 @@ The IPython notebook
:maxdepth: 2
notebook
nbformat
nbconvert
public_server
security

@ -0,0 +1,338 @@
.. _nbformat:
===========================
The Jupyter Notebook Format
===========================
Introduction
============
Jupyter (né IPython) notebook files are simple JSON documents,
containing text, source code, rich media output, and metadata.
each segment of the document is stored in a cell.
Some general points about the notebook format:
.. note::
*All* metadata fields are optional.
While the type and values of some metadata are defined,
no metadata values are required to be defined.
Top-level structure
===================
At the highest level, a Jupyter notebook is a dictionary with a few keys:
- metadata (dict)
- nbformat (int)
- nbformat_minor (int)
- cells (list)
.. sourcecode:: python
{
"metadata" : {
"signature": "hex-digest", # used for authenticating unsafe outputs on load
"kernel_info": {
# if kernel_info is defined, its name and language fields are required.
"name" : "the name of the kernel",
"language" : "the programming language of the kernel",
"codemirror_mode": "The name of the codemirror mode to use [optional]"
},
},
"nbformat": 4,
"nbformat_minor": 0,
"cells" : [
# list of cell dictionaries, see below
],
}
Some fields, such as code input and text output, are characteristically multi-line strings.
When these fields are written to disk, they **may** be written as a list of strings,
which should be joined with ``''`` when reading back into memory.
In programmatic APIs for working with notebooks (Python, Javascript),
these are always re-joined into the original multi-line string.
If you intend to work with notebook files directly,
you must allow multi-line string fields to be either a string or list of strings.
Cell Types
==========
There are a few basic cell types for encapsulating code and text.
All cells have the following basic structure:
.. sourcecode:: python
{
"cell_type" : "name",
"metadata" : {},
"source" : "single string or [list, of, strings]",
}
Markdown cells
--------------
Markdown cells are used for body-text, and contain markdown,
as defined in `GitHub-flavored markdown`_, and implemented in marked_.
.. _GitHub-flavored markdown: https://help.github.com/articles/github-flavored-markdown
.. _marked: https://github.com/chjj/marked
.. sourcecode:: python
{
"cell_type" : "markdown",
"metadata" : {},
"source" : ["some *markdown*"],
}
.. versionchanged:: nbformat 4.0
Heading cells have been removed, in favor of simple headings in markdown.
Code cells
----------
Code cells are the primary content of Jupyter notebooks.
They contain source code in the language of the document's associated kernel,
and a list of outputs associated with executing that code.
They also have an execution_count, which must be an integer or ``null``.
.. sourcecode:: python
{
"cell_type" : "code",
"execution_count": 1, # integer or null
"metadata" : {
"collapsed" : True, # whether the output of the cell is collapsed
"autoscroll": False, # any of true, false or "auto"
},
"source" : ["some code"],
"outputs": [{
# list of output dicts (described below)
"output_type": "stream",
...
}],
}
.. versionchanged:: nbformat 4.0
``input`` was renamed to ``source``, for consistency among cell types.
.. versionchanged:: nbformat 4.0
``prompt_number`` renamed to ``execution_count``
Code cell outputs
-----------------
A code cell can have a variety of outputs (stream data or rich mime-type output).
These correspond to :ref:`messages <messaging>` produced as a result of executing the cell.
All outputs have an ``output_type`` field,
which is a string defining what type of output it is.
stream output
*************
.. sourcecode:: python
{
"output_type" : "stream",
"name" : "stdout", # or stderr
"data" : ["multiline stream text"],
}
.. versionchanged:: nbformat 4.0
The keys ``stream`` and ``text`` were changed to ``name`` and ``data`` to match
the stream message specification.
display_data
************
Rich display outputs, as created by ``display_data`` messages,
contain data keyed by mime-type. This is often called a mime-bundle,
and shows up in various locations in the notebook format and message spec.
The metadata of these messages may be keyed by mime-type as well.
.. sourcecode:: python
{
"output_type" : "display_data",
"data" : {
"text/plain" : ["multiline text data"],
"image/png": ["base64-encoded-png-data"],
"application/json": {
# JSON data is included as-is
"json": "data",
},
},
"metadata" : {
"image/png": {
"width": 640,
"height": 480,
},
},
}
.. versionchanged:: nbformat 4.0
``application/json`` output is no longer double-serialized into a string.
.. versionchanged:: nbformat 4.0
mime-types are used for keys, instead of a combination of short names (``text``)
and mime-types, and are stored in a ``data`` key, rather than the top-level.
i.e. ``output.data['image/png']`` instead of ``output.png``.
execute_result
**************
Results of executing a cell (as created by ``displayhook`` in Python)
are stored in ``execute_result`` outputs.
`execute_result` outputs are identical to ``display_data``,
adding only a ``execution_count`` field, which must be an integer.
.. sourcecode:: python
{
"output_type" : "execute_result",
"execution_count": 42,
"data" : {
"text/plain" : ["multiline text data"],
"image/png": ["base64-encoded-png-data"],
"application/json": {
# JSON data is included as-is
"json": "data",
},
},
"metadata" : {
"image/png": {
"width": 640,
"height": 480,
},
},
}
.. versionchanged:: nbformat 4.0
``pyout`` renamed to ``execute_result``
.. versionchanged:: nbformat 4.0
``prompt_number`` renamed to ``execution_count``
error
*****
Failed execution may show a traceback
.. sourcecode:: python
{
'ename' : str, # Exception name, as a string
'evalue' : str, # Exception value, as a string
# The traceback will contain a list of frames,
# represented each as a string.
'traceback' : list,
}
.. versionchanged:: nbformat 4.0
``pyerr`` renamed to ``error``
.. _raw nbconvert cells:
Raw NBConvert cells
-------------------
A raw cell is defined as content that should be included *unmodified* in :ref:`nbconvert <nbconvert>` output.
For example, this cell could include raw LaTeX for nbconvert to pdf via latex,
or restructured text for use in Sphinx documentation.
The notebook authoring environment does not render raw cells.
The only logic in a raw cell is the `format` metadata field.
If defined, it specifies which nbconvert output format is the intended target
for the raw cell. When outputting to any other format,
the raw cell's contents will be excluded.
In the default case when this value is undefined,
a raw cell's contents will be included in any nbconvert output,
regardless of format.
.. sourcecode:: python
{
"cell_type" : "raw",
"metadata" : {
# the mime-type of the target nbconvert format.
# nbconvert to formats other than this will exclude this cell.
"format" : "mime/type"
},
"source" : ["some nbformat mime-type data"]
}
Metadata
========
Metadata is a place that you can put arbitrary JSONable information about
your notebook, cell, or output. Because it is a shared namespace,
any custom metadata should use a sufficiently unique namespace,
such as `metadata.kaylees_md.foo = "bar"`.
Metadata fields officially defined for Jupyter notebooks are listed here:
Notebook metadata
-----------------
The following metadata keys are defined at the notebook level:
=========== =============== ==============
Key Value Interpretation
=========== =============== ==============
kernelspec dict A :ref:`kernel specification <kernelspecs>`
signature str A hashed :ref:`signature <notebook_security>` of the notebook
=========== =============== ==============
Cell metadata
-------------
The following metadata keys are defined at the cell level:
=========== =============== ==============
Key Value Interpretation
=========== =============== ==============
collapsed bool Whether the cell's output container should be collapsed
autoscroll bool or 'auto' Whether the cell's output is scrolled, unscrolled, or autoscrolled
deletable bool If False, prevent deletion of the cell
format 'mime/type' The mime-type of a :ref:`Raw NBConvert Cell <raw nbconvert cells>`
name str A name for the cell. Should be unique
tags list of str A list of string tags on the cell. Commas are not allowed in a tag
=========== =============== ==============
Output metadata
---------------
The following metadata keys are defined for code cell outputs:
=========== =============== ==============
Key Value Interpretation
=========== =============== ==============
isolated bool Whether the output should be isolated into an IFrame
=========== =============== ==============

@ -199,6 +199,7 @@ def find_package_data():
'IPython.nbformat' : [
'tests/*.ipynb',
'v3/nbformat.v3.schema.json',
'v4/nbformat.v4.schema.json',
]
}

Loading…
Cancel
Save