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