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,
33 lyx2latex, 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 (check_token, del_complete_lines,
38 find_end_of_inset, find_end_of_layout, find_nonempty_line, find_re,
39 find_token, find_token_backwards, get_containing_layout,
40 get_containing_inset, get_value, is_in_inset)
43 ####################################################################
44 # Private helper functions
46 def revert_Argument_to_TeX_brace(document, line, endline, n, nmax, environment, opt, nolastopt):
48 Reverts an InsetArgument to TeX-code
50 revert_Argument_to_TeX_brace(document, LineOfBegin, LineOfEnd, StartArgument, EndArgument, isEnvironment, isOpt, notLastOpt)
51 LineOfBegin is the line of the \begin_layout or \begin_inset statement
52 LineOfEnd is the line of the \end_layout or \end_inset statement, if "0" is given, the end of the file is used instead
53 StartArgument is the number of the first argument that needs to be converted
54 EndArgument is the number of the last argument that needs to be converted or the last defined one
55 isEnvironment must be true, if the layout is for a LaTeX environment
56 isOpt must be true, if the argument is an optional one
57 notLastOpt must be true if the argument is mandatory and followed by optional ones
61 while lineArg != -1 and n < nmax + 1:
62 lineArg = find_token(document.body, "\\begin_inset Argument " + str(n), line)
63 if lineArg > endline and endline != 0:
66 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
67 # we have to assure that no other inset is in the Argument
68 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
69 endInset = find_token(document.body, "\\end_inset", beginPlain)
72 while beginInset < endInset and beginInset != -1:
73 beginInset = find_token(document.body, "\\begin_inset", k)
74 endInset = find_token(document.body, "\\end_inset", l)
77 if environment == False:
79 if nolastopt == False:
80 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}{")
82 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}")
83 del(document.body[lineArg : beginPlain + 1])
86 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("]")
87 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("[")
91 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}")
92 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("{")
95 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("]")
96 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("[")
102 ###############################################################################
104 ### Conversion and reversion routines
106 ###############################################################################
108 def convert_longtable_label_internal(document, forward):
110 Convert reference to "LongTableNoNumber" into "Unnumbered" if forward is True
113 old_reference = "\\begin_inset Caption LongTableNoNumber"
114 new_reference = "\\begin_inset Caption Unnumbered"
116 # if the purpose is to revert swap the strings roles
118 old_reference, new_reference = new_reference, old_reference
122 i = find_token(document.body, old_reference, i)
127 document.body[i] = new_reference
130 def convert_longtable_label(document):
131 convert_longtable_label_internal(document, True)
134 def revert_longtable_label(document):
135 convert_longtable_label_internal(document, False)
138 def convert_separator(document):
140 Convert layout separators to separator insets and add (LaTeX) paragraph
141 breaks in order to mimic previous LaTeX export.
144 parins = ["\\begin_inset Separator parbreak", "\\end_inset", ""]
145 parlay = ["\\begin_layout Standard", "\\begin_inset Separator parbreak",
146 "\\end_inset", "", "\\end_layout", ""]
148 "family" : "default",
149 "series" : "default",
158 i = find_token(document.body, "\\begin_deeper", i)
162 j = find_token_backwards(document.body, "\\end_layout", i-1)
164 # reset any text style before inserting the inset
165 lay = get_containing_layout(document.body, j-1)
167 content = "\n".join(document.body[lay[1]:lay[2]])
168 for val in list(sty_dict.keys()):
169 if content.find("\\%s" % val) != -1:
170 document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
173 document.body[j:j] = parins
174 i = i + len(parins) + 1
180 i = find_token(document.body, "\\align", i)
184 lay = get_containing_layout(document.body, i)
185 if lay != False and lay[0] == "Plain Layout":
189 j = find_token_backwards(document.body, "\\end_layout", i-1)
191 lay = get_containing_layout(document.body, j-1)
192 if lay != False and lay[0] == "Standard" \
193 and find_token(document.body, "\\align", lay[1], lay[2]) == -1 \
194 and find_token(document.body, "\\begin_inset VSpace", lay[1], lay[2]) == -1:
195 # reset any text style before inserting the inset
196 content = "\n".join(document.body[lay[1]:lay[2]])
197 for val in list(sty_dict.keys()):
198 if content.find("\\%s" % val) != -1:
199 document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
202 document.body[j:j] = parins
203 i = i + len(parins) + 1
209 regexp = re.compile(r'^\\begin_layout (?:(-*)|(\s*))(Separator|EndOfSlide)(?:(-*)|(\s*))$', re.IGNORECASE)
213 i = find_re(document.body, regexp, i)
217 j = find_end_of_layout(document.body, i)
219 document.warning("Malformed LyX document: Missing `\\end_layout'.")
222 lay = get_containing_layout(document.body, j-1)
224 lines = document.body[lay[3]:lay[2]]
228 document.body[i:j+1] = parlay
230 document.body[i+1:i+1] = lines
232 i = i + len(parlay) + len(lines) + 1
235 def revert_separator(document):
236 " Revert separator insets to layout separators "
238 beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
239 if document.textclass in beamer_classes:
240 beglaysep = "\\begin_layout Separator"
242 beglaysep = "\\begin_layout --Separator--"
244 parsep = [beglaysep, "", "\\end_layout", ""]
245 comert = ["\\begin_inset ERT", "status collapsed", "",
246 "\\begin_layout Plain Layout", "%", "\\end_layout",
247 "", "\\end_inset", ""]
248 empert = ["\\begin_inset ERT", "status collapsed", "",
249 "\\begin_layout Plain Layout", " ", "\\end_layout",
250 "", "\\end_inset", ""]
254 i = find_token(document.body, "\\begin_inset Separator", i)
258 lay = get_containing_layout(document.body, i)
260 document.warning("Malformed LyX document: Can't convert separator inset at line " + str(i))
267 kind = get_value(document.body, "\\begin_inset Separator", i, i+1, "plain").split()[1]
268 before = document.body[beg+1:i]
269 something_before = len(before) > 0 and len("".join(before)) > 0
270 j = find_end_of_inset(document.body, i)
271 after = document.body[j+1:end]
272 something_after = len(after) > 0 and len("".join(after)) > 0
274 beg = beg + len(before) + 1
275 elif something_before:
276 document.body[i:i] = ["\\end_layout", ""]
284 document.body[beg:j+1] = empert
287 document.body[beg:j+1] = comert
291 if layoutname == "Standard":
292 if not something_before:
293 document.body[beg:j+1] = parsep
295 document.body[i:i] = ["", "\\begin_layout Standard"]
298 document.body[beg:j+1] = ["\\begin_layout Standard"]
301 document.body[beg:j+1] = ["\\begin_deeper"]
303 end = end + 1 - (j + 1 - beg)
304 if not something_before:
305 document.body[i:i] = parsep
307 end = end + len(parsep)
308 document.body[i:i] = ["\\begin_layout Standard"]
309 document.body[end+2:end+2] = ["", "\\end_deeper", ""]
312 next_par_is_aligned = False
313 k = find_nonempty_line(document.body, end+1)
314 if k != -1 and check_token(document.body[k], "\\begin_layout"):
315 lay = get_containing_layout(document.body, k)
316 next_par_is_aligned = lay != False and \
317 find_token(document.body, "\\align", lay[1], lay[2]) != -1
318 if k != -1 and not next_par_is_aligned \
319 and not check_token(document.body[k], "\\end_deeper") \
320 and not check_token(document.body[k], "\\begin_deeper"):
321 if layoutname == "Standard":
322 document.body[beg:j+1] = [beglaysep]
325 document.body[beg:j+1] = ["\\begin_deeper", beglaysep]
326 end = end + 2 - (j + 1 - beg)
327 document.body[end+1:end+1] = ["", "\\end_deeper", ""]
331 del document.body[i:end+1]
333 del document.body[i:end-1]
338 def convert_parbreak(document):
340 Convert parbreak separators not specifically used to separate
341 environments to latexpar separators.
343 parbreakinset = "\\begin_inset Separator parbreak"
346 i = find_token(document.body, parbreakinset, i)
349 lay = get_containing_layout(document.body, i)
351 document.warning("Malformed LyX document: Can't convert separator inset at line " + str(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":
620 lines = document.body
622 while i+1 < len(lines):
627 # skip label width string (bug 10243):
628 if line.startswith("\\labelwidthstring"):
630 # Do not touch hyphens in some insets:
632 value, start, end = get_containing_inset(lines, i)
634 # False means no (or malformed) containing inset
635 value, start, end = "no inset", -1, -1
636 # We must not replace anything in insets that store LaTeX contents in .lyx files
637 # (math and command insets without overridden read() and write() methods.
638 # Filtering out IPA and ERT makes Text::readParToken() more simple,
639 # Flex Code is logical markup, typically rendered as typewriter
640 if (value.split()[0] in ["CommandInset", "ERT", "External", "Formula",
641 "FormulaMacro", "Graphics", "IPA", "listings"]
642 or value in ["Flex Code", "Flex URL"]):
646 layout, start, end, j = get_containing_layout(lines, i)
647 except TypeError: # no (or malformed) containing layout
648 document.warning("Malformed LyX document: "
649 "Can't find layout at line %d" % i)
651 if layout == "LyX-Code":
654 # We can have an arbitrary number of consecutive hyphens.
655 # Replace as LaTeX does: First try emdash, then endash
656 line = line.replace("---", "\\threehyphens\n")
657 line = line.replace("--", "\\twohyphens\n")
658 lines[i:i+1] = line.splitlines()
660 # remove ligature breaks between dashes
662 while i < len(lines):
664 if (line.endswith(r"-\SpecialChar \textcompwordmark{}") and
665 lines[i+1].startswith("-")):
666 lines[i] = line.replace(r"\SpecialChar \textcompwordmark{}",
672 def revert_dashes(document):
674 Remove preamble code from 2.3->2.2 conversion.
675 Prevent ligatures of existing --- and --.
676 Revert \\twohyphens and \\threehyphens to -- and ---.
678 del_complete_lines(document.preamble,
679 ['% Added by lyx2lyx',
680 r'\renewcommand{\textendash}{--}',
681 r'\renewcommand{\textemdash}{---}'])
683 # Insert ligature breaks to prevent ligation of hyphens to dashes:
684 lines = document.body
686 while i+1 < len(lines):
691 # skip label width string (bug 10243):
692 if line.startswith("\\labelwidthstring"):
694 # do not touch hyphens in some insets (cf. convert_dashes):
696 value, start, end = get_containing_inset(lines, i)
698 # False means no (or malformed) containing inset
699 value, start, end = "no inset", -1, -1
700 if (value.split()[0] in ["CommandInset", "ERT", "External", "Formula",
701 "FormulaMacro", "Graphics", "IPA", "listings"]
702 or value == "Flex URL"):
705 line = line.replace("--", "-\\SpecialChar \\textcompwordmark{}\n-")
706 document.body[i:i+1] = line.split('\n')
708 # Revert \twohyphens and \threehyphens:
710 while i < len(lines):
712 if not line.endswith("hyphens"):
714 elif line.endswith("\\twohyphens") or line.endswith("\\threehyphens"):
715 line = line.replace("\\twohyphens", "--")
716 line = line.replace("\\threehyphens", "---")
717 lines[i] = line + lines.pop(i+1)
722 # order is important for the last three!
723 phrases = ["LyX", "LaTeX2e", "LaTeX", "TeX"]
725 def is_part_of_converted_phrase(line, j, phrase):
726 "is phrase part of an already converted phrase?"
728 converted = "\\SpecialCharNoPassThru \\" + p
729 pos = j + len(phrase) - len(converted)
731 if line[pos:pos+len(converted)] == converted:
736 def convert_phrases(document):
737 "convert special phrases from plain text to \\SpecialCharNoPassThru"
739 if document.backend != "latex":
742 for phrase in phrases:
744 while i < len(document.body):
745 words = document.body[i].split()
746 if len(words) > 1 and words[0] == "\\begin_inset" and \
747 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
748 # must not replace anything in insets that store LaTeX contents in .lyx files
749 # (math and command insets withut overridden read() and write() methods
750 j = find_end_of_inset(document.body, i)
752 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
757 if document.body[i].find("\\") == 0:
760 j = document.body[i].find(phrase)
764 if not is_part_of_converted_phrase(document.body[i], j, phrase):
765 front = document.body[i][:j]
766 back = document.body[i][j+len(phrase):]
768 document.body.insert(i+1, back)
769 # We cannot use SpecialChar since we do not know whether we are outside passThru
770 document.body[i] = front + "\\SpecialCharNoPassThru \\" + phrase
774 def revert_phrases(document):
775 "convert special phrases to plain text"
778 while i < len(document.body):
779 words = document.body[i].split()
780 if len(words) > 1 and words[0] == "\\begin_inset" and \
781 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
782 # see convert_phrases
783 j = find_end_of_inset(document.body, i)
785 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
791 for phrase in phrases:
792 # we can replace SpecialChar since LyX ensures that it cannot be inserted into passThru parts
793 if document.body[i].find("\\SpecialChar \\" + phrase) >= 0:
794 document.body[i] = document.body[i].replace("\\SpecialChar \\" + phrase, phrase)
796 if document.body[i].find("\\SpecialCharNoPassThru \\" + phrase) >= 0:
797 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru \\" + phrase, phrase)
799 if replaced and i+1 < len(document.body) and \
800 (document.body[i+1].find("\\") != 0 or \
801 document.body[i+1].find("\\SpecialChar") == 0) and \
802 len(document.body[i]) + len(document.body[i+1]) <= 80:
803 document.body[i] = document.body[i] + document.body[i+1]
804 document.body[i+1:i+2] = []
809 def convert_specialchar_internal(document, forward):
810 specialchars = {"\\-":"softhyphen", "\\textcompwordmark{}":"ligaturebreak", \
811 "\\@.":"endofsentence", "\\ldots{}":"ldots", \
812 "\\menuseparator":"menuseparator", "\\slash{}":"breakableslash", \
813 "\\nobreakdash-":"nobreakdash", "\\LyX":"LyX", \
814 "\\TeX":"TeX", "\\LaTeX2e":"LaTeX2e", \
815 "\\LaTeX":"LaTeX" # must be after LaTeX2e
819 while i < len(document.body):
820 words = document.body[i].split()
821 if len(words) > 1 and words[0] == "\\begin_inset" and \
822 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
823 # see convert_phrases
824 j = find_end_of_inset(document.body, i)
826 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
831 for key, value in specialchars.items():
833 document.body[i] = document.body[i].replace("\\SpecialChar " + key, "\\SpecialChar " + value)
834 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + key, "\\SpecialCharNoPassThru " + value)
836 document.body[i] = document.body[i].replace("\\SpecialChar " + value, "\\SpecialChar " + key)
837 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + value, "\\SpecialCharNoPassThru " + key)
841 def convert_specialchar(document):
842 "convert special characters to new syntax"
843 convert_specialchar_internal(document, True)
846 def revert_specialchar(document):
847 "convert special characters to old syntax"
848 convert_specialchar_internal(document, False)
851 def revert_georgian(document):
852 "Set the document language to English but assure Georgian output"
854 if document.language == "georgian":
855 document.language = "english"
856 i = find_token(document.header, "\\language georgian", 0)
858 document.header[i] = "\\language english"
859 j = find_token(document.header, "\\language_package default", 0)
861 document.header[j] = "\\language_package babel"
862 k = find_token(document.header, "\\options", 0)
864 document.header[k] = document.header[k].replace("\\options", "\\options georgian,")
866 l = find_token(document.header, "\\use_default_options", 0)
867 document.header.insert(l + 1, "\\options georgian")
870 def revert_sigplan_doi(document):
871 " Reverts sigplanconf DOI layout to ERT "
873 if document.textclass != "sigplanconf":
878 i = find_token(document.body, "\\begin_layout DOI", i)
881 j = find_end_of_layout(document.body, i)
883 document.warning("Malformed LyX document: Can't find end of DOI layout")
887 content = lyx2latex(document, document.body[i:j + 1])
888 add_to_preamble(document, ["\\doi{" + content + "}"])
889 del document.body[i:j + 1]
893 def revert_ex_itemargs(document):
894 " Reverts \\item arguments of the example environments (Linguistics module) to TeX-code "
896 if not "linguistics" in document.get_module_list():
900 example_layouts = ["Numbered Examples (consecutive)", "Subexample"]
902 i = find_token(document.body, "\\begin_inset Argument item:", i)
905 j = find_end_of_inset(document.body, i)
906 # Find containing paragraph layout
907 parent = get_containing_layout(document.body, i)
909 document.warning("Malformed LyX document: Can't find parent paragraph layout")
913 layoutname = parent[0]
914 if layoutname in example_layouts:
915 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
916 endPlain = find_end_of_layout(document.body, beginPlain)
917 content = document.body[beginPlain + 1 : endPlain]
918 del document.body[i:j+1]
919 subst = put_cmd_in_ert("[") + content + put_cmd_in_ert("]")
920 document.body[parbeg : parbeg] = subst
924 def revert_forest(document):
925 " Reverts the forest environment (Linguistics module) to TeX-code "
927 if not "linguistics" in document.get_module_list():
932 i = find_token(document.body, "\\begin_inset Flex Structure Tree", i)
935 j = find_end_of_inset(document.body, i)
937 document.warning("Malformed LyX document: Can't find end of Structure Tree inset")
941 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
942 endPlain = find_end_of_layout(document.body, beginPlain)
943 content = lyx2latex(document, document.body[beginPlain : endPlain])
945 add_to_preamble(document, ["\\usepackage{forest}"])
947 document.body[i:j + 1] = put_cmd_in_ert("\\begin{forest}" + content + "\\end{forest}")
951 def revert_glossgroup(document):
952 " Reverts the GroupGlossedWords inset (Linguistics module) to TeX-code "
954 if not "linguistics" in document.get_module_list():
959 i = find_token(document.body, "\\begin_inset Flex GroupGlossedWords", i)
962 j = find_end_of_inset(document.body, i)
964 document.warning("Malformed LyX document: Can't find end of GroupGlossedWords inset")
968 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
969 endPlain = find_end_of_layout(document.body, beginPlain)
970 content = lyx2verbatim(document, document.body[beginPlain : endPlain])
972 document.body[i:j + 1] = ["{", "", content, "", "}"]
976 def revert_newgloss(document):
977 " Reverts the new Glosse insets (Linguistics module) to the old format "
979 if not "linguistics" in document.get_module_list():
982 glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
983 for glosse in glosses:
986 i = find_token(document.body, glosse, i)
989 j = find_end_of_inset(document.body, i)
991 document.warning("Malformed LyX document: Can't find end of Glosse inset")
995 arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
996 endarg = find_end_of_inset(document.body, arg)
999 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
1000 if argbeginPlain == -1:
1001 document.warning("Malformed LyX document: Can't find arg plain Layout")
1004 argendPlain = find_end_of_inset(document.body, argbeginPlain)
1005 argcontent = lyx2verbatim(document, document.body[argbeginPlain : argendPlain - 2])
1007 document.body[j:j] = ["", "\\begin_layout Plain Layout","\\backslash", "glt ",
1008 argcontent, "\\end_layout"]
1010 # remove Arg insets and paragraph, if it only contains this inset
1011 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
1012 del document.body[arg - 1 : endarg + 4]
1014 del document.body[arg : endarg + 1]
1016 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1017 endPlain = find_end_of_layout(document.body, beginPlain)
1018 content = lyx2verbatim(document, document.body[beginPlain : endPlain])
1020 document.body[beginPlain + 1:endPlain] = [content]
1023 # Dissolve ERT insets
1024 for glosse in glosses:
1027 i = find_token(document.body, glosse, i)
1030 j = find_end_of_inset(document.body, i)
1032 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1036 ert = find_token(document.body, "\\begin_inset ERT", i, j)
1039 ertend = find_end_of_inset(document.body, ert)
1041 document.warning("Malformed LyX document: Can't find end of ERT inset")
1044 ertcontent = get_ert(document.body, ert, True)
1045 document.body[ert : ertend + 1] = [ertcontent]
1049 def convert_newgloss(document):
1050 " Converts Glosse insets (Linguistics module) to the new format "
1052 if not "linguistics" in document.get_module_list():
1055 glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
1056 for glosse in glosses:
1059 i = find_token(document.body, glosse, i)
1062 j = find_end_of_inset(document.body, i)
1064 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1071 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", k, j)
1072 if beginPlain == -1:
1074 endPlain = find_end_of_layout(document.body, beginPlain)
1076 document.warning("Malformed LyX document: Can't find end of Glosse layout")
1080 glt = find_token(document.body, "\\backslash", beginPlain, endPlain)
1081 if glt != -1 and document.body[glt + 1].startswith("glt"):
1082 document.body[glt + 1] = document.body[glt + 1].lstrip("glt").lstrip()
1083 argcontent = document.body[glt + 1 : endPlain]
1084 document.body[beginPlain + 1 : endPlain] = ["\\begin_inset Argument 1", "status open", "",
1085 "\\begin_layout Plain Layout", "\\begin_inset ERT", "status open", "",
1086 "\\begin_layout Plain Layout", ""] + argcontent + ["\\end_layout", "", "\\end_inset", "",
1087 "\\end_layout", "", "\\end_inset"]
1089 content = document.body[beginPlain + 1 : endPlain]
1090 document.body[beginPlain + 1 : endPlain] = ["\\begin_inset ERT", "status open", "",
1091 "\\begin_layout Plain Layout"] + content + ["\\end_layout", "", "\\end_inset"]
1093 endPlain = find_end_of_layout(document.body, beginPlain)
1095 j = find_end_of_inset(document.body, i)
1100 def convert_BoxFeatures(document):
1101 " adds new box features "
1105 i = find_token(document.body, "height_special", i)
1108 document.body[i+1:i+1] = ['thickness "0.4pt"', 'separation "3pt"', 'shadowsize "4pt"']
1112 def revert_BoxFeatures(document):
1113 " outputs new box features as TeX code "
1117 defaultThick = "0.4pt"
1118 defaultShadow = "4pt"
1120 i = find_token(document.body, "thickness", i)
1123 binset = find_token(document.body, "\\begin_inset Box", i - 11)
1124 if binset == -1 or binset != i - 11:
1126 continue # then "thickness" is is just a word in the text
1127 einset = find_end_of_inset(document.body, binset)
1129 document.warning("Malformed LyX document: Can't find end of box inset!")
1132 # read out the values
1133 beg = document.body[i].find('"');
1134 end = document.body[i].rfind('"');
1135 thickness = document.body[i][beg+1:end];
1136 beg = document.body[i+1].find('"');
1137 end = document.body[i+1].rfind('"');
1138 separation = document.body[i+1][beg+1:end];
1139 beg = document.body[i+2].find('"');
1140 end = document.body[i+2].rfind('"');
1141 shadowsize = document.body[i+2][beg+1:end];
1142 # delete the specification
1143 del document.body[i:i+3]
1145 # first output the closing brace
1146 if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1147 document.body[einset -1 : einset - 1] = put_cmd_in_ert("}")
1148 # we have now the problem that if there is already \(f)colorbox in ERT around the inset
1149 # the ERT from this routine must be around it
1150 regexp = re.compile(r'^.*colorbox{.*$')
1151 pos = find_re(document.body, regexp, binset - 4)
1152 if pos != -1 and pos == binset - 4:
1156 # now output the lengths
1157 if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1158 document.body[pos : pos] = put_cmd_in_ert("{")
1159 if thickness != defaultThick:
1160 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness]
1161 if separation != defaultSep and thickness == defaultThick:
1162 document.body[pos + 5 : pos +6] = ["{\\backslash fboxsep " + separation]
1163 if separation != defaultSep and thickness != defaultThick:
1164 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation]
1165 if shadowsize != defaultShadow and separation == defaultSep and thickness == defaultThick:
1166 document.body[pos + 5 : pos +6] = ["{\\backslash shadowsize " + shadowsize]
1167 if shadowsize != defaultShadow and separation != defaultSep and thickness == defaultThick:
1168 document.body[pos + 5 : pos +6] = ["{\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1169 if shadowsize != defaultShadow and separation == defaultSep and thickness != defaultThick:
1170 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash shadowsize " + shadowsize]
1171 if shadowsize != defaultShadow and separation != defaultSep and thickness != defaultThick:
1172 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1175 def convert_origin(document):
1176 " Insert the origin tag "
1178 i = find_token(document.header, "\\textclass ", 0)
1180 document.warning("Malformed LyX document: No \\textclass!!")
1182 if document.dir == u'':
1186 if document.systemlyxdir and document.systemlyxdir != u'':
1188 if os.path.isabs(document.dir):
1189 absdir = os.path.normpath(document.dir)
1191 absdir = os.path.normpath(os.path.abspath(document.dir))
1192 if os.path.isabs(document.systemlyxdir):
1193 abssys = os.path.normpath(document.systemlyxdir)
1195 abssys = os.path.normpath(os.path.abspath(document.systemlyxdir))
1196 relpath = os.path.relpath(absdir, abssys)
1197 if relpath.find(u'..') == 0:
1202 origin = document.dir.replace(u'\\', u'/') + u'/'
1204 origin = os.path.join(u"/systemlyxdir", relpath).replace(u'\\', u'/') + u'/'
1205 document.header[i:i] = ["\\origin " + origin]
1208 def revert_origin(document):
1209 " Remove the origin tag "
1211 i = find_token(document.header, "\\origin ", 0)
1213 document.warning("Malformed LyX document: No \\origin!!")
1215 del document.header[i]
1218 color_names = ["brown", "darkgray", "gray", \
1219 "lightgray", "lime", "olive", "orange", \
1220 "pink", "purple", "teal", "violet"]
1222 def revert_textcolor(document):
1223 " revert new \\textcolor colors to TeX code "
1229 i = find_token(document.body, "\\color ", i)
1233 for color in list(color_names):
1234 if document.body[i] == "\\color " + color:
1235 # register that xcolor must be loaded in the preamble
1238 add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"])
1239 # find the next \\color and/or the next \\end_layout
1240 j = find_token(document.body, "\\color", i + 1)
1241 k = find_token(document.body, "\\end_layout", i + 1)
1242 if j == -1 and k != -1:
1245 # first output the closing brace
1247 document.body[k: k] = put_cmd_in_ert("}")
1249 document.body[j: j] = put_cmd_in_ert("}")
1250 # now output the \textcolor command
1251 document.body[i : i + 1] = put_cmd_in_ert("\\textcolor{" + color + "}{")
1255 def convert_colorbox(document):
1256 "Add color settings for boxes."
1259 i = find_token(document.body, "shadowsize", i)
1262 # check whether this is really a LyX Box setting
1263 start, end = is_in_inset(document.body, i, "\\begin_inset Box")
1265 find_token(document.body, "\\begin_layout", start, i) != -1):
1268 document.body[i+1:i+1] = ['framecolor "black"',
1269 'backgroundcolor "none"']
1273 def revert_colorbox(document):
1274 " outputs color settings for boxes as TeX code "
1277 defaultframecolor = "black"
1278 defaultbackcolor = "none"
1280 i = find_token(document.body, "framecolor", i)
1283 binset = find_token(document.body, "\\begin_inset Box", i - 14)
1286 einset = find_end_of_inset(document.body, binset)
1288 document.warning("Malformed LyX document: Can't find end of box inset!")
1290 # read out the values
1291 beg = document.body[i].find('"');
1292 end = document.body[i].rfind('"');
1293 framecolor = document.body[i][beg+1:end];
1294 beg = document.body[i + 1].find('"');
1295 end = document.body[i + 1].rfind('"');
1296 backcolor = document.body[i+1][beg+1:end];
1297 # delete the specification
1298 del document.body[i:i + 2]
1300 # first output the closing brace
1301 if framecolor != defaultframecolor or backcolor != defaultbackcolor:
1302 add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"])
1303 document.body[einset : einset] = put_cmd_in_ert("}")
1304 # determine the box type
1305 isBox = find_token(document.body, "\\begin_inset Box Boxed", binset)
1306 # now output the box commands
1307 if (framecolor != defaultframecolor and isBox == binset) or (backcolor != defaultbackcolor and isBox == binset):
1308 document.body[i - 14 : i - 14] = put_cmd_in_ert("\\fcolorbox{" + framecolor + "}{" + backcolor + "}{")
1309 # in the case we must also change the box type because the ERT code adds a frame
1310 document.body[i - 4] = "\\begin_inset Box Frameless"
1311 # if has_inner_box 0 we must set it and use_makebox to 1
1312 ibox = find_token(document.body, "has_inner_box", i - 4)
1313 if ibox == -1 or ibox != i - 1:
1314 document.warning("Malformed LyX document: Can't find has_inner_box statement!")
1316 # read out the value
1317 innerbox = document.body[ibox][-1:];
1319 document.body[ibox] = "has_inner_box 1"
1320 document.body[ibox + 3] = "use_makebox 1"
1321 if backcolor != defaultbackcolor and isBox != binset:
1322 document.body[i - 14 : i - 14] = put_cmd_in_ert("\\colorbox{" + backcolor + "}{")
1325 def revert_mathmulticol(document):
1326 " Convert formulas to ERT if they contain multicolumns "
1330 i = find_token(document.body, '\\begin_inset Formula', i)
1333 j = find_end_of_inset(document.body, i)
1335 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
1338 lines = document.body[i:j]
1339 lines[0] = lines[0].replace('\\begin_inset Formula', '').lstrip()
1340 code = "\n".join(lines)
1345 n = code.find("\\multicolumn", k)
1346 # no need to convert degenerated multicolumn cells,
1347 # they work in old LyX versions as "math ERT"
1348 if n != -1 and code.find("\\multicolumn{1}", k) != n:
1349 ert = put_cmd_in_ert(code)
1350 document.body[i:j+1] = ert
1356 i = find_end_of_inset(document.body, i)
1361 def revert_jss(document):
1362 " Reverts JSS In_Preamble commands to ERT in preamble "
1364 if document.textclass != "jss":
1373 # at first revert the inset layouts because they can be part of the In_Preamble layouts
1374 while m != -1 or j != -1 or h != -1 or k != -1 or n != -1:
1377 h = find_token(document.body, "\\begin_inset Flex Pkg", h)
1379 endh = find_end_of_inset(document.body, h)
1380 document.body[endh - 2 : endh + 1] = put_cmd_in_ert("}")
1381 document.body[h : h + 4] = put_cmd_in_ert("\\pkg{")
1385 m = find_token(document.body, "\\begin_inset Flex Proglang", m)
1387 endm = find_end_of_inset(document.body, m)
1388 document.body[endm - 2 : endm + 1] = put_cmd_in_ert("}")
1389 document.body[m : m + 4] = put_cmd_in_ert("\\proglang{")
1393 j = find_token(document.body, "\\begin_inset Flex Code", j)
1395 # assure that we are not in a Code Chunk inset
1396 if document.body[j][-1] == "e":
1397 endj = find_end_of_inset(document.body, j)
1398 document.body[endj - 2 : endj + 1] = put_cmd_in_ert("}")
1399 document.body[j : j + 4] = put_cmd_in_ert("\\code{")
1405 k = find_token(document.body, "\\begin_inset Flex E-mail", k)
1407 endk = find_end_of_inset(document.body, k)
1408 document.body[endk - 2 : endk + 1] = put_cmd_in_ert("}")
1409 document.body[k : k + 4] = put_cmd_in_ert("\\email{")
1413 n = find_token(document.body, "\\begin_inset Flex URL", n)
1415 endn = find_end_of_inset(document.body, n)
1416 document.body[endn - 2 : endn + 1] = put_cmd_in_ert("}")
1417 document.body[n : n + 4] = put_cmd_in_ert("\\url{")
1419 # now revert the In_Preamble layouts
1421 i = find_token(document.body, "\\begin_layout Title", 0)
1424 j = find_end_of_layout(document.body, i)
1426 document.warning("Malformed LyX document: Can't find end of Title layout")
1429 content = lyx2latex(document, document.body[i:j + 1])
1430 add_to_preamble(document, ["\\title{" + content + "}"])
1431 del document.body[i:j + 1]
1433 i = find_token(document.body, "\\begin_layout Author", 0)
1436 j = find_end_of_layout(document.body, i)
1438 document.warning("Malformed LyX document: Can't find end of Author layout")
1441 content = lyx2latex(document, document.body[i:j + 1])
1442 add_to_preamble(document, ["\\author{" + content + "}"])
1443 del document.body[i:j + 1]
1445 i = find_token(document.body, "\\begin_layout Plain Author", 0)
1448 j = find_end_of_layout(document.body, i)
1450 document.warning("Malformed LyX document: Can't find end of Plain Author layout")
1453 content = lyx2latex(document, document.body[i:j + 1])
1454 add_to_preamble(document, ["\\Plainauthor{" + content + "}"])
1455 del document.body[i:j + 1]
1457 i = find_token(document.body, "\\begin_layout Plain Title", 0)
1460 j = find_end_of_layout(document.body, i)
1462 document.warning("Malformed LyX document: Can't find end of Plain Title layout")
1465 content = lyx2latex(document, document.body[i:j + 1])
1466 add_to_preamble(document, ["\\Plaintitle{" + content + "}"])
1467 del document.body[i:j + 1]
1469 i = find_token(document.body, "\\begin_layout Short Title", 0)
1472 j = find_end_of_layout(document.body, i)
1474 document.warning("Malformed LyX document: Can't find end of Short Title layout")
1477 content = lyx2latex(document, document.body[i:j + 1])
1478 add_to_preamble(document, ["\\Shorttitle{" + content + "}"])
1479 del document.body[i:j + 1]
1481 i = find_token(document.body, "\\begin_layout Abstract", 0)
1484 j = find_end_of_layout(document.body, i)
1486 document.warning("Malformed LyX document: Can't find end of Abstract layout")
1489 content = lyx2latex(document, document.body[i:j + 1])
1490 add_to_preamble(document, ["\\Abstract{" + content + "}"])
1491 del document.body[i:j + 1]
1493 i = find_token(document.body, "\\begin_layout Keywords", 0)
1496 j = find_end_of_layout(document.body, i)
1498 document.warning("Malformed LyX document: Can't find end of Keywords layout")
1501 content = lyx2latex(document, document.body[i:j + 1])
1502 add_to_preamble(document, ["\\Keywords{" + content + "}"])
1503 del document.body[i:j + 1]
1505 i = find_token(document.body, "\\begin_layout Plain Keywords", 0)
1508 j = find_end_of_layout(document.body, i)
1510 document.warning("Malformed LyX document: Can't find end of Plain Keywords layout")
1513 content = lyx2latex(document, document.body[i:j + 1])
1514 add_to_preamble(document, ["\\Plainkeywords{" + content + "}"])
1515 del document.body[i:j + 1]
1517 i = find_token(document.body, "\\begin_layout Address", 0)
1520 j = find_end_of_layout(document.body, i)
1522 document.warning("Malformed LyX document: Can't find end of Address layout")
1525 content = lyx2latex(document, document.body[i:j + 1])
1526 add_to_preamble(document, ["\\Address{" + content + "}"])
1527 del document.body[i:j + 1]
1528 # finally handle the code layouts
1533 while m != -1 or j != -1 or h != -1 or k != -1:
1536 h = find_token(document.body, "\\begin_inset Flex Code Chunk", h)
1538 endh = find_end_of_inset(document.body, h)
1539 document.body[endh : endh + 1] = put_cmd_in_ert("\\end{CodeChunk}")
1540 document.body[h : h + 3] = put_cmd_in_ert("\\begin{CodeChunk}")
1541 document.body[h - 1 : h] = ["\\begin_layout Standard"]
1545 j = find_token(document.body, "\\begin_layout Code Input", j)
1547 endj = find_end_of_layout(document.body, j)
1548 document.body[endj : endj + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1549 document.body[endj + 3 : endj + 4] = put_cmd_in_ert("\\end{CodeInput}")
1550 document.body[endj + 13 : endj + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1551 document.body[j + 1 : j] = ["\\end_layout", "", "\\begin_layout Standard"]
1552 document.body[j : j + 1] = put_cmd_in_ert("\\begin{CodeInput}")
1556 k = find_token(document.body, "\\begin_layout Code Output", k)
1558 endk = find_end_of_layout(document.body, k)
1559 document.body[endk : endk + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1560 document.body[endk + 3 : endk + 4] = put_cmd_in_ert("\\end{CodeOutput}")
1561 document.body[endk + 13 : endk + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1562 document.body[k + 1 : k] = ["\\end_layout", "", "\\begin_layout Standard"]
1563 document.body[k : k + 1] = put_cmd_in_ert("\\begin{CodeOutput}")
1567 m = find_token(document.body, "\\begin_layout Code", m)
1569 endm = find_end_of_layout(document.body, m)
1570 document.body[endm : endm + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1571 document.body[endm + 3 : endm + 4] = put_cmd_in_ert("\\end{Code}")
1572 document.body[endm + 13 : endm + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1573 document.body[m + 1 : m] = ["\\end_layout", "", "\\begin_layout Standard"]
1574 document.body[m : m + 1] = put_cmd_in_ert("\\begin{Code}")
1578 def convert_subref(document):
1579 " converts sub: ref prefixes to subref: "
1582 rx = re.compile(r'^name \"sub:(.+)$')
1585 i = find_token(document.body, "\\begin_inset CommandInset label", i)
1588 j = find_end_of_inset(document.body, i)
1590 document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1594 for p in range(i, j):
1595 m = rx.match(document.body[p])
1598 document.body[p] = "name \"subsec:" + label
1602 rx = re.compile(r'^reference \"sub:(.+)$')
1605 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1608 j = find_end_of_inset(document.body, i)
1610 document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1614 for p in range(i, j):
1615 m = rx.match(document.body[p])
1618 document.body[p] = "reference \"subsec:" + label
1624 def revert_subref(document):
1625 " reverts subref: ref prefixes to sub: "
1628 rx = re.compile(r'^name \"subsec:(.+)$')
1631 i = find_token(document.body, "\\begin_inset CommandInset label", i)
1634 j = find_end_of_inset(document.body, i)
1636 document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1640 for p in range(i, j):
1641 m = rx.match(document.body[p])
1644 document.body[p] = "name \"sub:" + label
1649 rx = re.compile(r'^reference \"subsec:(.+)$')
1652 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1655 j = find_end_of_inset(document.body, i)
1657 document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1661 for p in range(i, j):
1662 m = rx.match(document.body[p])
1665 document.body[p] = "reference \"sub:" + label
1670 def convert_nounzip(document):
1671 " remove the noUnzip parameter of graphics insets "
1673 rx = re.compile(r'\s*noUnzip\s*$')
1676 i = find_token(document.body, "\\begin_inset Graphics", i)
1679 j = find_end_of_inset(document.body, i)
1681 document.warning("Malformed LyX document: Can't find end of graphics inset at line " + str(i))
1685 k = find_re(document.body, rx, i, j)
1687 del document.body[k]
1692 def convert_revert_external_bbox(document, forward):
1693 " add units to bounding box of external insets "
1695 rx = re.compile(r'^\s*boundingBox\s+\S+\s+\S+\s+\S+\s+\S+\s*$')
1698 i = find_token(document.body, "\\begin_inset External", i)
1701 j = find_end_of_inset(document.body, i)
1703 document.warning("Malformed LyX document: Can't find end of external inset at line " + str(i))
1706 k = find_re(document.body, rx, i, j)
1710 tokens = document.body[k].split()
1712 for t in range(1, 5):
1715 for t in range(1, 5):
1716 tokens[t] = length_in_bp(tokens[t])
1717 document.body[k] = "\tboundingBox " + tokens[1] + " " + tokens[2] + " " + \
1718 tokens[3] + " " + tokens[4]
1722 def convert_external_bbox(document):
1723 convert_revert_external_bbox(document, True)
1726 def revert_external_bbox(document):
1727 convert_revert_external_bbox(document, False)
1730 def revert_tcolorbox_1(document):
1731 " Reverts the Flex:Subtitle inset of tcolorbox to TeX-code "
1734 i = find_token(document.header, "tcolorbox", i)
1740 flex = find_token(document.body, "\\begin_inset Flex Subtitle", flex)
1743 flexEnd = find_end_of_inset(document.body, flex)
1744 wasOpt = revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, False, True, False)
1745 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, False, False, False)
1746 flexEnd = find_end_of_inset(document.body, flex)
1748 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\tcbsubtitle")
1750 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\tcbsubtitle{")
1751 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("}")
1755 def revert_tcolorbox_2(document):
1756 " Reverts the Flex:Raster_Color_Box inset of tcolorbox to TeX-code "
1759 i = find_token(document.header, "tcolorbox", i)
1765 flex = find_token(document.body, "\\begin_inset Flex Raster Color Box", flex)
1768 flexEnd = find_end_of_inset(document.body, flex)
1769 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1770 flexEnd = find_end_of_inset(document.body, flex)
1771 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{tcbraster}")
1772 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("\\end{tcbraster}")
1776 def revert_tcolorbox_3(document):
1777 " Reverts the Flex:Custom_Color_Box_1 inset of tcolorbox to TeX-code "
1780 i = find_token(document.header, "tcolorbox", i)
1786 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 1", flex)
1789 flexEnd = find_end_of_inset(document.body, flex)
1790 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1791 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1792 flexEnd = find_end_of_inset(document.body, flex)
1793 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxA}")
1794 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxA}")
1798 def revert_tcolorbox_4(document):
1799 " Reverts the Flex:Custom_Color_Box_2 inset of tcolorbox to TeX-code "
1802 i = find_token(document.header, "tcolorbox", i)
1808 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 2", flex)
1811 flexEnd = find_end_of_inset(document.body, flex)
1812 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1813 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1814 flexEnd = find_end_of_inset(document.body, flex)
1815 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxB}")
1816 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxB}")
1820 def revert_tcolorbox_5(document):
1821 " Reverts the Flex:Custom_Color_Box_3 inset of tcolorbox to TeX-code "
1824 i = find_token(document.header, "tcolorbox", i)
1830 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 3", flex)
1833 flexEnd = find_end_of_inset(document.body, flex)
1834 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1835 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1836 flexEnd = find_end_of_inset(document.body, flex)
1837 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxC}")
1838 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxC}")
1842 def revert_tcolorbox_6(document):
1843 " Reverts the Flex:Custom_Color_Box_4 inset of tcolorbox to TeX-code "
1846 i = find_token(document.header, "tcolorbox", i)
1852 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 4", flex)
1855 flexEnd = find_end_of_inset(document.body, flex)
1856 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1857 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1858 flexEnd = find_end_of_inset(document.body, flex)
1859 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxD}")
1860 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxD}")
1864 def revert_tcolorbox_7(document):
1865 " Reverts the Flex:Custom_Color_Box_5 inset of tcolorbox to TeX-code "
1868 i = find_token(document.header, "tcolorbox", i)
1874 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 5", flex)
1877 flexEnd = find_end_of_inset(document.body, flex)
1878 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1879 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1880 flexEnd = find_end_of_inset(document.body, flex)
1881 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxE}")
1882 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxE}")
1886 def revert_tcolorbox_8(document):
1887 " Reverts the layout New Color Box Type of tcolorbox to TeX-code "
1893 i = find_token(document.body, "\\begin_layout New Color Box Type", i)
1895 j = find_end_of_layout(document.body, i)
1896 wasOpt = revert_Argument_to_TeX_brace(document, i, j, 1, 1, False, True, False)
1897 revert_Argument_to_TeX_brace(document, i, 0, 2, 2, False, False, True)
1898 revert_Argument_to_TeX_brace(document, i, 0, 3, 4, False, True, False)
1899 document.body[i] = document.body[i].replace("\\begin_layout New Color Box Type", "\\begin_layout Standard")
1901 document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox")
1903 document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox{")
1904 k = find_end_of_inset(document.body, j)
1905 k = find_token(document.body, "\\end_inset", k + 1)
1906 k = find_token(document.body, "\\end_inset", k + 1)
1908 k = find_token(document.body, "\\end_inset", k + 1)
1909 document.body[k + 2 : j + 2] = put_cmd_in_ert("{")
1910 j = find_token(document.body, "\\begin_layout Standard", j + 1)
1911 document.body[j - 2 : j - 2] = put_cmd_in_ert("}")
1917 def revert_moderncv_1(document):
1918 " Reverts the new inset of moderncv to TeX-code in preamble "
1920 if document.textclass != "moderncv":
1926 # at first revert the new styles
1928 i = find_token(document.body, "\\begin_layout CVIcons", 0)
1931 j = find_end_of_layout(document.body, i)
1933 document.warning("Malformed LyX document: Can't find end of CVIcons layout")
1936 content = lyx2latex(document, document.body[i:j + 1])
1937 add_to_preamble(document, ["\\moderncvicons{" + content + "}"])
1938 del document.body[i:j + 1]
1940 i = find_token(document.body, "\\begin_layout CVColumnWidth", 0)
1943 j = find_end_of_layout(document.body, i)
1945 document.warning("Malformed LyX document: Can't find end of CVColumnWidth layout")
1948 content = lyx2latex(document, document.body[i:j + 1])
1949 add_to_preamble(document, ["\\setlength{\hintscolumnwidth}{" + content + "}"])
1950 del document.body[i:j + 1]
1951 # now change the new styles to the obsolete ones
1953 i = find_token(document.body, "\\begin_layout Name", 0)
1956 j = find_end_of_layout(document.body, i)
1958 document.warning("Malformed LyX document: Can't find end of Name layout")
1961 lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
1962 if lineArg > j and j != 0:
1965 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
1966 # we have to assure that no other inset is in the Argument
1967 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
1968 endInset = find_token(document.body, "\\end_inset", beginPlain)
1971 while beginInset < endInset and beginInset != -1:
1972 beginInset = find_token(document.body, "\\begin_inset", k)
1973 endInset = find_token(document.body, "\\end_inset", l)
1976 Arg2 = document.body[l + 5 : l + 6]
1978 document.body[i : i + 1]= ["\\begin_layout FirstName"]
1979 # delete the Argument inset
1980 del( document.body[endInset - 2 : endInset + 3])
1981 del( document.body[lineArg : beginPlain + 1])
1982 document.body[i + 4 : i + 4]= ["\\begin_layout FamilyName"] + Arg2 + ["\\end_layout"] + [""]
1985 def revert_moderncv_2(document):
1986 " Reverts the phone inset of moderncv to the obsoleted mobile or fax "
1988 if document.textclass != "moderncv":
1995 i = find_token(document.body, "\\begin_layout Phone", i)
1998 j = find_end_of_layout(document.body, i)
2000 document.warning("Malformed LyX document: Can't find end of Phone layout")
2003 lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
2004 if lineArg > j and j != 0:
2008 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
2009 # we have to assure that no other inset is in the Argument
2010 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
2011 endInset = find_token(document.body, "\\end_inset", beginPlain)
2014 while beginInset < endInset and beginInset != -1:
2015 beginInset = find_token(document.body, "\\begin_inset", k)
2016 endInset = find_token(document.body, "\\end_inset", l)
2019 Arg = document.body[beginPlain + 1 : beginPlain + 2]
2021 if Arg[0] == "mobile":
2022 document.body[i : i + 1]= ["\\begin_layout Mobile"]
2024 document.body[i : i + 1]= ["\\begin_layout Fax"]
2025 # delete the Argument inset
2026 del(document.body[endInset - 2 : endInset + 1])
2027 del(document.body[lineArg : beginPlain + 3])
2031 def convert_moderncv_phone(document):
2032 " Convert the Fax and Mobile inset of moderncv to the new phone inset "
2034 if document.textclass != "moderncv":
2041 "Mobile" : "mobile",
2045 rx = re.compile(r'^\\begin_layout (\S+)$')
2047 # substitute \fax and \mobile by \phone[fax] and \phone[mobile], respectively
2048 i = find_token(document.body, "\\begin_layout", i)
2052 m = rx.match(document.body[i])
2056 if val not in list(phone_dict.keys()):
2059 j = find_end_of_layout(document.body, i)
2061 document.warning("Malformed LyX document: Can't find end of Mobile layout")
2065 document.body[i : i + 1] = ["\\begin_layout Phone", "\\begin_inset Argument 1", "status open", "",
2066 "\\begin_layout Plain Layout", phone_dict[val], "\\end_layout", "",
2070 def convert_moderncv_name(document):
2071 " Convert the FirstName and LastName layout of moderncv to the general Name layout "
2073 if document.textclass != "moderncv":
2076 fnb = 0 # Begin of FirstName inset
2077 fne = 0 # End of FirstName inset
2078 lnb = 0 # Begin of LastName (FamilyName) inset
2079 lne = 0 # End of LastName (FamilyName) inset
2080 nb = 0 # Begin of substituting Name inset
2081 ne = 0 # End of substituting Name inset
2082 FirstName = [] # FirstName content
2083 FamilyName = [] # LastName content
2087 fnb = find_token(document.body, "\\begin_layout FirstName", fnb)
2089 fne = find_end_of_layout(document.body, fnb)
2091 document.warning("Malformed LyX document: Can't find end of FirstName layout")
2093 FirstName = document.body[fnb + 1 : fne]
2095 lnb = find_token(document.body, "\\begin_layout FamilyName", lnb)
2097 lne = find_end_of_layout(document.body, lnb)
2099 document.warning("Malformed LyX document: Can't find end of FamilyName layout")
2101 FamilyName = document.body[lnb + 1 : lne]
2102 # Determine the region for the substituting Name layout
2103 if fnb == -1 and lnb == -1: # Neither FirstName nor FamilyName exists -> Do nothing
2105 elif fnb == -1: # Only FamilyName exists -> New Name insets replaces that
2108 elif lnb == -1: # Only FirstName exists -> New Name insets replaces that
2111 elif fne > lne: # FirstName position before FamilyName -> New Name insets spans
2112 nb = lnb # from FamilyName begin
2113 ne = fne # to FirstName end
2114 else: # FirstName position before FamilyName -> New Name insets spans
2115 nb = fnb # from FirstName begin
2116 ne = lne # to FamilyName end
2118 # Insert the substituting layout now. If FirstName exists, use an otpional argument.
2120 document.body[nb : ne + 1] = ["\\begin_layout Name"] + FamilyName + ["\\end_layout", ""]
2122 document.body[nb : ne + 1] = ["\\begin_layout Name", "\\begin_inset Argument 1", "status open", "",
2123 "\\begin_layout Plain Layout"] + FirstName + ["\\end_layout", "",
2124 "\\end_inset", ""] + FamilyName + ["\\end_layout", ""]
2127 def revert_achemso(document):
2128 " Reverts the flex inset Latin to TeX code "
2130 if document.textclass != "achemso":
2135 i = find_token(document.body, "\\begin_inset Flex Latin", i)
2137 j = find_end_of_inset(document.body, i)
2141 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2142 endPlain = find_end_of_layout(document.body, beginPlain)
2143 content = lyx2latex(document, document.body[beginPlain : endPlain])
2144 document.body[i:j + 1] = put_cmd_in_ert("\\latin{" + content + "}")
2146 document.warning("Malformed LyX document: Can't find end of flex inset Latin")
2151 fontsettings = ["\\font_roman", "\\font_sans", "\\font_typewriter", "\\font_math", \
2152 "\\font_sf_scale", "\\font_tt_scale"]
2153 fontdefaults = ["default", "default", "default", "auto", "100", "100"]
2154 fontquotes = [True, True, True, True, False, False]
2156 def convert_fontsettings(document):
2157 " Duplicate font settings "
2159 i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2161 document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2162 use_non_tex_fonts = "false"
2164 use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2166 for f in fontsettings:
2167 i = find_token(document.header, f + " ", 0)
2169 document.warning("Malformed LyX document: No " + f + "!")
2171 # note that with i = -1, this will insert at the end
2173 value = fontdefaults[j]
2175 value = document.header[i][len(f):].strip()
2177 if use_non_tex_fonts == "true":
2178 document.header[i:i+1] = [f + ' "' + fontdefaults[j] + '" "' + value + '"']
2180 document.header[i:i+1] = [f + ' "' + value + '" "' + fontdefaults[j] + '"']
2182 if use_non_tex_fonts == "true":
2183 document.header[i:i+1] = [f + ' ' + fontdefaults[j] + ' ' + value]
2185 document.header[i:i+1] = [f + ' ' + value + ' ' + fontdefaults[j]]
2189 def revert_fontsettings(document):
2190 " Merge font settings "
2192 i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2194 document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2195 use_non_tex_fonts = "false"
2197 use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2199 for f in fontsettings:
2200 i = find_token(document.header, f + " ", 0)
2202 document.warning("Malformed LyX document: No " + f + "!")
2205 line = get_value(document.header, f, i)
2208 q2 = line.find('"', q1+1)
2209 q3 = line.find('"', q2+1)
2210 q4 = line.find('"', q3+1)
2211 if q1 == -1 or q2 == -1 or q3 == -1 or q4 == -1:
2212 document.warning("Malformed LyX document: Missing quotes!")
2215 if use_non_tex_fonts == "true":
2216 document.header[i:i+1] = [f + ' ' + line[q3+1:q4]]
2218 document.header[i:i+1] = [f + ' ' + line[q1+1:q2]]
2220 if use_non_tex_fonts == "true":
2221 document.header[i:i+1] = [f + ' ' + line.split()[1]]
2223 document.header[i:i+1] = [f + ' ' + line.split()[0]]
2227 def revert_solution(document):
2228 " Reverts the solution environment of the theorem module to TeX code "
2230 # Do we use one of the modules that provides Solution?
2232 mods = document.get_module_list()
2234 if mod == "theorems-std" or mod == "theorems-bytype" \
2235 or mod == "theorems-ams" or mod == "theorems-ams-bytype":
2245 i = find_token(document.body, "\\begin_layout Solution", i)
2249 is_starred = document.body[i].startswith("\\begin_layout Solution*")
2250 if is_starred == True:
2252 LyXName = "Solution*"
2253 theoremName = "newtheorem*"
2256 LyXName = "Solution"
2257 theoremName = "newtheorem"
2259 j = find_end_of_layout(document.body, i)
2261 document.warning("Malformed LyX document: Can't find end of " + LyXName + " layout")
2265 # if this is not a consecutive env, add start command
2268 begcmd = put_cmd_in_ert("\\begin{%s}" % (LaTeXName))
2270 # has this a consecutive theorem of same type?
2271 consecutive = document.body[j + 2] == "\\begin_layout " + LyXName
2273 # if this is not followed by a consecutive env, add end command
2275 document.body[j : j + 1] = put_cmd_in_ert("\\end{%s}" % (LaTeXName)) + ["\\end_layout"]
2277 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
2279 add_to_preamble(document, "\\theoremstyle{definition}")
2280 if is_starred or mod == "theorems-bytype" or mod == "theorems-ams-bytype":
2281 add_to_preamble(document, "\\%s{%s}{\\protect\\solutionname}" % \
2282 (theoremName, LaTeXName))
2283 else: # mod == "theorems-std" or mod == "theorems-ams" and not is_starred
2284 add_to_preamble(document, "\\%s{%s}[thm]{\\protect\\solutionname}" % \
2285 (theoremName, LaTeXName))
2287 add_to_preamble(document, "\\providecommand{\solutionname}{Solution}")
2291 def revert_verbatim_star(document):
2292 from lyx_2_1 import revert_verbatim
2293 revert_verbatim(document, True)
2296 def convert_save_props(document):
2297 " Add save_transient_properties parameter. "
2298 i = find_token(document.header, '\\begin_header', 0)
2300 document.warning("Malformed lyx document: Missing '\\begin_header'.")
2302 document.header.insert(i + 1, '\\save_transient_properties true')
2305 def revert_save_props(document):
2306 " Remove save_transient_properties parameter. "
2307 i = find_token(document.header, "\\save_transient_properties", 0)
2310 del document.header[i]
2313 def convert_info_tabular_feature(document):
2315 return arg.replace("inset-modify tabular", "tabular-feature")
2316 convert_info_insets(document, "shortcut(s)?|icon", f)
2319 def revert_info_tabular_feature(document):
2321 return arg.replace("tabular-feature", "inset-modify tabular")
2322 convert_info_insets(document, "shortcut(s)?|icon", f)
2329 supported_versions = ["2.2.0", "2.2"]
2331 [475, [convert_separator]],
2332 # nothing to do for 476: We consider it a bug that older versions
2333 # did not load amsmath automatically for these commands, and do not
2334 # want to hardcode amsmath off.
2340 [481, [convert_dashes]],
2341 [482, [convert_phrases]],
2342 [483, [convert_specialchar]],
2347 [488, [convert_newgloss]],
2348 [489, [convert_BoxFeatures]],
2349 [490, [convert_origin]],
2351 [492, [convert_colorbox]],
2354 [495, [convert_subref]],
2355 [496, [convert_nounzip]],
2356 [497, [convert_external_bbox]],
2358 [499, [convert_moderncv_phone, convert_moderncv_name]],
2360 [501, [convert_fontsettings]],
2363 [504, [convert_save_props]],
2365 [506, [convert_info_tabular_feature]],
2366 [507, [convert_longtable_label]],
2367 [508, [convert_parbreak]]
2371 [507, [revert_parbreak]],
2372 [506, [revert_longtable_label]],
2373 [505, [revert_info_tabular_feature]],
2375 [503, [revert_save_props]],
2376 [502, [revert_verbatim_star]],
2377 [501, [revert_solution]],
2378 [500, [revert_fontsettings]],
2379 [499, [revert_achemso]],
2380 [498, [revert_moderncv_1, revert_moderncv_2]],
2381 [497, [revert_tcolorbox_1, revert_tcolorbox_2,
2382 revert_tcolorbox_3, revert_tcolorbox_4, revert_tcolorbox_5,
2383 revert_tcolorbox_6, revert_tcolorbox_7, revert_tcolorbox_8]],
2384 [496, [revert_external_bbox]],
2385 [495, []], # nothing to do since the noUnzip parameter was optional
2386 [494, [revert_subref]],
2387 [493, [revert_jss]],
2388 [492, [revert_mathmulticol]],
2389 [491, [revert_colorbox]],
2390 [490, [revert_textcolor]],
2391 [489, [revert_origin]],
2392 [488, [revert_BoxFeatures]],
2393 [487, [revert_newgloss, revert_glossgroup]],
2394 [486, [revert_forest]],
2395 [485, [revert_ex_itemargs]],
2396 [484, [revert_sigplan_doi]],
2397 [483, [revert_georgian]],
2398 [482, [revert_specialchar]],
2399 [481, [revert_phrases]],
2400 [480, [revert_dashes]],
2401 [479, [revert_question_env]],
2402 [478, [revert_beamer_lemma]],
2403 [477, [revert_xarrow]],
2404 [476, [revert_swissgerman]],
2405 [475, [revert_smash]],
2406 [474, [revert_separator]]
2410 if __name__ == "__main__":