##// END OF EJS Templates
Merge pull request #4313 from minrk/remove-math-space...
Matthias Bussonnier -
r12910:68c851f3 merge
parent child Browse files
Show More
@@ -1,316 +1,315 b''
1 """This module defines Exporter, a highly configurable converter
1 """This module defines Exporter, 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
22 # other libs/dependencies
23 from jinja2 import Environment, FileSystemLoader, ChoiceLoader, TemplateNotFound
23 from jinja2 import Environment, FileSystemLoader, ChoiceLoader, TemplateNotFound
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.nbconvert import filters
30 from IPython.nbconvert import filters
31 from .exporter import Exporter
31 from .exporter import Exporter
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Globals and constants
34 # Globals and constants
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 #Jinja2 extensions to load.
37 #Jinja2 extensions to load.
38 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
38 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
39
39
40 default_filters = {
40 default_filters = {
41 'indent': text.indent,
41 'indent': text.indent,
42 'markdown2html': filters.markdown2html,
42 'markdown2html': filters.markdown2html,
43 'ansi2html': filters.ansi2html,
43 'ansi2html': filters.ansi2html,
44 'filter_data_type': filters.DataTypeFilter,
44 'filter_data_type': filters.DataTypeFilter,
45 'get_lines': filters.get_lines,
45 'get_lines': filters.get_lines,
46 'highlight2html': filters.highlight2html,
46 'highlight2html': filters.highlight2html,
47 'highlight2latex': filters.highlight2latex,
47 'highlight2latex': filters.highlight2latex,
48 'ipython2python': filters.ipython2python,
48 'ipython2python': filters.ipython2python,
49 'posix_path': filters.posix_path,
49 'posix_path': filters.posix_path,
50 'markdown2latex': filters.markdown2latex,
50 'markdown2latex': filters.markdown2latex,
51 'markdown2rst': filters.markdown2rst,
51 'markdown2rst': filters.markdown2rst,
52 'comment_lines': filters.comment_lines,
52 'comment_lines': filters.comment_lines,
53 'strip_ansi': filters.strip_ansi,
53 'strip_ansi': filters.strip_ansi,
54 'strip_dollars': filters.strip_dollars,
54 'strip_dollars': filters.strip_dollars,
55 'strip_files_prefix': filters.strip_files_prefix,
55 'strip_files_prefix': filters.strip_files_prefix,
56 'html2text' : filters.html2text,
56 'html2text' : filters.html2text,
57 'add_anchor': filters.add_anchor,
57 'add_anchor': filters.add_anchor,
58 'ansi2latex': filters.ansi2latex,
58 'ansi2latex': filters.ansi2latex,
59 'strip_math_space': filters.strip_math_space,
60 'wrap_text': filters.wrap_text,
59 'wrap_text': filters.wrap_text,
61 'escape_latex': filters.escape_latex,
60 'escape_latex': filters.escape_latex,
62 'citation2latex': filters.citation2latex,
61 'citation2latex': filters.citation2latex,
63 'path2url': filters.path2url,
62 'path2url': filters.path2url,
64 'add_prompts': filters.add_prompts,
63 'add_prompts': filters.add_prompts,
65 }
64 }
66
65
67 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
68 # Class
67 # Class
69 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
70
69
71 class TemplateExporter(Exporter):
70 class TemplateExporter(Exporter):
72 """
71 """
73 Exports notebooks into other file formats. Uses Jinja 2 templating engine
72 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
73 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/
74 template type along with new filters/preprocessors. If the filters/
76 preprocessors provided by default suffice, there is no need to inherit from
75 preprocessors provided by default suffice, there is no need to inherit from
77 this class. Instead, override the template_file and file_extension
76 this class. Instead, override the template_file and file_extension
78 traits via a config file.
77 traits via a config file.
79
78
80 {filters}
79 {filters}
81 """
80 """
82
81
83 # finish the docstring
82 # finish the docstring
84 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
83 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
85
84
86
85
87 template_file = Unicode(u'default',
86 template_file = Unicode(u'default',
88 config=True,
87 config=True,
89 help="Name of the template file to use")
88 help="Name of the template file to use")
90 def _template_file_changed(self, name, old, new):
89 def _template_file_changed(self, name, old, new):
91 if new == 'default':
90 if new == 'default':
92 self.template_file = self.default_template
91 self.template_file = self.default_template
93 else:
92 else:
94 self.template_file = new
93 self.template_file = new
95 self.template = None
94 self.template = None
96 self._load_template()
95 self._load_template()
97
96
98 default_template = Unicode(u'')
97 default_template = Unicode(u'')
99 template = Any()
98 template = Any()
100 environment = Any()
99 environment = Any()
101
100
102 template_path = List(['.'], config=True)
101 template_path = List(['.'], config=True)
103 def _template_path_changed(self, name, old, new):
102 def _template_path_changed(self, name, old, new):
104 self._load_template()
103 self._load_template()
105
104
106 default_template_path = Unicode(
105 default_template_path = Unicode(
107 os.path.join("..", "templates"),
106 os.path.join("..", "templates"),
108 help="Path where the template files are located.")
107 help="Path where the template files are located.")
109
108
110 template_skeleton_path = Unicode(
109 template_skeleton_path = Unicode(
111 os.path.join("..", "templates", "skeleton"),
110 os.path.join("..", "templates", "skeleton"),
112 help="Path where the template skeleton files are located.")
111 help="Path where the template skeleton files are located.")
113
112
114 #Jinja block definitions
113 #Jinja block definitions
115 jinja_comment_block_start = Unicode("", config=True)
114 jinja_comment_block_start = Unicode("", config=True)
116 jinja_comment_block_end = Unicode("", config=True)
115 jinja_comment_block_end = Unicode("", config=True)
117 jinja_variable_block_start = Unicode("", config=True)
116 jinja_variable_block_start = Unicode("", config=True)
118 jinja_variable_block_end = Unicode("", config=True)
117 jinja_variable_block_end = Unicode("", config=True)
119 jinja_logic_block_start = Unicode("", config=True)
118 jinja_logic_block_start = Unicode("", config=True)
120 jinja_logic_block_end = Unicode("", config=True)
119 jinja_logic_block_end = Unicode("", config=True)
121
120
122 #Extension that the template files use.
121 #Extension that the template files use.
123 template_extension = Unicode(".tpl", config=True)
122 template_extension = Unicode(".tpl", config=True)
124
123
125 filters = Dict(config=True,
124 filters = Dict(config=True,
126 help="""Dictionary of filters, by name and namespace, to add to the Jinja
125 help="""Dictionary of filters, by name and namespace, to add to the Jinja
127 environment.""")
126 environment.""")
128
127
129
128
130 def __init__(self, config=None, extra_loaders=None, **kw):
129 def __init__(self, config=None, extra_loaders=None, **kw):
131 """
130 """
132 Public constructor
131 Public constructor
133
132
134 Parameters
133 Parameters
135 ----------
134 ----------
136 config : config
135 config : config
137 User configuration instance.
136 User configuration instance.
138 extra_loaders : list[of Jinja Loaders]
137 extra_loaders : list[of Jinja Loaders]
139 ordered list of Jinja loader to find templates. Will be tried in order
138 ordered list of Jinja loader to find templates. Will be tried in order
140 before the default FileSystem ones.
139 before the default FileSystem ones.
141 template : str (optional, kw arg)
140 template : str (optional, kw arg)
142 Template to use when exporting.
141 Template to use when exporting.
143 """
142 """
144 if not config:
143 if not config:
145 config = self.default_config
144 config = self.default_config
146
145
147 super(Exporter, self).__init__(config=config, **kw)
146 super(Exporter, self).__init__(config=config, **kw)
148
147
149 #Init
148 #Init
150 self._init_template()
149 self._init_template()
151 self._init_environment(extra_loaders=extra_loaders)
150 self._init_environment(extra_loaders=extra_loaders)
152 self._init_preprocessors()
151 self._init_preprocessors()
153 self._init_filters()
152 self._init_filters()
154
153
155
154
156 def _load_template(self):
155 def _load_template(self):
157 """Load the Jinja template object from the template file
156 """Load the Jinja template object from the template file
158
157
159 This is a no-op if the template attribute is already defined,
158 This is a no-op if the template attribute is already defined,
160 or the Jinja environment is not setup yet.
159 or the Jinja environment is not setup yet.
161
160
162 This is triggered by various trait changes that would change the template.
161 This is triggered by various trait changes that would change the template.
163 """
162 """
164 if self.template is not None:
163 if self.template is not None:
165 return
164 return
166 # called too early, do nothing
165 # called too early, do nothing
167 if self.environment is None:
166 if self.environment is None:
168 return
167 return
169 # Try different template names during conversion. First try to load the
168 # Try different template names during conversion. First try to load the
170 # template by name with extension added, then try loading the template
169 # template by name with extension added, then try loading the template
171 # as if the name is explicitly specified, then try the name as a
170 # as if the name is explicitly specified, then try the name as a
172 # 'flavor', and lastly just try to load the template by module name.
171 # 'flavor', and lastly just try to load the template by module name.
173 module_name = self.__module__.rsplit('.', 1)[-1]
172 module_name = self.__module__.rsplit('.', 1)[-1]
174 try_names = []
173 try_names = []
175 if self.template_file:
174 if self.template_file:
176 try_names.extend([
175 try_names.extend([
177 self.template_file + self.template_extension,
176 self.template_file + self.template_extension,
178 self.template_file,
177 self.template_file,
179 module_name + '_' + self.template_file + self.template_extension,
178 module_name + '_' + self.template_file + self.template_extension,
180 ])
179 ])
181 try_names.append(module_name + self.template_extension)
180 try_names.append(module_name + self.template_extension)
182 for try_name in try_names:
181 for try_name in try_names:
183 self.log.debug("Attempting to load template %s", try_name)
182 self.log.debug("Attempting to load template %s", try_name)
184 try:
183 try:
185 self.template = self.environment.get_template(try_name)
184 self.template = self.environment.get_template(try_name)
186 except (TemplateNotFound, IOError):
185 except (TemplateNotFound, IOError):
187 pass
186 pass
188 except Exception as e:
187 except Exception as e:
189 self.log.warn("Unexpected exception loading template: %s", try_name, exc_info=True)
188 self.log.warn("Unexpected exception loading template: %s", try_name, exc_info=True)
190 else:
189 else:
191 self.log.info("Loaded template %s", try_name)
190 self.log.info("Loaded template %s", try_name)
192 break
191 break
193
192
194 def from_notebook_node(self, nb, resources=None, **kw):
193 def from_notebook_node(self, nb, resources=None, **kw):
195 """
194 """
196 Convert a notebook from a notebook node instance.
195 Convert a notebook from a notebook node instance.
197
196
198 Parameters
197 Parameters
199 ----------
198 ----------
200 nb : Notebook node
199 nb : Notebook node
201 resources : dict (**kw)
200 resources : dict (**kw)
202 of additional resources that can be accessed read/write by
201 of additional resources that can be accessed read/write by
203 preprocessors and filters.
202 preprocessors and filters.
204 """
203 """
205 nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw)
204 nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw)
206
205
207 self._load_template()
206 self._load_template()
208
207
209 if self.template is not None:
208 if self.template is not None:
210 output = self.template.render(nb=nb_copy, resources=resources)
209 output = self.template.render(nb=nb_copy, resources=resources)
211 else:
210 else:
212 raise IOError('template file "%s" could not be found' % self.template_file)
211 raise IOError('template file "%s" could not be found' % self.template_file)
213 return output, resources
212 return output, resources
214
213
215
214
216 def register_filter(self, name, jinja_filter):
215 def register_filter(self, name, jinja_filter):
217 """
216 """
218 Register a filter.
217 Register a filter.
219 A filter is a function that accepts and acts on one string.
218 A filter is a function that accepts and acts on one string.
220 The filters are accesible within the Jinja templating engine.
219 The filters are accesible within the Jinja templating engine.
221
220
222 Parameters
221 Parameters
223 ----------
222 ----------
224 name : str
223 name : str
225 name to give the filter in the Jinja engine
224 name to give the filter in the Jinja engine
226 filter : filter
225 filter : filter
227 """
226 """
228 if jinja_filter is None:
227 if jinja_filter is None:
229 raise TypeError('filter')
228 raise TypeError('filter')
230 isclass = isinstance(jinja_filter, type)
229 isclass = isinstance(jinja_filter, type)
231 constructed = not isclass
230 constructed = not isclass
232
231
233 #Handle filter's registration based on it's type
232 #Handle filter's registration based on it's type
234 if constructed and isinstance(jinja_filter, py3compat.string_types):
233 if constructed and isinstance(jinja_filter, py3compat.string_types):
235 #filter is a string, import the namespace and recursively call
234 #filter is a string, import the namespace and recursively call
236 #this register_filter method
235 #this register_filter method
237 filter_cls = import_item(jinja_filter)
236 filter_cls = import_item(jinja_filter)
238 return self.register_filter(name, filter_cls)
237 return self.register_filter(name, filter_cls)
239
238
240 if constructed and hasattr(jinja_filter, '__call__'):
239 if constructed and hasattr(jinja_filter, '__call__'):
241 #filter is a function, no need to construct it.
240 #filter is a function, no need to construct it.
242 self.environment.filters[name] = jinja_filter
241 self.environment.filters[name] = jinja_filter
243 return jinja_filter
242 return jinja_filter
244
243
245 elif isclass and isinstance(jinja_filter, MetaHasTraits):
244 elif isclass and isinstance(jinja_filter, MetaHasTraits):
246 #filter is configurable. Make sure to pass in new default for
245 #filter is configurable. Make sure to pass in new default for
247 #the enabled flag if one was specified.
246 #the enabled flag if one was specified.
248 filter_instance = jinja_filter(parent=self)
247 filter_instance = jinja_filter(parent=self)
249 self.register_filter(name, filter_instance )
248 self.register_filter(name, filter_instance )
250
249
251 elif isclass:
250 elif isclass:
252 #filter is not configurable, construct it
251 #filter is not configurable, construct it
253 filter_instance = jinja_filter()
252 filter_instance = jinja_filter()
254 self.register_filter(name, filter_instance)
253 self.register_filter(name, filter_instance)
255
254
256 else:
255 else:
257 #filter is an instance of something without a __call__
256 #filter is an instance of something without a __call__
258 #attribute.
257 #attribute.
259 raise TypeError('filter')
258 raise TypeError('filter')
260
259
261
260
262 def _init_template(self):
261 def _init_template(self):
263 """
262 """
264 Make sure a template name is specified. If one isn't specified, try to
263 Make sure a template name is specified. If one isn't specified, try to
265 build one from the information we know.
264 build one from the information we know.
266 """
265 """
267 self._template_file_changed('template_file', self.template_file, self.template_file)
266 self._template_file_changed('template_file', self.template_file, self.template_file)
268
267
269
268
270 def _init_environment(self, extra_loaders=None):
269 def _init_environment(self, extra_loaders=None):
271 """
270 """
272 Create the Jinja templating environment.
271 Create the Jinja templating environment.
273 """
272 """
274 here = os.path.dirname(os.path.realpath(__file__))
273 here = os.path.dirname(os.path.realpath(__file__))
275 loaders = []
274 loaders = []
276 if extra_loaders:
275 if extra_loaders:
277 loaders.extend(extra_loaders)
276 loaders.extend(extra_loaders)
278
277
279 paths = self.template_path
278 paths = self.template_path
280 paths.extend([os.path.join(here, self.default_template_path),
279 paths.extend([os.path.join(here, self.default_template_path),
281 os.path.join(here, self.template_skeleton_path)])
280 os.path.join(here, self.template_skeleton_path)])
282 loaders.append(FileSystemLoader(paths))
281 loaders.append(FileSystemLoader(paths))
283
282
284 self.environment = Environment(
283 self.environment = Environment(
285 loader= ChoiceLoader(loaders),
284 loader= ChoiceLoader(loaders),
286 extensions=JINJA_EXTENSIONS
285 extensions=JINJA_EXTENSIONS
287 )
286 )
288
287
289 #Set special Jinja2 syntax that will not conflict with latex.
288 #Set special Jinja2 syntax that will not conflict with latex.
290 if self.jinja_logic_block_start:
289 if self.jinja_logic_block_start:
291 self.environment.block_start_string = self.jinja_logic_block_start
290 self.environment.block_start_string = self.jinja_logic_block_start
292 if self.jinja_logic_block_end:
291 if self.jinja_logic_block_end:
293 self.environment.block_end_string = self.jinja_logic_block_end
292 self.environment.block_end_string = self.jinja_logic_block_end
294 if self.jinja_variable_block_start:
293 if self.jinja_variable_block_start:
295 self.environment.variable_start_string = self.jinja_variable_block_start
294 self.environment.variable_start_string = self.jinja_variable_block_start
296 if self.jinja_variable_block_end:
295 if self.jinja_variable_block_end:
297 self.environment.variable_end_string = self.jinja_variable_block_end
296 self.environment.variable_end_string = self.jinja_variable_block_end
298 if self.jinja_comment_block_start:
297 if self.jinja_comment_block_start:
299 self.environment.comment_start_string = self.jinja_comment_block_start
298 self.environment.comment_start_string = self.jinja_comment_block_start
300 if self.jinja_comment_block_end:
299 if self.jinja_comment_block_end:
301 self.environment.comment_end_string = self.jinja_comment_block_end
300 self.environment.comment_end_string = self.jinja_comment_block_end
302
301
303
302
304 def _init_filters(self):
303 def _init_filters(self):
305 """
304 """
306 Register all of the filters required for the exporter.
305 Register all of the filters required for the exporter.
307 """
306 """
308
307
309 #Add default filters to the Jinja2 environment
308 #Add default filters to the Jinja2 environment
310 for key, value in default_filters.items():
309 for key, value in default_filters.items():
311 self.register_filter(key, value)
310 self.register_filter(key, value)
312
311
313 #Load user filters. Overwrite existing filters if need be.
312 #Load user filters. Overwrite existing filters if need be.
314 if self.filters:
313 if self.filters:
315 for key, user_filter in self.filters.items():
314 for key, user_filter in self.filters.items():
316 self.register_filter(key, user_filter)
315 self.register_filter(key, user_filter)
@@ -1,123 +1,63 b''
1 """Latex filters.
1 """Latex filters.
2
2
3 Module of useful filters for processing Latex within Jinja latex templates.
3 Module of useful filters for processing Latex within Jinja latex templates.
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 import re
16 import re
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Globals and constants
19 # Globals and constants
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 LATEX_RE_SUBS = (
22 LATEX_RE_SUBS = (
23 (re.compile(r'\.\.\.+'), r'\\ldots'),
23 (re.compile(r'\.\.\.+'), r'\\ldots'),
24 )
24 )
25
25
26 # Latex substitutions for escaping latex.
26 # Latex substitutions for escaping latex.
27 # see: http://stackoverflow.com/questions/16259923/how-can-i-escape-latex-special-characters-inside-django-templates
27 # see: http://stackoverflow.com/questions/16259923/how-can-i-escape-latex-special-characters-inside-django-templates
28
28
29 LATEX_SUBS = {
29 LATEX_SUBS = {
30 '&': r'\&',
30 '&': r'\&',
31 '%': r'\%',
31 '%': r'\%',
32 '$': r'\$',
32 '$': r'\$',
33 '#': r'\#',
33 '#': r'\#',
34 '_': r'\_',
34 '_': r'\_',
35 '{': r'\{',
35 '{': r'\{',
36 '}': r'\}',
36 '}': r'\}',
37 '~': r'\textasciitilde{}',
37 '~': r'\textasciitilde{}',
38 '^': r'\^{}',
38 '^': r'\^{}',
39 '\\': r'\textbackslash{}',
39 '\\': r'\textbackslash{}',
40 }
40 }
41
41
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Functions
44 # Functions
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 __all__ = ['escape_latex',
47 __all__ = ['escape_latex']
48 'strip_math_space']
49
48
50 def escape_latex(text):
49 def escape_latex(text):
51 """
50 """
52 Escape characters that may conflict with latex.
51 Escape characters that may conflict with latex.
53
52
54 Parameters
53 Parameters
55 ----------
54 ----------
56 text : str
55 text : str
57 Text containing characters that may conflict with Latex
56 Text containing characters that may conflict with Latex
58 """
57 """
59 text = ''.join(LATEX_SUBS.get(c, c) for c in text)
58 text = ''.join(LATEX_SUBS.get(c, c) for c in text)
60 for pattern, replacement in LATEX_RE_SUBS:
59 for pattern, replacement in LATEX_RE_SUBS:
61 text = pattern.sub(replacement, text)
60 text = pattern.sub(replacement, text)
62
61
63 return text
62 return text
64
65
66 def strip_math_space(text):
67 """
68 Remove the space between latex math commands and enclosing $ symbols.
69 This filter is important because latex isn't as flexible as the notebook
70 front end when it comes to flagging math using ampersand symbols.
71
72 Parameters
73 ----------
74 text : str
75 Text to filter.
76 """
77
78 # First, scan through the markdown looking for $. If
79 # a $ symbol is found, without a preceding \, assume
80 # it is the start of a math block. UNLESS that $ is
81 # not followed by another within two math_lines.
82 math_regions = []
83 math_lines = 0
84 within_math = False
85 math_start_index = 0
86 ptext = ''
87 last_character = ""
88 skip = False
89 for index, char in enumerate(text):
90
91 #Make sure the character isn't preceeded by a backslash
92 if (char == "$" and last_character != "\\"):
93
94 # Close the math region if this is an ending $
95 if within_math:
96 within_math = False
97 skip = True
98 ptext = ptext+'$'+text[math_start_index+1:index].strip()+'$'
99 math_regions.append([math_start_index, index+1])
100 else:
101
102 # Start a new math region
103 within_math = True
104 math_start_index = index
105 math_lines = 0
106
107 # If we are in a math region, count the number of lines parsed.
108 # Cancel the math region if we find two line breaks!
109 elif char == "\n":
110 if within_math:
111 math_lines += 1
112 if math_lines > 1:
113 within_math = False
114 ptext = ptext+text[math_start_index:index]
115
63
116 # Remember the last character so we can easily watch
117 # for backslashes
118 last_character = char
119 if not within_math and not skip:
120 ptext = ptext+char
121 if skip:
122 skip = False
123 return ptext
@@ -1,66 +1,45 b''
1 """
1 """
2 Module with tests for Latex
2 Module with tests for Latex
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 ...tests.base import TestsBase
17 from ...tests.base import TestsBase
18 from ..latex import escape_latex, strip_math_space
18 from ..latex import escape_latex
19
19
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Class
22 # Class
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 class TestLatex(TestsBase):
25 class TestLatex(TestsBase):
26
26
27
27
28 def test_escape_latex(self):
28 def test_escape_latex(self):
29 """escape_latex test"""
29 """escape_latex test"""
30 tests = [
30 tests = [
31 (r'How are \you doing today?', r'How are \textbackslash{}you doing today?'),
31 (r'How are \you doing today?', r'How are \textbackslash{}you doing today?'),
32 (r'\escapechar=`\A\catcode`\|=0 |string|foo', r'\textbackslash{}escapechar=`\textbackslash{}A\textbackslash{}catcode`\textbackslash{}|=0 |string|foo'),
32 (r'\escapechar=`\A\catcode`\|=0 |string|foo', r'\textbackslash{}escapechar=`\textbackslash{}A\textbackslash{}catcode`\textbackslash{}|=0 |string|foo'),
33 (r'# $ % & ~ _ ^ \ { }', r'\# \$ \% \& \textasciitilde{} \_ \^{} \textbackslash{} \{ \}'),
33 (r'# $ % & ~ _ ^ \ { }', r'\# \$ \% \& \textasciitilde{} \_ \^{} \textbackslash{} \{ \}'),
34 ('...', r'\ldots'),
34 ('...', r'\ldots'),
35 ('','')]
35 ('','')]
36
36
37 for test in tests:
37 for test in tests:
38 self._try_escape_latex(test[0], test[1])
38 self._try_escape_latex(test[0], test[1])
39
39
40
40
41 def _try_escape_latex(self, test, result):
41 def _try_escape_latex(self, test, result):
42 """Try to remove latex from string"""
42 """Try to remove latex from string"""
43 self.assertEqual(escape_latex(test), result)
43 self.assertEqual(escape_latex(test), result)
44
44
45
45
46 def test_strip_math_space(self):
47 """strip_math_space test"""
48 tests = [
49 ('$e$','$e$'),
50 ('$ e $','$e$'),
51 ('xxx$e^i$yyy','xxx$e^i$yyy'),
52 ('xxx$ e^i $yyy','xxx$e^i$yyy'),
53 ('xxx$e^i $yyy','xxx$e^i$yyy'),
54 ('xxx$ e^i$yyy','xxx$e^i$yyy'),
55 ('\$ e $ e $','\$ e $e$'),
56 ('','')]
57
58 for test in tests:
59 self._try_strip_math_space(test[0], test[1])
60
61
62 def _try_strip_math_space(self, test, result):
63 """
64 Try to remove spaces between dollar symbols and contents correctly
65 """
66 self.assertEqual(strip_math_space(test), result)
@@ -1,74 +1,47 b''
1 """Module that allows latex output notebooks to be conditioned before
1 """Module that allows latex output notebooks to be conditioned before
2 they are converted.
2 they are converted.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
5 # Copyright (c) 2013, the IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 from __future__ import print_function, absolute_import
16 from __future__ import print_function, absolute_import
17 import os
18
17
19 # Third-party import, needed for Pygments latex definitions.
18 from .base import Preprocessor
20 from pygments.formatters import LatexFormatter
21
22 # ipy imports
23 from .base import (Preprocessor)
24 from IPython.nbconvert import filters
25
19
26 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
27 # Classes
21 # Classes
28 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
29
23
30 class LatexPreprocessor(Preprocessor):
24 class LatexPreprocessor(Preprocessor):
31 """
25 """Preprocessor for latex destined documents.
32 Converter for latex destined documents.
26
27 Mainly populates the `latex` key in the resources dict,
28 adding definitions for pygments highlight styles.
33 """
29 """
34
30
35 def preprocess(self, nb, resources):
31 def preprocess(self, nb, resources):
36 """
32 """Preprocessing to apply on each notebook.
37 Preprocessing to apply on each notebook.
38
33
39 Parameters
34 Parameters
40 ----------
35 ----------
41 nb : NotebookNode
36 nb : NotebookNode
42 Notebook being converted
37 Notebook being converted
43 resources : dictionary
38 resources : dictionary
44 Additional resources used in the conversion process. Allows
39 Additional resources used in the conversion process. Allows
45 preprocessors to pass variables into the Jinja engine.
40 preprocessors to pass variables into the Jinja engine.
46 """
41 """
47 # Generate Pygments definitions for Latex
42 # Generate Pygments definitions for Latex
48 resources["latex"] = {}
43 from pygments.formatters import LatexFormatter
49 resources["latex"]["pygments_definitions"] = LatexFormatter().get_style_defs()
50 return super(LatexPreprocessor, self).preprocess(nb, resources)
51
52
53 def preprocess_cell(self, cell, resources, index):
54 """
55 Apply a transformation on each cell,
56
57 Parameters
58 ----------
59 cell : NotebookNode cell
60 Notebook cell being processed
61 resources : dictionary
62 Additional resources used in the conversion process. Allows
63 preprocessors to pass variables into the Jinja engine.
64 index : int
65 Modified index of the cell being processed (see base.py)
66 """
67
44
68 #If the cell is a markdown cell, preprocess the ampersands used to
45 resources.setdefault("latex", {})
69 #remove the space between them and their contents. Latex will complain
46 resources["latex"].setdefault("pygments_definitions", LatexFormatter().get_style_defs())
70 #if spaces exist between the ampersands and the math content.
47 return nb, resources
71 #See filters.latex.rm_math_space for more information.
72 if hasattr(cell, "source") and cell.cell_type == "markdown":
73 cell.source = filters.strip_math_space(cell.source)
74 return cell, resources
@@ -1,51 +1,51 b''
1 """
1 """
2 Module with tests for the latex preprocessor
2 Module with tests for the latex preprocessor
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 .base import PreprocessorTestsBase
17 from .base import PreprocessorTestsBase
18 from ..latex import LatexPreprocessor
18 from ..latex import LatexPreprocessor
19
19
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Class
22 # Class
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 class TestLatex(PreprocessorTestsBase):
25 class TestLatex(PreprocessorTestsBase):
26 """Contains test functions for latex.py"""
26 """Contains test functions for latex.py"""
27
27
28
28
29 def build_preprocessor(self):
29 def build_preprocessor(self):
30 """Make an instance of a preprocessor"""
30 """Make an instance of a preprocessor"""
31 preprocessor = LatexPreprocessor()
31 preprocessor = LatexPreprocessor()
32 preprocessor.enabled = True
32 preprocessor.enabled = True
33 return preprocessor
33 return preprocessor
34
34
35 def test_constructor(self):
35 def test_constructor(self):
36 """Can a LatexPreprocessor be constructed?"""
36 """Can a LatexPreprocessor be constructed?"""
37 self.build_preprocessor()
37 self.build_preprocessor()
38
38
39
39
40 def test_output(self):
40 def test_output(self):
41 """Test the output of the LatexPreprocessor"""
41 """Test the output of the LatexPreprocessor"""
42 nb = self.build_notebook()
42 nb = self.build_notebook()
43 res = self.build_resources()
43 res = self.build_resources()
44 preprocessor = self.build_preprocessor()
44 preprocessor = self.build_preprocessor()
45 nb, res = preprocessor(nb, res)
45 nb, res = preprocessor(nb, res)
46
46
47 # Make sure the code cell wasn't modified.
47 # Make sure the code cell wasn't modified.
48 self.assertEqual(nb.worksheets[0].cells[0].input, '$ e $')
48 self.assertEqual(nb.worksheets[0].cells[0].input, '$ e $')
49
49
50 # Verify that the markdown cell was processed.
50 # Verify that the markdown cell wasn't processed.
51 self.assertEqual(nb.worksheets[0].cells[1].source, '$e$')
51 self.assertEqual(nb.worksheets[0].cells[1].source, '$ e $')
@@ -1,148 +1,148 b''
1 {%- extends 'display_priority.tpl' -%}
1 {%- extends 'display_priority.tpl' -%}
2
2
3
3
4 {% block codecell %}
4 {% block codecell %}
5 <div class="cell border-box-sizing code_cell vbox">
5 <div class="cell border-box-sizing code_cell vbox">
6 {{ super() }}
6 {{ super() }}
7 </div>
7 </div>
8 {%- endblock codecell %}
8 {%- endblock codecell %}
9
9
10 {% block input_group -%}
10 {% block input_group -%}
11 <div class="input hbox">
11 <div class="input hbox">
12 {{ super() }}
12 {{ super() }}
13 </div>
13 </div>
14 {% endblock input_group %}
14 {% endblock input_group %}
15
15
16 {% block output_group %}
16 {% block output_group %}
17 <div class="vbox output_wrapper">
17 <div class="vbox output_wrapper">
18 <div class="output vbox">
18 <div class="output vbox">
19 {{ super() }}
19 {{ super() }}
20 </div>
20 </div>
21 </div>
21 </div>
22 {% endblock output_group %}
22 {% endblock output_group %}
23
23
24 {% block in_prompt -%}
24 {% block in_prompt -%}
25 <div class="prompt input_prompt">
25 <div class="prompt input_prompt">
26 In&nbsp;[{{ cell.prompt_number }}]:
26 In&nbsp;[{{ cell.prompt_number }}]:
27 </div>
27 </div>
28 {%- endblock in_prompt %}
28 {%- endblock in_prompt %}
29
29
30 {#
30 {#
31 output_prompt doesn't do anything in HTML,
31 output_prompt doesn't do anything in HTML,
32 because there is a prompt div in each output area (see output block)
32 because there is a prompt div in each output area (see output block)
33 #}
33 #}
34 {% block output_prompt %}
34 {% block output_prompt %}
35 {% endblock output_prompt %}
35 {% endblock output_prompt %}
36
36
37 {% block input %}
37 {% block input %}
38 <div class="input_area box-flex1">
38 <div class="input_area box-flex1">
39 {{ cell.input | highlight2html(metadata=cell.metadata) }}
39 {{ cell.input | highlight2html(metadata=cell.metadata) }}
40 </div>
40 </div>
41 {%- endblock input %}
41 {%- endblock input %}
42
42
43 {% block output %}
43 {% block output %}
44 <div class="hbox output_area">
44 <div class="hbox output_area">
45 {%- if output.output_type == 'pyout' -%}
45 {%- if output.output_type == 'pyout' -%}
46 <div class="prompt output_prompt">
46 <div class="prompt output_prompt">
47 Out[{{ cell.prompt_number }}]:
47 Out[{{ cell.prompt_number }}]:
48 {%- else -%}
48 {%- else -%}
49 <div class="prompt">
49 <div class="prompt">
50 {%- endif -%}
50 {%- endif -%}
51 </div>
51 </div>
52 {{ super() }}
52 {{ super() }}
53 </div>
53 </div>
54 {% endblock output %}
54 {% endblock output %}
55
55
56 {% block markdowncell scoped %}
56 {% block markdowncell scoped %}
57 <div class="text_cell_render border-box-sizing rendered_html">
57 <div class="text_cell_render border-box-sizing rendered_html">
58 {{ cell.source | strip_math_space | markdown2html | strip_files_prefix }}
58 {{ cell.source | markdown2html | strip_files_prefix }}
59 </div>
59 </div>
60 {%- endblock markdowncell %}
60 {%- endblock markdowncell %}
61
61
62 {% block headingcell scoped %}
62 {% block headingcell scoped %}
63 <div class="text_cell_render border-box-sizing rendered_html">
63 <div class="text_cell_render border-box-sizing rendered_html">
64 {{ ("#" * cell.level + cell.source) | replace('\n', ' ') | strip_math_space | markdown2html | strip_files_prefix | add_anchor }}
64 {{ ("#" * cell.level + cell.source) | replace('\n', ' ') | markdown2html | strip_files_prefix | add_anchor }}
65 </div>
65 </div>
66 {% endblock headingcell %}
66 {% endblock headingcell %}
67
67
68 {% block rawcell scoped %}
68 {% block rawcell scoped %}
69 {{ cell.source }}
69 {{ cell.source }}
70 {% endblock rawcell %}
70 {% endblock rawcell %}
71
71
72 {% block unknowncell scoped %}
72 {% block unknowncell scoped %}
73 unknown type {{ cell.type }}
73 unknown type {{ cell.type }}
74 {% endblock unknowncell %}
74 {% endblock unknowncell %}
75
75
76 {% block pyout -%}
76 {% block pyout -%}
77 <div class="box-flex1 output_subarea output_pyout">
77 <div class="box-flex1 output_subarea output_pyout">
78 {% block data_priority scoped %}
78 {% block data_priority scoped %}
79 {{ super() }}
79 {{ super() }}
80 {% endblock %}
80 {% endblock %}
81 </div>
81 </div>
82 {%- endblock pyout %}
82 {%- endblock pyout %}
83
83
84 {% block stream_stdout -%}
84 {% block stream_stdout -%}
85 <div class="box-flex1 output_subarea output_stream output_stdout">
85 <div class="box-flex1 output_subarea output_stream output_stdout">
86 <pre>
86 <pre>
87 {{ output.text | ansi2html }}
87 {{ output.text | ansi2html }}
88 </pre>
88 </pre>
89 </div>
89 </div>
90 {%- endblock stream_stdout %}
90 {%- endblock stream_stdout %}
91
91
92 {% block stream_stderr -%}
92 {% block stream_stderr -%}
93 <div class="box-flex1 output_subarea output_stream output_stderr">
93 <div class="box-flex1 output_subarea output_stream output_stderr">
94 <pre>
94 <pre>
95 {{ output.text | ansi2html }}
95 {{ output.text | ansi2html }}
96 </pre>
96 </pre>
97 </div>
97 </div>
98 {%- endblock stream_stderr %}
98 {%- endblock stream_stderr %}
99
99
100 {% block data_svg -%}
100 {% block data_svg -%}
101 {{ output.svg }}
101 {{ output.svg }}
102 {%- endblock data_svg %}
102 {%- endblock data_svg %}
103
103
104 {% block data_html -%}
104 {% block data_html -%}
105 <div class="output_html rendered_html">
105 <div class="output_html rendered_html">
106 {{ output.html }}
106 {{ output.html }}
107 </div>
107 </div>
108 {%- endblock data_html %}
108 {%- endblock data_html %}
109
109
110 {% block data_png %}
110 {% block data_png %}
111 <img src="data:image/png;base64,{{ output.png }}">
111 <img src="data:image/png;base64,{{ output.png }}">
112 {%- endblock data_png %}
112 {%- endblock data_png %}
113
113
114 {% block data_jpg %}
114 {% block data_jpg %}
115 <img src="data:image/jpeg;base64,{{ output.jpeg }}">
115 <img src="data:image/jpeg;base64,{{ output.jpeg }}">
116 {%- endblock data_jpg %}
116 {%- endblock data_jpg %}
117
117
118 {% block data_latex %}
118 {% block data_latex %}
119 {{ output.latex }}
119 {{ output.latex }}
120 {%- endblock data_latex %}
120 {%- endblock data_latex %}
121
121
122 {% block pyerr -%}
122 {% block pyerr -%}
123 <div class="box-flex1 output_subarea output_pyerr">
123 <div class="box-flex1 output_subarea output_pyerr">
124 <pre>{{ super() }}</pre>
124 <pre>{{ super() }}</pre>
125 </div>
125 </div>
126 {%- endblock pyerr %}
126 {%- endblock pyerr %}
127
127
128 {%- block traceback_line %}
128 {%- block traceback_line %}
129 {{ line | ansi2html }}
129 {{ line | ansi2html }}
130 {%- endblock traceback_line %}
130 {%- endblock traceback_line %}
131
131
132 {%- block data_text %}
132 {%- block data_text %}
133 <pre>
133 <pre>
134 {{ output.text | ansi2html }}
134 {{ output.text | ansi2html }}
135 </pre>
135 </pre>
136 {%- endblock -%}
136 {%- endblock -%}
137
137
138 {%- block data_javascript %}
138 {%- block data_javascript %}
139 <script type="text/javascript">
139 <script type="text/javascript">
140 {{ output.javascript }}
140 {{ output.javascript }}
141 </script>
141 </script>
142 {%- endblock -%}
142 {%- endblock -%}
143
143
144 {%- block display_data scoped -%}
144 {%- block display_data scoped -%}
145 <div class="box-flex1 output_subarea output_display_data">
145 <div class="box-flex1 output_subarea output_display_data">
146 {{ super() }}
146 {{ super() }}
147 </div>
147 </div>
148 {%- endblock display_data -%} No newline at end of file
148 {%- endblock display_data -%}
General Comments 0
You need to be logged in to leave comments. Login now