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)
528 # Merge all paragraph parameters into a single line
529 while file.body[i + 1][:1] == '\\':
530 file.body[i] = file.body[i + 1] + ' ' + file.body[i]
533 line_top = find(file.body[i],"\\line_top")
534 line_bot = find(file.body[i],"\\line_bottom")
535 pb_top = find(file.body[i],"\\pagebreak_top")
536 pb_bot = find(file.body[i],"\\pagebreak_bottom")
537 vspace_top = find(file.body[i],"\\added_space_top")
538 vspace_bot = find(file.body[i],"\\added_space_bottom")
540 if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1:
543 for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom":
544 file.body[i] = replace(file.body[i], tag, "")
547 # the position could be change because of the removal of other
548 # paragraph properties above
549 vspace_top = find(file.body[i],"\\added_space_top")
550 tmp_list = split(file.body[i][vspace_top:])
551 vspace_top_value = tmp_list[1]
552 file.body[i] = file.body[i][:vspace_top] + join(tmp_list[2:])
555 # the position could be change because of the removal of other
556 # paragraph properties above
557 vspace_bot = find(file.body[i],"\\added_space_bottom")
558 tmp_list = split(file.body[i][vspace_bot:])
559 vspace_bot_value = tmp_list[1]
560 file.body[i] = file.body[i][:vspace_bot] + join(tmp_list[2:])
562 file.body[i] = strip(file.body[i])
565 # Create an empty paragraph for line and page break that belong
566 # above the paragraph
567 if pb_top !=-1 or line_top != -1 or vspace_top != -1:
569 paragraph_above = ['','\\begin_layout Standard','','']
572 paragraph_above.extend(['\\newpage ',''])
575 paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','',''])
578 paragraph_above.extend(['\\lyxline ',''])
580 paragraph_above.extend(['\\end_layout',''])
582 #inset new paragraph above the current paragraph
583 file.body[i-2:i-2] = paragraph_above
584 i = i + len(paragraph_above)
586 # Ensure that nested style are converted later.
587 k = find_end_of(file.body, i, "\\begin_layout", "\\end_layout")
592 if pb_bot !=-1 or line_bot != -1 or vspace_bot != -1:
594 paragraph_below = ['','\\begin_layout Standard','','']
597 paragraph_below.extend(['\\lyxline ',''])
600 paragraph_below.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','',''])
603 paragraph_below.extend(['\\newpage ',''])
605 paragraph_below.extend(['\\end_layout',''])
607 #inset new paragraph above the current paragraph
608 file.body[k + 1: k + 1] = paragraph_below
614 def convert_note(file):
617 i = find_tokens(file.body, ["\\begin_inset Note",
618 "\\begin_inset Comment",
619 "\\begin_inset Greyedout"], i)
623 file.body[i] = file.body[i][0:13] + 'Note ' + file.body[i][13:]
627 def revert_note(file):
628 note_header = "\\begin_inset Note "
631 i = find_token(file.body, note_header, i)
635 file.body[i] = "\\begin_inset " + file.body[i][len(note_header):]
642 def convert_box(file):
645 i = find_tokens(file.body, ["\\begin_inset Boxed",
646 "\\begin_inset Doublebox",
647 "\\begin_inset Frameless",
648 "\\begin_inset ovalbox",
649 "\\begin_inset Ovalbox",
650 "\\begin_inset Shadowbox"], i)
654 file.body[i] = file.body[i][0:13] + 'Box ' + file.body[i][13:]
658 def revert_box(file):
659 box_header = "\\begin_inset Box "
662 i = find_token(file.body, box_header, i)
666 file.body[i] = "\\begin_inset " + file.body[i][len(box_header):]
673 def convert_collapsable(file):
676 i = find_tokens(file.body, ["\\begin_inset Box",
677 "\\begin_inset Branch",
678 "\\begin_inset CharStyle",
679 "\\begin_inset Float",
680 "\\begin_inset Foot",
681 "\\begin_inset Marginal",
682 "\\begin_inset Note",
683 "\\begin_inset OptArg",
684 "\\begin_inset Wrap"], i)
688 # Seach for a line starting 'collapsed'
689 # If, however, we find a line starting '\begin_layout'
690 # (_always_ present) then break with a warning message
693 if (file.body[i] == "collapsed false"):
694 file.body[i] = "status open"
696 elif (file.body[i] == "collapsed true"):
697 file.body[i] = "status collapsed"
699 elif (file.body[i][:13] == "\\begin_layout"):
700 file.warning("Malformed LyX file: Missing 'collapsed'.")
707 def revert_collapsable(file):
710 i = find_tokens(file.body, ["\\begin_inset Box",
711 "\\begin_inset Branch",
712 "\\begin_inset CharStyle",
713 "\\begin_inset Float",
714 "\\begin_inset Foot",
715 "\\begin_inset Marginal",
716 "\\begin_inset Note",
717 "\\begin_inset OptArg",
718 "\\begin_inset Wrap"], i)
722 # Seach for a line starting 'status'
723 # If, however, we find a line starting '\begin_layout'
724 # (_always_ present) then break with a warning message
727 if (file.body[i] == "status open"):
728 file.body[i] = "collapsed false"
730 elif (file.body[i] == "status collapsed" or
731 file.body[i] == "status inlined"):
732 file.body[i] = "collapsed true"
734 elif (file.body[i][:13] == "\\begin_layout"):
735 file.warning("Malformed LyX file: Missing 'status'.")
745 def convert_ert(file):
748 i = find_token(file.body, "\\begin_inset ERT", i)
752 # Seach for a line starting 'status'
753 # If, however, we find a line starting '\begin_layout'
754 # (_always_ present) then break with a warning message
757 if (file.body[i] == "status Open"):
758 file.body[i] = "status open"
760 elif (file.body[i] == "status Collapsed"):
761 file.body[i] = "status collapsed"
763 elif (file.body[i] == "status Inlined"):
764 file.body[i] = "status inlined"
766 elif (file.body[i][:13] == "\\begin_layout"):
767 file.warning("Malformed LyX file: Missing 'status'.")
774 def revert_ert(file):
777 i = find_token(file.body, "\\begin_inset ERT", i)
781 # Seach for a line starting 'status'
782 # If, however, we find a line starting '\begin_layout'
783 # (_always_ present) then break with a warning message
786 if (file.body[i] == "status open"):
787 file.body[i] = "status Open"
789 elif (file.body[i] == "status collapsed"):
790 file.body[i] = "status Collapsed"
792 elif (file.body[i] == "status inlined"):
793 file.body[i] = "status Inlined"
795 elif (file.body[i][:13] == "\\begin_layout"):
796 file.warning("Malformed LyX file : Missing 'status'.")
806 def convert_minipage(file):
807 """ Convert minipages to the box inset.
808 We try to use the same order of arguments as lyx does.
811 inner_pos = ["c","t","b","s"]
815 i = find_token(file.body, "\\begin_inset Minipage", i)
819 file.body[i] = "\\begin_inset Box Frameless"
822 # convert old to new position using the pos list
823 if file.body[i][:8] == "position":
824 file.body[i] = 'position "%s"' % pos[int(file.body[i][9])]
826 file.body.insert(i, 'position "%s"' % pos[0])
829 file.body.insert(i, 'hor_pos "c"')
831 file.body.insert(i, 'has_inner_box 1')
834 # convert the inner_position
835 if file.body[i][:14] == "inner_position":
836 file.body[i] = 'inner_pos "%s"' % inner_pos[int(file.body[i][15])]
838 file.body.insert('inner_pos "%s"' % inner_pos[0])
841 # We need this since the new file format has a height and width
842 # in a different order.
843 if file.body[i][:6] == "height":
844 height = file.body[i][6:]
845 # test for default value of 221 and convert it accordingly
846 if height == ' "0pt"':
852 if file.body[i][:5] == "width":
853 width = file.body[i][5:]
858 if file.body[i][:9] == "collapsed":
859 if file.body[i][9:] == "true":
867 file.body.insert(i, 'use_parbox 0')
869 file.body.insert(i, 'width' + width)
871 file.body.insert(i, 'special "none"')
873 file.body.insert(i, 'height' + height)
875 file.body.insert(i, 'height_special "totalheight"')
877 file.body.insert(i, 'status ' + status)
881 # -------------------------------------------------------------------------------------------
882 # Convert backslashes and '\n' into valid ERT code, append the converted
883 # text to body[i] and return the (maybe incremented) line index i
884 def convert_ertbackslash(body, i, ert):
887 body[i] = body[i] + '\\backslash '
891 body[i+1:i+1] = ['\\newline ', '']
894 body[i] = body[i] + c
898 def convert_vspace(file):
900 # Get default spaceamount
901 i = find_token(file.header, '\\defskip', 0)
903 defskipamount = 'medskip'
905 defskipamount = split(file.header[i])[1]
910 i = find_token(file.body, '\\begin_inset VSpace', i)
913 spaceamount = split(file.body[i])[2]
915 # Are we at the beginning or end of a paragraph?
917 start = get_paragraph(file.body, i) + 1
918 for k in range(start, i):
919 if is_nonempty_line(file.body[k]):
923 j = find_end_of_inset(file.body, i)
925 file.warning("Malformed LyX file: Missing '\\end_inset'.")
928 end = get_next_paragraph(file.body, i)
929 for k in range(j + 1, end):
930 if is_nonempty_line(file.body[k]):
934 # Convert to paragraph formatting if we are at the beginning or end
935 # of a paragraph and the resulting paragraph would not be empty
936 if ((paragraph_start and not paragraph_end) or
937 (paragraph_end and not paragraph_start)):
938 # The order is important: del and insert invalidate some indices
942 file.body.insert(start, '\\added_space_top ' + spaceamount + ' ')
944 file.body.insert(start, '\\added_space_bottom ' + spaceamount + ' ')
948 file.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
949 '\\layout Standard', '', '\\backslash ']
951 if spaceamount[-1] == '*':
952 spaceamount = spaceamount[:-1]
957 # Replace defskip by the actual value
958 if spaceamount == 'defskip':
959 spaceamount = defskipamount
961 # LaTeX does not know \\smallskip* etc
963 if spaceamount == 'smallskip':
964 spaceamount = '\\smallskipamount'
965 elif spaceamount == 'medskip':
966 spaceamount = '\\medskipamount'
967 elif spaceamount == 'bigskip':
968 spaceamount = '\\bigskipamount'
969 elif spaceamount == 'vfill':
970 spaceamount = '\\fill'
972 # Finally output the LaTeX code
973 if (spaceamount == 'smallskip' or spaceamount == 'medskip' or
974 spaceamount == 'bigskip' or spaceamount == 'vfill'):
975 file.body.insert(i, spaceamount)
978 file.body.insert(i, 'vspace*{')
980 file.body.insert(i, 'vspace{')
981 i = convert_ertbackslash(file.body, i, spaceamount)
982 file.body[i] = file.body[i] + '}'
986 # Convert a LyX length into a LaTeX length
987 def convert_len(len, special):
988 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
989 "page%":"\\pagewidth", "line%":"\\linewidth",
990 "theight%":"\\textheight", "pheight%":"\\pageheight"}
992 # Convert special lengths
993 if special != 'none':
994 len = '%f\\' % len2value(len) + special
996 # Convert LyX units to LaTeX units
997 for unit in units.keys():
998 if find(len, unit) != -1:
999 len = '%f' % (len2value(len) / 100) + units[unit]
1005 # Convert a LyX length into valid ERT code and append it to body[i]
1006 # Return the (maybe incremented) line index i
1007 def convert_ertlen(body, i, len, special):
1008 # Convert backslashes and insert the converted length into body
1009 return convert_ertbackslash(body, i, convert_len(len, special))
1012 # Return the value of len without the unit in numerical form
1014 result = re.search('([+-]?[0-9.]+)', len)
1016 return float(result.group(1))
1017 # No number means 1.0
1021 # Convert text to ERT and insert it at body[i]
1022 # Return the index of the line after the inserted ERT
1023 def insert_ert(body, i, status, text):
1024 body[i:i] = ['\\begin_inset ERT', 'status ' + status, '',
1025 '\\layout Standard', '']
1027 i = convert_ertbackslash(body, i, text) + 1
1028 body[i:i] = ['', '\\end_inset', '']
1033 # Add text to the preamble if it is not already there.
1034 # Only the first line is checked!
1035 def add_to_preamble(file, text):
1036 i = find_token(file.header, '\\begin_preamble', 0)
1038 file.warning("Malformed LyX file: Missing '\\begin_preamble'.")
1040 j = find_token(file.header, '\\end_preamble', i)
1042 file.warning("Malformed LyX file: Missing '\\end_preamble'.")
1044 if find_token(file.header, text[0], i, j) != -1:
1046 file.header[j:j] = text
1049 def convert_frameless_box(file):
1050 pos = ['t', 'c', 'b']
1051 inner_pos = ['c', 't', 'b', 's']
1054 i = find_token(file.body, '\\begin_inset Frameless', i)
1057 j = find_end_of_inset(file.body, i)
1059 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1066 params = {'position':'0', 'hor_pos':'c', 'has_inner_box':'1',
1067 'inner_pos':'1', 'use_parbox':'0', 'width':'100col%',
1068 'special':'none', 'height':'1in',
1069 'height_special':'totalheight', 'collapsed':'false'}
1070 for key in params.keys():
1071 value = replace(get_value(file.body, key, i, j), '"', '')
1073 if key == 'position':
1074 # convert new to old position: 'position "t"' -> 0
1075 value = find_token(pos, value, 0)
1078 elif key == 'inner_pos':
1079 # convert inner position
1080 value = find_token(inner_pos, value, 0)
1085 j = del_token(file.body, key, i, j)
1088 # Convert to minipage or ERT?
1089 # Note that the inner_position and height parameters of a minipage
1090 # inset are ignored and not accessible for the user, although they
1091 # are present in the file format and correctly read in and written.
1092 # Therefore we convert to ERT if they do not have their LaTeX
1093 # defaults. These are:
1094 # - the value of "position" for "inner_pos"
1095 # - "\totalheight" for "height"
1096 if (params['use_parbox'] != '0' or
1097 params['has_inner_box'] != '1' or
1098 params['special'] != 'none' or
1099 inner_pos[params['inner_pos']] != pos[params['position']] or
1100 params['height_special'] != 'totalheight' or
1101 len2value(params['height']) != 1.0):
1103 # Here we know that this box is not supported in file format 224.
1104 # Therefore we need to convert it to ERT. We can't simply convert
1105 # the beginning and end of the box to ERT, because the
1106 # box inset may contain layouts that are different from the
1107 # surrounding layout. After the conversion the contents of the
1108 # box inset is on the same level as the surrounding text, and
1109 # paragraph layouts and align parameters can get mixed up.
1111 # A possible solution for this problem:
1112 # Convert the box to a minipage and redefine the minipage
1113 # environment in ERT so that the original box is simulated.
1114 # For minipages we could do this in a way that the width and
1115 # position can still be set from LyX, but this did not work well.
1116 # This is not possible for parboxes either, so we convert the
1117 # original box to ERT, put the minipage inset inside the box
1118 # and redefine the minipage environment to be empty.
1120 # Commands that are independant of a particular box can go to
1122 # We need to define lyxtolyxrealminipage with 3 optional
1123 # arguments although LyX 1.3 uses only the first one.
1124 # Otherwise we will get LaTeX errors if this document is
1125 # converted to format 225 or above again (LyX 1.4 uses all
1126 # optional arguments).
1127 add_to_preamble(file,
1128 ['% Commands inserted by lyx2lyx for frameless boxes',
1129 '% Save the original minipage environment',
1130 '\\let\\lyxtolyxrealminipage\\minipage',
1131 '\\let\\endlyxtolyxrealminipage\\endminipage',
1132 '% Define an empty lyxtolyximinipage environment',
1133 '% with 3 optional arguments',
1134 '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1135 '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1136 ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1137 ' {\\end{lyxtolyxiiiminipage}}',
1138 '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1139 ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1140 ' {\\end{lyxtolyxiiminipage}}',
1141 '\\newenvironment{lyxtolyxminipage}[1][c]%',
1142 ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1143 ' {\\end{lyxtolyximinipage}}'])
1145 if params['use_parbox'] != '0':
1148 ert = '\\begin{lyxtolyxrealminipage}'
1150 # convert optional arguments only if not latex default
1151 if (pos[params['position']] != 'c' or
1152 inner_pos[params['inner_pos']] != pos[params['position']] or
1153 params['height_special'] != 'totalheight' or
1154 len2value(params['height']) != 1.0):
1155 ert = ert + '[' + pos[params['position']] + ']'
1156 if (inner_pos[params['inner_pos']] != pos[params['position']] or
1157 params['height_special'] != 'totalheight' or
1158 len2value(params['height']) != 1.0):
1159 ert = ert + '[' + convert_len(params['height'],
1160 params['height_special']) + ']'
1161 if inner_pos[params['inner_pos']] != pos[params['position']]:
1162 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1164 ert = ert + '{' + convert_len(params['width'],
1165 params['special']) + '}'
1167 if params['use_parbox'] != '0':
1169 ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1170 ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1173 i = insert_ert(file.body, i, 'Collapsed', ert)
1174 j = j + i - old_i - 1
1176 file.body[i:i] = ['\\begin_inset Minipage',
1177 'position %d' % params['position'],
1180 'width "' + params['width'] + '"',
1181 'collapsed ' + params['collapsed']]
1185 # Restore the original minipage environment since we may have
1186 # minipages inside this box.
1187 # Start a new paragraph because the following may be nonstandard
1188 file.body[i:i] = ['\\layout Standard', '', '']
1191 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1192 ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1194 i = insert_ert(file.body, i, 'Collapsed', ert)
1195 j = j + i - old_i - 1
1197 # Redefine the minipage end before the inset end.
1198 # Start a new paragraph because the previous may be nonstandard
1199 file.body[j:j] = ['\\layout Standard', '', '']
1201 ert = '\\let\\endminipage\\endlyxtolyxminipage'
1202 j = insert_ert(file.body, j, 'Collapsed', ert)
1204 file.body.insert(j, '')
1207 # LyX writes '%\n' after each box. Therefore we need to end our
1208 # ERT with '%\n', too, since this may swallow a following space.
1209 if params['use_parbox'] != '0':
1212 ert = '\\end{lyxtolyxrealminipage}%\n'
1213 j = insert_ert(file.body, j, 'Collapsed', ert)
1215 # We don't need to restore the original minipage after the inset
1216 # end because the scope of the redefinition is the original box.
1220 # Convert to minipage
1221 file.body[i:i] = ['\\begin_inset Minipage',
1222 'position %d' % params['position'],
1223 'inner_position %d' % params['inner_pos'],
1224 'height "' + params['height'] + '"',
1225 'width "' + params['width'] + '"',
1226 'collapsed ' + params['collapsed']]
1233 def convert_jurabib(file):
1234 i = find_token(file.header, '\\use_numerical_citations', 0)
1236 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1238 file.header.insert(i + 1, '\\use_jurabib 0')
1241 def revert_jurabib(file):
1242 i = find_token(file.header, '\\use_jurabib', 0)
1244 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1246 if get_value(file.header, '\\use_jurabib', 0) != "0":
1247 file.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1248 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1256 def convert_bibtopic(file):
1257 i = find_token(file.header, '\\use_jurabib', 0)
1259 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1261 file.header.insert(i + 1, '\\use_bibtopic 0')
1264 def revert_bibtopic(file):
1265 i = find_token(file.header, '\\use_bibtopic', 0)
1267 file.warning("Malformed lyx file: Missing '\\use_bibtopic'.")
1269 if get_value(file.header, '\\use_bibtopic', 0) != "0":
1270 file.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1271 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1278 def convert_float(file):
1281 i = find_token(file.body, '\\begin_inset Float', i)
1284 # Seach for a line starting 'wide'
1285 # If, however, we find a line starting '\begin_layout'
1286 # (_always_ present) then break with a warning message
1289 if (file.body[i][:4] == "wide"):
1290 file.body.insert(i + 1, 'sideways false')
1292 elif (file.body[i][:13] == "\\begin_layout"):
1293 file.warning("Malformed lyx file: Missing 'wide'.")
1299 def revert_float(file):
1302 i = find_token(file.body, '\\begin_inset Float', i)
1305 j = find_end_of_inset(file.body, i)
1307 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1310 if get_value(file.body, 'sideways', i, j) != "false":
1311 file.warning("Conversion of 'sideways true' not yet implemented.")
1312 # Don't remove 'sideways' so that people will get warnings by lyx
1315 del_token(file.body, 'sideways', i, j)
1319 def convert_graphics(file):
1320 """ Add extension to filenames of insetgraphics if necessary.
1324 i = find_token(file.body, "\\begin_inset Graphics", i)
1328 j = find_token2(file.body, "filename", i)
1332 filename = split(file.body[j])[1]
1333 absname = os.path.normpath(os.path.join(file.dir, filename))
1334 if file.input == stdin and not os.path.isabs(filename):
1335 # We don't know the directory and cannot check the file.
1336 # We could use a heuristic and take the current directory,
1337 # and we could try to find out if filename has an extension,
1338 # but that would be just guesses and could be wrong.
1339 file.warning("""Warning: Can not determine whether file
1341 needs an extension when reading from standard input.
1342 You may need to correct the file manually or run
1343 lyx2lyx again with the .lyx file as commandline argument.""" % filename)
1345 # This needs to be the same algorithm as in pre 233 insetgraphics
1346 if access(absname, F_OK):
1348 if access(absname + ".ps", F_OK):
1349 file.body[j] = replace(file.body[j], filename, filename + ".ps")
1351 if access(absname + ".eps", F_OK):
1352 file.body[j] = replace(file.body[j], filename, filename + ".eps")
1356 # Convert firstname and surname from styles -> char styles
1358 def convert_names(file):
1359 """ Convert in the docbook backend from firstname and surname style
1362 if file.backend != "docbook":
1368 i = find_token(file.body, "\\begin_layout Author", i)
1373 while file.body[i] == "":
1376 if file.body[i][:11] != "\\end_layout" or file.body[i+2][:13] != "\\begin_deeper":
1381 i = find_end_of( file.body, i+3, "\\begin_deeper","\\end_deeper")
1383 # something is really wrong, abort
1384 file.warning("Missing \\end_deeper, after style Author.")
1385 file.warning("Aborted attempt to parse FirstName and Surname.")
1387 firstname, surname = "", ""
1389 name = file.body[k:i]
1391 j = find_token(name, "\\begin_layout FirstName", 0)
1394 while(name[j] != "\\end_layout"):
1395 firstname = firstname + name[j]
1398 j = find_token(name, "\\begin_layout Surname", 0)
1401 while(name[j] != "\\end_layout"):
1402 surname = surname + name[j]
1406 del file.body[k+2:i+1]
1408 file.body[k-1:k-1] = ["", "",
1409 "\\begin_inset CharStyle Firstname",
1412 "\\begin_layout Standard",
1420 "\\begin_inset CharStyle Surname",
1423 "\\begin_layout Standard",
1432 def revert_names(file):
1433 """ Revert in the docbook backend from firstname and surname char style
1436 if file.backend != "docbook":
1441 # \use_natbib 1 \cite_engine <style>
1442 # \use_numerical_citations 0 -> where <style> is one of
1443 # \use_jurabib 0 "basic", "natbib_authoryear",
1444 # "natbib_numerical" or "jurabib"
1445 def convert_cite_engine(file):
1446 a = find_token(file.header, "\\use_natbib", 0)
1448 file.warning("Malformed lyx file: Missing '\\use_natbib'.")
1451 b = find_token(file.header, "\\use_numerical_citations", 0)
1452 if b == -1 or b != a+1:
1453 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1456 c = find_token(file.header, "\\use_jurabib", 0)
1457 if c == -1 or c != b+1:
1458 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1461 use_natbib = int(split(file.header[a])[1])
1462 use_numerical_citations = int(split(file.header[b])[1])
1463 use_jurabib = int(split(file.header[c])[1])
1465 cite_engine = "basic"
1467 if use_numerical_citations:
1468 cite_engine = "natbib_numerical"
1470 cite_engine = "natbib_authoryear"
1472 cite_engine = "jurabib"
1474 del file.header[a:c+1]
1475 file.header.insert(a, "\\cite_engine " + cite_engine)
1478 def revert_cite_engine(file):
1479 i = find_token(file.header, "\\cite_engine", 0)
1481 file.warning("Malformed lyx file: Missing '\\cite_engine'.")
1484 cite_engine = split(file.header[i])[1]
1489 if cite_engine == "natbib_numerical":
1492 elif cite_engine == "natbib_authoryear":
1494 elif cite_engine == "jurabib":
1498 file.header.insert(i, "\\use_jurabib " + use_jurabib)
1499 file.header.insert(i, "\\use_numerical_citations " + use_numerical)
1500 file.header.insert(i, "\\use_natbib " + use_natbib)
1506 def convert_paperpackage(file):
1507 i = find_token(file.header, "\\paperpackage", 0)
1509 file.warning("Malformed lyx file: Missing '\\paperpackage'.")
1512 packages = {'a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
1513 paperpackage = split(file.header[i])[1]
1514 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1517 def revert_paperpackage(file):
1518 i = find_token(file.header, "\\paperpackage", 0)
1520 file.warning("Malformed lyx file: Missing '\\paperpackage'.")
1523 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
1525 paperpackage = split(file.header[i])[1]
1526 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1532 def convert_bullets(file):
1535 i = find_token(file.header, "\\bullet", i)
1538 if file.header[i][:12] == '\\bulletLaTeX':
1539 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1])
1542 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1]) +\
1543 ' ' + strip(file.header[i+2]) + ' ' + strip(file.header[i+3])
1545 del file.header[i+1:i + n]
1549 def revert_bullets(file):
1552 i = find_token(file.header, "\\bullet", i)
1555 if file.header[i][:12] == '\\bulletLaTeX':
1556 n = find(file.header[i], '"')
1558 file.warning("Malformed header.")
1561 file.header[i:i+1] = [file.header[i][:n-1],'\t' + file.header[i][n:], '\\end_bullet']
1564 frag = split(file.header[i])
1566 file.warning("Malformed header.")
1569 file.header[i:i+1] = [frag[0] + ' ' + frag[1],
1578 # \begin_header and \begin_document
1580 def add_begin_header(file):
1581 i = find_token(file.header, '\\lyxformat', 0)
1582 file.header.insert(i+1, '\\begin_header')
1583 file.header.insert(i+1, '\\begin_document')
1586 def remove_begin_header(file):
1587 i = find_token(file.header, "\\begin_document", 0)
1590 i = find_token(file.header, "\\begin_header", 0)
1596 # \begin_file.body and \end_file.body
1598 def add_begin_body(file):
1599 file.body.insert(0, '\\begin_body')
1600 file.body.insert(1, '')
1601 i = find_token(file.body, "\\end_document", 0)
1602 file.body.insert(i, '\\end_body')
1604 def remove_begin_body(file):
1605 i = find_token(file.body, "\\begin_body", 0)
1608 if not file.body[i]:
1610 i = find_token(file.body, "\\end_body", 0)
1618 def normalize_papersize(file):
1619 i = find_token(file.header, '\\papersize', 0)
1623 tmp = split(file.header[i])
1624 if tmp[1] == "Default":
1625 file.header[i] = '\\papersize default'
1627 if tmp[1] == "Custom":
1628 file.header[i] = '\\papersize custom'
1631 def denormalize_papersize(file):
1632 i = find_token(file.header, '\\papersize', 0)
1636 tmp = split(file.header[i])
1637 if tmp[1] == "custom":
1638 file.header[i] = '\\papersize Custom'
1642 # Strip spaces at end of command line
1644 def strip_end_space(file):
1645 for i in range(len(file.body)):
1646 if file.body[i][:1] == '\\':
1647 file.body[i] = strip(file.body[i])
1651 # Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes
1653 def use_x_boolean(file):
1654 bin2bool = {'0': 'false', '1': 'true'}
1655 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
1656 i = find_token(file.header, use, 0)
1659 decompose = split(file.header[i])
1660 file.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
1663 def use_x_binary(file):
1664 bool2bin = {'false': '0', 'true': '1'}
1665 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
1666 i = find_token(file.header, use, 0)
1669 decompose = split(file.header[i])
1670 file.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
1673 # Place all the paragraph parameters in their own line
1675 def normalize_paragraph_params(file):
1677 allowed_parameters = '\\paragraph_spacing', '\\noindent', '\\align', '\\labelwidthstring', "\\start_of_appendix"
1681 i = find_token(file.body, '\\begin_layout', i)
1687 if strip(body[i]) and split(body[i])[0] not in allowed_parameters:
1690 j = find(body[i],'\\', 1)
1693 body[i:i+1] = [strip(body[i][:j]), body[i][j:]]
1699 # Add/remove output_changes parameter
1701 def convert_output_changes (file):
1702 i = find_token(file.header, '\\tracking_changes', 0)
1704 file.warning("Malformed lyx file: Missing '\\tracking_changes'.")
1706 file.header.insert(i+1, '\\output_changes true')
1709 def revert_output_changes (file):
1710 i = find_token(file.header, '\\output_changes', 0)
1717 # Convert paragraph breaks and sanitize paragraphs
1719 def convert_ert_paragraphs(file):
1720 forbidden_settings = [
1721 # paragraph parameters
1722 '\\paragraph_spacing', '\\labelwidthstring',
1723 '\\start_of_appendix', '\\noindent',
1724 '\\leftindent', '\\align',
1726 '\\family', '\\series', '\\shape', '\\size',
1727 '\\emph', '\\numeric', '\\bar', '\\noun',
1728 '\\color', '\\lang']
1731 i = find_token(file.body, '\\begin_inset ERT', i)
1734 j = find_end_of_inset(file.body, i)
1736 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1740 # convert non-standard paragraphs to standard
1743 k = find_token(file.body, "\\begin_layout", k, j)
1746 file.body[k] = "\\begin_layout Standard"
1749 # remove all paragraph parameters and font settings
1752 if (strip(file.body[k]) and
1753 split(file.body[k])[0] in forbidden_settings):
1759 # insert an empty paragraph before each paragraph but the first
1763 k = find_token(file.body, "\\begin_layout Standard", k, j)
1770 file.body[k:k] = ["\\begin_layout Standard", "",
1775 # convert \\newline to new paragraph
1778 k = find_token(file.body, "\\newline", k, j)
1781 file.body[k:k+1] = ["\\end_layout", "", "\\begin_layout Standard"]
1788 # Remove double paragraph breaks
1790 def revert_ert_paragraphs(file):
1793 i = find_token(file.body, '\\begin_inset ERT', i)
1796 j = find_end_of_inset(file.body, i)
1798 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1802 # replace paragraph breaks with \newline
1805 k = find_token(file.body, "\\end_layout", k, j)
1806 l = find_token(file.body, "\\begin_layout", k, j)
1807 if k == -1 or l == -1:
1809 file.body[k:l+1] = ["\\newline"]
1813 # replace double \newlines with paragraph breaks
1816 k = find_token(file.body, "\\newline", k, j)
1820 while file.body[l] == "":
1822 if strip(file.body[l]) and split(file.body[l])[0] == "\\newline":
1823 file.body[k:l+1] = ["\\end_layout", "",
1824 "\\begin_layout Standard"]
1836 convert = [[223, [insert_tracking_changes, add_end_header, remove_color_default,
1837 convert_spaces, convert_bibtex, remove_insetparent]],
1838 [224, [convert_external, convert_comment]],
1839 [225, [add_end_layout, layout2begin_layout, convert_end_document,
1840 convert_table_valignment_middle, convert_breaks]],
1841 [226, [convert_note]],
1842 [227, [convert_box]],
1843 [228, [convert_collapsable, convert_ert]],
1844 [229, [convert_minipage]],
1845 [230, [convert_jurabib]],
1846 [231, [convert_float]],
1847 [232, [convert_bibtopic]],
1848 [233, [convert_graphics, convert_names]],
1849 [234, [convert_cite_engine]],
1850 [235, [convert_paperpackage]],
1851 [236, [convert_bullets, add_begin_header, add_begin_body,
1852 normalize_papersize, strip_end_space]],
1853 [237, [use_x_boolean]],
1854 [238, [update_latexaccents]],
1855 [239, [normalize_paragraph_params]],
1856 [240, [convert_output_changes]],
1857 [241, [convert_ert_paragraphs]]]
1859 revert = [[240, [revert_ert_paragraphs]],
1860 [239, [revert_output_changes]],
1863 [236, [use_x_binary]],
1864 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
1866 [234, [revert_paperpackage]],
1867 [233, [revert_cite_engine]],
1868 [232, [revert_names]],
1869 [231, [revert_bibtopic]],
1870 [230, [revert_float]],
1871 [229, [revert_jurabib]],
1873 [227, [revert_collapsable, revert_ert]],
1874 [226, [revert_box, revert_external_2]],
1875 [225, [revert_note]],
1876 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
1877 revert_valignment_middle, convert_vspace, convert_frameless_box]],
1878 [223, [revert_external_2, revert_comment, revert_eqref]],
1879 [221, [rm_end_header, revert_spaces, revert_bibtex,
1880 rm_tracking_changes, rm_body_changes]]]
1883 if __name__ == "__main__":