##// END OF EJS Templates
Add a plain text cell renderer.
Anton I. Sipos -
Show More
@@ -1,247 +1,253 b''
1 1 #!/usr/bin/env python
2 2 """A really simple notebook to rst/html exporter.
3 3
4 4 Usage
5 5
6 6 ./nb2html.py file.ipynb
7 7
8 8 Produces 'file.rst' and 'file.html', along with auto-generated figure files
9 9 called nb_figure_NN.png.
10 10
11 11 """
12 12
13 13 import os
14 14 import subprocess
15 15 import sys
16 16
17 17 from IPython.nbformat import current as nbformat
18 18 from IPython.utils.text import wrap_paragraphs, indent
19 19
20 20
21 21 # Cell converters
22 22
23 23 def unknown_cell(cell):
24 24 """Default converter for cells of unknown type.
25 25 """
26 26
27 27 return rst_directive('.. warning:: Unknown cell') + \
28 28 [repr(cell)]
29 29
30 30
31 31 def rst_directive(directive, text=''):
32 32 out = [directive, '']
33 33 if text:
34 34 out.extend([indent(text), ''])
35 35 return out
36 36
37 37 # Converters for parts of a cell.
38 38
39 39 class ConversionException(Exception):
40 40 pass
41 41
42 42
43 43 class Converter(object):
44 44 default_encoding = 'utf-8'
45 45
46 46 def __init__(self, fname):
47 47 self.fname = fname
48 48 self.dirpath = os.path.dirname(fname)
49 49
50 50 @property
51 51 def extension(self):
52 52 raise ConversionException("""extension must be defined in Converter
53 53 subclass""")
54 54
55 55 def dispatch(self, cell_type):
56 56 """return cell_type dependent render method, for example render_code
57 57 """
58 58 return getattr(self, 'render_' + cell_type, unknown_cell)
59 59
60 60 def convert(self):
61 61 lines = []
62 62 for cell in self.nb.worksheets[0].cells:
63 63 conv_fn = self.dispatch(cell.cell_type)
64 64 lines.extend(conv_fn(cell))
65 65 lines.append('')
66 66 return '\n'.join(lines)
67 67
68 68 def render(self):
69 69 "read, convert, and save self.fname"
70 70 self.read()
71 71 self.output = self.convert()
72 72 return self.save()
73 73
74 74 def read(self):
75 75 "read and parse notebook into NotebookNode called self.nb"
76 76 with open(self.fname) as f:
77 77 self.nb = nbformat.read(f, 'json')
78 78
79 79 def save(self, fname=None, encoding=None):
80 80 "read and parse notebook into self.nb"
81 81 if fname is None:
82 82 fname = os.path.splitext(self.fname)[0] + '.' + self.extension
83 83 if encoding is None:
84 84 encoding = self.default_encoding
85 85 with open(fname, 'w') as f:
86 86 f.write(self.output.encode(encoding))
87 87 return fname
88 88
89 89 def render_heading(self, cell):
90 90 raise NotImplementedError
91 91
92 92 def render_code(self, cell):
93 93 raise NotImplementedError
94 94
95 95 def render_markdown(self, cell):
96 96 raise NotImplementedError
97 97
98 98 def render_pyout(self, cell):
99 99 raise NotImplementedError
100 100
101 101 def render_display_data(self, cell):
102 102 raise NotImplementedError
103 103
104 104 def render_stream(self, cell):
105 105 raise NotImplementedError
106 106
107 107
108 108 class ConverterRST(Converter):
109 109 extension = 'rst'
110 110 figures_counter = 0
111 111
112 112 def render_heading(self, cell):
113 113 """convert a heading cell to rst
114 114
115 115 Returns list."""
116 116 heading_level = {1: '=', 2: '-', 3: '`', 4: '\'', 5: '.', 6: '~'}
117 117 marker = heading_level[cell.level]
118 118 return ['{0}\n{1}\n'.format(cell.source, marker * len(cell.source))]
119 119
120 120 def render_code(self, cell):
121 121 """Convert a code cell to rst
122 122
123 123 Returns list."""
124 124
125 125 if not cell.input:
126 126 return []
127 127
128 128 lines = ['In[%s]:' % cell.prompt_number, '']
129 129 lines.extend(rst_directive('.. code:: python', cell.input))
130 130
131 131 for output in cell.outputs:
132 132 conv_fn = self.dispatch(output.output_type)
133 133 lines.extend(conv_fn(output))
134 134
135 135 return lines
136 136
137 137 def render_markdown(self, cell):
138 138 """convert a markdown cell to rst
139 139
140 140 Returns list."""
141 141 return [cell.source]
142 142
143 def render_plaintext(self, cell):
144 """convert plain text to rst
145
146 Returns list."""
147 return [cell.source]
148
143 149 def render_pyout(self, output):
144 150 """convert pyout part of a code cell to rst
145 151
146 152 Returns list."""
147 153
148 154 lines = ['Out[%s]:' % output.prompt_number, '']
149 155
150 156 # output is a dictionary like object with type as a key
151 157 if 'latex' in output:
152 158 lines.extend(rst_directive('.. math::', output.latex))
153 159
154 160 if 'text' in output:
155 161 lines.extend(rst_directive('.. parsed-literal::', output.text))
156 162
157 163 return lines
158 164
159 165 def render_display_data(self, output):
160 166 """convert display data from the output of a code cell to rst.
161 167
162 168 Returns list.
163 169 """
164 170 lines = []
165 171
166 172 if 'png' in output:
167 173 fname = 'nb_figure_%s.png' % self.figures_counter
168 174 fullname = os.path.join(self.dirpath, fname)
169 175 with open(fullname, 'w') as f:
170 176 f.write(output.png.decode('base64'))
171 177
172 178 self.figures_counter += 1
173 179 lines.append('.. image:: %s' % fname)
174 180 lines.append('')
175 181
176 182 return lines
177 183
178 184 def render_stream(self, output):
179 185 """convert stream part of a code cell to rst
180 186
181 187 Returns list."""
182 188
183 189 lines = []
184 190
185 191 if 'text' in output:
186 192 lines.extend(rst_directive('.. parsed-literal::', output.text))
187 193
188 194 return lines
189 195
190 196
191 197 def rst2simplehtml(fname):
192 198 """Convert a rst file to simplified html suitable for blogger.
193 199
194 200 This just runs rst2html with certain parameters to produce really simple
195 201 html and strips the document header, so the resulting file can be easily
196 202 pasted into a blogger edit window.
197 203 """
198 204
199 205 # This is the template for the rst2html call that produces the cleanest,
200 206 # simplest html I could find. This should help in making it easier to
201 207 # paste into the blogspot html window, though I'm still having problems
202 208 # with linebreaks there...
203 209 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
204 210 "--no-generator --no-datestamp --no-source-link "
205 211 "--no-toc-backlinks --no-section-numbering "
206 212 "--strip-comments ")
207 213
208 214 cmd = "%s %s" % (cmd_template, fname)
209 215 proc = subprocess.Popen(cmd,
210 216 stdout=subprocess.PIPE,
211 217 stderr=subprocess.PIPE,
212 218 shell=True)
213 219 html, stderr = proc.communicate()
214 220 if stderr:
215 221 raise IOError(stderr)
216 222
217 223 # Make an iterator so breaking out holds state. Our implementation of
218 224 # searching for the html body below is basically a trivial little state
219 225 # machine, so we need this.
220 226 walker = iter(html.splitlines())
221 227
222 228 # Find start of main text, break out to then print until we find end /div.
223 229 # This may only work if there's a real title defined so we get a 'div class'
224 230 # tag, I haven't really tried.
225 231 for line in walker:
226 232 if line.startswith('<body>'):
227 233 break
228 234
229 235 newfname = os.path.splitext(fname)[0] + '.html'
230 236 with open(newfname, 'w') as f:
231 237 for line in walker:
232 238 if line.startswith('</body>'):
233 239 break
234 240 f.write(line)
235 241 f.write('\n')
236 242
237 243 return newfname
238 244
239 245
240 246 def main(fname):
241 247 """Convert a notebook to html in one step"""
242 248 converter = ConverterRST(fname)
243 249 converter.render()
244 250
245 251
246 252 if __name__ == '__main__':
247 253 main(sys.argv[1])
General Comments 0
You need to be logged in to leave comments. Login now