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