1 # This file is part of lyx2lyx
2 # -*- coding: iso-8859-1 -*-
3 # Copyright (C) 2002 Dekel Tsur <dekel@lyx.org>
4 # Copyright (C) 2002-2004 José Matos <jamatos@lyx.org>
5 # Copyright (C) 2004-2005 Georg Baum <Georg.Baum@post.rwth-aachen.de>
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 from os import access, F_OK
24 from parser_tools import find_token, find_end_of_inset, get_next_paragraph, \
25 get_paragraph, get_value, del_token, is_nonempty_line,\
26 find_tokens, find_end_of, find_token2, find_re
28 from string import replace, split, find, strip, join
30 from lyx_0_12 import update_latexaccents
33 # Remove \color default
35 def remove_color_default(file):
38 i = find_token(file.body, "\\color default", i)
41 file.body[i] = replace(file.body[i], "\\color default",
48 def add_end_header(file):
49 file.header.append("\\end_header");
52 def rm_end_header(file):
53 i = find_token(file.header, "\\end_header", 0)
60 # \SpecialChar ~ -> \InsetSpace ~
62 def convert_spaces(file):
63 for i in range(len(file.body)):
64 file.body[i] = replace(file.body[i],"\\SpecialChar ~","\\InsetSpace ~")
67 def revert_spaces(file):
68 for i in range(len(file.body)):
69 file.body[i] = replace(file.body[i],"\\InsetSpace ~", "\\SpecialChar ~")
73 # equivalent to lyx::support::escape()
75 def lyx_support_escape(lab):
76 hexdigit = ['0', '1', '2', '3', '4', '5', '6', '7',
77 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
81 if o >= 128 or c == '=' or c == '%':
83 enc = enc + hexdigit[o >> 4]
84 enc = enc + hexdigit[o & 15]
91 # \begin_inset LatexCommand \eqref -> ERT
93 def revert_eqref(file):
94 regexp = re.compile(r'^\\begin_inset\s+LatexCommand\s+\\eqref')
97 i = find_re(file.body, regexp, i)
100 eqref = lyx_support_escape(regexp.sub("", file.body[i]))
101 file.body[i:i+1] = ["\\begin_inset ERT", "status Collapsed", "",
102 "\\layout Standard", "", "\\backslash ",
110 def convert_bibtex(file):
111 for i in range(len(file.body)):
112 file.body[i] = replace(file.body[i],"\\begin_inset LatexCommand \\BibTeX",
113 "\\begin_inset LatexCommand \\bibtex")
116 def revert_bibtex(file):
117 for i in range(len(file.body)):
118 file.body[i] = replace(file.body[i], "\\begin_inset LatexCommand \\bibtex",
119 "\\begin_inset LatexCommand \\BibTeX")
125 def remove_insetparent(file):
128 i = find_token(file.body, "\\begin_inset LatexCommand \\lyxparent", i)
137 def convert_external(file):
138 external_rexp = re.compile(r'\\begin_inset External ([^,]*),"([^"]*)",')
139 external_header = "\\begin_inset External"
142 i = find_token(file.body, external_header, i)
145 look = external_rexp.search(file.body[i])
148 args[0] = look.group(1)
149 args[1] = look.group(2)
150 #FIXME: if the previous search fails then warn
152 if args[0] == "RasterImage":
153 # Convert a RasterImage External Inset to a Graphics Inset.
154 top = "\\begin_inset Graphics"
156 filename = "\tfilename " + args[1]
157 file.body[i:i+1] = [top, filename]
160 # Convert the old External Inset format to the new.
161 top = external_header
162 template = "\ttemplate " + args[0]
164 filename = "\tfilename " + args[1]
165 file.body[i:i+1] = [top, template, filename]
168 file.body[i:i+1] = [top, template]
172 def revert_external_1(file):
173 external_header = "\\begin_inset External"
176 i = find_token(file.body, external_header, i)
180 template = split(file.body[i+1])
184 filename = split(file.body[i+1])
188 params = split(file.body[i+1])
190 if file.body[i+1]: del file.body[i+1]
192 file.body[i] = file.body[i] + " " + template[0]+ ', "' + filename[0] + '", " '+ join(params[1:]) + '"'
196 def revert_external_2(file):
197 draft_token = '\tdraft'
200 i = find_token(file.body, '\\begin_inset External', i)
203 j = find_end_of_inset(file.body, i + 1)
205 #this should not happen
207 k = find_token(file.body, draft_token, i+1, j-1)
208 if (k != -1 and len(draft_token) == len(file.body[k])):
216 def convert_comment(file):
218 comment = "\\layout Comment"
220 i = find_token(file.body, comment, i)
224 file.body[i:i+1] = ["\\layout Standard","","",
225 "\\begin_inset Comment",
232 i = find_token(file.body, "\\layout", i)
234 i = len(file.body) - 1
235 file.body[i:i] = ["\\end_inset","",""]
238 j = find_token(file.body, '\\begin_deeper', old_i, i)
239 if j == -1: j = i + 1
240 k = find_token(file.body, '\\begin_inset', old_i, i)
241 if k == -1: k = i + 1
246 i = find_end_of( file.body, i, "\\begin_deeper","\\end_deeper")
248 #This case should not happen
249 #but if this happens deal with it greacefully adding
250 #the missing \end_deeper.
251 i = len(file.body) - 1
252 file.body[i:i] = ["\end_deeper",""]
260 i = find_end_of( file.body, i, "\\begin_inset","\\end_inset")
262 #This case should not happen
263 #but if this happens deal with it greacefully adding
264 #the missing \end_inset.
265 i = len(file.body) - 1
266 file.body[i:i] = ["\\end_inset","","","\\end_inset","",""]
272 if find(file.body[i], comment) == -1:
273 file.body[i:i] = ["\\end_inset"]
276 file.body[i:i+1] = ["\\layout Standard"]
280 def revert_comment(file):
283 i = find_tokens(file.body, ["\\begin_inset Comment", "\\begin_inset Greyedout"], i)
287 file.body[i] = "\\begin_inset Note"
294 def add_end_layout(file):
295 i = find_token(file.body, '\\layout', 0)
301 struct_stack = ["\\layout"]
304 i = find_tokens(file.body, ["\\begin_inset", "\\end_inset", "\\layout",
305 "\\begin_deeper", "\\end_deeper", "\\the_end"], i)
308 token = split(file.body[i])[0]
310 file.warning("Truncated file.")
312 file.body.insert(i, '\\the_end')
315 if token == "\\begin_inset":
316 struct_stack.append(token)
320 if token == "\\end_inset":
321 tail = struct_stack.pop()
322 if tail == "\\layout":
323 file.body.insert(i,"")
324 file.body.insert(i,"\\end_layout")
326 #Check if it is the correct tag
331 if token == "\\layout":
332 tail = struct_stack.pop()
334 file.body.insert(i,"")
335 file.body.insert(i,"\\end_layout")
338 struct_stack.append(tail)
340 struct_stack.append(token)
343 if token == "\\begin_deeper":
344 file.body.insert(i,"")
345 file.body.insert(i,"\\end_layout")
347 struct_stack.append(token)
350 if token == "\\end_deeper":
351 if struct_stack[-1] == '\\layout':
352 file.body.insert(i, '\\end_layout')
359 file.body.insert(i, "")
360 file.body.insert(i, "\\end_layout")
364 def rm_end_layout(file):
367 i = find_token(file.body, '\\end_layout', i)
376 # Handle change tracking keywords
378 def insert_tracking_changes(file):
379 i = find_token(file.header, "\\tracking_changes", 0)
381 file.header.append("\\tracking_changes 0")
384 def rm_tracking_changes(file):
385 i = find_token(file.header, "\\author", 0)
389 i = find_token(file.header, "\\tracking_changes", 0)
395 def rm_body_changes(file):
398 i = find_token(file.body, "\\change_", i)
406 # \layout -> \begin_layout
408 def layout2begin_layout(file):
411 i = find_token(file.body, '\\layout', i)
415 file.body[i] = replace(file.body[i], '\\layout', '\\begin_layout')
419 def begin_layout2layout(file):
422 i = find_token(file.body, '\\begin_layout', i)
426 file.body[i] = replace(file.body[i], '\\begin_layout', '\\layout')
431 # valignment="center" -> valignment="middle"
433 def convert_valignment_middle(body, start, end):
434 for i in range(start, end):
435 if re.search('^<(column|cell) .*valignment="center".*>$', body[i]):
436 body[i] = replace(body[i], 'valignment="center"', 'valignment="middle"')
439 def convert_table_valignment_middle(file):
440 regexp = re.compile(r'^\\begin_inset\s+Tabular')
443 i = find_re(file.body, regexp, i)
446 j = find_end_of_inset(file.body, i + 1)
448 #this should not happen
449 convert_valignment_middle(file.body, i + 1, len(file.body))
451 convert_valignment_middle(file.body, i + 1, j)
455 def revert_table_valignment_middle(body, start, end):
456 for i in range(start, end):
457 if re.search('^<(column|cell) .*valignment="middle".*>$', body[i]):
458 body[i] = replace(body[i], 'valignment="middle"', 'valignment="center"')
461 def revert_valignment_middle(file):
462 regexp = re.compile(r'^\\begin_inset\s+Tabular')
465 i = find_re(file.body, regexp, i)
468 j = find_end_of_inset(file.body, i + 1)
470 #this should not happen
471 revert_table_valignment_middle(file.body, i + 1, len(file.body))
473 revert_table_valignment_middle(file.body, i + 1, j)
478 # \the_end -> \end_document
480 def convert_end_document(file):
481 i = find_token(file.body, "\\the_end", 0)
483 file.body.append("\\end_document")
485 file.body[i] = "\\end_document"
488 def revert_end_document(file):
489 i = find_token(file.body, "\\end_document", 0)
491 file.body.append("\\the_end")
493 file.body[i] = "\\the_end"
497 # Convert line and page breaks
500 #\line_top \line_bottom \pagebreak_top \pagebreak_bottom \added_space_top xxx \added_space_bottom yyy
504 #\begin layout Standard
510 #\begin layout Standard
517 #\begin_inset VSpace xxx
522 #\begin_inset VSpace xxx
529 def convert_breaks(file):
530 par_params = ('added_space_bottom', 'added_space_top', 'align',
531 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
532 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
534 font_attributes = ['\\family', '\\series', '\\shape', '\\emph',
535 '\\numeric', '\\bar', '\\noun', '\\color', '\\lang']
536 attribute_values = ['default', 'default', 'default', 'default',
537 'default', 'default', 'default', 'none', file.language]
540 i = find_token(file.body, "\\begin_layout", i)
543 layout = split(file.body[i])[1]
546 # Merge all paragraph parameters into a single line
547 # We cannot check for '\\' only because paragraphs may start e.g.
549 while file.body[i + 1][:1] == '\\' and split(file.body[i + 1][1:])[0] in par_params:
550 file.body[i] = file.body[i + 1] + ' ' + file.body[i]
553 line_top = find(file.body[i],"\\line_top")
554 line_bot = find(file.body[i],"\\line_bottom")
555 pb_top = find(file.body[i],"\\pagebreak_top")
556 pb_bot = find(file.body[i],"\\pagebreak_bottom")
557 vspace_top = find(file.body[i],"\\added_space_top")
558 vspace_bot = find(file.body[i],"\\added_space_bottom")
560 if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1:
563 # Do we have a nonstandard paragraph? We need to create new paragraphs
564 # if yes to avoid putting lyxline etc. inside of special environments.
565 # This is wrong for itemize and enumerate environments, but it is
566 # impossible to convert these correctly.
567 # We want to avoid new paragraphs if possible becauase we want to
568 # inherit font sizes.
570 if (layout != "Standard" or find(file.body[i],"\\align") != -1 or
571 find(file.body[i],"\\labelwidthstring") != -1 or
572 find(file.body[i],"\\noindent") != -1):
575 # get the font size of the beginning of this paragraph, since we need
576 # it for the lyxline inset
578 while not is_nonempty_line(file.body[j]):
581 if find(file.body[j], "\\size") != -1:
582 size_top = split(file.body[j])[1]
584 for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom":
585 file.body[i] = replace(file.body[i], tag, "")
588 # the position could be change because of the removal of other
589 # paragraph properties above
590 vspace_top = find(file.body[i],"\\added_space_top")
591 tmp_list = split(file.body[i][vspace_top:])
592 vspace_top_value = tmp_list[1]
593 file.body[i] = file.body[i][:vspace_top] + join(tmp_list[2:])
596 # the position could be change because of the removal of other
597 # paragraph properties above
598 vspace_bot = find(file.body[i],"\\added_space_bottom")
599 tmp_list = split(file.body[i][vspace_bot:])
600 vspace_bot_value = tmp_list[1]
601 file.body[i] = file.body[i][:vspace_bot] + join(tmp_list[2:])
603 file.body[i] = strip(file.body[i])
606 # Create an empty paragraph or paragraph fragment for line and
607 # page break that belong above the paragraph
608 if pb_top !=-1 or line_top != -1 or vspace_top != -1:
610 paragraph_above = list()
612 # We need to create an extra paragraph for nonstandard environments
613 paragraph_above = ['\\begin_layout Standard', '']
616 paragraph_above.extend(['\\newpage ',''])
619 paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','',''])
623 paragraph_above.extend(['\\size ' + size_top + ' '])
624 # We need an additional vertical space of -\parskip.
625 # We can't use the vspace inset because it does not know \parskip.
626 paragraph_above.extend(['\\lyxline ', '', ''])
627 insert_ert(paragraph_above, len(paragraph_above) - 1, 'Collapsed',
628 '\\vspace{-1\\parskip}\n', file.format + 1)
629 paragraph_above.extend([''])
632 paragraph_above.extend(['\\end_layout ',''])
633 # insert new paragraph above the current paragraph
634 file.body[i-2:i-2] = paragraph_above
636 # insert new lines at the beginning of the current paragraph
637 file.body[i:i] = paragraph_above
639 i = i + len(paragraph_above)
641 # Ensure that nested style are converted later.
642 k = find_end_of(file.body, i, "\\begin_layout", "\\end_layout")
647 if pb_bot !=-1 or line_bot != -1 or vspace_bot != -1:
649 # get the font size of the end of this paragraph
653 if find(file.body[j], "\\size") != -1:
654 size_bot = split(file.body[j])[1]
656 elif find(file.body[j], "\\begin_inset") != -1:
658 j = find_end_of_inset(file.body, j)
662 paragraph_below = list()
664 # We need to create an extra paragraph for nonstandard environments
665 paragraph_below = ['', '\\begin_layout Standard', '']
667 for a in range(len(font_attributes)):
668 if find_token(file.body, font_attributes[a], i, k) != -1:
669 paragraph_below.extend([font_attributes[a] + ' ' + attribute_values[a]])
672 if nonstandard and size_bot != '':
673 paragraph_below.extend(['\\size ' + size_bot + ' '])
674 paragraph_below.extend(['\\lyxline ',''])
676 paragraph_below.extend(['\\size default '])
679 paragraph_below.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','',''])
682 paragraph_below.extend(['\\newpage ',''])
685 paragraph_below.extend(['\\end_layout '])
686 # insert new paragraph below the current paragraph
687 file.body[k+1:k+1] = paragraph_below
689 # insert new lines at the end of the current paragraph
690 file.body[k:k] = paragraph_below
696 def convert_note(file):
699 i = find_tokens(file.body, ["\\begin_inset Note",
700 "\\begin_inset Comment",
701 "\\begin_inset Greyedout"], i)
705 file.body[i] = file.body[i][0:13] + 'Note ' + file.body[i][13:]
709 def revert_note(file):
710 note_header = "\\begin_inset Note "
713 i = find_token(file.body, note_header, i)
717 file.body[i] = "\\begin_inset " + file.body[i][len(note_header):]
724 def convert_box(file):
727 i = find_tokens(file.body, ["\\begin_inset Boxed",
728 "\\begin_inset Doublebox",
729 "\\begin_inset Frameless",
730 "\\begin_inset ovalbox",
731 "\\begin_inset Ovalbox",
732 "\\begin_inset Shadowbox"], i)
736 file.body[i] = file.body[i][0:13] + 'Box ' + file.body[i][13:]
740 def revert_box(file):
741 box_header = "\\begin_inset Box "
744 i = find_token(file.body, box_header, i)
748 file.body[i] = "\\begin_inset " + file.body[i][len(box_header):]
755 def convert_collapsable(file):
758 i = find_tokens(file.body, ["\\begin_inset Box",
759 "\\begin_inset Branch",
760 "\\begin_inset CharStyle",
761 "\\begin_inset Float",
762 "\\begin_inset Foot",
763 "\\begin_inset Marginal",
764 "\\begin_inset Note",
765 "\\begin_inset OptArg",
766 "\\begin_inset Wrap"], i)
770 # Seach for a line starting 'collapsed'
771 # If, however, we find a line starting '\begin_layout'
772 # (_always_ present) then break with a warning message
775 if (file.body[i] == "collapsed false"):
776 file.body[i] = "status open"
778 elif (file.body[i] == "collapsed true"):
779 file.body[i] = "status collapsed"
781 elif (file.body[i][:13] == "\\begin_layout"):
782 file.warning("Malformed LyX file: Missing 'collapsed'.")
789 def revert_collapsable(file):
792 i = find_tokens(file.body, ["\\begin_inset Box",
793 "\\begin_inset Branch",
794 "\\begin_inset CharStyle",
795 "\\begin_inset Float",
796 "\\begin_inset Foot",
797 "\\begin_inset Marginal",
798 "\\begin_inset Note",
799 "\\begin_inset OptArg",
800 "\\begin_inset Wrap"], i)
804 # Seach for a line starting 'status'
805 # If, however, we find a line starting '\begin_layout'
806 # (_always_ present) then break with a warning message
809 if (file.body[i] == "status open"):
810 file.body[i] = "collapsed false"
812 elif (file.body[i] == "status collapsed" or
813 file.body[i] == "status inlined"):
814 file.body[i] = "collapsed true"
816 elif (file.body[i][:13] == "\\begin_layout"):
817 file.warning("Malformed LyX file: Missing 'status'.")
827 def convert_ert(file):
830 i = find_token(file.body, "\\begin_inset ERT", i)
834 # Seach for a line starting 'status'
835 # If, however, we find a line starting '\begin_layout'
836 # (_always_ present) then break with a warning message
839 if (file.body[i] == "status Open"):
840 file.body[i] = "status open"
842 elif (file.body[i] == "status Collapsed"):
843 file.body[i] = "status collapsed"
845 elif (file.body[i] == "status Inlined"):
846 file.body[i] = "status inlined"
848 elif (file.body[i][:13] == "\\begin_layout"):
849 file.warning("Malformed LyX file: Missing 'status'.")
856 def revert_ert(file):
859 i = find_token(file.body, "\\begin_inset ERT", i)
863 # Seach for a line starting 'status'
864 # If, however, we find a line starting '\begin_layout'
865 # (_always_ present) then break with a warning message
868 if (file.body[i] == "status open"):
869 file.body[i] = "status Open"
871 elif (file.body[i] == "status collapsed"):
872 file.body[i] = "status Collapsed"
874 elif (file.body[i] == "status inlined"):
875 file.body[i] = "status Inlined"
877 elif (file.body[i][:13] == "\\begin_layout"):
878 file.warning("Malformed LyX file : Missing 'status'.")
888 def convert_minipage(file):
889 """ Convert minipages to the box inset.
890 We try to use the same order of arguments as lyx does.
893 inner_pos = ["c","t","b","s"]
897 i = find_token(file.body, "\\begin_inset Minipage", i)
901 file.body[i] = "\\begin_inset Box Frameless"
904 # convert old to new position using the pos list
905 if file.body[i][:8] == "position":
906 file.body[i] = 'position "%s"' % pos[int(file.body[i][9])]
908 file.body.insert(i, 'position "%s"' % pos[0])
911 file.body.insert(i, 'hor_pos "c"')
913 file.body.insert(i, 'has_inner_box 1')
916 # convert the inner_position
917 if file.body[i][:14] == "inner_position":
918 file.body[i] = 'inner_pos "%s"' % inner_pos[int(file.body[i][15])]
920 file.body.insert('inner_pos "%s"' % inner_pos[0])
923 # We need this since the new file format has a height and width
924 # in a different order.
925 if file.body[i][:6] == "height":
926 height = file.body[i][6:]
927 # test for default value of 221 and convert it accordingly
928 if height == ' "0pt"' or height == ' "0"':
934 if file.body[i][:5] == "width":
935 width = file.body[i][5:]
940 if file.body[i][:9] == "collapsed":
941 if file.body[i][9:] == "true":
949 file.body.insert(i, 'use_parbox 0')
951 file.body.insert(i, 'width' + width)
953 file.body.insert(i, 'special "none"')
955 file.body.insert(i, 'height' + height)
957 file.body.insert(i, 'height_special "totalheight"')
959 file.body.insert(i, 'status ' + status)
963 # -------------------------------------------------------------------------------------------
964 # Convert backslashes and '\n' into valid ERT code, append the converted
965 # text to body[i] and return the (maybe incremented) line index i
966 def convert_ertbackslash(body, i, ert, format):
969 body[i] = body[i] + '\\backslash '
974 body[i+1:i+1] = ['\\newline ', '']
977 body[i+1:i+1] = ['\\end_layout', '', '\\begin_layout Standard', '']
980 body[i] = body[i] + c
984 # Converts lines in ERT code to LaTeX
985 # The surrounding \begin_layout ... \end_layout pair must not be included
986 def ert2latex(lines, format):
987 backslash = re.compile(r'\\backslash\s*$')
988 newline = re.compile(r'\\newline\s*$')
990 begin_layout = re.compile(r'\\layout\s*\S+$')
992 begin_layout = re.compile(r'\\begin_layout\s*\S+$')
993 end_layout = re.compile(r'\\end_layout\s*$')
995 for i in range(len(lines)):
996 line = backslash.sub('\\\\', lines[i])
998 if begin_layout.match(line):
1001 line = newline.sub('\n', line)
1003 if begin_layout.match(line):
1005 if format > 224 and end_layout.match(line):
1011 # get all paragraph parameters. They can be all on one line or on several lines.
1012 # lines[i] must be the first parameter line
1013 def get_par_params(lines, i):
1014 par_params = ('added_space_bottom', 'added_space_top', 'align',
1015 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
1016 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
1017 'start_of_appendix')
1018 # We cannot check for '\\' only because paragraphs may start e.g.
1019 # with '\\backslash'
1021 while lines[i][:1] == '\\' and split(lines[i][1:])[0] in par_params:
1022 params = params + ' ' + strip(lines[i])
1024 return strip(params)
1027 # convert LyX font size to LaTeX fontsize
1028 def lyxsize2latexsize(lyxsize):
1029 sizes = {"tiny" : "tiny", "scriptsize" : "scriptsize",
1030 "footnotesize" : "footnotesize", "small" : "small",
1031 "normal" : "normalsize", "large" : "large", "larger" : "Large",
1032 "largest" : "LARGE", "huge" : "huge", "giant" : "Huge"}
1033 if lyxsize in sizes:
1034 return '\\' + sizes[lyxsize]
1038 # Change vspace insets, page breaks and lyxlines to paragraph options
1039 # (if possible) or ERT
1040 def revert_breaks(file):
1042 # Get default spaceamount
1043 i = find_token(file.header, '\\defskip', 0)
1045 defskipamount = 'medskip'
1047 defskipamount = split(file.header[i])[1]
1049 keys = {"\\begin_inset" : "vspace", "\\lyxline" : "lyxline",
1050 "\\newpage" : "newpage"}
1051 keywords_top = {"vspace" : "\\added_space_top", "lyxline" : "\\line_top",
1052 "newpage" : "\\pagebreak_top"}
1053 keywords_bot = {"vspace" : "\\added_space_bottom", "lyxline" : "\\line_bottom",
1054 "newpage" : "\\pagebreak_bottom"}
1055 tokens = ["\\begin_inset VSpace", "\\lyxline", "\\newpage"]
1057 # Convert the insets
1060 i = find_tokens(file.body, tokens, i)
1064 # Are we at the beginning of a paragraph?
1066 this_par = get_paragraph(file.body, i, file.format - 1)
1067 start = this_par + 1
1068 params = get_par_params(file.body, start)
1070 # Paragraph parameters may be on one or more lines.
1071 # Find the start of the real paragraph text.
1072 while file.body[start][:1] == '\\' and split(file.body[start])[0] in params:
1074 for k in range(start, i):
1075 if find(file.body[k], "\\size") != -1:
1077 size = split(file.body[k])[1]
1078 elif is_nonempty_line(file.body[k]):
1081 # Find the end of the real paragraph text.
1082 next_par = get_next_paragraph(file.body, i, file.format - 1)
1084 file.warning("Malformed LyX file: Missing next paragraph.")
1088 # first line of our insets
1090 # last line of our insets
1091 inset_end = inset_start
1092 # Are we at the end of a paragraph?
1094 # start and end line numbers to delete if we convert this inset
1096 # is this inset a lyxline above a paragraph?
1098 # raw inset information
1100 # name of this inset
1102 # font size of this inset
1105 # Detect subsequent lyxline, vspace and pagebreak insets created by convert_breaks()
1109 if find_tokens(file.body, tokens, k) == k:
1111 lines.append(split(file.body[k]))
1112 insets.append(keys[lines[n][0]])
1113 del_lines.append([k, k])
1118 elif find(file.body[k], "\\size") != -1:
1120 size = split(file.body[k])[1]
1121 elif find_token(file.body, "\\begin_inset ERT", k) == k:
1122 ert_begin = find_token(file.body, "\\layout", k) + 1
1124 file.warning("Malformed LyX file: Missing '\\layout'.")
1126 ert_end = find_end_of_inset(file.body, k)
1128 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1130 ert = ert2latex(file.body[ert_begin:ert_end], file.format - 1)
1131 if (n > 0 and insets[n - 1] == "lyxline" and
1132 ert == '\\vspace{-1\\parskip}\n'):
1133 # vspace ERT created by convert_breaks() for top lyxline
1135 del_lines[n - 1][1] = ert_end
1141 elif (n > 0 and insets[n - 1] == "vspace" and
1142 find_token(file.body, "\\end_inset", k) == k):
1143 # ignore end of vspace inset
1144 del_lines[n - 1][1] = k
1146 elif is_nonempty_line(file.body[k]):
1151 # Determine space amount for vspace insets
1152 spaceamount = list()
1155 if insets[k] == "vspace":
1156 spaceamount.append(lines[k][2])
1157 arguments.append(' ' + spaceamount[k] + ' ')
1159 spaceamount.append('')
1160 arguments.append(' ')
1162 # Can we convert to top paragraph parameters?
1164 if ((n == 3 and insets[0] == "newpage" and insets[1] == "vspace" and
1165 insets[2] == "lyxline" and top[2]) or
1167 ((insets[0] == "newpage" and insets[1] == "vspace") or
1168 (insets[0] == "newpage" and insets[1] == "lyxline" and top[1]) or
1169 (insets[0] == "vspace" and insets[1] == "lyxline" and top[1]))) or
1170 (n == 1 and insets[0] == "lyxline" and top[0])):
1171 # These insets have been created before a paragraph by
1175 # Can we convert to bottom paragraph parameters?
1177 if ((n == 3 and insets[0] == "lyxline" and not top[0] and
1178 insets[1] == "vspace" and insets[2] == "newpage") or
1180 ((insets[0] == "lyxline" and not top[0] and insets[1] == "vspace") or
1181 (insets[0] == "lyxline" and not top[0] and insets[1] == "newpage") or
1182 (insets[0] == "vspace" and insets[1] == "newpage"))) or
1183 (n == 1 and insets[0] == "lyxline" and not top[0])):
1184 # These insets have been created after a paragraph by
1188 if paragraph_start and paragraph_end:
1189 # We are in a paragraph of our own.
1190 # We must not delete this paragraph if it has parameters
1192 # First try to merge with the previous paragraph.
1193 # We try the previous paragraph first because we would
1194 # otherwise need ERT for two subsequent vspaces.
1195 prev_par = get_paragraph(file.body, this_par - 1, file.format - 1) + 1
1196 if prev_par > 0 and not before:
1197 prev_params = get_par_params(file.body, prev_par + 1)
1199 # determine font size
1200 prev_size = "normal"
1202 while file.body[k][:1] == '\\' and split(file.body[k])[0] in prev_params:
1205 if find(file.body[k], "\\size") != -1:
1206 prev_size = split(file.body[k])[1]
1208 elif find(file.body[k], "\\begin_inset") != -1:
1210 k = find_end_of_inset(file.body, k)
1211 elif is_nonempty_line(file.body[k]):
1215 if (keywords_bot[insets[k]] in prev_params or
1216 (insets[k] == "lyxline" and sizes[k] != prev_size)):
1221 file.body.insert(prev_par + 1,
1222 keywords_bot[insets[k]] + arguments[k])
1223 del file.body[this_par+n:next_par-1+n]
1226 # Then try next paragraph
1227 if next_par > 0 and not after:
1228 next_params = get_par_params(file.body, next_par + 1)
1230 while file.body[k][:1] == '\\' and split(file.body[k])[0] in next_params:
1232 # determine font size
1233 next_size = "normal"
1236 if find(file.body[k], "\\size") != -1:
1237 next_size = split(file.body[k])[1]
1239 elif is_nonempty_line(file.body[k]):
1243 if (keywords_top[insets[k]] in next_params or
1244 (insets[k] == "lyxline" and sizes[k] != next_size)):
1249 file.body.insert(next_par + 1,
1250 keywords_top[insets[k]] + arguments[k])
1251 del file.body[this_par:next_par-1]
1254 elif paragraph_start or paragraph_end:
1255 # Convert to paragraph formatting if we are at the beginning or end
1256 # of a paragraph and the resulting paragraph would not be empty
1257 # The order is important: del and insert invalidate some indices
1259 keywords = keywords_top
1261 keywords = keywords_bot
1264 if keywords[insets[k]] in params:
1269 file.body.insert(this_par + 1,
1270 keywords[insets[k]] + arguments[k])
1271 for j in range(k, n):
1272 del_lines[j][0] = del_lines[j][0] + 1
1273 del_lines[j][1] = del_lines[j][1] + 1
1274 del file.body[del_lines[k][0]:del_lines[k][1]+1]
1275 deleted = del_lines[k][1] - del_lines[k][0] + 1
1276 for j in range(k + 1, n):
1277 del_lines[j][0] = del_lines[j][0] - deleted
1278 del_lines[j][1] = del_lines[j][1] - deleted
1282 # Convert the first inset to ERT.
1283 # The others are converted in the next loop runs (if they exist)
1284 if insets[0] == "vspace":
1285 file.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
1286 '\\layout Standard', '', '\\backslash ']
1288 if spaceamount[0][-1] == '*':
1289 spaceamount[0] = spaceamount[0][:-1]
1294 # Replace defskip by the actual value
1295 if spaceamount[0] == 'defskip':
1296 spaceamount[0] = defskipamount
1298 # LaTeX does not know \\smallskip* etc
1300 if spaceamount[0] == 'smallskip':
1301 spaceamount[0] = '\\smallskipamount'
1302 elif spaceamount[0] == 'medskip':
1303 spaceamount[0] = '\\medskipamount'
1304 elif spaceamount[0] == 'bigskip':
1305 spaceamount[0] = '\\bigskipamount'
1306 elif spaceamount[0] == 'vfill':
1307 spaceamount[0] = '\\fill'
1309 # Finally output the LaTeX code
1310 if (spaceamount[0] == 'smallskip' or spaceamount[0] == 'medskip' or
1311 spaceamount[0] == 'bigskip' or spaceamount[0] == 'vfill'):
1312 file.body.insert(i, spaceamount[0] + '{}')
1315 file.body.insert(i, 'vspace*{')
1317 file.body.insert(i, 'vspace{')
1318 i = convert_ertbackslash(file.body, i, spaceamount[0], file.format - 1)
1319 file.body[i] = file.body[i] + '}'
1321 elif insets[0] == "lyxline":
1323 latexsize = lyxsize2latexsize(size)
1325 file.warning("Could not convert LyX fontsize '%s' to LaTeX font size." % size)
1326 latexsize = '\\normalsize'
1327 i = insert_ert(file.body, i, 'Collapsed',
1328 '\\lyxline{%s}' % latexsize,
1330 # We use \providecommand so that we don't get an error if native
1331 # lyxlines are used (LyX writes first its own preamble and then
1332 # the user specified one)
1333 add_to_preamble(file,
1334 ['% Commands inserted by lyx2lyx for lyxlines',
1335 '\\providecommand{\\lyxline}[1]{',
1336 ' {#1 \\vspace{1ex} \\hrule width \\columnwidth \\vspace{1ex}}'
1338 elif insets[0] == "newpage":
1340 i = insert_ert(file.body, i, 'Collapsed', '\\newpage{}',
1344 # Convert a LyX length into a LaTeX length
1345 def convert_len(len, special):
1346 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
1347 "page%":"\\pagewidth", "line%":"\\linewidth",
1348 "theight%":"\\textheight", "pheight%":"\\pageheight"}
1350 # Convert special lengths
1351 if special != 'none':
1352 len = '%f\\' % len2value(len) + special
1354 # Convert LyX units to LaTeX units
1355 for unit in units.keys():
1356 if find(len, unit) != -1:
1357 len = '%f' % (len2value(len) / 100) + units[unit]
1363 # Convert a LyX length into valid ERT code and append it to body[i]
1364 # Return the (maybe incremented) line index i
1365 def convert_ertlen(body, i, len, special, format):
1366 # Convert backslashes and insert the converted length into body
1367 return convert_ertbackslash(body, i, convert_len(len, special), format)
1370 # Return the value of len without the unit in numerical form
1372 result = re.search('([+-]?[0-9.]+)', len)
1374 return float(result.group(1))
1375 # No number means 1.0
1379 # Convert text to ERT and insert it at body[i]
1380 # Return the index of the line after the inserted ERT
1381 def insert_ert(body, i, status, text, format):
1382 body[i:i] = ['\\begin_inset ERT', 'status ' + status, '']
1385 body[i:i] = ['\\layout Standard', '']
1387 body[i:i] = ['\\begin_layout Standard', '']
1388 i = i + 1 # i points now to the just created empty line
1389 i = convert_ertbackslash(body, i, text, format) + 1
1391 body[i:i] = ['\\end_layout']
1393 body[i:i] = ['', '\\end_inset', '']
1398 # Add text to the preamble if it is not already there.
1399 # Only the first line is checked!
1400 def add_to_preamble(file, text):
1401 if find_token(file.preamble, text[0], 0) != -1:
1404 file.preamble.extend(text)
1407 def convert_frameless_box(file):
1408 pos = ['t', 'c', 'b']
1409 inner_pos = ['c', 't', 'b', 's']
1412 i = find_token(file.body, '\\begin_inset Frameless', i)
1415 j = find_end_of_inset(file.body, i)
1417 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1424 params = {'position':0, 'hor_pos':'c', 'has_inner_box':'1',
1425 'inner_pos':1, 'use_parbox':'0', 'width':'100col%',
1426 'special':'none', 'height':'1in',
1427 'height_special':'totalheight', 'collapsed':'false'}
1428 for key in params.keys():
1429 value = replace(get_value(file.body, key, i, j), '"', '')
1431 if key == 'position':
1432 # convert new to old position: 'position "t"' -> 0
1433 value = find_token(pos, value, 0)
1436 elif key == 'inner_pos':
1437 # convert inner position
1438 value = find_token(inner_pos, value, 0)
1443 j = del_token(file.body, key, i, j)
1446 # Convert to minipage or ERT?
1447 # Note that the inner_position and height parameters of a minipage
1448 # inset are ignored and not accessible for the user, although they
1449 # are present in the file format and correctly read in and written.
1450 # Therefore we convert to ERT if they do not have their LaTeX
1451 # defaults. These are:
1452 # - the value of "position" for "inner_pos"
1453 # - "\totalheight" for "height"
1454 if (params['use_parbox'] != '0' or
1455 params['has_inner_box'] != '1' or
1456 params['special'] != 'none' or
1457 params['height_special'] != 'totalheight' or
1458 len2value(params['height']) != 1.0):
1460 # Here we know that this box is not supported in file format 224.
1461 # Therefore we need to convert it to ERT. We can't simply convert
1462 # the beginning and end of the box to ERT, because the
1463 # box inset may contain layouts that are different from the
1464 # surrounding layout. After the conversion the contents of the
1465 # box inset is on the same level as the surrounding text, and
1466 # paragraph layouts and align parameters can get mixed up.
1468 # A possible solution for this problem:
1469 # Convert the box to a minipage and redefine the minipage
1470 # environment in ERT so that the original box is simulated.
1471 # For minipages we could do this in a way that the width and
1472 # position can still be set from LyX, but this did not work well.
1473 # This is not possible for parboxes either, so we convert the
1474 # original box to ERT, put the minipage inset inside the box
1475 # and redefine the minipage environment to be empty.
1477 # Commands that are independant of a particular box can go to
1479 # We need to define lyxtolyxrealminipage with 3 optional
1480 # arguments although LyX 1.3 uses only the first one.
1481 # Otherwise we will get LaTeX errors if this document is
1482 # converted to format 225 or above again (LyX 1.4 uses all
1483 # optional arguments).
1484 add_to_preamble(file,
1485 ['% Commands inserted by lyx2lyx for frameless boxes',
1486 '% Save the original minipage environment',
1487 '\\let\\lyxtolyxrealminipage\\minipage',
1488 '\\let\\endlyxtolyxrealminipage\\endminipage',
1489 '% Define an empty lyxtolyximinipage environment',
1490 '% with 3 optional arguments',
1491 '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1492 '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1493 ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1494 ' {\\end{lyxtolyxiiiminipage}}',
1495 '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1496 ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1497 ' {\\end{lyxtolyxiiminipage}}',
1498 '\\newenvironment{lyxtolyxminipage}[1][c]%',
1499 ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1500 ' {\\end{lyxtolyximinipage}}'])
1502 if params['use_parbox'] != '0':
1505 ert = '\\begin{lyxtolyxrealminipage}'
1507 # convert optional arguments only if not latex default
1508 if (pos[params['position']] != 'c' or
1509 inner_pos[params['inner_pos']] != pos[params['position']] or
1510 params['height_special'] != 'totalheight' or
1511 len2value(params['height']) != 1.0):
1512 ert = ert + '[' + pos[params['position']] + ']'
1513 if (inner_pos[params['inner_pos']] != pos[params['position']] or
1514 params['height_special'] != 'totalheight' or
1515 len2value(params['height']) != 1.0):
1516 ert = ert + '[' + convert_len(params['height'],
1517 params['height_special']) + ']'
1518 if inner_pos[params['inner_pos']] != pos[params['position']]:
1519 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1521 ert = ert + '{' + convert_len(params['width'],
1522 params['special']) + '}'
1524 if params['use_parbox'] != '0':
1526 ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1527 ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1530 i = insert_ert(file.body, i, 'Collapsed', ert, file.format + 1)
1531 j = j + i - old_i - 1
1533 file.body[i:i] = ['\\begin_inset Minipage',
1534 'position %d' % params['position'],
1537 'width "' + params['width'] + '"',
1538 'collapsed ' + params['collapsed']]
1542 # Restore the original minipage environment since we may have
1543 # minipages inside this box.
1544 # Start a new paragraph because the following may be nonstandard
1545 file.body[i:i] = ['\\layout Standard', '', '']
1548 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1549 ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1551 i = insert_ert(file.body, i, 'Collapsed', ert, file.format + 1)
1552 j = j + i - old_i - 1
1554 # Redefine the minipage end before the inset end.
1555 # Start a new paragraph because the previous may be nonstandard
1556 file.body[j:j] = ['\\layout Standard', '', '']
1558 ert = '\\let\\endminipage\\endlyxtolyxminipage'
1559 j = insert_ert(file.body, j, 'Collapsed', ert, file.format + 1)
1561 file.body.insert(j, '')
1564 # LyX writes '%\n' after each box. Therefore we need to end our
1565 # ERT with '%\n', too, since this may swallow a following space.
1566 if params['use_parbox'] != '0':
1569 ert = '\\end{lyxtolyxrealminipage}%\n'
1570 j = insert_ert(file.body, j, 'Collapsed', ert, file.format + 1)
1572 # We don't need to restore the original minipage after the inset
1573 # end because the scope of the redefinition is the original box.
1577 # Convert to minipage
1578 file.body[i:i] = ['\\begin_inset Minipage',
1579 'position %d' % params['position'],
1580 'inner_position %d' % params['inner_pos'],
1581 'height "' + params['height'] + '"',
1582 'width "' + params['width'] + '"',
1583 'collapsed ' + params['collapsed']]
1590 def convert_jurabib(file):
1591 i = find_token(file.header, '\\use_numerical_citations', 0)
1593 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1595 file.header.insert(i + 1, '\\use_jurabib 0')
1598 def revert_jurabib(file):
1599 i = find_token(file.header, '\\use_jurabib', 0)
1601 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1603 if get_value(file.header, '\\use_jurabib', 0) != "0":
1604 file.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1605 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1613 def convert_bibtopic(file):
1614 i = find_token(file.header, '\\use_jurabib', 0)
1616 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1618 file.header.insert(i + 1, '\\use_bibtopic 0')
1621 def revert_bibtopic(file):
1622 i = find_token(file.header, '\\use_bibtopic', 0)
1624 file.warning("Malformed lyx file: Missing '\\use_bibtopic'.")
1626 if get_value(file.header, '\\use_bibtopic', 0) != "0":
1627 file.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1628 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1635 def convert_float(file):
1638 i = find_token(file.body, '\\begin_inset Float', i)
1641 # Seach for a line starting 'wide'
1642 # If, however, we find a line starting '\begin_layout'
1643 # (_always_ present) then break with a warning message
1646 if (file.body[i][:4] == "wide"):
1647 file.body.insert(i + 1, 'sideways false')
1649 elif (file.body[i][:13] == "\\begin_layout"):
1650 file.warning("Malformed lyx file: Missing 'wide'.")
1656 def revert_float(file):
1659 i = find_token(file.body, '\\begin_inset Float', i)
1662 j = find_end_of_inset(file.body, i)
1664 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1667 if get_value(file.body, 'sideways', i, j) != "false":
1668 file.warning("Conversion of 'sideways true' not yet implemented.")
1669 # Don't remove 'sideways' so that people will get warnings by lyx
1672 del_token(file.body, 'sideways', i, j)
1676 def convert_graphics(file):
1677 """ Add extension to filenames of insetgraphics if necessary.
1681 i = find_token(file.body, "\\begin_inset Graphics", i)
1685 j = find_token2(file.body, "filename", i)
1689 filename = split(file.body[j])[1]
1690 absname = os.path.normpath(os.path.join(file.dir, filename))
1691 if file.input == stdin and not os.path.isabs(filename):
1692 # We don't know the directory and cannot check the file.
1693 # We could use a heuristic and take the current directory,
1694 # and we could try to find out if filename has an extension,
1695 # but that would be just guesses and could be wrong.
1696 file.warning("""Warning: Can not determine whether file
1698 needs an extension when reading from standard input.
1699 You may need to correct the file manually or run
1700 lyx2lyx again with the .lyx file as commandline argument.""" % filename)
1702 # This needs to be the same algorithm as in pre 233 insetgraphics
1703 if access(absname, F_OK):
1705 if access(absname + ".ps", F_OK):
1706 file.body[j] = replace(file.body[j], filename, filename + ".ps")
1708 if access(absname + ".eps", F_OK):
1709 file.body[j] = replace(file.body[j], filename, filename + ".eps")
1713 # Convert firstname and surname from styles -> char styles
1715 def convert_names(file):
1716 """ Convert in the docbook backend from firstname and surname style
1719 if file.backend != "docbook":
1725 i = find_token(file.body, "\\begin_layout Author", i)
1730 while file.body[i] == "":
1733 if file.body[i][:11] != "\\end_layout" or file.body[i+2][:13] != "\\begin_deeper":
1738 i = find_end_of( file.body, i+3, "\\begin_deeper","\\end_deeper")
1740 # something is really wrong, abort
1741 file.warning("Missing \\end_deeper, after style Author.")
1742 file.warning("Aborted attempt to parse FirstName and Surname.")
1744 firstname, surname = "", ""
1746 name = file.body[k:i]
1748 j = find_token(name, "\\begin_layout FirstName", 0)
1751 while(name[j] != "\\end_layout"):
1752 firstname = firstname + name[j]
1755 j = find_token(name, "\\begin_layout Surname", 0)
1758 while(name[j] != "\\end_layout"):
1759 surname = surname + name[j]
1763 del file.body[k+2:i+1]
1765 file.body[k-1:k-1] = ["", "",
1766 "\\begin_inset CharStyle Firstname",
1769 "\\begin_layout Standard",
1777 "\\begin_inset CharStyle Surname",
1780 "\\begin_layout Standard",
1789 def revert_names(file):
1790 """ Revert in the docbook backend from firstname and surname char style
1793 if file.backend != "docbook":
1798 # \use_natbib 1 \cite_engine <style>
1799 # \use_numerical_citations 0 -> where <style> is one of
1800 # \use_jurabib 0 "basic", "natbib_authoryear",
1801 # "natbib_numerical" or "jurabib"
1802 def convert_cite_engine(file):
1803 a = find_token(file.header, "\\use_natbib", 0)
1805 file.warning("Malformed lyx file: Missing '\\use_natbib'.")
1808 b = find_token(file.header, "\\use_numerical_citations", 0)
1809 if b == -1 or b != a+1:
1810 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1813 c = find_token(file.header, "\\use_jurabib", 0)
1814 if c == -1 or c != b+1:
1815 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1818 use_natbib = int(split(file.header[a])[1])
1819 use_numerical_citations = int(split(file.header[b])[1])
1820 use_jurabib = int(split(file.header[c])[1])
1822 cite_engine = "basic"
1824 if use_numerical_citations:
1825 cite_engine = "natbib_numerical"
1827 cite_engine = "natbib_authoryear"
1829 cite_engine = "jurabib"
1831 del file.header[a:c+1]
1832 file.header.insert(a, "\\cite_engine " + cite_engine)
1835 def revert_cite_engine(file):
1836 i = find_token(file.header, "\\cite_engine", 0)
1838 file.warning("Malformed lyx file: Missing '\\cite_engine'.")
1841 cite_engine = split(file.header[i])[1]
1846 if cite_engine == "natbib_numerical":
1849 elif cite_engine == "natbib_authoryear":
1851 elif cite_engine == "jurabib":
1855 file.header.insert(i, "\\use_jurabib " + use_jurabib)
1856 file.header.insert(i, "\\use_numerical_citations " + use_numerical)
1857 file.header.insert(i, "\\use_natbib " + use_natbib)
1863 def convert_paperpackage(file):
1864 i = find_token(file.header, "\\paperpackage", 0)
1868 packages = {'default':'none','a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
1869 if len(split(file.header[i])) > 1:
1870 paperpackage = split(file.header[i])[1]
1871 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1873 file.header[i] = file.header[i] + ' widemarginsa4'
1876 def revert_paperpackage(file):
1877 i = find_token(file.header, "\\paperpackage", 0)
1881 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
1882 'widemarginsa4':'', 'default': 'default'}
1883 if len(split(file.header[i])) > 1:
1884 paperpackage = split(file.header[i])[1]
1886 paperpackage = 'default'
1887 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1893 def convert_bullets(file):
1896 i = find_token(file.header, "\\bullet", i)
1899 if file.header[i][:12] == '\\bulletLaTeX':
1900 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1])
1903 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1]) +\
1904 ' ' + strip(file.header[i+2]) + ' ' + strip(file.header[i+3])
1906 del file.header[i+1:i + n]
1910 def revert_bullets(file):
1913 i = find_token(file.header, "\\bullet", i)
1916 if file.header[i][:12] == '\\bulletLaTeX':
1917 n = find(file.header[i], '"')
1919 file.warning("Malformed header.")
1922 file.header[i:i+1] = [file.header[i][:n-1],'\t' + file.header[i][n:], '\\end_bullet']
1925 frag = split(file.header[i])
1927 file.warning("Malformed header.")
1930 file.header[i:i+1] = [frag[0] + ' ' + frag[1],
1939 # \begin_header and \begin_document
1941 def add_begin_header(file):
1942 i = find_token(file.header, '\\lyxformat', 0)
1943 file.header.insert(i+1, '\\begin_header')
1944 file.header.insert(i+1, '\\begin_document')
1947 def remove_begin_header(file):
1948 i = find_token(file.header, "\\begin_document", 0)
1951 i = find_token(file.header, "\\begin_header", 0)
1957 # \begin_file.body and \end_file.body
1959 def add_begin_body(file):
1960 file.body.insert(0, '\\begin_body')
1961 file.body.insert(1, '')
1962 i = find_token(file.body, "\\end_document", 0)
1963 file.body.insert(i, '\\end_body')
1965 def remove_begin_body(file):
1966 i = find_token(file.body, "\\begin_body", 0)
1969 if not file.body[i]:
1971 i = find_token(file.body, "\\end_body", 0)
1979 def normalize_papersize(file):
1980 i = find_token(file.header, '\\papersize', 0)
1984 tmp = split(file.header[i])
1985 if tmp[1] == "Default":
1986 file.header[i] = '\\papersize default'
1988 if tmp[1] == "Custom":
1989 file.header[i] = '\\papersize custom'
1992 def denormalize_papersize(file):
1993 i = find_token(file.header, '\\papersize', 0)
1997 tmp = split(file.header[i])
1998 if tmp[1] == "custom":
1999 file.header[i] = '\\papersize Custom'
2003 # Strip spaces at end of command line
2005 def strip_end_space(file):
2006 for i in range(len(file.body)):
2007 if file.body[i][:1] == '\\':
2008 file.body[i] = strip(file.body[i])
2012 # Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes
2014 def use_x_boolean(file):
2015 bin2bool = {'0': 'false', '1': 'true'}
2016 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2017 i = find_token(file.header, use, 0)
2020 decompose = split(file.header[i])
2021 file.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
2024 def use_x_binary(file):
2025 bool2bin = {'false': '0', 'true': '1'}
2026 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2027 i = find_token(file.header, use, 0)
2030 decompose = split(file.header[i])
2031 file.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
2034 # Place all the paragraph parameters in their own line
2036 def normalize_paragraph_params(file):
2038 allowed_parameters = '\\paragraph_spacing', '\\noindent', '\\align', '\\labelwidthstring', "\\start_of_appendix", "\\leftindent"
2042 i = find_token(file.body, '\\begin_layout', i)
2048 if strip(body[i]) and split(body[i])[0] not in allowed_parameters:
2051 j = find(body[i],'\\', 1)
2054 body[i:i+1] = [strip(body[i][:j]), body[i][j:]]
2060 # Add/remove output_changes parameter
2062 def convert_output_changes (file):
2063 i = find_token(file.header, '\\tracking_changes', 0)
2065 file.warning("Malformed lyx file: Missing '\\tracking_changes'.")
2067 file.header.insert(i+1, '\\output_changes true')
2070 def revert_output_changes (file):
2071 i = find_token(file.header, '\\output_changes', 0)
2078 # Convert paragraph breaks and sanitize paragraphs
2080 def convert_ert_paragraphs(file):
2081 forbidden_settings = [
2082 # paragraph parameters
2083 '\\paragraph_spacing', '\\labelwidthstring',
2084 '\\start_of_appendix', '\\noindent',
2085 '\\leftindent', '\\align',
2087 '\\family', '\\series', '\\shape', '\\size',
2088 '\\emph', '\\numeric', '\\bar', '\\noun',
2089 '\\color', '\\lang']
2092 i = find_token(file.body, '\\begin_inset ERT', i)
2095 j = find_end_of_inset(file.body, i)
2097 file.warning("Malformed lyx file: Missing '\\end_inset'.")
2101 # convert non-standard paragraphs to standard
2104 k = find_token(file.body, "\\begin_layout", k, j)
2107 file.body[k] = "\\begin_layout Standard"
2110 # remove all paragraph parameters and font settings
2113 if (strip(file.body[k]) and
2114 split(file.body[k])[0] in forbidden_settings):
2120 # insert an empty paragraph before each paragraph but the first
2124 k = find_token(file.body, "\\begin_layout Standard", k, j)
2131 file.body[k:k] = ["\\begin_layout Standard", "",
2136 # convert \\newline to new paragraph
2139 k = find_token(file.body, "\\newline", k, j)
2142 file.body[k:k+1] = ["\\end_layout", "", "\\begin_layout Standard"]
2149 # Remove double paragraph breaks
2151 def revert_ert_paragraphs(file):
2154 i = find_token(file.body, '\\begin_inset ERT', i)
2157 j = find_end_of_inset(file.body, i)
2159 file.warning("Malformed lyx file: Missing '\\end_inset'.")
2163 # replace paragraph breaks with \newline
2166 k = find_token(file.body, "\\end_layout", k, j)
2167 l = find_token(file.body, "\\begin_layout", k, j)
2168 if k == -1 or l == -1:
2170 file.body[k:l+1] = ["\\newline"]
2174 # replace double \newlines with paragraph breaks
2177 k = find_token(file.body, "\\newline", k, j)
2181 while file.body[l] == "":
2183 if strip(file.body[l]) and split(file.body[l])[0] == "\\newline":
2184 file.body[k:l+1] = ["\\end_layout", "",
2185 "\\begin_layout Standard"]
2193 def convert_french(file):
2194 regexp = re.compile(r'^\\language\s+frenchb')
2195 i = find_re(file.header, regexp, 0)
2197 file.header[i] = "\\language french"
2199 # Change language in the document body
2200 regexp = re.compile(r'^\\lang\s+frenchb')
2203 i = find_re(file.body, regexp, i)
2206 file.body[i] = "\\lang french"
2210 def remove_paperpackage(file):
2211 i = find_token(file.header, '\\paperpackage', 0)
2216 paperpackage = split(file.header[i])[1]
2218 if paperpackage in ("a4", "a4wide", "widemarginsa4"):
2219 conv = {"a4":"\\usepackage{a4}","a4wide": "\\usepackage{a4wide}",
2220 "widemarginsa4": "\\usepackage[widemargins]{a4}"}
2221 # for compatibility we ensure it is the first entry in preamble
2222 file.preamble[0:0] = [conv[paperpackage]]
2226 i = find_token(file.header, '\\papersize', 0)
2228 file.header[i] = "\\papersize default"
2235 convert = [[222, [insert_tracking_changes, add_end_header]],
2236 [223, [remove_color_default, convert_spaces, convert_bibtex, remove_insetparent]],
2237 [224, [convert_external, convert_comment]],
2238 [225, [add_end_layout, layout2begin_layout, convert_end_document,
2239 convert_table_valignment_middle, convert_breaks]],
2240 [226, [convert_note]],
2241 [227, [convert_box]],
2242 [228, [convert_collapsable, convert_ert]],
2243 [229, [convert_minipage]],
2244 [230, [convert_jurabib]],
2245 [231, [convert_float]],
2246 [232, [convert_bibtopic]],
2247 [233, [convert_graphics, convert_names]],
2248 [234, [convert_cite_engine]],
2249 [235, [convert_paperpackage]],
2250 [236, [convert_bullets, add_begin_header, add_begin_body,
2251 normalize_papersize, strip_end_space]],
2252 [237, [use_x_boolean]],
2253 [238, [update_latexaccents]],
2254 [239, [normalize_paragraph_params]],
2255 [240, [convert_output_changes]],
2256 [241, [convert_ert_paragraphs]],
2257 [242, [convert_french]],
2258 [243, [remove_paperpackage]]]
2260 revert = [[242, []],
2262 [240, [revert_ert_paragraphs]],
2263 [239, [revert_output_changes]],
2266 [236, [use_x_binary]],
2267 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
2269 [234, [revert_paperpackage]],
2270 [233, [revert_cite_engine]],
2271 [232, [revert_names]],
2272 [231, [revert_bibtopic]],
2273 [230, [revert_float]],
2274 [229, [revert_jurabib]],
2276 [227, [revert_collapsable, revert_ert]],
2277 [226, [revert_box, revert_external_2]],
2278 [225, [revert_note]],
2279 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
2280 revert_valignment_middle, revert_breaks, convert_frameless_box]],
2281 [223, [revert_external_2, revert_comment, revert_eqref]],
2282 [222, [revert_spaces, revert_bibtex]],
2283 [221, [rm_end_header, rm_tracking_changes, rm_body_changes]]]
2286 if __name__ == "__main__":