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