comparison env/lib/python3.9/site-packages/docutils/utils/math/math2html.py @ 0:4f3585e2f14b draft default tip

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