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