]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_2.py
a09ad2234044fef2b4325d7e5994582e6d287c01
[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, lyx2latex, \
34   length_in_bp#, \
35 #  insert_to_preamble, latex_length, revert_flex_inset, \
36 #  revert_font_attrs, hex2ratio, str2bool
37
38 from parser_tools import find_token, find_token_backwards, find_re, \
39      find_end_of_inset, find_end_of_layout, find_nonempty_line, \
40      get_containing_layout, get_value, check_token
41
42 ###############################################################################
43 ###
44 ### Conversion and reversion routines
45 ###
46 ###############################################################################
47
48 def convert_separator(document):
49     """
50     Convert layout separators to separator insets and add (LaTeX) paragraph
51     breaks in order to mimic previous LaTeX export.
52     """
53
54     parins = ["\\begin_inset Separator parbreak", "\\end_inset", ""]
55     parlay = ["\\begin_layout Standard", "\\begin_inset Separator parbreak",
56               "\\end_inset", "", "\\end_layout", ""]
57     sty_dict = {
58         "family" : "default",
59         "series" : "default",
60         "shape"  : "default",
61         "size"   : "default",
62         "bar"    : "default",
63         "color"  : "inherit"
64         }
65
66     i = 0
67     while 1:
68         i = find_token(document.body, "\\begin_deeper", i)
69         if i == -1:
70             break
71
72         j = find_token_backwards(document.body, "\\end_layout", i-1)
73         if j != -1:
74             # reset any text style before inserting the inset
75             lay = get_containing_layout(document.body, j-1)
76             if lay != False:
77                 content = "\n".join(document.body[lay[1]:lay[2]])
78                 for val in list(sty_dict.keys()):
79                     if content.find("\\%s" % val) != -1:
80                         document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
81                         i = i + 1
82                         j = j + 1
83             document.body[j:j] = parins
84             i = i + len(parins) + 1
85         else:
86             i = i + 1
87
88     i = 0
89     while 1:
90         i = find_token(document.body, "\\align", i)
91         if i == -1:
92             break
93
94         lay = get_containing_layout(document.body, i)
95         if lay != False and lay[0] == "Plain Layout":
96             i = i + 1
97             continue
98
99         j = find_token_backwards(document.body, "\\end_layout", i-1)
100         if j != -1:
101             lay = get_containing_layout(document.body, j-1)
102             if lay != False and lay[0] == "Standard" \
103                and find_token(document.body, "\\align", lay[1], lay[2]) == -1 \
104                and find_token(document.body, "\\begin_inset VSpace", lay[1], lay[2]) == -1:
105                 # reset any text style before inserting the inset
106                 content = "\n".join(document.body[lay[1]:lay[2]])
107                 for val in list(sty_dict.keys()):
108                     if content.find("\\%s" % val) != -1:
109                         document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
110                         i = i + 1
111                         j = j + 1
112                 document.body[j:j] = parins
113                 i = i + len(parins) + 1
114             else:
115                 i = i + 1
116         else:
117             i = i + 1
118
119     regexp = re.compile(r'^\\begin_layout (?:(-*)|(\s*))(Separator|EndOfSlide)(?:(-*)|(\s*))$', re.IGNORECASE)
120
121     i = 0
122     while 1:
123         i = find_re(document.body, regexp, i)
124         if i == -1:
125             return
126
127         j = find_end_of_layout(document.body, i)
128         if j == -1:
129             document.warning("Malformed LyX document: Missing `\\end_layout'.")
130             return
131
132         lay = get_containing_layout(document.body, j-1)
133         if lay != False:
134             lines = document.body[lay[3]:lay[2]]
135         else:
136             lines = []
137
138         document.body[i:j+1] = parlay
139         if len(lines) > 0:
140             document.body[i+1:i+1] = lines
141
142         i = i + len(parlay) + len(lines) + 1
143
144
145 def revert_separator(document):
146     " Revert separator insets to layout separators "
147
148     beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
149     if document.textclass in beamer_classes:
150         beglaysep = "\\begin_layout Separator"
151     else:
152         beglaysep = "\\begin_layout --Separator--"
153
154     parsep = [beglaysep, "", "\\end_layout", ""]
155     comert = ["\\begin_inset ERT", "status collapsed", "",
156               "\\begin_layout Plain Layout", "%", "\\end_layout",
157               "", "\\end_inset", ""]
158     empert = ["\\begin_inset ERT", "status collapsed", "",
159               "\\begin_layout Plain Layout", " ", "\\end_layout",
160               "", "\\end_inset", ""]
161
162     i = 0
163     while 1:
164         i = find_token(document.body, "\\begin_inset Separator", i)
165         if i == -1:
166             return
167
168         lay = get_containing_layout(document.body, i)
169         if lay == False:
170             document.warning("Malformed LyX document: Can't convert separator inset at line " + str(i))
171             i = i + 1
172             continue
173
174         layoutname = lay[0]
175         beg = lay[1]
176         end = lay[2]
177         kind = get_value(document.body, "\\begin_inset Separator", i, i+1, "plain").split()[1]
178         before = document.body[beg+1:i]
179         something_before = len(before) > 0 and len("".join(before)) > 0
180         j = find_end_of_inset(document.body, i)
181         after = document.body[j+1:end]
182         something_after = len(after) > 0 and len("".join(after)) > 0
183         if kind == "plain":
184             beg = beg + len(before) + 1
185         elif something_before:
186             document.body[i:i] = ["\\end_layout", ""]
187             i = i + 2
188             j = j + 2
189             beg = i
190             end = end + 2
191
192         if kind == "plain":
193             if something_after:
194                 document.body[beg:j+1] = empert
195                 i = i + len(empert)
196             else:
197                 document.body[beg:j+1] = comert
198                 i = i + len(comert)
199         else:
200             if something_after:
201                 if layoutname == "Standard":
202                     if not something_before:
203                         document.body[beg:j+1] = parsep
204                         i = i + len(parsep)
205                         document.body[i:i] = ["", "\\begin_layout Standard"]
206                         i = i + 2
207                     else:
208                         document.body[beg:j+1] = ["\\begin_layout Standard"]
209                         i = i + 1
210                 else:
211                     document.body[beg:j+1] = ["\\begin_deeper"]
212                     i = i + 1
213                     end = end + 1 - (j + 1 - beg)
214                     if not something_before:
215                         document.body[i:i] = parsep
216                         i = i + len(parsep)
217                         end = end + len(parsep)
218                     document.body[i:i] = ["\\begin_layout Standard"]
219                     document.body[end+2:end+2] = ["", "\\end_deeper", ""]
220                     i = i + 4
221             else:
222                 next_par_is_aligned = False
223                 k = find_nonempty_line(document.body, end+1)
224                 if k != -1 and check_token(document.body[k], "\\begin_layout"):
225                     lay = get_containing_layout(document.body, k)
226                     next_par_is_aligned = lay != False and \
227                             find_token(document.body, "\\align", lay[1], lay[2]) != -1
228                 if k != -1 and not next_par_is_aligned \
229                         and not check_token(document.body[k], "\\end_deeper") \
230                         and not check_token(document.body[k], "\\begin_deeper"):
231                     if layoutname == "Standard":
232                         document.body[beg:j+1] = [beglaysep]
233                         i = i + 1
234                     else:
235                         document.body[beg:j+1] = ["\\begin_deeper", beglaysep]
236                         end = end + 2 - (j + 1 - beg)
237                         document.body[end+1:end+1] = ["", "\\end_deeper", ""]
238                         i = i + 3
239                 else:
240                     if something_before:
241                         del document.body[i:end+1]
242                     else:
243                         del document.body[i:end-1]
244
245         i = i + 1
246
247
248 def revert_smash(document):
249     " Set amsmath to on if smash commands are used "
250
251     commands = ["smash[t]", "smash[b]", "notag"]
252     i = find_token(document.header, "\\use_package amsmath", 0)
253     if i == -1:
254         document.warning("Malformed LyX document: Can't find \\use_package amsmath.")
255         return;
256     value = get_value(document.header, "\\use_package amsmath", i).split()[1]
257     if value != "1":
258         # nothing to do if package is not auto but on or off
259         return;
260     j = 0
261     while True:
262         j = find_token(document.body, '\\begin_inset Formula', j)
263         if j == -1:
264             return
265         k = find_end_of_inset(document.body, j)
266         if k == -1:
267             document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(j))
268             j += 1
269             continue
270         code = "\n".join(document.body[j:k])
271         for c in commands:
272             if code.find("\\%s" % c) != -1:
273                 # set amsmath to on, since it is loaded by the newer format
274                 document.header[i] = "\\use_package amsmath 2"
275                 return
276         j = k
277
278
279 def revert_swissgerman(document):
280     " Set language german-ch-old to german "
281     i = 0
282     if document.language == "german-ch-old":
283         document.language = "german"
284         i = find_token(document.header, "\\language", 0)
285         if i != -1:
286             document.header[i] = "\\language german"
287     j = 0
288     while True:
289         j = find_token(document.body, "\\lang german-ch-old", j)
290         if j == -1:
291             return
292         document.body[j] = document.body[j].replace("\\lang german-ch-old", "\\lang german")
293         j = j + 1
294
295
296 def revert_use_package(document, pkg, commands, oldauto, supported):
297     # oldauto defines how the version we are reverting to behaves:
298     # if it is true, the old version uses the package automatically.
299     # if it is false, the old version never uses the package.
300     # If "supported" is true, the target version also supports this
301     # package natively.
302     regexp = re.compile(r'(\\use_package\s+%s)' % pkg)
303     p = find_re(document.header, regexp, 0)
304     value = "1" # default is auto
305     if p != -1:
306         value = get_value(document.header, "\\use_package" , p).split()[1]
307         if not supported:
308             del document.header[p]
309     if value == "2" and not supported: # on
310         add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
311     elif value == "1" and not oldauto: # auto
312         i = 0
313         while True:
314             i = find_token(document.body, '\\begin_inset Formula', i)
315             if i == -1:
316                 return
317             j = find_end_of_inset(document.body, i)
318             if j == -1:
319                 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
320                 i += 1
321                 continue
322             code = "\n".join(document.body[i:j])
323             for c in commands:
324                 if code.find("\\%s" % c) != -1:
325                     if supported:
326                         document.header[p] = "\\use_package " + pkg + " 2"
327                     else:
328                         add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
329                     return
330             i = j
331
332
333 mathtools_commands = ["xhookrightarrow", "xhookleftarrow", "xRightarrow", \
334                 "xrightharpoondown", "xrightharpoonup", "xrightleftharpoons", \
335                 "xLeftarrow", "xleftharpoondown", "xleftharpoonup", \
336                 "xleftrightarrow", "xLeftrightarrow", "xleftrightharpoons", \
337                 "xmapsto"]
338
339 def revert_xarrow(document):
340     "remove use_package mathtools"
341     revert_use_package(document, "mathtools", mathtools_commands, False, True)
342
343
344 def revert_beamer_lemma(document):
345     " Reverts beamer lemma layout to ERT "
346
347     beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
348     if document.textclass not in beamer_classes:
349         return
350
351     consecutive = False
352     i = 0
353     while True:
354         i = find_token(document.body, "\\begin_layout Lemma", i)
355         if i == -1:
356             return
357         j = find_end_of_layout(document.body, i)
358         if j == -1:
359             document.warning("Malformed LyX document: Can't find end of Lemma layout")
360             i += 1
361             continue
362         arg1 = find_token(document.body, "\\begin_inset Argument 1", i, j)
363         endarg1 = find_end_of_inset(document.body, arg1)
364         arg2 = find_token(document.body, "\\begin_inset Argument 2", i, j)
365         endarg2 = find_end_of_inset(document.body, arg2)
366         subst1 = []
367         subst2 = []
368         if arg1 != -1:
369             beginPlain1 = find_token(document.body, "\\begin_layout Plain Layout", arg1, endarg1)
370             if beginPlain1 == -1:
371                 document.warning("Malformed LyX document: Can't find arg1 plain Layout")
372                 i += 1
373                 continue
374             endPlain1 = find_end_of_inset(document.body, beginPlain1)
375             content1 = document.body[beginPlain1 + 1 : endPlain1 - 2]
376             subst1 = put_cmd_in_ert("<") + content1 + put_cmd_in_ert(">")
377         if arg2 != -1:
378             beginPlain2 = find_token(document.body, "\\begin_layout Plain Layout", arg2, endarg2)
379             if beginPlain2 == -1:
380                 document.warning("Malformed LyX document: Can't find arg2 plain Layout")
381                 i += 1
382                 continue
383             endPlain2 = find_end_of_inset(document.body, beginPlain2)
384             content2 = document.body[beginPlain2 + 1 : endPlain2 - 2]
385             subst2 = put_cmd_in_ert("[") + content2 + put_cmd_in_ert("]")
386
387         # remove Arg insets
388         if arg1 < arg2:
389             del document.body[arg2 : endarg2 + 1]
390             if arg1 != -1:
391                 del document.body[arg1 : endarg1 + 1]
392         if arg2 < arg1:
393             del document.body[arg1 : endarg1 + 1]
394             if arg2 != -1:
395                 del document.body[arg2 : endarg2 + 1]
396
397         # index of end layout has probably changed
398         j = find_end_of_layout(document.body, i)
399         if j == -1:
400             document.warning("Malformed LyX document: Can't find end of Lemma layout")
401             i += 1
402             continue
403
404         begcmd = []
405
406         # if this is not a consecutive env, add start command
407         if not consecutive:
408             begcmd = put_cmd_in_ert("\\begin{lemma}")
409
410         # has this a consecutive lemma?
411         consecutive = document.body[j + 2] == "\\begin_layout Lemma"
412
413         # if this is not followed by a consecutive env, add end command
414         if not consecutive:
415             document.body[j : j + 1] = put_cmd_in_ert("\\end{lemma}") + ["\\end_layout"]
416
417         document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd + subst1 + subst2
418
419         i = j
420
421
422
423 def revert_question_env(document):
424     """
425     Reverts question and question* environments of
426     theorems-ams-extended-bytype module to ERT
427     """
428
429     # Do we use theorems-ams-extended-bytype module?
430     have_mod = False
431     mods = document.get_module_list()
432     for mod in mods:
433         if mod == "theorems-ams-extended-bytype":
434             have_mod = True
435             continue
436
437     if not have_mod:
438         return
439
440     consecutive = False
441     i = 0
442     while True:
443         i = find_token(document.body, "\\begin_layout Question", i)
444         if i == -1:
445             return
446
447         starred = document.body[i] == "\\begin_layout Question*"
448
449         j = find_end_of_layout(document.body, i)
450         if j == -1:
451             document.warning("Malformed LyX document: Can't find end of Question layout")
452             i += 1
453             continue
454
455         # if this is not a consecutive env, add start command
456         begcmd = []
457         if not consecutive:
458             if starred:
459                 begcmd = put_cmd_in_ert("\\begin{question*}")
460             else:
461                 begcmd = put_cmd_in_ert("\\begin{question}")
462
463         # has this a consecutive theorem of same type?
464         consecutive = False
465         if starred:
466             consecutive = document.body[j + 2] == "\\begin_layout Question*"
467         else:
468             consecutive = document.body[j + 2] == "\\begin_layout Question"
469
470         # if this is not followed by a consecutive env, add end command
471         if not consecutive:
472             if starred:
473                 document.body[j : j + 1] = put_cmd_in_ert("\\end{question*}") + ["\\end_layout"]
474             else:
475                 document.body[j : j + 1] = put_cmd_in_ert("\\end{question}") + ["\\end_layout"]
476
477         document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd
478
479         add_to_preamble(document, "\\providecommand{\questionname}{Question}")
480
481         if starred:
482             add_to_preamble(document, "\\theoremstyle{plain}\n" \
483                                       "\\newtheorem*{question*}{\\protect\\questionname}")
484         else:
485             add_to_preamble(document, "\\theoremstyle{plain}\n" \
486                                       "\\newtheorem{question}{\\protect\\questionname}")
487
488         i = j
489
490
491 def convert_dashes(document):
492     "convert -- and --- to \\twohyphens and \\threehyphens"
493
494     if document.backend != "latex":
495         return
496
497     i = 0
498     while i < len(document.body):
499         words = document.body[i].split()
500         if len(words) > 1 and words[0] == "\\begin_inset" and \
501            words[1] in ["CommandInset", "ERT", "External", "Formula", "Graphics", "IPA", "listings"]:
502             # must not replace anything in insets that store LaTeX contents in .lyx files
503             # (math and command insets withut overridden read() and write() methods
504             # filtering out IPA makes Text::readParToken() more simple
505             # skip ERT as well since it is not needed there
506             j = find_end_of_inset(document.body, i)
507             if j == -1:
508                 document.warning("Malformed LyX document: Can't find end of " + words[1] + " inset at line " + str(i))
509                 i += 1
510             else:
511                 i = j
512             continue
513         while True:
514             j = document.body[i].find("--")
515             if j == -1:
516                 break
517             front = document.body[i][:j]
518             back = document.body[i][j+2:]
519             # We can have an arbitrary number of consecutive hyphens.
520             # These must be split into the corresponding number of two and three hyphens
521             # We must match what LaTeX does: First try emdash, then endash, then single hyphen
522             if back.find("-") == 0:
523                 back = back[1:]
524                 if len(back) > 0:
525                     document.body.insert(i+1, back)
526                 document.body[i] = front + "\\threehyphens"
527             else:
528                 if len(back) > 0:
529                     document.body.insert(i+1, back)
530                 document.body[i] = front + "\\twohyphens"
531         i += 1
532
533
534 def revert_dashes(document):
535     "convert \\twohyphens and \\threehyphens to -- and ---"
536
537     i = 0
538     while i < len(document.body):
539         words = document.body[i].split()
540         if len(words) > 1 and words[0] == "\\begin_inset" and \
541            words[1] in ["CommandInset", "ERT", "External", "Formula", "Graphics", "IPA", "listings"]:
542             # see convert_dashes
543             j = find_end_of_inset(document.body, i)
544             if j == -1:
545                 document.warning("Malformed LyX document: Can't find end of " + words[1] + " inset at line " + str(i))
546                 i += 1
547             else:
548                 i = j
549             continue
550         replaced = False
551         if document.body[i].find("\\twohyphens") >= 0:
552             document.body[i] = document.body[i].replace("\\twohyphens", "--")
553             replaced = True
554         if document.body[i].find("\\threehyphens") >= 0:
555             document.body[i] = document.body[i].replace("\\threehyphens", "---")
556             replaced = True
557         if replaced and i+1 < len(document.body) and \
558            (document.body[i+1].find("\\") != 0 or \
559             document.body[i+1].find("\\twohyphens") == 0 or
560             document.body[i+1].find("\\threehyphens") == 0) and \
561            len(document.body[i]) + len(document.body[i+1]) <= 80:
562             document.body[i] = document.body[i] + document.body[i+1]
563             document.body[i+1:i+2] = []
564         else:
565             i += 1
566
567
568 # order is important for the last three!
569 phrases = ["LyX", "LaTeX2e", "LaTeX", "TeX"]
570
571 def is_part_of_converted_phrase(line, j, phrase):
572     "is phrase part of an already converted phrase?"
573     for p in phrases:
574         converted = "\\SpecialCharNoPassThru \\" + p
575         pos = j + len(phrase) - len(converted)
576         if pos >= 0:
577             if line[pos:pos+len(converted)] == converted:
578                 return True
579     return False
580
581
582 def convert_phrases(document):
583     "convert special phrases from plain text to \\SpecialCharNoPassThru"
584
585     if document.backend != "latex":
586         return
587
588     for phrase in phrases:
589         i = 0
590         while i < len(document.body):
591             words = document.body[i].split()
592             if len(words) > 1 and words[0] == "\\begin_inset" and \
593                words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
594                 # must not replace anything in insets that store LaTeX contents in .lyx files
595                 # (math and command insets withut overridden read() and write() methods
596                 j = find_end_of_inset(document.body, i)
597                 if j == -1:
598                     document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
599                     i += 1
600                 else:
601                     i = j
602                 continue
603             if document.body[i].find("\\") == 0:
604                 i += 1
605                 continue
606             j = document.body[i].find(phrase)
607             if j == -1:
608                 i += 1
609                 continue
610             if not is_part_of_converted_phrase(document.body[i], j, phrase):
611                 front = document.body[i][:j]
612                 back = document.body[i][j+len(phrase):]
613                 if len(back) > 0:
614                     document.body.insert(i+1, back)
615                 # We cannot use SpecialChar since we do not know whether we are outside passThru
616                 document.body[i] = front + "\\SpecialCharNoPassThru \\" + phrase
617             i += 1
618
619
620 def revert_phrases(document):
621     "convert special phrases to plain text"
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", "External", "Formula", "Graphics", "listings"]:
628             # see convert_phrases
629             j = find_end_of_inset(document.body, i)
630             if j == -1:
631                 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
632                 i += 1
633             else:
634                 i = j
635             continue
636         replaced = False
637         for phrase in phrases:
638             # we can replace SpecialChar since LyX ensures that it cannot be inserted into passThru parts
639             if document.body[i].find("\\SpecialChar \\" + phrase) >= 0:
640                 document.body[i] = document.body[i].replace("\\SpecialChar \\" + phrase, phrase)
641                 replaced = True
642             if document.body[i].find("\\SpecialCharNoPassThru \\" + phrase) >= 0:
643                 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru \\" + phrase, phrase)
644                 replaced = True
645         if replaced and i+1 < len(document.body) and \
646            (document.body[i+1].find("\\") != 0 or \
647             document.body[i+1].find("\\SpecialChar") == 0) and \
648            len(document.body[i]) + len(document.body[i+1]) <= 80:
649             document.body[i] = document.body[i] + document.body[i+1]
650             document.body[i+1:i+2] = []
651             i -= 1
652         i += 1
653
654
655 def convert_specialchar_internal(document, forward):
656     specialchars = {"\\-":"softhyphen", "\\textcompwordmark{}":"ligaturebreak", \
657         "\\@.":"endofsentence", "\\ldots{}":"ldots", \
658         "\\menuseparator":"menuseparator", "\\slash{}":"breakableslash", \
659         "\\nobreakdash-":"nobreakdash", "\\LyX":"LyX", \
660         "\\TeX":"TeX", "\\LaTeX2e":"LaTeX2e", \
661         "\\LaTeX":"LaTeX" # must be after LaTeX2e
662     }
663
664     i = 0
665     while i < len(document.body):
666         words = document.body[i].split()
667         if len(words) > 1 and words[0] == "\\begin_inset" and \
668            words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
669             # see convert_phrases
670             j = find_end_of_inset(document.body, i)
671             if j == -1:
672                 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
673                 i += 1
674             else:
675                 i = j
676             continue
677         for key, value in specialchars.iteritems():
678             if forward:
679                 document.body[i] = document.body[i].replace("\\SpecialChar " + key, "\\SpecialChar " + value)
680                 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + key, "\\SpecialCharNoPassThru " + value)
681             else:
682                 document.body[i] = document.body[i].replace("\\SpecialChar " + value, "\\SpecialChar " + key)
683                 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + value, "\\SpecialCharNoPassThru " + key)
684         i += 1
685
686
687 def convert_specialchar(document):
688     "convert special characters to new syntax"
689     convert_specialchar_internal(document, True)
690
691
692 def revert_specialchar(document):
693     "convert special characters to old syntax"
694     convert_specialchar_internal(document, False)
695
696
697 def revert_georgian(document):
698     "Set the document language to English but assure Georgian output"
699
700     if document.language == "georgian":
701         document.language = "english"
702         i = find_token(document.header, "\\language georgian", 0)
703         if i != -1:
704             document.header[i] = "\\language english"
705         j = find_token(document.header, "\\language_package default", 0)
706         if j != -1:
707             document.header[j] = "\\language_package babel"
708         k = find_token(document.header, "\\options", 0)
709         if k != -1:
710             document.header[k] = document.header[k].replace("\\options", "\\options georgian,")
711         else:
712             l = find_token(document.header, "\\use_default_options", 0)
713             document.header.insert(l + 1, "\\options georgian")
714
715
716 def revert_sigplan_doi(document):
717     " Reverts sigplanconf DOI layout to ERT "
718
719     if document.textclass != "sigplanconf":
720         return
721
722     i = 0
723     while True:
724         i = find_token(document.body, "\\begin_layout DOI", i)
725         if i == -1:
726             return
727         j = find_end_of_layout(document.body, i)
728         if j == -1:
729             document.warning("Malformed LyX document: Can't find end of DOI layout")
730             i += 1
731             continue
732
733         content = lyx2latex(document, document.body[i:j + 1])
734         add_to_preamble(document, ["\\doi{" + content + "}"])
735         del document.body[i:j + 1]
736         # no need to reset i
737
738
739 def revert_ex_itemargs(document):
740     " Reverts \\item arguments of the example environments (Linguistics module) to TeX-code "
741
742     # Do we use the linguistics module?
743     have_mod = False
744     mods = document.get_module_list()
745     for mod in mods:
746         if mod == "linguistics":
747             have_mod = True
748             continue
749
750     if not have_mod:
751         return
752
753     i = 0
754     example_layouts = ["Numbered Examples (consecutive)", "Subexample"]
755     while True:
756         i = find_token(document.body, "\\begin_inset Argument item:", i)
757         if i == -1:
758             return
759         j = find_end_of_inset(document.body, i)
760         # Find containing paragraph layout
761         parent = get_containing_layout(document.body, i)
762         if parent == False:
763             document.warning("Malformed LyX document: Can't find parent paragraph layout")
764             i += 1
765             continue
766         parbeg = parent[3]
767         layoutname = parent[0]
768         if layoutname in example_layouts:
769             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
770             endPlain = find_end_of_layout(document.body, beginPlain)
771             content = document.body[beginPlain + 1 : endPlain]
772             del document.body[i:j+1]
773             subst = put_cmd_in_ert("[") + content + put_cmd_in_ert("]")
774             document.body[parbeg : parbeg] = subst
775         i += 1
776
777
778 def revert_forest(document):
779     " Reverts the forest environment (Linguistics module) to TeX-code "
780
781     # Do we use the linguistics module?
782     have_mod = False
783     mods = document.get_module_list()
784     for mod in mods:
785         if mod == "linguistics":
786             have_mod = True
787             continue
788
789     if not have_mod:
790         return
791
792     i = 0
793     while True:
794         i = find_token(document.body, "\\begin_inset Flex Structure Tree", i)
795         if i == -1:
796             return
797         j = find_end_of_inset(document.body, i)
798         if j == -1:
799             document.warning("Malformed LyX document: Can't find end of Structure Tree inset")
800             i += 1
801             continue
802
803         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
804         endPlain = find_end_of_layout(document.body, beginPlain)
805         content = lyx2latex(document, document.body[beginPlain : endPlain])
806
807         add_to_preamble(document, ["\\usepackage{forest}"])
808
809         document.body[i:j + 1] = ["\\begin_inset ERT", "status collapsed", "",
810                 "\\begin_layout Plain Layout", "", "\\backslash", 
811                 "begin{forest}", "\\end_layout", "", "\\begin_layout Plain Layout",
812                 content, "\\end_layout", "", "\\begin_layout Plain Layout",
813                 "\\backslash", "end{forest}", "", "\\end_layout", "", "\\end_inset"]
814         # no need to reset i
815
816
817 def revert_glossgroup(document):
818     " Reverts the GroupGlossedWords inset (Linguistics module) to TeX-code "
819
820     # Do we use the linguistics module?
821     have_mod = False
822     mods = document.get_module_list()
823     for mod in mods:
824         if mod == "linguistics":
825             have_mod = True
826             continue
827
828     if not have_mod:
829         return
830
831     i = 0
832     while True:
833         i = find_token(document.body, "\\begin_inset Flex GroupGlossedWords", i)
834         if i == -1:
835             return
836         j = find_end_of_inset(document.body, i)
837         if j == -1:
838             document.warning("Malformed LyX document: Can't find end of GroupGlossedWords inset")
839             i += 1
840             continue
841
842         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
843         endPlain = find_end_of_layout(document.body, beginPlain)
844         content = lyx2latex(document, document.body[beginPlain : endPlain])
845         document.warning("content: %s" % content)
846
847         document.body[i:j + 1] = ["{", "", content, "", "}"]
848         # no need to reset i
849
850
851 def revert_newgloss(document):
852     " Reverts the new Glosse insets (Linguistics module) to the old format "
853
854     # Do we use the linguistics module?
855     have_mod = False
856     mods = document.get_module_list()
857     for mod in mods:
858         if mod == "linguistics":
859             have_mod = True
860             continue
861
862     if not have_mod:
863         return
864
865     glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
866     for glosse in glosses:
867         i = 0
868         while True:
869             i = find_token(document.body, glosse, i)
870             if i == -1:
871                 break
872             j = find_end_of_inset(document.body, i)
873             if j == -1:
874                 document.warning("Malformed LyX document: Can't find end of Glosse inset")
875                 i += 1
876                 continue
877
878             arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
879             endarg = find_end_of_inset(document.body, arg)
880             argcontent = ""
881             if arg != -1:
882                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
883                 if argbeginPlain == -1:
884                     document.warning("Malformed LyX document: Can't find arg plain Layout")
885                     i += 1
886                     continue
887                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
888                 argcontent = lyx2latex(document, document.body[argbeginPlain : argendPlain - 2])
889
890                 document.body[j:j] = ["", "\\begin_layout Plain Layout","\\backslash", "glt ",
891                     argcontent, "\\end_layout"]
892
893                 # remove Arg insets and paragraph, if it only contains this inset
894                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
895                     del document.body[arg - 1 : endarg + 4]
896                 else:
897                     del document.body[arg : endarg + 1]
898
899             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
900             endPlain = find_end_of_layout(document.body, beginPlain)
901             content = lyx2latex(document, document.body[beginPlain : endPlain])
902
903             document.body[beginPlain + 1:endPlain] = [content]
904             i = beginPlain + 1
905
906
907 def convert_newgloss(document):
908     " Converts Glosse insets (Linguistics module) to the new format "
909
910     # Do we use the linguistics module?
911     have_mod = False
912     mods = document.get_module_list()
913     for mod in mods:
914         if mod == "linguistics":
915             have_mod = True
916             continue
917
918     if not have_mod:
919         return
920
921     glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
922     for glosse in glosses:
923         i = 0
924         while True:
925             i = find_token(document.body, glosse, i)
926             if i == -1:
927                 break
928             j = find_end_of_inset(document.body, i)
929             if j == -1:
930                 document.warning("Malformed LyX document: Can't find end of Glosse inset")
931                 i += 1
932                 continue
933
934             k = i
935             while True:
936                 argcontent = []
937                 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", k, j)
938                 if beginPlain == -1:
939                     break
940                 endPlain = find_end_of_layout(document.body, beginPlain)
941                 if endPlain == -1:
942                     document.warning("Malformed LyX document: Can't find end of Glosse layout")
943                     i += 1
944                     continue
945
946                 glt  = find_token(document.body, "\\backslash", beginPlain, endPlain)
947                 if glt != -1 and document.body[glt + 1].startswith("glt"):
948                     document.body[glt + 1] = document.body[glt + 1].lstrip("glt").lstrip()
949                     argcontent = document.body[glt + 1 : endPlain]
950                     document.body[beginPlain + 1 : endPlain] = ["\\begin_inset Argument 1", "status open", "",
951                         "\\begin_layout Plain Layout", "\\begin_inset ERT", "status open", "",
952                         "\\begin_layout Plain Layout", ""] + argcontent + ["\\end_layout", "", "\\end_inset", "",
953                         "\\end_layout", "", "\\end_inset"]
954                 else:
955                     content = document.body[beginPlain + 1 : endPlain]
956                     document.body[beginPlain + 1 : endPlain] = ["\\begin_inset ERT", "status open", "",
957                         "\\begin_layout Plain Layout"] + content + ["\\end_layout", "", "\\end_inset"]
958
959                 endPlain = find_end_of_layout(document.body, beginPlain)
960                 k = endPlain
961                 j = find_end_of_inset(document.body, i)
962
963             i = endPlain + 1
964
965
966 def convert_BoxFeatures(document):
967     " adds new box features "
968
969     i = 0
970     while True:
971         i = find_token(document.body, "height_special", i)
972         if i == -1:
973             return
974         document.body[i+1:i+1] = ['thickness "0.4pt"', 'separation "3pt"', 'shadowsize "4pt"']
975         i = i + 4
976
977
978 def revert_BoxFeatures(document):
979     " outputs new box features as TeX code "
980
981     i = 0
982     defaultSep = "3pt"
983     defaultThick = "0.4pt"
984     defaultShadow = "4pt"
985     while True:
986         i = find_token(document.body, "height_special", i)
987         if i == -1:
988             return
989         # read out the values
990         beg = document.body[i+1].find('"');
991         end = document.body[i+1].rfind('"');
992         thickness = document.body[i+1][beg+1:end];
993         beg = document.body[i+2].find('"');
994         end = document.body[i+2].rfind('"');
995         separation = document.body[i+2][beg+1:end];
996         beg = document.body[i+3].find('"');
997         end = document.body[i+3].rfind('"');
998         shadowsize = document.body[i+3][beg+1:end];
999         # delete the specification
1000         del document.body[i+1:i+4]
1001         # output ERT
1002         # first output the closing brace
1003         if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1004             document.body[i + 10 : i + 10] = put_cmd_in_ert("}")
1005         # now output the lengths
1006         if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1007             document.body[i - 10 : i - 10] = put_cmd_in_ert("{")
1008         if thickness != defaultThick:
1009             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness]
1010         if separation != defaultSep and thickness == defaultThick:
1011             document.body[i - 5 : i - 4] = ["{\\backslash fboxsep " + separation]
1012         if separation != defaultSep and thickness != defaultThick:
1013             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation]
1014         if shadowsize != defaultShadow and separation == defaultSep and thickness == defaultThick:
1015             document.body[i - 5 : i - 4] = ["{\\backslash shadowsize " + shadowsize]
1016         if shadowsize != defaultShadow and separation != defaultSep and thickness == defaultThick:
1017             document.body[i - 5 : i - 4] = ["{\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1018         if shadowsize != defaultShadow and separation == defaultSep and thickness != defaultThick:
1019             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash shadowsize " + shadowsize]
1020         if shadowsize != defaultShadow and separation != defaultSep and thickness != defaultThick:
1021             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1022         i = i + 11
1023
1024
1025 def convert_origin(document):
1026     " Insert the origin tag "
1027
1028     i = find_token(document.header, "\\textclass ", 0)
1029     if i == -1:
1030         document.warning("Malformed LyX document: No \\textclass!!")
1031         return;
1032     if document.dir == "":
1033         origin = "stdin"
1034     else:
1035         origin = document.dir.replace('\\', '/') + '/'
1036         if os.name != 'nt':
1037             origin = unicode(origin, sys.getfilesystemencoding())
1038     document.header[i:i] = ["\\origin " + origin]
1039
1040
1041 def revert_origin(document):
1042     " Remove the origin tag "
1043
1044     i = find_token(document.header, "\\origin ", 0)
1045     if i == -1:
1046         document.warning("Malformed LyX document: No \\origin!!")
1047         return;
1048     del document.header[i]
1049
1050
1051 color_names = ["brown", "darkgray", "gray", \
1052                "lightgray", "lime", "olive", "orange", \
1053                "pink", "purple", "teal", "violet"]
1054
1055 def revert_textcolor(document):
1056     " revert new \\textcolor colors to TeX code "
1057
1058     i = 0
1059     j = 0
1060     xcolor = False
1061     while True:
1062         i = find_token(document.body, "\\color ", i)
1063         if i == -1:
1064             return
1065         else:
1066             for color in list(color_names):
1067                 if document.body[i] == "\\color " + color:
1068                     # register that xcolor must be loaded in the preamble
1069                     if xcolor == False:
1070                         xcolor = True
1071                         add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\usepackage{xcolor}}{}"])
1072                     # find the next \\color and/or the next \\end_layout
1073                     j = find_token(document.body, "\\color", i + 1)
1074                     k = find_token(document.body, "\\end_layout", i + 1)
1075                     if j == -1 and k != -1:
1076                         j = k +1 
1077                     # output TeX code
1078                     # first output the closing brace
1079                     if k < j:
1080                         document.body[k: k] = put_cmd_in_ert("}")
1081                     else:
1082                         document.body[j: j] = put_cmd_in_ert("}")
1083                     # now output the \textcolor command
1084                     document.body[i : i + 1] = put_cmd_in_ert("\\textcolor{" + color + "}{")
1085         i = i + 1
1086
1087
1088 def convert_colorbox(document):
1089     " adds color settings for boxes "
1090
1091     i = 0
1092     while True:
1093         i = find_token(document.body, "shadowsize", i)
1094         if i == -1:
1095             return
1096         document.body[i+1:i+1] = ['framecolor "black"', 'backgroundcolor "none"']
1097         i = i + 3
1098
1099
1100 def revert_colorbox(document):
1101     " outputs color settings for boxes as TeX code "
1102
1103     binset = 0
1104     defaultframecolor = "black"
1105     defaultbackcolor = "none"
1106     while True:
1107         binset = find_token(document.body, "\\begin_inset Box", binset)
1108         if binset == -1:
1109             return
1110
1111         einset = find_end_of_inset(document.body, binset)
1112         if einset == -1:
1113             document.warning("Malformed LyX document: Can't find end of box inset!")
1114             binset += 1
1115             continue
1116
1117         blay = find_token(document.body, "\\begin_layout", binset, einset)
1118         if blay == -1:
1119             document.warning("Malformed LyX document: Can't find start of layout!")
1120             binset = einset
1121             continue
1122
1123         # doing it this way, we make sure only to find a framecolor option
1124         frame = find_token(document.body, "framecolor", binset, blay)
1125         if frame == -1:
1126             binset = einset
1127             continue
1128
1129         beg = document.body[frame].find('"')
1130         end = document.body[frame].rfind('"')
1131         framecolor = document.body[frame][beg+1:end]
1132
1133         # this should be on the next line
1134         bgcolor = frame + 1
1135         beg = document.body[bgcolor].find('"')
1136         end = document.body[bgcolor].rfind('"')
1137         backcolor = document.body[bgcolor][beg+1:end]
1138
1139         # delete those bits
1140         del document.body[frame:frame+2]
1141         # adjust end of inset
1142         einset -= 2
1143
1144         if document.body[binset] == "\\begin_inset Box Boxed" and \
1145             framecolor != defaultframecolor:
1146           document.body[binset] = "\\begin_inset Box Frameless"
1147
1148         # output TeX code
1149         # first output the closing brace
1150         if framecolor == defaultframecolor and backcolor == defaultbackcolor:
1151             # nothing needed
1152             pass
1153         else:
1154             document.body[einset + 1 : einset + 1] = put_cmd_in_ert("}")
1155             if framecolor != defaultframecolor:
1156                 document.body[binset:binset] = put_cmd_in_ert("\\backslash fcolorbox{" + framecolor + "}{" + backcolor + "}{")
1157             else:
1158               document.body[binset:binset] = put_cmd_in_ert("\\backslash colorbox{" + backcolor + "}{")
1159
1160         binset = einset
1161
1162
1163 def revert_mathmulticol(document):
1164     " Convert formulas to ERT if they contain multicolumns "
1165
1166     i = 0
1167     while True:
1168         i = find_token(document.body, '\\begin_inset Formula', i)
1169         if i == -1:
1170             return
1171         j = find_end_of_inset(document.body, i)
1172         if j == -1:
1173             document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
1174             i += 1
1175             continue
1176         lines = document.body[i:j]
1177         lines[0] = lines[0].replace('\\begin_inset Formula', '').lstrip()
1178         code = "\n".join(lines)
1179         converted = False
1180         k = 0
1181         n = 0
1182         while n >= 0:
1183             n = code.find("\\multicolumn", k)
1184             # no need to convert degenerated multicolumn cells,
1185             # they work in old LyX versions as "math ERT"
1186             if n != -1 and code.find("\\multicolumn{1}", k) != n:
1187                 ert = put_cmd_in_ert(code)
1188                 document.body[i:j+1] = ert
1189                 converted = True
1190                 break
1191             else:
1192                 k = n + 12
1193         if converted:
1194             i = find_end_of_inset(document.body, i)
1195         else:
1196             i = j
1197
1198
1199 def revert_jss(document):
1200     " Reverts JSS In_Preamble commands to ERT in preamble "
1201
1202     if document.textclass != "jss":
1203         return
1204
1205     h = 0
1206     m = 0
1207     j = 0
1208     k = 0
1209     n = 0
1210     while True:
1211       # at first revert the inset layouts because they can be part of the In_Preamble layouts
1212       while m != -1 or j != -1 or h != -1 or k != -1 or n != -1:
1213         # \pkg
1214         if h != -1:
1215           h = find_token(document.body, "\\begin_inset Flex Pkg", h)
1216         if h != -1:
1217           endh = find_end_of_inset(document.body, h)
1218           document.body[endh - 2 : endh + 1] = put_cmd_in_ert("}")
1219           document.body[h : h + 4] = put_cmd_in_ert("\\pkg{")
1220           h = h + 5
1221         # \proglang
1222         if m != -1:
1223           m = find_token(document.body, "\\begin_inset Flex Proglang", m)
1224         if m != -1:
1225           endm = find_end_of_inset(document.body, m)
1226           document.body[endm - 2 : endm + 1] = put_cmd_in_ert("}")
1227           document.body[m : m + 4] = put_cmd_in_ert("\\proglang{")
1228           m = m + 5
1229         # \code
1230         if j != -1:
1231           j = find_token(document.body, "\\begin_inset Flex Code", j)
1232         if j != -1:
1233           # assure that we are not in a Code Chunk inset
1234           if document.body[j][-1] == "e":
1235               endj = find_end_of_inset(document.body, j)
1236               document.body[endj - 2 : endj + 1] = put_cmd_in_ert("}")
1237               document.body[j : j + 4] = put_cmd_in_ert("\\code{")
1238               j = j + 5
1239           else:
1240               j = j + 1
1241         # \email
1242         if k != -1:
1243           k = find_token(document.body, "\\begin_inset Flex E-mail", k)
1244         if k != -1:
1245           endk = find_end_of_inset(document.body, k)
1246           document.body[endk - 2 : endk + 1] = put_cmd_in_ert("}")
1247           document.body[k : k + 4] = put_cmd_in_ert("\\email{")
1248           k = k + 5
1249         # \url
1250         if n != -1:
1251           n = find_token(document.body, "\\begin_inset Flex URL", n)
1252         if n != -1:
1253           endn = find_end_of_inset(document.body, n)
1254           document.body[endn - 2 : endn + 1] = put_cmd_in_ert("}")
1255           document.body[n : n + 4] = put_cmd_in_ert("\\url{")
1256           n = n + 5
1257       # now revert the In_Preamble layouts
1258       # \title
1259       i = find_token(document.body, "\\begin_layout Title", 0)
1260       if i == -1:
1261         return
1262       j = find_end_of_layout(document.body, i)
1263       if j == -1:
1264         document.warning("Malformed LyX document: Can't find end of Title layout")
1265         i += 1
1266         continue
1267       content = lyx2latex(document, document.body[i:j + 1])
1268       add_to_preamble(document, ["\\title{" + content + "}"])
1269       del document.body[i:j + 1]
1270       # \author
1271       i = find_token(document.body, "\\begin_layout Author", 0)
1272       if i == -1:
1273         return
1274       j = find_end_of_layout(document.body, i)
1275       if j == -1:
1276         document.warning("Malformed LyX document: Can't find end of Author layout")
1277         i += 1
1278         continue
1279       content = lyx2latex(document, document.body[i:j + 1])
1280       add_to_preamble(document, ["\\author{" + content + "}"])
1281       del document.body[i:j + 1]
1282       # \Plainauthor
1283       i = find_token(document.body, "\\begin_layout Plain Author", 0)
1284       if i == -1:
1285         return
1286       j = find_end_of_layout(document.body, i)
1287       if j == -1:
1288         document.warning("Malformed LyX document: Can't find end of Plain Author layout")
1289         i += 1
1290         continue
1291       content = lyx2latex(document, document.body[i:j + 1])
1292       add_to_preamble(document, ["\\Plainauthor{" + content + "}"])
1293       del document.body[i:j + 1]
1294       # \Plaintitle
1295       i = find_token(document.body, "\\begin_layout Plain Title", 0)
1296       if i == -1:
1297         return
1298       j = find_end_of_layout(document.body, i)
1299       if j == -1:
1300         document.warning("Malformed LyX document: Can't find end of Plain Title layout")
1301         i += 1
1302         continue
1303       content = lyx2latex(document, document.body[i:j + 1])
1304       add_to_preamble(document, ["\\Plaintitle{" + content + "}"])
1305       del document.body[i:j + 1]
1306       # \Shorttitle
1307       i = find_token(document.body, "\\begin_layout Short Title", 0)
1308       if i == -1:
1309         return
1310       j = find_end_of_layout(document.body, i)
1311       if j == -1:
1312         document.warning("Malformed LyX document: Can't find end of Short Title layout")
1313         i += 1
1314         continue
1315       content = lyx2latex(document, document.body[i:j + 1])
1316       add_to_preamble(document, ["\\Shorttitle{" + content + "}"])
1317       del document.body[i:j + 1]
1318       # \Abstract
1319       i = find_token(document.body, "\\begin_layout Abstract", 0)
1320       if i == -1:
1321         return
1322       j = find_end_of_layout(document.body, i)
1323       if j == -1:
1324         document.warning("Malformed LyX document: Can't find end of Abstract layout")
1325         i += 1
1326         continue
1327       content = lyx2latex(document, document.body[i:j + 1])
1328       add_to_preamble(document, ["\\Abstract{" + content + "}"])
1329       del document.body[i:j + 1]
1330       # \Keywords
1331       i = find_token(document.body, "\\begin_layout Keywords", 0)
1332       if i == -1:
1333         return
1334       j = find_end_of_layout(document.body, i)
1335       if j == -1:
1336         document.warning("Malformed LyX document: Can't find end of Keywords layout")
1337         i += 1
1338         continue
1339       content = lyx2latex(document, document.body[i:j + 1])
1340       add_to_preamble(document, ["\\Keywords{" + content + "}"])
1341       del document.body[i:j + 1]
1342       # \Plainkeywords
1343       i = find_token(document.body, "\\begin_layout Plain Keywords", 0)
1344       if i == -1:
1345         return
1346       j = find_end_of_layout(document.body, i)
1347       if j == -1:
1348         document.warning("Malformed LyX document: Can't find end of Plain Keywords layout")
1349         i += 1
1350         continue
1351       content = lyx2latex(document, document.body[i:j + 1])
1352       add_to_preamble(document, ["\\Plainkeywords{" + content + "}"])
1353       del document.body[i:j + 1]
1354       # \Address
1355       i = find_token(document.body, "\\begin_layout Address", 0)
1356       if i == -1:
1357         return
1358       j = find_end_of_layout(document.body, i)
1359       if j == -1:
1360         document.warning("Malformed LyX document: Can't find end of Address layout")
1361         i += 1
1362         continue
1363       content = lyx2latex(document, document.body[i:j + 1])
1364       add_to_preamble(document, ["\\Address{" + content + "}"])
1365       del document.body[i:j + 1]
1366       # finally handle the code layouts
1367       h = 0
1368       m = 0
1369       j = 0
1370       k = 0
1371       while m != -1 or j != -1 or h != -1 or k != -1:
1372         # \CodeChunk
1373         if h != -1:
1374           h = find_token(document.body, "\\begin_inset Flex Code Chunk", h)
1375         if h != -1:
1376           endh = find_end_of_inset(document.body, h)
1377           document.body[endh + 1 : endh] = ["\\end_layout"]
1378           document.body[endh : endh + 1] = put_cmd_in_ert("\\end{CodeChunk}")
1379           document.body[h : h + 3] = put_cmd_in_ert("\\begin{CodeChunk}")
1380           document.body[h - 1 : h] = ["\\begin_layout Standard"]
1381           h = h + 1
1382         # \CodeInput
1383         if j != -1:
1384           j = find_token(document.body, "\\begin_layout Code Input", j)
1385         if j != -1:
1386           endj = find_end_of_layout(document.body, j)
1387           document.body[endj : endj + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1388           document.body[endj + 3 : endj + 4] = put_cmd_in_ert("\\end{CodeInput}")
1389           document.body[endj + 13 : endj + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1390           document.body[j + 1 : j] = ["\\end_layout", "", "\\begin_layout Standard"]
1391           document.body[j : j + 1] = put_cmd_in_ert("\\begin{CodeInput}")
1392           j = j + 1
1393         # \CodeOutput
1394         if k != -1:
1395           k = find_token(document.body, "\\begin_layout Code Output", k)
1396         if k != -1:
1397           endk = find_end_of_layout(document.body, k)
1398           document.body[endk : endk + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1399           document.body[endk + 3 : endk + 4] = put_cmd_in_ert("\\end{CodeOutput}")
1400           document.body[endk + 13 : endk + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1401           document.body[k + 1 : k] = ["\\end_layout", "", "\\begin_layout Standard"]
1402           document.body[k : k + 1] = put_cmd_in_ert("\\begin{CodeOutput}")
1403           k = k + 1
1404         # \Code
1405         if m != -1:
1406           m = find_token(document.body, "\\begin_layout Code", m)
1407         if m != -1:
1408           endm = find_end_of_layout(document.body, m)
1409           document.body[endm : endm + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1410           document.body[endm + 3 : endm + 4] = put_cmd_in_ert("\\end{Code}")
1411           document.body[endm + 13 : endm + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1412           document.body[m + 1 : m] = ["\\end_layout", "", "\\begin_layout Standard"]
1413           document.body[m : m + 1] = put_cmd_in_ert("\\begin{Code}")
1414           m = m + 1
1415
1416
1417 def convert_subref(document):
1418     " converts sub: ref prefixes to subref: "
1419
1420     # 1) label insets
1421     rx = re.compile(r'^name \"sub:(.+)$')
1422     i = 0
1423     while True:
1424         i = find_token(document.body, "\\begin_inset CommandInset label", i)
1425         if i == -1:
1426             break
1427         j = find_end_of_inset(document.body, i)
1428         if j == -1:
1429             document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1430             i += 1
1431             continue
1432
1433         for p in range(i, j):
1434             m = rx.match(document.body[p])
1435             if m:
1436                 label = m.group(1)
1437                 document.body[p] = "name \"subsec:" + label
1438         i += 1
1439
1440     # 2) xref insets
1441     rx = re.compile(r'^reference \"sub:(.+)$')
1442     i = 0
1443     while True:
1444         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1445         if i == -1:
1446             return
1447         j = find_end_of_inset(document.body, i)
1448         if j == -1:
1449             document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1450             i += 1
1451             continue
1452
1453         for p in range(i, j):
1454             m = rx.match(document.body[p])
1455             if m:
1456                 label = m.group(1)
1457                 document.body[p] = "reference \"subsec:" + label
1458                 break
1459         i += 1
1460
1461
1462
1463 def revert_subref(document):
1464     " reverts subref: ref prefixes to sub: "
1465
1466     # 1) label insets
1467     rx = re.compile(r'^name \"subsec:(.+)$')
1468     i = 0
1469     while True:
1470         i = find_token(document.body, "\\begin_inset CommandInset label", i)
1471         if i == -1:
1472             break
1473         j = find_end_of_inset(document.body, i)
1474         if j == -1:
1475             document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1476             i += 1
1477             continue
1478
1479         for p in range(i, j):
1480             m = rx.match(document.body[p])
1481             if m:
1482                 label = m.group(1)
1483                 document.body[p] = "name \"sub:" + label
1484                 break
1485         i += 1
1486
1487     # 2) xref insets
1488     rx = re.compile(r'^reference \"subsec:(.+)$')
1489     i = 0
1490     while True:
1491         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1492         if i == -1:
1493             return
1494         j = find_end_of_inset(document.body, i)
1495         if j == -1:
1496             document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1497             i += 1
1498             continue
1499
1500         for p in range(i, j):
1501             m = rx.match(document.body[p])
1502             if m:
1503                 label = m.group(1)
1504                 document.body[p] = "reference \"sub:" + label
1505                 break
1506         i += 1
1507
1508
1509 def convert_nounzip(document):
1510     " remove the noUnzip parameter of graphics insets "
1511
1512     rx = re.compile(r'\s*noUnzip\s*$')
1513     i = 0
1514     while True:
1515         i = find_token(document.body, "\\begin_inset Graphics", i)
1516         if i == -1:
1517             break
1518         j = find_end_of_inset(document.body, i)
1519         if j == -1:
1520             document.warning("Malformed LyX document: Can't find end of graphics inset at line " + str(i))
1521             i += 1
1522             continue
1523
1524         k = find_re(document.body, rx, i, j)
1525         if k != -1:
1526           del document.body[k]
1527           j = j - 1
1528         i = j + 1
1529
1530
1531 def convert_revert_external_bbox(document, forward):
1532     " add units to bounding box of external insets "
1533
1534     rx = re.compile(r'^\s*boundingBox\s+\S+\s+\S+\s+\S+\s+\S+\s*$')
1535     i = 0
1536     while True:
1537         i = find_token(document.body, "\\begin_inset External", i)
1538         if i == -1:
1539             break
1540         j = find_end_of_inset(document.body, i)
1541         if j == -1:
1542             document.warning("Malformed LyX document: Can't find end of external inset at line " + str(i))
1543             i += 1
1544             continue
1545         k = find_re(document.body, rx, i, j)
1546         if k == -1:
1547             i = j + 1
1548             continue
1549         tokens = document.body[k].split()
1550         if forward:
1551             for t in range(1, 5):
1552                 tokens[t] += "bp"
1553         else:
1554             for t in range(1, 5):
1555                 tokens[t] = length_in_bp(tokens[t])
1556         document.body[k] = "\tboundingBox " + tokens[1] + " " + tokens[2] + " " + \
1557                            tokens[3] + " " + tokens[4]
1558         i = j + 1
1559
1560
1561 def convert_external_bbox(document):
1562     convert_revert_external_bbox(document, True)
1563
1564
1565 def revert_external_bbox(document):
1566     convert_revert_external_bbox(document, False)
1567
1568
1569 ##
1570 # Conversion hub
1571 #
1572
1573 supported_versions = ["2.2.0", "2.2"]
1574 convert = [
1575            [475, [convert_separator]],
1576            # nothing to do for 476: We consider it a bug that older versions
1577            # did not load amsmath automatically for these commands, and do not
1578            # want to hardcode amsmath off.
1579            [476, []],
1580            [477, []],
1581            [478, []],
1582            [479, []],
1583            [480, []],
1584            [481, [convert_dashes]],
1585            [482, [convert_phrases]],
1586            [483, [convert_specialchar]],
1587            [484, []],
1588            [485, []],
1589            [486, []],
1590            [487, []],
1591            [488, [convert_newgloss]],
1592            [489, [convert_BoxFeatures]],
1593            [490, [convert_origin]],
1594            [491, []],
1595            [492, [convert_colorbox]],
1596            [493, []],
1597            [494, []],
1598            [495, [convert_subref]],
1599            [496, [convert_nounzip]],
1600            [497, [convert_external_bbox]]
1601           ]
1602
1603 revert =  [
1604            [496, [revert_external_bbox]],
1605            [495, []], # nothing to do since the noUnzip parameter was optional
1606            [494, [revert_subref]],
1607            [493, [revert_jss]],
1608            [492, [revert_mathmulticol]],
1609            [491, [revert_colorbox]],
1610            [490, [revert_textcolor]],
1611            [489, [revert_origin]],
1612            [488, [revert_BoxFeatures]],
1613            [487, [revert_newgloss, revert_glossgroup]],
1614            [486, [revert_forest]],
1615            [485, [revert_ex_itemargs]],
1616            [484, [revert_sigplan_doi]],
1617            [483, [revert_georgian]],
1618            [482, [revert_specialchar]],
1619            [481, [revert_phrases]],
1620            [480, [revert_dashes]],
1621            [479, [revert_question_env]],
1622            [478, [revert_beamer_lemma]],
1623            [477, [revert_xarrow]],
1624            [476, [revert_swissgerman]],
1625            [475, [revert_smash]],
1626            [474, [revert_separator]]
1627           ]
1628
1629
1630 if __name__ == "__main__":
1631     pass