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