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,
31 from parser_tools import (check_token, del_complete_lines,
32 find_end_of_inset, find_end_of_layout, find_nonempty_line, find_re,
33 find_substring, find_token, find_token_backwards, get_containing_layout,
34 get_containing_inset, get_quoted_value, get_value, is_in_inset,
35 get_bool_value, set_bool_value)
38 ####################################################################
39 # Private helper functions
41 def revert_Argument_to_TeX_brace(document, line, endline, n, nmax, environment, opt, nolastopt):
43 Reverts an InsetArgument to TeX-code
45 revert_Argument_to_TeX_brace(document, LineOfBegin, LineOfEnd, StartArgument, EndArgument, isEnvironment, isOpt, notLastOpt)
46 LineOfBegin is the line of the \begin_layout or \begin_inset statement
47 LineOfEnd is the line of the \end_layout or \end_inset statement, if "0" is given, the end of the file is used instead
48 StartArgument is the number of the first argument that needs to be converted
49 EndArgument is the number of the last argument that needs to be converted or the last defined one
50 isEnvironment must be true, if the layout is for a LaTeX environment
51 isOpt must be true, if the argument is an optional one
52 notLastOpt must be true if the argument is mandatory and followed by optional ones
56 while lineArg != -1 and n < nmax + 1:
57 lineArg = find_token(document.body, "\\begin_inset Argument " + str(n), line)
58 if lineArg > endline and endline != 0:
61 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
62 # we have to assure that no other inset is in the Argument
63 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
64 endInset = find_token(document.body, "\\end_inset", beginPlain)
67 while beginInset < endInset and beginInset != -1:
68 beginInset = find_token(document.body, "\\begin_inset", k)
69 endInset = find_token(document.body, "\\end_inset", l)
72 if environment == False:
74 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}")
75 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("{")
78 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("]")
79 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("[")
83 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}")
84 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("{")
87 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("]")
88 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("[")
94 ###############################################################################
96 ### Conversion and reversion routines
98 ###############################################################################
100 def convert_longtable_label_internal(document, forward):
102 Convert reference to "LongTableNoNumber" into "Unnumbered" if forward is True
105 old_reference = "\\begin_inset Caption LongTableNoNumber"
106 new_reference = "\\begin_inset Caption Unnumbered"
108 # if the purpose is to revert swap the strings roles
110 old_reference, new_reference = new_reference, old_reference
114 i = find_token(document.body, old_reference, i)
119 document.body[i] = new_reference
122 def convert_longtable_label(document):
123 convert_longtable_label_internal(document, True)
126 def revert_longtable_label(document):
127 convert_longtable_label_internal(document, False)
130 def convert_separator(document):
132 Convert layout separators to separator insets and add (LaTeX) paragraph
133 breaks in order to mimic previous LaTeX export.
136 parins = ["\\begin_inset Separator parbreak", "\\end_inset", ""]
137 parlay = ["\\begin_layout Standard", "\\begin_inset Separator parbreak",
138 "\\end_inset", "", "\\end_layout", ""]
140 "family" : "default",
141 "series" : "default",
150 i = find_token(document.body, "\\begin_deeper", i)
154 j = find_token_backwards(document.body, "\\end_layout", i-1)
156 # reset any text style before inserting the inset
157 lay = get_containing_layout(document.body, j-1)
159 content = "\n".join(document.body[lay[1]:lay[2]])
160 for val in list(sty_dict.keys()):
161 if content.find("\\%s" % val) != -1:
162 document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
165 document.body[j:j] = parins
166 i = i + len(parins) + 1
172 i = find_token(document.body, "\\align", i)
176 lay = get_containing_layout(document.body, i)
177 if lay != False and lay[0] == "Plain Layout":
181 j = find_token_backwards(document.body, "\\end_layout", i-1)
183 # Very old LyX files do not have Plain Layout in insets (but Standard).
184 # So we additionally check here if there is no inset boundary
185 # between the previous layout and this one.
186 n = find_token(document.body, "\\end_inset", j, lay[1])
190 lay = get_containing_layout(document.body, j-1)
191 if lay != False and lay[0] == "Standard" \
192 and find_token(document.body, "\\align", lay[1], lay[2]) == -1 \
193 and find_token(document.body, "\\begin_inset VSpace", lay[1], lay[2]) == -1:
194 # reset any text style before inserting the inset
195 content = "\n".join(document.body[lay[1]:lay[2]])
196 for val in list(sty_dict.keys()):
197 if content.find("\\%s" % val) != -1:
198 document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
201 document.body[j:j] = parins
202 i = i + len(parins) + 1
208 regexp = re.compile(r'^\\begin_layout (?:(-*)|(\s*))(Separator|EndOfSlide)(?:(-*)|(\s*))$', re.IGNORECASE)
212 i = find_re(document.body, regexp, i)
216 j = find_end_of_layout(document.body, i)
218 document.warning("Malformed LyX document: Missing `\\end_layout'.")
221 lay = get_containing_layout(document.body, j-1)
223 lines = document.body[lay[3]:lay[2]]
227 document.body[i:j+1] = parlay
229 document.body[i+1:i+1] = lines
231 i = i + len(parlay) + len(lines) + 1
234 def revert_separator(document):
235 " Revert separator insets to layout separators "
237 beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
238 if document.textclass in beamer_classes:
239 beglaysep = "\\begin_layout Separator"
241 beglaysep = "\\begin_layout --Separator--"
243 parsep = [beglaysep, "", "\\end_layout", ""]
244 comert = ["\\begin_inset ERT", "status collapsed", "",
245 "\\begin_layout Plain Layout", "%", "\\end_layout",
246 "", "\\end_inset", ""]
247 empert = ["\\begin_inset ERT", "status collapsed", "",
248 "\\begin_layout Plain Layout", " ", "\\end_layout",
249 "", "\\end_inset", ""]
253 i = find_token(document.body, "\\begin_inset Separator", i)
257 lay = get_containing_layout(document.body, i)
259 document.warning("Malformed LyX document: Can't convert separator inset at line " + str(i))
266 kind = get_value(document.body, "\\begin_inset Separator", i, i+1, "plain").split()[1]
267 before = document.body[beg+1:i]
268 something_before = len(before) > 0 and len("".join(before)) > 0
269 j = find_end_of_inset(document.body, i)
270 after = document.body[j+1:end]
271 something_after = len(after) > 0 and len("".join(after)) > 0
273 beg = beg + len(before) + 1
274 elif something_before:
275 document.body[i:i] = ["\\end_layout", ""]
283 document.body[beg:j+1] = empert
286 document.body[beg:j+1] = comert
290 if layoutname == "Standard":
291 if not something_before:
292 document.body[beg:j+1] = parsep
294 document.body[i:i] = ["", "\\begin_layout Standard"]
297 document.body[beg:j+1] = ["\\begin_layout Standard"]
300 document.body[beg:j+1] = ["\\begin_deeper"]
302 end = end + 1 - (j + 1 - beg)
303 if not something_before:
304 document.body[i:i] = parsep
306 end = end + len(parsep)
307 document.body[i:i] = ["\\begin_layout Standard"]
308 document.body[end+2:end+2] = ["", "\\end_deeper", ""]
311 next_par_is_aligned = False
312 k = find_nonempty_line(document.body, end+1)
313 if k != -1 and check_token(document.body[k], "\\begin_layout"):
314 lay = get_containing_layout(document.body, k)
315 next_par_is_aligned = lay != False and \
316 find_token(document.body, "\\align", lay[1], lay[2]) != -1
317 if k != -1 and not next_par_is_aligned \
318 and not check_token(document.body[k], "\\end_deeper") \
319 and not check_token(document.body[k], "\\begin_deeper"):
320 if layoutname == "Standard":
321 document.body[beg:j+1] = [beglaysep]
324 document.body[beg:j+1] = ["\\begin_deeper", beglaysep]
325 end = end + 2 - (j + 1 - beg)
326 document.body[end+1:end+1] = ["", "\\end_deeper", ""]
330 del document.body[i:end+1]
332 del document.body[i:end-1]
337 def convert_parbreak(document):
339 Convert parbreak separators not specifically used to separate
340 environments to latexpar separators.
342 parbreakinset = "\\begin_inset Separator parbreak"
345 i = find_token(document.body, parbreakinset, i)
348 lay = get_containing_layout(document.body, i)
350 document.warning("Malformed LyX document: "
351 "Can't convert separator inset at line %d"%i)
354 if lay[0] == "Standard":
355 # Convert only if not alone in the paragraph
356 k1 = find_nonempty_line(document.body, lay[1] + 1, i + 1)
357 k2 = find_nonempty_line(document.body, i + 1, lay[2])
358 if (k1 < i) or (k2 > i + 1) or not check_token(document.body[i], parbreakinset):
359 document.body[i] = document.body[i].replace("parbreak", "latexpar")
361 document.body[i] = document.body[i].replace("parbreak", "latexpar")
365 def revert_parbreak(document):
367 Revert latexpar separators to parbreak separators.
371 i = find_token(document.body, "\\begin_inset Separator latexpar", i)
374 document.body[i] = document.body[i].replace("latexpar", "parbreak")
378 def revert_smash(document):
379 " Set amsmath to on if smash commands are used "
381 commands = ["smash[t]", "smash[b]", "notag"]
382 i = find_token(document.header, "\\use_package amsmath", 0)
384 document.warning("Malformed LyX document: Can't find \\use_package amsmath.")
386 value = get_value(document.header, "\\use_package amsmath", i).split()[1]
388 # nothing to do if package is not auto but on or off
392 j = find_token(document.body, '\\begin_inset Formula', j)
395 k = find_end_of_inset(document.body, j)
397 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(j))
400 code = "\n".join(document.body[j:k])
402 if code.find("\\%s" % c) != -1:
403 # set amsmath to on, since it is loaded by the newer format
404 document.header[i] = "\\use_package amsmath 2"
409 def revert_swissgerman(document):
410 " Set language german-ch-old to german "
412 if document.language == "german-ch-old":
413 document.language = "german"
414 i = find_token(document.header, "\\language", 0)
416 document.header[i] = "\\language german"
419 j = find_token(document.body, "\\lang german-ch-old", j)
422 document.body[j] = document.body[j].replace("\\lang german-ch-old", "\\lang german")
426 def revert_use_package(document, pkg, commands, oldauto, supported):
427 # oldauto defines how the version we are reverting to behaves:
428 # if it is true, the old version uses the package automatically.
429 # if it is false, the old version never uses the package.
430 # If "supported" is true, the target version also supports this
432 regexp = re.compile(r'(\\use_package\s+%s)' % pkg)
433 p = find_re(document.header, regexp, 0)
434 value = "1" # default is auto
436 value = get_value(document.header, "\\use_package" , p).split()[1]
438 del document.header[p]
439 if value == "2" and not supported: # on
440 add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
441 elif value == "1" and not oldauto: # auto
444 i = find_token(document.body, '\\begin_inset Formula', i)
447 j = find_end_of_inset(document.body, i)
449 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
452 code = "\n".join(document.body[i:j])
454 if code.find("\\%s" % c) != -1:
456 document.header[p] = "\\use_package " + pkg + " 2"
458 add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
463 mathtools_commands = ["xhookrightarrow", "xhookleftarrow", "xRightarrow", \
464 "xrightharpoondown", "xrightharpoonup", "xrightleftharpoons", \
465 "xLeftarrow", "xleftharpoondown", "xleftharpoonup", \
466 "xleftrightarrow", "xLeftrightarrow", "xleftrightharpoons", \
469 def revert_xarrow(document):
470 "remove use_package mathtools"
471 revert_use_package(document, "mathtools", mathtools_commands, False, True)
474 def revert_beamer_lemma(document):
475 " Reverts beamer lemma layout to ERT "
477 beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
478 if document.textclass not in beamer_classes:
484 i = find_token(document.body, "\\begin_layout Lemma", i)
487 j = find_end_of_layout(document.body, i)
489 document.warning("Malformed LyX document: Can't find end of Lemma layout")
492 arg1 = find_token(document.body, "\\begin_inset Argument 1", i, j)
493 endarg1 = find_end_of_inset(document.body, arg1)
494 arg2 = find_token(document.body, "\\begin_inset Argument 2", i, j)
495 endarg2 = find_end_of_inset(document.body, arg2)
499 beginPlain1 = find_token(document.body, "\\begin_layout Plain Layout", arg1, endarg1)
500 if beginPlain1 == -1:
501 document.warning("Malformed LyX document: Can't find arg1 plain Layout")
504 endPlain1 = find_end_of_inset(document.body, beginPlain1)
505 content1 = document.body[beginPlain1 + 1 : endPlain1 - 2]
506 subst1 = put_cmd_in_ert("<") + content1 + put_cmd_in_ert(">")
508 beginPlain2 = find_token(document.body, "\\begin_layout Plain Layout", arg2, endarg2)
509 if beginPlain2 == -1:
510 document.warning("Malformed LyX document: Can't find arg2 plain Layout")
513 endPlain2 = find_end_of_inset(document.body, beginPlain2)
514 content2 = document.body[beginPlain2 + 1 : endPlain2 - 2]
515 subst2 = put_cmd_in_ert("[") + content2 + put_cmd_in_ert("]")
519 del document.body[arg2 : endarg2 + 1]
521 del document.body[arg1 : endarg1 + 1]
523 del document.body[arg1 : endarg1 + 1]
525 del document.body[arg2 : endarg2 + 1]
527 # index of end layout has probably changed
528 j = find_end_of_layout(document.body, i)
530 document.warning("Malformed LyX document: Can't find end of Lemma layout")
536 # if this is not a consecutive env, add start command
538 begcmd = put_cmd_in_ert("\\begin{lemma}")
540 # has this a consecutive lemma?
541 consecutive = document.body[j + 2] == "\\begin_layout Lemma"
543 # if this is not followed by a consecutive env, add end command
545 document.body[j : j + 1] = put_cmd_in_ert("\\end{lemma}") + ["\\end_layout"]
547 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd + subst1 + subst2
553 def revert_question_env(document):
555 Reverts question and question* environments of
556 theorems-ams-extended-bytype module to ERT
559 # Do we use theorems-ams-extended-bytype module?
560 if not "theorems-ams-extended-bytype" in document.get_module_list():
566 i = find_token(document.body, "\\begin_layout Question", i)
570 starred = document.body[i] == "\\begin_layout Question*"
572 j = find_end_of_layout(document.body, i)
574 document.warning("Malformed LyX document: Can't find end of Question layout")
578 # if this is not a consecutive env, add start command
582 begcmd = put_cmd_in_ert("\\begin{question*}")
584 begcmd = put_cmd_in_ert("\\begin{question}")
586 # has this a consecutive theorem of same type?
589 consecutive = document.body[j + 2] == "\\begin_layout Question*"
591 consecutive = document.body[j + 2] == "\\begin_layout Question"
593 # if this is not followed by a consecutive env, add end command
596 document.body[j : j + 1] = put_cmd_in_ert("\\end{question*}") + ["\\end_layout"]
598 document.body[j : j + 1] = put_cmd_in_ert("\\end{question}") + ["\\end_layout"]
600 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
602 add_to_preamble(document, "\\providecommand{\questionname}{Question}")
605 add_to_preamble(document, "\\theoremstyle{plain}\n" \
606 "\\newtheorem*{question*}{\\protect\\questionname}")
608 add_to_preamble(document, "\\theoremstyle{plain}\n" \
609 "\\newtheorem{question}{\\protect\\questionname}")
614 def convert_dashes(document):
615 "convert -- and --- to \\twohyphens and \\threehyphens"
617 if document.backend != "latex":
622 i = find_substring(document.body, "--", i+1)
625 line = document.body[i]
626 # skip label width string (bug 10243):
627 if line.startswith("\\labelwidthstring"):
629 # Do not touch hyphens in some insets:
631 value, start, end = get_containing_inset(document.body, i)
633 # False means no (or malformed) containing inset
634 value, start, end = "no inset", -1, -1
635 # We must not replace anything in insets that store LaTeX contents in .lyx files
636 # (math and command insets without overridden read() and write() methods.
637 # Filtering out IPA and ERT makes Text::readParToken() more simple,
638 # Flex Code is logical markup, typically rendered as typewriter
639 if (value.split()[0] in ["CommandInset", "ERT", "External", "Formula",
640 "FormulaMacro", "Graphics", "IPA", "listings"]
641 or value in ["Flex Code", "Flex URL"]):
645 layout, start, end, j = get_containing_layout(document.body, i)
646 except TypeError: # no (or malformed) containing layout
647 document.warning("Malformed LyX document: "
648 "Can't find layout at line %d" % i)
650 if layout == "LyX-Code":
653 # We can have an arbitrary number of consecutive hyphens.
654 # Replace as LaTeX does: First try emdash, then endash
655 line = line.replace("---", "\\threehyphens\n")
656 line = line.replace("--", "\\twohyphens\n")
657 document.body[i:i+1] = line.split('\n')
659 # remove ligature breaks between dashes
662 i = find_substring(document.body,
663 r"-\SpecialChar \textcompwordmark{}", i+1)
666 if document.body[i+1].startswith("-"):
667 document.body[i] = document.body[i].replace(
668 r"\SpecialChar \textcompwordmark{}", document.body.pop(i+1))
671 def revert_dashes(document):
673 Remove preamble code from 2.3->2.2 conversion.
674 Prevent ligatures of existing --- and --.
675 Revert \\twohyphens and \\threehyphens to -- and ---.
677 del_complete_lines(document.preamble,
678 ['% Added by lyx2lyx',
679 r'\renewcommand{\textendash}{--}',
680 r'\renewcommand{\textemdash}{---}'])
682 # Insert ligature breaks to prevent ligation of hyphens to dashes:
685 i = find_substring(document.body, "--", i+1)
688 line = document.body[i]
689 # skip label width string (bug 10243):
690 if line.startswith("\\labelwidthstring"):
692 # do not touch hyphens in some insets (cf. convert_dashes):
694 value, start, end = get_containing_inset(document.body, i)
696 # False means no (or malformed) containing inset
697 value, start, end = "no inset", -1, -1
698 if (value.split()[0] in ["CommandInset", "ERT", "External", "Formula",
699 "FormulaMacro", "Graphics", "IPA", "listings"]
700 or value == "Flex URL"):
703 line = line.replace("--", "-\\SpecialChar \\textcompwordmark{}\n-")
704 document.body[i:i+1] = line.split('\n')
706 # Revert \twohyphens and \threehyphens:
708 while i < len(document.body):
709 line = document.body[i]
710 if not line.endswith("hyphens"):
712 elif line.endswith("\\twohyphens") or line.endswith("\\threehyphens"):
713 line = line.replace("\\twohyphens", "--")
714 line = line.replace("\\threehyphens", "---")
715 document.body[i] = line + document.body.pop(i+1)
720 # order is important for the last three!
721 phrases = ["LyX", "LaTeX2e", "LaTeX", "TeX"]
723 def is_part_of_converted_phrase(line, j, phrase):
724 "is phrase part of an already converted phrase?"
726 converted = "\\SpecialCharNoPassThru \\" + p
727 pos = j + len(phrase) - len(converted)
729 if line[pos:pos+len(converted)] == converted:
734 def convert_phrases(document):
735 "convert special phrases from plain text to \\SpecialCharNoPassThru"
737 if document.backend != "latex":
741 while i < len(document.body):
742 if document.body[i] and document.body[i][0] == "\\":
743 words = document.body[i].split()
744 if len(words) > 1 and words[0] == "\\begin_inset" and \
745 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
746 # must not replace anything in insets that store LaTeX contents in .lyx files
747 # (math and command insets without overridden read() and write() methods)
748 j = find_end_of_inset(document.body, i)
750 document.warning("Malformed LyX document: Can't find end of inset at line %d" % (i))
757 for phrase in phrases:
758 j = document.body[i].find(phrase)
761 if not is_part_of_converted_phrase(document.body[i], j, phrase):
762 front = document.body[i][:j]
763 back = document.body[i][j+len(phrase):]
765 document.body.insert(i+1, back)
766 # We cannot use SpecialChar since we do not know whether we are outside passThru
767 document.body[i] = front + "\\SpecialCharNoPassThru \\" + phrase
771 def revert_phrases(document):
772 "revert special phrases to plain text"
775 while i < len(document.body):
776 words = document.body[i].split()
777 if len(words) > 1 and words[0] == "\\begin_inset" and \
778 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
779 # see convert_phrases
780 j = find_end_of_inset(document.body, i)
782 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
788 for phrase in phrases:
789 # we can replace SpecialChar since LyX ensures that it cannot be inserted into passThru parts
790 if document.body[i].find("\\SpecialChar \\" + phrase) >= 0:
791 document.body[i] = document.body[i].replace("\\SpecialChar \\" + phrase, phrase)
793 if document.body[i].find("\\SpecialCharNoPassThru \\" + phrase) >= 0:
794 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru \\" + phrase, phrase)
796 if replaced and i+1 < len(document.body) and \
797 (document.body[i+1].find("\\") != 0 or \
798 document.body[i+1].find("\\SpecialChar") == 0) and \
799 len(document.body[i]) + len(document.body[i+1]) <= 80:
800 document.body[i] = document.body[i] + document.body[i+1]
801 document.body[i+1:i+2] = []
806 def convert_specialchar_internal(document, forward):
807 specialchars = {"\\-":"softhyphen", "\\textcompwordmark{}":"ligaturebreak", \
808 "\\@.":"endofsentence", "\\ldots{}":"ldots", \
809 "\\menuseparator":"menuseparator", "\\slash{}":"breakableslash", \
810 "\\nobreakdash-":"nobreakdash", "\\LyX":"LyX", \
811 "\\TeX":"TeX", "\\LaTeX2e":"LaTeX2e", \
812 "\\LaTeX":"LaTeX" # must be after LaTeX2e
816 while i < len(document.body):
817 if document.body[i] and document.body[i][0] == "\\":
818 words = document.body[i].split()
819 if len(words) > 1 and words[0] == "\\begin_inset" and \
820 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
821 # see convert_phrases
822 j = find_end_of_inset(document.body, i)
824 document.warning("Malformed LyX document: Can't find end of %s inset at line %d" % (words[1], i))
830 if not "\\SpecialChar" in document.body[i]:
833 for key, value in specialchars.items():
835 document.body[i] = document.body[i].replace("\\SpecialChar " + key, "\\SpecialChar " + value)
836 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + key, "\\SpecialCharNoPassThru " + value)
838 document.body[i] = document.body[i].replace("\\SpecialChar " + value, "\\SpecialChar " + key)
839 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + value, "\\SpecialCharNoPassThru " + key)
843 def convert_specialchar(document):
844 "convert special characters to new syntax"
845 convert_specialchar_internal(document, True)
848 def revert_specialchar(document):
849 "convert special characters to old syntax"
850 convert_specialchar_internal(document, False)
853 def revert_georgian(document):
854 "Set the document language to English but assure Georgian output"
856 revert_language(document, "georgian", "georgian", "")
859 def revert_sigplan_doi(document):
860 " Reverts sigplanconf DOI layout to ERT "
862 if document.textclass != "sigplanconf":
867 i = find_token(document.body, "\\begin_layout DOI", i)
870 j = find_end_of_layout(document.body, i)
872 document.warning("Malformed LyX document: Can't find end of DOI layout")
876 content = lyx2latex(document, document.body[i:j + 1])
877 add_to_preamble(document, ["\\doi{" + content + "}"])
878 del document.body[i:j + 1]
882 def revert_ex_itemargs(document):
883 " Reverts \\item arguments of the example environments (Linguistics module) to TeX-code "
885 if not "linguistics" in document.get_module_list():
889 example_layouts = ["Numbered Examples (consecutive)", "Subexample"]
891 i = find_token(document.body, "\\begin_inset Argument item:", i)
894 j = find_end_of_inset(document.body, i)
895 # Find containing paragraph layout
896 parent = get_containing_layout(document.body, i)
898 document.warning("Malformed LyX document: Can't find parent paragraph layout")
902 layoutname = parent[0]
903 if layoutname in example_layouts:
904 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
905 endPlain = find_end_of_layout(document.body, beginPlain)
906 content = document.body[beginPlain + 1 : endPlain]
907 del document.body[i:j+1]
908 subst = put_cmd_in_ert("[") + content + put_cmd_in_ert("]")
909 document.body[parbeg : parbeg] = subst
913 def revert_forest(document):
914 " Reverts the forest environment (Linguistics module) to TeX-code "
916 if not "linguistics" in document.get_module_list():
921 i = find_token(document.body, "\\begin_inset Flex Structure Tree", i)
924 j = find_end_of_inset(document.body, i)
926 document.warning("Malformed LyX document: Can't find end of Structure Tree inset")
930 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
931 endPlain = find_end_of_layout(document.body, beginPlain)
932 content = lyx2latex(document, document.body[beginPlain : endPlain])
934 add_to_preamble(document, ["\\usepackage{forest}"])
936 document.body[i:j + 1] = put_cmd_in_ert("\\begin{forest}" + content + "\\end{forest}")
940 def revert_glossgroup(document):
941 " Reverts the GroupGlossedWords inset (Linguistics module) to TeX-code "
943 if not "linguistics" in document.get_module_list():
948 i = find_token(document.body, "\\begin_inset Flex GroupGlossedWords", i)
951 j = find_end_of_inset(document.body, i)
953 document.warning("Malformed LyX document: Can't find end of GroupGlossedWords inset")
957 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
958 endPlain = find_end_of_layout(document.body, beginPlain)
959 content = lyx2verbatim(document, document.body[beginPlain : endPlain])
961 document.body[i:j + 1] = ["{", "", content, "", "}"]
965 def revert_newgloss(document):
966 " Reverts the new Glosse insets (Linguistics module) to the old format "
968 if not "linguistics" in document.get_module_list():
971 glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
972 for glosse in glosses:
975 i = find_token(document.body, glosse, i)
978 j = find_end_of_inset(document.body, i)
980 document.warning("Malformed LyX document: Can't find end of Glosse inset")
984 arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
985 endarg = find_end_of_inset(document.body, arg)
988 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
989 if argbeginPlain == -1:
990 document.warning("Malformed LyX document: Can't find arg plain Layout")
993 argendPlain = find_end_of_inset(document.body, argbeginPlain)
994 argcontent = lyx2verbatim(document, document.body[argbeginPlain : argendPlain - 2])
996 document.body[j:j] = ["", "\\begin_layout Plain Layout","\\backslash", "glt ",
997 argcontent, "\\end_layout"]
999 # remove Arg insets and paragraph, if it only contains this inset
1000 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
1001 del document.body[arg - 1 : endarg + 4]
1003 del document.body[arg : endarg + 1]
1005 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1006 endPlain = find_end_of_layout(document.body, beginPlain)
1007 content = lyx2verbatim(document, document.body[beginPlain : endPlain])
1009 document.body[beginPlain + 1:endPlain] = [content]
1012 # Dissolve ERT insets
1013 for glosse in glosses:
1016 i = find_token(document.body, glosse, i)
1019 j = find_end_of_inset(document.body, i)
1021 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1025 ert = find_token(document.body, "\\begin_inset ERT", i, j)
1028 ertend = find_end_of_inset(document.body, ert)
1030 document.warning("Malformed LyX document: Can't find end of ERT inset")
1033 ertcontent = get_ert(document.body, ert, True)
1034 document.body[ert : ertend + 1] = [ertcontent]
1038 def convert_newgloss(document):
1039 " Converts Glosse insets (Linguistics module) to the new format "
1041 if not "linguistics" in document.get_module_list():
1044 glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
1045 for glosse in glosses:
1048 i = find_token(document.body, glosse, i)
1051 j = find_end_of_inset(document.body, i)
1053 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1060 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", k, j)
1061 if beginPlain == -1:
1063 endPlain = find_end_of_layout(document.body, beginPlain)
1065 document.warning("Malformed LyX document: Can't find end of Glosse layout")
1069 glt = find_token(document.body, "\\backslash", beginPlain, endPlain)
1070 if glt != -1 and document.body[glt + 1].startswith("glt"):
1071 document.body[glt + 1] = document.body[glt + 1].lstrip("glt").lstrip()
1072 argcontent = document.body[glt + 1 : endPlain]
1073 document.body[beginPlain + 1 : endPlain] = ["\\begin_inset Argument 1", "status open", "",
1074 "\\begin_layout Plain Layout", "\\begin_inset ERT", "status open", "",
1075 "\\begin_layout Plain Layout", ""] + argcontent + ["\\end_layout", "", "\\end_inset", "",
1076 "\\end_layout", "", "\\end_inset"]
1078 content = document.body[beginPlain + 1 : endPlain]
1079 document.body[beginPlain + 1 : endPlain] = ["\\begin_inset ERT", "status open", "",
1080 "\\begin_layout Plain Layout"] + content + ["\\end_layout", "", "\\end_inset"]
1082 endPlain = find_end_of_layout(document.body, beginPlain)
1084 j = find_end_of_inset(document.body, i)
1089 def convert_BoxFeatures(document):
1090 " adds new box features "
1094 i = find_token(document.body, "height_special", i)
1097 document.body[i+1:i+1] = ['thickness "0.4pt"', 'separation "3pt"', 'shadowsize "4pt"']
1101 def revert_BoxFeatures(document):
1102 " outputs new box features as TeX code "
1106 defaultThick = "0.4pt"
1107 defaultShadow = "4pt"
1109 i = find_token(document.body, "thickness", i)
1112 binset = find_token(document.body, "\\begin_inset Box", i - 11)
1113 if binset == -1 or binset != i - 11:
1115 continue # then "thickness" is is just a word in the text
1116 einset = find_end_of_inset(document.body, binset)
1118 document.warning("Malformed LyX document: Can't find end of box inset!")
1121 # read out the values
1122 beg = document.body[i].find('"');
1123 end = document.body[i].rfind('"');
1124 thickness = document.body[i][beg+1:end];
1125 beg = document.body[i+1].find('"');
1126 end = document.body[i+1].rfind('"');
1127 separation = document.body[i+1][beg+1:end];
1128 beg = document.body[i+2].find('"');
1129 end = document.body[i+2].rfind('"');
1130 shadowsize = document.body[i+2][beg+1:end];
1132 # first output the closing brace
1133 if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1134 document.body[einset + 1 : einset + 1] = put_cmd_in_ert("}")
1135 # delete the specification
1136 del document.body[i:i+3]
1137 # we have now the problem that if there is already \(f)colorbox in ERT around the inset
1138 # the ERT from this routine must be around it
1139 regexp = re.compile(r'^.*colorbox{.*$')
1140 pos = find_re(document.body, regexp, binset - 4)
1141 if pos != -1 and pos == binset - 4:
1145 # now output the lengths
1146 if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1147 document.body[pos : pos] = put_cmd_in_ert("{")
1148 if thickness != defaultThick:
1149 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness]
1150 if separation != defaultSep and thickness == defaultThick:
1151 document.body[pos + 5 : pos +6] = ["{\\backslash fboxsep " + separation]
1152 if separation != defaultSep and thickness != defaultThick:
1153 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation]
1154 if shadowsize != defaultShadow and separation == defaultSep and thickness == defaultThick:
1155 document.body[pos + 5 : pos +6] = ["{\\backslash shadowsize " + shadowsize]
1156 if shadowsize != defaultShadow and separation != defaultSep and thickness == defaultThick:
1157 document.body[pos + 5 : pos +6] = ["{\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1158 if shadowsize != defaultShadow and separation == defaultSep and thickness != defaultThick:
1159 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash shadowsize " + shadowsize]
1160 if shadowsize != defaultShadow and separation != defaultSep and thickness != defaultThick:
1161 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1164 def convert_origin(document):
1165 " Insert the origin tag "
1167 i = find_token(document.header, "\\textclass ", 0)
1169 document.warning("Malformed LyX document: No \\textclass!!")
1171 if document.dir == u'':
1175 if document.systemlyxdir and document.systemlyxdir != u'':
1177 if os.path.isabs(document.dir):
1178 absdir = os.path.normpath(document.dir)
1180 absdir = os.path.normpath(os.path.abspath(document.dir))
1181 if os.path.isabs(document.systemlyxdir):
1182 abssys = os.path.normpath(document.systemlyxdir)
1184 abssys = os.path.normpath(os.path.abspath(document.systemlyxdir))
1185 relpath = os.path.relpath(absdir, abssys)
1186 if relpath.find(u'..') == 0:
1191 origin = document.dir.replace(u'\\', u'/') + u'/'
1193 origin = os.path.join(u"/systemlyxdir", relpath).replace(u'\\', u'/') + u'/'
1194 document.header[i:i] = ["\\origin " + origin]
1197 def revert_origin(document):
1198 " Remove the origin tag "
1200 i = find_token(document.header, "\\origin ", 0)
1202 document.warning("Malformed LyX document: No \\origin!!")
1204 del document.header[i]
1207 color_names = ["brown", "darkgray", "gray", \
1208 "lightgray", "lime", "olive", "orange", \
1209 "pink", "purple", "teal", "violet"]
1211 def revert_textcolor(document):
1212 " revert new \\textcolor colors to TeX code "
1218 i = find_token(document.body, "\\color ", i)
1222 for color in list(color_names):
1223 if document.body[i] == "\\color " + color:
1224 # register that xcolor must be loaded in the preamble
1227 add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"])
1228 # find the next \\color and/or the next \\end_layout
1229 j = find_token(document.body, "\\color", i + 1)
1230 k = find_token(document.body, "\\end_layout", i + 1)
1231 if j == -1 and k != -1:
1234 # first output the closing brace
1236 document.body[k: k] = put_cmd_in_ert("}")
1238 document.body[j: j] = put_cmd_in_ert("}")
1239 # now output the \textcolor command
1240 document.body[i : i + 1] = put_cmd_in_ert("\\textcolor{" + color + "}{")
1244 def convert_colorbox(document):
1245 "Add color settings for boxes."
1248 i = find_token(document.body, "shadowsize", i)
1251 # check whether this is really a LyX Box setting
1252 start, end = is_in_inset(document.body, i, "\\begin_inset Box")
1254 find_token(document.body, "\\begin_layout", start, i) != -1):
1257 document.body[i+1:i+1] = ['framecolor "black"',
1258 'backgroundcolor "none"']
1262 def revert_colorbox(document):
1263 """Change box color settings to LaTeX code."""
1267 i = find_token(document.body, "\\begin_inset Box", i)
1271 j = find_end_of_inset(document.body, i)
1272 k = find_token(document.body, "\\begin_layout", i, j)
1274 document.warning("Malformed LyX document: no layout in Box inset!")
1277 # Get and delete colour settings:
1278 framecolor = get_quoted_value(document.body, "framecolor", i, k, delete=True)
1279 backcolor = get_quoted_value(document.body, "backgroundcolor", i, k + 1, delete=True)
1280 if not framecolor or not backcolor:
1281 document.warning("Malformed LyX document: color options not found in Box inset!")
1284 if framecolor == "black" and backcolor == "none": # default values
1288 # Emulate non-default colours with LaTeX code:
1289 einset = find_end_of_inset(document.body, i)
1291 document.warning("Malformed LyX document: Can't find end of box inset!")
1294 add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"])
1295 # insert the closing brace first (keeps indices 'i' and 'einset' valid)
1296 document.body[einset+1:einset+1] = put_cmd_in_ert("}")
1297 # now insert the (f)color box command
1298 if ("Box Boxed" in document.body[i]): # framed box, use \fcolorbox
1299 # change the box type (frame added by \fcolorbox)
1300 document.body[i] = "\\begin_inset Box Frameless"
1301 # ensure an inner box:
1303 if not set_bool_value(document.body, "has_inner_box", True, i+3, i+4):
1304 set_bool_value(document.body, "use_makebox", True, i+6, i+7)
1306 document.warning("Malformed LyX document: 'has_inner_box' or "
1307 "'use_makebox' option not found in box inset!")
1308 ertinset = put_cmd_in_ert("\\fcolorbox{%s}{%s}{"% (framecolor, backcolor))
1310 ertinset = put_cmd_in_ert("\\colorbox{%s}{" % backcolor)
1311 document.body[i:i] = ertinset + [""]
1315 def revert_mathmulticol(document):
1316 " Convert formulas to ERT if they contain multicolumns "
1320 i = find_token(document.body, '\\begin_inset Formula', i)
1323 j = find_end_of_inset(document.body, i)
1325 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
1328 lines = document.body[i:j]
1329 lines[0] = lines[0].replace('\\begin_inset Formula', '').lstrip()
1330 code = "\n".join(lines)
1335 n = code.find("\\multicolumn", k)
1336 # no need to convert degenerated multicolumn cells,
1337 # they work in old LyX versions as "math ERT"
1338 if n != -1 and code.find("\\multicolumn{1}", k) != n:
1339 ert = put_cmd_in_ert(code)
1340 document.body[i:j+1] = ert
1346 i = find_end_of_inset(document.body, i)
1351 def revert_jss(document):
1352 " Reverts JSS In_Preamble commands to ERT in preamble "
1354 if document.textclass != "jss":
1357 # at first revert the inset layouts because
1358 # they can be part of the In_Preamble layouts
1361 "Proglang" : "proglang",
1366 for iltype in il_dict.keys():
1369 i = find_token(document.body, "\\begin_inset Flex " + iltype, i)
1373 if document.body[i] != "\\begin_inset Flex " + iltype:
1374 # Not an exact match!
1378 end = find_end_of_inset(document.body, i)
1380 document.warning("Malformed LyX document: No end of Flex " + iltype + " found!")
1383 document.body[end - 2 : end + 1] = put_cmd_in_ert("}")
1384 document.body[i : i + 4] = put_cmd_in_ert("\\%s{" % il_dict[iltype])
1387 # now revert the In_Preamble layouts
1390 "Author" : "author",
1391 "Plain Author" : "Plainauthor",
1392 "Plain Title" : "Plaintitle",
1393 "Short Title" : "Shorttitle",
1394 "Abstract" : "Abstract",
1395 "Keywords" : "Keywords",
1396 "Plain Keywords" : "Plainkeywords",
1397 "Address" : "Address",
1399 for ipltype in ipl_dict.keys():
1402 i = find_token(document.body, "\\begin_layout " + ipltype, i)
1406 end = find_end_of_layout(document.body, i)
1408 document.warning("Malformed LyX document: Can't find end of " + ipltype + " layout")
1412 content = lyx2latex(document, document.body[i:end + 1])
1413 add_to_preamble(document, ["\\" + ipl_dict[ipltype] + "{" + content + "}"])
1414 del document.body[i:end + 1]
1421 i = find_token(document.body, "\\begin_inset Flex Code Chunk", i)
1425 end = find_end_of_inset(document.body, i)
1427 document.warning("Malformed LyX document: No end of Flex Code Chunk found!")
1431 document.body[end : end + 1] = ["\\end_layout", "", "\\begin_layout Standard"] + put_cmd_in_ert("\\end{CodeChunk}")
1432 document.body[i : i + 2] = put_cmd_in_ert("\\begin{CodeChunk}")
1435 # finally handle the code layouts
1437 "Code Input" : "CodeInput",
1438 "Code Output" : "CodeOutput",
1441 for ctype in codes_dict.keys():
1444 i = find_token(document.body, "\\begin_layout " + ctype, i)
1447 if document.body[i] != "\\begin_layout " + ctype:
1448 # Not an exact match!
1451 end = find_end_of_layout(document.body, i)
1453 document.warning("Malformed LyX document: No end of " + ctype + " layout found!")
1457 # Handle subsequent layouts
1459 j = find_token(document.body, "\\begin_layout ", seq_end)
1460 if j == -1 or document.body[j] != "\\begin_layout " + ctype:
1462 this_end = find_end_of_layout(document.body, j)
1464 document.warning("Malformed LyX document: No end of " + ctype + " layout found!")
1468 document.body[seq_end + 1 : seq_end + 1] = ["\\end_inset", "\\end_layout", "", "\\begin_layout Standard"] + put_cmd_in_ert("\\end{%s}" % codes_dict[ctype])
1472 document.body[k] = document.body[k].replace("\\begin_layout " + ctype, "\\begin_layout Plain Layout")
1474 document.body[i : i + 1] = ["\\end_layout", "", "\\begin_layout Standard"] \
1475 + put_cmd_in_ert("\\begin{%s}" % codes_dict[ctype]) \
1476 + ["\\end_layout", "", "\\begin_layout Standard", "", "\\begin_inset ERT", "status open", "", "\\begin_layout Plain Layout"]
1480 def convert_subref(document):
1481 " converts sub: ref prefixes to subref: "
1484 rx = re.compile(r'^name \"sub:(.+)$')
1487 i = find_token(document.body, "\\begin_inset CommandInset label", i)
1490 j = find_end_of_inset(document.body, i)
1492 document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1496 for p in range(i, j):
1497 m = rx.match(document.body[p])
1500 document.body[p] = "name \"subsec:" + label
1504 rx = re.compile(r'^reference \"sub:(.+)$')
1507 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1510 j = find_end_of_inset(document.body, i)
1512 document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1516 for p in range(i, j):
1517 m = rx.match(document.body[p])
1520 document.body[p] = "reference \"subsec:" + label
1526 def revert_subref(document):
1527 " reverts subref: ref prefixes to sub: "
1530 rx = re.compile(r'^name \"subsec:(.+)$')
1533 i = find_token(document.body, "\\begin_inset CommandInset label", i)
1536 j = find_end_of_inset(document.body, i)
1538 document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1542 for p in range(i, j):
1543 m = rx.match(document.body[p])
1546 document.body[p] = "name \"sub:" + label
1551 rx = re.compile(r'^reference \"subsec:(.+)$')
1554 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1557 j = find_end_of_inset(document.body, i)
1559 document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1563 for p in range(i, j):
1564 m = rx.match(document.body[p])
1567 document.body[p] = "reference \"sub:" + label
1572 def convert_nounzip(document):
1573 " remove the noUnzip parameter of graphics insets "
1575 rx = re.compile(r'\s*noUnzip\s*$')
1578 i = find_token(document.body, "\\begin_inset Graphics", i)
1581 j = find_end_of_inset(document.body, i)
1583 document.warning("Malformed LyX document: Can't find end of graphics inset at line " + str(i))
1587 k = find_re(document.body, rx, i, j)
1589 del document.body[k]
1594 def convert_revert_external_bbox(document, forward):
1595 " add units to bounding box of external insets "
1597 rx = re.compile(r'^\s*boundingBox\s+\S+\s+\S+\s+\S+\s+\S+\s*$')
1600 i = find_token(document.body, "\\begin_inset External", i)
1603 j = find_end_of_inset(document.body, i)
1605 document.warning("Malformed LyX document: Can't find end of external inset at line " + str(i))
1608 k = find_re(document.body, rx, i, j)
1612 tokens = document.body[k].split()
1614 for t in range(1, 5):
1617 for t in range(1, 5):
1618 tokens[t] = length_in_bp(tokens[t])
1619 document.body[k] = "\tboundingBox " + tokens[1] + " " + tokens[2] + " " + \
1620 tokens[3] + " " + tokens[4]
1624 def convert_external_bbox(document):
1625 convert_revert_external_bbox(document, True)
1628 def revert_external_bbox(document):
1629 convert_revert_external_bbox(document, False)
1632 def revert_tcolorbox_1(document):
1633 " Reverts the Flex:Subtitle inset of tcolorbox to TeX-code "
1635 i = find_token(document.header, "tcolorbox", 0)
1642 flex = find_token(document.body, "\\begin_inset Flex Subtitle", flex)
1646 flexEnd = find_end_of_inset(document.body, flex)
1648 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1652 wasOpt = revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, False, True, False)
1653 flexEnd = find_end_of_inset(document.body, flex)
1655 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1658 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, False, False, False)
1659 flexEnd = find_end_of_inset(document.body, flex)
1661 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1665 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1667 document.warning("Malformed LyX document! No Flex Subtitle layout found.")
1671 ep = find_end_of_layout(document.body, bp)
1673 document.warning("Malformed LyX document! No end of layout found.")
1677 document.body[ep : flexEnd + 1] = put_cmd_in_ert("}")
1679 document.body[flex : bp + 1] = put_cmd_in_ert("\\tcbsubtitle")
1681 document.body[flex : bp + 1] = put_cmd_in_ert("\\tcbsubtitle{")
1685 def revert_tcolorbox_2(document):
1686 " Reverts the Flex:Raster_Color_Box inset of tcolorbox to TeX-code "
1688 i = find_token(document.header, "tcolorbox", 0)
1694 flex = find_token(document.body, "\\begin_inset Flex Raster Color Box", flex)
1698 flexEnd = find_end_of_inset(document.body, flex)
1700 document.warning("Malformed LyX document! No end of Flex Raster Color Box found.")
1704 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1706 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1708 document.warning("Malformed LyX document! No plain layout in Raster Color Box found.")
1712 ep = find_end_of_layout(document.body, bp)
1714 document.warning("Malformed LyX document! No end of layout found.")
1718 flexEnd = find_end_of_inset(document.body, flex)
1720 document.warning("Malformed LyX document! No end of Flex Raster Color Box found.")
1723 document.body[ep : flexEnd + 1] = put_cmd_in_ert("\\end{tcbraster}")
1724 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{tcbraster}")
1728 def revert_tcolorbox_3(document):
1729 " Reverts the Flex:Custom_Color_Box_1 inset of tcolorbox to TeX-code "
1731 i = find_token(document.header, "tcolorbox", 0)
1737 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 1", flex)
1741 flexEnd = find_end_of_inset(document.body, flex)
1743 document.warning("Malformed LyX document! No end of Flex Custom Color Box 1 found.")
1747 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1748 flexEnd = find_end_of_inset(document.body, flex)
1750 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1753 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
1755 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1757 document.warning("Malformed LyX document! No plain layout in Custom Color Box 1 found.")
1761 ep = find_end_of_layout(document.body, bp)
1763 document.warning("Malformed LyX document! No end of layout found.")
1767 flexEnd = find_end_of_inset(document.body, flex)
1769 document.warning("Malformed LyX document! No end of Flex Custom Color Box 1 found.")
1773 document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxA}")
1774 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxA}")
1778 def revert_tcolorbox_4(document):
1779 " Reverts the Flex:Custom_Color_Box_2 inset of tcolorbox to TeX-code "
1781 i = find_token(document.header, "tcolorbox", 0)
1787 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 2", flex)
1791 flexEnd = find_end_of_inset(document.body, flex)
1793 document.warning("Malformed LyX document! No end of Flex Custom Color Box 2 found.")
1797 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1798 flexEnd = find_end_of_inset(document.body, flex)
1800 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1803 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
1804 flexEnd = find_end_of_inset(document.body, flex)
1806 document.warning("Malformed LyX document! No end of Flex Custom Color Box 2 found.")
1810 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1812 document.warning("Malformed LyX document! No plain layout in Custom Color Box 2 found.")
1816 ep = find_end_of_layout(document.body, bp)
1818 document.warning("Malformed LyX document! No end of layout found.")
1822 document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxB}")
1823 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxB}")
1827 def revert_tcolorbox_5(document):
1828 " Reverts the Flex:Custom_Color_Box_3 inset of tcolorbox to TeX-code "
1830 i = find_token(document.header, "tcolorbox", 0)
1836 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 3", flex)
1840 flexEnd = find_end_of_inset(document.body, flex)
1842 document.warning("Malformed LyX document! No end of Flex Custom Color Box 3 found.")
1846 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1847 flexEnd = find_end_of_inset(document.body, flex)
1849 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1852 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
1853 flexEnd = find_end_of_inset(document.body, flex)
1855 document.warning("Malformed LyX document! No end of Flex Custom Color Box 3 found.")
1859 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1861 document.warning("Malformed LyX document! No plain layout in Custom Color Box 3 found.")
1865 ep = find_end_of_layout(document.body, bp)
1867 document.warning("Malformed LyX document! No end of layout found.")
1871 document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxC}")
1872 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxC}")
1876 def revert_tcolorbox_6(document):
1877 " Reverts the Flex:Custom_Color_Box_4 inset of tcolorbox to TeX-code "
1879 i = find_token(document.header, "tcolorbox", 0)
1885 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 4", flex)
1889 flexEnd = find_end_of_inset(document.body, flex)
1891 document.warning("Malformed LyX document! No end of Flex Custom Color Box 4 found.")
1895 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1896 flexEnd = find_end_of_inset(document.body, flex)
1898 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1901 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
1902 flexEnd = find_end_of_inset(document.body, flex)
1904 document.warning("Malformed LyX document! No end of Flex Custom Color Box 4 found.")
1908 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1910 document.warning("Malformed LyX document! No plain layout in Custom Color Box 4 found.")
1914 ep = find_end_of_layout(document.body, bp)
1916 document.warning("Malformed LyX document! No end of layout found.")
1920 document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxD}")
1921 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxD}")
1925 def revert_tcolorbox_7(document):
1926 " Reverts the Flex:Custom_Color_Box_5 inset of tcolorbox to TeX-code "
1928 i = find_token(document.header, "tcolorbox", 0)
1934 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 5", flex)
1938 flexEnd = find_end_of_inset(document.body, flex)
1940 document.warning("Malformed LyX document! No end of Flex Custom Color Box 5 found.")
1944 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1945 flexEnd = find_end_of_inset(document.body, flex)
1947 document.warning("Malformed LyX document! No end of Flex Subtitle found.")
1950 revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
1951 flexEnd = find_end_of_inset(document.body, flex)
1953 document.warning("Malformed LyX document! No end of Flex Custom Color Box 5 found.")
1957 bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
1959 document.warning("Malformed LyX document! No plain layout in Custom Color Box 5 found.")
1963 ep = find_end_of_layout(document.body, bp)
1965 document.warning("Malformed LyX document! No end of layout found.")
1969 document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxE}")
1970 document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxE}")
1974 def revert_tcolorbox_8(document):
1975 " Reverts the layout New Color Box Type of tcolorbox to TeX-code "
1979 i = find_token(document.body, "\\begin_layout New Color Box Type", i)
1983 j = find_end_of_layout(document.body, i)
1985 document.warning("Malformed LyX document! No end of New Color Box Type layout found.")
1989 wasOpt = revert_Argument_to_TeX_brace(document, i, j, 1, 1, False, True, True)
1990 j = find_end_of_layout(document.body, i)
1992 document.warning("Malformed LyX document! No end of New Color Box Type layout found.")
1995 revert_Argument_to_TeX_brace(document, i, j, 2, 2, False, False, 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, 3, 4, False, True, False)
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 document.body[i] = document.body[i].replace("\\begin_layout New Color Box Type", "\\begin_layout Standard")
2008 document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox")
2009 k = find_end_of_inset(document.body, j)
2010 document.body[k + 2 : j + 2] = put_cmd_in_ert("{") + ["\\begin_inset ERT", "status collapsed", "\\begin_layout Plain Layout"]
2011 j = find_token(document.body, "\\begin_layout Standard", j + 1)
2012 document.body[j - 2 : j - 2] = ["\\end_layout", "\\end_inset"] + put_cmd_in_ert("}")
2016 def revert_moderncv_1(document):
2017 " Reverts the new inset of moderncv to TeX-code in preamble "
2019 if document.textclass != "moderncv":
2025 # at first revert the new styles
2027 i = find_token(document.body, "\\begin_layout CVIcons", 0)
2030 j = find_end_of_layout(document.body, i)
2032 document.warning("Malformed LyX document: Can't find end of CVIcons layout")
2035 content = lyx2latex(document, document.body[i:j + 1])
2036 add_to_preamble(document, ["\\moderncvicons{" + content + "}"])
2037 del document.body[i:j + 1]
2039 i = find_token(document.body, "\\begin_layout CVColumnWidth", 0)
2042 j = find_end_of_layout(document.body, i)
2044 document.warning("Malformed LyX document: Can't find end of CVColumnWidth layout")
2047 content = lyx2latex(document, document.body[i:j + 1])
2048 add_to_preamble(document, ["\\setlength{\hintscolumnwidth}{" + content + "}"])
2049 del document.body[i:j + 1]
2050 # now change the new styles to the obsolete ones
2052 i = find_token(document.body, "\\begin_layout Name", 0)
2055 j = find_end_of_layout(document.body, i)
2057 document.warning("Malformed LyX document: Can't find end of Name layout")
2060 lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
2061 if lineArg > j and j != 0:
2064 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
2065 # we have to assure that no other inset is in the Argument
2066 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
2067 endInset = find_token(document.body, "\\end_inset", beginPlain)
2070 while beginInset < endInset and beginInset != -1:
2071 beginInset = find_token(document.body, "\\begin_inset", k)
2072 endInset = find_token(document.body, "\\end_inset", l)
2075 Arg2 = document.body[l + 5 : l + 6]
2077 document.body[i : i + 1]= ["\\begin_layout FirstName"]
2078 # delete the Argument inset
2079 del( document.body[endInset - 2 : endInset + 3])
2080 del( document.body[lineArg : beginPlain + 1])
2081 document.body[i + 4 : i + 4]= ["\\begin_layout FamilyName"] + Arg2 + ["\\end_layout"] + [""]
2084 def revert_moderncv_2(document):
2085 " Reverts the phone inset of moderncv to the obsoleted mobile or fax "
2087 if document.textclass != "moderncv":
2094 i = find_token(document.body, "\\begin_layout Phone", i)
2097 j = find_end_of_layout(document.body, i)
2099 document.warning("Malformed LyX document: Can't find end of Phone layout")
2102 lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
2103 if lineArg > j and j != 0:
2107 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
2108 # we have to assure that no other inset is in the Argument
2109 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
2110 endInset = find_token(document.body, "\\end_inset", beginPlain)
2113 while beginInset < endInset and beginInset != -1:
2114 beginInset = find_token(document.body, "\\begin_inset", k)
2115 endInset = find_token(document.body, "\\end_inset", l)
2118 Arg = document.body[beginPlain + 1 : beginPlain + 2]
2120 if Arg[0] == "mobile":
2121 document.body[i : i + 1]= ["\\begin_layout Mobile"]
2123 document.body[i : i + 1]= ["\\begin_layout Fax"]
2124 # delete the Argument inset
2125 del(document.body[endInset - 2 : endInset + 1])
2126 del(document.body[lineArg : beginPlain + 3])
2130 def convert_moderncv_phone(document):
2131 " Convert the Fax and Mobile inset of moderncv to the new phone inset "
2133 if document.textclass != "moderncv":
2140 "Mobile" : "mobile",
2144 rx = re.compile(r'^\\begin_layout (\S+)$')
2146 # substitute \fax and \mobile by \phone[fax] and \phone[mobile], respectively
2147 i = find_token(document.body, "\\begin_layout", i)
2151 m = rx.match(document.body[i])
2155 if val not in list(phone_dict.keys()):
2158 j = find_end_of_layout(document.body, i)
2160 document.warning("Malformed LyX document: Can't find end of Mobile layout")
2164 document.body[i : i + 1] = ["\\begin_layout Phone", "\\begin_inset Argument 1", "status open", "",
2165 "\\begin_layout Plain Layout", phone_dict[val], "\\end_layout", "",
2169 def convert_moderncv_name(document):
2170 " Convert the FirstName and LastName layout of moderncv to the general Name layout "
2172 if document.textclass != "moderncv":
2175 fnb = 0 # Begin of FirstName inset
2176 fne = 0 # End of FirstName inset
2177 lnb = 0 # Begin of LastName (FamilyName) inset
2178 lne = 0 # End of LastName (FamilyName) inset
2179 nb = 0 # Begin of substituting Name inset
2180 ne = 0 # End of substituting Name inset
2181 FirstName = [] # FirstName content
2182 FamilyName = [] # LastName content
2186 fnb = find_token(document.body, "\\begin_layout FirstName", fnb)
2188 fne = find_end_of_layout(document.body, fnb)
2190 document.warning("Malformed LyX document: Can't find end of FirstName layout")
2192 FirstName = document.body[fnb + 1 : fne]
2194 lnb = find_token(document.body, "\\begin_layout FamilyName", lnb)
2196 lne = find_end_of_layout(document.body, lnb)
2198 document.warning("Malformed LyX document: Can't find end of FamilyName layout")
2200 FamilyName = document.body[lnb + 1 : lne]
2201 # Determine the region for the substituting Name layout
2202 if fnb == -1 and lnb == -1: # Neither FirstName nor FamilyName exists -> Do nothing
2204 elif fnb == -1: # Only FamilyName exists -> New Name insets replaces that
2207 elif lnb == -1: # Only FirstName exists -> New Name insets replaces that
2210 elif fne > lne: # FirstName position before FamilyName -> New Name insets spans
2211 nb = lnb # from FamilyName begin
2212 ne = fne # to FirstName end
2213 else: # FirstName position before FamilyName -> New Name insets spans
2214 nb = fnb # from FirstName begin
2215 ne = lne # to FamilyName end
2217 # Insert the substituting layout now. If FirstName exists, use an otpional argument.
2219 document.body[nb : ne + 1] = ["\\begin_layout Name"] + FamilyName + ["\\end_layout", ""]
2221 document.body[nb : ne + 1] = ["\\begin_layout Name", "\\begin_inset Argument 1", "status open", "",
2222 "\\begin_layout Plain Layout"] + FirstName + ["\\end_layout", "",
2223 "\\end_inset", ""] + FamilyName + ["\\end_layout", ""]
2226 def revert_achemso(document):
2227 " Reverts the flex inset Latin to TeX code "
2229 if document.textclass != "achemso":
2234 i = find_token(document.body, "\\begin_inset Flex Latin", i)
2236 j = find_end_of_inset(document.body, i)
2240 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2241 endPlain = find_end_of_layout(document.body, beginPlain)
2242 content = lyx2latex(document, document.body[beginPlain : endPlain])
2243 document.body[i:j + 1] = put_cmd_in_ert("\\latin{" + content + "}")
2245 document.warning("Malformed LyX document: Can't find end of flex inset Latin")
2250 fontsettings = ["\\font_roman", "\\font_sans", "\\font_typewriter", "\\font_math", \
2251 "\\font_sf_scale", "\\font_tt_scale"]
2252 fontdefaults = ["default", "default", "default", "auto", "100", "100"]
2253 fontquotes = [True, True, True, True, False, False]
2255 def convert_fontsettings(document):
2256 " Duplicate font settings "
2258 i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2260 document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2261 use_non_tex_fonts = "false"
2263 use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2265 for f in fontsettings:
2266 i = find_token(document.header, f + " ", 0)
2268 document.warning("Malformed LyX document: No " + f + "!")
2270 # note that with i = -1, this will insert at the end
2272 value = fontdefaults[j]
2274 value = document.header[i][len(f):].strip()
2276 if use_non_tex_fonts == "true":
2277 document.header[i:i+1] = [f + ' "' + fontdefaults[j] + '" "' + value + '"']
2279 document.header[i:i+1] = [f + ' "' + value + '" "' + fontdefaults[j] + '"']
2281 if use_non_tex_fonts == "true":
2282 document.header[i:i+1] = [f + ' ' + fontdefaults[j] + ' ' + value]
2284 document.header[i:i+1] = [f + ' ' + value + ' ' + fontdefaults[j]]
2288 def revert_fontsettings(document):
2289 " Merge font settings "
2291 i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2293 document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2294 use_non_tex_fonts = "false"
2296 use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2298 for f in fontsettings:
2299 i = find_token(document.header, f + " ", 0)
2301 document.warning("Malformed LyX document: No " + f + "!")
2304 line = get_value(document.header, f, i)
2307 q2 = line.find('"', q1+1)
2308 q3 = line.find('"', q2+1)
2309 q4 = line.find('"', q3+1)
2310 if q1 == -1 or q2 == -1 or q3 == -1 or q4 == -1:
2311 document.warning("Malformed LyX document: Missing quotes!")
2314 if use_non_tex_fonts == "true":
2315 document.header[i:i+1] = [f + ' ' + line[q3+1:q4]]
2317 document.header[i:i+1] = [f + ' ' + line[q1+1:q2]]
2319 if use_non_tex_fonts == "true":
2320 document.header[i:i+1] = [f + ' ' + line.split()[1]]
2322 document.header[i:i+1] = [f + ' ' + line.split()[0]]
2326 def revert_solution(document):
2327 " Reverts the solution environment of the theorem module to TeX code "
2329 # Do we use one of the modules that provides Solution?
2331 mods = document.get_module_list()
2333 if mod == "theorems-std" or mod == "theorems-bytype" \
2334 or mod == "theorems-ams" or mod == "theorems-ams-bytype":
2344 i = find_token(document.body, "\\begin_layout Solution", i)
2348 is_starred = document.body[i].startswith("\\begin_layout Solution*")
2349 if is_starred == True:
2351 LyXName = "Solution*"
2352 theoremName = "newtheorem*"
2355 LyXName = "Solution"
2356 theoremName = "newtheorem"
2358 j = find_end_of_layout(document.body, i)
2360 document.warning("Malformed LyX document: Can't find end of " + LyXName + " layout")
2364 # if this is not a consecutive env, add start command
2367 begcmd = put_cmd_in_ert("\\begin{%s}" % (LaTeXName))
2369 # has this a consecutive theorem of same type?
2370 consecutive = document.body[j + 2] == "\\begin_layout " + LyXName
2372 # if this is not followed by a consecutive env, add end command
2374 document.body[j : j + 1] = put_cmd_in_ert("\\end{%s}" % (LaTeXName)) + ["\\end_layout"]
2376 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
2378 add_to_preamble(document, "\\theoremstyle{definition}")
2379 if is_starred or mod == "theorems-bytype" or mod == "theorems-ams-bytype":
2380 add_to_preamble(document, "\\%s{%s}{\\protect\\solutionname}" % \
2381 (theoremName, LaTeXName))
2382 else: # mod == "theorems-std" or mod == "theorems-ams" and not is_starred
2383 add_to_preamble(document, "\\%s{%s}[thm]{\\protect\\solutionname}" % \
2384 (theoremName, LaTeXName))
2386 add_to_preamble(document, "\\providecommand{\solutionname}{Solution}")
2390 def revert_verbatim_star(document):
2391 from lyx_2_1 import revert_verbatim
2392 revert_verbatim(document, True)
2395 def convert_save_props(document):
2396 " Add save_transient_properties parameter. "
2397 i = find_token(document.header, '\\begin_header', 0)
2399 document.warning("Malformed lyx document: Missing '\\begin_header'.")
2401 document.header.insert(i + 1, '\\save_transient_properties true')
2404 def revert_save_props(document):
2405 " Remove save_transient_properties parameter. "
2406 i = find_token(document.header, "\\save_transient_properties", 0)
2409 del document.header[i]
2412 def convert_info_tabular_feature(document):
2414 return arg.replace("inset-modify tabular", "tabular-feature")
2415 convert_info_insets(document, "shortcut(s)?|icon", f)
2418 def revert_info_tabular_feature(document):
2420 return arg.replace("tabular-feature", "inset-modify tabular")
2421 convert_info_insets(document, "shortcut(s)?|icon", f)
2428 supported_versions = ["2.2.0", "2.2"]
2430 [475, [convert_separator]],
2431 # nothing to do for 476: We consider it a bug that older versions
2432 # did not load amsmath automatically for these commands, and do not
2433 # want to hardcode amsmath off.
2439 [481, [convert_dashes]],
2440 [482, [convert_phrases]],
2441 [483, [convert_specialchar]],
2446 [488, [convert_newgloss]],
2447 [489, [convert_BoxFeatures]],
2448 [490, [convert_origin]],
2450 [492, [convert_colorbox]],
2453 [495, [convert_subref]],
2454 [496, [convert_nounzip]],
2455 [497, [convert_external_bbox]],
2457 [499, [convert_moderncv_phone, convert_moderncv_name]],
2459 [501, [convert_fontsettings]],
2462 [504, [convert_save_props]],
2464 [506, [convert_info_tabular_feature]],
2465 [507, [convert_longtable_label]],
2466 [508, [convert_parbreak]]
2470 [507, [revert_parbreak]],
2471 [506, [revert_longtable_label]],
2472 [505, [revert_info_tabular_feature]],
2474 [503, [revert_save_props]],
2475 [502, [revert_verbatim_star]],
2476 [501, [revert_solution]],
2477 [500, [revert_fontsettings]],
2478 [499, [revert_achemso]],
2479 [498, [revert_moderncv_1, revert_moderncv_2]],
2480 [497, [revert_tcolorbox_1, revert_tcolorbox_2,
2481 revert_tcolorbox_3, revert_tcolorbox_4, revert_tcolorbox_5,
2482 revert_tcolorbox_6, revert_tcolorbox_7, revert_tcolorbox_8]],
2483 [496, [revert_external_bbox]],
2484 [495, []], # nothing to do since the noUnzip parameter was optional
2485 [494, [revert_subref]],
2486 [493, [revert_jss]],
2487 [492, [revert_mathmulticol]],
2488 [491, [revert_colorbox]],
2489 [490, [revert_textcolor]],
2490 [489, [revert_origin]],
2491 [488, [revert_BoxFeatures]],
2492 [487, [revert_newgloss, revert_glossgroup]],
2493 [486, [revert_forest]],
2494 [485, [revert_ex_itemargs]],
2495 [484, [revert_sigplan_doi]],
2496 [483, [revert_georgian]],
2497 [482, [revert_specialchar]],
2498 [481, [revert_phrases]],
2499 [480, [revert_dashes]],
2500 [479, [revert_question_env]],
2501 [478, [revert_beamer_lemma]],
2502 [477, [revert_xarrow]],
2503 [476, [revert_swissgerman]],
2504 [475, [revert_smash]],
2505 [474, [revert_separator]]
2509 if __name__ == "__main__":