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