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
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):
401 i = find_token(file.body, '\\begin_inset Tabular', i)
404 j = find_end_of_inset(file.body, i + 1)
406 #this should not happen
407 convert_valignment_middle(file.body, i + 1, len(file.body))
409 convert_valignment_middle(file.body, i + 1, j)
413 def revert_table_valignment_middle(body, start, end):
414 for i in range(start, end):
415 if re.search('^<(column|cell) .*valignment="middle".*>$', body[i]):
416 body[i] = replace(body[i], 'valignment="middle"', 'valignment="center"')
419 def revert_valignment_middle(file):
422 i = find_token(file.body, '\\begin_inset Tabular', i)
425 j = find_end_of_inset(file.body, i + 1)
427 #this should not happen
428 revert_table_valignment_middle(file.body, i + 1, len(file.body))
430 revert_table_valignment_middle(file.body, i + 1, j)
435 # \the_end -> \end_document
437 def convert_end_document(file):
438 i = find_token(file.body, "\\the_end", 0)
440 file.body.append("\\end_document")
442 file.body[i] = "\\end_document"
445 def revert_end_document(file):
446 i = find_token(file.body, "\\end_document", 0)
448 file.body.append("\\the_end")
450 file.body[i] = "\\the_end"
454 # Convert line and page breaks
457 #\line_top \line_bottom \pagebreak_top \pagebreak_bottom \added_space_top xxx \added_space_bottom yyy
461 #\begin layout Standard
466 #\begin_inset VSpace xxx
470 #\begin_layout Standard
474 #\begin_layout Standard
476 #\begin_inset VSpace xxx
483 def convert_breaks(file):
486 i = find_token(file.body, "\\begin_layout", i)
490 line_top = find(file.body[i],"\\line_top")
491 line_bot = find(file.body[i],"\\line_bottom")
492 pb_top = find(file.body[i],"\\pagebreak_top")
493 pb_bot = find(file.body[i],"\\pagebreak_bottom")
494 vspace_top = find(file.body[i],"\\added_space_top")
495 vspace_bot = find(file.body[i],"\\added_space_bottom")
497 if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1:
500 for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom":
501 file.body[i] = replace(file.body[i], tag, "")
504 # the position could be change because of the removal of other
505 # paragraph properties above
506 vspace_top = find(file.body[i],"\\added_space_top")
507 tmp_list = split(file.body[i][vspace_top:])
508 vspace_top_value = tmp_list[1]
509 file.body[i] = file.body[i][:vspace_top] + join(tmp_list[2:])
512 # the position could be change because of the removal of other
513 # paragraph properties above
514 vspace_bot = find(file.body[i],"\\added_space_bottom")
515 tmp_list = split(file.body[i][vspace_bot:])
516 vspace_bot_value = tmp_list[1]
517 file.body[i] = file.body[i][:vspace_bot] + join(tmp_list[2:])
519 file.body[i] = strip(file.body[i])
522 # Create an empty paragraph for line and page break that belong
523 # above the paragraph
524 if pb_top !=-1 or line_top != -1 or vspace_bot != -1:
526 paragraph_above = ['','\\begin_layout Standard','','']
529 paragraph_above.extend(['\\newpage ',''])
532 paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','',''])
535 paragraph_above.extend(['\\lyxline ',''])
537 paragraph_above.extend(['\\end_layout',''])
539 #inset new paragraph above the current paragraph
540 file.body[i-2:i-2] = paragraph_above
541 i = i + len(paragraph_above)
543 # Ensure that nested style are converted later.
544 k = find_end_of(file.body, i, "\\begin_layout", "\\end_layout")
549 if pb_top !=-1 or line_top != -1 or vspace_bot != -1:
551 paragraph_bellow = ['','\\begin_layout Standard','','']
554 paragraph_bellow.extend(['\\lyxline ',''])
557 paragraph_bellow.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','',''])
560 paragraph_bellow.extend(['\\newpage ',''])
562 paragraph_bellow.extend(['\\end_layout',''])
564 #inset new paragraph above the current paragraph
565 file.body[k + 1: k + 1] = paragraph_bellow
571 def convert_note(file):
574 i = find_tokens(file.body, ["\\begin_inset Note",
575 "\\begin_inset Comment",
576 "\\begin_inset Greyedout"], i)
580 file.body[i] = file.body[i][0:13] + 'Note ' + file.body[i][13:]
584 def revert_note(file):
585 note_header = "\\begin_inset Note "
588 i = find_token(file.body, note_header, i)
592 file.body[i] = "\\begin_inset " + file.body[i][len(note_header):]
599 def convert_box(file):
602 i = find_tokens(file.body, ["\\begin_inset Boxed",
603 "\\begin_inset Doublebox",
604 "\\begin_inset Frameless",
605 "\\begin_inset ovalbox",
606 "\\begin_inset Ovalbox",
607 "\\begin_inset Shadowbox"], i)
611 file.body[i] = file.body[i][0:13] + 'Box ' + file.body[i][13:]
615 def revert_box(file):
616 box_header = "\\begin_inset Box "
619 i = find_token(file.body, box_header, i)
623 file.body[i] = "\\begin_inset " + file.body[i][len(box_header):]
630 def convert_collapsable(file):
633 i = find_tokens(file.body, ["\\begin_inset Box",
634 "\\begin_inset Branch",
635 "\\begin_inset CharStyle",
636 "\\begin_inset Float",
637 "\\begin_inset Foot",
638 "\\begin_inset Marginal",
639 "\\begin_inset Note",
640 "\\begin_inset OptArg",
641 "\\begin_inset Wrap"], i)
645 # Seach for a line starting 'collapsed'
646 # If, however, we find a line starting '\begin_layout'
647 # (_always_ present) then break with a warning message
650 if (file.body[i] == "collapsed false"):
651 file.body[i] = "status open"
653 elif (file.body[i] == "collapsed true"):
654 file.body[i] = "status collapsed"
656 elif (file.body[i][:13] == "\\begin_layout"):
657 file.warning("Malformed LyX file: Missing 'collapsed'.")
664 def revert_collapsable(file):
667 i = find_tokens(file.body, ["\\begin_inset Box",
668 "\\begin_inset Branch",
669 "\\begin_inset CharStyle",
670 "\\begin_inset Float",
671 "\\begin_inset Foot",
672 "\\begin_inset Marginal",
673 "\\begin_inset Note",
674 "\\begin_inset OptArg",
675 "\\begin_inset Wrap"], i)
679 # Seach for a line starting 'status'
680 # If, however, we find a line starting '\begin_layout'
681 # (_always_ present) then break with a warning message
684 if (file.body[i] == "status open"):
685 file.body[i] = "collapsed false"
687 elif (file.body[i] == "status collapsed" or
688 file.body[i] == "status inlined"):
689 file.body[i] = "collapsed true"
691 elif (file.body[i][:13] == "\\begin_layout"):
692 file.warning("Malformed LyX file: Missing 'status'.")
702 def convert_ert(file):
705 i = find_token(file.body, "\\begin_inset ERT", i)
709 # Seach for a line starting 'status'
710 # If, however, we find a line starting '\begin_layout'
711 # (_always_ present) then break with a warning message
714 if (file.body[i] == "status Open"):
715 file.body[i] = "status open"
717 elif (file.body[i] == "status Collapsed"):
718 file.body[i] = "status collapsed"
720 elif (file.body[i] == "status Inlined"):
721 file.body[i] = "status inlined"
723 elif (file.body[i][:13] == "\\begin_layout"):
724 file.warning("Malformed LyX file: Missing 'status'.")
731 def revert_ert(file):
734 i = find_token(file.body, "\\begin_inset ERT", i)
738 # Seach for a line starting 'status'
739 # If, however, we find a line starting '\begin_layout'
740 # (_always_ present) then break with a warning message
743 if (file.body[i] == "status open"):
744 file.body[i] = "status Open"
746 elif (file.body[i] == "status collapsed"):
747 file.body[i] = "status Collapsed"
749 elif (file.body[i] == "status inlined"):
750 file.body[i] = "status Inlined"
752 elif (file.body[i][:13] == "\\begin_layout"):
753 file.warning("Malformed LyX file : Missing 'status'.")
763 def convert_minipage(file):
764 """ Convert minipages to the box inset.
765 We try to use the same order of arguments as lyx does.
768 inner_pos = ["c","t","b","s"]
772 i = find_token(file.body, "\\begin_inset Minipage", i)
776 file.body[i] = "\\begin_inset Box Frameless"
779 # convert old to new position using the pos list
780 if file.body[i][:8] == "position":
781 file.body[i] = 'position "%s"' % pos[int(file.body[i][9])]
783 file.body.insert(i, 'position "%s"' % pos[0])
786 file.body.insert(i, 'hor_pos "c"')
788 file.body.insert(i, 'has_inner_box 1')
791 # convert the inner_position
792 if file.body[i][:14] == "inner_position":
793 file.body[i] = 'inner_pos "%s"' % inner_pos[int(file.body[i][15])]
795 file.body.insert('inner_pos "%s"' % inner_pos[0])
798 # We need this since the new file format has a height and width
799 # in a different order.
800 if file.body[i][:6] == "height":
801 height = file.body[i][6:]
802 # test for default value of 221 and convert it accordingly
803 if height == ' "0pt"':
809 if file.body[i][:5] == "width":
810 width = file.body[i][5:]
815 if file.body[i][:9] == "collapsed":
816 if file.body[i][9:] == "true":
824 file.body.insert(i, 'use_parbox 0')
826 file.body.insert(i, 'width' + width)
828 file.body.insert(i, 'special "none"')
830 file.body.insert(i, 'height' + height)
832 file.body.insert(i, 'height_special "totalheight"')
834 file.body.insert(i, 'status ' + status)
838 # -------------------------------------------------------------------------------------------
839 # Convert backslashes and '\n' into valid ERT code, append the converted
840 # text to body[i] and return the (maybe incremented) line index i
841 def convert_ertbackslash(body, i, ert):
844 body[i] = body[i] + '\\backslash '
848 body[i+1:i+1] = ['\\newline ', '']
851 body[i] = body[i] + c
855 def convert_vspace(file):
857 # Get default spaceamount
858 i = find_token(file.header, '\\defskip', 0)
860 defskipamount = 'medskip'
862 defskipamount = split(file.header[i])[1]
867 i = find_token(file.body, '\\begin_inset VSpace', i)
870 spaceamount = split(file.body[i])[2]
872 # Are we at the beginning or end of a paragraph?
874 start = get_paragraph(file.body, i) + 1
875 for k in range(start, i):
876 if is_nonempty_line(file.body[k]):
880 j = find_end_of_inset(file.body, i)
882 file.warning("Malformed LyX file: Missing '\\end_inset'.")
885 end = get_next_paragraph(file.body, i)
886 for k in range(j + 1, end):
887 if is_nonempty_line(file.body[k]):
891 # Convert to paragraph formatting if we are at the beginning or end
892 # of a paragraph and the resulting paragraph would not be empty
893 if ((paragraph_start and not paragraph_end) or
894 (paragraph_end and not paragraph_start)):
895 # The order is important: del and insert invalidate some indices
899 file.body.insert(start, '\\added_space_top ' + spaceamount + ' ')
901 file.body.insert(start, '\\added_space_bottom ' + spaceamount + ' ')
905 file.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
906 '\\layout Standard', '', '\\backslash ']
908 if spaceamount[-1] == '*':
909 spaceamount = spaceamount[:-1]
914 # Replace defskip by the actual value
915 if spaceamount == 'defskip':
916 spaceamount = defskipamount
918 # LaTeX does not know \\smallskip* etc
920 if spaceamount == 'smallskip':
921 spaceamount = '\\smallskipamount'
922 elif spaceamount == 'medskip':
923 spaceamount = '\\medskipamount'
924 elif spaceamount == 'bigskip':
925 spaceamount = '\\bigskipamount'
926 elif spaceamount == 'vfill':
927 spaceamount = '\\fill'
929 # Finally output the LaTeX code
930 if (spaceamount == 'smallskip' or spaceamount == 'medskip' or
931 spaceamount == 'bigskip' or spaceamount == 'vfill'):
932 file.body.insert(i, spaceamount)
935 file.body.insert(i, 'vspace*{')
937 file.body.insert(i, 'vspace{')
938 i = convert_ertbackslash(file.body, i, spaceamount)
939 file.body[i] = file.body[i] + '}'
943 # Convert a LyX length into a LaTeX length
944 def convert_len(len, special):
945 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
946 "page%":"\\pagewidth", "line%":"\\linewidth",
947 "theight%":"\\textheight", "pheight%":"\\pageheight"}
949 # Convert special lengths
950 if special != 'none':
951 len = '%f\\' % len2value(len) + special
953 # Convert LyX units to LaTeX units
954 for unit in units.keys():
955 if find(len, unit) != -1:
956 len = '%f' % (len2value(len) / 100) + units[unit]
962 # Convert a LyX length into valid ERT code and append it to body[i]
963 # Return the (maybe incremented) line index i
964 def convert_ertlen(body, i, len, special):
965 # Convert backslashes and insert the converted length into body
966 return convert_ertbackslash(body, i, convert_len(len, special))
969 # Return the value of len without the unit in numerical form
971 result = re.search('([+-]?[0-9.]+)', len)
973 return float(result.group(1))
974 # No number means 1.0
978 # Convert text to ERT and insert it at body[i]
979 # Return the index of the line after the inserted ERT
980 def insert_ert(body, i, status, text):
981 body[i:i] = ['\\begin_inset ERT', 'status ' + status, '',
982 '\\layout Standard', '']
984 i = convert_ertbackslash(body, i, text) + 1
985 body[i:i] = ['', '\\end_inset', '']
990 # Add text to the preamble if it is not already there.
991 # Only the first line is checked!
992 def add_to_preamble(file, text):
993 i = find_token(file.header, '\\begin_preamble', 0)
995 file.warning("Malformed LyX file: Missing '\\begin_preamble'.")
997 j = find_token(file.header, '\\end_preamble', i)
999 file.warning("Malformed LyX file: Missing '\\end_preamble'.")
1001 if find_token(file.header, text[0], i, j) != -1:
1003 file.header[j:j] = text
1006 def convert_frameless_box(file):
1007 pos = ['t', 'c', 'b']
1008 inner_pos = ['c', 't', 'b', 's']
1011 i = find_token(file.body, '\\begin_inset Frameless', i)
1014 j = find_end_of_inset(file.body, i)
1016 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1023 params = {'position':'0', 'hor_pos':'c', 'has_inner_box':'1',
1024 'inner_pos':'1', 'use_parbox':'0', 'width':'100col%',
1025 'special':'none', 'height':'1in',
1026 'height_special':'totalheight', 'collapsed':'false'}
1027 for key in params.keys():
1028 value = replace(get_value(file.body, key, i, j), '"', '')
1030 if key == 'position':
1031 # convert new to old position: 'position "t"' -> 0
1032 value = find_token(pos, value, 0)
1035 elif key == 'inner_pos':
1036 # convert inner position
1037 value = find_token(inner_pos, value, 0)
1042 j = del_token(file.body, key, i, j)
1045 # Convert to minipage or ERT?
1046 # Note that the inner_position and height parameters of a minipage
1047 # inset are ignored and not accessible for the user, although they
1048 # are present in the file format and correctly read in and written.
1049 # Therefore we convert to ERT if they do not have their LaTeX
1050 # defaults. These are:
1051 # - the value of "position" for "inner_pos"
1052 # - "\totalheight" for "height"
1053 if (params['use_parbox'] != '0' or
1054 params['has_inner_box'] != '1' or
1055 params['special'] != 'none' or
1056 inner_pos[params['inner_pos']] != pos[params['position']] or
1057 params['height_special'] != 'totalheight' or
1058 len2value(params['height']) != 1.0):
1060 # Here we know that this box is not supported in file format 224.
1061 # Therefore we need to convert it to ERT. We can't simply convert
1062 # the beginning and end of the box to ERT, because the
1063 # box inset may contain layouts that are different from the
1064 # surrounding layout. After the conversion the contents of the
1065 # box inset is on the same level as the surrounding text, and
1066 # paragraph layouts and align parameters can get mixed up.
1068 # A possible solution for this problem:
1069 # Convert the box to a minipage and redefine the minipage
1070 # environment in ERT so that the original box is simulated.
1071 # For minipages we could do this in a way that the width and
1072 # position can still be set from LyX, but this did not work well.
1073 # This is not possible for parboxes either, so we convert the
1074 # original box to ERT, put the minipage inset inside the box
1075 # and redefine the minipage environment to be empty.
1077 # Commands that are independant of a particular box can go to
1079 # We need to define lyxtolyxrealminipage with 3 optional
1080 # arguments although LyX 1.3 uses only the first one.
1081 # Otherwise we will get LaTeX errors if this document is
1082 # converted to format 225 or above again (LyX 1.4 uses all
1083 # optional arguments).
1084 add_to_preamble(file,
1085 ['% Commands inserted by lyx2lyx for frameless boxes',
1086 '% Save the original minipage environment',
1087 '\\let\\lyxtolyxrealminipage\\minipage',
1088 '\\let\\endlyxtolyxrealminipage\\endminipage',
1089 '% Define an empty lyxtolyximinipage environment',
1090 '% with 3 optional arguments',
1091 '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1092 '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1093 ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1094 ' {\\end{lyxtolyxiiiminipage}}',
1095 '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1096 ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1097 ' {\\end{lyxtolyxiiminipage}}',
1098 '\\newenvironment{lyxtolyxminipage}[1][c]%',
1099 ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1100 ' {\\end{lyxtolyximinipage}}'])
1102 if params['use_parbox'] != '0':
1105 ert = '\\begin{lyxtolyxrealminipage}'
1107 # convert optional arguments only if not latex default
1108 if (pos[params['position']] != 'c' or
1109 inner_pos[params['inner_pos']] != pos[params['position']] or
1110 params['height_special'] != 'totalheight' or
1111 len2value(params['height']) != 1.0):
1112 ert = ert + '[' + pos[params['position']] + ']'
1113 if (inner_pos[params['inner_pos']] != pos[params['position']] or
1114 params['height_special'] != 'totalheight' or
1115 len2value(params['height']) != 1.0):
1116 ert = ert + '[' + convert_len(params['height'],
1117 params['height_special']) + ']'
1118 if inner_pos[params['inner_pos']] != pos[params['position']]:
1119 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1121 ert = ert + '{' + convert_len(params['width'],
1122 params['special']) + '}'
1124 if params['use_parbox'] != '0':
1126 ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1127 ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1130 i = insert_ert(file.body, i, 'Collapsed', ert)
1131 j = j + i - old_i - 1
1133 file.body[i:i] = ['\\begin_inset Minipage',
1134 'position %d' % params['position'],
1137 'width "' + params['width'] + '"',
1138 'collapsed ' + params['collapsed']]
1142 # Restore the original minipage environment since we may have
1143 # minipages inside this box.
1144 # Start a new paragraph because the following may be nonstandard
1145 file.body[i:i] = ['\\layout Standard', '', '']
1148 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1149 ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1151 i = insert_ert(file.body, i, 'Collapsed', ert)
1152 j = j + i - old_i - 1
1154 # Redefine the minipage end before the inset end.
1155 # Start a new paragraph because the previous may be nonstandard
1156 file.body[j:j] = ['\\layout Standard', '', '']
1158 ert = '\\let\\endminipage\\endlyxtolyxminipage'
1159 j = insert_ert(file.body, j, 'Collapsed', ert)
1161 file.body.insert(j, '')
1164 # LyX writes '%\n' after each box. Therefore we need to end our
1165 # ERT with '%\n', too, since this may swallow a following space.
1166 if params['use_parbox'] != '0':
1169 ert = '\\end{lyxtolyxrealminipage}%\n'
1170 j = insert_ert(file.body, j, 'Collapsed', ert)
1172 # We don't need to restore the original minipage after the inset
1173 # end because the scope of the redefinition is the original box.
1177 # Convert to minipage
1178 file.body[i:i] = ['\\begin_inset Minipage',
1179 'position %d' % params['position'],
1180 'inner_position %d' % params['inner_pos'],
1181 'height "' + params['height'] + '"',
1182 'width "' + params['width'] + '"',
1183 'collapsed ' + params['collapsed']]
1190 def convert_jurabib(file):
1191 i = find_token(file.header, '\\use_numerical_citations', 0)
1193 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1195 file.header.insert(i + 1, '\\use_jurabib 0')
1198 def revert_jurabib(file):
1199 i = find_token(file.header, '\\use_jurabib', 0)
1201 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1203 if get_value(file.header, '\\use_jurabib', 0) != "0":
1204 file.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1205 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1213 def convert_bibtopic(file):
1214 i = find_token(file.header, '\\use_jurabib', 0)
1216 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1218 file.header.insert(i + 1, '\\use_bibtopic 0')
1221 def revert_bibtopic(file):
1222 i = find_token(file.header, '\\use_bibtopic', 0)
1224 file.warning("Malformed lyx file: Missing '\\use_bibtopic'.")
1226 if get_value(file.header, '\\use_bibtopic', 0) != "0":
1227 file.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1228 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1235 def convert_float(file):
1238 i = find_token(file.body, '\\begin_inset Float', i)
1241 # Seach for a line starting 'wide'
1242 # If, however, we find a line starting '\begin_layout'
1243 # (_always_ present) then break with a warning message
1246 if (file.body[i][:4] == "wide"):
1247 file.body.insert(i + 1, 'sideways false')
1249 elif (file.body[i][:13] == "\\begin_layout"):
1250 file.warning("Malformed lyx file: Missing 'wide'.")
1256 def revert_float(file):
1259 i = find_token(file.body, '\\begin_inset Float', i)
1262 j = find_end_of_inset(file.body, i)
1264 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1267 if get_value(file.body, 'sideways', i, j) != "false":
1268 file.warning("Conversion of 'sideways true' not yet implemented.")
1269 # Don't remove 'sideways' so that people will get warnings by lyx
1272 del_token(file.body, 'sideways', i, j)
1276 def convert_graphics(file):
1277 """ Add extension to filenames of insetgraphics if necessary.
1281 i = find_token(file.body, "\\begin_inset Graphics", i)
1285 j = find_token2(file.body, "filename", i)
1289 filename = split(file.body[j])[1]
1290 absname = os.path.normpath(os.path.join(file.dir, filename))
1291 if file.input == stdin and not os.path.isabs(filename):
1292 # We don't know the directory and cannot check the file.
1293 # We could use a heuristic and take the current directory,
1294 # and we could try to find out if filename has an extension,
1295 # but that would be just guesses and could be wrong.
1296 file.warning("""Warning: Can not determine whether file
1298 needs an extension when reading from standard input.
1299 You may need to correct the file manually or run
1300 lyx2lyx again with the .lyx file as commandline argument.""" % filename)
1302 # This needs to be the same algorithm as in pre 233 insetgraphics
1303 if access(absname, F_OK):
1305 if access(absname + ".ps", F_OK):
1306 file.body[j] = replace(file.body[j], filename, filename + ".ps")
1308 if access(absname + ".eps", F_OK):
1309 file.body[j] = replace(file.body[j], filename, filename + ".eps")
1313 # Convert firstname and surname from styles -> char styles
1315 def convert_names(file):
1316 """ Convert in the docbook backend from firstname and surname style
1319 if file.backend != "docbook":
1325 i = find_token(file.body, "\\begin_layout Author", i)
1330 while file.body[i] == "":
1333 if file.body[i][:11] != "\\end_layout" or file.body[i+2][:13] != "\\begin_deeper":
1338 i = find_end_of( file.body, i+3, "\\begin_deeper","\\end_deeper")
1340 # something is really wrong, abort
1341 file.warning("Missing \\end_deeper, after style Author.")
1342 file.warning("Aborted attempt to parse FirstName and Surname.")
1344 firstname, surname = "", ""
1346 name = file.body[k:i]
1348 j = find_token(name, "\\begin_layout FirstName", 0)
1351 while(name[j] != "\\end_layout"):
1352 firstname = firstname + name[j]
1355 j = find_token(name, "\\begin_layout Surname", 0)
1358 while(name[j] != "\\end_layout"):
1359 surname = surname + name[j]
1363 del file.body[k+2:i+1]
1365 file.body[k-1:k-1] = ["", "",
1366 "\\begin_inset CharStyle Firstname",
1369 "\\begin_layout Standard",
1377 "\\begin_inset CharStyle Surname",
1380 "\\begin_layout Standard",
1389 def revert_names(file):
1390 """ Revert in the docbook backend from firstname and surname char style
1393 if file.backend != "docbook":
1398 # \use_natbib 1 \cite_engine <style>
1399 # \use_numerical_citations 0 -> where <style> is one of
1400 # \use_jurabib 0 "basic", "natbib_authoryear",
1401 # "natbib_numerical" or "jurabib"
1402 def convert_cite_engine(file):
1403 a = find_token(file.header, "\\use_natbib", 0)
1405 file.warning("Malformed lyx file: Missing '\\use_natbib'.")
1408 b = find_token(file.header, "\\use_numerical_citations", 0)
1409 if b == -1 or b != a+1:
1410 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1413 c = find_token(file.header, "\\use_jurabib", 0)
1414 if c == -1 or c != b+1:
1415 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1418 use_natbib = int(split(file.header[a])[1])
1419 use_numerical_citations = int(split(file.header[b])[1])
1420 use_jurabib = int(split(file.header[c])[1])
1422 cite_engine = "basic"
1424 if use_numerical_citations:
1425 cite_engine = "natbib_numerical"
1427 cite_engine = "natbib_authoryear"
1429 cite_engine = "jurabib"
1431 del file.header[a:c+1]
1432 file.header.insert(a, "\\cite_engine " + cite_engine)
1435 def revert_cite_engine(file):
1436 i = find_token(file.header, "\\cite_engine", 0)
1438 file.warning("Malformed lyx file: Missing '\\cite_engine'.")
1441 cite_engine = split(file.header[i])[1]
1446 if cite_engine == "natbib_numerical":
1449 elif cite_engine == "natbib_authoryear":
1451 elif cite_engine == "jurabib":
1455 file.header.insert(i, "\\use_jurabib " + use_jurabib)
1456 file.header.insert(i, "\\use_numerical_citations " + use_numerical)
1457 file.header.insert(i, "\\use_natbib " + use_natbib)
1463 def convert_paperpackage(file):
1464 i = find_token(file.header, "\\paperpackage", 0)
1466 file.warning("Malformed lyx file: Missing '\\paperpackage'.")
1469 packages = {'a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
1470 paperpackage = split(file.header[i])[1]
1471 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1474 def revert_paperpackage(file):
1475 i = find_token(file.header, "\\paperpackage", 0)
1477 file.warning("Malformed lyx file: Missing '\\paperpackage'.")
1480 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
1482 paperpackage = split(file.header[i])[1]
1483 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1489 def convert_bullets(file):
1492 i = find_token(file.header, "\\bullet", i)
1495 if file.header[i][:12] == '\\bulletLaTeX':
1496 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1])
1499 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1]) +\
1500 ' ' + strip(file.header[i+2]) + ' ' + strip(file.header[i+3])
1502 del file.header[i+1:i + n]
1506 def revert_bullets(file):
1509 i = find_token(file.header, "\\bullet", i)
1512 if file.header[i][:12] == '\\bulletLaTeX':
1513 n = find(file.header[i], '"')
1515 file.warning("Malformed header.")
1518 file.header[i:i+1] = [file.header[i][:n-1],'\t' + file.header[i][n:], '\\end_bullet']
1521 frag = split(file.header[i])
1523 file.warning("Malformed header.")
1526 file.header[i:i+1] = [frag[0] + ' ' + frag[1],
1535 # \begin_header and \begin_document
1537 def add_begin_header(file):
1538 i = find_token(file.header, '\\lyxformat', 0)
1539 file.header.insert(i+1, '\\begin_header')
1540 file.header.insert(i+1, '\\begin_document')
1543 def remove_begin_header(file):
1544 i = find_token(file.header, "\\begin_document", 0)
1547 i = find_token(file.header, "\\begin_header", 0)
1553 # \begin_file.body and \end_file.body
1555 def add_begin_body(file):
1556 file.body.insert(0, '\\begin_body')
1557 file.body.insert(1, '')
1558 i = find_token(file.body, "\\end_document", 0)
1559 file.body.insert(i, '\\end_body')
1561 def remove_begin_body(file):
1562 i = find_token(file.body, "\\begin_body", 0)
1565 if not file.body[i]:
1567 i = find_token(file.body, "\\end_body", 0)
1575 def normalize_papersize(file):
1576 i = find_token(file.header, '\\papersize', 0)
1580 tmp = split(file.header[i])
1581 if tmp[1] == "Default":
1582 file.header[i] = '\\papersize default'
1584 if tmp[1] == "Custom":
1585 file.header[i] = '\\papersize custom'
1588 def denormalize_papersize(file):
1589 i = find_token(file.header, '\\papersize', 0)
1593 tmp = split(file.header[i])
1594 if tmp[1] == "custom":
1595 file.header[i] = '\\papersize Custom'
1599 # Strip spaces at end of command line
1601 def strip_end_space(file):
1602 for i in range(len(file.body)):
1603 if file.body[i][:1] == '\\':
1604 file.body[i] = strip(file.body[i])
1608 # Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes
1610 def use_x_boolean(file):
1611 bin2bool = {'0': 'false', '1': 'true'}
1612 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
1613 i = find_token(file.header, use, 0)
1616 decompose = split(file.header[i])
1617 file.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
1620 def use_x_binary(file):
1621 bool2bin = {'false': '0', 'true': '1'}
1622 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
1623 i = find_token(file.header, use, 0)
1626 decompose = split(file.header[i])
1627 file.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
1630 # Place all the paragraph parameters in their own line
1632 def normalize_paragraph_params(file):
1634 allowed_parameters = '\\paragraph_spacing', '\\noindent', '\\align', '\\labelwidthstring', "\\start_of_appendix"
1638 i = find_token(file.body, '\\begin_layout', i)
1644 if strip(body[i]) and split(body[i])[0] not in allowed_parameters:
1647 j = find(body[i],'\\', 1)
1650 body[i:i+1] = [strip(body[i][:j]), body[i][j:]]
1656 # Add/remove output_changes parameter
1658 def convert_output_changes (file):
1659 i = find_token(file.header, '\\tracking_changes', 0)
1661 file.warning("Malformed lyx file: Missing '\\tracking_changes'.")
1663 file.header.insert(i+1, '\\output_changes true')
1666 def revert_output_changes (file):
1667 i = find_token(file.header, '\\output_changes', 0)
1674 # Convert paragraph breaks and sanitize paragraphs
1676 def convert_ert_paragraphs(file):
1677 forbidden_settings = [
1678 # paragraph parameters
1679 '\\paragraph_spacing', '\\labelwidthstring',
1680 '\\start_of_appendix', '\\noindent',
1681 '\\leftindent', '\\align',
1683 '\\family', '\\series', '\\shape', '\\size',
1684 '\\emph', '\\numeric', '\\bar', '\\noun',
1685 '\\color', '\\lang']
1688 i = find_token(file.body, '\\begin_inset ERT', i)
1691 j = find_end_of_inset(file.body, i)
1693 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1697 # convert non-standard paragraphs to standard
1700 k = find_token(file.body, "\\begin_layout", k, j)
1703 file.body[k] = "\\begin_layout Standard"
1706 # remove all paragraph parameters and font settings
1709 if (strip(file.body[k]) and
1710 split(file.body[k])[0] in forbidden_settings):
1716 # insert an empty paragraph before each paragraph but the first
1720 k = find_token(file.body, "\\begin_layout Standard", k, j)
1727 file.body[k:k] = ["\\begin_layout Standard", "",
1732 # convert \\newline to new paragraph
1735 k = find_token(file.body, "\\newline", k, j)
1738 file.body[k:k+1] = ["\\end_layout", "", "\\begin_layout Standard"]
1745 # Remove double paragraph breaks
1747 def revert_ert_paragraphs(file):
1750 i = find_token(file.body, '\\begin_inset ERT', i)
1753 j = find_end_of_inset(file.body, i)
1755 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1759 # replace paragraph breaks with \newline
1762 k = find_token(file.body, "\\end_layout", k, j)
1763 l = find_token(file.body, "\\begin_layout", k, j)
1764 if k == -1 or l == -1:
1766 file.body[k:l+1] = ["\\newline"]
1770 # replace double \newlines with paragraph breaks
1773 k = find_token(file.body, "\\newline", k, j)
1777 while file.body[l] == "":
1779 if strip(file.body[l]) and split(file.body[l])[0] == "\\newline":
1780 file.body[k:l+1] = ["\\end_layout", "",
1781 "\\begin_layout Standard"]
1793 convert = [[223, [insert_tracking_changes, add_end_header, remove_color_default,
1794 convert_spaces, convert_bibtex, remove_insetparent]],
1795 [224, [convert_external, convert_comment]],
1796 [225, [add_end_layout, layout2begin_layout, convert_end_document,
1797 convert_table_valignment_middle, convert_breaks]],
1798 [226, [convert_note]],
1799 [227, [convert_box]],
1800 [228, [convert_collapsable, convert_ert]],
1801 [229, [convert_minipage]],
1802 [230, [convert_jurabib]],
1803 [231, [convert_float]],
1804 [232, [convert_bibtopic]],
1805 [233, [convert_graphics, convert_names]],
1806 [234, [convert_cite_engine]],
1807 [235, [convert_paperpackage]],
1808 [236, [convert_bullets, add_begin_header, add_begin_body,
1809 normalize_papersize, strip_end_space]],
1810 [237, [use_x_boolean]],
1811 [238, [update_latexaccents]],
1812 [239, [normalize_paragraph_params]],
1813 [240, [convert_output_changes]],
1814 [241, [convert_ert_paragraphs]]]
1816 revert = [[240, [revert_ert_paragraphs]],
1817 [239, [revert_output_changes]],
1820 [236, [use_x_binary]],
1821 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
1823 [234, [revert_paperpackage]],
1824 [233, [revert_cite_engine]],
1825 [232, [revert_names]],
1826 [231, [revert_bibtopic]],
1827 [230, [revert_float]],
1828 [229, [revert_jurabib]],
1830 [227, [revert_collapsable, revert_ert]],
1831 [226, [revert_box, revert_external_2]],
1832 [225, [revert_note]],
1833 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
1834 revert_valignment_middle, convert_vspace, convert_frameless_box]],
1835 [223, [revert_external_2, revert_comment]],
1836 [221, [rm_end_header, revert_spaces, revert_bibtex,
1837 rm_tracking_changes, rm_body_changes]]]
1840 if __name__ == "__main__":