diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py
index 08620f1..7993ca8 100644
--- a/IPython/frontend/qt/console/console_widget.py
+++ b/IPython/frontend/qt/console/console_widget.py
@@ -507,6 +507,89 @@ class ConsoleWidget(Configurable, QtGui.QWidget):
return
self._control.print_(printer)
+ def exportHtmlInline(self, parent = None):
+ self.exportHtml(parent, inline = True)
+
+ def exportHtml(self, parent = None, inline = False):
+ """ Export the contents of the ConsoleWidget as an HTML file.
+
+ If inline == True, include images as inline PNGs. Otherwise,
+ include them as links to external PNG files, mimicking the
+ Firefox's "Web Page, complete" behavior.
+ """
+ dialog = QtGui.QFileDialog(parent, 'Save HTML Document')
+ dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
+ dialog.setDefaultSuffix('htm')
+ dialog.setNameFilter('HTML document (*.htm)')
+ if dialog.exec_():
+ filename = str(dialog.selectedFiles()[0])
+ if(inline):
+ path = None
+ else:
+ offset = filename.rfind(".")
+ if(offset > 0):
+ path = filename[:offset]+"_files"
+ else:
+ path = filename+"_files"
+ import os
+ try:
+ os.mkdir(path)
+ except OSError:
+ # TODO: check that this is an "already exists" error
+ pass
+
+ f = open(filename, 'w')
+ try:
+ # N.B. this is overly restrictive, but Qt's output is
+ # predictable...
+ img_re = re.compile(r'')
+ f.write(img_re.sub(
+ lambda x: self.imagetag(x, path = path, format = "PNG"),
+ str(self._control.toHtml().toUtf8())))
+ finally:
+ f.close()
+ return filename
+ return None
+
+ def exportXhtml(self, parent = None):
+ """ Export the contents of the ConsoleWidget as an XHTML file
+ with figures as inline SVG.
+ """
+ dialog = QtGui.QFileDialog(parent, 'Save XHTML Document')
+ dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
+ dialog.setDefaultSuffix('xml')
+ dialog.setNameFilter('XHTML document (*.xml)')
+ if dialog.exec_():
+ filename = str(dialog.selectedFiles()[0])
+ f = open(filename, 'w')
+ try:
+ # N.B. this is overly restrictive, but Qt's output is
+ # predictable...
+ img_re = re.compile(r'')
+ html = str(self._control.toHtml().toUtf8())
+ # Hack to make xhtml header -- note that we are not doing
+ # any check for valid xml
+ offset = html.find("")
+ assert(offset > -1)
+ html = ('\n'+
+ html[offset+6:])
+ f.write(img_re.sub(
+ lambda x: self.imagetag(x, path = None, format = "SVG"),
+ html))
+ finally:
+ f.close()
+ return filename
+ return None
+
+ def imagetag(self, match, path = None):
+ """ Given an re.match object matching an image name in an HTML export,
+ return an appropriate substitution string for the image tag
+ (e.g., link, embedded image, ...). As a side effect, files may
+ be generated in the directory given by path."""
+
+ # Default case -- not enough information to generate tag
+ return ""
+
def prompt_to_top(self):
""" Moves the prompt to the top of the viewport.
"""
@@ -744,7 +827,15 @@ class ConsoleWidget(Configurable, QtGui.QWidget):
menu.addSeparator()
print_action = menu.addAction('Print', self.print_)
print_action.setEnabled(True)
-
+ html_action = menu.addAction('Export HTML (external PNGs)',
+ self.exportHtml)
+ html_action.setEnabled(True)
+ html_inline_action = menu.addAction('Export HTML (inline PNGs)',
+ self.exportHtmlInline)
+ html_inline_action.setEnabled(True)
+ xhtml_action = menu.addAction('Export XHTML (inline SVGs)',
+ self.exportXhtml)
+ xhtml_action.setEnabled(True)
return menu
def _control_key_down(self, modifiers, include_command=True):
diff --git a/IPython/frontend/qt/console/rich_ipython_widget.py b/IPython/frontend/qt/console/rich_ipython_widget.py
index 5a74eb7..7b18733 100644
--- a/IPython/frontend/qt/console/rich_ipython_widget.py
+++ b/IPython/frontend/qt/console/rich_ipython_widget.py
@@ -25,6 +25,9 @@ class RichIPythonWidget(IPythonWidget):
"""
kw['kind'] = 'rich'
super(RichIPythonWidget, self).__init__(*args, **kw)
+ # Dictionary for resolving Qt names to images when
+ # generating XHTML output
+ self._name2svg = {}
#---------------------------------------------------------------------------
# 'ConsoleWidget' protected interface
@@ -68,6 +71,7 @@ class RichIPythonWidget(IPythonWidget):
self._append_plain_text('Received invalid plot data.')
else:
format = self._add_image(image)
+ self._name2svg[str(format.name())] = svg
format.setProperty(self._svg_text_format_property, svg)
cursor = self._get_end_cursor()
cursor.insertBlock()
@@ -121,3 +125,53 @@ class RichIPythonWidget(IPythonWidget):
filename = dialog.selectedFiles()[0]
image = self._get_image(name)
image.save(filename, format)
+
+ def imagetag(self, match, path = None, format = "PNG"):
+ """ Given an re.match object matching an image name in an HTML dump,
+ return an appropriate substitution string for the image tag
+ (e.g., link, embedded image, ...). As a side effect, files may
+ be generated in the directory given by path."""
+
+ if(format == "PNG"):
+ try:
+ image = self._get_image(match.group("name"))
+ except KeyError:
+ return "Couldn't find image %s" % match.group("name")
+
+ if(path is not None):
+ relpath = path[path.rfind("/")+1:]
+ if(image.save("%s/qt_img%s.png" % (path,match.group("name")),
+ "PNG")):
+ return '' % (relpath,
+ match.group("name"))
+ else:
+ return "Couldn't save image!"
+ else:
+ ba = QtCore.QByteArray()
+ buffer_ = QtCore.QBuffer(ba)
+ buffer_.open(QtCore.QIODevice.WriteOnly)
+ image.save(buffer_, "PNG")
+ buffer_.close()
+ import re
+ return '' % (
+ re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
+
+ elif(format == "SVG"):
+ try:
+ svg = str(self._name2svg[match.group("name")])
+ except KeyError:
+ return "Couldn't find image %s" % match.group("name")
+
+ # Not currently checking path, because it's tricky to find a
+ # cross-browser way to embed external SVG images (e.g., via
+ # object or embed tags).
+
+ # Chop stand-alone header from matplotlib SVG
+ offset = svg.find("