# This file is part of lyx2lyx # -*- coding: iso-8859-1 -*- # Copyright (C) 2002 Dekel Tsur # Copyright (C) 2002-2004 José Matos # Copyright (C) 2004-2005 Georg Baum # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import re from os import access, F_OK import os.path from parser_tools import find_token, find_end_of_inset, get_next_paragraph, \ get_paragraph, get_value, del_token, is_nonempty_line,\ find_tokens, find_end_of, find_token2, find_re from sys import stdin from string import replace, split, find, strip, join from lyx_0_12 import update_latexaccents ## # Remove \color default # def remove_color_default(file): i = 0 while 1: i = find_token(file.body, "\\color default", i) if i == -1: return file.body[i] = replace(file.body[i], "\\color default", "\\color inherit") ## # Add \end_header # def add_end_header(file): file.header.append("\\end_header"); def rm_end_header(file): i = find_token(file.header, "\\end_header", 0) if i == -1: return del file.header[i] ## # \SpecialChar ~ -> \InsetSpace ~ # def convert_spaces(file): for i in range(len(file.body)): file.body[i] = replace(file.body[i],"\\SpecialChar ~","\\InsetSpace ~") def revert_spaces(file): regexp = re.compile(r'(.*)(\\InsetSpace\s+)(\S+)') i = 0 while 1: i = find_re(file.body, regexp, i) if i == -1: break space = regexp.match(file.body[i]).group(3) prepend = regexp.match(file.body[i]).group(1) if space == '~': file.body[i] = regexp.sub(prepend + '\\SpecialChar ~', file.body[i]) i = i + 1 else: file.body[i] = regexp.sub(prepend, file.body[i]) file.body[i+1:i+1] = '' if space == "\\space": space = "\\ " i = insert_ert(file.body, i+1, 'Collapsed', space, file.format - 1) ## # \InsetSpace \, -> \InsetSpace \thinspace{} # \InsetSpace \space -> \InsetSpace \space{} # def rename_spaces(file): for i in range(len(file.body)): file.body[i] = replace(file.body[i],"\\InsetSpace \\space","\\InsetSpace \\space{}") file.body[i] = replace(file.body[i],"\\InsetSpace \,","\\InsetSpace \\thinspace{}") def revert_space_names(file): for i in range(len(file.body)): file.body[i] = replace(file.body[i],"\\InsetSpace \\space{}","\\InsetSpace \\space") file.body[i] = replace(file.body[i],"\\InsetSpace \\thinspace{}","\\InsetSpace \\,") ## # equivalent to lyx::support::escape() # def lyx_support_escape(lab): hexdigit = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'] enc = "" for c in lab: o = ord(c) if o >= 128 or c == '=' or c == '%': enc = enc + '=' enc = enc + hexdigit[o >> 4] enc = enc + hexdigit[o & 15] else: enc = enc + c return enc; ## # \begin_inset LatexCommand \eqref -> ERT # def revert_eqref(file): regexp = re.compile(r'^\\begin_inset\s+LatexCommand\s+\\eqref') i = 0 while 1: i = find_re(file.body, regexp, i) if i == -1: break eqref = lyx_support_escape(regexp.sub("", file.body[i])) file.body[i:i+1] = ["\\begin_inset ERT", "status Collapsed", "", "\\layout Standard", "", "\\backslash ", "eqref" + eqref] i = i + 7 ## # BibTeX changes # def convert_bibtex(file): for i in range(len(file.body)): file.body[i] = replace(file.body[i],"\\begin_inset LatexCommand \\BibTeX", "\\begin_inset LatexCommand \\bibtex") def revert_bibtex(file): for i in range(len(file.body)): file.body[i] = replace(file.body[i], "\\begin_inset LatexCommand \\bibtex", "\\begin_inset LatexCommand \\BibTeX") ## # Remove \lyxparent # def remove_insetparent(file): i = 0 while 1: i = find_token(file.body, "\\begin_inset LatexCommand \\lyxparent", i) if i == -1: break del file.body[i:i+3] ## # Inset External # def convert_external(file): external_rexp = re.compile(r'\\begin_inset External ([^,]*),"([^"]*)",') external_header = "\\begin_inset External" i = 0 while 1: i = find_token(file.body, external_header, i) if i == -1: break look = external_rexp.search(file.body[i]) args = ['',''] if look: args[0] = look.group(1) args[1] = look.group(2) #FIXME: if the previous search fails then warn if args[0] == "RasterImage": # Convert a RasterImage External Inset to a Graphics Inset. top = "\\begin_inset Graphics" if args[1]: filename = "\tfilename " + args[1] file.body[i:i+1] = [top, filename] i = i + 1 else: # Convert the old External Inset format to the new. top = external_header template = "\ttemplate " + args[0] if args[1]: filename = "\tfilename " + args[1] file.body[i:i+1] = [top, template, filename] i = i + 2 else: file.body[i:i+1] = [top, template] i = i + 1 def revert_external_1(file): external_header = "\\begin_inset External" i = 0 while 1: i = find_token(file.body, external_header, i) if i == -1: break template = split(file.body[i+1]) template.reverse() del file.body[i+1] filename = split(file.body[i+1]) filename.reverse() del file.body[i+1] params = split(file.body[i+1]) params.reverse() if file.body[i+1]: del file.body[i+1] file.body[i] = file.body[i] + " " + template[0]+ ', "' + filename[0] + '", " '+ join(params[1:]) + '"' i = i + 1 def revert_external_2(file): draft_token = '\tdraft' i = 0 while 1: i = find_token(file.body, '\\begin_inset External', i) if i == -1: break j = find_end_of_inset(file.body, i + 1) if j == -1: #this should not happen break k = find_token(file.body, draft_token, i+1, j-1) if (k != -1 and len(draft_token) == len(file.body[k])): del file.body[k] i = j + 1 ## # Comment # def convert_comment(file): i = 0 comment = "\\layout Comment" while 1: i = find_token(file.body, comment, i) if i == -1: return file.body[i:i+1] = ["\\layout Standard","","", "\\begin_inset Comment", "collapsed true","", "\\layout Standard"] i = i + 7 while 1: old_i = i i = find_token(file.body, "\\layout", i) if i == -1: i = len(file.body) - 1 file.body[i:i] = ["\\end_inset","",""] return j = find_token(file.body, '\\begin_deeper', old_i, i) if j == -1: j = i + 1 k = find_token(file.body, '\\begin_inset', old_i, i) if k == -1: k = i + 1 if j < i and j < k: i = j del file.body[i] i = find_end_of( file.body, i, "\\begin_deeper","\\end_deeper") if i == -1: #This case should not happen #but if this happens deal with it greacefully adding #the missing \end_deeper. i = len(file.body) - 1 file.body[i:i] = ["\end_deeper",""] return else: del file.body[i] continue if k < i: i = k i = find_end_of( file.body, i, "\\begin_inset","\\end_inset") if i == -1: #This case should not happen #but if this happens deal with it greacefully adding #the missing \end_inset. i = len(file.body) - 1 file.body[i:i] = ["\\end_inset","","","\\end_inset","",""] return else: i = i + 1 continue if find(file.body[i], comment) == -1: file.body[i:i] = ["\\end_inset"] i = i + 1 break file.body[i:i+1] = ["\\layout Standard"] i = i + 1 def revert_comment(file): i = 0 while 1: i = find_tokens(file.body, ["\\begin_inset Comment", "\\begin_inset Greyedout"], i) if i == -1: return file.body[i] = "\\begin_inset Note" i = i + 1 ## # Add \end_layout # def add_end_layout(file): i = find_token(file.body, '\\layout', 0) if i == -1: return i = i + 1 struct_stack = ["\\layout"] while 1: i = find_tokens(file.body, ["\\begin_inset", "\\end_inset", "\\layout", "\\begin_deeper", "\\end_deeper", "\\the_end"], i) if i != -1: token = split(file.body[i])[0] else: file.warning("Truncated file.") i = len(file.body) file.body.insert(i, '\\the_end') token = "" if token == "\\begin_inset": struct_stack.append(token) i = i + 1 continue if token == "\\end_inset": tail = struct_stack.pop() if tail == "\\layout": file.body.insert(i,"") file.body.insert(i,"\\end_layout") i = i + 2 #Check if it is the correct tag struct_stack.pop() i = i + 1 continue if token == "\\layout": tail = struct_stack.pop() if tail == token: file.body.insert(i,"") file.body.insert(i,"\\end_layout") i = i + 3 else: struct_stack.append(tail) i = i + 1 struct_stack.append(token) continue if token == "\\begin_deeper": file.body.insert(i,"") file.body.insert(i,"\\end_layout") i = i + 3 struct_stack.append(token) continue if token == "\\end_deeper": if struct_stack[-1] == '\\layout': file.body.insert(i, '\\end_layout') i = i + 1 struct_stack.pop() i = i + 1 continue #case \end_document file.body.insert(i, "") file.body.insert(i, "\\end_layout") return def rm_end_layout(file): i = 0 while 1: i = find_token(file.body, '\\end_layout', i) if i == -1: return del file.body[i] ## # Handle change tracking keywords # def insert_tracking_changes(file): i = find_token(file.header, "\\tracking_changes", 0) if i == -1: file.header.append("\\tracking_changes 0") def rm_tracking_changes(file): i = find_token(file.header, "\\author", 0) if i != -1: del file.header[i] i = find_token(file.header, "\\tracking_changes", 0) if i == -1: return del file.header[i] def rm_body_changes(file): i = 0 while 1: i = find_token(file.body, "\\change_", i) if i == -1: return del file.body[i] ## # \layout -> \begin_layout # def layout2begin_layout(file): i = 0 while 1: i = find_token(file.body, '\\layout', i) if i == -1: return file.body[i] = replace(file.body[i], '\\layout', '\\begin_layout') i = i + 1 def begin_layout2layout(file): i = 0 while 1: i = find_token(file.body, '\\begin_layout', i) if i == -1: return file.body[i] = replace(file.body[i], '\\begin_layout', '\\layout') i = i + 1 ## # valignment="center" -> valignment="middle" # def convert_valignment_middle(body, start, end): for i in range(start, end): if re.search('^<(column|cell) .*valignment="center".*>$', body[i]): body[i] = replace(body[i], 'valignment="center"', 'valignment="middle"') def convert_table_valignment_middle(file): regexp = re.compile(r'^\\begin_inset\s+Tabular') i = 0 while 1: i = find_re(file.body, regexp, i) if i == -1: return j = find_end_of_inset(file.body, i + 1) if j == -1: #this should not happen convert_valignment_middle(file.body, i + 1, len(file.body)) return convert_valignment_middle(file.body, i + 1, j) i = j + 1 def revert_table_valignment_middle(body, start, end): for i in range(start, end): if re.search('^<(column|cell) .*valignment="middle".*>$', body[i]): body[i] = replace(body[i], 'valignment="middle"', 'valignment="center"') def revert_valignment_middle(file): regexp = re.compile(r'^\\begin_inset\s+Tabular') i = 0 while 1: i = find_re(file.body, regexp, i) if i == -1: return j = find_end_of_inset(file.body, i + 1) if j == -1: #this should not happen revert_table_valignment_middle(file.body, i + 1, len(file.body)) return revert_table_valignment_middle(file.body, i + 1, j) i = j + 1 ## # \the_end -> \end_document # def convert_end_document(file): i = find_token(file.body, "\\the_end", 0) if i == -1: file.body.append("\\end_document") return file.body[i] = "\\end_document" def revert_end_document(file): i = find_token(file.body, "\\end_document", 0) if i == -1: file.body.append("\\the_end") return file.body[i] = "\\the_end" ## # Convert line and page breaks # Old: #\layout Standard #\line_top \line_bottom \pagebreak_top \pagebreak_bottom \added_space_top xxx \added_space_bottom yyy #0 # # New: #\begin layout Standard # #\newpage # #\lyxline #\begin_inset ERT #\begin layout Standard #\backslash #vspace{-1\backslash #parskip} #\end_layout #\end_inset # #\begin_inset VSpace xxx #\end_inset # #0 # #\begin_inset VSpace xxx #\end_inset #\lyxline # #\newpage # #\end_layout def convert_breaks(file): par_params = ('added_space_bottom', 'added_space_top', 'align', 'labelwidthstring', 'line_bottom', 'line_top', 'noindent', 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing', 'start_of_appendix') font_attributes = ['\\family', '\\series', '\\shape', '\\emph', '\\numeric', '\\bar', '\\noun', '\\color', '\\lang'] attribute_values = ['default', 'default', 'default', 'default', 'default', 'default', 'default', 'none', file.language] i = 0 while 1: i = find_token(file.body, "\\begin_layout", i) if i == -1: return layout = split(file.body[i])[1] i = i + 1 # Merge all paragraph parameters into a single line # We cannot check for '\\' only because paragraphs may start e.g. # with '\\backslash' while file.body[i + 1][:1] == '\\' and split(file.body[i + 1][1:])[0] in par_params: file.body[i] = file.body[i + 1] + ' ' + file.body[i] del file.body[i+1] line_top = find(file.body[i],"\\line_top") line_bot = find(file.body[i],"\\line_bottom") pb_top = find(file.body[i],"\\pagebreak_top") pb_bot = find(file.body[i],"\\pagebreak_bottom") vspace_top = find(file.body[i],"\\added_space_top") vspace_bot = find(file.body[i],"\\added_space_bottom") if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1: continue # Do we have a nonstandard paragraph? We need to create new paragraphs # if yes to avoid putting lyxline etc. inside of special environments. # This is wrong for itemize and enumerate environments, but it is # impossible to convert these correctly. # We want to avoid new paragraphs if possible becauase we want to # inherit font sizes. nonstandard = 0 if (layout != "Standard" or find(file.body[i],"\\align") != -1 or find(file.body[i],"\\labelwidthstring") != -1 or find(file.body[i],"\\noindent") != -1): nonstandard = 1 # get the font size of the beginning of this paragraph, since we need # it for the lyxline inset j = i + 1 while not is_nonempty_line(file.body[j]): j = j + 1 size_top = "" if find(file.body[j], "\\size") != -1: size_top = split(file.body[j])[1] for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom": file.body[i] = replace(file.body[i], tag, "") if vspace_top != -1: # the position could be change because of the removal of other # paragraph properties above vspace_top = find(file.body[i],"\\added_space_top") tmp_list = split(file.body[i][vspace_top:]) vspace_top_value = tmp_list[1] file.body[i] = file.body[i][:vspace_top] + join(tmp_list[2:]) if vspace_bot != -1: # the position could be change because of the removal of other # paragraph properties above vspace_bot = find(file.body[i],"\\added_space_bottom") tmp_list = split(file.body[i][vspace_bot:]) vspace_bot_value = tmp_list[1] file.body[i] = file.body[i][:vspace_bot] + join(tmp_list[2:]) file.body[i] = strip(file.body[i]) i = i + 1 # Create an empty paragraph or paragraph fragment for line and # page break that belong above the paragraph if pb_top !=-1 or line_top != -1 or vspace_top != -1: paragraph_above = list() if nonstandard: # We need to create an extra paragraph for nonstandard environments paragraph_above = ['\\begin_layout Standard', ''] if pb_top != -1: paragraph_above.extend(['\\newpage ','']) if vspace_top != -1: paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','','']) if line_top != -1: if size_top != '': paragraph_above.extend(['\\size ' + size_top + ' ']) # We need an additional vertical space of -\parskip. # We can't use the vspace inset because it does not know \parskip. paragraph_above.extend(['\\lyxline ', '', '']) insert_ert(paragraph_above, len(paragraph_above) - 1, 'Collapsed', '\\vspace{-1\\parskip}\n', file.format + 1) paragraph_above.extend(['']) if nonstandard: paragraph_above.extend(['\\end_layout ','']) # insert new paragraph above the current paragraph file.body[i-2:i-2] = paragraph_above else: # insert new lines at the beginning of the current paragraph file.body[i:i] = paragraph_above i = i + len(paragraph_above) # Ensure that nested style are converted later. k = find_end_of(file.body, i, "\\begin_layout", "\\end_layout") if k == -1: return if pb_bot !=-1 or line_bot != -1 or vspace_bot != -1: # get the font size of the end of this paragraph size_bot = size_top j = i + 1 while j < k: if find(file.body[j], "\\size") != -1: size_bot = split(file.body[j])[1] j = j + 1 elif find(file.body[j], "\\begin_inset") != -1: # skip insets j = find_end_of_inset(file.body, j) else: j = j + 1 paragraph_below = list() if nonstandard: # We need to create an extra paragraph for nonstandard environments paragraph_below = ['', '\\begin_layout Standard', ''] else: for a in range(len(font_attributes)): if find_token(file.body, font_attributes[a], i, k) != -1: paragraph_below.extend([font_attributes[a] + ' ' + attribute_values[a]]) if line_bot != -1: if nonstandard and size_bot != '': paragraph_below.extend(['\\size ' + size_bot + ' ']) paragraph_below.extend(['\\lyxline ','']) if size_bot != '': paragraph_below.extend(['\\size default ']) if vspace_bot != -1: paragraph_below.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','','']) if pb_bot != -1: paragraph_below.extend(['\\newpage ','']) if nonstandard: paragraph_below.extend(['\\end_layout ']) # insert new paragraph below the current paragraph file.body[k+1:k+1] = paragraph_below else: # insert new lines at the end of the current paragraph file.body[k:k] = paragraph_below ## # Notes # def convert_note(file): i = 0 while 1: i = find_tokens(file.body, ["\\begin_inset Note", "\\begin_inset Comment", "\\begin_inset Greyedout"], i) if i == -1: break file.body[i] = file.body[i][0:13] + 'Note ' + file.body[i][13:] i = i + 1 def revert_note(file): note_header = "\\begin_inset Note " i = 0 while 1: i = find_token(file.body, note_header, i) if i == -1: break file.body[i] = "\\begin_inset " + file.body[i][len(note_header):] i = i + 1 ## # Box # def convert_box(file): i = 0 while 1: i = find_tokens(file.body, ["\\begin_inset Boxed", "\\begin_inset Doublebox", "\\begin_inset Frameless", "\\begin_inset ovalbox", "\\begin_inset Ovalbox", "\\begin_inset Shadowbox"], i) if i == -1: break file.body[i] = file.body[i][0:13] + 'Box ' + file.body[i][13:] i = i + 1 def revert_box(file): box_header = "\\begin_inset Box " i = 0 while 1: i = find_token(file.body, box_header, i) if i == -1: break file.body[i] = "\\begin_inset " + file.body[i][len(box_header):] i = i + 1 ## # Collapse # def convert_collapsable(file): i = 0 while 1: i = find_tokens(file.body, ["\\begin_inset Box", "\\begin_inset Branch", "\\begin_inset CharStyle", "\\begin_inset Float", "\\begin_inset Foot", "\\begin_inset Marginal", "\\begin_inset Note", "\\begin_inset OptArg", "\\begin_inset Wrap"], i) if i == -1: break # Seach for a line starting 'collapsed' # If, however, we find a line starting '\begin_layout' # (_always_ present) then break with a warning message i = i + 1 while 1: if (file.body[i] == "collapsed false"): file.body[i] = "status open" break elif (file.body[i] == "collapsed true"): file.body[i] = "status collapsed" break elif (file.body[i][:13] == "\\begin_layout"): file.warning("Malformed LyX file: Missing 'collapsed'.") break i = i + 1 i = i + 1 def revert_collapsable(file): i = 0 while 1: i = find_tokens(file.body, ["\\begin_inset Box", "\\begin_inset Branch", "\\begin_inset CharStyle", "\\begin_inset Float", "\\begin_inset Foot", "\\begin_inset Marginal", "\\begin_inset Note", "\\begin_inset OptArg", "\\begin_inset Wrap"], i) if i == -1: break # Seach for a line starting 'status' # If, however, we find a line starting '\begin_layout' # (_always_ present) then break with a warning message i = i + 1 while 1: if (file.body[i] == "status open"): file.body[i] = "collapsed false" break elif (file.body[i] == "status collapsed" or file.body[i] == "status inlined"): file.body[i] = "collapsed true" break elif (file.body[i][:13] == "\\begin_layout"): file.warning("Malformed LyX file: Missing 'status'.") break i = i + 1 i = i + 1 ## # ERT # def convert_ert(file): i = 0 while 1: i = find_token(file.body, "\\begin_inset ERT", i) if i == -1: break # Seach for a line starting 'status' # If, however, we find a line starting '\begin_layout' # (_always_ present) then break with a warning message i = i + 1 while 1: if (file.body[i] == "status Open"): file.body[i] = "status open" break elif (file.body[i] == "status Collapsed"): file.body[i] = "status collapsed" break elif (file.body[i] == "status Inlined"): file.body[i] = "status inlined" break elif (file.body[i][:13] == "\\begin_layout"): file.warning("Malformed LyX file: Missing 'status'.") break i = i + 1 i = i + 1 def revert_ert(file): i = 0 while 1: i = find_token(file.body, "\\begin_inset ERT", i) if i == -1: break # Seach for a line starting 'status' # If, however, we find a line starting '\begin_layout' # (_always_ present) then break with a warning message i = i + 1 while 1: if (file.body[i] == "status open"): file.body[i] = "status Open" break elif (file.body[i] == "status collapsed"): file.body[i] = "status Collapsed" break elif (file.body[i] == "status inlined"): file.body[i] = "status Inlined" break elif (file.body[i][:13] == "\\begin_layout"): file.warning("Malformed LyX file : Missing 'status'.") break i = i + 1 i = i + 1 ## # Minipages # def convert_minipage(file): """ Convert minipages to the box inset. We try to use the same order of arguments as lyx does. """ pos = ["t","c","b"] inner_pos = ["c","t","b","s"] i = 0 while 1: i = find_token(file.body, "\\begin_inset Minipage", i) if i == -1: return file.body[i] = "\\begin_inset Box Frameless" i = i + 1 # convert old to new position using the pos list if file.body[i][:8] == "position": file.body[i] = 'position "%s"' % pos[int(file.body[i][9])] else: file.body.insert(i, 'position "%s"' % pos[0]) i = i + 1 file.body.insert(i, 'hor_pos "c"') i = i + 1 file.body.insert(i, 'has_inner_box 1') i = i + 1 # convert the inner_position if file.body[i][:14] == "inner_position": file.body[i] = 'inner_pos "%s"' % inner_pos[int(file.body[i][15])] else: file.body.insert('inner_pos "%s"' % inner_pos[0]) i = i + 1 # We need this since the new file format has a height and width # in a different order. if file.body[i][:6] == "height": height = file.body[i][6:] # test for default value of 221 and convert it accordingly if height == ' "0pt"' or height == ' "0"': height = ' "1pt"' del file.body[i] else: height = ' "1pt"' if file.body[i][:5] == "width": width = file.body[i][5:] del file.body[i] else: width = ' "0"' if file.body[i][:9] == "collapsed": if file.body[i][9:] == "true": status = "collapsed" else: status = "open" del file.body[i] else: status = "collapsed" file.body.insert(i, 'use_parbox 0') i = i + 1 file.body.insert(i, 'width' + width) i = i + 1 file.body.insert(i, 'special "none"') i = i + 1 file.body.insert(i, 'height' + height) i = i + 1 file.body.insert(i, 'height_special "totalheight"') i = i + 1 file.body.insert(i, 'status ' + status) i = i + 1 # ------------------------------------------------------------------------------------------- # Convert backslashes and '\n' into valid ERT code, append the converted # text to body[i] and return the (maybe incremented) line index i def convert_ertbackslash(body, i, ert, format): for c in ert: if c == '\\': body[i] = body[i] + '\\backslash ' i = i + 1 body.insert(i, '') elif c == '\n': if format <= 240: body[i+1:i+1] = ['\\newline ', ''] i = i + 2 else: body[i+1:i+1] = ['\\end_layout', '', '\\begin_layout Standard', ''] i = i + 4 else: body[i] = body[i] + c return i # Converts lines in ERT code to LaTeX # The surrounding \begin_layout ... \end_layout pair must not be included def ert2latex(lines, format): backslash = re.compile(r'\\backslash\s*$') newline = re.compile(r'\\newline\s*$') if format <= 224: begin_layout = re.compile(r'\\layout\s*\S+$') else: begin_layout = re.compile(r'\\begin_layout\s*\S+$') end_layout = re.compile(r'\\end_layout\s*$') ert = '' for i in range(len(lines)): line = backslash.sub('\\\\', lines[i]) if format <= 240: if begin_layout.match(line): line = '\n\n' else: line = newline.sub('\n', line) else: if begin_layout.match(line): line = '\n' if format > 224 and end_layout.match(line): line = '' ert = ert + line return ert # get all paragraph parameters. They can be all on one line or on several lines. # lines[i] must be the first parameter line def get_par_params(lines, i): par_params = ('added_space_bottom', 'added_space_top', 'align', 'labelwidthstring', 'line_bottom', 'line_top', 'noindent', 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing', 'start_of_appendix') # We cannot check for '\\' only because paragraphs may start e.g. # with '\\backslash' params = '' while lines[i][:1] == '\\' and split(lines[i][1:])[0] in par_params: params = params + ' ' + strip(lines[i]) i = i + 1 return strip(params) # convert LyX font size to LaTeX fontsize def lyxsize2latexsize(lyxsize): sizes = {"tiny" : "tiny", "scriptsize" : "scriptsize", "footnotesize" : "footnotesize", "small" : "small", "normal" : "normalsize", "large" : "large", "larger" : "Large", "largest" : "LARGE", "huge" : "huge", "giant" : "Huge"} if lyxsize in sizes: return '\\' + sizes[lyxsize] return '' # Change vspace insets, page breaks and lyxlines to paragraph options # (if possible) or ERT def revert_breaks(file): # Get default spaceamount i = find_token(file.header, '\\defskip', 0) if i == -1: defskipamount = 'medskip' else: defskipamount = split(file.header[i])[1] keys = {"\\begin_inset" : "vspace", "\\lyxline" : "lyxline", "\\newpage" : "newpage"} keywords_top = {"vspace" : "\\added_space_top", "lyxline" : "\\line_top", "newpage" : "\\pagebreak_top"} keywords_bot = {"vspace" : "\\added_space_bottom", "lyxline" : "\\line_bottom", "newpage" : "\\pagebreak_bottom"} tokens = ["\\begin_inset VSpace", "\\lyxline", "\\newpage"] # Convert the insets i = 0 while 1: i = find_tokens(file.body, tokens, i) if i == -1: return # Are we at the beginning of a paragraph? paragraph_start = 1 this_par = get_paragraph(file.body, i, file.format - 1) start = this_par + 1 params = get_par_params(file.body, start) size = "normal" # Paragraph parameters may be on one or more lines. # Find the start of the real paragraph text. while file.body[start][:1] == '\\' and split(file.body[start])[0] in params: start = start + 1 for k in range(start, i): if find(file.body[k], "\\size") != -1: # store font size size = split(file.body[k])[1] elif is_nonempty_line(file.body[k]): paragraph_start = 0 break # Find the end of the real paragraph text. next_par = get_next_paragraph(file.body, i, file.format - 1) if next_par == -1: file.warning("Malformed LyX file: Missing next paragraph.") i = i + 1 continue # first line of our insets inset_start = i # last line of our insets inset_end = inset_start # Are we at the end of a paragraph? paragraph_end = 1 # start and end line numbers to delete if we convert this inset del_lines = list() # is this inset a lyxline above a paragraph? top = list() # raw inset information lines = list() # name of this inset insets = list() # font size of this inset sizes = list() # Detect subsequent lyxline, vspace and pagebreak insets created by convert_breaks() n = 0 k = inset_start while k < next_par: if find_tokens(file.body, tokens, k) == k: # inset to convert lines.append(split(file.body[k])) insets.append(keys[lines[n][0]]) del_lines.append([k, k]) top.append(0) sizes.append(size) n = n + 1 inset_end = k elif find(file.body[k], "\\size") != -1: # store font size size = split(file.body[k])[1] elif find_token(file.body, "\\begin_inset ERT", k) == k: ert_begin = find_token(file.body, "\\layout", k) + 1 if ert_begin == 0: file.warning("Malformed LyX file: Missing '\\layout'.") continue ert_end = find_end_of_inset(file.body, k) if ert_end == -1: file.warning("Malformed LyX file: Missing '\\end_inset'.") continue ert = ert2latex(file.body[ert_begin:ert_end], file.format - 1) if (n > 0 and insets[n - 1] == "lyxline" and ert == '\\vspace{-1\\parskip}\n'): # vspace ERT created by convert_breaks() for top lyxline top[n - 1] = 1 del_lines[n - 1][1] = ert_end inset_end = ert_end k = ert_end else: paragraph_end = 0 break elif (n > 0 and insets[n - 1] == "vspace" and find_token(file.body, "\\end_inset", k) == k): # ignore end of vspace inset del_lines[n - 1][1] = k inset_end = k elif is_nonempty_line(file.body[k]): paragraph_end = 0 break k = k + 1 # Determine space amount for vspace insets spaceamount = list() arguments = list() for k in range(n): if insets[k] == "vspace": spaceamount.append(lines[k][2]) arguments.append(' ' + spaceamount[k] + ' ') else: spaceamount.append('') arguments.append(' ') # Can we convert to top paragraph parameters? before = 0 if ((n == 3 and insets[0] == "newpage" and insets[1] == "vspace" and insets[2] == "lyxline" and top[2]) or (n == 2 and ((insets[0] == "newpage" and insets[1] == "vspace") or (insets[0] == "newpage" and insets[1] == "lyxline" and top[1]) or (insets[0] == "vspace" and insets[1] == "lyxline" and top[1]))) or (n == 1 and insets[0] == "lyxline" and top[0])): # These insets have been created before a paragraph by # convert_breaks() before = 1 # Can we convert to bottom paragraph parameters? after = 0 if ((n == 3 and insets[0] == "lyxline" and not top[0] and insets[1] == "vspace" and insets[2] == "newpage") or (n == 2 and ((insets[0] == "lyxline" and not top[0] and insets[1] == "vspace") or (insets[0] == "lyxline" and not top[0] and insets[1] == "newpage") or (insets[0] == "vspace" and insets[1] == "newpage"))) or (n == 1 and insets[0] == "lyxline" and not top[0])): # These insets have been created after a paragraph by # convert_breaks() after = 1 if paragraph_start and paragraph_end: # We are in a paragraph of our own. # We must not delete this paragraph if it has parameters if params == '': # First try to merge with the previous paragraph. # We try the previous paragraph first because we would # otherwise need ERT for two subsequent vspaces. prev_par = get_paragraph(file.body, this_par - 1, file.format - 1) + 1 if prev_par > 0 and not before: prev_params = get_par_params(file.body, prev_par + 1) ert = 0 # determine font size prev_size = "normal" k = prev_par + 1 while file.body[k][:1] == '\\' and split(file.body[k])[0] in prev_params: k = k + 1 while k < this_par: if find(file.body[k], "\\size") != -1: prev_size = split(file.body[k])[1] break elif find(file.body[k], "\\begin_inset") != -1: # skip insets k = find_end_of_inset(file.body, k) elif is_nonempty_line(file.body[k]): break k = k + 1 for k in range(n): if (keywords_bot[insets[k]] in prev_params or (insets[k] == "lyxline" and sizes[k] != prev_size)): ert = 1 break if not ert: for k in range(n): file.body.insert(prev_par + 1, keywords_bot[insets[k]] + arguments[k]) del file.body[this_par+n:next_par-1+n] i = this_par + n continue # Then try next paragraph if next_par > 0 and not after: next_params = get_par_params(file.body, next_par + 1) ert = 0 while file.body[k][:1] == '\\' and split(file.body[k])[0] in next_params: k = k + 1 # determine font size next_size = "normal" k = next_par + 1 while k < this_par: if find(file.body[k], "\\size") != -1: next_size = split(file.body[k])[1] break elif is_nonempty_line(file.body[k]): break k = k + 1 for k in range(n): if (keywords_top[insets[k]] in next_params or (insets[k] == "lyxline" and sizes[k] != next_size)): ert = 1 break if not ert: for k in range(n): file.body.insert(next_par + 1, keywords_top[insets[k]] + arguments[k]) del file.body[this_par:next_par-1] i = this_par continue elif paragraph_start or paragraph_end: # Convert to paragraph formatting if we are at the beginning or end # of a paragraph and the resulting paragraph would not be empty # The order is important: del and insert invalidate some indices if paragraph_start: keywords = keywords_top else: keywords = keywords_bot ert = 0 for k in range(n): if keywords[insets[k]] in params: ert = 1 break if not ert: for k in range(n): file.body.insert(this_par + 1, keywords[insets[k]] + arguments[k]) for j in range(k, n): del_lines[j][0] = del_lines[j][0] + 1 del_lines[j][1] = del_lines[j][1] + 1 del file.body[del_lines[k][0]:del_lines[k][1]+1] deleted = del_lines[k][1] - del_lines[k][0] + 1 for j in range(k + 1, n): del_lines[j][0] = del_lines[j][0] - deleted del_lines[j][1] = del_lines[j][1] - deleted i = this_par continue # Convert the first inset to ERT. # The others are converted in the next loop runs (if they exist) if insets[0] == "vspace": file.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '', '\\layout Standard', '', '\\backslash '] i = i + 6 if spaceamount[0][-1] == '*': spaceamount[0] = spaceamount[0][:-1] keep = 1 else: keep = 0 # Replace defskip by the actual value if spaceamount[0] == 'defskip': spaceamount[0] = defskipamount # LaTeX does not know \\smallskip* etc if keep: if spaceamount[0] == 'smallskip': spaceamount[0] = '\\smallskipamount' elif spaceamount[0] == 'medskip': spaceamount[0] = '\\medskipamount' elif spaceamount[0] == 'bigskip': spaceamount[0] = '\\bigskipamount' elif spaceamount[0] == 'vfill': spaceamount[0] = '\\fill' # Finally output the LaTeX code if (spaceamount[0] == 'smallskip' or spaceamount[0] == 'medskip' or spaceamount[0] == 'bigskip' or spaceamount[0] == 'vfill'): file.body.insert(i, spaceamount[0] + '{}') else : if keep: file.body.insert(i, 'vspace*{') else: file.body.insert(i, 'vspace{') i = convert_ertbackslash(file.body, i, spaceamount[0], file.format - 1) file.body[i] = file.body[i] + '}' i = i + 1 elif insets[0] == "lyxline": file.body[i] = '' latexsize = lyxsize2latexsize(size) if latexsize == '': file.warning("Could not convert LyX fontsize '%s' to LaTeX font size." % size) latexsize = '\\normalsize' i = insert_ert(file.body, i, 'Collapsed', '\\lyxline{%s}' % latexsize, file.format - 1) # We use \providecommand so that we don't get an error if native # lyxlines are used (LyX writes first its own preamble and then # the user specified one) add_to_preamble(file, ['% Commands inserted by lyx2lyx for lyxlines', '\\providecommand{\\lyxline}[1]{', ' {#1 \\vspace{1ex} \\hrule width \\columnwidth \\vspace{1ex}}' '}']) elif insets[0] == "newpage": file.body[i] = '' i = insert_ert(file.body, i, 'Collapsed', '\\newpage{}', file.format - 1) # Convert a LyX length into a LaTeX length def convert_len(len, special): units = {"text%":"\\textwidth", "col%":"\\columnwidth", "page%":"\\pagewidth", "line%":"\\linewidth", "theight%":"\\textheight", "pheight%":"\\pageheight"} # Convert special lengths if special != 'none': len = '%f\\' % len2value(len) + special # Convert LyX units to LaTeX units for unit in units.keys(): if find(len, unit) != -1: len = '%f' % (len2value(len) / 100) + units[unit] break return len # Convert a LyX length into valid ERT code and append it to body[i] # Return the (maybe incremented) line index i def convert_ertlen(body, i, len, special, format): # Convert backslashes and insert the converted length into body return convert_ertbackslash(body, i, convert_len(len, special), format) # Return the value of len without the unit in numerical form def len2value(len): result = re.search('([+-]?[0-9.]+)', len) if result: return float(result.group(1)) # No number means 1.0 return 1.0 # Convert text to ERT and insert it at body[i] # Return the index of the line after the inserted ERT def insert_ert(body, i, status, text, format): body[i:i] = ['\\begin_inset ERT', 'status ' + status, ''] i = i + 3 if format <= 224: body[i:i] = ['\\layout Standard', ''] else: body[i:i] = ['\\begin_layout Standard', ''] i = i + 1 # i points now to the just created empty line i = convert_ertbackslash(body, i, text, format) + 1 if format > 224: body[i:i] = ['\\end_layout'] i = i + 1 body[i:i] = ['', '\\end_inset', ''] i = i + 3 return i # Add text to the preamble if it is not already there. # Only the first line is checked! def add_to_preamble(file, text): if find_token(file.preamble, text[0], 0) != -1: return file.preamble.extend(text) def convert_frameless_box(file): pos = ['t', 'c', 'b'] inner_pos = ['c', 't', 'b', 's'] i = 0 while 1: i = find_token(file.body, '\\begin_inset Frameless', i) if i == -1: return j = find_end_of_inset(file.body, i) if j == -1: file.warning("Malformed LyX file: Missing '\\end_inset'.") i = i + 1 continue del file.body[i] j = j - 1 # Gather parameters params = {'position':0, 'hor_pos':'c', 'has_inner_box':'1', 'inner_pos':1, 'use_parbox':'0', 'width':'100col%', 'special':'none', 'height':'1in', 'height_special':'totalheight', 'collapsed':'false'} for key in params.keys(): value = replace(get_value(file.body, key, i, j), '"', '') if value != "": if key == 'position': # convert new to old position: 'position "t"' -> 0 value = find_token(pos, value, 0) if value != -1: params[key] = value elif key == 'inner_pos': # convert inner position value = find_token(inner_pos, value, 0) if value != -1: params[key] = value else: params[key] = value j = del_token(file.body, key, i, j) i = i + 1 # Convert to minipage or ERT? # Note that the inner_position and height parameters of a minipage # inset are ignored and not accessible for the user, although they # are present in the file format and correctly read in and written. # Therefore we convert to ERT if they do not have their LaTeX # defaults. These are: # - the value of "position" for "inner_pos" # - "\totalheight" for "height" if (params['use_parbox'] != '0' or params['has_inner_box'] != '1' or params['special'] != 'none' or params['height_special'] != 'totalheight' or len2value(params['height']) != 1.0): # Here we know that this box is not supported in file format 224. # Therefore we need to convert it to ERT. We can't simply convert # the beginning and end of the box to ERT, because the # box inset may contain layouts that are different from the # surrounding layout. After the conversion the contents of the # box inset is on the same level as the surrounding text, and # paragraph layouts and align parameters can get mixed up. # A possible solution for this problem: # Convert the box to a minipage and redefine the minipage # environment in ERT so that the original box is simulated. # For minipages we could do this in a way that the width and # position can still be set from LyX, but this did not work well. # This is not possible for parboxes either, so we convert the # original box to ERT, put the minipage inset inside the box # and redefine the minipage environment to be empty. # Commands that are independant of a particular box can go to # the preamble. # We need to define lyxtolyxrealminipage with 3 optional # arguments although LyX 1.3 uses only the first one. # Otherwise we will get LaTeX errors if this document is # converted to format 225 or above again (LyX 1.4 uses all # optional arguments). add_to_preamble(file, ['% Commands inserted by lyx2lyx for frameless boxes', '% Save the original minipage environment', '\\let\\lyxtolyxrealminipage\\minipage', '\\let\\endlyxtolyxrealminipage\\endminipage', '% Define an empty lyxtolyximinipage environment', '% with 3 optional arguments', '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}', '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%', ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%', ' {\\end{lyxtolyxiiiminipage}}', '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%', ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%', ' {\\end{lyxtolyxiiminipage}}', '\\newenvironment{lyxtolyxminipage}[1][c]%', ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}', ' {\\end{lyxtolyximinipage}}']) if params['use_parbox'] != '0': ert = '\\parbox' else: ert = '\\begin{lyxtolyxrealminipage}' # convert optional arguments only if not latex default if (pos[params['position']] != 'c' or inner_pos[params['inner_pos']] != pos[params['position']] or params['height_special'] != 'totalheight' or len2value(params['height']) != 1.0): ert = ert + '[' + pos[params['position']] + ']' if (inner_pos[params['inner_pos']] != pos[params['position']] or params['height_special'] != 'totalheight' or len2value(params['height']) != 1.0): ert = ert + '[' + convert_len(params['height'], params['height_special']) + ']' if inner_pos[params['inner_pos']] != pos[params['position']]: ert = ert + '[' + inner_pos[params['inner_pos']] + ']' ert = ert + '{' + convert_len(params['width'], params['special']) + '}' if params['use_parbox'] != '0': ert = ert + '{' ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n' ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n' old_i = i i = insert_ert(file.body, i, 'Collapsed', ert, file.format + 1) j = j + i - old_i - 1 file.body[i:i] = ['\\begin_inset Minipage', 'position %d' % params['position'], 'inner_position 1', 'height "1in"', 'width "' + params['width'] + '"', 'collapsed ' + params['collapsed']] i = i + 6 j = j + 6 # Restore the original minipage environment since we may have # minipages inside this box. # Start a new paragraph because the following may be nonstandard file.body[i:i] = ['\\layout Standard', '', ''] i = i + 2 j = j + 3 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n' ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%' old_i = i i = insert_ert(file.body, i, 'Collapsed', ert, file.format + 1) j = j + i - old_i - 1 # Redefine the minipage end before the inset end. # Start a new paragraph because the previous may be nonstandard file.body[j:j] = ['\\layout Standard', '', ''] j = j + 2 ert = '\\let\\endminipage\\endlyxtolyxminipage' j = insert_ert(file.body, j, 'Collapsed', ert, file.format + 1) j = j + 1 file.body.insert(j, '') j = j + 1 # LyX writes '%\n' after each box. Therefore we need to end our # ERT with '%\n', too, since this may swallow a following space. if params['use_parbox'] != '0': ert = '}%\n' else: ert = '\\end{lyxtolyxrealminipage}%\n' j = insert_ert(file.body, j, 'Collapsed', ert, file.format + 1) # We don't need to restore the original minipage after the inset # end because the scope of the redefinition is the original box. else: # Convert to minipage file.body[i:i] = ['\\begin_inset Minipage', 'position %d' % params['position'], 'inner_position %d' % params['inner_pos'], 'height "' + params['height'] + '"', 'width "' + params['width'] + '"', 'collapsed ' + params['collapsed']] i = i + 6 ## # Convert jurabib # def convert_jurabib(file): i = find_token(file.header, '\\use_numerical_citations', 0) if i == -1: file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.") return file.header.insert(i + 1, '\\use_jurabib 0') def revert_jurabib(file): i = find_token(file.header, '\\use_jurabib', 0) if i == -1: file.warning("Malformed lyx file: Missing '\\use_jurabib'.") return if get_value(file.header, '\\use_jurabib', 0) != "0": file.warning("Conversion of '\\use_jurabib = 1' not yet implemented.") # Don't remove '\\use_jurabib' so that people will get warnings by lyx return del file.header[i] ## # Convert bibtopic # def convert_bibtopic(file): i = find_token(file.header, '\\use_jurabib', 0) if i == -1: file.warning("Malformed lyx file: Missing '\\use_jurabib'.") return file.header.insert(i + 1, '\\use_bibtopic 0') def revert_bibtopic(file): i = find_token(file.header, '\\use_bibtopic', 0) if i == -1: file.warning("Malformed lyx file: Missing '\\use_bibtopic'.") return if get_value(file.header, '\\use_bibtopic', 0) != "0": file.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.") # Don't remove '\\use_jurabib' so that people will get warnings by lyx del file.header[i] ## # Sideway Floats # def convert_float(file): i = 0 while 1: i = find_token(file.body, '\\begin_inset Float', i) if i == -1: return # Seach for a line starting 'wide' # If, however, we find a line starting '\begin_layout' # (_always_ present) then break with a warning message i = i + 1 while 1: if (file.body[i][:4] == "wide"): file.body.insert(i + 1, 'sideways false') break elif (file.body[i][:13] == "\\begin_layout"): file.warning("Malformed lyx file: Missing 'wide'.") break i = i + 1 i = i + 1 def revert_float(file): i = 0 while 1: i = find_token(file.body, '\\begin_inset Float', i) if i == -1: return j = find_end_of_inset(file.body, i) if j == -1: file.warning("Malformed lyx file: Missing '\\end_inset'.") i = i + 1 continue if get_value(file.body, 'sideways', i, j) != "false": file.warning("Conversion of 'sideways true' not yet implemented.") # Don't remove 'sideways' so that people will get warnings by lyx i = i + 1 continue del_token(file.body, 'sideways', i, j) i = i + 1 def convert_graphics(file): """ Add extension to filenames of insetgraphics if necessary. """ i = 0 while 1: i = find_token(file.body, "\\begin_inset Graphics", i) if i == -1: return j = find_token2(file.body, "filename", i) if j == -1: return i = i + 1 filename = split(file.body[j])[1] absname = os.path.normpath(os.path.join(file.dir, filename)) if file.input == stdin and not os.path.isabs(filename): # We don't know the directory and cannot check the file. # We could use a heuristic and take the current directory, # and we could try to find out if filename has an extension, # but that would be just guesses and could be wrong. file.warning("""Warning: Can not determine whether file %s needs an extension when reading from standard input. You may need to correct the file manually or run lyx2lyx again with the .lyx file as commandline argument.""" % filename) continue # This needs to be the same algorithm as in pre 233 insetgraphics if access(absname, F_OK): continue if access(absname + ".ps", F_OK): file.body[j] = replace(file.body[j], filename, filename + ".ps") continue if access(absname + ".eps", F_OK): file.body[j] = replace(file.body[j], filename, filename + ".eps") ## # Convert firstname and surname from styles -> char styles # def convert_names(file): """ Convert in the docbook backend from firstname and surname style to charstyles. """ if file.backend != "docbook": return i = 0 while 1: i = find_token(file.body, "\\begin_layout Author", i) if i == -1: return i = i + 1 while file.body[i] == "": i = i + 1 if file.body[i][:11] != "\\end_layout" or file.body[i+2][:13] != "\\begin_deeper": i = i + 1 continue k = i i = find_end_of( file.body, i+3, "\\begin_deeper","\\end_deeper") if i == -1: # something is really wrong, abort file.warning("Missing \\end_deeper, after style Author.") file.warning("Aborted attempt to parse FirstName and Surname.") return firstname, surname = "", "" name = file.body[k:i] j = find_token(name, "\\begin_layout FirstName", 0) if j != -1: j = j + 1 while(name[j] != "\\end_layout"): firstname = firstname + name[j] j = j + 1 j = find_token(name, "\\begin_layout Surname", 0) if j != -1: j = j + 1 while(name[j] != "\\end_layout"): surname = surname + name[j] j = j + 1 # delete name del file.body[k+2:i+1] file.body[k-1:k-1] = ["", "", "\\begin_inset CharStyle Firstname", "status inlined", "", "\\begin_layout Standard", "", "%s" % firstname, "\end_layout", "", "\end_inset", "", "", "\\begin_inset CharStyle Surname", "status inlined", "", "\\begin_layout Standard", "", "%s" % surname, "\\end_layout", "", "\\end_inset", ""] def revert_names(file): """ Revert in the docbook backend from firstname and surname char style to styles. """ if file.backend != "docbook": return ## # \use_natbib 1 \cite_engine