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