]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_0.py
Fix bug #6113: Customized font color in footnote is not rendered in LyX.
[lyx.git] / lib / lyx2lyx / lyx_2_0.py
1 # This file is part of lyx2lyx
2 # -*- coding: utf-8 -*-
3 # Copyright (C) 2008 José Matos  <jamatos@lyx.org>
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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, 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 find_token, find_end_of, find_tokens, get_value, get_value_string
26
27 ####################################################################
28 # Private helper functions
29
30 def find_end_of_inset(lines, i):
31     " Find end of inset, where lines[i] is included."
32     return find_end_of(lines, i, "\\begin_inset", "\\end_inset")
33
34
35 def add_to_preamble(document, text):
36     """ Add text to the preamble if it is not already there.
37     Only the first line is checked!"""
38
39     if find_token(document.preamble, text[0], 0) != -1:
40         return
41
42     document.preamble.extend(text)
43
44
45 def insert_to_preamble(index, document, text):
46     """ Insert text to the preamble at a given line"""
47
48     document.preamble.insert(index, text)
49
50
51 def read_unicodesymbols():
52     " Read the unicodesymbols list of unicode characters and corresponding commands."
53     pathname = os.path.abspath(os.path.dirname(sys.argv[0]))
54     fp = open(os.path.join(pathname.strip('lyx2lyx'), 'unicodesymbols'))
55     spec_chars = []
56     # Two backslashes, followed by some non-word character, and then a character
57     # in brackets. The idea is to check for constructs like: \"{u}, which is how
58     # they are written in the unicodesymbols file; but they can also be written
59     # as: \"u or even \" u.
60     r = re.compile(r'\\\\(\W)\{(\w)\}')
61     for line in fp.readlines():
62         if line[0] != '#' and line.strip() != "":
63             line=line.replace(' "',' ') # remove all quotation marks with spaces before
64             line=line.replace('" ',' ') # remove all quotation marks with spaces after
65             line=line.replace(r'\"','"') # replace \" by " (for characters with diaeresis)
66             try:
67                 [ucs4,command,dead] = line.split(None,2)
68                 if command[0:1] != "\\":
69                     continue
70                 spec_chars.append([command, unichr(eval(ucs4))])
71             except:
72                 continue
73             m = r.match(command)
74             if m != None:
75                 command = "\\\\"
76                 # If the character is a double-quote, then we need to escape it, too,
77                 # since it is done that way in the LyX file.
78                 if m.group(1) == "\"":
79                     command += "\\"
80                 commandbl = command
81                 command += m.group(1) + m.group(2)
82                 commandbl += m.group(1) + ' ' + m.group(2)
83                 spec_chars.append([command, unichr(eval(ucs4))])
84                 spec_chars.append([commandbl, unichr(eval(ucs4))])
85     fp.close()
86     return spec_chars
87
88
89 unicode_reps = read_unicodesymbols()
90
91
92 def put_cmd_in_ert(string):
93     for rep in unicode_reps:
94         string = string.replace(rep[1], rep[0].replace('\\\\', '\\'))
95     string = string.replace('\\', "\\backslash\n")
96     string = "\\begin_inset ERT\nstatus collapsed\n\\begin_layout Plain Layout\n" \
97       + string + "\n\\end_layout\n\\end_inset"
98     return string
99
100
101 def lyx2latex(document, lines):
102     'Convert some LyX stuff into corresponding LaTeX stuff, as best we can.'
103     # clean up multiline stuff
104     content = ""
105     ert_end = 0
106
107     for curline in range(len(lines)):
108       line = lines[curline]
109       if line.startswith("\\begin_inset ERT"):
110           # We don't want to replace things inside ERT, so figure out
111           # where the end of the inset is.
112           ert_end = find_end_of_inset(lines, curline + 1)
113           continue
114       elif line.startswith("\\begin_inset Formula"):
115           line = line[20:]
116       elif line.startswith("\\begin_inset Quotes"):
117           # For now, we do a very basic reversion. Someone who understands
118           # quotes is welcome to fix it up.
119           qtype = line[20:].strip()
120           # lang = qtype[0]
121           side = qtype[1]
122           dbls = qtype[2]
123           if side == "l":
124               if dbls == "d":
125                   line = "``"
126               else:
127                   line = "`"
128           else:
129               if dbls == "d":
130                   line = "''"
131               else:
132                   line = "'"
133       elif line.isspace() or \
134             line.startswith("\\begin_layout") or \
135             line.startswith("\\end_layout") or \
136             line.startswith("\\begin_inset") or \
137             line.startswith("\\end_inset") or \
138             line.startswith("\\lang") or \
139             line.strip() == "status collapsed" or \
140             line.strip() == "status open":
141           #skip all that stuff
142           continue
143
144       # this needs to be added to the preamble because of cases like
145       # \textmu, \textbackslash, etc.
146       add_to_preamble(document, ['% added by lyx2lyx for converted index entries',
147                                  '\\@ifundefined{textmu}',
148                                  ' {\\usepackage{textcomp}}{}'])
149       # a lossless reversion is not possible
150       # try at least to handle some common insets and settings
151       if ert_end >= curline:
152           line = line.replace(r'\backslash', r'\\')
153       else:
154           line = line.replace('&', '\\&{}')
155           line = line.replace('#', '\\#{}')
156           line = line.replace('^', '\\^{}')
157           line = line.replace('%', '\\%{}')
158           line = line.replace('_', '\\_{}')
159           line = line.replace('$', '\\${}')
160
161           # Do the LyX text --> LaTeX conversion
162           for rep in unicode_reps:
163             line = line.replace(rep[1], rep[0] + "{}")
164           line = line.replace(r'\backslash', r'\textbackslash{}')
165           line = line.replace(r'\series bold', r'\bfseries{}').replace(r'\series default', r'\mdseries{}')
166           line = line.replace(r'\shape italic', r'\itshape{}').replace(r'\shape smallcaps', r'\scshape{}')
167           line = line.replace(r'\shape slanted', r'\slshape{}').replace(r'\shape default', r'\upshape{}')
168           line = line.replace(r'\emph on', r'\em{}').replace(r'\emph default', r'\em{}')
169           line = line.replace(r'\noun on', r'\scshape{}').replace(r'\noun default', r'\upshape{}')
170           line = line.replace(r'\bar under', r'\underbar{').replace(r'\bar default', r'}')
171           line = line.replace(r'\family sans', r'\sffamily{}').replace(r'\family default', r'\normalfont{}')
172           line = line.replace(r'\family typewriter', r'\ttfamily{}').replace(r'\family roman', r'\rmfamily{}')
173           line = line.replace(r'\InsetSpace ', r'').replace(r'\SpecialChar ', r'')
174       content += line
175     return content
176
177
178 def latex_length(string):
179     'Convert lengths to their LaTeX representation.'
180     i = 0
181     percent = False
182     # the string has the form
183     # ValueUnit+ValueUnit-ValueUnit or
184     # ValueUnit+-ValueUnit
185     # the + and - (glue lengths) are optional
186     # the + always precedes the -
187
188     # Convert relative lengths to LaTeX units
189     units = {"text%":"\\textwidth", "col%":"\\columnwidth",
190              "page%":"\\pagewidth", "line%":"\\linewidth",
191              "theight%":"\\textheight", "pheight%":"\\pageheight"}
192     for unit in units.keys():
193         i = string.find(unit)
194         if i != -1:
195             percent = True
196             minus = string.rfind("-", 1, i)
197             plus = string.rfind("+", 0, i)
198             latex_unit = units[unit]
199             if plus == -1 and minus == -1:
200                 value = string[:i]
201                 value = str(float(value)/100)
202                 end = string[i + len(unit):]
203                 string = value + latex_unit + end
204             if plus > minus:
205                 value = string[plus+1:i]
206                 value = str(float(value)/100)
207                 begin = string[:plus+1]
208                 end = string[i+len(unit):]
209                 string = begin + value + latex_unit + end
210             if plus < minus:
211                 value = string[minus+1:i]
212                 value = str(float(value)/100)
213                 begin = string[:minus+1]
214                 string = begin + value + latex_unit
215
216     # replace + and -, but only if the - is not the first character
217     string = string[0] + string[1:].replace("+", " plus ").replace("-", " minus ")
218     # handle the case where "+-1mm" was used, because LaTeX only understands
219     # "plus 1mm minus 1mm"
220     if string.find("plus  minus"):
221         lastvaluepos = string.rfind(" ")
222         lastvalue = string[lastvaluepos:]
223         string = string.replace("  ", lastvalue + " ")
224     if percent ==  False:
225         return "False," + string
226     else:
227         return "True," + string
228         
229
230 ####################################################################
231
232
233 def revert_swiss(document):
234     " Set language german-ch to ngerman "
235     i = 0
236     if document.language == "german-ch":
237         document.language = "ngerman"
238         i = find_token(document.header, "\\language", 0)
239         if i != -1:
240             document.header[i] = "\\language ngerman"
241     j = 0
242     while True:
243         j = find_token(document.body, "\\lang german-ch", j)
244         if j == -1:
245             return
246         document.body[j] = document.body[j].replace("\\lang german-ch", "\\lang ngerman")
247         j = j + 1
248
249
250 def revert_tabularvalign(document):
251    " Revert the tabular valign option "
252    i = 0
253    while True:
254        i = find_token(document.body, "\\begin_inset Tabular", i)
255        if i == -1:
256            return
257        j = find_end_of_inset(document.body, i)
258        if j == -1:
259            document.warning("Malformed LyX document: Could not find end of tabular.")
260            i = j
261            continue
262        # don't set a box for longtables, only delete tabularvalignment
263        # the alignment is 2 lines below \\begin_inset Tabular
264        p = document.body[i+2].find("islongtable")
265        if p > -1:
266            q = document.body[i+2].find("tabularvalignment")
267            if q > -1:
268                document.body[i+2] = document.body[i+2][:q-1]
269                document.body[i+2] = document.body[i+2] + '>'
270            i = i + 1
271
272        # when no longtable
273        if p == -1:
274          tabularvalignment = 'c'
275          # which valignment is specified?
276          m = document.body[i+2].find('tabularvalignment="top"')
277          if m > -1:
278              tabularvalignment = 't'
279          m = document.body[i+2].find('tabularvalignment="bottom"')
280          if m > -1:
281              tabularvalignment = 'b'
282          # delete tabularvalignment
283          q = document.body[i+2].find("tabularvalignment")
284          if q > -1:
285              document.body[i+2] = document.body[i+2][:q-1]
286              document.body[i+2] = document.body[i+2] + '>'
287
288          # don't add a box when centered
289          if tabularvalignment == 'c':
290              i = j
291              continue
292          subst = ['\\end_layout', '\\end_inset']
293          document.body[j+1:j+1] = subst # just inserts those lines
294          subst = ['\\begin_inset Box Frameless',
295              'position "' + tabularvalignment +'"',
296              'hor_pos "c"',
297              'has_inner_box 1',
298              'inner_pos "c"',
299              'use_parbox 0',
300              # we don't know the width, assume 50%
301              'width "50col%"',
302              'special "none"',
303              'height "1in"',
304              'height_special "totalheight"',
305              'status open',
306              '',
307              '\\begin_layout Plain Layout']
308          document.body[i:i] = subst # this just inserts the array at i
309          i += len(subst) + 2 # adjust i to save a few cycles
310
311
312 def revert_phantom(document):
313     " Reverts phantom to ERT "
314     i = 0
315     j = 0
316     while True:
317       i = find_token(document.body, "\\begin_inset Phantom Phantom", i)
318       if i == -1:
319           return
320       substi = document.body[i].replace('\\begin_inset Phantom Phantom', \
321                 '\\begin_inset ERT\nstatus collapsed\n\n' \
322                 '\\begin_layout Plain Layout\n\n\n\\backslash\n' \
323                 'phantom{\n\\end_layout\n\n\\end_inset\n')
324       substi = substi.split('\n')
325       document.body[i : i+4] = substi
326       i += len(substi)
327       j = find_token(document.body, "\\end_layout", i)
328       if j == -1:
329           document.warning("Malformed LyX document: Could not find end of Phantom inset.")
330           return
331       substj = document.body[j].replace('\\end_layout', \
332                 '\\size default\n\n\\begin_inset ERT\nstatus collapsed\n\n' \
333                 '\\begin_layout Plain Layout\n\n' \
334                 '}\n\\end_layout\n\n\\end_inset\n')
335       substj = substj.split('\n')
336       document.body[j : j+4] = substj
337       i += len(substj)
338
339
340 def revert_hphantom(document):
341     " Reverts hphantom to ERT "
342     i = 0
343     j = 0
344     while True:
345       i = find_token(document.body, "\\begin_inset Phantom HPhantom", i)
346       if i == -1:
347           return
348       substi = document.body[i].replace('\\begin_inset Phantom HPhantom', \
349                 '\\begin_inset ERT\nstatus collapsed\n\n' \
350                 '\\begin_layout Plain Layout\n\n\n\\backslash\n' \
351                 'hphantom{\n\\end_layout\n\n\\end_inset\n')
352       substi = substi.split('\n')
353       document.body[i : i+4] = substi
354       i += len(substi)
355       j = find_token(document.body, "\\end_layout", i)
356       if j == -1:
357           document.warning("Malformed LyX document: Could not find end of HPhantom inset.")
358           return
359       substj = document.body[j].replace('\\end_layout', \
360                 '\\size default\n\n\\begin_inset ERT\nstatus collapsed\n\n' \
361                 '\\begin_layout Plain Layout\n\n' \
362                 '}\n\\end_layout\n\n\\end_inset\n')
363       substj = substj.split('\n')
364       document.body[j : j+4] = substj
365       i += len(substj)
366
367
368 def revert_vphantom(document):
369     " Reverts vphantom to ERT "
370     i = 0
371     j = 0
372     while True:
373       i = find_token(document.body, "\\begin_inset Phantom VPhantom", i)
374       if i == -1:
375           return
376       substi = document.body[i].replace('\\begin_inset Phantom VPhantom', \
377                 '\\begin_inset ERT\nstatus collapsed\n\n' \
378                 '\\begin_layout Plain Layout\n\n\n\\backslash\n' \
379                 'vphantom{\n\\end_layout\n\n\\end_inset\n')
380       substi = substi.split('\n')
381       document.body[i : i+4] = substi
382       i += len(substi)
383       j = find_token(document.body, "\\end_layout", i)
384       if j == -1:
385           document.warning("Malformed LyX document: Could not find end of VPhantom inset.")
386           return
387       substj = document.body[j].replace('\\end_layout', \
388                 '\\size default\n\n\\begin_inset ERT\nstatus collapsed\n\n' \
389                 '\\begin_layout Plain Layout\n\n' \
390                 '}\n\\end_layout\n\n\\end_inset\n')
391       substj = substj.split('\n')
392       document.body[j : j+4] = substj
393       i += len(substj)
394
395
396 def revert_xetex(document):
397     " Reverts documents that use XeTeX "
398     i = find_token(document.header, '\\use_xetex', 0)
399     if i == -1:
400         document.warning("Malformed LyX document: Missing \\use_xetex.")
401         return
402     if get_value(document.header, "\\use_xetex", i) == 'false':
403         del document.header[i]
404         return
405     del document.header[i]
406     # 1.) set doc encoding to utf8-plain
407     i = find_token(document.header, "\\inputencoding", 0)
408     if i == -1:
409         document.warning("Malformed LyX document: Missing \\inputencoding.")
410     document.header[i] = "\\inputencoding utf8-plain"
411     # 2.) check font settings
412     l = find_token(document.header, "\\font_roman", 0)
413     if l == -1:
414         document.warning("Malformed LyX document: Missing \\font_roman.")
415     line = document.header[l]
416     l = re.compile(r'\\font_roman (.*)$')
417     m = l.match(line)
418     roman = m.group(1)
419     l = find_token(document.header, "\\font_sans", 0)
420     if l == -1:
421         document.warning("Malformed LyX document: Missing \\font_sans.")
422     line = document.header[l]
423     l = re.compile(r'\\font_sans (.*)$')
424     m = l.match(line)
425     sans = m.group(1)
426     l = find_token(document.header, "\\font_typewriter", 0)
427     if l == -1:
428         document.warning("Malformed LyX document: Missing \\font_typewriter.")
429     line = document.header[l]
430     l = re.compile(r'\\font_typewriter (.*)$')
431     m = l.match(line)
432     typewriter = m.group(1)
433     osf = get_value(document.header, '\\font_osf', 0) == "true"
434     sf_scale = float(get_value(document.header, '\\font_sf_scale', 0))
435     tt_scale = float(get_value(document.header, '\\font_tt_scale', 0))
436     # 3.) set preamble stuff
437     pretext = '%% This document must be processed with xelatex!\n'
438     pretext += '\\usepackage{fontspec}\n'
439     if roman != "default":
440         pretext += '\\setmainfont[Mapping=tex-text]{' + roman + '}\n'
441     if sans != "default":
442         pretext += '\\setsansfont['
443         if sf_scale != 100:
444             pretext += 'Scale=' + str(sf_scale / 100) + ','
445         pretext += 'Mapping=tex-text]{' + sans + '}\n'
446     if typewriter != "default":
447         pretext += '\\setmonofont'
448         if tt_scale != 100:
449             pretext += '[Scale=' + str(tt_scale / 100) + ']'
450         pretext += '{' + typewriter + '}\n'
451     if osf:
452         pretext += '\\defaultfontfeatures{Numbers=OldStyle}\n'
453     pretext += '\usepackage{xunicode}\n'
454     pretext += '\usepackage{xltxtra}\n'
455     insert_to_preamble(0, document, pretext)
456     # 4.) reset font settings
457     i = find_token(document.header, "\\font_roman", 0)
458     if i == -1:
459         document.warning("Malformed LyX document: Missing \\font_roman.")
460     document.header[i] = "\\font_roman default"
461     i = find_token(document.header, "\\font_sans", 0)
462     if i == -1:
463         document.warning("Malformed LyX document: Missing \\font_sans.")
464     document.header[i] = "\\font_sans default"
465     i = find_token(document.header, "\\font_typewriter", 0)
466     if i == -1:
467         document.warning("Malformed LyX document: Missing \\font_typewriter.")
468     document.header[i] = "\\font_typewriter default"
469     i = find_token(document.header, "\\font_osf", 0)
470     if i == -1:
471         document.warning("Malformed LyX document: Missing \\font_osf.")
472     document.header[i] = "\\font_osf false"
473     i = find_token(document.header, "\\font_sc", 0)
474     if i == -1:
475         document.warning("Malformed LyX document: Missing \\font_sc.")
476     document.header[i] = "\\font_sc false"
477     i = find_token(document.header, "\\font_sf_scale", 0)
478     if i == -1:
479         document.warning("Malformed LyX document: Missing \\font_sf_scale.")
480     document.header[i] = "\\font_sf_scale 100"
481     i = find_token(document.header, "\\font_tt_scale", 0)
482     if i == -1:
483         document.warning("Malformed LyX document: Missing \\font_tt_scale.")
484     document.header[i] = "\\font_tt_scale 100"
485
486
487 def revert_outputformat(document):
488     " Remove default output format param "
489     i = find_token(document.header, '\\default_output_format', 0)
490     if i == -1:
491         document.warning("Malformed LyX document: Missing \\default_output_format.")
492         return
493     del document.header[i]
494
495
496 def revert_backgroundcolor(document):
497     " Reverts background color to preamble code "
498     i = 0
499     colorcode = ""
500     while True:
501       i = find_token(document.header, "\\backgroundcolor", i)
502       if i == -1:
503           return
504       colorcode = get_value(document.header, '\\backgroundcolor', 0)
505       del document.header[i]
506       # don't clutter the preamble if backgroundcolor is not set
507       if colorcode == "#ffffff":
508           continue
509       # the color code is in the form #rrggbb where every character denotes a hex number
510       # convert the string to an int
511       red = string.atoi(colorcode[1:3],16)
512       # we want the output "0.5" for the value "127" therefore add here
513       if red != 0:
514           red = red + 1
515       redout = float(red) / 256
516       green = string.atoi(colorcode[3:5],16)
517       if green != 0:
518           green = green + 1
519       greenout = float(green) / 256
520       blue = string.atoi(colorcode[5:7],16)
521       if blue != 0:
522           blue = blue + 1
523       blueout = float(blue) / 256
524       # write the preamble
525       insert_to_preamble(0, document,
526                            '% Commands inserted by lyx2lyx to set the background color\n'
527                            + '\\@ifundefined{definecolor}{\\usepackage{color}}{}\n'
528                            + '\\definecolor{page_backgroundcolor}{rgb}{'
529                            + str(redout) + ', ' + str(greenout)
530                            + ', ' + str(blueout) + '}\n'
531                            + '\\pagecolor{page_backgroundcolor}\n')
532
533
534 def revert_splitindex(document):
535     " Reverts splitindex-aware documents "
536     i = find_token(document.header, '\\use_indices', 0)
537     if i == -1:
538         document.warning("Malformed LyX document: Missing \\use_indices.")
539         return
540     indices = get_value(document.header, "\\use_indices", i)
541     preamble = ""
542     if indices == "true":
543          preamble += "\\usepackage{splitidx}\n"
544     del document.header[i]
545     i = 0
546     while True:
547         i = find_token(document.header, "\\index", i)
548         if i == -1:
549             break
550         k = find_token(document.header, "\\end_index", i)
551         if k == -1:
552             document.warning("Malformed LyX document: Missing \\end_index.")
553             return
554         line = document.header[i]
555         l = re.compile(r'\\index (.*)$')
556         m = l.match(line)
557         iname = m.group(1)
558         ishortcut = get_value(document.header, '\\shortcut', i, k)
559         if ishortcut != "" and indices == "true":
560             preamble += "\\newindex[" + iname + "]{" + ishortcut + "}\n"
561         del document.header[i:k+1]
562         i = 0
563     if preamble != "":
564         insert_to_preamble(0, document, preamble)
565     i = 0
566     while True:
567         i = find_token(document.body, "\\begin_inset Index", i)
568         if i == -1:
569             break
570         line = document.body[i]
571         l = re.compile(r'\\begin_inset Index (.*)$')
572         m = l.match(line)
573         itype = m.group(1)
574         if itype == "idx" or indices == "false":
575             document.body[i] = "\\begin_inset Index"
576         else:
577             k = find_end_of_inset(document.body, i)
578             if k == -1:
579                  return
580             content = lyx2latex(document, document.body[i:k])
581             # escape quotes
582             content = content.replace('"', r'\"')
583             subst = [put_cmd_in_ert("\\sindex[" + itype + "]{" + content + "}")]
584             document.body[i:k+1] = subst
585         i = i + 1
586     i = 0
587     while True:
588         i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
589         if i == -1:
590             return
591         k = find_end_of_inset(document.body, i)
592         ptype = get_value(document.body, 'type', i, k).strip('"')
593         if ptype == "idx":
594             j = find_token(document.body, "type", i, k)
595             del document.body[j]
596         elif indices == "false":
597             del document.body[i:k+1]
598         else:
599             subst = [put_cmd_in_ert("\\printindex[" + ptype + "]{}")]
600             document.body[i:k+1] = subst
601         i = i + 1
602
603
604 def convert_splitindex(document):
605     " Converts index and printindex insets to splitindex-aware format "
606     i = 0
607     while True:
608         i = find_token(document.body, "\\begin_inset Index", i)
609         if i == -1:
610             break
611         document.body[i] = document.body[i].replace("\\begin_inset Index",
612             "\\begin_inset Index idx")
613         i = i + 1
614     i = 0
615     while True:
616         i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
617         if i == -1:
618             return
619         if document.body[i + 1].find('LatexCommand printindex') == -1:
620             document.warning("Malformed LyX document: Incomplete printindex inset.")
621             return
622         subst = ["LatexCommand printindex", 
623             "type \"idx\""]
624         document.body[i + 1:i + 2] = subst
625         i = i + 1
626
627
628 def revert_subindex(document):
629     " Reverts \\printsubindex CommandInset types "
630     i = find_token(document.header, '\\use_indices', 0)
631     if i == -1:
632         document.warning("Malformed LyX document: Missing \\use_indices.")
633         return
634     indices = get_value(document.header, "\\use_indices", i)
635     i = 0
636     while True:
637         i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
638         if i == -1:
639             return
640         k = find_end_of_inset(document.body, i)
641         ctype = get_value(document.body, 'LatexCommand', i, k)
642         if ctype != "printsubindex":
643             i = i + 1
644             continue
645         ptype = get_value(document.body, 'type', i, k).strip('"')
646         if indices == "false":
647             del document.body[i:k+1]
648         else:
649             subst = [put_cmd_in_ert("\\printsubindex[" + ptype + "]{}")]
650             document.body[i:k+1] = subst
651         i = i + 1
652
653
654 def revert_printindexall(document):
655     " Reverts \\print[sub]index* CommandInset types "
656     i = find_token(document.header, '\\use_indices', 0)
657     if i == -1:
658         document.warning("Malformed LyX document: Missing \\use_indices.")
659         return
660     indices = get_value(document.header, "\\use_indices", i)
661     i = 0
662     while True:
663         i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
664         if i == -1:
665             return
666         k = find_end_of_inset(document.body, i)
667         ctype = get_value(document.body, 'LatexCommand', i, k)
668         if ctype != "printindex*" and ctype != "printsubindex*":
669             i = i + 1
670             continue
671         if indices == "false":
672             del document.body[i:k+1]
673         else:
674             subst = [put_cmd_in_ert("\\" + ctype + "{}")]
675             document.body[i:k+1] = subst
676         i = i + 1
677
678
679 def revert_strikeout(document):
680     " Reverts \\strikeout character style "
681     while True:
682         i = find_token(document.body, '\\strikeout', 0)
683         if i == -1:
684             return
685         del document.body[i]
686
687
688 def revert_uulinewave(document):
689     " Reverts \\uuline, and \\uwave character styles "
690     while True:
691         i = find_token(document.body, '\\uuline', 0)
692         if i == -1:
693             break
694         del document.body[i]
695     while True:
696         i = find_token(document.body, '\\uwave', 0)
697         if i == -1:
698             return
699         del document.body[i]
700
701
702 def revert_ulinelatex(document):
703     " Reverts \\uline character style "
704     i = find_token(document.body, '\\bar under', 0)
705     if i == -1:
706         return
707     insert_to_preamble(0, document,
708             '% Commands inserted by lyx2lyx for proper underlining\n'
709             + '\\PassOptionsToPackage{normalem}{ulem}\n'
710             + '\\usepackage{ulem}\n'
711             + '\\let\\cite@rig\\cite\n'
712             + '\\newcommand{\\b@xcite}[2][\\%]{\\def\\def@pt{\\%}\\def\\pas@pt{#1}\n'
713             + '  \\mbox{\\ifx\\def@pt\\pas@pt\\cite@rig{#2}\\else\\cite@rig[#1]{#2}\\fi}}\n'
714             + '\\renewcommand{\\underbar}[1]{{\\let\\cite\\b@xcite\\uline{#1}}}\n')
715
716
717 def revert_custom_processors(document):
718     " Remove bibtex_command and index_command params "
719     i = find_token(document.header, '\\bibtex_command', 0)
720     if i == -1:
721         document.warning("Malformed LyX document: Missing \\bibtex_command.")
722         return
723     del document.header[i]
724     i = find_token(document.header, '\\index_command', 0)
725     if i == -1:
726         document.warning("Malformed LyX document: Missing \\index_command.")
727         return
728     del document.header[i]
729
730
731 def convert_nomencl_width(document):
732     " Add set_width param to nomencl_print "
733     i = 0
734     while True:
735       i = find_token(document.body, "\\begin_inset CommandInset nomencl_print", i)
736       if i == -1:
737         break
738       document.body.insert(i + 2, "set_width \"none\"")
739       i = i + 1
740
741
742 def revert_nomencl_width(document):
743     " Remove set_width param from nomencl_print "
744     i = 0
745     while True:
746       i = find_token(document.body, "\\begin_inset CommandInset nomencl_print", i)
747       if i == -1:
748         break
749       j = find_end_of_inset(document.body, i)
750       l = find_token(document.body, "set_width", i, j)
751       if l == -1:
752             document.warning("Can't find set_width option for nomencl_print!")
753             i = j
754             continue
755       del document.body[l]
756       i = i + 1
757
758
759 def revert_nomencl_cwidth(document):
760     " Remove width param from nomencl_print "
761     i = 0
762     while True:
763       i = find_token(document.body, "\\begin_inset CommandInset nomencl_print", i)
764       if i == -1:
765         break
766       j = find_end_of_inset(document.body, i)
767       l = find_token(document.body, "width", i, j)
768       if l == -1:
769             document.warning("Can't find width option for nomencl_print!")
770             i = j
771             continue
772       width = get_value(document.body, "width", i, j).strip('"')
773       del document.body[l]
774       add_to_preamble(document, ["\\setlength{\\nomlabelwidth}{" + width + "}"])
775       i = i + 1
776
777
778 def revert_applemac(document):
779     " Revert applemac encoding to auto "
780     i = 0
781     if document.encoding == "applemac":
782         document.encoding = "auto"
783         i = find_token(document.header, "\\encoding", 0)
784         if i != -1:
785             document.header[i] = "\\encoding auto"
786
787
788 def revert_longtable_align(document):
789     " Remove longtable alignment setting "
790     i = 0
791     j = 0
792     while True:
793       i = find_token(document.body, "\\begin_inset Tabular", i)
794       if i == -1:
795           break
796       # the alignment is 2 lines below \\begin_inset Tabular
797       j = document.body[i+2].find("longtabularalignment")
798       if j == -1:
799           break
800       document.body[i+2] = document.body[i+2][:j-1]
801       document.body[i+2] = document.body[i+2] + '>'
802       i = i + 1
803
804
805 def revert_branch_filename(document):
806     " Remove \\filename_suffix parameter from branches "
807     i = 0
808     while True:
809         i = find_token(document.header, "\\filename_suffix", i)
810         if i == -1:
811             return
812         del document.header[i]
813
814
815 def revert_paragraph_indentation(document):
816     " Revert custom paragraph indentation to preamble code "
817     i = 0
818     while True:
819       i = find_token(document.header, "\\paragraph_indentation", i)
820       if i == -1:
821           break
822       # only remove the preamble line if default
823       # otherwise also write the value to the preamble
824       length = get_value(document.header, "\\paragraph_indentation", i)
825       if length == "default":
826           del document.header[i]
827           break
828       else:
829           # handle percent lengths
830           # latex_length returns "bool,length"
831           length = latex_length(length).split(",")[1]
832           add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
833           add_to_preamble(document, ["\\setlength{\\parindent}{" + length + "}"])
834           del document.header[i]
835       i = i + 1
836
837
838 def revert_percent_skip_lengths(document):
839     " Revert relative lengths for paragraph skip separation to preamble code "
840     i = 0
841     while True:
842       i = find_token(document.header, "\\defskip", i)
843       if i == -1:
844           break
845       length = get_value(document.header, "\\defskip", i)
846       # only revert when a custom length was set and when
847       # it used a percent length
848       if length not in ('smallskip', 'medskip', 'bigskip'):
849           # handle percent lengths
850           length = latex_length(length)
851           # latex_length returns "bool,length"
852           percent = length.split(",")[0]
853           length = length.split(",")[1]
854           if percent == "True":
855               add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
856               add_to_preamble(document, ["\\setlength{\\parskip}{" + length + "}"])
857               # set defskip to medskip as default
858               document.header[i] = "\\defskip medskip"
859       i = i + 1
860
861
862 def revert_percent_vspace_lengths(document):
863     " Revert relative VSpace lengths to ERT "
864     i = 0
865     while True:
866       i = find_token(document.body, "\\begin_inset VSpace", i)
867       if i == -1:
868           break
869       # only revert if a custom length was set and if
870       # it used a percent length
871       line = document.body[i]
872       r = re.compile(r'\\begin_inset VSpace (.*)$')
873       m = r.match(line)
874       length = m.group(1)
875       if length not in ('defskip', 'smallskip', 'medskip', 'bigskip', 'vfill'):
876           # check if the space has a star (protected space)
877           protected = (document.body[i].rfind("*") != -1)
878           if protected:
879               length = length.rstrip('*')
880           # handle percent lengths
881           length = latex_length(length)
882           # latex_length returns "bool,length"
883           percent = length.split(",")[0]
884           length = length.split(",")[1]
885           # revert the VSpace inset to ERT
886           if percent == "True":
887               if protected:
888                   subst = [put_cmd_in_ert("\\vspace*{" + length + "}")]
889               else:
890                   subst = [put_cmd_in_ert("\\vspace{" + length + "}")]
891               document.body[i:i+2] = subst
892       i = i + 1
893
894
895 def revert_percent_hspace_lengths(document):
896     " Revert relative HSpace lengths to ERT "
897     i = 0
898     while True:
899       i = find_token(document.body, "\\begin_inset space \\hspace", i)
900       if i == -1:
901           break
902       protected = (document.body[i].find("\\hspace*{}") != -1)
903       # only revert if a custom length was set and if
904       # it used a percent length
905       length = get_value(document.body, '\\length', i+1)
906       if length == '':
907           document.warning("Malformed lyx document: Missing '\\length' in Space inset.")
908           return
909       # handle percent lengths
910       length = latex_length(length)
911       # latex_length returns "bool,length"
912       percent = length.split(",")[0]
913       length = length.split(",")[1]
914       # revert the HSpace inset to ERT
915       if percent == "True":
916           if protected:
917               subst = [put_cmd_in_ert("\\hspace*{" + length + "}")]
918           else:
919               subst = [put_cmd_in_ert("\\hspace{" + length + "}")]
920           document.body[i:i+3] = subst
921       i = i + 2
922
923
924 def revert_hspace_glue_lengths(document):
925     " Revert HSpace glue lengths to ERT "
926     i = 0
927     while True:
928       i = find_token(document.body, "\\begin_inset space \\hspace", i)
929       if i == -1:
930           break
931       protected = (document.body[i].find("\\hspace*{}") != -1)
932       length = get_value(document.body, '\\length', i+1)
933       if length == '':
934           document.warning("Malformed lyx document: Missing '\\length' in Space inset.")
935           return
936       # only revert if the length contains a plus or minus at pos != 0
937       glue  = re.compile(r'.+[\+-]')
938       if glue.search(length):
939           # handle percent lengths
940           # latex_length returns "bool,length"
941           length = latex_length(length).split(",")[1]
942           # revert the HSpace inset to ERT
943           if protected:
944               subst = [put_cmd_in_ert("\\hspace*{" + length + "}")]
945           else:
946               subst = [put_cmd_in_ert("\\hspace{" + length + "}")]
947           document.body[i:i+3] = subst
948       i = i + 2
949
950 def convert_author_id(document):
951     " Add the author_id to the \\author definition and make sure 0 is not used"
952     i = 0
953     j = 1
954     while True:
955         i = find_token(document.header, "\\author", i)
956         if i == -1:
957             break
958         
959         r = re.compile(r'(\\author) (\".*\")\s?(.*)$')
960         m = r.match(document.header[i])
961         if m != None:
962             name = m.group(2)
963             
964             email = ''
965             if m.lastindex == 3:
966                 email = m.group(3)
967             document.header[i] = "\\author %i %s %s" % (j, name, email)
968         j = j + 1
969         i = i + 1
970         
971     k = 0
972     while True:
973         k = find_token(document.body, "\\change_", k)
974         if k == -1:
975             break
976
977         change = document.body[k].split(' ');
978         if len(change) == 3:
979             type = change[0]
980             author_id = int(change[1])
981             time = change[2]
982             document.body[k] = "%s %i %s" % (type, author_id + 1, time)
983         k = k + 1
984
985 def revert_author_id(document):
986     " Remove the author_id from the \\author definition "
987     i = 0
988     j = 0
989     idmap = dict()
990     while True:
991         i = find_token(document.header, "\\author", i)
992         if i == -1:
993             break
994         
995         r = re.compile(r'(\\author) (\d+) (\".*\")\s?(.*)$')
996         m = r.match(document.header[i])
997         if m != None:
998             author_id = int(m.group(2))
999             idmap[author_id] = j
1000             name = m.group(3)
1001             
1002             email = ''
1003             if m.lastindex == 4:
1004                 email = m.group(4)
1005             document.header[i] = "\\author %s %s" % (name, email)
1006         i = i + 1
1007         j = j + 1
1008
1009     k = 0
1010     while True:
1011         k = find_token(document.body, "\\change_", k)
1012         if k == -1:
1013             break
1014
1015         change = document.body[k].split(' ');
1016         if len(change) == 3:
1017             type = change[0]
1018             author_id = int(change[1])
1019             time = change[2]
1020             document.body[k] = "%s %i %s" % (type, idmap[author_id], time)
1021         k = k + 1
1022
1023
1024 def revert_suppress_date(document):
1025     " Revert suppressing of default document date to preamble code "
1026     i = 0
1027     while True:
1028       i = find_token(document.header, "\\suppress_date", i)
1029       if i == -1:
1030           break
1031       # remove the preamble line and write to the preamble
1032       # when suppress_date was true
1033       date = get_value(document.header, "\\suppress_date", i)
1034       if date == "true":
1035           add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
1036           add_to_preamble(document, ["\\date{}"])
1037       del document.header[i]
1038       i = i + 1
1039
1040
1041 def revert_mhchem(document):
1042     "Revert mhchem loading to preamble code"
1043     i = 0
1044     j = 0
1045     k = 0
1046     i = find_token(document.header, "\\use_mhchem 1", 0)
1047     if i != -1:
1048         mhchem = "auto"
1049     else:
1050         i = find_token(document.header, "\\use_mhchem 2", 0)
1051         if i != -1:
1052             mhchem = "on"
1053     if mhchem == "auto":
1054         j = find_token(document.body, "\\cf{", 0)
1055         if j != -1:
1056             mhchem = "on"
1057         else:
1058             j = find_token(document.body, "\\ce{", 0)
1059             if j != -1:
1060                 mhchem = "on"
1061     if mhchem == "on":
1062         add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
1063         add_to_preamble(document, ["\\PassOptionsToPackage{version=3}{mhchem}"])
1064         add_to_preamble(document, ["\\usepackage{mhchem}"])
1065     k = find_token(document.header, "\\use_mhchem", 0)
1066     if k == -1:
1067         document.warning("Malformed LyX document: Could not find mhchem setting.")
1068         return
1069     del document.header[k]
1070
1071
1072 ##
1073 # Conversion hub
1074 #
1075
1076 supported_versions = ["2.0.0","2.0"]
1077 convert = [[346, []],
1078            [347, []],
1079            [348, []],
1080            [349, []],
1081            [350, []],
1082            [351, []],
1083            [352, [convert_splitindex]],
1084            [353, []],
1085            [354, []],
1086            [355, []],
1087            [356, []],
1088            [357, []],
1089            [358, []],
1090            [359, [convert_nomencl_width]],
1091            [360, []],
1092            [361, []],
1093            [362, []],
1094            [363, []],
1095            [364, []],
1096            [365, []],
1097            [366, []],
1098            [367, []],
1099            [368, []],
1100            [369, [convert_author_id]],
1101            [370, []],
1102            [371, []]
1103           ]
1104
1105 revert =  [[370, [revert_mhchem]],
1106            [369, [revert_suppress_date]],
1107            [368, [revert_author_id]],
1108            [367, [revert_hspace_glue_lengths]],
1109            [366, [revert_percent_vspace_lengths, revert_percent_hspace_lengths]],
1110            [365, [revert_percent_skip_lengths]],
1111            [364, [revert_paragraph_indentation]],
1112            [363, [revert_branch_filename]],
1113            [362, [revert_longtable_align]],
1114            [361, [revert_applemac]],
1115            [360, []],
1116            [359, [revert_nomencl_cwidth]],
1117            [358, [revert_nomencl_width]],
1118            [357, [revert_custom_processors]],
1119            [356, [revert_ulinelatex]],
1120            [355, [revert_uulinewave]],
1121            [354, [revert_strikeout]],
1122            [353, [revert_printindexall]],
1123            [352, [revert_subindex]],
1124            [351, [revert_splitindex]],
1125            [350, [revert_backgroundcolor]],
1126            [349, [revert_outputformat]],
1127            [348, [revert_xetex]],
1128            [347, [revert_phantom, revert_hphantom, revert_vphantom]],
1129            [346, [revert_tabularvalign]],
1130            [345, [revert_swiss]]
1131           ]
1132
1133
1134 if __name__ == "__main__":
1135     pass