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