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_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):
626 if (len(words) > 1 and words[0] == "\\begin_inset"
627 and (words[1] in ["CommandInset", "ERT", "External", "Formula",
628 "FormulaMacro", "Graphics", "IPA", "listings"]
629 or line.endswith("Flex Code"))):
630 # must not replace anything in insets that store LaTeX contents in .lyx files
631 # (math and command insets without overridden read() and write() methods
632 # filtering out IPA makes Text::readParToken() more simple
633 # skip ERT as well since it is not needed there
634 # Flex Code is logical markup, typically rendered as typewriter
635 j = find_end_of_inset(lines, i)
637 document.warning("Malformed LyX document: Can't find end of " +
638 words[1] + " inset at line " + str(i))
642 if lines[i] == "\\begin_layout LyX-Code":
643 j = find_end_of_layout(lines, i)
645 document.warning("Malformed LyX document: "
646 "Can't find end of %s layout at line %d" % (words[1],i))
650 if line.startswith("\\labelwidthstring"):
651 # skip label width string (bug 10243)
655 # We can have an arbitrary number of consecutive hyphens.
656 # Replace as LaTeX does: First try emdash, then endash
657 line = line.replace("---", "\\threehyphens\n")
658 line = line.replace("--", "\\twohyphens\n")
659 lines[i:i+1] = line.splitlines()
661 # remove ligature breaks between dashes
663 while i < len(lines):
665 if (line.endswith(r"-\SpecialChar \textcompwordmark{}") and
666 lines[i+1].startswith("-")):
667 lines[i] = line.replace(r"\SpecialChar \textcompwordmark{}",
673 def revert_dashes(document):
675 Prevent ligatures of existing --- and --.
676 Revert \\twohyphens and \\threehyphens to -- and ---.
677 Remove preamble code from 2.3->2.2 conversion.
679 del_complete_lines(document.preamble,
680 ['% Added by lyx2lyx',
681 r'\renewcommand{\textendash}{--}',
682 r'\renewcommand{\textemdash}{---}'])
683 # Insert ligature breaks to prevent ligation of hyphens to dashes:
684 lines = document.body
686 while i+1 < len(lines):
689 # skip label width string (bug 10243):
690 if line.startswith("\\labelwidthstring"):
692 # do not touch hyphens in some insets (cf. convert_dashes):
693 if line.startswith("\\begin_inset"):
695 if line.split()[1] in ["CommandInset", "ERT", "External",
696 "Formula", "FormulaMacro", "Graphics",
698 j = find_end_of_inset(lines, i)
700 document.warning("Malformed LyX document: Can't find "
701 "end of %s inset at line %d." % (itype, i))
707 line = line.replace("--", "-\\SpecialChar \\textcompwordmark{}\n-")
708 document.body[i:i+1] = line.split('\n')
709 # Revert \twohyphens and \threehyphens:
711 while i < len(lines):
713 if not line.endswith("hyphens"):
715 elif line.endswith("\\twohyphens") or line.endswith("\\threehyphens"):
716 line = line.replace("\\twohyphens", "--")
717 line = line.replace("\\threehyphens", "---")
718 lines[i] = line + lines.pop(i+1)
723 # order is important for the last three!
724 phrases = ["LyX", "LaTeX2e", "LaTeX", "TeX"]
726 def is_part_of_converted_phrase(line, j, phrase):
727 "is phrase part of an already converted phrase?"
729 converted = "\\SpecialCharNoPassThru \\" + p
730 pos = j + len(phrase) - len(converted)
732 if line[pos:pos+len(converted)] == converted:
737 def convert_phrases(document):
738 "convert special phrases from plain text to \\SpecialCharNoPassThru"
740 if document.backend != "latex":
743 for phrase in phrases:
745 while i < len(document.body):
746 words = document.body[i].split()
747 if len(words) > 1 and words[0] == "\\begin_inset" and \
748 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
749 # must not replace anything in insets that store LaTeX contents in .lyx files
750 # (math and command insets withut overridden read() and write() methods
751 j = find_end_of_inset(document.body, i)
753 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
758 if document.body[i].find("\\") == 0:
761 j = document.body[i].find(phrase)
765 if not is_part_of_converted_phrase(document.body[i], j, phrase):
766 front = document.body[i][:j]
767 back = document.body[i][j+len(phrase):]
769 document.body.insert(i+1, back)
770 # We cannot use SpecialChar since we do not know whether we are outside passThru
771 document.body[i] = front + "\\SpecialCharNoPassThru \\" + phrase
775 def revert_phrases(document):
776 "convert special phrases to plain text"
779 while i < len(document.body):
780 words = document.body[i].split()
781 if len(words) > 1 and words[0] == "\\begin_inset" and \
782 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
783 # see convert_phrases
784 j = find_end_of_inset(document.body, i)
786 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
792 for phrase in phrases:
793 # we can replace SpecialChar since LyX ensures that it cannot be inserted into passThru parts
794 if document.body[i].find("\\SpecialChar \\" + phrase) >= 0:
795 document.body[i] = document.body[i].replace("\\SpecialChar \\" + phrase, phrase)
797 if document.body[i].find("\\SpecialCharNoPassThru \\" + phrase) >= 0:
798 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru \\" + phrase, phrase)
800 if replaced and i+1 < len(document.body) and \
801 (document.body[i+1].find("\\") != 0 or \
802 document.body[i+1].find("\\SpecialChar") == 0) and \
803 len(document.body[i]) + len(document.body[i+1]) <= 80:
804 document.body[i] = document.body[i] + document.body[i+1]
805 document.body[i+1:i+2] = []
810 def convert_specialchar_internal(document, forward):
811 specialchars = {"\\-":"softhyphen", "\\textcompwordmark{}":"ligaturebreak", \
812 "\\@.":"endofsentence", "\\ldots{}":"ldots", \
813 "\\menuseparator":"menuseparator", "\\slash{}":"breakableslash", \
814 "\\nobreakdash-":"nobreakdash", "\\LyX":"LyX", \
815 "\\TeX":"TeX", "\\LaTeX2e":"LaTeX2e", \
816 "\\LaTeX":"LaTeX" # must be after LaTeX2e
820 while i < len(document.body):
821 words = document.body[i].split()
822 if len(words) > 1 and words[0] == "\\begin_inset" and \
823 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
824 # see convert_phrases
825 j = find_end_of_inset(document.body, i)
827 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
832 for key, value in specialchars.items():
834 document.body[i] = document.body[i].replace("\\SpecialChar " + key, "\\SpecialChar " + value)
835 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + key, "\\SpecialCharNoPassThru " + value)
837 document.body[i] = document.body[i].replace("\\SpecialChar " + value, "\\SpecialChar " + key)
838 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + value, "\\SpecialCharNoPassThru " + key)
842 def convert_specialchar(document):
843 "convert special characters to new syntax"
844 convert_specialchar_internal(document, True)
847 def revert_specialchar(document):
848 "convert special characters to old syntax"
849 convert_specialchar_internal(document, False)
852 def revert_georgian(document):
853 "Set the document language to English but assure Georgian output"
855 if document.language == "georgian":
856 document.language = "english"
857 i = find_token(document.header, "\\language georgian", 0)
859 document.header[i] = "\\language english"
860 j = find_token(document.header, "\\language_package default", 0)
862 document.header[j] = "\\language_package babel"
863 k = find_token(document.header, "\\options", 0)
865 document.header[k] = document.header[k].replace("\\options", "\\options georgian,")
867 l = find_token(document.header, "\\use_default_options", 0)
868 document.header.insert(l + 1, "\\options georgian")
871 def revert_sigplan_doi(document):
872 " Reverts sigplanconf DOI layout to ERT "
874 if document.textclass != "sigplanconf":
879 i = find_token(document.body, "\\begin_layout DOI", i)
882 j = find_end_of_layout(document.body, i)
884 document.warning("Malformed LyX document: Can't find end of DOI layout")
888 content = lyx2latex(document, document.body[i:j + 1])
889 add_to_preamble(document, ["\\doi{" + content + "}"])
890 del document.body[i:j + 1]
894 def revert_ex_itemargs(document):
895 " Reverts \\item arguments of the example environments (Linguistics module) to TeX-code "
897 if not "linguistics" in document.get_module_list():
901 example_layouts = ["Numbered Examples (consecutive)", "Subexample"]
903 i = find_token(document.body, "\\begin_inset Argument item:", i)
906 j = find_end_of_inset(document.body, i)
907 # Find containing paragraph layout
908 parent = get_containing_layout(document.body, i)
910 document.warning("Malformed LyX document: Can't find parent paragraph layout")
914 layoutname = parent[0]
915 if layoutname in example_layouts:
916 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
917 endPlain = find_end_of_layout(document.body, beginPlain)
918 content = document.body[beginPlain + 1 : endPlain]
919 del document.body[i:j+1]
920 subst = put_cmd_in_ert("[") + content + put_cmd_in_ert("]")
921 document.body[parbeg : parbeg] = subst
925 def revert_forest(document):
926 " Reverts the forest environment (Linguistics module) to TeX-code "
928 if not "linguistics" in document.get_module_list():
933 i = find_token(document.body, "\\begin_inset Flex Structure Tree", i)
936 j = find_end_of_inset(document.body, i)
938 document.warning("Malformed LyX document: Can't find end of Structure Tree inset")
942 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
943 endPlain = find_end_of_layout(document.body, beginPlain)
944 content = lyx2latex(document, document.body[beginPlain : endPlain])
946 add_to_preamble(document, ["\\usepackage{forest}"])
948 document.body[i:j + 1] = put_cmd_in_ert("\\begin{forest}" + content + "\\end{forest}")
952 def revert_glossgroup(document):
953 " Reverts the GroupGlossedWords inset (Linguistics module) to TeX-code "
955 if not "linguistics" in document.get_module_list():
960 i = find_token(document.body, "\\begin_inset Flex GroupGlossedWords", i)
963 j = find_end_of_inset(document.body, i)
965 document.warning("Malformed LyX document: Can't find end of GroupGlossedWords inset")
969 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
970 endPlain = find_end_of_layout(document.body, beginPlain)
971 content = lyx2verbatim(document, document.body[beginPlain : endPlain])
973 document.body[i:j + 1] = ["{", "", content, "", "}"]
977 def revert_newgloss(document):
978 " Reverts the new Glosse insets (Linguistics module) to the old format "
980 if not "linguistics" in document.get_module_list():
983 glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
984 for glosse in glosses:
987 i = find_token(document.body, glosse, i)
990 j = find_end_of_inset(document.body, i)
992 document.warning("Malformed LyX document: Can't find end of Glosse inset")
996 arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
997 endarg = find_end_of_inset(document.body, arg)
1000 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
1001 if argbeginPlain == -1:
1002 document.warning("Malformed LyX document: Can't find arg plain Layout")
1005 argendPlain = find_end_of_inset(document.body, argbeginPlain)
1006 argcontent = lyx2verbatim(document, document.body[argbeginPlain : argendPlain - 2])
1008 document.body[j:j] = ["", "\\begin_layout Plain Layout","\\backslash", "glt ",
1009 argcontent, "\\end_layout"]
1011 # remove Arg insets and paragraph, if it only contains this inset
1012 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
1013 del document.body[arg - 1 : endarg + 4]
1015 del document.body[arg : endarg + 1]
1017 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1018 endPlain = find_end_of_layout(document.body, beginPlain)
1019 content = lyx2verbatim(document, document.body[beginPlain : endPlain])
1021 document.body[beginPlain + 1:endPlain] = [content]
1024 # Dissolve ERT insets
1025 for glosse in glosses:
1028 i = find_token(document.body, glosse, i)
1031 j = find_end_of_inset(document.body, i)
1033 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1037 ert = find_token(document.body, "\\begin_inset ERT", i, j)
1040 ertend = find_end_of_inset(document.body, ert)
1042 document.warning("Malformed LyX document: Can't find end of ERT inset")
1045 ertcontent = get_ert(document.body, ert, True)
1046 document.body[ert : ertend + 1] = [ertcontent]
1050 def convert_newgloss(document):
1051 " Converts Glosse insets (Linguistics module) to the new format "
1053 if not "linguistics" in document.get_module_list():
1056 glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
1057 for glosse in glosses:
1060 i = find_token(document.body, glosse, i)
1063 j = find_end_of_inset(document.body, i)
1065 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1072 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", k, j)
1073 if beginPlain == -1:
1075 endPlain = find_end_of_layout(document.body, beginPlain)
1077 document.warning("Malformed LyX document: Can't find end of Glosse layout")
1081 glt = find_token(document.body, "\\backslash", beginPlain, endPlain)
1082 if glt != -1 and document.body[glt + 1].startswith("glt"):
1083 document.body[glt + 1] = document.body[glt + 1].lstrip("glt").lstrip()
1084 argcontent = document.body[glt + 1 : endPlain]
1085 document.body[beginPlain + 1 : endPlain] = ["\\begin_inset Argument 1", "status open", "",
1086 "\\begin_layout Plain Layout", "\\begin_inset ERT", "status open", "",
1087 "\\begin_layout Plain Layout", ""] + argcontent + ["\\end_layout", "", "\\end_inset", "",
1088 "\\end_layout", "", "\\end_inset"]
1090 content = document.body[beginPlain + 1 : endPlain]
1091 document.body[beginPlain + 1 : endPlain] = ["\\begin_inset ERT", "status open", "",
1092 "\\begin_layout Plain Layout"] + content + ["\\end_layout", "", "\\end_inset"]
1094 endPlain = find_end_of_layout(document.body, beginPlain)
1096 j = find_end_of_inset(document.body, i)
1101 def convert_BoxFeatures(document):
1102 " adds new box features "
1106 i = find_token(document.body, "height_special", i)
1109 document.body[i+1:i+1] = ['thickness "0.4pt"', 'separation "3pt"', 'shadowsize "4pt"']
1113 def revert_BoxFeatures(document):
1114 " outputs new box features as TeX code "
1118 defaultThick = "0.4pt"
1119 defaultShadow = "4pt"
1121 i = find_token(document.body, "thickness", i)
1124 binset = find_token(document.body, "\\begin_inset Box", i - 11)
1125 if binset == -1 or binset != i - 11:
1127 continue # then "thickness" is is just a word in the text
1128 einset = find_end_of_inset(document.body, binset)
1130 document.warning("Malformed LyX document: Can't find end of box inset!")
1133 # read out the values
1134 beg = document.body[i].find('"');
1135 end = document.body[i].rfind('"');
1136 thickness = document.body[i][beg+1:end];
1137 beg = document.body[i+1].find('"');
1138 end = document.body[i+1].rfind('"');
1139 separation = document.body[i+1][beg+1:end];
1140 beg = document.body[i+2].find('"');
1141 end = document.body[i+2].rfind('"');
1142 shadowsize = document.body[i+2][beg+1:end];
1143 # delete the specification
1144 del document.body[i:i+3]
1146 # first output the closing brace
1147 if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1148 document.body[einset -1 : einset - 1] = put_cmd_in_ert("}")
1149 # we have now the problem that if there is already \(f)colorbox in ERT around the inset
1150 # the ERT from this routine must be around it
1151 regexp = re.compile(r'^.*colorbox{.*$')
1152 pos = find_re(document.body, regexp, binset - 4)
1153 if pos != -1 and pos == binset - 4:
1157 # now output the lengths
1158 if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1159 document.body[pos : pos] = put_cmd_in_ert("{")
1160 if thickness != defaultThick:
1161 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness]
1162 if separation != defaultSep and thickness == defaultThick:
1163 document.body[pos + 5 : pos +6] = ["{\\backslash fboxsep " + separation]
1164 if separation != defaultSep and thickness != defaultThick:
1165 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation]
1166 if shadowsize != defaultShadow and separation == defaultSep and thickness == defaultThick:
1167 document.body[pos + 5 : pos +6] = ["{\\backslash shadowsize " + shadowsize]
1168 if shadowsize != defaultShadow and separation != defaultSep and thickness == defaultThick:
1169 document.body[pos + 5 : pos +6] = ["{\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1170 if shadowsize != defaultShadow and separation == defaultSep and thickness != defaultThick:
1171 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash shadowsize " + shadowsize]
1172 if shadowsize != defaultShadow and separation != defaultSep and thickness != defaultThick:
1173 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1176 def convert_origin(document):
1177 " Insert the origin tag "
1179 i = find_token(document.header, "\\textclass ", 0)
1181 document.warning("Malformed LyX document: No \\textclass!!")
1183 if document.dir == u'':
1187 if document.systemlyxdir and document.systemlyxdir != u'':
1189 if os.path.isabs(document.dir):
1190 absdir = os.path.normpath(document.dir)
1192 absdir = os.path.normpath(os.path.abspath(document.dir))
1193 if os.path.isabs(document.systemlyxdir):
1194 abssys = os.path.normpath(document.systemlyxdir)
1196 abssys = os.path.normpath(os.path.abspath(document.systemlyxdir))
1197 relpath = os.path.relpath(absdir, abssys)
1198 if relpath.find(u'..') == 0:
1203 origin = document.dir.replace(u'\\', u'/') + u'/'
1205 origin = os.path.join(u"/systemlyxdir", relpath).replace(u'\\', u'/') + u'/'
1206 document.header[i:i] = ["\\origin " + origin]
1209 def revert_origin(document):
1210 " Remove the origin tag "
1212 i = find_token(document.header, "\\origin ", 0)
1214 document.warning("Malformed LyX document: No \\origin!!")
1216 del document.header[i]
1219 color_names = ["brown", "darkgray", "gray", \
1220 "lightgray", "lime", "olive", "orange", \
1221 "pink", "purple", "teal", "violet"]
1223 def revert_textcolor(document):
1224 " revert new \\textcolor colors to TeX code "
1230 i = find_token(document.body, "\\color ", i)
1234 for color in list(color_names):
1235 if document.body[i] == "\\color " + color:
1236 # register that xcolor must be loaded in the preamble
1239 add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"])
1240 # find the next \\color and/or the next \\end_layout
1241 j = find_token(document.body, "\\color", i + 1)
1242 k = find_token(document.body, "\\end_layout", i + 1)
1243 if j == -1 and k != -1:
1246 # first output the closing brace
1248 document.body[k: k] = put_cmd_in_ert("}")
1250 document.body[j: j] = put_cmd_in_ert("}")
1251 # now output the \textcolor command
1252 document.body[i : i + 1] = put_cmd_in_ert("\\textcolor{" + color + "}{")
1256 def convert_colorbox(document):
1257 "Add color settings for boxes."
1260 i = find_token(document.body, "shadowsize", i)
1263 # check whether this is really a LyX Box setting
1264 start, end = is_in_inset(document.body, i, "\\begin_inset Box")
1266 find_token(document.body, "\\begin_layout", start, i) != -1):
1269 document.body[i+1:i+1] = ['framecolor "black"',
1270 'backgroundcolor "none"']
1274 def revert_colorbox(document):
1275 " outputs color settings for boxes as TeX code "
1278 defaultframecolor = "black"
1279 defaultbackcolor = "none"
1281 i = find_token(document.body, "framecolor", i)
1284 binset = find_token(document.body, "\\begin_inset Box", i - 14)
1287 einset = find_end_of_inset(document.body, binset)
1289 document.warning("Malformed LyX document: Can't find end of box inset!")
1291 # read out the values
1292 beg = document.body[i].find('"');
1293 end = document.body[i].rfind('"');
1294 framecolor = document.body[i][beg+1:end];
1295 beg = document.body[i + 1].find('"');
1296 end = document.body[i + 1].rfind('"');
1297 backcolor = document.body[i+1][beg+1:end];
1298 # delete the specification
1299 del document.body[i:i + 2]
1301 # first output the closing brace
1302 if framecolor != defaultframecolor or backcolor != defaultbackcolor:
1303 add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"])
1304 document.body[einset : einset] = put_cmd_in_ert("}")
1305 # determine the box type
1306 isBox = find_token(document.body, "\\begin_inset Box Boxed", binset)
1307 # now output the box commands
1308 if (framecolor != defaultframecolor and isBox == binset) or (backcolor != defaultbackcolor and isBox == binset):
1309 document.body[i - 14 : i - 14] = put_cmd_in_ert("\\fcolorbox{" + framecolor + "}{" + backcolor + "}{")
1310 # in the case we must also change the box type because the ERT code adds a frame
1311 document.body[i - 4] = "\\begin_inset Box Frameless"
1312 # if has_inner_box 0 we must set it and use_makebox to 1
1313 ibox = find_token(document.body, "has_inner_box", i - 4)
1314 if ibox == -1 or ibox != i - 1:
1315 document.warning("Malformed LyX document: Can't find has_inner_box statement!")
1317 # read out the value
1318 innerbox = document.body[ibox][-1:];
1320 document.body[ibox] = "has_inner_box 1"
1321 document.body[ibox + 3] = "use_makebox 1"
1322 if backcolor != defaultbackcolor and isBox != binset:
1323 document.body[i - 14 : i - 14] = put_cmd_in_ert("\\colorbox{" + backcolor + "}{")
1326 def revert_mathmulticol(document):
1327 " Convert formulas to ERT if they contain multicolumns "
1331 i = find_token(document.body, '\\begin_inset Formula', i)
1334 j = find_end_of_inset(document.body, i)
1336 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
1339 lines = document.body[i:j]
1340 lines[0] = lines[0].replace('\\begin_inset Formula', '').lstrip()
1341 code = "\n".join(lines)
1346 n = code.find("\\multicolumn", k)
1347 # no need to convert degenerated multicolumn cells,
1348 # they work in old LyX versions as "math ERT"
1349 if n != -1 and code.find("\\multicolumn{1}", k) != n:
1350 ert = put_cmd_in_ert(code)
1351 document.body[i:j+1] = ert
1357 i = find_end_of_inset(document.body, i)
1362 def revert_jss(document):
1363 " Reverts JSS In_Preamble commands to ERT in preamble "
1365 if document.textclass != "jss":
1374 # at first revert the inset layouts because they can be part of the In_Preamble layouts
1375 while m != -1 or j != -1 or h != -1 or k != -1 or n != -1:
1378 h = find_token(document.body, "\\begin_inset Flex Pkg", h)
1380 endh = find_end_of_inset(document.body, h)
1381 document.body[endh - 2 : endh + 1] = put_cmd_in_ert("}")
1382 document.body[h : h + 4] = put_cmd_in_ert("\\pkg{")
1386 m = find_token(document.body, "\\begin_inset Flex Proglang", m)
1388 endm = find_end_of_inset(document.body, m)
1389 document.body[endm - 2 : endm + 1] = put_cmd_in_ert("}")
1390 document.body[m : m + 4] = put_cmd_in_ert("\\proglang{")
1394 j = find_token(document.body, "\\begin_inset Flex Code", j)
1396 # assure that we are not in a Code Chunk inset
1397 if document.body[j][-1] == "e":
1398 endj = find_end_of_inset(document.body, j)
1399 document.body[endj - 2 : endj + 1] = put_cmd_in_ert("}")
1400 document.body[j : j + 4] = put_cmd_in_ert("\\code{")
1406 k = find_token(document.body, "\\begin_inset Flex E-mail", k)
1408 endk = find_end_of_inset(document.body, k)
1409 document.body[endk - 2 : endk + 1] = put_cmd_in_ert("}")
1410 document.body[k : k + 4] = put_cmd_in_ert("\\email{")
1414 n = find_token(document.body, "\\begin_inset Flex URL", n)
1416 endn = find_end_of_inset(document.body, n)
1417 document.body[endn - 2 : endn + 1] = put_cmd_in_ert("}")
1418 document.body[n : n + 4] = put_cmd_in_ert("\\url{")
1420 # now revert the In_Preamble layouts
1422 i = find_token(document.body, "\\begin_layout Title", 0)
1425 j = find_end_of_layout(document.body, i)
1427 document.warning("Malformed LyX document: Can't find end of Title layout")
1430 content = lyx2latex(document, document.body[i:j + 1])
1431 add_to_preamble(document, ["\\title{" + content + "}"])
1432 del document.body[i:j + 1]
1434 i = find_token(document.body, "\\begin_layout Author", 0)
1437 j = find_end_of_layout(document.body, i)
1439 document.warning("Malformed LyX document: Can't find end of Author layout")
1442 content = lyx2latex(document, document.body[i:j + 1])
1443 add_to_preamble(document, ["\\author{" + content + "}"])
1444 del document.body[i:j + 1]
1446 i = find_token(document.body, "\\begin_layout Plain Author", 0)
1449 j = find_end_of_layout(document.body, i)
1451 document.warning("Malformed LyX document: Can't find end of Plain Author layout")
1454 content = lyx2latex(document, document.body[i:j + 1])
1455 add_to_preamble(document, ["\\Plainauthor{" + content + "}"])
1456 del document.body[i:j + 1]
1458 i = find_token(document.body, "\\begin_layout Plain Title", 0)
1461 j = find_end_of_layout(document.body, i)
1463 document.warning("Malformed LyX document: Can't find end of Plain Title layout")
1466 content = lyx2latex(document, document.body[i:j + 1])
1467 add_to_preamble(document, ["\\Plaintitle{" + content + "}"])
1468 del document.body[i:j + 1]
1470 i = find_token(document.body, "\\begin_layout Short Title", 0)
1473 j = find_end_of_layout(document.body, i)
1475 document.warning("Malformed LyX document: Can't find end of Short Title layout")
1478 content = lyx2latex(document, document.body[i:j + 1])
1479 add_to_preamble(document, ["\\Shorttitle{" + content + "}"])
1480 del document.body[i:j + 1]
1482 i = find_token(document.body, "\\begin_layout Abstract", 0)
1485 j = find_end_of_layout(document.body, i)
1487 document.warning("Malformed LyX document: Can't find end of Abstract layout")
1490 content = lyx2latex(document, document.body[i:j + 1])
1491 add_to_preamble(document, ["\\Abstract{" + content + "}"])
1492 del document.body[i:j + 1]
1494 i = find_token(document.body, "\\begin_layout Keywords", 0)
1497 j = find_end_of_layout(document.body, i)
1499 document.warning("Malformed LyX document: Can't find end of Keywords layout")
1502 content = lyx2latex(document, document.body[i:j + 1])
1503 add_to_preamble(document, ["\\Keywords{" + content + "}"])
1504 del document.body[i:j + 1]
1506 i = find_token(document.body, "\\begin_layout Plain Keywords", 0)
1509 j = find_end_of_layout(document.body, i)
1511 document.warning("Malformed LyX document: Can't find end of Plain Keywords layout")
1514 content = lyx2latex(document, document.body[i:j + 1])
1515 add_to_preamble(document, ["\\Plainkeywords{" + content + "}"])
1516 del document.body[i:j + 1]
1518 i = find_token(document.body, "\\begin_layout Address", 0)
1521 j = find_end_of_layout(document.body, i)
1523 document.warning("Malformed LyX document: Can't find end of Address layout")
1526 content = lyx2latex(document, document.body[i:j + 1])
1527 add_to_preamble(document, ["\\Address{" + content + "}"])
1528 del document.body[i:j + 1]
1529 # finally handle the code layouts
1534 while m != -1 or j != -1 or h != -1 or k != -1:
1537 h = find_token(document.body, "\\begin_inset Flex Code Chunk", h)
1539 endh = find_end_of_inset(document.body, h)
1540 document.body[endh : endh + 1] = put_cmd_in_ert("\\end{CodeChunk}")
1541 document.body[h : h + 3] = put_cmd_in_ert("\\begin{CodeChunk}")
1542 document.body[h - 1 : h] = ["\\begin_layout Standard"]
1546 j = find_token(document.body, "\\begin_layout Code Input", j)
1548 endj = find_end_of_layout(document.body, j)
1549 document.body[endj : endj + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1550 document.body[endj + 3 : endj + 4] = put_cmd_in_ert("\\end{CodeInput}")
1551 document.body[endj + 13 : endj + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1552 document.body[j + 1 : j] = ["\\end_layout", "", "\\begin_layout Standard"]
1553 document.body[j : j + 1] = put_cmd_in_ert("\\begin{CodeInput}")
1557 k = find_token(document.body, "\\begin_layout Code Output", k)
1559 endk = find_end_of_layout(document.body, k)
1560 document.body[endk : endk + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1561 document.body[endk + 3 : endk + 4] = put_cmd_in_ert("\\end{CodeOutput}")
1562 document.body[endk + 13 : endk + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1563 document.body[k + 1 : k] = ["\\end_layout", "", "\\begin_layout Standard"]
1564 document.body[k : k + 1] = put_cmd_in_ert("\\begin{CodeOutput}")
1568 m = find_token(document.body, "\\begin_layout Code", m)
1570 endm = find_end_of_layout(document.body, m)
1571 document.body[endm : endm + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1572 document.body[endm + 3 : endm + 4] = put_cmd_in_ert("\\end{Code}")
1573 document.body[endm + 13 : endm + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1574 document.body[m + 1 : m] = ["\\end_layout", "", "\\begin_layout Standard"]
1575 document.body[m : m + 1] = put_cmd_in_ert("\\begin{Code}")
1579 def convert_subref(document):
1580 " converts sub: ref prefixes to subref: "
1583 rx = re.compile(r'^name \"sub:(.+)$')
1586 i = find_token(document.body, "\\begin_inset CommandInset label", i)
1589 j = find_end_of_inset(document.body, i)
1591 document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1595 for p in range(i, j):
1596 m = rx.match(document.body[p])
1599 document.body[p] = "name \"subsec:" + label
1603 rx = re.compile(r'^reference \"sub:(.+)$')
1606 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1609 j = find_end_of_inset(document.body, i)
1611 document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1615 for p in range(i, j):
1616 m = rx.match(document.body[p])
1619 document.body[p] = "reference \"subsec:" + label
1625 def revert_subref(document):
1626 " reverts subref: ref prefixes to sub: "
1629 rx = re.compile(r'^name \"subsec:(.+)$')
1632 i = find_token(document.body, "\\begin_inset CommandInset label", i)
1635 j = find_end_of_inset(document.body, i)
1637 document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1641 for p in range(i, j):
1642 m = rx.match(document.body[p])
1645 document.body[p] = "name \"sub:" + label
1650 rx = re.compile(r'^reference \"subsec:(.+)$')
1653 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1656 j = find_end_of_inset(document.body, i)
1658 document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1662 for p in range(i, j):
1663 m = rx.match(document.body[p])
1666 document.body[p] = "reference \"sub:" + label
1671 def convert_nounzip(document):
1672 " remove the noUnzip parameter of graphics insets "
1674 rx = re.compile(r'\s*noUnzip\s*$')
1677 i = find_token(document.body, "\\begin_inset Graphics", i)
1680 j = find_end_of_inset(document.body, i)
1682 document.warning("Malformed LyX document: Can't find end of graphics inset at line " + str(i))
1686 k = find_re(document.body, rx, i, j)
1688 del document.body[k]
1693 def convert_revert_external_bbox(document, forward):
1694 " add units to bounding box of external insets "
1696 rx = re.compile(r'^\s*boundingBox\s+\S+\s+\S+\s+\S+\s+\S+\s*$')
1699 i = find_token(document.body, "\\begin_inset External", i)
1702 j = find_end_of_inset(document.body, i)
1704 document.warning("Malformed LyX document: Can't find end of external inset at line " + str(i))
1707 k = find_re(document.body, rx, i, j)
1711 tokens = document.body[k].split()
1713 for t in range(1, 5):
1716 for t in range(1, 5):
1717 tokens[t] = length_in_bp(tokens[t])
1718 document.body[k] = "\tboundingBox " + tokens[1] + " " + tokens[2] + " " + \
1719 tokens[3] + " " + tokens[4]
1723 def convert_external_bbox(document):
1724 convert_revert_external_bbox(document, True)
1727 def revert_external_bbox(document):
1728 convert_revert_external_bbox(document, False)
1731 def revert_tcolorbox_1(document):
1732 " Reverts the Flex:Subtitle inset of tcolorbox to TeX-code "
1735 i = find_token(document.header, "tcolorbox", i)
1741 flex = find_token(document.body, "\\begin_inset Flex Subtitle", flex)
1744 flexEnd = find_end_of_inset(document.body, flex)
1745 wasOpt = revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, False, True, False)
1746 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, False, False, False)
1747 flexEnd = find_end_of_inset(document.body, flex)
1749 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\tcbsubtitle")
1751 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\tcbsubtitle{")
1752 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("}")
1756 def revert_tcolorbox_2(document):
1757 " Reverts the Flex:Raster_Color_Box inset of tcolorbox to TeX-code "
1760 i = find_token(document.header, "tcolorbox", i)
1766 flex = find_token(document.body, "\\begin_inset Flex Raster Color Box", flex)
1769 flexEnd = find_end_of_inset(document.body, flex)
1770 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1771 flexEnd = find_end_of_inset(document.body, flex)
1772 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{tcbraster}")
1773 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("\\end{tcbraster}")
1777 def revert_tcolorbox_3(document):
1778 " Reverts the Flex:Custom_Color_Box_1 inset of tcolorbox to TeX-code "
1781 i = find_token(document.header, "tcolorbox", i)
1787 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 1", flex)
1790 flexEnd = find_end_of_inset(document.body, flex)
1791 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1792 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1793 flexEnd = find_end_of_inset(document.body, flex)
1794 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxA}")
1795 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxA}")
1799 def revert_tcolorbox_4(document):
1800 " Reverts the Flex:Custom_Color_Box_2 inset of tcolorbox to TeX-code "
1803 i = find_token(document.header, "tcolorbox", i)
1809 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 2", flex)
1812 flexEnd = find_end_of_inset(document.body, flex)
1813 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1814 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1815 flexEnd = find_end_of_inset(document.body, flex)
1816 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxB}")
1817 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxB}")
1821 def revert_tcolorbox_5(document):
1822 " Reverts the Flex:Custom_Color_Box_3 inset of tcolorbox to TeX-code "
1825 i = find_token(document.header, "tcolorbox", i)
1831 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 3", flex)
1834 flexEnd = find_end_of_inset(document.body, flex)
1835 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1836 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1837 flexEnd = find_end_of_inset(document.body, flex)
1838 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxC}")
1839 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxC}")
1843 def revert_tcolorbox_6(document):
1844 " Reverts the Flex:Custom_Color_Box_4 inset of tcolorbox to TeX-code "
1847 i = find_token(document.header, "tcolorbox", i)
1853 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 4", flex)
1856 flexEnd = find_end_of_inset(document.body, flex)
1857 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1858 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1859 flexEnd = find_end_of_inset(document.body, flex)
1860 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxD}")
1861 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxD}")
1865 def revert_tcolorbox_7(document):
1866 " Reverts the Flex:Custom_Color_Box_5 inset of tcolorbox to TeX-code "
1869 i = find_token(document.header, "tcolorbox", i)
1875 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 5", flex)
1878 flexEnd = find_end_of_inset(document.body, flex)
1879 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1880 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1881 flexEnd = find_end_of_inset(document.body, flex)
1882 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxE}")
1883 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxE}")
1887 def revert_tcolorbox_8(document):
1888 " Reverts the layout New Color Box Type of tcolorbox to TeX-code "
1894 i = find_token(document.body, "\\begin_layout New Color Box Type", i)
1896 j = find_end_of_layout(document.body, i)
1897 wasOpt = revert_Argument_to_TeX_brace(document, i, j, 1, 1, False, True, False)
1898 revert_Argument_to_TeX_brace(document, i, 0, 2, 2, False, False, True)
1899 revert_Argument_to_TeX_brace(document, i, 0, 3, 4, False, True, False)
1900 document.body[i] = document.body[i].replace("\\begin_layout New Color Box Type", "\\begin_layout Standard")
1902 document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox")
1904 document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox{")
1905 k = find_end_of_inset(document.body, j)
1906 k = find_token(document.body, "\\end_inset", k + 1)
1907 k = find_token(document.body, "\\end_inset", k + 1)
1909 k = find_token(document.body, "\\end_inset", k + 1)
1910 document.body[k + 2 : j + 2] = put_cmd_in_ert("{")
1911 j = find_token(document.body, "\\begin_layout Standard", j + 1)
1912 document.body[j - 2 : j - 2] = put_cmd_in_ert("}")
1918 def revert_moderncv_1(document):
1919 " Reverts the new inset of moderncv to TeX-code in preamble "
1921 if document.textclass != "moderncv":
1927 # at first revert the new styles
1929 i = find_token(document.body, "\\begin_layout CVIcons", 0)
1932 j = find_end_of_layout(document.body, i)
1934 document.warning("Malformed LyX document: Can't find end of CVIcons layout")
1937 content = lyx2latex(document, document.body[i:j + 1])
1938 add_to_preamble(document, ["\\moderncvicons{" + content + "}"])
1939 del document.body[i:j + 1]
1941 i = find_token(document.body, "\\begin_layout CVColumnWidth", 0)
1944 j = find_end_of_layout(document.body, i)
1946 document.warning("Malformed LyX document: Can't find end of CVColumnWidth layout")
1949 content = lyx2latex(document, document.body[i:j + 1])
1950 add_to_preamble(document, ["\\setlength{\hintscolumnwidth}{" + content + "}"])
1951 del document.body[i:j + 1]
1952 # now change the new styles to the obsolete ones
1954 i = find_token(document.body, "\\begin_layout Name", 0)
1957 j = find_end_of_layout(document.body, i)
1959 document.warning("Malformed LyX document: Can't find end of Name layout")
1962 lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
1963 if lineArg > j and j != 0:
1966 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
1967 # we have to assure that no other inset is in the Argument
1968 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
1969 endInset = find_token(document.body, "\\end_inset", beginPlain)
1972 while beginInset < endInset and beginInset != -1:
1973 beginInset = find_token(document.body, "\\begin_inset", k)
1974 endInset = find_token(document.body, "\\end_inset", l)
1977 Arg2 = document.body[l + 5 : l + 6]
1979 document.body[i : i + 1]= ["\\begin_layout FirstName"]
1980 # delete the Argument inset
1981 del( document.body[endInset - 2 : endInset + 3])
1982 del( document.body[lineArg : beginPlain + 1])
1983 document.body[i + 4 : i + 4]= ["\\begin_layout FamilyName"] + Arg2 + ["\\end_layout"] + [""]
1986 def revert_moderncv_2(document):
1987 " Reverts the phone inset of moderncv to the obsoleted mobile or fax "
1989 if document.textclass != "moderncv":
1996 i = find_token(document.body, "\\begin_layout Phone", i)
1999 j = find_end_of_layout(document.body, i)
2001 document.warning("Malformed LyX document: Can't find end of Phone layout")
2004 lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
2005 if lineArg > j and j != 0:
2009 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
2010 # we have to assure that no other inset is in the Argument
2011 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
2012 endInset = find_token(document.body, "\\end_inset", beginPlain)
2015 while beginInset < endInset and beginInset != -1:
2016 beginInset = find_token(document.body, "\\begin_inset", k)
2017 endInset = find_token(document.body, "\\end_inset", l)
2020 Arg = document.body[beginPlain + 1 : beginPlain + 2]
2022 if Arg[0] == "mobile":
2023 document.body[i : i + 1]= ["\\begin_layout Mobile"]
2025 document.body[i : i + 1]= ["\\begin_layout Fax"]
2026 # delete the Argument inset
2027 del(document.body[endInset - 2 : endInset + 1])
2028 del(document.body[lineArg : beginPlain + 3])
2032 def convert_moderncv_phone(document):
2033 " Convert the Fax and Mobile inset of moderncv to the new phone inset "
2035 if document.textclass != "moderncv":
2042 "Mobile" : "mobile",
2046 rx = re.compile(r'^\\begin_layout (\S+)$')
2048 # substitute \fax and \mobile by \phone[fax] and \phone[mobile], respectively
2049 i = find_token(document.body, "\\begin_layout", i)
2053 m = rx.match(document.body[i])
2057 if val not in list(phone_dict.keys()):
2060 j = find_end_of_layout(document.body, i)
2062 document.warning("Malformed LyX document: Can't find end of Mobile layout")
2066 document.body[i : i + 1] = ["\\begin_layout Phone", "\\begin_inset Argument 1", "status open", "",
2067 "\\begin_layout Plain Layout", phone_dict[val], "\\end_layout", "",
2071 def convert_moderncv_name(document):
2072 " Convert the FirstName and LastName layout of moderncv to the general Name layout "
2074 if document.textclass != "moderncv":
2077 fnb = 0 # Begin of FirstName inset
2078 fne = 0 # End of FirstName inset
2079 lnb = 0 # Begin of LastName (FamilyName) inset
2080 lne = 0 # End of LastName (FamilyName) inset
2081 nb = 0 # Begin of substituting Name inset
2082 ne = 0 # End of substituting Name inset
2083 FirstName = [] # FirstName content
2084 FamilyName = [] # LastName content
2088 fnb = find_token(document.body, "\\begin_layout FirstName", fnb)
2090 fne = find_end_of_layout(document.body, fnb)
2092 document.warning("Malformed LyX document: Can't find end of FirstName layout")
2094 FirstName = document.body[fnb + 1 : fne]
2096 lnb = find_token(document.body, "\\begin_layout FamilyName", lnb)
2098 lne = find_end_of_layout(document.body, lnb)
2100 document.warning("Malformed LyX document: Can't find end of FamilyName layout")
2102 FamilyName = document.body[lnb + 1 : lne]
2103 # Determine the region for the substituting Name layout
2104 if fnb == -1 and lnb == -1: # Neither FirstName nor FamilyName exists -> Do nothing
2106 elif fnb == -1: # Only FamilyName exists -> New Name insets replaces that
2109 elif lnb == -1: # Only FirstName exists -> New Name insets replaces that
2112 elif fne > lne: # FirstName position before FamilyName -> New Name insets spans
2113 nb = lnb # from FamilyName begin
2114 ne = fne # to FirstName end
2115 else: # FirstName position before FamilyName -> New Name insets spans
2116 nb = fnb # from FirstName begin
2117 ne = lne # to FamilyName end
2119 # Insert the substituting layout now. If FirstName exists, use an otpional argument.
2121 document.body[nb : ne + 1] = ["\\begin_layout Name"] + FamilyName + ["\\end_layout", ""]
2123 document.body[nb : ne + 1] = ["\\begin_layout Name", "\\begin_inset Argument 1", "status open", "",
2124 "\\begin_layout Plain Layout"] + FirstName + ["\\end_layout", "",
2125 "\\end_inset", ""] + FamilyName + ["\\end_layout", ""]
2128 def revert_achemso(document):
2129 " Reverts the flex inset Latin to TeX code "
2131 if document.textclass != "achemso":
2136 i = find_token(document.body, "\\begin_inset Flex Latin", i)
2138 j = find_end_of_inset(document.body, i)
2142 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2143 endPlain = find_end_of_layout(document.body, beginPlain)
2144 content = lyx2latex(document, document.body[beginPlain : endPlain])
2145 document.body[i:j + 1] = put_cmd_in_ert("\\latin{" + content + "}")
2147 document.warning("Malformed LyX document: Can't find end of flex inset Latin")
2152 fontsettings = ["\\font_roman", "\\font_sans", "\\font_typewriter", "\\font_math", \
2153 "\\font_sf_scale", "\\font_tt_scale"]
2154 fontdefaults = ["default", "default", "default", "auto", "100", "100"]
2155 fontquotes = [True, True, True, True, False, False]
2157 def convert_fontsettings(document):
2158 " Duplicate font settings "
2160 i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2162 document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2163 use_non_tex_fonts = "false"
2165 use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2167 for f in fontsettings:
2168 i = find_token(document.header, f + " ", 0)
2170 document.warning("Malformed LyX document: No " + f + "!")
2172 # note that with i = -1, this will insert at the end
2174 value = fontdefaults[j]
2176 value = document.header[i][len(f):].strip()
2178 if use_non_tex_fonts == "true":
2179 document.header[i:i+1] = [f + ' "' + fontdefaults[j] + '" "' + value + '"']
2181 document.header[i:i+1] = [f + ' "' + value + '" "' + fontdefaults[j] + '"']
2183 if use_non_tex_fonts == "true":
2184 document.header[i:i+1] = [f + ' ' + fontdefaults[j] + ' ' + value]
2186 document.header[i:i+1] = [f + ' ' + value + ' ' + fontdefaults[j]]
2190 def revert_fontsettings(document):
2191 " Merge font settings "
2193 i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2195 document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2196 use_non_tex_fonts = "false"
2198 use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2200 for f in fontsettings:
2201 i = find_token(document.header, f + " ", 0)
2203 document.warning("Malformed LyX document: No " + f + "!")
2206 line = get_value(document.header, f, i)
2209 q2 = line.find('"', q1+1)
2210 q3 = line.find('"', q2+1)
2211 q4 = line.find('"', q3+1)
2212 if q1 == -1 or q2 == -1 or q3 == -1 or q4 == -1:
2213 document.warning("Malformed LyX document: Missing quotes!")
2216 if use_non_tex_fonts == "true":
2217 document.header[i:i+1] = [f + ' ' + line[q3+1:q4]]
2219 document.header[i:i+1] = [f + ' ' + line[q1+1:q2]]
2221 if use_non_tex_fonts == "true":
2222 document.header[i:i+1] = [f + ' ' + line.split()[1]]
2224 document.header[i:i+1] = [f + ' ' + line.split()[0]]
2228 def revert_solution(document):
2229 " Reverts the solution environment of the theorem module to TeX code "
2231 # Do we use one of the modules that provides Solution?
2233 mods = document.get_module_list()
2235 if mod == "theorems-std" or mod == "theorems-bytype" \
2236 or mod == "theorems-ams" or mod == "theorems-ams-bytype":
2246 i = find_token(document.body, "\\begin_layout Solution", i)
2250 is_starred = document.body[i].startswith("\\begin_layout Solution*")
2251 if is_starred == True:
2253 LyXName = "Solution*"
2254 theoremName = "newtheorem*"
2257 LyXName = "Solution"
2258 theoremName = "newtheorem"
2260 j = find_end_of_layout(document.body, i)
2262 document.warning("Malformed LyX document: Can't find end of " + LyXName + " layout")
2266 # if this is not a consecutive env, add start command
2269 begcmd = put_cmd_in_ert("\\begin{%s}" % (LaTeXName))
2271 # has this a consecutive theorem of same type?
2272 consecutive = document.body[j + 2] == "\\begin_layout " + LyXName
2274 # if this is not followed by a consecutive env, add end command
2276 document.body[j : j + 1] = put_cmd_in_ert("\\end{%s}" % (LaTeXName)) + ["\\end_layout"]
2278 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
2280 add_to_preamble(document, "\\theoremstyle{definition}")
2281 if is_starred or mod == "theorems-bytype" or mod == "theorems-ams-bytype":
2282 add_to_preamble(document, "\\%s{%s}{\\protect\\solutionname}" % \
2283 (theoremName, LaTeXName))
2284 else: # mod == "theorems-std" or mod == "theorems-ams" and not is_starred
2285 add_to_preamble(document, "\\%s{%s}[thm]{\\protect\\solutionname}" % \
2286 (theoremName, LaTeXName))
2288 add_to_preamble(document, "\\providecommand{\solutionname}{Solution}")
2292 def revert_verbatim_star(document):
2293 from lyx_2_1 import revert_verbatim
2294 revert_verbatim(document, True)
2297 def convert_save_props(document):
2298 " Add save_transient_properties parameter. "
2299 i = find_token(document.header, '\\begin_header', 0)
2301 document.warning("Malformed lyx document: Missing '\\begin_header'.")
2303 document.header.insert(i + 1, '\\save_transient_properties true')
2306 def revert_save_props(document):
2307 " Remove save_transient_properties parameter. "
2308 i = find_token(document.header, "\\save_transient_properties", 0)
2311 del document.header[i]
2314 def convert_info_tabular_feature(document):
2316 return arg.replace("inset-modify tabular", "tabular-feature")
2317 convert_info_insets(document, "shortcut(s)?|icon", f)
2320 def revert_info_tabular_feature(document):
2322 return arg.replace("tabular-feature", "inset-modify tabular")
2323 convert_info_insets(document, "shortcut(s)?|icon", f)
2330 supported_versions = ["2.2.0", "2.2"]
2332 [475, [convert_separator]],
2333 # nothing to do for 476: We consider it a bug that older versions
2334 # did not load amsmath automatically for these commands, and do not
2335 # want to hardcode amsmath off.
2341 [481, [convert_dashes]],
2342 [482, [convert_phrases]],
2343 [483, [convert_specialchar]],
2348 [488, [convert_newgloss]],
2349 [489, [convert_BoxFeatures]],
2350 [490, [convert_origin]],
2352 [492, [convert_colorbox]],
2355 [495, [convert_subref]],
2356 [496, [convert_nounzip]],
2357 [497, [convert_external_bbox]],
2359 [499, [convert_moderncv_phone, convert_moderncv_name]],
2361 [501, [convert_fontsettings]],
2364 [504, [convert_save_props]],
2366 [506, [convert_info_tabular_feature]],
2367 [507, [convert_longtable_label]],
2368 [508, [convert_parbreak]]
2372 [507, [revert_parbreak]],
2373 [506, [revert_longtable_label]],
2374 [505, [revert_info_tabular_feature]],
2376 [503, [revert_save_props]],
2377 [502, [revert_verbatim_star]],
2378 [501, [revert_solution]],
2379 [500, [revert_fontsettings]],
2380 [499, [revert_achemso]],
2381 [498, [revert_moderncv_1, revert_moderncv_2]],
2382 [497, [revert_tcolorbox_1, revert_tcolorbox_2,
2383 revert_tcolorbox_3, revert_tcolorbox_4, revert_tcolorbox_5,
2384 revert_tcolorbox_6, revert_tcolorbox_7, revert_tcolorbox_8]],
2385 [496, [revert_external_bbox]],
2386 [495, []], # nothing to do since the noUnzip parameter was optional
2387 [494, [revert_subref]],
2388 [493, [revert_jss]],
2389 [492, [revert_mathmulticol]],
2390 [491, [revert_colorbox]],
2391 [490, [revert_textcolor]],
2392 [489, [revert_origin]],
2393 [488, [revert_BoxFeatures]],
2394 [487, [revert_newgloss, revert_glossgroup]],
2395 [486, [revert_forest]],
2396 [485, [revert_ex_itemargs]],
2397 [484, [revert_sigplan_doi]],
2398 [483, [revert_georgian]],
2399 [482, [revert_specialchar]],
2400 [481, [revert_phrases]],
2401 [480, [revert_dashes]],
2402 [479, [revert_question_env]],
2403 [478, [revert_beamer_lemma]],
2404 [477, [revert_xarrow]],
2405 [476, [revert_swissgerman]],
2406 [475, [revert_smash]],
2407 [474, [revert_separator]]
2411 if __name__ == "__main__":