1 # This file is part of lyx2lyx
2 # Copyright (C) 2002 Dekel Tsur <dekel@lyx.org>
3 # Copyright (C) 2002-2004 José Matos <jamatos@lyx.org>
4 # Copyright (C) 2004-2005 Georg Baum <Georg.Baum@post.rwth-aachen.de>
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 """ Convert files to the file format generated by lyx 1.4"""
23 from os import access, F_OK
25 from parser_tools import check_token, find_token, \
26 get_value, is_nonempty_line, \
27 find_tokens, find_end_of, find_beginning_of, find_token_exact, find_tokens_exact, \
28 find_re, find_tokens_backwards
31 from lyx_0_12 import update_latexaccents
33 ####################################################################
34 # Private helper functions
36 def get_layout(line, default_layout):
37 " Get layout, if empty return the default layout."
44 def get_paragraph(lines, i, format):
45 "Finds the paragraph that contains line i."
48 begin_layout = "\\layout"
50 begin_layout = "\\begin_layout"
52 i = find_tokens_backwards(lines, ["\\end_inset", begin_layout], i)
54 if check_token(lines[i], begin_layout):
56 i = find_beginning_of_inset(lines, i)
60 def find_beginning_of_inset(lines, i):
61 " Find beginning of inset, where lines[i] is included."
62 return find_beginning_of(lines, i, "\\begin_inset", "\\end_inset")
65 def get_next_paragraph(lines, i, format):
66 "Finds the paragraph after the paragraph that contains line i."
69 tokens = ["\\begin_inset", "\\layout", "\\end_float", "\\the_end"]
71 tokens = ["\\begin_inset", "\\begin_layout", "\\end_float", "\\end_document"]
73 tokens = ["\\begin_inset", "\\begin_layout", "\\end_float", "\\end_body", "\\end_document"]
75 i = find_tokens(lines, tokens, i)
76 if not check_token(lines[i], "\\begin_inset"):
78 i = find_end_of_inset(lines, i)
82 def find_end_of_inset(lines, i):
83 r"Finds the matching \end_inset"
84 return find_end_of(lines, i, "\\begin_inset", "\\end_inset")
86 def del_token(lines, token, start, end):
87 """ del_token(lines, token, start, end) -> int
89 Find the lower line in lines where token is the first element and
92 Returns the number of lines remaining."""
94 k = find_token_exact(lines, token, start, end)
101 # End of helper functions
102 ####################################################################
104 def remove_color_default(document):
105 r" Remove \color default"
108 i = find_token(document.body, "\\color default", i)
111 document.body[i] = document.body[i].replace("\\color default",
115 def add_end_header(document):
117 document.header.append("\\end_header");
120 def rm_end_header(document):
121 r" Remove \end_header"
122 i = find_token(document.header, "\\end_header", 0)
125 del document.header[i]
128 def convert_amsmath(document):
129 " Convert \\use_amsmath"
130 i = find_token(document.header, "\\use_amsmath", 0)
132 document.warning("Malformed LyX document: Missing '\\use_amsmath'.")
134 tokens = document.header[i].split()
136 document.warning("Malformed LyX document: Could not parse line '%s'." % document.header[i])
139 use_amsmath = tokens[1]
140 # old: 0 == off, 1 == on
141 # new: 0 == off, 1 == auto, 2 == on
142 # translate off -> auto, since old format 'off' means auto in reality
143 if use_amsmath == '0':
144 document.header[i] = "\\use_amsmath 1"
146 document.header[i] = "\\use_amsmath 2"
149 def revert_amsmath(document):
150 " Revert \\use_amsmath"
151 i = find_token(document.header, "\\use_amsmath", 0)
153 document.warning("Malformed LyX document: Missing '\\use_amsmath'.")
155 tokens = document.header[i].split()
157 document.warning("Malformed LyX document: Could not parse line '%s'." % document.header[i])
160 use_amsmath = tokens[1]
161 # old: 0 == off, 1 == on
162 # new: 0 == off, 1 == auto, 2 == on
163 # translate auto -> off, since old format 'off' means auto in reality
164 if use_amsmath == '2':
165 document.header[i] = "\\use_amsmath 1"
167 document.header[i] = "\\use_amsmath 0"
170 def convert_spaces(document):
171 r" \SpecialChar ~ -> \InsetSpace ~"
172 for i in range(len(document.body)):
173 document.body[i] = document.body[i].replace("\\SpecialChar ~",
177 def revert_spaces(document):
178 r" \InsetSpace ~ -> \SpecialChar ~"
179 regexp = re.compile(r'(.*)(\\InsetSpace\s+)(\S+)')
182 i = find_re(document.body, regexp, i)
185 space = regexp.match(document.body[i]).group(3)
186 prepend = regexp.match(document.body[i]).group(1)
188 document.body[i] = regexp.sub(prepend + '\\SpecialChar ~', document.body[i])
191 document.body[i] = regexp.sub(prepend, document.body[i])
192 document.body[i+1:i+1] = ''
193 if space == "\\space":
195 i = insert_ert(document.body, i+1, 'Collapsed', space, document.format - 1, document.default_layout)
198 def rename_spaces(document):
199 """ \\InsetSpace \\, -> \\InsetSpace \thinspace{}
200 \\InsetSpace \\space -> \\InsetSpace \\space{}"""
201 for i in range(len(document.body)):
202 document.body[i] = document.body[i].replace("\\InsetSpace \\space",
203 "\\InsetSpace \\space{}")
204 document.body[i] = document.body[i].replace("\\InsetSpace \\,",
205 "\\InsetSpace \\thinspace{}")
208 def revert_space_names(document):
209 """ \\InsetSpace \thinspace{} -> \\InsetSpace \\,
210 \\InsetSpace \\space{} -> \\InsetSpace \\space"""
211 for i in range(len(document.body)):
212 document.body[i] = document.body[i].replace("\\InsetSpace \\space{}",
213 "\\InsetSpace \\space")
214 document.body[i] = document.body[i].replace("\\InsetSpace \\thinspace{}",
218 def lyx_support_escape(lab):
219 " Equivalent to pre-unicode lyx::support::escape()"
220 hexdigit = ['0', '1', '2', '3', '4', '5', '6', '7',
221 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
225 if o >= 128 or c == '=' or c == '%':
227 enc = enc + hexdigit[o >> 4]
228 enc = enc + hexdigit[o & 15]
234 def revert_eqref(document):
235 "\\begin_inset LatexCommand \\eqref -> ERT"
236 regexp = re.compile(r'^\\begin_inset\s+LatexCommand\s+\\eqref')
239 i = find_re(document.body, regexp, i)
242 eqref = lyx_support_escape(regexp.sub("", document.body[i]))
243 document.body[i:i+1] = ["\\begin_inset ERT", "status Collapsed", "",
244 '\\layout %s' % document.default_layout, "", "\\backslash ",
249 def convert_bibtex(document):
250 " Convert BibTeX changes."
251 for i in range(len(document.body)):
252 document.body[i] = document.body[i].replace("\\begin_inset LatexCommand \\BibTeX",
253 "\\begin_inset LatexCommand \\bibtex")
256 def revert_bibtex(document):
257 " Revert BibTeX changes."
258 for i in range(len(document.body)):
259 document.body[i] = document.body[i].replace("\\begin_inset LatexCommand \\bibtex",
260 "\\begin_inset LatexCommand \\BibTeX")
263 def remove_insetparent(document):
264 r" Remove \lyxparent"
267 i = find_token(document.body, "\\begin_inset LatexCommand \\lyxparent", i)
270 del document.body[i:i+3]
273 def convert_external(document):
274 " Convert inset External."
275 external_rexp = re.compile(r'\\begin_inset External ([^,]*),"([^"]*)",')
276 external_header = "\\begin_inset External"
279 i = find_token(document.body, external_header, i)
282 look = external_rexp.search(document.body[i])
285 args[0] = look.group(1)
286 args[1] = look.group(2)
287 #FIXME: if the previous search fails then warn
289 if args[0] == "RasterImage":
290 # Convert a RasterImage External Inset to a Graphics Inset.
291 top = "\\begin_inset Graphics"
293 filename = "\tfilename " + args[1]
294 document.body[i:i+1] = [top, filename]
297 # Convert the old External Inset format to the new.
298 top = external_header
299 template = "\ttemplate " + args[0]
301 filename = "\tfilename " + args[1]
302 document.body[i:i+1] = [top, template, filename]
305 document.body[i:i+1] = [top, template]
309 def revert_external_1(document):
310 " Revert inset External."
311 external_header = "\\begin_inset External"
314 i = find_token(document.body, external_header, i)
318 template = document.body[i+1].split()
320 del document.body[i+1]
322 filename = document.body[i+1].split()
324 del document.body[i+1]
326 params = document.body[i+1].split()
328 if document.body[i+1]: del document.body[i+1]
330 document.body[i] = document.body[i] + " " + template[0]+ ', "' + filename[0] + '", " '+ " ".join(params[1:]) + '"'
334 def revert_external_2(document):
335 " Revert inset External. (part II)"
336 draft_token = '\tdraft'
339 i = find_token(document.body, '\\begin_inset External', i)
342 j = find_end_of_inset(document.body, i + 1)
344 #this should not happen
346 k = find_token(document.body, draft_token, i+1, j-1)
347 if (k != -1 and len(draft_token) == len(document.body[k])):
352 def convert_comment(document):
353 " Convert \\layout comment"
355 comment = "\\layout Comment"
357 i = find_token(document.body, comment, i)
361 document.body[i:i+1] = ['\\layout %s' % document.default_layout,"","",
362 "\\begin_inset Comment",
364 '\\layout %s' % document.default_layout]
369 i = find_token(document.body, "\\layout", i)
371 i = len(document.body) - 1
372 document.body[i:i] = ["\\end_inset","",""]
375 j = find_token(document.body, '\\begin_deeper', old_i, i)
376 if j == -1: j = i + 1
377 k = find_token(document.body, '\\begin_inset', old_i, i)
378 if k == -1: k = i + 1
383 i = find_end_of( document.body, i, "\\begin_deeper","\\end_deeper")
385 #This case should not happen
386 #but if this happens deal with it greacefully adding
387 #the missing \end_deeper.
388 i = len(document.body) - 1
389 document.body[i:i] = ["\\end_deeper",""]
397 i = find_end_of( document.body, i, "\\begin_inset","\\end_inset")
399 #This case should not happen
400 #but if this happens deal with it greacefully adding
401 #the missing \end_inset.
402 i = len(document.body) - 1
403 document.body[i:i] = ["\\end_inset","","","\\end_inset","",""]
409 if document.body[i].find(comment) == -1:
410 document.body[i:i] = ["\\end_inset"]
413 document.body[i:i+1] = ['\\layout %s' % document.default_layout]
417 def revert_comment(document):
421 i = find_tokens(document.body, ["\\begin_inset Comment", "\\begin_inset Greyedout"], i)
425 document.body[i] = "\\begin_inset Note"
429 def add_end_layout(document):
431 i = find_token(document.body, '\\layout', 0)
437 struct_stack = ["\\layout"]
440 i = find_tokens(document.body, ["\\begin_inset", "\\end_inset", "\\layout",
441 "\\begin_deeper", "\\end_deeper", "\\the_end"], i)
444 token = document.body[i].split()[0]
446 document.warning("Truncated document.")
447 i = len(document.body)
448 document.body.insert(i, '\\the_end')
451 if token == "\\begin_inset":
452 struct_stack.append(token)
456 if token == "\\end_inset":
457 tail = struct_stack.pop()
458 if tail == "\\layout":
459 document.body.insert(i,"")
460 document.body.insert(i,"\\end_layout")
462 #Check if it is the correct tag
467 if token == "\\layout":
468 tail = struct_stack.pop()
470 document.body.insert(i,"")
471 document.body.insert(i,"\\end_layout")
474 struct_stack.append(tail)
476 struct_stack.append(token)
479 if token == "\\begin_deeper":
480 document.body.insert(i,"")
481 document.body.insert(i,"\\end_layout")
483 # consecutive begin_deeper only insert one end_layout
484 while document.body[i].startswith('\\begin_deeper'):
486 struct_stack.append(token)
489 if token == "\\end_deeper":
490 if struct_stack[-1] == '\\layout':
491 document.body.insert(i, '\\end_layout')
498 document.body.insert(i, "")
499 document.body.insert(i, "\\end_layout")
503 def rm_end_layout(document):
504 r" Remove \end_layout"
507 i = find_token(document.body, '\\end_layout', i)
515 def insert_tracking_changes(document):
516 " Handle change tracking keywords."
517 i = find_token(document.header, "\\tracking_changes", 0)
519 document.header.append("\\tracking_changes 0")
522 def rm_tracking_changes(document):
523 " Remove change tracking keywords."
524 i = find_token(document.header, "\\author", 0)
526 del document.header[i]
528 i = find_token(document.header, "\\tracking_changes", 0)
531 del document.header[i]
534 def rm_body_changes(document):
535 " Remove body changes."
538 i = find_token(document.body, "\\change_", i)
545 def layout2begin_layout(document):
546 r" \layout -> \begin_layout "
549 i = find_token(document.body, '\\layout', i)
553 document.body[i] = document.body[i].replace('\\layout', '\\begin_layout')
557 def begin_layout2layout(document):
558 r" \begin_layout -> \layout "
561 i = find_token(document.body, '\\begin_layout', i)
565 document.body[i] = document.body[i].replace('\\begin_layout', '\\layout')
569 def convert_valignment_middle(body, start, end):
570 'valignment="center" -> valignment="middle"'
571 for i in range(start, end):
572 if re.search('^<(column|cell) .*valignment="center".*>$', body[i]):
573 body[i] = body[i].replace('valignment="center"', 'valignment="middle"')
576 def convert_table_valignment_middle(document):
577 " Convert table valignment, center -> middle"
578 regexp = re.compile(r'^\\begin_inset\s+Tabular')
581 i = find_re(document.body, regexp, i)
584 j = find_end_of_inset(document.body, i + 1)
586 #this should not happen
587 convert_valignment_middle(document.body, i + 1, len(document.body))
589 convert_valignment_middle(document.body, i + 1, j)
593 def revert_table_valignment_middle(body, start, end):
594 " valignment, middle -> center"
595 for i in range(start, end):
596 if re.search('^<(column|cell) .*valignment="middle".*>$', body[i]):
597 body[i] = body[i].replace('valignment="middle"', 'valignment="center"')
600 def revert_valignment_middle(document):
601 " Convert table valignment, middle -> center"
602 regexp = re.compile(r'^\\begin_inset\s+Tabular')
605 i = find_re(document.body, regexp, i)
608 j = find_end_of_inset(document.body, i + 1)
610 #this should not happen
611 revert_table_valignment_middle(document.body, i + 1, len(document.body))
613 revert_table_valignment_middle(document.body, i + 1, j)
617 def convert_end_document(document):
618 "\\the_end -> \\end_document"
619 i = find_token(document.body, "\\the_end", 0)
621 document.body.append("\\end_document")
623 document.body[i] = "\\end_document"
626 def revert_end_document(document):
627 "\\end_document -> \\the_end"
628 i = find_token(document.body, "\\end_document", 0)
630 document.body.append("\\the_end")
632 document.body[i] = "\\the_end"
635 def convert_breaks(document):
637 Convert line and page breaks
640 \line_top \line_bottom \pagebreak_top \pagebreak_bottom \added_space_top xxx \added_space_bottom yyy
644 \begin layout Standard
650 \begin layout Standard
657 \begin_inset VSpace xxx
662 \begin_inset VSpace xxx
670 par_params = ('added_space_bottom', 'added_space_top', 'align',
671 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
672 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
674 font_attributes = ['\\family', '\\series', '\\shape', '\\emph',
675 '\\numeric', '\\bar', '\\noun', '\\color', '\\lang']
676 attribute_values = ['default', 'default', 'default', 'default',
677 'default', 'default', 'default', 'none', document.language]
680 i = find_token(document.body, "\\begin_layout", i)
683 layout = get_layout(document.body[i], document.default_layout)
686 # Merge all paragraph parameters into a single line
687 # We cannot check for '\\' only because paragraphs may start e.g.
689 while document.body[i + 1][:1] == '\\' and document.body[i + 1][1:].split()[0] in par_params:
690 document.body[i] = document.body[i + 1] + ' ' + document.body[i]
691 del document.body[i+1]
693 line_top = document.body[i].find("\\line_top")
694 line_bot = document.body[i].find("\\line_bottom")
695 pb_top = document.body[i].find("\\pagebreak_top")
696 pb_bot = document.body[i].find("\\pagebreak_bottom")
697 vspace_top = document.body[i].find("\\added_space_top")
698 vspace_bot = document.body[i].find("\\added_space_bottom")
700 if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1:
703 # Do we have a nonstandard paragraph? We need to create new paragraphs
704 # if yes to avoid putting lyxline etc. inside of special environments.
705 # This is wrong for itemize and enumerate environments, but it is
706 # impossible to convert these correctly.
707 # We want to avoid new paragraphs if possible becauase we want to
708 # inherit font sizes.
710 if (not document.is_default_layout(layout) or
711 document.body[i].find("\\align") != -1 or
712 document.body[i].find("\\labelwidthstring") != -1 or
713 document.body[i].find("\\noindent") != -1):
716 # get the font size of the beginning of this paragraph, since we need
717 # it for the lyxline inset
719 while not is_nonempty_line(document.body[j]):
722 if document.body[j].find("\\size") != -1:
723 size_top = document.body[j].split()[1]
725 for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom":
726 document.body[i] = document.body[i].replace(tag, "")
729 # the position could be change because of the removal of other
730 # paragraph properties above
731 vspace_top = document.body[i].find("\\added_space_top")
732 tmp_list = document.body[i][vspace_top:].split()
733 vspace_top_value = tmp_list[1]
734 document.body[i] = document.body[i][:vspace_top] + " ".join(tmp_list[2:])
737 # the position could be change because of the removal of other
738 # paragraph properties above
739 vspace_bot = document.body[i].find("\\added_space_bottom")
740 tmp_list = document.body[i][vspace_bot:].split()
741 vspace_bot_value = tmp_list[1]
742 document.body[i] = document.body[i][:vspace_bot] + " ".join(tmp_list[2:])
744 document.body[i] = document.body[i].strip()
747 # Create an empty paragraph or paragraph fragment for line and
748 # page break that belong above the paragraph
749 if pb_top !=-1 or line_top != -1 or vspace_top != -1:
751 paragraph_above = list()
753 # We need to create an extra paragraph for nonstandard environments
754 paragraph_above = ['\\begin_layout %s' % document.default_layout, '']
757 paragraph_above.extend(['\\newpage ',''])
760 paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','',''])
764 paragraph_above.extend(['\\size ' + size_top + ' '])
765 # We need an additional vertical space of -\parskip.
766 # We can't use the vspace inset because it does not know \parskip.
767 paragraph_above.extend(['\\lyxline ', '', ''])
768 insert_ert(paragraph_above, len(paragraph_above) - 1, 'Collapsed',
769 '\\vspace{-1\\parskip}\n', document.format + 1, document.default_layout)
770 paragraph_above.extend([''])
773 paragraph_above.extend(['\\end_layout ',''])
774 # insert new paragraph above the current paragraph
775 document.body[i-2:i-2] = paragraph_above
777 # insert new lines at the beginning of the current paragraph
778 document.body[i:i] = paragraph_above
780 i = i + len(paragraph_above)
782 # Ensure that nested style are converted later.
783 k = find_end_of(document.body, i, "\\begin_layout", "\\end_layout")
788 if pb_bot !=-1 or line_bot != -1 or vspace_bot != -1:
790 # get the font size of the end of this paragraph
794 if document.body[j].find("\\size") != -1:
795 size_bot = document.body[j].split()[1]
797 elif document.body[j].find("\\begin_inset") != -1:
799 j = find_end_of_inset(document.body, j)
803 paragraph_below = list()
805 # We need to create an extra paragraph for nonstandard environments
806 paragraph_below = ['', '\\begin_layout %s' % document.default_layout, '']
808 for a in range(len(font_attributes)):
809 if find_token(document.body, font_attributes[a], i, k) != -1:
810 paragraph_below.extend([font_attributes[a] + ' ' + attribute_values[a]])
813 if nonstandard and size_bot != '':
814 paragraph_below.extend(['\\size ' + size_bot + ' '])
815 paragraph_below.extend(['\\lyxline ',''])
817 paragraph_below.extend(['\\size default '])
820 paragraph_below.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','',''])
823 paragraph_below.extend(['\\newpage ',''])
826 paragraph_below.extend(['\\end_layout '])
827 # insert new paragraph below the current paragraph
828 document.body[k+1:k+1] = paragraph_below
830 # insert new lines at the end of the current paragraph
831 document.body[k:k] = paragraph_below
834 def convert_note(document):
838 i = find_tokens(document.body, ["\\begin_inset Note",
839 "\\begin_inset Comment",
840 "\\begin_inset Greyedout"], i)
844 document.body[i] = document.body[i][0:13] + 'Note ' + document.body[i][13:]
848 def revert_note(document):
850 note_header = "\\begin_inset Note "
853 i = find_token(document.body, note_header, i)
857 document.body[i] = "\\begin_inset " + document.body[i][len(note_header):]
861 def convert_box(document):
865 i = find_tokens(document.body, ["\\begin_inset Boxed",
866 "\\begin_inset Doublebox",
867 "\\begin_inset Frameless",
868 "\\begin_inset ovalbox",
869 "\\begin_inset Ovalbox",
870 "\\begin_inset Shadowbox"], i)
874 document.body[i] = document.body[i][0:13] + 'Box ' + document.body[i][13:]
878 def revert_box(document):
880 box_header = "\\begin_inset Box "
883 i = find_token(document.body, box_header, i)
887 document.body[i] = "\\begin_inset " + document.body[i][len(box_header):]
891 def convert_collapsible(document):
892 " Convert collapsed insets. "
895 i = find_tokens_exact(document.body, ["\\begin_inset Box",
896 "\\begin_inset Branch",
897 "\\begin_inset CharStyle",
898 "\\begin_inset Float",
899 "\\begin_inset Foot",
900 "\\begin_inset Marginal",
901 "\\begin_inset Note",
902 "\\begin_inset OptArg",
903 "\\begin_inset Wrap"], i)
907 # Seach for a line starting 'collapsed'
908 # If, however, we find a line starting '\begin_layout'
909 # (_always_ present) then break with a warning message
912 if (document.body[i] == "collapsed false"):
913 document.body[i] = "status open"
915 elif (document.body[i] == "collapsed true"):
916 document.body[i] = "status collapsed"
918 elif (document.body[i][:13] == "\\begin_layout"):
919 document.warning("Malformed LyX document: Missing 'collapsed'.")
926 def revert_collapsible(document):
927 " Revert collapsed insets. "
930 i = find_tokens_exact(document.body, ["\\begin_inset Box",
931 "\\begin_inset Branch",
932 "\\begin_inset CharStyle",
933 "\\begin_inset Float",
934 "\\begin_inset Foot",
935 "\\begin_inset Marginal",
936 "\\begin_inset Note",
937 "\\begin_inset OptArg",
938 "\\begin_inset Wrap"], i)
942 # Seach for a line starting 'status'
943 # If, however, we find a line starting '\begin_layout'
944 # (_always_ present) then break with a warning message
947 if (document.body[i] == "status open"):
948 document.body[i] = "collapsed false"
950 elif (document.body[i] == "status collapsed" or
951 document.body[i] == "status inlined"):
952 document.body[i] = "collapsed true"
954 elif (document.body[i][:13] == "\\begin_layout"):
955 document.warning("Malformed LyX document: Missing 'status'.")
962 def convert_ert(document):
966 i = find_token(document.body, "\\begin_inset ERT", i)
970 # Seach for a line starting 'status'
971 # If, however, we find a line starting '\begin_layout'
972 # (_always_ present) then break with a warning message
975 if (document.body[i] == "status Open"):
976 document.body[i] = "status open"
978 elif (document.body[i] == "status Collapsed"):
979 document.body[i] = "status collapsed"
981 elif (document.body[i] == "status Inlined"):
982 document.body[i] = "status inlined"
984 elif (document.body[i][:13] == "\\begin_layout"):
985 document.warning("Malformed LyX document: Missing 'status'.")
992 def revert_ert(document):
996 i = find_token(document.body, "\\begin_inset ERT", i)
1000 # Seach for a line starting 'status'
1001 # If, however, we find a line starting '\begin_layout'
1002 # (_always_ present) then break with a warning message
1005 if (document.body[i] == "status open"):
1006 document.body[i] = "status Open"
1008 elif (document.body[i] == "status collapsed"):
1009 document.body[i] = "status Collapsed"
1011 elif (document.body[i] == "status inlined"):
1012 document.body[i] = "status Inlined"
1014 elif (document.body[i][:13] == "\\begin_layout"):
1015 document.warning("Malformed LyX document : Missing 'status'.")
1022 def convert_minipage(document):
1023 """ Convert minipages to the box inset.
1024 We try to use the same order of arguments as lyx does.
1027 inner_pos = ["c","t","b","s"]
1031 i = find_token(document.body, "\\begin_inset Minipage", i)
1035 document.body[i] = "\\begin_inset Box Frameless"
1038 # convert old to new position using the pos list
1039 if document.body[i][:8] == "position":
1040 document.body[i] = 'position "%s"' % pos[int(document.body[i][9])]
1042 document.body.insert(i, 'position "%s"' % pos[0])
1045 document.body.insert(i, 'hor_pos "c"')
1047 document.body.insert(i, 'has_inner_box 1')
1050 # convert the inner_position
1051 if document.body[i][:14] == "inner_position":
1052 innerpos = inner_pos[int(document.body[i][15])]
1053 del document.body[i]
1055 innerpos = inner_pos[0]
1057 # We need this since the new file format has a height and width
1058 # in a different order.
1059 if document.body[i][:6] == "height":
1060 height = document.body[i][6:]
1061 # test for default value of 221 and convert it accordingly
1062 if height == ' "0pt"' or height == ' "0"':
1064 del document.body[i]
1068 if document.body[i][:5] == "width":
1069 width = document.body[i][5:]
1070 del document.body[i]
1074 if document.body[i][:9] == "collapsed":
1075 if document.body[i][9:] == "true":
1076 status = "collapsed"
1079 del document.body[i]
1081 status = "collapsed"
1083 # Handle special default case:
1084 if height == ' "1pt"' and innerpos == 'c':
1087 document.body.insert(i, 'inner_pos "' + innerpos + '"')
1089 document.body.insert(i, 'use_parbox 0')
1091 document.body.insert(i, 'width' + width)
1093 document.body.insert(i, 'special "none"')
1095 document.body.insert(i, 'height' + height)
1097 document.body.insert(i, 'height_special "totalheight"')
1099 document.body.insert(i, 'status ' + status)
1103 def convert_ertbackslash(body, i, ert, format, default_layout):
1104 r""" -------------------------------------------------------------------------------------------
1105 Convert backslashes and '\n' into valid ERT code, append the converted
1106 text to body[i] and return the (maybe incremented) line index i"""
1110 body[i] = body[i] + '\\backslash '
1115 body[i+1:i+1] = ['\\newline ', '']
1118 body[i+1:i+1] = ['\\end_layout', '', '\\begin_layout %s' % default_layout, '']
1121 body[i] = body[i] + c
1125 def ert2latex(lines, format):
1126 r""" Converts lines in ERT code to LaTeX
1127 The surrounding \begin_layout ... \end_layout pair must not be included"""
1129 backslash = re.compile(r'\\backslash\s*$')
1130 newline = re.compile(r'\\newline\s*$')
1132 begin_layout = re.compile(r'\\layout\s*\S+$')
1134 begin_layout = re.compile(r'\\begin_layout\s*\S+$')
1135 end_layout = re.compile(r'\\end_layout\s*$')
1137 for i in range(len(lines)):
1138 line = backslash.sub('\\\\', lines[i])
1140 if begin_layout.match(line):
1143 line = newline.sub('\n', line)
1145 if begin_layout.match(line):
1147 if format > 224 and end_layout.match(line):
1153 def get_par_params(lines, i):
1154 """ get all paragraph parameters. They can be all on one line or on several lines.
1155 lines[i] must be the first parameter line"""
1156 par_params = ('added_space_bottom', 'added_space_top', 'align',
1157 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
1158 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
1159 'start_of_appendix')
1160 # We cannot check for '\\' only because paragraphs may start e.g.
1161 # with '\\backslash'
1163 while lines[i][:1] == '\\' and lines[i][1:].split()[0] in par_params:
1164 params = params + ' ' + lines[i].strip()
1166 return params.strip()
1169 def lyxsize2latexsize(lyxsize):
1170 " Convert LyX font size to LaTeX fontsize. "
1171 sizes = {"tiny" : "tiny", "scriptsize" : "scriptsize",
1172 "footnotesize" : "footnotesize", "small" : "small",
1173 "normal" : "normalsize", "large" : "large", "larger" : "Large",
1174 "largest" : "LARGE", "huge" : "huge", "giant" : "Huge"}
1175 if lyxsize in sizes:
1176 return '\\' + sizes[lyxsize]
1180 def revert_breaks(document):
1181 """ Change vspace insets, page breaks and lyxlines to paragraph options
1182 (if possible) or ERT"""
1184 # Get default spaceamount
1185 i = find_token(document.header, '\\defskip', 0)
1187 defskipamount = 'medskip'
1189 defskipamount = document.header[i].split()[1]
1191 keys = {"\\begin_inset" : "vspace", "\\lyxline" : "lyxline",
1192 "\\newpage" : "newpage"}
1193 keywords_top = {"vspace" : "\\added_space_top", "lyxline" : "\\line_top",
1194 "newpage" : "\\pagebreak_top"}
1195 keywords_bot = {"vspace" : "\\added_space_bottom", "lyxline" : "\\line_bottom",
1196 "newpage" : "\\pagebreak_bottom"}
1197 tokens = ["\\begin_inset VSpace", "\\lyxline", "\\newpage"]
1199 # Convert the insets
1202 i = find_tokens(document.body, tokens, i)
1206 # Are we at the beginning of a paragraph?
1208 this_par = get_paragraph(document.body, i, document.format - 1)
1209 start = this_par + 1
1210 params = get_par_params(document.body, start)
1212 # Paragraph parameters may be on one or more lines.
1213 # Find the start of the real paragraph text.
1214 while document.body[start][:1] == '\\' and document.body[start].split()[0] in params:
1216 for k in range(start, i):
1217 if document.body[k].find("\\size") != -1:
1219 size = document.body[k].split()[1]
1220 elif is_nonempty_line(document.body[k]):
1223 # Find the end of the real paragraph text.
1224 next_par = get_next_paragraph(document.body, i, document.format - 1)
1226 document.warning("Malformed LyX document: Missing next paragraph.")
1230 # first line of our insets
1232 # last line of our insets
1233 inset_end = inset_start
1234 # Are we at the end of a paragraph?
1236 # start and end line numbers to delete if we convert this inset
1238 # is this inset a lyxline above a paragraph?
1240 # raw inset information
1242 # name of this inset
1244 # font size of this inset
1247 # Detect subsequent lyxline, vspace and pagebreak insets created by convert_breaks()
1251 if find_tokens(document.body, tokens, k) == k:
1253 lines.append(document.body[k].split())
1254 insets.append(keys[lines[n][0]])
1255 del_lines.append([k, k])
1260 elif document.body[k].find("\\size") != -1:
1262 size = document.body[k].split()[1]
1263 elif find_token(document.body, "\\begin_inset ERT", k) == k:
1264 ert_begin = find_token(document.body, "\\layout", k) + 1
1266 document.warning("Malformed LyX document: Missing '\\layout'.")
1268 ert_end = find_end_of_inset(document.body, k)
1270 document.warning("Malformed LyX document: Missing '\\end_inset'.")
1272 ert = ert2latex(document.body[ert_begin:ert_end], document.format - 1)
1273 if (n > 0 and insets[n - 1] == "lyxline" and
1274 ert == '\\vspace{-1\\parskip}\n'):
1275 # vspace ERT created by convert_breaks() for top lyxline
1277 del_lines[n - 1][1] = ert_end
1283 elif (n > 0 and insets[n - 1] == "vspace" and
1284 find_token(document.body, "\\end_inset", k) == k):
1285 # ignore end of vspace inset
1286 del_lines[n - 1][1] = k
1288 elif is_nonempty_line(document.body[k]):
1293 # Determine space amount for vspace insets
1294 spaceamount = list()
1297 if insets[k] == "vspace":
1298 spaceamount.append(lines[k][2])
1299 arguments.append(' ' + spaceamount[k] + ' ')
1301 spaceamount.append('')
1302 arguments.append(' ')
1304 # Can we convert to top paragraph parameters?
1306 if ((n == 3 and insets[0] == "newpage" and insets[1] == "vspace" and
1307 insets[2] == "lyxline" and top[2]) or
1309 ((insets[0] == "newpage" and insets[1] == "vspace") or
1310 (insets[0] == "newpage" and insets[1] == "lyxline" and top[1]) or
1311 (insets[0] == "vspace" and insets[1] == "lyxline" and top[1]))) or
1312 (n == 1 and insets[0] == "lyxline" and top[0])):
1313 # These insets have been created before a paragraph by
1317 # Can we convert to bottom paragraph parameters?
1319 if ((n == 3 and insets[0] == "lyxline" and not top[0] and
1320 insets[1] == "vspace" and insets[2] == "newpage") or
1322 ((insets[0] == "lyxline" and not top[0] and insets[1] == "vspace") or
1323 (insets[0] == "lyxline" and not top[0] and insets[1] == "newpage") or
1324 (insets[0] == "vspace" and insets[1] == "newpage"))) or
1325 (n == 1 and insets[0] == "lyxline" and not top[0])):
1326 # These insets have been created after a paragraph by
1330 if paragraph_start and paragraph_end:
1331 # We are in a paragraph of our own.
1332 # We must not delete this paragraph if it has parameters
1334 # First try to merge with the previous paragraph.
1335 # We try the previous paragraph first because we would
1336 # otherwise need ERT for two subsequent vspaces.
1337 prev_par = get_paragraph(document.body, this_par - 1, document.format - 1) + 1
1338 if prev_par > 0 and not before:
1339 prev_params = get_par_params(document.body, prev_par + 1)
1341 # determine font size
1342 prev_size = "normal"
1344 while document.body[k][:1] == '\\' and document.body[k].split()[0] in prev_params:
1347 if document.body[k].find("\\size") != -1:
1348 prev_size = document.body[k].split()[1]
1350 elif document.body[k].find("\\begin_inset") != -1:
1352 k = find_end_of_inset(document.body, k)
1353 elif is_nonempty_line(document.body[k]):
1357 if (keywords_bot[insets[k]] in prev_params or
1358 (insets[k] == "lyxline" and sizes[k] != prev_size)):
1363 document.body.insert(prev_par + 1,
1364 keywords_bot[insets[k]] + arguments[k])
1365 del document.body[this_par+n:next_par-1+n]
1368 # Then try next paragraph
1369 if next_par > 0 and not after:
1370 next_params = get_par_params(document.body, next_par + 1)
1372 while document.body[k][:1] == '\\' and document.body[k].split()[0] in next_params:
1374 # determine font size
1375 next_size = "normal"
1378 if document.body[k].find("\\size") != -1:
1379 next_size = document.body[k].split()[1]
1381 elif is_nonempty_line(document.body[k]):
1385 if (keywords_top[insets[k]] in next_params or
1386 (insets[k] == "lyxline" and sizes[k] != next_size)):
1391 document.body.insert(next_par + 1,
1392 keywords_top[insets[k]] + arguments[k])
1393 del document.body[this_par:next_par-1]
1396 elif paragraph_start or paragraph_end:
1397 # Convert to paragraph formatting if we are at the beginning or end
1398 # of a paragraph and the resulting paragraph would not be empty
1399 # The order is important: del and insert invalidate some indices
1401 keywords = keywords_top
1403 keywords = keywords_bot
1406 if keywords[insets[k]] in params:
1411 document.body.insert(this_par + 1,
1412 keywords[insets[k]] + arguments[k])
1413 for j in range(k, n):
1414 del_lines[j][0] = del_lines[j][0] + 1
1415 del_lines[j][1] = del_lines[j][1] + 1
1416 del document.body[del_lines[k][0]:del_lines[k][1]+1]
1417 deleted = del_lines[k][1] - del_lines[k][0] + 1
1418 for j in range(k + 1, n):
1419 del_lines[j][0] = del_lines[j][0] - deleted
1420 del_lines[j][1] = del_lines[j][1] - deleted
1424 # Convert the first inset to ERT.
1425 # The others are converted in the next loop runs (if they exist)
1426 if insets[0] == "vspace":
1427 document.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
1428 '\\layout %s' % document.default_layout, '', '\\backslash ']
1430 if spaceamount[0][-1] == '*':
1431 spaceamount[0] = spaceamount[0][:-1]
1436 # Replace defskip by the actual value
1437 if spaceamount[0] == 'defskip':
1438 spaceamount[0] = defskipamount
1440 # LaTeX does not know \\smallskip* etc
1442 if spaceamount[0] == 'smallskip':
1443 spaceamount[0] = '\\smallskipamount'
1444 elif spaceamount[0] == 'medskip':
1445 spaceamount[0] = '\\medskipamount'
1446 elif spaceamount[0] == 'bigskip':
1447 spaceamount[0] = '\\bigskipamount'
1448 elif spaceamount[0] == 'vfill':
1449 spaceamount[0] = '\\fill'
1451 # Finally output the LaTeX code
1452 if (spaceamount[0] == 'smallskip' or spaceamount[0] == 'medskip' or
1453 spaceamount[0] == 'bigskip' or spaceamount[0] == 'vfill'):
1454 document.body.insert(i, spaceamount[0] + '{}')
1457 document.body.insert(i, 'vspace*{')
1459 document.body.insert(i, 'vspace{')
1460 i = convert_ertbackslash(document.body, i, spaceamount[0], document.format - 1, document.default_layout)
1461 document.body[i] = document.body[i] + '}'
1463 elif insets[0] == "lyxline":
1464 document.body[i] = ''
1465 latexsize = lyxsize2latexsize(size)
1467 document.warning("Could not convert LyX fontsize '%s' to LaTeX font size." % size)
1468 latexsize = '\\normalsize'
1469 i = insert_ert(document.body, i, 'Collapsed',
1470 '\\lyxline{%s}' % latexsize,
1471 document.format - 1, document.default_layout)
1472 # We use \providecommand so that we don't get an error if native
1473 # lyxlines are used (LyX writes first its own preamble and then
1474 # the user specified one)
1475 add_to_preamble(document,
1476 ['% Commands inserted by lyx2lyx for lyxlines',
1477 '\\providecommand{\\lyxline}[1]{',
1478 ' {#1 \\vspace{1ex} \\hrule width \\columnwidth \\vspace{1ex}}'
1480 elif insets[0] == "newpage":
1481 document.body[i] = ''
1482 i = insert_ert(document.body, i, 'Collapsed', '\\newpage{}',
1483 document.format - 1, document.default_layout)
1486 # Convert a LyX length into a LaTeX length
1487 def convert_len(len, special):
1488 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
1489 "page%":"\\pagewidth", "line%":"\\linewidth",
1490 "theight%":"\\textheight", "pheight%":"\\pageheight"}
1492 # Convert special lengths
1493 if special != 'none':
1494 len = '%f\\' % len2value(len) + special
1496 # Convert LyX units to LaTeX units
1497 for unit in list(units.keys()):
1498 if len.find(unit) != -1:
1499 len = '%f' % (len2value(len) / 100) + units[unit]
1505 def convert_ertlen(body, i, len, special, format, default_layout):
1506 """ Convert a LyX length into valid ERT code and append it to body[i]
1507 Return the (maybe incremented) line index i
1508 Convert backslashes and insert the converted length into body. """
1509 return convert_ertbackslash(body, i, convert_len(len, special), format, default_layout)
1513 " Return the value of len without the unit in numerical form. "
1514 result = re.search('([+-]?[0-9.]+)', len)
1516 return float(result.group(1))
1517 # No number means 1.0
1521 def insert_ert(body, i, status, text, format, default_layout):
1522 """ Convert text to ERT and insert it at body[i]
1523 Return the index of the line after the inserted ERT"""
1525 body[i:i] = ['\\begin_inset ERT', 'status ' + status, '']
1528 body[i:i] = ['\\layout %s' % default_layout, '']
1530 body[i:i] = ['\\begin_layout %s' % default_layout, '']
1531 i = i + 1 # i points now to the just created empty line
1532 i = convert_ertbackslash(body, i, text, format, default_layout) + 1
1534 body[i:i] = ['\\end_layout']
1536 body[i:i] = ['', '\\end_inset', '']
1541 def add_to_preamble(document, text):
1542 """ Add text to the preamble if it is not already there.
1543 Only the first line is checked!"""
1545 if find_token(document.preamble, text[0], 0) != -1:
1548 document.preamble.extend(text)
1551 def convert_frameless_box(document):
1552 " Convert frameless box."
1553 pos = ['t', 'c', 'b']
1554 inner_pos = ['c', 't', 'b', 's']
1557 i = find_token(document.body, '\\begin_inset Frameless', i)
1560 j = find_end_of_inset(document.body, i)
1562 document.warning("Malformed LyX document: Missing '\\end_inset'.")
1565 del document.body[i]
1569 params = {'position':0, 'hor_pos':'c', 'has_inner_box':'1',
1570 'inner_pos':1, 'use_parbox':'0', 'width':'100col%',
1571 'special':'none', 'height':'1in',
1572 'height_special':'totalheight', 'collapsed':'false'}
1573 for key in list(params.keys()):
1574 value = get_value(document.body, key, i, j).replace('"', '')
1576 if key == 'position':
1577 # convert new to old position: 'position "t"' -> 0
1578 value = find_token(pos, value, 0)
1581 elif key == 'inner_pos':
1582 # convert inner position
1583 value = find_token(inner_pos, value, 0)
1588 j = del_token(document.body, key, i, j)
1591 # Convert to minipage or ERT?
1592 # Note that the inner_position and height parameters of a minipage
1593 # inset are ignored and not accessible for the user, although they
1594 # are present in the file format and correctly read in and written.
1595 # Therefore we convert to ERT if they do not have their LaTeX
1596 # defaults. These are:
1597 # - the value of "position" for "inner_pos"
1598 # - "\totalheight" for "height"
1599 if (params['use_parbox'] != '0' or
1600 params['has_inner_box'] != '1' or
1601 params['special'] != 'none' or
1602 params['height_special'] != 'totalheight' or
1603 len2value(params['height']) != 1.0):
1605 # Here we know that this box is not supported in file format 224.
1606 # Therefore we need to convert it to ERT. We can't simply convert
1607 # the beginning and end of the box to ERT, because the
1608 # box inset may contain layouts that are different from the
1609 # surrounding layout. After the conversion the contents of the
1610 # box inset is on the same level as the surrounding text, and
1611 # paragraph layouts and align parameters can get mixed up.
1613 # A possible solution for this problem:
1614 # Convert the box to a minipage and redefine the minipage
1615 # environment in ERT so that the original box is simulated.
1616 # For minipages we could do this in a way that the width and
1617 # position can still be set from LyX, but this did not work well.
1618 # This is not possible for parboxes either, so we convert the
1619 # original box to ERT, put the minipage inset inside the box
1620 # and redefine the minipage environment to be empty.
1622 # Commands that are independant of a particular box can go to
1624 # We need to define lyxtolyxrealminipage with 3 optional
1625 # arguments although LyX 1.3 uses only the first one.
1626 # Otherwise we will get LaTeX errors if this document is
1627 # converted to format 225 or above again (LyX 1.4 uses all
1628 # optional arguments).
1629 add_to_preamble(document,
1630 ['% Commands inserted by lyx2lyx for frameless boxes',
1631 '% Save the original minipage environment',
1632 '\\let\\lyxtolyxrealminipage\\minipage',
1633 '\\let\\endlyxtolyxrealminipage\\endminipage',
1634 '% Define an empty lyxtolyximinipage environment',
1635 '% with 3 optional arguments',
1636 '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1637 '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1638 ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1639 ' {\\end{lyxtolyxiiiminipage}}',
1640 '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1641 ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1642 ' {\\end{lyxtolyxiiminipage}}',
1643 '\\newenvironment{lyxtolyxminipage}[1][c]%',
1644 ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1645 ' {\\end{lyxtolyximinipage}}'])
1647 if params['use_parbox'] != '0':
1650 ert = '\\begin{lyxtolyxrealminipage}'
1652 # convert optional arguments only if not latex default
1653 if (pos[params['position']] != 'c' or
1654 inner_pos[params['inner_pos']] != pos[params['position']] or
1655 params['height_special'] != 'totalheight' or
1656 len2value(params['height']) != 1.0):
1657 ert = ert + '[' + pos[params['position']] + ']'
1658 if (inner_pos[params['inner_pos']] != pos[params['position']] or
1659 params['height_special'] != 'totalheight' or
1660 len2value(params['height']) != 1.0):
1661 ert = ert + '[' + convert_len(params['height'],
1662 params['height_special']) + ']'
1663 if inner_pos[params['inner_pos']] != pos[params['position']]:
1664 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1666 ert = ert + '{' + convert_len(params['width'],
1667 params['special']) + '}'
1669 if params['use_parbox'] != '0':
1671 ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1672 ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1675 i = insert_ert(document.body, i, 'Collapsed', ert, document.format - 1, document.default_layout)
1676 j = j + i - old_i - 1
1678 document.body[i:i] = ['\\begin_inset Minipage',
1679 'position %d' % params['position'],
1682 'width "' + params['width'] + '"',
1683 'collapsed ' + params['collapsed']]
1687 # Restore the original minipage environment since we may have
1688 # minipages inside this box.
1689 # Start a new paragraph because the following may be nonstandard
1690 document.body[i:i] = ['\\layout %s' % document.default_layout, '', '']
1693 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1694 ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1696 i = insert_ert(document.body, i, 'Collapsed', ert, document.format - 1, document.default_layout)
1697 j = j + i - old_i - 1
1699 # Redefine the minipage end before the inset end.
1700 # Start a new paragraph because the previous may be nonstandard
1701 document.body[j:j] = ['\\layout %s' % document.default_layout, '', '']
1703 ert = '\\let\\endminipage\\endlyxtolyxminipage'
1704 j = insert_ert(document.body, j, 'Collapsed', ert, document.format - 1, document.default_layout)
1706 document.body.insert(j, '')
1709 # LyX writes '%\n' after each box. Therefore we need to end our
1710 # ERT with '%\n', too, since this may swallow a following space.
1711 if params['use_parbox'] != '0':
1714 ert = '\\end{lyxtolyxrealminipage}%\n'
1715 j = insert_ert(document.body, j, 'Collapsed', ert, document.format - 1, document.default_layout)
1717 # We don't need to restore the original minipage after the inset
1718 # end because the scope of the redefinition is the original box.
1722 # Convert to minipage
1723 document.body[i:i] = ['\\begin_inset Minipage',
1724 'position %d' % params['position'],
1725 'inner_position %d' % params['inner_pos'],
1726 'height "' + params['height'] + '"',
1727 'width "' + params['width'] + '"',
1728 'collapsed ' + params['collapsed']]
1732 def remove_branches(document):
1733 " Remove branches. "
1736 i = find_token(document.header, "\\branch", i)
1739 document.warning("Removing branch %s." % document.header[i].split()[1])
1740 j = find_token(document.header, "\\end_branch", i)
1742 document.warning("Malformed LyX document: Missing '\\end_branch'.")
1744 del document.header[i:j+1]
1748 i = find_token(document.body, "\\begin_inset Branch", i)
1751 j = find_end_of_inset(document.body, i)
1753 document.warning("Malformed LyX document: Missing '\\end_inset'.")
1756 del document.body[i]
1757 del document.body[j - 1]
1758 # Seach for a line starting 'collapsed'
1759 # If, however, we find a line starting '\layout'
1760 # (_always_ present) then break with a warning message
1763 if (document.body[i][:9] == "collapsed"):
1764 del document.body[i]
1767 elif (document.body[i][:7] == "\\layout"):
1768 if collapsed_found == 0:
1769 document.warning("Malformed LyX document: Missing 'collapsed'.")
1770 # Delete this new paragraph, since it would not appear in
1771 # .tex output. This avoids also empty paragraphs.
1772 del document.body[i]
1777 def convert_jurabib(document):
1778 " Convert jurabib. "
1779 i = find_token(document.header, '\\use_numerical_citations', 0)
1781 document.warning("Malformed lyx document: Missing '\\use_numerical_citations'.")
1783 document.header.insert(i + 1, '\\use_jurabib 0')
1786 def revert_jurabib(document):
1788 i = find_token(document.header, '\\use_jurabib', 0)
1790 document.warning("Malformed lyx document: Missing '\\use_jurabib'.")
1792 if get_value(document.header, '\\use_jurabib', 0) != "0":
1793 document.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1794 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1796 del document.header[i]
1799 def convert_bibtopic(document):
1800 " Convert bibtopic. "
1801 i = find_token(document.header, '\\use_jurabib', 0)
1803 document.warning("Malformed lyx document: Missing '\\use_jurabib'.")
1805 document.header.insert(i + 1, '\\use_bibtopic 0')
1808 def revert_bibtopic(document):
1809 " Revert bibtopic. "
1810 i = find_token(document.header, '\\use_bibtopic', 0)
1812 document.warning("Malformed lyx document: Missing '\\use_bibtopic'.")
1814 if get_value(document.header, '\\use_bibtopic', 0) != "0":
1815 document.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1816 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1817 del document.header[i]
1820 def convert_float(document):
1821 " Convert sideway floats. "
1824 i = find_token_exact(document.body, '\\begin_inset Float', i)
1827 # Seach for a line starting 'wide'
1828 # If, however, we find a line starting '\begin_layout'
1829 # (_always_ present) then break with a warning message
1832 if (document.body[i][:4] == "wide"):
1833 document.body.insert(i + 1, 'sideways false')
1835 elif (document.body[i][:13] == "\\begin_layout"):
1836 document.warning("Malformed lyx document: Missing 'wide'.")
1842 def revert_float(document):
1843 " Revert sideways floats. "
1846 i = find_token_exact(document.body, '\\begin_inset Float', i)
1849 line = document.body[i]
1850 r = re.compile(r'\\begin_inset Float (.*)$')
1852 floattype = m.group(1)
1853 if floattype != "figure" and floattype != "table":
1856 j = find_end_of_inset(document.body, i)
1858 document.warning("Malformed lyx document: Missing '\\end_inset'.")
1861 if get_value(document.body, 'sideways', i, j) != "false":
1862 l = find_token(document.body, "\\begin_layout Standard", i + 1, j)
1864 document.warning("Malformed LyX document: Missing `\\begin_layout Standard' in Float inset.")
1866 document.body[j] = '\\layout Standard\n\\begin_inset ERT\nstatus Collapsed\n\n' \
1867 '\\layout Standard\n\n\n\\backslash\n' \
1868 'end{sideways' + floattype + '}\n\n\\end_inset\n'
1869 del document.body[i+1:l-1]
1870 document.body[i] = '\\begin_inset ERT\nstatus Collapsed\n\n' \
1871 '\\layout Standard\n\n\n\\backslash\n' \
1872 'begin{sideways' + floattype + '}\n\n\\end_inset\n\n'
1873 add_to_preamble(document,
1874 ['\\usepackage{rotfloat}\n'])
1877 del_token(document.body, 'sideways', i, j)
1881 def convert_graphics(document):
1882 """ Add extension to documentnames of insetgraphics if necessary.
1886 i = find_token(document.body, "\\begin_inset Graphics", i)
1890 j = find_token_exact(document.body, "documentname", i)
1894 filename = document.body[j].split()[1]
1895 if document.dir == '' and not os.path.isabs(filename):
1896 # We don't know the directory and cannot check the document.
1897 # We could use a heuristic and take the current directory,
1898 # and we could try to find out if documentname has an extension,
1899 # but that would be just guesses and could be wrong.
1900 document.warning("""Warning: Cannot determine whether document
1902 needs an extension when reading from standard input.
1903 You may need to correct the document manually or run
1904 lyx2lyx again with the .lyx document as commandline argument.""" % filename)
1906 absname = os.path.normpath(os.path.join(document.dir, filename))
1907 # This needs to be the same algorithm as in pre 233 insetgraphics
1908 if access(absname, F_OK):
1910 if access(absname + ".ps", F_OK):
1911 document.body[j] = document.body[j].replace(filename, filename + ".ps")
1913 if access(absname + ".eps", F_OK):
1914 document.body[j] = document.body[j].replace(filename, filename + ".eps")
1917 def convert_names(document):
1918 """ Convert in the docbook backend from firstname and surname style
1921 if document.backend != "docbook":
1927 i = find_token(document.body, "\\begin_layout Author", i)
1932 while document.body[i] == "":
1935 if document.body[i][:11] != "\\end_layout" or document.body[i+2][:13] != "\\begin_deeper":
1940 i = find_end_of( document.body, i+3, "\\begin_deeper","\\end_deeper")
1942 # something is really wrong, abort
1943 document.warning("Missing \\end_deeper, after style Author.")
1944 document.warning("Aborted attempt to parse FirstName and Surname.")
1946 firstname, surname = "", ""
1948 name = document.body[k:i]
1950 j = find_token(name, "\\begin_layout FirstName", 0)
1953 while(name[j] != "\\end_layout"):
1954 firstname = firstname + name[j]
1957 j = find_token(name, "\\begin_layout Surname", 0)
1960 while(name[j] != "\\end_layout"):
1961 surname = surname + name[j]
1965 del document.body[k+2:i+1]
1967 document.body[k-1:k-1] = ["", "",
1968 "\\begin_inset CharStyle Firstname",
1971 '\\begin_layout %s' % document.default_layout,
1979 "\\begin_inset CharStyle Surname",
1982 '\\begin_layout %s' % document.default_layout,
1991 def revert_names(document):
1992 """ Revert in the docbook backend from firstname and surname char style
1995 if document.backend != "docbook":
1999 def convert_cite_engine(document):
2000 r""" \use_natbib 1 \cite_engine <style>
2001 \use_numerical_citations 0 -> where <style> is one of
2002 \use_jurabib 0 "basic", "natbib_authoryear","""
2004 a = find_token(document.header, "\\use_natbib", 0)
2006 document.warning("Malformed lyx document: Missing '\\use_natbib'.")
2009 b = find_token(document.header, "\\use_numerical_citations", 0)
2010 if b == -1 or b != a+1:
2011 document.warning("Malformed lyx document: Missing '\\use_numerical_citations'.")
2014 c = find_token(document.header, "\\use_jurabib", 0)
2015 if c == -1 or c != b+1:
2016 document.warning("Malformed lyx document: Missing '\\use_jurabib'.")
2019 use_natbib = int(document.header[a].split()[1])
2020 use_numerical_citations = int(document.header[b].split()[1])
2021 use_jurabib = int(document.header[c].split()[1])
2023 cite_engine = "basic"
2025 if use_numerical_citations:
2026 cite_engine = "natbib_numerical"
2028 cite_engine = "natbib_authoryear"
2030 cite_engine = "jurabib"
2032 del document.header[a:c+1]
2033 document.header.insert(a, "\\cite_engine " + cite_engine)
2036 def revert_cite_engine(document):
2037 " Revert the cite engine. "
2038 i = find_token(document.header, "\\cite_engine", 0)
2040 document.warning("Malformed lyx document: Missing '\\cite_engine'.")
2043 cite_engine = document.header[i].split()[1]
2048 if cite_engine == "natbib_numerical":
2051 elif cite_engine == "natbib_authoryear":
2053 elif cite_engine == "jurabib":
2056 del document.header[i]
2057 document.header.insert(i, "\\use_jurabib " + use_jurabib)
2058 document.header.insert(i, "\\use_numerical_citations " + use_numerical)
2059 document.header.insert(i, "\\use_natbib " + use_natbib)
2062 def convert_paperpackage(document):
2063 " Convert paper package. "
2064 i = find_token(document.header, "\\paperpackage", 0)
2068 packages = {'default':'none','a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
2069 if len(document.header[i].split()) > 1:
2070 paperpackage = document.header[i].split()[1]
2071 document.header[i] = document.header[i].replace(paperpackage, packages[paperpackage])
2073 document.header[i] = document.header[i] + ' widemarginsa4'
2076 def revert_paperpackage(document):
2077 " Revert paper package. "
2078 i = find_token(document.header, "\\paperpackage", 0)
2082 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
2083 'widemarginsa4':'', 'default': 'default'}
2084 if len(document.header[i].split()) > 1:
2085 paperpackage = document.header[i].split()[1]
2087 paperpackage = 'default'
2088 document.header[i] = document.header[i].replace(paperpackage, packages[paperpackage])
2091 def convert_bullets(document):
2092 " Convert bullets. "
2095 i = find_token(document.header, "\\bullet", i)
2098 if document.header[i][:12] == '\\bulletLaTeX':
2099 document.header[i] = document.header[i] + ' ' + document.header[i+1].strip()
2102 document.header[i] = document.header[i] + ' ' + document.header[i+1].strip() +\
2103 ' ' + document.header[i+2].strip() + ' ' + document.header[i+3].strip()
2105 del document.header[i+1:i + n]
2109 def revert_bullets(document):
2113 i = find_token(document.header, "\\bullet", i)
2116 if document.header[i][:12] == '\\bulletLaTeX':
2117 n = document.header[i].find('"')
2119 document.warning("Malformed header.")
2122 document.header[i:i+1] = [document.header[i][:n-1],'\t' + document.header[i][n:], '\\end_bullet']
2125 frag = document.header[i].split()
2127 document.warning("Malformed header.")
2130 document.header[i:i+1] = [frag[0] + ' ' + frag[1],
2138 def add_begin_header(document):
2139 r" Add \begin_header and \begin_document. "
2140 i = find_token(document.header, '\\lyxformat', 0)
2141 document.header.insert(i+1, '\\begin_header')
2142 document.header.insert(i+1, '\\begin_document')
2145 def remove_begin_header(document):
2146 r" Remove \begin_header and \begin_document. "
2147 i = find_token(document.header, "\\begin_document", 0)
2149 del document.header[i]
2150 i = find_token(document.header, "\\begin_header", 0)
2152 del document.header[i]
2155 def add_begin_body(document):
2156 r" Add and \begin_document and \end_document"
2157 document.body.insert(0, '\\begin_body')
2158 document.body.insert(1, '')
2159 i = find_token(document.body, "\\end_document", 0)
2160 document.body.insert(i, '\\end_body')
2162 def remove_begin_body(document):
2163 r" Remove \begin_body and \end_body"
2164 i = find_token(document.body, "\\begin_body", 0)
2166 del document.body[i]
2167 if not document.body[i]:
2168 del document.body[i]
2169 i = find_token(document.body, "\\end_body", 0)
2171 del document.body[i]
2174 def normalize_papersize(document):
2175 r" Normalize \papersize"
2176 i = find_token(document.header, '\\papersize', 0)
2180 tmp = document.header[i].split()
2181 if tmp[1] == "Default":
2182 document.header[i] = '\\papersize default'
2184 if tmp[1] == "Custom":
2185 document.header[i] = '\\papersize custom'
2188 def denormalize_papersize(document):
2189 r" Revert \papersize"
2190 i = find_token(document.header, '\\papersize', 0)
2194 tmp = document.header[i].split()
2195 if tmp[1] == "custom":
2196 document.header[i] = '\\papersize Custom'
2199 def strip_end_space(document):
2200 " Strip spaces at end of command line. "
2201 for i in range(len(document.body)):
2202 if document.body[i][:1] == '\\':
2203 document.body[i] = document.body[i].strip()
2206 def use_x_boolean(document):
2207 r" Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes"
2208 bin2bool = {'0': 'false', '1': 'true'}
2209 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2210 i = find_token(document.header, use, 0)
2213 decompose = document.header[i].split()
2214 document.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
2217 def use_x_binary(document):
2218 r" Use digit values for \use_geometry, \use_bibtopic and \tracking_changes"
2219 bool2bin = {'false': '0', 'true': '1'}
2220 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2221 i = find_token(document.header, use, 0)
2224 decompose = document.header[i].split()
2225 document.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
2228 def normalize_paragraph_params(document):
2229 " Place all the paragraph parameters in their own line. "
2230 body = document.body
2232 allowed_parameters = '\\paragraph_spacing', '\\noindent', \
2233 '\\align', '\\labelwidthstring', "\\start_of_appendix", \
2238 i = find_token(document.body, '\\begin_layout', i)
2244 if body[i].strip() and body[i].split()[0] not in allowed_parameters:
2247 j = body[i].find('\\', 1)
2250 body[i:i+1] = [body[i][:j].strip(), body[i][j:]]
2255 def convert_output_changes (document):
2256 " Add output_changes parameter. "
2257 i = find_token(document.header, '\\tracking_changes', 0)
2259 document.warning("Malformed lyx document: Missing '\\tracking_changes'.")
2261 document.header.insert(i+1, '\\output_changes true')
2264 def revert_output_changes (document):
2265 " Remove output_changes parameter. "
2266 i = find_token(document.header, '\\output_changes', 0)
2269 del document.header[i]
2272 def convert_ert_paragraphs(document):
2273 " Convert paragraph breaks and sanitize paragraphs. "
2274 forbidden_settings = [
2275 # paragraph parameters
2276 '\\paragraph_spacing', '\\labelwidthstring',
2277 '\\start_of_appendix', '\\noindent',
2278 '\\leftindent', '\\align',
2280 '\\family', '\\series', '\\shape', '\\size',
2281 '\\emph', '\\numeric', '\\bar', '\\noun',
2282 '\\color', '\\lang']
2285 i = find_token(document.body, '\\begin_inset ERT', i)
2288 j = find_end_of_inset(document.body, i)
2290 document.warning("Malformed lyx document: Missing '\\end_inset'.")
2294 # convert non-standard paragraphs to standard
2297 k = find_token(document.body, "\\begin_layout", k, j)
2300 document.body[k] = '\\begin_layout %s' % document.default_layout
2303 # remove all paragraph parameters and font settings
2306 if (document.body[k].strip() and
2307 document.body[k].split()[0] in forbidden_settings):
2308 del document.body[k]
2313 # insert an empty paragraph before each paragraph but the first
2317 k = find_token(document.body, "\\begin_layout", k, j)
2324 document.body[k:k] = ['\\begin_layout %s' % document.default_layout, "",
2329 # convert \\newline to new paragraph
2332 k = find_token(document.body, "\\newline", k, j)
2335 document.body[k:k+1] = ["\\end_layout", "", '\\begin_layout %s' % document.default_layout]
2338 # We need an empty line if document.default_layout == ''
2339 if document.body[k] != '':
2340 document.body.insert(k, '')
2346 def revert_ert_paragraphs(document):
2347 " Remove double paragraph breaks. "
2350 i = find_token(document.body, '\\begin_inset ERT', i)
2353 j = find_end_of_inset(document.body, i)
2355 document.warning("Malformed lyx document: Missing '\\end_inset'.")
2359 # replace paragraph breaks with \newline
2362 k = find_token(document.body, "\\end_layout", k, j)
2363 l = find_token(document.body, "\\begin_layout", k, j)
2364 if k == -1 or l == -1:
2366 document.body[k:l+1] = ["\\newline"]
2370 # replace double \newlines with paragraph breaks
2373 k = find_token(document.body, "\\newline", k, j)
2377 while document.body[l] == "":
2379 if document.body[l].strip() and document.body[l].split()[0] == "\\newline":
2380 document.body[k:l+1] = ["\\end_layout", "",
2381 '\\begin_layout %s' % document.default_layout]
2384 # We need an empty line if document.default_layout == ''
2385 if document.body[l+1] != '':
2386 document.body.insert(l+1, '')
2394 def convert_french(document):
2395 " Convert frenchb. "
2396 regexp = re.compile(r'^\\language\s+frenchb')
2397 i = find_re(document.header, regexp, 0)
2399 document.header[i] = "\\language french"
2401 # Change language in the document body
2402 regexp = re.compile(r'^\\lang\s+frenchb')
2405 i = find_re(document.body, regexp, i)
2408 document.body[i] = "\\lang french"
2412 def remove_paperpackage(document):
2413 " Remove paper package. "
2414 i = find_token(document.header, '\\paperpackage', 0)
2419 paperpackage = document.header[i].split()[1]
2421 del document.header[i]
2423 if paperpackage not in ("a4", "a4wide", "widemarginsa4"):
2426 conv = {"a4":"\\usepackage{a4}","a4wide": "\\usepackage{a4wide}",
2427 "widemarginsa4": "\\usepackage[widemargins]{a4}"}
2428 # for compatibility we ensure it is the first entry in preamble
2429 document.preamble[0:0] = [conv[paperpackage]]
2431 i = find_token(document.header, '\\papersize', 0)
2433 document.header[i] = "\\papersize default"
2436 def remove_quotestimes(document):
2437 " Remove quotestimes. "
2438 i = find_token(document.header, '\\quotes_times', 0)
2441 del document.header[i]
2444 def convert_sgml_paragraphs(document):
2445 " Convert SGML paragraphs. "
2446 if document.backend != "docbook":
2451 i = find_token(document.body, "\\begin_layout SGML", i)
2456 document.body[i] = "\\begin_layout Standard"
2457 j = find_token(document.body, "\\end_layout", i)
2459 document.body[j+1:j+1] = ['','\\end_inset','','','\\end_layout']
2460 document.body[i+1:i+1] = ['\\begin_inset ERT','status inlined','','\\begin_layout Standard','']
2468 supported_versions = ["1.4.%d" % i for i in range(3)] + ["1.4"]
2469 convert = [[222, [insert_tracking_changes, add_end_header, convert_amsmath]],
2470 [223, [remove_color_default, convert_spaces, convert_bibtex, remove_insetparent]],
2471 [224, [convert_external, convert_comment]],
2472 [225, [add_end_layout, layout2begin_layout, convert_end_document,
2473 convert_table_valignment_middle, convert_breaks]],
2474 [226, [convert_note]],
2475 [227, [convert_box]],
2476 [228, [convert_collapsible, convert_ert]],
2477 [229, [convert_minipage]],
2478 [230, [convert_jurabib]],
2479 [231, [convert_float]],
2480 [232, [convert_bibtopic]],
2481 [233, [convert_graphics, convert_names]],
2482 [234, [convert_cite_engine]],
2483 [235, [convert_paperpackage]],
2484 [236, [convert_bullets, add_begin_header, add_begin_body,
2485 normalize_papersize, strip_end_space]],
2486 [237, [use_x_boolean]],
2487 [238, [update_latexaccents]],
2488 [239, [normalize_paragraph_params]],
2489 [240, [convert_output_changes]],
2490 [241, [convert_ert_paragraphs]],
2491 [242, [convert_french]],
2492 [243, [remove_paperpackage]],
2493 [244, [rename_spaces]],
2494 [245, [remove_quotestimes, convert_sgml_paragraphs]]]
2496 revert = [[244, []],
2497 [243, [revert_space_names]],
2500 [240, [revert_ert_paragraphs]],
2501 [239, [revert_output_changes]],
2504 [236, [use_x_binary]],
2505 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
2507 [234, [revert_paperpackage]],
2508 [233, [revert_cite_engine]],
2509 [232, [revert_names]],
2510 [231, [revert_bibtopic]],
2511 [230, [revert_float]],
2512 [229, [revert_jurabib]],
2514 [227, [revert_collapsible, revert_ert]],
2515 [226, [revert_box, revert_external_2]],
2516 [225, [revert_note]],
2517 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
2518 revert_valignment_middle, revert_breaks, convert_frameless_box,
2520 [223, [revert_external_2, revert_comment, revert_eqref]],
2521 [222, [revert_spaces, revert_bibtex]],
2522 [221, [revert_amsmath, rm_end_header, rm_tracking_changes, rm_body_changes]]]
2525 if __name__ == "__main__":