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