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