Mercurial > repos > bcclaywell > argo_navis
comparison venv/lib/python2.7/site-packages/docutils/utils/math/math2html.py @ 0:d67268158946 draft
planemo upload commit a3f181f5f126803c654b3a66dd4e83a48f7e203b
author | bcclaywell |
---|---|
date | Mon, 12 Oct 2015 17:43:33 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:d67268158946 |
---|---|
1 #! /usr/bin/env python | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # math2html: convert LaTeX equations to HTML output. | |
5 # | |
6 # Copyright (C) 2009-2011 Alex Fernández | |
7 # | |
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. | |
13 # | |
14 # .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause | |
15 | |
16 # Based on eLyXer: convert LyX source files to HTML output. | |
17 # http://elyxer.nongnu.org/ | |
18 | |
19 # --end-- | |
20 # Alex 20101110 | |
21 # eLyXer standalone formula conversion to HTML. | |
22 | |
23 | |
24 | |
25 | |
26 import sys | |
27 | |
28 class Trace(object): | |
29 "A tracing class" | |
30 | |
31 debugmode = False | |
32 quietmode = False | |
33 showlinesmode = False | |
34 | |
35 prefix = None | |
36 | |
37 def debug(cls, message): | |
38 "Show a debug message" | |
39 if not Trace.debugmode or Trace.quietmode: | |
40 return | |
41 Trace.show(message, sys.stdout) | |
42 | |
43 def message(cls, message): | |
44 "Show a trace message" | |
45 if Trace.quietmode: | |
46 return | |
47 if Trace.prefix and Trace.showlinesmode: | |
48 message = Trace.prefix + message | |
49 Trace.show(message, sys.stdout) | |
50 | |
51 def error(cls, message): | |
52 "Show an error message" | |
53 message = '* ' + message | |
54 if Trace.prefix and Trace.showlinesmode: | |
55 message = Trace.prefix + message | |
56 Trace.show(message, sys.stderr) | |
57 | |
58 def fatal(cls, message): | |
59 "Show an error message and terminate" | |
60 Trace.error('FATAL: ' + message) | |
61 exit(-1) | |
62 | |
63 def show(cls, message, channel): | |
64 "Show a message out of a channel" | |
65 if sys.version_info < (3,0): | |
66 message = message.encode('utf-8') | |
67 channel.write(message + '\n') | |
68 | |
69 debug = classmethod(debug) | |
70 message = classmethod(message) | |
71 error = classmethod(error) | |
72 fatal = classmethod(fatal) | |
73 show = classmethod(show) | |
74 | |
75 | |
76 | |
77 | |
78 import os.path | |
79 import sys | |
80 | |
81 | |
82 class BibStylesConfig(object): | |
83 "Configuration class from elyxer.config file" | |
84 | |
85 abbrvnat = { | |
86 | |
87 u'@article':u'$authors. $title. <i>$journal</i>,{ {$volume:}$pages,} $month $year.{ doi: $doi.}{ URL <a href="$url">$url</a>.}{ $note.}', | |
88 u'cite':u'$surname($year)', | |
89 u'default':u'$authors. <i>$title</i>. $publisher, $year.{ URL <a href="$url">$url</a>.}{ $note.}', | |
90 } | |
91 | |
92 alpha = { | |
93 | |
94 u'@article':u'$authors. $title.{ <i>$journal</i>{, {$volume}{($number)}}{: $pages}{, $year}.}{ <a href="$url">$url</a>.}{ <a href="$filename">$filename</a>.}{ $note.}', | |
95 u'cite':u'$Sur$YY', | |
96 u'default':u'$authors. $title.{ <i>$journal</i>,} $year.{ <a href="$url">$url</a>.}{ <a href="$filename">$filename</a>.}{ $note.}', | |
97 } | |
98 | |
99 authordate2 = { | |
100 | |
101 u'@article':u'$authors. $year. $title. <i>$journal</i>, <b>$volume</b>($number), $pages.{ URL <a href="$url">$url</a>.}{ $note.}', | |
102 u'@book':u'$authors. $year. <i>$title</i>. $publisher.{ URL <a href="$url">$url</a>.}{ $note.}', | |
103 u'cite':u'$surname, $year', | |
104 u'default':u'$authors. $year. <i>$title</i>. $publisher.{ URL <a href="$url">$url</a>.}{ $note.}', | |
105 } | |
106 | |
107 default = { | |
108 | |
109 u'@article':u'$authors: “$title”, <i>$journal</i>,{ pp. $pages,} $year.{ URL <a href="$url">$url</a>.}{ $note.}', | |
110 u'@book':u'{$authors: }<i>$title</i>{ ($editor, ed.)}.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', | |
111 u'@booklet':u'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', | |
112 u'@conference':u'$authors: “$title”, <i>$journal</i>,{ pp. $pages,} $year.{ URL <a href="$url">$url</a>.}{ $note.}', | |
113 u'@inbook':u'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', | |
114 u'@incollection':u'$authors: <i>$title</i>{ in <i>$booktitle</i>{ ($editor, ed.)}}.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', | |
115 u'@inproceedings':u'$authors: “$title”, <i>$journal</i>,{ pp. $pages,} $year.{ URL <a href="$url">$url</a>.}{ $note.}', | |
116 u'@manual':u'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', | |
117 u'@mastersthesis':u'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', | |
118 u'@misc':u'$authors: <i>$title</i>.{{ $publisher,}{ $howpublished,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', | |
119 u'@phdthesis':u'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', | |
120 u'@proceedings':u'$authors: “$title”, <i>$journal</i>,{ pp. $pages,} $year.{ URL <a href="$url">$url</a>.}{ $note.}', | |
121 u'@techreport':u'$authors: <i>$title</i>, $year.{ URL <a href="$url">$url</a>.}{ $note.}', | |
122 u'@unpublished':u'$authors: “$title”, <i>$journal</i>, $year.{ URL <a href="$url">$url</a>.}{ $note.}', | |
123 u'cite':u'$index', | |
124 u'default':u'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', | |
125 } | |
126 | |
127 defaulttags = { | |
128 u'YY':u'??', u'authors':u'', u'surname':u'', | |
129 } | |
130 | |
131 ieeetr = { | |
132 | |
133 u'@article':u'$authors, “$title”, <i>$journal</i>, vol. $volume, no. $number, pp. $pages, $year.{ URL <a href="$url">$url</a>.}{ $note.}', | |
134 u'@book':u'$authors, <i>$title</i>. $publisher, $year.{ URL <a href="$url">$url</a>.}{ $note.}', | |
135 u'cite':u'$index', | |
136 u'default':u'$authors, “$title”. $year.{ URL <a href="$url">$url</a>.}{ $note.}', | |
137 } | |
138 | |
139 plain = { | |
140 | |
141 u'@article':u'$authors. $title.{ <i>$journal</i>{, {$volume}{($number)}}{:$pages}{, $year}.}{ URL <a href="$url">$url</a>.}{ $note.}', | |
142 u'@book':u'$authors. <i>$title</i>. $publisher,{ $month} $year.{ URL <a href="$url">$url</a>.}{ $note.}', | |
143 u'@incollection':u'$authors. $title.{ In <i>$booktitle</i> {($editor, ed.)}.} $publisher,{ $month} $year.{ URL <a href="$url">$url</a>.}{ $note.}', | |
144 u'@inproceedings':u'$authors. $title. { <i>$booktitle</i>{, {$volume}{($number)}}{:$pages}{, $year}.}{ URL <a href="$url">$url</a>.}{ $note.}', | |
145 u'cite':u'$index', | |
146 u'default':u'{$authors. }$title.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', | |
147 } | |
148 | |
149 vancouver = { | |
150 | |
151 u'@article':u'$authors. $title. <i>$journal</i>, $year{;{<b>$volume</b>}{($number)}{:$pages}}.{ URL: <a href="$url">$url</a>.}{ $note.}', | |
152 u'@book':u'$authors. $title. {$publisher, }$year.{ URL: <a href="$url">$url</a>.}{ $note.}', | |
153 u'cite':u'$index', | |
154 u'default':u'$authors. $title; {$publisher, }$year.{ $howpublished.}{ URL: <a href="$url">$url</a>.}{ $note.}', | |
155 } | |
156 | |
157 class BibTeXConfig(object): | |
158 "Configuration class from elyxer.config file" | |
159 | |
160 replaced = { | |
161 u'--':u'—', u'..':u'.', | |
162 } | |
163 | |
164 class ContainerConfig(object): | |
165 "Configuration class from elyxer.config file" | |
166 | |
167 endings = { | |
168 u'Align':u'\\end_layout', u'BarredText':u'\\bar', | |
169 u'BoldText':u'\\series', u'Cell':u'</cell', | |
170 u'ChangeDeleted':u'\\change_unchanged', | |
171 u'ChangeInserted':u'\\change_unchanged', u'ColorText':u'\\color', | |
172 u'EmphaticText':u'\\emph', u'Hfill':u'\\hfill', u'Inset':u'\\end_inset', | |
173 u'Layout':u'\\end_layout', u'LyXFooter':u'\\end_document', | |
174 u'LyXHeader':u'\\end_header', u'Row':u'</row', u'ShapedText':u'\\shape', | |
175 u'SizeText':u'\\size', u'StrikeOut':u'\\strikeout', | |
176 u'TextFamily':u'\\family', u'VersalitasText':u'\\noun', | |
177 } | |
178 | |
179 extracttext = { | |
180 u'allowed':[u'StringContainer',u'Constant',u'FormulaConstant',], | |
181 u'cloned':[u'',], | |
182 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',], | |
183 } | |
184 | |
185 startendings = { | |
186 u'\\begin_deeper':u'\\end_deeper', u'\\begin_inset':u'\\end_inset', | |
187 u'\\begin_layout':u'\\end_layout', | |
188 } | |
189 | |
190 starts = { | |
191 u'':u'StringContainer', u'#LyX':u'BlackBox', u'</lyxtabular':u'BlackBox', | |
192 u'<cell':u'Cell', u'<column':u'Column', u'<row':u'Row', | |
193 u'\\align':u'Align', u'\\bar':u'BarredText', | |
194 u'\\bar default':u'BlackBox', u'\\bar no':u'BlackBox', | |
195 u'\\begin_body':u'BlackBox', u'\\begin_deeper':u'DeeperList', | |
196 u'\\begin_document':u'BlackBox', u'\\begin_header':u'LyXHeader', | |
197 u'\\begin_inset Argument':u'ShortTitle', | |
198 u'\\begin_inset Box':u'BoxInset', u'\\begin_inset Branch':u'Branch', | |
199 u'\\begin_inset Caption':u'Caption', | |
200 u'\\begin_inset CommandInset bibitem':u'BiblioEntry', | |
201 u'\\begin_inset CommandInset bibtex':u'BibTeX', | |
202 u'\\begin_inset CommandInset citation':u'BiblioCitation', | |
203 u'\\begin_inset CommandInset href':u'URL', | |
204 u'\\begin_inset CommandInset include':u'IncludeInset', | |
205 u'\\begin_inset CommandInset index_print':u'PrintIndex', | |
206 u'\\begin_inset CommandInset label':u'Label', | |
207 u'\\begin_inset CommandInset line':u'LineInset', | |
208 u'\\begin_inset CommandInset nomencl_print':u'PrintNomenclature', | |
209 u'\\begin_inset CommandInset nomenclature':u'NomenclatureEntry', | |
210 u'\\begin_inset CommandInset ref':u'Reference', | |
211 u'\\begin_inset CommandInset toc':u'TableOfContents', | |
212 u'\\begin_inset ERT':u'ERT', u'\\begin_inset Flex':u'FlexInset', | |
213 u'\\begin_inset Flex Chunkref':u'NewfangledChunkRef', | |
214 u'\\begin_inset Flex Marginnote':u'SideNote', | |
215 u'\\begin_inset Flex Sidenote':u'SideNote', | |
216 u'\\begin_inset Flex URL':u'FlexURL', u'\\begin_inset Float':u'Float', | |
217 u'\\begin_inset FloatList':u'ListOf', u'\\begin_inset Foot':u'Footnote', | |
218 u'\\begin_inset Formula':u'Formula', | |
219 u'\\begin_inset FormulaMacro':u'FormulaMacro', | |
220 u'\\begin_inset Graphics':u'Image', | |
221 u'\\begin_inset Index':u'IndexReference', | |
222 u'\\begin_inset Info':u'InfoInset', | |
223 u'\\begin_inset LatexCommand bibitem':u'BiblioEntry', | |
224 u'\\begin_inset LatexCommand bibtex':u'BibTeX', | |
225 u'\\begin_inset LatexCommand cite':u'BiblioCitation', | |
226 u'\\begin_inset LatexCommand citealt':u'BiblioCitation', | |
227 u'\\begin_inset LatexCommand citep':u'BiblioCitation', | |
228 u'\\begin_inset LatexCommand citet':u'BiblioCitation', | |
229 u'\\begin_inset LatexCommand htmlurl':u'URL', | |
230 u'\\begin_inset LatexCommand index':u'IndexReference', | |
231 u'\\begin_inset LatexCommand label':u'Label', | |
232 u'\\begin_inset LatexCommand nomenclature':u'NomenclatureEntry', | |
233 u'\\begin_inset LatexCommand prettyref':u'Reference', | |
234 u'\\begin_inset LatexCommand printindex':u'PrintIndex', | |
235 u'\\begin_inset LatexCommand printnomenclature':u'PrintNomenclature', | |
236 u'\\begin_inset LatexCommand ref':u'Reference', | |
237 u'\\begin_inset LatexCommand tableofcontents':u'TableOfContents', | |
238 u'\\begin_inset LatexCommand url':u'URL', | |
239 u'\\begin_inset LatexCommand vref':u'Reference', | |
240 u'\\begin_inset Marginal':u'SideNote', | |
241 u'\\begin_inset Newline':u'NewlineInset', | |
242 u'\\begin_inset Newpage':u'NewPageInset', u'\\begin_inset Note':u'Note', | |
243 u'\\begin_inset OptArg':u'ShortTitle', | |
244 u'\\begin_inset Phantom':u'PhantomText', | |
245 u'\\begin_inset Quotes':u'QuoteContainer', | |
246 u'\\begin_inset Tabular':u'Table', u'\\begin_inset Text':u'InsetText', | |
247 u'\\begin_inset VSpace':u'VerticalSpace', u'\\begin_inset Wrap':u'Wrap', | |
248 u'\\begin_inset listings':u'Listing', u'\\begin_inset space':u'Space', | |
249 u'\\begin_layout':u'Layout', u'\\begin_layout Abstract':u'Abstract', | |
250 u'\\begin_layout Author':u'Author', | |
251 u'\\begin_layout Bibliography':u'Bibliography', | |
252 u'\\begin_layout Chunk':u'NewfangledChunk', | |
253 u'\\begin_layout Description':u'Description', | |
254 u'\\begin_layout Enumerate':u'ListItem', | |
255 u'\\begin_layout Itemize':u'ListItem', u'\\begin_layout List':u'List', | |
256 u'\\begin_layout LyX-Code':u'LyXCode', | |
257 u'\\begin_layout Plain':u'PlainLayout', | |
258 u'\\begin_layout Standard':u'StandardLayout', | |
259 u'\\begin_layout Title':u'Title', u'\\begin_preamble':u'LyXPreamble', | |
260 u'\\change_deleted':u'ChangeDeleted', | |
261 u'\\change_inserted':u'ChangeInserted', | |
262 u'\\change_unchanged':u'BlackBox', u'\\color':u'ColorText', | |
263 u'\\color inherit':u'BlackBox', u'\\color none':u'BlackBox', | |
264 u'\\emph default':u'BlackBox', u'\\emph off':u'BlackBox', | |
265 u'\\emph on':u'EmphaticText', u'\\emph toggle':u'EmphaticText', | |
266 u'\\end_body':u'LyXFooter', u'\\family':u'TextFamily', | |
267 u'\\family default':u'BlackBox', u'\\family roman':u'BlackBox', | |
268 u'\\hfill':u'Hfill', u'\\labelwidthstring':u'BlackBox', | |
269 u'\\lang':u'LangLine', u'\\length':u'InsetLength', | |
270 u'\\lyxformat':u'LyXFormat', u'\\lyxline':u'LyXLine', | |
271 u'\\newline':u'Newline', u'\\newpage':u'NewPage', | |
272 u'\\noindent':u'BlackBox', u'\\noun default':u'BlackBox', | |
273 u'\\noun off':u'BlackBox', u'\\noun on':u'VersalitasText', | |
274 u'\\paragraph_spacing':u'BlackBox', u'\\series bold':u'BoldText', | |
275 u'\\series default':u'BlackBox', u'\\series medium':u'BlackBox', | |
276 u'\\shape':u'ShapedText', u'\\shape default':u'BlackBox', | |
277 u'\\shape up':u'BlackBox', u'\\size':u'SizeText', | |
278 u'\\size normal':u'BlackBox', u'\\start_of_appendix':u'StartAppendix', | |
279 u'\\strikeout default':u'BlackBox', u'\\strikeout on':u'StrikeOut', | |
280 } | |
281 | |
282 string = { | |
283 u'startcommand':u'\\', | |
284 } | |
285 | |
286 table = { | |
287 u'headers':[u'<lyxtabular',u'<features',], | |
288 } | |
289 | |
290 class EscapeConfig(object): | |
291 "Configuration class from elyxer.config file" | |
292 | |
293 chars = { | |
294 u'\n':u'', u' -- ':u' — ', u'\'':u'’', u'---':u'—', u'`':u'‘', | |
295 } | |
296 | |
297 commands = { | |
298 u'\\InsetSpace \\space{}':u' ', u'\\InsetSpace \\thinspace{}':u' ', | |
299 u'\\InsetSpace ~':u' ', u'\\SpecialChar \\-':u'', | |
300 u'\\SpecialChar \\@.':u'.', u'\\SpecialChar \\ldots{}':u'…', | |
301 u'\\SpecialChar \\menuseparator':u' ▷ ', | |
302 u'\\SpecialChar \\nobreakdash-':u'-', u'\\SpecialChar \\slash{}':u'/', | |
303 u'\\SpecialChar \\textcompwordmark{}':u'', u'\\backslash':u'\\', | |
304 } | |
305 | |
306 entities = { | |
307 u'&':u'&', u'<':u'<', u'>':u'>', | |
308 } | |
309 | |
310 html = { | |
311 u'/>':u'>', | |
312 } | |
313 | |
314 iso885915 = { | |
315 u' ':u' ', u' ':u' ', u' ':u' ', | |
316 } | |
317 | |
318 nonunicode = { | |
319 u' ':u' ', | |
320 } | |
321 | |
322 class FormulaConfig(object): | |
323 "Configuration class from elyxer.config file" | |
324 | |
325 alphacommands = { | |
326 u'\\AA':u'Å', u'\\AE':u'Æ', | |
327 u'\\AmS':u'<span class="versalitas">AmS</span>', u'\\DH':u'Ð', | |
328 u'\\L':u'Ł', u'\\O':u'Ø', u'\\OE':u'Œ', u'\\TH':u'Þ', u'\\aa':u'å', | |
329 u'\\ae':u'æ', u'\\alpha':u'α', u'\\beta':u'β', u'\\delta':u'δ', | |
330 u'\\dh':u'ð', u'\\epsilon':u'ϵ', u'\\eta':u'η', u'\\gamma':u'γ', | |
331 u'\\i':u'ı', u'\\imath':u'ı', u'\\iota':u'ι', u'\\j':u'ȷ', | |
332 u'\\jmath':u'ȷ', u'\\kappa':u'κ', u'\\l':u'ł', u'\\lambda':u'λ', | |
333 u'\\mu':u'μ', u'\\nu':u'ν', u'\\o':u'ø', u'\\oe':u'œ', u'\\omega':u'ω', | |
334 u'\\phi':u'φ', u'\\pi':u'π', u'\\psi':u'ψ', u'\\rho':u'ρ', | |
335 u'\\sigma':u'σ', u'\\ss':u'ß', u'\\tau':u'τ', u'\\textcrh':u'ħ', | |
336 u'\\th':u'þ', u'\\theta':u'θ', u'\\upsilon':u'υ', u'\\varDelta':u'∆', | |
337 u'\\varGamma':u'Γ', u'\\varLambda':u'Λ', u'\\varOmega':u'Ω', | |
338 u'\\varPhi':u'Φ', u'\\varPi':u'Π', u'\\varPsi':u'Ψ', u'\\varSigma':u'Σ', | |
339 u'\\varTheta':u'Θ', u'\\varUpsilon':u'Υ', u'\\varXi':u'Ξ', | |
340 u'\\varepsilon':u'ε', u'\\varkappa':u'ϰ', u'\\varphi':u'φ', | |
341 u'\\varpi':u'ϖ', u'\\varrho':u'ϱ', u'\\varsigma':u'ς', | |
342 u'\\vartheta':u'ϑ', u'\\xi':u'ξ', u'\\zeta':u'ζ', | |
343 } | |
344 | |
345 array = { | |
346 u'begin':u'\\begin', u'cellseparator':u'&', u'end':u'\\end', | |
347 u'rowseparator':u'\\\\', | |
348 } | |
349 | |
350 bigbrackets = { | |
351 u'(':[u'⎛',u'⎜',u'⎝',], u')':[u'⎞',u'⎟',u'⎠',], u'[':[u'⎡',u'⎢',u'⎣',], | |
352 u']':[u'⎤',u'⎥',u'⎦',], u'{':[u'⎧',u'⎪',u'⎨',u'⎩',], u'|':[u'|',], | |
353 u'}':[u'⎫',u'⎪',u'⎬',u'⎭',], u'∥':[u'∥',], | |
354 } | |
355 | |
356 bigsymbols = { | |
357 u'∑':[u'⎲',u'⎳',], u'∫':[u'⌠',u'⌡',], | |
358 } | |
359 | |
360 bracketcommands = { | |
361 u'\\left':u'span class="symbol"', | |
362 u'\\left.':u'<span class="leftdot"></span>', | |
363 u'\\middle':u'span class="symbol"', u'\\right':u'span class="symbol"', | |
364 u'\\right.':u'<span class="rightdot"></span>', | |
365 } | |
366 | |
367 combiningfunctions = { | |
368 u'\\"':u'̈', u'\\\'':u'́', u'\\^':u'̂', u'\\`':u'̀', u'\\acute':u'́', | |
369 u'\\bar':u'̄', u'\\breve':u'̆', u'\\c':u'̧', u'\\check':u'̌', | |
370 u'\\dddot':u'⃛', u'\\ddot':u'̈', u'\\dot':u'̇', u'\\grave':u'̀', | |
371 u'\\hat':u'̂', u'\\mathring':u'̊', u'\\overleftarrow':u'⃖', | |
372 u'\\overrightarrow':u'⃗', u'\\r':u'̊', u'\\s':u'̩', | |
373 u'\\textcircled':u'⃝', u'\\textsubring':u'̥', u'\\tilde':u'̃', | |
374 u'\\v':u'̌', u'\\vec':u'⃗', u'\\~':u'̃', | |
375 } | |
376 | |
377 commands = { | |
378 u'\\ ':u' ', u'\\!':u'', u'\\#':u'#', u'\\$':u'$', u'\\%':u'%', | |
379 u'\\&':u'&', u'\\,':u' ', u'\\:':u' ', u'\\;':u' ', | |
380 u'\\APLdownarrowbox':u'⍗', u'\\APLleftarrowbox':u'⍇', | |
381 u'\\APLrightarrowbox':u'⍈', u'\\APLuparrowbox':u'⍐', u'\\Box':u'□', | |
382 u'\\Bumpeq':u'≎', u'\\CIRCLE':u'●', u'\\Cap':u'⋒', u'\\CheckedBox':u'☑', | |
383 u'\\Circle':u'○', u'\\Coloneqq':u'⩴', u'\\Corresponds':u'≙', | |
384 u'\\Cup':u'⋓', u'\\Delta':u'Δ', u'\\Diamond':u'◇', u'\\Downarrow':u'⇓', | |
385 u'\\EUR':u'€', u'\\Game':u'⅁', u'\\Gamma':u'Γ', u'\\Im':u'ℑ', | |
386 u'\\Join':u'⨝', u'\\LEFTCIRCLE':u'◖', u'\\LEFTcircle':u'◐', | |
387 u'\\Lambda':u'Λ', u'\\Leftarrow':u'⇐', u'\\Lleftarrow':u'⇚', | |
388 u'\\Longleftarrow':u'⟸', u'\\Longleftrightarrow':u'⟺', | |
389 u'\\Longrightarrow':u'⟹', u'\\Lsh':u'↰', u'\\Mapsfrom':u'⇐|', | |
390 u'\\Mapsto':u'|⇒', u'\\Omega':u'Ω', u'\\P':u'¶', u'\\Phi':u'Φ', | |
391 u'\\Pi':u'Π', u'\\Pr':u'Pr', u'\\Psi':u'Ψ', u'\\RIGHTCIRCLE':u'◗', | |
392 u'\\RIGHTcircle':u'◑', u'\\Re':u'ℜ', u'\\Rrightarrow':u'⇛', | |
393 u'\\Rsh':u'↱', u'\\S':u'§', u'\\Sigma':u'Σ', u'\\Square':u'☐', | |
394 u'\\Subset':u'⋐', u'\\Supset':u'⋑', u'\\Theta':u'Θ', u'\\Uparrow':u'⇑', | |
395 u'\\Updownarrow':u'⇕', u'\\Upsilon':u'Υ', u'\\Vdash':u'⊩', | |
396 u'\\Vert':u'∥', u'\\Vvdash':u'⊪', u'\\XBox':u'☒', u'\\Xi':u'Ξ', | |
397 u'\\Yup':u'⅄', u'\\\\':u'<br/>', u'\\_':u'_', u'\\aleph':u'ℵ', | |
398 u'\\amalg':u'∐', u'\\angle':u'∠', u'\\aquarius':u'♒', | |
399 u'\\arccos':u'arccos', u'\\arcsin':u'arcsin', u'\\arctan':u'arctan', | |
400 u'\\arg':u'arg', u'\\aries':u'♈', u'\\ast':u'∗', u'\\asymp':u'≍', | |
401 u'\\backepsilon':u'∍', u'\\backprime':u'‵', u'\\backsimeq':u'⋍', | |
402 u'\\backslash':u'\\', u'\\barwedge':u'⊼', u'\\because':u'∵', | |
403 u'\\beth':u'ℶ', u'\\between':u'≬', u'\\bigcap':u'∩', u'\\bigcirc':u'○', | |
404 u'\\bigcup':u'∪', u'\\bigodot':u'⊙', u'\\bigoplus':u'⊕', | |
405 u'\\bigotimes':u'⊗', u'\\bigsqcup':u'⊔', u'\\bigstar':u'★', | |
406 u'\\bigtriangledown':u'▽', u'\\bigtriangleup':u'△', u'\\biguplus':u'⊎', | |
407 u'\\bigvee':u'∨', u'\\bigwedge':u'∧', u'\\blacklozenge':u'⧫', | |
408 u'\\blacksmiley':u'☻', u'\\blacksquare':u'■', u'\\blacktriangle':u'▲', | |
409 u'\\blacktriangledown':u'▼', u'\\blacktriangleright':u'▶', u'\\bot':u'⊥', | |
410 u'\\bowtie':u'⋈', u'\\box':u'▫', u'\\boxdot':u'⊡', u'\\bullet':u'•', | |
411 u'\\bumpeq':u'≏', u'\\cancer':u'♋', u'\\cap':u'∩', u'\\capricornus':u'♑', | |
412 u'\\cdot':u'⋅', u'\\cdots':u'⋯', u'\\centerdot':u'∙', | |
413 u'\\checkmark':u'✓', u'\\chi':u'χ', u'\\circ':u'○', u'\\circeq':u'≗', | |
414 u'\\circledR':u'®', u'\\circledast':u'⊛', u'\\circledcirc':u'⊚', | |
415 u'\\circleddash':u'⊝', u'\\clubsuit':u'♣', u'\\coloneqq':u'≔', | |
416 u'\\complement':u'∁', u'\\cong':u'≅', u'\\coprod':u'∐', | |
417 u'\\copyright':u'©', u'\\cos':u'cos', u'\\cosh':u'cosh', u'\\cot':u'cot', | |
418 u'\\coth':u'coth', u'\\csc':u'csc', u'\\cup':u'∪', | |
419 u'\\curvearrowleft':u'↶', u'\\curvearrowright':u'↷', u'\\dag':u'†', | |
420 u'\\dagger':u'†', u'\\daleth':u'ℸ', u'\\dashleftarrow':u'⇠', | |
421 u'\\dashv':u'⊣', u'\\ddag':u'‡', u'\\ddagger':u'‡', u'\\ddots':u'⋱', | |
422 u'\\deg':u'deg', u'\\det':u'det', u'\\diagdown':u'╲', u'\\diagup':u'╱', | |
423 u'\\diamond':u'◇', u'\\diamondsuit':u'♦', u'\\dim':u'dim', u'\\div':u'÷', | |
424 u'\\divideontimes':u'⋇', u'\\dotdiv':u'∸', u'\\doteq':u'≐', | |
425 u'\\doteqdot':u'≑', u'\\dotplus':u'∔', u'\\dots':u'…', | |
426 u'\\doublebarwedge':u'⌆', u'\\downarrow':u'↓', u'\\downdownarrows':u'⇊', | |
427 u'\\downharpoonleft':u'⇃', u'\\downharpoonright':u'⇂', u'\\earth':u'♁', | |
428 u'\\ell':u'ℓ', u'\\emptyset':u'∅', u'\\eqcirc':u'≖', u'\\eqcolon':u'≕', | |
429 u'\\eqsim':u'≂', u'\\euro':u'€', u'\\exists':u'∃', u'\\exp':u'exp', | |
430 u'\\fallingdotseq':u'≒', u'\\female':u'♀', u'\\flat':u'♭', | |
431 u'\\forall':u'∀', u'\\frown':u'⌢', u'\\frownie':u'☹', u'\\gcd':u'gcd', | |
432 u'\\gemini':u'♊', u'\\geq)':u'≥', u'\\geqq':u'≧', u'\\geqslant':u'≥', | |
433 u'\\gets':u'←', u'\\gg':u'≫', u'\\ggg':u'⋙', u'\\gimel':u'ℷ', | |
434 u'\\gneqq':u'≩', u'\\gnsim':u'⋧', u'\\gtrdot':u'⋗', u'\\gtreqless':u'⋚', | |
435 u'\\gtreqqless':u'⪌', u'\\gtrless':u'≷', u'\\gtrsim':u'≳', | |
436 u'\\guillemotleft':u'«', u'\\guillemotright':u'»', u'\\hbar':u'ℏ', | |
437 u'\\heartsuit':u'♥', u'\\hfill':u'<span class="hfill"> </span>', | |
438 u'\\hom':u'hom', u'\\hookleftarrow':u'↩', u'\\hookrightarrow':u'↪', | |
439 u'\\hslash':u'ℏ', u'\\idotsint':u'<span class="bigsymbol">∫⋯∫</span>', | |
440 u'\\iiint':u'<span class="bigsymbol">∭</span>', | |
441 u'\\iint':u'<span class="bigsymbol">∬</span>', u'\\imath':u'ı', | |
442 u'\\inf':u'inf', u'\\infty':u'∞', u'\\invneg':u'⌐', u'\\jmath':u'ȷ', | |
443 u'\\jupiter':u'♃', u'\\ker':u'ker', u'\\land':u'∧', | |
444 u'\\landupint':u'<span class="bigsymbol">∱</span>', u'\\langle':u'⟨', | |
445 u'\\lbrace':u'{', u'\\lbrace)':u'{', u'\\lbrack':u'[', u'\\lceil':u'⌈', | |
446 u'\\ldots':u'…', u'\\leadsto':u'⇝', u'\\leftarrow)':u'←', | |
447 u'\\leftarrowtail':u'↢', u'\\leftarrowtobar':u'⇤', | |
448 u'\\leftharpoondown':u'↽', u'\\leftharpoonup':u'↼', | |
449 u'\\leftleftarrows':u'⇇', u'\\leftleftharpoons':u'⥢', u'\\leftmoon':u'☾', | |
450 u'\\leftrightarrow':u'↔', u'\\leftrightarrows':u'⇆', | |
451 u'\\leftrightharpoons':u'⇋', u'\\leftthreetimes':u'⋋', u'\\leo':u'♌', | |
452 u'\\leq)':u'≤', u'\\leqq':u'≦', u'\\leqslant':u'≤', u'\\lessdot':u'⋖', | |
453 u'\\lesseqgtr':u'⋛', u'\\lesseqqgtr':u'⪋', u'\\lessgtr':u'≶', | |
454 u'\\lesssim':u'≲', u'\\lfloor':u'⌊', u'\\lg':u'lg', u'\\lhd':u'⊲', | |
455 u'\\libra':u'♎', u'\\lightning':u'↯', u'\\liminf':u'liminf', | |
456 u'\\limsup':u'limsup', u'\\ll':u'≪', u'\\lll':u'⋘', u'\\ln':u'ln', | |
457 u'\\lneqq':u'≨', u'\\lnot':u'¬', u'\\lnsim':u'⋦', u'\\log':u'log', | |
458 u'\\longleftarrow':u'⟵', u'\\longleftrightarrow':u'⟷', | |
459 u'\\longmapsto':u'⟼', u'\\longrightarrow':u'⟶', u'\\looparrowleft':u'↫', | |
460 u'\\looparrowright':u'↬', u'\\lor':u'∨', u'\\lozenge':u'◊', | |
461 u'\\ltimes':u'⋉', u'\\lyxlock':u'', u'\\male':u'♂', u'\\maltese':u'✠', | |
462 u'\\mapsfrom':u'↤', u'\\mapsto':u'↦', u'\\mathcircumflex':u'^', | |
463 u'\\max':u'max', u'\\measuredangle':u'∡', u'\\mercury':u'☿', | |
464 u'\\mho':u'℧', u'\\mid':u'∣', u'\\min':u'min', u'\\models':u'⊨', | |
465 u'\\mp':u'∓', u'\\multimap':u'⊸', u'\\nLeftarrow':u'⇍', | |
466 u'\\nLeftrightarrow':u'⇎', u'\\nRightarrow':u'⇏', u'\\nVDash':u'⊯', | |
467 u'\\nabla':u'∇', u'\\napprox':u'≉', u'\\natural':u'♮', u'\\ncong':u'≇', | |
468 u'\\nearrow':u'↗', u'\\neg':u'¬', u'\\neg)':u'¬', u'\\neptune':u'♆', | |
469 u'\\nequiv':u'≢', u'\\newline':u'<br/>', u'\\nexists':u'∄', | |
470 u'\\ngeqslant':u'≱', u'\\ngtr':u'≯', u'\\ngtrless':u'≹', u'\\ni':u'∋', | |
471 u'\\ni)':u'∋', u'\\nleftarrow':u'↚', u'\\nleftrightarrow':u'↮', | |
472 u'\\nleqslant':u'≰', u'\\nless':u'≮', u'\\nlessgtr':u'≸', u'\\nmid':u'∤', | |
473 u'\\nolimits':u'', u'\\nonumber':u'', u'\\not':u'¬', u'\\not<':u'≮', | |
474 u'\\not=':u'≠', u'\\not>':u'≯', u'\\notbackslash':u'⍀', u'\\notin':u'∉', | |
475 u'\\notni':u'∌', u'\\notslash':u'⌿', u'\\nparallel':u'∦', | |
476 u'\\nprec':u'⊀', u'\\nrightarrow':u'↛', u'\\nsim':u'≁', u'\\nsimeq':u'≄', | |
477 u'\\nsqsubset':u'⊏̸', u'\\nsubseteq':u'⊈', u'\\nsucc':u'⊁', | |
478 u'\\nsucccurlyeq':u'⋡', u'\\nsupset':u'⊅', u'\\nsupseteq':u'⊉', | |
479 u'\\ntriangleleft':u'⋪', u'\\ntrianglelefteq':u'⋬', | |
480 u'\\ntriangleright':u'⋫', u'\\ntrianglerighteq':u'⋭', u'\\nvDash':u'⊭', | |
481 u'\\nvdash':u'⊬', u'\\nwarrow':u'↖', u'\\odot':u'⊙', | |
482 u'\\officialeuro':u'€', u'\\oiiint':u'<span class="bigsymbol">∰</span>', | |
483 u'\\oiint':u'<span class="bigsymbol">∯</span>', | |
484 u'\\oint':u'<span class="bigsymbol">∮</span>', | |
485 u'\\ointclockwise':u'<span class="bigsymbol">∲</span>', | |
486 u'\\ointctrclockwise':u'<span class="bigsymbol">∳</span>', | |
487 u'\\ominus':u'⊖', u'\\oplus':u'⊕', u'\\oslash':u'⊘', u'\\otimes':u'⊗', | |
488 u'\\owns':u'∋', u'\\parallel':u'∥', u'\\partial':u'∂', u'\\perp':u'⊥', | |
489 u'\\pisces':u'♓', u'\\pitchfork':u'⋔', u'\\pluto':u'♇', u'\\pm':u'±', | |
490 u'\\pointer':u'➪', u'\\pounds':u'£', u'\\prec':u'≺', | |
491 u'\\preccurlyeq':u'≼', u'\\preceq':u'≼', u'\\precsim':u'≾', | |
492 u'\\prime':u'′', u'\\prompto':u'∝', u'\\qquad':u' ', u'\\quad':u' ', | |
493 u'\\quarternote':u'♩', u'\\rangle':u'⟩', u'\\rbrace':u'}', | |
494 u'\\rbrace)':u'}', u'\\rbrack':u']', u'\\rceil':u'⌉', u'\\rfloor':u'⌋', | |
495 u'\\rhd':u'⊳', u'\\rightarrow)':u'→', u'\\rightarrowtail':u'↣', | |
496 u'\\rightarrowtobar':u'⇥', u'\\rightharpoondown':u'⇁', | |
497 u'\\rightharpoonup':u'⇀', u'\\rightharpooondown':u'⇁', | |
498 u'\\rightharpooonup':u'⇀', u'\\rightleftarrows':u'⇄', | |
499 u'\\rightleftharpoons':u'⇌', u'\\rightmoon':u'☽', | |
500 u'\\rightrightarrows':u'⇉', u'\\rightrightharpoons':u'⥤', | |
501 u'\\rightthreetimes':u'⋌', u'\\risingdotseq':u'≓', u'\\rtimes':u'⋊', | |
502 u'\\sagittarius':u'♐', u'\\saturn':u'♄', u'\\scorpio':u'♏', | |
503 u'\\searrow':u'↘', u'\\sec':u'sec', u'\\setminus':u'∖', u'\\sharp':u'♯', | |
504 u'\\simeq':u'≃', u'\\sin':u'sin', u'\\sinh':u'sinh', u'\\slash':u'∕', | |
505 u'\\smile':u'⌣', u'\\smiley':u'☺', u'\\spadesuit':u'♠', | |
506 u'\\sphericalangle':u'∢', u'\\sqcap':u'⊓', u'\\sqcup':u'⊔', | |
507 u'\\sqsubset':u'⊏', u'\\sqsubseteq':u'⊑', u'\\sqsupset':u'⊐', | |
508 u'\\sqsupseteq':u'⊒', u'\\square':u'□', u'\\star':u'⋆', | |
509 u'\\subseteqq':u'⫅', u'\\subsetneqq':u'⫋', u'\\succ':u'≻', | |
510 u'\\succcurlyeq':u'≽', u'\\succeq':u'≽', u'\\succnsim':u'⋩', | |
511 u'\\succsim':u'≿', u'\\sun':u'☼', u'\\sup':u'sup', u'\\supseteqq':u'⫆', | |
512 u'\\supsetneqq':u'⫌', u'\\surd':u'√', u'\\swarrow':u'↙', u'\\tan':u'tan', | |
513 u'\\tanh':u'tanh', u'\\taurus':u'♉', u'\\textasciicircum':u'^', | |
514 u'\\textasciitilde':u'~', u'\\textbackslash':u'\\', | |
515 u'\\textcopyright':u'©\'', u'\\textdegree':u'°', u'\\textellipsis':u'…', | |
516 u'\\textemdash':u'—', u'\\textendash':u'—', u'\\texteuro':u'€', | |
517 u'\\textgreater':u'>', u'\\textless':u'<', u'\\textordfeminine':u'ª', | |
518 u'\\textordmasculine':u'º', u'\\textquotedblleft':u'“', | |
519 u'\\textquotedblright':u'”', u'\\textquoteright':u'’', | |
520 u'\\textregistered':u'®', u'\\textrightarrow':u'→', | |
521 u'\\textsection':u'§', u'\\texttrademark':u'™', | |
522 u'\\texttwosuperior':u'²', u'\\textvisiblespace':u' ', | |
523 u'\\therefore':u'∴', u'\\top':u'⊤', u'\\triangle':u'△', | |
524 u'\\triangleleft':u'⊲', u'\\trianglelefteq':u'⊴', u'\\triangleq':u'≜', | |
525 u'\\triangleright':u'▷', u'\\trianglerighteq':u'⊵', | |
526 u'\\twoheadleftarrow':u'↞', u'\\twoheadrightarrow':u'↠', | |
527 u'\\twonotes':u'♫', u'\\udot':u'⊍', u'\\unlhd':u'⊴', u'\\unrhd':u'⊵', | |
528 u'\\unrhl':u'⊵', u'\\uparrow':u'↑', u'\\updownarrow':u'↕', | |
529 u'\\upharpoonleft':u'↿', u'\\upharpoonright':u'↾', u'\\uplus':u'⊎', | |
530 u'\\upuparrows':u'⇈', u'\\uranus':u'♅', u'\\vDash':u'⊨', | |
531 u'\\varclubsuit':u'♧', u'\\vardiamondsuit':u'♦', u'\\varheartsuit':u'♥', | |
532 u'\\varnothing':u'∅', u'\\varspadesuit':u'♤', u'\\vdash':u'⊢', | |
533 u'\\vdots':u'⋮', u'\\vee':u'∨', u'\\vee)':u'∨', u'\\veebar':u'⊻', | |
534 u'\\vert':u'∣', u'\\virgo':u'♍', u'\\wedge':u'∧', u'\\wedge)':u'∧', | |
535 u'\\wp':u'℘', u'\\wr':u'≀', u'\\yen':u'¥', u'\\{':u'{', u'\\|':u'∥', | |
536 u'\\}':u'}', | |
537 } | |
538 | |
539 decoratedcommand = { | |
540 | |
541 } | |
542 | |
543 decoratingfunctions = { | |
544 u'\\overleftarrow':u'⟵', u'\\overrightarrow':u'⟶', u'\\widehat':u'^', | |
545 } | |
546 | |
547 endings = { | |
548 u'bracket':u'}', u'complex':u'\\]', u'endafter':u'}', | |
549 u'endbefore':u'\\end{', u'squarebracket':u']', | |
550 } | |
551 | |
552 environments = { | |
553 u'align':[u'r',u'l',], u'eqnarray':[u'r',u'c',u'l',], | |
554 u'gathered':[u'l',u'l',], | |
555 } | |
556 | |
557 fontfunctions = { | |
558 u'\\boldsymbol':u'b', u'\\mathbb':u'span class="blackboard"', | |
559 u'\\mathbb{A}':u'𝔸', u'\\mathbb{B}':u'𝔹', u'\\mathbb{C}':u'ℂ', | |
560 u'\\mathbb{D}':u'𝔻', u'\\mathbb{E}':u'𝔼', u'\\mathbb{F}':u'𝔽', | |
561 u'\\mathbb{G}':u'𝔾', u'\\mathbb{H}':u'ℍ', u'\\mathbb{J}':u'𝕁', | |
562 u'\\mathbb{K}':u'𝕂', u'\\mathbb{L}':u'𝕃', u'\\mathbb{N}':u'ℕ', | |
563 u'\\mathbb{O}':u'𝕆', u'\\mathbb{P}':u'ℙ', u'\\mathbb{Q}':u'ℚ', | |
564 u'\\mathbb{R}':u'ℝ', u'\\mathbb{S}':u'𝕊', u'\\mathbb{T}':u'𝕋', | |
565 u'\\mathbb{W}':u'𝕎', u'\\mathbb{Z}':u'ℤ', u'\\mathbf':u'b', | |
566 u'\\mathcal':u'span class="scriptfont"', u'\\mathcal{B}':u'ℬ', | |
567 u'\\mathcal{E}':u'ℰ', u'\\mathcal{F}':u'ℱ', u'\\mathcal{H}':u'ℋ', | |
568 u'\\mathcal{I}':u'ℐ', u'\\mathcal{L}':u'ℒ', u'\\mathcal{M}':u'ℳ', | |
569 u'\\mathcal{R}':u'ℛ', u'\\mathfrak':u'span class="fraktur"', | |
570 u'\\mathfrak{C}':u'ℭ', u'\\mathfrak{F}':u'𝔉', u'\\mathfrak{H}':u'ℌ', | |
571 u'\\mathfrak{I}':u'ℑ', u'\\mathfrak{R}':u'ℜ', u'\\mathfrak{Z}':u'ℨ', | |
572 u'\\mathit':u'i', u'\\mathring{A}':u'Å', u'\\mathring{U}':u'Ů', | |
573 u'\\mathring{a}':u'å', u'\\mathring{u}':u'ů', u'\\mathring{w}':u'ẘ', | |
574 u'\\mathring{y}':u'ẙ', u'\\mathrm':u'span class="mathrm"', | |
575 u'\\mathscr':u'span class="scriptfont"', u'\\mathscr{B}':u'ℬ', | |
576 u'\\mathscr{E}':u'ℰ', u'\\mathscr{F}':u'ℱ', u'\\mathscr{H}':u'ℋ', | |
577 u'\\mathscr{I}':u'ℐ', u'\\mathscr{L}':u'ℒ', u'\\mathscr{M}':u'ℳ', | |
578 u'\\mathscr{R}':u'ℛ', u'\\mathsf':u'span class="mathsf"', | |
579 u'\\mathtt':u'tt', | |
580 } | |
581 | |
582 hybridfunctions = { | |
583 | |
584 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"',], | |
585 u'\\boxed':[u'{$1}',u'f0{$1}',u'span class="boxed"',], | |
586 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"',], | |
587 u'\\color':[u'{$p!}{$1}',u'f0{$1}',u'span style="color: $p;"',], | |
588 u'\\colorbox':[u'{$p!}{$1}',u'f0{$1}',u'span class="colorbox" style="background: $p;"',], | |
589 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"',], | |
590 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"',], | |
591 u'\\displaystyle':[u'{$1}',u'f0{$1}',u'span class="displaystyle"',], | |
592 u'\\fbox':[u'{$1}',u'f0{$1}',u'span class="fbox"',], | |
593 u'\\fboxrule':[u'{$p!}',u'f0{}',u'ignored',], | |
594 u'\\fboxsep':[u'{$p!}',u'f0{}',u'ignored',], | |
595 u'\\fcolorbox':[u'{$p!}{$q!}{$1}',u'f0{$1}',u'span class="boxed" style="border-color: $p; background: $q;"',], | |
596 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"',], | |
597 u'\\framebox':[u'[$p!][$q!]{$1}',u'f0{$1}',u'span class="framebox align-$q" style="width: $p;"',], | |
598 u'\\href':[u'[$o]{$u!}{$t!}',u'f0{$t}',u'a href="$u"',], | |
599 u'\\hspace':[u'{$p!}',u'f0{ }',u'span class="hspace" style="width: $p;"',], | |
600 u'\\leftroot':[u'{$p!}',u'f0{ }',u'span class="leftroot" style="width: $p;px"',], | |
601 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"',], | |
602 u'\\parbox':[u'[$p!]{$w!}{$1}',u'f0{1}',u'div class="Boxed" style="width: $w;"',], | |
603 u'\\raisebox':[u'{$p!}{$1}',u'f0{$1.font}',u'span class="raisebox" style="vertical-align: $p;"',], | |
604 u'\\renewenvironment':[u'{$1!}{$2!}{$3!}',u'',], | |
605 u'\\rule':[u'[$v!]{$w!}{$h!}',u'f0/',u'hr class="line" style="width: $w; height: $h;"',], | |
606 u'\\scriptscriptstyle':[u'{$1}',u'f0{$1}',u'span class="scriptscriptstyle"',], | |
607 u'\\scriptstyle':[u'{$1}',u'f0{$1}',u'span class="scriptstyle"',], | |
608 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"',], | |
609 u'\\stackrel':[u'{$1}{$2}',u'f0{f1{$1}f2{$2}}',u'span class="stackrel"',u'span class="upstackrel"',u'span class="downstackrel"',], | |
610 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"',], | |
611 u'\\textcolor':[u'{$p!}{$1}',u'f0{$1}',u'span style="color: $p;"',], | |
612 u'\\textstyle':[u'{$1}',u'f0{$1}',u'span class="textstyle"',], | |
613 u'\\unit':[u'[$0]{$1}',u'$0f0{$1.font}',u'span class="unit"',], | |
614 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"',], | |
615 u'\\uproot':[u'{$p!}',u'f0{ }',u'span class="uproot" style="width: $p;px"',], | |
616 u'\\url':[u'{$u!}',u'f0{$u}',u'a href="$u"',], | |
617 u'\\vspace':[u'{$p!}',u'f0{ }',u'span class="vspace" style="height: $p;"',], | |
618 } | |
619 | |
620 hybridsizes = { | |
621 u'\\binom':u'$1+$2', u'\\cfrac':u'$1+$2', u'\\dbinom':u'$1+$2+1', | |
622 u'\\dfrac':u'$1+$2', u'\\frac':u'$1+$2', u'\\tbinom':u'$1+$2+1', | |
623 } | |
624 | |
625 labelfunctions = { | |
626 u'\\label':u'a name="#"', | |
627 } | |
628 | |
629 limitcommands = { | |
630 u'\\int':u'∫', u'\\intop':u'∫', u'\\lim':u'lim', u'\\prod':u'∏', | |
631 u'\\smallint':u'∫', u'\\sum':u'∑', | |
632 } | |
633 # TODO: setting for simple enlarged vs. piecewise symbols | |
634 for key in (u'\\int', u'\\intop', u'\\prod', u'\\sum'): | |
635 limitcommands[key] = '<span class="symbol">%s</span>' % limitcommands[key] | |
636 | |
637 misccommands = { | |
638 u'\\limits':u'LimitPreviousCommand', u'\\newcommand':u'MacroDefinition', | |
639 u'\\renewcommand':u'MacroDefinition', | |
640 u'\\setcounter':u'SetCounterFunction', u'\\tag':u'FormulaTag', | |
641 u'\\tag*':u'FormulaTag', | |
642 } | |
643 | |
644 modified = { | |
645 u'\n':u'', u' ':u'', u'$':u'', u'&':u' ', u'\'':u'’', u'+':u' + ', | |
646 u',':u', ', u'-':u' − ', u'/':u' ⁄ ', u'<':u' < ', u'=':u' = ', | |
647 u'>':u' > ', u'@':u'', u'~':u'', | |
648 } | |
649 | |
650 onefunctions = { | |
651 u'\\Big':u'span class="bigsymbol"', u'\\Bigg':u'span class="hugesymbol"', | |
652 u'\\bar':u'span class="bar"', u'\\begin{array}':u'span class="arraydef"', | |
653 u'\\big':u'span class="symbol"', u'\\bigg':u'span class="largesymbol"', | |
654 u'\\bigl':u'span class="bigsymbol"', u'\\bigr':u'span class="bigsymbol"', | |
655 u'\\centering':u'span class="align-center"', | |
656 u'\\ensuremath':u'span class="ensuremath"', | |
657 u'\\hphantom':u'span class="phantom"', | |
658 u'\\noindent':u'span class="noindent"', | |
659 u'\\overbrace':u'span class="overbrace"', | |
660 u'\\overline':u'span class="overline"', | |
661 u'\\phantom':u'span class="phantom"', | |
662 u'\\underbrace':u'span class="underbrace"', u'\\underline':u'u', | |
663 u'\\vphantom':u'span class="phantom"', | |
664 } | |
665 | |
666 spacedcommands = { | |
667 u'\\Leftrightarrow':u'⇔', u'\\Rightarrow':u'⇒', u'\\approx':u'≈', | |
668 u'\\dashrightarrow':u'⇢', u'\\equiv':u'≡', u'\\ge':u'≥', u'\\geq':u'≥', | |
669 u'\\implies':u' ⇒ ', u'\\in':u'∈', u'\\le':u'≤', u'\\leftarrow':u'←', | |
670 u'\\leq':u'≤', u'\\ne':u'≠', u'\\neq':u'≠', u'\\not\\in':u'∉', | |
671 u'\\propto':u'∝', u'\\rightarrow':u'→', u'\\rightsquigarrow':u'⇝', | |
672 u'\\sim':u'~', u'\\subset':u'⊂', u'\\subseteq':u'⊆', u'\\supset':u'⊃', | |
673 u'\\supseteq':u'⊇', u'\\times':u'×', u'\\to':u'→', | |
674 } | |
675 | |
676 starts = { | |
677 u'beginafter':u'}', u'beginbefore':u'\\begin{', u'bracket':u'{', | |
678 u'command':u'\\', u'comment':u'%', u'complex':u'\\[', u'simple':u'$', | |
679 u'squarebracket':u'[', u'unnumbered':u'*', | |
680 } | |
681 | |
682 symbolfunctions = { | |
683 u'^':u'sup', u'_':u'sub', | |
684 } | |
685 | |
686 textfunctions = { | |
687 u'\\mbox':u'span class="mbox"', u'\\text':u'span class="text"', | |
688 u'\\textbf':u'b', u'\\textipa':u'span class="textipa"', u'\\textit':u'i', | |
689 u'\\textnormal':u'span class="textnormal"', | |
690 u'\\textrm':u'span class="textrm"', | |
691 u'\\textsc':u'span class="versalitas"', | |
692 u'\\textsf':u'span class="textsf"', u'\\textsl':u'i', u'\\texttt':u'tt', | |
693 u'\\textup':u'span class="normal"', | |
694 } | |
695 | |
696 unmodified = { | |
697 | |
698 u'characters':[u'.',u'*',u'€',u'(',u')',u'[',u']',u':',u'·',u'!',u';',u'|',u'§',u'"',], | |
699 } | |
700 | |
701 urls = { | |
702 u'googlecharts':u'http://chart.googleapis.com/chart?cht=tx&chl=', | |
703 } | |
704 | |
705 class GeneralConfig(object): | |
706 "Configuration class from elyxer.config file" | |
707 | |
708 version = { | |
709 u'date':u'2011-06-27', u'lyxformat':u'413', u'number':u'1.2.3', | |
710 } | |
711 | |
712 class HeaderConfig(object): | |
713 "Configuration class from elyxer.config file" | |
714 | |
715 parameters = { | |
716 u'beginpreamble':u'\\begin_preamble', u'branch':u'\\branch', | |
717 u'documentclass':u'\\textclass', u'endbranch':u'\\end_branch', | |
718 u'endpreamble':u'\\end_preamble', u'language':u'\\language', | |
719 u'lstset':u'\\lstset', u'outputchanges':u'\\output_changes', | |
720 u'paragraphseparation':u'\\paragraph_separation', | |
721 u'pdftitle':u'\\pdf_title', u'secnumdepth':u'\\secnumdepth', | |
722 u'tocdepth':u'\\tocdepth', | |
723 } | |
724 | |
725 styles = { | |
726 | |
727 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',], | |
728 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',], | |
729 } | |
730 | |
731 class ImageConfig(object): | |
732 "Configuration class from elyxer.config file" | |
733 | |
734 converters = { | |
735 | |
736 u'imagemagick':u'convert[ -density $scale][ -define $format:use-cropbox=true] "$input" "$output"', | |
737 u'inkscape':u'inkscape "$input" --export-png="$output"', | |
738 } | |
739 | |
740 cropboxformats = { | |
741 u'.eps':u'ps', u'.pdf':u'pdf', u'.ps':u'ps', | |
742 } | |
743 | |
744 formats = { | |
745 u'default':u'.png', u'vector':[u'.svg',u'.eps',], | |
746 } | |
747 | |
748 class LayoutConfig(object): | |
749 "Configuration class from elyxer.config file" | |
750 | |
751 groupable = { | |
752 | |
753 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',], | |
754 } | |
755 | |
756 class NewfangleConfig(object): | |
757 "Configuration class from elyxer.config file" | |
758 | |
759 constants = { | |
760 u'chunkref':u'chunkref{', u'endcommand':u'}', u'endmark':u'>', | |
761 u'startcommand':u'\\', u'startmark':u'=<', | |
762 } | |
763 | |
764 class NumberingConfig(object): | |
765 "Configuration class from elyxer.config file" | |
766 | |
767 layouts = { | |
768 | |
769 u'ordered':[u'Chapter',u'Section',u'Subsection',u'Subsubsection',u'Paragraph',], | |
770 u'roman':[u'Part',u'Book',], | |
771 } | |
772 | |
773 sequence = { | |
774 u'symbols':[u'*',u'**',u'†',u'‡',u'§',u'§§',u'¶',u'¶¶',u'#',u'##',], | |
775 } | |
776 | |
777 class StyleConfig(object): | |
778 "Configuration class from elyxer.config file" | |
779 | |
780 hspaces = { | |
781 u'\\enskip{}':u' ', u'\\hfill{}':u'<span class="hfill"> </span>', | |
782 u'\\hspace*{\\fill}':u' ', u'\\hspace*{}':u'', u'\\hspace{}':u' ', | |
783 u'\\negthinspace{}':u'', u'\\qquad{}':u' ', u'\\quad{}':u' ', | |
784 u'\\space{}':u' ', u'\\thinspace{}':u' ', u'~':u' ', | |
785 } | |
786 | |
787 quotes = { | |
788 u'ald':u'»', u'als':u'›', u'ard':u'«', u'ars':u'‹', u'eld':u'“', | |
789 u'els':u'‘', u'erd':u'”', u'ers':u'’', u'fld':u'«', | |
790 u'fls':u'‹', u'frd':u'»', u'frs':u'›', u'gld':u'„', u'gls':u'‚', | |
791 u'grd':u'“', u'grs':u'‘', u'pld':u'„', u'pls':u'‚', u'prd':u'”', | |
792 u'prs':u'’', u'sld':u'”', u'srd':u'”', | |
793 } | |
794 | |
795 referenceformats = { | |
796 u'eqref':u'(@↕)', u'formatted':u'¶↕', u'nameref':u'$↕', u'pageref':u'#↕', | |
797 u'ref':u'@↕', u'vpageref':u'on-page#↕', u'vref':u'@on-page#↕', | |
798 } | |
799 | |
800 size = { | |
801 u'ignoredtexts':[u'col',u'text',u'line',u'page',u'theight',u'pheight',], | |
802 } | |
803 | |
804 vspaces = { | |
805 u'bigskip':u'<div class="bigskip"> </div>', | |
806 u'defskip':u'<div class="defskip"> </div>', | |
807 u'medskip':u'<div class="medskip"> </div>', | |
808 u'smallskip':u'<div class="smallskip"> </div>', | |
809 u'vfill':u'<div class="vfill"> </div>', | |
810 } | |
811 | |
812 class TOCConfig(object): | |
813 "Configuration class from elyxer.config file" | |
814 | |
815 extractplain = { | |
816 | |
817 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',], | |
818 u'cloned':[u'',], u'extracted':[u'',], | |
819 } | |
820 | |
821 extracttitle = { | |
822 u'allowed':[u'StringContainer',u'Constant',u'Space',], | |
823 u'cloned':[u'TextFamily',u'EmphaticText',u'VersalitasText',u'BarredText',u'SizeText',u'ColorText',u'LangLine',u'Formula',], | |
824 u'extracted':[u'PlainLayout',u'TaggedText',u'Align',u'Caption',u'StandardLayout',u'FlexInset',], | |
825 } | |
826 | |
827 class TagConfig(object): | |
828 "Configuration class from elyxer.config file" | |
829 | |
830 barred = { | |
831 u'under':u'u', | |
832 } | |
833 | |
834 family = { | |
835 u'sans':u'span class="sans"', u'typewriter':u'tt', | |
836 } | |
837 | |
838 flex = { | |
839 u'CharStyle:Code':u'span class="code"', | |
840 u'CharStyle:MenuItem':u'span class="menuitem"', | |
841 u'Code':u'span class="code"', u'MenuItem':u'span class="menuitem"', | |
842 u'Noun':u'span class="noun"', u'Strong':u'span class="strong"', | |
843 } | |
844 | |
845 group = { | |
846 u'layouts':[u'Quotation',u'Quote',], | |
847 } | |
848 | |
849 layouts = { | |
850 u'Center':u'div', u'Chapter':u'h?', u'Date':u'h2', u'Paragraph':u'div', | |
851 u'Part':u'h1', u'Quotation':u'blockquote', u'Quote':u'blockquote', | |
852 u'Section':u'h?', u'Subsection':u'h?', u'Subsubsection':u'h?', | |
853 } | |
854 | |
855 listitems = { | |
856 u'Enumerate':u'ol', u'Itemize':u'ul', | |
857 } | |
858 | |
859 notes = { | |
860 u'Comment':u'', u'Greyedout':u'span class="greyedout"', u'Note':u'', | |
861 } | |
862 | |
863 shaped = { | |
864 u'italic':u'i', u'slanted':u'i', u'smallcaps':u'span class="versalitas"', | |
865 } | |
866 | |
867 class TranslationConfig(object): | |
868 "Configuration class from elyxer.config file" | |
869 | |
870 constants = { | |
871 u'Appendix':u'Appendix', u'Book':u'Book', u'Chapter':u'Chapter', | |
872 u'Paragraph':u'Paragraph', u'Part':u'Part', u'Section':u'Section', | |
873 u'Subsection':u'Subsection', u'Subsubsection':u'Subsubsection', | |
874 u'abstract':u'Abstract', u'bibliography':u'Bibliography', | |
875 u'figure':u'figure', u'float-algorithm':u'Algorithm ', | |
876 u'float-figure':u'Figure ', u'float-listing':u'Listing ', | |
877 u'float-table':u'Table ', u'float-tableau':u'Tableau ', | |
878 u'footnotes':u'Footnotes', u'generated-by':u'Document generated by ', | |
879 u'generated-on':u' on ', u'index':u'Index', | |
880 u'jsmath-enable':u'Please enable JavaScript on your browser.', | |
881 u'jsmath-requires':u' requires JavaScript to correctly process the mathematics on this page. ', | |
882 u'jsmath-warning':u'Warning: ', u'list-algorithm':u'List of Algorithms', | |
883 u'list-figure':u'List of Figures', u'list-table':u'List of Tables', | |
884 u'list-tableau':u'List of Tableaux', u'main-page':u'Main page', | |
885 u'next':u'Next', u'nomenclature':u'Nomenclature', | |
886 u'on-page':u' on page ', u'prev':u'Prev', u'references':u'References', | |
887 u'toc':u'Table of Contents', u'toc-for':u'Contents for ', u'up':u'Up', | |
888 } | |
889 | |
890 languages = { | |
891 u'american':u'en', u'british':u'en', u'deutsch':u'de', u'dutch':u'nl', | |
892 u'english':u'en', u'french':u'fr', u'ngerman':u'de', u'spanish':u'es', | |
893 } | |
894 | |
895 | |
896 | |
897 | |
898 | |
899 | |
900 class CommandLineParser(object): | |
901 "A parser for runtime options" | |
902 | |
903 def __init__(self, options): | |
904 self.options = options | |
905 | |
906 def parseoptions(self, args): | |
907 "Parse command line options" | |
908 if len(args) == 0: | |
909 return None | |
910 while len(args) > 0 and args[0].startswith('--'): | |
911 key, value = self.readoption(args) | |
912 if not key: | |
913 return 'Option ' + value + ' not recognized' | |
914 if not value: | |
915 return 'Option ' + key + ' needs a value' | |
916 setattr(self.options, key, value) | |
917 return None | |
918 | |
919 def readoption(self, args): | |
920 "Read the key and value for an option" | |
921 arg = args[0][2:] | |
922 del args[0] | |
923 if '=' in arg: | |
924 key = self.readequalskey(arg, args) | |
925 else: | |
926 key = arg.replace('-', '') | |
927 if not hasattr(self.options, key): | |
928 return None, key | |
929 current = getattr(self.options, key) | |
930 if isinstance(current, bool): | |
931 return key, True | |
932 # read value | |
933 if len(args) == 0: | |
934 return key, None | |
935 if args[0].startswith('"'): | |
936 initial = args[0] | |
937 del args[0] | |
938 return key, self.readquoted(args, initial) | |
939 value = args[0] | |
940 del args[0] | |
941 if isinstance(current, list): | |
942 current.append(value) | |
943 return key, current | |
944 return key, value | |
945 | |
946 def readquoted(self, args, initial): | |
947 "Read a value between quotes" | |
948 value = initial[1:] | |
949 while len(args) > 0 and not args[0].endswith('"') and not args[0].startswith('--'): | |
950 value += ' ' + args[0] | |
951 del args[0] | |
952 if len(args) == 0 or args[0].startswith('--'): | |
953 return None | |
954 value += ' ' + args[0:-1] | |
955 return value | |
956 | |
957 def readequalskey(self, arg, args): | |
958 "Read a key using equals" | |
959 split = arg.split('=', 1) | |
960 key = split[0] | |
961 value = split[1] | |
962 args.insert(0, value) | |
963 return key | |
964 | |
965 | |
966 | |
967 class Options(object): | |
968 "A set of runtime options" | |
969 | |
970 instance = None | |
971 | |
972 location = None | |
973 nocopy = False | |
974 copyright = False | |
975 debug = False | |
976 quiet = False | |
977 version = False | |
978 hardversion = False | |
979 versiondate = False | |
980 html = False | |
981 help = False | |
982 showlines = True | |
983 unicode = False | |
984 iso885915 = False | |
985 css = [] | |
986 title = None | |
987 directory = None | |
988 destdirectory = None | |
989 toc = False | |
990 toctarget = '' | |
991 tocfor = None | |
992 forceformat = None | |
993 lyxformat = False | |
994 target = None | |
995 splitpart = None | |
996 memory = True | |
997 lowmem = False | |
998 nobib = False | |
999 converter = 'imagemagick' | |
1000 raw = False | |
1001 jsmath = None | |
1002 mathjax = None | |
1003 nofooter = False | |
1004 simplemath = False | |
1005 template = None | |
1006 noconvert = False | |
1007 notoclabels = False | |
1008 letterfoot = True | |
1009 numberfoot = False | |
1010 symbolfoot = False | |
1011 hoverfoot = True | |
1012 marginfoot = False | |
1013 endfoot = False | |
1014 supfoot = True | |
1015 alignfoot = False | |
1016 footnotes = None | |
1017 imageformat = None | |
1018 copyimages = False | |
1019 googlecharts = False | |
1020 embedcss = [] | |
1021 | |
1022 branches = dict() | |
1023 | |
1024 def parseoptions(self, args): | |
1025 "Parse command line options" | |
1026 Options.location = args[0] | |
1027 del args[0] | |
1028 parser = CommandLineParser(Options) | |
1029 result = parser.parseoptions(args) | |
1030 if result: | |
1031 Trace.error(result) | |
1032 self.usage() | |
1033 self.processoptions() | |
1034 | |
1035 def processoptions(self): | |
1036 "Process all options parsed." | |
1037 if Options.help: | |
1038 self.usage() | |
1039 if Options.version: | |
1040 self.showversion() | |
1041 if Options.hardversion: | |
1042 self.showhardversion() | |
1043 if Options.versiondate: | |
1044 self.showversiondate() | |
1045 if Options.lyxformat: | |
1046 self.showlyxformat() | |
1047 if Options.splitpart: | |
1048 try: | |
1049 Options.splitpart = int(Options.splitpart) | |
1050 if Options.splitpart <= 0: | |
1051 Trace.error('--splitpart requires a number bigger than zero') | |
1052 self.usage() | |
1053 except: | |
1054 Trace.error('--splitpart needs a numeric argument, not ' + Options.splitpart) | |
1055 self.usage() | |
1056 if Options.lowmem or Options.toc or Options.tocfor: | |
1057 Options.memory = False | |
1058 self.parsefootnotes() | |
1059 if Options.forceformat and not Options.imageformat: | |
1060 Options.imageformat = Options.forceformat | |
1061 if Options.imageformat == 'copy': | |
1062 Options.copyimages = True | |
1063 if Options.css == []: | |
1064 Options.css = ['http://elyxer.nongnu.org/lyx.css'] | |
1065 if Options.html: | |
1066 Options.simplemath = True | |
1067 if Options.toc and not Options.tocfor: | |
1068 Trace.error('Option --toc is deprecated; use --tocfor "page" instead') | |
1069 Options.tocfor = Options.toctarget | |
1070 if Options.nocopy: | |
1071 Trace.error('Option --nocopy is deprecated; it is no longer needed') | |
1072 # set in Trace if necessary | |
1073 for param in dir(Trace): | |
1074 if param.endswith('mode'): | |
1075 setattr(Trace, param, getattr(self, param[:-4])) | |
1076 | |
1077 def usage(self): | |
1078 "Show correct usage" | |
1079 Trace.error('Usage: ' + os.path.basename(Options.location) + ' [options] [filein] [fileout]') | |
1080 Trace.error('Convert LyX input file "filein" to HTML file "fileout".') | |
1081 Trace.error('If filein (or fileout) is not given use standard input (or output).') | |
1082 Trace.error('Main program of the eLyXer package (http://elyxer.nongnu.org/).') | |
1083 self.showoptions() | |
1084 | |
1085 def parsefootnotes(self): | |
1086 "Parse footnotes options." | |
1087 if not Options.footnotes: | |
1088 return | |
1089 Options.marginfoot = False | |
1090 Options.letterfoot = False | |
1091 options = Options.footnotes.split(',') | |
1092 for option in options: | |
1093 footoption = option + 'foot' | |
1094 if hasattr(Options, footoption): | |
1095 setattr(Options, footoption, True) | |
1096 else: | |
1097 Trace.error('Unknown footnotes option: ' + option) | |
1098 if not Options.endfoot and not Options.marginfoot and not Options.hoverfoot: | |
1099 Options.hoverfoot = True | |
1100 if not Options.numberfoot and not Options.symbolfoot: | |
1101 Options.letterfoot = True | |
1102 | |
1103 def showoptions(self): | |
1104 "Show all possible options" | |
1105 Trace.error(' Common options:') | |
1106 Trace.error(' --help: show this online help') | |
1107 Trace.error(' --quiet: disables all runtime messages') | |
1108 Trace.error('') | |
1109 Trace.error(' Advanced options:') | |
1110 Trace.error(' --debug: enable debugging messages (for developers)') | |
1111 Trace.error(' --version: show version number and release date') | |
1112 Trace.error(' --lyxformat: return the highest LyX version supported') | |
1113 Trace.error(' Options for HTML output:') | |
1114 Trace.error(' --title "title": set the generated page title') | |
1115 Trace.error(' --css "file.css": use a custom CSS file') | |
1116 Trace.error(' --embedcss "file.css": embed styles from elyxer.a CSS file into the output') | |
1117 Trace.error(' --html: output HTML 4.0 instead of the default XHTML') | |
1118 Trace.error(' --unicode: full Unicode output') | |
1119 Trace.error(' --iso885915: output a document with ISO-8859-15 encoding') | |
1120 Trace.error(' --nofooter: remove the footer "generated by eLyXer"') | |
1121 Trace.error(' --simplemath: do not generate fancy math constructions') | |
1122 Trace.error(' Options for image output:') | |
1123 Trace.error(' --directory "img_dir": look for images in the specified directory') | |
1124 Trace.error(' --destdirectory "dest": put converted images into this directory') | |
1125 Trace.error(' --imageformat ".ext": image output format, or "copy" to copy images') | |
1126 Trace.error(' --noconvert: do not convert images, use in original locations') | |
1127 Trace.error(' --converter "inkscape": use an alternative program to convert images') | |
1128 Trace.error(' Options for footnote display:') | |
1129 Trace.error(' --numberfoot: mark footnotes with numbers instead of letters') | |
1130 Trace.error(' --symbolfoot: mark footnotes with symbols (*, **...)') | |
1131 Trace.error(' --hoverfoot: show footnotes as hovering text (default)') | |
1132 Trace.error(' --marginfoot: show footnotes on the page margin') | |
1133 Trace.error(' --endfoot: show footnotes at the end of the page') | |
1134 Trace.error(' --supfoot: use superscript for footnote markers (default)') | |
1135 Trace.error(' --alignfoot: use aligned text for footnote markers') | |
1136 Trace.error(' --footnotes "options": specify several comma-separated footnotes options') | |
1137 Trace.error(' Available options are: "number", "symbol", "hover", "margin", "end",') | |
1138 Trace.error(' "sup", "align"') | |
1139 Trace.error(' Advanced output options:') | |
1140 Trace.error(' --splitpart "depth": split the resulting webpage at the given depth') | |
1141 Trace.error(' --tocfor "page": generate a TOC that points to the given page') | |
1142 Trace.error(' --target "frame": make all links point to the given frame') | |
1143 Trace.error(' --notoclabels: omit the part labels in the TOC, such as Chapter') | |
1144 Trace.error(' --lowmem: do the conversion on the fly (conserve memory)') | |
1145 Trace.error(' --raw: generate HTML without header or footer.') | |
1146 Trace.error(' --jsmath "URL": use jsMath from elyxer.the given URL to display equations') | |
1147 Trace.error(' --mathjax "URL": use MathJax from elyxer.the given URL to display equations') | |
1148 Trace.error(' --googlecharts: use Google Charts to generate formula images') | |
1149 Trace.error(' --template "file": use a template, put everything in <!--$content-->') | |
1150 Trace.error(' --copyright: add a copyright notice at the bottom') | |
1151 Trace.error(' Deprecated options:') | |
1152 Trace.error(' --toc: (deprecated) create a table of contents') | |
1153 Trace.error(' --toctarget "page": (deprecated) generate a TOC for the given page') | |
1154 Trace.error(' --nocopy: (deprecated) maintained for backwards compatibility') | |
1155 sys.exit() | |
1156 | |
1157 def showversion(self): | |
1158 "Return the current eLyXer version string" | |
1159 string = 'eLyXer version ' + GeneralConfig.version['number'] | |
1160 string += ' (' + GeneralConfig.version['date'] + ')' | |
1161 Trace.error(string) | |
1162 sys.exit() | |
1163 | |
1164 def showhardversion(self): | |
1165 "Return just the version string" | |
1166 Trace.message(GeneralConfig.version['number']) | |
1167 sys.exit() | |
1168 | |
1169 def showversiondate(self): | |
1170 "Return just the version dte" | |
1171 Trace.message(GeneralConfig.version['date']) | |
1172 sys.exit() | |
1173 | |
1174 def showlyxformat(self): | |
1175 "Return just the lyxformat parameter" | |
1176 Trace.message(GeneralConfig.version['lyxformat']) | |
1177 sys.exit() | |
1178 | |
1179 class BranchOptions(object): | |
1180 "A set of options for a branch" | |
1181 | |
1182 def __init__(self, name): | |
1183 self.name = name | |
1184 self.options = {'color':'#ffffff'} | |
1185 | |
1186 def set(self, key, value): | |
1187 "Set a branch option" | |
1188 if not key.startswith(ContainerConfig.string['startcommand']): | |
1189 Trace.error('Invalid branch option ' + key) | |
1190 return | |
1191 key = key.replace(ContainerConfig.string['startcommand'], '') | |
1192 self.options[key] = value | |
1193 | |
1194 def isselected(self): | |
1195 "Return if the branch is selected" | |
1196 if not 'selected' in self.options: | |
1197 return False | |
1198 return self.options['selected'] == '1' | |
1199 | |
1200 def __unicode__(self): | |
1201 "String representation" | |
1202 return 'options for ' + self.name + ': ' + unicode(self.options) | |
1203 | |
1204 | |
1205 | |
1206 | |
1207 import urllib | |
1208 | |
1209 | |
1210 | |
1211 | |
1212 | |
1213 | |
1214 | |
1215 | |
1216 class Cloner(object): | |
1217 "An object used to clone other objects." | |
1218 | |
1219 def clone(cls, original): | |
1220 "Return an exact copy of an object." | |
1221 "The original object must have an empty constructor." | |
1222 return cls.create(original.__class__) | |
1223 | |
1224 def create(cls, type): | |
1225 "Create an object of a given class." | |
1226 clone = type.__new__(type) | |
1227 clone.__init__() | |
1228 return clone | |
1229 | |
1230 clone = classmethod(clone) | |
1231 create = classmethod(create) | |
1232 | |
1233 class ContainerExtractor(object): | |
1234 "A class to extract certain containers." | |
1235 | |
1236 def __init__(self, config): | |
1237 "The config parameter is a map containing three lists: allowed, copied and extracted." | |
1238 "Each of the three is a list of class names for containers." | |
1239 "Allowed containers are included as is into the result." | |
1240 "Cloned containers are cloned and placed into the result." | |
1241 "Extracted containers are looked into." | |
1242 "All other containers are silently ignored." | |
1243 self.allowed = config['allowed'] | |
1244 self.cloned = config['cloned'] | |
1245 self.extracted = config['extracted'] | |
1246 | |
1247 def extract(self, container): | |
1248 "Extract a group of selected containers from elyxer.a container." | |
1249 list = [] | |
1250 locate = lambda c: c.__class__.__name__ in self.allowed + self.cloned | |
1251 recursive = lambda c: c.__class__.__name__ in self.extracted | |
1252 process = lambda c: self.process(c, list) | |
1253 container.recursivesearch(locate, recursive, process) | |
1254 return list | |
1255 | |
1256 def process(self, container, list): | |
1257 "Add allowed containers, clone cloned containers and add the clone." | |
1258 name = container.__class__.__name__ | |
1259 if name in self.allowed: | |
1260 list.append(container) | |
1261 elif name in self.cloned: | |
1262 list.append(self.safeclone(container)) | |
1263 else: | |
1264 Trace.error('Unknown container class ' + name) | |
1265 | |
1266 def safeclone(self, container): | |
1267 "Return a new container with contents only in a safe list, recursively." | |
1268 clone = Cloner.clone(container) | |
1269 clone.output = container.output | |
1270 clone.contents = self.extract(container) | |
1271 return clone | |
1272 | |
1273 | |
1274 | |
1275 | |
1276 | |
1277 | |
1278 class Parser(object): | |
1279 "A generic parser" | |
1280 | |
1281 def __init__(self): | |
1282 self.begin = 0 | |
1283 self.parameters = dict() | |
1284 | |
1285 def parseheader(self, reader): | |
1286 "Parse the header" | |
1287 header = reader.currentline().split() | |
1288 reader.nextline() | |
1289 self.begin = reader.linenumber | |
1290 return header | |
1291 | |
1292 def parseparameter(self, reader): | |
1293 "Parse a parameter" | |
1294 if reader.currentline().strip().startswith('<'): | |
1295 key, value = self.parsexml(reader) | |
1296 self.parameters[key] = value | |
1297 return | |
1298 split = reader.currentline().strip().split(' ', 1) | |
1299 reader.nextline() | |
1300 if len(split) == 0: | |
1301 return | |
1302 key = split[0] | |
1303 if len(split) == 1: | |
1304 self.parameters[key] = True | |
1305 return | |
1306 if not '"' in split[1]: | |
1307 self.parameters[key] = split[1].strip() | |
1308 return | |
1309 doublesplit = split[1].split('"') | |
1310 self.parameters[key] = doublesplit[1] | |
1311 | |
1312 def parsexml(self, reader): | |
1313 "Parse a parameter in xml form: <param attr1=value...>" | |
1314 strip = reader.currentline().strip() | |
1315 reader.nextline() | |
1316 if not strip.endswith('>'): | |
1317 Trace.error('XML parameter ' + strip + ' should be <...>') | |
1318 split = strip[1:-1].split() | |
1319 if len(split) == 0: | |
1320 Trace.error('Empty XML parameter <>') | |
1321 return None, None | |
1322 key = split[0] | |
1323 del split[0] | |
1324 if len(split) == 0: | |
1325 return key, dict() | |
1326 attrs = dict() | |
1327 for attr in split: | |
1328 if not '=' in attr: | |
1329 Trace.error('Erroneous attribute for ' + key + ': ' + attr) | |
1330 attr += '="0"' | |
1331 parts = attr.split('=') | |
1332 attrkey = parts[0] | |
1333 value = parts[1].split('"')[1] | |
1334 attrs[attrkey] = value | |
1335 return key, attrs | |
1336 | |
1337 def parseending(self, reader, process): | |
1338 "Parse until the current ending is found" | |
1339 if not self.ending: | |
1340 Trace.error('No ending for ' + unicode(self)) | |
1341 return | |
1342 while not reader.currentline().startswith(self.ending): | |
1343 process() | |
1344 | |
1345 def parsecontainer(self, reader, contents): | |
1346 container = self.factory.createcontainer(reader) | |
1347 if container: | |
1348 container.parent = self.parent | |
1349 contents.append(container) | |
1350 | |
1351 def __unicode__(self): | |
1352 "Return a description" | |
1353 return self.__class__.__name__ + ' (' + unicode(self.begin) + ')' | |
1354 | |
1355 class LoneCommand(Parser): | |
1356 "A parser for just one command line" | |
1357 | |
1358 def parse(self,reader): | |
1359 "Read nothing" | |
1360 return [] | |
1361 | |
1362 class TextParser(Parser): | |
1363 "A parser for a command and a bit of text" | |
1364 | |
1365 stack = [] | |
1366 | |
1367 def __init__(self, container): | |
1368 Parser.__init__(self) | |
1369 self.ending = None | |
1370 if container.__class__.__name__ in ContainerConfig.endings: | |
1371 self.ending = ContainerConfig.endings[container.__class__.__name__] | |
1372 self.endings = [] | |
1373 | |
1374 def parse(self, reader): | |
1375 "Parse lines as long as they are text" | |
1376 TextParser.stack.append(self.ending) | |
1377 self.endings = TextParser.stack + [ContainerConfig.endings['Layout'], | |
1378 ContainerConfig.endings['Inset'], self.ending] | |
1379 contents = [] | |
1380 while not self.isending(reader): | |
1381 self.parsecontainer(reader, contents) | |
1382 return contents | |
1383 | |
1384 def isending(self, reader): | |
1385 "Check if text is ending" | |
1386 current = reader.currentline().split() | |
1387 if len(current) == 0: | |
1388 return False | |
1389 if current[0] in self.endings: | |
1390 if current[0] in TextParser.stack: | |
1391 TextParser.stack.remove(current[0]) | |
1392 else: | |
1393 TextParser.stack = [] | |
1394 return True | |
1395 return False | |
1396 | |
1397 class ExcludingParser(Parser): | |
1398 "A parser that excludes the final line" | |
1399 | |
1400 def parse(self, reader): | |
1401 "Parse everything up to (and excluding) the final line" | |
1402 contents = [] | |
1403 self.parseending(reader, lambda: self.parsecontainer(reader, contents)) | |
1404 return contents | |
1405 | |
1406 class BoundedParser(ExcludingParser): | |
1407 "A parser bound by a final line" | |
1408 | |
1409 def parse(self, reader): | |
1410 "Parse everything, including the final line" | |
1411 contents = ExcludingParser.parse(self, reader) | |
1412 # skip last line | |
1413 reader.nextline() | |
1414 return contents | |
1415 | |
1416 class BoundedDummy(Parser): | |
1417 "A bound parser that ignores everything" | |
1418 | |
1419 def parse(self, reader): | |
1420 "Parse the contents of the container" | |
1421 self.parseending(reader, lambda: reader.nextline()) | |
1422 # skip last line | |
1423 reader.nextline() | |
1424 return [] | |
1425 | |
1426 class StringParser(Parser): | |
1427 "Parses just a string" | |
1428 | |
1429 def parseheader(self, reader): | |
1430 "Do nothing, just take note" | |
1431 self.begin = reader.linenumber + 1 | |
1432 return [] | |
1433 | |
1434 def parse(self, reader): | |
1435 "Parse a single line" | |
1436 contents = reader.currentline() | |
1437 reader.nextline() | |
1438 return contents | |
1439 | |
1440 class InsetParser(BoundedParser): | |
1441 "Parses a LyX inset" | |
1442 | |
1443 def parse(self, reader): | |
1444 "Parse inset parameters into a dictionary" | |
1445 startcommand = ContainerConfig.string['startcommand'] | |
1446 while reader.currentline() != '' and not reader.currentline().startswith(startcommand): | |
1447 self.parseparameter(reader) | |
1448 return BoundedParser.parse(self, reader) | |
1449 | |
1450 | |
1451 | |
1452 | |
1453 | |
1454 | |
1455 class ContainerOutput(object): | |
1456 "The generic HTML output for a container." | |
1457 | |
1458 def gethtml(self, container): | |
1459 "Show an error." | |
1460 Trace.error('gethtml() not implemented for ' + unicode(self)) | |
1461 | |
1462 def isempty(self): | |
1463 "Decide if the output is empty: by default, not empty." | |
1464 return False | |
1465 | |
1466 class EmptyOutput(ContainerOutput): | |
1467 | |
1468 def gethtml(self, container): | |
1469 "Return empty HTML code." | |
1470 return [] | |
1471 | |
1472 def isempty(self): | |
1473 "This output is particularly empty." | |
1474 return True | |
1475 | |
1476 class FixedOutput(ContainerOutput): | |
1477 "Fixed output" | |
1478 | |
1479 def gethtml(self, container): | |
1480 "Return constant HTML code" | |
1481 return container.html | |
1482 | |
1483 class ContentsOutput(ContainerOutput): | |
1484 "Outputs the contents converted to HTML" | |
1485 | |
1486 def gethtml(self, container): | |
1487 "Return the HTML code" | |
1488 html = [] | |
1489 if container.contents == None: | |
1490 return html | |
1491 for element in container.contents: | |
1492 if not hasattr(element, 'gethtml'): | |
1493 Trace.error('No html in ' + element.__class__.__name__ + ': ' + unicode(element)) | |
1494 return html | |
1495 html += element.gethtml() | |
1496 return html | |
1497 | |
1498 class TaggedOutput(ContentsOutput): | |
1499 "Outputs an HTML tag surrounding the contents." | |
1500 | |
1501 tag = None | |
1502 breaklines = False | |
1503 empty = False | |
1504 | |
1505 def settag(self, tag, breaklines=False, empty=False): | |
1506 "Set the value for the tag and other attributes." | |
1507 self.tag = tag | |
1508 if breaklines: | |
1509 self.breaklines = breaklines | |
1510 if empty: | |
1511 self.empty = empty | |
1512 return self | |
1513 | |
1514 def setbreaklines(self, breaklines): | |
1515 "Set the value for breaklines." | |
1516 self.breaklines = breaklines | |
1517 return self | |
1518 | |
1519 def gethtml(self, container): | |
1520 "Return the HTML code." | |
1521 if self.empty: | |
1522 return [self.selfclosing(container)] | |
1523 html = [self.open(container)] | |
1524 html += ContentsOutput.gethtml(self, container) | |
1525 html.append(self.close(container)) | |
1526 return html | |
1527 | |
1528 def open(self, container): | |
1529 "Get opening line." | |
1530 if not self.checktag(): | |
1531 return '' | |
1532 open = '<' + self.tag + '>' | |
1533 if self.breaklines: | |
1534 return open + '\n' | |
1535 return open | |
1536 | |
1537 def close(self, container): | |
1538 "Get closing line." | |
1539 if not self.checktag(): | |
1540 return '' | |
1541 close = '</' + self.tag.split()[0] + '>' | |
1542 if self.breaklines: | |
1543 return '\n' + close + '\n' | |
1544 return close | |
1545 | |
1546 def selfclosing(self, container): | |
1547 "Get self-closing line." | |
1548 if not self.checktag(): | |
1549 return '' | |
1550 selfclosing = '<' + self.tag + '/>' | |
1551 if self.breaklines: | |
1552 return selfclosing + '\n' | |
1553 return selfclosing | |
1554 | |
1555 def checktag(self): | |
1556 "Check that the tag is valid." | |
1557 if not self.tag: | |
1558 Trace.error('No tag in ' + unicode(container)) | |
1559 return False | |
1560 if self.tag == '': | |
1561 return False | |
1562 return True | |
1563 | |
1564 class FilteredOutput(ContentsOutput): | |
1565 "Returns the output in the contents, but filtered:" | |
1566 "some strings are replaced by others." | |
1567 | |
1568 def __init__(self): | |
1569 "Initialize the filters." | |
1570 self.filters = [] | |
1571 | |
1572 def addfilter(self, original, replacement): | |
1573 "Add a new filter: replace the original by the replacement." | |
1574 self.filters.append((original, replacement)) | |
1575 | |
1576 def gethtml(self, container): | |
1577 "Return the HTML code" | |
1578 result = [] | |
1579 html = ContentsOutput.gethtml(self, container) | |
1580 for line in html: | |
1581 result.append(self.filter(line)) | |
1582 return result | |
1583 | |
1584 def filter(self, line): | |
1585 "Filter a single line with all available filters." | |
1586 for original, replacement in self.filters: | |
1587 if original in line: | |
1588 line = line.replace(original, replacement) | |
1589 return line | |
1590 | |
1591 class StringOutput(ContainerOutput): | |
1592 "Returns a bare string as output" | |
1593 | |
1594 def gethtml(self, container): | |
1595 "Return a bare string" | |
1596 return [container.string] | |
1597 | |
1598 | |
1599 | |
1600 | |
1601 | |
1602 | |
1603 | |
1604 import sys | |
1605 import codecs | |
1606 | |
1607 | |
1608 class LineReader(object): | |
1609 "Reads a file line by line" | |
1610 | |
1611 def __init__(self, filename): | |
1612 if isinstance(filename, file): | |
1613 self.file = filename | |
1614 else: | |
1615 self.file = codecs.open(filename, 'rU', 'utf-8') | |
1616 self.linenumber = 1 | |
1617 self.lastline = None | |
1618 self.current = None | |
1619 self.mustread = True | |
1620 self.depleted = False | |
1621 try: | |
1622 self.readline() | |
1623 except UnicodeDecodeError: | |
1624 # try compressed file | |
1625 import gzip | |
1626 self.file = gzip.open(filename, 'rb') | |
1627 self.readline() | |
1628 | |
1629 def setstart(self, firstline): | |
1630 "Set the first line to read." | |
1631 for i in range(firstline): | |
1632 self.file.readline() | |
1633 self.linenumber = firstline | |
1634 | |
1635 def setend(self, lastline): | |
1636 "Set the last line to read." | |
1637 self.lastline = lastline | |
1638 | |
1639 def currentline(self): | |
1640 "Get the current line" | |
1641 if self.mustread: | |
1642 self.readline() | |
1643 return self.current | |
1644 | |
1645 def nextline(self): | |
1646 "Go to next line" | |
1647 if self.depleted: | |
1648 Trace.fatal('Read beyond file end') | |
1649 self.mustread = True | |
1650 | |
1651 def readline(self): | |
1652 "Read a line from elyxer.file" | |
1653 self.current = self.file.readline() | |
1654 if not isinstance(self.file, codecs.StreamReaderWriter): | |
1655 self.current = self.current.decode('utf-8') | |
1656 if len(self.current) == 0: | |
1657 self.depleted = True | |
1658 self.current = self.current.rstrip('\n\r') | |
1659 self.linenumber += 1 | |
1660 self.mustread = False | |
1661 Trace.prefix = 'Line ' + unicode(self.linenumber) + ': ' | |
1662 if self.linenumber % 1000 == 0: | |
1663 Trace.message('Parsing') | |
1664 | |
1665 def finished(self): | |
1666 "Find out if the file is finished" | |
1667 if self.lastline and self.linenumber == self.lastline: | |
1668 return True | |
1669 if self.mustread: | |
1670 self.readline() | |
1671 return self.depleted | |
1672 | |
1673 def close(self): | |
1674 self.file.close() | |
1675 | |
1676 class LineWriter(object): | |
1677 "Writes a file as a series of lists" | |
1678 | |
1679 file = False | |
1680 | |
1681 def __init__(self, filename): | |
1682 if isinstance(filename, file): | |
1683 self.file = filename | |
1684 self.filename = None | |
1685 else: | |
1686 self.filename = filename | |
1687 | |
1688 def write(self, strings): | |
1689 "Write a list of strings" | |
1690 for string in strings: | |
1691 if not isinstance(string, basestring): | |
1692 Trace.error('Not a string: ' + unicode(string) + ' in ' + unicode(strings)) | |
1693 return | |
1694 self.writestring(string) | |
1695 | |
1696 def writestring(self, string): | |
1697 "Write a string" | |
1698 if not self.file: | |
1699 self.file = codecs.open(self.filename, 'w', "utf-8") | |
1700 if self.file == sys.stdout and sys.version_info < (3,0): | |
1701 string = string.encode('utf-8') | |
1702 self.file.write(string) | |
1703 | |
1704 def writeline(self, line): | |
1705 "Write a line to file" | |
1706 self.writestring(line + '\n') | |
1707 | |
1708 def close(self): | |
1709 self.file.close() | |
1710 | |
1711 | |
1712 | |
1713 | |
1714 | |
1715 | |
1716 class Globable(object): | |
1717 """A bit of text which can be globbed (lumped together in bits). | |
1718 Methods current(), skipcurrent(), checkfor() and isout() have to be | |
1719 implemented by subclasses.""" | |
1720 | |
1721 leavepending = False | |
1722 | |
1723 def __init__(self): | |
1724 self.endinglist = EndingList() | |
1725 | |
1726 def checkbytemark(self): | |
1727 "Check for a Unicode byte mark and skip it." | |
1728 if self.finished(): | |
1729 return | |
1730 if ord(self.current()) == 0xfeff: | |
1731 self.skipcurrent() | |
1732 | |
1733 def isout(self): | |
1734 "Find out if we are out of the position yet." | |
1735 Trace.error('Unimplemented isout()') | |
1736 return True | |
1737 | |
1738 def current(self): | |
1739 "Return the current character." | |
1740 Trace.error('Unimplemented current()') | |
1741 return '' | |
1742 | |
1743 def checkfor(self, string): | |
1744 "Check for the given string in the current position." | |
1745 Trace.error('Unimplemented checkfor()') | |
1746 return False | |
1747 | |
1748 def finished(self): | |
1749 "Find out if the current text has finished." | |
1750 if self.isout(): | |
1751 if not self.leavepending: | |
1752 self.endinglist.checkpending() | |
1753 return True | |
1754 return self.endinglist.checkin(self) | |
1755 | |
1756 def skipcurrent(self): | |
1757 "Return the current character and skip it." | |
1758 Trace.error('Unimplemented skipcurrent()') | |
1759 return '' | |
1760 | |
1761 def glob(self, currentcheck): | |
1762 "Glob a bit of text that satisfies a check on the current char." | |
1763 glob = '' | |
1764 while not self.finished() and currentcheck(): | |
1765 glob += self.skipcurrent() | |
1766 return glob | |
1767 | |
1768 def globalpha(self): | |
1769 "Glob a bit of alpha text" | |
1770 return self.glob(lambda: self.current().isalpha()) | |
1771 | |
1772 def globnumber(self): | |
1773 "Glob a row of digits." | |
1774 return self.glob(lambda: self.current().isdigit()) | |
1775 | |
1776 def isidentifier(self): | |
1777 "Return if the current character is alphanumeric or _." | |
1778 if self.current().isalnum() or self.current() == '_': | |
1779 return True | |
1780 return False | |
1781 | |
1782 def globidentifier(self): | |
1783 "Glob alphanumeric and _ symbols." | |
1784 return self.glob(self.isidentifier) | |
1785 | |
1786 def isvalue(self): | |
1787 "Return if the current character is a value character:" | |
1788 "not a bracket or a space." | |
1789 if self.current().isspace(): | |
1790 return False | |
1791 if self.current() in '{}()': | |
1792 return False | |
1793 return True | |
1794 | |
1795 def globvalue(self): | |
1796 "Glob a value: any symbols but brackets." | |
1797 return self.glob(self.isvalue) | |
1798 | |
1799 def skipspace(self): | |
1800 "Skip all whitespace at current position." | |
1801 return self.glob(lambda: self.current().isspace()) | |
1802 | |
1803 def globincluding(self, magicchar): | |
1804 "Glob a bit of text up to (including) the magic char." | |
1805 glob = self.glob(lambda: self.current() != magicchar) + magicchar | |
1806 self.skip(magicchar) | |
1807 return glob | |
1808 | |
1809 def globexcluding(self, excluded): | |
1810 "Glob a bit of text up until (excluding) any excluded character." | |
1811 return self.glob(lambda: self.current() not in excluded) | |
1812 | |
1813 def pushending(self, ending, optional = False): | |
1814 "Push a new ending to the bottom" | |
1815 self.endinglist.add(ending, optional) | |
1816 | |
1817 def popending(self, expected = None): | |
1818 "Pop the ending found at the current position" | |
1819 if self.isout() and self.leavepending: | |
1820 return expected | |
1821 ending = self.endinglist.pop(self) | |
1822 if expected and expected != ending: | |
1823 Trace.error('Expected ending ' + expected + ', got ' + ending) | |
1824 self.skip(ending) | |
1825 return ending | |
1826 | |
1827 def nextending(self): | |
1828 "Return the next ending in the queue." | |
1829 nextending = self.endinglist.findending(self) | |
1830 if not nextending: | |
1831 return None | |
1832 return nextending.ending | |
1833 | |
1834 class EndingList(object): | |
1835 "A list of position endings" | |
1836 | |
1837 def __init__(self): | |
1838 self.endings = [] | |
1839 | |
1840 def add(self, ending, optional = False): | |
1841 "Add a new ending to the list" | |
1842 self.endings.append(PositionEnding(ending, optional)) | |
1843 | |
1844 def pickpending(self, pos): | |
1845 "Pick any pending endings from a parse position." | |
1846 self.endings += pos.endinglist.endings | |
1847 | |
1848 def checkin(self, pos): | |
1849 "Search for an ending" | |
1850 if self.findending(pos): | |
1851 return True | |
1852 return False | |
1853 | |
1854 def pop(self, pos): | |
1855 "Remove the ending at the current position" | |
1856 if pos.isout(): | |
1857 Trace.error('No ending out of bounds') | |
1858 return '' | |
1859 ending = self.findending(pos) | |
1860 if not ending: | |
1861 Trace.error('No ending at ' + pos.current()) | |
1862 return '' | |
1863 for each in reversed(self.endings): | |
1864 self.endings.remove(each) | |
1865 if each == ending: | |
1866 return each.ending | |
1867 elif not each.optional: | |
1868 Trace.error('Removed non-optional ending ' + each) | |
1869 Trace.error('No endings left') | |
1870 return '' | |
1871 | |
1872 def findending(self, pos): | |
1873 "Find the ending at the current position" | |
1874 if len(self.endings) == 0: | |
1875 return None | |
1876 for index, ending in enumerate(reversed(self.endings)): | |
1877 if ending.checkin(pos): | |
1878 return ending | |
1879 if not ending.optional: | |
1880 return None | |
1881 return None | |
1882 | |
1883 def checkpending(self): | |
1884 "Check if there are any pending endings" | |
1885 if len(self.endings) != 0: | |
1886 Trace.error('Pending ' + unicode(self) + ' left open') | |
1887 | |
1888 def __unicode__(self): | |
1889 "Printable representation" | |
1890 string = 'endings [' | |
1891 for ending in self.endings: | |
1892 string += unicode(ending) + ',' | |
1893 if len(self.endings) > 0: | |
1894 string = string[:-1] | |
1895 return string + ']' | |
1896 | |
1897 class PositionEnding(object): | |
1898 "An ending for a parsing position" | |
1899 | |
1900 def __init__(self, ending, optional): | |
1901 self.ending = ending | |
1902 self.optional = optional | |
1903 | |
1904 def checkin(self, pos): | |
1905 "Check for the ending" | |
1906 return pos.checkfor(self.ending) | |
1907 | |
1908 def __unicode__(self): | |
1909 "Printable representation" | |
1910 string = 'Ending ' + self.ending | |
1911 if self.optional: | |
1912 string += ' (optional)' | |
1913 return string | |
1914 | |
1915 | |
1916 | |
1917 class Position(Globable): | |
1918 """A position in a text to parse. | |
1919 Including those in Globable, functions to implement by subclasses are: | |
1920 skip(), identifier(), extract(), isout() and current().""" | |
1921 | |
1922 def __init__(self): | |
1923 Globable.__init__(self) | |
1924 | |
1925 def skip(self, string): | |
1926 "Skip a string" | |
1927 Trace.error('Unimplemented skip()') | |
1928 | |
1929 def identifier(self): | |
1930 "Return an identifier for the current position." | |
1931 Trace.error('Unimplemented identifier()') | |
1932 return 'Error' | |
1933 | |
1934 def extract(self, length): | |
1935 "Extract the next string of the given length, or None if not enough text," | |
1936 "without advancing the parse position." | |
1937 Trace.error('Unimplemented extract()') | |
1938 return None | |
1939 | |
1940 def checkfor(self, string): | |
1941 "Check for a string at the given position." | |
1942 return string == self.extract(len(string)) | |
1943 | |
1944 def checkforlower(self, string): | |
1945 "Check for a string in lower case." | |
1946 extracted = self.extract(len(string)) | |
1947 if not extracted: | |
1948 return False | |
1949 return string.lower() == self.extract(len(string)).lower() | |
1950 | |
1951 def skipcurrent(self): | |
1952 "Return the current character and skip it." | |
1953 current = self.current() | |
1954 self.skip(current) | |
1955 return current | |
1956 | |
1957 def next(self): | |
1958 "Advance the position and return the next character." | |
1959 self.skipcurrent() | |
1960 return self.current() | |
1961 | |
1962 def checkskip(self, string): | |
1963 "Check for a string at the given position; if there, skip it" | |
1964 if not self.checkfor(string): | |
1965 return False | |
1966 self.skip(string) | |
1967 return True | |
1968 | |
1969 def error(self, message): | |
1970 "Show an error message and the position identifier." | |
1971 Trace.error(message + ': ' + self.identifier()) | |
1972 | |
1973 class TextPosition(Position): | |
1974 "A parse position based on a raw text." | |
1975 | |
1976 def __init__(self, text): | |
1977 "Create the position from elyxer.some text." | |
1978 Position.__init__(self) | |
1979 self.pos = 0 | |
1980 self.text = text | |
1981 self.checkbytemark() | |
1982 | |
1983 def skip(self, string): | |
1984 "Skip a string of characters." | |
1985 self.pos += len(string) | |
1986 | |
1987 def identifier(self): | |
1988 "Return a sample of the remaining text." | |
1989 length = 30 | |
1990 if self.pos + length > len(self.text): | |
1991 length = len(self.text) - self.pos | |
1992 return '*' + self.text[self.pos:self.pos + length] + '*' | |
1993 | |
1994 def isout(self): | |
1995 "Find out if we are out of the text yet." | |
1996 return self.pos >= len(self.text) | |
1997 | |
1998 def current(self): | |
1999 "Return the current character, assuming we are not out." | |
2000 return self.text[self.pos] | |
2001 | |
2002 def extract(self, length): | |
2003 "Extract the next string of the given length, or None if not enough text." | |
2004 if self.pos + length > len(self.text): | |
2005 return None | |
2006 return self.text[self.pos : self.pos + length] | |
2007 | |
2008 class FilePosition(Position): | |
2009 "A parse position based on an underlying file." | |
2010 | |
2011 def __init__(self, filename): | |
2012 "Create the position from a file." | |
2013 Position.__init__(self) | |
2014 self.reader = LineReader(filename) | |
2015 self.pos = 0 | |
2016 self.checkbytemark() | |
2017 | |
2018 def skip(self, string): | |
2019 "Skip a string of characters." | |
2020 length = len(string) | |
2021 while self.pos + length > len(self.reader.currentline()): | |
2022 length -= len(self.reader.currentline()) - self.pos + 1 | |
2023 self.nextline() | |
2024 self.pos += length | |
2025 | |
2026 def currentline(self): | |
2027 "Get the current line of the underlying file." | |
2028 return self.reader.currentline() | |
2029 | |
2030 def nextline(self): | |
2031 "Go to the next line." | |
2032 self.reader.nextline() | |
2033 self.pos = 0 | |
2034 | |
2035 def linenumber(self): | |
2036 "Return the line number of the file." | |
2037 return self.reader.linenumber + 1 | |
2038 | |
2039 def identifier(self): | |
2040 "Return the current line and line number in the file." | |
2041 before = self.reader.currentline()[:self.pos - 1] | |
2042 after = self.reader.currentline()[self.pos:] | |
2043 return 'line ' + unicode(self.getlinenumber()) + ': ' + before + '*' + after | |
2044 | |
2045 def isout(self): | |
2046 "Find out if we are out of the text yet." | |
2047 if self.pos > len(self.reader.currentline()): | |
2048 if self.pos > len(self.reader.currentline()) + 1: | |
2049 Trace.error('Out of the line ' + self.reader.currentline() + ': ' + unicode(self.pos)) | |
2050 self.nextline() | |
2051 return self.reader.finished() | |
2052 | |
2053 def current(self): | |
2054 "Return the current character, assuming we are not out." | |
2055 if self.pos == len(self.reader.currentline()): | |
2056 return '\n' | |
2057 if self.pos > len(self.reader.currentline()): | |
2058 Trace.error('Out of the line ' + self.reader.currentline() + ': ' + unicode(self.pos)) | |
2059 return '*' | |
2060 return self.reader.currentline()[self.pos] | |
2061 | |
2062 def extract(self, length): | |
2063 "Extract the next string of the given length, or None if not enough text." | |
2064 if self.pos + length > len(self.reader.currentline()): | |
2065 return None | |
2066 return self.reader.currentline()[self.pos : self.pos + length] | |
2067 | |
2068 | |
2069 | |
2070 class Container(object): | |
2071 "A container for text and objects in a lyx file" | |
2072 | |
2073 partkey = None | |
2074 parent = None | |
2075 begin = None | |
2076 | |
2077 def __init__(self): | |
2078 self.contents = list() | |
2079 | |
2080 def process(self): | |
2081 "Process contents" | |
2082 pass | |
2083 | |
2084 def gethtml(self): | |
2085 "Get the resulting HTML" | |
2086 html = self.output.gethtml(self) | |
2087 if isinstance(html, basestring): | |
2088 Trace.error('Raw string ' + html) | |
2089 html = [html] | |
2090 return self.escapeall(html) | |
2091 | |
2092 def escapeall(self, lines): | |
2093 "Escape all lines in an array according to the output options." | |
2094 result = [] | |
2095 for line in lines: | |
2096 if Options.html: | |
2097 line = self.escape(line, EscapeConfig.html) | |
2098 if Options.iso885915: | |
2099 line = self.escape(line, EscapeConfig.iso885915) | |
2100 line = self.escapeentities(line) | |
2101 elif not Options.unicode: | |
2102 line = self.escape(line, EscapeConfig.nonunicode) | |
2103 result.append(line) | |
2104 return result | |
2105 | |
2106 def escape(self, line, replacements = EscapeConfig.entities): | |
2107 "Escape a line with replacements from elyxer.a map" | |
2108 pieces = replacements.keys() | |
2109 # do them in order | |
2110 pieces.sort() | |
2111 for piece in pieces: | |
2112 if piece in line: | |
2113 line = line.replace(piece, replacements[piece]) | |
2114 return line | |
2115 | |
2116 def escapeentities(self, line): | |
2117 "Escape all Unicode characters to HTML entities." | |
2118 result = '' | |
2119 pos = TextPosition(line) | |
2120 while not pos.finished(): | |
2121 if ord(pos.current()) > 128: | |
2122 codepoint = hex(ord(pos.current())) | |
2123 if codepoint == '0xd835': | |
2124 codepoint = hex(ord(pos.next()) + 0xf800) | |
2125 result += '&#' + codepoint[1:] + ';' | |
2126 else: | |
2127 result += pos.current() | |
2128 pos.skipcurrent() | |
2129 return result | |
2130 | |
2131 def searchall(self, type): | |
2132 "Search for all embedded containers of a given type" | |
2133 list = [] | |
2134 self.searchprocess(type, lambda container: list.append(container)) | |
2135 return list | |
2136 | |
2137 def searchremove(self, type): | |
2138 "Search for all containers of a type and remove them" | |
2139 list = self.searchall(type) | |
2140 for container in list: | |
2141 container.parent.contents.remove(container) | |
2142 return list | |
2143 | |
2144 def searchprocess(self, type, process): | |
2145 "Search for elements of a given type and process them" | |
2146 self.locateprocess(lambda container: isinstance(container, type), process) | |
2147 | |
2148 def locateprocess(self, locate, process): | |
2149 "Search for all embedded containers and process them" | |
2150 for container in self.contents: | |
2151 container.locateprocess(locate, process) | |
2152 if locate(container): | |
2153 process(container) | |
2154 | |
2155 def recursivesearch(self, locate, recursive, process): | |
2156 "Perform a recursive search in the container." | |
2157 for container in self.contents: | |
2158 if recursive(container): | |
2159 container.recursivesearch(locate, recursive, process) | |
2160 if locate(container): | |
2161 process(container) | |
2162 | |
2163 def extracttext(self): | |
2164 "Extract all text from elyxer.allowed containers." | |
2165 result = '' | |
2166 constants = ContainerExtractor(ContainerConfig.extracttext).extract(self) | |
2167 for constant in constants: | |
2168 result += constant.string | |
2169 return result | |
2170 | |
2171 def group(self, index, group, isingroup): | |
2172 "Group some adjoining elements into a group" | |
2173 if index >= len(self.contents): | |
2174 return | |
2175 if hasattr(self.contents[index], 'grouped'): | |
2176 return | |
2177 while index < len(self.contents) and isingroup(self.contents[index]): | |
2178 self.contents[index].grouped = True | |
2179 group.contents.append(self.contents[index]) | |
2180 self.contents.pop(index) | |
2181 self.contents.insert(index, group) | |
2182 | |
2183 def remove(self, index): | |
2184 "Remove a container but leave its contents" | |
2185 container = self.contents[index] | |
2186 self.contents.pop(index) | |
2187 while len(container.contents) > 0: | |
2188 self.contents.insert(index, container.contents.pop()) | |
2189 | |
2190 def tree(self, level = 0): | |
2191 "Show in a tree" | |
2192 Trace.debug(" " * level + unicode(self)) | |
2193 for container in self.contents: | |
2194 container.tree(level + 1) | |
2195 | |
2196 def getparameter(self, name): | |
2197 "Get the value of a parameter, if present." | |
2198 if not name in self.parameters: | |
2199 return None | |
2200 return self.parameters[name] | |
2201 | |
2202 def getparameterlist(self, name): | |
2203 "Get the value of a comma-separated parameter as a list." | |
2204 paramtext = self.getparameter(name) | |
2205 if not paramtext: | |
2206 return [] | |
2207 return paramtext.split(',') | |
2208 | |
2209 def hasemptyoutput(self): | |
2210 "Check if the parent's output is empty." | |
2211 current = self.parent | |
2212 while current: | |
2213 if current.output.isempty(): | |
2214 return True | |
2215 current = current.parent | |
2216 return False | |
2217 | |
2218 def __unicode__(self): | |
2219 "Get a description" | |
2220 if not self.begin: | |
2221 return self.__class__.__name__ | |
2222 return self.__class__.__name__ + '@' + unicode(self.begin) | |
2223 | |
2224 class BlackBox(Container): | |
2225 "A container that does not output anything" | |
2226 | |
2227 def __init__(self): | |
2228 self.parser = LoneCommand() | |
2229 self.output = EmptyOutput() | |
2230 self.contents = [] | |
2231 | |
2232 class LyXFormat(BlackBox): | |
2233 "Read the lyxformat command" | |
2234 | |
2235 def process(self): | |
2236 "Show warning if version < 276" | |
2237 version = int(self.header[1]) | |
2238 if version < 276: | |
2239 Trace.error('Warning: unsupported old format version ' + str(version)) | |
2240 if version > int(GeneralConfig.version['lyxformat']): | |
2241 Trace.error('Warning: unsupported new format version ' + str(version)) | |
2242 | |
2243 class StringContainer(Container): | |
2244 "A container for a single string" | |
2245 | |
2246 parsed = None | |
2247 | |
2248 def __init__(self): | |
2249 self.parser = StringParser() | |
2250 self.output = StringOutput() | |
2251 self.string = '' | |
2252 | |
2253 def process(self): | |
2254 "Replace special chars from elyxer.the contents." | |
2255 if self.parsed: | |
2256 self.string = self.replacespecial(self.parsed) | |
2257 self.parsed = None | |
2258 | |
2259 def replacespecial(self, line): | |
2260 "Replace all special chars from elyxer.a line" | |
2261 replaced = self.escape(line, EscapeConfig.entities) | |
2262 replaced = self.changeline(replaced) | |
2263 if ContainerConfig.string['startcommand'] in replaced and len(replaced) > 1: | |
2264 # unprocessed commands | |
2265 if self.begin: | |
2266 message = 'Unknown command at ' + unicode(self.begin) + ': ' | |
2267 else: | |
2268 message = 'Unknown command: ' | |
2269 Trace.error(message + replaced.strip()) | |
2270 return replaced | |
2271 | |
2272 def changeline(self, line): | |
2273 line = self.escape(line, EscapeConfig.chars) | |
2274 if not ContainerConfig.string['startcommand'] in line: | |
2275 return line | |
2276 line = self.escape(line, EscapeConfig.commands) | |
2277 return line | |
2278 | |
2279 def extracttext(self): | |
2280 "Return all text." | |
2281 return self.string | |
2282 | |
2283 def __unicode__(self): | |
2284 "Return a printable representation." | |
2285 result = 'StringContainer' | |
2286 if self.begin: | |
2287 result += '@' + unicode(self.begin) | |
2288 ellipsis = '...' | |
2289 if len(self.string.strip()) <= 15: | |
2290 ellipsis = '' | |
2291 return result + ' (' + self.string.strip()[:15] + ellipsis + ')' | |
2292 | |
2293 class Constant(StringContainer): | |
2294 "A constant string" | |
2295 | |
2296 def __init__(self, text): | |
2297 self.contents = [] | |
2298 self.string = text | |
2299 self.output = StringOutput() | |
2300 | |
2301 def __unicode__(self): | |
2302 return 'Constant: ' + self.string | |
2303 | |
2304 class TaggedText(Container): | |
2305 "Text inside a tag" | |
2306 | |
2307 output = None | |
2308 | |
2309 def __init__(self): | |
2310 self.parser = TextParser(self) | |
2311 self.output = TaggedOutput() | |
2312 | |
2313 def complete(self, contents, tag, breaklines=False): | |
2314 "Complete the tagged text and return it" | |
2315 self.contents = contents | |
2316 self.output.tag = tag | |
2317 self.output.breaklines = breaklines | |
2318 return self | |
2319 | |
2320 def constant(self, text, tag, breaklines=False): | |
2321 "Complete the tagged text with a constant" | |
2322 constant = Constant(text) | |
2323 return self.complete([constant], tag, breaklines) | |
2324 | |
2325 def __unicode__(self): | |
2326 "Return a printable representation." | |
2327 if not hasattr(self.output, 'tag'): | |
2328 return 'Emtpy tagged text' | |
2329 if not self.output.tag: | |
2330 return 'Tagged <unknown tag>' | |
2331 return 'Tagged <' + self.output.tag + '>' | |
2332 | |
2333 | |
2334 | |
2335 | |
2336 | |
2337 | |
2338 class DocumentParameters(object): | |
2339 "Global parameters for the document." | |
2340 | |
2341 pdftitle = None | |
2342 indentstandard = False | |
2343 tocdepth = 10 | |
2344 startinglevel = 0 | |
2345 maxdepth = 10 | |
2346 language = None | |
2347 bibliography = None | |
2348 outputchanges = False | |
2349 displaymode = False | |
2350 | |
2351 | |
2352 | |
2353 | |
2354 | |
2355 | |
2356 class FormulaParser(Parser): | |
2357 "Parses a formula" | |
2358 | |
2359 def parseheader(self, reader): | |
2360 "See if the formula is inlined" | |
2361 self.begin = reader.linenumber + 1 | |
2362 type = self.parsetype(reader) | |
2363 if not type: | |
2364 reader.nextline() | |
2365 type = self.parsetype(reader) | |
2366 if not type: | |
2367 Trace.error('Unknown formula type in ' + reader.currentline().strip()) | |
2368 return ['unknown'] | |
2369 return [type] | |
2370 | |
2371 def parsetype(self, reader): | |
2372 "Get the formula type from the first line." | |
2373 if reader.currentline().find(FormulaConfig.starts['simple']) >= 0: | |
2374 return 'inline' | |
2375 if reader.currentline().find(FormulaConfig.starts['complex']) >= 0: | |
2376 return 'block' | |
2377 if reader.currentline().find(FormulaConfig.starts['unnumbered']) >= 0: | |
2378 return 'block' | |
2379 if reader.currentline().find(FormulaConfig.starts['beginbefore']) >= 0: | |
2380 return 'numbered' | |
2381 return None | |
2382 | |
2383 def parse(self, reader): | |
2384 "Parse the formula until the end" | |
2385 formula = self.parseformula(reader) | |
2386 while not reader.currentline().startswith(self.ending): | |
2387 stripped = reader.currentline().strip() | |
2388 if len(stripped) > 0: | |
2389 Trace.error('Unparsed formula line ' + stripped) | |
2390 reader.nextline() | |
2391 reader.nextline() | |
2392 return formula | |
2393 | |
2394 def parseformula(self, reader): | |
2395 "Parse the formula contents" | |
2396 simple = FormulaConfig.starts['simple'] | |
2397 if simple in reader.currentline(): | |
2398 rest = reader.currentline().split(simple, 1)[1] | |
2399 if simple in rest: | |
2400 # formula is $...$ | |
2401 return self.parsesingleliner(reader, simple, simple) | |
2402 # formula is multiline $...$ | |
2403 return self.parsemultiliner(reader, simple, simple) | |
2404 if FormulaConfig.starts['complex'] in reader.currentline(): | |
2405 # formula of the form \[...\] | |
2406 return self.parsemultiliner(reader, FormulaConfig.starts['complex'], | |
2407 FormulaConfig.endings['complex']) | |
2408 beginbefore = FormulaConfig.starts['beginbefore'] | |
2409 beginafter = FormulaConfig.starts['beginafter'] | |
2410 if beginbefore in reader.currentline(): | |
2411 if reader.currentline().strip().endswith(beginafter): | |
2412 current = reader.currentline().strip() | |
2413 endsplit = current.split(beginbefore)[1].split(beginafter) | |
2414 startpiece = beginbefore + endsplit[0] + beginafter | |
2415 endbefore = FormulaConfig.endings['endbefore'] | |
2416 endafter = FormulaConfig.endings['endafter'] | |
2417 endpiece = endbefore + endsplit[0] + endafter | |
2418 return startpiece + self.parsemultiliner(reader, startpiece, endpiece) + endpiece | |
2419 Trace.error('Missing ' + beginafter + ' in ' + reader.currentline()) | |
2420 return '' | |
2421 begincommand = FormulaConfig.starts['command'] | |
2422 beginbracket = FormulaConfig.starts['bracket'] | |
2423 if begincommand in reader.currentline() and beginbracket in reader.currentline(): | |
2424 endbracket = FormulaConfig.endings['bracket'] | |
2425 return self.parsemultiliner(reader, beginbracket, endbracket) | |
2426 Trace.error('Formula beginning ' + reader.currentline() + ' is unknown') | |
2427 return '' | |
2428 | |
2429 def parsesingleliner(self, reader, start, ending): | |
2430 "Parse a formula in one line" | |
2431 line = reader.currentline().strip() | |
2432 if not start in line: | |
2433 Trace.error('Line ' + line + ' does not contain formula start ' + start) | |
2434 return '' | |
2435 if not line.endswith(ending): | |
2436 Trace.error('Formula ' + line + ' does not end with ' + ending) | |
2437 return '' | |
2438 index = line.index(start) | |
2439 rest = line[index + len(start):-len(ending)] | |
2440 reader.nextline() | |
2441 return rest | |
2442 | |
2443 def parsemultiliner(self, reader, start, ending): | |
2444 "Parse a formula in multiple lines" | |
2445 formula = '' | |
2446 line = reader.currentline() | |
2447 if not start in line: | |
2448 Trace.error('Line ' + line.strip() + ' does not contain formula start ' + start) | |
2449 return '' | |
2450 index = line.index(start) | |
2451 line = line[index + len(start):].strip() | |
2452 while not line.endswith(ending): | |
2453 formula += line + '\n' | |
2454 reader.nextline() | |
2455 line = reader.currentline() | |
2456 formula += line[:-len(ending)] | |
2457 reader.nextline() | |
2458 return formula | |
2459 | |
2460 class MacroParser(FormulaParser): | |
2461 "A parser for a formula macro." | |
2462 | |
2463 def parseheader(self, reader): | |
2464 "See if the formula is inlined" | |
2465 self.begin = reader.linenumber + 1 | |
2466 return ['inline'] | |
2467 | |
2468 def parse(self, reader): | |
2469 "Parse the formula until the end" | |
2470 formula = self.parsemultiliner(reader, self.parent.start, self.ending) | |
2471 reader.nextline() | |
2472 return formula | |
2473 | |
2474 | |
2475 | |
2476 | |
2477 | |
2478 | |
2479 | |
2480 | |
2481 | |
2482 class FormulaBit(Container): | |
2483 "A bit of a formula" | |
2484 | |
2485 type = None | |
2486 size = 1 | |
2487 original = '' | |
2488 | |
2489 def __init__(self): | |
2490 "The formula bit type can be 'alpha', 'number', 'font'." | |
2491 self.contents = [] | |
2492 self.output = ContentsOutput() | |
2493 | |
2494 def setfactory(self, factory): | |
2495 "Set the internal formula factory." | |
2496 self.factory = factory | |
2497 return self | |
2498 | |
2499 def add(self, bit): | |
2500 "Add any kind of formula bit already processed" | |
2501 self.contents.append(bit) | |
2502 self.original += bit.original | |
2503 bit.parent = self | |
2504 | |
2505 def skiporiginal(self, string, pos): | |
2506 "Skip a string and add it to the original formula" | |
2507 self.original += string | |
2508 if not pos.checkskip(string): | |
2509 Trace.error('String ' + string + ' not at ' + pos.identifier()) | |
2510 | |
2511 def computesize(self): | |
2512 "Compute the size of the bit as the max of the sizes of all contents." | |
2513 if len(self.contents) == 0: | |
2514 return 1 | |
2515 self.size = max([element.size for element in self.contents]) | |
2516 return self.size | |
2517 | |
2518 def clone(self): | |
2519 "Return a copy of itself." | |
2520 return self.factory.parseformula(self.original) | |
2521 | |
2522 def __unicode__(self): | |
2523 "Get a string representation" | |
2524 return self.__class__.__name__ + ' read in ' + self.original | |
2525 | |
2526 class TaggedBit(FormulaBit): | |
2527 "A tagged string in a formula" | |
2528 | |
2529 def constant(self, constant, tag): | |
2530 "Set the constant and the tag" | |
2531 self.output = TaggedOutput().settag(tag) | |
2532 self.add(FormulaConstant(constant)) | |
2533 return self | |
2534 | |
2535 def complete(self, contents, tag, breaklines = False): | |
2536 "Set the constant and the tag" | |
2537 self.contents = contents | |
2538 self.output = TaggedOutput().settag(tag, breaklines) | |
2539 return self | |
2540 | |
2541 def selfcomplete(self, tag): | |
2542 "Set the self-closing tag, no contents (as in <hr/>)." | |
2543 self.output = TaggedOutput().settag(tag, empty = True) | |
2544 return self | |
2545 | |
2546 class FormulaConstant(Constant): | |
2547 "A constant string in a formula" | |
2548 | |
2549 def __init__(self, string): | |
2550 "Set the constant string" | |
2551 Constant.__init__(self, string) | |
2552 self.original = string | |
2553 self.size = 1 | |
2554 self.type = None | |
2555 | |
2556 def computesize(self): | |
2557 "Compute the size of the constant: always 1." | |
2558 return self.size | |
2559 | |
2560 def clone(self): | |
2561 "Return a copy of itself." | |
2562 return FormulaConstant(self.original) | |
2563 | |
2564 def __unicode__(self): | |
2565 "Return a printable representation." | |
2566 return 'Formula constant: ' + self.string | |
2567 | |
2568 class RawText(FormulaBit): | |
2569 "A bit of text inside a formula" | |
2570 | |
2571 def detect(self, pos): | |
2572 "Detect a bit of raw text" | |
2573 return pos.current().isalpha() | |
2574 | |
2575 def parsebit(self, pos): | |
2576 "Parse alphabetic text" | |
2577 alpha = pos.globalpha() | |
2578 self.add(FormulaConstant(alpha)) | |
2579 self.type = 'alpha' | |
2580 | |
2581 class FormulaSymbol(FormulaBit): | |
2582 "A symbol inside a formula" | |
2583 | |
2584 modified = FormulaConfig.modified | |
2585 unmodified = FormulaConfig.unmodified['characters'] | |
2586 | |
2587 def detect(self, pos): | |
2588 "Detect a symbol" | |
2589 if pos.current() in FormulaSymbol.unmodified: | |
2590 return True | |
2591 if pos.current() in FormulaSymbol.modified: | |
2592 return True | |
2593 return False | |
2594 | |
2595 def parsebit(self, pos): | |
2596 "Parse the symbol" | |
2597 if pos.current() in FormulaSymbol.unmodified: | |
2598 self.addsymbol(pos.current(), pos) | |
2599 return | |
2600 if pos.current() in FormulaSymbol.modified: | |
2601 self.addsymbol(FormulaSymbol.modified[pos.current()], pos) | |
2602 return | |
2603 Trace.error('Symbol ' + pos.current() + ' not found') | |
2604 | |
2605 def addsymbol(self, symbol, pos): | |
2606 "Add a symbol" | |
2607 self.skiporiginal(pos.current(), pos) | |
2608 self.contents.append(FormulaConstant(symbol)) | |
2609 | |
2610 class FormulaNumber(FormulaBit): | |
2611 "A string of digits in a formula" | |
2612 | |
2613 def detect(self, pos): | |
2614 "Detect a digit" | |
2615 return pos.current().isdigit() | |
2616 | |
2617 def parsebit(self, pos): | |
2618 "Parse a bunch of digits" | |
2619 digits = pos.glob(lambda: pos.current().isdigit()) | |
2620 self.add(FormulaConstant(digits)) | |
2621 self.type = 'number' | |
2622 | |
2623 class Comment(FormulaBit): | |
2624 "A LaTeX comment: % to the end of the line." | |
2625 | |
2626 start = FormulaConfig.starts['comment'] | |
2627 | |
2628 def detect(self, pos): | |
2629 "Detect the %." | |
2630 return pos.current() == self.start | |
2631 | |
2632 def parsebit(self, pos): | |
2633 "Parse to the end of the line." | |
2634 self.original += pos.globincluding('\n') | |
2635 | |
2636 class WhiteSpace(FormulaBit): | |
2637 "Some white space inside a formula." | |
2638 | |
2639 def detect(self, pos): | |
2640 "Detect the white space." | |
2641 return pos.current().isspace() | |
2642 | |
2643 def parsebit(self, pos): | |
2644 "Parse all whitespace." | |
2645 self.original += pos.skipspace() | |
2646 | |
2647 def __unicode__(self): | |
2648 "Return a printable representation." | |
2649 return 'Whitespace: *' + self.original + '*' | |
2650 | |
2651 class Bracket(FormulaBit): | |
2652 "A {} bracket inside a formula" | |
2653 | |
2654 start = FormulaConfig.starts['bracket'] | |
2655 ending = FormulaConfig.endings['bracket'] | |
2656 | |
2657 def __init__(self): | |
2658 "Create a (possibly literal) new bracket" | |
2659 FormulaBit.__init__(self) | |
2660 self.inner = None | |
2661 | |
2662 def detect(self, pos): | |
2663 "Detect the start of a bracket" | |
2664 return pos.checkfor(self.start) | |
2665 | |
2666 def parsebit(self, pos): | |
2667 "Parse the bracket" | |
2668 self.parsecomplete(pos, self.innerformula) | |
2669 return self | |
2670 | |
2671 def parsetext(self, pos): | |
2672 "Parse a text bracket" | |
2673 self.parsecomplete(pos, self.innertext) | |
2674 return self | |
2675 | |
2676 def parseliteral(self, pos): | |
2677 "Parse a literal bracket" | |
2678 self.parsecomplete(pos, self.innerliteral) | |
2679 return self | |
2680 | |
2681 def parsecomplete(self, pos, innerparser): | |
2682 "Parse the start and end marks" | |
2683 if not pos.checkfor(self.start): | |
2684 Trace.error('Bracket should start with ' + self.start + ' at ' + pos.identifier()) | |
2685 return None | |
2686 self.skiporiginal(self.start, pos) | |
2687 pos.pushending(self.ending) | |
2688 innerparser(pos) | |
2689 self.original += pos.popending(self.ending) | |
2690 self.computesize() | |
2691 | |
2692 def innerformula(self, pos): | |
2693 "Parse a whole formula inside the bracket" | |
2694 while not pos.finished(): | |
2695 self.add(self.factory.parseany(pos)) | |
2696 | |
2697 def innertext(self, pos): | |
2698 "Parse some text inside the bracket, following textual rules." | |
2699 specialchars = FormulaConfig.symbolfunctions.keys() | |
2700 specialchars.append(FormulaConfig.starts['command']) | |
2701 specialchars.append(FormulaConfig.starts['bracket']) | |
2702 specialchars.append(Comment.start) | |
2703 while not pos.finished(): | |
2704 if pos.current() in specialchars: | |
2705 self.add(self.factory.parseany(pos)) | |
2706 if pos.checkskip(' '): | |
2707 self.original += ' ' | |
2708 else: | |
2709 self.add(FormulaConstant(pos.skipcurrent())) | |
2710 | |
2711 def innerliteral(self, pos): | |
2712 "Parse a literal inside the bracket, which does not generate HTML." | |
2713 self.literal = '' | |
2714 while not pos.finished() and not pos.current() == self.ending: | |
2715 if pos.current() == self.start: | |
2716 self.parseliteral(pos) | |
2717 else: | |
2718 self.literal += pos.skipcurrent() | |
2719 self.original += self.literal | |
2720 | |
2721 class SquareBracket(Bracket): | |
2722 "A [] bracket inside a formula" | |
2723 | |
2724 start = FormulaConfig.starts['squarebracket'] | |
2725 ending = FormulaConfig.endings['squarebracket'] | |
2726 | |
2727 def clone(self): | |
2728 "Return a new square bracket with the same contents." | |
2729 bracket = SquareBracket() | |
2730 bracket.contents = self.contents | |
2731 return bracket | |
2732 | |
2733 | |
2734 | |
2735 class MathsProcessor(object): | |
2736 "A processor for a maths construction inside the FormulaProcessor." | |
2737 | |
2738 def process(self, contents, index): | |
2739 "Process an element inside a formula." | |
2740 Trace.error('Unimplemented process() in ' + unicode(self)) | |
2741 | |
2742 def __unicode__(self): | |
2743 "Return a printable description." | |
2744 return 'Maths processor ' + self.__class__.__name__ | |
2745 | |
2746 class FormulaProcessor(object): | |
2747 "A processor specifically for formulas." | |
2748 | |
2749 processors = [] | |
2750 | |
2751 def process(self, bit): | |
2752 "Process the contents of every formula bit, recursively." | |
2753 self.processcontents(bit) | |
2754 self.processinsides(bit) | |
2755 self.traversewhole(bit) | |
2756 | |
2757 def processcontents(self, bit): | |
2758 "Process the contents of a formula bit." | |
2759 if not isinstance(bit, FormulaBit): | |
2760 return | |
2761 bit.process() | |
2762 for element in bit.contents: | |
2763 self.processcontents(element) | |
2764 | |
2765 def processinsides(self, bit): | |
2766 "Process the insides (limits, brackets) in a formula bit." | |
2767 if not isinstance(bit, FormulaBit): | |
2768 return | |
2769 for index, element in enumerate(bit.contents): | |
2770 for processor in self.processors: | |
2771 processor.process(bit.contents, index) | |
2772 # continue with recursive processing | |
2773 self.processinsides(element) | |
2774 | |
2775 def traversewhole(self, formula): | |
2776 "Traverse over the contents to alter variables and space units." | |
2777 last = None | |
2778 for bit, contents in self.traverse(formula): | |
2779 if bit.type == 'alpha': | |
2780 self.italicize(bit, contents) | |
2781 elif bit.type == 'font' and last and last.type == 'number': | |
2782 bit.contents.insert(0, FormulaConstant(u' ')) | |
2783 last = bit | |
2784 | |
2785 def traverse(self, bit): | |
2786 "Traverse a formula and yield a flattened structure of (bit, list) pairs." | |
2787 for element in bit.contents: | |
2788 if hasattr(element, 'type') and element.type: | |
2789 yield (element, bit.contents) | |
2790 elif isinstance(element, FormulaBit): | |
2791 for pair in self.traverse(element): | |
2792 yield pair | |
2793 | |
2794 def italicize(self, bit, contents): | |
2795 "Italicize the given bit of text." | |
2796 index = contents.index(bit) | |
2797 contents[index] = TaggedBit().complete([bit], 'i') | |
2798 | |
2799 | |
2800 | |
2801 | |
2802 class Formula(Container): | |
2803 "A LaTeX formula" | |
2804 | |
2805 def __init__(self): | |
2806 self.parser = FormulaParser() | |
2807 self.output = TaggedOutput().settag('span class="formula"') | |
2808 | |
2809 def process(self): | |
2810 "Convert the formula to tags" | |
2811 if self.header[0] == 'inline': | |
2812 DocumentParameters.displaymode = False | |
2813 else: | |
2814 DocumentParameters.displaymode = True | |
2815 self.output.settag('div class="formula"', True) | |
2816 if Options.jsmath: | |
2817 self.jsmath() | |
2818 elif Options.mathjax: | |
2819 self.mathjax() | |
2820 elif Options.googlecharts: | |
2821 self.googlecharts() | |
2822 else: | |
2823 self.classic() | |
2824 | |
2825 def jsmath(self): | |
2826 "Make the contents for jsMath." | |
2827 if self.header[0] != 'inline': | |
2828 self.output = TaggedOutput().settag('div class="math"') | |
2829 else: | |
2830 self.output = TaggedOutput().settag('span class="math"') | |
2831 self.contents = [Constant(self.parsed)] | |
2832 | |
2833 def mathjax(self): | |
2834 "Make the contents for MathJax." | |
2835 self.output.tag = 'span class="MathJax_Preview"' | |
2836 tag = 'script type="math/tex' | |
2837 if self.header[0] != 'inline': | |
2838 tag += ';mode=display' | |
2839 self.contents = [TaggedText().constant(self.parsed, tag + '"', True)] | |
2840 | |
2841 def googlecharts(self): | |
2842 "Make the contents using Google Charts http://code.google.com/apis/chart/." | |
2843 url = FormulaConfig.urls['googlecharts'] + urllib.quote_plus(self.parsed) | |
2844 img = '<img class="chart" src="' + url + '" alt="' + self.parsed + '"/>' | |
2845 self.contents = [Constant(img)] | |
2846 | |
2847 def classic(self): | |
2848 "Make the contents using classic output generation with XHTML and CSS." | |
2849 whole = FormulaFactory().parseformula(self.parsed) | |
2850 FormulaProcessor().process(whole) | |
2851 whole.parent = self | |
2852 self.contents = [whole] | |
2853 | |
2854 def parse(self, pos): | |
2855 "Parse using a parse position instead of self.parser." | |
2856 if pos.checkskip('$$'): | |
2857 self.parsedollarblock(pos) | |
2858 elif pos.checkskip('$'): | |
2859 self.parsedollarinline(pos) | |
2860 elif pos.checkskip('\\('): | |
2861 self.parseinlineto(pos, '\\)') | |
2862 elif pos.checkskip('\\['): | |
2863 self.parseblockto(pos, '\\]') | |
2864 else: | |
2865 pos.error('Unparseable formula') | |
2866 self.process() | |
2867 return self | |
2868 | |
2869 def parsedollarinline(self, pos): | |
2870 "Parse a $...$ formula." | |
2871 self.header = ['inline'] | |
2872 self.parsedollar(pos) | |
2873 | |
2874 def parsedollarblock(self, pos): | |
2875 "Parse a $$...$$ formula." | |
2876 self.header = ['block'] | |
2877 self.parsedollar(pos) | |
2878 if not pos.checkskip('$'): | |
2879 pos.error('Formula should be $$...$$, but last $ is missing.') | |
2880 | |
2881 def parsedollar(self, pos): | |
2882 "Parse to the next $." | |
2883 pos.pushending('$') | |
2884 self.parsed = pos.globexcluding('$') | |
2885 pos.popending('$') | |
2886 | |
2887 def parseinlineto(self, pos, limit): | |
2888 "Parse a \\(...\\) formula." | |
2889 self.header = ['inline'] | |
2890 self.parseupto(pos, limit) | |
2891 | |
2892 def parseblockto(self, pos, limit): | |
2893 "Parse a \\[...\\] formula." | |
2894 self.header = ['block'] | |
2895 self.parseupto(pos, limit) | |
2896 | |
2897 def parseupto(self, pos, limit): | |
2898 "Parse a formula that ends with the given command." | |
2899 pos.pushending(limit) | |
2900 self.parsed = pos.glob(lambda: True) | |
2901 pos.popending(limit) | |
2902 | |
2903 def __unicode__(self): | |
2904 "Return a printable representation." | |
2905 if self.partkey and self.partkey.number: | |
2906 return 'Formula (' + self.partkey.number + ')' | |
2907 return 'Unnumbered formula' | |
2908 | |
2909 class WholeFormula(FormulaBit): | |
2910 "Parse a whole formula" | |
2911 | |
2912 def detect(self, pos): | |
2913 "Not outside the formula is enough." | |
2914 return not pos.finished() | |
2915 | |
2916 def parsebit(self, pos): | |
2917 "Parse with any formula bit" | |
2918 while not pos.finished(): | |
2919 self.add(self.factory.parseany(pos)) | |
2920 | |
2921 class FormulaFactory(object): | |
2922 "Construct bits of formula" | |
2923 | |
2924 # bit types will be appended later | |
2925 types = [FormulaSymbol, RawText, FormulaNumber, Bracket, Comment, WhiteSpace] | |
2926 skippedtypes = [Comment, WhiteSpace] | |
2927 defining = False | |
2928 | |
2929 def __init__(self): | |
2930 "Initialize the map of instances." | |
2931 self.instances = dict() | |
2932 | |
2933 def detecttype(self, type, pos): | |
2934 "Detect a bit of a given type." | |
2935 if pos.finished(): | |
2936 return False | |
2937 return self.instance(type).detect(pos) | |
2938 | |
2939 def instance(self, type): | |
2940 "Get an instance of the given type." | |
2941 if not type in self.instances or not self.instances[type]: | |
2942 self.instances[type] = self.create(type) | |
2943 return self.instances[type] | |
2944 | |
2945 def create(self, type): | |
2946 "Create a new formula bit of the given type." | |
2947 return Cloner.create(type).setfactory(self) | |
2948 | |
2949 def clearskipped(self, pos): | |
2950 "Clear any skipped types." | |
2951 while not pos.finished(): | |
2952 if not self.skipany(pos): | |
2953 return | |
2954 return | |
2955 | |
2956 def skipany(self, pos): | |
2957 "Skip any skipped types." | |
2958 for type in self.skippedtypes: | |
2959 if self.instance(type).detect(pos): | |
2960 return self.parsetype(type, pos) | |
2961 return None | |
2962 | |
2963 def parseany(self, pos): | |
2964 "Parse any formula bit at the current location." | |
2965 for type in self.types + self.skippedtypes: | |
2966 if self.detecttype(type, pos): | |
2967 return self.parsetype(type, pos) | |
2968 Trace.error('Unrecognized formula at ' + pos.identifier()) | |
2969 return FormulaConstant(pos.skipcurrent()) | |
2970 | |
2971 def parsetype(self, type, pos): | |
2972 "Parse the given type and return it." | |
2973 bit = self.instance(type) | |
2974 self.instances[type] = None | |
2975 returnedbit = bit.parsebit(pos) | |
2976 if returnedbit: | |
2977 return returnedbit.setfactory(self) | |
2978 return bit | |
2979 | |
2980 def parseformula(self, formula): | |
2981 "Parse a string of text that contains a whole formula." | |
2982 pos = TextPosition(formula) | |
2983 whole = self.create(WholeFormula) | |
2984 if whole.detect(pos): | |
2985 whole.parsebit(pos) | |
2986 return whole | |
2987 # no formula found | |
2988 if not pos.finished(): | |
2989 Trace.error('Unknown formula at: ' + pos.identifier()) | |
2990 whole.add(TaggedBit().constant(formula, 'span class="unknown"')) | |
2991 return whole | |
2992 | |
2993 | |
2994 | |
2995 | |
2996 import unicodedata | |
2997 | |
2998 | |
2999 | |
3000 | |
3001 | |
3002 | |
3003 | |
3004 | |
3005 | |
3006 | |
3007 | |
3008 | |
3009 import gettext | |
3010 | |
3011 | |
3012 class Translator(object): | |
3013 "Reads the configuration file and tries to find a translation." | |
3014 "Otherwise falls back to the messages in the config file." | |
3015 | |
3016 instance = None | |
3017 | |
3018 def translate(cls, key): | |
3019 "Get the translated message for a key." | |
3020 return cls.instance.getmessage(key) | |
3021 | |
3022 translate = classmethod(translate) | |
3023 | |
3024 def __init__(self): | |
3025 self.translation = None | |
3026 self.first = True | |
3027 | |
3028 def findtranslation(self): | |
3029 "Find the translation for the document language." | |
3030 self.langcodes = None | |
3031 if not DocumentParameters.language: | |
3032 Trace.error('No language in document') | |
3033 return | |
3034 if not DocumentParameters.language in TranslationConfig.languages: | |
3035 Trace.error('Unknown language ' + DocumentParameters.language) | |
3036 return | |
3037 if TranslationConfig.languages[DocumentParameters.language] == 'en': | |
3038 return | |
3039 langcodes = [TranslationConfig.languages[DocumentParameters.language]] | |
3040 try: | |
3041 self.translation = gettext.translation('elyxer', None, langcodes) | |
3042 except IOError: | |
3043 Trace.error('No translation for ' + unicode(langcodes)) | |
3044 | |
3045 def getmessage(self, key): | |
3046 "Get the translated message for the given key." | |
3047 if self.first: | |
3048 self.findtranslation() | |
3049 self.first = False | |
3050 message = self.getuntranslated(key) | |
3051 if not self.translation: | |
3052 return message | |
3053 try: | |
3054 message = self.translation.ugettext(message) | |
3055 except IOError: | |
3056 pass | |
3057 return message | |
3058 | |
3059 def getuntranslated(self, key): | |
3060 "Get the untranslated message." | |
3061 if not key in TranslationConfig.constants: | |
3062 Trace.error('Cannot translate ' + key) | |
3063 return key | |
3064 return TranslationConfig.constants[key] | |
3065 | |
3066 Translator.instance = Translator() | |
3067 | |
3068 | |
3069 | |
3070 class NumberCounter(object): | |
3071 "A counter for numbers (by default)." | |
3072 "The type can be changed to return letters, roman numbers..." | |
3073 | |
3074 name = None | |
3075 value = None | |
3076 mode = None | |
3077 master = None | |
3078 | |
3079 letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' | |
3080 symbols = NumberingConfig.sequence['symbols'] | |
3081 romannumerals = [ | |
3082 ('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), ('C', 100), | |
3083 ('XC', 90), ('L', 50), ('XL', 40), ('X', 10), ('IX', 9), ('V', 5), | |
3084 ('IV', 4), ('I', 1) | |
3085 ] | |
3086 | |
3087 def __init__(self, name): | |
3088 "Give a name to the counter." | |
3089 self.name = name | |
3090 | |
3091 def setmode(self, mode): | |
3092 "Set the counter mode. Can be changed at runtime." | |
3093 self.mode = mode | |
3094 return self | |
3095 | |
3096 def init(self, value): | |
3097 "Set an initial value." | |
3098 self.value = value | |
3099 | |
3100 def gettext(self): | |
3101 "Get the next value as a text string." | |
3102 return unicode(self.value) | |
3103 | |
3104 def getletter(self): | |
3105 "Get the next value as a letter." | |
3106 return self.getsequence(self.letters) | |
3107 | |
3108 def getsymbol(self): | |
3109 "Get the next value as a symbol." | |
3110 return self.getsequence(self.symbols) | |
3111 | |
3112 def getsequence(self, sequence): | |
3113 "Get the next value from elyxer.a sequence." | |
3114 return sequence[(self.value - 1) % len(sequence)] | |
3115 | |
3116 def getroman(self): | |
3117 "Get the next value as a roman number." | |
3118 result = '' | |
3119 number = self.value | |
3120 for numeral, value in self.romannumerals: | |
3121 if number >= value: | |
3122 result += numeral * (number / value) | |
3123 number = number % value | |
3124 return result | |
3125 | |
3126 def getvalue(self): | |
3127 "Get the current value as configured in the current mode." | |
3128 if not self.mode or self.mode in ['text', '1']: | |
3129 return self.gettext() | |
3130 if self.mode == 'A': | |
3131 return self.getletter() | |
3132 if self.mode == 'a': | |
3133 return self.getletter().lower() | |
3134 if self.mode == 'I': | |
3135 return self.getroman() | |
3136 if self.mode == '*': | |
3137 return self.getsymbol() | |
3138 Trace.error('Unknown counter mode ' + self.mode) | |
3139 return self.gettext() | |
3140 | |
3141 def getnext(self): | |
3142 "Increase the current value and get the next value as configured." | |
3143 if not self.value: | |
3144 self.value = 0 | |
3145 self.value += 1 | |
3146 return self.getvalue() | |
3147 | |
3148 def reset(self): | |
3149 "Reset the counter." | |
3150 self.value = 0 | |
3151 | |
3152 def __unicode__(self): | |
3153 "Return a printable representation." | |
3154 result = 'Counter ' + self.name | |
3155 if self.mode: | |
3156 result += ' in mode ' + self.mode | |
3157 return result | |
3158 | |
3159 class DependentCounter(NumberCounter): | |
3160 "A counter which depends on another one (the master)." | |
3161 | |
3162 def setmaster(self, master): | |
3163 "Set the master counter." | |
3164 self.master = master | |
3165 self.last = self.master.getvalue() | |
3166 return self | |
3167 | |
3168 def getnext(self): | |
3169 "Increase or, if the master counter has changed, restart." | |
3170 if self.last != self.master.getvalue(): | |
3171 self.reset() | |
3172 value = NumberCounter.getnext(self) | |
3173 self.last = self.master.getvalue() | |
3174 return value | |
3175 | |
3176 def getvalue(self): | |
3177 "Get the value of the combined counter: master.dependent." | |
3178 return self.master.getvalue() + '.' + NumberCounter.getvalue(self) | |
3179 | |
3180 class NumberGenerator(object): | |
3181 "A number generator for unique sequences and hierarchical structures. Used in:" | |
3182 " * ordered part numbers: Chapter 3, Section 5.3." | |
3183 " * unique part numbers: Footnote 15, Bibliography cite [15]." | |
3184 " * chaptered part numbers: Figure 3.15, Equation (8.3)." | |
3185 " * unique roman part numbers: Part I, Book IV." | |
3186 | |
3187 chaptered = None | |
3188 generator = None | |
3189 | |
3190 romanlayouts = [x.lower() for x in NumberingConfig.layouts['roman']] | |
3191 orderedlayouts = [x.lower() for x in NumberingConfig.layouts['ordered']] | |
3192 | |
3193 counters = dict() | |
3194 appendix = None | |
3195 | |
3196 def deasterisk(self, type): | |
3197 "Remove the possible asterisk in a layout type." | |
3198 return type.replace('*', '') | |
3199 | |
3200 def isunique(self, type): | |
3201 "Find out if the layout type corresponds to a unique part." | |
3202 return self.isroman(type) | |
3203 | |
3204 def isroman(self, type): | |
3205 "Find out if the layout type should have roman numeration." | |
3206 return self.deasterisk(type).lower() in self.romanlayouts | |
3207 | |
3208 def isinordered(self, type): | |
3209 "Find out if the layout type corresponds to an (un)ordered part." | |
3210 return self.deasterisk(type).lower() in self.orderedlayouts | |
3211 | |
3212 def isnumbered(self, type): | |
3213 "Find out if the type for a layout corresponds to a numbered layout." | |
3214 if '*' in type: | |
3215 return False | |
3216 if self.isroman(type): | |
3217 return True | |
3218 if not self.isinordered(type): | |
3219 return False | |
3220 if self.getlevel(type) > DocumentParameters.maxdepth: | |
3221 return False | |
3222 return True | |
3223 | |
3224 def isunordered(self, type): | |
3225 "Find out if the type contains an asterisk, basically." | |
3226 return '*' in type | |
3227 | |
3228 def getlevel(self, type): | |
3229 "Get the level that corresponds to a layout type." | |
3230 if self.isunique(type): | |
3231 return 0 | |
3232 if not self.isinordered(type): | |
3233 Trace.error('Unknown layout type ' + type) | |
3234 return 0 | |
3235 type = self.deasterisk(type).lower() | |
3236 level = self.orderedlayouts.index(type) + 1 | |
3237 return level - DocumentParameters.startinglevel | |
3238 | |
3239 def getparttype(self, type): | |
3240 "Obtain the type for the part: without the asterisk, " | |
3241 "and switched to Appendix if necessary." | |
3242 if NumberGenerator.appendix and self.getlevel(type) == 1: | |
3243 return 'Appendix' | |
3244 return self.deasterisk(type) | |
3245 | |
3246 def generate(self, type): | |
3247 "Generate a number for a layout type." | |
3248 "Unique part types such as Part or Book generate roman numbers: Part I." | |
3249 "Ordered part types return dot-separated tuples: Chapter 5, Subsection 2.3.5." | |
3250 "Everything else generates unique numbers: Bibliography [1]." | |
3251 "Each invocation results in a new number." | |
3252 return self.getcounter(type).getnext() | |
3253 | |
3254 def getcounter(self, type): | |
3255 "Get the counter for the given type." | |
3256 type = type.lower() | |
3257 if not type in self.counters: | |
3258 self.counters[type] = self.create(type) | |
3259 return self.counters[type] | |
3260 | |
3261 def create(self, type): | |
3262 "Create a counter for the given type." | |
3263 if self.isnumbered(type) and self.getlevel(type) > 1: | |
3264 index = self.orderedlayouts.index(type) | |
3265 above = self.orderedlayouts[index - 1] | |
3266 master = self.getcounter(above) | |
3267 return self.createdependent(type, master) | |
3268 counter = NumberCounter(type) | |
3269 if self.isroman(type): | |
3270 counter.setmode('I') | |
3271 return counter | |
3272 | |
3273 def getdependentcounter(self, type, master): | |
3274 "Get (or create) a counter of the given type that depends on another." | |
3275 if not type in self.counters or not self.counters[type].master: | |
3276 self.counters[type] = self.createdependent(type, master) | |
3277 return self.counters[type] | |
3278 | |
3279 def createdependent(self, type, master): | |
3280 "Create a dependent counter given the master." | |
3281 return DependentCounter(type).setmaster(master) | |
3282 | |
3283 def startappendix(self): | |
3284 "Start appendices here." | |
3285 firsttype = self.orderedlayouts[DocumentParameters.startinglevel] | |
3286 counter = self.getcounter(firsttype) | |
3287 counter.setmode('A').reset() | |
3288 NumberGenerator.appendix = True | |
3289 | |
3290 class ChapteredGenerator(NumberGenerator): | |
3291 "Generate chaptered numbers, as in Chapter.Number." | |
3292 "Used in equations, figures: Equation (5.3), figure 8.15." | |
3293 | |
3294 def generate(self, type): | |
3295 "Generate a number which goes with first-level numbers (chapters). " | |
3296 "For the article classes a unique number is generated." | |
3297 if DocumentParameters.startinglevel > 0: | |
3298 return NumberGenerator.generator.generate(type) | |
3299 chapter = self.getcounter('Chapter') | |
3300 return self.getdependentcounter(type, chapter).getnext() | |
3301 | |
3302 | |
3303 NumberGenerator.chaptered = ChapteredGenerator() | |
3304 NumberGenerator.generator = NumberGenerator() | |
3305 | |
3306 | |
3307 | |
3308 | |
3309 | |
3310 | |
3311 class ContainerSize(object): | |
3312 "The size of a container." | |
3313 | |
3314 width = None | |
3315 height = None | |
3316 maxwidth = None | |
3317 maxheight = None | |
3318 scale = None | |
3319 | |
3320 def set(self, width = None, height = None): | |
3321 "Set the proper size with width and height." | |
3322 self.setvalue('width', width) | |
3323 self.setvalue('height', height) | |
3324 return self | |
3325 | |
3326 def setmax(self, maxwidth = None, maxheight = None): | |
3327 "Set max width and/or height." | |
3328 self.setvalue('maxwidth', maxwidth) | |
3329 self.setvalue('maxheight', maxheight) | |
3330 return self | |
3331 | |
3332 def readparameters(self, container): | |
3333 "Read some size parameters off a container." | |
3334 self.setparameter(container, 'width') | |
3335 self.setparameter(container, 'height') | |
3336 self.setparameter(container, 'scale') | |
3337 self.checkvalidheight(container) | |
3338 return self | |
3339 | |
3340 def setparameter(self, container, name): | |
3341 "Read a size parameter off a container, and set it if present." | |
3342 value = container.getparameter(name) | |
3343 self.setvalue(name, value) | |
3344 | |
3345 def setvalue(self, name, value): | |
3346 "Set the value of a parameter name, only if it's valid." | |
3347 value = self.processparameter(value) | |
3348 if value: | |
3349 setattr(self, name, value) | |
3350 | |
3351 def checkvalidheight(self, container): | |
3352 "Check if the height parameter is valid; otherwise erase it." | |
3353 heightspecial = container.getparameter('height_special') | |
3354 if self.height and self.extractnumber(self.height) == '1' and heightspecial == 'totalheight': | |
3355 self.height = None | |
3356 | |
3357 def processparameter(self, value): | |
3358 "Do the full processing on a parameter." | |
3359 if not value: | |
3360 return None | |
3361 if self.extractnumber(value) == '0': | |
3362 return None | |
3363 for ignored in StyleConfig.size['ignoredtexts']: | |
3364 if ignored in value: | |
3365 value = value.replace(ignored, '') | |
3366 return value | |
3367 | |
3368 def extractnumber(self, text): | |
3369 "Extract the first number in the given text." | |
3370 result = '' | |
3371 decimal = False | |
3372 for char in text: | |
3373 if char.isdigit(): | |
3374 result += char | |
3375 elif char == '.' and not decimal: | |
3376 result += char | |
3377 decimal = True | |
3378 else: | |
3379 return result | |
3380 return result | |
3381 | |
3382 def checkimage(self, width, height): | |
3383 "Check image dimensions, set them if possible." | |
3384 if width: | |
3385 self.maxwidth = unicode(width) + 'px' | |
3386 if self.scale and not self.width: | |
3387 self.width = self.scalevalue(width) | |
3388 if height: | |
3389 self.maxheight = unicode(height) + 'px' | |
3390 if self.scale and not self.height: | |
3391 self.height = self.scalevalue(height) | |
3392 if self.width and not self.height: | |
3393 self.height = 'auto' | |
3394 if self.height and not self.width: | |
3395 self.width = 'auto' | |
3396 | |
3397 def scalevalue(self, value): | |
3398 "Scale the value according to the image scale and return it as unicode." | |
3399 scaled = value * int(self.scale) / 100 | |
3400 return unicode(int(scaled)) + 'px' | |
3401 | |
3402 def removepercentwidth(self): | |
3403 "Remove percent width if present, to set it at the figure level." | |
3404 if not self.width: | |
3405 return None | |
3406 if not '%' in self.width: | |
3407 return None | |
3408 width = self.width | |
3409 self.width = None | |
3410 if self.height == 'auto': | |
3411 self.height = None | |
3412 return width | |
3413 | |
3414 def addstyle(self, container): | |
3415 "Add the proper style attribute to the output tag." | |
3416 if not isinstance(container.output, TaggedOutput): | |
3417 Trace.error('No tag to add style, in ' + unicode(container)) | |
3418 if not self.width and not self.height and not self.maxwidth and not self.maxheight: | |
3419 # nothing to see here; move along | |
3420 return | |
3421 tag = ' style="' | |
3422 tag += self.styleparameter('width') | |
3423 tag += self.styleparameter('maxwidth') | |
3424 tag += self.styleparameter('height') | |
3425 tag += self.styleparameter('maxheight') | |
3426 if tag[-1] == ' ': | |
3427 tag = tag[:-1] | |
3428 tag += '"' | |
3429 container.output.tag += tag | |
3430 | |
3431 def styleparameter(self, name): | |
3432 "Get the style for a single parameter." | |
3433 value = getattr(self, name) | |
3434 if value: | |
3435 return name.replace('max', 'max-') + ': ' + value + '; ' | |
3436 return '' | |
3437 | |
3438 | |
3439 | |
3440 class QuoteContainer(Container): | |
3441 "A container for a pretty quote" | |
3442 | |
3443 def __init__(self): | |
3444 self.parser = BoundedParser() | |
3445 self.output = FixedOutput() | |
3446 | |
3447 def process(self): | |
3448 "Process contents" | |
3449 self.type = self.header[2] | |
3450 if not self.type in StyleConfig.quotes: | |
3451 Trace.error('Quote type ' + self.type + ' not found') | |
3452 self.html = ['"'] | |
3453 return | |
3454 self.html = [StyleConfig.quotes[self.type]] | |
3455 | |
3456 class LyXLine(Container): | |
3457 "A Lyx line" | |
3458 | |
3459 def __init__(self): | |
3460 self.parser = LoneCommand() | |
3461 self.output = FixedOutput() | |
3462 | |
3463 def process(self): | |
3464 self.html = ['<hr class="line" />'] | |
3465 | |
3466 class EmphaticText(TaggedText): | |
3467 "Text with emphatic mode" | |
3468 | |
3469 def process(self): | |
3470 self.output.tag = 'i' | |
3471 | |
3472 class ShapedText(TaggedText): | |
3473 "Text shaped (italic, slanted)" | |
3474 | |
3475 def process(self): | |
3476 self.type = self.header[1] | |
3477 if not self.type in TagConfig.shaped: | |
3478 Trace.error('Unrecognized shape ' + self.header[1]) | |
3479 self.output.tag = 'span' | |
3480 return | |
3481 self.output.tag = TagConfig.shaped[self.type] | |
3482 | |
3483 class VersalitasText(TaggedText): | |
3484 "Text in versalitas" | |
3485 | |
3486 def process(self): | |
3487 self.output.tag = 'span class="versalitas"' | |
3488 | |
3489 class ColorText(TaggedText): | |
3490 "Colored text" | |
3491 | |
3492 def process(self): | |
3493 self.color = self.header[1] | |
3494 self.output.tag = 'span class="' + self.color + '"' | |
3495 | |
3496 class SizeText(TaggedText): | |
3497 "Sized text" | |
3498 | |
3499 def process(self): | |
3500 self.size = self.header[1] | |
3501 self.output.tag = 'span class="' + self.size + '"' | |
3502 | |
3503 class BoldText(TaggedText): | |
3504 "Bold text" | |
3505 | |
3506 def process(self): | |
3507 self.output.tag = 'b' | |
3508 | |
3509 class TextFamily(TaggedText): | |
3510 "A bit of text from elyxer.a different family" | |
3511 | |
3512 def process(self): | |
3513 "Parse the type of family" | |
3514 self.type = self.header[1] | |
3515 if not self.type in TagConfig.family: | |
3516 Trace.error('Unrecognized family ' + type) | |
3517 self.output.tag = 'span' | |
3518 return | |
3519 self.output.tag = TagConfig.family[self.type] | |
3520 | |
3521 class Hfill(TaggedText): | |
3522 "Horizontall fill" | |
3523 | |
3524 def process(self): | |
3525 self.output.tag = 'span class="hfill"' | |
3526 | |
3527 class BarredText(TaggedText): | |
3528 "Text with a bar somewhere" | |
3529 | |
3530 def process(self): | |
3531 "Parse the type of bar" | |
3532 self.type = self.header[1] | |
3533 if not self.type in TagConfig.barred: | |
3534 Trace.error('Unknown bar type ' + self.type) | |
3535 self.output.tag = 'span' | |
3536 return | |
3537 self.output.tag = TagConfig.barred[self.type] | |
3538 | |
3539 class LangLine(BlackBox): | |
3540 "A line with language information" | |
3541 | |
3542 def process(self): | |
3543 self.lang = self.header[1] | |
3544 | |
3545 class InsetLength(BlackBox): | |
3546 "A length measure inside an inset." | |
3547 | |
3548 def process(self): | |
3549 self.length = self.header[1] | |
3550 | |
3551 class Space(Container): | |
3552 "A space of several types" | |
3553 | |
3554 def __init__(self): | |
3555 self.parser = InsetParser() | |
3556 self.output = FixedOutput() | |
3557 | |
3558 def process(self): | |
3559 self.type = self.header[2] | |
3560 if self.type not in StyleConfig.hspaces: | |
3561 Trace.error('Unknown space type ' + self.type) | |
3562 self.html = [' '] | |
3563 return | |
3564 self.html = [StyleConfig.hspaces[self.type]] | |
3565 length = self.getlength() | |
3566 if not length: | |
3567 return | |
3568 self.output = TaggedOutput().settag('span class="hspace"', False) | |
3569 ContainerSize().set(length).addstyle(self) | |
3570 | |
3571 def getlength(self): | |
3572 "Get the space length from elyxer.the contents or parameters." | |
3573 if len(self.contents) == 0 or not isinstance(self.contents[0], InsetLength): | |
3574 return None | |
3575 return self.contents[0].length | |
3576 | |
3577 class VerticalSpace(Container): | |
3578 "An inset that contains a vertical space." | |
3579 | |
3580 def __init__(self): | |
3581 self.parser = InsetParser() | |
3582 self.output = FixedOutput() | |
3583 | |
3584 def process(self): | |
3585 "Set the correct tag" | |
3586 self.type = self.header[2] | |
3587 if self.type not in StyleConfig.vspaces: | |
3588 self.output = TaggedOutput().settag('div class="vspace" style="height: ' + self.type + ';"', True) | |
3589 return | |
3590 self.html = [StyleConfig.vspaces[self.type]] | |
3591 | |
3592 class Align(Container): | |
3593 "Bit of aligned text" | |
3594 | |
3595 def __init__(self): | |
3596 self.parser = ExcludingParser() | |
3597 self.output = TaggedOutput().setbreaklines(True) | |
3598 | |
3599 def process(self): | |
3600 self.output.tag = 'div class="' + self.header[1] + '"' | |
3601 | |
3602 class Newline(Container): | |
3603 "A newline" | |
3604 | |
3605 def __init__(self): | |
3606 self.parser = LoneCommand() | |
3607 self.output = FixedOutput() | |
3608 | |
3609 def process(self): | |
3610 "Process contents" | |
3611 self.html = ['<br/>\n'] | |
3612 | |
3613 class NewPage(Newline): | |
3614 "A new page" | |
3615 | |
3616 def process(self): | |
3617 "Process contents" | |
3618 self.html = ['<p><br/>\n</p>\n'] | |
3619 | |
3620 class Separator(Container): | |
3621 "A separator string which is not extracted by extracttext()." | |
3622 | |
3623 def __init__(self, constant): | |
3624 self.output = FixedOutput() | |
3625 self.contents = [] | |
3626 self.html = [constant] | |
3627 | |
3628 class StrikeOut(TaggedText): | |
3629 "Striken out text." | |
3630 | |
3631 def process(self): | |
3632 "Set the output tag to strike." | |
3633 self.output.tag = 'strike' | |
3634 | |
3635 class StartAppendix(BlackBox): | |
3636 "Mark to start an appendix here." | |
3637 "From this point on, all chapters become appendices." | |
3638 | |
3639 def process(self): | |
3640 "Activate the special numbering scheme for appendices, using letters." | |
3641 NumberGenerator.generator.startappendix() | |
3642 | |
3643 | |
3644 | |
3645 | |
3646 | |
3647 | |
3648 class Link(Container): | |
3649 "A link to another part of the document" | |
3650 | |
3651 anchor = None | |
3652 url = None | |
3653 type = None | |
3654 page = None | |
3655 target = None | |
3656 destination = None | |
3657 title = None | |
3658 | |
3659 def __init__(self): | |
3660 "Initialize the link, add target if configured." | |
3661 self.contents = [] | |
3662 self.parser = InsetParser() | |
3663 self.output = LinkOutput() | |
3664 if Options.target: | |
3665 self.target = Options.target | |
3666 | |
3667 def complete(self, text, anchor = None, url = None, type = None, title = None): | |
3668 "Complete the link." | |
3669 self.contents = [Constant(text)] | |
3670 if anchor: | |
3671 self.anchor = anchor | |
3672 if url: | |
3673 self.url = url | |
3674 if type: | |
3675 self.type = type | |
3676 if title: | |
3677 self.title = title | |
3678 return self | |
3679 | |
3680 def computedestination(self): | |
3681 "Use the destination link to fill in the destination URL." | |
3682 if not self.destination: | |
3683 return | |
3684 self.url = '' | |
3685 if self.destination.anchor: | |
3686 self.url = '#' + self.destination.anchor | |
3687 if self.destination.page: | |
3688 self.url = self.destination.page + self.url | |
3689 | |
3690 def setmutualdestination(self, destination): | |
3691 "Set another link as destination, and set its destination to this one." | |
3692 self.destination = destination | |
3693 destination.destination = self | |
3694 | |
3695 def __unicode__(self): | |
3696 "Return a printable representation." | |
3697 result = 'Link' | |
3698 if self.anchor: | |
3699 result += ' #' + self.anchor | |
3700 if self.url: | |
3701 result += ' to ' + self.url | |
3702 return result | |
3703 | |
3704 class URL(Link): | |
3705 "A clickable URL" | |
3706 | |
3707 def process(self): | |
3708 "Read URL from elyxer.parameters" | |
3709 target = self.escape(self.getparameter('target')) | |
3710 self.url = target | |
3711 type = self.getparameter('type') | |
3712 if type: | |
3713 self.url = self.escape(type) + target | |
3714 name = self.getparameter('name') | |
3715 if not name: | |
3716 name = target | |
3717 self.contents = [Constant(name)] | |
3718 | |
3719 class FlexURL(URL): | |
3720 "A flexible URL" | |
3721 | |
3722 def process(self): | |
3723 "Read URL from elyxer.contents" | |
3724 self.url = self.extracttext() | |
3725 | |
3726 class LinkOutput(ContainerOutput): | |
3727 "A link pointing to some destination" | |
3728 "Or an anchor (destination)" | |
3729 | |
3730 def gethtml(self, link): | |
3731 "Get the HTML code for the link" | |
3732 type = link.__class__.__name__ | |
3733 if link.type: | |
3734 type = link.type | |
3735 tag = 'a class="' + type + '"' | |
3736 if link.anchor: | |
3737 tag += ' name="' + link.anchor + '"' | |
3738 if link.destination: | |
3739 link.computedestination() | |
3740 if link.url: | |
3741 tag += ' href="' + link.url + '"' | |
3742 if link.target: | |
3743 tag += ' target="' + link.target + '"' | |
3744 if link.title: | |
3745 tag += ' title="' + link.title + '"' | |
3746 return TaggedOutput().settag(tag).gethtml(link) | |
3747 | |
3748 | |
3749 | |
3750 | |
3751 | |
3752 class Postprocessor(object): | |
3753 "Postprocess a container keeping some context" | |
3754 | |
3755 stages = [] | |
3756 | |
3757 def __init__(self): | |
3758 self.stages = StageDict(Postprocessor.stages, self) | |
3759 self.current = None | |
3760 self.last = None | |
3761 | |
3762 def postprocess(self, next): | |
3763 "Postprocess a container and its contents." | |
3764 self.postrecursive(self.current) | |
3765 result = self.postcurrent(next) | |
3766 self.last = self.current | |
3767 self.current = next | |
3768 return result | |
3769 | |
3770 def postrecursive(self, container): | |
3771 "Postprocess the container contents recursively" | |
3772 if not hasattr(container, 'contents'): | |
3773 return | |
3774 if len(container.contents) == 0: | |
3775 return | |
3776 if hasattr(container, 'postprocess'): | |
3777 if not container.postprocess: | |
3778 return | |
3779 postprocessor = Postprocessor() | |
3780 contents = [] | |
3781 for element in container.contents: | |
3782 post = postprocessor.postprocess(element) | |
3783 if post: | |
3784 contents.append(post) | |
3785 # two rounds to empty the pipeline | |
3786 for i in range(2): | |
3787 post = postprocessor.postprocess(None) | |
3788 if post: | |
3789 contents.append(post) | |
3790 container.contents = contents | |
3791 | |
3792 def postcurrent(self, next): | |
3793 "Postprocess the current element taking into account next and last." | |
3794 stage = self.stages.getstage(self.current) | |
3795 if not stage: | |
3796 return self.current | |
3797 return stage.postprocess(self.last, self.current, next) | |
3798 | |
3799 class StageDict(object): | |
3800 "A dictionary of stages corresponding to classes" | |
3801 | |
3802 def __init__(self, classes, postprocessor): | |
3803 "Instantiate an element from elyxer.each class and store as a dictionary" | |
3804 instances = self.instantiate(classes, postprocessor) | |
3805 self.stagedict = dict([(x.processedclass, x) for x in instances]) | |
3806 | |
3807 def instantiate(self, classes, postprocessor): | |
3808 "Instantiate an element from elyxer.each class" | |
3809 stages = [x.__new__(x) for x in classes] | |
3810 for element in stages: | |
3811 element.__init__() | |
3812 element.postprocessor = postprocessor | |
3813 return stages | |
3814 | |
3815 def getstage(self, element): | |
3816 "Get the stage for a given element, if the type is in the dict" | |
3817 if not element.__class__ in self.stagedict: | |
3818 return None | |
3819 return self.stagedict[element.__class__] | |
3820 | |
3821 | |
3822 | |
3823 class Label(Link): | |
3824 "A label to be referenced" | |
3825 | |
3826 names = dict() | |
3827 lastlayout = None | |
3828 | |
3829 def __init__(self): | |
3830 Link.__init__(self) | |
3831 self.lastnumbered = None | |
3832 | |
3833 def process(self): | |
3834 "Process a label container." | |
3835 key = self.getparameter('name') | |
3836 self.create(' ', key) | |
3837 self.lastnumbered = Label.lastlayout | |
3838 | |
3839 def create(self, text, key, type = 'Label'): | |
3840 "Create the label for a given key." | |
3841 self.key = key | |
3842 self.complete(text, anchor = key, type = type) | |
3843 Label.names[key] = self | |
3844 if key in Reference.references: | |
3845 for reference in Reference.references[key]: | |
3846 reference.destination = self | |
3847 return self | |
3848 | |
3849 def findpartkey(self): | |
3850 "Get the part key for the latest numbered container seen." | |
3851 numbered = self.numbered(self) | |
3852 if numbered and numbered.partkey: | |
3853 return numbered.partkey | |
3854 return '' | |
3855 | |
3856 def numbered(self, container): | |
3857 "Get the numbered container for the label." | |
3858 if container.partkey: | |
3859 return container | |
3860 if not container.parent: | |
3861 if self.lastnumbered: | |
3862 return self.lastnumbered | |
3863 return None | |
3864 return self.numbered(container.parent) | |
3865 | |
3866 def __unicode__(self): | |
3867 "Return a printable representation." | |
3868 if not hasattr(self, 'key'): | |
3869 return 'Unnamed label' | |
3870 return 'Label ' + self.key | |
3871 | |
3872 class Reference(Link): | |
3873 "A reference to a label." | |
3874 | |
3875 references = dict() | |
3876 key = 'none' | |
3877 | |
3878 def process(self): | |
3879 "Read the reference and set the arrow." | |
3880 self.key = self.getparameter('reference') | |
3881 if self.key in Label.names: | |
3882 self.direction = u'↑' | |
3883 label = Label.names[self.key] | |
3884 else: | |
3885 self.direction = u'↓' | |
3886 label = Label().complete(' ', self.key, 'preref') | |
3887 self.destination = label | |
3888 self.formatcontents() | |
3889 if not self.key in Reference.references: | |
3890 Reference.references[self.key] = [] | |
3891 Reference.references[self.key].append(self) | |
3892 | |
3893 def formatcontents(self): | |
3894 "Format the reference contents." | |
3895 formatkey = self.getparameter('LatexCommand') | |
3896 if not formatkey: | |
3897 formatkey = 'ref' | |
3898 self.formatted = u'↕' | |
3899 if formatkey in StyleConfig.referenceformats: | |
3900 self.formatted = StyleConfig.referenceformats[formatkey] | |
3901 else: | |
3902 Trace.error('Unknown reference format ' + formatkey) | |
3903 self.replace(u'↕', self.direction) | |
3904 self.replace('#', '1') | |
3905 self.replace('on-page', Translator.translate('on-page')) | |
3906 partkey = self.destination.findpartkey() | |
3907 # only if partkey and partkey.number are not null, send partkey.number | |
3908 self.replace('@', partkey and partkey.number) | |
3909 self.replace(u'¶', partkey and partkey.tocentry) | |
3910 if not '$' in self.formatted or not partkey or not partkey.titlecontents: | |
3911 if '$' in self.formatted: | |
3912 Trace.error('No title in ' + unicode(partkey)) | |
3913 self.contents = [Constant(self.formatted)] | |
3914 return | |
3915 pieces = self.formatted.split('$') | |
3916 self.contents = [Constant(pieces[0])] | |
3917 for piece in pieces[1:]: | |
3918 self.contents += partkey.titlecontents | |
3919 self.contents.append(Constant(piece)) | |
3920 | |
3921 def replace(self, key, value): | |
3922 "Replace a key in the format template with a value." | |
3923 if not key in self.formatted: | |
3924 return | |
3925 if not value: | |
3926 value = '' | |
3927 self.formatted = self.formatted.replace(key, value) | |
3928 | |
3929 def __unicode__(self): | |
3930 "Return a printable representation." | |
3931 return 'Reference ' + self.key | |
3932 | |
3933 | |
3934 | |
3935 class FormulaCommand(FormulaBit): | |
3936 "A LaTeX command inside a formula" | |
3937 | |
3938 types = [] | |
3939 start = FormulaConfig.starts['command'] | |
3940 commandmap = None | |
3941 | |
3942 def detect(self, pos): | |
3943 "Find the current command." | |
3944 return pos.checkfor(FormulaCommand.start) | |
3945 | |
3946 def parsebit(self, pos): | |
3947 "Parse the command." | |
3948 command = self.extractcommand(pos) | |
3949 bit = self.parsewithcommand(command, pos) | |
3950 if bit: | |
3951 return bit | |
3952 if command.startswith('\\up') or command.startswith('\\Up'): | |
3953 upgreek = self.parseupgreek(command, pos) | |
3954 if upgreek: | |
3955 return upgreek | |
3956 if not self.factory.defining: | |
3957 Trace.error('Unknown command ' + command) | |
3958 self.output = TaggedOutput().settag('span class="unknown"') | |
3959 self.add(FormulaConstant(command)) | |
3960 return None | |
3961 | |
3962 def parsewithcommand(self, command, pos): | |
3963 "Parse the command type once we have the command." | |
3964 for type in FormulaCommand.types: | |
3965 if command in type.commandmap: | |
3966 return self.parsecommandtype(command, type, pos) | |
3967 return None | |
3968 | |
3969 def parsecommandtype(self, command, type, pos): | |
3970 "Parse a given command type." | |
3971 bit = self.factory.create(type) | |
3972 bit.setcommand(command) | |
3973 returned = bit.parsebit(pos) | |
3974 if returned: | |
3975 return returned | |
3976 return bit | |
3977 | |
3978 def extractcommand(self, pos): | |
3979 "Extract the command from elyxer.the current position." | |
3980 if not pos.checkskip(FormulaCommand.start): | |
3981 pos.error('Missing command start ' + FormulaCommand.start) | |
3982 return | |
3983 if pos.finished(): | |
3984 return self.emptycommand(pos) | |
3985 if pos.current().isalpha(): | |
3986 # alpha command | |
3987 command = FormulaCommand.start + pos.globalpha() | |
3988 # skip mark of short command | |
3989 pos.checkskip('*') | |
3990 return command | |
3991 # symbol command | |
3992 return FormulaCommand.start + pos.skipcurrent() | |
3993 | |
3994 def emptycommand(self, pos): | |
3995 """Check for an empty command: look for command disguised as ending. | |
3996 Special case against '{ \{ \} }' situation.""" | |
3997 command = '' | |
3998 if not pos.isout(): | |
3999 ending = pos.nextending() | |
4000 if ending and pos.checkskip(ending): | |
4001 command = ending | |
4002 return FormulaCommand.start + command | |
4003 | |
4004 def parseupgreek(self, command, pos): | |
4005 "Parse the Greek \\up command.." | |
4006 if len(command) < 4: | |
4007 return None | |
4008 if command.startswith('\\up'): | |
4009 upcommand = '\\' + command[3:] | |
4010 elif pos.checkskip('\\Up'): | |
4011 upcommand = '\\' + command[3:4].upper() + command[4:] | |
4012 else: | |
4013 Trace.error('Impossible upgreek command: ' + command) | |
4014 return | |
4015 upgreek = self.parsewithcommand(upcommand, pos) | |
4016 if upgreek: | |
4017 upgreek.type = 'font' | |
4018 return upgreek | |
4019 | |
4020 class CommandBit(FormulaCommand): | |
4021 "A formula bit that includes a command" | |
4022 | |
4023 def setcommand(self, command): | |
4024 "Set the command in the bit" | |
4025 self.command = command | |
4026 if self.commandmap: | |
4027 self.original += command | |
4028 self.translated = self.commandmap[self.command] | |
4029 | |
4030 def parseparameter(self, pos): | |
4031 "Parse a parameter at the current position" | |
4032 self.factory.clearskipped(pos) | |
4033 if pos.finished(): | |
4034 return None | |
4035 parameter = self.factory.parseany(pos) | |
4036 self.add(parameter) | |
4037 return parameter | |
4038 | |
4039 def parsesquare(self, pos): | |
4040 "Parse a square bracket" | |
4041 self.factory.clearskipped(pos) | |
4042 if not self.factory.detecttype(SquareBracket, pos): | |
4043 return None | |
4044 bracket = self.factory.parsetype(SquareBracket, pos) | |
4045 self.add(bracket) | |
4046 return bracket | |
4047 | |
4048 def parseliteral(self, pos): | |
4049 "Parse a literal bracket." | |
4050 self.factory.clearskipped(pos) | |
4051 if not self.factory.detecttype(Bracket, pos): | |
4052 if not pos.isvalue(): | |
4053 Trace.error('No literal parameter found at: ' + pos.identifier()) | |
4054 return None | |
4055 return pos.globvalue() | |
4056 bracket = Bracket().setfactory(self.factory) | |
4057 self.add(bracket.parseliteral(pos)) | |
4058 return bracket.literal | |
4059 | |
4060 def parsesquareliteral(self, pos): | |
4061 "Parse a square bracket literally." | |
4062 self.factory.clearskipped(pos) | |
4063 if not self.factory.detecttype(SquareBracket, pos): | |
4064 return None | |
4065 bracket = SquareBracket().setfactory(self.factory) | |
4066 self.add(bracket.parseliteral(pos)) | |
4067 return bracket.literal | |
4068 | |
4069 def parsetext(self, pos): | |
4070 "Parse a text parameter." | |
4071 self.factory.clearskipped(pos) | |
4072 if not self.factory.detecttype(Bracket, pos): | |
4073 Trace.error('No text parameter for ' + self.command) | |
4074 return None | |
4075 bracket = Bracket().setfactory(self.factory).parsetext(pos) | |
4076 self.add(bracket) | |
4077 return bracket | |
4078 | |
4079 class EmptyCommand(CommandBit): | |
4080 "An empty command (without parameters)" | |
4081 | |
4082 commandmap = FormulaConfig.commands | |
4083 | |
4084 def parsebit(self, pos): | |
4085 "Parse a command without parameters" | |
4086 self.contents = [FormulaConstant(self.translated)] | |
4087 | |
4088 class SpacedCommand(CommandBit): | |
4089 "An empty command which should have math spacing in formulas." | |
4090 | |
4091 commandmap = FormulaConfig.spacedcommands | |
4092 | |
4093 def parsebit(self, pos): | |
4094 "Place as contents the command translated and spaced." | |
4095 self.contents = [FormulaConstant(u' ' + self.translated + u' ')] | |
4096 | |
4097 class AlphaCommand(EmptyCommand): | |
4098 "A command without paramters whose result is alphabetical" | |
4099 | |
4100 commandmap = FormulaConfig.alphacommands | |
4101 | |
4102 def parsebit(self, pos): | |
4103 "Parse the command and set type to alpha" | |
4104 EmptyCommand.parsebit(self, pos) | |
4105 self.type = 'alpha' | |
4106 | |
4107 class OneParamFunction(CommandBit): | |
4108 "A function of one parameter" | |
4109 | |
4110 commandmap = FormulaConfig.onefunctions | |
4111 simplified = False | |
4112 | |
4113 def parsebit(self, pos): | |
4114 "Parse a function with one parameter" | |
4115 self.output = TaggedOutput().settag(self.translated) | |
4116 self.parseparameter(pos) | |
4117 self.simplifyifpossible() | |
4118 | |
4119 def simplifyifpossible(self): | |
4120 "Try to simplify to a single character." | |
4121 if self.original in self.commandmap: | |
4122 self.output = FixedOutput() | |
4123 self.html = [self.commandmap[self.original]] | |
4124 self.simplified = True | |
4125 | |
4126 class SymbolFunction(CommandBit): | |
4127 "Find a function which is represented by a symbol (like _ or ^)" | |
4128 | |
4129 commandmap = FormulaConfig.symbolfunctions | |
4130 | |
4131 def detect(self, pos): | |
4132 "Find the symbol" | |
4133 return pos.current() in SymbolFunction.commandmap | |
4134 | |
4135 def parsebit(self, pos): | |
4136 "Parse the symbol" | |
4137 self.setcommand(pos.current()) | |
4138 pos.skip(self.command) | |
4139 self.output = TaggedOutput().settag(self.translated) | |
4140 self.parseparameter(pos) | |
4141 | |
4142 class TextFunction(CommandBit): | |
4143 "A function where parameters are read as text." | |
4144 | |
4145 commandmap = FormulaConfig.textfunctions | |
4146 | |
4147 def parsebit(self, pos): | |
4148 "Parse a text parameter" | |
4149 self.output = TaggedOutput().settag(self.translated) | |
4150 self.parsetext(pos) | |
4151 | |
4152 def process(self): | |
4153 "Set the type to font" | |
4154 self.type = 'font' | |
4155 | |
4156 class LabelFunction(CommandBit): | |
4157 "A function that acts as a label" | |
4158 | |
4159 commandmap = FormulaConfig.labelfunctions | |
4160 | |
4161 def parsebit(self, pos): | |
4162 "Parse a literal parameter" | |
4163 self.key = self.parseliteral(pos) | |
4164 | |
4165 def process(self): | |
4166 "Add an anchor with the label contents." | |
4167 self.type = 'font' | |
4168 self.label = Label().create(' ', self.key, type = 'eqnumber') | |
4169 self.contents = [self.label] | |
4170 # store as a Label so we know it's been seen | |
4171 Label.names[self.key] = self.label | |
4172 | |
4173 class FontFunction(OneParamFunction): | |
4174 "A function of one parameter that changes the font" | |
4175 | |
4176 commandmap = FormulaConfig.fontfunctions | |
4177 | |
4178 def process(self): | |
4179 "Simplify if possible using a single character." | |
4180 self.type = 'font' | |
4181 self.simplifyifpossible() | |
4182 | |
4183 FormulaFactory.types += [FormulaCommand, SymbolFunction] | |
4184 FormulaCommand.types = [ | |
4185 AlphaCommand, EmptyCommand, OneParamFunction, FontFunction, LabelFunction, | |
4186 TextFunction, SpacedCommand, | |
4187 ] | |
4188 | |
4189 | |
4190 | |
4191 | |
4192 | |
4193 | |
4194 | |
4195 | |
4196 | |
4197 | |
4198 | |
4199 | |
4200 class BigSymbol(object): | |
4201 "A big symbol generator." | |
4202 | |
4203 symbols = FormulaConfig.bigsymbols | |
4204 | |
4205 def __init__(self, symbol): | |
4206 "Create the big symbol." | |
4207 self.symbol = symbol | |
4208 | |
4209 def getpieces(self): | |
4210 "Get an array with all pieces." | |
4211 if not self.symbol in self.symbols: | |
4212 return [self.symbol] | |
4213 if self.smalllimit(): | |
4214 return [self.symbol] | |
4215 return self.symbols[self.symbol] | |
4216 | |
4217 def smalllimit(self): | |
4218 "Decide if the limit should be a small, one-line symbol." | |
4219 if not DocumentParameters.displaymode: | |
4220 return True | |
4221 if len(self.symbols[self.symbol]) == 1: | |
4222 return True | |
4223 return Options.simplemath | |
4224 | |
4225 class BigBracket(BigSymbol): | |
4226 "A big bracket generator." | |
4227 | |
4228 def __init__(self, size, bracket, alignment='l'): | |
4229 "Set the size and symbol for the bracket." | |
4230 self.size = size | |
4231 self.original = bracket | |
4232 self.alignment = alignment | |
4233 self.pieces = None | |
4234 if bracket in FormulaConfig.bigbrackets: | |
4235 self.pieces = FormulaConfig.bigbrackets[bracket] | |
4236 | |
4237 def getpiece(self, index): | |
4238 "Return the nth piece for the bracket." | |
4239 function = getattr(self, 'getpiece' + unicode(len(self.pieces))) | |
4240 return function(index) | |
4241 | |
4242 def getpiece1(self, index): | |
4243 "Return the only piece for a single-piece bracket." | |
4244 return self.pieces[0] | |
4245 | |
4246 def getpiece3(self, index): | |
4247 "Get the nth piece for a 3-piece bracket: parenthesis or square bracket." | |
4248 if index == 0: | |
4249 return self.pieces[0] | |
4250 if index == self.size - 1: | |
4251 return self.pieces[-1] | |
4252 return self.pieces[1] | |
4253 | |
4254 def getpiece4(self, index): | |
4255 "Get the nth piece for a 4-piece bracket: curly bracket." | |
4256 if index == 0: | |
4257 return self.pieces[0] | |
4258 if index == self.size - 1: | |
4259 return self.pieces[3] | |
4260 if index == (self.size - 1)/2: | |
4261 return self.pieces[2] | |
4262 return self.pieces[1] | |
4263 | |
4264 def getcell(self, index): | |
4265 "Get the bracket piece as an array cell." | |
4266 piece = self.getpiece(index) | |
4267 span = 'span class="bracket align-' + self.alignment + '"' | |
4268 return TaggedBit().constant(piece, span) | |
4269 | |
4270 def getcontents(self): | |
4271 "Get the bracket as an array or as a single bracket." | |
4272 if self.size == 1 or not self.pieces: | |
4273 return self.getsinglebracket() | |
4274 rows = [] | |
4275 for index in range(self.size): | |
4276 cell = self.getcell(index) | |
4277 rows.append(TaggedBit().complete([cell], 'span class="arrayrow"')) | |
4278 return [TaggedBit().complete(rows, 'span class="array"')] | |
4279 | |
4280 def getsinglebracket(self): | |
4281 "Return the bracket as a single sign." | |
4282 if self.original == '.': | |
4283 return [TaggedBit().constant('', 'span class="emptydot"')] | |
4284 return [TaggedBit().constant(self.original, 'span class="symbol"')] | |
4285 | |
4286 | |
4287 | |
4288 | |
4289 | |
4290 | |
4291 class FormulaEquation(CommandBit): | |
4292 "A simple numbered equation." | |
4293 | |
4294 piece = 'equation' | |
4295 | |
4296 def parsebit(self, pos): | |
4297 "Parse the array" | |
4298 self.output = ContentsOutput() | |
4299 self.add(self.factory.parsetype(WholeFormula, pos)) | |
4300 | |
4301 class FormulaCell(FormulaCommand): | |
4302 "An array cell inside a row" | |
4303 | |
4304 def setalignment(self, alignment): | |
4305 self.alignment = alignment | |
4306 self.output = TaggedOutput().settag('span class="arraycell align-' + alignment +'"', True) | |
4307 return self | |
4308 | |
4309 def parsebit(self, pos): | |
4310 self.factory.clearskipped(pos) | |
4311 if pos.finished(): | |
4312 return | |
4313 self.add(self.factory.parsetype(WholeFormula, pos)) | |
4314 | |
4315 class FormulaRow(FormulaCommand): | |
4316 "An array row inside an array" | |
4317 | |
4318 cellseparator = FormulaConfig.array['cellseparator'] | |
4319 | |
4320 def setalignments(self, alignments): | |
4321 self.alignments = alignments | |
4322 self.output = TaggedOutput().settag('span class="arrayrow"', True) | |
4323 return self | |
4324 | |
4325 def parsebit(self, pos): | |
4326 "Parse a whole row" | |
4327 index = 0 | |
4328 pos.pushending(self.cellseparator, optional=True) | |
4329 while not pos.finished(): | |
4330 cell = self.createcell(index) | |
4331 cell.parsebit(pos) | |
4332 self.add(cell) | |
4333 index += 1 | |
4334 pos.checkskip(self.cellseparator) | |
4335 if len(self.contents) == 0: | |
4336 self.output = EmptyOutput() | |
4337 | |
4338 def createcell(self, index): | |
4339 "Create the cell that corresponds to the given index." | |
4340 alignment = self.alignments[index % len(self.alignments)] | |
4341 return self.factory.create(FormulaCell).setalignment(alignment) | |
4342 | |
4343 class MultiRowFormula(CommandBit): | |
4344 "A formula with multiple rows." | |
4345 | |
4346 def parserows(self, pos): | |
4347 "Parse all rows, finish when no more row ends" | |
4348 self.rows = [] | |
4349 first = True | |
4350 for row in self.iteraterows(pos): | |
4351 if first: | |
4352 first = False | |
4353 else: | |
4354 # intersparse empty rows | |
4355 self.addempty() | |
4356 row.parsebit(pos) | |
4357 self.addrow(row) | |
4358 self.size = len(self.rows) | |
4359 | |
4360 def iteraterows(self, pos): | |
4361 "Iterate over all rows, end when no more row ends" | |
4362 rowseparator = FormulaConfig.array['rowseparator'] | |
4363 while True: | |
4364 pos.pushending(rowseparator, True) | |
4365 row = self.factory.create(FormulaRow) | |
4366 yield row.setalignments(self.alignments) | |
4367 if pos.checkfor(rowseparator): | |
4368 self.original += pos.popending(rowseparator) | |
4369 else: | |
4370 return | |
4371 | |
4372 def addempty(self): | |
4373 "Add an empty row." | |
4374 row = self.factory.create(FormulaRow).setalignments(self.alignments) | |
4375 for index, originalcell in enumerate(self.rows[-1].contents): | |
4376 cell = row.createcell(index) | |
4377 cell.add(FormulaConstant(u' ')) | |
4378 row.add(cell) | |
4379 self.addrow(row) | |
4380 | |
4381 def addrow(self, row): | |
4382 "Add a row to the contents and to the list of rows." | |
4383 self.rows.append(row) | |
4384 self.add(row) | |
4385 | |
4386 class FormulaArray(MultiRowFormula): | |
4387 "An array within a formula" | |
4388 | |
4389 piece = 'array' | |
4390 | |
4391 def parsebit(self, pos): | |
4392 "Parse the array" | |
4393 self.output = TaggedOutput().settag('span class="array"', False) | |
4394 self.parsealignments(pos) | |
4395 self.parserows(pos) | |
4396 | |
4397 def parsealignments(self, pos): | |
4398 "Parse the different alignments" | |
4399 # vertical | |
4400 self.valign = 'c' | |
4401 literal = self.parsesquareliteral(pos) | |
4402 if literal: | |
4403 self.valign = literal | |
4404 # horizontal | |
4405 literal = self.parseliteral(pos) | |
4406 self.alignments = [] | |
4407 for l in literal: | |
4408 self.alignments.append(l) | |
4409 | |
4410 class FormulaMatrix(MultiRowFormula): | |
4411 "A matrix (array with center alignment)." | |
4412 | |
4413 piece = 'matrix' | |
4414 | |
4415 def parsebit(self, pos): | |
4416 "Parse the matrix, set alignments to 'c'." | |
4417 self.output = TaggedOutput().settag('span class="array"', False) | |
4418 self.valign = 'c' | |
4419 self.alignments = ['c'] | |
4420 self.parserows(pos) | |
4421 | |
4422 class FormulaCases(MultiRowFormula): | |
4423 "A cases statement" | |
4424 | |
4425 piece = 'cases' | |
4426 | |
4427 def parsebit(self, pos): | |
4428 "Parse the cases" | |
4429 self.output = ContentsOutput() | |
4430 self.alignments = ['l', 'l'] | |
4431 self.parserows(pos) | |
4432 for row in self.contents: | |
4433 for cell in row.contents: | |
4434 cell.output.settag('span class="case align-l"', True) | |
4435 cell.contents.append(FormulaConstant(u' ')) | |
4436 array = TaggedBit().complete(self.contents, 'span class="bracketcases"', True) | |
4437 brace = BigBracket(len(self.contents), '{', 'l') | |
4438 self.contents = brace.getcontents() + [array] | |
4439 | |
4440 class EquationEnvironment(MultiRowFormula): | |
4441 "A \\begin{}...\\end equation environment with rows and cells." | |
4442 | |
4443 def parsebit(self, pos): | |
4444 "Parse the whole environment." | |
4445 self.output = TaggedOutput().settag('span class="environment"', False) | |
4446 environment = self.piece.replace('*', '') | |
4447 if environment in FormulaConfig.environments: | |
4448 self.alignments = FormulaConfig.environments[environment] | |
4449 else: | |
4450 Trace.error('Unknown equation environment ' + self.piece) | |
4451 self.alignments = ['l'] | |
4452 self.parserows(pos) | |
4453 | |
4454 class BeginCommand(CommandBit): | |
4455 "A \\begin{}...\end command and what it entails (array, cases, aligned)" | |
4456 | |
4457 commandmap = {FormulaConfig.array['begin']:''} | |
4458 | |
4459 types = [FormulaEquation, FormulaArray, FormulaCases, FormulaMatrix] | |
4460 | |
4461 def parsebit(self, pos): | |
4462 "Parse the begin command" | |
4463 command = self.parseliteral(pos) | |
4464 bit = self.findbit(command) | |
4465 ending = FormulaConfig.array['end'] + '{' + command + '}' | |
4466 pos.pushending(ending) | |
4467 bit.parsebit(pos) | |
4468 self.add(bit) | |
4469 self.original += pos.popending(ending) | |
4470 self.size = bit.size | |
4471 | |
4472 def findbit(self, piece): | |
4473 "Find the command bit corresponding to the \\begin{piece}" | |
4474 for type in BeginCommand.types: | |
4475 if piece.replace('*', '') == type.piece: | |
4476 return self.factory.create(type) | |
4477 bit = self.factory.create(EquationEnvironment) | |
4478 bit.piece = piece | |
4479 return bit | |
4480 | |
4481 FormulaCommand.types += [BeginCommand] | |
4482 | |
4483 | |
4484 | |
4485 class CombiningFunction(OneParamFunction): | |
4486 | |
4487 commandmap = FormulaConfig.combiningfunctions | |
4488 | |
4489 def parsebit(self, pos): | |
4490 "Parse a combining function." | |
4491 self.type = 'alpha' | |
4492 combining = self.translated | |
4493 parameter = self.parsesingleparameter(pos) | |
4494 if not parameter: | |
4495 Trace.error('Empty parameter for combining function ' + self.command) | |
4496 elif len(parameter.extracttext()) != 1: | |
4497 Trace.error('Applying combining function ' + self.command + ' to invalid string "' + parameter.extracttext() + '"') | |
4498 self.contents.append(Constant(combining)) | |
4499 | |
4500 def parsesingleparameter(self, pos): | |
4501 "Parse a parameter, or a single letter." | |
4502 self.factory.clearskipped(pos) | |
4503 if pos.finished(): | |
4504 Trace.error('Error while parsing single parameter at ' + pos.identifier()) | |
4505 return None | |
4506 if self.factory.detecttype(Bracket, pos) \ | |
4507 or self.factory.detecttype(FormulaCommand, pos): | |
4508 return self.parseparameter(pos) | |
4509 letter = FormulaConstant(pos.skipcurrent()) | |
4510 self.add(letter) | |
4511 return letter | |
4512 | |
4513 class DecoratingFunction(OneParamFunction): | |
4514 "A function that decorates some bit of text" | |
4515 | |
4516 commandmap = FormulaConfig.decoratingfunctions | |
4517 | |
4518 def parsebit(self, pos): | |
4519 "Parse a decorating function" | |
4520 self.type = 'alpha' | |
4521 symbol = self.translated | |
4522 self.symbol = TaggedBit().constant(symbol, 'span class="symbolover"') | |
4523 self.parameter = self.parseparameter(pos) | |
4524 self.output = TaggedOutput().settag('span class="withsymbol"') | |
4525 self.contents.insert(0, self.symbol) | |
4526 self.parameter.output = TaggedOutput().settag('span class="undersymbol"') | |
4527 self.simplifyifpossible() | |
4528 | |
4529 class LimitCommand(EmptyCommand): | |
4530 "A command which accepts limits above and below, in display mode." | |
4531 | |
4532 commandmap = FormulaConfig.limitcommands | |
4533 | |
4534 def parsebit(self, pos): | |
4535 "Parse a limit command." | |
4536 pieces = BigSymbol(self.translated).getpieces() | |
4537 self.output = TaggedOutput().settag('span class="limits"') | |
4538 for piece in pieces: | |
4539 self.contents.append(TaggedBit().constant(piece, 'span class="limit"')) | |
4540 | |
4541 class LimitPreviousCommand(LimitCommand): | |
4542 "A command to limit the previous command." | |
4543 | |
4544 commandmap = None | |
4545 | |
4546 def parsebit(self, pos): | |
4547 "Do nothing." | |
4548 self.output = TaggedOutput().settag('span class="limits"') | |
4549 self.factory.clearskipped(pos) | |
4550 | |
4551 def __unicode__(self): | |
4552 "Return a printable representation." | |
4553 return 'Limit previous command' | |
4554 | |
4555 class LimitsProcessor(MathsProcessor): | |
4556 "A processor for limits inside an element." | |
4557 | |
4558 def process(self, contents, index): | |
4559 "Process the limits for an element." | |
4560 if Options.simplemath: | |
4561 return | |
4562 if self.checklimits(contents, index): | |
4563 self.modifylimits(contents, index) | |
4564 if self.checkscript(contents, index) and self.checkscript(contents, index + 1): | |
4565 self.modifyscripts(contents, index) | |
4566 | |
4567 def checklimits(self, contents, index): | |
4568 "Check if the current position has a limits command." | |
4569 if not DocumentParameters.displaymode: | |
4570 return False | |
4571 if self.checkcommand(contents, index + 1, LimitPreviousCommand): | |
4572 self.limitsahead(contents, index) | |
4573 return False | |
4574 if not isinstance(contents[index], LimitCommand): | |
4575 return False | |
4576 return self.checkscript(contents, index + 1) | |
4577 | |
4578 def limitsahead(self, contents, index): | |
4579 "Limit the current element based on the next." | |
4580 contents[index + 1].add(contents[index].clone()) | |
4581 contents[index].output = EmptyOutput() | |
4582 | |
4583 def modifylimits(self, contents, index): | |
4584 "Modify a limits commands so that the limits appear above and below." | |
4585 limited = contents[index] | |
4586 subscript = self.getlimit(contents, index + 1) | |
4587 limited.contents.append(subscript) | |
4588 if self.checkscript(contents, index + 1): | |
4589 superscript = self.getlimit(contents, index + 1) | |
4590 else: | |
4591 superscript = TaggedBit().constant(u' ', 'sup class="limit"') | |
4592 limited.contents.insert(0, superscript) | |
4593 | |
4594 def getlimit(self, contents, index): | |
4595 "Get the limit for a limits command." | |
4596 limit = self.getscript(contents, index) | |
4597 limit.output.tag = limit.output.tag.replace('script', 'limit') | |
4598 return limit | |
4599 | |
4600 def modifyscripts(self, contents, index): | |
4601 "Modify the super- and subscript to appear vertically aligned." | |
4602 subscript = self.getscript(contents, index) | |
4603 # subscript removed so instead of index + 1 we get index again | |
4604 superscript = self.getscript(contents, index) | |
4605 scripts = TaggedBit().complete([superscript, subscript], 'span class="scripts"') | |
4606 contents.insert(index, scripts) | |
4607 | |
4608 def checkscript(self, contents, index): | |
4609 "Check if the current element is a sub- or superscript." | |
4610 return self.checkcommand(contents, index, SymbolFunction) | |
4611 | |
4612 def checkcommand(self, contents, index, type): | |
4613 "Check for the given type as the current element." | |
4614 if len(contents) <= index: | |
4615 return False | |
4616 return isinstance(contents[index], type) | |
4617 | |
4618 def getscript(self, contents, index): | |
4619 "Get the sub- or superscript." | |
4620 bit = contents[index] | |
4621 bit.output.tag += ' class="script"' | |
4622 del contents[index] | |
4623 return bit | |
4624 | |
4625 class BracketCommand(OneParamFunction): | |
4626 "A command which defines a bracket." | |
4627 | |
4628 commandmap = FormulaConfig.bracketcommands | |
4629 | |
4630 def parsebit(self, pos): | |
4631 "Parse the bracket." | |
4632 OneParamFunction.parsebit(self, pos) | |
4633 | |
4634 def create(self, direction, character): | |
4635 "Create the bracket for the given character." | |
4636 self.original = character | |
4637 self.command = '\\' + direction | |
4638 self.contents = [FormulaConstant(character)] | |
4639 return self | |
4640 | |
4641 class BracketProcessor(MathsProcessor): | |
4642 "A processor for bracket commands." | |
4643 | |
4644 def process(self, contents, index): | |
4645 "Convert the bracket using Unicode pieces, if possible." | |
4646 if Options.simplemath: | |
4647 return | |
4648 if self.checkleft(contents, index): | |
4649 return self.processleft(contents, index) | |
4650 | |
4651 def processleft(self, contents, index): | |
4652 "Process a left bracket." | |
4653 rightindex = self.findright(contents, index + 1) | |
4654 if not rightindex: | |
4655 return | |
4656 size = self.findmax(contents, index, rightindex) | |
4657 self.resize(contents[index], size) | |
4658 self.resize(contents[rightindex], size) | |
4659 | |
4660 def checkleft(self, contents, index): | |
4661 "Check if the command at the given index is left." | |
4662 return self.checkdirection(contents[index], '\\left') | |
4663 | |
4664 def checkright(self, contents, index): | |
4665 "Check if the command at the given index is right." | |
4666 return self.checkdirection(contents[index], '\\right') | |
4667 | |
4668 def checkdirection(self, bit, command): | |
4669 "Check if the given bit is the desired bracket command." | |
4670 if not isinstance(bit, BracketCommand): | |
4671 return False | |
4672 return bit.command == command | |
4673 | |
4674 def findright(self, contents, index): | |
4675 "Find the right bracket starting at the given index, or 0." | |
4676 depth = 1 | |
4677 while index < len(contents): | |
4678 if self.checkleft(contents, index): | |
4679 depth += 1 | |
4680 if self.checkright(contents, index): | |
4681 depth -= 1 | |
4682 if depth == 0: | |
4683 return index | |
4684 index += 1 | |
4685 return None | |
4686 | |
4687 def findmax(self, contents, leftindex, rightindex): | |
4688 "Find the max size of the contents between the two given indices." | |
4689 sliced = contents[leftindex:rightindex] | |
4690 return max([element.size for element in sliced]) | |
4691 | |
4692 def resize(self, command, size): | |
4693 "Resize a bracket command to the given size." | |
4694 character = command.extracttext() | |
4695 alignment = command.command.replace('\\', '') | |
4696 bracket = BigBracket(size, character, alignment) | |
4697 command.output = ContentsOutput() | |
4698 command.contents = bracket.getcontents() | |
4699 | |
4700 | |
4701 FormulaCommand.types += [ | |
4702 DecoratingFunction, CombiningFunction, LimitCommand, BracketCommand, | |
4703 ] | |
4704 | |
4705 FormulaProcessor.processors += [ | |
4706 LimitsProcessor(), BracketProcessor(), | |
4707 ] | |
4708 | |
4709 | |
4710 | |
4711 class ParameterDefinition(object): | |
4712 "The definition of a parameter in a hybrid function." | |
4713 "[] parameters are optional, {} parameters are mandatory." | |
4714 "Each parameter has a one-character name, like {$1} or {$p}." | |
4715 "A parameter that ends in ! like {$p!} is a literal." | |
4716 "Example: [$1]{$p!} reads an optional parameter $1 and a literal mandatory parameter p." | |
4717 | |
4718 parambrackets = [('[', ']'), ('{', '}')] | |
4719 | |
4720 def __init__(self): | |
4721 self.name = None | |
4722 self.literal = False | |
4723 self.optional = False | |
4724 self.value = None | |
4725 self.literalvalue = None | |
4726 | |
4727 def parse(self, pos): | |
4728 "Parse a parameter definition: [$0], {$x}, {$1!}..." | |
4729 for (opening, closing) in ParameterDefinition.parambrackets: | |
4730 if pos.checkskip(opening): | |
4731 if opening == '[': | |
4732 self.optional = True | |
4733 if not pos.checkskip('$'): | |
4734 Trace.error('Wrong parameter name, did you mean $' + pos.current() + '?') | |
4735 return None | |
4736 self.name = pos.skipcurrent() | |
4737 if pos.checkskip('!'): | |
4738 self.literal = True | |
4739 if not pos.checkskip(closing): | |
4740 Trace.error('Wrong parameter closing ' + pos.skipcurrent()) | |
4741 return None | |
4742 return self | |
4743 Trace.error('Wrong character in parameter template: ' + pos.skipcurrent()) | |
4744 return None | |
4745 | |
4746 def read(self, pos, function): | |
4747 "Read the parameter itself using the definition." | |
4748 if self.literal: | |
4749 if self.optional: | |
4750 self.literalvalue = function.parsesquareliteral(pos) | |
4751 else: | |
4752 self.literalvalue = function.parseliteral(pos) | |
4753 if self.literalvalue: | |
4754 self.value = FormulaConstant(self.literalvalue) | |
4755 elif self.optional: | |
4756 self.value = function.parsesquare(pos) | |
4757 else: | |
4758 self.value = function.parseparameter(pos) | |
4759 | |
4760 def __unicode__(self): | |
4761 "Return a printable representation." | |
4762 result = 'param ' + self.name | |
4763 if self.value: | |
4764 result += ': ' + unicode(self.value) | |
4765 else: | |
4766 result += ' (empty)' | |
4767 return result | |
4768 | |
4769 class ParameterFunction(CommandBit): | |
4770 "A function with a variable number of parameters defined in a template." | |
4771 "The parameters are defined as a parameter definition." | |
4772 | |
4773 def readparams(self, readtemplate, pos): | |
4774 "Read the params according to the template." | |
4775 self.params = dict() | |
4776 for paramdef in self.paramdefs(readtemplate): | |
4777 paramdef.read(pos, self) | |
4778 self.params['$' + paramdef.name] = paramdef | |
4779 | |
4780 def paramdefs(self, readtemplate): | |
4781 "Read each param definition in the template" | |
4782 pos = TextPosition(readtemplate) | |
4783 while not pos.finished(): | |
4784 paramdef = ParameterDefinition().parse(pos) | |
4785 if paramdef: | |
4786 yield paramdef | |
4787 | |
4788 def getparam(self, name): | |
4789 "Get a parameter as parsed." | |
4790 if not name in self.params: | |
4791 return None | |
4792 return self.params[name] | |
4793 | |
4794 def getvalue(self, name): | |
4795 "Get the value of a parameter." | |
4796 return self.getparam(name).value | |
4797 | |
4798 def getliteralvalue(self, name): | |
4799 "Get the literal value of a parameter." | |
4800 param = self.getparam(name) | |
4801 if not param or not param.literalvalue: | |
4802 return None | |
4803 return param.literalvalue | |
4804 | |
4805 class HybridFunction(ParameterFunction): | |
4806 """ | |
4807 A parameter function where the output is also defined using a template. | |
4808 The template can use a number of functions; each function has an associated | |
4809 tag. | |
4810 Example: [f0{$1},span class="fbox"] defines a function f0 which corresponds | |
4811 to a span of class fbox, yielding <span class="fbox">$1</span>. | |
4812 Literal parameters can be used in tags definitions: | |
4813 [f0{$1},span style="color: $p;"] | |
4814 yields <span style="color: $p;">$1</span>, where $p is a literal parameter. | |
4815 Sizes can be specified in hybridsizes, e.g. adding parameter sizes. By | |
4816 default the resulting size is the max of all arguments. Sizes are used | |
4817 to generate the right parameters. | |
4818 A function followed by a single / is output as a self-closing XHTML tag: | |
4819 [f0/,hr] | |
4820 will generate <hr/>. | |
4821 """ | |
4822 | |
4823 commandmap = FormulaConfig.hybridfunctions | |
4824 | |
4825 def parsebit(self, pos): | |
4826 "Parse a function with [] and {} parameters" | |
4827 readtemplate = self.translated[0] | |
4828 writetemplate = self.translated[1] | |
4829 self.readparams(readtemplate, pos) | |
4830 self.contents = self.writeparams(writetemplate) | |
4831 self.computehybridsize() | |
4832 | |
4833 def writeparams(self, writetemplate): | |
4834 "Write all params according to the template" | |
4835 return self.writepos(TextPosition(writetemplate)) | |
4836 | |
4837 def writepos(self, pos): | |
4838 "Write all params as read in the parse position." | |
4839 result = [] | |
4840 while not pos.finished(): | |
4841 if pos.checkskip('$'): | |
4842 param = self.writeparam(pos) | |
4843 if param: | |
4844 result.append(param) | |
4845 elif pos.checkskip('f'): | |
4846 function = self.writefunction(pos) | |
4847 if function: | |
4848 function.type = None | |
4849 result.append(function) | |
4850 elif pos.checkskip('('): | |
4851 result.append(self.writebracket('left', '(')) | |
4852 elif pos.checkskip(')'): | |
4853 result.append(self.writebracket('right', ')')) | |
4854 else: | |
4855 result.append(FormulaConstant(pos.skipcurrent())) | |
4856 return result | |
4857 | |
4858 def writeparam(self, pos): | |
4859 "Write a single param of the form $0, $x..." | |
4860 name = '$' + pos.skipcurrent() | |
4861 if not name in self.params: | |
4862 Trace.error('Unknown parameter ' + name) | |
4863 return None | |
4864 if not self.params[name]: | |
4865 return None | |
4866 if pos.checkskip('.'): | |
4867 self.params[name].value.type = pos.globalpha() | |
4868 return self.params[name].value | |
4869 | |
4870 def writefunction(self, pos): | |
4871 "Write a single function f0,...,fn." | |
4872 tag = self.readtag(pos) | |
4873 if not tag: | |
4874 return None | |
4875 if pos.checkskip('/'): | |
4876 # self-closing XHTML tag, such as <hr/> | |
4877 return TaggedBit().selfcomplete(tag) | |
4878 if not pos.checkskip('{'): | |
4879 Trace.error('Function should be defined in {}') | |
4880 return None | |
4881 pos.pushending('}') | |
4882 contents = self.writepos(pos) | |
4883 pos.popending() | |
4884 if len(contents) == 0: | |
4885 return None | |
4886 return TaggedBit().complete(contents, tag) | |
4887 | |
4888 def readtag(self, pos): | |
4889 "Get the tag corresponding to the given index. Does parameter substitution." | |
4890 if not pos.current().isdigit(): | |
4891 Trace.error('Function should be f0,...,f9: f' + pos.current()) | |
4892 return None | |
4893 index = int(pos.skipcurrent()) | |
4894 if 2 + index > len(self.translated): | |
4895 Trace.error('Function f' + unicode(index) + ' is not defined') | |
4896 return None | |
4897 tag = self.translated[2 + index] | |
4898 if not '$' in tag: | |
4899 return tag | |
4900 for variable in self.params: | |
4901 if variable in tag: | |
4902 param = self.params[variable] | |
4903 if not param.literal: | |
4904 Trace.error('Parameters in tag ' + tag + ' should be literal: {' + variable + '!}') | |
4905 continue | |
4906 if param.literalvalue: | |
4907 value = param.literalvalue | |
4908 else: | |
4909 value = '' | |
4910 tag = tag.replace(variable, value) | |
4911 return tag | |
4912 | |
4913 def writebracket(self, direction, character): | |
4914 "Return a new bracket looking at the given direction." | |
4915 return self.factory.create(BracketCommand).create(direction, character) | |
4916 | |
4917 def computehybridsize(self): | |
4918 "Compute the size of the hybrid function." | |
4919 if not self.command in HybridSize.configsizes: | |
4920 self.computesize() | |
4921 return | |
4922 self.size = HybridSize().getsize(self) | |
4923 # set the size in all elements at first level | |
4924 for element in self.contents: | |
4925 element.size = self.size | |
4926 | |
4927 class HybridSize(object): | |
4928 "The size associated with a hybrid function." | |
4929 | |
4930 configsizes = FormulaConfig.hybridsizes | |
4931 | |
4932 def getsize(self, function): | |
4933 "Read the size for a function and parse it." | |
4934 sizestring = self.configsizes[function.command] | |
4935 for name in function.params: | |
4936 if name in sizestring: | |
4937 size = function.params[name].value.computesize() | |
4938 sizestring = sizestring.replace(name, unicode(size)) | |
4939 if '$' in sizestring: | |
4940 Trace.error('Unconverted variable in hybrid size: ' + sizestring) | |
4941 return 1 | |
4942 return eval(sizestring) | |
4943 | |
4944 | |
4945 FormulaCommand.types += [HybridFunction] | |
4946 | |
4947 | |
4948 | |
4949 | |
4950 | |
4951 | |
4952 | |
4953 | |
4954 | |
4955 class HeaderParser(Parser): | |
4956 "Parses the LyX header" | |
4957 | |
4958 def parse(self, reader): | |
4959 "Parse header parameters into a dictionary, return the preamble." | |
4960 contents = [] | |
4961 self.parseending(reader, lambda: self.parseline(reader, contents)) | |
4962 # skip last line | |
4963 reader.nextline() | |
4964 return contents | |
4965 | |
4966 def parseline(self, reader, contents): | |
4967 "Parse a single line as a parameter or as a start" | |
4968 line = reader.currentline() | |
4969 if line.startswith(HeaderConfig.parameters['branch']): | |
4970 self.parsebranch(reader) | |
4971 return | |
4972 elif line.startswith(HeaderConfig.parameters['lstset']): | |
4973 LstParser().parselstset(reader) | |
4974 return | |
4975 elif line.startswith(HeaderConfig.parameters['beginpreamble']): | |
4976 contents.append(self.factory.createcontainer(reader)) | |
4977 return | |
4978 # no match | |
4979 self.parseparameter(reader) | |
4980 | |
4981 def parsebranch(self, reader): | |
4982 "Parse all branch definitions." | |
4983 branch = reader.currentline().split()[1] | |
4984 reader.nextline() | |
4985 subparser = HeaderParser().complete(HeaderConfig.parameters['endbranch']) | |
4986 subparser.parse(reader) | |
4987 options = BranchOptions(branch) | |
4988 for key in subparser.parameters: | |
4989 options.set(key, subparser.parameters[key]) | |
4990 Options.branches[branch] = options | |
4991 | |
4992 def complete(self, ending): | |
4993 "Complete the parser with the given ending." | |
4994 self.ending = ending | |
4995 return self | |
4996 | |
4997 class PreambleParser(Parser): | |
4998 "A parser for the LyX preamble." | |
4999 | |
5000 preamble = [] | |
5001 | |
5002 def parse(self, reader): | |
5003 "Parse the full preamble with all statements." | |
5004 self.ending = HeaderConfig.parameters['endpreamble'] | |
5005 self.parseending(reader, lambda: self.parsepreambleline(reader)) | |
5006 return [] | |
5007 | |
5008 def parsepreambleline(self, reader): | |
5009 "Parse a single preamble line." | |
5010 PreambleParser.preamble.append(reader.currentline()) | |
5011 reader.nextline() | |
5012 | |
5013 class LstParser(object): | |
5014 "Parse global and local lstparams." | |
5015 | |
5016 globalparams = dict() | |
5017 | |
5018 def parselstset(self, reader): | |
5019 "Parse a declaration of lstparams in lstset." | |
5020 paramtext = self.extractlstset(reader) | |
5021 if not '{' in paramtext: | |
5022 Trace.error('Missing opening bracket in lstset: ' + paramtext) | |
5023 return | |
5024 lefttext = paramtext.split('{')[1] | |
5025 croppedtext = lefttext[:-1] | |
5026 LstParser.globalparams = self.parselstparams(croppedtext) | |
5027 | |
5028 def extractlstset(self, reader): | |
5029 "Extract the global lstset parameters." | |
5030 paramtext = '' | |
5031 while not reader.finished(): | |
5032 paramtext += reader.currentline() | |
5033 reader.nextline() | |
5034 if paramtext.endswith('}'): | |
5035 return paramtext | |
5036 Trace.error('Could not find end of \\lstset settings; aborting') | |
5037 | |
5038 def parsecontainer(self, container): | |
5039 "Parse some lstparams from elyxer.a container." | |
5040 container.lstparams = LstParser.globalparams.copy() | |
5041 paramlist = container.getparameterlist('lstparams') | |
5042 container.lstparams.update(self.parselstparams(paramlist)) | |
5043 | |
5044 def parselstparams(self, paramlist): | |
5045 "Process a number of lstparams from elyxer.a list." | |
5046 paramdict = dict() | |
5047 for param in paramlist: | |
5048 if not '=' in param: | |
5049 if len(param.strip()) > 0: | |
5050 Trace.error('Invalid listing parameter ' + param) | |
5051 else: | |
5052 key, value = param.split('=', 1) | |
5053 paramdict[key] = value | |
5054 return paramdict | |
5055 | |
5056 | |
5057 | |
5058 | |
5059 class MacroDefinition(CommandBit): | |
5060 "A function that defines a new command (a macro)." | |
5061 | |
5062 macros = dict() | |
5063 | |
5064 def parsebit(self, pos): | |
5065 "Parse the function that defines the macro." | |
5066 self.output = EmptyOutput() | |
5067 self.parameternumber = 0 | |
5068 self.defaults = [] | |
5069 self.factory.defining = True | |
5070 self.parseparameters(pos) | |
5071 self.factory.defining = False | |
5072 Trace.debug('New command ' + self.newcommand + ' (' + \ | |
5073 unicode(self.parameternumber) + ' parameters)') | |
5074 self.macros[self.newcommand] = self | |
5075 | |
5076 def parseparameters(self, pos): | |
5077 "Parse all optional parameters (number of parameters, default values)" | |
5078 "and the mandatory definition." | |
5079 self.newcommand = self.parsenewcommand(pos) | |
5080 # parse number of parameters | |
5081 literal = self.parsesquareliteral(pos) | |
5082 if literal: | |
5083 self.parameternumber = int(literal) | |
5084 # parse all default values | |
5085 bracket = self.parsesquare(pos) | |
5086 while bracket: | |
5087 self.defaults.append(bracket) | |
5088 bracket = self.parsesquare(pos) | |
5089 # parse mandatory definition | |
5090 self.definition = self.parseparameter(pos) | |
5091 | |
5092 def parsenewcommand(self, pos): | |
5093 "Parse the name of the new command." | |
5094 self.factory.clearskipped(pos) | |
5095 if self.factory.detecttype(Bracket, pos): | |
5096 return self.parseliteral(pos) | |
5097 if self.factory.detecttype(FormulaCommand, pos): | |
5098 return self.factory.create(FormulaCommand).extractcommand(pos) | |
5099 Trace.error('Unknown formula bit in defining function at ' + pos.identifier()) | |
5100 return 'unknown' | |
5101 | |
5102 def instantiate(self): | |
5103 "Return an instance of the macro." | |
5104 return self.definition.clone() | |
5105 | |
5106 class MacroParameter(FormulaBit): | |
5107 "A parameter from elyxer.a macro." | |
5108 | |
5109 def detect(self, pos): | |
5110 "Find a macro parameter: #n." | |
5111 return pos.checkfor('#') | |
5112 | |
5113 def parsebit(self, pos): | |
5114 "Parse the parameter: #n." | |
5115 if not pos.checkskip('#'): | |
5116 Trace.error('Missing parameter start #.') | |
5117 return | |
5118 self.number = int(pos.skipcurrent()) | |
5119 self.original = '#' + unicode(self.number) | |
5120 self.contents = [TaggedBit().constant('#' + unicode(self.number), 'span class="unknown"')] | |
5121 | |
5122 class MacroFunction(CommandBit): | |
5123 "A function that was defined using a macro." | |
5124 | |
5125 commandmap = MacroDefinition.macros | |
5126 | |
5127 def parsebit(self, pos): | |
5128 "Parse a number of input parameters." | |
5129 self.output = FilteredOutput() | |
5130 self.values = [] | |
5131 macro = self.translated | |
5132 self.parseparameters(pos, macro) | |
5133 self.completemacro(macro) | |
5134 | |
5135 def parseparameters(self, pos, macro): | |
5136 "Parse as many parameters as are needed." | |
5137 self.parseoptional(pos, list(macro.defaults)) | |
5138 self.parsemandatory(pos, macro.parameternumber - len(macro.defaults)) | |
5139 if len(self.values) < macro.parameternumber: | |
5140 Trace.error('Missing parameters in macro ' + unicode(self)) | |
5141 | |
5142 def parseoptional(self, pos, defaults): | |
5143 "Parse optional parameters." | |
5144 optional = [] | |
5145 while self.factory.detecttype(SquareBracket, pos): | |
5146 optional.append(self.parsesquare(pos)) | |
5147 if len(optional) > len(defaults): | |
5148 break | |
5149 for value in optional: | |
5150 default = defaults.pop() | |
5151 if len(value.contents) > 0: | |
5152 self.values.append(value) | |
5153 else: | |
5154 self.values.append(default) | |
5155 self.values += defaults | |
5156 | |
5157 def parsemandatory(self, pos, number): | |
5158 "Parse a number of mandatory parameters." | |
5159 for index in range(number): | |
5160 parameter = self.parsemacroparameter(pos, number - index) | |
5161 if not parameter: | |
5162 return | |
5163 self.values.append(parameter) | |
5164 | |
5165 def parsemacroparameter(self, pos, remaining): | |
5166 "Parse a macro parameter. Could be a bracket or a single letter." | |
5167 "If there are just two values remaining and there is a running number," | |
5168 "parse as two separater numbers." | |
5169 self.factory.clearskipped(pos) | |
5170 if pos.finished(): | |
5171 return None | |
5172 if self.factory.detecttype(FormulaNumber, pos): | |
5173 return self.parsenumbers(pos, remaining) | |
5174 return self.parseparameter(pos) | |
5175 | |
5176 def parsenumbers(self, pos, remaining): | |
5177 "Parse the remaining parameters as a running number." | |
5178 "For example, 12 would be {1}{2}." | |
5179 number = self.factory.parsetype(FormulaNumber, pos) | |
5180 if not len(number.original) == remaining: | |
5181 return number | |
5182 for digit in number.original: | |
5183 value = self.factory.create(FormulaNumber) | |
5184 value.add(FormulaConstant(digit)) | |
5185 value.type = number | |
5186 self.values.append(value) | |
5187 return None | |
5188 | |
5189 def completemacro(self, macro): | |
5190 "Complete the macro with the parameters read." | |
5191 self.contents = [macro.instantiate()] | |
5192 replaced = [False] * len(self.values) | |
5193 for parameter in self.searchall(MacroParameter): | |
5194 index = parameter.number - 1 | |
5195 if index >= len(self.values): | |
5196 Trace.error('Macro parameter index out of bounds: ' + unicode(index)) | |
5197 return | |
5198 replaced[index] = True | |
5199 parameter.contents = [self.values[index].clone()] | |
5200 for index in range(len(self.values)): | |
5201 if not replaced[index]: | |
5202 self.addfilter(index, self.values[index]) | |
5203 | |
5204 def addfilter(self, index, value): | |
5205 "Add a filter for the given parameter number and parameter value." | |
5206 original = '#' + unicode(index + 1) | |
5207 value = ''.join(self.values[0].gethtml()) | |
5208 self.output.addfilter(original, value) | |
5209 | |
5210 class FormulaMacro(Formula): | |
5211 "A math macro defined in an inset." | |
5212 | |
5213 def __init__(self): | |
5214 self.parser = MacroParser() | |
5215 self.output = EmptyOutput() | |
5216 | |
5217 def __unicode__(self): | |
5218 "Return a printable representation." | |
5219 return 'Math macro' | |
5220 | |
5221 FormulaFactory.types += [ MacroParameter ] | |
5222 | |
5223 FormulaCommand.types += [ | |
5224 MacroFunction, | |
5225 ] | |
5226 | |
5227 | |
5228 | |
5229 def math2html(formula): | |
5230 "Convert some TeX math to HTML." | |
5231 factory = FormulaFactory() | |
5232 whole = factory.parseformula(formula) | |
5233 FormulaProcessor().process(whole) | |
5234 whole.process() | |
5235 return ''.join(whole.gethtml()) | |
5236 | |
5237 def main(): | |
5238 "Main function, called if invoked from elyxer.the command line" | |
5239 args = sys.argv | |
5240 Options().parseoptions(args) | |
5241 if len(args) != 1: | |
5242 Trace.error('Usage: math2html.py escaped_string') | |
5243 exit() | |
5244 result = math2html(args[0]) | |
5245 Trace.message(result) | |
5246 | |
5247 if __name__ == '__main__': | |
5248 main() | |
5249 |