##// END OF EJS Templates
add html_text and add_anchor filters...
MinRK -
Show More
@@ -1,346 +1,348 b''
1 1 """This module defines Exporter, a highly configurable converter
2 2 that uses Jinja2 to export notebook files into different formats.
3 3 """
4 4
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (c) 2013, the IPython Development Team.
7 7 #
8 8 # Distributed under the terms of the Modified BSD License.
9 9 #
10 10 # The full license is in the file COPYING.txt, distributed with this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 from __future__ import print_function, absolute_import
18 18
19 19 # Stdlib imports
20 20 import io
21 21 import os
22 22 import inspect
23 23 from copy import deepcopy
24 24
25 25 # other libs/dependencies
26 26 from jinja2 import Environment, FileSystemLoader, ChoiceLoader
27 27
28 28 # IPython imports
29 29 from IPython.config.configurable import Configurable
30 30 from IPython.config import Config
31 31 from IPython.nbformat import current as nbformat
32 32 from IPython.utils.traitlets import MetaHasTraits, Unicode
33 33 from IPython.utils.text import indent
34 34
35 35 from IPython.nbconvert import filters
36 36 from IPython.nbconvert import transformers
37 37
38 38 #-----------------------------------------------------------------------------
39 39 # Globals and constants
40 40 #-----------------------------------------------------------------------------
41 41
42 42 #Jinja2 extensions to load.
43 43 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
44 44
45 45 default_filters = {
46 46 'indent': indent,
47 47 'markdown': filters.markdown2html,
48 48 'ansi2html': filters.ansi2html,
49 49 'filter_data_type': filters.DataTypeFilter,
50 50 'get_lines': filters.get_lines,
51 51 'highlight': filters.highlight,
52 52 'highlight2html': filters.highlight,
53 53 'highlight2latex': filters.highlight2latex,
54 54 'markdown2latex': filters.markdown2latex,
55 55 'markdown2rst': filters.markdown2rst,
56 56 'pycomment': filters.python_comment,
57 57 'rm_ansi': filters.remove_ansi,
58 58 'rm_dollars': filters.strip_dollars,
59 59 'rm_fake': filters.rm_fake,
60 'html_text' : filters.html_text,
61 'add_anchor': filters.add_anchor,
60 62 'ansi2latex': filters.ansi2latex,
61 63 'rm_math_space': filters.rm_math_space,
62 64 'wrap': filters.wrap
63 65 }
64 66
65 67 #-----------------------------------------------------------------------------
66 68 # Class
67 69 #-----------------------------------------------------------------------------
68 70
69 71 class Exporter(Configurable):
70 72 """
71 73 Exports notebooks into other file formats. Uses Jinja 2 templating engine
72 74 to output new formats. Inherit from this class if you are creating a new
73 75 template type along with new filters/transformers. If the filters/
74 76 transformers provided by default suffice, there is no need to inherit from
75 77 this class. Instead, override the template_file and file_extension
76 78 traits via a config file.
77 79
78 80 {filters}
79 81 """
80 82
81 83 # finish the docstring
82 84 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
83 85
84 86
85 87 template_file = Unicode(
86 88 '', config=True,
87 89 help="Name of the template file to use")
88 90
89 91 file_extension = Unicode(
90 92 'txt', config=True,
91 93 help="Extension of the file that should be written to disk"
92 94 )
93 95
94 96 template_path = Unicode(
95 97 os.path.join("..", "templates"), config=True,
96 98 help="Path where the template files are located.")
97 99
98 100 template_skeleton_path = Unicode(
99 101 os.path.join("..", "templates", "skeleton"), config=True,
100 102 help="Path where the template skeleton files are located.")
101 103
102 104 #Jinja block definitions
103 105 jinja_comment_block_start = Unicode("", config=True)
104 106 jinja_comment_block_end = Unicode("", config=True)
105 107 jinja_variable_block_start = Unicode("", config=True)
106 108 jinja_variable_block_end = Unicode("", config=True)
107 109 jinja_logic_block_start = Unicode("", config=True)
108 110 jinja_logic_block_end = Unicode("", config=True)
109 111
110 112 #Extension that the template files use.
111 113 template_extension = Unicode(".tpl", config=True)
112 114
113 115 #Processors that process the input data prior to the export, set in the
114 116 #constructor for this class.
115 117 transformers = None
116 118
117 119
118 120 def __init__(self, transformers=None, filters=None, config=None, extra_loaders=None, **kw):
119 121 """
120 122 Public constructor
121 123
122 124 Parameters
123 125 ----------
124 126 transformers : list[of transformer]
125 127 Custom transformers to apply to the notebook prior to engaging
126 128 the Jinja template engine. Any transformers specified here
127 129 will override existing transformers if a naming conflict
128 130 occurs.
129 131 filters : dict[of filter]
130 132 filters specified here will override existing filters if a naming
131 133 conflict occurs. Filters are availlable in jinja template through
132 134 the name of the corresponding key. Cf class docstring for
133 135 availlable default filters.
134 136 config : config
135 137 User configuration instance.
136 138 extra_loaders : list[of Jinja Loaders]
137 139 ordered list of Jinja loder to find templates. Will be tried in order
138 140 before the default FileSysteme ones.
139 141 """
140 142
141 143 #Call the base class constructor
142 144 c = self.default_config
143 145 if config:
144 146 c.merge(config)
145 147
146 148 super(Exporter, self).__init__(config=c, **kw)
147 149
148 150 #Standard environment
149 151 self._init_environment(extra_loaders=extra_loaders)
150 152
151 153 #Add transformers
152 154 self._register_transformers()
153 155
154 156 #Add filters to the Jinja2 environment
155 157 self._register_filters()
156 158
157 159 #Load user transformers. Overwrite existing transformers if need be.
158 160 if transformers :
159 161 for transformer in transformers:
160 162 self.register_transformer(transformer)
161 163
162 164 #Load user filters. Overwrite existing filters if need be.
163 165 if not filters is None:
164 166 for key, user_filter in filters.iteritems():
165 167 self.register_filter(key, user_filter)
166 168
167 169 @property
168 170 def default_config(self):
169 171 return Config()
170 172
171 173
172 174
173 175 def from_notebook_node(self, nb, resources=None):
174 176 """
175 177 Convert a notebook from a notebook node instance.
176 178
177 179 Parameters
178 180 ----------
179 181 nb : Notebook node
180 182 resources : a dict of additional resources that
181 183 can be accessed read/write by transformers
182 184 and filters.
183 185 """
184 186 if resources is None:
185 187 resources = {}
186 188 nb, resources = self._preprocess(nb, resources)
187 189
188 190 #Load the template file.
189 191 self.template = self.environment.get_template(self.template_file+self.template_extension)
190 192
191 193 return self.template.render(nb=nb, resources=resources), resources
192 194
193 195
194 196 def from_filename(self, filename):
195 197 """
196 198 Convert a notebook from a notebook file.
197 199
198 200 Parameters
199 201 ----------
200 202 filename : str
201 203 Full filename of the notebook file to open and convert.
202 204 """
203 205
204 206 with io.open(filename) as f:
205 207 return self.from_notebook_node(nbformat.read(f, 'json'))
206 208
207 209
208 210 def from_file(self, file_stream):
209 211 """
210 212 Convert a notebook from a notebook file.
211 213
212 214 Parameters
213 215 ----------
214 216 file_stream : file-like object
215 217 Notebook file-like object to convert.
216 218 """
217 219 return self.from_notebook_node(nbformat.read(file_stream, 'json'))
218 220
219 221
220 222 def register_transformer(self, transformer):
221 223 """
222 224 Register a transformer.
223 225 Transformers are classes that act upon the notebook before it is
224 226 passed into the Jinja templating engine. Transformers are also
225 227 capable of passing additional information to the Jinja
226 228 templating engine.
227 229
228 230 Parameters
229 231 ----------
230 232 transformer : transformer
231 233 """
232 234 if self.transformers is None:
233 235 self.transformers = []
234 236
235 237 if inspect.isfunction(transformer):
236 238 self.transformers.append(transformer)
237 239 return transformer
238 240 elif isinstance(transformer, MetaHasTraits):
239 241 transformer_instance = transformer(config=self.config)
240 242 self.transformers.append(transformer_instance)
241 243 return transformer_instance
242 244 else:
243 245 transformer_instance = transformer()
244 246 self.transformers.append(transformer_instance)
245 247 return transformer_instance
246 248
247 249
248 250 def register_filter(self, name, filter):
249 251 """
250 252 Register a filter.
251 253 A filter is a function that accepts and acts on one string.
252 254 The filters are accesible within the Jinja templating engine.
253 255
254 256 Parameters
255 257 ----------
256 258 name : str
257 259 name to give the filter in the Jinja engine
258 260 filter : filter
259 261 """
260 262 if inspect.isfunction(filter):
261 263 self.environment.filters[name] = filter
262 264 elif isinstance(filter, MetaHasTraits):
263 265 self.environment.filters[name] = filter(config=self.config)
264 266 else:
265 267 self.environment.filters[name] = filter()
266 268 return self.environment.filters[name]
267 269
268 270
269 271 def _register_transformers(self):
270 272 """
271 273 Register all of the transformers needed for this exporter.
272 274 """
273 275
274 276 self.register_transformer(transformers.coalesce_streams)
275 277
276 278 #Remember the figure extraction transformer so it can be enabled and
277 279 #disabled easily later.
278 280 self.extract_figure_transformer = self.register_transformer(transformers.ExtractFigureTransformer)
279 281
280 282
281 283 def _register_filters(self):
282 284 """
283 285 Register all of the filters required for the exporter.
284 286 """
285 287 for k, v in default_filters.iteritems():
286 288 self.register_filter(k, v)
287 289
288 290
289 291 def _init_environment(self, extra_loaders=None):
290 292 """
291 293 Create the Jinja templating environment.
292 294 """
293 295 here = os.path.dirname(os.path.realpath(__file__))
294 296 loaders = []
295 297 if extra_loaders:
296 298 loaders.extend(extra_loaders)
297 299
298 300 loaders.append(FileSystemLoader([
299 301 os.path.join(here, self.template_path),
300 302 os.path.join(here, self.template_skeleton_path),
301 303 ]))
302 304
303 305 self.environment = Environment(
304 306 loader= ChoiceLoader(loaders),
305 307 extensions=JINJA_EXTENSIONS
306 308 )
307 309
308 310 #Set special Jinja2 syntax that will not conflict with latex.
309 311 if self.jinja_logic_block_start:
310 312 self.environment.block_start_string = self.jinja_logic_block_start
311 313 if self.jinja_logic_block_end:
312 314 self.environment.block_end_string = self.jinja_logic_block_end
313 315 if self.jinja_variable_block_start:
314 316 self.environment.variable_start_string = self.jinja_variable_block_start
315 317 if self.jinja_variable_block_end:
316 318 self.environment.variable_end_string = self.jinja_variable_block_end
317 319 if self.jinja_comment_block_start:
318 320 self.environment.comment_start_string = self.jinja_comment_block_start
319 321 if self.jinja_comment_block_end:
320 322 self.environment.comment_end_string = self.jinja_comment_block_end
321 323
322 324
323 325 def _preprocess(self, nb, resources):
324 326 """
325 327 Preprocess the notebook before passing it into the Jinja engine.
326 328 To preprocess the notebook is to apply all of the
327 329
328 330 Parameters
329 331 ----------
330 332 nb : notebook node
331 333 notebook that is being exported.
332 334 resources : a dict of additional resources that
333 335 can be accessed read/write by transformers
334 336 and filters.
335 337 """
336 338
337 339 # Do a deepcopy first,
338 340 # we are never safe enough with what the transformers could do.
339 341 nbc = deepcopy(nb)
340 342 resc = deepcopy(resources)
341 343 #Run each transformer on the notebook. Carry the output along
342 344 #to each transformer
343 345 for transformer in self.transformers:
344 346 nb, resources = transformer(nbc, resc)
345 347 return nb, resources
346 348
@@ -1,123 +1,151 b''
1 # coding: utf-8
1 2 """String filters.
2 3
3 4 Contains a collection of useful string manipulation filters for use in Jinja
4 5 templates.
5 6 """
6 7 #-----------------------------------------------------------------------------
7 8 # Copyright (c) 2013, the IPython Development Team.
8 9 #
9 10 # Distributed under the terms of the Modified BSD License.
10 11 #
11 12 # The full license is in the file COPYING.txt, distributed with this software.
12 13 #-----------------------------------------------------------------------------
13 14
14 15 #-----------------------------------------------------------------------------
15 16 # Imports
16 17 #-----------------------------------------------------------------------------
17 18
18 19 import re
19 20 import textwrap
21 from xml.etree import ElementTree
22
23 from IPython.utils import py3compat
20 24
21 25 #-----------------------------------------------------------------------------
22 26 # Functions
23 27 #-----------------------------------------------------------------------------
24 28
25 29 __all__ = [
26 30 'wrap',
31 'html_text',
32 'add_anchor',
27 33 'strip_dollars',
28 34 'rm_fake',
29 35 'python_comment',
30 36 'get_lines'
31 37 ]
32 38
33 39
34 40 def wrap(text, width=100):
35 41 """
36 42 Intelligently wrap text.
37 43 Wrap text without breaking words if possible.
38 44
39 45 Parameters
40 46 ----------
41 47 text : str
42 48 Text to wrap.
43 49 width : int, optional
44 50 Number of characters to wrap to, default 100.
45 51 """
46 52
47 53 split_text = text.split('\n')
48 54 wrp = map(lambda x:textwrap.wrap(x,width), split_text)
49 55 wrpd = map('\n'.join, wrp)
50 56 return '\n'.join(wrpd)
51 57
52 def single_line(text):
53 """Wrap multi-line text into a single line
58
59 def html_text(element):
60 """extract inner text from html
61
62 Analog of jQuery's $(element).text()
63 """
64 if not isinstance(element, (ElementTree.ElementTree, ElementTree.Element)):
65 element = ElementTree.fromstring(element)
66
67 text = element.text or ""
68 for child in element:
69 text += html_text(child)
70 text += (element.tail or "")
71 return text
72
73
74 def add_anchor(html):
75 """Add an anchor-link to an html header tag
54 76
55 Used in markdown heading cells, which are not allowed to be multiline.
77 For use in heading cells
56 78 """
57 return ''.join(text.splitlines())
79 h = ElementTree.fromstring(py3compat.cast_bytes_py2(html))
80 link = html_text(h).replace(' ', '-')
81 h.set('id', link)
82 a = ElementTree.Element("a", {"class" : "anchor-link", "href" : "#" + link})
83 a.text = u'ΒΆ'
84 h.append(a)
85 return ElementTree.tostring(h)
58 86
59 87
60 88 def strip_dollars(text):
61 89 """
62 90 Remove all dollar symbols from text
63 91
64 92 Parameters
65 93 ----------
66 94 text : str
67 95 Text to remove dollars from
68 96 """
69 97
70 98 return text.strip('$')
71 99
72 100
73 101 files_url_pattern = re.compile(r'(src|href)\=([\'"]?)files/')
74 102
75 103 def rm_fake(text):
76 104 """
77 105 Fix all fake URLs that start with `files/`,
78 106 stripping out the `files/` prefix.
79 107
80 108 Parameters
81 109 ----------
82 110 text : str
83 111 Text in which to replace 'src="files/real...' with 'src="real...'
84 112 """
85 113 return files_url_pattern.sub(r"\1=\2", text)
86 114
87 115
88 116 def python_comment(text):
89 117 """
90 118 Build a Python comment line from input text.
91 119
92 120 Parameters
93 121 ----------
94 122 text : str
95 123 Text to comment out.
96 124 """
97 125
98 126 #Replace line breaks with line breaks and comment symbols.
99 127 #Also add a comment symbol at the beginning to comment out
100 128 #the first line.
101 129 return '# '+'\n# '.join(text.split('\n'))
102 130
103 131
104 132 def get_lines(text, start=None,end=None):
105 133 """
106 134 Split the input text into separate lines and then return the
107 135 lines that the caller is interested in.
108 136
109 137 Parameters
110 138 ----------
111 139 text : str
112 140 Text to parse lines from.
113 141 start : int, optional
114 142 First line to grab from.
115 143 end : int, optional
116 144 Last line to grab from.
117 145 """
118 146
119 147 # Split the input into lines.
120 148 lines = text.split("\n")
121 149
122 150 # Return the right lines.
123 151 return "\n".join(lines[start:end]) #re-join
General Comments 0
You need to be logged in to leave comments. Login now