Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/docutils/utils/math/math2html.py @ 0:26e78fe6e8c4 draft
"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
| author | shellac |
|---|---|
| date | Sat, 02 May 2020 07:14:21 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:26e78fe6e8c4 |
|---|---|
| 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'&', u'<': u'<', u'>': u'>', | |
| 311 } | |
| 312 | |
| 313 html = { | |
| 314 u'/>': u'>', | |
| 315 } | |
| 316 | |
| 317 iso885915 = { | |
| 318 u' ': u' ', u' ': u' ', u' ': u' ', | |
| 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' < ', | |
| 690 u'=': u' = ', u'>': u' > ', 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'>', | |
| 846 u'startcommand': u'\\', u'startmark': u'=<', | |
| 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'“', | |
| 873 u'els': u'‘', u'erd': u'”', u'ers': u'’', 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 |
