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