2 # -*- coding: utf-8 -*-
4 # math2html: convert LaTeX equations to HTML output.
6 # Copyright (C) 2009-2011 Alex Fernández
8 # Released under the terms of the `2-Clause BSD license'_, in short:
9 # Copying and distribution of this file, with or without modification,
10 # are permitted in any medium without royalty provided the copyright
11 # notice and this notice are preserved.
12 # This file is offered as-is, without any warranty.
14 # .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause
16 # Based on eLyXer: convert LyX source files to HTML output.
17 # http://elyxer.nongnu.org/
21 # eLyXer standalone formula conversion to HTML.
28 if sys
.version_info
< (2,4):
29 def reversed(sequence
):
45 def debug(cls
, message
):
46 "Show a debug message"
47 if not Trace
.debugmode
or Trace
.quietmode
:
49 Trace
.show(message
, sys
.stdout
)
51 def message(cls
, message
):
52 "Show a trace message"
55 if Trace
.prefix
and Trace
.showlinesmode
:
56 message
= Trace
.prefix
+ message
57 Trace
.show(message
, sys
.stdout
)
59 def error(cls
, message
):
60 "Show an error message"
61 message
= '* ' + message
62 if Trace
.prefix
and Trace
.showlinesmode
:
63 message
= Trace
.prefix
+ message
64 Trace
.show(message
, sys
.stderr
)
66 def fatal(cls
, message
):
67 "Show an error message and terminate"
68 Trace
.error('FATAL: ' + message
)
71 def show(cls
, message
, channel
):
72 "Show a message out of a channel"
73 if sys
.version_info
< (3,0):
74 message
= message
.encode('utf-8')
75 channel
.write(message
+ '\n')
77 debug
= classmethod(debug
)
78 message
= classmethod(message
)
79 error
= classmethod(error
)
80 fatal
= classmethod(fatal
)
81 show
= classmethod(show
)
90 class BibStylesConfig(object):
91 "Configuration class from elyxer.config file"
95 u
'@article':u
'$authors. $title. <i>$journal</i>,{ {$volume:}$pages,} $month $year.{ doi: $doi.}{ URL <a href="$url">$url</a>.}{ $note.}',
96 u
'cite':u
'$surname($year)',
97 u
'default':u
'$authors. <i>$title</i>. $publisher, $year.{ URL <a href="$url">$url</a>.}{ $note.}',
102 u
'@article':u
'$authors. $title.{ <i>$journal</i>{, {$volume}{($number)}}{: $pages}{, $year}.}{ <a href="$url">$url</a>.}{ <a href="$filename">$filename</a>.}{ $note.}',
104 u
'default':u
'$authors. $title.{ <i>$journal</i>,} $year.{ <a href="$url">$url</a>.}{ <a href="$filename">$filename</a>.}{ $note.}',
109 u
'@article':u
'$authors. $year. $title. <i>$journal</i>, <b>$volume</b>($number), $pages.{ URL <a href="$url">$url</a>.}{ $note.}',
110 u
'@book':u
'$authors. $year. <i>$title</i>. $publisher.{ URL <a href="$url">$url</a>.}{ $note.}',
111 u
'cite':u
'$surname, $year',
112 u
'default':u
'$authors. $year. <i>$title</i>. $publisher.{ URL <a href="$url">$url</a>.}{ $note.}',
117 u
'@article':u
'$authors: “$title”, <i>$journal</i>,{ pp. $pages,} $year.{ URL <a href="$url">$url</a>.}{ $note.}',
118 u
'@book':u
'{$authors: }<i>$title</i>{ ($editor, ed.)}.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}',
119 u
'@booklet':u
'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}',
120 u
'@conference':u
'$authors: “$title”, <i>$journal</i>,{ pp. $pages,} $year.{ URL <a href="$url">$url</a>.}{ $note.}',
121 u
'@inbook':u
'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}',
122 u
'@incollection':u
'$authors: <i>$title</i>{ in <i>$booktitle</i>{ ($editor, ed.)}}.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}',
123 u
'@inproceedings':u
'$authors: “$title”, <i>$journal</i>,{ pp. $pages,} $year.{ URL <a href="$url">$url</a>.}{ $note.}',
124 u
'@manual':u
'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}',
125 u
'@mastersthesis':u
'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}',
126 u
'@misc':u
'$authors: <i>$title</i>.{{ $publisher,}{ $howpublished,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}',
127 u
'@phdthesis':u
'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}',
128 u
'@proceedings':u
'$authors: “$title”, <i>$journal</i>,{ pp. $pages,} $year.{ URL <a href="$url">$url</a>.}{ $note.}',
129 u
'@techreport':u
'$authors: <i>$title</i>, $year.{ URL <a href="$url">$url</a>.}{ $note.}',
130 u
'@unpublished':u
'$authors: “$title”, <i>$journal</i>, $year.{ URL <a href="$url">$url</a>.}{ $note.}',
132 u
'default':u
'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}',
136 u
'YY':u
'??', u
'authors':u
'', u
'surname':u
'',
141 u
'@article':u
'$authors, “$title”, <i>$journal</i>, vol. $volume, no. $number, pp. $pages, $year.{ URL <a href="$url">$url</a>.}{ $note.}',
142 u
'@book':u
'$authors, <i>$title</i>. $publisher, $year.{ URL <a href="$url">$url</a>.}{ $note.}',
144 u
'default':u
'$authors, “$title”. $year.{ URL <a href="$url">$url</a>.}{ $note.}',
149 u
'@article':u
'$authors. $title.{ <i>$journal</i>{, {$volume}{($number)}}{:$pages}{, $year}.}{ URL <a href="$url">$url</a>.}{ $note.}',
150 u
'@book':u
'$authors. <i>$title</i>. $publisher,{ $month} $year.{ URL <a href="$url">$url</a>.}{ $note.}',
151 u
'@incollection':u
'$authors. $title.{ In <i>$booktitle</i> {($editor, ed.)}.} $publisher,{ $month} $year.{ URL <a href="$url">$url</a>.}{ $note.}',
152 u
'@inproceedings':u
'$authors. $title. { <i>$booktitle</i>{, {$volume}{($number)}}{:$pages}{, $year}.}{ URL <a href="$url">$url</a>.}{ $note.}',
154 u
'default':u
'{$authors. }$title.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}',
159 u
'@article':u
'$authors. $title. <i>$journal</i>, $year{;{<b>$volume</b>}{($number)}{:$pages}}.{ URL: <a href="$url">$url</a>.}{ $note.}',
160 u
'@book':u
'$authors. $title. {$publisher, }$year.{ URL: <a href="$url">$url</a>.}{ $note.}',
162 u
'default':u
'$authors. $title; {$publisher, }$year.{ $howpublished.}{ URL: <a href="$url">$url</a>.}{ $note.}',
165 class BibTeXConfig(object):
166 "Configuration class from elyxer.config file"
169 u
'--':u
'—', u
'..':u
'.',
172 class ContainerConfig(object):
173 "Configuration class from elyxer.config file"
176 u
'Align':u
'\\end_layout', u
'BarredText':u
'\\bar',
177 u
'BoldText':u
'\\series', u
'Cell':u
'</cell',
178 u
'ChangeDeleted':u
'\\change_unchanged',
179 u
'ChangeInserted':u
'\\change_unchanged', u
'ColorText':u
'\\color',
180 u
'EmphaticText':u
'\\emph', u
'Hfill':u
'\\hfill', u
'Inset':u
'\\end_inset',
181 u
'Layout':u
'\\end_layout', u
'LyXFooter':u
'\\end_document',
182 u
'LyXHeader':u
'\\end_header', u
'Row':u
'</row', u
'ShapedText':u
'\\shape',
183 u
'SizeText':u
'\\size', u
'StrikeOut':u
'\\strikeout',
184 u
'TextFamily':u
'\\family', u
'VersalitasText':u
'\\noun',
188 u
'allowed':[u
'StringContainer',u
'Constant',u
'FormulaConstant',],
190 u
'extracted':[u
'PlainLayout',u
'TaggedText',u
'Align',u
'Caption',u
'TextFamily',u
'EmphaticText',u
'VersalitasText',u
'BarredText',u
'SizeText',u
'ColorText',u
'LangLine',u
'Formula',u
'Bracket',u
'RawText',u
'BibTag',u
'FormulaNumber',u
'AlphaCommand',u
'EmptyCommand',u
'OneParamFunction',u
'SymbolFunction',u
'TextFunction',u
'FontFunction',u
'CombiningFunction',u
'DecoratingFunction',u
'FormulaSymbol',u
'BracketCommand',u
'TeXCode',],
194 u
'\\begin_deeper':u
'\\end_deeper', u
'\\begin_inset':u
'\\end_inset',
195 u
'\\begin_layout':u
'\\end_layout',
199 u
'':u
'StringContainer', u
'#LyX':u
'BlackBox', u
'</lyxtabular':u
'BlackBox',
200 u
'<cell':u
'Cell', u
'<column':u
'Column', u
'<row':u
'Row',
201 u
'\\align':u
'Align', u
'\\bar':u
'BarredText',
202 u
'\\bar default':u
'BlackBox', u
'\\bar no':u
'BlackBox',
203 u
'\\begin_body':u
'BlackBox', u
'\\begin_deeper':u
'DeeperList',
204 u
'\\begin_document':u
'BlackBox', u
'\\begin_header':u
'LyXHeader',
205 u
'\\begin_inset Argument':u
'ShortTitle',
206 u
'\\begin_inset Box':u
'BoxInset', u
'\\begin_inset Branch':u
'Branch',
207 u
'\\begin_inset Caption':u
'Caption',
208 u
'\\begin_inset CommandInset bibitem':u
'BiblioEntry',
209 u
'\\begin_inset CommandInset bibtex':u
'BibTeX',
210 u
'\\begin_inset CommandInset citation':u
'BiblioCitation',
211 u
'\\begin_inset CommandInset href':u
'URL',
212 u
'\\begin_inset CommandInset include':u
'IncludeInset',
213 u
'\\begin_inset CommandInset index_print':u
'PrintIndex',
214 u
'\\begin_inset CommandInset label':u
'Label',
215 u
'\\begin_inset CommandInset line':u
'LineInset',
216 u
'\\begin_inset CommandInset nomencl_print':u
'PrintNomenclature',
217 u
'\\begin_inset CommandInset nomenclature':u
'NomenclatureEntry',
218 u
'\\begin_inset CommandInset ref':u
'Reference',
219 u
'\\begin_inset CommandInset toc':u
'TableOfContents',
220 u
'\\begin_inset ERT':u
'ERT', u
'\\begin_inset Flex':u
'FlexInset',
221 u
'\\begin_inset Flex Chunkref':u
'NewfangledChunkRef',
222 u
'\\begin_inset Flex Marginnote':u
'SideNote',
223 u
'\\begin_inset Flex Sidenote':u
'SideNote',
224 u
'\\begin_inset Flex URL':u
'FlexURL', u
'\\begin_inset Float':u
'Float',
225 u
'\\begin_inset FloatList':u
'ListOf', u
'\\begin_inset Foot':u
'Footnote',
226 u
'\\begin_inset Formula':u
'Formula',
227 u
'\\begin_inset FormulaMacro':u
'FormulaMacro',
228 u
'\\begin_inset Graphics':u
'Image',
229 u
'\\begin_inset Index':u
'IndexReference',
230 u
'\\begin_inset Info':u
'InfoInset',
231 u
'\\begin_inset LatexCommand bibitem':u
'BiblioEntry',
232 u
'\\begin_inset LatexCommand bibtex':u
'BibTeX',
233 u
'\\begin_inset LatexCommand cite':u
'BiblioCitation',
234 u
'\\begin_inset LatexCommand citealt':u
'BiblioCitation',
235 u
'\\begin_inset LatexCommand citep':u
'BiblioCitation',
236 u
'\\begin_inset LatexCommand citet':u
'BiblioCitation',
237 u
'\\begin_inset LatexCommand htmlurl':u
'URL',
238 u
'\\begin_inset LatexCommand index':u
'IndexReference',
239 u
'\\begin_inset LatexCommand label':u
'Label',
240 u
'\\begin_inset LatexCommand nomenclature':u
'NomenclatureEntry',
241 u
'\\begin_inset LatexCommand prettyref':u
'Reference',
242 u
'\\begin_inset LatexCommand printindex':u
'PrintIndex',
243 u
'\\begin_inset LatexCommand printnomenclature':u
'PrintNomenclature',
244 u
'\\begin_inset LatexCommand ref':u
'Reference',
245 u
'\\begin_inset LatexCommand tableofcontents':u
'TableOfContents',
246 u
'\\begin_inset LatexCommand url':u
'URL',
247 u
'\\begin_inset LatexCommand vref':u
'Reference',
248 u
'\\begin_inset Marginal':u
'SideNote',
249 u
'\\begin_inset Newline':u
'NewlineInset',
250 u
'\\begin_inset Newpage':u
'NewPageInset', u
'\\begin_inset Note':u
'Note',
251 u
'\\begin_inset OptArg':u
'ShortTitle',
252 u
'\\begin_inset Phantom':u
'PhantomText',
253 u
'\\begin_inset Quotes':u
'QuoteContainer',
254 u
'\\begin_inset Tabular':u
'Table', u
'\\begin_inset Text':u
'InsetText',
255 u
'\\begin_inset VSpace':u
'VerticalSpace', u
'\\begin_inset Wrap':u
'Wrap',
256 u
'\\begin_inset listings':u
'Listing', u
'\\begin_inset space':u
'Space',
257 u
'\\begin_layout':u
'Layout', u
'\\begin_layout Abstract':u
'Abstract',
258 u
'\\begin_layout Author':u
'Author',
259 u
'\\begin_layout Bibliography':u
'Bibliography',
260 u
'\\begin_layout Chunk':u
'NewfangledChunk',
261 u
'\\begin_layout Description':u
'Description',
262 u
'\\begin_layout Enumerate':u
'ListItem',
263 u
'\\begin_layout Itemize':u
'ListItem', u
'\\begin_layout List':u
'List',
264 u
'\\begin_layout LyX-Code':u
'LyXCode',
265 u
'\\begin_layout Plain':u
'PlainLayout',
266 u
'\\begin_layout Standard':u
'StandardLayout',
267 u
'\\begin_layout Title':u
'Title', u
'\\begin_preamble':u
'LyXPreamble',
268 u
'\\change_deleted':u
'ChangeDeleted',
269 u
'\\change_inserted':u
'ChangeInserted',
270 u
'\\change_unchanged':u
'BlackBox', u
'\\color':u
'ColorText',
271 u
'\\color inherit':u
'BlackBox', u
'\\color none':u
'BlackBox',
272 u
'\\emph default':u
'BlackBox', u
'\\emph off':u
'BlackBox',
273 u
'\\emph on':u
'EmphaticText', u
'\\emph toggle':u
'EmphaticText',
274 u
'\\end_body':u
'LyXFooter', u
'\\family':u
'TextFamily',
275 u
'\\family default':u
'BlackBox', u
'\\family roman':u
'BlackBox',
276 u
'\\hfill':u
'Hfill', u
'\\labelwidthstring':u
'BlackBox',
277 u
'\\lang':u
'LangLine', u
'\\length':u
'InsetLength',
278 u
'\\lyxformat':u
'LyXFormat', u
'\\lyxline':u
'LyXLine',
279 u
'\\newline':u
'Newline', u
'\\newpage':u
'NewPage',
280 u
'\\noindent':u
'BlackBox', u
'\\noun default':u
'BlackBox',
281 u
'\\noun off':u
'BlackBox', u
'\\noun on':u
'VersalitasText',
282 u
'\\paragraph_spacing':u
'BlackBox', u
'\\series bold':u
'BoldText',
283 u
'\\series default':u
'BlackBox', u
'\\series medium':u
'BlackBox',
284 u
'\\shape':u
'ShapedText', u
'\\shape default':u
'BlackBox',
285 u
'\\shape up':u
'BlackBox', u
'\\size':u
'SizeText',
286 u
'\\size normal':u
'BlackBox', u
'\\start_of_appendix':u
'StartAppendix',
287 u
'\\strikeout default':u
'BlackBox', u
'\\strikeout on':u
'StrikeOut',
291 u
'startcommand':u
'\\',
295 u
'headers':[u
'<lyxtabular',u
'<features',],
298 class EscapeConfig(object):
299 "Configuration class from elyxer.config file"
302 u
'\n':u
'', u
' -- ':u
' — ', u
'\'':u
'’', u
'---':u
'—', u
'`':u
'‘',
306 u
'\\InsetSpace \\space{}':u
' ', u
'\\InsetSpace \\thinspace{}':u
' ',
307 u
'\\InsetSpace ~':u
' ', u
'\\SpecialChar \\-':u
'',
308 u
'\\SpecialChar \\@.':u
'.', u
'\\SpecialChar \\ldots{}':u
'…',
309 u
'\\SpecialChar \\menuseparator':u
' ▷ ',
310 u
'\\SpecialChar \\nobreakdash-':u
'-', u
'\\SpecialChar \\slash{}':u
'/',
311 u
'\\SpecialChar \\textcompwordmark{}':u
'', u
'\\backslash':u
'\\',
315 u
'&':u
'&', u
'<':u
'<', u
'>':u
'>',
323 u
' ':u
' ', u
' ':u
' ', u
' ':u
' ',
330 class FormulaConfig(object):
331 "Configuration class from elyxer.config file"
334 u
'\\AA':u
'Å', u
'\\AE':u
'Æ',
335 u
'\\AmS':u
'<span class="versalitas">AmS</span>', u
'\\DH':u
'Ð',
336 u
'\\L':u
'Ł', u
'\\O':u
'Ø', u
'\\OE':u
'Œ', u
'\\TH':u
'Þ', u
'\\aa':u
'å',
337 u
'\\ae':u
'æ', u
'\\alpha':u
'α', u
'\\beta':u
'β', u
'\\delta':u
'δ',
338 u
'\\dh':u
'ð', u
'\\epsilon':u
'ϵ', u
'\\eta':u
'η', u
'\\gamma':u
'γ',
339 u
'\\i':u
'ı', u
'\\imath':u
'ı', u
'\\iota':u
'ι', u
'\\j':u
'ȷ',
340 u
'\\jmath':u
'ȷ', u
'\\kappa':u
'κ', u
'\\l':u
'ł', u
'\\lambda':u
'λ',
341 u
'\\mu':u
'μ', u
'\\nu':u
'ν', u
'\\o':u
'ø', u
'\\oe':u
'œ', u
'\\omega':u
'ω',
342 u
'\\phi':u
'φ', u
'\\pi':u
'π', u
'\\psi':u
'ψ', u
'\\rho':u
'ρ',
343 u
'\\sigma':u
'σ', u
'\\ss':u
'ß', u
'\\tau':u
'τ', u
'\\textcrh':u
'ħ',
344 u
'\\th':u
'þ', u
'\\theta':u
'θ', u
'\\upsilon':u
'υ', u
'\\varDelta':u
'∆',
345 u
'\\varGamma':u
'Γ', u
'\\varLambda':u
'Λ', u
'\\varOmega':u
'Ω',
346 u
'\\varPhi':u
'Φ', u
'\\varPi':u
'Π', u
'\\varPsi':u
'Ψ', u
'\\varSigma':u
'Σ',
347 u
'\\varTheta':u
'Θ', u
'\\varUpsilon':u
'Υ', u
'\\varXi':u
'Ξ',
348 u
'\\varepsilon':u
'ε', u
'\\varkappa':u
'ϰ', u
'\\varphi':u
'φ',
349 u
'\\varpi':u
'ϖ', u
'\\varrho':u
'ϱ', u
'\\varsigma':u
'ς',
350 u
'\\vartheta':u
'ϑ', u
'\\xi':u
'ξ', u
'\\zeta':u
'ζ',
354 u
'begin':u
'\\begin', u
'cellseparator':u
'&', u
'end':u
'\\end',
355 u
'rowseparator':u
'\\\\',
359 u
'(':[u
'⎛',u
'⎜',u
'⎝',], u
')':[u
'⎞',u
'⎟',u
'⎠',], u
'[':[u
'⎡',u
'⎢',u
'⎣',],
360 u
']':[u
'⎤',u
'⎥',u
'⎦',], u
'{':[u
'⎧',u
'⎪',u
'⎨',u
'⎩',], u
'|':[u
'|',],
361 u
'}':[u
'⎫',u
'⎪',u
'⎬',u
'⎭',], u
'∥':[u
'∥',],
365 u
'∑':[u
'⎲',u
'⎳',], u
'∫':[u
'⌠',u
'⌡',],
369 u
'\\left':u
'span class="symbol"',
370 u
'\\left.':u
'<span class="leftdot"></span>',
371 u
'\\middle':u
'span class="symbol"', u
'\\right':u
'span class="symbol"',
372 u
'\\right.':u
'<span class="rightdot"></span>',
375 combiningfunctions
= {
376 u
'\\"':u
'̈', u
'\\\'':u
'́', u
'\\^':u
'̂', u
'\\`':u
'̀', u
'\\acute':u
'́',
377 u
'\\bar':u
'̄', u
'\\breve':u
'̆', u
'\\c':u
'̧', u
'\\check':u
'̌',
378 u
'\\dddot':u
'⃛', u
'\\ddot':u
'̈', u
'\\dot':u
'̇', u
'\\grave':u
'̀',
379 u
'\\hat':u
'̂', u
'\\mathring':u
'̊', u
'\\overleftarrow':u
'⃖',
380 u
'\\overrightarrow':u
'⃗', u
'\\r':u
'̊', u
'\\s':u
'̩',
381 u
'\\textcircled':u
'⃝', u
'\\textsubring':u
'̥', u
'\\tilde':u
'̃',
382 u
'\\v':u
'̌', u
'\\vec':u
'⃗', u
'\\~':u
'̃',
386 u
'\\ ':u
' ', u
'\\!':u
'', u
'\\#':u
'#', u
'\\$':u
'$', u
'\\%':u
'%',
387 u
'\\&':u
'&', u
'\\,':u
' ', u
'\\:':u
' ', u
'\\;':u
' ',
388 u
'\\APLdownarrowbox':u
'⍗', u
'\\APLleftarrowbox':u
'⍇',
389 u
'\\APLrightarrowbox':u
'⍈', u
'\\APLuparrowbox':u
'⍐', u
'\\Box':u
'□',
390 u
'\\Bumpeq':u
'≎', u
'\\CIRCLE':u
'●', u
'\\Cap':u
'⋒', u
'\\CheckedBox':u
'☑',
391 u
'\\Circle':u
'○', u
'\\Coloneqq':u
'⩴', u
'\\Corresponds':u
'≙',
392 u
'\\Cup':u
'⋓', u
'\\Delta':u
'Δ', u
'\\Diamond':u
'◇', u
'\\Downarrow':u
'⇓',
393 u
'\\EUR':u
'€', u
'\\Game':u
'⅁', u
'\\Gamma':u
'Γ', u
'\\Im':u
'ℑ',
394 u
'\\Join':u
'⨝', u
'\\LEFTCIRCLE':u
'◖', u
'\\LEFTcircle':u
'◐',
395 u
'\\Lambda':u
'Λ', u
'\\Leftarrow':u
'⇐', u
'\\Lleftarrow':u
'⇚',
396 u
'\\Longleftarrow':u
'⟸', u
'\\Longleftrightarrow':u
'⟺',
397 u
'\\Longrightarrow':u
'⟹', u
'\\Lsh':u
'↰', u
'\\Mapsfrom':u
'⇐|',
398 u
'\\Mapsto':u
'|⇒', u
'\\Omega':u
'Ω', u
'\\P':u
'¶', u
'\\Phi':u
'Φ',
399 u
'\\Pi':u
'Π', u
'\\Pr':u
'Pr', u
'\\Psi':u
'Ψ', u
'\\RIGHTCIRCLE':u
'◗',
400 u
'\\RIGHTcircle':u
'◑', u
'\\Re':u
'ℜ', u
'\\Rrightarrow':u
'⇛',
401 u
'\\Rsh':u
'↱', u
'\\S':u
'§', u
'\\Sigma':u
'Σ', u
'\\Square':u
'☐',
402 u
'\\Subset':u
'⋐', u
'\\Supset':u
'⋑', u
'\\Theta':u
'Θ', u
'\\Uparrow':u
'⇑',
403 u
'\\Updownarrow':u
'⇕', u
'\\Upsilon':u
'Υ', u
'\\Vdash':u
'⊩',
404 u
'\\Vert':u
'∥', u
'\\Vvdash':u
'⊪', u
'\\XBox':u
'☒', u
'\\Xi':u
'Ξ',
405 u
'\\Yup':u
'⅄', u
'\\\\':u
'<br/>', u
'\\_':u
'_', u
'\\aleph':u
'ℵ',
406 u
'\\amalg':u
'∐', u
'\\angle':u
'∠', u
'\\aquarius':u
'♒',
407 u
'\\arccos':u
'arccos', u
'\\arcsin':u
'arcsin', u
'\\arctan':u
'arctan',
408 u
'\\arg':u
'arg', u
'\\aries':u
'♈', u
'\\ast':u
'∗', u
'\\asymp':u
'≍',
409 u
'\\backepsilon':u
'∍', u
'\\backprime':u
'‵', u
'\\backsimeq':u
'⋍',
410 u
'\\backslash':u
'\\', u
'\\barwedge':u
'⊼', u
'\\because':u
'∵',
411 u
'\\beth':u
'ℶ', u
'\\between':u
'≬', u
'\\bigcap':u
'∩', u
'\\bigcirc':u
'○',
412 u
'\\bigcup':u
'∪', u
'\\bigodot':u
'⊙', u
'\\bigoplus':u
'⊕',
413 u
'\\bigotimes':u
'⊗', u
'\\bigsqcup':u
'⊔', u
'\\bigstar':u
'★',
414 u
'\\bigtriangledown':u
'▽', u
'\\bigtriangleup':u
'△', u
'\\biguplus':u
'⊎',
415 u
'\\bigvee':u
'∨', u
'\\bigwedge':u
'∧', u
'\\blacklozenge':u
'⧫',
416 u
'\\blacksmiley':u
'☻', u
'\\blacksquare':u
'■', u
'\\blacktriangle':u
'▲',
417 u
'\\blacktriangledown':u
'▼', u
'\\blacktriangleright':u
'▶', u
'\\bot':u
'⊥',
418 u
'\\bowtie':u
'⋈', u
'\\box':u
'▫', u
'\\boxdot':u
'⊡', u
'\\bullet':u
'•',
419 u
'\\bumpeq':u
'≏', u
'\\cancer':u
'♋', u
'\\cap':u
'∩', u
'\\capricornus':u
'♑',
420 u
'\\cdot':u
'⋅', u
'\\cdots':u
'⋯', u
'\\centerdot':u
'∙',
421 u
'\\checkmark':u
'✓', u
'\\chi':u
'χ', u
'\\circ':u
'○', u
'\\circeq':u
'≗',
422 u
'\\circledR':u
'®', u
'\\circledast':u
'⊛', u
'\\circledcirc':u
'⊚',
423 u
'\\circleddash':u
'⊝', u
'\\clubsuit':u
'♣', u
'\\coloneqq':u
'≔',
424 u
'\\complement':u
'∁', u
'\\cong':u
'≅', u
'\\coprod':u
'∐',
425 u
'\\copyright':u
'©', u
'\\cos':u
'cos', u
'\\cosh':u
'cosh', u
'\\cot':u
'cot',
426 u
'\\coth':u
'coth', u
'\\csc':u
'csc', u
'\\cup':u
'∪',
427 u
'\\curvearrowleft':u
'↶', u
'\\curvearrowright':u
'↷', u
'\\dag':u
'†',
428 u
'\\dagger':u
'†', u
'\\daleth':u
'ℸ', u
'\\dashleftarrow':u
'⇠',
429 u
'\\dashv':u
'⊣', u
'\\ddag':u
'‡', u
'\\ddagger':u
'‡', u
'\\ddots':u
'⋱',
430 u
'\\deg':u
'deg', u
'\\det':u
'det', u
'\\diagdown':u
'╲', u
'\\diagup':u
'╱',
431 u
'\\diamond':u
'◇', u
'\\diamondsuit':u
'♦', u
'\\dim':u
'dim', u
'\\div':u
'÷',
432 u
'\\divideontimes':u
'⋇', u
'\\dotdiv':u
'∸', u
'\\doteq':u
'≐',
433 u
'\\doteqdot':u
'≑', u
'\\dotplus':u
'∔', u
'\\dots':u
'…',
434 u
'\\doublebarwedge':u
'⌆', u
'\\downarrow':u
'↓', u
'\\downdownarrows':u
'⇊',
435 u
'\\downharpoonleft':u
'⇃', u
'\\downharpoonright':u
'⇂', u
'\\earth':u
'♁',
436 u
'\\ell':u
'ℓ', u
'\\emptyset':u
'∅', u
'\\eqcirc':u
'≖', u
'\\eqcolon':u
'≕',
437 u
'\\eqsim':u
'≂', u
'\\euro':u
'€', u
'\\exists':u
'∃', u
'\\exp':u
'exp',
438 u
'\\fallingdotseq':u
'≒', u
'\\female':u
'♀', u
'\\flat':u
'♭',
439 u
'\\forall':u
'∀', u
'\\frown':u
'⌢', u
'\\frownie':u
'☹', u
'\\gcd':u
'gcd',
440 u
'\\gemini':u
'♊', u
'\\geq)':u
'≥', u
'\\geqq':u
'≧', u
'\\geqslant':u
'≥',
441 u
'\\gets':u
'←', u
'\\gg':u
'≫', u
'\\ggg':u
'⋙', u
'\\gimel':u
'ℷ',
442 u
'\\gneqq':u
'≩', u
'\\gnsim':u
'⋧', u
'\\gtrdot':u
'⋗', u
'\\gtreqless':u
'⋚',
443 u
'\\gtreqqless':u
'⪌', u
'\\gtrless':u
'≷', u
'\\gtrsim':u
'≳',
444 u
'\\guillemotleft':u
'«', u
'\\guillemotright':u
'»', u
'\\hbar':u
'ℏ',
445 u
'\\heartsuit':u
'♥', u
'\\hfill':u
'<span class="hfill"> </span>',
446 u
'\\hom':u
'hom', u
'\\hookleftarrow':u
'↩', u
'\\hookrightarrow':u
'↪',
447 u
'\\hslash':u
'ℏ', u
'\\idotsint':u
'<span class="bigsymbol">∫⋯∫</span>',
448 u
'\\iiint':u
'<span class="bigsymbol">∭</span>',
449 u
'\\iint':u
'<span class="bigsymbol">∬</span>', u
'\\imath':u
'ı',
450 u
'\\inf':u
'inf', u
'\\infty':u
'∞', u
'\\invneg':u
'⌐', u
'\\jmath':u
'ȷ',
451 u
'\\jupiter':u
'♃', u
'\\ker':u
'ker', u
'\\land':u
'∧',
452 u
'\\landupint':u
'<span class="bigsymbol">∱</span>', u
'\\langle':u
'⟨',
453 u
'\\lbrace':u
'{', u
'\\lbrace)':u
'{', u
'\\lbrack':u
'[', u
'\\lceil':u
'⌈',
454 u
'\\ldots':u
'…', u
'\\leadsto':u
'⇝', u
'\\leftarrow)':u
'←',
455 u
'\\leftarrowtail':u
'↢', u
'\\leftarrowtobar':u
'⇤',
456 u
'\\leftharpoondown':u
'↽', u
'\\leftharpoonup':u
'↼',
457 u
'\\leftleftarrows':u
'⇇', u
'\\leftleftharpoons':u
'⥢', u
'\\leftmoon':u
'☾',
458 u
'\\leftrightarrow':u
'↔', u
'\\leftrightarrows':u
'⇆',
459 u
'\\leftrightharpoons':u
'⇋', u
'\\leftthreetimes':u
'⋋', u
'\\leo':u
'♌',
460 u
'\\leq)':u
'≤', u
'\\leqq':u
'≦', u
'\\leqslant':u
'≤', u
'\\lessdot':u
'⋖',
461 u
'\\lesseqgtr':u
'⋛', u
'\\lesseqqgtr':u
'⪋', u
'\\lessgtr':u
'≶',
462 u
'\\lesssim':u
'≲', u
'\\lfloor':u
'⌊', u
'\\lg':u
'lg', u
'\\lhd':u
'⊲',
463 u
'\\libra':u
'♎', u
'\\lightning':u
'↯', u
'\\liminf':u
'liminf',
464 u
'\\limsup':u
'limsup', u
'\\ll':u
'≪', u
'\\lll':u
'⋘', u
'\\ln':u
'ln',
465 u
'\\lneqq':u
'≨', u
'\\lnot':u
'¬', u
'\\lnsim':u
'⋦', u
'\\log':u
'log',
466 u
'\\longleftarrow':u
'⟵', u
'\\longleftrightarrow':u
'⟷',
467 u
'\\longmapsto':u
'⟼', u
'\\longrightarrow':u
'⟶', u
'\\looparrowleft':u
'↫',
468 u
'\\looparrowright':u
'↬', u
'\\lor':u
'∨', u
'\\lozenge':u
'◊',
469 u
'\\ltimes':u
'⋉', u
'\\lyxlock':u
'', u
'\\male':u
'♂', u
'\\maltese':u
'✠',
470 u
'\\mapsfrom':u
'↤', u
'\\mapsto':u
'↦', u
'\\mathcircumflex':u
'^',
471 u
'\\max':u
'max', u
'\\measuredangle':u
'∡', u
'\\mercury':u
'☿',
472 u
'\\mho':u
'℧', u
'\\mid':u
'∣', u
'\\min':u
'min', u
'\\models':u
'⊨',
473 u
'\\mp':u
'∓', u
'\\multimap':u
'⊸', u
'\\nLeftarrow':u
'⇍',
474 u
'\\nLeftrightarrow':u
'⇎', u
'\\nRightarrow':u
'⇏', u
'\\nVDash':u
'⊯',
475 u
'\\nabla':u
'∇', u
'\\napprox':u
'≉', u
'\\natural':u
'♮', u
'\\ncong':u
'≇',
476 u
'\\nearrow':u
'↗', u
'\\neg':u
'¬', u
'\\neg)':u
'¬', u
'\\neptune':u
'♆',
477 u
'\\nequiv':u
'≢', u
'\\newline':u
'<br/>', u
'\\nexists':u
'∄',
478 u
'\\ngeqslant':u
'≱', u
'\\ngtr':u
'≯', u
'\\ngtrless':u
'≹', u
'\\ni':u
'∋',
479 u
'\\ni)':u
'∋', u
'\\nleftarrow':u
'↚', u
'\\nleftrightarrow':u
'↮',
480 u
'\\nleqslant':u
'≰', u
'\\nless':u
'≮', u
'\\nlessgtr':u
'≸', u
'\\nmid':u
'∤',
481 u
'\\nolimits':u
'', u
'\\nonumber':u
'', u
'\\not':u
'¬', u
'\\not<':u
'≮',
482 u
'\\not=':u
'≠', u
'\\not>':u
'≯', u
'\\notbackslash':u
'⍀', u
'\\notin':u
'∉',
483 u
'\\notni':u
'∌', u
'\\notslash':u
'⌿', u
'\\nparallel':u
'∦',
484 u
'\\nprec':u
'⊀', u
'\\nrightarrow':u
'↛', u
'\\nsim':u
'≁', u
'\\nsimeq':u
'≄',
485 u
'\\nsqsubset':u
'⊏̸', u
'\\nsubseteq':u
'⊈', u
'\\nsucc':u
'⊁',
486 u
'\\nsucccurlyeq':u
'⋡', u
'\\nsupset':u
'⊅', u
'\\nsupseteq':u
'⊉',
487 u
'\\ntriangleleft':u
'⋪', u
'\\ntrianglelefteq':u
'⋬',
488 u
'\\ntriangleright':u
'⋫', u
'\\ntrianglerighteq':u
'⋭', u
'\\nvDash':u
'⊭',
489 u
'\\nvdash':u
'⊬', u
'\\nwarrow':u
'↖', u
'\\odot':u
'⊙',
490 u
'\\officialeuro':u
'€', u
'\\oiiint':u
'<span class="bigsymbol">∰</span>',
491 u
'\\oiint':u
'<span class="bigsymbol">∯</span>',
492 u
'\\oint':u
'<span class="bigsymbol">∮</span>',
493 u
'\\ointclockwise':u
'<span class="bigsymbol">∲</span>',
494 u
'\\ointctrclockwise':u
'<span class="bigsymbol">∳</span>',
495 u
'\\ominus':u
'⊖', u
'\\oplus':u
'⊕', u
'\\oslash':u
'⊘', u
'\\otimes':u
'⊗',
496 u
'\\owns':u
'∋', u
'\\parallel':u
'∥', u
'\\partial':u
'∂', u
'\\perp':u
'⊥',
497 u
'\\pisces':u
'♓', u
'\\pitchfork':u
'⋔', u
'\\pluto':u
'♇', u
'\\pm':u
'±',
498 u
'\\pointer':u
'➪', u
'\\pounds':u
'£', u
'\\prec':u
'≺',
499 u
'\\preccurlyeq':u
'≼', u
'\\preceq':u
'≼', u
'\\precsim':u
'≾',
500 u
'\\prime':u
'′', u
'\\prompto':u
'∝', u
'\\qquad':u
' ', u
'\\quad':u
' ',
501 u
'\\quarternote':u
'♩', u
'\\rangle':u
'⟩', u
'\\rbrace':u
'}',
502 u
'\\rbrace)':u
'}', u
'\\rbrack':u
']', u
'\\rceil':u
'⌉', u
'\\rfloor':u
'⌋',
503 u
'\\rhd':u
'⊳', u
'\\rightarrow)':u
'→', u
'\\rightarrowtail':u
'↣',
504 u
'\\rightarrowtobar':u
'⇥', u
'\\rightharpoondown':u
'⇁',
505 u
'\\rightharpoonup':u
'⇀', u
'\\rightharpooondown':u
'⇁',
506 u
'\\rightharpooonup':u
'⇀', u
'\\rightleftarrows':u
'⇄',
507 u
'\\rightleftharpoons':u
'⇌', u
'\\rightmoon':u
'☽',
508 u
'\\rightrightarrows':u
'⇉', u
'\\rightrightharpoons':u
'⥤',
509 u
'\\rightthreetimes':u
'⋌', u
'\\risingdotseq':u
'≓', u
'\\rtimes':u
'⋊',
510 u
'\\sagittarius':u
'♐', u
'\\saturn':u
'♄', u
'\\scorpio':u
'♏',
511 u
'\\searrow':u
'↘', u
'\\sec':u
'sec', u
'\\setminus':u
'∖', u
'\\sharp':u
'♯',
512 u
'\\simeq':u
'≃', u
'\\sin':u
'sin', u
'\\sinh':u
'sinh', u
'\\slash':u
'∕',
513 u
'\\smile':u
'⌣', u
'\\smiley':u
'☺', u
'\\spadesuit':u
'♠',
514 u
'\\sphericalangle':u
'∢', u
'\\sqcap':u
'⊓', u
'\\sqcup':u
'⊔',
515 u
'\\sqsubset':u
'⊏', u
'\\sqsubseteq':u
'⊑', u
'\\sqsupset':u
'⊐',
516 u
'\\sqsupseteq':u
'⊒', u
'\\square':u
'□', u
'\\star':u
'⋆',
517 u
'\\subseteqq':u
'⫅', u
'\\subsetneqq':u
'⫋', u
'\\succ':u
'≻',
518 u
'\\succcurlyeq':u
'≽', u
'\\succeq':u
'≽', u
'\\succnsim':u
'⋩',
519 u
'\\succsim':u
'≿', u
'\\sun':u
'☼', u
'\\sup':u
'sup', u
'\\supseteqq':u
'⫆',
520 u
'\\supsetneqq':u
'⫌', u
'\\surd':u
'√', u
'\\swarrow':u
'↙', u
'\\tan':u
'tan',
521 u
'\\tanh':u
'tanh', u
'\\taurus':u
'♉', u
'\\textasciicircum':u
'^',
522 u
'\\textasciitilde':u
'~', u
'\\textbackslash':u
'\\',
523 u
'\\textcopyright':u
'©\'', u
'\\textdegree':u
'°', u
'\\textellipsis':u
'…',
524 u
'\\textemdash':u
'—', u
'\\textendash':u
'—', u
'\\texteuro':u
'€',
525 u
'\\textgreater':u
'>', u
'\\textless':u
'<', u
'\\textordfeminine':u
'ª',
526 u
'\\textordmasculine':u
'º', u
'\\textquotedblleft':u
'“',
527 u
'\\textquotedblright':u
'”', u
'\\textquoteright':u
'’',
528 u
'\\textregistered':u
'®', u
'\\textrightarrow':u
'→',
529 u
'\\textsection':u
'§', u
'\\texttrademark':u
'™',
530 u
'\\texttwosuperior':u
'²', u
'\\textvisiblespace':u
' ',
531 u
'\\therefore':u
'∴', u
'\\top':u
'⊤', u
'\\triangle':u
'△',
532 u
'\\triangleleft':u
'⊲', u
'\\trianglelefteq':u
'⊴', u
'\\triangleq':u
'≜',
533 u
'\\triangleright':u
'▷', u
'\\trianglerighteq':u
'⊵',
534 u
'\\twoheadleftarrow':u
'↞', u
'\\twoheadrightarrow':u
'↠',
535 u
'\\twonotes':u
'♫', u
'\\udot':u
'⊍', u
'\\unlhd':u
'⊴', u
'\\unrhd':u
'⊵',
536 u
'\\unrhl':u
'⊵', u
'\\uparrow':u
'↑', u
'\\updownarrow':u
'↕',
537 u
'\\upharpoonleft':u
'↿', u
'\\upharpoonright':u
'↾', u
'\\uplus':u
'⊎',
538 u
'\\upuparrows':u
'⇈', u
'\\uranus':u
'♅', u
'\\vDash':u
'⊨',
539 u
'\\varclubsuit':u
'♧', u
'\\vardiamondsuit':u
'♦', u
'\\varheartsuit':u
'♥',
540 u
'\\varnothing':u
'∅', u
'\\varspadesuit':u
'♤', u
'\\vdash':u
'⊢',
541 u
'\\vdots':u
'⋮', u
'\\vee':u
'∨', u
'\\vee)':u
'∨', u
'\\veebar':u
'⊻',
542 u
'\\vert':u
'∣', u
'\\virgo':u
'♍', u
'\\wedge':u
'∧', u
'\\wedge)':u
'∧',
543 u
'\\wp':u
'℘', u
'\\wr':u
'≀', u
'\\yen':u
'¥', u
'\\{':u
'{', u
'\\|':u
'∥',
551 decoratingfunctions
= {
552 u
'\\overleftarrow':u
'⟵', u
'\\overrightarrow':u
'⟶', u
'\\widehat':u
'^',
556 u
'bracket':u
'}', u
'complex':u
'\\]', u
'endafter':u
'}',
557 u
'endbefore':u
'\\end{', u
'squarebracket':u
']',
561 u
'align':[u
'r',u
'l',], u
'eqnarray':[u
'r',u
'c',u
'l',],
562 u
'gathered':[u
'l',u
'l',],
566 u
'\\boldsymbol':u
'b', u
'\\mathbb':u
'span class="blackboard"',
567 u
'\\mathbb{A}':u
'𝔸', u
'\\mathbb{B}':u
'𝔹', u
'\\mathbb{C}':u
'ℂ',
568 u
'\\mathbb{D}':u
'𝔻', u
'\\mathbb{E}':u
'𝔼', u
'\\mathbb{F}':u
'𝔽',
569 u
'\\mathbb{G}':u
'𝔾', u
'\\mathbb{H}':u
'ℍ', u
'\\mathbb{J}':u
'𝕁',
570 u
'\\mathbb{K}':u
'𝕂', u
'\\mathbb{L}':u
'𝕃', u
'\\mathbb{N}':u
'ℕ',
571 u
'\\mathbb{O}':u
'𝕆', u
'\\mathbb{P}':u
'ℙ', u
'\\mathbb{Q}':u
'ℚ',
572 u
'\\mathbb{R}':u
'ℝ', u
'\\mathbb{S}':u
'𝕊', u
'\\mathbb{T}':u
'𝕋',
573 u
'\\mathbb{W}':u
'𝕎', u
'\\mathbb{Z}':u
'ℤ', u
'\\mathbf':u
'b',
574 u
'\\mathcal':u
'span class="scriptfont"', u
'\\mathcal{B}':u
'ℬ',
575 u
'\\mathcal{E}':u
'ℰ', u
'\\mathcal{F}':u
'ℱ', u
'\\mathcal{H}':u
'ℋ',
576 u
'\\mathcal{I}':u
'ℐ', u
'\\mathcal{L}':u
'ℒ', u
'\\mathcal{M}':u
'ℳ',
577 u
'\\mathcal{R}':u
'ℛ', u
'\\mathfrak':u
'span class="fraktur"',
578 u
'\\mathfrak{C}':u
'ℭ', u
'\\mathfrak{F}':u
'𝔉', u
'\\mathfrak{H}':u
'ℌ',
579 u
'\\mathfrak{I}':u
'ℑ', u
'\\mathfrak{R}':u
'ℜ', u
'\\mathfrak{Z}':u
'ℨ',
580 u
'\\mathit':u
'i', u
'\\mathring{A}':u
'Å', u
'\\mathring{U}':u
'Ů',
581 u
'\\mathring{a}':u
'å', u
'\\mathring{u}':u
'ů', u
'\\mathring{w}':u
'ẘ',
582 u
'\\mathring{y}':u
'ẙ', u
'\\mathrm':u
'span class="mathrm"',
583 u
'\\mathscr':u
'span class="scriptfont"', u
'\\mathscr{B}':u
'ℬ',
584 u
'\\mathscr{E}':u
'ℰ', u
'\\mathscr{F}':u
'ℱ', u
'\\mathscr{H}':u
'ℋ',
585 u
'\\mathscr{I}':u
'ℐ', u
'\\mathscr{L}':u
'ℒ', u
'\\mathscr{M}':u
'ℳ',
586 u
'\\mathscr{R}':u
'ℛ', u
'\\mathsf':u
'span class="mathsf"',
592 u
'\\binom':[u
'{$1}{$2}',u
'f2{(}f0{f1{$1}f1{$2}}f2{)}',u
'span class="binom"',u
'span class="binomstack"',u
'span class="bigsymbol"',],
593 u
'\\boxed':[u
'{$1}',u
'f0{$1}',u
'span class="boxed"',],
594 u
'\\cfrac':[u
'[$p!]{$1}{$2}',u
'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}',u
'span class="fullfraction"',u
'span class="numerator align-$p"',u
'span class="denominator"',u
'span class="ignored"',],
595 u
'\\color':[u
'{$p!}{$1}',u
'f0{$1}',u
'span style="color: $p;"',],
596 u
'\\colorbox':[u
'{$p!}{$1}',u
'f0{$1}',u
'span class="colorbox" style="background: $p;"',],
597 u
'\\dbinom':[u
'{$1}{$2}',u
'(f0{f1{f2{$1}}f1{f2{ }}f1{f2{$2}}})',u
'span class="binomial"',u
'span class="binomrow"',u
'span class="binomcell"',],
598 u
'\\dfrac':[u
'{$1}{$2}',u
'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}',u
'span class="fullfraction"',u
'span class="numerator"',u
'span class="denominator"',u
'span class="ignored"',],
599 u
'\\displaystyle':[u
'{$1}',u
'f0{$1}',u
'span class="displaystyle"',],
600 u
'\\fbox':[u
'{$1}',u
'f0{$1}',u
'span class="fbox"',],
601 u
'\\fboxrule':[u
'{$p!}',u
'f0{}',u
'ignored',],
602 u
'\\fboxsep':[u
'{$p!}',u
'f0{}',u
'ignored',],
603 u
'\\fcolorbox':[u
'{$p!}{$q!}{$1}',u
'f0{$1}',u
'span class="boxed" style="border-color: $p; background: $q;"',],
604 u
'\\frac':[u
'{$1}{$2}',u
'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}',u
'span class="fraction"',u
'span class="numerator"',u
'span class="denominator"',u
'span class="ignored"',],
605 u
'\\framebox':[u
'[$p!][$q!]{$1}',u
'f0{$1}',u
'span class="framebox align-$q" style="width: $p;"',],
606 u
'\\href':[u
'[$o]{$u!}{$t!}',u
'f0{$t}',u
'a href="$u"',],
607 u
'\\hspace':[u
'{$p!}',u
'f0{ }',u
'span class="hspace" style="width: $p;"',],
608 u
'\\leftroot':[u
'{$p!}',u
'f0{ }',u
'span class="leftroot" style="width: $p;px"',],
609 u
'\\nicefrac':[u
'{$1}{$2}',u
'f0{f1{$1}⁄f2{$2}}',u
'span class="fraction"',u
'sup class="numerator"',u
'sub class="denominator"',u
'span class="ignored"',],
610 u
'\\parbox':[u
'[$p!]{$w!}{$1}',u
'f0{1}',u
'div class="Boxed" style="width: $w;"',],
611 u
'\\raisebox':[u
'{$p!}{$1}',u
'f0{$1.font}',u
'span class="raisebox" style="vertical-align: $p;"',],
612 u
'\\renewenvironment':[u
'{$1!}{$2!}{$3!}',u
'',],
613 u
'\\rule':[u
'[$v!]{$w!}{$h!}',u
'f0/',u
'hr class="line" style="width: $w; height: $h;"',],
614 u
'\\scriptscriptstyle':[u
'{$1}',u
'f0{$1}',u
'span class="scriptscriptstyle"',],
615 u
'\\scriptstyle':[u
'{$1}',u
'f0{$1}',u
'span class="scriptstyle"',],
616 u
'\\sqrt':[u
'[$0]{$1}',u
'f0{f1{$0}f2{√}f4{(}f3{$1}f4{)}}',u
'span class="sqrt"',u
'sup class="root"',u
'span class="radical"',u
'span class="root"',u
'span class="ignored"',],
617 u
'\\stackrel':[u
'{$1}{$2}',u
'f0{f1{$1}f2{$2}}',u
'span class="stackrel"',u
'span class="upstackrel"',u
'span class="downstackrel"',],
618 u
'\\tbinom':[u
'{$1}{$2}',u
'(f0{f1{f2{$1}}f1{f2{ }}f1{f2{$2}}})',u
'span class="binomial"',u
'span class="binomrow"',u
'span class="binomcell"',],
619 u
'\\textcolor':[u
'{$p!}{$1}',u
'f0{$1}',u
'span style="color: $p;"',],
620 u
'\\textstyle':[u
'{$1}',u
'f0{$1}',u
'span class="textstyle"',],
621 u
'\\unit':[u
'[$0]{$1}',u
'$0f0{$1.font}',u
'span class="unit"',],
622 u
'\\unitfrac':[u
'[$0]{$1}{$2}',u
'$0f0{f1{$1.font}⁄f2{$2.font}}',u
'span class="fraction"',u
'sup class="unit"',u
'sub class="unit"',],
623 u
'\\uproot':[u
'{$p!}',u
'f0{ }',u
'span class="uproot" style="width: $p;px"',],
624 u
'\\url':[u
'{$u!}',u
'f0{$u}',u
'a href="$u"',],
625 u
'\\vspace':[u
'{$p!}',u
'f0{ }',u
'span class="vspace" style="height: $p;"',],
629 u
'\\binom':u
'$1+$2', u
'\\cfrac':u
'$1+$2', u
'\\dbinom':u
'$1+$2+1',
630 u
'\\dfrac':u
'$1+$2', u
'\\frac':u
'$1+$2', u
'\\tbinom':u
'$1+$2+1',
634 u
'\\label':u
'a name="#"',
638 u
'\\int':u
'∫', u
'\\intop':u
'∫', u
'\\lim':u
'lim', u
'\\prod':u
'∏',
639 u
'\\smallint':u
'∫', u
'\\sum':u
'∑',
643 u
'\\limits':u
'LimitPreviousCommand', u
'\\newcommand':u
'MacroDefinition',
644 u
'\\renewcommand':u
'MacroDefinition',
645 u
'\\setcounter':u
'SetCounterFunction', u
'\\tag':u
'FormulaTag',
646 u
'\\tag*':u
'FormulaTag',
650 u
'\n':u
'', u
' ':u
'', u
'$':u
'', u
'&':u
' ', u
'\'':u
'’', u
'+':u
' + ',
651 u
',':u
', ', u
'-':u
' − ', u
'/':u
' ⁄ ', u
'<':u
' < ', u
'=':u
' = ',
652 u
'>':u
' > ', u
'@':u
'', u
'~':u
'',
656 u
'\\Big':u
'span class="bigsymbol"', u
'\\Bigg':u
'span class="hugesymbol"',
657 u
'\\bar':u
'span class="bar"', u
'\\begin{array}':u
'span class="arraydef"',
658 u
'\\big':u
'span class="symbol"', u
'\\bigg':u
'span class="largesymbol"',
659 u
'\\bigl':u
'span class="bigsymbol"', u
'\\bigr':u
'span class="bigsymbol"',
660 u
'\\centering':u
'span class="align-center"',
661 u
'\\ensuremath':u
'span class="ensuremath"',
662 u
'\\hphantom':u
'span class="phantom"',
663 u
'\\noindent':u
'span class="noindent"',
664 u
'\\overbrace':u
'span class="overbrace"',
665 u
'\\overline':u
'span class="overline"',
666 u
'\\phantom':u
'span class="phantom"',
667 u
'\\underbrace':u
'span class="underbrace"', u
'\\underline':u
'u',
668 u
'\\vphantom':u
'span class="phantom"',
672 u
'\\Leftrightarrow':u
'⇔', u
'\\Rightarrow':u
'⇒', u
'\\approx':u
'≈',
673 u
'\\dashrightarrow':u
'⇢', u
'\\equiv':u
'≡', u
'\\ge':u
'≥', u
'\\geq':u
'≥',
674 u
'\\implies':u
' ⇒ ', u
'\\in':u
'∈', u
'\\le':u
'≤', u
'\\leftarrow':u
'←',
675 u
'\\leq':u
'≤', u
'\\ne':u
'≠', u
'\\neq':u
'≠', u
'\\not\\in':u
'∉',
676 u
'\\propto':u
'∝', u
'\\rightarrow':u
'→', u
'\\rightsquigarrow':u
'⇝',
677 u
'\\sim':u
'~', u
'\\subset':u
'⊂', u
'\\subseteq':u
'⊆', u
'\\supset':u
'⊃',
678 u
'\\supseteq':u
'⊇', u
'\\times':u
'×', u
'\\to':u
'→',
682 u
'beginafter':u
'}', u
'beginbefore':u
'\\begin{', u
'bracket':u
'{',
683 u
'command':u
'\\', u
'comment':u
'%', u
'complex':u
'\\[', u
'simple':u
'$',
684 u
'squarebracket':u
'[', u
'unnumbered':u
'*',
688 u
'^':u
'sup', u
'_':u
'sub',
692 u
'\\mbox':u
'span class="mbox"', u
'\\text':u
'span class="text"',
693 u
'\\textbf':u
'b', u
'\\textipa':u
'span class="textipa"', u
'\\textit':u
'i',
694 u
'\\textnormal':u
'span class="textnormal"',
695 u
'\\textrm':u
'span class="textrm"',
696 u
'\\textsc':u
'span class="versalitas"',
697 u
'\\textsf':u
'span class="textsf"', u
'\\textsl':u
'i', u
'\\texttt':u
'tt',
698 u
'\\textup':u
'span class="normal"',
703 u
'characters':[u
'.',u
'*',u
'€',u
'(',u
')',u
'[',u
']',u
':',u
'·',u
'!',u
';',u
'|',u
'§',u
'"',],
707 u
'googlecharts':u
'http://chart.googleapis.com/chart?cht=tx&chl=',
710 class GeneralConfig(object):
711 "Configuration class from elyxer.config file"
714 u
'date':u
'2011-06-27', u
'lyxformat':u
'413', u
'number':u
'1.2.3',
717 class HeaderConfig(object):
718 "Configuration class from elyxer.config file"
721 u
'beginpreamble':u
'\\begin_preamble', u
'branch':u
'\\branch',
722 u
'documentclass':u
'\\textclass', u
'endbranch':u
'\\end_branch',
723 u
'endpreamble':u
'\\end_preamble', u
'language':u
'\\language',
724 u
'lstset':u
'\\lstset', u
'outputchanges':u
'\\output_changes',
725 u
'paragraphseparation':u
'\\paragraph_separation',
726 u
'pdftitle':u
'\\pdf_title', u
'secnumdepth':u
'\\secnumdepth',
727 u
'tocdepth':u
'\\tocdepth',
732 u
'article':[u
'article',u
'aastex',u
'aapaper',u
'acmsiggraph',u
'sigplanconf',u
'achemso',u
'amsart',u
'apa',u
'arab-article',u
'armenian-article',u
'article-beamer',u
'chess',u
'dtk',u
'elsarticle',u
'heb-article',u
'IEEEtran',u
'iopart',u
'kluwer',u
'scrarticle-beamer',u
'scrartcl',u
'extarticle',u
'paper',u
'mwart',u
'revtex4',u
'spie',u
'svglobal3',u
'ltugboat',u
'agu-dtd',u
'jgrga',u
'agums',u
'entcs',u
'egs',u
'ijmpc',u
'ijmpd',u
'singlecol-new',u
'doublecol-new',u
'isprs',u
'tarticle',u
'jsarticle',u
'jarticle',u
'jss',u
'literate-article',u
'siamltex',u
'cl2emult',u
'llncs',u
'svglobal',u
'svjog',u
'svprobth',],
733 u
'book':[u
'book',u
'amsbook',u
'scrbook',u
'extbook',u
'tufte-book',u
'report',u
'extreport',u
'scrreprt',u
'memoir',u
'tbook',u
'jsbook',u
'jbook',u
'mwbk',u
'svmono',u
'svmult',u
'treport',u
'jreport',u
'mwrep',],
736 class ImageConfig(object):
737 "Configuration class from elyxer.config file"
741 u
'imagemagick':u
'convert[ -density $scale][ -define $format:use-cropbox=true] "$input" "$output"',
742 u
'inkscape':u
'inkscape "$input" --export-png="$output"',
746 u
'.eps':u
'ps', u
'.pdf':u
'pdf', u
'.ps':u
'ps',
750 u
'default':u
'.png', u
'vector':[u
'.svg',u
'.eps',],
753 class LayoutConfig(object):
754 "Configuration class from elyxer.config file"
758 u
'allowed':[u
'StringContainer',u
'Constant',u
'TaggedText',u
'Align',u
'TextFamily',u
'EmphaticText',u
'VersalitasText',u
'BarredText',u
'SizeText',u
'ColorText',u
'LangLine',u
'Formula',],
761 class NewfangleConfig(object):
762 "Configuration class from elyxer.config file"
765 u
'chunkref':u
'chunkref{', u
'endcommand':u
'}', u
'endmark':u
'>',
766 u
'startcommand':u
'\\', u
'startmark':u
'=<',
769 class NumberingConfig(object):
770 "Configuration class from elyxer.config file"
774 u
'ordered':[u
'Chapter',u
'Section',u
'Subsection',u
'Subsubsection',u
'Paragraph',],
775 u
'roman':[u
'Part',u
'Book',],
779 u
'symbols':[u
'*',u
'**',u
'†',u
'‡',u
'§',u
'§§',u
'¶',u
'¶¶',u
'#',u
'##',],
782 class StyleConfig(object):
783 "Configuration class from elyxer.config file"
786 u
'\\enskip{}':u
' ', u
'\\hfill{}':u
'<span class="hfill"> </span>',
787 u
'\\hspace*{\\fill}':u
' ', u
'\\hspace*{}':u
'', u
'\\hspace{}':u
' ',
788 u
'\\negthinspace{}':u
'', u
'\\qquad{}':u
' ', u
'\\quad{}':u
' ',
789 u
'\\space{}':u
' ', u
'\\thinspace{}':u
' ', u
'~':u
' ',
793 u
'ald':u
'»', u
'als':u
'›', u
'ard':u
'«', u
'ars':u
'‹', u
'eld':u
'“',
794 u
'els':u
'‘', u
'erd':u
'”', u
'ers':u
'’', u
'fld':u
'«',
795 u
'fls':u
'‹', u
'frd':u
'»', u
'frs':u
'›', u
'gld':u
'„', u
'gls':u
'‚',
796 u
'grd':u
'“', u
'grs':u
'‘', u
'pld':u
'„', u
'pls':u
'‚', u
'prd':u
'”',
797 u
'prs':u
'’', u
'sld':u
'”', u
'srd':u
'”',
801 u
'eqref':u
'(@↕)', u
'formatted':u
'¶↕', u
'nameref':u
'$↕', u
'pageref':u
'#↕',
802 u
'ref':u
'@↕', u
'vpageref':u
'on-page#↕', u
'vref':u
'@on-page#↕',
806 u
'ignoredtexts':[u
'col',u
'text',u
'line',u
'page',u
'theight',u
'pheight',],
810 u
'bigskip':u
'<div class="bigskip"> </div>',
811 u
'defskip':u
'<div class="defskip"> </div>',
812 u
'medskip':u
'<div class="medskip"> </div>',
813 u
'smallskip':u
'<div class="smallskip"> </div>',
814 u
'vfill':u
'<div class="vfill"> </div>',
817 class TOCConfig(object):
818 "Configuration class from elyxer.config file"
822 u
'allowed':[u
'StringContainer',u
'Constant',u
'TaggedText',u
'Align',u
'TextFamily',u
'EmphaticText',u
'VersalitasText',u
'BarredText',u
'SizeText',u
'ColorText',u
'LangLine',u
'Formula',],
823 u
'cloned':[u
'',], u
'extracted':[u
'',],
827 u
'allowed':[u
'StringContainer',u
'Constant',u
'Space',],
828 u
'cloned':[u
'TextFamily',u
'EmphaticText',u
'VersalitasText',u
'BarredText',u
'SizeText',u
'ColorText',u
'LangLine',u
'Formula',],
829 u
'extracted':[u
'PlainLayout',u
'TaggedText',u
'Align',u
'Caption',u
'StandardLayout',u
'FlexInset',],
832 class TagConfig(object):
833 "Configuration class from elyxer.config file"
840 u
'sans':u
'span class="sans"', u
'typewriter':u
'tt',
844 u
'CharStyle:Code':u
'span class="code"',
845 u
'CharStyle:MenuItem':u
'span class="menuitem"',
846 u
'Code':u
'span class="code"', u
'MenuItem':u
'span class="menuitem"',
847 u
'Noun':u
'span class="noun"', u
'Strong':u
'span class="strong"',
851 u
'layouts':[u
'Quotation',u
'Quote',],
855 u
'Center':u
'div', u
'Chapter':u
'h?', u
'Date':u
'h2', u
'Paragraph':u
'div',
856 u
'Part':u
'h1', u
'Quotation':u
'blockquote', u
'Quote':u
'blockquote',
857 u
'Section':u
'h?', u
'Subsection':u
'h?', u
'Subsubsection':u
'h?',
861 u
'Enumerate':u
'ol', u
'Itemize':u
'ul',
865 u
'Comment':u
'', u
'Greyedout':u
'span class="greyedout"', u
'Note':u
'',
869 u
'italic':u
'i', u
'slanted':u
'i', u
'smallcaps':u
'span class="versalitas"',
872 class TranslationConfig(object):
873 "Configuration class from elyxer.config file"
876 u
'Appendix':u
'Appendix', u
'Book':u
'Book', u
'Chapter':u
'Chapter',
877 u
'Paragraph':u
'Paragraph', u
'Part':u
'Part', u
'Section':u
'Section',
878 u
'Subsection':u
'Subsection', u
'Subsubsection':u
'Subsubsection',
879 u
'abstract':u
'Abstract', u
'bibliography':u
'Bibliography',
880 u
'figure':u
'figure', u
'float-algorithm':u
'Algorithm ',
881 u
'float-figure':u
'Figure ', u
'float-listing':u
'Listing ',
882 u
'float-table':u
'Table ', u
'float-tableau':u
'Tableau ',
883 u
'footnotes':u
'Footnotes', u
'generated-by':u
'Document generated by ',
884 u
'generated-on':u
' on ', u
'index':u
'Index',
885 u
'jsmath-enable':u
'Please enable JavaScript on your browser.',
886 u
'jsmath-requires':u
' requires JavaScript to correctly process the mathematics on this page. ',
887 u
'jsmath-warning':u
'Warning: ', u
'list-algorithm':u
'List of Algorithms',
888 u
'list-figure':u
'List of Figures', u
'list-table':u
'List of Tables',
889 u
'list-tableau':u
'List of Tableaux', u
'main-page':u
'Main page',
890 u
'next':u
'Next', u
'nomenclature':u
'Nomenclature',
891 u
'on-page':u
' on page ', u
'prev':u
'Prev', u
'references':u
'References',
892 u
'toc':u
'Table of Contents', u
'toc-for':u
'Contents for ', u
'up':u
'Up',
896 u
'american':u
'en', u
'british':u
'en', u
'deutsch':u
'de', u
'dutch':u
'nl',
897 u
'english':u
'en', u
'french':u
'fr', u
'ngerman':u
'de', u
'spanish':u
'es',
905 class CommandLineParser(object):
906 "A parser for runtime options"
908 def __init__(self
, options
):
909 self
.options
= options
911 def parseoptions(self
, args
):
912 "Parse command line options"
915 while len(args
) > 0 and args
[0].startswith('--'):
916 key
, value
= self
.readoption(args
)
918 return 'Option ' + value
+ ' not recognized'
920 return 'Option ' + key
+ ' needs a value'
921 setattr(self
.options
, key
, value
)
924 def readoption(self
, args
):
925 "Read the key and value for an option"
929 key
= self
.readequalskey(arg
, args
)
931 key
= arg
.replace('-', '')
932 if not hasattr(self
.options
, key
):
934 current
= getattr(self
.options
, key
)
935 if isinstance(current
, bool):
940 if args
[0].startswith('"'):
943 return key
, self
.readquoted(args
, initial
)
946 if isinstance(current
, list):
947 current
.append(value
)
951 def readquoted(self
, args
, initial
):
952 "Read a value between quotes"
954 while len(args
) > 0 and not args
[0].endswith('"') and not args
[0].startswith('--'):
955 value
+= ' ' + args
[0]
957 if len(args
) == 0 or args
[0].startswith('--'):
959 value
+= ' ' + args
[0:-1]
962 def readequalskey(self
, arg
, args
):
963 "Read a key using equals"
964 split
= arg
.split('=', 1)
967 args
.insert(0, value
)
972 class Options(object):
973 "A set of runtime options"
1004 converter
= 'imagemagick'
1024 googlecharts
= False
1029 def parseoptions(self
, args
):
1030 "Parse command line options"
1031 Options
.location
= args
[0]
1033 parser
= CommandLineParser(Options
)
1034 result
= parser
.parseoptions(args
)
1038 self
.processoptions()
1040 def processoptions(self
):
1041 "Process all options parsed."
1046 if Options
.hardversion
:
1047 self
.showhardversion()
1048 if Options
.versiondate
:
1049 self
.showversiondate()
1050 if Options
.lyxformat
:
1051 self
.showlyxformat()
1052 if Options
.splitpart
:
1054 Options
.splitpart
= int(Options
.splitpart
)
1055 if Options
.splitpart
<= 0:
1056 Trace
.error('--splitpart requires a number bigger than zero')
1059 Trace
.error('--splitpart needs a numeric argument, not ' + Options
.splitpart
)
1061 if Options
.lowmem
or Options
.toc
or Options
.tocfor
:
1062 Options
.memory
= False
1063 self
.parsefootnotes()
1064 if Options
.forceformat
and not Options
.imageformat
:
1065 Options
.imageformat
= Options
.forceformat
1066 if Options
.imageformat
== 'copy':
1067 Options
.copyimages
= True
1068 if Options
.css
== []:
1069 Options
.css
= ['http://elyxer.nongnu.org/lyx.css']
1071 Options
.simplemath
= True
1072 if Options
.toc
and not Options
.tocfor
:
1073 Trace
.error('Option --toc is deprecated; use --tocfor "page" instead')
1074 Options
.tocfor
= Options
.toctarget
1076 Trace
.error('Option --nocopy is deprecated; it is no longer needed')
1077 # set in Trace if necessary
1078 for param
in dir(Trace
):
1079 if param
.endswith('mode'):
1080 setattr(Trace
, param
, getattr(self
, param
[:-4]))
1083 "Show correct usage"
1084 Trace
.error('Usage: ' + os
.path
.basename(Options
.location
) + ' [options] [filein] [fileout]')
1085 Trace
.error('Convert LyX input file "filein" to HTML file "fileout".')
1086 Trace
.error('If filein (or fileout) is not given use standard input (or output).')
1087 Trace
.error('Main program of the eLyXer package (http://elyxer.nongnu.org/).')
1090 def parsefootnotes(self
):
1091 "Parse footnotes options."
1092 if not Options
.footnotes
:
1094 Options
.marginfoot
= False
1095 Options
.letterfoot
= False
1096 options
= Options
.footnotes
.split(',')
1097 for option
in options
:
1098 footoption
= option
+ 'foot'
1099 if hasattr(Options
, footoption
):
1100 setattr(Options
, footoption
, True)
1102 Trace
.error('Unknown footnotes option: ' + option
)
1103 if not Options
.endfoot
and not Options
.marginfoot
and not Options
.hoverfoot
:
1104 Options
.hoverfoot
= True
1105 if not Options
.numberfoot
and not Options
.symbolfoot
:
1106 Options
.letterfoot
= True
1108 def showoptions(self
):
1109 "Show all possible options"
1110 Trace
.error(' Common options:')
1111 Trace
.error(' --help: show this online help')
1112 Trace
.error(' --quiet: disables all runtime messages')
1114 Trace
.error(' Advanced options:')
1115 Trace
.error(' --debug: enable debugging messages (for developers)')
1116 Trace
.error(' --version: show version number and release date')
1117 Trace
.error(' --lyxformat: return the highest LyX version supported')
1118 Trace
.error(' Options for HTML output:')
1119 Trace
.error(' --title "title": set the generated page title')
1120 Trace
.error(' --css "file.css": use a custom CSS file')
1121 Trace
.error(' --embedcss "file.css": embed styles from elyxer.a CSS file into the output')
1122 Trace
.error(' --html: output HTML 4.0 instead of the default XHTML')
1123 Trace
.error(' --unicode: full Unicode output')
1124 Trace
.error(' --iso885915: output a document with ISO-8859-15 encoding')
1125 Trace
.error(' --nofooter: remove the footer "generated by eLyXer"')
1126 Trace
.error(' --simplemath: do not generate fancy math constructions')
1127 Trace
.error(' Options for image output:')
1128 Trace
.error(' --directory "img_dir": look for images in the specified directory')
1129 Trace
.error(' --destdirectory "dest": put converted images into this directory')
1130 Trace
.error(' --imageformat ".ext": image output format, or "copy" to copy images')
1131 Trace
.error(' --noconvert: do not convert images, use in original locations')
1132 Trace
.error(' --converter "inkscape": use an alternative program to convert images')
1133 Trace
.error(' Options for footnote display:')
1134 Trace
.error(' --numberfoot: mark footnotes with numbers instead of letters')
1135 Trace
.error(' --symbolfoot: mark footnotes with symbols (*, **...)')
1136 Trace
.error(' --hoverfoot: show footnotes as hovering text (default)')
1137 Trace
.error(' --marginfoot: show footnotes on the page margin')
1138 Trace
.error(' --endfoot: show footnotes at the end of the page')
1139 Trace
.error(' --supfoot: use superscript for footnote markers (default)')
1140 Trace
.error(' --alignfoot: use aligned text for footnote markers')
1141 Trace
.error(' --footnotes "options": specify several comma-separated footnotes options')
1142 Trace
.error(' Available options are: "number", "symbol", "hover", "margin", "end",')
1143 Trace
.error(' "sup", "align"')
1144 Trace
.error(' Advanced output options:')
1145 Trace
.error(' --splitpart "depth": split the resulting webpage at the given depth')
1146 Trace
.error(' --tocfor "page": generate a TOC that points to the given page')
1147 Trace
.error(' --target "frame": make all links point to the given frame')
1148 Trace
.error(' --notoclabels: omit the part labels in the TOC, such as Chapter')
1149 Trace
.error(' --lowmem: do the conversion on the fly (conserve memory)')
1150 Trace
.error(' --raw: generate HTML without header or footer.')
1151 Trace
.error(' --jsmath "URL": use jsMath from elyxer.the given URL to display equations')
1152 Trace
.error(' --mathjax "URL": use MathJax from elyxer.the given URL to display equations')
1153 Trace
.error(' --googlecharts: use Google Charts to generate formula images')
1154 Trace
.error(' --template "file": use a template, put everything in <!--$content-->')
1155 Trace
.error(' --copyright: add a copyright notice at the bottom')
1156 Trace
.error(' Deprecated options:')
1157 Trace
.error(' --toc: (deprecated) create a table of contents')
1158 Trace
.error(' --toctarget "page": (deprecated) generate a TOC for the given page')
1159 Trace
.error(' --nocopy: (deprecated) maintained for backwards compatibility')
1162 def showversion(self
):
1163 "Return the current eLyXer version string"
1164 string
= 'eLyXer version ' + GeneralConfig
.version
['number']
1165 string
+= ' (' + GeneralConfig
.version
['date'] + ')'
1169 def showhardversion(self
):
1170 "Return just the version string"
1171 Trace
.message(GeneralConfig
.version
['number'])
1174 def showversiondate(self
):
1175 "Return just the version dte"
1176 Trace
.message(GeneralConfig
.version
['date'])
1179 def showlyxformat(self
):
1180 "Return just the lyxformat parameter"
1181 Trace
.message(GeneralConfig
.version
['lyxformat'])
1184 class BranchOptions(object):
1185 "A set of options for a branch"
1187 def __init__(self
, name
):
1189 self
.options
= {'color':'#ffffff'}
1191 def set(self
, key
, value
):
1192 "Set a branch option"
1193 if not key
.startswith(ContainerConfig
.string
['startcommand']):
1194 Trace
.error('Invalid branch option ' + key
)
1196 key
= key
.replace(ContainerConfig
.string
['startcommand'], '')
1197 self
.options
[key
] = value
1199 def isselected(self
):
1200 "Return if the branch is selected"
1201 if not 'selected' in self
.options
:
1203 return self
.options
['selected'] == '1'
1205 def __unicode__(self
):
1206 "String representation"
1207 return 'options for ' + self
.name
+ ': ' + unicode(self
.options
)
1221 class Cloner(object):
1222 "An object used to clone other objects."
1224 def clone(cls
, original
):
1225 "Return an exact copy of an object."
1226 "The original object must have an empty constructor."
1227 return cls
.create(original
.__class
__)
1229 def create(cls
, type):
1230 "Create an object of a given class."
1231 clone
= type.__new
__(type)
1235 clone
= classmethod(clone
)
1236 create
= classmethod(create
)
1238 class ContainerExtractor(object):
1239 "A class to extract certain containers."
1241 def __init__(self
, config
):
1242 "The config parameter is a map containing three lists: allowed, copied and extracted."
1243 "Each of the three is a list of class names for containers."
1244 "Allowed containers are included as is into the result."
1245 "Cloned containers are cloned and placed into the result."
1246 "Extracted containers are looked into."
1247 "All other containers are silently ignored."
1248 self
.allowed
= config
['allowed']
1249 self
.cloned
= config
['cloned']
1250 self
.extracted
= config
['extracted']
1252 def extract(self
, container
):
1253 "Extract a group of selected containers from elyxer.a container."
1255 locate
= lambda c
: c
.__class
__.__name
__ in self
.allowed
+ self
.cloned
1256 recursive
= lambda c
: c
.__class
__.__name
__ in self
.extracted
1257 process
= lambda c
: self
.process(c
, list)
1258 container
.recursivesearch(locate
, recursive
, process
)
1261 def process(self
, container
, list):
1262 "Add allowed containers, clone cloned containers and add the clone."
1263 name
= container
.__class
__.__name
__
1264 if name
in self
.allowed
:
1265 list.append(container
)
1266 elif name
in self
.cloned
:
1267 list.append(self
.safeclone(container
))
1269 Trace
.error('Unknown container class ' + name
)
1271 def safeclone(self
, container
):
1272 "Return a new container with contents only in a safe list, recursively."
1273 clone
= Cloner
.clone(container
)
1274 clone
.output
= container
.output
1275 clone
.contents
= self
.extract(container
)
1283 class Parser(object):
1288 self
.parameters
= dict()
1290 def parseheader(self
, reader
):
1292 header
= reader
.currentline().split()
1294 self
.begin
= reader
.linenumber
1297 def parseparameter(self
, reader
):
1299 if reader
.currentline().strip().startswith('<'):
1300 key
, value
= self
.parsexml(reader
)
1301 self
.parameters
[key
] = value
1303 split
= reader
.currentline().strip().split(' ', 1)
1309 self
.parameters
[key
] = True
1311 if not '"' in split
[1]:
1312 self
.parameters
[key
] = split
[1].strip()
1314 doublesplit
= split
[1].split('"')
1315 self
.parameters
[key
] = doublesplit
[1]
1317 def parsexml(self
, reader
):
1318 "Parse a parameter in xml form: <param attr1=value...>"
1319 strip
= reader
.currentline().strip()
1321 if not strip
.endswith('>'):
1322 Trace
.error('XML parameter ' + strip
+ ' should be <...>')
1323 split
= strip
[1:-1].split()
1325 Trace
.error('Empty XML parameter <>')
1334 Trace
.error('Erroneous attribute for ' + key
+ ': ' + attr
)
1336 parts
= attr
.split('=')
1338 value
= parts
[1].split('"')[1]
1339 attrs
[attrkey
] = value
1342 def parseending(self
, reader
, process
):
1343 "Parse until the current ending is found"
1345 Trace
.error('No ending for ' + unicode(self
))
1347 while not reader
.currentline().startswith(self
.ending
):
1350 def parsecontainer(self
, reader
, contents
):
1351 container
= self
.factory
.createcontainer(reader
)
1353 container
.parent
= self
.parent
1354 contents
.append(container
)
1356 def __unicode__(self
):
1357 "Return a description"
1358 return self
.__class
__.__name
__ + ' (' + unicode(self
.begin
) + ')'
1360 class LoneCommand(Parser
):
1361 "A parser for just one command line"
1363 def parse(self
,reader
):
1367 class TextParser(Parser
):
1368 "A parser for a command and a bit of text"
1372 def __init__(self
, container
):
1373 Parser
.__init
__(self
)
1375 if container
.__class
__.__name
__ in ContainerConfig
.endings
:
1376 self
.ending
= ContainerConfig
.endings
[container
.__class
__.__name
__]
1379 def parse(self
, reader
):
1380 "Parse lines as long as they are text"
1381 TextParser
.stack
.append(self
.ending
)
1382 self
.endings
= TextParser
.stack
+ [ContainerConfig
.endings
['Layout'],
1383 ContainerConfig
.endings
['Inset'], self
.ending
]
1385 while not self
.isending(reader
):
1386 self
.parsecontainer(reader
, contents
)
1389 def isending(self
, reader
):
1390 "Check if text is ending"
1391 current
= reader
.currentline().split()
1392 if len(current
) == 0:
1394 if current
[0] in self
.endings
:
1395 if current
[0] in TextParser
.stack
:
1396 TextParser
.stack
.remove(current
[0])
1398 TextParser
.stack
= []
1402 class ExcludingParser(Parser
):
1403 "A parser that excludes the final line"
1405 def parse(self
, reader
):
1406 "Parse everything up to (and excluding) the final line"
1408 self
.parseending(reader
, lambda: self
.parsecontainer(reader
, contents
))
1411 class BoundedParser(ExcludingParser
):
1412 "A parser bound by a final line"
1414 def parse(self
, reader
):
1415 "Parse everything, including the final line"
1416 contents
= ExcludingParser
.parse(self
, reader
)
1421 class BoundedDummy(Parser
):
1422 "A bound parser that ignores everything"
1424 def parse(self
, reader
):
1425 "Parse the contents of the container"
1426 self
.parseending(reader
, lambda: reader
.nextline())
1431 class StringParser(Parser
):
1432 "Parses just a string"
1434 def parseheader(self
, reader
):
1435 "Do nothing, just take note"
1436 self
.begin
= reader
.linenumber
+ 1
1439 def parse(self
, reader
):
1440 "Parse a single line"
1441 contents
= reader
.currentline()
1445 class InsetParser(BoundedParser
):
1446 "Parses a LyX inset"
1448 def parse(self
, reader
):
1449 "Parse inset parameters into a dictionary"
1450 startcommand
= ContainerConfig
.string
['startcommand']
1451 while reader
.currentline() != '' and not reader
.currentline().startswith(startcommand
):
1452 self
.parseparameter(reader
)
1453 return BoundedParser
.parse(self
, reader
)
1460 class ContainerOutput(object):
1461 "The generic HTML output for a container."
1463 def gethtml(self
, container
):
1465 Trace
.error('gethtml() not implemented for ' + unicode(self
))
1468 "Decide if the output is empty: by default, not empty."
1471 class EmptyOutput(ContainerOutput
):
1473 def gethtml(self
, container
):
1474 "Return empty HTML code."
1478 "This output is particularly empty."
1481 class FixedOutput(ContainerOutput
):
1484 def gethtml(self
, container
):
1485 "Return constant HTML code"
1486 return container
.html
1488 class ContentsOutput(ContainerOutput
):
1489 "Outputs the contents converted to HTML"
1491 def gethtml(self
, container
):
1492 "Return the HTML code"
1494 if container
.contents
== None:
1496 for element
in container
.contents
:
1497 if not hasattr(element
, 'gethtml'):
1498 Trace
.error('No html in ' + element
.__class
__.__name
__ + ': ' + unicode(element
))
1500 html
+= element
.gethtml()
1503 class TaggedOutput(ContentsOutput
):
1504 "Outputs an HTML tag surrounding the contents."
1510 def settag(self
, tag
, breaklines
=False, empty
=False):
1511 "Set the value for the tag and other attributes."
1514 self
.breaklines
= breaklines
1519 def setbreaklines(self
, breaklines
):
1520 "Set the value for breaklines."
1521 self
.breaklines
= breaklines
1524 def gethtml(self
, container
):
1525 "Return the HTML code."
1527 return [self
.selfclosing(container
)]
1528 html
= [self
.open(container
)]
1529 html
+= ContentsOutput
.gethtml(self
, container
)
1530 html
.append(self
.close(container
))
1533 def open(self
, container
):
1535 if not self
.checktag():
1537 open = '<' + self
.tag
+ '>'
1542 def close(self
, container
):
1544 if not self
.checktag():
1546 close
= '</' + self
.tag
.split()[0] + '>'
1548 return '\n' + close
+ '\n'
1551 def selfclosing(self
, container
):
1552 "Get self-closing line."
1553 if not self
.checktag():
1555 selfclosing
= '<' + self
.tag
+ '/>'
1557 return selfclosing
+ '\n'
1561 "Check that the tag is valid."
1563 Trace
.error('No tag in ' + unicode(container
))
1569 class FilteredOutput(ContentsOutput
):
1570 "Returns the output in the contents, but filtered:"
1571 "some strings are replaced by others."
1574 "Initialize the filters."
1577 def addfilter(self
, original
, replacement
):
1578 "Add a new filter: replace the original by the replacement."
1579 self
.filters
.append((original
, replacement
))
1581 def gethtml(self
, container
):
1582 "Return the HTML code"
1584 html
= ContentsOutput
.gethtml(self
, container
)
1586 result
.append(self
.filter(line
))
1589 def filter(self
, line
):
1590 "Filter a single line with all available filters."
1591 for original
, replacement
in self
.filters
:
1592 if original
in line
:
1593 line
= line
.replace(original
, replacement
)
1596 class StringOutput(ContainerOutput
):
1597 "Returns a bare string as output"
1599 def gethtml(self
, container
):
1600 "Return a bare string"
1601 return [container
.string
]
1613 class LineReader(object):
1614 "Reads a file line by line"
1616 def __init__(self
, filename
):
1617 if isinstance(filename
, file):
1618 self
.file = filename
1620 self
.file = codecs
.open(filename
, 'rU', 'utf-8')
1622 self
.lastline
= None
1624 self
.mustread
= True
1625 self
.depleted
= False
1628 except UnicodeDecodeError:
1629 # try compressed file
1631 self
.file = gzip
.open(filename
, 'rb')
1634 def setstart(self
, firstline
):
1635 "Set the first line to read."
1636 for i
in range(firstline
):
1637 self
.file.readline()
1638 self
.linenumber
= firstline
1640 def setend(self
, lastline
):
1641 "Set the last line to read."
1642 self
.lastline
= lastline
1644 def currentline(self
):
1645 "Get the current line"
1653 Trace
.fatal('Read beyond file end')
1654 self
.mustread
= True
1657 "Read a line from elyxer.file"
1658 self
.current
= self
.file.readline()
1659 if not isinstance(self
.file, codecs
.StreamReaderWriter
):
1660 self
.current
= self
.current
.decode('utf-8')
1661 if len(self
.current
) == 0:
1662 self
.depleted
= True
1663 self
.current
= self
.current
.rstrip('\n\r')
1664 self
.linenumber
+= 1
1665 self
.mustread
= False
1666 Trace
.prefix
= 'Line ' + unicode(self
.linenumber
) + ': '
1667 if self
.linenumber
% 1000 == 0:
1668 Trace
.message('Parsing')
1671 "Find out if the file is finished"
1672 if self
.lastline
and self
.linenumber
== self
.lastline
:
1676 return self
.depleted
1681 class LineWriter(object):
1682 "Writes a file as a series of lists"
1686 def __init__(self
, filename
):
1687 if isinstance(filename
, file):
1688 self
.file = filename
1689 self
.filename
= None
1691 self
.filename
= filename
1693 def write(self
, strings
):
1694 "Write a list of strings"
1695 for string
in strings
:
1696 if not isinstance(string
, basestring
):
1697 Trace
.error('Not a string: ' + unicode(string
) + ' in ' + unicode(strings
))
1699 self
.writestring(string
)
1701 def writestring(self
, string
):
1704 self
.file = codecs
.open(self
.filename
, 'w', "utf-8")
1705 if self
.file == sys
.stdout
and sys
.version_info
< (3,0):
1706 string
= string
.encode('utf-8')
1707 self
.file.write(string
)
1709 def writeline(self
, line
):
1710 "Write a line to file"
1711 self
.writestring(line
+ '\n')
1721 class Globable(object):
1722 """A bit of text which can be globbed (lumped together in bits).
1723 Methods current(), skipcurrent(), checkfor() and isout() have to be
1724 implemented by subclasses."""
1726 leavepending
= False
1729 self
.endinglist
= EndingList()
1731 def checkbytemark(self
):
1732 "Check for a Unicode byte mark and skip it."
1735 if ord(self
.current()) == 0xfeff:
1739 "Find out if we are out of the position yet."
1740 Trace
.error('Unimplemented isout()')
1744 "Return the current character."
1745 Trace
.error('Unimplemented current()')
1748 def checkfor(self
, string
):
1749 "Check for the given string in the current position."
1750 Trace
.error('Unimplemented checkfor()')
1754 "Find out if the current text has finished."
1756 if not self
.leavepending
:
1757 self
.endinglist
.checkpending()
1759 return self
.endinglist
.checkin(self
)
1761 def skipcurrent(self
):
1762 "Return the current character and skip it."
1763 Trace
.error('Unimplemented skipcurrent()')
1766 def glob(self
, currentcheck
):
1767 "Glob a bit of text that satisfies a check on the current char."
1769 while not self
.finished() and currentcheck():
1770 glob
+= self
.skipcurrent()
1773 def globalpha(self
):
1774 "Glob a bit of alpha text"
1775 return self
.glob(lambda: self
.current().isalpha())
1777 def globnumber(self
):
1778 "Glob a row of digits."
1779 return self
.glob(lambda: self
.current().isdigit())
1781 def isidentifier(self
):
1782 "Return if the current character is alphanumeric or _."
1783 if self
.current().isalnum() or self
.current() == '_':
1787 def globidentifier(self
):
1788 "Glob alphanumeric and _ symbols."
1789 return self
.glob(self
.isidentifier
)
1792 "Return if the current character is a value character:"
1793 "not a bracket or a space."
1794 if self
.current().isspace():
1796 if self
.current() in '{}()':
1800 def globvalue(self
):
1801 "Glob a value: any symbols but brackets."
1802 return self
.glob(self
.isvalue
)
1804 def skipspace(self
):
1805 "Skip all whitespace at current position."
1806 return self
.glob(lambda: self
.current().isspace())
1808 def globincluding(self
, magicchar
):
1809 "Glob a bit of text up to (including) the magic char."
1810 glob
= self
.glob(lambda: self
.current() != magicchar
) + magicchar
1811 self
.skip(magicchar
)
1814 def globexcluding(self
, excluded
):
1815 "Glob a bit of text up until (excluding) any excluded character."
1816 return self
.glob(lambda: self
.current() not in excluded
)
1818 def pushending(self
, ending
, optional
= False):
1819 "Push a new ending to the bottom"
1820 self
.endinglist
.add(ending
, optional
)
1822 def popending(self
, expected
= None):
1823 "Pop the ending found at the current position"
1824 if self
.isout() and self
.leavepending
:
1826 ending
= self
.endinglist
.pop(self
)
1827 if expected
and expected
!= ending
:
1828 Trace
.error('Expected ending ' + expected
+ ', got ' + ending
)
1832 def nextending(self
):
1833 "Return the next ending in the queue."
1834 nextending
= self
.endinglist
.findending(self
)
1837 return nextending
.ending
1839 class EndingList(object):
1840 "A list of position endings"
1845 def add(self
, ending
, optional
= False):
1846 "Add a new ending to the list"
1847 self
.endings
.append(PositionEnding(ending
, optional
))
1849 def pickpending(self
, pos
):
1850 "Pick any pending endings from a parse position."
1851 self
.endings
+= pos
.endinglist
.endings
1853 def checkin(self
, pos
):
1854 "Search for an ending"
1855 if self
.findending(pos
):
1860 "Remove the ending at the current position"
1862 Trace
.error('No ending out of bounds')
1864 ending
= self
.findending(pos
)
1866 Trace
.error('No ending at ' + pos
.current())
1868 for each
in reversed(self
.endings
):
1869 self
.endings
.remove(each
)
1872 elif not each
.optional
:
1873 Trace
.error('Removed non-optional ending ' + each
)
1874 Trace
.error('No endings left')
1877 def findending(self
, pos
):
1878 "Find the ending at the current position"
1879 if len(self
.endings
) == 0:
1881 for index
, ending
in enumerate(reversed(self
.endings
)):
1882 if ending
.checkin(pos
):
1884 if not ending
.optional
:
1888 def checkpending(self
):
1889 "Check if there are any pending endings"
1890 if len(self
.endings
) != 0:
1891 Trace
.error('Pending ' + unicode(self
) + ' left open')
1893 def __unicode__(self
):
1894 "Printable representation"
1895 string
= 'endings ['
1896 for ending
in self
.endings
:
1897 string
+= unicode(ending
) + ','
1898 if len(self
.endings
) > 0:
1899 string
= string
[:-1]
1902 class PositionEnding(object):
1903 "An ending for a parsing position"
1905 def __init__(self
, ending
, optional
):
1906 self
.ending
= ending
1907 self
.optional
= optional
1909 def checkin(self
, pos
):
1910 "Check for the ending"
1911 return pos
.checkfor(self
.ending
)
1913 def __unicode__(self
):
1914 "Printable representation"
1915 string
= 'Ending ' + self
.ending
1917 string
+= ' (optional)'
1922 class Position(Globable
):
1923 """A position in a text to parse.
1924 Including those in Globable, functions to implement by subclasses are:
1925 skip(), identifier(), extract(), isout() and current()."""
1928 Globable
.__init
__(self
)
1930 def skip(self
, string
):
1932 Trace
.error('Unimplemented skip()')
1934 def identifier(self
):
1935 "Return an identifier for the current position."
1936 Trace
.error('Unimplemented identifier()')
1939 def extract(self
, length
):
1940 "Extract the next string of the given length, or None if not enough text,"
1941 "without advancing the parse position."
1942 Trace
.error('Unimplemented extract()')
1945 def checkfor(self
, string
):
1946 "Check for a string at the given position."
1947 return string
== self
.extract(len(string
))
1949 def checkforlower(self
, string
):
1950 "Check for a string in lower case."
1951 extracted
= self
.extract(len(string
))
1954 return string
.lower() == self
.extract(len(string
)).lower()
1956 def skipcurrent(self
):
1957 "Return the current character and skip it."
1958 current
= self
.current()
1963 "Advance the position and return the next character."
1965 return self
.current()
1967 def checkskip(self
, string
):
1968 "Check for a string at the given position; if there, skip it"
1969 if not self
.checkfor(string
):
1974 def error(self
, message
):
1975 "Show an error message and the position identifier."
1976 Trace
.error(message
+ ': ' + self
.identifier())
1978 class TextPosition(Position
):
1979 "A parse position based on a raw text."
1981 def __init__(self
, text
):
1982 "Create the position from elyxer.some text."
1983 Position
.__init
__(self
)
1986 self
.checkbytemark()
1988 def skip(self
, string
):
1989 "Skip a string of characters."
1990 self
.pos
+= len(string
)
1992 def identifier(self
):
1993 "Return a sample of the remaining text."
1995 if self
.pos
+ length
> len(self
.text
):
1996 length
= len(self
.text
) - self
.pos
1997 return '*' + self
.text
[self
.pos
:self
.pos
+ length
] + '*'
2000 "Find out if we are out of the text yet."
2001 return self
.pos
>= len(self
.text
)
2004 "Return the current character, assuming we are not out."
2005 return self
.text
[self
.pos
]
2007 def extract(self
, length
):
2008 "Extract the next string of the given length, or None if not enough text."
2009 if self
.pos
+ length
> len(self
.text
):
2011 return self
.text
[self
.pos
: self
.pos
+ length
]
2013 class FilePosition(Position
):
2014 "A parse position based on an underlying file."
2016 def __init__(self
, filename
):
2017 "Create the position from a file."
2018 Position
.__init
__(self
)
2019 self
.reader
= LineReader(filename
)
2021 self
.checkbytemark()
2023 def skip(self
, string
):
2024 "Skip a string of characters."
2025 length
= len(string
)
2026 while self
.pos
+ length
> len(self
.reader
.currentline()):
2027 length
-= len(self
.reader
.currentline()) - self
.pos
+ 1
2031 def currentline(self
):
2032 "Get the current line of the underlying file."
2033 return self
.reader
.currentline()
2036 "Go to the next line."
2037 self
.reader
.nextline()
2040 def linenumber(self
):
2041 "Return the line number of the file."
2042 return self
.reader
.linenumber
+ 1
2044 def identifier(self
):
2045 "Return the current line and line number in the file."
2046 before
= self
.reader
.currentline()[:self
.pos
- 1]
2047 after
= self
.reader
.currentline()[self
.pos
:]
2048 return 'line ' + unicode(self
.getlinenumber()) + ': ' + before
+ '*' + after
2051 "Find out if we are out of the text yet."
2052 if self
.pos
> len(self
.reader
.currentline()):
2053 if self
.pos
> len(self
.reader
.currentline()) + 1:
2054 Trace
.error('Out of the line ' + self
.reader
.currentline() + ': ' + unicode(self
.pos
))
2056 return self
.reader
.finished()
2059 "Return the current character, assuming we are not out."
2060 if self
.pos
== len(self
.reader
.currentline()):
2062 if self
.pos
> len(self
.reader
.currentline()):
2063 Trace
.error('Out of the line ' + self
.reader
.currentline() + ': ' + unicode(self
.pos
))
2065 return self
.reader
.currentline()[self
.pos
]
2067 def extract(self
, length
):
2068 "Extract the next string of the given length, or None if not enough text."
2069 if self
.pos
+ length
> len(self
.reader
.currentline()):
2071 return self
.reader
.currentline()[self
.pos
: self
.pos
+ length
]
2075 class Container(object):
2076 "A container for text and objects in a lyx file"
2083 self
.contents
= list()
2090 "Get the resulting HTML"
2091 html
= self
.output
.gethtml(self
)
2092 if isinstance(html
, basestring
):
2093 Trace
.error('Raw string ' + html
)
2095 return self
.escapeall(html
)
2097 def escapeall(self
, lines
):
2098 "Escape all lines in an array according to the output options."
2102 line
= self
.escape(line
, EscapeConfig
.html
)
2103 if Options
.iso885915
:
2104 line
= self
.escape(line
, EscapeConfig
.iso885915
)
2105 line
= self
.escapeentities(line
)
2106 elif not Options
.unicode:
2107 line
= self
.escape(line
, EscapeConfig
.nonunicode
)
2111 def escape(self
, line
, replacements
= EscapeConfig
.entities
):
2112 "Escape a line with replacements from elyxer.a map"
2113 pieces
= replacements
.keys()
2116 for piece
in pieces
:
2118 line
= line
.replace(piece
, replacements
[piece
])
2121 def escapeentities(self
, line
):
2122 "Escape all Unicode characters to HTML entities."
2124 pos
= TextPosition(line
)
2125 while not pos
.finished():
2126 if ord(pos
.current()) > 128:
2127 codepoint
= hex(ord(pos
.current()))
2128 if codepoint
== '0xd835':
2129 codepoint
= hex(ord(pos
.next()) + 0xf800)
2130 result
+= '&#' + codepoint
[1:] + ';'
2132 result
+= pos
.current()
2136 def searchall(self
, type):
2137 "Search for all embedded containers of a given type"
2139 self
.searchprocess(type, lambda container
: list.append(container
))
2142 def searchremove(self
, type):
2143 "Search for all containers of a type and remove them"
2144 list = self
.searchall(type)
2145 for container
in list:
2146 container
.parent
.contents
.remove(container
)
2149 def searchprocess(self
, type, process
):
2150 "Search for elements of a given type and process them"
2151 self
.locateprocess(lambda container
: isinstance(container
, type), process
)
2153 def locateprocess(self
, locate
, process
):
2154 "Search for all embedded containers and process them"
2155 for container
in self
.contents
:
2156 container
.locateprocess(locate
, process
)
2157 if locate(container
):
2160 def recursivesearch(self
, locate
, recursive
, process
):
2161 "Perform a recursive search in the container."
2162 for container
in self
.contents
:
2163 if recursive(container
):
2164 container
.recursivesearch(locate
, recursive
, process
)
2165 if locate(container
):
2168 def extracttext(self
):
2169 "Extract all text from elyxer.allowed containers."
2171 constants
= ContainerExtractor(ContainerConfig
.extracttext
).extract(self
)
2172 for constant
in constants
:
2173 result
+= constant
.string
2176 def group(self
, index
, group
, isingroup
):
2177 "Group some adjoining elements into a group"
2178 if index
>= len(self
.contents
):
2180 if hasattr(self
.contents
[index
], 'grouped'):
2182 while index
< len(self
.contents
) and isingroup(self
.contents
[index
]):
2183 self
.contents
[index
].grouped
= True
2184 group
.contents
.append(self
.contents
[index
])
2185 self
.contents
.pop(index
)
2186 self
.contents
.insert(index
, group
)
2188 def remove(self
, index
):
2189 "Remove a container but leave its contents"
2190 container
= self
.contents
[index
]
2191 self
.contents
.pop(index
)
2192 while len(container
.contents
) > 0:
2193 self
.contents
.insert(index
, container
.contents
.pop())
2195 def tree(self
, level
= 0):
2197 Trace
.debug(" " * level
+ unicode(self
))
2198 for container
in self
.contents
:
2199 container
.tree(level
+ 1)
2201 def getparameter(self
, name
):
2202 "Get the value of a parameter, if present."
2203 if not name
in self
.parameters
:
2205 return self
.parameters
[name
]
2207 def getparameterlist(self
, name
):
2208 "Get the value of a comma-separated parameter as a list."
2209 paramtext
= self
.getparameter(name
)
2212 return paramtext
.split(',')
2214 def hasemptyoutput(self
):
2215 "Check if the parent's output is empty."
2216 current
= self
.parent
2218 if current
.output
.isempty():
2220 current
= current
.parent
2223 def __unicode__(self
):
2226 return self
.__class
__.__name
__
2227 return self
.__class
__.__name
__ + '@' + unicode(self
.begin
)
2229 class BlackBox(Container
):
2230 "A container that does not output anything"
2233 self
.parser
= LoneCommand()
2234 self
.output
= EmptyOutput()
2237 class LyXFormat(BlackBox
):
2238 "Read the lyxformat command"
2241 "Show warning if version < 276"
2242 version
= int(self
.header
[1])
2244 Trace
.error('Warning: unsupported old format version ' + str(version
))
2245 if version
> int(GeneralConfig
.version
['lyxformat']):
2246 Trace
.error('Warning: unsupported new format version ' + str(version
))
2248 class StringContainer(Container
):
2249 "A container for a single string"
2254 self
.parser
= StringParser()
2255 self
.output
= StringOutput()
2259 "Replace special chars from elyxer.the contents."
2261 self
.string
= self
.replacespecial(self
.parsed
)
2264 def replacespecial(self
, line
):
2265 "Replace all special chars from elyxer.a line"
2266 replaced
= self
.escape(line
, EscapeConfig
.entities
)
2267 replaced
= self
.changeline(replaced
)
2268 if ContainerConfig
.string
['startcommand'] in replaced
and len(replaced
) > 1:
2269 # unprocessed commands
2271 message
= 'Unknown command at ' + unicode(self
.begin
) + ': '
2273 message
= 'Unknown command: '
2274 Trace
.error(message
+ replaced
.strip())
2277 def changeline(self
, line
):
2278 line
= self
.escape(line
, EscapeConfig
.chars
)
2279 if not ContainerConfig
.string
['startcommand'] in line
:
2281 line
= self
.escape(line
, EscapeConfig
.commands
)
2284 def extracttext(self
):
2288 def __unicode__(self
):
2289 "Return a printable representation."
2290 result
= 'StringContainer'
2292 result
+= '@' + unicode(self
.begin
)
2294 if len(self
.string
.strip()) <= 15:
2296 return result
+ ' (' + self
.string
.strip()[:15] + ellipsis
+ ')'
2298 class Constant(StringContainer
):
2301 def __init__(self
, text
):
2304 self
.output
= StringOutput()
2306 def __unicode__(self
):
2307 return 'Constant: ' + self
.string
2309 class TaggedText(Container
):
2315 self
.parser
= TextParser(self
)
2316 self
.output
= TaggedOutput()
2318 def complete(self
, contents
, tag
, breaklines
=False):
2319 "Complete the tagged text and return it"
2320 self
.contents
= contents
2321 self
.output
.tag
= tag
2322 self
.output
.breaklines
= breaklines
2325 def constant(self
, text
, tag
, breaklines
=False):
2326 "Complete the tagged text with a constant"
2327 constant
= Constant(text
)
2328 return self
.complete([constant
], tag
, breaklines
)
2330 def __unicode__(self
):
2331 "Return a printable representation."
2332 if not hasattr(self
.output
, 'tag'):
2333 return 'Emtpy tagged text'
2334 if not self
.output
.tag
:
2335 return 'Tagged <unknown tag>'
2336 return 'Tagged <' + self
.output
.tag
+ '>'
2343 class DocumentParameters(object):
2344 "Global parameters for the document."
2347 indentstandard
= False
2353 outputchanges
= False
2361 class FormulaParser(Parser
):
2364 def parseheader(self
, reader
):
2365 "See if the formula is inlined"
2366 self
.begin
= reader
.linenumber
+ 1
2367 type = self
.parsetype(reader
)
2370 type = self
.parsetype(reader
)
2372 Trace
.error('Unknown formula type in ' + reader
.currentline().strip())
2376 def parsetype(self
, reader
):
2377 "Get the formula type from the first line."
2378 if reader
.currentline().find(FormulaConfig
.starts
['simple']) >= 0:
2380 if reader
.currentline().find(FormulaConfig
.starts
['complex']) >= 0:
2382 if reader
.currentline().find(FormulaConfig
.starts
['unnumbered']) >= 0:
2384 if reader
.currentline().find(FormulaConfig
.starts
['beginbefore']) >= 0:
2388 def parse(self
, reader
):
2389 "Parse the formula until the end"
2390 formula
= self
.parseformula(reader
)
2391 while not reader
.currentline().startswith(self
.ending
):
2392 stripped
= reader
.currentline().strip()
2393 if len(stripped
) > 0:
2394 Trace
.error('Unparsed formula line ' + stripped
)
2399 def parseformula(self
, reader
):
2400 "Parse the formula contents"
2401 simple
= FormulaConfig
.starts
['simple']
2402 if simple
in reader
.currentline():
2403 rest
= reader
.currentline().split(simple
, 1)[1]
2406 return self
.parsesingleliner(reader
, simple
, simple
)
2407 # formula is multiline $...$
2408 return self
.parsemultiliner(reader
, simple
, simple
)
2409 if FormulaConfig
.starts
['complex'] in reader
.currentline():
2410 # formula of the form \[...\]
2411 return self
.parsemultiliner(reader
, FormulaConfig
.starts
['complex'],
2412 FormulaConfig
.endings
['complex'])
2413 beginbefore
= FormulaConfig
.starts
['beginbefore']
2414 beginafter
= FormulaConfig
.starts
['beginafter']
2415 if beginbefore
in reader
.currentline():
2416 if reader
.currentline().strip().endswith(beginafter
):
2417 current
= reader
.currentline().strip()
2418 endsplit
= current
.split(beginbefore
)[1].split(beginafter
)
2419 startpiece
= beginbefore
+ endsplit
[0] + beginafter
2420 endbefore
= FormulaConfig
.endings
['endbefore']
2421 endafter
= FormulaConfig
.endings
['endafter']
2422 endpiece
= endbefore
+ endsplit
[0] + endafter
2423 return startpiece
+ self
.parsemultiliner(reader
, startpiece
, endpiece
) + endpiece
2424 Trace
.error('Missing ' + beginafter
+ ' in ' + reader
.currentline())
2426 begincommand
= FormulaConfig
.starts
['command']
2427 beginbracket
= FormulaConfig
.starts
['bracket']
2428 if begincommand
in reader
.currentline() and beginbracket
in reader
.currentline():
2429 endbracket
= FormulaConfig
.endings
['bracket']
2430 return self
.parsemultiliner(reader
, beginbracket
, endbracket
)
2431 Trace
.error('Formula beginning ' + reader
.currentline() + ' is unknown')
2434 def parsesingleliner(self
, reader
, start
, ending
):
2435 "Parse a formula in one line"
2436 line
= reader
.currentline().strip()
2437 if not start
in line
:
2438 Trace
.error('Line ' + line
+ ' does not contain formula start ' + start
)
2440 if not line
.endswith(ending
):
2441 Trace
.error('Formula ' + line
+ ' does not end with ' + ending
)
2443 index
= line
.index(start
)
2444 rest
= line
[index
+ len(start
):-len(ending
)]
2448 def parsemultiliner(self
, reader
, start
, ending
):
2449 "Parse a formula in multiple lines"
2451 line
= reader
.currentline()
2452 if not start
in line
:
2453 Trace
.error('Line ' + line
.strip() + ' does not contain formula start ' + start
)
2455 index
= line
.index(start
)
2456 line
= line
[index
+ len(start
):].strip()
2457 while not line
.endswith(ending
):
2458 formula
+= line
+ '\n'
2460 line
= reader
.currentline()
2461 formula
+= line
[:-len(ending
)]
2465 class MacroParser(FormulaParser
):
2466 "A parser for a formula macro."
2468 def parseheader(self
, reader
):
2469 "See if the formula is inlined"
2470 self
.begin
= reader
.linenumber
+ 1
2473 def parse(self
, reader
):
2474 "Parse the formula until the end"
2475 formula
= self
.parsemultiliner(reader
, self
.parent
.start
, self
.ending
)
2487 class FormulaBit(Container
):
2488 "A bit of a formula"
2495 "The formula bit type can be 'alpha', 'number', 'font'."
2497 self
.output
= ContentsOutput()
2499 def setfactory(self
, factory
):
2500 "Set the internal formula factory."
2501 self
.factory
= factory
2505 "Add any kind of formula bit already processed"
2506 self
.contents
.append(bit
)
2507 self
.original
+= bit
.original
2510 def skiporiginal(self
, string
, pos
):
2511 "Skip a string and add it to the original formula"
2512 self
.original
+= string
2513 if not pos
.checkskip(string
):
2514 Trace
.error('String ' + string
+ ' not at ' + pos
.identifier())
2516 def computesize(self
):
2517 "Compute the size of the bit as the max of the sizes of all contents."
2518 if len(self
.contents
) == 0:
2520 self
.size
= max([element
.size
for element
in self
.contents
])
2524 "Return a copy of itself."
2525 return self
.factory
.parseformula(self
.original
)
2527 def __unicode__(self
):
2528 "Get a string representation"
2529 return self
.__class
__.__name
__ + ' read in ' + self
.original
2531 class TaggedBit(FormulaBit
):
2532 "A tagged string in a formula"
2534 def constant(self
, constant
, tag
):
2535 "Set the constant and the tag"
2536 self
.output
= TaggedOutput().settag(tag
)
2537 self
.add(FormulaConstant(constant
))
2540 def complete(self
, contents
, tag
, breaklines
= False):
2541 "Set the constant and the tag"
2542 self
.contents
= contents
2543 self
.output
= TaggedOutput().settag(tag
, breaklines
)
2546 def selfcomplete(self
, tag
):
2547 "Set the self-closing tag, no contents (as in <hr/>)."
2548 self
.output
= TaggedOutput().settag(tag
, empty
= True)
2551 class FormulaConstant(Constant
):
2552 "A constant string in a formula"
2554 def __init__(self
, string
):
2555 "Set the constant string"
2556 Constant
.__init
__(self
, string
)
2557 self
.original
= string
2561 def computesize(self
):
2562 "Compute the size of the constant: always 1."
2566 "Return a copy of itself."
2567 return FormulaConstant(self
.original
)
2569 def __unicode__(self
):
2570 "Return a printable representation."
2571 return 'Formula constant: ' + self
.string
2573 class RawText(FormulaBit
):
2574 "A bit of text inside a formula"
2576 def detect(self
, pos
):
2577 "Detect a bit of raw text"
2578 return pos
.current().isalpha()
2580 def parsebit(self
, pos
):
2581 "Parse alphabetic text"
2582 alpha
= pos
.globalpha()
2583 self
.add(FormulaConstant(alpha
))
2586 class FormulaSymbol(FormulaBit
):
2587 "A symbol inside a formula"
2589 modified
= FormulaConfig
.modified
2590 unmodified
= FormulaConfig
.unmodified
['characters']
2592 def detect(self
, pos
):
2594 if pos
.current() in FormulaSymbol
.unmodified
:
2596 if pos
.current() in FormulaSymbol
.modified
:
2600 def parsebit(self
, pos
):
2602 if pos
.current() in FormulaSymbol
.unmodified
:
2603 self
.addsymbol(pos
.current(), pos
)
2605 if pos
.current() in FormulaSymbol
.modified
:
2606 self
.addsymbol(FormulaSymbol
.modified
[pos
.current()], pos
)
2608 Trace
.error('Symbol ' + pos
.current() + ' not found')
2610 def addsymbol(self
, symbol
, pos
):
2612 self
.skiporiginal(pos
.current(), pos
)
2613 self
.contents
.append(FormulaConstant(symbol
))
2615 class FormulaNumber(FormulaBit
):
2616 "A string of digits in a formula"
2618 def detect(self
, pos
):
2620 return pos
.current().isdigit()
2622 def parsebit(self
, pos
):
2623 "Parse a bunch of digits"
2624 digits
= pos
.glob(lambda: pos
.current().isdigit())
2625 self
.add(FormulaConstant(digits
))
2626 self
.type = 'number'
2628 class Comment(FormulaBit
):
2629 "A LaTeX comment: % to the end of the line."
2631 start
= FormulaConfig
.starts
['comment']
2633 def detect(self
, pos
):
2635 return pos
.current() == self
.start
2637 def parsebit(self
, pos
):
2638 "Parse to the end of the line."
2639 self
.original
+= pos
.globincluding('\n')
2641 class WhiteSpace(FormulaBit
):
2642 "Some white space inside a formula."
2644 def detect(self
, pos
):
2645 "Detect the white space."
2646 return pos
.current().isspace()
2648 def parsebit(self
, pos
):
2649 "Parse all whitespace."
2650 self
.original
+= pos
.skipspace()
2652 def __unicode__(self
):
2653 "Return a printable representation."
2654 return 'Whitespace: *' + self
.original
+ '*'
2656 class Bracket(FormulaBit
):
2657 "A {} bracket inside a formula"
2659 start
= FormulaConfig
.starts
['bracket']
2660 ending
= FormulaConfig
.endings
['bracket']
2663 "Create a (possibly literal) new bracket"
2664 FormulaBit
.__init
__(self
)
2667 def detect(self
, pos
):
2668 "Detect the start of a bracket"
2669 return pos
.checkfor(self
.start
)
2671 def parsebit(self
, pos
):
2673 self
.parsecomplete(pos
, self
.innerformula
)
2676 def parsetext(self
, pos
):
2677 "Parse a text bracket"
2678 self
.parsecomplete(pos
, self
.innertext
)
2681 def parseliteral(self
, pos
):
2682 "Parse a literal bracket"
2683 self
.parsecomplete(pos
, self
.innerliteral
)
2686 def parsecomplete(self
, pos
, innerparser
):
2687 "Parse the start and end marks"
2688 if not pos
.checkfor(self
.start
):
2689 Trace
.error('Bracket should start with ' + self
.start
+ ' at ' + pos
.identifier())
2691 self
.skiporiginal(self
.start
, pos
)
2692 pos
.pushending(self
.ending
)
2694 self
.original
+= pos
.popending(self
.ending
)
2697 def innerformula(self
, pos
):
2698 "Parse a whole formula inside the bracket"
2699 while not pos
.finished():
2700 self
.add(self
.factory
.parseany(pos
))
2702 def innertext(self
, pos
):
2703 "Parse some text inside the bracket, following textual rules."
2704 specialchars
= FormulaConfig
.symbolfunctions
.keys()
2705 specialchars
.append(FormulaConfig
.starts
['command'])
2706 specialchars
.append(FormulaConfig
.starts
['bracket'])
2707 specialchars
.append(Comment
.start
)
2708 while not pos
.finished():
2709 if pos
.current() in specialchars
:
2710 self
.add(self
.factory
.parseany(pos
))
2711 if pos
.checkskip(' '):
2712 self
.original
+= ' '
2714 self
.add(FormulaConstant(pos
.skipcurrent()))
2716 def innerliteral(self
, pos
):
2717 "Parse a literal inside the bracket, which does not generate HTML."
2719 while not pos
.finished() and not pos
.current() == self
.ending
:
2720 if pos
.current() == self
.start
:
2721 self
.parseliteral(pos
)
2723 self
.literal
+= pos
.skipcurrent()
2724 self
.original
+= self
.literal
2726 class SquareBracket(Bracket
):
2727 "A [] bracket inside a formula"
2729 start
= FormulaConfig
.starts
['squarebracket']
2730 ending
= FormulaConfig
.endings
['squarebracket']
2733 "Return a new square bracket with the same contents."
2734 bracket
= SquareBracket()
2735 bracket
.contents
= self
.contents
2740 class MathsProcessor(object):
2741 "A processor for a maths construction inside the FormulaProcessor."
2743 def process(self
, contents
, index
):
2744 "Process an element inside a formula."
2745 Trace
.error('Unimplemented process() in ' + unicode(self
))
2747 def __unicode__(self
):
2748 "Return a printable description."
2749 return 'Maths processor ' + self
.__class
__.__name
__
2751 class FormulaProcessor(object):
2752 "A processor specifically for formulas."
2756 def process(self
, bit
):
2757 "Process the contents of every formula bit, recursively."
2758 self
.processcontents(bit
)
2759 self
.processinsides(bit
)
2760 self
.traversewhole(bit
)
2762 def processcontents(self
, bit
):
2763 "Process the contents of a formula bit."
2764 if not isinstance(bit
, FormulaBit
):
2767 for element
in bit
.contents
:
2768 self
.processcontents(element
)
2770 def processinsides(self
, bit
):
2771 "Process the insides (limits, brackets) in a formula bit."
2772 if not isinstance(bit
, FormulaBit
):
2774 for index
, element
in enumerate(bit
.contents
):
2775 for processor
in self
.processors
:
2776 processor
.process(bit
.contents
, index
)
2777 # continue with recursive processing
2778 self
.processinsides(element
)
2780 def traversewhole(self
, formula
):
2781 "Traverse over the contents to alter variables and space units."
2783 for bit
, contents
in self
.traverse(formula
):
2784 if bit
.type == 'alpha':
2785 self
.italicize(bit
, contents
)
2786 elif bit
.type == 'font' and last
and last
.type == 'number':
2787 bit
.contents
.insert(0, FormulaConstant(u
' '))
2790 def traverse(self
, bit
):
2791 "Traverse a formula and yield a flattened structure of (bit, list) pairs."
2792 for element
in bit
.contents
:
2793 if hasattr(element
, 'type') and element
.type:
2794 yield (element
, bit
.contents
)
2795 elif isinstance(element
, FormulaBit
):
2796 for pair
in self
.traverse(element
):
2799 def italicize(self
, bit
, contents
):
2800 "Italicize the given bit of text."
2801 index
= contents
.index(bit
)
2802 contents
[index
] = TaggedBit().complete([bit
], 'i')
2807 class Formula(Container
):
2811 self
.parser
= FormulaParser()
2812 self
.output
= TaggedOutput().settag('span class="formula"')
2815 "Convert the formula to tags"
2816 if self
.header
[0] == 'inline':
2817 DocumentParameters
.displaymode
= False
2819 DocumentParameters
.displaymode
= True
2820 self
.output
.settag('div class="formula"', True)
2823 elif Options
.mathjax
:
2825 elif Options
.googlecharts
:
2831 "Make the contents for jsMath."
2832 if self
.header
[0] != 'inline':
2833 self
.output
= TaggedOutput().settag('div class="math"')
2835 self
.output
= TaggedOutput().settag('span class="math"')
2836 self
.contents
= [Constant(self
.parsed
)]
2839 "Make the contents for MathJax."
2840 self
.output
.tag
= 'span class="MathJax_Preview"'
2841 tag
= 'script type="math/tex'
2842 if self
.header
[0] != 'inline':
2843 tag
+= ';mode=display'
2844 self
.contents
= [TaggedText().constant(self
.parsed
, tag
+ '"', True)]
2846 def googlecharts(self
):
2847 "Make the contents using Google Charts http://code.google.com/apis/chart/."
2848 url
= FormulaConfig
.urls
['googlecharts'] + urllib
.quote_plus(self
.parsed
)
2849 img
= '<img class="chart" src="' + url
+ '" alt="' + self
.parsed
+ '"/>'
2850 self
.contents
= [Constant(img
)]
2853 "Make the contents using classic output generation with XHTML and CSS."
2854 whole
= FormulaFactory().parseformula(self
.parsed
)
2855 FormulaProcessor().process(whole
)
2857 self
.contents
= [whole
]
2859 def parse(self
, pos
):
2860 "Parse using a parse position instead of self.parser."
2861 if pos
.checkskip('$$'):
2862 self
.parsedollarblock(pos
)
2863 elif pos
.checkskip('$'):
2864 self
.parsedollarinline(pos
)
2865 elif pos
.checkskip('\\('):
2866 self
.parseinlineto(pos
, '\\)')
2867 elif pos
.checkskip('\\['):
2868 self
.parseblockto(pos
, '\\]')
2870 pos
.error('Unparseable formula')
2874 def parsedollarinline(self
, pos
):
2875 "Parse a $...$ formula."
2876 self
.header
= ['inline']
2877 self
.parsedollar(pos
)
2879 def parsedollarblock(self
, pos
):
2880 "Parse a $$...$$ formula."
2881 self
.header
= ['block']
2882 self
.parsedollar(pos
)
2883 if not pos
.checkskip('$'):
2884 pos
.error('Formula should be $$...$$, but last $ is missing.')
2886 def parsedollar(self
, pos
):
2887 "Parse to the next $."
2889 self
.parsed
= pos
.globexcluding('$')
2892 def parseinlineto(self
, pos
, limit
):
2893 "Parse a \\(...\\) formula."
2894 self
.header
= ['inline']
2895 self
.parseupto(pos
, limit
)
2897 def parseblockto(self
, pos
, limit
):
2898 "Parse a \\[...\\] formula."
2899 self
.header
= ['block']
2900 self
.parseupto(pos
, limit
)
2902 def parseupto(self
, pos
, limit
):
2903 "Parse a formula that ends with the given command."
2904 pos
.pushending(limit
)
2905 self
.parsed
= pos
.glob(lambda: True)
2906 pos
.popending(limit
)
2908 def __unicode__(self
):
2909 "Return a printable representation."
2910 if self
.partkey
and self
.partkey
.number
:
2911 return 'Formula (' + self
.partkey
.number
+ ')'
2912 return 'Unnumbered formula'
2914 class WholeFormula(FormulaBit
):
2915 "Parse a whole formula"
2917 def detect(self
, pos
):
2918 "Not outside the formula is enough."
2919 return not pos
.finished()
2921 def parsebit(self
, pos
):
2922 "Parse with any formula bit"
2923 while not pos
.finished():
2924 self
.add(self
.factory
.parseany(pos
))
2926 class FormulaFactory(object):
2927 "Construct bits of formula"
2929 # bit types will be appended later
2930 types
= [FormulaSymbol
, RawText
, FormulaNumber
, Bracket
, Comment
, WhiteSpace
]
2931 skippedtypes
= [Comment
, WhiteSpace
]
2935 "Initialize the map of instances."
2936 self
.instances
= dict()
2938 def detecttype(self
, type, pos
):
2939 "Detect a bit of a given type."
2942 return self
.instance(type).detect(pos
)
2944 def instance(self
, type):
2945 "Get an instance of the given type."
2946 if not type in self
.instances
or not self
.instances
[type]:
2947 self
.instances
[type] = self
.create(type)
2948 return self
.instances
[type]
2950 def create(self
, type):
2951 "Create a new formula bit of the given type."
2952 return Cloner
.create(type).setfactory(self
)
2954 def clearskipped(self
, pos
):
2955 "Clear any skipped types."
2956 while not pos
.finished():
2957 if not self
.skipany(pos
):
2961 def skipany(self
, pos
):
2962 "Skip any skipped types."
2963 for type in self
.skippedtypes
:
2964 if self
.instance(type).detect(pos
):
2965 return self
.parsetype(type, pos
)
2968 def parseany(self
, pos
):
2969 "Parse any formula bit at the current location."
2970 for type in self
.types
+ self
.skippedtypes
:
2971 if self
.detecttype(type, pos
):
2972 return self
.parsetype(type, pos
)
2973 Trace
.error('Unrecognized formula at ' + pos
.identifier())
2974 return FormulaConstant(pos
.skipcurrent())
2976 def parsetype(self
, type, pos
):
2977 "Parse the given type and return it."
2978 bit
= self
.instance(type)
2979 self
.instances
[type] = None
2980 returnedbit
= bit
.parsebit(pos
)
2982 return returnedbit
.setfactory(self
)
2985 def parseformula(self
, formula
):
2986 "Parse a string of text that contains a whole formula."
2987 pos
= TextPosition(formula
)
2988 whole
= self
.create(WholeFormula
)
2989 if whole
.detect(pos
):
2993 if not pos
.finished():
2994 Trace
.error('Unknown formula at: ' + pos
.identifier())
2995 whole
.add(TaggedBit().constant(formula
, 'span class="unknown"'))
3017 class Translator(object):
3018 "Reads the configuration file and tries to find a translation."
3019 "Otherwise falls back to the messages in the config file."
3023 def translate(cls
, key
):
3024 "Get the translated message for a key."
3025 return cls
.instance
.getmessage(key
)
3027 translate
= classmethod(translate
)
3030 self
.translation
= None
3033 def findtranslation(self
):
3034 "Find the translation for the document language."
3035 self
.langcodes
= None
3036 if not DocumentParameters
.language
:
3037 Trace
.error('No language in document')
3039 if not DocumentParameters
.language
in TranslationConfig
.languages
:
3040 Trace
.error('Unknown language ' + DocumentParameters
.language
)
3042 if TranslationConfig
.languages
[DocumentParameters
.language
] == 'en':
3044 langcodes
= [TranslationConfig
.languages
[DocumentParameters
.language
]]
3046 self
.translation
= gettext
.translation('elyxer', None, langcodes
)
3048 Trace
.error('No translation for ' + unicode(langcodes
))
3050 def getmessage(self
, key
):
3051 "Get the translated message for the given key."
3053 self
.findtranslation()
3055 message
= self
.getuntranslated(key
)
3056 if not self
.translation
:
3059 message
= self
.translation
.ugettext(message
)
3064 def getuntranslated(self
, key
):
3065 "Get the untranslated message."
3066 if not key
in TranslationConfig
.constants
:
3067 Trace
.error('Cannot translate ' + key
)
3069 return TranslationConfig
.constants
[key
]
3071 Translator
.instance
= Translator()
3075 class NumberCounter(object):
3076 "A counter for numbers (by default)."
3077 "The type can be changed to return letters, roman numbers..."
3084 letters
= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
3085 symbols
= NumberingConfig
.sequence
['symbols']
3087 ('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), ('C', 100),
3088 ('XC', 90), ('L', 50), ('XL', 40), ('X', 10), ('IX', 9), ('V', 5),
3092 def __init__(self
, name
):
3093 "Give a name to the counter."
3096 def setmode(self
, mode
):
3097 "Set the counter mode. Can be changed at runtime."
3101 def init(self
, value
):
3102 "Set an initial value."
3106 "Get the next value as a text string."
3107 return unicode(self
.value
)
3109 def getletter(self
):
3110 "Get the next value as a letter."
3111 return self
.getsequence(self
.letters
)
3113 def getsymbol(self
):
3114 "Get the next value as a symbol."
3115 return self
.getsequence(self
.symbols
)
3117 def getsequence(self
, sequence
):
3118 "Get the next value from elyxer.a sequence."
3119 return sequence
[(self
.value
- 1) % len(sequence
)]
3122 "Get the next value as a roman number."
3125 for numeral
, value
in self
.romannumerals
:
3127 result
+= numeral
* (number
/ value
)
3128 number
= number
% value
3132 "Get the current value as configured in the current mode."
3133 if not self
.mode
or self
.mode
in ['text', '1']:
3134 return self
.gettext()
3135 if self
.mode
== 'A':
3136 return self
.getletter()
3137 if self
.mode
== 'a':
3138 return self
.getletter().lower()
3139 if self
.mode
== 'I':
3140 return self
.getroman()
3141 if self
.mode
== '*':
3142 return self
.getsymbol()
3143 Trace
.error('Unknown counter mode ' + self
.mode
)
3144 return self
.gettext()
3147 "Increase the current value and get the next value as configured."
3151 return self
.getvalue()
3154 "Reset the counter."
3157 def __unicode__(self
):
3158 "Return a printable representation."
3159 result
= 'Counter ' + self
.name
3161 result
+= ' in mode ' + self
.mode
3164 class DependentCounter(NumberCounter
):
3165 "A counter which depends on another one (the master)."
3167 def setmaster(self
, master
):
3168 "Set the master counter."
3169 self
.master
= master
3170 self
.last
= self
.master
.getvalue()
3174 "Increase or, if the master counter has changed, restart."
3175 if self
.last
!= self
.master
.getvalue():
3177 value
= NumberCounter
.getnext(self
)
3178 self
.last
= self
.master
.getvalue()
3182 "Get the value of the combined counter: master.dependent."
3183 return self
.master
.getvalue() + '.' + NumberCounter
.getvalue(self
)
3185 class NumberGenerator(object):
3186 "A number generator for unique sequences and hierarchical structures. Used in:"
3187 " * ordered part numbers: Chapter 3, Section 5.3."
3188 " * unique part numbers: Footnote 15, Bibliography cite [15]."
3189 " * chaptered part numbers: Figure 3.15, Equation (8.3)."
3190 " * unique roman part numbers: Part I, Book IV."
3195 romanlayouts
= [x
.lower() for x
in NumberingConfig
.layouts
['roman']]
3196 orderedlayouts
= [x
.lower() for x
in NumberingConfig
.layouts
['ordered']]
3201 def deasterisk(self
, type):
3202 "Remove the possible asterisk in a layout type."
3203 return type.replace('*', '')
3205 def isunique(self
, type):
3206 "Find out if the layout type corresponds to a unique part."
3207 return self
.isroman(type)
3209 def isroman(self
, type):
3210 "Find out if the layout type should have roman numeration."
3211 return self
.deasterisk(type).lower() in self
.romanlayouts
3213 def isinordered(self
, type):
3214 "Find out if the layout type corresponds to an (un)ordered part."
3215 return self
.deasterisk(type).lower() in self
.orderedlayouts
3217 def isnumbered(self
, type):
3218 "Find out if the type for a layout corresponds to a numbered layout."
3221 if self
.isroman(type):
3223 if not self
.isinordered(type):
3225 if self
.getlevel(type) > DocumentParameters
.maxdepth
:
3229 def isunordered(self
, type):
3230 "Find out if the type contains an asterisk, basically."
3233 def getlevel(self
, type):
3234 "Get the level that corresponds to a layout type."
3235 if self
.isunique(type):
3237 if not self
.isinordered(type):
3238 Trace
.error('Unknown layout type ' + type)
3240 type = self
.deasterisk(type).lower()
3241 level
= self
.orderedlayouts
.index(type) + 1
3242 return level
- DocumentParameters
.startinglevel
3244 def getparttype(self
, type):
3245 "Obtain the type for the part: without the asterisk, "
3246 "and switched to Appendix if necessary."
3247 if NumberGenerator
.appendix
and self
.getlevel(type) == 1:
3249 return self
.deasterisk(type)
3251 def generate(self
, type):
3252 "Generate a number for a layout type."
3253 "Unique part types such as Part or Book generate roman numbers: Part I."
3254 "Ordered part types return dot-separated tuples: Chapter 5, Subsection 2.3.5."
3255 "Everything else generates unique numbers: Bibliography [1]."
3256 "Each invocation results in a new number."
3257 return self
.getcounter(type).getnext()
3259 def getcounter(self
, type):
3260 "Get the counter for the given type."
3262 if not type in self
.counters
:
3263 self
.counters
[type] = self
.create(type)
3264 return self
.counters
[type]
3266 def create(self
, type):
3267 "Create a counter for the given type."
3268 if self
.isnumbered(type) and self
.getlevel(type) > 1:
3269 index
= self
.orderedlayouts
.index(type)
3270 above
= self
.orderedlayouts
[index
- 1]
3271 master
= self
.getcounter(above
)
3272 return self
.createdependent(type, master
)
3273 counter
= NumberCounter(type)
3274 if self
.isroman(type):
3275 counter
.setmode('I')
3278 def getdependentcounter(self
, type, master
):
3279 "Get (or create) a counter of the given type that depends on another."
3280 if not type in self
.counters
or not self
.counters
[type].master
:
3281 self
.counters
[type] = self
.createdependent(type, master
)
3282 return self
.counters
[type]
3284 def createdependent(self
, type, master
):
3285 "Create a dependent counter given the master."
3286 return DependentCounter(type).setmaster(master
)
3288 def startappendix(self
):
3289 "Start appendices here."
3290 firsttype
= self
.orderedlayouts
[DocumentParameters
.startinglevel
]
3291 counter
= self
.getcounter(firsttype
)
3292 counter
.setmode('A').reset()
3293 NumberGenerator
.appendix
= True
3295 class ChapteredGenerator(NumberGenerator
):
3296 "Generate chaptered numbers, as in Chapter.Number."
3297 "Used in equations, figures: Equation (5.3), figure 8.15."
3299 def generate(self
, type):
3300 "Generate a number which goes with first-level numbers (chapters). "
3301 "For the article classes a unique number is generated."
3302 if DocumentParameters
.startinglevel
> 0:
3303 return NumberGenerator
.generator
.generate(type)
3304 chapter
= self
.getcounter('Chapter')
3305 return self
.getdependentcounter(type, chapter
).getnext()
3308 NumberGenerator
.chaptered
= ChapteredGenerator()
3309 NumberGenerator
.generator
= NumberGenerator()
3316 class ContainerSize(object):
3317 "The size of a container."
3325 def set(self
, width
= None, height
= None):
3326 "Set the proper size with width and height."
3327 self
.setvalue('width', width
)
3328 self
.setvalue('height', height
)
3331 def setmax(self
, maxwidth
= None, maxheight
= None):
3332 "Set max width and/or height."
3333 self
.setvalue('maxwidth', maxwidth
)
3334 self
.setvalue('maxheight', maxheight
)
3337 def readparameters(self
, container
):
3338 "Read some size parameters off a container."
3339 self
.setparameter(container
, 'width')
3340 self
.setparameter(container
, 'height')
3341 self
.setparameter(container
, 'scale')
3342 self
.checkvalidheight(container
)
3345 def setparameter(self
, container
, name
):
3346 "Read a size parameter off a container, and set it if present."
3347 value
= container
.getparameter(name
)
3348 self
.setvalue(name
, value
)
3350 def setvalue(self
, name
, value
):
3351 "Set the value of a parameter name, only if it's valid."
3352 value
= self
.processparameter(value
)
3354 setattr(self
, name
, value
)
3356 def checkvalidheight(self
, container
):
3357 "Check if the height parameter is valid; otherwise erase it."
3358 heightspecial
= container
.getparameter('height_special')
3359 if self
.height
and self
.extractnumber(self
.height
) == '1' and heightspecial
== 'totalheight':
3362 def processparameter(self
, value
):
3363 "Do the full processing on a parameter."
3366 if self
.extractnumber(value
) == '0':
3368 for ignored
in StyleConfig
.size
['ignoredtexts']:
3369 if ignored
in value
:
3370 value
= value
.replace(ignored
, '')
3373 def extractnumber(self
, text
):
3374 "Extract the first number in the given text."
3380 elif char
== '.' and not decimal
:
3387 def checkimage(self
, width
, height
):
3388 "Check image dimensions, set them if possible."
3390 self
.maxwidth
= unicode(width
) + 'px'
3391 if self
.scale
and not self
.width
:
3392 self
.width
= self
.scalevalue(width
)
3394 self
.maxheight
= unicode(height
) + 'px'
3395 if self
.scale
and not self
.height
:
3396 self
.height
= self
.scalevalue(height
)
3397 if self
.width
and not self
.height
:
3398 self
.height
= 'auto'
3399 if self
.height
and not self
.width
:
3402 def scalevalue(self
, value
):
3403 "Scale the value according to the image scale and return it as unicode."
3404 scaled
= value
* int(self
.scale
) / 100
3405 return unicode(int(scaled
)) + 'px'
3407 def removepercentwidth(self
):
3408 "Remove percent width if present, to set it at the figure level."
3411 if not '%' in self
.width
:
3415 if self
.height
== 'auto':
3419 def addstyle(self
, container
):
3420 "Add the proper style attribute to the output tag."
3421 if not isinstance(container
.output
, TaggedOutput
):
3422 Trace
.error('No tag to add style, in ' + unicode(container
))
3423 if not self
.width
and not self
.height
and not self
.maxwidth
and not self
.maxheight
:
3424 # nothing to see here; move along
3427 tag
+= self
.styleparameter('width')
3428 tag
+= self
.styleparameter('maxwidth')
3429 tag
+= self
.styleparameter('height')
3430 tag
+= self
.styleparameter('maxheight')
3434 container
.output
.tag
+= tag
3436 def styleparameter(self
, name
):
3437 "Get the style for a single parameter."
3438 value
= getattr(self
, name
)
3440 return name
.replace('max', 'max-') + ': ' + value
+ '; '
3445 class QuoteContainer(Container
):
3446 "A container for a pretty quote"
3449 self
.parser
= BoundedParser()
3450 self
.output
= FixedOutput()
3454 self
.type = self
.header
[2]
3455 if not self
.type in StyleConfig
.quotes
:
3456 Trace
.error('Quote type ' + self
.type + ' not found')
3459 self
.html
= [StyleConfig
.quotes
[self
.type]]
3461 class LyXLine(Container
):
3465 self
.parser
= LoneCommand()
3466 self
.output
= FixedOutput()
3469 self
.html
= ['<hr class="line" />']
3471 class EmphaticText(TaggedText
):
3472 "Text with emphatic mode"
3475 self
.output
.tag
= 'i'
3477 class ShapedText(TaggedText
):
3478 "Text shaped (italic, slanted)"
3481 self
.type = self
.header
[1]
3482 if not self
.type in TagConfig
.shaped
:
3483 Trace
.error('Unrecognized shape ' + self
.header
[1])
3484 self
.output
.tag
= 'span'
3486 self
.output
.tag
= TagConfig
.shaped
[self
.type]
3488 class VersalitasText(TaggedText
):
3489 "Text in versalitas"
3492 self
.output
.tag
= 'span class="versalitas"'
3494 class ColorText(TaggedText
):
3498 self
.color
= self
.header
[1]
3499 self
.output
.tag
= 'span class="' + self
.color
+ '"'
3501 class SizeText(TaggedText
):
3505 self
.size
= self
.header
[1]
3506 self
.output
.tag
= 'span class="' + self
.size
+ '"'
3508 class BoldText(TaggedText
):
3512 self
.output
.tag
= 'b'
3514 class TextFamily(TaggedText
):
3515 "A bit of text from elyxer.a different family"
3518 "Parse the type of family"
3519 self
.type = self
.header
[1]
3520 if not self
.type in TagConfig
.family
:
3521 Trace
.error('Unrecognized family ' + type)
3522 self
.output
.tag
= 'span'
3524 self
.output
.tag
= TagConfig
.family
[self
.type]
3526 class Hfill(TaggedText
):
3530 self
.output
.tag
= 'span class="hfill"'
3532 class BarredText(TaggedText
):
3533 "Text with a bar somewhere"
3536 "Parse the type of bar"
3537 self
.type = self
.header
[1]
3538 if not self
.type in TagConfig
.barred
:
3539 Trace
.error('Unknown bar type ' + self
.type)
3540 self
.output
.tag
= 'span'
3542 self
.output
.tag
= TagConfig
.barred
[self
.type]
3544 class LangLine(BlackBox
):
3545 "A line with language information"
3548 self
.lang
= self
.header
[1]
3550 class InsetLength(BlackBox
):
3551 "A length measure inside an inset."
3554 self
.length
= self
.header
[1]
3556 class Space(Container
):
3557 "A space of several types"
3560 self
.parser
= InsetParser()
3561 self
.output
= FixedOutput()
3564 self
.type = self
.header
[2]
3565 if self
.type not in StyleConfig
.hspaces
:
3566 Trace
.error('Unknown space type ' + self
.type)
3569 self
.html
= [StyleConfig
.hspaces
[self
.type]]
3570 length
= self
.getlength()
3573 self
.output
= TaggedOutput().settag('span class="hspace"', False)
3574 ContainerSize().set(length
).addstyle(self
)
3576 def getlength(self
):
3577 "Get the space length from elyxer.the contents or parameters."
3578 if len(self
.contents
) == 0 or not isinstance(self
.contents
[0], InsetLength
):
3580 return self
.contents
[0].length
3582 class VerticalSpace(Container
):
3583 "An inset that contains a vertical space."
3586 self
.parser
= InsetParser()
3587 self
.output
= FixedOutput()
3590 "Set the correct tag"
3591 self
.type = self
.header
[2]
3592 if self
.type not in StyleConfig
.vspaces
:
3593 self
.output
= TaggedOutput().settag('div class="vspace" style="height: ' + self
.type + ';"', True)
3595 self
.html
= [StyleConfig
.vspaces
[self
.type]]
3597 class Align(Container
):
3598 "Bit of aligned text"
3601 self
.parser
= ExcludingParser()
3602 self
.output
= TaggedOutput().setbreaklines(True)
3605 self
.output
.tag
= 'div class="' + self
.header
[1] + '"'
3607 class Newline(Container
):
3611 self
.parser
= LoneCommand()
3612 self
.output
= FixedOutput()
3616 self
.html
= ['<br/>\n']
3618 class NewPage(Newline
):
3623 self
.html
= ['<p><br/>\n</p>\n']
3625 class Separator(Container
):
3626 "A separator string which is not extracted by extracttext()."
3628 def __init__(self
, constant
):
3629 self
.output
= FixedOutput()
3631 self
.html
= [constant
]
3633 class StrikeOut(TaggedText
):
3637 "Set the output tag to strike."
3638 self
.output
.tag
= 'strike'
3640 class StartAppendix(BlackBox
):
3641 "Mark to start an appendix here."
3642 "From this point on, all chapters become appendices."
3645 "Activate the special numbering scheme for appendices, using letters."
3646 NumberGenerator
.generator
.startappendix()
3653 class Link(Container
):
3654 "A link to another part of the document"
3665 "Initialize the link, add target if configured."
3667 self
.parser
= InsetParser()
3668 self
.output
= LinkOutput()
3670 self
.target
= Options
.target
3672 def complete(self
, text
, anchor
= None, url
= None, type = None, title
= None):
3673 "Complete the link."
3674 self
.contents
= [Constant(text
)]
3676 self
.anchor
= anchor
3685 def computedestination(self
):
3686 "Use the destination link to fill in the destination URL."
3687 if not self
.destination
:
3690 if self
.destination
.anchor
:
3691 self
.url
= '#' + self
.destination
.anchor
3692 if self
.destination
.page
:
3693 self
.url
= self
.destination
.page
+ self
.url
3695 def setmutualdestination(self
, destination
):
3696 "Set another link as destination, and set its destination to this one."
3697 self
.destination
= destination
3698 destination
.destination
= self
3700 def __unicode__(self
):
3701 "Return a printable representation."
3704 result
+= ' #' + self
.anchor
3706 result
+= ' to ' + self
.url
3713 "Read URL from elyxer.parameters"
3714 target
= self
.escape(self
.getparameter('target'))
3716 type = self
.getparameter('type')
3718 self
.url
= self
.escape(type) + target
3719 name
= self
.getparameter('name')
3722 self
.contents
= [Constant(name
)]
3728 "Read URL from elyxer.contents"
3729 self
.url
= self
.extracttext()
3731 class LinkOutput(ContainerOutput
):
3732 "A link pointing to some destination"
3733 "Or an anchor (destination)"
3735 def gethtml(self
, link
):
3736 "Get the HTML code for the link"
3737 type = link
.__class
__.__name
__
3740 tag
= 'a class="' + type + '"'
3742 tag
+= ' name="' + link
.anchor
+ '"'
3743 if link
.destination
:
3744 link
.computedestination()
3746 tag
+= ' href="' + link
.url
+ '"'
3748 tag
+= ' target="' + link
.target
+ '"'
3750 tag
+= ' title="' + link
.title
+ '"'
3751 return TaggedOutput().settag(tag
).gethtml(link
)
3757 class Postprocessor(object):
3758 "Postprocess a container keeping some context"
3763 self
.stages
= StageDict(Postprocessor
.stages
, self
)
3767 def postprocess(self
, next
):
3768 "Postprocess a container and its contents."
3769 self
.postrecursive(self
.current
)
3770 result
= self
.postcurrent(next
)
3771 self
.last
= self
.current
3775 def postrecursive(self
, container
):
3776 "Postprocess the container contents recursively"
3777 if not hasattr(container
, 'contents'):
3779 if len(container
.contents
) == 0:
3781 if hasattr(container
, 'postprocess'):
3782 if not container
.postprocess
:
3784 postprocessor
= Postprocessor()
3786 for element
in container
.contents
:
3787 post
= postprocessor
.postprocess(element
)
3789 contents
.append(post
)
3790 # two rounds to empty the pipeline
3792 post
= postprocessor
.postprocess(None)
3794 contents
.append(post
)
3795 container
.contents
= contents
3797 def postcurrent(self
, next
):
3798 "Postprocess the current element taking into account next and last."
3799 stage
= self
.stages
.getstage(self
.current
)
3802 return stage
.postprocess(self
.last
, self
.current
, next
)
3804 class StageDict(object):
3805 "A dictionary of stages corresponding to classes"
3807 def __init__(self
, classes
, postprocessor
):
3808 "Instantiate an element from elyxer.each class and store as a dictionary"
3809 instances
= self
.instantiate(classes
, postprocessor
)
3810 self
.stagedict
= dict([(x
.processedclass
, x
) for x
in instances
])
3812 def instantiate(self
, classes
, postprocessor
):
3813 "Instantiate an element from elyxer.each class"
3814 stages
= [x
.__new
__(x
) for x
in classes
]
3815 for element
in stages
:
3817 element
.postprocessor
= postprocessor
3820 def getstage(self
, element
):
3821 "Get the stage for a given element, if the type is in the dict"
3822 if not element
.__class
__ in self
.stagedict
:
3824 return self
.stagedict
[element
.__class
__]
3829 "A label to be referenced"
3836 self
.lastnumbered
= None
3839 "Process a label container."
3840 key
= self
.getparameter('name')
3841 self
.create(' ', key
)
3842 self
.lastnumbered
= Label
.lastlayout
3844 def create(self
, text
, key
, type = 'Label'):
3845 "Create the label for a given key."
3847 self
.complete(text
, anchor
= key
, type = type)
3848 Label
.names
[key
] = self
3849 if key
in Reference
.references
:
3850 for reference
in Reference
.references
[key
]:
3851 reference
.destination
= self
3854 def findpartkey(self
):
3855 "Get the part key for the latest numbered container seen."
3856 numbered
= self
.numbered(self
)
3857 if numbered
and numbered
.partkey
:
3858 return numbered
.partkey
3861 def numbered(self
, container
):
3862 "Get the numbered container for the label."
3863 if container
.partkey
:
3865 if not container
.parent
:
3866 if self
.lastnumbered
:
3867 return self
.lastnumbered
3869 return self
.numbered(container
.parent
)
3871 def __unicode__(self
):
3872 "Return a printable representation."
3873 if not hasattr(self
, 'key'):
3874 return 'Unnamed label'
3875 return 'Label ' + self
.key
3877 class Reference(Link
):
3878 "A reference to a label."
3884 "Read the reference and set the arrow."
3885 self
.key
= self
.getparameter('reference')
3886 if self
.key
in Label
.names
:
3887 self
.direction
= u
'↑'
3888 label
= Label
.names
[self
.key
]
3890 self
.direction
= u
'↓'
3891 label
= Label().complete(' ', self
.key
, 'preref')
3892 self
.destination
= label
3893 self
.formatcontents()
3894 if not self
.key
in Reference
.references
:
3895 Reference
.references
[self
.key
] = []
3896 Reference
.references
[self
.key
].append(self
)
3898 def formatcontents(self
):
3899 "Format the reference contents."
3900 formatkey
= self
.getparameter('LatexCommand')
3903 self
.formatted
= u
'↕'
3904 if formatkey
in StyleConfig
.referenceformats
:
3905 self
.formatted
= StyleConfig
.referenceformats
[formatkey
]
3907 Trace
.error('Unknown reference format ' + formatkey
)
3908 self
.replace(u
'↕', self
.direction
)
3909 self
.replace('#', '1')
3910 self
.replace('on-page', Translator
.translate('on-page'))
3911 partkey
= self
.destination
.findpartkey()
3912 # only if partkey and partkey.number are not null, send partkey.number
3913 self
.replace('@', partkey
and partkey
.number
)
3914 self
.replace(u
'¶', partkey
and partkey
.tocentry
)
3915 if not '$' in self
.formatted
or not partkey
or not partkey
.titlecontents
:
3916 if '$' in self
.formatted
:
3917 Trace
.error('No title in ' + unicode(partkey
))
3918 self
.contents
= [Constant(self
.formatted
)]
3920 pieces
= self
.formatted
.split('$')
3921 self
.contents
= [Constant(pieces
[0])]
3922 for piece
in pieces
[1:]:
3923 self
.contents
+= partkey
.titlecontents
3924 self
.contents
.append(Constant(piece
))
3926 def replace(self
, key
, value
):
3927 "Replace a key in the format template with a value."
3928 if not key
in self
.formatted
:
3932 self
.formatted
= self
.formatted
.replace(key
, value
)
3934 def __unicode__(self
):
3935 "Return a printable representation."
3936 return 'Reference ' + self
.key
3940 class FormulaCommand(FormulaBit
):
3941 "A LaTeX command inside a formula"
3944 start
= FormulaConfig
.starts
['command']
3947 def detect(self
, pos
):
3948 "Find the current command."
3949 return pos
.checkfor(FormulaCommand
.start
)
3951 def parsebit(self
, pos
):
3952 "Parse the command."
3953 command
= self
.extractcommand(pos
)
3954 bit
= self
.parsewithcommand(command
, pos
)
3957 if command
.startswith('\\up') or command
.startswith('\\Up'):
3958 upgreek
= self
.parseupgreek(command
, pos
)
3961 if not self
.factory
.defining
:
3962 Trace
.error('Unknown command ' + command
)
3963 self
.output
= TaggedOutput().settag('span class="unknown"')
3964 self
.add(FormulaConstant(command
))
3967 def parsewithcommand(self
, command
, pos
):
3968 "Parse the command type once we have the command."
3969 for type in FormulaCommand
.types
:
3970 if command
in type.commandmap
:
3971 return self
.parsecommandtype(command
, type, pos
)
3974 def parsecommandtype(self
, command
, type, pos
):
3975 "Parse a given command type."
3976 bit
= self
.factory
.create(type)
3977 bit
.setcommand(command
)
3978 returned
= bit
.parsebit(pos
)
3983 def extractcommand(self
, pos
):
3984 "Extract the command from elyxer.the current position."
3985 if not pos
.checkskip(FormulaCommand
.start
):
3986 pos
.error('Missing command start ' + FormulaCommand
.start
)
3989 return self
.emptycommand(pos
)
3990 if pos
.current().isalpha():
3992 command
= FormulaCommand
.start
+ pos
.globalpha()
3993 # skip mark of short command
3997 return FormulaCommand
.start
+ pos
.skipcurrent()
3999 def emptycommand(self
, pos
):
4000 """Check for an empty command: look for command disguised as ending.
4001 Special case against '{ \{ \} }' situation."""
4004 ending
= pos
.nextending()
4005 if ending
and pos
.checkskip(ending
):
4007 return FormulaCommand
.start
+ command
4009 def parseupgreek(self
, command
, pos
):
4010 "Parse the Greek \\up command.."
4011 if len(command
) < 4:
4013 if command
.startswith('\\up'):
4014 upcommand
= '\\' + command
[3:]
4015 elif pos
.checkskip('\\Up'):
4016 upcommand
= '\\' + command
[3:4].upper() + command
[4:]
4018 Trace
.error('Impossible upgreek command: ' + command
)
4020 upgreek
= self
.parsewithcommand(upcommand
, pos
)
4022 upgreek
.type = 'font'
4025 class CommandBit(FormulaCommand
):
4026 "A formula bit that includes a command"
4028 def setcommand(self
, command
):
4029 "Set the command in the bit"
4030 self
.command
= command
4032 self
.original
+= command
4033 self
.translated
= self
.commandmap
[self
.command
]
4035 def parseparameter(self
, pos
):
4036 "Parse a parameter at the current position"
4037 self
.factory
.clearskipped(pos
)
4040 parameter
= self
.factory
.parseany(pos
)
4044 def parsesquare(self
, pos
):
4045 "Parse a square bracket"
4046 self
.factory
.clearskipped(pos
)
4047 if not self
.factory
.detecttype(SquareBracket
, pos
):
4049 bracket
= self
.factory
.parsetype(SquareBracket
, pos
)
4053 def parseliteral(self
, pos
):
4054 "Parse a literal bracket."
4055 self
.factory
.clearskipped(pos
)
4056 if not self
.factory
.detecttype(Bracket
, pos
):
4057 if not pos
.isvalue():
4058 Trace
.error('No literal parameter found at: ' + pos
.identifier())
4060 return pos
.globvalue()
4061 bracket
= Bracket().setfactory(self
.factory
)
4062 self
.add(bracket
.parseliteral(pos
))
4063 return bracket
.literal
4065 def parsesquareliteral(self
, pos
):
4066 "Parse a square bracket literally."
4067 self
.factory
.clearskipped(pos
)
4068 if not self
.factory
.detecttype(SquareBracket
, pos
):
4070 bracket
= SquareBracket().setfactory(self
.factory
)
4071 self
.add(bracket
.parseliteral(pos
))
4072 return bracket
.literal
4074 def parsetext(self
, pos
):
4075 "Parse a text parameter."
4076 self
.factory
.clearskipped(pos
)
4077 if not self
.factory
.detecttype(Bracket
, pos
):
4078 Trace
.error('No text parameter for ' + self
.command
)
4080 bracket
= Bracket().setfactory(self
.factory
).parsetext(pos
)
4084 class EmptyCommand(CommandBit
):
4085 "An empty command (without parameters)"
4087 commandmap
= FormulaConfig
.commands
4089 def parsebit(self
, pos
):
4090 "Parse a command without parameters"
4091 self
.contents
= [FormulaConstant(self
.translated
)]
4093 class SpacedCommand(CommandBit
):
4094 "An empty command which should have math spacing in formulas."
4096 commandmap
= FormulaConfig
.spacedcommands
4098 def parsebit(self
, pos
):
4099 "Place as contents the command translated and spaced."
4100 self
.contents
= [FormulaConstant(u
' ' + self
.translated
+ u
' ')]
4102 class AlphaCommand(EmptyCommand
):
4103 "A command without paramters whose result is alphabetical"
4105 commandmap
= FormulaConfig
.alphacommands
4107 def parsebit(self
, pos
):
4108 "Parse the command and set type to alpha"
4109 EmptyCommand
.parsebit(self
, pos
)
4112 class OneParamFunction(CommandBit
):
4113 "A function of one parameter"
4115 commandmap
= FormulaConfig
.onefunctions
4118 def parsebit(self
, pos
):
4119 "Parse a function with one parameter"
4120 self
.output
= TaggedOutput().settag(self
.translated
)
4121 self
.parseparameter(pos
)
4122 self
.simplifyifpossible()
4124 def simplifyifpossible(self
):
4125 "Try to simplify to a single character."
4126 if self
.original
in self
.commandmap
:
4127 self
.output
= FixedOutput()
4128 self
.html
= [self
.commandmap
[self
.original
]]
4129 self
.simplified
= True
4131 class SymbolFunction(CommandBit
):
4132 "Find a function which is represented by a symbol (like _ or ^)"
4134 commandmap
= FormulaConfig
.symbolfunctions
4136 def detect(self
, pos
):
4138 return pos
.current() in SymbolFunction
.commandmap
4140 def parsebit(self
, pos
):
4142 self
.setcommand(pos
.current())
4143 pos
.skip(self
.command
)
4144 self
.output
= TaggedOutput().settag(self
.translated
)
4145 self
.parseparameter(pos
)
4147 class TextFunction(CommandBit
):
4148 "A function where parameters are read as text."
4150 commandmap
= FormulaConfig
.textfunctions
4152 def parsebit(self
, pos
):
4153 "Parse a text parameter"
4154 self
.output
= TaggedOutput().settag(self
.translated
)
4158 "Set the type to font"
4161 class LabelFunction(CommandBit
):
4162 "A function that acts as a label"
4164 commandmap
= FormulaConfig
.labelfunctions
4166 def parsebit(self
, pos
):
4167 "Parse a literal parameter"
4168 self
.key
= self
.parseliteral(pos
)
4171 "Add an anchor with the label contents."
4173 self
.label
= Label().create(' ', self
.key
, type = 'eqnumber')
4174 self
.contents
= [self
.label
]
4175 # store as a Label so we know it's been seen
4176 Label
.names
[self
.key
] = self
.label
4178 class FontFunction(OneParamFunction
):
4179 "A function of one parameter that changes the font"
4181 commandmap
= FormulaConfig
.fontfunctions
4184 "Simplify if possible using a single character."
4186 self
.simplifyifpossible()
4188 FormulaFactory
.types
+= [FormulaCommand
, SymbolFunction
]
4189 FormulaCommand
.types
= [
4190 AlphaCommand
, EmptyCommand
, OneParamFunction
, FontFunction
, LabelFunction
,
4191 TextFunction
, SpacedCommand
,
4205 class BigSymbol(object):
4206 "A big symbol generator."
4208 symbols
= FormulaConfig
.bigsymbols
4210 def __init__(self
, symbol
):
4211 "Create the big symbol."
4212 self
.symbol
= symbol
4214 def getpieces(self
):
4215 "Get an array with all pieces."
4216 if not self
.symbol
in self
.symbols
:
4217 return [self
.symbol
]
4218 if self
.smalllimit():
4219 return [self
.symbol
]
4220 return self
.symbols
[self
.symbol
]
4222 def smalllimit(self
):
4223 "Decide if the limit should be a small, one-line symbol."
4224 if not DocumentParameters
.displaymode
:
4226 if len(self
.symbols
[self
.symbol
]) == 1:
4228 return Options
.simplemath
4230 class BigBracket(BigSymbol
):
4231 "A big bracket generator."
4233 def __init__(self
, size
, bracket
, alignment
='l'):
4234 "Set the size and symbol for the bracket."
4236 self
.original
= bracket
4237 self
.alignment
= alignment
4239 if bracket
in FormulaConfig
.bigbrackets
:
4240 self
.pieces
= FormulaConfig
.bigbrackets
[bracket
]
4242 def getpiece(self
, index
):
4243 "Return the nth piece for the bracket."
4244 function
= getattr(self
, 'getpiece' + unicode(len(self
.pieces
)))
4245 return function(index
)
4247 def getpiece1(self
, index
):
4248 "Return the only piece for a single-piece bracket."
4249 return self
.pieces
[0]
4251 def getpiece3(self
, index
):
4252 "Get the nth piece for a 3-piece bracket: parenthesis or square bracket."
4254 return self
.pieces
[0]
4255 if index
== self
.size
- 1:
4256 return self
.pieces
[-1]
4257 return self
.pieces
[1]
4259 def getpiece4(self
, index
):
4260 "Get the nth piece for a 4-piece bracket: curly bracket."
4262 return self
.pieces
[0]
4263 if index
== self
.size
- 1:
4264 return self
.pieces
[3]
4265 if index
== (self
.size
- 1)/2:
4266 return self
.pieces
[2]
4267 return self
.pieces
[1]
4269 def getcell(self
, index
):
4270 "Get the bracket piece as an array cell."
4271 piece
= self
.getpiece(index
)
4272 span
= 'span class="bracket align-' + self
.alignment
+ '"'
4273 return TaggedBit().constant(piece
, span
)
4275 def getcontents(self
):
4276 "Get the bracket as an array or as a single bracket."
4277 if self
.size
== 1 or not self
.pieces
:
4278 return self
.getsinglebracket()
4280 for index
in range(self
.size
):
4281 cell
= self
.getcell(index
)
4282 rows
.append(TaggedBit().complete([cell
], 'span class="arrayrow"'))
4283 return [TaggedBit().complete(rows
, 'span class="array"')]
4285 def getsinglebracket(self
):
4286 "Return the bracket as a single sign."
4287 if self
.original
== '.':
4288 return [TaggedBit().constant('', 'span class="emptydot"')]
4289 return [TaggedBit().constant(self
.original
, 'span class="symbol"')]
4296 class FormulaEquation(CommandBit
):
4297 "A simple numbered equation."
4301 def parsebit(self
, pos
):
4303 self
.output
= ContentsOutput()
4304 self
.add(self
.factory
.parsetype(WholeFormula
, pos
))
4306 class FormulaCell(FormulaCommand
):
4307 "An array cell inside a row"
4309 def setalignment(self
, alignment
):
4310 self
.alignment
= alignment
4311 self
.output
= TaggedOutput().settag('span class="arraycell align-' + alignment
+'"', True)
4314 def parsebit(self
, pos
):
4315 self
.factory
.clearskipped(pos
)
4318 self
.add(self
.factory
.parsetype(WholeFormula
, pos
))
4320 class FormulaRow(FormulaCommand
):
4321 "An array row inside an array"
4323 cellseparator
= FormulaConfig
.array
['cellseparator']
4325 def setalignments(self
, alignments
):
4326 self
.alignments
= alignments
4327 self
.output
= TaggedOutput().settag('span class="arrayrow"', True)
4330 def parsebit(self
, pos
):
4333 pos
.pushending(self
.cellseparator
, optional
=True)
4334 while not pos
.finished():
4335 cell
= self
.createcell(index
)
4339 pos
.checkskip(self
.cellseparator
)
4340 if len(self
.contents
) == 0:
4341 self
.output
= EmptyOutput()
4343 def createcell(self
, index
):
4344 "Create the cell that corresponds to the given index."
4345 alignment
= self
.alignments
[index
% len(self
.alignments
)]
4346 return self
.factory
.create(FormulaCell
).setalignment(alignment
)
4348 class MultiRowFormula(CommandBit
):
4349 "A formula with multiple rows."
4351 def parserows(self
, pos
):
4352 "Parse all rows, finish when no more row ends"
4355 for row
in self
.iteraterows(pos
):
4359 # intersparse empty rows
4363 self
.size
= len(self
.rows
)
4365 def iteraterows(self
, pos
):
4366 "Iterate over all rows, end when no more row ends"
4367 rowseparator
= FormulaConfig
.array
['rowseparator']
4369 pos
.pushending(rowseparator
, True)
4370 row
= self
.factory
.create(FormulaRow
)
4371 yield row
.setalignments(self
.alignments
)
4372 if pos
.checkfor(rowseparator
):
4373 self
.original
+= pos
.popending(rowseparator
)
4379 row
= self
.factory
.create(FormulaRow
).setalignments(self
.alignments
)
4380 for index
, originalcell
in enumerate(self
.rows
[-1].contents
):
4381 cell
= row
.createcell(index
)
4382 cell
.add(FormulaConstant(u
' '))
4386 def addrow(self
, row
):
4387 "Add a row to the contents and to the list of rows."
4388 self
.rows
.append(row
)
4391 class FormulaArray(MultiRowFormula
):
4392 "An array within a formula"
4396 def parsebit(self
, pos
):
4398 self
.output
= TaggedOutput().settag('span class="array"', False)
4399 self
.parsealignments(pos
)
4402 def parsealignments(self
, pos
):
4403 "Parse the different alignments"
4406 literal
= self
.parsesquareliteral(pos
)
4408 self
.valign
= literal
4410 literal
= self
.parseliteral(pos
)
4411 self
.alignments
= []
4413 self
.alignments
.append(l
)
4415 class FormulaMatrix(MultiRowFormula
):
4416 "A matrix (array with center alignment)."
4420 def parsebit(self
, pos
):
4421 "Parse the matrix, set alignments to 'c'."
4422 self
.output
= TaggedOutput().settag('span class="array"', False)
4424 self
.alignments
= ['c']
4427 class FormulaCases(MultiRowFormula
):
4432 def parsebit(self
, pos
):
4434 self
.output
= ContentsOutput()
4435 self
.alignments
= ['l', 'l']
4437 for row
in self
.contents
:
4438 for cell
in row
.contents
:
4439 cell
.output
.settag('span class="case align-l"', True)
4440 cell
.contents
.append(FormulaConstant(u
' '))
4441 array
= TaggedBit().complete(self
.contents
, 'span class="bracketcases"', True)
4442 brace
= BigBracket(len(self
.contents
), '{', 'l')
4443 self
.contents
= brace
.getcontents() + [array
]
4445 class EquationEnvironment(MultiRowFormula
):
4446 "A \\begin{}...\\end equation environment with rows and cells."
4448 def parsebit(self
, pos
):
4449 "Parse the whole environment."
4450 self
.output
= TaggedOutput().settag('span class="environment"', False)
4451 environment
= self
.piece
.replace('*', '')
4452 if environment
in FormulaConfig
.environments
:
4453 self
.alignments
= FormulaConfig
.environments
[environment
]
4455 Trace
.error('Unknown equation environment ' + self
.piece
)
4456 self
.alignments
= ['l']
4459 class BeginCommand(CommandBit
):
4460 "A \\begin{}...\end command and what it entails (array, cases, aligned)"
4462 commandmap
= {FormulaConfig
.array
['begin']:''}
4464 types
= [FormulaEquation
, FormulaArray
, FormulaCases
, FormulaMatrix
]
4466 def parsebit(self
, pos
):
4467 "Parse the begin command"
4468 command
= self
.parseliteral(pos
)
4469 bit
= self
.findbit(command
)
4470 ending
= FormulaConfig
.array
['end'] + '{' + command
+ '}'
4471 pos
.pushending(ending
)
4474 self
.original
+= pos
.popending(ending
)
4475 self
.size
= bit
.size
4477 def findbit(self
, piece
):
4478 "Find the command bit corresponding to the \\begin{piece}"
4479 for type in BeginCommand
.types
:
4480 if piece
.replace('*', '') == type.piece
:
4481 return self
.factory
.create(type)
4482 bit
= self
.factory
.create(EquationEnvironment
)
4486 FormulaCommand
.types
+= [BeginCommand
]
4490 class CombiningFunction(OneParamFunction
):
4492 commandmap
= FormulaConfig
.combiningfunctions
4494 def parsebit(self
, pos
):
4495 "Parse a combining function."
4497 combining
= self
.translated
4498 parameter
= self
.parsesingleparameter(pos
)
4500 Trace
.error('Empty parameter for combining function ' + self
.command
)
4501 elif len(parameter
.extracttext()) != 1:
4502 Trace
.error('Applying combining function ' + self
.command
+ ' to invalid string "' + parameter
.extracttext() + '"')
4503 self
.contents
.append(Constant(combining
))
4505 def parsesingleparameter(self
, pos
):
4506 "Parse a parameter, or a single letter."
4507 self
.factory
.clearskipped(pos
)
4509 Trace
.error('Error while parsing single parameter at ' + pos
.identifier())
4511 if self
.factory
.detecttype(Bracket
, pos
) \
4512 or self
.factory
.detecttype(FormulaCommand
, pos
):
4513 return self
.parseparameter(pos
)
4514 letter
= FormulaConstant(pos
.skipcurrent())
4518 class DecoratingFunction(OneParamFunction
):
4519 "A function that decorates some bit of text"
4521 commandmap
= FormulaConfig
.decoratingfunctions
4523 def parsebit(self
, pos
):
4524 "Parse a decorating function"
4526 symbol
= self
.translated
4527 self
.symbol
= TaggedBit().constant(symbol
, 'span class="symbolover"')
4528 self
.parameter
= self
.parseparameter(pos
)
4529 self
.output
= TaggedOutput().settag('span class="withsymbol"')
4530 self
.contents
.insert(0, self
.symbol
)
4531 self
.parameter
.output
= TaggedOutput().settag('span class="undersymbol"')
4532 self
.simplifyifpossible()
4534 class LimitCommand(EmptyCommand
):
4535 "A command which accepts limits above and below, in display mode."
4537 commandmap
= FormulaConfig
.limitcommands
4539 def parsebit(self
, pos
):
4540 "Parse a limit command."
4541 pieces
= BigSymbol(self
.translated
).getpieces()
4542 self
.output
= TaggedOutput().settag('span class="limits"')
4543 for piece
in pieces
:
4544 self
.contents
.append(TaggedBit().constant(piece
, 'span class="limit"'))
4546 class LimitPreviousCommand(LimitCommand
):
4547 "A command to limit the previous command."
4551 def parsebit(self
, pos
):
4553 self
.output
= TaggedOutput().settag('span class="limits"')
4554 self
.factory
.clearskipped(pos
)
4556 def __unicode__(self
):
4557 "Return a printable representation."
4558 return 'Limit previous command'
4560 class LimitsProcessor(MathsProcessor
):
4561 "A processor for limits inside an element."
4563 def process(self
, contents
, index
):
4564 "Process the limits for an element."
4565 if Options
.simplemath
:
4567 if self
.checklimits(contents
, index
):
4568 self
.modifylimits(contents
, index
)
4569 if self
.checkscript(contents
, index
) and self
.checkscript(contents
, index
+ 1):
4570 self
.modifyscripts(contents
, index
)
4572 def checklimits(self
, contents
, index
):
4573 "Check if the current position has a limits command."
4574 if not DocumentParameters
.displaymode
:
4576 if self
.checkcommand(contents
, index
+ 1, LimitPreviousCommand
):
4577 self
.limitsahead(contents
, index
)
4579 if not isinstance(contents
[index
], LimitCommand
):
4581 return self
.checkscript(contents
, index
+ 1)
4583 def limitsahead(self
, contents
, index
):
4584 "Limit the current element based on the next."
4585 contents
[index
+ 1].add(contents
[index
].clone())
4586 contents
[index
].output
= EmptyOutput()
4588 def modifylimits(self
, contents
, index
):
4589 "Modify a limits commands so that the limits appear above and below."
4590 limited
= contents
[index
]
4591 subscript
= self
.getlimit(contents
, index
+ 1)
4592 limited
.contents
.append(subscript
)
4593 if self
.checkscript(contents
, index
+ 1):
4594 superscript
= self
.getlimit(contents
, index
+ 1)
4596 superscript
= TaggedBit().constant(u
' ', 'sup class="limit"')
4597 limited
.contents
.insert(0, superscript
)
4599 def getlimit(self
, contents
, index
):
4600 "Get the limit for a limits command."
4601 limit
= self
.getscript(contents
, index
)
4602 limit
.output
.tag
= limit
.output
.tag
.replace('script', 'limit')
4605 def modifyscripts(self
, contents
, index
):
4606 "Modify the super- and subscript to appear vertically aligned."
4607 subscript
= self
.getscript(contents
, index
)
4608 # subscript removed so instead of index + 1 we get index again
4609 superscript
= self
.getscript(contents
, index
)
4610 scripts
= TaggedBit().complete([superscript
, subscript
], 'span class="scripts"')
4611 contents
.insert(index
, scripts
)
4613 def checkscript(self
, contents
, index
):
4614 "Check if the current element is a sub- or superscript."
4615 return self
.checkcommand(contents
, index
, SymbolFunction
)
4617 def checkcommand(self
, contents
, index
, type):
4618 "Check for the given type as the current element."
4619 if len(contents
) <= index
:
4621 return isinstance(contents
[index
], type)
4623 def getscript(self
, contents
, index
):
4624 "Get the sub- or superscript."
4625 bit
= contents
[index
]
4626 bit
.output
.tag
+= ' class="script"'
4630 class BracketCommand(OneParamFunction
):
4631 "A command which defines a bracket."
4633 commandmap
= FormulaConfig
.bracketcommands
4635 def parsebit(self
, pos
):
4636 "Parse the bracket."
4637 OneParamFunction
.parsebit(self
, pos
)
4639 def create(self
, direction
, character
):
4640 "Create the bracket for the given character."
4641 self
.original
= character
4642 self
.command
= '\\' + direction
4643 self
.contents
= [FormulaConstant(character
)]
4646 class BracketProcessor(MathsProcessor
):
4647 "A processor for bracket commands."
4649 def process(self
, contents
, index
):
4650 "Convert the bracket using Unicode pieces, if possible."
4651 if Options
.simplemath
:
4653 if self
.checkleft(contents
, index
):
4654 return self
.processleft(contents
, index
)
4656 def processleft(self
, contents
, index
):
4657 "Process a left bracket."
4658 rightindex
= self
.findright(contents
, index
+ 1)
4661 size
= self
.findmax(contents
, index
, rightindex
)
4662 self
.resize(contents
[index
], size
)
4663 self
.resize(contents
[rightindex
], size
)
4665 def checkleft(self
, contents
, index
):
4666 "Check if the command at the given index is left."
4667 return self
.checkdirection(contents
[index
], '\\left')
4669 def checkright(self
, contents
, index
):
4670 "Check if the command at the given index is right."
4671 return self
.checkdirection(contents
[index
], '\\right')
4673 def checkdirection(self
, bit
, command
):
4674 "Check if the given bit is the desired bracket command."
4675 if not isinstance(bit
, BracketCommand
):
4677 return bit
.command
== command
4679 def findright(self
, contents
, index
):
4680 "Find the right bracket starting at the given index, or 0."
4682 while index
< len(contents
):
4683 if self
.checkleft(contents
, index
):
4685 if self
.checkright(contents
, index
):
4692 def findmax(self
, contents
, leftindex
, rightindex
):
4693 "Find the max size of the contents between the two given indices."
4694 sliced
= contents
[leftindex
:rightindex
]
4695 return max([element
.size
for element
in sliced
])
4697 def resize(self
, command
, size
):
4698 "Resize a bracket command to the given size."
4699 character
= command
.extracttext()
4700 alignment
= command
.command
.replace('\\', '')
4701 bracket
= BigBracket(size
, character
, alignment
)
4702 command
.output
= ContentsOutput()
4703 command
.contents
= bracket
.getcontents()
4706 FormulaCommand
.types
+= [
4707 DecoratingFunction
, CombiningFunction
, LimitCommand
, BracketCommand
,
4710 FormulaProcessor
.processors
+= [
4711 LimitsProcessor(), BracketProcessor(),
4716 class ParameterDefinition(object):
4717 "The definition of a parameter in a hybrid function."
4718 "[] parameters are optional, {} parameters are mandatory."
4719 "Each parameter has a one-character name, like {$1} or {$p}."
4720 "A parameter that ends in ! like {$p!} is a literal."
4721 "Example: [$1]{$p!} reads an optional parameter $1 and a literal mandatory parameter p."
4723 parambrackets
= [('[', ']'), ('{', '}')]
4727 self
.literal
= False
4728 self
.optional
= False
4730 self
.literalvalue
= None
4732 def parse(self
, pos
):
4733 "Parse a parameter definition: [$0], {$x}, {$1!}..."
4734 for (opening
, closing
) in ParameterDefinition
.parambrackets
:
4735 if pos
.checkskip(opening
):
4737 self
.optional
= True
4738 if not pos
.checkskip('$'):
4739 Trace
.error('Wrong parameter name, did you mean $' + pos
.current() + '?')
4741 self
.name
= pos
.skipcurrent()
4742 if pos
.checkskip('!'):
4744 if not pos
.checkskip(closing
):
4745 Trace
.error('Wrong parameter closing ' + pos
.skipcurrent())
4748 Trace
.error('Wrong character in parameter template: ' + pos
.skipcurrent())
4751 def read(self
, pos
, function
):
4752 "Read the parameter itself using the definition."
4755 self
.literalvalue
= function
.parsesquareliteral(pos
)
4757 self
.literalvalue
= function
.parseliteral(pos
)
4758 if self
.literalvalue
:
4759 self
.value
= FormulaConstant(self
.literalvalue
)
4761 self
.value
= function
.parsesquare(pos
)
4763 self
.value
= function
.parseparameter(pos
)
4765 def __unicode__(self
):
4766 "Return a printable representation."
4767 result
= 'param ' + self
.name
4769 result
+= ': ' + unicode(self
.value
)
4771 result
+= ' (empty)'
4774 class ParameterFunction(CommandBit
):
4775 "A function with a variable number of parameters defined in a template."
4776 "The parameters are defined as a parameter definition."
4778 def readparams(self
, readtemplate
, pos
):
4779 "Read the params according to the template."
4780 self
.params
= dict()
4781 for paramdef
in self
.paramdefs(readtemplate
):
4782 paramdef
.read(pos
, self
)
4783 self
.params
['$' + paramdef
.name
] = paramdef
4785 def paramdefs(self
, readtemplate
):
4786 "Read each param definition in the template"
4787 pos
= TextPosition(readtemplate
)
4788 while not pos
.finished():
4789 paramdef
= ParameterDefinition().parse(pos
)
4793 def getparam(self
, name
):
4794 "Get a parameter as parsed."
4795 if not name
in self
.params
:
4797 return self
.params
[name
]
4799 def getvalue(self
, name
):
4800 "Get the value of a parameter."
4801 return self
.getparam(name
).value
4803 def getliteralvalue(self
, name
):
4804 "Get the literal value of a parameter."
4805 param
= self
.getparam(name
)
4806 if not param
or not param
.literalvalue
:
4808 return param
.literalvalue
4810 class HybridFunction(ParameterFunction
):
4812 A parameter function where the output is also defined using a template.
4813 The template can use a number of functions; each function has an associated
4815 Example: [f0{$1},span class="fbox"] defines a function f0 which corresponds
4816 to a span of class fbox, yielding <span class="fbox">$1</span>.
4817 Literal parameters can be used in tags definitions:
4818 [f0{$1},span style="color: $p;"]
4819 yields <span style="color: $p;">$1</span>, where $p is a literal parameter.
4820 Sizes can be specified in hybridsizes, e.g. adding parameter sizes. By
4821 default the resulting size is the max of all arguments. Sizes are used
4822 to generate the right parameters.
4823 A function followed by a single / is output as a self-closing XHTML tag:
4825 will generate <hr/>.
4828 commandmap
= FormulaConfig
.hybridfunctions
4830 def parsebit(self
, pos
):
4831 "Parse a function with [] and {} parameters"
4832 readtemplate
= self
.translated
[0]
4833 writetemplate
= self
.translated
[1]
4834 self
.readparams(readtemplate
, pos
)
4835 self
.contents
= self
.writeparams(writetemplate
)
4836 self
.computehybridsize()
4838 def writeparams(self
, writetemplate
):
4839 "Write all params according to the template"
4840 return self
.writepos(TextPosition(writetemplate
))
4842 def writepos(self
, pos
):
4843 "Write all params as read in the parse position."
4845 while not pos
.finished():
4846 if pos
.checkskip('$'):
4847 param
= self
.writeparam(pos
)
4849 result
.append(param
)
4850 elif pos
.checkskip('f'):
4851 function
= self
.writefunction(pos
)
4853 function
.type = None
4854 result
.append(function
)
4855 elif pos
.checkskip('('):
4856 result
.append(self
.writebracket('left', '('))
4857 elif pos
.checkskip(')'):
4858 result
.append(self
.writebracket('right', ')'))
4860 result
.append(FormulaConstant(pos
.skipcurrent()))
4863 def writeparam(self
, pos
):
4864 "Write a single param of the form $0, $x..."
4865 name
= '$' + pos
.skipcurrent()
4866 if not name
in self
.params
:
4867 Trace
.error('Unknown parameter ' + name
)
4869 if not self
.params
[name
]:
4871 if pos
.checkskip('.'):
4872 self
.params
[name
].value
.type = pos
.globalpha()
4873 return self
.params
[name
].value
4875 def writefunction(self
, pos
):
4876 "Write a single function f0,...,fn."
4877 tag
= self
.readtag(pos
)
4880 if pos
.checkskip('/'):
4881 # self-closing XHTML tag, such as <hr/>
4882 return TaggedBit().selfcomplete(tag
)
4883 if not pos
.checkskip('{'):
4884 Trace
.error('Function should be defined in {}')
4887 contents
= self
.writepos(pos
)
4889 if len(contents
) == 0:
4891 return TaggedBit().complete(contents
, tag
)
4893 def readtag(self
, pos
):
4894 "Get the tag corresponding to the given index. Does parameter substitution."
4895 if not pos
.current().isdigit():
4896 Trace
.error('Function should be f0,...,f9: f' + pos
.current())
4898 index
= int(pos
.skipcurrent())
4899 if 2 + index
> len(self
.translated
):
4900 Trace
.error('Function f' + unicode(index
) + ' is not defined')
4902 tag
= self
.translated
[2 + index
]
4905 for variable
in self
.params
:
4907 param
= self
.params
[variable
]
4908 if not param
.literal
:
4909 Trace
.error('Parameters in tag ' + tag
+ ' should be literal: {' + variable
+ '!}')
4911 if param
.literalvalue
:
4912 value
= param
.literalvalue
4915 tag
= tag
.replace(variable
, value
)
4918 def writebracket(self
, direction
, character
):
4919 "Return a new bracket looking at the given direction."
4920 return self
.factory
.create(BracketCommand
).create(direction
, character
)
4922 def computehybridsize(self
):
4923 "Compute the size of the hybrid function."
4924 if not self
.command
in HybridSize
.configsizes
:
4927 self
.size
= HybridSize().getsize(self
)
4928 # set the size in all elements at first level
4929 for element
in self
.contents
:
4930 element
.size
= self
.size
4932 class HybridSize(object):
4933 "The size associated with a hybrid function."
4935 configsizes
= FormulaConfig
.hybridsizes
4937 def getsize(self
, function
):
4938 "Read the size for a function and parse it."
4939 sizestring
= self
.configsizes
[function
.command
]
4940 for name
in function
.params
:
4941 if name
in sizestring
:
4942 size
= function
.params
[name
].value
.computesize()
4943 sizestring
= sizestring
.replace(name
, unicode(size
))
4944 if '$' in sizestring
:
4945 Trace
.error('Unconverted variable in hybrid size: ' + sizestring
)
4947 return eval(sizestring
)
4950 FormulaCommand
.types
+= [HybridFunction
]
4960 class HeaderParser(Parser
):
4961 "Parses the LyX header"
4963 def parse(self
, reader
):
4964 "Parse header parameters into a dictionary, return the preamble."
4966 self
.parseending(reader
, lambda: self
.parseline(reader
, contents
))
4971 def parseline(self
, reader
, contents
):
4972 "Parse a single line as a parameter or as a start"
4973 line
= reader
.currentline()
4974 if line
.startswith(HeaderConfig
.parameters
['branch']):
4975 self
.parsebranch(reader
)
4977 elif line
.startswith(HeaderConfig
.parameters
['lstset']):
4978 LstParser().parselstset(reader
)
4980 elif line
.startswith(HeaderConfig
.parameters
['beginpreamble']):
4981 contents
.append(self
.factory
.createcontainer(reader
))
4984 self
.parseparameter(reader
)
4986 def parsebranch(self
, reader
):
4987 "Parse all branch definitions."
4988 branch
= reader
.currentline().split()[1]
4990 subparser
= HeaderParser().complete(HeaderConfig
.parameters
['endbranch'])
4991 subparser
.parse(reader
)
4992 options
= BranchOptions(branch
)
4993 for key
in subparser
.parameters
:
4994 options
.set(key
, subparser
.parameters
[key
])
4995 Options
.branches
[branch
] = options
4997 def complete(self
, ending
):
4998 "Complete the parser with the given ending."
4999 self
.ending
= ending
5002 class PreambleParser(Parser
):
5003 "A parser for the LyX preamble."
5007 def parse(self
, reader
):
5008 "Parse the full preamble with all statements."
5009 self
.ending
= HeaderConfig
.parameters
['endpreamble']
5010 self
.parseending(reader
, lambda: self
.parsepreambleline(reader
))
5013 def parsepreambleline(self
, reader
):
5014 "Parse a single preamble line."
5015 PreambleParser
.preamble
.append(reader
.currentline())
5018 class LstParser(object):
5019 "Parse global and local lstparams."
5021 globalparams
= dict()
5023 def parselstset(self
, reader
):
5024 "Parse a declaration of lstparams in lstset."
5025 paramtext
= self
.extractlstset(reader
)
5026 if not '{' in paramtext
:
5027 Trace
.error('Missing opening bracket in lstset: ' + paramtext
)
5029 lefttext
= paramtext
.split('{')[1]
5030 croppedtext
= lefttext
[:-1]
5031 LstParser
.globalparams
= self
.parselstparams(croppedtext
)
5033 def extractlstset(self
, reader
):
5034 "Extract the global lstset parameters."
5036 while not reader
.finished():
5037 paramtext
+= reader
.currentline()
5039 if paramtext
.endswith('}'):
5041 Trace
.error('Could not find end of \\lstset settings; aborting')
5043 def parsecontainer(self
, container
):
5044 "Parse some lstparams from elyxer.a container."
5045 container
.lstparams
= LstParser
.globalparams
.copy()
5046 paramlist
= container
.getparameterlist('lstparams')
5047 container
.lstparams
.update(self
.parselstparams(paramlist
))
5049 def parselstparams(self
, paramlist
):
5050 "Process a number of lstparams from elyxer.a list."
5052 for param
in paramlist
:
5053 if not '=' in param
:
5054 if len(param
.strip()) > 0:
5055 Trace
.error('Invalid listing parameter ' + param
)
5057 key
, value
= param
.split('=', 1)
5058 paramdict
[key
] = value
5064 class MacroDefinition(CommandBit
):
5065 "A function that defines a new command (a macro)."
5069 def parsebit(self
, pos
):
5070 "Parse the function that defines the macro."
5071 self
.output
= EmptyOutput()
5072 self
.parameternumber
= 0
5074 self
.factory
.defining
= True
5075 self
.parseparameters(pos
)
5076 self
.factory
.defining
= False
5077 Trace
.debug('New command ' + self
.newcommand
+ ' (' + \
5078 unicode(self
.parameternumber
) + ' parameters)')
5079 self
.macros
[self
.newcommand
] = self
5081 def parseparameters(self
, pos
):
5082 "Parse all optional parameters (number of parameters, default values)"
5083 "and the mandatory definition."
5084 self
.newcommand
= self
.parsenewcommand(pos
)
5085 # parse number of parameters
5086 literal
= self
.parsesquareliteral(pos
)
5088 self
.parameternumber
= int(literal
)
5089 # parse all default values
5090 bracket
= self
.parsesquare(pos
)
5092 self
.defaults
.append(bracket
)
5093 bracket
= self
.parsesquare(pos
)
5094 # parse mandatory definition
5095 self
.definition
= self
.parseparameter(pos
)
5097 def parsenewcommand(self
, pos
):
5098 "Parse the name of the new command."
5099 self
.factory
.clearskipped(pos
)
5100 if self
.factory
.detecttype(Bracket
, pos
):
5101 return self
.parseliteral(pos
)
5102 if self
.factory
.detecttype(FormulaCommand
, pos
):
5103 return self
.factory
.create(FormulaCommand
).extractcommand(pos
)
5104 Trace
.error('Unknown formula bit in defining function at ' + pos
.identifier())
5107 def instantiate(self
):
5108 "Return an instance of the macro."
5109 return self
.definition
.clone()
5111 class MacroParameter(FormulaBit
):
5112 "A parameter from elyxer.a macro."
5114 def detect(self
, pos
):
5115 "Find a macro parameter: #n."
5116 return pos
.checkfor('#')
5118 def parsebit(self
, pos
):
5119 "Parse the parameter: #n."
5120 if not pos
.checkskip('#'):
5121 Trace
.error('Missing parameter start #.')
5123 self
.number
= int(pos
.skipcurrent())
5124 self
.original
= '#' + unicode(self
.number
)
5125 self
.contents
= [TaggedBit().constant('#' + unicode(self
.number
), 'span class="unknown"')]
5127 class MacroFunction(CommandBit
):
5128 "A function that was defined using a macro."
5130 commandmap
= MacroDefinition
.macros
5132 def parsebit(self
, pos
):
5133 "Parse a number of input parameters."
5134 self
.output
= FilteredOutput()
5136 macro
= self
.translated
5137 self
.parseparameters(pos
, macro
)
5138 self
.completemacro(macro
)
5140 def parseparameters(self
, pos
, macro
):
5141 "Parse as many parameters as are needed."
5142 self
.parseoptional(pos
, list(macro
.defaults
))
5143 self
.parsemandatory(pos
, macro
.parameternumber
- len(macro
.defaults
))
5144 if len(self
.values
) < macro
.parameternumber
:
5145 Trace
.error('Missing parameters in macro ' + unicode(self
))
5147 def parseoptional(self
, pos
, defaults
):
5148 "Parse optional parameters."
5150 while self
.factory
.detecttype(SquareBracket
, pos
):
5151 optional
.append(self
.parsesquare(pos
))
5152 if len(optional
) > len(defaults
):
5154 for value
in optional
:
5155 default
= defaults
.pop()
5156 if len(value
.contents
) > 0:
5157 self
.values
.append(value
)
5159 self
.values
.append(default
)
5160 self
.values
+= defaults
5162 def parsemandatory(self
, pos
, number
):
5163 "Parse a number of mandatory parameters."
5164 for index
in range(number
):
5165 parameter
= self
.parsemacroparameter(pos
, number
- index
)
5168 self
.values
.append(parameter
)
5170 def parsemacroparameter(self
, pos
, remaining
):
5171 "Parse a macro parameter. Could be a bracket or a single letter."
5172 "If there are just two values remaining and there is a running number,"
5173 "parse as two separater numbers."
5174 self
.factory
.clearskipped(pos
)
5177 if self
.factory
.detecttype(FormulaNumber
, pos
):
5178 return self
.parsenumbers(pos
, remaining
)
5179 return self
.parseparameter(pos
)
5181 def parsenumbers(self
, pos
, remaining
):
5182 "Parse the remaining parameters as a running number."
5183 "For example, 12 would be {1}{2}."
5184 number
= self
.factory
.parsetype(FormulaNumber
, pos
)
5185 if not len(number
.original
) == remaining
:
5187 for digit
in number
.original
:
5188 value
= self
.factory
.create(FormulaNumber
)
5189 value
.add(FormulaConstant(digit
))
5191 self
.values
.append(value
)
5194 def completemacro(self
, macro
):
5195 "Complete the macro with the parameters read."
5196 self
.contents
= [macro
.instantiate()]
5197 replaced
= [False] * len(self
.values
)
5198 for parameter
in self
.searchall(MacroParameter
):
5199 index
= parameter
.number
- 1
5200 if index
>= len(self
.values
):
5201 Trace
.error('Macro parameter index out of bounds: ' + unicode(index
))
5203 replaced
[index
] = True
5204 parameter
.contents
= [self
.values
[index
].clone()]
5205 for index
in range(len(self
.values
)):
5206 if not replaced
[index
]:
5207 self
.addfilter(index
, self
.values
[index
])
5209 def addfilter(self
, index
, value
):
5210 "Add a filter for the given parameter number and parameter value."
5211 original
= '#' + unicode(index
+ 1)
5212 value
= ''.join(self
.values
[0].gethtml())
5213 self
.output
.addfilter(original
, value
)
5215 class FormulaMacro(Formula
):
5216 "A math macro defined in an inset."
5219 self
.parser
= MacroParser()
5220 self
.output
= EmptyOutput()
5222 def __unicode__(self
):
5223 "Return a printable representation."
5226 FormulaFactory
.types
+= [ MacroParameter
]
5228 FormulaCommand
.types
+= [
5234 def math2html(formula
):
5235 "Convert some TeX math to HTML."
5236 factory
= FormulaFactory()
5237 whole
= factory
.parseformula(formula
)
5238 FormulaProcessor().process(whole
)
5240 return ''.join(whole
.gethtml())
5243 "Main function, called if invoked from elyxer.the command line"
5245 Options().parseoptions(args
)
5247 Trace
.error('Usage: math2html.py escaped_string')
5249 result
= math2html(args
[0])
5250 Trace
.message(result
)
5252 if __name__
== '__main__':