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