]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_0.py
Move DrawStrategy enum to update_flags.h.
[lyx.git] / lib / lyx2lyx / lyx_2_0.py
1 # This file is part of lyx2lyx
2 # Copyright (C) 2011 The LyX team
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17
18 """Convert files to the file format generated by lyx 2.0"""
19
20 import re
21
22 from lyx2lyx_tools import (
23     add_to_preamble,
24     hex2ratio,
25     insert_to_preamble,
26     latex_length,
27     lyx2latex,
28     put_cmd_in_ert,
29     revert_flex_inset,
30     revert_font_attrs,
31     revert_language,
32     str2bool,
33 )
34 from parser_tools import (
35     check_token,
36     del_complete_lines,
37     del_token,
38     find_end_of,
39     find_end_of_inset,
40     find_end_of_layout,
41     find_token,
42     find_token_backwards,
43     find_token_exact,
44     get_option_value,
45     get_quoted_value,
46     get_value,
47     is_in_inset,
48 )
49
50 ####################################################################
51 # Private helper functions
52
53
54 def remove_option(lines, m, option):
55     """removes option from line m. returns whether we did anything"""
56     l = lines[m].find(option)
57     if l == -1:
58         return False
59     val = lines[m][l:].split('"')[1]
60     lines[m] = lines[m][: l - 1] + lines[m][l + len(option + '="' + val + '"') :]
61     return True
62
63
64 ###############################################################################
65 ###
66 ### Conversion and reversion routines
67 ###
68 ###############################################################################
69
70
71 def revert_swiss(document):
72     "Set language german-ch to ngerman"
73     i = 0
74     if document.language == "german-ch":
75         document.language = "ngerman"
76         i = find_token(document.header, "\\language", 0)
77         if i != -1:
78             document.header[i] = "\\language ngerman"
79     j = 0
80     while True:
81         j = find_token(document.body, "\\lang german-ch", j)
82         if j == -1:
83             return
84         document.body[j] = document.body[j].replace("\\lang german-ch", "\\lang ngerman")
85         j = j + 1
86
87
88 def revert_tabularvalign(document):
89     "Revert the tabular valign option"
90     i = 0
91     while True:
92         i = find_token(document.body, "\\begin_inset Tabular", i)
93         if i == -1:
94             return
95         end = find_end_of_inset(document.body, i)
96         if end == -1:
97             document.warning("Can't find end of inset at line " + str(i))
98             i += 1
99             continue
100         fline = find_token(document.body, "<features", i, end)
101         if fline == -1:
102             document.warning("Can't find features for inset at line " + str(i))
103             i += 1
104             continue
105         p = document.body[fline].find("islongtable")
106         if p != -1:
107             q = document.body[fline].find("tabularvalignment")
108             if q != -1:
109                 document.body[fline] = re.sub(
110                     r" tabularvalignment=\"[a-z]+\"", "", document.body[fline]
111                 )
112             i += 1
113             continue
114
115         # no longtable
116         tabularvalignment = "c"
117         # which valignment is specified?
118         m = document.body[fline].find('tabularvalignment="top"')
119         if m != -1:
120             tabularvalignment = "t"
121         m = document.body[fline].find('tabularvalignment="bottom"')
122         if m != -1:
123             tabularvalignment = "b"
124         # delete tabularvalignment
125         q = document.body[fline].find("tabularvalignment")
126         if q != -1:
127             document.body[fline] = re.sub(
128                 r" tabularvalignment=\"[a-z]+\"", "", document.body[fline]
129             )
130
131         # don't add a box when centered
132         if tabularvalignment == "c":
133             i = end
134             continue
135         subst = ["\\end_inset", "\\end_layout"]
136         document.body[end:end] = subst  # just inserts those lines
137         subst = [
138             "\\begin_inset Box Frameless",
139             'position "' + tabularvalignment + '"',
140             'hor_pos "c"',
141             "has_inner_box 1",
142             'inner_pos "c"',
143             "use_parbox 0",
144             # we don't know the width, assume 50%
145             'width "50col%"',
146             'special "none"',
147             'height "1in"',
148             'height_special "totalheight"',
149             "status open",
150             "",
151             "\\begin_layout Plain Layout",
152         ]
153         document.body[i:i] = subst  # this just inserts the array at i
154         # since there could be a tabular inside a tabular, we cannot
155         # jump to end
156         i += len(subst)
157
158
159 def revert_phantom_types(document, ptype, cmd):
160     "Reverts phantom to ERT"
161     i = 0
162     while True:
163         i = find_token(document.body, "\\begin_inset Phantom " + ptype, i)
164         if i == -1:
165             return
166         end = find_end_of_inset(document.body, i)
167         if end == -1:
168             document.warning("Can't find end of inset at line " + str(i))
169             i += 1
170             continue
171         blay = find_token(document.body, "\\begin_layout Plain Layout", i, end)
172         if blay == -1:
173             document.warning("Can't find layout for inset at line " + str(i))
174             i = end
175             continue
176         bend = find_end_of_layout(document.body, blay)
177         if bend == -1:
178             document.warning(
179                 "Malformed LyX document: Could not find end of Phantom inset's layout."
180             )
181             i = end
182             continue
183         substi = [
184             "\\begin_inset ERT",
185             "status collapsed",
186             "",
187             "\\begin_layout Plain Layout",
188             "",
189             "",
190             "\\backslash",
191             cmd + "{",
192             "\\end_layout",
193             "",
194             "\\end_inset",
195         ]
196         substj = [
197             "\\size default",
198             "",
199             "\\begin_inset ERT",
200             "status collapsed",
201             "",
202             "\\begin_layout Plain Layout",
203             "",
204             "}",
205             "\\end_layout",
206             "",
207             "\\end_inset",
208         ]
209         # do the later one first so as not to mess up the numbering
210         document.body[bend : end + 1] = substj
211         document.body[i : blay + 1] = substi
212         i = end + len(substi) + len(substj) - (end - bend) - (blay - i) - 2
213
214
215 def revert_phantom(document):
216     revert_phantom_types(document, "Phantom", "phantom")
217
218
219 def revert_hphantom(document):
220     revert_phantom_types(document, "HPhantom", "hphantom")
221
222
223 def revert_vphantom(document):
224     revert_phantom_types(document, "VPhantom", "vphantom")
225
226
227 def revert_xetex(document):
228     "Reverts documents that use XeTeX"
229
230     i = find_token(document.header, "\\use_xetex", 0)
231     if i == -1:
232         document.warning("Malformed LyX document: Missing \\use_xetex.")
233         return
234     if not str2bool(get_value(document.header, "\\use_xetex", i)):
235         del document.header[i]
236         return
237     del document.header[i]
238
239     # 1.) set doc encoding to utf8-plain
240     i = find_token(document.header, "\\inputencoding", 0)
241     if i == -1:
242         document.warning("Malformed LyX document: Missing \\inputencoding.")
243     else:
244         document.header[i] = "\\inputencoding utf8-plain"
245
246     # 2.) check font settings
247     # defaults
248     roman = sans = typew = "default"
249     osf = False
250     sf_scale = tt_scale = 100.0
251
252     i = find_token(document.header, "\\font_roman", 0)
253     if i == -1:
254         document.warning("Malformed LyX document: Missing \\font_roman.")
255     else:
256         roman = get_value(document.header, "\\font_roman", i)
257         document.header[i] = "\\font_roman default"
258
259     i = find_token(document.header, "\\font_sans", 0)
260     if i == -1:
261         document.warning("Malformed LyX document: Missing \\font_sans.")
262     else:
263         sans = get_value(document.header, "\\font_sans", i)
264         document.header[i] = "\\font_sans default"
265
266     i = find_token(document.header, "\\font_typewriter", 0)
267     if i == -1:
268         document.warning("Malformed LyX document: Missing \\font_typewriter.")
269     else:
270         typew = get_value(document.header, "\\font_typewriter", i)
271         document.header[i] = "\\font_typewriter default"
272
273     i = find_token(document.header, "\\font_osf", 0)
274     if i == -1:
275         document.warning("Malformed LyX document: Missing \\font_osf.")
276     else:
277         osf = str2bool(get_value(document.header, "\\font_osf", i))
278         document.header[i] = "\\font_osf false"
279
280     i = find_token(document.header, "\\font_sc", 0)
281     if i == -1:
282         document.warning("Malformed LyX document: Missing \\font_sc.")
283     else:
284         # we do not need this value.
285         document.header[i] = "\\font_sc false"
286
287     i = find_token(document.header, "\\font_sf_scale", 0)
288     if i == -1:
289         document.warning("Malformed LyX document: Missing \\font_sf_scale.")
290     else:
291         val = get_value(document.header, "\\font_sf_scale", i)
292         try:
293             # float() can throw
294             sf_scale = float(val)
295         except:
296             document.warning("Invalid font_sf_scale value: " + val)
297         document.header[i] = "\\font_sf_scale 100"
298
299     i = find_token(document.header, "\\font_tt_scale", 0)
300     if i == -1:
301         document.warning("Malformed LyX document: Missing \\font_tt_scale.")
302     else:
303         val = get_value(document.header, "\\font_tt_scale", i)
304         try:
305             # float() can throw
306             tt_scale = float(val)
307         except:
308             document.warning("Invalid font_tt_scale value: " + val)
309         document.header[i] = "\\font_tt_scale 100"
310
311     # 3.) set preamble stuff
312     pretext = ["%% This document must be processed with xelatex!"]
313     pretext.append("\\usepackage{fontspec}")
314     if roman != "default":
315         pretext.append("\\setmainfont[Mapping=tex-text]{" + roman + "}")
316     if sans != "default":
317         sf = "\\setsansfont["
318         if sf_scale != 100.0:
319             sf += "Scale=" + str(sf_scale / 100.0) + ","
320         sf += "Mapping=tex-text]{" + sans + "}"
321         pretext.append(sf)
322     if typew != "default":
323         tw = "\\setmonofont"
324         if tt_scale != 100.0:
325             tw += "[Scale=" + str(tt_scale / 100.0) + "]"
326         tw += "{" + typew + "}"
327         pretext.append(tw)
328     if osf:
329         pretext.append("\\defaultfontfeatures{Numbers=OldStyle}")
330     pretext.append("\\usepackage{xunicode}")
331     pretext.append("\\usepackage{xltxtra}")
332     insert_to_preamble(document, pretext)
333
334
335 def revert_outputformat(document):
336     "Remove default output format param"
337
338     if not del_token(document.header, "\\default_output_format", 0):
339         document.warning("Malformed LyX document: Missing \\default_output_format.")
340
341
342 def revert_backgroundcolor(document):
343     "Reverts background color to preamble code"
344     i = find_token(document.header, "\\backgroundcolor", 0)
345     if i == -1:
346         return
347     colorcode = get_value(document.header, "\\backgroundcolor", i)
348     del document.header[i]
349     # don't clutter the preamble if backgroundcolor is not set
350     if colorcode == "#ffffff":
351         return
352     red = hex2ratio(colorcode[1:3])
353     green = hex2ratio(colorcode[3:5])
354     blue = hex2ratio(colorcode[5:7])
355     insert_to_preamble(
356         document,
357         [
358             "% To set the background color",
359             "\\@ifundefined{definecolor}{\\usepackage{color}}{}",
360             "\\definecolor{page_backgroundcolor}{rgb}{" + red + "," + green + "," + blue + "}",
361             "\\pagecolor{page_backgroundcolor}",
362         ],
363     )
364
365
366 def add_use_indices(document):
367     "Add \\use_indices if it is missing"
368     i = find_token(document.header, "\\use_indices", 0)
369     if i != -1:
370         return i
371     i = find_token(document.header, "\\use_bibtopic", 0)
372     if i == -1:
373         i = find_token(document.header, "\\cite_engine", 0)
374     if i == -1:
375         i = find_token(document.header, "\\use_mathdots", 0)
376     if i == -1:
377         i = find_token(document.header, "\\use_mhchem", 0)
378     if i == -1:
379         i = find_token(document.header, "\\use_esint", 0)
380     if i == -1:
381         i = find_token(document.header, "\\use_amsmath", 0)
382     if i == -1:
383         document.warning("Malformed LyX document: Missing \\use_indices.")
384         return -1
385     document.header.insert(i + 1, "\\use_indices 0")
386     return i + 1
387
388
389 def revert_splitindex(document):
390     "Reverts splitindex-aware documents"
391     i = add_use_indices(document)
392     if i == -1:
393         return
394     useindices = str2bool(get_value(document.header, "\\use_indices", i))
395     del document.header[i]
396     preamble = []
397     if useindices:
398         preamble.append("\\usepackage{splitidx})")
399
400     # deal with index declarations in the preamble
401     i = 0
402     while True:
403         i = find_token(document.header, "\\index", i)
404         if i == -1:
405             break
406         k = find_token(document.header, "\\end_index", i)
407         if k == -1:
408             document.warning("Malformed LyX document: Missing \\end_index.")
409             return
410         if useindices:
411             line = document.header[i]
412             l = re.compile(r"\\index (.*)$")
413             m = l.match(line)
414             iname = m.group(1)
415             ishortcut = get_value(document.header, "\\shortcut", i, k)
416             if ishortcut != "":
417                 preamble.append("\\newindex[" + iname + "]{" + ishortcut + "}")
418         del document.header[i : k + 1]
419     if preamble:
420         insert_to_preamble(document, preamble)
421
422     # deal with index insets
423     # these need to have the argument removed
424     i = 0
425     while True:
426         i = find_token(document.body, "\\begin_inset Index", i)
427         if i == -1:
428             break
429         line = document.body[i]
430         l = re.compile(r"\\begin_inset Index (.*)$")
431         m = l.match(line)
432         itype = m.group(1)
433         if itype == "idx" or useindices == "false":
434             document.body[i] = "\\begin_inset Index"
435         else:
436             k = find_end_of_inset(document.body, i)
437             if k == -1:
438                 document.warning("Can't find end of index inset!")
439                 i += 1
440                 continue
441             content = lyx2latex(document, document.body[i:k])
442             # escape quotes
443             content = content.replace('"', r"\"")
444             subst = put_cmd_in_ert("\\sindex[" + itype + "]{" + content + "}")
445             document.body[i : k + 1] = subst
446         i = i + 1
447
448     # deal with index_print insets
449     i = 0
450     while True:
451         i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
452         if i == -1:
453             return
454         k = find_end_of_inset(document.body, i)
455         ptype = get_quoted_value(document.body, "type", i, k)
456         if ptype == "idx":
457             j = find_token(document.body, "type", i, k)
458             del document.body[j]
459         elif not useindices:
460             del document.body[i : k + 1]
461         else:
462             subst = put_cmd_in_ert("\\printindex[" + ptype + "]{}")
463             document.body[i : k + 1] = subst
464         i = i + 1
465
466
467 def convert_splitindex(document):
468     "Converts index and printindex insets to splitindex-aware format"
469     add_use_indices(document)
470     i = 0
471     while True:
472         i = find_token(document.body, "\\begin_inset Index", i)
473         if i == -1:
474             break
475         document.body[i] = document.body[i].replace(
476             "\\begin_inset Index", "\\begin_inset Index idx"
477         )
478         i = i + 1
479     i = 0
480     while True:
481         i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
482         if i == -1:
483             return
484         if document.body[i + 1].find("LatexCommand printindex") == -1:
485             document.warning("Malformed LyX document: Incomplete printindex inset.")
486             return
487         subst = ["LatexCommand printindex", 'type "idx"']
488         document.body[i + 1 : i + 2] = subst
489         i = i + 1
490
491
492 def revert_subindex(document):
493     "Reverts \\printsubindex CommandInset types"
494     i = add_use_indices(document)
495     if i == -1:
496         return
497     useindices = str2bool(get_value(document.header, "\\use_indices", i))
498     i = 0
499     while True:
500         i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
501         if i == -1:
502             return
503         k = find_end_of_inset(document.body, i)
504         ctype = get_value(document.body, "LatexCommand", i, k)
505         if ctype != "printsubindex":
506             i = k + 1
507             continue
508         ptype = get_quoted_value(document.body, "type", i, k)
509         if not useindices:
510             del document.body[i : k + 1]
511         else:
512             subst = put_cmd_in_ert("\\printsubindex[" + ptype + "]{}")
513             document.body[i : k + 1] = subst
514         i = i + 1
515
516
517 def revert_printindexall(document):
518     "Reverts \\print[sub]index* CommandInset types"
519     i = add_use_indices(document)
520     if i == -1:
521         return
522     useindices = str2bool(get_value(document.header, "\\use_indices", i))
523     i = 0
524     while True:
525         i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
526         if i == -1:
527             return
528         k = find_end_of_inset(document.body, i)
529         ctype = get_value(document.body, "LatexCommand", i, k)
530         if ctype != "printindex*" and ctype != "printsubindex*":
531             i = k
532             continue
533         if not useindices:
534             del document.body[i : k + 1]
535         else:
536             subst = put_cmd_in_ert("\\" + ctype + "{}")
537             document.body[i : k + 1] = subst
538         i = i + 1
539
540
541 strikeout_preamble = [
542     "%  for proper underlining",
543     r"\PassOptionsToPackage{normalem}{ulem}",
544     r"\usepackage{ulem}",
545 ]
546
547
548 def convert_strikeout(document):
549     "Remove preamble code loading 'ulem' package."
550     del_complete_lines(document.preamble, ["% Added by lyx2lyx"] + strikeout_preamble)
551
552
553 def revert_strikeout(document):
554     "Reverts \\strikeout font attribute"
555     changed = revert_font_attrs(document.body, "\\uuline", "\\uuline")
556     changed = revert_font_attrs(document.body, "\\uwave", "\\uwave") or changed
557     changed = revert_font_attrs(document.body, "\\strikeout", "\\sout") or changed
558     if changed == True:
559         insert_to_preamble(document, strikeout_preamble)
560
561
562 ulinelatex_preamble = [
563     "% fix underbar in citations",
564     r"\let\cite@rig\cite",
565     r"\newcommand{\b@xcite}[2][\%]{\def\def@pt{\%}\def\pas@pt{#1}",
566     r"  \mbox{\ifx\def@pt\pas@pt\cite@rig{#2}\else\cite@rig[#1]{#2}\fi}}",
567     r"\renewcommand{\underbar}[1]{{\let\cite\b@xcite\uline{#1}}}",
568 ]
569
570
571 def convert_ulinelatex(document):
572     "Remove preamble code for \\uline font attribute."
573     del_complete_lines(document.preamble, ["% Added by lyx2lyx"] + ulinelatex_preamble)
574
575
576 def revert_ulinelatex(document):
577     "Add preamble code for \\uline font attribute in citations."
578     i = find_token(document.body, "\\bar under", 0)
579     if i == -1:
580         return
581     try:
582         document.preamble.index(r"\usepackage{ulem}")
583     except ValueError:
584         insert_to_preamble(document, strikeout_preamble)
585     insert_to_preamble(document, ulinelatex_preamble)
586
587
588 def revert_custom_processors(document):
589     "Remove bibtex_command and index_command params"
590
591     if not del_token(document.header, "\\bibtex_command", 0):
592         document.warning("Malformed LyX document: Missing \\bibtex_command.")
593
594     if not del_token(document.header, "\\index_command", 0):
595         document.warning("Malformed LyX document: Missing \\index_command.")
596
597
598 def convert_nomencl_width(document):
599     "Add set_width param to nomencl_print"
600     i = 0
601     while True:
602         i = find_token(document.body, "\\begin_inset CommandInset nomencl_print", i)
603         if i == -1:
604             break
605         document.body.insert(i + 2, 'set_width "none"')
606         i = i + 1
607
608
609 def revert_nomencl_width(document):
610     "Remove set_width param from nomencl_print"
611     i = 0
612     while True:
613         i = find_token(document.body, "\\begin_inset CommandInset nomencl_print", i)
614         if i == -1:
615             break
616         j = find_end_of_inset(document.body, i)
617         if not del_token(document.body, "set_width", i, j):
618             document.warning("Can't find set_width option for nomencl_print!")
619         i = j
620
621
622 def revert_nomencl_cwidth(document):
623     "Remove width param from nomencl_print"
624     i = 0
625     while True:
626         i = find_token(document.body, "\\begin_inset CommandInset nomencl_print", i)
627         if i == -1:
628             break
629         j = find_end_of_inset(document.body, i)
630         l = find_token(document.body, "width", i, j)
631         if l == -1:
632             i = j
633             continue
634         width = get_quoted_value(document.body, "width", i, j)
635         del document.body[l]
636         insert_to_preamble(document, ["\\setlength{\\nomlabelwidth}{" + width + "}"])
637         i = j - 1
638
639
640 def revert_applemac(document):
641     "Revert applemac encoding to auto"
642     if document.encoding != "applemac":
643         return
644     document.encoding = "auto"
645     i = find_token(document.header, "\\encoding", 0)
646     if i != -1:
647         document.header[i] = "\\encoding auto"
648
649
650 def revert_longtable_align(document):
651     "Remove longtable alignment setting"
652     i = 0
653     while True:
654         i = find_token(document.body, "\\begin_inset Tabular", i)
655         if i == -1:
656             break
657         end = find_end_of_inset(document.body, i)
658         if end == -1:
659             document.warning("Can't find end of inset at line " + str(i))
660             i += 1
661             continue
662         fline = find_token(document.body, "<features", i, end)
663         if fline == -1:
664             document.warning("Can't find features for inset at line " + str(i))
665             i += 1
666             continue
667         j = document.body[fline].find("longtabularalignment")
668         if j == -1:
669             i += 1
670             continue
671         # FIXME Is this correct? It wipes out everything after the
672         # one we found.
673         document.body[fline] = document.body[fline][: j - 1] + ">"
674         # since there could be a tabular inside this one, we
675         # cannot jump to end.
676         i += 1
677
678
679 def revert_branch_filename(document):
680     "Remove \\filename_suffix parameter from branches"
681     i = 0
682     while True:
683         i = find_token(document.header, "\\filename_suffix", i)
684         if i == -1:
685             return
686         del document.header[i]
687
688
689 def revert_paragraph_indentation(document):
690     "Revert custom paragraph indentation to preamble code"
691     i = find_token(document.header, "\\paragraph_indentation", 0)
692     if i == -1:
693         return
694     length = get_value(document.header, "\\paragraph_indentation", i)
695     # we need only remove the line if indentation is default
696     if length != "default":
697         # handle percent lengths
698         length = latex_length(length)[1]
699         insert_to_preamble(document, ["\\setlength{\\parindent}{" + length + "}"])
700     del document.header[i]
701
702
703 def revert_percent_skip_lengths(document):
704     "Revert relative lengths for paragraph skip separation to preamble code"
705     i = find_token(document.header, "\\defskip", 0)
706     if i == -1:
707         return
708     length = get_value(document.header, "\\defskip", i)
709     # only revert when a custom length was set and when
710     # it used a percent length
711     if length in ("smallskip", "medskip", "bigskip"):
712         return
713     # handle percent lengths
714     percent, length = latex_length(length)
715     if percent:
716         insert_to_preamble(document, ["\\setlength{\\parskip}{" + length + "}"])
717         # set defskip to medskip as default
718         document.header[i] = "\\defskip medskip"
719
720
721 def revert_percent_vspace_lengths(document):
722     "Revert relative VSpace lengths to ERT"
723     i = 0
724     while True:
725         i = find_token(document.body, "\\begin_inset VSpace", i)
726         if i == -1:
727             break
728         # only revert if a custom length was set and if
729         # it used a percent length
730         r = re.compile(r"\\begin_inset VSpace (.*)$")
731         m = r.match(document.body[i])
732         length = m.group(1)
733         if length in ("defskip", "smallskip", "medskip", "bigskip", "vfill"):
734             i += 1
735             continue
736         # check if the space has a star (protected space)
737         protected = document.body[i].rfind("*") != -1
738         if protected:
739             length = length.rstrip("*")
740         # handle percent lengths
741         percent, length = latex_length(length)
742         # revert the VSpace inset to ERT
743         if percent:
744             if protected:
745                 subst = put_cmd_in_ert("\\vspace*{" + length + "}")
746             else:
747                 subst = put_cmd_in_ert("\\vspace{" + length + "}")
748             document.body[i : i + 2] = subst
749         i += 1
750
751
752 def revert_percent_hspace_lengths(document):
753     "Revert relative HSpace lengths to ERT"
754     i = 0
755     while True:
756         i = find_token_exact(document.body, "\\begin_inset space \\hspace", i)
757         if i == -1:
758             break
759         j = find_end_of_inset(document.body, i)
760         if j == -1:
761             document.warning("Can't find end of inset at line " + str(i))
762             i += 1
763             continue
764         # only revert if a custom length was set...
765         length = get_value(document.body, "\\length", i + 1, j)
766         if length == "":
767             document.warning("Malformed lyx document: Missing '\\length' in Space inset.")
768             i = j
769             continue
770         protected = ""
771         if document.body[i].find("\\hspace*{}") != -1:
772             protected = "*"
773         # ...and if it used a percent length
774         percent, length = latex_length(length)
775         # revert the HSpace inset to ERT
776         if percent:
777             subst = put_cmd_in_ert("\\hspace" + protected + "{" + length + "}")
778             document.body[i : j + 1] = subst
779         # if we did a substitution, this will still be ok
780         i = j
781
782
783 def revert_hspace_glue_lengths(document):
784     "Revert HSpace glue lengths to ERT"
785     i = 0
786     while True:
787         i = find_token_exact(document.body, "\\begin_inset space \\hspace", i)
788         if i == -1:
789             break
790         j = find_end_of_inset(document.body, i)
791         if j == -1:
792             document.warning("Can't find end of inset at line " + str(i))
793             i += 1
794             continue
795         length = get_value(document.body, "\\length", i + 1, j)
796         if length == "":
797             document.warning("Malformed lyx document: Missing '\\length' in Space inset.")
798             i = j
799             continue
800         protected = ""
801         if document.body[i].find("\\hspace*{}") != -1:
802             protected = "*"
803         # only revert if the length contains a plus or minus at pos != 0
804         if length.find("-", 1) != -1 or length.find("+", 1) != -1:
805             # handle percent lengths
806             length = latex_length(length)[1]
807             # revert the HSpace inset to ERT
808             subst = put_cmd_in_ert("\\hspace" + protected + "{" + length + "}")
809             document.body[i : j + 1] = subst
810         i = j
811
812
813 def convert_author_id(document):
814     "Add the author_id to the \\author definition and make sure 0 is not used"
815     i = 0
816     anum = 1
817     re_author = re.compile(r"(\\author) (\".*\")\s*(.*)$")
818
819     while True:
820         i = find_token(document.header, "\\author", i)
821         if i == -1:
822             break
823         m = re_author.match(document.header[i])
824         if m:
825             name = m.group(2)
826             email = m.group(3)
827             document.header[i] = "\\author %i %s %s" % (anum, name, email)
828         anum += 1
829         i += 1
830
831     i = 0
832     while True:
833         i = find_token(document.body, "\\change_", i)
834         if i == -1:
835             break
836         change = document.body[i].split(" ")
837         if len(change) == 3:
838             type = change[0]
839             author_id = int(change[1])
840             time = change[2]
841             document.body[i] = "%s %i %s" % (type, author_id + 1, time)
842         i += 1
843
844
845 def revert_author_id(document):
846     "Remove the author_id from the \\author definition"
847     i = 0
848     anum = 0
849     rx = re.compile(r"(\\author)\s+(-?\d+)\s+(\".*\")\s*(.*)$")
850     idmap = dict()
851
852     while True:
853         i = find_token(document.header, "\\author", i)
854         if i == -1:
855             break
856         m = rx.match(document.header[i])
857         if m:
858             author_id = int(m.group(2))
859             idmap[author_id] = anum
860             name = m.group(3)
861             email = m.group(4)
862             document.header[i] = f"\\author {name} {email}"
863         i += 1
864         # FIXME Should this be incremented if we didn't match?
865         anum += 1
866
867     i = 0
868     while True:
869         i = find_token(document.body, "\\change_", i)
870         if i == -1:
871             break
872         change = document.body[i].split(" ")
873         if len(change) == 3:
874             type = change[0]
875             author_id = int(change[1])
876             time = change[2]
877             document.body[i] = "%s %i %s" % (type, idmap[author_id], time)
878         i += 1
879
880
881 def revert_suppress_date(document):
882     "Revert suppressing of default document date to preamble code"
883     i = find_token(document.header, "\\suppress_date", 0)
884     if i == -1:
885         return
886     # remove the preamble line and write to the preamble
887     # when suppress_date was true
888     date = str2bool(get_value(document.header, "\\suppress_date", i))
889     if date:
890         add_to_preamble(document, ["\\date{}"])
891     del document.header[i]
892
893
894 mhchem_preamble = [r"\PassOptionsToPackage{version=3}{mhchem}", r"\usepackage{mhchem}"]
895
896
897 def convert_mhchem(document):
898     "Set mhchem to off for versions older than 1.6.x"
899     if document.initial_format < 277:
900         # LyX 1.5.x and older did never load mhchem.
901         # Therefore we must switch it off: Documents that use mhchem have
902         # a manual \usepackage anyway, and documents not using mhchem but
903         # custom macros with the same names as mhchem commands might get
904         # corrupted if mhchem is automatically loaded.
905         mhchem = 0  # off
906     else:
907         # LyX 1.6.x did always load mhchem automatically.
908         mhchem = 1  # auto
909     i = find_token(document.header, "\\use_esint", 0)
910     if i == -1:
911         # pre-1.5.x document
912         i = find_token(document.header, "\\use_amsmath", 0)
913     if i == -1:
914         document.warning("Malformed LyX document: " "Could not find amsmath or esint setting.")
915         return
916     document.header.insert(i + 1, "\\use_mhchem %d" % mhchem)
917     # remove LyX-inserted preamble
918     if mhchem != 0:
919         del_complete_lines(document.preamble, ["% Added by lyx2lyx"] + mhchem_preamble)
920
921
922 def revert_mhchem(document):
923     "Revert mhchem loading to preamble code."
924
925     mhchem = get_value(document.header, "\\use_mhchem", delete=True)
926     try:
927         mhchem = int(mhchem)
928     except ValueError:
929         document.warning("Malformed LyX document: " "Could not find mhchem setting.")
930         mhchem = 1  # "auto"
931     # mhchem in {0: "off", 1: "auto", 2: "on"}
932
933     if mhchem == 1:  # "auto"
934         i = 0
935         while i != 1 and mhchem == 1:
936             i = find_token(document.body, "\\begin_inset Formula", i)
937             j = find_end_of_inset(document.body, i)
938             if j == -1:
939                 break
940             if (True for line in document.body[i:j] if r"\ce{" in line or r"\cf{" in line):
941                 mhchem = 2
942                 break
943             i += 1
944
945     if (
946         mhchem == 2  # on
947         and find_token(document.preamble, r"\usepackage{mhchem}") == -1
948     ):
949         insert_to_preamble(document, mhchem_preamble)
950
951
952 def revert_fontenc(document):
953     "Remove fontencoding param"
954     if not del_token(document.header, "\\fontencoding", 0):
955         document.warning("Malformed LyX document: Missing \\fontencoding.")
956
957
958 def merge_gbrief(document):
959     "Merge g-brief-en and g-brief-de to one class"
960
961     if document.textclass != "g-brief-de":
962         if document.textclass == "g-brief-en":
963             document.textclass = "g-brief"
964             document.set_textclass()
965         return
966
967     obsoletedby = {
968         "Brieftext": "Letter",
969         "Unterschrift": "Signature",
970         "Strasse": "Street",
971         "Zusatz": "Addition",
972         "Ort": "Town",
973         "Land": "State",
974         "RetourAdresse": "ReturnAddress",
975         "MeinZeichen": "MyRef",
976         "IhrZeichen": "YourRef",
977         "IhrSchreiben": "YourMail",
978         "Telefon": "Phone",
979         "BLZ": "BankCode",
980         "Konto": "BankAccount",
981         "Postvermerk": "PostalComment",
982         "Adresse": "Address",
983         "Datum": "Date",
984         "Betreff": "Reference",
985         "Anrede": "Opening",
986         "Anlagen": "Encl.",
987         "Verteiler": "cc",
988         "Gruss": "Closing",
989     }
990     i = 0
991     while True:
992         i = find_token(document.body, "\\begin_layout", i)
993         if i == -1:
994             break
995
996         layout = document.body[i][14:]
997         if layout in obsoletedby:
998             document.body[i] = "\\begin_layout " + obsoletedby[layout]
999
1000         i += 1
1001
1002     document.textclass = "g-brief"
1003     document.set_textclass()
1004
1005
1006 def revert_gbrief(document):
1007     "Revert g-brief to g-brief-en"
1008     if document.textclass == "g-brief":
1009         document.textclass = "g-brief-en"
1010         document.set_textclass()
1011
1012
1013 def revert_html_options(document):
1014     "Remove html options"
1015     del_token(document.header, "\\html_use_mathml", 0)
1016     del_token(document.header, "\\html_be_strict", 0)
1017
1018
1019 def revert_includeonly(document):
1020     i = 0
1021     while True:
1022         i = find_token(document.header, "\\begin_includeonly", i)
1023         if i == -1:
1024             return
1025         j = find_end_of(document.header, i, "\\begin_includeonly", "\\end_includeonly")
1026         if j == -1:
1027             document.warning("Unable to find end of includeonly section!!")
1028             break
1029         document.header[i : j + 1] = []
1030
1031
1032 def convert_includeall(document):
1033     "Add maintain_unincluded_children param"
1034
1035     i = 0
1036     i = find_token(document.header, "\\maintain_unincluded_children", 0)
1037     if i == -1:
1038         i = find_token(document.header, "\\textclass", 0)
1039         if i == -1:
1040             document.warning("Malformed LyX document! Missing \\textclass header.")
1041             return
1042         document.header.insert(i, "\\maintain_unincluded_children false")
1043         return
1044
1045
1046 def revert_includeall(document):
1047     "Remove maintain_unincluded_children param"
1048     del_token(document.header, "\\maintain_unincluded_children", 0)
1049
1050
1051 def revert_multirow(document):
1052     "Revert multirow cells in tables to TeX-code"
1053
1054     # first, let's find out if we need to do anything
1055     # cell type 3 is multirow begin cell
1056     i = find_token(document.body, '<cell multirow="3"', 0)
1057     if i == -1:
1058         return
1059
1060     add_to_preamble(document, ["\\usepackage{multirow}"])
1061
1062     begin_table = 0
1063     while True:
1064         # find begin/end of table
1065         begin_table = find_token(document.body, "<lyxtabular version=", begin_table)
1066         if begin_table == -1:
1067             break
1068         end_table = find_end_of(document.body, begin_table, "<lyxtabular", "</lyxtabular>")
1069         if end_table == -1:
1070             document.warning("Malformed LyX document: Could not find end of table.")
1071             begin_table += 1
1072             continue
1073         # does this table have multirow?
1074         i = find_token(document.body, '<cell multirow="3"', begin_table, end_table)
1075         if i == -1:
1076             begin_table = end_table
1077             continue
1078
1079         # store the number of rows and columns
1080         numrows = get_option_value(document.body[begin_table], "rows")
1081         numcols = get_option_value(document.body[begin_table], "columns")
1082         try:
1083             numrows = int(numrows)
1084             numcols = int(numcols)
1085         except:
1086             document.warning("Unable to determine rows and columns!")
1087             begin_table = end_table
1088             continue
1089
1090         mrstarts = []
1091         multirows = []
1092         # collect info on rows and columns of this table.
1093         begin_row = begin_table
1094         for row in range(numrows):
1095             begin_row = find_token(document.body, "<row>", begin_row, end_table)
1096             if begin_row == -1:
1097                 document.warning("Can't find row " + str(row + 1))
1098                 break
1099             end_row = find_end_of(document.body, begin_row, "<row>", "</row>")
1100             if end_row == -1:
1101                 document.warning("Can't find end of row " + str(row + 1))
1102                 break
1103             begin_cell = begin_row
1104             multirows.append([])
1105             for column in range(numcols):
1106                 begin_cell = find_token(document.body, "<cell ", begin_cell, end_row)
1107                 if begin_cell == -1:
1108                     document.warning(
1109                         "Can't find column " + str(column + 1) + "in row " + str(row + 1)
1110                     )
1111                     break
1112                 # NOTE
1113                 # this will fail if someone puts "</cell>" in a cell, but
1114                 # that seems fairly unlikely.
1115                 end_cell = find_end_of(document.body, begin_cell, "<cell", "</cell>")
1116                 if end_cell == -1:
1117                     document.warning(
1118                         "Can't find end of column " + str(column + 1) + "in row " + str(row + 1)
1119                     )
1120                     break
1121                 multirows[row].append([begin_cell, end_cell, 0])
1122                 if document.body[begin_cell].find('multirow="3"') != -1:
1123                     multirows[row][column][2] = 3  # begin multirow
1124                     mrstarts.append([row, column])
1125                 elif document.body[begin_cell].find('multirow="4"') != -1:
1126                     multirows[row][column][2] = 4  # in multirow
1127                 begin_cell = end_cell
1128             begin_row = end_row
1129         # end of table info collection
1130
1131         # work from the back to avoid messing up numbering
1132         mrstarts.reverse()
1133         for m in mrstarts:
1134             row = m[0]
1135             col = m[1]
1136             # get column width
1137             col_width = get_option_value(document.body[begin_table + 2 + col], "width")
1138             # "0pt" means that no width is specified
1139             if not col_width or col_width == "0pt":
1140                 col_width = "*"
1141             # determine the number of cells that are part of the multirow
1142             nummrs = 1
1143             for r in range(row + 1, numrows):
1144                 if multirows[r][col][2] != 4:
1145                     break
1146                 nummrs += 1
1147                 # take the opportunity to revert this line
1148                 lineno = multirows[r][col][0]
1149                 document.body[lineno] = (
1150                     document.body[lineno]
1151                     .replace(' multirow="4" ', " ")
1152                     .replace('valignment="middle"', 'valignment="top"')
1153                     .replace(' topline="true" ', " ")
1154                 )
1155                 # remove bottom line of previous multirow-part cell
1156                 lineno = multirows[r - 1][col][0]
1157                 document.body[lineno] = document.body[lineno].replace(
1158                     ' bottomline="true" ', " "
1159                 )
1160             # revert beginning cell
1161             bcell = multirows[row][col][0]
1162             ecell = multirows[row][col][1]
1163             document.body[bcell] = (
1164                 document.body[bcell]
1165                 .replace(' multirow="3" ', " ")
1166                 .replace('valignment="middle"', 'valignment="top"')
1167             )
1168             blay = find_token(document.body, "\\begin_layout", bcell, ecell)
1169             if blay == -1:
1170                 document.warning("Can't find layout for cell!")
1171                 continue
1172             bend = find_end_of_layout(document.body, blay)
1173             if bend == -1:
1174                 document.warning("Can't find end of layout for cell!")
1175                 continue
1176             # do the later one first, so as not to mess up the numbering
1177             # we are wrapping the whole cell in this ert
1178             # so before the end of the layout...
1179             document.body[bend:bend] = put_cmd_in_ert("}")
1180             # ...and after the beginning
1181             document.body[blay + 1 : blay + 1] = put_cmd_in_ert(
1182                 "\\multirow{" + str(nummrs) + "}{" + col_width + "}{"
1183             )
1184
1185         begin_table = end_table
1186
1187
1188 def convert_math_output(document):
1189     r"Convert \html_use_mathml to \html_math_output"
1190     i = find_token(document.header, "\\html_use_mathml", 0)
1191     if i == -1:
1192         return
1193     rgx = re.compile(r"\\html_use_mathml\s+(\w+)")
1194     m = rgx.match(document.header[i])
1195     newval = "0"  # MathML
1196     if m:
1197         val = str2bool(m.group(1))
1198         if not val:
1199             newval = "2"  # Images
1200     else:
1201         document.warning("Can't match " + document.header[i])
1202     document.header[i] = "\\html_math_output " + newval
1203
1204
1205 def revert_math_output(document):
1206     r"Revert \html_math_output to \html_use_mathml"
1207     i = find_token(document.header, "\\html_math_output", 0)
1208     if i == -1:
1209         return
1210     rgx = re.compile(r"\\html_math_output\s+(\d)")
1211     m = rgx.match(document.header[i])
1212     newval = "true"
1213     if m:
1214         val = m.group(1)
1215         if val == "1" or val == "2":
1216             newval = "false"
1217     else:
1218         document.warning("Unable to match " + document.header[i])
1219     document.header[i] = "\\html_use_mathml " + newval
1220
1221
1222 def revert_inset_preview(document):
1223     "Dissolves the preview inset"
1224     i = 0
1225     while True:
1226         i = find_token(document.body, "\\begin_inset Preview", i)
1227         if i == -1:
1228             return
1229         iend = find_end_of_inset(document.body, i)
1230         if iend == -1:
1231             document.warning("Malformed LyX document: Could not find end of Preview inset.")
1232             i += 1
1233             continue
1234
1235         # This has several issues.
1236         # We need to do something about the layouts inside InsetPreview.
1237         # If we just leave the first one, then we have something like:
1238         # \begin_layout Standard
1239         # ...
1240         # \begin_layout Standard
1241         # and we get a "no \end_layout" error. So something has to be done.
1242         # Ideally, we would check if it is the same as the layout we are in.
1243         # If so, we just remove it; if not, we end the active one. But it is
1244         # not easy to know what layout we are in, due to depth changes, etc,
1245         # and it is not clear to me how much work it is worth doing. In most
1246         # cases, the layout will probably be the same.
1247         #
1248         # For the same reason, we have to remove the \end_layout tag at the
1249         # end of the last layout in the inset. Again, that will sometimes be
1250         # wrong, but it will usually be right. To know what to do, we would
1251         # again have to know what layout the inset is in.
1252
1253         blay = find_token(document.body, "\\begin_layout", i, iend)
1254         if blay == -1:
1255             document.warning("Can't find layout for preview inset!")
1256             # always do the later one first...
1257             del document.body[iend]
1258             del document.body[i]
1259             # deletions mean we do not need to reset i
1260             continue
1261
1262         # This is where we would check what layout we are in.
1263         # The check for Standard is definitely wrong.
1264         #
1265         # lay = document.body[blay].split(None, 1)[1]
1266         # if lay != oldlayout:
1267         #     # record a boolean to tell us what to do later....
1268         #     # better to do it later, since (a) it won't mess up
1269         #     # the numbering and (b) we only modify at the end.
1270
1271         # we want to delete the last \\end_layout in this inset, too.
1272         # note that this may not be the \\end_layout that goes with blay!!
1273         bend = find_end_of_layout(document.body, blay)
1274         while True:
1275             tmp = find_token(document.body, "\\end_layout", bend + 1, iend)
1276             if tmp == -1:
1277                 break
1278             bend = tmp
1279         if bend == blay:
1280             document.warning("Unable to find last layout in preview inset!")
1281             del document.body[iend]
1282             del document.body[i]
1283             # deletions mean we do not need to reset i
1284             continue
1285         # always do the later one first...
1286         del document.body[iend]
1287         del document.body[bend]
1288         del document.body[i : blay + 1]
1289         # we do not need to reset i
1290
1291
1292 def revert_equalspacing_xymatrix(document):
1293     "Revert a Formula with xymatrix@! to an ERT inset"
1294     i = 0
1295     has_preamble = False
1296     has_equal_spacing = False
1297
1298     while True:
1299         i = find_token(document.body, "\\begin_inset Formula", i)
1300         if i == -1:
1301             break
1302         j = find_end_of_inset(document.body, i)
1303         if j == -1:
1304             document.warning("Malformed LyX document: Could not find end of Formula inset.")
1305             i += 1
1306             continue
1307
1308         for curline in range(i, j):
1309             found = document.body[curline].find("\\xymatrix@!")
1310             if found != -1:
1311                 break
1312
1313         if found != -1:
1314             has_equal_spacing = True
1315             content = [document.body[i][21:]]
1316             content += document.body[i + 1 : j]
1317             subst = put_cmd_in_ert(content)
1318             document.body[i : j + 1] = subst
1319             i += len(subst) - (j - i) + 1
1320         else:
1321             for curline in range(i, j):
1322                 l = document.body[curline].find("\\xymatrix")
1323                 if l != -1:
1324                     has_preamble = True
1325                     break
1326             i = j + 1
1327
1328     if has_equal_spacing and not has_preamble:
1329         add_to_preamble(document, ["\\usepackage[all]{xy}"])
1330
1331
1332 def revert_notefontcolor(document):
1333     "Reverts greyed-out note font color to preamble code"
1334
1335     i = find_token(document.header, "\\notefontcolor", 0)
1336     if i == -1:
1337         return
1338
1339     colorcode = get_value(document.header, "\\notefontcolor", i)
1340     del document.header[i]
1341
1342     # are there any grey notes?
1343     if find_token(document.body, "\\begin_inset Note Greyedout", 0) == -1:
1344         # no need to do anything else, and \renewcommand will throw
1345         # an error since lyxgreyedout will not exist.
1346         return
1347
1348     # the color code is in the form #rrggbb where every character denotes a hex number
1349     red = hex2ratio(colorcode[1:3])
1350     green = hex2ratio(colorcode[3:5])
1351     blue = hex2ratio(colorcode[5:7])
1352     # write the preamble
1353     insert_to_preamble(
1354         document,
1355         [
1356             "%  for greyed-out notes",
1357             "\\@ifundefined{definecolor}{\\usepackage{color}}{}"
1358             "\\definecolor{note_fontcolor}{rgb}{%s,%s,%s}" % (red, green, blue),
1359             "\\renewenvironment{lyxgreyedout}",
1360             " {\\textcolor{note_fontcolor}\\bgroup}{\\egroup}",
1361         ],
1362     )
1363
1364
1365 def revert_turkmen(document):
1366     "Set language Turkmen to English"
1367
1368     revert_language(document, "turkmen", "turkmen", "turkmen")
1369
1370
1371 def revert_fontcolor(document):
1372     "Reverts font color to preamble code"
1373     i = find_token(document.header, "\\fontcolor", 0)
1374     if i == -1:
1375         return
1376     colorcode = get_value(document.header, "\\fontcolor", i)
1377     del document.header[i]
1378     # don't clutter the preamble if font color is not set
1379     if colorcode == "#000000":
1380         return
1381     # the color code is in the form #rrggbb where every character denotes a hex number
1382     red = hex2ratio(colorcode[1:3])
1383     green = hex2ratio(colorcode[3:5])
1384     blue = hex2ratio(colorcode[5:7])
1385     # write the preamble
1386     insert_to_preamble(
1387         document,
1388         [
1389             "%  Set the font color",
1390             "\\@ifundefined{definecolor}{\\usepackage{color}}{}",
1391             f"\\definecolor{{document_fontcolor}}{{rgb}}{{{red},{green},{blue}}}",
1392             "\\color{document_fontcolor}",
1393         ],
1394     )
1395
1396
1397 def revert_shadedboxcolor(document):
1398     "Reverts shaded box color to preamble code"
1399     i = find_token(document.header, "\\boxbgcolor", 0)
1400     if i == -1:
1401         return
1402     colorcode = get_value(document.header, "\\boxbgcolor", i)
1403     del document.header[i]
1404     # the color code is in the form #rrggbb
1405     red = hex2ratio(colorcode[1:3])
1406     green = hex2ratio(colorcode[3:5])
1407     blue = hex2ratio(colorcode[5:7])
1408     # write the preamble
1409     insert_to_preamble(
1410         document,
1411         [
1412             "%  Set the color of boxes with shaded background",
1413             "\\@ifundefined{definecolor}{\\usepackage{color}}{}",
1414             f"\\definecolor{{shadecolor}}{{rgb}}{{{red},{green},{blue}}}",
1415         ],
1416     )
1417
1418
1419 def revert_lyx_version(document):
1420     "Reverts LyX Version information from Inset Info"
1421     version = "LyX version"
1422     try:
1423         import lyx2lyx_version
1424
1425         version = lyx2lyx_version.version
1426     except:
1427         pass
1428
1429     i = 0
1430     while True:
1431         i = find_token(document.body, "\\begin_inset Info", i)
1432         if i == -1:
1433             return
1434         j = find_end_of_inset(document.body, i + 1)
1435         if j == -1:
1436             document.warning("Malformed LyX document: Could not find end of Info inset.")
1437             i += 1
1438             continue
1439
1440         # We expect:
1441         # \begin_inset Info
1442         # type  "lyxinfo"
1443         # arg   "version"
1444         # \end_inset
1445         typ = get_quoted_value(document.body, "type", i, j)
1446         arg = get_quoted_value(document.body, "arg", i, j)
1447         if arg != "version" or typ != "lyxinfo":
1448             i = j + 1
1449             continue
1450
1451         # We do not actually know the version of LyX used to produce the document.
1452         # But we can use our version, since we are reverting.
1453         s = [version]
1454         # Now we want to check if the line after "\end_inset" is empty. It normally
1455         # is, so we want to remove it, too.
1456         lastline = j + 1
1457         if document.body[j + 1].strip() == "":
1458             lastline = j + 2
1459         document.body[i:lastline] = s
1460         i = i + 1
1461
1462
1463 def revert_math_scale(document):
1464     "Remove math scaling and LaTeX options"
1465     del_token(document.header, "\\html_math_img_scale", 0)
1466     del_token(document.header, "\\html_latex_start", 0)
1467     del_token(document.header, "\\html_latex_end", 0)
1468
1469
1470 def revert_pagesizes(document):
1471     "Revert page sizes to default"
1472     i = find_token(document.header, "\\papersize", 0)
1473     if i != -1:
1474         size = document.header[i][11:]
1475         if (
1476             size == "a0paper"
1477             or size == "a1paper"
1478             or size == "a2paper"
1479             or size == "a6paper"
1480             or size == "b0paper"
1481             or size == "b1paper"
1482             or size == "b2paper"
1483             or size == "b6paper"
1484             or size == "b0j"
1485             or size == "b1j"
1486             or size == "b2j"
1487             or size == "b3j"
1488             or size == "b4j"
1489             or size == "b5j"
1490             or size == "b6j"
1491         ):
1492             del document.header[i]
1493
1494
1495 def revert_DIN_C_pagesizes(document):
1496     "Revert DIN C page sizes to default"
1497     i = find_token(document.header, "\\papersize", 0)
1498     if i != -1:
1499         size = document.header[i][11:]
1500         if (
1501             size == "c0paper"
1502             or size == "c1paper"
1503             or size == "c2paper"
1504             or size == "c3paper"
1505             or size == "c4paper"
1506             or size == "c5paper"
1507             or size == "c6paper"
1508         ):
1509             del document.header[i]
1510
1511
1512 def convert_html_quotes(document):
1513     "Remove quotes around html_latex_start and html_latex_end"
1514
1515     i = find_token(document.header, "\\html_latex_start", 0)
1516     if i != -1:
1517         line = document.header[i]
1518         l = re.compile(r'\\html_latex_start\s+"(.*)"')
1519         m = l.match(line)
1520         if m:
1521             document.header[i] = "\\html_latex_start " + m.group(1)
1522
1523     i = find_token(document.header, "\\html_latex_end", 0)
1524     if i != -1:
1525         line = document.header[i]
1526         l = re.compile(r'\\html_latex_end\s+"(.*)"')
1527         m = l.match(line)
1528         if m:
1529             document.header[i] = "\\html_latex_end " + m.group(1)
1530
1531
1532 def revert_html_quotes(document):
1533     "Remove quotes around html_latex_start and html_latex_end"
1534
1535     i = find_token(document.header, "\\html_latex_start", 0)
1536     if i != -1:
1537         line = document.header[i]
1538         l = re.compile(r"\\html_latex_start\s+(.*)")
1539         m = l.match(line)
1540         if not m:
1541             document.warning("Weird html_latex_start line: " + line)
1542             del document.header[i]
1543         else:
1544             document.header[i] = '\\html_latex_start "' + m.group(1) + '"'
1545
1546     i = find_token(document.header, "\\html_latex_end", 0)
1547     if i != -1:
1548         line = document.header[i]
1549         l = re.compile(r"\\html_latex_end\s+(.*)")
1550         m = l.match(line)
1551         if not m:
1552             document.warning("Weird html_latex_end line: " + line)
1553             del document.header[i]
1554         else:
1555             document.header[i] = '\\html_latex_end "' + m.group(1) + '"'
1556
1557
1558 def revert_output_sync(document):
1559     "Remove forward search options"
1560     del_token(document.header, "\\output_sync_macro", 0)
1561     del_token(document.header, "\\output_sync", 0)
1562
1563
1564 def revert_align_decimal(document):
1565     i = 0
1566     while True:
1567         i = find_token(document.body, "\\begin_inset Tabular", i)
1568         if i == -1:
1569             return
1570         j = find_end_of_inset(document.body, i)
1571         if j == -1:
1572             document.warning("Unable to find end of Tabular inset at line " + str(i))
1573             i += 1
1574             continue
1575         cell = find_token(document.body, "<cell", i, j)
1576         if cell == -1:
1577             document.warning("Can't find any cells in Tabular inset at line " + str(i))
1578             i = j
1579             continue
1580         k = i + 1
1581         while True:
1582             k = find_token(document.body, "<column", k, cell)
1583             if k == -1:
1584                 return
1585             if document.body[k].find('alignment="decimal"') == -1:
1586                 k += 1
1587                 continue
1588             remove_option(document.body, k, "decimal_point")
1589             document.body[k] = document.body[k].replace(
1590                 'alignment="decimal"', 'alignment="center"'
1591             )
1592             k += 1
1593
1594
1595 def convert_optarg(document):
1596     "Convert \\begin_inset OptArg to \\begin_inset Argument"
1597     i = 0
1598     while True:
1599         i = find_token(document.body, "\\begin_inset OptArg", i)
1600         if i == -1:
1601             return
1602         document.body[i] = "\\begin_inset Argument"
1603         i += 1
1604
1605
1606 def revert_argument(document):
1607     "Convert \\begin_inset Argument to \\begin_inset OptArg"
1608     i = 0
1609     while True:
1610         i = find_token(document.body, "\\begin_inset Argument", i)
1611         if i == -1:
1612             return
1613         document.body[i] = "\\begin_inset OptArg"
1614         i += 1
1615
1616
1617 def revert_makebox(document):
1618     "Convert \\makebox to TeX code"
1619     i = 0
1620     while True:
1621         i = find_token(document.body, "\\begin_inset Box", i)
1622         if i == -1:
1623             break
1624         z = find_end_of_inset(document.body, i)
1625         if z == -1:
1626             document.warning("Malformed LyX document: Can't find end of box inset.")
1627             i += 1
1628             continue
1629         blay = find_token(document.body, "\\begin_layout", i, z)
1630         if blay == -1:
1631             document.warning("Malformed LyX document: Can't find layout in box.")
1632             i = z
1633             continue
1634         j = find_token(document.body, "use_makebox", i)
1635         if j == -1 or j != i + 6:
1636             document.warning("Malformed LyX document: Can't find use_makebox statement in box.")
1637             i = z
1638             continue
1639         # delete use_makebox
1640         if (
1641             not check_token(document.body[i], "\\begin_inset Box Frameless")
1642             or get_value(document.body, "use_makebox", j) != 1
1643         ):
1644             del document.body[j]
1645             i += 1
1646             continue
1647         bend = find_end_of_layout(document.body, blay)
1648         if bend == -1 or bend > z:
1649             document.warning("Malformed LyX document: Can't find end of layout in box.")
1650             i = z
1651             continue
1652         # determine the alignment
1653         align = get_quoted_value(document.body, "hor_pos", i, blay, "c")
1654         # determine the width
1655         length = get_quoted_value(document.body, "width", i, blay, "50col%")
1656         length = latex_length(length)[1]
1657         # remove the \end_layout \end_inset pair
1658         document.body[bend : z + 1] = put_cmd_in_ert("}")
1659         subst = "\\makebox[" + length + "][" + align + "]{"
1660         document.body[i : blay + 1] = put_cmd_in_ert(subst)
1661         i += 1
1662
1663
1664 def convert_use_makebox(document):
1665     "Adds use_makebox option for boxes"
1666     i = 0
1667     while True:
1668         i = find_token(document.body, "\\begin_inset Box", i)
1669         if i == -1:
1670             return
1671         k = find_token(document.body, "use_parbox", i)
1672         if k == -1 or k != i + 5:
1673             document.warning("Malformed LyX document: Can't find use_parbox statement in box.")
1674             i += 1
1675             continue
1676         if k == i + 5:
1677             document.body.insert(k + 1, "use_makebox 0")
1678         i += 1
1679
1680
1681 def revert_IEEEtran(document):
1682     "Convert IEEEtran layouts and styles to TeX code"
1683
1684     if document.textclass != "IEEEtran":
1685         return
1686
1687     revert_flex_inset(document, "IEEE membership", "\\IEEEmembership")
1688     revert_flex_inset(document, "Lowercase", "\\MakeLowercase")
1689
1690     layouts = (
1691         "Special Paper Notice",
1692         "After Title Text",
1693         "Publication ID",
1694         "Page headings",
1695         "Biography without photo",
1696     )
1697     latexcmd = {
1698         "Special Paper Notice": "\\IEEEspecialpapernotice",
1699         "After Title Text": "\\IEEEaftertitletext",
1700         "Publication ID": "\\IEEEpubid",
1701     }
1702     obsoletedby = {
1703         "Page headings": "MarkBoth",
1704         "Biography without photo": "BiographyNoPhoto",
1705     }
1706
1707     for layout in layouts:
1708         i = 0
1709         while True:
1710             i = find_token(document.body, "\\begin_layout " + layout, i)
1711             if i == -1:
1712                 break
1713             j = find_end_of_layout(document.body, i)
1714             if j == -1:
1715                 document.warning(
1716                     "Malformed LyX document: Can't find end of " + layout + " layout."
1717                 )
1718                 i += 1
1719                 continue
1720             if layout in list(obsoletedby.keys()):
1721                 document.body[i] = "\\begin_layout " + obsoletedby[layout]
1722                 i = j
1723                 continue
1724             content = lyx2latex(document, document.body[i : j + 1])
1725             add_to_preamble(document, [latexcmd[layout] + "{" + content + "}"])
1726             del document.body[i : j + 1]
1727             # no need to reset i
1728
1729
1730 def convert_prettyref(document):
1731     "Converts prettyref references to neutral formatted refs"
1732     re_ref = re.compile('^\\s*reference\\s+"(\\w+):(\\S+)"')
1733     nm_ref = re.compile('^\\s*name\\s+"(\\w+):(\\S+)"')
1734
1735     i = 0
1736     while True:
1737         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1738         if i == -1:
1739             break
1740         j = find_end_of_inset(document.body, i)
1741         if j == -1:
1742             document.warning("Malformed LyX document: No end of InsetRef!")
1743             i += 1
1744             continue
1745         k = find_token(document.body, "LatexCommand prettyref", i, j)
1746         if k != -1:
1747             document.body[k] = "LatexCommand formatted"
1748         i = j + 1
1749     document.header.insert(-1, "\\use_refstyle 0")
1750
1751
1752 def revert_refstyle(document):
1753     "Reverts neutral formatted refs to prettyref"
1754     re_ref = re.compile('^reference\\s+"(\\w+):(\\S+)"')
1755     nm_ref = re.compile('^\\s*name\\s+"(\\w+):(\\S+)"')
1756
1757     i = 0
1758     while True:
1759         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1760         if i == -1:
1761             break
1762         j = find_end_of_inset(document.body, i)
1763         if j == -1:
1764             document.warning("Malformed LyX document: No end of InsetRef")
1765             i += 1
1766             continue
1767         k = find_token(document.body, "LatexCommand formatted", i, j)
1768         if k != -1:
1769             document.body[k] = "LatexCommand prettyref"
1770         i = j + 1
1771     i = find_token(document.header, "\\use_refstyle", 0)
1772     if i != -1:
1773         document.header.pop(i)
1774
1775
1776 def revert_nameref(document):
1777     "Convert namerefs to regular references"
1778     cmds = ["Nameref", "nameref"]
1779     foundone = False
1780     rx = re.compile(r'reference "(.*)"')
1781     for cmd in cmds:
1782         i = 0
1783         oldcmd = "LatexCommand " + cmd
1784         while True:
1785             # It seems better to look for this, as most of the reference
1786             # insets won't be ones we care about.
1787             i = find_token(document.body, oldcmd, i)
1788             if i == -1:
1789                 break
1790             cmdloc = i
1791             i += 1
1792             # Make sure it is actually in an inset!
1793             # A normal line could begin with "LatexCommand nameref"!
1794             stins, endins = is_in_inset(document.body, cmdloc, "\\begin_inset CommandInset ref")
1795             if endins == -1:
1796                 continue
1797             # ok, so it is in an InsetRef
1798             refline = find_token(document.body, "reference", stins, endins)
1799             if refline == -1:
1800                 document.warning("Can't find reference for inset at line " + stinst + "!!")
1801                 continue
1802             m = rx.match(document.body[refline])
1803             if not m:
1804                 document.warning("Can't match reference line: " + document.body[ref])
1805                 continue
1806             foundone = True
1807             ref = m.group(1)
1808             newcontent = put_cmd_in_ert("\\" + cmd + "{" + ref + "}")
1809             document.body[stins : endins + 1] = newcontent
1810
1811     if foundone:
1812         add_to_preamble(document, ["\\usepackage{nameref}"])
1813
1814
1815 def remove_Nameref(document):
1816     "Convert Nameref commands to nameref commands"
1817     i = 0
1818     while True:
1819         # It seems better to look for this, as most of the reference
1820         # insets won't be ones we care about.
1821         i = find_token(document.body, "LatexCommand Nameref", i)
1822         if i == -1:
1823             break
1824         cmdloc = i
1825         i += 1
1826         # Make sure it is actually in an inset!
1827         val = is_in_inset(
1828             document.body, cmdloc, "\\begin_inset CommandInset ref", default=False
1829         )
1830         if not val:
1831             continue
1832         document.body[cmdloc] = "LatexCommand nameref"
1833
1834
1835 def revert_mathrsfs(document):
1836     r"Load mathrsfs if \mathrsfs us use in the document"
1837     i = 0
1838     for line in document.body:
1839         if line.find("\\mathscr{") != -1:
1840             add_to_preamble(document, ["\\usepackage{mathrsfs}"])
1841             return
1842
1843
1844 def convert_flexnames(document):
1845     "Convert \\begin_inset Flex Custom:Style to \\begin_inset Flex Style and similarly for CharStyle and Element."
1846
1847     i = 0
1848     rx = re.compile(r"^\\begin_inset Flex (?:Custom|CharStyle|Element):(.+)$")
1849     while True:
1850         i = find_token(document.body, "\\begin_inset Flex", i)
1851         if i == -1:
1852             return
1853         m = rx.match(document.body[i])
1854         if m:
1855             document.body[i] = "\\begin_inset Flex " + m.group(1)
1856         i += 1
1857
1858
1859 flex_insets = {
1860     "Alert": "CharStyle:Alert",
1861     "Code": "CharStyle:Code",
1862     "Concepts": "CharStyle:Concepts",
1863     "E-Mail": "CharStyle:E-Mail",
1864     "Emph": "CharStyle:Emph",
1865     "Expression": "CharStyle:Expression",
1866     "Initial": "CharStyle:Initial",
1867     "Institute": "CharStyle:Institute",
1868     "Meaning": "CharStyle:Meaning",
1869     "Noun": "CharStyle:Noun",
1870     "Strong": "CharStyle:Strong",
1871     "Structure": "CharStyle:Structure",
1872     "ArticleMode": "Custom:ArticleMode",
1873     "Endnote": "Custom:Endnote",
1874     "Glosse": "Custom:Glosse",
1875     "PresentationMode": "Custom:PresentationMode",
1876     "Tri-Glosse": "Custom:Tri-Glosse",
1877 }
1878
1879 flex_elements = {
1880     "Abbrev": "Element:Abbrev",
1881     "CCC-Code": "Element:CCC-Code",
1882     "Citation-number": "Element:Citation-number",
1883     "City": "Element:City",
1884     "Code": "Element:Code",
1885     "CODEN": "Element:CODEN",
1886     "Country": "Element:Country",
1887     "Day": "Element:Day",
1888     "Directory": "Element:Directory",
1889     "Dscr": "Element:Dscr",
1890     "Email": "Element:Email",
1891     "Emph": "Element:Emph",
1892     "Filename": "Element:Filename",
1893     "Firstname": "Element:Firstname",
1894     "Fname": "Element:Fname",
1895     "GuiButton": "Element:GuiButton",
1896     "GuiMenu": "Element:GuiMenu",
1897     "GuiMenuItem": "Element:GuiMenuItem",
1898     "ISSN": "Element:ISSN",
1899     "Issue-day": "Element:Issue-day",
1900     "Issue-months": "Element:Issue-months",
1901     "Issue-number": "Element:Issue-number",
1902     "KeyCap": "Element:KeyCap",
1903     "KeyCombo": "Element:KeyCombo",
1904     "Keyword": "Element:Keyword",
1905     "Literal": "Element:Literal",
1906     "MenuChoice": "Element:MenuChoice",
1907     "Month": "Element:Month",
1908     "Orgdiv": "Element:Orgdiv",
1909     "Orgname": "Element:Orgname",
1910     "Postcode": "Element:Postcode",
1911     "SS-Code": "Element:SS-Code",
1912     "SS-Title": "Element:SS-Title",
1913     "State": "Element:State",
1914     "Street": "Element:Street",
1915     "Surname": "Element:Surname",
1916     "Volume": "Element:Volume",
1917     "Year": "Element:Year",
1918 }
1919
1920
1921 def revert_flexnames(document):
1922     if document.backend == "latex":
1923         flexlist = flex_insets
1924     else:
1925         flexlist = flex_elements
1926
1927     rx = re.compile(r"^\\begin_inset Flex\s+(.+)$")
1928     i = 0
1929     while True:
1930         i = find_token(document.body, "\\begin_inset Flex", i)
1931         if i == -1:
1932             return
1933         m = rx.match(document.body[i])
1934         if not m:
1935             document.warning("Illegal flex inset: " + document.body[i])
1936             i += 1
1937             continue
1938         style = m.group(1)
1939         if style in flexlist:
1940             document.body[i] = "\\begin_inset Flex " + flexlist[style]
1941         i += 1
1942
1943
1944 def convert_mathdots(document):
1945     "Load mathdots automatically"
1946     i = find_token(document.header, "\\use_mhchem", 0)
1947     if i == -1:
1948         i = find_token(document.header, "\\use_esint", 0)
1949     if i == -1:
1950         document.warning("Malformed LyX document: Can't find \\use_mhchem.")
1951         return
1952     j = find_token(document.preamble, "\\usepackage{mathdots}", 0)
1953     if j == -1:
1954         document.header.insert(i + 1, "\\use_mathdots 0")
1955     else:
1956         document.header.insert(i + 1, "\\use_mathdots 2")
1957         del document.preamble[j]
1958
1959
1960 def revert_mathdots(document):
1961     "Load mathdots if used in the document"
1962
1963     mathdots = find_token(document.header, "\\use_mathdots", 0)
1964     if mathdots == -1:
1965         document.warning("No \\use_mathdots line. Assuming auto.")
1966     else:
1967         val = get_value(document.header, "\\use_mathdots", mathdots)
1968         del document.header[mathdots]
1969         try:
1970             usedots = int(val)
1971         except:
1972             document.warning("Invalid \\use_mathdots value: " + val + ". Assuming auto.")
1973             # probably usedots has not been changed, but be safe.
1974             usedots = 1
1975
1976         if usedots == 0:
1977             # do not load case
1978             return
1979         if usedots == 2:
1980             # force load case
1981             add_to_preamble(document, ["\\usepackage{mathdots}"])
1982             return
1983
1984     # so we are in the auto case. we want to load mathdots if \iddots is used.
1985     i = 0
1986     while True:
1987         i = find_token(document.body, "\\begin_inset Formula", i)
1988         if i == -1:
1989             return
1990         j = find_end_of_inset(document.body, i)
1991         if j == -1:
1992             document.warning(
1993                 "Malformed LyX document: Can't find end of Formula inset at line " + str(i)
1994             )
1995             i += 1
1996             continue
1997         code = "\n".join(document.body[i:j])
1998         if code.find("\\iddots") != -1:
1999             add_to_preamble(document, ["\\@ifundefined{iddots}{\\usepackage{mathdots}}"])
2000             return
2001         i = j
2002
2003
2004 def convert_rule(document):
2005     "Convert \\lyxline to CommandInset line."
2006     i = 0
2007
2008     inset = [
2009         "\\begin_inset CommandInset line",
2010         "LatexCommand rule",
2011         'offset "0.5ex"',
2012         'width "100line%"',
2013         'height "1pt"',
2014         "",
2015         "\\end_inset",
2016         "",
2017         "",
2018     ]
2019
2020     # if paragraphs are indented, we may have to unindent to get the
2021     # line to be full-width.
2022     indent = get_value(document.header, "\\paragraph_separation", 0)
2023     have_indent = indent == "indent"
2024
2025     while True:
2026         i = find_token(document.body, "\\lyxline", i)
2027         if i == -1:
2028             return
2029
2030         # we need to find out if this line follows other content
2031         # in its paragraph. find its layout....
2032         lastlay = find_token_backwards(document.body, "\\begin_layout", i)
2033         if lastlay == -1:
2034             document.warning("Can't find layout for line at " + str(i))
2035             # do the best we can.
2036             document.body[i : i + 1] = inset
2037             i += len(inset)
2038             continue
2039
2040         # ...and look for other content before it.
2041         lineisfirst = True
2042         for line in document.body[lastlay + 1 : i]:
2043             # is it empty or a paragraph option?
2044             if not line or line[0] == "\\":
2045                 continue
2046             lineisfirst = False
2047             break
2048
2049         if lineisfirst:
2050             document.body[i : i + 1] = inset
2051             if indent:
2052                 # we need to unindent, lest the line be too long
2053                 document.body.insert(lastlay + 1, "\\noindent")
2054             i += len(inset)
2055         else:
2056             # so our line is in the middle of a paragraph
2057             # we need to add a new line, lest this line follow the
2058             # other content on that line and run off the side of the page
2059             document.body[i : i + 1] = inset
2060             document.body[i:i] = ["\\begin_inset Newline newline", "\\end_inset", ""]
2061         i += len(inset)
2062
2063
2064 def revert_rule(document):
2065     "Revert line insets to Tex code"
2066     i = 0
2067     while True:
2068         i = find_token(document.body, "\\begin_inset CommandInset line", i)
2069         if i == -1:
2070             return
2071         # find end of inset
2072         j = find_token(document.body, "\\end_inset", i)
2073         if j == -1:
2074             document.warning("Malformed LyX document: Can't find end of line inset.")
2075             return
2076         # determine the optional offset
2077         offset = get_quoted_value(document.body, "offset", i, j)
2078         if offset:
2079             offset = "[" + offset + "]"
2080         # determine the width
2081         width = get_quoted_value(document.body, "width", i, j, "100col%")
2082         width = latex_length(width)[1]
2083         # determine the height
2084         height = get_quoted_value(document.body, "height", i, j, "1pt")
2085         height = latex_length(height)[1]
2086         # output the \rule command
2087         subst = "\\rule[" + offset + "]{" + width + "}{" + height + "}"
2088         document.body[i : j + 1] = put_cmd_in_ert(subst)
2089         i += len(subst) - (j - i)
2090
2091
2092 def revert_diagram(document):
2093     "Add the feyn package if \\Diagram is used in math"
2094     i = 0
2095     while True:
2096         i = find_token(document.body, "\\begin_inset Formula", i)
2097         if i == -1:
2098             return
2099         j = find_end_of_inset(document.body, i)
2100         if j == -1:
2101             document.warning("Malformed LyX document: Can't find end of Formula inset.")
2102             return
2103         lines = "\n".join(document.body[i:j])
2104         if lines.find("\\Diagram") == -1:
2105             i = j
2106             continue
2107         add_to_preamble(document, ["\\usepackage{feyn}"])
2108         # only need to do it once!
2109         return
2110
2111
2112 chapters = (
2113     "amsbook",
2114     "book",
2115     "docbook-book",
2116     "elsart",
2117     "extbook",
2118     "extreport",
2119     "jbook",
2120     "jreport",
2121     "jsbook",
2122     "literate-book",
2123     "literate-report",
2124     "memoir",
2125     "mwbk",
2126     "mwrep",
2127     "recipebook",
2128     "report",
2129     "scrbook",
2130     "scrreprt",
2131     "svmono",
2132     "svmult",
2133     "tbook",
2134     "treport",
2135     "tufte-book",
2136 )
2137
2138
2139 def convert_bibtex_clearpage(document):
2140     "insert a clear(double)page before bibliographystyle if bibtotoc option is used"
2141
2142     if document.textclass not in chapters:
2143         return
2144
2145     i = find_token(document.header, "\\papersides", 0)
2146     sides = 0
2147     if i == -1:
2148         document.warning("Malformed LyX document: Can't find papersides definition.")
2149         document.warning("Assuming single sided.")
2150         sides = 1
2151     else:
2152         val = get_value(document.header, "\\papersides", i)
2153         try:
2154             sides = int(val)
2155         except:
2156             pass
2157         if sides != 1 and sides != 2:
2158             document.warning("Invalid papersides value: " + val)
2159             document.warning("Assuming single sided.")
2160             sides = 1
2161
2162     j = 0
2163     while True:
2164         j = find_token(document.body, "\\begin_inset CommandInset bibtex", j)
2165         if j == -1:
2166             return
2167
2168         k = find_end_of_inset(document.body, j)
2169         if k == -1:
2170             document.warning("Can't find end of Bibliography inset at line " + str(j))
2171             j += 1
2172             continue
2173
2174         # only act if there is the option "bibtotoc"
2175         val = get_value(document.body, "options", j, k)
2176         if not val:
2177             document.warning("Can't find options for bibliography inset at line " + str(j))
2178             j = k
2179             continue
2180
2181         if val.find("bibtotoc") == -1:
2182             j = k
2183             continue
2184
2185         # so we want to insert a new page right before the paragraph that
2186         # this bibliography thing is in.
2187         lay = find_token_backwards(document.body, "\\begin_layout", j)
2188         if lay == -1:
2189             document.warning(
2190                 "Can't find layout containing bibliography inset at line " + str(j)
2191             )
2192             j = k
2193             continue
2194
2195         if sides == 1:
2196             cmd = "clearpage"
2197         else:
2198             cmd = "cleardoublepage"
2199         subst = [
2200             "\\begin_layout Standard",
2201             "\\begin_inset Newpage " + cmd,
2202             "\\end_inset",
2203             "",
2204             "",
2205             "\\end_layout",
2206             "",
2207         ]
2208         document.body[lay:lay] = subst
2209         j = k + len(subst)
2210
2211
2212 def revert_bibtex_clearpage(document):
2213     "remove clear(double)page before bibliographystyle if bibtotoc option is used"
2214
2215     if document.textclass not in chapters:
2216         return
2217
2218     i = find_token(document.header, "\\papersides", 0)
2219     sides = 0
2220     if i == -1:
2221         document.warning("Malformed LyX document: Can't find papersides definition.")
2222         document.warning("Assuming single sided.")
2223         sides = 1
2224     else:
2225         val = get_value(document.header, "\\papersides", i)
2226         try:
2227             sides = int(val)
2228         except:
2229             pass
2230         if sides != 1 and sides != 2:
2231             document.warning("Invalid papersides value: " + val)
2232             document.warning("Assuming single sided.")
2233             sides = 1
2234
2235     j = 0
2236     while True:
2237         j = find_token(document.body, "\\begin_inset CommandInset bibtex", j)
2238         if j == -1:
2239             return
2240
2241         k = find_end_of_inset(document.body, j)
2242         if k == -1:
2243             document.warning("Can't find end of Bibliography inset at line " + str(j))
2244             j += 1
2245             continue
2246
2247         # only act if there is the option "bibtotoc"
2248         val = get_value(document.body, "options", j, k)
2249         if not val:
2250             document.warning("Can't find options for bibliography inset at line " + str(j))
2251             j = k
2252             continue
2253
2254         if val.find("bibtotoc") == -1:
2255             j = k
2256             continue
2257
2258         # we had inserted \\clear[double]page right before the paragraph that
2259         # this bibliography thing is in. Remove this. The older format has the
2260         # respective command hardcoded.
2261         lay = find_token_backwards(document.body, "\\begin_layout", j)
2262         if lay == -1:
2263             document.warning(
2264                 "Can't find layout containing bibliography inset at line " + str(j)
2265             )
2266             j = k
2267             continue
2268         # Find the layout before this.
2269         lay = find_token_backwards(document.body, "\\begin_layout", lay - 1)
2270         if lay == -1:
2271             document.warning("Can't find layout before bibliography inset at line " + str(j))
2272             j = k
2273             continue
2274
2275         if sides == 1:
2276             cmd = "clearpage"
2277         else:
2278             cmd = "cleardoublepage"
2279
2280         if (
2281             document.body[lay] != "\\begin_layout Standard"
2282             or document.body[lay + 1] != "\\begin_inset Newpage " + cmd
2283         ):
2284             j = k
2285             continue
2286         layend = find_end_of_layout(document.body, lay)
2287         if layend == -1:
2288             document.warning(
2289                 "Can't find end of layout containg newpage inset at line " + str(layend)
2290             )
2291             j += 1
2292             continue
2293
2294         del document.body[lay : layend + 1]
2295         j = lay
2296
2297
2298 def check_passthru(document):
2299     tc = document.textclass
2300     ok = tc == "literate-article" or tc == "literate-book" or tc == "literate-report"
2301     if not ok:
2302         mods = document.get_module_list()
2303         for mod in mods:
2304             if mod == "sweave" or mod == "noweb":
2305                 ok = True
2306                 break
2307     return ok
2308
2309
2310 def convert_passthru(document):
2311     "http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg161298.html"
2312     if not check_passthru:
2313         return
2314
2315     rx = re.compile("\\\\begin_layout \\s*(\\w+)")
2316     beg = 0
2317     for lay in ["Chunk", "Scrap"]:
2318         while True:
2319             beg = find_token(document.body, "\\begin_layout " + lay, beg)
2320             if beg == -1:
2321                 break
2322             end = find_end_of_layout(document.body, beg)
2323             if end == -1:
2324                 document.warning("Can't find end of layout at line " + str(beg))
2325                 beg += 1
2326                 continue
2327
2328             # we are now going to replace newline insets within this layout
2329             # by new instances of this layout. so we have repeated layouts
2330             # instead of newlines.
2331
2332             # if the paragraph has any customization, however, we do not want to
2333             # do the replacement.
2334             if document.body[beg + 1].startswith("\\"):
2335                 beg = end + 1
2336                 continue
2337
2338             ns = beg
2339             while True:
2340                 ns = find_token(document.body, "\\begin_inset Newline newline", ns, end)
2341                 if ns == -1:
2342                     break
2343                 ne = find_end_of_inset(document.body, ns)
2344                 if ne == -1 or ne > end:
2345                     document.warning("Can't find end of inset at line " + str(ne))
2346                     ns += 1
2347                     continue
2348                 if document.body[ne + 1] == "":
2349                     ne += 1
2350                 subst = ["\\end_layout", "", "\\begin_layout " + lay]
2351                 document.body[ns : ne + 1] = subst
2352                 # now we need to adjust end, in particular, but might as well
2353                 # do ns properly, too
2354                 newlines = (ne - ns) - len(subst)
2355                 ns += newlines + 2
2356                 end += newlines + 2
2357
2358             # ok, we now want to find out if the next layout is the
2359             # same as this one. if so, we will insert an extra copy of it
2360             didit = False
2361             next = find_token(document.body, "\\begin_layout", end)
2362             if next != -1:
2363                 m = rx.match(document.body[next])
2364                 if m:
2365                     nextlay = m.group(1)
2366                     if nextlay == lay:
2367                         subst = ["\\begin_layout " + lay, "", "\\end_layout", ""]
2368                         document.body[next:next] = subst
2369                         didit = True
2370             beg = end + 1
2371             if didit:
2372                 beg += 4  # for the extra layout
2373
2374
2375 def revert_passthru(document):
2376     "http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg161298.html"
2377     if not check_passthru:
2378         return
2379     rx = re.compile("\\\\begin_layout \\s*(\\w+)")
2380     beg = 0
2381     for lay in ["Chunk", "Scrap"]:
2382         while True:
2383             beg = find_token(document.body, "\\begin_layout " + lay, beg)
2384             if beg == -1:
2385                 break
2386             end = find_end_of_layout(document.body, beg)
2387             if end == -1:
2388                 document.warning("Can't find end of layout at line " + str(beg))
2389                 beg += 1
2390                 continue
2391
2392             # we now want to find out if the next layout is the
2393             # same as this one. but we will need to do this over and
2394             # over again.
2395             while True:
2396                 next = find_token(document.body, "\\begin_layout", end)
2397                 if next == -1:
2398                     break
2399                 m = rx.match(document.body[next])
2400                 if not m:
2401                     break
2402                 nextlay = m.group(1)
2403                 if nextlay != lay:
2404                     break
2405                 # so it is the same layout again. we now want to know if it is empty.
2406                 # but first let's check and make sure there is no content between the
2407                 # two layouts. i'm not sure if that can happen or not.
2408                 for l in range(end + 1, next):
2409                     if document.body[l] != "":
2410                         document.warning("Found content between adjacent " + lay + " layouts!")
2411                         break
2412                 nextend = find_end_of_layout(document.body, next)
2413                 if nextend == -1:
2414                     document.warning("Can't find end of layout at line " + str(next))
2415                     break
2416                 empty = True
2417                 for l in range(next + 1, nextend):
2418                     if document.body[l] != "":
2419                         empty = False
2420                         break
2421                 if empty:
2422                     # empty layouts just get removed
2423                     # should we check if it's before yet another such layout?
2424                     del document.body[next : nextend + 1]
2425                     # and we do not want to check again. we know the next layout
2426                     # should be another Chunk and should be left as is.
2427                     break
2428                 else:
2429                     # if it's not empty, then we want to insert a newline in place
2430                     # of the layout switch
2431                     subst = ["\\begin_inset Newline newline", "\\end_inset", ""]
2432                     document.body[end : next + 1] = subst
2433                     # and now we have to find the end of the new, larger layout
2434                     newend = find_end_of_layout(document.body, beg)
2435                     if newend == -1:
2436                         document.warning("Can't find end of new layout at line " + str(beg))
2437                         break
2438                     end = newend
2439             beg = end + 1
2440
2441
2442 def revert_multirowOffset(document):
2443     "Revert multirow cells with offset in tables to TeX-code"
2444     # this routine is the same as the revert_multirow routine except that
2445     # it checks additionally for the offset
2446
2447     # first, let's find out if we need to do anything
2448     i = find_token(document.body, '<cell multirow="3" mroffset=', 0)
2449     if i == -1:
2450         return
2451
2452     add_to_preamble(document, ["\\usepackage{multirow}"])
2453
2454     rgx = re.compile(r'mroffset="[^"]+?"')
2455     begin_table = 0
2456
2457     while True:
2458         # find begin/end of table
2459         begin_table = find_token(document.body, "<lyxtabular version=", begin_table)
2460         if begin_table == -1:
2461             break
2462         end_table = find_end_of(document.body, begin_table, "<lyxtabular", "</lyxtabular>")
2463         if end_table == -1:
2464             document.warning("Malformed LyX document: Could not find end of table.")
2465             begin_table += 1
2466             continue
2467         # does this table have multirow?
2468         i = find_token(document.body, '<cell multirow="3"', begin_table, end_table)
2469         if i == -1:
2470             begin_table = end_table
2471             continue
2472
2473         # store the number of rows and columns
2474         numrows = get_option_value(document.body[begin_table], "rows")
2475         numcols = get_option_value(document.body[begin_table], "columns")
2476         try:
2477             numrows = int(numrows)
2478             numcols = int(numcols)
2479         except:
2480             document.warning("Unable to determine rows and columns!")
2481             begin_table = end_table
2482             continue
2483
2484         mrstarts = []
2485         multirows = []
2486         # collect info on rows and columns of this table.
2487         begin_row = begin_table
2488         for row in range(numrows):
2489             begin_row = find_token(document.body, "<row>", begin_row, end_table)
2490             if begin_row == -1:
2491                 document.warning("Can't find row " + str(row + 1))
2492                 break
2493             end_row = find_end_of(document.body, begin_row, "<row>", "</row>")
2494             if end_row == -1:
2495                 document.warning("Can't find end of row " + str(row + 1))
2496                 break
2497             begin_cell = begin_row
2498             multirows.append([])
2499             for column in range(numcols):
2500                 begin_cell = find_token(document.body, "<cell ", begin_cell, end_row)
2501                 if begin_cell == -1:
2502                     document.warning(
2503                         "Can't find column " + str(column + 1) + "in row " + str(row + 1)
2504                     )
2505                     break
2506                 # NOTE
2507                 # this will fail if someone puts "</cell>" in a cell, but
2508                 # that seems fairly unlikely.
2509                 end_cell = find_end_of(document.body, begin_cell, "<cell", "</cell>")
2510                 if end_cell == -1:
2511                     document.warning(
2512                         "Can't find end of column " + str(column + 1) + "in row " + str(row + 1)
2513                     )
2514                     break
2515                 multirows[row].append([begin_cell, end_cell, 0])
2516                 if document.body[begin_cell].find('multirow="3" mroffset=') != -1:
2517                     multirows[row][column][2] = 3  # begin multirow
2518                     mrstarts.append([row, column])
2519                 elif document.body[begin_cell].find('multirow="4"') != -1:
2520                     multirows[row][column][2] = 4  # in multirow
2521                 begin_cell = end_cell
2522             begin_row = end_row
2523         # end of table info collection
2524
2525         # work from the back to avoid messing up numbering
2526         mrstarts.reverse()
2527         for m in mrstarts:
2528             row = m[0]
2529             col = m[1]
2530             # get column width
2531             col_width = get_option_value(document.body[begin_table + 2 + col], "width")
2532             # "0pt" means that no width is specified
2533             if not col_width or col_width == "0pt":
2534                 col_width = "*"
2535             # determine the number of cells that are part of the multirow
2536             nummrs = 1
2537             for r in range(row + 1, numrows):
2538                 if multirows[r][col][2] != 4:
2539                     break
2540                 nummrs += 1
2541                 # take the opportunity to revert this line
2542                 lineno = multirows[r][col][0]
2543                 document.body[lineno] = (
2544                     document.body[lineno]
2545                     .replace(' multirow="4" ', " ")
2546                     .replace('valignment="middle"', 'valignment="top"')
2547                     .replace(' topline="true" ', " ")
2548                 )
2549                 # remove bottom line of previous multirow-part cell
2550                 lineno = multirows[r - 1][col][0]
2551                 document.body[lineno] = document.body[lineno].replace(
2552                     ' bottomline="true" ', " "
2553                 )
2554             # revert beginning cell
2555             bcell = multirows[row][col][0]
2556             ecell = multirows[row][col][1]
2557             offset = get_option_value(document.body[bcell], "mroffset")
2558             document.body[bcell] = (
2559                 document.body[bcell]
2560                 .replace(' multirow="3" ', " ")
2561                 .replace('valignment="middle"', 'valignment="top"')
2562             )
2563             # remove mroffset option
2564             document.body[bcell] = rgx.sub("", document.body[bcell])
2565
2566             blay = find_token(document.body, "\\begin_layout", bcell, ecell)
2567             if blay == -1:
2568                 document.warning("Can't find layout for cell!")
2569                 continue
2570             bend = find_end_of_layout(document.body, blay)
2571             if bend == -1:
2572                 document.warning("Can't find end of layout for cell!")
2573                 continue
2574             # do the later one first, so as not to mess up the numbering
2575             # we are wrapping the whole cell in this ert
2576             # so before the end of the layout...
2577             document.body[bend:bend] = put_cmd_in_ert("}")
2578             # ...and after the beginning
2579             document.body[blay + 1 : blay + 1] = put_cmd_in_ert(
2580                 "\\multirow{" + str(nummrs) + "}{" + col_width + "}[" + offset + "]{"
2581             )
2582
2583         # on to the next table
2584         begin_table = end_table
2585
2586
2587 def revert_script(document):
2588     "Convert subscript/superscript inset to TeX code"
2589     i = 0
2590     foundsubscript = False
2591     while True:
2592         i = find_token(document.body, "\\begin_inset script", i)
2593         if i == -1:
2594             break
2595         z = find_end_of_inset(document.body, i)
2596         if z == -1:
2597             document.warning("Malformed LyX document: Can't find end of script inset.")
2598             i += 1
2599             continue
2600         blay = find_token(document.body, "\\begin_layout", i, z)
2601         if blay == -1:
2602             document.warning("Malformed LyX document: Can't find layout in script inset.")
2603             i = z
2604             continue
2605
2606         if check_token(document.body[i], "\\begin_inset script subscript"):
2607             subst = "\\textsubscript{"
2608             foundsubscript = True
2609         elif check_token(document.body[i], "\\begin_inset script superscript"):
2610             subst = "\\textsuperscript{"
2611         else:
2612             document.warning("Malformed LyX document: Unknown type of script inset.")
2613             i = z
2614             continue
2615         bend = find_end_of_layout(document.body, blay)
2616         if bend == -1 or bend > z:
2617             document.warning(
2618                 "Malformed LyX document: Can't find end of layout in script inset."
2619             )
2620             i = z
2621             continue
2622         # remove the \end_layout \end_inset pair
2623         document.body[bend : z + 1] = put_cmd_in_ert("}")
2624         document.body[i : blay + 1] = put_cmd_in_ert(subst)
2625         i += 1
2626     # these classes provide a \textsubscript command:
2627     # FIXME: Would be nice if we could use the information of the .layout file here
2628     classes = ["memoir", "scrartcl", "scrbook", "scrlttr2", "scrreprt"]
2629     if foundsubscript and find_token_exact(classes, document.textclass, 0) == -1:
2630         add_to_preamble(document, ["\\usepackage{subscript}"])
2631
2632
2633 def convert_use_xetex(document):
2634     "convert \\use_xetex to \\use_non_tex_fonts"
2635     i = find_token(document.header, "\\use_xetex", 0)
2636     if i == -1:
2637         document.header.insert(-1, "\\use_non_tex_fonts 0")
2638     else:
2639         val = get_value(document.header, "\\use_xetex", 0)
2640         document.header[i] = "\\use_non_tex_fonts " + val
2641
2642
2643 def revert_use_xetex(document):
2644     "revert \\use_non_tex_fonts to \\use_xetex"
2645     i = 0
2646     i = find_token(document.header, "\\use_non_tex_fonts", 0)
2647     if i == -1:
2648         document.warning("Malformed document. No \\use_non_tex_fonts param!")
2649         return
2650
2651     val = get_value(document.header, "\\use_non_tex_fonts", 0)
2652     document.header[i] = "\\use_xetex " + val
2653
2654
2655 def revert_labeling(document):
2656     koma = (
2657         "scrartcl",
2658         "scrarticle-beamer",
2659         "scrbook",
2660         "scrlettr",
2661         "scrlttr2",
2662         "scrreprt",
2663     )
2664     if document.textclass in koma:
2665         return
2666     i = 0
2667     while True:
2668         i = find_token_exact(document.body, "\\begin_layout Labeling", i)
2669         if i == -1:
2670             return
2671         document.body[i] = "\\begin_layout List"
2672
2673
2674 def revert_langpack(document):
2675     "revert \\language_package parameter"
2676     i = 0
2677     i = find_token(document.header, "\\language_package", 0)
2678     if i == -1:
2679         document.warning("Malformed document. No \\language_package param!")
2680         return
2681
2682     del document.header[i]
2683
2684
2685 def convert_langpack(document):
2686     "Add \\language_package parameter"
2687     i = find_token(document.header, r"\language", 0)
2688     if i == -1:
2689         document.warning("Malformed document. No \\language defined!")
2690         return
2691
2692     document.header.insert(i + 1, "\\language_package default")
2693
2694
2695 def revert_tabularwidth(document):
2696     i = 0
2697     while True:
2698         i = find_token(document.body, "\\begin_inset Tabular", i)
2699         if i == -1:
2700             return
2701         j = find_end_of_inset(document.body, i)
2702         if j == -1:
2703             document.warning("Unable to find end of Tabular inset at line " + str(i))
2704             i += 1
2705             continue
2706         i += 1
2707         features = find_token(document.body, "<features", i, j)
2708         if features == -1:
2709             document.warning("Can't find any features in Tabular inset at line " + str(i))
2710             i = j
2711             continue
2712         if document.body[features].find('alignment="tabularwidth"') != -1:
2713             remove_option(document.body, features, "tabularwidth")
2714
2715
2716 def revert_html_css_as_file(document):
2717     if not del_token(document.header, "\\html_css_as_file", 0):
2718         document.warning("Malformed LyX document: Missing \\html_css_as_file.")
2719
2720
2721 ##
2722 # Conversion hub
2723 #
2724
2725 supported_versions = ["2.0.0", "2.0"]
2726 convert = [
2727     [346, []],
2728     [347, []],
2729     [348, []],
2730     [349, []],
2731     [350, []],
2732     [351, []],
2733     [352, [convert_splitindex]],
2734     [353, []],
2735     [354, []],
2736     [355, [convert_strikeout]],
2737     [356, []],
2738     [357, [convert_ulinelatex]],
2739     [358, []],
2740     [359, [convert_nomencl_width]],
2741     [360, []],
2742     [361, []],
2743     [362, []],
2744     [363, []],
2745     [364, []],
2746     [365, []],
2747     [366, []],
2748     [367, []],
2749     [368, []],
2750     [369, [convert_author_id]],
2751     [370, []],
2752     [371, [convert_mhchem]],
2753     [372, []],
2754     [373, [merge_gbrief]],
2755     [374, []],
2756     [375, []],
2757     [376, [convert_includeall]],
2758     [377, []],
2759     [378, []],
2760     [379, [convert_math_output]],
2761     [380, []],
2762     [381, []],
2763     [382, []],
2764     [383, []],
2765     [384, []],
2766     [385, []],
2767     [386, []],
2768     [387, []],
2769     [388, []],
2770     [389, [convert_html_quotes]],
2771     [390, []],
2772     [391, []],
2773     [392, []],
2774     [393, [convert_optarg]],
2775     [394, [convert_use_makebox]],
2776     [395, []],
2777     [396, []],
2778     [397, [remove_Nameref]],
2779     [398, []],
2780     [399, [convert_mathdots]],
2781     [400, [convert_rule]],
2782     [401, []],
2783     [402, [convert_bibtex_clearpage]],
2784     [403, [convert_flexnames]],
2785     [404, [convert_prettyref]],
2786     [405, []],
2787     [406, [convert_passthru]],
2788     [407, []],
2789     [408, []],
2790     [409, [convert_use_xetex]],
2791     [410, []],
2792     [411, [convert_langpack]],
2793     [412, []],
2794     [413, []],
2795 ]
2796
2797 revert = [
2798     [412, [revert_html_css_as_file]],
2799     [411, [revert_tabularwidth]],
2800     [410, [revert_langpack]],
2801     [409, [revert_labeling]],
2802     [408, [revert_use_xetex]],
2803     [407, [revert_script]],
2804     [406, [revert_multirowOffset]],
2805     [405, [revert_passthru]],
2806     [404, []],
2807     [403, [revert_refstyle]],
2808     [402, [revert_flexnames]],
2809     [401, [revert_bibtex_clearpage]],
2810     [400, [revert_diagram]],
2811     [399, [revert_rule]],
2812     [398, [revert_mathdots]],
2813     [397, [revert_mathrsfs]],
2814     [396, []],
2815     [395, [revert_nameref]],
2816     [394, [revert_DIN_C_pagesizes]],
2817     [393, [revert_makebox]],
2818     [392, [revert_argument]],
2819     [391, []],
2820     [390, [revert_align_decimal, revert_IEEEtran]],
2821     [389, [revert_output_sync]],
2822     [388, [revert_html_quotes]],
2823     [387, [revert_pagesizes]],
2824     [386, [revert_math_scale]],
2825     [385, [revert_lyx_version]],
2826     [384, [revert_shadedboxcolor]],
2827     [383, [revert_fontcolor]],
2828     [382, [revert_turkmen]],
2829     [381, [revert_notefontcolor]],
2830     [380, [revert_equalspacing_xymatrix]],
2831     [379, [revert_inset_preview]],
2832     [378, [revert_math_output]],
2833     [377, []],
2834     [376, [revert_multirow]],
2835     [375, [revert_includeall]],
2836     [374, [revert_includeonly]],
2837     [373, [revert_html_options]],
2838     [372, [revert_gbrief]],
2839     [371, [revert_fontenc]],
2840     [370, [revert_mhchem]],
2841     [369, [revert_suppress_date]],
2842     [368, [revert_author_id]],
2843     [367, [revert_hspace_glue_lengths]],
2844     [366, [revert_percent_vspace_lengths, revert_percent_hspace_lengths]],
2845     [365, [revert_percent_skip_lengths]],
2846     [364, [revert_paragraph_indentation]],
2847     [363, [revert_branch_filename]],
2848     [362, [revert_longtable_align]],
2849     [361, [revert_applemac]],
2850     [360, []],
2851     [359, [revert_nomencl_cwidth]],
2852     [358, [revert_nomencl_width]],
2853     [357, [revert_custom_processors]],
2854     [356, [revert_ulinelatex]],
2855     [355, []],
2856     [354, [revert_strikeout]],
2857     [353, [revert_printindexall]],
2858     [352, [revert_subindex]],
2859     [351, [revert_splitindex]],
2860     [350, [revert_backgroundcolor]],
2861     [349, [revert_outputformat]],
2862     [348, [revert_xetex]],
2863     [347, [revert_phantom, revert_hphantom, revert_vphantom]],
2864     [346, [revert_tabularvalign]],
2865     [345, [revert_swiss]],
2866 ]
2867
2868
2869 if __name__ == "__main__":
2870     pass