See http://bugzilla.lyx.org/show_bug.cgi?id=1990. lyx2lyx does not revert pagebreak and lyxline insets and can therefore create invalid .lyx files. The attached patch fixes that by extending convert_vspace() to revert_breaks(), making it symmetric to convert_breaks(). I have tested it with file attached to the bug report. The test file contains all cases that I can think of, and it works.
Lars, can I commit this? Georg
diff -p -r -U 3 -X excl.tmp lyx-1.4-clean/lib/lyx2lyx/ChangeLog lyx-1.4-cvs/lib/lyx2lyx/ChangeLog --- lyx-1.4-clean/lib/lyx2lyx/ChangeLog 2005-08-19 10:47:40.000000000 +0200 +++ lyx-1.4-cvs/lib/lyx2lyx/ChangeLog 2005-08-28 17:28:20.000000000 +0200 @@ -1,3 +1,8 @@ +2005-08-28 Georg Baum <[EMAIL PROTECTED]> + + * lyx_1_4.py (convert_vspace): rename to revert_breaks and revert + \lyxline and \newpage, too + 2005-08-18 José Matos <[EMAIL PROTECTED]> * LyX.py (read, write): add preamble as data member of LyX_Base, diff -p -r -U 3 -X excl.tmp lyx-1.4-clean/lib/lyx2lyx/lyx_1_4.py lyx-1.4-cvs/lib/lyx2lyx/lyx_1_4.py --- lyx-1.4-clean/lib/lyx2lyx/lyx_1_4.py 2005-08-19 10:47:41.000000000 +0200 +++ lyx-1.4-cvs/lib/lyx2lyx/lyx_1_4.py 2005-08-28 17:22:58.000000000 +0200 @@ -907,92 +907,258 @@ def convert_ertbackslash(body, i, ert): return i -def convert_vspace(file): +# 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) + + +# 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' + defskipamount = 'medskip' else: - defskipamount = split(file.header[i])[1] + 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_token(file.body, '\\begin_inset VSpace', i) + i = find_tokens(file.body, tokens, i) if i == -1: return - spaceamount = split(file.body[i])[2] + lines = list() + insets = list() + lines.append(split(file.body[i])) + insets.append(keys[lines[0][0]]) + + # Are we at the beginning of a paragraph? + paragraph_start = 1 + start = get_paragraph(file.body, i) + 1 + params = get_par_params(file.body, start) + # 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 is_nonempty_line(file.body[k]): + paragraph_start = 0 + break + + # Are we at the end of a paragraph? + paragraph_end = 1 + if insets[0] == "vspace": + j = find_end_of_inset(file.body, i) + if j == -1: + file.warning("Malformed lyx file: Missing '\\end_inset'.") + i = i + 1 + continue + else: + j = i + end = get_next_paragraph(file.body, i) + for k in range(j + 1, end): + if is_nonempty_line(file.body[k]): + paragraph_end = 0 + break + + # Detect paragraphs created by convert_breaks() + before = 0 + after = 0 + if paragraph_start: + # Make a copy of the paragraph, since we need to strip empty lines + paragraph = list() + for k in range(start, end): + if is_nonempty_line(file.body[k]): + if (len(paragraph) > 0 and + find_token(file.body, "\\end_inset", k) == k): + paragraph[len(paragraph) - 1] = paragraph[len(paragraph) - 1] + ' ' + file.body[k] + else: + paragraph.append(file.body[k]) + + # Do we have a second inset to convert in this paragraph? + if ((len(paragraph) == 2 or len(paragraph) == 3) and + find_tokens(paragraph, tokens, 1) == 1): + lines.append(split(paragraph[1])) + insets.append(keys[lines[1][0]]) + del paragraph[1] + # Do we have a third inset to convert in this paragraph? + if (len(paragraph) == 2 and + find_tokens(paragraph, tokens, 1) == 1): + lines.append(split(paragraph[1])) + insets.append(keys[lines[2][0]]) + del paragraph[1] + + if len(paragraph) == 1: + # We have only insets that need to be converted in this paragraph + if ((len(insets) == 3 and insets[0] == "newpage" and + insets[1] == "vspace" and insets[2] == "lyxline") or + (len(insets) == 2 and + ((insets[0] == "newpage" and insets[1] == "vspace") or + (insets[0] == "newpage" and insets[1] == "lyxline") or + (insets[0] == "vspace" and insets[1] == "lyxline")))): + # This paragraph has been created before a paragraph by + # convert_breaks() + before = 1 + paragraph_start = 1 + paragraph_end = 1 + if ((len(insets) == 3 and insets[0] == "lyxline" and + insets[1] == "vspace" and insets[2] == "newpage") or + (len(insets) == 2 and + ((insets[0] == "lyxline" and insets[1] == "vspace") or + (insets[0] == "lyxline" and insets[1] == "newpage") or + (insets[0] == "vspace" and insets[1] == "newpage")))): + # This paragraph has been created after a paragraph by + # convert_breaks() + paragraph_start = 1 + paragraph_end = 1 + after = 1 + + # Determine space amount for vspace insets + spaceamount = list() + arguments = list() + for k in range(len(lines)): + if insets[k] == "vspace": + spaceamount.append(lines[k][2]) + arguments.append(' ' + spaceamount[k] + ' ') + else: + spaceamount.append('') + arguments.append(' ') - # Are we at the beginning or end of a paragraph? - paragraph_start = 1 - start = get_paragraph(file.body, i) + 1 - for k in range(start, i): - if is_nonempty_line(file.body[k]): - paragraph_start = 0 - break - paragraph_end = 1 - j = find_end_of_inset(file.body, i) - if j == -1: - file.warning("Malformed LyX file: Missing '\\end_inset'.") - i = i + 1 - continue - end = get_next_paragraph(file.body, i) - for k in range(j + 1, end): - if is_nonempty_line(file.body[k]): - paragraph_end = 0 - break - - # Convert to paragraph formatting if we are at the beginning or end - # of a paragraph and the resulting paragraph would not be empty - if ((paragraph_start and not paragraph_end) or - (paragraph_end and not paragraph_start)): - # The order is important: del and insert invalidate some indices - del file.body[j] - del file.body[i] - if paragraph_start: - file.body.insert(start, '\\added_space_top ' + spaceamount + ' ') - else: - file.body.insert(start, '\\added_space_bottom ' + spaceamount + ' ') - continue + 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, start - 2) + 1 + if prev_par > 0 and not before: + prev_params = get_par_params(file.body, prev_par + 1) + ert = 0 + n = len(insets) + for k in range(n): + if keywords_bot[insets[k]] in prev_params: + ert = 1 + break + if not ert: + for k in range(len(insets)): + file.body.insert(prev_par + 1, + keywords_bot[insets[k]] + arguments[k]) + del file.body[start-1+n:end+n] + i = start + n + continue + # Then try next paragraph + next_par = end + if next_par > 0 and not after: + next_params = get_par_params(file.body, next_par + 1) + ert = 0 + n = len(insets) + for k in range(n): + if keywords_top[insets[k]] in next_params: + ert = 1 + break + if not ert: + for k in range(len(insets)): + file.body.insert(next_par + 1, + keywords_top[insets[k]] + arguments[k]) + del file.body[start-1:end] + i = start + continue + else: + # 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: + if j != i: + del file.body[j] + del file.body[i] + file.body.insert(start, keywords_top[insets[0]] + arguments[0]) + continue + elif paragraph_end: + if j != i: + del file.body[j] + del file.body[i] + file.body.insert(start, keywords_bot[insets[0]] + arguments[0]) + continue - # Convert to ERT - file.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '', - '\\layout Standard', '', '\\backslash '] - i = i + 6 - if spaceamount[-1] == '*': - spaceamount = spaceamount[:-1] - keep = 1 - else: - keep = 0 + # 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 == 'defskip': - spaceamount = defskipamount - - # LaTeX does not know \\smallskip* etc - if keep: - if spaceamount == 'smallskip': - spaceamount = '\\smallskipamount' - elif spaceamount == 'medskip': - spaceamount = '\\medskipamount' - elif spaceamount == 'bigskip': - spaceamount = '\\bigskipamount' - elif spaceamount == 'vfill': - spaceamount = '\\fill' - - # Finally output the LaTeX code - if (spaceamount == 'smallskip' or spaceamount == 'medskip' or - spaceamount == 'bigskip' or spaceamount == 'vfill'): - file.body.insert(i, spaceamount) - else : - if keep: - file.body.insert(i, 'vspace*{') - else: - file.body.insert(i, 'vspace{') - i = convert_ertbackslash(file.body, i, spaceamount) - file.body[i] = file.body[i] + '}' - i = i + 1 + # 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.body[i] = file.body[i] + '}' + i = i + 1 + elif insets[0] == "lyxline": + file.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '', + '\\layout Standard', '', '\\backslash', + 'lyxline{}', '', '\\end_inset', ''] + i = i + 10 + # 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+1] = ['\\begin_inset ERT', 'status Collapsed', '', + '\\layout Standard', '', '\\backslash', + 'newpage{}', '', '\\end_inset', ''] + i = i + 10 # Convert a LyX length into a LaTeX length