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