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 pre-unicode 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 # consecutive begin_deeper only insert one end_layout
470 while document.body[i].startswith('\\begin_deeper'):
472 struct_stack.append(token)
475 if token == "\\end_deeper":
476 if struct_stack[-1] == '\\layout':
477 document.body.insert(i, '\\end_layout')
484 document.body.insert(i, "")
485 document.body.insert(i, "\\end_layout")
489 def rm_end_layout(document):
490 " Remove \end_layout"
493 i = find_token(document.body, '\\end_layout', i)
501 def insert_tracking_changes(document):
502 " Handle change tracking keywords."
503 i = find_token(document.header, "\\tracking_changes", 0)
505 document.header.append("\\tracking_changes 0")
508 def rm_tracking_changes(document):
509 " Remove change tracking keywords."
510 i = find_token(document.header, "\\author", 0)
512 del document.header[i]
514 i = find_token(document.header, "\\tracking_changes", 0)
517 del document.header[i]
520 def rm_body_changes(document):
521 " Remove body changes."
524 i = find_token(document.body, "\\change_", i)
531 def layout2begin_layout(document):
532 " \layout -> \begin_layout "
535 i = find_token(document.body, '\\layout', i)
539 document.body[i] = document.body[i].replace('\\layout', '\\begin_layout')
543 def begin_layout2layout(document):
544 " \begin_layout -> \layout "
547 i = find_token(document.body, '\\begin_layout', i)
551 document.body[i] = document.body[i].replace('\\begin_layout', '\\layout')
555 def convert_valignment_middle(body, start, end):
556 'valignment="center" -> valignment="middle"'
557 for i in range(start, end):
558 if re.search('^<(column|cell) .*valignment="center".*>$', body[i]):
559 body[i] = body[i].replace('valignment="center"', 'valignment="middle"')
562 def convert_table_valignment_middle(document):
563 " Convert table valignment, center -> middle"
564 regexp = re.compile(r'^\\begin_inset\s+Tabular')
567 i = find_re(document.body, regexp, i)
570 j = find_end_of_inset(document.body, i + 1)
572 #this should not happen
573 convert_valignment_middle(document.body, i + 1, len(document.body))
575 convert_valignment_middle(document.body, i + 1, j)
579 def revert_table_valignment_middle(body, start, end):
580 " valignment, middle -> center"
581 for i in range(start, end):
582 if re.search('^<(column|cell) .*valignment="middle".*>$', body[i]):
583 body[i] = body[i].replace('valignment="middle"', 'valignment="center"')
586 def revert_valignment_middle(document):
587 " Convert table valignment, middle -> center"
588 regexp = re.compile(r'^\\begin_inset\s+Tabular')
591 i = find_re(document.body, regexp, i)
594 j = find_end_of_inset(document.body, i + 1)
596 #this should not happen
597 revert_table_valignment_middle(document.body, i + 1, len(document.body))
599 revert_table_valignment_middle(document.body, i + 1, j)
603 def convert_end_document(document):
604 "\\the_end -> \\end_document"
605 i = find_token(document.body, "\\the_end", 0)
607 document.body.append("\\end_document")
609 document.body[i] = "\\end_document"
612 def revert_end_document(document):
613 "\\end_document -> \\the_end"
614 i = find_token(document.body, "\\end_document", 0)
616 document.body.append("\\the_end")
618 document.body[i] = "\\the_end"
621 def convert_breaks(document):
623 Convert line and page breaks
626 \line_top \line_bottom \pagebreak_top \pagebreak_bottom \added_space_top xxx \added_space_bottom yyy
630 \begin layout Standard
636 \begin layout Standard
643 \begin_inset VSpace xxx
648 \begin_inset VSpace xxx
656 par_params = ('added_space_bottom', 'added_space_top', 'align',
657 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
658 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
660 font_attributes = ['\\family', '\\series', '\\shape', '\\emph',
661 '\\numeric', '\\bar', '\\noun', '\\color', '\\lang']
662 attribute_values = ['default', 'default', 'default', 'default',
663 'default', 'default', 'default', 'none', document.language]
666 i = find_token(document.body, "\\begin_layout", i)
669 layout = get_layout(document.body[i], document.default_layout)
672 # Merge all paragraph parameters into a single line
673 # We cannot check for '\\' only because paragraphs may start e.g.
675 while document.body[i + 1][:1] == '\\' and document.body[i + 1][1:].split()[0] in par_params:
676 document.body[i] = document.body[i + 1] + ' ' + document.body[i]
677 del document.body[i+1]
679 line_top = document.body[i].find("\\line_top")
680 line_bot = document.body[i].find("\\line_bottom")
681 pb_top = document.body[i].find("\\pagebreak_top")
682 pb_bot = document.body[i].find("\\pagebreak_bottom")
683 vspace_top = document.body[i].find("\\added_space_top")
684 vspace_bot = document.body[i].find("\\added_space_bottom")
686 if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1:
689 # Do we have a nonstandard paragraph? We need to create new paragraphs
690 # if yes to avoid putting lyxline etc. inside of special environments.
691 # This is wrong for itemize and enumerate environments, but it is
692 # impossible to convert these correctly.
693 # We want to avoid new paragraphs if possible becauase we want to
694 # inherit font sizes.
696 if (not document.is_default_layout(layout) or
697 document.body[i].find("\\align") != -1 or
698 document.body[i].find("\\labelwidthstring") != -1 or
699 document.body[i].find("\\noindent") != -1):
702 # get the font size of the beginning of this paragraph, since we need
703 # it for the lyxline inset
705 while not is_nonempty_line(document.body[j]):
708 if document.body[j].find("\\size") != -1:
709 size_top = document.body[j].split()[1]
711 for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom":
712 document.body[i] = document.body[i].replace(tag, "")
715 # the position could be change because of the removal of other
716 # paragraph properties above
717 vspace_top = document.body[i].find("\\added_space_top")
718 tmp_list = document.body[i][vspace_top:].split()
719 vspace_top_value = tmp_list[1]
720 document.body[i] = document.body[i][:vspace_top] + " ".join(tmp_list[2:])
723 # the position could be change because of the removal of other
724 # paragraph properties above
725 vspace_bot = document.body[i].find("\\added_space_bottom")
726 tmp_list = document.body[i][vspace_bot:].split()
727 vspace_bot_value = tmp_list[1]
728 document.body[i] = document.body[i][:vspace_bot] + " ".join(tmp_list[2:])
730 document.body[i] = document.body[i].strip()
733 # Create an empty paragraph or paragraph fragment for line and
734 # page break that belong above the paragraph
735 if pb_top !=-1 or line_top != -1 or vspace_top != -1:
737 paragraph_above = list()
739 # We need to create an extra paragraph for nonstandard environments
740 paragraph_above = ['\\begin_layout %s' % document.default_layout, '']
743 paragraph_above.extend(['\\newpage ',''])
746 paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','',''])
750 paragraph_above.extend(['\\size ' + size_top + ' '])
751 # We need an additional vertical space of -\parskip.
752 # We can't use the vspace inset because it does not know \parskip.
753 paragraph_above.extend(['\\lyxline ', '', ''])
754 insert_ert(paragraph_above, len(paragraph_above) - 1, 'Collapsed',
755 '\\vspace{-1\\parskip}\n', document.format + 1, document.default_layout)
756 paragraph_above.extend([''])
759 paragraph_above.extend(['\\end_layout ',''])
760 # insert new paragraph above the current paragraph
761 document.body[i-2:i-2] = paragraph_above
763 # insert new lines at the beginning of the current paragraph
764 document.body[i:i] = paragraph_above
766 i = i + len(paragraph_above)
768 # Ensure that nested style are converted later.
769 k = find_end_of(document.body, i, "\\begin_layout", "\\end_layout")
774 if pb_bot !=-1 or line_bot != -1 or vspace_bot != -1:
776 # get the font size of the end of this paragraph
780 if document.body[j].find("\\size") != -1:
781 size_bot = document.body[j].split()[1]
783 elif document.body[j].find("\\begin_inset") != -1:
785 j = find_end_of_inset(document.body, j)
789 paragraph_below = list()
791 # We need to create an extra paragraph for nonstandard environments
792 paragraph_below = ['', '\\begin_layout %s' % document.default_layout, '']
794 for a in range(len(font_attributes)):
795 if find_token(document.body, font_attributes[a], i, k) != -1:
796 paragraph_below.extend([font_attributes[a] + ' ' + attribute_values[a]])
799 if nonstandard and size_bot != '':
800 paragraph_below.extend(['\\size ' + size_bot + ' '])
801 paragraph_below.extend(['\\lyxline ',''])
803 paragraph_below.extend(['\\size default '])
806 paragraph_below.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','',''])
809 paragraph_below.extend(['\\newpage ',''])
812 paragraph_below.extend(['\\end_layout '])
813 # insert new paragraph below the current paragraph
814 document.body[k+1:k+1] = paragraph_below
816 # insert new lines at the end of the current paragraph
817 document.body[k:k] = paragraph_below
820 def convert_note(document):
824 i = find_tokens(document.body, ["\\begin_inset Note",
825 "\\begin_inset Comment",
826 "\\begin_inset Greyedout"], i)
830 document.body[i] = document.body[i][0:13] + 'Note ' + document.body[i][13:]
834 def revert_note(document):
836 note_header = "\\begin_inset Note "
839 i = find_token(document.body, note_header, i)
843 document.body[i] = "\\begin_inset " + document.body[i][len(note_header):]
847 def convert_box(document):
851 i = find_tokens(document.body, ["\\begin_inset Boxed",
852 "\\begin_inset Doublebox",
853 "\\begin_inset Frameless",
854 "\\begin_inset ovalbox",
855 "\\begin_inset Ovalbox",
856 "\\begin_inset Shadowbox"], i)
860 document.body[i] = document.body[i][0:13] + 'Box ' + document.body[i][13:]
864 def revert_box(document):
866 box_header = "\\begin_inset Box "
869 i = find_token(document.body, box_header, i)
873 document.body[i] = "\\begin_inset " + document.body[i][len(box_header):]
877 def convert_collapsable(document):
878 " Convert collapsed insets. "
881 i = find_tokens_exact(document.body, ["\\begin_inset Box",
882 "\\begin_inset Branch",
883 "\\begin_inset CharStyle",
884 "\\begin_inset Float",
885 "\\begin_inset Foot",
886 "\\begin_inset Marginal",
887 "\\begin_inset Note",
888 "\\begin_inset OptArg",
889 "\\begin_inset Wrap"], i)
893 # Seach for a line starting 'collapsed'
894 # If, however, we find a line starting '\begin_layout'
895 # (_always_ present) then break with a warning message
898 if (document.body[i] == "collapsed false"):
899 document.body[i] = "status open"
901 elif (document.body[i] == "collapsed true"):
902 document.body[i] = "status collapsed"
904 elif (document.body[i][:13] == "\\begin_layout"):
905 document.warning("Malformed LyX document: Missing 'collapsed'.")
912 def revert_collapsable(document):
913 " Revert collapsed insets. "
916 i = find_tokens_exact(document.body, ["\\begin_inset Box",
917 "\\begin_inset Branch",
918 "\\begin_inset CharStyle",
919 "\\begin_inset Float",
920 "\\begin_inset Foot",
921 "\\begin_inset Marginal",
922 "\\begin_inset Note",
923 "\\begin_inset OptArg",
924 "\\begin_inset Wrap"], i)
928 # Seach for a line starting 'status'
929 # If, however, we find a line starting '\begin_layout'
930 # (_always_ present) then break with a warning message
933 if (document.body[i] == "status open"):
934 document.body[i] = "collapsed false"
936 elif (document.body[i] == "status collapsed" or
937 document.body[i] == "status inlined"):
938 document.body[i] = "collapsed true"
940 elif (document.body[i][:13] == "\\begin_layout"):
941 document.warning("Malformed LyX document: Missing 'status'.")
948 def convert_ert(document):
952 i = find_token(document.body, "\\begin_inset ERT", i)
956 # Seach for a line starting 'status'
957 # If, however, we find a line starting '\begin_layout'
958 # (_always_ present) then break with a warning message
961 if (document.body[i] == "status Open"):
962 document.body[i] = "status open"
964 elif (document.body[i] == "status Collapsed"):
965 document.body[i] = "status collapsed"
967 elif (document.body[i] == "status Inlined"):
968 document.body[i] = "status inlined"
970 elif (document.body[i][:13] == "\\begin_layout"):
971 document.warning("Malformed LyX document: Missing 'status'.")
978 def revert_ert(document):
982 i = find_token(document.body, "\\begin_inset ERT", i)
986 # Seach for a line starting 'status'
987 # If, however, we find a line starting '\begin_layout'
988 # (_always_ present) then break with a warning message
991 if (document.body[i] == "status open"):
992 document.body[i] = "status Open"
994 elif (document.body[i] == "status collapsed"):
995 document.body[i] = "status Collapsed"
997 elif (document.body[i] == "status inlined"):
998 document.body[i] = "status Inlined"
1000 elif (document.body[i][:13] == "\\begin_layout"):
1001 document.warning("Malformed LyX document : Missing 'status'.")
1008 def convert_minipage(document):
1009 """ Convert minipages to the box inset.
1010 We try to use the same order of arguments as lyx does.
1013 inner_pos = ["c","t","b","s"]
1017 i = find_token(document.body, "\\begin_inset Minipage", i)
1021 document.body[i] = "\\begin_inset Box Frameless"
1024 # convert old to new position using the pos list
1025 if document.body[i][:8] == "position":
1026 document.body[i] = 'position "%s"' % pos[int(document.body[i][9])]
1028 document.body.insert(i, 'position "%s"' % pos[0])
1031 document.body.insert(i, 'hor_pos "c"')
1033 document.body.insert(i, 'has_inner_box 1')
1036 # convert the inner_position
1037 if document.body[i][:14] == "inner_position":
1038 innerpos = inner_pos[int(document.body[i][15])]
1039 del document.body[i]
1041 innerpos = inner_pos[0]
1043 # We need this since the new file format has a height and width
1044 # in a different order.
1045 if document.body[i][:6] == "height":
1046 height = document.body[i][6:]
1047 # test for default value of 221 and convert it accordingly
1048 if height == ' "0pt"' or height == ' "0"':
1050 del document.body[i]
1054 if document.body[i][:5] == "width":
1055 width = document.body[i][5:]
1056 del document.body[i]
1060 if document.body[i][:9] == "collapsed":
1061 if document.body[i][9:] == "true":
1062 status = "collapsed"
1065 del document.body[i]
1067 status = "collapsed"
1069 # Handle special default case:
1070 if height == ' "1pt"' and innerpos == 'c':
1073 document.body.insert(i, 'inner_pos "' + innerpos + '"')
1075 document.body.insert(i, 'use_parbox 0')
1077 document.body.insert(i, 'width' + width)
1079 document.body.insert(i, 'special "none"')
1081 document.body.insert(i, 'height' + height)
1083 document.body.insert(i, 'height_special "totalheight"')
1085 document.body.insert(i, 'status ' + status)
1089 def convert_ertbackslash(body, i, ert, format, default_layout):
1090 r""" -------------------------------------------------------------------------------------------
1091 Convert backslashes and '\n' into valid ERT code, append the converted
1092 text to body[i] and return the (maybe incremented) line index i"""
1096 body[i] = body[i] + '\\backslash '
1101 body[i+1:i+1] = ['\\newline ', '']
1104 body[i+1:i+1] = ['\\end_layout', '', '\\begin_layout %s' % default_layout, '']
1107 body[i] = body[i] + c
1111 def ert2latex(lines, format):
1112 r""" Converts lines in ERT code to LaTeX
1113 The surrounding \begin_layout ... \end_layout pair must not be included"""
1115 backslash = re.compile(r'\\backslash\s*$')
1116 newline = re.compile(r'\\newline\s*$')
1118 begin_layout = re.compile(r'\\layout\s*\S+$')
1120 begin_layout = re.compile(r'\\begin_layout\s*\S+$')
1121 end_layout = re.compile(r'\\end_layout\s*$')
1123 for i in range(len(lines)):
1124 line = backslash.sub('\\\\', lines[i])
1126 if begin_layout.match(line):
1129 line = newline.sub('\n', line)
1131 if begin_layout.match(line):
1133 if format > 224 and end_layout.match(line):
1139 def get_par_params(lines, i):
1140 """ get all paragraph parameters. They can be all on one line or on several lines.
1141 lines[i] must be the first parameter line"""
1142 par_params = ('added_space_bottom', 'added_space_top', 'align',
1143 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
1144 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
1145 'start_of_appendix')
1146 # We cannot check for '\\' only because paragraphs may start e.g.
1147 # with '\\backslash'
1149 while lines[i][:1] == '\\' and lines[i][1:].split()[0] in par_params:
1150 params = params + ' ' + lines[i].strip()
1152 return params.strip()
1155 def lyxsize2latexsize(lyxsize):
1156 " Convert LyX font size to LaTeX fontsize. "
1157 sizes = {"tiny" : "tiny", "scriptsize" : "scriptsize",
1158 "footnotesize" : "footnotesize", "small" : "small",
1159 "normal" : "normalsize", "large" : "large", "larger" : "Large",
1160 "largest" : "LARGE", "huge" : "huge", "giant" : "Huge"}
1161 if lyxsize in sizes:
1162 return '\\' + sizes[lyxsize]
1166 def revert_breaks(document):
1167 """ Change vspace insets, page breaks and lyxlines to paragraph options
1168 (if possible) or ERT"""
1170 # Get default spaceamount
1171 i = find_token(document.header, '\\defskip', 0)
1173 defskipamount = 'medskip'
1175 defskipamount = document.header[i].split()[1]
1177 keys = {"\\begin_inset" : "vspace", "\\lyxline" : "lyxline",
1178 "\\newpage" : "newpage"}
1179 keywords_top = {"vspace" : "\\added_space_top", "lyxline" : "\\line_top",
1180 "newpage" : "\\pagebreak_top"}
1181 keywords_bot = {"vspace" : "\\added_space_bottom", "lyxline" : "\\line_bottom",
1182 "newpage" : "\\pagebreak_bottom"}
1183 tokens = ["\\begin_inset VSpace", "\\lyxline", "\\newpage"]
1185 # Convert the insets
1188 i = find_tokens(document.body, tokens, i)
1192 # Are we at the beginning of a paragraph?
1194 this_par = get_paragraph(document.body, i, document.format - 1)
1195 start = this_par + 1
1196 params = get_par_params(document.body, start)
1198 # Paragraph parameters may be on one or more lines.
1199 # Find the start of the real paragraph text.
1200 while document.body[start][:1] == '\\' and document.body[start].split()[0] in params:
1202 for k in range(start, i):
1203 if document.body[k].find("\\size") != -1:
1205 size = document.body[k].split()[1]
1206 elif is_nonempty_line(document.body[k]):
1209 # Find the end of the real paragraph text.
1210 next_par = get_next_paragraph(document.body, i, document.format - 1)
1212 document.warning("Malformed LyX document: Missing next paragraph.")
1216 # first line of our insets
1218 # last line of our insets
1219 inset_end = inset_start
1220 # Are we at the end of a paragraph?
1222 # start and end line numbers to delete if we convert this inset
1224 # is this inset a lyxline above a paragraph?
1226 # raw inset information
1228 # name of this inset
1230 # font size of this inset
1233 # Detect subsequent lyxline, vspace and pagebreak insets created by convert_breaks()
1237 if find_tokens(document.body, tokens, k) == k:
1239 lines.append(document.body[k].split())
1240 insets.append(keys[lines[n][0]])
1241 del_lines.append([k, k])
1246 elif document.body[k].find("\\size") != -1:
1248 size = document.body[k].split()[1]
1249 elif find_token(document.body, "\\begin_inset ERT", k) == k:
1250 ert_begin = find_token(document.body, "\\layout", k) + 1
1252 document.warning("Malformed LyX document: Missing '\\layout'.")
1254 ert_end = find_end_of_inset(document.body, k)
1256 document.warning("Malformed LyX document: Missing '\\end_inset'.")
1258 ert = ert2latex(document.body[ert_begin:ert_end], document.format - 1)
1259 if (n > 0 and insets[n - 1] == "lyxline" and
1260 ert == '\\vspace{-1\\parskip}\n'):
1261 # vspace ERT created by convert_breaks() for top lyxline
1263 del_lines[n - 1][1] = ert_end
1269 elif (n > 0 and insets[n - 1] == "vspace" and
1270 find_token(document.body, "\\end_inset", k) == k):
1271 # ignore end of vspace inset
1272 del_lines[n - 1][1] = k
1274 elif is_nonempty_line(document.body[k]):
1279 # Determine space amount for vspace insets
1280 spaceamount = list()
1283 if insets[k] == "vspace":
1284 spaceamount.append(lines[k][2])
1285 arguments.append(' ' + spaceamount[k] + ' ')
1287 spaceamount.append('')
1288 arguments.append(' ')
1290 # Can we convert to top paragraph parameters?
1292 if ((n == 3 and insets[0] == "newpage" and insets[1] == "vspace" and
1293 insets[2] == "lyxline" and top[2]) or
1295 ((insets[0] == "newpage" and insets[1] == "vspace") or
1296 (insets[0] == "newpage" and insets[1] == "lyxline" and top[1]) or
1297 (insets[0] == "vspace" and insets[1] == "lyxline" and top[1]))) or
1298 (n == 1 and insets[0] == "lyxline" and top[0])):
1299 # These insets have been created before a paragraph by
1303 # Can we convert to bottom paragraph parameters?
1305 if ((n == 3 and insets[0] == "lyxline" and not top[0] and
1306 insets[1] == "vspace" and insets[2] == "newpage") or
1308 ((insets[0] == "lyxline" and not top[0] and insets[1] == "vspace") or
1309 (insets[0] == "lyxline" and not top[0] and insets[1] == "newpage") or
1310 (insets[0] == "vspace" and insets[1] == "newpage"))) or
1311 (n == 1 and insets[0] == "lyxline" and not top[0])):
1312 # These insets have been created after a paragraph by
1316 if paragraph_start and paragraph_end:
1317 # We are in a paragraph of our own.
1318 # We must not delete this paragraph if it has parameters
1320 # First try to merge with the previous paragraph.
1321 # We try the previous paragraph first because we would
1322 # otherwise need ERT for two subsequent vspaces.
1323 prev_par = get_paragraph(document.body, this_par - 1, document.format - 1) + 1
1324 if prev_par > 0 and not before:
1325 prev_params = get_par_params(document.body, prev_par + 1)
1327 # determine font size
1328 prev_size = "normal"
1330 while document.body[k][:1] == '\\' and document.body[k].split()[0] in prev_params:
1333 if document.body[k].find("\\size") != -1:
1334 prev_size = document.body[k].split()[1]
1336 elif document.body[k].find("\\begin_inset") != -1:
1338 k = find_end_of_inset(document.body, k)
1339 elif is_nonempty_line(document.body[k]):
1343 if (keywords_bot[insets[k]] in prev_params or
1344 (insets[k] == "lyxline" and sizes[k] != prev_size)):
1349 document.body.insert(prev_par + 1,
1350 keywords_bot[insets[k]] + arguments[k])
1351 del document.body[this_par+n:next_par-1+n]
1354 # Then try next paragraph
1355 if next_par > 0 and not after:
1356 next_params = get_par_params(document.body, next_par + 1)
1358 while document.body[k][:1] == '\\' and document.body[k].split()[0] in next_params:
1360 # determine font size
1361 next_size = "normal"
1364 if document.body[k].find("\\size") != -1:
1365 next_size = document.body[k].split()[1]
1367 elif is_nonempty_line(document.body[k]):
1371 if (keywords_top[insets[k]] in next_params or
1372 (insets[k] == "lyxline" and sizes[k] != next_size)):
1377 document.body.insert(next_par + 1,
1378 keywords_top[insets[k]] + arguments[k])
1379 del document.body[this_par:next_par-1]
1382 elif paragraph_start or paragraph_end:
1383 # Convert to paragraph formatting if we are at the beginning or end
1384 # of a paragraph and the resulting paragraph would not be empty
1385 # The order is important: del and insert invalidate some indices
1387 keywords = keywords_top
1389 keywords = keywords_bot
1392 if keywords[insets[k]] in params:
1397 document.body.insert(this_par + 1,
1398 keywords[insets[k]] + arguments[k])
1399 for j in range(k, n):
1400 del_lines[j][0] = del_lines[j][0] + 1
1401 del_lines[j][1] = del_lines[j][1] + 1
1402 del document.body[del_lines[k][0]:del_lines[k][1]+1]
1403 deleted = del_lines[k][1] - del_lines[k][0] + 1
1404 for j in range(k + 1, n):
1405 del_lines[j][0] = del_lines[j][0] - deleted
1406 del_lines[j][1] = del_lines[j][1] - deleted
1410 # Convert the first inset to ERT.
1411 # The others are converted in the next loop runs (if they exist)
1412 if insets[0] == "vspace":
1413 document.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
1414 '\\layout %s' % document.default_layout, '', '\\backslash ']
1416 if spaceamount[0][-1] == '*':
1417 spaceamount[0] = spaceamount[0][:-1]
1422 # Replace defskip by the actual value
1423 if spaceamount[0] == 'defskip':
1424 spaceamount[0] = defskipamount
1426 # LaTeX does not know \\smallskip* etc
1428 if spaceamount[0] == 'smallskip':
1429 spaceamount[0] = '\\smallskipamount'
1430 elif spaceamount[0] == 'medskip':
1431 spaceamount[0] = '\\medskipamount'
1432 elif spaceamount[0] == 'bigskip':
1433 spaceamount[0] = '\\bigskipamount'
1434 elif spaceamount[0] == 'vfill':
1435 spaceamount[0] = '\\fill'
1437 # Finally output the LaTeX code
1438 if (spaceamount[0] == 'smallskip' or spaceamount[0] == 'medskip' or
1439 spaceamount[0] == 'bigskip' or spaceamount[0] == 'vfill'):
1440 document.body.insert(i, spaceamount[0] + '{}')
1443 document.body.insert(i, 'vspace*{')
1445 document.body.insert(i, 'vspace{')
1446 i = convert_ertbackslash(document.body, i, spaceamount[0], document.format - 1, document.default_layout)
1447 document.body[i] = document.body[i] + '}'
1449 elif insets[0] == "lyxline":
1450 document.body[i] = ''
1451 latexsize = lyxsize2latexsize(size)
1453 document.warning("Could not convert LyX fontsize '%s' to LaTeX font size." % size)
1454 latexsize = '\\normalsize'
1455 i = insert_ert(document.body, i, 'Collapsed',
1456 '\\lyxline{%s}' % latexsize,
1457 document.format - 1, document.default_layout)
1458 # We use \providecommand so that we don't get an error if native
1459 # lyxlines are used (LyX writes first its own preamble and then
1460 # the user specified one)
1461 add_to_preamble(document,
1462 ['% Commands inserted by lyx2lyx for lyxlines',
1463 '\\providecommand{\\lyxline}[1]{',
1464 ' {#1 \\vspace{1ex} \\hrule width \\columnwidth \\vspace{1ex}}'
1466 elif insets[0] == "newpage":
1467 document.body[i] = ''
1468 i = insert_ert(document.body, i, 'Collapsed', '\\newpage{}',
1469 document.format - 1, document.default_layout)
1472 # Convert a LyX length into a LaTeX length
1473 def convert_len(len, special):
1474 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
1475 "page%":"\\pagewidth", "line%":"\\linewidth",
1476 "theight%":"\\textheight", "pheight%":"\\pageheight"}
1478 # Convert special lengths
1479 if special != 'none':
1480 len = '%f\\' % len2value(len) + special
1482 # Convert LyX units to LaTeX units
1483 for unit in units.keys():
1484 if len.find(unit) != -1:
1485 len = '%f' % (len2value(len) / 100) + units[unit]
1491 def convert_ertlen(body, i, len, special, format, default_layout):
1492 """ Convert a LyX length into valid ERT code and append it to body[i]
1493 Return the (maybe incremented) line index i
1494 Convert backslashes and insert the converted length into body. """
1495 return convert_ertbackslash(body, i, convert_len(len, special), format, default_layout)
1499 " Return the value of len without the unit in numerical form. "
1500 result = re.search('([+-]?[0-9.]+)', len)
1502 return float(result.group(1))
1503 # No number means 1.0
1507 def insert_ert(body, i, status, text, format, default_layout):
1508 """ Convert text to ERT and insert it at body[i]
1509 Return the index of the line after the inserted ERT"""
1511 body[i:i] = ['\\begin_inset ERT', 'status ' + status, '']
1514 body[i:i] = ['\\layout %s' % default_layout, '']
1516 body[i:i] = ['\\begin_layout %s' % default_layout, '']
1517 i = i + 1 # i points now to the just created empty line
1518 i = convert_ertbackslash(body, i, text, format, default_layout) + 1
1520 body[i:i] = ['\\end_layout']
1522 body[i:i] = ['', '\\end_inset', '']
1527 def add_to_preamble(document, text):
1528 """ Add text to the preamble if it is not already there.
1529 Only the first line is checked!"""
1531 if find_token(document.preamble, text[0], 0) != -1:
1534 document.preamble.extend(text)
1537 def convert_frameless_box(document):
1538 " Convert frameless box."
1539 pos = ['t', 'c', 'b']
1540 inner_pos = ['c', 't', 'b', 's']
1543 i = find_token(document.body, '\\begin_inset Frameless', i)
1546 j = find_end_of_inset(document.body, i)
1548 document.warning("Malformed LyX document: Missing '\\end_inset'.")
1551 del document.body[i]
1555 params = {'position':0, 'hor_pos':'c', 'has_inner_box':'1',
1556 'inner_pos':1, 'use_parbox':'0', 'width':'100col%',
1557 'special':'none', 'height':'1in',
1558 'height_special':'totalheight', 'collapsed':'false'}
1559 for key in params.keys():
1560 value = get_value(document.body, key, i, j).replace('"', '')
1562 if key == 'position':
1563 # convert new to old position: 'position "t"' -> 0
1564 value = find_token(pos, value, 0)
1567 elif key == 'inner_pos':
1568 # convert inner position
1569 value = find_token(inner_pos, value, 0)
1574 j = del_token(document.body, key, i, j)
1577 # Convert to minipage or ERT?
1578 # Note that the inner_position and height parameters of a minipage
1579 # inset are ignored and not accessible for the user, although they
1580 # are present in the file format and correctly read in and written.
1581 # Therefore we convert to ERT if they do not have their LaTeX
1582 # defaults. These are:
1583 # - the value of "position" for "inner_pos"
1584 # - "\totalheight" for "height"
1585 if (params['use_parbox'] != '0' or
1586 params['has_inner_box'] != '1' or
1587 params['special'] != 'none' or
1588 params['height_special'] != 'totalheight' or
1589 len2value(params['height']) != 1.0):
1591 # Here we know that this box is not supported in file format 224.
1592 # Therefore we need to convert it to ERT. We can't simply convert
1593 # the beginning and end of the box to ERT, because the
1594 # box inset may contain layouts that are different from the
1595 # surrounding layout. After the conversion the contents of the
1596 # box inset is on the same level as the surrounding text, and
1597 # paragraph layouts and align parameters can get mixed up.
1599 # A possible solution for this problem:
1600 # Convert the box to a minipage and redefine the minipage
1601 # environment in ERT so that the original box is simulated.
1602 # For minipages we could do this in a way that the width and
1603 # position can still be set from LyX, but this did not work well.
1604 # This is not possible for parboxes either, so we convert the
1605 # original box to ERT, put the minipage inset inside the box
1606 # and redefine the minipage environment to be empty.
1608 # Commands that are independant of a particular box can go to
1610 # We need to define lyxtolyxrealminipage with 3 optional
1611 # arguments although LyX 1.3 uses only the first one.
1612 # Otherwise we will get LaTeX errors if this document is
1613 # converted to format 225 or above again (LyX 1.4 uses all
1614 # optional arguments).
1615 add_to_preamble(document,
1616 ['% Commands inserted by lyx2lyx for frameless boxes',
1617 '% Save the original minipage environment',
1618 '\\let\\lyxtolyxrealminipage\\minipage',
1619 '\\let\\endlyxtolyxrealminipage\\endminipage',
1620 '% Define an empty lyxtolyximinipage environment',
1621 '% with 3 optional arguments',
1622 '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1623 '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1624 ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1625 ' {\\end{lyxtolyxiiiminipage}}',
1626 '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1627 ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1628 ' {\\end{lyxtolyxiiminipage}}',
1629 '\\newenvironment{lyxtolyxminipage}[1][c]%',
1630 ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1631 ' {\\end{lyxtolyximinipage}}'])
1633 if params['use_parbox'] != '0':
1636 ert = '\\begin{lyxtolyxrealminipage}'
1638 # convert optional arguments only if not latex default
1639 if (pos[params['position']] != 'c' or
1640 inner_pos[params['inner_pos']] != pos[params['position']] or
1641 params['height_special'] != 'totalheight' or
1642 len2value(params['height']) != 1.0):
1643 ert = ert + '[' + pos[params['position']] + ']'
1644 if (inner_pos[params['inner_pos']] != pos[params['position']] or
1645 params['height_special'] != 'totalheight' or
1646 len2value(params['height']) != 1.0):
1647 ert = ert + '[' + convert_len(params['height'],
1648 params['height_special']) + ']'
1649 if inner_pos[params['inner_pos']] != pos[params['position']]:
1650 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1652 ert = ert + '{' + convert_len(params['width'],
1653 params['special']) + '}'
1655 if params['use_parbox'] != '0':
1657 ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1658 ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1661 i = insert_ert(document.body, i, 'Collapsed', ert, document.format - 1, document.default_layout)
1662 j = j + i - old_i - 1
1664 document.body[i:i] = ['\\begin_inset Minipage',
1665 'position %d' % params['position'],
1668 'width "' + params['width'] + '"',
1669 'collapsed ' + params['collapsed']]
1673 # Restore the original minipage environment since we may have
1674 # minipages inside this box.
1675 # Start a new paragraph because the following may be nonstandard
1676 document.body[i:i] = ['\\layout %s' % document.default_layout, '', '']
1679 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1680 ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1682 i = insert_ert(document.body, i, 'Collapsed', ert, document.format - 1, document.default_layout)
1683 j = j + i - old_i - 1
1685 # Redefine the minipage end before the inset end.
1686 # Start a new paragraph because the previous may be nonstandard
1687 document.body[j:j] = ['\\layout %s' % document.default_layout, '', '']
1689 ert = '\\let\\endminipage\\endlyxtolyxminipage'
1690 j = insert_ert(document.body, j, 'Collapsed', ert, document.format - 1, document.default_layout)
1692 document.body.insert(j, '')
1695 # LyX writes '%\n' after each box. Therefore we need to end our
1696 # ERT with '%\n', too, since this may swallow a following space.
1697 if params['use_parbox'] != '0':
1700 ert = '\\end{lyxtolyxrealminipage}%\n'
1701 j = insert_ert(document.body, j, 'Collapsed', ert, document.format - 1, document.default_layout)
1703 # We don't need to restore the original minipage after the inset
1704 # end because the scope of the redefinition is the original box.
1708 # Convert to minipage
1709 document.body[i:i] = ['\\begin_inset Minipage',
1710 'position %d' % params['position'],
1711 'inner_position %d' % params['inner_pos'],
1712 'height "' + params['height'] + '"',
1713 'width "' + params['width'] + '"',
1714 'collapsed ' + params['collapsed']]
1718 def remove_branches(document):
1719 " Remove branches. "
1722 i = find_token(document.header, "\\branch", i)
1725 document.warning("Removing branch %s." % document.header[i].split()[1])
1726 j = find_token(document.header, "\\end_branch", i)
1728 document.warning("Malformed LyX document: Missing '\\end_branch'.")
1730 del document.header[i:j+1]
1734 i = find_token(document.body, "\\begin_inset Branch", i)
1737 j = find_end_of_inset(document.body, i)
1739 document.warning("Malformed LyX document: Missing '\\end_inset'.")
1742 del document.body[i]
1743 del document.body[j - 1]
1744 # Seach for a line starting 'collapsed'
1745 # If, however, we find a line starting '\layout'
1746 # (_always_ present) then break with a warning message
1749 if (document.body[i][:9] == "collapsed"):
1750 del document.body[i]
1753 elif (document.body[i][:7] == "\\layout"):
1754 if collapsed_found == 0:
1755 document.warning("Malformed LyX document: Missing 'collapsed'.")
1756 # Delete this new paragraph, since it would not appear in
1757 # .tex output. This avoids also empty paragraphs.
1758 del document.body[i]
1763 def convert_jurabib(document):
1764 " Convert jurabib. "
1765 i = find_token(document.header, '\\use_numerical_citations', 0)
1767 document.warning("Malformed lyx document: Missing '\\use_numerical_citations'.")
1769 document.header.insert(i + 1, '\\use_jurabib 0')
1772 def revert_jurabib(document):
1774 i = find_token(document.header, '\\use_jurabib', 0)
1776 document.warning("Malformed lyx document: Missing '\\use_jurabib'.")
1778 if get_value(document.header, '\\use_jurabib', 0) != "0":
1779 document.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1780 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1782 del document.header[i]
1785 def convert_bibtopic(document):
1786 " Convert bibtopic. "
1787 i = find_token(document.header, '\\use_jurabib', 0)
1789 document.warning("Malformed lyx document: Missing '\\use_jurabib'.")
1791 document.header.insert(i + 1, '\\use_bibtopic 0')
1794 def revert_bibtopic(document):
1795 " Revert bibtopic. "
1796 i = find_token(document.header, '\\use_bibtopic', 0)
1798 document.warning("Malformed lyx document: Missing '\\use_bibtopic'.")
1800 if get_value(document.header, '\\use_bibtopic', 0) != "0":
1801 document.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1802 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1803 del document.header[i]
1806 def convert_float(document):
1807 " Convert sideway floats. "
1810 i = find_token_exact(document.body, '\\begin_inset Float', i)
1813 # Seach for a line starting 'wide'
1814 # If, however, we find a line starting '\begin_layout'
1815 # (_always_ present) then break with a warning message
1818 if (document.body[i][:4] == "wide"):
1819 document.body.insert(i + 1, 'sideways false')
1821 elif (document.body[i][:13] == "\\begin_layout"):
1822 document.warning("Malformed lyx document: Missing 'wide'.")
1828 def revert_float(document):
1829 " Revert sideways floats. "
1832 i = find_token_exact(document.body, '\\begin_inset Float', i)
1835 line = document.body[i]
1836 r = re.compile(r'\\begin_inset Float (.*)$')
1838 floattype = m.group(1)
1839 if floattype != "figure" and floattype != "table":
1842 j = find_end_of_inset(document.body, i)
1844 document.warning("Malformed lyx document: Missing '\\end_inset'.")
1847 if get_value(document.body, 'sideways', i, j) != "false":
1848 l = find_token(document.body, "\\begin_layout Standard", i + 1, j)
1850 document.warning("Malformed LyX document: Missing `\\begin_layout Standard' in Float inset.")
1852 document.body[j] = '\\layout Standard\n\\begin_inset ERT\nstatus Collapsed\n\n' \
1853 '\\layout Standard\n\n\n\\backslash\n' \
1854 'end{sideways' + floattype + '}\n\n\\end_inset\n'
1855 del document.body[i+1:l-1]
1856 document.body[i] = '\\begin_inset ERT\nstatus Collapsed\n\n' \
1857 '\\layout Standard\n\n\n\\backslash\n' \
1858 'begin{sideways' + floattype + '}\n\n\\end_inset\n\n'
1859 add_to_preamble(document,
1860 ['\\usepackage{rotfloat}\n'])
1863 del_token(document.body, 'sideways', i, j)
1867 def convert_graphics(document):
1868 """ Add extension to documentnames of insetgraphics if necessary.
1872 i = find_token(document.body, "\\begin_inset Graphics", i)
1876 j = find_token_exact(document.body, "documentname", i)
1880 filename = document.body[j].split()[1]
1881 absname = os.path.normpath(os.path.join(document.dir, filename))
1882 if document.input == stdin and not os.path.isabs(filename):
1883 # We don't know the directory and cannot check the document.
1884 # We could use a heuristic and take the current directory,
1885 # and we could try to find out if documentname has an extension,
1886 # but that would be just guesses and could be wrong.
1887 document.warning("""Warning: Cannot determine whether document
1889 needs an extension when reading from standard input.
1890 You may need to correct the document manually or run
1891 lyx2lyx again with the .lyx document as commandline argument.""" % filename)
1893 # This needs to be the same algorithm as in pre 233 insetgraphics
1894 if access(absname, F_OK):
1896 if access(absname + ".ps", F_OK):
1897 document.body[j] = document.body[j].replace(filename, filename + ".ps")
1899 if access(absname + ".eps", F_OK):
1900 document.body[j] = document.body[j].replace(filename, filename + ".eps")
1903 def convert_names(document):
1904 """ Convert in the docbook backend from firstname and surname style
1907 if document.backend != "docbook":
1913 i = find_token(document.body, "\\begin_layout Author", i)
1918 while document.body[i] == "":
1921 if document.body[i][:11] != "\\end_layout" or document.body[i+2][:13] != "\\begin_deeper":
1926 i = find_end_of( document.body, i+3, "\\begin_deeper","\\end_deeper")
1928 # something is really wrong, abort
1929 document.warning("Missing \\end_deeper, after style Author.")
1930 document.warning("Aborted attempt to parse FirstName and Surname.")
1932 firstname, surname = "", ""
1934 name = document.body[k:i]
1936 j = find_token(name, "\\begin_layout FirstName", 0)
1939 while(name[j] != "\\end_layout"):
1940 firstname = firstname + name[j]
1943 j = find_token(name, "\\begin_layout Surname", 0)
1946 while(name[j] != "\\end_layout"):
1947 surname = surname + name[j]
1951 del document.body[k+2:i+1]
1953 document.body[k-1:k-1] = ["", "",
1954 "\\begin_inset CharStyle Firstname",
1957 '\\begin_layout %s' % document.default_layout,
1965 "\\begin_inset CharStyle Surname",
1968 '\\begin_layout %s' % document.default_layout,
1977 def revert_names(document):
1978 """ Revert in the docbook backend from firstname and surname char style
1981 if document.backend != "docbook":
1985 def convert_cite_engine(document):
1986 r""" \use_natbib 1 \cite_engine <style>
1987 \use_numerical_citations 0 -> where <style> is one of
1988 \use_jurabib 0 "basic", "natbib_authoryear","""
1990 a = find_token(document.header, "\\use_natbib", 0)
1992 document.warning("Malformed lyx document: Missing '\\use_natbib'.")
1995 b = find_token(document.header, "\\use_numerical_citations", 0)
1996 if b == -1 or b != a+1:
1997 document.warning("Malformed lyx document: Missing '\\use_numerical_citations'.")
2000 c = find_token(document.header, "\\use_jurabib", 0)
2001 if c == -1 or c != b+1:
2002 document.warning("Malformed lyx document: Missing '\\use_jurabib'.")
2005 use_natbib = int(document.header[a].split()[1])
2006 use_numerical_citations = int(document.header[b].split()[1])
2007 use_jurabib = int(document.header[c].split()[1])
2009 cite_engine = "basic"
2011 if use_numerical_citations:
2012 cite_engine = "natbib_numerical"
2014 cite_engine = "natbib_authoryear"
2016 cite_engine = "jurabib"
2018 del document.header[a:c+1]
2019 document.header.insert(a, "\\cite_engine " + cite_engine)
2022 def revert_cite_engine(document):
2023 " Revert the cite engine. "
2024 i = find_token(document.header, "\\cite_engine", 0)
2026 document.warning("Malformed lyx document: Missing '\\cite_engine'.")
2029 cite_engine = document.header[i].split()[1]
2034 if cite_engine == "natbib_numerical":
2037 elif cite_engine == "natbib_authoryear":
2039 elif cite_engine == "jurabib":
2042 del document.header[i]
2043 document.header.insert(i, "\\use_jurabib " + use_jurabib)
2044 document.header.insert(i, "\\use_numerical_citations " + use_numerical)
2045 document.header.insert(i, "\\use_natbib " + use_natbib)
2048 def convert_paperpackage(document):
2049 " Convert paper package. "
2050 i = find_token(document.header, "\\paperpackage", 0)
2054 packages = {'default':'none','a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
2055 if len(document.header[i].split()) > 1:
2056 paperpackage = document.header[i].split()[1]
2057 document.header[i] = document.header[i].replace(paperpackage, packages[paperpackage])
2059 document.header[i] = document.header[i] + ' widemarginsa4'
2062 def revert_paperpackage(document):
2063 " Revert paper package. "
2064 i = find_token(document.header, "\\paperpackage", 0)
2068 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
2069 'widemarginsa4':'', 'default': 'default'}
2070 if len(document.header[i].split()) > 1:
2071 paperpackage = document.header[i].split()[1]
2073 paperpackage = 'default'
2074 document.header[i] = document.header[i].replace(paperpackage, packages[paperpackage])
2077 def convert_bullets(document):
2078 " Convert bullets. "
2081 i = find_token(document.header, "\\bullet", i)
2084 if document.header[i][:12] == '\\bulletLaTeX':
2085 document.header[i] = document.header[i] + ' ' + document.header[i+1].strip()
2088 document.header[i] = document.header[i] + ' ' + document.header[i+1].strip() +\
2089 ' ' + document.header[i+2].strip() + ' ' + document.header[i+3].strip()
2091 del document.header[i+1:i + n]
2095 def revert_bullets(document):
2099 i = find_token(document.header, "\\bullet", i)
2102 if document.header[i][:12] == '\\bulletLaTeX':
2103 n = document.header[i].find('"')
2105 document.warning("Malformed header.")
2108 document.header[i:i+1] = [document.header[i][:n-1],'\t' + document.header[i][n:], '\\end_bullet']
2111 frag = document.header[i].split()
2113 document.warning("Malformed header.")
2116 document.header[i:i+1] = [frag[0] + ' ' + frag[1],
2124 def add_begin_header(document):
2125 r" Add \begin_header and \begin_document. "
2126 i = find_token(document.header, '\\lyxformat', 0)
2127 document.header.insert(i+1, '\\begin_header')
2128 document.header.insert(i+1, '\\begin_document')
2131 def remove_begin_header(document):
2132 r" Remove \begin_header and \begin_document. "
2133 i = find_token(document.header, "\\begin_document", 0)
2135 del document.header[i]
2136 i = find_token(document.header, "\\begin_header", 0)
2138 del document.header[i]
2141 def add_begin_body(document):
2142 r" Add and \begin_document and \end_document"
2143 document.body.insert(0, '\\begin_body')
2144 document.body.insert(1, '')
2145 i = find_token(document.body, "\\end_document", 0)
2146 document.body.insert(i, '\\end_body')
2148 def remove_begin_body(document):
2149 r" Remove \begin_body and \end_body"
2150 i = find_token(document.body, "\\begin_body", 0)
2152 del document.body[i]
2153 if not document.body[i]:
2154 del document.body[i]
2155 i = find_token(document.body, "\\end_body", 0)
2157 del document.body[i]
2160 def normalize_papersize(document):
2161 r" Normalize \papersize"
2162 i = find_token(document.header, '\\papersize', 0)
2166 tmp = document.header[i].split()
2167 if tmp[1] == "Default":
2168 document.header[i] = '\\papersize default'
2170 if tmp[1] == "Custom":
2171 document.header[i] = '\\papersize custom'
2174 def denormalize_papersize(document):
2175 r" Revert \papersize"
2176 i = find_token(document.header, '\\papersize', 0)
2180 tmp = document.header[i].split()
2181 if tmp[1] == "custom":
2182 document.header[i] = '\\papersize Custom'
2185 def strip_end_space(document):
2186 " Strip spaces at end of command line. "
2187 for i in range(len(document.body)):
2188 if document.body[i][:1] == '\\':
2189 document.body[i] = document.body[i].strip()
2192 def use_x_boolean(document):
2193 r" Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes"
2194 bin2bool = {'0': 'false', '1': 'true'}
2195 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2196 i = find_token(document.header, use, 0)
2199 decompose = document.header[i].split()
2200 document.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
2203 def use_x_binary(document):
2204 r" Use digit values for \use_geometry, \use_bibtopic and \tracking_changes"
2205 bool2bin = {'false': '0', 'true': '1'}
2206 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2207 i = find_token(document.header, use, 0)
2210 decompose = document.header[i].split()
2211 document.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
2214 def normalize_paragraph_params(document):
2215 " Place all the paragraph parameters in their own line. "
2216 body = document.body
2218 allowed_parameters = '\\paragraph_spacing', '\\noindent', \
2219 '\\align', '\\labelwidthstring', "\\start_of_appendix", \
2224 i = find_token(document.body, '\\begin_layout', i)
2230 if body[i].strip() and body[i].split()[0] not in allowed_parameters:
2233 j = body[i].find('\\', 1)
2236 body[i:i+1] = [body[i][:j].strip(), body[i][j:]]
2241 def convert_output_changes (document):
2242 " Add output_changes parameter. "
2243 i = find_token(document.header, '\\tracking_changes', 0)
2245 document.warning("Malformed lyx document: Missing '\\tracking_changes'.")
2247 document.header.insert(i+1, '\\output_changes true')
2250 def revert_output_changes (document):
2251 " Remove output_changes parameter. "
2252 i = find_token(document.header, '\\output_changes', 0)
2255 del document.header[i]
2258 def convert_ert_paragraphs(document):
2259 " Convert paragraph breaks and sanitize paragraphs. "
2260 forbidden_settings = [
2261 # paragraph parameters
2262 '\\paragraph_spacing', '\\labelwidthstring',
2263 '\\start_of_appendix', '\\noindent',
2264 '\\leftindent', '\\align',
2266 '\\family', '\\series', '\\shape', '\\size',
2267 '\\emph', '\\numeric', '\\bar', '\\noun',
2268 '\\color', '\\lang']
2271 i = find_token(document.body, '\\begin_inset ERT', i)
2274 j = find_end_of_inset(document.body, i)
2276 document.warning("Malformed lyx document: Missing '\\end_inset'.")
2280 # convert non-standard paragraphs to standard
2283 k = find_token(document.body, "\\begin_layout", k, j)
2286 document.body[k] = '\\begin_layout %s' % document.default_layout
2289 # remove all paragraph parameters and font settings
2292 if (document.body[k].strip() and
2293 document.body[k].split()[0] in forbidden_settings):
2294 del document.body[k]
2299 # insert an empty paragraph before each paragraph but the first
2303 k = find_token(document.body, "\\begin_layout", k, j)
2310 document.body[k:k] = ['\\begin_layout %s' % document.default_layout, "",
2315 # convert \\newline to new paragraph
2318 k = find_token(document.body, "\\newline", k, j)
2321 document.body[k:k+1] = ["\\end_layout", "", '\\begin_layout %s' % document.default_layout]
2324 # We need an empty line if document.default_layout == ''
2325 if document.body[k] != '':
2326 document.body.insert(k, '')
2332 def revert_ert_paragraphs(document):
2333 " Remove double paragraph breaks. "
2336 i = find_token(document.body, '\\begin_inset ERT', i)
2339 j = find_end_of_inset(document.body, i)
2341 document.warning("Malformed lyx document: Missing '\\end_inset'.")
2345 # replace paragraph breaks with \newline
2348 k = find_token(document.body, "\\end_layout", k, j)
2349 l = find_token(document.body, "\\begin_layout", k, j)
2350 if k == -1 or l == -1:
2352 document.body[k:l+1] = ["\\newline"]
2356 # replace double \newlines with paragraph breaks
2359 k = find_token(document.body, "\\newline", k, j)
2363 while document.body[l] == "":
2365 if document.body[l].strip() and document.body[l].split()[0] == "\\newline":
2366 document.body[k:l+1] = ["\\end_layout", "",
2367 '\\begin_layout %s' % document.default_layout]
2370 # We need an empty line if document.default_layout == ''
2371 if document.body[l+1] != '':
2372 document.body.insert(l+1, '')
2380 def convert_french(document):
2381 " Convert frenchb. "
2382 regexp = re.compile(r'^\\language\s+frenchb')
2383 i = find_re(document.header, regexp, 0)
2385 document.header[i] = "\\language french"
2387 # Change language in the document body
2388 regexp = re.compile(r'^\\lang\s+frenchb')
2391 i = find_re(document.body, regexp, i)
2394 document.body[i] = "\\lang french"
2398 def remove_paperpackage(document):
2399 " Remove paper package. "
2400 i = find_token(document.header, '\\paperpackage', 0)
2405 paperpackage = document.header[i].split()[1]
2407 del document.header[i]
2409 if paperpackage not in ("a4", "a4wide", "widemarginsa4"):
2412 conv = {"a4":"\\usepackage{a4}","a4wide": "\\usepackage{a4wide}",
2413 "widemarginsa4": "\\usepackage[widemargins]{a4}"}
2414 # for compatibility we ensure it is the first entry in preamble
2415 document.preamble[0:0] = [conv[paperpackage]]
2417 i = find_token(document.header, '\\papersize', 0)
2419 document.header[i] = "\\papersize default"
2422 def remove_quotestimes(document):
2423 " Remove quotestimes. "
2424 i = find_token(document.header, '\\quotes_times', 0)
2427 del document.header[i]
2430 def convert_sgml_paragraphs(document):
2431 " Convert SGML paragraphs. "
2432 if document.backend != "docbook":
2437 i = find_token(document.body, "\\begin_layout SGML", i)
2442 document.body[i] = "\\begin_layout Standard"
2443 j = find_token(document.body, "\\end_layout", i)
2445 document.body[j+1:j+1] = ['','\\end_inset','','','\\end_layout']
2446 document.body[i+1:i+1] = ['\\begin_inset ERT','status inlined','','\\begin_layout Standard','']
2454 supported_versions = ["1.4.%d" % i for i in range(3)] + ["1.4"]
2455 convert = [[222, [insert_tracking_changes, add_end_header, convert_amsmath]],
2456 [223, [remove_color_default, convert_spaces, convert_bibtex, remove_insetparent]],
2457 [224, [convert_external, convert_comment]],
2458 [225, [add_end_layout, layout2begin_layout, convert_end_document,
2459 convert_table_valignment_middle, convert_breaks]],
2460 [226, [convert_note]],
2461 [227, [convert_box]],
2462 [228, [convert_collapsable, convert_ert]],
2463 [229, [convert_minipage]],
2464 [230, [convert_jurabib]],
2465 [231, [convert_float]],
2466 [232, [convert_bibtopic]],
2467 [233, [convert_graphics, convert_names]],
2468 [234, [convert_cite_engine]],
2469 [235, [convert_paperpackage]],
2470 [236, [convert_bullets, add_begin_header, add_begin_body,
2471 normalize_papersize, strip_end_space]],
2472 [237, [use_x_boolean]],
2473 [238, [update_latexaccents]],
2474 [239, [normalize_paragraph_params]],
2475 [240, [convert_output_changes]],
2476 [241, [convert_ert_paragraphs]],
2477 [242, [convert_french]],
2478 [243, [remove_paperpackage]],
2479 [244, [rename_spaces]],
2480 [245, [remove_quotestimes, convert_sgml_paragraphs]]]
2482 revert = [[244, []],
2483 [243, [revert_space_names]],
2486 [240, [revert_ert_paragraphs]],
2487 [239, [revert_output_changes]],
2490 [236, [use_x_binary]],
2491 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
2493 [234, [revert_paperpackage]],
2494 [233, [revert_cite_engine]],
2495 [232, [revert_names]],
2496 [231, [revert_bibtopic]],
2497 [230, [revert_float]],
2498 [229, [revert_jurabib]],
2500 [227, [revert_collapsable, revert_ert]],
2501 [226, [revert_box, revert_external_2]],
2502 [225, [revert_note]],
2503 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
2504 revert_valignment_middle, revert_breaks, convert_frameless_box,
2506 [223, [revert_external_2, revert_comment, revert_eqref]],
2507 [222, [revert_spaces, revert_bibtex]],
2508 [221, [revert_amsmath, rm_end_header, rm_tracking_changes, rm_body_changes]]]
2511 if __name__ == "__main__":