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