]> git.lyx.org Git - features.git/blob - lib/lyx2lyx/lyx_2_2.py
8571c5cdcc2fa0d5ad1d6bb2143ca82e122cb6cb
[features.git] / lib / lyx2lyx / lyx_2_2.py
1 # -*- coding: utf-8 -*-
2 # This file is part of lyx2lyx
3 # -*- coding: utf-8 -*-
4 # Copyright (C) 2015 The LyX team
5 #
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.
10 #
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.
15 #
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.
19
20 """ Convert files to the file format generated by lyx 2.2"""
21
22 import re, string
23 import unicodedata
24 import sys, os
25
26 # Uncomment only what you need to import, please.
27
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
32
33 from lyx2lyx_tools import add_to_preamble, put_cmd_in_ert, get_ert, lyx2latex, \
34   lyx2verbatim, length_in_bp
35 #  insert_to_preamble, latex_length, revert_flex_inset, \
36 #  revert_font_attrs, hex2ratio, str2bool
37
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
41
42 ####################################################################
43 # Private helper functions
44
45 def revert_Argument_to_TeX_brace(document, line, endline, n, nmax, environment, opt, nolastopt):
46     '''
47     Reverts an InsetArgument to TeX-code
48     usage:
49     revert_Argument_to_TeX_brace(document, LineOfBegin, LineOfEnd, StartArgument, EndArgument, isEnvironment, isOpt, notLastOpt)
50     LineOfBegin is the line  of the \begin_layout or \begin_inset statement
51     LineOfEnd is the line  of the \end_layout or \end_inset statement, if "0" is given, the end of the file is used instead
52     StartArgument is the number of the first argument that needs to be converted
53     EndArgument is the number of the last argument that needs to be converted or the last defined one
54     isEnvironment must be true, if the layout is for a LaTeX environment
55     isOpt must be true, if the argument is an optional one
56     notLastOpt must be true if the argument is mandatory and followed by optional ones
57     '''
58     lineArg = 0
59     wasOpt = False
60     while lineArg != -1 and n < nmax + 1:
61       lineArg = find_token(document.body, "\\begin_inset Argument " + str(n), line)
62       if lineArg > endline and endline != 0:
63         return wasOpt
64       if lineArg != -1:
65         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
66         # we have to assure that no other inset is in the Argument
67         beginInset = find_token(document.body, "\\begin_inset", beginPlain)
68         endInset = find_token(document.body, "\\end_inset", beginPlain)
69         k = beginPlain + 1
70         l = k
71         while beginInset < endInset and beginInset != -1:
72           beginInset = find_token(document.body, "\\begin_inset", k)
73           endInset = find_token(document.body, "\\end_inset", l)
74           k = beginInset + 1
75           l = endInset + 1
76         if environment == False:
77           if opt == False:
78             if nolastopt == False:
79               document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}{")
80             else:
81               document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}") 
82             del(document.body[lineArg : beginPlain + 1])
83             wasOpt = False
84           else:
85             document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("]")
86             document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("[")
87             wasOpt = True
88         else:
89           if opt == False:
90             document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}")
91             document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("{")
92             wasOpt = False
93           else:
94             document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("]")
95             document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("[")
96             wasOpt = True
97         n += 1
98     return wasOpt
99
100
101 ###############################################################################
102 ###
103 ### Conversion and reversion routines
104 ###
105 ###############################################################################
106
107 def convert_separator(document):
108     """
109     Convert layout separators to separator insets and add (LaTeX) paragraph
110     breaks in order to mimic previous LaTeX export.
111     """
112
113     parins = ["\\begin_inset Separator parbreak", "\\end_inset", ""]
114     parlay = ["\\begin_layout Standard", "\\begin_inset Separator parbreak",
115               "\\end_inset", "", "\\end_layout", ""]
116     sty_dict = {
117         "family" : "default",
118         "series" : "default",
119         "shape"  : "default",
120         "size"   : "default",
121         "bar"    : "default",
122         "color"  : "inherit"
123         }
124
125     i = 0
126     while 1:
127         i = find_token(document.body, "\\begin_deeper", i)
128         if i == -1:
129             break
130
131         j = find_token_backwards(document.body, "\\end_layout", i-1)
132         if j != -1:
133             # reset any text style before inserting the inset
134             lay = get_containing_layout(document.body, j-1)
135             if lay != False:
136                 content = "\n".join(document.body[lay[1]:lay[2]])
137                 for val in list(sty_dict.keys()):
138                     if content.find("\\%s" % val) != -1:
139                         document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
140                         i = i + 1
141                         j = j + 1
142             document.body[j:j] = parins
143             i = i + len(parins) + 1
144         else:
145             i = i + 1
146
147     i = 0
148     while 1:
149         i = find_token(document.body, "\\align", i)
150         if i == -1:
151             break
152
153         lay = get_containing_layout(document.body, i)
154         if lay != False and lay[0] == "Plain Layout":
155             i = i + 1
156             continue
157
158         j = find_token_backwards(document.body, "\\end_layout", i-1)
159         if j != -1:
160             lay = get_containing_layout(document.body, j-1)
161             if lay != False and lay[0] == "Standard" \
162                and find_token(document.body, "\\align", lay[1], lay[2]) == -1 \
163                and find_token(document.body, "\\begin_inset VSpace", lay[1], lay[2]) == -1:
164                 # reset any text style before inserting the inset
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])]
169                         i = i + 1
170                         j = j + 1
171                 document.body[j:j] = parins
172                 i = i + len(parins) + 1
173             else:
174                 i = i + 1
175         else:
176             i = i + 1
177
178     regexp = re.compile(r'^\\begin_layout (?:(-*)|(\s*))(Separator|EndOfSlide)(?:(-*)|(\s*))$', re.IGNORECASE)
179
180     i = 0
181     while 1:
182         i = find_re(document.body, regexp, i)
183         if i == -1:
184             return
185
186         j = find_end_of_layout(document.body, i)
187         if j == -1:
188             document.warning("Malformed LyX document: Missing `\\end_layout'.")
189             return
190
191         lay = get_containing_layout(document.body, j-1)
192         if lay != False:
193             lines = document.body[lay[3]:lay[2]]
194         else:
195             lines = []
196
197         document.body[i:j+1] = parlay
198         if len(lines) > 0:
199             document.body[i+1:i+1] = lines
200
201         i = i + len(parlay) + len(lines) + 1
202
203
204 def revert_separator(document):
205     " Revert separator insets to layout separators "
206
207     beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
208     if document.textclass in beamer_classes:
209         beglaysep = "\\begin_layout Separator"
210     else:
211         beglaysep = "\\begin_layout --Separator--"
212
213     parsep = [beglaysep, "", "\\end_layout", ""]
214     comert = ["\\begin_inset ERT", "status collapsed", "",
215               "\\begin_layout Plain Layout", "%", "\\end_layout",
216               "", "\\end_inset", ""]
217     empert = ["\\begin_inset ERT", "status collapsed", "",
218               "\\begin_layout Plain Layout", " ", "\\end_layout",
219               "", "\\end_inset", ""]
220
221     i = 0
222     while 1:
223         i = find_token(document.body, "\\begin_inset Separator", i)
224         if i == -1:
225             return
226
227         lay = get_containing_layout(document.body, i)
228         if lay == False:
229             document.warning("Malformed LyX document: Can't convert separator inset at line " + str(i))
230             i = i + 1
231             continue
232
233         layoutname = lay[0]
234         beg = lay[1]
235         end = lay[2]
236         kind = get_value(document.body, "\\begin_inset Separator", i, i+1, "plain").split()[1]
237         before = document.body[beg+1:i]
238         something_before = len(before) > 0 and len("".join(before)) > 0
239         j = find_end_of_inset(document.body, i)
240         after = document.body[j+1:end]
241         something_after = len(after) > 0 and len("".join(after)) > 0
242         if kind == "plain":
243             beg = beg + len(before) + 1
244         elif something_before:
245             document.body[i:i] = ["\\end_layout", ""]
246             i = i + 2
247             j = j + 2
248             beg = i
249             end = end + 2
250
251         if kind == "plain":
252             if something_after:
253                 document.body[beg:j+1] = empert
254                 i = i + len(empert)
255             else:
256                 document.body[beg:j+1] = comert
257                 i = i + len(comert)
258         else:
259             if something_after:
260                 if layoutname == "Standard":
261                     if not something_before:
262                         document.body[beg:j+1] = parsep
263                         i = i + len(parsep)
264                         document.body[i:i] = ["", "\\begin_layout Standard"]
265                         i = i + 2
266                     else:
267                         document.body[beg:j+1] = ["\\begin_layout Standard"]
268                         i = i + 1
269                 else:
270                     document.body[beg:j+1] = ["\\begin_deeper"]
271                     i = i + 1
272                     end = end + 1 - (j + 1 - beg)
273                     if not something_before:
274                         document.body[i:i] = parsep
275                         i = i + len(parsep)
276                         end = end + len(parsep)
277                     document.body[i:i] = ["\\begin_layout Standard"]
278                     document.body[end+2:end+2] = ["", "\\end_deeper", ""]
279                     i = i + 4
280             else:
281                 next_par_is_aligned = False
282                 k = find_nonempty_line(document.body, end+1)
283                 if k != -1 and check_token(document.body[k], "\\begin_layout"):
284                     lay = get_containing_layout(document.body, k)
285                     next_par_is_aligned = lay != False and \
286                             find_token(document.body, "\\align", lay[1], lay[2]) != -1
287                 if k != -1 and not next_par_is_aligned \
288                         and not check_token(document.body[k], "\\end_deeper") \
289                         and not check_token(document.body[k], "\\begin_deeper"):
290                     if layoutname == "Standard":
291                         document.body[beg:j+1] = [beglaysep]
292                         i = i + 1
293                     else:
294                         document.body[beg:j+1] = ["\\begin_deeper", beglaysep]
295                         end = end + 2 - (j + 1 - beg)
296                         document.body[end+1:end+1] = ["", "\\end_deeper", ""]
297                         i = i + 3
298                 else:
299                     if something_before:
300                         del document.body[i:end+1]
301                     else:
302                         del document.body[i:end-1]
303
304         i = i + 1
305
306
307 def revert_smash(document):
308     " Set amsmath to on if smash commands are used "
309
310     commands = ["smash[t]", "smash[b]", "notag"]
311     i = find_token(document.header, "\\use_package amsmath", 0)
312     if i == -1:
313         document.warning("Malformed LyX document: Can't find \\use_package amsmath.")
314         return
315     value = get_value(document.header, "\\use_package amsmath", i).split()[1]
316     if value != "1":
317         # nothing to do if package is not auto but on or off
318         return
319     j = 0
320     while True:
321         j = find_token(document.body, '\\begin_inset Formula', j)
322         if j == -1:
323             return
324         k = find_end_of_inset(document.body, j)
325         if k == -1:
326             document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(j))
327             j += 1
328             continue
329         code = "\n".join(document.body[j:k])
330         for c in commands:
331             if code.find("\\%s" % c) != -1:
332                 # set amsmath to on, since it is loaded by the newer format
333                 document.header[i] = "\\use_package amsmath 2"
334                 return
335         j = k
336
337
338 def revert_swissgerman(document):
339     " Set language german-ch-old to german "
340     i = 0
341     if document.language == "german-ch-old":
342         document.language = "german"
343         i = find_token(document.header, "\\language", 0)
344         if i != -1:
345             document.header[i] = "\\language german"
346     j = 0
347     while True:
348         j = find_token(document.body, "\\lang german-ch-old", j)
349         if j == -1:
350             return
351         document.body[j] = document.body[j].replace("\\lang german-ch-old", "\\lang german")
352         j = j + 1
353
354
355 def revert_use_package(document, pkg, commands, oldauto, supported):
356     # oldauto defines how the version we are reverting to behaves:
357     # if it is true, the old version uses the package automatically.
358     # if it is false, the old version never uses the package.
359     # If "supported" is true, the target version also supports this
360     # package natively.
361     regexp = re.compile(r'(\\use_package\s+%s)' % pkg)
362     p = find_re(document.header, regexp, 0)
363     value = "1" # default is auto
364     if p != -1:
365         value = get_value(document.header, "\\use_package" , p).split()[1]
366         if not supported:
367             del document.header[p]
368     if value == "2" and not supported: # on
369         add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
370     elif value == "1" and not oldauto: # auto
371         i = 0
372         while True:
373             i = find_token(document.body, '\\begin_inset Formula', i)
374             if i == -1:
375                 return
376             j = find_end_of_inset(document.body, i)
377             if j == -1:
378                 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
379                 i += 1
380                 continue
381             code = "\n".join(document.body[i:j])
382             for c in commands:
383                 if code.find("\\%s" % c) != -1:
384                     if supported:
385                         document.header[p] = "\\use_package " + pkg + " 2"
386                     else:
387                         add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
388                     return
389             i = j
390
391
392 mathtools_commands = ["xhookrightarrow", "xhookleftarrow", "xRightarrow", \
393                 "xrightharpoondown", "xrightharpoonup", "xrightleftharpoons", \
394                 "xLeftarrow", "xleftharpoondown", "xleftharpoonup", \
395                 "xleftrightarrow", "xLeftrightarrow", "xleftrightharpoons", \
396                 "xmapsto"]
397
398 def revert_xarrow(document):
399     "remove use_package mathtools"
400     revert_use_package(document, "mathtools", mathtools_commands, False, True)
401
402
403 def revert_beamer_lemma(document):
404     " Reverts beamer lemma layout to ERT "
405
406     beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
407     if document.textclass not in beamer_classes:
408         return
409
410     consecutive = False
411     i = 0
412     while True:
413         i = find_token(document.body, "\\begin_layout Lemma", i)
414         if i == -1:
415             return
416         j = find_end_of_layout(document.body, i)
417         if j == -1:
418             document.warning("Malformed LyX document: Can't find end of Lemma layout")
419             i += 1
420             continue
421         arg1 = find_token(document.body, "\\begin_inset Argument 1", i, j)
422         endarg1 = find_end_of_inset(document.body, arg1)
423         arg2 = find_token(document.body, "\\begin_inset Argument 2", i, j)
424         endarg2 = find_end_of_inset(document.body, arg2)
425         subst1 = []
426         subst2 = []
427         if arg1 != -1:
428             beginPlain1 = find_token(document.body, "\\begin_layout Plain Layout", arg1, endarg1)
429             if beginPlain1 == -1:
430                 document.warning("Malformed LyX document: Can't find arg1 plain Layout")
431                 i += 1
432                 continue
433             endPlain1 = find_end_of_inset(document.body, beginPlain1)
434             content1 = document.body[beginPlain1 + 1 : endPlain1 - 2]
435             subst1 = put_cmd_in_ert("<") + content1 + put_cmd_in_ert(">")
436         if arg2 != -1:
437             beginPlain2 = find_token(document.body, "\\begin_layout Plain Layout", arg2, endarg2)
438             if beginPlain2 == -1:
439                 document.warning("Malformed LyX document: Can't find arg2 plain Layout")
440                 i += 1
441                 continue
442             endPlain2 = find_end_of_inset(document.body, beginPlain2)
443             content2 = document.body[beginPlain2 + 1 : endPlain2 - 2]
444             subst2 = put_cmd_in_ert("[") + content2 + put_cmd_in_ert("]")
445
446         # remove Arg insets
447         if arg1 < arg2:
448             del document.body[arg2 : endarg2 + 1]
449             if arg1 != -1:
450                 del document.body[arg1 : endarg1 + 1]
451         if arg2 < arg1:
452             del document.body[arg1 : endarg1 + 1]
453             if arg2 != -1:
454                 del document.body[arg2 : endarg2 + 1]
455
456         # index of end layout has probably changed
457         j = find_end_of_layout(document.body, i)
458         if j == -1:
459             document.warning("Malformed LyX document: Can't find end of Lemma layout")
460             i += 1
461             continue
462
463         begcmd = []
464
465         # if this is not a consecutive env, add start command
466         if not consecutive:
467             begcmd = put_cmd_in_ert("\\begin{lemma}")
468
469         # has this a consecutive lemma?
470         consecutive = document.body[j + 2] == "\\begin_layout Lemma"
471
472         # if this is not followed by a consecutive env, add end command
473         if not consecutive:
474             document.body[j : j + 1] = put_cmd_in_ert("\\end{lemma}") + ["\\end_layout"]
475
476         document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd + subst1 + subst2
477
478         i = j
479
480
481
482 def revert_question_env(document):
483     """
484     Reverts question and question* environments of
485     theorems-ams-extended-bytype module to ERT
486     """
487
488     # Do we use theorems-ams-extended-bytype module?
489     if not "theorems-ams-extended-bytype" in document.get_module_list():
490         return
491
492     consecutive = False
493     i = 0
494     while True:
495         i = find_token(document.body, "\\begin_layout Question", i)
496         if i == -1:
497             return
498
499         starred = document.body[i] == "\\begin_layout Question*"
500
501         j = find_end_of_layout(document.body, i)
502         if j == -1:
503             document.warning("Malformed LyX document: Can't find end of Question layout")
504             i += 1
505             continue
506
507         # if this is not a consecutive env, add start command
508         begcmd = []
509         if not consecutive:
510             if starred:
511                 begcmd = put_cmd_in_ert("\\begin{question*}")
512             else:
513                 begcmd = put_cmd_in_ert("\\begin{question}")
514
515         # has this a consecutive theorem of same type?
516         consecutive = False
517         if starred:
518             consecutive = document.body[j + 2] == "\\begin_layout Question*"
519         else:
520             consecutive = document.body[j + 2] == "\\begin_layout Question"
521
522         # if this is not followed by a consecutive env, add end command
523         if not consecutive:
524             if starred:
525                 document.body[j : j + 1] = put_cmd_in_ert("\\end{question*}") + ["\\end_layout"]
526             else:
527                 document.body[j : j + 1] = put_cmd_in_ert("\\end{question}") + ["\\end_layout"]
528
529         document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
530
531         add_to_preamble(document, "\\providecommand{\questionname}{Question}")
532
533         if starred:
534             add_to_preamble(document, "\\theoremstyle{plain}\n" \
535                                       "\\newtheorem*{question*}{\\protect\\questionname}")
536         else:
537             add_to_preamble(document, "\\theoremstyle{plain}\n" \
538                                       "\\newtheorem{question}{\\protect\\questionname}")
539
540         i = j
541
542
543 def convert_dashes(document):
544     "convert -- and --- to \\twohyphens and \\threehyphens"
545
546     if document.backend != "latex":
547         return
548
549     i = 0
550     while i < len(document.body):
551         words = document.body[i].split()
552         if len(words) > 1 and words[0] == "\\begin_inset" and \
553            words[1] in ["CommandInset", "ERT", "External", "Formula", "Graphics", "IPA", "listings"]:
554             # must not replace anything in insets that store LaTeX contents in .lyx files
555             # (math and command insets withut overridden read() and write() methods
556             # filtering out IPA makes Text::readParToken() more simple
557             # skip ERT as well since it is not needed there
558             j = find_end_of_inset(document.body, i)
559             if j == -1:
560                 document.warning("Malformed LyX document: Can't find end of " + words[1] + " inset at line " + str(i))
561                 i += 1
562             else:
563                 i = j
564             continue
565         while True:
566             j = document.body[i].find("--")
567             if j == -1:
568                 break
569             front = document.body[i][:j]
570             back = document.body[i][j+2:]
571             # We can have an arbitrary number of consecutive hyphens.
572             # These must be split into the corresponding number of two and three hyphens
573             # We must match what LaTeX does: First try emdash, then endash, then single hyphen
574             if back.find("-") == 0:
575                 back = back[1:]
576                 if len(back) > 0:
577                     document.body.insert(i+1, back)
578                 document.body[i] = front + "\\threehyphens"
579             else:
580                 if len(back) > 0:
581                     document.body.insert(i+1, back)
582                 document.body[i] = front + "\\twohyphens"
583         i += 1
584
585
586 def revert_dashes(document):
587     "convert \\twohyphens and \\threehyphens to -- and ---"
588
589     i = 0
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", "ERT", "External", "Formula", "Graphics", "IPA", "listings"]:
594             # see convert_dashes
595             j = find_end_of_inset(document.body, i)
596             if j == -1:
597                 document.warning("Malformed LyX document: Can't find end of " + words[1] + " inset at line " + str(i))
598                 i += 1
599             else:
600                 i = j
601             continue
602         replaced = False
603         if document.body[i].find("\\twohyphens") >= 0:
604             document.body[i] = document.body[i].replace("\\twohyphens", "--")
605             replaced = True
606         if document.body[i].find("\\threehyphens") >= 0:
607             document.body[i] = document.body[i].replace("\\threehyphens", "---")
608             replaced = True
609         if replaced and i+1 < len(document.body) and \
610            (document.body[i+1].find("\\") != 0 or \
611             document.body[i+1].find("\\twohyphens") == 0 or
612             document.body[i+1].find("\\threehyphens") == 0) and \
613            len(document.body[i]) + len(document.body[i+1]) <= 80:
614             document.body[i] = document.body[i] + document.body[i+1]
615             document.body[i+1:i+2] = []
616         else:
617             i += 1
618
619
620 # order is important for the last three!
621 phrases = ["LyX", "LaTeX2e", "LaTeX", "TeX"]
622
623 def is_part_of_converted_phrase(line, j, phrase):
624     "is phrase part of an already converted phrase?"
625     for p in phrases:
626         converted = "\\SpecialCharNoPassThru \\" + p
627         pos = j + len(phrase) - len(converted)
628         if pos >= 0:
629             if line[pos:pos+len(converted)] == converted:
630                 return True
631     return False
632
633
634 def convert_phrases(document):
635     "convert special phrases from plain text to \\SpecialCharNoPassThru"
636
637     if document.backend != "latex":
638         return
639
640     for phrase in phrases:
641         i = 0
642         while i < len(document.body):
643             words = document.body[i].split()
644             if len(words) > 1 and words[0] == "\\begin_inset" and \
645                words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
646                 # must not replace anything in insets that store LaTeX contents in .lyx files
647                 # (math and command insets withut overridden read() and write() methods
648                 j = find_end_of_inset(document.body, i)
649                 if j == -1:
650                     document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
651                     i += 1
652                 else:
653                     i = j
654                 continue
655             if document.body[i].find("\\") == 0:
656                 i += 1
657                 continue
658             j = document.body[i].find(phrase)
659             if j == -1:
660                 i += 1
661                 continue
662             if not is_part_of_converted_phrase(document.body[i], j, phrase):
663                 front = document.body[i][:j]
664                 back = document.body[i][j+len(phrase):]
665                 if len(back) > 0:
666                     document.body.insert(i+1, back)
667                 # We cannot use SpecialChar since we do not know whether we are outside passThru
668                 document.body[i] = front + "\\SpecialCharNoPassThru \\" + phrase
669             i += 1
670
671
672 def revert_phrases(document):
673     "convert special phrases to plain text"
674
675     i = 0
676     while i < len(document.body):
677         words = document.body[i].split()
678         if len(words) > 1 and words[0] == "\\begin_inset" and \
679            words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
680             # see convert_phrases
681             j = find_end_of_inset(document.body, i)
682             if j == -1:
683                 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
684                 i += 1
685             else:
686                 i = j
687             continue
688         replaced = False
689         for phrase in phrases:
690             # we can replace SpecialChar since LyX ensures that it cannot be inserted into passThru parts
691             if document.body[i].find("\\SpecialChar \\" + phrase) >= 0:
692                 document.body[i] = document.body[i].replace("\\SpecialChar \\" + phrase, phrase)
693                 replaced = True
694             if document.body[i].find("\\SpecialCharNoPassThru \\" + phrase) >= 0:
695                 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru \\" + phrase, phrase)
696                 replaced = True
697         if replaced and i+1 < len(document.body) and \
698            (document.body[i+1].find("\\") != 0 or \
699             document.body[i+1].find("\\SpecialChar") == 0) and \
700            len(document.body[i]) + len(document.body[i+1]) <= 80:
701             document.body[i] = document.body[i] + document.body[i+1]
702             document.body[i+1:i+2] = []
703             i -= 1
704         i += 1
705
706
707 def convert_specialchar_internal(document, forward):
708     specialchars = {"\\-":"softhyphen", "\\textcompwordmark{}":"ligaturebreak", \
709         "\\@.":"endofsentence", "\\ldots{}":"ldots", \
710         "\\menuseparator":"menuseparator", "\\slash{}":"breakableslash", \
711         "\\nobreakdash-":"nobreakdash", "\\LyX":"LyX", \
712         "\\TeX":"TeX", "\\LaTeX2e":"LaTeX2e", \
713         "\\LaTeX":"LaTeX" # must be after LaTeX2e
714     }
715
716     i = 0
717     while i < len(document.body):
718         words = document.body[i].split()
719         if len(words) > 1 and words[0] == "\\begin_inset" and \
720            words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
721             # see convert_phrases
722             j = find_end_of_inset(document.body, i)
723             if j == -1:
724                 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
725                 i += 1
726             else:
727                 i = j
728             continue
729         for key, value in specialchars.iteritems():
730             if forward:
731                 document.body[i] = document.body[i].replace("\\SpecialChar " + key, "\\SpecialChar " + value)
732                 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + key, "\\SpecialCharNoPassThru " + value)
733             else:
734                 document.body[i] = document.body[i].replace("\\SpecialChar " + value, "\\SpecialChar " + key)
735                 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + value, "\\SpecialCharNoPassThru " + key)
736         i += 1
737
738
739 def convert_specialchar(document):
740     "convert special characters to new syntax"
741     convert_specialchar_internal(document, True)
742
743
744 def revert_specialchar(document):
745     "convert special characters to old syntax"
746     convert_specialchar_internal(document, False)
747
748
749 def revert_georgian(document):
750     "Set the document language to English but assure Georgian output"
751
752     if document.language == "georgian":
753         document.language = "english"
754         i = find_token(document.header, "\\language georgian", 0)
755         if i != -1:
756             document.header[i] = "\\language english"
757         j = find_token(document.header, "\\language_package default", 0)
758         if j != -1:
759             document.header[j] = "\\language_package babel"
760         k = find_token(document.header, "\\options", 0)
761         if k != -1:
762             document.header[k] = document.header[k].replace("\\options", "\\options georgian,")
763         else:
764             l = find_token(document.header, "\\use_default_options", 0)
765             document.header.insert(l + 1, "\\options georgian")
766
767
768 def revert_sigplan_doi(document):
769     " Reverts sigplanconf DOI layout to ERT "
770
771     if document.textclass != "sigplanconf":
772         return
773
774     i = 0
775     while True:
776         i = find_token(document.body, "\\begin_layout DOI", i)
777         if i == -1:
778             return
779         j = find_end_of_layout(document.body, i)
780         if j == -1:
781             document.warning("Malformed LyX document: Can't find end of DOI layout")
782             i += 1
783             continue
784
785         content = lyx2latex(document, document.body[i:j + 1])
786         add_to_preamble(document, ["\\doi{" + content + "}"])
787         del document.body[i:j + 1]
788         # no need to reset i
789
790
791 def revert_ex_itemargs(document):
792     " Reverts \\item arguments of the example environments (Linguistics module) to TeX-code "
793
794     if not "linguistics" in document.get_module_list():
795         return
796
797     i = 0
798     example_layouts = ["Numbered Examples (consecutive)", "Subexample"]
799     while True:
800         i = find_token(document.body, "\\begin_inset Argument item:", i)
801         if i == -1:
802             return
803         j = find_end_of_inset(document.body, i)
804         # Find containing paragraph layout
805         parent = get_containing_layout(document.body, i)
806         if parent == False:
807             document.warning("Malformed LyX document: Can't find parent paragraph layout")
808             i += 1
809             continue
810         parbeg = parent[3]
811         layoutname = parent[0]
812         if layoutname in example_layouts:
813             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
814             endPlain = find_end_of_layout(document.body, beginPlain)
815             content = document.body[beginPlain + 1 : endPlain]
816             del document.body[i:j+1]
817             subst = put_cmd_in_ert("[") + content + put_cmd_in_ert("]")
818             document.body[parbeg : parbeg] = subst
819         i += 1
820
821
822 def revert_forest(document):
823     " Reverts the forest environment (Linguistics module) to TeX-code "
824
825     if not "linguistics" in document.get_module_list():
826         return
827
828     i = 0
829     while True:
830         i = find_token(document.body, "\\begin_inset Flex Structure Tree", i)
831         if i == -1:
832             return
833         j = find_end_of_inset(document.body, i)
834         if j == -1:
835             document.warning("Malformed LyX document: Can't find end of Structure Tree inset")
836             i += 1
837             continue
838
839         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
840         endPlain = find_end_of_layout(document.body, beginPlain)
841         content = lyx2latex(document, document.body[beginPlain : endPlain])
842
843         add_to_preamble(document, ["\\usepackage{forest}"])
844
845         document.body[i:j + 1] = put_cmd_in_ert("\\begin{forest}" + content + "\\end{forest}")
846         # no need to reset i
847
848
849 def revert_glossgroup(document):
850     " Reverts the GroupGlossedWords inset (Linguistics module) to TeX-code "
851
852     if not "linguistics" in document.get_module_list():
853         return
854
855     i = 0
856     while True:
857         i = find_token(document.body, "\\begin_inset Flex GroupGlossedWords", i)
858         if i == -1:
859             return
860         j = find_end_of_inset(document.body, i)
861         if j == -1:
862             document.warning("Malformed LyX document: Can't find end of GroupGlossedWords inset")
863             i += 1
864             continue
865
866         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
867         endPlain = find_end_of_layout(document.body, beginPlain)
868         content = lyx2verbatim(document, document.body[beginPlain : endPlain])
869
870         document.body[i:j + 1] = ["{", "", content, "", "}"]
871         # no need to reset i
872
873
874 def revert_newgloss(document):
875     " Reverts the new Glosse insets (Linguistics module) to the old format "
876
877     if not "linguistics" in document.get_module_list():
878         return
879
880     glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
881     for glosse in glosses:
882         i = 0
883         while True:
884             i = find_token(document.body, glosse, i)
885             if i == -1:
886                 break
887             j = find_end_of_inset(document.body, i)
888             if j == -1:
889                 document.warning("Malformed LyX document: Can't find end of Glosse inset")
890                 i += 1
891                 continue
892
893             arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
894             endarg = find_end_of_inset(document.body, arg)
895             argcontent = ""
896             if arg != -1:
897                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
898                 if argbeginPlain == -1:
899                     document.warning("Malformed LyX document: Can't find arg plain Layout")
900                     i += 1
901                     continue
902                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
903                 argcontent = lyx2verbatim(document, document.body[argbeginPlain : argendPlain - 2])
904
905                 document.body[j:j] = ["", "\\begin_layout Plain Layout","\\backslash", "glt ",
906                     argcontent, "\\end_layout"]
907
908                 # remove Arg insets and paragraph, if it only contains this inset
909                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
910                     del document.body[arg - 1 : endarg + 4]
911                 else:
912                     del document.body[arg : endarg + 1]
913
914             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
915             endPlain = find_end_of_layout(document.body, beginPlain)
916             content = lyx2verbatim(document, document.body[beginPlain : endPlain])
917
918             document.body[beginPlain + 1:endPlain] = [content]
919             i = beginPlain + 1
920
921     # Dissolve ERT insets
922     for glosse in glosses:
923         i = 0
924         while True:
925             i = find_token(document.body, "\\begin_inset ERT", i)
926             if i == -1:
927                 return
928             j = find_end_of_inset(document.body, i)
929             if j == -1:
930                 document.warning("Malformed LyX document: Can't find end of ERT inset")
931                 i += 1
932                 continue
933             ert = get_ert(document.body, i, True)
934             document.body[i:j+1] = [ert]
935             i = i + 1
936
937
938 def convert_newgloss(document):
939     " Converts Glosse insets (Linguistics module) to the new format "
940
941     if not "linguistics" in document.get_module_list():
942         return
943
944     glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
945     for glosse in glosses:
946         i = 0
947         while True:
948             i = find_token(document.body, glosse, i)
949             if i == -1:
950                 break
951             j = find_end_of_inset(document.body, i)
952             if j == -1:
953                 document.warning("Malformed LyX document: Can't find end of Glosse inset")
954                 i += 1
955                 continue
956
957             k = i
958             while True:
959                 argcontent = []
960                 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", k, j)
961                 if beginPlain == -1:
962                     break
963                 endPlain = find_end_of_layout(document.body, beginPlain)
964                 if endPlain == -1:
965                     document.warning("Malformed LyX document: Can't find end of Glosse layout")
966                     i += 1
967                     continue
968
969                 glt  = find_token(document.body, "\\backslash", beginPlain, endPlain)
970                 if glt != -1 and document.body[glt + 1].startswith("glt"):
971                     document.body[glt + 1] = document.body[glt + 1].lstrip("glt").lstrip()
972                     argcontent = document.body[glt + 1 : endPlain]
973                     document.body[beginPlain + 1 : endPlain] = ["\\begin_inset Argument 1", "status open", "",
974                         "\\begin_layout Plain Layout", "\\begin_inset ERT", "status open", "",
975                         "\\begin_layout Plain Layout", ""] + argcontent + ["\\end_layout", "", "\\end_inset", "",
976                         "\\end_layout", "", "\\end_inset"]
977                 else:
978                     content = document.body[beginPlain + 1 : endPlain]
979                     document.body[beginPlain + 1 : endPlain] = ["\\begin_inset ERT", "status open", "",
980                         "\\begin_layout Plain Layout"] + content + ["\\end_layout", "", "\\end_inset"]
981
982                 endPlain = find_end_of_layout(document.body, beginPlain)
983                 k = endPlain
984                 j = find_end_of_inset(document.body, i)
985
986             i = endPlain + 1
987
988
989 def convert_BoxFeatures(document):
990     " adds new box features "
991
992     i = 0
993     while True:
994         i = find_token(document.body, "height_special", i)
995         if i == -1:
996             return
997         document.body[i+1:i+1] = ['thickness "0.4pt"', 'separation "3pt"', 'shadowsize "4pt"']
998         i = i + 4
999
1000
1001 def revert_BoxFeatures(document):
1002     " outputs new box features as TeX code "
1003
1004     i = 0
1005     defaultSep = "3pt"
1006     defaultThick = "0.4pt"
1007     defaultShadow = "4pt"
1008     while True:
1009         i = find_token(document.body, "height_special", i)
1010         if i == -1:
1011             return
1012         # read out the values
1013         beg = document.body[i+1].find('"');
1014         end = document.body[i+1].rfind('"');
1015         thickness = document.body[i+1][beg+1:end];
1016         beg = document.body[i+2].find('"');
1017         end = document.body[i+2].rfind('"');
1018         separation = document.body[i+2][beg+1:end];
1019         beg = document.body[i+3].find('"');
1020         end = document.body[i+3].rfind('"');
1021         shadowsize = document.body[i+3][beg+1:end];
1022         # delete the specification
1023         del document.body[i+1:i+4]
1024         # output ERT
1025         # first output the closing brace
1026         if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1027             document.body[i + 10 : i + 10] = put_cmd_in_ert("}")
1028         # now output the lengths
1029         if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1030             document.body[i - 10 : i - 10] = put_cmd_in_ert("{")
1031         if thickness != defaultThick:
1032             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness]
1033         if separation != defaultSep and thickness == defaultThick:
1034             document.body[i - 5 : i - 4] = ["{\\backslash fboxsep " + separation]
1035         if separation != defaultSep and thickness != defaultThick:
1036             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation]
1037         if shadowsize != defaultShadow and separation == defaultSep and thickness == defaultThick:
1038             document.body[i - 5 : i - 4] = ["{\\backslash shadowsize " + shadowsize]
1039         if shadowsize != defaultShadow and separation != defaultSep and thickness == defaultThick:
1040             document.body[i - 5 : i - 4] = ["{\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1041         if shadowsize != defaultShadow and separation == defaultSep and thickness != defaultThick:
1042             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash shadowsize " + shadowsize]
1043         if shadowsize != defaultShadow and separation != defaultSep and thickness != defaultThick:
1044             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1045         i = i + 11
1046
1047
1048 def convert_origin(document):
1049     " Insert the origin tag "
1050
1051     i = find_token(document.header, "\\textclass ", 0)
1052     if i == -1:
1053         document.warning("Malformed LyX document: No \\textclass!!")
1054         return
1055     if document.dir == "":
1056         origin = "stdin"
1057     else:
1058         relpath = ''
1059         if document.systemlyxdir and document.systemlyxdir != '':
1060             try:
1061                 if os.path.isabs(document.dir):
1062                     absdir = os.path.normpath(document.dir)
1063                 else:
1064                     absdir = os.path.normpath(os.path.abspath(document.dir))
1065                 if os.path.isabs(document.systemlyxdir):
1066                     abssys = os.path.normpath(document.systemlyxdir)
1067                 else:
1068                     abssys = os.path.normpath(os.path.abspath(document.systemlyxdir))
1069                 relpath = os.path.relpath(absdir, abssys)
1070                 if relpath.find('..') == 0:
1071                     relpath = ''
1072             except:
1073                 relpath = ''
1074         if relpath == '':
1075             origin = document.dir.replace('\\', '/') + '/'
1076         else:
1077             origin = os.path.join("/systemlyxdir", relpath).replace('\\', '/') + '/'
1078         if os.name != 'nt':
1079             origin = unicode(origin, sys.getfilesystemencoding())
1080     document.header[i:i] = ["\\origin " + origin]
1081
1082
1083 def revert_origin(document):
1084     " Remove the origin tag "
1085
1086     i = find_token(document.header, "\\origin ", 0)
1087     if i == -1:
1088         document.warning("Malformed LyX document: No \\origin!!")
1089         return
1090     del document.header[i]
1091
1092
1093 color_names = ["brown", "darkgray", "gray", \
1094                "lightgray", "lime", "olive", "orange", \
1095                "pink", "purple", "teal", "violet"]
1096
1097 def revert_textcolor(document):
1098     " revert new \\textcolor colors to TeX code "
1099
1100     i = 0
1101     j = 0
1102     xcolor = False
1103     while True:
1104         i = find_token(document.body, "\\color ", i)
1105         if i == -1:
1106             return
1107         else:
1108             for color in list(color_names):
1109                 if document.body[i] == "\\color " + color:
1110                     # register that xcolor must be loaded in the preamble
1111                     if xcolor == False:
1112                         xcolor = True
1113                         add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\usepackage{xcolor}}{}"])
1114                     # find the next \\color and/or the next \\end_layout
1115                     j = find_token(document.body, "\\color", i + 1)
1116                     k = find_token(document.body, "\\end_layout", i + 1)
1117                     if j == -1 and k != -1:
1118                         j = k +1 
1119                     # output TeX code
1120                     # first output the closing brace
1121                     if k < j:
1122                         document.body[k: k] = put_cmd_in_ert("}")
1123                     else:
1124                         document.body[j: j] = put_cmd_in_ert("}")
1125                     # now output the \textcolor command
1126                     document.body[i : i + 1] = put_cmd_in_ert("\\textcolor{" + color + "}{")
1127         i = i + 1
1128
1129
1130 def convert_colorbox(document):
1131     " adds color settings for boxes "
1132
1133     i = 0
1134     while True:
1135         i = find_token(document.body, "shadowsize", i)
1136         if i == -1:
1137             return
1138         document.body[i+1:i+1] = ['framecolor "black"', 'backgroundcolor "none"']
1139         i = i + 3
1140
1141
1142 def revert_colorbox(document):
1143     " outputs color settings for boxes as TeX code "
1144
1145     binset = 0
1146     defaultframecolor = "black"
1147     defaultbackcolor = "none"
1148     while True:
1149         binset = find_token(document.body, "\\begin_inset Box", binset)
1150         if binset == -1:
1151             return
1152
1153         einset = find_end_of_inset(document.body, binset)
1154         if einset == -1:
1155             document.warning("Malformed LyX document: Can't find end of box inset!")
1156             binset += 1
1157             continue
1158
1159         blay = find_token(document.body, "\\begin_layout", binset, einset)
1160         if blay == -1:
1161             document.warning("Malformed LyX document: Can't find start of layout!")
1162             binset = einset
1163             continue
1164
1165         # doing it this way, we make sure only to find a framecolor option
1166         frame = find_token(document.body, "framecolor", binset, blay)
1167         if frame == -1:
1168             binset = einset
1169             continue
1170
1171         beg = document.body[frame].find('"')
1172         end = document.body[frame].rfind('"')
1173         framecolor = document.body[frame][beg + 1 : end]
1174
1175         # this should be on the next line
1176         bgcolor = frame + 1
1177         beg = document.body[bgcolor].find('"')
1178         end = document.body[bgcolor].rfind('"')
1179         backcolor = document.body[bgcolor][beg + 1 : end]
1180
1181         # delete those bits
1182         del document.body[frame : frame + 2]
1183         # adjust end of inset
1184         einset -= 2
1185
1186         if document.body[binset] == "\\begin_inset Box Boxed" and \
1187             framecolor != defaultframecolor:
1188           document.body[binset] = "\\begin_inset Box Frameless"
1189
1190         # output TeX code
1191         # first output the closing brace
1192         if framecolor == defaultframecolor and backcolor == defaultbackcolor:
1193             # nothing needed
1194             pass
1195         else:
1196             # we also neeed to load xcolor in the preamble but only once
1197             add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\usepackage{xcolor}}{}"])
1198             document.body[einset + 1 : einset + 1] = put_cmd_in_ert("}")
1199             if framecolor != defaultframecolor:
1200                 document.body[binset:binset] = put_cmd_in_ert("\\fcolorbox{" + framecolor + "}{" + backcolor + "}{")
1201             else:
1202               document.body[binset:binset] = put_cmd_in_ert("\\colorbox{" + backcolor + "}{")
1203
1204         binset = einset
1205
1206
1207 def revert_mathmulticol(document):
1208     " Convert formulas to ERT if they contain multicolumns "
1209
1210     i = 0
1211     while True:
1212         i = find_token(document.body, '\\begin_inset Formula', i)
1213         if i == -1:
1214             return
1215         j = find_end_of_inset(document.body, i)
1216         if j == -1:
1217             document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
1218             i += 1
1219             continue
1220         lines = document.body[i:j]
1221         lines[0] = lines[0].replace('\\begin_inset Formula', '').lstrip()
1222         code = "\n".join(lines)
1223         converted = False
1224         k = 0
1225         n = 0
1226         while n >= 0:
1227             n = code.find("\\multicolumn", k)
1228             # no need to convert degenerated multicolumn cells,
1229             # they work in old LyX versions as "math ERT"
1230             if n != -1 and code.find("\\multicolumn{1}", k) != n:
1231                 ert = put_cmd_in_ert(code)
1232                 document.body[i:j+1] = ert
1233                 converted = True
1234                 break
1235             else:
1236                 k = n + 12
1237         if converted:
1238             i = find_end_of_inset(document.body, i)
1239         else:
1240             i = j
1241
1242
1243 def revert_jss(document):
1244     " Reverts JSS In_Preamble commands to ERT in preamble "
1245
1246     if document.textclass != "jss":
1247         return
1248
1249     h = 0
1250     m = 0
1251     j = 0
1252     k = 0
1253     n = 0
1254     while True:
1255       # at first revert the inset layouts because they can be part of the In_Preamble layouts
1256       while m != -1 or j != -1 or h != -1 or k != -1 or n != -1:
1257         # \pkg
1258         if h != -1:
1259           h = find_token(document.body, "\\begin_inset Flex Pkg", h)
1260         if h != -1:
1261           endh = find_end_of_inset(document.body, h)
1262           document.body[endh - 2 : endh + 1] = put_cmd_in_ert("}")
1263           document.body[h : h + 4] = put_cmd_in_ert("\\pkg{")
1264           h = h + 5
1265         # \proglang
1266         if m != -1:
1267           m = find_token(document.body, "\\begin_inset Flex Proglang", m)
1268         if m != -1:
1269           endm = find_end_of_inset(document.body, m)
1270           document.body[endm - 2 : endm + 1] = put_cmd_in_ert("}")
1271           document.body[m : m + 4] = put_cmd_in_ert("\\proglang{")
1272           m = m + 5
1273         # \code
1274         if j != -1:
1275           j = find_token(document.body, "\\begin_inset Flex Code", j)
1276         if j != -1:
1277           # assure that we are not in a Code Chunk inset
1278           if document.body[j][-1] == "e":
1279               endj = find_end_of_inset(document.body, j)
1280               document.body[endj - 2 : endj + 1] = put_cmd_in_ert("}")
1281               document.body[j : j + 4] = put_cmd_in_ert("\\code{")
1282               j = j + 5
1283           else:
1284               j = j + 1
1285         # \email
1286         if k != -1:
1287           k = find_token(document.body, "\\begin_inset Flex E-mail", k)
1288         if k != -1:
1289           endk = find_end_of_inset(document.body, k)
1290           document.body[endk - 2 : endk + 1] = put_cmd_in_ert("}")
1291           document.body[k : k + 4] = put_cmd_in_ert("\\email{")
1292           k = k + 5
1293         # \url
1294         if n != -1:
1295           n = find_token(document.body, "\\begin_inset Flex URL", n)
1296         if n != -1:
1297           endn = find_end_of_inset(document.body, n)
1298           document.body[endn - 2 : endn + 1] = put_cmd_in_ert("}")
1299           document.body[n : n + 4] = put_cmd_in_ert("\\url{")
1300           n = n + 5
1301       # now revert the In_Preamble layouts
1302       # \title
1303       i = find_token(document.body, "\\begin_layout Title", 0)
1304       if i == -1:
1305         return
1306       j = find_end_of_layout(document.body, i)
1307       if j == -1:
1308         document.warning("Malformed LyX document: Can't find end of Title layout")
1309         i += 1
1310         continue
1311       content = lyx2latex(document, document.body[i:j + 1])
1312       add_to_preamble(document, ["\\title{" + content + "}"])
1313       del document.body[i:j + 1]
1314       # \author
1315       i = find_token(document.body, "\\begin_layout Author", 0)
1316       if i == -1:
1317         return
1318       j = find_end_of_layout(document.body, i)
1319       if j == -1:
1320         document.warning("Malformed LyX document: Can't find end of Author layout")
1321         i += 1
1322         continue
1323       content = lyx2latex(document, document.body[i:j + 1])
1324       add_to_preamble(document, ["\\author{" + content + "}"])
1325       del document.body[i:j + 1]
1326       # \Plainauthor
1327       i = find_token(document.body, "\\begin_layout Plain Author", 0)
1328       if i == -1:
1329         return
1330       j = find_end_of_layout(document.body, i)
1331       if j == -1:
1332         document.warning("Malformed LyX document: Can't find end of Plain Author layout")
1333         i += 1
1334         continue
1335       content = lyx2latex(document, document.body[i:j + 1])
1336       add_to_preamble(document, ["\\Plainauthor{" + content + "}"])
1337       del document.body[i:j + 1]
1338       # \Plaintitle
1339       i = find_token(document.body, "\\begin_layout Plain Title", 0)
1340       if i == -1:
1341         return
1342       j = find_end_of_layout(document.body, i)
1343       if j == -1:
1344         document.warning("Malformed LyX document: Can't find end of Plain Title layout")
1345         i += 1
1346         continue
1347       content = lyx2latex(document, document.body[i:j + 1])
1348       add_to_preamble(document, ["\\Plaintitle{" + content + "}"])
1349       del document.body[i:j + 1]
1350       # \Shorttitle
1351       i = find_token(document.body, "\\begin_layout Short Title", 0)
1352       if i == -1:
1353         return
1354       j = find_end_of_layout(document.body, i)
1355       if j == -1:
1356         document.warning("Malformed LyX document: Can't find end of Short Title layout")
1357         i += 1
1358         continue
1359       content = lyx2latex(document, document.body[i:j + 1])
1360       add_to_preamble(document, ["\\Shorttitle{" + content + "}"])
1361       del document.body[i:j + 1]
1362       # \Abstract
1363       i = find_token(document.body, "\\begin_layout Abstract", 0)
1364       if i == -1:
1365         return
1366       j = find_end_of_layout(document.body, i)
1367       if j == -1:
1368         document.warning("Malformed LyX document: Can't find end of Abstract layout")
1369         i += 1
1370         continue
1371       content = lyx2latex(document, document.body[i:j + 1])
1372       add_to_preamble(document, ["\\Abstract{" + content + "}"])
1373       del document.body[i:j + 1]
1374       # \Keywords
1375       i = find_token(document.body, "\\begin_layout Keywords", 0)
1376       if i == -1:
1377         return
1378       j = find_end_of_layout(document.body, i)
1379       if j == -1:
1380         document.warning("Malformed LyX document: Can't find end of Keywords layout")
1381         i += 1
1382         continue
1383       content = lyx2latex(document, document.body[i:j + 1])
1384       add_to_preamble(document, ["\\Keywords{" + content + "}"])
1385       del document.body[i:j + 1]
1386       # \Plainkeywords
1387       i = find_token(document.body, "\\begin_layout Plain Keywords", 0)
1388       if i == -1:
1389         return
1390       j = find_end_of_layout(document.body, i)
1391       if j == -1:
1392         document.warning("Malformed LyX document: Can't find end of Plain Keywords layout")
1393         i += 1
1394         continue
1395       content = lyx2latex(document, document.body[i:j + 1])
1396       add_to_preamble(document, ["\\Plainkeywords{" + content + "}"])
1397       del document.body[i:j + 1]
1398       # \Address
1399       i = find_token(document.body, "\\begin_layout Address", 0)
1400       if i == -1:
1401         return
1402       j = find_end_of_layout(document.body, i)
1403       if j == -1:
1404         document.warning("Malformed LyX document: Can't find end of Address layout")
1405         i += 1
1406         continue
1407       content = lyx2latex(document, document.body[i:j + 1])
1408       add_to_preamble(document, ["\\Address{" + content + "}"])
1409       del document.body[i:j + 1]
1410       # finally handle the code layouts
1411       h = 0
1412       m = 0
1413       j = 0
1414       k = 0
1415       while m != -1 or j != -1 or h != -1 or k != -1:
1416         # \CodeChunk
1417         if h != -1:
1418           h = find_token(document.body, "\\begin_inset Flex Code Chunk", h)
1419         if h != -1:
1420           endh = find_end_of_inset(document.body, h)
1421           document.body[endh + 1 : endh] = ["\\end_layout"]
1422           document.body[endh : endh + 1] = put_cmd_in_ert("\\end{CodeChunk}")
1423           document.body[h : h + 3] = put_cmd_in_ert("\\begin{CodeChunk}")
1424           document.body[h - 1 : h] = ["\\begin_layout Standard"]
1425           h = h + 1
1426         # \CodeInput
1427         if j != -1:
1428           j = find_token(document.body, "\\begin_layout Code Input", j)
1429         if j != -1:
1430           endj = find_end_of_layout(document.body, j)
1431           document.body[endj : endj + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1432           document.body[endj + 3 : endj + 4] = put_cmd_in_ert("\\end{CodeInput}")
1433           document.body[endj + 13 : endj + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1434           document.body[j + 1 : j] = ["\\end_layout", "", "\\begin_layout Standard"]
1435           document.body[j : j + 1] = put_cmd_in_ert("\\begin{CodeInput}")
1436           j = j + 1
1437         # \CodeOutput
1438         if k != -1:
1439           k = find_token(document.body, "\\begin_layout Code Output", k)
1440         if k != -1:
1441           endk = find_end_of_layout(document.body, k)
1442           document.body[endk : endk + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1443           document.body[endk + 3 : endk + 4] = put_cmd_in_ert("\\end{CodeOutput}")
1444           document.body[endk + 13 : endk + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1445           document.body[k + 1 : k] = ["\\end_layout", "", "\\begin_layout Standard"]
1446           document.body[k : k + 1] = put_cmd_in_ert("\\begin{CodeOutput}")
1447           k = k + 1
1448         # \Code
1449         if m != -1:
1450           m = find_token(document.body, "\\begin_layout Code", m)
1451         if m != -1:
1452           endm = find_end_of_layout(document.body, m)
1453           document.body[endm : endm + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1454           document.body[endm + 3 : endm + 4] = put_cmd_in_ert("\\end{Code}")
1455           document.body[endm + 13 : endm + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1456           document.body[m + 1 : m] = ["\\end_layout", "", "\\begin_layout Standard"]
1457           document.body[m : m + 1] = put_cmd_in_ert("\\begin{Code}")
1458           m = m + 1
1459
1460
1461 def convert_subref(document):
1462     " converts sub: ref prefixes to subref: "
1463
1464     # 1) label insets
1465     rx = re.compile(r'^name \"sub:(.+)$')
1466     i = 0
1467     while True:
1468         i = find_token(document.body, "\\begin_inset CommandInset label", i)
1469         if i == -1:
1470             break
1471         j = find_end_of_inset(document.body, i)
1472         if j == -1:
1473             document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1474             i += 1
1475             continue
1476
1477         for p in range(i, j):
1478             m = rx.match(document.body[p])
1479             if m:
1480                 label = m.group(1)
1481                 document.body[p] = "name \"subsec:" + label
1482         i += 1
1483
1484     # 2) xref insets
1485     rx = re.compile(r'^reference \"sub:(.+)$')
1486     i = 0
1487     while True:
1488         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1489         if i == -1:
1490             return
1491         j = find_end_of_inset(document.body, i)
1492         if j == -1:
1493             document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1494             i += 1
1495             continue
1496
1497         for p in range(i, j):
1498             m = rx.match(document.body[p])
1499             if m:
1500                 label = m.group(1)
1501                 document.body[p] = "reference \"subsec:" + label
1502                 break
1503         i += 1
1504
1505
1506
1507 def revert_subref(document):
1508     " reverts subref: ref prefixes to sub: "
1509
1510     # 1) label insets
1511     rx = re.compile(r'^name \"subsec:(.+)$')
1512     i = 0
1513     while True:
1514         i = find_token(document.body, "\\begin_inset CommandInset label", i)
1515         if i == -1:
1516             break
1517         j = find_end_of_inset(document.body, i)
1518         if j == -1:
1519             document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1520             i += 1
1521             continue
1522
1523         for p in range(i, j):
1524             m = rx.match(document.body[p])
1525             if m:
1526                 label = m.group(1)
1527                 document.body[p] = "name \"sub:" + label
1528                 break
1529         i += 1
1530
1531     # 2) xref insets
1532     rx = re.compile(r'^reference \"subsec:(.+)$')
1533     i = 0
1534     while True:
1535         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1536         if i == -1:
1537             return
1538         j = find_end_of_inset(document.body, i)
1539         if j == -1:
1540             document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1541             i += 1
1542             continue
1543
1544         for p in range(i, j):
1545             m = rx.match(document.body[p])
1546             if m:
1547                 label = m.group(1)
1548                 document.body[p] = "reference \"sub:" + label
1549                 break
1550         i += 1
1551
1552
1553 def convert_nounzip(document):
1554     " remove the noUnzip parameter of graphics insets "
1555
1556     rx = re.compile(r'\s*noUnzip\s*$')
1557     i = 0
1558     while True:
1559         i = find_token(document.body, "\\begin_inset Graphics", i)
1560         if i == -1:
1561             break
1562         j = find_end_of_inset(document.body, i)
1563         if j == -1:
1564             document.warning("Malformed LyX document: Can't find end of graphics inset at line " + str(i))
1565             i += 1
1566             continue
1567
1568         k = find_re(document.body, rx, i, j)
1569         if k != -1:
1570           del document.body[k]
1571           j = j - 1
1572         i = j + 1
1573
1574
1575 def convert_revert_external_bbox(document, forward):
1576     " add units to bounding box of external insets "
1577
1578     rx = re.compile(r'^\s*boundingBox\s+\S+\s+\S+\s+\S+\s+\S+\s*$')
1579     i = 0
1580     while True:
1581         i = find_token(document.body, "\\begin_inset External", i)
1582         if i == -1:
1583             break
1584         j = find_end_of_inset(document.body, i)
1585         if j == -1:
1586             document.warning("Malformed LyX document: Can't find end of external inset at line " + str(i))
1587             i += 1
1588             continue
1589         k = find_re(document.body, rx, i, j)
1590         if k == -1:
1591             i = j + 1
1592             continue
1593         tokens = document.body[k].split()
1594         if forward:
1595             for t in range(1, 5):
1596                 tokens[t] += "bp"
1597         else:
1598             for t in range(1, 5):
1599                 tokens[t] = length_in_bp(tokens[t])
1600         document.body[k] = "\tboundingBox " + tokens[1] + " " + tokens[2] + " " + \
1601                            tokens[3] + " " + tokens[4]
1602         i = j + 1
1603
1604
1605 def convert_external_bbox(document):
1606     convert_revert_external_bbox(document, True)
1607
1608
1609 def revert_external_bbox(document):
1610     convert_revert_external_bbox(document, False)
1611
1612
1613 def revert_tcolorbox_1(document):
1614   " Reverts the Flex:Subtitle inset of tcolorbox to TeX-code "
1615   i = -1
1616   while True:
1617     i = find_token(document.header, "tcolorbox", i)
1618     if i == -1:
1619       break
1620     else:    
1621       flex = 0
1622       flexEnd = -1
1623       flex = find_token(document.body, "\\begin_inset Flex Subtitle", flex)
1624       if flex == -1:
1625         return flexEnd
1626       flexEnd = find_end_of_inset(document.body, flex)
1627       wasOpt = revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, False, True, False)
1628       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, False, False, False)
1629       flexEnd = find_end_of_inset(document.body, flex)
1630       if wasOpt == True:
1631         document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\tcbsubtitle")
1632       else:
1633         document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\tcbsubtitle{")
1634       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("}")
1635       flex += 1
1636
1637
1638 def revert_tcolorbox_2(document):
1639   " Reverts the Flex:Raster_Color_Box inset of tcolorbox to TeX-code "
1640   i = -1
1641   while True:
1642     i = find_token(document.header, "tcolorbox", i)
1643     if i == -1:
1644       break
1645     else:    
1646       flex = 0
1647       flexEnd = -1
1648       flex = find_token(document.body, "\\begin_inset Flex Raster Color Box", flex)
1649       if flex == -1:
1650         return flexEnd
1651       flexEnd = find_end_of_inset(document.body, flex)
1652       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1653       flexEnd = find_end_of_inset(document.body, flex)
1654       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{tcbraster}")
1655       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("\\end{tcbraster}")
1656       flex += 1
1657
1658
1659 def revert_tcolorbox_3(document):
1660   " Reverts the Flex:Custom_Color_Box_1 inset of tcolorbox to TeX-code "
1661   i = -1
1662   while True:
1663     i = find_token(document.header, "tcolorbox", i)
1664     if i == -1:
1665       break
1666     else:    
1667       flex = 0
1668       flexEnd = -1
1669       flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 1", flex)
1670       if flex == -1:
1671         return flexEnd
1672       flexEnd = find_end_of_inset(document.body, flex)
1673       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1674       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1675       flexEnd = find_end_of_inset(document.body, flex)
1676       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxA}")
1677       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxA}")
1678       flex += 1
1679
1680
1681 def revert_tcolorbox_4(document):
1682   " Reverts the Flex:Custom_Color_Box_2 inset of tcolorbox to TeX-code "
1683   i = -1
1684   while True:
1685     i = find_token(document.header, "tcolorbox", i)
1686     if i == -1:
1687       break
1688     else:    
1689       flex = 0
1690       flexEnd = -1
1691       flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 2", flex)
1692       if flex == -1:
1693         return flexEnd
1694       flexEnd = find_end_of_inset(document.body, flex)
1695       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1696       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1697       flexEnd = find_end_of_inset(document.body, flex)
1698       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxB}")
1699       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxB}")
1700       flex += 1
1701
1702
1703 def revert_tcolorbox_5(document):
1704   " Reverts the Flex:Custom_Color_Box_3 inset of tcolorbox to TeX-code "
1705   i = -1
1706   while True:
1707     i = find_token(document.header, "tcolorbox", i)
1708     if i == -1:
1709       break
1710     else:    
1711       flex = 0
1712       flexEnd = -1
1713       flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 3", flex)
1714       if flex == -1:
1715         return flexEnd
1716       flexEnd = find_end_of_inset(document.body, flex)
1717       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1718       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1719       flexEnd = find_end_of_inset(document.body, flex)
1720       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxC}")
1721       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxC}")
1722       flex += 1
1723
1724
1725 def revert_tcolorbox_6(document):
1726   " Reverts the Flex:Custom_Color_Box_4 inset of tcolorbox to TeX-code "
1727   i = -1
1728   while True:
1729     i = find_token(document.header, "tcolorbox", i)
1730     if i == -1:
1731       break
1732     else:    
1733       flex = 0
1734       flexEnd = -1
1735       flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 4", flex)
1736       if flex == -1:
1737         return flexEnd
1738       flexEnd = find_end_of_inset(document.body, flex)
1739       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1740       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1741       flexEnd = find_end_of_inset(document.body, flex)
1742       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxD}")
1743       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxD}")
1744       flex += 1
1745
1746
1747 def revert_tcolorbox_7(document):
1748   " Reverts the Flex:Custom_Color_Box_5 inset of tcolorbox to TeX-code "
1749   i = -1
1750   while True:
1751     i = find_token(document.header, "tcolorbox", i)
1752     if i == -1:
1753       break
1754     else:    
1755       flex = 0
1756       flexEnd = -1
1757       flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 5", flex)
1758       if flex == -1:
1759         return flexEnd
1760       flexEnd = find_end_of_inset(document.body, flex)
1761       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1762       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1763       flexEnd = find_end_of_inset(document.body, flex)
1764       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxE}")
1765       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxE}")
1766       flex += 1
1767
1768
1769 def revert_tcolorbox_8(document):
1770   " Reverts the layout New Color Box Type of tcolorbox to TeX-code "
1771   i = 0
1772   j = 0
1773   k = 0
1774   while True:
1775     if i != -1:
1776       i = find_token(document.body, "\\begin_layout New Color Box Type", i)
1777     if i != -1:
1778       j = find_end_of_layout(document.body, i)
1779       wasOpt = revert_Argument_to_TeX_brace(document, i, j, 1, 1, False, True, False)
1780       revert_Argument_to_TeX_brace(document, i, 0, 2, 2, False, False, True)
1781       revert_Argument_to_TeX_brace(document, i, 0, 3, 4, False, True, False)
1782       document.body[i] = document.body[i].replace("\\begin_layout New Color Box Type", "\\begin_layout Standard")
1783       if wasOpt == True:
1784         document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox")
1785       else:
1786         document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox{")
1787       k = find_end_of_inset(document.body, j)
1788       k = find_token(document.body, "\\end_inset", k + 1)
1789       k = find_token(document.body, "\\end_inset", k + 1)
1790       if wasOpt == True:
1791         k = find_token(document.body, "\\end_inset", k + 1)
1792       document.body[k + 2 : j + 2] = put_cmd_in_ert("{")
1793       j = find_token(document.body, "\\begin_layout Standard", j + 1)
1794       document.body[j - 2 : j - 2] = put_cmd_in_ert("}")
1795       i += 1
1796     if i == -1:
1797       return
1798
1799
1800 def revert_moderncv_1(document):
1801   " Reverts the new inset of moderncv to TeX-code in preamble "
1802   
1803   if document.textclass != "moderncv":
1804     return
1805   i = 0
1806   j = 0
1807   lineArg = 0
1808   while True:
1809     # at first revert the new styles
1810     # \moderncvicons
1811     i = find_token(document.body, "\\begin_layout CVIcons", 0)
1812     if i == -1:
1813       return
1814     j = find_end_of_layout(document.body, i)
1815     if j == -1:
1816       document.warning("Malformed LyX document: Can't find end of CVIcons layout")
1817       i += 1
1818       continue
1819     content = lyx2latex(document, document.body[i:j + 1])
1820     add_to_preamble(document, ["\\moderncvicons{" + content + "}"])
1821     del document.body[i:j + 1]
1822     # \hintscolumnwidth
1823     i = find_token(document.body, "\\begin_layout CVColumnWidth", 0)
1824     if i == -1:
1825       return
1826     j = find_end_of_layout(document.body, i)
1827     if j == -1:
1828       document.warning("Malformed LyX document: Can't find end of CVColumnWidth layout")
1829       i += 1
1830       continue
1831     content = lyx2latex(document, document.body[i:j + 1])
1832     add_to_preamble(document, ["\\setlength{\hintscolumnwidth}{" + content + "}"])
1833     del document.body[i:j + 1]
1834     # now change the new styles to the obsolete ones
1835     # \name
1836     i = find_token(document.body, "\\begin_layout Name", 0)
1837     if i == -1:
1838       return
1839     j = find_end_of_layout(document.body, i)
1840     if j == -1:
1841       document.warning("Malformed LyX document: Can't find end of Name layout")
1842       i += 1
1843       continue
1844     lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
1845     if lineArg > j and j != 0:
1846       return
1847     if lineArg != -1:
1848       beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
1849       # we have to assure that no other inset is in the Argument
1850       beginInset = find_token(document.body, "\\begin_inset", beginPlain)
1851       endInset = find_token(document.body, "\\end_inset", beginPlain)
1852       k = beginPlain + 1
1853       l = k
1854       while beginInset < endInset and beginInset != -1:
1855         beginInset = find_token(document.body, "\\begin_inset", k)
1856         endInset = find_token(document.body, "\\end_inset", l)
1857         k = beginInset + 1
1858         l = endInset + 1
1859       Arg2 = document.body[l + 5 : l + 6]
1860       # rename the style
1861       document.body[i : i + 1]= ["\\begin_layout FirstName"]
1862       # delete the Argument inset
1863       del( document.body[endInset - 2 : endInset + 3])
1864       del( document.body[lineArg : beginPlain + 1])
1865       document.body[i + 4 : i + 4]= ["\\begin_layout FamilyName"] + Arg2 + ["\\end_layout"] + [""]
1866
1867
1868 def revert_moderncv_2(document):
1869   " Reverts the phone inset of moderncv to the obsoleted mobile or fax "
1870   
1871   if document.textclass != "moderncv":
1872     return
1873   i = 0
1874   j = 0
1875   lineArg = 0
1876   while True:
1877     # \phone
1878     i = find_token(document.body, "\\begin_layout Phone", i)
1879     if i == -1:
1880       return
1881     j = find_end_of_layout(document.body, i)
1882     if j == -1:
1883       document.warning("Malformed LyX document: Can't find end of Phone layout")
1884       i += 1
1885       return
1886     lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
1887     if lineArg > j and j != 0:
1888       i += 1
1889       continue
1890     if lineArg != -1:
1891       beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
1892       # we have to assure that no other inset is in the Argument
1893       beginInset = find_token(document.body, "\\begin_inset", beginPlain)
1894       endInset = find_token(document.body, "\\end_inset", beginPlain)
1895       k = beginPlain + 1
1896       l = k
1897       while beginInset < endInset and beginInset != -1:
1898         beginInset = find_token(document.body, "\\begin_inset", k)
1899         endInset = find_token(document.body, "\\end_inset", l)
1900         k = beginInset + 1
1901         l = endInset + 1
1902       Arg = document.body[beginPlain + 1 : beginPlain + 2]
1903       # rename the style
1904       if Arg[0] == "mobile":
1905         document.body[i : i + 1]= ["\\begin_layout Mobile"]
1906       if Arg[0] == "fax":
1907         document.body[i : i + 1]= ["\\begin_layout Fax"]
1908       # delete the Argument inset
1909       del(document.body[endInset - 2 : endInset + 1])
1910       del(document.body[lineArg : beginPlain + 3])
1911     i += 1
1912
1913
1914 def convert_moderncv(document):
1915   " Convert the Fax and Mobile inset of moderncv to the new phone inset "
1916   
1917   if document.textclass != "moderncv":
1918     return
1919   i = 0
1920   j = 0
1921   lineArg = 0
1922   while True:
1923     # \mobile
1924     i = find_token(document.body, "\\begin_layout Mobile", i)
1925     if i == -1:
1926       return
1927     j = find_end_of_layout(document.body, i)
1928     if j == -1:
1929       document.warning("Malformed LyX document: Can't find end of Mobile layout")
1930       i += 1
1931       return
1932     document.body[i + 1 : i + 1] = ["\\begin_inset Argument 1", "status open", "",
1933                         "\\begin_layout Plain Layout", "mobile", "\\end_layout", "",
1934                         "\\end_inset", ""]
1935     # \fax
1936     i = find_token(document.body, "\\begin_layout Fax", i)
1937     if i == -1:
1938       return
1939     j = find_end_of_layout(document.body, i)
1940     if j == -1:
1941       document.warning("Malformed LyX document: Can't find end of Fax layout")
1942       i += 1
1943       return
1944     document.body[i + 1 : i + 1] = ["\\begin_inset Argument 1", "status open", "",
1945                         "\\begin_layout Plain Layout", "fax", "\\end_layout", "",
1946                         "\\end_inset", ""]
1947     # \firstname and \familyname
1948     i1 = find_token(document.body, "\\begin_layout FirstName", 0)
1949     if i1 == -1:
1950       return
1951     j1 = find_end_of_layout(document.body, i1)
1952     if j1 == -1:
1953       document.warning("Malformed LyX document: Can't find end of FirstName layout")
1954       i1 += 1
1955       return
1956     FirstName = document.body[i1 + 1 : i1 + 2]
1957     i2 = find_token(document.body, "\\begin_layout FamilyName", 0)
1958     if i2 == -1:
1959       return
1960     j2 = find_end_of_layout(document.body, i2)
1961     if j2 == -1:
1962       document.warning("Malformed LyX document: Can't find end of FamilyName layout")
1963       i2 += 1
1964       return
1965     FamilyName = document.body[i2 + 1 : i2 + 2]
1966     if j1 > j2:
1967       k = j1
1968       l = i2
1969     else:
1970       k = j2
1971       l = i1
1972     document.body[k + 1 : k + 1] = ["\\begin_layout Name", "\\begin_inset Argument 1", "status open", "",
1973                         "\\begin_layout Plain Layout", FirstName[0], "\\end_layout", "",
1974                         "\\end_inset", "", FamilyName[0], "\\end_layout", ""]
1975     #document.body[i2 + 1 : i2 + 1] = ["hellok: ", str(k)]
1976     del(document.body[l : k])
1977     i += 1
1978     i1 += 1
1979     i2 += 1
1980
1981
1982 def revert_achemso(document):
1983   " Reverts the flex inset Latin to TeX code "
1984   
1985   if document.textclass != "achemso":
1986     return
1987   i = 0
1988   j = 0
1989   while True:
1990     i = find_token(document.body, "\\begin_inset Flex Latin", i)
1991     if i != -1:
1992       j = find_end_of_inset(document.body, i)
1993     else:
1994       return
1995     if j != -1:
1996       beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1997       endPlain = find_end_of_layout(document.body, beginPlain)
1998       content = lyx2latex(document, document.body[beginPlain : endPlain])
1999       document.body[i:j + 1] = put_cmd_in_ert("\\latin{" + content + "}")
2000     else:
2001       document.warning("Malformed LyX document: Can't find end of flex inset Latin")
2002       return
2003     i += 1
2004
2005
2006 fontsettings = ["\\font_roman", "\\font_sans", "\\font_typewriter", "\\font_math", \
2007                 "\\font_sf_scale", "\\font_tt_scale"]
2008 fontdefaults = ["default", "default", "default", "auto", "100", "100"]
2009 fontquotes = [True, True, True, True, False, False]
2010
2011 def convert_fontsettings(document):
2012     " Duplicate font settings "
2013
2014     i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2015     if i == -1:
2016         document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2017         use_non_tex_fonts = "false"
2018     else:
2019         use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2020     j = 0
2021     for f in fontsettings:
2022         i = find_token(document.header, f + " ", 0)
2023         if i == -1:
2024             document.warning("Malformed LyX document: No " + f + "!")
2025             # we can fix that
2026             # note that with i = -1, this will insert at the end
2027             # of the header
2028             value = fontdefaults[j]
2029         else:
2030             value = document.header[i][len(f):].strip()
2031         if fontquotes[j]:
2032             if use_non_tex_fonts == "true":
2033                 document.header[i:i+1] = [f + ' "' + fontdefaults[j] + '" "' + value + '"']
2034             else:
2035                 document.header[i:i+1] = [f + ' "' + value + '" "' + fontdefaults[j] + '"']
2036         else:
2037             if use_non_tex_fonts == "true":
2038                 document.header[i:i+1] = [f + ' ' + fontdefaults[j] + ' ' + value]
2039             else:
2040                 document.header[i:i+1] = [f + ' ' + value + ' ' + fontdefaults[j]]
2041         j = j + 1
2042
2043
2044 def revert_fontsettings(document):
2045     " Merge font settings "
2046
2047     i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2048     if i == -1:
2049         document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2050         use_non_tex_fonts = "false"
2051     else:
2052         use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2053     j = 0
2054     for f in fontsettings:
2055         i = find_token(document.header, f + " ", 0)
2056         if i == -1:
2057             document.warning("Malformed LyX document: No " + f + "!")
2058             j = j + 1
2059             continue
2060         line = get_value(document.header, f, i)
2061         if fontquotes[j]:
2062             q1 = line.find('"')
2063             q2 = line.find('"', q1+1)
2064             q3 = line.find('"', q2+1)
2065             q4 = line.find('"', q3+1)
2066             if q1 == -1 or q2 == -1 or q3 == -1 or q4 == -1:
2067                 document.warning("Malformed LyX document: Missing quotes!")
2068                 j = j + 1
2069                 continue
2070             if use_non_tex_fonts == "true":
2071                 document.header[i:i+1] = [f + ' ' + line[q3+1:q4]]
2072             else:
2073                 document.header[i:i+1] = [f + ' ' + line[q1+1:q2]]
2074         else:
2075             if use_non_tex_fonts == "true":
2076                 document.header[i:i+1] = [f + ' ' + line.split()[1]]
2077             else:
2078                 document.header[i:i+1] = [f + ' ' + line.split()[0]]
2079         j = j + 1
2080
2081
2082 def revert_solution(document):
2083     " Reverts the solution environment of the theorem module to TeX code "
2084
2085     # Do we use one of the modules that provides Solution?
2086     have_mod = False
2087     mods = document.get_module_list()
2088     for mod in mods:
2089         if mod == "theorems-std" or mod == "theorems-bytype" \
2090         or mod == "theorems-ams" or mod == "theorems-ams-bytype":
2091             have_mod = True
2092             break
2093     if not have_mod:
2094         return
2095
2096     consecutive = False
2097     is_starred = False
2098     i = 0
2099     while True:
2100         i = find_token(document.body, "\\begin_layout Solution", i)
2101         if i == -1:
2102             return
2103
2104         is_starred = document.body[i].startswith("\\begin_layout Solution*")
2105         if is_starred == True:
2106             LaTeXName = "sol*"
2107             LyXName = "Solution*"
2108             theoremName = "newtheorem*"
2109         else:
2110             LaTeXName = "sol"
2111             LyXName = "Solution"
2112             theoremName = "newtheorem"
2113
2114         j = find_end_of_layout(document.body, i)
2115         if j == -1:
2116             document.warning("Malformed LyX document: Can't find end of " + LyXName + " layout")
2117             i += 1
2118             continue
2119
2120         # if this is not a consecutive env, add start command
2121         begcmd = []
2122         if not consecutive:
2123             begcmd = put_cmd_in_ert("\\begin{%s}" % (LaTeXName))
2124
2125         # has this a consecutive theorem of same type?
2126         consecutive = document.body[j + 2] == "\\begin_layout " + LyXName
2127
2128         # if this is not followed by a consecutive env, add end command
2129         if not consecutive:
2130             document.body[j : j + 1] = put_cmd_in_ert("\\end{%s}" % (LaTeXName)) + ["\\end_layout"]
2131
2132         document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
2133
2134         add_to_preamble(document, "\\theoremstyle{definition}")
2135         if is_starred or mod == "theorems-bytype" or mod == "theorems-ams-bytype":
2136             add_to_preamble(document, "\\%s{%s}{\\protect\\solutionname}" % \
2137                 (theoremName, LaTeXName))
2138         else: # mod == "theorems-std" or mod == "theorems-ams" and not is_starred
2139             add_to_preamble(document, "\\%s{%s}[thm]{\\protect\\solutionname}" % \
2140                 (theoremName, LaTeXName))
2141
2142         add_to_preamble(document, "\\providecommand{\solutionname}{Solution}")
2143         i = j
2144
2145
2146 def revert_verbatim_star(document):
2147     from lyx_2_1 import revert_verbatim
2148     revert_verbatim(document, True)
2149
2150
2151 ##
2152 # Conversion hub
2153 #
2154
2155 supported_versions = ["2.2.0", "2.2"]
2156 convert = [
2157            [475, [convert_separator]],
2158            # nothing to do for 476: We consider it a bug that older versions
2159            # did not load amsmath automatically for these commands, and do not
2160            # want to hardcode amsmath off.
2161            [476, []],
2162            [477, []],
2163            [478, []],
2164            [479, []],
2165            [480, []],
2166            [481, [convert_dashes]],
2167            [482, [convert_phrases]],
2168            [483, [convert_specialchar]],
2169            [484, []],
2170            [485, []],
2171            [486, []],
2172            [487, []],
2173            [488, [convert_newgloss]],
2174            [489, [convert_BoxFeatures]],
2175            [490, [convert_origin]],
2176            [491, []],
2177            [492, [convert_colorbox]],
2178            [493, []],
2179            [494, []],
2180            [495, [convert_subref]],
2181            [496, [convert_nounzip]],
2182            [497, [convert_external_bbox]],
2183            [498, []],
2184            [499, [convert_moderncv]],
2185            [500, []],
2186            [501, [convert_fontsettings]],
2187            [502, []],
2188            [503, []]
2189           ]
2190
2191 revert =  [
2192            [502, [revert_verbatim_star]],
2193            [501, [revert_solution]],
2194            [500, [revert_fontsettings]],
2195            [499, [revert_achemso]],
2196            [498, [revert_moderncv_1, revert_moderncv_2]],
2197            [497, [revert_tcolorbox_1, revert_tcolorbox_2,
2198                   revert_tcolorbox_3, revert_tcolorbox_4, revert_tcolorbox_5,
2199                   revert_tcolorbox_6, revert_tcolorbox_7, revert_tcolorbox_8]],
2200            [496, [revert_external_bbox]],
2201            [495, []], # nothing to do since the noUnzip parameter was optional
2202            [494, [revert_subref]],
2203            [493, [revert_jss]],
2204            [492, [revert_mathmulticol]],
2205            [491, [revert_colorbox]],
2206            [490, [revert_textcolor]],
2207            [489, [revert_origin]],
2208            [488, [revert_BoxFeatures]],
2209            [487, [revert_newgloss, revert_glossgroup]],
2210            [486, [revert_forest]],
2211            [485, [revert_ex_itemargs]],
2212            [484, [revert_sigplan_doi]],
2213            [483, [revert_georgian]],
2214            [482, [revert_specialchar]],
2215            [481, [revert_phrases]],
2216            [480, [revert_dashes]],
2217            [479, [revert_question_env]],
2218            [478, [revert_beamer_lemma]],
2219            [477, [revert_xarrow]],
2220            [476, [revert_swissgerman]],
2221            [475, [revert_smash]],
2222            [474, [revert_separator]]
2223           ]
2224
2225
2226 if __name__ == "__main__":
2227     pass