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