##// END OF EJS Templates
create configurable preprocessors
Matthias BUSSONNIER -
Show More
@@ -1,220 +1,227 b''
1 1 """Base classes for the notebook conversion pipeline.
2 2
3 3 This module defines Converter, from which all objects designed to implement
4 4 a conversion of IPython notebooks to some other format should inherit.
5 5 """
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (c) 2012, the IPython Development Team.
8 8 #
9 9 # Distributed under the terms of the Modified BSD License.
10 10 #
11 11 # The full license is in the file COPYING.txt, distributed with this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 from __future__ import print_function, absolute_import
19 from .transformers import extract_figure_transformer
20 19 import converters.transformers as trans
21 20 from converters.jinja_filters import (python_comment, indent,
22 21 rm_fake, remove_ansi, markdown, highlight,
23 22 ansi2html, markdown2latex, escape_tex)
24 23
25 24
25
26 26 # Stdlib imports
27 27 import io
28 28 import os
29 29 from IPython.utils import path
30 from IPython.utils.traitlets import MetaHasTraits
30 31
31 32 from jinja2 import Environment, FileSystemLoader
32 33 env = Environment(
33 34 loader=FileSystemLoader([
34 35 './templates/',
35 36 './templates/skeleton/',
36 37 ]),
37 38 extensions=['jinja2.ext.loopcontrols']
38 39 )
39 40
40 41 texenv = Environment(
41 42 loader=FileSystemLoader([
42 43 './templates/tex/',
43 44 './templates/skeleton/tex/',
44 45 ]),
45 46 extensions=['jinja2.ext.loopcontrols']
46 47 )
47 48
48 49 # IPython imports
49 50 from IPython.nbformat import current as nbformat
50 51 from IPython.config.configurable import Configurable
51 52 from IPython.utils.traitlets import ( Unicode, Any, List, Bool)
52 53
53 54 #-----------------------------------------------------------------------------
54 55 # Class declarations
55 56 #-----------------------------------------------------------------------------
56 57 class ConversionException(Exception):
57 58 pass
58 59
59 60
60 61 def header_body():
61 62 """Return the body of the header as a list of strings."""
62 63
63 64 from pygments.formatters import HtmlFormatter
64 65
65 66 header = []
66 67 static = os.path.join(path.get_ipython_package_dir(),
67 68 'frontend', 'html', 'notebook', 'static',
68 69 )
69 70 here = os.path.split(os.path.realpath(__file__))[0]
70 71 css = os.path.join(static, 'css')
71 72 for sheet in [
72 73 # do we need jquery and prettify?
73 74 # os.path.join(static, 'jquery', 'css', 'themes', 'base',
74 75 # 'jquery-ui.min.css'),
75 76 # os.path.join(static, 'prettify', 'prettify.css'),
76 77 os.path.join(css, 'boilerplate.css'),
77 78 os.path.join(css, 'fbm.css'),
78 79 os.path.join(css, 'notebook.css'),
79 80 os.path.join(css, 'renderedhtml.css'),
80 81 # our overrides:
81 82 os.path.join(here, '..', 'css', 'static_html.css'),
82 83 ]:
83 84
84 85 with io.open(sheet, encoding='utf-8') as f:
85 86 s = f.read()
86 87 header.append(s)
87 88
88 89 pygments_css = HtmlFormatter().get_style_defs('.highlight')
89 90 header.append(pygments_css)
90 91 return header
91 92
92 93
93 94
94 95
95 96
96 97 inlining = {}
97 98 inlining['css'] = header_body()
98 99
99 100
100 101
101 102 texenv.block_start_string = '((*'
102 103 texenv.block_end_string = '*))'
103 104 texenv.variable_start_string = '((('
104 105 texenv.variable_end_string = ')))'
105 106 texenv.comment_start_string = '((='
106 107 texenv.comment_end_string = '=))'
107 108 texenv.filters['escape_tex'] = escape_tex
108 109
109 110
110 111 class ConverterTemplate(Configurable):
111 112 """ A Jinja2 base converter templates"""
112 113
113 114 display_data_priority = List(['html', 'pdf', 'svg', 'latex', 'png', 'jpg', 'jpeg' , 'text'],
114 115 config=True,
115 116 help= """
116 117 An ordered list of prefered output type, the first
117 118 encounterd will usually be used when converting discarding
118 119 the others.
119 120 """
120 121 )
121 122
122 pre_transformer_order = List(['haspyout_transformer'],
123 pre_transformer_order = List(['haspyout_transformer', 'Foobar'],
123 124 config=True,
124 125 help= """
125 126 An ordered list of pre transformer to apply to the ipynb
126 127 file befor running through templates
127 128 """
128 129 )
129 130
130 131 extract_figures = Bool(False,
131 132 config=True,
132 133 help= """
133 134 wether to remove figure data from ipynb and store them in auxiliary
134 135 dictionnary
135 136 """
136 137 )
137 138
138 139 tex_environement = Bool(False,
139 140 config=True,
140 141 help=""" is this a tex environment or not """)
141 142
142 143 template_file = Unicode('',
143 144 config=True,
144 145 help=""" whetever """ )
145 146 #-------------------------------------------------------------------------
146 147 # Instance-level attributes that are set in the constructor for this
147 148 # class.
148 149 #-------------------------------------------------------------------------
149 150 infile = Any()
150 151
151 152
152 153 infile_dir = Unicode()
153 154
154 155 def filter_data_type(self, output):
155 156 for fmt in self.display_data_priority:
156 157 if fmt in output:
157 158 return [fmt]
158 159
159 160 def __init__(self, preprocessors=[], config=None, **kw):
160 161 """
161 162 config: the Configurable confg object to pass around
162 163
163 164 preprocessors: list of function to run on ipynb json data before conversion
164 165 to extract/inline file,
165 166
166 167 """
167 168 super(ConverterTemplate, self).__init__(config=config, **kw)
168 169 self.env = texenv if self.tex_environement else env
169 170 self.ext = '.tplx' if self.tex_environement else '.tpl'
170 171 self.nb = None
171 172 self.preprocessors = preprocessors
173
172 174 for name in self.pre_transformer_order:
173 self.preprocessors.append(getattr(trans, name))
175 tr = getattr(trans, name)
176 if isinstance(tr, MetaHasTraits):
177 tr = tr(config=config)
178 self.preprocessors.append(tr)
174 179 if self.extract_figures:
175 self.preprocessors.append(extract_figure_transformer)
180 self.preprocessors.append(trans.ExtractFigureTransformer(config=config))
176 181
177 182 self.env.filters['filter_data_type'] = self.filter_data_type
178 183 self.env.filters['pycomment'] = python_comment
179 184 self.env.filters['indent'] = indent
180 185 self.env.filters['rm_fake'] = rm_fake
181 186 self.env.filters['rm_ansi'] = remove_ansi
182 187 self.env.filters['markdown'] = markdown
183 188 self.env.filters['highlight'] = highlight
184 189 self.env.filters['ansi2html'] = ansi2html
185 190 self.env.filters['markdown2latex'] = markdown2latex
186 191
187 192 self.template = self.env.get_template(self.template_file+self.ext)
188 193
189 194
190 195 def process(self):
191 196 """
192 197 preprocess the notebook json for easier use with the templates.
193 198 will call all the `preprocessor`s in order before returning it.
194 199 """
195 200 nb = self.nb
196 201
197 202 # dict of 'resources' that could be made by the preprocessors
198 203 # like key/value data to extract files from ipynb like in latex conversion
199 204 resources = {}
200 205
201 206 for preprocessor in self.preprocessors:
202 207 nb, resources = preprocessor(nb, resources)
203 208
204 209 return nb, resources
205 210
206 211 def convert(self):
207 212 """ convert the ipynb file
208 213
209 214 return both the converted ipynb file and a dict containing potential
210 215 other resources
211 216 """
212 217 nb, resources = self.process()
213 218 return self.template.render(nb=nb, inlining=inlining), resources
214 219
215 220
216 221 def read(self, filename):
217 222 "read and parse notebook into NotebookNode called self.nb"
218 223 with io.open(filename) as f:
219 224 self.nb = nbformat.read(f, 'json')
220 225
226
227
@@ -1,64 +1,125 b''
1 1 """
2 2
3 3 """
4 4
5 from __future__ import print_function
6
7 from IPython.config.configurable import Configurable
8 from IPython.utils.traitlets import Unicode, Bool, Dict
9
10 class ConfigurableTransformers(Configurable):
11 """ A configurable transformer """
12
13 def __init__(self, config=None, **kw):
14 super(ConfigurableTransformers, self).__init__(config=config, **kw)
15
16 def __call__(self, nb, other):
17 try :
18 for worksheet in nb.worksheets :
19 for index, cell in enumerate(worksheet.cells):
20 worksheet.cells[index], other = self.cell_transform(cell, other, index)
21 return nb, other
22 except NotImplementedError as error :
23 raise NotImplementedError('should be implemented by subclass')
24
25 def cell_transform(self, cell, other, index):
26 """
27 Overwrite if you want to apply a transformation on each cell
28 """
29 raise NotImplementedError('should be implemented by subclass')
30
31
32 class Foobar(ConfigurableTransformers):
33 message = Unicode('-- nothing', config=True)
34
35
36 def cell_transform(self, cell, other, index):
37 return cell, other
38
39
5 40 def cell_preprocessor(function):
6 41 """ wrap a function to be executed on all cells of a notebook
7 42
8 43 wrapped function parameters :
9 44 cell : the cell
10 45 other : external resources
11 46 index : index of the cell
12 47 """
13 48 def wrappedfunc(nb, other):
14 49 for worksheet in nb.worksheets :
15 50 for index, cell in enumerate(worksheet.cells):
16 51 worksheet.cells[index], other = function(cell, other, index)
17 52 return nb, other
18 53 return wrappedfunc
19 54
20 55
21 56 @cell_preprocessor
22 57 def haspyout_transformer(cell, other, count):
23 58 """
24 59 Add a haspyout flag to cell that have it
25 60
26 61 Easier for templating, where you can't know in advance
27 62 wether to write the out prompt
28 63
29 64 """
30 65 cell.type = cell.cell_type
31 66 cell.haspyout = False
32 67 for out in cell.get('outputs', []):
33 68 if out.output_type == 'pyout':
34 69 cell.haspyout = True
35 70 break
36 71 return cell, other
37 72
38 73
39 74 # todo, make the key part configurable.
40 def _new_figure(data, fmt, count):
75
76 class ExtractFigureTransformer(ConfigurableTransformers):
77 enabled = Bool(False,
78 config=True,
79 help=""" If set to false, this transformer will be no-op """
80 )
81
82 extra_ext_map = Dict({},
83 config=True,
84 help="""extra map to override extension based on type.
85 Usefull for latex where svg will be converted to pdf before inclusion
86 """
87 )
88
89
90 #to do change this to .format {} syntax
91 key_tpl = Unicode('_fig_%02i.%s', config=True)
92
93 def _get_ext(self, ext):
94 if ext in self.extra_ext_map :
95 return self.extra_ext_map[ext]
96 return ext
97
98 def _new_figure(self, data, fmt, count):
41 99 """Create a new figure file in the given format.
42 100
43 101 Returns a path relative to the input file.
44 102 """
45 figname = '_fig_%02i.%s' % (count, fmt)
103 figname = self.key_tpl % (count, self._get_ext(fmt))
104 key = self.key_tpl % (count, fmt)
46 105
47 106 # Binary files are base64-encoded, SVG is already XML
48 107 if fmt in ('png', 'jpg', 'pdf'):
49 108 data = data.decode('base64')
50 109
51 return figname, data
110 return figname, key, data
52 111
53 @cell_preprocessor
54 def extract_figure_transformer(cell, other, count):
112
113 def cell_transform(self, cell, other, count):
114 if not self.enabled:
115 return cell, other
55 116 for i, out in enumerate(cell.get('outputs', [])):
56 117 for type in ['html', 'pdf', 'svg', 'latex', 'png', 'jpg', 'jpeg']:
57 118 if out.hasattr(type):
58 figname, data = _new_figure(out[type], type, count)
119 figname, key, data = self._new_figure(out[type], type, count)
59 120 cell.outputs[i][type] = figname
60 121 out['key_'+type] = figname
61 other[figname] = data
122 other[key] = data
62 123 count = count+1
63 124 return cell, other
64 125
@@ -1,5 +1,8 b''
1 1 c = get_config()
2 2
3
3 4 c.ConverterTemplate.extract_figures=False
4 5 c.ConverterTemplate.template_file='fullhtml'
5 6 c.ConverterTemplate.tex_environement=False
7
8 c.ExtractFigureTransformer.enabled = False
@@ -1,5 +1,7 b''
1 1 c = get_config()
2 2
3 c.ConverterTemplate.extract_figures=False
3 c.ConverterTemplate.extract_figures=True
4 4 c.ConverterTemplate.template_file='latex_base'
5 5 c.ConverterTemplate.tex_environement=True
6
7 c.ExtractFigureTransformer.extra_ext_map={'svg':'pdf'}
@@ -1,94 +1,94 b''
1 1 #!/usr/bin/env python
2 2 #-----------------------------------------------------------------------------
3 3 # Imports
4 4 #-----------------------------------------------------------------------------
5 5 from __future__ import print_function
6 6 import sys
7 7 import io
8 8 import os
9 9
10 10 from converters.template import *
11 11 from converters.template import ConverterTemplate
12 12 from converters.html import ConverterHTML
13 13 # From IPython
14 14
15 15 # All the stuff needed for the configurable things
16 16 from IPython.config.application import Application
17 17 from IPython.config.loader import ConfigFileNotFound
18 18 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, CaselessStrEnum
19 19
20 from converters.transformers import (ConfigurableTransformers,Foobar,ExtractFigureTransformer)
21
20 22
21 23 class NbconvertApp(Application):
22 24
23 25
24 26 def __init__(self, **kwargs):
25 27 super(NbconvertApp, self).__init__(**kwargs)
26 28 self.classes.insert(0,ConverterTemplate)
29 # register class here to have help with help all
30 self.classes.insert(0,ExtractFigureTransformer)
31 self.classes.insert(0,Foobar)
27 32 # ensure those are registerd
28 33
29 34 def load_config_file(self, profile_name):
30 35 try:
31 36 Application.load_config_file(
32 37 self,
33 38 profile_name+'.nbcv',
34 39 path=[os.path.join(os.getcwdu(),'profile')]
35 40 )
36 41 except ConfigFileNotFound:
37 42 self.log.warn("Config file for profile '%s' not found, giving up ",profile_name)
38 43 exit(1)
39 44
40 45
41 46 def initialize(self, argv=None):
42 47 self.parse_command_line(argv)
43 48 cl_config = self.config
44 49 profile_file = sys.argv[1]
45 50 self.load_config_file(profile_file)
46 51 self.update_config(cl_config)
47 52
48 53
49 54
50 55 def run(self):
51 56 """Convert a notebook to html in one step"""
52 57 template_file = (self.extra_args or [None])[0]
53 58 ipynb_file = (self.extra_args or [None])[1]
54 59
55 60 template_file = sys.argv[1]
56 61
57 if template_file.startswith('latex'):
58 tex_environement=True
59 else:
60 tex_environement=False
61
62 62 C = ConverterTemplate(tplfile=sys.argv[1],
63 63 config=self.config)
64 64 C.read(ipynb_file)
65 65
66 66 output,resources = C.convert()
67 67
68 68 print(output.encode('utf-8'))
69 69
70 70 keys = resources.keys()
71 71 if keys :
72 72 print('''
73 73 ====================== Keys in Resources ==================================
74 74 ''')
75 75 print(resources.keys())
76 76 print("""
77 77 ===========================================================================
78 78 you are responsible from writing those data do a file in the right place if
79 79 they need to be.
80 80 ===========================================================================
81 81 """)
82 82
83 83 def main():
84 84 """Convert a notebook to html in one step"""
85 85 app = NbconvertApp.instance()
86 86 app.initialize()
87 87 app.start()
88 88 app.run()
89 89 #-----------------------------------------------------------------------------
90 90 # Script main
91 91 #-----------------------------------------------------------------------------
92 92
93 93 if __name__ == '__main__':
94 94 main()
@@ -1,207 +1,214 b''
1 1 ((*- extends 'display_priority.tplx' -*))
2 2
3 3 ((* block in_prompt *))((* endblock in_prompt *))
4 4
5 5 ((* block output_prompt *))((* endblock output_prompt *))
6 6
7 7 ((* block codecell *))\begin{codecell}((( super() )))
8 8 \end{codecell}
9 9 ((* endblock *))
10 10
11 11 ((* block input *))
12 12 \begin{codeinput}
13 13 \begin{lstlisting}
14 14 ((( cell.input )))
15 15 \end{lstlisting}
16 16 \end{codeinput}
17 17 ((* endblock input *))
18 18
19 19
20 20 ((= Those Two are for error displaying
21 21 even if the first one seem to do nothing,
22 22 it introduces a new line
23 23
24 24 =))
25 25 ((* block pyerr *))((( super() )))
26 26 ((* endblock pyerr *))
27 27
28 28 ((* block traceback_line *))
29 29 ((( line |indent| rm_ansi )))((* endblock traceback_line *))
30 30 ((= .... =))
31 31
32 32
33 33 ((*- block output_group -*))
34 34 \begin{codeoutput}
35 35 ((( super() )))
36 36 \end{codeoutput}((* endblock *))
37 37
38 38 ((*- block data_png -*))
39 39 \begin{center}
40 \includegraphics[width=0.7\textwidth]{(((output.png)))}
40 \includegraphics[width=0.7\textwidth]{(((output.key_png)))}
41 \par
42 \end{center}
43 ((*- endblock -*))
44
45 ((*- block data_svg -*))
46 \begin{center}
47 \includegraphics[width=0.7\textwidth]{(((output.key_svg)))}
41 48 \par
42 49 \end{center}
43 50 ((*- endblock -*))
44 51
45 52 ((* block pyout *))
46 53 ((( output.text)))
47 54 ((* endblock pyout *))
48 55
49 56 ((* block data_text *))
50 57 \begin{verbatim}
51 58 ((( output.text )))
52 59 \end{verbatim}
53 60 ((* endblock *))
54 61
55 62 ((* block stream *))
56 63 \begin{verbatim}
57 64 ((( output.text)))
58 65 \end{verbatim}
59 66 ((* endblock stream *))
60 67
61 68
62 69
63 70
64 71 ((* block markdowncell scoped *))((( cell.source | markdown2latex )))
65 72 ((* endblock markdowncell *))
66 73
67 74 ((* block headingcell scoped *))
68 75 \section{((( cell.source)))}
69 76 ((* endblock headingcell *))
70 77
71 78 ((* block rawcell scoped *))
72 79 ((( cell.source | pycomment )))
73 80 ((* endblock rawcell *))
74 81
75 82 ((* block unknowncell scoped *))
76 83 unknown type (((cell.type)))
77 84 ((* endblock unknowncell *))
78 85
79 86
80 87
81 88 ((* block body *))\begin{document}
82 89 ((( super() )))
83 90 \end{document}
84 91 ((* endblock*))
85 92
86 93 ((* block header *))
87 94 %% This file was auto-generated by IPython.
88 95 %% Conversion from the original notebook file:
89 96 %% tests/ipynbref/Gun_Data.orig.ipynb
90 97 %%
91 98 \documentclass[11pt,english]{article}
92 99
93 100 %% This is the automatic preamble used by IPython. Note that it does *not*
94 101 %% include a documentclass declaration, that is added at runtime to the overall
95 102 %% document.
96 103
97 104 \usepackage{amsmath}
98 105 \usepackage{amssymb}
99 106 \usepackage{graphicx}
100 107 \usepackage{ucs}
101 108 \usepackage[utf8x]{inputenc}
102 109
103 110 % needed for markdown enumerations to work
104 111 \usepackage{enumerate}
105 112
106 113 % Slightly bigger margins than the latex defaults
107 114 \usepackage{geometry}
108 115 \geometry{verbose,tmargin=3cm,bmargin=3cm,lmargin=2.5cm,rmargin=2.5cm}
109 116
110 117 % Define a few colors for use in code, links and cell shading
111 118 \usepackage{color}
112 119 \definecolor{orange}{cmyk}{0,0.4,0.8,0.2}
113 120 \definecolor{darkorange}{rgb}{.71,0.21,0.01}
114 121 \definecolor{darkgreen}{rgb}{.12,.54,.11}
115 122 \definecolor{myteal}{rgb}{.26, .44, .56}
116 123 \definecolor{gray}{gray}{0.45}
117 124 \definecolor{lightgray}{gray}{.95}
118 125 \definecolor{mediumgray}{gray}{.8}
119 126 \definecolor{inputbackground}{rgb}{.95, .95, .85}
120 127 \definecolor{outputbackground}{rgb}{.95, .95, .95}
121 128 \definecolor{traceback}{rgb}{1, .95, .95}
122 129
123 130 % Framed environments for code cells (inputs, outputs, errors, ...). The
124 131 % various uses of \unskip (or not) at the end were fine-tuned by hand, so don't
125 132 % randomly change them unless you're sure of the effect it will have.
126 133 \usepackage{framed}
127 134
128 135 % remove extraneous vertical space in boxes
129 136 \setlength\fboxsep{0pt}
130 137
131 138 % codecell is the whole input+output set of blocks that a Code cell can
132 139 % generate.
133 140
134 141 % TODO: unfortunately, it seems that using a framed codecell environment breaks
135 142 % the ability of the frames inside of it to be broken across pages. This
136 143 % causes at least the problem of having lots of empty space at the bottom of
137 144 % pages as new frames are moved to the next page, and if a single frame is too
138 145 % long to fit on a page, will completely stop latex from compiling the
139 146 % document. So unless we figure out a solution to this, we'll have to instead
140 147 % leave the codecell env. as empty. I'm keeping the original codecell
141 148 % definition here (a thin vertical bar) for reference, in case we find a
142 149 % solution to the page break issue.
143 150
144 151 %% \newenvironment{codecell}{%
145 152 %% \def\FrameCommand{\color{mediumgray} \vrule width 1pt \hspace{5pt}}%
146 153 %% \MakeFramed{\vspace{-0.5em}}}
147 154 %% {\unskip\endMakeFramed}
148 155
149 156 % For now, make this a no-op...
150 157 \newenvironment{codecell}{}
151 158
152 159 \newenvironment{codeinput}{%
153 160 \def\FrameCommand{\colorbox{inputbackground}}%
154 161 \MakeFramed{\advance\hsize-\width \FrameRestore}}
155 162 {\unskip\endMakeFramed}
156 163
157 164 \newenvironment{codeoutput}{%
158 165 \def\FrameCommand{\colorbox{outputbackground}}%
159 166 \vspace{-1.4em}
160 167 \MakeFramed{\advance\hsize-\width \FrameRestore}}
161 168 {\unskip\medskip\endMakeFramed}
162 169
163 170 \newenvironment{traceback}{%
164 171 \def\FrameCommand{\colorbox{traceback}}%
165 172 \MakeFramed{\advance\hsize-\width \FrameRestore}}
166 173 {\endMakeFramed}
167 174
168 175 % Use and configure listings package for nicely formatted code
169 176 \usepackage{listingsutf8}
170 177 \lstset{
171 178 language=python,
172 179 inputencoding=utf8x,
173 180 extendedchars=\true,
174 181 aboveskip=\smallskipamount,
175 182 belowskip=\smallskipamount,
176 183 xleftmargin=2mm,
177 184 breaklines=true,
178 185 basicstyle=\small \ttfamily,
179 186 showstringspaces=false,
180 187 keywordstyle=\color{blue}\bfseries,
181 188 commentstyle=\color{myteal},
182 189 stringstyle=\color{darkgreen},
183 190 identifierstyle=\color{darkorange},
184 191 columns=fullflexible, % tighter character kerning, like verb
185 192 }
186 193
187 194 % The hyperref package gives us a pdf with properly built
188 195 % internal navigation ('pdf bookmarks' for the table of contents,
189 196 % internal cross-reference links, web links for URLs, etc.)
190 197 \usepackage{hyperref}
191 198 \hypersetup{
192 199 breaklinks=true, % so long urls are correctly broken across lines
193 200 colorlinks=true,
194 201 urlcolor=blue,
195 202 linkcolor=darkorange,
196 203 citecolor=darkgreen,
197 204 }
198 205
199 206 % hardcode size of all verbatim environments to be a bit smaller
200 207 \makeatletter
201 208 \g@addto@macro\@verbatim\small\topsep=0.5em\partopsep=0pt
202 209 \makeatother
203 210
204 211 % Prevent overflowing lines due to urls and other hard-to-break entities.
205 212 \sloppy
206 213
207 214 ((* endblock *))
General Comments 0
You need to be logged in to leave comments. Login now