##// END OF EJS Templates
add ascii_only filter
MinRK -
Show More
@@ -1,322 +1,323 b''
1 """This module defines TemplateExporter, a highly configurable converter
1 """This module defines TemplateExporter, a highly configurable converter
2 that uses Jinja2 to export notebook files into different formats.
2 that uses Jinja2 to export notebook files into different formats.
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 from __future__ import print_function, absolute_import
17 from __future__ import print_function, absolute_import
18
18
19 # Stdlib imports
19 # Stdlib imports
20 import os
20 import os
21
21
22 # other libs/dependencies are imported at runtime
22 # other libs/dependencies are imported at runtime
23 # to move ImportErrors to runtime when the requirement is actually needed
23 # to move ImportErrors to runtime when the requirement is actually needed
24
24
25 # IPython imports
25 # IPython imports
26 from IPython.utils.traitlets import MetaHasTraits, Unicode, List, Dict, Any
26 from IPython.utils.traitlets import MetaHasTraits, Unicode, List, Dict, Any
27 from IPython.utils.importstring import import_item
27 from IPython.utils.importstring import import_item
28 from IPython.utils import py3compat, text
28 from IPython.utils import py3compat, text
29
29
30 from IPython.nbformat.current import docstring_nbformat_mod
30 from IPython.nbformat.current import docstring_nbformat_mod
31 from IPython.nbconvert import filters
31 from IPython.nbconvert import filters
32 from .exporter import Exporter
32 from .exporter import Exporter
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Globals and constants
35 # Globals and constants
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 #Jinja2 extensions to load.
38 #Jinja2 extensions to load.
39 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
39 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
40
40
41 default_filters = {
41 default_filters = {
42 'indent': text.indent,
42 'indent': text.indent,
43 'markdown2html': filters.markdown2html,
43 'markdown2html': filters.markdown2html,
44 'ansi2html': filters.ansi2html,
44 'ansi2html': filters.ansi2html,
45 'filter_data_type': filters.DataTypeFilter,
45 'filter_data_type': filters.DataTypeFilter,
46 'get_lines': filters.get_lines,
46 'get_lines': filters.get_lines,
47 'highlight2html': filters.Highlight2Html,
47 'highlight2html': filters.Highlight2Html,
48 'highlight2latex': filters.Highlight2Latex,
48 'highlight2latex': filters.Highlight2Latex,
49 'ipython2python': filters.ipython2python,
49 'ipython2python': filters.ipython2python,
50 'posix_path': filters.posix_path,
50 'posix_path': filters.posix_path,
51 'markdown2latex': filters.markdown2latex,
51 'markdown2latex': filters.markdown2latex,
52 'markdown2rst': filters.markdown2rst,
52 'markdown2rst': filters.markdown2rst,
53 'comment_lines': filters.comment_lines,
53 'comment_lines': filters.comment_lines,
54 'strip_ansi': filters.strip_ansi,
54 'strip_ansi': filters.strip_ansi,
55 'strip_dollars': filters.strip_dollars,
55 'strip_dollars': filters.strip_dollars,
56 'strip_files_prefix': filters.strip_files_prefix,
56 'strip_files_prefix': filters.strip_files_prefix,
57 'html2text' : filters.html2text,
57 'html2text' : filters.html2text,
58 'add_anchor': filters.add_anchor,
58 'add_anchor': filters.add_anchor,
59 'ansi2latex': filters.ansi2latex,
59 'ansi2latex': filters.ansi2latex,
60 'wrap_text': filters.wrap_text,
60 'wrap_text': filters.wrap_text,
61 'escape_latex': filters.escape_latex,
61 'escape_latex': filters.escape_latex,
62 'citation2latex': filters.citation2latex,
62 'citation2latex': filters.citation2latex,
63 'path2url': filters.path2url,
63 'path2url': filters.path2url,
64 'add_prompts': filters.add_prompts,
64 'add_prompts': filters.add_prompts,
65 'ascii_only': filters.ascii_only,
65 }
66 }
66
67
67 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
68 # Class
69 # Class
69 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
70
71
71 class TemplateExporter(Exporter):
72 class TemplateExporter(Exporter):
72 """
73 """
73 Exports notebooks into other file formats. Uses Jinja 2 templating engine
74 Exports notebooks into other file formats. Uses Jinja 2 templating engine
74 to output new formats. Inherit from this class if you are creating a new
75 to output new formats. Inherit from this class if you are creating a new
75 template type along with new filters/preprocessors. If the filters/
76 template type along with new filters/preprocessors. If the filters/
76 preprocessors provided by default suffice, there is no need to inherit from
77 preprocessors provided by default suffice, there is no need to inherit from
77 this class. Instead, override the template_file and file_extension
78 this class. Instead, override the template_file and file_extension
78 traits via a config file.
79 traits via a config file.
79
80
80 {filters}
81 {filters}
81 """
82 """
82
83
83 # finish the docstring
84 # finish the docstring
84 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
85 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
85
86
86
87
87 template_file = Unicode(u'default',
88 template_file = Unicode(u'default',
88 config=True,
89 config=True,
89 help="Name of the template file to use")
90 help="Name of the template file to use")
90 def _template_file_changed(self, name, old, new):
91 def _template_file_changed(self, name, old, new):
91 if new == 'default':
92 if new == 'default':
92 self.template_file = self.default_template
93 self.template_file = self.default_template
93 else:
94 else:
94 self.template_file = new
95 self.template_file = new
95 self.template = None
96 self.template = None
96 self._load_template()
97 self._load_template()
97
98
98 default_template = Unicode(u'')
99 default_template = Unicode(u'')
99 template = Any()
100 template = Any()
100 environment = Any()
101 environment = Any()
101
102
102 template_path = List(['.'], config=True)
103 template_path = List(['.'], config=True)
103 def _template_path_changed(self, name, old, new):
104 def _template_path_changed(self, name, old, new):
104 self._load_template()
105 self._load_template()
105
106
106 default_template_path = Unicode(
107 default_template_path = Unicode(
107 os.path.join("..", "templates"),
108 os.path.join("..", "templates"),
108 help="Path where the template files are located.")
109 help="Path where the template files are located.")
109
110
110 template_skeleton_path = Unicode(
111 template_skeleton_path = Unicode(
111 os.path.join("..", "templates", "skeleton"),
112 os.path.join("..", "templates", "skeleton"),
112 help="Path where the template skeleton files are located.")
113 help="Path where the template skeleton files are located.")
113
114
114 #Jinja block definitions
115 #Jinja block definitions
115 jinja_comment_block_start = Unicode("", config=True)
116 jinja_comment_block_start = Unicode("", config=True)
116 jinja_comment_block_end = Unicode("", config=True)
117 jinja_comment_block_end = Unicode("", config=True)
117 jinja_variable_block_start = Unicode("", config=True)
118 jinja_variable_block_start = Unicode("", config=True)
118 jinja_variable_block_end = Unicode("", config=True)
119 jinja_variable_block_end = Unicode("", config=True)
119 jinja_logic_block_start = Unicode("", config=True)
120 jinja_logic_block_start = Unicode("", config=True)
120 jinja_logic_block_end = Unicode("", config=True)
121 jinja_logic_block_end = Unicode("", config=True)
121
122
122 #Extension that the template files use.
123 #Extension that the template files use.
123 template_extension = Unicode(".tpl", config=True)
124 template_extension = Unicode(".tpl", config=True)
124
125
125 filters = Dict(config=True,
126 filters = Dict(config=True,
126 help="""Dictionary of filters, by name and namespace, to add to the Jinja
127 help="""Dictionary of filters, by name and namespace, to add to the Jinja
127 environment.""")
128 environment.""")
128
129
129 raw_mimetypes = List(config=True,
130 raw_mimetypes = List(config=True,
130 help="""formats of raw cells to be included in this Exporter's output."""
131 help="""formats of raw cells to be included in this Exporter's output."""
131 )
132 )
132 def _raw_mimetypes_default(self):
133 def _raw_mimetypes_default(self):
133 return [self.output_mimetype, '']
134 return [self.output_mimetype, '']
134
135
135
136
136 def __init__(self, config=None, extra_loaders=None, **kw):
137 def __init__(self, config=None, extra_loaders=None, **kw):
137 """
138 """
138 Public constructor
139 Public constructor
139
140
140 Parameters
141 Parameters
141 ----------
142 ----------
142 config : config
143 config : config
143 User configuration instance.
144 User configuration instance.
144 extra_loaders : list[of Jinja Loaders]
145 extra_loaders : list[of Jinja Loaders]
145 ordered list of Jinja loader to find templates. Will be tried in order
146 ordered list of Jinja loader to find templates. Will be tried in order
146 before the default FileSystem ones.
147 before the default FileSystem ones.
147 template : str (optional, kw arg)
148 template : str (optional, kw arg)
148 Template to use when exporting.
149 Template to use when exporting.
149 """
150 """
150 super(TemplateExporter, self).__init__(config=config, **kw)
151 super(TemplateExporter, self).__init__(config=config, **kw)
151
152
152 #Init
153 #Init
153 self._init_template()
154 self._init_template()
154 self._init_environment(extra_loaders=extra_loaders)
155 self._init_environment(extra_loaders=extra_loaders)
155 self._init_preprocessors()
156 self._init_preprocessors()
156 self._init_filters()
157 self._init_filters()
157
158
158
159
159 def _load_template(self):
160 def _load_template(self):
160 """Load the Jinja template object from the template file
161 """Load the Jinja template object from the template file
161
162
162 This is a no-op if the template attribute is already defined,
163 This is a no-op if the template attribute is already defined,
163 or the Jinja environment is not setup yet.
164 or the Jinja environment is not setup yet.
164
165
165 This is triggered by various trait changes that would change the template.
166 This is triggered by various trait changes that would change the template.
166 """
167 """
167 from jinja2 import TemplateNotFound
168 from jinja2 import TemplateNotFound
168
169
169 if self.template is not None:
170 if self.template is not None:
170 return
171 return
171 # called too early, do nothing
172 # called too early, do nothing
172 if self.environment is None:
173 if self.environment is None:
173 return
174 return
174 # Try different template names during conversion. First try to load the
175 # Try different template names during conversion. First try to load the
175 # template by name with extension added, then try loading the template
176 # template by name with extension added, then try loading the template
176 # as if the name is explicitly specified, then try the name as a
177 # as if the name is explicitly specified, then try the name as a
177 # 'flavor', and lastly just try to load the template by module name.
178 # 'flavor', and lastly just try to load the template by module name.
178 try_names = []
179 try_names = []
179 if self.template_file:
180 if self.template_file:
180 try_names.extend([
181 try_names.extend([
181 self.template_file + self.template_extension,
182 self.template_file + self.template_extension,
182 self.template_file,
183 self.template_file,
183 ])
184 ])
184 for try_name in try_names:
185 for try_name in try_names:
185 self.log.debug("Attempting to load template %s", try_name)
186 self.log.debug("Attempting to load template %s", try_name)
186 try:
187 try:
187 self.template = self.environment.get_template(try_name)
188 self.template = self.environment.get_template(try_name)
188 except (TemplateNotFound, IOError):
189 except (TemplateNotFound, IOError):
189 pass
190 pass
190 except Exception as e:
191 except Exception as e:
191 self.log.warn("Unexpected exception loading template: %s", try_name, exc_info=True)
192 self.log.warn("Unexpected exception loading template: %s", try_name, exc_info=True)
192 else:
193 else:
193 self.log.info("Loaded template %s", try_name)
194 self.log.info("Loaded template %s", try_name)
194 break
195 break
195
196
196 @docstring_nbformat_mod
197 @docstring_nbformat_mod
197 def from_notebook_node(self, nb, resources=None, **kw):
198 def from_notebook_node(self, nb, resources=None, **kw):
198 """
199 """
199 Convert a notebook from a notebook node instance.
200 Convert a notebook from a notebook node instance.
200
201
201 Parameters
202 Parameters
202 ----------
203 ----------
203 nb : :class:`~{nbformat_mod}.nbbase.NotebookNode`
204 nb : :class:`~{nbformat_mod}.nbbase.NotebookNode`
204 Notebook node
205 Notebook node
205 resources : dict
206 resources : dict
206 Additional resources that can be accessed read/write by
207 Additional resources that can be accessed read/write by
207 preprocessors and filters.
208 preprocessors and filters.
208 """
209 """
209 nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw)
210 nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw)
210 resources.setdefault('raw_mimetypes', self.raw_mimetypes)
211 resources.setdefault('raw_mimetypes', self.raw_mimetypes)
211
212
212 self._load_template()
213 self._load_template()
213
214
214 if self.template is not None:
215 if self.template is not None:
215 output = self.template.render(nb=nb_copy, resources=resources)
216 output = self.template.render(nb=nb_copy, resources=resources)
216 else:
217 else:
217 raise IOError('template file "%s" could not be found' % self.template_file)
218 raise IOError('template file "%s" could not be found' % self.template_file)
218 return output, resources
219 return output, resources
219
220
220
221
221 def register_filter(self, name, jinja_filter):
222 def register_filter(self, name, jinja_filter):
222 """
223 """
223 Register a filter.
224 Register a filter.
224 A filter is a function that accepts and acts on one string.
225 A filter is a function that accepts and acts on one string.
225 The filters are accesible within the Jinja templating engine.
226 The filters are accesible within the Jinja templating engine.
226
227
227 Parameters
228 Parameters
228 ----------
229 ----------
229 name : str
230 name : str
230 name to give the filter in the Jinja engine
231 name to give the filter in the Jinja engine
231 filter : filter
232 filter : filter
232 """
233 """
233 if jinja_filter is None:
234 if jinja_filter is None:
234 raise TypeError('filter')
235 raise TypeError('filter')
235 isclass = isinstance(jinja_filter, type)
236 isclass = isinstance(jinja_filter, type)
236 constructed = not isclass
237 constructed = not isclass
237
238
238 #Handle filter's registration based on it's type
239 #Handle filter's registration based on it's type
239 if constructed and isinstance(jinja_filter, py3compat.string_types):
240 if constructed and isinstance(jinja_filter, py3compat.string_types):
240 #filter is a string, import the namespace and recursively call
241 #filter is a string, import the namespace and recursively call
241 #this register_filter method
242 #this register_filter method
242 filter_cls = import_item(jinja_filter)
243 filter_cls = import_item(jinja_filter)
243 return self.register_filter(name, filter_cls)
244 return self.register_filter(name, filter_cls)
244
245
245 if constructed and hasattr(jinja_filter, '__call__'):
246 if constructed and hasattr(jinja_filter, '__call__'):
246 #filter is a function, no need to construct it.
247 #filter is a function, no need to construct it.
247 self.environment.filters[name] = jinja_filter
248 self.environment.filters[name] = jinja_filter
248 return jinja_filter
249 return jinja_filter
249
250
250 elif isclass and isinstance(jinja_filter, MetaHasTraits):
251 elif isclass and isinstance(jinja_filter, MetaHasTraits):
251 #filter is configurable. Make sure to pass in new default for
252 #filter is configurable. Make sure to pass in new default for
252 #the enabled flag if one was specified.
253 #the enabled flag if one was specified.
253 filter_instance = jinja_filter(parent=self)
254 filter_instance = jinja_filter(parent=self)
254 self.register_filter(name, filter_instance )
255 self.register_filter(name, filter_instance )
255
256
256 elif isclass:
257 elif isclass:
257 #filter is not configurable, construct it
258 #filter is not configurable, construct it
258 filter_instance = jinja_filter()
259 filter_instance = jinja_filter()
259 self.register_filter(name, filter_instance)
260 self.register_filter(name, filter_instance)
260
261
261 else:
262 else:
262 #filter is an instance of something without a __call__
263 #filter is an instance of something without a __call__
263 #attribute.
264 #attribute.
264 raise TypeError('filter')
265 raise TypeError('filter')
265
266
266
267
267 def _init_template(self):
268 def _init_template(self):
268 """
269 """
269 Make sure a template name is specified. If one isn't specified, try to
270 Make sure a template name is specified. If one isn't specified, try to
270 build one from the information we know.
271 build one from the information we know.
271 """
272 """
272 self._template_file_changed('template_file', self.template_file, self.template_file)
273 self._template_file_changed('template_file', self.template_file, self.template_file)
273
274
274
275
275 def _init_environment(self, extra_loaders=None):
276 def _init_environment(self, extra_loaders=None):
276 """
277 """
277 Create the Jinja templating environment.
278 Create the Jinja templating environment.
278 """
279 """
279 from jinja2 import Environment, ChoiceLoader, FileSystemLoader
280 from jinja2 import Environment, ChoiceLoader, FileSystemLoader
280 here = os.path.dirname(os.path.realpath(__file__))
281 here = os.path.dirname(os.path.realpath(__file__))
281 loaders = []
282 loaders = []
282 if extra_loaders:
283 if extra_loaders:
283 loaders.extend(extra_loaders)
284 loaders.extend(extra_loaders)
284
285
285 paths = self.template_path
286 paths = self.template_path
286 paths.extend([os.path.join(here, self.default_template_path),
287 paths.extend([os.path.join(here, self.default_template_path),
287 os.path.join(here, self.template_skeleton_path)])
288 os.path.join(here, self.template_skeleton_path)])
288 loaders.append(FileSystemLoader(paths))
289 loaders.append(FileSystemLoader(paths))
289
290
290 self.environment = Environment(
291 self.environment = Environment(
291 loader= ChoiceLoader(loaders),
292 loader= ChoiceLoader(loaders),
292 extensions=JINJA_EXTENSIONS
293 extensions=JINJA_EXTENSIONS
293 )
294 )
294
295
295 #Set special Jinja2 syntax that will not conflict with latex.
296 #Set special Jinja2 syntax that will not conflict with latex.
296 if self.jinja_logic_block_start:
297 if self.jinja_logic_block_start:
297 self.environment.block_start_string = self.jinja_logic_block_start
298 self.environment.block_start_string = self.jinja_logic_block_start
298 if self.jinja_logic_block_end:
299 if self.jinja_logic_block_end:
299 self.environment.block_end_string = self.jinja_logic_block_end
300 self.environment.block_end_string = self.jinja_logic_block_end
300 if self.jinja_variable_block_start:
301 if self.jinja_variable_block_start:
301 self.environment.variable_start_string = self.jinja_variable_block_start
302 self.environment.variable_start_string = self.jinja_variable_block_start
302 if self.jinja_variable_block_end:
303 if self.jinja_variable_block_end:
303 self.environment.variable_end_string = self.jinja_variable_block_end
304 self.environment.variable_end_string = self.jinja_variable_block_end
304 if self.jinja_comment_block_start:
305 if self.jinja_comment_block_start:
305 self.environment.comment_start_string = self.jinja_comment_block_start
306 self.environment.comment_start_string = self.jinja_comment_block_start
306 if self.jinja_comment_block_end:
307 if self.jinja_comment_block_end:
307 self.environment.comment_end_string = self.jinja_comment_block_end
308 self.environment.comment_end_string = self.jinja_comment_block_end
308
309
309
310
310 def _init_filters(self):
311 def _init_filters(self):
311 """
312 """
312 Register all of the filters required for the exporter.
313 Register all of the filters required for the exporter.
313 """
314 """
314
315
315 #Add default filters to the Jinja2 environment
316 #Add default filters to the Jinja2 environment
316 for key, value in default_filters.items():
317 for key, value in default_filters.items():
317 self.register_filter(key, value)
318 self.register_filter(key, value)
318
319
319 #Load user filters. Overwrite existing filters if need be.
320 #Load user filters. Overwrite existing filters if need be.
320 if self.filters:
321 if self.filters:
321 for key, user_filter in self.filters.items():
322 for key, user_filter in self.filters.items():
322 self.register_filter(key, user_filter)
323 self.register_filter(key, user_filter)
@@ -1,215 +1,221 b''
1 # coding: utf-8
1 # coding: utf-8
2 """String filters.
2 """String filters.
3
3
4 Contains a collection of useful string manipulation filters for use in Jinja
4 Contains a collection of useful string manipulation filters for use in Jinja
5 templates.
5 templates.
6 """
6 """
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (c) 2013, the IPython Development Team.
8 # Copyright (c) 2013, the IPython Development Team.
9 #
9 #
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11 #
11 #
12 # The full license is in the file COPYING.txt, distributed with this software.
12 # The full license is in the file COPYING.txt, distributed with this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import os
19 import os
20 import re
20 import re
21 import textwrap
21 import textwrap
22 try:
22 try:
23 from urllib.parse import quote # Py 3
23 from urllib.parse import quote # Py 3
24 except ImportError:
24 except ImportError:
25 from urllib2 import quote # Py 2
25 from urllib2 import quote # Py 2
26 from xml.etree import ElementTree
26 from xml.etree import ElementTree
27
27
28 from IPython.core.interactiveshell import InteractiveShell
28 from IPython.core.interactiveshell import InteractiveShell
29 from IPython.utils import py3compat
29 from IPython.utils import py3compat
30
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Functions
32 # Functions
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34
34
35 __all__ = [
35 __all__ = [
36 'wrap_text',
36 'wrap_text',
37 'html2text',
37 'html2text',
38 'add_anchor',
38 'add_anchor',
39 'strip_dollars',
39 'strip_dollars',
40 'strip_files_prefix',
40 'strip_files_prefix',
41 'comment_lines',
41 'comment_lines',
42 'get_lines',
42 'get_lines',
43 'ipython2python',
43 'ipython2python',
44 'posix_path',
44 'posix_path',
45 'path2url',
45 'path2url',
46 'add_prompts'
46 'add_prompts',
47 'ascii_only',
47 ]
48 ]
48
49
49
50
50 def wrap_text(text, width=100):
51 def wrap_text(text, width=100):
51 """
52 """
52 Intelligently wrap text.
53 Intelligently wrap text.
53 Wrap text without breaking words if possible.
54 Wrap text without breaking words if possible.
54
55
55 Parameters
56 Parameters
56 ----------
57 ----------
57 text : str
58 text : str
58 Text to wrap.
59 Text to wrap.
59 width : int, optional
60 width : int, optional
60 Number of characters to wrap to, default 100.
61 Number of characters to wrap to, default 100.
61 """
62 """
62
63
63 split_text = text.split('\n')
64 split_text = text.split('\n')
64 wrp = map(lambda x:textwrap.wrap(x,width), split_text)
65 wrp = map(lambda x:textwrap.wrap(x,width), split_text)
65 wrpd = map('\n'.join, wrp)
66 wrpd = map('\n'.join, wrp)
66 return '\n'.join(wrpd)
67 return '\n'.join(wrpd)
67
68
68
69
69 def html2text(element):
70 def html2text(element):
70 """extract inner text from html
71 """extract inner text from html
71
72
72 Analog of jQuery's $(element).text()
73 Analog of jQuery's $(element).text()
73 """
74 """
74 if isinstance(element, py3compat.string_types):
75 if isinstance(element, py3compat.string_types):
75 try:
76 try:
76 element = ElementTree.fromstring(element)
77 element = ElementTree.fromstring(element)
77 except Exception:
78 except Exception:
78 # failed to parse, just return it unmodified
79 # failed to parse, just return it unmodified
79 return element
80 return element
80
81
81 text = element.text or ""
82 text = element.text or ""
82 for child in element:
83 for child in element:
83 text += html2text(child)
84 text += html2text(child)
84 text += (element.tail or "")
85 text += (element.tail or "")
85 return text
86 return text
86
87
87
88
88 def add_anchor(html):
89 def add_anchor(html):
89 """Add an anchor-link to an html header tag
90 """Add an anchor-link to an html header tag
90
91
91 For use in heading cells
92 For use in heading cells
92 """
93 """
93 try:
94 try:
94 h = ElementTree.fromstring(py3compat.cast_bytes_py2(html, encoding='utf-8'))
95 h = ElementTree.fromstring(py3compat.cast_bytes_py2(html, encoding='utf-8'))
95 except Exception:
96 except Exception:
96 # failed to parse, just return it unmodified
97 # failed to parse, just return it unmodified
97 return html
98 return html
98 link = html2text(h).replace(' ', '-')
99 link = html2text(h).replace(' ', '-')
99 h.set('id', link)
100 h.set('id', link)
100 a = ElementTree.Element("a", {"class" : "anchor-link", "href" : "#" + link})
101 a = ElementTree.Element("a", {"class" : "anchor-link", "href" : "#" + link})
101 a.text = u'ΒΆ'
102 a.text = u'ΒΆ'
102 h.append(a)
103 h.append(a)
103
104
104 # Known issue of Python3.x, ElementTree.tostring() returns a byte string
105 # Known issue of Python3.x, ElementTree.tostring() returns a byte string
105 # instead of a text string. See issue http://bugs.python.org/issue10942
106 # instead of a text string. See issue http://bugs.python.org/issue10942
106 # Workaround is to make sure the bytes are casted to a string.
107 # Workaround is to make sure the bytes are casted to a string.
107 return py3compat.decode(ElementTree.tostring(h), 'utf-8')
108 return py3compat.decode(ElementTree.tostring(h), 'utf-8')
108
109
109
110
110 def add_prompts(code, first='>>> ', cont='... '):
111 def add_prompts(code, first='>>> ', cont='... '):
111 """Add prompts to code snippets"""
112 """Add prompts to code snippets"""
112 new_code = []
113 new_code = []
113 code_list = code.split('\n')
114 code_list = code.split('\n')
114 new_code.append(first + code_list[0])
115 new_code.append(first + code_list[0])
115 for line in code_list[1:]:
116 for line in code_list[1:]:
116 new_code.append(cont + line)
117 new_code.append(cont + line)
117 return '\n'.join(new_code)
118 return '\n'.join(new_code)
118
119
119
120
120 def strip_dollars(text):
121 def strip_dollars(text):
121 """
122 """
122 Remove all dollar symbols from text
123 Remove all dollar symbols from text
123
124
124 Parameters
125 Parameters
125 ----------
126 ----------
126 text : str
127 text : str
127 Text to remove dollars from
128 Text to remove dollars from
128 """
129 """
129
130
130 return text.strip('$')
131 return text.strip('$')
131
132
132
133
133 files_url_pattern = re.compile(r'(src|href)\=([\'"]?)/?files/')
134 files_url_pattern = re.compile(r'(src|href)\=([\'"]?)/?files/')
134 markdown_url_pattern = re.compile(r'(!?)\[(?P<caption>.*?)\]\(/?files/(?P<location>.*?)\)')
135 markdown_url_pattern = re.compile(r'(!?)\[(?P<caption>.*?)\]\(/?files/(?P<location>.*?)\)')
135
136
136 def strip_files_prefix(text):
137 def strip_files_prefix(text):
137 """
138 """
138 Fix all fake URLs that start with `files/`, stripping out the `files/` prefix.
139 Fix all fake URLs that start with `files/`, stripping out the `files/` prefix.
139 Applies to both urls (for html) and relative paths (for markdown paths).
140 Applies to both urls (for html) and relative paths (for markdown paths).
140
141
141 Parameters
142 Parameters
142 ----------
143 ----------
143 text : str
144 text : str
144 Text in which to replace 'src="files/real...' with 'src="real...'
145 Text in which to replace 'src="files/real...' with 'src="real...'
145 """
146 """
146 cleaned_text = files_url_pattern.sub(r"\1=\2", text)
147 cleaned_text = files_url_pattern.sub(r"\1=\2", text)
147 cleaned_text = markdown_url_pattern.sub(r'\1[\2](\3)', cleaned_text)
148 cleaned_text = markdown_url_pattern.sub(r'\1[\2](\3)', cleaned_text)
148 return cleaned_text
149 return cleaned_text
149
150
150
151
151 def comment_lines(text, prefix='# '):
152 def comment_lines(text, prefix='# '):
152 """
153 """
153 Build a Python comment line from input text.
154 Build a Python comment line from input text.
154
155
155 Parameters
156 Parameters
156 ----------
157 ----------
157 text : str
158 text : str
158 Text to comment out.
159 Text to comment out.
159 prefix : str
160 prefix : str
160 Character to append to the start of each line.
161 Character to append to the start of each line.
161 """
162 """
162
163
163 #Replace line breaks with line breaks and comment symbols.
164 #Replace line breaks with line breaks and comment symbols.
164 #Also add a comment symbol at the beginning to comment out
165 #Also add a comment symbol at the beginning to comment out
165 #the first line.
166 #the first line.
166 return prefix + ('\n'+prefix).join(text.split('\n'))
167 return prefix + ('\n'+prefix).join(text.split('\n'))
167
168
168
169
169 def get_lines(text, start=None,end=None):
170 def get_lines(text, start=None,end=None):
170 """
171 """
171 Split the input text into separate lines and then return the
172 Split the input text into separate lines and then return the
172 lines that the caller is interested in.
173 lines that the caller is interested in.
173
174
174 Parameters
175 Parameters
175 ----------
176 ----------
176 text : str
177 text : str
177 Text to parse lines from.
178 Text to parse lines from.
178 start : int, optional
179 start : int, optional
179 First line to grab from.
180 First line to grab from.
180 end : int, optional
181 end : int, optional
181 Last line to grab from.
182 Last line to grab from.
182 """
183 """
183
184
184 # Split the input into lines.
185 # Split the input into lines.
185 lines = text.split("\n")
186 lines = text.split("\n")
186
187
187 # Return the right lines.
188 # Return the right lines.
188 return "\n".join(lines[start:end]) #re-join
189 return "\n".join(lines[start:end]) #re-join
189
190
190 def ipython2python(code):
191 def ipython2python(code):
191 """Transform IPython syntax to pure Python syntax
192 """Transform IPython syntax to pure Python syntax
192
193
193 Parameters
194 Parameters
194 ----------
195 ----------
195
196
196 code : str
197 code : str
197 IPython code, to be transformed to pure Python
198 IPython code, to be transformed to pure Python
198 """
199 """
199 shell = InteractiveShell.instance()
200 shell = InteractiveShell.instance()
200 return shell.input_transformer_manager.transform_cell(code)
201 return shell.input_transformer_manager.transform_cell(code)
201
202
202 def posix_path(path):
203 def posix_path(path):
203 """Turn a path into posix-style path/to/etc
204 """Turn a path into posix-style path/to/etc
204
205
205 Mainly for use in latex on Windows,
206 Mainly for use in latex on Windows,
206 where native Windows paths are not allowed.
207 where native Windows paths are not allowed.
207 """
208 """
208 if os.path.sep != '/':
209 if os.path.sep != '/':
209 return path.replace(os.path.sep, '/')
210 return path.replace(os.path.sep, '/')
210 return path
211 return path
211
212
212 def path2url(path):
213 def path2url(path):
213 """Turn a file path into a URL"""
214 """Turn a file path into a URL"""
214 parts = path.split(os.path.sep)
215 parts = path.split(os.path.sep)
215 return '/'.join(quote(part) for part in parts)
216 return '/'.join(quote(part) for part in parts)
217
218 def ascii_only(s):
219 """ensure a string is ascii"""
220 s = py3compat.cast_unicode(s)
221 return s.encode('ascii', 'replace').decode('ascii') No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now