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