]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_0.py
33e2e9aeab3d196798fe94f7122ded35e7468d30
[lyx.git] / lib / lyx2lyx / lyx_2_0.py
1 # -*- coding: utf-8 -*-
2 # This file is part of lyx2lyx
3 # -*- coding: utf-8 -*-
4 # Copyright (C) 2010 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.0"""
21
22 import re, string
23 import unicodedata
24 import sys, os
25
26 from parser_tools import find_token, find_end_of, find_tokens, get_value, get_value_string
27
28 ####################################################################
29 # Private helper functions
30
31 def remove_option(document, m, option):
32     l = document.body[m].find(option)
33     if l != -1:
34         val = document.body[m][l:].split('"')[1]
35         document.body[m] = document.body[m][:l - 1] + document.body[m][l+len(option + '="' + val + '"'):]
36     return l
37
38 def find_end_of_inset(lines, i):
39     " Find end of inset, where lines[i] is included."
40     return find_end_of(lines, i, "\\begin_inset", "\\end_inset")
41
42
43 def find_end_of_layout(lines, i):
44     " Find end of layout, where lines[i] is included."
45     return find_end_of(lines, i, "\\begin_layout", "\\end_layout")
46
47
48 # Note that text can be either a list of lines or a single line.
49 def add_to_preamble(document, text):
50     """ Add text to the preamble if it is not already there.
51     Only the first line is checked!"""
52
53     if not type(text) is list:
54       # split on \n just in case
55       # it'll give us the one element list we want
56       # if there's no \n, too
57       text = text.split('\n')
58
59     if find_token(document.preamble, text[0], 0) != -1:
60         return
61
62     document.preamble.extend(text)
63
64
65 # Note that text can be either a list of lines or a single line.
66 # It should really be a list.
67 def insert_to_preamble(index, document, text):
68     """ Insert text to the preamble at a given line"""
69     
70     if not type(text) is list:
71       # split on \n just in case
72       # it'll give us the one element list we want
73       # if there's no \n, too
74       text = text.split('\n')
75
76     document.preamble[index:index] = text
77
78
79 def read_unicodesymbols():
80     " Read the unicodesymbols list of unicode characters and corresponding commands."
81     pathname = os.path.abspath(os.path.dirname(sys.argv[0]))
82     fp = open(os.path.join(pathname.strip('lyx2lyx'), 'unicodesymbols'))
83     spec_chars = []
84     # Two backslashes, followed by some non-word character, and then a character
85     # in brackets. The idea is to check for constructs like: \"{u}, which is how
86     # they are written in the unicodesymbols file; but they can also be written
87     # as: \"u or even \" u.
88     r = re.compile(r'\\\\(\W)\{(\w)\}')
89     for line in fp.readlines():
90         if line[0] != '#' and line.strip() != "":
91             line=line.replace(' "',' ') # remove all quotation marks with spaces before
92             line=line.replace('" ',' ') # remove all quotation marks with spaces after
93             line=line.replace(r'\"','"') # replace \" by " (for characters with diaeresis)
94             try:
95                 [ucs4,command,dead] = line.split(None,2)
96                 if command[0:1] != "\\":
97                     continue
98                 spec_chars.append([command, unichr(eval(ucs4))])
99             except:
100                 continue
101             m = r.match(command)
102             if m != None:
103                 command = "\\\\"
104                 # If the character is a double-quote, then we need to escape it, too,
105                 # since it is done that way in the LyX file.
106                 if m.group(1) == "\"":
107                     command += "\\"
108                 commandbl = command
109                 command += m.group(1) + m.group(2)
110                 commandbl += m.group(1) + ' ' + m.group(2)
111                 spec_chars.append([command, unichr(eval(ucs4))])
112                 spec_chars.append([commandbl, unichr(eval(ucs4))])
113     fp.close()
114     return spec_chars
115
116
117 unicode_reps = read_unicodesymbols()
118
119
120 # DO NOT USE THIS ROUTINE ANY MORE. Better yet, replace the uses that
121 # have been made of it with uses of put_cmd_in_ert.
122 def old_put_cmd_in_ert(string):
123     for rep in unicode_reps:
124         string = string.replace(rep[1], rep[0].replace('\\\\', '\\'))
125     string = string.replace('\\', "\\backslash\n")
126     string = "\\begin_inset ERT\nstatus collapsed\n\\begin_layout Plain Layout\n" \
127       + string + "\n\\end_layout\n\\end_inset"
128     return string
129
130
131 # This routine wraps some content in an ERT inset. 
132 #
133 # NOTE: The function accepts either a single string or a LIST of strings as
134 # argument. But it returns a LIST of strings, split on \n, so that it does 
135 # not have embedded newlines.
136
137 # This is how lyx2lyx represents a LyX document: as a list of strings, 
138 # each representing a line of a LyX file. Embedded newlines confuse 
139 # lyx2lyx very much.
140 #
141 # A call to this routine will often go something like this:
142 #   i = find_token('\\begin_inset FunkyInset', ...)
143 #   ...
144 #   j = find_end_of_inset(document.body, i)
145 #   content = ...extract content from insets
146 #   # that could be as simple as: 
147 #   # content = lyx2latex(document[i:j + 1])
148 #   ert = put_cmd_in_ert(content)
149 #   document.body[i:j] = ert
150 # Now, before we continue, we need to reset i appropriately. Normally,
151 # this would be: 
152 #   i += len(ert)
153 # That puts us right after the ERT we just inserted.
154 #
155 def put_cmd_in_ert(arg):
156     ret = ["\\begin_inset ERT", "status collapsed", "\\begin_layout Plain Layout", ""]
157     # Despite the warnings just given, it will be faster for us to work
158     # with a single string internally. That way, we only go through the
159     # unicode_reps loop once.
160     if type(arg) is list:
161       s = "\n".join(arg)
162     else:
163       s = arg
164     for rep in unicode_reps:
165       s = s.replace(rep[1], rep[0].replace('\\\\', '\\'))
166     s = s.replace('\\', "\\backslash\n")
167     ret += s.splitlines()
168     ret += ["\\end_layout", "\\end_inset"]
169     return ret
170
171             
172 def lyx2latex(document, lines):
173     'Convert some LyX stuff into corresponding LaTeX stuff, as best we can.'
174     # clean up multiline stuff
175     content = ""
176     ert_end = 0
177     note_end = 0
178     hspace = ""
179
180     for curline in range(len(lines)):
181       line = lines[curline]
182       if line.startswith("\\begin_inset Note Note"):
183           # We want to skip LyX notes, so remember where the inset ends
184           note_end = find_end_of_inset(lines, curline + 1)
185           continue
186       elif note_end >= curline:
187           # Skip LyX notes
188           continue
189       elif line.startswith("\\begin_inset ERT"):
190           # We don't want to replace things inside ERT, so figure out
191           # where the end of the inset is.
192           ert_end = find_end_of_inset(lines, curline + 1)
193           continue
194       elif line.startswith("\\begin_inset Formula"):
195           line = line[20:]
196       elif line.startswith("\\begin_inset Quotes"):
197           # For now, we do a very basic reversion. Someone who understands
198           # quotes is welcome to fix it up.
199           qtype = line[20:].strip()
200           # lang = qtype[0]
201           side = qtype[1]
202           dbls = qtype[2]
203           if side == "l":
204               if dbls == "d":
205                   line = "``"
206               else:
207                   line = "`"
208           else:
209               if dbls == "d":
210                   line = "''"
211               else:
212                   line = "'"
213       elif line.startswith("\\begin_inset space"):
214           line = line[18:].strip()
215           if line.startswith("\\hspace"):
216               # Account for both \hspace and \hspace*
217               hspace = line[:-2]
218               continue
219           elif line == "\\space{}":
220               line = "\\ "
221           elif line == "\\thinspace{}":
222               line = "\\,"
223       elif hspace != "":
224           # The LyX length is in line[8:], after the \length keyword
225           length = latex_length(line[8:])[1]
226           line = hspace + "{" + length + "}"
227           hspace = ""
228       elif line.isspace() or \
229             line.startswith("\\begin_layout") or \
230             line.startswith("\\end_layout") or \
231             line.startswith("\\begin_inset") or \
232             line.startswith("\\end_inset") or \
233             line.startswith("\\lang") or \
234             line.strip() == "status collapsed" or \
235             line.strip() == "status open":
236           #skip all that stuff
237           continue
238
239       # this needs to be added to the preamble because of cases like
240       # \textmu, \textbackslash, etc.
241       add_to_preamble(document, ['% added by lyx2lyx for converted index entries',
242                                  '\\@ifundefined{textmu}',
243                                  ' {\\usepackage{textcomp}}{}'])
244       # a lossless reversion is not possible
245       # try at least to handle some common insets and settings
246       if ert_end >= curline:
247           line = line.replace(r'\backslash', '\\')
248       else:
249           # No need to add "{}" after single-nonletter macros
250           line = line.replace('&', '\\&')
251           line = line.replace('#', '\\#')
252           line = line.replace('^', '\\textasciicircum{}')
253           line = line.replace('%', '\\%')
254           line = line.replace('_', '\\_')
255           line = line.replace('$', '\\$')
256
257           # Do the LyX text --> LaTeX conversion
258           for rep in unicode_reps:
259             line = line.replace(rep[1], rep[0] + "{}")
260           line = line.replace(r'\backslash', r'\textbackslash{}')
261           line = line.replace(r'\series bold', r'\bfseries{}').replace(r'\series default', r'\mdseries{}')
262           line = line.replace(r'\shape italic', r'\itshape{}').replace(r'\shape smallcaps', r'\scshape{}')
263           line = line.replace(r'\shape slanted', r'\slshape{}').replace(r'\shape default', r'\upshape{}')
264           line = line.replace(r'\emph on', r'\em{}').replace(r'\emph default', r'\em{}')
265           line = line.replace(r'\noun on', r'\scshape{}').replace(r'\noun default', r'\upshape{}')
266           line = line.replace(r'\bar under', r'\underbar{').replace(r'\bar default', r'}')
267           line = line.replace(r'\family sans', r'\sffamily{}').replace(r'\family default', r'\normalfont{}')
268           line = line.replace(r'\family typewriter', r'\ttfamily{}').replace(r'\family roman', r'\rmfamily{}')
269           line = line.replace(r'\InsetSpace ', r'').replace(r'\SpecialChar ', r'')
270       content += line
271     return content
272
273
274 def latex_length(slen):
275     ''' 
276     Convert lengths to their LaTeX representation. Returns (bool, length),
277     where the bool tells us if it was a percentage, and the length is the
278     LaTeX representation.
279     '''
280     i = 0
281     percent = False
282     # the slen has the form
283     # ValueUnit+ValueUnit-ValueUnit or
284     # ValueUnit+-ValueUnit
285     # the + and - (glue lengths) are optional
286     # the + always precedes the -
287
288     # Convert relative lengths to LaTeX units
289     units = {"text%":"\\textwidth", "col%":"\\columnwidth",
290              "page%":"\\paperwidth", "line%":"\\linewidth",
291              "theight%":"\\textheight", "pheight%":"\\paperheight"}
292     for unit in units.keys():
293         i = slen.find(unit)
294         if i == -1:
295             continue
296         percent = True
297         minus = slen.rfind("-", 1, i)
298         plus = slen.rfind("+", 0, i)
299         latex_unit = units[unit]
300         if plus == -1 and minus == -1:
301             value = slen[:i]
302             value = str(float(value)/100)
303             end = slen[i + len(unit):]
304             slen = value + latex_unit + end
305         if plus > minus:
306             value = slen[plus + 1:i]
307             value = str(float(value)/100)
308             begin = slen[:plus + 1]
309             end = slen[i+len(unit):]
310             slen = begin + value + latex_unit + end
311         if plus < minus:
312             value = slen[minus + 1:i]
313             value = str(float(value)/100)
314             begin = slen[:minus + 1]
315             slen = begin + value + latex_unit
316
317     # replace + and -, but only if the - is not the first character
318     slen = slen[0] + slen[1:].replace("+", " plus ").replace("-", " minus ")
319     # handle the case where "+-1mm" was used, because LaTeX only understands
320     # "plus 1mm minus 1mm"
321     if slen.find("plus  minus"):
322         lastvaluepos = slen.rfind(" ")
323         lastvalue = slen[lastvaluepos:]
324         slen = slen.replace("  ", lastvalue + " ")
325     return (percent, slen)
326
327
328 def revert_flex_inset(document, name, LaTeXname, position):
329   " Convert flex insets to TeX code "
330   i = position
331   while True:
332     i = find_token(document.body, '\\begin_inset Flex ' + name, i)
333     if i == -1:
334       return
335     z = find_end_of_inset(document.body, i)
336     if z == -1:
337       document.warning("Malformed LyX document: Can't find end of Flex " + name + " inset.")
338       return
339     # remove the \end_inset
340     document.body[z - 2:z + 1] = put_cmd_in_ert("}")
341     # we need to reset character layouts if necessary
342     j = find_token(document.body, '\\emph on', i, z)
343     k = find_token(document.body, '\\noun on', i, z)
344     l = find_token(document.body, '\\series', i, z)
345     m = find_token(document.body, '\\family', i, z)
346     n = find_token(document.body, '\\shape', i, z)
347     o = find_token(document.body, '\\color', i, z)
348     p = find_token(document.body, '\\size', i, z)
349     q = find_token(document.body, '\\bar under', i, z)
350     r = find_token(document.body, '\\uuline on', i, z)
351     s = find_token(document.body, '\\uwave on', i, z)
352     t = find_token(document.body, '\\strikeout on', i, z)
353     if j != -1:
354       document.body.insert(z - 2, "\\emph default")
355     if k != -1:
356       document.body.insert(z - 2, "\\noun default")
357     if l != -1:
358       document.body.insert(z - 2, "\\series default")
359     if m != -1:
360       document.body.insert(z - 2, "\\family default")
361     if n != -1:
362       document.body.insert(z - 2, "\\shape default")
363     if o != -1:
364       document.body.insert(z - 2, "\\color inherit")
365     if p != -1:
366       document.body.insert(z - 2, "\\size default")
367     if q != -1:
368       document.body.insert(z - 2, "\\bar default")
369     if r != -1:
370       document.body.insert(z - 2, "\\uuline default")
371     if s != -1:
372       document.body.insert(z - 2, "\\uwave default")
373     if t != -1:
374       document.body.insert(z - 2, "\\strikeout default")
375     document.body[i:i + 4] = put_cmd_in_ert(LaTeXname + "{")
376     i += 1
377
378
379 def revert_font_attrs(document, name, LaTeXname):
380   " Reverts font changes to TeX code "
381   i = 0
382   changed = False
383   while True:
384     i = find_token(document.body, name + ' on', i)
385     if i == -1:
386       return changed
387     j = find_token(document.body, name + ' default', i)
388     k = find_token(document.body, name + ' on', i + 1)
389     # if there is no default set, the style ends with the layout
390     # assure hereby that we found the correct layout end
391     if j != -1 and (j < k or k == -1):
392       document.body[j:j + 1] = put_cmd_in_ert("}")
393     else:
394       j = find_token(document.body, '\\end_layout', i)
395       document.body[j:j] = put_cmd_in_ert("}")
396     document.body[i:i + 1] = put_cmd_in_ert(LaTeXname + "{")
397     changed = True
398     i += 1
399
400
401 def revert_layout_command(document, name, LaTeXname, position):
402   " Reverts a command from a layout to TeX code "
403   i = position
404   while True:
405     i = find_token(document.body, '\\begin_layout ' + name, i)
406     if i == -1:
407       return
408     k = -1
409     # find the next layout
410     j = i + 1
411     while k == -1:
412       j = find_token(document.body, '\\begin_layout', j)
413       l = len(document.body)
414       # if nothing was found it was the last layout of the document
415       if j == -1:
416         document.body[l - 4:l - 4] = put_cmd_in_ert("}")
417         k = 0
418       # exclude plain layout because this can be TeX code or another inset
419       elif document.body[j] != '\\begin_layout Plain Layout':
420         document.body[j - 2:j - 2] = put_cmd_in_ert("}")
421         k = 0
422       else:
423         j += 1
424     document.body[i] = '\\begin_layout Standard'
425     document.body[i + 1:i + 1] = put_cmd_in_ert(LaTeXname + "{")
426     i += 1
427
428
429 def hex2ratio(s):
430     val = string.atoi(s, 16)
431     if val != 0:
432       val += 1
433     return str(val / 256.0)
434
435
436 ###############################################################################
437 ###
438 ### Conversion and reversion routines
439 ###
440 ###############################################################################
441
442 def revert_swiss(document):
443     " Set language german-ch to ngerman "
444     i = 0
445     if document.language == "german-ch":
446         document.language = "ngerman"
447         i = find_token(document.header, "\\language", 0)
448         if i != -1:
449             document.header[i] = "\\language ngerman"
450     j = 0
451     while True:
452         j = find_token(document.body, "\\lang german-ch", j)
453         if j == -1:
454             return
455         document.body[j] = document.body[j].replace("\\lang german-ch", "\\lang ngerman")
456         j = j + 1
457
458
459 def revert_tabularvalign(document):
460    " Revert the tabular valign option "
461    i = 0
462    while True:
463       i = find_token(document.body, "\\begin_inset Tabular", i)
464       if i == -1:
465           return
466       end = find_end_of_inset(document.body, i)
467       if end == -1:
468           document.warning("Can't find end of inset at line " + str(i))
469           i += 1
470           continue
471       fline = find_token(document.body, "<features", i, end)
472       if fline == -1:
473           document.warning("Can't find features for inset at line " + str(i))
474           i += 1
475           continue
476       p = document.body[fline].find("islongtable")
477       if p != -1:
478           q = document.body[fline].find("tabularvalignment")
479           if q != -1:
480               # FIXME
481               # This seems wrong: It removes everything after 
482               # tabularvalignment, too.
483               document.body[fline] = document.body[fline][:q - 1] + '>'
484           i += 1
485           continue
486
487        # no longtable
488       tabularvalignment = 'c'
489       # which valignment is specified?
490       m = document.body[fline].find('tabularvalignment="top"')
491       if m != -1:
492           tabularvalignment = 't'
493       m = document.body[fline].find('tabularvalignment="bottom"')
494       if m != -1:
495           tabularvalignment = 'b'
496       # delete tabularvalignment
497       q = document.body[fline].find("tabularvalignment")
498       if q != -1:
499           # FIXME
500           # This seems wrong: It removes everything after 
501           # tabularvalignment, too.
502           document.body[fline] = document.body[fline][:q - 1] + '>'
503
504       # don't add a box when centered
505       if tabularvalignment == 'c':
506           i = end
507           continue
508       subst = ['\\end_layout', '\\end_inset']
509       document.body[end:end] = subst # just inserts those lines
510       subst = ['\\begin_inset Box Frameless',
511           'position "' + tabularvalignment +'"',
512           'hor_pos "c"',
513           'has_inner_box 1',
514           'inner_pos "c"',
515           'use_parbox 0',
516           # we don't know the width, assume 50%
517           'width "50col%"',
518           'special "none"',
519           'height "1in"',
520           'height_special "totalheight"',
521           'status open',
522           '',
523           '\\begin_layout Plain Layout']
524       document.body[i:i] = subst # this just inserts the array at i
525       # since there could be a tabular inside a tabular, we cannot
526       # jump to end
527       i += len(subst)
528
529
530 def revert_phantom_types(document, ptype, cmd):
531     " Reverts phantom to ERT "
532     i = 0
533     while True:
534       i = find_token(document.body, "\\begin_inset Phantom " + ptype, i)
535       if i == -1:
536           return
537       end = find_end_of_inset(document.body, i)
538       if end == -1:
539           document.warning("Can't find end of inset at line " + str(i))
540           i += 1
541           continue
542       blay = find_token(document.body, "\\begin_layout Plain Layout", i, end)
543       if blay == -1:
544           document.warning("Can't find layout for inset at line " + str(i))
545           i = end
546           continue
547       bend = find_token(document.body, "\\end_layout", blay, end)
548       if bend == -1:
549           document.warning("Malformed LyX document: Could not find end of Phantom inset's layout.")
550           i = end
551           continue
552       substi = ["\\begin_inset ERT", "status collapsed", "",
553                 "\\begin_layout Plain Layout", "", "", "\\backslash", 
554                 cmd + "{", "\\end_layout", "", "\\end_inset"]
555       substj = ["\\size default", "", "\\begin_inset ERT", "status collapsed", "",
556                 "\\begin_layout Plain Layout", "", "}", "\\end_layout", "", "\\end_inset"]
557       # do the later one first so as not to mess up the numbering
558       document.body[bend:end + 1] = substj
559       document.body[i:blay + 1] = substi
560       i = end + len(substi) + len(substj) - (end - bend) - (blay - i) - 2
561
562
563 def revert_phantom(document):
564     revert_phantom_types(document, "Phantom", "phantom")
565     
566 def revert_hphantom(document):
567     revert_phantom_types(document, "HPhantom", "hphantom")
568
569 def revert_vphantom(document):
570     revert_phantom_types(document, "VPhantom", "vphantom")
571
572
573 def revert_xetex(document):
574     " Reverts documents that use XeTeX "
575     i = find_token(document.header, '\\use_xetex', 0)
576     if i == -1:
577         document.warning("Malformed LyX document: Missing \\use_xetex.")
578         return
579     if get_value(document.header, "\\use_xetex", i) == 'false':
580         del document.header[i]
581         return
582     del document.header[i]
583     # 1.) set doc encoding to utf8-plain
584     i = find_token(document.header, "\\inputencoding", 0)
585     if i == -1:
586         document.warning("Malformed LyX document: Missing \\inputencoding.")
587     document.header[i] = "\\inputencoding utf8-plain"
588     # 2.) check font settings
589     l = find_token(document.header, "\\font_roman", 0)
590     if l == -1:
591         document.warning("Malformed LyX document: Missing \\font_roman.")
592     line = document.header[l]
593     l = re.compile(r'\\font_roman (.*)$')
594     m = l.match(line)
595     roman = m.group(1)
596     l = find_token(document.header, "\\font_sans", 0)
597     if l == -1:
598         document.warning("Malformed LyX document: Missing \\font_sans.")
599     line = document.header[l]
600     l = re.compile(r'\\font_sans (.*)$')
601     m = l.match(line)
602     sans = m.group(1)
603     l = find_token(document.header, "\\font_typewriter", 0)
604     if l == -1:
605         document.warning("Malformed LyX document: Missing \\font_typewriter.")
606     line = document.header[l]
607     l = re.compile(r'\\font_typewriter (.*)$')
608     m = l.match(line)
609     typewriter = m.group(1)
610     osf = get_value(document.header, '\\font_osf', 0) == "true"
611     sf_scale = float(get_value(document.header, '\\font_sf_scale', 0))
612     tt_scale = float(get_value(document.header, '\\font_tt_scale', 0))
613     # 3.) set preamble stuff
614     pretext = '%% This document must be processed with xelatex!\n'
615     pretext += '\\usepackage{fontspec}\n'
616     if roman != "default":
617         pretext += '\\setmainfont[Mapping=tex-text]{' + roman + '}\n'
618     if sans != "default":
619         pretext += '\\setsansfont['
620         if sf_scale != 100:
621             pretext += 'Scale=' + str(sf_scale / 100) + ','
622         pretext += 'Mapping=tex-text]{' + sans + '}\n'
623     if typewriter != "default":
624         pretext += '\\setmonofont'
625         if tt_scale != 100:
626             pretext += '[Scale=' + str(tt_scale / 100) + ']'
627         pretext += '{' + typewriter + '}\n'
628     if osf:
629         pretext += '\\defaultfontfeatures{Numbers=OldStyle}\n'
630     pretext += '\usepackage{xunicode}\n'
631     pretext += '\usepackage{xltxtra}\n'
632     insert_to_preamble(0, document, pretext)
633     # 4.) reset font settings
634     i = find_token(document.header, "\\font_roman", 0)
635     if i == -1:
636         document.warning("Malformed LyX document: Missing \\font_roman.")
637     document.header[i] = "\\font_roman default"
638     i = find_token(document.header, "\\font_sans", 0)
639     if i == -1:
640         document.warning("Malformed LyX document: Missing \\font_sans.")
641     document.header[i] = "\\font_sans default"
642     i = find_token(document.header, "\\font_typewriter", 0)
643     if i == -1:
644         document.warning("Malformed LyX document: Missing \\font_typewriter.")
645     document.header[i] = "\\font_typewriter default"
646     i = find_token(document.header, "\\font_osf", 0)
647     if i == -1:
648         document.warning("Malformed LyX document: Missing \\font_osf.")
649     document.header[i] = "\\font_osf false"
650     i = find_token(document.header, "\\font_sc", 0)
651     if i == -1:
652         document.warning("Malformed LyX document: Missing \\font_sc.")
653     document.header[i] = "\\font_sc false"
654     i = find_token(document.header, "\\font_sf_scale", 0)
655     if i == -1:
656         document.warning("Malformed LyX document: Missing \\font_sf_scale.")
657     document.header[i] = "\\font_sf_scale 100"
658     i = find_token(document.header, "\\font_tt_scale", 0)
659     if i == -1:
660         document.warning("Malformed LyX document: Missing \\font_tt_scale.")
661     document.header[i] = "\\font_tt_scale 100"
662
663
664 def revert_outputformat(document):
665     " Remove default output format param "
666     i = find_token(document.header, '\\default_output_format', 0)
667     if i == -1:
668         document.warning("Malformed LyX document: Missing \\default_output_format.")
669         return
670     del document.header[i]
671
672
673 def revert_backgroundcolor(document):
674     " Reverts background color to preamble code "
675     i = find_token(document.header, "\\backgroundcolor", 0)
676     if i == -1:
677         return
678     colorcode = get_value(document.header, '\\backgroundcolor', i)
679     del document.header[i]
680     # don't clutter the preamble if backgroundcolor is not set
681     if colorcode == "#ffffff":
682         return
683     red   = hex2ratio(colorcode[1:3])
684     green = hex2ratio(colorcode[3:5])
685     blue  = hex2ratio(colorcode[5:7])
686     insert_to_preamble(0, document,
687                           '% Commands inserted by lyx2lyx to set the background color\n'
688                           + '\\@ifundefined{definecolor}{\\usepackage{color}}{}\n'
689                           + '\\definecolor{page_backgroundcolor}{rgb}{'
690                           + red + ',' + green + ',' + blue + '}\n'
691                           + '\\pagecolor{page_backgroundcolor}\n')
692
693
694 def revert_splitindex(document):
695     " Reverts splitindex-aware documents "
696     i = find_token(document.header, '\\use_indices', 0)
697     if i == -1:
698         document.warning("Malformed LyX document: Missing \\use_indices.")
699         return
700     indices = get_value(document.header, "\\use_indices", i)
701     preamble = ""
702     useindices = (indices == "true")
703     if useindices:
704          preamble += "\\usepackage{splitidx}\n"
705     del document.header[i]
706     
707     # deal with index declarations in the preamble
708     i = 0
709     while True:
710         i = find_token(document.header, "\\index", i)
711         if i == -1:
712             break
713         k = find_token(document.header, "\\end_index", i)
714         if k == -1:
715             document.warning("Malformed LyX document: Missing \\end_index.")
716             return
717         if useindices:    
718           line = document.header[i]
719           l = re.compile(r'\\index (.*)$')
720           m = l.match(line)
721           iname = m.group(1)
722           ishortcut = get_value(document.header, '\\shortcut', i, k)
723           if ishortcut != "":
724               preamble += "\\newindex[" + iname + "]{" + ishortcut + "}\n"
725         del document.header[i:k + 1]
726     if preamble != "":
727         insert_to_preamble(0, document, preamble)
728         
729     # deal with index insets
730     # these need to have the argument removed
731     i = 0
732     while True:
733         i = find_token(document.body, "\\begin_inset Index", i)
734         if i == -1:
735             break
736         line = document.body[i]
737         l = re.compile(r'\\begin_inset Index (.*)$')
738         m = l.match(line)
739         itype = m.group(1)
740         if itype == "idx" or indices == "false":
741             document.body[i] = "\\begin_inset Index"
742         else:
743             k = find_end_of_inset(document.body, i)
744             if k == -1:
745                 document.warning("Can't find end of index inset!")
746                 i += 1
747                 continue
748             content = lyx2latex(document, document.body[i:k])
749             # escape quotes
750             content = content.replace('"', r'\"')
751             subst = put_cmd_in_ert("\\sindex[" + itype + "]{" + content + "}")
752             document.body[i:k + 1] = subst
753         i = i + 1
754         
755     # deal with index_print insets
756     i = 0
757     while True:
758         i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
759         if i == -1:
760             return
761         k = find_end_of_inset(document.body, i)
762         ptype = get_value(document.body, 'type', i, k).strip('"')
763         if ptype == "idx":
764             j = find_token(document.body, "type", i, k)
765             del document.body[j]
766         elif not useindices:
767             del document.body[i:k + 1]
768         else:
769             subst = put_cmd_in_ert("\\printindex[" + ptype + "]{}")
770             document.body[i:k + 1] = subst
771         i = i + 1
772
773
774 def convert_splitindex(document):
775     " Converts index and printindex insets to splitindex-aware format "
776     i = 0
777     while True:
778         i = find_token(document.body, "\\begin_inset Index", i)
779         if i == -1:
780             break
781         document.body[i] = document.body[i].replace("\\begin_inset Index",
782             "\\begin_inset Index idx")
783         i = i + 1
784     i = 0
785     while True:
786         i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
787         if i == -1:
788             return
789         if document.body[i + 1].find('LatexCommand printindex') == -1:
790             document.warning("Malformed LyX document: Incomplete printindex inset.")
791             return
792         subst = ["LatexCommand printindex", 
793             "type \"idx\""]
794         document.body[i + 1:i + 2] = subst
795         i = i + 1
796
797
798 def revert_subindex(document):
799     " Reverts \\printsubindex CommandInset types "
800     i = find_token(document.header, '\\use_indices', 0)
801     if i == -1:
802         document.warning("Malformed LyX document: Missing \\use_indices.")
803         return
804     indices = get_value(document.header, "\\use_indices", i)
805     useindices = (indices == "true")
806     i = 0
807     while True:
808         i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
809         if i == -1:
810             return
811         k = find_end_of_inset(document.body, i)
812         ctype = get_value(document.body, 'LatexCommand', i, k)
813         if ctype != "printsubindex":
814             i = k + 1
815             continue
816         ptype = get_value(document.body, 'type', i, k).strip('"')
817         if not useindices:
818             del document.body[i:k + 1]
819         else:
820             subst = put_cmd_in_ert("\\printsubindex[" + ptype + "]{}")
821             document.body[i:k + 1] = subst
822         i = i + 1
823
824
825 def revert_printindexall(document):
826     " Reverts \\print[sub]index* CommandInset types "
827     i = find_token(document.header, '\\use_indices', 0)
828     if i == -1:
829         document.warning("Malformed LyX document: Missing \\use_indices.")
830         return
831     indices = get_value(document.header, "\\use_indices", i)
832     useindices = (indices == "true")
833     i = 0
834     while True:
835         i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
836         if i == -1:
837             return
838         k = find_end_of_inset(document.body, i)
839         ctype = get_value(document.body, 'LatexCommand', i, k)
840         if ctype != "printindex*" and ctype != "printsubindex*":
841             i = k
842             continue
843         if not useindices:
844             del document.body[i:k + 1]
845         else:
846             subst = put_cmd_in_ert("\\" + ctype + "{}")
847             document.body[i:k + 1] = subst
848         i = i + 1
849
850
851 def revert_strikeout(document):
852   " Reverts \\strikeout font attribute "
853   changed = revert_font_attrs(document, "\\uuline", "\\uuline")
854   changed = revert_font_attrs(document, "\\uwave", "\\uwave") or changed
855   changed = revert_font_attrs(document, "\\strikeout", "\\sout")  or changed
856   if changed == True:
857     insert_to_preamble(0, document,
858         '% Commands inserted by lyx2lyx for proper underlining\n'
859         + '\\PassOptionsToPackage{normalem}{ulem}\n'
860         + '\\usepackage{ulem}\n')
861
862
863 def revert_ulinelatex(document):
864     " Reverts \\uline font attribute "
865     i = find_token(document.body, '\\bar under', 0)
866     if i == -1:
867         return
868     insert_to_preamble(0, document,
869             '% Commands inserted by lyx2lyx for proper underlining\n'
870             + '\\PassOptionsToPackage{normalem}{ulem}\n'
871             + '\\usepackage{ulem}\n'
872             + '\\let\\cite@rig\\cite\n'
873             + '\\newcommand{\\b@xcite}[2][\\%]{\\def\\def@pt{\\%}\\def\\pas@pt{#1}\n'
874             + '  \\mbox{\\ifx\\def@pt\\pas@pt\\cite@rig{#2}\\else\\cite@rig[#1]{#2}\\fi}}\n'
875             + '\\renewcommand{\\underbar}[1]{{\\let\\cite\\b@xcite\\uline{#1}}}\n')
876
877
878 def revert_custom_processors(document):
879     " Remove bibtex_command and index_command params "
880     i = find_token(document.header, '\\bibtex_command', 0)
881     if i == -1:
882         document.warning("Malformed LyX document: Missing \\bibtex_command.")
883     else:
884         del document.header[i]
885     i = find_token(document.header, '\\index_command', 0)
886     if i == -1:
887         document.warning("Malformed LyX document: Missing \\index_command.")
888     else:
889         del document.header[i]
890
891
892 def convert_nomencl_width(document):
893     " Add set_width param to nomencl_print "
894     i = 0
895     while True:
896       i = find_token(document.body, "\\begin_inset CommandInset nomencl_print", i)
897       if i == -1:
898         break
899       document.body.insert(i + 2, "set_width \"none\"")
900       i = i + 1
901
902
903 def revert_nomencl_width(document):
904     " Remove set_width param from nomencl_print "
905     i = 0
906     while True:
907       i = find_token(document.body, "\\begin_inset CommandInset nomencl_print", i)
908       if i == -1:
909         break
910       j = find_end_of_inset(document.body, i)
911       l = find_token(document.body, "set_width", i, j)
912       if l == -1:
913             document.warning("Can't find set_width option for nomencl_print!")
914             i = j
915             continue
916       del document.body[l]
917       i = j - 1
918
919
920 def revert_nomencl_cwidth(document):
921     " Remove width param from nomencl_print "
922     i = 0
923     while True:
924       i = find_token(document.body, "\\begin_inset CommandInset nomencl_print", i)
925       if i == -1:
926         break
927       j = find_end_of_inset(document.body, i)
928       l = find_token(document.body, "width", i, j)
929       if l == -1:
930         document.warning("Can't find width option for nomencl_print!")
931         i = j
932         continue
933       width = get_value(document.body, "width", i, j).strip('"')
934       del document.body[l]
935       add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
936       add_to_preamble(document, ["\\setlength{\\nomlabelwidth}{" + width + "}"])
937       i = j - 1
938
939
940 def revert_applemac(document):
941     " Revert applemac encoding to auto "
942     if document.encoding != "applemac":
943       return
944     document.encoding = "auto"
945     i = find_token(document.header, "\\encoding", 0)
946     if i != -1:
947         document.header[i] = "\\encoding auto"
948
949
950 def revert_longtable_align(document):
951     " Remove longtable alignment setting "
952     i = 0
953     while True:
954       i = find_token(document.body, "\\begin_inset Tabular", i)
955       if i == -1:
956           break
957       end = find_end_of_inset(document.body, i)
958       if end == -1:
959           document.warning("Can't find end of inset at line " + str(i))
960           i += 1
961           continue
962       fline = find_token(document.body, "<features", i, end)
963       if fline == -1:
964           document.warning("Can't find features for inset at line " + str(i))
965           i += 1
966           continue
967       j = document.body[fline].find("longtabularalignment")
968       if j == -1:
969           i += 1
970           continue
971       # FIXME Is this correct? It wipes out everything after the 
972       # one we found.
973       document.body[fline] = document.body[fline][:j - 1] + '>'
974       # since there could be a tabular inside this one, we 
975       # cannot jump to end.
976       i += 1
977
978
979 def revert_branch_filename(document):
980     " Remove \\filename_suffix parameter from branches "
981     i = 0
982     while True:
983         i = find_token(document.header, "\\filename_suffix", i)
984         if i == -1:
985             return
986         del document.header[i]
987
988
989 def revert_paragraph_indentation(document):
990     " Revert custom paragraph indentation to preamble code "
991     i = find_token(document.header, "\\paragraph_indentation", 0)
992     if i == -1:
993       return
994     length = get_value(document.header, "\\paragraph_indentation", i)
995     # we need only remove the line if indentation is default
996     if length != "default":
997       # handle percent lengths
998       length = latex_length(length)[1]
999       add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
1000       add_to_preamble(document, ["\\setlength{\\parindent}{" + length + "}"])
1001     del document.header[i]
1002
1003
1004 def revert_percent_skip_lengths(document):
1005     " Revert relative lengths for paragraph skip separation to preamble code "
1006     i = find_token(document.header, "\\defskip", 0)
1007     if i == -1:
1008         return
1009     length = get_value(document.header, "\\defskip", i)
1010     # only revert when a custom length was set and when
1011     # it used a percent length
1012     if length in ('smallskip', 'medskip', 'bigskip'):
1013         return
1014     # handle percent lengths
1015     percent, length = latex_length(length)
1016     if percent == "True":
1017         add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
1018         add_to_preamble(document, ["\\setlength{\\parskip}{" + length + "}"])
1019         # set defskip to medskip as default
1020         document.header[i] = "\\defskip medskip"
1021
1022
1023 def revert_percent_vspace_lengths(document):
1024     " Revert relative VSpace lengths to ERT "
1025     i = 0
1026     while True:
1027       i = find_token(document.body, "\\begin_inset VSpace", i)
1028       if i == -1:
1029           break
1030       # only revert if a custom length was set and if
1031       # it used a percent length
1032       r = re.compile(r'\\begin_inset VSpace (.*)$')
1033       m = r.match(document.body[i])
1034       length = m.group(1)
1035       if length in ('defskip', 'smallskip', 'medskip', 'bigskip', 'vfill'):
1036          i += 1
1037          continue
1038       # check if the space has a star (protected space)
1039       protected = (document.body[i].rfind("*") != -1)
1040       if protected:
1041           length = length.rstrip('*')
1042       # handle percent lengths
1043       percent, length = latex_length(length)
1044       # revert the VSpace inset to ERT
1045       if percent == "True":
1046           if protected:
1047               subst = put_cmd_in_ert("\\vspace*{" + length + "}")
1048           else:
1049               subst = put_cmd_in_ert("\\vspace{" + length + "}")
1050           document.body[i:i + 2] = subst
1051       i += 1
1052
1053
1054 def revert_percent_hspace_lengths(document):
1055     " Revert relative HSpace lengths to ERT "
1056     i = 0
1057     while True:
1058       i = find_token(document.body, "\\begin_inset space \\hspace", i)
1059       if i == -1:
1060           break
1061       j = find_end_of_inset(document.body, i)
1062       if j == -1:
1063           document.warning("Can't find end of inset at line " + str(i))
1064           i += 1
1065           continue
1066       # only revert if a custom length was set...
1067       length = get_value(document.body, '\\length', i + 1, j)
1068       if length == '':
1069           document.warning("Malformed lyx document: Missing '\\length' in Space inset.")
1070           i = j
1071           continue
1072       protected = ""
1073       if document.body[i].find("\\hspace*{}") != -1:
1074           protected = "*"
1075       # ...and if it used a percent length
1076       percent, length = latex_length(length)
1077       # revert the HSpace inset to ERT
1078       if percent == "True":
1079           subst = put_cmd_in_ert("\\hspace" + protected + "{" + length + "}")
1080           document.body[i:j + 1] = subst
1081       # if we did a substitution, this will still be ok
1082       i = j
1083
1084
1085 def revert_hspace_glue_lengths(document):
1086     " Revert HSpace glue lengths to ERT "
1087     i = 0
1088     while True:
1089       i = find_token(document.body, "\\begin_inset space \\hspace", i)
1090       if i == -1:
1091           break
1092       j = find_end_of_inset(document.body, i)
1093       if j == -1:
1094           document.warning("Can't find end of inset at line " + str(i))
1095           i += 1
1096           continue
1097       length = get_value(document.body, '\\length', i + 1, j)
1098       if length == '':
1099           document.warning("Malformed lyx document: Missing '\\length' in Space inset.")
1100           i = j
1101           continue
1102       protected = ""
1103       if document.body[i].find("\\hspace*{}") != -1:
1104           protected = "*"
1105       # only revert if the length contains a plus or minus at pos != 0
1106       if length.find('-',1) != -1 or length.find('+',1) != -1:
1107           # handle percent lengths
1108           length = latex_length(length)[1]
1109           # revert the HSpace inset to ERT
1110           subst = put_cmd_in_ert("\\hspace" + protected + "{" + length + "}")
1111           document.body[i:j+1] = subst
1112       i = j
1113
1114
1115 def convert_author_id(document):
1116     " Add the author_id to the \\author definition and make sure 0 is not used"
1117     i = 0
1118     anum = 1
1119     re_author = re.compile(r'(\\author) (\".*\")\s*(.*)$')
1120     
1121     while True:
1122         i = find_token(document.header, "\\author", i)
1123         if i == -1:
1124             break
1125         m = re_author.match(document.header[i])
1126         if m:
1127             name = m.group(2)
1128             email = m.group(3)
1129             document.header[i] = "\\author %i %s %s" % (anum, name, email)
1130         # FIXME Should this really be incremented if we didn't match?
1131         anum += 1
1132         i += 1
1133         
1134     i = 0
1135     while True:
1136         i = find_token(document.body, "\\change_", i)
1137         if i == -1:
1138             break
1139         change = document.body[i].split(' ');
1140         if len(change) == 3:
1141             type = change[0]
1142             author_id = int(change[1])
1143             time = change[2]
1144             document.body[i] = "%s %i %s" % (type, author_id + 1, time)
1145         i += 1
1146
1147
1148 def revert_author_id(document):
1149     " Remove the author_id from the \\author definition "
1150     i = 0
1151     anum = 0
1152     rx = re.compile(r'(\\author)\s+(\d+)\s+(\".*\")\s*(.*)$')
1153     idmap = dict()
1154
1155     while True:
1156         i = find_token(document.header, "\\author", i)
1157         if i == -1:
1158             break
1159         m = rx.match(document.header[i])
1160         if m:
1161             author_id = int(m.group(2))
1162             idmap[author_id] = anum
1163             name = m.group(3)
1164             email = m.group(4)
1165             document.header[i] = "\\author %s %s" % (name, email)
1166         i += 1
1167         # FIXME Should this be incremented if we didn't match?
1168         anum += 1
1169
1170     i = 0
1171     while True:
1172         i = find_token(document.body, "\\change_", i)
1173         if i == -1:
1174             break
1175         change = document.body[i].split(' ');
1176         if len(change) == 3:
1177             type = change[0]
1178             author_id = int(change[1])
1179             time = change[2]
1180             document.body[i] = "%s %i %s" % (type, idmap[author_id], time)
1181         i += 1
1182
1183
1184 def revert_suppress_date(document):
1185     " Revert suppressing of default document date to preamble code "
1186     i = find_token(document.header, "\\suppress_date", 0)
1187     if i == -1:
1188         return
1189     # remove the preamble line and write to the preamble
1190     # when suppress_date was true
1191     date = get_value(document.header, "\\suppress_date", i)
1192     if date == "true":
1193         add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
1194         add_to_preamble(document, ["\\date{}"])
1195     del document.header[i]
1196
1197
1198 def revert_mhchem(document):
1199     "Revert mhchem loading to preamble code"
1200
1201     mhchem = "off"
1202     i = find_token(document.header, "\\use_mhchem", 0)
1203     if i == -1:
1204         document.warning("Malformed LyX document: Could not find mhchem setting.")
1205         mhchem = "auto"
1206     else:
1207         val = get_value(document.header, "\\use_mhchem", i)
1208         if val == "1":
1209             mhchem = "auto"
1210         elif val == "2":
1211             mhchem = "on"
1212         del document.header[i]
1213
1214     if mhchem == "auto":
1215         i = 0
1216         while True:
1217             i = find_token(document.body, "\\begin_inset Formula", i)
1218             if i == -1:
1219                break
1220             line = document.body[i]
1221             if line.find("\\ce{") != -1 or line.find("\\cf{") != 1:
1222               mhchem = "on"
1223               break
1224             i += 1
1225
1226     if mhchem == "on":
1227         pre = ["% lyx2lyx mhchem commands", 
1228           "\\PassOptionsToPackage{version=3}{mhchem}", 
1229           "\\usepackage{mhchem}"]
1230         add_to_preamble(document, pre) 
1231
1232
1233 def revert_fontenc(document):
1234     " Remove fontencoding param "
1235     i = find_token(document.header, '\\fontencoding', 0)
1236     if i == -1:
1237         document.warning("Malformed LyX document: Missing \\fontencoding.")
1238         return
1239     del document.header[i]
1240
1241
1242 def merge_gbrief(document):
1243     " Merge g-brief-en and g-brief-de to one class "
1244
1245     if document.textclass != "g-brief-de":
1246         if document.textclass == "g-brief-en":
1247             document.textclass = "g-brief"
1248             document.set_textclass()
1249         return
1250
1251     obsoletedby = { "Brieftext":       "Letter",
1252                     "Unterschrift":    "Signature",
1253                     "Strasse":         "Street",
1254                     "Zusatz":          "Addition",
1255                     "Ort":             "Town",
1256                     "Land":            "State",
1257                     "RetourAdresse":   "ReturnAddress",
1258                     "MeinZeichen":     "MyRef",
1259                     "IhrZeichen":      "YourRef",
1260                     "IhrSchreiben":    "YourMail",
1261                     "Telefon":         "Phone",
1262                     "BLZ":             "BankCode",
1263                     "Konto":           "BankAccount",
1264                     "Postvermerk":     "PostalComment",
1265                     "Adresse":         "Address",
1266                     "Datum":           "Date",
1267                     "Betreff":         "Reference",
1268                     "Anrede":          "Opening",
1269                     "Anlagen":         "Encl.",
1270                     "Verteiler":       "cc",
1271                     "Gruss":           "Closing"}
1272     i = 0
1273     while 1:
1274         i = find_token(document.body, "\\begin_layout", i)
1275         if i == -1:
1276             break
1277
1278         layout = document.body[i][14:]
1279         if layout in obsoletedby:
1280             document.body[i] = "\\begin_layout " + obsoletedby[layout]
1281
1282         i += 1
1283         
1284     document.textclass = "g-brief"
1285     document.set_textclass()
1286
1287
1288 def revert_gbrief(document):
1289     " Revert g-brief to g-brief-en "
1290     if document.textclass == "g-brief":
1291         document.textclass = "g-brief-en"
1292         document.set_textclass()
1293
1294
1295 def revert_html_options(document):
1296     " Remove html options "
1297     i = find_token(document.header, '\\html_use_mathml', 0)
1298     if i != -1:
1299         del document.header[i]
1300     i = find_token(document.header, '\\html_be_strict', 0)
1301     if i != -1:
1302         del document.header[i]
1303
1304
1305 def revert_includeonly(document):
1306     i = 0
1307     while True:
1308         i = find_token(document.header, "\\begin_includeonly", i)
1309         if i == -1:
1310             return
1311         j = find_end_of(document.header, i, "\\begin_includeonly", "\\end_includeonly")
1312         if j == -1:
1313             document.warning("Unable to find end of includeonly section!!")
1314             break
1315         document.header[i : j + 1] = []
1316
1317
1318 def revert_includeall(document):
1319     " Remove maintain_unincluded_children param "
1320     i = find_token(document.header, '\\maintain_unincluded_children', 0)
1321     if i != -1:
1322         del document.header[i]
1323
1324
1325 def revert_multirow(document):
1326     " Revert multirow cells in tables to TeX-code"
1327     i = 0
1328     multirow = False
1329     while True:
1330       # cell type 3 is multirow begin cell
1331       i = find_token(document.body, '<cell multirow="3"', i)
1332       if i == -1:
1333           break
1334       # a multirow cell was found
1335       multirow = True
1336       # remove the multirow tag, set the valignment to top
1337       # and remove the bottom line
1338       # FIXME Are we sure these always have space around them?
1339       document.body[i] = document.body[i].replace(' multirow="3" ', ' ')
1340       document.body[i] = document.body[i].replace('valignment="middle"', 'valignment="top"')
1341       document.body[i] = document.body[i].replace(' bottomline="true" ', ' ')
1342       # write ERT to create the multirow cell
1343       # use 2 rows and 2cm as default with because the multirow span
1344       # and the column width is only hardly accessible
1345       cend = find_token(document.body, "</cell>", i)
1346       if cend == -1:
1347           document.warning("Malformed LyX document: Could not find end of tabular cell.")
1348           i += 1
1349           continue
1350       blay = find_token(document.body, "\\begin_layout", i, cend)
1351       if blay == -1:
1352           document.warning("Can't find layout for cell!")
1353           i = j
1354           continue
1355       bend = find_end_of_layout(document.body, blay)
1356       if blay == -1:
1357           document.warning("Can't find end of layout for cell!")
1358           i = cend
1359           continue
1360
1361       # do the later one first, so as not to mess up the numbering
1362       # we are wrapping the whole cell in this ert
1363       # so before the end of the layout...
1364       document.body[bend:bend] = put_cmd_in_ert("}")
1365       # ...and after the beginning
1366       document.body[blay+1:blay+1] = put_cmd_in_ert("\\multirow{2}{2cm}{")
1367
1368       while True:
1369           # cell type 4 is multirow part cell
1370           k = find_token(document.body, '<cell multirow="4"', cend)
1371           if k == -1:
1372               break
1373           # remove the multirow tag, set the valignment to top
1374           # and remove the top line
1375           # FIXME Are we sure these always have space around them?
1376           document.body[k] = document.body[k].replace(' multirow="4" ', ' ')
1377           document.body[k] = document.body[k].replace('valignment="middle"', 'valignment="top"')
1378           document.body[k] = document.body[k].replace(' topline="true" ', ' ')
1379           k += 1
1380       # this will always be ok
1381       i = cend
1382
1383     if multirow == True:
1384         add_to_preamble(document, 
1385           ["% lyx2lyx multirow additions ", "\\usepackage{multirow}"])
1386
1387
1388 def convert_math_output(document):
1389     " Convert \html_use_mathml to \html_math_output "
1390     i = find_token(document.header, "\\html_use_mathml", 0)
1391     if i == -1:
1392         return
1393     rgx = re.compile(r'\\html_use_mathml\s+(\w+)')
1394     m = rgx.match(document.header[i])
1395     newval = "0" # MathML
1396     if m:
1397       val = m.group(1)
1398       if val != "true":
1399         newval = "2" # Images
1400     else:
1401       document.warning("Can't match " + document.header[i])
1402     document.header[i] = "\\html_math_output " + newval
1403
1404
1405 def revert_math_output(document):
1406     " Revert \html_math_output to \html_use_mathml "
1407     i = find_token(document.header, "\\html_math_output", 0)
1408     if i == -1:
1409         return
1410     rgx = re.compile(r'\\html_math_output\s+(\d)')
1411     m = rgx.match(document.header[i])
1412     newval = "true"
1413     if m:
1414         val = m.group(1)
1415         if val == "1" or val == "2":
1416             newval = "false"
1417     else:
1418         document.warning("Unable to match " + document.header[i])
1419     document.header[i] = "\\html_use_mathml " + newval
1420                 
1421
1422
1423 def revert_inset_preview(document):
1424     " Dissolves the preview inset "
1425     i = 0
1426     while True:
1427       i = find_token(document.body, "\\begin_inset Preview", i)
1428       if i == -1:
1429           return
1430       iend = find_end_of_inset(document.body, i)
1431       if iend == -1:
1432           document.warning("Malformed LyX document: Could not find end of Preview inset.")
1433           i += 1
1434           continue
1435       
1436       # This has several issues.
1437       # We need to do something about the layouts inside InsetPreview.
1438       # If we just leave the first one, then we have something like:
1439       # \begin_layout Standard
1440       # ...
1441       # \begin_layout Standard
1442       # and we get a "no \end_layout" error. So something has to be done.
1443       # Ideally, we would check if it is the same as the layout we are in.
1444       # If so, we just remove it; if not, we end the active one. But it is 
1445       # not easy to know what layout we are in, due to depth changes, etc,
1446       # and it is not clear to me how much work it is worth doing. In most
1447       # cases, the layout will probably be the same.
1448       # 
1449       # For the same reason, we have to remove the \end_layout tag at the
1450       # end of the last layout in the inset. Again, that will sometimes be
1451       # wrong, but it will usually be right. To know what to do, we would
1452       # again have to know what layout the inset is in.
1453       
1454       blay = find_token(document.body, "\\begin_layout", i, iend)
1455       if blay == -1:
1456           document.warning("Can't find layout for preview inset!")
1457           # always do the later one first...
1458           del document.body[iend]
1459           del document.body[i]
1460           # deletions mean we do not need to reset i
1461           continue
1462
1463       # This is where we would check what layout we are in.
1464       # The check for Standard is definitely wrong.
1465       # 
1466       # lay = document.body[blay].split(None, 1)[1]
1467       # if lay != oldlayout:
1468       #     # record a boolean to tell us what to do later....
1469       #     # better to do it later, since (a) it won't mess up
1470       #     # the numbering and (b) we only modify at the end.
1471         
1472       # we want to delete the last \\end_layout in this inset, too.
1473       # note that this may not be the \\end_layout that goes with blay!!
1474       bend = find_end_of_layout(document.body, blay)
1475       while True:
1476           tmp = find_token(document.body, "\\end_layout", bend + 1, iend)
1477           if tmp == -1:
1478               break
1479           bend = tmp
1480       if bend == blay:
1481           document.warning("Unable to find last layout in preview inset!")
1482           del document.body[iend]
1483           del document.body[i]
1484           # deletions mean we do not need to reset i
1485           continue
1486       # always do the later one first...
1487       del document.body[iend]
1488       del document.body[bend]
1489       del document.body[i:blay + 1]
1490       # we do not need to reset i
1491                 
1492
1493 def revert_equalspacing_xymatrix(document):
1494     " Revert a Formula with xymatrix@! to an ERT inset "
1495     i = 0
1496     has_preamble = False
1497     has_equal_spacing = False
1498
1499     while True:
1500       i = find_token(document.body, "\\begin_inset Formula", i)
1501       if i == -1:
1502           break
1503       j = find_end_of_inset(document.body, i)
1504       if j == -1:
1505           document.warning("Malformed LyX document: Could not find end of Formula inset.")
1506           i += 1
1507           continue
1508       
1509       for curline in range(i,j):
1510           found = document.body[curline].find("\\xymatrix@!")
1511           if found != -1:
1512               break
1513  
1514       if found != -1:
1515           has_equal_spacing = True
1516           content = [document.body[i][21:]]
1517           content += document.body[i + 1:j]
1518           subst = put_cmd_in_ert(content)
1519           document.body[i:j + 1] = subst
1520           i += len(subst) - (j - i) + 1
1521       else:
1522           for curline in range(i,j):
1523               l = document.body[curline].find("\\xymatrix")
1524               if l != -1:
1525                   has_preamble = True;
1526                   break;
1527           i = j + 1
1528   
1529     if has_equal_spacing and not has_preamble:
1530         add_to_preamble(document, ['% lyx2lyx xymatrix addition', '\\usepackage[all]{xy}'])
1531
1532
1533 def revert_notefontcolor(document):
1534     " Reverts greyed-out note font color to preamble code "
1535
1536     i = find_token(document.header, "\\notefontcolor", 0)
1537     if i == -1:
1538         return
1539     colorcode = get_value(document.header, '\\notefontcolor', i)
1540     del document.header[i]
1541     # the color code is in the form #rrggbb where every character denotes a hex number
1542     red = hex2ratio(colorcode[1:3])
1543     green = hex2ratio(colorcode[3:5])
1544     blue = hex2ratio(colorcode[5:7])
1545     # write the preamble
1546     insert_to_preamble(0, document,
1547       ['% Commands inserted by lyx2lyx to set the font color',
1548         '% for greyed-out notes',
1549         '\\@ifundefined{definecolor}{\\usepackage{color}}{}'
1550         '\\definecolor{note_fontcolor}{rgb}{'
1551           + str(red) + ', ' + str(green)
1552           + ', ' + str(blue) + '}',
1553         '\\renewenvironment{lyxgreyedout}',
1554         ' {\\textcolor{note_fontcolor}\\bgroup}{\\egroup}'])
1555
1556
1557 def revert_turkmen(document):
1558     "Set language Turkmen to English" 
1559     i = 0 
1560     if document.language == "turkmen": 
1561         document.language = "english" 
1562         i = find_token(document.header, "\\language", 0) 
1563         if i != -1: 
1564             document.header[i] = "\\language english" 
1565     j = 0 
1566     while True: 
1567         j = find_token(document.body, "\\lang turkmen", j) 
1568         if j == -1: 
1569             return 
1570         document.body[j] = document.body[j].replace("\\lang turkmen", "\\lang english") 
1571         j = j + 1 
1572
1573
1574 def revert_fontcolor(document):
1575     " Reverts font color to preamble code "
1576     i = 0
1577     colorcode = ""
1578     while True:
1579       i = find_token(document.header, "\\fontcolor", i)
1580       if i == -1:
1581           return
1582       colorcode = get_value(document.header, '\\fontcolor', 0)
1583       del document.header[i]
1584       # don't clutter the preamble if backgroundcolor is not set
1585       if colorcode == "#000000":
1586           continue
1587       # the color code is in the form #rrggbb where every character denotes a hex number
1588       # convert the string to an int
1589       red = string.atoi(colorcode[1:3],16)
1590       # we want the output "0.5" for the value "127" therefore add here
1591       if red != 0:
1592           red = red + 1
1593       redout = float(red) / 256
1594       green = string.atoi(colorcode[3:5],16)
1595       if green != 0:
1596           green = green + 1
1597       greenout = float(green) / 256
1598       blue = string.atoi(colorcode[5:7],16)
1599       if blue != 0:
1600           blue = blue + 1
1601       blueout = float(blue) / 256
1602       # write the preamble
1603       insert_to_preamble(0, document,
1604                            '% Commands inserted by lyx2lyx to set the font color\n'
1605                            + '\\@ifundefined{definecolor}{\\usepackage{color}}{}\n'
1606                            + '\\definecolor{document_fontcolor}{rgb}{'
1607                            + str(redout) + ', ' + str(greenout)
1608                            + ', ' + str(blueout) + '}\n'
1609                            + '\\color{document_fontcolor}\n')
1610
1611 def revert_shadedboxcolor(document):
1612     " Reverts shaded box color to preamble code "
1613     i = 0
1614     colorcode = ""
1615     while True:
1616       i = find_token(document.header, "\\boxbgcolor", i)
1617       if i == -1:
1618           return
1619       colorcode = get_value(document.header, '\\boxbgcolor', 0)
1620       del document.header[i]
1621       # the color code is in the form #rrggbb where every character denotes a hex number
1622       # convert the string to an int
1623       red = string.atoi(colorcode[1:3],16)
1624       # we want the output "0.5" for the value "127" therefore increment here
1625       if red != 0:
1626           red = red + 1
1627       redout = float(red) / 256
1628       green = string.atoi(colorcode[3:5],16)
1629       if green != 0:
1630           green = green + 1
1631       greenout = float(green) / 256
1632       blue = string.atoi(colorcode[5:7],16)
1633       if blue != 0:
1634           blue = blue + 1
1635       blueout = float(blue) / 256
1636       # write the preamble
1637       insert_to_preamble(0, document,
1638                            '% Commands inserted by lyx2lyx to set the color\n'
1639                            '% of boxes with shaded background\n'
1640                            + '\\@ifundefined{definecolor}{\\usepackage{color}}{}\n'
1641                            + '\\definecolor{shadecolor}{rgb}{'
1642                            + str(redout) + ', ' + str(greenout)
1643                            + ', ' + str(blueout) + '}\n')
1644
1645
1646 def revert_lyx_version(document):
1647     " Reverts LyX Version information from Inset Info "
1648     version = "LyX version"
1649     try:
1650         import lyx2lyx_version
1651         version = lyx2lyx_version.version
1652     except:
1653         pass
1654
1655     i = 0
1656     while 1:
1657         i = find_token(document.body, '\\begin_inset Info', i)
1658         if i == -1:
1659             return
1660         j = find_end_of_inset(document.body, i + 1)
1661         if j == -1:
1662             # should not happen
1663             document.warning("Malformed LyX document: Could not find end of Info inset.")
1664         # We expect:
1665         # \begin_inset Info
1666         # type  "lyxinfo"
1667         # arg   "version"
1668         # \end_inset
1669         # but we shall try to be forgiving.
1670         arg = typ = ""
1671         for k in range(i, j):
1672             if document.body[k].startswith("arg"):
1673                 arg = document.body[k][3:].strip().strip('"')
1674             if document.body[k].startswith("type"):
1675                 typ = document.body[k][4:].strip().strip('"')
1676         if arg != "version" or typ != "lyxinfo":
1677             i = j + 1
1678             continue
1679
1680         # We do not actually know the version of LyX used to produce the document.
1681         # But we can use our version, since we are reverting.
1682         s = [version]
1683         # Now we want to check if the line after "\end_inset" is empty. It normally
1684         # is, so we want to remove it, too.
1685         lastline = j + 1
1686         if document.body[j + 1].strip() == "":
1687             lastline = j + 2
1688         document.body[i: lastline] = s
1689         i = i + 1
1690
1691
1692 def revert_math_scale(document):
1693   " Remove math scaling and LaTeX options "
1694   i = find_token(document.header, '\\html_math_img_scale', 0)
1695   if i != -1:
1696     del document.header[i]
1697   i = find_token(document.header, '\\html_latex_start', 0)
1698   if i != -1:
1699     del document.header[i]
1700   i = find_token(document.header, '\\html_latex_end', 0)
1701   if i != -1:
1702     del document.header[i]
1703
1704
1705 def revert_pagesizes(document):
1706   i = 0
1707   " Revert page sizes to default "
1708   i = find_token(document.header, '\\papersize', 0)
1709   if i != -1:
1710     size = document.header[i][11:]
1711     if size == "a0paper" or size == "a1paper" or size == "a2paper" \
1712     or size == "a6paper" or size == "b0paper" or size == "b1paper" \
1713     or size == "b2paper" or size == "b6paper" or size == "b0j" \
1714     or size == "b1j" or size == "b2j" or size == "b3j" or size == "b4j" \
1715     or size == "b5j" or size == "b6j":
1716       del document.header[i]
1717
1718
1719 def revert_DIN_C_pagesizes(document):
1720   i = 0
1721   " Revert DIN C page sizes to default "
1722   i = find_token(document.header, '\\papersize', 0)
1723   if i != -1:
1724     size = document.header[i][11:]
1725     if size == "c0paper" or size == "c1paper" or size == "c2paper" \
1726     or size == "c3paper" or size == "c4paper" or size == "c5paper" \
1727     or size == "c6paper":
1728       del document.header[i]
1729
1730
1731 def convert_html_quotes(document):
1732   " Remove quotes around html_latex_start and html_latex_end "
1733
1734   i = find_token(document.header, '\\html_latex_start', 0)
1735   if i != -1:
1736     line = document.header[i]
1737     l = re.compile(r'\\html_latex_start\s+"(.*)"')
1738     m = l.match(line)
1739     if m != None:
1740       document.header[i] = "\\html_latex_start " + m.group(1)
1741       
1742   i = find_token(document.header, '\\html_latex_end', 0)
1743   if i != -1:
1744     line = document.header[i]
1745     l = re.compile(r'\\html_latex_end\s+"(.*)"')
1746     m = l.match(line)
1747     if m != None:
1748       document.header[i] = "\\html_latex_end " + m.group(1)
1749       
1750
1751 def revert_html_quotes(document):
1752   " Remove quotes around html_latex_start and html_latex_end "
1753   
1754   i = find_token(document.header, '\\html_latex_start', 0)
1755   if i != -1:
1756     line = document.header[i]
1757     l = re.compile(r'\\html_latex_start\s+(.*)')
1758     m = l.match(line)
1759     document.header[i] = "\\html_latex_start \"" + m.group(1) + "\""
1760       
1761   i = find_token(document.header, '\\html_latex_end', 0)
1762   if i != -1:
1763     line = document.header[i]
1764     l = re.compile(r'\\html_latex_end\s+(.*)')
1765     m = l.match(line)
1766     document.header[i] = "\\html_latex_end \"" + m.group(1) + "\""
1767
1768
1769 def revert_output_sync(document):
1770   " Remove forward search options "
1771   i = find_token(document.header, '\\output_sync_macro', 0)
1772   if i != -1:
1773     del document.header[i]
1774   i = find_token(document.header, '\\output_sync', 0)
1775   if i != -1:
1776     del document.header[i]
1777
1778
1779 def convert_beamer_args(document):
1780   " Convert ERT arguments in Beamer to InsetArguments "
1781
1782   if document.textclass != "beamer" and document.textclass != "article-beamer":
1783     return
1784   
1785   layouts = ("Block", "ExampleBlock", "AlertBlock")
1786   for layout in layouts:
1787     blay = 0
1788     while True:
1789       blay = find_token(document.body, '\\begin_layout ' + layout, blay)
1790       if blay == -1:
1791         break
1792       elay = find_end_of(document.body, blay, '\\begin_layout', '\\end_layout')
1793       if elay == -1:
1794         document.warning("Malformed LyX document: Can't find end of " + layout + " layout.")
1795         blay += 1
1796         continue
1797       bert = find_token(document.body, '\\begin_inset ERT', blay)
1798       if bert == -1:
1799         document.warning("Malformed Beamer LyX document: Can't find argument of " + layout + " layout.")
1800         blay = elay + 1
1801         continue
1802       eert = find_end_of_inset(document.body, bert)
1803       if eert == -1:
1804         document.warning("Malformed LyX document: Can't find end of ERT.")
1805         blay = elay + 1
1806         continue
1807       
1808       # So the ERT inset begins at line k and goes to line l. We now wrap it in 
1809       # an argument inset.
1810       # Do the end first, so as not to mess up the variables.
1811       document.body[eert + 1:eert + 1] = ['', '\\end_layout', '', '\\end_inset', '']
1812       document.body[bert:bert] = ['\\begin_inset OptArg', 'status open', '', 
1813           '\\begin_layout Plain Layout']
1814       blay = elay + 9
1815
1816
1817 def revert_beamer_args(document):
1818   " Revert Beamer arguments to ERT "
1819   
1820   if document.textclass != "beamer" and document.textclass != "article-beamer":
1821     return
1822     
1823   layouts = ("Block", "ExampleBlock", "AlertBlock")
1824   for layout in layouts:
1825     blay = 0
1826     while True:
1827       blay = find_token(document.body, '\\begin_layout ' + layout, blay)
1828       if blay == -1:
1829         break
1830       elay = find_end_of(document.body, blay, '\\begin_layout', '\\end_layout')
1831       if elay == -1:
1832         document.warning("Malformed LyX document: Can't find end of " + layout + " layout.")
1833         blay += 1
1834         continue
1835       bopt = find_token(document.body, '\\begin_inset OptArg', blay)
1836       if bopt == -1:
1837         # it is legal not to have one of these
1838         blay = elay + 1
1839         continue
1840       eopt = find_end_of_inset(document.body, bopt)
1841       if eopt == -1:
1842         document.warning("Malformed LyX document: Can't find end of argument.")
1843         blay = elay + 1
1844         continue
1845       bplay = find_token(document.body, '\\begin_layout Plain Layout', blay)
1846       if bplay == -1:
1847         document.warning("Malformed LyX document: Can't find plain layout.")
1848         blay = elay + 1
1849         continue
1850       eplay = find_end_of(document.body, bplay, '\\begin_layout', '\\end_layout')
1851       if eplay == -1:
1852         document.warning("Malformed LyX document: Can't find end of plain layout.")
1853         blay = elay + 1
1854         continue
1855       # So the content of the argument inset goes from bplay + 1 to eplay - 1
1856       bcont = bplay + 1
1857       if bcont >= eplay:
1858         # Hmm.
1859         document.warning(str(bcont) + " " + str(eplay))
1860         blay = blay + 1
1861         continue
1862       # we convert the content of the argument into pure LaTeX...
1863       content = lyx2latex(document, document.body[bcont:eplay])
1864       strlist = put_cmd_in_ert(["{" + content + "}"])
1865       
1866       # now replace the optional argument with the ERT
1867       document.body[bopt:eopt + 1] = strlist
1868       blay = blay + 1
1869
1870
1871 def revert_align_decimal(document):
1872   l = 0
1873   while True:
1874     l = document.body[l].find('alignment=decimal')
1875     if l == -1:
1876         break
1877     remove_option(document, l, 'decimal_point')
1878     document.body[l].replace('decimal', 'center')
1879
1880
1881 def convert_optarg(document):
1882   " Convert \\begin_inset OptArg to \\begin_inset Argument "
1883   i = 0
1884   while 1:
1885     i = find_token(document.body, '\\begin_inset OptArg', i)
1886     if i == -1:
1887       return
1888     document.body[i] = "\\begin_inset Argument"
1889     i += 1
1890
1891
1892 def revert_argument(document):
1893   " Convert \\begin_inset Argument to \\begin_inset OptArg "
1894   i = 0
1895   while 1:
1896     i = find_token(document.body, '\\begin_inset Argument', i)
1897     if i == -1:
1898       return
1899     document.body[i] = "\\begin_inset OptArg"
1900     i += 1
1901
1902
1903 def revert_makebox(document):
1904   " Convert \\makebox to TeX code "
1905   i = 0
1906   while 1:
1907     # only revert frameless boxes without an inner box
1908     i = find_token(document.body, '\\begin_inset Box Frameless', i)
1909     if i == -1:
1910       # remove the option use_makebox
1911       revert_use_makebox(document)
1912       return
1913     z = find_end_of_inset(document.body, i)
1914     if z == -1:
1915       document.warning("Malformed LyX document: Can't find end of box inset.")
1916       return
1917     j = find_token(document.body, 'use_makebox 1', i)
1918     # assure we found the makebox of the current box
1919     if j < z and j != -1:
1920       y = find_token(document.body, "\\begin_layout", i)
1921       if y > z or y == -1:
1922         document.warning("Malformed LyX document: Can't find layout in box.")
1923         return
1924       # remove the \end_layout \end_inset pair
1925       document.body[z - 2:z + 1] = put_cmd_in_ert("}")
1926       # determine the alignment
1927       k = find_token(document.body, 'hor_pos', j - 4)
1928       align = document.body[k][9]
1929       # determine the width
1930       l = find_token(document.body, 'width "', j + 1)
1931       length = document.body[l][7:]
1932       # remove trailing '"'
1933       length = length[:-1]
1934       length = latex_length(length)[1]
1935       subst = "\\makebox[" + length + "][" \
1936         + align + "]{"
1937       document.body[i:y + 1] = put_cmd_in_ert(subst)
1938     i += 1
1939
1940
1941 def revert_use_makebox(document):
1942   " Deletes use_makebox option of boxes "
1943   h = 0
1944   while 1:
1945     # remove the option use_makebox
1946     h = find_token(document.body, 'use_makebox', 0)
1947     if h == -1:
1948       return
1949     del document.body[h]
1950     h += 1
1951
1952
1953 def convert_use_makebox(document):
1954   " Adds use_makebox option for boxes "
1955   i = 0
1956   while 1:
1957     # remove the option use_makebox
1958     i = find_token(document.body, '\\begin_inset Box', i)
1959     if i == -1:
1960       return
1961     k = find_token(document.body, 'use_parbox', i)
1962     if k == -1:
1963       document.warning("Malformed LyX document: Can't find use_parbox statement in box.")
1964       return
1965     document.body.insert(k + 1, "use_makebox 0")
1966     i = k + 1
1967
1968
1969 def revert_IEEEtran(document):
1970   " Convert IEEEtran layouts and styles to TeX code "
1971   if document.textclass != "IEEEtran":
1972     return
1973   revert_flex_inset(document, "IEEE membership", "\\IEEEmembership", 0)
1974   revert_flex_inset(document, "Lowercase", "\\MakeLowercase", 0)
1975   layouts = ("Special Paper Notice", "After Title Text", "Publication ID",
1976              "Page headings", "Biography without photo")
1977   latexcmd = {"Special Paper Notice": "\\IEEEspecialpapernotice",
1978               "After Title Text":     "\\IEEEaftertitletext",
1979               "Publication ID":       "\\IEEEpubid"}
1980   obsoletedby = {"Page headings":            "MarkBoth",
1981                  "Biography without photo":  "BiographyNoPhoto"}
1982   for layout in layouts:
1983     i = 0
1984     while True:
1985         i = find_token(document.body, '\\begin_layout ' + layout, i)
1986         if i == -1:
1987           break
1988         j = find_end_of(document.body, i, '\\begin_layout', '\\end_layout')
1989         if j == -1:
1990           document.warning("Malformed LyX document: Can't find end of " + layout + " layout.")
1991           i += 1
1992           continue
1993         if layout in obsoletedby:
1994           document.body[i] = "\\begin_layout " + obsoletedby[layout]
1995           i = j
1996         else:
1997           content = lyx2latex(document, document.body[i:j + 1])
1998           add_to_preamble(document, [latexcmd[layout] + "{" + content + "}"])
1999           del document.body[i:j + 1]
2000
2001
2002 def convert_prettyref(document):
2003         " Converts prettyref references to neutral formatted refs "
2004         re_ref = re.compile("^\s*reference\s+\"(\w+):(\S+)\"")
2005         nm_ref = re.compile("^\s*name\s+\"(\w+):(\S+)\"")
2006
2007         i = 0
2008         while True:
2009                 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
2010                 if i == -1:
2011                         break
2012                 j = find_end_of_inset(document.body, i)
2013                 if j == -1:
2014                         document.warning("Malformed LyX document: No end of InsetRef!")
2015                         i += 1
2016                         continue
2017                 k = find_token(document.body, "LatexCommand prettyref", i)
2018                 if k != -1 and k < j:
2019                         document.body[k] = "LatexCommand formatted"
2020                 i = j + 1
2021         document.header.insert(-1, "\\use_refstyle 0")
2022                 
2023  
2024 def revert_refstyle(document):
2025         " Reverts neutral formatted refs to prettyref "
2026         re_ref = re.compile("^reference\s+\"(\w+):(\S+)\"")
2027         nm_ref = re.compile("^\s*name\s+\"(\w+):(\S+)\"")
2028
2029         i = 0
2030         while True:
2031                 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
2032                 if i == -1:
2033                         break
2034                 j = find_end_of_inset(document.body, i)
2035                 if j == -1:
2036                         document.warning("Malformed LyX document: No end of InsetRef")
2037                         i += 1
2038                         continue
2039                 k = find_token(document.body, "LatexCommand formatted", i)
2040                 if k != -1 and k < j:
2041                         document.body[k] = "LatexCommand prettyref"
2042                 i = j + 1
2043         i = find_token(document.header, "\\use_refstyle", 0)
2044         if i != -1:
2045                 document.header.pop(i)
2046  
2047
2048 def revert_nameref(document):
2049   " Convert namerefs to regular references "
2050   cmds = ["Nameref", "nameref"]
2051   foundone = False
2052   rx = re.compile(r'reference "(.*)"')
2053   for cmd in cmds:
2054     i = 0
2055     oldcmd = "LatexCommand " + cmd
2056     while 1:
2057       # It seems better to look for this, as most of the reference
2058       # insets won't be ones we care about.
2059       i = find_token(document.body, oldcmd, i)
2060       if i == -1:
2061         break
2062       cmdloc = i
2063       i += 1
2064       # Make sure it is actually in an inset!
2065       # We could just check document.lines[i-1], but that relies
2066       # upon something that might easily change.
2067       # We'll look back a few lines.
2068       stins = cmdloc - 10
2069       if stins < 0:
2070         stins = 0
2071       stins = find_token(document.body, "\\begin_inset CommandInset ref", stins)
2072       if stins == -1 or stins > cmdloc:
2073         continue
2074       endins = find_end_of_inset(document.body, stins)
2075       if endins == -1:
2076         document.warning("Can't find end of inset at line " + stins + "!!")
2077         continue
2078       if endins < cmdloc:
2079         continue
2080       refline = find_token(document.body, "reference", stins)
2081       if refline == -1 or refline > endins:
2082         document.warning("Can't find reference for inset at line " + stinst + "!!")
2083         continue
2084       m = rx.match(document.body[refline])
2085       if not m:
2086         document.warning("Can't match reference line: " + document.body[ref])
2087         continue
2088       foundone = True
2089       ref = m.group(1)
2090       newcontent = ['\\begin_inset ERT', 'status collapsed', '', \
2091         '\\begin_layout Plain Layout', '', '\\backslash', \
2092         cmd + '{' + ref + '}', '\\end_layout', '', '\\end_inset']
2093       document.body[stins:endins + 1] = newcontent
2094   if foundone:
2095     add_to_preamble(document, "\usepackage{nameref}")
2096
2097
2098 def remove_Nameref(document):
2099   " Convert Nameref commands to nameref commands "
2100   i = 0
2101   while 1:
2102     # It seems better to look for this, as most of the reference
2103     # insets won't be ones we care about.
2104     i = find_token(document.body, "LatexCommand Nameref" , i)
2105     if i == -1:
2106       break
2107     cmdloc = i
2108     i += 1
2109     
2110     # Make sure it is actually in an inset!
2111     # We could just check document.lines[i-1], but that relies
2112     # upon something that might easily change.
2113     # We'll look back a few lines.
2114     stins = cmdloc - 10
2115     if stins < 0:
2116       stins = 0
2117     stins = find_token(document.body, "\\begin_inset CommandInset ref", stins)
2118     if stins == -1 or stins > cmdloc:
2119       continue
2120     endins = find_end_of_inset(document.body, stins)
2121     if endins == -1:
2122       document.warning("Can't find end of inset at line " + stins + "!!")
2123       continue
2124     if endins < cmdloc:
2125       continue
2126     document.body[cmdloc] = "LatexCommand nameref"
2127
2128
2129 def revert_mathrsfs(document):
2130     " Load mathrsfs if \mathrsfs us use in the document "
2131     i = 0
2132     end = len(document.body) - 1
2133     while True:
2134       j = document.body[i].find("\\mathscr{")
2135       if j != -1:
2136         add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
2137         add_to_preamble(document, ["\\usepackage{mathrsfs}"])
2138         break
2139       if i == end:
2140         break
2141       i += 1
2142
2143
2144 def convert_flexnames(document):
2145     "Convert \\begin_inset Flex Custom:Style to \\begin_inset Flex Style and similarly for CharStyle and Element."
2146     
2147     i = 0
2148     rx = re.compile(r'^\\begin_inset Flex (?:Custom|CharStyle|Element):(.+)$')
2149     while True:
2150       i = find_token(document.body, "\\begin_inset Flex", i)
2151       if i == -1:
2152         return
2153       m = rx.match(document.body[i])
2154       if m:
2155         document.body[i] = "\\begin_inset Flex " + m.group(1)
2156       i += 1
2157
2158
2159 flex_insets = [
2160   ["Alert", "CharStyle:Alert"],
2161   ["Code", "CharStyle:Code"],
2162   ["Concepts", "CharStyle:Concepts"],
2163   ["E-Mail", "CharStyle:E-Mail"],
2164   ["Emph", "CharStyle:Emph"],
2165   ["Expression", "CharStyle:Expression"],
2166   ["Initial", "CharStyle:Initial"],
2167   ["Institute", "CharStyle:Institute"],
2168   ["Meaning", "CharStyle:Meaning"],
2169   ["Noun", "CharStyle:Noun"],
2170   ["Strong", "CharStyle:Strong"],
2171   ["Structure", "CharStyle:Structure"],
2172   ["ArticleMode", "Custom:ArticleMode"],
2173   ["Endnote", "Custom:Endnote"],
2174   ["Glosse", "Custom:Glosse"],
2175   ["PresentationMode", "Custom:PresentationMode"],
2176   ["Tri-Glosse", "Custom:Tri-Glosse"]
2177 ]
2178
2179 flex_elements = [
2180   ["Abbrev", "Element:Abbrev"],
2181   ["CCC-Code", "Element:CCC-Code"],
2182   ["Citation-number", "Element:Citation-number"],
2183   ["City", "Element:City"],
2184   ["Code", "Element:Code"],
2185   ["CODEN", "Element:CODEN"],
2186   ["Country", "Element:Country"],
2187   ["Day", "Element:Day"],
2188   ["Directory", "Element:Directory"],
2189   ["Dscr", "Element:Dscr"],
2190   ["Email", "Element:Email"],
2191   ["Emph", "Element:Emph"],
2192   ["Filename", "Element:Filename"],
2193   ["Firstname", "Element:Firstname"],
2194   ["Fname", "Element:Fname"],
2195   ["GuiButton", "Element:GuiButton"],
2196   ["GuiMenu", "Element:GuiMenu"],
2197   ["GuiMenuItem", "Element:GuiMenuItem"],
2198   ["ISSN", "Element:ISSN"],
2199   ["Issue-day", "Element:Issue-day"],
2200   ["Issue-months", "Element:Issue-months"],
2201   ["Issue-number", "Element:Issue-number"],
2202   ["KeyCap", "Element:KeyCap"],
2203   ["KeyCombo", "Element:KeyCombo"],
2204   ["Keyword", "Element:Keyword"],
2205   ["Literal", "Element:Literal"],
2206   ["MenuChoice", "Element:MenuChoice"],
2207   ["Month", "Element:Month"],
2208   ["Orgdiv", "Element:Orgdiv"],
2209   ["Orgname", "Element:Orgname"],
2210   ["Postcode", "Element:Postcode"],
2211   ["SS-Code", "Element:SS-Code"],
2212   ["SS-Title", "Element:SS-Title"],
2213   ["State", "Element:State"],
2214   ["Street", "Element:Street"],
2215   ["Surname", "Element:Surname"],
2216   ["Volume", "Element:Volume"],
2217   ["Year", "Element:Year"]
2218 ]
2219
2220
2221 def revert_flexnames(document):
2222   if document.backend == "latex":
2223     flexlist = flex_insets
2224   else:
2225     flexlist = flex_elements
2226   
2227   rx = re.compile(r'^\\begin_inset Flex\s+(.+)$')
2228   i = 0
2229   while True:
2230     i = find_token(document.body, "\\begin_inset Flex", i)
2231     if i == -1:
2232       return
2233     m = rx.match(document.body[i])
2234     if not m:
2235       document.warning("Illegal flex inset: " + document.body[i])
2236       i += 1
2237       continue
2238     
2239     style = m.group(1)
2240     for f in flexlist:
2241       if f[0] == style:
2242         document.body[i] = "\\begin_inset Flex " + f[1]
2243         break
2244
2245     i += 1
2246
2247
2248 def convert_mathdots(document):
2249     " Load mathdots automatically "
2250     while True:
2251       i = find_token(document.header, "\\use_esint" , 0)
2252       if i != -1:
2253         document.header.insert(i + 1, "\\use_mathdots 1")
2254       break
2255
2256
2257 def revert_mathdots(document):
2258     " Load mathdots if used in the document "
2259     i = 0
2260     ddots = re.compile(r'\\begin_inset Formula .*\\ddots', re.DOTALL)
2261     vdots = re.compile(r'\\begin_inset Formula .*\\vdots', re.DOTALL)
2262     iddots = re.compile(r'\\begin_inset Formula .*\\iddots', re.DOTALL)
2263     mathdots = find_token(document.header, "\\use_mathdots" , 0)
2264     no = find_token(document.header, "\\use_mathdots 0" , 0)
2265     auto = find_token(document.header, "\\use_mathdots 1" , 0)
2266     yes = find_token(document.header, "\\use_mathdots 2" , 0)
2267     if mathdots != -1:
2268       del document.header[mathdots]
2269     while True:
2270       i = find_token(document.body, '\\begin_inset Formula', i)
2271       if i == -1:
2272         return
2273       j = find_end_of_inset(document.body, i)
2274       if j == -1:
2275         document.warning("Malformed LyX document: Can't find end of Formula inset.")
2276         return 
2277       k = ddots.search("\n".join(document.body[i:j]))
2278       l = vdots.search("\n".join(document.body[i:j]))
2279       m = iddots.search("\n".join(document.body[i:j]))
2280       if (yes == -1) and ((no != -1) or (not k and not l and not m) or (auto != -1 and not m)):
2281         i += 1
2282         continue
2283       # use \@ifundefined to catch also the "auto" case
2284       add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
2285       add_to_preamble(document, ["\\@ifundefined{iddots}{\\usepackage{mathdots}}\n"])
2286       return
2287
2288
2289 def convert_rule(document):
2290     " Convert \\lyxline to CommandInset line "
2291     i = 0
2292     while True:
2293       i = find_token(document.body, "\\lyxline" , i)
2294       if i == -1:
2295         return
2296         
2297       j = find_token(document.body, "\\color" , i - 2)
2298       if j == i - 2:
2299         color = document.body[j] + '\n'
2300       else:
2301         color = ''
2302       k = find_token(document.body, "\\begin_layout Standard" , i - 4)
2303       # we need to handle the case that \lyxline is in a separate paragraph and that it is colored
2304       # the result is then an extra empty paragraph which we get by adding an empty ERT inset
2305       if k == i - 4 and j == i - 2 and document.body[i - 1] == '':
2306         layout = '\\begin_inset ERT\nstatus collapsed\n\n\\begin_layout Plain Layout\n\n\n\\end_layout\n\n\\end_inset\n' \
2307           + '\\end_layout\n\n' \
2308           + '\\begin_layout Standard\n'
2309       elif k == i - 2 and document.body[i - 1] == '':
2310         layout = ''
2311       else:
2312         layout = '\\end_layout\n\n' \
2313           + '\\begin_layout Standard\n'
2314       l = find_token(document.body, "\\begin_layout Standard" , i + 4)
2315       if l == i + 4 and document.body[i + 1] == '':
2316         layout2 = ''
2317       else:
2318         layout2 = '\\end_layout\n' \
2319           + '\n\\begin_layout Standard\n'
2320       subst = layout \
2321         + '\\noindent\n\n' \
2322         + color \
2323         + '\\begin_inset CommandInset line\n' \
2324         + 'LatexCommand rule\n' \
2325         + 'offset "0.5ex"\n' \
2326         + 'width "100line%"\n' \
2327         + 'height "1pt"\n' \
2328         + '\n\\end_inset\n\n\n' \
2329         + layout2
2330       document.body[i] = subst
2331       i += 1
2332
2333
2334 def revert_rule(document):
2335     " Revert line insets to Tex code "
2336     i = 0
2337     while 1:
2338       i = find_token(document.body, "\\begin_inset CommandInset line" , i)
2339       if i == -1:
2340         return
2341       # find end of inset
2342       j = find_token(document.body, "\\end_inset" , i)
2343       # assure we found the end_inset of the current inset
2344       if j > i + 6 or j == -1:
2345         document.warning("Malformed LyX document: Can't find end of line inset.")
2346         return
2347       # determine the optional offset
2348       k = find_token(document.body, 'offset', i, j)
2349       if k != -1:
2350         offset = document.body[k][8:-1]
2351       else:
2352         offset = ""
2353       # determine the width
2354       l = find_token(document.body, 'width', i, j)
2355       if l != -1:
2356         width = document.body[l][7:-1]
2357       else:
2358         width = "100col%"
2359       # determine the height
2360       m = find_token(document.body, 'height', i, j)
2361       if m != -1:
2362         height = document.body[m][8:-1]
2363       else:
2364         height = "1pt"
2365       # output the \rule command
2366       if offset:
2367         subst = "\\rule[" + offset + "]{" + width + "}{" + height + "}"
2368       else:
2369         subst = "\\rule{" + width + "}{" + height + "}"
2370       document.body[i:j + 1] = put_cmd_in_ert(subst)
2371       i += 1
2372
2373
2374 def revert_diagram(document):
2375   " Add the feyn package if \\Diagram is used in math "
2376   i = 0
2377   re_diagram = re.compile(r'\\begin_inset Formula .*\\Diagram', re.DOTALL)
2378   while True:
2379     i = find_token(document.body, '\\begin_inset Formula', i)
2380     if i == -1:
2381       return
2382     j = find_end_of_inset(document.body, i)
2383     if j == -1:
2384         document.warning("Malformed LyX document: Can't find end of Formula inset.")
2385         return 
2386     m = re_diagram.search("\n".join(document.body[i:j]))
2387     if not m:
2388       i += 1
2389       continue
2390     add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
2391     add_to_preamble(document, "\\usepackage{feyn}")
2392     # only need to do it once!
2393     return
2394
2395
2396 def convert_bibtex_clearpage(document):
2397   " insert a clear(double)page bibliographystyle if bibtotoc option is used "
2398
2399   i = find_token(document.header, '\\papersides', 0)
2400   if i == -1:
2401     document.warning("Malformed LyX document: Can't find papersides definition.")
2402     return
2403   sides = int(document.header[i][12])
2404
2405   j = 0
2406   while True:
2407     j = find_token(document.body, "\\begin_inset CommandInset bibtex", j)
2408     if j == -1:
2409       return
2410
2411     k = find_end_of_inset(document.body, j)
2412     if k == -1:
2413       document.warning("Can't find end of Bibliography inset at line " + str(j))
2414       j += 1
2415       continue
2416
2417     # only act if there is the option "bibtotoc"
2418     m = find_token(document.body, 'options', j, k)
2419     if m == -1:
2420       document.warning("Can't find options for bibliography inset at line " + str(j))
2421       j = k
2422       continue
2423     
2424     optline = document.body[m]
2425     idx = optline.find("bibtotoc")
2426     if idx == -1:
2427       j = k
2428       continue
2429     
2430     # so we want to insert a new page right before the paragraph that
2431     # this bibliography thing is in. we'll look for it backwards.
2432     lay = j - 1
2433     while lay >= 0:
2434       if document.body[lay].startswith("\\begin_layout"):
2435         break
2436       lay -= 1
2437
2438     if lay < 0:
2439       document.warning("Can't find layout containing bibliography inset at line " + str(j))
2440       j = k
2441       continue
2442
2443     subst1 = '\\begin_layout Standard\n' \
2444       + '\\begin_inset Newpage clearpage\n' \
2445       + '\\end_inset\n\n\n' \
2446       + '\\end_layout\n'
2447     subst2 = '\\begin_layout Standard\n' \
2448       + '\\begin_inset Newpage cleardoublepage\n' \
2449       + '\\end_inset\n\n\n' \
2450       + '\\end_layout\n'
2451     if sides == 1:
2452       document.body.insert(lay, subst1)
2453       document.warning(subst1)
2454     else:
2455       document.body.insert(lay, subst2)
2456       document.warning(subst2)
2457
2458     j = k
2459
2460
2461 ##
2462 # Conversion hub
2463 #
2464
2465 supported_versions = ["2.0.0","2.0"]
2466 convert = [[346, []],
2467            [347, []],
2468            [348, []],
2469            [349, []],
2470            [350, []],
2471            [351, []],
2472            [352, [convert_splitindex]],
2473            [353, []],
2474            [354, []],
2475            [355, []],
2476            [356, []],
2477            [357, []],
2478            [358, []],
2479            [359, [convert_nomencl_width]],
2480            [360, []],
2481            [361, []],
2482            [362, []],
2483            [363, []],
2484            [364, []],
2485            [365, []],
2486            [366, []],
2487            [367, []],
2488            [368, []],
2489            [369, [convert_author_id]],
2490            [370, []],
2491            [371, []],
2492            [372, []],
2493            [373, [merge_gbrief]],
2494            [374, []],
2495            [375, []],
2496            [376, []],
2497            [377, []],
2498            [378, []],
2499            [379, [convert_math_output]],
2500            [380, []],
2501            [381, []],
2502            [382, []],
2503            [383, []],
2504            [384, []],
2505            [385, []],
2506            [386, []],
2507            [387, []],
2508            [388, []],
2509            [389, [convert_html_quotes]],
2510            [390, []],
2511            [391, []],
2512            [392, []],
2513            [393, [convert_optarg]],
2514            [394, [convert_use_makebox]],
2515            [395, []],
2516            [396, []],
2517            [397, [remove_Nameref]],
2518            [398, []],
2519            [399, [convert_mathdots]],
2520            [400, [convert_rule]],
2521            [401, []],
2522            [402, [convert_bibtex_clearpage]],
2523            [403, [convert_flexnames]],
2524            [404, [convert_prettyref]]
2525 ]
2526
2527 revert =  [[403, [revert_refstyle]],
2528            [402, [revert_flexnames]],
2529            [401, []],
2530            [400, [revert_diagram]],
2531            [399, [revert_rule]],
2532            [398, [revert_mathdots]],
2533            [397, [revert_mathrsfs]],
2534            [396, []],
2535            [395, [revert_nameref]],
2536            [394, [revert_DIN_C_pagesizes]],
2537            [393, [revert_makebox]],
2538            [392, [revert_argument]],
2539            [391, [revert_beamer_args]],
2540            [390, [revert_align_decimal, revert_IEEEtran]],
2541            [389, [revert_output_sync]],
2542            [388, [revert_html_quotes]],
2543            [387, [revert_pagesizes]],
2544            [386, [revert_math_scale]],
2545            [385, [revert_lyx_version]],
2546            [384, [revert_shadedboxcolor]],
2547            [383, [revert_fontcolor]],
2548            [382, [revert_turkmen]],
2549            [381, [revert_notefontcolor]],
2550            [380, [revert_equalspacing_xymatrix]],
2551            [379, [revert_inset_preview]],
2552            [378, [revert_math_output]],
2553            [377, []],
2554            [376, [revert_multirow]],
2555            [375, [revert_includeall]],
2556            [374, [revert_includeonly]],
2557            [373, [revert_html_options]],
2558            [372, [revert_gbrief]],
2559            [371, [revert_fontenc]],
2560            [370, [revert_mhchem]],
2561            [369, [revert_suppress_date]],
2562            [368, [revert_author_id]],
2563            [367, [revert_hspace_glue_lengths]],
2564            [366, [revert_percent_vspace_lengths, revert_percent_hspace_lengths]],
2565            [365, [revert_percent_skip_lengths]],
2566            [364, [revert_paragraph_indentation]],
2567            [363, [revert_branch_filename]],
2568            [362, [revert_longtable_align]],
2569            [361, [revert_applemac]],
2570            [360, []],
2571            [359, [revert_nomencl_cwidth]],
2572            [358, [revert_nomencl_width]],
2573            [357, [revert_custom_processors]],
2574            [356, [revert_ulinelatex]],
2575            [355, []],
2576            [354, [revert_strikeout]],
2577            [353, [revert_printindexall]],
2578            [352, [revert_subindex]],
2579            [351, [revert_splitindex]],
2580            [350, [revert_backgroundcolor]],
2581            [349, [revert_outputformat]],
2582            [348, [revert_xetex]],
2583            [347, [revert_phantom, revert_hphantom, revert_vphantom]],
2584            [346, [revert_tabularvalign]],
2585            [345, [revert_swiss]]
2586           ]
2587
2588
2589 if __name__ == "__main__":
2590     pass