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
31 from string import replace, split, find, strip, join
33 from lyx_0_12 import update_latexaccents
35 ####################################################################
36 # Private helper functions
38 def get_layout(line, default_layout):
39 " Get layout, if empty return the default layout."
46 def get_paragraph(lines, i, format):
47 "Finds the paragraph that contains line i."
50 begin_layout = "\\layout"
52 begin_layout = "\\begin_layout"
54 i = find_tokens_backwards(lines, ["\\end_inset", begin_layout], i)
56 if check_token(lines[i], begin_layout):
58 i = find_beginning_of_inset(lines, i)
62 def find_beginning_of_inset(lines, i):
63 " Find beginning of inset, where lines[i] is included."
64 return find_beginning_of(lines, i, "\\begin_inset", "\\end_inset")
67 def get_next_paragraph(lines, i, format):
68 "Finds the paragraph after the paragraph that contains line i."
71 tokens = ["\\begin_inset", "\\layout", "\\end_float", "\\the_end"]
73 tokens = ["\\begin_inset", "\\begin_layout", "\\end_float", "\\end_document"]
75 tokens = ["\\begin_inset", "\\begin_layout", "\\end_float", "\\end_body", "\\end_document"]
77 i = find_tokens(lines, tokens, i)
78 if not check_token(lines[i], "\\begin_inset"):
80 i = find_end_of_inset(lines, i)
84 def find_end_of_inset(lines, i):
85 "Finds the matching \end_inset"
86 return find_end_of(lines, i, "\\begin_inset", "\\end_inset")
88 # End of helper functions
89 ####################################################################
91 def remove_color_default(document):
92 " Remove \color default"
95 i = find_token(document.body, "\\color default", i)
98 document.body[i] = replace(document.body[i], "\\color default",
102 def add_end_header(document):
104 document.header.append("\\end_header");
107 def rm_end_header(document):
108 " Remove \end_header"
109 i = find_token(document.header, "\\end_header", 0)
112 del document.header[i]
115 def convert_amsmath(document):
116 " Convert \\use_amsmath"
117 i = find_token(document.header, "\\use_amsmath", 0)
119 document.warning("Malformed LyX document: Missing '\\use_amsmath'.")
121 tokens = split(document.header[i])
123 document.warning("Malformed LyX document: Could not parse line '%s'." % document.header[i])
126 use_amsmath = tokens[1]
127 # old: 0 == off, 1 == on
128 # new: 0 == off, 1 == auto, 2 == on
129 # translate off -> auto, since old format 'off' means auto in reality
130 if use_amsmath == '0':
131 document.header[i] = "\\use_amsmath 1"
133 document.header[i] = "\\use_amsmath 2"
136 def revert_amsmath(document):
137 " Revert \\use_amsmath"
138 i = find_token(document.header, "\\use_amsmath", 0)
140 document.warning("Malformed LyX document: Missing '\\use_amsmath'.")
142 tokens = split(document.header[i])
144 document.warning("Malformed LyX document: Could not parse line '%s'." % document.header[i])
147 use_amsmath = tokens[1]
148 # old: 0 == off, 1 == on
149 # new: 0 == off, 1 == auto, 2 == on
150 # translate auto -> off, since old format 'off' means auto in reality
151 if use_amsmath == '2':
152 document.header[i] = "\\use_amsmath 1"
154 document.header[i] = "\\use_amsmath 0"
157 def convert_spaces(document):
158 " \SpecialChar ~ -> \InsetSpace ~"
159 for i in range(len(document.body)):
160 document.body[i] = replace(document.body[i],"\\SpecialChar ~","\\InsetSpace ~")
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] = replace(document.body[i],"\\InsetSpace \\space","\\InsetSpace \\space{}")
189 document.body[i] = replace(document.body[i],"\\InsetSpace \,","\\InsetSpace \\thinspace{}")
192 def revert_space_names(document):
193 """ \InsetSpace \thinspace{} -> \InsetSpace \,
194 \InsetSpace \space{} -> \InsetSpace \space"""
195 for i in range(len(document.body)):
196 document.body[i] = replace(document.body[i],"\\InsetSpace \\space{}","\\InsetSpace \\space")
197 document.body[i] = replace(document.body[i],"\\InsetSpace \\thinspace{}","\\InsetSpace \\,")
200 def lyx_support_escape(lab):
201 " Equivalent to lyx::support::escape()"
202 hexdigit = ['0', '1', '2', '3', '4', '5', '6', '7',
203 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
207 if o >= 128 or c == '=' or c == '%':
209 enc = enc + hexdigit[o >> 4]
210 enc = enc + hexdigit[o & 15]
216 def revert_eqref(document):
217 "\\begin_inset LatexCommand \\eqref -> ERT"
218 regexp = re.compile(r'^\\begin_inset\s+LatexCommand\s+\\eqref')
221 i = find_re(document.body, regexp, i)
224 eqref = lyx_support_escape(regexp.sub("", document.body[i]))
225 document.body[i:i+1] = ["\\begin_inset ERT", "status Collapsed", "",
226 '\\layout %s' % document.default_layout, "", "\\backslash ",
231 def convert_bibtex(document):
232 " Convert BibTeX changes."
233 for i in range(len(document.body)):
234 document.body[i] = replace(document.body[i],"\\begin_inset LatexCommand \\BibTeX",
235 "\\begin_inset LatexCommand \\bibtex")
238 def revert_bibtex(document):
239 " Revert BibTeX changes."
240 for i in range(len(document.body)):
241 document.body[i] = replace(document.body[i], "\\begin_inset LatexCommand \\bibtex",
242 "\\begin_inset LatexCommand \\BibTeX")
245 def remove_insetparent(document):
249 i = find_token(document.body, "\\begin_inset LatexCommand \\lyxparent", i)
252 del document.body[i:i+3]
255 def convert_external(document):
256 " Convert inset External."
257 external_rexp = re.compile(r'\\begin_inset External ([^,]*),"([^"]*)",')
258 external_header = "\\begin_inset External"
261 i = find_token(document.body, external_header, i)
264 look = external_rexp.search(document.body[i])
267 args[0] = look.group(1)
268 args[1] = look.group(2)
269 #FIXME: if the previous search fails then warn
271 if args[0] == "RasterImage":
272 # Convert a RasterImage External Inset to a Graphics Inset.
273 top = "\\begin_inset Graphics"
275 filename = "\tfilename " + args[1]
276 document.body[i:i+1] = [top, filename]
279 # Convert the old External Inset format to the new.
280 top = external_header
281 template = "\ttemplate " + args[0]
283 filename = "\tfilename " + args[1]
284 document.body[i:i+1] = [top, template, filename]
287 document.body[i:i+1] = [top, template]
291 def revert_external_1(document):
292 " Revert inset External."
293 external_header = "\\begin_inset External"
296 i = find_token(document.body, external_header, i)
300 template = split(document.body[i+1])
302 del document.body[i+1]
304 filename = split(document.body[i+1])
306 del document.body[i+1]
308 params = split(document.body[i+1])
310 if document.body[i+1]: del document.body[i+1]
312 document.body[i] = document.body[i] + " " + template[0]+ ', "' + filename[0] + '", " '+ join(params[1:]) + '"'
316 def revert_external_2(document):
317 " Revert inset External. (part II)"
318 draft_token = '\tdraft'
321 i = find_token(document.body, '\\begin_inset External', i)
324 j = find_end_of_inset(document.body, i + 1)
326 #this should not happen
328 k = find_token(document.body, draft_token, i+1, j-1)
329 if (k != -1 and len(draft_token) == len(document.body[k])):
334 def convert_comment(document):
335 " Convert \\layout comment"
337 comment = "\\layout Comment"
339 i = find_token(document.body, comment, i)
343 document.body[i:i+1] = ['\\layout %s' % document.default_layout,"","",
344 "\\begin_inset Comment",
346 '\\layout %s' % document.default_layout]
351 i = find_token(document.body, "\\layout", i)
353 i = len(document.body) - 1
354 document.body[i:i] = ["\\end_inset","",""]
357 j = find_token(document.body, '\\begin_deeper', old_i, i)
358 if j == -1: j = i + 1
359 k = find_token(document.body, '\\begin_inset', old_i, i)
360 if k == -1: k = i + 1
365 i = find_end_of( document.body, i, "\\begin_deeper","\\end_deeper")
367 #This case should not happen
368 #but if this happens deal with it greacefully adding
369 #the missing \end_deeper.
370 i = len(document.body) - 1
371 document.body[i:i] = ["\end_deeper",""]
379 i = find_end_of( document.body, i, "\\begin_inset","\\end_inset")
381 #This case should not happen
382 #but if this happens deal with it greacefully adding
383 #the missing \end_inset.
384 i = len(document.body) - 1
385 document.body[i:i] = ["\\end_inset","","","\\end_inset","",""]
391 if find(document.body[i], comment) == -1:
392 document.body[i:i] = ["\\end_inset"]
395 document.body[i:i+1] = ['\\layout %s' % document.default_layout]
399 def revert_comment(document):
403 i = find_tokens(document.body, ["\\begin_inset Comment", "\\begin_inset Greyedout"], i)
407 document.body[i] = "\\begin_inset Note"
411 def add_end_layout(document):
413 i = find_token(document.body, '\\layout', 0)
419 struct_stack = ["\\layout"]
422 i = find_tokens(document.body, ["\\begin_inset", "\\end_inset", "\\layout",
423 "\\begin_deeper", "\\end_deeper", "\\the_end"], i)
426 token = split(document.body[i])[0]
428 document.warning("Truncated document.")
429 i = len(document.body)
430 document.body.insert(i, '\\the_end')
433 if token == "\\begin_inset":
434 struct_stack.append(token)
438 if token == "\\end_inset":
439 tail = struct_stack.pop()
440 if tail == "\\layout":
441 document.body.insert(i,"")
442 document.body.insert(i,"\\end_layout")
444 #Check if it is the correct tag
449 if token == "\\layout":
450 tail = struct_stack.pop()
452 document.body.insert(i,"")
453 document.body.insert(i,"\\end_layout")
456 struct_stack.append(tail)
458 struct_stack.append(token)
461 if token == "\\begin_deeper":
462 document.body.insert(i,"")
463 document.body.insert(i,"\\end_layout")
465 struct_stack.append(token)
468 if token == "\\end_deeper":
469 if struct_stack[-1] == '\\layout':
470 document.body.insert(i, '\\end_layout')
477 document.body.insert(i, "")
478 document.body.insert(i, "\\end_layout")
482 def rm_end_layout(document):
483 " Remove \end_layout"
486 i = find_token(document.body, '\\end_layout', i)
494 def insert_tracking_changes(document):
495 " Handle change tracking keywords."
496 i = find_token(document.header, "\\tracking_changes", 0)
498 document.header.append("\\tracking_changes 0")
501 def rm_tracking_changes(document):
502 " Remove change tracking keywords."
503 i = find_token(document.header, "\\author", 0)
505 del document.header[i]
507 i = find_token(document.header, "\\tracking_changes", 0)
510 del document.header[i]
513 def rm_body_changes(document):
514 " Remove body changes."
517 i = find_token(document.body, "\\change_", i)
524 def layout2begin_layout(document):
525 " \layout -> \begin_layout "
528 i = find_token(document.body, '\\layout', i)
532 document.body[i] = replace(document.body[i], '\\layout', '\\begin_layout')
536 def begin_layout2layout(document):
537 " \begin_layout -> \layout "
540 i = find_token(document.body, '\\begin_layout', i)
544 document.body[i] = replace(document.body[i], '\\begin_layout', '\\layout')
548 def convert_valignment_middle(body, start, end):
549 'valignment="center" -> valignment="middle"'
550 for i in range(start, end):
551 if re.search('^<(column|cell) .*valignment="center".*>$', body[i]):
552 body[i] = replace(body[i], 'valignment="center"', 'valignment="middle"')
555 def convert_table_valignment_middle(document):
556 " Convert table valignment, center -> middle"
557 regexp = re.compile(r'^\\begin_inset\s+Tabular')
560 i = find_re(document.body, regexp, i)
563 j = find_end_of_inset(document.body, i + 1)
565 #this should not happen
566 convert_valignment_middle(document.body, i + 1, len(document.body))
568 convert_valignment_middle(document.body, i + 1, j)
572 def revert_table_valignment_middle(body, start, end):
573 " valignment, middle -> center"
574 for i in range(start, end):
575 if re.search('^<(column|cell) .*valignment="middle".*>$', body[i]):
576 body[i] = replace(body[i], 'valignment="middle"', 'valignment="center"')
579 def revert_valignment_middle(document):
580 " Convert table valignment, middle -> center"
581 regexp = re.compile(r'^\\begin_inset\s+Tabular')
584 i = find_re(document.body, regexp, i)
587 j = find_end_of_inset(document.body, i + 1)
589 #this should not happen
590 revert_table_valignment_middle(document.body, i + 1, len(document.body))
592 revert_table_valignment_middle(document.body, i + 1, j)
596 def convert_end_document(document):
597 "\\the_end -> \\end_document"
598 i = find_token(document.body, "\\the_end", 0)
600 document.body.append("\\end_document")
602 document.body[i] = "\\end_document"
605 def revert_end_document(document):
606 "\\end_document -> \\the_end"
607 i = find_token(document.body, "\\end_document", 0)
609 document.body.append("\\the_end")
611 document.body[i] = "\\the_end"
614 def convert_breaks(document):
616 Convert line and page breaks
619 \line_top \line_bottom \pagebreak_top \pagebreak_bottom \added_space_top xxx \added_space_bottom yyy
623 \begin layout Standard
629 \begin layout Standard
636 \begin_inset VSpace xxx
641 \begin_inset VSpace xxx
649 par_params = ('added_space_bottom', 'added_space_top', 'align',
650 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
651 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
653 font_attributes = ['\\family', '\\series', '\\shape', '\\emph',
654 '\\numeric', '\\bar', '\\noun', '\\color', '\\lang']
655 attribute_values = ['default', 'default', 'default', 'default',
656 'default', 'default', 'default', 'none', document.language]
659 i = find_token(document.body, "\\begin_layout", i)
662 layout = get_layout(document.body[i], document.default_layout)
665 # Merge all paragraph parameters into a single line
666 # We cannot check for '\\' only because paragraphs may start e.g.
668 while document.body[i + 1][:1] == '\\' and split(document.body[i + 1][1:])[0] in par_params:
669 document.body[i] = document.body[i + 1] + ' ' + document.body[i]
670 del document.body[i+1]
672 line_top = find(document.body[i],"\\line_top")
673 line_bot = find(document.body[i],"\\line_bottom")
674 pb_top = find(document.body[i],"\\pagebreak_top")
675 pb_bot = find(document.body[i],"\\pagebreak_bottom")
676 vspace_top = find(document.body[i],"\\added_space_top")
677 vspace_bot = find(document.body[i],"\\added_space_bottom")
679 if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1:
682 # Do we have a nonstandard paragraph? We need to create new paragraphs
683 # if yes to avoid putting lyxline etc. inside of special environments.
684 # This is wrong for itemize and enumerate environments, but it is
685 # impossible to convert these correctly.
686 # We want to avoid new paragraphs if possible becauase we want to
687 # inherit font sizes.
689 if (not document.is_default_layout(layout) or
690 find(document.body[i],"\\align") != -1 or
691 find(document.body[i],"\\labelwidthstring") != -1 or
692 find(document.body[i],"\\noindent") != -1):
695 # get the font size of the beginning of this paragraph, since we need
696 # it for the lyxline inset
698 while not is_nonempty_line(document.body[j]):
701 if find(document.body[j], "\\size") != -1:
702 size_top = split(document.body[j])[1]
704 for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom":
705 document.body[i] = replace(document.body[i], tag, "")
708 # the position could be change because of the removal of other
709 # paragraph properties above
710 vspace_top = find(document.body[i],"\\added_space_top")
711 tmp_list = split(document.body[i][vspace_top:])
712 vspace_top_value = tmp_list[1]
713 document.body[i] = document.body[i][:vspace_top] + join(tmp_list[2:])
716 # the position could be change because of the removal of other
717 # paragraph properties above
718 vspace_bot = find(document.body[i],"\\added_space_bottom")
719 tmp_list = split(document.body[i][vspace_bot:])
720 vspace_bot_value = tmp_list[1]
721 document.body[i] = document.body[i][:vspace_bot] + join(tmp_list[2:])
723 document.body[i] = strip(document.body[i])
726 # Create an empty paragraph or paragraph fragment for line and
727 # page break that belong above the paragraph
728 if pb_top !=-1 or line_top != -1 or vspace_top != -1:
730 paragraph_above = list()
732 # We need to create an extra paragraph for nonstandard environments
733 paragraph_above = ['\\begin_layout %s' % document.default_layout, '']
736 paragraph_above.extend(['\\newpage ',''])
739 paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','',''])
743 paragraph_above.extend(['\\size ' + size_top + ' '])
744 # We need an additional vertical space of -\parskip.
745 # We can't use the vspace inset because it does not know \parskip.
746 paragraph_above.extend(['\\lyxline ', '', ''])
747 insert_ert(paragraph_above, len(paragraph_above) - 1, 'Collapsed',
748 '\\vspace{-1\\parskip}\n', document.format + 1, document.default_layout)
749 paragraph_above.extend([''])
752 paragraph_above.extend(['\\end_layout ',''])
753 # insert new paragraph above the current paragraph
754 document.body[i-2:i-2] = paragraph_above
756 # insert new lines at the beginning of the current paragraph
757 document.body[i:i] = paragraph_above
759 i = i + len(paragraph_above)
761 # Ensure that nested style are converted later.
762 k = find_end_of(document.body, i, "\\begin_layout", "\\end_layout")
767 if pb_bot !=-1 or line_bot != -1 or vspace_bot != -1:
769 # get the font size of the end of this paragraph
773 if find(document.body[j], "\\size") != -1:
774 size_bot = split(document.body[j])[1]
776 elif find(document.body[j], "\\begin_inset") != -1:
778 j = find_end_of_inset(document.body, j)
782 paragraph_below = list()
784 # We need to create an extra paragraph for nonstandard environments
785 paragraph_below = ['', '\\begin_layout %s' % document.default_layout, '']
787 for a in range(len(font_attributes)):
788 if find_token(document.body, font_attributes[a], i, k) != -1:
789 paragraph_below.extend([font_attributes[a] + ' ' + attribute_values[a]])
792 if nonstandard and size_bot != '':
793 paragraph_below.extend(['\\size ' + size_bot + ' '])
794 paragraph_below.extend(['\\lyxline ',''])
796 paragraph_below.extend(['\\size default '])
799 paragraph_below.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','',''])
802 paragraph_below.extend(['\\newpage ',''])
805 paragraph_below.extend(['\\end_layout '])
806 # insert new paragraph below the current paragraph
807 document.body[k+1:k+1] = paragraph_below
809 # insert new lines at the end of the current paragraph
810 document.body[k:k] = paragraph_below
813 def convert_note(document):
817 i = find_tokens(document.body, ["\\begin_inset Note",
818 "\\begin_inset Comment",
819 "\\begin_inset Greyedout"], i)
823 document.body[i] = document.body[i][0:13] + 'Note ' + document.body[i][13:]
827 def revert_note(document):
829 note_header = "\\begin_inset Note "
832 i = find_token(document.body, note_header, i)
836 document.body[i] = "\\begin_inset " + document.body[i][len(note_header):]
840 def convert_box(document):
844 i = find_tokens(document.body, ["\\begin_inset Boxed",
845 "\\begin_inset Doublebox",
846 "\\begin_inset Frameless",
847 "\\begin_inset ovalbox",
848 "\\begin_inset Ovalbox",
849 "\\begin_inset Shadowbox"], i)
853 document.body[i] = document.body[i][0:13] + 'Box ' + document.body[i][13:]
857 def revert_box(document):
859 box_header = "\\begin_inset Box "
862 i = find_token(document.body, box_header, i)
866 document.body[i] = "\\begin_inset " + document.body[i][len(box_header):]
870 def convert_collapsable(document):
871 " Convert collapsed insets. "
874 i = find_tokens_exact(document.body, ["\\begin_inset Box",
875 "\\begin_inset Branch",
876 "\\begin_inset CharStyle",
877 "\\begin_inset Float",
878 "\\begin_inset Foot",
879 "\\begin_inset Marginal",
880 "\\begin_inset Note",
881 "\\begin_inset OptArg",
882 "\\begin_inset Wrap"], i)
886 # Seach for a line starting 'collapsed'
887 # If, however, we find a line starting '\begin_layout'
888 # (_always_ present) then break with a warning message
891 if (document.body[i] == "collapsed false"):
892 document.body[i] = "status open"
894 elif (document.body[i] == "collapsed true"):
895 document.body[i] = "status collapsed"
897 elif (document.body[i][:13] == "\\begin_layout"):
898 document.warning("Malformed LyX document: Missing 'collapsed'.")
905 def revert_collapsable(document):
906 " Revert collapsed insets. "
909 i = find_tokens_exact(document.body, ["\\begin_inset Box",
910 "\\begin_inset Branch",
911 "\\begin_inset CharStyle",
912 "\\begin_inset Float",
913 "\\begin_inset Foot",
914 "\\begin_inset Marginal",
915 "\\begin_inset Note",
916 "\\begin_inset OptArg",
917 "\\begin_inset Wrap"], i)
921 # Seach for a line starting 'status'
922 # If, however, we find a line starting '\begin_layout'
923 # (_always_ present) then break with a warning message
926 if (document.body[i] == "status open"):
927 document.body[i] = "collapsed false"
929 elif (document.body[i] == "status collapsed" or
930 document.body[i] == "status inlined"):
931 document.body[i] = "collapsed true"
933 elif (document.body[i][:13] == "\\begin_layout"):
934 document.warning("Malformed LyX document: Missing 'status'.")
941 def convert_ert(document):
945 i = find_token(document.body, "\\begin_inset ERT", i)
949 # Seach for a line starting 'status'
950 # If, however, we find a line starting '\begin_layout'
951 # (_always_ present) then break with a warning message
954 if (document.body[i] == "status Open"):
955 document.body[i] = "status open"
957 elif (document.body[i] == "status Collapsed"):
958 document.body[i] = "status collapsed"
960 elif (document.body[i] == "status Inlined"):
961 document.body[i] = "status inlined"
963 elif (document.body[i][:13] == "\\begin_layout"):
964 document.warning("Malformed LyX document: Missing 'status'.")
971 def revert_ert(document):
975 i = find_token(document.body, "\\begin_inset ERT", i)
979 # Seach for a line starting 'status'
980 # If, however, we find a line starting '\begin_layout'
981 # (_always_ present) then break with a warning message
984 if (document.body[i] == "status open"):
985 document.body[i] = "status Open"
987 elif (document.body[i] == "status collapsed"):
988 document.body[i] = "status Collapsed"
990 elif (document.body[i] == "status inlined"):
991 document.body[i] = "status Inlined"
993 elif (document.body[i][:13] == "\\begin_layout"):
994 document.warning("Malformed LyX document : Missing 'status'.")
1001 def convert_minipage(document):
1002 """ Convert minipages to the box inset.
1003 We try to use the same order of arguments as lyx does.
1006 inner_pos = ["c","t","b","s"]
1010 i = find_token(document.body, "\\begin_inset Minipage", i)
1014 document.body[i] = "\\begin_inset Box Frameless"
1017 # convert old to new position using the pos list
1018 if document.body[i][:8] == "position":
1019 document.body[i] = 'position "%s"' % pos[int(document.body[i][9])]
1021 document.body.insert(i, 'position "%s"' % pos[0])
1024 document.body.insert(i, 'hor_pos "c"')
1026 document.body.insert(i, 'has_inner_box 1')
1029 # convert the inner_position
1030 if document.body[i][:14] == "inner_position":
1031 innerpos = inner_pos[int(document.body[i][15])]
1032 del document.body[i]
1034 innerpos = inner_pos[0]
1036 # We need this since the new file format has a height and width
1037 # in a different order.
1038 if document.body[i][:6] == "height":
1039 height = document.body[i][6:]
1040 # test for default value of 221 and convert it accordingly
1041 if height == ' "0pt"' or height == ' "0"':
1043 del document.body[i]
1047 if document.body[i][:5] == "width":
1048 width = document.body[i][5:]
1049 del document.body[i]
1053 if document.body[i][:9] == "collapsed":
1054 if document.body[i][9:] == "true":
1055 status = "collapsed"
1058 del document.body[i]
1060 status = "collapsed"
1062 # Handle special default case:
1063 if height == ' "1pt"' and innerpos == 'c':
1066 document.body.insert(i, 'inner_pos "' + innerpos + '"')
1068 document.body.insert(i, 'use_parbox 0')
1070 document.body.insert(i, 'width' + width)
1072 document.body.insert(i, 'special "none"')
1074 document.body.insert(i, 'height' + height)
1076 document.body.insert(i, 'height_special "totalheight"')
1078 document.body.insert(i, 'status ' + status)
1082 def convert_ertbackslash(body, i, ert, format, default_layout):
1083 r""" -------------------------------------------------------------------------------------------
1084 Convert backslashes and '\n' into valid ERT code, append the converted
1085 text to body[i] and return the (maybe incremented) line index i"""
1089 body[i] = body[i] + '\\backslash '
1094 body[i+1:i+1] = ['\\newline ', '']
1097 body[i+1:i+1] = ['\\end_layout', '', '\\begin_layout %s' % default_layout, '']
1100 body[i] = body[i] + c
1104 def ert2latex(lines, format):
1105 r""" Converts lines in ERT code to LaTeX
1106 The surrounding \begin_layout ... \end_layout pair must not be included"""
1108 backslash = re.compile(r'\\backslash\s*$')
1109 newline = re.compile(r'\\newline\s*$')
1111 begin_layout = re.compile(r'\\layout\s*\S+$')
1113 begin_layout = re.compile(r'\\begin_layout\s*\S+$')
1114 end_layout = re.compile(r'\\end_layout\s*$')
1116 for i in range(len(lines)):
1117 line = backslash.sub('\\\\', lines[i])
1119 if begin_layout.match(line):
1122 line = newline.sub('\n', line)
1124 if begin_layout.match(line):
1126 if format > 224 and end_layout.match(line):
1132 def get_par_params(lines, i):
1133 """ get all paragraph parameters. They can be all on one line or on several lines.
1134 lines[i] must be the first parameter line"""
1135 par_params = ('added_space_bottom', 'added_space_top', 'align',
1136 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
1137 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
1138 'start_of_appendix')
1139 # We cannot check for '\\' only because paragraphs may start e.g.
1140 # with '\\backslash'
1142 while lines[i][:1] == '\\' and split(lines[i][1:])[0] in par_params:
1143 params = params + ' ' + strip(lines[i])
1145 return strip(params)
1148 def lyxsize2latexsize(lyxsize):
1149 " Convert LyX font size to LaTeX fontsize. "
1150 sizes = {"tiny" : "tiny", "scriptsize" : "scriptsize",
1151 "footnotesize" : "footnotesize", "small" : "small",
1152 "normal" : "normalsize", "large" : "large", "larger" : "Large",
1153 "largest" : "LARGE", "huge" : "huge", "giant" : "Huge"}
1154 if lyxsize in sizes:
1155 return '\\' + sizes[lyxsize]
1159 def revert_breaks(document):
1160 """ Change vspace insets, page breaks and lyxlines to paragraph options
1161 (if possible) or ERT"""
1163 # Get default spaceamount
1164 i = find_token(document.header, '\\defskip', 0)
1166 defskipamount = 'medskip'
1168 defskipamount = split(document.header[i])[1]
1170 keys = {"\\begin_inset" : "vspace", "\\lyxline" : "lyxline",
1171 "\\newpage" : "newpage"}
1172 keywords_top = {"vspace" : "\\added_space_top", "lyxline" : "\\line_top",
1173 "newpage" : "\\pagebreak_top"}
1174 keywords_bot = {"vspace" : "\\added_space_bottom", "lyxline" : "\\line_bottom",
1175 "newpage" : "\\pagebreak_bottom"}
1176 tokens = ["\\begin_inset VSpace", "\\lyxline", "\\newpage"]
1178 # Convert the insets
1181 i = find_tokens(document.body, tokens, i)
1185 # Are we at the beginning of a paragraph?
1187 this_par = get_paragraph(document.body, i, document.format - 1)
1188 start = this_par + 1
1189 params = get_par_params(document.body, start)
1191 # Paragraph parameters may be on one or more lines.
1192 # Find the start of the real paragraph text.
1193 while document.body[start][:1] == '\\' and split(document.body[start])[0] in params:
1195 for k in range(start, i):
1196 if find(document.body[k], "\\size") != -1:
1198 size = split(document.body[k])[1]
1199 elif is_nonempty_line(document.body[k]):
1202 # Find the end of the real paragraph text.
1203 next_par = get_next_paragraph(document.body, i, document.format - 1)
1205 document.warning("Malformed LyX document: Missing next paragraph.")
1209 # first line of our insets
1211 # last line of our insets
1212 inset_end = inset_start
1213 # Are we at the end of a paragraph?
1215 # start and end line numbers to delete if we convert this inset
1217 # is this inset a lyxline above a paragraph?
1219 # raw inset information
1221 # name of this inset
1223 # font size of this inset
1226 # Detect subsequent lyxline, vspace and pagebreak insets created by convert_breaks()
1230 if find_tokens(document.body, tokens, k) == k:
1232 lines.append(split(document.body[k]))
1233 insets.append(keys[lines[n][0]])
1234 del_lines.append([k, k])
1239 elif find(document.body[k], "\\size") != -1:
1241 size = split(document.body[k])[1]
1242 elif find_token(document.body, "\\begin_inset ERT", k) == k:
1243 ert_begin = find_token(document.body, "\\layout", k) + 1
1245 document.warning("Malformed LyX document: Missing '\\layout'.")
1247 ert_end = find_end_of_inset(document.body, k)
1249 document.warning("Malformed LyX document: Missing '\\end_inset'.")
1251 ert = ert2latex(document.body[ert_begin:ert_end], document.format - 1)
1252 if (n > 0 and insets[n - 1] == "lyxline" and
1253 ert == '\\vspace{-1\\parskip}\n'):
1254 # vspace ERT created by convert_breaks() for top lyxline
1256 del_lines[n - 1][1] = ert_end
1262 elif (n > 0 and insets[n - 1] == "vspace" and
1263 find_token(document.body, "\\end_inset", k) == k):
1264 # ignore end of vspace inset
1265 del_lines[n - 1][1] = k
1267 elif is_nonempty_line(document.body[k]):
1272 # Determine space amount for vspace insets
1273 spaceamount = list()
1276 if insets[k] == "vspace":
1277 spaceamount.append(lines[k][2])
1278 arguments.append(' ' + spaceamount[k] + ' ')
1280 spaceamount.append('')
1281 arguments.append(' ')
1283 # Can we convert to top paragraph parameters?
1285 if ((n == 3 and insets[0] == "newpage" and insets[1] == "vspace" and
1286 insets[2] == "lyxline" and top[2]) or
1288 ((insets[0] == "newpage" and insets[1] == "vspace") or
1289 (insets[0] == "newpage" and insets[1] == "lyxline" and top[1]) or
1290 (insets[0] == "vspace" and insets[1] == "lyxline" and top[1]))) or
1291 (n == 1 and insets[0] == "lyxline" and top[0])):
1292 # These insets have been created before a paragraph by
1296 # Can we convert to bottom paragraph parameters?
1298 if ((n == 3 and insets[0] == "lyxline" and not top[0] and
1299 insets[1] == "vspace" and insets[2] == "newpage") or
1301 ((insets[0] == "lyxline" and not top[0] and insets[1] == "vspace") or
1302 (insets[0] == "lyxline" and not top[0] and insets[1] == "newpage") or
1303 (insets[0] == "vspace" and insets[1] == "newpage"))) or
1304 (n == 1 and insets[0] == "lyxline" and not top[0])):
1305 # These insets have been created after a paragraph by
1309 if paragraph_start and paragraph_end:
1310 # We are in a paragraph of our own.
1311 # We must not delete this paragraph if it has parameters
1313 # First try to merge with the previous paragraph.
1314 # We try the previous paragraph first because we would
1315 # otherwise need ERT for two subsequent vspaces.
1316 prev_par = get_paragraph(document.body, this_par - 1, document.format - 1) + 1
1317 if prev_par > 0 and not before:
1318 prev_params = get_par_params(document.body, prev_par + 1)
1320 # determine font size
1321 prev_size = "normal"
1323 while document.body[k][:1] == '\\' and split(document.body[k])[0] in prev_params:
1326 if find(document.body[k], "\\size") != -1:
1327 prev_size = split(document.body[k])[1]
1329 elif find(document.body[k], "\\begin_inset") != -1:
1331 k = find_end_of_inset(document.body, k)
1332 elif is_nonempty_line(document.body[k]):
1336 if (keywords_bot[insets[k]] in prev_params or
1337 (insets[k] == "lyxline" and sizes[k] != prev_size)):
1342 document.body.insert(prev_par + 1,
1343 keywords_bot[insets[k]] + arguments[k])
1344 del document.body[this_par+n:next_par-1+n]
1347 # Then try next paragraph
1348 if next_par > 0 and not after:
1349 next_params = get_par_params(document.body, next_par + 1)
1351 while document.body[k][:1] == '\\' and split(document.body[k])[0] in next_params:
1353 # determine font size
1354 next_size = "normal"
1357 if find(document.body[k], "\\size") != -1:
1358 next_size = split(document.body[k])[1]
1360 elif is_nonempty_line(document.body[k]):
1364 if (keywords_top[insets[k]] in next_params or
1365 (insets[k] == "lyxline" and sizes[k] != next_size)):
1370 document.body.insert(next_par + 1,
1371 keywords_top[insets[k]] + arguments[k])
1372 del document.body[this_par:next_par-1]
1375 elif paragraph_start or paragraph_end:
1376 # Convert to paragraph formatting if we are at the beginning or end
1377 # of a paragraph and the resulting paragraph would not be empty
1378 # The order is important: del and insert invalidate some indices
1380 keywords = keywords_top
1382 keywords = keywords_bot
1385 if keywords[insets[k]] in params:
1390 document.body.insert(this_par + 1,
1391 keywords[insets[k]] + arguments[k])
1392 for j in range(k, n):
1393 del_lines[j][0] = del_lines[j][0] + 1
1394 del_lines[j][1] = del_lines[j][1] + 1
1395 del document.body[del_lines[k][0]:del_lines[k][1]+1]
1396 deleted = del_lines[k][1] - del_lines[k][0] + 1
1397 for j in range(k + 1, n):
1398 del_lines[j][0] = del_lines[j][0] - deleted
1399 del_lines[j][1] = del_lines[j][1] - deleted
1403 # Convert the first inset to ERT.
1404 # The others are converted in the next loop runs (if they exist)
1405 if insets[0] == "vspace":
1406 document.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
1407 '\\layout %s' % document.default_layout, '', '\\backslash ']
1409 if spaceamount[0][-1] == '*':
1410 spaceamount[0] = spaceamount[0][:-1]
1415 # Replace defskip by the actual value
1416 if spaceamount[0] == 'defskip':
1417 spaceamount[0] = defskipamount
1419 # LaTeX does not know \\smallskip* etc
1421 if spaceamount[0] == 'smallskip':
1422 spaceamount[0] = '\\smallskipamount'
1423 elif spaceamount[0] == 'medskip':
1424 spaceamount[0] = '\\medskipamount'
1425 elif spaceamount[0] == 'bigskip':
1426 spaceamount[0] = '\\bigskipamount'
1427 elif spaceamount[0] == 'vfill':
1428 spaceamount[0] = '\\fill'
1430 # Finally output the LaTeX code
1431 if (spaceamount[0] == 'smallskip' or spaceamount[0] == 'medskip' or
1432 spaceamount[0] == 'bigskip' or spaceamount[0] == 'vfill'):
1433 document.body.insert(i, spaceamount[0] + '{}')
1436 document.body.insert(i, 'vspace*{')
1438 document.body.insert(i, 'vspace{')
1439 i = convert_ertbackslash(document.body, i, spaceamount[0], document.format - 1, document.default_layout)
1440 document.body[i] = document.body[i] + '}'
1442 elif insets[0] == "lyxline":
1443 document.body[i] = ''
1444 latexsize = lyxsize2latexsize(size)
1446 document.warning("Could not convert LyX fontsize '%s' to LaTeX font size." % size)
1447 latexsize = '\\normalsize'
1448 i = insert_ert(document.body, i, 'Collapsed',
1449 '\\lyxline{%s}' % latexsize,
1450 document.format - 1, document.default_layout)
1451 # We use \providecommand so that we don't get an error if native
1452 # lyxlines are used (LyX writes first its own preamble and then
1453 # the user specified one)
1454 add_to_preamble(document,
1455 ['% Commands inserted by lyx2lyx for lyxlines',
1456 '\\providecommand{\\lyxline}[1]{',
1457 ' {#1 \\vspace{1ex} \\hrule width \\columnwidth \\vspace{1ex}}'
1459 elif insets[0] == "newpage":
1460 document.body[i] = ''
1461 i = insert_ert(document.body, i, 'Collapsed', '\\newpage{}',
1462 document.format - 1, document.default_layout)
1465 # Convert a LyX length into a LaTeX length
1466 def convert_len(len, special):
1467 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
1468 "page%":"\\pagewidth", "line%":"\\linewidth",
1469 "theight%":"\\textheight", "pheight%":"\\pageheight"}
1471 # Convert special lengths
1472 if special != 'none':
1473 len = '%f\\' % len2value(len) + special
1475 # Convert LyX units to LaTeX units
1476 for unit in units.keys():
1477 if find(len, unit) != -1:
1478 len = '%f' % (len2value(len) / 100) + units[unit]
1484 def convert_ertlen(body, i, len, special, format, default_layout):
1485 """ Convert a LyX length into valid ERT code and append it to body[i]
1486 Return the (maybe incremented) line index i
1487 Convert backslashes and insert the converted length into body. """
1488 return convert_ertbackslash(body, i, convert_len(len, special), format, default_layout)
1492 " Return the value of len without the unit in numerical form. "
1493 result = re.search('([+-]?[0-9.]+)', len)
1495 return float(result.group(1))
1496 # No number means 1.0
1500 def insert_ert(body, i, status, text, format, default_layout):
1501 """ Convert text to ERT and insert it at body[i]
1502 Return the index of the line after the inserted ERT"""
1504 body[i:i] = ['\\begin_inset ERT', 'status ' + status, '']
1507 body[i:i] = ['\\layout %s' % default_layout, '']
1509 body[i:i] = ['\\begin_layout %s' % default_layout, '']
1510 i = i + 1 # i points now to the just created empty line
1511 i = convert_ertbackslash(body, i, text, format, default_layout) + 1
1513 body[i:i] = ['\\end_layout']
1515 body[i:i] = ['', '\\end_inset', '']
1520 def add_to_preamble(document, text):
1521 """ Add text to the preamble if it is not already there.
1522 Only the first line is checked!"""
1524 if find_token(document.preamble, text[0], 0) != -1:
1527 document.preamble.extend(text)
1530 def convert_frameless_box(document):
1531 " Convert frameless box."
1532 pos = ['t', 'c', 'b']
1533 inner_pos = ['c', 't', 'b', 's']
1536 i = find_token(document.body, '\\begin_inset Frameless', i)
1539 j = find_end_of_inset(document.body, i)
1541 document.warning("Malformed LyX document: Missing '\\end_inset'.")
1544 del document.body[i]
1548 params = {'position':0, 'hor_pos':'c', 'has_inner_box':'1',
1549 'inner_pos':1, 'use_parbox':'0', 'width':'100col%',
1550 'special':'none', 'height':'1in',
1551 'height_special':'totalheight', 'collapsed':'false'}
1552 for key in params.keys():
1553 value = replace(get_value(document.body, key, i, j), '"', '')
1555 if key == 'position':
1556 # convert new to old position: 'position "t"' -> 0
1557 value = find_token(pos, value, 0)
1560 elif key == 'inner_pos':
1561 # convert inner position
1562 value = find_token(inner_pos, value, 0)
1567 j = del_token(document.body, key, i, j)
1570 # Convert to minipage or ERT?
1571 # Note that the inner_position and height parameters of a minipage
1572 # inset are ignored and not accessible for the user, although they
1573 # are present in the file format and correctly read in and written.
1574 # Therefore we convert to ERT if they do not have their LaTeX
1575 # defaults. These are:
1576 # - the value of "position" for "inner_pos"
1577 # - "\totalheight" for "height"
1578 if (params['use_parbox'] != '0' or
1579 params['has_inner_box'] != '1' or
1580 params['special'] != 'none' or
1581 params['height_special'] != 'totalheight' or
1582 len2value(params['height']) != 1.0):
1584 # Here we know that this box is not supported in file format 224.
1585 # Therefore we need to convert it to ERT. We can't simply convert
1586 # the beginning and end of the box to ERT, because the
1587 # box inset may contain layouts that are different from the
1588 # surrounding layout. After the conversion the contents of the
1589 # box inset is on the same level as the surrounding text, and
1590 # paragraph layouts and align parameters can get mixed up.
1592 # A possible solution for this problem:
1593 # Convert the box to a minipage and redefine the minipage
1594 # environment in ERT so that the original box is simulated.
1595 # For minipages we could do this in a way that the width and
1596 # position can still be set from LyX, but this did not work well.
1597 # This is not possible for parboxes either, so we convert the
1598 # original box to ERT, put the minipage inset inside the box
1599 # and redefine the minipage environment to be empty.
1601 # Commands that are independant of a particular box can go to
1603 # We need to define lyxtolyxrealminipage with 3 optional
1604 # arguments although LyX 1.3 uses only the first one.
1605 # Otherwise we will get LaTeX errors if this document is
1606 # converted to format 225 or above again (LyX 1.4 uses all
1607 # optional arguments).
1608 add_to_preamble(document,
1609 ['% Commands inserted by lyx2lyx for frameless boxes',
1610 '% Save the original minipage environment',
1611 '\\let\\lyxtolyxrealminipage\\minipage',
1612 '\\let\\endlyxtolyxrealminipage\\endminipage',
1613 '% Define an empty lyxtolyximinipage environment',
1614 '% with 3 optional arguments',
1615 '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1616 '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1617 ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1618 ' {\\end{lyxtolyxiiiminipage}}',
1619 '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1620 ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1621 ' {\\end{lyxtolyxiiminipage}}',
1622 '\\newenvironment{lyxtolyxminipage}[1][c]%',
1623 ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1624 ' {\\end{lyxtolyximinipage}}'])
1626 if params['use_parbox'] != '0':
1629 ert = '\\begin{lyxtolyxrealminipage}'
1631 # convert optional arguments only if not latex default
1632 if (pos[params['position']] != 'c' or
1633 inner_pos[params['inner_pos']] != pos[params['position']] or
1634 params['height_special'] != 'totalheight' or
1635 len2value(params['height']) != 1.0):
1636 ert = ert + '[' + pos[params['position']] + ']'
1637 if (inner_pos[params['inner_pos']] != pos[params['position']] or
1638 params['height_special'] != 'totalheight' or
1639 len2value(params['height']) != 1.0):
1640 ert = ert + '[' + convert_len(params['height'],
1641 params['height_special']) + ']'
1642 if inner_pos[params['inner_pos']] != pos[params['position']]:
1643 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1645 ert = ert + '{' + convert_len(params['width'],
1646 params['special']) + '}'
1648 if params['use_parbox'] != '0':
1650 ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1651 ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1654 i = insert_ert(document.body, i, 'Collapsed', ert, document.format - 1, document.default_layout)
1655 j = j + i - old_i - 1
1657 document.body[i:i] = ['\\begin_inset Minipage',
1658 'position %d' % params['position'],
1661 'width "' + params['width'] + '"',
1662 'collapsed ' + params['collapsed']]
1666 # Restore the original minipage environment since we may have
1667 # minipages inside this box.
1668 # Start a new paragraph because the following may be nonstandard
1669 document.body[i:i] = ['\\layout %s' % document.default_layout, '', '']
1672 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1673 ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1675 i = insert_ert(document.body, i, 'Collapsed', ert, document.format - 1, document.default_layout)
1676 j = j + i - old_i - 1
1678 # Redefine the minipage end before the inset end.
1679 # Start a new paragraph because the previous may be nonstandard
1680 document.body[j:j] = ['\\layout %s' % document.default_layout, '', '']
1682 ert = '\\let\\endminipage\\endlyxtolyxminipage'
1683 j = insert_ert(document.body, j, 'Collapsed', ert, document.format - 1, document.default_layout)
1685 document.body.insert(j, '')
1688 # LyX writes '%\n' after each box. Therefore we need to end our
1689 # ERT with '%\n', too, since this may swallow a following space.
1690 if params['use_parbox'] != '0':
1693 ert = '\\end{lyxtolyxrealminipage}%\n'
1694 j = insert_ert(document.body, j, 'Collapsed', ert, document.format - 1, document.default_layout)
1696 # We don't need to restore the original minipage after the inset
1697 # end because the scope of the redefinition is the original box.
1701 # Convert to minipage
1702 document.body[i:i] = ['\\begin_inset Minipage',
1703 'position %d' % params['position'],
1704 'inner_position %d' % params['inner_pos'],
1705 'height "' + params['height'] + '"',
1706 'width "' + params['width'] + '"',
1707 'collapsed ' + params['collapsed']]
1711 def remove_branches(document):
1712 " Remove branches. "
1715 i = find_token(document.header, "\\branch", i)
1718 document.warning("Removing branch %s." % split(document.header[i])[1])
1719 j = find_token(document.header, "\\end_branch", i)
1721 document.warning("Malformed LyX document: Missing '\\end_branch'.")
1723 del document.header[i:j+1]
1727 i = find_token(document.body, "\\begin_inset Branch", i)
1730 j = find_end_of_inset(document.body, i)
1732 document.warning("Malformed LyX document: Missing '\\end_inset'.")
1735 del document.body[i]
1736 del document.body[j - 1]
1737 # Seach for a line starting 'collapsed'
1738 # If, however, we find a line starting '\layout'
1739 # (_always_ present) then break with a warning message
1742 if (document.body[i][:9] == "collapsed"):
1743 del document.body[i]
1746 elif (document.body[i][:7] == "\\layout"):
1747 if collapsed_found == 0:
1748 document.warning("Malformed LyX document: Missing 'collapsed'.")
1749 # Delete this new paragraph, since it would not appear in
1750 # .tex output. This avoids also empty paragraphs.
1751 del document.body[i]
1756 def convert_jurabib(document):
1757 " Convert jurabib. "
1758 i = find_token(document.header, '\\use_numerical_citations', 0)
1760 document.warning("Malformed lyx document: Missing '\\use_numerical_citations'.")
1762 document.header.insert(i + 1, '\\use_jurabib 0')
1765 def revert_jurabib(document):
1767 i = find_token(document.header, '\\use_jurabib', 0)
1769 document.warning("Malformed lyx document: Missing '\\use_jurabib'.")
1771 if get_value(document.header, '\\use_jurabib', 0) != "0":
1772 document.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1773 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1775 del document.header[i]
1778 def convert_bibtopic(document):
1779 " Convert bibtopic. "
1780 i = find_token(document.header, '\\use_jurabib', 0)
1782 document.warning("Malformed lyx document: Missing '\\use_jurabib'.")
1784 document.header.insert(i + 1, '\\use_bibtopic 0')
1787 def revert_bibtopic(document):
1788 " Revert bibtopic. "
1789 i = find_token(document.header, '\\use_bibtopic', 0)
1791 document.warning("Malformed lyx document: Missing '\\use_bibtopic'.")
1793 if get_value(document.header, '\\use_bibtopic', 0) != "0":
1794 document.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1795 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1796 del document.header[i]
1799 def convert_float(document):
1800 " Convert sideway floats. "
1803 i = find_token_exact(document.body, '\\begin_inset Float', i)
1806 # Seach for a line starting 'wide'
1807 # If, however, we find a line starting '\begin_layout'
1808 # (_always_ present) then break with a warning message
1811 if (document.body[i][:4] == "wide"):
1812 document.body.insert(i + 1, 'sideways false')
1814 elif (document.body[i][:13] == "\\begin_layout"):
1815 document.warning("Malformed lyx document: Missing 'wide'.")
1821 def revert_float(document):
1822 " Revert sideway floats. "
1825 i = find_token_exact(document.body, '\\begin_inset Float', i)
1828 j = find_end_of_inset(document.body, i)
1830 document.warning("Malformed lyx document: Missing '\\end_inset'.")
1833 if get_value(document.body, 'sideways', i, j) != "false":
1834 document.warning("Conversion of 'sideways true' not yet implemented.")
1835 # Don't remove 'sideways' so that people will get warnings by lyx
1838 del_token(document.body, 'sideways', i, j)
1842 def convert_graphics(document):
1843 """ Add extension to documentnames of insetgraphics if necessary.
1847 i = find_token(document.body, "\\begin_inset Graphics", i)
1851 j = find_token_exact(document.body, "documentname", i)
1855 filename = split(document.body[j])[1]
1856 absname = os.path.normpath(os.path.join(document.dir, filename))
1857 if document.input == stdin and not os.path.isabs(filename):
1858 # We don't know the directory and cannot check the document.
1859 # We could use a heuristic and take the current directory,
1860 # and we could try to find out if documentname has an extension,
1861 # but that would be just guesses and could be wrong.
1862 document.warning("""Warning: Can not determine whether document
1864 needs an extension when reading from standard input.
1865 You may need to correct the document manually or run
1866 lyx2lyx again with the .lyx document as commandline argument.""" % filename)
1868 # This needs to be the same algorithm as in pre 233 insetgraphics
1869 if access(absname, F_OK):
1871 if access(absname + ".ps", F_OK):
1872 document.body[j] = replace(document.body[j], filename, filename + ".ps")
1874 if access(absname + ".eps", F_OK):
1875 document.body[j] = replace(document.body[j], filename, filename + ".eps")
1878 def convert_names(document):
1879 """ Convert in the docbook backend from firstname and surname style
1882 if document.backend != "docbook":
1888 i = find_token(document.body, "\\begin_layout Author", i)
1893 while document.body[i] == "":
1896 if document.body[i][:11] != "\\end_layout" or document.body[i+2][:13] != "\\begin_deeper":
1901 i = find_end_of( document.body, i+3, "\\begin_deeper","\\end_deeper")
1903 # something is really wrong, abort
1904 document.warning("Missing \\end_deeper, after style Author.")
1905 document.warning("Aborted attempt to parse FirstName and Surname.")
1907 firstname, surname = "", ""
1909 name = document.body[k:i]
1911 j = find_token(name, "\\begin_layout FirstName", 0)
1914 while(name[j] != "\\end_layout"):
1915 firstname = firstname + name[j]
1918 j = find_token(name, "\\begin_layout Surname", 0)
1921 while(name[j] != "\\end_layout"):
1922 surname = surname + name[j]
1926 del document.body[k+2:i+1]
1928 document.body[k-1:k-1] = ["", "",
1929 "\\begin_inset CharStyle Firstname",
1932 '\\begin_layout %s' % document.default_layout,
1940 "\\begin_inset CharStyle Surname",
1943 '\\begin_layout %s' % document.default_layout,
1952 def revert_names(document):
1953 """ Revert in the docbook backend from firstname and surname char style
1956 if document.backend != "docbook":
1960 def convert_cite_engine(document):
1961 r""" \use_natbib 1 \cite_engine <style>
1962 \use_numerical_citations 0 -> where <style> is one of
1963 \use_jurabib 0 "basic", "natbib_authoryear","""
1965 a = find_token(document.header, "\\use_natbib", 0)
1967 document.warning("Malformed lyx document: Missing '\\use_natbib'.")
1970 b = find_token(document.header, "\\use_numerical_citations", 0)
1971 if b == -1 or b != a+1:
1972 document.warning("Malformed lyx document: Missing '\\use_numerical_citations'.")
1975 c = find_token(document.header, "\\use_jurabib", 0)
1976 if c == -1 or c != b+1:
1977 document.warning("Malformed lyx document: Missing '\\use_jurabib'.")
1980 use_natbib = int(split(document.header[a])[1])
1981 use_numerical_citations = int(split(document.header[b])[1])
1982 use_jurabib = int(split(document.header[c])[1])
1984 cite_engine = "basic"
1986 if use_numerical_citations:
1987 cite_engine = "natbib_numerical"
1989 cite_engine = "natbib_authoryear"
1991 cite_engine = "jurabib"
1993 del document.header[a:c+1]
1994 document.header.insert(a, "\\cite_engine " + cite_engine)
1997 def revert_cite_engine(document):
1998 " Revert the cite engine. "
1999 i = find_token(document.header, "\\cite_engine", 0)
2001 document.warning("Malformed lyx document: Missing '\\cite_engine'.")
2004 cite_engine = split(document.header[i])[1]
2009 if cite_engine == "natbib_numerical":
2012 elif cite_engine == "natbib_authoryear":
2014 elif cite_engine == "jurabib":
2017 del document.header[i]
2018 document.header.insert(i, "\\use_jurabib " + use_jurabib)
2019 document.header.insert(i, "\\use_numerical_citations " + use_numerical)
2020 document.header.insert(i, "\\use_natbib " + use_natbib)
2023 def convert_paperpackage(document):
2024 " Convert paper package. "
2025 i = find_token(document.header, "\\paperpackage", 0)
2029 packages = {'default':'none','a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
2030 if len(split(document.header[i])) > 1:
2031 paperpackage = split(document.header[i])[1]
2032 document.header[i] = replace(document.header[i], paperpackage, packages[paperpackage])
2034 document.header[i] = document.header[i] + ' widemarginsa4'
2037 def revert_paperpackage(document):
2038 " Revert paper package. "
2039 i = find_token(document.header, "\\paperpackage", 0)
2043 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
2044 'widemarginsa4':'', 'default': 'default'}
2045 if len(split(document.header[i])) > 1:
2046 paperpackage = split(document.header[i])[1]
2048 paperpackage = 'default'
2049 document.header[i] = replace(document.header[i], paperpackage, packages[paperpackage])
2052 def convert_bullets(document):
2053 " Convert bullets. "
2056 i = find_token(document.header, "\\bullet", i)
2059 if document.header[i][:12] == '\\bulletLaTeX':
2060 document.header[i] = document.header[i] + ' ' + strip(document.header[i+1])
2063 document.header[i] = document.header[i] + ' ' + strip(document.header[i+1]) +\
2064 ' ' + strip(document.header[i+2]) + ' ' + strip(document.header[i+3])
2066 del document.header[i+1:i + n]
2070 def revert_bullets(document):
2074 i = find_token(document.header, "\\bullet", i)
2077 if document.header[i][:12] == '\\bulletLaTeX':
2078 n = find(document.header[i], '"')
2080 document.warning("Malformed header.")
2083 document.header[i:i+1] = [document.header[i][:n-1],'\t' + document.header[i][n:], '\\end_bullet']
2086 frag = split(document.header[i])
2088 document.warning("Malformed header.")
2091 document.header[i:i+1] = [frag[0] + ' ' + frag[1],
2099 def add_begin_header(document):
2100 r" Add \begin_header and \begin_document. "
2101 i = find_token(document.header, '\\lyxformat', 0)
2102 document.header.insert(i+1, '\\begin_header')
2103 document.header.insert(i+1, '\\begin_document')
2106 def remove_begin_header(document):
2107 r" Remove \begin_header and \begin_document. "
2108 i = find_token(document.header, "\\begin_document", 0)
2110 del document.header[i]
2111 i = find_token(document.header, "\\begin_header", 0)
2113 del document.header[i]
2116 def add_begin_body(document):
2117 r" Add and \begin_document and \end_document"
2118 document.body.insert(0, '\\begin_body')
2119 document.body.insert(1, '')
2120 i = find_token(document.body, "\\end_document", 0)
2121 document.body.insert(i, '\\end_body')
2123 def remove_begin_body(document):
2124 r" Remove \begin_body and \end_body"
2125 i = find_token(document.body, "\\begin_body", 0)
2127 del document.body[i]
2128 if not document.body[i]:
2129 del document.body[i]
2130 i = find_token(document.body, "\\end_body", 0)
2132 del document.body[i]
2135 def normalize_papersize(document):
2136 r" Normalize \papersize"
2137 i = find_token(document.header, '\\papersize', 0)
2141 tmp = split(document.header[i])
2142 if tmp[1] == "Default":
2143 document.header[i] = '\\papersize default'
2145 if tmp[1] == "Custom":
2146 document.header[i] = '\\papersize custom'
2149 def denormalize_papersize(document):
2150 r" Revert \papersize"
2151 i = find_token(document.header, '\\papersize', 0)
2155 tmp = split(document.header[i])
2156 if tmp[1] == "custom":
2157 document.header[i] = '\\papersize Custom'
2160 def strip_end_space(document):
2161 " Strip spaces at end of command line. "
2162 for i in range(len(document.body)):
2163 if document.body[i][:1] == '\\':
2164 document.body[i] = strip(document.body[i])
2167 def use_x_boolean(document):
2168 r" Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes"
2169 bin2bool = {'0': 'false', '1': 'true'}
2170 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2171 i = find_token(document.header, use, 0)
2174 decompose = split(document.header[i])
2175 document.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
2178 def use_x_binary(document):
2179 r" Use digit values for \use_geometry, \use_bibtopic and \tracking_changes"
2180 bool2bin = {'false': '0', 'true': '1'}
2181 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2182 i = find_token(document.header, use, 0)
2185 decompose = split(document.header[i])
2186 document.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
2189 def normalize_paragraph_params(document):
2190 " Place all the paragraph parameters in their own line. "
2191 body = document.body
2193 allowed_parameters = '\\paragraph_spacing', '\\noindent', \
2194 '\\align', '\\labelwidthstring', "\\start_of_appendix", \
2199 i = find_token(document.body, '\\begin_layout', i)
2205 if strip(body[i]) and split(body[i])[0] not in allowed_parameters:
2208 j = find(body[i],'\\', 1)
2211 body[i:i+1] = [strip(body[i][:j]), body[i][j:]]
2216 def convert_output_changes (document):
2217 " Add output_changes parameter. "
2218 i = find_token(document.header, '\\tracking_changes', 0)
2220 document.warning("Malformed lyx document: Missing '\\tracking_changes'.")
2222 document.header.insert(i+1, '\\output_changes true')
2225 def revert_output_changes (document):
2226 " Remove output_changes parameter. "
2227 i = find_token(document.header, '\\output_changes', 0)
2230 del document.header[i]
2233 def convert_ert_paragraphs(document):
2234 " Convert paragraph breaks and sanitize paragraphs. "
2235 forbidden_settings = [
2236 # paragraph parameters
2237 '\\paragraph_spacing', '\\labelwidthstring',
2238 '\\start_of_appendix', '\\noindent',
2239 '\\leftindent', '\\align',
2241 '\\family', '\\series', '\\shape', '\\size',
2242 '\\emph', '\\numeric', '\\bar', '\\noun',
2243 '\\color', '\\lang']
2246 i = find_token(document.body, '\\begin_inset ERT', i)
2249 j = find_end_of_inset(document.body, i)
2251 document.warning("Malformed lyx document: Missing '\\end_inset'.")
2255 # convert non-standard paragraphs to standard
2258 k = find_token(document.body, "\\begin_layout", k, j)
2261 document.body[k] = '\\begin_layout %s' % document.default_layout
2264 # remove all paragraph parameters and font settings
2267 if (strip(document.body[k]) and
2268 split(document.body[k])[0] in forbidden_settings):
2269 del document.body[k]
2274 # insert an empty paragraph before each paragraph but the first
2278 k = find_token(document.body, "\\begin_layout", k, j)
2285 document.body[k:k] = ['\\begin_layout %s' % document.default_layout, "",
2290 # convert \\newline to new paragraph
2293 k = find_token(document.body, "\\newline", k, j)
2296 document.body[k:k+1] = ["\\end_layout", "", '\\begin_layout %s' % document.default_layout]
2299 # We need an empty line if document.default_layout == ''
2300 if document.body[k-1] != '':
2301 document.body.insert(k-1, '')
2307 def revert_ert_paragraphs(document):
2308 " Remove double paragraph breaks. "
2311 i = find_token(document.body, '\\begin_inset ERT', i)
2314 j = find_end_of_inset(document.body, i)
2316 document.warning("Malformed lyx document: Missing '\\end_inset'.")
2320 # replace paragraph breaks with \newline
2323 k = find_token(document.body, "\\end_layout", k, j)
2324 l = find_token(document.body, "\\begin_layout", k, j)
2325 if k == -1 or l == -1:
2327 document.body[k:l+1] = ["\\newline"]
2331 # replace double \newlines with paragraph breaks
2334 k = find_token(document.body, "\\newline", k, j)
2338 while document.body[l] == "":
2340 if strip(document.body[l]) and split(document.body[l])[0] == "\\newline":
2341 document.body[k:l+1] = ["\\end_layout", "",
2342 '\\begin_layout %s' % document.default_layout]
2345 # We need an empty line if document.default_layout == ''
2346 if document.body[l+1] != '':
2347 document.body.insert(l+1, '')
2355 def convert_french(document):
2356 " Convert frenchb. "
2357 regexp = re.compile(r'^\\language\s+frenchb')
2358 i = find_re(document.header, regexp, 0)
2360 document.header[i] = "\\language french"
2362 # Change language in the document body
2363 regexp = re.compile(r'^\\lang\s+frenchb')
2366 i = find_re(document.body, regexp, i)
2369 document.body[i] = "\\lang french"
2373 def remove_paperpackage(document):
2374 " Remove paper package. "
2375 i = find_token(document.header, '\\paperpackage', 0)
2380 paperpackage = split(document.header[i])[1]
2382 del document.header[i]
2384 if paperpackage not in ("a4", "a4wide", "widemarginsa4"):
2387 conv = {"a4":"\\usepackage{a4}","a4wide": "\\usepackage{a4wide}",
2388 "widemarginsa4": "\\usepackage[widemargins]{a4}"}
2389 # for compatibility we ensure it is the first entry in preamble
2390 document.preamble[0:0] = [conv[paperpackage]]
2392 i = find_token(document.header, '\\papersize', 0)
2394 document.header[i] = "\\papersize default"
2397 def remove_quotestimes(document):
2398 " Remove quotestimes. "
2399 i = find_token(document.header, '\\quotes_times', 0)
2402 del document.header[i]
2405 def convert_sgml_paragraphs(document):
2406 " Convert SGML paragraphs. "
2407 if document.backend != "docbook":
2412 i = find_token(document.body, "\\begin_layout SGML", i)
2417 document.body[i] = "\\begin_layout Standard"
2418 j = find_token(document.body, "\\end_layout", i)
2420 document.body[j+1:j+1] = ['','\\end_inset','','','\\end_layout']
2421 document.body[i+1:i+1] = ['\\begin_inset ERT','status inlined','','\\begin_layout Standard','']
2429 supported_versions = ["1.4.%d" % i for i in range(3)] + ["1.4"]
2430 convert = [[222, [insert_tracking_changes, add_end_header, convert_amsmath]],
2431 [223, [remove_color_default, convert_spaces, convert_bibtex, remove_insetparent]],
2432 [224, [convert_external, convert_comment]],
2433 [225, [add_end_layout, layout2begin_layout, convert_end_document,
2434 convert_table_valignment_middle, convert_breaks]],
2435 [226, [convert_note]],
2436 [227, [convert_box]],
2437 [228, [convert_collapsable, convert_ert]],
2438 [229, [convert_minipage]],
2439 [230, [convert_jurabib]],
2440 [231, [convert_float]],
2441 [232, [convert_bibtopic]],
2442 [233, [convert_graphics, convert_names]],
2443 [234, [convert_cite_engine]],
2444 [235, [convert_paperpackage]],
2445 [236, [convert_bullets, add_begin_header, add_begin_body,
2446 normalize_papersize, strip_end_space]],
2447 [237, [use_x_boolean]],
2448 [238, [update_latexaccents]],
2449 [239, [normalize_paragraph_params]],
2450 [240, [convert_output_changes]],
2451 [241, [convert_ert_paragraphs]],
2452 [242, [convert_french]],
2453 [243, [remove_paperpackage]],
2454 [244, [rename_spaces]],
2455 [245, [remove_quotestimes, convert_sgml_paragraphs]]]
2457 revert = [[244, []],
2458 [243, [revert_space_names]],
2461 [240, [revert_ert_paragraphs]],
2462 [239, [revert_output_changes]],
2465 [236, [use_x_binary]],
2466 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
2468 [234, [revert_paperpackage]],
2469 [233, [revert_cite_engine]],
2470 [232, [revert_names]],
2471 [231, [revert_bibtopic]],
2472 [230, [revert_float]],
2473 [229, [revert_jurabib]],
2475 [227, [revert_collapsable, revert_ert]],
2476 [226, [revert_box, revert_external_2]],
2477 [225, [revert_note]],
2478 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
2479 revert_valignment_middle, revert_breaks, convert_frameless_box,
2481 [223, [revert_external_2, revert_comment, revert_eqref]],
2482 [222, [revert_spaces, revert_bibtex]],
2483 [221, [revert_amsmath, rm_end_header, rm_tracking_changes, rm_body_changes]]]
2486 if __name__ == "__main__":