1 # -*- coding: utf-8 -*-
2 # This file is part of lyx2lyx
3 # -*- coding: utf-8 -*-
4 # Copyright (C) 2011 The LyX team
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 """ Convert files to the file format generated by lyx 2.2"""
26 # Uncomment only what you need to import, please.
28 #from parser_tools import find_token, find_end_of, find_tokens, \
29 # find_token_exact, find_end_of_inset, find_end_of_layout, \
30 # find_token_backwards, is_in_inset, get_value, get_quoted_value, \
31 # del_token, check_token, get_option_value
33 from lyx2lyx_tools import add_to_preamble, put_cmd_in_ert, lyx2latex, \
35 # insert_to_preamble, latex_length, revert_flex_inset, \
36 # revert_font_attrs, hex2ratio, str2bool
38 from parser_tools import find_token, find_token_backwards, find_re, \
39 find_end_of_inset, find_end_of_layout, find_nonempty_line, \
40 get_containing_layout, get_value, check_token
42 ###############################################################################
44 ### Conversion and reversion routines
46 ###############################################################################
48 def convert_separator(document):
50 Convert layout separators to separator insets and add (LaTeX) paragraph
51 breaks in order to mimic previous LaTeX export.
54 parins = ["\\begin_inset Separator parbreak", "\\end_inset", ""]
55 parlay = ["\\begin_layout Standard", "\\begin_inset Separator parbreak",
56 "\\end_inset", "", "\\end_layout", ""]
68 i = find_token(document.body, "\\begin_deeper", i)
72 j = find_token_backwards(document.body, "\\end_layout", i-1)
74 # reset any text style before inserting the inset
75 lay = get_containing_layout(document.body, j-1)
77 content = "\n".join(document.body[lay[1]:lay[2]])
78 for val in list(sty_dict.keys()):
79 if content.find("\\%s" % val) != -1:
80 document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
83 document.body[j:j] = parins
84 i = i + len(parins) + 1
90 i = find_token(document.body, "\\align", i)
94 lay = get_containing_layout(document.body, i)
95 if lay != False and lay[0] == "Plain Layout":
99 j = find_token_backwards(document.body, "\\end_layout", i-1)
101 lay = get_containing_layout(document.body, j-1)
102 if lay != False and lay[0] == "Standard" \
103 and find_token(document.body, "\\align", lay[1], lay[2]) == -1 \
104 and find_token(document.body, "\\begin_inset VSpace", lay[1], lay[2]) == -1:
105 # reset any text style before inserting the inset
106 content = "\n".join(document.body[lay[1]:lay[2]])
107 for val in list(sty_dict.keys()):
108 if content.find("\\%s" % val) != -1:
109 document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
112 document.body[j:j] = parins
113 i = i + len(parins) + 1
119 regexp = re.compile(r'^\\begin_layout (?:(-*)|(\s*))(Separator|EndOfSlide)(?:(-*)|(\s*))$', re.IGNORECASE)
123 i = find_re(document.body, regexp, i)
127 j = find_end_of_layout(document.body, i)
129 document.warning("Malformed LyX document: Missing `\\end_layout'.")
132 lay = get_containing_layout(document.body, j-1)
134 lines = document.body[lay[3]:lay[2]]
138 document.body[i:j+1] = parlay
140 document.body[i+1:i+1] = lines
142 i = i + len(parlay) + len(lines) + 1
145 def revert_separator(document):
146 " Revert separator insets to layout separators "
148 beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
149 if document.textclass in beamer_classes:
150 beglaysep = "\\begin_layout Separator"
152 beglaysep = "\\begin_layout --Separator--"
154 parsep = [beglaysep, "", "\\end_layout", ""]
155 comert = ["\\begin_inset ERT", "status collapsed", "",
156 "\\begin_layout Plain Layout", "%", "\\end_layout",
157 "", "\\end_inset", ""]
158 empert = ["\\begin_inset ERT", "status collapsed", "",
159 "\\begin_layout Plain Layout", " ", "\\end_layout",
160 "", "\\end_inset", ""]
164 i = find_token(document.body, "\\begin_inset Separator", i)
168 lay = get_containing_layout(document.body, i)
170 document.warning("Malformed LyX document: Can't convert separator inset at line " + str(i))
177 kind = get_value(document.body, "\\begin_inset Separator", i, i+1, "plain").split()[1]
178 before = document.body[beg+1:i]
179 something_before = len(before) > 0 and len("".join(before)) > 0
180 j = find_end_of_inset(document.body, i)
181 after = document.body[j+1:end]
182 something_after = len(after) > 0 and len("".join(after)) > 0
184 beg = beg + len(before) + 1
185 elif something_before:
186 document.body[i:i] = ["\\end_layout", ""]
194 document.body[beg:j+1] = empert
197 document.body[beg:j+1] = comert
201 if layoutname == "Standard":
202 if not something_before:
203 document.body[beg:j+1] = parsep
205 document.body[i:i] = ["", "\\begin_layout Standard"]
208 document.body[beg:j+1] = ["\\begin_layout Standard"]
211 document.body[beg:j+1] = ["\\begin_deeper"]
213 end = end + 1 - (j + 1 - beg)
214 if not something_before:
215 document.body[i:i] = parsep
217 end = end + len(parsep)
218 document.body[i:i] = ["\\begin_layout Standard"]
219 document.body[end+2:end+2] = ["", "\\end_deeper", ""]
222 next_par_is_aligned = False
223 k = find_nonempty_line(document.body, end+1)
224 if k != -1 and check_token(document.body[k], "\\begin_layout"):
225 lay = get_containing_layout(document.body, k)
226 next_par_is_aligned = lay != False and \
227 find_token(document.body, "\\align", lay[1], lay[2]) != -1
228 if k != -1 and not next_par_is_aligned \
229 and not check_token(document.body[k], "\\end_deeper") \
230 and not check_token(document.body[k], "\\begin_deeper"):
231 if layoutname == "Standard":
232 document.body[beg:j+1] = [beglaysep]
235 document.body[beg:j+1] = ["\\begin_deeper", beglaysep]
236 end = end + 2 - (j + 1 - beg)
237 document.body[end+1:end+1] = ["", "\\end_deeper", ""]
241 del document.body[i:end+1]
243 del document.body[i:end-1]
248 def revert_smash(document):
249 " Set amsmath to on if smash commands are used "
251 commands = ["smash[t]", "smash[b]", "notag"]
252 i = find_token(document.header, "\\use_package amsmath", 0)
254 document.warning("Malformed LyX document: Can't find \\use_package amsmath.")
256 value = get_value(document.header, "\\use_package amsmath", i).split()[1]
258 # nothing to do if package is not auto but on or off
262 j = find_token(document.body, '\\begin_inset Formula', j)
265 k = find_end_of_inset(document.body, j)
267 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(j))
270 code = "\n".join(document.body[j:k])
272 if code.find("\\%s" % c) != -1:
273 # set amsmath to on, since it is loaded by the newer format
274 document.header[i] = "\\use_package amsmath 2"
279 def revert_swissgerman(document):
280 " Set language german-ch-old to german "
282 if document.language == "german-ch-old":
283 document.language = "german"
284 i = find_token(document.header, "\\language", 0)
286 document.header[i] = "\\language german"
289 j = find_token(document.body, "\\lang german-ch-old", j)
292 document.body[j] = document.body[j].replace("\\lang german-ch-old", "\\lang german")
296 def revert_use_package(document, pkg, commands, oldauto, supported):
297 # oldauto defines how the version we are reverting to behaves:
298 # if it is true, the old version uses the package automatically.
299 # if it is false, the old version never uses the package.
300 # If "supported" is true, the target version also supports this
302 regexp = re.compile(r'(\\use_package\s+%s)' % pkg)
303 p = find_re(document.header, regexp, 0)
304 value = "1" # default is auto
306 value = get_value(document.header, "\\use_package" , p).split()[1]
308 del document.header[p]
309 if value == "2" and not supported: # on
310 add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
311 elif value == "1" and not oldauto: # auto
314 i = find_token(document.body, '\\begin_inset Formula', i)
317 j = find_end_of_inset(document.body, i)
319 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
322 code = "\n".join(document.body[i:j])
324 if code.find("\\%s" % c) != -1:
326 document.header[p] = "\\use_package " + pkg + " 2"
328 add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
333 mathtools_commands = ["xhookrightarrow", "xhookleftarrow", "xRightarrow", \
334 "xrightharpoondown", "xrightharpoonup", "xrightleftharpoons", \
335 "xLeftarrow", "xleftharpoondown", "xleftharpoonup", \
336 "xleftrightarrow", "xLeftrightarrow", "xleftrightharpoons", \
339 def revert_xarrow(document):
340 "remove use_package mathtools"
341 revert_use_package(document, "mathtools", mathtools_commands, False, True)
344 def revert_beamer_lemma(document):
345 " Reverts beamer lemma layout to ERT "
347 beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
348 if document.textclass not in beamer_classes:
354 i = find_token(document.body, "\\begin_layout Lemma", i)
357 j = find_end_of_layout(document.body, i)
359 document.warning("Malformed LyX document: Can't find end of Lemma layout")
362 arg1 = find_token(document.body, "\\begin_inset Argument 1", i, j)
363 endarg1 = find_end_of_inset(document.body, arg1)
364 arg2 = find_token(document.body, "\\begin_inset Argument 2", i, j)
365 endarg2 = find_end_of_inset(document.body, arg2)
369 beginPlain1 = find_token(document.body, "\\begin_layout Plain Layout", arg1, endarg1)
370 if beginPlain1 == -1:
371 document.warning("Malformed LyX document: Can't find arg1 plain Layout")
374 endPlain1 = find_end_of_inset(document.body, beginPlain1)
375 content1 = document.body[beginPlain1 + 1 : endPlain1 - 2]
376 subst1 = put_cmd_in_ert("<") + content1 + put_cmd_in_ert(">")
378 beginPlain2 = find_token(document.body, "\\begin_layout Plain Layout", arg2, endarg2)
379 if beginPlain2 == -1:
380 document.warning("Malformed LyX document: Can't find arg2 plain Layout")
383 endPlain2 = find_end_of_inset(document.body, beginPlain2)
384 content2 = document.body[beginPlain2 + 1 : endPlain2 - 2]
385 subst2 = put_cmd_in_ert("[") + content2 + put_cmd_in_ert("]")
389 del document.body[arg2 : endarg2 + 1]
391 del document.body[arg1 : endarg1 + 1]
393 del document.body[arg1 : endarg1 + 1]
395 del document.body[arg2 : endarg2 + 1]
397 # index of end layout has probably changed
398 j = find_end_of_layout(document.body, i)
400 document.warning("Malformed LyX document: Can't find end of Lemma layout")
406 # if this is not a consecutive env, add start command
408 begcmd = put_cmd_in_ert("\\begin{lemma}")
410 # has this a consecutive lemma?
411 consecutive = document.body[j + 2] == "\\begin_layout Lemma"
413 # if this is not followed by a consecutive env, add end command
415 document.body[j : j + 1] = put_cmd_in_ert("\\end{lemma}") + ["\\end_layout"]
417 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd + subst1 + subst2
423 def revert_question_env(document):
425 Reverts question and question* environments of
426 theorems-ams-extended-bytype module to ERT
429 # Do we use theorems-ams-extended-bytype module?
431 mods = document.get_module_list()
433 if mod == "theorems-ams-extended-bytype":
443 i = find_token(document.body, "\\begin_layout Question", i)
447 starred = document.body[i] == "\\begin_layout Question*"
449 j = find_end_of_layout(document.body, i)
451 document.warning("Malformed LyX document: Can't find end of Question layout")
455 # if this is not a consecutive env, add start command
459 begcmd = put_cmd_in_ert("\\begin{question*}")
461 begcmd = put_cmd_in_ert("\\begin{question}")
463 # has this a consecutive theorem of same type?
466 consecutive = document.body[j + 2] == "\\begin_layout Question*"
468 consecutive = document.body[j + 2] == "\\begin_layout Question"
470 # if this is not followed by a consecutive env, add end command
473 document.body[j : j + 1] = put_cmd_in_ert("\\end{question*}") + ["\\end_layout"]
475 document.body[j : j + 1] = put_cmd_in_ert("\\end{question}") + ["\\end_layout"]
477 document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
479 add_to_preamble(document, "\\providecommand{\questionname}{Question}")
482 add_to_preamble(document, "\\theoremstyle{plain}\n" \
483 "\\newtheorem*{question*}{\\protect\\questionname}")
485 add_to_preamble(document, "\\theoremstyle{plain}\n" \
486 "\\newtheorem{question}{\\protect\\questionname}")
491 def convert_dashes(document):
492 "convert -- and --- to \\twohyphens and \\threehyphens"
494 if document.backend != "latex":
498 while i < len(document.body):
499 words = document.body[i].split()
500 if len(words) > 1 and words[0] == "\\begin_inset" and \
501 words[1] in ["CommandInset", "ERT", "External", "Formula", "Graphics", "IPA", "listings"]:
502 # must not replace anything in insets that store LaTeX contents in .lyx files
503 # (math and command insets withut overridden read() and write() methods
504 # filtering out IPA makes Text::readParToken() more simple
505 # skip ERT as well since it is not needed there
506 j = find_end_of_inset(document.body, i)
508 document.warning("Malformed LyX document: Can't find end of " + words[1] + " inset at line " + str(i))
514 j = document.body[i].find("--")
517 front = document.body[i][:j]
518 back = document.body[i][j+2:]
519 # We can have an arbitrary number of consecutive hyphens.
520 # These must be split into the corresponding number of two and three hyphens
521 # We must match what LaTeX does: First try emdash, then endash, then single hyphen
522 if back.find("-") == 0:
525 document.body.insert(i+1, back)
526 document.body[i] = front + "\\threehyphens"
529 document.body.insert(i+1, back)
530 document.body[i] = front + "\\twohyphens"
534 def revert_dashes(document):
535 "convert \\twohyphens and \\threehyphens to -- and ---"
538 while i < len(document.body):
539 words = document.body[i].split()
540 if len(words) > 1 and words[0] == "\\begin_inset" and \
541 words[1] in ["CommandInset", "ERT", "External", "Formula", "Graphics", "IPA", "listings"]:
543 j = find_end_of_inset(document.body, i)
545 document.warning("Malformed LyX document: Can't find end of " + words[1] + " inset at line " + str(i))
551 if document.body[i].find("\\twohyphens") >= 0:
552 document.body[i] = document.body[i].replace("\\twohyphens", "--")
554 if document.body[i].find("\\threehyphens") >= 0:
555 document.body[i] = document.body[i].replace("\\threehyphens", "---")
557 if replaced and i+1 < len(document.body) and \
558 (document.body[i+1].find("\\") != 0 or \
559 document.body[i+1].find("\\twohyphens") == 0 or
560 document.body[i+1].find("\\threehyphens") == 0) and \
561 len(document.body[i]) + len(document.body[i+1]) <= 80:
562 document.body[i] = document.body[i] + document.body[i+1]
563 document.body[i+1:i+2] = []
568 # order is important for the last three!
569 phrases = ["LyX", "LaTeX2e", "LaTeX", "TeX"]
571 def is_part_of_converted_phrase(line, j, phrase):
572 "is phrase part of an already converted phrase?"
574 converted = "\\SpecialCharNoPassThru \\" + p
575 pos = j + len(phrase) - len(converted)
577 if line[pos:pos+len(converted)] == converted:
582 def convert_phrases(document):
583 "convert special phrases from plain text to \\SpecialCharNoPassThru"
585 if document.backend != "latex":
588 for phrase in phrases:
590 while i < len(document.body):
591 words = document.body[i].split()
592 if len(words) > 1 and words[0] == "\\begin_inset" and \
593 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
594 # must not replace anything in insets that store LaTeX contents in .lyx files
595 # (math and command insets withut overridden read() and write() methods
596 j = find_end_of_inset(document.body, i)
598 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
603 if document.body[i].find("\\") == 0:
606 j = document.body[i].find(phrase)
610 if not is_part_of_converted_phrase(document.body[i], j, phrase):
611 front = document.body[i][:j]
612 back = document.body[i][j+len(phrase):]
614 document.body.insert(i+1, back)
615 # We cannot use SpecialChar since we do not know whether we are outside passThru
616 document.body[i] = front + "\\SpecialCharNoPassThru \\" + phrase
620 def revert_phrases(document):
621 "convert special phrases to plain text"
624 while i < len(document.body):
625 words = document.body[i].split()
626 if len(words) > 1 and words[0] == "\\begin_inset" and \
627 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
628 # see convert_phrases
629 j = find_end_of_inset(document.body, i)
631 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
637 for phrase in phrases:
638 # we can replace SpecialChar since LyX ensures that it cannot be inserted into passThru parts
639 if document.body[i].find("\\SpecialChar \\" + phrase) >= 0:
640 document.body[i] = document.body[i].replace("\\SpecialChar \\" + phrase, phrase)
642 if document.body[i].find("\\SpecialCharNoPassThru \\" + phrase) >= 0:
643 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru \\" + phrase, phrase)
645 if replaced and i+1 < len(document.body) and \
646 (document.body[i+1].find("\\") != 0 or \
647 document.body[i+1].find("\\SpecialChar") == 0) and \
648 len(document.body[i]) + len(document.body[i+1]) <= 80:
649 document.body[i] = document.body[i] + document.body[i+1]
650 document.body[i+1:i+2] = []
655 def convert_specialchar_internal(document, forward):
656 specialchars = {"\\-":"softhyphen", "\\textcompwordmark{}":"ligaturebreak", \
657 "\\@.":"endofsentence", "\\ldots{}":"ldots", \
658 "\\menuseparator":"menuseparator", "\\slash{}":"breakableslash", \
659 "\\nobreakdash-":"nobreakdash", "\\LyX":"LyX", \
660 "\\TeX":"TeX", "\\LaTeX2e":"LaTeX2e", \
661 "\\LaTeX":"LaTeX" # must be after LaTeX2e
665 while i < len(document.body):
666 words = document.body[i].split()
667 if len(words) > 1 and words[0] == "\\begin_inset" and \
668 words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
669 # see convert_phrases
670 j = find_end_of_inset(document.body, i)
672 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
677 for key, value in specialchars.iteritems():
679 document.body[i] = document.body[i].replace("\\SpecialChar " + key, "\\SpecialChar " + value)
680 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + key, "\\SpecialCharNoPassThru " + value)
682 document.body[i] = document.body[i].replace("\\SpecialChar " + value, "\\SpecialChar " + key)
683 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + value, "\\SpecialCharNoPassThru " + key)
687 def convert_specialchar(document):
688 "convert special characters to new syntax"
689 convert_specialchar_internal(document, True)
692 def revert_specialchar(document):
693 "convert special characters to old syntax"
694 convert_specialchar_internal(document, False)
697 def revert_georgian(document):
698 "Set the document language to English but assure Georgian output"
700 if document.language == "georgian":
701 document.language = "english"
702 i = find_token(document.header, "\\language georgian", 0)
704 document.header[i] = "\\language english"
705 j = find_token(document.header, "\\language_package default", 0)
707 document.header[j] = "\\language_package babel"
708 k = find_token(document.header, "\\options", 0)
710 document.header[k] = document.header[k].replace("\\options", "\\options georgian,")
712 l = find_token(document.header, "\\use_default_options", 0)
713 document.header.insert(l + 1, "\\options georgian")
716 def revert_sigplan_doi(document):
717 " Reverts sigplanconf DOI layout to ERT "
719 if document.textclass != "sigplanconf":
724 i = find_token(document.body, "\\begin_layout DOI", i)
727 j = find_end_of_layout(document.body, i)
729 document.warning("Malformed LyX document: Can't find end of DOI layout")
733 content = lyx2latex(document, document.body[i:j + 1])
734 add_to_preamble(document, ["\\doi{" + content + "}"])
735 del document.body[i:j + 1]
739 def revert_ex_itemargs(document):
740 " Reverts \\item arguments of the example environments (Linguistics module) to TeX-code "
742 # Do we use the linguistics module?
744 mods = document.get_module_list()
746 if mod == "linguistics":
754 example_layouts = ["Numbered Examples (consecutive)", "Subexample"]
756 i = find_token(document.body, "\\begin_inset Argument item:", i)
759 j = find_end_of_inset(document.body, i)
760 # Find containing paragraph layout
761 parent = get_containing_layout(document.body, i)
763 document.warning("Malformed LyX document: Can't find parent paragraph layout")
767 layoutname = parent[0]
768 if layoutname in example_layouts:
769 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
770 endPlain = find_end_of_layout(document.body, beginPlain)
771 content = document.body[beginPlain + 1 : endPlain]
772 del document.body[i:j+1]
773 subst = put_cmd_in_ert("[") + content + put_cmd_in_ert("]")
774 document.body[parbeg : parbeg] = subst
778 def revert_forest(document):
779 " Reverts the forest environment (Linguistics module) to TeX-code "
781 # Do we use the linguistics module?
783 mods = document.get_module_list()
785 if mod == "linguistics":
794 i = find_token(document.body, "\\begin_inset Flex Structure Tree", i)
797 j = find_end_of_inset(document.body, i)
799 document.warning("Malformed LyX document: Can't find end of Structure Tree inset")
803 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
804 endPlain = find_end_of_layout(document.body, beginPlain)
805 content = lyx2latex(document, document.body[beginPlain : endPlain])
807 add_to_preamble(document, ["\\usepackage{forest}"])
809 document.body[i:j + 1] = ["\\begin_inset ERT", "status collapsed", "",
810 "\\begin_layout Plain Layout", "", "\\backslash",
811 "begin{forest}", "\\end_layout", "", "\\begin_layout Plain Layout",
812 content, "\\end_layout", "", "\\begin_layout Plain Layout",
813 "\\backslash", "end{forest}", "", "\\end_layout", "", "\\end_inset"]
817 def revert_glossgroup(document):
818 " Reverts the GroupGlossedWords inset (Linguistics module) to TeX-code "
820 # Do we use the linguistics module?
822 mods = document.get_module_list()
824 if mod == "linguistics":
833 i = find_token(document.body, "\\begin_inset Flex GroupGlossedWords", i)
836 j = find_end_of_inset(document.body, i)
838 document.warning("Malformed LyX document: Can't find end of GroupGlossedWords inset")
842 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
843 endPlain = find_end_of_layout(document.body, beginPlain)
844 content = lyx2latex(document, document.body[beginPlain : endPlain])
845 document.warning("content: %s" % content)
847 document.body[i:j + 1] = ["{", "", content, "", "}"]
851 def revert_newgloss(document):
852 " Reverts the new Glosse insets (Linguistics module) to the old format "
854 # Do we use the linguistics module?
856 mods = document.get_module_list()
858 if mod == "linguistics":
865 glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
866 for glosse in glosses:
869 i = find_token(document.body, glosse, i)
872 j = find_end_of_inset(document.body, i)
874 document.warning("Malformed LyX document: Can't find end of Glosse inset")
878 arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
879 endarg = find_end_of_inset(document.body, arg)
882 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
883 if argbeginPlain == -1:
884 document.warning("Malformed LyX document: Can't find arg plain Layout")
887 argendPlain = find_end_of_inset(document.body, argbeginPlain)
888 argcontent = lyx2latex(document, document.body[argbeginPlain : argendPlain - 2])
890 document.body[j:j] = ["", "\\begin_layout Plain Layout","\\backslash", "glt ",
891 argcontent, "\\end_layout"]
893 # remove Arg insets and paragraph, if it only contains this inset
894 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
895 del document.body[arg - 1 : endarg + 4]
897 del document.body[arg : endarg + 1]
899 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
900 endPlain = find_end_of_layout(document.body, beginPlain)
901 content = lyx2latex(document, document.body[beginPlain : endPlain])
903 document.body[beginPlain + 1:endPlain] = [content]
907 def convert_newgloss(document):
908 " Converts Glosse insets (Linguistics module) to the new format "
910 # Do we use the linguistics module?
912 mods = document.get_module_list()
914 if mod == "linguistics":
921 glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
922 for glosse in glosses:
925 i = find_token(document.body, glosse, i)
928 j = find_end_of_inset(document.body, i)
930 document.warning("Malformed LyX document: Can't find end of Glosse inset")
937 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", k, j)
940 endPlain = find_end_of_layout(document.body, beginPlain)
942 document.warning("Malformed LyX document: Can't find end of Glosse layout")
946 glt = find_token(document.body, "\\backslash", beginPlain, endPlain)
947 if glt != -1 and document.body[glt + 1].startswith("glt"):
948 document.body[glt + 1] = document.body[glt + 1].lstrip("glt").lstrip()
949 argcontent = document.body[glt + 1 : endPlain]
950 document.body[beginPlain + 1 : endPlain] = ["\\begin_inset Argument 1", "status open", "",
951 "\\begin_layout Plain Layout", "\\begin_inset ERT", "status open", "",
952 "\\begin_layout Plain Layout", ""] + argcontent + ["\\end_layout", "", "\\end_inset", "",
953 "\\end_layout", "", "\\end_inset"]
955 content = document.body[beginPlain + 1 : endPlain]
956 document.body[beginPlain + 1 : endPlain] = ["\\begin_inset ERT", "status open", "",
957 "\\begin_layout Plain Layout"] + content + ["\\end_layout", "", "\\end_inset"]
959 endPlain = find_end_of_layout(document.body, beginPlain)
961 j = find_end_of_inset(document.body, i)
966 def convert_BoxFeatures(document):
967 " adds new box features "
971 i = find_token(document.body, "height_special", i)
974 document.body[i+1:i+1] = ['thickness "0.4pt"', 'separation "3pt"', 'shadowsize "4pt"']
978 def revert_BoxFeatures(document):
979 " outputs new box features as TeX code "
983 defaultThick = "0.4pt"
984 defaultShadow = "4pt"
986 i = find_token(document.body, "height_special", i)
989 # read out the values
990 beg = document.body[i+1].find('"');
991 end = document.body[i+1].rfind('"');
992 thickness = document.body[i+1][beg+1:end];
993 beg = document.body[i+2].find('"');
994 end = document.body[i+2].rfind('"');
995 separation = document.body[i+2][beg+1:end];
996 beg = document.body[i+3].find('"');
997 end = document.body[i+3].rfind('"');
998 shadowsize = document.body[i+3][beg+1:end];
999 # delete the specification
1000 del document.body[i+1:i+4]
1002 # first output the closing brace
1003 if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1004 document.body[i + 10 : i + 10] = put_cmd_in_ert("}")
1005 # now output the lengths
1006 if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1007 document.body[i - 10 : i - 10] = put_cmd_in_ert("{")
1008 if thickness != defaultThick:
1009 document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness]
1010 if separation != defaultSep and thickness == defaultThick:
1011 document.body[i - 5 : i - 4] = ["{\\backslash fboxsep " + separation]
1012 if separation != defaultSep and thickness != defaultThick:
1013 document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation]
1014 if shadowsize != defaultShadow and separation == defaultSep and thickness == defaultThick:
1015 document.body[i - 5 : i - 4] = ["{\\backslash shadowsize " + shadowsize]
1016 if shadowsize != defaultShadow and separation != defaultSep and thickness == defaultThick:
1017 document.body[i - 5 : i - 4] = ["{\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1018 if shadowsize != defaultShadow and separation == defaultSep and thickness != defaultThick:
1019 document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash shadowsize " + shadowsize]
1020 if shadowsize != defaultShadow and separation != defaultSep and thickness != defaultThick:
1021 document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1025 def convert_origin(document):
1026 " Insert the origin tag "
1028 i = find_token(document.header, "\\textclass ", 0)
1030 document.warning("Malformed LyX document: No \\textclass!!")
1032 if document.dir == "":
1035 origin = document.dir.replace('\\', '/') + '/'
1037 origin = unicode(origin, sys.getfilesystemencoding())
1038 document.header[i:i] = ["\\origin " + origin]
1041 def revert_origin(document):
1042 " Remove the origin tag "
1044 i = find_token(document.header, "\\origin ", 0)
1046 document.warning("Malformed LyX document: No \\origin!!")
1048 del document.header[i]
1051 color_names = ["brown", "darkgray", "gray", \
1052 "lightgray", "lime", "olive", "orange", \
1053 "pink", "purple", "teal", "violet"]
1055 def revert_textcolor(document):
1056 " revert new \\textcolor colors to TeX code "
1062 i = find_token(document.body, "\\color ", i)
1066 for color in list(color_names):
1067 if document.body[i] == "\\color " + color:
1068 # register that xcolor must be loaded in the preamble
1071 add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\usepackage{xcolor}}{}"])
1072 # find the next \\color and/or the next \\end_layout
1073 j = find_token(document.body, "\\color", i + 1)
1074 k = find_token(document.body, "\\end_layout", i + 1)
1075 if j == -1 and k != -1:
1078 # first output the closing brace
1080 document.body[k: k] = put_cmd_in_ert("}")
1082 document.body[j: j] = put_cmd_in_ert("}")
1083 # now output the \textcolor command
1084 document.body[i : i + 1] = put_cmd_in_ert("\\textcolor{" + color + "}{")
1088 def convert_colorbox(document):
1089 " adds color settings for boxes "
1093 i = find_token(document.body, "shadowsize", i)
1096 document.body[i+1:i+1] = ['framecolor "black"', 'backgroundcolor "none"']
1100 def revert_colorbox(document):
1101 " outputs color settings for boxes as TeX code "
1104 defaultframecolor = "black"
1105 defaultbackcolor = "none"
1107 binset = find_token(document.body, "\\begin_inset Box", binset)
1111 einset = find_end_of_inset(document.body, binset)
1113 document.warning("Malformed LyX document: Can't find end of box inset!")
1117 blay = find_token(document.body, "\\begin_layout", binset, einset)
1119 document.warning("Malformed LyX document: Can't find start of layout!")
1123 # doing it this way, we make sure only to find a framecolor option
1124 frame = find_token(document.body, "framecolor", binset, blay)
1129 beg = document.body[frame].find('"')
1130 end = document.body[frame].rfind('"')
1131 framecolor = document.body[frame][beg+1:end]
1133 # this should be on the next line
1135 beg = document.body[bgcolor].find('"')
1136 end = document.body[bgcolor].rfind('"')
1137 backcolor = document.body[bgcolor][beg+1:end]
1140 del document.body[frame:frame+2]
1141 # adjust end of inset
1144 if document.body[binset] == "\\begin_inset Box Boxed" and \
1145 framecolor != defaultframecolor:
1146 document.body[binset] = "\\begin_inset Box Frameless"
1149 # first output the closing brace
1150 if framecolor == defaultframecolor and backcolor == defaultbackcolor:
1154 document.body[einset + 1 : einset + 1] = put_cmd_in_ert("}")
1155 if framecolor != defaultframecolor:
1156 document.body[binset:binset] = put_cmd_in_ert("\\backslash fcolorbox{" + framecolor + "}{" + backcolor + "}{")
1158 document.body[binset:binset] = put_cmd_in_ert("\\backslash colorbox{" + backcolor + "}{")
1163 def revert_mathmulticol(document):
1164 " Convert formulas to ERT if they contain multicolumns "
1168 i = find_token(document.body, '\\begin_inset Formula', i)
1171 j = find_end_of_inset(document.body, i)
1173 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
1176 lines = document.body[i:j]
1177 lines[0] = lines[0].replace('\\begin_inset Formula', '').lstrip()
1178 code = "\n".join(lines)
1183 n = code.find("\\multicolumn", k)
1184 # no need to convert degenerated multicolumn cells,
1185 # they work in old LyX versions as "math ERT"
1186 if n != -1 and code.find("\\multicolumn{1}", k) != n:
1187 ert = put_cmd_in_ert(code)
1188 document.body[i:j+1] = ert
1194 i = find_end_of_inset(document.body, i)
1199 def revert_jss(document):
1200 " Reverts JSS In_Preamble commands to ERT in preamble "
1202 if document.textclass != "jss":
1211 # at first revert the inset layouts because they can be part of the In_Preamble layouts
1212 while m != -1 or j != -1 or h != -1 or k != -1 or n != -1:
1215 h = find_token(document.body, "\\begin_inset Flex Pkg", h)
1217 endh = find_end_of_inset(document.body, h)
1218 document.body[endh - 2 : endh + 1] = put_cmd_in_ert("}")
1219 document.body[h : h + 4] = put_cmd_in_ert("\\pkg{")
1223 m = find_token(document.body, "\\begin_inset Flex Proglang", m)
1225 endm = find_end_of_inset(document.body, m)
1226 document.body[endm - 2 : endm + 1] = put_cmd_in_ert("}")
1227 document.body[m : m + 4] = put_cmd_in_ert("\\proglang{")
1231 j = find_token(document.body, "\\begin_inset Flex Code", j)
1233 # assure that we are not in a Code Chunk inset
1234 if document.body[j][-1] == "e":
1235 endj = find_end_of_inset(document.body, j)
1236 document.body[endj - 2 : endj + 1] = put_cmd_in_ert("}")
1237 document.body[j : j + 4] = put_cmd_in_ert("\\code{")
1243 k = find_token(document.body, "\\begin_inset Flex E-mail", k)
1245 endk = find_end_of_inset(document.body, k)
1246 document.body[endk - 2 : endk + 1] = put_cmd_in_ert("}")
1247 document.body[k : k + 4] = put_cmd_in_ert("\\email{")
1251 n = find_token(document.body, "\\begin_inset Flex URL", n)
1253 endn = find_end_of_inset(document.body, n)
1254 document.body[endn - 2 : endn + 1] = put_cmd_in_ert("}")
1255 document.body[n : n + 4] = put_cmd_in_ert("\\url{")
1257 # now revert the In_Preamble layouts
1259 i = find_token(document.body, "\\begin_layout Title", 0)
1262 j = find_end_of_layout(document.body, i)
1264 document.warning("Malformed LyX document: Can't find end of Title layout")
1267 content = lyx2latex(document, document.body[i:j + 1])
1268 add_to_preamble(document, ["\\title{" + content + "}"])
1269 del document.body[i:j + 1]
1271 i = find_token(document.body, "\\begin_layout Author", 0)
1274 j = find_end_of_layout(document.body, i)
1276 document.warning("Malformed LyX document: Can't find end of Author layout")
1279 content = lyx2latex(document, document.body[i:j + 1])
1280 add_to_preamble(document, ["\\author{" + content + "}"])
1281 del document.body[i:j + 1]
1283 i = find_token(document.body, "\\begin_layout Plain Author", 0)
1286 j = find_end_of_layout(document.body, i)
1288 document.warning("Malformed LyX document: Can't find end of Plain Author layout")
1291 content = lyx2latex(document, document.body[i:j + 1])
1292 add_to_preamble(document, ["\\Plainauthor{" + content + "}"])
1293 del document.body[i:j + 1]
1295 i = find_token(document.body, "\\begin_layout Plain Title", 0)
1298 j = find_end_of_layout(document.body, i)
1300 document.warning("Malformed LyX document: Can't find end of Plain Title layout")
1303 content = lyx2latex(document, document.body[i:j + 1])
1304 add_to_preamble(document, ["\\Plaintitle{" + content + "}"])
1305 del document.body[i:j + 1]
1307 i = find_token(document.body, "\\begin_layout Short Title", 0)
1310 j = find_end_of_layout(document.body, i)
1312 document.warning("Malformed LyX document: Can't find end of Short Title layout")
1315 content = lyx2latex(document, document.body[i:j + 1])
1316 add_to_preamble(document, ["\\Shorttitle{" + content + "}"])
1317 del document.body[i:j + 1]
1319 i = find_token(document.body, "\\begin_layout Abstract", 0)
1322 j = find_end_of_layout(document.body, i)
1324 document.warning("Malformed LyX document: Can't find end of Abstract layout")
1327 content = lyx2latex(document, document.body[i:j + 1])
1328 add_to_preamble(document, ["\\Abstract{" + content + "}"])
1329 del document.body[i:j + 1]
1331 i = find_token(document.body, "\\begin_layout Keywords", 0)
1334 j = find_end_of_layout(document.body, i)
1336 document.warning("Malformed LyX document: Can't find end of Keywords layout")
1339 content = lyx2latex(document, document.body[i:j + 1])
1340 add_to_preamble(document, ["\\Keywords{" + content + "}"])
1341 del document.body[i:j + 1]
1343 i = find_token(document.body, "\\begin_layout Plain Keywords", 0)
1346 j = find_end_of_layout(document.body, i)
1348 document.warning("Malformed LyX document: Can't find end of Plain Keywords layout")
1351 content = lyx2latex(document, document.body[i:j + 1])
1352 add_to_preamble(document, ["\\Plainkeywords{" + content + "}"])
1353 del document.body[i:j + 1]
1355 i = find_token(document.body, "\\begin_layout Address", 0)
1358 j = find_end_of_layout(document.body, i)
1360 document.warning("Malformed LyX document: Can't find end of Address layout")
1363 content = lyx2latex(document, document.body[i:j + 1])
1364 add_to_preamble(document, ["\\Address{" + content + "}"])
1365 del document.body[i:j + 1]
1366 # finally handle the code layouts
1371 while m != -1 or j != -1 or h != -1 or k != -1:
1374 h = find_token(document.body, "\\begin_inset Flex Code Chunk", h)
1376 endh = find_end_of_inset(document.body, h)
1377 document.body[endh + 1 : endh] = ["\\end_layout"]
1378 document.body[endh : endh + 1] = put_cmd_in_ert("\\end{CodeChunk}")
1379 document.body[h : h + 3] = put_cmd_in_ert("\\begin{CodeChunk}")
1380 document.body[h - 1 : h] = ["\\begin_layout Standard"]
1384 j = find_token(document.body, "\\begin_layout Code Input", j)
1386 endj = find_end_of_layout(document.body, j)
1387 document.body[endj : endj + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1388 document.body[endj + 3 : endj + 4] = put_cmd_in_ert("\\end{CodeInput}")
1389 document.body[endj + 13 : endj + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1390 document.body[j + 1 : j] = ["\\end_layout", "", "\\begin_layout Standard"]
1391 document.body[j : j + 1] = put_cmd_in_ert("\\begin{CodeInput}")
1395 k = find_token(document.body, "\\begin_layout Code Output", k)
1397 endk = find_end_of_layout(document.body, k)
1398 document.body[endk : endk + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1399 document.body[endk + 3 : endk + 4] = put_cmd_in_ert("\\end{CodeOutput}")
1400 document.body[endk + 13 : endk + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1401 document.body[k + 1 : k] = ["\\end_layout", "", "\\begin_layout Standard"]
1402 document.body[k : k + 1] = put_cmd_in_ert("\\begin{CodeOutput}")
1406 m = find_token(document.body, "\\begin_layout Code", m)
1408 endm = find_end_of_layout(document.body, m)
1409 document.body[endm : endm + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1410 document.body[endm + 3 : endm + 4] = put_cmd_in_ert("\\end{Code}")
1411 document.body[endm + 13 : endm + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1412 document.body[m + 1 : m] = ["\\end_layout", "", "\\begin_layout Standard"]
1413 document.body[m : m + 1] = put_cmd_in_ert("\\begin{Code}")
1417 def convert_subref(document):
1418 " converts sub: ref prefixes to subref: "
1421 rx = re.compile(r'^name \"sub:(.+)$')
1424 i = find_token(document.body, "\\begin_inset CommandInset label", i)
1427 j = find_end_of_inset(document.body, i)
1429 document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1433 for p in range(i, j):
1434 m = rx.match(document.body[p])
1437 document.body[p] = "name \"subsec:" + label
1441 rx = re.compile(r'^reference \"sub:(.+)$')
1444 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1447 j = find_end_of_inset(document.body, i)
1449 document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1453 for p in range(i, j):
1454 m = rx.match(document.body[p])
1457 document.body[p] = "reference \"subsec:" + label
1463 def revert_subref(document):
1464 " reverts subref: ref prefixes to sub: "
1467 rx = re.compile(r'^name \"subsec:(.+)$')
1470 i = find_token(document.body, "\\begin_inset CommandInset label", i)
1473 j = find_end_of_inset(document.body, i)
1475 document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1479 for p in range(i, j):
1480 m = rx.match(document.body[p])
1483 document.body[p] = "name \"sub:" + label
1488 rx = re.compile(r'^reference \"subsec:(.+)$')
1491 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1494 j = find_end_of_inset(document.body, i)
1496 document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1500 for p in range(i, j):
1501 m = rx.match(document.body[p])
1504 document.body[p] = "reference \"sub:" + label
1509 def convert_nounzip(document):
1510 " remove the noUnzip parameter of graphics insets "
1512 rx = re.compile(r'\s*noUnzip\s*$')
1515 i = find_token(document.body, "\\begin_inset Graphics", i)
1518 j = find_end_of_inset(document.body, i)
1520 document.warning("Malformed LyX document: Can't find end of graphics inset at line " + str(i))
1524 k = find_re(document.body, rx, i, j)
1526 del document.body[k]
1531 def convert_revert_external_bbox(document, forward):
1532 " add units to bounding box of external insets "
1534 rx = re.compile(r'^\s*boundingBox\s+\S+\s+\S+\s+\S+\s+\S+\s*$')
1537 i = find_token(document.body, "\\begin_inset External", i)
1540 j = find_end_of_inset(document.body, i)
1542 document.warning("Malformed LyX document: Can't find end of external inset at line " + str(i))
1545 k = find_re(document.body, rx, i, j)
1549 tokens = document.body[k].split()
1551 for t in range(1, 5):
1554 for t in range(1, 5):
1555 tokens[t] = length_in_bp(tokens[t])
1556 document.body[k] = "\tboundingBox " + tokens[1] + " " + tokens[2] + " " + \
1557 tokens[3] + " " + tokens[4]
1561 def convert_external_bbox(document):
1562 convert_revert_external_bbox(document, True)
1565 def revert_external_bbox(document):
1566 convert_revert_external_bbox(document, False)
1573 supported_versions = ["2.2.0", "2.2"]
1575 [475, [convert_separator]],
1576 # nothing to do for 476: We consider it a bug that older versions
1577 # did not load amsmath automatically for these commands, and do not
1578 # want to hardcode amsmath off.
1584 [481, [convert_dashes]],
1585 [482, [convert_phrases]],
1586 [483, [convert_specialchar]],
1591 [488, [convert_newgloss]],
1592 [489, [convert_BoxFeatures]],
1593 [490, [convert_origin]],
1595 [492, [convert_colorbox]],
1598 [495, [convert_subref]],
1599 [496, [convert_nounzip]],
1600 [497, [convert_external_bbox]]
1604 [496, [revert_external_bbox]],
1605 [495, []], # nothing to do since the noUnzip parameter was optional
1606 [494, [revert_subref]],
1607 [493, [revert_jss]],
1608 [492, [revert_mathmulticol]],
1609 [491, [revert_colorbox]],
1610 [490, [revert_textcolor]],
1611 [489, [revert_origin]],
1612 [488, [revert_BoxFeatures]],
1613 [487, [revert_newgloss, revert_glossgroup]],
1614 [486, [revert_forest]],
1615 [485, [revert_ex_itemargs]],
1616 [484, [revert_sigplan_doi]],
1617 [483, [revert_georgian]],
1618 [482, [revert_specialchar]],
1619 [481, [revert_phrases]],
1620 [480, [revert_dashes]],
1621 [479, [revert_question_env]],
1622 [478, [revert_beamer_lemma]],
1623 [477, [revert_xarrow]],
1624 [476, [revert_swissgerman]],
1625 [475, [revert_smash]],
1626 [474, [revert_separator]]
1630 if __name__ == "__main__":