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