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 check_token, find_token, \
25 get_value, del_token, is_nonempty_line, \
26 find_tokens, find_end_of, find_beginning_of, find_token_exact, find_tokens_exact, \
27 find_re, find_tokens_backwards
29 from string import replace, split, find, strip, join
31 from lyx_0_12 import update_latexaccents
33 ####################################################################
34 # Private helper functions
36 def get_layout(line, default_layout):
43 def get_paragraph(lines, i, format):
44 "Finds the paragraph that contains line i."
47 begin_layout = "\\layout"
49 begin_layout = "\\begin_layout"
51 i = find_tokens_backwards(lines, ["\\end_inset", begin_layout], i)
53 if check_token(lines[i], begin_layout):
55 i = find_beginning_of_inset(lines, i)
59 def find_beginning_of_inset(lines, i):
60 return find_beginning_of(lines, i, "\\begin_inset", "\\end_inset")
63 def get_next_paragraph(lines, i, format):
64 "Finds the paragraph after the paragraph that contains line i."
67 tokens = ["\\begin_inset", "\\layout", "\\end_float", "\\the_end"]
69 tokens = ["\\begin_inset", "\\begin_layout", "\\end_float", "\\end_document"]
71 tokens = ["\\begin_inset", "\\begin_layout", "\\end_float", "\\end_body", "\\end_document"]
73 i = find_tokens(lines, tokens, i)
74 if not check_token(lines[i], "\\begin_inset"):
76 i = find_end_of_inset(lines, i)
80 def find_end_of_inset(lines, i):
81 "Finds the matching \end_inset"
82 return find_end_of(lines, i, "\\begin_inset", "\\end_inset")
84 # End of helper functions
85 ####################################################################
89 # Remove \color default
91 def remove_color_default(file):
94 i = find_token(file.body, "\\color default", i)
97 file.body[i] = replace(file.body[i], "\\color default",
104 def add_end_header(file):
105 file.header.append("\\end_header");
108 def rm_end_header(file):
109 i = find_token(file.header, "\\end_header", 0)
115 def convert_amsmath(file):
116 i = find_token(file.header, "\\use_amsmath", 0)
118 file.warning("Malformed LyX file: Missing '\\use_amsmath'.")
120 tokens = split(file.header[i])
122 file.warning("Malformed LyX file: Could not parse line '%s'." % file.header[i])
125 use_amsmath = tokens[1]
126 # old: 0 == off, 1 == on
127 # new: 0 == off, 1 == auto, 2 == on
128 # translate off -> auto, since old format 'off' means auto in reality
129 if use_amsmath == '0':
130 file.header[i] = "\\use_amsmath 1"
132 file.header[i] = "\\use_amsmath 2"
135 def revert_amsmath(file):
136 i = find_token(file.header, "\\use_amsmath", 0)
138 file.warning("Malformed LyX file: Missing '\\use_amsmath'.")
140 tokens = split(file.header[i])
142 file.warning("Malformed LyX file: Could not parse line '%s'." % file.header[i])
145 use_amsmath = tokens[1]
146 # old: 0 == off, 1 == on
147 # new: 0 == off, 1 == auto, 2 == on
148 # translate auto -> off, since old format 'off' means auto in reality
149 if use_amsmath == '2':
150 file.header[i] = "\\use_amsmath 1"
152 file.header[i] = "\\use_amsmath 0"
156 # \SpecialChar ~ -> \InsetSpace ~
158 def convert_spaces(file):
159 for i in range(len(file.body)):
160 file.body[i] = replace(file.body[i],"\\SpecialChar ~","\\InsetSpace ~")
163 def revert_spaces(file):
164 regexp = re.compile(r'(.*)(\\InsetSpace\s+)(\S+)')
167 i = find_re(file.body, regexp, i)
170 space = regexp.match(file.body[i]).group(3)
171 prepend = regexp.match(file.body[i]).group(1)
173 file.body[i] = regexp.sub(prepend + '\\SpecialChar ~', file.body[i])
176 file.body[i] = regexp.sub(prepend, file.body[i])
177 file.body[i+1:i+1] = ''
178 if space == "\\space":
180 i = insert_ert(file.body, i+1, 'Collapsed', space, file.format - 1, file.default_layout)
183 # \InsetSpace \, -> \InsetSpace \thinspace{}
184 # \InsetSpace \space -> \InsetSpace \space{}
186 def rename_spaces(file):
187 for i in range(len(file.body)):
188 file.body[i] = replace(file.body[i],"\\InsetSpace \\space","\\InsetSpace \\space{}")
189 file.body[i] = replace(file.body[i],"\\InsetSpace \,","\\InsetSpace \\thinspace{}")
192 def revert_space_names(file):
193 for i in range(len(file.body)):
194 file.body[i] = replace(file.body[i],"\\InsetSpace \\space{}","\\InsetSpace \\space")
195 file.body[i] = replace(file.body[i],"\\InsetSpace \\thinspace{}","\\InsetSpace \\,")
199 # equivalent to lyx::support::escape()
201 def lyx_support_escape(lab):
202 hexdigit = ['0', '1', '2', '3', '4', '5', '6', '7',
203 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
207 if o >= 128 or c == '=' or c == '%':
209 enc = enc + hexdigit[o >> 4]
210 enc = enc + hexdigit[o & 15]
217 # \begin_inset LatexCommand \eqref -> ERT
219 def revert_eqref(file):
220 regexp = re.compile(r'^\\begin_inset\s+LatexCommand\s+\\eqref')
223 i = find_re(file.body, regexp, i)
226 eqref = lyx_support_escape(regexp.sub("", file.body[i]))
227 file.body[i:i+1] = ["\\begin_inset ERT", "status Collapsed", "",
228 '\\layout %s' % file.default_layout, "", "\\backslash ",
236 def convert_bibtex(file):
237 for i in range(len(file.body)):
238 file.body[i] = replace(file.body[i],"\\begin_inset LatexCommand \\BibTeX",
239 "\\begin_inset LatexCommand \\bibtex")
242 def revert_bibtex(file):
243 for i in range(len(file.body)):
244 file.body[i] = replace(file.body[i], "\\begin_inset LatexCommand \\bibtex",
245 "\\begin_inset LatexCommand \\BibTeX")
251 def remove_insetparent(file):
254 i = find_token(file.body, "\\begin_inset LatexCommand \\lyxparent", i)
263 def convert_external(file):
264 external_rexp = re.compile(r'\\begin_inset External ([^,]*),"([^"]*)",')
265 external_header = "\\begin_inset External"
268 i = find_token(file.body, external_header, i)
271 look = external_rexp.search(file.body[i])
274 args[0] = look.group(1)
275 args[1] = look.group(2)
276 #FIXME: if the previous search fails then warn
278 if args[0] == "RasterImage":
279 # Convert a RasterImage External Inset to a Graphics Inset.
280 top = "\\begin_inset Graphics"
282 filename = "\tfilename " + args[1]
283 file.body[i:i+1] = [top, filename]
286 # Convert the old External Inset format to the new.
287 top = external_header
288 template = "\ttemplate " + args[0]
290 filename = "\tfilename " + args[1]
291 file.body[i:i+1] = [top, template, filename]
294 file.body[i:i+1] = [top, template]
298 def revert_external_1(file):
299 external_header = "\\begin_inset External"
302 i = find_token(file.body, external_header, i)
306 template = split(file.body[i+1])
310 filename = split(file.body[i+1])
314 params = split(file.body[i+1])
316 if file.body[i+1]: del file.body[i+1]
318 file.body[i] = file.body[i] + " " + template[0]+ ', "' + filename[0] + '", " '+ join(params[1:]) + '"'
322 def revert_external_2(file):
323 draft_token = '\tdraft'
326 i = find_token(file.body, '\\begin_inset External', i)
329 j = find_end_of_inset(file.body, i + 1)
331 #this should not happen
333 k = find_token(file.body, draft_token, i+1, j-1)
334 if (k != -1 and len(draft_token) == len(file.body[k])):
342 def convert_comment(file):
344 comment = "\\layout Comment"
346 i = find_token(file.body, comment, i)
350 file.body[i:i+1] = ['\\layout %s' % file.default_layout,"","",
351 "\\begin_inset Comment",
353 '\\layout %s' % file.default_layout]
358 i = find_token(file.body, "\\layout", i)
360 i = len(file.body) - 1
361 file.body[i:i] = ["\\end_inset","",""]
364 j = find_token(file.body, '\\begin_deeper', old_i, i)
365 if j == -1: j = i + 1
366 k = find_token(file.body, '\\begin_inset', old_i, i)
367 if k == -1: k = i + 1
372 i = find_end_of( file.body, i, "\\begin_deeper","\\end_deeper")
374 #This case should not happen
375 #but if this happens deal with it greacefully adding
376 #the missing \end_deeper.
377 i = len(file.body) - 1
378 file.body[i:i] = ["\end_deeper",""]
386 i = find_end_of( file.body, i, "\\begin_inset","\\end_inset")
388 #This case should not happen
389 #but if this happens deal with it greacefully adding
390 #the missing \end_inset.
391 i = len(file.body) - 1
392 file.body[i:i] = ["\\end_inset","","","\\end_inset","",""]
398 if find(file.body[i], comment) == -1:
399 file.body[i:i] = ["\\end_inset"]
402 file.body[i:i+1] = ['\\layout %s' % file.default_layout]
406 def revert_comment(file):
409 i = find_tokens(file.body, ["\\begin_inset Comment", "\\begin_inset Greyedout"], i)
413 file.body[i] = "\\begin_inset Note"
420 def add_end_layout(file):
421 i = find_token(file.body, '\\layout', 0)
427 struct_stack = ["\\layout"]
430 i = find_tokens(file.body, ["\\begin_inset", "\\end_inset", "\\layout",
431 "\\begin_deeper", "\\end_deeper", "\\the_end"], i)
434 token = split(file.body[i])[0]
436 file.warning("Truncated file.")
438 file.body.insert(i, '\\the_end')
441 if token == "\\begin_inset":
442 struct_stack.append(token)
446 if token == "\\end_inset":
447 tail = struct_stack.pop()
448 if tail == "\\layout":
449 file.body.insert(i,"")
450 file.body.insert(i,"\\end_layout")
452 #Check if it is the correct tag
457 if token == "\\layout":
458 tail = struct_stack.pop()
460 file.body.insert(i,"")
461 file.body.insert(i,"\\end_layout")
464 struct_stack.append(tail)
466 struct_stack.append(token)
469 if token == "\\begin_deeper":
470 file.body.insert(i,"")
471 file.body.insert(i,"\\end_layout")
473 struct_stack.append(token)
476 if token == "\\end_deeper":
477 if struct_stack[-1] == '\\layout':
478 file.body.insert(i, '\\end_layout')
485 file.body.insert(i, "")
486 file.body.insert(i, "\\end_layout")
490 def rm_end_layout(file):
493 i = find_token(file.body, '\\end_layout', i)
502 # Handle change tracking keywords
504 def insert_tracking_changes(file):
505 i = find_token(file.header, "\\tracking_changes", 0)
507 file.header.append("\\tracking_changes 0")
510 def rm_tracking_changes(file):
511 i = find_token(file.header, "\\author", 0)
515 i = find_token(file.header, "\\tracking_changes", 0)
521 def rm_body_changes(file):
524 i = find_token(file.body, "\\change_", i)
532 # \layout -> \begin_layout
534 def layout2begin_layout(file):
537 i = find_token(file.body, '\\layout', i)
541 file.body[i] = replace(file.body[i], '\\layout', '\\begin_layout')
545 def begin_layout2layout(file):
548 i = find_token(file.body, '\\begin_layout', i)
552 file.body[i] = replace(file.body[i], '\\begin_layout', '\\layout')
557 # valignment="center" -> valignment="middle"
559 def convert_valignment_middle(body, start, end):
560 for i in range(start, end):
561 if re.search('^<(column|cell) .*valignment="center".*>$', body[i]):
562 body[i] = replace(body[i], 'valignment="center"', 'valignment="middle"')
565 def convert_table_valignment_middle(file):
566 regexp = re.compile(r'^\\begin_inset\s+Tabular')
569 i = find_re(file.body, regexp, i)
572 j = find_end_of_inset(file.body, i + 1)
574 #this should not happen
575 convert_valignment_middle(file.body, i + 1, len(file.body))
577 convert_valignment_middle(file.body, i + 1, j)
581 def revert_table_valignment_middle(body, start, end):
582 for i in range(start, end):
583 if re.search('^<(column|cell) .*valignment="middle".*>$', body[i]):
584 body[i] = replace(body[i], 'valignment="middle"', 'valignment="center"')
587 def revert_valignment_middle(file):
588 regexp = re.compile(r'^\\begin_inset\s+Tabular')
591 i = find_re(file.body, regexp, i)
594 j = find_end_of_inset(file.body, i + 1)
596 #this should not happen
597 revert_table_valignment_middle(file.body, i + 1, len(file.body))
599 revert_table_valignment_middle(file.body, i + 1, j)
604 # \the_end -> \end_document
606 def convert_end_document(file):
607 i = find_token(file.body, "\\the_end", 0)
609 file.body.append("\\end_document")
611 file.body[i] = "\\end_document"
614 def revert_end_document(file):
615 i = find_token(file.body, "\\end_document", 0)
617 file.body.append("\\the_end")
619 file.body[i] = "\\the_end"
623 # Convert line and page breaks
626 #\line_top \line_bottom \pagebreak_top \pagebreak_bottom \added_space_top xxx \added_space_bottom yyy
630 #\begin layout Standard
636 #\begin layout Standard
643 #\begin_inset VSpace xxx
648 #\begin_inset VSpace xxx
655 def convert_breaks(file):
656 par_params = ('added_space_bottom', 'added_space_top', 'align',
657 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
658 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
660 font_attributes = ['\\family', '\\series', '\\shape', '\\emph',
661 '\\numeric', '\\bar', '\\noun', '\\color', '\\lang']
662 attribute_values = ['default', 'default', 'default', 'default',
663 'default', 'default', 'default', 'none', file.language]
666 i = find_token(file.body, "\\begin_layout", i)
669 layout = get_layout(file.body[i], file.default_layout)
672 # Merge all paragraph parameters into a single line
673 # We cannot check for '\\' only because paragraphs may start e.g.
675 while file.body[i + 1][:1] == '\\' and split(file.body[i + 1][1:])[0] in par_params:
676 file.body[i] = file.body[i + 1] + ' ' + file.body[i]
679 line_top = find(file.body[i],"\\line_top")
680 line_bot = find(file.body[i],"\\line_bottom")
681 pb_top = find(file.body[i],"\\pagebreak_top")
682 pb_bot = find(file.body[i],"\\pagebreak_bottom")
683 vspace_top = find(file.body[i],"\\added_space_top")
684 vspace_bot = find(file.body[i],"\\added_space_bottom")
686 if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1:
689 # Do we have a nonstandard paragraph? We need to create new paragraphs
690 # if yes to avoid putting lyxline etc. inside of special environments.
691 # This is wrong for itemize and enumerate environments, but it is
692 # impossible to convert these correctly.
693 # We want to avoid new paragraphs if possible becauase we want to
694 # inherit font sizes.
696 if (not file.is_default_layout(layout) or
697 find(file.body[i],"\\align") != -1 or
698 find(file.body[i],"\\labelwidthstring") != -1 or
699 find(file.body[i],"\\noindent") != -1):
702 # get the font size of the beginning of this paragraph, since we need
703 # it for the lyxline inset
705 while not is_nonempty_line(file.body[j]):
708 if find(file.body[j], "\\size") != -1:
709 size_top = split(file.body[j])[1]
711 for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom":
712 file.body[i] = replace(file.body[i], tag, "")
715 # the position could be change because of the removal of other
716 # paragraph properties above
717 vspace_top = find(file.body[i],"\\added_space_top")
718 tmp_list = split(file.body[i][vspace_top:])
719 vspace_top_value = tmp_list[1]
720 file.body[i] = file.body[i][:vspace_top] + join(tmp_list[2:])
723 # the position could be change because of the removal of other
724 # paragraph properties above
725 vspace_bot = find(file.body[i],"\\added_space_bottom")
726 tmp_list = split(file.body[i][vspace_bot:])
727 vspace_bot_value = tmp_list[1]
728 file.body[i] = file.body[i][:vspace_bot] + join(tmp_list[2:])
730 file.body[i] = strip(file.body[i])
733 # Create an empty paragraph or paragraph fragment for line and
734 # page break that belong above the paragraph
735 if pb_top !=-1 or line_top != -1 or vspace_top != -1:
737 paragraph_above = list()
739 # We need to create an extra paragraph for nonstandard environments
740 paragraph_above = ['\\begin_layout %s' % file.default_layout, '']
743 paragraph_above.extend(['\\newpage ',''])
746 paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','',''])
750 paragraph_above.extend(['\\size ' + size_top + ' '])
751 # We need an additional vertical space of -\parskip.
752 # We can't use the vspace inset because it does not know \parskip.
753 paragraph_above.extend(['\\lyxline ', '', ''])
754 insert_ert(paragraph_above, len(paragraph_above) - 1, 'Collapsed',
755 '\\vspace{-1\\parskip}\n', file.format + 1, file.default_layout)
756 paragraph_above.extend([''])
759 paragraph_above.extend(['\\end_layout ',''])
760 # insert new paragraph above the current paragraph
761 file.body[i-2:i-2] = paragraph_above
763 # insert new lines at the beginning of the current paragraph
764 file.body[i:i] = paragraph_above
766 i = i + len(paragraph_above)
768 # Ensure that nested style are converted later.
769 k = find_end_of(file.body, i, "\\begin_layout", "\\end_layout")
774 if pb_bot !=-1 or line_bot != -1 or vspace_bot != -1:
776 # get the font size of the end of this paragraph
780 if find(file.body[j], "\\size") != -1:
781 size_bot = split(file.body[j])[1]
783 elif find(file.body[j], "\\begin_inset") != -1:
785 j = find_end_of_inset(file.body, j)
789 paragraph_below = list()
791 # We need to create an extra paragraph for nonstandard environments
792 paragraph_below = ['', '\\begin_layout %s' % file.default_layout, '']
794 for a in range(len(font_attributes)):
795 if find_token(file.body, font_attributes[a], i, k) != -1:
796 paragraph_below.extend([font_attributes[a] + ' ' + attribute_values[a]])
799 if nonstandard and size_bot != '':
800 paragraph_below.extend(['\\size ' + size_bot + ' '])
801 paragraph_below.extend(['\\lyxline ',''])
803 paragraph_below.extend(['\\size default '])
806 paragraph_below.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','',''])
809 paragraph_below.extend(['\\newpage ',''])
812 paragraph_below.extend(['\\end_layout '])
813 # insert new paragraph below the current paragraph
814 file.body[k+1:k+1] = paragraph_below
816 # insert new lines at the end of the current paragraph
817 file.body[k:k] = paragraph_below
823 def convert_note(file):
826 i = find_tokens(file.body, ["\\begin_inset Note",
827 "\\begin_inset Comment",
828 "\\begin_inset Greyedout"], i)
832 file.body[i] = file.body[i][0:13] + 'Note ' + file.body[i][13:]
836 def revert_note(file):
837 note_header = "\\begin_inset Note "
840 i = find_token(file.body, note_header, i)
844 file.body[i] = "\\begin_inset " + file.body[i][len(note_header):]
851 def convert_box(file):
854 i = find_tokens(file.body, ["\\begin_inset Boxed",
855 "\\begin_inset Doublebox",
856 "\\begin_inset Frameless",
857 "\\begin_inset ovalbox",
858 "\\begin_inset Ovalbox",
859 "\\begin_inset Shadowbox"], i)
863 file.body[i] = file.body[i][0:13] + 'Box ' + file.body[i][13:]
867 def revert_box(file):
868 box_header = "\\begin_inset Box "
871 i = find_token(file.body, box_header, i)
875 file.body[i] = "\\begin_inset " + file.body[i][len(box_header):]
882 def convert_collapsable(file):
885 i = find_tokens_exact(file.body, ["\\begin_inset Box",
886 "\\begin_inset Branch",
887 "\\begin_inset CharStyle",
888 "\\begin_inset Float",
889 "\\begin_inset Foot",
890 "\\begin_inset Marginal",
891 "\\begin_inset Note",
892 "\\begin_inset OptArg",
893 "\\begin_inset Wrap"], i)
897 # Seach for a line starting 'collapsed'
898 # If, however, we find a line starting '\begin_layout'
899 # (_always_ present) then break with a warning message
902 if (file.body[i] == "collapsed false"):
903 file.body[i] = "status open"
905 elif (file.body[i] == "collapsed true"):
906 file.body[i] = "status collapsed"
908 elif (file.body[i][:13] == "\\begin_layout"):
909 file.warning("Malformed LyX file: Missing 'collapsed'.")
916 def revert_collapsable(file):
919 i = find_tokens_exact(file.body, ["\\begin_inset Box",
920 "\\begin_inset Branch",
921 "\\begin_inset CharStyle",
922 "\\begin_inset Float",
923 "\\begin_inset Foot",
924 "\\begin_inset Marginal",
925 "\\begin_inset Note",
926 "\\begin_inset OptArg",
927 "\\begin_inset Wrap"], i)
931 # Seach for a line starting 'status'
932 # If, however, we find a line starting '\begin_layout'
933 # (_always_ present) then break with a warning message
936 if (file.body[i] == "status open"):
937 file.body[i] = "collapsed false"
939 elif (file.body[i] == "status collapsed" or
940 file.body[i] == "status inlined"):
941 file.body[i] = "collapsed true"
943 elif (file.body[i][:13] == "\\begin_layout"):
944 file.warning("Malformed LyX file: Missing 'status'.")
954 def convert_ert(file):
957 i = find_token(file.body, "\\begin_inset ERT", i)
961 # Seach for a line starting 'status'
962 # If, however, we find a line starting '\begin_layout'
963 # (_always_ present) then break with a warning message
966 if (file.body[i] == "status Open"):
967 file.body[i] = "status open"
969 elif (file.body[i] == "status Collapsed"):
970 file.body[i] = "status collapsed"
972 elif (file.body[i] == "status Inlined"):
973 file.body[i] = "status inlined"
975 elif (file.body[i][:13] == "\\begin_layout"):
976 file.warning("Malformed LyX file: Missing 'status'.")
983 def revert_ert(file):
986 i = find_token(file.body, "\\begin_inset ERT", i)
990 # Seach for a line starting 'status'
991 # If, however, we find a line starting '\begin_layout'
992 # (_always_ present) then break with a warning message
995 if (file.body[i] == "status open"):
996 file.body[i] = "status Open"
998 elif (file.body[i] == "status collapsed"):
999 file.body[i] = "status Collapsed"
1001 elif (file.body[i] == "status inlined"):
1002 file.body[i] = "status Inlined"
1004 elif (file.body[i][:13] == "\\begin_layout"):
1005 file.warning("Malformed LyX file : Missing 'status'.")
1015 def convert_minipage(file):
1016 """ Convert minipages to the box inset.
1017 We try to use the same order of arguments as lyx does.
1020 inner_pos = ["c","t","b","s"]
1024 i = find_token(file.body, "\\begin_inset Minipage", i)
1028 file.body[i] = "\\begin_inset Box Frameless"
1031 # convert old to new position using the pos list
1032 if file.body[i][:8] == "position":
1033 file.body[i] = 'position "%s"' % pos[int(file.body[i][9])]
1035 file.body.insert(i, 'position "%s"' % pos[0])
1038 file.body.insert(i, 'hor_pos "c"')
1040 file.body.insert(i, 'has_inner_box 1')
1043 # convert the inner_position
1044 if file.body[i][:14] == "inner_position":
1045 innerpos = inner_pos[int(file.body[i][15])]
1048 innerpos = inner_pos[0]
1050 # We need this since the new file format has a height and width
1051 # in a different order.
1052 if file.body[i][:6] == "height":
1053 height = file.body[i][6:]
1054 # test for default value of 221 and convert it accordingly
1055 if height == ' "0pt"' or height == ' "0"':
1061 if file.body[i][:5] == "width":
1062 width = file.body[i][5:]
1067 if file.body[i][:9] == "collapsed":
1068 if file.body[i][9:] == "true":
1069 status = "collapsed"
1074 status = "collapsed"
1076 # Handle special default case:
1077 if height == ' "1pt"' and innerpos == 'c':
1080 file.body.insert(i, 'inner_pos "' + innerpos + '"')
1082 file.body.insert(i, 'use_parbox 0')
1084 file.body.insert(i, 'width' + width)
1086 file.body.insert(i, 'special "none"')
1088 file.body.insert(i, 'height' + height)
1090 file.body.insert(i, 'height_special "totalheight"')
1092 file.body.insert(i, 'status ' + status)
1096 # -------------------------------------------------------------------------------------------
1097 # Convert backslashes and '\n' into valid ERT code, append the converted
1098 # text to body[i] and return the (maybe incremented) line index i
1099 def convert_ertbackslash(body, i, ert, format, default_layout):
1102 body[i] = body[i] + '\\backslash '
1107 body[i+1:i+1] = ['\\newline ', '']
1110 body[i+1:i+1] = ['\\end_layout', '', '\\begin_layout %s' % default_layout, '']
1113 body[i] = body[i] + c
1117 # Converts lines in ERT code to LaTeX
1118 # The surrounding \begin_layout ... \end_layout pair must not be included
1119 def ert2latex(lines, format):
1120 backslash = re.compile(r'\\backslash\s*$')
1121 newline = re.compile(r'\\newline\s*$')
1123 begin_layout = re.compile(r'\\layout\s*\S+$')
1125 begin_layout = re.compile(r'\\begin_layout\s*\S+$')
1126 end_layout = re.compile(r'\\end_layout\s*$')
1128 for i in range(len(lines)):
1129 line = backslash.sub('\\\\', lines[i])
1131 if begin_layout.match(line):
1134 line = newline.sub('\n', line)
1136 if begin_layout.match(line):
1138 if format > 224 and end_layout.match(line):
1144 # get all paragraph parameters. They can be all on one line or on several lines.
1145 # lines[i] must be the first parameter line
1146 def get_par_params(lines, i):
1147 par_params = ('added_space_bottom', 'added_space_top', 'align',
1148 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
1149 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
1150 'start_of_appendix')
1151 # We cannot check for '\\' only because paragraphs may start e.g.
1152 # with '\\backslash'
1154 while lines[i][:1] == '\\' and split(lines[i][1:])[0] in par_params:
1155 params = params + ' ' + strip(lines[i])
1157 return strip(params)
1160 # convert LyX font size to LaTeX fontsize
1161 def lyxsize2latexsize(lyxsize):
1162 sizes = {"tiny" : "tiny", "scriptsize" : "scriptsize",
1163 "footnotesize" : "footnotesize", "small" : "small",
1164 "normal" : "normalsize", "large" : "large", "larger" : "Large",
1165 "largest" : "LARGE", "huge" : "huge", "giant" : "Huge"}
1166 if lyxsize in sizes:
1167 return '\\' + sizes[lyxsize]
1171 # Change vspace insets, page breaks and lyxlines to paragraph options
1172 # (if possible) or ERT
1173 def revert_breaks(file):
1175 # Get default spaceamount
1176 i = find_token(file.header, '\\defskip', 0)
1178 defskipamount = 'medskip'
1180 defskipamount = split(file.header[i])[1]
1182 keys = {"\\begin_inset" : "vspace", "\\lyxline" : "lyxline",
1183 "\\newpage" : "newpage"}
1184 keywords_top = {"vspace" : "\\added_space_top", "lyxline" : "\\line_top",
1185 "newpage" : "\\pagebreak_top"}
1186 keywords_bot = {"vspace" : "\\added_space_bottom", "lyxline" : "\\line_bottom",
1187 "newpage" : "\\pagebreak_bottom"}
1188 tokens = ["\\begin_inset VSpace", "\\lyxline", "\\newpage"]
1190 # Convert the insets
1193 i = find_tokens(file.body, tokens, i)
1197 # Are we at the beginning of a paragraph?
1199 this_par = get_paragraph(file.body, i, file.format - 1)
1200 start = this_par + 1
1201 params = get_par_params(file.body, start)
1203 # Paragraph parameters may be on one or more lines.
1204 # Find the start of the real paragraph text.
1205 while file.body[start][:1] == '\\' and split(file.body[start])[0] in params:
1207 for k in range(start, i):
1208 if find(file.body[k], "\\size") != -1:
1210 size = split(file.body[k])[1]
1211 elif is_nonempty_line(file.body[k]):
1214 # Find the end of the real paragraph text.
1215 next_par = get_next_paragraph(file.body, i, file.format - 1)
1217 file.warning("Malformed LyX file: Missing next paragraph.")
1221 # first line of our insets
1223 # last line of our insets
1224 inset_end = inset_start
1225 # Are we at the end of a paragraph?
1227 # start and end line numbers to delete if we convert this inset
1229 # is this inset a lyxline above a paragraph?
1231 # raw inset information
1233 # name of this inset
1235 # font size of this inset
1238 # Detect subsequent lyxline, vspace and pagebreak insets created by convert_breaks()
1242 if find_tokens(file.body, tokens, k) == k:
1244 lines.append(split(file.body[k]))
1245 insets.append(keys[lines[n][0]])
1246 del_lines.append([k, k])
1251 elif find(file.body[k], "\\size") != -1:
1253 size = split(file.body[k])[1]
1254 elif find_token(file.body, "\\begin_inset ERT", k) == k:
1255 ert_begin = find_token(file.body, "\\layout", k) + 1
1257 file.warning("Malformed LyX file: Missing '\\layout'.")
1259 ert_end = find_end_of_inset(file.body, k)
1261 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1263 ert = ert2latex(file.body[ert_begin:ert_end], file.format - 1)
1264 if (n > 0 and insets[n - 1] == "lyxline" and
1265 ert == '\\vspace{-1\\parskip}\n'):
1266 # vspace ERT created by convert_breaks() for top lyxline
1268 del_lines[n - 1][1] = ert_end
1274 elif (n > 0 and insets[n - 1] == "vspace" and
1275 find_token(file.body, "\\end_inset", k) == k):
1276 # ignore end of vspace inset
1277 del_lines[n - 1][1] = k
1279 elif is_nonempty_line(file.body[k]):
1284 # Determine space amount for vspace insets
1285 spaceamount = list()
1288 if insets[k] == "vspace":
1289 spaceamount.append(lines[k][2])
1290 arguments.append(' ' + spaceamount[k] + ' ')
1292 spaceamount.append('')
1293 arguments.append(' ')
1295 # Can we convert to top paragraph parameters?
1297 if ((n == 3 and insets[0] == "newpage" and insets[1] == "vspace" and
1298 insets[2] == "lyxline" and top[2]) or
1300 ((insets[0] == "newpage" and insets[1] == "vspace") or
1301 (insets[0] == "newpage" and insets[1] == "lyxline" and top[1]) or
1302 (insets[0] == "vspace" and insets[1] == "lyxline" and top[1]))) or
1303 (n == 1 and insets[0] == "lyxline" and top[0])):
1304 # These insets have been created before a paragraph by
1308 # Can we convert to bottom paragraph parameters?
1310 if ((n == 3 and insets[0] == "lyxline" and not top[0] and
1311 insets[1] == "vspace" and insets[2] == "newpage") or
1313 ((insets[0] == "lyxline" and not top[0] and insets[1] == "vspace") or
1314 (insets[0] == "lyxline" and not top[0] and insets[1] == "newpage") or
1315 (insets[0] == "vspace" and insets[1] == "newpage"))) or
1316 (n == 1 and insets[0] == "lyxline" and not top[0])):
1317 # These insets have been created after a paragraph by
1321 if paragraph_start and paragraph_end:
1322 # We are in a paragraph of our own.
1323 # We must not delete this paragraph if it has parameters
1325 # First try to merge with the previous paragraph.
1326 # We try the previous paragraph first because we would
1327 # otherwise need ERT for two subsequent vspaces.
1328 prev_par = get_paragraph(file.body, this_par - 1, file.format - 1) + 1
1329 if prev_par > 0 and not before:
1330 prev_params = get_par_params(file.body, prev_par + 1)
1332 # determine font size
1333 prev_size = "normal"
1335 while file.body[k][:1] == '\\' and split(file.body[k])[0] in prev_params:
1338 if find(file.body[k], "\\size") != -1:
1339 prev_size = split(file.body[k])[1]
1341 elif find(file.body[k], "\\begin_inset") != -1:
1343 k = find_end_of_inset(file.body, k)
1344 elif is_nonempty_line(file.body[k]):
1348 if (keywords_bot[insets[k]] in prev_params or
1349 (insets[k] == "lyxline" and sizes[k] != prev_size)):
1354 file.body.insert(prev_par + 1,
1355 keywords_bot[insets[k]] + arguments[k])
1356 del file.body[this_par+n:next_par-1+n]
1359 # Then try next paragraph
1360 if next_par > 0 and not after:
1361 next_params = get_par_params(file.body, next_par + 1)
1363 while file.body[k][:1] == '\\' and split(file.body[k])[0] in next_params:
1365 # determine font size
1366 next_size = "normal"
1369 if find(file.body[k], "\\size") != -1:
1370 next_size = split(file.body[k])[1]
1372 elif is_nonempty_line(file.body[k]):
1376 if (keywords_top[insets[k]] in next_params or
1377 (insets[k] == "lyxline" and sizes[k] != next_size)):
1382 file.body.insert(next_par + 1,
1383 keywords_top[insets[k]] + arguments[k])
1384 del file.body[this_par:next_par-1]
1387 elif paragraph_start or paragraph_end:
1388 # Convert to paragraph formatting if we are at the beginning or end
1389 # of a paragraph and the resulting paragraph would not be empty
1390 # The order is important: del and insert invalidate some indices
1392 keywords = keywords_top
1394 keywords = keywords_bot
1397 if keywords[insets[k]] in params:
1402 file.body.insert(this_par + 1,
1403 keywords[insets[k]] + arguments[k])
1404 for j in range(k, n):
1405 del_lines[j][0] = del_lines[j][0] + 1
1406 del_lines[j][1] = del_lines[j][1] + 1
1407 del file.body[del_lines[k][0]:del_lines[k][1]+1]
1408 deleted = del_lines[k][1] - del_lines[k][0] + 1
1409 for j in range(k + 1, n):
1410 del_lines[j][0] = del_lines[j][0] - deleted
1411 del_lines[j][1] = del_lines[j][1] - deleted
1415 # Convert the first inset to ERT.
1416 # The others are converted in the next loop runs (if they exist)
1417 if insets[0] == "vspace":
1418 file.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
1419 '\\layout %s' % file.default_layout, '', '\\backslash ']
1421 if spaceamount[0][-1] == '*':
1422 spaceamount[0] = spaceamount[0][:-1]
1427 # Replace defskip by the actual value
1428 if spaceamount[0] == 'defskip':
1429 spaceamount[0] = defskipamount
1431 # LaTeX does not know \\smallskip* etc
1433 if spaceamount[0] == 'smallskip':
1434 spaceamount[0] = '\\smallskipamount'
1435 elif spaceamount[0] == 'medskip':
1436 spaceamount[0] = '\\medskipamount'
1437 elif spaceamount[0] == 'bigskip':
1438 spaceamount[0] = '\\bigskipamount'
1439 elif spaceamount[0] == 'vfill':
1440 spaceamount[0] = '\\fill'
1442 # Finally output the LaTeX code
1443 if (spaceamount[0] == 'smallskip' or spaceamount[0] == 'medskip' or
1444 spaceamount[0] == 'bigskip' or spaceamount[0] == 'vfill'):
1445 file.body.insert(i, spaceamount[0] + '{}')
1448 file.body.insert(i, 'vspace*{')
1450 file.body.insert(i, 'vspace{')
1451 i = convert_ertbackslash(file.body, i, spaceamount[0], file.format - 1, file.default_layout)
1452 file.body[i] = file.body[i] + '}'
1454 elif insets[0] == "lyxline":
1456 latexsize = lyxsize2latexsize(size)
1458 file.warning("Could not convert LyX fontsize '%s' to LaTeX font size." % size)
1459 latexsize = '\\normalsize'
1460 i = insert_ert(file.body, i, 'Collapsed',
1461 '\\lyxline{%s}' % latexsize,
1462 file.format - 1, file.default_layout)
1463 # We use \providecommand so that we don't get an error if native
1464 # lyxlines are used (LyX writes first its own preamble and then
1465 # the user specified one)
1466 add_to_preamble(file,
1467 ['% Commands inserted by lyx2lyx for lyxlines',
1468 '\\providecommand{\\lyxline}[1]{',
1469 ' {#1 \\vspace{1ex} \\hrule width \\columnwidth \\vspace{1ex}}'
1471 elif insets[0] == "newpage":
1473 i = insert_ert(file.body, i, 'Collapsed', '\\newpage{}',
1474 file.format - 1, file.default_layout)
1477 # Convert a LyX length into a LaTeX length
1478 def convert_len(len, special):
1479 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
1480 "page%":"\\pagewidth", "line%":"\\linewidth",
1481 "theight%":"\\textheight", "pheight%":"\\pageheight"}
1483 # Convert special lengths
1484 if special != 'none':
1485 len = '%f\\' % len2value(len) + special
1487 # Convert LyX units to LaTeX units
1488 for unit in units.keys():
1489 if find(len, unit) != -1:
1490 len = '%f' % (len2value(len) / 100) + units[unit]
1496 # Convert a LyX length into valid ERT code and append it to body[i]
1497 # Return the (maybe incremented) line index i
1498 def convert_ertlen(body, i, len, special, format, default_layout):
1499 # Convert backslashes and insert the converted length into body
1500 return convert_ertbackslash(body, i, convert_len(len, special), format, default_layout)
1503 # Return the value of len without the unit in numerical form
1505 result = re.search('([+-]?[0-9.]+)', len)
1507 return float(result.group(1))
1508 # No number means 1.0
1512 # Convert text to ERT and insert it at body[i]
1513 # Return the index of the line after the inserted ERT
1514 def insert_ert(body, i, status, text, format, default_layout):
1515 body[i:i] = ['\\begin_inset ERT', 'status ' + status, '']
1518 body[i:i] = ['\\layout %s' % default_layout, '']
1520 body[i:i] = ['\\begin_layout %s' % default_layout, '']
1521 i = i + 1 # i points now to the just created empty line
1522 i = convert_ertbackslash(body, i, text, format, default_layout) + 1
1524 body[i:i] = ['\\end_layout']
1526 body[i:i] = ['', '\\end_inset', '']
1531 # Add text to the preamble if it is not already there.
1532 # Only the first line is checked!
1533 def add_to_preamble(file, text):
1534 if find_token(file.preamble, text[0], 0) != -1:
1537 file.preamble.extend(text)
1540 def convert_frameless_box(file):
1541 pos = ['t', 'c', 'b']
1542 inner_pos = ['c', 't', 'b', 's']
1545 i = find_token(file.body, '\\begin_inset Frameless', i)
1548 j = find_end_of_inset(file.body, i)
1550 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1557 params = {'position':0, 'hor_pos':'c', 'has_inner_box':'1',
1558 'inner_pos':1, 'use_parbox':'0', 'width':'100col%',
1559 'special':'none', 'height':'1in',
1560 'height_special':'totalheight', 'collapsed':'false'}
1561 for key in params.keys():
1562 value = replace(get_value(file.body, key, i, j), '"', '')
1564 if key == 'position':
1565 # convert new to old position: 'position "t"' -> 0
1566 value = find_token(pos, value, 0)
1569 elif key == 'inner_pos':
1570 # convert inner position
1571 value = find_token(inner_pos, value, 0)
1576 j = del_token(file.body, key, i, j)
1579 # Convert to minipage or ERT?
1580 # Note that the inner_position and height parameters of a minipage
1581 # inset are ignored and not accessible for the user, although they
1582 # are present in the file format and correctly read in and written.
1583 # Therefore we convert to ERT if they do not have their LaTeX
1584 # defaults. These are:
1585 # - the value of "position" for "inner_pos"
1586 # - "\totalheight" for "height"
1587 if (params['use_parbox'] != '0' or
1588 params['has_inner_box'] != '1' or
1589 params['special'] != 'none' or
1590 params['height_special'] != 'totalheight' or
1591 len2value(params['height']) != 1.0):
1593 # Here we know that this box is not supported in file format 224.
1594 # Therefore we need to convert it to ERT. We can't simply convert
1595 # the beginning and end of the box to ERT, because the
1596 # box inset may contain layouts that are different from the
1597 # surrounding layout. After the conversion the contents of the
1598 # box inset is on the same level as the surrounding text, and
1599 # paragraph layouts and align parameters can get mixed up.
1601 # A possible solution for this problem:
1602 # Convert the box to a minipage and redefine the minipage
1603 # environment in ERT so that the original box is simulated.
1604 # For minipages we could do this in a way that the width and
1605 # position can still be set from LyX, but this did not work well.
1606 # This is not possible for parboxes either, so we convert the
1607 # original box to ERT, put the minipage inset inside the box
1608 # and redefine the minipage environment to be empty.
1610 # Commands that are independant of a particular box can go to
1612 # We need to define lyxtolyxrealminipage with 3 optional
1613 # arguments although LyX 1.3 uses only the first one.
1614 # Otherwise we will get LaTeX errors if this document is
1615 # converted to format 225 or above again (LyX 1.4 uses all
1616 # optional arguments).
1617 add_to_preamble(file,
1618 ['% Commands inserted by lyx2lyx for frameless boxes',
1619 '% Save the original minipage environment',
1620 '\\let\\lyxtolyxrealminipage\\minipage',
1621 '\\let\\endlyxtolyxrealminipage\\endminipage',
1622 '% Define an empty lyxtolyximinipage environment',
1623 '% with 3 optional arguments',
1624 '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1625 '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1626 ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1627 ' {\\end{lyxtolyxiiiminipage}}',
1628 '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1629 ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1630 ' {\\end{lyxtolyxiiminipage}}',
1631 '\\newenvironment{lyxtolyxminipage}[1][c]%',
1632 ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1633 ' {\\end{lyxtolyximinipage}}'])
1635 if params['use_parbox'] != '0':
1638 ert = '\\begin{lyxtolyxrealminipage}'
1640 # convert optional arguments only if not latex default
1641 if (pos[params['position']] != 'c' or
1642 inner_pos[params['inner_pos']] != pos[params['position']] or
1643 params['height_special'] != 'totalheight' or
1644 len2value(params['height']) != 1.0):
1645 ert = ert + '[' + pos[params['position']] + ']'
1646 if (inner_pos[params['inner_pos']] != pos[params['position']] or
1647 params['height_special'] != 'totalheight' or
1648 len2value(params['height']) != 1.0):
1649 ert = ert + '[' + convert_len(params['height'],
1650 params['height_special']) + ']'
1651 if inner_pos[params['inner_pos']] != pos[params['position']]:
1652 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1654 ert = ert + '{' + convert_len(params['width'],
1655 params['special']) + '}'
1657 if params['use_parbox'] != '0':
1659 ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1660 ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1663 i = insert_ert(file.body, i, 'Collapsed', ert, file.format - 1, file.default_layout)
1664 j = j + i - old_i - 1
1666 file.body[i:i] = ['\\begin_inset Minipage',
1667 'position %d' % params['position'],
1670 'width "' + params['width'] + '"',
1671 'collapsed ' + params['collapsed']]
1675 # Restore the original minipage environment since we may have
1676 # minipages inside this box.
1677 # Start a new paragraph because the following may be nonstandard
1678 file.body[i:i] = ['\\layout %s' % file.default_layout, '', '']
1681 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1682 ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1684 i = insert_ert(file.body, i, 'Collapsed', ert, file.format - 1, file.default_layout)
1685 j = j + i - old_i - 1
1687 # Redefine the minipage end before the inset end.
1688 # Start a new paragraph because the previous may be nonstandard
1689 file.body[j:j] = ['\\layout %s' % file.default_layout, '', '']
1691 ert = '\\let\\endminipage\\endlyxtolyxminipage'
1692 j = insert_ert(file.body, j, 'Collapsed', ert, file.format - 1, file.default_layout)
1694 file.body.insert(j, '')
1697 # LyX writes '%\n' after each box. Therefore we need to end our
1698 # ERT with '%\n', too, since this may swallow a following space.
1699 if params['use_parbox'] != '0':
1702 ert = '\\end{lyxtolyxrealminipage}%\n'
1703 j = insert_ert(file.body, j, 'Collapsed', ert, file.format - 1, file.default_layout)
1705 # We don't need to restore the original minipage after the inset
1706 # end because the scope of the redefinition is the original box.
1710 # Convert to minipage
1711 file.body[i:i] = ['\\begin_inset Minipage',
1712 'position %d' % params['position'],
1713 'inner_position %d' % params['inner_pos'],
1714 'height "' + params['height'] + '"',
1715 'width "' + params['width'] + '"',
1716 'collapsed ' + params['collapsed']]
1720 def remove_branches(file):
1723 i = find_token(file.header, "\\branch", i)
1726 file.warning("Removing branch %s." % split(file.header[i])[1])
1727 j = find_token(file.header, "\\end_branch", i)
1729 file.warning("Malformed LyX file: Missing '\\end_branch'.")
1731 del file.header[i:j+1]
1735 i = find_token(file.body, "\\begin_inset Branch", i)
1738 j = find_end_of_inset(file.body, i)
1740 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1744 del file.body[j - 1]
1745 # Seach for a line starting 'collapsed'
1746 # If, however, we find a line starting '\layout'
1747 # (_always_ present) then break with a warning message
1750 if (file.body[i][:9] == "collapsed"):
1754 elif (file.body[i][:7] == "\\layout"):
1755 if collapsed_found == 0:
1756 file.warning("Malformed LyX file: Missing 'collapsed'.")
1757 # Delete this new paragraph, since it would not appear in
1758 # .tex output. This avoids also empty paragraphs.
1768 def convert_jurabib(file):
1769 i = find_token(file.header, '\\use_numerical_citations', 0)
1771 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1773 file.header.insert(i + 1, '\\use_jurabib 0')
1776 def revert_jurabib(file):
1777 i = find_token(file.header, '\\use_jurabib', 0)
1779 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1781 if get_value(file.header, '\\use_jurabib', 0) != "0":
1782 file.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1783 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1791 def convert_bibtopic(file):
1792 i = find_token(file.header, '\\use_jurabib', 0)
1794 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1796 file.header.insert(i + 1, '\\use_bibtopic 0')
1799 def revert_bibtopic(file):
1800 i = find_token(file.header, '\\use_bibtopic', 0)
1802 file.warning("Malformed lyx file: Missing '\\use_bibtopic'.")
1804 if get_value(file.header, '\\use_bibtopic', 0) != "0":
1805 file.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1806 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1813 def convert_float(file):
1816 i = find_token_exact(file.body, '\\begin_inset Float', i)
1819 # Seach for a line starting 'wide'
1820 # If, however, we find a line starting '\begin_layout'
1821 # (_always_ present) then break with a warning message
1824 if (file.body[i][:4] == "wide"):
1825 file.body.insert(i + 1, 'sideways false')
1827 elif (file.body[i][:13] == "\\begin_layout"):
1828 file.warning("Malformed lyx file: Missing 'wide'.")
1834 def revert_float(file):
1837 i = find_token_exact(file.body, '\\begin_inset Float', i)
1840 j = find_end_of_inset(file.body, i)
1842 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1845 if get_value(file.body, 'sideways', i, j) != "false":
1846 file.warning("Conversion of 'sideways true' not yet implemented.")
1847 # Don't remove 'sideways' so that people will get warnings by lyx
1850 del_token(file.body, 'sideways', i, j)
1854 def convert_graphics(file):
1855 """ Add extension to filenames of insetgraphics if necessary.
1859 i = find_token(file.body, "\\begin_inset Graphics", i)
1863 j = find_token_exact(file.body, "filename", i)
1867 filename = split(file.body[j])[1]
1868 absname = os.path.normpath(os.path.join(file.dir, filename))
1869 if file.input == stdin and not os.path.isabs(filename):
1870 # We don't know the directory and cannot check the file.
1871 # We could use a heuristic and take the current directory,
1872 # and we could try to find out if filename has an extension,
1873 # but that would be just guesses and could be wrong.
1874 file.warning("""Warning: Can not determine whether file
1876 needs an extension when reading from standard input.
1877 You may need to correct the file manually or run
1878 lyx2lyx again with the .lyx file as commandline argument.""" % filename)
1880 # This needs to be the same algorithm as in pre 233 insetgraphics
1881 if access(absname, F_OK):
1883 if access(absname + ".ps", F_OK):
1884 file.body[j] = replace(file.body[j], filename, filename + ".ps")
1886 if access(absname + ".eps", F_OK):
1887 file.body[j] = replace(file.body[j], filename, filename + ".eps")
1891 # Convert firstname and surname from styles -> char styles
1893 def convert_names(file):
1894 """ Convert in the docbook backend from firstname and surname style
1897 if file.backend != "docbook":
1903 i = find_token(file.body, "\\begin_layout Author", i)
1908 while file.body[i] == "":
1911 if file.body[i][:11] != "\\end_layout" or file.body[i+2][:13] != "\\begin_deeper":
1916 i = find_end_of( file.body, i+3, "\\begin_deeper","\\end_deeper")
1918 # something is really wrong, abort
1919 file.warning("Missing \\end_deeper, after style Author.")
1920 file.warning("Aborted attempt to parse FirstName and Surname.")
1922 firstname, surname = "", ""
1924 name = file.body[k:i]
1926 j = find_token(name, "\\begin_layout FirstName", 0)
1929 while(name[j] != "\\end_layout"):
1930 firstname = firstname + name[j]
1933 j = find_token(name, "\\begin_layout Surname", 0)
1936 while(name[j] != "\\end_layout"):
1937 surname = surname + name[j]
1941 del file.body[k+2:i+1]
1943 file.body[k-1:k-1] = ["", "",
1944 "\\begin_inset CharStyle Firstname",
1947 '\\begin_layout %s' % file.default_layout,
1955 "\\begin_inset CharStyle Surname",
1958 '\\begin_layout %s' % file.default_layout,
1967 def revert_names(file):
1968 """ Revert in the docbook backend from firstname and surname char style
1971 if file.backend != "docbook":
1976 # \use_natbib 1 \cite_engine <style>
1977 # \use_numerical_citations 0 -> where <style> is one of
1978 # \use_jurabib 0 "basic", "natbib_authoryear",
1979 # "natbib_numerical" or "jurabib"
1980 def convert_cite_engine(file):
1981 a = find_token(file.header, "\\use_natbib", 0)
1983 file.warning("Malformed lyx file: Missing '\\use_natbib'.")
1986 b = find_token(file.header, "\\use_numerical_citations", 0)
1987 if b == -1 or b != a+1:
1988 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1991 c = find_token(file.header, "\\use_jurabib", 0)
1992 if c == -1 or c != b+1:
1993 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1996 use_natbib = int(split(file.header[a])[1])
1997 use_numerical_citations = int(split(file.header[b])[1])
1998 use_jurabib = int(split(file.header[c])[1])
2000 cite_engine = "basic"
2002 if use_numerical_citations:
2003 cite_engine = "natbib_numerical"
2005 cite_engine = "natbib_authoryear"
2007 cite_engine = "jurabib"
2009 del file.header[a:c+1]
2010 file.header.insert(a, "\\cite_engine " + cite_engine)
2013 def revert_cite_engine(file):
2014 i = find_token(file.header, "\\cite_engine", 0)
2016 file.warning("Malformed lyx file: Missing '\\cite_engine'.")
2019 cite_engine = split(file.header[i])[1]
2024 if cite_engine == "natbib_numerical":
2027 elif cite_engine == "natbib_authoryear":
2029 elif cite_engine == "jurabib":
2033 file.header.insert(i, "\\use_jurabib " + use_jurabib)
2034 file.header.insert(i, "\\use_numerical_citations " + use_numerical)
2035 file.header.insert(i, "\\use_natbib " + use_natbib)
2041 def convert_paperpackage(file):
2042 i = find_token(file.header, "\\paperpackage", 0)
2046 packages = {'default':'none','a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
2047 if len(split(file.header[i])) > 1:
2048 paperpackage = split(file.header[i])[1]
2049 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
2051 file.header[i] = file.header[i] + ' widemarginsa4'
2054 def revert_paperpackage(file):
2055 i = find_token(file.header, "\\paperpackage", 0)
2059 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
2060 'widemarginsa4':'', 'default': 'default'}
2061 if len(split(file.header[i])) > 1:
2062 paperpackage = split(file.header[i])[1]
2064 paperpackage = 'default'
2065 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
2071 def convert_bullets(file):
2074 i = find_token(file.header, "\\bullet", i)
2077 if file.header[i][:12] == '\\bulletLaTeX':
2078 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1])
2081 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1]) +\
2082 ' ' + strip(file.header[i+2]) + ' ' + strip(file.header[i+3])
2084 del file.header[i+1:i + n]
2088 def revert_bullets(file):
2091 i = find_token(file.header, "\\bullet", i)
2094 if file.header[i][:12] == '\\bulletLaTeX':
2095 n = find(file.header[i], '"')
2097 file.warning("Malformed header.")
2100 file.header[i:i+1] = [file.header[i][:n-1],'\t' + file.header[i][n:], '\\end_bullet']
2103 frag = split(file.header[i])
2105 file.warning("Malformed header.")
2108 file.header[i:i+1] = [frag[0] + ' ' + frag[1],
2117 # \begin_header and \begin_document
2119 def add_begin_header(file):
2120 i = find_token(file.header, '\\lyxformat', 0)
2121 file.header.insert(i+1, '\\begin_header')
2122 file.header.insert(i+1, '\\begin_document')
2125 def remove_begin_header(file):
2126 i = find_token(file.header, "\\begin_document", 0)
2129 i = find_token(file.header, "\\begin_header", 0)
2135 # \begin_file.body and \end_file.body
2137 def add_begin_body(file):
2138 file.body.insert(0, '\\begin_body')
2139 file.body.insert(1, '')
2140 i = find_token(file.body, "\\end_document", 0)
2141 file.body.insert(i, '\\end_body')
2143 def remove_begin_body(file):
2144 i = find_token(file.body, "\\begin_body", 0)
2147 if not file.body[i]:
2149 i = find_token(file.body, "\\end_body", 0)
2157 def normalize_papersize(file):
2158 i = find_token(file.header, '\\papersize', 0)
2162 tmp = split(file.header[i])
2163 if tmp[1] == "Default":
2164 file.header[i] = '\\papersize default'
2166 if tmp[1] == "Custom":
2167 file.header[i] = '\\papersize custom'
2170 def denormalize_papersize(file):
2171 i = find_token(file.header, '\\papersize', 0)
2175 tmp = split(file.header[i])
2176 if tmp[1] == "custom":
2177 file.header[i] = '\\papersize Custom'
2181 # Strip spaces at end of command line
2183 def strip_end_space(file):
2184 for i in range(len(file.body)):
2185 if file.body[i][:1] == '\\':
2186 file.body[i] = strip(file.body[i])
2190 # Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes
2192 def use_x_boolean(file):
2193 bin2bool = {'0': 'false', '1': 'true'}
2194 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2195 i = find_token(file.header, use, 0)
2198 decompose = split(file.header[i])
2199 file.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
2202 def use_x_binary(file):
2203 bool2bin = {'false': '0', 'true': '1'}
2204 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2205 i = find_token(file.header, use, 0)
2208 decompose = split(file.header[i])
2209 file.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
2212 # Place all the paragraph parameters in their own line
2214 def normalize_paragraph_params(file):
2216 allowed_parameters = '\\paragraph_spacing', '\\noindent', '\\align', '\\labelwidthstring', "\\start_of_appendix", "\\leftindent"
2220 i = find_token(file.body, '\\begin_layout', i)
2226 if strip(body[i]) and split(body[i])[0] not in allowed_parameters:
2229 j = find(body[i],'\\', 1)
2232 body[i:i+1] = [strip(body[i][:j]), body[i][j:]]
2238 # Add/remove output_changes parameter
2240 def convert_output_changes (file):
2241 i = find_token(file.header, '\\tracking_changes', 0)
2243 file.warning("Malformed lyx file: Missing '\\tracking_changes'.")
2245 file.header.insert(i+1, '\\output_changes true')
2248 def revert_output_changes (file):
2249 i = find_token(file.header, '\\output_changes', 0)
2256 # Convert paragraph breaks and sanitize paragraphs
2258 def convert_ert_paragraphs(file):
2259 forbidden_settings = [
2260 # paragraph parameters
2261 '\\paragraph_spacing', '\\labelwidthstring',
2262 '\\start_of_appendix', '\\noindent',
2263 '\\leftindent', '\\align',
2265 '\\family', '\\series', '\\shape', '\\size',
2266 '\\emph', '\\numeric', '\\bar', '\\noun',
2267 '\\color', '\\lang']
2270 i = find_token(file.body, '\\begin_inset ERT', i)
2273 j = find_end_of_inset(file.body, i)
2275 file.warning("Malformed lyx file: Missing '\\end_inset'.")
2279 # convert non-standard paragraphs to standard
2282 k = find_token(file.body, "\\begin_layout", k, j)
2285 file.body[k] = '\\begin_layout %s' % file.default_layout
2288 # remove all paragraph parameters and font settings
2291 if (strip(file.body[k]) and
2292 split(file.body[k])[0] in forbidden_settings):
2298 # insert an empty paragraph before each paragraph but the first
2302 k = find_token(file.body, "\\begin_layout", k, j)
2309 file.body[k:k] = ['\\begin_layout %s' % file.default_layout, "",
2314 # convert \\newline to new paragraph
2317 k = find_token(file.body, "\\newline", k, j)
2320 file.body[k:k+1] = ["\\end_layout", "", '\\begin_layout %s' % file.default_layout]
2323 # We need an empty line if file.default_layout == ''
2324 if file.body[k-1] != '':
2325 file.body.insert(k-1, '')
2332 # Remove double paragraph breaks
2334 def revert_ert_paragraphs(file):
2337 i = find_token(file.body, '\\begin_inset ERT', i)
2340 j = find_end_of_inset(file.body, i)
2342 file.warning("Malformed lyx file: Missing '\\end_inset'.")
2346 # replace paragraph breaks with \newline
2349 k = find_token(file.body, "\\end_layout", k, j)
2350 l = find_token(file.body, "\\begin_layout", k, j)
2351 if k == -1 or l == -1:
2353 file.body[k:l+1] = ["\\newline"]
2357 # replace double \newlines with paragraph breaks
2360 k = find_token(file.body, "\\newline", k, j)
2364 while file.body[l] == "":
2366 if strip(file.body[l]) and split(file.body[l])[0] == "\\newline":
2367 file.body[k:l+1] = ["\\end_layout", "",
2368 '\\begin_layout %s' % file.default_layout]
2371 # We need an empty line if file.default_layout == ''
2372 if file.body[l+1] != '':
2373 file.body.insert(l+1, '')
2381 def convert_french(file):
2382 regexp = re.compile(r'^\\language\s+frenchb')
2383 i = find_re(file.header, regexp, 0)
2385 file.header[i] = "\\language french"
2387 # Change language in the document body
2388 regexp = re.compile(r'^\\lang\s+frenchb')
2391 i = find_re(file.body, regexp, i)
2394 file.body[i] = "\\lang french"
2398 def remove_paperpackage(file):
2399 i = find_token(file.header, '\\paperpackage', 0)
2404 paperpackage = split(file.header[i])[1]
2408 if paperpackage not in ("a4", "a4wide", "widemarginsa4"):
2411 conv = {"a4":"\\usepackage{a4}","a4wide": "\\usepackage{a4wide}",
2412 "widemarginsa4": "\\usepackage[widemargins]{a4}"}
2413 # for compatibility we ensure it is the first entry in preamble
2414 file.preamble[0:0] = [conv[paperpackage]]
2416 i = find_token(file.header, '\\papersize', 0)
2418 file.header[i] = "\\papersize default"
2421 def remove_quotestimes(file):
2422 i = find_token(file.header, '\\quotes_times', 0)
2429 # Convert SGML paragraphs
2431 def convert_sgml_paragraphs(file):
2432 if file.backend != "docbook":
2437 i = find_token(file.body, "\\begin_layout SGML", i)
2442 file.body[i] = "\\begin_layout Standard"
2443 j = find_token(file.body, "\\end_layout", i)
2445 file.body[j+1:j+1] = ['','\\end_inset','','','\\end_layout']
2446 file.body[i+1:i+1] = ['\\begin_inset ERT','status inlined','','\\begin_layout Standard','']
2453 convert = [[222, [insert_tracking_changes, add_end_header, convert_amsmath]],
2454 [223, [remove_color_default, convert_spaces, convert_bibtex, remove_insetparent]],
2455 [224, [convert_external, convert_comment]],
2456 [225, [add_end_layout, layout2begin_layout, convert_end_document,
2457 convert_table_valignment_middle, convert_breaks]],
2458 [226, [convert_note]],
2459 [227, [convert_box]],
2460 [228, [convert_collapsable, convert_ert]],
2461 [229, [convert_minipage]],
2462 [230, [convert_jurabib]],
2463 [231, [convert_float]],
2464 [232, [convert_bibtopic]],
2465 [233, [convert_graphics, convert_names]],
2466 [234, [convert_cite_engine]],
2467 [235, [convert_paperpackage]],
2468 [236, [convert_bullets, add_begin_header, add_begin_body,
2469 normalize_papersize, strip_end_space]],
2470 [237, [use_x_boolean]],
2471 [238, [update_latexaccents]],
2472 [239, [normalize_paragraph_params]],
2473 [240, [convert_output_changes]],
2474 [241, [convert_ert_paragraphs]],
2475 [242, [convert_french]],
2476 [243, [remove_paperpackage]],
2477 [244, [rename_spaces]],
2478 [245, [remove_quotestimes, convert_sgml_paragraphs]]]
2480 revert = [[244, []],
2481 [243, [revert_space_names]],
2484 [240, [revert_ert_paragraphs]],
2485 [239, [revert_output_changes]],
2488 [236, [use_x_binary]],
2489 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
2491 [234, [revert_paperpackage]],
2492 [233, [revert_cite_engine]],
2493 [232, [revert_names]],
2494 [231, [revert_bibtopic]],
2495 [230, [revert_float]],
2496 [229, [revert_jurabib]],
2498 [227, [revert_collapsable, revert_ert]],
2499 [226, [revert_box, revert_external_2]],
2500 [225, [revert_note]],
2501 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
2502 revert_valignment_middle, revert_breaks, convert_frameless_box,
2504 [223, [revert_external_2, revert_comment, revert_eqref]],
2505 [222, [revert_spaces, revert_bibtex]],
2506 [221, [revert_amsmath, rm_end_header, rm_tracking_changes, rm_body_changes]]]
2509 if __name__ == "__main__":