1 # This file is part of lyx2lyx
2 # -*- coding: iso-8859-1 -*-
3 # Copyright (C) 2002 Dekel Tsur <dekel@lyx.org>
4 # Copyright (C) 2002-2004 José Matos <jamatos@lyx.org>
5 # Copyright (C) 2004-2005 Georg Baum <Georg.Baum@post.rwth-aachen.de>
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 from os import access, F_OK
24 from parser_tools import find_token, find_end_of_inset, get_next_paragraph, \
25 get_paragraph, get_value, del_token, is_nonempty_line,\
26 find_tokens, find_end_of, find_token2, find_re,\
29 from string import replace, split, find, strip, join
31 from lyx_0_12 import update_latexaccents
34 # Remove \color default
36 def remove_color_default(file):
39 i = find_token(file.body, "\\color default", i)
42 file.body[i] = replace(file.body[i], "\\color default",
49 def add_end_header(file):
50 file.header.append("\\end_header");
53 def rm_end_header(file):
54 i = find_token(file.header, "\\end_header", 0)
61 # \SpecialChar ~ -> \InsetSpace ~
63 def convert_spaces(file):
64 for i in range(len(file.body)):
65 file.body[i] = replace(file.body[i],"\\SpecialChar ~","\\InsetSpace ~")
68 def revert_spaces(file):
69 regexp = re.compile(r'(.*)(\\InsetSpace\s+)(\S+)')
72 i = find_re(file.body, regexp, i)
75 space = regexp.match(file.body[i]).group(3)
76 prepend = regexp.match(file.body[i]).group(1)
78 file.body[i] = regexp.sub(prepend + '\\SpecialChar ~', file.body[i])
81 file.body[i] = regexp.sub(prepend, file.body[i])
82 file.body[i+1:i+1] = ''
83 if space == "\\space":
85 i = insert_ert(file.body, i+1, 'Collapsed', space, file.format - 1, file.default_layout)
88 # \InsetSpace \, -> \InsetSpace \thinspace{}
89 # \InsetSpace \space -> \InsetSpace \space{}
91 def rename_spaces(file):
92 for i in range(len(file.body)):
93 file.body[i] = replace(file.body[i],"\\InsetSpace \\space","\\InsetSpace \\space{}")
94 file.body[i] = replace(file.body[i],"\\InsetSpace \,","\\InsetSpace \\thinspace{}")
97 def revert_space_names(file):
98 for i in range(len(file.body)):
99 file.body[i] = replace(file.body[i],"\\InsetSpace \\space{}","\\InsetSpace \\space")
100 file.body[i] = replace(file.body[i],"\\InsetSpace \\thinspace{}","\\InsetSpace \\,")
104 # equivalent to lyx::support::escape()
106 def lyx_support_escape(lab):
107 hexdigit = ['0', '1', '2', '3', '4', '5', '6', '7',
108 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
112 if o >= 128 or c == '=' or c == '%':
114 enc = enc + hexdigit[o >> 4]
115 enc = enc + hexdigit[o & 15]
122 # \begin_inset LatexCommand \eqref -> ERT
124 def revert_eqref(file):
125 regexp = re.compile(r'^\\begin_inset\s+LatexCommand\s+\\eqref')
128 i = find_re(file.body, regexp, i)
131 eqref = lyx_support_escape(regexp.sub("", file.body[i]))
132 file.body[i:i+1] = ["\\begin_inset ERT", "status Collapsed", "",
133 '\\layout %s' % file.default_layout, "", "\\backslash ",
141 def convert_bibtex(file):
142 for i in range(len(file.body)):
143 file.body[i] = replace(file.body[i],"\\begin_inset LatexCommand \\BibTeX",
144 "\\begin_inset LatexCommand \\bibtex")
147 def revert_bibtex(file):
148 for i in range(len(file.body)):
149 file.body[i] = replace(file.body[i], "\\begin_inset LatexCommand \\bibtex",
150 "\\begin_inset LatexCommand \\BibTeX")
156 def remove_insetparent(file):
159 i = find_token(file.body, "\\begin_inset LatexCommand \\lyxparent", i)
168 def convert_external(file):
169 external_rexp = re.compile(r'\\begin_inset External ([^,]*),"([^"]*)",')
170 external_header = "\\begin_inset External"
173 i = find_token(file.body, external_header, i)
176 look = external_rexp.search(file.body[i])
179 args[0] = look.group(1)
180 args[1] = look.group(2)
181 #FIXME: if the previous search fails then warn
183 if args[0] == "RasterImage":
184 # Convert a RasterImage External Inset to a Graphics Inset.
185 top = "\\begin_inset Graphics"
187 filename = "\tfilename " + args[1]
188 file.body[i:i+1] = [top, filename]
191 # Convert the old External Inset format to the new.
192 top = external_header
193 template = "\ttemplate " + args[0]
195 filename = "\tfilename " + args[1]
196 file.body[i:i+1] = [top, template, filename]
199 file.body[i:i+1] = [top, template]
203 def revert_external_1(file):
204 external_header = "\\begin_inset External"
207 i = find_token(file.body, external_header, i)
211 template = split(file.body[i+1])
215 filename = split(file.body[i+1])
219 params = split(file.body[i+1])
221 if file.body[i+1]: del file.body[i+1]
223 file.body[i] = file.body[i] + " " + template[0]+ ', "' + filename[0] + '", " '+ join(params[1:]) + '"'
227 def revert_external_2(file):
228 draft_token = '\tdraft'
231 i = find_token(file.body, '\\begin_inset External', i)
234 j = find_end_of_inset(file.body, i + 1)
236 #this should not happen
238 k = find_token(file.body, draft_token, i+1, j-1)
239 if (k != -1 and len(draft_token) == len(file.body[k])):
247 def convert_comment(file):
249 comment = "\\layout Comment"
251 i = find_token(file.body, comment, i)
255 file.body[i:i+1] = ['\\layout %s' % file.default_layout,"","",
256 "\\begin_inset Comment",
258 '\\layout %s' % file.default_layout]
263 i = find_token(file.body, "\\layout", i)
265 i = len(file.body) - 1
266 file.body[i:i] = ["\\end_inset","",""]
269 j = find_token(file.body, '\\begin_deeper', old_i, i)
270 if j == -1: j = i + 1
271 k = find_token(file.body, '\\begin_inset', old_i, i)
272 if k == -1: k = i + 1
277 i = find_end_of( file.body, i, "\\begin_deeper","\\end_deeper")
279 #This case should not happen
280 #but if this happens deal with it greacefully adding
281 #the missing \end_deeper.
282 i = len(file.body) - 1
283 file.body[i:i] = ["\end_deeper",""]
291 i = find_end_of( file.body, i, "\\begin_inset","\\end_inset")
293 #This case should not happen
294 #but if this happens deal with it greacefully adding
295 #the missing \end_inset.
296 i = len(file.body) - 1
297 file.body[i:i] = ["\\end_inset","","","\\end_inset","",""]
303 if find(file.body[i], comment) == -1:
304 file.body[i:i] = ["\\end_inset"]
307 file.body[i:i+1] = ['\\layout %s' % file.default_layout]
311 def revert_comment(file):
314 i = find_tokens(file.body, ["\\begin_inset Comment", "\\begin_inset Greyedout"], i)
318 file.body[i] = "\\begin_inset Note"
325 def add_end_layout(file):
326 i = find_token(file.body, '\\layout', 0)
332 struct_stack = ["\\layout"]
335 i = find_tokens(file.body, ["\\begin_inset", "\\end_inset", "\\layout",
336 "\\begin_deeper", "\\end_deeper", "\\the_end"], i)
339 token = split(file.body[i])[0]
341 file.warning("Truncated file.")
343 file.body.insert(i, '\\the_end')
346 if token == "\\begin_inset":
347 struct_stack.append(token)
351 if token == "\\end_inset":
352 tail = struct_stack.pop()
353 if tail == "\\layout":
354 file.body.insert(i,"")
355 file.body.insert(i,"\\end_layout")
357 #Check if it is the correct tag
362 if token == "\\layout":
363 tail = struct_stack.pop()
365 file.body.insert(i,"")
366 file.body.insert(i,"\\end_layout")
369 struct_stack.append(tail)
371 struct_stack.append(token)
374 if token == "\\begin_deeper":
375 file.body.insert(i,"")
376 file.body.insert(i,"\\end_layout")
378 struct_stack.append(token)
381 if token == "\\end_deeper":
382 if struct_stack[-1] == '\\layout':
383 file.body.insert(i, '\\end_layout')
390 file.body.insert(i, "")
391 file.body.insert(i, "\\end_layout")
395 def rm_end_layout(file):
398 i = find_token(file.body, '\\end_layout', i)
407 # Handle change tracking keywords
409 def insert_tracking_changes(file):
410 i = find_token(file.header, "\\tracking_changes", 0)
412 file.header.append("\\tracking_changes 0")
415 def rm_tracking_changes(file):
416 i = find_token(file.header, "\\author", 0)
420 i = find_token(file.header, "\\tracking_changes", 0)
426 def rm_body_changes(file):
429 i = find_token(file.body, "\\change_", i)
437 # \layout -> \begin_layout
439 def layout2begin_layout(file):
442 i = find_token(file.body, '\\layout', i)
446 file.body[i] = replace(file.body[i], '\\layout', '\\begin_layout')
450 def begin_layout2layout(file):
453 i = find_token(file.body, '\\begin_layout', i)
457 file.body[i] = replace(file.body[i], '\\begin_layout', '\\layout')
462 # valignment="center" -> valignment="middle"
464 def convert_valignment_middle(body, start, end):
465 for i in range(start, end):
466 if re.search('^<(column|cell) .*valignment="center".*>$', body[i]):
467 body[i] = replace(body[i], 'valignment="center"', 'valignment="middle"')
470 def convert_table_valignment_middle(file):
471 regexp = re.compile(r'^\\begin_inset\s+Tabular')
474 i = find_re(file.body, regexp, i)
477 j = find_end_of_inset(file.body, i + 1)
479 #this should not happen
480 convert_valignment_middle(file.body, i + 1, len(file.body))
482 convert_valignment_middle(file.body, i + 1, j)
486 def revert_table_valignment_middle(body, start, end):
487 for i in range(start, end):
488 if re.search('^<(column|cell) .*valignment="middle".*>$', body[i]):
489 body[i] = replace(body[i], 'valignment="middle"', 'valignment="center"')
492 def revert_valignment_middle(file):
493 regexp = re.compile(r'^\\begin_inset\s+Tabular')
496 i = find_re(file.body, regexp, i)
499 j = find_end_of_inset(file.body, i + 1)
501 #this should not happen
502 revert_table_valignment_middle(file.body, i + 1, len(file.body))
504 revert_table_valignment_middle(file.body, i + 1, j)
509 # \the_end -> \end_document
511 def convert_end_document(file):
512 i = find_token(file.body, "\\the_end", 0)
514 file.body.append("\\end_document")
516 file.body[i] = "\\end_document"
519 def revert_end_document(file):
520 i = find_token(file.body, "\\end_document", 0)
522 file.body.append("\\the_end")
524 file.body[i] = "\\the_end"
528 # Convert line and page breaks
531 #\line_top \line_bottom \pagebreak_top \pagebreak_bottom \added_space_top xxx \added_space_bottom yyy
535 #\begin layout Standard
541 #\begin layout Standard
548 #\begin_inset VSpace xxx
553 #\begin_inset VSpace xxx
560 def convert_breaks(file):
561 par_params = ('added_space_bottom', 'added_space_top', 'align',
562 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
563 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
565 font_attributes = ['\\family', '\\series', '\\shape', '\\emph',
566 '\\numeric', '\\bar', '\\noun', '\\color', '\\lang']
567 attribute_values = ['default', 'default', 'default', 'default',
568 'default', 'default', 'default', 'none', file.language]
571 i = find_token(file.body, "\\begin_layout", i)
574 layout = get_layout(file.body[i], file.default_layout)
577 # Merge all paragraph parameters into a single line
578 # We cannot check for '\\' only because paragraphs may start e.g.
580 while file.body[i + 1][:1] == '\\' and split(file.body[i + 1][1:])[0] in par_params:
581 file.body[i] = file.body[i + 1] + ' ' + file.body[i]
584 line_top = find(file.body[i],"\\line_top")
585 line_bot = find(file.body[i],"\\line_bottom")
586 pb_top = find(file.body[i],"\\pagebreak_top")
587 pb_bot = find(file.body[i],"\\pagebreak_bottom")
588 vspace_top = find(file.body[i],"\\added_space_top")
589 vspace_bot = find(file.body[i],"\\added_space_bottom")
591 if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1:
594 # Do we have a nonstandard paragraph? We need to create new paragraphs
595 # if yes to avoid putting lyxline etc. inside of special environments.
596 # This is wrong for itemize and enumerate environments, but it is
597 # impossible to convert these correctly.
598 # We want to avoid new paragraphs if possible becauase we want to
599 # inherit font sizes.
601 if (not file.is_default_layout(layout) or
602 find(file.body[i],"\\align") != -1 or
603 find(file.body[i],"\\labelwidthstring") != -1 or
604 find(file.body[i],"\\noindent") != -1):
607 # get the font size of the beginning of this paragraph, since we need
608 # it for the lyxline inset
610 while not is_nonempty_line(file.body[j]):
613 if find(file.body[j], "\\size") != -1:
614 size_top = split(file.body[j])[1]
616 for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom":
617 file.body[i] = replace(file.body[i], tag, "")
620 # the position could be change because of the removal of other
621 # paragraph properties above
622 vspace_top = find(file.body[i],"\\added_space_top")
623 tmp_list = split(file.body[i][vspace_top:])
624 vspace_top_value = tmp_list[1]
625 file.body[i] = file.body[i][:vspace_top] + join(tmp_list[2:])
628 # the position could be change because of the removal of other
629 # paragraph properties above
630 vspace_bot = find(file.body[i],"\\added_space_bottom")
631 tmp_list = split(file.body[i][vspace_bot:])
632 vspace_bot_value = tmp_list[1]
633 file.body[i] = file.body[i][:vspace_bot] + join(tmp_list[2:])
635 file.body[i] = strip(file.body[i])
638 # Create an empty paragraph or paragraph fragment for line and
639 # page break that belong above the paragraph
640 if pb_top !=-1 or line_top != -1 or vspace_top != -1:
642 paragraph_above = list()
644 # We need to create an extra paragraph for nonstandard environments
645 paragraph_above = ['\\begin_layout %s' % file.default_layout, '']
648 paragraph_above.extend(['\\newpage ',''])
651 paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','',''])
655 paragraph_above.extend(['\\size ' + size_top + ' '])
656 # We need an additional vertical space of -\parskip.
657 # We can't use the vspace inset because it does not know \parskip.
658 paragraph_above.extend(['\\lyxline ', '', ''])
659 insert_ert(paragraph_above, len(paragraph_above) - 1, 'Collapsed',
660 '\\vspace{-1\\parskip}\n', file.format + 1, file.default_layout)
661 paragraph_above.extend([''])
664 paragraph_above.extend(['\\end_layout ',''])
665 # insert new paragraph above the current paragraph
666 file.body[i-2:i-2] = paragraph_above
668 # insert new lines at the beginning of the current paragraph
669 file.body[i:i] = paragraph_above
671 i = i + len(paragraph_above)
673 # Ensure that nested style are converted later.
674 k = find_end_of(file.body, i, "\\begin_layout", "\\end_layout")
679 if pb_bot !=-1 or line_bot != -1 or vspace_bot != -1:
681 # get the font size of the end of this paragraph
685 if find(file.body[j], "\\size") != -1:
686 size_bot = split(file.body[j])[1]
688 elif find(file.body[j], "\\begin_inset") != -1:
690 j = find_end_of_inset(file.body, j)
694 paragraph_below = list()
696 # We need to create an extra paragraph for nonstandard environments
697 paragraph_below = ['', '\\begin_layout %s' % file.default_layout, '']
699 for a in range(len(font_attributes)):
700 if find_token(file.body, font_attributes[a], i, k) != -1:
701 paragraph_below.extend([font_attributes[a] + ' ' + attribute_values[a]])
704 if nonstandard and size_bot != '':
705 paragraph_below.extend(['\\size ' + size_bot + ' '])
706 paragraph_below.extend(['\\lyxline ',''])
708 paragraph_below.extend(['\\size default '])
711 paragraph_below.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','',''])
714 paragraph_below.extend(['\\newpage ',''])
717 paragraph_below.extend(['\\end_layout '])
718 # insert new paragraph below the current paragraph
719 file.body[k+1:k+1] = paragraph_below
721 # insert new lines at the end of the current paragraph
722 file.body[k:k] = paragraph_below
728 def convert_note(file):
731 i = find_tokens(file.body, ["\\begin_inset Note",
732 "\\begin_inset Comment",
733 "\\begin_inset Greyedout"], i)
737 file.body[i] = file.body[i][0:13] + 'Note ' + file.body[i][13:]
741 def revert_note(file):
742 note_header = "\\begin_inset Note "
745 i = find_token(file.body, note_header, i)
749 file.body[i] = "\\begin_inset " + file.body[i][len(note_header):]
756 def convert_box(file):
759 i = find_tokens(file.body, ["\\begin_inset Boxed",
760 "\\begin_inset Doublebox",
761 "\\begin_inset Frameless",
762 "\\begin_inset ovalbox",
763 "\\begin_inset Ovalbox",
764 "\\begin_inset Shadowbox"], i)
768 file.body[i] = file.body[i][0:13] + 'Box ' + file.body[i][13:]
772 def revert_box(file):
773 box_header = "\\begin_inset Box "
776 i = find_token(file.body, box_header, i)
780 file.body[i] = "\\begin_inset " + file.body[i][len(box_header):]
787 def convert_collapsable(file):
790 i = find_tokens(file.body, ["\\begin_inset Box",
791 "\\begin_inset Branch",
792 "\\begin_inset CharStyle",
793 "\\begin_inset Float",
794 "\\begin_inset Foot",
795 "\\begin_inset Marginal",
796 "\\begin_inset Note",
797 "\\begin_inset OptArg",
798 "\\begin_inset Wrap"], i)
802 # Seach for a line starting 'collapsed'
803 # If, however, we find a line starting '\begin_layout'
804 # (_always_ present) then break with a warning message
807 if (file.body[i] == "collapsed false"):
808 file.body[i] = "status open"
810 elif (file.body[i] == "collapsed true"):
811 file.body[i] = "status collapsed"
813 elif (file.body[i][:13] == "\\begin_layout"):
814 file.warning("Malformed LyX file: Missing 'collapsed'.")
821 def revert_collapsable(file):
824 i = find_tokens(file.body, ["\\begin_inset Box",
825 "\\begin_inset Branch",
826 "\\begin_inset CharStyle",
827 "\\begin_inset Float",
828 "\\begin_inset Foot",
829 "\\begin_inset Marginal",
830 "\\begin_inset Note",
831 "\\begin_inset OptArg",
832 "\\begin_inset Wrap"], i)
836 # Seach for a line starting 'status'
837 # If, however, we find a line starting '\begin_layout'
838 # (_always_ present) then break with a warning message
841 if (file.body[i] == "status open"):
842 file.body[i] = "collapsed false"
844 elif (file.body[i] == "status collapsed" or
845 file.body[i] == "status inlined"):
846 file.body[i] = "collapsed true"
848 elif (file.body[i][:13] == "\\begin_layout"):
849 file.warning("Malformed LyX file: Missing 'status'.")
859 def convert_ert(file):
862 i = find_token(file.body, "\\begin_inset ERT", i)
866 # Seach for a line starting 'status'
867 # If, however, we find a line starting '\begin_layout'
868 # (_always_ present) then break with a warning message
871 if (file.body[i] == "status Open"):
872 file.body[i] = "status open"
874 elif (file.body[i] == "status Collapsed"):
875 file.body[i] = "status collapsed"
877 elif (file.body[i] == "status Inlined"):
878 file.body[i] = "status inlined"
880 elif (file.body[i][:13] == "\\begin_layout"):
881 file.warning("Malformed LyX file: Missing 'status'.")
888 def revert_ert(file):
891 i = find_token(file.body, "\\begin_inset ERT", i)
895 # Seach for a line starting 'status'
896 # If, however, we find a line starting '\begin_layout'
897 # (_always_ present) then break with a warning message
900 if (file.body[i] == "status open"):
901 file.body[i] = "status Open"
903 elif (file.body[i] == "status collapsed"):
904 file.body[i] = "status Collapsed"
906 elif (file.body[i] == "status inlined"):
907 file.body[i] = "status Inlined"
909 elif (file.body[i][:13] == "\\begin_layout"):
910 file.warning("Malformed LyX file : Missing 'status'.")
920 def convert_minipage(file):
921 """ Convert minipages to the box inset.
922 We try to use the same order of arguments as lyx does.
925 inner_pos = ["c","t","b","s"]
929 i = find_token(file.body, "\\begin_inset Minipage", i)
933 file.body[i] = "\\begin_inset Box Frameless"
936 # convert old to new position using the pos list
937 if file.body[i][:8] == "position":
938 file.body[i] = 'position "%s"' % pos[int(file.body[i][9])]
940 file.body.insert(i, 'position "%s"' % pos[0])
943 file.body.insert(i, 'hor_pos "c"')
945 file.body.insert(i, 'has_inner_box 1')
948 # convert the inner_position
949 if file.body[i][:14] == "inner_position":
950 file.body[i] = 'inner_pos "%s"' % inner_pos[int(file.body[i][15])]
952 file.body.insert('inner_pos "%s"' % inner_pos[0])
955 # We need this since the new file format has a height and width
956 # in a different order.
957 if file.body[i][:6] == "height":
958 height = file.body[i][6:]
959 # test for default value of 221 and convert it accordingly
960 if height == ' "0pt"' or height == ' "0"':
966 if file.body[i][:5] == "width":
967 width = file.body[i][5:]
972 if file.body[i][:9] == "collapsed":
973 if file.body[i][9:] == "true":
981 file.body.insert(i, 'use_parbox 0')
983 file.body.insert(i, 'width' + width)
985 file.body.insert(i, 'special "none"')
987 file.body.insert(i, 'height' + height)
989 file.body.insert(i, 'height_special "totalheight"')
991 file.body.insert(i, 'status ' + status)
995 # -------------------------------------------------------------------------------------------
996 # Convert backslashes and '\n' into valid ERT code, append the converted
997 # text to body[i] and return the (maybe incremented) line index i
998 def convert_ertbackslash(body, i, ert, format, default_layout):
1001 body[i] = body[i] + '\\backslash '
1006 body[i+1:i+1] = ['\\newline ', '']
1009 body[i+1:i+1] = ['\\end_layout', '', '\\begin_layout %s' % default_layout, '']
1012 body[i] = body[i] + c
1016 # Converts lines in ERT code to LaTeX
1017 # The surrounding \begin_layout ... \end_layout pair must not be included
1018 def ert2latex(lines, format):
1019 backslash = re.compile(r'\\backslash\s*$')
1020 newline = re.compile(r'\\newline\s*$')
1022 begin_layout = re.compile(r'\\layout\s*\S+$')
1024 begin_layout = re.compile(r'\\begin_layout\s*\S+$')
1025 end_layout = re.compile(r'\\end_layout\s*$')
1027 for i in range(len(lines)):
1028 line = backslash.sub('\\\\', lines[i])
1030 if begin_layout.match(line):
1033 line = newline.sub('\n', line)
1035 if begin_layout.match(line):
1037 if format > 224 and end_layout.match(line):
1043 # get all paragraph parameters. They can be all on one line or on several lines.
1044 # lines[i] must be the first parameter line
1045 def get_par_params(lines, i):
1046 par_params = ('added_space_bottom', 'added_space_top', 'align',
1047 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
1048 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
1049 'start_of_appendix')
1050 # We cannot check for '\\' only because paragraphs may start e.g.
1051 # with '\\backslash'
1053 while lines[i][:1] == '\\' and split(lines[i][1:])[0] in par_params:
1054 params = params + ' ' + strip(lines[i])
1056 return strip(params)
1059 # convert LyX font size to LaTeX fontsize
1060 def lyxsize2latexsize(lyxsize):
1061 sizes = {"tiny" : "tiny", "scriptsize" : "scriptsize",
1062 "footnotesize" : "footnotesize", "small" : "small",
1063 "normal" : "normalsize", "large" : "large", "larger" : "Large",
1064 "largest" : "LARGE", "huge" : "huge", "giant" : "Huge"}
1065 if lyxsize in sizes:
1066 return '\\' + sizes[lyxsize]
1070 # Change vspace insets, page breaks and lyxlines to paragraph options
1071 # (if possible) or ERT
1072 def revert_breaks(file):
1074 # Get default spaceamount
1075 i = find_token(file.header, '\\defskip', 0)
1077 defskipamount = 'medskip'
1079 defskipamount = split(file.header[i])[1]
1081 keys = {"\\begin_inset" : "vspace", "\\lyxline" : "lyxline",
1082 "\\newpage" : "newpage"}
1083 keywords_top = {"vspace" : "\\added_space_top", "lyxline" : "\\line_top",
1084 "newpage" : "\\pagebreak_top"}
1085 keywords_bot = {"vspace" : "\\added_space_bottom", "lyxline" : "\\line_bottom",
1086 "newpage" : "\\pagebreak_bottom"}
1087 tokens = ["\\begin_inset VSpace", "\\lyxline", "\\newpage"]
1089 # Convert the insets
1092 i = find_tokens(file.body, tokens, i)
1096 # Are we at the beginning of a paragraph?
1098 this_par = get_paragraph(file.body, i, file.format - 1)
1099 start = this_par + 1
1100 params = get_par_params(file.body, start)
1102 # Paragraph parameters may be on one or more lines.
1103 # Find the start of the real paragraph text.
1104 while file.body[start][:1] == '\\' and split(file.body[start])[0] in params:
1106 for k in range(start, i):
1107 if find(file.body[k], "\\size") != -1:
1109 size = split(file.body[k])[1]
1110 elif is_nonempty_line(file.body[k]):
1113 # Find the end of the real paragraph text.
1114 next_par = get_next_paragraph(file.body, i, file.format - 1)
1116 file.warning("Malformed LyX file: Missing next paragraph.")
1120 # first line of our insets
1122 # last line of our insets
1123 inset_end = inset_start
1124 # Are we at the end of a paragraph?
1126 # start and end line numbers to delete if we convert this inset
1128 # is this inset a lyxline above a paragraph?
1130 # raw inset information
1132 # name of this inset
1134 # font size of this inset
1137 # Detect subsequent lyxline, vspace and pagebreak insets created by convert_breaks()
1141 if find_tokens(file.body, tokens, k) == k:
1143 lines.append(split(file.body[k]))
1144 insets.append(keys[lines[n][0]])
1145 del_lines.append([k, k])
1150 elif find(file.body[k], "\\size") != -1:
1152 size = split(file.body[k])[1]
1153 elif find_token(file.body, "\\begin_inset ERT", k) == k:
1154 ert_begin = find_token(file.body, "\\layout", k) + 1
1156 file.warning("Malformed LyX file: Missing '\\layout'.")
1158 ert_end = find_end_of_inset(file.body, k)
1160 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1162 ert = ert2latex(file.body[ert_begin:ert_end], file.format - 1)
1163 if (n > 0 and insets[n - 1] == "lyxline" and
1164 ert == '\\vspace{-1\\parskip}\n'):
1165 # vspace ERT created by convert_breaks() for top lyxline
1167 del_lines[n - 1][1] = ert_end
1173 elif (n > 0 and insets[n - 1] == "vspace" and
1174 find_token(file.body, "\\end_inset", k) == k):
1175 # ignore end of vspace inset
1176 del_lines[n - 1][1] = k
1178 elif is_nonempty_line(file.body[k]):
1183 # Determine space amount for vspace insets
1184 spaceamount = list()
1187 if insets[k] == "vspace":
1188 spaceamount.append(lines[k][2])
1189 arguments.append(' ' + spaceamount[k] + ' ')
1191 spaceamount.append('')
1192 arguments.append(' ')
1194 # Can we convert to top paragraph parameters?
1196 if ((n == 3 and insets[0] == "newpage" and insets[1] == "vspace" and
1197 insets[2] == "lyxline" and top[2]) or
1199 ((insets[0] == "newpage" and insets[1] == "vspace") or
1200 (insets[0] == "newpage" and insets[1] == "lyxline" and top[1]) or
1201 (insets[0] == "vspace" and insets[1] == "lyxline" and top[1]))) or
1202 (n == 1 and insets[0] == "lyxline" and top[0])):
1203 # These insets have been created before a paragraph by
1207 # Can we convert to bottom paragraph parameters?
1209 if ((n == 3 and insets[0] == "lyxline" and not top[0] and
1210 insets[1] == "vspace" and insets[2] == "newpage") or
1212 ((insets[0] == "lyxline" and not top[0] and insets[1] == "vspace") or
1213 (insets[0] == "lyxline" and not top[0] and insets[1] == "newpage") or
1214 (insets[0] == "vspace" and insets[1] == "newpage"))) or
1215 (n == 1 and insets[0] == "lyxline" and not top[0])):
1216 # These insets have been created after a paragraph by
1220 if paragraph_start and paragraph_end:
1221 # We are in a paragraph of our own.
1222 # We must not delete this paragraph if it has parameters
1224 # First try to merge with the previous paragraph.
1225 # We try the previous paragraph first because we would
1226 # otherwise need ERT for two subsequent vspaces.
1227 prev_par = get_paragraph(file.body, this_par - 1, file.format - 1) + 1
1228 if prev_par > 0 and not before:
1229 prev_params = get_par_params(file.body, prev_par + 1)
1231 # determine font size
1232 prev_size = "normal"
1234 while file.body[k][:1] == '\\' and split(file.body[k])[0] in prev_params:
1237 if find(file.body[k], "\\size") != -1:
1238 prev_size = split(file.body[k])[1]
1240 elif find(file.body[k], "\\begin_inset") != -1:
1242 k = find_end_of_inset(file.body, k)
1243 elif is_nonempty_line(file.body[k]):
1247 if (keywords_bot[insets[k]] in prev_params or
1248 (insets[k] == "lyxline" and sizes[k] != prev_size)):
1253 file.body.insert(prev_par + 1,
1254 keywords_bot[insets[k]] + arguments[k])
1255 del file.body[this_par+n:next_par-1+n]
1258 # Then try next paragraph
1259 if next_par > 0 and not after:
1260 next_params = get_par_params(file.body, next_par + 1)
1262 while file.body[k][:1] == '\\' and split(file.body[k])[0] in next_params:
1264 # determine font size
1265 next_size = "normal"
1268 if find(file.body[k], "\\size") != -1:
1269 next_size = split(file.body[k])[1]
1271 elif is_nonempty_line(file.body[k]):
1275 if (keywords_top[insets[k]] in next_params or
1276 (insets[k] == "lyxline" and sizes[k] != next_size)):
1281 file.body.insert(next_par + 1,
1282 keywords_top[insets[k]] + arguments[k])
1283 del file.body[this_par:next_par-1]
1286 elif paragraph_start or paragraph_end:
1287 # Convert to paragraph formatting if we are at the beginning or end
1288 # of a paragraph and the resulting paragraph would not be empty
1289 # The order is important: del and insert invalidate some indices
1291 keywords = keywords_top
1293 keywords = keywords_bot
1296 if keywords[insets[k]] in params:
1301 file.body.insert(this_par + 1,
1302 keywords[insets[k]] + arguments[k])
1303 for j in range(k, n):
1304 del_lines[j][0] = del_lines[j][0] + 1
1305 del_lines[j][1] = del_lines[j][1] + 1
1306 del file.body[del_lines[k][0]:del_lines[k][1]+1]
1307 deleted = del_lines[k][1] - del_lines[k][0] + 1
1308 for j in range(k + 1, n):
1309 del_lines[j][0] = del_lines[j][0] - deleted
1310 del_lines[j][1] = del_lines[j][1] - deleted
1314 # Convert the first inset to ERT.
1315 # The others are converted in the next loop runs (if they exist)
1316 if insets[0] == "vspace":
1317 file.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
1318 '\\layout %s' % file.default_layout, '', '\\backslash ']
1320 if spaceamount[0][-1] == '*':
1321 spaceamount[0] = spaceamount[0][:-1]
1326 # Replace defskip by the actual value
1327 if spaceamount[0] == 'defskip':
1328 spaceamount[0] = defskipamount
1330 # LaTeX does not know \\smallskip* etc
1332 if spaceamount[0] == 'smallskip':
1333 spaceamount[0] = '\\smallskipamount'
1334 elif spaceamount[0] == 'medskip':
1335 spaceamount[0] = '\\medskipamount'
1336 elif spaceamount[0] == 'bigskip':
1337 spaceamount[0] = '\\bigskipamount'
1338 elif spaceamount[0] == 'vfill':
1339 spaceamount[0] = '\\fill'
1341 # Finally output the LaTeX code
1342 if (spaceamount[0] == 'smallskip' or spaceamount[0] == 'medskip' or
1343 spaceamount[0] == 'bigskip' or spaceamount[0] == 'vfill'):
1344 file.body.insert(i, spaceamount[0] + '{}')
1347 file.body.insert(i, 'vspace*{')
1349 file.body.insert(i, 'vspace{')
1350 i = convert_ertbackslash(file.body, i, spaceamount[0], file.format - 1, file.default_layout)
1351 file.body[i] = file.body[i] + '}'
1353 elif insets[0] == "lyxline":
1355 latexsize = lyxsize2latexsize(size)
1357 file.warning("Could not convert LyX fontsize '%s' to LaTeX font size." % size)
1358 latexsize = '\\normalsize'
1359 i = insert_ert(file.body, i, 'Collapsed',
1360 '\\lyxline{%s}' % latexsize,
1361 file.format - 1, file.default_layout)
1362 # We use \providecommand so that we don't get an error if native
1363 # lyxlines are used (LyX writes first its own preamble and then
1364 # the user specified one)
1365 add_to_preamble(file,
1366 ['% Commands inserted by lyx2lyx for lyxlines',
1367 '\\providecommand{\\lyxline}[1]{',
1368 ' {#1 \\vspace{1ex} \\hrule width \\columnwidth \\vspace{1ex}}'
1370 elif insets[0] == "newpage":
1372 i = insert_ert(file.body, i, 'Collapsed', '\\newpage{}',
1373 file.format - 1, file.default_layout)
1376 # Convert a LyX length into a LaTeX length
1377 def convert_len(len, special):
1378 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
1379 "page%":"\\pagewidth", "line%":"\\linewidth",
1380 "theight%":"\\textheight", "pheight%":"\\pageheight"}
1382 # Convert special lengths
1383 if special != 'none':
1384 len = '%f\\' % len2value(len) + special
1386 # Convert LyX units to LaTeX units
1387 for unit in units.keys():
1388 if find(len, unit) != -1:
1389 len = '%f' % (len2value(len) / 100) + units[unit]
1395 # Convert a LyX length into valid ERT code and append it to body[i]
1396 # Return the (maybe incremented) line index i
1397 def convert_ertlen(body, i, len, special, format, default_layout):
1398 # Convert backslashes and insert the converted length into body
1399 return convert_ertbackslash(body, i, convert_len(len, special), format, default_layout)
1402 # Return the value of len without the unit in numerical form
1404 result = re.search('([+-]?[0-9.]+)', len)
1406 return float(result.group(1))
1407 # No number means 1.0
1411 # Convert text to ERT and insert it at body[i]
1412 # Return the index of the line after the inserted ERT
1413 def insert_ert(body, i, status, text, format, default_layout):
1414 body[i:i] = ['\\begin_inset ERT', 'status ' + status, '']
1417 body[i:i] = ['\\layout %s' % default_layout, '']
1419 body[i:i] = ['\\begin_layout %s' % default_layout, '']
1420 i = i + 1 # i points now to the just created empty line
1421 i = convert_ertbackslash(body, i, text, format, default_layout) + 1
1423 body[i:i] = ['\\end_layout']
1425 body[i:i] = ['', '\\end_inset', '']
1430 # Add text to the preamble if it is not already there.
1431 # Only the first line is checked!
1432 def add_to_preamble(file, text):
1433 if find_token(file.preamble, text[0], 0) != -1:
1436 file.preamble.extend(text)
1439 def convert_frameless_box(file):
1440 pos = ['t', 'c', 'b']
1441 inner_pos = ['c', 't', 'b', 's']
1444 i = find_token(file.body, '\\begin_inset Frameless', i)
1447 j = find_end_of_inset(file.body, i)
1449 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1456 params = {'position':0, 'hor_pos':'c', 'has_inner_box':'1',
1457 'inner_pos':1, 'use_parbox':'0', 'width':'100col%',
1458 'special':'none', 'height':'1in',
1459 'height_special':'totalheight', 'collapsed':'false'}
1460 for key in params.keys():
1461 value = replace(get_value(file.body, key, i, j), '"', '')
1463 if key == 'position':
1464 # convert new to old position: 'position "t"' -> 0
1465 value = find_token(pos, value, 0)
1468 elif key == 'inner_pos':
1469 # convert inner position
1470 value = find_token(inner_pos, value, 0)
1475 j = del_token(file.body, key, i, j)
1478 # Convert to minipage or ERT?
1479 # Note that the inner_position and height parameters of a minipage
1480 # inset are ignored and not accessible for the user, although they
1481 # are present in the file format and correctly read in and written.
1482 # Therefore we convert to ERT if they do not have their LaTeX
1483 # defaults. These are:
1484 # - the value of "position" for "inner_pos"
1485 # - "\totalheight" for "height"
1486 if (params['use_parbox'] != '0' or
1487 params['has_inner_box'] != '1' or
1488 params['special'] != 'none' or
1489 params['height_special'] != 'totalheight' or
1490 len2value(params['height']) != 1.0):
1492 # Here we know that this box is not supported in file format 224.
1493 # Therefore we need to convert it to ERT. We can't simply convert
1494 # the beginning and end of the box to ERT, because the
1495 # box inset may contain layouts that are different from the
1496 # surrounding layout. After the conversion the contents of the
1497 # box inset is on the same level as the surrounding text, and
1498 # paragraph layouts and align parameters can get mixed up.
1500 # A possible solution for this problem:
1501 # Convert the box to a minipage and redefine the minipage
1502 # environment in ERT so that the original box is simulated.
1503 # For minipages we could do this in a way that the width and
1504 # position can still be set from LyX, but this did not work well.
1505 # This is not possible for parboxes either, so we convert the
1506 # original box to ERT, put the minipage inset inside the box
1507 # and redefine the minipage environment to be empty.
1509 # Commands that are independant of a particular box can go to
1511 # We need to define lyxtolyxrealminipage with 3 optional
1512 # arguments although LyX 1.3 uses only the first one.
1513 # Otherwise we will get LaTeX errors if this document is
1514 # converted to format 225 or above again (LyX 1.4 uses all
1515 # optional arguments).
1516 add_to_preamble(file,
1517 ['% Commands inserted by lyx2lyx for frameless boxes',
1518 '% Save the original minipage environment',
1519 '\\let\\lyxtolyxrealminipage\\minipage',
1520 '\\let\\endlyxtolyxrealminipage\\endminipage',
1521 '% Define an empty lyxtolyximinipage environment',
1522 '% with 3 optional arguments',
1523 '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1524 '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1525 ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1526 ' {\\end{lyxtolyxiiiminipage}}',
1527 '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1528 ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1529 ' {\\end{lyxtolyxiiminipage}}',
1530 '\\newenvironment{lyxtolyxminipage}[1][c]%',
1531 ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1532 ' {\\end{lyxtolyximinipage}}'])
1534 if params['use_parbox'] != '0':
1537 ert = '\\begin{lyxtolyxrealminipage}'
1539 # convert optional arguments only if not latex default
1540 if (pos[params['position']] != 'c' or
1541 inner_pos[params['inner_pos']] != pos[params['position']] or
1542 params['height_special'] != 'totalheight' or
1543 len2value(params['height']) != 1.0):
1544 ert = ert + '[' + pos[params['position']] + ']'
1545 if (inner_pos[params['inner_pos']] != pos[params['position']] or
1546 params['height_special'] != 'totalheight' or
1547 len2value(params['height']) != 1.0):
1548 ert = ert + '[' + convert_len(params['height'],
1549 params['height_special']) + ']'
1550 if inner_pos[params['inner_pos']] != pos[params['position']]:
1551 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1553 ert = ert + '{' + convert_len(params['width'],
1554 params['special']) + '}'
1556 if params['use_parbox'] != '0':
1558 ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1559 ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1562 i = insert_ert(file.body, i, 'Collapsed', ert, file.format - 1, file.default_layout)
1563 j = j + i - old_i - 1
1565 file.body[i:i] = ['\\begin_inset Minipage',
1566 'position %d' % params['position'],
1569 'width "' + params['width'] + '"',
1570 'collapsed ' + params['collapsed']]
1574 # Restore the original minipage environment since we may have
1575 # minipages inside this box.
1576 # Start a new paragraph because the following may be nonstandard
1577 file.body[i:i] = ['\\layout %s' % file.default_layout, '', '']
1580 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1581 ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1583 i = insert_ert(file.body, i, 'Collapsed', ert, file.format - 1, file.default_layout)
1584 j = j + i - old_i - 1
1586 # Redefine the minipage end before the inset end.
1587 # Start a new paragraph because the previous may be nonstandard
1588 file.body[j:j] = ['\\layout %s' % file.default_layout, '', '']
1590 ert = '\\let\\endminipage\\endlyxtolyxminipage'
1591 j = insert_ert(file.body, j, 'Collapsed', ert, file.format - 1, file.default_layout)
1593 file.body.insert(j, '')
1596 # LyX writes '%\n' after each box. Therefore we need to end our
1597 # ERT with '%\n', too, since this may swallow a following space.
1598 if params['use_parbox'] != '0':
1601 ert = '\\end{lyxtolyxrealminipage}%\n'
1602 j = insert_ert(file.body, j, 'Collapsed', ert, file.format - 1, file.default_layout)
1604 # We don't need to restore the original minipage after the inset
1605 # end because the scope of the redefinition is the original box.
1609 # Convert to minipage
1610 file.body[i:i] = ['\\begin_inset Minipage',
1611 'position %d' % params['position'],
1612 'inner_position %d' % params['inner_pos'],
1613 'height "' + params['height'] + '"',
1614 'width "' + params['width'] + '"',
1615 'collapsed ' + params['collapsed']]
1619 def remove_branches(file):
1622 i = find_token(file.header, "\\branch", i)
1625 file.warning("Removing branch %s." % split(file.header[i])[1])
1626 j = find_token(file.header, "\\end_branch", i)
1628 file.warning("Malformed LyX file: Missing '\\end_branch'.")
1630 del file.header[i:j+1]
1634 i = find_token(file.body, "\\begin_inset Branch", i)
1637 j = find_end_of_inset(file.body, i)
1639 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1643 del file.body[j - 1]
1644 # Seach for a line starting 'collapsed'
1645 # If, however, we find a line starting '\layout'
1646 # (_always_ present) then break with a warning message
1649 if (file.body[i][:9] == "collapsed"):
1653 elif (file.body[i][:7] == "\\layout"):
1654 if collapsed_found == 0:
1655 file.warning("Malformed LyX file: Missing 'collapsed'.")
1656 # Delete this new paragraph, since it would not appear in
1657 # .tex output. This avoids also empty paragraphs.
1667 def convert_jurabib(file):
1668 i = find_token(file.header, '\\use_numerical_citations', 0)
1670 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1672 file.header.insert(i + 1, '\\use_jurabib 0')
1675 def revert_jurabib(file):
1676 i = find_token(file.header, '\\use_jurabib', 0)
1678 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1680 if get_value(file.header, '\\use_jurabib', 0) != "0":
1681 file.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1682 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1690 def convert_bibtopic(file):
1691 i = find_token(file.header, '\\use_jurabib', 0)
1693 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1695 file.header.insert(i + 1, '\\use_bibtopic 0')
1698 def revert_bibtopic(file):
1699 i = find_token(file.header, '\\use_bibtopic', 0)
1701 file.warning("Malformed lyx file: Missing '\\use_bibtopic'.")
1703 if get_value(file.header, '\\use_bibtopic', 0) != "0":
1704 file.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1705 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1712 def convert_float(file):
1715 i = find_token(file.body, '\\begin_inset Float', i)
1718 # Seach for a line starting 'wide'
1719 # If, however, we find a line starting '\begin_layout'
1720 # (_always_ present) then break with a warning message
1723 if (file.body[i][:4] == "wide"):
1724 file.body.insert(i + 1, 'sideways false')
1726 elif (file.body[i][:13] == "\\begin_layout"):
1727 file.warning("Malformed lyx file: Missing 'wide'.")
1733 def revert_float(file):
1736 i = find_token(file.body, '\\begin_inset Float', i)
1739 j = find_end_of_inset(file.body, i)
1741 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1744 if get_value(file.body, 'sideways', i, j) != "false":
1745 file.warning("Conversion of 'sideways true' not yet implemented.")
1746 # Don't remove 'sideways' so that people will get warnings by lyx
1749 del_token(file.body, 'sideways', i, j)
1753 def convert_graphics(file):
1754 """ Add extension to filenames of insetgraphics if necessary.
1758 i = find_token(file.body, "\\begin_inset Graphics", i)
1762 j = find_token2(file.body, "filename", i)
1766 filename = split(file.body[j])[1]
1767 absname = os.path.normpath(os.path.join(file.dir, filename))
1768 if file.input == stdin and not os.path.isabs(filename):
1769 # We don't know the directory and cannot check the file.
1770 # We could use a heuristic and take the current directory,
1771 # and we could try to find out if filename has an extension,
1772 # but that would be just guesses and could be wrong.
1773 file.warning("""Warning: Can not determine whether file
1775 needs an extension when reading from standard input.
1776 You may need to correct the file manually or run
1777 lyx2lyx again with the .lyx file as commandline argument.""" % filename)
1779 # This needs to be the same algorithm as in pre 233 insetgraphics
1780 if access(absname, F_OK):
1782 if access(absname + ".ps", F_OK):
1783 file.body[j] = replace(file.body[j], filename, filename + ".ps")
1785 if access(absname + ".eps", F_OK):
1786 file.body[j] = replace(file.body[j], filename, filename + ".eps")
1790 # Convert firstname and surname from styles -> char styles
1792 def convert_names(file):
1793 """ Convert in the docbook backend from firstname and surname style
1796 if file.backend != "docbook":
1802 i = find_token(file.body, "\\begin_layout Author", i)
1807 while file.body[i] == "":
1810 if file.body[i][:11] != "\\end_layout" or file.body[i+2][:13] != "\\begin_deeper":
1815 i = find_end_of( file.body, i+3, "\\begin_deeper","\\end_deeper")
1817 # something is really wrong, abort
1818 file.warning("Missing \\end_deeper, after style Author.")
1819 file.warning("Aborted attempt to parse FirstName and Surname.")
1821 firstname, surname = "", ""
1823 name = file.body[k:i]
1825 j = find_token(name, "\\begin_layout FirstName", 0)
1828 while(name[j] != "\\end_layout"):
1829 firstname = firstname + name[j]
1832 j = find_token(name, "\\begin_layout Surname", 0)
1835 while(name[j] != "\\end_layout"):
1836 surname = surname + name[j]
1840 del file.body[k+2:i+1]
1842 file.body[k-1:k-1] = ["", "",
1843 "\\begin_inset CharStyle Firstname",
1846 '\\begin_layout %s' % file.default_layout,
1854 "\\begin_inset CharStyle Surname",
1857 '\\begin_layout %s' % file.default_layout,
1866 def revert_names(file):
1867 """ Revert in the docbook backend from firstname and surname char style
1870 if file.backend != "docbook":
1875 # \use_natbib 1 \cite_engine <style>
1876 # \use_numerical_citations 0 -> where <style> is one of
1877 # \use_jurabib 0 "basic", "natbib_authoryear",
1878 # "natbib_numerical" or "jurabib"
1879 def convert_cite_engine(file):
1880 a = find_token(file.header, "\\use_natbib", 0)
1882 file.warning("Malformed lyx file: Missing '\\use_natbib'.")
1885 b = find_token(file.header, "\\use_numerical_citations", 0)
1886 if b == -1 or b != a+1:
1887 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1890 c = find_token(file.header, "\\use_jurabib", 0)
1891 if c == -1 or c != b+1:
1892 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1895 use_natbib = int(split(file.header[a])[1])
1896 use_numerical_citations = int(split(file.header[b])[1])
1897 use_jurabib = int(split(file.header[c])[1])
1899 cite_engine = "basic"
1901 if use_numerical_citations:
1902 cite_engine = "natbib_numerical"
1904 cite_engine = "natbib_authoryear"
1906 cite_engine = "jurabib"
1908 del file.header[a:c+1]
1909 file.header.insert(a, "\\cite_engine " + cite_engine)
1912 def revert_cite_engine(file):
1913 i = find_token(file.header, "\\cite_engine", 0)
1915 file.warning("Malformed lyx file: Missing '\\cite_engine'.")
1918 cite_engine = split(file.header[i])[1]
1923 if cite_engine == "natbib_numerical":
1926 elif cite_engine == "natbib_authoryear":
1928 elif cite_engine == "jurabib":
1932 file.header.insert(i, "\\use_jurabib " + use_jurabib)
1933 file.header.insert(i, "\\use_numerical_citations " + use_numerical)
1934 file.header.insert(i, "\\use_natbib " + use_natbib)
1940 def convert_paperpackage(file):
1941 i = find_token(file.header, "\\paperpackage", 0)
1945 packages = {'default':'none','a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
1946 if len(split(file.header[i])) > 1:
1947 paperpackage = split(file.header[i])[1]
1948 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1950 file.header[i] = file.header[i] + ' widemarginsa4'
1953 def revert_paperpackage(file):
1954 i = find_token(file.header, "\\paperpackage", 0)
1958 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
1959 'widemarginsa4':'', 'default': 'default'}
1960 if len(split(file.header[i])) > 1:
1961 paperpackage = split(file.header[i])[1]
1963 paperpackage = 'default'
1964 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1970 def convert_bullets(file):
1973 i = find_token(file.header, "\\bullet", i)
1976 if file.header[i][:12] == '\\bulletLaTeX':
1977 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1])
1980 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1]) +\
1981 ' ' + strip(file.header[i+2]) + ' ' + strip(file.header[i+3])
1983 del file.header[i+1:i + n]
1987 def revert_bullets(file):
1990 i = find_token(file.header, "\\bullet", i)
1993 if file.header[i][:12] == '\\bulletLaTeX':
1994 n = find(file.header[i], '"')
1996 file.warning("Malformed header.")
1999 file.header[i:i+1] = [file.header[i][:n-1],'\t' + file.header[i][n:], '\\end_bullet']
2002 frag = split(file.header[i])
2004 file.warning("Malformed header.")
2007 file.header[i:i+1] = [frag[0] + ' ' + frag[1],
2016 # \begin_header and \begin_document
2018 def add_begin_header(file):
2019 i = find_token(file.header, '\\lyxformat', 0)
2020 file.header.insert(i+1, '\\begin_header')
2021 file.header.insert(i+1, '\\begin_document')
2024 def remove_begin_header(file):
2025 i = find_token(file.header, "\\begin_document", 0)
2028 i = find_token(file.header, "\\begin_header", 0)
2034 # \begin_file.body and \end_file.body
2036 def add_begin_body(file):
2037 file.body.insert(0, '\\begin_body')
2038 file.body.insert(1, '')
2039 i = find_token(file.body, "\\end_document", 0)
2040 file.body.insert(i, '\\end_body')
2042 def remove_begin_body(file):
2043 i = find_token(file.body, "\\begin_body", 0)
2046 if not file.body[i]:
2048 i = find_token(file.body, "\\end_body", 0)
2056 def normalize_papersize(file):
2057 i = find_token(file.header, '\\papersize', 0)
2061 tmp = split(file.header[i])
2062 if tmp[1] == "Default":
2063 file.header[i] = '\\papersize default'
2065 if tmp[1] == "Custom":
2066 file.header[i] = '\\papersize custom'
2069 def denormalize_papersize(file):
2070 i = find_token(file.header, '\\papersize', 0)
2074 tmp = split(file.header[i])
2075 if tmp[1] == "custom":
2076 file.header[i] = '\\papersize Custom'
2080 # Strip spaces at end of command line
2082 def strip_end_space(file):
2083 for i in range(len(file.body)):
2084 if file.body[i][:1] == '\\':
2085 file.body[i] = strip(file.body[i])
2089 # Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes
2091 def use_x_boolean(file):
2092 bin2bool = {'0': 'false', '1': 'true'}
2093 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2094 i = find_token(file.header, use, 0)
2097 decompose = split(file.header[i])
2098 file.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
2101 def use_x_binary(file):
2102 bool2bin = {'false': '0', 'true': '1'}
2103 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2104 i = find_token(file.header, use, 0)
2107 decompose = split(file.header[i])
2108 file.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
2111 # Place all the paragraph parameters in their own line
2113 def normalize_paragraph_params(file):
2115 allowed_parameters = '\\paragraph_spacing', '\\noindent', '\\align', '\\labelwidthstring', "\\start_of_appendix", "\\leftindent"
2119 i = find_token(file.body, '\\begin_layout', i)
2125 if strip(body[i]) and split(body[i])[0] not in allowed_parameters:
2128 j = find(body[i],'\\', 1)
2131 body[i:i+1] = [strip(body[i][:j]), body[i][j:]]
2137 # Add/remove output_changes parameter
2139 def convert_output_changes (file):
2140 i = find_token(file.header, '\\tracking_changes', 0)
2142 file.warning("Malformed lyx file: Missing '\\tracking_changes'.")
2144 file.header.insert(i+1, '\\output_changes true')
2147 def revert_output_changes (file):
2148 i = find_token(file.header, '\\output_changes', 0)
2155 # Convert paragraph breaks and sanitize paragraphs
2157 def convert_ert_paragraphs(file):
2158 forbidden_settings = [
2159 # paragraph parameters
2160 '\\paragraph_spacing', '\\labelwidthstring',
2161 '\\start_of_appendix', '\\noindent',
2162 '\\leftindent', '\\align',
2164 '\\family', '\\series', '\\shape', '\\size',
2165 '\\emph', '\\numeric', '\\bar', '\\noun',
2166 '\\color', '\\lang']
2169 i = find_token(file.body, '\\begin_inset ERT', i)
2172 j = find_end_of_inset(file.body, i)
2174 file.warning("Malformed lyx file: Missing '\\end_inset'.")
2178 # convert non-standard paragraphs to standard
2181 k = find_token(file.body, "\\begin_layout", k, j)
2184 file.body[k] = '\\begin_layout %s' % file.default_layout
2187 # remove all paragraph parameters and font settings
2190 if (strip(file.body[k]) and
2191 split(file.body[k])[0] in forbidden_settings):
2197 # insert an empty paragraph before each paragraph but the first
2201 k = find_token(file.body, "\\begin_layout", k, j)
2208 file.body[k:k] = ['\\begin_layout %s' % file.default_layout, "",
2213 # convert \\newline to new paragraph
2216 k = find_token(file.body, "\\newline", k, j)
2219 file.body[k:k+1] = ["\\end_layout", "", '\\begin_layout %s' % file.default_layout]
2222 # We need an empty line if file.default_layout == ''
2223 if file.body[k-1] != '':
2224 file.body.insert(k-1, '')
2231 # Remove double paragraph breaks
2233 def revert_ert_paragraphs(file):
2236 i = find_token(file.body, '\\begin_inset ERT', i)
2239 j = find_end_of_inset(file.body, i)
2241 file.warning("Malformed lyx file: Missing '\\end_inset'.")
2245 # replace paragraph breaks with \newline
2248 k = find_token(file.body, "\\end_layout", k, j)
2249 l = find_token(file.body, "\\begin_layout", k, j)
2250 if k == -1 or l == -1:
2252 file.body[k:l+1] = ["\\newline"]
2256 # replace double \newlines with paragraph breaks
2259 k = find_token(file.body, "\\newline", k, j)
2263 while file.body[l] == "":
2265 if strip(file.body[l]) and split(file.body[l])[0] == "\\newline":
2266 file.body[k:l+1] = ["\\end_layout", "",
2267 '\\begin_layout %s' % file.default_layout]
2270 # We need an empty line if file.default_layout == ''
2271 if file.body[l+1] != '':
2272 file.body.insert(l+1, '')
2280 def convert_french(file):
2281 regexp = re.compile(r'^\\language\s+frenchb')
2282 i = find_re(file.header, regexp, 0)
2284 file.header[i] = "\\language french"
2286 # Change language in the document body
2287 regexp = re.compile(r'^\\lang\s+frenchb')
2290 i = find_re(file.body, regexp, i)
2293 file.body[i] = "\\lang french"
2297 def remove_paperpackage(file):
2298 i = find_token(file.header, '\\paperpackage', 0)
2303 paperpackage = split(file.header[i])[1]
2305 if paperpackage in ("a4", "a4wide", "widemarginsa4"):
2306 conv = {"a4":"\\usepackage{a4}","a4wide": "\\usepackage{a4wide}",
2307 "widemarginsa4": "\\usepackage[widemargins]{a4}"}
2308 # for compatibility we ensure it is the first entry in preamble
2309 file.preamble[0:0] = [conv[paperpackage]]
2313 i = find_token(file.header, '\\papersize', 0)
2315 file.header[i] = "\\papersize default"
2318 def remove_quotestimes(file):
2319 i = find_token(file.header, '\\quotes_times', 0)
2329 convert = [[222, [insert_tracking_changes, add_end_header]],
2330 [223, [remove_color_default, convert_spaces, convert_bibtex, remove_insetparent]],
2331 [224, [convert_external, convert_comment]],
2332 [225, [add_end_layout, layout2begin_layout, convert_end_document,
2333 convert_table_valignment_middle, convert_breaks]],
2334 [226, [convert_note]],
2335 [227, [convert_box]],
2336 [228, [convert_collapsable, convert_ert]],
2337 [229, [convert_minipage]],
2338 [230, [convert_jurabib]],
2339 [231, [convert_float]],
2340 [232, [convert_bibtopic]],
2341 [233, [convert_graphics, convert_names]],
2342 [234, [convert_cite_engine]],
2343 [235, [convert_paperpackage]],
2344 [236, [convert_bullets, add_begin_header, add_begin_body,
2345 normalize_papersize, strip_end_space]],
2346 [237, [use_x_boolean]],
2347 [238, [update_latexaccents]],
2348 [239, [normalize_paragraph_params]],
2349 [240, [convert_output_changes]],
2350 [241, [convert_ert_paragraphs]],
2351 [242, [convert_french]],
2352 [243, [remove_paperpackage]],
2353 [244, [rename_spaces]],
2354 [245, [remove_quotestimes]]]
2356 revert = [[244, []],
2357 [243, [revert_space_names]],
2360 [240, [revert_ert_paragraphs]],
2361 [239, [revert_output_changes]],
2364 [236, [use_x_binary]],
2365 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
2367 [234, [revert_paperpackage]],
2368 [233, [revert_cite_engine]],
2369 [232, [revert_names]],
2370 [231, [revert_bibtopic]],
2371 [230, [revert_float]],
2372 [229, [revert_jurabib]],
2374 [227, [revert_collapsable, revert_ert]],
2375 [226, [revert_box, revert_external_2]],
2376 [225, [revert_note]],
2377 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
2378 revert_valignment_middle, revert_breaks, convert_frameless_box,
2380 [223, [revert_external_2, revert_comment, revert_eqref]],
2381 [222, [revert_spaces, revert_bibtex]],
2382 [221, [rm_end_header, rm_tracking_changes, rm_body_changes]]]
2385 if __name__ == "__main__":