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
28 from string import replace, split, find, strip, join
30 from lyx_0_12 import update_latexaccents
33 # Remove \color default
35 def remove_color_default(file):
38 i = find_token(file.body, "\\color default", i)
41 file.body[i] = replace(file.body[i], "\\color default",
48 def add_end_header(file):
49 file.header.append("\\end_header");
52 def rm_end_header(file):
53 i = find_token(file.header, "\\end_header", 0)
60 # \SpecialChar ~ -> \InsetSpace ~
62 def convert_spaces(file):
63 for i in range(len(file.body)):
64 file.body[i] = replace(file.body[i],"\\SpecialChar ~","\\InsetSpace ~")
67 def revert_spaces(file):
68 regexp = re.compile(r'(.*)(\\InsetSpace\s+)(\S+)')
71 i = find_re(file.body, regexp, i)
74 space = regexp.match(file.body[i]).group(3)
75 prepend = regexp.match(file.body[i]).group(1)
77 file.body[i] = regexp.sub(prepend + '\\SpecialChar ~', file.body[i])
80 file.body[i] = regexp.sub(prepend, file.body[i])
81 file.body[i+1:i+1] = ''
82 if space == "\\space":
84 i = insert_ert(file.body, i+1, 'Collapsed', space, file.format - 1)
87 # \InsetSpace \, -> \InsetSpace \thinspace{}
88 # \InsetSpace \space -> \InsetSpace \space{}
90 def rename_spaces(file):
91 for i in range(len(file.body)):
92 file.body[i] = replace(file.body[i],"\\InsetSpace \\space","\\InsetSpace \\space{}")
93 file.body[i] = replace(file.body[i],"\\InsetSpace \,","\\InsetSpace \\thinspace{}")
96 def revert_space_names(file):
97 for i in range(len(file.body)):
98 file.body[i] = replace(file.body[i],"\\InsetSpace \\space{}","\\InsetSpace \\space")
99 file.body[i] = replace(file.body[i],"\\InsetSpace \\thinspace{}","\\InsetSpace \\,")
103 # equivalent to lyx::support::escape()
105 def lyx_support_escape(lab):
106 hexdigit = ['0', '1', '2', '3', '4', '5', '6', '7',
107 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
111 if o >= 128 or c == '=' or c == '%':
113 enc = enc + hexdigit[o >> 4]
114 enc = enc + hexdigit[o & 15]
121 # \begin_inset LatexCommand \eqref -> ERT
123 def revert_eqref(file):
124 regexp = re.compile(r'^\\begin_inset\s+LatexCommand\s+\\eqref')
127 i = find_re(file.body, regexp, i)
130 eqref = lyx_support_escape(regexp.sub("", file.body[i]))
131 file.body[i:i+1] = ["\\begin_inset ERT", "status Collapsed", "",
132 "\\layout Standard", "", "\\backslash ",
140 def convert_bibtex(file):
141 for i in range(len(file.body)):
142 file.body[i] = replace(file.body[i],"\\begin_inset LatexCommand \\BibTeX",
143 "\\begin_inset LatexCommand \\bibtex")
146 def revert_bibtex(file):
147 for i in range(len(file.body)):
148 file.body[i] = replace(file.body[i], "\\begin_inset LatexCommand \\bibtex",
149 "\\begin_inset LatexCommand \\BibTeX")
155 def remove_insetparent(file):
158 i = find_token(file.body, "\\begin_inset LatexCommand \\lyxparent", i)
167 def convert_external(file):
168 external_rexp = re.compile(r'\\begin_inset External ([^,]*),"([^"]*)",')
169 external_header = "\\begin_inset External"
172 i = find_token(file.body, external_header, i)
175 look = external_rexp.search(file.body[i])
178 args[0] = look.group(1)
179 args[1] = look.group(2)
180 #FIXME: if the previous search fails then warn
182 if args[0] == "RasterImage":
183 # Convert a RasterImage External Inset to a Graphics Inset.
184 top = "\\begin_inset Graphics"
186 filename = "\tfilename " + args[1]
187 file.body[i:i+1] = [top, filename]
190 # Convert the old External Inset format to the new.
191 top = external_header
192 template = "\ttemplate " + args[0]
194 filename = "\tfilename " + args[1]
195 file.body[i:i+1] = [top, template, filename]
198 file.body[i:i+1] = [top, template]
202 def revert_external_1(file):
203 external_header = "\\begin_inset External"
206 i = find_token(file.body, external_header, i)
210 template = split(file.body[i+1])
214 filename = split(file.body[i+1])
218 params = split(file.body[i+1])
220 if file.body[i+1]: del file.body[i+1]
222 file.body[i] = file.body[i] + " " + template[0]+ ', "' + filename[0] + '", " '+ join(params[1:]) + '"'
226 def revert_external_2(file):
227 draft_token = '\tdraft'
230 i = find_token(file.body, '\\begin_inset External', i)
233 j = find_end_of_inset(file.body, i + 1)
235 #this should not happen
237 k = find_token(file.body, draft_token, i+1, j-1)
238 if (k != -1 and len(draft_token) == len(file.body[k])):
246 def convert_comment(file):
248 comment = "\\layout Comment"
250 i = find_token(file.body, comment, i)
254 file.body[i:i+1] = ["\\layout Standard","","",
255 "\\begin_inset Comment",
262 i = find_token(file.body, "\\layout", i)
264 i = len(file.body) - 1
265 file.body[i:i] = ["\\end_inset","",""]
268 j = find_token(file.body, '\\begin_deeper', old_i, i)
269 if j == -1: j = i + 1
270 k = find_token(file.body, '\\begin_inset', old_i, i)
271 if k == -1: k = i + 1
276 i = find_end_of( file.body, i, "\\begin_deeper","\\end_deeper")
278 #This case should not happen
279 #but if this happens deal with it greacefully adding
280 #the missing \end_deeper.
281 i = len(file.body) - 1
282 file.body[i:i] = ["\end_deeper",""]
290 i = find_end_of( file.body, i, "\\begin_inset","\\end_inset")
292 #This case should not happen
293 #but if this happens deal with it greacefully adding
294 #the missing \end_inset.
295 i = len(file.body) - 1
296 file.body[i:i] = ["\\end_inset","","","\\end_inset","",""]
302 if find(file.body[i], comment) == -1:
303 file.body[i:i] = ["\\end_inset"]
306 file.body[i:i+1] = ["\\layout Standard"]
310 def revert_comment(file):
313 i = find_tokens(file.body, ["\\begin_inset Comment", "\\begin_inset Greyedout"], i)
317 file.body[i] = "\\begin_inset Note"
324 def add_end_layout(file):
325 i = find_token(file.body, '\\layout', 0)
331 struct_stack = ["\\layout"]
334 i = find_tokens(file.body, ["\\begin_inset", "\\end_inset", "\\layout",
335 "\\begin_deeper", "\\end_deeper", "\\the_end"], i)
338 token = split(file.body[i])[0]
340 file.warning("Truncated file.")
342 file.body.insert(i, '\\the_end')
345 if token == "\\begin_inset":
346 struct_stack.append(token)
350 if token == "\\end_inset":
351 tail = struct_stack.pop()
352 if tail == "\\layout":
353 file.body.insert(i,"")
354 file.body.insert(i,"\\end_layout")
356 #Check if it is the correct tag
361 if token == "\\layout":
362 tail = struct_stack.pop()
364 file.body.insert(i,"")
365 file.body.insert(i,"\\end_layout")
368 struct_stack.append(tail)
370 struct_stack.append(token)
373 if token == "\\begin_deeper":
374 file.body.insert(i,"")
375 file.body.insert(i,"\\end_layout")
377 struct_stack.append(token)
380 if token == "\\end_deeper":
381 if struct_stack[-1] == '\\layout':
382 file.body.insert(i, '\\end_layout')
389 file.body.insert(i, "")
390 file.body.insert(i, "\\end_layout")
394 def rm_end_layout(file):
397 i = find_token(file.body, '\\end_layout', i)
406 # Handle change tracking keywords
408 def insert_tracking_changes(file):
409 i = find_token(file.header, "\\tracking_changes", 0)
411 file.header.append("\\tracking_changes 0")
414 def rm_tracking_changes(file):
415 i = find_token(file.header, "\\author", 0)
419 i = find_token(file.header, "\\tracking_changes", 0)
425 def rm_body_changes(file):
428 i = find_token(file.body, "\\change_", i)
436 # \layout -> \begin_layout
438 def layout2begin_layout(file):
441 i = find_token(file.body, '\\layout', i)
445 file.body[i] = replace(file.body[i], '\\layout', '\\begin_layout')
449 def begin_layout2layout(file):
452 i = find_token(file.body, '\\begin_layout', i)
456 file.body[i] = replace(file.body[i], '\\begin_layout', '\\layout')
461 # valignment="center" -> valignment="middle"
463 def convert_valignment_middle(body, start, end):
464 for i in range(start, end):
465 if re.search('^<(column|cell) .*valignment="center".*>$', body[i]):
466 body[i] = replace(body[i], 'valignment="center"', 'valignment="middle"')
469 def convert_table_valignment_middle(file):
470 regexp = re.compile(r'^\\begin_inset\s+Tabular')
473 i = find_re(file.body, regexp, i)
476 j = find_end_of_inset(file.body, i + 1)
478 #this should not happen
479 convert_valignment_middle(file.body, i + 1, len(file.body))
481 convert_valignment_middle(file.body, i + 1, j)
485 def revert_table_valignment_middle(body, start, end):
486 for i in range(start, end):
487 if re.search('^<(column|cell) .*valignment="middle".*>$', body[i]):
488 body[i] = replace(body[i], 'valignment="middle"', 'valignment="center"')
491 def revert_valignment_middle(file):
492 regexp = re.compile(r'^\\begin_inset\s+Tabular')
495 i = find_re(file.body, regexp, i)
498 j = find_end_of_inset(file.body, i + 1)
500 #this should not happen
501 revert_table_valignment_middle(file.body, i + 1, len(file.body))
503 revert_table_valignment_middle(file.body, i + 1, j)
508 # \the_end -> \end_document
510 def convert_end_document(file):
511 i = find_token(file.body, "\\the_end", 0)
513 file.body.append("\\end_document")
515 file.body[i] = "\\end_document"
518 def revert_end_document(file):
519 i = find_token(file.body, "\\end_document", 0)
521 file.body.append("\\the_end")
523 file.body[i] = "\\the_end"
527 # Convert line and page breaks
530 #\line_top \line_bottom \pagebreak_top \pagebreak_bottom \added_space_top xxx \added_space_bottom yyy
534 #\begin layout Standard
540 #\begin layout Standard
547 #\begin_inset VSpace xxx
552 #\begin_inset VSpace xxx
559 def convert_breaks(file):
560 par_params = ('added_space_bottom', 'added_space_top', 'align',
561 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
562 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
564 font_attributes = ['\\family', '\\series', '\\shape', '\\emph',
565 '\\numeric', '\\bar', '\\noun', '\\color', '\\lang']
566 attribute_values = ['default', 'default', 'default', 'default',
567 'default', 'default', 'default', 'none', file.language]
570 i = find_token(file.body, "\\begin_layout", i)
573 layout = split(file.body[i])[1]
576 # Merge all paragraph parameters into a single line
577 # We cannot check for '\\' only because paragraphs may start e.g.
579 while file.body[i + 1][:1] == '\\' and split(file.body[i + 1][1:])[0] in par_params:
580 file.body[i] = file.body[i + 1] + ' ' + file.body[i]
583 line_top = find(file.body[i],"\\line_top")
584 line_bot = find(file.body[i],"\\line_bottom")
585 pb_top = find(file.body[i],"\\pagebreak_top")
586 pb_bot = find(file.body[i],"\\pagebreak_bottom")
587 vspace_top = find(file.body[i],"\\added_space_top")
588 vspace_bot = find(file.body[i],"\\added_space_bottom")
590 if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1:
593 # Do we have a nonstandard paragraph? We need to create new paragraphs
594 # if yes to avoid putting lyxline etc. inside of special environments.
595 # This is wrong for itemize and enumerate environments, but it is
596 # impossible to convert these correctly.
597 # We want to avoid new paragraphs if possible becauase we want to
598 # inherit font sizes.
600 if (layout != "Standard" or find(file.body[i],"\\align") != -1 or
601 find(file.body[i],"\\labelwidthstring") != -1 or
602 find(file.body[i],"\\noindent") != -1):
605 # get the font size of the beginning of this paragraph, since we need
606 # it for the lyxline inset
608 while not is_nonempty_line(file.body[j]):
611 if find(file.body[j], "\\size") != -1:
612 size_top = split(file.body[j])[1]
614 for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom":
615 file.body[i] = replace(file.body[i], tag, "")
618 # the position could be change because of the removal of other
619 # paragraph properties above
620 vspace_top = find(file.body[i],"\\added_space_top")
621 tmp_list = split(file.body[i][vspace_top:])
622 vspace_top_value = tmp_list[1]
623 file.body[i] = file.body[i][:vspace_top] + join(tmp_list[2:])
626 # the position could be change because of the removal of other
627 # paragraph properties above
628 vspace_bot = find(file.body[i],"\\added_space_bottom")
629 tmp_list = split(file.body[i][vspace_bot:])
630 vspace_bot_value = tmp_list[1]
631 file.body[i] = file.body[i][:vspace_bot] + join(tmp_list[2:])
633 file.body[i] = strip(file.body[i])
636 # Create an empty paragraph or paragraph fragment for line and
637 # page break that belong above the paragraph
638 if pb_top !=-1 or line_top != -1 or vspace_top != -1:
640 paragraph_above = list()
642 # We need to create an extra paragraph for nonstandard environments
643 paragraph_above = ['\\begin_layout Standard', '']
646 paragraph_above.extend(['\\newpage ',''])
649 paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','',''])
653 paragraph_above.extend(['\\size ' + size_top + ' '])
654 # We need an additional vertical space of -\parskip.
655 # We can't use the vspace inset because it does not know \parskip.
656 paragraph_above.extend(['\\lyxline ', '', ''])
657 insert_ert(paragraph_above, len(paragraph_above) - 1, 'Collapsed',
658 '\\vspace{-1\\parskip}\n', file.format + 1)
659 paragraph_above.extend([''])
662 paragraph_above.extend(['\\end_layout ',''])
663 # insert new paragraph above the current paragraph
664 file.body[i-2:i-2] = paragraph_above
666 # insert new lines at the beginning of the current paragraph
667 file.body[i:i] = paragraph_above
669 i = i + len(paragraph_above)
671 # Ensure that nested style are converted later.
672 k = find_end_of(file.body, i, "\\begin_layout", "\\end_layout")
677 if pb_bot !=-1 or line_bot != -1 or vspace_bot != -1:
679 # get the font size of the end of this paragraph
683 if find(file.body[j], "\\size") != -1:
684 size_bot = split(file.body[j])[1]
686 elif find(file.body[j], "\\begin_inset") != -1:
688 j = find_end_of_inset(file.body, j)
692 paragraph_below = list()
694 # We need to create an extra paragraph for nonstandard environments
695 paragraph_below = ['', '\\begin_layout Standard', '']
697 for a in range(len(font_attributes)):
698 if find_token(file.body, font_attributes[a], i, k) != -1:
699 paragraph_below.extend([font_attributes[a] + ' ' + attribute_values[a]])
702 if nonstandard and size_bot != '':
703 paragraph_below.extend(['\\size ' + size_bot + ' '])
704 paragraph_below.extend(['\\lyxline ',''])
706 paragraph_below.extend(['\\size default '])
709 paragraph_below.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','',''])
712 paragraph_below.extend(['\\newpage ',''])
715 paragraph_below.extend(['\\end_layout '])
716 # insert new paragraph below the current paragraph
717 file.body[k+1:k+1] = paragraph_below
719 # insert new lines at the end of the current paragraph
720 file.body[k:k] = paragraph_below
726 def convert_note(file):
729 i = find_tokens(file.body, ["\\begin_inset Note",
730 "\\begin_inset Comment",
731 "\\begin_inset Greyedout"], i)
735 file.body[i] = file.body[i][0:13] + 'Note ' + file.body[i][13:]
739 def revert_note(file):
740 note_header = "\\begin_inset Note "
743 i = find_token(file.body, note_header, i)
747 file.body[i] = "\\begin_inset " + file.body[i][len(note_header):]
754 def convert_box(file):
757 i = find_tokens(file.body, ["\\begin_inset Boxed",
758 "\\begin_inset Doublebox",
759 "\\begin_inset Frameless",
760 "\\begin_inset ovalbox",
761 "\\begin_inset Ovalbox",
762 "\\begin_inset Shadowbox"], i)
766 file.body[i] = file.body[i][0:13] + 'Box ' + file.body[i][13:]
770 def revert_box(file):
771 box_header = "\\begin_inset Box "
774 i = find_token(file.body, box_header, i)
778 file.body[i] = "\\begin_inset " + file.body[i][len(box_header):]
785 def convert_collapsable(file):
788 i = find_tokens(file.body, ["\\begin_inset Box",
789 "\\begin_inset Branch",
790 "\\begin_inset CharStyle",
791 "\\begin_inset Float",
792 "\\begin_inset Foot",
793 "\\begin_inset Marginal",
794 "\\begin_inset Note",
795 "\\begin_inset OptArg",
796 "\\begin_inset Wrap"], i)
800 # Seach for a line starting 'collapsed'
801 # If, however, we find a line starting '\begin_layout'
802 # (_always_ present) then break with a warning message
805 if (file.body[i] == "collapsed false"):
806 file.body[i] = "status open"
808 elif (file.body[i] == "collapsed true"):
809 file.body[i] = "status collapsed"
811 elif (file.body[i][:13] == "\\begin_layout"):
812 file.warning("Malformed LyX file: Missing 'collapsed'.")
819 def revert_collapsable(file):
822 i = find_tokens(file.body, ["\\begin_inset Box",
823 "\\begin_inset Branch",
824 "\\begin_inset CharStyle",
825 "\\begin_inset Float",
826 "\\begin_inset Foot",
827 "\\begin_inset Marginal",
828 "\\begin_inset Note",
829 "\\begin_inset OptArg",
830 "\\begin_inset Wrap"], i)
834 # Seach for a line starting 'status'
835 # If, however, we find a line starting '\begin_layout'
836 # (_always_ present) then break with a warning message
839 if (file.body[i] == "status open"):
840 file.body[i] = "collapsed false"
842 elif (file.body[i] == "status collapsed" or
843 file.body[i] == "status inlined"):
844 file.body[i] = "collapsed true"
846 elif (file.body[i][:13] == "\\begin_layout"):
847 file.warning("Malformed LyX file: Missing 'status'.")
857 def convert_ert(file):
860 i = find_token(file.body, "\\begin_inset ERT", i)
864 # Seach for a line starting 'status'
865 # If, however, we find a line starting '\begin_layout'
866 # (_always_ present) then break with a warning message
869 if (file.body[i] == "status Open"):
870 file.body[i] = "status open"
872 elif (file.body[i] == "status Collapsed"):
873 file.body[i] = "status collapsed"
875 elif (file.body[i] == "status Inlined"):
876 file.body[i] = "status inlined"
878 elif (file.body[i][:13] == "\\begin_layout"):
879 file.warning("Malformed LyX file: Missing 'status'.")
886 def revert_ert(file):
889 i = find_token(file.body, "\\begin_inset ERT", i)
893 # Seach for a line starting 'status'
894 # If, however, we find a line starting '\begin_layout'
895 # (_always_ present) then break with a warning message
898 if (file.body[i] == "status open"):
899 file.body[i] = "status Open"
901 elif (file.body[i] == "status collapsed"):
902 file.body[i] = "status Collapsed"
904 elif (file.body[i] == "status inlined"):
905 file.body[i] = "status Inlined"
907 elif (file.body[i][:13] == "\\begin_layout"):
908 file.warning("Malformed LyX file : Missing 'status'.")
918 def convert_minipage(file):
919 """ Convert minipages to the box inset.
920 We try to use the same order of arguments as lyx does.
923 inner_pos = ["c","t","b","s"]
927 i = find_token(file.body, "\\begin_inset Minipage", i)
931 file.body[i] = "\\begin_inset Box Frameless"
934 # convert old to new position using the pos list
935 if file.body[i][:8] == "position":
936 file.body[i] = 'position "%s"' % pos[int(file.body[i][9])]
938 file.body.insert(i, 'position "%s"' % pos[0])
941 file.body.insert(i, 'hor_pos "c"')
943 file.body.insert(i, 'has_inner_box 1')
946 # convert the inner_position
947 if file.body[i][:14] == "inner_position":
948 file.body[i] = 'inner_pos "%s"' % inner_pos[int(file.body[i][15])]
950 file.body.insert('inner_pos "%s"' % inner_pos[0])
953 # We need this since the new file format has a height and width
954 # in a different order.
955 if file.body[i][:6] == "height":
956 height = file.body[i][6:]
957 # test for default value of 221 and convert it accordingly
958 if height == ' "0pt"' or height == ' "0"':
964 if file.body[i][:5] == "width":
965 width = file.body[i][5:]
970 if file.body[i][:9] == "collapsed":
971 if file.body[i][9:] == "true":
979 file.body.insert(i, 'use_parbox 0')
981 file.body.insert(i, 'width' + width)
983 file.body.insert(i, 'special "none"')
985 file.body.insert(i, 'height' + height)
987 file.body.insert(i, 'height_special "totalheight"')
989 file.body.insert(i, 'status ' + status)
993 # -------------------------------------------------------------------------------------------
994 # Convert backslashes and '\n' into valid ERT code, append the converted
995 # text to body[i] and return the (maybe incremented) line index i
996 def convert_ertbackslash(body, i, ert, format):
999 body[i] = body[i] + '\\backslash '
1004 body[i+1:i+1] = ['\\newline ', '']
1007 body[i+1:i+1] = ['\\end_layout', '', '\\begin_layout Standard', '']
1010 body[i] = body[i] + c
1014 # Converts lines in ERT code to LaTeX
1015 # The surrounding \begin_layout ... \end_layout pair must not be included
1016 def ert2latex(lines, format):
1017 backslash = re.compile(r'\\backslash\s*$')
1018 newline = re.compile(r'\\newline\s*$')
1020 begin_layout = re.compile(r'\\layout\s*\S+$')
1022 begin_layout = re.compile(r'\\begin_layout\s*\S+$')
1023 end_layout = re.compile(r'\\end_layout\s*$')
1025 for i in range(len(lines)):
1026 line = backslash.sub('\\\\', lines[i])
1028 if begin_layout.match(line):
1031 line = newline.sub('\n', line)
1033 if begin_layout.match(line):
1035 if format > 224 and end_layout.match(line):
1041 # get all paragraph parameters. They can be all on one line or on several lines.
1042 # lines[i] must be the first parameter line
1043 def get_par_params(lines, i):
1044 par_params = ('added_space_bottom', 'added_space_top', 'align',
1045 'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
1046 'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
1047 'start_of_appendix')
1048 # We cannot check for '\\' only because paragraphs may start e.g.
1049 # with '\\backslash'
1051 while lines[i][:1] == '\\' and split(lines[i][1:])[0] in par_params:
1052 params = params + ' ' + strip(lines[i])
1054 return strip(params)
1057 # convert LyX font size to LaTeX fontsize
1058 def lyxsize2latexsize(lyxsize):
1059 sizes = {"tiny" : "tiny", "scriptsize" : "scriptsize",
1060 "footnotesize" : "footnotesize", "small" : "small",
1061 "normal" : "normalsize", "large" : "large", "larger" : "Large",
1062 "largest" : "LARGE", "huge" : "huge", "giant" : "Huge"}
1063 if lyxsize in sizes:
1064 return '\\' + sizes[lyxsize]
1068 # Change vspace insets, page breaks and lyxlines to paragraph options
1069 # (if possible) or ERT
1070 def revert_breaks(file):
1072 # Get default spaceamount
1073 i = find_token(file.header, '\\defskip', 0)
1075 defskipamount = 'medskip'
1077 defskipamount = split(file.header[i])[1]
1079 keys = {"\\begin_inset" : "vspace", "\\lyxline" : "lyxline",
1080 "\\newpage" : "newpage"}
1081 keywords_top = {"vspace" : "\\added_space_top", "lyxline" : "\\line_top",
1082 "newpage" : "\\pagebreak_top"}
1083 keywords_bot = {"vspace" : "\\added_space_bottom", "lyxline" : "\\line_bottom",
1084 "newpage" : "\\pagebreak_bottom"}
1085 tokens = ["\\begin_inset VSpace", "\\lyxline", "\\newpage"]
1087 # Convert the insets
1090 i = find_tokens(file.body, tokens, i)
1094 # Are we at the beginning of a paragraph?
1096 this_par = get_paragraph(file.body, i, file.format - 1)
1097 start = this_par + 1
1098 params = get_par_params(file.body, start)
1100 # Paragraph parameters may be on one or more lines.
1101 # Find the start of the real paragraph text.
1102 while file.body[start][:1] == '\\' and split(file.body[start])[0] in params:
1104 for k in range(start, i):
1105 if find(file.body[k], "\\size") != -1:
1107 size = split(file.body[k])[1]
1108 elif is_nonempty_line(file.body[k]):
1111 # Find the end of the real paragraph text.
1112 next_par = get_next_paragraph(file.body, i, file.format - 1)
1114 file.warning("Malformed LyX file: Missing next paragraph.")
1118 # first line of our insets
1120 # last line of our insets
1121 inset_end = inset_start
1122 # Are we at the end of a paragraph?
1124 # start and end line numbers to delete if we convert this inset
1126 # is this inset a lyxline above a paragraph?
1128 # raw inset information
1130 # name of this inset
1132 # font size of this inset
1135 # Detect subsequent lyxline, vspace and pagebreak insets created by convert_breaks()
1139 if find_tokens(file.body, tokens, k) == k:
1141 lines.append(split(file.body[k]))
1142 insets.append(keys[lines[n][0]])
1143 del_lines.append([k, k])
1148 elif find(file.body[k], "\\size") != -1:
1150 size = split(file.body[k])[1]
1151 elif find_token(file.body, "\\begin_inset ERT", k) == k:
1152 ert_begin = find_token(file.body, "\\layout", k) + 1
1154 file.warning("Malformed LyX file: Missing '\\layout'.")
1156 ert_end = find_end_of_inset(file.body, k)
1158 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1160 ert = ert2latex(file.body[ert_begin:ert_end], file.format - 1)
1161 if (n > 0 and insets[n - 1] == "lyxline" and
1162 ert == '\\vspace{-1\\parskip}\n'):
1163 # vspace ERT created by convert_breaks() for top lyxline
1165 del_lines[n - 1][1] = ert_end
1171 elif (n > 0 and insets[n - 1] == "vspace" and
1172 find_token(file.body, "\\end_inset", k) == k):
1173 # ignore end of vspace inset
1174 del_lines[n - 1][1] = k
1176 elif is_nonempty_line(file.body[k]):
1181 # Determine space amount for vspace insets
1182 spaceamount = list()
1185 if insets[k] == "vspace":
1186 spaceamount.append(lines[k][2])
1187 arguments.append(' ' + spaceamount[k] + ' ')
1189 spaceamount.append('')
1190 arguments.append(' ')
1192 # Can we convert to top paragraph parameters?
1194 if ((n == 3 and insets[0] == "newpage" and insets[1] == "vspace" and
1195 insets[2] == "lyxline" and top[2]) or
1197 ((insets[0] == "newpage" and insets[1] == "vspace") or
1198 (insets[0] == "newpage" and insets[1] == "lyxline" and top[1]) or
1199 (insets[0] == "vspace" and insets[1] == "lyxline" and top[1]))) or
1200 (n == 1 and insets[0] == "lyxline" and top[0])):
1201 # These insets have been created before a paragraph by
1205 # Can we convert to bottom paragraph parameters?
1207 if ((n == 3 and insets[0] == "lyxline" and not top[0] and
1208 insets[1] == "vspace" and insets[2] == "newpage") or
1210 ((insets[0] == "lyxline" and not top[0] and insets[1] == "vspace") or
1211 (insets[0] == "lyxline" and not top[0] and insets[1] == "newpage") or
1212 (insets[0] == "vspace" and insets[1] == "newpage"))) or
1213 (n == 1 and insets[0] == "lyxline" and not top[0])):
1214 # These insets have been created after a paragraph by
1218 if paragraph_start and paragraph_end:
1219 # We are in a paragraph of our own.
1220 # We must not delete this paragraph if it has parameters
1222 # First try to merge with the previous paragraph.
1223 # We try the previous paragraph first because we would
1224 # otherwise need ERT for two subsequent vspaces.
1225 prev_par = get_paragraph(file.body, this_par - 1, file.format - 1) + 1
1226 if prev_par > 0 and not before:
1227 prev_params = get_par_params(file.body, prev_par + 1)
1229 # determine font size
1230 prev_size = "normal"
1232 while file.body[k][:1] == '\\' and split(file.body[k])[0] in prev_params:
1235 if find(file.body[k], "\\size") != -1:
1236 prev_size = split(file.body[k])[1]
1238 elif find(file.body[k], "\\begin_inset") != -1:
1240 k = find_end_of_inset(file.body, k)
1241 elif is_nonempty_line(file.body[k]):
1245 if (keywords_bot[insets[k]] in prev_params or
1246 (insets[k] == "lyxline" and sizes[k] != prev_size)):
1251 file.body.insert(prev_par + 1,
1252 keywords_bot[insets[k]] + arguments[k])
1253 del file.body[this_par+n:next_par-1+n]
1256 # Then try next paragraph
1257 if next_par > 0 and not after:
1258 next_params = get_par_params(file.body, next_par + 1)
1260 while file.body[k][:1] == '\\' and split(file.body[k])[0] in next_params:
1262 # determine font size
1263 next_size = "normal"
1266 if find(file.body[k], "\\size") != -1:
1267 next_size = split(file.body[k])[1]
1269 elif is_nonempty_line(file.body[k]):
1273 if (keywords_top[insets[k]] in next_params or
1274 (insets[k] == "lyxline" and sizes[k] != next_size)):
1279 file.body.insert(next_par + 1,
1280 keywords_top[insets[k]] + arguments[k])
1281 del file.body[this_par:next_par-1]
1284 elif paragraph_start or paragraph_end:
1285 # Convert to paragraph formatting if we are at the beginning or end
1286 # of a paragraph and the resulting paragraph would not be empty
1287 # The order is important: del and insert invalidate some indices
1289 keywords = keywords_top
1291 keywords = keywords_bot
1294 if keywords[insets[k]] in params:
1299 file.body.insert(this_par + 1,
1300 keywords[insets[k]] + arguments[k])
1301 for j in range(k, n):
1302 del_lines[j][0] = del_lines[j][0] + 1
1303 del_lines[j][1] = del_lines[j][1] + 1
1304 del file.body[del_lines[k][0]:del_lines[k][1]+1]
1305 deleted = del_lines[k][1] - del_lines[k][0] + 1
1306 for j in range(k + 1, n):
1307 del_lines[j][0] = del_lines[j][0] - deleted
1308 del_lines[j][1] = del_lines[j][1] - deleted
1312 # Convert the first inset to ERT.
1313 # The others are converted in the next loop runs (if they exist)
1314 if insets[0] == "vspace":
1315 file.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
1316 '\\layout Standard', '', '\\backslash ']
1318 if spaceamount[0][-1] == '*':
1319 spaceamount[0] = spaceamount[0][:-1]
1324 # Replace defskip by the actual value
1325 if spaceamount[0] == 'defskip':
1326 spaceamount[0] = defskipamount
1328 # LaTeX does not know \\smallskip* etc
1330 if spaceamount[0] == 'smallskip':
1331 spaceamount[0] = '\\smallskipamount'
1332 elif spaceamount[0] == 'medskip':
1333 spaceamount[0] = '\\medskipamount'
1334 elif spaceamount[0] == 'bigskip':
1335 spaceamount[0] = '\\bigskipamount'
1336 elif spaceamount[0] == 'vfill':
1337 spaceamount[0] = '\\fill'
1339 # Finally output the LaTeX code
1340 if (spaceamount[0] == 'smallskip' or spaceamount[0] == 'medskip' or
1341 spaceamount[0] == 'bigskip' or spaceamount[0] == 'vfill'):
1342 file.body.insert(i, spaceamount[0] + '{}')
1345 file.body.insert(i, 'vspace*{')
1347 file.body.insert(i, 'vspace{')
1348 i = convert_ertbackslash(file.body, i, spaceamount[0], file.format - 1)
1349 file.body[i] = file.body[i] + '}'
1351 elif insets[0] == "lyxline":
1353 latexsize = lyxsize2latexsize(size)
1355 file.warning("Could not convert LyX fontsize '%s' to LaTeX font size." % size)
1356 latexsize = '\\normalsize'
1357 i = insert_ert(file.body, i, 'Collapsed',
1358 '\\lyxline{%s}' % latexsize,
1360 # We use \providecommand so that we don't get an error if native
1361 # lyxlines are used (LyX writes first its own preamble and then
1362 # the user specified one)
1363 add_to_preamble(file,
1364 ['% Commands inserted by lyx2lyx for lyxlines',
1365 '\\providecommand{\\lyxline}[1]{',
1366 ' {#1 \\vspace{1ex} \\hrule width \\columnwidth \\vspace{1ex}}'
1368 elif insets[0] == "newpage":
1370 i = insert_ert(file.body, i, 'Collapsed', '\\newpage{}',
1374 # Convert a LyX length into a LaTeX length
1375 def convert_len(len, special):
1376 units = {"text%":"\\textwidth", "col%":"\\columnwidth",
1377 "page%":"\\pagewidth", "line%":"\\linewidth",
1378 "theight%":"\\textheight", "pheight%":"\\pageheight"}
1380 # Convert special lengths
1381 if special != 'none':
1382 len = '%f\\' % len2value(len) + special
1384 # Convert LyX units to LaTeX units
1385 for unit in units.keys():
1386 if find(len, unit) != -1:
1387 len = '%f' % (len2value(len) / 100) + units[unit]
1393 # Convert a LyX length into valid ERT code and append it to body[i]
1394 # Return the (maybe incremented) line index i
1395 def convert_ertlen(body, i, len, special, format):
1396 # Convert backslashes and insert the converted length into body
1397 return convert_ertbackslash(body, i, convert_len(len, special), format)
1400 # Return the value of len without the unit in numerical form
1402 result = re.search('([+-]?[0-9.]+)', len)
1404 return float(result.group(1))
1405 # No number means 1.0
1409 # Convert text to ERT and insert it at body[i]
1410 # Return the index of the line after the inserted ERT
1411 def insert_ert(body, i, status, text, format):
1412 body[i:i] = ['\\begin_inset ERT', 'status ' + status, '']
1415 body[i:i] = ['\\layout Standard', '']
1417 body[i:i] = ['\\begin_layout Standard', '']
1418 i = i + 1 # i points now to the just created empty line
1419 i = convert_ertbackslash(body, i, text, format) + 1
1421 body[i:i] = ['\\end_layout']
1423 body[i:i] = ['', '\\end_inset', '']
1428 # Add text to the preamble if it is not already there.
1429 # Only the first line is checked!
1430 def add_to_preamble(file, text):
1431 if find_token(file.preamble, text[0], 0) != -1:
1434 file.preamble.extend(text)
1437 def convert_frameless_box(file):
1438 pos = ['t', 'c', 'b']
1439 inner_pos = ['c', 't', 'b', 's']
1442 i = find_token(file.body, '\\begin_inset Frameless', i)
1445 j = find_end_of_inset(file.body, i)
1447 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1454 params = {'position':0, 'hor_pos':'c', 'has_inner_box':'1',
1455 'inner_pos':1, 'use_parbox':'0', 'width':'100col%',
1456 'special':'none', 'height':'1in',
1457 'height_special':'totalheight', 'collapsed':'false'}
1458 for key in params.keys():
1459 value = replace(get_value(file.body, key, i, j), '"', '')
1461 if key == 'position':
1462 # convert new to old position: 'position "t"' -> 0
1463 value = find_token(pos, value, 0)
1466 elif key == 'inner_pos':
1467 # convert inner position
1468 value = find_token(inner_pos, value, 0)
1473 j = del_token(file.body, key, i, j)
1476 # Convert to minipage or ERT?
1477 # Note that the inner_position and height parameters of a minipage
1478 # inset are ignored and not accessible for the user, although they
1479 # are present in the file format and correctly read in and written.
1480 # Therefore we convert to ERT if they do not have their LaTeX
1481 # defaults. These are:
1482 # - the value of "position" for "inner_pos"
1483 # - "\totalheight" for "height"
1484 if (params['use_parbox'] != '0' or
1485 params['has_inner_box'] != '1' or
1486 params['special'] != 'none' or
1487 params['height_special'] != 'totalheight' or
1488 len2value(params['height']) != 1.0):
1490 # Here we know that this box is not supported in file format 224.
1491 # Therefore we need to convert it to ERT. We can't simply convert
1492 # the beginning and end of the box to ERT, because the
1493 # box inset may contain layouts that are different from the
1494 # surrounding layout. After the conversion the contents of the
1495 # box inset is on the same level as the surrounding text, and
1496 # paragraph layouts and align parameters can get mixed up.
1498 # A possible solution for this problem:
1499 # Convert the box to a minipage and redefine the minipage
1500 # environment in ERT so that the original box is simulated.
1501 # For minipages we could do this in a way that the width and
1502 # position can still be set from LyX, but this did not work well.
1503 # This is not possible for parboxes either, so we convert the
1504 # original box to ERT, put the minipage inset inside the box
1505 # and redefine the minipage environment to be empty.
1507 # Commands that are independant of a particular box can go to
1509 # We need to define lyxtolyxrealminipage with 3 optional
1510 # arguments although LyX 1.3 uses only the first one.
1511 # Otherwise we will get LaTeX errors if this document is
1512 # converted to format 225 or above again (LyX 1.4 uses all
1513 # optional arguments).
1514 add_to_preamble(file,
1515 ['% Commands inserted by lyx2lyx for frameless boxes',
1516 '% Save the original minipage environment',
1517 '\\let\\lyxtolyxrealminipage\\minipage',
1518 '\\let\\endlyxtolyxrealminipage\\endminipage',
1519 '% Define an empty lyxtolyximinipage environment',
1520 '% with 3 optional arguments',
1521 '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1522 '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1523 ' {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1524 ' {\\end{lyxtolyxiiiminipage}}',
1525 '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1526 ' {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1527 ' {\\end{lyxtolyxiiminipage}}',
1528 '\\newenvironment{lyxtolyxminipage}[1][c]%',
1529 ' {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1530 ' {\\end{lyxtolyximinipage}}'])
1532 if params['use_parbox'] != '0':
1535 ert = '\\begin{lyxtolyxrealminipage}'
1537 # convert optional arguments only if not latex default
1538 if (pos[params['position']] != 'c' or
1539 inner_pos[params['inner_pos']] != pos[params['position']] or
1540 params['height_special'] != 'totalheight' or
1541 len2value(params['height']) != 1.0):
1542 ert = ert + '[' + pos[params['position']] + ']'
1543 if (inner_pos[params['inner_pos']] != pos[params['position']] or
1544 params['height_special'] != 'totalheight' or
1545 len2value(params['height']) != 1.0):
1546 ert = ert + '[' + convert_len(params['height'],
1547 params['height_special']) + ']'
1548 if inner_pos[params['inner_pos']] != pos[params['position']]:
1549 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1551 ert = ert + '{' + convert_len(params['width'],
1552 params['special']) + '}'
1554 if params['use_parbox'] != '0':
1556 ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1557 ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1560 i = insert_ert(file.body, i, 'Collapsed', ert, file.format - 1)
1561 j = j + i - old_i - 1
1563 file.body[i:i] = ['\\begin_inset Minipage',
1564 'position %d' % params['position'],
1567 'width "' + params['width'] + '"',
1568 'collapsed ' + params['collapsed']]
1572 # Restore the original minipage environment since we may have
1573 # minipages inside this box.
1574 # Start a new paragraph because the following may be nonstandard
1575 file.body[i:i] = ['\\layout Standard', '', '']
1578 ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1579 ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1581 i = insert_ert(file.body, i, 'Collapsed', ert, file.format - 1)
1582 j = j + i - old_i - 1
1584 # Redefine the minipage end before the inset end.
1585 # Start a new paragraph because the previous may be nonstandard
1586 file.body[j:j] = ['\\layout Standard', '', '']
1588 ert = '\\let\\endminipage\\endlyxtolyxminipage'
1589 j = insert_ert(file.body, j, 'Collapsed', ert, file.format - 1)
1591 file.body.insert(j, '')
1594 # LyX writes '%\n' after each box. Therefore we need to end our
1595 # ERT with '%\n', too, since this may swallow a following space.
1596 if params['use_parbox'] != '0':
1599 ert = '\\end{lyxtolyxrealminipage}%\n'
1600 j = insert_ert(file.body, j, 'Collapsed', ert, file.format - 1)
1602 # We don't need to restore the original minipage after the inset
1603 # end because the scope of the redefinition is the original box.
1607 # Convert to minipage
1608 file.body[i:i] = ['\\begin_inset Minipage',
1609 'position %d' % params['position'],
1610 'inner_position %d' % params['inner_pos'],
1611 'height "' + params['height'] + '"',
1612 'width "' + params['width'] + '"',
1613 'collapsed ' + params['collapsed']]
1617 def remove_branches(file):
1620 i = find_token(file.header, "\\branch", i)
1623 file.warning("Removing branch %s." % split(file.header[i])[1])
1624 j = find_token(file.header, "\\end_branch", i)
1626 file.warning("Malformed LyX file: Missing '\\end_branch'.")
1628 del file.header[i:j+1]
1632 i = find_token(file.body, "\\begin_inset Branch", i)
1635 j = find_end_of_inset(file.body, i)
1637 file.warning("Malformed LyX file: Missing '\\end_inset'.")
1641 del file.body[j - 1]
1642 # Seach for a line starting 'collapsed'
1643 # If, however, we find a line starting '\layout'
1644 # (_always_ present) then break with a warning message
1647 if (file.body[i][:9] == "collapsed"):
1651 elif (file.body[i][:7] == "\\layout"):
1652 if collapsed_found == 0:
1653 file.warning("Malformed LyX file: Missing 'collapsed'.")
1654 # Delete this new paragraph, since it would not appear in
1655 # .tex output. This avoids also empty paragraphs.
1665 def convert_jurabib(file):
1666 i = find_token(file.header, '\\use_numerical_citations', 0)
1668 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1670 file.header.insert(i + 1, '\\use_jurabib 0')
1673 def revert_jurabib(file):
1674 i = find_token(file.header, '\\use_jurabib', 0)
1676 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1678 if get_value(file.header, '\\use_jurabib', 0) != "0":
1679 file.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1680 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1688 def convert_bibtopic(file):
1689 i = find_token(file.header, '\\use_jurabib', 0)
1691 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1693 file.header.insert(i + 1, '\\use_bibtopic 0')
1696 def revert_bibtopic(file):
1697 i = find_token(file.header, '\\use_bibtopic', 0)
1699 file.warning("Malformed lyx file: Missing '\\use_bibtopic'.")
1701 if get_value(file.header, '\\use_bibtopic', 0) != "0":
1702 file.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1703 # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1710 def convert_float(file):
1713 i = find_token(file.body, '\\begin_inset Float', i)
1716 # Seach for a line starting 'wide'
1717 # If, however, we find a line starting '\begin_layout'
1718 # (_always_ present) then break with a warning message
1721 if (file.body[i][:4] == "wide"):
1722 file.body.insert(i + 1, 'sideways false')
1724 elif (file.body[i][:13] == "\\begin_layout"):
1725 file.warning("Malformed lyx file: Missing 'wide'.")
1731 def revert_float(file):
1734 i = find_token(file.body, '\\begin_inset Float', i)
1737 j = find_end_of_inset(file.body, i)
1739 file.warning("Malformed lyx file: Missing '\\end_inset'.")
1742 if get_value(file.body, 'sideways', i, j) != "false":
1743 file.warning("Conversion of 'sideways true' not yet implemented.")
1744 # Don't remove 'sideways' so that people will get warnings by lyx
1747 del_token(file.body, 'sideways', i, j)
1751 def convert_graphics(file):
1752 """ Add extension to filenames of insetgraphics if necessary.
1756 i = find_token(file.body, "\\begin_inset Graphics", i)
1760 j = find_token2(file.body, "filename", i)
1764 filename = split(file.body[j])[1]
1765 absname = os.path.normpath(os.path.join(file.dir, filename))
1766 if file.input == stdin and not os.path.isabs(filename):
1767 # We don't know the directory and cannot check the file.
1768 # We could use a heuristic and take the current directory,
1769 # and we could try to find out if filename has an extension,
1770 # but that would be just guesses and could be wrong.
1771 file.warning("""Warning: Can not determine whether file
1773 needs an extension when reading from standard input.
1774 You may need to correct the file manually or run
1775 lyx2lyx again with the .lyx file as commandline argument.""" % filename)
1777 # This needs to be the same algorithm as in pre 233 insetgraphics
1778 if access(absname, F_OK):
1780 if access(absname + ".ps", F_OK):
1781 file.body[j] = replace(file.body[j], filename, filename + ".ps")
1783 if access(absname + ".eps", F_OK):
1784 file.body[j] = replace(file.body[j], filename, filename + ".eps")
1788 # Convert firstname and surname from styles -> char styles
1790 def convert_names(file):
1791 """ Convert in the docbook backend from firstname and surname style
1794 if file.backend != "docbook":
1800 i = find_token(file.body, "\\begin_layout Author", i)
1805 while file.body[i] == "":
1808 if file.body[i][:11] != "\\end_layout" or file.body[i+2][:13] != "\\begin_deeper":
1813 i = find_end_of( file.body, i+3, "\\begin_deeper","\\end_deeper")
1815 # something is really wrong, abort
1816 file.warning("Missing \\end_deeper, after style Author.")
1817 file.warning("Aborted attempt to parse FirstName and Surname.")
1819 firstname, surname = "", ""
1821 name = file.body[k:i]
1823 j = find_token(name, "\\begin_layout FirstName", 0)
1826 while(name[j] != "\\end_layout"):
1827 firstname = firstname + name[j]
1830 j = find_token(name, "\\begin_layout Surname", 0)
1833 while(name[j] != "\\end_layout"):
1834 surname = surname + name[j]
1838 del file.body[k+2:i+1]
1840 file.body[k-1:k-1] = ["", "",
1841 "\\begin_inset CharStyle Firstname",
1844 "\\begin_layout Standard",
1852 "\\begin_inset CharStyle Surname",
1855 "\\begin_layout Standard",
1864 def revert_names(file):
1865 """ Revert in the docbook backend from firstname and surname char style
1868 if file.backend != "docbook":
1873 # \use_natbib 1 \cite_engine <style>
1874 # \use_numerical_citations 0 -> where <style> is one of
1875 # \use_jurabib 0 "basic", "natbib_authoryear",
1876 # "natbib_numerical" or "jurabib"
1877 def convert_cite_engine(file):
1878 a = find_token(file.header, "\\use_natbib", 0)
1880 file.warning("Malformed lyx file: Missing '\\use_natbib'.")
1883 b = find_token(file.header, "\\use_numerical_citations", 0)
1884 if b == -1 or b != a+1:
1885 file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1888 c = find_token(file.header, "\\use_jurabib", 0)
1889 if c == -1 or c != b+1:
1890 file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1893 use_natbib = int(split(file.header[a])[1])
1894 use_numerical_citations = int(split(file.header[b])[1])
1895 use_jurabib = int(split(file.header[c])[1])
1897 cite_engine = "basic"
1899 if use_numerical_citations:
1900 cite_engine = "natbib_numerical"
1902 cite_engine = "natbib_authoryear"
1904 cite_engine = "jurabib"
1906 del file.header[a:c+1]
1907 file.header.insert(a, "\\cite_engine " + cite_engine)
1910 def revert_cite_engine(file):
1911 i = find_token(file.header, "\\cite_engine", 0)
1913 file.warning("Malformed lyx file: Missing '\\cite_engine'.")
1916 cite_engine = split(file.header[i])[1]
1921 if cite_engine == "natbib_numerical":
1924 elif cite_engine == "natbib_authoryear":
1926 elif cite_engine == "jurabib":
1930 file.header.insert(i, "\\use_jurabib " + use_jurabib)
1931 file.header.insert(i, "\\use_numerical_citations " + use_numerical)
1932 file.header.insert(i, "\\use_natbib " + use_natbib)
1938 def convert_paperpackage(file):
1939 i = find_token(file.header, "\\paperpackage", 0)
1943 packages = {'default':'none','a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
1944 if len(split(file.header[i])) > 1:
1945 paperpackage = split(file.header[i])[1]
1946 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1948 file.header[i] = file.header[i] + ' widemarginsa4'
1951 def revert_paperpackage(file):
1952 i = find_token(file.header, "\\paperpackage", 0)
1956 packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
1957 'widemarginsa4':'', 'default': 'default'}
1958 if len(split(file.header[i])) > 1:
1959 paperpackage = split(file.header[i])[1]
1961 paperpackage = 'default'
1962 file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1968 def convert_bullets(file):
1971 i = find_token(file.header, "\\bullet", i)
1974 if file.header[i][:12] == '\\bulletLaTeX':
1975 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1])
1978 file.header[i] = file.header[i] + ' ' + strip(file.header[i+1]) +\
1979 ' ' + strip(file.header[i+2]) + ' ' + strip(file.header[i+3])
1981 del file.header[i+1:i + n]
1985 def revert_bullets(file):
1988 i = find_token(file.header, "\\bullet", i)
1991 if file.header[i][:12] == '\\bulletLaTeX':
1992 n = find(file.header[i], '"')
1994 file.warning("Malformed header.")
1997 file.header[i:i+1] = [file.header[i][:n-1],'\t' + file.header[i][n:], '\\end_bullet']
2000 frag = split(file.header[i])
2002 file.warning("Malformed header.")
2005 file.header[i:i+1] = [frag[0] + ' ' + frag[1],
2014 # \begin_header and \begin_document
2016 def add_begin_header(file):
2017 i = find_token(file.header, '\\lyxformat', 0)
2018 file.header.insert(i+1, '\\begin_header')
2019 file.header.insert(i+1, '\\begin_document')
2022 def remove_begin_header(file):
2023 i = find_token(file.header, "\\begin_document", 0)
2026 i = find_token(file.header, "\\begin_header", 0)
2032 # \begin_file.body and \end_file.body
2034 def add_begin_body(file):
2035 file.body.insert(0, '\\begin_body')
2036 file.body.insert(1, '')
2037 i = find_token(file.body, "\\end_document", 0)
2038 file.body.insert(i, '\\end_body')
2040 def remove_begin_body(file):
2041 i = find_token(file.body, "\\begin_body", 0)
2044 if not file.body[i]:
2046 i = find_token(file.body, "\\end_body", 0)
2054 def normalize_papersize(file):
2055 i = find_token(file.header, '\\papersize', 0)
2059 tmp = split(file.header[i])
2060 if tmp[1] == "Default":
2061 file.header[i] = '\\papersize default'
2063 if tmp[1] == "Custom":
2064 file.header[i] = '\\papersize custom'
2067 def denormalize_papersize(file):
2068 i = find_token(file.header, '\\papersize', 0)
2072 tmp = split(file.header[i])
2073 if tmp[1] == "custom":
2074 file.header[i] = '\\papersize Custom'
2078 # Strip spaces at end of command line
2080 def strip_end_space(file):
2081 for i in range(len(file.body)):
2082 if file.body[i][:1] == '\\':
2083 file.body[i] = strip(file.body[i])
2087 # Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes
2089 def use_x_boolean(file):
2090 bin2bool = {'0': 'false', '1': 'true'}
2091 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2092 i = find_token(file.header, use, 0)
2095 decompose = split(file.header[i])
2096 file.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
2099 def use_x_binary(file):
2100 bool2bin = {'false': '0', 'true': '1'}
2101 for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
2102 i = find_token(file.header, use, 0)
2105 decompose = split(file.header[i])
2106 file.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
2109 # Place all the paragraph parameters in their own line
2111 def normalize_paragraph_params(file):
2113 allowed_parameters = '\\paragraph_spacing', '\\noindent', '\\align', '\\labelwidthstring', "\\start_of_appendix", "\\leftindent"
2117 i = find_token(file.body, '\\begin_layout', i)
2123 if strip(body[i]) and split(body[i])[0] not in allowed_parameters:
2126 j = find(body[i],'\\', 1)
2129 body[i:i+1] = [strip(body[i][:j]), body[i][j:]]
2135 # Add/remove output_changes parameter
2137 def convert_output_changes (file):
2138 i = find_token(file.header, '\\tracking_changes', 0)
2140 file.warning("Malformed lyx file: Missing '\\tracking_changes'.")
2142 file.header.insert(i+1, '\\output_changes true')
2145 def revert_output_changes (file):
2146 i = find_token(file.header, '\\output_changes', 0)
2153 # Convert paragraph breaks and sanitize paragraphs
2155 def convert_ert_paragraphs(file):
2156 forbidden_settings = [
2157 # paragraph parameters
2158 '\\paragraph_spacing', '\\labelwidthstring',
2159 '\\start_of_appendix', '\\noindent',
2160 '\\leftindent', '\\align',
2162 '\\family', '\\series', '\\shape', '\\size',
2163 '\\emph', '\\numeric', '\\bar', '\\noun',
2164 '\\color', '\\lang']
2167 i = find_token(file.body, '\\begin_inset ERT', i)
2170 j = find_end_of_inset(file.body, i)
2172 file.warning("Malformed lyx file: Missing '\\end_inset'.")
2176 # convert non-standard paragraphs to standard
2179 k = find_token(file.body, "\\begin_layout", k, j)
2182 file.body[k] = "\\begin_layout Standard"
2185 # remove all paragraph parameters and font settings
2188 if (strip(file.body[k]) and
2189 split(file.body[k])[0] in forbidden_settings):
2195 # insert an empty paragraph before each paragraph but the first
2199 k = find_token(file.body, "\\begin_layout Standard", k, j)
2206 file.body[k:k] = ["\\begin_layout Standard", "",
2211 # convert \\newline to new paragraph
2214 k = find_token(file.body, "\\newline", k, j)
2217 file.body[k:k+1] = ["\\end_layout", "", "\\begin_layout Standard"]
2224 # Remove double paragraph breaks
2226 def revert_ert_paragraphs(file):
2229 i = find_token(file.body, '\\begin_inset ERT', i)
2232 j = find_end_of_inset(file.body, i)
2234 file.warning("Malformed lyx file: Missing '\\end_inset'.")
2238 # replace paragraph breaks with \newline
2241 k = find_token(file.body, "\\end_layout", k, j)
2242 l = find_token(file.body, "\\begin_layout", k, j)
2243 if k == -1 or l == -1:
2245 file.body[k:l+1] = ["\\newline"]
2249 # replace double \newlines with paragraph breaks
2252 k = find_token(file.body, "\\newline", k, j)
2256 while file.body[l] == "":
2258 if strip(file.body[l]) and split(file.body[l])[0] == "\\newline":
2259 file.body[k:l+1] = ["\\end_layout", "",
2260 "\\begin_layout Standard"]
2268 def convert_french(file):
2269 regexp = re.compile(r'^\\language\s+frenchb')
2270 i = find_re(file.header, regexp, 0)
2272 file.header[i] = "\\language french"
2274 # Change language in the document body
2275 regexp = re.compile(r'^\\lang\s+frenchb')
2278 i = find_re(file.body, regexp, i)
2281 file.body[i] = "\\lang french"
2285 def remove_paperpackage(file):
2286 i = find_token(file.header, '\\paperpackage', 0)
2291 paperpackage = split(file.header[i])[1]
2293 if paperpackage in ("a4", "a4wide", "widemarginsa4"):
2294 conv = {"a4":"\\usepackage{a4}","a4wide": "\\usepackage{a4wide}",
2295 "widemarginsa4": "\\usepackage[widemargins]{a4}"}
2296 # for compatibility we ensure it is the first entry in preamble
2297 file.preamble[0:0] = [conv[paperpackage]]
2301 i = find_token(file.header, '\\papersize', 0)
2303 file.header[i] = "\\papersize default"
2306 def remove_quotestimes(file):
2307 i = find_token(file.header, '\\quotes_times', 0)
2317 convert = [[222, [insert_tracking_changes, add_end_header]],
2318 [223, [remove_color_default, convert_spaces, convert_bibtex, remove_insetparent]],
2319 [224, [convert_external, convert_comment]],
2320 [225, [add_end_layout, layout2begin_layout, convert_end_document,
2321 convert_table_valignment_middle, convert_breaks]],
2322 [226, [convert_note]],
2323 [227, [convert_box]],
2324 [228, [convert_collapsable, convert_ert]],
2325 [229, [convert_minipage]],
2326 [230, [convert_jurabib]],
2327 [231, [convert_float]],
2328 [232, [convert_bibtopic]],
2329 [233, [convert_graphics, convert_names]],
2330 [234, [convert_cite_engine]],
2331 [235, [convert_paperpackage]],
2332 [236, [convert_bullets, add_begin_header, add_begin_body,
2333 normalize_papersize, strip_end_space]],
2334 [237, [use_x_boolean]],
2335 [238, [update_latexaccents]],
2336 [239, [normalize_paragraph_params]],
2337 [240, [convert_output_changes]],
2338 [241, [convert_ert_paragraphs]],
2339 [242, [convert_french]],
2340 [243, [remove_paperpackage]],
2341 [244, [rename_spaces]],
2342 [245, [remove_quotestimes]]]
2344 revert = [[244, []],
2345 [243, [revert_space_names]],
2348 [240, [revert_ert_paragraphs]],
2349 [239, [revert_output_changes]],
2352 [236, [use_x_binary]],
2353 [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
2355 [234, [revert_paperpackage]],
2356 [233, [revert_cite_engine]],
2357 [232, [revert_names]],
2358 [231, [revert_bibtopic]],
2359 [230, [revert_float]],
2360 [229, [revert_jurabib]],
2362 [227, [revert_collapsable, revert_ert]],
2363 [226, [revert_box, revert_external_2]],
2364 [225, [revert_note]],
2365 [224, [rm_end_layout, begin_layout2layout, revert_end_document,
2366 revert_valignment_middle, revert_breaks, convert_frameless_box,
2368 [223, [revert_external_2, revert_comment, revert_eqref]],
2369 [222, [revert_spaces, revert_bibtex]],
2370 [221, [rm_end_header, rm_tracking_changes, rm_body_changes]]]
2373 if __name__ == "__main__":