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 sideway floats. "
1832 i = find_token_exact(document.body, '\\begin_inset Float', i)
1835 j = find_end_of_inset(document.body, i)
1837 document.warning("Malformed lyx document: Missing '\\end_inset'.")
1840 if get_value(document.body, 'sideways', i, j) != "false":
1841 document.warning("Conversion of 'sideways true' not yet implemented.")
1842 # Don't remove 'sideways' so that people will get warnings by lyx
1845 del_token(document.body, 'sideways', i, j)
1849 def convert_graphics(document):
1850 """ Add extension to documentnames of insetgraphics if necessary.
1854 i = find_token(document.body, "\\begin_inset Graphics", i)
1858 j = find_token_exact(document.body, "documentname", i)
1862 filename = document.body[j].split()[1]
1863 absname = os.path.normpath(os.path.join(document.dir, filename))
1864 if document.input == stdin and not os.path.isabs(filename):
1865 # We don't know the directory and cannot check the document.
1866 # We could use a heuristic and take the current directory,
1867 # and we could try to find out if documentname has an extension,
1868 # but that would be just guesses and could be wrong.
1869 document.warning("""Warning: Cannot determine whether document
1871 needs an extension when reading from standard input.
1872 You may need to correct the document manually or run
1873 lyx2lyx again with the .lyx document as commandline argument.""" % filename)
1875 # This needs to be the same algorithm as in pre 233 insetgraphics
1876 if access(absname, F_OK):
1878 if access(absname + ".ps", F_OK):
1879 document.body[j] = document.body[j].replace(filename, filename + ".ps")
1881 if access(absname + ".eps", F_OK):
1882 document.body[j] = document.body[j].replace(filename, filename + ".eps")
1885 def convert_names(document):
1886 """ Convert in the docbook backend from firstname and surname style
1889 if document.backend != "docbook":
1895 i = find_token(document.body, "\\begin_layout Author", i)
1900 while document.body[i] == "":
1903 if document.body[i][:11] != "\\end_layout" or document.body[i+2][:13] != "\\begin_deeper":
1908 i = find_end_of( document.body, i+3, "\\begin_deeper","\\end_deeper")
1910 # something is really wrong, abort
1911 document.warning("Missing \\end_deeper, after style Author.")
1912 document.warning("Aborted attempt to parse FirstName and Surname.")
1914 firstname, surname = "", ""
1916 name = document.body[k:i]
1918 j = find_token(name, "\\begin_layout FirstName", 0)
1921 while(name[j] != "\\end_layout"):
1922 firstname = firstname + name[j]
1925 j = find_token(name, "\\begin_layout Surname", 0)
1928 while(name[j] != "\\end_layout"):
1929 surname = surname + name[j]
1933 del document.body[k+2:i+1]
1935 document.body[k-1:k-1] = ["", "",
1936 "\\begin_inset CharStyle Firstname",
1939 '\\begin_layout %s' % document.default_layout,
1947 "\\begin_inset CharStyle Surname",
1950 '\\begin_layout %s' % document.default_layout,
1959 def revert_names(document):
1960 """ Revert in the docbook backend from firstname and surname char style
1963 if document.backend != "docbook":
1967 def convert_cite_engine(document):
1968 r""" \use_natbib 1 \cite_engine <style>
1969 \use_numerical_citations 0 -> where <style> is one of
1970 \use_jurabib 0 "basic", "natbib_authoryear","""
1972 a = find_token(document.header, "\\use_natbib", 0)
1974 document.warning("Malformed lyx document: Missing '\\use_natbib'.")
1977 b = find_token(document.header, "\\use_numerical_citations", 0)
1978 if b == -1 or b != a+1:
1979 document.warning("Malformed lyx document: Missing '\\use_numerical_citations'.")
1982 c = find_token(document.header, "\\use_jurabib", 0)
1983 if c == -1 or c != b+1:
1984 document.warning("Malformed lyx document: Missing '\\use_jurabib'.")
1987 use_natbib = int(document.header[a].split()[1])
1988 use_numerical_citations = int(document.header[b].split()[1])
1989 use_jurabib = int(document.header[c].split()[1])
1991 cite_engine = "basic"
1993 if use_numerical_citations:
1994 cite_engine = "natbib_numerical"
1996 cite_engine = "natbib_authoryear"
1998 cite_engine = "jurabib"
2000 del document.header[a:c+1]
2001 document.header.insert(a, "\\cite_engine " + cite_engine)
2004 def revert_cite_engine(document):
2005 " Revert the cite engine. "
2006 i = find_token(document.header, "\\cite_engine", 0)
2008 document.warning("Malformed lyx document: Missing '\\cite_engine'.")
2011 cite_engine = document.header[i].split()[1]
2016 if cite_engine == "natbib_numerical":
2019 elif cite_engine == "natbib_authoryear":
2021 elif cite_engine == "jurabib":
2024 del document.header[i]
2025 document.header.insert(i, "\\use_jurabib " + use_jurabib)
2026 document.header.insert(i, "\\use_numerical_citations " + use_numerical)
2027 document.header.insert(i, "\\use_natbib " + use_natbib)
2030 def convert_paperpackage(document):
2031 " Convert paper package. "
2032 i = find_token(document.header, "\\paperpackage", 0)
2036 packages = {'default':'none','a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
2037 if len(document.header[i].split()) > 1:
2038 paperpackage = document.header[i].split()[1]
2039 document.header[i] = document.header[i].replace(paperpackage, packages[paperpackage])
2041 document.header[i] = document.header[i] + ' widemarginsa4'
2044 def revert_paperpackage(document):
2045 " Revert paper package. "
2046 i = find_token(document.header, "\\paperpackage", 0)
2050 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
2051 'widemarginsa4':'', 'default': 'default'}
2052 if len(document.header[i].split()) > 1:
2053 paperpackage = document.header[i].split()[1]
2055 paperpackage = 'default'
2056 document.header[i] = document.header[i].replace(paperpackage, packages[paperpackage])
2059 def convert_bullets(document):
2060 " Convert bullets. "
2063 i = find_token(document.header, "\\bullet", i)
2066 if document.header[i][:12] == '\\bulletLaTeX':
2067 document.header[i] = document.header[i] + ' ' + document.header[i+1].strip()
2070 document.header[i] = document.header[i] + ' ' + document.header[i+1].strip() +\
2071 ' ' + document.header[i+2].strip() + ' ' + document.header[i+3].strip()
2073 del document.header[i+1:i + n]
2077 def revert_bullets(document):
2081 i = find_token(document.header, "\\bullet", i)
2084 if document.header[i][:12] == '\\bulletLaTeX':
2085 n = document.header[i].find('"')
2087 document.warning("Malformed header.")
2090 document.header[i:i+1] = [document.header[i][:n-1],'\t' + document.header[i][n:], '\\end_bullet']
2093 frag = document.header[i].split()
2095 document.warning("Malformed header.")
2098 document.header[i:i+1] = [frag[0] + ' ' + frag[1],
2106 def add_begin_header(document):
2107 r" Add \begin_header and \begin_document. "
2108 i = find_token(document.header, '\\lyxformat', 0)
2109 document.header.insert(i+1, '\\begin_header')
2110 document.header.insert(i+1, '\\begin_document')
2113 def remove_begin_header(document):
2114 r" Remove \begin_header and \begin_document. "
2115 i = find_token(document.header, "\\begin_document", 0)
2117 del document.header[i]
2118 i = find_token(document.header, "\\begin_header", 0)
2120 del document.header[i]
2123 def add_begin_body(document):
2124 r" Add and \begin_document and \end_document"
2125 document.body.insert(0, '\\begin_body')
2126 document.body.insert(1, '')
2127 i = find_token(document.body, "\\end_document", 0)
2128 document.body.insert(i, '\\end_body')
2130 def remove_begin_body(document):
2131 r" Remove \begin_body and \end_body"
2132 i = find_token(document.body, "\\begin_body", 0)
2134 del document.body[i]
2135 if not document.body[i]:
2136 del document.body[i]
2137 i = find_token(document.body, "\\end_body", 0)
2139 del document.body[i]
2142 def normalize_papersize(document):
2143 r" Normalize \papersize"
2144 i = find_token(document.header, '\\papersize', 0)
2148 tmp = document.header[i].split()
2149 if tmp[1] == "Default":
2150 document.header[i] = '\\papersize default'
2152 if tmp[1] == "Custom":
2153 document.header[i] = '\\papersize custom'
2156 def denormalize_papersize(document):
2157 r" Revert \papersize"
2158 i = find_token(document.header, '\\papersize', 0)
2162 tmp = document.header[i].split()
2163 if tmp[1] == "custom":
2164 document.header[i] = '\\papersize Custom'
2167 def strip_end_space(document):
2168 " Strip spaces at end of command line. "
2169 for i in range(len(document.body)):
2170 if document.body[i][:1] == '\\':
2171 document.body[i] = document.body[i].strip()
2174 def use_x_boolean(document):
2175 r" Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes"
2176 bin2bool = {'0': 'false', '1': 'true'}
2177 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2178 i = find_token(document.header, use, 0)
2181 decompose = document.header[i].split()
2182 document.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
2185 def use_x_binary(document):
2186 r" Use digit values for \use_geometry, \use_bibtopic and \tracking_changes"
2187 bool2bin = {'false': '0', 'true': '1'}
2188 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2189 i = find_token(document.header, use, 0)
2192 decompose = document.header[i].split()
2193 document.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
2196 def normalize_paragraph_params(document):
2197 " Place all the paragraph parameters in their own line. "
2198 body = document.body
2200 allowed_parameters = '\\paragraph_spacing', '\\noindent', \
2201 '\\align', '\\labelwidthstring', "\\start_of_appendix", \
2206 i = find_token(document.body, '\\begin_layout', i)
2212 if body[i].strip() and body[i].split()[0] not in allowed_parameters:
2215 j = body[i].find('\\', 1)
2218 body[i:i+1] = [body[i][:j].strip(), body[i][j:]]
2223 def convert_output_changes (document):
2224 " Add output_changes parameter. "
2225 i = find_token(document.header, '\\tracking_changes', 0)
2227 document.warning("Malformed lyx document: Missing '\\tracking_changes'.")
2229 document.header.insert(i+1, '\\output_changes true')
2232 def revert_output_changes (document):
2233 " Remove output_changes parameter. "
2234 i = find_token(document.header, '\\output_changes', 0)
2237 del document.header[i]
2240 def convert_ert_paragraphs(document):
2241 " Convert paragraph breaks and sanitize paragraphs. "
2242 forbidden_settings = [
2243 # paragraph parameters
2244 '\\paragraph_spacing', '\\labelwidthstring',
2245 '\\start_of_appendix', '\\noindent',
2246 '\\leftindent', '\\align',
2248 '\\family', '\\series', '\\shape', '\\size',
2249 '\\emph', '\\numeric', '\\bar', '\\noun',
2250 '\\color', '\\lang']
2253 i = find_token(document.body, '\\begin_inset ERT', i)
2256 j = find_end_of_inset(document.body, i)
2258 document.warning("Malformed lyx document: Missing '\\end_inset'.")
2262 # convert non-standard paragraphs to standard
2265 k = find_token(document.body, "\\begin_layout", k, j)
2268 document.body[k] = '\\begin_layout %s' % document.default_layout
2271 # remove all paragraph parameters and font settings
2274 if (document.body[k].strip() and
2275 document.body[k].split()[0] in forbidden_settings):
2276 del document.body[k]
2281 # insert an empty paragraph before each paragraph but the first
2285 k = find_token(document.body, "\\begin_layout", k, j)
2292 document.body[k:k] = ['\\begin_layout %s' % document.default_layout, "",
2297 # convert \\newline to new paragraph
2300 k = find_token(document.body, "\\newline", k, j)
2303 document.body[k:k+1] = ["\\end_layout", "", '\\begin_layout %s' % document.default_layout]
2306 # We need an empty line if document.default_layout == ''
2307 if document.body[k] != '':
2308 document.body.insert(k, '')
2314 def revert_ert_paragraphs(document):
2315 " Remove double paragraph breaks. "
2318 i = find_token(document.body, '\\begin_inset ERT', i)
2321 j = find_end_of_inset(document.body, i)
2323 document.warning("Malformed lyx document: Missing '\\end_inset'.")
2327 # replace paragraph breaks with \newline
2330 k = find_token(document.body, "\\end_layout", k, j)
2331 l = find_token(document.body, "\\begin_layout", k, j)
2332 if k == -1 or l == -1:
2334 document.body[k:l+1] = ["\\newline"]
2338 # replace double \newlines with paragraph breaks
2341 k = find_token(document.body, "\\newline", k, j)
2345 while document.body[l] == "":
2347 if document.body[l].strip() and document.body[l].split()[0] == "\\newline":
2348 document.body[k:l+1] = ["\\end_layout", "",
2349 '\\begin_layout %s' % document.default_layout]
2352 # We need an empty line if document.default_layout == ''
2353 if document.body[l+1] != '':
2354 document.body.insert(l+1, '')
2362 def convert_french(document):
2363 " Convert frenchb. "
2364 regexp = re.compile(r'^\\language\s+frenchb')
2365 i = find_re(document.header, regexp, 0)
2367 document.header[i] = "\\language french"
2369 # Change language in the document body
2370 regexp = re.compile(r'^\\lang\s+frenchb')
2373 i = find_re(document.body, regexp, i)
2376 document.body[i] = "\\lang french"
2380 def remove_paperpackage(document):
2381 " Remove paper package. "
2382 i = find_token(document.header, '\\paperpackage', 0)
2387 paperpackage = document.header[i].split()[1]
2389 del document.header[i]
2391 if paperpackage not in ("a4", "a4wide", "widemarginsa4"):
2394 conv = {"a4":"\\usepackage{a4}","a4wide": "\\usepackage{a4wide}",
2395 "widemarginsa4": "\\usepackage[widemargins]{a4}"}
2396 # for compatibility we ensure it is the first entry in preamble
2397 document.preamble[0:0] = [conv[paperpackage]]
2399 i = find_token(document.header, '\\papersize', 0)
2401 document.header[i] = "\\papersize default"
2404 def remove_quotestimes(document):
2405 " Remove quotestimes. "
2406 i = find_token(document.header, '\\quotes_times', 0)
2409 del document.header[i]
2412 def convert_sgml_paragraphs(document):
2413 " Convert SGML paragraphs. "
2414 if document.backend != "docbook":
2419 i = find_token(document.body, "\\begin_layout SGML", i)
2424 document.body[i] = "\\begin_layout Standard"
2425 j = find_token(document.body, "\\end_layout", i)
2427 document.body[j+1:j+1] = ['','\\end_inset','','','\\end_layout']
2428 document.body[i+1:i+1] = ['\\begin_inset ERT','status inlined','','\\begin_layout Standard','']
2436 supported_versions = ["1.4.%d" % i for i in range(3)] + ["1.4"]
2437 convert = [[222, [insert_tracking_changes, add_end_header, convert_amsmath]],
2438 [223, [remove_color_default, convert_spaces, convert_bibtex, remove_insetparent]],
2439 [224, [convert_external, convert_comment]],
2440 [225, [add_end_layout, layout2begin_layout, convert_end_document,
2441 convert_table_valignment_middle, convert_breaks]],
2442 [226, [convert_note]],
2443 [227, [convert_box]],
2444 [228, [convert_collapsable, convert_ert]],
2445 [229, [convert_minipage]],
2446 [230, [convert_jurabib]],
2447 [231, [convert_float]],
2448 [232, [convert_bibtopic]],
2449 [233, [convert_graphics, convert_names]],
2450 [234, [convert_cite_engine]],
2451 [235, [convert_paperpackage]],
2452 [236, [convert_bullets, add_begin_header, add_begin_body,
2453 normalize_papersize, strip_end_space]],
2454 [237, [use_x_boolean]],
2455 [238, [update_latexaccents]],
2456 [239, [normalize_paragraph_params]],
2457 [240, [convert_output_changes]],
2458 [241, [convert_ert_paragraphs]],
2459 [242, [convert_french]],
2460 [243, [remove_paperpackage]],
2461 [244, [rename_spaces]],
2462 [245, [remove_quotestimes, convert_sgml_paragraphs]]]
2464 revert = [[244, []],
2465 [243, [revert_space_names]],
2468 [240, [revert_ert_paragraphs]],
2469 [239, [revert_output_changes]],
2472 [236, [use_x_binary]],
2473 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
2475 [234, [revert_paperpackage]],
2476 [233, [revert_cite_engine]],
2477 [232, [revert_names]],
2478 [231, [revert_bibtopic]],
2479 [230, [revert_float]],
2480 [229, [revert_jurabib]],
2482 [227, [revert_collapsable, revert_ert]],
2483 [226, [revert_box, revert_external_2]],
2484 [225, [revert_note]],
2485 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
2486 revert_valignment_middle, revert_breaks, convert_frameless_box,
2488 [223, [revert_external_2, revert_comment, revert_eqref]],
2489 [222, [revert_spaces, revert_bibtex]],
2490 [221, [revert_amsmath, rm_end_header, rm_tracking_changes, rm_body_changes]]]
2493 if __name__ == "__main__":