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_token_exact, find_tokens_exact,\
29 from string import replace, split, find, strip, join
31 from lyx_0_12 import update_latexaccents
34 # Remove \color default
36 def remove_color_default(file):
39 i = find_token(file.body, "\\color default", i)
42 file.body[i] = replace(file.body[i], "\\color default",
49 def add_end_header(file):
50 file.header.append("\\end_header");
53 def rm_end_header(file):
54 i = find_token(file.header, "\\end_header", 0)
60 def convert_amsmath(file):
61 i = find_token(file.header, "\\use_amsmath", 0)
63 file.warning("Malformed LyX file: Missing '\\use_amsmath'.")
65 tokens = split(file.header[i])
67 file.warning("Malformed LyX file: Could not parse line '%s'." % file.header[i])
70 use_amsmath = tokens[1]
71 # old: 0 == off, 1 == on
72 # new: 0 == off, 1 == auto, 2 == on
73 # translate off -> auto, since old format 'off' means auto in reality
74 if use_amsmath == '0':
75 file.header[i] = "\\use_amsmath 1"
77 file.header[i] = "\\use_amsmath 2"
80 def revert_amsmath(file):
81 i = find_token(file.header, "\\use_amsmath", 0)
83 file.warning("Malformed LyX file: Missing '\\use_amsmath'.")
85 tokens = split(file.header[i])
87 file.warning("Malformed LyX file: Could not parse line '%s'." % file.header[i])
90 use_amsmath = tokens[1]
91 # old: 0 == off, 1 == on
92 # new: 0 == off, 1 == auto, 2 == on
93 # translate auto -> off, since old format 'off' means auto in reality
94 if use_amsmath == '2':
95 file.header[i] = "\\use_amsmath 1"
97 file.header[i] = "\\use_amsmath 0"
101 # \SpecialChar ~ -> \InsetSpace ~
103 def convert_spaces(file):
104 for i in range(len(file.body)):
105 file.body[i] = replace(file.body[i],"\\SpecialChar ~","\\InsetSpace ~")
108 def revert_spaces(file):
109 regexp = re.compile(r'(.*)(\\InsetSpace\s+)(\S+)')
112 i = find_re(file.body, regexp, i)
115 space = regexp.match(file.body[i]).group(3)
116 prepend = regexp.match(file.body[i]).group(1)
118 file.body[i] = regexp.sub(prepend + '\\SpecialChar ~', file.body[i])
121 file.body[i] = regexp.sub(prepend, file.body[i])
122 file.body[i+1:i+1] = ''
123 if space == "\\space":
125 i = insert_ert(file.body, i+1, 'Collapsed', space, file.format - 1, file.default_layout)
128 # \InsetSpace \, -> \InsetSpace \thinspace{}
129 # \InsetSpace \space -> \InsetSpace \space{}
131 def rename_spaces(file):
132 for i in range(len(file.body)):
133 file.body[i] = replace(file.body[i],"\\InsetSpace \\space","\\InsetSpace \\space{}")
134 file.body[i] = replace(file.body[i],"\\InsetSpace \,","\\InsetSpace \\thinspace{}")
137 def revert_space_names(file):
138 for i in range(len(file.body)):
139 file.body[i] = replace(file.body[i],"\\InsetSpace \\space{}","\\InsetSpace \\space")
140 file.body[i] = replace(file.body[i],"\\InsetSpace \\thinspace{}","\\InsetSpace \\,")
144 # equivalent to lyx::support::escape()
146 def lyx_support_escape(lab):
147 hexdigit = ['0', '1', '2', '3', '4', '5', '6', '7',
148 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
152 if o >= 128 or c == '=' or c == '%':
154 enc = enc + hexdigit[o >> 4]
155 enc = enc + hexdigit[o & 15]
162 # \begin_inset LatexCommand \eqref -> ERT
164 def revert_eqref(file):
165 regexp = re.compile(r'^\\begin_inset\s+LatexCommand\s+\\eqref')
168 i = find_re(file.body, regexp, i)
171 eqref = lyx_support_escape(regexp.sub("", file.body[i]))
172 file.body[i:i+1] = ["\\begin_inset ERT", "status Collapsed", "",
173 '\\layout %s' % file.default_layout, "", "\\backslash ",
181 def convert_bibtex(file):
182 for i in range(len(file.body)):
183 file.body[i] = replace(file.body[i],"\\begin_inset LatexCommand \\BibTeX",
184 "\\begin_inset LatexCommand \\bibtex")
187 def revert_bibtex(file):
188 for i in range(len(file.body)):
189 file.body[i] = replace(file.body[i], "\\begin_inset LatexCommand \\bibtex",
190 "\\begin_inset LatexCommand \\BibTeX")
196 def remove_insetparent(file):
199 i = find_token(file.body, "\\begin_inset LatexCommand \\lyxparent", i)
208 def convert_external(file):
209 external_rexp = re.compile(r'\\begin_inset External ([^,]*),"([^"]*)",')
210 external_header = "\\begin_inset External"
213 i = find_token(file.body, external_header, i)
216 look = external_rexp.search(file.body[i])
219 args[0] = look.group(1)
220 args[1] = look.group(2)
221 #FIXME: if the previous search fails then warn
223 if args[0] == "RasterImage":
224 # Convert a RasterImage External Inset to a Graphics Inset.
225 top = "\\begin_inset Graphics"
227 filename = "\tfilename " + args[1]
228 file.body[i:i+1] = [top, filename]
231 # Convert the old External Inset format to the new.
232 top = external_header
233 template = "\ttemplate " + args[0]
235 filename = "\tfilename " + args[1]
236 file.body[i:i+1] = [top, template, filename]
239 file.body[i:i+1] = [top, template]
243 def revert_external_1(file):
244 external_header = "\\begin_inset External"
247 i = find_token(file.body, external_header, i)
251 template = split(file.body[i+1])
255 filename = split(file.body[i+1])
259 params = split(file.body[i+1])
261 if file.body[i+1]: del file.body[i+1]
263 file.body[i] = file.body[i] + " " + template[0]+ ', "' + filename[0] + '", " '+ join(params[1:]) + '"'
267 def revert_external_2(file):
268 draft_token = '\tdraft'
271 i = find_token(file.body, '\\begin_inset External', i)
274 j = find_end_of_inset(file.body, i + 1)
276 #this should not happen
278 k = find_token(file.body, draft_token, i+1, j-1)
279 if (k != -1 and len(draft_token) == len(file.body[k])):
287 def convert_comment(file):
289 comment = "\\layout Comment"
291 i = find_token(file.body, comment, i)
295 file.body[i:i+1] = ['\\layout %s' % file.default_layout,"","",
296 "\\begin_inset Comment",
298 '\\layout %s' % file.default_layout]
303 i = find_token(file.body, "\\layout", i)
305 i = len(file.body) - 1
306 file.body[i:i] = ["\\end_inset","",""]
309 j = find_token(file.body, '\\begin_deeper', old_i, i)
310 if j == -1: j = i + 1
311 k = find_token(file.body, '\\begin_inset', old_i, i)
312 if k == -1: k = i + 1
317 i = find_end_of( file.body, i, "\\begin_deeper","\\end_deeper")
319 #This case should not happen
320 #but if this happens deal with it greacefully adding
321 #the missing \end_deeper.
322 i = len(file.body) - 1
323 file.body[i:i] = ["\end_deeper",""]
331 i = find_end_of( file.body, i, "\\begin_inset","\\end_inset")
333 #This case should not happen
334 #but if this happens deal with it greacefully adding
335 #the missing \end_inset.
336 i = len(file.body) - 1
337 file.body[i:i] = ["\\end_inset","","","\\end_inset","",""]
343 if find(file.body[i], comment) == -1:
344 file.body[i:i] = ["\\end_inset"]
347 file.body[i:i+1] = ['\\layout %s' % file.default_layout]
351 def revert_comment(file):
354 i = find_tokens(file.body, ["\\begin_inset Comment", "\\begin_inset Greyedout"], i)
358 file.body[i] = "\\begin_inset Note"
365 def add_end_layout(file):
366 i = find_token(file.body, '\\layout', 0)
372 struct_stack = ["\\layout"]
375 i = find_tokens(file.body, ["\\begin_inset", "\\end_inset", "\\layout",
376 "\\begin_deeper", "\\end_deeper", "\\the_end"], i)
379 token = split(file.body[i])[0]
381 file.warning("Truncated file.")
383 file.body.insert(i, '\\the_end')
386 if token == "\\begin_inset":
387 struct_stack.append(token)
391 if token == "\\end_inset":
392 tail = struct_stack.pop()
393 if tail == "\\layout":
394 file.body.insert(i,"")
395 file.body.insert(i,"\\end_layout")
397 #Check if it is the correct tag
402 if token == "\\layout":
403 tail = struct_stack.pop()
405 file.body.insert(i,"")
406 file.body.insert(i,"\\end_layout")
409 struct_stack.append(tail)
411 struct_stack.append(token)
414 if token == "\\begin_deeper":
415 file.body.insert(i,"")
416 file.body.insert(i,"\\end_layout")
418 struct_stack.append(token)
421 if token == "\\end_deeper":
422 if struct_stack[-1] == '\\layout':
423 file.body.insert(i, '\\end_layout')
430 file.body.insert(i, "")
431 file.body.insert(i, "\\end_layout")
435 def rm_end_layout(file):
438 i = find_token(file.body, '\\end_layout', i)
447 # Handle change tracking keywords
449 def insert_tracking_changes(file):
450 i = find_token(file.header, "\\tracking_changes", 0)
452 file.header.append("\\tracking_changes 0")
455 def rm_tracking_changes(file):
456 i = find_token(file.header, "\\author", 0)
460 i = find_token(file.header, "\\tracking_changes", 0)
466 def rm_body_changes(file):
469 i = find_token(file.body, "\\change_", i)
477 # \layout -> \begin_layout
479 def layout2begin_layout(file):
482 i = find_token(file.body, '\\layout', i)
486 file.body[i] = replace(file.body[i], '\\layout', '\\begin_layout')
490 def begin_layout2layout(file):
493 i = find_token(file.body, '\\begin_layout', i)
497 file.body[i] = replace(file.body[i], '\\begin_layout', '\\layout')
502 # valignment="center" -> valignment="middle"
504 def convert_valignment_middle(body, start, end):
505 for i in range(start, end):
506 if re.search('^<(column|cell) .*valignment="center".*>$', body[i]):
507 body[i] = replace(body[i], 'valignment="center"', 'valignment="middle"')
510 def convert_table_valignment_middle(file):
511 regexp = re.compile(r'^\\begin_inset\s+Tabular')
514 i = find_re(file.body, regexp, i)
517 j = find_end_of_inset(file.body, i + 1)
519 #this should not happen
520 convert_valignment_middle(file.body, i + 1, len(file.body))
522 convert_valignment_middle(file.body, i + 1, j)
526 def revert_table_valignment_middle(body, start, end):
527 for i in range(start, end):
528 if re.search('^<(column|cell) .*valignment="middle".*>$', body[i]):
529 body[i] = replace(body[i], 'valignment="middle"', 'valignment="center"')
532 def revert_valignment_middle(file):
533 regexp = re.compile(r'^\\begin_inset\s+Tabular')
536 i = find_re(file.body, regexp, i)
539 j = find_end_of_inset(file.body, i + 1)
541 #this should not happen
542 revert_table_valignment_middle(file.body, i + 1, len(file.body))
544 revert_table_valignment_middle(file.body, i + 1, j)
549 # \the_end -> \end_document
551 def convert_end_document(file):
552 i = find_token(file.body, "\\the_end", 0)
554 file.body.append("\\end_document")
556 file.body[i] = "\\end_document"
559 def revert_end_document(file):
560 i = find_token(file.body, "\\end_document", 0)
562 file.body.append("\\the_end")
564 file.body[i] = "\\the_end"
568 # Convert line and page breaks
571 #\line_top \line_bottom \pagebreak_top \pagebreak_bottom \added_space_top xxx \added_space_bottom yyy
575 #\begin layout Standard
581 #\begin layout Standard
588 #\begin_inset VSpace xxx
593 #\begin_inset VSpace xxx
600 def convert_breaks(file):
601 par_params = ('added_space_bottom', 'added_space_top', 'align',
602 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
603 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
605 font_attributes = ['\\family', '\\series', '\\shape', '\\emph',
606 '\\numeric', '\\bar', '\\noun', '\\color', '\\lang']
607 attribute_values = ['default', 'default', 'default', 'default',
608 'default', 'default', 'default', 'none', file.language]
611 i = find_token(file.body, "\\begin_layout", i)
614 layout = get_layout(file.body[i], file.default_layout)
617 # Merge all paragraph parameters into a single line
618 # We cannot check for '\\' only because paragraphs may start e.g.
620 while file.body[i + 1][:1] == '\\' and split(file.body[i + 1][1:])[0] in par_params:
621 file.body[i] = file.body[i + 1] + ' ' + file.body[i]
624 line_top = find(file.body[i],"\\line_top")
625 line_bot = find(file.body[i],"\\line_bottom")
626 pb_top = find(file.body[i],"\\pagebreak_top")
627 pb_bot = find(file.body[i],"\\pagebreak_bottom")
628 vspace_top = find(file.body[i],"\\added_space_top")
629 vspace_bot = find(file.body[i],"\\added_space_bottom")
631 if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1:
634 # Do we have a nonstandard paragraph? We need to create new paragraphs
635 # if yes to avoid putting lyxline etc. inside of special environments.
636 # This is wrong for itemize and enumerate environments, but it is
637 # impossible to convert these correctly.
638 # We want to avoid new paragraphs if possible becauase we want to
639 # inherit font sizes.
641 if (not file.is_default_layout(layout) or
642 find(file.body[i],"\\align") != -1 or
643 find(file.body[i],"\\labelwidthstring") != -1 or
644 find(file.body[i],"\\noindent") != -1):
647 # get the font size of the beginning of this paragraph, since we need
648 # it for the lyxline inset
650 while not is_nonempty_line(file.body[j]):
653 if find(file.body[j], "\\size") != -1:
654 size_top = split(file.body[j])[1]
656 for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom":
657 file.body[i] = replace(file.body[i], tag, "")
660 # the position could be change because of the removal of other
661 # paragraph properties above
662 vspace_top = find(file.body[i],"\\added_space_top")
663 tmp_list = split(file.body[i][vspace_top:])
664 vspace_top_value = tmp_list[1]
665 file.body[i] = file.body[i][:vspace_top] + join(tmp_list[2:])
668 # the position could be change because of the removal of other
669 # paragraph properties above
670 vspace_bot = find(file.body[i],"\\added_space_bottom")
671 tmp_list = split(file.body[i][vspace_bot:])
672 vspace_bot_value = tmp_list[1]
673 file.body[i] = file.body[i][:vspace_bot] + join(tmp_list[2:])
675 file.body[i] = strip(file.body[i])
678 # Create an empty paragraph or paragraph fragment for line and
679 # page break that belong above the paragraph
680 if pb_top !=-1 or line_top != -1 or vspace_top != -1:
682 paragraph_above = list()
684 # We need to create an extra paragraph for nonstandard environments
685 paragraph_above = ['\\begin_layout %s' % file.default_layout, '']
688 paragraph_above.extend(['\\newpage ',''])
691 paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','',''])
695 paragraph_above.extend(['\\size ' + size_top + ' '])
696 # We need an additional vertical space of -\parskip.
697 # We can't use the vspace inset because it does not know \parskip.
698 paragraph_above.extend(['\\lyxline ', '', ''])
699 insert_ert(paragraph_above, len(paragraph_above) - 1, 'Collapsed',
700 '\\vspace{-1\\parskip}\n', file.format + 1, file.default_layout)
701 paragraph_above.extend([''])
704 paragraph_above.extend(['\\end_layout ',''])
705 # insert new paragraph above the current paragraph
706 file.body[i-2:i-2] = paragraph_above
708 # insert new lines at the beginning of the current paragraph
709 file.body[i:i] = paragraph_above
711 i = i + len(paragraph_above)
713 # Ensure that nested style are converted later.
714 k = find_end_of(file.body, i, "\\begin_layout", "\\end_layout")
719 if pb_bot !=-1 or line_bot != -1 or vspace_bot != -1:
721 # get the font size of the end of this paragraph
725 if find(file.body[j], "\\size") != -1:
726 size_bot = split(file.body[j])[1]
728 elif find(file.body[j], "\\begin_inset") != -1:
730 j = find_end_of_inset(file.body, j)
734 paragraph_below = list()
736 # We need to create an extra paragraph for nonstandard environments
737 paragraph_below = ['', '\\begin_layout %s' % file.default_layout, '']
739 for a in range(len(font_attributes)):
740 if find_token(file.body, font_attributes[a], i, k) != -1:
741 paragraph_below.extend([font_attributes[a] + ' ' + attribute_values[a]])
744 if nonstandard and size_bot != '':
745 paragraph_below.extend(['\\size ' + size_bot + ' '])
746 paragraph_below.extend(['\\lyxline ',''])
748 paragraph_below.extend(['\\size default '])
751 paragraph_below.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','',''])
754 paragraph_below.extend(['\\newpage ',''])
757 paragraph_below.extend(['\\end_layout '])
758 # insert new paragraph below the current paragraph
759 file.body[k+1:k+1] = paragraph_below
761 # insert new lines at the end of the current paragraph
762 file.body[k:k] = paragraph_below
768 def convert_note(file):
771 i = find_tokens(file.body, ["\\begin_inset Note",
772 "\\begin_inset Comment",
773 "\\begin_inset Greyedout"], i)
777 file.body[i] = file.body[i][0:13] + 'Note ' + file.body[i][13:]
781 def revert_note(file):
782 note_header = "\\begin_inset Note "
785 i = find_token(file.body, note_header, i)
789 file.body[i] = "\\begin_inset " + file.body[i][len(note_header):]
796 def convert_box(file):
799 i = find_tokens(file.body, ["\\begin_inset Boxed",
800 "\\begin_inset Doublebox",
801 "\\begin_inset Frameless",
802 "\\begin_inset ovalbox",
803 "\\begin_inset Ovalbox",
804 "\\begin_inset Shadowbox"], i)
808 file.body[i] = file.body[i][0:13] + 'Box ' + file.body[i][13:]
812 def revert_box(file):
813 box_header = "\\begin_inset Box "
816 i = find_token(file.body, box_header, i)
820 file.body[i] = "\\begin_inset " + file.body[i][len(box_header):]
827 def convert_collapsable(file):
830 i = find_tokens_exact(file.body, ["\\begin_inset Box",
831 "\\begin_inset Branch",
832 "\\begin_inset CharStyle",
833 "\\begin_inset Float",
834 "\\begin_inset Foot",
835 "\\begin_inset Marginal",
836 "\\begin_inset Note",
837 "\\begin_inset OptArg",
838 "\\begin_inset Wrap"], i)
842 # Seach for a line starting 'collapsed'
843 # If, however, we find a line starting '\begin_layout'
844 # (_always_ present) then break with a warning message
847 if (file.body[i] == "collapsed false"):
848 file.body[i] = "status open"
850 elif (file.body[i] == "collapsed true"):
851 file.body[i] = "status collapsed"
853 elif (file.body[i][:13] == "\\begin_layout"):
854 file.warning("Malformed LyX file: Missing 'collapsed'.")
861 def revert_collapsable(file):
864 i = find_tokens_exact(file.body, ["\\begin_inset Box",
865 "\\begin_inset Branch",
866 "\\begin_inset CharStyle",
867 "\\begin_inset Float",
868 "\\begin_inset Foot",
869 "\\begin_inset Marginal",
870 "\\begin_inset Note",
871 "\\begin_inset OptArg",
872 "\\begin_inset Wrap"], i)
876 # Seach for a line starting 'status'
877 # If, however, we find a line starting '\begin_layout'
878 # (_always_ present) then break with a warning message
881 if (file.body[i] == "status open"):
882 file.body[i] = "collapsed false"
884 elif (file.body[i] == "status collapsed" or
885 file.body[i] == "status inlined"):
886 file.body[i] = "collapsed true"
888 elif (file.body[i][:13] == "\\begin_layout"):
889 file.warning("Malformed LyX file: Missing 'status'.")
899 def convert_ert(file):
902 i = find_token(file.body, "\\begin_inset ERT", i)
906 # Seach for a line starting 'status'
907 # If, however, we find a line starting '\begin_layout'
908 # (_always_ present) then break with a warning message
911 if (file.body[i] == "status Open"):
912 file.body[i] = "status open"
914 elif (file.body[i] == "status Collapsed"):
915 file.body[i] = "status collapsed"
917 elif (file.body[i] == "status Inlined"):
918 file.body[i] = "status inlined"
920 elif (file.body[i][:13] == "\\begin_layout"):
921 file.warning("Malformed LyX file: Missing 'status'.")
928 def revert_ert(file):
931 i = find_token(file.body, "\\begin_inset ERT", i)
935 # Seach for a line starting 'status'
936 # If, however, we find a line starting '\begin_layout'
937 # (_always_ present) then break with a warning message
940 if (file.body[i] == "status open"):
941 file.body[i] = "status Open"
943 elif (file.body[i] == "status collapsed"):
944 file.body[i] = "status Collapsed"
946 elif (file.body[i] == "status inlined"):
947 file.body[i] = "status Inlined"
949 elif (file.body[i][:13] == "\\begin_layout"):
950 file.warning("Malformed LyX file : Missing 'status'.")
960 def convert_minipage(file):
961 """ Convert minipages to the box inset.
962 We try to use the same order of arguments as lyx does.
965 inner_pos = ["c","t","b","s"]
969 i = find_token(file.body, "\\begin_inset Minipage", i)
973 file.body[i] = "\\begin_inset Box Frameless"
976 # convert old to new position using the pos list
977 if file.body[i][:8] == "position":
978 file.body[i] = 'position "%s"' % pos[int(file.body[i][9])]
980 file.body.insert(i, 'position "%s"' % pos[0])
983 file.body.insert(i, 'hor_pos "c"')
985 file.body.insert(i, 'has_inner_box 1')
988 # convert the inner_position
989 if file.body[i][:14] == "inner_position":
990 innerpos = inner_pos[int(file.body[i][15])]
993 innerpos = inner_pos[0]
995 # We need this since the new file format has a height and width
996 # in a different order.
997 if file.body[i][:6] == "height":
998 height = file.body[i][6:]
999 # test for default value of 221 and convert it accordingly
1000 if height == ' "0pt"' or height == ' "0"':
1006 if file.body[i][:5] == "width":
1007 width = file.body[i][5:]
1012 if file.body[i][:9] == "collapsed":
1013 if file.body[i][9:] == "true":
1014 status = "collapsed"
1019 status = "collapsed"
1021 # Handle special default case:
1022 if height == ' "1pt"' and innerpos == 'c':
1025 file.body.insert(i, 'inner_pos "' + innerpos + '"')
1027 file.body.insert(i, 'use_parbox 0')
1029 file.body.insert(i, 'width' + width)
1031 file.body.insert(i, 'special "none"')
1033 file.body.insert(i, 'height' + height)
1035 file.body.insert(i, 'height_special "totalheight"')
1037 file.body.insert(i, 'status ' + status)
1041 # -------------------------------------------------------------------------------------------
1042 # Convert backslashes and '\n' into valid ERT code, append the converted
1043 # text to body[i] and return the (maybe incremented) line index i
1044 def convert_ertbackslash(body, i, ert, format, default_layout):
1047 body[i] = body[i] + '\\backslash '
1052 body[i+1:i+1] = ['\\newline ', '']
1055 body[i+1:i+1] = ['\\end_layout', '', '\\begin_layout %s' % default_layout, '']
1058 body[i] = body[i] + c
1062 # Converts lines in ERT code to LaTeX
1063 # The surrounding \begin_layout ... \end_layout pair must not be included
1064 def ert2latex(lines, format):
1065 backslash = re.compile(r'\\backslash\s*$')
1066 newline = re.compile(r'\\newline\s*$')
1068 begin_layout = re.compile(r'\\layout\s*\S+$')
1070 begin_layout = re.compile(r'\\begin_layout\s*\S+$')
1071 end_layout = re.compile(r'\\end_layout\s*$')
1073 for i in range(len(lines)):
1074 line = backslash.sub('\\\\', lines[i])
1076 if begin_layout.match(line):
1079 line = newline.sub('\n', line)
1081 if begin_layout.match(line):
1083 if format > 224 and end_layout.match(line):
1089 # get all paragraph parameters. They can be all on one line or on several lines.
1090 # lines[i] must be the first parameter line
1091 def get_par_params(lines, i):
1092 par_params = ('added_space_bottom', 'added_space_top', 'align',
1093 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
1094 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
1095 'start_of_appendix')
1096 # We cannot check for '\\' only because paragraphs may start e.g.
1097 # with '\\backslash'
1099 while lines[i][:1] == '\\' and split(lines[i][1:])[0] in par_params:
1100 params = params + ' ' + strip(lines[i])
1102 return strip(params)
1105 # convert LyX font size to LaTeX fontsize
1106 def lyxsize2latexsize(lyxsize):
1107 sizes = {"tiny" : "tiny", "scriptsize" : "scriptsize",
1108 "footnotesize" : "footnotesize", "small" : "small",
1109 "normal" : "normalsize", "large" : "large", "larger" : "Large",
1110 "largest" : "LARGE", "huge" : "huge", "giant" : "Huge"}
1111 if lyxsize in sizes:
1112 return '\\' + sizes[lyxsize]
1116 # Change vspace insets, page breaks and lyxlines to paragraph options
1117 # (if possible) or ERT
1118 def revert_breaks(file):
1120 # Get default spaceamount
1121 i = find_token(file.header, '\\defskip', 0)
1123 defskipamount = 'medskip'
1125 defskipamount = split(file.header[i])[1]
1127 keys = {"\\begin_inset" : "vspace", "\\lyxline" : "lyxline",
1128 "\\newpage" : "newpage"}
1129 keywords_top = {"vspace" : "\\added_space_top", "lyxline" : "\\line_top",
1130 "newpage" : "\\pagebreak_top"}
1131 keywords_bot = {"vspace" : "\\added_space_bottom", "lyxline" : "\\line_bottom",
1132 "newpage" : "\\pagebreak_bottom"}
1133 tokens = ["\\begin_inset VSpace", "\\lyxline", "\\newpage"]
1135 # Convert the insets
1138 i = find_tokens(file.body, tokens, i)
1142 # Are we at the beginning of a paragraph?
1144 this_par = get_paragraph(file.body, i, file.format - 1)
1145 start = this_par + 1
1146 params = get_par_params(file.body, start)
1148 # Paragraph parameters may be on one or more lines.
1149 # Find the start of the real paragraph text.
1150 while file.body[start][:1] == '\\' and split(file.body[start])[0] in params:
1152 for k in range(start, i):
1153 if find(file.body[k], "\\size") != -1:
1155 size = split(file.body[k])[1]
1156 elif is_nonempty_line(file.body[k]):
1159 # Find the end of the real paragraph text.
1160 next_par = get_next_paragraph(file.body, i, file.format - 1)
1162 file.warning("Malformed LyX file: Missing next paragraph.")
1166 # first line of our insets
1168 # last line of our insets
1169 inset_end = inset_start
1170 # Are we at the end of a paragraph?
1172 # start and end line numbers to delete if we convert this inset
1174 # is this inset a lyxline above a paragraph?
1176 # raw inset information
1178 # name of this inset
1180 # font size of this inset
1183 # Detect subsequent lyxline, vspace and pagebreak insets created by convert_breaks()
1187 if find_tokens(file.body, tokens, k) == k:
1189 lines.append(split(file.body[k]))
1190 insets.append(keys[lines[n][0]])
1191 del_lines.append([k, k])
1196 elif find(file.body[k], "\\size") != -1:
1198 size = split(file.body[k])[1]
1199 elif find_token(file.body, "\\begin_inset ERT", k) == k:
1200 ert_begin = find_token(file.body, "\\layout", k) + 1
1202 file.warning("Malformed LyX file: Missing '\\layout'.")
1204 ert_end = find_end_of_inset(file.body, k)
1206 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1208 ert = ert2latex(file.body[ert_begin:ert_end], file.format - 1)
1209 if (n > 0 and insets[n - 1] == "lyxline" and
1210 ert == '\\vspace{-1\\parskip}\n'):
1211 # vspace ERT created by convert_breaks() for top lyxline
1213 del_lines[n - 1][1] = ert_end
1219 elif (n > 0 and insets[n - 1] == "vspace" and
1220 find_token(file.body, "\\end_inset", k) == k):
1221 # ignore end of vspace inset
1222 del_lines[n - 1][1] = k
1224 elif is_nonempty_line(file.body[k]):
1229 # Determine space amount for vspace insets
1230 spaceamount = list()
1233 if insets[k] == "vspace":
1234 spaceamount.append(lines[k][2])
1235 arguments.append(' ' + spaceamount[k] + ' ')
1237 spaceamount.append('')
1238 arguments.append(' ')
1240 # Can we convert to top paragraph parameters?
1242 if ((n == 3 and insets[0] == "newpage" and insets[1] == "vspace" and
1243 insets[2] == "lyxline" and top[2]) or
1245 ((insets[0] == "newpage" and insets[1] == "vspace") or
1246 (insets[0] == "newpage" and insets[1] == "lyxline" and top[1]) or
1247 (insets[0] == "vspace" and insets[1] == "lyxline" and top[1]))) or
1248 (n == 1 and insets[0] == "lyxline" and top[0])):
1249 # These insets have been created before a paragraph by
1253 # Can we convert to bottom paragraph parameters?
1255 if ((n == 3 and insets[0] == "lyxline" and not top[0] and
1256 insets[1] == "vspace" and insets[2] == "newpage") or
1258 ((insets[0] == "lyxline" and not top[0] and insets[1] == "vspace") or
1259 (insets[0] == "lyxline" and not top[0] and insets[1] == "newpage") or
1260 (insets[0] == "vspace" and insets[1] == "newpage"))) or
1261 (n == 1 and insets[0] == "lyxline" and not top[0])):
1262 # These insets have been created after a paragraph by
1266 if paragraph_start and paragraph_end:
1267 # We are in a paragraph of our own.
1268 # We must not delete this paragraph if it has parameters
1270 # First try to merge with the previous paragraph.
1271 # We try the previous paragraph first because we would
1272 # otherwise need ERT for two subsequent vspaces.
1273 prev_par = get_paragraph(file.body, this_par - 1, file.format - 1) + 1
1274 if prev_par > 0 and not before:
1275 prev_params = get_par_params(file.body, prev_par + 1)
1277 # determine font size
1278 prev_size = "normal"
1280 while file.body[k][:1] == '\\' and split(file.body[k])[0] in prev_params:
1283 if find(file.body[k], "\\size") != -1:
1284 prev_size = split(file.body[k])[1]
1286 elif find(file.body[k], "\\begin_inset") != -1:
1288 k = find_end_of_inset(file.body, k)
1289 elif is_nonempty_line(file.body[k]):
1293 if (keywords_bot[insets[k]] in prev_params or
1294 (insets[k] == "lyxline" and sizes[k] != prev_size)):
1299 file.body.insert(prev_par + 1,
1300 keywords_bot[insets[k]] + arguments[k])
1301 del file.body[this_par+n:next_par-1+n]
1304 # Then try next paragraph
1305 if next_par > 0 and not after:
1306 next_params = get_par_params(file.body, next_par + 1)
1308 while file.body[k][:1] == '\\' and split(file.body[k])[0] in next_params:
1310 # determine font size
1311 next_size = "normal"
1314 if find(file.body[k], "\\size") != -1:
1315 next_size = split(file.body[k])[1]
1317 elif is_nonempty_line(file.body[k]):
1321 if (keywords_top[insets[k]] in next_params or
1322 (insets[k] == "lyxline" and sizes[k] != next_size)):
1327 file.body.insert(next_par + 1,
1328 keywords_top[insets[k]] + arguments[k])
1329 del file.body[this_par:next_par-1]
1332 elif paragraph_start or paragraph_end:
1333 # Convert to paragraph formatting if we are at the beginning or end
1334 # of a paragraph and the resulting paragraph would not be empty
1335 # The order is important: del and insert invalidate some indices
1337 keywords = keywords_top
1339 keywords = keywords_bot
1342 if keywords[insets[k]] in params:
1347 file.body.insert(this_par + 1,
1348 keywords[insets[k]] + arguments[k])
1349 for j in range(k, n):
1350 del_lines[j][0] = del_lines[j][0] + 1
1351 del_lines[j][1] = del_lines[j][1] + 1
1352 del file.body[del_lines[k][0]:del_lines[k][1]+1]
1353 deleted = del_lines[k][1] - del_lines[k][0] + 1
1354 for j in range(k + 1, n):
1355 del_lines[j][0] = del_lines[j][0] - deleted
1356 del_lines[j][1] = del_lines[j][1] - deleted
1360 # Convert the first inset to ERT.
1361 # The others are converted in the next loop runs (if they exist)
1362 if insets[0] == "vspace":
1363 file.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
1364 '\\layout %s' % file.default_layout, '', '\\backslash ']
1366 if spaceamount[0][-1] == '*':
1367 spaceamount[0] = spaceamount[0][:-1]
1372 # Replace defskip by the actual value
1373 if spaceamount[0] == 'defskip':
1374 spaceamount[0] = defskipamount
1376 # LaTeX does not know \\smallskip* etc
1378 if spaceamount[0] == 'smallskip':
1379 spaceamount[0] = '\\smallskipamount'
1380 elif spaceamount[0] == 'medskip':
1381 spaceamount[0] = '\\medskipamount'
1382 elif spaceamount[0] == 'bigskip':
1383 spaceamount[0] = '\\bigskipamount'
1384 elif spaceamount[0] == 'vfill':
1385 spaceamount[0] = '\\fill'
1387 # Finally output the LaTeX code
1388 if (spaceamount[0] == 'smallskip' or spaceamount[0] == 'medskip' or
1389 spaceamount[0] == 'bigskip' or spaceamount[0] == 'vfill'):
1390 file.body.insert(i, spaceamount[0] + '{}')
1393 file.body.insert(i, 'vspace*{')
1395 file.body.insert(i, 'vspace{')
1396 i = convert_ertbackslash(file.body, i, spaceamount[0], file.format - 1, file.default_layout)
1397 file.body[i] = file.body[i] + '}'
1399 elif insets[0] == "lyxline":
1401 latexsize = lyxsize2latexsize(size)
1403 file.warning("Could not convert LyX fontsize '%s' to LaTeX font size." % size)
1404 latexsize = '\\normalsize'
1405 i = insert_ert(file.body, i, 'Collapsed',
1406 '\\lyxline{%s}' % latexsize,
1407 file.format - 1, file.default_layout)
1408 # We use \providecommand so that we don't get an error if native
1409 # lyxlines are used (LyX writes first its own preamble and then
1410 # the user specified one)
1411 add_to_preamble(file,
1412 ['% Commands inserted by lyx2lyx for lyxlines',
1413 '\\providecommand{\\lyxline}[1]{',
1414 ' {#1 \\vspace{1ex} \\hrule width \\columnwidth \\vspace{1ex}}'
1416 elif insets[0] == "newpage":
1418 i = insert_ert(file.body, i, 'Collapsed', '\\newpage{}',
1419 file.format - 1, file.default_layout)
1422 # Convert a LyX length into a LaTeX length
1423 def convert_len(len, special):
1424 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
1425 "page%":"\\pagewidth", "line%":"\\linewidth",
1426 "theight%":"\\textheight", "pheight%":"\\pageheight"}
1428 # Convert special lengths
1429 if special != 'none':
1430 len = '%f\\' % len2value(len) + special
1432 # Convert LyX units to LaTeX units
1433 for unit in units.keys():
1434 if find(len, unit) != -1:
1435 len = '%f' % (len2value(len) / 100) + units[unit]
1441 # Convert a LyX length into valid ERT code and append it to body[i]
1442 # Return the (maybe incremented) line index i
1443 def convert_ertlen(body, i, len, special, format, default_layout):
1444 # Convert backslashes and insert the converted length into body
1445 return convert_ertbackslash(body, i, convert_len(len, special), format, default_layout)
1448 # Return the value of len without the unit in numerical form
1450 result = re.search('([+-]?[0-9.]+)', len)
1452 return float(result.group(1))
1453 # No number means 1.0
1457 # Convert text to ERT and insert it at body[i]
1458 # Return the index of the line after the inserted ERT
1459 def insert_ert(body, i, status, text, format, default_layout):
1460 body[i:i] = ['\\begin_inset ERT', 'status ' + status, '']
1463 body[i:i] = ['\\layout %s' % default_layout, '']
1465 body[i:i] = ['\\begin_layout %s' % default_layout, '']
1466 i = i + 1 # i points now to the just created empty line
1467 i = convert_ertbackslash(body, i, text, format, default_layout) + 1
1469 body[i:i] = ['\\end_layout']
1471 body[i:i] = ['', '\\end_inset', '']
1476 # Add text to the preamble if it is not already there.
1477 # Only the first line is checked!
1478 def add_to_preamble(file, text):
1479 if find_token(file.preamble, text[0], 0) != -1:
1482 file.preamble.extend(text)
1485 def convert_frameless_box(file):
1486 pos = ['t', 'c', 'b']
1487 inner_pos = ['c', 't', 'b', 's']
1490 i = find_token(file.body, '\\begin_inset Frameless', i)
1493 j = find_end_of_inset(file.body, i)
1495 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1502 params = {'position':0, 'hor_pos':'c', 'has_inner_box':'1',
1503 'inner_pos':1, 'use_parbox':'0', 'width':'100col%',
1504 'special':'none', 'height':'1in',
1505 'height_special':'totalheight', 'collapsed':'false'}
1506 for key in params.keys():
1507 value = replace(get_value(file.body, key, i, j), '"', '')
1509 if key == 'position':
1510 # convert new to old position: 'position "t"' -> 0
1511 value = find_token(pos, value, 0)
1514 elif key == 'inner_pos':
1515 # convert inner position
1516 value = find_token(inner_pos, value, 0)
1521 j = del_token(file.body, key, i, j)
1524 # Convert to minipage or ERT?
1525 # Note that the inner_position and height parameters of a minipage
1526 # inset are ignored and not accessible for the user, although they
1527 # are present in the file format and correctly read in and written.
1528 # Therefore we convert to ERT if they do not have their LaTeX
1529 # defaults. These are:
1530 # - the value of "position" for "inner_pos"
1531 # - "\totalheight" for "height"
1532 if (params['use_parbox'] != '0' or
1533 params['has_inner_box'] != '1' or
1534 params['special'] != 'none' or
1535 params['height_special'] != 'totalheight' or
1536 len2value(params['height']) != 1.0):
1538 # Here we know that this box is not supported in file format 224.
1539 # Therefore we need to convert it to ERT. We can't simply convert
1540 # the beginning and end of the box to ERT, because the
1541 # box inset may contain layouts that are different from the
1542 # surrounding layout. After the conversion the contents of the
1543 # box inset is on the same level as the surrounding text, and
1544 # paragraph layouts and align parameters can get mixed up.
1546 # A possible solution for this problem:
1547 # Convert the box to a minipage and redefine the minipage
1548 # environment in ERT so that the original box is simulated.
1549 # For minipages we could do this in a way that the width and
1550 # position can still be set from LyX, but this did not work well.
1551 # This is not possible for parboxes either, so we convert the
1552 # original box to ERT, put the minipage inset inside the box
1553 # and redefine the minipage environment to be empty.
1555 # Commands that are independant of a particular box can go to
1557 # We need to define lyxtolyxrealminipage with 3 optional
1558 # arguments although LyX 1.3 uses only the first one.
1559 # Otherwise we will get LaTeX errors if this document is
1560 # converted to format 225 or above again (LyX 1.4 uses all
1561 # optional arguments).
1562 add_to_preamble(file,
1563 ['% Commands inserted by lyx2lyx for frameless boxes',
1564 '% Save the original minipage environment',
1565 '\\let\\lyxtolyxrealminipage\\minipage',
1566 '\\let\\endlyxtolyxrealminipage\\endminipage',
1567 '% Define an empty lyxtolyximinipage environment',
1568 '% with 3 optional arguments',
1569 '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1570 '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1571 ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1572 ' {\\end{lyxtolyxiiiminipage}}',
1573 '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1574 ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1575 ' {\\end{lyxtolyxiiminipage}}',
1576 '\\newenvironment{lyxtolyxminipage}[1][c]%',
1577 ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1578 ' {\\end{lyxtolyximinipage}}'])
1580 if params['use_parbox'] != '0':
1583 ert = '\\begin{lyxtolyxrealminipage}'
1585 # convert optional arguments only if not latex default
1586 if (pos[params['position']] != 'c' or
1587 inner_pos[params['inner_pos']] != pos[params['position']] or
1588 params['height_special'] != 'totalheight' or
1589 len2value(params['height']) != 1.0):
1590 ert = ert + '[' + pos[params['position']] + ']'
1591 if (inner_pos[params['inner_pos']] != pos[params['position']] or
1592 params['height_special'] != 'totalheight' or
1593 len2value(params['height']) != 1.0):
1594 ert = ert + '[' + convert_len(params['height'],
1595 params['height_special']) + ']'
1596 if inner_pos[params['inner_pos']] != pos[params['position']]:
1597 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1599 ert = ert + '{' + convert_len(params['width'],
1600 params['special']) + '}'
1602 if params['use_parbox'] != '0':
1604 ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1605 ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1608 i = insert_ert(file.body, i, 'Collapsed', ert, file.format - 1, file.default_layout)
1609 j = j + i - old_i - 1
1611 file.body[i:i] = ['\\begin_inset Minipage',
1612 'position %d' % params['position'],
1615 'width "' + params['width'] + '"',
1616 'collapsed ' + params['collapsed']]
1620 # Restore the original minipage environment since we may have
1621 # minipages inside this box.
1622 # Start a new paragraph because the following may be nonstandard
1623 file.body[i:i] = ['\\layout %s' % file.default_layout, '', '']
1626 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1627 ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1629 i = insert_ert(file.body, i, 'Collapsed', ert, file.format - 1, file.default_layout)
1630 j = j + i - old_i - 1
1632 # Redefine the minipage end before the inset end.
1633 # Start a new paragraph because the previous may be nonstandard
1634 file.body[j:j] = ['\\layout %s' % file.default_layout, '', '']
1636 ert = '\\let\\endminipage\\endlyxtolyxminipage'
1637 j = insert_ert(file.body, j, 'Collapsed', ert, file.format - 1, file.default_layout)
1639 file.body.insert(j, '')
1642 # LyX writes '%\n' after each box. Therefore we need to end our
1643 # ERT with '%\n', too, since this may swallow a following space.
1644 if params['use_parbox'] != '0':
1647 ert = '\\end{lyxtolyxrealminipage}%\n'
1648 j = insert_ert(file.body, j, 'Collapsed', ert, file.format - 1, file.default_layout)
1650 # We don't need to restore the original minipage after the inset
1651 # end because the scope of the redefinition is the original box.
1655 # Convert to minipage
1656 file.body[i:i] = ['\\begin_inset Minipage',
1657 'position %d' % params['position'],
1658 'inner_position %d' % params['inner_pos'],
1659 'height "' + params['height'] + '"',
1660 'width "' + params['width'] + '"',
1661 'collapsed ' + params['collapsed']]
1665 def remove_branches(file):
1668 i = find_token(file.header, "\\branch", i)
1671 file.warning("Removing branch %s." % split(file.header[i])[1])
1672 j = find_token(file.header, "\\end_branch", i)
1674 file.warning("Malformed LyX file: Missing '\\end_branch'.")
1676 del file.header[i:j+1]
1680 i = find_token(file.body, "\\begin_inset Branch", i)
1683 j = find_end_of_inset(file.body, i)
1685 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1689 del file.body[j - 1]
1690 # Seach for a line starting 'collapsed'
1691 # If, however, we find a line starting '\layout'
1692 # (_always_ present) then break with a warning message
1695 if (file.body[i][:9] == "collapsed"):
1699 elif (file.body[i][:7] == "\\layout"):
1700 if collapsed_found == 0:
1701 file.warning("Malformed LyX file: Missing 'collapsed'.")
1702 # Delete this new paragraph, since it would not appear in
1703 # .tex output. This avoids also empty paragraphs.
1713 def convert_jurabib(file):
1714 i = find_token(file.header, '\\use_numerical_citations', 0)
1716 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1718 file.header.insert(i + 1, '\\use_jurabib 0')
1721 def revert_jurabib(file):
1722 i = find_token(file.header, '\\use_jurabib', 0)
1724 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1726 if get_value(file.header, '\\use_jurabib', 0) != "0":
1727 file.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1728 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1736 def convert_bibtopic(file):
1737 i = find_token(file.header, '\\use_jurabib', 0)
1739 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1741 file.header.insert(i + 1, '\\use_bibtopic 0')
1744 def revert_bibtopic(file):
1745 i = find_token(file.header, '\\use_bibtopic', 0)
1747 file.warning("Malformed lyx file: Missing '\\use_bibtopic'.")
1749 if get_value(file.header, '\\use_bibtopic', 0) != "0":
1750 file.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1751 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1758 def convert_float(file):
1761 i = find_token_exact(file.body, '\\begin_inset Float', i)
1764 # Seach for a line starting 'wide'
1765 # If, however, we find a line starting '\begin_layout'
1766 # (_always_ present) then break with a warning message
1769 if (file.body[i][:4] == "wide"):
1770 file.body.insert(i + 1, 'sideways false')
1772 elif (file.body[i][:13] == "\\begin_layout"):
1773 file.warning("Malformed lyx file: Missing 'wide'.")
1779 def revert_float(file):
1782 i = find_token_exact(file.body, '\\begin_inset Float', i)
1785 j = find_end_of_inset(file.body, i)
1787 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1790 if get_value(file.body, 'sideways', i, j) != "false":
1791 file.warning("Conversion of 'sideways true' not yet implemented.")
1792 # Don't remove 'sideways' so that people will get warnings by lyx
1795 del_token(file.body, 'sideways', i, j)
1799 def convert_graphics(file):
1800 """ Add extension to filenames of insetgraphics if necessary.
1804 i = find_token(file.body, "\\begin_inset Graphics", i)
1808 j = find_token_exact(file.body, "filename", i)
1812 filename = split(file.body[j])[1]
1813 absname = os.path.normpath(os.path.join(file.dir, filename))
1814 if file.input == stdin and not os.path.isabs(filename):
1815 # We don't know the directory and cannot check the file.
1816 # We could use a heuristic and take the current directory,
1817 # and we could try to find out if filename has an extension,
1818 # but that would be just guesses and could be wrong.
1819 file.warning("""Warning: Can not determine whether file
1821 needs an extension when reading from standard input.
1822 You may need to correct the file manually or run
1823 lyx2lyx again with the .lyx file as commandline argument.""" % filename)
1825 # This needs to be the same algorithm as in pre 233 insetgraphics
1826 if access(absname, F_OK):
1828 if access(absname + ".ps", F_OK):
1829 file.body[j] = replace(file.body[j], filename, filename + ".ps")
1831 if access(absname + ".eps", F_OK):
1832 file.body[j] = replace(file.body[j], filename, filename + ".eps")
1836 # Convert firstname and surname from styles -> char styles
1838 def convert_names(file):
1839 """ Convert in the docbook backend from firstname and surname style
1842 if file.backend != "docbook":
1848 i = find_token(file.body, "\\begin_layout Author", i)
1853 while file.body[i] == "":
1856 if file.body[i][:11] != "\\end_layout" or file.body[i+2][:13] != "\\begin_deeper":
1861 i = find_end_of( file.body, i+3, "\\begin_deeper","\\end_deeper")
1863 # something is really wrong, abort
1864 file.warning("Missing \\end_deeper, after style Author.")
1865 file.warning("Aborted attempt to parse FirstName and Surname.")
1867 firstname, surname = "", ""
1869 name = file.body[k:i]
1871 j = find_token(name, "\\begin_layout FirstName", 0)
1874 while(name[j] != "\\end_layout"):
1875 firstname = firstname + name[j]
1878 j = find_token(name, "\\begin_layout Surname", 0)
1881 while(name[j] != "\\end_layout"):
1882 surname = surname + name[j]
1886 del file.body[k+2:i+1]
1888 file.body[k-1:k-1] = ["", "",
1889 "\\begin_inset CharStyle Firstname",
1892 '\\begin_layout %s' % file.default_layout,
1900 "\\begin_inset CharStyle Surname",
1903 '\\begin_layout %s' % file.default_layout,
1912 def revert_names(file):
1913 """ Revert in the docbook backend from firstname and surname char style
1916 if file.backend != "docbook":
1921 # \use_natbib 1 \cite_engine <style>
1922 # \use_numerical_citations 0 -> where <style> is one of
1923 # \use_jurabib 0 "basic", "natbib_authoryear",
1924 # "natbib_numerical" or "jurabib"
1925 def convert_cite_engine(file):
1926 a = find_token(file.header, "\\use_natbib", 0)
1928 file.warning("Malformed lyx file: Missing '\\use_natbib'.")
1931 b = find_token(file.header, "\\use_numerical_citations", 0)
1932 if b == -1 or b != a+1:
1933 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1936 c = find_token(file.header, "\\use_jurabib", 0)
1937 if c == -1 or c != b+1:
1938 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1941 use_natbib = int(split(file.header[a])[1])
1942 use_numerical_citations = int(split(file.header[b])[1])
1943 use_jurabib = int(split(file.header[c])[1])
1945 cite_engine = "basic"
1947 if use_numerical_citations:
1948 cite_engine = "natbib_numerical"
1950 cite_engine = "natbib_authoryear"
1952 cite_engine = "jurabib"
1954 del file.header[a:c+1]
1955 file.header.insert(a, "\\cite_engine " + cite_engine)
1958 def revert_cite_engine(file):
1959 i = find_token(file.header, "\\cite_engine", 0)
1961 file.warning("Malformed lyx file: Missing '\\cite_engine'.")
1964 cite_engine = split(file.header[i])[1]
1969 if cite_engine == "natbib_numerical":
1972 elif cite_engine == "natbib_authoryear":
1974 elif cite_engine == "jurabib":
1978 file.header.insert(i, "\\use_jurabib " + use_jurabib)
1979 file.header.insert(i, "\\use_numerical_citations " + use_numerical)
1980 file.header.insert(i, "\\use_natbib " + use_natbib)
1986 def convert_paperpackage(file):
1987 i = find_token(file.header, "\\paperpackage", 0)
1991 packages = {'default':'none','a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
1992 if len(split(file.header[i])) > 1:
1993 paperpackage = split(file.header[i])[1]
1994 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1996 file.header[i] = file.header[i] + ' widemarginsa4'
1999 def revert_paperpackage(file):
2000 i = find_token(file.header, "\\paperpackage", 0)
2004 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
2005 'widemarginsa4':'', 'default': 'default'}
2006 if len(split(file.header[i])) > 1:
2007 paperpackage = split(file.header[i])[1]
2009 paperpackage = 'default'
2010 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
2016 def convert_bullets(file):
2019 i = find_token(file.header, "\\bullet", i)
2022 if file.header[i][:12] == '\\bulletLaTeX':
2023 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1])
2026 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1]) +\
2027 ' ' + strip(file.header[i+2]) + ' ' + strip(file.header[i+3])
2029 del file.header[i+1:i + n]
2033 def revert_bullets(file):
2036 i = find_token(file.header, "\\bullet", i)
2039 if file.header[i][:12] == '\\bulletLaTeX':
2040 n = find(file.header[i], '"')
2042 file.warning("Malformed header.")
2045 file.header[i:i+1] = [file.header[i][:n-1],'\t' + file.header[i][n:], '\\end_bullet']
2048 frag = split(file.header[i])
2050 file.warning("Malformed header.")
2053 file.header[i:i+1] = [frag[0] + ' ' + frag[1],
2062 # \begin_header and \begin_document
2064 def add_begin_header(file):
2065 i = find_token(file.header, '\\lyxformat', 0)
2066 file.header.insert(i+1, '\\begin_header')
2067 file.header.insert(i+1, '\\begin_document')
2070 def remove_begin_header(file):
2071 i = find_token(file.header, "\\begin_document", 0)
2074 i = find_token(file.header, "\\begin_header", 0)
2080 # \begin_file.body and \end_file.body
2082 def add_begin_body(file):
2083 file.body.insert(0, '\\begin_body')
2084 file.body.insert(1, '')
2085 i = find_token(file.body, "\\end_document", 0)
2086 file.body.insert(i, '\\end_body')
2088 def remove_begin_body(file):
2089 i = find_token(file.body, "\\begin_body", 0)
2092 if not file.body[i]:
2094 i = find_token(file.body, "\\end_body", 0)
2102 def normalize_papersize(file):
2103 i = find_token(file.header, '\\papersize', 0)
2107 tmp = split(file.header[i])
2108 if tmp[1] == "Default":
2109 file.header[i] = '\\papersize default'
2111 if tmp[1] == "Custom":
2112 file.header[i] = '\\papersize custom'
2115 def denormalize_papersize(file):
2116 i = find_token(file.header, '\\papersize', 0)
2120 tmp = split(file.header[i])
2121 if tmp[1] == "custom":
2122 file.header[i] = '\\papersize Custom'
2126 # Strip spaces at end of command line
2128 def strip_end_space(file):
2129 for i in range(len(file.body)):
2130 if file.body[i][:1] == '\\':
2131 file.body[i] = strip(file.body[i])
2135 # Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes
2137 def use_x_boolean(file):
2138 bin2bool = {'0': 'false', '1': 'true'}
2139 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2140 i = find_token(file.header, use, 0)
2143 decompose = split(file.header[i])
2144 file.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
2147 def use_x_binary(file):
2148 bool2bin = {'false': '0', 'true': '1'}
2149 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2150 i = find_token(file.header, use, 0)
2153 decompose = split(file.header[i])
2154 file.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
2157 # Place all the paragraph parameters in their own line
2159 def normalize_paragraph_params(file):
2161 allowed_parameters = '\\paragraph_spacing', '\\noindent', '\\align', '\\labelwidthstring', "\\start_of_appendix", "\\leftindent"
2165 i = find_token(file.body, '\\begin_layout', i)
2171 if strip(body[i]) and split(body[i])[0] not in allowed_parameters:
2174 j = find(body[i],'\\', 1)
2177 body[i:i+1] = [strip(body[i][:j]), body[i][j:]]
2183 # Add/remove output_changes parameter
2185 def convert_output_changes (file):
2186 i = find_token(file.header, '\\tracking_changes', 0)
2188 file.warning("Malformed lyx file: Missing '\\tracking_changes'.")
2190 file.header.insert(i+1, '\\output_changes true')
2193 def revert_output_changes (file):
2194 i = find_token(file.header, '\\output_changes', 0)
2201 # Convert paragraph breaks and sanitize paragraphs
2203 def convert_ert_paragraphs(file):
2204 forbidden_settings = [
2205 # paragraph parameters
2206 '\\paragraph_spacing', '\\labelwidthstring',
2207 '\\start_of_appendix', '\\noindent',
2208 '\\leftindent', '\\align',
2210 '\\family', '\\series', '\\shape', '\\size',
2211 '\\emph', '\\numeric', '\\bar', '\\noun',
2212 '\\color', '\\lang']
2215 i = find_token(file.body, '\\begin_inset ERT', i)
2218 j = find_end_of_inset(file.body, i)
2220 file.warning("Malformed lyx file: Missing '\\end_inset'.")
2224 # convert non-standard paragraphs to standard
2227 k = find_token(file.body, "\\begin_layout", k, j)
2230 file.body[k] = '\\begin_layout %s' % file.default_layout
2233 # remove all paragraph parameters and font settings
2236 if (strip(file.body[k]) and
2237 split(file.body[k])[0] in forbidden_settings):
2243 # insert an empty paragraph before each paragraph but the first
2247 k = find_token(file.body, "\\begin_layout", k, j)
2254 file.body[k:k] = ['\\begin_layout %s' % file.default_layout, "",
2259 # convert \\newline to new paragraph
2262 k = find_token(file.body, "\\newline", k, j)
2265 file.body[k:k+1] = ["\\end_layout", "", '\\begin_layout %s' % file.default_layout]
2268 # We need an empty line if file.default_layout == ''
2269 if file.body[k-1] != '':
2270 file.body.insert(k-1, '')
2277 # Remove double paragraph breaks
2279 def revert_ert_paragraphs(file):
2282 i = find_token(file.body, '\\begin_inset ERT', i)
2285 j = find_end_of_inset(file.body, i)
2287 file.warning("Malformed lyx file: Missing '\\end_inset'.")
2291 # replace paragraph breaks with \newline
2294 k = find_token(file.body, "\\end_layout", k, j)
2295 l = find_token(file.body, "\\begin_layout", k, j)
2296 if k == -1 or l == -1:
2298 file.body[k:l+1] = ["\\newline"]
2302 # replace double \newlines with paragraph breaks
2305 k = find_token(file.body, "\\newline", k, j)
2309 while file.body[l] == "":
2311 if strip(file.body[l]) and split(file.body[l])[0] == "\\newline":
2312 file.body[k:l+1] = ["\\end_layout", "",
2313 '\\begin_layout %s' % file.default_layout]
2316 # We need an empty line if file.default_layout == ''
2317 if file.body[l+1] != '':
2318 file.body.insert(l+1, '')
2326 def convert_french(file):
2327 regexp = re.compile(r'^\\language\s+frenchb')
2328 i = find_re(file.header, regexp, 0)
2330 file.header[i] = "\\language french"
2332 # Change language in the document body
2333 regexp = re.compile(r'^\\lang\s+frenchb')
2336 i = find_re(file.body, regexp, i)
2339 file.body[i] = "\\lang french"
2343 def remove_paperpackage(file):
2344 i = find_token(file.header, '\\paperpackage', 0)
2349 paperpackage = split(file.header[i])[1]
2353 if paperpackage not in ("a4", "a4wide", "widemarginsa4"):
2356 conv = {"a4":"\\usepackage{a4}","a4wide": "\\usepackage{a4wide}",
2357 "widemarginsa4": "\\usepackage[widemargins]{a4}"}
2358 # for compatibility we ensure it is the first entry in preamble
2359 file.preamble[0:0] = [conv[paperpackage]]
2361 i = find_token(file.header, '\\papersize', 0)
2363 file.header[i] = "\\papersize default"
2366 def remove_quotestimes(file):
2367 i = find_token(file.header, '\\quotes_times', 0)
2374 # Convert SGML paragraphs
2376 def convert_sgml_paragraphs(file):
2377 if file.backend != "docbook":
2382 i = find_token(file.body, "\\begin_layout SGML", i)
2387 file.body[i] = "\\begin_layout Standard"
2388 j = find_token(file.body, "\\end_layout", i)
2390 file.body[j+1:j+1] = ['','\\end_inset','','','\\end_layout']
2391 file.body[i+1:i+1] = ['\\begin_inset ERT','status inlined','','\\begin_layout Standard','']
2398 convert = [[222, [insert_tracking_changes, add_end_header, convert_amsmath]],
2399 [223, [remove_color_default, convert_spaces, convert_bibtex, remove_insetparent]],
2400 [224, [convert_external, convert_comment]],
2401 [225, [add_end_layout, layout2begin_layout, convert_end_document,
2402 convert_table_valignment_middle, convert_breaks]],
2403 [226, [convert_note]],
2404 [227, [convert_box]],
2405 [228, [convert_collapsable, convert_ert]],
2406 [229, [convert_minipage]],
2407 [230, [convert_jurabib]],
2408 [231, [convert_float]],
2409 [232, [convert_bibtopic]],
2410 [233, [convert_graphics, convert_names]],
2411 [234, [convert_cite_engine]],
2412 [235, [convert_paperpackage]],
2413 [236, [convert_bullets, add_begin_header, add_begin_body,
2414 normalize_papersize, strip_end_space]],
2415 [237, [use_x_boolean]],
2416 [238, [update_latexaccents]],
2417 [239, [normalize_paragraph_params]],
2418 [240, [convert_output_changes]],
2419 [241, [convert_ert_paragraphs]],
2420 [242, [convert_french]],
2421 [243, [remove_paperpackage]],
2422 [244, [rename_spaces]],
2423 [245, [remove_quotestimes, convert_sgml_paragraphs]]]
2425 revert = [[244, []],
2426 [243, [revert_space_names]],
2429 [240, [revert_ert_paragraphs]],
2430 [239, [revert_output_changes]],
2433 [236, [use_x_binary]],
2434 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
2436 [234, [revert_paperpackage]],
2437 [233, [revert_cite_engine]],
2438 [232, [revert_names]],
2439 [231, [revert_bibtopic]],
2440 [230, [revert_float]],
2441 [229, [revert_jurabib]],
2443 [227, [revert_collapsable, revert_ert]],
2444 [226, [revert_box, revert_external_2]],
2445 [225, [revert_note]],
2446 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
2447 revert_valignment_middle, revert_breaks, convert_frameless_box,
2449 [223, [revert_external_2, revert_comment, revert_eqref]],
2450 [222, [revert_spaces, revert_bibtex]],
2451 [221, [revert_amsmath, rm_end_header, rm_tracking_changes, rm_body_changes]]]
2454 if __name__ == "__main__":