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