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