1 # -*- coding: utf-8 -*-
2 # This file is part of lyx2lyx
3 # -*- coding: utf-8 -*-
4 # Copyright (C) 2008 José Matos <jamatos@lyx.org>
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 """ Convert files to the file format generated by lyx 2.0"""
25 import lyx2lyx_version
27 from parser_tools import find_token, find_end_of, find_tokens, get_value, get_value_string
29 ####################################################################
30 # Private helper functions
32 def find_end_of_inset(lines, i):
33 " Find end of inset, where lines[i] is included."
34 return find_end_of(lines, i, "\\begin_inset", "\\end_inset")
37 def add_to_preamble(document, text):
38 """ Add text to the preamble if it is not already there.
39 Only the first line is checked!"""
41 if find_token(document.preamble, text[0], 0) != -1:
44 document.preamble.extend(text)
47 def insert_to_preamble(index, document, text):
48 """ Insert text to the preamble at a given line"""
50 document.preamble.insert(index, text)
53 def read_unicodesymbols():
54 " Read the unicodesymbols list of unicode characters and corresponding commands."
55 pathname = os.path.abspath(os.path.dirname(sys.argv[0]))
56 fp = open(os.path.join(pathname.strip('lyx2lyx'), 'unicodesymbols'))
58 # Two backslashes, followed by some non-word character, and then a character
59 # in brackets. The idea is to check for constructs like: \"{u}, which is how
60 # they are written in the unicodesymbols file; but they can also be written
61 # as: \"u or even \" u.
62 r = re.compile(r'\\\\(\W)\{(\w)\}')
63 for line in fp.readlines():
64 if line[0] != '#' and line.strip() != "":
65 line=line.replace(' "',' ') # remove all quotation marks with spaces before
66 line=line.replace('" ',' ') # remove all quotation marks with spaces after
67 line=line.replace(r'\"','"') # replace \" by " (for characters with diaeresis)
69 [ucs4,command,dead] = line.split(None,2)
70 if command[0:1] != "\\":
72 spec_chars.append([command, unichr(eval(ucs4))])
78 # If the character is a double-quote, then we need to escape it, too,
79 # since it is done that way in the LyX file.
80 if m.group(1) == "\"":
83 command += m.group(1) + m.group(2)
84 commandbl += m.group(1) + ' ' + m.group(2)
85 spec_chars.append([command, unichr(eval(ucs4))])
86 spec_chars.append([commandbl, unichr(eval(ucs4))])
91 unicode_reps = read_unicodesymbols()
94 # DO NOT USE THIS ROUTINE ANY MORE. Better yet, replace the uses that
95 # have been made of it with uses of put_cmd_in_ert.
96 def old_put_cmd_in_ert(string):
97 for rep in unicode_reps:
98 string = string.replace(rep[1], rep[0].replace('\\\\', '\\'))
99 string = string.replace('\\', "\\backslash\n")
100 string = "\\begin_inset ERT\nstatus collapsed\n\\begin_layout Plain Layout\n" \
101 + string + "\n\\end_layout\n\\end_inset"
105 # This routine wraps some content in an ERT inset. It returns a
106 # LIST of strings. This is how lyx2lyx works: with a list of strings,
107 # each representing a line of a LyX file. Embedded newlines confuse
109 # For this same reason, we expect as input a LIST of strings, not
110 # something with embedded newlines. That said, if any of your strings
111 # do have embedded newlines, the string will eventually get split on
112 # them and you'll get a list back.
114 # A call to this routine will often go something like this:
115 # i = find_token('\\begin_inset FunkyInset', ...)
117 # j = find_end_of_inset(document.body, i)
118 # content = ...extract content from insets
119 # ert = put_cmd_in_ert(content)
120 # document.body[i:j] = ert
121 # Now, before we continue, we need to reset i appropriately. Normally,
124 # That puts us right after the ERT we just inserted.
125 def put_cmd_in_ert(strlist):
126 ret = ["\\begin_inset ERT", "status collapsed", "\\begin_layout Plain Layout\n"]
127 # Despite the warnings just given, it will be faster for us to work
128 # with a single string internally. That way, we only go through the
129 # unicode_reps loop once.
130 s = "\n".join(strlist)
131 for rep in unicode_reps:
132 s = s.replace(rep[1], rep[0].replace('\\\\', '\\'))
133 s = s.replace('\\', "\\backslash\n")
134 ret += s.splitlines()
135 ret += ["\\end_layout", "\\end_inset"]
139 def lyx2latex(document, lines):
140 'Convert some LyX stuff into corresponding LaTeX stuff, as best we can.'
141 # clean up multiline stuff
145 for curline in range(len(lines)):
146 line = lines[curline]
147 if line.startswith("\\begin_inset ERT"):
148 # We don't want to replace things inside ERT, so figure out
149 # where the end of the inset is.
150 ert_end = find_end_of_inset(lines, curline + 1)
152 elif line.startswith("\\begin_inset Formula"):
154 elif line.startswith("\\begin_inset Quotes"):
155 # For now, we do a very basic reversion. Someone who understands
156 # quotes is welcome to fix it up.
157 qtype = line[20:].strip()
171 elif line.isspace() or \
172 line.startswith("\\begin_layout") or \
173 line.startswith("\\end_layout") or \
174 line.startswith("\\begin_inset") or \
175 line.startswith("\\end_inset") or \
176 line.startswith("\\lang") or \
177 line.strip() == "status collapsed" or \
178 line.strip() == "status open":
182 # this needs to be added to the preamble because of cases like
183 # \textmu, \textbackslash, etc.
184 add_to_preamble(document, ['% added by lyx2lyx for converted index entries',
185 '\\@ifundefined{textmu}',
186 ' {\\usepackage{textcomp}}{}'])
187 # a lossless reversion is not possible
188 # try at least to handle some common insets and settings
189 if ert_end >= curline:
190 line = line.replace(r'\backslash', r'\\')
192 line = line.replace('&', '\\&{}')
193 line = line.replace('#', '\\#{}')
194 line = line.replace('^', '\\^{}')
195 line = line.replace('%', '\\%{}')
196 line = line.replace('_', '\\_{}')
197 line = line.replace('$', '\\${}')
199 # Do the LyX text --> LaTeX conversion
200 for rep in unicode_reps:
201 line = line.replace(rep[1], rep[0] + "{}")
202 line = line.replace(r'\backslash', r'\textbackslash{}')
203 line = line.replace(r'\series bold', r'\bfseries{}').replace(r'\series default', r'\mdseries{}')
204 line = line.replace(r'\shape italic', r'\itshape{}').replace(r'\shape smallcaps', r'\scshape{}')
205 line = line.replace(r'\shape slanted', r'\slshape{}').replace(r'\shape default', r'\upshape{}')
206 line = line.replace(r'\emph on', r'\em{}').replace(r'\emph default', r'\em{}')
207 line = line.replace(r'\noun on', r'\scshape{}').replace(r'\noun default', r'\upshape{}')
208 line = line.replace(r'\bar under', r'\underbar{').replace(r'\bar default', r'}')
209 line = line.replace(r'\family sans', r'\sffamily{}').replace(r'\family default', r'\normalfont{}')
210 line = line.replace(r'\family typewriter', r'\ttfamily{}').replace(r'\family roman', r'\rmfamily{}')
211 line = line.replace(r'\InsetSpace ', r'').replace(r'\SpecialChar ', r'')
216 def latex_length(string):
217 'Convert lengths to their LaTeX representation.'
220 # the string has the form
221 # ValueUnit+ValueUnit-ValueUnit or
222 # ValueUnit+-ValueUnit
223 # the + and - (glue lengths) are optional
224 # the + always precedes the -
226 # Convert relative lengths to LaTeX units
227 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
228 "page%":"\\pagewidth", "line%":"\\linewidth",
229 "theight%":"\\textheight", "pheight%":"\\pageheight"}
230 for unit in units.keys():
231 i = string.find(unit)
234 minus = string.rfind("-", 1, i)
235 plus = string.rfind("+", 0, i)
236 latex_unit = units[unit]
237 if plus == -1 and minus == -1:
239 value = str(float(value)/100)
240 end = string[i + len(unit):]
241 string = value + latex_unit + end
243 value = string[plus+1:i]
244 value = str(float(value)/100)
245 begin = string[:plus+1]
246 end = string[i+len(unit):]
247 string = begin + value + latex_unit + end
249 value = string[minus+1:i]
250 value = str(float(value)/100)
251 begin = string[:minus+1]
252 string = begin + value + latex_unit
254 # replace + and -, but only if the - is not the first character
255 string = string[0] + string[1:].replace("+", " plus ").replace("-", " minus ")
256 # handle the case where "+-1mm" was used, because LaTeX only understands
257 # "plus 1mm minus 1mm"
258 if string.find("plus minus"):
259 lastvaluepos = string.rfind(" ")
260 lastvalue = string[lastvaluepos:]
261 string = string.replace(" ", lastvalue + " ")
263 return "False," + string
265 return "True," + string
268 ####################################################################
271 def revert_swiss(document):
272 " Set language german-ch to ngerman "
274 if document.language == "german-ch":
275 document.language = "ngerman"
276 i = find_token(document.header, "\\language", 0)
278 document.header[i] = "\\language ngerman"
281 j = find_token(document.body, "\\lang german-ch", j)
284 document.body[j] = document.body[j].replace("\\lang german-ch", "\\lang ngerman")
288 def revert_tabularvalign(document):
289 " Revert the tabular valign option "
292 i = find_token(document.body, "\\begin_inset Tabular", i)
295 j = find_token(document.body, "</cell>", i)
297 document.warning("Malformed LyX document: Could not find end of tabular cell.")
300 # don't set a box for longtables, only delete tabularvalignment
301 # the alignment is 2 lines below \\begin_inset Tabular
302 p = document.body[i+2].find("islongtable")
304 q = document.body[i+2].find("tabularvalignment")
306 document.body[i+2] = document.body[i+2][:q-1]
307 document.body[i+2] = document.body[i+2] + '>'
312 tabularvalignment = 'c'
313 # which valignment is specified?
314 m = document.body[i+2].find('tabularvalignment="top"')
316 tabularvalignment = 't'
317 m = document.body[i+2].find('tabularvalignment="bottom"')
319 tabularvalignment = 'b'
320 # delete tabularvalignment
321 q = document.body[i+2].find("tabularvalignment")
323 document.body[i+2] = document.body[i+2][:q-1]
324 document.body[i+2] = document.body[i+2] + '>'
326 # don't add a box when centered
327 if tabularvalignment == 'c':
330 subst = ['\\end_layout', '\\end_inset']
331 document.body[j:j] = subst # just inserts those lines
332 subst = ['\\begin_inset Box Frameless',
333 'position "' + tabularvalignment +'"',
338 # we don't know the width, assume 50%
342 'height_special "totalheight"',
345 '\\begin_layout Plain Layout']
346 document.body[i:i] = subst # this just inserts the array at i
347 i += len(subst) + 2 # adjust i to save a few cycles
350 def revert_phantom(document):
351 " Reverts phantom to ERT "
355 i = find_token(document.body, "\\begin_inset Phantom Phantom", i)
358 substi = document.body[i].replace('\\begin_inset Phantom Phantom', \
359 '\\begin_inset ERT\nstatus collapsed\n\n' \
360 '\\begin_layout Plain Layout\n\n\n\\backslash\n' \
361 'phantom{\n\\end_layout\n\n\\end_inset\n')
362 substi = substi.split('\n')
363 document.body[i : i+4] = substi
365 j = find_token(document.body, "\\end_layout", i)
367 document.warning("Malformed LyX document: Could not find end of Phantom inset.")
369 substj = document.body[j].replace('\\end_layout', \
370 '\\size default\n\n\\begin_inset ERT\nstatus collapsed\n\n' \
371 '\\begin_layout Plain Layout\n\n' \
372 '}\n\\end_layout\n\n\\end_inset\n')
373 substj = substj.split('\n')
374 document.body[j : j+4] = substj
378 def revert_hphantom(document):
379 " Reverts hphantom to ERT "
383 i = find_token(document.body, "\\begin_inset Phantom HPhantom", i)
386 substi = document.body[i].replace('\\begin_inset Phantom HPhantom', \
387 '\\begin_inset ERT\nstatus collapsed\n\n' \
388 '\\begin_layout Plain Layout\n\n\n\\backslash\n' \
389 'hphantom{\n\\end_layout\n\n\\end_inset\n')
390 substi = substi.split('\n')
391 document.body[i : i+4] = substi
393 j = find_token(document.body, "\\end_layout", i)
395 document.warning("Malformed LyX document: Could not find end of HPhantom inset.")
397 substj = document.body[j].replace('\\end_layout', \
398 '\\size default\n\n\\begin_inset ERT\nstatus collapsed\n\n' \
399 '\\begin_layout Plain Layout\n\n' \
400 '}\n\\end_layout\n\n\\end_inset\n')
401 substj = substj.split('\n')
402 document.body[j : j+4] = substj
406 def revert_vphantom(document):
407 " Reverts vphantom to ERT "
411 i = find_token(document.body, "\\begin_inset Phantom VPhantom", i)
414 substi = document.body[i].replace('\\begin_inset Phantom VPhantom', \
415 '\\begin_inset ERT\nstatus collapsed\n\n' \
416 '\\begin_layout Plain Layout\n\n\n\\backslash\n' \
417 'vphantom{\n\\end_layout\n\n\\end_inset\n')
418 substi = substi.split('\n')
419 document.body[i : i+4] = substi
421 j = find_token(document.body, "\\end_layout", i)
423 document.warning("Malformed LyX document: Could not find end of VPhantom inset.")
425 substj = document.body[j].replace('\\end_layout', \
426 '\\size default\n\n\\begin_inset ERT\nstatus collapsed\n\n' \
427 '\\begin_layout Plain Layout\n\n' \
428 '}\n\\end_layout\n\n\\end_inset\n')
429 substj = substj.split('\n')
430 document.body[j : j+4] = substj
434 def revert_xetex(document):
435 " Reverts documents that use XeTeX "
436 i = find_token(document.header, '\\use_xetex', 0)
438 document.warning("Malformed LyX document: Missing \\use_xetex.")
440 if get_value(document.header, "\\use_xetex", i) == 'false':
441 del document.header[i]
443 del document.header[i]
444 # 1.) set doc encoding to utf8-plain
445 i = find_token(document.header, "\\inputencoding", 0)
447 document.warning("Malformed LyX document: Missing \\inputencoding.")
448 document.header[i] = "\\inputencoding utf8-plain"
449 # 2.) check font settings
450 l = find_token(document.header, "\\font_roman", 0)
452 document.warning("Malformed LyX document: Missing \\font_roman.")
453 line = document.header[l]
454 l = re.compile(r'\\font_roman (.*)$')
457 l = find_token(document.header, "\\font_sans", 0)
459 document.warning("Malformed LyX document: Missing \\font_sans.")
460 line = document.header[l]
461 l = re.compile(r'\\font_sans (.*)$')
464 l = find_token(document.header, "\\font_typewriter", 0)
466 document.warning("Malformed LyX document: Missing \\font_typewriter.")
467 line = document.header[l]
468 l = re.compile(r'\\font_typewriter (.*)$')
470 typewriter = m.group(1)
471 osf = get_value(document.header, '\\font_osf', 0) == "true"
472 sf_scale = float(get_value(document.header, '\\font_sf_scale', 0))
473 tt_scale = float(get_value(document.header, '\\font_tt_scale', 0))
474 # 3.) set preamble stuff
475 pretext = '%% This document must be processed with xelatex!\n'
476 pretext += '\\usepackage{fontspec}\n'
477 if roman != "default":
478 pretext += '\\setmainfont[Mapping=tex-text]{' + roman + '}\n'
479 if sans != "default":
480 pretext += '\\setsansfont['
482 pretext += 'Scale=' + str(sf_scale / 100) + ','
483 pretext += 'Mapping=tex-text]{' + sans + '}\n'
484 if typewriter != "default":
485 pretext += '\\setmonofont'
487 pretext += '[Scale=' + str(tt_scale / 100) + ']'
488 pretext += '{' + typewriter + '}\n'
490 pretext += '\\defaultfontfeatures{Numbers=OldStyle}\n'
491 pretext += '\usepackage{xunicode}\n'
492 pretext += '\usepackage{xltxtra}\n'
493 insert_to_preamble(0, document, pretext)
494 # 4.) reset font settings
495 i = find_token(document.header, "\\font_roman", 0)
497 document.warning("Malformed LyX document: Missing \\font_roman.")
498 document.header[i] = "\\font_roman default"
499 i = find_token(document.header, "\\font_sans", 0)
501 document.warning("Malformed LyX document: Missing \\font_sans.")
502 document.header[i] = "\\font_sans default"
503 i = find_token(document.header, "\\font_typewriter", 0)
505 document.warning("Malformed LyX document: Missing \\font_typewriter.")
506 document.header[i] = "\\font_typewriter default"
507 i = find_token(document.header, "\\font_osf", 0)
509 document.warning("Malformed LyX document: Missing \\font_osf.")
510 document.header[i] = "\\font_osf false"
511 i = find_token(document.header, "\\font_sc", 0)
513 document.warning("Malformed LyX document: Missing \\font_sc.")
514 document.header[i] = "\\font_sc false"
515 i = find_token(document.header, "\\font_sf_scale", 0)
517 document.warning("Malformed LyX document: Missing \\font_sf_scale.")
518 document.header[i] = "\\font_sf_scale 100"
519 i = find_token(document.header, "\\font_tt_scale", 0)
521 document.warning("Malformed LyX document: Missing \\font_tt_scale.")
522 document.header[i] = "\\font_tt_scale 100"
525 def revert_outputformat(document):
526 " Remove default output format param "
527 i = find_token(document.header, '\\default_output_format', 0)
529 document.warning("Malformed LyX document: Missing \\default_output_format.")
531 del document.header[i]
534 def revert_backgroundcolor(document):
535 " Reverts background color to preamble code "
539 i = find_token(document.header, "\\backgroundcolor", i)
542 colorcode = get_value(document.header, '\\backgroundcolor', 0)
543 del document.header[i]
544 # don't clutter the preamble if backgroundcolor is not set
545 if colorcode == "#ffffff":
547 # the color code is in the form #rrggbb where every character denotes a hex number
548 # convert the string to an int
549 red = string.atoi(colorcode[1:3],16)
550 # we want the output "0.5" for the value "127" therefore add here
553 redout = float(red) / 256
554 green = string.atoi(colorcode[3:5],16)
557 greenout = float(green) / 256
558 blue = string.atoi(colorcode[5:7],16)
561 blueout = float(blue) / 256
563 insert_to_preamble(0, document,
564 '% Commands inserted by lyx2lyx to set the background color\n'
565 + '\\@ifundefined{definecolor}{\\usepackage{color}}{}\n'
566 + '\\definecolor{page_backgroundcolor}{rgb}{'
567 + str(redout) + ', ' + str(greenout)
568 + ', ' + str(blueout) + '}\n'
569 + '\\pagecolor{page_backgroundcolor}\n')
572 def revert_splitindex(document):
573 " Reverts splitindex-aware documents "
574 i = find_token(document.header, '\\use_indices', 0)
576 document.warning("Malformed LyX document: Missing \\use_indices.")
578 indices = get_value(document.header, "\\use_indices", i)
580 if indices == "true":
581 preamble += "\\usepackage{splitidx}\n"
582 del document.header[i]
585 i = find_token(document.header, "\\index", i)
588 k = find_token(document.header, "\\end_index", i)
590 document.warning("Malformed LyX document: Missing \\end_index.")
592 line = document.header[i]
593 l = re.compile(r'\\index (.*)$')
596 ishortcut = get_value(document.header, '\\shortcut', i, k)
597 if ishortcut != "" and indices == "true":
598 preamble += "\\newindex[" + iname + "]{" + ishortcut + "}\n"
599 del document.header[i:k+1]
602 insert_to_preamble(0, document, preamble)
605 i = find_token(document.body, "\\begin_inset Index", i)
608 line = document.body[i]
609 l = re.compile(r'\\begin_inset Index (.*)$')
612 if itype == "idx" or indices == "false":
613 document.body[i] = "\\begin_inset Index"
615 k = find_end_of_inset(document.body, i)
618 content = lyx2latex(document, document.body[i:k])
620 content = content.replace('"', r'\"')
621 subst = [old_put_cmd_in_ert("\\sindex[" + itype + "]{" + content + "}")]
622 document.body[i:k+1] = subst
626 i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
629 k = find_end_of_inset(document.body, i)
630 ptype = get_value(document.body, 'type', i, k).strip('"')
632 j = find_token(document.body, "type", i, k)
634 elif indices == "false":
635 del document.body[i:k+1]
637 subst = [old_put_cmd_in_ert("\\printindex[" + ptype + "]{}")]
638 document.body[i:k+1] = subst
642 def convert_splitindex(document):
643 " Converts index and printindex insets to splitindex-aware format "
646 i = find_token(document.body, "\\begin_inset Index", i)
649 document.body[i] = document.body[i].replace("\\begin_inset Index",
650 "\\begin_inset Index idx")
654 i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
657 if document.body[i + 1].find('LatexCommand printindex') == -1:
658 document.warning("Malformed LyX document: Incomplete printindex inset.")
660 subst = ["LatexCommand printindex",
662 document.body[i + 1:i + 2] = subst
666 def revert_subindex(document):
667 " Reverts \\printsubindex CommandInset types "
668 i = find_token(document.header, '\\use_indices', 0)
670 document.warning("Malformed LyX document: Missing \\use_indices.")
672 indices = get_value(document.header, "\\use_indices", i)
675 i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
678 k = find_end_of_inset(document.body, i)
679 ctype = get_value(document.body, 'LatexCommand', i, k)
680 if ctype != "printsubindex":
683 ptype = get_value(document.body, 'type', i, k).strip('"')
684 if indices == "false":
685 del document.body[i:k+1]
687 subst = [old_put_cmd_in_ert("\\printsubindex[" + ptype + "]{}")]
688 document.body[i:k+1] = subst
692 def revert_printindexall(document):
693 " Reverts \\print[sub]index* CommandInset types "
694 i = find_token(document.header, '\\use_indices', 0)
696 document.warning("Malformed LyX document: Missing \\use_indices.")
698 indices = get_value(document.header, "\\use_indices", i)
701 i = find_token(document.body, "\\begin_inset CommandInset index_print", i)
704 k = find_end_of_inset(document.body, i)
705 ctype = get_value(document.body, 'LatexCommand', i, k)
706 if ctype != "printindex*" and ctype != "printsubindex*":
709 if indices == "false":
710 del document.body[i:k+1]
712 subst = [old_put_cmd_in_ert("\\" + ctype + "{}")]
713 document.body[i:k+1] = subst
717 def revert_strikeout(document):
718 " Reverts \\strikeout character style "
720 i = find_token(document.body, '\\strikeout', 0)
726 def revert_uulinewave(document):
727 " Reverts \\uuline, and \\uwave character styles "
729 i = find_token(document.body, '\\uuline', 0)
734 i = find_token(document.body, '\\uwave', 0)
740 def revert_ulinelatex(document):
741 " Reverts \\uline character style "
742 i = find_token(document.body, '\\bar under', 0)
745 insert_to_preamble(0, document,
746 '% Commands inserted by lyx2lyx for proper underlining\n'
747 + '\\PassOptionsToPackage{normalem}{ulem}\n'
748 + '\\usepackage{ulem}\n'
749 + '\\let\\cite@rig\\cite\n'
750 + '\\newcommand{\\b@xcite}[2][\\%]{\\def\\def@pt{\\%}\\def\\pas@pt{#1}\n'
751 + ' \\mbox{\\ifx\\def@pt\\pas@pt\\cite@rig{#2}\\else\\cite@rig[#1]{#2}\\fi}}\n'
752 + '\\renewcommand{\\underbar}[1]{{\\let\\cite\\b@xcite\\uline{#1}}}\n')
755 def revert_custom_processors(document):
756 " Remove bibtex_command and index_command params "
757 i = find_token(document.header, '\\bibtex_command', 0)
759 document.warning("Malformed LyX document: Missing \\bibtex_command.")
761 del document.header[i]
762 i = find_token(document.header, '\\index_command', 0)
764 document.warning("Malformed LyX document: Missing \\index_command.")
766 del document.header[i]
769 def convert_nomencl_width(document):
770 " Add set_width param to nomencl_print "
773 i = find_token(document.body, "\\begin_inset CommandInset nomencl_print", i)
776 document.body.insert(i + 2, "set_width \"none\"")
780 def revert_nomencl_width(document):
781 " Remove set_width param from nomencl_print "
784 i = find_token(document.body, "\\begin_inset CommandInset nomencl_print", i)
787 j = find_end_of_inset(document.body, i)
788 l = find_token(document.body, "set_width", i, j)
790 document.warning("Can't find set_width option for nomencl_print!")
797 def revert_nomencl_cwidth(document):
798 " Remove width param from nomencl_print "
801 i = find_token(document.body, "\\begin_inset CommandInset nomencl_print", i)
804 j = find_end_of_inset(document.body, i)
805 l = find_token(document.body, "width", i, j)
807 #Can't find width option for nomencl_print
810 width = get_value(document.body, "width", i, j).strip('"')
812 add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
813 add_to_preamble(document, ["\\setlength{\\nomlabelwidth}{" + width + "}"])
817 def revert_applemac(document):
818 " Revert applemac encoding to auto "
820 if document.encoding == "applemac":
821 document.encoding = "auto"
822 i = find_token(document.header, "\\encoding", 0)
824 document.header[i] = "\\encoding auto"
827 def revert_longtable_align(document):
828 " Remove longtable alignment setting "
832 i = find_token(document.body, "\\begin_inset Tabular", i)
835 # the alignment is 2 lines below \\begin_inset Tabular
836 j = document.body[i+2].find("longtabularalignment")
839 document.body[i+2] = document.body[i+2][:j-1]
840 document.body[i+2] = document.body[i+2] + '>'
844 def revert_branch_filename(document):
845 " Remove \\filename_suffix parameter from branches "
848 i = find_token(document.header, "\\filename_suffix", i)
851 del document.header[i]
854 def revert_paragraph_indentation(document):
855 " Revert custom paragraph indentation to preamble code "
858 i = find_token(document.header, "\\paragraph_indentation", i)
861 # only remove the preamble line if default
862 # otherwise also write the value to the preamble
863 length = get_value(document.header, "\\paragraph_indentation", i)
864 if length == "default":
865 del document.header[i]
868 # handle percent lengths
869 # latex_length returns "bool,length"
870 length = latex_length(length).split(",")[1]
871 add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
872 add_to_preamble(document, ["\\setlength{\\parindent}{" + length + "}"])
873 del document.header[i]
877 def revert_percent_skip_lengths(document):
878 " Revert relative lengths for paragraph skip separation to preamble code "
881 i = find_token(document.header, "\\defskip", i)
884 length = get_value(document.header, "\\defskip", i)
885 # only revert when a custom length was set and when
886 # it used a percent length
887 if length not in ('smallskip', 'medskip', 'bigskip'):
888 # handle percent lengths
889 length = latex_length(length)
890 # latex_length returns "bool,length"
891 percent = length.split(",")[0]
892 length = length.split(",")[1]
893 if percent == "True":
894 add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
895 add_to_preamble(document, ["\\setlength{\\parskip}{" + length + "}"])
896 # set defskip to medskip as default
897 document.header[i] = "\\defskip medskip"
901 def revert_percent_vspace_lengths(document):
902 " Revert relative VSpace lengths to ERT "
905 i = find_token(document.body, "\\begin_inset VSpace", i)
908 # only revert if a custom length was set and if
909 # it used a percent length
910 line = document.body[i]
911 r = re.compile(r'\\begin_inset VSpace (.*)$')
914 if length not in ('defskip', 'smallskip', 'medskip', 'bigskip', 'vfill'):
915 # check if the space has a star (protected space)
916 protected = (document.body[i].rfind("*") != -1)
918 length = length.rstrip('*')
919 # handle percent lengths
920 length = latex_length(length)
921 # latex_length returns "bool,length"
922 percent = length.split(",")[0]
923 length = length.split(",")[1]
924 # revert the VSpace inset to ERT
925 if percent == "True":
927 subst = [old_put_cmd_in_ert("\\vspace*{" + length + "}")]
929 subst = [old_put_cmd_in_ert("\\vspace{" + length + "}")]
930 document.body[i:i+2] = subst
934 def revert_percent_hspace_lengths(document):
935 " Revert relative HSpace lengths to ERT "
938 i = find_token(document.body, "\\begin_inset space \\hspace", i)
941 protected = (document.body[i].find("\\hspace*{}") != -1)
942 # only revert if a custom length was set and if
943 # it used a percent length
944 length = get_value(document.body, '\\length', i+1)
946 document.warning("Malformed lyx document: Missing '\\length' in Space inset.")
948 # handle percent lengths
949 length = latex_length(length)
950 # latex_length returns "bool,length"
951 percent = length.split(",")[0]
952 length = length.split(",")[1]
953 # revert the HSpace inset to ERT
954 if percent == "True":
956 subst = [old_put_cmd_in_ert("\\hspace*{" + length + "}")]
958 subst = [old_put_cmd_in_ert("\\hspace{" + length + "}")]
959 document.body[i:i+3] = subst
963 def revert_hspace_glue_lengths(document):
964 " Revert HSpace glue lengths to ERT "
967 i = find_token(document.body, "\\begin_inset space \\hspace", i)
970 protected = (document.body[i].find("\\hspace*{}") != -1)
971 length = get_value(document.body, '\\length', i+1)
973 document.warning("Malformed lyx document: Missing '\\length' in Space inset.")
975 # only revert if the length contains a plus or minus at pos != 0
976 glue = re.compile(r'.+[\+-]')
977 if glue.search(length):
978 # handle percent lengths
979 # latex_length returns "bool,length"
980 length = latex_length(length).split(",")[1]
981 # revert the HSpace inset to ERT
983 subst = [old_put_cmd_in_ert("\\hspace*{" + length + "}")]
985 subst = [old_put_cmd_in_ert("\\hspace{" + length + "}")]
986 document.body[i:i+3] = subst
989 def convert_author_id(document):
990 " Add the author_id to the \\author definition and make sure 0 is not used"
994 i = find_token(document.header, "\\author", i)
998 r = re.compile(r'(\\author) (\".*\")\s?(.*)$')
999 m = r.match(document.header[i])
1004 if m.lastindex == 3:
1006 document.header[i] = "\\author %i %s %s" % (j, name, email)
1012 k = find_token(document.body, "\\change_", k)
1016 change = document.body[k].split(' ');
1017 if len(change) == 3:
1019 author_id = int(change[1])
1021 document.body[k] = "%s %i %s" % (type, author_id + 1, time)
1024 def revert_author_id(document):
1025 " Remove the author_id from the \\author definition "
1030 i = find_token(document.header, "\\author", i)
1034 r = re.compile(r'(\\author) (\d+) (\".*\")\s?(.*)$')
1035 m = r.match(document.header[i])
1037 author_id = int(m.group(2))
1038 idmap[author_id] = j
1042 if m.lastindex == 4:
1044 document.header[i] = "\\author %s %s" % (name, email)
1050 k = find_token(document.body, "\\change_", k)
1054 change = document.body[k].split(' ');
1055 if len(change) == 3:
1057 author_id = int(change[1])
1059 document.body[k] = "%s %i %s" % (type, idmap[author_id], time)
1063 def revert_suppress_date(document):
1064 " Revert suppressing of default document date to preamble code "
1067 i = find_token(document.header, "\\suppress_date", i)
1070 # remove the preamble line and write to the preamble
1071 # when suppress_date was true
1072 date = get_value(document.header, "\\suppress_date", i)
1074 add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
1075 add_to_preamble(document, ["\\date{}"])
1076 del document.header[i]
1080 def revert_mhchem(document):
1081 "Revert mhchem loading to preamble code"
1085 i = find_token(document.header, "\\use_mhchem 1", 0)
1089 i = find_token(document.header, "\\use_mhchem 2", 0)
1092 if mhchem == "auto":
1093 j = find_token(document.body, "\\cf{", 0)
1097 j = find_token(document.body, "\\ce{", 0)
1101 add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
1102 add_to_preamble(document, ["\\PassOptionsToPackage{version=3}{mhchem}"])
1103 add_to_preamble(document, ["\\usepackage{mhchem}"])
1104 k = find_token(document.header, "\\use_mhchem", 0)
1106 document.warning("Malformed LyX document: Could not find mhchem setting.")
1108 del document.header[k]
1111 def revert_fontenc(document):
1112 " Remove fontencoding param "
1113 i = find_token(document.header, '\\fontencoding', 0)
1115 document.warning("Malformed LyX document: Missing \\fontencoding.")
1117 del document.header[i]
1120 def merge_gbrief(document):
1121 " Merge g-brief-en and g-brief-de to one class "
1123 if document.textclass != "g-brief-de":
1124 if document.textclass == "g-brief-en":
1125 document.textclass = "g-brief"
1126 document.set_textclass()
1129 obsoletedby = { "Brieftext": "Letter",
1130 "Unterschrift": "Signature",
1131 "Strasse": "Street",
1132 "Zusatz": "Addition",
1135 "RetourAdresse": "ReturnAddress",
1136 "MeinZeichen": "MyRef",
1137 "IhrZeichen": "YourRef",
1138 "IhrSchreiben": "YourMail",
1141 "Konto": "BankAccount",
1142 "Postvermerk": "PostalComment",
1143 "Adresse": "Address",
1145 "Betreff": "Reference",
1146 "Anrede": "Opening",
1152 i = find_token(document.body, "\\begin_layout", i)
1156 layout = document.body[i][14:]
1157 if layout in obsoletedby:
1158 document.body[i] = "\\begin_layout " + obsoletedby[layout]
1162 document.textclass = "g-brief"
1163 document.set_textclass()
1166 def revert_gbrief(document):
1167 " Revert g-brief to g-brief-en "
1168 if document.textclass == "g-brief":
1169 document.textclass = "g-brief-en"
1170 document.set_textclass()
1173 def revert_html_options(document):
1174 " Remove html options "
1175 i = find_token(document.header, '\\html_use_mathml', 0)
1177 del document.header[i]
1178 i = find_token(document.header, '\\html_be_strict', 0)
1180 del document.header[i]
1183 def revert_includeonly(document):
1186 i = find_token(document.header, "\\begin_includeonly", i)
1189 j = find_end_of(document.header, i, "\\begin_includeonly", "\\end_includeonly")
1191 # this should not happen
1193 document.header[i : j + 1] = []
1196 def revert_includeall(document):
1197 " Remove maintain_unincluded_children param "
1198 i = find_token(document.header, '\\maintain_unincluded_children', 0)
1200 del document.header[i]
1203 def revert_multirow(document):
1204 " Revert multirow cells in tables "
1208 # cell type 3 is multirow begin cell
1209 i = find_token(document.body, '<cell multirow="3"', i)
1212 # a multirow cell was found
1214 # remove the multirow tag, set the valignment to top
1215 # and remove the bottom line
1216 document.body[i] = document.body[i].replace(' multirow="3" ', ' ')
1217 document.body[i] = document.body[i].replace('valignment="middle"', 'valignment="top"')
1218 document.body[i] = document.body[i].replace(' bottomline="true" ', ' ')
1219 # write ERT to create the multirow cell
1220 # use 2 rows and 2cm as default with because the multirow span
1221 # and the column width is only hardly accessible
1222 subst = [old_put_cmd_in_ert("\\multirow{2}{2cm}{")]
1223 document.body[i + 4:i + 4] = subst
1224 i = find_token(document.body, "</cell>", i)
1226 document.warning("Malformed LyX document: Could not find end of tabular cell.")
1228 subst = [old_put_cmd_in_ert("}")]
1229 document.body[i - 3:i - 3] = subst
1230 # cell type 4 is multirow part cell
1231 i = find_token(document.body, '<cell multirow="4"', i)
1234 # remove the multirow tag, set the valignment to top
1235 # and remove the top line
1236 document.body[i] = document.body[i].replace(' multirow="4" ', ' ')
1237 document.body[i] = document.body[i].replace('valignment="middle"', 'valignment="top"')
1238 document.body[i] = document.body[i].replace(' topline="true" ', ' ')
1240 if multirow == True:
1241 add_to_preamble(document, ["% this command was inserted by lyx2lyx"])
1242 add_to_preamble(document, ["\\usepackage{multirow}"])
1245 def convert_math_output(document):
1246 " Convert \html_use_mathml to \html_math_output "
1247 i = find_token(document.header, "\\html_use_mathml", 0)
1250 rgx = re.compile(r'\\html_use_mathml\s+(\w+)')
1251 m = rgx.match(document.header[i])
1253 newval = "0" # MathML
1256 newval = "2" # Images
1257 document.header[i] = "\\html_math_output " + newval
1260 def revert_math_output(document):
1261 " Revert \html_math_output to \html_use_mathml "
1262 i = find_token(document.header, "\\html_math_output", 0)
1265 rgx = re.compile(r'\\html_math_output\s+(\d)')
1266 m = rgx.match(document.header[i])
1270 if val == "1" or val == "2":
1273 document.warning("Unable to match " + document.header[i])
1274 document.header[i] = "\\html_use_mathml " + newval
1278 def revert_inset_preview(document):
1279 " Dissolves the preview inset "
1284 i = find_token(document.body, "\\begin_inset Preview", i)
1287 j = find_end_of_inset(document.body, i)
1289 document.warning("Malformed LyX document: Could not find end of Preview inset.")
1291 #If the layout is Standard we need to remove it, otherwise there
1292 #will be paragraph breaks that shouldn't be there.
1293 k = find_token(document.body, "\\begin_layout Standard", i)
1295 del document.body[i : i+3]
1296 del document.body[j-5 : j-2]
1299 del document.body[i]
1300 del document.body[j-1]
1304 def revert_equalspacing_xymatrix(document):
1305 " Revert a Formula with xymatrix@! to an ERT inset "
1308 has_preamble = False
1309 has_equal_spacing = False
1312 i = find_token(document.body, "\\begin_inset Formula", i)
1315 j = find_end_of_inset(document.body, i)
1317 document.warning("Malformed LyX document: Could not find end of Formula inset.")
1320 for curline in range(i,j):
1321 found = document.body[curline].find("\\xymatrix@!")
1326 has_equal_spacing = True
1327 content = [document.body[i][21:]]
1328 content += document.body[i+1:j]
1329 subst = put_cmd_in_ert(content)
1330 document.body[i:j+1] = subst
1333 for curline in range(i,j):
1334 l = document.body[curline].find("\\xymatrix")
1336 has_preamble = True;
1339 if has_equal_spacing and not has_preamble:
1340 add_to_preamble(document, ['\\usepackage[all]{xy}'])
1343 def revert_notefontcolor(document):
1344 " Reverts greyed-out note font color to preamble code "
1348 i = find_token(document.header, "\\notefontcolor", i)
1351 colorcode = get_value(document.header, '\\notefontcolor', 0)
1352 del document.header[i]
1353 # the color code is in the form #rrggbb where every character denotes a hex number
1354 # convert the string to an int
1355 red = string.atoi(colorcode[1:3],16)
1356 # we want the output "0.5" for the value "127" therefore increment here
1359 redout = float(red) / 256
1360 green = string.atoi(colorcode[3:5],16)
1363 greenout = float(green) / 256
1364 blue = string.atoi(colorcode[5:7],16)
1367 blueout = float(blue) / 256
1368 # write the preamble
1369 insert_to_preamble(0, document,
1370 '% Commands inserted by lyx2lyx to set the font color\n'
1371 '% for greyed-out notes\n'
1372 + '\\@ifundefined{definecolor}{\\usepackage{color}}{}\n'
1373 + '\\definecolor{note_fontcolor}{rgb}{'
1374 + str(redout) + ', ' + str(greenout)
1375 + ', ' + str(blueout) + '}\n'
1376 + '\\renewenvironment{lyxgreyedout}\n'
1377 + ' {\\textcolor{note_fontcolor}\\bgroup}{\\egroup}\n')
1380 def revert_turkmen(document):
1381 "Set language Turkmen to English"
1383 if document.language == "turkmen":
1384 document.language = "english"
1385 i = find_token(document.header, "\\language", 0)
1387 document.header[i] = "\\language english"
1390 j = find_token(document.body, "\\lang turkmen", j)
1393 document.body[j] = document.body[j].replace("\\lang turkmen", "\\lang english")
1397 def revert_fontcolor(document):
1398 " Reverts font color to preamble code "
1402 i = find_token(document.header, "\\fontcolor", i)
1405 colorcode = get_value(document.header, '\\fontcolor', 0)
1406 del document.header[i]
1407 # don't clutter the preamble if backgroundcolor is not set
1408 if colorcode == "#000000":
1410 # the color code is in the form #rrggbb where every character denotes a hex number
1411 # convert the string to an int
1412 red = string.atoi(colorcode[1:3],16)
1413 # we want the output "0.5" for the value "127" therefore add here
1416 redout = float(red) / 256
1417 green = string.atoi(colorcode[3:5],16)
1420 greenout = float(green) / 256
1421 blue = string.atoi(colorcode[5:7],16)
1424 blueout = float(blue) / 256
1425 # write the preamble
1426 insert_to_preamble(0, document,
1427 '% Commands inserted by lyx2lyx to set the font color\n'
1428 + '\\@ifundefined{definecolor}{\\usepackage{color}}{}\n'
1429 + '\\definecolor{document_fontcolor}{rgb}{'
1430 + str(redout) + ', ' + str(greenout)
1431 + ', ' + str(blueout) + '}\n'
1432 + '\\color{document_fontcolor}\n')
1435 def revert_shadedboxcolor(document):
1436 " Reverts shaded box color to preamble code "
1440 i = find_token(document.header, "\\boxbgcolor", i)
1443 colorcode = get_value(document.header, '\\boxbgcolor', 0)
1444 del document.header[i]
1445 # the color code is in the form #rrggbb where every character denotes a hex number
1446 # convert the string to an int
1447 red = string.atoi(colorcode[1:3],16)
1448 # we want the output "0.5" for the value "127" therefore increment here
1451 redout = float(red) / 256
1452 green = string.atoi(colorcode[3:5],16)
1455 greenout = float(green) / 256
1456 blue = string.atoi(colorcode[5:7],16)
1459 blueout = float(blue) / 256
1460 # write the preamble
1461 insert_to_preamble(0, document,
1462 '% Commands inserted by lyx2lyx to set the color\n'
1463 '% of boxes with shaded background\n'
1464 + '\\@ifundefined{definecolor}{\\usepackage{color}}{}\n'
1465 + '\\definecolor{shadecolor}{rgb}{'
1466 + str(redout) + ', ' + str(greenout)
1467 + ', ' + str(blueout) + '}\n')
1470 def revert_lyx_version(document):
1471 " Reverts LyX Version information from Inset Info "
1474 i = find_token(document.body, '\\begin_inset Info', i)
1477 j = find_end_of_inset(document.body, i + 1)
1480 document.warning("Malformed LyX document: Could not find end of Info inset.")
1486 # but we shall try to be forgiving.
1488 for k in range(i, j):
1489 if document.body[k].startswith("arg"):
1490 arg = document.body[k][3:].strip().strip('"')
1491 if document.body[k].startswith("type"):
1492 typ = document.body[k][4:].strip().strip('"')
1493 if arg != "version" or typ != "lyxinfo":
1496 # We do not actually know the version of LyX used to produce the document.
1497 # But we can use our version, since we are reverting.
1498 s = [lyx2lyx_version.version]
1499 # Now we want to check if the line after "\end_inset" is empty. It normally
1500 # is, so we want to remove it, too.
1502 if document.body[j+1].strip() == "":
1504 document.body[i: lastline] = s
1512 supported_versions = ["2.0.0","2.0"]
1513 convert = [[346, []],
1519 [352, [convert_splitindex]],
1526 [359, [convert_nomencl_width]],
1536 [369, [convert_author_id]],
1540 [373, [merge_gbrief]],
1546 [379, [convert_math_output]],
1556 revert = [[385, [revert_lyx_version]],
1557 [384, [revert_shadedboxcolor]],
1558 [383, [revert_fontcolor]],
1559 [382, [revert_turkmen]],
1560 [381, [revert_notefontcolor]],
1561 [380, [revert_equalspacing_xymatrix]],
1562 [379, [revert_inset_preview]],
1563 [378, [revert_math_output]],
1565 [376, [revert_multirow]],
1566 [375, [revert_includeall]],
1567 [374, [revert_includeonly]],
1568 [373, [revert_html_options]],
1569 [372, [revert_gbrief]],
1570 [371, [revert_fontenc]],
1571 [370, [revert_mhchem]],
1572 [369, [revert_suppress_date]],
1573 [368, [revert_author_id]],
1574 [367, [revert_hspace_glue_lengths]],
1575 [366, [revert_percent_vspace_lengths, revert_percent_hspace_lengths]],
1576 [365, [revert_percent_skip_lengths]],
1577 [364, [revert_paragraph_indentation]],
1578 [363, [revert_branch_filename]],
1579 [362, [revert_longtable_align]],
1580 [361, [revert_applemac]],
1582 [359, [revert_nomencl_cwidth]],
1583 [358, [revert_nomencl_width]],
1584 [357, [revert_custom_processors]],
1585 [356, [revert_ulinelatex]],
1586 [355, [revert_uulinewave]],
1587 [354, [revert_strikeout]],
1588 [353, [revert_printindexall]],
1589 [352, [revert_subindex]],
1590 [351, [revert_splitindex]],
1591 [350, [revert_backgroundcolor]],
1592 [349, [revert_outputformat]],
1593 [348, [revert_xetex]],
1594 [347, [revert_phantom, revert_hphantom, revert_vphantom]],
1595 [346, [revert_tabularvalign]],
1596 [345, [revert_swiss]]
1600 if __name__ == "__main__":