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