]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_0.py
Don't use widest label for numerical citations.
[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.warning("Malformed LyX document: Can't find \\use_mhchem.")
1823         return;
1824     j = find_token(document.preamble, "\\usepackage{mathdots}", 0)
1825     if j == -1:
1826         document.header.insert(i + 1, "\\use_mathdots 0")
1827     else:
1828         document.header.insert(i + 1, "\\use_mathdots 2")
1829         del document.preamble[j]
1830
1831
1832 def revert_mathdots(document):
1833     " Load mathdots if used in the document "
1834
1835     mathdots = find_token(document.header, "\\use_mathdots" , 0)
1836     if mathdots == -1:
1837       document.warning("No \\use_mathdots line. Assuming auto.")
1838     else:
1839       val = get_value(document.header, "\\use_mathdots", mathdots)
1840       del document.header[mathdots]
1841       try:
1842         usedots = int(val)
1843       except:
1844         document.warning("Invalid \\use_mathdots value: " + val + ". Assuming auto.")
1845         # probably usedots has not been changed, but be safe.
1846         usedots = 1
1847
1848       if usedots == 0:
1849         # do not load case
1850         return
1851       if usedots == 2:
1852         # force load case
1853         add_to_preamble(document, ["\\usepackage{mathdots}"])
1854         return
1855     
1856     # so we are in the auto case. we want to load mathdots if \iddots is used.
1857     i = 0
1858     while True:
1859       i = find_token(document.body, '\\begin_inset Formula', i)
1860       if i == -1:
1861         return
1862       j = find_end_of_inset(document.body, i)
1863       if j == -1:
1864         document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
1865         i += 1
1866         continue
1867       code = "\n".join(document.body[i:j])
1868       if code.find("\\iddots") != -1:
1869         add_to_preamble(document, ["\\@ifundefined{iddots}{\\usepackage{mathdots}}"])
1870         return
1871       i = j
1872
1873
1874 def convert_rule(document):
1875     " Convert \\lyxline to CommandInset line. "
1876     i = 0
1877     
1878     inset = ['\\begin_inset CommandInset line',
1879       'LatexCommand rule',
1880       'offset "0.5ex"',
1881       'width "100line%"',
1882       'height "1pt"', '',
1883       '\\end_inset', '', '']
1884
1885     # if paragraphs are indented, we may have to unindent to get the
1886     # line to be full-width.
1887     indent = get_value(document.header, "\\paragraph_separation", 0)
1888     have_indent = (indent == "indent")
1889
1890     while True:
1891       i = find_token(document.body, "\\lyxline" , i)
1892       if i == -1:
1893         return
1894
1895       # we need to find out if this line follows other content
1896       # in its paragraph. find its layout....
1897       lastlay = find_token_backwards(document.body, "\\begin_layout", i)
1898       if lastlay == -1:
1899         document.warning("Can't find layout for line at " + str(i))
1900         # do the best we can.
1901         document.body[i:i+1] = inset
1902         i += len(inset)
1903         continue
1904
1905       # ...and look for other content before it.
1906       lineisfirst = True
1907       for line in document.body[lastlay + 1:i]:
1908         # is it empty or a paragraph option?
1909         if not line or line[0] == '\\':
1910           continue
1911         lineisfirst = False
1912         break
1913
1914       if lineisfirst:
1915         document.body[i:i+1] = inset
1916         if indent:
1917           # we need to unindent, lest the line be too long
1918           document.body.insert(lastlay + 1, "\\noindent")
1919         i += len(inset)
1920       else:
1921         # so our line is in the middle of a paragraph
1922         # we need to add a new line, lest this line follow the
1923         # other content on that line and run off the side of the page
1924         document.body[i:i+1] = inset
1925         document.body[i:i] = ["\\begin_inset Newline newline", "\\end_inset", ""]
1926       i += len(inset)
1927
1928
1929 def revert_rule(document):
1930     " Revert line insets to Tex code "
1931     i = 0
1932     while 1:
1933       i = find_token(document.body, "\\begin_inset CommandInset line" , i)
1934       if i == -1:
1935         return
1936       # find end of inset
1937       j = find_token(document.body, "\\end_inset" , i)
1938       if j == -1:
1939         document.warning("Malformed LyX document: Can't find end of line inset.")
1940         return
1941       # determine the optional offset
1942       offset = get_quoted_value(document.body, 'offset', i, j)
1943       if offset:
1944         offset = '[' + offset + ']'
1945       # determine the width
1946       width = get_quoted_value(document.body, 'width', i, j, "100col%")
1947       width = latex_length(width)[1]
1948       # determine the height
1949       height = get_quoted_value(document.body, 'height', i, j, "1pt")
1950       height = latex_length(height)[1]
1951       # output the \rule command
1952       subst = "\\rule[" + offset + "]{" + width + "}{" + height + "}"
1953       document.body[i:j + 1] = put_cmd_in_ert(subst)
1954       i += len(subst) - (j - i)
1955
1956
1957 def revert_diagram(document):
1958   " Add the feyn package if \\Diagram is used in math "
1959   i = 0
1960   while True:
1961     i = find_token(document.body, '\\begin_inset Formula', i)
1962     if i == -1:
1963       return
1964     j = find_end_of_inset(document.body, i)
1965     if j == -1:
1966         document.warning("Malformed LyX document: Can't find end of Formula inset.")
1967         return 
1968     lines = "\n".join(document.body[i:j])
1969     if lines.find("\\Diagram") == -1:
1970       i = j
1971       continue
1972     add_to_preamble(document, ["\\usepackage{feyn}"])
1973     # only need to do it once!
1974     return
1975
1976 chapters = ("amsbook", "book", "docbook-book", "elsart", "extbook", "extreport", 
1977     "jbook", "jreport", "jsbook", "literate-book", "literate-report", "memoir", 
1978     "mwbk", "mwrep", "recipebook", "report", "scrbook", "scrreprt", "svmono", 
1979     "svmult", "tbook", "treport", "tufte-book")
1980
1981 def convert_bibtex_clearpage(document):
1982   " insert a clear(double)page bibliographystyle if bibtotoc option is used "
1983
1984   if document.textclass not in chapters:
1985     return
1986
1987   i = find_token(document.header, '\\papersides', 0)
1988   sides = 0
1989   if i == -1:
1990     document.warning("Malformed LyX document: Can't find papersides definition.")
1991     document.warning("Assuming single sided.")
1992     sides = 1
1993   else:
1994     val = get_value(document.header, "\\papersides", i)
1995     try:
1996       sides = int(val)
1997     except:
1998       pass
1999     if sides != 1 and sides != 2:
2000       document.warning("Invalid papersides value: " + val)
2001       document.warning("Assuming single sided.")
2002       sides = 1
2003
2004   j = 0
2005   while True:
2006     j = find_token(document.body, "\\begin_inset CommandInset bibtex", j)
2007     if j == -1:
2008       return
2009
2010     k = find_end_of_inset(document.body, j)
2011     if k == -1:
2012       document.warning("Can't find end of Bibliography inset at line " + str(j))
2013       j += 1
2014       continue
2015
2016     # only act if there is the option "bibtotoc"
2017     val = get_value(document.body, 'options', j, k)
2018     if not val:
2019       document.warning("Can't find options for bibliography inset at line " + str(j))
2020       j = k
2021       continue
2022     
2023     if val.find("bibtotoc") == -1:
2024       j = k
2025       continue
2026     
2027     # so we want to insert a new page right before the paragraph that
2028     # this bibliography thing is in. 
2029     lay = find_token_backwards(document.body, "\\begin_layout", j)
2030     if lay == -1:
2031       document.warning("Can't find layout containing bibliography inset at line " + str(j))
2032       j = k
2033       continue
2034
2035     if sides == 1:
2036       cmd = "clearpage"
2037     else:
2038       cmd = "cleardoublepage"
2039     subst = ['\\begin_layout Standard',
2040         '\\begin_inset Newpage ' + cmd,
2041         '\\end_inset', '', '',
2042         '\\end_layout', '']
2043     document.body[lay:lay] = subst
2044     j = k + len(subst)
2045
2046
2047 def check_passthru(document):
2048   tc = document.textclass
2049   ok = (tc == "literate-article" or tc == "literate-book" or tc == "literate-report")
2050   if not ok:
2051     mods = document.get_module_list()
2052     for mod in mods:
2053       if mod == "sweave" or mod == "noweb":
2054         ok = True
2055         break
2056   return ok
2057
2058
2059 def convert_passthru(document):
2060     " http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg161298.html "
2061     if not check_passthru:
2062       return
2063     
2064     rx = re.compile("\\\\begin_layout \s*(\w+)")
2065     beg = 0
2066     for lay in ["Chunk", "Scrap"]:
2067       while True:
2068         beg = find_token(document.body, "\\begin_layout " + lay, beg)
2069         if beg == -1:
2070           break
2071         end = find_end_of_layout(document.body, beg)
2072         if end == -1:
2073           document.warning("Can't find end of layout at line " + str(beg))
2074           beg += 1
2075           continue
2076
2077         # we are now going to replace newline insets within this layout
2078         # by new instances of this layout. so we have repeated layouts
2079         # instead of newlines.
2080
2081         # if the paragraph has any customization, however, we do not want to
2082         # do the replacement.
2083         if document.body[beg + 1].startswith("\\"):
2084           beg = end + 1
2085           continue
2086
2087         ns = beg
2088         while True:
2089           ns = find_token(document.body, "\\begin_inset Newline newline", ns, end)
2090           if ns == -1:
2091             break
2092           ne = find_end_of_inset(document.body, ns)
2093           if ne == -1 or ne > end:
2094             document.warning("Can't find end of inset at line " + str(nb))
2095             ns += 1
2096             continue
2097           if document.body[ne + 1] == "":
2098             ne += 1
2099           subst = ["\\end_layout", "", "\\begin_layout " + lay]
2100           document.body[ns:ne + 1] = subst
2101           # now we need to adjust end, in particular, but might as well
2102           # do ns properly, too
2103           newlines = (ne - ns) - len(subst)
2104           ns += newlines + 2
2105           end += newlines + 2
2106
2107         # ok, we now want to find out if the next layout is the
2108         # same as this one. if so, we will insert an extra copy of it
2109         didit = False
2110         next = find_token(document.body, "\\begin_layout", end)
2111         if next != -1:
2112           m = rx.match(document.body[next])
2113           if m:
2114             nextlay = m.group(1)
2115             if nextlay == lay:
2116               subst = ["\\begin_layout " + lay, "", "\\end_layout", ""]
2117               document.body[next:next] = subst
2118               didit = True
2119         beg = end + 1
2120         if didit:
2121           beg += 4 # for the extra layout
2122     
2123
2124 def revert_passthru(document):
2125     " http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg161298.html "
2126     if not check_passthru:
2127       return
2128     rx = re.compile("\\\\begin_layout \s*(\w+)")
2129     beg = 0
2130     for lay in ["Chunk", "Scrap"]:
2131       while True:
2132         beg = find_token(document.body, "\\begin_layout " + lay, beg)
2133         if beg == -1:
2134           break
2135         end = find_end_of_layout(document.body, beg)
2136         if end == -1:
2137           document.warning("Can't find end of layout at line " + str(beg))
2138           beg += 1
2139           continue
2140         
2141         # we now want to find out if the next layout is the
2142         # same as this one. but we will need to do this over and
2143         # over again.
2144         while True:
2145           next = find_token(document.body, "\\begin_layout", end)
2146           if next == -1:
2147             break
2148           m = rx.match(document.body[next])
2149           if not m:
2150             break
2151           nextlay = m.group(1)
2152           if nextlay != lay:
2153             break
2154           # so it is the same layout again. we now want to know if it is empty.
2155           # but first let's check and make sure there is no content between the
2156           # two layouts. i'm not sure if that can happen or not.
2157           for l in range(end + 1, next):
2158             document.warning("c'" + document.body[l] + "'")
2159             if document.body[l] != "":
2160               document.warning("Found content between adjacent " + lay + " layouts!")
2161               break
2162           nextend = find_end_of_layout(document.body, next)
2163           if nextend == -1:
2164             document.warning("Can't find end of layout at line " + str(next))
2165             break
2166           empty = True
2167           for l in range(next + 1, nextend):
2168             document.warning("e'" + document.body[l] + "'")
2169             if document.body[l] != "":
2170               empty = False
2171               break
2172           if empty:
2173             # empty layouts just get removed
2174             # should we check if it's before yet another such layout?
2175             del document.body[next : nextend + 1]
2176             # and we do not want to check again. we know the next layout
2177             # should be another Chunk and should be left as is.
2178             break
2179           else:
2180             # if it's not empty, then we want to insert a newline in place
2181             # of the layout switch
2182             subst = ["\\begin_inset Newline newline", "\\end_inset", ""]
2183             document.body[end : next + 1] = subst
2184             # and now we have to find the end of the new, larger layout
2185             newend = find_end_of_layout(document.body, beg)
2186             if newend == -1:
2187               document.warning("Can't find end of new layout at line " + str(beg))
2188               break
2189             end = newend
2190         beg = end + 1
2191
2192
2193 def revert_multirowOffset(document):
2194     " Revert multirow cells with offset in tables to TeX-code"
2195     # this routine is the same as the revert_multirow routine except that
2196     # it checks additionally for the offset
2197
2198     # first, let's find out if we need to do anything
2199     i = find_token(document.body, '<cell multirow="3" mroffset=', 0)
2200     if i == -1:
2201       return
2202
2203     add_to_preamble(document, ["\\usepackage{multirow}"])
2204
2205     rgx = re.compile(r'mroffset="[^"]+?"')
2206     begin_table = 0
2207
2208     while True:
2209         # find begin/end of table
2210         begin_table = find_token(document.body, '<lyxtabular version=', begin_table)
2211         if begin_table == -1:
2212             break
2213         end_table = find_end_of(document.body, begin_table, '<lyxtabular', '</lyxtabular>')
2214         if end_table == -1:
2215             document.warning("Malformed LyX document: Could not find end of table.")
2216             begin_table += 1
2217             continue
2218         # does this table have multirow?
2219         i = find_token(document.body, '<cell multirow="3"', begin_table, end_table)
2220         if i == -1:
2221             begin_table = end_table
2222             continue
2223         
2224         # store the number of rows and columns
2225         numrows = get_option_value(document.body[begin_table], "rows")
2226         numcols = get_option_value(document.body[begin_table], "columns")
2227         try:
2228           numrows = int(numrows)
2229           numcols = int(numcols)
2230         except:
2231           document.warning("Unable to determine rows and columns!")
2232           begin_table = end_table
2233           continue
2234
2235         mrstarts = []
2236         multirows = []
2237         # collect info on rows and columns of this table.
2238         begin_row = begin_table
2239         for row in range(numrows):
2240             begin_row = find_token(document.body, '<row>', begin_row, end_table)
2241             if begin_row == -1:
2242               document.warning("Can't find row " + str(row + 1))
2243               break
2244             end_row = find_end_of(document.body, begin_row, '<row>', '</row>')
2245             if end_row == -1:
2246               document.warning("Can't find end of row " + str(row + 1))
2247               break
2248             begin_cell = begin_row
2249             multirows.append([])
2250             for column in range(numcols):            
2251                 begin_cell = find_token(document.body, '<cell ', begin_cell, end_row)
2252                 if begin_cell == -1:
2253                   document.warning("Can't find column " + str(column + 1) + \
2254                     "in row " + str(row + 1))
2255                   break
2256                 # NOTE 
2257                 # this will fail if someone puts "</cell>" in a cell, but
2258                 # that seems fairly unlikely.
2259                 end_cell = find_end_of(document.body, begin_cell, '<cell', '</cell>')
2260                 if end_cell == -1:
2261                   document.warning("Can't find end of column " + str(column + 1) + \
2262                     "in row " + str(row + 1))
2263                   break
2264                 multirows[row].append([begin_cell, end_cell, 0])
2265                 if document.body[begin_cell].find('multirow="3" mroffset=') != -1:
2266                   multirows[row][column][2] = 3 # begin multirow
2267                   mrstarts.append([row, column])
2268                 elif document.body[begin_cell].find('multirow="4"') != -1:
2269                   multirows[row][column][2] = 4 # in multirow
2270                 begin_cell = end_cell
2271             begin_row = end_row
2272         # end of table info collection
2273
2274         # work from the back to avoid messing up numbering
2275         mrstarts.reverse()
2276         for m in mrstarts:
2277             row = m[0]
2278             col = m[1]
2279             # get column width
2280             col_width = get_option_value(document.body[begin_table + 2 + col], "width")
2281             # "0pt" means that no width is specified
2282             if not col_width or col_width == "0pt":
2283               col_width = "*"
2284             # determine the number of cells that are part of the multirow
2285             nummrs = 1
2286             for r in range(row + 1, numrows):
2287                 if multirows[r][col][2] != 4:
2288                   break
2289                 nummrs += 1
2290                 # take the opportunity to revert this line
2291                 lineno = multirows[r][col][0]
2292                 document.body[lineno] = document.body[lineno].\
2293                   replace(' multirow="4" ', ' ').\
2294                   replace('valignment="middle"', 'valignment="top"').\
2295                   replace(' topline="true" ', ' ')
2296                 # remove bottom line of previous multirow-part cell
2297                 lineno = multirows[r-1][col][0]
2298                 document.body[lineno] = document.body[lineno].replace(' bottomline="true" ', ' ')
2299             # revert beginning cell
2300             bcell = multirows[row][col][0]
2301             ecell = multirows[row][col][1]
2302             offset = get_option_value(document.body[bcell], "mroffset")
2303             document.body[bcell] = document.body[bcell].\
2304               replace(' multirow="3" ', ' ').\
2305               replace('valignment="middle"', 'valignment="top"')
2306             # remove mroffset option
2307             document.body[bcell] = rgx.sub('', document.body[bcell])
2308             
2309             blay = find_token(document.body, "\\begin_layout", bcell, ecell)
2310             if blay == -1:
2311               document.warning("Can't find layout for cell!")
2312               continue
2313             bend = find_end_of_layout(document.body, blay)
2314             if bend == -1:
2315               document.warning("Can't find end of layout for cell!")
2316               continue
2317             # do the later one first, so as not to mess up the numbering
2318             # we are wrapping the whole cell in this ert
2319             # so before the end of the layout...
2320             document.body[bend:bend] = put_cmd_in_ert("}")
2321             # ...and after the beginning
2322             document.body[blay + 1:blay + 1] = \
2323               put_cmd_in_ert("\\multirow{" + str(nummrs) + "}{" + col_width + "}[" \
2324                   + offset + "]{")
2325
2326         # on to the next table
2327         begin_table = end_table
2328
2329
2330 def revert_script(document):
2331     " Convert subscript/superscript inset to TeX code "
2332     i = 0
2333     foundsubscript = False
2334     while 1:
2335         i = find_token(document.body, '\\begin_inset script', i)
2336         if i == -1:
2337             break
2338         z = find_end_of_inset(document.body, i)
2339         if z == -1:
2340             document.warning("Malformed LyX document: Can't find end of script inset.")
2341             i += 1
2342             continue
2343         blay = find_token(document.body, "\\begin_layout", i, z)
2344         if blay == -1:
2345             document.warning("Malformed LyX document: Can't find layout in script inset.")
2346             i = z
2347             continue
2348
2349         if check_token(document.body[i], "\\begin_inset script subscript"):
2350             subst = '\\textsubscript{'
2351             foundsubscript = True
2352         elif check_token(document.body[i], "\\begin_inset script superscript"):
2353             subst = '\\textsuperscript{'
2354         else:
2355             document.warning("Malformed LyX document: Unknown type of script inset.")
2356             i = z
2357             continue
2358         bend = find_end_of_layout(document.body, blay)
2359         if bend == -1 or bend > z:
2360             document.warning("Malformed LyX document: Can't find end of layout in script inset.")
2361             i = z
2362             continue
2363         # remove the \end_layout \end_inset pair
2364         document.body[bend:z + 1] = put_cmd_in_ert("}")
2365         document.body[i:blay + 1] = put_cmd_in_ert(subst)
2366         i += 1
2367     # these classes provide a \textsubscript command:
2368     # FIXME: Would be nice if we could use the information of the .layout file here
2369     classes = ["memoir", "scrartcl", "scrbook", "scrlttr2", "scrreprt"]
2370     if foundsubscript and find_token_exact(classes, document.textclass, 0) == -1:
2371         add_to_preamble(document, ['\\usepackage{subscript}'])
2372
2373
2374 def convert_use_xetex(document):
2375     " convert \\use_xetex to \\use_non_tex_fonts "
2376     i = 0
2377     i = find_token(document.header, "\\use_xetex", 0)
2378     if i == -1:
2379         return
2380     
2381     val = get_value(document.header, "\\use_xetex", 0)
2382     document.header[i] = "\\use_non_tex_fonts " + val
2383
2384
2385 def revert_use_xetex(document):
2386     " revert \\use_non_tex_fonts to \\use_xetex "
2387     i = 0
2388     i = find_token(document.header, "\\use_non_tex_fonts", 0)
2389     if i == -1:
2390         document.warning("Malformed document. No \\use_non_tex_fonts param!")
2391         return
2392
2393     val = get_value(document.header, "\\use_non_tex_fonts", 0)
2394     document.header[i] = "\\use_xetex " + val
2395
2396
2397 def revert_labeling(document):
2398     koma = ("scrartcl", "scrarticle-beamer", "scrbook", "scrlettr",
2399         "scrlttr2", "scrreprt")
2400     if document.textclass in koma:
2401         return
2402     i = 0
2403     while True:
2404         i = find_token_exact(document.body, "\\begin_layout Labeling", i)
2405         if i == -1:
2406             return
2407         document.body[i] = "\\begin_layout List"
2408
2409
2410 def revert_langpack(document):
2411     " revert \\language_package parameter "
2412     i = 0
2413     i = find_token(document.header, "\\language_package", 0)
2414     if i == -1:
2415         document.warning("Malformed document. No \\language_package param!")
2416         return
2417
2418     del document.header[i]
2419
2420
2421 def convert_langpack(document):
2422     " Add \\language_package parameter "
2423     i = find_token(document.header, "\language" , 0)
2424     if i == -1:
2425         document.warning("Malformed document. No \\language defined!")
2426         return
2427
2428     document.header.insert(i + 1, "\\language_package default")
2429
2430
2431 def revert_tabularwidth(document):
2432   i = 0
2433   while True:
2434     i = find_token(document.body, "\\begin_inset Tabular", i)
2435     if i == -1:
2436       return
2437     j = find_end_of_inset(document.body, i)
2438     if j == -1:
2439       document.warning("Unable to find end of Tabular inset at line " + str(i))
2440       i += 1
2441       continue
2442     i += 1
2443     features = find_token(document.body, "<features", i, j)
2444     if features == -1:
2445       document.warning("Can't find any features in Tabular inset at line " + str(i))
2446       i = j
2447       continue
2448     if document.body[features].find('alignment="tabularwidth"') != -1:
2449       remove_option(document.body, features, 'tabularwidth')
2450
2451 def revert_html_css_as_file(document):
2452   if not del_token(document.header, '\\html_css_as_file', 0):
2453     document.warning("Malformed LyX document: Missing \\html_css_as_file.")
2454
2455
2456 ##
2457 # Conversion hub
2458 #
2459
2460 supported_versions = ["2.0.0","2.0"]
2461 convert = [[346, []],
2462            [347, []],
2463            [348, []],
2464            [349, []],
2465            [350, []],
2466            [351, []],
2467            [352, [convert_splitindex]],
2468            [353, []],
2469            [354, []],
2470            [355, []],
2471            [356, []],
2472            [357, []],
2473            [358, []],
2474            [359, [convert_nomencl_width]],
2475            [360, []],
2476            [361, []],
2477            [362, []],
2478            [363, []],
2479            [364, []],
2480            [365, []],
2481            [366, []],
2482            [367, []],
2483            [368, []],
2484            [369, [convert_author_id]],
2485            [370, []],
2486            [371, [convert_mhchem]],
2487            [372, []],
2488            [373, [merge_gbrief]],
2489            [374, []],
2490            [375, []],
2491            [376, []],
2492            [377, []],
2493            [378, []],
2494            [379, [convert_math_output]],
2495            [380, []],
2496            [381, []],
2497            [382, []],
2498            [383, []],
2499            [384, []],
2500            [385, []],
2501            [386, []],
2502            [387, []],
2503            [388, []],
2504            [389, [convert_html_quotes]],
2505            [390, []],
2506            [391, []],
2507            [392, []],
2508            [393, [convert_optarg]],
2509            [394, [convert_use_makebox]],
2510            [395, []],
2511            [396, []],
2512            [397, [remove_Nameref]],
2513            [398, []],
2514            [399, [convert_mathdots]],
2515            [400, [convert_rule]],
2516            [401, []],
2517            [402, [convert_bibtex_clearpage]],
2518            [403, [convert_flexnames]],
2519            [404, [convert_prettyref]],
2520            [405, []],
2521            [406, [convert_passthru]],
2522            [407, []],
2523            [408, []],
2524            [409, [convert_use_xetex]],
2525            [410, []],
2526            [411, [convert_langpack]],
2527            [412, []],
2528            [413, []]
2529 ]
2530
2531 revert =  [[412, [revert_html_css_as_file]],
2532            [411, [revert_tabularwidth]],
2533            [410, [revert_langpack]],
2534            [409, [revert_labeling]],
2535            [408, [revert_use_xetex]],
2536            [407, [revert_script]],
2537            [406, [revert_multirowOffset]],
2538            [405, [revert_passthru]],
2539            [404, []],
2540            [403, [revert_refstyle]],
2541            [402, [revert_flexnames]],
2542            [401, []],
2543            [400, [revert_diagram]],
2544            [399, [revert_rule]],
2545            [398, [revert_mathdots]],
2546            [397, [revert_mathrsfs]],
2547            [396, []],
2548            [395, [revert_nameref]],
2549            [394, [revert_DIN_C_pagesizes]],
2550            [393, [revert_makebox]],
2551            [392, [revert_argument]],
2552            [391, []],
2553            [390, [revert_align_decimal, revert_IEEEtran]],
2554            [389, [revert_output_sync]],
2555            [388, [revert_html_quotes]],
2556            [387, [revert_pagesizes]],
2557            [386, [revert_math_scale]],
2558            [385, [revert_lyx_version]],
2559            [384, [revert_shadedboxcolor]],
2560            [383, [revert_fontcolor]],
2561            [382, [revert_turkmen]],
2562            [381, [revert_notefontcolor]],
2563            [380, [revert_equalspacing_xymatrix]],
2564            [379, [revert_inset_preview]],
2565            [378, [revert_math_output]],
2566            [377, []],
2567            [376, [revert_multirow]],
2568            [375, [revert_includeall]],
2569            [374, [revert_includeonly]],
2570            [373, [revert_html_options]],
2571            [372, [revert_gbrief]],
2572            [371, [revert_fontenc]],
2573            [370, [revert_mhchem]],
2574            [369, [revert_suppress_date]],
2575            [368, [revert_author_id]],
2576            [367, [revert_hspace_glue_lengths]],
2577            [366, [revert_percent_vspace_lengths, revert_percent_hspace_lengths]],
2578            [365, [revert_percent_skip_lengths]],
2579            [364, [revert_paragraph_indentation]],
2580            [363, [revert_branch_filename]],
2581            [362, [revert_longtable_align]],
2582            [361, [revert_applemac]],
2583            [360, []],
2584            [359, [revert_nomencl_cwidth]],
2585            [358, [revert_nomencl_width]],
2586            [357, [revert_custom_processors]],
2587            [356, [revert_ulinelatex]],
2588            [355, []],
2589            [354, [revert_strikeout]],
2590            [353, [revert_printindexall]],
2591            [352, [revert_subindex]],
2592            [351, [revert_splitindex]],
2593            [350, [revert_backgroundcolor]],
2594            [349, [revert_outputformat]],
2595            [348, [revert_xetex]],
2596            [347, [revert_phantom, revert_hphantom, revert_vphantom]],
2597            [346, [revert_tabularvalign]],
2598            [345, [revert_swiss]]
2599           ]
2600
2601
2602 if __name__ == "__main__":
2603     pass