Permalink
Browse files

Add HTML export options

Three export options (HTML w/ external PNGs, HTML w/ inline PNGs,
and XHTML w/ inline SVGs) are added to the context menu.
  • Loading branch information...
1 parent 4e2d3af commit badeab10d3254c484342a73467674f665261dfa8 @markvoorhies committed Oct 11, 2010
Showing with 146 additions and 1 deletion.
  1. +92 −1 IPython/frontend/qt/console/console_widget.py
  2. +54 −0 IPython/frontend/qt/console/rich_ipython_widget.py
@@ -507,6 +507,89 @@ def print_(self, printer = None):
return
self._control.print_(printer)
+ def exportHtmlInline(self, parent = None):
@fperez

fperez Oct 11, 2010

Is this method inherited from the parent Qt widget? If not, it should be named_with_underscores. We use PEP-8 names everywhere, except when overriding parent methods whose name we don't control.

Same applies to the rest below, I won't repeat it.

+ 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'<img src="(?P<name>[\d]+)" />')
+ 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'<img src="(?P<name>[\d]+)" />')
+ 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("<html>")
+ assert(offset > -1)
+ html = ('<html xmlns="http://www.w3.org/1999/xhtml">\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 @@ def _context_menu_make(self, pos):
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):
@@ -25,6 +25,9 @@ def __init__(self, *args, **kw):
"""
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 @@ def _process_execute_payload(self, item):
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 @@ def _save_image(self, name, format='PNG'):
filename = dialog.selectedFiles()[0]
image = self._get_image(name)
image.save(filename, format)
+
+ def imagetag(self, match, path = None, format = "PNG"):
@fperez

fperez Oct 11, 2010

Name image_tag? And does the format need to be all uppercased? If it's case insensitive, I'd prefer using 'png' for the format spec.

+ """ 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 "<b>Couldn't find image %s</b>" % 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 '<img src="%s/qt_img%s.png">' % (relpath,
+ match.group("name"))
+ else:
+ return "<b>Couldn't save image!</b>"
+ else:
+ ba = QtCore.QByteArray()
+ buffer_ = QtCore.QBuffer(ba)
+ buffer_.open(QtCore.QIODevice.WriteOnly)
+ image.save(buffer_, "PNG")
+ buffer_.close()
+ import re
+ return '<img src="data:image/png;base64,\n%s\n" />' % (
+ re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
+
+ elif(format == "SVG"):
+ try:
+ svg = str(self._name2svg[match.group("name")])
+ except KeyError:
+ return "<b>Couldn't find image %s</b>" % 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("<svg")
+ assert(offset > -1)
+
+ return svg[offset:]
+
+ else:
+ return '<b>Unrecognized image format</b>'
+

0 comments on commit badeab1

Please sign in to comment.