]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_0.py
1d7c8b7d72210e526898919ce8e92cf743d74272
[lyx.git] / lib / lyx2lyx / lyx_2_0.py
1 # -*- coding: utf-8 -*-
2 # This file is part of lyx2lyx
3 # -*- coding: utf-8 -*-
4 # Copyright (C) 2010 The LyX team
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 """ Convert files to the file format generated by lyx 2.0"""
21
22 import re, string
23 import unicodedata
24 import sys, os
25
26 from parser_tools import find_token, find_end_of, find_tokens, \
27   find_end_of_inset, find_end_of_layout, find_token_backwards, \
28   is_in_inset, get_value, get_quoted_value, del_token
29   
30 from lyx2lyx_tools import add_to_preamble, insert_to_preamble, \
31   put_cmd_in_ert, lyx2latex, latex_length, revert_flex_inset, \
32   revert_font_attrs, hex2ratio, str2bool
33
34 ####################################################################
35 # Private helper functions
36
37 def remove_option(lines, m, option):
38     ''' removes option from line m. returns whether we did anything '''
39     l = lines[m].find(option)
40     if l == -1:
41         return False
42     val = lines[m][l:].split('"')[1]
43     lines[m] = lines[m][:l - 1] + lines[m][l+len(option + '="' + val + '"'):]
44     return True
45
46
47 # DO NOT USE THIS ROUTINE ANY MORE. Better yet, replace the uses that
48 # have been made of it with uses of put_cmd_in_ert.
49 def old_put_cmd_in_ert(string):
50     for rep in unicode_reps:
51         string = string.replace(rep[1], rep[0].replace('\\\\', '\\'))
52     string = string.replace('\\', "\\backslash\n")
53     string = "\\begin_inset ERT\nstatus collapsed\n\\begin_layout Plain Layout\n" \
54       + string + "\n\\end_layout\n\\end_inset"
55     return string
56
57
58 ###############################################################################
59 ###
60 ### Conversion and reversion routines
61 ###
62 ###############################################################################
63
64 def revert_swiss(document):
65     " Set language german-ch to ngerman "
66     i = 0
67     if document.language == "german-ch":
68         document.language = "ngerman"
69         i = find_token(document.header, "\\language", 0)
70         if i != -1:
71             document.header[i] = "\\language ngerman"
72     j = 0
73     while True:
74         j = find_token(document.body, "\\lang german-ch", j)
75         if j == -1:
76             return
77         document.body[j] = document.body[j].replace("\\lang german-ch", "\\lang ngerman")
78         j = j + 1
79
80
81 def revert_tabularvalign(document):
82    " Revert the tabular valign option "
83    i = 0
84    while True:
85       i = find_token(document.body, "\\begin_inset Tabular", i)
86       if i == -1:
87           return
88       end = find_end_of_inset(document.body, i)
89       if end == -1:
90           document.warning("Can't find end of inset at line " + str(i))
91           i += 1
92           continue
93       fline = find_token(document.body, "<features", i, end)
94       if fline == -1:
95           document.warning("Can't find features for inset at line " + str(i))
96           i += 1
97           continue
98       p = document.body[fline].find("islongtable")
99       if p != -1:
100           q = document.body[fline].find("tabularvalignment")
101           if q != -1:
102               # FIXME
103               # This seems wrong: It removes everything after 
104               # tabularvalignment, too.
105               document.body[fline] = document.body[fline][:q - 1] + '>'
106           i += 1
107           continue
108
109        # no longtable
110       tabularvalignment = 'c'
111       # which valignment is specified?
112       m = document.body[fline].find('tabularvalignment="top"')
113       if m != -1:
114           tabularvalignment = 't'
115       m = document.body[fline].find('tabularvalignment="bottom"')
116       if m != -1:
117           tabularvalignment = 'b'
118       # delete tabularvalignment
119       q = document.body[fline].find("tabularvalignment")
120       if q != -1:
121           # FIXME
122           # This seems wrong: It removes everything after 
123           # tabularvalignment, too.
124           document.body[fline] = document.body[fline][:q - 1] + '>'
125
126       # don't add a box when centered
127       if tabularvalignment == 'c':
128           i = end
129           continue
130       subst = ['\\end_layout', '\\end_inset']
131       document.body[end:end] = subst # just inserts those lines
132       subst = ['\\begin_inset Box Frameless',
133           'position "' + tabularvalignment +'"',
134           'hor_pos "c"',
135           'has_inner_box 1',
136           'inner_pos "c"',
137           'use_parbox 0',
138           # we don't know the width, assume 50%
139           'width "50col%"',
140           'special "none"',
141           'height "1in"',
142           'height_special "totalheight"',
143           'status open',
144           '',
145           '\\begin_layout Plain Layout']
146       document.body[i:i] = subst # this just inserts the array at i
147       # since there could be a tabular inside a tabular, we cannot
148       # jump to end
149       i += len(subst)
150
151
152 def revert_phantom_types(document, ptype, cmd):
153     " Reverts phantom to ERT "
154     i = 0
155     while True:
156       i = find_token(document.body, "\\begin_inset Phantom " + ptype, i)
157       if i == -1:
158           return
159       end = find_end_of_inset(document.body, i)
160       if end == -1:
161           document.warning("Can't find end of inset at line " + str(i))
162           i += 1
163           continue
164       blay = find_token(document.body, "\\begin_layout Plain Layout", i, end)
165       if blay == -1:
166           document.warning("Can't find layout for inset at line " + str(i))
167           i = end
168           continue
169       bend = find_token(document.body, "\\end_layout", blay, end)
170       if bend == -1:
171           document.warning("Malformed LyX document: Could not find end of Phantom inset's layout.")
172           i = end
173           continue
174       substi = ["\\begin_inset ERT", "status collapsed", "",
175                 "\\begin_layout Plain Layout", "", "", "\\backslash", 
176                 cmd + "{", "\\end_layout", "", "\\end_inset"]
177       substj = ["\\size default", "", "\\begin_inset ERT", "status collapsed", "",
178                 "\\begin_layout Plain Layout", "", "}", "\\end_layout", "", "\\end_inset"]
179       # do the later one first so as not to mess up the numbering
180       document.body[bend:end + 1] = substj
181       document.body[i:blay + 1] = substi
182       i = end + len(substi) + len(substj) - (end - bend) - (blay - i) - 2
183
184
185 def revert_phantom(document):
186     revert_phantom_types(document, "Phantom", "phantom")
187     
188 def revert_hphantom(document):
189     revert_phantom_types(document, "HPhantom", "hphantom")
190
191 def revert_vphantom(document):
192     revert_phantom_types(document, "VPhantom", "vphantom")
193
194
195 def revert_xetex(document):
196     " Reverts documents that use XeTeX "
197
198     i = find_token(document.header, '\\use_xetex', 0)
199     if i == -1:
200         document.warning("Malformed LyX document: Missing \\use_xetex.")
201         return
202     if not str2bool(get_value(document.header, "\\use_xetex", i)):
203         del document.header[i]
204         return
205     del document.header[i]
206
207     # 1.) set doc encoding to utf8-plain
208     i = find_token(document.header, "\\inputencoding", 0)
209     if i == -1:
210         document.warning("Malformed LyX document: Missing \\inputencoding.")
211     else:
212         document.header[i] = "\\inputencoding utf8-plain"
213
214     # 2.) check font settings
215     # defaults
216     roman = sans = typew = default
217     osf = False
218     sf_scale = tt_scale = 100.0
219     
220     i = find_token(document.header, "\\font_roman", 0)
221     if i == -1:
222         document.warning("Malformed LyX document: Missing \\font_roman.")
223     else:
224         roman = get_value(document.header, "\\font_roman", i)
225         document.header[i] = "\\font_roman default"
226
227     i = find_token(document.header, "\\font_sans", 0)
228     if i == -1:
229         document.warning("Malformed LyX document: Missing \\font_sans.")
230     else:
231         sans = get_value(document.header, "\\font_sans", i)
232         document.header[i] = "\\font_sans default"
233     
234     i = find_token(document.header, "\\font_typewriter", 0)
235     if i == -1:
236         document.warning("Malformed LyX document: Missing \\font_typewriter.")
237     else:
238         typew = get_value(document.header, "\\font_typewriter", i)
239         document.header[i] = "\\font_typewriter default"
240
241     i = find_token(document.header, "\\font_osf", 0)
242     if i == -1:
243         document.warning("Malformed LyX document: Missing \\font_osf.")
244     else:
245         osf = str2bool(get_value(document.header, "\\font_osf", i))
246         document.header[i] = "\\font_osf false"
247
248     i = find_token(document.header, "\\font_sc", 0)
249     if i == -1:
250         document.warning("Malformed LyX document: Missing \\font_sc.")
251     else:
252         # we do not need this value.
253         document.header[i] = "\\font_sc false"
254     
255     i = find_token(document.header, "\\font_sf_scale", 0)
256     if i == -1:
257         document.warning("Malformed LyX document: Missing \\font_sf_scale.")
258     else:
259       val = get_value(document.header, '\\font_sf_scale', i)
260       try:
261         # float() can throw
262         sf_scale = float(val)
263       except:
264         document.warning("Invalid font_sf_scale value: " + val)
265       document.header[i] = "\\font_sf_scale 100"
266
267     i = find_token(document.header, "\\font_tt_scale", 0)
268     if i == -1:
269         document.warning("Malformed LyX document: Missing \\font_tt_scale.")
270     else:
271         val = get_value(document.header, '\\font_tt_scale', i)
272         try:
273           # float() can throw
274           tt_scale = float(val)
275         except:
276           document.warning("Invalid font_tt_scale value: " + val)
277         document.header[i] = "\\font_tt_scale 100"
278
279     # 3.) set preamble stuff
280     pretext = ['%% This document must be processed with xelatex!']
281     pretext.append('\\usepackage{fontspec}')
282     if roman != "default":
283         pretext.append('\\setmainfont[Mapping=tex-text]{' + roman + '}')
284     if sans != "default":
285         sf = '\\setsansfont['
286         if sf_scale != 100.0:
287             sf += 'Scale=' + str(sf_scale / 100.0) + ','
288         sf += 'Mapping=tex-text]{' + sans + '}'
289         pretext.append(sf)
290     if typewriter != "default":
291         tw = '\\setmonofont'
292         if tt_scale != 100.0:
293             tw += '[Scale=' + str(tt_scale / 100.0) + ']'
294         tw += '{' + typewriter + '}'
295         pretext.append(tw)
296     if osf:
297         pretext.append('\\defaultfontfeatures{Numbers=OldStyle}')
298     pretext.append('\usepackage{xunicode}')
299     pretext.append('\usepackage{xltxtra}')
300     insert_to_preamble(document, pretext)
301
302
303 def revert_outputformat(document):
304     " Remove default output format param "
305     
306     if not del_token(document.header, '\\default_output_format', 0):
307         document.warning("Malformed LyX document: Missing \\default_output_format.")
308
309
310 def revert_backgroundcolor(document):
311     " Reverts background color to preamble code "
312     i = find_token(document.header, "\\backgroundcolor", 0)
313     if i == -1:
314         return
315     colorcode = get_value(document.header, '\\backgroundcolor', i)
316     del document.header[i]
317     # don't clutter the preamble if backgroundcolor is not set
318     if colorcode == "#ffffff":
319         return
320     red   = hex2ratio(colorcode[1:3])
321     green = hex2ratio(colorcode[3:5])
322     blue  = hex2ratio(colorcode[5:7])
323     insert_to_preamble(document, \
324         ['% To set the background color',
325         '\\@ifundefined{definecolor}{\\usepackage{color}}{}',
326         '\\definecolor{page_backgroundcolor}{rgb}{' + red + ',' + green + ',' + blue + '}',
327         '\\pagecolor{page_backgroundcolor}'])
328
329
330 def revert_splitindex(document):
331     " Reverts splitindex-aware documents "
332     i = find_token(document.header, '\\use_indices', 0)
333     if i == -1:
334         document.warning("Malformed LyX document: Missing \\use_indices.")
335         return
336     useindices = str2bool(get_value(document.header, "\\use_indices", i))
337     del document.header[i]
338     preamble = []
339     if useindices:
340          preamble.append("\\usepackage{splitidx})")
341     
342     # deal with index declarations in the preamble
343     i = 0
344     while True:
345         i = find_token(document.header, "\\index", i)
346         if i == -1:
347             break
348         k = find_token(document.header, "\\end_index", i)
349         if k == -1:
350             document.warning("Malformed LyX document: Missing \\end_index.")
351             return
352         if useindices:    
353           line = document.header[i]
354           l = re.compile(r'\\index (.*)$')
355           m = l.match(line)
356           iname = m.group(1)
357           ishortcut = get_value(document.header, '\\shortcut', i, k)
358           if ishortcut != "":
359               preamble.append("\\newindex[" + iname + "]{" + ishortcut + "}")
360         del document.header[i:k + 1]
361     if preamble:
362         insert_to_preamble(document, preamble)
363         
364     # deal with index insets
365     # these need to have the argument removed
366     i = 0
367     while True:
368         i = find_token(document.body, "\\begin_inset Index", i)
369         if i == -1:
370             break
371         line = document.body[i]
372         l = re.compile(r'\\begin_inset Index (.*)$')
373         m = l.match(line)
374         itype = m.group(1)
375         if itype == "idx" or indices == "false":
376             document.body[i] = "\\begin_inset Index"
377         else:
378             k = find_end_of_inset(document.body, i)
379             if k == -1:
380                 document.warning("Can't find end of index inset!")
381                 i += 1
382                 continue
383             content = lyx2latex(document, document.body[i:k])
384             # escape quotes
385             content = content.replace('"', r'\"')
386             subst = put_cmd_in_ert("\\sindex[" + itype + "]{" + content + "}")
387             document.body[i:k + 1] = subst
388         i = i + 1
389         
390     # deal with index_print insets
391     i = 0
392     while True:
393         i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
394         if i == -1:
395             return
396         k = find_end_of_inset(document.body, i)
397         ptype = get_quoted_value(document.body, 'type', i, k)
398         if ptype == "idx":
399             j = find_token(document.body, "type", i, k)
400             del document.body[j]
401         elif not useindices:
402             del document.body[i:k + 1]
403         else:
404             subst = put_cmd_in_ert("\\printindex[" + ptype + "]{}")
405             document.body[i:k + 1] = subst
406         i = i + 1
407
408
409 def convert_splitindex(document):
410     " Converts index and printindex insets to splitindex-aware format "
411     i = 0
412     while True:
413         i = find_token(document.body, "\\begin_inset Index", i)
414         if i == -1:
415             break
416         document.body[i] = document.body[i].replace("\\begin_inset Index",
417             "\\begin_inset Index idx")
418         i = i + 1
419     i = 0
420     while True:
421         i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
422         if i == -1:
423             return
424         if document.body[i + 1].find('LatexCommand printindex') == -1:
425             document.warning("Malformed LyX document: Incomplete printindex inset.")
426             return
427         subst = ["LatexCommand printindex", 
428             "type \"idx\""]
429         document.body[i + 1:i + 2] = subst
430         i = i + 1
431
432
433 def revert_subindex(document):
434     " Reverts \\printsubindex CommandInset types "
435     i = find_token(document.header, '\\use_indices', 0)
436     if i == -1:
437         document.warning("Malformed LyX document: Missing \\use_indices.")
438         return
439     useindices = str2bool(get_value(document.header, "\\use_indices", i))
440     i = 0
441     while True:
442         i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
443         if i == -1:
444             return
445         k = find_end_of_inset(document.body, i)
446         ctype = get_value(document.body, 'LatexCommand', i, k)
447         if ctype != "printsubindex":
448             i = k + 1
449             continue
450         ptype = get_quoted_value(document.body, 'type', i, k)
451         if not useindices:
452             del document.body[i:k + 1]
453         else:
454             subst = put_cmd_in_ert("\\printsubindex[" + ptype + "]{}")
455             document.body[i:k + 1] = subst
456         i = i + 1
457
458
459 def revert_printindexall(document):
460     " Reverts \\print[sub]index* CommandInset types "
461     i = find_token(document.header, '\\use_indices', 0)
462     if i == -1:
463         document.warning("Malformed LyX document: Missing \\use_indices.")
464         return
465     useindices = str2bool(get_value(document.header, "\\use_indices", i))
466     i = 0
467     while True:
468         i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
469         if i == -1:
470             return
471         k = find_end_of_inset(document.body, i)
472         ctype = get_value(document.body, 'LatexCommand', i, k)
473         if ctype != "printindex*" and ctype != "printsubindex*":
474             i = k
475             continue
476         if not useindices:
477             del document.body[i:k + 1]
478         else:
479             subst = put_cmd_in_ert("\\" + ctype + "{}")
480             document.body[i:k + 1] = subst
481         i = i + 1
482
483
484 def revert_strikeout(document):
485   " Reverts \\strikeout font attribute "
486   changed = revert_font_attrs(document.body, "\\uuline", "\\uuline")
487   changed = revert_font_attrs(document.body, "\\uwave", "\\uwave") or changed
488   changed = revert_font_attrs(document.body, "\\strikeout", "\\sout")  or changed
489   if changed == True:
490     insert_to_preamble(document, \
491         ['%  for proper underlining',
492         '\\PassOptionsToPackage{normalem}{ulem}',
493         '\\usepackage{ulem}'])
494
495
496 def revert_ulinelatex(document):
497     " Reverts \\uline font attribute "
498     i = find_token(document.body, '\\bar under', 0)
499     if i == -1:
500         return
501     insert_to_preamble(document,\
502             ['%  for proper underlining',
503             '\\PassOptionsToPackage{normalem}{ulem}',
504             '\\usepackage{ulem}',
505             '\\let\\cite@rig\\cite',
506             '\\newcommand{\\b@xcite}[2][\\%]{\\def\\def@pt{\\%}\\def\\pas@pt{#1}',
507             '  \\mbox{\\ifx\\def@pt\\pas@pt\\cite@rig{#2}\\else\\cite@rig[#1]{#2}\\fi}}',
508             '\\renewcommand{\\underbar}[1]{{\\let\\cite\\b@xcite\\uline{#1}}}'])
509
510
511 def revert_custom_processors(document):
512     " Remove bibtex_command and index_command params "
513     
514     if not del_token(document.header, '\\bibtex_command', 0):
515         document.warning("Malformed LyX document: Missing \\bibtex_command.")
516     
517     if not del_token(document.header, '\\index_command', 0):
518         document.warning("Malformed LyX document: Missing \\index_command.")
519
520
521 def convert_nomencl_width(document):
522     " Add set_width param to nomencl_print "
523     i = 0
524     while True:
525       i = find_token(document.body, "\\begin_inset CommandInset nomencl_print", i)
526       if i == -1:
527         break
528       document.body.insert(i + 2, "set_width \"none\"")
529       i = i + 1
530
531
532 def revert_nomencl_width(document):
533     " Remove set_width param from nomencl_print "
534     i = 0
535     while True:
536       i = find_token(document.body, "\\begin_inset CommandInset nomencl_print", i)
537       if i == -1:
538         break
539       j = find_end_of_inset(document.body, i)
540       if not del_token(document.body, "set_width", i, j):
541             document.warning("Can't find set_width option for nomencl_print!")
542       i = j
543
544
545 def revert_nomencl_cwidth(document):
546     " Remove width param from nomencl_print "
547     i = 0
548     while True:
549       i = find_token(document.body, "\\begin_inset CommandInset nomencl_print", i)
550       if i == -1:
551         break
552       j = find_end_of_inset(document.body, i)
553       l = find_token(document.body, "width", i, j)
554       if l == -1:
555         document.warning("Can't find width option for nomencl_print!")
556         i = j
557         continue
558       width = get_quoted_value(document.body, "width", i, j)
559       del document.body[l]
560       insert_to_preamble(document, ["\\setlength{\\nomlabelwidth}{" + width + "}"])
561       i = j - 1
562
563
564 def revert_applemac(document):
565     " Revert applemac encoding to auto "
566     if document.encoding != "applemac":
567       return
568     document.encoding = "auto"
569     i = find_token(document.header, "\\encoding", 0)
570     if i != -1:
571         document.header[i] = "\\encoding auto"
572
573
574 def revert_longtable_align(document):
575     " Remove longtable alignment setting "
576     i = 0
577     while True:
578       i = find_token(document.body, "\\begin_inset Tabular", i)
579       if i == -1:
580           break
581       end = find_end_of_inset(document.body, i)
582       if end == -1:
583           document.warning("Can't find end of inset at line " + str(i))
584           i += 1
585           continue
586       fline = find_token(document.body, "<features", i, end)
587       if fline == -1:
588           document.warning("Can't find features for inset at line " + str(i))
589           i += 1
590           continue
591       j = document.body[fline].find("longtabularalignment")
592       if j == -1:
593           i += 1
594           continue
595       # FIXME Is this correct? It wipes out everything after the 
596       # one we found.
597       document.body[fline] = document.body[fline][:j - 1] + '>'
598       # since there could be a tabular inside this one, we 
599       # cannot jump to end.
600       i += 1
601
602
603 def revert_branch_filename(document):
604     " Remove \\filename_suffix parameter from branches "
605     i = 0
606     while True:
607         i = find_token(document.header, "\\filename_suffix", i)
608         if i == -1:
609             return
610         del document.header[i]
611
612
613 def revert_paragraph_indentation(document):
614     " Revert custom paragraph indentation to preamble code "
615     i = find_token(document.header, "\\paragraph_indentation", 0)
616     if i == -1:
617       return
618     length = get_value(document.header, "\\paragraph_indentation", i)
619     # we need only remove the line if indentation is default
620     if length != "default":
621       # handle percent lengths
622       length = latex_length(length)[1]
623       insert_to_preamble(document, ["\\setlength{\\parindent}{" + length + "}"])
624     del document.header[i]
625
626
627 def revert_percent_skip_lengths(document):
628     " Revert relative lengths for paragraph skip separation to preamble code "
629     i = find_token(document.header, "\\defskip", 0)
630     if i == -1:
631         return
632     length = get_value(document.header, "\\defskip", i)
633     # only revert when a custom length was set and when
634     # it used a percent length
635     if length in ('smallskip', 'medskip', 'bigskip'):
636         return
637     # handle percent lengths
638     percent, length = latex_length(length)
639     if percent:
640         insert_to_preamble(document, ["\\setlength{\\parskip}{" + length + "}"])
641         # set defskip to medskip as default
642         document.header[i] = "\\defskip medskip"
643
644
645 def revert_percent_vspace_lengths(document):
646     " Revert relative VSpace lengths to ERT "
647     i = 0
648     while True:
649       i = find_token(document.body, "\\begin_inset VSpace", i)
650       if i == -1:
651           break
652       # only revert if a custom length was set and if
653       # it used a percent length
654       r = re.compile(r'\\begin_inset VSpace (.*)$')
655       m = r.match(document.body[i])
656       length = m.group(1)
657       if length in ('defskip', 'smallskip', 'medskip', 'bigskip', 'vfill'):
658          i += 1
659          continue
660       # check if the space has a star (protected space)
661       protected = (document.body[i].rfind("*") != -1)
662       if protected:
663           length = length.rstrip('*')
664       # handle percent lengths
665       percent, length = latex_length(length)
666       # revert the VSpace inset to ERT
667       if percent:
668           if protected:
669               subst = put_cmd_in_ert("\\vspace*{" + length + "}")
670           else:
671               subst = put_cmd_in_ert("\\vspace{" + length + "}")
672           document.body[i:i + 2] = subst
673       i += 1
674
675
676 def revert_percent_hspace_lengths(document):
677     " Revert relative HSpace lengths to ERT "
678     i = 0
679     while True:
680       i = find_token(document.body, "\\begin_inset space \\hspace", i)
681       if i == -1:
682           break
683       j = find_end_of_inset(document.body, i)
684       if j == -1:
685           document.warning("Can't find end of inset at line " + str(i))
686           i += 1
687           continue
688       # only revert if a custom length was set...
689       length = get_value(document.body, '\\length', i + 1, j)
690       if length == '':
691           document.warning("Malformed lyx document: Missing '\\length' in Space inset.")
692           i = j
693           continue
694       protected = ""
695       if document.body[i].find("\\hspace*{}") != -1:
696           protected = "*"
697       # ...and if it used a percent length
698       percent, length = latex_length(length)
699       # revert the HSpace inset to ERT
700       if percent:
701           subst = put_cmd_in_ert("\\hspace" + protected + "{" + length + "}")
702           document.body[i:j + 1] = subst
703       # if we did a substitution, this will still be ok
704       i = j
705
706
707 def revert_hspace_glue_lengths(document):
708     " Revert HSpace glue lengths to ERT "
709     i = 0
710     while True:
711       i = find_token(document.body, "\\begin_inset space \\hspace", i)
712       if i == -1:
713           break
714       j = find_end_of_inset(document.body, i)
715       if j == -1:
716           document.warning("Can't find end of inset at line " + str(i))
717           i += 1
718           continue
719       length = get_value(document.body, '\\length', i + 1, j)
720       if length == '':
721           document.warning("Malformed lyx document: Missing '\\length' in Space inset.")
722           i = j
723           continue
724       protected = ""
725       if document.body[i].find("\\hspace*{}") != -1:
726           protected = "*"
727       # only revert if the length contains a plus or minus at pos != 0
728       if length.find('-',1) != -1 or length.find('+',1) != -1:
729           # handle percent lengths
730           length = latex_length(length)[1]
731           # revert the HSpace inset to ERT
732           subst = put_cmd_in_ert("\\hspace" + protected + "{" + length + "}")
733           document.body[i:j+1] = subst
734       i = j
735
736
737 def convert_author_id(document):
738     " Add the author_id to the \\author definition and make sure 0 is not used"
739     i = 0
740     anum = 1
741     re_author = re.compile(r'(\\author) (\".*\")\s*(.*)$')
742     
743     while True:
744         i = find_token(document.header, "\\author", i)
745         if i == -1:
746             break
747         m = re_author.match(document.header[i])
748         if m:
749             name = m.group(2)
750             email = m.group(3)
751             document.header[i] = "\\author %i %s %s" % (anum, name, email)
752         # FIXME Should this really be incremented if we didn't match?
753         anum += 1
754         i += 1
755         
756     i = 0
757     while True:
758         i = find_token(document.body, "\\change_", i)
759         if i == -1:
760             break
761         change = document.body[i].split(' ');
762         if len(change) == 3:
763             type = change[0]
764             author_id = int(change[1])
765             time = change[2]
766             document.body[i] = "%s %i %s" % (type, author_id + 1, time)
767         i += 1
768
769
770 def revert_author_id(document):
771     " Remove the author_id from the \\author definition "
772     i = 0
773     anum = 0
774     rx = re.compile(r'(\\author)\s+([-\d]+)\s+(\".*\")\s*(.*)$')
775     idmap = dict()
776
777     while True:
778         i = find_token(document.header, "\\author", i)
779         if i == -1:
780             break
781         m = rx.match(document.header[i])
782         if m:
783             author_id = int(m.group(2))
784             idmap[author_id] = anum
785             name = m.group(3)
786             email = m.group(4)
787             document.header[i] = "\\author %s %s" % (name, email)
788         i += 1
789         # FIXME Should this be incremented if we didn't match?
790         anum += 1
791
792     i = 0
793     while True:
794         i = find_token(document.body, "\\change_", i)
795         if i == -1:
796             break
797         change = document.body[i].split(' ');
798         if len(change) == 3:
799             type = change[0]
800             author_id = int(change[1])
801             time = change[2]
802             document.body[i] = "%s %i %s" % (type, idmap[author_id], time)
803         i += 1
804
805
806 def revert_suppress_date(document):
807     " Revert suppressing of default document date to preamble code "
808     i = find_token(document.header, "\\suppress_date", 0)
809     if i == -1:
810         return
811     # remove the preamble line and write to the preamble
812     # when suppress_date was true
813     date = str2bool(get_value(document.header, "\\suppress_date", i))
814     if date:
815         add_to_preamble(document, ["\\date{}"])
816     del document.header[i]
817
818
819 def revert_mhchem(document):
820     "Revert mhchem loading to preamble code"
821
822     mhchem = "off"
823     i = find_token(document.header, "\\use_mhchem", 0)
824     if i == -1:
825         document.warning("Malformed LyX document: Could not find mhchem setting.")
826         mhchem = "auto"
827     else:
828         val = get_value(document.header, "\\use_mhchem", i)
829         if val == "1":
830             mhchem = "auto"
831         elif val == "2":
832             mhchem = "on"
833         del document.header[i]
834
835     if mhchem == "off":
836       # don't load case
837       return 
838
839     if mhchem == "auto":
840         i = 0
841         while True:
842             i = find_token(document.body, "\\begin_inset Formula", i)
843             if i == -1:
844                break
845             line = document.body[i]
846             if line.find("\\ce{") != -1 or line.find("\\cf{") != -1:
847               mhchem = "on"
848               break
849             i += 1
850
851     if mhchem == "on":
852         pre = ["\\PassOptionsToPackage{version=3}{mhchem}", 
853           "\\usepackage{mhchem}"]
854         insert_to_preamble(document, pre) 
855
856
857 def revert_fontenc(document):
858     " Remove fontencoding param "
859     if not del_token(document.header, '\\fontencoding', 0):
860         document.warning("Malformed LyX document: Missing \\fontencoding.")
861
862
863 def merge_gbrief(document):
864     " Merge g-brief-en and g-brief-de to one class "
865
866     if document.textclass != "g-brief-de":
867         if document.textclass == "g-brief-en":
868             document.textclass = "g-brief"
869             document.set_textclass()
870         return
871
872     obsoletedby = { "Brieftext":       "Letter",
873                     "Unterschrift":    "Signature",
874                     "Strasse":         "Street",
875                     "Zusatz":          "Addition",
876                     "Ort":             "Town",
877                     "Land":            "State",
878                     "RetourAdresse":   "ReturnAddress",
879                     "MeinZeichen":     "MyRef",
880                     "IhrZeichen":      "YourRef",
881                     "IhrSchreiben":    "YourMail",
882                     "Telefon":         "Phone",
883                     "BLZ":             "BankCode",
884                     "Konto":           "BankAccount",
885                     "Postvermerk":     "PostalComment",
886                     "Adresse":         "Address",
887                     "Datum":           "Date",
888                     "Betreff":         "Reference",
889                     "Anrede":          "Opening",
890                     "Anlagen":         "Encl.",
891                     "Verteiler":       "cc",
892                     "Gruss":           "Closing"}
893     i = 0
894     while 1:
895         i = find_token(document.body, "\\begin_layout", i)
896         if i == -1:
897             break
898
899         layout = document.body[i][14:]
900         if layout in obsoletedby:
901             document.body[i] = "\\begin_layout " + obsoletedby[layout]
902
903         i += 1
904         
905     document.textclass = "g-brief"
906     document.set_textclass()
907
908
909 def revert_gbrief(document):
910     " Revert g-brief to g-brief-en "
911     if document.textclass == "g-brief":
912         document.textclass = "g-brief-en"
913         document.set_textclass()
914
915
916 def revert_html_options(document):
917     " Remove html options "
918     del_token(document.header, '\\html_use_mathml', 0)
919     del_token(document.header, '\\html_be_strict', 0)
920
921
922 def revert_includeonly(document):
923     i = 0
924     while True:
925         i = find_token(document.header, "\\begin_includeonly", i)
926         if i == -1:
927             return
928         j = find_end_of(document.header, i, "\\begin_includeonly", "\\end_includeonly")
929         if j == -1:
930             document.warning("Unable to find end of includeonly section!!")
931             break
932         document.header[i : j + 1] = []
933
934
935 def revert_includeall(document):
936     " Remove maintain_unincluded_children param "
937     del_token(document.header, '\\maintain_unincluded_children', 0)
938
939
940 def revert_multirow(document):
941     " Revert multirow cells in tables to TeX-code"
942     i = 0
943     multirow = False
944     while True:
945       # cell type 3 is multirow begin cell
946       i = find_token(document.body, '<cell multirow="3"', i)
947       if i == -1:
948           break
949       # a multirow cell was found
950       multirow = True
951       # remove the multirow tag, set the valignment to top
952       # and remove the bottom line
953       # FIXME Are we sure these always have space around them?
954       document.body[i] = document.body[i].replace(' multirow="3" ', ' ')
955       document.body[i] = document.body[i].replace('valignment="middle"', 'valignment="top"')
956       document.body[i] = document.body[i].replace(' bottomline="true" ', ' ')
957       # write ERT to create the multirow cell
958       # use 2 rows and 2cm as default with because the multirow span
959       # and the column width is only hardly accessible
960       cend = find_token(document.body, "</cell>", i)
961       if cend == -1:
962           document.warning("Malformed LyX document: Could not find end of tabular cell.")
963           i += 1
964           continue
965       blay = find_token(document.body, "\\begin_layout", i, cend)
966       if blay == -1:
967           document.warning("Can't find layout for cell!")
968           i = j
969           continue
970       bend = find_end_of_layout(document.body, blay)
971       if blay == -1:
972           document.warning("Can't find end of layout for cell!")
973           i = cend
974           continue
975
976       # do the later one first, so as not to mess up the numbering
977       # we are wrapping the whole cell in this ert
978       # so before the end of the layout...
979       document.body[bend:bend] = put_cmd_in_ert("}")
980       # ...and after the beginning
981       document.body[blay+1:blay+1] = put_cmd_in_ert("\\multirow{2}{2cm}{")
982
983       while True:
984           # cell type 4 is multirow part cell
985           k = find_token(document.body, '<cell multirow="4"', cend)
986           if k == -1:
987               break
988           # remove the multirow tag, set the valignment to top
989           # and remove the top line
990           # FIXME Are we sure these always have space around them?
991           document.body[k] = document.body[k].replace(' multirow="4" ', ' ')
992           document.body[k] = document.body[k].replace('valignment="middle"', 'valignment="top"')
993           document.body[k] = document.body[k].replace(' topline="true" ', ' ')
994           k += 1
995       # this will always be ok
996       i = cend
997
998     if multirow == True:
999         add_to_preamble(document, ["\\usepackage{multirow}"])
1000
1001
1002 def convert_math_output(document):
1003     " Convert \html_use_mathml to \html_math_output "
1004     i = find_token(document.header, "\\html_use_mathml", 0)
1005     if i == -1:
1006         return
1007     rgx = re.compile(r'\\html_use_mathml\s+(\w+)')
1008     m = rgx.match(document.header[i])
1009     newval = "0" # MathML
1010     if m:
1011       val = str2bool(m.group(1))
1012       if not val:
1013         newval = "2" # Images
1014     else:
1015       document.warning("Can't match " + document.header[i])
1016     document.header[i] = "\\html_math_output " + newval
1017
1018
1019 def revert_math_output(document):
1020     " Revert \html_math_output to \html_use_mathml "
1021     i = find_token(document.header, "\\html_math_output", 0)
1022     if i == -1:
1023         return
1024     rgx = re.compile(r'\\html_math_output\s+(\d)')
1025     m = rgx.match(document.header[i])
1026     newval = "true"
1027     if m:
1028         val = m.group(1)
1029         if val == "1" or val == "2":
1030             newval = "false"
1031     else:
1032         document.warning("Unable to match " + document.header[i])
1033     document.header[i] = "\\html_use_mathml " + newval
1034                 
1035
1036
1037 def revert_inset_preview(document):
1038     " Dissolves the preview inset "
1039     i = 0
1040     while True:
1041       i = find_token(document.body, "\\begin_inset Preview", i)
1042       if i == -1:
1043           return
1044       iend = find_end_of_inset(document.body, i)
1045       if iend == -1:
1046           document.warning("Malformed LyX document: Could not find end of Preview inset.")
1047           i += 1
1048           continue
1049       
1050       # This has several issues.
1051       # We need to do something about the layouts inside InsetPreview.
1052       # If we just leave the first one, then we have something like:
1053       # \begin_layout Standard
1054       # ...
1055       # \begin_layout Standard
1056       # and we get a "no \end_layout" error. So something has to be done.
1057       # Ideally, we would check if it is the same as the layout we are in.
1058       # If so, we just remove it; if not, we end the active one. But it is 
1059       # not easy to know what layout we are in, due to depth changes, etc,
1060       # and it is not clear to me how much work it is worth doing. In most
1061       # cases, the layout will probably be the same.
1062       # 
1063       # For the same reason, we have to remove the \end_layout tag at the
1064       # end of the last layout in the inset. Again, that will sometimes be
1065       # wrong, but it will usually be right. To know what to do, we would
1066       # again have to know what layout the inset is in.
1067       
1068       blay = find_token(document.body, "\\begin_layout", i, iend)
1069       if blay == -1:
1070           document.warning("Can't find layout for preview inset!")
1071           # always do the later one first...
1072           del document.body[iend]
1073           del document.body[i]
1074           # deletions mean we do not need to reset i
1075           continue
1076
1077       # This is where we would check what layout we are in.
1078       # The check for Standard is definitely wrong.
1079       # 
1080       # lay = document.body[blay].split(None, 1)[1]
1081       # if lay != oldlayout:
1082       #     # record a boolean to tell us what to do later....
1083       #     # better to do it later, since (a) it won't mess up
1084       #     # the numbering and (b) we only modify at the end.
1085         
1086       # we want to delete the last \\end_layout in this inset, too.
1087       # note that this may not be the \\end_layout that goes with blay!!
1088       bend = find_end_of_layout(document.body, blay)
1089       while True:
1090           tmp = find_token(document.body, "\\end_layout", bend + 1, iend)
1091           if tmp == -1:
1092               break
1093           bend = tmp
1094       if bend == blay:
1095           document.warning("Unable to find last layout in preview inset!")
1096           del document.body[iend]
1097           del document.body[i]
1098           # deletions mean we do not need to reset i
1099           continue
1100       # always do the later one first...
1101       del document.body[iend]
1102       del document.body[bend]
1103       del document.body[i:blay + 1]
1104       # we do not need to reset i
1105                 
1106
1107 def revert_equalspacing_xymatrix(document):
1108     " Revert a Formula with xymatrix@! to an ERT inset "
1109     i = 0
1110     has_preamble = False
1111     has_equal_spacing = False
1112
1113     while True:
1114       i = find_token(document.body, "\\begin_inset Formula", i)
1115       if i == -1:
1116           break
1117       j = find_end_of_inset(document.body, i)
1118       if j == -1:
1119           document.warning("Malformed LyX document: Could not find end of Formula inset.")
1120           i += 1
1121           continue
1122       
1123       for curline in range(i,j):
1124           found = document.body[curline].find("\\xymatrix@!")
1125           if found != -1:
1126               break
1127  
1128       if found != -1:
1129           has_equal_spacing = True
1130           content = [document.body[i][21:]]
1131           content += document.body[i + 1:j]
1132           subst = put_cmd_in_ert(content)
1133           document.body[i:j + 1] = subst
1134           i += len(subst) - (j - i) + 1
1135       else:
1136           for curline in range(i,j):
1137               l = document.body[curline].find("\\xymatrix")
1138               if l != -1:
1139                   has_preamble = True;
1140                   break;
1141           i = j + 1
1142   
1143     if has_equal_spacing and not has_preamble:
1144         add_to_preamble(document, ['\\usepackage[all]{xy}'])
1145
1146
1147 def revert_notefontcolor(document):
1148     " Reverts greyed-out note font color to preamble code "
1149
1150     i = find_token(document.header, "\\notefontcolor", 0)
1151     if i == -1:
1152         return
1153
1154     # are there any grey notes?
1155     if find_token(document.body, "\\begin_inset Note Greyedout", 0) == -1:
1156         # no need to do anything, and \renewcommand will throw an error
1157         # since lyxgreyedout will not exist.
1158         return
1159
1160     colorcode = get_value(document.header, '\\notefontcolor', i)
1161     del document.header[i]
1162     # the color code is in the form #rrggbb where every character denotes a hex number
1163     red = hex2ratio(colorcode[1:3])
1164     green = hex2ratio(colorcode[3:5])
1165     blue = hex2ratio(colorcode[5:7])
1166     # write the preamble
1167     insert_to_preamble(document,
1168       [ '%  for greyed-out notes',
1169         '\\@ifundefined{definecolor}{\\usepackage{color}}{}'
1170         '\\definecolor{note_fontcolor}{rgb}{%s,%s,%s}' % (red, green, blue),
1171         '\\renewenvironment{lyxgreyedout}',
1172         ' {\\textcolor{note_fontcolor}\\bgroup}{\\egroup}'])
1173
1174
1175 def revert_turkmen(document):
1176     "Set language Turkmen to English" 
1177
1178     if document.language == "turkmen": 
1179         document.language = "english" 
1180         i = find_token(document.header, "\\language", 0) 
1181         if i != -1: 
1182             document.header[i] = "\\language english" 
1183
1184     j = 0 
1185     while True: 
1186         j = find_token(document.body, "\\lang turkmen", j) 
1187         if j == -1: 
1188             return 
1189         document.body[j] = document.body[j].replace("\\lang turkmen", "\\lang english") 
1190         j += 1 
1191
1192
1193 def revert_fontcolor(document):
1194     " Reverts font color to preamble code "
1195     i = find_token(document.header, "\\fontcolor", 0)
1196     if i == -1:
1197         return
1198     colorcode = get_value(document.header, '\\fontcolor', i)
1199     del document.header[i]
1200     # don't clutter the preamble if font color is not set
1201     if colorcode == "#000000":
1202         return
1203     # the color code is in the form #rrggbb where every character denotes a hex number
1204     red = hex2ratio(colorcode[1:3])
1205     green = hex2ratio(colorcode[3:5])
1206     blue = hex2ratio(colorcode[5:7])
1207     # write the preamble
1208     insert_to_preamble(document,
1209       ['%  Set the font color',
1210       '\\@ifundefined{definecolor}{\\usepackage{color}}{}',
1211       '\\definecolor{document_fontcolor}{rgb}{%s,%s,%s}' % (red, green, blue),
1212       '\\color{document_fontcolor}'])
1213
1214
1215 def revert_shadedboxcolor(document):
1216     " Reverts shaded box color to preamble code "
1217     i = find_token(document.header, "\\boxbgcolor", 0)
1218     if i == -1:
1219         return
1220     colorcode = get_value(document.header, '\\boxbgcolor', i)
1221     del document.header[i]
1222     # the color code is in the form #rrggbb
1223     red = hex2ratio(colorcode[1:3])
1224     green = hex2ratio(colorcode[3:5])
1225     blue = hex2ratio(colorcode[5:7])
1226     # write the preamble
1227     insert_to_preamble(document,
1228       ['%  Set the color of boxes with shaded background',
1229       '\\@ifundefined{definecolor}{\\usepackage{color}}{}',
1230       "\\definecolor{shadecolor}{rgb}{%s,%s,%s}" % (red, green, blue)])
1231
1232
1233 def revert_lyx_version(document):
1234     " Reverts LyX Version information from Inset Info "
1235     version = "LyX version"
1236     try:
1237         import lyx2lyx_version
1238         version = lyx2lyx_version.version
1239     except:
1240         pass
1241
1242     i = 0
1243     while 1:
1244         i = find_token(document.body, '\\begin_inset Info', i)
1245         if i == -1:
1246             return
1247         j = find_end_of_inset(document.body, i + 1)
1248         if j == -1:
1249             document.warning("Malformed LyX document: Could not find end of Info inset.")
1250             i += 1
1251             continue
1252
1253         # We expect:
1254         # \begin_inset Info
1255         # type  "lyxinfo"
1256         # arg   "version"
1257         # \end_inset
1258         typ = get_quoted_value(document.body, "type", i, j)
1259         arg = get_quoted_value(document.body, "arg", i, j)
1260         if arg != "version" or typ != "lyxinfo":
1261             i = j + 1
1262             continue
1263
1264         # We do not actually know the version of LyX used to produce the document.
1265         # But we can use our version, since we are reverting.
1266         s = [version]
1267         # Now we want to check if the line after "\end_inset" is empty. It normally
1268         # is, so we want to remove it, too.
1269         lastline = j + 1
1270         if document.body[j + 1].strip() == "":
1271             lastline = j + 2
1272         document.body[i: lastline] = s
1273         i = i + 1
1274
1275
1276 def revert_math_scale(document):
1277   " Remove math scaling and LaTeX options "
1278   del_token(document.header, '\\html_math_img_scale', 0)
1279   del_token(document.header, '\\html_latex_start', 0)
1280   del_token(document.header, '\\html_latex_end', 0)
1281
1282
1283 def revert_pagesizes(document):
1284   " Revert page sizes to default "
1285   i = find_token(document.header, '\\papersize', 0)
1286   if i != -1:
1287     size = document.header[i][11:]
1288     if size == "a0paper" or size == "a1paper" or size == "a2paper" \
1289     or size == "a6paper" or size == "b0paper" or size == "b1paper" \
1290     or size == "b2paper" or size == "b6paper" or size == "b0j" \
1291     or size == "b1j" or size == "b2j" or size == "b3j" or size == "b4j" \
1292     or size == "b5j" or size == "b6j":
1293       del document.header[i]
1294
1295
1296 def revert_DIN_C_pagesizes(document):
1297   " Revert DIN C page sizes to default "
1298   i = find_token(document.header, '\\papersize', 0)
1299   if i != -1:
1300     size = document.header[i][11:]
1301     if size == "c0paper" or size == "c1paper" or size == "c2paper" \
1302     or size == "c3paper" or size == "c4paper" or size == "c5paper" \
1303     or size == "c6paper":
1304       del document.header[i]
1305
1306
1307 def convert_html_quotes(document):
1308   " Remove quotes around html_latex_start and html_latex_end "
1309
1310   i = find_token(document.header, '\\html_latex_start', 0)
1311   if i != -1:
1312     line = document.header[i]
1313     l = re.compile(r'\\html_latex_start\s+"(.*)"')
1314     m = l.match(line)
1315     if m:
1316       document.header[i] = "\\html_latex_start " + m.group(1)
1317       
1318   i = find_token(document.header, '\\html_latex_end', 0)
1319   if i != -1:
1320     line = document.header[i]
1321     l = re.compile(r'\\html_latex_end\s+"(.*)"')
1322     m = l.match(line)
1323     if m:
1324       document.header[i] = "\\html_latex_end " + m.group(1)
1325       
1326
1327 def revert_html_quotes(document):
1328   " Remove quotes around html_latex_start and html_latex_end "
1329   
1330   i = find_token(document.header, '\\html_latex_start', 0)
1331   if i != -1:
1332     line = document.header[i]
1333     l = re.compile(r'\\html_latex_start\s+(.*)')
1334     m = l.match(line)
1335     if not m:
1336         document.warning("Weird html_latex_start line: " + line)
1337         del document.header[i]
1338     else:
1339         document.header[i] = "\\html_latex_start \"" + m.group(1) + "\""
1340       
1341   i = find_token(document.header, '\\html_latex_end', 0)
1342   if i != -1:
1343     line = document.header[i]
1344     l = re.compile(r'\\html_latex_end\s+(.*)')
1345     m = l.match(line)
1346     if not m:
1347         document.warning("Weird html_latex_end line: " + line)
1348         del document.header[i]
1349     else:
1350         document.header[i] = "\\html_latex_end \"" + m.group(1) + "\""
1351
1352
1353 def revert_output_sync(document):
1354   " Remove forward search options "
1355   del_token(document.header, '\\output_sync_macro', 0)
1356   del_token(document.header, '\\output_sync', 0)
1357
1358
1359 def revert_align_decimal(document):
1360   i = 0
1361   while True:
1362     i = find_token(document.body, "\\begin_inset Tabular", i)
1363     if i == -1:
1364       return
1365     j = find_end_of_inset(document.body, i)
1366     if j == -1:
1367       document.warning("Unable to find end of Tabular inset at line " + str(i))
1368       i += 1
1369       continue
1370     cell = find_token(document.body, "<cell", i, j)
1371     if cell == -1:
1372       document.warning("Can't find any cells in Tabular inset at line " + str(i))
1373       i = j
1374       continue
1375     k = i + 1
1376     while True:
1377       k = find_token(document.body, "<column", k, cell)
1378       if k == -1:
1379         return
1380       if document.body[k].find('alignment="decimal"') == -1:
1381         k += 1
1382         continue
1383       remove_option(document.body, k, 'decimal_point')
1384       document.body[k] = \
1385         document.body[k].replace('alignment="decimal"', 'alignment="center"')
1386       k += 1
1387
1388
1389 def convert_optarg(document):
1390   " Convert \\begin_inset OptArg to \\begin_inset Argument "
1391   i = 0
1392   while 1:
1393     i = find_token(document.body, '\\begin_inset OptArg', i)
1394     if i == -1:
1395       return
1396     document.body[i] = "\\begin_inset Argument"
1397     i += 1
1398
1399
1400 def revert_argument(document):
1401   " Convert \\begin_inset Argument to \\begin_inset OptArg "
1402   i = 0
1403   while 1:
1404     i = find_token(document.body, '\\begin_inset Argument', i)
1405     if i == -1:
1406       return
1407     document.body[i] = "\\begin_inset OptArg"
1408     i += 1
1409
1410
1411 def revert_makebox(document):
1412   " Convert \\makebox to TeX code "
1413   i = 0
1414   while 1:
1415     # only revert frameless boxes without an inner box
1416     i = find_token(document.body, '\\begin_inset Box Frameless', i)
1417     if i == -1:
1418       return
1419     z = find_end_of_inset(document.body, i)
1420     if z == -1:
1421       document.warning("Malformed LyX document: Can't find end of box inset.")
1422       i += 1
1423       continue
1424     blay = find_token(document.body, "\\begin_layout", i, z)
1425     if blay == -1:
1426       document.warning("Malformed LyX document: Can't find layout in box.")
1427       i = z
1428       continue
1429     # by looking before the layout we make sure we're actually finding
1430     # an option, not text.
1431     j = find_token(document.body, 'use_makebox', i, blay)
1432     if j == -1:
1433         i = z
1434         continue
1435     val = get_value(document.body, 'use_makebox', j)
1436     if val != "1":
1437         del document.body[j]
1438         i = z
1439         continue
1440     bend = find_end_of_layout(document.body, blay)
1441     if bend == -1 or bend > z:
1442         document.warning("Malformed LyX document: Can't find end of layout in box.")
1443         i = z
1444         continue
1445     # determine the alignment
1446     align = get_quoted_value(document.body, 'hor_pos', i, blay, "c")
1447     # determine the width
1448     length = get_quoted_value(document.body, 'width', i, blay, "50col%")
1449     length = latex_length(length)[1]
1450     # remove the \end_layout \end_inset pair
1451     document.body[bend:z + 1] = put_cmd_in_ert("}")
1452     subst = "\\makebox[" + length + "][" \
1453       + align + "]{"
1454     document.body[i:blay + 1] = put_cmd_in_ert(subst)
1455     i += 1
1456
1457
1458 def convert_use_makebox(document):
1459   " Adds use_makebox option for boxes "
1460   i = 0
1461   while 1:
1462     i = find_token(document.body, '\\begin_inset Box', i)
1463     if i == -1:
1464       return
1465     # all of this is to make sure we actually find the use_parbox
1466     # that is an option for this box, not some text elsewhere.
1467     z = find_end_of_inset(document.body, i)
1468     if z == -1:
1469       document.warning("Can't find end of box inset!!")
1470       i += 1
1471       continue
1472     blay = find_token(document.body, "\\begin_layout", i, z)
1473     if blay == -1:
1474       document.warning("Can't find layout in box inset!!")
1475       i = z
1476       continue
1477     # so now we are looking for use_parbox before the box's layout
1478     k = find_token(document.body, 'use_parbox', i, blay)
1479     if k == -1:
1480       document.warning("Malformed LyX document: Can't find use_parbox statement in box.")
1481       i = z
1482       continue
1483     document.body.insert(k + 1, "use_makebox 0")
1484     i = z + 1
1485
1486
1487 def revert_IEEEtran(document):
1488   " Convert IEEEtran layouts and styles to TeX code "
1489   if document.textclass != "IEEEtran":
1490     return
1491   revert_flex_inset(document.body, "IEEE membership", "\\IEEEmembership")
1492   revert_flex_inset(document.body, "Lowercase", "\\MakeLowercase")
1493   layouts = ("Special Paper Notice", "After Title Text", "Publication ID",
1494              "Page headings", "Biography without photo")
1495   latexcmd = {"Special Paper Notice": "\\IEEEspecialpapernotice",
1496               "After Title Text":     "\\IEEEaftertitletext",
1497               "Publication ID":       "\\IEEEpubid"}
1498   obsoletedby = {"Page headings":            "MarkBoth",
1499                  "Biography without photo":  "BiographyNoPhoto"}
1500   for layout in layouts:
1501     i = 0
1502     while True:
1503         i = find_token(document.body, '\\begin_layout ' + layout, i)
1504         if i == -1:
1505           break
1506         j = find_end_of_layout(document.body, i)
1507         if j == -1:
1508           document.warning("Malformed LyX document: Can't find end of " + layout + " layout.")
1509           i += 1
1510           continue
1511         if layout in obsoletedby:
1512           document.body[i] = "\\begin_layout " + obsoletedby[layout]
1513           i = j
1514           continue
1515         content = lyx2latex(document, document.body[i:j + 1])
1516         add_to_preamble(document, [latexcmd[layout] + "{" + content + "}"])
1517         del document.body[i:j + 1]
1518         # no need to reset i
1519
1520
1521 def convert_prettyref(document):
1522         " Converts prettyref references to neutral formatted refs "
1523         re_ref = re.compile("^\s*reference\s+\"(\w+):(\S+)\"")
1524         nm_ref = re.compile("^\s*name\s+\"(\w+):(\S+)\"")
1525
1526         i = 0
1527         while True:
1528                 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1529                 if i == -1:
1530                         break
1531                 j = find_end_of_inset(document.body, i)
1532                 if j == -1:
1533                         document.warning("Malformed LyX document: No end of InsetRef!")
1534                         i += 1
1535                         continue
1536                 k = find_token(document.body, "LatexCommand prettyref", i, j)
1537                 if k != -1:
1538                         document.body[k] = "LatexCommand formatted"
1539                 i = j + 1
1540         document.header.insert(-1, "\\use_refstyle 0")
1541                 
1542  
1543 def revert_refstyle(document):
1544         " Reverts neutral formatted refs to prettyref "
1545         re_ref = re.compile("^reference\s+\"(\w+):(\S+)\"")
1546         nm_ref = re.compile("^\s*name\s+\"(\w+):(\S+)\"")
1547
1548         i = 0
1549         while True:
1550                 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1551                 if i == -1:
1552                         break
1553                 j = find_end_of_inset(document.body, i)
1554                 if j == -1:
1555                         document.warning("Malformed LyX document: No end of InsetRef")
1556                         i += 1
1557                         continue
1558                 k = find_token(document.body, "LatexCommand formatted", i, j)
1559                 if k != -1:
1560                         document.body[k] = "LatexCommand prettyref"
1561                 i = j + 1
1562         i = find_token(document.header, "\\use_refstyle", 0)
1563         if i != -1:
1564                 document.header.pop(i)
1565  
1566
1567 def revert_nameref(document):
1568   " Convert namerefs to regular references "
1569   cmds = ["Nameref", "nameref"]
1570   foundone = False
1571   rx = re.compile(r'reference "(.*)"')
1572   for cmd in cmds:
1573     i = 0
1574     oldcmd = "LatexCommand " + cmd
1575     while 1:
1576       # It seems better to look for this, as most of the reference
1577       # insets won't be ones we care about.
1578       i = find_token(document.body, oldcmd, i)
1579       if i == -1:
1580         break
1581       cmdloc = i
1582       i += 1
1583       # Make sure it is actually in an inset!
1584       # A normal line could begin with "LatexCommand nameref"!
1585       val = is_in_inset(document.body, cmdloc, \
1586           "\\begin_inset CommandInset ref")
1587       if not val:
1588           continue
1589       stins, endins = val
1590
1591       # ok, so it is in an InsetRef
1592       refline = find_token(document.body, "reference", stins, endins)
1593       if refline == -1:
1594         document.warning("Can't find reference for inset at line " + stinst + "!!")
1595         continue
1596       m = rx.match(document.body[refline])
1597       if not m:
1598         document.warning("Can't match reference line: " + document.body[ref])
1599         continue
1600       foundone = True
1601       ref = m.group(1)
1602       newcontent = put_cmd_in_ert('\\' + cmd + '{' + ref + '}')
1603       document.body[stins:endins + 1] = newcontent
1604
1605   if foundone:
1606     add_to_preamble(document, ["\usepackage{nameref}"])
1607
1608
1609 def remove_Nameref(document):
1610   " Convert Nameref commands to nameref commands "
1611   i = 0
1612   while 1:
1613     # It seems better to look for this, as most of the reference
1614     # insets won't be ones we care about.
1615     i = find_token(document.body, "LatexCommand Nameref" , i)
1616     if i == -1:
1617       break
1618     cmdloc = i
1619     i += 1
1620     
1621     # Make sure it is actually in an inset!
1622     val = is_in_inset(document.body, cmdloc, \
1623         "\\begin_inset CommandInset ref")
1624     if not val:
1625       continue
1626     document.body[cmdloc] = "LatexCommand nameref"
1627
1628
1629 def revert_mathrsfs(document):
1630     " Load mathrsfs if \mathrsfs us use in the document "
1631     i = 0
1632     for line in document.body:
1633       if line.find("\\mathscr{") != -1:
1634         add_to_preamble(document, ["\\usepackage{mathrsfs}"])
1635         return
1636
1637
1638 def convert_flexnames(document):
1639     "Convert \\begin_inset Flex Custom:Style to \\begin_inset Flex Style and similarly for CharStyle and Element."
1640     
1641     i = 0
1642     rx = re.compile(r'^\\begin_inset Flex (?:Custom|CharStyle|Element):(.+)$')
1643     while True:
1644       i = find_token(document.body, "\\begin_inset Flex", i)
1645       if i == -1:
1646         return
1647       m = rx.match(document.body[i])
1648       if m:
1649         document.body[i] = "\\begin_inset Flex " + m.group(1)
1650       i += 1
1651
1652
1653 flex_insets = {
1654   "Alert" : "CharStyle:Alert",
1655   "Code" : "CharStyle:Code",
1656   "Concepts" : "CharStyle:Concepts",
1657   "E-Mail" : "CharStyle:E-Mail",
1658   "Emph" : "CharStyle:Emph",
1659   "Expression" : "CharStyle:Expression",
1660   "Initial" : "CharStyle:Initial",
1661   "Institute" : "CharStyle:Institute",
1662   "Meaning" : "CharStyle:Meaning",
1663   "Noun" : "CharStyle:Noun",
1664   "Strong" : "CharStyle:Strong",
1665   "Structure" : "CharStyle:Structure",
1666   "ArticleMode" : "Custom:ArticleMode",
1667   "Endnote" : "Custom:Endnote",
1668   "Glosse" : "Custom:Glosse",
1669   "PresentationMode" : "Custom:PresentationMode",
1670   "Tri-Glosse" : "Custom:Tri-Glosse"
1671 }
1672
1673 flex_elements = {
1674   "Abbrev" : "Element:Abbrev",
1675   "CCC-Code" : "Element:CCC-Code",
1676   "Citation-number" : "Element:Citation-number",
1677   "City" : "Element:City",
1678   "Code" : "Element:Code",
1679   "CODEN" : "Element:CODEN",
1680   "Country" : "Element:Country",
1681   "Day" : "Element:Day",
1682   "Directory" : "Element:Directory",
1683   "Dscr" : "Element:Dscr",
1684   "Email" : "Element:Email",
1685   "Emph" : "Element:Emph",
1686   "Filename" : "Element:Filename",
1687   "Firstname" : "Element:Firstname",
1688   "Fname" : "Element:Fname",
1689   "GuiButton" : "Element:GuiButton",
1690   "GuiMenu" : "Element:GuiMenu",
1691   "GuiMenuItem" : "Element:GuiMenuItem",
1692   "ISSN" : "Element:ISSN",
1693   "Issue-day" : "Element:Issue-day",
1694   "Issue-months" : "Element:Issue-months",
1695   "Issue-number" : "Element:Issue-number",
1696   "KeyCap" : "Element:KeyCap",
1697   "KeyCombo" : "Element:KeyCombo",
1698   "Keyword" : "Element:Keyword",
1699   "Literal" : "Element:Literal",
1700   "MenuChoice" : "Element:MenuChoice",
1701   "Month" : "Element:Month",
1702   "Orgdiv" : "Element:Orgdiv",
1703   "Orgname" : "Element:Orgname",
1704   "Postcode" : "Element:Postcode",
1705   "SS-Code" : "Element:SS-Code",
1706   "SS-Title" : "Element:SS-Title",
1707   "State" : "Element:State",
1708   "Street" : "Element:Street",
1709   "Surname" : "Element:Surname",
1710   "Volume" : "Element:Volume",
1711   "Year" : "Element:Year"
1712 }
1713
1714
1715 def revert_flexnames(document):
1716   if document.backend == "latex":
1717     flexlist = flex_insets
1718   else:
1719     flexlist = flex_elements
1720   
1721   rx = re.compile(r'^\\begin_inset Flex\s+(.+)$')
1722   i = 0
1723   while True:
1724     i = find_token(document.body, "\\begin_inset Flex", i)
1725     if i == -1:
1726       return
1727     m = rx.match(document.body[i])
1728     if not m:
1729       document.warning("Illegal flex inset: " + document.body[i])
1730       i += 1
1731       continue
1732     style = m.group(1)
1733     if style in flexlist:
1734       document.body[i] = "\\begin_inset Flex " + flexlist[style]
1735     i += 1
1736
1737
1738 def convert_mathdots(document):
1739     " Load mathdots automatically "
1740     i = find_token(document.header, "\\use_esint" , 0)
1741     if i != -1:
1742       document.header.insert(i + 1, "\\use_mathdots 1")
1743
1744
1745 def revert_mathdots(document):
1746     " Load mathdots if used in the document "
1747
1748     mathdots = find_token(document.header, "\\use_mathdots" , 0)
1749     if mathdots == -1:
1750       document.warning("No \\usemathdots line. Assuming auto.")
1751     else:
1752       val = get_value(document.header, "\\use_mathdots", mathdots)
1753       del document.header[mathdots]
1754       try:
1755         usedots = int(val)
1756       except:
1757         document.warning("Invalid \\use_mathdots value: " + val + ". Assuming auto.")
1758         # probably usedots has not been changed, but be safe.
1759         usedots = 1
1760
1761       if usedots == 0:
1762         # do not load case
1763         return
1764       if usedots == 2:
1765         # force load case
1766         add_to_preamble(["\\usepackage{mathdots}"])
1767         return
1768     
1769     # so we are in the auto case. we want to load mathdots if \iddots is used.
1770     i = 0
1771     while True:
1772       i = find_token(document.body, '\\begin_inset Formula', i)
1773       if i == -1:
1774         return
1775       j = find_end_of_inset(document.body, i)
1776       if j == -1:
1777         document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
1778         i += 1
1779         continue
1780       code = "\n".join(document.body[i:j])
1781       if code.find("\\iddots") != -1:
1782         add_to_preamble(document, ["\\@ifundefined{iddots}{\\usepackage{mathdots}}"])
1783         return
1784       i = j
1785
1786
1787 def convert_rule(document):
1788     " Convert \\lyxline to CommandInset line. "
1789     i = 0
1790     
1791     inset = ['\\begin_inset CommandInset line',
1792       'LatexCommand rule',
1793       'offset "0.5ex"',
1794       'width "100line%"',
1795       'height "1pt"', '',
1796       '\\end_inset', '', '']
1797
1798     # if paragraphs are indented, we may have to unindent to get the
1799     # line to be full-width.
1800     indent = get_value(document.header, "\\paragraph_separation", 0)
1801     have_indent = (indent == "indent")
1802
1803     while True:
1804       i = find_token(document.body, "\\lyxline" , i)
1805       if i == -1:
1806         return
1807
1808       # we need to find out if this line follows other content
1809       # in its paragraph. find its layout....
1810       lastlay = find_token_backwards(document.body, "\\begin_layout", i)
1811       if lastlay == -1:
1812         document.warning("Can't find layout for line at " + str(i))
1813         # do the best we can.
1814         document.body[i:i+1] = inset
1815         i += len(inset)
1816         continue
1817
1818       # ...and look for other content before it.
1819       lineisfirst = True
1820       for line in document.body[lastlay + 1:i]:
1821         # is it empty or a paragraph option?
1822         if not line or line[0] == '\\':
1823           continue
1824         lineisfirst = False
1825         break
1826
1827       if lineisfirst:
1828         document.body[i:i+1] = inset
1829         if indent:
1830           # we need to unindent, lest the line be too long
1831           document.body.insert(lastlay + 1, "\\noindent")
1832         i += len(inset)
1833       else:
1834         # so our line is in the middle of a paragraph
1835         # we need to add a new line, lest this line follow the
1836         # other content on that line and run off the side of the page
1837         document.body[i:i+1] = inset
1838         document.body[i:i] = ["\\begin_inset Newline newline", "\\end_inset", ""]
1839       i += len(inset)
1840
1841
1842 def revert_rule(document):
1843     " Revert line insets to Tex code "
1844     i = 0
1845     while 1:
1846       i = find_token(document.body, "\\begin_inset CommandInset line" , i)
1847       if i == -1:
1848         return
1849       # find end of inset
1850       j = find_token(document.body, "\\end_inset" , i)
1851       if j == -1:
1852         document.warning("Malformed LyX document: Can't find end of line inset.")
1853         return
1854       # determine the optional offset
1855       offset = get_quoted_value(document.body, 'offset', i, j)
1856       if offset:
1857         offset = '[' + offset + ']'
1858       # determine the width
1859       width = get_quoted_value(document.body, 'width', i, j, "100col%")
1860       width = latex_length(width)[1]
1861       # determine the height
1862       height = get_quoted_value(document.body, 'height', i, j, "1pt")
1863       height = latex_length(height)[1]
1864       # output the \rule command
1865       subst = "\\rule[" + offset + "]{" + width + "}{" + height + "}"
1866       document.body[i:j + 1] = put_cmd_in_ert(subst)
1867       i += len(subst) - (j - i)
1868
1869
1870 def revert_diagram(document):
1871   " Add the feyn package if \\Diagram is used in math "
1872   i = 0
1873   while True:
1874     i = find_token(document.body, '\\begin_inset Formula', i)
1875     if i == -1:
1876       return
1877     j = find_end_of_inset(document.body, i)
1878     if j == -1:
1879         document.warning("Malformed LyX document: Can't find end of Formula inset.")
1880         return 
1881     lines = "\n".join(document.body[i:j])
1882     if lines.find("\\Diagram") == -1:
1883       i = j
1884       continue
1885     add_to_preamble(document, ["\\usepackage{feyn}"])
1886     # only need to do it once!
1887     return
1888
1889
1890 def convert_bibtex_clearpage(document):
1891   " insert a clear(double)page bibliographystyle if bibtotoc option is used "
1892
1893   i = find_token(document.header, '\\papersides', 0)
1894   sides = 0
1895   if i == -1:
1896     document.warning("Malformed LyX document: Can't find papersides definition.")
1897     document.warning("Assuming single sided.")
1898     sides = 1
1899   else:
1900     val = get_value(document.header, "\\papersides", i)
1901     try:
1902       sides = int(val)
1903     except:
1904       pass
1905     if sides != 1 and sides != 2:
1906       document.warning("Invalid papersides value: " + val)
1907       document.warning("Assuming single sided.")
1908       sides = 1
1909
1910   j = 0
1911   while True:
1912     j = find_token(document.body, "\\begin_inset CommandInset bibtex", j)
1913     if j == -1:
1914       return
1915
1916     k = find_end_of_inset(document.body, j)
1917     if k == -1:
1918       document.warning("Can't find end of Bibliography inset at line " + str(j))
1919       j += 1
1920       continue
1921
1922     # only act if there is the option "bibtotoc"
1923     val = get_value(document.body, 'options', j, k)
1924     if not val:
1925       document.warning("Can't find options for bibliography inset at line " + str(j))
1926       j = k
1927       continue
1928     
1929     if val.find("bibtotoc") == -1:
1930       j = k
1931       continue
1932     
1933     # so we want to insert a new page right before the paragraph that
1934     # this bibliography thing is in. 
1935     lay = find_token_backwards(document.body, "\\begin_layout", j)
1936     if lay == -1:
1937       document.warning("Can't find layout containing bibliography inset at line " + str(j))
1938       j = k
1939       continue
1940
1941     if sides == 1:
1942       cmd = "clearpage"
1943     else:
1944       cmd = "cleardoublepage"
1945     subst = ['\\begin_layout Standard',
1946         '\\begin_inset Newpage ' + cmd,
1947         '\\end_inset', '', '',
1948         '\\end_layout', '']
1949     document.body[lay:lay] = subst
1950     j = k + len(subst)
1951
1952
1953 ##
1954 # Conversion hub
1955 #
1956
1957 supported_versions = ["2.0.0","2.0"]
1958 convert = [[346, []],
1959            [347, []],
1960            [348, []],
1961            [349, []],
1962            [350, []],
1963            [351, []],
1964            [352, [convert_splitindex]],
1965            [353, []],
1966            [354, []],
1967            [355, []],
1968            [356, []],
1969            [357, []],
1970            [358, []],
1971            [359, [convert_nomencl_width]],
1972            [360, []],
1973            [361, []],
1974            [362, []],
1975            [363, []],
1976            [364, []],
1977            [365, []],
1978            [366, []],
1979            [367, []],
1980            [368, []],
1981            [369, [convert_author_id]],
1982            [370, []],
1983            [371, []],
1984            [372, []],
1985            [373, [merge_gbrief]],
1986            [374, []],
1987            [375, []],
1988            [376, []],
1989            [377, []],
1990            [378, []],
1991            [379, [convert_math_output]],
1992            [380, []],
1993            [381, []],
1994            [382, []],
1995            [383, []],
1996            [384, []],
1997            [385, []],
1998            [386, []],
1999            [387, []],
2000            [388, []],
2001            [389, [convert_html_quotes]],
2002            [390, []],
2003            [391, []],
2004            [392, []],
2005            [393, [convert_optarg]],
2006            [394, [convert_use_makebox]],
2007            [395, []],
2008            [396, []],
2009            [397, [remove_Nameref]],
2010            [398, []],
2011            [399, [convert_mathdots]],
2012            [400, [convert_rule]],
2013            [401, []],
2014            [402, [convert_bibtex_clearpage]],
2015            [403, [convert_flexnames]],
2016            [404, [convert_prettyref]],
2017            [405, []]
2018 ]
2019
2020 revert =  [[404, []],
2021            [403, [revert_refstyle]],
2022            [402, [revert_flexnames]],
2023            [401, []],
2024            [400, [revert_diagram]],
2025            [399, [revert_rule]],
2026            [398, [revert_mathdots]],
2027            [397, [revert_mathrsfs]],
2028            [396, []],
2029            [395, [revert_nameref]],
2030            [394, [revert_DIN_C_pagesizes]],
2031            [393, [revert_makebox]],
2032            [392, [revert_argument]],
2033            [391, []],
2034            [390, [revert_align_decimal, revert_IEEEtran]],
2035            [389, [revert_output_sync]],
2036            [388, [revert_html_quotes]],
2037            [387, [revert_pagesizes]],
2038            [386, [revert_math_scale]],
2039            [385, [revert_lyx_version]],
2040            [384, [revert_shadedboxcolor]],
2041            [383, [revert_fontcolor]],
2042            [382, [revert_turkmen]],
2043            [381, [revert_notefontcolor]],
2044            [380, [revert_equalspacing_xymatrix]],
2045            [379, [revert_inset_preview]],
2046            [378, [revert_math_output]],
2047            [377, []],
2048            [376, [revert_multirow]],
2049            [375, [revert_includeall]],
2050            [374, [revert_includeonly]],
2051            [373, [revert_html_options]],
2052            [372, [revert_gbrief]],
2053            [371, [revert_fontenc]],
2054            [370, [revert_mhchem]],
2055            [369, [revert_suppress_date]],
2056            [368, [revert_author_id]],
2057            [367, [revert_hspace_glue_lengths]],
2058            [366, [revert_percent_vspace_lengths, revert_percent_hspace_lengths]],
2059            [365, [revert_percent_skip_lengths]],
2060            [364, [revert_paragraph_indentation]],
2061            [363, [revert_branch_filename]],
2062            [362, [revert_longtable_align]],
2063            [361, [revert_applemac]],
2064            [360, []],
2065            [359, [revert_nomencl_cwidth]],
2066            [358, [revert_nomencl_width]],
2067            [357, [revert_custom_processors]],
2068            [356, [revert_ulinelatex]],
2069            [355, []],
2070            [354, [revert_strikeout]],
2071            [353, [revert_printindexall]],
2072            [352, [revert_subindex]],
2073            [351, [revert_splitindex]],
2074            [350, [revert_backgroundcolor]],
2075            [349, [revert_outputformat]],
2076            [348, [revert_xetex]],
2077            [347, [revert_phantom, revert_hphantom, revert_vphantom]],
2078            [346, [revert_tabularvalign]],
2079            [345, [revert_swiss]]
2080           ]
2081
2082
2083 if __name__ == "__main__":
2084     pass