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: "
350 "Can't convert separator inset at line %d"%i)
353 if lay[0] == "Standard":
354 # Convert only if not alone in the paragraph
355 k1 = find_nonempty_line(document.body, lay[1] + 1, i + 1)
356 k2 = find_nonempty_line(document.body, i + 1, lay[2])
357 if (k1 < i) or (k2 > i + 1) or not check_token(document.body[i], parbreakinset):
358 document.body[i] = document.body[i].replace("parbreak", "latexpar")
360 document.body[i] = document.body[i].replace("parbreak", "latexpar")
364 def revert_parbreak(document):
366 Revert latexpar separators to parbreak separators.
370 i = find_token(document.body, "\\begin_inset Separator latexpar", i)
373 document.body[i] = document.body[i].replace("latexpar", "parbreak")
377 def revert_smash(document):
378 " Set amsmath to on if smash commands are used "
380 commands = ["smash[t]", "smash[b]", "notag"]
381 i = find_token(document.header, "\\use_package amsmath", 0)
383 document.warning("Malformed LyX document: Can't find \\use_package amsmath.")
385 value = get_value(document.header, "\\use_package amsmath", i).split()[1]
387 # nothing to do if package is not auto but on or off
391 j = find_token(document.body, '\\begin_inset Formula', j)
394 k = find_end_of_inset(document.body, j)
396 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(j))
399 code = "\n".join(document.body[j:k])
401 if code.find("\\%s" % c) != -1:
402 # set amsmath to on, since it is loaded by the newer format
403 document.header[i] = "\\use_package amsmath 2"
408 def revert_swissgerman(document):
409 " Set language german-ch-old to german "
411 if document.language == "german-ch-old":
412 document.language = "german"
413 i = find_token(document.header, "\\language", 0)
415 document.header[i] = "\\language german"
418 j = find_token(document.body, "\\lang german-ch-old", j)
421 document.body[j] = document.body[j].replace("\\lang german-ch-old", "\\lang german")
425 def revert_use_package(document, pkg, commands, oldauto, supported):
426 # oldauto defines how the version we are reverting to behaves:
427 # if it is true, the old version uses the package automatically.
428 # if it is false, the old version never uses the package.
429 # If "supported" is true, the target version also supports this
431 regexp = re.compile(r'(\\use_package\s+%s)' % pkg)
432 p = find_re(document.header, regexp, 0)
433 value = "1" # default is auto
435 value = get_value(document.header, "\\use_package" , p).split()[1]
437 del document.header[p]
438 if value == "2" and not supported: # on
439 add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
440 elif value == "1" and not oldauto: # auto
443 i = find_token(document.body, '\\begin_inset Formula', i)
446 j = find_end_of_inset(document.body, i)
448 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
451 code = "\n".join(document.body[i:j])
453 if code.find("\\%s" % c) != -1:
455 document.header[p] = "\\use_package " + pkg + " 2"
457 add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
462 mathtools_commands = ["xhookrightarrow", "xhookleftarrow", "xRightarrow", \
463 "xrightharpoondown", "xrightharpoonup", "xrightleftharpoons", \
464 "xLeftarrow", "xleftharpoondown", "xleftharpoonup", \
465 "xleftrightarrow", "xLeftrightarrow", "xleftrightharpoons", \
468 def revert_xarrow(document):
469 "remove use_package mathtools"
470 revert_use_package(document, "mathtools", mathtools_commands, False, True)
473 def revert_beamer_lemma(document):
474 " Reverts beamer lemma layout to ERT "
476 beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
477 if document.textclass not in beamer_classes:
483 i = find_token(document.body, "\\begin_layout Lemma", i)
486 j = find_end_of_layout(document.body, i)
488 document.warning("Malformed LyX document: Can't find end of Lemma layout")
491 arg1 = find_token(document.body, "\\begin_inset Argument 1", i, j)
492 endarg1 = find_end_of_inset(document.body, arg1)
493 arg2 = find_token(document.body, "\\begin_inset Argument 2", i, j)
494 endarg2 = find_end_of_inset(document.body, arg2)
498 beginPlain1 = find_token(document.body, "\\begin_layout Plain Layout", arg1, endarg1)
499 if beginPlain1 == -1:
500 document.warning("Malformed LyX document: Can't find arg1 plain Layout")
503 endPlain1 = find_end_of_inset(document.body, beginPlain1)
504 content1 = document.body[beginPlain1 + 1 : endPlain1 - 2]
505 subst1 = put_cmd_in_ert("<") + content1 + put_cmd_in_ert(">")
507 beginPlain2 = find_token(document.body, "\\begin_layout Plain Layout", arg2, endarg2)
508 if beginPlain2 == -1:
509 document.warning("Malformed LyX document: Can't find arg2 plain Layout")
512 endPlain2 = find_end_of_inset(document.body, beginPlain2)
513 content2 = document.body[beginPlain2 + 1 : endPlain2 - 2]
514 subst2 = put_cmd_in_ert("[") + content2 + put_cmd_in_ert("]")
518 del document.body[arg2 : endarg2 + 1]
520 del document.body[arg1 : endarg1 + 1]
522 del document.body[arg1 : endarg1 + 1]
524 del document.body[arg2 : endarg2 + 1]
526 # index of end layout has probably changed
527 j = find_end_of_layout(document.body, i)
529 document.warning("Malformed LyX document: Can't find end of Lemma layout")
535 # if this is not a consecutive env, add start command
537 begcmd = put_cmd_in_ert("\\begin{lemma}")
539 # has this a consecutive lemma?
540 consecutive = document.body[j + 2] == "\\begin_layout Lemma"
542 # if this is not followed by a consecutive env, add end command
544 document.body[j : j + 1] = put_cmd_in_ert("\\end{lemma}") + ["\\end_layout"]
546 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd + subst1 + subst2
552 def revert_question_env(document):
554 Reverts question and question* environments of
555 theorems-ams-extended-bytype module to ERT
558 # Do we use theorems-ams-extended-bytype module?
559 if not "theorems-ams-extended-bytype" in document.get_module_list():
565 i = find_token(document.body, "\\begin_layout Question", i)
569 starred = document.body[i] == "\\begin_layout Question*"
571 j = find_end_of_layout(document.body, i)
573 document.warning("Malformed LyX document: Can't find end of Question layout")
577 # if this is not a consecutive env, add start command
581 begcmd = put_cmd_in_ert("\\begin{question*}")
583 begcmd = put_cmd_in_ert("\\begin{question}")
585 # has this a consecutive theorem of same type?
588 consecutive = document.body[j + 2] == "\\begin_layout Question*"
590 consecutive = document.body[j + 2] == "\\begin_layout Question"
592 # if this is not followed by a consecutive env, add end command
595 document.body[j : j + 1] = put_cmd_in_ert("\\end{question*}") + ["\\end_layout"]
597 document.body[j : j + 1] = put_cmd_in_ert("\\end{question}") + ["\\end_layout"]
599 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
601 add_to_preamble(document, "\\providecommand{\questionname}{Question}")
604 add_to_preamble(document, "\\theoremstyle{plain}\n" \
605 "\\newtheorem*{question*}{\\protect\\questionname}")
607 add_to_preamble(document, "\\theoremstyle{plain}\n" \
608 "\\newtheorem{question}{\\protect\\questionname}")
613 def convert_dashes(document):
614 "convert -- and --- to \\twohyphens and \\threehyphens"
616 if document.backend != "latex":
621 i = find_substring(document.body, "--", i+1)
624 line = document.body[i]
625 # skip label width string (bug 10243):
626 if line.startswith("\\labelwidthstring"):
628 # Do not touch hyphens in some insets:
630 value, start, end = get_containing_inset(document.body, i)
632 # False means no (or malformed) containing inset
633 value, start, end = "no inset", -1, -1
634 # We must not replace anything in insets that store LaTeX contents in .lyx files
635 # (math and command insets without overridden read() and write() methods.
636 # Filtering out IPA and ERT makes Text::readParToken() more simple,
637 # Flex Code is logical markup, typically rendered as typewriter
638 if (value.split()[0] in ["CommandInset", "ERT", "External", "Formula",
639 "FormulaMacro", "Graphics", "IPA", "listings"]
640 or value in ["Flex Code", "Flex URL"]):
644 layout, start, end, j = get_containing_layout(document.body, i)
645 except TypeError: # no (or malformed) containing layout
646 document.warning("Malformed LyX document: "
647 "Can't find layout at line %d" % i)
649 if layout == "LyX-Code":
652 # We can have an arbitrary number of consecutive hyphens.
653 # Replace as LaTeX does: First try emdash, then endash
654 line = line.replace("---", "\\threehyphens\n")
655 line = line.replace("--", "\\twohyphens\n")
656 document.body[i:i+1] = line.split('\n')
658 # remove ligature breaks between dashes
661 i = find_substring(document.body,
662 r"-\SpecialChar \textcompwordmark{}", i+1)
665 if document.body[i+1].startswith("-"):
666 document.body[i] = document.body[i].replace(
667 r"\SpecialChar \textcompwordmark{}", document.body.pop(i+1))
670 def revert_dashes(document):
672 Remove preamble code from 2.3->2.2 conversion.
673 Prevent ligatures of existing --- and --.
674 Revert \\twohyphens and \\threehyphens to -- and ---.
676 del_complete_lines(document.preamble,
677 ['% Added by lyx2lyx',
678 r'\renewcommand{\textendash}{--}',
679 r'\renewcommand{\textemdash}{---}'])
681 # Insert ligature breaks to prevent ligation of hyphens to dashes:
684 i = find_substring(document.body, "--", i+1)
687 line = document.body[i]
688 # skip label width string (bug 10243):
689 if line.startswith("\\labelwidthstring"):
691 # do not touch hyphens in some insets (cf. convert_dashes):
693 value, start, end = get_containing_inset(document.body, i)
695 # False means no (or malformed) containing inset
696 value, start, end = "no inset", -1, -1
697 if (value.split()[0] in ["CommandInset", "ERT", "External", "Formula",
698 "FormulaMacro", "Graphics", "IPA", "listings"]
699 or value == "Flex URL"):
702 line = line.replace("--", "-\\SpecialChar \\textcompwordmark{}\n-")
703 document.body[i:i+1] = line.split('\n')
705 # Revert \twohyphens and \threehyphens:
707 while i < len(document.body):
708 line = document.body[i]
709 if not line.endswith("hyphens"):
711 elif line.endswith("\\twohyphens") or line.endswith("\\threehyphens"):
712 line = line.replace("\\twohyphens", "--")
713 line = line.replace("\\threehyphens", "---")
714 document.body[i] = line + document.body.pop(i+1)
719 # order is important for the last three!
720 phrases = ["LyX", "LaTeX2e", "LaTeX", "TeX"]
722 def is_part_of_converted_phrase(line, j, phrase):
723 "is phrase part of an already converted phrase?"
725 converted = "\\SpecialCharNoPassThru \\" + p
726 pos = j + len(phrase) - len(converted)
728 if line[pos:pos+len(converted)] == converted:
733 def convert_phrases(document):
734 "convert special phrases from plain text to \\SpecialCharNoPassThru"
736 if document.backend != "latex":
740 while i < len(document.body):
741 if document.body[i] and document.body[i][0] == "\\":
742 words = document.body[i].split()
743 if len(words) > 1 and words[0] == "\\begin_inset" and \
744 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
745 # must not replace anything in insets that store LaTeX contents in .lyx files
746 # (math and command insets without overridden read() and write() methods)
747 j = find_end_of_inset(document.body, i)
749 document.warning("Malformed LyX document: Can't find end of inset at line %d" % (i))
756 for phrase in phrases:
757 j = document.body[i].find(phrase)
760 if not is_part_of_converted_phrase(document.body[i], j, phrase):
761 front = document.body[i][:j]
762 back = document.body[i][j+len(phrase):]
764 document.body.insert(i+1, back)
765 # We cannot use SpecialChar since we do not know whether we are outside passThru
766 document.body[i] = front + "\\SpecialCharNoPassThru \\" + phrase
770 def revert_phrases(document):
771 "revert special phrases to plain text"
774 while i < len(document.body):
775 words = document.body[i].split()
776 if len(words) > 1 and words[0] == "\\begin_inset" and \
777 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
778 # see convert_phrases
779 j = find_end_of_inset(document.body, i)
781 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
787 for phrase in phrases:
788 # we can replace SpecialChar since LyX ensures that it cannot be inserted into passThru parts
789 if document.body[i].find("\\SpecialChar \\" + phrase) >= 0:
790 document.body[i] = document.body[i].replace("\\SpecialChar \\" + phrase, phrase)
792 if document.body[i].find("\\SpecialCharNoPassThru \\" + phrase) >= 0:
793 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru \\" + phrase, phrase)
795 if replaced and i+1 < len(document.body) and \
796 (document.body[i+1].find("\\") != 0 or \
797 document.body[i+1].find("\\SpecialChar") == 0) and \
798 len(document.body[i]) + len(document.body[i+1]) <= 80:
799 document.body[i] = document.body[i] + document.body[i+1]
800 document.body[i+1:i+2] = []
805 def convert_specialchar_internal(document, forward):
806 specialchars = {"\\-":"softhyphen", "\\textcompwordmark{}":"ligaturebreak", \
807 "\\@.":"endofsentence", "\\ldots{}":"ldots", \
808 "\\menuseparator":"menuseparator", "\\slash{}":"breakableslash", \
809 "\\nobreakdash-":"nobreakdash", "\\LyX":"LyX", \
810 "\\TeX":"TeX", "\\LaTeX2e":"LaTeX2e", \
811 "\\LaTeX":"LaTeX" # must be after LaTeX2e
815 while i < len(document.body):
816 if document.body[i] and document.body[i][0] == "\\":
817 words = document.body[i].split()
818 if len(words) > 1 and words[0] == "\\begin_inset" and \
819 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
820 # see convert_phrases
821 j = find_end_of_inset(document.body, i)
823 document.warning("Malformed LyX document: Can't find end of %s inset at line %d" % (words[1], i))
829 if not "\\SpecialChar" in document.body[i]:
832 for key, value in specialchars.items():
834 document.body[i] = document.body[i].replace("\\SpecialChar " + key, "\\SpecialChar " + value)
835 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + key, "\\SpecialCharNoPassThru " + value)
837 document.body[i] = document.body[i].replace("\\SpecialChar " + value, "\\SpecialChar " + key)
838 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + value, "\\SpecialCharNoPassThru " + key)
842 def convert_specialchar(document):
843 "convert special characters to new syntax"
844 convert_specialchar_internal(document, True)
847 def revert_specialchar(document):
848 "convert special characters to old syntax"
849 convert_specialchar_internal(document, False)
852 def revert_georgian(document):
853 "Set the document language to English but assure Georgian output"
855 if document.language == "georgian":
856 document.language = "english"
857 i = find_token(document.header, "\\language georgian", 0)
859 document.header[i] = "\\language english"
860 j = find_token(document.header, "\\language_package default", 0)
862 document.header[j] = "\\language_package babel"
863 insert_document_option(document, "georgian")
866 def revert_sigplan_doi(document):
867 " Reverts sigplanconf DOI layout to ERT "
869 if document.textclass != "sigplanconf":
874 i = find_token(document.body, "\\begin_layout DOI", i)
877 j = find_end_of_layout(document.body, i)
879 document.warning("Malformed LyX document: Can't find end of DOI layout")
883 content = lyx2latex(document, document.body[i:j + 1])
884 add_to_preamble(document, ["\\doi{" + content + "}"])
885 del document.body[i:j + 1]
889 def revert_ex_itemargs(document):
890 " Reverts \\item arguments of the example environments (Linguistics module) to TeX-code "
892 if not "linguistics" in document.get_module_list():
896 example_layouts = ["Numbered Examples (consecutive)", "Subexample"]
898 i = find_token(document.body, "\\begin_inset Argument item:", i)
901 j = find_end_of_inset(document.body, i)
902 # Find containing paragraph layout
903 parent = get_containing_layout(document.body, i)
905 document.warning("Malformed LyX document: Can't find parent paragraph layout")
909 layoutname = parent[0]
910 if layoutname in example_layouts:
911 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
912 endPlain = find_end_of_layout(document.body, beginPlain)
913 content = document.body[beginPlain + 1 : endPlain]
914 del document.body[i:j+1]
915 subst = put_cmd_in_ert("[") + content + put_cmd_in_ert("]")
916 document.body[parbeg : parbeg] = subst
920 def revert_forest(document):
921 " Reverts the forest environment (Linguistics module) to TeX-code "
923 if not "linguistics" in document.get_module_list():
928 i = find_token(document.body, "\\begin_inset Flex Structure Tree", i)
931 j = find_end_of_inset(document.body, i)
933 document.warning("Malformed LyX document: Can't find end of Structure Tree inset")
937 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
938 endPlain = find_end_of_layout(document.body, beginPlain)
939 content = lyx2latex(document, document.body[beginPlain : endPlain])
941 add_to_preamble(document, ["\\usepackage{forest}"])
943 document.body[i:j + 1] = put_cmd_in_ert("\\begin{forest}" + content + "\\end{forest}")
947 def revert_glossgroup(document):
948 " Reverts the GroupGlossedWords inset (Linguistics module) to TeX-code "
950 if not "linguistics" in document.get_module_list():
955 i = find_token(document.body, "\\begin_inset Flex GroupGlossedWords", i)
958 j = find_end_of_inset(document.body, i)
960 document.warning("Malformed LyX document: Can't find end of GroupGlossedWords inset")
964 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
965 endPlain = find_end_of_layout(document.body, beginPlain)
966 content = lyx2verbatim(document, document.body[beginPlain : endPlain])
968 document.body[i:j + 1] = ["{", "", content, "", "}"]
972 def revert_newgloss(document):
973 " Reverts the new Glosse insets (Linguistics module) to the old format "
975 if not "linguistics" in document.get_module_list():
978 glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
979 for glosse in glosses:
982 i = find_token(document.body, glosse, i)
985 j = find_end_of_inset(document.body, i)
987 document.warning("Malformed LyX document: Can't find end of Glosse inset")
991 arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
992 endarg = find_end_of_inset(document.body, arg)
995 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
996 if argbeginPlain == -1:
997 document.warning("Malformed LyX document: Can't find arg plain Layout")
1000 argendPlain = find_end_of_inset(document.body, argbeginPlain)
1001 argcontent = lyx2verbatim(document, document.body[argbeginPlain : argendPlain - 2])
1003 document.body[j:j] = ["", "\\begin_layout Plain Layout","\\backslash", "glt ",
1004 argcontent, "\\end_layout"]
1006 # remove Arg insets and paragraph, if it only contains this inset
1007 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
1008 del document.body[arg - 1 : endarg + 4]
1010 del document.body[arg : endarg + 1]
1012 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1013 endPlain = find_end_of_layout(document.body, beginPlain)
1014 content = lyx2verbatim(document, document.body[beginPlain : endPlain])
1016 document.body[beginPlain + 1:endPlain] = [content]
1019 # Dissolve ERT insets
1020 for glosse in glosses:
1023 i = find_token(document.body, glosse, i)
1026 j = find_end_of_inset(document.body, i)
1028 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1032 ert = find_token(document.body, "\\begin_inset ERT", i, j)
1035 ertend = find_end_of_inset(document.body, ert)
1037 document.warning("Malformed LyX document: Can't find end of ERT inset")
1040 ertcontent = get_ert(document.body, ert, True)
1041 document.body[ert : ertend + 1] = [ertcontent]
1045 def convert_newgloss(document):
1046 " Converts Glosse insets (Linguistics module) to the new format "
1048 if not "linguistics" in document.get_module_list():
1051 glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
1052 for glosse in glosses:
1055 i = find_token(document.body, glosse, i)
1058 j = find_end_of_inset(document.body, i)
1060 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1067 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", k, j)
1068 if beginPlain == -1:
1070 endPlain = find_end_of_layout(document.body, beginPlain)
1072 document.warning("Malformed LyX document: Can't find end of Glosse layout")
1076 glt = find_token(document.body, "\\backslash", beginPlain, endPlain)
1077 if glt != -1 and document.body[glt + 1].startswith("glt"):
1078 document.body[glt + 1] = document.body[glt + 1].lstrip("glt").lstrip()
1079 argcontent = document.body[glt + 1 : endPlain]
1080 document.body[beginPlain + 1 : endPlain] = ["\\begin_inset Argument 1", "status open", "",
1081 "\\begin_layout Plain Layout", "\\begin_inset ERT", "status open", "",
1082 "\\begin_layout Plain Layout", ""] + argcontent + ["\\end_layout", "", "\\end_inset", "",
1083 "\\end_layout", "", "\\end_inset"]
1085 content = document.body[beginPlain + 1 : endPlain]
1086 document.body[beginPlain + 1 : endPlain] = ["\\begin_inset ERT", "status open", "",
1087 "\\begin_layout Plain Layout"] + content + ["\\end_layout", "", "\\end_inset"]
1089 endPlain = find_end_of_layout(document.body, beginPlain)
1091 j = find_end_of_inset(document.body, i)
1096 def convert_BoxFeatures(document):
1097 " adds new box features "
1101 i = find_token(document.body, "height_special", i)
1104 document.body[i+1:i+1] = ['thickness "0.4pt"', 'separation "3pt"', 'shadowsize "4pt"']
1108 def revert_BoxFeatures(document):
1109 " outputs new box features as TeX code "
1113 defaultThick = "0.4pt"
1114 defaultShadow = "4pt"
1116 i = find_token(document.body, "thickness", i)
1119 binset = find_token(document.body, "\\begin_inset Box", i - 11)
1120 if binset == -1 or binset != i - 11:
1122 continue # then "thickness" is is just a word in the text
1123 einset = find_end_of_inset(document.body, binset)
1125 document.warning("Malformed LyX document: Can't find end of box inset!")
1128 # read out the values
1129 beg = document.body[i].find('"');
1130 end = document.body[i].rfind('"');
1131 thickness = document.body[i][beg+1:end];
1132 beg = document.body[i+1].find('"');
1133 end = document.body[i+1].rfind('"');
1134 separation = document.body[i+1][beg+1:end];
1135 beg = document.body[i+2].find('"');
1136 end = document.body[i+2].rfind('"');
1137 shadowsize = document.body[i+2][beg+1:end];
1139 # first output the closing brace
1140 if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1141 document.body[einset + 1 : einset + 1] = put_cmd_in_ert("}")
1142 # delete the specification
1143 del document.body[i:i+3]
1144 # we have now the problem that if there is already \(f)colorbox in ERT around the inset
1145 # the ERT from this routine must be around it
1146 regexp = re.compile(r'^.*colorbox{.*$')
1147 pos = find_re(document.body, regexp, binset - 4)
1148 if pos != -1 and pos == binset - 4:
1152 # now output the lengths
1153 if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1154 document.body[pos : pos] = put_cmd_in_ert("{")
1155 if thickness != defaultThick:
1156 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness]
1157 if separation != defaultSep and thickness == defaultThick:
1158 document.body[pos + 5 : pos +6] = ["{\\backslash fboxsep " + separation]
1159 if separation != defaultSep and thickness != defaultThick:
1160 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation]
1161 if shadowsize != defaultShadow and separation == defaultSep and thickness == defaultThick:
1162 document.body[pos + 5 : pos +6] = ["{\\backslash shadowsize " + shadowsize]
1163 if shadowsize != defaultShadow and separation != defaultSep and thickness == defaultThick:
1164 document.body[pos + 5 : pos +6] = ["{\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1165 if shadowsize != defaultShadow and separation == defaultSep and thickness != defaultThick:
1166 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash shadowsize " + shadowsize]
1167 if shadowsize != defaultShadow and separation != defaultSep and thickness != defaultThick:
1168 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1171 def convert_origin(document):
1172 " Insert the origin tag "
1174 i = find_token(document.header, "\\textclass ", 0)
1176 document.warning("Malformed LyX document: No \\textclass!!")
1178 if document.dir == u'':
1182 if document.systemlyxdir and document.systemlyxdir != u'':
1184 if os.path.isabs(document.dir):
1185 absdir = os.path.normpath(document.dir)
1187 absdir = os.path.normpath(os.path.abspath(document.dir))
1188 if os.path.isabs(document.systemlyxdir):
1189 abssys = os.path.normpath(document.systemlyxdir)
1191 abssys = os.path.normpath(os.path.abspath(document.systemlyxdir))
1192 relpath = os.path.relpath(absdir, abssys)
1193 if relpath.find(u'..') == 0:
1198 origin = document.dir.replace(u'\\', u'/') + u'/'
1200 origin = os.path.join(u"/systemlyxdir", relpath).replace(u'\\', u'/') + u'/'
1201 document.header[i:i] = ["\\origin " + origin]
1204 def revert_origin(document):
1205 " Remove the origin tag "
1207 i = find_token(document.header, "\\origin ", 0)
1209 document.warning("Malformed LyX document: No \\origin!!")
1211 del document.header[i]
1214 color_names = ["brown", "darkgray", "gray", \
1215 "lightgray", "lime", "olive", "orange", \
1216 "pink", "purple", "teal", "violet"]
1218 def revert_textcolor(document):
1219 " revert new \\textcolor colors to TeX code "
1225 i = find_token(document.body, "\\color ", i)
1229 for color in list(color_names):
1230 if document.body[i] == "\\color " + color:
1231 # register that xcolor must be loaded in the preamble
1234 add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"])
1235 # find the next \\color and/or the next \\end_layout
1236 j = find_token(document.body, "\\color", i + 1)
1237 k = find_token(document.body, "\\end_layout", i + 1)
1238 if j == -1 and k != -1:
1241 # first output the closing brace
1243 document.body[k: k] = put_cmd_in_ert("}")
1245 document.body[j: j] = put_cmd_in_ert("}")
1246 # now output the \textcolor command
1247 document.body[i : i + 1] = put_cmd_in_ert("\\textcolor{" + color + "}{")
1251 def convert_colorbox(document):
1252 "Add color settings for boxes."
1255 i = find_token(document.body, "shadowsize", i)
1258 # check whether this is really a LyX Box setting
1259 start, end = is_in_inset(document.body, i, "\\begin_inset Box")
1261 find_token(document.body, "\\begin_layout", start, i) != -1):
1264 document.body[i+1:i+1] = ['framecolor "black"',
1265 'backgroundcolor "none"']
1269 def revert_colorbox(document):
1270 """Change box color settings to LaTeX code."""
1274 i = find_token(document.body, "\\begin_inset Box", i)
1278 j = find_end_of_inset(document.body, i)
1279 k = find_token(document.body, "\\begin_layout", i, j)
1281 document.warning("Malformed LyX document: no layout in Box inset!")
1284 # Get and delete colour settings:
1285 framecolor = get_quoted_value(document.body, "framecolor", i, k, delete=True)
1286 backcolor = get_quoted_value(document.body, "backgroundcolor", i, k + 1, delete=True)
1287 if not framecolor or not backcolor:
1288 document.warning("Malformed LyX document: color options not found in Box inset!")
1291 if framecolor == "black" and backcolor == "none": # default values
1295 # Emulate non-default colours with LaTeX code:
1296 einset = find_end_of_inset(document.body, i)
1298 document.warning("Malformed LyX document: Can't find end of box inset!")
1301 add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"])
1302 # insert the closing brace first (keeps indices 'i' and 'einset' valid)
1303 document.body[einset+1:einset+1] = put_cmd_in_ert("}")
1304 # now insert the (f)color box command
1305 if ("Box Boxed" in document.body[i]): # framed box, use \fcolorbox
1306 # change the box type (frame added by \fcolorbox)
1307 document.body[i] = "\\begin_inset Box Frameless"
1308 # ensure an inner box:
1310 if not set_bool_value(document.body, "has_inner_box", True, i+3, i+4):
1311 set_bool_value(document.body, "use_makebox", True, i+6, i+7)
1313 document.warning("Malformed LyX document: 'has_inner_box' or "
1314 "'use_makebox' option not found in box inset!")
1315 ertinset = put_cmd_in_ert("\\fcolorbox{%s}{%s}{"% (framecolor, backcolor))
1317 ertinset = put_cmd_in_ert("\\colorbox{%s}{" % backcolor)
1318 document.body[i:i] = ertinset + [""]
1322 def revert_mathmulticol(document):
1323 " Convert formulas to ERT if they contain multicolumns "
1327 i = find_token(document.body, '\\begin_inset Formula', i)
1330 j = find_end_of_inset(document.body, i)
1332 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
1335 lines = document.body[i:j]
1336 lines[0] = lines[0].replace('\\begin_inset Formula', '').lstrip()
1337 code = "\n".join(lines)
1342 n = code.find("\\multicolumn", k)
1343 # no need to convert degenerated multicolumn cells,
1344 # they work in old LyX versions as "math ERT"
1345 if n != -1 and code.find("\\multicolumn{1}", k) != n:
1346 ert = put_cmd_in_ert(code)
1347 document.body[i:j+1] = ert
1353 i = find_end_of_inset(document.body, i)
1358 def revert_jss(document):
1359 " Reverts JSS In_Preamble commands to ERT in preamble "
1361 if document.textclass != "jss":
1364 # at first revert the inset layouts because
1365 # they can be part of the In_Preamble layouts
1368 "Proglang" : "proglang",
1373 for iltype in il_dict.keys():
1376 i = find_token(document.body, "\\begin_inset Flex " + iltype, i)
1380 if document.body[i] != "\\begin_inset Flex " + iltype:
1381 # Not an exact match!
1385 end = find_end_of_inset(document.body, i)
1387 document.warning("Malformed LyX document: No end of Flex " + iltype + " found!")
1390 document.body[end - 2 : end + 1] = put_cmd_in_ert("}")
1391 document.body[i : i + 4] = put_cmd_in_ert("\\%s{" % il_dict[iltype])
1394 # now revert the In_Preamble layouts
1397 "Author" : "author",
1398 "Plain Author" : "Plainauthor",
1399 "Plain Title" : "Plaintitle",
1400 "Short Title" : "Shorttitle",
1401 "Abstract" : "Abstract",
1402 "Keywords" : "Keywords",
1403 "Plain Keywords" : "Plainkeywords",
1404 "Address" : "Address",
1406 for ipltype in ipl_dict.keys():
1409 i = find_token(document.body, "\\begin_layout " + ipltype, i)
1413 end = find_end_of_layout(document.body, i)
1415 document.warning("Malformed LyX document: Can't find end of " + ipltype + " layout")
1419 content = lyx2latex(document, document.body[i:end + 1])
1420 add_to_preamble(document, ["\\" + ipl_dict[ipltype] + "{" + content + "}"])
1421 del document.body[i:end + 1]
1428 i = find_token(document.body, "\\begin_inset Flex Code Chunk", i)
1432 end = find_end_of_inset(document.body, i)
1434 document.warning("Malformed LyX document: No end of Flex Code Chunk found!")
1438 document.body[end : end + 1] = ["\\end_layout", "", "\\begin_layout Standard"] + put_cmd_in_ert("\\end{CodeChunk}")
1439 document.body[i : i + 2] = put_cmd_in_ert("\\begin{CodeChunk}")
1442 # finally handle the code layouts
1444 "Code Input" : "CodeInput",
1445 "Code Output" : "CodeOutput",
1448 for ctype in codes_dict.keys():
1451 i = find_token(document.body, "\\begin_layout " + ctype, i)
1454 if document.body[i] != "\\begin_layout " + ctype:
1455 # Not an exact match!
1458 end = find_end_of_layout(document.body, i)
1460 document.warning("Malformed LyX document: No end of " + ctype + " layout found!")
1464 # Handle subsequent layouts
1466 j = find_token(document.body, "\\begin_layout ", seq_end)
1467 if j == -1 or document.body[j] != "\\begin_layout " + ctype:
1469 this_end = find_end_of_layout(document.body, j)
1471 document.warning("Malformed LyX document: No end of " + ctype + " layout found!")
1475 document.body[seq_end + 1 : seq_end + 1] = ["\\end_inset", "\\end_layout", "", "\\begin_layout Standard"] + put_cmd_in_ert("\\end{%s}" % codes_dict[ctype])
1479 document.body[k] = document.body[k].replace("\\begin_layout " + ctype, "\\begin_layout Plain Layout")
1481 document.body[i : i + 1] = ["\\end_layout", "", "\\begin_layout Standard"] \
1482 + put_cmd_in_ert("\\begin{%s}" % codes_dict[ctype]) \
1483 + ["\\end_layout", "", "\\begin_layout Standard", "", "\\begin_inset ERT", "status open", "", "\\begin_layout Plain Layout"]
1487 def convert_subref(document):
1488 " converts sub: ref prefixes to subref: "
1491 rx = re.compile(r'^name \"sub:(.+)$')
1494 i = find_token(document.body, "\\begin_inset CommandInset label", i)
1497 j = find_end_of_inset(document.body, i)
1499 document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1503 for p in range(i, j):
1504 m = rx.match(document.body[p])
1507 document.body[p] = "name \"subsec:" + label
1511 rx = re.compile(r'^reference \"sub:(.+)$')
1514 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1517 j = find_end_of_inset(document.body, i)
1519 document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1523 for p in range(i, j):
1524 m = rx.match(document.body[p])
1527 document.body[p] = "reference \"subsec:" + label
1533 def revert_subref(document):
1534 " reverts subref: ref prefixes to sub: "
1537 rx = re.compile(r'^name \"subsec:(.+)$')
1540 i = find_token(document.body, "\\begin_inset CommandInset label", i)
1543 j = find_end_of_inset(document.body, i)
1545 document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1549 for p in range(i, j):
1550 m = rx.match(document.body[p])
1553 document.body[p] = "name \"sub:" + label
1558 rx = re.compile(r'^reference \"subsec:(.+)$')
1561 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1564 j = find_end_of_inset(document.body, i)
1566 document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1570 for p in range(i, j):
1571 m = rx.match(document.body[p])
1574 document.body[p] = "reference \"sub:" + label
1579 def convert_nounzip(document):
1580 " remove the noUnzip parameter of graphics insets "
1582 rx = re.compile(r'\s*noUnzip\s*$')
1585 i = find_token(document.body, "\\begin_inset Graphics", i)
1588 j = find_end_of_inset(document.body, i)
1590 document.warning("Malformed LyX document: Can't find end of graphics inset at line " + str(i))
1594 k = find_re(document.body, rx, i, j)
1596 del document.body[k]
1601 def convert_revert_external_bbox(document, forward):
1602 " add units to bounding box of external insets "
1604 rx = re.compile(r'^\s*boundingBox\s+\S+\s+\S+\s+\S+\s+\S+\s*$')
1607 i = find_token(document.body, "\\begin_inset External", i)
1610 j = find_end_of_inset(document.body, i)
1612 document.warning("Malformed LyX document: Can't find end of external inset at line " + str(i))
1615 k = find_re(document.body, rx, i, j)
1619 tokens = document.body[k].split()
1621 for t in range(1, 5):
1624 for t in range(1, 5):
1625 tokens[t] = length_in_bp(tokens[t])
1626 document.body[k] = "\tboundingBox " + tokens[1] + " " + tokens[2] + " " + \
1627 tokens[3] + " " + tokens[4]
1631 def convert_external_bbox(document):
1632 convert_revert_external_bbox(document, True)
1635 def revert_external_bbox(document):
1636 convert_revert_external_bbox(document, False)
1639 def revert_tcolorbox_1(document):
1640 " Reverts the Flex:Subtitle inset of tcolorbox to TeX-code "
1642 i = find_token(document.header, "tcolorbox", 0)
1649 flex = find_token(document.body, "\\begin_inset Flex Subtitle", flex)
1653 flexEnd = find_end_of_inset(document.body, flex)
1655 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1659 wasOpt = revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, False, True, False)
1660 flexEnd = find_end_of_inset(document.body, flex)
1662 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1665 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, False, False, False)
1666 flexEnd = find_end_of_inset(document.body, flex)
1668 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1672 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1674 document.warning("Malformed LyX document! No Flex Subtitle layout found.")
1678 ep = find_end_of_layout(document.body, bp)
1680 document.warning("Malformed LyX document! No end of layout found.")
1684 document.body[ep : flexEnd + 1] = put_cmd_in_ert("}")
1686 document.body[flex : bp + 1] = put_cmd_in_ert("\\tcbsubtitle")
1688 document.body[flex : bp + 1] = put_cmd_in_ert("\\tcbsubtitle{")
1692 def revert_tcolorbox_2(document):
1693 " Reverts the Flex:Raster_Color_Box inset of tcolorbox to TeX-code "
1695 i = find_token(document.header, "tcolorbox", 0)
1701 flex = find_token(document.body, "\\begin_inset Flex Raster Color Box", flex)
1705 flexEnd = find_end_of_inset(document.body, flex)
1707 document.warning("Malformed LyX document! No end of Flex Raster Color Box found.")
1711 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1713 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1715 document.warning("Malformed LyX document! No plain layout in Raster Color Box found.")
1719 ep = find_end_of_layout(document.body, bp)
1721 document.warning("Malformed LyX document! No end of layout found.")
1725 flexEnd = find_end_of_inset(document.body, flex)
1727 document.warning("Malformed LyX document! No end of Flex Raster Color Box found.")
1730 document.body[ep : flexEnd + 1] = put_cmd_in_ert("\\end{tcbraster}")
1731 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{tcbraster}")
1735 def revert_tcolorbox_3(document):
1736 " Reverts the Flex:Custom_Color_Box_1 inset of tcolorbox to TeX-code "
1738 i = find_token(document.header, "tcolorbox", 0)
1744 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 1", flex)
1748 flexEnd = find_end_of_inset(document.body, flex)
1750 document.warning("Malformed LyX document! No end of Flex Custom Color Box 1 found.")
1754 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1755 flexEnd = find_end_of_inset(document.body, flex)
1757 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1760 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
1762 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1764 document.warning("Malformed LyX document! No plain layout in Custom Color Box 1 found.")
1768 ep = find_end_of_layout(document.body, bp)
1770 document.warning("Malformed LyX document! No end of layout found.")
1774 flexEnd = find_end_of_inset(document.body, flex)
1776 document.warning("Malformed LyX document! No end of Flex Custom Color Box 1 found.")
1780 document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxA}")
1781 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxA}")
1785 def revert_tcolorbox_4(document):
1786 " Reverts the Flex:Custom_Color_Box_2 inset of tcolorbox to TeX-code "
1788 i = find_token(document.header, "tcolorbox", 0)
1794 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 2", flex)
1798 flexEnd = find_end_of_inset(document.body, flex)
1800 document.warning("Malformed LyX document! No end of Flex Custom Color Box 2 found.")
1804 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1805 flexEnd = find_end_of_inset(document.body, flex)
1807 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1810 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
1811 flexEnd = find_end_of_inset(document.body, flex)
1813 document.warning("Malformed LyX document! No end of Flex Custom Color Box 2 found.")
1817 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1819 document.warning("Malformed LyX document! No plain layout in Custom Color Box 2 found.")
1823 ep = find_end_of_layout(document.body, bp)
1825 document.warning("Malformed LyX document! No end of layout found.")
1829 document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxB}")
1830 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxB}")
1834 def revert_tcolorbox_5(document):
1835 " Reverts the Flex:Custom_Color_Box_3 inset of tcolorbox to TeX-code "
1837 i = find_token(document.header, "tcolorbox", 0)
1843 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 3", flex)
1847 flexEnd = find_end_of_inset(document.body, flex)
1849 document.warning("Malformed LyX document! No end of Flex Custom Color Box 3 found.")
1853 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1854 flexEnd = find_end_of_inset(document.body, flex)
1856 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1859 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
1860 flexEnd = find_end_of_inset(document.body, flex)
1862 document.warning("Malformed LyX document! No end of Flex Custom Color Box 3 found.")
1866 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1868 document.warning("Malformed LyX document! No plain layout in Custom Color Box 3 found.")
1872 ep = find_end_of_layout(document.body, bp)
1874 document.warning("Malformed LyX document! No end of layout found.")
1878 document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxC}")
1879 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxC}")
1883 def revert_tcolorbox_6(document):
1884 " Reverts the Flex:Custom_Color_Box_4 inset of tcolorbox to TeX-code "
1886 i = find_token(document.header, "tcolorbox", 0)
1892 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 4", flex)
1896 flexEnd = find_end_of_inset(document.body, flex)
1898 document.warning("Malformed LyX document! No end of Flex Custom Color Box 4 found.")
1902 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1903 flexEnd = find_end_of_inset(document.body, flex)
1905 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1908 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
1909 flexEnd = find_end_of_inset(document.body, flex)
1911 document.warning("Malformed LyX document! No end of Flex Custom Color Box 4 found.")
1915 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1917 document.warning("Malformed LyX document! No plain layout in Custom Color Box 4 found.")
1921 ep = find_end_of_layout(document.body, bp)
1923 document.warning("Malformed LyX document! No end of layout found.")
1927 document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxD}")
1928 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxD}")
1932 def revert_tcolorbox_7(document):
1933 " Reverts the Flex:Custom_Color_Box_5 inset of tcolorbox to TeX-code "
1935 i = find_token(document.header, "tcolorbox", 0)
1941 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 5", flex)
1945 flexEnd = find_end_of_inset(document.body, flex)
1947 document.warning("Malformed LyX document! No end of Flex Custom Color Box 5 found.")
1951 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1952 flexEnd = find_end_of_inset(document.body, flex)
1954 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1957 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
1958 flexEnd = find_end_of_inset(document.body, flex)
1960 document.warning("Malformed LyX document! No end of Flex Custom Color Box 5 found.")
1964 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1966 document.warning("Malformed LyX document! No plain layout in Custom Color Box 5 found.")
1970 ep = find_end_of_layout(document.body, bp)
1972 document.warning("Malformed LyX document! No end of layout found.")
1976 document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxE}")
1977 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxE}")
1981 def revert_tcolorbox_8(document):
1982 " Reverts the layout New Color Box Type of tcolorbox to TeX-code "
1986 i = find_token(document.body, "\\begin_layout New Color Box Type", i)
1990 j = find_end_of_layout(document.body, i)
1992 document.warning("Malformed LyX document! No end of New Color Box Type layout found.")
1996 wasOpt = revert_Argument_to_TeX_brace(document, i, j, 1, 1, False, True, True)
1997 j = find_end_of_layout(document.body, i)
1999 document.warning("Malformed LyX document! No end of New Color Box Type layout found.")
2002 revert_Argument_to_TeX_brace(document, i, j, 2, 2, False, False, True)
2003 j = find_end_of_layout(document.body, i)
2005 document.warning("Malformed LyX document! No end of New Color Box Type layout found.")
2008 revert_Argument_to_TeX_brace(document, i, j, 3, 4, False, True, False)
2009 j = find_end_of_layout(document.body, i)
2011 document.warning("Malformed LyX document! No end of New Color Box Type layout found.")
2014 document.body[i] = document.body[i].replace("\\begin_layout New Color Box Type", "\\begin_layout Standard")
2015 document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox")
2016 k = find_end_of_inset(document.body, j)
2017 document.body[k + 2 : j + 2] = put_cmd_in_ert("{") + ["\\begin_inset ERT", "status collapsed", "\\begin_layout Plain Layout"]
2018 j = find_token(document.body, "\\begin_layout Standard", j + 1)
2019 document.body[j - 2 : j - 2] = ["\\end_layout", "\\end_inset"] + put_cmd_in_ert("}")
2023 def revert_moderncv_1(document):
2024 " Reverts the new inset of moderncv to TeX-code in preamble "
2026 if document.textclass != "moderncv":
2032 # at first revert the new styles
2034 i = find_token(document.body, "\\begin_layout CVIcons", 0)
2037 j = find_end_of_layout(document.body, i)
2039 document.warning("Malformed LyX document: Can't find end of CVIcons layout")
2042 content = lyx2latex(document, document.body[i:j + 1])
2043 add_to_preamble(document, ["\\moderncvicons{" + content + "}"])
2044 del document.body[i:j + 1]
2046 i = find_token(document.body, "\\begin_layout CVColumnWidth", 0)
2049 j = find_end_of_layout(document.body, i)
2051 document.warning("Malformed LyX document: Can't find end of CVColumnWidth layout")
2054 content = lyx2latex(document, document.body[i:j + 1])
2055 add_to_preamble(document, ["\\setlength{\hintscolumnwidth}{" + content + "}"])
2056 del document.body[i:j + 1]
2057 # now change the new styles to the obsolete ones
2059 i = find_token(document.body, "\\begin_layout Name", 0)
2062 j = find_end_of_layout(document.body, i)
2064 document.warning("Malformed LyX document: Can't find end of Name layout")
2067 lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
2068 if lineArg > j and j != 0:
2071 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
2072 # we have to assure that no other inset is in the Argument
2073 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
2074 endInset = find_token(document.body, "\\end_inset", beginPlain)
2077 while beginInset < endInset and beginInset != -1:
2078 beginInset = find_token(document.body, "\\begin_inset", k)
2079 endInset = find_token(document.body, "\\end_inset", l)
2082 Arg2 = document.body[l + 5 : l + 6]
2084 document.body[i : i + 1]= ["\\begin_layout FirstName"]
2085 # delete the Argument inset
2086 del( document.body[endInset - 2 : endInset + 3])
2087 del( document.body[lineArg : beginPlain + 1])
2088 document.body[i + 4 : i + 4]= ["\\begin_layout FamilyName"] + Arg2 + ["\\end_layout"] + [""]
2091 def revert_moderncv_2(document):
2092 " Reverts the phone inset of moderncv to the obsoleted mobile or fax "
2094 if document.textclass != "moderncv":
2101 i = find_token(document.body, "\\begin_layout Phone", i)
2104 j = find_end_of_layout(document.body, i)
2106 document.warning("Malformed LyX document: Can't find end of Phone layout")
2109 lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
2110 if lineArg > j and j != 0:
2114 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
2115 # we have to assure that no other inset is in the Argument
2116 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
2117 endInset = find_token(document.body, "\\end_inset", beginPlain)
2120 while beginInset < endInset and beginInset != -1:
2121 beginInset = find_token(document.body, "\\begin_inset", k)
2122 endInset = find_token(document.body, "\\end_inset", l)
2125 Arg = document.body[beginPlain + 1 : beginPlain + 2]
2127 if Arg[0] == "mobile":
2128 document.body[i : i + 1]= ["\\begin_layout Mobile"]
2130 document.body[i : i + 1]= ["\\begin_layout Fax"]
2131 # delete the Argument inset
2132 del(document.body[endInset - 2 : endInset + 1])
2133 del(document.body[lineArg : beginPlain + 3])
2137 def convert_moderncv_phone(document):
2138 " Convert the Fax and Mobile inset of moderncv to the new phone inset "
2140 if document.textclass != "moderncv":
2147 "Mobile" : "mobile",
2151 rx = re.compile(r'^\\begin_layout (\S+)$')
2153 # substitute \fax and \mobile by \phone[fax] and \phone[mobile], respectively
2154 i = find_token(document.body, "\\begin_layout", i)
2158 m = rx.match(document.body[i])
2162 if val not in list(phone_dict.keys()):
2165 j = find_end_of_layout(document.body, i)
2167 document.warning("Malformed LyX document: Can't find end of Mobile layout")
2171 document.body[i : i + 1] = ["\\begin_layout Phone", "\\begin_inset Argument 1", "status open", "",
2172 "\\begin_layout Plain Layout", phone_dict[val], "\\end_layout", "",
2176 def convert_moderncv_name(document):
2177 " Convert the FirstName and LastName layout of moderncv to the general Name layout "
2179 if document.textclass != "moderncv":
2182 fnb = 0 # Begin of FirstName inset
2183 fne = 0 # End of FirstName inset
2184 lnb = 0 # Begin of LastName (FamilyName) inset
2185 lne = 0 # End of LastName (FamilyName) inset
2186 nb = 0 # Begin of substituting Name inset
2187 ne = 0 # End of substituting Name inset
2188 FirstName = [] # FirstName content
2189 FamilyName = [] # LastName content
2193 fnb = find_token(document.body, "\\begin_layout FirstName", fnb)
2195 fne = find_end_of_layout(document.body, fnb)
2197 document.warning("Malformed LyX document: Can't find end of FirstName layout")
2199 FirstName = document.body[fnb + 1 : fne]
2201 lnb = find_token(document.body, "\\begin_layout FamilyName", lnb)
2203 lne = find_end_of_layout(document.body, lnb)
2205 document.warning("Malformed LyX document: Can't find end of FamilyName layout")
2207 FamilyName = document.body[lnb + 1 : lne]
2208 # Determine the region for the substituting Name layout
2209 if fnb == -1 and lnb == -1: # Neither FirstName nor FamilyName exists -> Do nothing
2211 elif fnb == -1: # Only FamilyName exists -> New Name insets replaces that
2214 elif lnb == -1: # Only FirstName exists -> New Name insets replaces that
2217 elif fne > lne: # FirstName position before FamilyName -> New Name insets spans
2218 nb = lnb # from FamilyName begin
2219 ne = fne # to FirstName end
2220 else: # FirstName position before FamilyName -> New Name insets spans
2221 nb = fnb # from FirstName begin
2222 ne = lne # to FamilyName end
2224 # Insert the substituting layout now. If FirstName exists, use an otpional argument.
2226 document.body[nb : ne + 1] = ["\\begin_layout Name"] + FamilyName + ["\\end_layout", ""]
2228 document.body[nb : ne + 1] = ["\\begin_layout Name", "\\begin_inset Argument 1", "status open", "",
2229 "\\begin_layout Plain Layout"] + FirstName + ["\\end_layout", "",
2230 "\\end_inset", ""] + FamilyName + ["\\end_layout", ""]
2233 def revert_achemso(document):
2234 " Reverts the flex inset Latin to TeX code "
2236 if document.textclass != "achemso":
2241 i = find_token(document.body, "\\begin_inset Flex Latin", i)
2243 j = find_end_of_inset(document.body, i)
2247 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2248 endPlain = find_end_of_layout(document.body, beginPlain)
2249 content = lyx2latex(document, document.body[beginPlain : endPlain])
2250 document.body[i:j + 1] = put_cmd_in_ert("\\latin{" + content + "}")
2252 document.warning("Malformed LyX document: Can't find end of flex inset Latin")
2257 fontsettings = ["\\font_roman", "\\font_sans", "\\font_typewriter", "\\font_math", \
2258 "\\font_sf_scale", "\\font_tt_scale"]
2259 fontdefaults = ["default", "default", "default", "auto", "100", "100"]
2260 fontquotes = [True, True, True, True, False, False]
2262 def convert_fontsettings(document):
2263 " Duplicate font settings "
2265 i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2267 document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2268 use_non_tex_fonts = "false"
2270 use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2272 for f in fontsettings:
2273 i = find_token(document.header, f + " ", 0)
2275 document.warning("Malformed LyX document: No " + f + "!")
2277 # note that with i = -1, this will insert at the end
2279 value = fontdefaults[j]
2281 value = document.header[i][len(f):].strip()
2283 if use_non_tex_fonts == "true":
2284 document.header[i:i+1] = [f + ' "' + fontdefaults[j] + '" "' + value + '"']
2286 document.header[i:i+1] = [f + ' "' + value + '" "' + fontdefaults[j] + '"']
2288 if use_non_tex_fonts == "true":
2289 document.header[i:i+1] = [f + ' ' + fontdefaults[j] + ' ' + value]
2291 document.header[i:i+1] = [f + ' ' + value + ' ' + fontdefaults[j]]
2295 def revert_fontsettings(document):
2296 " Merge font settings "
2298 i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2300 document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2301 use_non_tex_fonts = "false"
2303 use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2305 for f in fontsettings:
2306 i = find_token(document.header, f + " ", 0)
2308 document.warning("Malformed LyX document: No " + f + "!")
2311 line = get_value(document.header, f, i)
2314 q2 = line.find('"', q1+1)
2315 q3 = line.find('"', q2+1)
2316 q4 = line.find('"', q3+1)
2317 if q1 == -1 or q2 == -1 or q3 == -1 or q4 == -1:
2318 document.warning("Malformed LyX document: Missing quotes!")
2321 if use_non_tex_fonts == "true":
2322 document.header[i:i+1] = [f + ' ' + line[q3+1:q4]]
2324 document.header[i:i+1] = [f + ' ' + line[q1+1:q2]]
2326 if use_non_tex_fonts == "true":
2327 document.header[i:i+1] = [f + ' ' + line.split()[1]]
2329 document.header[i:i+1] = [f + ' ' + line.split()[0]]
2333 def revert_solution(document):
2334 " Reverts the solution environment of the theorem module to TeX code "
2336 # Do we use one of the modules that provides Solution?
2338 mods = document.get_module_list()
2340 if mod == "theorems-std" or mod == "theorems-bytype" \
2341 or mod == "theorems-ams" or mod == "theorems-ams-bytype":
2351 i = find_token(document.body, "\\begin_layout Solution", i)
2355 is_starred = document.body[i].startswith("\\begin_layout Solution*")
2356 if is_starred == True:
2358 LyXName = "Solution*"
2359 theoremName = "newtheorem*"
2362 LyXName = "Solution"
2363 theoremName = "newtheorem"
2365 j = find_end_of_layout(document.body, i)
2367 document.warning("Malformed LyX document: Can't find end of " + LyXName + " layout")
2371 # if this is not a consecutive env, add start command
2374 begcmd = put_cmd_in_ert("\\begin{%s}" % (LaTeXName))
2376 # has this a consecutive theorem of same type?
2377 consecutive = document.body[j + 2] == "\\begin_layout " + LyXName
2379 # if this is not followed by a consecutive env, add end command
2381 document.body[j : j + 1] = put_cmd_in_ert("\\end{%s}" % (LaTeXName)) + ["\\end_layout"]
2383 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
2385 add_to_preamble(document, "\\theoremstyle{definition}")
2386 if is_starred or mod == "theorems-bytype" or mod == "theorems-ams-bytype":
2387 add_to_preamble(document, "\\%s{%s}{\\protect\\solutionname}" % \
2388 (theoremName, LaTeXName))
2389 else: # mod == "theorems-std" or mod == "theorems-ams" and not is_starred
2390 add_to_preamble(document, "\\%s{%s}[thm]{\\protect\\solutionname}" % \
2391 (theoremName, LaTeXName))
2393 add_to_preamble(document, "\\providecommand{\solutionname}{Solution}")
2397 def revert_verbatim_star(document):
2398 from lyx_2_1 import revert_verbatim
2399 revert_verbatim(document, True)
2402 def convert_save_props(document):
2403 " Add save_transient_properties parameter. "
2404 i = find_token(document.header, '\\begin_header', 0)
2406 document.warning("Malformed lyx document: Missing '\\begin_header'.")
2408 document.header.insert(i + 1, '\\save_transient_properties true')
2411 def revert_save_props(document):
2412 " Remove save_transient_properties parameter. "
2413 i = find_token(document.header, "\\save_transient_properties", 0)
2416 del document.header[i]
2419 def convert_info_tabular_feature(document):
2421 return arg.replace("inset-modify tabular", "tabular-feature")
2422 convert_info_insets(document, "shortcut(s)?|icon", f)
2425 def revert_info_tabular_feature(document):
2427 return arg.replace("tabular-feature", "inset-modify tabular")
2428 convert_info_insets(document, "shortcut(s)?|icon", f)
2435 supported_versions = ["2.2.0", "2.2"]
2437 [475, [convert_separator]],
2438 # nothing to do for 476: We consider it a bug that older versions
2439 # did not load amsmath automatically for these commands, and do not
2440 # want to hardcode amsmath off.
2446 [481, [convert_dashes]],
2447 [482, [convert_phrases]],
2448 [483, [convert_specialchar]],
2453 [488, [convert_newgloss]],
2454 [489, [convert_BoxFeatures]],
2455 [490, [convert_origin]],
2457 [492, [convert_colorbox]],
2460 [495, [convert_subref]],
2461 [496, [convert_nounzip]],
2462 [497, [convert_external_bbox]],
2464 [499, [convert_moderncv_phone, convert_moderncv_name]],
2466 [501, [convert_fontsettings]],
2469 [504, [convert_save_props]],
2471 [506, [convert_info_tabular_feature]],
2472 [507, [convert_longtable_label]],
2473 [508, [convert_parbreak]]
2477 [507, [revert_parbreak]],
2478 [506, [revert_longtable_label]],
2479 [505, [revert_info_tabular_feature]],
2481 [503, [revert_save_props]],
2482 [502, [revert_verbatim_star]],
2483 [501, [revert_solution]],
2484 [500, [revert_fontsettings]],
2485 [499, [revert_achemso]],
2486 [498, [revert_moderncv_1, revert_moderncv_2]],
2487 [497, [revert_tcolorbox_1, revert_tcolorbox_2,
2488 revert_tcolorbox_3, revert_tcolorbox_4, revert_tcolorbox_5,
2489 revert_tcolorbox_6, revert_tcolorbox_7, revert_tcolorbox_8]],
2490 [496, [revert_external_bbox]],
2491 [495, []], # nothing to do since the noUnzip parameter was optional
2492 [494, [revert_subref]],
2493 [493, [revert_jss]],
2494 [492, [revert_mathmulticol]],
2495 [491, [revert_colorbox]],
2496 [490, [revert_textcolor]],
2497 [489, [revert_origin]],
2498 [488, [revert_BoxFeatures]],
2499 [487, [revert_newgloss, revert_glossgroup]],
2500 [486, [revert_forest]],
2501 [485, [revert_ex_itemargs]],
2502 [484, [revert_sigplan_doi]],
2503 [483, [revert_georgian]],
2504 [482, [revert_specialchar]],
2505 [481, [revert_phrases]],
2506 [480, [revert_dashes]],
2507 [479, [revert_question_env]],
2508 [478, [revert_beamer_lemma]],
2509 [477, [revert_xarrow]],
2510 [476, [revert_swissgerman]],
2511 [475, [revert_smash]],
2512 [474, [revert_separator]]
2516 if __name__ == "__main__":