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