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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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, 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 def del_token(lines, token, start, end):
88 """ del_token(lines, token, start, end) -> int
90 Find the lower line in lines where token is the first element and
93 Returns the number of lines remaining."""
95 k = find_token_exact(lines, token, start, end)
102 # End of helper functions
103 ####################################################################
105 def remove_color_default(document):
106 " Remove \color default"
109 i = find_token(document.body, "\\color default", i)
112 document.body[i] = document.body[i].replace("\\color default",
116 def add_end_header(document):
118 document.header.append("\\end_header");
121 def rm_end_header(document):
122 " Remove \end_header"
123 i = find_token(document.header, "\\end_header", 0)
126 del document.header[i]
129 def convert_amsmath(document):
130 " Convert \\use_amsmath"
131 i = find_token(document.header, "\\use_amsmath", 0)
133 document.warning("Malformed LyX document: Missing '\\use_amsmath'.")
135 tokens = document.header[i].split()
137 document.warning("Malformed LyX document: Could not parse line '%s'." % document.header[i])
140 use_amsmath = tokens[1]
141 # old: 0 == off, 1 == on
142 # new: 0 == off, 1 == auto, 2 == on
143 # translate off -> auto, since old format 'off' means auto in reality
144 if use_amsmath == '0':
145 document.header[i] = "\\use_amsmath 1"
147 document.header[i] = "\\use_amsmath 2"
150 def revert_amsmath(document):
151 " Revert \\use_amsmath"
152 i = find_token(document.header, "\\use_amsmath", 0)
154 document.warning("Malformed LyX document: Missing '\\use_amsmath'.")
156 tokens = document.header[i].split()
158 document.warning("Malformed LyX document: Could not parse line '%s'." % document.header[i])
161 use_amsmath = tokens[1]
162 # old: 0 == off, 1 == on
163 # new: 0 == off, 1 == auto, 2 == on
164 # translate auto -> off, since old format 'off' means auto in reality
165 if use_amsmath == '2':
166 document.header[i] = "\\use_amsmath 1"
168 document.header[i] = "\\use_amsmath 0"
171 def convert_spaces(document):
172 " \SpecialChar ~ -> \InsetSpace ~"
173 for i in range(len(document.body)):
174 document.body[i] = document.body[i].replace("\\SpecialChar ~",
178 def revert_spaces(document):
179 " \InsetSpace ~ -> \SpecialChar ~"
180 regexp = re.compile(r'(.*)(\\InsetSpace\s+)(\S+)')
183 i = find_re(document.body, regexp, i)
186 space = regexp.match(document.body[i]).group(3)
187 prepend = regexp.match(document.body[i]).group(1)
189 document.body[i] = regexp.sub(prepend + '\\SpecialChar ~', document.body[i])
192 document.body[i] = regexp.sub(prepend, document.body[i])
193 document.body[i+1:i+1] = ''
194 if space == "\\space":
196 i = insert_ert(document.body, i+1, 'Collapsed', space, document.format - 1, document.default_layout)
199 def rename_spaces(document):
200 """ \InsetSpace \, -> \InsetSpace \thinspace{}
201 \InsetSpace \space -> \InsetSpace \space{}"""
202 for i in range(len(document.body)):
203 document.body[i] = document.body[i].replace("\\InsetSpace \\space",
204 "\\InsetSpace \\space{}")
205 document.body[i] = document.body[i].replace("\\InsetSpace \,",
206 "\\InsetSpace \\thinspace{}")
209 def revert_space_names(document):
210 """ \InsetSpace \thinspace{} -> \InsetSpace \,
211 \InsetSpace \space{} -> \InsetSpace \space"""
212 for i in range(len(document.body)):
213 document.body[i] = document.body[i].replace("\\InsetSpace \\space{}",
214 "\\InsetSpace \\space")
215 document.body[i] = document.body[i].replace("\\InsetSpace \\thinspace{}",
219 def lyx_support_escape(lab):
220 " Equivalent to pre-unicode lyx::support::escape()"
221 hexdigit = ['0', '1', '2', '3', '4', '5', '6', '7',
222 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
226 if o >= 128 or c == '=' or c == '%':
228 enc = enc + hexdigit[o >> 4]
229 enc = enc + hexdigit[o & 15]
235 def revert_eqref(document):
236 "\\begin_inset LatexCommand \\eqref -> ERT"
237 regexp = re.compile(r'^\\begin_inset\s+LatexCommand\s+\\eqref')
240 i = find_re(document.body, regexp, i)
243 eqref = lyx_support_escape(regexp.sub("", document.body[i]))
244 document.body[i:i+1] = ["\\begin_inset ERT", "status Collapsed", "",
245 '\\layout %s' % document.default_layout, "", "\\backslash ",
250 def convert_bibtex(document):
251 " Convert BibTeX changes."
252 for i in range(len(document.body)):
253 document.body[i] = document.body[i].replace("\\begin_inset LatexCommand \\BibTeX",
254 "\\begin_inset LatexCommand \\bibtex")
257 def revert_bibtex(document):
258 " Revert BibTeX changes."
259 for i in range(len(document.body)):
260 document.body[i] = document.body[i].replace("\\begin_inset LatexCommand \\bibtex",
261 "\\begin_inset LatexCommand \\BibTeX")
264 def remove_insetparent(document):
268 i = find_token(document.body, "\\begin_inset LatexCommand \\lyxparent", i)
271 del document.body[i:i+3]
274 def convert_external(document):
275 " Convert inset External."
276 external_rexp = re.compile(r'\\begin_inset External ([^,]*),"([^"]*)",')
277 external_header = "\\begin_inset External"
280 i = find_token(document.body, external_header, i)
283 look = external_rexp.search(document.body[i])
286 args[0] = look.group(1)
287 args[1] = look.group(2)
288 #FIXME: if the previous search fails then warn
290 if args[0] == "RasterImage":
291 # Convert a RasterImage External Inset to a Graphics Inset.
292 top = "\\begin_inset Graphics"
294 filename = "\tfilename " + args[1]
295 document.body[i:i+1] = [top, filename]
298 # Convert the old External Inset format to the new.
299 top = external_header
300 template = "\ttemplate " + args[0]
302 filename = "\tfilename " + args[1]
303 document.body[i:i+1] = [top, template, filename]
306 document.body[i:i+1] = [top, template]
310 def revert_external_1(document):
311 " Revert inset External."
312 external_header = "\\begin_inset External"
315 i = find_token(document.body, external_header, i)
319 template = document.body[i+1].split()
321 del document.body[i+1]
323 filename = document.body[i+1].split()
325 del document.body[i+1]
327 params = document.body[i+1].split()
329 if document.body[i+1]: del document.body[i+1]
331 document.body[i] = document.body[i] + " " + template[0]+ ', "' + filename[0] + '", " '+ " ".join(params[1:]) + '"'
335 def revert_external_2(document):
336 " Revert inset External. (part II)"
337 draft_token = '\tdraft'
340 i = find_token(document.body, '\\begin_inset External', i)
343 j = find_end_of_inset(document.body, i + 1)
345 #this should not happen
347 k = find_token(document.body, draft_token, i+1, j-1)
348 if (k != -1 and len(draft_token) == len(document.body[k])):
353 def convert_comment(document):
354 " Convert \\layout comment"
356 comment = "\\layout Comment"
358 i = find_token(document.body, comment, i)
362 document.body[i:i+1] = ['\\layout %s' % document.default_layout,"","",
363 "\\begin_inset Comment",
365 '\\layout %s' % document.default_layout]
370 i = find_token(document.body, "\\layout", i)
372 i = len(document.body) - 1
373 document.body[i:i] = ["\\end_inset","",""]
376 j = find_token(document.body, '\\begin_deeper', old_i, i)
377 if j == -1: j = i + 1
378 k = find_token(document.body, '\\begin_inset', old_i, i)
379 if k == -1: k = i + 1
384 i = find_end_of( document.body, i, "\\begin_deeper","\\end_deeper")
386 #This case should not happen
387 #but if this happens deal with it greacefully adding
388 #the missing \end_deeper.
389 i = len(document.body) - 1
390 document.body[i:i] = ["\\end_deeper",""]
398 i = find_end_of( document.body, i, "\\begin_inset","\\end_inset")
400 #This case should not happen
401 #but if this happens deal with it greacefully adding
402 #the missing \end_inset.
403 i = len(document.body) - 1
404 document.body[i:i] = ["\\end_inset","","","\\end_inset","",""]
410 if document.body[i].find(comment) == -1:
411 document.body[i:i] = ["\\end_inset"]
414 document.body[i:i+1] = ['\\layout %s' % document.default_layout]
418 def revert_comment(document):
422 i = find_tokens(document.body, ["\\begin_inset Comment", "\\begin_inset Greyedout"], i)
426 document.body[i] = "\\begin_inset Note"
430 def add_end_layout(document):
432 i = find_token(document.body, '\\layout', 0)
438 struct_stack = ["\\layout"]
441 i = find_tokens(document.body, ["\\begin_inset", "\\end_inset", "\\layout",
442 "\\begin_deeper", "\\end_deeper", "\\the_end"], i)
445 token = document.body[i].split()[0]
447 document.warning("Truncated document.")
448 i = len(document.body)
449 document.body.insert(i, '\\the_end')
452 if token == "\\begin_inset":
453 struct_stack.append(token)
457 if token == "\\end_inset":
458 tail = struct_stack.pop()
459 if tail == "\\layout":
460 document.body.insert(i,"")
461 document.body.insert(i,"\\end_layout")
463 #Check if it is the correct tag
468 if token == "\\layout":
469 tail = struct_stack.pop()
471 document.body.insert(i,"")
472 document.body.insert(i,"\\end_layout")
475 struct_stack.append(tail)
477 struct_stack.append(token)
480 if token == "\\begin_deeper":
481 document.body.insert(i,"")
482 document.body.insert(i,"\\end_layout")
484 # consecutive begin_deeper only insert one end_layout
485 while document.body[i].startswith('\\begin_deeper'):
487 struct_stack.append(token)
490 if token == "\\end_deeper":
491 if struct_stack[-1] == '\\layout':
492 document.body.insert(i, '\\end_layout')
499 document.body.insert(i, "")
500 document.body.insert(i, "\\end_layout")
504 def rm_end_layout(document):
505 " Remove \end_layout"
508 i = find_token(document.body, '\\end_layout', i)
516 def insert_tracking_changes(document):
517 " Handle change tracking keywords."
518 i = find_token(document.header, "\\tracking_changes", 0)
520 document.header.append("\\tracking_changes 0")
523 def rm_tracking_changes(document):
524 " Remove change tracking keywords."
525 i = find_token(document.header, "\\author", 0)
527 del document.header[i]
529 i = find_token(document.header, "\\tracking_changes", 0)
532 del document.header[i]
535 def rm_body_changes(document):
536 " Remove body changes."
539 i = find_token(document.body, "\\change_", i)
546 def layout2begin_layout(document):
547 " \layout -> \begin_layout "
550 i = find_token(document.body, '\\layout', i)
554 document.body[i] = document.body[i].replace('\\layout', '\\begin_layout')
558 def begin_layout2layout(document):
559 " \begin_layout -> \layout "
562 i = find_token(document.body, '\\begin_layout', i)
566 document.body[i] = document.body[i].replace('\\begin_layout', '\\layout')
570 def convert_valignment_middle(body, start, end):
571 'valignment="center" -> valignment="middle"'
572 for i in range(start, end):
573 if re.search('^<(column|cell) .*valignment="center".*>$', body[i]):
574 body[i] = body[i].replace('valignment="center"', 'valignment="middle"')
577 def convert_table_valignment_middle(document):
578 " Convert table valignment, center -> middle"
579 regexp = re.compile(r'^\\begin_inset\s+Tabular')
582 i = find_re(document.body, regexp, i)
585 j = find_end_of_inset(document.body, i + 1)
587 #this should not happen
588 convert_valignment_middle(document.body, i + 1, len(document.body))
590 convert_valignment_middle(document.body, i + 1, j)
594 def revert_table_valignment_middle(body, start, end):
595 " valignment, middle -> center"
596 for i in range(start, end):
597 if re.search('^<(column|cell) .*valignment="middle".*>$', body[i]):
598 body[i] = body[i].replace('valignment="middle"', 'valignment="center"')
601 def revert_valignment_middle(document):
602 " Convert table valignment, middle -> center"
603 regexp = re.compile(r'^\\begin_inset\s+Tabular')
606 i = find_re(document.body, regexp, i)
609 j = find_end_of_inset(document.body, i + 1)
611 #this should not happen
612 revert_table_valignment_middle(document.body, i + 1, len(document.body))
614 revert_table_valignment_middle(document.body, i + 1, j)
618 def convert_end_document(document):
619 "\\the_end -> \\end_document"
620 i = find_token(document.body, "\\the_end", 0)
622 document.body.append("\\end_document")
624 document.body[i] = "\\end_document"
627 def revert_end_document(document):
628 "\\end_document -> \\the_end"
629 i = find_token(document.body, "\\end_document", 0)
631 document.body.append("\\the_end")
633 document.body[i] = "\\the_end"
636 def convert_breaks(document):
638 Convert line and page breaks
641 \line_top \line_bottom \pagebreak_top \pagebreak_bottom \added_space_top xxx \added_space_bottom yyy
645 \begin layout Standard
651 \begin layout Standard
658 \begin_inset VSpace xxx
663 \begin_inset VSpace xxx
671 par_params = ('added_space_bottom', 'added_space_top', 'align',
672 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
673 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
675 font_attributes = ['\\family', '\\series', '\\shape', '\\emph',
676 '\\numeric', '\\bar', '\\noun', '\\color', '\\lang']
677 attribute_values = ['default', 'default', 'default', 'default',
678 'default', 'default', 'default', 'none', document.language]
681 i = find_token(document.body, "\\begin_layout", i)
684 layout = get_layout(document.body[i], document.default_layout)
687 # Merge all paragraph parameters into a single line
688 # We cannot check for '\\' only because paragraphs may start e.g.
690 while document.body[i + 1][:1] == '\\' and document.body[i + 1][1:].split()[0] in par_params:
691 document.body[i] = document.body[i + 1] + ' ' + document.body[i]
692 del document.body[i+1]
694 line_top = document.body[i].find("\\line_top")
695 line_bot = document.body[i].find("\\line_bottom")
696 pb_top = document.body[i].find("\\pagebreak_top")
697 pb_bot = document.body[i].find("\\pagebreak_bottom")
698 vspace_top = document.body[i].find("\\added_space_top")
699 vspace_bot = document.body[i].find("\\added_space_bottom")
701 if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1:
704 # Do we have a nonstandard paragraph? We need to create new paragraphs
705 # if yes to avoid putting lyxline etc. inside of special environments.
706 # This is wrong for itemize and enumerate environments, but it is
707 # impossible to convert these correctly.
708 # We want to avoid new paragraphs if possible becauase we want to
709 # inherit font sizes.
711 if (not document.is_default_layout(layout) or
712 document.body[i].find("\\align") != -1 or
713 document.body[i].find("\\labelwidthstring") != -1 or
714 document.body[i].find("\\noindent") != -1):
717 # get the font size of the beginning of this paragraph, since we need
718 # it for the lyxline inset
720 while not is_nonempty_line(document.body[j]):
723 if document.body[j].find("\\size") != -1:
724 size_top = document.body[j].split()[1]
726 for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom":
727 document.body[i] = document.body[i].replace(tag, "")
730 # the position could be change because of the removal of other
731 # paragraph properties above
732 vspace_top = document.body[i].find("\\added_space_top")
733 tmp_list = document.body[i][vspace_top:].split()
734 vspace_top_value = tmp_list[1]
735 document.body[i] = document.body[i][:vspace_top] + " ".join(tmp_list[2:])
738 # the position could be change because of the removal of other
739 # paragraph properties above
740 vspace_bot = document.body[i].find("\\added_space_bottom")
741 tmp_list = document.body[i][vspace_bot:].split()
742 vspace_bot_value = tmp_list[1]
743 document.body[i] = document.body[i][:vspace_bot] + " ".join(tmp_list[2:])
745 document.body[i] = document.body[i].strip()
748 # Create an empty paragraph or paragraph fragment for line and
749 # page break that belong above the paragraph
750 if pb_top !=-1 or line_top != -1 or vspace_top != -1:
752 paragraph_above = list()
754 # We need to create an extra paragraph for nonstandard environments
755 paragraph_above = ['\\begin_layout %s' % document.default_layout, '']
758 paragraph_above.extend(['\\newpage ',''])
761 paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','',''])
765 paragraph_above.extend(['\\size ' + size_top + ' '])
766 # We need an additional vertical space of -\parskip.
767 # We can't use the vspace inset because it does not know \parskip.
768 paragraph_above.extend(['\\lyxline ', '', ''])
769 insert_ert(paragraph_above, len(paragraph_above) - 1, 'Collapsed',
770 '\\vspace{-1\\parskip}\n', document.format + 1, document.default_layout)
771 paragraph_above.extend([''])
774 paragraph_above.extend(['\\end_layout ',''])
775 # insert new paragraph above the current paragraph
776 document.body[i-2:i-2] = paragraph_above
778 # insert new lines at the beginning of the current paragraph
779 document.body[i:i] = paragraph_above
781 i = i + len(paragraph_above)
783 # Ensure that nested style are converted later.
784 k = find_end_of(document.body, i, "\\begin_layout", "\\end_layout")
789 if pb_bot !=-1 or line_bot != -1 or vspace_bot != -1:
791 # get the font size of the end of this paragraph
795 if document.body[j].find("\\size") != -1:
796 size_bot = document.body[j].split()[1]
798 elif document.body[j].find("\\begin_inset") != -1:
800 j = find_end_of_inset(document.body, j)
804 paragraph_below = list()
806 # We need to create an extra paragraph for nonstandard environments
807 paragraph_below = ['', '\\begin_layout %s' % document.default_layout, '']
809 for a in range(len(font_attributes)):
810 if find_token(document.body, font_attributes[a], i, k) != -1:
811 paragraph_below.extend([font_attributes[a] + ' ' + attribute_values[a]])
814 if nonstandard and size_bot != '':
815 paragraph_below.extend(['\\size ' + size_bot + ' '])
816 paragraph_below.extend(['\\lyxline ',''])
818 paragraph_below.extend(['\\size default '])
821 paragraph_below.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','',''])
824 paragraph_below.extend(['\\newpage ',''])
827 paragraph_below.extend(['\\end_layout '])
828 # insert new paragraph below the current paragraph
829 document.body[k+1:k+1] = paragraph_below
831 # insert new lines at the end of the current paragraph
832 document.body[k:k] = paragraph_below
835 def convert_note(document):
839 i = find_tokens(document.body, ["\\begin_inset Note",
840 "\\begin_inset Comment",
841 "\\begin_inset Greyedout"], i)
845 document.body[i] = document.body[i][0:13] + 'Note ' + document.body[i][13:]
849 def revert_note(document):
851 note_header = "\\begin_inset Note "
854 i = find_token(document.body, note_header, i)
858 document.body[i] = "\\begin_inset " + document.body[i][len(note_header):]
862 def convert_box(document):
866 i = find_tokens(document.body, ["\\begin_inset Boxed",
867 "\\begin_inset Doublebox",
868 "\\begin_inset Frameless",
869 "\\begin_inset ovalbox",
870 "\\begin_inset Ovalbox",
871 "\\begin_inset Shadowbox"], i)
875 document.body[i] = document.body[i][0:13] + 'Box ' + document.body[i][13:]
879 def revert_box(document):
881 box_header = "\\begin_inset Box "
884 i = find_token(document.body, box_header, i)
888 document.body[i] = "\\begin_inset " + document.body[i][len(box_header):]
892 def convert_collapsable(document):
893 " Convert collapsed insets. "
896 i = find_tokens_exact(document.body, ["\\begin_inset Box",
897 "\\begin_inset Branch",
898 "\\begin_inset CharStyle",
899 "\\begin_inset Float",
900 "\\begin_inset Foot",
901 "\\begin_inset Marginal",
902 "\\begin_inset Note",
903 "\\begin_inset OptArg",
904 "\\begin_inset Wrap"], i)
908 # Seach for a line starting 'collapsed'
909 # If, however, we find a line starting '\begin_layout'
910 # (_always_ present) then break with a warning message
913 if (document.body[i] == "collapsed false"):
914 document.body[i] = "status open"
916 elif (document.body[i] == "collapsed true"):
917 document.body[i] = "status collapsed"
919 elif (document.body[i][:13] == "\\begin_layout"):
920 document.warning("Malformed LyX document: Missing 'collapsed'.")
927 def revert_collapsable(document):
928 " Revert collapsed insets. "
931 i = find_tokens_exact(document.body, ["\\begin_inset Box",
932 "\\begin_inset Branch",
933 "\\begin_inset CharStyle",
934 "\\begin_inset Float",
935 "\\begin_inset Foot",
936 "\\begin_inset Marginal",
937 "\\begin_inset Note",
938 "\\begin_inset OptArg",
939 "\\begin_inset Wrap"], i)
943 # Seach for a line starting 'status'
944 # If, however, we find a line starting '\begin_layout'
945 # (_always_ present) then break with a warning message
948 if (document.body[i] == "status open"):
949 document.body[i] = "collapsed false"
951 elif (document.body[i] == "status collapsed" or
952 document.body[i] == "status inlined"):
953 document.body[i] = "collapsed true"
955 elif (document.body[i][:13] == "\\begin_layout"):
956 document.warning("Malformed LyX document: Missing 'status'.")
963 def convert_ert(document):
967 i = find_token(document.body, "\\begin_inset ERT", i)
971 # Seach for a line starting 'status'
972 # If, however, we find a line starting '\begin_layout'
973 # (_always_ present) then break with a warning message
976 if (document.body[i] == "status Open"):
977 document.body[i] = "status open"
979 elif (document.body[i] == "status Collapsed"):
980 document.body[i] = "status collapsed"
982 elif (document.body[i] == "status Inlined"):
983 document.body[i] = "status inlined"
985 elif (document.body[i][:13] == "\\begin_layout"):
986 document.warning("Malformed LyX document: Missing 'status'.")
993 def revert_ert(document):
997 i = find_token(document.body, "\\begin_inset ERT", i)
1001 # Seach for a line starting 'status'
1002 # If, however, we find a line starting '\begin_layout'
1003 # (_always_ present) then break with a warning message
1006 if (document.body[i] == "status open"):
1007 document.body[i] = "status Open"
1009 elif (document.body[i] == "status collapsed"):
1010 document.body[i] = "status Collapsed"
1012 elif (document.body[i] == "status inlined"):
1013 document.body[i] = "status Inlined"
1015 elif (document.body[i][:13] == "\\begin_layout"):
1016 document.warning("Malformed LyX document : Missing 'status'.")
1023 def convert_minipage(document):
1024 """ Convert minipages to the box inset.
1025 We try to use the same order of arguments as lyx does.
1028 inner_pos = ["c","t","b","s"]
1032 i = find_token(document.body, "\\begin_inset Minipage", i)
1036 document.body[i] = "\\begin_inset Box Frameless"
1039 # convert old to new position using the pos list
1040 if document.body[i][:8] == "position":
1041 document.body[i] = 'position "%s"' % pos[int(document.body[i][9])]
1043 document.body.insert(i, 'position "%s"' % pos[0])
1046 document.body.insert(i, 'hor_pos "c"')
1048 document.body.insert(i, 'has_inner_box 1')
1051 # convert the inner_position
1052 if document.body[i][:14] == "inner_position":
1053 innerpos = inner_pos[int(document.body[i][15])]
1054 del document.body[i]
1056 innerpos = inner_pos[0]
1058 # We need this since the new file format has a height and width
1059 # in a different order.
1060 if document.body[i][:6] == "height":
1061 height = document.body[i][6:]
1062 # test for default value of 221 and convert it accordingly
1063 if height == ' "0pt"' or height == ' "0"':
1065 del document.body[i]
1069 if document.body[i][:5] == "width":
1070 width = document.body[i][5:]
1071 del document.body[i]
1075 if document.body[i][:9] == "collapsed":
1076 if document.body[i][9:] == "true":
1077 status = "collapsed"
1080 del document.body[i]
1082 status = "collapsed"
1084 # Handle special default case:
1085 if height == ' "1pt"' and innerpos == 'c':
1088 document.body.insert(i, 'inner_pos "' + innerpos + '"')
1090 document.body.insert(i, 'use_parbox 0')
1092 document.body.insert(i, 'width' + width)
1094 document.body.insert(i, 'special "none"')
1096 document.body.insert(i, 'height' + height)
1098 document.body.insert(i, 'height_special "totalheight"')
1100 document.body.insert(i, 'status ' + status)
1104 def convert_ertbackslash(body, i, ert, format, default_layout):
1105 r""" -------------------------------------------------------------------------------------------
1106 Convert backslashes and '\n' into valid ERT code, append the converted
1107 text to body[i] and return the (maybe incremented) line index i"""
1111 body[i] = body[i] + '\\backslash '
1116 body[i+1:i+1] = ['\\newline ', '']
1119 body[i+1:i+1] = ['\\end_layout', '', '\\begin_layout %s' % default_layout, '']
1122 body[i] = body[i] + c
1126 def ert2latex(lines, format):
1127 r""" Converts lines in ERT code to LaTeX
1128 The surrounding \begin_layout ... \end_layout pair must not be included"""
1130 backslash = re.compile(r'\\backslash\s*$')
1131 newline = re.compile(r'\\newline\s*$')
1133 begin_layout = re.compile(r'\\layout\s*\S+$')
1135 begin_layout = re.compile(r'\\begin_layout\s*\S+$')
1136 end_layout = re.compile(r'\\end_layout\s*$')
1138 for i in range(len(lines)):
1139 line = backslash.sub('\\\\', lines[i])
1141 if begin_layout.match(line):
1144 line = newline.sub('\n', line)
1146 if begin_layout.match(line):
1148 if format > 224 and end_layout.match(line):
1154 def get_par_params(lines, i):
1155 """ get all paragraph parameters. They can be all on one line or on several lines.
1156 lines[i] must be the first parameter line"""
1157 par_params = ('added_space_bottom', 'added_space_top', 'align',
1158 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
1159 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
1160 'start_of_appendix')
1161 # We cannot check for '\\' only because paragraphs may start e.g.
1162 # with '\\backslash'
1164 while lines[i][:1] == '\\' and lines[i][1:].split()[0] in par_params:
1165 params = params + ' ' + lines[i].strip()
1167 return params.strip()
1170 def lyxsize2latexsize(lyxsize):
1171 " Convert LyX font size to LaTeX fontsize. "
1172 sizes = {"tiny" : "tiny", "scriptsize" : "scriptsize",
1173 "footnotesize" : "footnotesize", "small" : "small",
1174 "normal" : "normalsize", "large" : "large", "larger" : "Large",
1175 "largest" : "LARGE", "huge" : "huge", "giant" : "Huge"}
1176 if lyxsize in sizes:
1177 return '\\' + sizes[lyxsize]
1181 def revert_breaks(document):
1182 """ Change vspace insets, page breaks and lyxlines to paragraph options
1183 (if possible) or ERT"""
1185 # Get default spaceamount
1186 i = find_token(document.header, '\\defskip', 0)
1188 defskipamount = 'medskip'
1190 defskipamount = document.header[i].split()[1]
1192 keys = {"\\begin_inset" : "vspace", "\\lyxline" : "lyxline",
1193 "\\newpage" : "newpage"}
1194 keywords_top = {"vspace" : "\\added_space_top", "lyxline" : "\\line_top",
1195 "newpage" : "\\pagebreak_top"}
1196 keywords_bot = {"vspace" : "\\added_space_bottom", "lyxline" : "\\line_bottom",
1197 "newpage" : "\\pagebreak_bottom"}
1198 tokens = ["\\begin_inset VSpace", "\\lyxline", "\\newpage"]
1200 # Convert the insets
1203 i = find_tokens(document.body, tokens, i)
1207 # Are we at the beginning of a paragraph?
1209 this_par = get_paragraph(document.body, i, document.format - 1)
1210 start = this_par + 1
1211 params = get_par_params(document.body, start)
1213 # Paragraph parameters may be on one or more lines.
1214 # Find the start of the real paragraph text.
1215 while document.body[start][:1] == '\\' and document.body[start].split()[0] in params:
1217 for k in range(start, i):
1218 if document.body[k].find("\\size") != -1:
1220 size = document.body[k].split()[1]
1221 elif is_nonempty_line(document.body[k]):
1224 # Find the end of the real paragraph text.
1225 next_par = get_next_paragraph(document.body, i, document.format - 1)
1227 document.warning("Malformed LyX document: Missing next paragraph.")
1231 # first line of our insets
1233 # last line of our insets
1234 inset_end = inset_start
1235 # Are we at the end of a paragraph?
1237 # start and end line numbers to delete if we convert this inset
1239 # is this inset a lyxline above a paragraph?
1241 # raw inset information
1243 # name of this inset
1245 # font size of this inset
1248 # Detect subsequent lyxline, vspace and pagebreak insets created by convert_breaks()
1252 if find_tokens(document.body, tokens, k) == k:
1254 lines.append(document.body[k].split())
1255 insets.append(keys[lines[n][0]])
1256 del_lines.append([k, k])
1261 elif document.body[k].find("\\size") != -1:
1263 size = document.body[k].split()[1]
1264 elif find_token(document.body, "\\begin_inset ERT", k) == k:
1265 ert_begin = find_token(document.body, "\\layout", k) + 1
1267 document.warning("Malformed LyX document: Missing '\\layout'.")
1269 ert_end = find_end_of_inset(document.body, k)
1271 document.warning("Malformed LyX document: Missing '\\end_inset'.")
1273 ert = ert2latex(document.body[ert_begin:ert_end], document.format - 1)
1274 if (n > 0 and insets[n - 1] == "lyxline" and
1275 ert == '\\vspace{-1\\parskip}\n'):
1276 # vspace ERT created by convert_breaks() for top lyxline
1278 del_lines[n - 1][1] = ert_end
1284 elif (n > 0 and insets[n - 1] == "vspace" and
1285 find_token(document.body, "\\end_inset", k) == k):
1286 # ignore end of vspace inset
1287 del_lines[n - 1][1] = k
1289 elif is_nonempty_line(document.body[k]):
1294 # Determine space amount for vspace insets
1295 spaceamount = list()
1298 if insets[k] == "vspace":
1299 spaceamount.append(lines[k][2])
1300 arguments.append(' ' + spaceamount[k] + ' ')
1302 spaceamount.append('')
1303 arguments.append(' ')
1305 # Can we convert to top paragraph parameters?
1307 if ((n == 3 and insets[0] == "newpage" and insets[1] == "vspace" and
1308 insets[2] == "lyxline" and top[2]) or
1310 ((insets[0] == "newpage" and insets[1] == "vspace") or
1311 (insets[0] == "newpage" and insets[1] == "lyxline" and top[1]) or
1312 (insets[0] == "vspace" and insets[1] == "lyxline" and top[1]))) or
1313 (n == 1 and insets[0] == "lyxline" and top[0])):
1314 # These insets have been created before a paragraph by
1318 # Can we convert to bottom paragraph parameters?
1320 if ((n == 3 and insets[0] == "lyxline" and not top[0] and
1321 insets[1] == "vspace" and insets[2] == "newpage") or
1323 ((insets[0] == "lyxline" and not top[0] and insets[1] == "vspace") or
1324 (insets[0] == "lyxline" and not top[0] and insets[1] == "newpage") or
1325 (insets[0] == "vspace" and insets[1] == "newpage"))) or
1326 (n == 1 and insets[0] == "lyxline" and not top[0])):
1327 # These insets have been created after a paragraph by
1331 if paragraph_start and paragraph_end:
1332 # We are in a paragraph of our own.
1333 # We must not delete this paragraph if it has parameters
1335 # First try to merge with the previous paragraph.
1336 # We try the previous paragraph first because we would
1337 # otherwise need ERT for two subsequent vspaces.
1338 prev_par = get_paragraph(document.body, this_par - 1, document.format - 1) + 1
1339 if prev_par > 0 and not before:
1340 prev_params = get_par_params(document.body, prev_par + 1)
1342 # determine font size
1343 prev_size = "normal"
1345 while document.body[k][:1] == '\\' and document.body[k].split()[0] in prev_params:
1348 if document.body[k].find("\\size") != -1:
1349 prev_size = document.body[k].split()[1]
1351 elif document.body[k].find("\\begin_inset") != -1:
1353 k = find_end_of_inset(document.body, k)
1354 elif is_nonempty_line(document.body[k]):
1358 if (keywords_bot[insets[k]] in prev_params or
1359 (insets[k] == "lyxline" and sizes[k] != prev_size)):
1364 document.body.insert(prev_par + 1,
1365 keywords_bot[insets[k]] + arguments[k])
1366 del document.body[this_par+n:next_par-1+n]
1369 # Then try next paragraph
1370 if next_par > 0 and not after:
1371 next_params = get_par_params(document.body, next_par + 1)
1373 while document.body[k][:1] == '\\' and document.body[k].split()[0] in next_params:
1375 # determine font size
1376 next_size = "normal"
1379 if document.body[k].find("\\size") != -1:
1380 next_size = document.body[k].split()[1]
1382 elif is_nonempty_line(document.body[k]):
1386 if (keywords_top[insets[k]] in next_params or
1387 (insets[k] == "lyxline" and sizes[k] != next_size)):
1392 document.body.insert(next_par + 1,
1393 keywords_top[insets[k]] + arguments[k])
1394 del document.body[this_par:next_par-1]
1397 elif paragraph_start or paragraph_end:
1398 # Convert to paragraph formatting if we are at the beginning or end
1399 # of a paragraph and the resulting paragraph would not be empty
1400 # The order is important: del and insert invalidate some indices
1402 keywords = keywords_top
1404 keywords = keywords_bot
1407 if keywords[insets[k]] in params:
1412 document.body.insert(this_par + 1,
1413 keywords[insets[k]] + arguments[k])
1414 for j in range(k, n):
1415 del_lines[j][0] = del_lines[j][0] + 1
1416 del_lines[j][1] = del_lines[j][1] + 1
1417 del document.body[del_lines[k][0]:del_lines[k][1]+1]
1418 deleted = del_lines[k][1] - del_lines[k][0] + 1
1419 for j in range(k + 1, n):
1420 del_lines[j][0] = del_lines[j][0] - deleted
1421 del_lines[j][1] = del_lines[j][1] - deleted
1425 # Convert the first inset to ERT.
1426 # The others are converted in the next loop runs (if they exist)
1427 if insets[0] == "vspace":
1428 document.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
1429 '\\layout %s' % document.default_layout, '', '\\backslash ']
1431 if spaceamount[0][-1] == '*':
1432 spaceamount[0] = spaceamount[0][:-1]
1437 # Replace defskip by the actual value
1438 if spaceamount[0] == 'defskip':
1439 spaceamount[0] = defskipamount
1441 # LaTeX does not know \\smallskip* etc
1443 if spaceamount[0] == 'smallskip':
1444 spaceamount[0] = '\\smallskipamount'
1445 elif spaceamount[0] == 'medskip':
1446 spaceamount[0] = '\\medskipamount'
1447 elif spaceamount[0] == 'bigskip':
1448 spaceamount[0] = '\\bigskipamount'
1449 elif spaceamount[0] == 'vfill':
1450 spaceamount[0] = '\\fill'
1452 # Finally output the LaTeX code
1453 if (spaceamount[0] == 'smallskip' or spaceamount[0] == 'medskip' or
1454 spaceamount[0] == 'bigskip' or spaceamount[0] == 'vfill'):
1455 document.body.insert(i, spaceamount[0] + '{}')
1458 document.body.insert(i, 'vspace*{')
1460 document.body.insert(i, 'vspace{')
1461 i = convert_ertbackslash(document.body, i, spaceamount[0], document.format - 1, document.default_layout)
1462 document.body[i] = document.body[i] + '}'
1464 elif insets[0] == "lyxline":
1465 document.body[i] = ''
1466 latexsize = lyxsize2latexsize(size)
1468 document.warning("Could not convert LyX fontsize '%s' to LaTeX font size." % size)
1469 latexsize = '\\normalsize'
1470 i = insert_ert(document.body, i, 'Collapsed',
1471 '\\lyxline{%s}' % latexsize,
1472 document.format - 1, document.default_layout)
1473 # We use \providecommand so that we don't get an error if native
1474 # lyxlines are used (LyX writes first its own preamble and then
1475 # the user specified one)
1476 add_to_preamble(document,
1477 ['% Commands inserted by lyx2lyx for lyxlines',
1478 '\\providecommand{\\lyxline}[1]{',
1479 ' {#1 \\vspace{1ex} \\hrule width \\columnwidth \\vspace{1ex}}'
1481 elif insets[0] == "newpage":
1482 document.body[i] = ''
1483 i = insert_ert(document.body, i, 'Collapsed', '\\newpage{}',
1484 document.format - 1, document.default_layout)
1487 # Convert a LyX length into a LaTeX length
1488 def convert_len(len, special):
1489 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
1490 "page%":"\\pagewidth", "line%":"\\linewidth",
1491 "theight%":"\\textheight", "pheight%":"\\pageheight"}
1493 # Convert special lengths
1494 if special != 'none':
1495 len = '%f\\' % len2value(len) + special
1497 # Convert LyX units to LaTeX units
1498 for unit in units.keys():
1499 if len.find(unit) != -1:
1500 len = '%f' % (len2value(len) / 100) + units[unit]
1506 def convert_ertlen(body, i, len, special, format, default_layout):
1507 """ Convert a LyX length into valid ERT code and append it to body[i]
1508 Return the (maybe incremented) line index i
1509 Convert backslashes and insert the converted length into body. """
1510 return convert_ertbackslash(body, i, convert_len(len, special), format, default_layout)
1514 " Return the value of len without the unit in numerical form. "
1515 result = re.search('([+-]?[0-9.]+)', len)
1517 return float(result.group(1))
1518 # No number means 1.0
1522 def insert_ert(body, i, status, text, format, default_layout):
1523 """ Convert text to ERT and insert it at body[i]
1524 Return the index of the line after the inserted ERT"""
1526 body[i:i] = ['\\begin_inset ERT', 'status ' + status, '']
1529 body[i:i] = ['\\layout %s' % default_layout, '']
1531 body[i:i] = ['\\begin_layout %s' % default_layout, '']
1532 i = i + 1 # i points now to the just created empty line
1533 i = convert_ertbackslash(body, i, text, format, default_layout) + 1
1535 body[i:i] = ['\\end_layout']
1537 body[i:i] = ['', '\\end_inset', '']
1542 def add_to_preamble(document, text):
1543 """ Add text to the preamble if it is not already there.
1544 Only the first line is checked!"""
1546 if find_token(document.preamble, text[0], 0) != -1:
1549 document.preamble.extend(text)
1552 def convert_frameless_box(document):
1553 " Convert frameless box."
1554 pos = ['t', 'c', 'b']
1555 inner_pos = ['c', 't', 'b', 's']
1558 i = find_token(document.body, '\\begin_inset Frameless', i)
1561 j = find_end_of_inset(document.body, i)
1563 document.warning("Malformed LyX document: Missing '\\end_inset'.")
1566 del document.body[i]
1570 params = {'position':0, 'hor_pos':'c', 'has_inner_box':'1',
1571 'inner_pos':1, 'use_parbox':'0', 'width':'100col%',
1572 'special':'none', 'height':'1in',
1573 'height_special':'totalheight', 'collapsed':'false'}
1574 for key in params.keys():
1575 value = get_value(document.body, key, i, j).replace('"', '')
1577 if key == 'position':
1578 # convert new to old position: 'position "t"' -> 0
1579 value = find_token(pos, value, 0)
1582 elif key == 'inner_pos':
1583 # convert inner position
1584 value = find_token(inner_pos, value, 0)
1589 j = del_token(document.body, key, i, j)
1592 # Convert to minipage or ERT?
1593 # Note that the inner_position and height parameters of a minipage
1594 # inset are ignored and not accessible for the user, although they
1595 # are present in the file format and correctly read in and written.
1596 # Therefore we convert to ERT if they do not have their LaTeX
1597 # defaults. These are:
1598 # - the value of "position" for "inner_pos"
1599 # - "\totalheight" for "height"
1600 if (params['use_parbox'] != '0' or
1601 params['has_inner_box'] != '1' or
1602 params['special'] != 'none' or
1603 params['height_special'] != 'totalheight' or
1604 len2value(params['height']) != 1.0):
1606 # Here we know that this box is not supported in file format 224.
1607 # Therefore we need to convert it to ERT. We can't simply convert
1608 # the beginning and end of the box to ERT, because the
1609 # box inset may contain layouts that are different from the
1610 # surrounding layout. After the conversion the contents of the
1611 # box inset is on the same level as the surrounding text, and
1612 # paragraph layouts and align parameters can get mixed up.
1614 # A possible solution for this problem:
1615 # Convert the box to a minipage and redefine the minipage
1616 # environment in ERT so that the original box is simulated.
1617 # For minipages we could do this in a way that the width and
1618 # position can still be set from LyX, but this did not work well.
1619 # This is not possible for parboxes either, so we convert the
1620 # original box to ERT, put the minipage inset inside the box
1621 # and redefine the minipage environment to be empty.
1623 # Commands that are independant of a particular box can go to
1625 # We need to define lyxtolyxrealminipage with 3 optional
1626 # arguments although LyX 1.3 uses only the first one.
1627 # Otherwise we will get LaTeX errors if this document is
1628 # converted to format 225 or above again (LyX 1.4 uses all
1629 # optional arguments).
1630 add_to_preamble(document,
1631 ['% Commands inserted by lyx2lyx for frameless boxes',
1632 '% Save the original minipage environment',
1633 '\\let\\lyxtolyxrealminipage\\minipage',
1634 '\\let\\endlyxtolyxrealminipage\\endminipage',
1635 '% Define an empty lyxtolyximinipage environment',
1636 '% with 3 optional arguments',
1637 '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1638 '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1639 ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1640 ' {\\end{lyxtolyxiiiminipage}}',
1641 '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1642 ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1643 ' {\\end{lyxtolyxiiminipage}}',
1644 '\\newenvironment{lyxtolyxminipage}[1][c]%',
1645 ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1646 ' {\\end{lyxtolyximinipage}}'])
1648 if params['use_parbox'] != '0':
1651 ert = '\\begin{lyxtolyxrealminipage}'
1653 # convert optional arguments only if not latex default
1654 if (pos[params['position']] != 'c' or
1655 inner_pos[params['inner_pos']] != pos[params['position']] or
1656 params['height_special'] != 'totalheight' or
1657 len2value(params['height']) != 1.0):
1658 ert = ert + '[' + pos[params['position']] + ']'
1659 if (inner_pos[params['inner_pos']] != pos[params['position']] or
1660 params['height_special'] != 'totalheight' or
1661 len2value(params['height']) != 1.0):
1662 ert = ert + '[' + convert_len(params['height'],
1663 params['height_special']) + ']'
1664 if inner_pos[params['inner_pos']] != pos[params['position']]:
1665 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1667 ert = ert + '{' + convert_len(params['width'],
1668 params['special']) + '}'
1670 if params['use_parbox'] != '0':
1672 ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1673 ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1676 i = insert_ert(document.body, i, 'Collapsed', ert, document.format - 1, document.default_layout)
1677 j = j + i - old_i - 1
1679 document.body[i:i] = ['\\begin_inset Minipage',
1680 'position %d' % params['position'],
1683 'width "' + params['width'] + '"',
1684 'collapsed ' + params['collapsed']]
1688 # Restore the original minipage environment since we may have
1689 # minipages inside this box.
1690 # Start a new paragraph because the following may be nonstandard
1691 document.body[i:i] = ['\\layout %s' % document.default_layout, '', '']
1694 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1695 ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1697 i = insert_ert(document.body, i, 'Collapsed', ert, document.format - 1, document.default_layout)
1698 j = j + i - old_i - 1
1700 # Redefine the minipage end before the inset end.
1701 # Start a new paragraph because the previous may be nonstandard
1702 document.body[j:j] = ['\\layout %s' % document.default_layout, '', '']
1704 ert = '\\let\\endminipage\\endlyxtolyxminipage'
1705 j = insert_ert(document.body, j, 'Collapsed', ert, document.format - 1, document.default_layout)
1707 document.body.insert(j, '')
1710 # LyX writes '%\n' after each box. Therefore we need to end our
1711 # ERT with '%\n', too, since this may swallow a following space.
1712 if params['use_parbox'] != '0':
1715 ert = '\\end{lyxtolyxrealminipage}%\n'
1716 j = insert_ert(document.body, j, 'Collapsed', ert, document.format - 1, document.default_layout)
1718 # We don't need to restore the original minipage after the inset
1719 # end because the scope of the redefinition is the original box.
1723 # Convert to minipage
1724 document.body[i:i] = ['\\begin_inset Minipage',
1725 'position %d' % params['position'],
1726 'inner_position %d' % params['inner_pos'],
1727 'height "' + params['height'] + '"',
1728 'width "' + params['width'] + '"',
1729 'collapsed ' + params['collapsed']]
1733 def remove_branches(document):
1734 " Remove branches. "
1737 i = find_token(document.header, "\\branch", i)
1740 document.warning("Removing branch %s." % document.header[i].split()[1])
1741 j = find_token(document.header, "\\end_branch", i)
1743 document.warning("Malformed LyX document: Missing '\\end_branch'.")
1745 del document.header[i:j+1]
1749 i = find_token(document.body, "\\begin_inset Branch", i)
1752 j = find_end_of_inset(document.body, i)
1754 document.warning("Malformed LyX document: Missing '\\end_inset'.")
1757 del document.body[i]
1758 del document.body[j - 1]
1759 # Seach for a line starting 'collapsed'
1760 # If, however, we find a line starting '\layout'
1761 # (_always_ present) then break with a warning message
1764 if (document.body[i][:9] == "collapsed"):
1765 del document.body[i]
1768 elif (document.body[i][:7] == "\\layout"):
1769 if collapsed_found == 0:
1770 document.warning("Malformed LyX document: Missing 'collapsed'.")
1771 # Delete this new paragraph, since it would not appear in
1772 # .tex output. This avoids also empty paragraphs.
1773 del document.body[i]
1778 def convert_jurabib(document):
1779 " Convert jurabib. "
1780 i = find_token(document.header, '\\use_numerical_citations', 0)
1782 document.warning("Malformed lyx document: Missing '\\use_numerical_citations'.")
1784 document.header.insert(i + 1, '\\use_jurabib 0')
1787 def revert_jurabib(document):
1789 i = find_token(document.header, '\\use_jurabib', 0)
1791 document.warning("Malformed lyx document: Missing '\\use_jurabib'.")
1793 if get_value(document.header, '\\use_jurabib', 0) != "0":
1794 document.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1795 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1797 del document.header[i]
1800 def convert_bibtopic(document):
1801 " Convert bibtopic. "
1802 i = find_token(document.header, '\\use_jurabib', 0)
1804 document.warning("Malformed lyx document: Missing '\\use_jurabib'.")
1806 document.header.insert(i + 1, '\\use_bibtopic 0')
1809 def revert_bibtopic(document):
1810 " Revert bibtopic. "
1811 i = find_token(document.header, '\\use_bibtopic', 0)
1813 document.warning("Malformed lyx document: Missing '\\use_bibtopic'.")
1815 if get_value(document.header, '\\use_bibtopic', 0) != "0":
1816 document.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1817 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1818 del document.header[i]
1821 def convert_float(document):
1822 " Convert sideway floats. "
1825 i = find_token_exact(document.body, '\\begin_inset Float', i)
1828 # Seach for a line starting 'wide'
1829 # If, however, we find a line starting '\begin_layout'
1830 # (_always_ present) then break with a warning message
1833 if (document.body[i][:4] == "wide"):
1834 document.body.insert(i + 1, 'sideways false')
1836 elif (document.body[i][:13] == "\\begin_layout"):
1837 document.warning("Malformed lyx document: Missing 'wide'.")
1843 def revert_float(document):
1844 " Revert sideways floats. "
1847 i = find_token_exact(document.body, '\\begin_inset Float', i)
1850 line = document.body[i]
1851 r = re.compile(r'\\begin_inset Float (.*)$')
1853 floattype = m.group(1)
1854 if floattype != "figure" and floattype != "table":
1857 j = find_end_of_inset(document.body, i)
1859 document.warning("Malformed lyx document: Missing '\\end_inset'.")
1862 if get_value(document.body, 'sideways', i, j) != "false":
1863 l = find_token(document.body, "\\begin_layout Standard", i + 1, j)
1865 document.warning("Malformed LyX document: Missing `\\begin_layout Standard' in Float inset.")
1867 document.body[j] = '\\layout Standard\n\\begin_inset ERT\nstatus Collapsed\n\n' \
1868 '\\layout Standard\n\n\n\\backslash\n' \
1869 'end{sideways' + floattype + '}\n\n\\end_inset\n'
1870 del document.body[i+1:l-1]
1871 document.body[i] = '\\begin_inset ERT\nstatus Collapsed\n\n' \
1872 '\\layout Standard\n\n\n\\backslash\n' \
1873 'begin{sideways' + floattype + '}\n\n\\end_inset\n\n'
1874 add_to_preamble(document,
1875 ['\\usepackage{rotfloat}\n'])
1878 del_token(document.body, 'sideways', i, j)
1882 def convert_graphics(document):
1883 """ Add extension to documentnames of insetgraphics if necessary.
1887 i = find_token(document.body, "\\begin_inset Graphics", i)
1891 j = find_token_exact(document.body, "documentname", i)
1895 filename = document.body[j].split()[1]
1896 absname = os.path.normpath(os.path.join(document.dir, filename))
1897 if document.input == stdin and not os.path.isabs(filename):
1898 # We don't know the directory and cannot check the document.
1899 # We could use a heuristic and take the current directory,
1900 # and we could try to find out if documentname has an extension,
1901 # but that would be just guesses and could be wrong.
1902 document.warning("""Warning: Cannot determine whether document
1904 needs an extension when reading from standard input.
1905 You may need to correct the document manually or run
1906 lyx2lyx again with the .lyx document as commandline argument.""" % filename)
1908 # This needs to be the same algorithm as in pre 233 insetgraphics
1909 if access(absname, F_OK):
1911 if access(absname + ".ps", F_OK):
1912 document.body[j] = document.body[j].replace(filename, filename + ".ps")
1914 if access(absname + ".eps", F_OK):
1915 document.body[j] = document.body[j].replace(filename, filename + ".eps")
1918 def convert_names(document):
1919 """ Convert in the docbook backend from firstname and surname style
1922 if document.backend != "docbook":
1928 i = find_token(document.body, "\\begin_layout Author", i)
1933 while document.body[i] == "":
1936 if document.body[i][:11] != "\\end_layout" or document.body[i+2][:13] != "\\begin_deeper":
1941 i = find_end_of( document.body, i+3, "\\begin_deeper","\\end_deeper")
1943 # something is really wrong, abort
1944 document.warning("Missing \\end_deeper, after style Author.")
1945 document.warning("Aborted attempt to parse FirstName and Surname.")
1947 firstname, surname = "", ""
1949 name = document.body[k:i]
1951 j = find_token(name, "\\begin_layout FirstName", 0)
1954 while(name[j] != "\\end_layout"):
1955 firstname = firstname + name[j]
1958 j = find_token(name, "\\begin_layout Surname", 0)
1961 while(name[j] != "\\end_layout"):
1962 surname = surname + name[j]
1966 del document.body[k+2:i+1]
1968 document.body[k-1:k-1] = ["", "",
1969 "\\begin_inset CharStyle Firstname",
1972 '\\begin_layout %s' % document.default_layout,
1980 "\\begin_inset CharStyle Surname",
1983 '\\begin_layout %s' % document.default_layout,
1992 def revert_names(document):
1993 """ Revert in the docbook backend from firstname and surname char style
1996 if document.backend != "docbook":
2000 def convert_cite_engine(document):
2001 r""" \use_natbib 1 \cite_engine <style>
2002 \use_numerical_citations 0 -> where <style> is one of
2003 \use_jurabib 0 "basic", "natbib_authoryear","""
2005 a = find_token(document.header, "\\use_natbib", 0)
2007 document.warning("Malformed lyx document: Missing '\\use_natbib'.")
2010 b = find_token(document.header, "\\use_numerical_citations", 0)
2011 if b == -1 or b != a+1:
2012 document.warning("Malformed lyx document: Missing '\\use_numerical_citations'.")
2015 c = find_token(document.header, "\\use_jurabib", 0)
2016 if c == -1 or c != b+1:
2017 document.warning("Malformed lyx document: Missing '\\use_jurabib'.")
2020 use_natbib = int(document.header[a].split()[1])
2021 use_numerical_citations = int(document.header[b].split()[1])
2022 use_jurabib = int(document.header[c].split()[1])
2024 cite_engine = "basic"
2026 if use_numerical_citations:
2027 cite_engine = "natbib_numerical"
2029 cite_engine = "natbib_authoryear"
2031 cite_engine = "jurabib"
2033 del document.header[a:c+1]
2034 document.header.insert(a, "\\cite_engine " + cite_engine)
2037 def revert_cite_engine(document):
2038 " Revert the cite engine. "
2039 i = find_token(document.header, "\\cite_engine", 0)
2041 document.warning("Malformed lyx document: Missing '\\cite_engine'.")
2044 cite_engine = document.header[i].split()[1]
2049 if cite_engine == "natbib_numerical":
2052 elif cite_engine == "natbib_authoryear":
2054 elif cite_engine == "jurabib":
2057 del document.header[i]
2058 document.header.insert(i, "\\use_jurabib " + use_jurabib)
2059 document.header.insert(i, "\\use_numerical_citations " + use_numerical)
2060 document.header.insert(i, "\\use_natbib " + use_natbib)
2063 def convert_paperpackage(document):
2064 " Convert paper package. "
2065 i = find_token(document.header, "\\paperpackage", 0)
2069 packages = {'default':'none','a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
2070 if len(document.header[i].split()) > 1:
2071 paperpackage = document.header[i].split()[1]
2072 document.header[i] = document.header[i].replace(paperpackage, packages[paperpackage])
2074 document.header[i] = document.header[i] + ' widemarginsa4'
2077 def revert_paperpackage(document):
2078 " Revert paper package. "
2079 i = find_token(document.header, "\\paperpackage", 0)
2083 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
2084 'widemarginsa4':'', 'default': 'default'}
2085 if len(document.header[i].split()) > 1:
2086 paperpackage = document.header[i].split()[1]
2088 paperpackage = 'default'
2089 document.header[i] = document.header[i].replace(paperpackage, packages[paperpackage])
2092 def convert_bullets(document):
2093 " Convert bullets. "
2096 i = find_token(document.header, "\\bullet", i)
2099 if document.header[i][:12] == '\\bulletLaTeX':
2100 document.header[i] = document.header[i] + ' ' + document.header[i+1].strip()
2103 document.header[i] = document.header[i] + ' ' + document.header[i+1].strip() +\
2104 ' ' + document.header[i+2].strip() + ' ' + document.header[i+3].strip()
2106 del document.header[i+1:i + n]
2110 def revert_bullets(document):
2114 i = find_token(document.header, "\\bullet", i)
2117 if document.header[i][:12] == '\\bulletLaTeX':
2118 n = document.header[i].find('"')
2120 document.warning("Malformed header.")
2123 document.header[i:i+1] = [document.header[i][:n-1],'\t' + document.header[i][n:], '\\end_bullet']
2126 frag = document.header[i].split()
2128 document.warning("Malformed header.")
2131 document.header[i:i+1] = [frag[0] + ' ' + frag[1],
2139 def add_begin_header(document):
2140 r" Add \begin_header and \begin_document. "
2141 i = find_token(document.header, '\\lyxformat', 0)
2142 document.header.insert(i+1, '\\begin_header')
2143 document.header.insert(i+1, '\\begin_document')
2146 def remove_begin_header(document):
2147 r" Remove \begin_header and \begin_document. "
2148 i = find_token(document.header, "\\begin_document", 0)
2150 del document.header[i]
2151 i = find_token(document.header, "\\begin_header", 0)
2153 del document.header[i]
2156 def add_begin_body(document):
2157 r" Add and \begin_document and \end_document"
2158 document.body.insert(0, '\\begin_body')
2159 document.body.insert(1, '')
2160 i = find_token(document.body, "\\end_document", 0)
2161 document.body.insert(i, '\\end_body')
2163 def remove_begin_body(document):
2164 r" Remove \begin_body and \end_body"
2165 i = find_token(document.body, "\\begin_body", 0)
2167 del document.body[i]
2168 if not document.body[i]:
2169 del document.body[i]
2170 i = find_token(document.body, "\\end_body", 0)
2172 del document.body[i]
2175 def normalize_papersize(document):
2176 r" Normalize \papersize"
2177 i = find_token(document.header, '\\papersize', 0)
2181 tmp = document.header[i].split()
2182 if tmp[1] == "Default":
2183 document.header[i] = '\\papersize default'
2185 if tmp[1] == "Custom":
2186 document.header[i] = '\\papersize custom'
2189 def denormalize_papersize(document):
2190 r" Revert \papersize"
2191 i = find_token(document.header, '\\papersize', 0)
2195 tmp = document.header[i].split()
2196 if tmp[1] == "custom":
2197 document.header[i] = '\\papersize Custom'
2200 def strip_end_space(document):
2201 " Strip spaces at end of command line. "
2202 for i in range(len(document.body)):
2203 if document.body[i][:1] == '\\':
2204 document.body[i] = document.body[i].strip()
2207 def use_x_boolean(document):
2208 r" Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes"
2209 bin2bool = {'0': 'false', '1': 'true'}
2210 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2211 i = find_token(document.header, use, 0)
2214 decompose = document.header[i].split()
2215 document.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
2218 def use_x_binary(document):
2219 r" Use digit values for \use_geometry, \use_bibtopic and \tracking_changes"
2220 bool2bin = {'false': '0', 'true': '1'}
2221 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2222 i = find_token(document.header, use, 0)
2225 decompose = document.header[i].split()
2226 document.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
2229 def normalize_paragraph_params(document):
2230 " Place all the paragraph parameters in their own line. "
2231 body = document.body
2233 allowed_parameters = '\\paragraph_spacing', '\\noindent', \
2234 '\\align', '\\labelwidthstring', "\\start_of_appendix", \
2239 i = find_token(document.body, '\\begin_layout', i)
2245 if body[i].strip() and body[i].split()[0] not in allowed_parameters:
2248 j = body[i].find('\\', 1)
2251 body[i:i+1] = [body[i][:j].strip(), body[i][j:]]
2256 def convert_output_changes (document):
2257 " Add output_changes parameter. "
2258 i = find_token(document.header, '\\tracking_changes', 0)
2260 document.warning("Malformed lyx document: Missing '\\tracking_changes'.")
2262 document.header.insert(i+1, '\\output_changes true')
2265 def revert_output_changes (document):
2266 " Remove output_changes parameter. "
2267 i = find_token(document.header, '\\output_changes', 0)
2270 del document.header[i]
2273 def convert_ert_paragraphs(document):
2274 " Convert paragraph breaks and sanitize paragraphs. "
2275 forbidden_settings = [
2276 # paragraph parameters
2277 '\\paragraph_spacing', '\\labelwidthstring',
2278 '\\start_of_appendix', '\\noindent',
2279 '\\leftindent', '\\align',
2281 '\\family', '\\series', '\\shape', '\\size',
2282 '\\emph', '\\numeric', '\\bar', '\\noun',
2283 '\\color', '\\lang']
2286 i = find_token(document.body, '\\begin_inset ERT', i)
2289 j = find_end_of_inset(document.body, i)
2291 document.warning("Malformed lyx document: Missing '\\end_inset'.")
2295 # convert non-standard paragraphs to standard
2298 k = find_token(document.body, "\\begin_layout", k, j)
2301 document.body[k] = '\\begin_layout %s' % document.default_layout
2304 # remove all paragraph parameters and font settings
2307 if (document.body[k].strip() and
2308 document.body[k].split()[0] in forbidden_settings):
2309 del document.body[k]
2314 # insert an empty paragraph before each paragraph but the first
2318 k = find_token(document.body, "\\begin_layout", k, j)
2325 document.body[k:k] = ['\\begin_layout %s' % document.default_layout, "",
2330 # convert \\newline to new paragraph
2333 k = find_token(document.body, "\\newline", k, j)
2336 document.body[k:k+1] = ["\\end_layout", "", '\\begin_layout %s' % document.default_layout]
2339 # We need an empty line if document.default_layout == ''
2340 if document.body[k] != '':
2341 document.body.insert(k, '')
2347 def revert_ert_paragraphs(document):
2348 " Remove double paragraph breaks. "
2351 i = find_token(document.body, '\\begin_inset ERT', i)
2354 j = find_end_of_inset(document.body, i)
2356 document.warning("Malformed lyx document: Missing '\\end_inset'.")
2360 # replace paragraph breaks with \newline
2363 k = find_token(document.body, "\\end_layout", k, j)
2364 l = find_token(document.body, "\\begin_layout", k, j)
2365 if k == -1 or l == -1:
2367 document.body[k:l+1] = ["\\newline"]
2371 # replace double \newlines with paragraph breaks
2374 k = find_token(document.body, "\\newline", k, j)
2378 while document.body[l] == "":
2380 if document.body[l].strip() and document.body[l].split()[0] == "\\newline":
2381 document.body[k:l+1] = ["\\end_layout", "",
2382 '\\begin_layout %s' % document.default_layout]
2385 # We need an empty line if document.default_layout == ''
2386 if document.body[l+1] != '':
2387 document.body.insert(l+1, '')
2395 def convert_french(document):
2396 " Convert frenchb. "
2397 regexp = re.compile(r'^\\language\s+frenchb')
2398 i = find_re(document.header, regexp, 0)
2400 document.header[i] = "\\language french"
2402 # Change language in the document body
2403 regexp = re.compile(r'^\\lang\s+frenchb')
2406 i = find_re(document.body, regexp, i)
2409 document.body[i] = "\\lang french"
2413 def remove_paperpackage(document):
2414 " Remove paper package. "
2415 i = find_token(document.header, '\\paperpackage', 0)
2420 paperpackage = document.header[i].split()[1]
2422 del document.header[i]
2424 if paperpackage not in ("a4", "a4wide", "widemarginsa4"):
2427 conv = {"a4":"\\usepackage{a4}","a4wide": "\\usepackage{a4wide}",
2428 "widemarginsa4": "\\usepackage[widemargins]{a4}"}
2429 # for compatibility we ensure it is the first entry in preamble
2430 document.preamble[0:0] = [conv[paperpackage]]
2432 i = find_token(document.header, '\\papersize', 0)
2434 document.header[i] = "\\papersize default"
2437 def remove_quotestimes(document):
2438 " Remove quotestimes. "
2439 i = find_token(document.header, '\\quotes_times', 0)
2442 del document.header[i]
2445 def convert_sgml_paragraphs(document):
2446 " Convert SGML paragraphs. "
2447 if document.backend != "docbook":
2452 i = find_token(document.body, "\\begin_layout SGML", i)
2457 document.body[i] = "\\begin_layout Standard"
2458 j = find_token(document.body, "\\end_layout", i)
2460 document.body[j+1:j+1] = ['','\\end_inset','','','\\end_layout']
2461 document.body[i+1:i+1] = ['\\begin_inset ERT','status inlined','','\\begin_layout Standard','']
2469 supported_versions = ["1.4.%d" % i for i in range(3)] + ["1.4"]
2470 convert = [[222, [insert_tracking_changes, add_end_header, convert_amsmath]],
2471 [223, [remove_color_default, convert_spaces, convert_bibtex, remove_insetparent]],
2472 [224, [convert_external, convert_comment]],
2473 [225, [add_end_layout, layout2begin_layout, convert_end_document,
2474 convert_table_valignment_middle, convert_breaks]],
2475 [226, [convert_note]],
2476 [227, [convert_box]],
2477 [228, [convert_collapsable, convert_ert]],
2478 [229, [convert_minipage]],
2479 [230, [convert_jurabib]],
2480 [231, [convert_float]],
2481 [232, [convert_bibtopic]],
2482 [233, [convert_graphics, convert_names]],
2483 [234, [convert_cite_engine]],
2484 [235, [convert_paperpackage]],
2485 [236, [convert_bullets, add_begin_header, add_begin_body,
2486 normalize_papersize, strip_end_space]],
2487 [237, [use_x_boolean]],
2488 [238, [update_latexaccents]],
2489 [239, [normalize_paragraph_params]],
2490 [240, [convert_output_changes]],
2491 [241, [convert_ert_paragraphs]],
2492 [242, [convert_french]],
2493 [243, [remove_paperpackage]],
2494 [244, [rename_spaces]],
2495 [245, [remove_quotestimes, convert_sgml_paragraphs]]]
2497 revert = [[244, []],
2498 [243, [revert_space_names]],
2501 [240, [revert_ert_paragraphs]],
2502 [239, [revert_output_changes]],
2505 [236, [use_x_binary]],
2506 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
2508 [234, [revert_paperpackage]],
2509 [233, [revert_cite_engine]],
2510 [232, [revert_names]],
2511 [231, [revert_bibtopic]],
2512 [230, [revert_float]],
2513 [229, [revert_jurabib]],
2515 [227, [revert_collapsable, revert_ert]],
2516 [226, [revert_box, revert_external_2]],
2517 [225, [revert_note]],
2518 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
2519 revert_valignment_middle, revert_breaks, convert_frameless_box,
2521 [223, [revert_external_2, revert_comment, revert_eqref]],
2522 [222, [revert_spaces, revert_bibtex]],
2523 [221, [revert_amsmath, rm_end_header, rm_tracking_changes, rm_body_changes]]]
2526 if __name__ == "__main__":