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