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 file.body[i] = 'inner_pos "%s"' % inner_pos[int(file.body[i][15])]
992 file.body.insert('inner_pos "%s"' % 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 file.body.insert(i, 'use_parbox 0')
1023 file.body.insert(i, 'width' + width)
1025 file.body.insert(i, 'special "none"')
1027 file.body.insert(i, 'height' + height)
1029 file.body.insert(i, 'height_special "totalheight"')
1031 file.body.insert(i, 'status ' + status)
1035 # -------------------------------------------------------------------------------------------
1036 # Convert backslashes and '\n' into valid ERT code, append the converted
1037 # text to body[i] and return the (maybe incremented) line index i
1038 def convert_ertbackslash(body, i, ert, format, default_layout):
1041 body[i] = body[i] + '\\backslash '
1046 body[i+1:i+1] = ['\\newline ', '']
1049 body[i+1:i+1] = ['\\end_layout', '', '\\begin_layout %s' % default_layout, '']
1052 body[i] = body[i] + c
1056 # Converts lines in ERT code to LaTeX
1057 # The surrounding \begin_layout ... \end_layout pair must not be included
1058 def ert2latex(lines, format):
1059 backslash = re.compile(r'\\backslash\s*$')
1060 newline = re.compile(r'\\newline\s*$')
1062 begin_layout = re.compile(r'\\layout\s*\S+$')
1064 begin_layout = re.compile(r'\\begin_layout\s*\S+$')
1065 end_layout = re.compile(r'\\end_layout\s*$')
1067 for i in range(len(lines)):
1068 line = backslash.sub('\\\\', lines[i])
1070 if begin_layout.match(line):
1073 line = newline.sub('\n', line)
1075 if begin_layout.match(line):
1077 if format > 224 and end_layout.match(line):
1083 # get all paragraph parameters. They can be all on one line or on several lines.
1084 # lines[i] must be the first parameter line
1085 def get_par_params(lines, i):
1086 par_params = ('added_space_bottom', 'added_space_top', 'align',
1087 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
1088 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
1089 'start_of_appendix')
1090 # We cannot check for '\\' only because paragraphs may start e.g.
1091 # with '\\backslash'
1093 while lines[i][:1] == '\\' and split(lines[i][1:])[0] in par_params:
1094 params = params + ' ' + strip(lines[i])
1096 return strip(params)
1099 # convert LyX font size to LaTeX fontsize
1100 def lyxsize2latexsize(lyxsize):
1101 sizes = {"tiny" : "tiny", "scriptsize" : "scriptsize",
1102 "footnotesize" : "footnotesize", "small" : "small",
1103 "normal" : "normalsize", "large" : "large", "larger" : "Large",
1104 "largest" : "LARGE", "huge" : "huge", "giant" : "Huge"}
1105 if lyxsize in sizes:
1106 return '\\' + sizes[lyxsize]
1110 # Change vspace insets, page breaks and lyxlines to paragraph options
1111 # (if possible) or ERT
1112 def revert_breaks(file):
1114 # Get default spaceamount
1115 i = find_token(file.header, '\\defskip', 0)
1117 defskipamount = 'medskip'
1119 defskipamount = split(file.header[i])[1]
1121 keys = {"\\begin_inset" : "vspace", "\\lyxline" : "lyxline",
1122 "\\newpage" : "newpage"}
1123 keywords_top = {"vspace" : "\\added_space_top", "lyxline" : "\\line_top",
1124 "newpage" : "\\pagebreak_top"}
1125 keywords_bot = {"vspace" : "\\added_space_bottom", "lyxline" : "\\line_bottom",
1126 "newpage" : "\\pagebreak_bottom"}
1127 tokens = ["\\begin_inset VSpace", "\\lyxline", "\\newpage"]
1129 # Convert the insets
1132 i = find_tokens(file.body, tokens, i)
1136 # Are we at the beginning of a paragraph?
1138 this_par = get_paragraph(file.body, i, file.format - 1)
1139 start = this_par + 1
1140 params = get_par_params(file.body, start)
1142 # Paragraph parameters may be on one or more lines.
1143 # Find the start of the real paragraph text.
1144 while file.body[start][:1] == '\\' and split(file.body[start])[0] in params:
1146 for k in range(start, i):
1147 if find(file.body[k], "\\size") != -1:
1149 size = split(file.body[k])[1]
1150 elif is_nonempty_line(file.body[k]):
1153 # Find the end of the real paragraph text.
1154 next_par = get_next_paragraph(file.body, i, file.format - 1)
1156 file.warning("Malformed LyX file: Missing next paragraph.")
1160 # first line of our insets
1162 # last line of our insets
1163 inset_end = inset_start
1164 # Are we at the end of a paragraph?
1166 # start and end line numbers to delete if we convert this inset
1168 # is this inset a lyxline above a paragraph?
1170 # raw inset information
1172 # name of this inset
1174 # font size of this inset
1177 # Detect subsequent lyxline, vspace and pagebreak insets created by convert_breaks()
1181 if find_tokens(file.body, tokens, k) == k:
1183 lines.append(split(file.body[k]))
1184 insets.append(keys[lines[n][0]])
1185 del_lines.append([k, k])
1190 elif find(file.body[k], "\\size") != -1:
1192 size = split(file.body[k])[1]
1193 elif find_token(file.body, "\\begin_inset ERT", k) == k:
1194 ert_begin = find_token(file.body, "\\layout", k) + 1
1196 file.warning("Malformed LyX file: Missing '\\layout'.")
1198 ert_end = find_end_of_inset(file.body, k)
1200 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1202 ert = ert2latex(file.body[ert_begin:ert_end], file.format - 1)
1203 if (n > 0 and insets[n - 1] == "lyxline" and
1204 ert == '\\vspace{-1\\parskip}\n'):
1205 # vspace ERT created by convert_breaks() for top lyxline
1207 del_lines[n - 1][1] = ert_end
1213 elif (n > 0 and insets[n - 1] == "vspace" and
1214 find_token(file.body, "\\end_inset", k) == k):
1215 # ignore end of vspace inset
1216 del_lines[n - 1][1] = k
1218 elif is_nonempty_line(file.body[k]):
1223 # Determine space amount for vspace insets
1224 spaceamount = list()
1227 if insets[k] == "vspace":
1228 spaceamount.append(lines[k][2])
1229 arguments.append(' ' + spaceamount[k] + ' ')
1231 spaceamount.append('')
1232 arguments.append(' ')
1234 # Can we convert to top paragraph parameters?
1236 if ((n == 3 and insets[0] == "newpage" and insets[1] == "vspace" and
1237 insets[2] == "lyxline" and top[2]) or
1239 ((insets[0] == "newpage" and insets[1] == "vspace") or
1240 (insets[0] == "newpage" and insets[1] == "lyxline" and top[1]) or
1241 (insets[0] == "vspace" and insets[1] == "lyxline" and top[1]))) or
1242 (n == 1 and insets[0] == "lyxline" and top[0])):
1243 # These insets have been created before a paragraph by
1247 # Can we convert to bottom paragraph parameters?
1249 if ((n == 3 and insets[0] == "lyxline" and not top[0] and
1250 insets[1] == "vspace" and insets[2] == "newpage") or
1252 ((insets[0] == "lyxline" and not top[0] and insets[1] == "vspace") or
1253 (insets[0] == "lyxline" and not top[0] and insets[1] == "newpage") or
1254 (insets[0] == "vspace" and insets[1] == "newpage"))) or
1255 (n == 1 and insets[0] == "lyxline" and not top[0])):
1256 # These insets have been created after a paragraph by
1260 if paragraph_start and paragraph_end:
1261 # We are in a paragraph of our own.
1262 # We must not delete this paragraph if it has parameters
1264 # First try to merge with the previous paragraph.
1265 # We try the previous paragraph first because we would
1266 # otherwise need ERT for two subsequent vspaces.
1267 prev_par = get_paragraph(file.body, this_par - 1, file.format - 1) + 1
1268 if prev_par > 0 and not before:
1269 prev_params = get_par_params(file.body, prev_par + 1)
1271 # determine font size
1272 prev_size = "normal"
1274 while file.body[k][:1] == '\\' and split(file.body[k])[0] in prev_params:
1277 if find(file.body[k], "\\size") != -1:
1278 prev_size = split(file.body[k])[1]
1280 elif find(file.body[k], "\\begin_inset") != -1:
1282 k = find_end_of_inset(file.body, k)
1283 elif is_nonempty_line(file.body[k]):
1287 if (keywords_bot[insets[k]] in prev_params or
1288 (insets[k] == "lyxline" and sizes[k] != prev_size)):
1293 file.body.insert(prev_par + 1,
1294 keywords_bot[insets[k]] + arguments[k])
1295 del file.body[this_par+n:next_par-1+n]
1298 # Then try next paragraph
1299 if next_par > 0 and not after:
1300 next_params = get_par_params(file.body, next_par + 1)
1302 while file.body[k][:1] == '\\' and split(file.body[k])[0] in next_params:
1304 # determine font size
1305 next_size = "normal"
1308 if find(file.body[k], "\\size") != -1:
1309 next_size = split(file.body[k])[1]
1311 elif is_nonempty_line(file.body[k]):
1315 if (keywords_top[insets[k]] in next_params or
1316 (insets[k] == "lyxline" and sizes[k] != next_size)):
1321 file.body.insert(next_par + 1,
1322 keywords_top[insets[k]] + arguments[k])
1323 del file.body[this_par:next_par-1]
1326 elif paragraph_start or paragraph_end:
1327 # Convert to paragraph formatting if we are at the beginning or end
1328 # of a paragraph and the resulting paragraph would not be empty
1329 # The order is important: del and insert invalidate some indices
1331 keywords = keywords_top
1333 keywords = keywords_bot
1336 if keywords[insets[k]] in params:
1341 file.body.insert(this_par + 1,
1342 keywords[insets[k]] + arguments[k])
1343 for j in range(k, n):
1344 del_lines[j][0] = del_lines[j][0] + 1
1345 del_lines[j][1] = del_lines[j][1] + 1
1346 del file.body[del_lines[k][0]:del_lines[k][1]+1]
1347 deleted = del_lines[k][1] - del_lines[k][0] + 1
1348 for j in range(k + 1, n):
1349 del_lines[j][0] = del_lines[j][0] - deleted
1350 del_lines[j][1] = del_lines[j][1] - deleted
1354 # Convert the first inset to ERT.
1355 # The others are converted in the next loop runs (if they exist)
1356 if insets[0] == "vspace":
1357 file.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
1358 '\\layout %s' % file.default_layout, '', '\\backslash ']
1360 if spaceamount[0][-1] == '*':
1361 spaceamount[0] = spaceamount[0][:-1]
1366 # Replace defskip by the actual value
1367 if spaceamount[0] == 'defskip':
1368 spaceamount[0] = defskipamount
1370 # LaTeX does not know \\smallskip* etc
1372 if spaceamount[0] == 'smallskip':
1373 spaceamount[0] = '\\smallskipamount'
1374 elif spaceamount[0] == 'medskip':
1375 spaceamount[0] = '\\medskipamount'
1376 elif spaceamount[0] == 'bigskip':
1377 spaceamount[0] = '\\bigskipamount'
1378 elif spaceamount[0] == 'vfill':
1379 spaceamount[0] = '\\fill'
1381 # Finally output the LaTeX code
1382 if (spaceamount[0] == 'smallskip' or spaceamount[0] == 'medskip' or
1383 spaceamount[0] == 'bigskip' or spaceamount[0] == 'vfill'):
1384 file.body.insert(i, spaceamount[0] + '{}')
1387 file.body.insert(i, 'vspace*{')
1389 file.body.insert(i, 'vspace{')
1390 i = convert_ertbackslash(file.body, i, spaceamount[0], file.format - 1, file.default_layout)
1391 file.body[i] = file.body[i] + '}'
1393 elif insets[0] == "lyxline":
1395 latexsize = lyxsize2latexsize(size)
1397 file.warning("Could not convert LyX fontsize '%s' to LaTeX font size." % size)
1398 latexsize = '\\normalsize'
1399 i = insert_ert(file.body, i, 'Collapsed',
1400 '\\lyxline{%s}' % latexsize,
1401 file.format - 1, file.default_layout)
1402 # We use \providecommand so that we don't get an error if native
1403 # lyxlines are used (LyX writes first its own preamble and then
1404 # the user specified one)
1405 add_to_preamble(file,
1406 ['% Commands inserted by lyx2lyx for lyxlines',
1407 '\\providecommand{\\lyxline}[1]{',
1408 ' {#1 \\vspace{1ex} \\hrule width \\columnwidth \\vspace{1ex}}'
1410 elif insets[0] == "newpage":
1412 i = insert_ert(file.body, i, 'Collapsed', '\\newpage{}',
1413 file.format - 1, file.default_layout)
1416 # Convert a LyX length into a LaTeX length
1417 def convert_len(len, special):
1418 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
1419 "page%":"\\pagewidth", "line%":"\\linewidth",
1420 "theight%":"\\textheight", "pheight%":"\\pageheight"}
1422 # Convert special lengths
1423 if special != 'none':
1424 len = '%f\\' % len2value(len) + special
1426 # Convert LyX units to LaTeX units
1427 for unit in units.keys():
1428 if find(len, unit) != -1:
1429 len = '%f' % (len2value(len) / 100) + units[unit]
1435 # Convert a LyX length into valid ERT code and append it to body[i]
1436 # Return the (maybe incremented) line index i
1437 def convert_ertlen(body, i, len, special, format, default_layout):
1438 # Convert backslashes and insert the converted length into body
1439 return convert_ertbackslash(body, i, convert_len(len, special), format, default_layout)
1442 # Return the value of len without the unit in numerical form
1444 result = re.search('([+-]?[0-9.]+)', len)
1446 return float(result.group(1))
1447 # No number means 1.0
1451 # Convert text to ERT and insert it at body[i]
1452 # Return the index of the line after the inserted ERT
1453 def insert_ert(body, i, status, text, format, default_layout):
1454 body[i:i] = ['\\begin_inset ERT', 'status ' + status, '']
1457 body[i:i] = ['\\layout %s' % default_layout, '']
1459 body[i:i] = ['\\begin_layout %s' % default_layout, '']
1460 i = i + 1 # i points now to the just created empty line
1461 i = convert_ertbackslash(body, i, text, format, default_layout) + 1
1463 body[i:i] = ['\\end_layout']
1465 body[i:i] = ['', '\\end_inset', '']
1470 # Add text to the preamble if it is not already there.
1471 # Only the first line is checked!
1472 def add_to_preamble(file, text):
1473 if find_token(file.preamble, text[0], 0) != -1:
1476 file.preamble.extend(text)
1479 def convert_frameless_box(file):
1480 pos = ['t', 'c', 'b']
1481 inner_pos = ['c', 't', 'b', 's']
1484 i = find_token(file.body, '\\begin_inset Frameless', i)
1487 j = find_end_of_inset(file.body, i)
1489 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1496 params = {'position':0, 'hor_pos':'c', 'has_inner_box':'1',
1497 'inner_pos':1, 'use_parbox':'0', 'width':'100col%',
1498 'special':'none', 'height':'1in',
1499 'height_special':'totalheight', 'collapsed':'false'}
1500 for key in params.keys():
1501 value = replace(get_value(file.body, key, i, j), '"', '')
1503 if key == 'position':
1504 # convert new to old position: 'position "t"' -> 0
1505 value = find_token(pos, value, 0)
1508 elif key == 'inner_pos':
1509 # convert inner position
1510 value = find_token(inner_pos, value, 0)
1515 j = del_token(file.body, key, i, j)
1518 # Convert to minipage or ERT?
1519 # Note that the inner_position and height parameters of a minipage
1520 # inset are ignored and not accessible for the user, although they
1521 # are present in the file format and correctly read in and written.
1522 # Therefore we convert to ERT if they do not have their LaTeX
1523 # defaults. These are:
1524 # - the value of "position" for "inner_pos"
1525 # - "\totalheight" for "height"
1526 if (params['use_parbox'] != '0' or
1527 params['has_inner_box'] != '1' or
1528 params['special'] != 'none' or
1529 params['height_special'] != 'totalheight' or
1530 len2value(params['height']) != 1.0):
1532 # Here we know that this box is not supported in file format 224.
1533 # Therefore we need to convert it to ERT. We can't simply convert
1534 # the beginning and end of the box to ERT, because the
1535 # box inset may contain layouts that are different from the
1536 # surrounding layout. After the conversion the contents of the
1537 # box inset is on the same level as the surrounding text, and
1538 # paragraph layouts and align parameters can get mixed up.
1540 # A possible solution for this problem:
1541 # Convert the box to a minipage and redefine the minipage
1542 # environment in ERT so that the original box is simulated.
1543 # For minipages we could do this in a way that the width and
1544 # position can still be set from LyX, but this did not work well.
1545 # This is not possible for parboxes either, so we convert the
1546 # original box to ERT, put the minipage inset inside the box
1547 # and redefine the minipage environment to be empty.
1549 # Commands that are independant of a particular box can go to
1551 # We need to define lyxtolyxrealminipage with 3 optional
1552 # arguments although LyX 1.3 uses only the first one.
1553 # Otherwise we will get LaTeX errors if this document is
1554 # converted to format 225 or above again (LyX 1.4 uses all
1555 # optional arguments).
1556 add_to_preamble(file,
1557 ['% Commands inserted by lyx2lyx for frameless boxes',
1558 '% Save the original minipage environment',
1559 '\\let\\lyxtolyxrealminipage\\minipage',
1560 '\\let\\endlyxtolyxrealminipage\\endminipage',
1561 '% Define an empty lyxtolyximinipage environment',
1562 '% with 3 optional arguments',
1563 '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1564 '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1565 ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1566 ' {\\end{lyxtolyxiiiminipage}}',
1567 '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1568 ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1569 ' {\\end{lyxtolyxiiminipage}}',
1570 '\\newenvironment{lyxtolyxminipage}[1][c]%',
1571 ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1572 ' {\\end{lyxtolyximinipage}}'])
1574 if params['use_parbox'] != '0':
1577 ert = '\\begin{lyxtolyxrealminipage}'
1579 # convert optional arguments only if not latex default
1580 if (pos[params['position']] != 'c' or
1581 inner_pos[params['inner_pos']] != pos[params['position']] or
1582 params['height_special'] != 'totalheight' or
1583 len2value(params['height']) != 1.0):
1584 ert = ert + '[' + pos[params['position']] + ']'
1585 if (inner_pos[params['inner_pos']] != pos[params['position']] or
1586 params['height_special'] != 'totalheight' or
1587 len2value(params['height']) != 1.0):
1588 ert = ert + '[' + convert_len(params['height'],
1589 params['height_special']) + ']'
1590 if inner_pos[params['inner_pos']] != pos[params['position']]:
1591 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1593 ert = ert + '{' + convert_len(params['width'],
1594 params['special']) + '}'
1596 if params['use_parbox'] != '0':
1598 ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1599 ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1602 i = insert_ert(file.body, i, 'Collapsed', ert, file.format - 1, file.default_layout)
1603 j = j + i - old_i - 1
1605 file.body[i:i] = ['\\begin_inset Minipage',
1606 'position %d' % params['position'],
1609 'width "' + params['width'] + '"',
1610 'collapsed ' + params['collapsed']]
1614 # Restore the original minipage environment since we may have
1615 # minipages inside this box.
1616 # Start a new paragraph because the following may be nonstandard
1617 file.body[i:i] = ['\\layout %s' % file.default_layout, '', '']
1620 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1621 ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1623 i = insert_ert(file.body, i, 'Collapsed', ert, file.format - 1, file.default_layout)
1624 j = j + i - old_i - 1
1626 # Redefine the minipage end before the inset end.
1627 # Start a new paragraph because the previous may be nonstandard
1628 file.body[j:j] = ['\\layout %s' % file.default_layout, '', '']
1630 ert = '\\let\\endminipage\\endlyxtolyxminipage'
1631 j = insert_ert(file.body, j, 'Collapsed', ert, file.format - 1, file.default_layout)
1633 file.body.insert(j, '')
1636 # LyX writes '%\n' after each box. Therefore we need to end our
1637 # ERT with '%\n', too, since this may swallow a following space.
1638 if params['use_parbox'] != '0':
1641 ert = '\\end{lyxtolyxrealminipage}%\n'
1642 j = insert_ert(file.body, j, 'Collapsed', ert, file.format - 1, file.default_layout)
1644 # We don't need to restore the original minipage after the inset
1645 # end because the scope of the redefinition is the original box.
1649 # Convert to minipage
1650 file.body[i:i] = ['\\begin_inset Minipage',
1651 'position %d' % params['position'],
1652 'inner_position %d' % params['inner_pos'],
1653 'height "' + params['height'] + '"',
1654 'width "' + params['width'] + '"',
1655 'collapsed ' + params['collapsed']]
1659 def remove_branches(file):
1662 i = find_token(file.header, "\\branch", i)
1665 file.warning("Removing branch %s." % split(file.header[i])[1])
1666 j = find_token(file.header, "\\end_branch", i)
1668 file.warning("Malformed LyX file: Missing '\\end_branch'.")
1670 del file.header[i:j+1]
1674 i = find_token(file.body, "\\begin_inset Branch", i)
1677 j = find_end_of_inset(file.body, i)
1679 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1683 del file.body[j - 1]
1684 # Seach for a line starting 'collapsed'
1685 # If, however, we find a line starting '\layout'
1686 # (_always_ present) then break with a warning message
1689 if (file.body[i][:9] == "collapsed"):
1693 elif (file.body[i][:7] == "\\layout"):
1694 if collapsed_found == 0:
1695 file.warning("Malformed LyX file: Missing 'collapsed'.")
1696 # Delete this new paragraph, since it would not appear in
1697 # .tex output. This avoids also empty paragraphs.
1707 def convert_jurabib(file):
1708 i = find_token(file.header, '\\use_numerical_citations', 0)
1710 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1712 file.header.insert(i + 1, '\\use_jurabib 0')
1715 def revert_jurabib(file):
1716 i = find_token(file.header, '\\use_jurabib', 0)
1718 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1720 if get_value(file.header, '\\use_jurabib', 0) != "0":
1721 file.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1722 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1730 def convert_bibtopic(file):
1731 i = find_token(file.header, '\\use_jurabib', 0)
1733 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1735 file.header.insert(i + 1, '\\use_bibtopic 0')
1738 def revert_bibtopic(file):
1739 i = find_token(file.header, '\\use_bibtopic', 0)
1741 file.warning("Malformed lyx file: Missing '\\use_bibtopic'.")
1743 if get_value(file.header, '\\use_bibtopic', 0) != "0":
1744 file.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1745 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1752 def convert_float(file):
1755 i = find_token_exact(file.body, '\\begin_inset Float', i)
1758 # Seach for a line starting 'wide'
1759 # If, however, we find a line starting '\begin_layout'
1760 # (_always_ present) then break with a warning message
1763 if (file.body[i][:4] == "wide"):
1764 file.body.insert(i + 1, 'sideways false')
1766 elif (file.body[i][:13] == "\\begin_layout"):
1767 file.warning("Malformed lyx file: Missing 'wide'.")
1773 def revert_float(file):
1776 i = find_token_exact(file.body, '\\begin_inset Float', i)
1779 j = find_end_of_inset(file.body, i)
1781 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1784 if get_value(file.body, 'sideways', i, j) != "false":
1785 file.warning("Conversion of 'sideways true' not yet implemented.")
1786 # Don't remove 'sideways' so that people will get warnings by lyx
1789 del_token(file.body, 'sideways', i, j)
1793 def convert_graphics(file):
1794 """ Add extension to filenames of insetgraphics if necessary.
1798 i = find_token(file.body, "\\begin_inset Graphics", i)
1802 j = find_token_exact(file.body, "filename", i)
1806 filename = split(file.body[j])[1]
1807 absname = os.path.normpath(os.path.join(file.dir, filename))
1808 if file.input == stdin and not os.path.isabs(filename):
1809 # We don't know the directory and cannot check the file.
1810 # We could use a heuristic and take the current directory,
1811 # and we could try to find out if filename has an extension,
1812 # but that would be just guesses and could be wrong.
1813 file.warning("""Warning: Can not determine whether file
1815 needs an extension when reading from standard input.
1816 You may need to correct the file manually or run
1817 lyx2lyx again with the .lyx file as commandline argument.""" % filename)
1819 # This needs to be the same algorithm as in pre 233 insetgraphics
1820 if access(absname, F_OK):
1822 if access(absname + ".ps", F_OK):
1823 file.body[j] = replace(file.body[j], filename, filename + ".ps")
1825 if access(absname + ".eps", F_OK):
1826 file.body[j] = replace(file.body[j], filename, filename + ".eps")
1830 # Convert firstname and surname from styles -> char styles
1832 def convert_names(file):
1833 """ Convert in the docbook backend from firstname and surname style
1836 if file.backend != "docbook":
1842 i = find_token(file.body, "\\begin_layout Author", i)
1847 while file.body[i] == "":
1850 if file.body[i][:11] != "\\end_layout" or file.body[i+2][:13] != "\\begin_deeper":
1855 i = find_end_of( file.body, i+3, "\\begin_deeper","\\end_deeper")
1857 # something is really wrong, abort
1858 file.warning("Missing \\end_deeper, after style Author.")
1859 file.warning("Aborted attempt to parse FirstName and Surname.")
1861 firstname, surname = "", ""
1863 name = file.body[k:i]
1865 j = find_token(name, "\\begin_layout FirstName", 0)
1868 while(name[j] != "\\end_layout"):
1869 firstname = firstname + name[j]
1872 j = find_token(name, "\\begin_layout Surname", 0)
1875 while(name[j] != "\\end_layout"):
1876 surname = surname + name[j]
1880 del file.body[k+2:i+1]
1882 file.body[k-1:k-1] = ["", "",
1883 "\\begin_inset CharStyle Firstname",
1886 '\\begin_layout %s' % file.default_layout,
1894 "\\begin_inset CharStyle Surname",
1897 '\\begin_layout %s' % file.default_layout,
1906 def revert_names(file):
1907 """ Revert in the docbook backend from firstname and surname char style
1910 if file.backend != "docbook":
1915 # \use_natbib 1 \cite_engine <style>
1916 # \use_numerical_citations 0 -> where <style> is one of
1917 # \use_jurabib 0 "basic", "natbib_authoryear",
1918 # "natbib_numerical" or "jurabib"
1919 def convert_cite_engine(file):
1920 a = find_token(file.header, "\\use_natbib", 0)
1922 file.warning("Malformed lyx file: Missing '\\use_natbib'.")
1925 b = find_token(file.header, "\\use_numerical_citations", 0)
1926 if b == -1 or b != a+1:
1927 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1930 c = find_token(file.header, "\\use_jurabib", 0)
1931 if c == -1 or c != b+1:
1932 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1935 use_natbib = int(split(file.header[a])[1])
1936 use_numerical_citations = int(split(file.header[b])[1])
1937 use_jurabib = int(split(file.header[c])[1])
1939 cite_engine = "basic"
1941 if use_numerical_citations:
1942 cite_engine = "natbib_numerical"
1944 cite_engine = "natbib_authoryear"
1946 cite_engine = "jurabib"
1948 del file.header[a:c+1]
1949 file.header.insert(a, "\\cite_engine " + cite_engine)
1952 def revert_cite_engine(file):
1953 i = find_token(file.header, "\\cite_engine", 0)
1955 file.warning("Malformed lyx file: Missing '\\cite_engine'.")
1958 cite_engine = split(file.header[i])[1]
1963 if cite_engine == "natbib_numerical":
1966 elif cite_engine == "natbib_authoryear":
1968 elif cite_engine == "jurabib":
1972 file.header.insert(i, "\\use_jurabib " + use_jurabib)
1973 file.header.insert(i, "\\use_numerical_citations " + use_numerical)
1974 file.header.insert(i, "\\use_natbib " + use_natbib)
1980 def convert_paperpackage(file):
1981 i = find_token(file.header, "\\paperpackage", 0)
1985 packages = {'default':'none','a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
1986 if len(split(file.header[i])) > 1:
1987 paperpackage = split(file.header[i])[1]
1988 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1990 file.header[i] = file.header[i] + ' widemarginsa4'
1993 def revert_paperpackage(file):
1994 i = find_token(file.header, "\\paperpackage", 0)
1998 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
1999 'widemarginsa4':'', 'default': 'default'}
2000 if len(split(file.header[i])) > 1:
2001 paperpackage = split(file.header[i])[1]
2003 paperpackage = 'default'
2004 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
2010 def convert_bullets(file):
2013 i = find_token(file.header, "\\bullet", i)
2016 if file.header[i][:12] == '\\bulletLaTeX':
2017 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1])
2020 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1]) +\
2021 ' ' + strip(file.header[i+2]) + ' ' + strip(file.header[i+3])
2023 del file.header[i+1:i + n]
2027 def revert_bullets(file):
2030 i = find_token(file.header, "\\bullet", i)
2033 if file.header[i][:12] == '\\bulletLaTeX':
2034 n = find(file.header[i], '"')
2036 file.warning("Malformed header.")
2039 file.header[i:i+1] = [file.header[i][:n-1],'\t' + file.header[i][n:], '\\end_bullet']
2042 frag = split(file.header[i])
2044 file.warning("Malformed header.")
2047 file.header[i:i+1] = [frag[0] + ' ' + frag[1],
2056 # \begin_header and \begin_document
2058 def add_begin_header(file):
2059 i = find_token(file.header, '\\lyxformat', 0)
2060 file.header.insert(i+1, '\\begin_header')
2061 file.header.insert(i+1, '\\begin_document')
2064 def remove_begin_header(file):
2065 i = find_token(file.header, "\\begin_document", 0)
2068 i = find_token(file.header, "\\begin_header", 0)
2074 # \begin_file.body and \end_file.body
2076 def add_begin_body(file):
2077 file.body.insert(0, '\\begin_body')
2078 file.body.insert(1, '')
2079 i = find_token(file.body, "\\end_document", 0)
2080 file.body.insert(i, '\\end_body')
2082 def remove_begin_body(file):
2083 i = find_token(file.body, "\\begin_body", 0)
2086 if not file.body[i]:
2088 i = find_token(file.body, "\\end_body", 0)
2096 def normalize_papersize(file):
2097 i = find_token(file.header, '\\papersize', 0)
2101 tmp = split(file.header[i])
2102 if tmp[1] == "Default":
2103 file.header[i] = '\\papersize default'
2105 if tmp[1] == "Custom":
2106 file.header[i] = '\\papersize custom'
2109 def denormalize_papersize(file):
2110 i = find_token(file.header, '\\papersize', 0)
2114 tmp = split(file.header[i])
2115 if tmp[1] == "custom":
2116 file.header[i] = '\\papersize Custom'
2120 # Strip spaces at end of command line
2122 def strip_end_space(file):
2123 for i in range(len(file.body)):
2124 if file.body[i][:1] == '\\':
2125 file.body[i] = strip(file.body[i])
2129 # Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes
2131 def use_x_boolean(file):
2132 bin2bool = {'0': 'false', '1': 'true'}
2133 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2134 i = find_token(file.header, use, 0)
2137 decompose = split(file.header[i])
2138 file.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
2141 def use_x_binary(file):
2142 bool2bin = {'false': '0', 'true': '1'}
2143 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2144 i = find_token(file.header, use, 0)
2147 decompose = split(file.header[i])
2148 file.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
2151 # Place all the paragraph parameters in their own line
2153 def normalize_paragraph_params(file):
2155 allowed_parameters = '\\paragraph_spacing', '\\noindent', '\\align', '\\labelwidthstring', "\\start_of_appendix", "\\leftindent"
2159 i = find_token(file.body, '\\begin_layout', i)
2165 if strip(body[i]) and split(body[i])[0] not in allowed_parameters:
2168 j = find(body[i],'\\', 1)
2171 body[i:i+1] = [strip(body[i][:j]), body[i][j:]]
2177 # Add/remove output_changes parameter
2179 def convert_output_changes (file):
2180 i = find_token(file.header, '\\tracking_changes', 0)
2182 file.warning("Malformed lyx file: Missing '\\tracking_changes'.")
2184 file.header.insert(i+1, '\\output_changes true')
2187 def revert_output_changes (file):
2188 i = find_token(file.header, '\\output_changes', 0)
2195 # Convert paragraph breaks and sanitize paragraphs
2197 def convert_ert_paragraphs(file):
2198 forbidden_settings = [
2199 # paragraph parameters
2200 '\\paragraph_spacing', '\\labelwidthstring',
2201 '\\start_of_appendix', '\\noindent',
2202 '\\leftindent', '\\align',
2204 '\\family', '\\series', '\\shape', '\\size',
2205 '\\emph', '\\numeric', '\\bar', '\\noun',
2206 '\\color', '\\lang']
2209 i = find_token(file.body, '\\begin_inset ERT', i)
2212 j = find_end_of_inset(file.body, i)
2214 file.warning("Malformed lyx file: Missing '\\end_inset'.")
2218 # convert non-standard paragraphs to standard
2221 k = find_token(file.body, "\\begin_layout", k, j)
2224 file.body[k] = '\\begin_layout %s' % file.default_layout
2227 # remove all paragraph parameters and font settings
2230 if (strip(file.body[k]) and
2231 split(file.body[k])[0] in forbidden_settings):
2237 # insert an empty paragraph before each paragraph but the first
2241 k = find_token(file.body, "\\begin_layout", k, j)
2248 file.body[k:k] = ['\\begin_layout %s' % file.default_layout, "",
2253 # convert \\newline to new paragraph
2256 k = find_token(file.body, "\\newline", k, j)
2259 file.body[k:k+1] = ["\\end_layout", "", '\\begin_layout %s' % file.default_layout]
2262 # We need an empty line if file.default_layout == ''
2263 if file.body[k-1] != '':
2264 file.body.insert(k-1, '')
2271 # Remove double paragraph breaks
2273 def revert_ert_paragraphs(file):
2276 i = find_token(file.body, '\\begin_inset ERT', i)
2279 j = find_end_of_inset(file.body, i)
2281 file.warning("Malformed lyx file: Missing '\\end_inset'.")
2285 # replace paragraph breaks with \newline
2288 k = find_token(file.body, "\\end_layout", k, j)
2289 l = find_token(file.body, "\\begin_layout", k, j)
2290 if k == -1 or l == -1:
2292 file.body[k:l+1] = ["\\newline"]
2296 # replace double \newlines with paragraph breaks
2299 k = find_token(file.body, "\\newline", k, j)
2303 while file.body[l] == "":
2305 if strip(file.body[l]) and split(file.body[l])[0] == "\\newline":
2306 file.body[k:l+1] = ["\\end_layout", "",
2307 '\\begin_layout %s' % file.default_layout]
2310 # We need an empty line if file.default_layout == ''
2311 if file.body[l+1] != '':
2312 file.body.insert(l+1, '')
2320 def convert_french(file):
2321 regexp = re.compile(r'^\\language\s+frenchb')
2322 i = find_re(file.header, regexp, 0)
2324 file.header[i] = "\\language french"
2326 # Change language in the document body
2327 regexp = re.compile(r'^\\lang\s+frenchb')
2330 i = find_re(file.body, regexp, i)
2333 file.body[i] = "\\lang french"
2337 def remove_paperpackage(file):
2338 i = find_token(file.header, '\\paperpackage', 0)
2343 paperpackage = split(file.header[i])[1]
2347 if paperpackage not in ("a4", "a4wide", "widemarginsa4"):
2350 conv = {"a4":"\\usepackage{a4}","a4wide": "\\usepackage{a4wide}",
2351 "widemarginsa4": "\\usepackage[widemargins]{a4}"}
2352 # for compatibility we ensure it is the first entry in preamble
2353 file.preamble[0:0] = [conv[paperpackage]]
2355 i = find_token(file.header, '\\papersize', 0)
2357 file.header[i] = "\\papersize default"
2360 def remove_quotestimes(file):
2361 i = find_token(file.header, '\\quotes_times', 0)
2371 convert = [[222, [insert_tracking_changes, add_end_header, convert_amsmath]],
2372 [223, [remove_color_default, convert_spaces, convert_bibtex, remove_insetparent]],
2373 [224, [convert_external, convert_comment]],
2374 [225, [add_end_layout, layout2begin_layout, convert_end_document,
2375 convert_table_valignment_middle, convert_breaks]],
2376 [226, [convert_note]],
2377 [227, [convert_box]],
2378 [228, [convert_collapsable, convert_ert]],
2379 [229, [convert_minipage]],
2380 [230, [convert_jurabib]],
2381 [231, [convert_float]],
2382 [232, [convert_bibtopic]],
2383 [233, [convert_graphics, convert_names]],
2384 [234, [convert_cite_engine]],
2385 [235, [convert_paperpackage]],
2386 [236, [convert_bullets, add_begin_header, add_begin_body,
2387 normalize_papersize, strip_end_space]],
2388 [237, [use_x_boolean]],
2389 [238, [update_latexaccents]],
2390 [239, [normalize_paragraph_params]],
2391 [240, [convert_output_changes]],
2392 [241, [convert_ert_paragraphs]],
2393 [242, [convert_french]],
2394 [243, [remove_paperpackage]],
2395 [244, [rename_spaces]],
2396 [245, [remove_quotestimes]]]
2398 revert = [[244, []],
2399 [243, [revert_space_names]],
2402 [240, [revert_ert_paragraphs]],
2403 [239, [revert_output_changes]],
2406 [236, [use_x_binary]],
2407 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
2409 [234, [revert_paperpackage]],
2410 [233, [revert_cite_engine]],
2411 [232, [revert_names]],
2412 [231, [revert_bibtopic]],
2413 [230, [revert_float]],
2414 [229, [revert_jurabib]],
2416 [227, [revert_collapsable, revert_ert]],
2417 [226, [revert_box, revert_external_2]],
2418 [225, [revert_note]],
2419 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
2420 revert_valignment_middle, revert_breaks, convert_frameless_box,
2422 [223, [revert_external_2, revert_comment, revert_eqref]],
2423 [222, [revert_spaces, revert_bibtex]],
2424 [221, [revert_amsmath, rm_end_header, rm_tracking_changes, rm_body_changes]]]
2427 if __name__ == "__main__":