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