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