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 parser_tools import find_token, find_end_of, find_tokens, \
28 # find_token_exact, find_end_of_inset, find_end_of_layout, \
29 # find_token_backwards, is_in_inset, get_value, get_quoted_value, \
30 # del_token, check_token, get_option_value
32 from lyx2lyx_tools import add_to_preamble, put_cmd_in_ert, get_ert, lyx2latex, \
33 lyx2verbatim, length_in_bp, convert_info_insets
34 # insert_to_preamble, latex_length, revert_flex_inset, \
35 # revert_font_attrs, hex2ratio, str2bool
37 from parser_tools import find_token, find_token_backwards, find_re, \
38 find_end_of_inset, find_end_of_layout, find_nonempty_line, \
39 get_containing_layout, get_value, check_token
41 ####################################################################
42 # Private helper functions
44 def revert_Argument_to_TeX_brace(document, line, endline, n, nmax, environment, opt, nolastopt):
46 Reverts an InsetArgument to TeX-code
48 revert_Argument_to_TeX_brace(document, LineOfBegin, LineOfEnd, StartArgument, EndArgument, isEnvironment, isOpt, notLastOpt)
49 LineOfBegin is the line of the \begin_layout or \begin_inset statement
50 LineOfEnd is the line of the \end_layout or \end_inset statement, if "0" is given, the end of the file is used instead
51 StartArgument is the number of the first argument that needs to be converted
52 EndArgument is the number of the last argument that needs to be converted or the last defined one
53 isEnvironment must be true, if the layout is for a LaTeX environment
54 isOpt must be true, if the argument is an optional one
55 notLastOpt must be true if the argument is mandatory and followed by optional ones
59 while lineArg != -1 and n < nmax + 1:
60 lineArg = find_token(document.body, "\\begin_inset Argument " + str(n), line)
61 if lineArg > endline and endline != 0:
64 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
65 # we have to assure that no other inset is in the Argument
66 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
67 endInset = find_token(document.body, "\\end_inset", beginPlain)
70 while beginInset < endInset and beginInset != -1:
71 beginInset = find_token(document.body, "\\begin_inset", k)
72 endInset = find_token(document.body, "\\end_inset", l)
75 if environment == False:
77 if nolastopt == False:
78 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}{")
80 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}")
81 del(document.body[lineArg : beginPlain + 1])
84 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("]")
85 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("[")
89 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}")
90 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("{")
93 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("]")
94 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("[")
100 ###############################################################################
102 ### Conversion and reversion routines
104 ###############################################################################
106 def convert_longtable_label_internal(document, forward):
108 Convert reference to "LongTableNoNumber" into "Unnumbered" if forward is True
111 old_reference = "\\begin_inset Caption LongTableNoNumber"
112 new_reference = "\\begin_inset Caption Unnumbered"
114 # if the purpose is to revert swap the strings roles
116 old_reference, new_reference = new_reference, old_reference
120 i = find_token(document.body, old_reference, i)
125 document.body[i] = new_reference
128 def convert_longtable_label(document):
129 convert_longtable_label_internal(document, True)
132 def revert_longtable_label(document):
133 convert_longtable_label_internal(document, False)
136 def convert_separator(document):
138 Convert layout separators to separator insets and add (LaTeX) paragraph
139 breaks in order to mimic previous LaTeX export.
142 parins = ["\\begin_inset Separator parbreak", "\\end_inset", ""]
143 parlay = ["\\begin_layout Standard", "\\begin_inset Separator parbreak",
144 "\\end_inset", "", "\\end_layout", ""]
146 "family" : "default",
147 "series" : "default",
156 i = find_token(document.body, "\\begin_deeper", i)
160 j = find_token_backwards(document.body, "\\end_layout", i-1)
162 # reset any text style before inserting the inset
163 lay = get_containing_layout(document.body, j-1)
165 content = "\n".join(document.body[lay[1]:lay[2]])
166 for val in list(sty_dict.keys()):
167 if content.find("\\%s" % val) != -1:
168 document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
171 document.body[j:j] = parins
172 i = i + len(parins) + 1
178 i = find_token(document.body, "\\align", i)
182 lay = get_containing_layout(document.body, i)
183 if lay != False and lay[0] == "Plain Layout":
187 j = find_token_backwards(document.body, "\\end_layout", i-1)
189 lay = get_containing_layout(document.body, j-1)
190 if lay != False and lay[0] == "Standard" \
191 and find_token(document.body, "\\align", lay[1], lay[2]) == -1 \
192 and find_token(document.body, "\\begin_inset VSpace", lay[1], lay[2]) == -1:
193 # reset any text style before inserting the inset
194 content = "\n".join(document.body[lay[1]:lay[2]])
195 for val in list(sty_dict.keys()):
196 if content.find("\\%s" % val) != -1:
197 document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
200 document.body[j:j] = parins
201 i = i + len(parins) + 1
207 regexp = re.compile(r'^\\begin_layout (?:(-*)|(\s*))(Separator|EndOfSlide)(?:(-*)|(\s*))$', re.IGNORECASE)
211 i = find_re(document.body, regexp, i)
215 j = find_end_of_layout(document.body, i)
217 document.warning("Malformed LyX document: Missing `\\end_layout'.")
220 lay = get_containing_layout(document.body, j-1)
222 lines = document.body[lay[3]:lay[2]]
226 document.body[i:j+1] = parlay
228 document.body[i+1:i+1] = lines
230 i = i + len(parlay) + len(lines) + 1
233 def revert_separator(document):
234 " Revert separator insets to layout separators "
236 beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
237 if document.textclass in beamer_classes:
238 beglaysep = "\\begin_layout Separator"
240 beglaysep = "\\begin_layout --Separator--"
242 parsep = [beglaysep, "", "\\end_layout", ""]
243 comert = ["\\begin_inset ERT", "status collapsed", "",
244 "\\begin_layout Plain Layout", "%", "\\end_layout",
245 "", "\\end_inset", ""]
246 empert = ["\\begin_inset ERT", "status collapsed", "",
247 "\\begin_layout Plain Layout", " ", "\\end_layout",
248 "", "\\end_inset", ""]
252 i = find_token(document.body, "\\begin_inset Separator", i)
256 lay = get_containing_layout(document.body, i)
258 document.warning("Malformed LyX document: Can't convert separator inset at line " + str(i))
265 kind = get_value(document.body, "\\begin_inset Separator", i, i+1, "plain").split()[1]
266 before = document.body[beg+1:i]
267 something_before = len(before) > 0 and len("".join(before)) > 0
268 j = find_end_of_inset(document.body, i)
269 after = document.body[j+1:end]
270 something_after = len(after) > 0 and len("".join(after)) > 0
272 beg = beg + len(before) + 1
273 elif something_before:
274 document.body[i:i] = ["\\end_layout", ""]
282 document.body[beg:j+1] = empert
285 document.body[beg:j+1] = comert
289 if layoutname == "Standard":
290 if not something_before:
291 document.body[beg:j+1] = parsep
293 document.body[i:i] = ["", "\\begin_layout Standard"]
296 document.body[beg:j+1] = ["\\begin_layout Standard"]
299 document.body[beg:j+1] = ["\\begin_deeper"]
301 end = end + 1 - (j + 1 - beg)
302 if not something_before:
303 document.body[i:i] = parsep
305 end = end + len(parsep)
306 document.body[i:i] = ["\\begin_layout Standard"]
307 document.body[end+2:end+2] = ["", "\\end_deeper", ""]
310 next_par_is_aligned = False
311 k = find_nonempty_line(document.body, end+1)
312 if k != -1 and check_token(document.body[k], "\\begin_layout"):
313 lay = get_containing_layout(document.body, k)
314 next_par_is_aligned = lay != False and \
315 find_token(document.body, "\\align", lay[1], lay[2]) != -1
316 if k != -1 and not next_par_is_aligned \
317 and not check_token(document.body[k], "\\end_deeper") \
318 and not check_token(document.body[k], "\\begin_deeper"):
319 if layoutname == "Standard":
320 document.body[beg:j+1] = [beglaysep]
323 document.body[beg:j+1] = ["\\begin_deeper", beglaysep]
324 end = end + 2 - (j + 1 - beg)
325 document.body[end+1:end+1] = ["", "\\end_deeper", ""]
329 del document.body[i:end+1]
331 del document.body[i:end-1]
336 def convert_parbreak(document):
338 Convert parbreak separators not specifically used to separate
339 environments to latexpar separators.
341 parbreakinset = "\\begin_inset Separator parbreak"
344 i = find_token(document.body, parbreakinset, i)
347 lay = get_containing_layout(document.body, i)
349 document.warning("Malformed LyX document: Can't convert separator inset at line " + str(i))
352 if lay[0] == "Standard":
353 # Convert only if not alone in the paragraph
354 k1 = find_nonempty_line(document.body, lay[1] + 1, i + 1)
355 k2 = find_nonempty_line(document.body, i + 1, lay[2])
356 if (k1 < i) or (k2 > i + 1) or not check_token(document.body[i], parbreakinset):
357 document.body[i] = document.body[i].replace("parbreak", "latexpar")
359 document.body[i] = document.body[i].replace("parbreak", "latexpar")
363 def revert_parbreak(document):
365 Revert latexpar separators to parbreak separators.
369 i = find_token(document.body, "\\begin_inset Separator latexpar", i)
372 document.body[i] = document.body[i].replace("latexpar", "parbreak")
376 def revert_smash(document):
377 " Set amsmath to on if smash commands are used "
379 commands = ["smash[t]", "smash[b]", "notag"]
380 i = find_token(document.header, "\\use_package amsmath", 0)
382 document.warning("Malformed LyX document: Can't find \\use_package amsmath.")
384 value = get_value(document.header, "\\use_package amsmath", i).split()[1]
386 # nothing to do if package is not auto but on or off
390 j = find_token(document.body, '\\begin_inset Formula', j)
393 k = find_end_of_inset(document.body, j)
395 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(j))
398 code = "\n".join(document.body[j:k])
400 if code.find("\\%s" % c) != -1:
401 # set amsmath to on, since it is loaded by the newer format
402 document.header[i] = "\\use_package amsmath 2"
407 def revert_swissgerman(document):
408 " Set language german-ch-old to german "
410 if document.language == "german-ch-old":
411 document.language = "german"
412 i = find_token(document.header, "\\language", 0)
414 document.header[i] = "\\language german"
417 j = find_token(document.body, "\\lang german-ch-old", j)
420 document.body[j] = document.body[j].replace("\\lang german-ch-old", "\\lang german")
424 def revert_use_package(document, pkg, commands, oldauto, supported):
425 # oldauto defines how the version we are reverting to behaves:
426 # if it is true, the old version uses the package automatically.
427 # if it is false, the old version never uses the package.
428 # If "supported" is true, the target version also supports this
430 regexp = re.compile(r'(\\use_package\s+%s)' % pkg)
431 p = find_re(document.header, regexp, 0)
432 value = "1" # default is auto
434 value = get_value(document.header, "\\use_package" , p).split()[1]
436 del document.header[p]
437 if value == "2" and not supported: # on
438 add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
439 elif value == "1" and not oldauto: # auto
442 i = find_token(document.body, '\\begin_inset Formula', i)
445 j = find_end_of_inset(document.body, i)
447 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
450 code = "\n".join(document.body[i:j])
452 if code.find("\\%s" % c) != -1:
454 document.header[p] = "\\use_package " + pkg + " 2"
456 add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
461 mathtools_commands = ["xhookrightarrow", "xhookleftarrow", "xRightarrow", \
462 "xrightharpoondown", "xrightharpoonup", "xrightleftharpoons", \
463 "xLeftarrow", "xleftharpoondown", "xleftharpoonup", \
464 "xleftrightarrow", "xLeftrightarrow", "xleftrightharpoons", \
467 def revert_xarrow(document):
468 "remove use_package mathtools"
469 revert_use_package(document, "mathtools", mathtools_commands, False, True)
472 def revert_beamer_lemma(document):
473 " Reverts beamer lemma layout to ERT "
475 beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
476 if document.textclass not in beamer_classes:
482 i = find_token(document.body, "\\begin_layout Lemma", i)
485 j = find_end_of_layout(document.body, i)
487 document.warning("Malformed LyX document: Can't find end of Lemma layout")
490 arg1 = find_token(document.body, "\\begin_inset Argument 1", i, j)
491 endarg1 = find_end_of_inset(document.body, arg1)
492 arg2 = find_token(document.body, "\\begin_inset Argument 2", i, j)
493 endarg2 = find_end_of_inset(document.body, arg2)
497 beginPlain1 = find_token(document.body, "\\begin_layout Plain Layout", arg1, endarg1)
498 if beginPlain1 == -1:
499 document.warning("Malformed LyX document: Can't find arg1 plain Layout")
502 endPlain1 = find_end_of_inset(document.body, beginPlain1)
503 content1 = document.body[beginPlain1 + 1 : endPlain1 - 2]
504 subst1 = put_cmd_in_ert("<") + content1 + put_cmd_in_ert(">")
506 beginPlain2 = find_token(document.body, "\\begin_layout Plain Layout", arg2, endarg2)
507 if beginPlain2 == -1:
508 document.warning("Malformed LyX document: Can't find arg2 plain Layout")
511 endPlain2 = find_end_of_inset(document.body, beginPlain2)
512 content2 = document.body[beginPlain2 + 1 : endPlain2 - 2]
513 subst2 = put_cmd_in_ert("[") + content2 + put_cmd_in_ert("]")
517 del document.body[arg2 : endarg2 + 1]
519 del document.body[arg1 : endarg1 + 1]
521 del document.body[arg1 : endarg1 + 1]
523 del document.body[arg2 : endarg2 + 1]
525 # index of end layout has probably changed
526 j = find_end_of_layout(document.body, i)
528 document.warning("Malformed LyX document: Can't find end of Lemma layout")
534 # if this is not a consecutive env, add start command
536 begcmd = put_cmd_in_ert("\\begin{lemma}")
538 # has this a consecutive lemma?
539 consecutive = document.body[j + 2] == "\\begin_layout Lemma"
541 # if this is not followed by a consecutive env, add end command
543 document.body[j : j + 1] = put_cmd_in_ert("\\end{lemma}") + ["\\end_layout"]
545 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd + subst1 + subst2
551 def revert_question_env(document):
553 Reverts question and question* environments of
554 theorems-ams-extended-bytype module to ERT
557 # Do we use theorems-ams-extended-bytype module?
558 if not "theorems-ams-extended-bytype" in document.get_module_list():
564 i = find_token(document.body, "\\begin_layout Question", i)
568 starred = document.body[i] == "\\begin_layout Question*"
570 j = find_end_of_layout(document.body, i)
572 document.warning("Malformed LyX document: Can't find end of Question layout")
576 # if this is not a consecutive env, add start command
580 begcmd = put_cmd_in_ert("\\begin{question*}")
582 begcmd = put_cmd_in_ert("\\begin{question}")
584 # has this a consecutive theorem of same type?
587 consecutive = document.body[j + 2] == "\\begin_layout Question*"
589 consecutive = document.body[j + 2] == "\\begin_layout Question"
591 # if this is not followed by a consecutive env, add end command
594 document.body[j : j + 1] = put_cmd_in_ert("\\end{question*}") + ["\\end_layout"]
596 document.body[j : j + 1] = put_cmd_in_ert("\\end{question}") + ["\\end_layout"]
598 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
600 add_to_preamble(document, "\\providecommand{\questionname}{Question}")
603 add_to_preamble(document, "\\theoremstyle{plain}\n" \
604 "\\newtheorem*{question*}{\\protect\\questionname}")
606 add_to_preamble(document, "\\theoremstyle{plain}\n" \
607 "\\newtheorem{question}{\\protect\\questionname}")
612 def convert_dashes(document):
613 "convert -- and --- to \\twohyphens and \\threehyphens"
615 if document.backend != "latex":
619 while i < len(document.body):
620 words = document.body[i].split()
621 if (len(words) > 1 and words[0] == "\\begin_inset"
622 and (words[1] in ["CommandInset", "ERT", "External", "Formula",
623 "FormulaMacro", "Graphics", "IPA", "listings"]
624 or ' '.join(words[1:]) == "Flex Code")):
625 # must not replace anything in insets that store LaTeX contents in .lyx files
626 # (math and command insets without overridden read() and write() methods
627 # filtering out IPA makes Text::readParToken() more simple
628 # skip ERT as well since it is not needed there
629 # Flex Code is logical markup, typically rendered as typewriter
630 j = find_end_of_inset(document.body, i)
632 document.warning("Malformed LyX document: Can't find end of " + words[1] + " inset at line " + str(i))
637 if document.body[i] == "\\begin_layout LyX-Code":
638 j = find_end_of_layout(document.body, i)
640 document.warning("Malformed LyX document: "
641 "Can't find end of %s layout at line %d" % (words[1],i))
647 if len(words) > 0 and words[0] in ["\\leftindent", "\\paragraph_spacing", "\\align", "\\labelwidthstring"]:
648 # skip paragraph parameters (bug 10243)
652 j = document.body[i].find("--")
655 front = document.body[i][:j]
656 back = document.body[i][j+2:]
657 # We can have an arbitrary number of consecutive hyphens.
658 # These must be split into the corresponding number of two and three hyphens
659 # We must match what LaTeX does: First try emdash, then endash, then single hyphen
660 if back.find("-") == 0:
663 document.body.insert(i+1, back)
664 document.body[i] = front + "\\threehyphens"
667 document.body.insert(i+1, back)
668 document.body[i] = front + "\\twohyphens"
672 def revert_dashes(document):
673 "convert \\twohyphens and \\threehyphens to -- and ---"
675 # eventually remove preamble code from 2.3->2.2 conversion:
676 for i, line in enumerate(document.preamble):
677 if i > 1 and line == r'\renewcommand{\textemdash}{---}':
678 if (document.preamble[i-1] == r'\renewcommand{\textendash}{--}'
679 and document.preamble[i-2] == '% Added by lyx2lyx'):
680 del document.preamble[i-2:i+1]
682 while i < len(document.body):
683 words = document.body[i].split()
684 if len(words) > 1 and words[0] == "\\begin_inset" and \
685 words[1] in ["CommandInset", "ERT", "External", "Formula", "Graphics", "IPA", "listings"]:
687 j = find_end_of_inset(document.body, i)
689 document.warning("Malformed LyX document: Can't find end of " + words[1] + " inset at line " + str(i))
695 if document.body[i].find("\\twohyphens") >= 0:
696 document.body[i] = document.body[i].replace("\\twohyphens", "--")
698 if document.body[i].find("\\threehyphens") >= 0:
699 document.body[i] = document.body[i].replace("\\threehyphens", "---")
701 if replaced and i+1 < len(document.body) and \
702 (document.body[i+1].find("\\") != 0 or \
703 document.body[i+1].find("\\twohyphens") == 0 or
704 document.body[i+1].find("\\threehyphens") == 0) and \
705 len(document.body[i]) + len(document.body[i+1]) <= 80:
706 document.body[i] = document.body[i] + document.body[i+1]
707 document.body[i+1:i+2] = []
712 # order is important for the last three!
713 phrases = ["LyX", "LaTeX2e", "LaTeX", "TeX"]
715 def is_part_of_converted_phrase(line, j, phrase):
716 "is phrase part of an already converted phrase?"
718 converted = "\\SpecialCharNoPassThru \\" + p
719 pos = j + len(phrase) - len(converted)
721 if line[pos:pos+len(converted)] == converted:
726 def convert_phrases(document):
727 "convert special phrases from plain text to \\SpecialCharNoPassThru"
729 if document.backend != "latex":
732 for phrase in phrases:
734 while i < len(document.body):
735 words = document.body[i].split()
736 if len(words) > 1 and words[0] == "\\begin_inset" and \
737 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
738 # must not replace anything in insets that store LaTeX contents in .lyx files
739 # (math and command insets withut overridden read() and write() methods
740 j = find_end_of_inset(document.body, i)
742 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
747 if document.body[i].find("\\") == 0:
750 j = document.body[i].find(phrase)
754 if not is_part_of_converted_phrase(document.body[i], j, phrase):
755 front = document.body[i][:j]
756 back = document.body[i][j+len(phrase):]
758 document.body.insert(i+1, back)
759 # We cannot use SpecialChar since we do not know whether we are outside passThru
760 document.body[i] = front + "\\SpecialCharNoPassThru \\" + phrase
764 def revert_phrases(document):
765 "convert special phrases to plain text"
768 while i < len(document.body):
769 words = document.body[i].split()
770 if len(words) > 1 and words[0] == "\\begin_inset" and \
771 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
772 # see convert_phrases
773 j = find_end_of_inset(document.body, i)
775 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
781 for phrase in phrases:
782 # we can replace SpecialChar since LyX ensures that it cannot be inserted into passThru parts
783 if document.body[i].find("\\SpecialChar \\" + phrase) >= 0:
784 document.body[i] = document.body[i].replace("\\SpecialChar \\" + phrase, phrase)
786 if document.body[i].find("\\SpecialCharNoPassThru \\" + phrase) >= 0:
787 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru \\" + phrase, phrase)
789 if replaced and i+1 < len(document.body) and \
790 (document.body[i+1].find("\\") != 0 or \
791 document.body[i+1].find("\\SpecialChar") == 0) and \
792 len(document.body[i]) + len(document.body[i+1]) <= 80:
793 document.body[i] = document.body[i] + document.body[i+1]
794 document.body[i+1:i+2] = []
799 def convert_specialchar_internal(document, forward):
800 specialchars = {"\\-":"softhyphen", "\\textcompwordmark{}":"ligaturebreak", \
801 "\\@.":"endofsentence", "\\ldots{}":"ldots", \
802 "\\menuseparator":"menuseparator", "\\slash{}":"breakableslash", \
803 "\\nobreakdash-":"nobreakdash", "\\LyX":"LyX", \
804 "\\TeX":"TeX", "\\LaTeX2e":"LaTeX2e", \
805 "\\LaTeX":"LaTeX" # must be after LaTeX2e
809 while i < len(document.body):
810 words = document.body[i].split()
811 if len(words) > 1 and words[0] == "\\begin_inset" and \
812 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
813 # see convert_phrases
814 j = find_end_of_inset(document.body, i)
816 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
821 for key, value in specialchars.items():
823 document.body[i] = document.body[i].replace("\\SpecialChar " + key, "\\SpecialChar " + value)
824 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + key, "\\SpecialCharNoPassThru " + value)
826 document.body[i] = document.body[i].replace("\\SpecialChar " + value, "\\SpecialChar " + key)
827 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + value, "\\SpecialCharNoPassThru " + key)
831 def convert_specialchar(document):
832 "convert special characters to new syntax"
833 convert_specialchar_internal(document, True)
836 def revert_specialchar(document):
837 "convert special characters to old syntax"
838 convert_specialchar_internal(document, False)
841 def revert_georgian(document):
842 "Set the document language to English but assure Georgian output"
844 if document.language == "georgian":
845 document.language = "english"
846 i = find_token(document.header, "\\language georgian", 0)
848 document.header[i] = "\\language english"
849 j = find_token(document.header, "\\language_package default", 0)
851 document.header[j] = "\\language_package babel"
852 k = find_token(document.header, "\\options", 0)
854 document.header[k] = document.header[k].replace("\\options", "\\options georgian,")
856 l = find_token(document.header, "\\use_default_options", 0)
857 document.header.insert(l + 1, "\\options georgian")
860 def revert_sigplan_doi(document):
861 " Reverts sigplanconf DOI layout to ERT "
863 if document.textclass != "sigplanconf":
868 i = find_token(document.body, "\\begin_layout DOI", i)
871 j = find_end_of_layout(document.body, i)
873 document.warning("Malformed LyX document: Can't find end of DOI layout")
877 content = lyx2latex(document, document.body[i:j + 1])
878 add_to_preamble(document, ["\\doi{" + content + "}"])
879 del document.body[i:j + 1]
883 def revert_ex_itemargs(document):
884 " Reverts \\item arguments of the example environments (Linguistics module) to TeX-code "
886 if not "linguistics" in document.get_module_list():
890 example_layouts = ["Numbered Examples (consecutive)", "Subexample"]
892 i = find_token(document.body, "\\begin_inset Argument item:", i)
895 j = find_end_of_inset(document.body, i)
896 # Find containing paragraph layout
897 parent = get_containing_layout(document.body, i)
899 document.warning("Malformed LyX document: Can't find parent paragraph layout")
903 layoutname = parent[0]
904 if layoutname in example_layouts:
905 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
906 endPlain = find_end_of_layout(document.body, beginPlain)
907 content = document.body[beginPlain + 1 : endPlain]
908 del document.body[i:j+1]
909 subst = put_cmd_in_ert("[") + content + put_cmd_in_ert("]")
910 document.body[parbeg : parbeg] = subst
914 def revert_forest(document):
915 " Reverts the forest environment (Linguistics module) to TeX-code "
917 if not "linguistics" in document.get_module_list():
922 i = find_token(document.body, "\\begin_inset Flex Structure Tree", i)
925 j = find_end_of_inset(document.body, i)
927 document.warning("Malformed LyX document: Can't find end of Structure Tree inset")
931 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
932 endPlain = find_end_of_layout(document.body, beginPlain)
933 content = lyx2latex(document, document.body[beginPlain : endPlain])
935 add_to_preamble(document, ["\\usepackage{forest}"])
937 document.body[i:j + 1] = put_cmd_in_ert("\\begin{forest}" + content + "\\end{forest}")
941 def revert_glossgroup(document):
942 " Reverts the GroupGlossedWords inset (Linguistics module) to TeX-code "
944 if not "linguistics" in document.get_module_list():
949 i = find_token(document.body, "\\begin_inset Flex GroupGlossedWords", i)
952 j = find_end_of_inset(document.body, i)
954 document.warning("Malformed LyX document: Can't find end of GroupGlossedWords inset")
958 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
959 endPlain = find_end_of_layout(document.body, beginPlain)
960 content = lyx2verbatim(document, document.body[beginPlain : endPlain])
962 document.body[i:j + 1] = ["{", "", content, "", "}"]
966 def revert_newgloss(document):
967 " Reverts the new Glosse insets (Linguistics module) to the old format "
969 if not "linguistics" in document.get_module_list():
972 glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
973 for glosse in glosses:
976 i = find_token(document.body, glosse, i)
979 j = find_end_of_inset(document.body, i)
981 document.warning("Malformed LyX document: Can't find end of Glosse inset")
985 arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
986 endarg = find_end_of_inset(document.body, arg)
989 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
990 if argbeginPlain == -1:
991 document.warning("Malformed LyX document: Can't find arg plain Layout")
994 argendPlain = find_end_of_inset(document.body, argbeginPlain)
995 argcontent = lyx2verbatim(document, document.body[argbeginPlain : argendPlain - 2])
997 document.body[j:j] = ["", "\\begin_layout Plain Layout","\\backslash", "glt ",
998 argcontent, "\\end_layout"]
1000 # remove Arg insets and paragraph, if it only contains this inset
1001 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
1002 del document.body[arg - 1 : endarg + 4]
1004 del document.body[arg : endarg + 1]
1006 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1007 endPlain = find_end_of_layout(document.body, beginPlain)
1008 content = lyx2verbatim(document, document.body[beginPlain : endPlain])
1010 document.body[beginPlain + 1:endPlain] = [content]
1013 # Dissolve ERT insets
1014 for glosse in glosses:
1017 i = find_token(document.body, glosse, i)
1020 j = find_end_of_inset(document.body, i)
1022 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1026 ert = find_token(document.body, "\\begin_inset ERT", i, j)
1029 ertend = find_end_of_inset(document.body, ert)
1031 document.warning("Malformed LyX document: Can't find end of ERT inset")
1034 ertcontent = get_ert(document.body, ert, True)
1035 document.body[ert : ertend + 1] = [ertcontent]
1039 def convert_newgloss(document):
1040 " Converts Glosse insets (Linguistics module) to the new format "
1042 if not "linguistics" in document.get_module_list():
1045 glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
1046 for glosse in glosses:
1049 i = find_token(document.body, glosse, i)
1052 j = find_end_of_inset(document.body, i)
1054 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1061 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", k, j)
1062 if beginPlain == -1:
1064 endPlain = find_end_of_layout(document.body, beginPlain)
1066 document.warning("Malformed LyX document: Can't find end of Glosse layout")
1070 glt = find_token(document.body, "\\backslash", beginPlain, endPlain)
1071 if glt != -1 and document.body[glt + 1].startswith("glt"):
1072 document.body[glt + 1] = document.body[glt + 1].lstrip("glt").lstrip()
1073 argcontent = document.body[glt + 1 : endPlain]
1074 document.body[beginPlain + 1 : endPlain] = ["\\begin_inset Argument 1", "status open", "",
1075 "\\begin_layout Plain Layout", "\\begin_inset ERT", "status open", "",
1076 "\\begin_layout Plain Layout", ""] + argcontent + ["\\end_layout", "", "\\end_inset", "",
1077 "\\end_layout", "", "\\end_inset"]
1079 content = document.body[beginPlain + 1 : endPlain]
1080 document.body[beginPlain + 1 : endPlain] = ["\\begin_inset ERT", "status open", "",
1081 "\\begin_layout Plain Layout"] + content + ["\\end_layout", "", "\\end_inset"]
1083 endPlain = find_end_of_layout(document.body, beginPlain)
1085 j = find_end_of_inset(document.body, i)
1090 def convert_BoxFeatures(document):
1091 " adds new box features "
1095 i = find_token(document.body, "height_special", i)
1098 document.body[i+1:i+1] = ['thickness "0.4pt"', 'separation "3pt"', 'shadowsize "4pt"']
1102 def revert_BoxFeatures(document):
1103 " outputs new box features as TeX code "
1107 defaultThick = "0.4pt"
1108 defaultShadow = "4pt"
1110 i = find_token(document.body, "thickness", i)
1113 binset = find_token(document.body, "\\begin_inset Box", i - 11)
1114 if binset == -1 or binset != i - 11:
1116 continue # then "thickness" is is just a word in the text
1117 einset = find_end_of_inset(document.body, binset)
1119 document.warning("Malformed LyX document: Can't find end of box inset!")
1122 # read out the values
1123 beg = document.body[i].find('"');
1124 end = document.body[i].rfind('"');
1125 thickness = document.body[i][beg+1:end];
1126 beg = document.body[i+1].find('"');
1127 end = document.body[i+1].rfind('"');
1128 separation = document.body[i+1][beg+1:end];
1129 beg = document.body[i+2].find('"');
1130 end = document.body[i+2].rfind('"');
1131 shadowsize = document.body[i+2][beg+1:end];
1132 # delete the specification
1133 del document.body[i:i+3]
1135 # first output the closing brace
1136 if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1137 document.body[einset -1 : einset - 1] = put_cmd_in_ert("}")
1138 # we have now the problem that if there is already \(f)colorbox in ERT around the inset
1139 # the ERT from this routine must be around it
1140 regexp = re.compile(r'^.*colorbox{.*$')
1141 pos = find_re(document.body, regexp, binset - 4)
1142 if pos != -1 and pos == binset - 4:
1146 # now output the lengths
1147 if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1148 document.body[pos : pos] = put_cmd_in_ert("{")
1149 if thickness != defaultThick:
1150 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness]
1151 if separation != defaultSep and thickness == defaultThick:
1152 document.body[pos + 5 : pos +6] = ["{\\backslash fboxsep " + separation]
1153 if separation != defaultSep and thickness != defaultThick:
1154 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation]
1155 if shadowsize != defaultShadow and separation == defaultSep and thickness == defaultThick:
1156 document.body[pos + 5 : pos +6] = ["{\\backslash shadowsize " + shadowsize]
1157 if shadowsize != defaultShadow and separation != defaultSep and thickness == defaultThick:
1158 document.body[pos + 5 : pos +6] = ["{\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1159 if shadowsize != defaultShadow and separation == defaultSep and thickness != defaultThick:
1160 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash shadowsize " + shadowsize]
1161 if shadowsize != defaultShadow and separation != defaultSep and thickness != defaultThick:
1162 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1165 def convert_origin(document):
1166 " Insert the origin tag "
1168 i = find_token(document.header, "\\textclass ", 0)
1170 document.warning("Malformed LyX document: No \\textclass!!")
1172 if document.dir == u'':
1176 if document.systemlyxdir and document.systemlyxdir != u'':
1178 if os.path.isabs(document.dir):
1179 absdir = os.path.normpath(document.dir)
1181 absdir = os.path.normpath(os.path.abspath(document.dir))
1182 if os.path.isabs(document.systemlyxdir):
1183 abssys = os.path.normpath(document.systemlyxdir)
1185 abssys = os.path.normpath(os.path.abspath(document.systemlyxdir))
1186 relpath = os.path.relpath(absdir, abssys)
1187 if relpath.find(u'..') == 0:
1192 origin = document.dir.replace(u'\\', u'/') + u'/'
1194 origin = os.path.join(u"/systemlyxdir", relpath).replace(u'\\', u'/') + u'/'
1195 document.header[i:i] = ["\\origin " + origin]
1198 def revert_origin(document):
1199 " Remove the origin tag "
1201 i = find_token(document.header, "\\origin ", 0)
1203 document.warning("Malformed LyX document: No \\origin!!")
1205 del document.header[i]
1208 color_names = ["brown", "darkgray", "gray", \
1209 "lightgray", "lime", "olive", "orange", \
1210 "pink", "purple", "teal", "violet"]
1212 def revert_textcolor(document):
1213 " revert new \\textcolor colors to TeX code "
1219 i = find_token(document.body, "\\color ", i)
1223 for color in list(color_names):
1224 if document.body[i] == "\\color " + color:
1225 # register that xcolor must be loaded in the preamble
1228 add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"])
1229 # find the next \\color and/or the next \\end_layout
1230 j = find_token(document.body, "\\color", i + 1)
1231 k = find_token(document.body, "\\end_layout", i + 1)
1232 if j == -1 and k != -1:
1235 # first output the closing brace
1237 document.body[k: k] = put_cmd_in_ert("}")
1239 document.body[j: j] = put_cmd_in_ert("}")
1240 # now output the \textcolor command
1241 document.body[i : i + 1] = put_cmd_in_ert("\\textcolor{" + color + "}{")
1245 def convert_colorbox(document):
1246 " adds color settings for boxes "
1250 i = find_token(document.body, "shadowsize", i)
1253 document.body[i+1:i+1] = ['framecolor "black"', 'backgroundcolor "none"']
1257 def revert_colorbox(document):
1258 " outputs color settings for boxes as TeX code "
1261 defaultframecolor = "black"
1262 defaultbackcolor = "none"
1264 i = find_token(document.body, "framecolor", i)
1267 binset = find_token(document.body, "\\begin_inset Box", i - 14)
1270 einset = find_end_of_inset(document.body, binset)
1272 document.warning("Malformed LyX document: Can't find end of box inset!")
1274 # read out the values
1275 beg = document.body[i].find('"');
1276 end = document.body[i].rfind('"');
1277 framecolor = document.body[i][beg+1:end];
1278 beg = document.body[i + 1].find('"');
1279 end = document.body[i + 1].rfind('"');
1280 backcolor = document.body[i+1][beg+1:end];
1281 # delete the specification
1282 del document.body[i:i + 2]
1284 # first output the closing brace
1285 if framecolor != defaultframecolor or backcolor != defaultbackcolor:
1286 add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"])
1287 document.body[einset : einset] = put_cmd_in_ert("}")
1288 # determine the box type
1289 isBox = find_token(document.body, "\\begin_inset Box Boxed", binset)
1290 # now output the box commands
1291 if (framecolor != defaultframecolor and isBox == binset) or (backcolor != defaultbackcolor and isBox == binset):
1292 document.body[i - 14 : i - 14] = put_cmd_in_ert("\\fcolorbox{" + framecolor + "}{" + backcolor + "}{")
1293 # in the case we must also change the box type because the ERT code adds a frame
1294 document.body[i - 4] = "\\begin_inset Box Frameless"
1295 # if has_inner_box 0 we must set it and use_makebox to 1
1296 ibox = find_token(document.body, "has_inner_box", i - 4)
1297 if ibox == -1 or ibox != i - 1:
1298 document.warning("Malformed LyX document: Can't find has_inner_box statement!")
1300 # read out the value
1301 innerbox = document.body[ibox][-1:];
1303 document.body[ibox] = "has_inner_box 1"
1304 document.body[ibox + 3] = "use_makebox 1"
1305 if backcolor != defaultbackcolor and isBox != binset:
1306 document.body[i - 14 : i - 14] = put_cmd_in_ert("\\colorbox{" + backcolor + "}{")
1309 def revert_mathmulticol(document):
1310 " Convert formulas to ERT if they contain multicolumns "
1314 i = find_token(document.body, '\\begin_inset Formula', i)
1317 j = find_end_of_inset(document.body, i)
1319 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
1322 lines = document.body[i:j]
1323 lines[0] = lines[0].replace('\\begin_inset Formula', '').lstrip()
1324 code = "\n".join(lines)
1329 n = code.find("\\multicolumn", k)
1330 # no need to convert degenerated multicolumn cells,
1331 # they work in old LyX versions as "math ERT"
1332 if n != -1 and code.find("\\multicolumn{1}", k) != n:
1333 ert = put_cmd_in_ert(code)
1334 document.body[i:j+1] = ert
1340 i = find_end_of_inset(document.body, i)
1345 def revert_jss(document):
1346 " Reverts JSS In_Preamble commands to ERT in preamble "
1348 if document.textclass != "jss":
1357 # at first revert the inset layouts because they can be part of the In_Preamble layouts
1358 while m != -1 or j != -1 or h != -1 or k != -1 or n != -1:
1361 h = find_token(document.body, "\\begin_inset Flex Pkg", h)
1363 endh = find_end_of_inset(document.body, h)
1364 document.body[endh - 2 : endh + 1] = put_cmd_in_ert("}")
1365 document.body[h : h + 4] = put_cmd_in_ert("\\pkg{")
1369 m = find_token(document.body, "\\begin_inset Flex Proglang", m)
1371 endm = find_end_of_inset(document.body, m)
1372 document.body[endm - 2 : endm + 1] = put_cmd_in_ert("}")
1373 document.body[m : m + 4] = put_cmd_in_ert("\\proglang{")
1377 j = find_token(document.body, "\\begin_inset Flex Code", j)
1379 # assure that we are not in a Code Chunk inset
1380 if document.body[j][-1] == "e":
1381 endj = find_end_of_inset(document.body, j)
1382 document.body[endj - 2 : endj + 1] = put_cmd_in_ert("}")
1383 document.body[j : j + 4] = put_cmd_in_ert("\\code{")
1389 k = find_token(document.body, "\\begin_inset Flex E-mail", k)
1391 endk = find_end_of_inset(document.body, k)
1392 document.body[endk - 2 : endk + 1] = put_cmd_in_ert("}")
1393 document.body[k : k + 4] = put_cmd_in_ert("\\email{")
1397 n = find_token(document.body, "\\begin_inset Flex URL", n)
1399 endn = find_end_of_inset(document.body, n)
1400 document.body[endn - 2 : endn + 1] = put_cmd_in_ert("}")
1401 document.body[n : n + 4] = put_cmd_in_ert("\\url{")
1403 # now revert the In_Preamble layouts
1405 i = find_token(document.body, "\\begin_layout Title", 0)
1408 j = find_end_of_layout(document.body, i)
1410 document.warning("Malformed LyX document: Can't find end of Title layout")
1413 content = lyx2latex(document, document.body[i:j + 1])
1414 add_to_preamble(document, ["\\title{" + content + "}"])
1415 del document.body[i:j + 1]
1417 i = find_token(document.body, "\\begin_layout Author", 0)
1420 j = find_end_of_layout(document.body, i)
1422 document.warning("Malformed LyX document: Can't find end of Author layout")
1425 content = lyx2latex(document, document.body[i:j + 1])
1426 add_to_preamble(document, ["\\author{" + content + "}"])
1427 del document.body[i:j + 1]
1429 i = find_token(document.body, "\\begin_layout Plain Author", 0)
1432 j = find_end_of_layout(document.body, i)
1434 document.warning("Malformed LyX document: Can't find end of Plain Author layout")
1437 content = lyx2latex(document, document.body[i:j + 1])
1438 add_to_preamble(document, ["\\Plainauthor{" + content + "}"])
1439 del document.body[i:j + 1]
1441 i = find_token(document.body, "\\begin_layout Plain Title", 0)
1444 j = find_end_of_layout(document.body, i)
1446 document.warning("Malformed LyX document: Can't find end of Plain Title layout")
1449 content = lyx2latex(document, document.body[i:j + 1])
1450 add_to_preamble(document, ["\\Plaintitle{" + content + "}"])
1451 del document.body[i:j + 1]
1453 i = find_token(document.body, "\\begin_layout Short Title", 0)
1456 j = find_end_of_layout(document.body, i)
1458 document.warning("Malformed LyX document: Can't find end of Short Title layout")
1461 content = lyx2latex(document, document.body[i:j + 1])
1462 add_to_preamble(document, ["\\Shorttitle{" + content + "}"])
1463 del document.body[i:j + 1]
1465 i = find_token(document.body, "\\begin_layout Abstract", 0)
1468 j = find_end_of_layout(document.body, i)
1470 document.warning("Malformed LyX document: Can't find end of Abstract layout")
1473 content = lyx2latex(document, document.body[i:j + 1])
1474 add_to_preamble(document, ["\\Abstract{" + content + "}"])
1475 del document.body[i:j + 1]
1477 i = find_token(document.body, "\\begin_layout Keywords", 0)
1480 j = find_end_of_layout(document.body, i)
1482 document.warning("Malformed LyX document: Can't find end of Keywords layout")
1485 content = lyx2latex(document, document.body[i:j + 1])
1486 add_to_preamble(document, ["\\Keywords{" + content + "}"])
1487 del document.body[i:j + 1]
1489 i = find_token(document.body, "\\begin_layout Plain Keywords", 0)
1492 j = find_end_of_layout(document.body, i)
1494 document.warning("Malformed LyX document: Can't find end of Plain Keywords layout")
1497 content = lyx2latex(document, document.body[i:j + 1])
1498 add_to_preamble(document, ["\\Plainkeywords{" + content + "}"])
1499 del document.body[i:j + 1]
1501 i = find_token(document.body, "\\begin_layout Address", 0)
1504 j = find_end_of_layout(document.body, i)
1506 document.warning("Malformed LyX document: Can't find end of Address layout")
1509 content = lyx2latex(document, document.body[i:j + 1])
1510 add_to_preamble(document, ["\\Address{" + content + "}"])
1511 del document.body[i:j + 1]
1512 # finally handle the code layouts
1517 while m != -1 or j != -1 or h != -1 or k != -1:
1520 h = find_token(document.body, "\\begin_inset Flex Code Chunk", h)
1522 endh = find_end_of_inset(document.body, h)
1523 document.body[endh : endh + 1] = put_cmd_in_ert("\\end{CodeChunk}")
1524 document.body[h : h + 3] = put_cmd_in_ert("\\begin{CodeChunk}")
1525 document.body[h - 1 : h] = ["\\begin_layout Standard"]
1529 j = find_token(document.body, "\\begin_layout Code Input", j)
1531 endj = find_end_of_layout(document.body, j)
1532 document.body[endj : endj + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1533 document.body[endj + 3 : endj + 4] = put_cmd_in_ert("\\end{CodeInput}")
1534 document.body[endj + 13 : endj + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1535 document.body[j + 1 : j] = ["\\end_layout", "", "\\begin_layout Standard"]
1536 document.body[j : j + 1] = put_cmd_in_ert("\\begin{CodeInput}")
1540 k = find_token(document.body, "\\begin_layout Code Output", k)
1542 endk = find_end_of_layout(document.body, k)
1543 document.body[endk : endk + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1544 document.body[endk + 3 : endk + 4] = put_cmd_in_ert("\\end{CodeOutput}")
1545 document.body[endk + 13 : endk + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1546 document.body[k + 1 : k] = ["\\end_layout", "", "\\begin_layout Standard"]
1547 document.body[k : k + 1] = put_cmd_in_ert("\\begin{CodeOutput}")
1551 m = find_token(document.body, "\\begin_layout Code", m)
1553 endm = find_end_of_layout(document.body, m)
1554 document.body[endm : endm + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1555 document.body[endm + 3 : endm + 4] = put_cmd_in_ert("\\end{Code}")
1556 document.body[endm + 13 : endm + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1557 document.body[m + 1 : m] = ["\\end_layout", "", "\\begin_layout Standard"]
1558 document.body[m : m + 1] = put_cmd_in_ert("\\begin{Code}")
1562 def convert_subref(document):
1563 " converts sub: ref prefixes to subref: "
1566 rx = re.compile(r'^name \"sub:(.+)$')
1569 i = find_token(document.body, "\\begin_inset CommandInset label", i)
1572 j = find_end_of_inset(document.body, i)
1574 document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1578 for p in range(i, j):
1579 m = rx.match(document.body[p])
1582 document.body[p] = "name \"subsec:" + label
1586 rx = re.compile(r'^reference \"sub:(.+)$')
1589 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1592 j = find_end_of_inset(document.body, i)
1594 document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1598 for p in range(i, j):
1599 m = rx.match(document.body[p])
1602 document.body[p] = "reference \"subsec:" + label
1608 def revert_subref(document):
1609 " reverts subref: ref prefixes to sub: "
1612 rx = re.compile(r'^name \"subsec:(.+)$')
1615 i = find_token(document.body, "\\begin_inset CommandInset label", i)
1618 j = find_end_of_inset(document.body, i)
1620 document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1624 for p in range(i, j):
1625 m = rx.match(document.body[p])
1628 document.body[p] = "name \"sub:" + label
1633 rx = re.compile(r'^reference \"subsec:(.+)$')
1636 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1639 j = find_end_of_inset(document.body, i)
1641 document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1645 for p in range(i, j):
1646 m = rx.match(document.body[p])
1649 document.body[p] = "reference \"sub:" + label
1654 def convert_nounzip(document):
1655 " remove the noUnzip parameter of graphics insets "
1657 rx = re.compile(r'\s*noUnzip\s*$')
1660 i = find_token(document.body, "\\begin_inset Graphics", i)
1663 j = find_end_of_inset(document.body, i)
1665 document.warning("Malformed LyX document: Can't find end of graphics inset at line " + str(i))
1669 k = find_re(document.body, rx, i, j)
1671 del document.body[k]
1676 def convert_revert_external_bbox(document, forward):
1677 " add units to bounding box of external insets "
1679 rx = re.compile(r'^\s*boundingBox\s+\S+\s+\S+\s+\S+\s+\S+\s*$')
1682 i = find_token(document.body, "\\begin_inset External", i)
1685 j = find_end_of_inset(document.body, i)
1687 document.warning("Malformed LyX document: Can't find end of external inset at line " + str(i))
1690 k = find_re(document.body, rx, i, j)
1694 tokens = document.body[k].split()
1696 for t in range(1, 5):
1699 for t in range(1, 5):
1700 tokens[t] = length_in_bp(tokens[t])
1701 document.body[k] = "\tboundingBox " + tokens[1] + " " + tokens[2] + " " + \
1702 tokens[3] + " " + tokens[4]
1706 def convert_external_bbox(document):
1707 convert_revert_external_bbox(document, True)
1710 def revert_external_bbox(document):
1711 convert_revert_external_bbox(document, False)
1714 def revert_tcolorbox_1(document):
1715 " Reverts the Flex:Subtitle inset of tcolorbox to TeX-code "
1718 i = find_token(document.header, "tcolorbox", i)
1724 flex = find_token(document.body, "\\begin_inset Flex Subtitle", flex)
1727 flexEnd = find_end_of_inset(document.body, flex)
1728 wasOpt = revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, False, True, False)
1729 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, False, False, False)
1730 flexEnd = find_end_of_inset(document.body, flex)
1732 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\tcbsubtitle")
1734 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\tcbsubtitle{")
1735 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("}")
1739 def revert_tcolorbox_2(document):
1740 " Reverts the Flex:Raster_Color_Box inset of tcolorbox to TeX-code "
1743 i = find_token(document.header, "tcolorbox", i)
1749 flex = find_token(document.body, "\\begin_inset Flex Raster Color Box", flex)
1752 flexEnd = find_end_of_inset(document.body, flex)
1753 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1754 flexEnd = find_end_of_inset(document.body, flex)
1755 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{tcbraster}")
1756 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("\\end{tcbraster}")
1760 def revert_tcolorbox_3(document):
1761 " Reverts the Flex:Custom_Color_Box_1 inset of tcolorbox to TeX-code "
1764 i = find_token(document.header, "tcolorbox", i)
1770 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 1", flex)
1773 flexEnd = find_end_of_inset(document.body, flex)
1774 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1775 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1776 flexEnd = find_end_of_inset(document.body, flex)
1777 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxA}")
1778 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxA}")
1782 def revert_tcolorbox_4(document):
1783 " Reverts the Flex:Custom_Color_Box_2 inset of tcolorbox to TeX-code "
1786 i = find_token(document.header, "tcolorbox", i)
1792 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 2", flex)
1795 flexEnd = find_end_of_inset(document.body, flex)
1796 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1797 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1798 flexEnd = find_end_of_inset(document.body, flex)
1799 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxB}")
1800 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxB}")
1804 def revert_tcolorbox_5(document):
1805 " Reverts the Flex:Custom_Color_Box_3 inset of tcolorbox to TeX-code "
1808 i = find_token(document.header, "tcolorbox", i)
1814 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 3", flex)
1817 flexEnd = find_end_of_inset(document.body, flex)
1818 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1819 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1820 flexEnd = find_end_of_inset(document.body, flex)
1821 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxC}")
1822 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxC}")
1826 def revert_tcolorbox_6(document):
1827 " Reverts the Flex:Custom_Color_Box_4 inset of tcolorbox to TeX-code "
1830 i = find_token(document.header, "tcolorbox", i)
1836 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 4", flex)
1839 flexEnd = find_end_of_inset(document.body, flex)
1840 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1841 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1842 flexEnd = find_end_of_inset(document.body, flex)
1843 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxD}")
1844 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxD}")
1848 def revert_tcolorbox_7(document):
1849 " Reverts the Flex:Custom_Color_Box_5 inset of tcolorbox to TeX-code "
1852 i = find_token(document.header, "tcolorbox", i)
1858 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 5", flex)
1861 flexEnd = find_end_of_inset(document.body, flex)
1862 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1863 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1864 flexEnd = find_end_of_inset(document.body, flex)
1865 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxE}")
1866 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxE}")
1870 def revert_tcolorbox_8(document):
1871 " Reverts the layout New Color Box Type of tcolorbox to TeX-code "
1877 i = find_token(document.body, "\\begin_layout New Color Box Type", i)
1879 j = find_end_of_layout(document.body, i)
1880 wasOpt = revert_Argument_to_TeX_brace(document, i, j, 1, 1, False, True, False)
1881 revert_Argument_to_TeX_brace(document, i, 0, 2, 2, False, False, True)
1882 revert_Argument_to_TeX_brace(document, i, 0, 3, 4, False, True, False)
1883 document.body[i] = document.body[i].replace("\\begin_layout New Color Box Type", "\\begin_layout Standard")
1885 document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox")
1887 document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox{")
1888 k = find_end_of_inset(document.body, j)
1889 k = find_token(document.body, "\\end_inset", k + 1)
1890 k = find_token(document.body, "\\end_inset", k + 1)
1892 k = find_token(document.body, "\\end_inset", k + 1)
1893 document.body[k + 2 : j + 2] = put_cmd_in_ert("{")
1894 j = find_token(document.body, "\\begin_layout Standard", j + 1)
1895 document.body[j - 2 : j - 2] = put_cmd_in_ert("}")
1901 def revert_moderncv_1(document):
1902 " Reverts the new inset of moderncv to TeX-code in preamble "
1904 if document.textclass != "moderncv":
1910 # at first revert the new styles
1912 i = find_token(document.body, "\\begin_layout CVIcons", 0)
1915 j = find_end_of_layout(document.body, i)
1917 document.warning("Malformed LyX document: Can't find end of CVIcons layout")
1920 content = lyx2latex(document, document.body[i:j + 1])
1921 add_to_preamble(document, ["\\moderncvicons{" + content + "}"])
1922 del document.body[i:j + 1]
1924 i = find_token(document.body, "\\begin_layout CVColumnWidth", 0)
1927 j = find_end_of_layout(document.body, i)
1929 document.warning("Malformed LyX document: Can't find end of CVColumnWidth layout")
1932 content = lyx2latex(document, document.body[i:j + 1])
1933 add_to_preamble(document, ["\\setlength{\hintscolumnwidth}{" + content + "}"])
1934 del document.body[i:j + 1]
1935 # now change the new styles to the obsolete ones
1937 i = find_token(document.body, "\\begin_layout Name", 0)
1940 j = find_end_of_layout(document.body, i)
1942 document.warning("Malformed LyX document: Can't find end of Name layout")
1945 lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
1946 if lineArg > j and j != 0:
1949 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
1950 # we have to assure that no other inset is in the Argument
1951 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
1952 endInset = find_token(document.body, "\\end_inset", beginPlain)
1955 while beginInset < endInset and beginInset != -1:
1956 beginInset = find_token(document.body, "\\begin_inset", k)
1957 endInset = find_token(document.body, "\\end_inset", l)
1960 Arg2 = document.body[l + 5 : l + 6]
1962 document.body[i : i + 1]= ["\\begin_layout FirstName"]
1963 # delete the Argument inset
1964 del( document.body[endInset - 2 : endInset + 3])
1965 del( document.body[lineArg : beginPlain + 1])
1966 document.body[i + 4 : i + 4]= ["\\begin_layout FamilyName"] + Arg2 + ["\\end_layout"] + [""]
1969 def revert_moderncv_2(document):
1970 " Reverts the phone inset of moderncv to the obsoleted mobile or fax "
1972 if document.textclass != "moderncv":
1979 i = find_token(document.body, "\\begin_layout Phone", i)
1982 j = find_end_of_layout(document.body, i)
1984 document.warning("Malformed LyX document: Can't find end of Phone layout")
1987 lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
1988 if lineArg > j and j != 0:
1992 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
1993 # we have to assure that no other inset is in the Argument
1994 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
1995 endInset = find_token(document.body, "\\end_inset", beginPlain)
1998 while beginInset < endInset and beginInset != -1:
1999 beginInset = find_token(document.body, "\\begin_inset", k)
2000 endInset = find_token(document.body, "\\end_inset", l)
2003 Arg = document.body[beginPlain + 1 : beginPlain + 2]
2005 if Arg[0] == "mobile":
2006 document.body[i : i + 1]= ["\\begin_layout Mobile"]
2008 document.body[i : i + 1]= ["\\begin_layout Fax"]
2009 # delete the Argument inset
2010 del(document.body[endInset - 2 : endInset + 1])
2011 del(document.body[lineArg : beginPlain + 3])
2015 def convert_moderncv_phone(document):
2016 " Convert the Fax and Mobile inset of moderncv to the new phone inset "
2018 if document.textclass != "moderncv":
2025 "Mobile" : "mobile",
2029 rx = re.compile(r'^\\begin_layout (\S+)$')
2031 # substitute \fax and \mobile by \phone[fax] and \phone[mobile], respectively
2032 i = find_token(document.body, "\\begin_layout", i)
2036 m = rx.match(document.body[i])
2040 if val not in list(phone_dict.keys()):
2043 j = find_end_of_layout(document.body, i)
2045 document.warning("Malformed LyX document: Can't find end of Mobile layout")
2049 document.body[i : i + 1] = ["\\begin_layout Phone", "\\begin_inset Argument 1", "status open", "",
2050 "\\begin_layout Plain Layout", phone_dict[val], "\\end_layout", "",
2054 def convert_moderncv_name(document):
2055 " Convert the FirstName and LastName layout of moderncv to the general Name layout "
2057 if document.textclass != "moderncv":
2060 fnb = 0 # Begin of FirstName inset
2061 fne = 0 # End of FirstName inset
2062 lnb = 0 # Begin of LastName (FamilyName) inset
2063 lne = 0 # End of LastName (FamilyName) inset
2064 nb = 0 # Begin of substituting Name inset
2065 ne = 0 # End of substituting Name inset
2066 FirstName = [] # FirstName content
2067 FamilyName = [] # LastName content
2071 fnb = find_token(document.body, "\\begin_layout FirstName", fnb)
2073 fne = find_end_of_layout(document.body, fnb)
2075 document.warning("Malformed LyX document: Can't find end of FirstName layout")
2077 FirstName = document.body[fnb + 1 : fne]
2079 lnb = find_token(document.body, "\\begin_layout FamilyName", lnb)
2081 lne = find_end_of_layout(document.body, lnb)
2083 document.warning("Malformed LyX document: Can't find end of FamilyName layout")
2085 FamilyName = document.body[lnb + 1 : lne]
2086 # Determine the region for the substituting Name layout
2087 if fnb == -1 and lnb == -1: # Neither FirstName nor FamilyName exists -> Do nothing
2089 elif fnb == -1: # Only FamilyName exists -> New Name insets replaces that
2092 elif lnb == -1: # Only FirstName exists -> New Name insets replaces that
2095 elif fne > lne: # FirstName position before FamilyName -> New Name insets spans
2096 nb = lnb # from FamilyName begin
2097 ne = fne # to FirstName end
2098 else: # FirstName position before FamilyName -> New Name insets spans
2099 nb = fnb # from FirstName begin
2100 ne = lne # to FamilyName end
2102 # Insert the substituting layout now. If FirstName exists, use an otpional argument.
2104 document.body[nb : ne + 1] = ["\\begin_layout Name"] + FamilyName + ["\\end_layout", ""]
2106 document.body[nb : ne + 1] = ["\\begin_layout Name", "\\begin_inset Argument 1", "status open", "",
2107 "\\begin_layout Plain Layout"] + FirstName + ["\\end_layout", "",
2108 "\\end_inset", ""] + FamilyName + ["\\end_layout", ""]
2111 def revert_achemso(document):
2112 " Reverts the flex inset Latin to TeX code "
2114 if document.textclass != "achemso":
2119 i = find_token(document.body, "\\begin_inset Flex Latin", i)
2121 j = find_end_of_inset(document.body, i)
2125 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2126 endPlain = find_end_of_layout(document.body, beginPlain)
2127 content = lyx2latex(document, document.body[beginPlain : endPlain])
2128 document.body[i:j + 1] = put_cmd_in_ert("\\latin{" + content + "}")
2130 document.warning("Malformed LyX document: Can't find end of flex inset Latin")
2135 fontsettings = ["\\font_roman", "\\font_sans", "\\font_typewriter", "\\font_math", \
2136 "\\font_sf_scale", "\\font_tt_scale"]
2137 fontdefaults = ["default", "default", "default", "auto", "100", "100"]
2138 fontquotes = [True, True, True, True, False, False]
2140 def convert_fontsettings(document):
2141 " Duplicate font settings "
2143 i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2145 document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2146 use_non_tex_fonts = "false"
2148 use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2150 for f in fontsettings:
2151 i = find_token(document.header, f + " ", 0)
2153 document.warning("Malformed LyX document: No " + f + "!")
2155 # note that with i = -1, this will insert at the end
2157 value = fontdefaults[j]
2159 value = document.header[i][len(f):].strip()
2161 if use_non_tex_fonts == "true":
2162 document.header[i:i+1] = [f + ' "' + fontdefaults[j] + '" "' + value + '"']
2164 document.header[i:i+1] = [f + ' "' + value + '" "' + fontdefaults[j] + '"']
2166 if use_non_tex_fonts == "true":
2167 document.header[i:i+1] = [f + ' ' + fontdefaults[j] + ' ' + value]
2169 document.header[i:i+1] = [f + ' ' + value + ' ' + fontdefaults[j]]
2173 def revert_fontsettings(document):
2174 " Merge font settings "
2176 i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2178 document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2179 use_non_tex_fonts = "false"
2181 use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2183 for f in fontsettings:
2184 i = find_token(document.header, f + " ", 0)
2186 document.warning("Malformed LyX document: No " + f + "!")
2189 line = get_value(document.header, f, i)
2192 q2 = line.find('"', q1+1)
2193 q3 = line.find('"', q2+1)
2194 q4 = line.find('"', q3+1)
2195 if q1 == -1 or q2 == -1 or q3 == -1 or q4 == -1:
2196 document.warning("Malformed LyX document: Missing quotes!")
2199 if use_non_tex_fonts == "true":
2200 document.header[i:i+1] = [f + ' ' + line[q3+1:q4]]
2202 document.header[i:i+1] = [f + ' ' + line[q1+1:q2]]
2204 if use_non_tex_fonts == "true":
2205 document.header[i:i+1] = [f + ' ' + line.split()[1]]
2207 document.header[i:i+1] = [f + ' ' + line.split()[0]]
2211 def revert_solution(document):
2212 " Reverts the solution environment of the theorem module to TeX code "
2214 # Do we use one of the modules that provides Solution?
2216 mods = document.get_module_list()
2218 if mod == "theorems-std" or mod == "theorems-bytype" \
2219 or mod == "theorems-ams" or mod == "theorems-ams-bytype":
2229 i = find_token(document.body, "\\begin_layout Solution", i)
2233 is_starred = document.body[i].startswith("\\begin_layout Solution*")
2234 if is_starred == True:
2236 LyXName = "Solution*"
2237 theoremName = "newtheorem*"
2240 LyXName = "Solution"
2241 theoremName = "newtheorem"
2243 j = find_end_of_layout(document.body, i)
2245 document.warning("Malformed LyX document: Can't find end of " + LyXName + " layout")
2249 # if this is not a consecutive env, add start command
2252 begcmd = put_cmd_in_ert("\\begin{%s}" % (LaTeXName))
2254 # has this a consecutive theorem of same type?
2255 consecutive = document.body[j + 2] == "\\begin_layout " + LyXName
2257 # if this is not followed by a consecutive env, add end command
2259 document.body[j : j + 1] = put_cmd_in_ert("\\end{%s}" % (LaTeXName)) + ["\\end_layout"]
2261 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
2263 add_to_preamble(document, "\\theoremstyle{definition}")
2264 if is_starred or mod == "theorems-bytype" or mod == "theorems-ams-bytype":
2265 add_to_preamble(document, "\\%s{%s}{\\protect\\solutionname}" % \
2266 (theoremName, LaTeXName))
2267 else: # mod == "theorems-std" or mod == "theorems-ams" and not is_starred
2268 add_to_preamble(document, "\\%s{%s}[thm]{\\protect\\solutionname}" % \
2269 (theoremName, LaTeXName))
2271 add_to_preamble(document, "\\providecommand{\solutionname}{Solution}")
2275 def revert_verbatim_star(document):
2276 from lyx_2_1 import revert_verbatim
2277 revert_verbatim(document, True)
2280 def convert_save_props(document):
2281 " Add save_transient_properties parameter. "
2282 i = find_token(document.header, '\\begin_header', 0)
2284 document.warning("Malformed lyx document: Missing '\\begin_header'.")
2286 document.header.insert(i + 1, '\\save_transient_properties true')
2289 def revert_save_props(document):
2290 " Remove save_transient_properties parameter. "
2291 i = find_token(document.header, "\\save_transient_properties", 0)
2294 del document.header[i]
2297 def convert_info_tabular_feature(document):
2299 return arg.replace("inset-modify tabular", "tabular-feature")
2300 convert_info_insets(document, "shortcut(s)?|icon", f)
2303 def revert_info_tabular_feature(document):
2305 return arg.replace("tabular-feature", "inset-modify tabular")
2306 convert_info_insets(document, "shortcut(s)?|icon", f)
2313 supported_versions = ["2.2.0", "2.2"]
2315 [475, [convert_separator]],
2316 # nothing to do for 476: We consider it a bug that older versions
2317 # did not load amsmath automatically for these commands, and do not
2318 # want to hardcode amsmath off.
2324 [481, [convert_dashes]],
2325 [482, [convert_phrases]],
2326 [483, [convert_specialchar]],
2331 [488, [convert_newgloss]],
2332 [489, [convert_BoxFeatures]],
2333 [490, [convert_origin]],
2335 [492, [convert_colorbox]],
2338 [495, [convert_subref]],
2339 [496, [convert_nounzip]],
2340 [497, [convert_external_bbox]],
2342 [499, [convert_moderncv_phone, convert_moderncv_name]],
2344 [501, [convert_fontsettings]],
2347 [504, [convert_save_props]],
2349 [506, [convert_info_tabular_feature]],
2350 [507, [convert_longtable_label]],
2351 [508, [convert_parbreak]]
2355 [507, [revert_parbreak]],
2356 [506, [revert_longtable_label]],
2357 [505, [revert_info_tabular_feature]],
2359 [503, [revert_save_props]],
2360 [502, [revert_verbatim_star]],
2361 [501, [revert_solution]],
2362 [500, [revert_fontsettings]],
2363 [499, [revert_achemso]],
2364 [498, [revert_moderncv_1, revert_moderncv_2]],
2365 [497, [revert_tcolorbox_1, revert_tcolorbox_2,
2366 revert_tcolorbox_3, revert_tcolorbox_4, revert_tcolorbox_5,
2367 revert_tcolorbox_6, revert_tcolorbox_7, revert_tcolorbox_8]],
2368 [496, [revert_external_bbox]],
2369 [495, []], # nothing to do since the noUnzip parameter was optional
2370 [494, [revert_subref]],
2371 [493, [revert_jss]],
2372 [492, [revert_mathmulticol]],
2373 [491, [revert_colorbox]],
2374 [490, [revert_textcolor]],
2375 [489, [revert_origin]],
2376 [488, [revert_BoxFeatures]],
2377 [487, [revert_newgloss, revert_glossgroup]],
2378 [486, [revert_forest]],
2379 [485, [revert_ex_itemargs]],
2380 [484, [revert_sigplan_doi]],
2381 [483, [revert_georgian]],
2382 [482, [revert_specialchar]],
2383 [481, [revert_phrases]],
2384 [480, [revert_dashes]],
2385 [479, [revert_question_env]],
2386 [478, [revert_beamer_lemma]],
2387 [477, [revert_xarrow]],
2388 [476, [revert_swissgerman]],
2389 [475, [revert_smash]],
2390 [474, [revert_separator]]
2394 if __name__ == "__main__":