]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_0.py
d7891087b73326af8ffe321a5c0c5c255729edcb
[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(numrows)
987           document.warning("Unable to determine rows and columns!")
988           begin_table = end_table
989           continue
990
991         mrstarts = []
992         multirows = []
993         # collect info on rows and columns of this table.
994         begin_row = begin_table
995         for row in range(numrows):
996             begin_row = find_token(document.body, '<row>', begin_row, end_table)
997             if begin_row == -1:
998               document.warning("Can't find row " + str(row + 1))
999               break
1000             end_row = find_end_of(document.body, begin_row, '<row>', '</row>')
1001             if end_row == -1:
1002               document.warning("Can't find end of row " + str(row + 1))
1003               break
1004             begin_cell = begin_row
1005             multirows.append([])
1006             for column in range(numcols):            
1007                 begin_cell = find_token(document.body, '<cell ', begin_cell, end_row)
1008                 if begin_cell == -1:
1009                   document.warning("Can't find column " + str(column + 1) + \
1010                     "in row " + str(row + 1))
1011                   break
1012                 # NOTE 
1013                 # this will fail if someone puts "</cell>" in a cell, but
1014                 # that seems fairly unlikely.
1015                 end_cell = find_end_of(document.body, begin_cell, '<cell', '</cell>')
1016                 if end_cell == -1:
1017                   document.warning("Can't find end of column " + str(column + 1) + \
1018                     "in row " + str(row + 1))
1019                   break
1020                 multirows[row].append([begin_cell, end_cell, 0])
1021                 if document.body[begin_cell].find('multirow="3"') != -1:
1022                   multirows[row][column][2] = 3 # begin multirow
1023                   mrstarts.append([row, column])
1024                 elif document.body[begin_cell].find('multirow="4"') != -1:
1025                   multirows[row][column][2] = 4 # in multirow
1026                 begin_cell = end_cell
1027             begin_row = end_row
1028         # end of table info collection
1029
1030         # work from the back to avoid messing up numbering
1031         mrstarts.reverse()
1032         for m in mrstarts:
1033             row = m[0]
1034             col = m[1]
1035             # get column width
1036             col_width = get_option_value(document.body[begin_table + 2 + col], "width")
1037             # "0pt" means that no width is specified
1038             if not col_width or col_width == "0pt":
1039               col_width = "*"
1040             # determine the number of cells that are part of the multirow
1041             nummrs = 1
1042             for r in range(row + 1, numrows):
1043                 if multirows[r][col][2] != 4:
1044                   break
1045                 nummrs += 1
1046                 # take the opportunity to revert this line
1047                 lineno = multirows[r][col][0]
1048                 document.body[lineno] = document.body[lineno].\
1049                   replace(' multirow="4" ', ' ').\
1050                   replace('valignment="middle"', 'valignment="top"').\
1051                   replace(' topline="true" ', ' ')
1052                 # remove bottom line of previous multirow-part cell
1053                 lineno = multirows[r-1][col][0]
1054                 document.body[lineno] = document.body[lineno].replace(' bottomline="true" ', ' ')
1055             # revert beginning cell
1056             bcell = multirows[row][col][0]
1057             ecell = multirows[row][col][1]
1058             document.body[bcell] = document.body[bcell].\
1059               replace(' multirow="3" ', ' ').\
1060               replace('valignment="middle"', 'valignment="top"')
1061             blay = find_token(document.body, "\\begin_layout", bcell, ecell)
1062             if blay == -1:
1063               document.warning("Can't find layout for cell!")
1064               continue
1065             bend = find_end_of_layout(document.body, blay)
1066             if bend == -1:
1067               document.warning("Can't find end of layout for cell!")
1068               continue
1069             # do the later one first, so as not to mess up the numbering
1070             # we are wrapping the whole cell in this ert
1071             # so before the end of the layout...
1072             document.body[bend:bend] = put_cmd_in_ert("}")
1073             # ...and after the beginning
1074             document.body[blay + 1:blay + 1] = \
1075               put_cmd_in_ert("\\multirow{" + str(nummrs) + "}{" + col_width + "}{")
1076
1077         begin_table = end_table
1078
1079
1080 def convert_math_output(document):
1081     " Convert \html_use_mathml to \html_math_output "
1082     i = find_token(document.header, "\\html_use_mathml", 0)
1083     if i == -1:
1084         return
1085     rgx = re.compile(r'\\html_use_mathml\s+(\w+)')
1086     m = rgx.match(document.header[i])
1087     newval = "0" # MathML
1088     if m:
1089       val = str2bool(m.group(1))
1090       if not val:
1091         newval = "2" # Images
1092     else:
1093       document.warning("Can't match " + document.header[i])
1094     document.header[i] = "\\html_math_output " + newval
1095
1096
1097 def revert_math_output(document):
1098     " Revert \html_math_output to \html_use_mathml "
1099     i = find_token(document.header, "\\html_math_output", 0)
1100     if i == -1:
1101         return
1102     rgx = re.compile(r'\\html_math_output\s+(\d)')
1103     m = rgx.match(document.header[i])
1104     newval = "true"
1105     if m:
1106         val = m.group(1)
1107         if val == "1" or val == "2":
1108             newval = "false"
1109     else:
1110         document.warning("Unable to match " + document.header[i])
1111     document.header[i] = "\\html_use_mathml " + newval
1112                 
1113
1114
1115 def revert_inset_preview(document):
1116     " Dissolves the preview inset "
1117     i = 0
1118     while True:
1119       i = find_token(document.body, "\\begin_inset Preview", i)
1120       if i == -1:
1121           return
1122       iend = find_end_of_inset(document.body, i)
1123       if iend == -1:
1124           document.warning("Malformed LyX document: Could not find end of Preview inset.")
1125           i += 1
1126           continue
1127       
1128       # This has several issues.
1129       # We need to do something about the layouts inside InsetPreview.
1130       # If we just leave the first one, then we have something like:
1131       # \begin_layout Standard
1132       # ...
1133       # \begin_layout Standard
1134       # and we get a "no \end_layout" error. So something has to be done.
1135       # Ideally, we would check if it is the same as the layout we are in.
1136       # If so, we just remove it; if not, we end the active one. But it is 
1137       # not easy to know what layout we are in, due to depth changes, etc,
1138       # and it is not clear to me how much work it is worth doing. In most
1139       # cases, the layout will probably be the same.
1140       # 
1141       # For the same reason, we have to remove the \end_layout tag at the
1142       # end of the last layout in the inset. Again, that will sometimes be
1143       # wrong, but it will usually be right. To know what to do, we would
1144       # again have to know what layout the inset is in.
1145       
1146       blay = find_token(document.body, "\\begin_layout", i, iend)
1147       if blay == -1:
1148           document.warning("Can't find layout for preview inset!")
1149           # always do the later one first...
1150           del document.body[iend]
1151           del document.body[i]
1152           # deletions mean we do not need to reset i
1153           continue
1154
1155       # This is where we would check what layout we are in.
1156       # The check for Standard is definitely wrong.
1157       # 
1158       # lay = document.body[blay].split(None, 1)[1]
1159       # if lay != oldlayout:
1160       #     # record a boolean to tell us what to do later....
1161       #     # better to do it later, since (a) it won't mess up
1162       #     # the numbering and (b) we only modify at the end.
1163         
1164       # we want to delete the last \\end_layout in this inset, too.
1165       # note that this may not be the \\end_layout that goes with blay!!
1166       bend = find_end_of_layout(document.body, blay)
1167       while True:
1168           tmp = find_token(document.body, "\\end_layout", bend + 1, iend)
1169           if tmp == -1:
1170               break
1171           bend = tmp
1172       if bend == blay:
1173           document.warning("Unable to find last layout in preview inset!")
1174           del document.body[iend]
1175           del document.body[i]
1176           # deletions mean we do not need to reset i
1177           continue
1178       # always do the later one first...
1179       del document.body[iend]
1180       del document.body[bend]
1181       del document.body[i:blay + 1]
1182       # we do not need to reset i
1183                 
1184
1185 def revert_equalspacing_xymatrix(document):
1186     " Revert a Formula with xymatrix@! to an ERT inset "
1187     i = 0
1188     has_preamble = False
1189     has_equal_spacing = False
1190
1191     while True:
1192       i = find_token(document.body, "\\begin_inset Formula", i)
1193       if i == -1:
1194           break
1195       j = find_end_of_inset(document.body, i)
1196       if j == -1:
1197           document.warning("Malformed LyX document: Could not find end of Formula inset.")
1198           i += 1
1199           continue
1200       
1201       for curline in range(i,j):
1202           found = document.body[curline].find("\\xymatrix@!")
1203           if found != -1:
1204               break
1205  
1206       if found != -1:
1207           has_equal_spacing = True
1208           content = [document.body[i][21:]]
1209           content += document.body[i + 1:j]
1210           subst = put_cmd_in_ert(content)
1211           document.body[i:j + 1] = subst
1212           i += len(subst) - (j - i) + 1
1213       else:
1214           for curline in range(i,j):
1215               l = document.body[curline].find("\\xymatrix")
1216               if l != -1:
1217                   has_preamble = True;
1218                   break;
1219           i = j + 1
1220   
1221     if has_equal_spacing and not has_preamble:
1222         add_to_preamble(document, ['\\usepackage[all]{xy}'])
1223
1224
1225 def revert_notefontcolor(document):
1226     " Reverts greyed-out note font color to preamble code "
1227
1228     i = find_token(document.header, "\\notefontcolor", 0)
1229     if i == -1:
1230         return
1231
1232     colorcode = get_value(document.header, '\\notefontcolor', i)
1233     del document.header[i]
1234
1235     # are there any grey notes?
1236     if find_token(document.body, "\\begin_inset Note Greyedout", 0) == -1:
1237         # no need to do anything else, and \renewcommand will throw 
1238         # an error since lyxgreyedout will not exist.
1239         return
1240
1241     # the color code is in the form #rrggbb where every character denotes a hex number
1242     red = hex2ratio(colorcode[1:3])
1243     green = hex2ratio(colorcode[3:5])
1244     blue = hex2ratio(colorcode[5:7])
1245     # write the preamble
1246     insert_to_preamble(document,
1247       [ '%  for greyed-out notes',
1248         '\\@ifundefined{definecolor}{\\usepackage{color}}{}'
1249         '\\definecolor{note_fontcolor}{rgb}{%s,%s,%s}' % (red, green, blue),
1250         '\\renewenvironment{lyxgreyedout}',
1251         ' {\\textcolor{note_fontcolor}\\bgroup}{\\egroup}'])
1252
1253
1254 def revert_turkmen(document):
1255     "Set language Turkmen to English" 
1256
1257     if document.language == "turkmen": 
1258         document.language = "english" 
1259         i = find_token(document.header, "\\language", 0) 
1260         if i != -1: 
1261             document.header[i] = "\\language english" 
1262
1263     j = 0 
1264     while True: 
1265         j = find_token(document.body, "\\lang turkmen", j) 
1266         if j == -1: 
1267             return 
1268         document.body[j] = document.body[j].replace("\\lang turkmen", "\\lang english") 
1269         j += 1 
1270
1271
1272 def revert_fontcolor(document):
1273     " Reverts font color to preamble code "
1274     i = find_token(document.header, "\\fontcolor", 0)
1275     if i == -1:
1276         return
1277     colorcode = get_value(document.header, '\\fontcolor', i)
1278     del document.header[i]
1279     # don't clutter the preamble if font color is not set
1280     if colorcode == "#000000":
1281         return
1282     # the color code is in the form #rrggbb where every character denotes a hex number
1283     red = hex2ratio(colorcode[1:3])
1284     green = hex2ratio(colorcode[3:5])
1285     blue = hex2ratio(colorcode[5:7])
1286     # write the preamble
1287     insert_to_preamble(document,
1288       ['%  Set the font color',
1289       '\\@ifundefined{definecolor}{\\usepackage{color}}{}',
1290       '\\definecolor{document_fontcolor}{rgb}{%s,%s,%s}' % (red, green, blue),
1291       '\\color{document_fontcolor}'])
1292
1293
1294 def revert_shadedboxcolor(document):
1295     " Reverts shaded box color to preamble code "
1296     i = find_token(document.header, "\\boxbgcolor", 0)
1297     if i == -1:
1298         return
1299     colorcode = get_value(document.header, '\\boxbgcolor', i)
1300     del document.header[i]
1301     # the color code is in the form #rrggbb
1302     red = hex2ratio(colorcode[1:3])
1303     green = hex2ratio(colorcode[3:5])
1304     blue = hex2ratio(colorcode[5:7])
1305     # write the preamble
1306     insert_to_preamble(document,
1307       ['%  Set the color of boxes with shaded background',
1308       '\\@ifundefined{definecolor}{\\usepackage{color}}{}',
1309       "\\definecolor{shadecolor}{rgb}{%s,%s,%s}" % (red, green, blue)])
1310
1311
1312 def revert_lyx_version(document):
1313     " Reverts LyX Version information from Inset Info "
1314     version = "LyX version"
1315     try:
1316         import lyx2lyx_version
1317         version = lyx2lyx_version.version
1318     except:
1319         pass
1320
1321     i = 0
1322     while 1:
1323         i = find_token(document.body, '\\begin_inset Info', i)
1324         if i == -1:
1325             return
1326         j = find_end_of_inset(document.body, i + 1)
1327         if j == -1:
1328             document.warning("Malformed LyX document: Could not find end of Info inset.")
1329             i += 1
1330             continue
1331
1332         # We expect:
1333         # \begin_inset Info
1334         # type  "lyxinfo"
1335         # arg   "version"
1336         # \end_inset
1337         typ = get_quoted_value(document.body, "type", i, j)
1338         arg = get_quoted_value(document.body, "arg", i, j)
1339         if arg != "version" or typ != "lyxinfo":
1340             i = j + 1
1341             continue
1342
1343         # We do not actually know the version of LyX used to produce the document.
1344         # But we can use our version, since we are reverting.
1345         s = [version]
1346         # Now we want to check if the line after "\end_inset" is empty. It normally
1347         # is, so we want to remove it, too.
1348         lastline = j + 1
1349         if document.body[j + 1].strip() == "":
1350             lastline = j + 2
1351         document.body[i: lastline] = s
1352         i = i + 1
1353
1354
1355 def revert_math_scale(document):
1356   " Remove math scaling and LaTeX options "
1357   del_token(document.header, '\\html_math_img_scale', 0)
1358   del_token(document.header, '\\html_latex_start', 0)
1359   del_token(document.header, '\\html_latex_end', 0)
1360
1361
1362 def revert_pagesizes(document):
1363   " Revert page sizes to default "
1364   i = find_token(document.header, '\\papersize', 0)
1365   if i != -1:
1366     size = document.header[i][11:]
1367     if size == "a0paper" or size == "a1paper" or size == "a2paper" \
1368     or size == "a6paper" or size == "b0paper" or size == "b1paper" \
1369     or size == "b2paper" or size == "b6paper" or size == "b0j" \
1370     or size == "b1j" or size == "b2j" or size == "b3j" or size == "b4j" \
1371     or size == "b5j" or size == "b6j":
1372       del document.header[i]
1373
1374
1375 def revert_DIN_C_pagesizes(document):
1376   " Revert DIN C page sizes to default "
1377   i = find_token(document.header, '\\papersize', 0)
1378   if i != -1:
1379     size = document.header[i][11:]
1380     if size == "c0paper" or size == "c1paper" or size == "c2paper" \
1381     or size == "c3paper" or size == "c4paper" or size == "c5paper" \
1382     or size == "c6paper":
1383       del document.header[i]
1384
1385
1386 def convert_html_quotes(document):
1387   " Remove quotes around html_latex_start and html_latex_end "
1388
1389   i = find_token(document.header, '\\html_latex_start', 0)
1390   if i != -1:
1391     line = document.header[i]
1392     l = re.compile(r'\\html_latex_start\s+"(.*)"')
1393     m = l.match(line)
1394     if m:
1395       document.header[i] = "\\html_latex_start " + m.group(1)
1396       
1397   i = find_token(document.header, '\\html_latex_end', 0)
1398   if i != -1:
1399     line = document.header[i]
1400     l = re.compile(r'\\html_latex_end\s+"(.*)"')
1401     m = l.match(line)
1402     if m:
1403       document.header[i] = "\\html_latex_end " + m.group(1)
1404       
1405
1406 def revert_html_quotes(document):
1407   " Remove quotes around html_latex_start and html_latex_end "
1408   
1409   i = find_token(document.header, '\\html_latex_start', 0)
1410   if i != -1:
1411     line = document.header[i]
1412     l = re.compile(r'\\html_latex_start\s+(.*)')
1413     m = l.match(line)
1414     if not m:
1415         document.warning("Weird html_latex_start line: " + line)
1416         del document.header[i]
1417     else:
1418         document.header[i] = "\\html_latex_start \"" + m.group(1) + "\""
1419       
1420   i = find_token(document.header, '\\html_latex_end', 0)
1421   if i != -1:
1422     line = document.header[i]
1423     l = re.compile(r'\\html_latex_end\s+(.*)')
1424     m = l.match(line)
1425     if not m:
1426         document.warning("Weird html_latex_end line: " + line)
1427         del document.header[i]
1428     else:
1429         document.header[i] = "\\html_latex_end \"" + m.group(1) + "\""
1430
1431
1432 def revert_output_sync(document):
1433   " Remove forward search options "
1434   del_token(document.header, '\\output_sync_macro', 0)
1435   del_token(document.header, '\\output_sync', 0)
1436
1437
1438 def revert_align_decimal(document):
1439   i = 0
1440   while True:
1441     i = find_token(document.body, "\\begin_inset Tabular", i)
1442     if i == -1:
1443       return
1444     j = find_end_of_inset(document.body, i)
1445     if j == -1:
1446       document.warning("Unable to find end of Tabular inset at line " + str(i))
1447       i += 1
1448       continue
1449     cell = find_token(document.body, "<cell", i, j)
1450     if cell == -1:
1451       document.warning("Can't find any cells in Tabular inset at line " + str(i))
1452       i = j
1453       continue
1454     k = i + 1
1455     while True:
1456       k = find_token(document.body, "<column", k, cell)
1457       if k == -1:
1458         return
1459       if document.body[k].find('alignment="decimal"') == -1:
1460         k += 1
1461         continue
1462       remove_option(document.body, k, 'decimal_point')
1463       document.body[k] = \
1464         document.body[k].replace('alignment="decimal"', 'alignment="center"')
1465       k += 1
1466
1467
1468 def convert_optarg(document):
1469   " Convert \\begin_inset OptArg to \\begin_inset Argument "
1470   i = 0
1471   while 1:
1472     i = find_token(document.body, '\\begin_inset OptArg', i)
1473     if i == -1:
1474       return
1475     document.body[i] = "\\begin_inset Argument"
1476     i += 1
1477
1478
1479 def revert_argument(document):
1480   " Convert \\begin_inset Argument to \\begin_inset OptArg "
1481   i = 0
1482   while 1:
1483     i = find_token(document.body, '\\begin_inset Argument', i)
1484     if i == -1:
1485       return
1486     document.body[i] = "\\begin_inset OptArg"
1487     i += 1
1488
1489
1490 def revert_makebox(document):
1491   " Convert \\makebox to TeX code "
1492   i = 0
1493   while 1:
1494     i = find_token(document.body, '\\begin_inset Box', i)
1495     if i == -1:
1496       break
1497     z = find_end_of_inset(document.body, i)
1498     if z == -1:
1499       document.warning("Malformed LyX document: Can't find end of box inset.")
1500       i += 1
1501       continue
1502     blay = find_token(document.body, "\\begin_layout", i, z)
1503     if blay == -1:
1504       document.warning("Malformed LyX document: Can't find layout in box.")
1505       i = z
1506       continue
1507     # by looking before the layout we make sure we're actually finding
1508     # an option, not text.
1509     j = find_token(document.body, 'use_makebox', i, blay)
1510     if j == -1:
1511         i = z
1512         continue
1513     
1514     if not check_token(document.body[i], "\\begin_inset Box Frameless") \
1515       or get_value(document.body, 'use_makebox', j) != 1:
1516         del document.body[j]
1517         i = z
1518         continue
1519     bend = find_end_of_layout(document.body, blay)
1520     if bend == -1 or bend > z:
1521         document.warning("Malformed LyX document: Can't find end of layout in box.")
1522         i = z
1523         continue
1524     # determine the alignment
1525     align = get_quoted_value(document.body, 'hor_pos', i, blay, "c")
1526     # determine the width
1527     length = get_quoted_value(document.body, 'width', i, blay, "50col%")
1528     length = latex_length(length)[1]
1529     # remove the \end_layout \end_inset pair
1530     document.body[bend:z + 1] = put_cmd_in_ert("}")
1531     subst = "\\makebox[" + length + "][" \
1532       + align + "]{"
1533     document.body[i:blay + 1] = put_cmd_in_ert(subst)
1534     i += 1
1535
1536
1537 def convert_use_makebox(document):
1538   " Adds use_makebox option for boxes "
1539   i = 0
1540   while 1:
1541     i = find_token(document.body, '\\begin_inset Box', i)
1542     if i == -1:
1543       return
1544     # all of this is to make sure we actually find the use_parbox
1545     # that is an option for this box, not some text elsewhere.
1546     z = find_end_of_inset(document.body, i)
1547     if z == -1:
1548       document.warning("Can't find end of box inset!!")
1549       i += 1
1550       continue
1551     blay = find_token(document.body, "\\begin_layout", i, z)
1552     if blay == -1:
1553       document.warning("Can't find layout in box inset!!")
1554       i = z
1555       continue
1556     # so now we are looking for use_parbox before the box's layout
1557     k = find_token(document.body, 'use_parbox', i, blay)
1558     if k == -1:
1559       document.warning("Malformed LyX document: Can't find use_parbox statement in box.")
1560       i = z
1561       continue
1562     document.body.insert(k + 1, "use_makebox 0")
1563     i = blay + 1 # not z + 1 (box insets may be nested)
1564
1565
1566 def revert_IEEEtran(document):
1567   " Convert IEEEtran layouts and styles to TeX code "
1568   if document.textclass != "IEEEtran":
1569     return
1570   revert_flex_inset(document.body, "IEEE membership", "\\IEEEmembership")
1571   revert_flex_inset(document.body, "Lowercase", "\\MakeLowercase")
1572   layouts = ("Special Paper Notice", "After Title Text", "Publication ID",
1573              "Page headings", "Biography without photo")
1574   latexcmd = {"Special Paper Notice": "\\IEEEspecialpapernotice",
1575               "After Title Text":     "\\IEEEaftertitletext",
1576               "Publication ID":       "\\IEEEpubid"}
1577   obsoletedby = {"Page headings":            "MarkBoth",
1578                  "Biography without photo":  "BiographyNoPhoto"}
1579   for layout in layouts:
1580     i = 0
1581     while True:
1582         i = find_token(document.body, '\\begin_layout ' + layout, i)
1583         if i == -1:
1584           break
1585         j = find_end_of_layout(document.body, i)
1586         if j == -1:
1587           document.warning("Malformed LyX document: Can't find end of " + layout + " layout.")
1588           i += 1
1589           continue
1590         if layout in obsoletedby:
1591           document.body[i] = "\\begin_layout " + obsoletedby[layout]
1592           i = j
1593           continue
1594         content = lyx2latex(document, document.body[i:j + 1])
1595         add_to_preamble(document, [latexcmd[layout] + "{" + content + "}"])
1596         del document.body[i:j + 1]
1597         # no need to reset i
1598
1599
1600 def convert_prettyref(document):
1601         " Converts prettyref references to neutral formatted refs "
1602         re_ref = re.compile("^\s*reference\s+\"(\w+):(\S+)\"")
1603         nm_ref = re.compile("^\s*name\s+\"(\w+):(\S+)\"")
1604
1605         i = 0
1606         while True:
1607                 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1608                 if i == -1:
1609                         break
1610                 j = find_end_of_inset(document.body, i)
1611                 if j == -1:
1612                         document.warning("Malformed LyX document: No end of InsetRef!")
1613                         i += 1
1614                         continue
1615                 k = find_token(document.body, "LatexCommand prettyref", i, j)
1616                 if k != -1:
1617                         document.body[k] = "LatexCommand formatted"
1618                 i = j + 1
1619         document.header.insert(-1, "\\use_refstyle 0")
1620                 
1621  
1622 def revert_refstyle(document):
1623         " Reverts neutral formatted refs to prettyref "
1624         re_ref = re.compile("^reference\s+\"(\w+):(\S+)\"")
1625         nm_ref = re.compile("^\s*name\s+\"(\w+):(\S+)\"")
1626
1627         i = 0
1628         while True:
1629                 i = find_token(document.body, "\\begin_inset CommandInset ref", i)
1630                 if i == -1:
1631                         break
1632                 j = find_end_of_inset(document.body, i)
1633                 if j == -1:
1634                         document.warning("Malformed LyX document: No end of InsetRef")
1635                         i += 1
1636                         continue
1637                 k = find_token(document.body, "LatexCommand formatted", i, j)
1638                 if k != -1:
1639                         document.body[k] = "LatexCommand prettyref"
1640                 i = j + 1
1641         i = find_token(document.header, "\\use_refstyle", 0)
1642         if i != -1:
1643                 document.header.pop(i)
1644  
1645
1646 def revert_nameref(document):
1647   " Convert namerefs to regular references "
1648   cmds = ["Nameref", "nameref"]
1649   foundone = False
1650   rx = re.compile(r'reference "(.*)"')
1651   for cmd in cmds:
1652     i = 0
1653     oldcmd = "LatexCommand " + cmd
1654     while 1:
1655       # It seems better to look for this, as most of the reference
1656       # insets won't be ones we care about.
1657       i = find_token(document.body, oldcmd, i)
1658       if i == -1:
1659         break
1660       cmdloc = i
1661       i += 1
1662       # Make sure it is actually in an inset!
1663       # A normal line could begin with "LatexCommand nameref"!
1664       val = is_in_inset(document.body, cmdloc, \
1665           "\\begin_inset CommandInset ref")
1666       if not val:
1667           continue
1668       stins, endins = val
1669
1670       # ok, so it is in an InsetRef
1671       refline = find_token(document.body, "reference", stins, endins)
1672       if refline == -1:
1673         document.warning("Can't find reference for inset at line " + stinst + "!!")
1674         continue
1675       m = rx.match(document.body[refline])
1676       if not m:
1677         document.warning("Can't match reference line: " + document.body[ref])
1678         continue
1679       foundone = True
1680       ref = m.group(1)
1681       newcontent = put_cmd_in_ert('\\' + cmd + '{' + ref + '}')
1682       document.body[stins:endins + 1] = newcontent
1683
1684   if foundone:
1685     add_to_preamble(document, ["\usepackage{nameref}"])
1686
1687
1688 def remove_Nameref(document):
1689   " Convert Nameref commands to nameref commands "
1690   i = 0
1691   while 1:
1692     # It seems better to look for this, as most of the reference
1693     # insets won't be ones we care about.
1694     i = find_token(document.body, "LatexCommand Nameref" , i)
1695     if i == -1:
1696       break
1697     cmdloc = i
1698     i += 1
1699     
1700     # Make sure it is actually in an inset!
1701     val = is_in_inset(document.body, cmdloc, \
1702         "\\begin_inset CommandInset ref")
1703     if not val:
1704       continue
1705     document.body[cmdloc] = "LatexCommand nameref"
1706
1707
1708 def revert_mathrsfs(document):
1709     " Load mathrsfs if \mathrsfs us use in the document "
1710     i = 0
1711     for line in document.body:
1712       if line.find("\\mathscr{") != -1:
1713         add_to_preamble(document, ["\\usepackage{mathrsfs}"])
1714         return
1715
1716
1717 def convert_flexnames(document):
1718     "Convert \\begin_inset Flex Custom:Style to \\begin_inset Flex Style and similarly for CharStyle and Element."
1719     
1720     i = 0
1721     rx = re.compile(r'^\\begin_inset Flex (?:Custom|CharStyle|Element):(.+)$')
1722     while True:
1723       i = find_token(document.body, "\\begin_inset Flex", i)
1724       if i == -1:
1725         return
1726       m = rx.match(document.body[i])
1727       if m:
1728         document.body[i] = "\\begin_inset Flex " + m.group(1)
1729       i += 1
1730
1731
1732 flex_insets = {
1733   "Alert" : "CharStyle:Alert",
1734   "Code" : "CharStyle:Code",
1735   "Concepts" : "CharStyle:Concepts",
1736   "E-Mail" : "CharStyle:E-Mail",
1737   "Emph" : "CharStyle:Emph",
1738   "Expression" : "CharStyle:Expression",
1739   "Initial" : "CharStyle:Initial",
1740   "Institute" : "CharStyle:Institute",
1741   "Meaning" : "CharStyle:Meaning",
1742   "Noun" : "CharStyle:Noun",
1743   "Strong" : "CharStyle:Strong",
1744   "Structure" : "CharStyle:Structure",
1745   "ArticleMode" : "Custom:ArticleMode",
1746   "Endnote" : "Custom:Endnote",
1747   "Glosse" : "Custom:Glosse",
1748   "PresentationMode" : "Custom:PresentationMode",
1749   "Tri-Glosse" : "Custom:Tri-Glosse"
1750 }
1751
1752 flex_elements = {
1753   "Abbrev" : "Element:Abbrev",
1754   "CCC-Code" : "Element:CCC-Code",
1755   "Citation-number" : "Element:Citation-number",
1756   "City" : "Element:City",
1757   "Code" : "Element:Code",
1758   "CODEN" : "Element:CODEN",
1759   "Country" : "Element:Country",
1760   "Day" : "Element:Day",
1761   "Directory" : "Element:Directory",
1762   "Dscr" : "Element:Dscr",
1763   "Email" : "Element:Email",
1764   "Emph" : "Element:Emph",
1765   "Filename" : "Element:Filename",
1766   "Firstname" : "Element:Firstname",
1767   "Fname" : "Element:Fname",
1768   "GuiButton" : "Element:GuiButton",
1769   "GuiMenu" : "Element:GuiMenu",
1770   "GuiMenuItem" : "Element:GuiMenuItem",
1771   "ISSN" : "Element:ISSN",
1772   "Issue-day" : "Element:Issue-day",
1773   "Issue-months" : "Element:Issue-months",
1774   "Issue-number" : "Element:Issue-number",
1775   "KeyCap" : "Element:KeyCap",
1776   "KeyCombo" : "Element:KeyCombo",
1777   "Keyword" : "Element:Keyword",
1778   "Literal" : "Element:Literal",
1779   "MenuChoice" : "Element:MenuChoice",
1780   "Month" : "Element:Month",
1781   "Orgdiv" : "Element:Orgdiv",
1782   "Orgname" : "Element:Orgname",
1783   "Postcode" : "Element:Postcode",
1784   "SS-Code" : "Element:SS-Code",
1785   "SS-Title" : "Element:SS-Title",
1786   "State" : "Element:State",
1787   "Street" : "Element:Street",
1788   "Surname" : "Element:Surname",
1789   "Volume" : "Element:Volume",
1790   "Year" : "Element:Year"
1791 }
1792
1793
1794 def revert_flexnames(document):
1795   if document.backend == "latex":
1796     flexlist = flex_insets
1797   else:
1798     flexlist = flex_elements
1799   
1800   rx = re.compile(r'^\\begin_inset Flex\s+(.+)$')
1801   i = 0
1802   while True:
1803     i = find_token(document.body, "\\begin_inset Flex", i)
1804     if i == -1:
1805       return
1806     m = rx.match(document.body[i])
1807     if not m:
1808       document.warning("Illegal flex inset: " + document.body[i])
1809       i += 1
1810       continue
1811     style = m.group(1)
1812     if style in flexlist:
1813       document.body[i] = "\\begin_inset Flex " + flexlist[style]
1814     i += 1
1815
1816
1817 def convert_mathdots(document):
1818     " Load mathdots automatically "
1819     i = find_token(document.header, "\\use_mhchem" , 0)
1820     if i == -1:
1821       i = find_token(document.header, "\\use_esint" , 0)
1822     if i != -1:
1823       document.header.insert(i + 1, "\\use_mathdots 1")
1824
1825
1826 def revert_mathdots(document):
1827     " Load mathdots if used in the document "
1828
1829     mathdots = find_token(document.header, "\\use_mathdots" , 0)
1830     if mathdots == -1:
1831       document.warning("No \\use_mathdots line. Assuming auto.")
1832     else:
1833       val = get_value(document.header, "\\use_mathdots", mathdots)
1834       del document.header[mathdots]
1835       try:
1836         usedots = int(val)
1837       except:
1838         document.warning("Invalid \\use_mathdots value: " + val + ". Assuming auto.")
1839         # probably usedots has not been changed, but be safe.
1840         usedots = 1
1841
1842       if usedots == 0:
1843         # do not load case
1844         return
1845       if usedots == 2:
1846         # force load case
1847         add_to_preamble(document, ["\\usepackage{mathdots}"])
1848         return
1849     
1850     # so we are in the auto case. we want to load mathdots if \iddots is used.
1851     i = 0
1852     while True:
1853       i = find_token(document.body, '\\begin_inset Formula', i)
1854       if i == -1:
1855         return
1856       j = find_end_of_inset(document.body, i)
1857       if j == -1:
1858         document.warning("Malformed LyX document: Can't find end of Formula inset at line " + str(i))
1859         i += 1
1860         continue
1861       code = "\n".join(document.body[i:j])
1862       if code.find("\\iddots") != -1:
1863         add_to_preamble(document, ["\\@ifundefined{iddots}{\\usepackage{mathdots}}"])
1864         return
1865       i = j
1866
1867
1868 def convert_rule(document):
1869     " Convert \\lyxline to CommandInset line. "
1870     i = 0
1871     
1872     inset = ['\\begin_inset CommandInset line',
1873       'LatexCommand rule',
1874       'offset "0.5ex"',
1875       'width "100line%"',
1876       'height "1pt"', '',
1877       '\\end_inset', '', '']
1878
1879     # if paragraphs are indented, we may have to unindent to get the
1880     # line to be full-width.
1881     indent = get_value(document.header, "\\paragraph_separation", 0)
1882     have_indent = (indent == "indent")
1883
1884     while True:
1885       i = find_token(document.body, "\\lyxline" , i)
1886       if i == -1:
1887         return
1888
1889       # we need to find out if this line follows other content
1890       # in its paragraph. find its layout....
1891       lastlay = find_token_backwards(document.body, "\\begin_layout", i)
1892       if lastlay == -1:
1893         document.warning("Can't find layout for line at " + str(i))
1894         # do the best we can.
1895         document.body[i:i+1] = inset
1896         i += len(inset)
1897         continue
1898
1899       # ...and look for other content before it.
1900       lineisfirst = True
1901       for line in document.body[lastlay + 1:i]:
1902         # is it empty or a paragraph option?
1903         if not line or line[0] == '\\':
1904           continue
1905         lineisfirst = False
1906         break
1907
1908       if lineisfirst:
1909         document.body[i:i+1] = inset
1910         if indent:
1911           # we need to unindent, lest the line be too long
1912           document.body.insert(lastlay + 1, "\\noindent")
1913         i += len(inset)
1914       else:
1915         # so our line is in the middle of a paragraph
1916         # we need to add a new line, lest this line follow the
1917         # other content on that line and run off the side of the page
1918         document.body[i:i+1] = inset
1919         document.body[i:i] = ["\\begin_inset Newline newline", "\\end_inset", ""]
1920       i += len(inset)
1921
1922
1923 def revert_rule(document):
1924     " Revert line insets to Tex code "
1925     i = 0
1926     while 1:
1927       i = find_token(document.body, "\\begin_inset CommandInset line" , i)
1928       if i == -1:
1929         return
1930       # find end of inset
1931       j = find_token(document.body, "\\end_inset" , i)
1932       if j == -1:
1933         document.warning("Malformed LyX document: Can't find end of line inset.")
1934         return
1935       # determine the optional offset
1936       offset = get_quoted_value(document.body, 'offset', i, j)
1937       if offset:
1938         offset = '[' + offset + ']'
1939       # determine the width
1940       width = get_quoted_value(document.body, 'width', i, j, "100col%")
1941       width = latex_length(width)[1]
1942       # determine the height
1943       height = get_quoted_value(document.body, 'height', i, j, "1pt")
1944       height = latex_length(height)[1]
1945       # output the \rule command
1946       subst = "\\rule[" + offset + "]{" + width + "}{" + height + "}"
1947       document.body[i:j + 1] = put_cmd_in_ert(subst)
1948       i += len(subst) - (j - i)
1949
1950
1951 def revert_diagram(document):
1952   " Add the feyn package if \\Diagram is used in math "
1953   i = 0
1954   while True:
1955     i = find_token(document.body, '\\begin_inset Formula', i)
1956     if i == -1:
1957       return
1958     j = find_end_of_inset(document.body, i)
1959     if j == -1:
1960         document.warning("Malformed LyX document: Can't find end of Formula inset.")
1961         return 
1962     lines = "\n".join(document.body[i:j])
1963     if lines.find("\\Diagram") == -1:
1964       i = j
1965       continue
1966     add_to_preamble(document, ["\\usepackage{feyn}"])
1967     # only need to do it once!
1968     return
1969
1970 chapters = ("amsbook", "book", "docbook-book", "elsart", "extbook", "extreport", 
1971     "jbook", "jreport", "jsbook", "literate-book", "literate-report", "memoir", 
1972     "mwbk", "mwrep", "recipebook", "report", "scrbook", "scrreprt", "svmono", 
1973     "svmult", "tbook", "treport", "tufte-book")
1974
1975 def convert_bibtex_clearpage(document):
1976   " insert a clear(double)page bibliographystyle if bibtotoc option is used "
1977
1978   if document.textclass not in chapters:
1979     return
1980
1981   i = find_token(document.header, '\\papersides', 0)
1982   sides = 0
1983   if i == -1:
1984     document.warning("Malformed LyX document: Can't find papersides definition.")
1985     document.warning("Assuming single sided.")
1986     sides = 1
1987   else:
1988     val = get_value(document.header, "\\papersides", i)
1989     try:
1990       sides = int(val)
1991     except:
1992       pass
1993     if sides != 1 and sides != 2:
1994       document.warning("Invalid papersides value: " + val)
1995       document.warning("Assuming single sided.")
1996       sides = 1
1997
1998   j = 0
1999   while True:
2000     j = find_token(document.body, "\\begin_inset CommandInset bibtex", j)
2001     if j == -1:
2002       return
2003
2004     k = find_end_of_inset(document.body, j)
2005     if k == -1:
2006       document.warning("Can't find end of Bibliography inset at line " + str(j))
2007       j += 1
2008       continue
2009
2010     # only act if there is the option "bibtotoc"
2011     val = get_value(document.body, 'options', j, k)
2012     if not val:
2013       document.warning("Can't find options for bibliography inset at line " + str(j))
2014       j = k
2015       continue
2016     
2017     if val.find("bibtotoc") == -1:
2018       j = k
2019       continue
2020     
2021     # so we want to insert a new page right before the paragraph that
2022     # this bibliography thing is in. 
2023     lay = find_token_backwards(document.body, "\\begin_layout", j)
2024     if lay == -1:
2025       document.warning("Can't find layout containing bibliography inset at line " + str(j))
2026       j = k
2027       continue
2028
2029     if sides == 1:
2030       cmd = "clearpage"
2031     else:
2032       cmd = "cleardoublepage"
2033     subst = ['\\begin_layout Standard',
2034         '\\begin_inset Newpage ' + cmd,
2035         '\\end_inset', '', '',
2036         '\\end_layout', '']
2037     document.body[lay:lay] = subst
2038     j = k + len(subst)
2039
2040
2041 def check_passthru(document):
2042   tc = document.textclass
2043   ok = (tc == "literate-article" or tc == "literate-book" or tc == "literate-report")
2044   if not ok:
2045     mods = document.get_module_list()
2046     for mod in mods:
2047       if mod == "sweave" or mod == "noweb":
2048         ok = True
2049         break
2050   return ok
2051
2052
2053 def convert_passthru(document):
2054     " http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg161298.html "
2055     if not check_passthru:
2056       return
2057     
2058     rx = re.compile("\\\\begin_layout \s*(\w+)")
2059     beg = 0
2060     for lay in ["Chunk", "Scrap"]:
2061       while True:
2062         beg = find_token(document.body, "\\begin_layout " + lay, beg)
2063         if beg == -1:
2064           break
2065         end = find_end_of_layout(document.body, beg)
2066         if end == -1:
2067           document.warning("Can't find end of layout at line " + str(beg))
2068           beg += 1
2069           continue
2070
2071         # we are now going to replace newline insets within this layout
2072         # by new instances of this layout. so we have repeated layouts
2073         # instead of newlines.
2074
2075         # if the paragraph has any customization, however, we do not want to
2076         # do the replacement.
2077         if document.body[beg + 1].startswith("\\"):
2078           beg = end + 1
2079           continue
2080
2081         ns = beg
2082         while True:
2083           ns = find_token(document.body, "\\begin_inset Newline newline", ns, end)
2084           if ns == -1:
2085             break
2086           ne = find_end_of_inset(document.body, ns)
2087           if ne == -1 or ne > end:
2088             document.warning("Can't find end of inset at line " + str(nb))
2089             ns += 1
2090             continue
2091           if document.body[ne + 1] == "":
2092             ne += 1
2093           subst = ["\\end_layout", "", "\\begin_layout " + lay]
2094           document.body[ns:ne + 1] = subst
2095           # now we need to adjust end, in particular, but might as well
2096           # do ns properly, too
2097           newlines = (ne - ns) - len(subst)
2098           ns += newlines + 2
2099           end += newlines + 2
2100
2101         # ok, we now want to find out if the next layout is the
2102         # same as this one. if so, we will insert an extra copy of it
2103         didit = False
2104         next = find_token(document.body, "\\begin_layout", end)
2105         if next != -1:
2106           m = rx.match(document.body[next])
2107           if m:
2108             nextlay = m.group(1)
2109             if nextlay == lay:
2110               subst = ["\\begin_layout " + lay, "", "\\end_layout", ""]
2111               document.body[next:next] = subst
2112               didit = True
2113         beg = end + 1
2114         if didit:
2115           beg += 4 # for the extra layout
2116     
2117
2118 def revert_passthru(document):
2119     " http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg161298.html "
2120     if not check_passthru:
2121       return
2122     rx = re.compile("\\\\begin_layout \s*(\w+)")
2123     beg = 0
2124     for lay in ["Chunk", "Scrap"]:
2125       while True:
2126         beg = find_token(document.body, "\\begin_layout " + lay, beg)
2127         if beg == -1:
2128           break
2129         end = find_end_of_layout(document.body, beg)
2130         if end == -1:
2131           document.warning("Can't find end of layout at line " + str(beg))
2132           beg += 1
2133           continue
2134         
2135         # we now want to find out if the next layout is the
2136         # same as this one. but we will need to do this over and
2137         # over again.
2138         while True:
2139           next = find_token(document.body, "\\begin_layout", end)
2140           if next == -1:
2141             break
2142           m = rx.match(document.body[next])
2143           if not m:
2144             break
2145           nextlay = m.group(1)
2146           if nextlay != lay:
2147             break
2148           # so it is the same layout again. we now want to know if it is empty.
2149           # but first let's check and make sure there is no content between the
2150           # two layouts. i'm not sure if that can happen or not.
2151           for l in range(end + 1, next):
2152             document.warning("c'" + document.body[l] + "'")
2153             if document.body[l] != "":
2154               document.warning("Found content between adjacent " + lay + " layouts!")
2155               break
2156           nextend = find_end_of_layout(document.body, next)
2157           if nextend == -1:
2158             document.warning("Can't find end of layout at line " + str(next))
2159             break
2160           empty = True
2161           for l in range(next + 1, nextend):
2162             document.warning("e'" + document.body[l] + "'")
2163             if document.body[l] != "":
2164               empty = False
2165               break
2166           if empty:
2167             # empty layouts just get removed
2168             # should we check if it's before yet another such layout?
2169             del document.body[next : nextend + 1]
2170             # and we do not want to check again. we know the next layout
2171             # should be another Chunk and should be left as is.
2172             break
2173           else:
2174             # if it's not empty, then we want to insert a newline in place
2175             # of the layout switch
2176             subst = ["\\begin_inset Newline newline", "\\end_inset", ""]
2177             document.body[end : next + 1] = subst
2178             # and now we have to find the end of the new, larger layout
2179             newend = find_end_of_layout(document.body, beg)
2180             if newend == -1:
2181               document.warning("Can't find end of new layout at line " + str(beg))
2182               break
2183             end = newend
2184         beg = end + 1
2185
2186
2187 def revert_multirowOffset(document):
2188     " Revert multirow cells with offset in tables to TeX-code"
2189     # this routine is the same as the revert_multirow routine except that
2190     # it checks additionally for the offset
2191
2192     # first, let's find out if we need to do anything
2193     i = find_token(document.body, '<cell multirow="3" mroffset=', 0)
2194     if i == -1:
2195       return
2196
2197     add_to_preamble(document, ["\\usepackage{multirow}"])
2198
2199     rgx = re.compile(r'mroffset="[^"]+?"')
2200     begin_table = 0
2201
2202     while True:
2203         # find begin/end of table
2204         begin_table = find_token(document.body, '<lyxtabular version=', begin_table)
2205         if begin_table == -1:
2206             break
2207         end_table = find_end_of(document.body, begin_table, '<lyxtabular', '</lyxtabular>')
2208         if end_table == -1:
2209             document.warning("Malformed LyX document: Could not find end of table.")
2210             begin_table += 1
2211             continue
2212         # does this table have multirow?
2213         i = find_token(document.body, '<cell multirow="3"', begin_table, end_table)
2214         if i == -1:
2215             begin_table = end_table
2216             continue
2217         
2218         # store the number of rows and columns
2219         numrows = get_option_value(document.body[begin_table], "rows")
2220         numcols = get_option_value(document.body[begin_table], "columns")
2221         try:
2222           numrows = int(numrows)
2223           numcols = int(numcols)
2224         except:
2225           document.warning(numrows)
2226           document.warning("Unable to determine rows and columns!")
2227           begin_table = end_table
2228           continue
2229
2230         mrstarts = []
2231         multirows = []
2232         # collect info on rows and columns of this table.
2233         begin_row = begin_table
2234         for row in range(numrows):
2235             begin_row = find_token(document.body, '<row>', begin_row, end_table)
2236             if begin_row == -1:
2237               document.warning("Can't find row " + str(row + 1))
2238               break
2239             end_row = find_end_of(document.body, begin_row, '<row>', '</row>')
2240             if end_row == -1:
2241               document.warning("Can't find end of row " + str(row + 1))
2242               break
2243             begin_cell = begin_row
2244             multirows.append([])
2245             for column in range(numcols):            
2246                 begin_cell = find_token(document.body, '<cell ', begin_cell, end_row)
2247                 if begin_cell == -1:
2248                   document.warning("Can't find column " + str(column + 1) + \
2249                     "in row " + str(row + 1))
2250                   break
2251                 # NOTE 
2252                 # this will fail if someone puts "</cell>" in a cell, but
2253                 # that seems fairly unlikely.
2254                 end_cell = find_end_of(document.body, begin_cell, '<cell', '</cell>')
2255                 if end_cell == -1:
2256                   document.warning("Can't find end of column " + str(column + 1) + \
2257                     "in row " + str(row + 1))
2258                   break
2259                 multirows[row].append([begin_cell, end_cell, 0])
2260                 if document.body[begin_cell].find('multirow="3" mroffset=') != -1:
2261                   multirows[row][column][2] = 3 # begin multirow
2262                   mrstarts.append([row, column])
2263                 elif document.body[begin_cell].find('multirow="4"') != -1:
2264                   multirows[row][column][2] = 4 # in multirow
2265                 begin_cell = end_cell
2266             begin_row = end_row
2267         # end of table info collection
2268
2269         # work from the back to avoid messing up numbering
2270         mrstarts.reverse()
2271         for m in mrstarts:
2272             row = m[0]
2273             col = m[1]
2274             # get column width
2275             col_width = get_option_value(document.body[begin_table + 2 + col], "width")
2276             # "0pt" means that no width is specified
2277             if not col_width or col_width == "0pt":
2278               col_width = "*"
2279             # determine the number of cells that are part of the multirow
2280             nummrs = 1
2281             for r in range(row + 1, numrows):
2282                 if multirows[r][col][2] != 4:
2283                   break
2284                 nummrs += 1
2285                 # take the opportunity to revert this line
2286                 lineno = multirows[r][col][0]
2287                 document.body[lineno] = document.body[lineno].\
2288                   replace(' multirow="4" ', ' ').\
2289                   replace('valignment="middle"', 'valignment="top"').\
2290                   replace(' topline="true" ', ' ')
2291                 # remove bottom line of previous multirow-part cell
2292                 lineno = multirows[r-1][col][0]
2293                 document.body[lineno] = document.body[lineno].replace(' bottomline="true" ', ' ')
2294             # revert beginning cell
2295             bcell = multirows[row][col][0]
2296             ecell = multirows[row][col][1]
2297             offset = get_option_value(document.body[bcell], "mroffset")
2298             document.body[bcell] = document.body[bcell].\
2299               replace(' multirow="3" ', ' ').\
2300               replace('valignment="middle"', 'valignment="top"')
2301             # remove mroffset option
2302             document.body[bcell] = rgx.sub('', document.body[bcell])
2303             
2304             blay = find_token(document.body, "\\begin_layout", bcell, ecell)
2305             if blay == -1:
2306               document.warning("Can't find layout for cell!")
2307               continue
2308             bend = find_end_of_layout(document.body, blay)
2309             if bend == -1:
2310               document.warning("Can't find end of layout for cell!")
2311               continue
2312             # do the later one first, so as not to mess up the numbering
2313             # we are wrapping the whole cell in this ert
2314             # so before the end of the layout...
2315             document.body[bend:bend] = put_cmd_in_ert("}")
2316             # ...and after the beginning
2317             document.body[blay + 1:blay + 1] = \
2318               put_cmd_in_ert("\\multirow{" + str(nummrs) + "}{" + col_width + "}[" \
2319                   + offset + "]{")
2320
2321         # on to the next table
2322         begin_table = end_table
2323
2324
2325 def revert_script(document):
2326     " Convert subscript/superscript inset to TeX code "
2327     i = 0
2328     foundsubscript = False
2329     while 1:
2330         i = find_token(document.body, '\\begin_inset script', i)
2331         if i == -1:
2332             break
2333         z = find_end_of_inset(document.body, i)
2334         if z == -1:
2335             document.warning("Malformed LyX document: Can't find end of script inset.")
2336             i += 1
2337             continue
2338         blay = find_token(document.body, "\\begin_layout", i, z)
2339         if blay == -1:
2340             document.warning("Malformed LyX document: Can't find layout in script inset.")
2341             i = z
2342             continue
2343
2344         if check_token(document.body[i], "\\begin_inset script subscript"):
2345             subst = '\\textsubscript{'
2346             foundsubscript = True
2347         elif check_token(document.body[i], "\\begin_inset script superscript"):
2348             subst = '\\textsuperscript{'
2349         else:
2350             document.warning("Malformed LyX document: Unknown type of script inset.")
2351             i = z
2352             continue
2353         bend = find_end_of_layout(document.body, blay)
2354         if bend == -1 or bend > z:
2355             document.warning("Malformed LyX document: Can't find end of layout in script inset.")
2356             i = z
2357             continue
2358         # remove the \end_layout \end_inset pair
2359         document.body[bend:z + 1] = put_cmd_in_ert("}")
2360         document.body[i:blay + 1] = put_cmd_in_ert(subst)
2361         i += 1
2362     # these classes provide a \textsubscript command:
2363     # FIXME: Would be nice if we could use the information of the .layout file here
2364     classes = ["memoir", "scrartcl", "scrbook", "scrlttr2", "scrreprt"]
2365     if foundsubscript and find_token_exact(classes, document.textclass, 0) == -1:
2366         add_to_preamble(document, ['\\usepackage{subscript}'])
2367
2368
2369 def convert_use_xetex(document):
2370     " convert \\use_xetex to \\use_non_tex_fonts "
2371     i = 0
2372     i = find_token(document.header, "\\use_xetex", 0)
2373     if i == -1:
2374         return
2375     
2376     val = get_value(document.header, "\\use_xetex", 0)
2377     document.header[i] = "\\use_non_tex_fonts " + val
2378
2379
2380 def revert_use_xetex(document):
2381     " revert \\use_non_tex_fonts to \\use_xetex "
2382     i = 0
2383     i = find_token(document.header, "\\use_non_tex_fonts", 0)
2384     if i == -1:
2385         document.warning("Malformed document. No \\use_non_tex_fonts param!")
2386         return
2387
2388     val = get_value(document.header, "\\use_non_tex_fonts", 0)
2389     document.header[i] = "\\use_xetex " + val
2390
2391
2392 def revert_labeling(document):
2393     koma = ("scrartcl", "scrarticle-beamer", "scrbook", "scrlettr",
2394         "scrlttr2", "scrreprt")
2395     if document.textclass in koma:
2396         return
2397     i = 0
2398     while True:
2399         i = find_token_exact(document.body, "\\begin_layout Labeling", i)
2400         if i == -1:
2401             return
2402         document.body[i] = "\\begin_layout List"
2403
2404
2405 def revert_langpack(document):
2406     " revert \\language_package parameter "
2407     i = 0
2408     i = find_token(document.header, "\\language_package", 0)
2409     if i == -1:
2410         document.warning("Malformed document. No \\language_package param!")
2411         return
2412
2413     del document.header[i]
2414
2415
2416 def convert_langpack(document):
2417     " Add \\language_package parameter "
2418     i = find_token(document.header, "\language" , 0)
2419     if i == -1:
2420         document.warning("Malformed document. No \\language defined!")
2421         return
2422
2423     document.header.insert(i + 1, "\\language_package default")
2424
2425
2426 def revert_tabularwidth(document):
2427   i = 0
2428   while True:
2429     i = find_token(document.body, "\\begin_inset Tabular", i)
2430     if i == -1:
2431       return
2432     j = find_end_of_inset(document.body, i)
2433     if j == -1:
2434       document.warning("Unable to find end of Tabular inset at line " + str(i))
2435       i += 1
2436       continue
2437     i += 1
2438     features = find_token(document.body, "<features", i, j)
2439     if features == -1:
2440       document.warning("Can't find any features in Tabular inset at line " + str(i))
2441       i = j
2442       continue
2443     if document.body[features].find('alignment="tabularwidth"') != -1:
2444       remove_option(document.body, features, 'tabularwidth')
2445
2446 def revert_html_css_as_file(document):
2447   if not del_token(document.header, '\\html_css_as_file', 0):
2448     document.warning("Malformed LyX document: Missing \\html_css_as_file.")
2449
2450
2451 ##
2452 # Conversion hub
2453 #
2454
2455 supported_versions = ["2.0.0","2.0"]
2456 convert = [[346, []],
2457            [347, []],
2458            [348, []],
2459            [349, []],
2460            [350, []],
2461            [351, []],
2462            [352, [convert_splitindex]],
2463            [353, []],
2464            [354, []],
2465            [355, []],
2466            [356, []],
2467            [357, []],
2468            [358, []],
2469            [359, [convert_nomencl_width]],
2470            [360, []],
2471            [361, []],
2472            [362, []],
2473            [363, []],
2474            [364, []],
2475            [365, []],
2476            [366, []],
2477            [367, []],
2478            [368, []],
2479            [369, [convert_author_id]],
2480            [370, []],
2481            [371, [convert_mhchem]],
2482            [372, []],
2483            [373, [merge_gbrief]],
2484            [374, []],
2485            [375, []],
2486            [376, []],
2487            [377, []],
2488            [378, []],
2489            [379, [convert_math_output]],
2490            [380, []],
2491            [381, []],
2492            [382, []],
2493            [383, []],
2494            [384, []],
2495            [385, []],
2496            [386, []],
2497            [387, []],
2498            [388, []],
2499            [389, [convert_html_quotes]],
2500            [390, []],
2501            [391, []],
2502            [392, []],
2503            [393, [convert_optarg]],
2504            [394, [convert_use_makebox]],
2505            [395, []],
2506            [396, []],
2507            [397, [remove_Nameref]],
2508            [398, []],
2509            [399, [convert_mathdots]],
2510            [400, [convert_rule]],
2511            [401, []],
2512            [402, [convert_bibtex_clearpage]],
2513            [403, [convert_flexnames]],
2514            [404, [convert_prettyref]],
2515            [405, []],
2516            [406, [convert_passthru]],
2517            [407, []],
2518            [408, []],
2519            [409, [convert_use_xetex]],
2520            [410, []],
2521            [411, [convert_langpack]],
2522            [412, []],
2523            [413, []]
2524 ]
2525
2526 revert =  [[412, [revert_html_css_as_file]],
2527            [411, [revert_tabularwidth]],
2528            [410, [revert_langpack]],
2529            [409, [revert_labeling]],
2530            [408, [revert_use_xetex]],
2531            [407, [revert_script]],
2532            [406, [revert_multirowOffset]],
2533            [405, [revert_passthru]],
2534            [404, []],
2535            [403, [revert_refstyle]],
2536            [402, [revert_flexnames]],
2537            [401, []],
2538            [400, [revert_diagram]],
2539            [399, [revert_rule]],
2540            [398, [revert_mathdots]],
2541            [397, [revert_mathrsfs]],
2542            [396, []],
2543            [395, [revert_nameref]],
2544            [394, [revert_DIN_C_pagesizes]],
2545            [393, [revert_makebox]],
2546            [392, [revert_argument]],
2547            [391, []],
2548            [390, [revert_align_decimal, revert_IEEEtran]],
2549            [389, [revert_output_sync]],
2550            [388, [revert_html_quotes]],
2551            [387, [revert_pagesizes]],
2552            [386, [revert_math_scale]],
2553            [385, [revert_lyx_version]],
2554            [384, [revert_shadedboxcolor]],
2555            [383, [revert_fontcolor]],
2556            [382, [revert_turkmen]],
2557            [381, [revert_notefontcolor]],
2558            [380, [revert_equalspacing_xymatrix]],
2559            [379, [revert_inset_preview]],
2560            [378, [revert_math_output]],
2561            [377, []],
2562            [376, [revert_multirow]],
2563            [375, [revert_includeall]],
2564            [374, [revert_includeonly]],
2565            [373, [revert_html_options]],
2566            [372, [revert_gbrief]],
2567            [371, [revert_fontenc]],
2568            [370, [revert_mhchem]],
2569            [369, [revert_suppress_date]],
2570            [368, [revert_author_id]],
2571            [367, [revert_hspace_glue_lengths]],
2572            [366, [revert_percent_vspace_lengths, revert_percent_hspace_lengths]],
2573            [365, [revert_percent_skip_lengths]],
2574            [364, [revert_paragraph_indentation]],
2575            [363, [revert_branch_filename]],
2576            [362, [revert_longtable_align]],
2577            [361, [revert_applemac]],
2578            [360, []],
2579            [359, [revert_nomencl_cwidth]],
2580            [358, [revert_nomencl_width]],
2581            [357, [revert_custom_processors]],
2582            [356, [revert_ulinelatex]],
2583            [355, []],
2584            [354, [revert_strikeout]],
2585            [353, [revert_printindexall]],
2586            [352, [revert_subindex]],
2587            [351, [revert_splitindex]],
2588            [350, [revert_backgroundcolor]],
2589            [349, [revert_outputformat]],
2590            [348, [revert_xetex]],
2591            [347, [revert_phantom, revert_hphantom, revert_vphantom]],
2592            [346, [revert_tabularvalign]],
2593            [345, [revert_swiss]]
2594           ]
2595
2596
2597 if __name__ == "__main__":
2598     pass