1 # -*- coding: utf-8 -*-
2 # This file is part of lyx2lyx
3 # Copyright (C) 2015 The LyX team
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.
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.
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.
19 """ Convert files to the file format generated by lyx 2.2"""
25 # Uncomment only what you need to import, please.
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)
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)
37 ####################################################################
38 # Private helper functions
40 def revert_Argument_to_TeX_brace(document, line, endline, n, nmax, environment, opt, nolastopt):
42 Reverts an InsetArgument to TeX-code
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
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:
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)
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)
71 if environment == False:
73 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}")
74 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("{")
77 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("]")
78 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("[")
82 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}")
83 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("{")
86 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("]")
87 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("[")
93 ###############################################################################
95 ### Conversion and reversion routines
97 ###############################################################################
99 def convert_longtable_label_internal(document, forward):
101 Convert reference to "LongTableNoNumber" into "Unnumbered" if forward is True
104 old_reference = "\\begin_inset Caption LongTableNoNumber"
105 new_reference = "\\begin_inset Caption Unnumbered"
107 # if the purpose is to revert swap the strings roles
109 old_reference, new_reference = new_reference, old_reference
113 i = find_token(document.body, old_reference, i)
118 document.body[i] = new_reference
121 def convert_longtable_label(document):
122 convert_longtable_label_internal(document, True)
125 def revert_longtable_label(document):
126 convert_longtable_label_internal(document, False)
129 def convert_separator(document):
131 Convert layout separators to separator insets and add (LaTeX) paragraph
132 breaks in order to mimic previous LaTeX export.
135 parins = ["\\begin_inset Separator parbreak", "\\end_inset", ""]
136 parlay = ["\\begin_layout Standard", "\\begin_inset Separator parbreak",
137 "\\end_inset", "", "\\end_layout", ""]
139 "family" : "default",
140 "series" : "default",
149 i = find_token(document.body, "\\begin_deeper", i)
153 j = find_token_backwards(document.body, "\\end_layout", i-1)
155 # reset any text style before inserting the inset
156 lay = get_containing_layout(document.body, j-1)
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])]
164 document.body[j:j] = parins
165 i = i + len(parins) + 1
171 i = find_token(document.body, "\\align", i)
175 lay = get_containing_layout(document.body, i)
176 if lay != False and lay[0] == "Plain Layout":
180 j = find_token_backwards(document.body, "\\end_layout", i-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])
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])]
200 document.body[j:j] = parins
201 i = i + len(parins) + 1
207 regexp = re.compile(r'^\\begin_layout (?:(-*)|(\s*))(Separator|EndOfSlide)(?:(-*)|(\s*))$', re.IGNORECASE)
211 i = find_re(document.body, regexp, i)
215 j = find_end_of_layout(document.body, i)
217 document.warning("Malformed LyX document: Missing `\\end_layout'.")
220 lay = get_containing_layout(document.body, j-1)
222 lines = document.body[lay[3]:lay[2]]
226 document.body[i:j+1] = parlay
228 document.body[i+1:i+1] = lines
230 i = i + len(parlay) + len(lines) + 1
233 def revert_separator(document):
234 " Revert separator insets to layout separators "
236 beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
237 if document.textclass in beamer_classes:
238 beglaysep = "\\begin_layout Separator"
240 beglaysep = "\\begin_layout --Separator--"
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", ""]
252 i = find_token(document.body, "\\begin_inset Separator", i)
256 lay = get_containing_layout(document.body, i)
258 document.warning("Malformed LyX document: Can't convert separator inset at line " + str(i))
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
272 beg = beg + len(before) + 1
273 elif something_before:
274 document.body[i:i] = ["\\end_layout", ""]
282 document.body[beg:j+1] = empert
285 document.body[beg:j+1] = comert
289 if layoutname == "Standard":
290 if not something_before:
291 document.body[beg:j+1] = parsep
293 document.body[i:i] = ["", "\\begin_layout Standard"]
296 document.body[beg:j+1] = ["\\begin_layout Standard"]
299 document.body[beg:j+1] = ["\\begin_deeper"]
301 end = end + 1 - (j + 1 - beg)
302 if not something_before:
303 document.body[i:i] = parsep
305 end = end + len(parsep)
306 document.body[i:i] = ["\\begin_layout Standard"]
307 document.body[end+2:end+2] = ["", "\\end_deeper", ""]
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]
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", ""]
329 del document.body[i:end+1]
331 del document.body[i:end-1]
336 def convert_parbreak(document):
338 Convert parbreak separators not specifically used to separate
339 environments to latexpar separators.
341 parbreakinset = "\\begin_inset Separator parbreak"
344 i = find_token(document.body, parbreakinset, i)
347 lay = get_containing_layout(document.body, i)
349 document.warning("Malformed LyX document: Can't convert separator inset at line " + str(i))
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")
359 document.body[i] = document.body[i].replace("parbreak", "latexpar")
363 def revert_parbreak(document):
365 Revert latexpar separators to parbreak separators.
369 i = find_token(document.body, "\\begin_inset Separator latexpar", i)
372 document.body[i] = document.body[i].replace("latexpar", "parbreak")
376 def revert_smash(document):
377 " Set amsmath to on if smash commands are used "
379 commands = ["smash[t]", "smash[b]", "notag"]
380 i = find_token(document.header, "\\use_package amsmath", 0)
382 document.warning("Malformed LyX document: Can't find \\use_package amsmath.")
384 value = get_value(document.header, "\\use_package amsmath", i).split()[1]
386 # nothing to do if package is not auto but on or off
390 j = find_token(document.body, '\\begin_inset Formula', j)
393 k = find_end_of_inset(document.body, j)
395 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(j))
398 code = "\n".join(document.body[j:k])
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"
407 def revert_swissgerman(document):
408 " Set language german-ch-old to german "
410 if document.language == "german-ch-old":
411 document.language = "german"
412 i = find_token(document.header, "\\language", 0)
414 document.header[i] = "\\language german"
417 j = find_token(document.body, "\\lang german-ch-old", j)
420 document.body[j] = document.body[j].replace("\\lang german-ch-old", "\\lang german")
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
430 regexp = re.compile(r'(\\use_package\s+%s)' % pkg)
431 p = find_re(document.header, regexp, 0)
432 value = "1" # default is auto
434 value = get_value(document.header, "\\use_package" , p).split()[1]
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
442 i = find_token(document.body, '\\begin_inset Formula', i)
445 j = find_end_of_inset(document.body, i)
447 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
450 code = "\n".join(document.body[i:j])
452 if code.find("\\%s" % c) != -1:
454 document.header[p] = "\\use_package " + pkg + " 2"
456 add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
461 mathtools_commands = ["xhookrightarrow", "xhookleftarrow", "xRightarrow", \
462 "xrightharpoondown", "xrightharpoonup", "xrightleftharpoons", \
463 "xLeftarrow", "xleftharpoondown", "xleftharpoonup", \
464 "xleftrightarrow", "xLeftrightarrow", "xleftrightharpoons", \
467 def revert_xarrow(document):
468 "remove use_package mathtools"
469 revert_use_package(document, "mathtools", mathtools_commands, False, True)
472 def revert_beamer_lemma(document):
473 " Reverts beamer lemma layout to ERT "
475 beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
476 if document.textclass not in beamer_classes:
482 i = find_token(document.body, "\\begin_layout Lemma", i)
485 j = find_end_of_layout(document.body, i)
487 document.warning("Malformed LyX document: Can't find end of Lemma layout")
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)
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")
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(">")
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")
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("]")
517 del document.body[arg2 : endarg2 + 1]
519 del document.body[arg1 : endarg1 + 1]
521 del document.body[arg1 : endarg1 + 1]
523 del document.body[arg2 : endarg2 + 1]
525 # index of end layout has probably changed
526 j = find_end_of_layout(document.body, i)
528 document.warning("Malformed LyX document: Can't find end of Lemma layout")
534 # if this is not a consecutive env, add start command
536 begcmd = put_cmd_in_ert("\\begin{lemma}")
538 # has this a consecutive lemma?
539 consecutive = document.body[j + 2] == "\\begin_layout Lemma"
541 # if this is not followed by a consecutive env, add end command
543 document.body[j : j + 1] = put_cmd_in_ert("\\end{lemma}") + ["\\end_layout"]
545 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd + subst1 + subst2
551 def revert_question_env(document):
553 Reverts question and question* environments of
554 theorems-ams-extended-bytype module to ERT
557 # Do we use theorems-ams-extended-bytype module?
558 if not "theorems-ams-extended-bytype" in document.get_module_list():
564 i = find_token(document.body, "\\begin_layout Question", i)
568 starred = document.body[i] == "\\begin_layout Question*"
570 j = find_end_of_layout(document.body, i)
572 document.warning("Malformed LyX document: Can't find end of Question layout")
576 # if this is not a consecutive env, add start command
580 begcmd = put_cmd_in_ert("\\begin{question*}")
582 begcmd = put_cmd_in_ert("\\begin{question}")
584 # has this a consecutive theorem of same type?
587 consecutive = document.body[j + 2] == "\\begin_layout Question*"
589 consecutive = document.body[j + 2] == "\\begin_layout Question"
591 # if this is not followed by a consecutive env, add end command
594 document.body[j : j + 1] = put_cmd_in_ert("\\end{question*}") + ["\\end_layout"]
596 document.body[j : j + 1] = put_cmd_in_ert("\\end{question}") + ["\\end_layout"]
598 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
600 add_to_preamble(document, "\\providecommand{\questionname}{Question}")
603 add_to_preamble(document, "\\theoremstyle{plain}\n" \
604 "\\newtheorem*{question*}{\\protect\\questionname}")
606 add_to_preamble(document, "\\theoremstyle{plain}\n" \
607 "\\newtheorem{question}{\\protect\\questionname}")
612 def convert_dashes(document):
613 "convert -- and --- to \\twohyphens and \\threehyphens"
615 if document.backend != "latex":
620 i = find_substring(document.body, "--", i+1)
623 line = document.body[i]
624 # skip label width string (bug 10243):
625 if line.startswith("\\labelwidthstring"):
627 # Do not touch hyphens in some insets:
629 value, start, end = get_containing_inset(document.body, i)
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"]):
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)
648 if layout == "LyX-Code":
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')
657 # remove ligature breaks between dashes
660 i = find_substring(document.body,
661 r"-\SpecialChar \textcompwordmark{}", i+1)
664 if document.body[i+1].startswith("-"):
665 document.body[i] = document.body[i].replace(
666 r"\SpecialChar \textcompwordmark{}", document.body.pop(i+1))
669 def revert_dashes(document):
671 Remove preamble code from 2.3->2.2 conversion.
672 Prevent ligatures of existing --- and --.
673 Revert \\twohyphens and \\threehyphens to -- and ---.
675 del_complete_lines(document.preamble,
676 ['% Added by lyx2lyx',
677 r'\renewcommand{\textendash}{--}',
678 r'\renewcommand{\textemdash}{---}'])
680 # Insert ligature breaks to prevent ligation of hyphens to dashes:
683 i = find_substring(document.body, "--", i+1)
686 line = document.body[i]
687 # skip label width string (bug 10243):
688 if line.startswith("\\labelwidthstring"):
690 # do not touch hyphens in some insets (cf. convert_dashes):
692 value, start, end = get_containing_inset(document.body, i)
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"):
701 line = line.replace("--", "-\\SpecialChar \\textcompwordmark{}\n-")
702 document.body[i:i+1] = line.split('\n')
704 # Revert \twohyphens and \threehyphens:
706 while i < len(document.body):
707 line = document.body[i]
708 if not line.endswith("hyphens"):
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)
718 # order is important for the last three!
719 phrases = ["LyX", "LaTeX2e", "LaTeX", "TeX"]
721 def is_part_of_converted_phrase(line, j, phrase):
722 "is phrase part of an already converted phrase?"
724 converted = "\\SpecialCharNoPassThru \\" + p
725 pos = j + len(phrase) - len(converted)
727 if line[pos:pos+len(converted)] == converted:
732 def convert_phrases(document):
733 "convert special phrases from plain text to \\SpecialCharNoPassThru"
735 if document.backend != "latex":
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)
748 document.warning("Malformed LyX document: Can't find end of inset at line %d" % (i))
755 for phrase in phrases:
756 j = document.body[i].find(phrase)
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):]
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
769 def revert_phrases(document):
770 "revert special phrases to plain text"
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)
780 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
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)
791 if document.body[i].find("\\SpecialCharNoPassThru \\" + phrase) >= 0:
792 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru \\" + phrase, phrase)
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] = []
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
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)
822 document.warning("Malformed LyX document: Can't find end of %s inset at line %d" % (words[1], i))
828 if not "\\SpecialChar" in document.body[i]:
831 for key, value in specialchars.items():
833 document.body[i] = document.body[i].replace("\\SpecialChar " + key, "\\SpecialChar " + value)
834 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + key, "\\SpecialCharNoPassThru " + value)
836 document.body[i] = document.body[i].replace("\\SpecialChar " + value, "\\SpecialChar " + key)
837 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + value, "\\SpecialCharNoPassThru " + key)
841 def convert_specialchar(document):
842 "convert special characters to new syntax"
843 convert_specialchar_internal(document, True)
846 def revert_specialchar(document):
847 "convert special characters to old syntax"
848 convert_specialchar_internal(document, False)
851 def revert_georgian(document):
852 "Set the document language to English but assure Georgian output"
854 if document.language == "georgian":
855 document.language = "english"
856 i = find_token(document.header, "\\language georgian", 0)
858 document.header[i] = "\\language english"
859 j = find_token(document.header, "\\language_package default", 0)
861 document.header[j] = "\\language_package babel"
862 insert_document_option(document, "georgian")
865 def revert_sigplan_doi(document):
866 " Reverts sigplanconf DOI layout to ERT "
868 if document.textclass != "sigplanconf":
873 i = find_token(document.body, "\\begin_layout DOI", i)
876 j = find_end_of_layout(document.body, i)
878 document.warning("Malformed LyX document: Can't find end of DOI layout")
882 content = lyx2latex(document, document.body[i:j + 1])
883 add_to_preamble(document, ["\\doi{" + content + "}"])
884 del document.body[i:j + 1]
888 def revert_ex_itemargs(document):
889 " Reverts \\item arguments of the example environments (Linguistics module) to TeX-code "
891 if not "linguistics" in document.get_module_list():
895 example_layouts = ["Numbered Examples (consecutive)", "Subexample"]
897 i = find_token(document.body, "\\begin_inset Argument item:", i)
900 j = find_end_of_inset(document.body, i)
901 # Find containing paragraph layout
902 parent = get_containing_layout(document.body, i)
904 document.warning("Malformed LyX document: Can't find parent paragraph layout")
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
919 def revert_forest(document):
920 " Reverts the forest environment (Linguistics module) to TeX-code "
922 if not "linguistics" in document.get_module_list():
927 i = find_token(document.body, "\\begin_inset Flex Structure Tree", i)
930 j = find_end_of_inset(document.body, i)
932 document.warning("Malformed LyX document: Can't find end of Structure Tree inset")
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])
940 add_to_preamble(document, ["\\usepackage{forest}"])
942 document.body[i:j + 1] = put_cmd_in_ert("\\begin{forest}" + content + "\\end{forest}")
946 def revert_glossgroup(document):
947 " Reverts the GroupGlossedWords inset (Linguistics module) to TeX-code "
949 if not "linguistics" in document.get_module_list():
954 i = find_token(document.body, "\\begin_inset Flex GroupGlossedWords", i)
957 j = find_end_of_inset(document.body, i)
959 document.warning("Malformed LyX document: Can't find end of GroupGlossedWords inset")
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])
967 document.body[i:j + 1] = ["{", "", content, "", "}"]
971 def revert_newgloss(document):
972 " Reverts the new Glosse insets (Linguistics module) to the old format "
974 if not "linguistics" in document.get_module_list():
977 glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
978 for glosse in glosses:
981 i = find_token(document.body, glosse, i)
984 j = find_end_of_inset(document.body, i)
986 document.warning("Malformed LyX document: Can't find end of Glosse inset")
990 arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
991 endarg = find_end_of_inset(document.body, arg)
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")
999 argendPlain = find_end_of_inset(document.body, argbeginPlain)
1000 argcontent = lyx2verbatim(document, document.body[argbeginPlain : argendPlain - 2])
1002 document.body[j:j] = ["", "\\begin_layout Plain Layout","\\backslash", "glt ",
1003 argcontent, "\\end_layout"]
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]
1009 del document.body[arg : endarg + 1]
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])
1015 document.body[beginPlain + 1:endPlain] = [content]
1018 # Dissolve ERT insets
1019 for glosse in glosses:
1022 i = find_token(document.body, glosse, i)
1025 j = find_end_of_inset(document.body, i)
1027 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1031 ert = find_token(document.body, "\\begin_inset ERT", i, j)
1034 ertend = find_end_of_inset(document.body, ert)
1036 document.warning("Malformed LyX document: Can't find end of ERT inset")
1039 ertcontent = get_ert(document.body, ert, True)
1040 document.body[ert : ertend + 1] = [ertcontent]
1044 def convert_newgloss(document):
1045 " Converts Glosse insets (Linguistics module) to the new format "
1047 if not "linguistics" in document.get_module_list():
1050 glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
1051 for glosse in glosses:
1054 i = find_token(document.body, glosse, i)
1057 j = find_end_of_inset(document.body, i)
1059 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1066 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", k, j)
1067 if beginPlain == -1:
1069 endPlain = find_end_of_layout(document.body, beginPlain)
1071 document.warning("Malformed LyX document: Can't find end of Glosse layout")
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"]
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"]
1088 endPlain = find_end_of_layout(document.body, beginPlain)
1090 j = find_end_of_inset(document.body, i)
1095 def convert_BoxFeatures(document):
1096 " adds new box features "
1100 i = find_token(document.body, "height_special", i)
1103 document.body[i+1:i+1] = ['thickness "0.4pt"', 'separation "3pt"', 'shadowsize "4pt"']
1107 def revert_BoxFeatures(document):
1108 " outputs new box features as TeX code "
1112 defaultThick = "0.4pt"
1113 defaultShadow = "4pt"
1115 i = find_token(document.body, "thickness", i)
1118 binset = find_token(document.body, "\\begin_inset Box", i - 11)
1119 if binset == -1 or binset != i - 11:
1121 continue # then "thickness" is is just a word in the text
1122 einset = find_end_of_inset(document.body, binset)
1124 document.warning("Malformed LyX document: Can't find end of box inset!")
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];
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:
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]
1170 def convert_origin(document):
1171 " Insert the origin tag "
1173 i = find_token(document.header, "\\textclass ", 0)
1175 document.warning("Malformed LyX document: No \\textclass!!")
1177 if document.dir == u'':
1181 if document.systemlyxdir and document.systemlyxdir != u'':
1183 if os.path.isabs(document.dir):
1184 absdir = os.path.normpath(document.dir)
1186 absdir = os.path.normpath(os.path.abspath(document.dir))
1187 if os.path.isabs(document.systemlyxdir):
1188 abssys = os.path.normpath(document.systemlyxdir)
1190 abssys = os.path.normpath(os.path.abspath(document.systemlyxdir))
1191 relpath = os.path.relpath(absdir, abssys)
1192 if relpath.find(u'..') == 0:
1197 origin = document.dir.replace(u'\\', u'/') + u'/'
1199 origin = os.path.join(u"/systemlyxdir", relpath).replace(u'\\', u'/') + u'/'
1200 document.header[i:i] = ["\\origin " + origin]
1203 def revert_origin(document):
1204 " Remove the origin tag "
1206 i = find_token(document.header, "\\origin ", 0)
1208 document.warning("Malformed LyX document: No \\origin!!")
1210 del document.header[i]
1213 color_names = ["brown", "darkgray", "gray", \
1214 "lightgray", "lime", "olive", "orange", \
1215 "pink", "purple", "teal", "violet"]
1217 def revert_textcolor(document):
1218 " revert new \\textcolor colors to TeX code "
1224 i = find_token(document.body, "\\color ", i)
1228 for color in list(color_names):
1229 if document.body[i] == "\\color " + color:
1230 # register that xcolor must be loaded in the preamble
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:
1240 # first output the closing brace
1242 document.body[k: k] = put_cmd_in_ert("}")
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 + "}{")
1250 def convert_colorbox(document):
1251 "Add color settings for boxes."
1254 i = find_token(document.body, "shadowsize", i)
1257 # check whether this is really a LyX Box setting
1258 start, end = is_in_inset(document.body, i, "\\begin_inset Box")
1260 find_token(document.body, "\\begin_layout", start, i) != -1):
1263 document.body[i+1:i+1] = ['framecolor "black"',
1264 'backgroundcolor "none"']
1268 def revert_colorbox(document):
1269 """Change box color settings to LaTeX code."""
1273 i = find_token(document.body, "\\begin_inset Box", i)
1277 j = find_end_of_inset(document.body, i)
1278 k = find_token(document.body, "\\begin_layout", i, j)
1280 document.warning("Malformed LyX document: no layout in Box inset!")
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!")
1290 if framecolor == "black" and backcolor == "none": # default values
1294 # Emulate non-default colours with LaTeX code:
1295 einset = find_end_of_inset(document.body, i)
1297 document.warning("Malformed LyX document: Can't find end of box inset!")
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:
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)
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))
1316 ertinset = put_cmd_in_ert("\\colorbox{%s}{" % backcolor)
1317 document.body[i:i] = ertinset + [""]
1321 def revert_mathmulticol(document):
1322 " Convert formulas to ERT if they contain multicolumns "
1326 i = find_token(document.body, '\\begin_inset Formula', i)
1329 j = find_end_of_inset(document.body, i)
1331 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
1334 lines = document.body[i:j]
1335 lines[0] = lines[0].replace('\\begin_inset Formula', '').lstrip()
1336 code = "\n".join(lines)
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
1352 i = find_end_of_inset(document.body, i)
1357 def revert_jss(document):
1358 " Reverts JSS In_Preamble commands to ERT in preamble "
1360 if document.textclass != "jss":
1363 # at first revert the inset layouts because
1364 # they can be part of the In_Preamble layouts
1367 "Proglang" : "proglang",
1372 for iltype in il_dict.keys():
1375 i = find_token(document.body, "\\begin_inset Flex " + iltype, i)
1379 if iltype == "Code" and document.body[i][-1] != "e":
1380 # Code Chunk inset. Continue
1384 end = find_end_of_inset(document.body, i)
1386 document.warning("Malformed LyX document: No end of Flex " + iltype + " found!")
1389 document.body[end - 2 : end + 1] = put_cmd_in_ert("}")
1390 document.body[i : i + 4] = put_cmd_in_ert("\\%s{" % il_dict[iltype])
1393 # now revert the In_Preamble layouts
1396 "Author" : "author",
1397 "Plain Author" : "Plainauthor",
1398 "Plain Title" : "Plaintitle",
1399 "Short Title" : "Shorttitle",
1400 "Abstract" : "Abstract",
1401 "Keywords" : "Keywords",
1402 "Plain Keywords" : "Plainkeywords",
1403 "Address" : "Address",
1405 for ipltype in ipl_dict.keys():
1408 i = find_token(document.body, "\\begin_layout " + ipltype, i)
1412 end = find_end_of_layout(document.body, i)
1414 document.warning("Malformed LyX document: Can't find end of " + ipltype + " layout")
1418 content = lyx2latex(document, document.body[i:end + 1])
1419 add_to_preamble(document, ["\\" + ipl_dict[ipltype] + "{" + content + "}"])
1420 del document.body[i:end + 1]
1427 i = find_token(document.body, "\\begin_inset Flex Code Chunk", i)
1431 end = find_end_of_inset(document.body, i)
1433 document.warning("Malformed LyX document: No end of Flex Code Chunk found!")
1437 document.body[end : end + 1] = ["\\end_layout", "", "\\begin_layout Standard"] + put_cmd_in_ert("\\end{CodeChunk}")
1438 document.body[i : i + 2] = put_cmd_in_ert("\\begin{CodeChunk}")
1441 # finally handle the code layouts
1443 "Code Input" : "CodeInput",
1444 "Code Output" : "CodeOutput",
1447 for ctype in codes_dict.keys():
1450 i = find_token(document.body, "\\begin_layout " + ctype, i)
1453 if document.body[i] != "\\begin_layout " + ctype:
1454 # Not an exact match!
1457 end = find_end_of_layout(document.body, i)
1459 document.warning("Malformed LyX document: No end of " + ctype + " layout found!")
1463 # Handle subsequent layouts
1465 j = find_token(document.body, "\\begin_layout ", seq_end)
1466 if j == -1 or document.body[j] != "\\begin_layout " + ctype:
1468 this_end = find_end_of_layout(document.body, j)
1470 document.warning("Malformed LyX document: No end of " + ctype + " layout found!")
1474 document.body[seq_end + 1 : seq_end + 1] = ["\\end_inset", "\\end_layout", "", "\\begin_layout Standard"] + put_cmd_in_ert("\\end{%s}" % codes_dict[ctype])
1478 document.body[k] = document.body[k].replace("\\begin_layout " + ctype, "\\begin_layout Plain Layout")
1480 document.body[i : i + 1] = ["\\end_layout", "", "\\begin_layout Standard"] \
1481 + put_cmd_in_ert("\\begin{%s}" % codes_dict[ctype]) \
1482 + ["\\end_layout", "", "\\begin_layout Standard", "", "\\begin_inset ERT", "status open", "", "\\begin_layout Plain Layout"]
1486 def convert_subref(document):
1487 " converts sub: ref prefixes to subref: "
1490 rx = re.compile(r'^name \"sub:(.+)$')
1493 i = find_token(document.body, "\\begin_inset CommandInset label", i)
1496 j = find_end_of_inset(document.body, i)
1498 document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1502 for p in range(i, j):
1503 m = rx.match(document.body[p])
1506 document.body[p] = "name \"subsec:" + label
1510 rx = re.compile(r'^reference \"sub:(.+)$')
1513 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1516 j = find_end_of_inset(document.body, i)
1518 document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1522 for p in range(i, j):
1523 m = rx.match(document.body[p])
1526 document.body[p] = "reference \"subsec:" + label
1532 def revert_subref(document):
1533 " reverts subref: ref prefixes to sub: "
1536 rx = re.compile(r'^name \"subsec:(.+)$')
1539 i = find_token(document.body, "\\begin_inset CommandInset label", i)
1542 j = find_end_of_inset(document.body, i)
1544 document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1548 for p in range(i, j):
1549 m = rx.match(document.body[p])
1552 document.body[p] = "name \"sub:" + label
1557 rx = re.compile(r'^reference \"subsec:(.+)$')
1560 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1563 j = find_end_of_inset(document.body, i)
1565 document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1569 for p in range(i, j):
1570 m = rx.match(document.body[p])
1573 document.body[p] = "reference \"sub:" + label
1578 def convert_nounzip(document):
1579 " remove the noUnzip parameter of graphics insets "
1581 rx = re.compile(r'\s*noUnzip\s*$')
1584 i = find_token(document.body, "\\begin_inset Graphics", i)
1587 j = find_end_of_inset(document.body, i)
1589 document.warning("Malformed LyX document: Can't find end of graphics inset at line " + str(i))
1593 k = find_re(document.body, rx, i, j)
1595 del document.body[k]
1600 def convert_revert_external_bbox(document, forward):
1601 " add units to bounding box of external insets "
1603 rx = re.compile(r'^\s*boundingBox\s+\S+\s+\S+\s+\S+\s+\S+\s*$')
1606 i = find_token(document.body, "\\begin_inset External", i)
1609 j = find_end_of_inset(document.body, i)
1611 document.warning("Malformed LyX document: Can't find end of external inset at line " + str(i))
1614 k = find_re(document.body, rx, i, j)
1618 tokens = document.body[k].split()
1620 for t in range(1, 5):
1623 for t in range(1, 5):
1624 tokens[t] = length_in_bp(tokens[t])
1625 document.body[k] = "\tboundingBox " + tokens[1] + " " + tokens[2] + " " + \
1626 tokens[3] + " " + tokens[4]
1630 def convert_external_bbox(document):
1631 convert_revert_external_bbox(document, True)
1634 def revert_external_bbox(document):
1635 convert_revert_external_bbox(document, False)
1638 def revert_tcolorbox_1(document):
1639 " Reverts the Flex:Subtitle inset of tcolorbox to TeX-code "
1641 i = find_token(document.header, "tcolorbox", 0)
1648 flex = find_token(document.body, "\\begin_inset Flex Subtitle", flex)
1652 flexEnd = find_end_of_inset(document.body, flex)
1654 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1658 wasOpt = revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, False, True, False)
1659 flexEnd = find_end_of_inset(document.body, flex)
1661 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1664 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, False, False, False)
1665 flexEnd = find_end_of_inset(document.body, flex)
1667 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1671 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1673 document.warning("Malformed LyX document! No Flex Subtitle layout found.")
1677 ep = find_end_of_layout(document.body, bp)
1679 document.warning("Malformed LyX document! No end of layout found.")
1683 document.body[ep : flexEnd + 1] = put_cmd_in_ert("}")
1685 document.body[flex : bp + 1] = put_cmd_in_ert("\\tcbsubtitle")
1687 document.body[flex : bp + 1] = put_cmd_in_ert("\\tcbsubtitle{")
1691 def revert_tcolorbox_2(document):
1692 " Reverts the Flex:Raster_Color_Box inset of tcolorbox to TeX-code "
1694 i = find_token(document.header, "tcolorbox", 0)
1700 flex = find_token(document.body, "\\begin_inset Flex Raster Color Box", flex)
1704 flexEnd = find_end_of_inset(document.body, flex)
1706 document.warning("Malformed LyX document! No end of Flex Raster Color Box found.")
1710 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1712 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1714 document.warning("Malformed LyX document! No plain layout in Raster Color Box found.")
1718 ep = find_end_of_layout(document.body, bp)
1720 document.warning("Malformed LyX document! No end of layout found.")
1724 flexEnd = find_end_of_inset(document.body, flex)
1726 document.warning("Malformed LyX document! No end of Flex Raster Color Box found.")
1729 document.body[ep : flexEnd + 1] = put_cmd_in_ert("\\end{tcbraster}")
1730 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{tcbraster}")
1734 def revert_tcolorbox_3(document):
1735 " Reverts the Flex:Custom_Color_Box_1 inset of tcolorbox to TeX-code "
1737 i = find_token(document.header, "tcolorbox", 0)
1743 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 1", flex)
1747 flexEnd = find_end_of_inset(document.body, flex)
1749 document.warning("Malformed LyX document! No end of Flex Custom Color Box 1 found.")
1753 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1754 flexEnd = find_end_of_inset(document.body, flex)
1756 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1759 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
1761 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1763 document.warning("Malformed LyX document! No plain layout in Custom Color Box 1 found.")
1767 ep = find_end_of_layout(document.body, bp)
1769 document.warning("Malformed LyX document! No end of layout found.")
1773 flexEnd = find_end_of_inset(document.body, flex)
1775 document.warning("Malformed LyX document! No end of Flex Custom Color Box 1 found.")
1779 document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxA}")
1780 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxA}")
1784 def revert_tcolorbox_4(document):
1785 " Reverts the Flex:Custom_Color_Box_2 inset of tcolorbox to TeX-code "
1787 i = find_token(document.header, "tcolorbox", 0)
1793 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 2", flex)
1797 flexEnd = find_end_of_inset(document.body, flex)
1799 document.warning("Malformed LyX document! No end of Flex Custom Color Box 2 found.")
1803 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1804 flexEnd = find_end_of_inset(document.body, flex)
1806 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1809 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
1810 flexEnd = find_end_of_inset(document.body, flex)
1812 document.warning("Malformed LyX document! No end of Flex Custom Color Box 2 found.")
1816 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1818 document.warning("Malformed LyX document! No plain layout in Custom Color Box 2 found.")
1822 ep = find_end_of_layout(document.body, bp)
1824 document.warning("Malformed LyX document! No end of layout found.")
1828 document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxB}")
1829 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxB}")
1833 def revert_tcolorbox_5(document):
1834 " Reverts the Flex:Custom_Color_Box_3 inset of tcolorbox to TeX-code "
1836 i = find_token(document.header, "tcolorbox", 0)
1842 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 3", flex)
1846 flexEnd = find_end_of_inset(document.body, flex)
1848 document.warning("Malformed LyX document! No end of Flex Custom Color Box 3 found.")
1852 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1853 flexEnd = find_end_of_inset(document.body, flex)
1855 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1858 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
1859 flexEnd = find_end_of_inset(document.body, flex)
1861 document.warning("Malformed LyX document! No end of Flex Custom Color Box 3 found.")
1865 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1867 document.warning("Malformed LyX document! No plain layout in Custom Color Box 3 found.")
1871 ep = find_end_of_layout(document.body, bp)
1873 document.warning("Malformed LyX document! No end of layout found.")
1877 document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxC}")
1878 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxC}")
1882 def revert_tcolorbox_6(document):
1883 " Reverts the Flex:Custom_Color_Box_4 inset of tcolorbox to TeX-code "
1885 i = find_token(document.header, "tcolorbox", 0)
1891 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 4", flex)
1895 flexEnd = find_end_of_inset(document.body, flex)
1897 document.warning("Malformed LyX document! No end of Flex Custom Color Box 4 found.")
1901 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1902 flexEnd = find_end_of_inset(document.body, flex)
1904 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1907 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
1908 flexEnd = find_end_of_inset(document.body, flex)
1910 document.warning("Malformed LyX document! No end of Flex Custom Color Box 4 found.")
1914 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1916 document.warning("Malformed LyX document! No plain layout in Custom Color Box 4 found.")
1920 ep = find_end_of_layout(document.body, bp)
1922 document.warning("Malformed LyX document! No end of layout found.")
1926 document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxD}")
1927 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxD}")
1931 def revert_tcolorbox_7(document):
1932 " Reverts the Flex:Custom_Color_Box_5 inset of tcolorbox to TeX-code "
1934 i = find_token(document.header, "tcolorbox", 0)
1940 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 5", flex)
1944 flexEnd = find_end_of_inset(document.body, flex)
1946 document.warning("Malformed LyX document! No end of Flex Custom Color Box 5 found.")
1950 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1951 flexEnd = find_end_of_inset(document.body, flex)
1953 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1956 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
1957 flexEnd = find_end_of_inset(document.body, flex)
1959 document.warning("Malformed LyX document! No end of Flex Custom Color Box 5 found.")
1963 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1965 document.warning("Malformed LyX document! No plain layout in Custom Color Box 5 found.")
1969 ep = find_end_of_layout(document.body, bp)
1971 document.warning("Malformed LyX document! No end of layout found.")
1975 document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxE}")
1976 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxE}")
1980 def revert_tcolorbox_8(document):
1981 " Reverts the layout New Color Box Type of tcolorbox to TeX-code "
1985 i = find_token(document.body, "\\begin_layout New Color Box Type", i)
1989 j = find_end_of_layout(document.body, i)
1991 document.warning("Malformed LyX document! No end of New Color Box Type layout found.")
1995 wasOpt = revert_Argument_to_TeX_brace(document, i, j, 1, 1, False, True, True)
1996 j = find_end_of_layout(document.body, i)
1998 document.warning("Malformed LyX document! No end of New Color Box Type layout found.")
2001 revert_Argument_to_TeX_brace(document, i, j, 2, 2, False, False, True)
2002 j = find_end_of_layout(document.body, i)
2004 document.warning("Malformed LyX document! No end of New Color Box Type layout found.")
2007 revert_Argument_to_TeX_brace(document, i, j, 3, 4, False, True, False)
2008 j = find_end_of_layout(document.body, i)
2010 document.warning("Malformed LyX document! No end of New Color Box Type layout found.")
2013 document.body[i] = document.body[i].replace("\\begin_layout New Color Box Type", "\\begin_layout Standard")
2014 document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox")
2015 k = find_end_of_inset(document.body, j)
2016 document.body[k + 2 : j + 2] = put_cmd_in_ert("{") + ["\\begin_inset ERT", "status collapsed", "\\begin_layout Plain Layout"]
2017 j = find_token(document.body, "\\begin_layout Standard", j + 1)
2018 document.body[j - 2 : j - 2] = ["\\end_layout", "\\end_inset"] + put_cmd_in_ert("}")
2022 def revert_moderncv_1(document):
2023 " Reverts the new inset of moderncv to TeX-code in preamble "
2025 if document.textclass != "moderncv":
2031 # at first revert the new styles
2033 i = find_token(document.body, "\\begin_layout CVIcons", 0)
2036 j = find_end_of_layout(document.body, i)
2038 document.warning("Malformed LyX document: Can't find end of CVIcons layout")
2041 content = lyx2latex(document, document.body[i:j + 1])
2042 add_to_preamble(document, ["\\moderncvicons{" + content + "}"])
2043 del document.body[i:j + 1]
2045 i = find_token(document.body, "\\begin_layout CVColumnWidth", 0)
2048 j = find_end_of_layout(document.body, i)
2050 document.warning("Malformed LyX document: Can't find end of CVColumnWidth layout")
2053 content = lyx2latex(document, document.body[i:j + 1])
2054 add_to_preamble(document, ["\\setlength{\hintscolumnwidth}{" + content + "}"])
2055 del document.body[i:j + 1]
2056 # now change the new styles to the obsolete ones
2058 i = find_token(document.body, "\\begin_layout Name", 0)
2061 j = find_end_of_layout(document.body, i)
2063 document.warning("Malformed LyX document: Can't find end of Name layout")
2066 lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
2067 if lineArg > j and j != 0:
2070 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
2071 # we have to assure that no other inset is in the Argument
2072 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
2073 endInset = find_token(document.body, "\\end_inset", beginPlain)
2076 while beginInset < endInset and beginInset != -1:
2077 beginInset = find_token(document.body, "\\begin_inset", k)
2078 endInset = find_token(document.body, "\\end_inset", l)
2081 Arg2 = document.body[l + 5 : l + 6]
2083 document.body[i : i + 1]= ["\\begin_layout FirstName"]
2084 # delete the Argument inset
2085 del( document.body[endInset - 2 : endInset + 3])
2086 del( document.body[lineArg : beginPlain + 1])
2087 document.body[i + 4 : i + 4]= ["\\begin_layout FamilyName"] + Arg2 + ["\\end_layout"] + [""]
2090 def revert_moderncv_2(document):
2091 " Reverts the phone inset of moderncv to the obsoleted mobile or fax "
2093 if document.textclass != "moderncv":
2100 i = find_token(document.body, "\\begin_layout Phone", i)
2103 j = find_end_of_layout(document.body, i)
2105 document.warning("Malformed LyX document: Can't find end of Phone layout")
2108 lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
2109 if lineArg > j and j != 0:
2113 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
2114 # we have to assure that no other inset is in the Argument
2115 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
2116 endInset = find_token(document.body, "\\end_inset", beginPlain)
2119 while beginInset < endInset and beginInset != -1:
2120 beginInset = find_token(document.body, "\\begin_inset", k)
2121 endInset = find_token(document.body, "\\end_inset", l)
2124 Arg = document.body[beginPlain + 1 : beginPlain + 2]
2126 if Arg[0] == "mobile":
2127 document.body[i : i + 1]= ["\\begin_layout Mobile"]
2129 document.body[i : i + 1]= ["\\begin_layout Fax"]
2130 # delete the Argument inset
2131 del(document.body[endInset - 2 : endInset + 1])
2132 del(document.body[lineArg : beginPlain + 3])
2136 def convert_moderncv_phone(document):
2137 " Convert the Fax and Mobile inset of moderncv to the new phone inset "
2139 if document.textclass != "moderncv":
2146 "Mobile" : "mobile",
2150 rx = re.compile(r'^\\begin_layout (\S+)$')
2152 # substitute \fax and \mobile by \phone[fax] and \phone[mobile], respectively
2153 i = find_token(document.body, "\\begin_layout", i)
2157 m = rx.match(document.body[i])
2161 if val not in list(phone_dict.keys()):
2164 j = find_end_of_layout(document.body, i)
2166 document.warning("Malformed LyX document: Can't find end of Mobile layout")
2170 document.body[i : i + 1] = ["\\begin_layout Phone", "\\begin_inset Argument 1", "status open", "",
2171 "\\begin_layout Plain Layout", phone_dict[val], "\\end_layout", "",
2175 def convert_moderncv_name(document):
2176 " Convert the FirstName and LastName layout of moderncv to the general Name layout "
2178 if document.textclass != "moderncv":
2181 fnb = 0 # Begin of FirstName inset
2182 fne = 0 # End of FirstName inset
2183 lnb = 0 # Begin of LastName (FamilyName) inset
2184 lne = 0 # End of LastName (FamilyName) inset
2185 nb = 0 # Begin of substituting Name inset
2186 ne = 0 # End of substituting Name inset
2187 FirstName = [] # FirstName content
2188 FamilyName = [] # LastName content
2192 fnb = find_token(document.body, "\\begin_layout FirstName", fnb)
2194 fne = find_end_of_layout(document.body, fnb)
2196 document.warning("Malformed LyX document: Can't find end of FirstName layout")
2198 FirstName = document.body[fnb + 1 : fne]
2200 lnb = find_token(document.body, "\\begin_layout FamilyName", lnb)
2202 lne = find_end_of_layout(document.body, lnb)
2204 document.warning("Malformed LyX document: Can't find end of FamilyName layout")
2206 FamilyName = document.body[lnb + 1 : lne]
2207 # Determine the region for the substituting Name layout
2208 if fnb == -1 and lnb == -1: # Neither FirstName nor FamilyName exists -> Do nothing
2210 elif fnb == -1: # Only FamilyName exists -> New Name insets replaces that
2213 elif lnb == -1: # Only FirstName exists -> New Name insets replaces that
2216 elif fne > lne: # FirstName position before FamilyName -> New Name insets spans
2217 nb = lnb # from FamilyName begin
2218 ne = fne # to FirstName end
2219 else: # FirstName position before FamilyName -> New Name insets spans
2220 nb = fnb # from FirstName begin
2221 ne = lne # to FamilyName end
2223 # Insert the substituting layout now. If FirstName exists, use an otpional argument.
2225 document.body[nb : ne + 1] = ["\\begin_layout Name"] + FamilyName + ["\\end_layout", ""]
2227 document.body[nb : ne + 1] = ["\\begin_layout Name", "\\begin_inset Argument 1", "status open", "",
2228 "\\begin_layout Plain Layout"] + FirstName + ["\\end_layout", "",
2229 "\\end_inset", ""] + FamilyName + ["\\end_layout", ""]
2232 def revert_achemso(document):
2233 " Reverts the flex inset Latin to TeX code "
2235 if document.textclass != "achemso":
2240 i = find_token(document.body, "\\begin_inset Flex Latin", i)
2242 j = find_end_of_inset(document.body, i)
2246 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2247 endPlain = find_end_of_layout(document.body, beginPlain)
2248 content = lyx2latex(document, document.body[beginPlain : endPlain])
2249 document.body[i:j + 1] = put_cmd_in_ert("\\latin{" + content + "}")
2251 document.warning("Malformed LyX document: Can't find end of flex inset Latin")
2256 fontsettings = ["\\font_roman", "\\font_sans", "\\font_typewriter", "\\font_math", \
2257 "\\font_sf_scale", "\\font_tt_scale"]
2258 fontdefaults = ["default", "default", "default", "auto", "100", "100"]
2259 fontquotes = [True, True, True, True, False, False]
2261 def convert_fontsettings(document):
2262 " Duplicate font settings "
2264 i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2266 document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2267 use_non_tex_fonts = "false"
2269 use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2271 for f in fontsettings:
2272 i = find_token(document.header, f + " ", 0)
2274 document.warning("Malformed LyX document: No " + f + "!")
2276 # note that with i = -1, this will insert at the end
2278 value = fontdefaults[j]
2280 value = document.header[i][len(f):].strip()
2282 if use_non_tex_fonts == "true":
2283 document.header[i:i+1] = [f + ' "' + fontdefaults[j] + '" "' + value + '"']
2285 document.header[i:i+1] = [f + ' "' + value + '" "' + fontdefaults[j] + '"']
2287 if use_non_tex_fonts == "true":
2288 document.header[i:i+1] = [f + ' ' + fontdefaults[j] + ' ' + value]
2290 document.header[i:i+1] = [f + ' ' + value + ' ' + fontdefaults[j]]
2294 def revert_fontsettings(document):
2295 " Merge font settings "
2297 i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2299 document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2300 use_non_tex_fonts = "false"
2302 use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2304 for f in fontsettings:
2305 i = find_token(document.header, f + " ", 0)
2307 document.warning("Malformed LyX document: No " + f + "!")
2310 line = get_value(document.header, f, i)
2313 q2 = line.find('"', q1+1)
2314 q3 = line.find('"', q2+1)
2315 q4 = line.find('"', q3+1)
2316 if q1 == -1 or q2 == -1 or q3 == -1 or q4 == -1:
2317 document.warning("Malformed LyX document: Missing quotes!")
2320 if use_non_tex_fonts == "true":
2321 document.header[i:i+1] = [f + ' ' + line[q3+1:q4]]
2323 document.header[i:i+1] = [f + ' ' + line[q1+1:q2]]
2325 if use_non_tex_fonts == "true":
2326 document.header[i:i+1] = [f + ' ' + line.split()[1]]
2328 document.header[i:i+1] = [f + ' ' + line.split()[0]]
2332 def revert_solution(document):
2333 " Reverts the solution environment of the theorem module to TeX code "
2335 # Do we use one of the modules that provides Solution?
2337 mods = document.get_module_list()
2339 if mod == "theorems-std" or mod == "theorems-bytype" \
2340 or mod == "theorems-ams" or mod == "theorems-ams-bytype":
2350 i = find_token(document.body, "\\begin_layout Solution", i)
2354 is_starred = document.body[i].startswith("\\begin_layout Solution*")
2355 if is_starred == True:
2357 LyXName = "Solution*"
2358 theoremName = "newtheorem*"
2361 LyXName = "Solution"
2362 theoremName = "newtheorem"
2364 j = find_end_of_layout(document.body, i)
2366 document.warning("Malformed LyX document: Can't find end of " + LyXName + " layout")
2370 # if this is not a consecutive env, add start command
2373 begcmd = put_cmd_in_ert("\\begin{%s}" % (LaTeXName))
2375 # has this a consecutive theorem of same type?
2376 consecutive = document.body[j + 2] == "\\begin_layout " + LyXName
2378 # if this is not followed by a consecutive env, add end command
2380 document.body[j : j + 1] = put_cmd_in_ert("\\end{%s}" % (LaTeXName)) + ["\\end_layout"]
2382 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
2384 add_to_preamble(document, "\\theoremstyle{definition}")
2385 if is_starred or mod == "theorems-bytype" or mod == "theorems-ams-bytype":
2386 add_to_preamble(document, "\\%s{%s}{\\protect\\solutionname}" % \
2387 (theoremName, LaTeXName))
2388 else: # mod == "theorems-std" or mod == "theorems-ams" and not is_starred
2389 add_to_preamble(document, "\\%s{%s}[thm]{\\protect\\solutionname}" % \
2390 (theoremName, LaTeXName))
2392 add_to_preamble(document, "\\providecommand{\solutionname}{Solution}")
2396 def revert_verbatim_star(document):
2397 from lyx_2_1 import revert_verbatim
2398 revert_verbatim(document, True)
2401 def convert_save_props(document):
2402 " Add save_transient_properties parameter. "
2403 i = find_token(document.header, '\\begin_header', 0)
2405 document.warning("Malformed lyx document: Missing '\\begin_header'.")
2407 document.header.insert(i + 1, '\\save_transient_properties true')
2410 def revert_save_props(document):
2411 " Remove save_transient_properties parameter. "
2412 i = find_token(document.header, "\\save_transient_properties", 0)
2415 del document.header[i]
2418 def convert_info_tabular_feature(document):
2420 return arg.replace("inset-modify tabular", "tabular-feature")
2421 convert_info_insets(document, "shortcut(s)?|icon", f)
2424 def revert_info_tabular_feature(document):
2426 return arg.replace("tabular-feature", "inset-modify tabular")
2427 convert_info_insets(document, "shortcut(s)?|icon", f)
2434 supported_versions = ["2.2.0", "2.2"]
2436 [475, [convert_separator]],
2437 # nothing to do for 476: We consider it a bug that older versions
2438 # did not load amsmath automatically for these commands, and do not
2439 # want to hardcode amsmath off.
2445 [481, [convert_dashes]],
2446 [482, [convert_phrases]],
2447 [483, [convert_specialchar]],
2452 [488, [convert_newgloss]],
2453 [489, [convert_BoxFeatures]],
2454 [490, [convert_origin]],
2456 [492, [convert_colorbox]],
2459 [495, [convert_subref]],
2460 [496, [convert_nounzip]],
2461 [497, [convert_external_bbox]],
2463 [499, [convert_moderncv_phone, convert_moderncv_name]],
2465 [501, [convert_fontsettings]],
2468 [504, [convert_save_props]],
2470 [506, [convert_info_tabular_feature]],
2471 [507, [convert_longtable_label]],
2472 [508, [convert_parbreak]]
2476 [507, [revert_parbreak]],
2477 [506, [revert_longtable_label]],
2478 [505, [revert_info_tabular_feature]],
2480 [503, [revert_save_props]],
2481 [502, [revert_verbatim_star]],
2482 [501, [revert_solution]],
2483 [500, [revert_fontsettings]],
2484 [499, [revert_achemso]],
2485 [498, [revert_moderncv_1, revert_moderncv_2]],
2486 [497, [revert_tcolorbox_1, revert_tcolorbox_2,
2487 revert_tcolorbox_3, revert_tcolorbox_4, revert_tcolorbox_5,
2488 revert_tcolorbox_6, revert_tcolorbox_7, revert_tcolorbox_8]],
2489 [496, [revert_external_bbox]],
2490 [495, []], # nothing to do since the noUnzip parameter was optional
2491 [494, [revert_subref]],
2492 [493, [revert_jss]],
2493 [492, [revert_mathmulticol]],
2494 [491, [revert_colorbox]],
2495 [490, [revert_textcolor]],
2496 [489, [revert_origin]],
2497 [488, [revert_BoxFeatures]],
2498 [487, [revert_newgloss, revert_glossgroup]],
2499 [486, [revert_forest]],
2500 [485, [revert_ex_itemargs]],
2501 [484, [revert_sigplan_doi]],
2502 [483, [revert_georgian]],
2503 [482, [revert_specialchar]],
2504 [481, [revert_phrases]],
2505 [480, [revert_dashes]],
2506 [479, [revert_question_env]],
2507 [478, [revert_beamer_lemma]],
2508 [477, [revert_xarrow]],
2509 [476, [revert_swissgerman]],
2510 [475, [revert_smash]],
2511 [474, [revert_separator]]
2515 if __name__ == "__main__":