]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_2.py
lyx_2_2.py: remove double code
[lyx.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, convert_info_insets
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, glosse, i)
926             if i == -1:
927                 break
928             j = find_end_of_inset(document.body, i)
929             if j == -1:
930                 document.warning("Malformed LyX document: Can't find end of Glosse inset")
931                 i += 1
932                 continue
933             while True:
934                 ert = find_token(document.body, "\\begin_inset ERT", i, j)
935                 if ert == -1:
936                     break
937                 ertend = find_end_of_inset(document.body, ert)
938                 if ertend == -1:
939                     document.warning("Malformed LyX document: Can't find end of ERT inset")
940                     ert += 1
941                     continue
942                 ertcontent = get_ert(document.body, ert, True)
943                 document.body[ert : ertend + 1] = [ertcontent]
944             i += 1
945
946
947 def convert_newgloss(document):
948     " Converts Glosse insets (Linguistics module) to the new format "
949
950     if not "linguistics" in document.get_module_list():
951         return
952
953     glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
954     for glosse in glosses:
955         i = 0
956         while True:
957             i = find_token(document.body, glosse, i)
958             if i == -1:
959                 break
960             j = find_end_of_inset(document.body, i)
961             if j == -1:
962                 document.warning("Malformed LyX document: Can't find end of Glosse inset")
963                 i += 1
964                 continue
965
966             k = i
967             while True:
968                 argcontent = []
969                 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", k, j)
970                 if beginPlain == -1:
971                     break
972                 endPlain = find_end_of_layout(document.body, beginPlain)
973                 if endPlain == -1:
974                     document.warning("Malformed LyX document: Can't find end of Glosse layout")
975                     i += 1
976                     continue
977
978                 glt  = find_token(document.body, "\\backslash", beginPlain, endPlain)
979                 if glt != -1 and document.body[glt + 1].startswith("glt"):
980                     document.body[glt + 1] = document.body[glt + 1].lstrip("glt").lstrip()
981                     argcontent = document.body[glt + 1 : endPlain]
982                     document.body[beginPlain + 1 : endPlain] = ["\\begin_inset Argument 1", "status open", "",
983                         "\\begin_layout Plain Layout", "\\begin_inset ERT", "status open", "",
984                         "\\begin_layout Plain Layout", ""] + argcontent + ["\\end_layout", "", "\\end_inset", "",
985                         "\\end_layout", "", "\\end_inset"]
986                 else:
987                     content = document.body[beginPlain + 1 : endPlain]
988                     document.body[beginPlain + 1 : endPlain] = ["\\begin_inset ERT", "status open", "",
989                         "\\begin_layout Plain Layout"] + content + ["\\end_layout", "", "\\end_inset"]
990
991                 endPlain = find_end_of_layout(document.body, beginPlain)
992                 k = endPlain
993                 j = find_end_of_inset(document.body, i)
994
995             i = endPlain + 1
996
997
998 def convert_BoxFeatures(document):
999     " adds new box features "
1000
1001     i = 0
1002     while True:
1003         i = find_token(document.body, "height_special", i)
1004         if i == -1:
1005             return
1006         document.body[i+1:i+1] = ['thickness "0.4pt"', 'separation "3pt"', 'shadowsize "4pt"']
1007         i = i + 4
1008
1009
1010 def revert_BoxFeatures(document):
1011     " outputs new box features as TeX code "
1012
1013     i = 0
1014     defaultSep = "3pt"
1015     defaultThick = "0.4pt"
1016     defaultShadow = "4pt"
1017     while True:
1018         i = find_token(document.body, "height_special", i)
1019         if i == -1:
1020             return
1021         # read out the values
1022         beg = document.body[i+1].find('"');
1023         end = document.body[i+1].rfind('"');
1024         thickness = document.body[i+1][beg+1:end];
1025         beg = document.body[i+2].find('"');
1026         end = document.body[i+2].rfind('"');
1027         separation = document.body[i+2][beg+1:end];
1028         beg = document.body[i+3].find('"');
1029         end = document.body[i+3].rfind('"');
1030         shadowsize = document.body[i+3][beg+1:end];
1031         # delete the specification
1032         del document.body[i+1:i+4]
1033         # output ERT
1034         # first output the closing brace
1035         if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1036             document.body[i + 10 : i + 10] = put_cmd_in_ert("}")
1037         # now output the lengths
1038         if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1039             document.body[i - 10 : i - 10] = put_cmd_in_ert("{")
1040         if thickness != defaultThick:
1041             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness]
1042         if separation != defaultSep and thickness == defaultThick:
1043             document.body[i - 5 : i - 4] = ["{\\backslash fboxsep " + separation]
1044         if separation != defaultSep and thickness != defaultThick:
1045             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation]
1046         if shadowsize != defaultShadow and separation == defaultSep and thickness == defaultThick:
1047             document.body[i - 5 : i - 4] = ["{\\backslash shadowsize " + shadowsize]
1048         if shadowsize != defaultShadow and separation != defaultSep and thickness == defaultThick:
1049             document.body[i - 5 : i - 4] = ["{\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1050         if shadowsize != defaultShadow and separation == defaultSep and thickness != defaultThick:
1051             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash shadowsize " + shadowsize]
1052         if shadowsize != defaultShadow and separation != defaultSep and thickness != defaultThick:
1053             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1054         i = i + 11
1055
1056
1057 def convert_origin(document):
1058     " Insert the origin tag "
1059
1060     i = find_token(document.header, "\\textclass ", 0)
1061     if i == -1:
1062         document.warning("Malformed LyX document: No \\textclass!!")
1063         return
1064     if document.dir == "":
1065         origin = "stdin"
1066     else:
1067         relpath = ''
1068         if document.systemlyxdir and document.systemlyxdir != '':
1069             try:
1070                 if os.path.isabs(document.dir):
1071                     absdir = os.path.normpath(document.dir)
1072                 else:
1073                     absdir = os.path.normpath(os.path.abspath(document.dir))
1074                 if os.path.isabs(document.systemlyxdir):
1075                     abssys = os.path.normpath(document.systemlyxdir)
1076                 else:
1077                     abssys = os.path.normpath(os.path.abspath(document.systemlyxdir))
1078                 relpath = os.path.relpath(absdir, abssys)
1079                 if relpath.find('..') == 0:
1080                     relpath = ''
1081             except:
1082                 relpath = ''
1083         if relpath == '':
1084             origin = document.dir.replace('\\', '/') + '/'
1085         else:
1086             origin = os.path.join("/systemlyxdir", relpath).replace('\\', '/') + '/'
1087         if os.name != 'nt':
1088             origin = unicode(origin, sys.getfilesystemencoding())
1089     document.header[i:i] = ["\\origin " + origin]
1090
1091
1092 def revert_origin(document):
1093     " Remove the origin tag "
1094
1095     i = find_token(document.header, "\\origin ", 0)
1096     if i == -1:
1097         document.warning("Malformed LyX document: No \\origin!!")
1098         return
1099     del document.header[i]
1100
1101
1102 color_names = ["brown", "darkgray", "gray", \
1103                "lightgray", "lime", "olive", "orange", \
1104                "pink", "purple", "teal", "violet"]
1105
1106 def revert_textcolor(document):
1107     " revert new \\textcolor colors to TeX code "
1108
1109     i = 0
1110     j = 0
1111     xcolor = False
1112     while True:
1113         i = find_token(document.body, "\\color ", i)
1114         if i == -1:
1115             return
1116         else:
1117             for color in list(color_names):
1118                 if document.body[i] == "\\color " + color:
1119                     # register that xcolor must be loaded in the preamble
1120                     if xcolor == False:
1121                         xcolor = True
1122                         add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\usepackage{xcolor}}{}"])
1123                     # find the next \\color and/or the next \\end_layout
1124                     j = find_token(document.body, "\\color", i + 1)
1125                     k = find_token(document.body, "\\end_layout", i + 1)
1126                     if j == -1 and k != -1:
1127                         j = k +1 
1128                     # output TeX code
1129                     # first output the closing brace
1130                     if k < j:
1131                         document.body[k: k] = put_cmd_in_ert("}")
1132                     else:
1133                         document.body[j: j] = put_cmd_in_ert("}")
1134                     # now output the \textcolor command
1135                     document.body[i : i + 1] = put_cmd_in_ert("\\textcolor{" + color + "}{")
1136         i = i + 1
1137
1138
1139 def convert_colorbox(document):
1140     " adds color settings for boxes "
1141
1142     i = 0
1143     while True:
1144         i = find_token(document.body, "shadowsize", i)
1145         if i == -1:
1146             return
1147         document.body[i+1:i+1] = ['framecolor "black"', 'backgroundcolor "none"']
1148         i = i + 3
1149
1150
1151 def revert_colorbox(document):
1152     " outputs color settings for boxes as TeX code "
1153
1154     binset = 0
1155     defaultframecolor = "black"
1156     defaultbackcolor = "none"
1157     while True:
1158         binset = find_token(document.body, "\\begin_inset Box", binset)
1159         if binset == -1:
1160             return
1161
1162         einset = find_end_of_inset(document.body, binset)
1163         if einset == -1:
1164             document.warning("Malformed LyX document: Can't find end of box inset!")
1165             binset += 1
1166             continue
1167
1168         blay = find_token(document.body, "\\begin_layout", binset, einset)
1169         if blay == -1:
1170             document.warning("Malformed LyX document: Can't find start of layout!")
1171             binset = einset
1172             continue
1173
1174         # doing it this way, we make sure only to find a framecolor option
1175         frame = find_token(document.body, "framecolor", binset, blay)
1176         if frame == -1:
1177             binset = einset
1178             continue
1179
1180         beg = document.body[frame].find('"')
1181         end = document.body[frame].rfind('"')
1182         framecolor = document.body[frame][beg + 1 : end]
1183
1184         # this should be on the next line
1185         bgcolor = frame + 1
1186         beg = document.body[bgcolor].find('"')
1187         end = document.body[bgcolor].rfind('"')
1188         backcolor = document.body[bgcolor][beg + 1 : end]
1189
1190         # delete those bits
1191         del document.body[frame : frame + 2]
1192         # adjust end of inset
1193         einset -= 2
1194
1195         if document.body[binset] == "\\begin_inset Box Boxed" and \
1196             framecolor != defaultframecolor:
1197           document.body[binset] = "\\begin_inset Box Frameless"
1198
1199         # output TeX code
1200         # first output the closing brace
1201         if framecolor == defaultframecolor and backcolor == defaultbackcolor:
1202             # nothing needed
1203             pass
1204         else:
1205             # we also neeed to load xcolor in the preamble but only once
1206             add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\usepackage{xcolor}}{}"])
1207             document.body[einset + 1 : einset + 1] = put_cmd_in_ert("}")
1208             if framecolor != defaultframecolor:
1209                 document.body[binset:binset] = put_cmd_in_ert("\\fcolorbox{" + framecolor + "}{" + backcolor + "}{")
1210             else:
1211               document.body[binset:binset] = put_cmd_in_ert("\\colorbox{" + backcolor + "}{")
1212
1213         binset = einset
1214
1215
1216 def revert_mathmulticol(document):
1217     " Convert formulas to ERT if they contain multicolumns "
1218
1219     i = 0
1220     while True:
1221         i = find_token(document.body, '\\begin_inset Formula', i)
1222         if i == -1:
1223             return
1224         j = find_end_of_inset(document.body, i)
1225         if j == -1:
1226             document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
1227             i += 1
1228             continue
1229         lines = document.body[i:j]
1230         lines[0] = lines[0].replace('\\begin_inset Formula', '').lstrip()
1231         code = "\n".join(lines)
1232         converted = False
1233         k = 0
1234         n = 0
1235         while n >= 0:
1236             n = code.find("\\multicolumn", k)
1237             # no need to convert degenerated multicolumn cells,
1238             # they work in old LyX versions as "math ERT"
1239             if n != -1 and code.find("\\multicolumn{1}", k) != n:
1240                 ert = put_cmd_in_ert(code)
1241                 document.body[i:j+1] = ert
1242                 converted = True
1243                 break
1244             else:
1245                 k = n + 12
1246         if converted:
1247             i = find_end_of_inset(document.body, i)
1248         else:
1249             i = j
1250
1251
1252 def revert_jss(document):
1253     " Reverts JSS In_Preamble commands to ERT in preamble "
1254
1255     if document.textclass != "jss":
1256         return
1257
1258     h = 0
1259     m = 0
1260     j = 0
1261     k = 0
1262     n = 0
1263     while True:
1264       # at first revert the inset layouts because they can be part of the In_Preamble layouts
1265       while m != -1 or j != -1 or h != -1 or k != -1 or n != -1:
1266         # \pkg
1267         if h != -1:
1268           h = find_token(document.body, "\\begin_inset Flex Pkg", h)
1269         if h != -1:
1270           endh = find_end_of_inset(document.body, h)
1271           document.body[endh - 2 : endh + 1] = put_cmd_in_ert("}")
1272           document.body[h : h + 4] = put_cmd_in_ert("\\pkg{")
1273           h = h + 5
1274         # \proglang
1275         if m != -1:
1276           m = find_token(document.body, "\\begin_inset Flex Proglang", m)
1277         if m != -1:
1278           endm = find_end_of_inset(document.body, m)
1279           document.body[endm - 2 : endm + 1] = put_cmd_in_ert("}")
1280           document.body[m : m + 4] = put_cmd_in_ert("\\proglang{")
1281           m = m + 5
1282         # \code
1283         if j != -1:
1284           j = find_token(document.body, "\\begin_inset Flex Code", j)
1285         if j != -1:
1286           # assure that we are not in a Code Chunk inset
1287           if document.body[j][-1] == "e":
1288               endj = find_end_of_inset(document.body, j)
1289               document.body[endj - 2 : endj + 1] = put_cmd_in_ert("}")
1290               document.body[j : j + 4] = put_cmd_in_ert("\\code{")
1291               j = j + 5
1292           else:
1293               j = j + 1
1294         # \email
1295         if k != -1:
1296           k = find_token(document.body, "\\begin_inset Flex E-mail", k)
1297         if k != -1:
1298           endk = find_end_of_inset(document.body, k)
1299           document.body[endk - 2 : endk + 1] = put_cmd_in_ert("}")
1300           document.body[k : k + 4] = put_cmd_in_ert("\\email{")
1301           k = k + 5
1302         # \url
1303         if n != -1:
1304           n = find_token(document.body, "\\begin_inset Flex URL", n)
1305         if n != -1:
1306           endn = find_end_of_inset(document.body, n)
1307           document.body[endn - 2 : endn + 1] = put_cmd_in_ert("}")
1308           document.body[n : n + 4] = put_cmd_in_ert("\\url{")
1309           n = n + 5
1310       # now revert the In_Preamble layouts
1311       # \title
1312       i = find_token(document.body, "\\begin_layout Title", 0)
1313       if i == -1:
1314         return
1315       j = find_end_of_layout(document.body, i)
1316       if j == -1:
1317         document.warning("Malformed LyX document: Can't find end of Title layout")
1318         i += 1
1319         continue
1320       content = lyx2latex(document, document.body[i:j + 1])
1321       add_to_preamble(document, ["\\title{" + content + "}"])
1322       del document.body[i:j + 1]
1323       # \author
1324       i = find_token(document.body, "\\begin_layout Author", 0)
1325       if i == -1:
1326         return
1327       j = find_end_of_layout(document.body, i)
1328       if j == -1:
1329         document.warning("Malformed LyX document: Can't find end of Author layout")
1330         i += 1
1331         continue
1332       content = lyx2latex(document, document.body[i:j + 1])
1333       add_to_preamble(document, ["\\author{" + content + "}"])
1334       del document.body[i:j + 1]
1335       # \Plainauthor
1336       i = find_token(document.body, "\\begin_layout Plain Author", 0)
1337       if i == -1:
1338         return
1339       j = find_end_of_layout(document.body, i)
1340       if j == -1:
1341         document.warning("Malformed LyX document: Can't find end of Plain Author layout")
1342         i += 1
1343         continue
1344       content = lyx2latex(document, document.body[i:j + 1])
1345       add_to_preamble(document, ["\\Plainauthor{" + content + "}"])
1346       del document.body[i:j + 1]
1347       # \Plaintitle
1348       i = find_token(document.body, "\\begin_layout Plain Title", 0)
1349       if i == -1:
1350         return
1351       j = find_end_of_layout(document.body, i)
1352       if j == -1:
1353         document.warning("Malformed LyX document: Can't find end of Plain Title layout")
1354         i += 1
1355         continue
1356       content = lyx2latex(document, document.body[i:j + 1])
1357       add_to_preamble(document, ["\\Plaintitle{" + content + "}"])
1358       del document.body[i:j + 1]
1359       # \Shorttitle
1360       i = find_token(document.body, "\\begin_layout Short Title", 0)
1361       if i == -1:
1362         return
1363       j = find_end_of_layout(document.body, i)
1364       if j == -1:
1365         document.warning("Malformed LyX document: Can't find end of Short Title layout")
1366         i += 1
1367         continue
1368       content = lyx2latex(document, document.body[i:j + 1])
1369       add_to_preamble(document, ["\\Shorttitle{" + content + "}"])
1370       del document.body[i:j + 1]
1371       # \Abstract
1372       i = find_token(document.body, "\\begin_layout Abstract", 0)
1373       if i == -1:
1374         return
1375       j = find_end_of_layout(document.body, i)
1376       if j == -1:
1377         document.warning("Malformed LyX document: Can't find end of Abstract layout")
1378         i += 1
1379         continue
1380       content = lyx2latex(document, document.body[i:j + 1])
1381       add_to_preamble(document, ["\\Abstract{" + content + "}"])
1382       del document.body[i:j + 1]
1383       # \Keywords
1384       i = find_token(document.body, "\\begin_layout Keywords", 0)
1385       if i == -1:
1386         return
1387       j = find_end_of_layout(document.body, i)
1388       if j == -1:
1389         document.warning("Malformed LyX document: Can't find end of Keywords layout")
1390         i += 1
1391         continue
1392       content = lyx2latex(document, document.body[i:j + 1])
1393       add_to_preamble(document, ["\\Keywords{" + content + "}"])
1394       del document.body[i:j + 1]
1395       # \Plainkeywords
1396       i = find_token(document.body, "\\begin_layout Plain Keywords", 0)
1397       if i == -1:
1398         return
1399       j = find_end_of_layout(document.body, i)
1400       if j == -1:
1401         document.warning("Malformed LyX document: Can't find end of Plain Keywords layout")
1402         i += 1
1403         continue
1404       content = lyx2latex(document, document.body[i:j + 1])
1405       add_to_preamble(document, ["\\Plainkeywords{" + content + "}"])
1406       del document.body[i:j + 1]
1407       # \Address
1408       i = find_token(document.body, "\\begin_layout Address", 0)
1409       if i == -1:
1410         return
1411       j = find_end_of_layout(document.body, i)
1412       if j == -1:
1413         document.warning("Malformed LyX document: Can't find end of Address layout")
1414         i += 1
1415         continue
1416       content = lyx2latex(document, document.body[i:j + 1])
1417       add_to_preamble(document, ["\\Address{" + content + "}"])
1418       del document.body[i:j + 1]
1419       # finally handle the code layouts
1420       h = 0
1421       m = 0
1422       j = 0
1423       k = 0
1424       while m != -1 or j != -1 or h != -1 or k != -1:
1425         # \CodeChunk
1426         if h != -1:
1427           h = find_token(document.body, "\\begin_inset Flex Code Chunk", h)
1428         if h != -1:
1429           endh = find_end_of_inset(document.body, h)
1430           document.body[endh : endh + 1] = put_cmd_in_ert("\\end{CodeChunk}")
1431           document.body[h : h + 3] = put_cmd_in_ert("\\begin{CodeChunk}")
1432           document.body[h - 1 : h] = ["\\begin_layout Standard"]
1433           h = h + 1
1434         # \CodeInput
1435         if j != -1:
1436           j = find_token(document.body, "\\begin_layout Code Input", j)
1437         if j != -1:
1438           endj = find_end_of_layout(document.body, j)
1439           document.body[endj : endj + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1440           document.body[endj + 3 : endj + 4] = put_cmd_in_ert("\\end{CodeInput}")
1441           document.body[endj + 13 : endj + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1442           document.body[j + 1 : j] = ["\\end_layout", "", "\\begin_layout Standard"]
1443           document.body[j : j + 1] = put_cmd_in_ert("\\begin{CodeInput}")
1444           j = j + 1
1445         # \CodeOutput
1446         if k != -1:
1447           k = find_token(document.body, "\\begin_layout Code Output", k)
1448         if k != -1:
1449           endk = find_end_of_layout(document.body, k)
1450           document.body[endk : endk + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1451           document.body[endk + 3 : endk + 4] = put_cmd_in_ert("\\end{CodeOutput}")
1452           document.body[endk + 13 : endk + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1453           document.body[k + 1 : k] = ["\\end_layout", "", "\\begin_layout Standard"]
1454           document.body[k : k + 1] = put_cmd_in_ert("\\begin{CodeOutput}")
1455           k = k + 1
1456         # \Code
1457         if m != -1:
1458           m = find_token(document.body, "\\begin_layout Code", m)
1459         if m != -1:
1460           endm = find_end_of_layout(document.body, m)
1461           document.body[endm : endm + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1462           document.body[endm + 3 : endm + 4] = put_cmd_in_ert("\\end{Code}")
1463           document.body[endm + 13 : endm + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1464           document.body[m + 1 : m] = ["\\end_layout", "", "\\begin_layout Standard"]
1465           document.body[m : m + 1] = put_cmd_in_ert("\\begin{Code}")
1466           m = m + 1
1467
1468
1469 def convert_subref(document):
1470     " converts sub: ref prefixes to subref: "
1471
1472     # 1) label insets
1473     rx = re.compile(r'^name \"sub:(.+)$')
1474     i = 0
1475     while True:
1476         i = find_token(document.body, "\\begin_inset CommandInset label", i)
1477         if i == -1:
1478             break
1479         j = find_end_of_inset(document.body, i)
1480         if j == -1:
1481             document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1482             i += 1
1483             continue
1484
1485         for p in range(i, j):
1486             m = rx.match(document.body[p])
1487             if m:
1488                 label = m.group(1)
1489                 document.body[p] = "name \"subsec:" + label
1490         i += 1
1491
1492     # 2) xref insets
1493     rx = re.compile(r'^reference \"sub:(.+)$')
1494     i = 0
1495     while True:
1496         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1497         if i == -1:
1498             return
1499         j = find_end_of_inset(document.body, i)
1500         if j == -1:
1501             document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1502             i += 1
1503             continue
1504
1505         for p in range(i, j):
1506             m = rx.match(document.body[p])
1507             if m:
1508                 label = m.group(1)
1509                 document.body[p] = "reference \"subsec:" + label
1510                 break
1511         i += 1
1512
1513
1514
1515 def revert_subref(document):
1516     " reverts subref: ref prefixes to sub: "
1517
1518     # 1) label insets
1519     rx = re.compile(r'^name \"subsec:(.+)$')
1520     i = 0
1521     while True:
1522         i = find_token(document.body, "\\begin_inset CommandInset label", i)
1523         if i == -1:
1524             break
1525         j = find_end_of_inset(document.body, i)
1526         if j == -1:
1527             document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1528             i += 1
1529             continue
1530
1531         for p in range(i, j):
1532             m = rx.match(document.body[p])
1533             if m:
1534                 label = m.group(1)
1535                 document.body[p] = "name \"sub:" + label
1536                 break
1537         i += 1
1538
1539     # 2) xref insets
1540     rx = re.compile(r'^reference \"subsec:(.+)$')
1541     i = 0
1542     while True:
1543         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1544         if i == -1:
1545             return
1546         j = find_end_of_inset(document.body, i)
1547         if j == -1:
1548             document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1549             i += 1
1550             continue
1551
1552         for p in range(i, j):
1553             m = rx.match(document.body[p])
1554             if m:
1555                 label = m.group(1)
1556                 document.body[p] = "reference \"sub:" + label
1557                 break
1558         i += 1
1559
1560
1561 def convert_nounzip(document):
1562     " remove the noUnzip parameter of graphics insets "
1563
1564     rx = re.compile(r'\s*noUnzip\s*$')
1565     i = 0
1566     while True:
1567         i = find_token(document.body, "\\begin_inset Graphics", i)
1568         if i == -1:
1569             break
1570         j = find_end_of_inset(document.body, i)
1571         if j == -1:
1572             document.warning("Malformed LyX document: Can't find end of graphics inset at line " + str(i))
1573             i += 1
1574             continue
1575
1576         k = find_re(document.body, rx, i, j)
1577         if k != -1:
1578           del document.body[k]
1579           j = j - 1
1580         i = j + 1
1581
1582
1583 def convert_revert_external_bbox(document, forward):
1584     " add units to bounding box of external insets "
1585
1586     rx = re.compile(r'^\s*boundingBox\s+\S+\s+\S+\s+\S+\s+\S+\s*$')
1587     i = 0
1588     while True:
1589         i = find_token(document.body, "\\begin_inset External", i)
1590         if i == -1:
1591             break
1592         j = find_end_of_inset(document.body, i)
1593         if j == -1:
1594             document.warning("Malformed LyX document: Can't find end of external inset at line " + str(i))
1595             i += 1
1596             continue
1597         k = find_re(document.body, rx, i, j)
1598         if k == -1:
1599             i = j + 1
1600             continue
1601         tokens = document.body[k].split()
1602         if forward:
1603             for t in range(1, 5):
1604                 tokens[t] += "bp"
1605         else:
1606             for t in range(1, 5):
1607                 tokens[t] = length_in_bp(tokens[t])
1608         document.body[k] = "\tboundingBox " + tokens[1] + " " + tokens[2] + " " + \
1609                            tokens[3] + " " + tokens[4]
1610         i = j + 1
1611
1612
1613 def convert_external_bbox(document):
1614     convert_revert_external_bbox(document, True)
1615
1616
1617 def revert_external_bbox(document):
1618     convert_revert_external_bbox(document, False)
1619
1620
1621 def revert_tcolorbox_1(document):
1622   " Reverts the Flex:Subtitle inset of tcolorbox to TeX-code "
1623   i = -1
1624   while True:
1625     i = find_token(document.header, "tcolorbox", i)
1626     if i == -1:
1627       break
1628     else:    
1629       flex = 0
1630       flexEnd = -1
1631       flex = find_token(document.body, "\\begin_inset Flex Subtitle", flex)
1632       if flex == -1:
1633         return flexEnd
1634       flexEnd = find_end_of_inset(document.body, flex)
1635       wasOpt = revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, False, True, False)
1636       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, False, False, False)
1637       flexEnd = find_end_of_inset(document.body, flex)
1638       if wasOpt == True:
1639         document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\tcbsubtitle")
1640       else:
1641         document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\tcbsubtitle{")
1642       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("}")
1643       flex += 1
1644
1645
1646 def revert_tcolorbox_2(document):
1647   " Reverts the Flex:Raster_Color_Box inset of tcolorbox to TeX-code "
1648   i = -1
1649   while True:
1650     i = find_token(document.header, "tcolorbox", i)
1651     if i == -1:
1652       break
1653     else:    
1654       flex = 0
1655       flexEnd = -1
1656       flex = find_token(document.body, "\\begin_inset Flex Raster Color Box", flex)
1657       if flex == -1:
1658         return flexEnd
1659       flexEnd = find_end_of_inset(document.body, flex)
1660       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1661       flexEnd = find_end_of_inset(document.body, flex)
1662       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{tcbraster}")
1663       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("\\end{tcbraster}")
1664       flex += 1
1665
1666
1667 def revert_tcolorbox_3(document):
1668   " Reverts the Flex:Custom_Color_Box_1 inset of tcolorbox to TeX-code "
1669   i = -1
1670   while True:
1671     i = find_token(document.header, "tcolorbox", i)
1672     if i == -1:
1673       break
1674     else:    
1675       flex = 0
1676       flexEnd = -1
1677       flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 1", flex)
1678       if flex == -1:
1679         return flexEnd
1680       flexEnd = find_end_of_inset(document.body, flex)
1681       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1682       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1683       flexEnd = find_end_of_inset(document.body, flex)
1684       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxA}")
1685       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxA}")
1686       flex += 1
1687
1688
1689 def revert_tcolorbox_4(document):
1690   " Reverts the Flex:Custom_Color_Box_2 inset of tcolorbox to TeX-code "
1691   i = -1
1692   while True:
1693     i = find_token(document.header, "tcolorbox", i)
1694     if i == -1:
1695       break
1696     else:    
1697       flex = 0
1698       flexEnd = -1
1699       flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 2", flex)
1700       if flex == -1:
1701         return flexEnd
1702       flexEnd = find_end_of_inset(document.body, flex)
1703       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1704       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1705       flexEnd = find_end_of_inset(document.body, flex)
1706       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxB}")
1707       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxB}")
1708       flex += 1
1709
1710
1711 def revert_tcolorbox_5(document):
1712   " Reverts the Flex:Custom_Color_Box_3 inset of tcolorbox to TeX-code "
1713   i = -1
1714   while True:
1715     i = find_token(document.header, "tcolorbox", i)
1716     if i == -1:
1717       break
1718     else:    
1719       flex = 0
1720       flexEnd = -1
1721       flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 3", flex)
1722       if flex == -1:
1723         return flexEnd
1724       flexEnd = find_end_of_inset(document.body, flex)
1725       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1726       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1727       flexEnd = find_end_of_inset(document.body, flex)
1728       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxC}")
1729       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxC}")
1730       flex += 1
1731
1732
1733 def revert_tcolorbox_6(document):
1734   " Reverts the Flex:Custom_Color_Box_4 inset of tcolorbox to TeX-code "
1735   i = -1
1736   while True:
1737     i = find_token(document.header, "tcolorbox", i)
1738     if i == -1:
1739       break
1740     else:    
1741       flex = 0
1742       flexEnd = -1
1743       flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 4", flex)
1744       if flex == -1:
1745         return flexEnd
1746       flexEnd = find_end_of_inset(document.body, flex)
1747       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1748       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1749       flexEnd = find_end_of_inset(document.body, flex)
1750       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxD}")
1751       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxD}")
1752       flex += 1
1753
1754
1755 def revert_tcolorbox_7(document):
1756   " Reverts the Flex:Custom_Color_Box_5 inset of tcolorbox to TeX-code "
1757   i = -1
1758   while True:
1759     i = find_token(document.header, "tcolorbox", i)
1760     if i == -1:
1761       break
1762     else:    
1763       flex = 0
1764       flexEnd = -1
1765       flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 5", flex)
1766       if flex == -1:
1767         return flexEnd
1768       flexEnd = find_end_of_inset(document.body, flex)
1769       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1770       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1771       flexEnd = find_end_of_inset(document.body, flex)
1772       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxE}")
1773       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxE}")
1774       flex += 1
1775
1776
1777 def revert_tcolorbox_8(document):
1778   " Reverts the layout New Color Box Type of tcolorbox to TeX-code "
1779   i = 0
1780   j = 0
1781   k = 0
1782   while True:
1783     if i != -1:
1784       i = find_token(document.body, "\\begin_layout New Color Box Type", i)
1785     if i != -1:
1786       j = find_end_of_layout(document.body, i)
1787       wasOpt = revert_Argument_to_TeX_brace(document, i, j, 1, 1, False, True, False)
1788       revert_Argument_to_TeX_brace(document, i, 0, 2, 2, False, False, True)
1789       revert_Argument_to_TeX_brace(document, i, 0, 3, 4, False, True, False)
1790       document.body[i] = document.body[i].replace("\\begin_layout New Color Box Type", "\\begin_layout Standard")
1791       if wasOpt == True:
1792         document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox")
1793       else:
1794         document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox{")
1795       k = find_end_of_inset(document.body, j)
1796       k = find_token(document.body, "\\end_inset", k + 1)
1797       k = find_token(document.body, "\\end_inset", k + 1)
1798       if wasOpt == True:
1799         k = find_token(document.body, "\\end_inset", k + 1)
1800       document.body[k + 2 : j + 2] = put_cmd_in_ert("{")
1801       j = find_token(document.body, "\\begin_layout Standard", j + 1)
1802       document.body[j - 2 : j - 2] = put_cmd_in_ert("}")
1803       i += 1
1804     if i == -1:
1805       return
1806
1807
1808 def revert_moderncv_1(document):
1809   " Reverts the new inset of moderncv to TeX-code in preamble "
1810   
1811   if document.textclass != "moderncv":
1812     return
1813   i = 0
1814   j = 0
1815   lineArg = 0
1816   while True:
1817     # at first revert the new styles
1818     # \moderncvicons
1819     i = find_token(document.body, "\\begin_layout CVIcons", 0)
1820     if i == -1:
1821       return
1822     j = find_end_of_layout(document.body, i)
1823     if j == -1:
1824       document.warning("Malformed LyX document: Can't find end of CVIcons layout")
1825       i += 1
1826       continue
1827     content = lyx2latex(document, document.body[i:j + 1])
1828     add_to_preamble(document, ["\\moderncvicons{" + content + "}"])
1829     del document.body[i:j + 1]
1830     # \hintscolumnwidth
1831     i = find_token(document.body, "\\begin_layout CVColumnWidth", 0)
1832     if i == -1:
1833       return
1834     j = find_end_of_layout(document.body, i)
1835     if j == -1:
1836       document.warning("Malformed LyX document: Can't find end of CVColumnWidth layout")
1837       i += 1
1838       continue
1839     content = lyx2latex(document, document.body[i:j + 1])
1840     add_to_preamble(document, ["\\setlength{\hintscolumnwidth}{" + content + "}"])
1841     del document.body[i:j + 1]
1842     # now change the new styles to the obsolete ones
1843     # \name
1844     i = find_token(document.body, "\\begin_layout Name", 0)
1845     if i == -1:
1846       return
1847     j = find_end_of_layout(document.body, i)
1848     if j == -1:
1849       document.warning("Malformed LyX document: Can't find end of Name layout")
1850       i += 1
1851       continue
1852     lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
1853     if lineArg > j and j != 0:
1854       return
1855     if lineArg != -1:
1856       beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
1857       # we have to assure that no other inset is in the Argument
1858       beginInset = find_token(document.body, "\\begin_inset", beginPlain)
1859       endInset = find_token(document.body, "\\end_inset", beginPlain)
1860       k = beginPlain + 1
1861       l = k
1862       while beginInset < endInset and beginInset != -1:
1863         beginInset = find_token(document.body, "\\begin_inset", k)
1864         endInset = find_token(document.body, "\\end_inset", l)
1865         k = beginInset + 1
1866         l = endInset + 1
1867       Arg2 = document.body[l + 5 : l + 6]
1868       # rename the style
1869       document.body[i : i + 1]= ["\\begin_layout FirstName"]
1870       # delete the Argument inset
1871       del( document.body[endInset - 2 : endInset + 3])
1872       del( document.body[lineArg : beginPlain + 1])
1873       document.body[i + 4 : i + 4]= ["\\begin_layout FamilyName"] + Arg2 + ["\\end_layout"] + [""]
1874
1875
1876 def revert_moderncv_2(document):
1877   " Reverts the phone inset of moderncv to the obsoleted mobile or fax "
1878   
1879   if document.textclass != "moderncv":
1880     return
1881   i = 0
1882   j = 0
1883   lineArg = 0
1884   while True:
1885     # \phone
1886     i = find_token(document.body, "\\begin_layout Phone", i)
1887     if i == -1:
1888       return
1889     j = find_end_of_layout(document.body, i)
1890     if j == -1:
1891       document.warning("Malformed LyX document: Can't find end of Phone layout")
1892       i += 1
1893       return
1894     lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
1895     if lineArg > j and j != 0:
1896       i += 1
1897       continue
1898     if lineArg != -1:
1899       beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
1900       # we have to assure that no other inset is in the Argument
1901       beginInset = find_token(document.body, "\\begin_inset", beginPlain)
1902       endInset = find_token(document.body, "\\end_inset", beginPlain)
1903       k = beginPlain + 1
1904       l = k
1905       while beginInset < endInset and beginInset != -1:
1906         beginInset = find_token(document.body, "\\begin_inset", k)
1907         endInset = find_token(document.body, "\\end_inset", l)
1908         k = beginInset + 1
1909         l = endInset + 1
1910       Arg = document.body[beginPlain + 1 : beginPlain + 2]
1911       # rename the style
1912       if Arg[0] == "mobile":
1913         document.body[i : i + 1]= ["\\begin_layout Mobile"]
1914       if Arg[0] == "fax":
1915         document.body[i : i + 1]= ["\\begin_layout Fax"]
1916       # delete the Argument inset
1917       del(document.body[endInset - 2 : endInset + 1])
1918       del(document.body[lineArg : beginPlain + 3])
1919     i += 1
1920
1921
1922 def convert_moderncv_phone(document):
1923     " Convert the Fax and Mobile inset of moderncv to the new phone inset "
1924
1925     if document.textclass != "moderncv":
1926         return
1927     i = 0
1928     j = 0
1929     lineArg = 0
1930
1931     phone_dict = {
1932         "Mobile" : "mobile",
1933         "Fax" : "fax",
1934         }
1935
1936     rx = re.compile(r'^\\begin_layout (\S+)$')
1937     while True:
1938         # substitute \fax and \mobile by \phone[fax] and \phone[mobile], respectively
1939         i = find_token(document.body, "\\begin_layout", i)
1940         if i == -1:
1941             return
1942
1943         m = rx.match(document.body[i])
1944         val = ""
1945         if m:
1946             val = m.group(1)
1947         if val not in list(phone_dict.keys()):
1948             i += 1
1949             continue
1950         j = find_end_of_layout(document.body, i)
1951         if j == -1:
1952             document.warning("Malformed LyX document: Can't find end of Mobile layout")
1953             i += 1
1954             return
1955
1956         document.body[i : i + 1] = ["\\begin_layout Phone", "\\begin_inset Argument 1", "status open", "",
1957                             "\\begin_layout Plain Layout", phone_dict[val], "\\end_layout", "",
1958                             "\\end_inset", ""]
1959
1960
1961 def convert_moderncv_name(document):
1962     " Convert the FirstName and LastName layout of moderncv to the general Name layout "
1963
1964     if document.textclass != "moderncv":
1965         return
1966
1967     fnb = 0 # Begin of FirstName inset
1968     fne = 0 # End of FirstName inset
1969     lnb = 0 # Begin of LastName (FamilyName) inset
1970     lne = 0 # End of LastName (FamilyName) inset
1971     nb = 0 # Begin of substituting Name inset
1972     ne = 0 # End of substituting Name inset
1973     FirstName = [] # FirstName content
1974     FamilyName = [] # LastName content
1975
1976     while True:
1977         # locate FirstName
1978         fnb = find_token(document.body, "\\begin_layout FirstName", fnb)
1979         if fnb != -1:
1980             fne = find_end_of_layout(document.body, fnb)
1981             if fne == -1:
1982                 document.warning("Malformed LyX document: Can't find end of FirstName layout")
1983                 return
1984             FirstName = document.body[fnb + 1 : fne]
1985         # locate FamilyName
1986         lnb = find_token(document.body, "\\begin_layout FamilyName", lnb)
1987         if lnb != -1:
1988             lne = find_end_of_layout(document.body, lnb)
1989             if lne == -1:
1990                 document.warning("Malformed LyX document: Can't find end of FamilyName layout")
1991                 return
1992             FamilyName = document.body[lnb + 1 : lne]
1993         # Determine the region for the substituting Name layout
1994         if fnb == -1 and lnb == -1: # Neither FirstName nor FamilyName exists -> Do nothing
1995             return
1996         elif fnb == -1: # Only FamilyName exists -> New Name insets replaces that
1997             nb = lnb
1998             ne = lne
1999         elif lnb == -1: # Only FirstName exists -> New Name insets replaces that
2000             nb = fnb
2001             ne = fne
2002         elif fne > lne: # FirstName position before FamilyName -> New Name insets spans
2003             nb = lnb #     from FamilyName begin
2004             ne = fne #     to FirstName end
2005         else: #           FirstName position before FamilyName -> New Name insets spans
2006             nb = fnb #     from FirstName begin
2007             ne = lne #     to FamilyName end
2008
2009         # Insert the substituting layout now. If FirstName exists, use an otpional argument.
2010         if FirstName == []:
2011             document.body[nb : ne + 1] = ["\\begin_layout Name"] + FamilyName + ["\\end_layout", ""]
2012         else:
2013             document.body[nb : ne + 1] = ["\\begin_layout Name", "\\begin_inset Argument 1", "status open", "",
2014                                 "\\begin_layout Plain Layout"] + FirstName + ["\\end_layout", "",
2015                                 "\\end_inset", ""] + FamilyName + ["\\end_layout", ""]
2016
2017
2018 def revert_achemso(document):
2019   " Reverts the flex inset Latin to TeX code "
2020   
2021   if document.textclass != "achemso":
2022     return
2023   i = 0
2024   j = 0
2025   while True:
2026     i = find_token(document.body, "\\begin_inset Flex Latin", i)
2027     if i != -1:
2028       j = find_end_of_inset(document.body, i)
2029     else:
2030       return
2031     if j != -1:
2032       beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2033       endPlain = find_end_of_layout(document.body, beginPlain)
2034       content = lyx2latex(document, document.body[beginPlain : endPlain])
2035       document.body[i:j + 1] = put_cmd_in_ert("\\latin{" + content + "}")
2036     else:
2037       document.warning("Malformed LyX document: Can't find end of flex inset Latin")
2038       return
2039     i += 1
2040
2041
2042 fontsettings = ["\\font_roman", "\\font_sans", "\\font_typewriter", "\\font_math", \
2043                 "\\font_sf_scale", "\\font_tt_scale"]
2044 fontdefaults = ["default", "default", "default", "auto", "100", "100"]
2045 fontquotes = [True, True, True, True, False, False]
2046
2047 def convert_fontsettings(document):
2048     " Duplicate font settings "
2049
2050     i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2051     if i == -1:
2052         document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2053         use_non_tex_fonts = "false"
2054     else:
2055         use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2056     j = 0
2057     for f in fontsettings:
2058         i = find_token(document.header, f + " ", 0)
2059         if i == -1:
2060             document.warning("Malformed LyX document: No " + f + "!")
2061             # we can fix that
2062             # note that with i = -1, this will insert at the end
2063             # of the header
2064             value = fontdefaults[j]
2065         else:
2066             value = document.header[i][len(f):].strip()
2067         if fontquotes[j]:
2068             if use_non_tex_fonts == "true":
2069                 document.header[i:i+1] = [f + ' "' + fontdefaults[j] + '" "' + value + '"']
2070             else:
2071                 document.header[i:i+1] = [f + ' "' + value + '" "' + fontdefaults[j] + '"']
2072         else:
2073             if use_non_tex_fonts == "true":
2074                 document.header[i:i+1] = [f + ' ' + fontdefaults[j] + ' ' + value]
2075             else:
2076                 document.header[i:i+1] = [f + ' ' + value + ' ' + fontdefaults[j]]
2077         j = j + 1
2078
2079
2080 def revert_fontsettings(document):
2081     " Merge font settings "
2082
2083     i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2084     if i == -1:
2085         document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2086         use_non_tex_fonts = "false"
2087     else:
2088         use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2089     j = 0
2090     for f in fontsettings:
2091         i = find_token(document.header, f + " ", 0)
2092         if i == -1:
2093             document.warning("Malformed LyX document: No " + f + "!")
2094             j = j + 1
2095             continue
2096         line = get_value(document.header, f, i)
2097         if fontquotes[j]:
2098             q1 = line.find('"')
2099             q2 = line.find('"', q1+1)
2100             q3 = line.find('"', q2+1)
2101             q4 = line.find('"', q3+1)
2102             if q1 == -1 or q2 == -1 or q3 == -1 or q4 == -1:
2103                 document.warning("Malformed LyX document: Missing quotes!")
2104                 j = j + 1
2105                 continue
2106             if use_non_tex_fonts == "true":
2107                 document.header[i:i+1] = [f + ' ' + line[q3+1:q4]]
2108             else:
2109                 document.header[i:i+1] = [f + ' ' + line[q1+1:q2]]
2110         else:
2111             if use_non_tex_fonts == "true":
2112                 document.header[i:i+1] = [f + ' ' + line.split()[1]]
2113             else:
2114                 document.header[i:i+1] = [f + ' ' + line.split()[0]]
2115         j = j + 1
2116
2117
2118 def revert_solution(document):
2119     " Reverts the solution environment of the theorem module to TeX code "
2120
2121     # Do we use one of the modules that provides Solution?
2122     have_mod = False
2123     mods = document.get_module_list()
2124     for mod in mods:
2125         if mod == "theorems-std" or mod == "theorems-bytype" \
2126         or mod == "theorems-ams" or mod == "theorems-ams-bytype":
2127             have_mod = True
2128             break
2129     if not have_mod:
2130         return
2131
2132     consecutive = False
2133     is_starred = False
2134     i = 0
2135     while True:
2136         i = find_token(document.body, "\\begin_layout Solution", i)
2137         if i == -1:
2138             return
2139
2140         is_starred = document.body[i].startswith("\\begin_layout Solution*")
2141         if is_starred == True:
2142             LaTeXName = "sol*"
2143             LyXName = "Solution*"
2144             theoremName = "newtheorem*"
2145         else:
2146             LaTeXName = "sol"
2147             LyXName = "Solution"
2148             theoremName = "newtheorem"
2149
2150         j = find_end_of_layout(document.body, i)
2151         if j == -1:
2152             document.warning("Malformed LyX document: Can't find end of " + LyXName + " layout")
2153             i += 1
2154             continue
2155
2156         # if this is not a consecutive env, add start command
2157         begcmd = []
2158         if not consecutive:
2159             begcmd = put_cmd_in_ert("\\begin{%s}" % (LaTeXName))
2160
2161         # has this a consecutive theorem of same type?
2162         consecutive = document.body[j + 2] == "\\begin_layout " + LyXName
2163
2164         # if this is not followed by a consecutive env, add end command
2165         if not consecutive:
2166             document.body[j : j + 1] = put_cmd_in_ert("\\end{%s}" % (LaTeXName)) + ["\\end_layout"]
2167
2168         document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
2169
2170         add_to_preamble(document, "\\theoremstyle{definition}")
2171         if is_starred or mod == "theorems-bytype" or mod == "theorems-ams-bytype":
2172             add_to_preamble(document, "\\%s{%s}{\\protect\\solutionname}" % \
2173                 (theoremName, LaTeXName))
2174         else: # mod == "theorems-std" or mod == "theorems-ams" and not is_starred
2175             add_to_preamble(document, "\\%s{%s}[thm]{\\protect\\solutionname}" % \
2176                 (theoremName, LaTeXName))
2177
2178         add_to_preamble(document, "\\providecommand{\solutionname}{Solution}")
2179         i = j
2180
2181
2182 def revert_verbatim_star(document):
2183     from lyx_2_1 import revert_verbatim
2184     revert_verbatim(document, True)
2185
2186
2187 def convert_save_props(document):
2188     " Add save_transient_properties parameter. "
2189     i = find_token(document.header, '\\begin_header', 0)
2190     if i == -1:
2191         document.warning("Malformed lyx document: Missing '\\begin_header'.")
2192         return
2193     document.header.insert(i + 1, '\\save_transient_properties true')
2194
2195
2196 def revert_save_props(document):
2197     " Remove save_transient_properties parameter. "
2198     i = find_token(document.header, "\\save_transient_properties", 0)
2199     if i == -1:
2200         return
2201     del document.header[i]
2202
2203
2204 def delete_TOG_project_URL(document):
2205     i = 0
2206     j = 0
2207     while True:
2208         # delete the TOG project URL
2209         i = find_token(document.body, "\\begin_layout TOG project URL", i)
2210         if i != -1:
2211             j = find_end_of_layout(document.body, i + 1)
2212         else:
2213             return
2214         if j != -1:
2215             del(document.body[i : j + 1])
2216         else:
2217             document.warning("Malformed LyX document: Can't find end of layout TOG project URL")
2218             return
2219         i += 1
2220
2221
2222 def delete_TOG_video_URL(document):
2223     i = 0
2224     j = 0
2225     while True:
2226         # delete the TOG video URL
2227         i = find_token(document.body, "\\begin_layout TOG video URL", i)
2228         if i != -1:
2229             j = find_end_of_layout(document.body, i)
2230         else:
2231             return
2232         if j != -1:
2233             del(document.body[i : j + 1])
2234         else:
2235             document.warning("Malformed LyX document: Can't find end of layout TOG video URL")
2236             return
2237         i += 1
2238
2239
2240 def delete_TOG_data_URL(document):
2241     i = 0
2242     j = 0
2243     while True:
2244         # delete the TOG video URL
2245         i = find_token(document.body, "\\begin_layout TOG data URL", i)
2246         if i != -1:
2247             j = find_end_of_layout(document.body, i)
2248         else:
2249             return
2250         if j != -1:
2251             del(document.body[i : j + 1])
2252         else:
2253             document.warning("Malformed LyX document: Can't find end of layout TOG data URL")
2254             return
2255         i += 1
2256
2257
2258 def delete_TOG_code_URL(document):
2259     i = 0
2260     j = 0
2261     while True:
2262         # delete the TOG video URL
2263         i = find_token(document.body, "\\begin_layout TOG code URL", i)
2264         if i != -1:
2265             j = find_end_of_layout(document.body, i)
2266         else:
2267             return
2268         if j != -1:
2269             del(document.body[i : j + 1])
2270         else:
2271             document.warning("Malformed LyX document: Can't find end of layout TOG code URL")
2272             return
2273         i += 1
2274
2275
2276 def convert_ACM_siggraph(document):
2277     " Convert to version 0.92 of acmsiggraph. "
2278     if document.textclass != "acmsiggraph":
2279         return
2280     # at first delete the now nonexistent styles since their info is now
2281     # not needed and even unwanted
2282     delete_TOG_project_URL(document)
2283     delete_TOG_video_URL(document)
2284     delete_TOG_data_URL(document)
2285     delete_TOG_code_URL(document)
2286     # now add a note that the user knows that he still has work to do
2287     note = ["\\begin_layout Standard", "\\begin_inset Note Note", "status open", "",
2288               "\\begin_layout Plain Layout", "", "\\series bold",
2289               "\\color red", "Important note:", "\series default",
2290               " This file was converted by \\SpecialChar LyX  to the format of acmsigplan 0.92.",
2291               " This conversion is incomplete because you must add new information about",
2292               " your article.",
2293               " To see what is required, open the \\SpecialChar LyX  template file ",
2294               "\\family sans",
2295               "ACM-siggraph.lyx",
2296               "\\family default",
2297               ".",
2298               "\\end_layout",
2299               "",
2300               "\\end_inset",
2301               "",
2302               "",
2303               "\\end_layout",
2304               ""]
2305     document.body[1:1] = note
2306
2307
2308 def convert_info_tabular_feature(document):
2309     def f(arg):
2310         return arg.replace("inset-modify tabular", "tabular-feature")
2311     convert_info_insets(document, "shortcut(s)?|icon", f)
2312
2313
2314 def revert_info_tabular_feature(document):
2315     def f(arg):
2316         return arg.replace("tabular-feature", "inset-modify tabular")
2317     convert_info_insets(document, "shortcut(s)?|icon", f)
2318
2319
2320 ##
2321 # Conversion hub
2322 #
2323
2324 supported_versions = ["2.2.0", "2.2"]
2325 convert = [
2326            [475, [convert_separator]],
2327            # nothing to do for 476: We consider it a bug that older versions
2328            # did not load amsmath automatically for these commands, and do not
2329            # want to hardcode amsmath off.
2330            [476, []],
2331            [477, []],
2332            [478, []],
2333            [479, []],
2334            [480, []],
2335            [481, [convert_dashes]],
2336            [482, [convert_phrases]],
2337            [483, [convert_specialchar]],
2338            [484, []],
2339            [485, []],
2340            [486, []],
2341            [487, []],
2342            [488, [convert_newgloss]],
2343            [489, [convert_BoxFeatures]],
2344            [490, [convert_origin]],
2345            [491, []],
2346            [492, [convert_colorbox]],
2347            [493, []],
2348            [494, []],
2349            [495, [convert_subref]],
2350            [496, [convert_nounzip]],
2351            [497, [convert_external_bbox]],
2352            [498, []],
2353            [499, [convert_moderncv_phone, convert_moderncv_name]],
2354            [500, []],
2355            [501, [convert_fontsettings]],
2356            [502, []],
2357            [503, []],
2358            [504, [convert_save_props]],
2359            [505, [convert_ACM_siggraph]],
2360            [506, [convert_info_tabular_feature]]
2361           ]
2362
2363 revert =  [
2364            [505, [revert_info_tabular_feature]],
2365            [504, []],
2366            [503, [revert_save_props]],
2367            [502, [revert_verbatim_star]],
2368            [501, [revert_solution]],
2369            [500, [revert_fontsettings]],
2370            [499, [revert_achemso]],
2371            [498, [revert_moderncv_1, revert_moderncv_2]],
2372            [497, [revert_tcolorbox_1, revert_tcolorbox_2,
2373                   revert_tcolorbox_3, revert_tcolorbox_4, revert_tcolorbox_5,
2374                   revert_tcolorbox_6, revert_tcolorbox_7, revert_tcolorbox_8]],
2375            [496, [revert_external_bbox]],
2376            [495, []], # nothing to do since the noUnzip parameter was optional
2377            [494, [revert_subref]],
2378            [493, [revert_jss]],
2379            [492, [revert_mathmulticol]],
2380            [491, [revert_colorbox]],
2381            [490, [revert_textcolor]],
2382            [489, [revert_origin]],
2383            [488, [revert_BoxFeatures]],
2384            [487, [revert_newgloss, revert_glossgroup]],
2385            [486, [revert_forest]],
2386            [485, [revert_ex_itemargs]],
2387            [484, [revert_sigplan_doi]],
2388            [483, [revert_georgian]],
2389            [482, [revert_specialchar]],
2390            [481, [revert_phrases]],
2391            [480, [revert_dashes]],
2392            [479, [revert_question_env]],
2393            [478, [revert_beamer_lemma]],
2394            [477, [revert_xarrow]],
2395            [476, [revert_swissgerman]],
2396            [475, [revert_smash]],
2397            [474, [revert_separator]]
2398           ]
2399
2400
2401 if __name__ == "__main__":
2402     pass