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