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