@@ -173,6 +180,13 @@
diff --git a/IPython/nbformat/current.py b/IPython/nbformat/current.py
index ab4fa3b..a1e29dc 100644
--- a/IPython/nbformat/current.py
+++ b/IPython/nbformat/current.py
@@ -21,20 +21,21 @@ import json
from xml.etree import ElementTree as ET
import re
+from IPython.nbformat import v3
from IPython.nbformat import v2
from IPython.nbformat import v1
-from IPython.nbformat.v2 import (
+from IPython.nbformat.v3 import (
NotebookNode,
new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
- parse_filename, new_metadata, new_author
+ parse_filename, new_metadata, new_author, new_heading_cell
)
#-----------------------------------------------------------------------------
# Code
#-----------------------------------------------------------------------------
-current_nbformat = 2
+current_nbformat = 3
class NBFormatError(Exception):
@@ -48,17 +49,6 @@ def parse_json(s, **kwargs):
return nbformat, d
-def parse_xml(s, **kwargs):
- """Parse a string into a (nbformat, etree) tuple."""
- root = ET.fromstring(s)
- nbformat_e = root.find('nbformat')
- if nbformat_e is not None:
- nbformat = int(nbformat_e.text)
- else:
- raise NBFormatError('No nbformat version found')
- return nbformat, root
-
-
def parse_py(s, **kwargs):
"""Parse a string into a (nbformat, string) tuple."""
pattern = r'# (?P\d+)'
@@ -66,7 +56,7 @@ def parse_py(s, **kwargs):
if m is not None:
nbformat = int(m.group('nbformat'))
else:
- nbformat = 2
+ nbformat = 3
return nbformat, s
@@ -75,26 +65,19 @@ def reads_json(s, **kwargs):
nbformat, d = parse_json(s, **kwargs)
if nbformat == 1:
nb = v1.to_notebook_json(d, **kwargs)
- nb = v2.convert_to_this_nbformat(nb, orig_version=1)
+ nb = v3.convert_to_this_nbformat(nb, orig_version=1)
elif nbformat == 2:
nb = v2.to_notebook_json(d, **kwargs)
+ nb = v3.convert_to_this_nbformat(nb, orig_version=2)
+ elif nbformat == 3:
+ nb = v3.to_notebook_json(d, **kwargs)
else:
raise NBFormatError('Unsupported JSON nbformat version: %i' % nbformat)
return nb
def writes_json(nb, **kwargs):
- return v2.writes_json(nb, **kwargs)
-
-
-def reads_xml(s, **kwargs):
- """Read an XML notebook from a string and return the NotebookNode object."""
- nbformat, root = parse_xml(s, **kwargs)
- if nbformat == 2:
- nb = v2.to_notebook_xml(root, **kwargs)
- else:
- raise NBFormatError('Unsupported XML nbformat version: %i' % nbformat)
- return nb
+ return v3.writes_json(nb, **kwargs)
def reads_py(s, **kwargs):
@@ -102,13 +85,15 @@ def reads_py(s, **kwargs):
nbformat, s = parse_py(s, **kwargs)
if nbformat == 2:
nb = v2.to_notebook_py(s, **kwargs)
+ elif nbformat == 3:
+ nb = v3.to_notebook_py(s, **kwargs)
else:
raise NBFormatError('Unsupported PY nbformat version: %i' % nbformat)
return nb
def writes_py(nb, **kwargs):
- return v2.writes_py(nb, **kwargs)
+ return v3.writes_py(nb, **kwargs)
# High level API
@@ -133,9 +118,7 @@ def reads(s, format, **kwargs):
The notebook that was read.
"""
format = unicode(format)
- if format == u'xml':
- return reads_xml(s, **kwargs)
- elif format == u'json' or format == u'ipynb':
+ if format == u'json' or format == u'ipynb':
return reads_json(s, **kwargs)
elif format == u'py':
return reads_py(s, **kwargs)
@@ -161,9 +144,7 @@ def writes(nb, format, **kwargs):
The notebook string.
"""
format = unicode(format)
- if format == u'xml':
- raise NotImplementedError('Write to XML files is not implemented.')
- elif format == u'json' or format == u'ipynb':
+ if format == u'json' or format == u'ipynb':
return writes_json(nb, **kwargs)
elif format == u'py':
return writes_py(nb, **kwargs)
diff --git a/IPython/nbformat/v3/__init__.py b/IPython/nbformat/v3/__init__.py
new file mode 100644
index 0000000..2c86f51
--- /dev/null
+++ b/IPython/nbformat/v3/__init__.py
@@ -0,0 +1,74 @@
+"""The main API for the v2 notebook format.
+
+Authors:
+
+* Brian Granger
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) 2008-2011 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+from .nbbase import (
+ NotebookNode,
+ new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
+ new_metadata, new_author, new_heading_cell
+)
+
+from .nbjson import reads as reads_json, writes as writes_json
+from .nbjson import reads as read_json, writes as write_json
+from .nbjson import to_notebook as to_notebook_json
+
+from .nbpy import reads as reads_py, writes as writes_py
+from .nbpy import reads as read_py, writes as write_py
+from .nbpy import to_notebook as to_notebook_py
+
+from .convert import convert_to_this_nbformat
+
+#-----------------------------------------------------------------------------
+# Code
+#-----------------------------------------------------------------------------
+
+def parse_filename(fname):
+ """Parse a notebook filename.
+
+ This function takes a notebook filename and returns the notebook
+ format (json/py) and the notebook name. This logic can be
+ summarized as follows:
+
+ * notebook.ipynb -> (notebook.ipynb, notebook, json)
+ * notebook.json -> (notebook.json, notebook, json)
+ * notebook.py -> (notebook.py, notebook, py)
+ * notebook -> (notebook.ipynb, notebook, json)
+
+ Parameters
+ ----------
+ fname : unicode
+ The notebook filename. The filename can use a specific filename
+ extention (.ipynb, .json, .py) or none, in which case .ipynb will
+ be assumed.
+
+ Returns
+ -------
+ (fname, name, format) : (unicode, unicode, unicode)
+ The filename, notebook name and format.
+ """
+ if fname.endswith(u'.ipynb'):
+ format = u'json'
+ elif fname.endswith(u'.json'):
+ format = u'json'
+ elif fname.endswith(u'.py'):
+ format = u'py'
+ else:
+ fname = fname + u'.ipynb'
+ format = u'json'
+ name = fname.split('.')[0]
+ return fname, name, format
+
diff --git a/IPython/nbformat/v3/convert.py b/IPython/nbformat/v3/convert.py
new file mode 100644
index 0000000..8480b9d
--- /dev/null
+++ b/IPython/nbformat/v3/convert.py
@@ -0,0 +1,48 @@
+"""Code for converting notebooks to and from the v2 format.
+
+Authors:
+
+* Brian Granger
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) 2008-2011 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+from .nbbase import (
+ new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output
+)
+
+from IPython.nbformat import v2
+
+#-----------------------------------------------------------------------------
+# Code
+#-----------------------------------------------------------------------------
+
+def convert_to_this_nbformat(nb, orig_version=2):
+ """Convert a notebook to the v2 format.
+
+ Parameters
+ ----------
+ nb : NotebookNode
+ The Python representation of the notebook to convert.
+ orig_version : int
+ The original version of the notebook to convert.
+ """
+ if orig_version == 1:
+ nb = v2.convert_to_this_nbformat(nb)
+ orig_version = 2
+ if orig_version == 2:
+ return nb
+ elif orig_version == 3:
+ return nb
+ else:
+ raise ValueError('Cannot convert a notebook from v%s to v3' % orig_version)
+
diff --git a/IPython/nbformat/v3/nbbase.py b/IPython/nbformat/v3/nbbase.py
new file mode 100644
index 0000000..9d218eb
--- /dev/null
+++ b/IPython/nbformat/v3/nbbase.py
@@ -0,0 +1,191 @@
+"""The basic dict based notebook format.
+
+The Python representation of a notebook is a nested structure of
+dictionary subclasses that support attribute access
+(IPython.utils.ipstruct.Struct). The functions in this module are merely
+helpers to build the structs in the right form.
+
+Authors:
+
+* Brian Granger
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) 2008-2011 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+import pprint
+import uuid
+
+from IPython.utils.ipstruct import Struct
+
+#-----------------------------------------------------------------------------
+# Code
+#-----------------------------------------------------------------------------
+
+class NotebookNode(Struct):
+ pass
+
+
+def from_dict(d):
+ if isinstance(d, dict):
+ newd = NotebookNode()
+ for k,v in d.items():
+ newd[k] = from_dict(v)
+ return newd
+ elif isinstance(d, (tuple, list)):
+ return [from_dict(i) for i in d]
+ else:
+ return d
+
+
+def new_output(output_type=None, output_text=None, output_png=None,
+ output_html=None, output_svg=None, output_latex=None, output_json=None,
+ output_javascript=None, output_jpeg=None, prompt_number=None,
+ etype=None, evalue=None, traceback=None):
+ """Create a new code cell with input and output"""
+ output = NotebookNode()
+ if output_type is not None:
+ output.output_type = unicode(output_type)
+
+ if output_type != 'pyerr':
+ if output_text is not None:
+ output.text = unicode(output_text)
+ if output_png is not None:
+ output.png = bytes(output_png)
+ if output_jpeg is not None:
+ output.jpeg = bytes(output_jpeg)
+ if output_html is not None:
+ output.html = unicode(output_html)
+ if output_svg is not None:
+ output.svg = unicode(output_svg)
+ if output_latex is not None:
+ output.latex = unicode(output_latex)
+ if output_json is not None:
+ output.json = unicode(output_json)
+ if output_javascript is not None:
+ output.javascript = unicode(output_javascript)
+
+ if output_type == u'pyout':
+ if prompt_number is not None:
+ output.prompt_number = int(prompt_number)
+
+ if output_type == u'pyerr':
+ if etype is not None:
+ output.etype = unicode(etype)
+ if evalue is not None:
+ output.evalue = unicode(evalue)
+ if traceback is not None:
+ output.traceback = [unicode(frame) for frame in list(traceback)]
+
+ return output
+
+
+def new_code_cell(input=None, prompt_number=None, outputs=None,
+ language=u'python', collapsed=False):
+ """Create a new code cell with input and output"""
+ cell = NotebookNode()
+ cell.cell_type = u'code'
+ if language is not None:
+ cell.language = unicode(language)
+ if input is not None:
+ cell.input = unicode(input)
+ if prompt_number is not None:
+ cell.prompt_number = int(prompt_number)
+ if outputs is None:
+ cell.outputs = []
+ else:
+ cell.outputs = outputs
+ if collapsed is not None:
+ cell.collapsed = bool(collapsed)
+
+ return cell
+
+def new_text_cell(cell_type, source=None, rendered=None):
+ """Create a new text cell."""
+ cell = NotebookNode()
+ if source is not None:
+ cell.source = unicode(source)
+ if rendered is not None:
+ cell.rendered = unicode(rendered)
+ cell.cell_type = cell_type
+ return cell
+
+
+def new_heading_cell(source=None, rendered=None, level=1):
+ """Create a new section cell with a given integer level."""
+ cell = NotebookNode()
+ cell.cell_type = u'heading'
+ if source is not None:
+ cell.source = unicode(source)
+ if rendered is not None:
+ cell.rendered = unicode(rendered)
+ cell.level = int(level)
+ return cell
+
+
+def new_worksheet(name=None, cells=None):
+ """Create a worksheet by name with with a list of cells."""
+ ws = NotebookNode()
+ if name is not None:
+ ws.name = unicode(name)
+ if cells is None:
+ ws.cells = []
+ else:
+ ws.cells = list(cells)
+ return ws
+
+
+def new_notebook(metadata=None, worksheets=None):
+ """Create a notebook by name, id and a list of worksheets."""
+ nb = NotebookNode()
+ nb.nbformat = 3
+ if worksheets is None:
+ nb.worksheets = []
+ else:
+ nb.worksheets = list(worksheets)
+ if metadata is None:
+ nb.metadata = new_metadata()
+ else:
+ nb.metadata = NotebookNode(metadata)
+ return nb
+
+
+def new_metadata(name=None, authors=None, license=None, created=None,
+ modified=None, gistid=None):
+ """Create a new metadata node."""
+ metadata = NotebookNode()
+ if name is not None:
+ metadata.name = unicode(name)
+ if authors is not None:
+ metadata.authors = list(authors)
+ if created is not None:
+ metadata.created = unicode(created)
+ if modified is not None:
+ metadata.modified = unicode(modified)
+ if license is not None:
+ metadata.license = unicode(license)
+ if gistid is not None:
+ metadata.gistid = unicode(gistid)
+ return metadata
+
+def new_author(name=None, email=None, affiliation=None, url=None):
+ """Create a new author."""
+ author = NotebookNode()
+ if name is not None:
+ author.name = unicode(name)
+ if email is not None:
+ author.email = unicode(email)
+ if affiliation is not None:
+ author.affiliation = unicode(affiliation)
+ if url is not None:
+ author.url = unicode(url)
+ return author
+
diff --git a/IPython/nbformat/v3/nbjson.py b/IPython/nbformat/v3/nbjson.py
new file mode 100644
index 0000000..558a613
--- /dev/null
+++ b/IPython/nbformat/v3/nbjson.py
@@ -0,0 +1,70 @@
+"""Read and write notebooks in JSON format.
+
+Authors:
+
+* Brian Granger
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) 2008-2011 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+import copy
+import json
+
+from .nbbase import from_dict
+from .rwbase import (
+ NotebookReader, NotebookWriter, restore_bytes, rejoin_lines, split_lines
+)
+
+#-----------------------------------------------------------------------------
+# Code
+#-----------------------------------------------------------------------------
+
+class BytesEncoder(json.JSONEncoder):
+ """A JSON encoder that accepts b64 (and other *ascii*) bytestrings."""
+ def default(self, obj):
+ if isinstance(obj, bytes):
+ return obj.decode('ascii')
+ return json.JSONEncoder.default(self, obj)
+
+
+class JSONReader(NotebookReader):
+
+ def reads(self, s, **kwargs):
+ nb = json.loads(s, **kwargs)
+ nb = self.to_notebook(nb, **kwargs)
+ return nb
+
+ def to_notebook(self, d, **kwargs):
+ return restore_bytes(rejoin_lines(from_dict(d)))
+
+
+class JSONWriter(NotebookWriter):
+
+ def writes(self, nb, **kwargs):
+ kwargs['cls'] = BytesEncoder
+ kwargs['indent'] = 1
+ kwargs['sort_keys'] = True
+ kwargs['separators'] = (',',': ')
+ if kwargs.pop('split_lines', True):
+ nb = split_lines(copy.deepcopy(nb))
+ return json.dumps(nb, **kwargs)
+
+
+_reader = JSONReader()
+_writer = JSONWriter()
+
+reads = _reader.reads
+read = _reader.read
+to_notebook = _reader.to_notebook
+write = _writer.write
+writes = _writer.writes
+
diff --git a/IPython/nbformat/v3/nbpy.py b/IPython/nbformat/v3/nbpy.py
new file mode 100644
index 0000000..ec7daf1
--- /dev/null
+++ b/IPython/nbformat/v3/nbpy.py
@@ -0,0 +1,200 @@
+"""Read and write notebooks as regular .py files.
+
+Authors:
+
+* Brian Granger
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) 2008-2011 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+import re
+from .rwbase import NotebookReader, NotebookWriter
+from .nbbase import (
+ new_code_cell, new_text_cell, new_worksheet,
+ new_notebook, new_heading_cell
+)
+
+#-----------------------------------------------------------------------------
+# Code
+#-----------------------------------------------------------------------------
+
+_encoding_declaration_re = re.compile(r"^#.*coding[:=]\s*([-\w.]+)")
+
+class PyReaderError(Exception):
+ pass
+
+
+class PyReader(NotebookReader):
+
+ def reads(self, s, **kwargs):
+ return self.to_notebook(s,**kwargs)
+
+ def to_notebook(self, s, **kwargs):
+ lines = s.splitlines()
+ cells = []
+ cell_lines = []
+ kwargs = {}
+ state = u'codecell'
+ for line in lines:
+ if line.startswith(u'# ') or _encoding_declaration_re.match(line):
+ pass
+ elif line.startswith(u'# '):
+ cell = self.new_cell(state, cell_lines, **kwargs)
+ if cell is not None:
+ cells.append(cell)
+ state = u'codecell'
+ cell_lines = []
+ kwargs = {}
+ elif line.startswith(u'# '):
+ cell = self.new_cell(state, cell_lines, **kwargs)
+ if cell is not None:
+ cells.append(cell)
+ state = u'htmlcell'
+ cell_lines = []
+ kwargs = {}
+ elif line.startswith(u'# '):
+ cell = self.new_cell(state, cell_lines, **kwargs)
+ if cell is not None:
+ cells.append(cell)
+ state = u'markdowncell'
+ cell_lines = []
+ kwargs = {}
+ elif line.startswith(u'# '):
+ cell = self.new_cell(state, cell_lines, **kwargs)
+ if cell is not None:
+ cells.append(cell)
+ state = u'plaintextcell'
+ cell_lines = []
+ kwargs = {}
+ elif line.startswith(u'# \d)>',line)
+ if m is not None:
+ state = u'headingcell'
+ kwargs = {}
+ kwargs['level'] = int(m.group('level'))
+ else:
+ state = u'codecell'
+ kwargs = {}
+ cell_lines = []
+ else:
+ cell_lines.append(line)
+ if cell_lines and state == u'codecell':
+ cell = self.new_cell(state, cell_lines)
+ if cell is not None:
+ cells.append(cell)
+ ws = new_worksheet(cells=cells)
+ nb = new_notebook(worksheets=[ws])
+ return nb
+
+ def new_cell(self, state, lines, **kwargs):
+ if state == u'codecell':
+ input = u'\n'.join(lines)
+ input = input.strip(u'\n')
+ if input:
+ return new_code_cell(input=input)
+ elif state == u'htmlcell':
+ text = self._remove_comments(lines)
+ if text:
+ return new_text_cell(u'html',source=text)
+ elif state == u'markdowncell':
+ text = self._remove_comments(lines)
+ if text:
+ return new_text_cell(u'markdown',source=text)
+ elif state == u'plaintextcell':
+ text = self._remove_comments(lines)
+ if text:
+ return new_text_cell(u'plaintext',source=text)
+ elif state == u'headingcell':
+ text = self._remove_comments(lines)
+ level = kwargs.get('level',1)
+ if text:
+ return new_heading_cell(source=text,level=level)
+
+ def _remove_comments(self, lines):
+ new_lines = []
+ for line in lines:
+ if line.startswith(u'#'):
+ new_lines.append(line[2:])
+ else:
+ new_lines.append(line)
+ text = u'\n'.join(new_lines)
+ text = text.strip(u'\n')
+ return text
+
+ def split_lines_into_blocks(self, lines):
+ if len(lines) == 1:
+ yield lines[0]
+ raise StopIteration()
+ import ast
+ source = '\n'.join(lines)
+ code = ast.parse(source)
+ starts = [x.lineno-1 for x in code.body]
+ for i in range(len(starts)-1):
+ yield '\n'.join(lines[starts[i]:starts[i+1]]).strip('\n')
+ yield '\n'.join(lines[starts[-1]:]).strip('\n')
+
+
+class PyWriter(NotebookWriter):
+
+ def writes(self, nb, **kwargs):
+ lines = [u'# -*- coding: utf-8 -*-']
+ lines.extend([u'# 2',''])
+ for ws in nb.worksheets:
+ for cell in ws.cells:
+ if cell.cell_type == u'code':
+ input = cell.get(u'input')
+ if input is not None:
+ lines.extend([u'# ',u''])
+ lines.extend(input.splitlines())
+ lines.append(u'')
+ elif cell.cell_type == u'html':
+ input = cell.get(u'source')
+ if input is not None:
+ lines.extend([u'# ',u''])
+ lines.extend([u'# ' + line for line in input.splitlines()])
+ lines.append(u'')
+ elif cell.cell_type == u'markdown':
+ input = cell.get(u'source')
+ if input is not None:
+ lines.extend([u'# ',u''])
+ lines.extend([u'# ' + line for line in input.splitlines()])
+ lines.append(u'')
+ elif cell.cell_type == u'plaintext':
+ input = cell.get(u'source')
+ if input is not None:
+ lines.extend([u'# ',u''])
+ lines.extend([u'# ' + line for line in input.splitlines()])
+ lines.append(u'')
+ elif cell.cell_type == u'heading':
+ input = cell.get(u'source')
+ level = cell.get(u'level',1)
+ if input is not None:
+ lines.extend([u'# ' % level,u''])
+ lines.extend([u'# ' + line for line in input.splitlines()])
+ lines.append(u'')
+ lines.append('')
+ return unicode('\n'.join(lines))
+
+
+_reader = PyReader()
+_writer = PyWriter()
+
+reads = _reader.reads
+read = _reader.read
+to_notebook = _reader.to_notebook
+write = _writer.write
+writes = _writer.writes
+
diff --git a/IPython/nbformat/v3/rwbase.py b/IPython/nbformat/v3/rwbase.py
new file mode 100644
index 0000000..7566b33
--- /dev/null
+++ b/IPython/nbformat/v3/rwbase.py
@@ -0,0 +1,165 @@
+"""Base classes and utilities for readers and writers.
+
+Authors:
+
+* Brian Granger
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) 2008-2011 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+from base64 import encodestring, decodestring
+import pprint
+
+from IPython.utils.py3compat import str_to_bytes
+
+#-----------------------------------------------------------------------------
+# Code
+#-----------------------------------------------------------------------------
+
+def restore_bytes(nb):
+ """Restore bytes of image data from unicode-only formats.
+
+ Base64 encoding is handled elsewhere. Bytes objects in the notebook are
+ always b64-encoded. We DO NOT encode/decode around file formats.
+ """
+ for ws in nb.worksheets:
+ for cell in ws.cells:
+ if cell.cell_type == 'code':
+ for output in cell.outputs:
+ if 'png' in output:
+ output.png = str_to_bytes(output.png, 'ascii')
+ if 'jpeg' in output:
+ output.jpeg = str_to_bytes(output.jpeg, 'ascii')
+ return nb
+
+# output keys that are likely to have multiline values
+_multiline_outputs = ['text', 'html', 'svg', 'latex', 'javascript', 'json']
+
+def rejoin_lines(nb):
+ """rejoin multiline text into strings
+
+ For reversing effects of ``split_lines(nb)``.
+
+ This only rejoins lines that have been split, so if text objects were not split
+ they will pass through unchanged.
+
+ Used when reading JSON files that may have been passed through split_lines.
+ """
+ for ws in nb.worksheets:
+ for cell in ws.cells:
+ if cell.cell_type == 'code':
+ if 'input' in cell and isinstance(cell.input, list):
+ cell.input = u'\n'.join(cell.input)
+ for output in cell.outputs:
+ for key in _multiline_outputs:
+ item = output.get(key, None)
+ if isinstance(item, list):
+ output[key] = u'\n'.join(item)
+ else: # text, heading cell
+ for key in ['source', 'rendered']:
+ item = cell.get(key, None)
+ if isinstance(item, list):
+ cell[key] = u'\n'.join(item)
+ return nb
+
+
+def split_lines(nb):
+ """split likely multiline text into lists of strings
+
+ For file output more friendly to line-based VCS. ``rejoin_lines(nb)`` will
+ reverse the effects of ``split_lines(nb)``.
+
+ Used when writing JSON files.
+ """
+ for ws in nb.worksheets:
+ for cell in ws.cells:
+ if cell.cell_type == 'code':
+ if 'input' in cell and isinstance(cell.input, basestring):
+ cell.input = cell.input.splitlines()
+ for output in cell.outputs:
+ for key in _multiline_outputs:
+ item = output.get(key, None)
+ if isinstance(item, basestring):
+ output[key] = item.splitlines()
+ else: # text, heading cell
+ for key in ['source', 'rendered']:
+ item = cell.get(key, None)
+ if isinstance(item, basestring):
+ cell[key] = item.splitlines()
+ return nb
+
+# b64 encode/decode are never actually used, because all bytes objects in
+# the notebook are already b64-encoded, and we don't need/want to double-encode
+
+def base64_decode(nb):
+ """Restore all bytes objects in the notebook from base64-encoded strings.
+
+ Note: This is never used
+ """
+ for ws in nb.worksheets:
+ for cell in ws.cells:
+ if cell.cell_type == 'code':
+ for output in cell.outputs:
+ if 'png' in output:
+ if isinstance(output.png, unicode):
+ output.png = output.png.encode('ascii')
+ output.png = decodestring(output.png)
+ if 'jpeg' in output:
+ if isinstance(output.jpeg, unicode):
+ output.jpeg = output.jpeg.encode('ascii')
+ output.jpeg = decodestring(output.jpeg)
+ return nb
+
+
+def base64_encode(nb):
+ """Base64 encode all bytes objects in the notebook.
+
+ These will be b64-encoded unicode strings
+
+ Note: This is never used
+ """
+ for ws in nb.worksheets:
+ for cell in ws.cells:
+ if cell.cell_type == 'code':
+ for output in cell.outputs:
+ if 'png' in output:
+ output.png = encodestring(output.png).decode('ascii')
+ if 'jpeg' in output:
+ output.jpeg = encodestring(output.jpeg).decode('ascii')
+ return nb
+
+
+class NotebookReader(object):
+ """A class for reading notebooks."""
+
+ def reads(self, s, **kwargs):
+ """Read a notebook from a string."""
+ raise NotImplementedError("loads must be implemented in a subclass")
+
+ def read(self, fp, **kwargs):
+ """Read a notebook from a file like object"""
+ return self.read(fp.read(), **kwargs)
+
+
+class NotebookWriter(object):
+ """A class for writing notebooks."""
+
+ def writes(self, nb, **kwargs):
+ """Write a notebook to a string."""
+ raise NotImplementedError("loads must be implemented in a subclass")
+
+ def write(self, nb, fp, **kwargs):
+ """Write a notebook to a file like object"""
+ return fp.write(self.writes(nb,**kwargs))
+
+
+
diff --git a/IPython/nbformat/v3/tests/__init__.py b/IPython/nbformat/v3/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/IPython/nbformat/v3/tests/__init__.py
diff --git a/IPython/nbformat/v3/tests/nbexamples.py b/IPython/nbformat/v3/tests/nbexamples.py
new file mode 100644
index 0000000..6482fa7
--- /dev/null
+++ b/IPython/nbformat/v3/tests/nbexamples.py
@@ -0,0 +1,127 @@
+import os
+from base64 import encodestring
+
+from ..nbbase import (
+ NotebookNode,
+ new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output,
+ new_metadata, new_author, new_heading_cell
+)
+
+# some random base64-encoded *bytes*
+png = encodestring(os.urandom(5))
+jpeg = encodestring(os.urandom(6))
+
+ws = new_worksheet(name='worksheet1')
+
+ws.cells.append(new_text_cell(
+ u'html',
+ source='Some NumPy Examples',
+ rendered='Some NumPy Examples'
+))
+
+
+ws.cells.append(new_code_cell(
+ input='import numpy',
+ prompt_number=1,
+ collapsed=False
+))
+
+ws.cells.append(new_text_cell(
+ u'markdown',
+ source='A random array',
+ rendered='A random array'
+))
+
+ws.cells.append(new_text_cell(
+ u'plaintext',
+ source='A random array',
+))
+
+ws.cells.append(new_heading_cell(
+ u'My Heading',
+ level=2
+))
+
+ws.cells.append(new_code_cell(
+ input='a = numpy.random.rand(100)',
+ prompt_number=2,
+ collapsed=True
+))
+
+ws.cells.append(new_code_cell(
+ input='print a',
+ prompt_number=3,
+ collapsed=False,
+ outputs=[new_output(
+ output_type=u'pyout',
+ output_text=u'',
+ output_html=u'The HTML rep',
+ output_latex=u'$a$',
+ output_png=png,
+ output_jpeg=jpeg,
+ output_svg=u'