1 # This file is part of lyx2lyx
2 # -*- coding: utf-8 -*-
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.
21 """ Convert files to the file format generated by lyx 1.4"""
24 from os import access, F_OK
26 from parser_tools import check_token, find_token, \
27 get_value, del_token, is_nonempty_line, \
28 find_tokens, find_end_of, find_beginning_of, find_token_exact, find_tokens_exact, \
29 find_re, find_tokens_backwards
32 from lyx_0_12 import update_latexaccents
34 ####################################################################
35 # Private helper functions
37 def get_layout(line, default_layout):
38 " Get layout, if empty return the default layout."
45 def get_paragraph(lines, i, format):
46 "Finds the paragraph that contains line i."
49 begin_layout = "\\layout"
51 begin_layout = "\\begin_layout"
53 i = find_tokens_backwards(lines, ["\\end_inset", begin_layout], i)
55 if check_token(lines[i], begin_layout):
57 i = find_beginning_of_inset(lines, i)
61 def find_beginning_of_inset(lines, i):
62 " Find beginning of inset, where lines[i] is included."
63 return find_beginning_of(lines, i, "\\begin_inset", "\\end_inset")
66 def get_next_paragraph(lines, i, format):
67 "Finds the paragraph after the paragraph that contains line i."
70 tokens = ["\\begin_inset", "\\layout", "\\end_float", "\\the_end"]
72 tokens = ["\\begin_inset", "\\begin_layout", "\\end_float", "\\end_document"]
74 tokens = ["\\begin_inset", "\\begin_layout", "\\end_float", "\\end_body", "\\end_document"]
76 i = find_tokens(lines, tokens, i)
77 if not check_token(lines[i], "\\begin_inset"):
79 i = find_end_of_inset(lines, i)
83 def find_end_of_inset(lines, i):
84 "Finds the matching \end_inset"
85 return find_end_of(lines, i, "\\begin_inset", "\\end_inset")
87 # End of helper functions
88 ####################################################################
90 def remove_color_default(document):
91 " Remove \color default"
94 i = find_token(document.body, "\\color default", i)
97 document.body[i] = document.body[i].replace("\\color default",
101 def add_end_header(document):
103 document.header.append("\\end_header");
106 def rm_end_header(document):
107 " Remove \end_header"
108 i = find_token(document.header, "\\end_header", 0)
111 del document.header[i]
114 def convert_amsmath(document):
115 " Convert \\use_amsmath"
116 i = find_token(document.header, "\\use_amsmath", 0)
118 document.warning("Malformed LyX document: Missing '\\use_amsmath'.")
120 tokens = document.header[i].split()
122 document.warning("Malformed LyX document: Could not parse line '%s'." % document.header[i])
125 use_amsmath = tokens[1]
126 # old: 0 == off, 1 == on
127 # new: 0 == off, 1 == auto, 2 == on
128 # translate off -> auto, since old format 'off' means auto in reality
129 if use_amsmath == '0':
130 document.header[i] = "\\use_amsmath 1"
132 document.header[i] = "\\use_amsmath 2"
135 def revert_amsmath(document):
136 " Revert \\use_amsmath"
137 i = find_token(document.header, "\\use_amsmath", 0)
139 document.warning("Malformed LyX document: Missing '\\use_amsmath'.")
141 tokens = document.header[i].split()
143 document.warning("Malformed LyX document: Could not parse line '%s'." % document.header[i])
146 use_amsmath = tokens[1]
147 # old: 0 == off, 1 == on
148 # new: 0 == off, 1 == auto, 2 == on
149 # translate auto -> off, since old format 'off' means auto in reality
150 if use_amsmath == '2':
151 document.header[i] = "\\use_amsmath 1"
153 document.header[i] = "\\use_amsmath 0"
156 def convert_spaces(document):
157 " \SpecialChar ~ -> \InsetSpace ~"
158 for i in range(len(document.body)):
159 document.body[i] = document.body[i].replace("\\SpecialChar ~",
163 def revert_spaces(document):
164 " \InsetSpace ~ -> \SpecialChar ~"
165 regexp = re.compile(r'(.*)(\\InsetSpace\s+)(\S+)')
168 i = find_re(document.body, regexp, i)
171 space = regexp.match(document.body[i]).group(3)
172 prepend = regexp.match(document.body[i]).group(1)
174 document.body[i] = regexp.sub(prepend + '\\SpecialChar ~', document.body[i])
177 document.body[i] = regexp.sub(prepend, document.body[i])
178 document.body[i+1:i+1] = ''
179 if space == "\\space":
181 i = insert_ert(document.body, i+1, 'Collapsed', space, document.format - 1, document.default_layout)
184 def rename_spaces(document):
185 """ \InsetSpace \, -> \InsetSpace \thinspace{}
186 \InsetSpace \space -> \InsetSpace \space{}"""
187 for i in range(len(document.body)):
188 document.body[i] = document.body[i].replace("\\InsetSpace \\space",
189 "\\InsetSpace \\space{}")
190 document.body[i] = document.body[i].replace("\\InsetSpace \,",
191 "\\InsetSpace \\thinspace{}")
194 def revert_space_names(document):
195 """ \InsetSpace \thinspace{} -> \InsetSpace \,
196 \InsetSpace \space{} -> \InsetSpace \space"""
197 for i in range(len(document.body)):
198 document.body[i] = document.body[i].replace("\\InsetSpace \\space{}",
199 "\\InsetSpace \\space")
200 document.body[i] = document.body[i].replace("\\InsetSpace \\thinspace{}",
204 def lyx_support_escape(lab):
205 " Equivalent to lyx::support::escape()"
206 hexdigit = ['0', '1', '2', '3', '4', '5', '6', '7',
207 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
211 if o >= 128 or c == '=' or c == '%':
213 enc = enc + hexdigit[o >> 4]
214 enc = enc + hexdigit[o & 15]
220 def revert_eqref(document):
221 "\\begin_inset LatexCommand \\eqref -> ERT"
222 regexp = re.compile(r'^\\begin_inset\s+LatexCommand\s+\\eqref')
225 i = find_re(document.body, regexp, i)
228 eqref = lyx_support_escape(regexp.sub("", document.body[i]))
229 document.body[i:i+1] = ["\\begin_inset ERT", "status Collapsed", "",
230 '\\layout %s' % document.default_layout, "", "\\backslash ",
235 def convert_bibtex(document):
236 " Convert BibTeX changes."
237 for i in range(len(document.body)):
238 document.body[i] = document.body[i].replace("\\begin_inset LatexCommand \\BibTeX",
239 "\\begin_inset LatexCommand \\bibtex")
242 def revert_bibtex(document):
243 " Revert BibTeX changes."
244 for i in range(len(document.body)):
245 document.body[i] = document.body[i].replace("\\begin_inset LatexCommand \\bibtex",
246 "\\begin_inset LatexCommand \\BibTeX")
249 def remove_insetparent(document):
253 i = find_token(document.body, "\\begin_inset LatexCommand \\lyxparent", i)
256 del document.body[i:i+3]
259 def convert_external(document):
260 " Convert inset External."
261 external_rexp = re.compile(r'\\begin_inset External ([^,]*),"([^"]*)",')
262 external_header = "\\begin_inset External"
265 i = find_token(document.body, external_header, i)
268 look = external_rexp.search(document.body[i])
271 args[0] = look.group(1)
272 args[1] = look.group(2)
273 #FIXME: if the previous search fails then warn
275 if args[0] == "RasterImage":
276 # Convert a RasterImage External Inset to a Graphics Inset.
277 top = "\\begin_inset Graphics"
279 filename = "\tfilename " + args[1]
280 document.body[i:i+1] = [top, filename]
283 # Convert the old External Inset format to the new.
284 top = external_header
285 template = "\ttemplate " + args[0]
287 filename = "\tfilename " + args[1]
288 document.body[i:i+1] = [top, template, filename]
291 document.body[i:i+1] = [top, template]
295 def revert_external_1(document):
296 " Revert inset External."
297 external_header = "\\begin_inset External"
300 i = find_token(document.body, external_header, i)
304 template = document.body[i+1].split()
306 del document.body[i+1]
308 filename = document.body[i+1].split()
310 del document.body[i+1]
312 params = document.body[i+1].split()
314 if document.body[i+1]: del document.body[i+1]
316 document.body[i] = document.body[i] + " " + template[0]+ ', "' + filename[0] + '", " '+ "".join(params[1:]) + '"'
320 def revert_external_2(document):
321 " Revert inset External. (part II)"
322 draft_token = '\tdraft'
325 i = find_token(document.body, '\\begin_inset External', i)
328 j = find_end_of_inset(document.body, i + 1)
330 #this should not happen
332 k = find_token(document.body, draft_token, i+1, j-1)
333 if (k != -1 and len(draft_token) == len(document.body[k])):
338 def convert_comment(document):
339 " Convert \\layout comment"
341 comment = "\\layout Comment"
343 i = find_token(document.body, comment, i)
347 document.body[i:i+1] = ['\\layout %s' % document.default_layout,"","",
348 "\\begin_inset Comment",
350 '\\layout %s' % document.default_layout]
355 i = find_token(document.body, "\\layout", i)
357 i = len(document.body) - 1
358 document.body[i:i] = ["\\end_inset","",""]
361 j = find_token(document.body, '\\begin_deeper', old_i, i)
362 if j == -1: j = i + 1
363 k = find_token(document.body, '\\begin_inset', old_i, i)
364 if k == -1: k = i + 1
369 i = find_end_of( document.body, i, "\\begin_deeper","\\end_deeper")
371 #This case should not happen
372 #but if this happens deal with it greacefully adding
373 #the missing \end_deeper.
374 i = len(document.body) - 1
375 document.body[i:i] = ["\end_deeper",""]
383 i = find_end_of( document.body, i, "\\begin_inset","\\end_inset")
385 #This case should not happen
386 #but if this happens deal with it greacefully adding
387 #the missing \end_inset.
388 i = len(document.body) - 1
389 document.body[i:i] = ["\\end_inset","","","\\end_inset","",""]
395 if document.body[i].find(comment) == -1:
396 document.body[i:i] = ["\\end_inset"]
399 document.body[i:i+1] = ['\\layout %s' % document.default_layout]
403 def revert_comment(document):
407 i = find_tokens(document.body, ["\\begin_inset Comment", "\\begin_inset Greyedout"], i)
411 document.body[i] = "\\begin_inset Note"
415 def add_end_layout(document):
417 i = find_token(document.body, '\\layout', 0)
423 struct_stack = ["\\layout"]
426 i = find_tokens(document.body, ["\\begin_inset", "\\end_inset", "\\layout",
427 "\\begin_deeper", "\\end_deeper", "\\the_end"], i)
430 token = document.body[i].split()[0]
432 document.warning("Truncated document.")
433 i = len(document.body)
434 document.body.insert(i, '\\the_end')
437 if token == "\\begin_inset":
438 struct_stack.append(token)
442 if token == "\\end_inset":
443 tail = struct_stack.pop()
444 if tail == "\\layout":
445 document.body.insert(i,"")
446 document.body.insert(i,"\\end_layout")
448 #Check if it is the correct tag
453 if token == "\\layout":
454 tail = struct_stack.pop()
456 document.body.insert(i,"")
457 document.body.insert(i,"\\end_layout")
460 struct_stack.append(tail)
462 struct_stack.append(token)
465 if token == "\\begin_deeper":
466 document.body.insert(i,"")
467 document.body.insert(i,"\\end_layout")
469 struct_stack.append(token)
472 if token == "\\end_deeper":
473 if struct_stack[-1] == '\\layout':
474 document.body.insert(i, '\\end_layout')
481 document.body.insert(i, "")
482 document.body.insert(i, "\\end_layout")
486 def rm_end_layout(document):
487 " Remove \end_layout"
490 i = find_token(document.body, '\\end_layout', i)
498 def insert_tracking_changes(document):
499 " Handle change tracking keywords."
500 i = find_token(document.header, "\\tracking_changes", 0)
502 document.header.append("\\tracking_changes 0")
505 def rm_tracking_changes(document):
506 " Remove change tracking keywords."
507 i = find_token(document.header, "\\author", 0)
509 del document.header[i]
511 i = find_token(document.header, "\\tracking_changes", 0)
514 del document.header[i]
517 def rm_body_changes(document):
518 " Remove body changes."
521 i = find_token(document.body, "\\change_", i)
528 def layout2begin_layout(document):
529 " \layout -> \begin_layout "
532 i = find_token(document.body, '\\layout', i)
536 document.body[i] = document.body[i].replace('\\layout', '\\begin_layout')
540 def begin_layout2layout(document):
541 " \begin_layout -> \layout "
544 i = find_token(document.body, '\\begin_layout', i)
548 document.body[i] = document.body[i].replace('\\begin_layout', '\\layout')
552 def convert_valignment_middle(body, start, end):
553 'valignment="center" -> valignment="middle"'
554 for i in range(start, end):
555 if re.search('^<(column|cell) .*valignment="center".*>$', body[i]):
556 body[i] = body[i].replace('valignment="center"', 'valignment="middle"')
559 def convert_table_valignment_middle(document):
560 " Convert table valignment, center -> middle"
561 regexp = re.compile(r'^\\begin_inset\s+Tabular')
564 i = find_re(document.body, regexp, i)
567 j = find_end_of_inset(document.body, i + 1)
569 #this should not happen
570 convert_valignment_middle(document.body, i + 1, len(document.body))
572 convert_valignment_middle(document.body, i + 1, j)
576 def revert_table_valignment_middle(body, start, end):
577 " valignment, middle -> center"
578 for i in range(start, end):
579 if re.search('^<(column|cell) .*valignment="middle".*>$', body[i]):
580 body[i] = body[i].replace('valignment="middle"', 'valignment="center"')
583 def revert_valignment_middle(document):
584 " Convert table valignment, middle -> center"
585 regexp = re.compile(r'^\\begin_inset\s+Tabular')
588 i = find_re(document.body, regexp, i)
591 j = find_end_of_inset(document.body, i + 1)
593 #this should not happen
594 revert_table_valignment_middle(document.body, i + 1, len(document.body))
596 revert_table_valignment_middle(document.body, i + 1, j)
600 def convert_end_document(document):
601 "\\the_end -> \\end_document"
602 i = find_token(document.body, "\\the_end", 0)
604 document.body.append("\\end_document")
606 document.body[i] = "\\end_document"
609 def revert_end_document(document):
610 "\\end_document -> \\the_end"
611 i = find_token(document.body, "\\end_document", 0)
613 document.body.append("\\the_end")
615 document.body[i] = "\\the_end"
618 def convert_breaks(document):
620 Convert line and page breaks
623 \line_top \line_bottom \pagebreak_top \pagebreak_bottom \added_space_top xxx \added_space_bottom yyy
627 \begin layout Standard
633 \begin layout Standard
640 \begin_inset VSpace xxx
645 \begin_inset VSpace xxx
653 par_params = ('added_space_bottom', 'added_space_top', 'align',
654 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
655 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
657 font_attributes = ['\\family', '\\series', '\\shape', '\\emph',
658 '\\numeric', '\\bar', '\\noun', '\\color', '\\lang']
659 attribute_values = ['default', 'default', 'default', 'default',
660 'default', 'default', 'default', 'none', document.language]
663 i = find_token(document.body, "\\begin_layout", i)
666 layout = get_layout(document.body[i], document.default_layout)
669 # Merge all paragraph parameters into a single line
670 # We cannot check for '\\' only because paragraphs may start e.g.
672 while document.body[i + 1][:1] == '\\' and document.body[i + 1][1:].split()[0] in par_params:
673 document.body[i] = document.body[i + 1] + ' ' + document.body[i]
674 del document.body[i+1]
676 line_top = document.body[i].find("\\line_top")
677 line_bot = document.body[i].find("\\line_bottom")
678 pb_top = document.body[i].find("\\pagebreak_top")
679 pb_bot = document.body[i].find("\\pagebreak_bottom")
680 vspace_top = document.body[i].find("\\added_space_top")
681 vspace_bot = document.body[i].find("\\added_space_bottom")
683 if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1:
686 # Do we have a nonstandard paragraph? We need to create new paragraphs
687 # if yes to avoid putting lyxline etc. inside of special environments.
688 # This is wrong for itemize and enumerate environments, but it is
689 # impossible to convert these correctly.
690 # We want to avoid new paragraphs if possible becauase we want to
691 # inherit font sizes.
693 if (not document.is_default_layout(layout) or
694 document.body[i].find("\\align") != -1 or
695 document.body[i].find("\\labelwidthstring") != -1 or
696 document.body[i].find("\\noindent") != -1):
699 # get the font size of the beginning of this paragraph, since we need
700 # it for the lyxline inset
702 while not is_nonempty_line(document.body[j]):
705 if document.body[j].find("\\size") != -1:
706 size_top = document.body[j].split()[1]
708 for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom":
709 document.body[i] = document.body[i].replace(tag, "")
712 # the position could be change because of the removal of other
713 # paragraph properties above
714 vspace_top = document.body[i].find("\\added_space_top")
715 tmp_list = document.body[i][vspace_top:].split()
716 vspace_top_value = tmp_list[1]
717 document.body[i] = document.body[i][:vspace_top] + "".join(tmp_list[2:])
720 # the position could be change because of the removal of other
721 # paragraph properties above
722 vspace_bot = document.body[i].find("\\added_space_bottom")
723 tmp_list = document.body[i][vspace_bot:].split()
724 vspace_bot_value = tmp_list[1]
725 document.body[i] = document.body[i][:vspace_bot] + "".join(tmp_list[2:])
727 document.body[i] = document.body[i].strip()
730 # Create an empty paragraph or paragraph fragment for line and
731 # page break that belong above the paragraph
732 if pb_top !=-1 or line_top != -1 or vspace_top != -1:
734 paragraph_above = list()
736 # We need to create an extra paragraph for nonstandard environments
737 paragraph_above = ['\\begin_layout %s' % document.default_layout, '']
740 paragraph_above.extend(['\\newpage ',''])
743 paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','',''])
747 paragraph_above.extend(['\\size ' + size_top + ' '])
748 # We need an additional vertical space of -\parskip.
749 # We can't use the vspace inset because it does not know \parskip.
750 paragraph_above.extend(['\\lyxline ', '', ''])
751 insert_ert(paragraph_above, len(paragraph_above) - 1, 'Collapsed',
752 '\\vspace{-1\\parskip}\n', document.format + 1, document.default_layout)
753 paragraph_above.extend([''])
756 paragraph_above.extend(['\\end_layout ',''])
757 # insert new paragraph above the current paragraph
758 document.body[i-2:i-2] = paragraph_above
760 # insert new lines at the beginning of the current paragraph
761 document.body[i:i] = paragraph_above
763 i = i + len(paragraph_above)
765 # Ensure that nested style are converted later.
766 k = find_end_of(document.body, i, "\\begin_layout", "\\end_layout")
771 if pb_bot !=-1 or line_bot != -1 or vspace_bot != -1:
773 # get the font size of the end of this paragraph
777 if document.body[j].find("\\size") != -1:
778 size_bot = document.body[j].split()[1]
780 elif document.body[j].find("\\begin_inset") != -1:
782 j = find_end_of_inset(document.body, j)
786 paragraph_below = list()
788 # We need to create an extra paragraph for nonstandard environments
789 paragraph_below = ['', '\\begin_layout %s' % document.default_layout, '']
791 for a in range(len(font_attributes)):
792 if find_token(document.body, font_attributes[a], i, k) != -1:
793 paragraph_below.extend([font_attributes[a] + ' ' + attribute_values[a]])
796 if nonstandard and size_bot != '':
797 paragraph_below.extend(['\\size ' + size_bot + ' '])
798 paragraph_below.extend(['\\lyxline ',''])
800 paragraph_below.extend(['\\size default '])
803 paragraph_below.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','',''])
806 paragraph_below.extend(['\\newpage ',''])
809 paragraph_below.extend(['\\end_layout '])
810 # insert new paragraph below the current paragraph
811 document.body[k+1:k+1] = paragraph_below
813 # insert new lines at the end of the current paragraph
814 document.body[k:k] = paragraph_below
817 def convert_note(document):
821 i = find_tokens(document.body, ["\\begin_inset Note",
822 "\\begin_inset Comment",
823 "\\begin_inset Greyedout"], i)
827 document.body[i] = document.body[i][0:13] + 'Note ' + document.body[i][13:]
831 def revert_note(document):
833 note_header = "\\begin_inset Note "
836 i = find_token(document.body, note_header, i)
840 document.body[i] = "\\begin_inset " + document.body[i][len(note_header):]
844 def convert_box(document):
848 i = find_tokens(document.body, ["\\begin_inset Boxed",
849 "\\begin_inset Doublebox",
850 "\\begin_inset Frameless",
851 "\\begin_inset ovalbox",
852 "\\begin_inset Ovalbox",
853 "\\begin_inset Shadowbox"], i)
857 document.body[i] = document.body[i][0:13] + 'Box ' + document.body[i][13:]
861 def revert_box(document):
863 box_header = "\\begin_inset Box "
866 i = find_token(document.body, box_header, i)
870 document.body[i] = "\\begin_inset " + document.body[i][len(box_header):]
874 def convert_collapsable(document):
875 " Convert collapsed insets. "
878 i = find_tokens_exact(document.body, ["\\begin_inset Box",
879 "\\begin_inset Branch",
880 "\\begin_inset CharStyle",
881 "\\begin_inset Float",
882 "\\begin_inset Foot",
883 "\\begin_inset Marginal",
884 "\\begin_inset Note",
885 "\\begin_inset OptArg",
886 "\\begin_inset Wrap"], i)
890 # Seach for a line starting 'collapsed'
891 # If, however, we find a line starting '\begin_layout'
892 # (_always_ present) then break with a warning message
895 if (document.body[i] == "collapsed false"):
896 document.body[i] = "status open"
898 elif (document.body[i] == "collapsed true"):
899 document.body[i] = "status collapsed"
901 elif (document.body[i][:13] == "\\begin_layout"):
902 document.warning("Malformed LyX document: Missing 'collapsed'.")
909 def revert_collapsable(document):
910 " Revert collapsed insets. "
913 i = find_tokens_exact(document.body, ["\\begin_inset Box",
914 "\\begin_inset Branch",
915 "\\begin_inset CharStyle",
916 "\\begin_inset Float",
917 "\\begin_inset Foot",
918 "\\begin_inset Marginal",
919 "\\begin_inset Note",
920 "\\begin_inset OptArg",
921 "\\begin_inset Wrap"], i)
925 # Seach for a line starting 'status'
926 # If, however, we find a line starting '\begin_layout'
927 # (_always_ present) then break with a warning message
930 if (document.body[i] == "status open"):
931 document.body[i] = "collapsed false"
933 elif (document.body[i] == "status collapsed" or
934 document.body[i] == "status inlined"):
935 document.body[i] = "collapsed true"
937 elif (document.body[i][:13] == "\\begin_layout"):
938 document.warning("Malformed LyX document: Missing 'status'.")
945 def convert_ert(document):
949 i = find_token(document.body, "\\begin_inset ERT", i)
953 # Seach for a line starting 'status'
954 # If, however, we find a line starting '\begin_layout'
955 # (_always_ present) then break with a warning message
958 if (document.body[i] == "status Open"):
959 document.body[i] = "status open"
961 elif (document.body[i] == "status Collapsed"):
962 document.body[i] = "status collapsed"
964 elif (document.body[i] == "status Inlined"):
965 document.body[i] = "status inlined"
967 elif (document.body[i][:13] == "\\begin_layout"):
968 document.warning("Malformed LyX document: Missing 'status'.")
975 def revert_ert(document):
979 i = find_token(document.body, "\\begin_inset ERT", i)
983 # Seach for a line starting 'status'
984 # If, however, we find a line starting '\begin_layout'
985 # (_always_ present) then break with a warning message
988 if (document.body[i] == "status open"):
989 document.body[i] = "status Open"
991 elif (document.body[i] == "status collapsed"):
992 document.body[i] = "status Collapsed"
994 elif (document.body[i] == "status inlined"):
995 document.body[i] = "status Inlined"
997 elif (document.body[i][:13] == "\\begin_layout"):
998 document.warning("Malformed LyX document : Missing 'status'.")
1005 def convert_minipage(document):
1006 """ Convert minipages to the box inset.
1007 We try to use the same order of arguments as lyx does.
1010 inner_pos = ["c","t","b","s"]
1014 i = find_token(document.body, "\\begin_inset Minipage", i)
1018 document.body[i] = "\\begin_inset Box Frameless"
1021 # convert old to new position using the pos list
1022 if document.body[i][:8] == "position":
1023 document.body[i] = 'position "%s"' % pos[int(document.body[i][9])]
1025 document.body.insert(i, 'position "%s"' % pos[0])
1028 document.body.insert(i, 'hor_pos "c"')
1030 document.body.insert(i, 'has_inner_box 1')
1033 # convert the inner_position
1034 if document.body[i][:14] == "inner_position":
1035 innerpos = inner_pos[int(document.body[i][15])]
1036 del document.body[i]
1038 innerpos = inner_pos[0]
1040 # We need this since the new file format has a height and width
1041 # in a different order.
1042 if document.body[i][:6] == "height":
1043 height = document.body[i][6:]
1044 # test for default value of 221 and convert it accordingly
1045 if height == ' "0pt"' or height == ' "0"':
1047 del document.body[i]
1051 if document.body[i][:5] == "width":
1052 width = document.body[i][5:]
1053 del document.body[i]
1057 if document.body[i][:9] == "collapsed":
1058 if document.body[i][9:] == "true":
1059 status = "collapsed"
1062 del document.body[i]
1064 status = "collapsed"
1066 # Handle special default case:
1067 if height == ' "1pt"' and innerpos == 'c':
1070 document.body.insert(i, 'inner_pos "' + innerpos + '"')
1072 document.body.insert(i, 'use_parbox 0')
1074 document.body.insert(i, 'width' + width)
1076 document.body.insert(i, 'special "none"')
1078 document.body.insert(i, 'height' + height)
1080 document.body.insert(i, 'height_special "totalheight"')
1082 document.body.insert(i, 'status ' + status)
1086 def convert_ertbackslash(body, i, ert, format, default_layout):
1087 r""" -------------------------------------------------------------------------------------------
1088 Convert backslashes and '\n' into valid ERT code, append the converted
1089 text to body[i] and return the (maybe incremented) line index i"""
1093 body[i] = body[i] + '\\backslash '
1098 body[i+1:i+1] = ['\\newline ', '']
1101 body[i+1:i+1] = ['\\end_layout', '', '\\begin_layout %s' % default_layout, '']
1104 body[i] = body[i] + c
1108 def ert2latex(lines, format):
1109 r""" Converts lines in ERT code to LaTeX
1110 The surrounding \begin_layout ... \end_layout pair must not be included"""
1112 backslash = re.compile(r'\\backslash\s*$')
1113 newline = re.compile(r'\\newline\s*$')
1115 begin_layout = re.compile(r'\\layout\s*\S+$')
1117 begin_layout = re.compile(r'\\begin_layout\s*\S+$')
1118 end_layout = re.compile(r'\\end_layout\s*$')
1120 for i in range(len(lines)):
1121 line = backslash.sub('\\\\', lines[i])
1123 if begin_layout.match(line):
1126 line = newline.sub('\n', line)
1128 if begin_layout.match(line):
1130 if format > 224 and end_layout.match(line):
1136 def get_par_params(lines, i):
1137 """ get all paragraph parameters. They can be all on one line or on several lines.
1138 lines[i] must be the first parameter line"""
1139 par_params = ('added_space_bottom', 'added_space_top', 'align',
1140 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
1141 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
1142 'start_of_appendix')
1143 # We cannot check for '\\' only because paragraphs may start e.g.
1144 # with '\\backslash'
1146 while lines[i][:1] == '\\' and lines[i][1:].split()[0] in par_params:
1147 params = params + ' ' + lines[i].strip()
1149 return params.strip()
1152 def lyxsize2latexsize(lyxsize):
1153 " Convert LyX font size to LaTeX fontsize. "
1154 sizes = {"tiny" : "tiny", "scriptsize" : "scriptsize",
1155 "footnotesize" : "footnotesize", "small" : "small",
1156 "normal" : "normalsize", "large" : "large", "larger" : "Large",
1157 "largest" : "LARGE", "huge" : "huge", "giant" : "Huge"}
1158 if lyxsize in sizes:
1159 return '\\' + sizes[lyxsize]
1163 def revert_breaks(document):
1164 """ Change vspace insets, page breaks and lyxlines to paragraph options
1165 (if possible) or ERT"""
1167 # Get default spaceamount
1168 i = find_token(document.header, '\\defskip', 0)
1170 defskipamount = 'medskip'
1172 defskipamount = document.header[i].split()[1]
1174 keys = {"\\begin_inset" : "vspace", "\\lyxline" : "lyxline",
1175 "\\newpage" : "newpage"}
1176 keywords_top = {"vspace" : "\\added_space_top", "lyxline" : "\\line_top",
1177 "newpage" : "\\pagebreak_top"}
1178 keywords_bot = {"vspace" : "\\added_space_bottom", "lyxline" : "\\line_bottom",
1179 "newpage" : "\\pagebreak_bottom"}
1180 tokens = ["\\begin_inset VSpace", "\\lyxline", "\\newpage"]
1182 # Convert the insets
1185 i = find_tokens(document.body, tokens, i)
1189 # Are we at the beginning of a paragraph?
1191 this_par = get_paragraph(document.body, i, document.format - 1)
1192 start = this_par + 1
1193 params = get_par_params(document.body, start)
1195 # Paragraph parameters may be on one or more lines.
1196 # Find the start of the real paragraph text.
1197 while document.body[start][:1] == '\\' and document.body[start].split()[0] in params:
1199 for k in range(start, i):
1200 if document.body[k].find("\\size") != -1:
1202 size = document.body[k].split()[1]
1203 elif is_nonempty_line(document.body[k]):
1206 # Find the end of the real paragraph text.
1207 next_par = get_next_paragraph(document.body, i, document.format - 1)
1209 document.warning("Malformed LyX document: Missing next paragraph.")
1213 # first line of our insets
1215 # last line of our insets
1216 inset_end = inset_start
1217 # Are we at the end of a paragraph?
1219 # start and end line numbers to delete if we convert this inset
1221 # is this inset a lyxline above a paragraph?
1223 # raw inset information
1225 # name of this inset
1227 # font size of this inset
1230 # Detect subsequent lyxline, vspace and pagebreak insets created by convert_breaks()
1234 if find_tokens(document.body, tokens, k) == k:
1236 lines.append(document.body[k].split())
1237 insets.append(keys[lines[n][0]])
1238 del_lines.append([k, k])
1243 elif document.body[k].find("\\size") != -1:
1245 size = document.body[k].split()[1]
1246 elif find_token(document.body, "\\begin_inset ERT", k) == k:
1247 ert_begin = find_token(document.body, "\\layout", k) + 1
1249 document.warning("Malformed LyX document: Missing '\\layout'.")
1251 ert_end = find_end_of_inset(document.body, k)
1253 document.warning("Malformed LyX document: Missing '\\end_inset'.")
1255 ert = ert2latex(document.body[ert_begin:ert_end], document.format - 1)
1256 if (n > 0 and insets[n - 1] == "lyxline" and
1257 ert == '\\vspace{-1\\parskip}\n'):
1258 # vspace ERT created by convert_breaks() for top lyxline
1260 del_lines[n - 1][1] = ert_end
1266 elif (n > 0 and insets[n - 1] == "vspace" and
1267 find_token(document.body, "\\end_inset", k) == k):
1268 # ignore end of vspace inset
1269 del_lines[n - 1][1] = k
1271 elif is_nonempty_line(document.body[k]):
1276 # Determine space amount for vspace insets
1277 spaceamount = list()
1280 if insets[k] == "vspace":
1281 spaceamount.append(lines[k][2])
1282 arguments.append(' ' + spaceamount[k] + ' ')
1284 spaceamount.append('')
1285 arguments.append(' ')
1287 # Can we convert to top paragraph parameters?
1289 if ((n == 3 and insets[0] == "newpage" and insets[1] == "vspace" and
1290 insets[2] == "lyxline" and top[2]) or
1292 ((insets[0] == "newpage" and insets[1] == "vspace") or
1293 (insets[0] == "newpage" and insets[1] == "lyxline" and top[1]) or
1294 (insets[0] == "vspace" and insets[1] == "lyxline" and top[1]))) or
1295 (n == 1 and insets[0] == "lyxline" and top[0])):
1296 # These insets have been created before a paragraph by
1300 # Can we convert to bottom paragraph parameters?
1302 if ((n == 3 and insets[0] == "lyxline" and not top[0] and
1303 insets[1] == "vspace" and insets[2] == "newpage") or
1305 ((insets[0] == "lyxline" and not top[0] and insets[1] == "vspace") or
1306 (insets[0] == "lyxline" and not top[0] and insets[1] == "newpage") or
1307 (insets[0] == "vspace" and insets[1] == "newpage"))) or
1308 (n == 1 and insets[0] == "lyxline" and not top[0])):
1309 # These insets have been created after a paragraph by
1313 if paragraph_start and paragraph_end:
1314 # We are in a paragraph of our own.
1315 # We must not delete this paragraph if it has parameters
1317 # First try to merge with the previous paragraph.
1318 # We try the previous paragraph first because we would
1319 # otherwise need ERT for two subsequent vspaces.
1320 prev_par = get_paragraph(document.body, this_par - 1, document.format - 1) + 1
1321 if prev_par > 0 and not before:
1322 prev_params = get_par_params(document.body, prev_par + 1)
1324 # determine font size
1325 prev_size = "normal"
1327 while document.body[k][:1] == '\\' and document.body[k].split()[0] in prev_params:
1330 if document.body[k].find("\\size") != -1:
1331 prev_size = document.body[k].split()[1]
1333 elif document.body[k].find("\\begin_inset") != -1:
1335 k = find_end_of_inset(document.body, k)
1336 elif is_nonempty_line(document.body[k]):
1340 if (keywords_bot[insets[k]] in prev_params or
1341 (insets[k] == "lyxline" and sizes[k] != prev_size)):
1346 document.body.insert(prev_par + 1,
1347 keywords_bot[insets[k]] + arguments[k])
1348 del document.body[this_par+n:next_par-1+n]
1351 # Then try next paragraph
1352 if next_par > 0 and not after:
1353 next_params = get_par_params(document.body, next_par + 1)
1355 while document.body[k][:1] == '\\' and document.body[k].split()[0] in next_params:
1357 # determine font size
1358 next_size = "normal"
1361 if document.body[k].find("\\size") != -1:
1362 next_size = document.body[k].split()[1]
1364 elif is_nonempty_line(document.body[k]):
1368 if (keywords_top[insets[k]] in next_params or
1369 (insets[k] == "lyxline" and sizes[k] != next_size)):
1374 document.body.insert(next_par + 1,
1375 keywords_top[insets[k]] + arguments[k])
1376 del document.body[this_par:next_par-1]
1379 elif paragraph_start or paragraph_end:
1380 # Convert to paragraph formatting if we are at the beginning or end
1381 # of a paragraph and the resulting paragraph would not be empty
1382 # The order is important: del and insert invalidate some indices
1384 keywords = keywords_top
1386 keywords = keywords_bot
1389 if keywords[insets[k]] in params:
1394 document.body.insert(this_par + 1,
1395 keywords[insets[k]] + arguments[k])
1396 for j in range(k, n):
1397 del_lines[j][0] = del_lines[j][0] + 1
1398 del_lines[j][1] = del_lines[j][1] + 1
1399 del document.body[del_lines[k][0]:del_lines[k][1]+1]
1400 deleted = del_lines[k][1] - del_lines[k][0] + 1
1401 for j in range(k + 1, n):
1402 del_lines[j][0] = del_lines[j][0] - deleted
1403 del_lines[j][1] = del_lines[j][1] - deleted
1407 # Convert the first inset to ERT.
1408 # The others are converted in the next loop runs (if they exist)
1409 if insets[0] == "vspace":
1410 document.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
1411 '\\layout %s' % document.default_layout, '', '\\backslash ']
1413 if spaceamount[0][-1] == '*':
1414 spaceamount[0] = spaceamount[0][:-1]
1419 # Replace defskip by the actual value
1420 if spaceamount[0] == 'defskip':
1421 spaceamount[0] = defskipamount
1423 # LaTeX does not know \\smallskip* etc
1425 if spaceamount[0] == 'smallskip':
1426 spaceamount[0] = '\\smallskipamount'
1427 elif spaceamount[0] == 'medskip':
1428 spaceamount[0] = '\\medskipamount'
1429 elif spaceamount[0] == 'bigskip':
1430 spaceamount[0] = '\\bigskipamount'
1431 elif spaceamount[0] == 'vfill':
1432 spaceamount[0] = '\\fill'
1434 # Finally output the LaTeX code
1435 if (spaceamount[0] == 'smallskip' or spaceamount[0] == 'medskip' or
1436 spaceamount[0] == 'bigskip' or spaceamount[0] == 'vfill'):
1437 document.body.insert(i, spaceamount[0] + '{}')
1440 document.body.insert(i, 'vspace*{')
1442 document.body.insert(i, 'vspace{')
1443 i = convert_ertbackslash(document.body, i, spaceamount[0], document.format - 1, document.default_layout)
1444 document.body[i] = document.body[i] + '}'
1446 elif insets[0] == "lyxline":
1447 document.body[i] = ''
1448 latexsize = lyxsize2latexsize(size)
1450 document.warning("Could not convert LyX fontsize '%s' to LaTeX font size." % size)
1451 latexsize = '\\normalsize'
1452 i = insert_ert(document.body, i, 'Collapsed',
1453 '\\lyxline{%s}' % latexsize,
1454 document.format - 1, document.default_layout)
1455 # We use \providecommand so that we don't get an error if native
1456 # lyxlines are used (LyX writes first its own preamble and then
1457 # the user specified one)
1458 add_to_preamble(document,
1459 ['% Commands inserted by lyx2lyx for lyxlines',
1460 '\\providecommand{\\lyxline}[1]{',
1461 ' {#1 \\vspace{1ex} \\hrule width \\columnwidth \\vspace{1ex}}'
1463 elif insets[0] == "newpage":
1464 document.body[i] = ''
1465 i = insert_ert(document.body, i, 'Collapsed', '\\newpage{}',
1466 document.format - 1, document.default_layout)
1469 # Convert a LyX length into a LaTeX length
1470 def convert_len(len, special):
1471 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
1472 "page%":"\\pagewidth", "line%":"\\linewidth",
1473 "theight%":"\\textheight", "pheight%":"\\pageheight"}
1475 # Convert special lengths
1476 if special != 'none':
1477 len = '%f\\' % len2value(len) + special
1479 # Convert LyX units to LaTeX units
1480 for unit in units.keys():
1481 if len.find(unit) != -1:
1482 len = '%f' % (len2value(len) / 100) + units[unit]
1488 def convert_ertlen(body, i, len, special, format, default_layout):
1489 """ Convert a LyX length into valid ERT code and append it to body[i]
1490 Return the (maybe incremented) line index i
1491 Convert backslashes and insert the converted length into body. """
1492 return convert_ertbackslash(body, i, convert_len(len, special), format, default_layout)
1496 " Return the value of len without the unit in numerical form. "
1497 result = re.search('([+-]?[0-9.]+)', len)
1499 return float(result.group(1))
1500 # No number means 1.0
1504 def insert_ert(body, i, status, text, format, default_layout):
1505 """ Convert text to ERT and insert it at body[i]
1506 Return the index of the line after the inserted ERT"""
1508 body[i:i] = ['\\begin_inset ERT', 'status ' + status, '']
1511 body[i:i] = ['\\layout %s' % default_layout, '']
1513 body[i:i] = ['\\begin_layout %s' % default_layout, '']
1514 i = i + 1 # i points now to the just created empty line
1515 i = convert_ertbackslash(body, i, text, format, default_layout) + 1
1517 body[i:i] = ['\\end_layout']
1519 body[i:i] = ['', '\\end_inset', '']
1524 def add_to_preamble(document, text):
1525 """ Add text to the preamble if it is not already there.
1526 Only the first line is checked!"""
1528 if find_token(document.preamble, text[0], 0) != -1:
1531 document.preamble.extend(text)
1534 def convert_frameless_box(document):
1535 " Convert frameless box."
1536 pos = ['t', 'c', 'b']
1537 inner_pos = ['c', 't', 'b', 's']
1540 i = find_token(document.body, '\\begin_inset Frameless', i)
1543 j = find_end_of_inset(document.body, i)
1545 document.warning("Malformed LyX document: Missing '\\end_inset'.")
1548 del document.body[i]
1552 params = {'position':0, 'hor_pos':'c', 'has_inner_box':'1',
1553 'inner_pos':1, 'use_parbox':'0', 'width':'100col%',
1554 'special':'none', 'height':'1in',
1555 'height_special':'totalheight', 'collapsed':'false'}
1556 for key in params.keys():
1557 value = get_value(document.body, key, i, j).replace('"', '')
1559 if key == 'position':
1560 # convert new to old position: 'position "t"' -> 0
1561 value = find_token(pos, value, 0)
1564 elif key == 'inner_pos':
1565 # convert inner position
1566 value = find_token(inner_pos, value, 0)
1571 j = del_token(document.body, key, i, j)
1574 # Convert to minipage or ERT?
1575 # Note that the inner_position and height parameters of a minipage
1576 # inset are ignored and not accessible for the user, although they
1577 # are present in the file format and correctly read in and written.
1578 # Therefore we convert to ERT if they do not have their LaTeX
1579 # defaults. These are:
1580 # - the value of "position" for "inner_pos"
1581 # - "\totalheight" for "height"
1582 if (params['use_parbox'] != '0' or
1583 params['has_inner_box'] != '1' or
1584 params['special'] != 'none' or
1585 params['height_special'] != 'totalheight' or
1586 len2value(params['height']) != 1.0):
1588 # Here we know that this box is not supported in file format 224.
1589 # Therefore we need to convert it to ERT. We can't simply convert
1590 # the beginning and end of the box to ERT, because the
1591 # box inset may contain layouts that are different from the
1592 # surrounding layout. After the conversion the contents of the
1593 # box inset is on the same level as the surrounding text, and
1594 # paragraph layouts and align parameters can get mixed up.
1596 # A possible solution for this problem:
1597 # Convert the box to a minipage and redefine the minipage
1598 # environment in ERT so that the original box is simulated.
1599 # For minipages we could do this in a way that the width and
1600 # position can still be set from LyX, but this did not work well.
1601 # This is not possible for parboxes either, so we convert the
1602 # original box to ERT, put the minipage inset inside the box
1603 # and redefine the minipage environment to be empty.
1605 # Commands that are independant of a particular box can go to
1607 # We need to define lyxtolyxrealminipage with 3 optional
1608 # arguments although LyX 1.3 uses only the first one.
1609 # Otherwise we will get LaTeX errors if this document is
1610 # converted to format 225 or above again (LyX 1.4 uses all
1611 # optional arguments).
1612 add_to_preamble(document,
1613 ['% Commands inserted by lyx2lyx for frameless boxes',
1614 '% Save the original minipage environment',
1615 '\\let\\lyxtolyxrealminipage\\minipage',
1616 '\\let\\endlyxtolyxrealminipage\\endminipage',
1617 '% Define an empty lyxtolyximinipage environment',
1618 '% with 3 optional arguments',
1619 '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1620 '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1621 ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1622 ' {\\end{lyxtolyxiiiminipage}}',
1623 '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1624 ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1625 ' {\\end{lyxtolyxiiminipage}}',
1626 '\\newenvironment{lyxtolyxminipage}[1][c]%',
1627 ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1628 ' {\\end{lyxtolyximinipage}}'])
1630 if params['use_parbox'] != '0':
1633 ert = '\\begin{lyxtolyxrealminipage}'
1635 # convert optional arguments only if not latex default
1636 if (pos[params['position']] != 'c' or
1637 inner_pos[params['inner_pos']] != pos[params['position']] or
1638 params['height_special'] != 'totalheight' or
1639 len2value(params['height']) != 1.0):
1640 ert = ert + '[' + pos[params['position']] + ']'
1641 if (inner_pos[params['inner_pos']] != pos[params['position']] or
1642 params['height_special'] != 'totalheight' or
1643 len2value(params['height']) != 1.0):
1644 ert = ert + '[' + convert_len(params['height'],
1645 params['height_special']) + ']'
1646 if inner_pos[params['inner_pos']] != pos[params['position']]:
1647 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1649 ert = ert + '{' + convert_len(params['width'],
1650 params['special']) + '}'
1652 if params['use_parbox'] != '0':
1654 ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1655 ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1658 i = insert_ert(document.body, i, 'Collapsed', ert, document.format - 1, document.default_layout)
1659 j = j + i - old_i - 1
1661 document.body[i:i] = ['\\begin_inset Minipage',
1662 'position %d' % params['position'],
1665 'width "' + params['width'] + '"',
1666 'collapsed ' + params['collapsed']]
1670 # Restore the original minipage environment since we may have
1671 # minipages inside this box.
1672 # Start a new paragraph because the following may be nonstandard
1673 document.body[i:i] = ['\\layout %s' % document.default_layout, '', '']
1676 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1677 ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1679 i = insert_ert(document.body, i, 'Collapsed', ert, document.format - 1, document.default_layout)
1680 j = j + i - old_i - 1
1682 # Redefine the minipage end before the inset end.
1683 # Start a new paragraph because the previous may be nonstandard
1684 document.body[j:j] = ['\\layout %s' % document.default_layout, '', '']
1686 ert = '\\let\\endminipage\\endlyxtolyxminipage'
1687 j = insert_ert(document.body, j, 'Collapsed', ert, document.format - 1, document.default_layout)
1689 document.body.insert(j, '')
1692 # LyX writes '%\n' after each box. Therefore we need to end our
1693 # ERT with '%\n', too, since this may swallow a following space.
1694 if params['use_parbox'] != '0':
1697 ert = '\\end{lyxtolyxrealminipage}%\n'
1698 j = insert_ert(document.body, j, 'Collapsed', ert, document.format - 1, document.default_layout)
1700 # We don't need to restore the original minipage after the inset
1701 # end because the scope of the redefinition is the original box.
1705 # Convert to minipage
1706 document.body[i:i] = ['\\begin_inset Minipage',
1707 'position %d' % params['position'],
1708 'inner_position %d' % params['inner_pos'],
1709 'height "' + params['height'] + '"',
1710 'width "' + params['width'] + '"',
1711 'collapsed ' + params['collapsed']]
1715 def remove_branches(document):
1716 " Remove branches. "
1719 i = find_token(document.header, "\\branch", i)
1722 document.warning("Removing branch %s." % document.header[i].split()[1])
1723 j = find_token(document.header, "\\end_branch", i)
1725 document.warning("Malformed LyX document: Missing '\\end_branch'.")
1727 del document.header[i:j+1]
1731 i = find_token(document.body, "\\begin_inset Branch", i)
1734 j = find_end_of_inset(document.body, i)
1736 document.warning("Malformed LyX document: Missing '\\end_inset'.")
1739 del document.body[i]
1740 del document.body[j - 1]
1741 # Seach for a line starting 'collapsed'
1742 # If, however, we find a line starting '\layout'
1743 # (_always_ present) then break with a warning message
1746 if (document.body[i][:9] == "collapsed"):
1747 del document.body[i]
1750 elif (document.body[i][:7] == "\\layout"):
1751 if collapsed_found == 0:
1752 document.warning("Malformed LyX document: Missing 'collapsed'.")
1753 # Delete this new paragraph, since it would not appear in
1754 # .tex output. This avoids also empty paragraphs.
1755 del document.body[i]
1760 def convert_jurabib(document):
1761 " Convert jurabib. "
1762 i = find_token(document.header, '\\use_numerical_citations', 0)
1764 document.warning("Malformed lyx document: Missing '\\use_numerical_citations'.")
1766 document.header.insert(i + 1, '\\use_jurabib 0')
1769 def revert_jurabib(document):
1771 i = find_token(document.header, '\\use_jurabib', 0)
1773 document.warning("Malformed lyx document: Missing '\\use_jurabib'.")
1775 if get_value(document.header, '\\use_jurabib', 0) != "0":
1776 document.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1777 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1779 del document.header[i]
1782 def convert_bibtopic(document):
1783 " Convert bibtopic. "
1784 i = find_token(document.header, '\\use_jurabib', 0)
1786 document.warning("Malformed lyx document: Missing '\\use_jurabib'.")
1788 document.header.insert(i + 1, '\\use_bibtopic 0')
1791 def revert_bibtopic(document):
1792 " Revert bibtopic. "
1793 i = find_token(document.header, '\\use_bibtopic', 0)
1795 document.warning("Malformed lyx document: Missing '\\use_bibtopic'.")
1797 if get_value(document.header, '\\use_bibtopic', 0) != "0":
1798 document.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1799 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1800 del document.header[i]
1803 def convert_float(document):
1804 " Convert sideway floats. "
1807 i = find_token_exact(document.body, '\\begin_inset Float', i)
1810 # Seach for a line starting 'wide'
1811 # If, however, we find a line starting '\begin_layout'
1812 # (_always_ present) then break with a warning message
1815 if (document.body[i][:4] == "wide"):
1816 document.body.insert(i + 1, 'sideways false')
1818 elif (document.body[i][:13] == "\\begin_layout"):
1819 document.warning("Malformed lyx document: Missing 'wide'.")
1825 def revert_float(document):
1826 " Revert sideway floats. "
1829 i = find_token_exact(document.body, '\\begin_inset Float', i)
1832 j = find_end_of_inset(document.body, i)
1834 document.warning("Malformed lyx document: Missing '\\end_inset'.")
1837 if get_value(document.body, 'sideways', i, j) != "false":
1838 document.warning("Conversion of 'sideways true' not yet implemented.")
1839 # Don't remove 'sideways' so that people will get warnings by lyx
1842 del_token(document.body, 'sideways', i, j)
1846 def convert_graphics(document):
1847 """ Add extension to documentnames of insetgraphics if necessary.
1851 i = find_token(document.body, "\\begin_inset Graphics", i)
1855 j = find_token_exact(document.body, "documentname", i)
1859 filename = document.body[j].split()[1]
1860 absname = os.path.normpath(os.path.join(document.dir, filename))
1861 if document.input == stdin and not os.path.isabs(filename):
1862 # We don't know the directory and cannot check the document.
1863 # We could use a heuristic and take the current directory,
1864 # and we could try to find out if documentname has an extension,
1865 # but that would be just guesses and could be wrong.
1866 document.warning("""Warning: Can not determine whether document
1868 needs an extension when reading from standard input.
1869 You may need to correct the document manually or run
1870 lyx2lyx again with the .lyx document as commandline argument.""" % filename)
1872 # This needs to be the same algorithm as in pre 233 insetgraphics
1873 if access(absname, F_OK):
1875 if access(absname + ".ps", F_OK):
1876 document.body[j] = document.body[j].replace(filename, filename + ".ps")
1878 if access(absname + ".eps", F_OK):
1879 document.body[j] = document.body[j].replace(filename, filename + ".eps")
1882 def convert_names(document):
1883 """ Convert in the docbook backend from firstname and surname style
1886 if document.backend != "docbook":
1892 i = find_token(document.body, "\\begin_layout Author", i)
1897 while document.body[i] == "":
1900 if document.body[i][:11] != "\\end_layout" or document.body[i+2][:13] != "\\begin_deeper":
1905 i = find_end_of( document.body, i+3, "\\begin_deeper","\\end_deeper")
1907 # something is really wrong, abort
1908 document.warning("Missing \\end_deeper, after style Author.")
1909 document.warning("Aborted attempt to parse FirstName and Surname.")
1911 firstname, surname = "", ""
1913 name = document.body[k:i]
1915 j = find_token(name, "\\begin_layout FirstName", 0)
1918 while(name[j] != "\\end_layout"):
1919 firstname = firstname + name[j]
1922 j = find_token(name, "\\begin_layout Surname", 0)
1925 while(name[j] != "\\end_layout"):
1926 surname = surname + name[j]
1930 del document.body[k+2:i+1]
1932 document.body[k-1:k-1] = ["", "",
1933 "\\begin_inset CharStyle Firstname",
1936 '\\begin_layout %s' % document.default_layout,
1944 "\\begin_inset CharStyle Surname",
1947 '\\begin_layout %s' % document.default_layout,
1956 def revert_names(document):
1957 """ Revert in the docbook backend from firstname and surname char style
1960 if document.backend != "docbook":
1964 def convert_cite_engine(document):
1965 r""" \use_natbib 1 \cite_engine <style>
1966 \use_numerical_citations 0 -> where <style> is one of
1967 \use_jurabib 0 "basic", "natbib_authoryear","""
1969 a = find_token(document.header, "\\use_natbib", 0)
1971 document.warning("Malformed lyx document: Missing '\\use_natbib'.")
1974 b = find_token(document.header, "\\use_numerical_citations", 0)
1975 if b == -1 or b != a+1:
1976 document.warning("Malformed lyx document: Missing '\\use_numerical_citations'.")
1979 c = find_token(document.header, "\\use_jurabib", 0)
1980 if c == -1 or c != b+1:
1981 document.warning("Malformed lyx document: Missing '\\use_jurabib'.")
1984 use_natbib = int(document.header[a].split()[1])
1985 use_numerical_citations = int(document.header[b].split()[1])
1986 use_jurabib = int(document.header[c].split()[1])
1988 cite_engine = "basic"
1990 if use_numerical_citations:
1991 cite_engine = "natbib_numerical"
1993 cite_engine = "natbib_authoryear"
1995 cite_engine = "jurabib"
1997 del document.header[a:c+1]
1998 document.header.insert(a, "\\cite_engine " + cite_engine)
2001 def revert_cite_engine(document):
2002 " Revert the cite engine. "
2003 i = find_token(document.header, "\\cite_engine", 0)
2005 document.warning("Malformed lyx document: Missing '\\cite_engine'.")
2008 cite_engine = document.header[i].split()[1]
2013 if cite_engine == "natbib_numerical":
2016 elif cite_engine == "natbib_authoryear":
2018 elif cite_engine == "jurabib":
2021 del document.header[i]
2022 document.header.insert(i, "\\use_jurabib " + use_jurabib)
2023 document.header.insert(i, "\\use_numerical_citations " + use_numerical)
2024 document.header.insert(i, "\\use_natbib " + use_natbib)
2027 def convert_paperpackage(document):
2028 " Convert paper package. "
2029 i = find_token(document.header, "\\paperpackage", 0)
2033 packages = {'default':'none','a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
2034 if len(document.header[i].split()) > 1:
2035 paperpackage = document.header[i].split()[1]
2036 document.header[i] = document.header[i].replace(paperpackage, packages[paperpackage])
2038 document.header[i] = document.header[i] + ' widemarginsa4'
2041 def revert_paperpackage(document):
2042 " Revert paper package. "
2043 i = find_token(document.header, "\\paperpackage", 0)
2047 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
2048 'widemarginsa4':'', 'default': 'default'}
2049 if len(document.header[i].split()) > 1:
2050 paperpackage = document.header[i].split()[1]
2052 paperpackage = 'default'
2053 document.header[i] = document.header[i].replace(paperpackage, packages[paperpackage])
2056 def convert_bullets(document):
2057 " Convert bullets. "
2060 i = find_token(document.header, "\\bullet", i)
2063 if document.header[i][:12] == '\\bulletLaTeX':
2064 document.header[i] = document.header[i] + ' ' + document.header[i+1].strip()
2067 document.header[i] = document.header[i] + ' ' + document.header[i+1].strip() +\
2068 ' ' + document.header[i+2].strip() + ' ' + document.header[i+3].strip()
2070 del document.header[i+1:i + n]
2074 def revert_bullets(document):
2078 i = find_token(document.header, "\\bullet", i)
2081 if document.header[i][:12] == '\\bulletLaTeX':
2082 n = document.header[i].find('"')
2084 document.warning("Malformed header.")
2087 document.header[i:i+1] = [document.header[i][:n-1],'\t' + document.header[i][n:], '\\end_bullet']
2090 frag = document.header[i].split()
2092 document.warning("Malformed header.")
2095 document.header[i:i+1] = [frag[0] + ' ' + frag[1],
2103 def add_begin_header(document):
2104 r" Add \begin_header and \begin_document. "
2105 i = find_token(document.header, '\\lyxformat', 0)
2106 document.header.insert(i+1, '\\begin_header')
2107 document.header.insert(i+1, '\\begin_document')
2110 def remove_begin_header(document):
2111 r" Remove \begin_header and \begin_document. "
2112 i = find_token(document.header, "\\begin_document", 0)
2114 del document.header[i]
2115 i = find_token(document.header, "\\begin_header", 0)
2117 del document.header[i]
2120 def add_begin_body(document):
2121 r" Add and \begin_document and \end_document"
2122 document.body.insert(0, '\\begin_body')
2123 document.body.insert(1, '')
2124 i = find_token(document.body, "\\end_document", 0)
2125 document.body.insert(i, '\\end_body')
2127 def remove_begin_body(document):
2128 r" Remove \begin_body and \end_body"
2129 i = find_token(document.body, "\\begin_body", 0)
2131 del document.body[i]
2132 if not document.body[i]:
2133 del document.body[i]
2134 i = find_token(document.body, "\\end_body", 0)
2136 del document.body[i]
2139 def normalize_papersize(document):
2140 r" Normalize \papersize"
2141 i = find_token(document.header, '\\papersize', 0)
2145 tmp = document.header[i].split()
2146 if tmp[1] == "Default":
2147 document.header[i] = '\\papersize default'
2149 if tmp[1] == "Custom":
2150 document.header[i] = '\\papersize custom'
2153 def denormalize_papersize(document):
2154 r" Revert \papersize"
2155 i = find_token(document.header, '\\papersize', 0)
2159 tmp = document.header[i].split()
2160 if tmp[1] == "custom":
2161 document.header[i] = '\\papersize Custom'
2164 def strip_end_space(document):
2165 " Strip spaces at end of command line. "
2166 for i in range(len(document.body)):
2167 if document.body[i][:1] == '\\':
2168 document.body[i] = document.body[i].strip()
2171 def use_x_boolean(document):
2172 r" Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes"
2173 bin2bool = {'0': 'false', '1': 'true'}
2174 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2175 i = find_token(document.header, use, 0)
2178 decompose = document.header[i].split()
2179 document.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
2182 def use_x_binary(document):
2183 r" Use digit values for \use_geometry, \use_bibtopic and \tracking_changes"
2184 bool2bin = {'false': '0', 'true': '1'}
2185 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2186 i = find_token(document.header, use, 0)
2189 decompose = document.header[i].split()
2190 document.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
2193 def normalize_paragraph_params(document):
2194 " Place all the paragraph parameters in their own line. "
2195 body = document.body
2197 allowed_parameters = '\\paragraph_spacing', '\\noindent', \
2198 '\\align', '\\labelwidthstring', "\\start_of_appendix", \
2203 i = find_token(document.body, '\\begin_layout', i)
2209 if body[i].strip() and body[i].split()[0] not in allowed_parameters:
2212 j = body[i].find('\\', 1)
2215 body[i:i+1] = [body[i][:j].strip(), body[i][j:]]
2220 def convert_output_changes (document):
2221 " Add output_changes parameter. "
2222 i = find_token(document.header, '\\tracking_changes', 0)
2224 document.warning("Malformed lyx document: Missing '\\tracking_changes'.")
2226 document.header.insert(i+1, '\\output_changes true')
2229 def revert_output_changes (document):
2230 " Remove output_changes parameter. "
2231 i = find_token(document.header, '\\output_changes', 0)
2234 del document.header[i]
2237 def convert_ert_paragraphs(document):
2238 " Convert paragraph breaks and sanitize paragraphs. "
2239 forbidden_settings = [
2240 # paragraph parameters
2241 '\\paragraph_spacing', '\\labelwidthstring',
2242 '\\start_of_appendix', '\\noindent',
2243 '\\leftindent', '\\align',
2245 '\\family', '\\series', '\\shape', '\\size',
2246 '\\emph', '\\numeric', '\\bar', '\\noun',
2247 '\\color', '\\lang']
2250 i = find_token(document.body, '\\begin_inset ERT', i)
2253 j = find_end_of_inset(document.body, i)
2255 document.warning("Malformed lyx document: Missing '\\end_inset'.")
2259 # convert non-standard paragraphs to standard
2262 k = find_token(document.body, "\\begin_layout", k, j)
2265 document.body[k] = '\\begin_layout %s' % document.default_layout
2268 # remove all paragraph parameters and font settings
2271 if (document.body[k].strip() and
2272 document.body[k].split()[0] in forbidden_settings):
2273 del document.body[k]
2278 # insert an empty paragraph before each paragraph but the first
2282 k = find_token(document.body, "\\begin_layout", k, j)
2289 document.body[k:k] = ['\\begin_layout %s' % document.default_layout, "",
2294 # convert \\newline to new paragraph
2297 k = find_token(document.body, "\\newline", k, j)
2300 document.body[k:k+1] = ["\\end_layout", "", '\\begin_layout %s' % document.default_layout]
2303 # We need an empty line if document.default_layout == ''
2304 if document.body[k-1] != '':
2305 document.body.insert(k-1, '')
2311 def revert_ert_paragraphs(document):
2312 " Remove double paragraph breaks. "
2315 i = find_token(document.body, '\\begin_inset ERT', i)
2318 j = find_end_of_inset(document.body, i)
2320 document.warning("Malformed lyx document: Missing '\\end_inset'.")
2324 # replace paragraph breaks with \newline
2327 k = find_token(document.body, "\\end_layout", k, j)
2328 l = find_token(document.body, "\\begin_layout", k, j)
2329 if k == -1 or l == -1:
2331 document.body[k:l+1] = ["\\newline"]
2335 # replace double \newlines with paragraph breaks
2338 k = find_token(document.body, "\\newline", k, j)
2342 while document.body[l] == "":
2344 if document.body[l].strip() and document.body[l].split()[0] == "\\newline":
2345 document.body[k:l+1] = ["\\end_layout", "",
2346 '\\begin_layout %s' % document.default_layout]
2349 # We need an empty line if document.default_layout == ''
2350 if document.body[l+1] != '':
2351 document.body.insert(l+1, '')
2359 def convert_french(document):
2360 " Convert frenchb. "
2361 regexp = re.compile(r'^\\language\s+frenchb')
2362 i = find_re(document.header, regexp, 0)
2364 document.header[i] = "\\language french"
2366 # Change language in the document body
2367 regexp = re.compile(r'^\\lang\s+frenchb')
2370 i = find_re(document.body, regexp, i)
2373 document.body[i] = "\\lang french"
2377 def remove_paperpackage(document):
2378 " Remove paper package. "
2379 i = find_token(document.header, '\\paperpackage', 0)
2384 paperpackage = document.header[i].split()[1]
2386 del document.header[i]
2388 if paperpackage not in ("a4", "a4wide", "widemarginsa4"):
2391 conv = {"a4":"\\usepackage{a4}","a4wide": "\\usepackage{a4wide}",
2392 "widemarginsa4": "\\usepackage[widemargins]{a4}"}
2393 # for compatibility we ensure it is the first entry in preamble
2394 document.preamble[0:0] = [conv[paperpackage]]
2396 i = find_token(document.header, '\\papersize', 0)
2398 document.header[i] = "\\papersize default"
2401 def remove_quotestimes(document):
2402 " Remove quotestimes. "
2403 i = find_token(document.header, '\\quotes_times', 0)
2406 del document.header[i]
2409 def convert_sgml_paragraphs(document):
2410 " Convert SGML paragraphs. "
2411 if document.backend != "docbook":
2416 i = find_token(document.body, "\\begin_layout SGML", i)
2421 document.body[i] = "\\begin_layout Standard"
2422 j = find_token(document.body, "\\end_layout", i)
2424 document.body[j+1:j+1] = ['','\\end_inset','','','\\end_layout']
2425 document.body[i+1:i+1] = ['\\begin_inset ERT','status inlined','','\\begin_layout Standard','']
2433 supported_versions = ["1.4.%d" % i for i in range(3)] + ["1.4"]
2434 convert = [[222, [insert_tracking_changes, add_end_header, convert_amsmath]],
2435 [223, [remove_color_default, convert_spaces, convert_bibtex, remove_insetparent]],
2436 [224, [convert_external, convert_comment]],
2437 [225, [add_end_layout, layout2begin_layout, convert_end_document,
2438 convert_table_valignment_middle, convert_breaks]],
2439 [226, [convert_note]],
2440 [227, [convert_box]],
2441 [228, [convert_collapsable, convert_ert]],
2442 [229, [convert_minipage]],
2443 [230, [convert_jurabib]],
2444 [231, [convert_float]],
2445 [232, [convert_bibtopic]],
2446 [233, [convert_graphics, convert_names]],
2447 [234, [convert_cite_engine]],
2448 [235, [convert_paperpackage]],
2449 [236, [convert_bullets, add_begin_header, add_begin_body,
2450 normalize_papersize, strip_end_space]],
2451 [237, [use_x_boolean]],
2452 [238, [update_latexaccents]],
2453 [239, [normalize_paragraph_params]],
2454 [240, [convert_output_changes]],
2455 [241, [convert_ert_paragraphs]],
2456 [242, [convert_french]],
2457 [243, [remove_paperpackage]],
2458 [244, [rename_spaces]],
2459 [245, [remove_quotestimes, convert_sgml_paragraphs]]]
2461 revert = [[244, []],
2462 [243, [revert_space_names]],
2465 [240, [revert_ert_paragraphs]],
2466 [239, [revert_output_changes]],
2469 [236, [use_x_binary]],
2470 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
2472 [234, [revert_paperpackage]],
2473 [233, [revert_cite_engine]],
2474 [232, [revert_names]],
2475 [231, [revert_bibtopic]],
2476 [230, [revert_float]],
2477 [229, [revert_jurabib]],
2479 [227, [revert_collapsable, revert_ert]],
2480 [226, [revert_box, revert_external_2]],
2481 [225, [revert_note]],
2482 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
2483 revert_valignment_middle, revert_breaks, convert_frameless_box,
2485 [223, [revert_external_2, revert_comment, revert_eqref]],
2486 [222, [revert_spaces, revert_bibtex]],
2487 [221, [revert_amsmath, rm_end_header, rm_tracking_changes, rm_body_changes]]]
2490 if __name__ == "__main__":