]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_2.py
Merge remote-tracking branch 'features/scroll-reloaded'
[lyx.git] / lib / lyx2lyx / lyx_2_2.py
1 # -*- coding: utf-8 -*-
2 # This file is part of lyx2lyx
3 # -*- coding: utf-8 -*-
4 # Copyright (C) 2011 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#, \
34 #  insert_to_preamble, lyx2latex, latex_length, revert_flex_inset, \
35 #  revert_font_attrs, hex2ratio, str2bool
36
37 from parser_tools import find_token, find_token_backwards, find_re, \
38      find_end_of_inset, find_end_of_layout, find_nonempty_line, \
39      get_containing_layout, get_value, check_token
40
41 ###############################################################################
42 ###
43 ### Conversion and reversion routines
44 ###
45 ###############################################################################
46
47 def convert_separator(document):
48     """
49     Convert layout separators to separator insets and add (LaTeX) paragraph
50     breaks in order to mimic previous LaTeX export.
51     """
52
53     parins = ["\\begin_inset Separator parbreak", "\\end_inset", ""]
54     parlay = ["\\begin_layout Standard", "\\begin_inset Separator parbreak",
55               "\\end_inset", "", "\\end_layout", ""]
56     sty_dict = {
57         "family" : "default",
58         "series" : "default",
59         "shape"  : "default",
60         "size"   : "default",
61         "bar"    : "default",
62         "color"  : "inherit"
63         }
64
65     i = 0
66     while 1:
67         i = find_token(document.body, "\\begin_deeper", i)
68         if i == -1:
69             break
70
71         j = find_token_backwards(document.body, "\\end_layout", i-1)
72         if j != -1:
73             # reset any text style before inserting the inset
74             lay = get_containing_layout(document.body, j-1)
75             if lay != False:
76                 content = "\n".join(document.body[lay[1]:lay[2]])
77                 for val in sty_dict.keys():
78                     if content.find("\\%s" % val) != -1:
79                         document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
80                         i = i + 1
81                         j = j + 1
82             document.body[j:j] = parins
83             i = i + len(parins) + 1
84         else:
85             i = i + 1
86
87     i = 0
88     while 1:
89         i = find_token(document.body, "\\align", i)
90         if i == -1:
91             break
92
93         lay = get_containing_layout(document.body, i)
94         if lay != False and lay[0] == "Plain Layout":
95             i = i + 1
96             continue
97
98         j = find_token_backwards(document.body, "\\end_layout", i-1)
99         if j != -1:
100             lay = get_containing_layout(document.body, j-1)
101             if lay != False and lay[0] == "Standard" \
102                and find_token(document.body, "\\align", lay[1], lay[2]) == -1 \
103                and find_token(document.body, "\\begin_inset VSpace", lay[1], lay[2]) == -1:
104                 # reset any text style before inserting the inset
105                 content = "\n".join(document.body[lay[1]:lay[2]])
106                 for val in sty_dict.keys():
107                     if content.find("\\%s" % val) != -1:
108                         document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
109                         i = i + 1
110                         j = j + 1
111                 document.body[j:j] = parins
112                 i = i + len(parins) + 1
113             else:
114                 i = i + 1
115         else:
116             i = i + 1
117
118     regexp = re.compile(r'^\\begin_layout (?:(-*)|(\s*))(Separator|EndOfSlide)(?:(-*)|(\s*))$', re.IGNORECASE)
119
120     i = 0
121     while 1:
122         i = find_re(document.body, regexp, i)
123         if i == -1:
124             return
125
126         j = find_end_of_layout(document.body, i)
127         if j == -1:
128             document.warning("Malformed LyX document: Missing `\\end_layout'.")
129             return
130
131         lay = get_containing_layout(document.body, j-1)
132         if lay != False:
133             lines = document.body[lay[3]:lay[2]]
134         else:
135             lines = []
136
137         document.body[i:j+1] = parlay
138         if len(lines) > 0:
139             document.body[i+1:i+1] = lines
140
141         i = i + len(parlay) + len(lines) + 1
142
143
144 def revert_separator(document):
145     " Revert separator insets to layout separators "
146
147     beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
148     if document.textclass in beamer_classes:
149         beglaysep = "\\begin_layout Separator"
150     else:
151         beglaysep = "\\begin_layout --Separator--"
152
153     parsep = [beglaysep, "", "\\end_layout", ""]
154     comert = ["\\begin_inset ERT", "status collapsed", "",
155               "\\begin_layout Plain Layout", "%", "\\end_layout",
156               "", "\\end_inset", ""]
157     empert = ["\\begin_inset ERT", "status collapsed", "",
158               "\\begin_layout Plain Layout", " ", "\\end_layout",
159               "", "\\end_inset", ""]
160
161     i = 0
162     while 1:
163         i = find_token(document.body, "\\begin_inset Separator", i)
164         if i == -1:
165             return
166
167         lay = get_containing_layout(document.body, i)
168         if lay == False:
169             document.warning("Malformed LyX document: Can't convert separator inset at line " + str(i))
170             i = i + 1
171             continue
172
173         layoutname = lay[0]
174         beg = lay[1]
175         end = lay[2]
176         kind = get_value(document.body, "\\begin_inset Separator", i, i+1, "plain").split()[1]
177         before = document.body[beg+1:i]
178         something_before = len(before) > 0 and len("".join(before)) > 0
179         j = find_end_of_inset(document.body, i)
180         after = document.body[j+1:end]
181         something_after = len(after) > 0 and len("".join(after)) > 0
182         if kind == "plain":
183             beg = beg + len(before) + 1
184         elif something_before:
185             document.body[i:i] = ["\\end_layout", ""]
186             i = i + 2
187             j = j + 2
188             beg = i
189             end = end + 2
190
191         if kind == "plain":
192             if something_after:
193                 document.body[beg:j+1] = empert
194                 i = i + len(empert)
195             else:
196                 document.body[beg:j+1] = comert
197                 i = i + len(comert)
198         else:
199             if something_after:
200                 if layoutname == "Standard":
201                     if not something_before:
202                         document.body[beg:j+1] = parsep
203                         i = i + len(parsep)
204                         document.body[i:i] = ["", "\\begin_layout Standard"]
205                         i = i + 2
206                     else:
207                         document.body[beg:j+1] = ["\\begin_layout Standard"]
208                         i = i + 1
209                 else:
210                     document.body[beg:j+1] = ["\\begin_deeper"]
211                     i = i + 1
212                     end = end + 1 - (j + 1 - beg)
213                     if not something_before:
214                         document.body[i:i] = parsep
215                         i = i + len(parsep)
216                         end = end + len(parsep)
217                     document.body[i:i] = ["\\begin_layout Standard"]
218                     document.body[end+2:end+2] = ["", "\\end_deeper", ""]
219                     i = i + 4
220             else:
221                 next_par_is_aligned = False
222                 k = find_nonempty_line(document.body, end+1)
223                 if k != -1 and check_token(document.body[k], "\\begin_layout"):
224                     lay = get_containing_layout(document.body, k)
225                     next_par_is_aligned = lay != False and \
226                             find_token(document.body, "\\align", lay[1], lay[2]) != -1
227                 if k != -1 and not next_par_is_aligned \
228                         and not check_token(document.body[k], "\\end_deeper") \
229                         and not check_token(document.body[k], "\\begin_deeper"):
230                     if layoutname == "Standard":
231                         document.body[beg:j+1] = [beglaysep]
232                         i = i + 1
233                     else:
234                         document.body[beg:j+1] = ["\\begin_deeper", beglaysep]
235                         end = end + 2 - (j + 1 - beg)
236                         document.body[end+1:end+1] = ["", "\\end_deeper", ""]
237                         i = i + 3
238                 else:
239                     if something_before:
240                         del document.body[i:end+1]
241                     else:
242                         del document.body[i:end-1]
243
244         i = i + 1
245
246
247 def revert_smash(document):
248     " Set amsmath to on if smash commands are used "
249
250     commands = ["smash[t]", "smash[b]", "notag"]
251     i = find_token(document.header, "\\use_package amsmath", 0)
252     if i == -1:
253         document.warning("Malformed LyX document: Can't find \\use_package amsmath.")
254         return;
255     value = get_value(document.header, "\\use_package amsmath", i).split()[1]
256     if value != "1":
257         # nothing to do if package is not auto but on or off
258         return;
259     j = 0
260     while True:
261         j = find_token(document.body, '\\begin_inset Formula', j)
262         if j == -1:
263             return
264         k = find_end_of_inset(document.body, j)
265         if k == -1:
266             document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(j))
267             j += 1
268             continue
269         code = "\n".join(document.body[j:k])
270         for c in commands:
271             if code.find("\\%s" % c) != -1:
272                 # set amsmath to on, since it is loaded by the newer format
273                 document.header[i] = "\\use_package amsmath 2"
274                 return
275         j = k
276
277
278 def revert_swissgerman(document):
279     " Set language german-ch-old to german "
280     i = 0
281     if document.language == "german-ch-old":
282         document.language = "german"
283         i = find_token(document.header, "\\language", 0)
284         if i != -1:
285             document.header[i] = "\\language german"
286     j = 0
287     while True:
288         j = find_token(document.body, "\\lang german-ch-old", j)
289         if j == -1:
290             return
291         document.body[j] = document.body[j].replace("\\lang german-ch-old", "\\lang german")
292         j = j + 1
293
294
295 def revert_use_package(document, pkg, commands, oldauto):
296     # oldauto defines how the version we are reverting to behaves:
297     # if it is true, the old version uses the package automatically.
298     # if it is false, the old version never uses the package.
299     regexp = re.compile(r'(\\use_package\s+%s)' % pkg)
300     i = find_re(document.header, regexp, 0)
301     value = "1" # default is auto
302     if i != -1:
303         value = get_value(document.header, "\\use_package" , i).split()[1]
304         del document.header[i]
305     if value == "2": # on
306         add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
307     elif value == "1" and not oldauto: # auto
308         i = 0
309         while True:
310             i = find_token(document.body, '\\begin_inset Formula', i)
311             if i == -1:
312                 return
313             j = find_end_of_inset(document.body, i)
314             if j == -1:
315                 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
316                 i += 1
317                 continue
318             code = "\n".join(document.body[i:j])
319             for c in commands:
320                 if code.find("\\%s" % c) != -1:
321                     add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
322                     return
323             i = j
324
325
326 mathtools_commands = ["xhookrightarrow", "xhookleftarrow", "xRightarrow", \
327                 "xrightharpoondown", "xrightharpoonup", "xrightleftharpoons", \
328                 "xLeftarrow", "xleftharpoondown", "xleftharpoonup", \
329                 "xleftrightarrow", "xLeftrightarrow", "xleftrightharpoons", \
330                 "xmapsto"]
331
332 def revert_xarrow(document):
333     "remove use_package mathtools"
334     revert_use_package(document, "mathtools", mathtools_commands, False)
335
336
337 def revert_beamer_lemma(document):
338     " Reverts beamer lemma layout to ERT "
339     
340     beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
341     if document.textclass not in beamer_classes:
342         return
343
344     consecutive = False
345     i = 0
346     while True:
347         i = find_token(document.body, "\\begin_layout Lemma", i)
348         if i == -1:
349             return
350         j = find_end_of_layout(document.body, i)
351         if j == -1:
352             document.warning("Malformed LyX document: Can't find end of Lemma layout")
353             i += 1
354             continue
355         arg1 = find_token(document.body, "\\begin_inset Argument 1", i, j)
356         endarg1 = find_end_of_inset(document.body, arg1)
357         arg2 = find_token(document.body, "\\begin_inset Argument 2", i, j)
358         endarg2 = find_end_of_inset(document.body, arg2)
359         subst1 = []
360         subst2 = []
361         if arg1 != -1:
362             beginPlain1 = find_token(document.body, "\\begin_layout Plain Layout", arg1, endarg1)
363             if beginPlain1 == -1:
364                 document.warning("Malformed LyX document: Can't find arg1 plain Layout")
365                 i += 1
366                 continue
367             endPlain1 = find_end_of_inset(document.body, beginPlain1)
368             content1 = document.body[beginPlain1 + 1 : endPlain1 - 2]
369             subst1 = put_cmd_in_ert("<") + content1 + put_cmd_in_ert(">")
370         if arg2 != -1:
371             beginPlain2 = find_token(document.body, "\\begin_layout Plain Layout", arg2, endarg2)
372             if beginPlain2 == -1:
373                 document.warning("Malformed LyX document: Can't find arg2 plain Layout")
374                 i += 1
375                 continue
376             endPlain2 = find_end_of_inset(document.body, beginPlain2)
377             content2 = document.body[beginPlain2 + 1 : endPlain2 - 2]
378             subst2 = put_cmd_in_ert("[") + content2 + put_cmd_in_ert("]")
379
380         # remove Arg insets
381         if arg1 < arg2:
382             del document.body[arg2 : endarg2 + 1]
383             if arg1 != -1:
384                 del document.body[arg1 : endarg1 + 1]
385         if arg2 < arg1:
386             del document.body[arg1 : endarg1 + 1]
387             if arg2 != -1:
388                 del document.body[arg2 : endarg2 + 1]
389
390         # index of end layout has probably changed
391         j = find_end_of_layout(document.body, i)
392         if j == -1:
393             document.warning("Malformed LyX document: Can't find end of Lemma layout")
394             i += 1
395             continue
396
397         begcmd = []
398
399         # if this is not a consecutive env, add start command
400         if not consecutive:
401             begcmd = put_cmd_in_ert("\\begin{lemma}")
402
403         # has this a consecutive lemma?
404         consecutive = document.body[j + 2] == "\\begin_layout Lemma"
405
406         # if this is not followed by a consecutive env, add end command
407         if not consecutive:
408             document.body[j : j + 1] = put_cmd_in_ert("\\end{lemma}") + ["\\end_layout"]
409
410         document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd + subst1 + subst2
411
412         i = j
413
414
415
416 def revert_question_env(document):
417     """
418     Reverts question and question* environments of
419     theorems-ams-extended-bytype module to ERT
420     """
421
422     # Do we use theorems-ams-extended-bytype module?
423     have_mod = False
424     mods = document.get_module_list()
425     for mod in mods:
426         if mod == "theorems-ams-extended-bytype":
427             have_mod = True
428             continue
429
430     if not have_mod:
431         return
432
433     consecutive = False
434     i = 0
435     while True:
436         i = find_token(document.body, "\\begin_layout Question", i)
437         if i == -1:
438             return
439
440         starred = document.body[i] == "\\begin_layout Question*"
441
442         j = find_end_of_layout(document.body, i)
443         if j == -1:
444             document.warning("Malformed LyX document: Can't find end of Question layout")
445             i += 1
446             continue
447
448         # if this is not a consecutive env, add start command
449         begcmd = []
450         if not consecutive:
451             if starred:
452                 begcmd = put_cmd_in_ert("\\begin{question*}")
453             else:
454                 begcmd = put_cmd_in_ert("\\begin{question}")
455
456         # has this a consecutive theorem of same type?
457         consecutive = False
458         if starred:
459             consecutive = document.body[j + 2] == "\\begin_layout Question*"
460         else:
461             consecutive = document.body[j + 2] == "\\begin_layout Question"
462
463         # if this is not followed by a consecutive env, add end command
464         if not consecutive:
465             if starred:
466                 document.body[j : j + 1] = put_cmd_in_ert("\\end{question*}") + ["\\end_layout"]
467             else:
468                 document.body[j : j + 1] = put_cmd_in_ert("\\end{question}") + ["\\end_layout"]
469
470         document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
471
472         add_to_preamble(document, "\\providecommand{\questionname}{Question}")
473
474         if starred:
475             add_to_preamble(document, "\\theoremstyle{plain}\n" \
476                                       "\\newtheorem*{question*}{\\protect\\questionname}")
477         else:
478             add_to_preamble(document, "\\theoremstyle{plain}\n" \
479                                       "\\newtheorem{question}{\\protect\\questionname}")
480
481         i = j
482
483   
484 ##
485 # Conversion hub
486 #
487
488 supported_versions = ["2.2.0", "2.2"]
489 convert = [
490            [475, [convert_separator]],
491            # nothing to do for 476: We consider it a bug that older versions
492            # did not load amsmath automatically for these commands, and do not
493            # want to hardcode amsmath off.
494            [476, []],
495            [477, []],
496            [478, []],
497            [479, []],
498            [480, []]
499           ]
500
501 revert =  [
502            [479, [revert_question_env]],
503            [478, [revert_beamer_lemma]],
504            [477, [revert_xarrow]],
505            [476, [revert_swissgerman]],
506            [475, [revert_smash]],
507            [474, [revert_separator]]
508           ]
509
510
511 if __name__ == "__main__":
512     pass