1 # -*- coding: utf-8 -*-
2 # This file is part of lyx2lyx
3 # Copyright (C) 2015 The LyX team
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 """ Convert files to the file format generated by lyx 2.2"""
25 # Uncomment only what you need to import, please.
27 #from parser_tools import find_token, find_end_of, find_tokens, \
28 # find_token_exact, find_end_of_inset, find_end_of_layout, \
29 # find_token_backwards, is_in_inset, get_value, get_quoted_value, \
30 # del_token, check_token, get_option_value
32 from lyx2lyx_tools import add_to_preamble, put_cmd_in_ert, get_ert, lyx2latex, \
33 lyx2verbatim, length_in_bp, convert_info_insets
34 # insert_to_preamble, latex_length, revert_flex_inset, \
35 # revert_font_attrs, hex2ratio, str2bool
37 from parser_tools import find_token, find_token_backwards, find_re, \
38 find_end_of_inset, find_end_of_layout, find_nonempty_line, \
39 get_containing_layout, get_value, check_token
41 ####################################################################
42 # Private helper functions
44 def revert_Argument_to_TeX_brace(document, line, endline, n, nmax, environment, opt, nolastopt):
46 Reverts an InsetArgument to TeX-code
48 revert_Argument_to_TeX_brace(document, LineOfBegin, LineOfEnd, StartArgument, EndArgument, isEnvironment, isOpt, notLastOpt)
49 LineOfBegin is the line of the \begin_layout or \begin_inset statement
50 LineOfEnd is the line of the \end_layout or \end_inset statement, if "0" is given, the end of the file is used instead
51 StartArgument is the number of the first argument that needs to be converted
52 EndArgument is the number of the last argument that needs to be converted or the last defined one
53 isEnvironment must be true, if the layout is for a LaTeX environment
54 isOpt must be true, if the argument is an optional one
55 notLastOpt must be true if the argument is mandatory and followed by optional ones
59 while lineArg != -1 and n < nmax + 1:
60 lineArg = find_token(document.body, "\\begin_inset Argument " + str(n), line)
61 if lineArg > endline and endline != 0:
64 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
65 # we have to assure that no other inset is in the Argument
66 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
67 endInset = find_token(document.body, "\\end_inset", beginPlain)
70 while beginInset < endInset and beginInset != -1:
71 beginInset = find_token(document.body, "\\begin_inset", k)
72 endInset = find_token(document.body, "\\end_inset", l)
75 if environment == False:
77 if nolastopt == False:
78 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}{")
80 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}")
81 del(document.body[lineArg : beginPlain + 1])
84 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("]")
85 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("[")
89 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}")
90 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("{")
93 document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("]")
94 document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("[")
100 ###############################################################################
102 ### Conversion and reversion routines
104 ###############################################################################
106 def convert_longtable_label_internal(document, forward):
108 Convert reference to "LongTableNoNumber" into "Unnumbered" if forward is True
111 old_reference = "\\begin_inset Caption LongTableNoNumber"
112 new_reference = "\\begin_inset Caption Unnumbered"
114 # if the purpose is to revert swap the strings roles
116 old_reference, new_reference = new_reference, old_reference
120 i = find_token(document.body, old_reference, i)
125 document.body[i] = new_reference
128 def convert_longtable_label(document):
129 convert_longtable_label_internal(document, True)
132 def revert_longtable_label(document):
133 convert_longtable_label_internal(document, False)
136 def convert_separator(document):
138 Convert layout separators to separator insets and add (LaTeX) paragraph
139 breaks in order to mimic previous LaTeX export.
142 parins = ["\\begin_inset Separator parbreak", "\\end_inset", ""]
143 parlay = ["\\begin_layout Standard", "\\begin_inset Separator parbreak",
144 "\\end_inset", "", "\\end_layout", ""]
146 "family" : "default",
147 "series" : "default",
156 i = find_token(document.body, "\\begin_deeper", i)
160 j = find_token_backwards(document.body, "\\end_layout", i-1)
162 # reset any text style before inserting the inset
163 lay = get_containing_layout(document.body, j-1)
165 content = "\n".join(document.body[lay[1]:lay[2]])
166 for val in list(sty_dict.keys()):
167 if content.find("\\%s" % val) != -1:
168 document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
171 document.body[j:j] = parins
172 i = i + len(parins) + 1
178 i = find_token(document.body, "\\align", i)
182 lay = get_containing_layout(document.body, i)
183 if lay != False and lay[0] == "Plain Layout":
187 j = find_token_backwards(document.body, "\\end_layout", i-1)
189 # Very old LyX files do not have Plain Layout in insets (but Standard).
190 # So we additionally check here if there is no inset boundary
191 # between the previous layout and this one.
192 n = find_token(document.body, "\\end_inset", j, lay[1])
196 lay = get_containing_layout(document.body, j-1)
197 if lay != False and lay[0] == "Standard" \
198 and find_token(document.body, "\\align", lay[1], lay[2]) == -1 \
199 and find_token(document.body, "\\begin_inset VSpace", lay[1], lay[2]) == -1:
200 # reset any text style before inserting the inset
201 content = "\n".join(document.body[lay[1]:lay[2]])
202 for val in list(sty_dict.keys()):
203 if content.find("\\%s" % val) != -1:
204 document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
207 document.body[j:j] = parins
208 i = i + len(parins) + 1
214 regexp = re.compile(r'^\\begin_layout (?:(-*)|(\s*))(Separator|EndOfSlide)(?:(-*)|(\s*))$', re.IGNORECASE)
218 i = find_re(document.body, regexp, i)
222 j = find_end_of_layout(document.body, i)
224 document.warning("Malformed LyX document: Missing `\\end_layout'.")
227 lay = get_containing_layout(document.body, j-1)
229 lines = document.body[lay[3]:lay[2]]
233 document.body[i:j+1] = parlay
235 document.body[i+1:i+1] = lines
237 i = i + len(parlay) + len(lines) + 1
240 def revert_separator(document):
241 " Revert separator insets to layout separators "
243 beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
244 if document.textclass in beamer_classes:
245 beglaysep = "\\begin_layout Separator"
247 beglaysep = "\\begin_layout --Separator--"
249 parsep = [beglaysep, "", "\\end_layout", ""]
250 comert = ["\\begin_inset ERT", "status collapsed", "",
251 "\\begin_layout Plain Layout", "%", "\\end_layout",
252 "", "\\end_inset", ""]
253 empert = ["\\begin_inset ERT", "status collapsed", "",
254 "\\begin_layout Plain Layout", " ", "\\end_layout",
255 "", "\\end_inset", ""]
259 i = find_token(document.body, "\\begin_inset Separator", i)
263 lay = get_containing_layout(document.body, i)
265 document.warning("Malformed LyX document: Can't convert separator inset at line " + str(i))
272 kind = get_value(document.body, "\\begin_inset Separator", i, i+1, "plain").split()[1]
273 before = document.body[beg+1:i]
274 something_before = len(before) > 0 and len("".join(before)) > 0
275 j = find_end_of_inset(document.body, i)
276 after = document.body[j+1:end]
277 something_after = len(after) > 0 and len("".join(after)) > 0
279 beg = beg + len(before) + 1
280 elif something_before:
281 document.body[i:i] = ["\\end_layout", ""]
289 document.body[beg:j+1] = empert
292 document.body[beg:j+1] = comert
296 if layoutname == "Standard":
297 if not something_before:
298 document.body[beg:j+1] = parsep
300 document.body[i:i] = ["", "\\begin_layout Standard"]
303 document.body[beg:j+1] = ["\\begin_layout Standard"]
306 document.body[beg:j+1] = ["\\begin_deeper"]
308 end = end + 1 - (j + 1 - beg)
309 if not something_before:
310 document.body[i:i] = parsep
312 end = end + len(parsep)
313 document.body[i:i] = ["\\begin_layout Standard"]
314 document.body[end+2:end+2] = ["", "\\end_deeper", ""]
317 next_par_is_aligned = False
318 k = find_nonempty_line(document.body, end+1)
319 if k != -1 and check_token(document.body[k], "\\begin_layout"):
320 lay = get_containing_layout(document.body, k)
321 next_par_is_aligned = lay != False and \
322 find_token(document.body, "\\align", lay[1], lay[2]) != -1
323 if k != -1 and not next_par_is_aligned \
324 and not check_token(document.body[k], "\\end_deeper") \
325 and not check_token(document.body[k], "\\begin_deeper"):
326 if layoutname == "Standard":
327 document.body[beg:j+1] = [beglaysep]
330 document.body[beg:j+1] = ["\\begin_deeper", beglaysep]
331 end = end + 2 - (j + 1 - beg)
332 document.body[end+1:end+1] = ["", "\\end_deeper", ""]
336 del document.body[i:end+1]
338 del document.body[i:end-1]
343 def convert_parbreak(document):
345 Convert parbreak separators not specifically used to separate
346 environments to latexpar separators.
348 parbreakinset = "\\begin_inset Separator parbreak"
351 i = find_token(document.body, parbreakinset, i)
354 lay = get_containing_layout(document.body, i)
356 document.warning("Malformed LyX document: Can't convert separator inset at line " + str(i))
359 if lay[0] == "Standard":
360 # Convert only if not alone in the paragraph
361 k1 = find_nonempty_line(document.body, lay[1] + 1, i + 1)
362 k2 = find_nonempty_line(document.body, i + 1, lay[2])
363 if (k1 < i) or (k2 > i + 1) or not check_token(document.body[i], parbreakinset):
364 document.body[i] = document.body[i].replace("parbreak", "latexpar")
366 document.body[i] = document.body[i].replace("parbreak", "latexpar")
370 def revert_parbreak(document):
372 Revert latexpar separators to parbreak separators.
376 i = find_token(document.body, "\\begin_inset Separator latexpar", i)
379 document.body[i] = document.body[i].replace("latexpar", "parbreak")
383 def revert_smash(document):
384 " Set amsmath to on if smash commands are used "
386 commands = ["smash[t]", "smash[b]", "notag"]
387 i = find_token(document.header, "\\use_package amsmath", 0)
389 document.warning("Malformed LyX document: Can't find \\use_package amsmath.")
391 value = get_value(document.header, "\\use_package amsmath", i).split()[1]
393 # nothing to do if package is not auto but on or off
397 j = find_token(document.body, '\\begin_inset Formula', j)
400 k = find_end_of_inset(document.body, j)
402 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(j))
405 code = "\n".join(document.body[j:k])
407 if code.find("\\%s" % c) != -1:
408 # set amsmath to on, since it is loaded by the newer format
409 document.header[i] = "\\use_package amsmath 2"
414 def revert_swissgerman(document):
415 " Set language german-ch-old to german "
417 if document.language == "german-ch-old":
418 document.language = "german"
419 i = find_token(document.header, "\\language", 0)
421 document.header[i] = "\\language german"
424 j = find_token(document.body, "\\lang german-ch-old", j)
427 document.body[j] = document.body[j].replace("\\lang german-ch-old", "\\lang german")
431 def revert_use_package(document, pkg, commands, oldauto, supported):
432 # oldauto defines how the version we are reverting to behaves:
433 # if it is true, the old version uses the package automatically.
434 # if it is false, the old version never uses the package.
435 # If "supported" is true, the target version also supports this
437 regexp = re.compile(r'(\\use_package\s+%s)' % pkg)
438 p = find_re(document.header, regexp, 0)
439 value = "1" # default is auto
441 value = get_value(document.header, "\\use_package" , p).split()[1]
443 del document.header[p]
444 if value == "2" and not supported: # on
445 add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
446 elif value == "1" and not oldauto: # auto
449 i = find_token(document.body, '\\begin_inset Formula', i)
452 j = find_end_of_inset(document.body, i)
454 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
457 code = "\n".join(document.body[i:j])
459 if code.find("\\%s" % c) != -1:
461 document.header[p] = "\\use_package " + pkg + " 2"
463 add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
468 mathtools_commands = ["xhookrightarrow", "xhookleftarrow", "xRightarrow", \
469 "xrightharpoondown", "xrightharpoonup", "xrightleftharpoons", \
470 "xLeftarrow", "xleftharpoondown", "xleftharpoonup", \
471 "xleftrightarrow", "xLeftrightarrow", "xleftrightharpoons", \
474 def revert_xarrow(document):
475 "remove use_package mathtools"
476 revert_use_package(document, "mathtools", mathtools_commands, False, True)
479 def revert_beamer_lemma(document):
480 " Reverts beamer lemma layout to ERT "
482 beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
483 if document.textclass not in beamer_classes:
489 i = find_token(document.body, "\\begin_layout Lemma", i)
492 j = find_end_of_layout(document.body, i)
494 document.warning("Malformed LyX document: Can't find end of Lemma layout")
497 arg1 = find_token(document.body, "\\begin_inset Argument 1", i, j)
498 endarg1 = find_end_of_inset(document.body, arg1)
499 arg2 = find_token(document.body, "\\begin_inset Argument 2", i, j)
500 endarg2 = find_end_of_inset(document.body, arg2)
504 beginPlain1 = find_token(document.body, "\\begin_layout Plain Layout", arg1, endarg1)
505 if beginPlain1 == -1:
506 document.warning("Malformed LyX document: Can't find arg1 plain Layout")
509 endPlain1 = find_end_of_inset(document.body, beginPlain1)
510 content1 = document.body[beginPlain1 + 1 : endPlain1 - 2]
511 subst1 = put_cmd_in_ert("<") + content1 + put_cmd_in_ert(">")
513 beginPlain2 = find_token(document.body, "\\begin_layout Plain Layout", arg2, endarg2)
514 if beginPlain2 == -1:
515 document.warning("Malformed LyX document: Can't find arg2 plain Layout")
518 endPlain2 = find_end_of_inset(document.body, beginPlain2)
519 content2 = document.body[beginPlain2 + 1 : endPlain2 - 2]
520 subst2 = put_cmd_in_ert("[") + content2 + put_cmd_in_ert("]")
524 del document.body[arg2 : endarg2 + 1]
526 del document.body[arg1 : endarg1 + 1]
528 del document.body[arg1 : endarg1 + 1]
530 del document.body[arg2 : endarg2 + 1]
532 # index of end layout has probably changed
533 j = find_end_of_layout(document.body, i)
535 document.warning("Malformed LyX document: Can't find end of Lemma layout")
541 # if this is not a consecutive env, add start command
543 begcmd = put_cmd_in_ert("\\begin{lemma}")
545 # has this a consecutive lemma?
546 consecutive = document.body[j + 2] == "\\begin_layout Lemma"
548 # if this is not followed by a consecutive env, add end command
550 document.body[j : j + 1] = put_cmd_in_ert("\\end{lemma}") + ["\\end_layout"]
552 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd + subst1 + subst2
558 def revert_question_env(document):
560 Reverts question and question* environments of
561 theorems-ams-extended-bytype module to ERT
564 # Do we use theorems-ams-extended-bytype module?
565 if not "theorems-ams-extended-bytype" in document.get_module_list():
571 i = find_token(document.body, "\\begin_layout Question", i)
575 starred = document.body[i] == "\\begin_layout Question*"
577 j = find_end_of_layout(document.body, i)
579 document.warning("Malformed LyX document: Can't find end of Question layout")
583 # if this is not a consecutive env, add start command
587 begcmd = put_cmd_in_ert("\\begin{question*}")
589 begcmd = put_cmd_in_ert("\\begin{question}")
591 # has this a consecutive theorem of same type?
594 consecutive = document.body[j + 2] == "\\begin_layout Question*"
596 consecutive = document.body[j + 2] == "\\begin_layout Question"
598 # if this is not followed by a consecutive env, add end command
601 document.body[j : j + 1] = put_cmd_in_ert("\\end{question*}") + ["\\end_layout"]
603 document.body[j : j + 1] = put_cmd_in_ert("\\end{question}") + ["\\end_layout"]
605 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
607 add_to_preamble(document, "\\providecommand{\questionname}{Question}")
610 add_to_preamble(document, "\\theoremstyle{plain}\n" \
611 "\\newtheorem*{question*}{\\protect\\questionname}")
613 add_to_preamble(document, "\\theoremstyle{plain}\n" \
614 "\\newtheorem{question}{\\protect\\questionname}")
619 def convert_dashes(document):
620 "convert -- and --- to \\twohyphens and \\threehyphens"
622 if document.backend != "latex":
626 while i < len(document.body):
627 words = document.body[i].split()
628 if (len(words) > 1 and words[0] == "\\begin_inset"
629 and (words[1] in ["CommandInset", "ERT", "External", "Formula",
630 "FormulaMacro", "Graphics", "IPA", "listings"]
631 or ' '.join(words[1:]) == "Flex Code")):
632 # must not replace anything in insets that store LaTeX contents in .lyx files
633 # (math and command insets without overridden read() and write() methods
634 # filtering out IPA makes Text::readParToken() more simple
635 # skip ERT as well since it is not needed there
636 # Flex Code is logical markup, typically rendered as typewriter
637 j = find_end_of_inset(document.body, i)
639 document.warning("Malformed LyX document: Can't find end of " + words[1] + " inset at line " + str(i))
644 if document.body[i] == "\\begin_layout LyX-Code":
645 j = find_end_of_layout(document.body, i)
647 document.warning("Malformed LyX document: "
648 "Can't find end of %s layout at line %d" % (words[1],i))
654 if len(words) > 0 and words[0] in ["\\leftindent", "\\paragraph_spacing", "\\align", "\\labelwidthstring"]:
655 # skip paragraph parameters (bug 10243)
659 j = document.body[i].find("--")
662 front = document.body[i][:j]
663 back = document.body[i][j+2:]
664 # We can have an arbitrary number of consecutive hyphens.
665 # These must be split into the corresponding number of two and three hyphens
666 # We must match what LaTeX does: First try emdash, then endash, then single hyphen
667 if back.find("-") == 0:
670 document.body.insert(i+1, back)
671 document.body[i] = front + "\\threehyphens"
674 document.body.insert(i+1, back)
675 document.body[i] = front + "\\twohyphens"
679 def revert_dashes(document):
680 "convert \\twohyphens and \\threehyphens to -- and ---"
682 # eventually remove preamble code from 2.3->2.2 conversion:
683 for i, line in enumerate(document.preamble):
684 if i > 1 and line == r'\renewcommand{\textemdash}{---}':
685 if (document.preamble[i-1] == r'\renewcommand{\textendash}{--}'
686 and document.preamble[i-2] == '% Added by lyx2lyx'):
687 del document.preamble[i-2:i+1]
689 while i < len(document.body):
690 words = document.body[i].split()
691 if len(words) > 1 and words[0] == "\\begin_inset" and \
692 words[1] in ["CommandInset", "ERT", "External", "Formula", "Graphics", "IPA", "listings"]:
694 j = find_end_of_inset(document.body, i)
696 document.warning("Malformed LyX document: Can't find end of " + words[1] + " inset at line " + str(i))
702 if document.body[i].find("\\twohyphens") >= 0:
703 document.body[i] = document.body[i].replace("\\twohyphens", "--")
705 if document.body[i].find("\\threehyphens") >= 0:
706 document.body[i] = document.body[i].replace("\\threehyphens", "---")
708 if replaced and i+1 < len(document.body) and \
709 (document.body[i+1].find("\\") != 0 or \
710 document.body[i+1].find("\\twohyphens") == 0 or
711 document.body[i+1].find("\\threehyphens") == 0) and \
712 len(document.body[i]) + len(document.body[i+1]) <= 80:
713 document.body[i] = document.body[i] + document.body[i+1]
714 document.body[i+1:i+2] = []
719 # order is important for the last three!
720 phrases = ["LyX", "LaTeX2e", "LaTeX", "TeX"]
722 def is_part_of_converted_phrase(line, j, phrase):
723 "is phrase part of an already converted phrase?"
725 converted = "\\SpecialCharNoPassThru \\" + p
726 pos = j + len(phrase) - len(converted)
728 if line[pos:pos+len(converted)] == converted:
733 def convert_phrases(document):
734 "convert special phrases from plain text to \\SpecialCharNoPassThru"
736 if document.backend != "latex":
739 for phrase in phrases:
741 while i < len(document.body):
742 words = document.body[i].split()
743 if len(words) > 1 and words[0] == "\\begin_inset" and \
744 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
745 # must not replace anything in insets that store LaTeX contents in .lyx files
746 # (math and command insets withut overridden read() and write() methods
747 j = find_end_of_inset(document.body, i)
749 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
754 if document.body[i].find("\\") == 0:
757 j = document.body[i].find(phrase)
760 if not is_part_of_converted_phrase(document.body[i], j, phrase):
761 front = document.body[i][:j]
762 back = document.body[i][j+len(phrase):]
764 document.body.insert(i+1, back)
765 # We cannot use SpecialChar since we do not know whether we are outside passThru
766 document.body[i] = front + "\\SpecialCharNoPassThru \\" + phrase
770 def revert_phrases(document):
771 "convert special phrases to plain text"
774 while i < len(document.body):
775 words = document.body[i].split()
776 if len(words) > 1 and words[0] == "\\begin_inset" and \
777 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
778 # see convert_phrases
779 j = find_end_of_inset(document.body, i)
781 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
787 for phrase in phrases:
788 # we can replace SpecialChar since LyX ensures that it cannot be inserted into passThru parts
789 if document.body[i].find("\\SpecialChar \\" + phrase) >= 0:
790 document.body[i] = document.body[i].replace("\\SpecialChar \\" + phrase, phrase)
792 if document.body[i].find("\\SpecialCharNoPassThru \\" + phrase) >= 0:
793 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru \\" + phrase, phrase)
795 if replaced and i+1 < len(document.body) and \
796 (document.body[i+1].find("\\") != 0 or \
797 document.body[i+1].find("\\SpecialChar") == 0) and \
798 len(document.body[i]) + len(document.body[i+1]) <= 80:
799 document.body[i] = document.body[i] + document.body[i+1]
800 document.body[i+1:i+2] = []
805 def convert_specialchar_internal(document, forward):
806 specialchars = {"\\-":"softhyphen", "\\textcompwordmark{}":"ligaturebreak", \
807 "\\@.":"endofsentence", "\\ldots{}":"ldots", \
808 "\\menuseparator":"menuseparator", "\\slash{}":"breakableslash", \
809 "\\nobreakdash-":"nobreakdash", "\\LyX":"LyX", \
810 "\\TeX":"TeX", "\\LaTeX2e":"LaTeX2e", \
811 "\\LaTeX":"LaTeX" # must be after LaTeX2e
815 while i < len(document.body):
816 words = document.body[i].split()
817 if len(words) > 1 and words[0] == "\\begin_inset" and \
818 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
819 # see convert_phrases
820 j = find_end_of_inset(document.body, i)
822 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
827 for key, value in specialchars.items():
829 document.body[i] = document.body[i].replace("\\SpecialChar " + key, "\\SpecialChar " + value)
830 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + key, "\\SpecialCharNoPassThru " + value)
832 document.body[i] = document.body[i].replace("\\SpecialChar " + value, "\\SpecialChar " + key)
833 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + value, "\\SpecialCharNoPassThru " + key)
837 def convert_specialchar(document):
838 "convert special characters to new syntax"
839 convert_specialchar_internal(document, True)
842 def revert_specialchar(document):
843 "convert special characters to old syntax"
844 convert_specialchar_internal(document, False)
847 def revert_georgian(document):
848 "Set the document language to English but assure Georgian output"
850 if document.language == "georgian":
851 document.language = "english"
852 i = find_token(document.header, "\\language georgian", 0)
854 document.header[i] = "\\language english"
855 j = find_token(document.header, "\\language_package default", 0)
857 document.header[j] = "\\language_package babel"
858 k = find_token(document.header, "\\options", 0)
860 document.header[k] = document.header[k].replace("\\options", "\\options georgian,")
862 l = find_token(document.header, "\\use_default_options", 0)
863 document.header.insert(l + 1, "\\options georgian")
866 def revert_sigplan_doi(document):
867 " Reverts sigplanconf DOI layout to ERT "
869 if document.textclass != "sigplanconf":
874 i = find_token(document.body, "\\begin_layout DOI", i)
877 j = find_end_of_layout(document.body, i)
879 document.warning("Malformed LyX document: Can't find end of DOI layout")
883 content = lyx2latex(document, document.body[i:j + 1])
884 add_to_preamble(document, ["\\doi{" + content + "}"])
885 del document.body[i:j + 1]
889 def revert_ex_itemargs(document):
890 " Reverts \\item arguments of the example environments (Linguistics module) to TeX-code "
892 if not "linguistics" in document.get_module_list():
896 example_layouts = ["Numbered Examples (consecutive)", "Subexample"]
898 i = find_token(document.body, "\\begin_inset Argument item:", i)
901 j = find_end_of_inset(document.body, i)
902 # Find containing paragraph layout
903 parent = get_containing_layout(document.body, i)
905 document.warning("Malformed LyX document: Can't find parent paragraph layout")
909 layoutname = parent[0]
910 if layoutname in example_layouts:
911 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
912 endPlain = find_end_of_layout(document.body, beginPlain)
913 content = document.body[beginPlain + 1 : endPlain]
914 del document.body[i:j+1]
915 subst = put_cmd_in_ert("[") + content + put_cmd_in_ert("]")
916 document.body[parbeg : parbeg] = subst
920 def revert_forest(document):
921 " Reverts the forest environment (Linguistics module) to TeX-code "
923 if not "linguistics" in document.get_module_list():
928 i = find_token(document.body, "\\begin_inset Flex Structure Tree", i)
931 j = find_end_of_inset(document.body, i)
933 document.warning("Malformed LyX document: Can't find end of Structure Tree inset")
937 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
938 endPlain = find_end_of_layout(document.body, beginPlain)
939 content = lyx2latex(document, document.body[beginPlain : endPlain])
941 add_to_preamble(document, ["\\usepackage{forest}"])
943 document.body[i:j + 1] = put_cmd_in_ert("\\begin{forest}" + content + "\\end{forest}")
947 def revert_glossgroup(document):
948 " Reverts the GroupGlossedWords inset (Linguistics module) to TeX-code "
950 if not "linguistics" in document.get_module_list():
955 i = find_token(document.body, "\\begin_inset Flex GroupGlossedWords", i)
958 j = find_end_of_inset(document.body, i)
960 document.warning("Malformed LyX document: Can't find end of GroupGlossedWords inset")
964 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
965 endPlain = find_end_of_layout(document.body, beginPlain)
966 content = lyx2verbatim(document, document.body[beginPlain : endPlain])
968 document.body[i:j + 1] = ["{", "", content, "", "}"]
972 def revert_newgloss(document):
973 " Reverts the new Glosse insets (Linguistics module) to the old format "
975 if not "linguistics" in document.get_module_list():
978 glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
979 for glosse in glosses:
982 i = find_token(document.body, glosse, i)
985 j = find_end_of_inset(document.body, i)
987 document.warning("Malformed LyX document: Can't find end of Glosse inset")
991 arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
992 endarg = find_end_of_inset(document.body, arg)
995 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
996 if argbeginPlain == -1:
997 document.warning("Malformed LyX document: Can't find arg plain Layout")
1000 argendPlain = find_end_of_inset(document.body, argbeginPlain)
1001 argcontent = lyx2verbatim(document, document.body[argbeginPlain : argendPlain - 2])
1003 document.body[j:j] = ["", "\\begin_layout Plain Layout","\\backslash", "glt ",
1004 argcontent, "\\end_layout"]
1006 # remove Arg insets and paragraph, if it only contains this inset
1007 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
1008 del document.body[arg - 1 : endarg + 4]
1010 del document.body[arg : endarg + 1]
1012 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1013 endPlain = find_end_of_layout(document.body, beginPlain)
1014 content = lyx2verbatim(document, document.body[beginPlain : endPlain])
1016 document.body[beginPlain + 1:endPlain] = [content]
1019 # Dissolve ERT insets
1020 for glosse in glosses:
1023 i = find_token(document.body, glosse, i)
1026 j = find_end_of_inset(document.body, i)
1028 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1032 ert = find_token(document.body, "\\begin_inset ERT", i, j)
1035 ertend = find_end_of_inset(document.body, ert)
1037 document.warning("Malformed LyX document: Can't find end of ERT inset")
1040 ertcontent = get_ert(document.body, ert, True)
1041 document.body[ert : ertend + 1] = [ertcontent]
1045 def convert_newgloss(document):
1046 " Converts Glosse insets (Linguistics module) to the new format "
1048 if not "linguistics" in document.get_module_list():
1051 glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
1052 for glosse in glosses:
1055 i = find_token(document.body, glosse, i)
1058 j = find_end_of_inset(document.body, i)
1060 document.warning("Malformed LyX document: Can't find end of Glosse inset")
1067 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", k, j)
1068 if beginPlain == -1:
1070 endPlain = find_end_of_layout(document.body, beginPlain)
1072 document.warning("Malformed LyX document: Can't find end of Glosse layout")
1076 glt = find_token(document.body, "\\backslash", beginPlain, endPlain)
1077 if glt != -1 and document.body[glt + 1].startswith("glt"):
1078 document.body[glt + 1] = document.body[glt + 1].lstrip("glt").lstrip()
1079 argcontent = document.body[glt + 1 : endPlain]
1080 document.body[beginPlain + 1 : endPlain] = ["\\begin_inset Argument 1", "status open", "",
1081 "\\begin_layout Plain Layout", "\\begin_inset ERT", "status open", "",
1082 "\\begin_layout Plain Layout", ""] + argcontent + ["\\end_layout", "", "\\end_inset", "",
1083 "\\end_layout", "", "\\end_inset"]
1085 content = document.body[beginPlain + 1 : endPlain]
1086 document.body[beginPlain + 1 : endPlain] = ["\\begin_inset ERT", "status open", "",
1087 "\\begin_layout Plain Layout"] + content + ["\\end_layout", "", "\\end_inset"]
1089 endPlain = find_end_of_layout(document.body, beginPlain)
1091 j = find_end_of_inset(document.body, i)
1096 def convert_BoxFeatures(document):
1097 " adds new box features "
1101 i = find_token(document.body, "height_special", i)
1104 document.body[i+1:i+1] = ['thickness "0.4pt"', 'separation "3pt"', 'shadowsize "4pt"']
1108 def revert_BoxFeatures(document):
1109 " outputs new box features as TeX code "
1113 defaultThick = "0.4pt"
1114 defaultShadow = "4pt"
1116 i = find_token(document.body, "thickness", i)
1119 binset = find_token(document.body, "\\begin_inset Box", i - 11)
1120 if binset == -1 or binset != i - 11:
1122 continue # then "thickness" is is just a word in the text
1123 einset = find_end_of_inset(document.body, binset)
1125 document.warning("Malformed LyX document: Can't find end of box inset!")
1128 # read out the values
1129 beg = document.body[i].find('"');
1130 end = document.body[i].rfind('"');
1131 thickness = document.body[i][beg+1:end];
1132 beg = document.body[i+1].find('"');
1133 end = document.body[i+1].rfind('"');
1134 separation = document.body[i+1][beg+1:end];
1135 beg = document.body[i+2].find('"');
1136 end = document.body[i+2].rfind('"');
1137 shadowsize = document.body[i+2][beg+1:end];
1138 # delete the specification
1139 del document.body[i:i+3]
1141 # first output the closing brace
1142 if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1143 document.body[einset -1 : einset - 1] = put_cmd_in_ert("}")
1144 # we have now the problem that if there is already \(f)colorbox in ERT around the inset
1145 # the ERT from this routine must be around it
1146 regexp = re.compile(r'^.*colorbox{.*$')
1147 pos = find_re(document.body, regexp, binset - 4)
1148 if pos != -1 and pos == binset - 4:
1152 # now output the lengths
1153 if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1154 document.body[pos : pos] = put_cmd_in_ert("{")
1155 if thickness != defaultThick:
1156 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness]
1157 if separation != defaultSep and thickness == defaultThick:
1158 document.body[pos + 5 : pos +6] = ["{\\backslash fboxsep " + separation]
1159 if separation != defaultSep and thickness != defaultThick:
1160 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation]
1161 if shadowsize != defaultShadow and separation == defaultSep and thickness == defaultThick:
1162 document.body[pos + 5 : pos +6] = ["{\\backslash shadowsize " + shadowsize]
1163 if shadowsize != defaultShadow and separation != defaultSep and thickness == defaultThick:
1164 document.body[pos + 5 : pos +6] = ["{\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1165 if shadowsize != defaultShadow and separation == defaultSep and thickness != defaultThick:
1166 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash shadowsize " + shadowsize]
1167 if shadowsize != defaultShadow and separation != defaultSep and thickness != defaultThick:
1168 document.body[pos + 5 : pos +6] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1171 def convert_origin(document):
1172 " Insert the origin tag "
1174 i = find_token(document.header, "\\textclass ", 0)
1176 document.warning("Malformed LyX document: No \\textclass!!")
1178 if document.dir == u'':
1182 if document.systemlyxdir and document.systemlyxdir != u'':
1184 if os.path.isabs(document.dir):
1185 absdir = os.path.normpath(document.dir)
1187 absdir = os.path.normpath(os.path.abspath(document.dir))
1188 if os.path.isabs(document.systemlyxdir):
1189 abssys = os.path.normpath(document.systemlyxdir)
1191 abssys = os.path.normpath(os.path.abspath(document.systemlyxdir))
1192 relpath = os.path.relpath(absdir, abssys)
1193 if relpath.find(u'..') == 0:
1198 origin = document.dir.replace(u'\\', u'/') + u'/'
1200 origin = os.path.join(u"/systemlyxdir", relpath).replace(u'\\', u'/') + u'/'
1201 document.header[i:i] = ["\\origin " + origin]
1204 def revert_origin(document):
1205 " Remove the origin tag "
1207 i = find_token(document.header, "\\origin ", 0)
1209 document.warning("Malformed LyX document: No \\origin!!")
1211 del document.header[i]
1214 color_names = ["brown", "darkgray", "gray", \
1215 "lightgray", "lime", "olive", "orange", \
1216 "pink", "purple", "teal", "violet"]
1218 def revert_textcolor(document):
1219 " revert new \\textcolor colors to TeX code "
1225 i = find_token(document.body, "\\color ", i)
1229 for color in list(color_names):
1230 if document.body[i] == "\\color " + color:
1231 # register that xcolor must be loaded in the preamble
1234 add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"])
1235 # find the next \\color and/or the next \\end_layout
1236 j = find_token(document.body, "\\color", i + 1)
1237 k = find_token(document.body, "\\end_layout", i + 1)
1238 if j == -1 and k != -1:
1241 # first output the closing brace
1243 document.body[k: k] = put_cmd_in_ert("}")
1245 document.body[j: j] = put_cmd_in_ert("}")
1246 # now output the \textcolor command
1247 document.body[i : i + 1] = put_cmd_in_ert("\\textcolor{" + color + "}{")
1251 def convert_colorbox(document):
1252 " adds color settings for boxes "
1256 i = find_token(document.body, "shadowsize", i)
1259 document.body[i+1:i+1] = ['framecolor "black"', 'backgroundcolor "none"']
1263 def revert_colorbox(document):
1264 " outputs color settings for boxes as TeX code "
1267 defaultframecolor = "black"
1268 defaultbackcolor = "none"
1270 i = find_token(document.body, "framecolor", i)
1273 binset = find_token(document.body, "\\begin_inset Box", i - 14)
1276 einset = find_end_of_inset(document.body, binset)
1278 document.warning("Malformed LyX document: Can't find end of box inset!")
1280 # read out the values
1281 beg = document.body[i].find('"');
1282 end = document.body[i].rfind('"');
1283 framecolor = document.body[i][beg+1:end];
1284 beg = document.body[i + 1].find('"');
1285 end = document.body[i + 1].rfind('"');
1286 backcolor = document.body[i+1][beg+1:end];
1287 # delete the specification
1288 del document.body[i:i + 2]
1290 # first output the closing brace
1291 if framecolor != defaultframecolor or backcolor != defaultbackcolor:
1292 add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"])
1293 document.body[einset : einset] = put_cmd_in_ert("}")
1294 # determine the box type
1295 isBox = find_token(document.body, "\\begin_inset Box Boxed", binset)
1296 # now output the box commands
1297 if (framecolor != defaultframecolor and isBox == binset) or (backcolor != defaultbackcolor and isBox == binset):
1298 document.body[i - 14 : i - 14] = put_cmd_in_ert("\\fcolorbox{" + framecolor + "}{" + backcolor + "}{")
1299 # in the case we must also change the box type because the ERT code adds a frame
1300 document.body[i - 4] = "\\begin_inset Box Frameless"
1301 # if has_inner_box 0 we must set it and use_makebox to 1
1302 ibox = find_token(document.body, "has_inner_box", i - 4)
1303 if ibox == -1 or ibox != i - 1:
1304 document.warning("Malformed LyX document: Can't find has_inner_box statement!")
1306 # read out the value
1307 innerbox = document.body[ibox][-1:];
1309 document.body[ibox] = "has_inner_box 1"
1310 document.body[ibox + 3] = "use_makebox 1"
1311 if backcolor != defaultbackcolor and isBox != binset:
1312 document.body[i - 14 : i - 14] = put_cmd_in_ert("\\colorbox{" + backcolor + "}{")
1315 def revert_mathmulticol(document):
1316 " Convert formulas to ERT if they contain multicolumns "
1320 i = find_token(document.body, '\\begin_inset Formula', i)
1323 j = find_end_of_inset(document.body, i)
1325 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
1328 lines = document.body[i:j]
1329 lines[0] = lines[0].replace('\\begin_inset Formula', '').lstrip()
1330 code = "\n".join(lines)
1335 n = code.find("\\multicolumn", k)
1336 # no need to convert degenerated multicolumn cells,
1337 # they work in old LyX versions as "math ERT"
1338 if n != -1 and code.find("\\multicolumn{1}", k) != n:
1339 ert = put_cmd_in_ert(code)
1340 document.body[i:j+1] = ert
1346 i = find_end_of_inset(document.body, i)
1351 def revert_jss(document):
1352 " Reverts JSS In_Preamble commands to ERT in preamble "
1354 if document.textclass != "jss":
1363 # at first revert the inset layouts because they can be part of the In_Preamble layouts
1364 while m != -1 or j != -1 or h != -1 or k != -1 or n != -1:
1367 h = find_token(document.body, "\\begin_inset Flex Pkg", h)
1369 endh = find_end_of_inset(document.body, h)
1370 document.body[endh - 2 : endh + 1] = put_cmd_in_ert("}")
1371 document.body[h : h + 4] = put_cmd_in_ert("\\pkg{")
1375 m = find_token(document.body, "\\begin_inset Flex Proglang", m)
1377 endm = find_end_of_inset(document.body, m)
1378 document.body[endm - 2 : endm + 1] = put_cmd_in_ert("}")
1379 document.body[m : m + 4] = put_cmd_in_ert("\\proglang{")
1383 j = find_token(document.body, "\\begin_inset Flex Code", j)
1385 # assure that we are not in a Code Chunk inset
1386 if document.body[j][-1] == "e":
1387 endj = find_end_of_inset(document.body, j)
1388 document.body[endj - 2 : endj + 1] = put_cmd_in_ert("}")
1389 document.body[j : j + 4] = put_cmd_in_ert("\\code{")
1395 k = find_token(document.body, "\\begin_inset Flex E-mail", k)
1397 endk = find_end_of_inset(document.body, k)
1398 document.body[endk - 2 : endk + 1] = put_cmd_in_ert("}")
1399 document.body[k : k + 4] = put_cmd_in_ert("\\email{")
1403 n = find_token(document.body, "\\begin_inset Flex URL", n)
1405 endn = find_end_of_inset(document.body, n)
1406 document.body[endn - 2 : endn + 1] = put_cmd_in_ert("}")
1407 document.body[n : n + 4] = put_cmd_in_ert("\\url{")
1409 # now revert the In_Preamble layouts
1411 i = find_token(document.body, "\\begin_layout Title", 0)
1414 j = find_end_of_layout(document.body, i)
1416 document.warning("Malformed LyX document: Can't find end of Title layout")
1419 content = lyx2latex(document, document.body[i:j + 1])
1420 add_to_preamble(document, ["\\title{" + content + "}"])
1421 del document.body[i:j + 1]
1423 i = find_token(document.body, "\\begin_layout Author", 0)
1426 j = find_end_of_layout(document.body, i)
1428 document.warning("Malformed LyX document: Can't find end of Author layout")
1431 content = lyx2latex(document, document.body[i:j + 1])
1432 add_to_preamble(document, ["\\author{" + content + "}"])
1433 del document.body[i:j + 1]
1435 i = find_token(document.body, "\\begin_layout Plain Author", 0)
1438 j = find_end_of_layout(document.body, i)
1440 document.warning("Malformed LyX document: Can't find end of Plain Author layout")
1443 content = lyx2latex(document, document.body[i:j + 1])
1444 add_to_preamble(document, ["\\Plainauthor{" + content + "}"])
1445 del document.body[i:j + 1]
1447 i = find_token(document.body, "\\begin_layout Plain Title", 0)
1450 j = find_end_of_layout(document.body, i)
1452 document.warning("Malformed LyX document: Can't find end of Plain Title layout")
1455 content = lyx2latex(document, document.body[i:j + 1])
1456 add_to_preamble(document, ["\\Plaintitle{" + content + "}"])
1457 del document.body[i:j + 1]
1459 i = find_token(document.body, "\\begin_layout Short Title", 0)
1462 j = find_end_of_layout(document.body, i)
1464 document.warning("Malformed LyX document: Can't find end of Short Title layout")
1467 content = lyx2latex(document, document.body[i:j + 1])
1468 add_to_preamble(document, ["\\Shorttitle{" + content + "}"])
1469 del document.body[i:j + 1]
1471 i = find_token(document.body, "\\begin_layout Abstract", 0)
1474 j = find_end_of_layout(document.body, i)
1476 document.warning("Malformed LyX document: Can't find end of Abstract layout")
1479 content = lyx2latex(document, document.body[i:j + 1])
1480 add_to_preamble(document, ["\\Abstract{" + content + "}"])
1481 del document.body[i:j + 1]
1483 i = find_token(document.body, "\\begin_layout Keywords", 0)
1486 j = find_end_of_layout(document.body, i)
1488 document.warning("Malformed LyX document: Can't find end of Keywords layout")
1491 content = lyx2latex(document, document.body[i:j + 1])
1492 add_to_preamble(document, ["\\Keywords{" + content + "}"])
1493 del document.body[i:j + 1]
1495 i = find_token(document.body, "\\begin_layout Plain Keywords", 0)
1498 j = find_end_of_layout(document.body, i)
1500 document.warning("Malformed LyX document: Can't find end of Plain Keywords layout")
1503 content = lyx2latex(document, document.body[i:j + 1])
1504 add_to_preamble(document, ["\\Plainkeywords{" + content + "}"])
1505 del document.body[i:j + 1]
1507 i = find_token(document.body, "\\begin_layout Address", 0)
1510 j = find_end_of_layout(document.body, i)
1512 document.warning("Malformed LyX document: Can't find end of Address layout")
1515 content = lyx2latex(document, document.body[i:j + 1])
1516 add_to_preamble(document, ["\\Address{" + content + "}"])
1517 del document.body[i:j + 1]
1518 # finally handle the code layouts
1523 while m != -1 or j != -1 or h != -1 or k != -1:
1526 h = find_token(document.body, "\\begin_inset Flex Code Chunk", h)
1528 endh = find_end_of_inset(document.body, h)
1529 document.body[endh : endh + 1] = put_cmd_in_ert("\\end{CodeChunk}")
1530 document.body[h : h + 3] = put_cmd_in_ert("\\begin{CodeChunk}")
1531 document.body[h - 1 : h] = ["\\begin_layout Standard"]
1535 j = find_token(document.body, "\\begin_layout Code Input", j)
1537 endj = find_end_of_layout(document.body, j)
1538 document.body[endj : endj + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1539 document.body[endj + 3 : endj + 4] = put_cmd_in_ert("\\end{CodeInput}")
1540 document.body[endj + 13 : endj + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1541 document.body[j + 1 : j] = ["\\end_layout", "", "\\begin_layout Standard"]
1542 document.body[j : j + 1] = put_cmd_in_ert("\\begin{CodeInput}")
1546 k = find_token(document.body, "\\begin_layout Code Output", k)
1548 endk = find_end_of_layout(document.body, k)
1549 document.body[endk : endk + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1550 document.body[endk + 3 : endk + 4] = put_cmd_in_ert("\\end{CodeOutput}")
1551 document.body[endk + 13 : endk + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1552 document.body[k + 1 : k] = ["\\end_layout", "", "\\begin_layout Standard"]
1553 document.body[k : k + 1] = put_cmd_in_ert("\\begin{CodeOutput}")
1557 m = find_token(document.body, "\\begin_layout Code", m)
1559 endm = find_end_of_layout(document.body, m)
1560 document.body[endm : endm + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1561 document.body[endm + 3 : endm + 4] = put_cmd_in_ert("\\end{Code}")
1562 document.body[endm + 13 : endm + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1563 document.body[m + 1 : m] = ["\\end_layout", "", "\\begin_layout Standard"]
1564 document.body[m : m + 1] = put_cmd_in_ert("\\begin{Code}")
1568 def convert_subref(document):
1569 " converts sub: ref prefixes to subref: "
1572 rx = re.compile(r'^name \"sub:(.+)$')
1575 i = find_token(document.body, "\\begin_inset CommandInset label", i)
1578 j = find_end_of_inset(document.body, i)
1580 document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1584 for p in range(i, j):
1585 m = rx.match(document.body[p])
1588 document.body[p] = "name \"subsec:" + label
1592 rx = re.compile(r'^reference \"sub:(.+)$')
1595 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1598 j = find_end_of_inset(document.body, i)
1600 document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1604 for p in range(i, j):
1605 m = rx.match(document.body[p])
1608 document.body[p] = "reference \"subsec:" + label
1614 def revert_subref(document):
1615 " reverts subref: ref prefixes to sub: "
1618 rx = re.compile(r'^name \"subsec:(.+)$')
1621 i = find_token(document.body, "\\begin_inset CommandInset label", i)
1624 j = find_end_of_inset(document.body, i)
1626 document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1630 for p in range(i, j):
1631 m = rx.match(document.body[p])
1634 document.body[p] = "name \"sub:" + label
1639 rx = re.compile(r'^reference \"subsec:(.+)$')
1642 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1645 j = find_end_of_inset(document.body, i)
1647 document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1651 for p in range(i, j):
1652 m = rx.match(document.body[p])
1655 document.body[p] = "reference \"sub:" + label
1660 def convert_nounzip(document):
1661 " remove the noUnzip parameter of graphics insets "
1663 rx = re.compile(r'\s*noUnzip\s*$')
1666 i = find_token(document.body, "\\begin_inset Graphics", i)
1669 j = find_end_of_inset(document.body, i)
1671 document.warning("Malformed LyX document: Can't find end of graphics inset at line " + str(i))
1675 k = find_re(document.body, rx, i, j)
1677 del document.body[k]
1682 def convert_revert_external_bbox(document, forward):
1683 " add units to bounding box of external insets "
1685 rx = re.compile(r'^\s*boundingBox\s+\S+\s+\S+\s+\S+\s+\S+\s*$')
1688 i = find_token(document.body, "\\begin_inset External", i)
1691 j = find_end_of_inset(document.body, i)
1693 document.warning("Malformed LyX document: Can't find end of external inset at line " + str(i))
1696 k = find_re(document.body, rx, i, j)
1700 tokens = document.body[k].split()
1702 for t in range(1, 5):
1705 for t in range(1, 5):
1706 tokens[t] = length_in_bp(tokens[t])
1707 document.body[k] = "\tboundingBox " + tokens[1] + " " + tokens[2] + " " + \
1708 tokens[3] + " " + tokens[4]
1712 def convert_external_bbox(document):
1713 convert_revert_external_bbox(document, True)
1716 def revert_external_bbox(document):
1717 convert_revert_external_bbox(document, False)
1720 def revert_tcolorbox_1(document):
1721 " Reverts the Flex:Subtitle inset of tcolorbox to TeX-code "
1724 i = find_token(document.header, "tcolorbox", i)
1730 flex = find_token(document.body, "\\begin_inset Flex Subtitle", flex)
1733 flexEnd = find_end_of_inset(document.body, flex)
1734 wasOpt = revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, False, True, False)
1735 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, False, False, False)
1736 flexEnd = find_end_of_inset(document.body, flex)
1738 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\tcbsubtitle")
1740 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\tcbsubtitle{")
1741 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("}")
1745 def revert_tcolorbox_2(document):
1746 " Reverts the Flex:Raster_Color_Box inset of tcolorbox to TeX-code "
1749 i = find_token(document.header, "tcolorbox", i)
1755 flex = find_token(document.body, "\\begin_inset Flex Raster Color Box", flex)
1758 flexEnd = find_end_of_inset(document.body, flex)
1759 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1760 flexEnd = find_end_of_inset(document.body, flex)
1761 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{tcbraster}")
1762 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("\\end{tcbraster}")
1766 def revert_tcolorbox_3(document):
1767 " Reverts the Flex:Custom_Color_Box_1 inset of tcolorbox to TeX-code "
1770 i = find_token(document.header, "tcolorbox", i)
1776 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 1", flex)
1779 flexEnd = find_end_of_inset(document.body, flex)
1780 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1781 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1782 flexEnd = find_end_of_inset(document.body, flex)
1783 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxA}")
1784 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxA}")
1788 def revert_tcolorbox_4(document):
1789 " Reverts the Flex:Custom_Color_Box_2 inset of tcolorbox to TeX-code "
1792 i = find_token(document.header, "tcolorbox", i)
1798 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 2", flex)
1801 flexEnd = find_end_of_inset(document.body, flex)
1802 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1803 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1804 flexEnd = find_end_of_inset(document.body, flex)
1805 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxB}")
1806 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxB}")
1810 def revert_tcolorbox_5(document):
1811 " Reverts the Flex:Custom_Color_Box_3 inset of tcolorbox to TeX-code "
1814 i = find_token(document.header, "tcolorbox", i)
1820 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 3", flex)
1823 flexEnd = find_end_of_inset(document.body, flex)
1824 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1825 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1826 flexEnd = find_end_of_inset(document.body, flex)
1827 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxC}")
1828 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxC}")
1832 def revert_tcolorbox_6(document):
1833 " Reverts the Flex:Custom_Color_Box_4 inset of tcolorbox to TeX-code "
1836 i = find_token(document.header, "tcolorbox", i)
1842 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 4", flex)
1845 flexEnd = find_end_of_inset(document.body, flex)
1846 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1847 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1848 flexEnd = find_end_of_inset(document.body, flex)
1849 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxD}")
1850 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxD}")
1854 def revert_tcolorbox_7(document):
1855 " Reverts the Flex:Custom_Color_Box_5 inset of tcolorbox to TeX-code "
1858 i = find_token(document.header, "tcolorbox", i)
1864 flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 5", flex)
1867 flexEnd = find_end_of_inset(document.body, flex)
1868 revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1869 revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1870 flexEnd = find_end_of_inset(document.body, flex)
1871 document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxE}")
1872 document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxE}")
1876 def revert_tcolorbox_8(document):
1877 " Reverts the layout New Color Box Type of tcolorbox to TeX-code "
1883 i = find_token(document.body, "\\begin_layout New Color Box Type", i)
1885 j = find_end_of_layout(document.body, i)
1886 wasOpt = revert_Argument_to_TeX_brace(document, i, j, 1, 1, False, True, False)
1887 revert_Argument_to_TeX_brace(document, i, 0, 2, 2, False, False, True)
1888 revert_Argument_to_TeX_brace(document, i, 0, 3, 4, False, True, False)
1889 document.body[i] = document.body[i].replace("\\begin_layout New Color Box Type", "\\begin_layout Standard")
1891 document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox")
1893 document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox{")
1894 k = find_end_of_inset(document.body, j)
1895 k = find_token(document.body, "\\end_inset", k + 1)
1896 k = find_token(document.body, "\\end_inset", k + 1)
1898 k = find_token(document.body, "\\end_inset", k + 1)
1899 document.body[k + 2 : j + 2] = put_cmd_in_ert("{")
1900 j = find_token(document.body, "\\begin_layout Standard", j + 1)
1901 document.body[j - 2 : j - 2] = put_cmd_in_ert("}")
1907 def revert_moderncv_1(document):
1908 " Reverts the new inset of moderncv to TeX-code in preamble "
1910 if document.textclass != "moderncv":
1916 # at first revert the new styles
1918 i = find_token(document.body, "\\begin_layout CVIcons", 0)
1921 j = find_end_of_layout(document.body, i)
1923 document.warning("Malformed LyX document: Can't find end of CVIcons layout")
1926 content = lyx2latex(document, document.body[i:j + 1])
1927 add_to_preamble(document, ["\\moderncvicons{" + content + "}"])
1928 del document.body[i:j + 1]
1930 i = find_token(document.body, "\\begin_layout CVColumnWidth", 0)
1933 j = find_end_of_layout(document.body, i)
1935 document.warning("Malformed LyX document: Can't find end of CVColumnWidth layout")
1938 content = lyx2latex(document, document.body[i:j + 1])
1939 add_to_preamble(document, ["\\setlength{\hintscolumnwidth}{" + content + "}"])
1940 del document.body[i:j + 1]
1941 # now change the new styles to the obsolete ones
1943 i = find_token(document.body, "\\begin_layout Name", 0)
1946 j = find_end_of_layout(document.body, i)
1948 document.warning("Malformed LyX document: Can't find end of Name layout")
1951 lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
1952 if lineArg > j and j != 0:
1955 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
1956 # we have to assure that no other inset is in the Argument
1957 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
1958 endInset = find_token(document.body, "\\end_inset", beginPlain)
1961 while beginInset < endInset and beginInset != -1:
1962 beginInset = find_token(document.body, "\\begin_inset", k)
1963 endInset = find_token(document.body, "\\end_inset", l)
1966 Arg2 = document.body[l + 5 : l + 6]
1968 document.body[i : i + 1]= ["\\begin_layout FirstName"]
1969 # delete the Argument inset
1970 del( document.body[endInset - 2 : endInset + 3])
1971 del( document.body[lineArg : beginPlain + 1])
1972 document.body[i + 4 : i + 4]= ["\\begin_layout FamilyName"] + Arg2 + ["\\end_layout"] + [""]
1975 def revert_moderncv_2(document):
1976 " Reverts the phone inset of moderncv to the obsoleted mobile or fax "
1978 if document.textclass != "moderncv":
1985 i = find_token(document.body, "\\begin_layout Phone", i)
1988 j = find_end_of_layout(document.body, i)
1990 document.warning("Malformed LyX document: Can't find end of Phone layout")
1993 lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
1994 if lineArg > j and j != 0:
1998 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
1999 # we have to assure that no other inset is in the Argument
2000 beginInset = find_token(document.body, "\\begin_inset", beginPlain)
2001 endInset = find_token(document.body, "\\end_inset", beginPlain)
2004 while beginInset < endInset and beginInset != -1:
2005 beginInset = find_token(document.body, "\\begin_inset", k)
2006 endInset = find_token(document.body, "\\end_inset", l)
2009 Arg = document.body[beginPlain + 1 : beginPlain + 2]
2011 if Arg[0] == "mobile":
2012 document.body[i : i + 1]= ["\\begin_layout Mobile"]
2014 document.body[i : i + 1]= ["\\begin_layout Fax"]
2015 # delete the Argument inset
2016 del(document.body[endInset - 2 : endInset + 1])
2017 del(document.body[lineArg : beginPlain + 3])
2021 def convert_moderncv_phone(document):
2022 " Convert the Fax and Mobile inset of moderncv to the new phone inset "
2024 if document.textclass != "moderncv":
2031 "Mobile" : "mobile",
2035 rx = re.compile(r'^\\begin_layout (\S+)$')
2037 # substitute \fax and \mobile by \phone[fax] and \phone[mobile], respectively
2038 i = find_token(document.body, "\\begin_layout", i)
2042 m = rx.match(document.body[i])
2046 if val not in list(phone_dict.keys()):
2049 j = find_end_of_layout(document.body, i)
2051 document.warning("Malformed LyX document: Can't find end of Mobile layout")
2055 document.body[i : i + 1] = ["\\begin_layout Phone", "\\begin_inset Argument 1", "status open", "",
2056 "\\begin_layout Plain Layout", phone_dict[val], "\\end_layout", "",
2060 def convert_moderncv_name(document):
2061 " Convert the FirstName and LastName layout of moderncv to the general Name layout "
2063 if document.textclass != "moderncv":
2066 fnb = 0 # Begin of FirstName inset
2067 fne = 0 # End of FirstName inset
2068 lnb = 0 # Begin of LastName (FamilyName) inset
2069 lne = 0 # End of LastName (FamilyName) inset
2070 nb = 0 # Begin of substituting Name inset
2071 ne = 0 # End of substituting Name inset
2072 FirstName = [] # FirstName content
2073 FamilyName = [] # LastName content
2077 fnb = find_token(document.body, "\\begin_layout FirstName", fnb)
2079 fne = find_end_of_layout(document.body, fnb)
2081 document.warning("Malformed LyX document: Can't find end of FirstName layout")
2083 FirstName = document.body[fnb + 1 : fne]
2085 lnb = find_token(document.body, "\\begin_layout FamilyName", lnb)
2087 lne = find_end_of_layout(document.body, lnb)
2089 document.warning("Malformed LyX document: Can't find end of FamilyName layout")
2091 FamilyName = document.body[lnb + 1 : lne]
2092 # Determine the region for the substituting Name layout
2093 if fnb == -1 and lnb == -1: # Neither FirstName nor FamilyName exists -> Do nothing
2095 elif fnb == -1: # Only FamilyName exists -> New Name insets replaces that
2098 elif lnb == -1: # Only FirstName exists -> New Name insets replaces that
2101 elif fne > lne: # FirstName position before FamilyName -> New Name insets spans
2102 nb = lnb # from FamilyName begin
2103 ne = fne # to FirstName end
2104 else: # FirstName position before FamilyName -> New Name insets spans
2105 nb = fnb # from FirstName begin
2106 ne = lne # to FamilyName end
2108 # Insert the substituting layout now. If FirstName exists, use an otpional argument.
2110 document.body[nb : ne + 1] = ["\\begin_layout Name"] + FamilyName + ["\\end_layout", ""]
2112 document.body[nb : ne + 1] = ["\\begin_layout Name", "\\begin_inset Argument 1", "status open", "",
2113 "\\begin_layout Plain Layout"] + FirstName + ["\\end_layout", "",
2114 "\\end_inset", ""] + FamilyName + ["\\end_layout", ""]
2117 def revert_achemso(document):
2118 " Reverts the flex inset Latin to TeX code "
2120 if document.textclass != "achemso":
2125 i = find_token(document.body, "\\begin_inset Flex Latin", i)
2127 j = find_end_of_inset(document.body, i)
2131 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2132 endPlain = find_end_of_layout(document.body, beginPlain)
2133 content = lyx2latex(document, document.body[beginPlain : endPlain])
2134 document.body[i:j + 1] = put_cmd_in_ert("\\latin{" + content + "}")
2136 document.warning("Malformed LyX document: Can't find end of flex inset Latin")
2141 fontsettings = ["\\font_roman", "\\font_sans", "\\font_typewriter", "\\font_math", \
2142 "\\font_sf_scale", "\\font_tt_scale"]
2143 fontdefaults = ["default", "default", "default", "auto", "100", "100"]
2144 fontquotes = [True, True, True, True, False, False]
2146 def convert_fontsettings(document):
2147 " Duplicate font settings "
2149 i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2151 document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2152 use_non_tex_fonts = "false"
2154 use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2156 for f in fontsettings:
2157 i = find_token(document.header, f + " ", 0)
2159 document.warning("Malformed LyX document: No " + f + "!")
2161 # note that with i = -1, this will insert at the end
2163 value = fontdefaults[j]
2165 value = document.header[i][len(f):].strip()
2167 if use_non_tex_fonts == "true":
2168 document.header[i:i+1] = [f + ' "' + fontdefaults[j] + '" "' + value + '"']
2170 document.header[i:i+1] = [f + ' "' + value + '" "' + fontdefaults[j] + '"']
2172 if use_non_tex_fonts == "true":
2173 document.header[i:i+1] = [f + ' ' + fontdefaults[j] + ' ' + value]
2175 document.header[i:i+1] = [f + ' ' + value + ' ' + fontdefaults[j]]
2179 def revert_fontsettings(document):
2180 " Merge font settings "
2182 i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2184 document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2185 use_non_tex_fonts = "false"
2187 use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2189 for f in fontsettings:
2190 i = find_token(document.header, f + " ", 0)
2192 document.warning("Malformed LyX document: No " + f + "!")
2195 line = get_value(document.header, f, i)
2198 q2 = line.find('"', q1+1)
2199 q3 = line.find('"', q2+1)
2200 q4 = line.find('"', q3+1)
2201 if q1 == -1 or q2 == -1 or q3 == -1 or q4 == -1:
2202 document.warning("Malformed LyX document: Missing quotes!")
2205 if use_non_tex_fonts == "true":
2206 document.header[i:i+1] = [f + ' ' + line[q3+1:q4]]
2208 document.header[i:i+1] = [f + ' ' + line[q1+1:q2]]
2210 if use_non_tex_fonts == "true":
2211 document.header[i:i+1] = [f + ' ' + line.split()[1]]
2213 document.header[i:i+1] = [f + ' ' + line.split()[0]]
2217 def revert_solution(document):
2218 " Reverts the solution environment of the theorem module to TeX code "
2220 # Do we use one of the modules that provides Solution?
2222 mods = document.get_module_list()
2224 if mod == "theorems-std" or mod == "theorems-bytype" \
2225 or mod == "theorems-ams" or mod == "theorems-ams-bytype":
2235 i = find_token(document.body, "\\begin_layout Solution", i)
2239 is_starred = document.body[i].startswith("\\begin_layout Solution*")
2240 if is_starred == True:
2242 LyXName = "Solution*"
2243 theoremName = "newtheorem*"
2246 LyXName = "Solution"
2247 theoremName = "newtheorem"
2249 j = find_end_of_layout(document.body, i)
2251 document.warning("Malformed LyX document: Can't find end of " + LyXName + " layout")
2255 # if this is not a consecutive env, add start command
2258 begcmd = put_cmd_in_ert("\\begin{%s}" % (LaTeXName))
2260 # has this a consecutive theorem of same type?
2261 consecutive = document.body[j + 2] == "\\begin_layout " + LyXName
2263 # if this is not followed by a consecutive env, add end command
2265 document.body[j : j + 1] = put_cmd_in_ert("\\end{%s}" % (LaTeXName)) + ["\\end_layout"]
2267 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
2269 add_to_preamble(document, "\\theoremstyle{definition}")
2270 if is_starred or mod == "theorems-bytype" or mod == "theorems-ams-bytype":
2271 add_to_preamble(document, "\\%s{%s}{\\protect\\solutionname}" % \
2272 (theoremName, LaTeXName))
2273 else: # mod == "theorems-std" or mod == "theorems-ams" and not is_starred
2274 add_to_preamble(document, "\\%s{%s}[thm]{\\protect\\solutionname}" % \
2275 (theoremName, LaTeXName))
2277 add_to_preamble(document, "\\providecommand{\solutionname}{Solution}")
2281 def revert_verbatim_star(document):
2282 from lyx_2_1 import revert_verbatim
2283 revert_verbatim(document, True)
2286 def convert_save_props(document):
2287 " Add save_transient_properties parameter. "
2288 i = find_token(document.header, '\\begin_header', 0)
2290 document.warning("Malformed lyx document: Missing '\\begin_header'.")
2292 document.header.insert(i + 1, '\\save_transient_properties true')
2295 def revert_save_props(document):
2296 " Remove save_transient_properties parameter. "
2297 i = find_token(document.header, "\\save_transient_properties", 0)
2300 del document.header[i]
2303 def convert_info_tabular_feature(document):
2305 return arg.replace("inset-modify tabular", "tabular-feature")
2306 convert_info_insets(document, "shortcut(s)?|icon", f)
2309 def revert_info_tabular_feature(document):
2311 return arg.replace("tabular-feature", "inset-modify tabular")
2312 convert_info_insets(document, "shortcut(s)?|icon", f)
2319 supported_versions = ["2.2.0", "2.2"]
2321 [475, [convert_separator]],
2322 # nothing to do for 476: We consider it a bug that older versions
2323 # did not load amsmath automatically for these commands, and do not
2324 # want to hardcode amsmath off.
2330 [481, [convert_dashes]],
2331 [482, [convert_phrases]],
2332 [483, [convert_specialchar]],
2337 [488, [convert_newgloss]],
2338 [489, [convert_BoxFeatures]],
2339 [490, [convert_origin]],
2341 [492, [convert_colorbox]],
2344 [495, [convert_subref]],
2345 [496, [convert_nounzip]],
2346 [497, [convert_external_bbox]],
2348 [499, [convert_moderncv_phone, convert_moderncv_name]],
2350 [501, [convert_fontsettings]],
2353 [504, [convert_save_props]],
2355 [506, [convert_info_tabular_feature]],
2356 [507, [convert_longtable_label]],
2357 [508, [convert_parbreak]]
2361 [507, [revert_parbreak]],
2362 [506, [revert_longtable_label]],
2363 [505, [revert_info_tabular_feature]],
2365 [503, [revert_save_props]],
2366 [502, [revert_verbatim_star]],
2367 [501, [revert_solution]],
2368 [500, [revert_fontsettings]],
2369 [499, [revert_achemso]],
2370 [498, [revert_moderncv_1, revert_moderncv_2]],
2371 [497, [revert_tcolorbox_1, revert_tcolorbox_2,
2372 revert_tcolorbox_3, revert_tcolorbox_4, revert_tcolorbox_5,
2373 revert_tcolorbox_6, revert_tcolorbox_7, revert_tcolorbox_8]],
2374 [496, [revert_external_bbox]],
2375 [495, []], # nothing to do since the noUnzip parameter was optional
2376 [494, [revert_subref]],
2377 [493, [revert_jss]],
2378 [492, [revert_mathmulticol]],
2379 [491, [revert_colorbox]],
2380 [490, [revert_textcolor]],
2381 [489, [revert_origin]],
2382 [488, [revert_BoxFeatures]],
2383 [487, [revert_newgloss, revert_glossgroup]],
2384 [486, [revert_forest]],
2385 [485, [revert_ex_itemargs]],
2386 [484, [revert_sigplan_doi]],
2387 [483, [revert_georgian]],
2388 [482, [revert_specialchar]],
2389 [481, [revert_phrases]],
2390 [480, [revert_dashes]],
2391 [479, [revert_question_env]],
2392 [478, [revert_beamer_lemma]],
2393 [477, [revert_xarrow]],
2394 [476, [revert_swissgerman]],
2395 [475, [revert_smash]],
2396 [474, [revert_separator]]
2400 if __name__ == "__main__":