]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_2.py
Reformat lyx2lyx code using ruff
[lyx.git] / lib / lyx2lyx / lyx_2_2.py
1 # This file is part of lyx2lyx
2 # Copyright (C) 2015 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17
18 """Convert files to the file format generated by lyx 2.2"""
19
20 import re, string
21 import unicodedata
22 import sys, os
23
24 # Uncomment only what you need to import, please.
25
26 from lyx2lyx_tools import (
27     add_to_preamble,
28     put_cmd_in_ert,
29     get_ert,
30     lyx2latex,
31     lyx2verbatim,
32     length_in_bp,
33     convert_info_insets,
34     insert_document_option,
35     revert_language,
36 )
37
38 from parser_tools import (
39     check_token,
40     del_complete_lines,
41     find_end_of_inset,
42     find_end_of_layout,
43     find_nonempty_line,
44     find_re,
45     find_substring,
46     find_token,
47     find_token_backwards,
48     get_containing_layout,
49     get_containing_inset,
50     get_quoted_value,
51     get_value,
52     is_in_inset,
53     get_bool_value,
54     set_bool_value,
55 )
56
57
58 ####################################################################
59 # Private helper functions
60
61
62 def revert_Argument_to_TeX_brace(document, line, endline, n, nmax, environment, opt, nolastopt):
63     r"""
64     Reverts an InsetArgument to TeX-code
65     usage:
66     revert_Argument_to_TeX_brace(document, LineOfBegin, LineOfEnd, StartArgument, EndArgument, isEnvironment, isOpt, notLastOpt)
67     LineOfBegin is the line  of the \begin_layout or \begin_inset statement
68     LineOfEnd is the line  of the \end_layout or \end_inset statement, if "0" is given, the end of the file is used instead
69     StartArgument is the number of the first argument that needs to be converted
70     EndArgument is the number of the last argument that needs to be converted or the last defined one
71     isEnvironment must be true, if the layout is for a LaTeX environment
72     isOpt must be true, if the argument is an optional one
73     notLastOpt must be true if the argument is mandatory and followed by optional ones
74     """
75     lineArg = 0
76     wasOpt = False
77     while lineArg != -1 and n < nmax + 1:
78         lineArg = find_token(document.body, "\\begin_inset Argument " + str(n), line)
79         if lineArg > endline and endline != 0:
80             return wasOpt
81         if lineArg != -1:
82             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
83             # we have to assure that no other inset is in the Argument
84             beginInset = find_token(document.body, "\\begin_inset", beginPlain)
85             endInset = find_token(document.body, "\\end_inset", beginPlain)
86             k = beginPlain + 1
87             l = k
88             while beginInset < endInset and beginInset != -1:
89                 beginInset = find_token(document.body, "\\begin_inset", k)
90                 endInset = find_token(document.body, "\\end_inset", l)
91                 k = beginInset + 1
92                 l = endInset + 1
93             if environment == False:
94                 if opt == False:
95                     document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}")
96                     document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("{")
97                     wasOpt = False
98                 else:
99                     document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("]")
100                     document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("[")
101                     wasOpt = True
102             else:
103                 if opt == False:
104                     document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}")
105                     document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("{")
106                     wasOpt = False
107                 else:
108                     document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("]")
109                     document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("[")
110                     wasOpt = True
111             n += 1
112     return wasOpt
113
114
115 ###############################################################################
116 ###
117 ### Conversion and reversion routines
118 ###
119 ###############################################################################
120
121
122 def convert_longtable_label_internal(document, forward):
123     """
124     Convert reference to "LongTableNoNumber" into "Unnumbered" if forward is True
125     else revert it.
126     """
127     old_reference = "\\begin_inset Caption LongTableNoNumber"
128     new_reference = "\\begin_inset Caption Unnumbered"
129
130     # if the purpose is to revert swap the strings roles
131     if not forward:
132         old_reference, new_reference = new_reference, old_reference
133
134     i = 0
135     while True:
136         i = find_token(document.body, old_reference, i)
137
138         if i == -1:
139             return
140
141         document.body[i] = new_reference
142
143
144 def convert_longtable_label(document):
145     convert_longtable_label_internal(document, True)
146
147
148 def revert_longtable_label(document):
149     convert_longtable_label_internal(document, False)
150
151
152 def convert_separator(document):
153     """
154     Convert layout separators to separator insets and add (LaTeX) paragraph
155     breaks in order to mimic previous LaTeX export.
156     """
157
158     parins = ["\\begin_inset Separator parbreak", "\\end_inset", ""]
159     parlay = [
160         "\\begin_layout Standard",
161         "\\begin_inset Separator parbreak",
162         "\\end_inset",
163         "",
164         "\\end_layout",
165         "",
166     ]
167     sty_dict = {
168         "family": "default",
169         "series": "default",
170         "shape": "default",
171         "size": "default",
172         "bar": "default",
173         "color": "inherit",
174     }
175
176     i = 0
177     while True:
178         i = find_token(document.body, "\\begin_deeper", i)
179         if i == -1:
180             break
181
182         j = find_token_backwards(document.body, "\\end_layout", i - 1)
183         if j != -1:
184             # reset any text style before inserting the inset
185             lay = get_containing_layout(document.body, j - 1)
186             if lay != False:
187                 content = "\n".join(document.body[lay[1] : lay[2]])
188                 for val in list(sty_dict.keys()):
189                     if content.find("\\%s" % val) != -1:
190                         document.body[j:j] = [f"\\{val} {sty_dict[val]}"]
191                         i = i + 1
192                         j = j + 1
193             document.body[j:j] = parins
194             i = i + len(parins) + 1
195         else:
196             i = i + 1
197
198     i = 0
199     while True:
200         i = find_token(document.body, "\\align", i)
201         if i == -1:
202             break
203
204         lay = get_containing_layout(document.body, i)
205         if lay != False and lay[0] == "Plain Layout":
206             i = i + 1
207             continue
208
209         j = find_token_backwards(document.body, "\\end_layout", i - 1)
210         if j != -1:
211             # Very old LyX files do not have Plain Layout in insets (but Standard).
212             # So we additionally check here if there is no inset boundary
213             # between the previous layout and this one.
214             n = find_token(document.body, "\\end_inset", j, lay[1])
215             if n != -1:
216                 i = i + 1
217                 continue
218             lay = get_containing_layout(document.body, j - 1)
219             if (
220                 lay != False
221                 and lay[0] == "Standard"
222                 and find_token(document.body, "\\align", lay[1], lay[2]) == -1
223                 and find_token(document.body, "\\begin_inset VSpace", lay[1], lay[2]) == -1
224             ):
225                 # reset any text style before inserting the inset
226                 content = "\n".join(document.body[lay[1] : lay[2]])
227                 for val in list(sty_dict.keys()):
228                     if content.find("\\%s" % val) != -1:
229                         document.body[j:j] = [f"\\{val} {sty_dict[val]}"]
230                         i = i + 1
231                         j = j + 1
232                 document.body[j:j] = parins
233                 i = i + len(parins) + 1
234             else:
235                 i = i + 1
236         else:
237             i = i + 1
238
239     regexp = re.compile(
240         r"^\\begin_layout (?:(-*)|(\s*))(Separator|EndOfSlide)(?:(-*)|(\s*))$",
241         re.IGNORECASE,
242     )
243
244     i = 0
245     while True:
246         i = find_re(document.body, regexp, i)
247         if i == -1:
248             return
249
250         j = find_end_of_layout(document.body, i)
251         if j == -1:
252             document.warning("Malformed LyX document: Missing `\\end_layout'.")
253             return
254
255         lay = get_containing_layout(document.body, j - 1)
256         if lay != False:
257             lines = document.body[lay[3] : lay[2]]
258         else:
259             lines = []
260
261         document.body[i : j + 1] = parlay
262         if len(lines) > 0:
263             document.body[i + 1 : i + 1] = lines
264
265         i = i + len(parlay) + len(lines) + 1
266
267
268 def revert_separator(document):
269     "Revert separator insets to layout separators"
270
271     beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
272     if document.textclass in beamer_classes:
273         beglaysep = "\\begin_layout Separator"
274     else:
275         beglaysep = "\\begin_layout --Separator--"
276
277     parsep = [beglaysep, "", "\\end_layout", ""]
278     comert = [
279         "\\begin_inset ERT",
280         "status collapsed",
281         "",
282         "\\begin_layout Plain Layout",
283         "%",
284         "\\end_layout",
285         "",
286         "\\end_inset",
287         "",
288     ]
289     empert = [
290         "\\begin_inset ERT",
291         "status collapsed",
292         "",
293         "\\begin_layout Plain Layout",
294         " ",
295         "\\end_layout",
296         "",
297         "\\end_inset",
298         "",
299     ]
300
301     i = 0
302     while True:
303         i = find_token(document.body, "\\begin_inset Separator", i)
304         if i == -1:
305             return
306
307         lay = get_containing_layout(document.body, i)
308         if lay == False:
309             document.warning(
310                 "Malformed LyX document: Can't convert separator inset at line " + str(i)
311             )
312             i = i + 1
313             continue
314
315         layoutname = lay[0]
316         beg = lay[1]
317         end = lay[2]
318         kind = get_value(document.body, "\\begin_inset Separator", i, i + 1, "plain").split()[1]
319         before = document.body[beg + 1 : i]
320         something_before = len(before) > 0 and len("".join(before)) > 0
321         j = find_end_of_inset(document.body, i)
322         after = document.body[j + 1 : end]
323         something_after = len(after) > 0 and len("".join(after)) > 0
324         if kind == "plain":
325             beg = beg + len(before) + 1
326         elif something_before:
327             document.body[i:i] = ["\\end_layout", ""]
328             i = i + 2
329             j = j + 2
330             beg = i
331             end = end + 2
332
333         if kind == "plain":
334             if something_after:
335                 document.body[beg : j + 1] = empert
336                 i = i + len(empert)
337             else:
338                 document.body[beg : j + 1] = comert
339                 i = i + len(comert)
340         else:
341             if something_after:
342                 if layoutname == "Standard":
343                     if not something_before:
344                         document.body[beg : j + 1] = parsep
345                         i = i + len(parsep)
346                         document.body[i:i] = ["", "\\begin_layout Standard"]
347                         i = i + 2
348                     else:
349                         document.body[beg : j + 1] = ["\\begin_layout Standard"]
350                         i = i + 1
351                 else:
352                     document.body[beg : j + 1] = ["\\begin_deeper"]
353                     i = i + 1
354                     end = end + 1 - (j + 1 - beg)
355                     if not something_before:
356                         document.body[i:i] = parsep
357                         i = i + len(parsep)
358                         end = end + len(parsep)
359                     document.body[i:i] = ["\\begin_layout Standard"]
360                     document.body[end + 2 : end + 2] = ["", "\\end_deeper", ""]
361                     i = i + 4
362             else:
363                 next_par_is_aligned = False
364                 k = find_nonempty_line(document.body, end + 1)
365                 if k != -1 and check_token(document.body[k], "\\begin_layout"):
366                     lay = get_containing_layout(document.body, k)
367                     next_par_is_aligned = (
368                         lay != False
369                         and find_token(document.body, "\\align", lay[1], lay[2]) != -1
370                     )
371                 if (
372                     k != -1
373                     and not next_par_is_aligned
374                     and not check_token(document.body[k], "\\end_deeper")
375                     and not check_token(document.body[k], "\\begin_deeper")
376                 ):
377                     if layoutname == "Standard":
378                         document.body[beg : j + 1] = [beglaysep]
379                         i = i + 1
380                     else:
381                         document.body[beg : j + 1] = ["\\begin_deeper", beglaysep]
382                         end = end + 2 - (j + 1 - beg)
383                         document.body[end + 1 : end + 1] = ["", "\\end_deeper", ""]
384                         i = i + 3
385                 else:
386                     if something_before:
387                         del document.body[i : end + 1]
388                     else:
389                         del document.body[i : end - 1]
390
391         i = i + 1
392
393
394 def convert_parbreak(document):
395     """
396     Convert parbreak separators not specifically used to separate
397     environments to latexpar separators.
398     """
399     parbreakinset = "\\begin_inset Separator parbreak"
400     i = 0
401     while True:
402         i = find_token(document.body, parbreakinset, i)
403         if i == -1:
404             return
405         lay = get_containing_layout(document.body, i)
406         if lay == False:
407             document.warning(
408                 "Malformed LyX document: " "Can't convert separator inset at line %d" % i
409             )
410             i += 1
411             continue
412         if lay[0] == "Standard":
413             # Convert only if not alone in the paragraph
414             k1 = find_nonempty_line(document.body, lay[1] + 1, i + 1)
415             k2 = find_nonempty_line(document.body, i + 1, lay[2])
416             if (k1 < i) or (k2 > i + 1) or not check_token(document.body[i], parbreakinset):
417                 document.body[i] = document.body[i].replace("parbreak", "latexpar")
418         else:
419             document.body[i] = document.body[i].replace("parbreak", "latexpar")
420         i += 1
421
422
423 def revert_parbreak(document):
424     """
425     Revert latexpar separators to parbreak separators.
426     """
427     i = 0
428     while True:
429         i = find_token(document.body, "\\begin_inset Separator latexpar", i)
430         if i == -1:
431             return
432         document.body[i] = document.body[i].replace("latexpar", "parbreak")
433         i += 1
434
435
436 def revert_smash(document):
437     "Set amsmath to on if smash commands are used"
438
439     commands = ["smash[t]", "smash[b]", "notag"]
440     i = find_token(document.header, "\\use_package amsmath", 0)
441     if i == -1:
442         document.warning("Malformed LyX document: Can't find \\use_package amsmath.")
443         return
444     value = get_value(document.header, "\\use_package amsmath", i).split()[1]
445     if value != "1":
446         # nothing to do if package is not auto but on or off
447         return
448     j = 0
449     while True:
450         j = find_token(document.body, "\\begin_inset Formula", j)
451         if j == -1:
452             return
453         k = find_end_of_inset(document.body, j)
454         if k == -1:
455             document.warning(
456                 "Malformed LyX document: Can't find end of Formula inset at line " + str(j)
457             )
458             j += 1
459             continue
460         code = "\n".join(document.body[j:k])
461         for c in commands:
462             if code.find("\\%s" % c) != -1:
463                 # set amsmath to on, since it is loaded by the newer format
464                 document.header[i] = "\\use_package amsmath 2"
465                 return
466         j = k
467
468
469 def revert_swissgerman(document):
470     "Set language german-ch-old to german"
471     i = 0
472     if document.language == "german-ch-old":
473         document.language = "german"
474         i = find_token(document.header, "\\language", 0)
475         if i != -1:
476             document.header[i] = "\\language german"
477     j = 0
478     while True:
479         j = find_token(document.body, "\\lang german-ch-old", j)
480         if j == -1:
481             return
482         document.body[j] = document.body[j].replace("\\lang german-ch-old", "\\lang german")
483         j = j + 1
484
485
486 def revert_use_package(document, pkg, commands, oldauto, supported):
487     # oldauto defines how the version we are reverting to behaves:
488     # if it is true, the old version uses the package automatically.
489     # if it is false, the old version never uses the package.
490     # If "supported" is true, the target version also supports this
491     # package natively.
492     regexp = re.compile(r"(\\use_package\s+%s)" % pkg)
493     p = find_re(document.header, regexp, 0)
494     value = "1"  # default is auto
495     if p != -1:
496         value = get_value(document.header, "\\use_package", p).split()[1]
497         if not supported:
498             del document.header[p]
499     if value == "2" and not supported:  # on
500         add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
501     elif value == "1" and not oldauto:  # auto
502         i = 0
503         while True:
504             i = find_token(document.body, "\\begin_inset Formula", i)
505             if i == -1:
506                 return
507             j = find_end_of_inset(document.body, i)
508             if j == -1:
509                 document.warning(
510                     "Malformed LyX document: Can't find end of Formula inset at line " + str(i)
511                 )
512                 i += 1
513                 continue
514             code = "\n".join(document.body[i:j])
515             for c in commands:
516                 if code.find("\\%s" % c) != -1:
517                     if supported:
518                         document.header[p] = "\\use_package " + pkg + " 2"
519                     else:
520                         add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
521                     return
522             i = j
523
524
525 mathtools_commands = [
526     "xhookrightarrow",
527     "xhookleftarrow",
528     "xRightarrow",
529     "xrightharpoondown",
530     "xrightharpoonup",
531     "xrightleftharpoons",
532     "xLeftarrow",
533     "xleftharpoondown",
534     "xleftharpoonup",
535     "xleftrightarrow",
536     "xLeftrightarrow",
537     "xleftrightharpoons",
538     "xmapsto",
539 ]
540
541
542 def revert_xarrow(document):
543     "remove use_package mathtools"
544     revert_use_package(document, "mathtools", mathtools_commands, False, True)
545
546
547 def revert_beamer_lemma(document):
548     "Reverts beamer lemma layout to ERT"
549
550     beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
551     if document.textclass not in beamer_classes:
552         return
553
554     consecutive = False
555     i = 0
556     while True:
557         i = find_token(document.body, "\\begin_layout Lemma", i)
558         if i == -1:
559             return
560         j = find_end_of_layout(document.body, i)
561         if j == -1:
562             document.warning("Malformed LyX document: Can't find end of Lemma layout")
563             i += 1
564             continue
565         arg1 = find_token(document.body, "\\begin_inset Argument 1", i, j)
566         endarg1 = find_end_of_inset(document.body, arg1)
567         arg2 = find_token(document.body, "\\begin_inset Argument 2", i, j)
568         endarg2 = find_end_of_inset(document.body, arg2)
569         subst1 = []
570         subst2 = []
571         if arg1 != -1:
572             beginPlain1 = find_token(
573                 document.body, "\\begin_layout Plain Layout", arg1, endarg1
574             )
575             if beginPlain1 == -1:
576                 document.warning("Malformed LyX document: Can't find arg1 plain Layout")
577                 i += 1
578                 continue
579             endPlain1 = find_end_of_inset(document.body, beginPlain1)
580             content1 = document.body[beginPlain1 + 1 : endPlain1 - 2]
581             subst1 = put_cmd_in_ert("<") + content1 + put_cmd_in_ert(">")
582         if arg2 != -1:
583             beginPlain2 = find_token(
584                 document.body, "\\begin_layout Plain Layout", arg2, endarg2
585             )
586             if beginPlain2 == -1:
587                 document.warning("Malformed LyX document: Can't find arg2 plain Layout")
588                 i += 1
589                 continue
590             endPlain2 = find_end_of_inset(document.body, beginPlain2)
591             content2 = document.body[beginPlain2 + 1 : endPlain2 - 2]
592             subst2 = put_cmd_in_ert("[") + content2 + put_cmd_in_ert("]")
593
594         # remove Arg insets
595         if arg1 < arg2:
596             del document.body[arg2 : endarg2 + 1]
597             if arg1 != -1:
598                 del document.body[arg1 : endarg1 + 1]
599         if arg2 < arg1:
600             del document.body[arg1 : endarg1 + 1]
601             if arg2 != -1:
602                 del document.body[arg2 : endarg2 + 1]
603
604         # index of end layout has probably changed
605         j = find_end_of_layout(document.body, i)
606         if j == -1:
607             document.warning("Malformed LyX document: Can't find end of Lemma layout")
608             i += 1
609             continue
610
611         begcmd = []
612
613         # if this is not a consecutive env, add start command
614         if not consecutive:
615             begcmd = put_cmd_in_ert("\\begin{lemma}")
616
617         # has this a consecutive lemma?
618         consecutive = document.body[j + 2] == "\\begin_layout Lemma"
619
620         # if this is not followed by a consecutive env, add end command
621         if not consecutive:
622             document.body[j : j + 1] = put_cmd_in_ert("\\end{lemma}") + ["\\end_layout"]
623
624         document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd + subst1 + subst2
625
626         i = j
627
628
629 def revert_question_env(document):
630     """
631     Reverts question and question* environments of
632     theorems-ams-extended-bytype module to ERT
633     """
634
635     # Do we use theorems-ams-extended-bytype module?
636     if not "theorems-ams-extended-bytype" in document.get_module_list():
637         return
638
639     consecutive = False
640     i = 0
641     while True:
642         i = find_token(document.body, "\\begin_layout Question", i)
643         if i == -1:
644             return
645
646         starred = document.body[i] == "\\begin_layout Question*"
647
648         j = find_end_of_layout(document.body, i)
649         if j == -1:
650             document.warning("Malformed LyX document: Can't find end of Question layout")
651             i += 1
652             continue
653
654         # if this is not a consecutive env, add start command
655         begcmd = []
656         if not consecutive:
657             if starred:
658                 begcmd = put_cmd_in_ert("\\begin{question*}")
659             else:
660                 begcmd = put_cmd_in_ert("\\begin{question}")
661
662         # has this a consecutive theorem of same type?
663         consecutive = False
664         if starred:
665             consecutive = document.body[j + 2] == "\\begin_layout Question*"
666         else:
667             consecutive = document.body[j + 2] == "\\begin_layout Question"
668
669         # if this is not followed by a consecutive env, add end command
670         if not consecutive:
671             if starred:
672                 document.body[j : j + 1] = put_cmd_in_ert("\\end{question*}") + ["\\end_layout"]
673             else:
674                 document.body[j : j + 1] = put_cmd_in_ert("\\end{question}") + ["\\end_layout"]
675
676         document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
677
678         add_to_preamble(document, "\\providecommand{\\questionname}{Question}")
679
680         if starred:
681             add_to_preamble(
682                 document,
683                 "\\theoremstyle{plain}\n" "\\newtheorem*{question*}{\\protect\\questionname}",
684             )
685         else:
686             add_to_preamble(
687                 document,
688                 "\\theoremstyle{plain}\n" "\\newtheorem{question}{\\protect\\questionname}",
689             )
690
691         i = j
692
693
694 def convert_dashes(document):
695     "convert -- and --- to \\twohyphens and \\threehyphens"
696
697     if document.backend != "latex":
698         return
699
700     i = 0
701     while True:
702         i = find_substring(document.body, "--", i + 1)
703         if i == -1:
704             break
705         line = document.body[i]
706         # skip label width string (bug 10243):
707         if line.startswith("\\labelwidthstring"):
708             continue
709         # Do not touch hyphens in some insets:
710         try:
711             value, start, end = get_containing_inset(document.body, i)
712         except TypeError:
713             # False means no (or malformed) containing inset
714             value, start, end = "no inset", -1, -1
715         # We must not replace anything in insets that store LaTeX contents in .lyx files
716         # (math and command insets without overridden read() and write() methods.
717         # Filtering out IPA and ERT makes Text::readParToken() more simple,
718         # Flex Code is logical markup, typically rendered as typewriter
719         if value.split()[0] in [
720             "CommandInset",
721             "ERT",
722             "External",
723             "Formula",
724             "FormulaMacro",
725             "Graphics",
726             "IPA",
727             "listings",
728         ] or value in ["Flex Code", "Flex URL"]:
729             i = end
730             continue
731         try:
732             layout, start, end, j = get_containing_layout(document.body, i)
733         except TypeError:  # no (or malformed) containing layout
734             document.warning("Malformed LyX document: " "Can't find layout at line %d" % i)
735             continue
736         if layout == "LyX-Code":
737             i = end
738             continue
739         # We can have an arbitrary number of consecutive hyphens.
740         # Replace as LaTeX does: First try emdash, then endash
741         line = line.replace("---", "\\threehyphens\n")
742         line = line.replace("--", "\\twohyphens\n")
743         document.body[i : i + 1] = line.split("\n")
744
745     # remove ligature breaks between dashes
746     i = 0
747     while True:
748         i = find_substring(document.body, r"-\SpecialChar \textcompwordmark{}", i + 1)
749         if i == -1:
750             break
751         if document.body[i + 1].startswith("-"):
752             document.body[i] = document.body[i].replace(
753                 r"\SpecialChar \textcompwordmark{}", document.body.pop(i + 1)
754             )
755
756
757 def revert_dashes(document):
758     """
759     Remove preamble code from 2.3->2.2 conversion.
760     Prevent ligatures of existing --- and --.
761     Revert \\twohyphens and \\threehyphens to -- and ---.
762     """
763     del_complete_lines(
764         document.preamble,
765         [
766             "% Added by lyx2lyx",
767             r"\renewcommand{\textendash}{--}",
768             r"\renewcommand{\textemdash}{---}",
769         ],
770     )
771
772     # Insert ligature breaks to prevent ligation of hyphens to dashes:
773     i = 0
774     while True:
775         i = find_substring(document.body, "--", i + 1)
776         if i == -1:
777             break
778         line = document.body[i]
779         # skip label width string (bug 10243):
780         if line.startswith("\\labelwidthstring"):
781             continue
782         # do not touch hyphens in some insets (cf. convert_dashes):
783         try:
784             value, start, end = get_containing_inset(document.body, i)
785         except TypeError:
786             # False means no (or malformed) containing inset
787             value, start, end = "no inset", -1, -1
788         if (
789             value.split()[0]
790             in [
791                 "CommandInset",
792                 "ERT",
793                 "External",
794                 "Formula",
795                 "FormulaMacro",
796                 "Graphics",
797                 "IPA",
798                 "listings",
799             ]
800             or value == "Flex URL"
801         ):
802             i = end
803             continue
804         line = line.replace("--", "-\\SpecialChar \\textcompwordmark{}\n-")
805         document.body[i : i + 1] = line.split("\n")
806
807     # Revert \twohyphens and \threehyphens:
808     i = 1
809     while i < len(document.body):
810         line = document.body[i]
811         if not line.endswith("hyphens"):
812             i += 1
813         elif line.endswith("\\twohyphens") or line.endswith("\\threehyphens"):
814             line = line.replace("\\twohyphens", "--")
815             line = line.replace("\\threehyphens", "---")
816             document.body[i] = line + document.body.pop(i + 1)
817         else:
818             i += 1
819
820
821 # order is important for the last three!
822 phrases = ["LyX", "LaTeX2e", "LaTeX", "TeX"]
823
824
825 def is_part_of_converted_phrase(line, j, phrase):
826     "is phrase part of an already converted phrase?"
827     for p in phrases:
828         converted = "\\SpecialCharNoPassThru \\" + p
829         pos = j + len(phrase) - len(converted)
830         if pos >= 0:
831             if line[pos : pos + len(converted)] == converted:
832                 return True
833     return False
834
835
836 def convert_phrases(document):
837     "convert special phrases from plain text to \\SpecialCharNoPassThru"
838
839     if document.backend != "latex":
840         return
841
842     i = 0
843     while i < len(document.body):
844         if document.body[i] and document.body[i][0] == "\\":
845             words = document.body[i].split()
846             if (
847                 len(words) > 1
848                 and words[0] == "\\begin_inset"
849                 and words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]
850             ):
851                 # must not replace anything in insets that store LaTeX contents in .lyx files
852                 # (math and command insets without overridden read() and write() methods)
853                 j = find_end_of_inset(document.body, i)
854                 if j == -1:
855                     document.warning(
856                         "Malformed LyX document: Can't find end of inset at line %d" % (i)
857                     )
858                     i += 1
859                 else:
860                     i = j
861             else:
862                 i += 1
863             continue
864         for phrase in phrases:
865             j = document.body[i].find(phrase)
866             if j == -1:
867                 continue
868             if not is_part_of_converted_phrase(document.body[i], j, phrase):
869                 front = document.body[i][:j]
870                 back = document.body[i][j + len(phrase) :]
871                 if len(back) > 0:
872                     document.body.insert(i + 1, back)
873                 # We cannot use SpecialChar since we do not know whether we are outside passThru
874                 document.body[i] = front + "\\SpecialCharNoPassThru \\" + phrase
875         i += 1
876
877
878 def revert_phrases(document):
879     "revert special phrases to plain text"
880
881     i = 0
882     while i < len(document.body):
883         words = document.body[i].split()
884         if (
885             len(words) > 1
886             and words[0] == "\\begin_inset"
887             and words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]
888         ):
889             # see convert_phrases
890             j = find_end_of_inset(document.body, i)
891             if j == -1:
892                 document.warning(
893                     "Malformed LyX document: Can't find end of Formula inset at line " + str(i)
894                 )
895                 i += 1
896             else:
897                 i = j
898             continue
899         replaced = False
900         for phrase in phrases:
901             # we can replace SpecialChar since LyX ensures that it cannot be inserted into passThru parts
902             if document.body[i].find("\\SpecialChar \\" + phrase) >= 0:
903                 document.body[i] = document.body[i].replace("\\SpecialChar \\" + phrase, phrase)
904                 replaced = True
905             if document.body[i].find("\\SpecialCharNoPassThru \\" + phrase) >= 0:
906                 document.body[i] = document.body[i].replace(
907                     "\\SpecialCharNoPassThru \\" + phrase, phrase
908                 )
909                 replaced = True
910         if (
911             replaced
912             and i + 1 < len(document.body)
913             and (
914                 document.body[i + 1].find("\\") != 0
915                 or document.body[i + 1].find("\\SpecialChar") == 0
916             )
917             and len(document.body[i]) + len(document.body[i + 1]) <= 80
918         ):
919             document.body[i] = document.body[i] + document.body[i + 1]
920             document.body[i + 1 : i + 2] = []
921             i -= 1
922         i += 1
923
924
925 def convert_specialchar_internal(document, forward):
926     specialchars = {
927         "\\-": "softhyphen",
928         "\\textcompwordmark{}": "ligaturebreak",
929         "\\@.": "endofsentence",
930         "\\ldots{}": "ldots",
931         "\\menuseparator": "menuseparator",
932         "\\slash{}": "breakableslash",
933         "\\nobreakdash-": "nobreakdash",
934         "\\LyX": "LyX",
935         "\\TeX": "TeX",
936         "\\LaTeX2e": "LaTeX2e",
937         "\\LaTeX": "LaTeX",  # must be after LaTeX2e
938     }
939
940     i = 0
941     while i < len(document.body):
942         if document.body[i] and document.body[i][0] == "\\":
943             words = document.body[i].split()
944             if (
945                 len(words) > 1
946                 and words[0] == "\\begin_inset"
947                 and words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]
948             ):
949                 # see convert_phrases
950                 j = find_end_of_inset(document.body, i)
951                 if j == -1:
952                     document.warning(
953                         "Malformed LyX document: Can't find end of %s inset at line %d"
954                         % (words[1], i)
955                     )
956                     i += 1
957                 else:
958                     i = j
959                 continue
960             # else...
961         if not "\\SpecialChar" in document.body[i]:
962             i += 1
963             continue
964         for key, value in specialchars.items():
965             if forward:
966                 document.body[i] = document.body[i].replace(
967                     "\\SpecialChar " + key, "\\SpecialChar " + value
968                 )
969                 document.body[i] = document.body[i].replace(
970                     "\\SpecialCharNoPassThru " + key, "\\SpecialCharNoPassThru " + value
971                 )
972             else:
973                 document.body[i] = document.body[i].replace(
974                     "\\SpecialChar " + value, "\\SpecialChar " + key
975                 )
976                 document.body[i] = document.body[i].replace(
977                     "\\SpecialCharNoPassThru " + value, "\\SpecialCharNoPassThru " + key
978                 )
979         i += 1
980
981
982 def convert_specialchar(document):
983     "convert special characters to new syntax"
984     convert_specialchar_internal(document, True)
985
986
987 def revert_specialchar(document):
988     "convert special characters to old syntax"
989     convert_specialchar_internal(document, False)
990
991
992 def revert_georgian(document):
993     "Set the document language to English but assure Georgian output"
994
995     revert_language(document, "georgian", "georgian", "")
996
997
998 def revert_sigplan_doi(document):
999     "Reverts sigplanconf DOI layout to ERT"
1000
1001     if document.textclass != "sigplanconf":
1002         return
1003
1004     i = 0
1005     while True:
1006         i = find_token(document.body, "\\begin_layout DOI", i)
1007         if i == -1:
1008             return
1009         j = find_end_of_layout(document.body, i)
1010         if j == -1:
1011             document.warning("Malformed LyX document: Can't find end of DOI layout")
1012             i += 1
1013             continue
1014
1015         content = lyx2latex(document, document.body[i : j + 1])
1016         add_to_preamble(document, ["\\doi{" + content + "}"])
1017         del document.body[i : j + 1]
1018         # no need to reset i
1019
1020
1021 def revert_ex_itemargs(document):
1022     "Reverts \\item arguments of the example environments (Linguistics module) to TeX-code"
1023
1024     if not "linguistics" in document.get_module_list():
1025         return
1026
1027     i = 0
1028     example_layouts = ["Numbered Examples (consecutive)", "Subexample"]
1029     while True:
1030         i = find_token(document.body, "\\begin_inset Argument item:", i)
1031         if i == -1:
1032             return
1033         j = find_end_of_inset(document.body, i)
1034         # Find containing paragraph layout
1035         parent = get_containing_layout(document.body, i)
1036         if parent == False:
1037             document.warning("Malformed LyX document: Can't find parent paragraph layout")
1038             i += 1
1039             continue
1040         parbeg = parent[3]
1041         layoutname = parent[0]
1042         if layoutname in example_layouts:
1043             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1044             endPlain = find_end_of_layout(document.body, beginPlain)
1045             content = document.body[beginPlain + 1 : endPlain]
1046             del document.body[i : j + 1]
1047             subst = put_cmd_in_ert("[") + content + put_cmd_in_ert("]")
1048             document.body[parbeg:parbeg] = subst
1049         i += 1
1050
1051
1052 def revert_forest(document):
1053     "Reverts the forest environment (Linguistics module) to TeX-code"
1054
1055     if not "linguistics" in document.get_module_list():
1056         return
1057
1058     i = 0
1059     while True:
1060         i = find_token(document.body, "\\begin_inset Flex Structure Tree", i)
1061         if i == -1:
1062             return
1063         j = find_end_of_inset(document.body, i)
1064         if j == -1:
1065             document.warning("Malformed LyX document: Can't find end of Structure Tree inset")
1066             i += 1
1067             continue
1068
1069         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1070         endPlain = find_end_of_layout(document.body, beginPlain)
1071         content = lyx2latex(document, document.body[beginPlain:endPlain])
1072
1073         add_to_preamble(document, ["\\usepackage{forest}"])
1074
1075         document.body[i : j + 1] = put_cmd_in_ert("\\begin{forest}" + content + "\\end{forest}")
1076         # no need to reset i
1077
1078
1079 def revert_glossgroup(document):
1080     "Reverts the GroupGlossedWords inset (Linguistics module) to TeX-code"
1081
1082     if not "linguistics" in document.get_module_list():
1083         return
1084
1085     i = 0
1086     while True:
1087         i = find_token(document.body, "\\begin_inset Flex GroupGlossedWords", i)
1088         if i == -1:
1089             return
1090         j = find_end_of_inset(document.body, i)
1091         if j == -1:
1092             document.warning(
1093                 "Malformed LyX document: Can't find end of GroupGlossedWords inset"
1094             )
1095             i += 1
1096             continue
1097
1098         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1099         endPlain = find_end_of_layout(document.body, beginPlain)
1100         content = lyx2verbatim(document, document.body[beginPlain:endPlain])
1101
1102         document.body[i : j + 1] = ["{", "", content, "", "}"]
1103         # no need to reset i
1104
1105
1106 def revert_newgloss(document):
1107     "Reverts the new Glosse insets (Linguistics module) to the old format"
1108
1109     if not "linguistics" in document.get_module_list():
1110         return
1111
1112     glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
1113     for glosse in glosses:
1114         i = 0
1115         while True:
1116             i = find_token(document.body, glosse, i)
1117             if i == -1:
1118                 break
1119             j = find_end_of_inset(document.body, i)
1120             if j == -1:
1121                 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1122                 i += 1
1123                 continue
1124
1125             arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
1126             endarg = find_end_of_inset(document.body, arg)
1127             argcontent = ""
1128             if arg != -1:
1129                 argbeginPlain = find_token(
1130                     document.body, "\\begin_layout Plain Layout", arg, endarg
1131                 )
1132                 if argbeginPlain == -1:
1133                     document.warning("Malformed LyX document: Can't find arg plain Layout")
1134                     i += 1
1135                     continue
1136                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
1137                 argcontent = lyx2verbatim(
1138                     document, document.body[argbeginPlain : argendPlain - 2]
1139                 )
1140
1141                 document.body[j:j] = [
1142                     "",
1143                     "\\begin_layout Plain Layout",
1144                     "\\backslash",
1145                     "glt ",
1146                     argcontent,
1147                     "\\end_layout",
1148                 ]
1149
1150                 # remove Arg insets and paragraph, if it only contains this inset
1151                 if (
1152                     document.body[arg - 1] == "\\begin_layout Plain Layout"
1153                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
1154                 ):
1155                     del document.body[arg - 1 : endarg + 4]
1156                 else:
1157                     del document.body[arg : endarg + 1]
1158
1159             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1160             endPlain = find_end_of_layout(document.body, beginPlain)
1161             content = lyx2verbatim(document, document.body[beginPlain:endPlain])
1162
1163             document.body[beginPlain + 1 : endPlain] = [content]
1164             i = beginPlain + 1
1165
1166     # Dissolve ERT insets
1167     for glosse in glosses:
1168         i = 0
1169         while True:
1170             i = find_token(document.body, glosse, i)
1171             if i == -1:
1172                 break
1173             j = find_end_of_inset(document.body, i)
1174             if j == -1:
1175                 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1176                 i += 1
1177                 continue
1178             while True:
1179                 ert = find_token(document.body, "\\begin_inset ERT", i, j)
1180                 if ert == -1:
1181                     break
1182                 ertend = find_end_of_inset(document.body, ert)
1183                 if ertend == -1:
1184                     document.warning("Malformed LyX document: Can't find end of ERT inset")
1185                     ert += 1
1186                     continue
1187                 ertcontent = get_ert(document.body, ert, True)
1188                 document.body[ert : ertend + 1] = [ertcontent]
1189             i += 1
1190
1191
1192 def convert_newgloss(document):
1193     "Converts Glosse insets (Linguistics module) to the new format"
1194
1195     if not "linguistics" in document.get_module_list():
1196         return
1197
1198     glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
1199     for glosse in glosses:
1200         i = 0
1201         while True:
1202             i = find_token(document.body, glosse, i)
1203             if i == -1:
1204                 break
1205             j = find_end_of_inset(document.body, i)
1206             if j == -1:
1207                 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1208                 i += 1
1209                 continue
1210
1211             k = i
1212             while True:
1213                 argcontent = []
1214                 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", k, j)
1215                 if beginPlain == -1:
1216                     break
1217                 endPlain = find_end_of_layout(document.body, beginPlain)
1218                 if endPlain == -1:
1219                     document.warning("Malformed LyX document: Can't find end of Glosse layout")
1220                     i += 1
1221                     continue
1222
1223                 glt = find_token(document.body, "\\backslash", beginPlain, endPlain)
1224                 if glt != -1 and document.body[glt + 1].startswith("glt"):
1225                     document.body[glt + 1] = document.body[glt + 1].lstrip("glt").lstrip()
1226                     argcontent = document.body[glt + 1 : endPlain]
1227                     document.body[beginPlain + 1 : endPlain] = (
1228                         [
1229                             "\\begin_inset Argument 1",
1230                             "status open",
1231                             "",
1232                             "\\begin_layout Plain Layout",
1233                             "\\begin_inset ERT",
1234                             "status open",
1235                             "",
1236                             "\\begin_layout Plain Layout",
1237                             "",
1238                         ]
1239                         + argcontent
1240                         + [
1241                             "\\end_layout",
1242                             "",
1243                             "\\end_inset",
1244                             "",
1245                             "\\end_layout",
1246                             "",
1247                             "\\end_inset",
1248                         ]
1249                     )
1250                 else:
1251                     content = document.body[beginPlain + 1 : endPlain]
1252                     document.body[beginPlain + 1 : endPlain] = (
1253                         [
1254                             "\\begin_inset ERT",
1255                             "status open",
1256                             "",
1257                             "\\begin_layout Plain Layout",
1258                         ]
1259                         + content
1260                         + ["\\end_layout", "", "\\end_inset"]
1261                     )
1262
1263                 endPlain = find_end_of_layout(document.body, beginPlain)
1264                 k = endPlain
1265                 j = find_end_of_inset(document.body, i)
1266
1267             i = endPlain + 1
1268
1269
1270 def convert_BoxFeatures(document):
1271     "adds new box features"
1272
1273     i = 0
1274     while True:
1275         i = find_token(document.body, "height_special", i)
1276         if i == -1:
1277             return
1278         document.body[i + 1 : i + 1] = [
1279             'thickness "0.4pt"',
1280             'separation "3pt"',
1281             'shadowsize "4pt"',
1282         ]
1283         i = i + 4
1284
1285
1286 def revert_BoxFeatures(document):
1287     "outputs new box features as TeX code"
1288
1289     i = 0
1290     defaultSep = "3pt"
1291     defaultThick = "0.4pt"
1292     defaultShadow = "4pt"
1293     while True:
1294         i = find_token(document.body, "thickness", i)
1295         if i == -1:
1296             return
1297         binset = find_token(document.body, "\\begin_inset Box", i - 11)
1298         if binset == -1 or binset != i - 11:
1299             i = i + 1
1300             continue  # then "thickness" is is just a word in the text
1301         einset = find_end_of_inset(document.body, binset)
1302         if einset == -1:
1303             document.warning("Malformed LyX document: Can't find end of box inset!")
1304             i = i + 1
1305             continue
1306         # read out the values
1307         beg = document.body[i].find('"')
1308         end = document.body[i].rfind('"')
1309         thickness = document.body[i][beg + 1 : end]
1310         beg = document.body[i + 1].find('"')
1311         end = document.body[i + 1].rfind('"')
1312         separation = document.body[i + 1][beg + 1 : end]
1313         beg = document.body[i + 2].find('"')
1314         end = document.body[i + 2].rfind('"')
1315         shadowsize = document.body[i + 2][beg + 1 : end]
1316         # output ERT
1317         # first output the closing brace
1318         if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1319             document.body[einset + 1 : einset + 1] = put_cmd_in_ert("}")
1320         # delete the specification
1321         del document.body[i : i + 3]
1322         # we have now the problem that if there is already \(f)colorbox in ERT around the inset
1323         # the ERT from this routine must be around it
1324         regexp = re.compile(r"^.*colorbox{.*$")
1325         pos = find_re(document.body, regexp, binset - 4)
1326         if pos != -1 and pos == binset - 4:
1327             pos = i - 11 - 10
1328         else:
1329             pos = i - 11
1330         # now output the lengths
1331         if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1332             document.body[pos:pos] = put_cmd_in_ert("{")
1333         if thickness != defaultThick:
1334             document.body[pos + 5 : pos + 6] = ["{\\backslash fboxrule " + thickness]
1335         if separation != defaultSep and thickness == defaultThick:
1336             document.body[pos + 5 : pos + 6] = ["{\\backslash fboxsep " + separation]
1337         if separation != defaultSep and thickness != defaultThick:
1338             document.body[pos + 5 : pos + 6] = [
1339                 "{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation
1340             ]
1341         if (
1342             shadowsize != defaultShadow
1343             and separation == defaultSep
1344             and thickness == defaultThick
1345         ):
1346             document.body[pos + 5 : pos + 6] = ["{\\backslash shadowsize " + shadowsize]
1347         if (
1348             shadowsize != defaultShadow
1349             and separation != defaultSep
1350             and thickness == defaultThick
1351         ):
1352             document.body[pos + 5 : pos + 6] = [
1353                 "{\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize
1354             ]
1355         if (
1356             shadowsize != defaultShadow
1357             and separation == defaultSep
1358             and thickness != defaultThick
1359         ):
1360             document.body[pos + 5 : pos + 6] = [
1361                 "{\\backslash fboxrule " + thickness + "\\backslash shadowsize " + shadowsize
1362             ]
1363         if (
1364             shadowsize != defaultShadow
1365             and separation != defaultSep
1366             and thickness != defaultThick
1367         ):
1368             document.body[pos + 5 : pos + 6] = [
1369                 "{\\backslash fboxrule "
1370                 + thickness
1371                 + "\\backslash fboxsep "
1372                 + separation
1373                 + "\\backslash shadowsize "
1374                 + shadowsize
1375             ]
1376
1377
1378 def convert_origin(document):
1379     "Insert the origin tag"
1380
1381     i = find_token(document.header, "\\textclass ", 0)
1382     if i == -1:
1383         document.warning("Malformed LyX document: No \\textclass!!")
1384         return
1385     if document.dir == "":
1386         origin = "stdin"
1387     else:
1388         relpath = ""
1389         if document.systemlyxdir and document.systemlyxdir != "":
1390             try:
1391                 if os.path.isabs(document.dir):
1392                     absdir = os.path.normpath(document.dir)
1393                 else:
1394                     absdir = os.path.normpath(os.path.abspath(document.dir))
1395                 if os.path.isabs(document.systemlyxdir):
1396                     abssys = os.path.normpath(document.systemlyxdir)
1397                 else:
1398                     abssys = os.path.normpath(os.path.abspath(document.systemlyxdir))
1399                 relpath = os.path.relpath(absdir, abssys)
1400                 if relpath.find("..") == 0:
1401                     relpath = ""
1402             except:
1403                 relpath = ""
1404         if relpath == "":
1405             origin = document.dir.replace("\\", "/") + "/"
1406         else:
1407             origin = os.path.join("/systemlyxdir", relpath).replace("\\", "/") + "/"
1408     document.header[i:i] = ["\\origin " + origin]
1409
1410
1411 def revert_origin(document):
1412     "Remove the origin tag"
1413
1414     i = find_token(document.header, "\\origin ", 0)
1415     if i == -1:
1416         document.warning("Malformed LyX document: No \\origin!!")
1417         return
1418     del document.header[i]
1419
1420
1421 color_names = [
1422     "brown",
1423     "darkgray",
1424     "gray",
1425     "lightgray",
1426     "lime",
1427     "olive",
1428     "orange",
1429     "pink",
1430     "purple",
1431     "teal",
1432     "violet",
1433 ]
1434
1435
1436 def revert_textcolor(document):
1437     "revert new \\textcolor colors to TeX code"
1438
1439     i = 0
1440     j = 0
1441     xcolor = False
1442     while True:
1443         i = find_token(document.body, "\\color ", i)
1444         if i == -1:
1445             return
1446         else:
1447             for color in list(color_names):
1448                 if document.body[i] == "\\color " + color:
1449                     # register that xcolor must be loaded in the preamble
1450                     if xcolor == False:
1451                         xcolor = True
1452                         add_to_preamble(
1453                             document,
1454                             ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"],
1455                         )
1456                     # find the next \\color and/or the next \\end_layout
1457                     j = find_token(document.body, "\\color", i + 1)
1458                     k = find_token(document.body, "\\end_layout", i + 1)
1459                     if j == -1 and k != -1:
1460                         j = k + 1
1461                     # output TeX code
1462                     # first output the closing brace
1463                     if k < j:
1464                         document.body[k:k] = put_cmd_in_ert("}")
1465                     else:
1466                         document.body[j:j] = put_cmd_in_ert("}")
1467                     # now output the \textcolor command
1468                     document.body[i : i + 1] = put_cmd_in_ert("\\textcolor{" + color + "}{")
1469         i = i + 1
1470
1471
1472 def convert_colorbox(document):
1473     "Add color settings for boxes."
1474     i = 1
1475     while True:
1476         i = find_token(document.body, "shadowsize", i)
1477         if i == -1:
1478             return
1479         # check whether this is really a LyX Box setting
1480         start, end = is_in_inset(document.body, i, "\\begin_inset Box")
1481         if end == -1 or find_token(document.body, "\\begin_layout", start, i) != -1:
1482             i += 1
1483             continue
1484         document.body[i + 1 : i + 1] = ['framecolor "black"', 'backgroundcolor "none"']
1485         i += 3
1486
1487
1488 def revert_colorbox(document):
1489     """Change box color settings to LaTeX code."""
1490
1491     i = 0
1492     while True:
1493         i = find_token(document.body, "\\begin_inset Box", i)
1494         if i == -1:
1495             return
1496
1497         j = find_end_of_inset(document.body, i)
1498         k = find_token(document.body, "\\begin_layout", i, j)
1499         if k == -1:
1500             document.warning("Malformed LyX document: no layout in Box inset!")
1501             i += 1
1502             continue
1503         # Get and delete colour settings:
1504         framecolor = get_quoted_value(document.body, "framecolor", i, k, delete=True)
1505         backcolor = get_quoted_value(document.body, "backgroundcolor", i, k + 1, delete=True)
1506         if not framecolor or not backcolor:
1507             document.warning("Malformed LyX document: color options not found in Box inset!")
1508             i += 1
1509             continue
1510         if framecolor == "black" and backcolor == "none":  # default values
1511             i += 1
1512             continue
1513
1514         # Emulate non-default colours with LaTeX code:
1515         einset = find_end_of_inset(document.body, i)
1516         if einset == -1:
1517             document.warning("Malformed LyX document: Can't find end of box inset!")
1518             i += 1
1519             continue
1520         add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"])
1521         # insert the closing brace first (keeps indices 'i' and 'einset' valid)
1522         document.body[einset + 1 : einset + 1] = put_cmd_in_ert("}")
1523         # now insert the (f)color box command
1524         if "Box Boxed" in document.body[i]:  # framed box, use \fcolorbox
1525             # change the box type (frame added by \fcolorbox)
1526             document.body[i] = "\\begin_inset Box Frameless"
1527             # ensure an inner box:
1528             try:
1529                 if not set_bool_value(document.body, "has_inner_box", True, i + 3, i + 4):
1530                     set_bool_value(document.body, "use_makebox", True, i + 6, i + 7)
1531             except ValueError:
1532                 document.warning(
1533                     "Malformed LyX document: 'has_inner_box' or "
1534                     "'use_makebox' option not found in box inset!"
1535                 )
1536             ertinset = put_cmd_in_ert("\\fcolorbox{%s}{%s}{" % (framecolor, backcolor))
1537         else:
1538             ertinset = put_cmd_in_ert("\\colorbox{%s}{" % backcolor)
1539         document.body[i:i] = ertinset + [""]
1540         i += 13
1541
1542
1543 def revert_mathmulticol(document):
1544     "Convert formulas to ERT if they contain multicolumns"
1545
1546     i = 0
1547     while True:
1548         i = find_token(document.body, "\\begin_inset Formula", i)
1549         if i == -1:
1550             return
1551         j = find_end_of_inset(document.body, i)
1552         if j == -1:
1553             document.warning(
1554                 "Malformed LyX document: Can't find end of Formula inset at line " + str(i)
1555             )
1556             i += 1
1557             continue
1558         lines = document.body[i:j]
1559         lines[0] = lines[0].replace("\\begin_inset Formula", "").lstrip()
1560         code = "\n".join(lines)
1561         converted = False
1562         k = 0
1563         n = 0
1564         while n >= 0:
1565             n = code.find("\\multicolumn", k)
1566             # no need to convert degenerated multicolumn cells,
1567             # they work in old LyX versions as "math ERT"
1568             if n != -1 and code.find("\\multicolumn{1}", k) != n:
1569                 ert = put_cmd_in_ert(code)
1570                 document.body[i : j + 1] = ert
1571                 converted = True
1572                 break
1573             else:
1574                 k = n + 12
1575         if converted:
1576             i = find_end_of_inset(document.body, i)
1577         else:
1578             i = j
1579
1580
1581 def revert_jss(document):
1582     "Reverts JSS In_Preamble commands to ERT in preamble"
1583
1584     if document.textclass != "jss":
1585         return
1586
1587     # at first revert the inset layouts because
1588     # they can be part of the In_Preamble layouts
1589     il_dict = {
1590         "Pkg": "pkg",
1591         "Proglang": "proglang",
1592         "Code": "code",
1593         "E-mail": "email",
1594         "URL": "url",
1595     }
1596     for iltype in il_dict.keys():
1597         i = 0
1598         while True:
1599             i = find_token(document.body, "\\begin_inset Flex " + iltype, i)
1600             if i == -1:
1601                 break
1602
1603             if document.body[i] != "\\begin_inset Flex " + iltype:
1604                 # Not an exact match!
1605                 i += 1
1606                 continue
1607
1608             end = find_end_of_inset(document.body, i)
1609             if end == -1:
1610                 document.warning("Malformed LyX document: No end of Flex " + iltype + " found!")
1611                 i += 1
1612                 continue
1613             document.body[end - 2 : end + 1] = put_cmd_in_ert("}")
1614             document.body[i : i + 4] = put_cmd_in_ert("\\%s{" % il_dict[iltype])
1615             i += 1
1616
1617     # now revert the In_Preamble layouts
1618     ipl_dict = {
1619         "Title": "title",
1620         "Author": "author",
1621         "Plain Author": "Plainauthor",
1622         "Plain Title": "Plaintitle",
1623         "Short Title": "Shorttitle",
1624         "Abstract": "Abstract",
1625         "Keywords": "Keywords",
1626         "Plain Keywords": "Plainkeywords",
1627         "Address": "Address",
1628     }
1629     for ipltype in ipl_dict.keys():
1630         i = 0
1631         while True:
1632             i = find_token(document.body, "\\begin_layout " + ipltype, i)
1633             if i == -1:
1634                 break
1635
1636             end = find_end_of_layout(document.body, i)
1637             if end == -1:
1638                 document.warning(
1639                     "Malformed LyX document: Can't find end of " + ipltype + " layout"
1640                 )
1641                 i += 1
1642                 continue
1643
1644             content = lyx2latex(document, document.body[i : end + 1])
1645             add_to_preamble(document, ["\\" + ipl_dict[ipltype] + "{" + content + "}"])
1646             del document.body[i : end + 1]
1647             i += 1
1648
1649     # Now code chunks
1650     i = 0
1651     while True:
1652         # \CodeChunk
1653         i = find_token(document.body, "\\begin_inset Flex Code Chunk", i)
1654         if i == -1:
1655             break
1656
1657         end = find_end_of_inset(document.body, i)
1658         if end == -1:
1659             document.warning("Malformed LyX document: No end of Flex Code Chunk found!")
1660             i += 1
1661             continue
1662
1663         document.body[end : end + 1] = [
1664             "\\end_layout",
1665             "",
1666             "\\begin_layout Standard",
1667         ] + put_cmd_in_ert("\\end{CodeChunk}")
1668         document.body[i : i + 2] = put_cmd_in_ert("\\begin{CodeChunk}")
1669         i += 1
1670
1671     # finally handle the code layouts
1672     codes_dict = {
1673         "Code Input": "CodeInput",
1674         "Code Output": "CodeOutput",
1675         "Code": "Code",
1676     }
1677     for ctype in codes_dict.keys():
1678         i = 0
1679         while True:
1680             i = find_token(document.body, "\\begin_layout " + ctype, i)
1681             if i == -1:
1682                 break
1683             if document.body[i] != "\\begin_layout " + ctype:
1684                 # Not an exact match!
1685                 i += 1
1686                 continue
1687             end = find_end_of_layout(document.body, i)
1688             if end == -1:
1689                 document.warning(
1690                     "Malformed LyX document: No end of " + ctype + " layout found!"
1691                 )
1692                 i += 1
1693                 continue
1694             seq_end = end
1695             # Handle subsequent layouts
1696             while True:
1697                 j = find_token(document.body, "\\begin_layout ", seq_end)
1698                 if j == -1 or document.body[j] != "\\begin_layout " + ctype:
1699                     break
1700                 this_end = find_end_of_layout(document.body, j)
1701                 if this_end == -1:
1702                     document.warning(
1703                         "Malformed LyX document: No end of " + ctype + " layout found!"
1704                     )
1705                     j += 1
1706                     continue
1707                 seq_end = this_end
1708             document.body[seq_end + 1 : seq_end + 1] = [
1709                 "\\end_inset",
1710                 "\\end_layout",
1711                 "",
1712                 "\\begin_layout Standard",
1713             ] + put_cmd_in_ert("\\end{%s}" % codes_dict[ctype])
1714             if seq_end != end:
1715                 k = i + 1
1716                 while k < seq_end:
1717                     document.body[k] = document.body[k].replace(
1718                         "\\begin_layout " + ctype, "\\begin_layout Plain Layout"
1719                     )
1720                     k += 1
1721             document.body[i : i + 1] = (
1722                 ["\\end_layout", "", "\\begin_layout Standard"]
1723                 + put_cmd_in_ert("\\begin{%s}" % codes_dict[ctype])
1724                 + [
1725                     "\\end_layout",
1726                     "",
1727                     "\\begin_layout Standard",
1728                     "",
1729                     "\\begin_inset ERT",
1730                     "status open",
1731                     "",
1732                     "\\begin_layout Plain Layout",
1733                 ]
1734             )
1735             i += 1
1736
1737
1738 def convert_subref(document):
1739     "converts sub: ref prefixes to subref:"
1740
1741     # 1) label insets
1742     rx = re.compile(r"^name \"sub:(.+)$")
1743     i = 0
1744     while True:
1745         i = find_token(document.body, "\\begin_inset CommandInset label", i)
1746         if i == -1:
1747             break
1748         j = find_end_of_inset(document.body, i)
1749         if j == -1:
1750             document.warning(
1751                 "Malformed LyX document: Can't find end of Label inset at line " + str(i)
1752             )
1753             i += 1
1754             continue
1755
1756         for p in range(i, j):
1757             m = rx.match(document.body[p])
1758             if m:
1759                 label = m.group(1)
1760                 document.body[p] = 'name "subsec:' + label
1761         i += 1
1762
1763     # 2) xref insets
1764     rx = re.compile(r"^reference \"sub:(.+)$")
1765     i = 0
1766     while True:
1767         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1768         if i == -1:
1769             return
1770         j = find_end_of_inset(document.body, i)
1771         if j == -1:
1772             document.warning(
1773                 "Malformed LyX document: Can't find end of Ref inset at line " + str(i)
1774             )
1775             i += 1
1776             continue
1777
1778         for p in range(i, j):
1779             m = rx.match(document.body[p])
1780             if m:
1781                 label = m.group(1)
1782                 document.body[p] = 'reference "subsec:' + label
1783                 break
1784         i += 1
1785
1786
1787 def revert_subref(document):
1788     "reverts subref: ref prefixes to sub:"
1789
1790     # 1) label insets
1791     rx = re.compile(r"^name \"subsec:(.+)$")
1792     i = 0
1793     while True:
1794         i = find_token(document.body, "\\begin_inset CommandInset label", i)
1795         if i == -1:
1796             break
1797         j = find_end_of_inset(document.body, i)
1798         if j == -1:
1799             document.warning(
1800                 "Malformed LyX document: Can't find end of Label inset at line " + str(i)
1801             )
1802             i += 1
1803             continue
1804
1805         for p in range(i, j):
1806             m = rx.match(document.body[p])
1807             if m:
1808                 label = m.group(1)
1809                 document.body[p] = 'name "sub:' + label
1810                 break
1811         i += 1
1812
1813     # 2) xref insets
1814     rx = re.compile(r"^reference \"subsec:(.+)$")
1815     i = 0
1816     while True:
1817         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1818         if i == -1:
1819             return
1820         j = find_end_of_inset(document.body, i)
1821         if j == -1:
1822             document.warning(
1823                 "Malformed LyX document: Can't find end of Ref inset at line " + str(i)
1824             )
1825             i += 1
1826             continue
1827
1828         for p in range(i, j):
1829             m = rx.match(document.body[p])
1830             if m:
1831                 label = m.group(1)
1832                 document.body[p] = 'reference "sub:' + label
1833                 break
1834         i += 1
1835
1836
1837 def convert_nounzip(document):
1838     "remove the noUnzip parameter of graphics insets"
1839
1840     rx = re.compile(r"\s*noUnzip\s*$")
1841     i = 0
1842     while True:
1843         i = find_token(document.body, "\\begin_inset Graphics", i)
1844         if i == -1:
1845             break
1846         j = find_end_of_inset(document.body, i)
1847         if j == -1:
1848             document.warning(
1849                 "Malformed LyX document: Can't find end of graphics inset at line " + str(i)
1850             )
1851             i += 1
1852             continue
1853
1854         k = find_re(document.body, rx, i, j)
1855         if k != -1:
1856             del document.body[k]
1857             j = j - 1
1858         i = j + 1
1859
1860
1861 def convert_revert_external_bbox(document, forward):
1862     "add units to bounding box of external insets"
1863
1864     rx = re.compile(r"^\s*boundingBox\s+\S+\s+\S+\s+\S+\s+\S+\s*$")
1865     i = 0
1866     while True:
1867         i = find_token(document.body, "\\begin_inset External", i)
1868         if i == -1:
1869             break
1870         j = find_end_of_inset(document.body, i)
1871         if j == -1:
1872             document.warning(
1873                 "Malformed LyX document: Can't find end of external inset at line " + str(i)
1874             )
1875             i += 1
1876             continue
1877         k = find_re(document.body, rx, i, j)
1878         if k == -1:
1879             i = j + 1
1880             continue
1881         tokens = document.body[k].split()
1882         if forward:
1883             for t in range(1, 5):
1884                 tokens[t] += "bp"
1885         else:
1886             for t in range(1, 5):
1887                 tokens[t] = length_in_bp(tokens[t])
1888         document.body[k] = (
1889             "\tboundingBox " + tokens[1] + " " + tokens[2] + " " + tokens[3] + " " + tokens[4]
1890         )
1891         i = j + 1
1892
1893
1894 def convert_external_bbox(document):
1895     convert_revert_external_bbox(document, True)
1896
1897
1898 def revert_external_bbox(document):
1899     convert_revert_external_bbox(document, False)
1900
1901
1902 def revert_tcolorbox_1(document):
1903     "Reverts the Flex:Subtitle inset of tcolorbox to TeX-code"
1904
1905     i = find_token(document.header, "tcolorbox", 0)
1906     if i == -1:
1907         return
1908
1909     flex = 0
1910
1911     while True:
1912         flex = find_token(document.body, "\\begin_inset Flex Subtitle", flex)
1913         if flex == -1:
1914             return
1915
1916         flexEnd = find_end_of_inset(document.body, flex)
1917         if flexEnd == -1:
1918             document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1919             flex += 1
1920             continue
1921
1922         wasOpt = revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, False, True, False)
1923         flexEnd = find_end_of_inset(document.body, flex)
1924         if flexEnd == -1:
1925             document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1926             flex += 1
1927             continue
1928         revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, False, False, False)
1929         flexEnd = find_end_of_inset(document.body, flex)
1930         if flexEnd == -1:
1931             document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1932             flex += 1
1933             continue
1934
1935         bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1936         if bp == -1:
1937             document.warning("Malformed LyX document! No Flex Subtitle layout found.")
1938             flex += 1
1939             continue
1940
1941         ep = find_end_of_layout(document.body, bp)
1942         if ep == -1:
1943             document.warning("Malformed LyX document! No end of layout found.")
1944             flex += 1
1945             continue
1946
1947         document.body[ep : flexEnd + 1] = put_cmd_in_ert("}")
1948         if wasOpt == True:
1949             document.body[flex : bp + 1] = put_cmd_in_ert("\\tcbsubtitle")
1950         else:
1951             document.body[flex : bp + 1] = put_cmd_in_ert("\\tcbsubtitle{")
1952         flex += 1
1953
1954
1955 def revert_tcolorbox_2(document):
1956     "Reverts the Flex:Raster_Color_Box inset of tcolorbox to TeX-code"
1957
1958     i = find_token(document.header, "tcolorbox", 0)
1959     if i == -1:
1960         return
1961
1962     flex = 0
1963     while True:
1964         flex = find_token(document.body, "\\begin_inset Flex Raster Color Box", flex)
1965         if flex == -1:
1966             return
1967
1968         flexEnd = find_end_of_inset(document.body, flex)
1969         if flexEnd == -1:
1970             document.warning("Malformed LyX document! No end of Flex Raster Color Box found.")
1971             flex += 1
1972             continue
1973
1974         revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1975
1976         bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1977         if bp == -1:
1978             document.warning(
1979                 "Malformed LyX document! No plain layout in Raster Color Box found."
1980             )
1981             flex += 1
1982             continue
1983
1984         ep = find_end_of_layout(document.body, bp)
1985         if ep == -1:
1986             document.warning("Malformed LyX document! No end of layout found.")
1987             flex += 1
1988             continue
1989
1990         flexEnd = find_end_of_inset(document.body, flex)
1991         if flexEnd == -1:
1992             document.warning("Malformed LyX document! No end of Flex Raster Color Box found.")
1993             flex += 1
1994             continue
1995         document.body[ep : flexEnd + 1] = put_cmd_in_ert("\\end{tcbraster}")
1996         document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{tcbraster}")
1997         flex += 1
1998
1999
2000 def revert_tcolorbox_3(document):
2001     "Reverts the Flex:Custom_Color_Box_1 inset of tcolorbox to TeX-code"
2002
2003     i = find_token(document.header, "tcolorbox", 0)
2004     if i == -1:
2005         return
2006
2007     flex = 0
2008     while True:
2009         flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 1", flex)
2010         if flex == -1:
2011             return
2012
2013         flexEnd = find_end_of_inset(document.body, flex)
2014         if flexEnd == -1:
2015             document.warning("Malformed LyX document! No end of Flex Custom Color Box 1 found.")
2016             flex += 1
2017             continue
2018
2019         revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
2020         flexEnd = find_end_of_inset(document.body, flex)
2021         if flexEnd == -1:
2022             document.warning("Malformed LyX document! No end of Flex Subtitle found.")
2023             flex += 1
2024             continue
2025         revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
2026
2027         bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
2028         if bp == -1:
2029             document.warning(
2030                 "Malformed LyX document! No plain layout in Custom Color Box 1 found."
2031             )
2032             flex += 1
2033             continue
2034
2035         ep = find_end_of_layout(document.body, bp)
2036         if ep == -1:
2037             document.warning("Malformed LyX document! No end of layout found.")
2038             flex += 1
2039             continue
2040
2041         flexEnd = find_end_of_inset(document.body, flex)
2042         if flexEnd == -1:
2043             document.warning("Malformed LyX document! No end of Flex Custom Color Box 1 found.")
2044             flex += 1
2045             continue
2046
2047         document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxA}")
2048         document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxA}")
2049         flex += 1
2050
2051
2052 def revert_tcolorbox_4(document):
2053     "Reverts the Flex:Custom_Color_Box_2 inset of tcolorbox to TeX-code"
2054
2055     i = find_token(document.header, "tcolorbox", 0)
2056     if i == -1:
2057         return
2058
2059     flex = 0
2060     while True:
2061         flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 2", flex)
2062         if flex == -1:
2063             return
2064
2065         flexEnd = find_end_of_inset(document.body, flex)
2066         if flexEnd == -1:
2067             document.warning("Malformed LyX document! No end of Flex Custom Color Box 2 found.")
2068             flex += 1
2069             continue
2070
2071         revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
2072         flexEnd = find_end_of_inset(document.body, flex)
2073         if flexEnd == -1:
2074             document.warning("Malformed LyX document! No end of Flex Subtitle found.")
2075             flex += 1
2076             continue
2077         revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
2078         flexEnd = find_end_of_inset(document.body, flex)
2079         if flexEnd == -1:
2080             document.warning("Malformed LyX document! No end of Flex Custom Color Box 2 found.")
2081             flex += 1
2082             continue
2083
2084         bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
2085         if bp == -1:
2086             document.warning(
2087                 "Malformed LyX document! No plain layout in Custom Color Box 2 found."
2088             )
2089             flex += 1
2090             continue
2091
2092         ep = find_end_of_layout(document.body, bp)
2093         if ep == -1:
2094             document.warning("Malformed LyX document! No end of layout found.")
2095             flex += 1
2096             continue
2097
2098         document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxB}")
2099         document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxB}")
2100         flex += 1
2101
2102
2103 def revert_tcolorbox_5(document):
2104     "Reverts the Flex:Custom_Color_Box_3 inset of tcolorbox to TeX-code"
2105
2106     i = find_token(document.header, "tcolorbox", 0)
2107     if i == -1:
2108         return
2109
2110     flex = 0
2111     while True:
2112         flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 3", flex)
2113         if flex == -1:
2114             return
2115
2116         flexEnd = find_end_of_inset(document.body, flex)
2117         if flexEnd == -1:
2118             document.warning("Malformed LyX document! No end of Flex Custom Color Box 3 found.")
2119             flex += 1
2120             continue
2121
2122         revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
2123         flexEnd = find_end_of_inset(document.body, flex)
2124         if flexEnd == -1:
2125             document.warning("Malformed LyX document! No end of Flex Subtitle found.")
2126             flex += 1
2127             continue
2128         revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
2129         flexEnd = find_end_of_inset(document.body, flex)
2130         if flexEnd == -1:
2131             document.warning("Malformed LyX document! No end of Flex Custom Color Box 3 found.")
2132             flex += 1
2133             continue
2134
2135         bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
2136         if bp == -1:
2137             document.warning(
2138                 "Malformed LyX document! No plain layout in Custom Color Box 3 found."
2139             )
2140             flex += 1
2141             continue
2142
2143         ep = find_end_of_layout(document.body, bp)
2144         if ep == -1:
2145             document.warning("Malformed LyX document! No end of layout found.")
2146             flex += 1
2147             continue
2148
2149         document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxC}")
2150         document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxC}")
2151         flex += 1
2152
2153
2154 def revert_tcolorbox_6(document):
2155     "Reverts the Flex:Custom_Color_Box_4 inset of tcolorbox to TeX-code"
2156
2157     i = find_token(document.header, "tcolorbox", 0)
2158     if i == -1:
2159         return
2160
2161     flex = 0
2162     while True:
2163         flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 4", flex)
2164         if flex == -1:
2165             return
2166
2167         flexEnd = find_end_of_inset(document.body, flex)
2168         if flexEnd == -1:
2169             document.warning("Malformed LyX document! No end of Flex Custom Color Box 4 found.")
2170             flex += 1
2171             continue
2172
2173         revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
2174         flexEnd = find_end_of_inset(document.body, flex)
2175         if flexEnd == -1:
2176             document.warning("Malformed LyX document! No end of Flex Subtitle found.")
2177             flex += 1
2178             continue
2179         revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
2180         flexEnd = find_end_of_inset(document.body, flex)
2181         if flexEnd == -1:
2182             document.warning("Malformed LyX document! No end of Flex Custom Color Box 4 found.")
2183             flex += 1
2184             continue
2185
2186         bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
2187         if bp == -1:
2188             document.warning(
2189                 "Malformed LyX document! No plain layout in Custom Color Box 4 found."
2190             )
2191             flex += 1
2192             continue
2193
2194         ep = find_end_of_layout(document.body, bp)
2195         if ep == -1:
2196             document.warning("Malformed LyX document! No end of layout found.")
2197             flex += 1
2198             continue
2199
2200         document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxD}")
2201         document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxD}")
2202         flex += 1
2203
2204
2205 def revert_tcolorbox_7(document):
2206     "Reverts the Flex:Custom_Color_Box_5 inset of tcolorbox to TeX-code"
2207
2208     i = find_token(document.header, "tcolorbox", 0)
2209     if i == -1:
2210         return
2211
2212     flex = 0
2213     while True:
2214         flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 5", flex)
2215         if flex == -1:
2216             return
2217
2218         flexEnd = find_end_of_inset(document.body, flex)
2219         if flexEnd == -1:
2220             document.warning("Malformed LyX document! No end of Flex Custom Color Box 5 found.")
2221             flex += 1
2222             continue
2223
2224         revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
2225         flexEnd = find_end_of_inset(document.body, flex)
2226         if flexEnd == -1:
2227             document.warning("Malformed LyX document! No end of Flex Subtitle found.")
2228             flex += 1
2229             continue
2230         revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
2231         flexEnd = find_end_of_inset(document.body, flex)
2232         if flexEnd == -1:
2233             document.warning("Malformed LyX document! No end of Flex Custom Color Box 5 found.")
2234             flex += 1
2235             continue
2236
2237         bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
2238         if bp == -1:
2239             document.warning(
2240                 "Malformed LyX document! No plain layout in Custom Color Box 5 found."
2241             )
2242             flex += 1
2243             continue
2244
2245         ep = find_end_of_layout(document.body, bp)
2246         if ep == -1:
2247             document.warning("Malformed LyX document! No end of layout found.")
2248             flex += 1
2249             continue
2250
2251         document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxE}")
2252         document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxE}")
2253         flex += 1
2254
2255
2256 def revert_tcolorbox_8(document):
2257     "Reverts the layout New Color Box Type of tcolorbox to TeX-code"
2258
2259     i = 0
2260     while True:
2261         i = find_token(document.body, "\\begin_layout New Color Box Type", i)
2262         if i == -1:
2263             return
2264
2265         j = find_end_of_layout(document.body, i)
2266         if j == -1:
2267             document.warning(
2268                 "Malformed LyX document! No end of New Color Box Type layout found."
2269             )
2270             i += 1
2271             continue
2272
2273         wasOpt = revert_Argument_to_TeX_brace(document, i, j, 1, 1, False, True, True)
2274         j = find_end_of_layout(document.body, i)
2275         if j == -1:
2276             document.warning(
2277                 "Malformed LyX document! No end of New Color Box Type layout found."
2278             )
2279             i += 1
2280             continue
2281         revert_Argument_to_TeX_brace(document, i, j, 2, 2, False, False, True)
2282         j = find_end_of_layout(document.body, i)
2283         if j == -1:
2284             document.warning(
2285                 "Malformed LyX document! No end of New Color Box Type layout found."
2286             )
2287             i += 1
2288             continue
2289         revert_Argument_to_TeX_brace(document, i, j, 3, 4, False, True, False)
2290         j = find_end_of_layout(document.body, i)
2291         if j == -1:
2292             document.warning(
2293                 "Malformed LyX document! No end of New Color Box Type layout found."
2294             )
2295             i += 1
2296             continue
2297         document.body[i] = document.body[i].replace(
2298             "\\begin_layout New Color Box Type", "\\begin_layout Standard"
2299         )
2300         document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox")
2301         k = find_end_of_inset(document.body, j)
2302         document.body[k + 2 : j + 2] = put_cmd_in_ert("{") + [
2303             "\\begin_inset ERT",
2304             "status collapsed",
2305             "\\begin_layout Plain Layout",
2306         ]
2307         j = find_token(document.body, "\\begin_layout Standard", j + 1)
2308         document.body[j - 2 : j - 2] = ["\\end_layout", "\\end_inset"] + put_cmd_in_ert("}")
2309         i += 1
2310
2311
2312 def revert_moderncv_1(document):
2313     "Reverts the new inset of moderncv to TeX-code in preamble"
2314
2315     if document.textclass != "moderncv":
2316         return
2317     i = 0
2318     j = 0
2319     lineArg = 0
2320     while True:
2321         # at first revert the new styles
2322         # \moderncvicons
2323         i = find_token(document.body, "\\begin_layout CVIcons", 0)
2324         if i == -1:
2325             return
2326         j = find_end_of_layout(document.body, i)
2327         if j == -1:
2328             document.warning("Malformed LyX document: Can't find end of CVIcons layout")
2329             i += 1
2330             continue
2331         content = lyx2latex(document, document.body[i : j + 1])
2332         add_to_preamble(document, ["\\moderncvicons{" + content + "}"])
2333         del document.body[i : j + 1]
2334         # \hintscolumnwidth
2335         i = find_token(document.body, "\\begin_layout CVColumnWidth", 0)
2336         if i == -1:
2337             return
2338         j = find_end_of_layout(document.body, i)
2339         if j == -1:
2340             document.warning("Malformed LyX document: Can't find end of CVColumnWidth layout")
2341             i += 1
2342             continue
2343         content = lyx2latex(document, document.body[i : j + 1])
2344         add_to_preamble(document, ["\\setlength{\\hintscolumnwidth}{" + content + "}"])
2345         del document.body[i : j + 1]
2346         # now change the new styles to the obsolete ones
2347         # \name
2348         i = find_token(document.body, "\\begin_layout Name", 0)
2349         if i == -1:
2350             return
2351         j = find_end_of_layout(document.body, i)
2352         if j == -1:
2353             document.warning("Malformed LyX document: Can't find end of Name layout")
2354             i += 1
2355             continue
2356         lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
2357         if lineArg > j and j != 0:
2358             return
2359         if lineArg != -1:
2360             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
2361             # we have to assure that no other inset is in the Argument
2362             beginInset = find_token(document.body, "\\begin_inset", beginPlain)
2363             endInset = find_token(document.body, "\\end_inset", beginPlain)
2364             k = beginPlain + 1
2365             l = k
2366             while beginInset < endInset and beginInset != -1:
2367                 beginInset = find_token(document.body, "\\begin_inset", k)
2368                 endInset = find_token(document.body, "\\end_inset", l)
2369                 k = beginInset + 1
2370                 l = endInset + 1
2371             Arg2 = document.body[l + 5 : l + 6]
2372             # rename the style
2373             document.body[i : i + 1] = ["\\begin_layout FirstName"]
2374             # delete the Argument inset
2375             del document.body[endInset - 2 : endInset + 3]
2376             del document.body[lineArg : beginPlain + 1]
2377             document.body[i + 4 : i + 4] = (
2378                 ["\\begin_layout FamilyName"] + Arg2 + ["\\end_layout"] + [""]
2379             )
2380
2381
2382 def revert_moderncv_2(document):
2383     "Reverts the phone inset of moderncv to the obsoleted mobile or fax"
2384
2385     if document.textclass != "moderncv":
2386         return
2387     i = 0
2388     j = 0
2389     lineArg = 0
2390     while True:
2391         # \phone
2392         i = find_token(document.body, "\\begin_layout Phone", i)
2393         if i == -1:
2394             return
2395         j = find_end_of_layout(document.body, i)
2396         if j == -1:
2397             document.warning("Malformed LyX document: Can't find end of Phone layout")
2398             i += 1
2399             return
2400         lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
2401         if lineArg > j and j != 0:
2402             i += 1
2403             continue
2404         if lineArg != -1:
2405             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
2406             # we have to assure that no other inset is in the Argument
2407             beginInset = find_token(document.body, "\\begin_inset", beginPlain)
2408             endInset = find_token(document.body, "\\end_inset", beginPlain)
2409             k = beginPlain + 1
2410             l = k
2411             while beginInset < endInset and beginInset != -1:
2412                 beginInset = find_token(document.body, "\\begin_inset", k)
2413                 endInset = find_token(document.body, "\\end_inset", l)
2414                 k = beginInset + 1
2415                 l = endInset + 1
2416             Arg = document.body[beginPlain + 1 : beginPlain + 2]
2417             # rename the style
2418             if Arg[0] == "mobile":
2419                 document.body[i : i + 1] = ["\\begin_layout Mobile"]
2420             if Arg[0] == "fax":
2421                 document.body[i : i + 1] = ["\\begin_layout Fax"]
2422             # delete the Argument inset
2423             del document.body[endInset - 2 : endInset + 1]
2424             del document.body[lineArg : beginPlain + 3]
2425         i += 1
2426
2427
2428 def convert_moderncv_phone(document):
2429     "Convert the Fax and Mobile inset of moderncv to the new phone inset"
2430
2431     if document.textclass != "moderncv":
2432         return
2433     i = 0
2434     j = 0
2435     lineArg = 0
2436
2437     phone_dict = {
2438         "Mobile": "mobile",
2439         "Fax": "fax",
2440     }
2441
2442     rx = re.compile(r"^\\begin_layout (\S+)$")
2443     while True:
2444         # substitute \fax and \mobile by \phone[fax] and \phone[mobile], respectively
2445         i = find_token(document.body, "\\begin_layout", i)
2446         if i == -1:
2447             return
2448
2449         m = rx.match(document.body[i])
2450         val = ""
2451         if m:
2452             val = m.group(1)
2453         if val not in list(phone_dict.keys()):
2454             i += 1
2455             continue
2456         j = find_end_of_layout(document.body, i)
2457         if j == -1:
2458             document.warning("Malformed LyX document: Can't find end of Mobile layout")
2459             i += 1
2460             return
2461
2462         document.body[i : i + 1] = [
2463             "\\begin_layout Phone",
2464             "\\begin_inset Argument 1",
2465             "status open",
2466             "",
2467             "\\begin_layout Plain Layout",
2468             phone_dict[val],
2469             "\\end_layout",
2470             "",
2471             "\\end_inset",
2472             "",
2473         ]
2474
2475
2476 def convert_moderncv_name(document):
2477     "Convert the FirstName and LastName layout of moderncv to the general Name layout"
2478
2479     if document.textclass != "moderncv":
2480         return
2481
2482     fnb = 0  # Begin of FirstName inset
2483     fne = 0  # End of FirstName inset
2484     lnb = 0  # Begin of LastName (FamilyName) inset
2485     lne = 0  # End of LastName (FamilyName) inset
2486     nb = 0  # Begin of substituting Name inset
2487     ne = 0  # End of substituting Name inset
2488     FirstName = []  # FirstName content
2489     FamilyName = []  # LastName content
2490
2491     while True:
2492         # locate FirstName
2493         fnb = find_token(document.body, "\\begin_layout FirstName", fnb)
2494         if fnb != -1:
2495             fne = find_end_of_layout(document.body, fnb)
2496             if fne == -1:
2497                 document.warning("Malformed LyX document: Can't find end of FirstName layout")
2498                 return
2499             FirstName = document.body[fnb + 1 : fne]
2500         # locate FamilyName
2501         lnb = find_token(document.body, "\\begin_layout FamilyName", lnb)
2502         if lnb != -1:
2503             lne = find_end_of_layout(document.body, lnb)
2504             if lne == -1:
2505                 document.warning("Malformed LyX document: Can't find end of FamilyName layout")
2506                 return
2507             FamilyName = document.body[lnb + 1 : lne]
2508         # Determine the region for the substituting Name layout
2509         if fnb == -1 and lnb == -1:  # Neither FirstName nor FamilyName exists -> Do nothing
2510             return
2511         elif fnb == -1:  # Only FamilyName exists -> New Name insets replaces that
2512             nb = lnb
2513             ne = lne
2514         elif lnb == -1:  # Only FirstName exists -> New Name insets replaces that
2515             nb = fnb
2516             ne = fne
2517         elif fne > lne:  # FirstName position before FamilyName -> New Name insets spans
2518             nb = lnb  #     from FamilyName begin
2519             ne = fne  #     to FirstName end
2520         else:  #           FirstName position before FamilyName -> New Name insets spans
2521             nb = fnb  #     from FirstName begin
2522             ne = lne  #     to FamilyName end
2523
2524         # Insert the substituting layout now. If FirstName exists, use an otpional argument.
2525         if FirstName == []:
2526             document.body[nb : ne + 1] = (
2527                 ["\\begin_layout Name"] + FamilyName + ["\\end_layout", ""]
2528             )
2529         else:
2530             document.body[nb : ne + 1] = (
2531                 [
2532                     "\\begin_layout Name",
2533                     "\\begin_inset Argument 1",
2534                     "status open",
2535                     "",
2536                     "\\begin_layout Plain Layout",
2537                 ]
2538                 + FirstName
2539                 + ["\\end_layout", "", "\\end_inset", ""]
2540                 + FamilyName
2541                 + ["\\end_layout", ""]
2542             )
2543
2544
2545 def revert_achemso(document):
2546     "Reverts the flex inset Latin to TeX code"
2547
2548     if document.textclass != "achemso":
2549         return
2550     i = 0
2551     j = 0
2552     while True:
2553         i = find_token(document.body, "\\begin_inset Flex Latin", i)
2554         if i != -1:
2555             j = find_end_of_inset(document.body, i)
2556         else:
2557             return
2558         if j != -1:
2559             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2560             endPlain = find_end_of_layout(document.body, beginPlain)
2561             content = lyx2latex(document, document.body[beginPlain:endPlain])
2562             document.body[i : j + 1] = put_cmd_in_ert("\\latin{" + content + "}")
2563         else:
2564             document.warning("Malformed LyX document: Can't find end of flex inset Latin")
2565             return
2566         i += 1
2567
2568
2569 fontsettings = [
2570     "\\font_roman",
2571     "\\font_sans",
2572     "\\font_typewriter",
2573     "\\font_math",
2574     "\\font_sf_scale",
2575     "\\font_tt_scale",
2576 ]
2577 fontdefaults = ["default", "default", "default", "auto", "100", "100"]
2578 fontquotes = [True, True, True, True, False, False]
2579
2580
2581 def convert_fontsettings(document):
2582     "Duplicate font settings"
2583
2584     i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2585     if i == -1:
2586         document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2587         use_non_tex_fonts = "false"
2588     else:
2589         use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2590     j = 0
2591     for f in fontsettings:
2592         i = find_token(document.header, f + " ", 0)
2593         if i == -1:
2594             document.warning("Malformed LyX document: No " + f + "!")
2595             # we can fix that
2596             # note that with i = -1, this will insert at the end
2597             # of the header
2598             value = fontdefaults[j]
2599         else:
2600             value = document.header[i][len(f) :].strip()
2601         if fontquotes[j]:
2602             if use_non_tex_fonts == "true":
2603                 document.header[i : i + 1] = [f + ' "' + fontdefaults[j] + '" "' + value + '"']
2604             else:
2605                 document.header[i : i + 1] = [f + ' "' + value + '" "' + fontdefaults[j] + '"']
2606         else:
2607             if use_non_tex_fonts == "true":
2608                 document.header[i : i + 1] = [f + " " + fontdefaults[j] + " " + value]
2609             else:
2610                 document.header[i : i + 1] = [f + " " + value + " " + fontdefaults[j]]
2611         j = j + 1
2612
2613
2614 def revert_fontsettings(document):
2615     "Merge font settings"
2616
2617     i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2618     if i == -1:
2619         document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2620         use_non_tex_fonts = "false"
2621     else:
2622         use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2623     j = 0
2624     for f in fontsettings:
2625         i = find_token(document.header, f + " ", 0)
2626         if i == -1:
2627             document.warning("Malformed LyX document: No " + f + "!")
2628             j = j + 1
2629             continue
2630         line = get_value(document.header, f, i)
2631         if fontquotes[j]:
2632             q1 = line.find('"')
2633             q2 = line.find('"', q1 + 1)
2634             q3 = line.find('"', q2 + 1)
2635             q4 = line.find('"', q3 + 1)
2636             if q1 == -1 or q2 == -1 or q3 == -1 or q4 == -1:
2637                 document.warning("Malformed LyX document: Missing quotes!")
2638                 j = j + 1
2639                 continue
2640             if use_non_tex_fonts == "true":
2641                 document.header[i : i + 1] = [f + " " + line[q3 + 1 : q4]]
2642             else:
2643                 document.header[i : i + 1] = [f + " " + line[q1 + 1 : q2]]
2644         else:
2645             if use_non_tex_fonts == "true":
2646                 document.header[i : i + 1] = [f + " " + line.split()[1]]
2647             else:
2648                 document.header[i : i + 1] = [f + " " + line.split()[0]]
2649         j = j + 1
2650
2651
2652 def revert_solution(document):
2653     "Reverts the solution environment of the theorem module to TeX code"
2654
2655     # Do we use one of the modules that provides Solution?
2656     have_mod = False
2657     mods = document.get_module_list()
2658     for mod in mods:
2659         if (
2660             mod == "theorems-std"
2661             or mod == "theorems-bytype"
2662             or mod == "theorems-ams"
2663             or mod == "theorems-ams-bytype"
2664         ):
2665             have_mod = True
2666             break
2667     if not have_mod:
2668         return
2669
2670     consecutive = False
2671     is_starred = False
2672     i = 0
2673     while True:
2674         i = find_token(document.body, "\\begin_layout Solution", i)
2675         if i == -1:
2676             return
2677
2678         is_starred = document.body[i].startswith("\\begin_layout Solution*")
2679         if is_starred == True:
2680             LaTeXName = "sol*"
2681             LyXName = "Solution*"
2682             theoremName = "newtheorem*"
2683         else:
2684             LaTeXName = "sol"
2685             LyXName = "Solution"
2686             theoremName = "newtheorem"
2687
2688         j = find_end_of_layout(document.body, i)
2689         if j == -1:
2690             document.warning("Malformed LyX document: Can't find end of " + LyXName + " layout")
2691             i += 1
2692             continue
2693
2694         # if this is not a consecutive env, add start command
2695         begcmd = []
2696         if not consecutive:
2697             begcmd = put_cmd_in_ert("\\begin{%s}" % (LaTeXName))
2698
2699         # has this a consecutive theorem of same type?
2700         consecutive = document.body[j + 2] == "\\begin_layout " + LyXName
2701
2702         # if this is not followed by a consecutive env, add end command
2703         if not consecutive:
2704             document.body[j : j + 1] = put_cmd_in_ert("\\end{%s}" % (LaTeXName)) + [
2705                 "\\end_layout"
2706             ]
2707
2708         document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
2709
2710         add_to_preamble(document, "\\theoremstyle{definition}")
2711         if is_starred or mod == "theorems-bytype" or mod == "theorems-ams-bytype":
2712             add_to_preamble(
2713                 document, "\\%s{%s}{\\protect\\solutionname}" % (theoremName, LaTeXName)
2714             )
2715         else:  # mod == "theorems-std" or mod == "theorems-ams" and not is_starred
2716             add_to_preamble(
2717                 document,
2718                 "\\%s{%s}[thm]{\\protect\\solutionname}" % (theoremName, LaTeXName),
2719             )
2720
2721         add_to_preamble(document, "\\providecommand{\\solutionname}{Solution}")
2722         i = j
2723
2724
2725 def revert_verbatim_star(document):
2726     from lyx_2_1 import revert_verbatim
2727
2728     revert_verbatim(document, True)
2729
2730
2731 def convert_save_props(document):
2732     "Add save_transient_properties parameter."
2733     i = find_token(document.header, "\\begin_header", 0)
2734     if i == -1:
2735         document.warning("Malformed lyx document: Missing '\\begin_header'.")
2736         return
2737     document.header.insert(i + 1, "\\save_transient_properties true")
2738
2739
2740 def revert_save_props(document):
2741     "Remove save_transient_properties parameter."
2742     i = find_token(document.header, "\\save_transient_properties", 0)
2743     if i == -1:
2744         return
2745     del document.header[i]
2746
2747
2748 def convert_info_tabular_feature(document):
2749     def f(arg):
2750         return arg.replace("inset-modify tabular", "tabular-feature")
2751
2752     convert_info_insets(document, "shortcut(s)?|icon", f)
2753
2754
2755 def revert_info_tabular_feature(document):
2756     def f(arg):
2757         return arg.replace("tabular-feature", "inset-modify tabular")
2758
2759     convert_info_insets(document, "shortcut(s)?|icon", f)
2760
2761
2762 ##
2763 # Conversion hub
2764 #
2765
2766 supported_versions = ["2.2.0", "2.2"]
2767 convert = [
2768     [475, [convert_separator]],
2769     # nothing to do for 476: We consider it a bug that older versions
2770     # did not load amsmath automatically for these commands, and do not
2771     # want to hardcode amsmath off.
2772     [476, []],
2773     [477, []],
2774     [478, []],
2775     [479, []],
2776     [480, []],
2777     [481, [convert_dashes]],
2778     [482, [convert_phrases]],
2779     [483, [convert_specialchar]],
2780     [484, []],
2781     [485, []],
2782     [486, []],
2783     [487, []],
2784     [488, [convert_newgloss]],
2785     [489, [convert_BoxFeatures]],
2786     [490, [convert_origin]],
2787     [491, []],
2788     [492, [convert_colorbox]],
2789     [493, []],
2790     [494, []],
2791     [495, [convert_subref]],
2792     [496, [convert_nounzip]],
2793     [497, [convert_external_bbox]],
2794     [498, []],
2795     [499, [convert_moderncv_phone, convert_moderncv_name]],
2796     [500, []],
2797     [501, [convert_fontsettings]],
2798     [502, []],
2799     [503, []],
2800     [504, [convert_save_props]],
2801     [505, []],
2802     [506, [convert_info_tabular_feature]],
2803     [507, [convert_longtable_label]],
2804     [508, [convert_parbreak]],
2805 ]
2806
2807 revert = [
2808     [507, [revert_parbreak]],
2809     [506, [revert_longtable_label]],
2810     [505, [revert_info_tabular_feature]],
2811     [504, []],
2812     [503, [revert_save_props]],
2813     [502, [revert_verbatim_star]],
2814     [501, [revert_solution]],
2815     [500, [revert_fontsettings]],
2816     [499, [revert_achemso]],
2817     [498, [revert_moderncv_1, revert_moderncv_2]],
2818     [
2819         497,
2820         [
2821             revert_tcolorbox_1,
2822             revert_tcolorbox_2,
2823             revert_tcolorbox_3,
2824             revert_tcolorbox_4,
2825             revert_tcolorbox_5,
2826             revert_tcolorbox_6,
2827             revert_tcolorbox_7,
2828             revert_tcolorbox_8,
2829         ],
2830     ],
2831     [496, [revert_external_bbox]],
2832     [495, []],  # nothing to do since the noUnzip parameter was optional
2833     [494, [revert_subref]],
2834     [493, [revert_jss]],
2835     [492, [revert_mathmulticol]],
2836     [491, [revert_colorbox]],
2837     [490, [revert_textcolor]],
2838     [489, [revert_origin]],
2839     [488, [revert_BoxFeatures]],
2840     [487, [revert_newgloss, revert_glossgroup]],
2841     [486, [revert_forest]],
2842     [485, [revert_ex_itemargs]],
2843     [484, [revert_sigplan_doi]],
2844     [483, [revert_georgian]],
2845     [482, [revert_specialchar]],
2846     [481, [revert_phrases]],
2847     [480, [revert_dashes]],
2848     [479, [revert_question_env]],
2849     [478, [revert_beamer_lemma]],
2850     [477, [revert_xarrow]],
2851     [476, [revert_swissgerman]],
2852     [475, [revert_smash]],
2853     [474, [revert_separator]],
2854 ]
2855
2856
2857 if __name__ == "__main__":
2858     pass