##// END OF EJS Templates
Remove unnecessary import statement and fix naked assertion.
epatters -
Show More
@@ -1,237 +1,235 b''
1 1 """ Defines classes and functions for working with Qt's rich text system.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Imports
5 5 #-----------------------------------------------------------------------------
6 6
7 from __future__ import with_statement
8
9 7 # Standard library imports.
10 8 import os
11 9 import re
12 10
13 11 # System library imports.
14 12 from IPython.external.qt import QtGui
15 13
16 14 #-----------------------------------------------------------------------------
17 15 # Constants
18 16 #-----------------------------------------------------------------------------
19 17
20 18 # A regular expression for an HTML paragraph with no content.
21 19 EMPTY_P_RE = re.compile(r'<p[^/>]*>\s*</p>')
22 20
23 21 # A regular expression for matching images in rich text HTML.
24 22 # Note that this is overly restrictive, but Qt's output is predictable...
25 23 IMG_RE = re.compile(r'<img src="(?P<name>[\d]+)" />')
26 24
27 25 #-----------------------------------------------------------------------------
28 26 # Classes
29 27 #-----------------------------------------------------------------------------
30 28
31 29 class HtmlExporter(object):
32 30 """ A stateful HTML exporter for a Q(Plain)TextEdit.
33 31
34 32 This class is designed for convenient user interaction.
35 33 """
36 34
37 35 def __init__(self, control):
38 36 """ Creates an HtmlExporter for the given Q(Plain)TextEdit.
39 37 """
40 38 assert isinstance(control, (QtGui.QPlainTextEdit, QtGui.QTextEdit))
41 39 self.control = control
42 40 self.filename = 'ipython.html'
43 41 self.image_tag = None
44 42 self.inline_png = None
45 43
46 44 def export(self):
47 45 """ Displays a dialog for exporting HTML generated by Qt's rich text
48 46 system.
49 47
50 48 Returns
51 49 -------
52 50 The name of the file that was saved, or None if no file was saved.
53 51 """
54 52 parent = self.control.window()
55 53 dialog = QtGui.QFileDialog(parent, 'Save as...')
56 54 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
57 55 filters = [
58 56 'HTML with PNG figures (*.html *.htm)',
59 57 'XHTML with inline SVG figures (*.xhtml *.xml)'
60 58 ]
61 59 dialog.setNameFilters(filters)
62 60 if self.filename:
63 61 dialog.selectFile(self.filename)
64 62 root,ext = os.path.splitext(self.filename)
65 63 if ext.lower() in ('.xml', '.xhtml'):
66 64 dialog.selectNameFilter(filters[-1])
67 65
68 66 if dialog.exec_():
69 67 self.filename = dialog.selectedFiles()[0]
70 68 choice = dialog.selectedNameFilter()
71 69 html = self.control.document().toHtml().encode('utf-8')
72 70
73 71 # Configure the exporter.
74 72 if choice.startswith('XHTML'):
75 73 exporter = export_xhtml
76 74 else:
77 75 # If there are PNGs, decide how to export them.
78 76 inline = self.inline_png
79 77 if inline is None and IMG_RE.search(html):
80 78 dialog = QtGui.QDialog(parent)
81 79 dialog.setWindowTitle('Save as...')
82 80 layout = QtGui.QVBoxLayout(dialog)
83 81 msg = "Exporting HTML with PNGs"
84 82 info = "Would you like inline PNGs (single large html " \
85 83 "file) or external image files?"
86 84 checkbox = QtGui.QCheckBox("&Don't ask again")
87 85 checkbox.setShortcut('D')
88 86 ib = QtGui.QPushButton("&Inline")
89 87 ib.setShortcut('I')
90 88 eb = QtGui.QPushButton("&External")
91 89 eb.setShortcut('E')
92 90 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
93 91 dialog.windowTitle(), msg)
94 92 box.setInformativeText(info)
95 93 box.addButton(ib, QtGui.QMessageBox.NoRole)
96 94 box.addButton(eb, QtGui.QMessageBox.YesRole)
97 95 box.setDefaultButton(ib)
98 96 layout.setSpacing(0)
99 97 layout.addWidget(box)
100 98 layout.addWidget(checkbox)
101 99 dialog.setLayout(layout)
102 100 dialog.show()
103 101 reply = box.exec_()
104 102 dialog.hide()
105 103 inline = (reply == 0)
106 104 if checkbox.checkState():
107 105 # Don't ask anymore; always use this choice.
108 106 self.inline_png = inline
109 107 exporter = lambda h, f, i: export_html(h, f, i, inline)
110 108
111 109 # Perform the export!
112 110 try:
113 111 return exporter(html, self.filename, self.image_tag)
114 112 except Exception, e:
115 113 msg = "Error exporting HTML to %s\n" % self.filename + str(e)
116 114 reply = QtGui.QMessageBox.warning(parent, 'Error', msg,
117 115 QtGui.QMessageBox.Ok, QtGui.QMessageBox.Ok)
118 116
119 117 return None
120 118
121 119 #-----------------------------------------------------------------------------
122 120 # Functions
123 121 #-----------------------------------------------------------------------------
124 122
125 123 def export_html(html, filename, image_tag = None, inline = True):
126 124 """ Export the contents of the ConsoleWidget as HTML.
127 125
128 126 Parameters:
129 127 -----------
130 128 html : str,
131 129 A utf-8 encoded Python string containing the Qt HTML to export.
132 130
133 131 filename : str
134 132 The file to be saved.
135 133
136 134 image_tag : callable, optional (default None)
137 135 Used to convert images. See ``default_image_tag()`` for information.
138 136
139 137 inline : bool, optional [default True]
140 138 If True, include images as inline PNGs. Otherwise, include them as
141 139 links to external PNG files, mimicking web browsers' "Web Page,
142 140 Complete" behavior.
143 141 """
144 142 if image_tag is None:
145 143 image_tag = default_image_tag
146 144
147 145 if inline:
148 146 path = None
149 147 else:
150 148 root,ext = os.path.splitext(filename)
151 149 path = root + "_files"
152 150 if os.path.isfile(path):
153 151 raise OSError("%s exists, but is not a directory." % path)
154 152
155 153 with open(filename, 'w') as f:
156 154 html = fix_html(html)
157 155 f.write(IMG_RE.sub(lambda x: image_tag(x, path = path, format = "png"),
158 156 html))
159 157
160 158
161 159 def export_xhtml(html, filename, image_tag=None):
162 160 """ Export the contents of the ConsoleWidget as XHTML with inline SVGs.
163 161
164 162 Parameters:
165 163 -----------
166 164 html : str,
167 165 A utf-8 encoded Python string containing the Qt HTML to export.
168 166
169 167 filename : str
170 168 The file to be saved.
171 169
172 170 image_tag : callable, optional (default None)
173 171 Used to convert images. See ``default_image_tag()`` for information.
174 172 """
175 173 if image_tag is None:
176 174 image_tag = default_image_tag
177 175
178 176 with open(filename, 'w') as f:
179 177 # Hack to make xhtml header -- note that we are not doing any check for
180 178 # valid XML.
181 179 offset = html.find("<html>")
182 assert(offset > -1)
180 assert offset > -1, 'Invalid HTML string: no <html> tag.'
183 181 html = ('<html xmlns="http://www.w3.org/1999/xhtml">\n'+
184 182 html[offset+6:])
185 183
186 184 html = fix_html(html)
187 185 f.write(IMG_RE.sub(lambda x: image_tag(x, path = None, format = "svg"),
188 186 html))
189 187
190 188
191 189 def default_image_tag(match, path = None, format = "png"):
192 190 """ Return (X)HTML mark-up for the image-tag given by match.
193 191
194 192 This default implementation merely removes the image, and exists mostly
195 193 for documentation purposes. More information than is present in the Qt
196 194 HTML is required to supply the images.
197 195
198 196 Parameters
199 197 ----------
200 198 match : re.SRE_Match
201 199 A match to an HTML image tag as exported by Qt, with match.group("Name")
202 200 containing the matched image ID.
203 201
204 202 path : string|None, optional [default None]
205 203 If not None, specifies a path to which supporting files may be written
206 204 (e.g., for linked images). If None, all images are to be included
207 205 inline.
208 206
209 207 format : "png"|"svg", optional [default "png"]
210 208 Format for returned or referenced images.
211 209 """
212 210 return ''
213 211
214 212
215 213 def fix_html(html):
216 214 """ Transforms a Qt-generated HTML string into a standards-compliant one.
217 215
218 216 Parameters:
219 217 -----------
220 218 html : str,
221 219 A utf-8 encoded Python string containing the Qt HTML.
222 220 """
223 221 # A UTF-8 declaration is needed for proper rendering of some characters
224 222 # (e.g., indented commands) when viewing exported HTML on a local system
225 223 # (i.e., without seeing an encoding declaration in an HTTP header).
226 224 # C.f. http://www.w3.org/International/O-charset for details.
227 225 offset = html.find('<head>')
228 226 if offset > -1:
229 227 html = (html[:offset+6]+
230 228 '\n<meta http-equiv="Content-Type" '+
231 229 'content="text/html; charset=utf-8" />\n'+
232 230 html[offset+6:])
233 231
234 232 # Replace empty paragraphs tags with line breaks.
235 233 html = re.sub(EMPTY_P_RE, '<br/>', html)
236 234
237 235 return html
General Comments 0
You need to be logged in to leave comments. Login now