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