]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_1_6.py
2f8ebd24b8a6ae9664902d786a0e2eda3a3e8432
[lyx.git] / lib / lyx2lyx / lyx_1_6.py
1 # This file is part of lyx2lyx
2 # -*- coding: utf-8 -*-
3 # Copyright (C) 2007-2008 The LyX Team <lyx-devel@lists.lyx.org>
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18
19 """ Convert files to the file format generated by lyx 1.6"""
20
21 import re
22 import unicodedata
23 import sys, os
24
25 from parser_tools import find_token, find_end_of, find_tokens, get_value
26
27 ####################################################################
28 # Private helper functions
29
30
31 def get_value_string(lines, token, start, end = 0, trim = False, default = ""):
32     """ get_value_string(lines, token, start[[, end], trim, default]) -> string
33
34     Return tokens after token as string, in lines, where
35     token is the first element. When trim is used, the first and last character
36     of the string is trimmed."""
37
38     val = get_value(lines, token, start, end, "")
39     if not val:
40       return default
41     if trim:
42       return val[1:-1]
43     return val
44
45
46 def find_end_of_inset(lines, i):
47     " Find end of inset, where lines[i] is included."
48     return find_end_of(lines, i, "\\begin_inset", "\\end_inset")
49
50 # WARNING!
51 # DO NOT do this:
52 #   document.body[i] = wrap_insert_ert(...)
53 # wrap_into_ert may returns a multiline string, which should NOT appear
54 # in document.body. Instead, do something like this:
55 #   subst = wrap_inset_ert(...)
56 #   subst = subst.split('\n')
57 #   document.body[i:i+1] = subst
58 #   i+= len(subst) - 1
59 # where the last statement resets the counter to accord with the added
60 # lines.
61 def wrap_into_ert(string, src, dst):
62     '''Within string, replace occurrences of src with dst, wrapped into ERT
63        E.g.: wrap_into_ert('sch\"on', "\\", "\\backslash") is:
64        sch<ERT>\\backslash</ERT>"on'''
65     return string.replace(src, '\n\\begin_inset ERT\nstatus collapsed\n\\begin_layout Standard\n'
66       + dst + '\n\\end_layout\n\\end_inset\n')
67
68 def put_cmd_in_ert(string):
69     for rep in unicode_reps:
70         string = string.replace(rep[1], rep[0].replace('\\\\', '\\'))
71     string = string.replace('\\', "\\backslash\n")
72     string = "\\begin_inset ERT\nstatus collapsed\n\\begin_layout Standard\n" \
73       + string + "\n\\end_layout\n\\end_inset"
74     return string
75
76 def add_to_preamble(document, text):
77     """ Add text to the preamble if it is not already there.
78     Only the first line is checked!"""
79
80     if find_token(document.preamble, text[0], 0) != -1:
81         return
82
83     document.preamble.extend(text)
84
85 def insert_to_preamble(index, document, text):
86     """ Insert text to the preamble at a given line"""
87
88     document.preamble.insert(index, text)
89
90 # Convert a LyX length into a LaTeX length
91 def convert_len(len):
92     units = {"text%":"\\backslash\ntextwidth", "col%":"\\backslash\ncolumnwidth",
93              "page%":"\\backslash\npagewidth", "line%":"\\backslash\nlinewidth",
94              "theight%":"\\backslash\ntextheight", "pheight%":"\\backslash\npageheight"}
95
96     # Convert LyX units to LaTeX units
97     for unit in list(units.keys()):
98         if len.find(unit) != -1:
99             len = '%f' % (len2value(len) / 100)
100             len = len.strip('0') + units[unit]
101             break
102
103     return len
104
105 # Return the value of len without the unit in numerical form.
106 def len2value(len):
107     result = re.search('([+-]?[0-9.]+)', len)
108     if result:
109         return float(result.group(1))
110     # No number means 1.0
111     return 1.0
112
113 # Unfortunately, this doesn't really work, since Standard isn't always default.
114 # But it's as good as we can do right now.
115 def find_default_layout(document, start, end):
116     l = find_token(document.body, "\\begin_layout Standard", start, end)
117     if l == -1:
118         l = find_token(document.body, "\\begin_layout PlainLayout", start, end)
119     if l == -1:
120         l = find_token(document.body, "\\begin_layout Plain Layout", start, end)
121     return l
122
123 def get_option(document, m, option, default):
124     l = document.body[m].find(option)
125     val = default
126     if l != -1:
127         val = document.body[m][l:].split('"')[1]
128     return val
129
130 def remove_option(document, m, option):
131     l = document.body[m].find(option)
132     if l != -1:
133         val = document.body[m][l:].split('"')[1]
134         document.body[m] = document.body[m][:l-1] + document.body[m][l+len(option + '="' + val + '"'):]
135     return l
136
137 def set_option(document, m, option, value):
138     l = document.body[m].find(option)
139     if l != -1:
140         oldval = document.body[m][l:].split('"')[1]
141         l = l + len(option + '="')
142         document.body[m] = document.body[m][:l] + value + document.body[m][l+len(oldval):]
143     else:
144         document.body[m] = document.body[m][:-1] + ' ' + option + '="' + value + '">'
145     return l
146
147
148 # FIXME: Use the version in unicode_symbols.py which has some bug fixes
149 def read_unicodesymbols():
150     " Read the unicodesymbols list of unicode characters and corresponding commands."
151     pathname = os.path.abspath(os.path.dirname(sys.argv[0]))
152     fp = open(os.path.join(pathname.strip('lyx2lyx'), 'unicodesymbols'))
153     spec_chars = []
154     # Two backslashes, followed by some non-word character, and then a character
155     # in brackets. The idea is to check for constructs like: \"{u}, which is how
156     # they are written in the unicodesymbols file; but they can also be written
157     # as: \"u or even \" u.
158     r = re.compile(r'\\\\(\W)\{(\w)\}')
159     for line in fp.readlines():
160         if line[0] != '#' and line.strip() != "":
161             line=line.replace(' "',' ') # remove all quotation marks with spaces before
162             line=line.replace('" ',' ') # remove all quotation marks with spaces after
163             line=line.replace(r'\"','"') # replace \" by " (for characters with diaeresis)
164             try:
165                 [ucs4,command,dead] = line.split(None,2)
166                 if command[0:1] != "\\":
167                     continue
168                 spec_chars.append([command, unichr(eval(ucs4))])
169             except:
170                 continue
171             m = r.match(command)
172             if m != None:
173                 command = "\\\\"
174                 # If the character is a double-quote, then we need to escape it, too,
175                 # since it is done that way in the LyX file.
176                 if m.group(1) == "\"":
177                     command += "\\"
178                 commandbl = command
179                 command += m.group(1) + m.group(2)
180                 commandbl += m.group(1) + ' ' + m.group(2)
181                 spec_chars.append([command, unichr(eval(ucs4))])
182                 spec_chars.append([commandbl, unichr(eval(ucs4))])
183     fp.close()
184     return spec_chars
185
186
187 def extract_argument(line):
188     'Extracts a LaTeX argument from the start of line. Returns (arg, rest).'
189
190     if not line:
191         return (None, "")
192
193     bracere = re.compile("(\s*)(.*)")
194     n = bracere.match(line)
195     whitespace = n.group(1)
196     stuff = n.group(2)
197     brace = stuff[:1]
198     if brace != "[" and brace != "{":
199         return (None, line)
200
201     # find closing brace
202     remain = stuff[1:]
203     pos = 0
204     num = 1
205     term = "}"
206     if brace == "[":
207         term = "]"
208     skip = False
209     for c in remain:
210         if skip:
211             skip = False
212         elif c == "\\":
213             skip = True
214         elif c == brace:
215             num += 1
216         elif c == term:
217             num -= 1
218         if c == 0:
219             break
220         pos += 1
221     if num != 0:
222         # We never found the matching brace
223         # So, to be on the safe side, let's just return everything
224         # which will then get wrapped as ERT
225         return (line, "")
226     return (line[:pos + 1], line[pos + 1:])
227
228
229 def latex2ert(line, isindex):
230     '''Converts LaTeX commands into ERT. line may well be a multi-line
231        string when it is returned.'''
232     if not line:
233         return line
234
235     retval = ""
236     ## FIXME Escaped \ ??
237     # This regex looks for a LaTeX command---i.e., something of the form
238     # "\alPhaStuFF", or "\X", where X is any character---where the command
239     # may also be preceded by an additional backslash, which is how it would
240     # appear (e.g.) in an InsetIndex.
241     labelre = re.compile(r'(.*?)\\?(\\(?:[a-zA-Z]+|.))(.*)')
242
243     m = labelre.match(line)
244     while m != None:
245         retval += m.group(1)
246         cmd = m.group(2)
247         end = m.group(3)
248
249         while True:
250             (arg, rest) = extract_argument(end)
251             if arg == None:
252                 break
253             cmd += arg
254             end = rest
255         # If we wanted to put labels into an InsetLabel, for example, then we
256         # would just need to test here for cmd == "label" and then take some
257         # appropriate action, i.e., to use arg to get the content and then
258         # wrap it appropriately.
259         cmd = put_cmd_in_ert(cmd)
260         retval += "\n" + cmd + "\n"
261         line = end
262         m = labelre.match(line)
263     # put all remaining braces in ERT
264     line = wrap_into_ert(line, '}', '}')
265     line = wrap_into_ert(line, '{', '{')
266     if isindex:
267         # active character that is not available in all font encodings
268         line = wrap_into_ert(line, '|', '|')
269     retval += line
270     return retval
271
272
273 unicode_reps = read_unicodesymbols()
274
275 #Bug 5022....
276 #Might should do latex2ert first, then deal with stuff that DOESN'T
277 #end up inside ERT. That routine could be modified so that it returned
278 #a list of lines, and we could then skip ERT bits and only deal with
279 #the other bits.
280 def latex2lyx(data, isindex):
281     '''Takes a string, possibly multi-line, and returns the result of
282     converting LaTeX constructs into LyX constructs. Returns a list of
283     lines, suitable for insertion into document.body.
284     The bool isindex specifies whether we are in an index macro (which
285     has some specific active characters that need to be ERTed).'''
286
287     if not data:
288         return [""]
289     retval = []
290
291     # Convert LaTeX to Unicode
292     # Commands of this sort need to be checked to make sure they are
293     # followed by a non-alpha character, lest we replace too much.
294     hardone = re.compile(r'^\\\\[a-zA-Z]+$')
295
296     for rep in unicode_reps:
297         if hardone.match(rep[0]):
298             pos = 0
299             while True:
300                 pos = data.find(rep[0], pos)
301                 if pos == -1:
302                     break
303                 nextpos = pos + len(rep[0])
304                 if nextpos < len(data) and data[nextpos].isalpha():
305                     # not the end of that command
306                     pos = nextpos
307                     continue
308                 data = data[:pos] + rep[1] + data[nextpos:]
309                 pos = nextpos
310         else:
311             data = data.replace(rep[0], rep[1])
312
313     # Generic
314     # \" -> ":
315     data = wrap_into_ert(data, r'\"', '"')
316     # \\ -> \:
317     data = data.replace('\\\\', '\\')
318
319     # Math:
320     mathre = re.compile('^(.*?)(\$.*?\$)(.*)')
321     lines = data.split('\n')
322     for line in lines:
323         #document.warning("LINE: " + line)
324         #document.warning(str(i) + ":" + document.body[i])
325         #document.warning("LAST: " + document.body[-1])
326         g = line
327         m = mathre.match(g)
328         while m != None:
329             s = m.group(1)
330             f = m.group(2).replace('\\\\', '\\')
331             g = m.group(3)
332             if s:
333                 # this is non-math!
334                 s = latex2ert(s, isindex)
335                 subst = s.split('\n')
336                 retval += subst
337             retval.append("\\begin_inset Formula " + f)
338             retval.append("\\end_inset")
339             m = mathre.match(g)
340         # Handle whatever is left, which is just text
341         g = latex2ert(g, isindex)
342         subst = g.split('\n')
343         retval += subst
344     return retval
345
346
347 def lyxline2latex(document, line, inert):
348     'Convert some LyX stuff into corresponding LaTeX stuff line-wise, as best we can.'
349     if line.startswith("\\begin_inset Formula"):
350         line = line[20:]
351     elif line.startswith("\\begin_inset Quotes"):
352         # For now, we do a very basic reversion. Someone who understands
353         # quotes is welcome to fix it up.
354         qtype = line[20:].strip()
355         # lang = qtype[0]
356         side = qtype[1]
357         dbls = qtype[2]
358         if side == "l":
359             if dbls == "d":
360                 line = "``"
361             else:
362                 line = "`"
363         else:
364             if dbls == "d":
365                 line = "''"
366             else:
367                 line = "'"
368     elif line.isspace() or \
369           line.startswith("\\begin_layout") or \
370           line.startswith("\\end_layout") or \
371           line.startswith("\\begin_inset") or \
372           line.startswith("\\end_inset") or \
373           line.startswith("\\lang") or \
374           line.strip() == "status collapsed" or \
375           line.strip() == "status open":
376         #skip all that stuff
377         return ""
378
379     # this needs to be added to the preamble because of cases like
380     # \textmu, \textbackslash, etc.
381     add_to_preamble(document, ['% added by lyx2lyx for converted entries',
382                                '\\@ifundefined{textmu}',
383                                ' {\\usepackage{textcomp}}{}'])
384     # a lossless reversion is not possible
385     # try at least to handle some common insets and settings
386     if inert:
387         line = line.replace(r'\backslash', '\\')
388     else:
389         line = line.replace('&', '\\&{}')
390         line = line.replace('#', '\\#{}')
391         line = line.replace('^', '\\^{}')
392         line = line.replace('%', '\\%{}')
393         line = line.replace('_', '\\_{}')
394         line = line.replace('$', '\\${}')
395
396         # Do the LyX text --> LaTeX conversion
397         for rep in unicode_reps:
398             line = line.replace(rep[1], rep[0].replace('\\\\', '\\') + "{}")
399             line = line.replace(r'\backslash', r'\textbackslash{}')
400             line = line.replace(r'\series bold', r'\bfseries{}').replace(r'\series default', r'\mdseries{}')
401             line = line.replace(r'\shape italic', r'\itshape{}').replace(r'\shape smallcaps', r'\scshape{}')
402             line = line.replace(r'\shape slanted', r'\slshape{}').replace(r'\shape default', r'\upshape{}')
403             line = line.replace(r'\emph on', r'\em{}').replace(r'\emph default', r'\em{}')
404             line = line.replace(r'\noun on', r'\scshape{}').replace(r'\noun default', r'\upshape{}')
405             line = line.replace(r'\bar under', r'\underbar{').replace(r'\bar default', r'}')
406             line = line.replace(r'\family sans', r'\sffamily{}').replace(r'\family default', r'\normalfont{}')
407             line = line.replace(r'\family typewriter', r'\ttfamily{}').replace(r'\family roman', r'\rmfamily{}')
408             line = line.replace(r'\InsetSpace ', r'').replace(r'\SpecialChar ', r'')
409     return line
410
411
412 def lyx2latex(document, lines):
413     'Convert some LyX stuff into corresponding LaTeX stuff, as best we can.'
414     # clean up multiline stuff
415     content = ""
416     ert_end = 0
417
418     for curline in range(len(lines)):
419         line = lines[curline]
420         if line.startswith("\\begin_inset ERT"):
421             # We don't want to replace things inside ERT, so figure out
422             # where the end of the inset is.
423             ert_end = find_end_of_inset(lines, curline + 1)
424             continue
425         inert = ert_end >= curline
426         content += lyxline2latex(document, lines[curline], inert)
427       
428     return content
429
430
431 ####################################################################
432
433 def convert_ltcaption(document):
434     i = 0
435     while True:
436         i = find_token(document.body, "\\begin_inset Tabular", i)
437         if i == -1:
438             return
439         j = find_end_of_inset(document.body, i + 1)
440         if j == -1:
441             document.warning("Malformed LyX document: Could not find end of tabular.")
442             i += 1
443             continue
444
445         nrows = int(document.body[i+1].split('"')[3])
446         ncols = int(document.body[i+1].split('"')[5])
447
448         m = i + 1
449         for k in range(nrows):
450             m = find_token(document.body, "<row", m)
451             r = m
452             caption = 'false'
453             for k in range(ncols):
454                 m = find_token(document.body, "<cell", m)
455                 if (k == 0):
456                     mend = find_token(document.body, "</cell>", m + 1)
457                     # first look for caption insets
458                     mcap = find_token(document.body, "\\begin_inset Caption", m + 1, mend)
459                     # then look for ERT captions
460                     if mcap == -1:
461                         mcap = find_token(document.body, "caption", m + 1, mend)
462                         if mcap > -1:
463                             mcap = find_token(document.body, "\\backslash", mcap - 1, mcap)
464                     if mcap > -1:
465                         caption = 'true'
466                 if caption == 'true':
467                     if (k == 0):
468                         set_option(document, r, 'caption', 'true')
469                         set_option(document, m, 'multicolumn', '1')
470                         set_option(document, m, 'bottomline', 'false')
471                         set_option(document, m, 'topline', 'false')
472                         set_option(document, m, 'rightline', 'false')
473                         set_option(document, m, 'leftline', 'false')
474                         #j = find_end_of_inset(document.body, j + 1)
475                     else:
476                         set_option(document, m, 'multicolumn', '2')
477                 m = m + 1
478             m = m + 1
479
480         i = j + 1
481
482
483 #FIXME Use of wrap_into_ert can confuse lyx2lyx
484 def revert_ltcaption(document):
485     i = 0
486     while True:
487         i = find_token(document.body, "\\begin_inset Tabular", i)
488         if i == -1:
489             return
490         j = find_end_of_inset(document.body, i + 1)
491         if j == -1:
492             document.warning("Malformed LyX document: Could not find end of tabular.")
493             i += 1
494             continue
495
496         m = i + 1
497         nrows = int(document.body[i+1].split('"')[3])
498         ncols = int(document.body[i+1].split('"')[5])
499
500         for k in range(nrows):
501             m = find_token(document.body, "<row", m)
502             caption = get_option(document, m, 'caption', 'false')
503             if caption == 'true':
504                 remove_option(document, m, 'caption')
505                 for k in range(ncols):
506                     m = find_token(document.body, "<cell", m)
507                     remove_option(document, m, 'multicolumn')
508                     if k == 0:
509                         m = find_token(document.body, "\\begin_inset Caption", m)
510                         if m == -1:
511                             return
512                         m = find_end_of_inset(document.body, m + 1)
513                         document.body[m] += wrap_into_ert("","","\\backslash\n\\backslash\n%")
514                     m = m + 1
515             m = m + 1
516         i = j + 1
517
518
519 def convert_tablines(document):
520     i = 0
521     while True:
522         i = find_token(document.body, "\\begin_inset Tabular", i)
523         if i == -1:
524             # LyX 1.3 inserted an extra space between \begin_inset
525             # and Tabular so let us try if this is the case and fix it.
526             i = find_token(document.body, "\\begin_inset  Tabular", i)
527             if i == -1:
528                 return
529             else:
530                 document.body[i] = "\\begin_inset Tabular"
531         j = find_end_of_inset(document.body, i + 1)
532         if j == -1:
533             document.warning("Malformed LyX document: Could not find end of tabular.")
534             i += 1
535             continue
536
537         m = i + 1
538         nrows = int(document.body[i+1].split('"')[3])
539         ncols = int(document.body[i+1].split('"')[5])
540
541         col_info = []
542         for k in range(ncols):
543             m = find_token(document.body, "<column", m)
544             left = get_option(document, m, 'leftline', 'false')
545             right = get_option(document, m, 'rightline', 'false')
546             col_info.append([left, right])
547             remove_option(document, m, 'leftline')
548             remove_option(document, m, 'rightline')
549             m = m + 1
550
551         row_info = []
552         for k in range(nrows):
553             m = find_token(document.body, "<row", m)
554             top = get_option(document, m, 'topline', 'false')
555             bottom = get_option(document, m, 'bottomline', 'false')
556             row_info.append([top, bottom])
557             remove_option(document, m, 'topline')
558             remove_option(document, m, 'bottomline')
559             m = m + 1
560
561         m = i + 1
562         mc_info = []
563         for k in range(nrows*ncols):
564             m = find_token(document.body, "<cell", m)
565             mc_info.append(get_option(document, m, 'multicolumn', '0'))
566             m = m + 1
567         m = i + 1
568         for l in range(nrows):
569             for k in range(ncols):
570                 m = find_token(document.body, '<cell', m)
571                 if mc_info[l*ncols + k] == '0':
572                     r = set_option(document, m, 'topline', row_info[l][0])
573                     r = set_option(document, m, 'bottomline', row_info[l][1])
574                     r = set_option(document, m, 'leftline', col_info[k][0])
575                     r = set_option(document, m, 'rightline', col_info[k][1])
576                 elif mc_info[l*ncols + k] == '1':
577                     s = k + 1
578                     while s < ncols and mc_info[l*ncols + s] == '2':
579                         s = s + 1
580                     if s < ncols and mc_info[l*ncols + s] != '1':
581                         r = set_option(document, m, 'rightline', col_info[k][1])
582                     if k > 0 and mc_info[l*ncols + k - 1] == '0':
583                         r = set_option(document, m, 'leftline', col_info[k][0])
584                 m = m + 1
585         i = j + 1
586
587
588 def revert_tablines(document):
589     i = 0
590     while True:
591         i = find_token(document.body, "\\begin_inset Tabular", i)
592         if i == -1:
593             return
594         j = find_end_of_inset(document.body, i)
595         if j == -1:
596             document.warning("Malformed LyX document: Could not find end of tabular.")
597             i += 1
598             continue
599
600         m = i + 1
601         nrows = int(document.body[i+1].split('"')[3])
602         ncols = int(document.body[i+1].split('"')[5])
603
604         lines = []
605         for k in range(nrows*ncols):
606             m = find_token(document.body, "<cell", m)
607             top = get_option(document, m, 'topline', 'false')
608             bottom = get_option(document, m, 'bottomline', 'false')
609             left = get_option(document, m, 'leftline', 'false')
610             right = get_option(document, m, 'rightline', 'false')
611             lines.append([top, bottom, left, right])
612             m = m + 1
613
614         # we will want to ignore longtable captions
615         m = i + 1
616         caption_info = []
617         for k in range(nrows):
618             m = find_token(document.body, "<row", m)
619             caption = get_option(document, m, 'caption', 'false')
620             caption_info.append([caption])
621             m = m + 1
622
623         m = i + 1
624         col_info = []
625         for k in range(ncols):
626             m = find_token(document.body, "<column", m)
627             left = 'true'
628             for l in range(nrows):
629                 left = lines[l*ncols + k][2]
630                 if left == 'false' and caption_info[l] == 'false':
631                     break
632             set_option(document, m, 'leftline', left)
633             right = 'true'
634             for l in range(nrows):
635                 right = lines[l*ncols + k][3]
636                 if right == 'false' and caption_info[l] == 'false':
637                     break
638             set_option(document, m, 'rightline', right)
639             m = m + 1
640
641         row_info = []
642         for k in range(nrows):
643             m = find_token(document.body, "<row", m)
644             top = 'true'
645             for l in range(ncols):
646                 top = lines[k*ncols + l][0]
647                 if top == 'false':
648                     break
649             if caption_info[k] == 'false':
650                 top = 'false'
651             set_option(document, m, 'topline', top)
652             bottom = 'true'
653             for l in range(ncols):
654                 bottom = lines[k*ncols + l][1]
655                 if bottom == 'false':
656                     break
657             if caption_info[k] == 'false':
658                 bottom = 'false'
659             set_option(document, m, 'bottomline', bottom)
660             m = m + 1
661
662         i = j + 1
663
664
665 def fix_wrong_tables(document):
666     i = 0
667     while True:
668         i = find_token(document.body, "\\begin_inset Tabular", i)
669         if i == -1:
670             return
671         j = find_end_of_inset(document.body, i + 1)
672         if j == -1:
673             document.warning("Malformed LyX document: Could not find end of tabular.")
674             i += 1
675             continue
676
677         m = i + 1
678         nrows = int(document.body[i+1].split('"')[3])
679         ncols = int(document.body[i+1].split('"')[5])
680
681         for l in range(nrows):
682             prev_multicolumn = 0
683             for k in range(ncols):
684                 m = find_token(document.body, '<cell', m)
685
686                 if document.body[m].find('multicolumn') != -1:
687                     multicol_cont = int(document.body[m].split('"')[1])
688
689                     if multicol_cont == 2 and (k == 0 or prev_multicolumn == 0):
690                         document.body[m] = document.body[m][:5] + document.body[m][21:]
691                         prev_multicolumn = 0
692                     else:
693                         prev_multicolumn = multicol_cont
694                 else:
695                     prev_multicolumn = 0
696
697         i = j + 1
698
699
700 def close_begin_deeper(document):
701     i = 0
702     depth = 0
703     while True:
704         i = find_tokens(document.body, ["\\begin_deeper", "\\end_deeper"], i)
705
706         if i == -1:
707             break
708
709         if document.body[i][:13] == "\\begin_deeper":
710             depth += 1
711         else:
712             depth -= 1
713
714         i += 1
715
716     document.body[-2:-2] = ['\\end_deeper' for i in range(depth)]
717
718
719 def long_charstyle_names(document):
720     i = 0
721     while True:
722         i = find_token(document.body, "\\begin_inset CharStyle", i)
723         if i == -1:
724             return
725         document.body[i] = document.body[i].replace("CharStyle ", "CharStyle CharStyle:")
726         i += 1
727
728 def revert_long_charstyle_names(document):
729     i = 0
730     while True:
731         i = find_token(document.body, "\\begin_inset CharStyle", i)
732         if i == -1:
733             return
734         document.body[i] = document.body[i].replace("CharStyle CharStyle:", "CharStyle ")
735         i += 1
736
737
738 def axe_show_label(document):
739     i = 0
740     while True:
741         i = find_token(document.body, "\\begin_inset CharStyle", i)
742         if i == -1:
743             return
744         if document.body[i + 1].find("show_label") != -1:
745             if document.body[i + 1].find("true") != -1:
746                 document.body[i + 1] = "status open"
747                 del document.body[ i + 2]
748             else:
749                 if document.body[i + 1].find("false") != -1:
750                     document.body[i + 1] = "status collapsed"
751                     del document.body[ i + 2]
752                 else:
753                     document.warning("Malformed LyX document: show_label neither false nor true.")
754         else:
755             document.warning("Malformed LyX document: show_label missing in CharStyle.")
756
757         i += 1
758
759
760 def revert_show_label(document):
761     i = 0
762     while True:
763         i = find_token(document.body, "\\begin_inset CharStyle", i)
764         if i == -1:
765             return
766         if document.body[i + 1].find("status open") != -1:
767             document.body.insert(i + 1, "show_label true")
768         else:
769             if document.body[i + 1].find("status collapsed") != -1:
770                 document.body.insert(i + 1, "show_label false")
771             else:
772                 document.warning("Malformed LyX document: no legal status line in CharStyle.")
773         i += 1
774
775 def revert_begin_modules(document):
776     i = 0
777     while True:
778         i = find_token(document.header, "\\begin_modules", i)
779         if i == -1:
780             return
781         j = find_end_of(document.header, i, "\\begin_modules", "\\end_modules")
782         if j == -1:
783             # this should not happen
784             break
785         document.header[i : j + 1] = []
786
787 def convert_flex(document):
788     "Convert CharStyle to Flex"
789     i = 0
790     while True:
791         i = find_token(document.body, "\\begin_inset CharStyle", i)
792         if i == -1:
793             return
794         document.body[i] = document.body[i].replace('\\begin_inset CharStyle', '\\begin_inset Flex')
795
796 def revert_flex(document):
797     "Revert Flex to CharStyle"
798     i = 0
799     while True:
800         i = find_token(document.body, "\\begin_inset Flex", i)
801         if i == -1:
802             return
803         document.body[i] = document.body[i].replace('\\begin_inset Flex', '\\begin_inset CharStyle')
804
805
806 def revert_pdf_options(document):
807         "Revert PDF options for hyperref."
808         # store the PDF options and delete the entries from the Lyx file
809         i = 0
810         hyperref = False
811         title = ""
812         author = ""
813         subject = ""
814         keywords = ""
815         bookmarks = ""
816         bookmarksnumbered = ""
817         bookmarksopen = ""
818         bookmarksopenlevel = ""
819         breaklinks = ""
820         pdfborder = ""
821         colorlinks = ""
822         backref = ""
823         pagebackref = ""
824         pagemode = ""
825         otheroptions = ""
826         i = find_token(document.header, "\\use_hyperref", i)
827         if i != -1:
828             hyperref = get_value(document.header, "\\use_hyperref", i) == 'true'
829             del document.header[i]
830         i = find_token(document.header, "\\pdf_store_options", i)
831         if i != -1:
832             del document.header[i]
833         i = find_token(document.header, "\\pdf_title", 0)
834         if i != -1:
835             title = get_value_string(document.header, '\\pdf_title', 0, 0, True)
836             title = ' pdftitle={' + title + '}'
837             del document.header[i]
838         i = find_token(document.header, "\\pdf_author", 0)
839         if i != -1:
840             author = get_value_string(document.header, '\\pdf_author', 0, 0, True)
841             if title == "":
842                 author = ' pdfauthor={' + author + '}'
843             else:
844                 author = ',\n pdfauthor={' + author + '}'
845             del document.header[i]
846         i = find_token(document.header, "\\pdf_subject", 0)
847         if i != -1:
848             subject = get_value_string(document.header, '\\pdf_subject', 0, 0, True)
849             if title == "" and author == "":
850                 subject = ' pdfsubject={' + subject + '}'
851             else:
852                 subject = ',\n pdfsubject={' + subject + '}'
853             del document.header[i]
854         i = find_token(document.header, "\\pdf_keywords", 0)
855         if i != -1:
856             keywords = get_value_string(document.header, '\\pdf_keywords', 0, 0, True)
857             if title == "" and author == "" and subject == "":
858                 keywords = ' pdfkeywords={' + keywords + '}'
859             else:
860                 keywords = ',\n pdfkeywords={' + keywords + '}'
861             del document.header[i]
862         i = find_token(document.header, "\\pdf_bookmarks", 0)
863         if i != -1:
864             bookmarks = get_value_string(document.header, '\\pdf_bookmarks', 0)
865             bookmarks = ',\n bookmarks=' + bookmarks
866             del document.header[i]
867         i = find_token(document.header, "\\pdf_bookmarksnumbered", i)
868         if i != -1:
869             bookmarksnumbered = get_value_string(document.header, '\\pdf_bookmarksnumbered', 0)
870             bookmarksnumbered = ',\n bookmarksnumbered=' + bookmarksnumbered
871             del document.header[i]
872         i = find_token(document.header, "\\pdf_bookmarksopen", i)
873         if i != -1:
874             bookmarksopen = get_value_string(document.header, '\\pdf_bookmarksopen', 0)
875             bookmarksopen = ',\n bookmarksopen=' + bookmarksopen
876             del document.header[i]
877         i = find_token(document.header, "\\pdf_bookmarksopenlevel", i)
878         if i != -1:
879             bookmarksopenlevel = get_value_string(document.header, '\\pdf_bookmarksopenlevel', 0, 0, True)
880             bookmarksopenlevel = ',\n bookmarksopenlevel=' + bookmarksopenlevel
881             del document.header[i]
882         i = find_token(document.header, "\\pdf_breaklinks", i)
883         if i != -1:
884             breaklinks = get_value_string(document.header, '\\pdf_breaklinks', 0)
885             breaklinks = ',\n breaklinks=' + breaklinks
886             del document.header[i]
887         i = find_token(document.header, "\\pdf_pdfborder", i)
888         if i != -1:
889             pdfborder = get_value_string(document.header, '\\pdf_pdfborder', 0)
890             if pdfborder == 'true':
891                 pdfborder = ',\n pdfborder={0 0 0}'
892             else:
893                 pdfborder = ',\n pdfborder={0 0 1}'
894             del document.header[i]
895         i = find_token(document.header, "\\pdf_colorlinks", i)
896         if i != -1:
897             colorlinks = get_value_string(document.header, '\\pdf_colorlinks', 0)
898             colorlinks = ',\n colorlinks=' + colorlinks
899             del document.header[i]
900         i = find_token(document.header, "\\pdf_backref", i)
901         if i != -1:
902             backref = get_value_string(document.header, '\\pdf_backref', 0)
903             backref = ',\n backref=' + backref
904             del document.header[i]
905         i = find_token(document.header, "\\pdf_pagebackref", i)
906         if i != -1:
907             pagebackref = get_value_string(document.header, '\\pdf_pagebackref', 0)
908             pagebackref = ',\n pagebackref=' + pagebackref
909             del document.header[i]
910         i = find_token(document.header, "\\pdf_pagemode", 0)
911         if i != -1:
912             pagemode = get_value_string(document.header, '\\pdf_pagemode', 0)
913             pagemode = ',\n pdfpagemode=' + pagemode
914             del document.header[i]
915         i = find_token(document.header, "\\pdf_quoted_options", 0)
916         if i != -1:
917             otheroptions = get_value_string(document.header, '\\pdf_quoted_options', 0, 0, True)
918             if title == "" and author == "" and subject == "" and keywords == "":
919                 otheroptions = ' ' + otheroptions
920             else:
921                 otheroptions = ',\n ' + otheroptions
922             del document.header[i]
923
924         # write to the preamble when hyperref was used
925         if hyperref == True:
926             # preamble write preparations
927             # bookmark numbers are only output when they are turned on
928             if bookmarksopen == ',\n bookmarksopen=true':
929                 bookmarksopen = bookmarksopen + bookmarksopenlevel
930             if bookmarks == ',\n bookmarks=true':
931                 bookmarks = bookmarks + bookmarksnumbered + bookmarksopen
932             else:
933                 bookmarks = bookmarks
934             # hypersetup is only output when there are things to be set up
935             setupstart = '\\hypersetup{%\n'
936             setupend = ' }\n'
937             if otheroptions == "" and title == "" and  author == ""\
938                and  subject == "" and keywords == "":
939                 setupstart = ""
940                 setupend = ""
941             # write the preamble
942             # babel must be loaded before hyperref and hyperref the first part
943             # of the preamble, like in LyX 1.6
944             insert_to_preamble(0, document,
945                                  '% Commands inserted by lyx2lyx for PDF properties\n'
946                                  + '\\usepackage{babel}\n'
947                                  + '\\usepackage[unicode=true'
948                                  + bookmarks
949                                  + breaklinks
950                                  + pdfborder
951                                  + backref
952                                  + pagebackref
953                                  + colorlinks
954                                  + pagemode
955                                  + ']\n'
956                                  + ' {hyperref}\n'
957                                  + setupstart
958                                  + title
959                                  + author
960                                  + subject
961                                  + keywords
962                                  + otheroptions
963                                  + setupend)
964
965
966 def remove_inzip_options(document):
967     "Remove inzipName and embed options from the Graphics inset"
968     i = 0
969     while 1:
970         i = find_token(document.body, "\\begin_inset Graphics", i)
971         if i == -1:
972             return
973         j = find_end_of_inset(document.body, i + 1)
974         if j == -1:
975             # should not happen
976             document.warning("Malformed LyX document: Could not find end of graphics inset.")
977             i += 1
978             continue
979         # If there's a inzip param, just remove that
980         k = find_token(document.body, "\tinzipName", i + 1, j)
981         if k != -1:
982             del document.body[k]
983             # embed option must follow the inzipName option
984             del document.body[k+1]
985         i = i + 1
986
987
988 def convert_inset_command(document):
989     """
990         Convert:
991             \begin_inset LatexCommand cmd
992         to
993             \begin_inset CommandInset InsetType
994             LatexCommand cmd
995     """
996     i = 0
997     while 1:
998         i = find_token(document.body, "\\begin_inset LatexCommand", i)
999         if i == -1:
1000             return
1001         line = document.body[i]
1002         r = re.compile(r'\\begin_inset LatexCommand (.*)$')
1003         m = r.match(line)
1004         cmdName = m.group(1)
1005         insetName = ""
1006         #this is adapted from factory.cpp
1007         if cmdName[0:4].lower() == "cite":
1008             insetName = "citation"
1009         elif cmdName == "url" or cmdName == "htmlurl":
1010             insetName = "url"
1011         elif cmdName[-3:] == "ref":
1012             insetName = "ref"
1013         elif cmdName == "tableofcontents":
1014             insetName = "toc"
1015         elif cmdName == "printnomenclature":
1016             insetName = "nomencl_print"
1017         elif cmdName == "printindex":
1018             insetName = "index_print"
1019         else:
1020             insetName = cmdName
1021         insertion = ["\\begin_inset CommandInset " + insetName, "LatexCommand " + cmdName]
1022         document.body[i : i+1] = insertion
1023
1024
1025 def revert_inset_command(document):
1026     """
1027         Convert:
1028             \begin_inset CommandInset InsetType
1029             LatexCommand cmd
1030         to
1031             \begin_inset LatexCommand cmd
1032         Some insets may end up being converted to insets earlier versions of LyX
1033         will not be able to recognize. Not sure what to do about that.
1034     """
1035     i = 0
1036     while 1:
1037         i = find_token(document.body, "\\begin_inset CommandInset", i)
1038         if i == -1:
1039             return
1040         nextline = document.body[i+1]
1041         r = re.compile(r'LatexCommand\s+(.*)$')
1042         m = r.match(nextline)
1043         if not m:
1044             document.warning("Malformed LyX document: Missing LatexCommand in " + document.body[i] + ".")
1045             i += 1
1046             continue
1047         cmdName = m.group(1)
1048         insertion = ["\\begin_inset LatexCommand " + cmdName]
1049         document.body[i : i+2] = insertion
1050
1051
1052 def convert_wrapfig_options(document):
1053     "Convert optional options for wrap floats (wrapfig)."
1054     # adds the tokens "lines", "placement", and "overhang"
1055     i = 0
1056     while True:
1057         i = find_token(document.body, "\\begin_inset Wrap figure", i)
1058         if i == -1:
1059             return
1060         document.body.insert(i + 1, "lines 0")
1061         j = find_token(document.body, "placement", i)
1062         # placement can be already set or not; if not, set it
1063         if j == i+2:
1064             document.body.insert(i + 3, "overhang 0col%")
1065         else:
1066            document.body.insert(i + 2, "placement o")
1067            document.body.insert(i + 3, "overhang 0col%")
1068         i = i + 1
1069
1070
1071 def revert_wrapfig_options(document):
1072     "Revert optional options for wrap floats (wrapfig)."
1073     i = 0
1074     while True:
1075         i = find_token(document.body, "\\begin_inset Wrap figure", i)
1076         if i == -1:
1077             return
1078         j = find_end_of_inset(document.body, i)
1079         if j == -1:
1080             document.warning("Can't find end of Wrap inset at line " + str(i))
1081             i += 1
1082             continue
1083         k = find_default_layout(document, i, j)
1084         if k == -1:
1085             document.warning("Can't find default layout for Wrap figure!")
1086             i = j
1087             continue
1088         # Options should be between i and k now
1089         l = find_token(document.body, "lines", i, k)
1090         if l == -1:
1091             document.warning("Can't find lines option for Wrap figure!")
1092             i = k
1093             continue
1094         m = find_token(document.body, "overhang", i + 1, k)
1095         if m == -1:
1096             document.warning("Malformed LyX document: Couldn't find overhang parameter of wrap float!")
1097             i = k
1098             continue
1099         # Do these in reverse order
1100         del document.body[m]
1101         del document.body[l]
1102         i = k
1103
1104
1105 def convert_latexcommand_index(document):
1106     "Convert from LatexCommand form to collapsable form."
1107     i = 0
1108     r1 = re.compile('name "(.*)"')
1109     while True:
1110         i = find_token(document.body, "\\begin_inset CommandInset index", i)
1111         if i == -1:
1112             return
1113         if document.body[i + 1] != "LatexCommand index": # Might also be index_print
1114             i += 1
1115             continue
1116         j = find_end_of_inset(document.body, i + 1)
1117         if j == -1:
1118             document.warning("Unable to find end of index inset at line " + str(i) + "!")
1119             i += 2
1120             continue
1121         m = r1.match(document.body[i + 2])
1122         if m == None:
1123             document.warning("Unable to match: " + document.body[i+2])
1124             # this can happen with empty index insets!
1125             linelist = [""]
1126         else:
1127             fullcontent = m.group(1)
1128             linelist = latex2lyx(fullcontent, True)
1129         #document.warning(fullcontent)
1130
1131         linelist = ["\\begin_inset Index", "status collapsed", "\\begin_layout Standard", ""] + \
1132                    linelist + ["\\end_layout"]
1133         document.body[i : j] = linelist
1134         i += len(linelist) - (j - i)
1135
1136
1137 def revert_latexcommand_index(document):
1138     "Revert from collapsable form to LatexCommand form."
1139     i = 0
1140     while True:
1141         i = find_token(document.body, "\\begin_inset Index", i)
1142         if i == -1:
1143           return
1144         j = find_end_of_inset(document.body, i + 1)
1145         if j == -1:
1146           return
1147
1148         content = lyx2latex(document, document.body[i:j])
1149         # escape quotes
1150         content = content.replace('"', r'\"')
1151         document.body[i:j] = ["\\begin_inset CommandInset index", "LatexCommand index",
1152             "name " + '"' + content + '"', ""]
1153         i += 5
1154
1155
1156 def revert_wraptable(document):
1157     "Revert wrap table to wrap figure."
1158     i = 0
1159     while True:
1160         i = find_token(document.body, "\\begin_inset Wrap table", i)
1161         if i == -1:
1162             return
1163         document.body[i] = document.body[i].replace('\\begin_inset Wrap table', '\\begin_inset Wrap figure')
1164         i = i + 1
1165
1166
1167 def revert_vietnamese(document):
1168     "Set language Vietnamese to English"
1169     # Set document language from Vietnamese to English
1170     i = 0
1171     if document.language == "vietnamese":
1172         document.language = "english"
1173         i = find_token(document.header, "\\language", 0)
1174         if i != -1:
1175             document.header[i] = "\\language english"
1176     j = 0
1177     while True:
1178         j = find_token(document.body, "\\lang vietnamese", j)
1179         if j == -1:
1180             return
1181         document.body[j] = document.body[j].replace("\\lang vietnamese", "\\lang english")
1182         j = j + 1
1183
1184
1185 def convert_japanese_cjk(document):
1186     "Set language japanese to japanese-cjk"
1187     # Set document language from japanese-plain to japanese
1188     i = 0
1189     if document.language == "japanese":
1190         document.language = "japanese-cjk"
1191         i = find_token(document.header, "\\language", 0)
1192         if i != -1:
1193             document.header[i] = "\\language japanese-cjk"
1194     j = 0
1195     while True:
1196         j = find_token(document.body, "\\lang japanese", j)
1197         if j == -1:
1198             return
1199         document.body[j] = document.body[j].replace("\\lang japanese", "\\lang japanese-cjk")
1200         j = j + 1
1201
1202
1203 def revert_japanese(document):
1204     "Set language japanese-plain to japanese"
1205     # Set document language from japanese-plain to japanese
1206     i = 0
1207     if document.language == "japanese-plain":
1208         document.language = "japanese"
1209         i = find_token(document.header, "\\language", 0)
1210         if i != -1:
1211             document.header[i] = "\\language japanese"
1212     j = 0
1213     while True:
1214         j = find_token(document.body, "\\lang japanese-plain", j)
1215         if j == -1:
1216             return
1217         document.body[j] = document.body[j].replace("\\lang japanese-plain", "\\lang japanese")
1218         j = j + 1
1219
1220
1221 def revert_japanese_cjk(document):
1222     "Set language japanese-cjk to japanese"
1223     # Set document language from japanese-plain to japanese
1224     i = 0
1225     if document.language == "japanese-cjk":
1226         document.language = "japanese"
1227         i = find_token(document.header, "\\language", 0)
1228         if i != -1:
1229             document.header[i] = "\\language japanese"
1230     j = 0
1231     while True:
1232         j = find_token(document.body, "\\lang japanese-cjk", j)
1233         if j == -1:
1234             return
1235         document.body[j] = document.body[j].replace("\\lang japanese-cjk", "\\lang japanese")
1236         j = j + 1
1237
1238
1239 def revert_japanese_encoding(document):
1240     "Set input encoding form EUC-JP-plain to EUC-JP etc."
1241     # Set input encoding form EUC-JP-plain to EUC-JP etc.
1242     i = 0
1243     i = find_token(document.header, "\\inputencoding EUC-JP-plain", 0)
1244     if i != -1:
1245         document.header[i] = "\\inputencoding EUC-JP"
1246     j = 0
1247     j = find_token(document.header, "\\inputencoding JIS-plain", 0)
1248     if j != -1:
1249         document.header[j] = "\\inputencoding JIS"
1250     k = 0
1251     k = find_token(document.header, "\\inputencoding SJIS-plain", 0)
1252     if k != -1: # convert to UTF8 since there is currently no SJIS encoding
1253         document.header[k] = "\\inputencoding UTF8"
1254
1255
1256 def revert_inset_info(document):
1257     'Replace info inset with its content'
1258     i = 0
1259     while 1:
1260         i = find_token(document.body, '\\begin_inset Info', i)
1261         if i == -1:
1262             return
1263         j = find_end_of_inset(document.body, i + 1)
1264         if j == -1:
1265             # should not happen
1266             document.warning("Malformed LyX document: Could not find end of Info inset.")
1267             i += 1
1268             continue
1269         type = 'unknown'
1270         arg = ''
1271         for k in range(i, j+1):
1272             if document.body[k].startswith("arg"):
1273                 arg = document.body[k][3:].strip()
1274                 # remove embracing quotation marks
1275                 if arg[0] == '"':
1276                     arg = arg[1:]
1277                 if arg[len(arg) - 1] == '"':
1278                     arg = arg[:len(arg) - 1]
1279                 # \" to straight quote
1280                 arg = arg.replace(r'\"', '"')
1281                 # \ to \backslash
1282                 arg = arg.replace(r'\\', "\\backslash\n")
1283             if document.body[k].startswith("type"):
1284                 type = document.body[k][4:].strip().strip('"')
1285         # I think there is a newline after \\end_inset, which should be removed.
1286         if document.body[j + 1].strip() == "":
1287             document.body[i : (j + 2)] = [type + ':' + arg]
1288         else:
1289             document.body[i : (j + 1)] = [type + ':' + arg]
1290
1291
1292 def convert_pdf_options(document):
1293     # Set the pdfusetitle tag, delete the pdf_store_options,
1294     # set quotes for bookmarksopenlevel"
1295     has_hr = get_value(document.header, "\\use_hyperref", 0, default = "0")
1296     if has_hr == "1":
1297         k = find_token(document.header, "\\use_hyperref", 0)
1298         document.header.insert(k + 1, "\\pdf_pdfusetitle true")
1299     k = find_token(document.header, "\\pdf_store_options", 0)
1300     if k != -1:
1301         del document.header[k]
1302     i = find_token(document.header, "\\pdf_bookmarksopenlevel", k)
1303     if i == -1: return
1304     document.header[i] = document.header[i].replace('"', '')
1305
1306
1307 def revert_pdf_options_2(document):
1308     # reset the pdfusetitle tag, set quotes for bookmarksopenlevel"
1309     k = find_token(document.header, "\\use_hyperref", 0)
1310     i = find_token(document.header, "\\pdf_pdfusetitle", k)
1311     if i != -1:
1312         del document.header[i]
1313     i = find_token(document.header, "\\pdf_bookmarksopenlevel", k)
1314     if i == -1: return
1315     values = document.header[i].split()
1316     values[1] = ' "' + values[1] + '"'
1317     document.header[i] = ''.join(values)
1318
1319
1320 def convert_htmlurl(document):
1321     'Convert "htmlurl" to "href" insets for docbook'
1322     if document.backend != "docbook":
1323       return
1324     i = 0
1325     while True:
1326       i = find_token(document.body, "\\begin_inset CommandInset url", i)
1327       if i == -1:
1328         return
1329       document.body[i] = "\\begin_inset CommandInset href"
1330       document.body[i + 1] = "LatexCommand href"
1331       i = i + 1
1332
1333
1334 def convert_url(document):
1335     'Convert url insets to url charstyles'
1336     if document.backend == "docbook":
1337       return
1338     i = 0
1339     while True:
1340       i = find_token(document.body, "\\begin_inset CommandInset url", i)
1341       if i == -1:
1342         break
1343       n = find_token(document.body, "name", i)
1344       if n == i + 2:
1345         # place the URL name in typewriter before the new URL insert
1346         # grab the name 'bla' from the e.g. the line 'name "bla"',
1347         # therefore start with the 6th character
1348         name = document.body[n][6:-1]
1349         newname = [name + " "]
1350         document.body[i:i] = newname
1351         i = i + 1
1352       j = find_token(document.body, "target", i)
1353       if j == -1:
1354         document.warning("Malformed LyX document: Can't find target for url inset")
1355         i += 1
1356         continue
1357       target = document.body[j][8:-1]
1358       k = find_token(document.body, "\\end_inset", j)
1359       if k == -1:
1360         document.warning("Malformed LyX document: Can't find end of url inset")
1361         i = j
1362         continue
1363       newstuff = ["\\begin_inset Flex URL",
1364         "status collapsed", "",
1365         "\\begin_layout Standard",
1366         "",
1367         target,
1368         "\\end_layout",
1369         ""]
1370       document.body[i:k] = newstuff
1371       i = i + len(newstuff)
1372
1373 def convert_ams_classes(document):
1374   tc = document.textclass
1375   if (tc != "amsart" and tc != "amsart-plain" and
1376       tc != "amsart-seq" and tc != "amsbook"):
1377     return
1378   if tc == "amsart-plain":
1379     document.textclass = "amsart"
1380     document.set_textclass()
1381     document.add_module("Theorems (Starred)")
1382     return
1383   if tc == "amsart-seq":
1384     document.textclass = "amsart"
1385     document.set_textclass()
1386   document.add_module("Theorems (AMS)")
1387
1388   #Now we want to see if any of the environments in the extended theorems
1389   #module were used in this document. If so, we'll add that module, too.
1390   layouts = ["Criterion", "Algorithm", "Axiom", "Condition", "Note",  \
1391     "Notation", "Summary", "Acknowledgement", "Conclusion", "Fact", \
1392     "Assumption"]
1393
1394   r = re.compile(r'^\\begin_layout (.*?)\*?\s*$')
1395   i = 0
1396   while True:
1397     i = find_token(document.body, "\\begin_layout", i)
1398     if i == -1:
1399       return
1400     m = r.match(document.body[i])
1401     if m == None:
1402       # This is an empty layout
1403       # document.warning("Weirdly formed \\begin_layout at line %d of body!" % i)
1404       i += 1
1405       continue
1406     m = m.group(1)
1407     if layouts.count(m) != 0:
1408       document.add_module("Theorems (AMS-Extended)")
1409       return
1410     i += 1
1411
1412 def revert_href(document):
1413     'Reverts hyperlink insets (href) to url insets (url)'
1414     i = 0
1415     while True:
1416       i = find_token(document.body, "\\begin_inset CommandInset href", i)
1417       if i == -1:
1418           return
1419       document.body[i : i + 2] = \
1420         ["\\begin_inset CommandInset url", "LatexCommand url"]
1421       i = i + 2
1422
1423 def revert_url(document):
1424     'Reverts Flex URL insets to old-style URL insets'
1425     i = 0
1426     while True:
1427         i = find_token(document.body, "\\begin_inset Flex URL", i)
1428         if i == -1:
1429             return
1430         j = find_end_of_inset(document.body, i)
1431         if j == -1:
1432             document.warning("Can't find end of inset in revert_url!")
1433             return
1434         k = find_default_layout(document, i, j)
1435         if k == -1:
1436             document.warning("Can't find default layout in revert_url!")
1437             i = j
1438             continue
1439         l = find_end_of(document.body, k, "\\begin_layout", "\\end_layout")
1440         if l == -1 or l >= j:
1441             document.warning("Can't find end of default layout in revert_url!")
1442             i = j
1443             continue
1444         # OK, so the inset's data is between lines k and l.
1445         data =  " ".join(document.body[k+1:l])
1446         data = data.strip()
1447         newinset = ["\\begin_inset LatexCommand url", "target \"" + data + "\"",\
1448                     "", "\\end_inset"]
1449         document.body[i:j+1] = newinset
1450         i = i + len(newinset)
1451
1452
1453 def convert_include(document):
1454   'Converts include insets to new format.'
1455   i = 0
1456   r = re.compile(r'\\begin_inset Include\s+\\([^{]+){([^}]*)}(?:\[(.*)\])?')
1457   while True:
1458     i = find_token(document.body, "\\begin_inset Include", i)
1459     if i == -1:
1460       return
1461     line = document.body[i]
1462     previewline = document.body[i + 1]
1463     m = r.match(line)
1464     if m == None:
1465       document.warning("Unable to match line " + str(i) + " of body!")
1466       i += 1
1467       continue
1468     cmd = m.group(1)
1469     fn  = m.group(2)
1470     opt = m.group(3)
1471     insertion = ["\\begin_inset CommandInset include",
1472        "LatexCommand " + cmd, previewline,
1473        "filename \"" + fn + "\""]
1474     newlines = 2
1475     if opt:
1476       insertion.append("lstparams " + '"' + opt + '"')
1477       newlines += 1
1478     document.body[i : i + 2] = insertion
1479     i += newlines
1480
1481
1482 def revert_include(document):
1483   'Reverts include insets to old format.'
1484   i = 0
1485   r0 = re.compile('preview.*')
1486   r1 = re.compile('LatexCommand (.+)')
1487   r2 = re.compile('filename "(.+)"')
1488   r3 = re.compile('lstparams "(.*)"')
1489   while True:
1490     i = find_token(document.body, "\\begin_inset CommandInset include", i)
1491     if i == -1:
1492       return
1493     nextline = i + 1
1494     m = r1.match(document.body[nextline])
1495     if m == None:
1496       document.warning("Malformed LyX document: No LatexCommand line for `" +
1497         document.body[i] + "' on line " + str(i) + ".")
1498       i += 1
1499       continue
1500     cmd = m.group(1)
1501     nextline += 1
1502     if r0.match(document.body[nextline]):
1503       previewline = document.body[nextline]
1504       nextline += 1
1505     else:
1506       previewline = ""
1507     m = r2.match(document.body[nextline])
1508     if m == None:
1509       document.warning("Malformed LyX document: No filename line for `" + \
1510         document.body[i] + "' on line " + str(i) + ".")
1511       i += 2
1512       continue
1513     fn = m.group(1)
1514     nextline += 1
1515     options = ""
1516     if (cmd == "lstinputlisting"):
1517       m = r3.match(document.body[nextline])
1518       if m != None:
1519         options = m.group(1)
1520         numlines = 5
1521         nextline += 1
1522     newline = "\\begin_inset Include \\" + cmd + "{" + fn + "}"
1523     if options:
1524       newline += ("[" + options + "]")
1525     insertion = [newline]
1526     if previewline != "":
1527       insertion.append(previewline)
1528     document.body[i : nextline] = insertion
1529     i += 2
1530
1531
1532 def revert_albanian(document):
1533     "Set language Albanian to English"
1534     i = 0
1535     if document.language == "albanian":
1536         document.language = "english"
1537         i = find_token(document.header, "\\language", 0)
1538         if i != -1:
1539             document.header[i] = "\\language english"
1540     j = 0
1541     while True:
1542         j = find_token(document.body, "\\lang albanian", j)
1543         if j == -1:
1544             return
1545         document.body[j] = document.body[j].replace("\\lang albanian", "\\lang english")
1546         j = j + 1
1547
1548
1549 def revert_lowersorbian(document):
1550     "Set language lower Sorbian to English"
1551     i = 0
1552     if document.language == "lowersorbian":
1553         document.language = "english"
1554         i = find_token(document.header, "\\language", 0)
1555         if i != -1:
1556             document.header[i] = "\\language english"
1557     j = 0
1558     while True:
1559         j = find_token(document.body, "\\lang lowersorbian", j)
1560         if j == -1:
1561             return
1562         document.body[j] = document.body[j].replace("\\lang lowersorbian", "\\lang english")
1563         j = j + 1
1564
1565
1566 def revert_uppersorbian(document):
1567     "Set language uppersorbian to usorbian as this was used in LyX 1.5"
1568     i = 0
1569     if document.language == "uppersorbian":
1570         document.language = "usorbian"
1571         i = find_token(document.header, "\\language", 0)
1572         if i != -1:
1573             document.header[i] = "\\language usorbian"
1574     j = 0
1575     while True:
1576         j = find_token(document.body, "\\lang uppersorbian", j)
1577         if j == -1:
1578             return
1579         document.body[j] = document.body[j].replace("\\lang uppersorbian", "\\lang usorbian")
1580         j = j + 1
1581
1582
1583 def convert_usorbian(document):
1584     "Set language usorbian to uppersorbian"
1585     i = 0
1586     if document.language == "usorbian":
1587         document.language = "uppersorbian"
1588         i = find_token(document.header, "\\language", 0)
1589         if i != -1:
1590             document.header[i] = "\\language uppersorbian"
1591     j = 0
1592     while True:
1593         j = find_token(document.body, "\\lang usorbian", j)
1594         if j == -1:
1595             return
1596         document.body[j] = document.body[j].replace("\\lang usorbian", "\\lang uppersorbian")
1597         j = j + 1
1598
1599
1600 def convert_macro_global(document):
1601     "Remove TeX code command \global when it is in front of a macro"
1602     # math macros are nowadays already defined \global, so that an additional
1603     # \global would make the document uncompilable, see
1604     # http://www.lyx.org/trac/ticket/5371
1605     # We're looking for something like this:
1606     # \begin_inset ERT
1607     # status collapsed
1608     #
1609     # \begin_layout Plain Layout
1610     #
1611     #
1612     # \backslash
1613     # global
1614     # \end_layout
1615     #
1616     # \end_inset
1617     #
1618     #
1619     # \begin_inset FormulaMacro
1620     # \renewcommand{\foo}{123}
1621     # \end_inset
1622     i = 0
1623     while True:
1624         i = find_token(document.body, "\\begin_inset FormulaMacro", i)
1625         if i == -1:
1626             return
1627         # if i <= 13, then there isn't enough room for the ERT
1628         if i <= 12:
1629             i += 1
1630             continue
1631         if document.body[i-6] == "global":
1632             del document.body[i-13 : i]
1633             i = i - 12
1634         else:
1635             i += 1
1636
1637
1638 def revert_macro_optional_params(document):
1639     "Convert macro definitions with optional parameters into ERTs"
1640     # Stub to convert macro definitions with one or more optional parameters
1641     # into uninterpreted ERT insets
1642
1643
1644 def revert_hyperlinktype(document):
1645     'Reverts hyperlink type'
1646     i = 0
1647     j = 0
1648     while True:
1649       i = find_token(document.body, "target", i)
1650       if i == -1:
1651           return
1652       j = find_token(document.body, "type", i)
1653       if j == -1:
1654           return
1655       if j == i + 1:
1656           del document.body[j]
1657       i = i + 1
1658
1659
1660 def revert_pagebreak(document):
1661     'Reverts pagebreak to ERT'
1662     i = 0
1663     while True:
1664       i = find_token(document.body, "\\pagebreak", i)
1665       if i == -1:
1666           return
1667       document.body[i] = '\\begin_inset ERT\nstatus collapsed\n\n' \
1668       '\\begin_layout Standard\n\n\n\\backslash\n' \
1669       'pagebreak{}\n\\end_layout\n\n\\end_inset\n\n'
1670       i = i + 1
1671
1672
1673 def revert_linebreak(document):
1674     'Reverts linebreak to ERT'
1675     i = 0
1676     while True:
1677       i = find_token(document.body, "\\linebreak", i)
1678       if i == -1:
1679           return
1680       document.body[i] = '\\begin_inset ERT\nstatus collapsed\n\n' \
1681       '\\begin_layout Standard\n\n\n\\backslash\n' \
1682       'linebreak{}\n\\end_layout\n\n\\end_inset\n\n'
1683       i = i + 1
1684
1685
1686 def revert_latin(document):
1687     "Set language Latin to English"
1688     i = 0
1689     if document.language == "latin":
1690         document.language = "english"
1691         i = find_token(document.header, "\\language", 0)
1692         if i != -1:
1693             document.header[i] = "\\language english"
1694     j = 0
1695     while True:
1696         j = find_token(document.body, "\\lang latin", j)
1697         if j == -1:
1698             return
1699         document.body[j] = document.body[j].replace("\\lang latin", "\\lang english")
1700         j = j + 1
1701
1702
1703 def revert_samin(document):
1704     "Set language North Sami to English"
1705     i = 0
1706     if document.language == "samin":
1707         document.language = "english"
1708         i = find_token(document.header, "\\language", 0)
1709         if i != -1:
1710             document.header[i] = "\\language english"
1711     j = 0
1712     while True:
1713         j = find_token(document.body, "\\lang samin", j)
1714         if j == -1:
1715             return
1716         document.body[j] = document.body[j].replace("\\lang samin", "\\lang english")
1717         j = j + 1
1718
1719
1720 def convert_serbocroatian(document):
1721     "Set language Serbocroatian to Croatian as this was really Croatian in LyX 1.5"
1722     i = 0
1723     if document.language == "serbocroatian":
1724         document.language = "croatian"
1725         i = find_token(document.header, "\\language", 0)
1726         if i != -1:
1727             document.header[i] = "\\language croatian"
1728     j = 0
1729     while True:
1730         j = find_token(document.body, "\\lang serbocroatian", j)
1731         if j == -1:
1732             return
1733         document.body[j] = document.body[j].replace("\\lang serbocroatian", "\\lang croatian")
1734         j = j + 1
1735
1736
1737 def convert_framed_notes(document):
1738     "Convert framed notes to boxes. "
1739     i = 0
1740     while 1:
1741         i = find_tokens(document.body, ["\\begin_inset Note Framed", "\\begin_inset Note Shaded"], i)
1742         if i == -1:
1743             return
1744         subst = [document.body[i].replace("\\begin_inset Note", "\\begin_inset Box"),
1745                  'position "t"',
1746                  'hor_pos "c"',
1747                  'has_inner_box 0',
1748                  'inner_pos "t"',
1749                  'use_parbox 0',
1750                  'width "100col%"',
1751                  'special "none"',
1752                  'height "1in"',
1753                  'height_special "totalheight"']
1754         document.body[i:i+1] = subst
1755         i = i + 9
1756
1757
1758 def convert_module_names(document):
1759   modulemap = { 'Braille' : 'braille', 'Endnote' : 'endnotes', 'Foot to End' : 'foottoend',\
1760     'Hanging' : 'hanging', 'Linguistics' : 'linguistics', 'Logical Markup' : 'logicalmkup', \
1761     'Theorems (AMS-Extended)' : 'theorems-ams-extended', 'Theorems (AMS)' : 'theorems-ams', \
1762     'Theorems (Order By Chapter)' : 'theorems-chap', 'Theorems (Order By Section)' : 'theorems-sec', \
1763     'Theorems (Starred)' : 'theorems-starred', 'Theorems' : 'theorems-std' }
1764   modlist = document.get_module_list()
1765   if len(modlist) == 0:
1766     return
1767   newmodlist = []
1768   for mod in modlist:
1769     if mod in modulemap:
1770       newmodlist.append(modulemap[mod])
1771     else:
1772       document.warning("Can't find module %s in the module map!" % mod)
1773       newmodlist.append(mod)
1774   document.set_module_list(newmodlist)
1775
1776
1777 def revert_module_names(document):
1778   modulemap = { 'braille' : 'Braille', 'endnotes' : 'Endnote', 'foottoend' : 'Foot to End',\
1779     'hanging' : 'Hanging', 'linguistics' : 'Linguistics', 'logicalmkup' : 'Logical Markup', \
1780     'theorems-ams-extended' : 'Theorems (AMS-Extended)', 'theorems-ams' : 'Theorems (AMS)', \
1781     'theorems-chap' : 'Theorems (Order By Chapter)', 'theorems-sec' : 'Theorems (Order By Section)', \
1782     'theorems-starred' : 'Theorems (Starred)', 'theorems-std' : 'Theorems'}
1783   modlist = document.get_module_list()
1784   if len(modlist) == 0:
1785     return
1786   newmodlist = []
1787   for mod in modlist:
1788     if mod in modulemap:
1789       newmodlist.append(modulemap[mod])
1790     else:
1791       document.warning("Can't find module %s in the module map!" % mod)
1792       newmodlist.append(mod)
1793   document.set_module_list(newmodlist)
1794
1795
1796 def revert_colsep(document):
1797     i = find_token(document.header, "\\columnsep", 0)
1798     if i == -1:
1799         return
1800     colsepline = document.header[i]
1801     r = re.compile(r'\\columnsep (.*)')
1802     m = r.match(colsepline)
1803     if not m:
1804         document.warning("Malformed column separation line!")
1805         return
1806     colsep = m.group(1)
1807     del document.header[i]
1808     #it seems to be safe to add the package even if it is already used
1809     pretext = ["\\usepackage{geometry}", "\\geometry{columnsep=" + colsep + "}"]
1810
1811     add_to_preamble(document, pretext)
1812
1813
1814 def revert_framed_notes(document):
1815     "Revert framed boxes to notes. "
1816     i = 0
1817     while 1:
1818         i = find_tokens(document.body, ["\\begin_inset Box Framed", "\\begin_inset Box Shaded"], i)
1819
1820         if i == -1:
1821             return
1822         j = find_end_of_inset(document.body, i + 1)
1823         if j == -1:
1824             # should not happen
1825             document.warning("Malformed LyX document: Could not find end of Box inset.")
1826             i += 1
1827             continue
1828         k = find_token(document.body, "status", i + 1, j)
1829         if k == -1:
1830             document.warning("Malformed LyX document: Missing `status' tag in Box inset.")
1831             i = j
1832             continue
1833         status = document.body[k]
1834         l = find_default_layout(document, i + 1, j)
1835         if l == -1:
1836             document.warning("Malformed LyX document: Missing `\\begin_layout' in Box inset.")
1837             i = j
1838             continue
1839         m = find_token(document.body, "\\end_layout", i + 1, j)
1840         if m == -1:
1841             document.warning("Malformed LyX document: Missing `\\end_layout' in Box inset.")
1842             i = j
1843             continue
1844         ibox = find_token(document.body, "has_inner_box 1", i + 1, k)
1845         pbox = find_token(document.body, "use_parbox 1", i + 1, k)
1846         if ibox == -1 and pbox == -1:
1847             document.body[i] = document.body[i].replace("\\begin_inset Box", "\\begin_inset Note")
1848             del document.body[i+1:k]
1849         else:
1850             document.body[i] = document.body[i].replace("\\begin_inset Box Shaded", "\\begin_inset Box Frameless")
1851             subst1 = [document.body[l],
1852                       "\\begin_inset Note Shaded",
1853                       status,
1854                       '\\begin_layout Standard']
1855             document.body[l:l + 1] = subst1
1856             subst2 = [document.body[m], "\\end_layout", "\\end_inset"]
1857             document.body[m:m + 1] = subst2
1858         i = i + 1
1859
1860
1861 def revert_slash(document):
1862     'Revert \\SpecialChar \\slash{} to ERT'
1863     i = 0
1864     while i < len(document.body):
1865         m = re.match(r'(.*)\\SpecialChar \\slash{}(.*)', document.body[i])
1866         if m:
1867             before = m.group(1)
1868             after = m.group(2)
1869             subst = [before,
1870                      '\\begin_inset ERT',
1871                      'status collapsed', '',
1872                      '\\begin_layout Standard',
1873                      '', '', '\\backslash',
1874                      'slash{}',
1875                      '\\end_layout', '',
1876                      '\\end_inset', '',
1877                      after]
1878             document.body[i: i+1] = subst
1879             i = i + len(subst)
1880         else:
1881             i = i + 1
1882
1883
1884 def revert_nobreakdash(document):
1885     'Revert \\SpecialChar \\nobreakdash- to ERT'
1886     i = 0
1887     while i < len(document.body):
1888         m = re.match(r'(.*)\\SpecialChar \\nobreakdash-(.*)', document.body[i])
1889         if m:
1890             before = m.group(1)
1891             after = m.group(2)
1892             subst = [before,
1893                      '\\begin_inset ERT',
1894                     'status collapsed', '',
1895                     '\\begin_layout Standard', '', '',
1896                     '\\backslash',
1897                     'nobreakdash-',
1898                     '\\end_layout', '',
1899                     '\\end_inset', '',
1900                      after]
1901             document.body[i: i+1] = subst
1902             i = i + len(subst)
1903             j = find_token(document.header, "\\use_amsmath", 0)
1904             if j == -1:
1905                 document.warning("Malformed LyX document: Missing '\\use_amsmath'.")
1906                 i += 1
1907                 continue
1908             document.header[j] = "\\use_amsmath 2"
1909         else:
1910             i = i + 1
1911
1912
1913 #Returns number of lines added/removed
1914 def revert_nocite_key(body, start, end):
1915     'key "..." -> \nocite{...}'
1916     r = re.compile(r'^key "(.*)"')
1917     i = start
1918     j = end
1919     while i < j:
1920         m = r.match(body[i])
1921         if m:
1922             body[i:i+1] = ["\\backslash", "nocite{" + m.group(1) + "}"]
1923             j += 1     # because we added a line
1924             i += 2     # skip that line
1925         else:
1926             del body[i]
1927             j -= 1     # because we deleted a line
1928             # no need to change i, since it now points to the next line
1929     return j - end
1930
1931
1932 def revert_nocite(document):
1933     "Revert LatexCommand nocite to ERT"
1934     i = 0
1935     while 1:
1936         i = find_token(document.body, "\\begin_inset CommandInset citation", i)
1937         if i == -1:
1938             return
1939         if (document.body[i+1] != "LatexCommand nocite"):
1940             # note that we already incremented i
1941             i = i + 1
1942             continue
1943         insetEnd = find_end_of_inset(document.body, i)
1944         if insetEnd == -1:
1945             #this should not happen
1946             document.warning("End of CommandInset citation not found in revert_nocite!")
1947             return
1948
1949         paramLocation = i + 2 #start of the inset's parameters
1950         addedLines = 0
1951         document.body[i:i+2] = \
1952             ["\\begin_inset ERT", "status collapsed", "", "\\begin_layout Standard"]
1953         # that added two lines
1954         paramLocation += 2
1955         insetEnd += 2
1956         #print insetEnd, document.body[i: insetEnd + 1]
1957         insetEnd += revert_nocite_key(document.body, paramLocation, insetEnd)
1958         #print insetEnd, document.body[i: insetEnd + 1]
1959         document.body.insert(insetEnd, "\\end_layout")
1960         document.body.insert(insetEnd + 1, "")
1961         i = insetEnd + 1
1962
1963
1964 def revert_btprintall(document):
1965     "Revert (non-bibtopic) btPrintAll option to ERT \nocite{*}"
1966     i = find_token(document.header, '\\use_bibtopic', 0)
1967     if i == -1:
1968         document.warning("Malformed lyx document: Missing '\\use_bibtopic'.")
1969         return
1970     if get_value(document.header, '\\use_bibtopic', 0) == "false":
1971         i = 0
1972         while i < len(document.body):
1973             i = find_token(document.body, "\\begin_inset CommandInset bibtex", i)
1974             if i == -1:
1975                 return
1976             j = find_end_of_inset(document.body, i + 1)
1977             if j == -1:
1978                 #this should not happen
1979                 document.warning("End of CommandInset bibtex not found in revert_btprintall!")
1980                 j = len(document.body)
1981             # this range isn't really right, but it should be OK, since we shouldn't
1982             # see more than one matching line in each inset
1983             addedlines = 0
1984             for k in range(i, j):
1985                 if (document.body[k] == 'btprint "btPrintAll"'):
1986                     del document.body[k]
1987                     subst = ["\\begin_inset ERT",
1988                              "status collapsed", "",
1989                              "\\begin_layout Standard", "",
1990                              "\\backslash",
1991                              "nocite{*}",
1992                              "\\end_layout",
1993                              "\\end_inset"]
1994                     document.body[i:i] = subst
1995                     addlines = addedlines + len(subst) - 1
1996             i = j + addedlines
1997
1998
1999 def revert_bahasam(document):
2000     "Set language Bahasa Malaysia to Bahasa Indonesia"
2001     i = 0
2002     if document.language == "bahasam":
2003         document.language = "bahasa"
2004         i = find_token(document.header, "\\language", 0)
2005         if i != -1:
2006             document.header[i] = "\\language bahasa"
2007     j = 0
2008     while True:
2009         j = find_token(document.body, "\\lang bahasam", j)
2010         if j == -1:
2011             return
2012         document.body[j] = document.body[j].replace("\\lang bahasam", "\\lang bahasa")
2013         j = j + 1
2014
2015
2016 def revert_interlingua(document):
2017     "Set language Interlingua to English"
2018     i = 0
2019     if document.language == "interlingua":
2020         document.language = "english"
2021         i = find_token(document.header, "\\language", 0)
2022         if i != -1:
2023             document.header[i] = "\\language english"
2024     j = 0
2025     while True:
2026         j = find_token(document.body, "\\lang interlingua", j)
2027         if j == -1:
2028             return
2029         document.body[j] = document.body[j].replace("\\lang interlingua", "\\lang english")
2030         j = j + 1
2031
2032
2033 def revert_serbianlatin(document):
2034     "Set language Serbian-Latin to Croatian"
2035     i = 0
2036     if document.language == "serbian-latin":
2037         document.language = "croatian"
2038         i = find_token(document.header, "\\language", 0)
2039         if i != -1:
2040             document.header[i] = "\\language croatian"
2041     j = 0
2042     while True:
2043         j = find_token(document.body, "\\lang serbian-latin", j)
2044         if j == -1:
2045             return
2046         document.body[j] = document.body[j].replace("\\lang serbian-latin", "\\lang croatian")
2047         j = j + 1
2048
2049
2050 def revert_rotfloat(document):
2051     " Revert sideways custom floats. "
2052     i = 0
2053     while 1:
2054         # whitespace intended (exclude \\begin_inset FloatList)
2055         i = find_token(document.body, "\\begin_inset Float ", i)
2056         if i == -1:
2057             return
2058         line = document.body[i]
2059         r = re.compile(r'\\begin_inset Float (.*)$')
2060         m = r.match(line)
2061         if m == None:
2062             document.warning("Unable to match line " + str(i) + " of body!")
2063             i += 1
2064             continue
2065         floattype = m.group(1)
2066         if floattype == "figure" or floattype == "table":
2067             i += 1
2068             continue
2069         j = find_end_of_inset(document.body, i)
2070         if j == -1:
2071             document.warning("Malformed lyx document: Missing '\\end_inset' in revert_rotfloat.")
2072             i += 1
2073             continue
2074         addedLines = 0
2075         if get_value(document.body, 'sideways', i, j) == "false":
2076             i += 1
2077             continue
2078         l = find_default_layout(document, i + 1, j)
2079         if l == -1:
2080             document.warning("Malformed LyX document: Missing `\\begin_layout' in Float inset.")
2081             i = j
2082             continue
2083         subst = ['\\begin_layout Standard',
2084                   '\\begin_inset ERT',
2085                   'status collapsed', '',
2086                   '\\begin_layout Standard', '', '',
2087                   '\\backslash', '',
2088                   'end{sideways' + floattype + '}',
2089                   '\\end_layout', '', '\\end_inset']
2090         document.body[j : j+1] = subst
2091         addedLines = len(subst) - 1
2092         del document.body[i+1 : l]
2093         addedLines -= (l-1) - (i+1)
2094         subst = ['\\begin_inset ERT', 'status collapsed', '',
2095                   '\\begin_layout Standard', '', '', '\\backslash',
2096                   'begin{sideways' + floattype + '}',
2097                   '\\end_layout', '', '\\end_inset', '',
2098                   '\\end_layout', '']
2099         document.body[i : i+1] = subst
2100         addedLines += len(subst) - 1
2101         if floattype == "algorithm":
2102             add_to_preamble(document,
2103                             ['% Commands inserted by lyx2lyx for sideways algorithm float',
2104                               '\\usepackage{rotfloat}',
2105                               '\\floatstyle{ruled}',
2106                               '\\newfloat{algorithm}{tbp}{loa}',
2107                               '\\floatname{algorithm}{Algorithm}'])
2108         else:
2109             document.warning("Cannot create preamble definition for custom float" + floattype + ".")
2110         i += addedLines + 1
2111
2112
2113 def revert_widesideways(document):
2114     " Revert wide sideways floats. "
2115     i = 0
2116     while 1:
2117         # whitespace intended (exclude \\begin_inset FloatList)
2118         i = find_token(document.body, '\\begin_inset Float ', i)
2119         if i == -1:
2120             return
2121         line = document.body[i]
2122         r = re.compile(r'\\begin_inset Float (.*)$')
2123         m = r.match(line)
2124         if m == None:
2125             document.warning("Unable to match line " + str(i) + " of body!")
2126             i += 1
2127             continue
2128         floattype = m.group(1)
2129         if floattype != "figure" and floattype != "table":
2130             i += 1
2131             continue
2132         j = find_end_of_inset(document.body, i)
2133         if j == -1:
2134             document.warning("Malformed lyx document: Missing '\\end_inset' in revert_widesideways.")
2135             i += 1
2136             continue
2137         if get_value(document.body, 'sideways', i, j) == "false" or \
2138            get_value(document.body, 'wide', i, j) == "false":
2139              i += 1
2140              continue
2141         l = find_default_layout(document, i + 1, j)
2142         if l == -1:
2143             document.warning("Malformed LyX document: Missing `\\begin_layout' in Float inset.")
2144             i = j
2145             continue
2146         subst = ['\\begin_layout Standard', '\\begin_inset ERT',
2147                   'status collapsed', '',
2148                   '\\begin_layout Standard', '', '', '\\backslash',
2149                   'end{sideways' + floattype + '*}',
2150                   '\\end_layout', '', '\\end_inset']
2151         document.body[j : j+1] = subst
2152         addedLines = len(subst) - 1
2153         del document.body[i+1:l-1]
2154         addedLines -= (l-1) - (i+1)
2155         subst = ['\\begin_inset ERT', 'status collapsed', '',
2156                  '\\begin_layout Standard', '', '', '\\backslash',
2157                  'begin{sideways' + floattype + '*}', '\\end_layout', '',
2158                  '\\end_inset', '', '\\end_layout', '']
2159         document.body[i : i+1] = subst
2160         addedLines += len(subst) - 1
2161         add_to_preamble(document, ['\\usepackage{rotfloat}\n'])
2162         i += addedLines + 1
2163
2164
2165 def revert_inset_embedding(document, type):
2166     ' Remove embed tag from certain type of insets'
2167     i = 0
2168     while 1:
2169         i = find_token(document.body, "\\begin_inset %s" % type, i)
2170         if i == -1:
2171             return
2172         j = find_end_of_inset(document.body, i)
2173         if j == -1:
2174             document.warning("Malformed lyx document: Missing '\\end_inset' in revert_inset_embedding.")
2175             i = i + 1
2176             continue
2177         k = find_token(document.body, "\tembed", i, j)
2178         if k == -1:
2179             k = find_token(document.body, "embed", i, j)
2180         if k != -1:
2181             del document.body[k]
2182         i = i + 1
2183
2184
2185 def revert_external_embedding(document):
2186     ' Remove embed tag from external inset '
2187     revert_inset_embedding(document, 'External')
2188
2189
2190 def convert_subfig(document):
2191     " Convert subfigures to subfloats. "
2192     i = 0
2193     while 1:
2194         addedLines = 0
2195         i = find_token(document.body, '\\begin_inset Graphics', i)
2196         if i == -1:
2197             return
2198         endInset = find_end_of_inset(document.body, i)
2199         if endInset == -1:
2200             document.warning("Malformed lyx document: Missing '\\end_inset' in convert_subfig.")
2201             i += 1
2202             continue
2203         k = find_token(document.body, '\tsubcaption', i, endInset)
2204         if k == -1:
2205             i = endInset
2206             continue
2207         l = find_token(document.body, '\tsubcaptionText', i, endInset)
2208         if l == -1:
2209             caption = ""
2210         else:
2211             caption = document.body[l][16:].strip('"')
2212             del document.body[l]
2213             addedLines -= 1
2214         del document.body[k]
2215         addedLines -= 1
2216         subst = ['\\begin_inset Float figure', 'wide false', 'sideways false',
2217                  'status open', '', '\\begin_layout Plain Layout', '\\begin_inset Caption',
2218                  '', '\\begin_layout Plain Layout'] + latex2lyx(caption, False) + \
2219                  [ '\\end_layout', '', '\\end_inset', '',
2220                  '\\end_layout', '', '\\begin_layout Plain Layout']
2221         document.body[i : i] = subst
2222         addedLines += len(subst)
2223         endInset += addedLines
2224         subst = ['', '\\end_inset', '', '\\end_layout']
2225         document.body[endInset : endInset] = subst
2226         addedLines += len(subst)
2227         i += addedLines + 1
2228
2229
2230 def revert_subfig(document):
2231     " Revert subfloats. "
2232     i = 0
2233     while 1:
2234         # whitespace intended (exclude \\begin_inset FloatList)
2235         i = find_tokens(document.body, ['\\begin_inset Float ', '\\begin_inset Wrap'], i)
2236         if i == -1:
2237             return
2238         j = 0
2239         addedLines = 0
2240         while j != -1:
2241             j = find_end_of_inset(document.body, i)
2242             if j == -1:
2243                 document.warning("Malformed lyx document: Missing '\\end_inset' (float) at line " + str(i + len(document.header)) + ".\n\t" + document.body[i])
2244                 # document.warning(document.body[i-1] + "\n" + document.body[i+1])
2245                 i += 1
2246                 continue # this will get us back to the outer loop, since j == -1
2247             # look for embedded float (= subfloat)
2248             # whitespace intended (exclude \\begin_inset FloatList)
2249             k = find_token(document.body, '\\begin_inset Float ', i + 1, j)
2250             if k == -1:
2251                 break
2252             # is the subfloat aligned?
2253             al = find_token(document.body, '\\align ', k - 1, j)
2254             alignment_beg = ""
2255             alignment_end = ""
2256             if al != -1:
2257                 if get_value(document.body, '\\align', al) == "center":
2258                     alignment_beg = "\\backslash\nbegin{centering}"
2259                     alignment_end = "\\backslash\npar\\backslash\nend{centering}"
2260                 elif get_value(document.body, '\\align', al) == "left":
2261                     alignment_beg = "\\backslash\nbegin{raggedright}"
2262                     alignment_end = "\\backslash\npar\\backslash\nend{raggedright}"
2263                 elif get_value(document.body, '\\align', al) == "right":
2264                     alignment_beg = "\\backslash\nbegin{raggedleft}"
2265                     alignment_end = "\\backslash\npar\\backslash\nend{raggedleft}"
2266             l = find_end_of_inset(document.body, k)
2267             if l == -1:
2268                 document.warning("Malformed lyx document: Missing '\\end_inset' (embedded float).")
2269                 i += 1
2270                 j = -1
2271                 continue # escape to the outer loop
2272             m = find_default_layout(document, k + 1, l)
2273             # caption?
2274             cap = find_token(document.body, '\\begin_inset Caption', k + 1, l)
2275             caption = ''
2276             shortcap = ''
2277             capend = cap
2278             if cap != -1:
2279                 capend = find_end_of_inset(document.body, cap)
2280                 if capend == -1:
2281                     document.warning("Malformed lyx document: Missing '\\end_inset' (caption).")
2282                     return
2283                 # label?
2284                 label = ''
2285                 lbl = find_token(document.body, '\\begin_inset CommandInset label', cap, capend)
2286                 if lbl != -1:
2287                     lblend = find_end_of_inset(document.body, lbl + 1)
2288                     if lblend == -1:
2289                         document.warning("Malformed lyx document: Missing '\\end_inset' (label).")
2290                         return
2291                     for line in document.body[lbl:lblend + 1]:
2292                         if line.startswith('name '):
2293                             label = line.split()[1].strip('"')
2294                             break
2295                 else:
2296                     lbl = capend
2297                     lblend = capend
2298                     label = ''
2299                 # opt arg?
2300                 opt = find_token(document.body, '\\begin_inset OptArg', cap, capend)
2301                 if opt != -1:
2302                     optend = find_end_of_inset(document.body, opt)
2303                     if optend == -1:
2304                         document.warning("Malformed LyX document: Missing '\\end_inset' (OptArg).")
2305                         return
2306                     optc = find_default_layout(document, opt, optend)
2307                     if optc == -1:
2308                         document.warning("Malformed LyX document: Missing `\\begin_layout' in Float inset.")
2309                         return
2310                     optcend = find_end_of(document.body, optc, "\\begin_layout", "\\end_layout")
2311                     for line in document.body[optc:optcend]:
2312                         if not line.startswith('\\'):
2313                             shortcap += line.strip()
2314                 else:
2315                     opt = capend
2316                     optend = capend
2317                 for line in document.body[cap:capend]:
2318                     if line in document.body[lbl:lblend]:
2319                         continue
2320                     elif line in document.body[opt:optend]:
2321                         continue
2322                     else:
2323                         inert = True
2324                         caption += lyxline2latex(document, line, inert)
2325                 if len(label) > 0:
2326                     caption += "\n\\backslash\nlabel{" + label + "}"
2327             subst = '\\begin_layout PlainLayout\n\\begin_inset ERT\nstatus collapsed\n\n' \
2328                       '\\begin_layout PlainLayout\n\n}' + alignment_end + \
2329                       '\n\\end_layout\n\n\\end_inset\n\n' \
2330                       '\\end_layout\n\n\\begin_layout PlainLayout\n'
2331             subst = subst.split('\n')
2332             document.body[l : l+1] = subst
2333             addedLines = len(subst) - 1
2334             # this is before l and so is unchanged by the multiline insertion
2335             if cap != capend:
2336                 del document.body[cap:capend+1]
2337                 addedLines -= (capend + 1 - cap)
2338             del document.body[k+1:m-1]
2339             addedLines -= (m - 1 - (k + 1))
2340             insertion = '\\begin_inset ERT\nstatus collapsed\n\n' \
2341                         '\\begin_layout PlainLayout\n\n' + alignment_beg + '\n\\backslash\n' \
2342                         'subfloat'
2343             if len(shortcap) > 0:
2344                 insertion = insertion + "[" + shortcap + "]"
2345             if len(caption) > 0:
2346                 insertion = insertion + "[" + caption + "]"
2347             insertion = insertion + '{%\n\\end_layout\n\n\\end_inset\n\n\\end_layout\n'
2348             insertion = insertion.split('\n')
2349             document.body[k : k + 1] = insertion
2350             addedLines += len(insertion) - 1
2351             al = find_token(document.body, '\\align ', k - 1, j + addedLines)
2352             if al != -1:
2353                 del document.body[al]
2354                 addedLines -= 1
2355             add_to_preamble(document, ['\\usepackage{subfig}\n'])
2356         i += addedLines + 1
2357
2358
2359 def revert_wrapplacement(document):
2360     " Revert placement options wrap floats (wrapfig). "
2361     i = 0
2362     while True:
2363         i = find_token(document.body, "\\begin_inset Wrap figure", i)
2364         if i == -1:
2365             return
2366         e = find_end_of_inset(document.body, i)
2367         j = find_token(document.body, "placement", i + 1, e)
2368         if j == -1:
2369             document.warning("Malformed LyX document: Couldn't find placement parameter of wrap float.")
2370             i += 1
2371             continue
2372         r = re.compile("placement (o|i|l|r|O|I|L|R)")
2373         m = r.match(document.body[j])
2374         if m == None:
2375             document.warning("Malformed LyX document: Placement option isn't O|I|R|L!")
2376         else:
2377             document.body[j] = "placement " + m.group(1).lower()
2378         i = j
2379
2380
2381 def remove_extra_embedded_files(document):
2382     " Remove \extra_embedded_files from buffer params "
2383     i = find_token(document.header, '\\extra_embedded_files', 0)
2384     if i == -1:
2385         return
2386     document.header.pop(i)
2387
2388
2389 def convert_spaceinset(document):
2390     " Convert '\\InsetSpace foo' to '\\begin_inset Space foo\n\\end_inset' "
2391     i = 0
2392     while i < len(document.body):
2393         m = re.match(r'(.*)\\InsetSpace (.*)', document.body[i])
2394         if m:
2395             before = m.group(1)
2396             after = m.group(2)
2397             subst = [before, "\\begin_inset Space " + after, "\\end_inset"]
2398             document.body[i: i+1] = subst
2399             i = i + len(subst)
2400         else:
2401             i = i + 1
2402
2403
2404 def revert_spaceinset(document):
2405     " Revert '\\begin_inset Space foo\n\\end_inset' to '\\InsetSpace foo' "
2406     i = 0
2407     while True:
2408         i = find_token(document.body, "\\begin_inset Space", i)
2409         if i == -1:
2410             return
2411         j = find_end_of_inset(document.body, i)
2412         if j == -1:
2413             document.warning("Malformed LyX document: Could not find end of space inset.")
2414             i += 1
2415             continue
2416         document.body[i] = document.body[i].replace('\\begin_inset Space', '\\InsetSpace')
2417         del document.body[j]
2418
2419
2420 def convert_hfill(document):
2421     " Convert hfill to space inset "
2422     i = 0
2423     while True:
2424         i = find_token(document.body, "\\hfill", i)
2425         if i == -1:
2426             return
2427         subst = document.body[i].replace('\\hfill', \
2428                   '\n\\begin_inset Space \\hfill{}\n\\end_inset')
2429         subst = subst.split('\n')
2430         document.body[i : i+1] = subst
2431         i += len(subst)
2432
2433
2434 def revert_hfills(document):
2435     ' Revert \\hfill commands '
2436     hfill = re.compile(r'\\hfill')
2437     dotfill = re.compile(r'\\dotfill')
2438     hrulefill = re.compile(r'\\hrulefill')
2439     i = 0
2440     while True:
2441         i = find_token(document.body, "\\InsetSpace", i)
2442         if i == -1:
2443             return
2444         if hfill.search(document.body[i]):
2445             document.body[i] = \
2446               document.body[i].replace('\\InsetSpace \\hfill{}', '\\hfill')
2447             i += 1
2448             continue
2449         if dotfill.search(document.body[i]):
2450             subst = document.body[i].replace('\\InsetSpace \\dotfill{}', \
2451               '\\begin_inset ERT\nstatus collapsed\n\n' \
2452               '\\begin_layout Standard\n\n\n\\backslash\n' \
2453               'dotfill{}\n\\end_layout\n\n\\end_inset\n\n')
2454             subst = subst.split('\n')
2455             document.body[i : i+1] = subst
2456             i += len(subst)
2457             continue
2458         if hrulefill.search(document.body[i]):
2459             subst = document.body[i].replace('\\InsetSpace \\hrulefill{}', \
2460               '\\begin_inset ERT\nstatus collapsed\n\n' \
2461               '\\begin_layout Standard\n\n\n\\backslash\n' \
2462               'hrulefill{}\n\\end_layout\n\n\\end_inset\n\n')
2463             subst = subst.split('\n')
2464             document.body[i : i+1] = subst
2465             i += len(subst)
2466             continue
2467         i += 1
2468
2469 def revert_hspace(document):
2470     ' Revert \\InsetSpace \\hspace{} to ERT '
2471     i = 0
2472     hspace = re.compile(r'\\hspace{}')
2473     hstar  = re.compile(r'\\hspace\*{}')
2474     while True:
2475         i = find_token(document.body, "\\InsetSpace \\hspace", i)
2476         if i == -1:
2477             return
2478         length = get_value(document.body, '\\length', i+1)
2479         if length == '':
2480             document.warning("Malformed lyx document: Missing '\\length' in Space inset.")
2481             return
2482         del document.body[i+1]
2483         addedLines = -1
2484         if hstar.search(document.body[i]):
2485             subst = document.body[i].replace('\\InsetSpace \\hspace*{}', \
2486               '\\begin_inset ERT\nstatus collapsed\n\n' \
2487               '\\begin_layout Standard\n\n\n\\backslash\n' \
2488               'hspace*{' + length + '}\n\\end_layout\n\n\\end_inset\n\n')
2489             subst = subst.split('\n')
2490             document.body[i : i+1] = subst
2491             addedLines += len(subst) - 1
2492             i += addedLines + 1
2493             continue
2494         if hspace.search(document.body[i]):
2495             subst = document.body[i].replace('\\InsetSpace \\hspace{}', \
2496               '\\begin_inset ERT\nstatus collapsed\n\n' \
2497               '\\begin_layout Standard\n\n\n\\backslash\n' \
2498               'hspace{' + length + '}\n\\end_layout\n\n\\end_inset\n\n')
2499             subst = subst.split('\n')
2500             document.body[i : i+1] = subst
2501             addedLines += len(subst) - 1
2502             i += addedLines + 1
2503             continue
2504         i += 1
2505
2506
2507 def revert_protected_hfill(document):
2508     ' Revert \\begin_inset Space \\hspace*{\\fill} to ERT '
2509     i = 0
2510     while True:
2511         i = find_token(document.body, '\\begin_inset Space \\hspace*{\\fill}', i)
2512         if i == -1:
2513             return
2514         j = find_end_of_inset(document.body, i)
2515         if j == -1:
2516             document.warning("Malformed LyX document: Could not find end of space inset.")
2517             i += 1
2518             continue
2519         del document.body[j]
2520         subst = document.body[i].replace('\\begin_inset Space \\hspace*{\\fill}', \
2521           '\\begin_inset ERT\nstatus collapsed\n\n' \
2522           '\\begin_layout Standard\n\n\n\\backslash\n' \
2523           'hspace*{\n\\backslash\nfill}\n\\end_layout\n\n\\end_inset\n\n')
2524         subst = subst.split('\n')
2525         document.body[i : i+1] = subst
2526         i += len(subst)
2527
2528
2529 def revert_leftarrowfill(document):
2530     ' Revert \\begin_inset Space \\leftarrowfill{} to ERT '
2531     i = 0
2532     while True:
2533         i = find_token(document.body, '\\begin_inset Space \\leftarrowfill{}', i)
2534         if i == -1:
2535             return
2536         j = find_end_of_inset(document.body, i)
2537         if j == -1:
2538             document.warning("Malformed LyX document: Could not find end of space inset.")
2539             i += 1
2540             continue
2541         del document.body[j]
2542         subst = document.body[i].replace('\\begin_inset Space \\leftarrowfill{}', \
2543           '\\begin_inset ERT\nstatus collapsed\n\n' \
2544           '\\begin_layout Standard\n\n\n\\backslash\n' \
2545           'leftarrowfill{}\n\\end_layout\n\n\\end_inset\n\n')
2546         subst = subst.split('\n')
2547         document.body[i : i+1] = subst
2548         i += len(subst)
2549
2550
2551 def revert_rightarrowfill(document):
2552     ' Revert \\begin_inset Space \\rightarrowfill{} to ERT '
2553     i = 0
2554     while True:
2555         i = find_token(document.body, '\\begin_inset Space \\rightarrowfill{}', i)
2556         if i == -1:
2557             return
2558         j = find_end_of_inset(document.body, i)
2559         if j == -1:
2560             document.warning("Malformed LyX document: Could not find end of space inset.")
2561             i += 1
2562             continue
2563         del document.body[j]
2564         subst = document.body[i].replace('\\begin_inset Space \\rightarrowfill{}', \
2565           '\\begin_inset ERT\nstatus collapsed\n\n' \
2566           '\\begin_layout Standard\n\n\n\\backslash\n' \
2567           'rightarrowfill{}\n\\end_layout\n\n\\end_inset\n\n')
2568         subst = subst.split('\n')
2569         document.body[i : i+1] = subst
2570         i += len(subst)
2571
2572
2573 def revert_upbracefill(document):
2574     ' Revert \\begin_inset Space \\upbracefill{} to ERT '
2575     i = 0
2576     while True:
2577         i = find_token(document.body, '\\begin_inset Space \\upbracefill{}', i)
2578         if i == -1:
2579             return
2580         j = find_end_of_inset(document.body, i)
2581         if j == -1:
2582             document.warning("Malformed LyX document: Could not find end of space inset.")
2583             i += 1
2584             continue
2585         del document.body[j]
2586         subst = document.body[i].replace('\\begin_inset Space \\upbracefill{}', \
2587           '\\begin_inset ERT\nstatus collapsed\n\n' \
2588           '\\begin_layout Standard\n\n\n\\backslash\n' \
2589           'upbracefill{}\n\\end_layout\n\n\\end_inset\n\n')
2590         subst = subst.split('\n')
2591         document.body[i : i+1] = subst
2592         i += len(subst)
2593
2594
2595 def revert_downbracefill(document):
2596     ' Revert \\begin_inset Space \\downbracefill{} to ERT '
2597     i = 0
2598     while True:
2599         i = find_token(document.body, '\\begin_inset Space \\downbracefill{}', i)
2600         if i == -1:
2601             return
2602         j = find_end_of_inset(document.body, i)
2603         if j == -1:
2604             document.warning("Malformed LyX document: Could not find end of space inset.")
2605             i += 1
2606             continue
2607         del document.body[j]
2608         subst = document.body[i].replace('\\begin_inset Space \\downbracefill{}', \
2609           '\\begin_inset ERT\nstatus collapsed\n\n' \
2610           '\\begin_layout Standard\n\n\n\\backslash\n' \
2611           'downbracefill{}\n\\end_layout\n\n\\end_inset\n\n')
2612         subst = subst.split('\n')
2613         document.body[i : i+1] = subst
2614         i += len(subst)
2615
2616
2617 def revert_local_layout(document):
2618     ' Revert local layout headers.'
2619     i = 0
2620     while True:
2621         i = find_token(document.header, "\\begin_local_layout", i)
2622         if i == -1:
2623             return
2624         j = find_end_of(document.header, i, "\\begin_local_layout", "\\end_local_layout")
2625         if j == -1:
2626             # this should not happen
2627             break
2628         document.header[i : j + 1] = []
2629
2630
2631 def convert_pagebreaks(document):
2632     ' Convert inline Newpage insets to new format '
2633     i = 0
2634     while True:
2635         i = find_token(document.body, '\\newpage', i)
2636         if i == -1:
2637             break
2638         document.body[i:i+1] = ['\\begin_inset Newpage newpage',
2639                                 '\\end_inset']
2640     i = 0
2641     while True:
2642         i = find_token(document.body, '\\pagebreak', i)
2643         if i == -1:
2644             break
2645         document.body[i:i+1] = ['\\begin_inset Newpage pagebreak',
2646                                 '\\end_inset']
2647     i = 0
2648     while True:
2649         i = find_token(document.body, '\\clearpage', i)
2650         if i == -1:
2651             break
2652         document.body[i:i+1] = ['\\begin_inset Newpage clearpage',
2653                                 '\\end_inset']
2654     i = 0
2655     while True:
2656         i = find_token(document.body, '\\cleardoublepage', i)
2657         if i == -1:
2658             break
2659         document.body[i:i+1] = ['\\begin_inset Newpage cleardoublepage',
2660                                 '\\end_inset']
2661
2662
2663 def revert_pagebreaks(document):
2664     ' Revert \\begin_inset Newpage to previous inline format '
2665     i = 0
2666     while True:
2667         i = find_token(document.body, '\\begin_inset Newpage', i)
2668         if i == -1:
2669             return
2670         j = find_end_of_inset(document.body, i)
2671         if j == -1:
2672             document.warning("Malformed LyX document: Could not find end of Newpage inset.")
2673             i += 1
2674             continue
2675         del document.body[j]
2676         document.body[i] = document.body[i].replace('\\begin_inset Newpage newpage', '\\newpage')
2677         document.body[i] = document.body[i].replace('\\begin_inset Newpage pagebreak', '\\pagebreak')
2678         document.body[i] = document.body[i].replace('\\begin_inset Newpage clearpage', '\\clearpage')
2679         document.body[i] = document.body[i].replace('\\begin_inset Newpage cleardoublepage', '\\cleardoublepage')
2680
2681
2682 def convert_linebreaks(document):
2683     ' Convert inline Newline insets to new format '
2684     i = 0
2685     while True:
2686         i = find_token(document.body, '\\newline', i)
2687         if i == -1:
2688             break
2689         document.body[i:i+1] = ['\\begin_inset Newline newline',
2690                                 '\\end_inset']
2691     i = 0
2692     while True:
2693         i = find_token(document.body, '\\linebreak', i)
2694         if i == -1:
2695             break
2696         document.body[i:i+1] = ['\\begin_inset Newline linebreak',
2697                                 '\\end_inset']
2698
2699
2700 def revert_linebreaks(document):
2701     ' Revert \\begin_inset Newline to previous inline format '
2702     i = 0
2703     while True:
2704         i = find_token(document.body, '\\begin_inset Newline', i)
2705         if i == -1:
2706             return
2707         j = find_end_of_inset(document.body, i)
2708         if j == -1:
2709             document.warning("Malformed LyX document: Could not find end of Newline inset.")
2710             i += 1
2711             continue
2712         del document.body[j]
2713         document.body[i] = document.body[i].replace('\\begin_inset Newline newline', '\\newline')
2714         document.body[i] = document.body[i].replace('\\begin_inset Newline linebreak', '\\linebreak')
2715
2716
2717 def convert_japanese_plain(document):
2718     ' Set language japanese-plain to japanese '
2719     i = 0
2720     if document.language == "japanese-plain":
2721         document.language = "japanese"
2722         i = find_token(document.header, "\\language", 0)
2723         if i != -1:
2724             document.header[i] = "\\language japanese"
2725     j = 0
2726     while True:
2727         j = find_token(document.body, "\\lang japanese-plain", j)
2728         if j == -1:
2729             return
2730         document.body[j] = document.body[j].replace("\\lang japanese-plain", "\\lang japanese")
2731         j = j + 1
2732
2733
2734 def revert_pdfpages(document):
2735     ' Revert pdfpages external inset to ERT '
2736     i = 0
2737     while 1:
2738         i = find_token(document.body, "\\begin_inset External", i)
2739         if i == -1:
2740             return
2741         j = find_end_of_inset(document.body, i)
2742         if j == -1:
2743             document.warning("Malformed lyx document: Missing '\\end_inset' in revert_pdfpages.")
2744             i = i + 1
2745             continue
2746         if get_value(document.body, 'template', i, j) == "PDFPages":
2747             filename = get_value(document.body, 'filename', i, j)
2748             extra = ''
2749             r = re.compile(r'\textra PDFLaTeX \"(.*)\"$')
2750             for k in range(i, j):
2751                 m = r.match(document.body[k])
2752                 if m:
2753                     extra = m.group(1)
2754             angle = get_value(document.body, 'rotateAngle', i, j)
2755             width = get_value(document.body, 'width', i, j)
2756             height = get_value(document.body, 'height', i, j)
2757             scale = get_value(document.body, 'scale', i, j)
2758             keepAspectRatio = find_token(document.body, "\tkeepAspectRatio", i, j)
2759             options = extra
2760             if angle != '':
2761                  if options != '':
2762                      options += ",angle=" + angle
2763                  else:
2764                      options += "angle=" + angle
2765             if width != '':
2766                  if options != '':
2767                      options += ",width=" + convert_len(width)
2768                  else:
2769                      options += "width=" + convert_len(width)
2770             if height != '':
2771                  if options != '':
2772                      options += ",height=" + convert_len(height)
2773                  else:
2774                      options += "height=" + convert_len(height)
2775             if scale != '':
2776                  if options != '':
2777                      options += ",scale=" + scale
2778                  else:
2779                      options += "scale=" + scale
2780             if keepAspectRatio != '':
2781                  if options != '':
2782                      options += ",keepaspectratio"
2783                  else:
2784                      options += "keepaspectratio"
2785             if options != '':
2786                      options = '[' + options + ']'
2787             del document.body[i+1:j+1]
2788             document.body[i:i+1] = ['\\begin_inset ERT',
2789                                 'status collapsed',
2790                                 '',
2791                                 '\\begin_layout Standard',
2792                                 '',
2793                                 '\\backslash',
2794                                 'includepdf' + options + '{' + filename + '}',
2795                                 '\\end_layout',
2796                                 '',
2797                                 '\\end_inset']
2798             add_to_preamble(document, ['\\usepackage{pdfpages}\n'])
2799             i = i + 1
2800             continue
2801         i = i + 1
2802
2803
2804 def revert_mexican(document):
2805     ' Set language Spanish(Mexico) to Spanish '
2806     i = 0
2807     if document.language == "spanish-mexico":
2808         document.language = "spanish"
2809         i = find_token(document.header, "\\language", 0)
2810         if i != -1:
2811             document.header[i] = "\\language spanish"
2812     j = 0
2813     while True:
2814         j = find_token(document.body, "\\lang spanish-mexico", j)
2815         if j == -1:
2816             return
2817         document.body[j] = document.body[j].replace("\\lang spanish-mexico", "\\lang spanish")
2818         j = j + 1
2819
2820
2821 def remove_embedding(document):
2822     ' Remove embed tag from all insets '
2823     revert_inset_embedding(document, 'Graphics')
2824     revert_inset_embedding(document, 'External')
2825     revert_inset_embedding(document, 'CommandInset include')
2826     revert_inset_embedding(document, 'CommandInset bibtex')
2827
2828
2829 def revert_master(document):
2830     ' Remove master param '
2831     i = find_token(document.header, "\\master", 0)
2832     if i != -1:
2833         del document.header[i]
2834
2835
2836 def revert_graphics_group(document):
2837     ' Revert group information from graphics insets '
2838     i = 0
2839     while 1:
2840         i = find_token(document.body, "\\begin_inset Graphics", i)
2841         if i == -1:
2842             return
2843         j = find_end_of_inset(document.body, i)
2844         if j == -1:
2845             document.warning("Malformed lyx document: Missing '\\end_inset' in revert_graphics_group.")
2846             i = i + 1
2847             continue
2848         k = find_token(document.body, " groupId", i, j)
2849         if k == -1:
2850             i = i + 1
2851             continue
2852         del document.body[k]
2853         i = i + 1
2854
2855
2856 def update_apa_styles(document):
2857     ' Replace obsolete styles '
2858
2859     if document.textclass != "apa":
2860         return
2861
2862     obsoletedby = { "Acknowledgments": "Acknowledgements",
2863                     "Section*":        "Section",
2864                     "Subsection*":     "Subsection",
2865                     "Subsubsection*":  "Subsubsection",
2866                     "Paragraph*":      "Paragraph",
2867                     "Subparagraph*":   "Subparagraph"}
2868     i = 0
2869     while 1:
2870         i = find_token(document.body, "\\begin_layout", i)
2871         if i == -1:
2872             return
2873
2874         layout = document.body[i][14:]
2875         if layout in obsoletedby:
2876             document.body[i] = "\\begin_layout " + obsoletedby[layout]
2877
2878         i += 1
2879
2880
2881 def convert_paper_sizes(document):
2882     ' exchange size options legalpaper and executivepaper to correct order '
2883     # routine is needed to fix http://www.lyx.org/trac/ticket/4868
2884     i = 0
2885     j = 0
2886     i = find_token(document.header, "\\papersize executivepaper", 0)
2887     if i != -1:
2888         document.header[i] = "\\papersize legalpaper"
2889         return
2890     j = find_token(document.header, "\\papersize legalpaper", 0)
2891     if j != -1:
2892         document.header[j] = "\\papersize executivepaper"
2893
2894
2895 def revert_paper_sizes(document):
2896     ' exchange size options legalpaper and executivepaper to correct order '
2897     i = 0
2898     j = 0
2899     i = find_token(document.header, "\\papersize executivepaper", 0)
2900     if i != -1:
2901         document.header[i] = "\\papersize legalpaper"
2902         return
2903     j = find_token(document.header, "\\papersize legalpaper", 0)
2904     if j != -1:
2905         document.header[j] = "\\papersize executivepaper"
2906
2907
2908 def convert_InsetSpace(document):
2909     " Convert '\\begin_inset Space foo' to '\\begin_inset space foo'"
2910     i = 0
2911     while True:
2912         i = find_token(document.body, "\\begin_inset Space", i)
2913         if i == -1:
2914             return
2915         document.body[i] = document.body[i].replace('\\begin_inset Space', '\\begin_inset space')
2916
2917
2918 def revert_InsetSpace(document):
2919     " Revert '\\begin_inset space foo' to '\\begin_inset Space foo'"
2920     i = 0
2921     while True:
2922         i = find_token(document.body, "\\begin_inset space", i)
2923         if i == -1:
2924             return
2925         document.body[i] = document.body[i].replace('\\begin_inset space', '\\begin_inset Space')
2926
2927
2928 def convert_display_enum(document):
2929     " Convert 'display foo' to 'display false/true'"
2930     i = 0
2931     while True:
2932         i = find_token(document.body, "\tdisplay", i)
2933         if i == -1:
2934             return
2935         val = get_value(document.body, 'display', i)
2936         if val == "none":
2937             document.body[i] = document.body[i].replace('none', 'false')
2938         if val == "default":
2939             document.body[i] = document.body[i].replace('default', 'true')
2940         if val == "monochrome":
2941             document.body[i] = document.body[i].replace('monochrome', 'true')
2942         if val == "grayscale":
2943             document.body[i] = document.body[i].replace('grayscale', 'true')
2944         if val == "color":
2945             document.body[i] = document.body[i].replace('color', 'true')
2946         if val == "preview":
2947             document.body[i] = document.body[i].replace('preview', 'true')
2948         i += 1
2949
2950
2951 def revert_display_enum(document):
2952     " Revert 'display false/true' to 'display none/color'"
2953     i = 0
2954     while True:
2955         i = find_token(document.body, "\tdisplay", i)
2956         if i == -1:
2957             return
2958         val = get_value(document.body, 'display', i)
2959         if val == "false":
2960             document.body[i] = document.body[i].replace('false', 'none')
2961         if val == "true":
2962             document.body[i] = document.body[i].replace('true', 'default')
2963         i += 1
2964
2965
2966 def remove_fontsCJK(document):
2967     ' Remove font_cjk param '
2968     i = find_token(document.header, "\\font_cjk", 0)
2969     if i != -1:
2970         del document.header[i]
2971
2972
2973 def convert_plain_layout(document):
2974     " Convert 'PlainLayout' to 'Plain Layout'"
2975     i = 0
2976     while True:
2977         i = find_token(document.body, '\\begin_layout PlainLayout', i)
2978         if i == -1:
2979             return
2980         document.body[i] = document.body[i].replace('\\begin_layout PlainLayout', \
2981           '\\begin_layout Plain Layout')
2982         i += 1
2983
2984
2985 def revert_plain_layout(document):
2986     " Revert 'Plain Layout' to 'PlainLayout'"
2987     i = 0
2988     while True:
2989         i = find_token(document.body, '\\begin_layout Plain Layout', i)
2990         if i == -1:
2991             return
2992         document.body[i] = document.body[i].replace('\\begin_layout Plain Layout', \
2993           '\\begin_layout PlainLayout')
2994         i += 1
2995
2996
2997 def revert_plainlayout(document):
2998     " Revert 'PlainLayout' to 'Standard'"
2999     i = 0
3000     while True:
3001         i = find_token(document.body, '\\begin_layout PlainLayout', i)
3002         if i == -1:
3003             return
3004         # This will be incorrect for some document classes, since Standard is not always
3005         # the default. But (a) it is probably the best we can do and (b) it will actually
3006         # work, in fact, since an unknown layout will be converted to default.
3007         document.body[i] = document.body[i].replace('\\begin_layout PlainLayout', \
3008           '\\begin_layout Standard')
3009         i += 1
3010
3011
3012 def revert_polytonicgreek(document):
3013     "Set language polytonic Greek to Greek"
3014     i = 0
3015     if document.language == "polutonikogreek":
3016         document.language = "greek"
3017         i = find_token(document.header, "\\language", 0)
3018         if i != -1:
3019             document.header[i] = "\\language greek"
3020     j = 0
3021     while True:
3022         j = find_token(document.body, "\\lang polutonikogreek", j)
3023         if j == -1:
3024             return
3025         document.body[j] = document.body[j].replace("\\lang polutonikogreek", "\\lang greek")
3026         j = j + 1
3027
3028
3029 def revert_removed_modules(document):
3030     i = 0
3031     while True:
3032         i = find_token(document.header, "\\begin_remove_modules", i)
3033         if i == -1:
3034             return
3035         j = find_end_of(document.header, i, "\\begin_remove_modules", "\\end_remove_modules")
3036         if j == -1:
3037             # this should not happen
3038             break
3039         document.header[i : j + 1] = []
3040
3041
3042 def add_plain_layout(document):
3043     i = 0
3044     while True:
3045         i = find_token(document.body, "\\begin_layout", i)
3046         if i == -1:
3047             return
3048         if len(document.body[i].split()) == 1:
3049             document.body[i] = "\\begin_layout Plain Layout"
3050         i += 1
3051
3052
3053 def revert_tabulators(document):
3054     "Revert tabulators to 4 spaces"
3055     i = 0
3056     while True:
3057         i = find_token(document.body, "\t", i)
3058         if i == -1:
3059             return
3060         document.body[i] = document.body[i].replace("\t", "    ")
3061         i += 1
3062
3063
3064 def revert_tabsize(document):
3065     "Revert the tabsize parameter of listings"
3066     i = 0
3067     j = 0
3068     while True:
3069         # either it is the only parameter
3070         i = find_token(document.body, 'lstparams "tabsize=4"', i)
3071         if i != -1:
3072             del document.body[i]
3073         # or the last one
3074         j = find_token(document.body, "lstparams", j)
3075         if j == -1:
3076             return
3077         pos = document.body[j].find(",tabsize=")
3078         document.body[j] = document.body[j][:pos] + '"'
3079         i += 1
3080         j += 1
3081
3082
3083 def revert_mongolian(document):
3084     "Set language Mongolian to English"
3085     i = 0
3086     if document.language == "mongolian":
3087         document.language = "english"
3088         i = find_token(document.header, "\\language", 0)
3089         if i != -1:
3090             document.header[i] = "\\language english"
3091     j = 0
3092     while True:
3093         j = find_token(document.body, "\\lang mongolian", j)
3094         if j == -1:
3095             return
3096         document.body[j] = document.body[j].replace("\\lang mongolian", "\\lang english")
3097         j = j + 1
3098
3099
3100 def revert_default_options(document):
3101     ' Remove param use_default_options '
3102     i = find_token(document.header, "\\use_default_options", 0)
3103     if i != -1:
3104         del document.header[i]
3105
3106
3107 def convert_default_options(document):
3108     ' Add param use_default_options and set it to false '
3109     i = find_token(document.header, "\\textclass", 0)
3110     if i == -1:
3111         document.warning("Malformed LyX document: Missing `\\textclass'.")
3112         return
3113     document.header.insert(i, '\\use_default_options false')
3114
3115
3116 def revert_backref_options(document):
3117     ' Revert option pdf_backref=page to pagebackref '
3118     i = find_token(document.header, "\\pdf_backref page", 0)
3119     if i != -1:
3120         document.header[i] = "\\pdf_pagebackref true"
3121
3122
3123 def convert_backref_options(document):
3124     ' We have changed the option pagebackref to backref=true '
3125     i = find_token(document.header, "\\pdf_pagebackref true", 0)
3126     if i != -1:
3127         document.header[i] = "\\pdf_backref page"
3128     j = find_token(document.header, "\\pdf_pagebackref false", 0)
3129     if j != -1:
3130         del document.header[j]
3131     # backref=true was not a valid option, we meant backref=section
3132     k = find_token(document.header, "\\pdf_backref true", 0)
3133     if k != -1 and i != -1:
3134         del document.header[k]
3135     elif k != -1 and j != -1:
3136         document.header[k] = "\\pdf_backref section"
3137
3138
3139 def convert_charstyle_element(document):
3140     "Convert CharStyle to Element for docbook backend"
3141     if document.backend != "docbook":
3142         return
3143     i = 0
3144     while True:
3145         i = find_token(document.body, "\\begin_inset Flex CharStyle:", i)
3146         if i == -1:
3147             return
3148         document.body[i] = document.body[i].replace('\\begin_inset Flex CharStyle:',
3149                                                     '\\begin_inset Flex Element:')
3150
3151 def revert_charstyle_element(document):
3152     "Convert Element to CharStyle for docbook backend"
3153     if document.backend != "docbook":
3154         return
3155     i = 0
3156     while True:
3157         i = find_token(document.body, "\\begin_inset Flex Element:", i)
3158         if i == -1:
3159             return
3160         document.body[i] = document.body[i].replace('\\begin_inset Flex Element:',
3161                                                     '\\begin_inset Flex CharStyle:')
3162
3163 ##
3164 # Conversion hub
3165 #
3166
3167 supported_versions = ["1.6.0","1.6"]
3168 convert = [[277, [fix_wrong_tables]],
3169            [278, [close_begin_deeper]],
3170            [279, [long_charstyle_names]],
3171            [280, [axe_show_label]],
3172            [281, []],
3173            [282, []],
3174            [283, [convert_flex]],
3175            [284, []],
3176            [285, []],
3177            [286, []],
3178            [287, [convert_wrapfig_options]],
3179            [288, [convert_inset_command]],
3180            [289, [convert_latexcommand_index]],
3181            [290, []],
3182            [291, []],
3183            [292, [convert_japanese_cjk]],
3184            [293, []],
3185            [294, [convert_pdf_options]],
3186            [295, [convert_htmlurl, convert_url]],
3187            [296, [convert_include]],
3188            [297, [convert_usorbian]],
3189            [298, [convert_macro_global]],
3190            [299, []],
3191            [300, []],
3192            [301, []],
3193            [302, []],
3194            [303, [convert_serbocroatian]],
3195            [304, [convert_framed_notes]],
3196            [305, []],
3197            [306, []],
3198            [307, []],
3199            [308, []],
3200            [309, []],
3201            [310, []],
3202            [311, [convert_ams_classes]],
3203            [312, []],
3204            [313, [convert_module_names]],
3205            [314, []],
3206            [315, []],
3207            [316, [convert_subfig]],
3208            [317, []],
3209            [318, []],
3210            [319, [convert_spaceinset, convert_hfill]],
3211            [320, []],
3212            [321, [convert_tablines]],
3213            [322, [convert_plain_layout]],
3214            [323, [convert_pagebreaks]],
3215            [324, [convert_linebreaks]],
3216            [325, [convert_japanese_plain]],
3217            [326, []],
3218            [327, []],
3219            [328, [remove_embedding, remove_extra_embedded_files, remove_inzip_options]],
3220            [329, []],
3221            [330, []],
3222            [331, [convert_ltcaption]],
3223            [332, []],
3224            [333, [update_apa_styles]],
3225            [334, [convert_paper_sizes]],
3226            [335, [convert_InsetSpace]],
3227            [336, []],
3228            [337, [convert_display_enum]],
3229            [338, []],
3230            [339, []],
3231            [340, [add_plain_layout]],
3232            [341, []],
3233            [342, []],
3234            [343, [convert_default_options]],
3235            [344, [convert_backref_options]],
3236            [345, [convert_charstyle_element]]
3237           ]
3238
3239 revert =  [[344, [revert_charstyle_element]],
3240            [343, [revert_backref_options]],
3241            [342, [revert_default_options]],
3242            [341, [revert_mongolian]],
3243            [340, [revert_tabulators, revert_tabsize]],
3244            [339, []],
3245            [338, [revert_removed_modules]],
3246            [337, [revert_polytonicgreek]],
3247            [336, [revert_display_enum]],
3248            [335, [remove_fontsCJK]],
3249            [334, [revert_InsetSpace]],
3250            [333, [revert_paper_sizes]],
3251            [332, []],
3252            [331, [revert_graphics_group]],
3253            [330, [revert_ltcaption]],
3254            [329, [revert_leftarrowfill, revert_rightarrowfill, revert_upbracefill, revert_downbracefill]],
3255            [328, [revert_master]],
3256            [327, []],
3257            [326, [revert_mexican]],
3258            [325, [revert_pdfpages]],
3259            [324, []],
3260            [323, [revert_linebreaks]],
3261            [322, [revert_pagebreaks]],
3262            [321, [revert_local_layout, revert_plain_layout]],
3263            [320, [revert_tablines]],
3264            [319, [revert_protected_hfill]],
3265            [318, [revert_spaceinset, revert_hfills, revert_hspace]],
3266            [317, [remove_extra_embedded_files]],
3267            [316, [revert_wrapplacement]],
3268            [315, [revert_subfig]],
3269            [314, [revert_colsep, revert_plainlayout]],
3270            [313, []],
3271            [312, [revert_module_names]],
3272            [311, [revert_rotfloat, revert_widesideways]],
3273            [310, [revert_external_embedding]],
3274            [309, [revert_btprintall]],
3275            [308, [revert_nocite]],
3276            [307, [revert_serbianlatin]],
3277            [306, [revert_slash, revert_nobreakdash]],
3278            [305, [revert_interlingua]],
3279            [304, [revert_bahasam]],
3280            [303, [revert_framed_notes]],
3281            [302, []],
3282            [301, [revert_latin, revert_samin]],
3283            [300, [revert_linebreak]],
3284            [299, [revert_pagebreak]],
3285            [298, [revert_hyperlinktype]],
3286            [297, [revert_macro_optional_params]],
3287            [296, [revert_albanian, revert_lowersorbian, revert_uppersorbian]],
3288            [295, [revert_include]],
3289            [294, [revert_href, revert_url]],
3290            [293, [revert_pdf_options_2]],
3291            [292, [revert_inset_info]],
3292            [291, [revert_japanese, revert_japanese_encoding, revert_japanese_cjk]],
3293            [290, [revert_vietnamese]],
3294            [289, [revert_wraptable]],
3295            [288, [revert_latexcommand_index]],
3296            [287, [revert_inset_command]],
3297            [286, [revert_wrapfig_options]],
3298            [285, [revert_pdf_options]],
3299            [284, [remove_inzip_options]],
3300            [283, []],
3301            [282, [revert_flex]],
3302            [281, []],
3303            [280, [revert_begin_modules]],
3304            [279, [revert_show_label]],
3305            [278, [revert_long_charstyle_names]],
3306            [277, []],
3307            [276, []]
3308           ]
3309
3310
3311 if __name__ == "__main__":
3312     pass