]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_2.py
Escape backslash
[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 #  insert_to_preamble, 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 list(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 list(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 def convert_dashes(document):
485     "convert -- and --- to \\twohyphens and \\threehyphens"
486
487     if document.backend != "latex":
488         return
489
490     i = 0
491     while i < len(document.body):
492         words = document.body[i].split()
493         if len(words) > 1 and words[0] == "\\begin_inset" and \
494            words[1] in ["CommandInset", "ERT", "External", "Formula", "Graphics", "IPA", "listings"]:
495             # must not replace anything in insets that store LaTeX contents in .lyx files
496             # (math and command insets withut overridden read() and write() methods
497             # filtering out IPA makes Text::readParToken() more simple
498             # skip ERT as well since it is not needed there
499             j = find_end_of_inset(document.body, i)
500             if j == -1:
501                 document.warning("Malformed LyX document: Can't find end of " + words[1] + " inset at line " + str(i))
502                 i += 1
503             else:
504                 i = j
505             continue
506         while True:
507             j = document.body[i].find("--")
508             if j == -1:
509                 break
510             front = document.body[i][:j]
511             back = document.body[i][j+2:]
512             # We can have an arbitrary number of consecutive hyphens.
513             # These must be split into the corresponding number of two and three hyphens
514             # We must match what LaTeX does: First try emdash, then endash, then single hyphen
515             if back.find("-") == 0:
516                 back = back[1:]
517                 if len(back) > 0:
518                     document.body.insert(i+1, back)
519                 document.body[i] = front + "\\threehyphens"
520             else:
521                 if len(back) > 0:
522                     document.body.insert(i+1, back)
523                 document.body[i] = front + "\\twohyphens"
524         i += 1
525
526
527 def revert_dashes(document):
528     "convert \\twohyphens and \\threehyphens to -- and ---"
529
530     i = 0
531     while i < len(document.body):
532         words = document.body[i].split()
533         if len(words) > 1 and words[0] == "\\begin_inset" and \
534            words[1] in ["CommandInset", "ERT", "External", "Formula", "Graphics", "IPA", "listings"]:
535             # see convert_dashes
536             j = find_end_of_inset(document.body, i)
537             if j == -1:
538                 document.warning("Malformed LyX document: Can't find end of " + words[1] + " inset at line " + str(i))
539                 i += 1
540             else:
541                 i = j
542             continue
543         replaced = False
544         if document.body[i].find("\\twohyphens") >= 0:
545             document.body[i] = document.body[i].replace("\\twohyphens", "--")
546             replaced = True
547         if document.body[i].find("\\threehyphens") >= 0:
548             document.body[i] = document.body[i].replace("\\threehyphens", "---")
549             replaced = True
550         if replaced and i+1 < len(document.body) and \
551            (document.body[i+1].find("\\") != 0 or \
552             document.body[i+1].find("\\twohyphens") == 0 or
553             document.body[i+1].find("\\threehyphens") == 0) and \
554            len(document.body[i]) + len(document.body[i+1]) <= 80:
555             document.body[i] = document.body[i] + document.body[i+1]
556             document.body[i+1:i+2] = []
557         else:
558             i += 1
559
560
561 # order is important for the last three!
562 phrases = ["LyX", "LaTeX2e", "LaTeX", "TeX"]
563
564 def is_part_of_converted_phrase(line, j, phrase):
565     "is phrase part of an already converted phrase?"
566     for p in phrases:
567         converted = "\\SpecialCharNoPassThru \\" + p
568         pos = j + len(phrase) - len(converted)
569         if pos >= 0:
570             if line[pos:pos+len(converted)] == converted:
571                 return True
572     return False
573
574
575 def convert_phrases(document):
576     "convert special phrases from plain text to \\SpecialCharNoPassThru"
577
578     if document.backend != "latex":
579         return
580
581     for phrase in phrases:
582         i = 0
583         while i < len(document.body):
584             words = document.body[i].split()
585             if len(words) > 1 and words[0] == "\\begin_inset" and \
586                words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
587                 # must not replace anything in insets that store LaTeX contents in .lyx files
588                 # (math and command insets withut overridden read() and write() methods
589                 j = find_end_of_inset(document.body, i)
590                 if j == -1:
591                     document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
592                     i += 1
593                 else:
594                     i = j
595                 continue
596             if document.body[i].find("\\") == 0:
597                 i += 1
598                 continue
599             j = document.body[i].find(phrase)
600             if j == -1:
601                 i += 1
602                 continue
603             if not is_part_of_converted_phrase(document.body[i], j, phrase):
604                 front = document.body[i][:j]
605                 back = document.body[i][j+len(phrase):]
606                 if len(back) > 0:
607                     document.body.insert(i+1, back)
608                 # We cannot use SpecialChar since we do not know whether we are outside passThru
609                 document.body[i] = front + "\\SpecialCharNoPassThru \\" + phrase
610             i += 1
611
612
613 def revert_phrases(document):
614     "convert special phrases to plain text"
615
616     i = 0
617     while i < len(document.body):
618         words = document.body[i].split()
619         if len(words) > 1 and words[0] == "\\begin_inset" and \
620            words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
621             # see convert_phrases
622             j = find_end_of_inset(document.body, i)
623             if j == -1:
624                 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
625                 i += 1
626             else:
627                 i = j
628             continue
629         replaced = False
630         for phrase in phrases:
631             # we can replace SpecialChar since LyX ensures that it cannot be inserted into passThru parts
632             if document.body[i].find("\\SpecialChar \\" + phrase) >= 0:
633                 document.body[i] = document.body[i].replace("\\SpecialChar \\" + phrase, phrase)
634                 replaced = True
635             if document.body[i].find("\\SpecialCharNoPassThru \\" + phrase) >= 0:
636                 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru \\" + phrase, phrase)
637                 replaced = True
638         if replaced and i+1 < len(document.body) and \
639            (document.body[i+1].find("\\") != 0 or \
640             document.body[i+1].find("\\SpecialChar") == 0) and \
641            len(document.body[i]) + len(document.body[i+1]) <= 80:
642             document.body[i] = document.body[i] + document.body[i+1]
643             document.body[i+1:i+2] = []
644             i -= 1
645         i += 1
646
647
648 def convert_specialchar_internal(document, forward):
649     specialchars = {"\\-":"softhyphen", "\\textcompwordmark{}":"ligaturebreak", \
650         "\\@.":"endofsentence", "\\ldots{}":"ldots", \
651         "\\menuseparator":"menuseparator", "\\slash{}":"breakableslash", \
652         "\\nobreakdash-":"nobreakdash", "\\LyX":"LyX", \
653         "\\TeX":"TeX", "\\LaTeX2e":"LaTeX2e", \
654         "\\LaTeX":"LaTeX" # must be after LaTeX2e
655     }
656
657     i = 0
658     while i < len(document.body):
659         words = document.body[i].split()
660         if len(words) > 1 and words[0] == "\\begin_inset" and \
661            words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]:
662             # see convert_phrases
663             j = find_end_of_inset(document.body, i)
664             if j == -1:
665                 document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
666                 i += 1
667             else:
668                 i = j
669             continue
670         for key, value in specialchars.iteritems():
671             if forward:
672                 document.body[i] = document.body[i].replace("\\SpecialChar " + key, "\\SpecialChar " + value)
673                 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + key, "\\SpecialCharNoPassThru " + value)
674             else:
675                 document.body[i] = document.body[i].replace("\\SpecialChar " + value, "\\SpecialChar " + key)
676                 document.body[i] = document.body[i].replace("\\SpecialCharNoPassThru " + value, "\\SpecialCharNoPassThru " + key)
677         i += 1
678
679
680 def convert_specialchar(document):
681     "convert special characters to new syntax"
682     convert_specialchar_internal(document, True)
683
684
685 def revert_specialchar(document):
686     "convert special characters to old syntax"
687     convert_specialchar_internal(document, False)
688
689
690 def revert_georgian(document):
691     "Set the document language to English but assure Georgian output"
692
693     if document.language == "georgian":
694         document.language = "english"
695         i = find_token(document.header, "\\language georgian", 0)
696         if i != -1:
697             document.header[i] = "\\language english"
698         j = find_token(document.header, "\\language_package default", 0)
699         if j != -1:
700             document.header[j] = "\\language_package babel"
701         k = find_token(document.header, "\\options", 0)
702         if k != -1:
703             document.header[k] = document.header[k].replace("\\options", "\\options georgian,")
704         else:
705             l = find_token(document.header, "\\use_default_options", 0)
706             document.header.insert(l + 1, "\\options georgian")
707
708
709 def revert_sigplan_doi(document):
710     " Reverts sigplanconf DOI layout to ERT "
711
712     if document.textclass != "sigplanconf":
713         return
714
715     i = 0
716     while True:
717         i = find_token(document.body, "\\begin_layout DOI", i)
718         if i == -1:
719             return
720         j = find_end_of_layout(document.body, i)
721         if j == -1:
722             document.warning("Malformed LyX document: Can't find end of DOI layout")
723             i += 1
724             continue
725
726         content = lyx2latex(document, document.body[i:j + 1])
727         add_to_preamble(document, ["\\doi{" + content + "}"])
728         del document.body[i:j + 1]
729         # no need to reset i
730
731
732 def revert_ex_itemargs(document):
733     " Reverts \\item arguments of the example environments (Linguistics module) to TeX-code "
734
735     # Do we use the linguistics module?
736     have_mod = False
737     mods = document.get_module_list()
738     for mod in mods:
739         if mod == "linguistics":
740             have_mod = True
741             continue
742
743     if not have_mod:
744         return
745
746     i = 0
747     example_layouts = ["Numbered Examples (consecutive)", "Subexample"]
748     while True:
749         i = find_token(document.body, "\\begin_inset Argument item:", i)
750         if i == -1:
751             return
752         j = find_end_of_inset(document.body, i)
753         # Find containing paragraph layout
754         parent = get_containing_layout(document.body, i)
755         if parent == False:
756             document.warning("Malformed LyX document: Can't find parent paragraph layout")
757             i += 1
758             continue
759         parbeg = parent[3]
760         layoutname = parent[0]
761         if layoutname in example_layouts:
762             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
763             endPlain = find_end_of_layout(document.body, beginPlain)
764             content = document.body[beginPlain + 1 : endPlain]
765             del document.body[i:j+1]
766             subst = put_cmd_in_ert("[") + content + put_cmd_in_ert("]")
767             document.body[parbeg : parbeg] = subst
768         i += 1
769
770
771 def revert_forest(document):
772     " Reverts the forest environment (Linguistics module) to TeX-code "
773
774     # Do we use the linguistics module?
775     have_mod = False
776     mods = document.get_module_list()
777     for mod in mods:
778         if mod == "linguistics":
779             have_mod = True
780             continue
781
782     if not have_mod:
783         return
784
785     i = 0
786     while True:
787         i = find_token(document.body, "\\begin_inset Flex Structure Tree", i)
788         if i == -1:
789             return
790         j = find_end_of_inset(document.body, i)
791         if j == -1:
792             document.warning("Malformed LyX document: Can't find end of Structure Tree inset")
793             i += 1
794             continue
795
796         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
797         endPlain = find_end_of_layout(document.body, beginPlain)
798         content = lyx2latex(document, document.body[beginPlain : endPlain])
799
800         add_to_preamble(document, ["\\usepackage{forest}"])
801
802         document.body[i:j + 1] = ["\\begin_inset ERT", "status collapsed", "",
803                 "\\begin_layout Plain Layout", "", "\\backslash", 
804                 "begin{forest}", "\\end_layout", "", "\\begin_layout Plain Layout",
805                 content, "\\end_layout", "", "\\begin_layout Plain Layout",
806                 "\\backslash", "end{forest}", "", "\\end_layout", "", "\\end_inset"]
807         # no need to reset i
808
809
810 def revert_glossgroup(document):
811     " Reverts the GroupGlossedWords inset (Linguistics module) to TeX-code "
812
813     # Do we use the linguistics module?
814     have_mod = False
815     mods = document.get_module_list()
816     for mod in mods:
817         if mod == "linguistics":
818             have_mod = True
819             continue
820
821     if not have_mod:
822         return
823
824     i = 0
825     while True:
826         i = find_token(document.body, "\\begin_inset Flex GroupGlossedWords", i)
827         if i == -1:
828             return
829         j = find_end_of_inset(document.body, i)
830         if j == -1:
831             document.warning("Malformed LyX document: Can't find end of GroupGlossedWords inset")
832             i += 1
833             continue
834
835         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
836         endPlain = find_end_of_layout(document.body, beginPlain)
837         content = lyx2latex(document, document.body[beginPlain : endPlain])
838         document.warning("content: %s" % content)
839
840         document.body[i:j + 1] = ["{", "", content, "", "}"]
841         # no need to reset i
842
843
844 def revert_newgloss(document):
845     " Reverts the new Glosse insets (Linguistics module) to the old format "
846
847     # Do we use the linguistics module?
848     have_mod = False
849     mods = document.get_module_list()
850     for mod in mods:
851         if mod == "linguistics":
852             have_mod = True
853             continue
854
855     if not have_mod:
856         return
857
858     glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
859     for glosse in glosses:
860         i = 0
861         while True:
862             i = find_token(document.body, glosse, i)
863             if i == -1:
864                 break
865             j = find_end_of_inset(document.body, i)
866             if j == -1:
867                 document.warning("Malformed LyX document: Can't find end of Glosse inset")
868                 i += 1
869                 continue
870
871             arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
872             endarg = find_end_of_inset(document.body, arg)
873             argcontent = ""
874             if arg != -1:
875                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
876                 if argbeginPlain == -1:
877                     document.warning("Malformed LyX document: Can't find arg plain Layout")
878                     i += 1
879                     continue
880                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
881                 argcontent = lyx2latex(document, document.body[argbeginPlain : argendPlain - 2])
882
883                 document.body[j:j] = ["", "\\begin_layout Plain Layout","\\backslash", "glt ",
884                     argcontent, "\\end_layout"]
885
886                 # remove Arg insets and paragraph, if it only contains this inset
887                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
888                     del document.body[arg - 1 : endarg + 4]
889                 else:
890                     del document.body[arg : endarg + 1]
891
892             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
893             endPlain = find_end_of_layout(document.body, beginPlain)
894             content = lyx2latex(document, document.body[beginPlain : endPlain])
895
896             document.body[beginPlain + 1:endPlain] = [content]
897             i = beginPlain + 1
898
899
900 def convert_newgloss(document):
901     " Converts Glosse insets (Linguistics module) to the new format "
902
903     # Do we use the linguistics module?
904     have_mod = False
905     mods = document.get_module_list()
906     for mod in mods:
907         if mod == "linguistics":
908             have_mod = True
909             continue
910
911     if not have_mod:
912         return
913
914     glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
915     for glosse in glosses:
916         i = 0
917         while True:
918             i = find_token(document.body, glosse, i)
919             if i == -1:
920                 break
921             j = find_end_of_inset(document.body, i)
922             if j == -1:
923                 document.warning("Malformed LyX document: Can't find end of Glosse inset")
924                 i += 1
925                 continue
926
927             k = i
928             while True:
929                 argcontent = []
930                 beginPlain = find_token(document.body, "\\begin_layout Plain Layout", k, j)
931                 if beginPlain == -1:
932                     break
933                 endPlain = find_end_of_layout(document.body, beginPlain)
934                 if endPlain == -1:
935                     document.warning("Malformed LyX document: Can't find end of Glosse layout")
936                     i += 1
937                     continue
938
939                 glt  = find_token(document.body, "\\backslash", beginPlain, endPlain)
940                 if glt != -1 and document.body[glt + 1].startswith("glt"):
941                     document.body[glt + 1] = document.body[glt + 1].lstrip("glt").lstrip()
942                     argcontent = document.body[glt + 1 : endPlain]
943                     document.body[beginPlain + 1 : endPlain] = ["\\begin_inset Argument 1", "status open", "",
944                         "\\begin_layout Plain Layout", "\\begin_inset ERT", "status open", "",
945                         "\\begin_layout Plain Layout", ""] + argcontent + ["\\end_layout", "", "\\end_inset", "",
946                         "\\end_layout", "", "\\end_inset"]
947                 else:
948                     content = document.body[beginPlain + 1 : endPlain]
949                     document.body[beginPlain + 1 : endPlain] = ["\\begin_inset ERT", "status open", "",
950                         "\\begin_layout Plain Layout"] + content + ["\\end_layout", "", "\\end_inset"]
951
952                 endPlain = find_end_of_layout(document.body, beginPlain)
953                 k = endPlain
954                 j = find_end_of_inset(document.body, i)
955
956             i = endPlain + 1
957
958
959 def convert_BoxFeatures(document):
960     " adds new box features "
961
962     i = 0
963     while True:
964         i = find_token(document.body, "height_special", i)
965         if i == -1:
966             return
967         document.body[i+1:i+1] = ['thickness "0.4pt"', 'separation "3pt"', 'shadowsize "4pt"']
968         i = i + 4
969
970
971 def revert_BoxFeatures(document):
972     " outputs new box features as TeX code "
973
974     i = 0
975     defaultSep = "3pt"
976     defaultThick = "0.4pt"
977     defaultShadow = "4pt"
978     while True:
979         i = find_token(document.body, "height_special", i)
980         if i == -1:
981             return
982         # read out the values
983         beg = document.body[i+1].find('"');
984         end = document.body[i+1].rfind('"');
985         thickness = document.body[i+1][beg+1:end];
986         beg = document.body[i+2].find('"');
987         end = document.body[i+2].rfind('"');
988         separation = document.body[i+2][beg+1:end];
989         beg = document.body[i+3].find('"');
990         end = document.body[i+3].rfind('"');
991         shadowsize = document.body[i+3][beg+1:end];
992         # delete the specification
993         del document.body[i+1:i+4]
994         # output ERT
995         # first output the closing brace
996         if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
997             document.body[i + 10 : i + 10] = put_cmd_in_ert("}")
998         # now output the lengths
999         if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
1000             document.body[i - 10 : i - 10] = put_cmd_in_ert("{")
1001         if thickness != defaultThick:
1002             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness]
1003         if separation != defaultSep and thickness == defaultThick:
1004             document.body[i - 5 : i - 4] = ["{\\backslash fboxsep " + separation]
1005         if separation != defaultSep and thickness != defaultThick:
1006             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation]
1007         if shadowsize != defaultShadow and separation == defaultSep and thickness == defaultThick:
1008             document.body[i - 5 : i - 4] = ["{\\backslash shadowsize " + shadowsize]
1009         if shadowsize != defaultShadow and separation != defaultSep and thickness == defaultThick:
1010             document.body[i - 5 : i - 4] = ["{\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1011         if shadowsize != defaultShadow and separation == defaultSep and thickness != defaultThick:
1012             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash shadowsize " + shadowsize]
1013         if shadowsize != defaultShadow and separation != defaultSep and thickness != defaultThick:
1014             document.body[i - 5 : i - 4] = ["{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize]
1015         i = i + 11
1016
1017
1018 def convert_origin(document):
1019     " Insert the origin tag "
1020
1021     i = find_token(document.header, "\\textclass ", 0)
1022     if i == -1:
1023         document.warning("Malformed LyX document: No \\textclass!!")
1024         return;
1025     if document.dir == "":
1026         origin = "stdin"
1027     else:
1028         origin = document.dir.replace('\\', '/') + '/'
1029         if os.name != 'nt':
1030             origin = unicode(origin, sys.getfilesystemencoding())
1031     document.header[i:i] = ["\\origin " + origin]
1032
1033
1034 def revert_origin(document):
1035     " Remove the origin tag "
1036
1037     i = find_token(document.header, "\\origin ", 0)
1038     if i == -1:
1039         document.warning("Malformed LyX document: No \\origin!!")
1040         return;
1041     del document.header[i]
1042
1043
1044 color_names = ["brown", "darkgray", "gray", \
1045                "lightgray", "lime", "olive", "orange", \
1046                "pink", "purple", "teal", "violet"]
1047
1048 def revert_textcolor(document):
1049     " revert new \\texcolor colors to TeX code "
1050
1051     i = 0
1052     j = 0
1053     xcolor = False
1054     add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}"])
1055     while True:
1056         i = find_token(document.body, "\\color ", i)
1057         if i == -1:
1058             return
1059         else:
1060             for color in list(color_names):
1061                 if document.body[i] == "\\color " + color:
1062                     # register that xcolor must be loaded in the preamble
1063                     if xcolor == False:
1064                         xcolor = True
1065                         add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\usepackage{xcolor}}"])
1066                     # find the next \\color and/or the next \\end_layout
1067                     j = find_token(document.body, "\\color", i + 1)
1068                     k = find_token(document.body, "\\end_layout", i + 1)
1069                     if j == -1 and k != -1:
1070                         j = k +1 
1071                     # output TeX code
1072                     # first output the closing brace
1073                     if k < j:
1074                         document.body[k: k] = put_cmd_in_ert("}")
1075                     else:
1076                         document.body[j: j] = put_cmd_in_ert("}")
1077                     # now output the \textcolor command
1078                     document.body[i : i + 1] = put_cmd_in_ert("\\textcolor{" + color + "}{")
1079         i = i + 1
1080
1081
1082 def convert_colorbox(document):
1083     " adds color settings for boxes "
1084
1085     i = 0
1086     while True:
1087         i = find_token(document.body, "shadowsize", i)
1088         if i == -1:
1089             return
1090         document.body[i+1:i+1] = ['framecolor "black"', 'backgroundcolor "none"']
1091         i = i + 3
1092
1093
1094 def revert_colorbox(document):
1095     " outputs color settings for boxes as TeX code "
1096
1097     binset = 0
1098     defaultframecolor = "black"
1099     defaultbackcolor = "none"
1100     while True:
1101         binset = find_token(document.body, "\\begin_inset Box", binset)
1102         if binset == -1:
1103             return
1104
1105         einset = find_end_of_inset(document.body, binset)
1106         if einset == -1:
1107             document.warning("Malformed LyX document: Can't find end of box inset!")
1108             binset += 1
1109             continue
1110
1111         blay = find_token(document.body, "\\begin_layout", binset, einset)
1112         if blay == -1:
1113             document.warning("Malformed LyX document: Can't find start of layout!")
1114             binset = einset
1115             continue
1116
1117         # doing it this way, we make sure only to find a framecolor option
1118         frame = find_token(document.body, "framecolor", binset, blay)
1119         if frame == -1:
1120             binset = einset
1121             continue
1122
1123         beg = document.body[frame].find('"')
1124         end = document.body[frame].rfind('"')
1125         framecolor = document.body[frame][beg+1:end]
1126
1127         # this should be on the next line
1128         bgcolor = frame + 1
1129         beg = document.body[bgcolor].find('"')
1130         end = document.body[bgcolor].rfind('"')
1131         backcolor = document.body[bgcolor][beg+1:end]
1132
1133         # delete those bits
1134         del document.body[frame:frame+2]
1135         # adjust end of inset
1136         einset -= 2
1137
1138         if document.body[binset] == "\\begin_inset Box Boxed" and \
1139             framecolor != defaultframecolor:
1140           document.body[binset] = "\\begin_inset Box Frameless"
1141
1142         # output TeX code
1143         # first output the closing brace
1144         if framecolor == defaultframecolor and backcolor == defaultbackcolor:
1145             # nothing needed
1146             pass
1147         else:
1148             document.body[einset + 1 : einset + 1] = put_cmd_in_ert("}")
1149             if framecolor != defaultframecolor:
1150                 document.body[binset:binset] = put_cmd_in_ert("\\backslash fcolorbox{" + framecolor + "}{" + backcolor + "}{")
1151             else:
1152               document.body[binset:binset] = put_cmd_in_ert("\\backslash colorbox{" + backcolor + "}{")
1153
1154         binset = einset
1155
1156
1157 def revert_mathmulticol(document):
1158     " Convert formulas to ERT if they contain multicolumns "
1159
1160     i = 0
1161     while True:
1162         i = find_token(document.body, '\\begin_inset Formula', i)
1163         if i == -1:
1164             return
1165         j = find_end_of_inset(document.body, i)
1166         if j == -1:
1167             document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
1168             i += 1
1169             continue
1170         lines = document.body[i:j]
1171         lines[0] = lines[0].replace('\\begin_inset Formula', '').lstrip()
1172         code = "\n".join(lines)
1173         converted = False
1174         k = 0
1175         n = 0
1176         while n >= 0:
1177             n = code.find("\\multicolumn", k)
1178             # no need to convert degenerated multicolumn cells,
1179             # they work in old LyX versions as "math ERT"
1180             if n != -1 and code.find("\\multicolumn{1}", k) != n:
1181                 ert = put_cmd_in_ert(code)
1182                 document.body[i:j+1] = ert
1183                 converted = True
1184                 break
1185             else:
1186                 k = n + 12
1187         if converted:
1188             i = find_end_of_inset(document.body, i)
1189         else:
1190             i = j
1191
1192
1193 def revert_jss(document):
1194     " Reverts JSS In_Preamble commands to ERT in preamble "
1195
1196     if document.textclass != "jss":
1197         return
1198
1199     h = 0
1200     m = 0
1201     j = 0
1202     k = 0
1203     n = 0
1204     while True:
1205       # at first revert the inset layouts because they can be part of the In_Preamble layouts
1206       while m != -1 or j != -1 or h != -1 or k != -1 or n != -1:
1207         # \pkg
1208         if h != -1:
1209           h = find_token(document.body, "\\begin_inset Flex Pkg", h)
1210         if h != -1:
1211           endh = find_end_of_inset(document.body, h)
1212           document.body[endh - 2 : endh + 1] = put_cmd_in_ert("}")
1213           document.body[h : h + 4] = put_cmd_in_ert("\\pkg{")
1214           h = h + 5
1215         # \proglang
1216         if m != -1:
1217           m = find_token(document.body, "\\begin_inset Flex Proglang", m)
1218         if m != -1:
1219           endm = find_end_of_inset(document.body, m)
1220           document.body[endm - 2 : endm + 1] = put_cmd_in_ert("}")
1221           document.body[m : m + 4] = put_cmd_in_ert("\\proglang{")
1222           m = m + 5
1223         # \code
1224         if j != -1:
1225           j = find_token(document.body, "\\begin_inset Flex Code", j)
1226         if j != -1:
1227           # assure that we are not in a Code Chunk inset
1228           if document.body[j][-1] == "e":
1229               endj = find_end_of_inset(document.body, j)
1230               document.body[endj - 2 : endj + 1] = put_cmd_in_ert("}")
1231               document.body[j : j + 4] = put_cmd_in_ert("\\code{")
1232               j = j + 5
1233           else:
1234               j = j + 1
1235         # \email
1236         if k != -1:
1237           k = find_token(document.body, "\\begin_inset Flex E-mail", k)
1238         if k != -1:
1239           endk = find_end_of_inset(document.body, k)
1240           document.body[endk - 2 : endk + 1] = put_cmd_in_ert("}")
1241           document.body[k : k + 4] = put_cmd_in_ert("\\email{")
1242           k = k + 5
1243         # \url
1244         if n != -1:
1245           n = find_token(document.body, "\\begin_inset Flex URL", n)
1246         if n != -1:
1247           endn = find_end_of_inset(document.body, n)
1248           document.body[endn - 2 : endn + 1] = put_cmd_in_ert("}")
1249           document.body[n : n + 4] = put_cmd_in_ert("\\url{")
1250           n = n + 5
1251       # now revert the In_Preamble layouts
1252       # \title
1253       i = find_token(document.body, "\\begin_layout Title", 0)
1254       if i == -1:
1255         return
1256       j = find_end_of_layout(document.body, i)
1257       if j == -1:
1258         document.warning("Malformed LyX document: Can't find end of Title layout")
1259         i += 1
1260         continue
1261       content = lyx2latex(document, document.body[i:j + 1])
1262       add_to_preamble(document, ["\\title{" + content + "}"])
1263       del document.body[i:j + 1]
1264       # \author
1265       i = find_token(document.body, "\\begin_layout Author", 0)
1266       if i == -1:
1267         return
1268       j = find_end_of_layout(document.body, i)
1269       if j == -1:
1270         document.warning("Malformed LyX document: Can't find end of Author layout")
1271         i += 1
1272         continue
1273       content = lyx2latex(document, document.body[i:j + 1])
1274       add_to_preamble(document, ["\\author{" + content + "}"])
1275       del document.body[i:j + 1]
1276       # \Plainauthor
1277       i = find_token(document.body, "\\begin_layout Plain Author", 0)
1278       if i == -1:
1279         return
1280       j = find_end_of_layout(document.body, i)
1281       if j == -1:
1282         document.warning("Malformed LyX document: Can't find end of Plain Author layout")
1283         i += 1
1284         continue
1285       content = lyx2latex(document, document.body[i:j + 1])
1286       add_to_preamble(document, ["\\Plainauthor{" + content + "}"])
1287       del document.body[i:j + 1]
1288       # \Plaintitle
1289       i = find_token(document.body, "\\begin_layout Plain Title", 0)
1290       if i == -1:
1291         return
1292       j = find_end_of_layout(document.body, i)
1293       if j == -1:
1294         document.warning("Malformed LyX document: Can't find end of Plain Title layout")
1295         i += 1
1296         continue
1297       content = lyx2latex(document, document.body[i:j + 1])
1298       add_to_preamble(document, ["\\Plaintitle{" + content + "}"])
1299       del document.body[i:j + 1]
1300       # \Shorttitle
1301       i = find_token(document.body, "\\begin_layout Short Title", 0)
1302       if i == -1:
1303         return
1304       j = find_end_of_layout(document.body, i)
1305       if j == -1:
1306         document.warning("Malformed LyX document: Can't find end of Short Title layout")
1307         i += 1
1308         continue
1309       content = lyx2latex(document, document.body[i:j + 1])
1310       add_to_preamble(document, ["\\Shorttitle{" + content + "}"])
1311       del document.body[i:j + 1]
1312       # \Abstract
1313       i = find_token(document.body, "\\begin_layout Abstract", 0)
1314       if i == -1:
1315         return
1316       j = find_end_of_layout(document.body, i)
1317       if j == -1:
1318         document.warning("Malformed LyX document: Can't find end of Abstract layout")
1319         i += 1
1320         continue
1321       content = lyx2latex(document, document.body[i:j + 1])
1322       add_to_preamble(document, ["\\Abstract{" + content + "}"])
1323       del document.body[i:j + 1]
1324       # \Keywords
1325       i = find_token(document.body, "\\begin_layout Keywords", 0)
1326       if i == -1:
1327         return
1328       j = find_end_of_layout(document.body, i)
1329       if j == -1:
1330         document.warning("Malformed LyX document: Can't find end of Keywords layout")
1331         i += 1
1332         continue
1333       content = lyx2latex(document, document.body[i:j + 1])
1334       add_to_preamble(document, ["\\Keywords{" + content + "}"])
1335       del document.body[i:j + 1]
1336       # \Plainkeywords
1337       i = find_token(document.body, "\\begin_layout Plain Keywords", 0)
1338       if i == -1:
1339         return
1340       j = find_end_of_layout(document.body, i)
1341       if j == -1:
1342         document.warning("Malformed LyX document: Can't find end of Plain Keywords layout")
1343         i += 1
1344         continue
1345       content = lyx2latex(document, document.body[i:j + 1])
1346       add_to_preamble(document, ["\\Plainkeywords{" + content + "}"])
1347       del document.body[i:j + 1]
1348       # \Address
1349       i = find_token(document.body, "\\begin_layout Address", 0)
1350       if i == -1:
1351         return
1352       j = find_end_of_layout(document.body, i)
1353       if j == -1:
1354         document.warning("Malformed LyX document: Can't find end of Address layout")
1355         i += 1
1356         continue
1357       content = lyx2latex(document, document.body[i:j + 1])
1358       add_to_preamble(document, ["\\Address{" + content + "}"])
1359       del document.body[i:j + 1]
1360       # finally handle the code layouts
1361       h = 0
1362       m = 0
1363       j = 0
1364       k = 0
1365       while m != -1 or j != -1 or h != -1 or k != -1:
1366         # \CodeChunk
1367         if h != -1:
1368           h = find_token(document.body, "\\begin_inset Flex Code Chunk", h)
1369         if h != -1:
1370           endh = find_end_of_inset(document.body, h)
1371           document.body[endh + 1 : endh] = ["\\end_layout"]
1372           document.body[endh : endh + 1] = put_cmd_in_ert("\\end{CodeChunk}")
1373           document.body[h : h + 3] = put_cmd_in_ert("\\begin{CodeChunk}")
1374           document.body[h - 1 : h] = ["\\begin_layout Standard"]
1375           h = h + 1
1376         # \CodeInput
1377         if j != -1:
1378           j = find_token(document.body, "\\begin_layout Code Input", j)
1379         if j != -1:
1380           endj = find_end_of_layout(document.body, j)
1381           document.body[endj : endj + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1382           document.body[endj + 3 : endj + 4] = put_cmd_in_ert("\\end{CodeInput}")
1383           document.body[endj + 13 : endj + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1384           document.body[j + 1 : j] = ["\\end_layout", "", "\\begin_layout Standard"]
1385           document.body[j : j + 1] = put_cmd_in_ert("\\begin{CodeInput}")
1386           j = j + 1
1387         # \CodeOutput
1388         if k != -1:
1389           k = find_token(document.body, "\\begin_layout Code Output", k)
1390         if k != -1:
1391           endk = find_end_of_layout(document.body, k)
1392           document.body[endk : endk + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1393           document.body[endk + 3 : endk + 4] = put_cmd_in_ert("\\end{CodeOutput}")
1394           document.body[endk + 13 : endk + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1395           document.body[k + 1 : k] = ["\\end_layout", "", "\\begin_layout Standard"]
1396           document.body[k : k + 1] = put_cmd_in_ert("\\begin{CodeOutput}")
1397           k = k + 1
1398         # \Code
1399         if m != -1:
1400           m = find_token(document.body, "\\begin_layout Code", m)
1401         if m != -1:
1402           endm = find_end_of_layout(document.body, m)
1403           document.body[endm : endm + 1] = ["\\end_layout", "", "\\begin_layout Standard"]
1404           document.body[endm + 3 : endm + 4] = put_cmd_in_ert("\\end{Code}")
1405           document.body[endm + 13 : endm + 13] = ["\\end_layout", "", "\\begin_layout Standard"]
1406           document.body[m + 1 : m] = ["\\end_layout", "", "\\begin_layout Standard"]
1407           document.body[m : m + 1] = put_cmd_in_ert("\\begin{Code}")
1408           m = m + 1
1409
1410
1411 def convert_subref(document):
1412     " converts sub: ref prefixes to subref: "
1413
1414     # 1) label insets
1415     rx = re.compile(r'^name \"sub:(.+)$')
1416     i = 0
1417     while True:
1418         i = find_token(document.body, "\\begin_inset CommandInset label", i)
1419         if i == -1:
1420             break
1421         j = find_end_of_inset(document.body, i)
1422         if j == -1:
1423             document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1424             i += 1
1425             continue
1426
1427         for p in range(i, j):
1428             m = rx.match(document.body[p])
1429             if m:
1430                 label = m.group(1)
1431                 document.body[p] = "name \"subsec:" + label
1432         i += 1
1433
1434     # 2) xref insets
1435     rx = re.compile(r'^reference \"sub:(.+)$')
1436     i = 0
1437     while True:
1438         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1439         if i == -1:
1440             return
1441         j = find_end_of_inset(document.body, i)
1442         if j == -1:
1443             document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1444             i += 1
1445             continue
1446
1447         for p in range(i, j):
1448             m = rx.match(document.body[p])
1449             if m:
1450                 label = m.group(1)
1451                 document.body[p] = "reference \"subsec:" + label
1452                 break
1453         i += 1
1454
1455
1456
1457 def revert_subref(document):
1458     " reverts subref: ref prefixes to sub: "
1459
1460     # 1) label insets
1461     rx = re.compile(r'^name \"subsec:(.+)$')
1462     i = 0
1463     while True:
1464         i = find_token(document.body, "\\begin_inset CommandInset label", i)
1465         if i == -1:
1466             break
1467         j = find_end_of_inset(document.body, i)
1468         if j == -1:
1469             document.warning("Malformed LyX document: Can't find end of Label inset at line " + str(i))
1470             i += 1
1471             continue
1472
1473         for p in range(i, j):
1474             m = rx.match(document.body[p])
1475             if m:
1476                 label = m.group(1)
1477                 document.body[p] = "name \"sub:" + label
1478                 break
1479         i += 1
1480
1481     # 2) xref insets
1482     rx = re.compile(r'^reference \"subsec:(.+)$')
1483     i = 0
1484     while True:
1485         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1486         if i == -1:
1487             return
1488         j = find_end_of_inset(document.body, i)
1489         if j == -1:
1490             document.warning("Malformed LyX document: Can't find end of Ref inset at line " + str(i))
1491             i += 1
1492             continue
1493
1494         for p in range(i, j):
1495             m = rx.match(document.body[p])
1496             if m:
1497                 label = m.group(1)
1498                 document.body[p] = "reference \"sub:" + label
1499                 break
1500         i += 1
1501
1502
1503 ##
1504 # Conversion hub
1505 #
1506
1507 supported_versions = ["2.2.0", "2.2"]
1508 convert = [
1509            [475, [convert_separator]],
1510            # nothing to do for 476: We consider it a bug that older versions
1511            # did not load amsmath automatically for these commands, and do not
1512            # want to hardcode amsmath off.
1513            [476, []],
1514            [477, []],
1515            [478, []],
1516            [479, []],
1517            [480, []],
1518            [481, [convert_dashes]],
1519            [482, [convert_phrases]],
1520            [483, [convert_specialchar]],
1521            [484, []],
1522            [485, []],
1523            [486, []],
1524            [487, []],
1525            [488, [convert_newgloss]],
1526            [489, [convert_BoxFeatures]],
1527            [490, [convert_origin]],
1528            [491, []],
1529            [492, [convert_colorbox]],
1530            [493, []],
1531            [494, []],
1532            [495, [convert_subref]]
1533           ]
1534
1535 revert =  [
1536            [494, [revert_subref]],
1537            [493, [revert_jss]],
1538            [492, [revert_mathmulticol]],
1539            [491, [revert_colorbox]],
1540            [490, [revert_textcolor]],
1541            [489, [revert_origin]],
1542            [488, [revert_BoxFeatures]],
1543            [487, [revert_newgloss, revert_glossgroup]],
1544            [486, [revert_forest]],
1545            [485, [revert_ex_itemargs]],
1546            [484, [revert_sigplan_doi]],
1547            [483, [revert_georgian]],
1548            [482, [revert_specialchar]],
1549            [481, [revert_phrases]],
1550            [480, [revert_dashes]],
1551            [479, [revert_question_env]],
1552            [478, [revert_beamer_lemma]],
1553            [477, [revert_xarrow]],
1554            [476, [revert_swissgerman]],
1555            [475, [revert_smash]],
1556            [474, [revert_separator]]
1557           ]
1558
1559
1560 if __name__ == "__main__":
1561     pass