]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_2.py
lyx_2_2.py: fix reversion of colored boxes with special features
[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, "height_special", i)
1092         if i == -1:
1093             return
1094         # read out the values
1095         beg = document.body[i+1].find('"');
1096         end = document.body[i+1].rfind('"');
1097         thickness = document.body[i+1][beg+1:end];
1098         beg = document.body[i+2].find('"');
1099         end = document.body[i+2].rfind('"');
1100         separation = document.body[i+2][beg+1:end];
1101         beg = document.body[i+3].find('"');
1102         end = document.body[i+3].rfind('"');
1103         shadowsize = document.body[i+3][beg+1:end];
1104         # delete the specification
1105         del document.body[i+1:i+4]
1106         # output ERT
1107         # first output the closing brace
1108         if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1109             document.body[i + 10 : i + 10] = put_cmd_in_ert("}")
1110         # now output the lengths
1111         if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1112             document.body[i - 10 : i - 10] = put_cmd_in_ert("{")
1113         if thickness != defaultThick:
1114             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness]
1115         if separation != defaultSep and thickness == defaultThick:
1116             document.body[i - 5 : i - 4] = ["{\\backslash fboxsep " + separation]
1117         if separation != defaultSep and thickness != defaultThick:
1118             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation]
1119         if shadowsize != defaultShadow and separation == defaultSep and thickness == defaultThick:
1120             document.body[i - 5 : i - 4] = ["{\\backslash shadowsize " + shadowsize]
1121         if shadowsize != defaultShadow and separation != defaultSep and thickness == defaultThick:
1122             document.body[i - 5 : i - 4] = ["{\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1123         if shadowsize != defaultShadow and separation == defaultSep and thickness != defaultThick:
1124             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash shadowsize " + shadowsize]
1125         if shadowsize != defaultShadow and separation != defaultSep and thickness != defaultThick:
1126             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1127         i = i + 11
1128
1129
1130 def convert_origin(document):
1131     " Insert the origin tag "
1132
1133     i = find_token(document.header, "\\textclass ", 0)
1134     if i == -1:
1135         document.warning("Malformed LyX document: No \\textclass!!")
1136         return
1137     if document.dir == u'':
1138         origin = u'stdin'
1139     else:
1140         relpath = u''
1141         if document.systemlyxdir and document.systemlyxdir != u'':
1142             try:
1143                 if os.path.isabs(document.dir):
1144                     absdir = os.path.normpath(document.dir)
1145                 else:
1146                     absdir = os.path.normpath(os.path.abspath(document.dir))
1147                 if os.path.isabs(document.systemlyxdir):
1148                     abssys = os.path.normpath(document.systemlyxdir)
1149                 else:
1150                     abssys = os.path.normpath(os.path.abspath(document.systemlyxdir))
1151                 relpath = os.path.relpath(absdir, abssys)
1152                 if relpath.find(u'..') == 0:
1153                     relpath = u''
1154             except:
1155                 relpath = u''
1156         if relpath == u'':
1157             origin = document.dir.replace(u'\\', u'/') + u'/'
1158         else:
1159             origin = os.path.join(u"/systemlyxdir", relpath).replace(u'\\', u'/') + u'/'
1160     document.header[i:i] = ["\\origin " + origin]
1161
1162
1163 def revert_origin(document):
1164     " Remove the origin tag "
1165
1166     i = find_token(document.header, "\\origin ", 0)
1167     if i == -1:
1168         document.warning("Malformed LyX document: No \\origin!!")
1169         return
1170     del document.header[i]
1171
1172
1173 color_names = ["brown", "darkgray", "gray", \
1174                "lightgray", "lime", "olive", "orange", \
1175                "pink", "purple", "teal", "violet"]
1176
1177 def revert_textcolor(document):
1178     " revert new \\textcolor colors to TeX code "
1179
1180     i = 0
1181     j = 0
1182     xcolor = False
1183     while True:
1184         i = find_token(document.body, "\\color ", i)
1185         if i == -1:
1186             return
1187         else:
1188             for color in list(color_names):
1189                 if document.body[i] == "\\color " + color:
1190                     # register that xcolor must be loaded in the preamble
1191                     if xcolor == False:
1192                         xcolor = True
1193                         add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"])
1194                     # find the next \\color and/or the next \\end_layout
1195                     j = find_token(document.body, "\\color", i + 1)
1196                     k = find_token(document.body, "\\end_layout", i + 1)
1197                     if j == -1 and k != -1:
1198                         j = k +1
1199                     # output TeX code
1200                     # first output the closing brace
1201                     if k < j:
1202                         document.body[k: k] = put_cmd_in_ert("}")
1203                     else:
1204                         document.body[j: j] = put_cmd_in_ert("}")
1205                     # now output the \textcolor command
1206                     document.body[i : i + 1] = put_cmd_in_ert("\\textcolor{" + color + "}{")
1207         i = i + 1
1208
1209
1210 def convert_colorbox(document):
1211     " adds color settings for boxes "
1212
1213     i = 0
1214     while True:
1215         i = find_token(document.body, "shadowsize", i)
1216         if i == -1:
1217             return
1218         document.body[i+1:i+1] = ['framecolor "black"', 'backgroundcolor "none"']
1219         i = i + 3
1220
1221
1222 def revert_colorbox(document):
1223     " outputs color settings for boxes as TeX code "
1224
1225     binset = 0
1226     defaultframecolor = "black"
1227     defaultbackcolor = "none"
1228     while True:
1229         binset = find_token(document.body, "\\begin_inset Box", binset)
1230         if binset == -1:
1231             return
1232
1233         einset = find_end_of_inset(document.body, binset)
1234         if einset == -1:
1235             document.warning("Malformed LyX document: Can't find end of box inset!")
1236             binset += 1
1237             continue
1238
1239         blay = find_token(document.body, "\\begin_layout", binset, einset)
1240         if blay == -1:
1241             document.warning("Malformed LyX document: Can't find start of layout!")
1242             binset = einset
1243             continue
1244
1245         # doing it this way, we make sure only to find a framecolor option
1246         frame = find_token(document.body, "framecolor", binset, blay)
1247         if frame == -1:
1248             binset = einset
1249             continue
1250
1251         beg = document.body[frame].find('"')
1252         end = document.body[frame].rfind('"')
1253         framecolor = document.body[frame][beg + 1 : end]
1254
1255         # this should be on the next line
1256         bgcolor = frame + 1
1257         beg = document.body[bgcolor].find('"')
1258         end = document.body[bgcolor].rfind('"')
1259         backcolor = document.body[bgcolor][beg + 1 : end]
1260
1261         # delete those bits
1262         del document.body[frame : frame + 2]
1263         # adjust end of inset
1264         einset -= 2
1265
1266         if document.body[binset] == "\\begin_inset Box Boxed" and \
1267             framecolor != defaultframecolor:
1268           document.body[binset] = "\\begin_inset Box Frameless"
1269
1270         # output TeX code
1271         # first output the closing brace
1272         if framecolor == defaultframecolor and backcolor == defaultbackcolor:
1273             # nothing needed
1274             pass
1275         else:
1276             # we also neeed to load xcolor in the preamble but only once
1277             add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"])
1278             document.body[einset + 2 : einset + 2] = put_cmd_in_ert("}")
1279             if framecolor != defaultframecolor:
1280                 document.body[binset:binset] = put_cmd_in_ert("\\fcolorbox{" + framecolor + "}{" + backcolor + "}{")
1281             else:
1282               document.body[binset:binset] = put_cmd_in_ert("\\colorbox{" + backcolor + "}{")
1283
1284         binset = einset
1285
1286
1287 def revert_mathmulticol(document):
1288     " Convert formulas to ERT if they contain multicolumns "
1289
1290     i = 0
1291     while True:
1292         i = find_token(document.body, '\\begin_inset Formula', i)
1293         if i == -1:
1294             return
1295         j = find_end_of_inset(document.body, i)
1296         if j == -1:
1297             document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
1298             i += 1
1299             continue
1300         lines = document.body[i:j]
1301         lines[0] = lines[0].replace('\\begin_inset Formula', '').lstrip()
1302         code = "\n".join(lines)
1303         converted = False
1304         k = 0
1305         n = 0
1306         while n >= 0:
1307             n = code.find("\\multicolumn", k)
1308             # no need to convert degenerated multicolumn cells,
1309             # they work in old LyX versions as "math ERT"
1310             if n != -1 and code.find("\\multicolumn{1}", k) != n:
1311                 ert = put_cmd_in_ert(code)
1312                 document.body[i:j+1] = ert
1313                 converted = True
1314                 break
1315             else:
1316                 k = n + 12
1317         if converted:
1318             i = find_end_of_inset(document.body, i)
1319         else:
1320             i = j
1321
1322
1323 def revert_jss(document):
1324     " Reverts JSS In_Preamble commands to ERT in preamble "
1325
1326     if document.textclass != "jss":
1327         return
1328
1329     h = 0
1330     m = 0
1331     j = 0
1332     k = 0
1333     n = 0
1334     while True:
1335       # at first revert the inset layouts because they can be part of the In_Preamble layouts
1336       while m != -1 or j != -1 or h != -1 or k != -1 or n != -1:
1337         # \pkg
1338         if h != -1:
1339           h = find_token(document.body, "\\begin_inset Flex Pkg", h)
1340         if h != -1:
1341           endh = find_end_of_inset(document.body, h)
1342           document.body[endh - 2 : endh + 1] = put_cmd_in_ert("}")
1343           document.body[h : h + 4] = put_cmd_in_ert("\\pkg{")
1344           h = h + 5
1345         # \proglang
1346         if m != -1:
1347           m = find_token(document.body, "\\begin_inset Flex Proglang", m)
1348         if m != -1:
1349           endm = find_end_of_inset(document.body, m)
1350           document.body[endm - 2 : endm + 1] = put_cmd_in_ert("}")
1351           document.body[m : m + 4] = put_cmd_in_ert("\\proglang{")
1352           m = m + 5
1353         # \code
1354         if j != -1:
1355           j = find_token(document.body, "\\begin_inset Flex Code", j)
1356         if j != -1:
1357           # assure that we are not in a Code Chunk inset
1358           if document.body[j][-1] == "e":
1359               endj = find_end_of_inset(document.body, j)
1360               document.body[endj - 2 : endj + 1] = put_cmd_in_ert("}")
1361               document.body[j : j + 4] = put_cmd_in_ert("\\code{")
1362               j = j + 5
1363           else:
1364               j = j + 1
1365         # \email
1366         if k != -1:
1367           k = find_token(document.body, "\\begin_inset Flex E-mail", k)
1368         if k != -1:
1369           endk = find_end_of_inset(document.body, k)
1370           document.body[endk - 2 : endk + 1] = put_cmd_in_ert("}")
1371           document.body[k : k + 4] = put_cmd_in_ert("\\email{")
1372           k = k + 5
1373         # \url
1374         if n != -1:
1375           n = find_token(document.body, "\\begin_inset Flex URL", n)
1376         if n != -1:
1377           endn = find_end_of_inset(document.body, n)
1378           document.body[endn - 2 : endn + 1] = put_cmd_in_ert("}")
1379           document.body[n : n + 4] = put_cmd_in_ert("\\url{")
1380           n = n + 5
1381       # now revert the In_Preamble layouts
1382       # \title
1383       i = find_token(document.body, "\\begin_layout Title", 0)
1384       if i == -1:
1385         return
1386       j = find_end_of_layout(document.body, i)
1387       if j == -1:
1388         document.warning("Malformed LyX document: Can't find end of Title layout")
1389         i += 1
1390         continue
1391       content = lyx2latex(document, document.body[i:j + 1])
1392       add_to_preamble(document, ["\\title{" + content + "}"])
1393       del document.body[i:j + 1]
1394       # \author
1395       i = find_token(document.body, "\\begin_layout Author", 0)
1396       if i == -1:
1397         return
1398       j = find_end_of_layout(document.body, i)
1399       if j == -1:
1400         document.warning("Malformed LyX document: Can't find end of Author layout")
1401         i += 1
1402         continue
1403       content = lyx2latex(document, document.body[i:j + 1])
1404       add_to_preamble(document, ["\\author{" + content + "}"])
1405       del document.body[i:j + 1]
1406       # \Plainauthor
1407       i = find_token(document.body, "\\begin_layout Plain Author", 0)
1408       if i == -1:
1409         return
1410       j = find_end_of_layout(document.body, i)
1411       if j == -1:
1412         document.warning("Malformed LyX document: Can't find end of Plain Author layout")
1413         i += 1
1414         continue
1415       content = lyx2latex(document, document.body[i:j + 1])
1416       add_to_preamble(document, ["\\Plainauthor{" + content + "}"])
1417       del document.body[i:j + 1]
1418       # \Plaintitle
1419       i = find_token(document.body, "\\begin_layout Plain Title", 0)
1420       if i == -1:
1421         return
1422       j = find_end_of_layout(document.body, i)
1423       if j == -1:
1424         document.warning("Malformed LyX document: Can't find end of Plain Title layout")
1425         i += 1
1426         continue
1427       content = lyx2latex(document, document.body[i:j + 1])
1428       add_to_preamble(document, ["\\Plaintitle{" + content + "}"])
1429       del document.body[i:j + 1]
1430       # \Shorttitle
1431       i = find_token(document.body, "\\begin_layout Short Title", 0)
1432       if i == -1:
1433         return
1434       j = find_end_of_layout(document.body, i)
1435       if j == -1:
1436         document.warning("Malformed LyX document: Can't find end of Short Title layout")
1437         i += 1
1438         continue
1439       content = lyx2latex(document, document.body[i:j + 1])
1440       add_to_preamble(document, ["\\Shorttitle{" + content + "}"])
1441       del document.body[i:j + 1]
1442       # \Abstract
1443       i = find_token(document.body, "\\begin_layout Abstract", 0)
1444       if i == -1:
1445         return
1446       j = find_end_of_layout(document.body, i)
1447       if j == -1:
1448         document.warning("Malformed LyX document: Can't find end of Abstract layout")
1449         i += 1
1450         continue
1451       content = lyx2latex(document, document.body[i:j + 1])
1452       add_to_preamble(document, ["\\Abstract{" + content + "}"])
1453       del document.body[i:j + 1]
1454       # \Keywords
1455       i = find_token(document.body, "\\begin_layout Keywords", 0)
1456       if i == -1:
1457         return
1458       j = find_end_of_layout(document.body, i)
1459       if j == -1:
1460         document.warning("Malformed LyX document: Can't find end of Keywords layout")
1461         i += 1
1462         continue
1463       content = lyx2latex(document, document.body[i:j + 1])
1464       add_to_preamble(document, ["\\Keywords{" + content + "}"])
1465       del document.body[i:j + 1]
1466       # \Plainkeywords
1467       i = find_token(document.body, "\\begin_layout Plain Keywords", 0)
1468       if i == -1:
1469         return
1470       j = find_end_of_layout(document.body, i)
1471       if j == -1:
1472         document.warning("Malformed LyX document: Can't find end of Plain Keywords layout")
1473         i += 1
1474         continue
1475       content = lyx2latex(document, document.body[i:j + 1])
1476       add_to_preamble(document, ["\\Plainkeywords{" + content + "}"])
1477       del document.body[i:j + 1]
1478       # \Address
1479       i = find_token(document.body, "\\begin_layout Address", 0)
1480       if i == -1:
1481         return
1482       j = find_end_of_layout(document.body, i)
1483       if j == -1:
1484         document.warning("Malformed LyX document: Can't find end of Address layout")
1485         i += 1
1486         continue
1487       content = lyx2latex(document, document.body[i:j + 1])
1488       add_to_preamble(document, ["\\Address{" + content + "}"])
1489       del document.body[i:j + 1]
1490       # finally handle the code layouts
1491       h = 0
1492       m = 0
1493       j = 0
1494       k = 0
1495       while m != -1 or j != -1 or h != -1 or k != -1:
1496         # \CodeChunk
1497         if h != -1:
1498           h = find_token(document.body, "\\begin_inset Flex Code Chunk", h)
1499         if h != -1:
1500           endh = find_end_of_inset(document.body, h)
1501           document.body[endh : endh + 1] = put_cmd_in_ert("\\end{CodeChunk}")
1502           document.body[h : h + 3] = put_cmd_in_ert("\\begin{CodeChunk}")
1503           document.body[h - 1 : h] = ["\\begin_layout Standard"]
1504           h = h + 1
1505         # \CodeInput
1506         if j != -1:
1507           j = find_token(document.body, "\\begin_layout Code Input", j)
1508         if j != -1:
1509           endj = find_end_of_layout(document.body, j)
1510           document.body[endj : endj + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1511           document.body[endj + 3 : endj + 4] = put_cmd_in_ert("\\end{CodeInput}")
1512           document.body[endj + 13 : endj + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1513           document.body[j + 1 : j] = ["\\end_layout", "", "\\begin_layout Standard"]
1514           document.body[j : j + 1] = put_cmd_in_ert("\\begin{CodeInput}")
1515           j = j + 1
1516         # \CodeOutput
1517         if k != -1:
1518           k = find_token(document.body, "\\begin_layout Code Output", k)
1519         if k != -1:
1520           endk = find_end_of_layout(document.body, k)
1521           document.body[endk : endk + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1522           document.body[endk + 3 : endk + 4] = put_cmd_in_ert("\\end{CodeOutput}")
1523           document.body[endk + 13 : endk + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1524           document.body[k + 1 : k] = ["\\end_layout", "", "\\begin_layout Standard"]
1525           document.body[k : k + 1] = put_cmd_in_ert("\\begin{CodeOutput}")
1526           k = k + 1
1527         # \Code
1528         if m != -1:
1529           m = find_token(document.body, "\\begin_layout Code", m)
1530         if m != -1:
1531           endm = find_end_of_layout(document.body, m)
1532           document.body[endm : endm + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1533           document.body[endm + 3 : endm + 4] = put_cmd_in_ert("\\end{Code}")
1534           document.body[endm + 13 : endm + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1535           document.body[m + 1 : m] = ["\\end_layout", "", "\\begin_layout Standard"]
1536           document.body[m : m + 1] = put_cmd_in_ert("\\begin{Code}")
1537           m = m + 1
1538
1539
1540 def convert_subref(document):
1541     " converts sub: ref prefixes to subref: "
1542
1543     # 1) label insets
1544     rx = re.compile(r'^name \"sub:(.+)$')
1545     i = 0
1546     while True:
1547         i = find_token(document.body, "\\begin_inset CommandInset label", i)
1548         if i == -1:
1549             break
1550         j = find_end_of_inset(document.body, i)
1551         if j == -1:
1552             document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1553             i += 1
1554             continue
1555
1556         for p in range(i, j):
1557             m = rx.match(document.body[p])
1558             if m:
1559                 label = m.group(1)
1560                 document.body[p] = "name \"subsec:" + label
1561         i += 1
1562
1563     # 2) xref insets
1564     rx = re.compile(r'^reference \"sub:(.+)$')
1565     i = 0
1566     while True:
1567         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1568         if i == -1:
1569             return
1570         j = find_end_of_inset(document.body, i)
1571         if j == -1:
1572             document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1573             i += 1
1574             continue
1575
1576         for p in range(i, j):
1577             m = rx.match(document.body[p])
1578             if m:
1579                 label = m.group(1)
1580                 document.body[p] = "reference \"subsec:" + label
1581                 break
1582         i += 1
1583
1584
1585
1586 def revert_subref(document):
1587     " reverts subref: ref prefixes to sub: "
1588
1589     # 1) label insets
1590     rx = re.compile(r'^name \"subsec:(.+)$')
1591     i = 0
1592     while True:
1593         i = find_token(document.body, "\\begin_inset CommandInset label", i)
1594         if i == -1:
1595             break
1596         j = find_end_of_inset(document.body, i)
1597         if j == -1:
1598             document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1599             i += 1
1600             continue
1601
1602         for p in range(i, j):
1603             m = rx.match(document.body[p])
1604             if m:
1605                 label = m.group(1)
1606                 document.body[p] = "name \"sub:" + label
1607                 break
1608         i += 1
1609
1610     # 2) xref insets
1611     rx = re.compile(r'^reference \"subsec:(.+)$')
1612     i = 0
1613     while True:
1614         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1615         if i == -1:
1616             return
1617         j = find_end_of_inset(document.body, i)
1618         if j == -1:
1619             document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1620             i += 1
1621             continue
1622
1623         for p in range(i, j):
1624             m = rx.match(document.body[p])
1625             if m:
1626                 label = m.group(1)
1627                 document.body[p] = "reference \"sub:" + label
1628                 break
1629         i += 1
1630
1631
1632 def convert_nounzip(document):
1633     " remove the noUnzip parameter of graphics insets "
1634
1635     rx = re.compile(r'\s*noUnzip\s*$')
1636     i = 0
1637     while True:
1638         i = find_token(document.body, "\\begin_inset Graphics", i)
1639         if i == -1:
1640             break
1641         j = find_end_of_inset(document.body, i)
1642         if j == -1:
1643             document.warning("Malformed LyX document: Can't find end of graphics inset at line " + str(i))
1644             i += 1
1645             continue
1646
1647         k = find_re(document.body, rx, i, j)
1648         if k != -1:
1649           del document.body[k]
1650           j = j - 1
1651         i = j + 1
1652
1653
1654 def convert_revert_external_bbox(document, forward):
1655     " add units to bounding box of external insets "
1656
1657     rx = re.compile(r'^\s*boundingBox\s+\S+\s+\S+\s+\S+\s+\S+\s*$')
1658     i = 0
1659     while True:
1660         i = find_token(document.body, "\\begin_inset External", i)
1661         if i == -1:
1662             break
1663         j = find_end_of_inset(document.body, i)
1664         if j == -1:
1665             document.warning("Malformed LyX document: Can't find end of external inset at line " + str(i))
1666             i += 1
1667             continue
1668         k = find_re(document.body, rx, i, j)
1669         if k == -1:
1670             i = j + 1
1671             continue
1672         tokens = document.body[k].split()
1673         if forward:
1674             for t in range(1, 5):
1675                 tokens[t] += "bp"
1676         else:
1677             for t in range(1, 5):
1678                 tokens[t] = length_in_bp(tokens[t])
1679         document.body[k] = "\tboundingBox " + tokens[1] + " " + tokens[2] + " " + \
1680                            tokens[3] + " " + tokens[4]
1681         i = j + 1
1682
1683
1684 def convert_external_bbox(document):
1685     convert_revert_external_bbox(document, True)
1686
1687
1688 def revert_external_bbox(document):
1689     convert_revert_external_bbox(document, False)
1690
1691
1692 def revert_tcolorbox_1(document):
1693   " Reverts the Flex:Subtitle inset of tcolorbox to TeX-code "
1694   i = -1
1695   while True:
1696     i = find_token(document.header, "tcolorbox", i)
1697     if i == -1:
1698       break
1699     else:
1700       flex = 0
1701       flexEnd = -1
1702       flex = find_token(document.body, "\\begin_inset Flex Subtitle", flex)
1703       if flex == -1:
1704         return flexEnd
1705       flexEnd = find_end_of_inset(document.body, flex)
1706       wasOpt = revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, False, True, False)
1707       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, False, False, False)
1708       flexEnd = find_end_of_inset(document.body, flex)
1709       if wasOpt == True:
1710         document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\tcbsubtitle")
1711       else:
1712         document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\tcbsubtitle{")
1713       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("}")
1714       flex += 1
1715
1716
1717 def revert_tcolorbox_2(document):
1718   " Reverts the Flex:Raster_Color_Box inset of tcolorbox to TeX-code "
1719   i = -1
1720   while True:
1721     i = find_token(document.header, "tcolorbox", i)
1722     if i == -1:
1723       break
1724     else:
1725       flex = 0
1726       flexEnd = -1
1727       flex = find_token(document.body, "\\begin_inset Flex Raster Color Box", flex)
1728       if flex == -1:
1729         return flexEnd
1730       flexEnd = find_end_of_inset(document.body, flex)
1731       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1732       flexEnd = find_end_of_inset(document.body, flex)
1733       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{tcbraster}")
1734       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("\\end{tcbraster}")
1735       flex += 1
1736
1737
1738 def revert_tcolorbox_3(document):
1739   " Reverts the Flex:Custom_Color_Box_1 inset of tcolorbox to TeX-code "
1740   i = -1
1741   while True:
1742     i = find_token(document.header, "tcolorbox", i)
1743     if i == -1:
1744       break
1745     else:
1746       flex = 0
1747       flexEnd = -1
1748       flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 1", flex)
1749       if flex == -1:
1750         return flexEnd
1751       flexEnd = find_end_of_inset(document.body, flex)
1752       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1753       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1754       flexEnd = find_end_of_inset(document.body, flex)
1755       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxA}")
1756       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxA}")
1757       flex += 1
1758
1759
1760 def revert_tcolorbox_4(document):
1761   " Reverts the Flex:Custom_Color_Box_2 inset of tcolorbox to TeX-code "
1762   i = -1
1763   while True:
1764     i = find_token(document.header, "tcolorbox", i)
1765     if i == -1:
1766       break
1767     else:
1768       flex = 0
1769       flexEnd = -1
1770       flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 2", flex)
1771       if flex == -1:
1772         return flexEnd
1773       flexEnd = find_end_of_inset(document.body, flex)
1774       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1775       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1776       flexEnd = find_end_of_inset(document.body, flex)
1777       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxB}")
1778       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxB}")
1779       flex += 1
1780
1781
1782 def revert_tcolorbox_5(document):
1783   " Reverts the Flex:Custom_Color_Box_3 inset of tcolorbox to TeX-code "
1784   i = -1
1785   while True:
1786     i = find_token(document.header, "tcolorbox", i)
1787     if i == -1:
1788       break
1789     else:
1790       flex = 0
1791       flexEnd = -1
1792       flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 3", flex)
1793       if flex == -1:
1794         return flexEnd
1795       flexEnd = find_end_of_inset(document.body, flex)
1796       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1797       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1798       flexEnd = find_end_of_inset(document.body, flex)
1799       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxC}")
1800       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxC}")
1801       flex += 1
1802
1803
1804 def revert_tcolorbox_6(document):
1805   " Reverts the Flex:Custom_Color_Box_4 inset of tcolorbox to TeX-code "
1806   i = -1
1807   while True:
1808     i = find_token(document.header, "tcolorbox", i)
1809     if i == -1:
1810       break
1811     else:
1812       flex = 0
1813       flexEnd = -1
1814       flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 4", flex)
1815       if flex == -1:
1816         return flexEnd
1817       flexEnd = find_end_of_inset(document.body, flex)
1818       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1819       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1820       flexEnd = find_end_of_inset(document.body, flex)
1821       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxD}")
1822       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxD}")
1823       flex += 1
1824
1825
1826 def revert_tcolorbox_7(document):
1827   " Reverts the Flex:Custom_Color_Box_5 inset of tcolorbox to TeX-code "
1828   i = -1
1829   while True:
1830     i = find_token(document.header, "tcolorbox", i)
1831     if i == -1:
1832       break
1833     else:
1834       flex = 0
1835       flexEnd = -1
1836       flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 5", flex)
1837       if flex == -1:
1838         return flexEnd
1839       flexEnd = find_end_of_inset(document.body, flex)
1840       revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
1841       revert_Argument_to_TeX_brace(document, flex, 0, 2, 2, True, False, False)
1842       flexEnd = find_end_of_inset(document.body, flex)
1843       document.body[flex + 0 : flex + 4] = put_cmd_in_ert("\\begin{cBoxE}")
1844       document.body[flexEnd + 4 : flexEnd + 7] = put_cmd_in_ert("{}\\end{cBoxE}")
1845       flex += 1
1846
1847
1848 def revert_tcolorbox_8(document):
1849   " Reverts the layout New Color Box Type of tcolorbox to TeX-code "
1850   i = 0
1851   j = 0
1852   k = 0
1853   while True:
1854     if i != -1:
1855       i = find_token(document.body, "\\begin_layout New Color Box Type", i)
1856     if i != -1:
1857       j = find_end_of_layout(document.body, i)
1858       wasOpt = revert_Argument_to_TeX_brace(document, i, j, 1, 1, False, True, False)
1859       revert_Argument_to_TeX_brace(document, i, 0, 2, 2, False, False, True)
1860       revert_Argument_to_TeX_brace(document, i, 0, 3, 4, False, True, False)
1861       document.body[i] = document.body[i].replace("\\begin_layout New Color Box Type", "\\begin_layout Standard")
1862       if wasOpt == True:
1863         document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox")
1864       else:
1865         document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox{")
1866       k = find_end_of_inset(document.body, j)
1867       k = find_token(document.body, "\\end_inset", k + 1)
1868       k = find_token(document.body, "\\end_inset", k + 1)
1869       if wasOpt == True:
1870         k = find_token(document.body, "\\end_inset", k + 1)
1871       document.body[k + 2 : j + 2] = put_cmd_in_ert("{")
1872       j = find_token(document.body, "\\begin_layout Standard", j + 1)
1873       document.body[j - 2 : j - 2] = put_cmd_in_ert("}")
1874       i += 1
1875     if i == -1:
1876       return
1877
1878
1879 def revert_moderncv_1(document):
1880   " Reverts the new inset of moderncv to TeX-code in preamble "
1881
1882   if document.textclass != "moderncv":
1883     return
1884   i = 0
1885   j = 0
1886   lineArg = 0
1887   while True:
1888     # at first revert the new styles
1889     # \moderncvicons
1890     i = find_token(document.body, "\\begin_layout CVIcons", 0)
1891     if i == -1:
1892       return
1893     j = find_end_of_layout(document.body, i)
1894     if j == -1:
1895       document.warning("Malformed LyX document: Can't find end of CVIcons layout")
1896       i += 1
1897       continue
1898     content = lyx2latex(document, document.body[i:j + 1])
1899     add_to_preamble(document, ["\\moderncvicons{" + content + "}"])
1900     del document.body[i:j + 1]
1901     # \hintscolumnwidth
1902     i = find_token(document.body, "\\begin_layout CVColumnWidth", 0)
1903     if i == -1:
1904       return
1905     j = find_end_of_layout(document.body, i)
1906     if j == -1:
1907       document.warning("Malformed LyX document: Can't find end of CVColumnWidth layout")
1908       i += 1
1909       continue
1910     content = lyx2latex(document, document.body[i:j + 1])
1911     add_to_preamble(document, ["\\setlength{\hintscolumnwidth}{" + content + "}"])
1912     del document.body[i:j + 1]
1913     # now change the new styles to the obsolete ones
1914     # \name
1915     i = find_token(document.body, "\\begin_layout Name", 0)
1916     if i == -1:
1917       return
1918     j = find_end_of_layout(document.body, i)
1919     if j == -1:
1920       document.warning("Malformed LyX document: Can't find end of Name layout")
1921       i += 1
1922       continue
1923     lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
1924     if lineArg > j and j != 0:
1925       return
1926     if lineArg != -1:
1927       beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
1928       # we have to assure that no other inset is in the Argument
1929       beginInset = find_token(document.body, "\\begin_inset", beginPlain)
1930       endInset = find_token(document.body, "\\end_inset", beginPlain)
1931       k = beginPlain + 1
1932       l = k
1933       while beginInset < endInset and beginInset != -1:
1934         beginInset = find_token(document.body, "\\begin_inset", k)
1935         endInset = find_token(document.body, "\\end_inset", l)
1936         k = beginInset + 1
1937         l = endInset + 1
1938       Arg2 = document.body[l + 5 : l + 6]
1939       # rename the style
1940       document.body[i : i + 1]= ["\\begin_layout FirstName"]
1941       # delete the Argument inset
1942       del( document.body[endInset - 2 : endInset + 3])
1943       del( document.body[lineArg : beginPlain + 1])
1944       document.body[i + 4 : i + 4]= ["\\begin_layout FamilyName"] + Arg2 + ["\\end_layout"] + [""]
1945
1946
1947 def revert_moderncv_2(document):
1948   " Reverts the phone inset of moderncv to the obsoleted mobile or fax "
1949
1950   if document.textclass != "moderncv":
1951     return
1952   i = 0
1953   j = 0
1954   lineArg = 0
1955   while True:
1956     # \phone
1957     i = find_token(document.body, "\\begin_layout Phone", i)
1958     if i == -1:
1959       return
1960     j = find_end_of_layout(document.body, i)
1961     if j == -1:
1962       document.warning("Malformed LyX document: Can't find end of Phone layout")
1963       i += 1
1964       return
1965     lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
1966     if lineArg > j and j != 0:
1967       i += 1
1968       continue
1969     if lineArg != -1:
1970       beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
1971       # we have to assure that no other inset is in the Argument
1972       beginInset = find_token(document.body, "\\begin_inset", beginPlain)
1973       endInset = find_token(document.body, "\\end_inset", beginPlain)
1974       k = beginPlain + 1
1975       l = k
1976       while beginInset < endInset and beginInset != -1:
1977         beginInset = find_token(document.body, "\\begin_inset", k)
1978         endInset = find_token(document.body, "\\end_inset", l)
1979         k = beginInset + 1
1980         l = endInset + 1
1981       Arg = document.body[beginPlain + 1 : beginPlain + 2]
1982       # rename the style
1983       if Arg[0] == "mobile":
1984         document.body[i : i + 1]= ["\\begin_layout Mobile"]
1985       if Arg[0] == "fax":
1986         document.body[i : i + 1]= ["\\begin_layout Fax"]
1987       # delete the Argument inset
1988       del(document.body[endInset - 2 : endInset + 1])
1989       del(document.body[lineArg : beginPlain + 3])
1990     i += 1
1991
1992
1993 def convert_moderncv_phone(document):
1994     " Convert the Fax and Mobile inset of moderncv to the new phone inset "
1995
1996     if document.textclass != "moderncv":
1997         return
1998     i = 0
1999     j = 0
2000     lineArg = 0
2001
2002     phone_dict = {
2003         "Mobile" : "mobile",
2004         "Fax" : "fax",
2005         }
2006
2007     rx = re.compile(r'^\\begin_layout (\S+)$')
2008     while True:
2009         # substitute \fax and \mobile by \phone[fax] and \phone[mobile], respectively
2010         i = find_token(document.body, "\\begin_layout", i)
2011         if i == -1:
2012             return
2013
2014         m = rx.match(document.body[i])
2015         val = ""
2016         if m:
2017             val = m.group(1)
2018         if val not in list(phone_dict.keys()):
2019             i += 1
2020             continue
2021         j = find_end_of_layout(document.body, i)
2022         if j == -1:
2023             document.warning("Malformed LyX document: Can't find end of Mobile layout")
2024             i += 1
2025             return
2026
2027         document.body[i : i + 1] = ["\\begin_layout Phone", "\\begin_inset Argument 1", "status open", "",
2028                             "\\begin_layout Plain Layout", phone_dict[val], "\\end_layout", "",
2029                             "\\end_inset", ""]
2030
2031
2032 def convert_moderncv_name(document):
2033     " Convert the FirstName and LastName layout of moderncv to the general Name layout "
2034
2035     if document.textclass != "moderncv":
2036         return
2037
2038     fnb = 0 # Begin of FirstName inset
2039     fne = 0 # End of FirstName inset
2040     lnb = 0 # Begin of LastName (FamilyName) inset
2041     lne = 0 # End of LastName (FamilyName) inset
2042     nb = 0 # Begin of substituting Name inset
2043     ne = 0 # End of substituting Name inset
2044     FirstName = [] # FirstName content
2045     FamilyName = [] # LastName content
2046
2047     while True:
2048         # locate FirstName
2049         fnb = find_token(document.body, "\\begin_layout FirstName", fnb)
2050         if fnb != -1:
2051             fne = find_end_of_layout(document.body, fnb)
2052             if fne == -1:
2053                 document.warning("Malformed LyX document: Can't find end of FirstName layout")
2054                 return
2055             FirstName = document.body[fnb + 1 : fne]
2056         # locate FamilyName
2057         lnb = find_token(document.body, "\\begin_layout FamilyName", lnb)
2058         if lnb != -1:
2059             lne = find_end_of_layout(document.body, lnb)
2060             if lne == -1:
2061                 document.warning("Malformed LyX document: Can't find end of FamilyName layout")
2062                 return
2063             FamilyName = document.body[lnb + 1 : lne]
2064         # Determine the region for the substituting Name layout
2065         if fnb == -1 and lnb == -1: # Neither FirstName nor FamilyName exists -> Do nothing
2066             return
2067         elif fnb == -1: # Only FamilyName exists -> New Name insets replaces that
2068             nb = lnb
2069             ne = lne
2070         elif lnb == -1: # Only FirstName exists -> New Name insets replaces that
2071             nb = fnb
2072             ne = fne
2073         elif fne > lne: # FirstName position before FamilyName -> New Name insets spans
2074             nb = lnb #     from FamilyName begin
2075             ne = fne #     to FirstName end
2076         else: #           FirstName position before FamilyName -> New Name insets spans
2077             nb = fnb #     from FirstName begin
2078             ne = lne #     to FamilyName end
2079
2080         # Insert the substituting layout now. If FirstName exists, use an otpional argument.
2081         if FirstName == []:
2082             document.body[nb : ne + 1] = ["\\begin_layout Name"] + FamilyName + ["\\end_layout", ""]
2083         else:
2084             document.body[nb : ne + 1] = ["\\begin_layout Name", "\\begin_inset Argument 1", "status open", "",
2085                                 "\\begin_layout Plain Layout"] + FirstName + ["\\end_layout", "",
2086                                 "\\end_inset", ""] + FamilyName + ["\\end_layout", ""]
2087
2088
2089 def revert_achemso(document):
2090   " Reverts the flex inset Latin to TeX code "
2091
2092   if document.textclass != "achemso":
2093     return
2094   i = 0
2095   j = 0
2096   while True:
2097     i = find_token(document.body, "\\begin_inset Flex Latin", i)
2098     if i != -1:
2099       j = find_end_of_inset(document.body, i)
2100     else:
2101       return
2102     if j != -1:
2103       beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2104       endPlain = find_end_of_layout(document.body, beginPlain)
2105       content = lyx2latex(document, document.body[beginPlain : endPlain])
2106       document.body[i:j + 1] = put_cmd_in_ert("\\latin{" + content + "}")
2107     else:
2108       document.warning("Malformed LyX document: Can't find end of flex inset Latin")
2109       return
2110     i += 1
2111
2112
2113 fontsettings = ["\\font_roman", "\\font_sans", "\\font_typewriter", "\\font_math", \
2114                 "\\font_sf_scale", "\\font_tt_scale"]
2115 fontdefaults = ["default", "default", "default", "auto", "100", "100"]
2116 fontquotes = [True, True, True, True, False, False]
2117
2118 def convert_fontsettings(document):
2119     " Duplicate font settings "
2120
2121     i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2122     if i == -1:
2123         document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2124         use_non_tex_fonts = "false"
2125     else:
2126         use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2127     j = 0
2128     for f in fontsettings:
2129         i = find_token(document.header, f + " ", 0)
2130         if i == -1:
2131             document.warning("Malformed LyX document: No " + f + "!")
2132             # we can fix that
2133             # note that with i = -1, this will insert at the end
2134             # of the header
2135             value = fontdefaults[j]
2136         else:
2137             value = document.header[i][len(f):].strip()
2138         if fontquotes[j]:
2139             if use_non_tex_fonts == "true":
2140                 document.header[i:i+1] = [f + ' "' + fontdefaults[j] + '" "' + value + '"']
2141             else:
2142                 document.header[i:i+1] = [f + ' "' + value + '" "' + fontdefaults[j] + '"']
2143         else:
2144             if use_non_tex_fonts == "true":
2145                 document.header[i:i+1] = [f + ' ' + fontdefaults[j] + ' ' + value]
2146             else:
2147                 document.header[i:i+1] = [f + ' ' + value + ' ' + fontdefaults[j]]
2148         j = j + 1
2149
2150
2151 def revert_fontsettings(document):
2152     " Merge font settings "
2153
2154     i = find_token(document.header, "\\use_non_tex_fonts ", 0)
2155     if i == -1:
2156         document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
2157         use_non_tex_fonts = "false"
2158     else:
2159         use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
2160     j = 0
2161     for f in fontsettings:
2162         i = find_token(document.header, f + " ", 0)
2163         if i == -1:
2164             document.warning("Malformed LyX document: No " + f + "!")
2165             j = j + 1
2166             continue
2167         line = get_value(document.header, f, i)
2168         if fontquotes[j]:
2169             q1 = line.find('"')
2170             q2 = line.find('"', q1+1)
2171             q3 = line.find('"', q2+1)
2172             q4 = line.find('"', q3+1)
2173             if q1 == -1 or q2 == -1 or q3 == -1 or q4 == -1:
2174                 document.warning("Malformed LyX document: Missing quotes!")
2175                 j = j + 1
2176                 continue
2177             if use_non_tex_fonts == "true":
2178                 document.header[i:i+1] = [f + ' ' + line[q3+1:q4]]
2179             else:
2180                 document.header[i:i+1] = [f + ' ' + line[q1+1:q2]]
2181         else:
2182             if use_non_tex_fonts == "true":
2183                 document.header[i:i+1] = [f + ' ' + line.split()[1]]
2184             else:
2185                 document.header[i:i+1] = [f + ' ' + line.split()[0]]
2186         j = j + 1
2187
2188
2189 def revert_solution(document):
2190     " Reverts the solution environment of the theorem module to TeX code "
2191
2192     # Do we use one of the modules that provides Solution?
2193     have_mod = False
2194     mods = document.get_module_list()
2195     for mod in mods:
2196         if mod == "theorems-std" or mod == "theorems-bytype" \
2197         or mod == "theorems-ams" or mod == "theorems-ams-bytype":
2198             have_mod = True
2199             break
2200     if not have_mod:
2201         return
2202
2203     consecutive = False
2204     is_starred = False
2205     i = 0
2206     while True:
2207         i = find_token(document.body, "\\begin_layout Solution", i)
2208         if i == -1:
2209             return
2210
2211         is_starred = document.body[i].startswith("\\begin_layout Solution*")
2212         if is_starred == True:
2213             LaTeXName = "sol*"
2214             LyXName = "Solution*"
2215             theoremName = "newtheorem*"
2216         else:
2217             LaTeXName = "sol"
2218             LyXName = "Solution"
2219             theoremName = "newtheorem"
2220
2221         j = find_end_of_layout(document.body, i)
2222         if j == -1:
2223             document.warning("Malformed LyX document: Can't find end of " + LyXName + " layout")
2224             i += 1
2225             continue
2226
2227         # if this is not a consecutive env, add start command
2228         begcmd = []
2229         if not consecutive:
2230             begcmd = put_cmd_in_ert("\\begin{%s}" % (LaTeXName))
2231
2232         # has this a consecutive theorem of same type?
2233         consecutive = document.body[j + 2] == "\\begin_layout " + LyXName
2234
2235         # if this is not followed by a consecutive env, add end command
2236         if not consecutive:
2237             document.body[j : j + 1] = put_cmd_in_ert("\\end{%s}" % (LaTeXName)) + ["\\end_layout"]
2238
2239         document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
2240
2241         add_to_preamble(document, "\\theoremstyle{definition}")
2242         if is_starred or mod == "theorems-bytype" or mod == "theorems-ams-bytype":
2243             add_to_preamble(document, "\\%s{%s}{\\protect\\solutionname}" % \
2244                 (theoremName, LaTeXName))
2245         else: # mod == "theorems-std" or mod == "theorems-ams" and not is_starred
2246             add_to_preamble(document, "\\%s{%s}[thm]{\\protect\\solutionname}" % \
2247                 (theoremName, LaTeXName))
2248
2249         add_to_preamble(document, "\\providecommand{\solutionname}{Solution}")
2250         i = j
2251
2252
2253 def revert_verbatim_star(document):
2254     from lyx_2_1 import revert_verbatim
2255     revert_verbatim(document, True)
2256
2257
2258 def convert_save_props(document):
2259     " Add save_transient_properties parameter. "
2260     i = find_token(document.header, '\\begin_header', 0)
2261     if i == -1:
2262         document.warning("Malformed lyx document: Missing '\\begin_header'.")
2263         return
2264     document.header.insert(i + 1, '\\save_transient_properties true')
2265
2266
2267 def revert_save_props(document):
2268     " Remove save_transient_properties parameter. "
2269     i = find_token(document.header, "\\save_transient_properties", 0)
2270     if i == -1:
2271         return
2272     del document.header[i]
2273
2274
2275 def convert_info_tabular_feature(document):
2276     def f(arg):
2277         return arg.replace("inset-modify tabular", "tabular-feature")
2278     convert_info_insets(document, "shortcut(s)?|icon", f)
2279
2280
2281 def revert_info_tabular_feature(document):
2282     def f(arg):
2283         return arg.replace("tabular-feature", "inset-modify tabular")
2284     convert_info_insets(document, "shortcut(s)?|icon", f)
2285
2286
2287 ##
2288 # Conversion hub
2289 #
2290
2291 supported_versions = ["2.2.0", "2.2"]
2292 convert = [
2293            [475, [convert_separator]],
2294            # nothing to do for 476: We consider it a bug that older versions
2295            # did not load amsmath automatically for these commands, and do not
2296            # want to hardcode amsmath off.
2297            [476, []],
2298            [477, []],
2299            [478, []],
2300            [479, []],
2301            [480, []],
2302            [481, [convert_dashes]],
2303            [482, [convert_phrases]],
2304            [483, [convert_specialchar]],
2305            [484, []],
2306            [485, []],
2307            [486, []],
2308            [487, []],
2309            [488, [convert_newgloss]],
2310            [489, [convert_BoxFeatures]],
2311            [490, [convert_origin]],
2312            [491, []],
2313            [492, [convert_colorbox]],
2314            [493, []],
2315            [494, []],
2316            [495, [convert_subref]],
2317            [496, [convert_nounzip]],
2318            [497, [convert_external_bbox]],
2319            [498, []],
2320            [499, [convert_moderncv_phone, convert_moderncv_name]],
2321            [500, []],
2322            [501, [convert_fontsettings]],
2323            [502, []],
2324            [503, []],
2325            [504, [convert_save_props]],
2326            [505, []],
2327            [506, [convert_info_tabular_feature]],
2328            [507, [convert_longtable_label]],
2329            [508, [convert_parbreak]]
2330           ]
2331
2332 revert =  [
2333            [507, [revert_parbreak]],
2334            [506, [revert_longtable_label]],
2335            [505, [revert_info_tabular_feature]],
2336            [504, []],
2337            [503, [revert_save_props]],
2338            [502, [revert_verbatim_star]],
2339            [501, [revert_solution]],
2340            [500, [revert_fontsettings]],
2341            [499, [revert_achemso]],
2342            [498, [revert_moderncv_1, revert_moderncv_2]],
2343            [497, [revert_tcolorbox_1, revert_tcolorbox_2,
2344                   revert_tcolorbox_3, revert_tcolorbox_4, revert_tcolorbox_5,
2345                   revert_tcolorbox_6, revert_tcolorbox_7, revert_tcolorbox_8]],
2346            [496, [revert_external_bbox]],
2347            [495, []], # nothing to do since the noUnzip parameter was optional
2348            [494, [revert_subref]],
2349            [493, [revert_jss]],
2350            [492, [revert_mathmulticol]],
2351            [491, [revert_colorbox]],
2352            [490, [revert_textcolor]],
2353            [489, [revert_origin]],
2354            [488, [revert_BoxFeatures]],
2355            [487, [revert_newgloss, revert_glossgroup]],
2356            [486, [revert_forest]],
2357            [485, [revert_ex_itemargs]],
2358            [484, [revert_sigplan_doi]],
2359            [483, [revert_georgian]],
2360            [482, [revert_specialchar]],
2361            [481, [revert_phrases]],
2362            [480, [revert_dashes]],
2363            [479, [revert_question_env]],
2364            [478, [revert_beamer_lemma]],
2365            [477, [revert_xarrow]],
2366            [476, [revert_swissgerman]],
2367            [475, [revert_smash]],
2368            [474, [revert_separator]]
2369           ]
2370
2371
2372 if __name__ == "__main__":
2373     pass