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