Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
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...
commit badeab10d3254c484342a73467674f665261dfa8 1 parent 4e2d3af
markvoorhies authored
93 IPython/frontend/qt/console/console_widget.py
View
@@ -507,6 +507,89 @@ def print_(self, printer = None):
return
self._control.print_(printer)
+ def exportHtmlInline(self, parent = None):
Fernando Perez
fperez added a note

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 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):
54 IPython/frontend/qt/console/rich_ipython_widget.py
View
@@ -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"):
Fernando Perez
fperez added a note

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ """ 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>'
+
Fernando Perez

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.

Fernando Perez

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.

Please sign in to comment.
Something went wrong with that request. Please try again.