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 regexp = re.compile(r'(.*)(\\InsetSpace\s+)(\S+)')
71 i = find_re(file.body, regexp, i)
74 space = regexp.match(file.body[i]).group(3)
75 prepend = regexp.match(file.body[i]).group(1)
77 file.body[i] = regexp.sub(prepend + '\\SpecialChar ~', file.body[i])
80 file.body[i] = regexp.sub(prepend, file.body[i])
81 file.body[i+1:i+1] = ''
82 if space == "\\space":
84 i = insert_ert(file.body, i+1, 'Collapsed', space, file.format - 1)
87 # \InsetSpace \, -> \InsetSpace \thinspace{}
88 # \InsetSpace \space -> \InsetSpace \space{}
90 def rename_spaces(file):
91 for i in range(len(file.body)):
92 file.body[i] = replace(file.body[i],"\\InsetSpace \\space","\\InsetSpace \\space{}")
93 file.body[i] = replace(file.body[i],"\\InsetSpace \,","\\InsetSpace \\thinspace{}")
96 def revert_space_names(file):
97 for i in range(len(file.body)):
98 file.body[i] = replace(file.body[i],"\\InsetSpace \\space{}","\\InsetSpace \\space")
99 file.body[i] = replace(file.body[i],"\\InsetSpace \\thinspace{}","\\InsetSpace \\,")
103 # equivalent to lyx::support::escape()
105 def lyx_support_escape(lab):
106 hexdigit = ['0', '1', '2', '3', '4', '5', '6', '7',
107 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
111 if o >= 128 or c == '=' or c == '%':
113 enc = enc + hexdigit[o >> 4]
114 enc = enc + hexdigit[o & 15]
121 # \begin_inset LatexCommand \eqref -> ERT
123 def revert_eqref(file):
124 regexp = re.compile(r'^\\begin_inset\s+LatexCommand\s+\\eqref')
127 i = find_re(file.body, regexp, i)
130 eqref = lyx_support_escape(regexp.sub("", file.body[i]))
131 file.body[i:i+1] = ["\\begin_inset ERT", "status Collapsed", "",
132 "\\layout Standard", "", "\\backslash ",
140 def convert_bibtex(file):
141 for i in range(len(file.body)):
142 file.body[i] = replace(file.body[i],"\\begin_inset LatexCommand \\BibTeX",
143 "\\begin_inset LatexCommand \\bibtex")
146 def revert_bibtex(file):
147 for i in range(len(file.body)):
148 file.body[i] = replace(file.body[i], "\\begin_inset LatexCommand \\bibtex",
149 "\\begin_inset LatexCommand \\BibTeX")
155 def remove_insetparent(file):
158 i = find_token(file.body, "\\begin_inset LatexCommand \\lyxparent", i)
167 def convert_external(file):
168 external_rexp = re.compile(r'\\begin_inset External ([^,]*),"([^"]*)",')
169 external_header = "\\begin_inset External"
172 i = find_token(file.body, external_header, i)
175 look = external_rexp.search(file.body[i])
178 args[0] = look.group(1)
179 args[1] = look.group(2)
180 #FIXME: if the previous search fails then warn
182 if args[0] == "RasterImage":
183 # Convert a RasterImage External Inset to a Graphics Inset.
184 top = "\\begin_inset Graphics"
186 filename = "\tfilename " + args[1]
187 file.body[i:i+1] = [top, filename]
190 # Convert the old External Inset format to the new.
191 top = external_header
192 template = "\ttemplate " + args[0]
194 filename = "\tfilename " + args[1]
195 file.body[i:i+1] = [top, template, filename]
198 file.body[i:i+1] = [top, template]
202 def revert_external_1(file):
203 external_header = "\\begin_inset External"
206 i = find_token(file.body, external_header, i)
210 template = split(file.body[i+1])
214 filename = split(file.body[i+1])
218 params = split(file.body[i+1])
220 if file.body[i+1]: del file.body[i+1]
222 file.body[i] = file.body[i] + " " + template[0]+ ', "' + filename[0] + '", " '+ join(params[1:]) + '"'
226 def revert_external_2(file):
227 draft_token = '\tdraft'
230 i = find_token(file.body, '\\begin_inset External', i)
233 j = find_end_of_inset(file.body, i + 1)
235 #this should not happen
237 k = find_token(file.body, draft_token, i+1, j-1)
238 if (k != -1 and len(draft_token) == len(file.body[k])):
246 def convert_comment(file):
248 comment = "\\layout Comment"
250 i = find_token(file.body, comment, i)
254 file.body[i:i+1] = ["\\layout Standard","","",
255 "\\begin_inset Comment",
262 i = find_token(file.body, "\\layout", i)
264 i = len(file.body) - 1
265 file.body[i:i] = ["\\end_inset","",""]
268 j = find_token(file.body, '\\begin_deeper', old_i, i)
269 if j == -1: j = i + 1
270 k = find_token(file.body, '\\begin_inset', old_i, i)
271 if k == -1: k = i + 1
276 i = find_end_of( file.body, i, "\\begin_deeper","\\end_deeper")
278 #This case should not happen
279 #but if this happens deal with it greacefully adding
280 #the missing \end_deeper.
281 i = len(file.body) - 1
282 file.body[i:i] = ["\end_deeper",""]
290 i = find_end_of( file.body, i, "\\begin_inset","\\end_inset")
292 #This case should not happen
293 #but if this happens deal with it greacefully adding
294 #the missing \end_inset.
295 i = len(file.body) - 1
296 file.body[i:i] = ["\\end_inset","","","\\end_inset","",""]
302 if find(file.body[i], comment) == -1:
303 file.body[i:i] = ["\\end_inset"]
306 file.body[i:i+1] = ["\\layout Standard"]
310 def revert_comment(file):
313 i = find_tokens(file.body, ["\\begin_inset Comment", "\\begin_inset Greyedout"], i)
317 file.body[i] = "\\begin_inset Note"
324 def add_end_layout(file):
325 i = find_token(file.body, '\\layout', 0)
331 struct_stack = ["\\layout"]
334 i = find_tokens(file.body, ["\\begin_inset", "\\end_inset", "\\layout",
335 "\\begin_deeper", "\\end_deeper", "\\the_end"], i)
338 token = split(file.body[i])[0]
340 file.warning("Truncated file.")
342 file.body.insert(i, '\\the_end')
345 if token == "\\begin_inset":
346 struct_stack.append(token)
350 if token == "\\end_inset":
351 tail = struct_stack.pop()
352 if tail == "\\layout":
353 file.body.insert(i,"")
354 file.body.insert(i,"\\end_layout")
356 #Check if it is the correct tag
361 if token == "\\layout":
362 tail = struct_stack.pop()
364 file.body.insert(i,"")
365 file.body.insert(i,"\\end_layout")
368 struct_stack.append(tail)
370 struct_stack.append(token)
373 if token == "\\begin_deeper":
374 file.body.insert(i,"")
375 file.body.insert(i,"\\end_layout")
377 struct_stack.append(token)
380 if token == "\\end_deeper":
381 if struct_stack[-1] == '\\layout':
382 file.body.insert(i, '\\end_layout')
389 file.body.insert(i, "")
390 file.body.insert(i, "\\end_layout")
394 def rm_end_layout(file):
397 i = find_token(file.body, '\\end_layout', i)
406 # Handle change tracking keywords
408 def insert_tracking_changes(file):
409 i = find_token(file.header, "\\tracking_changes", 0)
411 file.header.append("\\tracking_changes 0")
414 def rm_tracking_changes(file):
415 i = find_token(file.header, "\\author", 0)
419 i = find_token(file.header, "\\tracking_changes", 0)
425 def rm_body_changes(file):
428 i = find_token(file.body, "\\change_", i)
436 # \layout -> \begin_layout
438 def layout2begin_layout(file):
441 i = find_token(file.body, '\\layout', i)
445 file.body[i] = replace(file.body[i], '\\layout', '\\begin_layout')
449 def begin_layout2layout(file):
452 i = find_token(file.body, '\\begin_layout', i)
456 file.body[i] = replace(file.body[i], '\\begin_layout', '\\layout')
461 # valignment="center" -> valignment="middle"
463 def convert_valignment_middle(body, start, end):
464 for i in range(start, end):
465 if re.search('^<(column|cell) .*valignment="center".*>$', body[i]):
466 body[i] = replace(body[i], 'valignment="center"', 'valignment="middle"')
469 def convert_table_valignment_middle(file):
470 regexp = re.compile(r'^\\begin_inset\s+Tabular')
473 i = find_re(file.body, regexp, i)
476 j = find_end_of_inset(file.body, i + 1)
478 #this should not happen
479 convert_valignment_middle(file.body, i + 1, len(file.body))
481 convert_valignment_middle(file.body, i + 1, j)
485 def revert_table_valignment_middle(body, start, end):
486 for i in range(start, end):
487 if re.search('^<(column|cell) .*valignment="middle".*>$', body[i]):
488 body[i] = replace(body[i], 'valignment="middle"', 'valignment="center"')
491 def revert_valignment_middle(file):
492 regexp = re.compile(r'^\\begin_inset\s+Tabular')
495 i = find_re(file.body, regexp, i)
498 j = find_end_of_inset(file.body, i + 1)
500 #this should not happen
501 revert_table_valignment_middle(file.body, i + 1, len(file.body))
503 revert_table_valignment_middle(file.body, i + 1, j)
508 # \the_end -> \end_document
510 def convert_end_document(file):
511 i = find_token(file.body, "\\the_end", 0)
513 file.body.append("\\end_document")
515 file.body[i] = "\\end_document"
518 def revert_end_document(file):
519 i = find_token(file.body, "\\end_document", 0)
521 file.body.append("\\the_end")
523 file.body[i] = "\\the_end"
527 # Convert line and page breaks
530 #\line_top \line_bottom \pagebreak_top \pagebreak_bottom \added_space_top xxx \added_space_bottom yyy
534 #\begin layout Standard
540 #\begin layout Standard
547 #\begin_inset VSpace xxx
552 #\begin_inset VSpace xxx
559 def convert_breaks(file):
560 par_params = ('added_space_bottom', 'added_space_top', 'align',
561 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
562 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
564 font_attributes = ['\\family', '\\series', '\\shape', '\\emph',
565 '\\numeric', '\\bar', '\\noun', '\\color', '\\lang']
566 attribute_values = ['default', 'default', 'default', 'default',
567 'default', 'default', 'default', 'none', file.language]
570 i = find_token(file.body, "\\begin_layout", i)
573 layout = split(file.body[i])[1]
576 # Merge all paragraph parameters into a single line
577 # We cannot check for '\\' only because paragraphs may start e.g.
579 while file.body[i + 1][:1] == '\\' and split(file.body[i + 1][1:])[0] in par_params:
580 file.body[i] = file.body[i + 1] + ' ' + file.body[i]
583 line_top = find(file.body[i],"\\line_top")
584 line_bot = find(file.body[i],"\\line_bottom")
585 pb_top = find(file.body[i],"\\pagebreak_top")
586 pb_bot = find(file.body[i],"\\pagebreak_bottom")
587 vspace_top = find(file.body[i],"\\added_space_top")
588 vspace_bot = find(file.body[i],"\\added_space_bottom")
590 if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1:
593 # Do we have a nonstandard paragraph? We need to create new paragraphs
594 # if yes to avoid putting lyxline etc. inside of special environments.
595 # This is wrong for itemize and enumerate environments, but it is
596 # impossible to convert these correctly.
597 # We want to avoid new paragraphs if possible becauase we want to
598 # inherit font sizes.
600 if (layout != "Standard" or find(file.body[i],"\\align") != -1 or
601 find(file.body[i],"\\labelwidthstring") != -1 or
602 find(file.body[i],"\\noindent") != -1):
605 # get the font size of the beginning of this paragraph, since we need
606 # it for the lyxline inset
608 while not is_nonempty_line(file.body[j]):
611 if find(file.body[j], "\\size") != -1:
612 size_top = split(file.body[j])[1]
614 for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom":
615 file.body[i] = replace(file.body[i], tag, "")
618 # the position could be change because of the removal of other
619 # paragraph properties above
620 vspace_top = find(file.body[i],"\\added_space_top")
621 tmp_list = split(file.body[i][vspace_top:])
622 vspace_top_value = tmp_list[1]
623 file.body[i] = file.body[i][:vspace_top] + join(tmp_list[2:])
626 # the position could be change because of the removal of other
627 # paragraph properties above
628 vspace_bot = find(file.body[i],"\\added_space_bottom")
629 tmp_list = split(file.body[i][vspace_bot:])
630 vspace_bot_value = tmp_list[1]
631 file.body[i] = file.body[i][:vspace_bot] + join(tmp_list[2:])
633 file.body[i] = strip(file.body[i])
636 # Create an empty paragraph or paragraph fragment for line and
637 # page break that belong above the paragraph
638 if pb_top !=-1 or line_top != -1 or vspace_top != -1:
640 paragraph_above = list()
642 # We need to create an extra paragraph for nonstandard environments
643 paragraph_above = ['\\begin_layout Standard', '']
646 paragraph_above.extend(['\\newpage ',''])
649 paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','',''])
653 paragraph_above.extend(['\\size ' + size_top + ' '])
654 # We need an additional vertical space of -\parskip.
655 # We can't use the vspace inset because it does not know \parskip.
656 paragraph_above.extend(['\\lyxline ', '', ''])
657 insert_ert(paragraph_above, len(paragraph_above) - 1, 'Collapsed',
658 '\\vspace{-1\\parskip}\n', file.format + 1)
659 paragraph_above.extend([''])
662 paragraph_above.extend(['\\end_layout ',''])
663 # insert new paragraph above the current paragraph
664 file.body[i-2:i-2] = paragraph_above
666 # insert new lines at the beginning of the current paragraph
667 file.body[i:i] = paragraph_above
669 i = i + len(paragraph_above)
671 # Ensure that nested style are converted later.
672 k = find_end_of(file.body, i, "\\begin_layout", "\\end_layout")
677 if pb_bot !=-1 or line_bot != -1 or vspace_bot != -1:
679 # get the font size of the end of this paragraph
683 if find(file.body[j], "\\size") != -1:
684 size_bot = split(file.body[j])[1]
686 elif find(file.body[j], "\\begin_inset") != -1:
688 j = find_end_of_inset(file.body, j)
692 paragraph_below = list()
694 # We need to create an extra paragraph for nonstandard environments
695 paragraph_below = ['', '\\begin_layout Standard', '']
697 for a in range(len(font_attributes)):
698 if find_token(file.body, font_attributes[a], i, k) != -1:
699 paragraph_below.extend([font_attributes[a] + ' ' + attribute_values[a]])
702 if nonstandard and size_bot != '':
703 paragraph_below.extend(['\\size ' + size_bot + ' '])
704 paragraph_below.extend(['\\lyxline ',''])
706 paragraph_below.extend(['\\size default '])
709 paragraph_below.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','',''])
712 paragraph_below.extend(['\\newpage ',''])
715 paragraph_below.extend(['\\end_layout '])
716 # insert new paragraph below the current paragraph
717 file.body[k+1:k+1] = paragraph_below
719 # insert new lines at the end of the current paragraph
720 file.body[k:k] = paragraph_below
726 def convert_note(file):
729 i = find_tokens(file.body, ["\\begin_inset Note",
730 "\\begin_inset Comment",
731 "\\begin_inset Greyedout"], i)
735 file.body[i] = file.body[i][0:13] + 'Note ' + file.body[i][13:]
739 def revert_note(file):
740 note_header = "\\begin_inset Note "
743 i = find_token(file.body, note_header, i)
747 file.body[i] = "\\begin_inset " + file.body[i][len(note_header):]
754 def convert_box(file):
757 i = find_tokens(file.body, ["\\begin_inset Boxed",
758 "\\begin_inset Doublebox",
759 "\\begin_inset Frameless",
760 "\\begin_inset ovalbox",
761 "\\begin_inset Ovalbox",
762 "\\begin_inset Shadowbox"], i)
766 file.body[i] = file.body[i][0:13] + 'Box ' + file.body[i][13:]
770 def revert_box(file):
771 box_header = "\\begin_inset Box "
774 i = find_token(file.body, box_header, i)
778 file.body[i] = "\\begin_inset " + file.body[i][len(box_header):]
785 def convert_collapsable(file):
788 i = find_tokens(file.body, ["\\begin_inset Box",
789 "\\begin_inset Branch",
790 "\\begin_inset CharStyle",
791 "\\begin_inset Float",
792 "\\begin_inset Foot",
793 "\\begin_inset Marginal",
794 "\\begin_inset Note",
795 "\\begin_inset OptArg",
796 "\\begin_inset Wrap"], i)
800 # Seach for a line starting 'collapsed'
801 # If, however, we find a line starting '\begin_layout'
802 # (_always_ present) then break with a warning message
805 if (file.body[i] == "collapsed false"):
806 file.body[i] = "status open"
808 elif (file.body[i] == "collapsed true"):
809 file.body[i] = "status collapsed"
811 elif (file.body[i][:13] == "\\begin_layout"):
812 file.warning("Malformed LyX file: Missing 'collapsed'.")
819 def revert_collapsable(file):
822 i = find_tokens(file.body, ["\\begin_inset Box",
823 "\\begin_inset Branch",
824 "\\begin_inset CharStyle",
825 "\\begin_inset Float",
826 "\\begin_inset Foot",
827 "\\begin_inset Marginal",
828 "\\begin_inset Note",
829 "\\begin_inset OptArg",
830 "\\begin_inset Wrap"], 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] = "collapsed false"
842 elif (file.body[i] == "status collapsed" or
843 file.body[i] == "status inlined"):
844 file.body[i] = "collapsed true"
846 elif (file.body[i][:13] == "\\begin_layout"):
847 file.warning("Malformed LyX file: Missing 'status'.")
857 def convert_ert(file):
860 i = find_token(file.body, "\\begin_inset ERT", i)
864 # Seach for a line starting 'status'
865 # If, however, we find a line starting '\begin_layout'
866 # (_always_ present) then break with a warning message
869 if (file.body[i] == "status Open"):
870 file.body[i] = "status open"
872 elif (file.body[i] == "status Collapsed"):
873 file.body[i] = "status collapsed"
875 elif (file.body[i] == "status Inlined"):
876 file.body[i] = "status inlined"
878 elif (file.body[i][:13] == "\\begin_layout"):
879 file.warning("Malformed LyX file: Missing 'status'.")
886 def revert_ert(file):
889 i = find_token(file.body, "\\begin_inset ERT", i)
893 # Seach for a line starting 'status'
894 # If, however, we find a line starting '\begin_layout'
895 # (_always_ present) then break with a warning message
898 if (file.body[i] == "status open"):
899 file.body[i] = "status Open"
901 elif (file.body[i] == "status collapsed"):
902 file.body[i] = "status Collapsed"
904 elif (file.body[i] == "status inlined"):
905 file.body[i] = "status Inlined"
907 elif (file.body[i][:13] == "\\begin_layout"):
908 file.warning("Malformed LyX file : Missing 'status'.")
918 def convert_minipage(file):
919 """ Convert minipages to the box inset.
920 We try to use the same order of arguments as lyx does.
923 inner_pos = ["c","t","b","s"]
927 i = find_token(file.body, "\\begin_inset Minipage", i)
931 file.body[i] = "\\begin_inset Box Frameless"
934 # convert old to new position using the pos list
935 if file.body[i][:8] == "position":
936 file.body[i] = 'position "%s"' % pos[int(file.body[i][9])]
938 file.body.insert(i, 'position "%s"' % pos[0])
941 file.body.insert(i, 'hor_pos "c"')
943 file.body.insert(i, 'has_inner_box 1')
946 # convert the inner_position
947 if file.body[i][:14] == "inner_position":
948 file.body[i] = 'inner_pos "%s"' % inner_pos[int(file.body[i][15])]
950 file.body.insert('inner_pos "%s"' % inner_pos[0])
953 # We need this since the new file format has a height and width
954 # in a different order.
955 if file.body[i][:6] == "height":
956 height = file.body[i][6:]
957 # test for default value of 221 and convert it accordingly
958 if height == ' "0pt"' or height == ' "0"':
964 if file.body[i][:5] == "width":
965 width = file.body[i][5:]
970 if file.body[i][:9] == "collapsed":
971 if file.body[i][9:] == "true":
979 file.body.insert(i, 'use_parbox 0')
981 file.body.insert(i, 'width' + width)
983 file.body.insert(i, 'special "none"')
985 file.body.insert(i, 'height' + height)
987 file.body.insert(i, 'height_special "totalheight"')
989 file.body.insert(i, 'status ' + status)
993 # -------------------------------------------------------------------------------------------
994 # Convert backslashes and '\n' into valid ERT code, append the converted
995 # text to body[i] and return the (maybe incremented) line index i
996 def convert_ertbackslash(body, i, ert, format):
999 body[i] = body[i] + '\\backslash '
1004 body[i+1:i+1] = ['\\newline ', '']
1007 body[i+1:i+1] = ['\\end_layout', '', '\\begin_layout Standard', '']
1010 body[i] = body[i] + c
1014 # Converts lines in ERT code to LaTeX
1015 # The surrounding \begin_layout ... \end_layout pair must not be included
1016 def ert2latex(lines, format):
1017 backslash = re.compile(r'\\backslash\s*$')
1018 newline = re.compile(r'\\newline\s*$')
1020 begin_layout = re.compile(r'\\layout\s*\S+$')
1022 begin_layout = re.compile(r'\\begin_layout\s*\S+$')
1023 end_layout = re.compile(r'\\end_layout\s*$')
1025 for i in range(len(lines)):
1026 line = backslash.sub('\\\\', lines[i])
1028 if begin_layout.match(line):
1031 line = newline.sub('\n', line)
1033 if begin_layout.match(line):
1035 if format > 224 and end_layout.match(line):
1041 # get all paragraph parameters. They can be all on one line or on several lines.
1042 # lines[i] must be the first parameter line
1043 def get_par_params(lines, i):
1044 par_params = ('added_space_bottom', 'added_space_top', 'align',
1045 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
1046 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
1047 'start_of_appendix')
1048 # We cannot check for '\\' only because paragraphs may start e.g.
1049 # with '\\backslash'
1051 while lines[i][:1] == '\\' and split(lines[i][1:])[0] in par_params:
1052 params = params + ' ' + strip(lines[i])
1054 return strip(params)
1057 # convert LyX font size to LaTeX fontsize
1058 def lyxsize2latexsize(lyxsize):
1059 sizes = {"tiny" : "tiny", "scriptsize" : "scriptsize",
1060 "footnotesize" : "footnotesize", "small" : "small",
1061 "normal" : "normalsize", "large" : "large", "larger" : "Large",
1062 "largest" : "LARGE", "huge" : "huge", "giant" : "Huge"}
1063 if lyxsize in sizes:
1064 return '\\' + sizes[lyxsize]
1068 # Change vspace insets, page breaks and lyxlines to paragraph options
1069 # (if possible) or ERT
1070 def revert_breaks(file):
1072 # Get default spaceamount
1073 i = find_token(file.header, '\\defskip', 0)
1075 defskipamount = 'medskip'
1077 defskipamount = split(file.header[i])[1]
1079 keys = {"\\begin_inset" : "vspace", "\\lyxline" : "lyxline",
1080 "\\newpage" : "newpage"}
1081 keywords_top = {"vspace" : "\\added_space_top", "lyxline" : "\\line_top",
1082 "newpage" : "\\pagebreak_top"}
1083 keywords_bot = {"vspace" : "\\added_space_bottom", "lyxline" : "\\line_bottom",
1084 "newpage" : "\\pagebreak_bottom"}
1085 tokens = ["\\begin_inset VSpace", "\\lyxline", "\\newpage"]
1087 # Convert the insets
1090 i = find_tokens(file.body, tokens, i)
1094 # Are we at the beginning of a paragraph?
1096 this_par = get_paragraph(file.body, i, file.format - 1)
1097 start = this_par + 1
1098 params = get_par_params(file.body, start)
1100 # Paragraph parameters may be on one or more lines.
1101 # Find the start of the real paragraph text.
1102 while file.body[start][:1] == '\\' and split(file.body[start])[0] in params:
1104 for k in range(start, i):
1105 if find(file.body[k], "\\size") != -1:
1107 size = split(file.body[k])[1]
1108 elif is_nonempty_line(file.body[k]):
1111 # Find the end of the real paragraph text.
1112 next_par = get_next_paragraph(file.body, i, file.format - 1)
1114 file.warning("Malformed LyX file: Missing next paragraph.")
1118 # first line of our insets
1120 # last line of our insets
1121 inset_end = inset_start
1122 # Are we at the end of a paragraph?
1124 # start and end line numbers to delete if we convert this inset
1126 # is this inset a lyxline above a paragraph?
1128 # raw inset information
1130 # name of this inset
1132 # font size of this inset
1135 # Detect subsequent lyxline, vspace and pagebreak insets created by convert_breaks()
1139 if find_tokens(file.body, tokens, k) == k:
1141 lines.append(split(file.body[k]))
1142 insets.append(keys[lines[n][0]])
1143 del_lines.append([k, k])
1148 elif find(file.body[k], "\\size") != -1:
1150 size = split(file.body[k])[1]
1151 elif find_token(file.body, "\\begin_inset ERT", k) == k:
1152 ert_begin = find_token(file.body, "\\layout", k) + 1
1154 file.warning("Malformed LyX file: Missing '\\layout'.")
1156 ert_end = find_end_of_inset(file.body, k)
1158 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1160 ert = ert2latex(file.body[ert_begin:ert_end], file.format - 1)
1161 if (n > 0 and insets[n - 1] == "lyxline" and
1162 ert == '\\vspace{-1\\parskip}\n'):
1163 # vspace ERT created by convert_breaks() for top lyxline
1165 del_lines[n - 1][1] = ert_end
1171 elif (n > 0 and insets[n - 1] == "vspace" and
1172 find_token(file.body, "\\end_inset", k) == k):
1173 # ignore end of vspace inset
1174 del_lines[n - 1][1] = k
1176 elif is_nonempty_line(file.body[k]):
1181 # Determine space amount for vspace insets
1182 spaceamount = list()
1185 if insets[k] == "vspace":
1186 spaceamount.append(lines[k][2])
1187 arguments.append(' ' + spaceamount[k] + ' ')
1189 spaceamount.append('')
1190 arguments.append(' ')
1192 # Can we convert to top paragraph parameters?
1194 if ((n == 3 and insets[0] == "newpage" and insets[1] == "vspace" and
1195 insets[2] == "lyxline" and top[2]) or
1197 ((insets[0] == "newpage" and insets[1] == "vspace") or
1198 (insets[0] == "newpage" and insets[1] == "lyxline" and top[1]) or
1199 (insets[0] == "vspace" and insets[1] == "lyxline" and top[1]))) or
1200 (n == 1 and insets[0] == "lyxline" and top[0])):
1201 # These insets have been created before a paragraph by
1205 # Can we convert to bottom paragraph parameters?
1207 if ((n == 3 and insets[0] == "lyxline" and not top[0] and
1208 insets[1] == "vspace" and insets[2] == "newpage") or
1210 ((insets[0] == "lyxline" and not top[0] and insets[1] == "vspace") or
1211 (insets[0] == "lyxline" and not top[0] and insets[1] == "newpage") or
1212 (insets[0] == "vspace" and insets[1] == "newpage"))) or
1213 (n == 1 and insets[0] == "lyxline" and not top[0])):
1214 # These insets have been created after a paragraph by
1218 if paragraph_start and paragraph_end:
1219 # We are in a paragraph of our own.
1220 # We must not delete this paragraph if it has parameters
1222 # First try to merge with the previous paragraph.
1223 # We try the previous paragraph first because we would
1224 # otherwise need ERT for two subsequent vspaces.
1225 prev_par = get_paragraph(file.body, this_par - 1, file.format - 1) + 1
1226 if prev_par > 0 and not before:
1227 prev_params = get_par_params(file.body, prev_par + 1)
1229 # determine font size
1230 prev_size = "normal"
1232 while file.body[k][:1] == '\\' and split(file.body[k])[0] in prev_params:
1235 if find(file.body[k], "\\size") != -1:
1236 prev_size = split(file.body[k])[1]
1238 elif find(file.body[k], "\\begin_inset") != -1:
1240 k = find_end_of_inset(file.body, k)
1241 elif is_nonempty_line(file.body[k]):
1245 if (keywords_bot[insets[k]] in prev_params or
1246 (insets[k] == "lyxline" and sizes[k] != prev_size)):
1251 file.body.insert(prev_par + 1,
1252 keywords_bot[insets[k]] + arguments[k])
1253 del file.body[this_par+n:next_par-1+n]
1256 # Then try next paragraph
1257 if next_par > 0 and not after:
1258 next_params = get_par_params(file.body, next_par + 1)
1260 while file.body[k][:1] == '\\' and split(file.body[k])[0] in next_params:
1262 # determine font size
1263 next_size = "normal"
1266 if find(file.body[k], "\\size") != -1:
1267 next_size = split(file.body[k])[1]
1269 elif is_nonempty_line(file.body[k]):
1273 if (keywords_top[insets[k]] in next_params or
1274 (insets[k] == "lyxline" and sizes[k] != next_size)):
1279 file.body.insert(next_par + 1,
1280 keywords_top[insets[k]] + arguments[k])
1281 del file.body[this_par:next_par-1]
1284 elif paragraph_start or paragraph_end:
1285 # Convert to paragraph formatting if we are at the beginning or end
1286 # of a paragraph and the resulting paragraph would not be empty
1287 # The order is important: del and insert invalidate some indices
1289 keywords = keywords_top
1291 keywords = keywords_bot
1294 if keywords[insets[k]] in params:
1299 file.body.insert(this_par + 1,
1300 keywords[insets[k]] + arguments[k])
1301 for j in range(k, n):
1302 del_lines[j][0] = del_lines[j][0] + 1
1303 del_lines[j][1] = del_lines[j][1] + 1
1304 del file.body[del_lines[k][0]:del_lines[k][1]+1]
1305 deleted = del_lines[k][1] - del_lines[k][0] + 1
1306 for j in range(k + 1, n):
1307 del_lines[j][0] = del_lines[j][0] - deleted
1308 del_lines[j][1] = del_lines[j][1] - deleted
1312 # Convert the first inset to ERT.
1313 # The others are converted in the next loop runs (if they exist)
1314 if insets[0] == "vspace":
1315 file.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
1316 '\\layout Standard', '', '\\backslash ']
1318 if spaceamount[0][-1] == '*':
1319 spaceamount[0] = spaceamount[0][:-1]
1324 # Replace defskip by the actual value
1325 if spaceamount[0] == 'defskip':
1326 spaceamount[0] = defskipamount
1328 # LaTeX does not know \\smallskip* etc
1330 if spaceamount[0] == 'smallskip':
1331 spaceamount[0] = '\\smallskipamount'
1332 elif spaceamount[0] == 'medskip':
1333 spaceamount[0] = '\\medskipamount'
1334 elif spaceamount[0] == 'bigskip':
1335 spaceamount[0] = '\\bigskipamount'
1336 elif spaceamount[0] == 'vfill':
1337 spaceamount[0] = '\\fill'
1339 # Finally output the LaTeX code
1340 if (spaceamount[0] == 'smallskip' or spaceamount[0] == 'medskip' or
1341 spaceamount[0] == 'bigskip' or spaceamount[0] == 'vfill'):
1342 file.body.insert(i, spaceamount[0] + '{}')
1345 file.body.insert(i, 'vspace*{')
1347 file.body.insert(i, 'vspace{')
1348 i = convert_ertbackslash(file.body, i, spaceamount[0], file.format - 1)
1349 file.body[i] = file.body[i] + '}'
1351 elif insets[0] == "lyxline":
1353 latexsize = lyxsize2latexsize(size)
1355 file.warning("Could not convert LyX fontsize '%s' to LaTeX font size." % size)
1356 latexsize = '\\normalsize'
1357 i = insert_ert(file.body, i, 'Collapsed',
1358 '\\lyxline{%s}' % latexsize,
1360 # We use \providecommand so that we don't get an error if native
1361 # lyxlines are used (LyX writes first its own preamble and then
1362 # the user specified one)
1363 add_to_preamble(file,
1364 ['% Commands inserted by lyx2lyx for lyxlines',
1365 '\\providecommand{\\lyxline}[1]{',
1366 ' {#1 \\vspace{1ex} \\hrule width \\columnwidth \\vspace{1ex}}'
1368 elif insets[0] == "newpage":
1370 i = insert_ert(file.body, i, 'Collapsed', '\\newpage{}',
1374 # Convert a LyX length into a LaTeX length
1375 def convert_len(len, special):
1376 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
1377 "page%":"\\pagewidth", "line%":"\\linewidth",
1378 "theight%":"\\textheight", "pheight%":"\\pageheight"}
1380 # Convert special lengths
1381 if special != 'none':
1382 len = '%f\\' % len2value(len) + special
1384 # Convert LyX units to LaTeX units
1385 for unit in units.keys():
1386 if find(len, unit) != -1:
1387 len = '%f' % (len2value(len) / 100) + units[unit]
1393 # Convert a LyX length into valid ERT code and append it to body[i]
1394 # Return the (maybe incremented) line index i
1395 def convert_ertlen(body, i, len, special, format):
1396 # Convert backslashes and insert the converted length into body
1397 return convert_ertbackslash(body, i, convert_len(len, special), format)
1400 # Return the value of len without the unit in numerical form
1402 result = re.search('([+-]?[0-9.]+)', len)
1404 return float(result.group(1))
1405 # No number means 1.0
1409 # Convert text to ERT and insert it at body[i]
1410 # Return the index of the line after the inserted ERT
1411 def insert_ert(body, i, status, text, format):
1412 body[i:i] = ['\\begin_inset ERT', 'status ' + status, '']
1415 body[i:i] = ['\\layout Standard', '']
1417 body[i:i] = ['\\begin_layout Standard', '']
1418 i = i + 1 # i points now to the just created empty line
1419 i = convert_ertbackslash(body, i, text, format) + 1
1421 body[i:i] = ['\\end_layout']
1423 body[i:i] = ['', '\\end_inset', '']
1428 # Add text to the preamble if it is not already there.
1429 # Only the first line is checked!
1430 def add_to_preamble(file, text):
1431 if find_token(file.preamble, text[0], 0) != -1:
1434 file.preamble.extend(text)
1437 def convert_frameless_box(file):
1438 pos = ['t', 'c', 'b']
1439 inner_pos = ['c', 't', 'b', 's']
1442 i = find_token(file.body, '\\begin_inset Frameless', i)
1445 j = find_end_of_inset(file.body, i)
1447 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1454 params = {'position':0, 'hor_pos':'c', 'has_inner_box':'1',
1455 'inner_pos':1, 'use_parbox':'0', 'width':'100col%',
1456 'special':'none', 'height':'1in',
1457 'height_special':'totalheight', 'collapsed':'false'}
1458 for key in params.keys():
1459 value = replace(get_value(file.body, key, i, j), '"', '')
1461 if key == 'position':
1462 # convert new to old position: 'position "t"' -> 0
1463 value = find_token(pos, value, 0)
1466 elif key == 'inner_pos':
1467 # convert inner position
1468 value = find_token(inner_pos, value, 0)
1473 j = del_token(file.body, key, i, j)
1476 # Convert to minipage or ERT?
1477 # Note that the inner_position and height parameters of a minipage
1478 # inset are ignored and not accessible for the user, although they
1479 # are present in the file format and correctly read in and written.
1480 # Therefore we convert to ERT if they do not have their LaTeX
1481 # defaults. These are:
1482 # - the value of "position" for "inner_pos"
1483 # - "\totalheight" for "height"
1484 if (params['use_parbox'] != '0' or
1485 params['has_inner_box'] != '1' or
1486 params['special'] != 'none' or
1487 params['height_special'] != 'totalheight' or
1488 len2value(params['height']) != 1.0):
1490 # Here we know that this box is not supported in file format 224.
1491 # Therefore we need to convert it to ERT. We can't simply convert
1492 # the beginning and end of the box to ERT, because the
1493 # box inset may contain layouts that are different from the
1494 # surrounding layout. After the conversion the contents of the
1495 # box inset is on the same level as the surrounding text, and
1496 # paragraph layouts and align parameters can get mixed up.
1498 # A possible solution for this problem:
1499 # Convert the box to a minipage and redefine the minipage
1500 # environment in ERT so that the original box is simulated.
1501 # For minipages we could do this in a way that the width and
1502 # position can still be set from LyX, but this did not work well.
1503 # This is not possible for parboxes either, so we convert the
1504 # original box to ERT, put the minipage inset inside the box
1505 # and redefine the minipage environment to be empty.
1507 # Commands that are independant of a particular box can go to
1509 # We need to define lyxtolyxrealminipage with 3 optional
1510 # arguments although LyX 1.3 uses only the first one.
1511 # Otherwise we will get LaTeX errors if this document is
1512 # converted to format 225 or above again (LyX 1.4 uses all
1513 # optional arguments).
1514 add_to_preamble(file,
1515 ['% Commands inserted by lyx2lyx for frameless boxes',
1516 '% Save the original minipage environment',
1517 '\\let\\lyxtolyxrealminipage\\minipage',
1518 '\\let\\endlyxtolyxrealminipage\\endminipage',
1519 '% Define an empty lyxtolyximinipage environment',
1520 '% with 3 optional arguments',
1521 '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1522 '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1523 ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1524 ' {\\end{lyxtolyxiiiminipage}}',
1525 '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1526 ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1527 ' {\\end{lyxtolyxiiminipage}}',
1528 '\\newenvironment{lyxtolyxminipage}[1][c]%',
1529 ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1530 ' {\\end{lyxtolyximinipage}}'])
1532 if params['use_parbox'] != '0':
1535 ert = '\\begin{lyxtolyxrealminipage}'
1537 # convert optional arguments only if not latex default
1538 if (pos[params['position']] != 'c' or
1539 inner_pos[params['inner_pos']] != pos[params['position']] or
1540 params['height_special'] != 'totalheight' or
1541 len2value(params['height']) != 1.0):
1542 ert = ert + '[' + pos[params['position']] + ']'
1543 if (inner_pos[params['inner_pos']] != pos[params['position']] or
1544 params['height_special'] != 'totalheight' or
1545 len2value(params['height']) != 1.0):
1546 ert = ert + '[' + convert_len(params['height'],
1547 params['height_special']) + ']'
1548 if inner_pos[params['inner_pos']] != pos[params['position']]:
1549 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1551 ert = ert + '{' + convert_len(params['width'],
1552 params['special']) + '}'
1554 if params['use_parbox'] != '0':
1556 ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1557 ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1560 i = insert_ert(file.body, i, 'Collapsed', ert, file.format + 1)
1561 j = j + i - old_i - 1
1563 file.body[i:i] = ['\\begin_inset Minipage',
1564 'position %d' % params['position'],
1567 'width "' + params['width'] + '"',
1568 'collapsed ' + params['collapsed']]
1572 # Restore the original minipage environment since we may have
1573 # minipages inside this box.
1574 # Start a new paragraph because the following may be nonstandard
1575 file.body[i:i] = ['\\layout Standard', '', '']
1578 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1579 ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1581 i = insert_ert(file.body, i, 'Collapsed', ert, file.format + 1)
1582 j = j + i - old_i - 1
1584 # Redefine the minipage end before the inset end.
1585 # Start a new paragraph because the previous may be nonstandard
1586 file.body[j:j] = ['\\layout Standard', '', '']
1588 ert = '\\let\\endminipage\\endlyxtolyxminipage'
1589 j = insert_ert(file.body, j, 'Collapsed', ert, file.format + 1)
1591 file.body.insert(j, '')
1594 # LyX writes '%\n' after each box. Therefore we need to end our
1595 # ERT with '%\n', too, since this may swallow a following space.
1596 if params['use_parbox'] != '0':
1599 ert = '\\end{lyxtolyxrealminipage}%\n'
1600 j = insert_ert(file.body, j, 'Collapsed', ert, file.format + 1)
1602 # We don't need to restore the original minipage after the inset
1603 # end because the scope of the redefinition is the original box.
1607 # Convert to minipage
1608 file.body[i:i] = ['\\begin_inset Minipage',
1609 'position %d' % params['position'],
1610 'inner_position %d' % params['inner_pos'],
1611 'height "' + params['height'] + '"',
1612 'width "' + params['width'] + '"',
1613 'collapsed ' + params['collapsed']]
1620 def convert_jurabib(file):
1621 i = find_token(file.header, '\\use_numerical_citations', 0)
1623 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1625 file.header.insert(i + 1, '\\use_jurabib 0')
1628 def revert_jurabib(file):
1629 i = find_token(file.header, '\\use_jurabib', 0)
1631 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1633 if get_value(file.header, '\\use_jurabib', 0) != "0":
1634 file.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1635 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1643 def convert_bibtopic(file):
1644 i = find_token(file.header, '\\use_jurabib', 0)
1646 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1648 file.header.insert(i + 1, '\\use_bibtopic 0')
1651 def revert_bibtopic(file):
1652 i = find_token(file.header, '\\use_bibtopic', 0)
1654 file.warning("Malformed lyx file: Missing '\\use_bibtopic'.")
1656 if get_value(file.header, '\\use_bibtopic', 0) != "0":
1657 file.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1658 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1665 def convert_float(file):
1668 i = find_token(file.body, '\\begin_inset Float', i)
1671 # Seach for a line starting 'wide'
1672 # If, however, we find a line starting '\begin_layout'
1673 # (_always_ present) then break with a warning message
1676 if (file.body[i][:4] == "wide"):
1677 file.body.insert(i + 1, 'sideways false')
1679 elif (file.body[i][:13] == "\\begin_layout"):
1680 file.warning("Malformed lyx file: Missing 'wide'.")
1686 def revert_float(file):
1689 i = find_token(file.body, '\\begin_inset Float', i)
1692 j = find_end_of_inset(file.body, i)
1694 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1697 if get_value(file.body, 'sideways', i, j) != "false":
1698 file.warning("Conversion of 'sideways true' not yet implemented.")
1699 # Don't remove 'sideways' so that people will get warnings by lyx
1702 del_token(file.body, 'sideways', i, j)
1706 def convert_graphics(file):
1707 """ Add extension to filenames of insetgraphics if necessary.
1711 i = find_token(file.body, "\\begin_inset Graphics", i)
1715 j = find_token2(file.body, "filename", i)
1719 filename = split(file.body[j])[1]
1720 absname = os.path.normpath(os.path.join(file.dir, filename))
1721 if file.input == stdin and not os.path.isabs(filename):
1722 # We don't know the directory and cannot check the file.
1723 # We could use a heuristic and take the current directory,
1724 # and we could try to find out if filename has an extension,
1725 # but that would be just guesses and could be wrong.
1726 file.warning("""Warning: Can not determine whether file
1728 needs an extension when reading from standard input.
1729 You may need to correct the file manually or run
1730 lyx2lyx again with the .lyx file as commandline argument.""" % filename)
1732 # This needs to be the same algorithm as in pre 233 insetgraphics
1733 if access(absname, F_OK):
1735 if access(absname + ".ps", F_OK):
1736 file.body[j] = replace(file.body[j], filename, filename + ".ps")
1738 if access(absname + ".eps", F_OK):
1739 file.body[j] = replace(file.body[j], filename, filename + ".eps")
1743 # Convert firstname and surname from styles -> char styles
1745 def convert_names(file):
1746 """ Convert in the docbook backend from firstname and surname style
1749 if file.backend != "docbook":
1755 i = find_token(file.body, "\\begin_layout Author", i)
1760 while file.body[i] == "":
1763 if file.body[i][:11] != "\\end_layout" or file.body[i+2][:13] != "\\begin_deeper":
1768 i = find_end_of( file.body, i+3, "\\begin_deeper","\\end_deeper")
1770 # something is really wrong, abort
1771 file.warning("Missing \\end_deeper, after style Author.")
1772 file.warning("Aborted attempt to parse FirstName and Surname.")
1774 firstname, surname = "", ""
1776 name = file.body[k:i]
1778 j = find_token(name, "\\begin_layout FirstName", 0)
1781 while(name[j] != "\\end_layout"):
1782 firstname = firstname + name[j]
1785 j = find_token(name, "\\begin_layout Surname", 0)
1788 while(name[j] != "\\end_layout"):
1789 surname = surname + name[j]
1793 del file.body[k+2:i+1]
1795 file.body[k-1:k-1] = ["", "",
1796 "\\begin_inset CharStyle Firstname",
1799 "\\begin_layout Standard",
1807 "\\begin_inset CharStyle Surname",
1810 "\\begin_layout Standard",
1819 def revert_names(file):
1820 """ Revert in the docbook backend from firstname and surname char style
1823 if file.backend != "docbook":
1828 # \use_natbib 1 \cite_engine <style>
1829 # \use_numerical_citations 0 -> where <style> is one of
1830 # \use_jurabib 0 "basic", "natbib_authoryear",
1831 # "natbib_numerical" or "jurabib"
1832 def convert_cite_engine(file):
1833 a = find_token(file.header, "\\use_natbib", 0)
1835 file.warning("Malformed lyx file: Missing '\\use_natbib'.")
1838 b = find_token(file.header, "\\use_numerical_citations", 0)
1839 if b == -1 or b != a+1:
1840 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1843 c = find_token(file.header, "\\use_jurabib", 0)
1844 if c == -1 or c != b+1:
1845 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1848 use_natbib = int(split(file.header[a])[1])
1849 use_numerical_citations = int(split(file.header[b])[1])
1850 use_jurabib = int(split(file.header[c])[1])
1852 cite_engine = "basic"
1854 if use_numerical_citations:
1855 cite_engine = "natbib_numerical"
1857 cite_engine = "natbib_authoryear"
1859 cite_engine = "jurabib"
1861 del file.header[a:c+1]
1862 file.header.insert(a, "\\cite_engine " + cite_engine)
1865 def revert_cite_engine(file):
1866 i = find_token(file.header, "\\cite_engine", 0)
1868 file.warning("Malformed lyx file: Missing '\\cite_engine'.")
1871 cite_engine = split(file.header[i])[1]
1876 if cite_engine == "natbib_numerical":
1879 elif cite_engine == "natbib_authoryear":
1881 elif cite_engine == "jurabib":
1885 file.header.insert(i, "\\use_jurabib " + use_jurabib)
1886 file.header.insert(i, "\\use_numerical_citations " + use_numerical)
1887 file.header.insert(i, "\\use_natbib " + use_natbib)
1893 def convert_paperpackage(file):
1894 i = find_token(file.header, "\\paperpackage", 0)
1898 packages = {'default':'none','a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
1899 if len(split(file.header[i])) > 1:
1900 paperpackage = split(file.header[i])[1]
1901 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1903 file.header[i] = file.header[i] + ' widemarginsa4'
1906 def revert_paperpackage(file):
1907 i = find_token(file.header, "\\paperpackage", 0)
1911 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
1912 'widemarginsa4':'', 'default': 'default'}
1913 if len(split(file.header[i])) > 1:
1914 paperpackage = split(file.header[i])[1]
1916 paperpackage = 'default'
1917 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1923 def convert_bullets(file):
1926 i = find_token(file.header, "\\bullet", i)
1929 if file.header[i][:12] == '\\bulletLaTeX':
1930 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1])
1933 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1]) +\
1934 ' ' + strip(file.header[i+2]) + ' ' + strip(file.header[i+3])
1936 del file.header[i+1:i + n]
1940 def revert_bullets(file):
1943 i = find_token(file.header, "\\bullet", i)
1946 if file.header[i][:12] == '\\bulletLaTeX':
1947 n = find(file.header[i], '"')
1949 file.warning("Malformed header.")
1952 file.header[i:i+1] = [file.header[i][:n-1],'\t' + file.header[i][n:], '\\end_bullet']
1955 frag = split(file.header[i])
1957 file.warning("Malformed header.")
1960 file.header[i:i+1] = [frag[0] + ' ' + frag[1],
1969 # \begin_header and \begin_document
1971 def add_begin_header(file):
1972 i = find_token(file.header, '\\lyxformat', 0)
1973 file.header.insert(i+1, '\\begin_header')
1974 file.header.insert(i+1, '\\begin_document')
1977 def remove_begin_header(file):
1978 i = find_token(file.header, "\\begin_document", 0)
1981 i = find_token(file.header, "\\begin_header", 0)
1987 # \begin_file.body and \end_file.body
1989 def add_begin_body(file):
1990 file.body.insert(0, '\\begin_body')
1991 file.body.insert(1, '')
1992 i = find_token(file.body, "\\end_document", 0)
1993 file.body.insert(i, '\\end_body')
1995 def remove_begin_body(file):
1996 i = find_token(file.body, "\\begin_body", 0)
1999 if not file.body[i]:
2001 i = find_token(file.body, "\\end_body", 0)
2009 def normalize_papersize(file):
2010 i = find_token(file.header, '\\papersize', 0)
2014 tmp = split(file.header[i])
2015 if tmp[1] == "Default":
2016 file.header[i] = '\\papersize default'
2018 if tmp[1] == "Custom":
2019 file.header[i] = '\\papersize custom'
2022 def denormalize_papersize(file):
2023 i = find_token(file.header, '\\papersize', 0)
2027 tmp = split(file.header[i])
2028 if tmp[1] == "custom":
2029 file.header[i] = '\\papersize Custom'
2033 # Strip spaces at end of command line
2035 def strip_end_space(file):
2036 for i in range(len(file.body)):
2037 if file.body[i][:1] == '\\':
2038 file.body[i] = strip(file.body[i])
2042 # Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes
2044 def use_x_boolean(file):
2045 bin2bool = {'0': 'false', '1': 'true'}
2046 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2047 i = find_token(file.header, use, 0)
2050 decompose = split(file.header[i])
2051 file.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
2054 def use_x_binary(file):
2055 bool2bin = {'false': '0', 'true': '1'}
2056 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2057 i = find_token(file.header, use, 0)
2060 decompose = split(file.header[i])
2061 file.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
2064 # Place all the paragraph parameters in their own line
2066 def normalize_paragraph_params(file):
2068 allowed_parameters = '\\paragraph_spacing', '\\noindent', '\\align', '\\labelwidthstring', "\\start_of_appendix", "\\leftindent"
2072 i = find_token(file.body, '\\begin_layout', i)
2078 if strip(body[i]) and split(body[i])[0] not in allowed_parameters:
2081 j = find(body[i],'\\', 1)
2084 body[i:i+1] = [strip(body[i][:j]), body[i][j:]]
2090 # Add/remove output_changes parameter
2092 def convert_output_changes (file):
2093 i = find_token(file.header, '\\tracking_changes', 0)
2095 file.warning("Malformed lyx file: Missing '\\tracking_changes'.")
2097 file.header.insert(i+1, '\\output_changes true')
2100 def revert_output_changes (file):
2101 i = find_token(file.header, '\\output_changes', 0)
2108 # Convert paragraph breaks and sanitize paragraphs
2110 def convert_ert_paragraphs(file):
2111 forbidden_settings = [
2112 # paragraph parameters
2113 '\\paragraph_spacing', '\\labelwidthstring',
2114 '\\start_of_appendix', '\\noindent',
2115 '\\leftindent', '\\align',
2117 '\\family', '\\series', '\\shape', '\\size',
2118 '\\emph', '\\numeric', '\\bar', '\\noun',
2119 '\\color', '\\lang']
2122 i = find_token(file.body, '\\begin_inset ERT', i)
2125 j = find_end_of_inset(file.body, i)
2127 file.warning("Malformed lyx file: Missing '\\end_inset'.")
2131 # convert non-standard paragraphs to standard
2134 k = find_token(file.body, "\\begin_layout", k, j)
2137 file.body[k] = "\\begin_layout Standard"
2140 # remove all paragraph parameters and font settings
2143 if (strip(file.body[k]) and
2144 split(file.body[k])[0] in forbidden_settings):
2150 # insert an empty paragraph before each paragraph but the first
2154 k = find_token(file.body, "\\begin_layout Standard", k, j)
2161 file.body[k:k] = ["\\begin_layout Standard", "",
2166 # convert \\newline to new paragraph
2169 k = find_token(file.body, "\\newline", k, j)
2172 file.body[k:k+1] = ["\\end_layout", "", "\\begin_layout Standard"]
2179 # Remove double paragraph breaks
2181 def revert_ert_paragraphs(file):
2184 i = find_token(file.body, '\\begin_inset ERT', i)
2187 j = find_end_of_inset(file.body, i)
2189 file.warning("Malformed lyx file: Missing '\\end_inset'.")
2193 # replace paragraph breaks with \newline
2196 k = find_token(file.body, "\\end_layout", k, j)
2197 l = find_token(file.body, "\\begin_layout", k, j)
2198 if k == -1 or l == -1:
2200 file.body[k:l+1] = ["\\newline"]
2204 # replace double \newlines with paragraph breaks
2207 k = find_token(file.body, "\\newline", k, j)
2211 while file.body[l] == "":
2213 if strip(file.body[l]) and split(file.body[l])[0] == "\\newline":
2214 file.body[k:l+1] = ["\\end_layout", "",
2215 "\\begin_layout Standard"]
2223 def convert_french(file):
2224 regexp = re.compile(r'^\\language\s+frenchb')
2225 i = find_re(file.header, regexp, 0)
2227 file.header[i] = "\\language french"
2229 # Change language in the document body
2230 regexp = re.compile(r'^\\lang\s+frenchb')
2233 i = find_re(file.body, regexp, i)
2236 file.body[i] = "\\lang french"
2240 def remove_paperpackage(file):
2241 i = find_token(file.header, '\\paperpackage', 0)
2246 paperpackage = split(file.header[i])[1]
2248 if paperpackage in ("a4", "a4wide", "widemarginsa4"):
2249 conv = {"a4":"\\usepackage{a4}","a4wide": "\\usepackage{a4wide}",
2250 "widemarginsa4": "\\usepackage[widemargins]{a4}"}
2251 # for compatibility we ensure it is the first entry in preamble
2252 file.preamble[0:0] = [conv[paperpackage]]
2256 i = find_token(file.header, '\\papersize', 0)
2258 file.header[i] = "\\papersize default"
2265 convert = [[222, [insert_tracking_changes, add_end_header]],
2266 [223, [remove_color_default, convert_spaces, convert_bibtex, remove_insetparent]],
2267 [224, [convert_external, convert_comment]],
2268 [225, [add_end_layout, layout2begin_layout, convert_end_document,
2269 convert_table_valignment_middle, convert_breaks]],
2270 [226, [convert_note]],
2271 [227, [convert_box]],
2272 [228, [convert_collapsable, convert_ert]],
2273 [229, [convert_minipage]],
2274 [230, [convert_jurabib]],
2275 [231, [convert_float]],
2276 [232, [convert_bibtopic]],
2277 [233, [convert_graphics, convert_names]],
2278 [234, [convert_cite_engine]],
2279 [235, [convert_paperpackage]],
2280 [236, [convert_bullets, add_begin_header, add_begin_body,
2281 normalize_papersize, strip_end_space]],
2282 [237, [use_x_boolean]],
2283 [238, [update_latexaccents]],
2284 [239, [normalize_paragraph_params]],
2285 [240, [convert_output_changes]],
2286 [241, [convert_ert_paragraphs]],
2287 [242, [convert_french]],
2288 [243, [remove_paperpackage]],
2289 [244, [rename_spaces]]]
2291 revert = [[243, [revert_space_names]],
2294 [240, [revert_ert_paragraphs]],
2295 [239, [revert_output_changes]],
2298 [236, [use_x_binary]],
2299 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
2301 [234, [revert_paperpackage]],
2302 [233, [revert_cite_engine]],
2303 [232, [revert_names]],
2304 [231, [revert_bibtopic]],
2305 [230, [revert_float]],
2306 [229, [revert_jurabib]],
2308 [227, [revert_collapsable, revert_ert]],
2309 [226, [revert_box, revert_external_2]],
2310 [225, [revert_note]],
2311 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
2312 revert_valignment_middle, revert_breaks, convert_frameless_box]],
2313 [223, [revert_external_2, revert_comment, revert_eqref]],
2314 [222, [revert_spaces, revert_bibtex]],
2315 [221, [rm_end_header, rm_tracking_changes, rm_body_changes]]]
2318 if __name__ == "__main__":