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 ~")
75 def convert_bibtex(file):
76 for i in range(len(file.body)):
77 file.body[i] = replace(file.body[i],"\\begin_inset LatexCommand \\BibTeX",
78 "\\begin_inset LatexCommand \\bibtex")
81 def revert_bibtex(file):
82 for i in range(len(file.body)):
83 file.body[i] = replace(file.body[i], "\\begin_inset LatexCommand \\bibtex",
84 "\\begin_inset LatexCommand \\BibTeX")
90 def remove_insetparent(file):
93 i = find_token(file.body, "\\begin_inset LatexCommand \\lyxparent", i)
102 def convert_external(file):
103 external_rexp = re.compile(r'\\begin_inset External ([^,]*),"([^"]*)",')
104 external_header = "\\begin_inset External"
107 i = find_token(file.body, external_header, i)
110 look = external_rexp.search(file.body[i])
113 args[0] = look.group(1)
114 args[1] = look.group(2)
115 #FIXME: if the previous search fails then warn
117 if args[0] == "RasterImage":
118 # Convert a RasterImage External Inset to a Graphics Inset.
119 top = "\\begin_inset Graphics"
121 filename = "\tfilename " + args[1]
122 file.body[i:i+1] = [top, filename]
125 # Convert the old External Inset format to the new.
126 top = external_header
127 template = "\ttemplate " + args[0]
129 filename = "\tfilename " + args[1]
130 file.body[i:i+1] = [top, template, filename]
133 file.body[i:i+1] = [top, template]
137 def revert_external_1(file):
138 external_header = "\\begin_inset External"
141 i = find_token(file.body, external_header, i)
145 template = split(file.body[i+1])
149 filename = split(file.body[i+1])
153 params = split(file.body[i+1])
155 if file.body[i+1]: del file.body[i+1]
157 file.body[i] = file.body[i] + " " + template[0]+ ', "' + filename[0] + '", " '+ join(params[1:]) + '"'
161 def revert_external_2(file):
162 draft_token = '\tdraft'
165 i = find_token(file.body, '\\begin_inset External', i)
168 j = find_end_of_inset(file.body, i + 1)
170 #this should not happen
172 k = find_token(file.body, draft_token, i+1, j-1)
173 if (k != -1 and len(draft_token) == len(file.body[k])):
181 def convert_comment(file):
183 comment = "\\layout Comment"
185 i = find_token(file.body, comment, i)
189 file.body[i:i+1] = ["\\layout Standard","","",
190 "\\begin_inset Comment",
197 i = find_token(file.body, "\\layout", i)
199 i = len(file.body) - 1
200 file.body[i:i] = ["\\end_inset","",""]
203 j = find_token(file.body, '\\begin_deeper', old_i, i)
204 if j == -1: j = i + 1
205 k = find_token(file.body, '\\begin_inset', old_i, i)
206 if k == -1: k = i + 1
211 i = find_end_of( file.body, i, "\\begin_deeper","\\end_deeper")
213 #This case should not happen
214 #but if this happens deal with it greacefully adding
215 #the missing \end_deeper.
216 i = len(file.body) - 1
217 file.body[i:i] = ["\end_deeper",""]
225 i = find_end_of( file.body, i, "\\begin_inset","\\end_inset")
227 #This case should not happen
228 #but if this happens deal with it greacefully adding
229 #the missing \end_inset.
230 i = len(file.body) - 1
231 file.body[i:i] = ["\\end_inset","","","\\end_inset","",""]
237 if find(file.body[i], comment) == -1:
238 file.body[i:i] = ["\\end_inset"]
241 file.body[i:i+1] = ["\\layout Standard"]
245 def revert_comment(file):
248 i = find_tokens(file.body, ["\\begin_inset Comment", "\\begin_inset Greyedout"], i)
252 file.body[i] = "\\begin_inset Note"
259 def add_end_layout(file):
260 i = find_token(file.body, '\\layout', 0)
266 struct_stack = ["\\layout"]
269 i = find_tokens(file.body, ["\\begin_inset", "\\end_inset", "\\layout",
270 "\\begin_deeper", "\\end_deeper", "\\the_end"], i)
272 token = split(file.body[i])[0]
274 if token == "\\begin_inset":
275 struct_stack.append(token)
279 if token == "\\end_inset":
280 tail = struct_stack.pop()
281 if tail == "\\layout":
282 file.body.insert(i,"")
283 file.body.insert(i,"\\end_layout")
285 #Check if it is the correct tag
290 if token == "\\layout":
291 tail = struct_stack.pop()
293 file.body.insert(i,"")
294 file.body.insert(i,"\\end_layout")
297 struct_stack.append(tail)
299 struct_stack.append(token)
302 if token == "\\begin_deeper":
303 file.body.insert(i,"")
304 file.body.insert(i,"\\end_layout")
306 struct_stack.append(token)
309 if token == "\\end_deeper":
310 if struct_stack[-1] == '\\layout':
311 file.body.insert(i, '\\end_layout')
318 file.body.insert(i, "")
319 file.body.insert(i, "\\end_layout")
323 def rm_end_layout(file):
326 i = find_token(file.body, '\\end_layout', i)
335 # Handle change tracking keywords
337 def insert_tracking_changes(file):
338 i = find_token(file.header, "\\tracking_changes", 0)
340 file.header.append("\\tracking_changes 0")
343 def rm_tracking_changes(file):
344 i = find_token(file.header, "\\author", 0)
348 i = find_token(file.header, "\\tracking_changes", 0)
354 def rm_body_changes(file):
357 i = find_token(file.body, "\\change_", i)
365 # \layout -> \begin_layout
367 def layout2begin_layout(file):
370 i = find_token(file.body, '\\layout', i)
374 file.body[i] = replace(file.body[i], '\\layout', '\\begin_layout')
378 def begin_layout2layout(file):
381 i = find_token(file.body, '\\begin_layout', i)
385 file.body[i] = replace(file.body[i], '\\begin_layout', '\\layout')
390 # valignment="center" -> valignment="middle"
392 def convert_valignment_middle(body, start, end):
393 for i in range(start, end):
394 if re.search('^<(column|cell) .*valignment="center".*>$', body[i]):
395 body[i] = replace(body[i], 'valignment="center"', 'valignment="middle"')
398 def convert_table_valignment_middle(file):
399 regexp = re.compile(r'^\\begin_inset\s+Tabular')
402 i = find_re(file.body, regexp, i)
405 j = find_end_of_inset(file.body, i + 1)
407 #this should not happen
408 convert_valignment_middle(file.body, i + 1, len(file.body))
410 convert_valignment_middle(file.body, i + 1, j)
414 def revert_table_valignment_middle(body, start, end):
415 for i in range(start, end):
416 if re.search('^<(column|cell) .*valignment="middle".*>$', body[i]):
417 body[i] = replace(body[i], 'valignment="middle"', 'valignment="center"')
420 def revert_valignment_middle(file):
421 regexp = re.compile(r'^\\begin_inset\s+Tabular')
424 i = find_re(file.body, regexp, i)
427 j = find_end_of_inset(file.body, i + 1)
429 #this should not happen
430 revert_table_valignment_middle(file.body, i + 1, len(file.body))
432 revert_table_valignment_middle(file.body, i + 1, j)
437 # \the_end -> \end_document
439 def convert_end_document(file):
440 i = find_token(file.body, "\\the_end", 0)
442 file.body.append("\\end_document")
444 file.body[i] = "\\end_document"
447 def revert_end_document(file):
448 i = find_token(file.body, "\\end_document", 0)
450 file.body.append("\\the_end")
452 file.body[i] = "\\the_end"
456 # Convert line and page breaks
459 #\line_top \line_bottom \pagebreak_top \pagebreak_bottom \added_space_top xxx \added_space_bottom yyy
463 #\begin layout Standard
468 #\begin_inset VSpace xxx
472 #\begin_layout Standard
476 #\begin_layout Standard
478 #\begin_inset VSpace xxx
485 def convert_breaks(file):
488 i = find_token(file.body, "\\begin_layout", i)
492 line_top = find(file.body[i],"\\line_top")
493 line_bot = find(file.body[i],"\\line_bottom")
494 pb_top = find(file.body[i],"\\pagebreak_top")
495 pb_bot = find(file.body[i],"\\pagebreak_bottom")
496 vspace_top = find(file.body[i],"\\added_space_top")
497 vspace_bot = find(file.body[i],"\\added_space_bottom")
499 if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1:
502 for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom":
503 file.body[i] = replace(file.body[i], tag, "")
506 # the position could be change because of the removal of other
507 # paragraph properties above
508 vspace_top = find(file.body[i],"\\added_space_top")
509 tmp_list = split(file.body[i][vspace_top:])
510 vspace_top_value = tmp_list[1]
511 file.body[i] = file.body[i][:vspace_top] + join(tmp_list[2:])
514 # the position could be change because of the removal of other
515 # paragraph properties above
516 vspace_bot = find(file.body[i],"\\added_space_bottom")
517 tmp_list = split(file.body[i][vspace_bot:])
518 vspace_bot_value = tmp_list[1]
519 file.body[i] = file.body[i][:vspace_bot] + join(tmp_list[2:])
521 file.body[i] = strip(file.body[i])
524 # Create an empty paragraph for line and page break that belong
525 # above the paragraph
526 if pb_top !=-1 or line_top != -1 or vspace_bot != -1:
528 paragraph_above = ['','\\begin_layout Standard','','']
531 paragraph_above.extend(['\\newpage ',''])
534 paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','',''])
537 paragraph_above.extend(['\\lyxline ',''])
539 paragraph_above.extend(['\\end_layout',''])
541 #inset new paragraph above the current paragraph
542 file.body[i-2:i-2] = paragraph_above
543 i = i + len(paragraph_above)
545 # Ensure that nested style are converted later.
546 k = find_end_of(file.body, i, "\\begin_layout", "\\end_layout")
551 if pb_top !=-1 or line_top != -1 or vspace_bot != -1:
553 paragraph_bellow = ['','\\begin_layout Standard','','']
556 paragraph_bellow.extend(['\\lyxline ',''])
559 paragraph_bellow.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','',''])
562 paragraph_bellow.extend(['\\newpage ',''])
564 paragraph_bellow.extend(['\\end_layout',''])
566 #inset new paragraph above the current paragraph
567 file.body[k + 1: k + 1] = paragraph_bellow
573 def convert_note(file):
576 i = find_tokens(file.body, ["\\begin_inset Note",
577 "\\begin_inset Comment",
578 "\\begin_inset Greyedout"], i)
582 file.body[i] = file.body[i][0:13] + 'Note ' + file.body[i][13:]
586 def revert_note(file):
587 note_header = "\\begin_inset Note "
590 i = find_token(file.body, note_header, i)
594 file.body[i] = "\\begin_inset " + file.body[i][len(note_header):]
601 def convert_box(file):
604 i = find_tokens(file.body, ["\\begin_inset Boxed",
605 "\\begin_inset Doublebox",
606 "\\begin_inset Frameless",
607 "\\begin_inset ovalbox",
608 "\\begin_inset Ovalbox",
609 "\\begin_inset Shadowbox"], i)
613 file.body[i] = file.body[i][0:13] + 'Box ' + file.body[i][13:]
617 def revert_box(file):
618 box_header = "\\begin_inset Box "
621 i = find_token(file.body, box_header, i)
625 file.body[i] = "\\begin_inset " + file.body[i][len(box_header):]
632 def convert_collapsable(file):
635 i = find_tokens(file.body, ["\\begin_inset Box",
636 "\\begin_inset Branch",
637 "\\begin_inset CharStyle",
638 "\\begin_inset Float",
639 "\\begin_inset Foot",
640 "\\begin_inset Marginal",
641 "\\begin_inset Note",
642 "\\begin_inset OptArg",
643 "\\begin_inset Wrap"], i)
647 # Seach for a line starting 'collapsed'
648 # If, however, we find a line starting '\begin_layout'
649 # (_always_ present) then break with a warning message
652 if (file.body[i] == "collapsed false"):
653 file.body[i] = "status open"
655 elif (file.body[i] == "collapsed true"):
656 file.body[i] = "status collapsed"
658 elif (file.body[i][:13] == "\\begin_layout"):
659 file.warning("Malformed LyX file: Missing 'collapsed'.")
666 def revert_collapsable(file):
669 i = find_tokens(file.body, ["\\begin_inset Box",
670 "\\begin_inset Branch",
671 "\\begin_inset CharStyle",
672 "\\begin_inset Float",
673 "\\begin_inset Foot",
674 "\\begin_inset Marginal",
675 "\\begin_inset Note",
676 "\\begin_inset OptArg",
677 "\\begin_inset Wrap"], i)
681 # Seach for a line starting 'status'
682 # If, however, we find a line starting '\begin_layout'
683 # (_always_ present) then break with a warning message
686 if (file.body[i] == "status open"):
687 file.body[i] = "collapsed false"
689 elif (file.body[i] == "status collapsed" or
690 file.body[i] == "status inlined"):
691 file.body[i] = "collapsed true"
693 elif (file.body[i][:13] == "\\begin_layout"):
694 file.warning("Malformed LyX file: Missing 'status'.")
704 def convert_ert(file):
707 i = find_token(file.body, "\\begin_inset ERT", i)
711 # Seach for a line starting 'status'
712 # If, however, we find a line starting '\begin_layout'
713 # (_always_ present) then break with a warning message
716 if (file.body[i] == "status Open"):
717 file.body[i] = "status open"
719 elif (file.body[i] == "status Collapsed"):
720 file.body[i] = "status collapsed"
722 elif (file.body[i] == "status Inlined"):
723 file.body[i] = "status inlined"
725 elif (file.body[i][:13] == "\\begin_layout"):
726 file.warning("Malformed LyX file: Missing 'status'.")
733 def revert_ert(file):
736 i = find_token(file.body, "\\begin_inset ERT", i)
740 # Seach for a line starting 'status'
741 # If, however, we find a line starting '\begin_layout'
742 # (_always_ present) then break with a warning message
745 if (file.body[i] == "status open"):
746 file.body[i] = "status Open"
748 elif (file.body[i] == "status collapsed"):
749 file.body[i] = "status Collapsed"
751 elif (file.body[i] == "status inlined"):
752 file.body[i] = "status Inlined"
754 elif (file.body[i][:13] == "\\begin_layout"):
755 file.warning("Malformed LyX file : Missing 'status'.")
765 def convert_minipage(file):
766 """ Convert minipages to the box inset.
767 We try to use the same order of arguments as lyx does.
770 inner_pos = ["c","t","b","s"]
774 i = find_token(file.body, "\\begin_inset Minipage", i)
778 file.body[i] = "\\begin_inset Box Frameless"
781 # convert old to new position using the pos list
782 if file.body[i][:8] == "position":
783 file.body[i] = 'position "%s"' % pos[int(file.body[i][9])]
785 file.body.insert(i, 'position "%s"' % pos[0])
788 file.body.insert(i, 'hor_pos "c"')
790 file.body.insert(i, 'has_inner_box 1')
793 # convert the inner_position
794 if file.body[i][:14] == "inner_position":
795 file.body[i] = 'inner_pos "%s"' % inner_pos[int(file.body[i][15])]
797 file.body.insert('inner_pos "%s"' % inner_pos[0])
800 # We need this since the new file format has a height and width
801 # in a different order.
802 if file.body[i][:6] == "height":
803 height = file.body[i][6:]
804 # test for default value of 221 and convert it accordingly
805 if height == ' "0pt"':
811 if file.body[i][:5] == "width":
812 width = file.body[i][5:]
817 if file.body[i][:9] == "collapsed":
818 if file.body[i][9:] == "true":
826 file.body.insert(i, 'use_parbox 0')
828 file.body.insert(i, 'width' + width)
830 file.body.insert(i, 'special "none"')
832 file.body.insert(i, 'height' + height)
834 file.body.insert(i, 'height_special "totalheight"')
836 file.body.insert(i, 'status ' + status)
840 # -------------------------------------------------------------------------------------------
841 # Convert backslashes and '\n' into valid ERT code, append the converted
842 # text to body[i] and return the (maybe incremented) line index i
843 def convert_ertbackslash(body, i, ert):
846 body[i] = body[i] + '\\backslash '
850 body[i+1:i+1] = ['\\newline ', '']
853 body[i] = body[i] + c
857 def convert_vspace(file):
859 # Get default spaceamount
860 i = find_token(file.header, '\\defskip', 0)
862 defskipamount = 'medskip'
864 defskipamount = split(file.header[i])[1]
869 i = find_token(file.body, '\\begin_inset VSpace', i)
872 spaceamount = split(file.body[i])[2]
874 # Are we at the beginning or end of a paragraph?
876 start = get_paragraph(file.body, i) + 1
877 for k in range(start, i):
878 if is_nonempty_line(file.body[k]):
882 j = find_end_of_inset(file.body, i)
884 file.warning("Malformed LyX file: Missing '\\end_inset'.")
887 end = get_next_paragraph(file.body, i)
888 for k in range(j + 1, end):
889 if is_nonempty_line(file.body[k]):
893 # Convert to paragraph formatting if we are at the beginning or end
894 # of a paragraph and the resulting paragraph would not be empty
895 if ((paragraph_start and not paragraph_end) or
896 (paragraph_end and not paragraph_start)):
897 # The order is important: del and insert invalidate some indices
901 file.body.insert(start, '\\added_space_top ' + spaceamount + ' ')
903 file.body.insert(start, '\\added_space_bottom ' + spaceamount + ' ')
907 file.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
908 '\\layout Standard', '', '\\backslash ']
910 if spaceamount[-1] == '*':
911 spaceamount = spaceamount[:-1]
916 # Replace defskip by the actual value
917 if spaceamount == 'defskip':
918 spaceamount = defskipamount
920 # LaTeX does not know \\smallskip* etc
922 if spaceamount == 'smallskip':
923 spaceamount = '\\smallskipamount'
924 elif spaceamount == 'medskip':
925 spaceamount = '\\medskipamount'
926 elif spaceamount == 'bigskip':
927 spaceamount = '\\bigskipamount'
928 elif spaceamount == 'vfill':
929 spaceamount = '\\fill'
931 # Finally output the LaTeX code
932 if (spaceamount == 'smallskip' or spaceamount == 'medskip' or
933 spaceamount == 'bigskip' or spaceamount == 'vfill'):
934 file.body.insert(i, spaceamount)
937 file.body.insert(i, 'vspace*{')
939 file.body.insert(i, 'vspace{')
940 i = convert_ertbackslash(file.body, i, spaceamount)
941 file.body[i] = file.body[i] + '}'
945 # Convert a LyX length into a LaTeX length
946 def convert_len(len, special):
947 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
948 "page%":"\\pagewidth", "line%":"\\linewidth",
949 "theight%":"\\textheight", "pheight%":"\\pageheight"}
951 # Convert special lengths
952 if special != 'none':
953 len = '%f\\' % len2value(len) + special
955 # Convert LyX units to LaTeX units
956 for unit in units.keys():
957 if find(len, unit) != -1:
958 len = '%f' % (len2value(len) / 100) + units[unit]
964 # Convert a LyX length into valid ERT code and append it to body[i]
965 # Return the (maybe incremented) line index i
966 def convert_ertlen(body, i, len, special):
967 # Convert backslashes and insert the converted length into body
968 return convert_ertbackslash(body, i, convert_len(len, special))
971 # Return the value of len without the unit in numerical form
973 result = re.search('([+-]?[0-9.]+)', len)
975 return float(result.group(1))
976 # No number means 1.0
980 # Convert text to ERT and insert it at body[i]
981 # Return the index of the line after the inserted ERT
982 def insert_ert(body, i, status, text):
983 body[i:i] = ['\\begin_inset ERT', 'status ' + status, '',
984 '\\layout Standard', '']
986 i = convert_ertbackslash(body, i, text) + 1
987 body[i:i] = ['', '\\end_inset', '']
992 # Add text to the preamble if it is not already there.
993 # Only the first line is checked!
994 def add_to_preamble(file, text):
995 i = find_token(file.header, '\\begin_preamble', 0)
997 file.warning("Malformed LyX file: Missing '\\begin_preamble'.")
999 j = find_token(file.header, '\\end_preamble', i)
1001 file.warning("Malformed LyX file: Missing '\\end_preamble'.")
1003 if find_token(file.header, text[0], i, j) != -1:
1005 file.header[j:j] = text
1008 def convert_frameless_box(file):
1009 pos = ['t', 'c', 'b']
1010 inner_pos = ['c', 't', 'b', 's']
1013 i = find_token(file.body, '\\begin_inset Frameless', i)
1016 j = find_end_of_inset(file.body, i)
1018 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1025 params = {'position':'0', 'hor_pos':'c', 'has_inner_box':'1',
1026 'inner_pos':'1', 'use_parbox':'0', 'width':'100col%',
1027 'special':'none', 'height':'1in',
1028 'height_special':'totalheight', 'collapsed':'false'}
1029 for key in params.keys():
1030 value = replace(get_value(file.body, key, i, j), '"', '')
1032 if key == 'position':
1033 # convert new to old position: 'position "t"' -> 0
1034 value = find_token(pos, value, 0)
1037 elif key == 'inner_pos':
1038 # convert inner position
1039 value = find_token(inner_pos, value, 0)
1044 j = del_token(file.body, key, i, j)
1047 # Convert to minipage or ERT?
1048 # Note that the inner_position and height parameters of a minipage
1049 # inset are ignored and not accessible for the user, although they
1050 # are present in the file format and correctly read in and written.
1051 # Therefore we convert to ERT if they do not have their LaTeX
1052 # defaults. These are:
1053 # - the value of "position" for "inner_pos"
1054 # - "\totalheight" for "height"
1055 if (params['use_parbox'] != '0' or
1056 params['has_inner_box'] != '1' or
1057 params['special'] != 'none' or
1058 inner_pos[params['inner_pos']] != pos[params['position']] or
1059 params['height_special'] != 'totalheight' or
1060 len2value(params['height']) != 1.0):
1062 # Here we know that this box is not supported in file format 224.
1063 # Therefore we need to convert it to ERT. We can't simply convert
1064 # the beginning and end of the box to ERT, because the
1065 # box inset may contain layouts that are different from the
1066 # surrounding layout. After the conversion the contents of the
1067 # box inset is on the same level as the surrounding text, and
1068 # paragraph layouts and align parameters can get mixed up.
1070 # A possible solution for this problem:
1071 # Convert the box to a minipage and redefine the minipage
1072 # environment in ERT so that the original box is simulated.
1073 # For minipages we could do this in a way that the width and
1074 # position can still be set from LyX, but this did not work well.
1075 # This is not possible for parboxes either, so we convert the
1076 # original box to ERT, put the minipage inset inside the box
1077 # and redefine the minipage environment to be empty.
1079 # Commands that are independant of a particular box can go to
1081 # We need to define lyxtolyxrealminipage with 3 optional
1082 # arguments although LyX 1.3 uses only the first one.
1083 # Otherwise we will get LaTeX errors if this document is
1084 # converted to format 225 or above again (LyX 1.4 uses all
1085 # optional arguments).
1086 add_to_preamble(file,
1087 ['% Commands inserted by lyx2lyx for frameless boxes',
1088 '% Save the original minipage environment',
1089 '\\let\\lyxtolyxrealminipage\\minipage',
1090 '\\let\\endlyxtolyxrealminipage\\endminipage',
1091 '% Define an empty lyxtolyximinipage environment',
1092 '% with 3 optional arguments',
1093 '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1094 '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1095 ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1096 ' {\\end{lyxtolyxiiiminipage}}',
1097 '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1098 ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1099 ' {\\end{lyxtolyxiiminipage}}',
1100 '\\newenvironment{lyxtolyxminipage}[1][c]%',
1101 ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1102 ' {\\end{lyxtolyximinipage}}'])
1104 if params['use_parbox'] != '0':
1107 ert = '\\begin{lyxtolyxrealminipage}'
1109 # convert optional arguments only if not latex default
1110 if (pos[params['position']] != 'c' or
1111 inner_pos[params['inner_pos']] != pos[params['position']] or
1112 params['height_special'] != 'totalheight' or
1113 len2value(params['height']) != 1.0):
1114 ert = ert + '[' + pos[params['position']] + ']'
1115 if (inner_pos[params['inner_pos']] != pos[params['position']] or
1116 params['height_special'] != 'totalheight' or
1117 len2value(params['height']) != 1.0):
1118 ert = ert + '[' + convert_len(params['height'],
1119 params['height_special']) + ']'
1120 if inner_pos[params['inner_pos']] != pos[params['position']]:
1121 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1123 ert = ert + '{' + convert_len(params['width'],
1124 params['special']) + '}'
1126 if params['use_parbox'] != '0':
1128 ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1129 ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1132 i = insert_ert(file.body, i, 'Collapsed', ert)
1133 j = j + i - old_i - 1
1135 file.body[i:i] = ['\\begin_inset Minipage',
1136 'position %d' % params['position'],
1139 'width "' + params['width'] + '"',
1140 'collapsed ' + params['collapsed']]
1144 # Restore the original minipage environment since we may have
1145 # minipages inside this box.
1146 # Start a new paragraph because the following may be nonstandard
1147 file.body[i:i] = ['\\layout Standard', '', '']
1150 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1151 ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1153 i = insert_ert(file.body, i, 'Collapsed', ert)
1154 j = j + i - old_i - 1
1156 # Redefine the minipage end before the inset end.
1157 # Start a new paragraph because the previous may be nonstandard
1158 file.body[j:j] = ['\\layout Standard', '', '']
1160 ert = '\\let\\endminipage\\endlyxtolyxminipage'
1161 j = insert_ert(file.body, j, 'Collapsed', ert)
1163 file.body.insert(j, '')
1166 # LyX writes '%\n' after each box. Therefore we need to end our
1167 # ERT with '%\n', too, since this may swallow a following space.
1168 if params['use_parbox'] != '0':
1171 ert = '\\end{lyxtolyxrealminipage}%\n'
1172 j = insert_ert(file.body, j, 'Collapsed', ert)
1174 # We don't need to restore the original minipage after the inset
1175 # end because the scope of the redefinition is the original box.
1179 # Convert to minipage
1180 file.body[i:i] = ['\\begin_inset Minipage',
1181 'position %d' % params['position'],
1182 'inner_position %d' % params['inner_pos'],
1183 'height "' + params['height'] + '"',
1184 'width "' + params['width'] + '"',
1185 'collapsed ' + params['collapsed']]
1192 def convert_jurabib(file):
1193 i = find_token(file.header, '\\use_numerical_citations', 0)
1195 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1197 file.header.insert(i + 1, '\\use_jurabib 0')
1200 def revert_jurabib(file):
1201 i = find_token(file.header, '\\use_jurabib', 0)
1203 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1205 if get_value(file.header, '\\use_jurabib', 0) != "0":
1206 file.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1207 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1215 def convert_bibtopic(file):
1216 i = find_token(file.header, '\\use_jurabib', 0)
1218 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1220 file.header.insert(i + 1, '\\use_bibtopic 0')
1223 def revert_bibtopic(file):
1224 i = find_token(file.header, '\\use_bibtopic', 0)
1226 file.warning("Malformed lyx file: Missing '\\use_bibtopic'.")
1228 if get_value(file.header, '\\use_bibtopic', 0) != "0":
1229 file.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1230 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1237 def convert_float(file):
1240 i = find_token(file.body, '\\begin_inset Float', i)
1243 # Seach for a line starting 'wide'
1244 # If, however, we find a line starting '\begin_layout'
1245 # (_always_ present) then break with a warning message
1248 if (file.body[i][:4] == "wide"):
1249 file.body.insert(i + 1, 'sideways false')
1251 elif (file.body[i][:13] == "\\begin_layout"):
1252 file.warning("Malformed lyx file: Missing 'wide'.")
1258 def revert_float(file):
1261 i = find_token(file.body, '\\begin_inset Float', i)
1264 j = find_end_of_inset(file.body, i)
1266 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1269 if get_value(file.body, 'sideways', i, j) != "false":
1270 file.warning("Conversion of 'sideways true' not yet implemented.")
1271 # Don't remove 'sideways' so that people will get warnings by lyx
1274 del_token(file.body, 'sideways', i, j)
1278 def convert_graphics(file):
1279 """ Add extension to filenames of insetgraphics if necessary.
1283 i = find_token(file.body, "\\begin_inset Graphics", i)
1287 j = find_token2(file.body, "filename", i)
1291 filename = split(file.body[j])[1]
1292 absname = os.path.normpath(os.path.join(file.dir, filename))
1293 if file.input == stdin and not os.path.isabs(filename):
1294 # We don't know the directory and cannot check the file.
1295 # We could use a heuristic and take the current directory,
1296 # and we could try to find out if filename has an extension,
1297 # but that would be just guesses and could be wrong.
1298 file.warning("""Warning: Can not determine whether file
1300 needs an extension when reading from standard input.
1301 You may need to correct the file manually or run
1302 lyx2lyx again with the .lyx file as commandline argument.""" % filename)
1304 # This needs to be the same algorithm as in pre 233 insetgraphics
1305 if access(absname, F_OK):
1307 if access(absname + ".ps", F_OK):
1308 file.body[j] = replace(file.body[j], filename, filename + ".ps")
1310 if access(absname + ".eps", F_OK):
1311 file.body[j] = replace(file.body[j], filename, filename + ".eps")
1315 # Convert firstname and surname from styles -> char styles
1317 def convert_names(file):
1318 """ Convert in the docbook backend from firstname and surname style
1321 if file.backend != "docbook":
1327 i = find_token(file.body, "\\begin_layout Author", i)
1332 while file.body[i] == "":
1335 if file.body[i][:11] != "\\end_layout" or file.body[i+2][:13] != "\\begin_deeper":
1340 i = find_end_of( file.body, i+3, "\\begin_deeper","\\end_deeper")
1342 # something is really wrong, abort
1343 file.warning("Missing \\end_deeper, after style Author.")
1344 file.warning("Aborted attempt to parse FirstName and Surname.")
1346 firstname, surname = "", ""
1348 name = file.body[k:i]
1350 j = find_token(name, "\\begin_layout FirstName", 0)
1353 while(name[j] != "\\end_layout"):
1354 firstname = firstname + name[j]
1357 j = find_token(name, "\\begin_layout Surname", 0)
1360 while(name[j] != "\\end_layout"):
1361 surname = surname + name[j]
1365 del file.body[k+2:i+1]
1367 file.body[k-1:k-1] = ["", "",
1368 "\\begin_inset CharStyle Firstname",
1371 "\\begin_layout Standard",
1379 "\\begin_inset CharStyle Surname",
1382 "\\begin_layout Standard",
1391 def revert_names(file):
1392 """ Revert in the docbook backend from firstname and surname char style
1395 if file.backend != "docbook":
1400 # \use_natbib 1 \cite_engine <style>
1401 # \use_numerical_citations 0 -> where <style> is one of
1402 # \use_jurabib 0 "basic", "natbib_authoryear",
1403 # "natbib_numerical" or "jurabib"
1404 def convert_cite_engine(file):
1405 a = find_token(file.header, "\\use_natbib", 0)
1407 file.warning("Malformed lyx file: Missing '\\use_natbib'.")
1410 b = find_token(file.header, "\\use_numerical_citations", 0)
1411 if b == -1 or b != a+1:
1412 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1415 c = find_token(file.header, "\\use_jurabib", 0)
1416 if c == -1 or c != b+1:
1417 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1420 use_natbib = int(split(file.header[a])[1])
1421 use_numerical_citations = int(split(file.header[b])[1])
1422 use_jurabib = int(split(file.header[c])[1])
1424 cite_engine = "basic"
1426 if use_numerical_citations:
1427 cite_engine = "natbib_numerical"
1429 cite_engine = "natbib_authoryear"
1431 cite_engine = "jurabib"
1433 del file.header[a:c+1]
1434 file.header.insert(a, "\\cite_engine " + cite_engine)
1437 def revert_cite_engine(file):
1438 i = find_token(file.header, "\\cite_engine", 0)
1440 file.warning("Malformed lyx file: Missing '\\cite_engine'.")
1443 cite_engine = split(file.header[i])[1]
1448 if cite_engine == "natbib_numerical":
1451 elif cite_engine == "natbib_authoryear":
1453 elif cite_engine == "jurabib":
1457 file.header.insert(i, "\\use_jurabib " + use_jurabib)
1458 file.header.insert(i, "\\use_numerical_citations " + use_numerical)
1459 file.header.insert(i, "\\use_natbib " + use_natbib)
1465 def convert_paperpackage(file):
1466 i = find_token(file.header, "\\paperpackage", 0)
1468 file.warning("Malformed lyx file: Missing '\\paperpackage'.")
1471 packages = {'a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
1472 paperpackage = split(file.header[i])[1]
1473 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1476 def revert_paperpackage(file):
1477 i = find_token(file.header, "\\paperpackage", 0)
1479 file.warning("Malformed lyx file: Missing '\\paperpackage'.")
1482 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
1484 paperpackage = split(file.header[i])[1]
1485 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1491 def convert_bullets(file):
1494 i = find_token(file.header, "\\bullet", i)
1497 if file.header[i][:12] == '\\bulletLaTeX':
1498 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1])
1501 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1]) +\
1502 ' ' + strip(file.header[i+2]) + ' ' + strip(file.header[i+3])
1504 del file.header[i+1:i + n]
1508 def revert_bullets(file):
1511 i = find_token(file.header, "\\bullet", i)
1514 if file.header[i][:12] == '\\bulletLaTeX':
1515 n = find(file.header[i], '"')
1517 file.warning("Malformed header.")
1520 file.header[i:i+1] = [file.header[i][:n-1],'\t' + file.header[i][n:], '\\end_bullet']
1523 frag = split(file.header[i])
1525 file.warning("Malformed header.")
1528 file.header[i:i+1] = [frag[0] + ' ' + frag[1],
1537 # \begin_header and \begin_document
1539 def add_begin_header(file):
1540 i = find_token(file.header, '\\lyxformat', 0)
1541 file.header.insert(i+1, '\\begin_header')
1542 file.header.insert(i+1, '\\begin_document')
1545 def remove_begin_header(file):
1546 i = find_token(file.header, "\\begin_document", 0)
1549 i = find_token(file.header, "\\begin_header", 0)
1555 # \begin_file.body and \end_file.body
1557 def add_begin_body(file):
1558 file.body.insert(0, '\\begin_body')
1559 file.body.insert(1, '')
1560 i = find_token(file.body, "\\end_document", 0)
1561 file.body.insert(i, '\\end_body')
1563 def remove_begin_body(file):
1564 i = find_token(file.body, "\\begin_body", 0)
1567 if not file.body[i]:
1569 i = find_token(file.body, "\\end_body", 0)
1577 def normalize_papersize(file):
1578 i = find_token(file.header, '\\papersize', 0)
1582 tmp = split(file.header[i])
1583 if tmp[1] == "Default":
1584 file.header[i] = '\\papersize default'
1586 if tmp[1] == "Custom":
1587 file.header[i] = '\\papersize custom'
1590 def denormalize_papersize(file):
1591 i = find_token(file.header, '\\papersize', 0)
1595 tmp = split(file.header[i])
1596 if tmp[1] == "custom":
1597 file.header[i] = '\\papersize Custom'
1601 # Strip spaces at end of command line
1603 def strip_end_space(file):
1604 for i in range(len(file.body)):
1605 if file.body[i][:1] == '\\':
1606 file.body[i] = strip(file.body[i])
1610 # Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes
1612 def use_x_boolean(file):
1613 bin2bool = {'0': 'false', '1': 'true'}
1614 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
1615 i = find_token(file.header, use, 0)
1618 decompose = split(file.header[i])
1619 file.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
1622 def use_x_binary(file):
1623 bool2bin = {'false': '0', 'true': '1'}
1624 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
1625 i = find_token(file.header, use, 0)
1628 decompose = split(file.header[i])
1629 file.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
1632 # Place all the paragraph parameters in their own line
1634 def normalize_paragraph_params(file):
1636 allowed_parameters = '\\paragraph_spacing', '\\noindent', '\\align', '\\labelwidthstring', "\\start_of_appendix"
1640 i = find_token(file.body, '\\begin_layout', i)
1646 if strip(body[i]) and split(body[i])[0] not in allowed_parameters:
1649 j = find(body[i],'\\', 1)
1652 body[i:i+1] = [strip(body[i][:j]), body[i][j:]]
1658 # Add/remove output_changes parameter
1660 def convert_output_changes (file):
1661 i = find_token(file.header, '\\tracking_changes', 0)
1663 file.warning("Malformed lyx file: Missing '\\tracking_changes'.")
1665 file.header.insert(i+1, '\\output_changes true')
1668 def revert_output_changes (file):
1669 i = find_token(file.header, '\\output_changes', 0)
1676 # Convert paragraph breaks and sanitize paragraphs
1678 def convert_ert_paragraphs(file):
1679 forbidden_settings = [
1680 # paragraph parameters
1681 '\\paragraph_spacing', '\\labelwidthstring',
1682 '\\start_of_appendix', '\\noindent',
1683 '\\leftindent', '\\align',
1685 '\\family', '\\series', '\\shape', '\\size',
1686 '\\emph', '\\numeric', '\\bar', '\\noun',
1687 '\\color', '\\lang']
1690 i = find_token(file.body, '\\begin_inset ERT', i)
1693 j = find_end_of_inset(file.body, i)
1695 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1699 # convert non-standard paragraphs to standard
1702 k = find_token(file.body, "\\begin_layout", k, j)
1705 file.body[k] = "\\begin_layout Standard"
1708 # remove all paragraph parameters and font settings
1711 if (strip(file.body[k]) and
1712 split(file.body[k])[0] in forbidden_settings):
1718 # insert an empty paragraph before each paragraph but the first
1722 k = find_token(file.body, "\\begin_layout Standard", k, j)
1729 file.body[k:k] = ["\\begin_layout Standard", "",
1734 # convert \\newline to new paragraph
1737 k = find_token(file.body, "\\newline", k, j)
1740 file.body[k:k+1] = ["\\end_layout", "", "\\begin_layout Standard"]
1747 # Remove double paragraph breaks
1749 def revert_ert_paragraphs(file):
1752 i = find_token(file.body, '\\begin_inset ERT', i)
1755 j = find_end_of_inset(file.body, i)
1757 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1761 # replace paragraph breaks with \newline
1764 k = find_token(file.body, "\\end_layout", k, j)
1765 l = find_token(file.body, "\\begin_layout", k, j)
1766 if k == -1 or l == -1:
1768 file.body[k:l+1] = ["\\newline"]
1772 # replace double \newlines with paragraph breaks
1775 k = find_token(file.body, "\\newline", k, j)
1779 while file.body[l] == "":
1781 if strip(file.body[l]) and split(file.body[l])[0] == "\\newline":
1782 file.body[k:l+1] = ["\\end_layout", "",
1783 "\\begin_layout Standard"]
1795 convert = [[223, [insert_tracking_changes, add_end_header, remove_color_default,
1796 convert_spaces, convert_bibtex, remove_insetparent]],
1797 [224, [convert_external, convert_comment]],
1798 [225, [add_end_layout, layout2begin_layout, convert_end_document,
1799 convert_table_valignment_middle, convert_breaks]],
1800 [226, [convert_note]],
1801 [227, [convert_box]],
1802 [228, [convert_collapsable, convert_ert]],
1803 [229, [convert_minipage]],
1804 [230, [convert_jurabib]],
1805 [231, [convert_float]],
1806 [232, [convert_bibtopic]],
1807 [233, [convert_graphics, convert_names]],
1808 [234, [convert_cite_engine]],
1809 [235, [convert_paperpackage]],
1810 [236, [convert_bullets, add_begin_header, add_begin_body,
1811 normalize_papersize, strip_end_space]],
1812 [237, [use_x_boolean]],
1813 [238, [update_latexaccents]],
1814 [239, [normalize_paragraph_params]],
1815 [240, [convert_output_changes]],
1816 [241, [convert_ert_paragraphs]]]
1818 revert = [[240, [revert_ert_paragraphs]],
1819 [239, [revert_output_changes]],
1822 [236, [use_x_binary]],
1823 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
1825 [234, [revert_paperpackage]],
1826 [233, [revert_cite_engine]],
1827 [232, [revert_names]],
1828 [231, [revert_bibtopic]],
1829 [230, [revert_float]],
1830 [229, [revert_jurabib]],
1832 [227, [revert_collapsable, revert_ert]],
1833 [226, [revert_box, revert_external_2]],
1834 [225, [revert_note]],
1835 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
1836 revert_valignment_middle, convert_vspace, convert_frameless_box]],
1837 [223, [revert_external_2, revert_comment]],
1838 [221, [rm_end_header, revert_spaces, revert_bibtex,
1839 rm_tracking_changes, rm_body_changes]]]
1842 if __name__ == "__main__":