1 # This file is part of lyx2lyx
2 # -*- coding: iso-8859-1 -*-
3 # Copyright (C) 2002 Dekel Tsur <dekel@lyx.org>
4 # Copyright (C) 2002-2004 José Matos <jamatos@lyx.org>
5 # Copyright (C) 2004-2005 Georg Baum <Georg.Baum@post.rwth-aachen.de>
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 from os import access, F_OK
24 from parser_tools import find_token, find_end_of_inset, get_next_paragraph, \
25 get_paragraph, get_value, del_token, is_nonempty_line,\
26 find_tokens, find_end_of, find_token2, find_re
28 from string import replace, split, find, strip, join
30 from lyx_0_12 import update_latexaccents
33 # Remove \color default
35 def remove_color_default(file):
38 i = find_token(file.body, "\\color default", i)
41 file.body[i] = replace(file.body[i], "\\color default",
48 def add_end_header(file):
49 file.header.append("\\end_header");
52 def rm_end_header(file):
53 i = find_token(file.header, "\\end_header", 0)
60 # \SpecialChar ~ -> \InsetSpace ~
62 def convert_spaces(file):
63 for i in range(len(file.body)):
64 file.body[i] = replace(file.body[i],"\\SpecialChar ~","\\InsetSpace ~")
67 def revert_spaces(file):
68 for i in range(len(file.body)):
69 file.body[i] = replace(file.body[i],"\\InsetSpace ~", "\\SpecialChar ~")
73 # equivalent to lyx::support::escape()
75 def lyx_support_escape(lab):
76 hexdigit = ['0', '1', '2', '3', '4', '5', '6', '7',
77 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
81 if o >= 128 or c == '=' or c == '%':
83 enc = enc + hexdigit[o >> 4]
84 enc = enc + hexdigit[o & 15]
91 # \begin_inset LatexCommand \eqref -> ERT
93 def revert_eqref(file):
94 regexp = re.compile(r'^\\begin_inset\s+LatexCommand\s+\\eqref')
97 i = find_re(file.body, regexp, i)
100 eqref = lyx_support_escape(regexp.sub("", file.body[i]))
101 file.body[i:i+1] = ["\\begin_inset ERT", "status Collapsed", "",
102 "\\layout Standard", "", "\\backslash ",
110 def convert_bibtex(file):
111 for i in range(len(file.body)):
112 file.body[i] = replace(file.body[i],"\\begin_inset LatexCommand \\BibTeX",
113 "\\begin_inset LatexCommand \\bibtex")
116 def revert_bibtex(file):
117 for i in range(len(file.body)):
118 file.body[i] = replace(file.body[i], "\\begin_inset LatexCommand \\bibtex",
119 "\\begin_inset LatexCommand \\BibTeX")
125 def remove_insetparent(file):
128 i = find_token(file.body, "\\begin_inset LatexCommand \\lyxparent", i)
137 def convert_external(file):
138 external_rexp = re.compile(r'\\begin_inset External ([^,]*),"([^"]*)",')
139 external_header = "\\begin_inset External"
142 i = find_token(file.body, external_header, i)
145 look = external_rexp.search(file.body[i])
148 args[0] = look.group(1)
149 args[1] = look.group(2)
150 #FIXME: if the previous search fails then warn
152 if args[0] == "RasterImage":
153 # Convert a RasterImage External Inset to a Graphics Inset.
154 top = "\\begin_inset Graphics"
156 filename = "\tfilename " + args[1]
157 file.body[i:i+1] = [top, filename]
160 # Convert the old External Inset format to the new.
161 top = external_header
162 template = "\ttemplate " + args[0]
164 filename = "\tfilename " + args[1]
165 file.body[i:i+1] = [top, template, filename]
168 file.body[i:i+1] = [top, template]
172 def revert_external_1(file):
173 external_header = "\\begin_inset External"
176 i = find_token(file.body, external_header, i)
180 template = split(file.body[i+1])
184 filename = split(file.body[i+1])
188 params = split(file.body[i+1])
190 if file.body[i+1]: del file.body[i+1]
192 file.body[i] = file.body[i] + " " + template[0]+ ', "' + filename[0] + '", " '+ join(params[1:]) + '"'
196 def revert_external_2(file):
197 draft_token = '\tdraft'
200 i = find_token(file.body, '\\begin_inset External', i)
203 j = find_end_of_inset(file.body, i + 1)
205 #this should not happen
207 k = find_token(file.body, draft_token, i+1, j-1)
208 if (k != -1 and len(draft_token) == len(file.body[k])):
216 def convert_comment(file):
218 comment = "\\layout Comment"
220 i = find_token(file.body, comment, i)
224 file.body[i:i+1] = ["\\layout Standard","","",
225 "\\begin_inset Comment",
232 i = find_token(file.body, "\\layout", i)
234 i = len(file.body) - 1
235 file.body[i:i] = ["\\end_inset","",""]
238 j = find_token(file.body, '\\begin_deeper', old_i, i)
239 if j == -1: j = i + 1
240 k = find_token(file.body, '\\begin_inset', old_i, i)
241 if k == -1: k = i + 1
246 i = find_end_of( file.body, i, "\\begin_deeper","\\end_deeper")
248 #This case should not happen
249 #but if this happens deal with it greacefully adding
250 #the missing \end_deeper.
251 i = len(file.body) - 1
252 file.body[i:i] = ["\end_deeper",""]
260 i = find_end_of( file.body, i, "\\begin_inset","\\end_inset")
262 #This case should not happen
263 #but if this happens deal with it greacefully adding
264 #the missing \end_inset.
265 i = len(file.body) - 1
266 file.body[i:i] = ["\\end_inset","","","\\end_inset","",""]
272 if find(file.body[i], comment) == -1:
273 file.body[i:i] = ["\\end_inset"]
276 file.body[i:i+1] = ["\\layout Standard"]
280 def revert_comment(file):
283 i = find_tokens(file.body, ["\\begin_inset Comment", "\\begin_inset Greyedout"], i)
287 file.body[i] = "\\begin_inset Note"
294 def add_end_layout(file):
295 i = find_token(file.body, '\\layout', 0)
301 struct_stack = ["\\layout"]
304 i = find_tokens(file.body, ["\\begin_inset", "\\end_inset", "\\layout",
305 "\\begin_deeper", "\\end_deeper", "\\the_end"], i)
307 token = split(file.body[i])[0]
309 if token == "\\begin_inset":
310 struct_stack.append(token)
314 if token == "\\end_inset":
315 tail = struct_stack.pop()
316 if tail == "\\layout":
317 file.body.insert(i,"")
318 file.body.insert(i,"\\end_layout")
320 #Check if it is the correct tag
325 if token == "\\layout":
326 tail = struct_stack.pop()
328 file.body.insert(i,"")
329 file.body.insert(i,"\\end_layout")
332 struct_stack.append(tail)
334 struct_stack.append(token)
337 if token == "\\begin_deeper":
338 file.body.insert(i,"")
339 file.body.insert(i,"\\end_layout")
341 struct_stack.append(token)
344 if token == "\\end_deeper":
345 if struct_stack[-1] == '\\layout':
346 file.body.insert(i, '\\end_layout')
353 file.body.insert(i, "")
354 file.body.insert(i, "\\end_layout")
358 def rm_end_layout(file):
361 i = find_token(file.body, '\\end_layout', i)
370 # Handle change tracking keywords
372 def insert_tracking_changes(file):
373 i = find_token(file.header, "\\tracking_changes", 0)
375 file.header.append("\\tracking_changes 0")
378 def rm_tracking_changes(file):
379 i = find_token(file.header, "\\author", 0)
383 i = find_token(file.header, "\\tracking_changes", 0)
389 def rm_body_changes(file):
392 i = find_token(file.body, "\\change_", i)
400 # \layout -> \begin_layout
402 def layout2begin_layout(file):
405 i = find_token(file.body, '\\layout', i)
409 file.body[i] = replace(file.body[i], '\\layout', '\\begin_layout')
413 def begin_layout2layout(file):
416 i = find_token(file.body, '\\begin_layout', i)
420 file.body[i] = replace(file.body[i], '\\begin_layout', '\\layout')
425 # valignment="center" -> valignment="middle"
427 def convert_valignment_middle(body, start, end):
428 for i in range(start, end):
429 if re.search('^<(column|cell) .*valignment="center".*>$', body[i]):
430 body[i] = replace(body[i], 'valignment="center"', 'valignment="middle"')
433 def convert_table_valignment_middle(file):
434 regexp = re.compile(r'^\\begin_inset\s+Tabular')
437 i = find_re(file.body, regexp, i)
440 j = find_end_of_inset(file.body, i + 1)
442 #this should not happen
443 convert_valignment_middle(file.body, i + 1, len(file.body))
445 convert_valignment_middle(file.body, i + 1, j)
449 def revert_table_valignment_middle(body, start, end):
450 for i in range(start, end):
451 if re.search('^<(column|cell) .*valignment="middle".*>$', body[i]):
452 body[i] = replace(body[i], 'valignment="middle"', 'valignment="center"')
455 def revert_valignment_middle(file):
456 regexp = re.compile(r'^\\begin_inset\s+Tabular')
459 i = find_re(file.body, regexp, i)
462 j = find_end_of_inset(file.body, i + 1)
464 #this should not happen
465 revert_table_valignment_middle(file.body, i + 1, len(file.body))
467 revert_table_valignment_middle(file.body, i + 1, j)
472 # \the_end -> \end_document
474 def convert_end_document(file):
475 i = find_token(file.body, "\\the_end", 0)
477 file.body.append("\\end_document")
479 file.body[i] = "\\end_document"
482 def revert_end_document(file):
483 i = find_token(file.body, "\\end_document", 0)
485 file.body.append("\\the_end")
487 file.body[i] = "\\the_end"
491 # Convert line and page breaks
494 #\line_top \line_bottom \pagebreak_top \pagebreak_bottom \added_space_top xxx \added_space_bottom yyy
498 #\begin layout Standard
503 #\begin_inset VSpace xxx
507 #\begin_layout Standard
511 #\begin_layout Standard
513 #\begin_inset VSpace xxx
520 def convert_breaks(file):
523 i = find_token(file.body, "\\begin_layout", i)
527 line_top = find(file.body[i],"\\line_top")
528 line_bot = find(file.body[i],"\\line_bottom")
529 pb_top = find(file.body[i],"\\pagebreak_top")
530 pb_bot = find(file.body[i],"\\pagebreak_bottom")
531 vspace_top = find(file.body[i],"\\added_space_top")
532 vspace_bot = find(file.body[i],"\\added_space_bottom")
534 if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1:
537 for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom":
538 file.body[i] = replace(file.body[i], tag, "")
541 # the position could be change because of the removal of other
542 # paragraph properties above
543 vspace_top = find(file.body[i],"\\added_space_top")
544 tmp_list = split(file.body[i][vspace_top:])
545 vspace_top_value = tmp_list[1]
546 file.body[i] = file.body[i][:vspace_top] + join(tmp_list[2:])
549 # the position could be change because of the removal of other
550 # paragraph properties above
551 vspace_bot = find(file.body[i],"\\added_space_bottom")
552 tmp_list = split(file.body[i][vspace_bot:])
553 vspace_bot_value = tmp_list[1]
554 file.body[i] = file.body[i][:vspace_bot] + join(tmp_list[2:])
556 file.body[i] = strip(file.body[i])
559 # Create an empty paragraph for line and page break that belong
560 # above the paragraph
561 if pb_top !=-1 or line_top != -1 or vspace_bot != -1:
563 paragraph_above = ['','\\begin_layout Standard','','']
566 paragraph_above.extend(['\\newpage ',''])
569 paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','',''])
572 paragraph_above.extend(['\\lyxline ',''])
574 paragraph_above.extend(['\\end_layout',''])
576 #inset new paragraph above the current paragraph
577 file.body[i-2:i-2] = paragraph_above
578 i = i + len(paragraph_above)
580 # Ensure that nested style are converted later.
581 k = find_end_of(file.body, i, "\\begin_layout", "\\end_layout")
586 if pb_top !=-1 or line_top != -1 or vspace_bot != -1:
588 paragraph_bellow = ['','\\begin_layout Standard','','']
591 paragraph_bellow.extend(['\\lyxline ',''])
594 paragraph_bellow.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','',''])
597 paragraph_bellow.extend(['\\newpage ',''])
599 paragraph_bellow.extend(['\\end_layout',''])
601 #inset new paragraph above the current paragraph
602 file.body[k + 1: k + 1] = paragraph_bellow
608 def convert_note(file):
611 i = find_tokens(file.body, ["\\begin_inset Note",
612 "\\begin_inset Comment",
613 "\\begin_inset Greyedout"], i)
617 file.body[i] = file.body[i][0:13] + 'Note ' + file.body[i][13:]
621 def revert_note(file):
622 note_header = "\\begin_inset Note "
625 i = find_token(file.body, note_header, i)
629 file.body[i] = "\\begin_inset " + file.body[i][len(note_header):]
636 def convert_box(file):
639 i = find_tokens(file.body, ["\\begin_inset Boxed",
640 "\\begin_inset Doublebox",
641 "\\begin_inset Frameless",
642 "\\begin_inset ovalbox",
643 "\\begin_inset Ovalbox",
644 "\\begin_inset Shadowbox"], i)
648 file.body[i] = file.body[i][0:13] + 'Box ' + file.body[i][13:]
652 def revert_box(file):
653 box_header = "\\begin_inset Box "
656 i = find_token(file.body, box_header, i)
660 file.body[i] = "\\begin_inset " + file.body[i][len(box_header):]
667 def convert_collapsable(file):
670 i = find_tokens(file.body, ["\\begin_inset Box",
671 "\\begin_inset Branch",
672 "\\begin_inset CharStyle",
673 "\\begin_inset Float",
674 "\\begin_inset Foot",
675 "\\begin_inset Marginal",
676 "\\begin_inset Note",
677 "\\begin_inset OptArg",
678 "\\begin_inset Wrap"], i)
682 # Seach for a line starting 'collapsed'
683 # If, however, we find a line starting '\begin_layout'
684 # (_always_ present) then break with a warning message
687 if (file.body[i] == "collapsed false"):
688 file.body[i] = "status open"
690 elif (file.body[i] == "collapsed true"):
691 file.body[i] = "status collapsed"
693 elif (file.body[i][:13] == "\\begin_layout"):
694 file.warning("Malformed LyX file: Missing 'collapsed'.")
701 def revert_collapsable(file):
704 i = find_tokens(file.body, ["\\begin_inset Box",
705 "\\begin_inset Branch",
706 "\\begin_inset CharStyle",
707 "\\begin_inset Float",
708 "\\begin_inset Foot",
709 "\\begin_inset Marginal",
710 "\\begin_inset Note",
711 "\\begin_inset OptArg",
712 "\\begin_inset Wrap"], i)
716 # Seach for a line starting 'status'
717 # If, however, we find a line starting '\begin_layout'
718 # (_always_ present) then break with a warning message
721 if (file.body[i] == "status open"):
722 file.body[i] = "collapsed false"
724 elif (file.body[i] == "status collapsed" or
725 file.body[i] == "status inlined"):
726 file.body[i] = "collapsed true"
728 elif (file.body[i][:13] == "\\begin_layout"):
729 file.warning("Malformed LyX file: Missing 'status'.")
739 def convert_ert(file):
742 i = find_token(file.body, "\\begin_inset ERT", i)
746 # Seach for a line starting 'status'
747 # If, however, we find a line starting '\begin_layout'
748 # (_always_ present) then break with a warning message
751 if (file.body[i] == "status Open"):
752 file.body[i] = "status open"
754 elif (file.body[i] == "status Collapsed"):
755 file.body[i] = "status collapsed"
757 elif (file.body[i] == "status Inlined"):
758 file.body[i] = "status inlined"
760 elif (file.body[i][:13] == "\\begin_layout"):
761 file.warning("Malformed LyX file: Missing 'status'.")
768 def revert_ert(file):
771 i = find_token(file.body, "\\begin_inset ERT", i)
775 # Seach for a line starting 'status'
776 # If, however, we find a line starting '\begin_layout'
777 # (_always_ present) then break with a warning message
780 if (file.body[i] == "status open"):
781 file.body[i] = "status Open"
783 elif (file.body[i] == "status collapsed"):
784 file.body[i] = "status Collapsed"
786 elif (file.body[i] == "status inlined"):
787 file.body[i] = "status Inlined"
789 elif (file.body[i][:13] == "\\begin_layout"):
790 file.warning("Malformed LyX file : Missing 'status'.")
800 def convert_minipage(file):
801 """ Convert minipages to the box inset.
802 We try to use the same order of arguments as lyx does.
805 inner_pos = ["c","t","b","s"]
809 i = find_token(file.body, "\\begin_inset Minipage", i)
813 file.body[i] = "\\begin_inset Box Frameless"
816 # convert old to new position using the pos list
817 if file.body[i][:8] == "position":
818 file.body[i] = 'position "%s"' % pos[int(file.body[i][9])]
820 file.body.insert(i, 'position "%s"' % pos[0])
823 file.body.insert(i, 'hor_pos "c"')
825 file.body.insert(i, 'has_inner_box 1')
828 # convert the inner_position
829 if file.body[i][:14] == "inner_position":
830 file.body[i] = 'inner_pos "%s"' % inner_pos[int(file.body[i][15])]
832 file.body.insert('inner_pos "%s"' % inner_pos[0])
835 # We need this since the new file format has a height and width
836 # in a different order.
837 if file.body[i][:6] == "height":
838 height = file.body[i][6:]
839 # test for default value of 221 and convert it accordingly
840 if height == ' "0pt"':
846 if file.body[i][:5] == "width":
847 width = file.body[i][5:]
852 if file.body[i][:9] == "collapsed":
853 if file.body[i][9:] == "true":
861 file.body.insert(i, 'use_parbox 0')
863 file.body.insert(i, 'width' + width)
865 file.body.insert(i, 'special "none"')
867 file.body.insert(i, 'height' + height)
869 file.body.insert(i, 'height_special "totalheight"')
871 file.body.insert(i, 'status ' + status)
875 # -------------------------------------------------------------------------------------------
876 # Convert backslashes and '\n' into valid ERT code, append the converted
877 # text to body[i] and return the (maybe incremented) line index i
878 def convert_ertbackslash(body, i, ert):
881 body[i] = body[i] + '\\backslash '
885 body[i+1:i+1] = ['\\newline ', '']
888 body[i] = body[i] + c
892 def convert_vspace(file):
894 # Get default spaceamount
895 i = find_token(file.header, '\\defskip', 0)
897 defskipamount = 'medskip'
899 defskipamount = split(file.header[i])[1]
904 i = find_token(file.body, '\\begin_inset VSpace', i)
907 spaceamount = split(file.body[i])[2]
909 # Are we at the beginning or end of a paragraph?
911 start = get_paragraph(file.body, i) + 1
912 for k in range(start, i):
913 if is_nonempty_line(file.body[k]):
917 j = find_end_of_inset(file.body, i)
919 file.warning("Malformed LyX file: Missing '\\end_inset'.")
922 end = get_next_paragraph(file.body, i)
923 for k in range(j + 1, end):
924 if is_nonempty_line(file.body[k]):
928 # Convert to paragraph formatting if we are at the beginning or end
929 # of a paragraph and the resulting paragraph would not be empty
930 if ((paragraph_start and not paragraph_end) or
931 (paragraph_end and not paragraph_start)):
932 # The order is important: del and insert invalidate some indices
936 file.body.insert(start, '\\added_space_top ' + spaceamount + ' ')
938 file.body.insert(start, '\\added_space_bottom ' + spaceamount + ' ')
942 file.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
943 '\\layout Standard', '', '\\backslash ']
945 if spaceamount[-1] == '*':
946 spaceamount = spaceamount[:-1]
951 # Replace defskip by the actual value
952 if spaceamount == 'defskip':
953 spaceamount = defskipamount
955 # LaTeX does not know \\smallskip* etc
957 if spaceamount == 'smallskip':
958 spaceamount = '\\smallskipamount'
959 elif spaceamount == 'medskip':
960 spaceamount = '\\medskipamount'
961 elif spaceamount == 'bigskip':
962 spaceamount = '\\bigskipamount'
963 elif spaceamount == 'vfill':
964 spaceamount = '\\fill'
966 # Finally output the LaTeX code
967 if (spaceamount == 'smallskip' or spaceamount == 'medskip' or
968 spaceamount == 'bigskip' or spaceamount == 'vfill'):
969 file.body.insert(i, spaceamount)
972 file.body.insert(i, 'vspace*{')
974 file.body.insert(i, 'vspace{')
975 i = convert_ertbackslash(file.body, i, spaceamount)
976 file.body[i] = file.body[i] + '}'
980 # Convert a LyX length into a LaTeX length
981 def convert_len(len, special):
982 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
983 "page%":"\\pagewidth", "line%":"\\linewidth",
984 "theight%":"\\textheight", "pheight%":"\\pageheight"}
986 # Convert special lengths
987 if special != 'none':
988 len = '%f\\' % len2value(len) + special
990 # Convert LyX units to LaTeX units
991 for unit in units.keys():
992 if find(len, unit) != -1:
993 len = '%f' % (len2value(len) / 100) + units[unit]
999 # Convert a LyX length into valid ERT code and append it to body[i]
1000 # Return the (maybe incremented) line index i
1001 def convert_ertlen(body, i, len, special):
1002 # Convert backslashes and insert the converted length into body
1003 return convert_ertbackslash(body, i, convert_len(len, special))
1006 # Return the value of len without the unit in numerical form
1008 result = re.search('([+-]?[0-9.]+)', len)
1010 return float(result.group(1))
1011 # No number means 1.0
1015 # Convert text to ERT and insert it at body[i]
1016 # Return the index of the line after the inserted ERT
1017 def insert_ert(body, i, status, text):
1018 body[i:i] = ['\\begin_inset ERT', 'status ' + status, '',
1019 '\\layout Standard', '']
1021 i = convert_ertbackslash(body, i, text) + 1
1022 body[i:i] = ['', '\\end_inset', '']
1027 # Add text to the preamble if it is not already there.
1028 # Only the first line is checked!
1029 def add_to_preamble(file, text):
1030 i = find_token(file.header, '\\begin_preamble', 0)
1032 file.warning("Malformed LyX file: Missing '\\begin_preamble'.")
1034 j = find_token(file.header, '\\end_preamble', i)
1036 file.warning("Malformed LyX file: Missing '\\end_preamble'.")
1038 if find_token(file.header, text[0], i, j) != -1:
1040 file.header[j:j] = text
1043 def convert_frameless_box(file):
1044 pos = ['t', 'c', 'b']
1045 inner_pos = ['c', 't', 'b', 's']
1048 i = find_token(file.body, '\\begin_inset Frameless', i)
1051 j = find_end_of_inset(file.body, i)
1053 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1060 params = {'position':'0', 'hor_pos':'c', 'has_inner_box':'1',
1061 'inner_pos':'1', 'use_parbox':'0', 'width':'100col%',
1062 'special':'none', 'height':'1in',
1063 'height_special':'totalheight', 'collapsed':'false'}
1064 for key in params.keys():
1065 value = replace(get_value(file.body, key, i, j), '"', '')
1067 if key == 'position':
1068 # convert new to old position: 'position "t"' -> 0
1069 value = find_token(pos, value, 0)
1072 elif key == 'inner_pos':
1073 # convert inner position
1074 value = find_token(inner_pos, value, 0)
1079 j = del_token(file.body, key, i, j)
1082 # Convert to minipage or ERT?
1083 # Note that the inner_position and height parameters of a minipage
1084 # inset are ignored and not accessible for the user, although they
1085 # are present in the file format and correctly read in and written.
1086 # Therefore we convert to ERT if they do not have their LaTeX
1087 # defaults. These are:
1088 # - the value of "position" for "inner_pos"
1089 # - "\totalheight" for "height"
1090 if (params['use_parbox'] != '0' or
1091 params['has_inner_box'] != '1' or
1092 params['special'] != 'none' or
1093 inner_pos[params['inner_pos']] != pos[params['position']] or
1094 params['height_special'] != 'totalheight' or
1095 len2value(params['height']) != 1.0):
1097 # Here we know that this box is not supported in file format 224.
1098 # Therefore we need to convert it to ERT. We can't simply convert
1099 # the beginning and end of the box to ERT, because the
1100 # box inset may contain layouts that are different from the
1101 # surrounding layout. After the conversion the contents of the
1102 # box inset is on the same level as the surrounding text, and
1103 # paragraph layouts and align parameters can get mixed up.
1105 # A possible solution for this problem:
1106 # Convert the box to a minipage and redefine the minipage
1107 # environment in ERT so that the original box is simulated.
1108 # For minipages we could do this in a way that the width and
1109 # position can still be set from LyX, but this did not work well.
1110 # This is not possible for parboxes either, so we convert the
1111 # original box to ERT, put the minipage inset inside the box
1112 # and redefine the minipage environment to be empty.
1114 # Commands that are independant of a particular box can go to
1116 # We need to define lyxtolyxrealminipage with 3 optional
1117 # arguments although LyX 1.3 uses only the first one.
1118 # Otherwise we will get LaTeX errors if this document is
1119 # converted to format 225 or above again (LyX 1.4 uses all
1120 # optional arguments).
1121 add_to_preamble(file,
1122 ['% Commands inserted by lyx2lyx for frameless boxes',
1123 '% Save the original minipage environment',
1124 '\\let\\lyxtolyxrealminipage\\minipage',
1125 '\\let\\endlyxtolyxrealminipage\\endminipage',
1126 '% Define an empty lyxtolyximinipage environment',
1127 '% with 3 optional arguments',
1128 '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1129 '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1130 ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1131 ' {\\end{lyxtolyxiiiminipage}}',
1132 '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1133 ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1134 ' {\\end{lyxtolyxiiminipage}}',
1135 '\\newenvironment{lyxtolyxminipage}[1][c]%',
1136 ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1137 ' {\\end{lyxtolyximinipage}}'])
1139 if params['use_parbox'] != '0':
1142 ert = '\\begin{lyxtolyxrealminipage}'
1144 # convert optional arguments only if not latex default
1145 if (pos[params['position']] != 'c' or
1146 inner_pos[params['inner_pos']] != pos[params['position']] or
1147 params['height_special'] != 'totalheight' or
1148 len2value(params['height']) != 1.0):
1149 ert = ert + '[' + pos[params['position']] + ']'
1150 if (inner_pos[params['inner_pos']] != pos[params['position']] or
1151 params['height_special'] != 'totalheight' or
1152 len2value(params['height']) != 1.0):
1153 ert = ert + '[' + convert_len(params['height'],
1154 params['height_special']) + ']'
1155 if inner_pos[params['inner_pos']] != pos[params['position']]:
1156 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1158 ert = ert + '{' + convert_len(params['width'],
1159 params['special']) + '}'
1161 if params['use_parbox'] != '0':
1163 ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1164 ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1167 i = insert_ert(file.body, i, 'Collapsed', ert)
1168 j = j + i - old_i - 1
1170 file.body[i:i] = ['\\begin_inset Minipage',
1171 'position %d' % params['position'],
1174 'width "' + params['width'] + '"',
1175 'collapsed ' + params['collapsed']]
1179 # Restore the original minipage environment since we may have
1180 # minipages inside this box.
1181 # Start a new paragraph because the following may be nonstandard
1182 file.body[i:i] = ['\\layout Standard', '', '']
1185 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1186 ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1188 i = insert_ert(file.body, i, 'Collapsed', ert)
1189 j = j + i - old_i - 1
1191 # Redefine the minipage end before the inset end.
1192 # Start a new paragraph because the previous may be nonstandard
1193 file.body[j:j] = ['\\layout Standard', '', '']
1195 ert = '\\let\\endminipage\\endlyxtolyxminipage'
1196 j = insert_ert(file.body, j, 'Collapsed', ert)
1198 file.body.insert(j, '')
1201 # LyX writes '%\n' after each box. Therefore we need to end our
1202 # ERT with '%\n', too, since this may swallow a following space.
1203 if params['use_parbox'] != '0':
1206 ert = '\\end{lyxtolyxrealminipage}%\n'
1207 j = insert_ert(file.body, j, 'Collapsed', ert)
1209 # We don't need to restore the original minipage after the inset
1210 # end because the scope of the redefinition is the original box.
1214 # Convert to minipage
1215 file.body[i:i] = ['\\begin_inset Minipage',
1216 'position %d' % params['position'],
1217 'inner_position %d' % params['inner_pos'],
1218 'height "' + params['height'] + '"',
1219 'width "' + params['width'] + '"',
1220 'collapsed ' + params['collapsed']]
1227 def convert_jurabib(file):
1228 i = find_token(file.header, '\\use_numerical_citations', 0)
1230 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1232 file.header.insert(i + 1, '\\use_jurabib 0')
1235 def revert_jurabib(file):
1236 i = find_token(file.header, '\\use_jurabib', 0)
1238 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1240 if get_value(file.header, '\\use_jurabib', 0) != "0":
1241 file.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1242 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1250 def convert_bibtopic(file):
1251 i = find_token(file.header, '\\use_jurabib', 0)
1253 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1255 file.header.insert(i + 1, '\\use_bibtopic 0')
1258 def revert_bibtopic(file):
1259 i = find_token(file.header, '\\use_bibtopic', 0)
1261 file.warning("Malformed lyx file: Missing '\\use_bibtopic'.")
1263 if get_value(file.header, '\\use_bibtopic', 0) != "0":
1264 file.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1265 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1272 def convert_float(file):
1275 i = find_token(file.body, '\\begin_inset Float', i)
1278 # Seach for a line starting 'wide'
1279 # If, however, we find a line starting '\begin_layout'
1280 # (_always_ present) then break with a warning message
1283 if (file.body[i][:4] == "wide"):
1284 file.body.insert(i + 1, 'sideways false')
1286 elif (file.body[i][:13] == "\\begin_layout"):
1287 file.warning("Malformed lyx file: Missing 'wide'.")
1293 def revert_float(file):
1296 i = find_token(file.body, '\\begin_inset Float', i)
1299 j = find_end_of_inset(file.body, i)
1301 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1304 if get_value(file.body, 'sideways', i, j) != "false":
1305 file.warning("Conversion of 'sideways true' not yet implemented.")
1306 # Don't remove 'sideways' so that people will get warnings by lyx
1309 del_token(file.body, 'sideways', i, j)
1313 def convert_graphics(file):
1314 """ Add extension to filenames of insetgraphics if necessary.
1318 i = find_token(file.body, "\\begin_inset Graphics", i)
1322 j = find_token2(file.body, "filename", i)
1326 filename = split(file.body[j])[1]
1327 absname = os.path.normpath(os.path.join(file.dir, filename))
1328 if file.input == stdin and not os.path.isabs(filename):
1329 # We don't know the directory and cannot check the file.
1330 # We could use a heuristic and take the current directory,
1331 # and we could try to find out if filename has an extension,
1332 # but that would be just guesses and could be wrong.
1333 file.warning("""Warning: Can not determine whether file
1335 needs an extension when reading from standard input.
1336 You may need to correct the file manually or run
1337 lyx2lyx again with the .lyx file as commandline argument.""" % filename)
1339 # This needs to be the same algorithm as in pre 233 insetgraphics
1340 if access(absname, F_OK):
1342 if access(absname + ".ps", F_OK):
1343 file.body[j] = replace(file.body[j], filename, filename + ".ps")
1345 if access(absname + ".eps", F_OK):
1346 file.body[j] = replace(file.body[j], filename, filename + ".eps")
1350 # Convert firstname and surname from styles -> char styles
1352 def convert_names(file):
1353 """ Convert in the docbook backend from firstname and surname style
1356 if file.backend != "docbook":
1362 i = find_token(file.body, "\\begin_layout Author", i)
1367 while file.body[i] == "":
1370 if file.body[i][:11] != "\\end_layout" or file.body[i+2][:13] != "\\begin_deeper":
1375 i = find_end_of( file.body, i+3, "\\begin_deeper","\\end_deeper")
1377 # something is really wrong, abort
1378 file.warning("Missing \\end_deeper, after style Author.")
1379 file.warning("Aborted attempt to parse FirstName and Surname.")
1381 firstname, surname = "", ""
1383 name = file.body[k:i]
1385 j = find_token(name, "\\begin_layout FirstName", 0)
1388 while(name[j] != "\\end_layout"):
1389 firstname = firstname + name[j]
1392 j = find_token(name, "\\begin_layout Surname", 0)
1395 while(name[j] != "\\end_layout"):
1396 surname = surname + name[j]
1400 del file.body[k+2:i+1]
1402 file.body[k-1:k-1] = ["", "",
1403 "\\begin_inset CharStyle Firstname",
1406 "\\begin_layout Standard",
1414 "\\begin_inset CharStyle Surname",
1417 "\\begin_layout Standard",
1426 def revert_names(file):
1427 """ Revert in the docbook backend from firstname and surname char style
1430 if file.backend != "docbook":
1435 # \use_natbib 1 \cite_engine <style>
1436 # \use_numerical_citations 0 -> where <style> is one of
1437 # \use_jurabib 0 "basic", "natbib_authoryear",
1438 # "natbib_numerical" or "jurabib"
1439 def convert_cite_engine(file):
1440 a = find_token(file.header, "\\use_natbib", 0)
1442 file.warning("Malformed lyx file: Missing '\\use_natbib'.")
1445 b = find_token(file.header, "\\use_numerical_citations", 0)
1446 if b == -1 or b != a+1:
1447 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1450 c = find_token(file.header, "\\use_jurabib", 0)
1451 if c == -1 or c != b+1:
1452 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1455 use_natbib = int(split(file.header[a])[1])
1456 use_numerical_citations = int(split(file.header[b])[1])
1457 use_jurabib = int(split(file.header[c])[1])
1459 cite_engine = "basic"
1461 if use_numerical_citations:
1462 cite_engine = "natbib_numerical"
1464 cite_engine = "natbib_authoryear"
1466 cite_engine = "jurabib"
1468 del file.header[a:c+1]
1469 file.header.insert(a, "\\cite_engine " + cite_engine)
1472 def revert_cite_engine(file):
1473 i = find_token(file.header, "\\cite_engine", 0)
1475 file.warning("Malformed lyx file: Missing '\\cite_engine'.")
1478 cite_engine = split(file.header[i])[1]
1483 if cite_engine == "natbib_numerical":
1486 elif cite_engine == "natbib_authoryear":
1488 elif cite_engine == "jurabib":
1492 file.header.insert(i, "\\use_jurabib " + use_jurabib)
1493 file.header.insert(i, "\\use_numerical_citations " + use_numerical)
1494 file.header.insert(i, "\\use_natbib " + use_natbib)
1500 def convert_paperpackage(file):
1501 i = find_token(file.header, "\\paperpackage", 0)
1503 file.warning("Malformed lyx file: Missing '\\paperpackage'.")
1506 packages = {'a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
1507 paperpackage = split(file.header[i])[1]
1508 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1511 def revert_paperpackage(file):
1512 i = find_token(file.header, "\\paperpackage", 0)
1514 file.warning("Malformed lyx file: Missing '\\paperpackage'.")
1517 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
1519 paperpackage = split(file.header[i])[1]
1520 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1526 def convert_bullets(file):
1529 i = find_token(file.header, "\\bullet", i)
1532 if file.header[i][:12] == '\\bulletLaTeX':
1533 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1])
1536 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1]) +\
1537 ' ' + strip(file.header[i+2]) + ' ' + strip(file.header[i+3])
1539 del file.header[i+1:i + n]
1543 def revert_bullets(file):
1546 i = find_token(file.header, "\\bullet", i)
1549 if file.header[i][:12] == '\\bulletLaTeX':
1550 n = find(file.header[i], '"')
1552 file.warning("Malformed header.")
1555 file.header[i:i+1] = [file.header[i][:n-1],'\t' + file.header[i][n:], '\\end_bullet']
1558 frag = split(file.header[i])
1560 file.warning("Malformed header.")
1563 file.header[i:i+1] = [frag[0] + ' ' + frag[1],
1572 # \begin_header and \begin_document
1574 def add_begin_header(file):
1575 i = find_token(file.header, '\\lyxformat', 0)
1576 file.header.insert(i+1, '\\begin_header')
1577 file.header.insert(i+1, '\\begin_document')
1580 def remove_begin_header(file):
1581 i = find_token(file.header, "\\begin_document", 0)
1584 i = find_token(file.header, "\\begin_header", 0)
1590 # \begin_file.body and \end_file.body
1592 def add_begin_body(file):
1593 file.body.insert(0, '\\begin_body')
1594 file.body.insert(1, '')
1595 i = find_token(file.body, "\\end_document", 0)
1596 file.body.insert(i, '\\end_body')
1598 def remove_begin_body(file):
1599 i = find_token(file.body, "\\begin_body", 0)
1602 if not file.body[i]:
1604 i = find_token(file.body, "\\end_body", 0)
1612 def normalize_papersize(file):
1613 i = find_token(file.header, '\\papersize', 0)
1617 tmp = split(file.header[i])
1618 if tmp[1] == "Default":
1619 file.header[i] = '\\papersize default'
1621 if tmp[1] == "Custom":
1622 file.header[i] = '\\papersize custom'
1625 def denormalize_papersize(file):
1626 i = find_token(file.header, '\\papersize', 0)
1630 tmp = split(file.header[i])
1631 if tmp[1] == "custom":
1632 file.header[i] = '\\papersize Custom'
1636 # Strip spaces at end of command line
1638 def strip_end_space(file):
1639 for i in range(len(file.body)):
1640 if file.body[i][:1] == '\\':
1641 file.body[i] = strip(file.body[i])
1645 # Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes
1647 def use_x_boolean(file):
1648 bin2bool = {'0': 'false', '1': 'true'}
1649 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
1650 i = find_token(file.header, use, 0)
1653 decompose = split(file.header[i])
1654 file.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
1657 def use_x_binary(file):
1658 bool2bin = {'false': '0', 'true': '1'}
1659 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
1660 i = find_token(file.header, use, 0)
1663 decompose = split(file.header[i])
1664 file.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
1667 # Place all the paragraph parameters in their own line
1669 def normalize_paragraph_params(file):
1671 allowed_parameters = '\\paragraph_spacing', '\\noindent', '\\align', '\\labelwidthstring', "\\start_of_appendix"
1675 i = find_token(file.body, '\\begin_layout', i)
1681 if strip(body[i]) and split(body[i])[0] not in allowed_parameters:
1684 j = find(body[i],'\\', 1)
1687 body[i:i+1] = [strip(body[i][:j]), body[i][j:]]
1693 # Add/remove output_changes parameter
1695 def convert_output_changes (file):
1696 i = find_token(file.header, '\\tracking_changes', 0)
1698 file.warning("Malformed lyx file: Missing '\\tracking_changes'.")
1700 file.header.insert(i+1, '\\output_changes true')
1703 def revert_output_changes (file):
1704 i = find_token(file.header, '\\output_changes', 0)
1711 # Convert paragraph breaks and sanitize paragraphs
1713 def convert_ert_paragraphs(file):
1714 forbidden_settings = [
1715 # paragraph parameters
1716 '\\paragraph_spacing', '\\labelwidthstring',
1717 '\\start_of_appendix', '\\noindent',
1718 '\\leftindent', '\\align',
1720 '\\family', '\\series', '\\shape', '\\size',
1721 '\\emph', '\\numeric', '\\bar', '\\noun',
1722 '\\color', '\\lang']
1725 i = find_token(file.body, '\\begin_inset ERT', i)
1728 j = find_end_of_inset(file.body, i)
1730 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1734 # convert non-standard paragraphs to standard
1737 k = find_token(file.body, "\\begin_layout", k, j)
1740 file.body[k] = "\\begin_layout Standard"
1743 # remove all paragraph parameters and font settings
1746 if (strip(file.body[k]) and
1747 split(file.body[k])[0] in forbidden_settings):
1753 # insert an empty paragraph before each paragraph but the first
1757 k = find_token(file.body, "\\begin_layout Standard", k, j)
1764 file.body[k:k] = ["\\begin_layout Standard", "",
1769 # convert \\newline to new paragraph
1772 k = find_token(file.body, "\\newline", k, j)
1775 file.body[k:k+1] = ["\\end_layout", "", "\\begin_layout Standard"]
1782 # Remove double paragraph breaks
1784 def revert_ert_paragraphs(file):
1787 i = find_token(file.body, '\\begin_inset ERT', i)
1790 j = find_end_of_inset(file.body, i)
1792 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1796 # replace paragraph breaks with \newline
1799 k = find_token(file.body, "\\end_layout", k, j)
1800 l = find_token(file.body, "\\begin_layout", k, j)
1801 if k == -1 or l == -1:
1803 file.body[k:l+1] = ["\\newline"]
1807 # replace double \newlines with paragraph breaks
1810 k = find_token(file.body, "\\newline", k, j)
1814 while file.body[l] == "":
1816 if strip(file.body[l]) and split(file.body[l])[0] == "\\newline":
1817 file.body[k:l+1] = ["\\end_layout", "",
1818 "\\begin_layout Standard"]
1830 convert = [[223, [insert_tracking_changes, add_end_header, remove_color_default,
1831 convert_spaces, convert_bibtex, remove_insetparent]],
1832 [224, [convert_external, convert_comment]],
1833 [225, [add_end_layout, layout2begin_layout, convert_end_document,
1834 convert_table_valignment_middle, convert_breaks]],
1835 [226, [convert_note]],
1836 [227, [convert_box]],
1837 [228, [convert_collapsable, convert_ert]],
1838 [229, [convert_minipage]],
1839 [230, [convert_jurabib]],
1840 [231, [convert_float]],
1841 [232, [convert_bibtopic]],
1842 [233, [convert_graphics, convert_names]],
1843 [234, [convert_cite_engine]],
1844 [235, [convert_paperpackage]],
1845 [236, [convert_bullets, add_begin_header, add_begin_body,
1846 normalize_papersize, strip_end_space]],
1847 [237, [use_x_boolean]],
1848 [238, [update_latexaccents]],
1849 [239, [normalize_paragraph_params]],
1850 [240, [convert_output_changes]],
1851 [241, [convert_ert_paragraphs]]]
1853 revert = [[240, [revert_ert_paragraphs]],
1854 [239, [revert_output_changes]],
1857 [236, [use_x_binary]],
1858 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
1860 [234, [revert_paperpackage]],
1861 [233, [revert_cite_engine]],
1862 [232, [revert_names]],
1863 [231, [revert_bibtopic]],
1864 [230, [revert_float]],
1865 [229, [revert_jurabib]],
1867 [227, [revert_collapsable, revert_ert]],
1868 [226, [revert_box, revert_external_2]],
1869 [225, [revert_note]],
1870 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
1871 revert_valignment_middle, convert_vspace, convert_frameless_box]],
1872 [223, [revert_external_2, revert_comment, revert_eqref]],
1873 [221, [rm_end_header, revert_spaces, revert_bibtex,
1874 rm_tracking_changes, rm_body_changes]]]
1877 if __name__ == "__main__":