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