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