##// END OF EJS Templates
remove heading cells in v4
MinRK -
Show More
@@ -11,7 +11,7 b' import requests'
11 11 from IPython.html.utils import url_path_join
12 12 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
13 13 from IPython.nbformat.current import (new_notebook, write,
14 new_heading_cell, new_code_cell,
14 new_markdown_cell, new_code_cell,
15 15 new_output)
16 16
17 17 from IPython.testing.decorators import onlyif_cmds_exist
@@ -55,7 +55,7 b' class APITest(NotebookTestBase):'
55 55
56 56 nb = new_notebook()
57 57
58 nb.cells.append(new_heading_cell(u'Created by test Β³'))
58 nb.cells.append(new_markdown_cell(u'Created by test Β³'))
59 59 cc1 = new_code_cell(source=u'print(2*6)')
60 60 cc1.outputs.append(new_output(output_type="stream", text=u'12'))
61 61 cc1.outputs.append(new_output(output_type="execute_result",
@@ -255,7 +255,7 b' class FileContentsManager(ContentsManager):'
255 255 try:
256 256 nb = current.read(f, u'json')
257 257 except Exception as e:
258 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
258 raise web.HTTPError(400, u"Unreadable Notebook: %s %r" % (os_path, e))
259 259 self.mark_trusted_cells(nb, name, path)
260 260 model['content'] = nb
261 261 model['format'] = 'json'
@@ -16,7 +16,7 b' from IPython.html.utils import url_path_join, url_escape'
16 16 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
17 17 from IPython.nbformat import current
18 18 from IPython.nbformat.current import (new_notebook, write, read,
19 new_heading_cell, to_notebook_json)
19 new_markdown_cell, to_notebook_json)
20 20 from IPython.nbformat import v2
21 21 from IPython.utils import py3compat
22 22 from IPython.utils.data import uniq_stable
@@ -415,7 +415,7 b' class APITest(NotebookTestBase):'
415 415 resp = self.api.read('a.ipynb', 'foo')
416 416 nbcontent = json.loads(resp.text)['content']
417 417 nb = to_notebook_json(nbcontent)
418 nb.cells.append(new_heading_cell(u'Created by test Β³'))
418 nb.cells.append(new_markdown_cell(u'Created by test Β³'))
419 419
420 420 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
421 421 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
@@ -452,7 +452,7 b' class APITest(NotebookTestBase):'
452 452 # Modify it
453 453 nbcontent = json.loads(resp.text)['content']
454 454 nb = to_notebook_json(nbcontent)
455 hcell = new_heading_cell('Created by test')
455 hcell = new_markdown_cell('Created by test')
456 456 nb.cells.append(hcell)
457 457 # Save
458 458 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
@@ -139,12 +139,6 b' define(['
139 139 .append($('<option/>').attr('value','code').text('Code'))
140 140 .append($('<option/>').attr('value','markdown').text('Markdown'))
141 141 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
142 .append($('<option/>').attr('value','heading1').text('Heading 1'))
143 .append($('<option/>').attr('value','heading2').text('Heading 2'))
144 .append($('<option/>').attr('value','heading3').text('Heading 3'))
145 .append($('<option/>').attr('value','heading4').text('Heading 4'))
146 .append($('<option/>').attr('value','heading5').text('Heading 5'))
147 .append($('<option/>').attr('value','heading6').text('Heading 6'))
148 142 );
149 143 };
150 144
@@ -190,24 +184,18 b' define(['
190 184
191 185 this.element.find('#cell_type').change(function () {
192 186 var cell_type = $(this).val();
193 if (cell_type === 'code') {
187 switch (cell_type) {
188 case 'code':
194 189 that.notebook.to_code();
195 } else if (cell_type === 'markdown') {
190 break;
191 case 'markdown':
196 192 that.notebook.to_markdown();
197 } else if (cell_type === 'raw') {
193 break;
194 case 'raw':
198 195 that.notebook.to_raw();
199 } else if (cell_type === 'heading1') {
200 that.notebook.to_heading(undefined, 1);
201 } else if (cell_type === 'heading2') {
202 that.notebook.to_heading(undefined, 2);
203 } else if (cell_type === 'heading3') {
204 that.notebook.to_heading(undefined, 3);
205 } else if (cell_type === 'heading4') {
206 that.notebook.to_heading(undefined, 4);
207 } else if (cell_type === 'heading5') {
208 that.notebook.to_heading(undefined, 5);
209 } else if (cell_type === 'heading6') {
210 that.notebook.to_heading(undefined, 6);
196 break;
197 default:
198 console.log("unrecognized cell type:", cell_type);
211 199 }
212 200 });
213 201 this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
@@ -824,7 +824,7 b' define(['
824 824 * Index will be brought back into the accessible range [0,n]
825 825 *
826 826 * @method insert_cell_at_index
827 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
827 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
828 828 * @param [index] {int} a valid index where to insert cell
829 829 *
830 830 * @return cell {cell|null} created cell or null
@@ -860,15 +860,19 b' define(['
860 860 notebook: this,
861 861 tooltip: this.tooltip,
862 862 };
863 if (type === 'code') {
863 switch(type) {
864 case 'code':
864 865 cell = new codecell.CodeCell(this.kernel, cell_options);
865 866 cell.set_input_prompt();
866 } else if (type === 'markdown') {
867 break;
868 case 'markdown':
867 869 cell = new textcell.MarkdownCell(cell_options);
868 } else if (type === 'raw') {
870 break;
871 case 'raw':
869 872 cell = new textcell.RawCell(cell_options);
870 } else if (type === 'heading') {
871 cell = new textcell.HeadingCell(cell_options);
873 break;
874 default:
875 console.log("invalid cell type: ", type);
872 876 }
873 877
874 878 if(this._insert_element_at_index(cell.element,index)) {
@@ -1090,10 +1094,10 b' define(['
1090 1094 if (this.is_valid_cell_index(i)) {
1091 1095 var source_cell = this.get_cell(i);
1092 1096 var target_cell = null;
1093 if (source_cell instanceof textcell.HeadingCell) {
1094 source_cell.set_level(level);
1097 if (source_cell instanceof textcell.MarkdownCell) {
1098 source_cell.set_heading_level(level);
1095 1099 } else {
1096 target_cell = this.insert_cell_below('heading',i);
1100 target_cell = this.insert_cell_below('markdown',i);
1097 1101 var text = source_cell.get_text();
1098 1102 if (text === source_cell.placeholder) {
1099 1103 text = '';
@@ -1101,9 +1105,9 b' define(['
1101 1105 //metadata
1102 1106 target_cell.metadata = source_cell.metadata;
1103 1107 // We must show the editor before setting its contents
1104 target_cell.set_level(level);
1105 1108 target_cell.unrender();
1106 1109 target_cell.set_text(text);
1110 target_cell.set_heading_level(level);
1107 1111 // make this value the starting point, so that we can only undo
1108 1112 // to this state, instead of a blank cell
1109 1113 target_cell.code_mirror.clearHistory();
@@ -1117,7 +1121,7 b' define(['
1117 1121 }
1118 1122 this.set_dirty(true);
1119 1123 this.events.trigger('selected_cell_type_changed.Notebook',
1120 {'cell_type':'heading',level:level}
1124 {'cell_type':'markdown',level:level}
1121 1125 );
1122 1126 }
1123 1127 };
@@ -1526,8 +1530,8 b' define(['
1526 1530 }
1527 1531 this.codemirror_mode = newmode;
1528 1532 codecell.CodeCell.options_default.cm_config.mode = newmode;
1529 modename = newmode.mode || newmode.name || newmode
1530
1533 modename = newmode.mode || newmode.name || newmode;
1534
1531 1535 that = this;
1532 1536 utils.requireCodeMirrorMode(modename, function () {
1533 1537 $.map(that.get_cells(), function(cell, i) {
@@ -1539,7 +1543,7 b' define(['
1539 1543 cell.cm_config.mode = newmode;
1540 1544 }
1541 1545 });
1542 })
1546 });
1543 1547 };
1544 1548
1545 1549 // Session related things
@@ -2383,13 +2387,13 b' define(['
2383 2387 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2384 2388 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2385 2389 utils.log_ajax_error(xhr, status, error);
2386 var msg;
2390 var msg = $("<div>");
2387 2391 if (xhr.status === 400) {
2388 msg = escape(utils.ajax_error_msg(xhr));
2392 msg.text(utils.ajax_error_msg(xhr));
2389 2393 } else if (xhr.status === 500) {
2390 msg = "An unknown error occurred while loading this notebook. " +
2394 msg.text("An unknown error occurred while loading this notebook. " +
2391 2395 "This version can load notebook formats " +
2392 "v" + this.nbformat + " or earlier. See the server log for details.";
2396 "v" + this.nbformat + " or earlier. See the server log for details.");
2393 2397 }
2394 2398 dialog.modal({
2395 2399 notebook: this,
@@ -222,6 +222,26 b' define(['
222 222
223 223 MarkdownCell.prototype = Object.create(TextCell.prototype);
224 224
225 MarkdownCell.prototype.set_heading_level = function (level) {
226 // make a markdown cell a heading
227 level = level || 1;
228 var source = this.get_text();
229 // \s\S appears to be the js version of multi-line dot-all
230 var match = source.match(/(#*)\s*([\s\S]*)/);
231 // strip the leading `#` if it's already there
232 if (match) {
233 source = match[2];
234 }
235 // add `#` markdown heading prefix
236 var new_text = new Array(level + 1).join('#') + ' ' + source;
237
238 this.set_text(new_text);
239 this.refresh();
240 if (this.rendered) {
241 this.render();
242 }
243 };
244
225 245 /**
226 246 * @method render
227 247 */
@@ -238,6 +258,19 b' define(['
238 258 html = mathjaxutils.replace_math(html, math);
239 259 html = security.sanitize_html(html);
240 260 html = $($.parseHTML(html));
261 // add anchors to headings
262 // console.log(html);
263 html.find(":header").addBack(":header").each(function (i, h) {
264 h = $(h);
265 var hash = h.text().replace(/ /g, '-');
266 h.attr('id', hash);
267 h.append(
268 $('<a/>')
269 .addClass('anchor-link')
270 .attr('href', '#' + hash)
271 .text('ΒΆ')
272 );
273 })
241 274 // links in markdown cells should open in new tabs
242 275 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
243 276 this.set_rendered(html);
@@ -305,121 +338,15 b' define(['
305 338 return cont;
306 339 };
307 340
308
309 var HeadingCell = function (options) {
310 // Constructor
311 //
312 // Parameters:
313 // options: dictionary
314 // Dictionary of keyword arguments.
315 // events: $(Events) instance
316 // config: dictionary
317 // keyboard_manager: KeyboardManager instance
318 // notebook: Notebook instance
319 options = options || {};
320 var config = utils.mergeopt(HeadingCell, options.config);
321 TextCell.apply(this, [$.extend({}, options, {config: config})]);
322
323 this.level = 1;
324 this.cell_type = 'heading';
325 };
326
327 HeadingCell.options_default = {
328 cm_config: {
329 theme: 'heading-1'
330 },
331 placeholder: "Type Heading Here"
332 };
333
334 HeadingCell.prototype = Object.create(TextCell.prototype);
335
336 /** @method fromJSON */
337 HeadingCell.prototype.fromJSON = function (data) {
338 if (data.level !== undefined){
339 this.level = data.level;
340 }
341 TextCell.prototype.fromJSON.apply(this, arguments);
342 this.code_mirror.setOption("theme", "heading-"+this.level);
343 };
344
345
346 /** @method toJSON */
347 HeadingCell.prototype.toJSON = function () {
348 var data = TextCell.prototype.toJSON.apply(this);
349 data.level = this.get_level();
350 return data;
351 };
352
353 /**
354 * Change heading level of cell, and re-render
355 * @method set_level
356 */
357 HeadingCell.prototype.set_level = function (level) {
358 this.level = level;
359 this.code_mirror.setOption("theme", "heading-"+level);
360
361 if (this.rendered) {
362 this.rendered = false;
363 this.render();
364 }
365 };
366
367 /** The depth of header cell, based on html (h1 to h6)
368 * @method get_level
369 * @return {integer} level - for 1 to 6
370 */
371 HeadingCell.prototype.get_level = function () {
372 return this.level;
373 };
374
375
376 HeadingCell.prototype.get_rendered = function () {
377 var r = this.element.find("div.text_cell_render");
378 return r.children().first().html();
379 };
380
381 HeadingCell.prototype.render = function () {
382 var cont = TextCell.prototype.render.apply(this);
383 if (cont) {
384 var text = this.get_text();
385 var math = null;
386 // Markdown headings must be a single line
387 text = text.replace(/\n/g, ' ');
388 if (text === "") { text = this.placeholder; }
389 text = new Array(this.level + 1).join("#") + " " + text;
390 var text_and_math = mathjaxutils.remove_math(text);
391 text = text_and_math[0];
392 math = text_and_math[1];
393 var html = marked.parser(marked.lexer(text));
394 html = mathjaxutils.replace_math(html, math);
395 html = security.sanitize_html(html);
396 var h = $($.parseHTML(html));
397 // add id and linkback anchor
398 var hash = h.text().trim().replace(/ /g, '-');
399 h.attr('id', hash);
400 h.append(
401 $('<a/>')
402 .addClass('anchor-link')
403 .attr('href', '#' + hash)
404 .text('ΒΆ')
405 );
406 this.set_rendered(h);
407 this.typeset();
408 }
409 return cont;
410 };
411
412 341 // Backwards compatability.
413 342 IPython.TextCell = TextCell;
414 343 IPython.MarkdownCell = MarkdownCell;
415 344 IPython.RawCell = RawCell;
416 IPython.HeadingCell = HeadingCell;
417 345
418 346 var textcell = {
419 'TextCell': TextCell,
420 'MarkdownCell': MarkdownCell,
421 'RawCell': RawCell,
422 'HeadingCell': HeadingCell,
347 TextCell: TextCell,
348 MarkdownCell: MarkdownCell,
349 RawCell: RawCell,
423 350 };
424 351 return textcell;
425 352 });
@@ -189,12 +189,6 b' class="notebook_app"'
189 189 <li id="to_raw"
190 190 title="Contents will pass through nbconvert unmodified">
191 191 <a href="#">Raw NBConvert</a></li>
192 <li id="to_heading1"><a href="#">Heading 1</a></li>
193 <li id="to_heading2"><a href="#">Heading 2</a></li>
194 <li id="to_heading3"><a href="#">Heading 3</a></li>
195 <li id="to_heading4"><a href="#">Heading 4</a></li>
196 <li id="to_heading5"><a href="#">Heading 5</a></li>
197 <li id="to_heading6"><a href="#">Heading 6</a></li>
198 192 </ul>
199 193 </li>
200 194 <li class="divider"></li>
@@ -52,12 +52,12 b' casper.notebook_test(function () {'
52 52
53 53 this.then(function () {
54 54 this.select_cell(2);
55 this.trigger_keydown('1'); // switch it to heading for the next test
56 this.test.assertEquals(this.get_cell(2).cell_type, 'heading', 'test cell is heading');
55 this.trigger_keydown('y'); // switch it to code for the next test
56 this.test.assertEquals(this.get_cell(2).cell_type, 'code', 'test cell is code');
57 57 this.trigger_keydown('b'); // new cell below
58 this.test.assertEquals(this.get_cell(3).cell_type, 'heading', 'b; inserts a heading cell below heading cell');
58 this.test.assertEquals(this.get_cell(3).cell_type, 'code', 'b; inserts a code cell below code cell');
59 59 this.trigger_keydown('a'); // new cell above
60 this.test.assertEquals(this.get_cell(3).cell_type, 'heading', 'a; inserts a heading cell below heading cell');
60 this.test.assertEquals(this.get_cell(3).cell_type, 'code', 'a; inserts a code cell below code cell');
61 61 });
62 62
63 63 this.thenEvaluate(function() {
@@ -4,25 +4,38 b''
4 4 casper.notebook_test(function () {
5 5 this.then(function () {
6 6 // Cell mode change
7 this.select_cell(0);
7 var index = 0;
8 this.select_cell(index);
9 var a = 'hello\nmulti\nline';
10 this.set_cell_text(index, a);
8 11 this.trigger_keydown('esc','r');
9 this.test.assertEquals(this.get_cell(0).cell_type, 'raw', 'r; cell is raw');
12 this.test.assertEquals(this.get_cell(index).cell_type, 'raw', 'r; cell is raw');
10 13 this.trigger_keydown('1');
11 this.test.assertEquals(this.get_cell(0).cell_type, 'heading', '1; cell is heading');
12 this.test.assertEquals(this.get_cell(0).level, 1, '1; cell is level 1 heading');
14 this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '1; cell is markdown');
15 this.test.assertEquals(this.get_cell_text(index), '# ' + a, '1; markdown heading');
13 16 this.trigger_keydown('2');
14 this.test.assertEquals(this.get_cell(0).level, 2, '2; cell is level 2 heading');
17 this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '2; cell is markdown');
18 this.test.assertEquals(this.get_cell_text(index), '## ' + a, '2; markdown heading');
15 19 this.trigger_keydown('3');
16 this.test.assertEquals(this.get_cell(0).level, 3, '3; cell is level 3 heading');
20 this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '3; cell is markdown');
21 this.test.assertEquals(this.get_cell_text(index), '### ' + a, '3; markdown heading');
17 22 this.trigger_keydown('4');
18 this.test.assertEquals(this.get_cell(0).level, 4, '4; cell is level 4 heading');
23 this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '4; cell is markdown');
24 this.test.assertEquals(this.get_cell_text(index), '#### ' + a, '4; markdown heading');
19 25 this.trigger_keydown('5');
20 this.test.assertEquals(this.get_cell(0).level, 5, '5; cell is level 5 heading');
26 this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '5; cell is markdown');
27 this.test.assertEquals(this.get_cell_text(index), '##### ' + a, '5; markdown heading');
21 28 this.trigger_keydown('6');
22 this.test.assertEquals(this.get_cell(0).level, 6, '6; cell is level 6 heading');
29 this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '6; cell is markdown');
30 this.test.assertEquals(this.get_cell_text(index), '###### ' + a, '6; markdown heading');
23 31 this.trigger_keydown('m');
24 this.test.assertEquals(this.get_cell(0).cell_type, 'markdown', 'm; cell is markdown');
32 this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', 'm; cell is markdown');
33 this.test.assertEquals(this.get_cell_text(index), '###### ' + a, 'm; still markdown heading');
25 34 this.trigger_keydown('y');
26 this.test.assertEquals(this.get_cell(0).cell_type, 'code', 'y; cell is code');
35 this.test.assertEquals(this.get_cell(index).cell_type, 'code', 'y; cell is code');
36 this.test.assertEquals(this.get_cell_text(index), '###### ' + a, 'y; still has hashes');
37 this.trigger_keydown('1');
38 this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '1; cell is markdown');
39 this.test.assertEquals(this.get_cell_text(index), '# ' + a, '1; markdown heading');
27 40 });
28 41 }); No newline at end of file
@@ -10,38 +10,53 b' casper.notebook_test(function () {'
10 10 cell.render();
11 11 return cell.get_rendered();
12 12 });
13 this.test.assertEquals(output.trim(), '<h1 id=\"foo\">Foo</h1>', 'Markdown JS API works.');
13 this.test.assertEquals(output.trim(), '<h1 id=\"Foo\">Foo<a class=\"anchor-link\" href=\"#Foo\">ΒΆ</a></h1>', 'Markdown JS API works.');
14 14
15 15 // Test menubar entries.
16 16 output = this.evaluate(function () {
17 17 $('#to_code').mouseenter().click();
18 18 $('#to_markdown').mouseenter().click();
19 19 var cell = IPython.notebook.get_selected_cell();
20 cell.set_text('# Bar');
20 cell.set_text('**Bar**');
21 21 $('#run_cell').mouseenter().click();
22 22 return cell.get_rendered();
23 23 });
24 this.test.assertEquals(output.trim(), '<h1 id=\"bar\">Bar</h1>', 'Markdown menubar items work.');
24 this.test.assertEquals(output.trim(), '<p><strong>Bar</strong></p>', 'Markdown menubar items work.');
25 25
26 26 // Test toolbar buttons.
27 27 output = this.evaluate(function () {
28 28 $('#cell_type').val('code').change();
29 29 $('#cell_type').val('markdown').change();
30 30 var cell = IPython.notebook.get_selected_cell();
31 cell.set_text('# Baz');
31 cell.set_text('*Baz*');
32 32 $('#run_b').click();
33 33 return cell.get_rendered();
34 34 });
35 this.test.assertEquals(output.trim(), '<h1 id=\"baz\">Baz</h1>', 'Markdown toolbar items work.');
35 this.test.assertEquals(output.trim(), '<p><em>Baz</em></p>', 'Markdown toolbar items work.');
36 36
37 // Test JavaScript models.
38 var output = this.evaluate(function () {
37 // Test markdown headings
39 38
39 var text = 'multi\nline';
40
41 this.evaluate(function (text) {
40 42 var cell = IPython.notebook.insert_cell_at_index('markdown', 0);
41 cell.set_text('# Qux');
42 cell.render();
43 return cell.get_rendered();
44 });
45 this.test.assertEquals(output.trim(), '<h1 id=\"qux\">Qux</h1>', 'Markdown JS API works.');
43 cell.set_text(text);
44 }, {text: text});
45
46 var set_level = function (level) {
47 return casper.evaluate(function (level) {
48 var cell = IPython.notebook.get_cell(0);
49 cell.set_heading_level(level);
50 return cell.get_text();
51 }, {level: level});
52 };
46 53
54 var level_text;
55 var levels = [ 1, 2, 3, 4, 5, 6, 2, 1 ];
56 for (var idx=0; idx < levels.length; idx++) {
57 var level = levels[idx];
58 level_text = set_level(level);
59 hashes = new Array(level + 1).join('#');
60 this.test.assertEquals(level_text, hashes + ' ' + text, 'markdown set_heading_level ' + level);
61 }
47 62 });
@@ -1,11 +1,10 b''
1 1 {
2 2 "cells": [
3 3 {
4 "cell_type": "heading",
5 "level": 1,
4 "cell_type": "markdown",
6 5 "metadata": {},
7 6 "source": [
8 "NumPy and Matplotlib examples"
7 "# NumPy and Matplotlib examples"
9 8 ]
10 9 },
11 10 {
@@ -8,6 +8,7 b' import json'
8 8 from .base import ExportersTestsBase
9 9 from ..notebook import NotebookExporter
10 10
11 from IPython.nbformat.current import validate
11 12 from IPython.testing.tools import assert_big_text_equal
12 13
13 14 class TestNotebookExporter(ExportersTestsBase):
@@ -29,7 +30,7 b' class TestNotebookExporter(ExportersTestsBase):'
29 30 exporter = self.exporter_class(nbformat_version=3)
30 31 (output, resources) = exporter.from_filename(self._get_notebook())
31 32 nb = json.loads(output)
32 self.assertEqual(nb['nbformat'], 3)
33 validate(nb)
33 34
34 35 def test_downgrade_2(self):
35 36 exporter = self.exporter_class(nbformat_version=2)
@@ -21,6 +21,7 b' from pygments.formatters import HtmlFormatter'
21 21 from pygments.util import ClassNotFound
22 22
23 23 # IPython imports
24 from IPython.nbconvert.filters.strings import add_anchor
24 25 from IPython.nbconvert.utils.pandoc import pandoc
25 26 from IPython.nbconvert.utils.exceptions import ConversionException
26 27 from IPython.utils.decorators import undoc
@@ -146,6 +147,10 b' class IPythonRenderer(mistune.Renderer):'
146 147 formatter = HtmlFormatter()
147 148 return highlight(code, lexer, formatter)
148 149
150 def header(self, text, level, raw=None):
151 html = super(IPythonRenderer, self).header(text, level, raw=raw)
152 return add_anchor(html)
153
149 154 # Pass math through unaltered - mathjax does the rendering in the browser
150 155 def block_math(self, text):
151 156 return '$$%s$$' % text
@@ -4,17 +4,9 b''
4 4 Contains a collection of useful string manipulation filters for use in Jinja
5 5 templates.
6 6 """
7 #-----------------------------------------------------------------------------
8 # Copyright (c) 2013, the IPython Development Team.
9 #
10 # Distributed under the terms of the Modified BSD License.
11 #
12 # The full license is in the file COPYING.txt, distributed with this software.
13 #-----------------------------------------------------------------------------
14 7
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
8 # Copyright (c) IPython Development Team.
9 # Distributed under the terms of the Modified BSD License.
18 10
19 11 import os
20 12 import re
@@ -28,9 +20,6 b' from xml.etree import ElementTree'
28 20 from IPython.core.interactiveshell import InteractiveShell
29 21 from IPython.utils import py3compat
30 22
31 #-----------------------------------------------------------------------------
32 # Functions
33 #-----------------------------------------------------------------------------
34 23
35 24 __all__ = [
36 25 'wrap_text',
@@ -88,9 +77,9 b' def html2text(element):'
88 77
89 78
90 79 def add_anchor(html):
91 """Add an anchor-link to an html header tag
80 """Add an anchor-link to an html header
92 81
93 For use in heading cells
82 For use on markdown headings
94 83 """
95 84 try:
96 85 h = ElementTree.fromstring(py3compat.cast_bytes_py2(html, encoding='utf-8'))
@@ -1,3 +1,4 b''
1 # coding: utf-8
1 2 """Tests for conversions from markdown to other formats"""
2 3
3 4 # Copyright (c) IPython Development Team.
@@ -26,7 +27,8 b' class TestMarkdown(TestsBase):'
26 27 '#test',
27 28 '##test',
28 29 'test\n----',
29 'test [link](https://google.com/)']
30 'test [link](https://google.com/)',
31 ]
30 32
31 33 tokens = [
32 34 '*test',
@@ -39,7 +41,8 b' class TestMarkdown(TestsBase):'
39 41 'test',
40 42 'test',
41 43 'test',
42 ('test', 'https://google.com/')]
44 ('test', 'https://google.com/'),
45 ]
43 46
44 47
45 48 @dec.onlyif_cmds_exist('pandoc')
@@ -87,6 +90,17 b' class TestMarkdown(TestsBase):'
87 90 for index, test in enumerate(self.tests):
88 91 self._try_markdown(markdown2html, test, self.tokens[index])
89 92
93 def test_markdown2html_heading_anchors(self):
94 for md, tokens in [
95 ('# test',
96 ('<h1', '>test', 'id="test"', u'&#182;</a>', "anchor-link")
97 ),
98 ('###test head space',
99 ('<h3', '>test head space', 'id="test-head-space"', u'&#182;</a>', "anchor-link")
100 )
101 ]:
102 self._try_markdown(markdown2html, md, tokens)
103
90 104 def test_markdown2html_math(self):
91 105 # Mathematical expressions should be passed through unaltered
92 106 cases = [("\\begin{equation*}\n"
@@ -79,17 +79,6 b' In&nbsp;[&nbsp;]:'
79 79 </div>
80 80 {%- endblock markdowncell %}
81 81
82 {% block headingcell scoped %}
83 <div class="cell border-box-sizing text_cell rendered">
84 {{ self.empty_in_prompt() }}
85 <div class="inner_cell">
86 <div class="text_cell_render border-box-sizing rendered_html">
87 {{ ("#" * cell.level + cell.source) | replace('\n', ' ') | markdown2html | strip_files_prefix | add_anchor }}
88 </div>
89 </div>
90 </div>
91 {% endblock headingcell %}
92
93 82 {% block unknowncell scoped %}
94 83 unknown type {{ cell.type }}
95 84 {% endblock unknowncell %}
@@ -185,27 +185,6 b' This template does not define a docclass, the inheriting class must define this.'
185 185 ((*- endblock figure -*))
186 186 ((*- endmacro *))
187 187
188 % Draw heading cell. Explicitly map different cell levels.
189 ((* block headingcell scoped *))
190
191 ((* if cell.level == 1 -*))
192 ((* block h1 -*))\section((* endblock h1 -*))
193 ((* elif cell.level == 2 -*))
194 ((* block h2 -*))\subsection((* endblock h2 -*))
195 ((* elif cell.level == 3 -*))
196 ((* block h3 -*))\subsubsection((* endblock h3 -*))
197 ((* elif cell.level == 4 -*))
198 ((* block h4 -*))\paragraph((* endblock h4 -*))
199 ((* elif cell.level == 5 -*))
200 ((* block h5 -*))\subparagraph((* endblock h5 -*))
201 ((* elif cell.level == 6 -*))
202 ((* block h6 -*))\\*\textit((* endblock h6 -*))
203 ((*- endif -*))
204 {((( cell.source | replace('\n', ' ') | citation2latex | strip_files_prefix | prevent_list_blocks | markdown2latex(markup='markdown_strict+tex_math_dollars') )))}
205
206
207 ((* endblock headingcell *))
208
209 188 % Redirect execute_result to display data priority.
210 189 ((* block execute_result scoped *))
211 190 ((* block data_priority scoped *))
@@ -75,9 +75,6 b' consider calling super even if it is a leave block, we might insert more blocks '
75 75 ((*- elif cell.cell_type in ['markdown'] -*))
76 76 ((*- block markdowncell scoped-*))
77 77 ((*- endblock markdowncell -*))
78 ((*- elif cell.cell_type in ['heading'] -*))
79 ((*- block headingcell scoped-*))
80 ((*- endblock headingcell -*))
81 78 ((*- elif cell.cell_type in ['raw'] -*))
82 79 ((*- block rawcell scoped -*))
83 80 ((* if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) *))
@@ -58,11 +58,6 b''
58 58 {{ cell.source }}
59 59 {% endblock markdowncell %}
60 60
61
62 {% block headingcell scoped %}
63 {{ '#' * cell.level }} {{ cell.source | replace('\n', ' ') }}
64 {% endblock headingcell %}
65
66 61 {% block unknowncell scoped %}
67 62 unknown type {{ cell.type }}
68 63 {% endblock unknowncell %} No newline at end of file
@@ -15,7 +15,3 b''
15 15 {% block markdowncell scoped %}
16 16 {{ cell.source | comment_lines }}
17 17 {% endblock markdowncell %}
18
19 {% block headingcell scoped %}
20 {{ '#' * cell.level }}{{ cell.source | replace('\n', ' ') | comment_lines }}
21 {% endblock headingcell %}
@@ -71,9 +71,6 b' consider calling super even if it is a leave block, we might insert more blocks '
71 71 {%- elif cell.cell_type in ['markdown'] -%}
72 72 {%- block markdowncell scoped-%}
73 73 {%- endblock markdowncell -%}
74 {%- elif cell.cell_type in ['heading'] -%}
75 {%- block headingcell scoped-%}
76 {%- endblock headingcell -%}
77 74 {%- elif cell.cell_type in ['raw'] -%}
78 75 {%- block rawcell scoped -%}
79 76 {% if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) %}
@@ -1,11 +1,10 b''
1 1 {
2 2 "cells": [
3 3 {
4 "cell_type": "heading",
5 "level": 1,
4 "cell_type": "markdown",
6 5 "metadata": {},
7 6 "source": [
8 "A simple SymPy example"
7 "# A simple SymPy example"
9 8 ]
10 9 },
11 10 {
@@ -1,11 +1,10 b''
1 1 {
2 2 "cells": [
3 3 {
4 "cell_type": "heading",
5 "level": 1,
4 "cell_type": "markdown",
6 5 "metadata": {},
7 6 "source": [
8 "NumPy and Matplotlib examples"
7 "# NumPy and Matplotlib examples"
9 8 ]
10 9 },
11 10 {
@@ -157,11 +156,10 b''
157 156 ]
158 157 },
159 158 {
160 "cell_type": "heading",
161 "level": 2,
159 "cell_type": "markdown",
162 160 "metadata": {},
163 161 "source": [
164 "Here is a very long heading that pandoc will wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap"
162 "## Here is a very long heading that pandoc will wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap"
165 163 ]
166 164 },
167 165 {
@@ -1,145 +1,306 b''
1 1 {
2 "metadata": {
3 "name": 0
4 },
5 "nbformat": 3,
6 "nbformat_minor": 0,
7 "worksheets": [
2 "cells": [
8 3 {
9 "cells": [
10 {
11 "cell_type": "heading",
12 "level": 1,
13 "source": [
14 "nbconvert latex test"
15 ]
16 },
17 {
18 "cell_type": "markdown",
19 "metadata": {},
20 "source": [
21 "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus bibendum felis dictum sodales. Ut suscipit, orci ut interdum imperdiet, purus ligula mollis *justo*, non malesuada nisl augue eget lorem. Donec bibendum, erat sit amet porttitor aliquam, urna lorem ornare libero, in vehicula diam diam ut ante. Nam non urna rhoncus, accumsan elit sit amet, mollis tellus. Vestibulum nec tellus metus. Vestibulum tempor, ligula et vehicula rhoncus, sapien turpis faucibus lorem, id dapibus turpis mauris ac orci. Sed volutpat vestibulum venenatis."
22 ]
23 },
24 {
25 "cell_type": "heading",
26 "level": 2,
27 "metadata": {},
28 "source": [
29 "Printed Using Python"
30 ]
31 },
32 {
33 "cell_type": "code",
34 "collapsed": false,
35 "input": [
36 "print(\"hello\")"
37 ],
38 "language": "python",
39 "outputs": [
40 {
41 "output_type": "stream",
42 "stream": "stdout",
43 "text": [
44 "hello\n"
45 ]
46 }
47 ],
48 "prompt_number": 1
49 },
4 "cell_type": "markdown",
5 "metadata": {}
6 },
7 {
8 "cell_type": "markdown",
9 "metadata": {},
10 "source": [
11 "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus bibendum felis dictum sodales. Ut suscipit, orci ut interdum imperdiet, purus ligula mollis *justo*, non malesuada nisl augue eget lorem. Donec bibendum, erat sit amet porttitor aliquam, urna lorem ornare libero, in vehicula diam diam ut ante. Nam non urna rhoncus, accumsan elit sit amet, mollis tellus. Vestibulum nec tellus metus. Vestibulum tempor, ligula et vehicula rhoncus, sapien turpis faucibus lorem, id dapibus turpis mauris ac orci. Sed volutpat vestibulum venenatis."
12 ]
13 },
14 {
15 "cell_type": "heading",
16 "level": 2,
17 "metadata": {},
18 "source": [
19 "Printed Using Python"
20 ]
21 },
22 {
23 "cell_type": "code",
24 "execution_count": 1,
25 "metadata": {
26 "collapsed": false
27 },
28 "outputs": [
50 29 {
51 "cell_type": "heading",
52 "level": 1000,
53 "metadata": {},
54 "source": [
55 "Pyout"
30 "name": "stdout",
31 "output_type": "bad stream",
32 "text": [
33 "hello\n"
56 34 ]
57 },
35 }
36 ],
37 "source": [
38 "print(\"hello\")"
39 ]
40 },
41 {
42 "cell_type": "markdown",
43 "metadata": {},
44 "source": [
45 "## Pyout"
46 ]
47 },
48 {
49 "cell_type": "code",
50 "execution_count": 3,
51 "metadata": {
52 "collapsed": false
53 },
54 "outputs": [
58 55 {
59 "cell_type": "code",
60 "collapsed": false,
61 "input": [
62 "from IPython.display import HTML\n",
63 "HTML(\"\"\"\n",
64 "<script>\n",
65 "console.log(\"hello\");\n",
66 "</script>\n",
67 "<b>HTML</b>\n",
68 "\"\"\")"
69 ],
70 "language": "python",
56 "data": {
57 "text/html": [
58 "\n",
59 "<script>\n",
60 "console.log(\"hello\");\n",
61 "</script>\n",
62 "<b>HTML</b>\n"
63 ],
64 "text/plain": [
65 "<IPython.core.display.HTML at 0x1112757d0>"
66 ]
67 },
68 "execution_count": 3,
71 69 "metadata": {},
72 "outputs": [
73 {
74 "html": [
75 "\n",
76 "<script>\n",
77 "console.log(\"hello\");\n",
78 "</script>\n",
79 "<b>HTML</b>\n"
80 ],
81 "metadata": {},
82 "output_type": "pyout",
83 "prompt_number": 3,
84 "text": [
85 "<IPython.core.display.HTML at 0x1112757d0>"
86 ]
87 }
88 ],
89 "prompt_number": 3
90 },
70 "output_type": "execute_result"
71 }
72 ],
73 "source": [
74 "from IPython.display import HTML\n",
75 "HTML(\"\"\"\n",
76 "<script>\n",
77 "console.log(\"hello\");\n",
78 "</script>\n",
79 "<b>HTML</b>\n",
80 "\"\"\")"
81 ]
82 },
83 {
84 "cell_type": "code",
85 "execution_count": 7,
86 "metadata": {
87 "collapsed": false
88 },
89 "outputs": [
91 90 {
92 "cell_type": "code",
93 "collapsed": false,
94 "input": [
95 "%%javascript\n",
96 "console.log(\"hi\");"
97 ],
98 "language": "python",
91 "data": {
92 "application/javascript": [
93 "console.log(\"hi\");"
94 ],
95 "text/plain": [
96 "<IPython.core.display.Javascript at 0x1112b4b50>"
97 ]
98 },
99 99 "metadata": {},
100 "outputs": [
101 {
102 "javascript": [
103 "console.log(\"hi\");"
104 ],
105 "metadata": {},
106 "output_type": "display_data",
107 "text": [
108 "<IPython.core.display.Javascript at 0x1112b4b50>"
109 ]
110 }
111 ],
112 "prompt_number": 7
113 },
114 {
115 "cell_type": "heading",
116 "level": 3,
117 "metadata": {}
118 },
100 "output_type": "display_data"
101 }
102 ],
103 "source": [
104 "%%javascript\n",
105 "console.log(\"hi\");"
106 ]
107 },
108 {
109 "cell_type": "markdown",
110 "metadata": {},
111 "source": [
112 "### Image"
113 ]
114 },
115 {
116 "cell_type": "code",
117 "execution_count": 6,
118 "metadata": {
119 "collapsed": false
120 },
121 "outputs": [
119 122 {
120 "cell_type": "code",
121 "collapsed": false,
122 "input": [
123 "from IPython.display import Image\n",
124 "Image(\"http://ipython.org/_static/IPy_header.png\")"
125 ],
126 "language": "python",
123 "data": {
124 "image/png": [
125 "iVBORw0KGgoAAAANSUhEUgAAAggAAABDCAYAAAD5/P3lAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n",
126 "AAAH3AAAB9wBYvxo6AAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURB\n",
127 "VHic7Z15uBxF1bjfugkJhCWBsCSAJGACNg4QCI3RT1lEAVE+UEBNOmwCDcjHT1wQgU+WD3dFxA1o\n",
128 "CAikAZFFVlnCjizpsCUjHQjBIAkQlpCFJGS79fvjdGf69vTsc2fuza33eeaZmeqq6jM9vZw6dc4p\n",
129 "BUwC+tE+fqW1fqmRDpRSHjCggS40sBxYDCxKvL8KzNBaL21EPoPB0DPIWVY/4NlE0ffzYfhgu+Qx\n",
130 "GHoy/YFjaK+CcB3QkIIAHAWs3wRZsuhUSs0CXgQeBm7UWi/spn0Z+jA5yxpEfYruqnwYllRic5a1\n",
131 "MaWv8U5gaT4M19Sx396IAnZLfB/SLkEMhp5O/3YL0AvoAHaKXl8HLlZK3QZcpbWe0lbJDOsaHuDU\n",
132 "0e4u4JAy2wPk/C1JzrKWArOQ0fUtwH35MOysQxaDwbCO0NFuAXoh6wPjgQeUUvcqpUa0WyCDoQls\n",
133 "CIwBjgfuAV7KWdY+7RWpmJxlXZezrEdylvXxdstiMKzrGAtCYxwI/EspdZbW+g/tFsbQ67kQuBHY\n",
134 "FNgseh9FV6vCbUAeWBC9PgBeq2EfS6J2MQOBrRDTe5KdgAdzlvW1fBjeUUP/3UbOsoYBE6OvG7VT\n",
135 "FoOhL9Af+BUwFLkZpV+DaY6V4UPkRpb1+ncT+m8nGwK/V0oN01qf025hDL2XfBi+DLycLMtZVo6u\n",
136 "CsKfGnSq8/NheEpqHwOBEcDBwJnAsGhTP2ByzrJG5cPwnQb22Sy+0G4BDIa+RH+t9dmlNiqlFKIk\n",
137 "JJWGi+jq5JPmq8BbJJQArfXqpkncczlbKbVQa/3rdgtiMNRCPgxXAK8Ar+Qs63LgXmDvaPPGwPeA\n",
138 "H7VJvCRfbLcABkNfouwUg9ZaAwuj178BlFLvVejzgR4WFviM1npcuQpKqf6IyXIjxLS7GzAWuUnu\n",
139 "XsO+fqWUellr3ZBJdq/jr9+BDn1uve07O9Rz0y6f8PtGZGgWe53oT6SBkZ/q1/nHZy47aloTRTKU\n",
140 "IR+Gy3OWNR6Zxtg0Kv4KRkEwGPocxgcBiCwcsSI0F5iOhF+ilPok8C3gVGS+thK/VErdrbWuO2ys\n",
141 "s/+aLZTuOKbe9krrIUCPUBB0B+PQ1P1bdKe6EzAKQgvJh+GbOct6gkJkxM45y+qXDIWMHBhjBWJe\n",
142 "PgyDWvaRs6zPIVObAG/nw/DpEvUGAp8E9gGGJzbtl7Os7cvs4skqp0V0Yl8jgcOBjyMDhbmIZeWl\n",
143 "fBg+UUVfReQsayhwELAnsAXi6/E28BxwTz4MP6iyn92RaSCA+/NhuCwqXx9R4MYhU0MfRTK/AjyW\n",
144 "D8MFGd0ZDFVhFIQKaK3/BXxfKXUlklTq0xWafAI4Driyu2UzGLqRlygoCArYHJif2H4gcFb0+Z2c\n",
145 "ZW2bD8NV1XScs6yNgH8g/jsAPwCeTmzfFPgjYsnbiez71MUVdnMQcF8V4nyUs6whwB8QX4+0s2Ys\n",
146 "0yPAt/NhGFbRZ/wbzgO+DaxXotqqnGX9GbigCkXhf5CBCsDngYdzljURGQhsWqLN+znL+iFwdT4M\n",
147 "dYk6BkNJTJhjlWitQ2Bf4P4qqv848t8wGHor6Yd9+ruHJFkC2BI4rIa+D6egHKwmstYlGAxMQCwH\n",
148 "rRjEPI5ER5S7ZvcFXsxZ1phKneUsawSi8HyH0soB0bbvAM9Ebaplt5xlnYkct1LKAYiFZhJwSQ19\n",
149 "GwxrMRaEGtBar1RKfRX4JxIzXortou3PN1mE+YgJsSwaeoLHOQCqUy3QSr9eqZ6G/gq2aYVMhqrY\n",
150 "OfF5FeJwvJZ8GM7JWdY/gC9HRS7wtyr7Pjrx+e6MqYC3KLbU7Qhck/h+FJIKvRRVjfSREXicU8EH\n",
151 "pgAvIIqLBZwGfC7avl5Uf29KkLOsTZCMq8npj9sQx89no37HIlaAODplNPBIzrJ2z4dhNVlaT0HC\n",
152 "XwFmIkrAC4if2PaIz8/3KCgn385Z1pX5MJxeRd8Gw1qMglAjWutlSqnTgUcqVP0SzVYQtP5mcMXE\n",
153 "SvvtUUy9YsK5QEWHy7EnTB6lOtSsFohkqEDOsgYAdqJoagkT9Z8pKAj75yzr4/kwnF2h748ho/GY\n",
154 "q9J1oqiKLj4JOctKK8Yz8mH4Yrl9VcnHkXVYTsyHoZ8WJWdZNyPThbF5/3M5yzowH4alpi9+T0E5\n",
155 "WA18Nx+Gf0zVeRG4KmdZ90R9bwCMRKwyX69C5h2j91uA4/JhuCSxbTYwJWdZtwNPIFbifsAFSISZ\n",
156 "wVA1ZoqhDrTWjyIjjXIc3ApZDIZu4ELgY4nvt5Wody8wJ/qsgBOr6HsihfvOfCRrY7v5dYZyAECk\n",
157 "GP0ISEZmZYZ55yxrB8SyEXNxhnKQ7Pt64H8TRUfmLGuXKmWeC4xPKQfJvp9CLCJlZTYYymEUhPq5\n",
158 "tcL2XVsihcHQJHKWtU3Osi5GnAZj5iKWgiKitRouTxQdl7OscnPu0HV64dp8GLY7R8pyxEGxJPkw\n",
159 "fBcZ9ceUSvN8IoV76upK/UZcgawcG3NKqYopfleFU+gDic/b5SzLWIwNNWFOmPqp5CG9sVJqPa11\n",
160 "VZ7dBkOL2D1nWcmcBkOR8MFtgM/QdTXJZcCR+TBcXqa/SYj5egAFZ8VMX4ScZe2FRPnEXF2z9M3n\n",
161 "3nwYVsrtAmK6/0z0uVR4ZXLtivvzYfhGpU7zYbgkZ1k3ACdHRQdWIQsUO3ZmkUzB3Q/xjaolLbeh\n",
162 "j2MUhDrRWr+mlFpJ+eV5hyIxz4YWs98Fj/Rf8uZbozo0/ZYt7D8rf9ORK9stUw/hU9GrEnMAp1R+\n",
163 "gph8GL4bzdNPiIpOorSzYtJ68FS1IYPdTLWp3hcnPm+Q3pizrA7E+TCmFn+aZN0dcpY1LB+G5e4b\n",
164 "y6rM8bA49X39GmQyGMwUQ4NUGnkMrbDd0A3sdeLk4z6cN+89pTtDTWd+gyErF+7pTv5eu+XqJbyK\n",
165 "TDHsmg/DJ6tsc2ni8+dzljUqXSGaevhmoqjIObFNVBzlV8kQug4W5tbQNl13WGatAv+poW+DoW6M\n",
166 "BaExPgC2LrO9nHWhpSilDqI4NPMhrfXUJvS9M/DfqeJXtdY3N9p3rex50uQ9lFKT6BrTvoFCXbTX\n",
167 "yZNfmnrZxHtbLVMP4xng74nvK5DzeD7wfIWRayb5MHwiZ1kzgF0oOCuemar2ZQoK8zLgr7Xup5t4\n",
168 "s0n9DEl9b0RBSPeV5q0a+jYY6sYoCI1RacnZ91siRXUMAH6eKnsYicdulDOAY1NlpzWh35pRqG9R\n",
169 "IuGN7uw4AfG878s8nw/DX3RDv5dScGY8NmdZP86HYXJaJzm9cHMp7/s2UHdK9BTpKaxBNbRN163k\n",
170 "t9Rux05DH8FMMTTGZhW2v9sSKarjbopNk/sqpUY30qlSahCSGS/JCuD6RvqtF6UpMm/HaHTJbYaG\n",
171 "mQzED/0umRVzlrUZhXwJ0HOmF5pJOlXyxzJrZbNt6rtZP8HQIzAKQp0opTZAlsItxTKtdTnv75YS\n",
172 "LR7lpYqrjV0vx2EUH4fbtdZtucnpMqOrDjPy6jYii8DkRFHSYnAEhem22cBjrZKrVeTDcCldTf/p\n",
173 "h345ksrEGprnF2EwNIRREOrnMxW2z2uJFLVxJcXmy2OVUo34ShydUda+EaIq7T2u0SZTY/eSdFY8\n",
174 "MGdZm0efk86J6/LCQUnFp5pIkZjkcvQz8mH4YZPkMRgawigI9VNp7v7BlkhRA1rr+RQneNqC2hba\n",
175 "WYtSajiS9z3JXLomaGktq/VllLIUdKqSWe0MjZMPwxlIel8Q/6Zv5CxrGIX8AJ10XU+hFtIRQ+UW\n",
176 "KWoXyYyTu+Qsa79KDXKWNRpJyx5zZ9OlMhjqxCgIdaCU6g98o0K1npBCNotLM8rcOvuagCRgSXKN\n",
177 "1rozq3IrCCZNfFkrfRjotWsCaJinUBODK51/tkuuPkTy/DoYOIDCfeb+fBjW4t2/lqhdcmRdbUri\n",
178 "VnILXS2HZ1WRvfAcCk61K4A/dYdgBkM9GAWhPr5F6XSrIBf6Qy2SpSaidSReShV/XilV7veUIj29\n",
179 "oOkB2fGmXT7x7sCbOGpFf7VZx4A1m0/znG2nehMyc+0bms7NFJxzxwH7J7Y1OvWUPG9/mLOsLRvs\n",
180 "r6lEaaOT0TtfBB5ITLWsJWdZg3KWdRNwTKL4wnwYzu9mMQ2GqjFhjjWilBqBpJYtx51a66UV6rST\n",
181 "S+maJz52VvxRdvVilFK7UbzexGNa67Kr+bWS6X+ekPYs79HkLGt34JOI+Xyz6D2d1vfMnGUdini6\n",
182 "L0C851/Oh2HD+SyaQT4MV+YsaxJyLm1Gwf9gAXBHg93/JNHHtsArOcuajCztPBDYCkkytBXg5sOw\n",
183 "5QmF8mF4W86yLgK+HxXtC8zKWVaALMm8CslHsicS7RFzL8VhyAZDWzEKQg0opbYE7qd8prPVdF2h\n",
184 "rSdyLfALYMNE2XFKqR/XsHbEURll62L4Wiv5PuBUqPPF6JXkLuCQbpGoPi4HfohYKGMHWD9axrlu\n",
185 "8mF4Z7RuwfioaDBwaonqRemQW0U+DH+Qs6xFwHnIFNwQsv+3mMnA8dHiVwZDj8FMMVSJUuow4DkK\n",
186 "a7GX4gqt9cstEKlutNaL6boULMho5tBq2iul+lH8IFuCmJcNfZx8GM6hOCFVU5THfBhOQHxfylkH\n",
187 "3gY+asb+6iUfhhcCewC3l5BlFbJk/P75MDwqlVTKYOgRKK1rizhSSk2h67ximo1abV5XSi2n9EIk\n",
188 "z2itx5XYVqnfQcjI7DiqW2XtfeCTUbRA3ex50nWfUrqjeJEcrfcLrpj4SCN9xyilxgDPp4of0Fof\n",
189 "UEXbg4B/pIqv1FrXnVNh7AmTR3V0qIwwRH1E4E28pd5+De0hZ1m/Bb4bfX0+H4Z7dMM+hgGjkDwC\n",
190 "S5FpjFk9bR4/Z1mDkGmF4VHR20g4Y3oxJYOhR9EXphg6lFLlVjFbH0mZvDGwCTAayCFe0ntTOZ1y\n",
191 "zDLgkEaVg1ahtX5BKfUU8OlE8ReUUjtorSstCduzch8YehSR5/6ERFG3nBvRuhE9frXUfBguA6pd\n",
192 "+Mpg6DH0BQXBBro7o+Ea4Bta66e6eT/N5lK6KggKOAE4u1QDpdTGFOdNmNkLf7uh+zgYcRQEMa+3\n",
193 "Je22wWBoDOOD0DhLgYla67vaLUgd3ETxglLHRXkeSnEExQ5gbQ9tNPQokis5TsqHoVlbwGDohRgF\n",
194 "oTECYHet9Y3tFqQetNYrKDb/DqN46eYk6emF1UhUhMFAzrImUEhDvgr4VRvFMRgMDWAUhPpYAvwf\n",
195 "8Bmte31+/8uQBEdJMjMrKqW2o5A2N+YfWusePw9s6F5yltWRs6zxwKRE8RXtyEVgMBiaQ1/wQWgm\n",
196 "eWTe/jqtdU9Zz74htNavKaXuAw5KFB+glBqptZ6Tqj6RQlrYGDO90AfJWdY5wNeQFQwHIAmetk5U\n",
197 "eZFCsiCDwdALMQpCed5AphEC4NF12BHvUroqCAoJ7TwvVS+d++BdJEmPoe+xKRLnn0UeODwfhm3N\n",
198 "RWAwGBqjLygIbwN/LbNdI1MGH6ReL/eWkMUmcDeSeGa7RNlRSqnzdZQoQym1C7Bzqt11NWReNKxb\n",
199 "zEMU6GHAesBiYCaSLOviaF0Cg8HQi+kLCsLrWuvT2y1ET0ZrvUYp5SG57mO2Bz4LPB59/2ZRQ5P7\n",
200 "oM+SD8OLgYvbLYfBYOg+jJOiIeZKxOs8STJiIb28daC1/lf3imQwGAyGdmEUBAMA0XTKraniI5VS\n",
201 "A6O0zOnloI31wGAwGNZhjIJgSHJp6vtgJBNlehW65cANLZHIYDAYDG3BKAiGtWitHwVeShV/muLF\n",
202 "uW7VWi9qjVQGg8FgaAd9wUnRUBuXAn9IfN8f+FyqTo/OfbDnSX8brDpXnqEUe2ropzQvdtDx66ev\n",
203 "GN9XolIMPQDb9T8LrBd4zsPtlsXQe7Bd/0BgQeA5QbtlMQqCIc21wC+ADaPv6WWu5wAPtVKgWtjt\n",
204 "6Os2XG/9jhdQjIzTQ2rFF9bQecy4E2/I9UQlwXb9LYDDK1R7K/Cc21shj6FxbNcfDjwGKNv1Rwae\n",
205 "83q7ZWo2tusPBb6ELGW9BbAICX99Gngs8Jx0hlZDBWzXHwvcC6ywXX9o4DlL2ymPURAMXdBaL1ZK\n",
206 "+ZRItwz8Jc6N0BMZMFB9GxiZsWnzTjrPAH7QWomqYgTF/h9pngC6RUGwXf+XwC2B50ztjv57M7br\n",
207 "XwJMCjxneo1NP0SWgAfJq7LOYLv+esAFwOkUL9wWM912/d0Dz+lsnWQ9A9v1BwEXAT8PPKfWVOML\n",
208 "kPVt3kNWQm0rxgfBkEWph5UG/tJCOWqnQ40ttUkrvWcrRamWwHOmAZsguSfGAi9Hmy5AUhgPAz7f\n",
209 "Hfu2XX8k8ENgx+7ovzdju/4uwP9D/peaCDxnCbANsF3gOYubLVu7sF1/AHAHcBaiHDwI/C+ywNsE\n",
210 "4KfA68BdfVE5iNgbOBmxqtRE4Dn/BoYDnwg8Z02zBasVY0EwFKG1fkEp9RTioJjkIa11zzaVarYq\n",
211 "vVFt2TpBaiN6oCwB5tiu/2FUPCvwnLTTaLM5oJv77800dGwCz1kXHXkvRNKydwI/Cjzn1+kKtuuf\n",
212 "i2TX7Ks0et681yxBGsUoCIZSBBQrCL0h98EbdW7rddiuPwoYFJu/bdffFNgL2BZ4DZgWKR5ZbRWS\n",
213 "2+KIqGiE7fpjUtXmlrtZRdaHscBAYDowM/CckimWbdffFfgw8JzXou/9kfUccojV5MXAcz4s0XYw\n",
214 "sCsymu8PzAVmBJ7zVqn9pdoPRVKF7wSsAN4EgqzRve36HcAoZDEqgO0zjs3rged8kGo3gOJ05ADT\n",
215 "s0bTkan+k9HXGaVGjNFxykVf81nH2Hb9Ich/MRJJeT291H9fL7brj6CwANfPspQDgOi3rijRx/rI\n",
216 "b8kB7wPPBZ4zL6Ne/JvfCDzn/WhufhvgvsBzVkR1dgN2AR4JPGduom38P7wXeM7c6FzfCfgU4iMR\n",
217 "lFLebNfPIefXzMBzikz8tusPQyx676bljmTeCfhyVLST7frp//TV9Dluu/6GwOhUvTWB58zIkjFq\n",
218 "sykyNfmfwHMW2K7fLzoWeyDTFPnAc14t1T7qYwNgT+Rc/wi5ZyT/N20UBEMRSqn+wNdTxQspTqTU\n",
219 "41BaP6yVOipzGzzSYnG6m6uBz0YPv7OQm3dytc35tuuflHZutF3/BuArwEaJ4p/QNdU2wGnAH9M7\n",
220 "jRSTG5CbS5LQdv2joymTLKYBzwHjbNc/DomW2TCxfbXt+sMCz3k/sa8RwM+Qh/X6qf5W2q4/CTit\n",
221 "zMN1OPB7CopQktW2658YeM5fEvXvRKZzBiXqZaWUPha4JlW2NfB8Rt0hiANfmjWIuf5jiLPfvVm/\n",
222 "AfmvbgNmB54zKrkheuD+Bjg11Wap7fpnBJ5TybelFk4E+iE+Fb+ptbHt+scg//nGqfJbgeMDz1mY\n",
223 "KN4UOZYX2q7fSWHhuNdt198ZOBc4MypbbLv+5wPPeTb6PiJqe5ft+ichx3WXRN8rbdc/OfCcrGis\n",
224 "R4ChiHKSlSn2f4BzkOvitMRvCKJ9DEzU9TPafwGZlkkyBvExSrKUrtdnmoOBycA5tus/iCyat3li\n",
225 "u7Zd/0rk2ihS1mzXPwT4E3LulaLTKAiGLL6EaMlJbtBat91pphIjFw289t9DVh4N7Jva9EKnWnpJ\n",
226 "G0RqBXcjCa08YCqy/PJE4L8A33b9HQPPeTNR/0bgvujzGchoywPSq5U+nd6R7fp7IDfRjYDrEE99\n",
227 "DeyHrPb5lO364xI36zTb2q4/AUnt/SSyLHQHMvJZklQOIhYChyCLid2FWBoGIQrDfwGnAP8Gskzd\n",
228 "VvSbBgPvIMdpJjLHuxdikXgg1ewa4Jbo84+BHRAFI/3gT9/QQZa+/iIy9zwccVQrSeA5nbbrX4s8\n",
229 "cI6htIIQK7xdFJLIAvEEYjmYBlyP/E4LeXj92Xb94YHnnFtOjhrYJ3q/vtbpE9v1fwqcjYxUL0GO\n",
230 "51bI//g1YIzt+mNTSgJIivfNEIXgBOThfx0ySv8Nct7vgzgfj0+1HQf8E5iPKM/vI+vLHA9cZbs+\n",
231 "JZSEevgDBZ++3yIKzgVI1FeSrCnD6ci0zebAJxCfjmoZjxzXPPBL5By0gW8jCt3sqHwtkYL1N0RB\n",
232 "/R2ymOG2yHE5CLFAHAu8ahQEQxbfyijrDdML3HTTkWvUBRfsb88bPb6TzjEK+oHKL184YHL+Jmdl\n",
233 "u+XrJsYBhwaec0dcYLu+hzw0dkcu/AvjbUmLgu36DqIgPB54zuQq9nURMgI8LjnyBibZrj8z2s/l\n",
234 "tuvvVcJJbWvkXDoi8JzbKu0s8JxFtut/IqXgAPzOdv0/IiPnb5KhICAjpMGIEjAhPV1iu35HWsbA\n",
235 "c25ObD8ZURAeqibENBqpTYnark8FBSHiakRBOMx2/cHpB29kSv4KooSlLRYnIcrBHcBXk7/Fdv0b\n",
236 "gReAM23Xvz7wnJlVyFIJK3qfXUsj2/U/jiiiq4B9ktEytuv/Fhlpfx2xEnw31XxHYLfAc6bbrv8k\n",
237 "cny/Bnwz8Jy/2q6/DTLd9F8Zu94ceXAeEHhOvM7MNbbrT0UU4vNs15+c2FY3gedcm/hNP0EUhDvL\n",
238 "KMrJtkuIFPboWNWiIOSAO4HDE7/Dj67FSxEn21+m2pyOWDpuCDxn7fG2Xf8e4F1EIVsceE5oohgM\n",
239 "XVBKjURuSEke11qXMhv3OPR553VO9Sb407yJZwTexO8FnnNV/qYj11XlAOCfSeUA1s4D/y36mp7f\n",
240 "rAvb9fdGLDMzU8pBzMXIg2wsMhLKQiFhgxWVg5gM5SDm+uh9VHqD7fr7IlaNFcAJWb4UPcHLPvCc\n",
241 "2YgVZn3gyIwq30AsQg8lQ+aiefUfR1/PzlB08sD9Udusfmsi2t+Q6GutjspnIE6L16dDaSN/irMR\n",
242 "p8dTbddPOxK/nwgxTZr8747e30SsEkNL7PvXGQrAVYgvwggK/gK9mXMyfuON0fvWkY9Dkp2i97uT\n",
243 "hYHnLKNgURsDxknRUMz5FJ8XP22DHIbqSc9pxsSOW8ObtJ89ovdXbNcvpQC8j4zcdiTbnAoy4q2b\n",
244 "6Ia3CYV5/Y0zqsXOf4/WEYveaq5GQuOOQaZekhydqJNkW2BLZF2UzhL/R+xE2XAIa+A52nb9lUho\n",
245 "Y63hd7GD5d1ZGwPPmW27/iuIUrkLXc/n9xP13rZd/yNgVezoF8n1NjAyyyKETGGl97fGdv1/IlaL\n",
246 "3h7e+06WM2PgOQtt11+GTMcNo6vVJ1aWsyK+4nvFQjAKgiGBUmoshfnOmGe11vdl1Tf0GOaUKI9v\n",
247 "lqrE9lqJb6b/Hb3KsU2Zba/VslPb9bdDfA0ORLz0N62iWWxVqMkc3iZuRuawP2u7/g6JKI9RSCTR\n",
248 "YoodhOP/YgNKK2Ix2zZJzjnINMN2NbaL/4uiaIUE/0EUhB3pqiCkMwl2IscjXZZFJ/B2iW1xRtWR\n",
249 "ZWTqDcwps63U9f8Q0TSN7fp/iK0PtuvviPjmrCHyR1qrICilNkTmHjZDLsDke/JzOtwnzY1KqXcR\n",
250 "R4cFiBab9XlRT87I19dQSo1GNPz0tJOxHvR8mhrOVobB0XuAOBiWo1zmwaqdXW3X3x+4BzGVv4SM\n",
251 "pN9AnPEg21McxMIArTs2dRN4zoe26/8NOA6xGJwfbYqV9b8GnrM81Sz+Lz5A0qOXo2y4Ww3MoT4F\n",
252 "IY4+KTfNF58TaXN4VthstVNDitLKcdxvOjKmEj0tv0M953fs87E3Eul0B2JliBflOzfwnFcA+iul\n",
253 "5iEmwQFNEBaK569L0amUWggcqrXO8gg2FKHG2CdW4Uem9XvBlUflu7RUaiByU3lPa92ZKN8cSav8\n",
254 "fUQBTHKr1rrqueIsxp18/eg1azrLjSYB6NfRsY3G6Is9nDjDYxh4zundvbMotvtm5N50duA5P09t\n",
255 "T0faJIkfirU+zNrF1YiC4FBQECZE73/JqB//F+u14r+ImIVEOB1iu/6ZNfhwzEamp7YuU2e7RN1m\n",
256 "oZBnW5YVIfZ1qNWfotw51yuIph++hET0bAkcikwpTAEuCjxnSly3PzIP0a8NcnYgD6SBlSoaIhQX\n",
257 "V2UtVup24LBU6S7IyG+NUuodZP52awojrTSvIjeshlij9XdQKh2jXYRRDtpGfOCruQfEpmzbdn0V\n",
258 "dP9iPLsgjnEryI67Lzd/PCt6/5Tt+v3LJXAqQ/z7ut2ZO/Ccx23XfxUYZbt+7D8xCngl8Jwsa80s\n",
259 "ZBS8ke36O7cg4ybA5UgegJ0QE/XN5auvZRaiIMQRF12wXX8TCv9ls6eERpOtIMR+EXNS5YsRh8dS\n",
260 "To/V+CzUck21i6uR5++4wHNeKFXJRDH0PfoR5fqmtHKwDDhCa73O5JA3lCSeF04v6Z3FPRTMzBO7\n",
261 "S6AE8Q12PbomgYn5Xpm29yMPhu2RUK96iKMn9q6zfa38JXo/NHoly7oQeM5K4Iro60+jKINuJVJC\n",
262 "Yu/439uuX805A4VkWyfbrp+V/MdFnOmeCmpfFKsSRYMc2/U/DeyG3OfSjpOx5WmfVHmcuXFcFfus\n",
263 "5ZpqObbrb45EtswqpxyAcVI0FDMbOFxrXeT9a+heopvnEArzolvashT0wmbEapdgGpIU5XDb9R9F\n",
264 "YqrXQyyL8wPPeTeuGHjOMtv1T0VuqldH6W//jigNmyHOcAcBgwPPcZog20xkRLcJ8DPb9S9CRqM7\n",
265 "I7kDvoDE1hfdxwLPWWy7/plI7oCLbNffHXm4zUQeRtsjGRP/EXhOKSfcABkpj49i5+9G/putgHmB\n",
266 "5yxIN4iSF21C14V6Rtiu/yYSW15uHv4a4P8oKAedlPcvOAv4KmItfCTKKfAS8v8NR1ILHwnsl5GA\n",
267 "qF7ORdYaGA48HGWyfBqYgViDRwCfQR72PkDgOU9E2TvHI4m0TgeeRczb30DyH2iKcyA0ymrgWNv1\n",
268 "FyDK1NvIQ3tStN3LCH+9HUl29UPb9echFo8BUbtLEKfJtJ9EmgA59ifbrj8bCR3cGDlvZqdTLcPa\n",
269 "9NCbUMhs2GFLKvPFSAKxZl7/CxEL8pgoA+QMxD+kE3HenAHcHnjOGmNB6Dt8iGjHWSFKK4HHkcQr\n",
270 "OxvloLXYrr+77fqrEIejNyiE6P0WccZbabv+lFLtG+Ry5AY/BHkYfRDtR9M79QAAA3FJREFUcwYS\n",
271 "NdCFwHPuQR6a7wHfAR5GMhk+i9xcT6G6KIOKBJ6zFBn9r0GUmBlIWN9ziHf/5yjO/phsfy2yqt4i\n",
272 "xOJxF3INTI9k/Q7ZoV4xv0PC5LZCci4sQm6g08kYHdquvxy5lt4DwsSmF5EENCts1//Idv3M9LbR\n",
273 "egJTkEx4NvBA1joFifqLIjkeR6wcfwdeQfIFTEEcjHNU79RXkShvw95Ixs5+yOj/KuSh+ATiAHcq\n",
274 "xb4fxwOXRfJMQc6zlxGF6B3g4MBznmmWnBFzEUfP0xDFcCGiAG+JHKushESXIdanjRBF4l3EInAj\n",
275 "8vuOqWK/5yNRGaOQFNkfIhkOX6CQgwAA2/W3jkI3V0T7ejjatAFyXb2PXP/LbVnroWGi6bbzo697\n",
276 "IlaWk5Br93wkk+jztusP7o94Lna7eaoMZU0cVXIAped7eqGZfP2ZqmPFl+ptrVf3n19UpvVMYLRS\n",
277 "agBywxuEjLwWAe9qrTMXV2mUzs7OP/Xrp+6qt33Hmn5Zue3XNeZTOVoky5nqKiQkrNT883Qk3WvJ\n",
278 "sMLAc1bbrv9Z5AH6KWRkOB+5wRWlWo7a3Ga7/mOIomAho/GFyI30YeDREru7ELlOq07TG3jONbbr\n",
279 "T0Nu9KOQm+i/gFsDz3nTdv2fI2FbpdpfHnlpH4LcnHdAlIz5yLErqXgFnvOR7fo28lDYE7lu3kKO\n",
280 "TdZ9K52xrhTl7knnUVB6SqVeTsr4apQU6lDEbG4hCsFbROsRBE1ebjrwnNB2/XGIGf5gRBkYhPyv\n",
281 "7yDpjR9MtVkOnGK7/vWIgrFrVPcF4O8ZKbaXIuduWkH6KfL/JbkEsWClfWK2CDzHt10/jzhXjkGO\n",
282 "yzNIZEiRD00ga3ocaLv+kUh2xo8hSuVURKmIUyiXVGYCWVzKQlJD7xrJNg85b9LX8RLgF6X6SpFU\n",
283 "9Cpe28gaJgORqEEAbNffDLlvHIQoAndR8NEYilwjExD/nwuUiTQ0GAwGw7qC7fqjEUvKqsBzmhWd\n",
284 "t05gu/5pyNoifw48J9N5PForxQeeNFMMBoPBYDD0DWL/llvK1In9jt4zCoLBYDAYDH2DePo5MwrJ\n",
285 "dv0hFPwTnjBRDAaDwWAw9A3+hPgOHRPl25iK+FhsiuR4OARx0Lwf+J1REAwGg8Fg6AMEnvNklL78\n",
286 "HMRRca/E5hVINNIVwI2B56z6/3ExLRI31pXNAAAAAElFTkSuQmCC\n"
287 ],
288 "text/plain": [
289 "<IPython.core.display.Image at 0x111275490>"
290 ]
291 },
292 "execution_count": 6,
127 293 "metadata": {},
128 "outputs": [
129 {
130 "metadata": {},
131 "output_type": "pyout",
132 "png": "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\n",
133 "prompt_number": 6,
134 "text": [
135 "<IPython.core.display.Image at 0x111275490>"
136 ]
137 }
138 ],
139 "prompt_number": 6
294 "output_type": "execute_result"
140 295 }
141 296 ],
142 "metadata": {}
297 "source": [
298 "from IPython.display import Image\n",
299 "Image(\"http://ipython.org/_static/IPy_header.png\")"
300 ]
143 301 }
144 ]
145 }
302 ],
303 "metadata": {},
304 "nbformat": 4,
305 "nbformat_minor": 0
306 } No newline at end of file
@@ -1,11 +1,10 b''
1 1 {
2 2 "cells": [
3 3 {
4 "cell_type": "heading",
5 "level": 1,
4 "cell_type": "markdown",
6 5 "metadata": {},
7 6 "source": [
8 "nbconvert latex test"
7 "# nbconvert latex test"
9 8 ]
10 9 },
11 10 {
@@ -16,11 +15,10 b''
16 15 ]
17 16 },
18 17 {
19 "cell_type": "heading",
20 "level": 2,
18 "cell_type": "markdown",
21 19 "metadata": {},
22 20 "source": [
23 "Printed Using Python"
21 "## Printed Using Python"
24 22 ]
25 23 },
26 24 {
@@ -43,11 +41,10 b''
43 41 ]
44 42 },
45 43 {
46 "cell_type": "heading",
47 "level": 2,
44 "cell_type": "markdown",
48 45 "metadata": {},
49 46 "source": [
50 "Pyout"
47 "## Pyout"
51 48 ]
52 49 },
53 50 {
@@ -111,11 +108,10 b''
111 108 ]
112 109 },
113 110 {
114 "cell_type": "heading",
115 "level": 3,
111 "cell_type": "markdown",
116 112 "metadata": {},
117 113 "source": [
118 "Image"
114 "### Image"
119 115 ]
120 116 },
121 117 {
@@ -28,7 +28,7 b' class TestValidator(TestsBase):'
28 28 self.assertEqual(isvalid(nb), True)
29 29
30 30 def test_nb4(self):
31 """Test that a v3 notebook passes validation"""
31 """Test that a v4 notebook passes validation"""
32 32 with self.fopen(u'test4.ipynb', u'r') as f:
33 33 nb = read(f, u'json')
34 34 validate(nb)
@@ -37,9 +37,9 b' class TestValidator(TestsBase):'
37 37 def test_invalid(self):
38 38 """Test than an invalid notebook does not pass validation"""
39 39 # this notebook has a few different errors:
40 # - the name is an integer, rather than a string
41 40 # - one cell is missing its source
42 # - one cell has an invalid level
41 # - invalid cell type
42 # - invalid output_type
43 43 with self.fopen(u'invalid.ipynb', u'r') as f:
44 44 nb = read(f, u'json')
45 45 with self.assertRaises(ValidationError):
@@ -6,7 +6,7 b''
6 6 from .nbbase import (
7 7 NotebookNode, from_dict,
8 8 nbformat, nbformat_minor, nbformat_schema,
9 new_code_cell, new_heading_cell, new_markdown_cell, new_notebook,
9 new_code_cell, new_markdown_cell, new_notebook,
10 10 new_output, output_from_msg,
11 11 )
12 12
@@ -4,6 +4,7 b''
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 import json
7 import re
7 8
8 9 from .nbbase import (
9 10 nbformat, nbformat_minor,
@@ -72,6 +73,7 b' def upgrade(nb, from_version=3, from_minor=0):'
72 73 def upgrade_cell(cell):
73 74 """upgrade a cell from v3 to v4
74 75
76 heading cell -> markdown heading
75 77 code cell:
76 78 - remove language metadata
77 79 - cell.input -> cell.source
@@ -82,9 +84,16 b' def upgrade_cell(cell):'
82 84 if cell.cell_type == 'code':
83 85 cell.pop('language', '')
84 86 cell.metadata.collapsed = cell.pop('collapsed')
85 cell.source = cell.pop('input')
87 cell.source = cell.pop('input', '')
86 88 cell.execution_count = cell.pop('prompt_number', None)
87 89 cell.outputs = upgrade_outputs(cell.outputs)
90 elif cell.cell_type == 'heading':
91 cell.cell_type = 'markdown'
92 level = cell.pop('level', 1)
93 cell.source = '{hashes} {single_line}'.format(
94 hashes='#' * level,
95 single_line = ' '.join(cell.get('source', '').splitlines()),
96 )
88 97 elif cell.cell_type == 'html':
89 98 # Technically, this exists. It will never happen in practice.
90 99 cell.cell_type = 'markdown'
@@ -98,6 +107,8 b' def downgrade_cell(cell):'
98 107 - cell.input <- cell.source
99 108 - cell.prompt_number <- cell.execution_count
100 109 - update outputs
110 markdown cell:
111 - single-line heading -> heading cell
101 112 """
102 113 if cell.cell_type == 'code':
103 114 cell.language = 'python'
@@ -105,6 +116,13 b' def downgrade_cell(cell):'
105 116 cell.prompt_number = cell.pop('execution_count', None)
106 117 cell.collapsed = cell.metadata.pop('collapsed', False)
107 118 cell.outputs = downgrade_outputs(cell.outputs)
119 elif cell.cell_type == 'markdown':
120 source = cell.get('source', '')
121 if '\n' not in source and source.startswith('#'):
122 prefix, text = re.match(r'(#+)\s*(.*)', source).groups()
123 cell.cell_type = 'heading'
124 cell.source = text
125 cell.level = len(prefix)
108 126 return cell
109 127
110 128 _mime_map = {
@@ -121,16 +121,6 b" def new_markdown_cell(source='', **kwargs):"
121 121 validate(cell, 'markdown_cell')
122 122 return cell
123 123
124 def new_heading_cell(source='', **kwargs):
125 """Create a new heading cell"""
126 cell = NotebookNode(cell_type='heading', source=source)
127 cell.update(from_dict(kwargs))
128 cell.setdefault('metadata', NotebookNode())
129 cell.setdefault('level', 1)
130
131 validate(cell, 'heading_cell')
132 return cell
133
134 124 def new_raw_cell(source='', **kwargs):
135 125 """Create a new raw cell"""
136 126 cell = NotebookNode(cell_type='raw', source=source)
@@ -59,7 +59,6 b''
59 59 "oneOf": [
60 60 {"$ref": "#/definitions/raw_cell"},
61 61 {"$ref": "#/definitions/markdown_cell"},
62 {"$ref": "#/definitions/heading_cell"},
63 62 {"$ref": "#/definitions/code_cell"}
64 63 ]
65 64 }
@@ -118,34 +117,6 b''
118 117 }
119 118 },
120 119
121 "heading_cell": {
122 "description": "Notebook heading cell.",
123 "type": "object",
124 "additionalProperties": false,
125 "required": ["cell_type", "metadata", "source", "level"],
126 "properties": {
127 "cell_type": {
128 "description": "String identifying the type of cell.",
129 "enum": ["heading"]
130 },
131 "metadata": {
132 "description": "Cell-level metadata.",
133 "type": "object",
134 "properties": {
135 "name": {"$ref": "#/definitions/misc/metadata_name"},
136 "tags": {"$ref": "#/definitions/misc/metadata_tags"}
137 },
138 "additionalProperties": true
139 },
140 "source": {"$ref": "#/definitions/misc/source"},
141 "level": {
142 "description": "Level of heading cells.",
143 "type": "integer",
144 "minimum": 1
145 }
146 }
147 },
148
149 120 "code_cell": {
150 121 "description": "Notebook code cell.",
151 122 "type": "object",
@@ -44,14 +44,15 b' def rejoin_lines(nb):'
44 44 for cell in nb.cells:
45 45 if 'source' in cell and isinstance(cell.source, list):
46 46 cell.source = _join_lines(cell.source)
47 if cell.cell_type == 'code':
48 for output in cell.outputs:
49 if output.output_type in {'execute_result', 'display_data'}:
50 for key, value in output.data.items():
47 if cell.get('cell_type', None) == 'code':
48 for output in cell.get('outputs', []):
49 output_type = output.get('output_type', '')
50 if output_type in {'execute_result', 'display_data'}:
51 for key, value in output.get('data', {}).items():
51 52 if key != 'application/json' and isinstance(value, list):
52 53 output.data[key] = _join_lines(value)
53 elif output.output_type == 'stream':
54 if isinstance(output.text, list):
54 elif output_type:
55 if isinstance(output.get('text', ''), list):
55 56 output.text = _join_lines(output.text)
56 57 return nb
57 58
@@ -4,7 +4,7 b' import os'
4 4 from base64 import encodestring
5 5
6 6 from ..nbbase import (
7 new_code_cell, new_heading_cell, new_markdown_cell, new_notebook,
7 new_code_cell, new_markdown_cell, new_notebook,
8 8 new_output, new_raw_cell
9 9 )
10 10
@@ -31,9 +31,8 b' cells.append(new_raw_cell('
31 31 source='A random array',
32 32 ))
33 33
34 cells.append(new_heading_cell(
35 source=u'My Heading',
36 level=2,
34 cells.append(new_markdown_cell(
35 source=u'## My Heading',
37 36 ))
38 37
39 38 cells.append(new_code_cell(
@@ -1,10 +1,13 b''
1 1 import copy
2 2
3 import nose.tools as nt
4
3 5 from IPython.nbformat.current import validate
4 6 from .. import convert
5 7
6 8 from . import nbexamples
7 9 from IPython.nbformat.v3.tests import nbexamples as v3examples
10 from IPython.nbformat import v3, v4
8 11
9 12 def test_upgrade_notebook():
10 13 nb03 = copy.deepcopy(v3examples.nb0)
@@ -17,3 +20,48 b' def test_downgrade_notebook():'
17 20 validate(nb04)
18 21 nb03 = convert.downgrade(nb04)
19 22 validate(nb03)
23
24 def test_upgrade_heading():
25 v3h = v3.new_heading_cell
26 v4m = v4.new_markdown_cell
27 for v3cell, expected in [
28 (
29 v3h(source='foo', level=1),
30 v4m(source='# foo'),
31 ),
32 (
33 v3h(source='foo\nbar\nmulti-line\n', level=4),
34 v4m(source='#### foo bar multi-line'),
35 ),
36 ]:
37 upgraded = convert.upgrade_cell(v3cell)
38 nt.assert_equal(upgraded, expected)
39
40 def test_downgrade_heading():
41 v3h = v3.new_heading_cell
42 v4m = v4.new_markdown_cell
43 v3m = lambda source: v3.new_text_cell('markdown', source)
44 for v4cell, expected in [
45 (
46 v4m(source='# foo'),
47 v3h(source='foo', level=1),
48 ),
49 (
50 v4m(source='#foo'),
51 v3h(source='foo', level=1),
52 ),
53 (
54 v4m(source='#\tfoo'),
55 v3h(source='foo', level=1),
56 ),
57 (
58 v4m(source='# \t foo'),
59 v3h(source='foo', level=1),
60 ),
61 (
62 v4m(source='# foo\nbar'),
63 v3m(source='# foo\nbar'),
64 ),
65 ]:
66 downgraded = convert.downgrade_cell(v4cell)
67 nt.assert_equal(downgraded, expected)
@@ -6,7 +6,7 b' import nose.tools as nt'
6 6 from IPython.nbformat.validator import isvalid, validate, ValidationError
7 7 from ..nbbase import (
8 8 NotebookNode, nbformat,
9 new_code_cell, new_heading_cell, new_markdown_cell, new_notebook,
9 new_code_cell, new_markdown_cell, new_notebook,
10 10 new_output, new_raw_cell,
11 11 )
12 12
@@ -34,17 +34,6 b' def test_raw_cell():'
34 34 cell = new_raw_cell('hi')
35 35 nt.assert_equal(cell.source, u'hi')
36 36
37 def test_empty_heading_cell():
38 cell = new_heading_cell()
39 nt.assert_equal(cell.cell_type, u'heading')
40 nt.assert_equal(cell.source, '')
41 nt.assert_equal(cell.level, 1)
42
43 def test_heading_cell():
44 cell = new_heading_cell(u'hi', level=2)
45 nt.assert_equal(cell.source, u'hi')
46 nt.assert_equal(cell.level, 2)
47
48 37 def test_empty_code_cell():
49 38 cell = new_code_cell('hi')
50 39 nt.assert_equal(cell.cell_type, 'code')
@@ -12,7 +12,7 b' from IPython.nbformat.validator import validate, ValidationError'
12 12 from ..nbjson import reads
13 13 from ..nbbase import (
14 14 nbformat,
15 new_code_cell, new_heading_cell, new_markdown_cell, new_notebook,
15 new_code_cell, new_markdown_cell, new_notebook,
16 16 new_output, new_raw_cell,
17 17 )
18 18
@@ -73,31 +73,6 b' def test_invalid_markdown_cell():'
73 73 with nt.assert_raises(ValidationError):
74 74 validate4(cell, 'markdown_cell')
75 75
76 def test_invalid_heading_cell():
77 cell = new_heading_cell()
78
79 cell['source'] = 5
80 with nt.assert_raises(ValidationError):
81 validate4(cell, 'heading_cell')
82
83 cell = new_heading_cell()
84 del cell['metadata']
85
86 with nt.assert_raises(ValidationError):
87 validate4(cell, 'heading_cell')
88
89 cell = new_heading_cell()
90 del cell['source']
91
92 with nt.assert_raises(ValidationError):
93 validate4(cell, 'heading_cell')
94
95 cell = new_heading_cell()
96 del cell['cell_type']
97
98 with nt.assert_raises(ValidationError):
99 validate4(cell, 'heading_cell')
100
101 76 def test_invalid_raw_cell():
102 77 cell = new_raw_cell()
103 78
@@ -82,24 +82,9 b' as defined in `GitHub-flavored markdown`_, and implemented in marked_.'
82 82 "source" : ["some *markdown*"],
83 83 }
84 84
85 .. versionchanged:: 4.0
85 86
86 Heading cells
87 -------------
88
89 Heading cells are single lines describing a section header (mapping onto h1-h6 tags in HTML).
90 These cells indicate structure of the document,
91 and are used for things like outline-views and automatically generating HTML anchors
92 within the page for quick navigation.
93 They have a ``level`` field, with an integer value from 1-6 (inclusive).
94
95 .. sourcecode:: python
96
97 {
98 "cell_type" : "markdown",
99 "metadata" : {},
100 "level" : 1, # An integer on [1-6]
101 "source" : ["A simple heading"],
102 }
87 Heading cells have been removed, in favor of simple headings in markdown.
103 88
104 89
105 90 Code cells
General Comments 0
You need to be logged in to leave comments. Login now