]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_1_4.py
Fix handling of older tex2lyx files.
[lyx.git] / lib / lyx2lyx / lyx_1_4.py
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>
6 #
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.
11 #
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.
16 #
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.
20
21 import re
22 from os import access, F_OK
23 import os.path
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
27 from sys import stdin
28 from string import replace, split, find, strip, join
29
30 from lyx_0_12 import update_latexaccents
31
32 ##
33 # Remove \color default
34 #
35 def remove_color_default(file):
36     i = 0
37     while 1:
38         i = find_token(file.body, "\\color default", i)
39         if i == -1:
40             return
41         file.body[i] = replace(file.body[i], "\\color default",
42                            "\\color inherit")
43
44
45 ##
46 # Add \end_header
47 #
48 def add_end_header(file):
49     file.header.append("\\end_header");
50
51
52 def rm_end_header(file):
53     i = find_token(file.header, "\\end_header", 0)
54     if i == -1:
55         return
56     del file.header[i]
57
58
59 ##
60 # \SpecialChar ~ -> \InsetSpace ~
61 #
62 def convert_spaces(file):
63     for i in range(len(file.body)):
64         file.body[i] = replace(file.body[i],"\\SpecialChar ~","\\InsetSpace ~")
65
66
67 def revert_spaces(file):
68     for i in range(len(file.body)):
69         file.body[i] = replace(file.body[i],"\\InsetSpace ~", "\\SpecialChar ~")
70
71
72 ##
73 # equivalent to lyx::support::escape()
74 #
75 def lyx_support_escape(lab):
76     hexdigit = ['0', '1', '2', '3', '4', '5', '6', '7',
77                 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
78     enc = ""
79     for c in lab:
80         o = ord(c)
81         if o >= 128 or c == '=' or c == '%':
82             enc = enc + '='
83             enc = enc + hexdigit[o >> 4]
84             enc = enc + hexdigit[o & 15]
85         else:
86             enc = enc + c
87     return enc;
88
89
90 ##
91 # \begin_inset LatexCommand \eqref -> ERT
92 #
93 def revert_eqref(file):
94     regexp = re.compile(r'^\\begin_inset\s+LatexCommand\s+\\eqref')
95     i = 0
96     while 1:
97         i = find_re(file.body, regexp, i)
98         if i == -1:
99             break
100         eqref = lyx_support_escape(regexp.sub("", file.body[i]))
101         file.body[i:i+1] = ["\\begin_inset ERT", "status Collapsed", "",
102                             "\\layout Standard", "", "\\backslash ",
103                             "eqref" + eqref]
104         i = i + 7
105
106
107 ##
108 # BibTeX changes
109 #
110 def convert_bibtex(file):
111     for i in range(len(file.body)):
112         file.body[i] = replace(file.body[i],"\\begin_inset LatexCommand \\BibTeX",
113                                   "\\begin_inset LatexCommand \\bibtex")
114
115
116 def revert_bibtex(file):
117     for i in range(len(file.body)):
118         file.body[i] = replace(file.body[i], "\\begin_inset LatexCommand \\bibtex",
119                                   "\\begin_inset LatexCommand \\BibTeX")
120
121
122 ##
123 # Remove \lyxparent
124 #
125 def remove_insetparent(file):
126     i = 0
127     while 1:
128         i = find_token(file.body, "\\begin_inset LatexCommand \\lyxparent", i)
129         if i == -1:
130             break
131         del file.body[i:i+3]
132
133
134 ##
135 #  Inset External
136 #
137 def convert_external(file):
138     external_rexp = re.compile(r'\\begin_inset External ([^,]*),"([^"]*)",')
139     external_header = "\\begin_inset External"
140     i = 0
141     while 1:
142         i = find_token(file.body, external_header, i)
143         if i == -1:
144             break
145         look = external_rexp.search(file.body[i])
146         args = ['','']
147         if look:
148             args[0] = look.group(1)
149             args[1] = look.group(2)
150         #FIXME: if the previous search fails then warn
151
152         if args[0] == "RasterImage":
153             # Convert a RasterImage External Inset to a Graphics Inset.
154             top = "\\begin_inset Graphics"
155             if args[1]:
156                 filename = "\tfilename " + args[1]
157             file.body[i:i+1] = [top, filename]
158             i = i + 1
159         else:
160             # Convert the old External Inset format to the new.
161             top = external_header
162             template = "\ttemplate " + args[0]
163             if args[1]:
164                 filename = "\tfilename " + args[1]
165                 file.body[i:i+1] = [top, template, filename]
166                 i = i + 2
167             else:
168                 file.body[i:i+1] = [top, template]
169                 i = i + 1
170
171
172 def revert_external_1(file):
173     external_header = "\\begin_inset External"
174     i = 0
175     while 1:
176         i = find_token(file.body, external_header, i)
177         if i == -1:
178             break
179
180         template = split(file.body[i+1])
181         template.reverse()
182         del file.body[i+1]
183
184         filename = split(file.body[i+1])
185         filename.reverse()
186         del file.body[i+1]
187
188         params = split(file.body[i+1])
189         params.reverse()
190         if file.body[i+1]: del file.body[i+1]
191
192         file.body[i] = file.body[i] + " " + template[0]+ ', "' + filename[0] + '", " '+ join(params[1:]) + '"'
193         i = i + 1
194
195
196 def revert_external_2(file):
197     draft_token = '\tdraft'
198     i = 0
199     while 1:
200         i = find_token(file.body, '\\begin_inset External', i)
201         if i == -1:
202             break
203         j = find_end_of_inset(file.body, i + 1)
204         if j == -1:
205             #this should not happen
206             break
207         k = find_token(file.body, draft_token, i+1, j-1)
208         if (k != -1 and len(draft_token) == len(file.body[k])):
209             del file.body[k]
210         i = j + 1
211
212
213 ##
214 # Comment
215 #
216 def convert_comment(file):
217     i = 0
218     comment = "\\layout Comment"
219     while 1:
220         i = find_token(file.body, comment, i)
221         if i == -1:
222             return
223
224         file.body[i:i+1] = ["\\layout Standard","","",
225                         "\\begin_inset Comment",
226                         "collapsed true","",
227                         "\\layout Standard"]
228         i = i + 7
229
230         while 1:
231                 old_i = i
232                 i = find_token(file.body, "\\layout", i)
233                 if i == -1:
234                     i = len(file.body) - 1
235                     file.body[i:i] = ["\\end_inset","",""]
236                     return
237
238                 j = find_token(file.body, '\\begin_deeper', old_i, i)
239                 if j == -1: j = i + 1
240                 k = find_token(file.body, '\\begin_inset', old_i, i)
241                 if k == -1: k = i + 1
242
243                 if j < i and j < k:
244                     i = j
245                     del file.body[i]
246                     i = find_end_of( file.body, i, "\\begin_deeper","\\end_deeper")
247                     if i == -1:
248                         #This case should not happen
249                         #but if this happens deal with it greacefully adding
250                         #the missing \end_deeper.
251                         i = len(file.body) - 1
252                         file.body[i:i] = ["\end_deeper",""]
253                         return
254                     else:
255                         del file.body[i]
256                         continue
257
258                 if k < i:
259                     i = k
260                     i = find_end_of( file.body, i, "\\begin_inset","\\end_inset")
261                     if i == -1:
262                         #This case should not happen
263                         #but if this happens deal with it greacefully adding
264                         #the missing \end_inset.
265                         i = len(file.body) - 1
266                         file.body[i:i] = ["\\end_inset","","","\\end_inset","",""]
267                         return
268                     else:
269                         i = i + 1
270                         continue
271
272                 if find(file.body[i], comment) == -1:
273                     file.body[i:i] = ["\\end_inset"]
274                     i = i + 1
275                     break
276                 file.body[i:i+1] = ["\\layout Standard"]
277                 i = i + 1
278
279
280 def revert_comment(file):
281     i = 0
282     while 1:
283         i = find_tokens(file.body, ["\\begin_inset Comment", "\\begin_inset Greyedout"], i)
284
285         if i == -1:
286             return
287         file.body[i] = "\\begin_inset Note"
288         i = i + 1
289
290
291 ##
292 # Add \end_layout
293 #
294 def add_end_layout(file):
295     i = find_token(file.body, '\\layout', 0)
296
297     if i == -1:
298         return
299
300     i = i + 1
301     struct_stack = ["\\layout"]
302
303     while 1:
304         i = find_tokens(file.body, ["\\begin_inset", "\\end_inset", "\\layout",
305                                 "\\begin_deeper", "\\end_deeper", "\\the_end"], i)
306
307         token = split(file.body[i])[0]
308
309         if token == "\\begin_inset":
310             struct_stack.append(token)
311             i = i + 1
312             continue
313
314         if token == "\\end_inset":
315             tail = struct_stack.pop()
316             if tail == "\\layout":
317                 file.body.insert(i,"")
318                 file.body.insert(i,"\\end_layout")
319                 i = i + 2
320                 #Check if it is the correct tag
321                 struct_stack.pop()
322             i = i + 1
323             continue
324
325         if token == "\\layout":
326             tail = struct_stack.pop()
327             if tail == token:
328                 file.body.insert(i,"")
329                 file.body.insert(i,"\\end_layout")
330                 i = i + 3
331             else:
332                 struct_stack.append(tail)
333                 i = i + 1
334             struct_stack.append(token)
335             continue
336
337         if token == "\\begin_deeper":
338             file.body.insert(i,"")
339             file.body.insert(i,"\\end_layout")
340             i = i + 3
341             struct_stack.append(token)
342             continue
343
344         if token == "\\end_deeper":
345             if struct_stack[-1] == '\\layout':
346                 file.body.insert(i, '\\end_layout')
347                 i = i + 1
348                 struct_stack.pop()
349             i = i + 1
350             continue
351
352         #case \end_document
353         file.body.insert(i, "")
354         file.body.insert(i, "\\end_layout")
355         return
356
357
358 def rm_end_layout(file):
359     i = 0
360     while 1:
361         i = find_token(file.body, '\\end_layout', i)
362
363         if i == -1:
364             return
365
366         del file.body[i]
367
368
369 ##
370 # Handle change tracking keywords
371 #
372 def insert_tracking_changes(file):
373     i = find_token(file.header, "\\tracking_changes", 0)
374     if i == -1:
375         file.header.append("\\tracking_changes 0")
376
377
378 def rm_tracking_changes(file):
379     i = find_token(file.header, "\\author", 0)
380     if i != -1:
381         del file.header[i]
382
383     i = find_token(file.header, "\\tracking_changes", 0)
384     if i == -1:
385         return
386     del file.header[i]
387
388
389 def rm_body_changes(file):
390     i = 0
391     while 1:
392         i = find_token(file.body, "\\change_", i)
393         if i == -1:
394             return
395
396         del file.body[i]
397
398
399 ##
400 # \layout -> \begin_layout
401 #
402 def layout2begin_layout(file):
403     i = 0
404     while 1:
405         i = find_token(file.body, '\\layout', i)
406         if i == -1:
407             return
408
409         file.body[i] = replace(file.body[i], '\\layout', '\\begin_layout')
410         i = i + 1
411
412
413 def begin_layout2layout(file):
414     i = 0
415     while 1:
416         i = find_token(file.body, '\\begin_layout', i)
417         if i == -1:
418             return
419
420         file.body[i] = replace(file.body[i], '\\begin_layout', '\\layout')
421         i = i + 1
422
423
424 ##
425 # valignment="center" -> valignment="middle"
426 #
427 def convert_valignment_middle(body, start, end):
428     for i in range(start, end):
429         if re.search('^<(column|cell) .*valignment="center".*>$', body[i]):
430             body[i] = replace(body[i], 'valignment="center"', 'valignment="middle"')
431
432
433 def convert_table_valignment_middle(file):
434     regexp = re.compile(r'^\\begin_inset\s+Tabular')
435     i = 0
436     while 1:
437         i = find_re(file.body, regexp, i)
438         if i == -1:
439             return
440         j = find_end_of_inset(file.body, i + 1)
441         if j == -1:
442             #this should not happen
443             convert_valignment_middle(file.body, i + 1, len(file.body))
444             return
445         convert_valignment_middle(file.body, i + 1, j)
446         i = j + 1
447
448
449 def revert_table_valignment_middle(body, start, end):
450     for i in range(start, end):
451         if re.search('^<(column|cell) .*valignment="middle".*>$', body[i]):
452             body[i] = replace(body[i], 'valignment="middle"', 'valignment="center"')
453
454
455 def revert_valignment_middle(file):
456     regexp = re.compile(r'^\\begin_inset\s+Tabular')
457     i = 0
458     while 1:
459         i = find_re(file.body, regexp, i)
460         if i == -1:
461             return
462         j = find_end_of_inset(file.body, i + 1)
463         if j == -1:
464             #this should not happen
465             revert_table_valignment_middle(file.body, i + 1, len(file.body))
466             return
467         revert_table_valignment_middle(file.body, i + 1, j)
468         i = j + 1
469
470
471 ##
472 #  \the_end -> \end_document
473 #
474 def convert_end_document(file):
475     i = find_token(file.body, "\\the_end", 0)
476     if i == -1:
477         file.body.append("\\end_document")
478         return
479     file.body[i] = "\\end_document"
480
481
482 def revert_end_document(file):
483     i = find_token(file.body, "\\end_document", 0)
484     if i == -1:
485         file.body.append("\\the_end")
486         return
487     file.body[i] = "\\the_end"
488
489
490 ##
491 # Convert line and page breaks
492 # Old:
493 #\layout Standard
494 #\line_top \line_bottom \pagebreak_top \pagebreak_bottom \added_space_top xxx \added_space_bottom yyy
495 #0
496 #
497 # New:
498 #\begin layout Standard
499 #
500 #\newpage
501 #
502 #\lyxline
503 #\begin_inset VSpace xxx
504 #\end_inset
505 #
506 #\end_layout
507 #\begin_layout Standard
508 #
509 #0
510 #\end_layout
511 #\begin_layout Standard
512 #
513 #\begin_inset VSpace xxx
514 #\end_inset
515 #\lyxline
516 #
517 #\newpage
518 #
519 #\end_layout
520 def convert_breaks(file):
521     par_params = ('added_space_bottom', 'added_space_top', 'align',
522                  'labelwidthstring', 'line_bottom', 'line_top', 'noindent',
523                  'pagebreak_bottom', 'pagebreak_top', 'paragraph_spacing',
524                  'start_of_appendix')
525     i = 0
526     while 1:
527         i = find_token(file.body, "\\begin_layout", i)
528         if i == -1:
529             return
530         i = i + 1
531
532         # Merge all paragraph parameters into a single line
533         # We cannot check for '\\' only because paragraphs may start e.g.
534         # with '\\backslash'
535         while file.body[i + 1][:1] == '\\' and split(file.body[i + 1][1:])[0] in par_params:
536             file.body[i] = file.body[i + 1] + ' ' + file.body[i]
537             del file.body[i+1]
538
539         line_top   = find(file.body[i],"\\line_top")
540         line_bot   = find(file.body[i],"\\line_bottom")
541         pb_top     = find(file.body[i],"\\pagebreak_top")
542         pb_bot     = find(file.body[i],"\\pagebreak_bottom")
543         vspace_top = find(file.body[i],"\\added_space_top")
544         vspace_bot = find(file.body[i],"\\added_space_bottom")
545
546         if line_top == -1 and line_bot == -1 and pb_bot == -1 and pb_top == -1 and vspace_top == -1 and vspace_bot == -1:
547             continue
548
549         for tag in "\\line_top", "\\line_bottom", "\\pagebreak_top", "\\pagebreak_bottom":
550             file.body[i] = replace(file.body[i], tag, "")
551
552         if vspace_top != -1:
553             # the position could be change because of the removal of other
554             # paragraph properties above
555             vspace_top = find(file.body[i],"\\added_space_top")
556             tmp_list = split(file.body[i][vspace_top:])
557             vspace_top_value = tmp_list[1]
558             file.body[i] = file.body[i][:vspace_top] + join(tmp_list[2:])
559
560         if vspace_bot != -1:
561             # the position could be change because of the removal of other
562             # paragraph properties above
563             vspace_bot = find(file.body[i],"\\added_space_bottom")
564             tmp_list = split(file.body[i][vspace_bot:])
565             vspace_bot_value = tmp_list[1]
566             file.body[i] = file.body[i][:vspace_bot] + join(tmp_list[2:])
567
568         file.body[i] = strip(file.body[i])
569         i = i + 1
570
571         #  Create an empty paragraph for line and page break that belong
572         # above the paragraph
573         if pb_top !=-1 or line_top != -1 or vspace_top != -1:
574
575             paragraph_above = ['','\\begin_layout Standard','','']
576
577             if pb_top != -1:
578                 paragraph_above.extend(['\\newpage ',''])
579
580             if vspace_top != -1:
581                 paragraph_above.extend(['\\begin_inset VSpace ' + vspace_top_value,'\\end_inset','',''])
582
583             if line_top != -1:
584                 paragraph_above.extend(['\\lyxline ',''])
585
586             paragraph_above.extend(['\\end_layout',''])
587
588             #inset new paragraph above the current paragraph
589             file.body[i-2:i-2] = paragraph_above
590             i = i + len(paragraph_above)
591
592         # Ensure that nested style are converted later.
593         k = find_end_of(file.body, i, "\\begin_layout", "\\end_layout")
594
595         if k == -1:
596             return
597
598         if pb_bot !=-1 or line_bot != -1 or vspace_bot != -1:
599
600             paragraph_below = ['','\\begin_layout Standard','','']
601
602             if line_bot != -1:
603                 paragraph_below.extend(['\\lyxline ',''])
604
605             if vspace_bot != -1:
606                 paragraph_below.extend(['\\begin_inset VSpace ' + vspace_bot_value,'\\end_inset','',''])
607
608             if pb_bot != -1:
609                 paragraph_below.extend(['\\newpage ',''])
610
611             paragraph_below.extend(['\\end_layout',''])
612
613             #inset new paragraph above the current paragraph
614             file.body[k + 1: k + 1] = paragraph_below
615
616
617 ##
618 #  Notes
619 #
620 def convert_note(file):
621     i = 0
622     while 1:
623         i = find_tokens(file.body, ["\\begin_inset Note",
624                                 "\\begin_inset Comment",
625                                 "\\begin_inset Greyedout"], i)
626         if i == -1:
627             break
628
629         file.body[i] = file.body[i][0:13] + 'Note ' + file.body[i][13:]
630         i = i + 1
631
632
633 def revert_note(file):
634     note_header = "\\begin_inset Note "
635     i = 0
636     while 1:
637         i = find_token(file.body, note_header, i)
638         if i == -1:
639             break
640
641         file.body[i] = "\\begin_inset " + file.body[i][len(note_header):]
642         i = i + 1
643
644
645 ##
646 # Box
647 #
648 def convert_box(file):
649     i = 0
650     while 1:
651         i = find_tokens(file.body, ["\\begin_inset Boxed",
652                                 "\\begin_inset Doublebox",
653                                 "\\begin_inset Frameless",
654                                 "\\begin_inset ovalbox",
655                                 "\\begin_inset Ovalbox",
656                                 "\\begin_inset Shadowbox"], i)
657         if i == -1:
658             break
659
660         file.body[i] = file.body[i][0:13] + 'Box ' + file.body[i][13:]
661         i = i + 1
662
663
664 def revert_box(file):
665     box_header = "\\begin_inset Box "
666     i = 0
667     while 1:
668         i = find_token(file.body, box_header, i)
669         if i == -1:
670             break
671
672         file.body[i] = "\\begin_inset " + file.body[i][len(box_header):]
673         i = i + 1
674
675
676 ##
677 # Collapse
678 #
679 def convert_collapsable(file):
680     i = 0
681     while 1:
682         i = find_tokens(file.body, ["\\begin_inset Box",
683                                 "\\begin_inset Branch",
684                                 "\\begin_inset CharStyle",
685                                 "\\begin_inset Float",
686                                 "\\begin_inset Foot",
687                                 "\\begin_inset Marginal",
688                                 "\\begin_inset Note",
689                                 "\\begin_inset OptArg",
690                                 "\\begin_inset Wrap"], i)
691         if i == -1:
692             break
693
694         # Seach for a line starting 'collapsed'
695         # If, however, we find a line starting '\begin_layout'
696         # (_always_ present) then break with a warning message
697         i = i + 1
698         while 1:
699             if (file.body[i] == "collapsed false"):
700                 file.body[i] = "status open"
701                 break
702             elif (file.body[i] == "collapsed true"):
703                 file.body[i] = "status collapsed"
704                 break
705             elif (file.body[i][:13] == "\\begin_layout"):
706                 file.warning("Malformed LyX file: Missing 'collapsed'.")
707                 break
708             i = i + 1
709
710         i = i + 1
711
712
713 def revert_collapsable(file):
714     i = 0
715     while 1:
716         i = find_tokens(file.body, ["\\begin_inset Box",
717                                 "\\begin_inset Branch",
718                                 "\\begin_inset CharStyle",
719                                 "\\begin_inset Float",
720                                 "\\begin_inset Foot",
721                                 "\\begin_inset Marginal",
722                                 "\\begin_inset Note",
723                                 "\\begin_inset OptArg",
724                                 "\\begin_inset Wrap"], i)
725         if i == -1:
726             break
727
728         # Seach for a line starting 'status'
729         # If, however, we find a line starting '\begin_layout'
730         # (_always_ present) then break with a warning message
731         i = i + 1
732         while 1:
733             if (file.body[i] == "status open"):
734                 file.body[i] = "collapsed false"
735                 break
736             elif (file.body[i] == "status collapsed" or
737                   file.body[i] == "status inlined"):
738                 file.body[i] = "collapsed true"
739                 break
740             elif (file.body[i][:13] == "\\begin_layout"):
741                 file.warning("Malformed LyX file: Missing 'status'.")
742                 break
743             i = i + 1
744
745         i = i + 1
746
747
748 ##
749 #  ERT
750 #
751 def convert_ert(file):
752     i = 0
753     while 1:
754         i = find_token(file.body, "\\begin_inset ERT", i)
755         if i == -1:
756             break
757
758         # Seach for a line starting 'status'
759         # If, however, we find a line starting '\begin_layout'
760         # (_always_ present) then break with a warning message
761         i = i + 1
762         while 1:
763             if (file.body[i] == "status Open"):
764                 file.body[i] = "status open"
765                 break
766             elif (file.body[i] == "status Collapsed"):
767                 file.body[i] = "status collapsed"
768                 break
769             elif (file.body[i] == "status Inlined"):
770                 file.body[i] = "status inlined"
771                 break
772             elif (file.body[i][:13] == "\\begin_layout"):
773                 file.warning("Malformed LyX file: Missing 'status'.")
774                 break
775             i = i + 1
776
777         i = i + 1
778
779
780 def revert_ert(file):
781     i = 0
782     while 1:
783         i = find_token(file.body, "\\begin_inset ERT", i)
784         if i == -1:
785             break
786
787         # Seach for a line starting 'status'
788         # If, however, we find a line starting '\begin_layout'
789         # (_always_ present) then break with a warning message
790         i = i + 1
791         while 1:
792             if (file.body[i] == "status open"):
793                 file.body[i] = "status Open"
794                 break
795             elif (file.body[i] == "status collapsed"):
796                 file.body[i] = "status Collapsed"
797                 break
798             elif (file.body[i] == "status inlined"):
799                 file.body[i] = "status Inlined"
800                 break
801             elif (file.body[i][:13] == "\\begin_layout"):
802                 file.warning("Malformed LyX file : Missing 'status'.")
803                 break
804             i = i + 1
805
806         i = i + 1
807
808
809 ##
810 # Minipages
811 #
812 def convert_minipage(file):
813     """ Convert minipages to the box inset.
814     We try to use the same order of arguments as lyx does.
815     """
816     pos = ["t","c","b"]
817     inner_pos = ["c","t","b","s"]
818
819     i = 0
820     while 1:
821         i = find_token(file.body, "\\begin_inset Minipage", i)
822         if i == -1:
823             return
824
825         file.body[i] = "\\begin_inset Box Frameless"
826         i = i + 1
827
828         # convert old to new position using the pos list
829         if file.body[i][:8] == "position":
830             file.body[i] = 'position "%s"' % pos[int(file.body[i][9])]
831         else:
832             file.body.insert(i, 'position "%s"' % pos[0])
833         i = i + 1
834
835         file.body.insert(i, 'hor_pos "c"')
836         i = i + 1
837         file.body.insert(i, 'has_inner_box 1')
838         i = i + 1
839
840         # convert the inner_position
841         if file.body[i][:14] == "inner_position":
842             file.body[i] = 'inner_pos "%s"' %  inner_pos[int(file.body[i][15])]
843         else:
844             file.body.insert('inner_pos "%s"' % inner_pos[0])
845         i = i + 1
846
847         # We need this since the new file format has a height and width
848         # in a different order.
849         if file.body[i][:6] == "height":
850             height = file.body[i][6:]
851             # test for default value of 221 and convert it accordingly
852             if height == ' "0pt"':
853                 height = ' "1pt"'
854             del file.body[i]
855         else:
856             height = ' "1pt"'
857
858         if file.body[i][:5] == "width":
859             width = file.body[i][5:]
860             del file.body[i]
861         else:
862             width = ' "0"'
863
864         if file.body[i][:9] == "collapsed":
865             if file.body[i][9:] == "true":
866                 status = "collapsed"
867             else:
868                 status = "open"
869             del file.body[i]
870         else:
871             status = "collapsed"
872
873         file.body.insert(i, 'use_parbox 0')
874         i = i + 1
875         file.body.insert(i, 'width' + width)
876         i = i + 1
877         file.body.insert(i, 'special "none"')
878         i = i + 1
879         file.body.insert(i, 'height' + height)
880         i = i + 1
881         file.body.insert(i, 'height_special "totalheight"')
882         i = i + 1
883         file.body.insert(i, 'status ' + status)
884         i = i + 1
885
886
887 # -------------------------------------------------------------------------------------------
888 # Convert backslashes and '\n' into valid ERT code, append the converted
889 # text to body[i] and return the (maybe incremented) line index i
890 def convert_ertbackslash(body, i, ert):
891     for c in ert:
892         if c == '\\':
893             body[i] = body[i] + '\\backslash '
894             i = i + 1
895             body.insert(i, '')
896         elif c == '\n':
897             body[i+1:i+1] = ['\\newline ', '']
898             i = i + 2
899         else:
900             body[i] = body[i] + c
901     return i
902
903
904 def convert_vspace(file):
905
906     # Get default spaceamount
907     i = find_token(file.header, '\\defskip', 0)
908     if i == -1:
909         defskipamount = 'medskip'
910     else:
911         defskipamount = split(file.header[i])[1]
912
913     # Convert the insets
914     i = 0
915     while 1:
916         i = find_token(file.body, '\\begin_inset VSpace', i)
917         if i == -1:
918             return
919         spaceamount = split(file.body[i])[2]
920
921         # Are we at the beginning or end of a paragraph?
922         paragraph_start = 1
923         start = get_paragraph(file.body, i) + 1
924         for k in range(start, i):
925             if is_nonempty_line(file.body[k]):
926                 paragraph_start = 0
927                 break
928         paragraph_end = 1
929         j = find_end_of_inset(file.body, i)
930         if j == -1:
931             file.warning("Malformed LyX file: Missing '\\end_inset'.")
932             i = i + 1
933             continue
934         end = get_next_paragraph(file.body, i)
935         for k in range(j + 1, end):
936             if is_nonempty_line(file.body[k]):
937                 paragraph_end = 0
938                 break
939
940         # Convert to paragraph formatting if we are at the beginning or end
941         # of a paragraph and the resulting paragraph would not be empty
942         if ((paragraph_start and not paragraph_end) or
943             (paragraph_end   and not paragraph_start)):
944             # The order is important: del and insert invalidate some indices
945             del file.body[j]
946             del file.body[i]
947             if paragraph_start:
948                 file.body.insert(start, '\\added_space_top ' + spaceamount + ' ')
949             else:
950                 file.body.insert(start, '\\added_space_bottom ' + spaceamount + ' ')
951             continue
952
953         # Convert to ERT
954         file.body[i:i+1] = ['\\begin_inset ERT', 'status Collapsed', '',
955                         '\\layout Standard', '', '\\backslash ']
956         i = i + 6
957         if spaceamount[-1] == '*':
958             spaceamount = spaceamount[:-1]
959             keep = 1
960         else:
961             keep = 0
962
963         # Replace defskip by the actual value
964         if spaceamount == 'defskip':
965             spaceamount = defskipamount
966
967         # LaTeX does not know \\smallskip* etc
968         if keep:
969             if spaceamount == 'smallskip':
970                 spaceamount = '\\smallskipamount'
971             elif spaceamount == 'medskip':
972                 spaceamount = '\\medskipamount'
973             elif spaceamount == 'bigskip':
974                 spaceamount = '\\bigskipamount'
975             elif spaceamount == 'vfill':
976                 spaceamount = '\\fill'
977
978         # Finally output the LaTeX code
979         if (spaceamount == 'smallskip' or spaceamount == 'medskip' or
980             spaceamount == 'bigskip'   or spaceamount == 'vfill'):
981             file.body.insert(i, spaceamount)
982         else :
983             if keep:
984                 file.body.insert(i, 'vspace*{')
985             else:
986                 file.body.insert(i, 'vspace{')
987             i = convert_ertbackslash(file.body, i, spaceamount)
988             file.body[i] =  file.body[i] + '}'
989         i = i + 1
990
991
992 # Convert a LyX length into a LaTeX length
993 def convert_len(len, special):
994     units = {"text%":"\\textwidth", "col%":"\\columnwidth",
995              "page%":"\\pagewidth", "line%":"\\linewidth",
996              "theight%":"\\textheight", "pheight%":"\\pageheight"}
997
998     # Convert special lengths
999     if special != 'none':
1000         len = '%f\\' % len2value(len) + special
1001
1002     # Convert LyX units to LaTeX units
1003     for unit in units.keys():
1004         if find(len, unit) != -1:
1005             len = '%f' % (len2value(len) / 100) + units[unit]
1006             break
1007
1008     return len
1009
1010
1011 # Convert a LyX length into valid ERT code and append it to body[i]
1012 # Return the (maybe incremented) line index i
1013 def convert_ertlen(body, i, len, special):
1014     # Convert backslashes and insert the converted length into body
1015     return convert_ertbackslash(body, i, convert_len(len, special))
1016
1017
1018 # Return the value of len without the unit in numerical form
1019 def len2value(len):
1020     result = re.search('([+-]?[0-9.]+)', len)
1021     if result:
1022         return float(result.group(1))
1023     # No number means 1.0
1024     return 1.0
1025
1026
1027 # Convert text to ERT and insert it at body[i]
1028 # Return the index of the line after the inserted ERT
1029 def insert_ert(body, i, status, text):
1030     body[i:i] = ['\\begin_inset ERT', 'status ' + status, '',
1031                  '\\layout Standard', '']
1032     i = i + 5
1033     i = convert_ertbackslash(body, i, text) + 1
1034     body[i:i] = ['', '\\end_inset', '']
1035     i = i + 3
1036     return i
1037
1038
1039 # Add text to the preamble if it is not already there.
1040 # Only the first line is checked!
1041 def add_to_preamble(file, text):
1042     i = find_token(file.header, '\\begin_preamble', 0)
1043     if i == -1:
1044         file.warning("Malformed LyX file: Missing '\\begin_preamble'.")
1045         return
1046     j = find_token(file.header, '\\end_preamble', i)
1047     if j == -1:
1048         file.warning("Malformed LyX file: Missing '\\end_preamble'.")
1049         return
1050     if find_token(file.header, text[0], i, j) != -1:
1051         return
1052     file.header[j:j] = text
1053
1054
1055 def convert_frameless_box(file):
1056     pos = ['t', 'c', 'b']
1057     inner_pos = ['c', 't', 'b', 's']
1058     i = 0
1059     while 1:
1060         i = find_token(file.body, '\\begin_inset Frameless', i)
1061         if i == -1:
1062             return
1063         j = find_end_of_inset(file.body, i)
1064         if j == -1:
1065             file.warning("Malformed LyX file: Missing '\\end_inset'.")
1066             i = i + 1
1067             continue
1068         del file.body[i]
1069         j = j - 1
1070
1071         # Gather parameters
1072         params = {'position':'0', 'hor_pos':'c', 'has_inner_box':'1',
1073                   'inner_pos':'1', 'use_parbox':'0', 'width':'100col%',
1074                   'special':'none', 'height':'1in',
1075                   'height_special':'totalheight', 'collapsed':'false'}
1076         for key in params.keys():
1077             value = replace(get_value(file.body, key, i, j), '"', '')
1078             if value != "":
1079                 if key == 'position':
1080                     # convert new to old position: 'position "t"' -> 0
1081                     value = find_token(pos, value, 0)
1082                     if value != -1:
1083                         params[key] = value
1084                 elif key == 'inner_pos':
1085                     # convert inner position
1086                     value = find_token(inner_pos, value, 0)
1087                     if value != -1:
1088                         params[key] = value
1089                 else:
1090                     params[key] = value
1091                 j = del_token(file.body, key, i, j)
1092         i = i + 1
1093
1094         # Convert to minipage or ERT?
1095         # Note that the inner_position and height parameters of a minipage
1096         # inset are ignored and not accessible for the user, although they
1097         # are present in the file format and correctly read in and written.
1098         # Therefore we convert to ERT if they do not have their LaTeX
1099         # defaults. These are:
1100         # - the value of "position" for "inner_pos"
1101         # - "\totalheight"          for "height"
1102         if (params['use_parbox'] != '0' or
1103             params['has_inner_box'] != '1' or
1104             params['special'] != 'none' or
1105             inner_pos[params['inner_pos']] != pos[params['position']] or
1106             params['height_special'] != 'totalheight' or
1107             len2value(params['height']) != 1.0):
1108
1109             # Here we know that this box is not supported in file format 224.
1110             # Therefore we need to convert it to ERT. We can't simply convert
1111             # the beginning and end of the box to ERT, because the
1112             # box inset may contain layouts that are different from the
1113             # surrounding layout. After the conversion the contents of the
1114             # box inset is on the same level as the surrounding text, and
1115             # paragraph layouts and align parameters can get mixed up.
1116
1117             # A possible solution for this problem:
1118             # Convert the box to a minipage and redefine the minipage
1119             # environment in ERT so that the original box is simulated.
1120             # For minipages we could do this in a way that the width and
1121             # position can still be set from LyX, but this did not work well.
1122             # This is not possible for parboxes either, so we convert the
1123             # original box to ERT, put the minipage inset inside the box
1124             # and redefine the minipage environment to be empty.
1125
1126             # Commands that are independant of a particular box can go to
1127             # the preamble.
1128             # We need to define lyxtolyxrealminipage with 3 optional
1129             # arguments although LyX 1.3 uses only the first one.
1130             # Otherwise we will get LaTeX errors if this document is
1131             # converted to format 225 or above again (LyX 1.4 uses all
1132             # optional arguments).
1133             add_to_preamble(file,
1134                 ['% Commands inserted by lyx2lyx for frameless boxes',
1135                  '% Save the original minipage environment',
1136                  '\\let\\lyxtolyxrealminipage\\minipage',
1137                  '\\let\\endlyxtolyxrealminipage\\endminipage',
1138                  '% Define an empty lyxtolyximinipage environment',
1139                  '% with 3 optional arguments',
1140                  '\\newenvironment{lyxtolyxiiiminipage}[4]{}{}',
1141                  '\\newenvironment{lyxtolyxiiminipage}[2][\\lyxtolyxargi]%',
1142                  '  {\\begin{lyxtolyxiiiminipage}{\\lyxtolyxargi}{\\lyxtolyxargii}{#1}{#2}}%',
1143                  '  {\\end{lyxtolyxiiiminipage}}',
1144                  '\\newenvironment{lyxtolyximinipage}[1][\\totalheight]%',
1145                  '  {\\def\\lyxtolyxargii{{#1}}\\begin{lyxtolyxiiminipage}}%',
1146                  '  {\\end{lyxtolyxiiminipage}}',
1147                  '\\newenvironment{lyxtolyxminipage}[1][c]%',
1148                  '  {\\def\\lyxtolyxargi{{#1}}\\begin{lyxtolyximinipage}}',
1149                  '  {\\end{lyxtolyximinipage}}'])
1150
1151             if params['use_parbox'] != '0':
1152                 ert = '\\parbox'
1153             else:
1154                 ert = '\\begin{lyxtolyxrealminipage}'
1155
1156             # convert optional arguments only if not latex default
1157             if (pos[params['position']] != 'c' or
1158                 inner_pos[params['inner_pos']] != pos[params['position']] or
1159                 params['height_special'] != 'totalheight' or
1160                 len2value(params['height']) != 1.0):
1161                 ert = ert + '[' + pos[params['position']] + ']'
1162             if (inner_pos[params['inner_pos']] != pos[params['position']] or
1163                 params['height_special'] != 'totalheight' or
1164                 len2value(params['height']) != 1.0):
1165                 ert = ert + '[' + convert_len(params['height'],
1166                                               params['height_special']) + ']'
1167             if inner_pos[params['inner_pos']] != pos[params['position']]:
1168                 ert = ert + '[' + inner_pos[params['inner_pos']] + ']'
1169
1170             ert = ert + '{' + convert_len(params['width'],
1171                                           params['special']) + '}'
1172
1173             if params['use_parbox'] != '0':
1174                 ert = ert + '{'
1175             ert = ert + '\\let\\minipage\\lyxtolyxminipage%\n'
1176             ert = ert + '\\let\\endminipage\\endlyxtolyxminipage%\n'
1177
1178             old_i = i
1179             i = insert_ert(file.body, i, 'Collapsed', ert)
1180             j = j + i - old_i - 1
1181
1182             file.body[i:i] = ['\\begin_inset Minipage',
1183                               'position %d' % params['position'],
1184                               'inner_position 1',
1185                               'height "1in"',
1186                               'width "' + params['width'] + '"',
1187                               'collapsed ' + params['collapsed']]
1188             i = i + 6
1189             j = j + 6
1190
1191             # Restore the original minipage environment since we may have
1192             # minipages inside this box.
1193             # Start a new paragraph because the following may be nonstandard
1194             file.body[i:i] = ['\\layout Standard', '', '']
1195             i = i + 2
1196             j = j + 3
1197             ert = '\\let\\minipage\\lyxtolyxrealminipage%\n'
1198             ert = ert + '\\let\\endminipage\\lyxtolyxrealendminipage%'
1199             old_i = i
1200             i = insert_ert(file.body, i, 'Collapsed', ert)
1201             j = j + i - old_i - 1
1202
1203             # Redefine the minipage end before the inset end.
1204             # Start a new paragraph because the previous may be nonstandard
1205             file.body[j:j] = ['\\layout Standard', '', '']
1206             j = j + 2
1207             ert = '\\let\\endminipage\\endlyxtolyxminipage'
1208             j = insert_ert(file.body, j, 'Collapsed', ert)
1209             j = j + 1
1210             file.body.insert(j, '')
1211             j = j + 1
1212
1213             # LyX writes '%\n' after each box. Therefore we need to end our
1214             # ERT with '%\n', too, since this may swallow a following space.
1215             if params['use_parbox'] != '0':
1216                 ert = '}%\n'
1217             else:
1218                 ert = '\\end{lyxtolyxrealminipage}%\n'
1219             j = insert_ert(file.body, j, 'Collapsed', ert)
1220
1221             # We don't need to restore the original minipage after the inset
1222             # end because the scope of the redefinition is the original box.
1223
1224         else:
1225
1226             # Convert to minipage
1227             file.body[i:i] = ['\\begin_inset Minipage',
1228                               'position %d' % params['position'],
1229                               'inner_position %d' % params['inner_pos'],
1230                               'height "' + params['height'] + '"',
1231                               'width "' + params['width'] + '"',
1232                               'collapsed ' + params['collapsed']]
1233             i = i + 6
1234
1235 ##
1236 # Convert jurabib
1237 #
1238
1239 def convert_jurabib(file):
1240     i = find_token(file.header, '\\use_numerical_citations', 0)
1241     if i == -1:
1242         file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1243         return
1244     file.header.insert(i + 1, '\\use_jurabib 0')
1245
1246
1247 def revert_jurabib(file):
1248     i = find_token(file.header, '\\use_jurabib', 0)
1249     if i == -1:
1250         file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1251         return
1252     if get_value(file.header, '\\use_jurabib', 0) != "0":
1253         file.warning("Conversion of '\\use_jurabib = 1' not yet implemented.")
1254         # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1255         return
1256     del file.header[i]
1257
1258 ##
1259 # Convert bibtopic
1260 #
1261
1262 def convert_bibtopic(file):
1263     i = find_token(file.header, '\\use_jurabib', 0)
1264     if i == -1:
1265         file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1266         return
1267     file.header.insert(i + 1, '\\use_bibtopic 0')
1268
1269
1270 def revert_bibtopic(file):
1271     i = find_token(file.header, '\\use_bibtopic', 0)
1272     if i == -1:
1273         file.warning("Malformed lyx file: Missing '\\use_bibtopic'.")
1274         return
1275     if get_value(file.header, '\\use_bibtopic', 0) != "0":
1276         file.warning("Conversion of '\\use_bibtopic = 1' not yet implemented.")
1277         # Don't remove '\\use_jurabib' so that people will get warnings by lyx
1278     del file.header[i]
1279
1280 ##
1281 # Sideway Floats
1282 #
1283
1284 def convert_float(file):
1285     i = 0
1286     while 1:
1287         i = find_token(file.body, '\\begin_inset Float', i)
1288         if i == -1:
1289             return
1290         # Seach for a line starting 'wide'
1291         # If, however, we find a line starting '\begin_layout'
1292         # (_always_ present) then break with a warning message
1293         i = i + 1
1294         while 1:
1295             if (file.body[i][:4] == "wide"):
1296                 file.body.insert(i + 1, 'sideways false')
1297                 break
1298             elif (file.body[i][:13] == "\\begin_layout"):
1299                 file.warning("Malformed lyx file: Missing 'wide'.")
1300                 break
1301             i = i + 1
1302         i = i + 1
1303
1304
1305 def revert_float(file):
1306     i = 0
1307     while 1:
1308         i = find_token(file.body, '\\begin_inset Float', i)
1309         if i == -1:
1310             return
1311         j = find_end_of_inset(file.body, i)
1312         if j == -1:
1313             file.warning("Malformed lyx file: Missing '\\end_inset'.")
1314             i = i + 1
1315             continue
1316         if get_value(file.body, 'sideways', i, j) != "false":
1317             file.warning("Conversion of 'sideways true' not yet implemented.")
1318             # Don't remove 'sideways' so that people will get warnings by lyx
1319             i = i + 1
1320             continue
1321         del_token(file.body, 'sideways', i, j)
1322         i = i + 1
1323
1324
1325 def convert_graphics(file):
1326     """ Add extension to filenames of insetgraphics if necessary.
1327     """
1328     i = 0
1329     while 1:
1330         i = find_token(file.body, "\\begin_inset Graphics", i)
1331         if i == -1:
1332             return
1333
1334         j = find_token2(file.body, "filename", i)
1335         if j == -1:
1336             return
1337         i = i + 1
1338         filename = split(file.body[j])[1]
1339         absname = os.path.normpath(os.path.join(file.dir, filename))
1340         if file.input == stdin and not os.path.isabs(filename):
1341             # We don't know the directory and cannot check the file.
1342             # We could use a heuristic and take the current directory,
1343             # and we could try to find out if filename has an extension,
1344             # but that would be just guesses and could be wrong.
1345             file.warning("""Warning: Can not determine whether file
1346          %s
1347          needs an extension when reading from standard input.
1348          You may need to correct the file manually or run
1349          lyx2lyx again with the .lyx file as commandline argument.""" % filename)
1350             continue
1351         # This needs to be the same algorithm as in pre 233 insetgraphics
1352         if access(absname, F_OK):
1353             continue
1354         if access(absname + ".ps", F_OK):
1355             file.body[j] = replace(file.body[j], filename, filename + ".ps")
1356             continue
1357         if access(absname + ".eps", F_OK):
1358             file.body[j] = replace(file.body[j], filename, filename + ".eps")
1359
1360
1361 ##
1362 # Convert firstname and surname from styles -> char styles
1363 #
1364 def convert_names(file):
1365     """ Convert in the docbook backend from firstname and surname style
1366     to charstyles.
1367     """
1368     if file.backend != "docbook":
1369         return
1370
1371     i = 0
1372
1373     while 1:
1374         i = find_token(file.body, "\\begin_layout Author", i)
1375         if i == -1:
1376             return
1377
1378         i = i + 1
1379         while file.body[i] == "":
1380             i = i + 1
1381
1382         if file.body[i][:11] != "\\end_layout" or file.body[i+2][:13] != "\\begin_deeper":
1383             i = i + 1
1384             continue
1385
1386         k = i
1387         i = find_end_of( file.body, i+3, "\\begin_deeper","\\end_deeper")
1388         if i == -1:
1389             # something is really wrong, abort
1390             file.warning("Missing \\end_deeper, after style Author.")
1391             file.warning("Aborted attempt to parse FirstName and Surname.")
1392             return
1393         firstname, surname = "", ""
1394
1395         name = file.body[k:i]
1396
1397         j = find_token(name, "\\begin_layout FirstName", 0)
1398         if j != -1:
1399             j = j + 1
1400             while(name[j] != "\\end_layout"):
1401                 firstname = firstname + name[j]
1402                 j = j + 1
1403
1404         j = find_token(name, "\\begin_layout Surname", 0)
1405         if j != -1:
1406             j = j + 1
1407             while(name[j] != "\\end_layout"):
1408                 surname = surname + name[j]
1409                 j = j + 1
1410
1411         # delete name
1412         del file.body[k+2:i+1]
1413
1414         file.body[k-1:k-1] = ["", "",
1415                           "\\begin_inset CharStyle Firstname",
1416                           "status inlined",
1417                           "",
1418                           "\\begin_layout Standard",
1419                           "",
1420                           "%s" % firstname,
1421                           "\end_layout",
1422                           "",
1423                           "\end_inset",
1424                           "",
1425                           "",
1426                           "\\begin_inset CharStyle Surname",
1427                           "status inlined",
1428                           "",
1429                           "\\begin_layout Standard",
1430                           "",
1431                           "%s" % surname,
1432                           "\\end_layout",
1433                           "",
1434                           "\\end_inset",
1435                           ""]
1436
1437
1438 def revert_names(file):
1439     """ Revert in the docbook backend from firstname and surname char style
1440     to styles.
1441     """
1442     if file.backend != "docbook":
1443         return
1444
1445
1446 ##
1447 #    \use_natbib 1                       \cite_engine <style>
1448 #    \use_numerical_citations 0     ->   where <style> is one of
1449 #    \use_jurabib 0                      "basic", "natbib_authoryear",
1450 #                                        "natbib_numerical" or "jurabib"
1451 def convert_cite_engine(file):
1452     a = find_token(file.header, "\\use_natbib", 0)
1453     if a == -1:
1454         file.warning("Malformed lyx file: Missing '\\use_natbib'.")
1455         return
1456
1457     b = find_token(file.header, "\\use_numerical_citations", 0)
1458     if b == -1 or b != a+1:
1459         file.warning("Malformed lyx file: Missing '\\use_numerical_citations'.")
1460         return
1461
1462     c = find_token(file.header, "\\use_jurabib", 0)
1463     if c == -1 or c != b+1:
1464         file.warning("Malformed lyx file: Missing '\\use_jurabib'.")
1465         return
1466
1467     use_natbib = int(split(file.header[a])[1])
1468     use_numerical_citations = int(split(file.header[b])[1])
1469     use_jurabib = int(split(file.header[c])[1])
1470
1471     cite_engine = "basic"
1472     if use_natbib:
1473         if use_numerical_citations:
1474             cite_engine = "natbib_numerical"
1475         else:
1476              cite_engine = "natbib_authoryear"
1477     elif use_jurabib:
1478         cite_engine = "jurabib"
1479
1480     del file.header[a:c+1]
1481     file.header.insert(a, "\\cite_engine " + cite_engine)
1482
1483
1484 def revert_cite_engine(file):
1485     i = find_token(file.header, "\\cite_engine", 0)
1486     if i == -1:
1487         file.warning("Malformed lyx file: Missing '\\cite_engine'.")
1488         return
1489
1490     cite_engine = split(file.header[i])[1]
1491
1492     use_natbib = '0'
1493     use_numerical = '0'
1494     use_jurabib = '0'
1495     if cite_engine == "natbib_numerical":
1496         use_natbib = '1'
1497         use_numerical = '1'
1498     elif cite_engine == "natbib_authoryear":
1499         use_natbib = '1'
1500     elif cite_engine == "jurabib":
1501         use_jurabib = '1'
1502
1503     del file.header[i]
1504     file.header.insert(i, "\\use_jurabib " + use_jurabib)
1505     file.header.insert(i, "\\use_numerical_citations " + use_numerical)
1506     file.header.insert(i, "\\use_natbib " + use_natbib)
1507
1508
1509 ##
1510 # Paper package
1511 #
1512 def convert_paperpackage(file):
1513     i = find_token(file.header, "\\paperpackage", 0)
1514     if i == -1:
1515         file.warning("Malformed lyx file: Missing '\\paperpackage'.")
1516         return
1517
1518     packages = {'default':'none','a4':'none', 'a4wide':'a4', 'widemarginsa4':'a4wide'}
1519     paperpackage = split(file.header[i])[1]
1520     file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1521
1522
1523 def revert_paperpackage(file):
1524     i = find_token(file.header, "\\paperpackage", 0)
1525     if i == -1:
1526         file.warning("Malformed lyx file: Missing '\\paperpackage'.")
1527         return
1528
1529     packages = {'none':'a4', 'a4':'a4wide', 'a4wide':'widemarginsa4',
1530                 'widemarginsa4':''}
1531     paperpackage = split(file.header[i])[1]
1532     file.header[i] = replace(file.header[i], paperpackage, packages[paperpackage])
1533
1534
1535 ##
1536 # Bullets
1537 #
1538 def convert_bullets(file):
1539     i = 0
1540     while 1:
1541         i = find_token(file.header, "\\bullet", i)
1542         if i == -1:
1543             return
1544         if file.header[i][:12] == '\\bulletLaTeX':
1545             file.header[i] = file.header[i] + ' ' + strip(file.header[i+1])
1546             n = 3
1547         else:
1548             file.header[i] = file.header[i] + ' ' + strip(file.header[i+1]) +\
1549                         ' ' + strip(file.header[i+2]) + ' ' + strip(file.header[i+3])
1550             n = 5
1551         del file.header[i+1:i + n]
1552         i = i + 1
1553
1554
1555 def revert_bullets(file):
1556     i = 0
1557     while 1:
1558         i = find_token(file.header, "\\bullet", i)
1559         if i == -1:
1560             return
1561         if file.header[i][:12] == '\\bulletLaTeX':
1562             n = find(file.header[i], '"')
1563             if n == -1:
1564                 file.warning("Malformed header.")
1565                 return
1566             else:
1567                 file.header[i:i+1] = [file.header[i][:n-1],'\t' + file.header[i][n:], '\\end_bullet']
1568             i = i + 3
1569         else:
1570             frag = split(file.header[i])
1571             if len(frag) != 5:
1572                 file.warning("Malformed header.")
1573                 return
1574             else:
1575                 file.header[i:i+1] = [frag[0] + ' ' + frag[1],
1576                                  '\t' + frag[2],
1577                                  '\t' + frag[3],
1578                                  '\t' + frag[4],
1579                                  '\\end_bullet']
1580                 i = i + 5
1581
1582
1583 ##
1584 # \begin_header and \begin_document
1585 #
1586 def add_begin_header(file):
1587     i = find_token(file.header, '\\lyxformat', 0)
1588     file.header.insert(i+1, '\\begin_header')
1589     file.header.insert(i+1, '\\begin_document')
1590
1591
1592 def remove_begin_header(file):
1593     i = find_token(file.header, "\\begin_document", 0)
1594     if i != -1:
1595         del file.header[i]
1596     i = find_token(file.header, "\\begin_header", 0)
1597     if i != -1:
1598         del file.header[i]
1599
1600
1601 ##
1602 # \begin_file.body and \end_file.body
1603 #
1604 def add_begin_body(file):
1605     file.body.insert(0, '\\begin_body')
1606     file.body.insert(1, '')
1607     i = find_token(file.body, "\\end_document", 0)
1608     file.body.insert(i, '\\end_body')
1609
1610 def remove_begin_body(file):
1611     i = find_token(file.body, "\\begin_body", 0)
1612     if i != -1:
1613         del file.body[i]
1614         if not file.body[i]:
1615             del file.body[i]
1616     i = find_token(file.body, "\\end_body", 0)
1617     if i != -1:
1618         del file.body[i]
1619
1620
1621 ##
1622 # \papersize
1623 #
1624 def normalize_papersize(file):
1625     i = find_token(file.header, '\\papersize', 0)
1626     if i == -1:
1627         return
1628
1629     tmp = split(file.header[i])
1630     if tmp[1] == "Default":
1631         file.header[i] = '\\papersize default'
1632         return
1633     if tmp[1] == "Custom":
1634         file.header[i] = '\\papersize custom'
1635
1636
1637 def denormalize_papersize(file):
1638     i = find_token(file.header, '\\papersize', 0)
1639     if i == -1:
1640         return
1641
1642     tmp = split(file.header[i])
1643     if tmp[1] == "custom":
1644         file.header[i] = '\\papersize Custom'
1645
1646
1647 ##
1648 # Strip spaces at end of command line
1649 #
1650 def strip_end_space(file):
1651     for i in range(len(file.body)):
1652         if file.body[i][:1] == '\\':
1653             file.body[i] = strip(file.body[i])
1654
1655
1656 ##
1657 # Use boolean values for \use_geometry, \use_bibtopic and \tracking_changes
1658 #
1659 def use_x_boolean(file):
1660     bin2bool = {'0': 'false', '1': 'true'}
1661     for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
1662         i = find_token(file.header, use, 0)
1663         if i == -1:
1664             continue
1665         decompose = split(file.header[i])
1666         file.header[i] = decompose[0] + ' ' + bin2bool[decompose[1]]
1667
1668
1669 def use_x_binary(file):
1670     bool2bin = {'false': '0', 'true': '1'}
1671     for use in '\\use_geometry', '\\use_bibtopic', '\\tracking_changes':
1672         i = find_token(file.header, use, 0)
1673         if i == -1:
1674             continue
1675         decompose = split(file.header[i])
1676         file.header[i] = decompose[0] + ' ' + bool2bin[decompose[1]]
1677
1678 ##
1679 # Place all the paragraph parameters in their own line
1680 #
1681 def normalize_paragraph_params(file):
1682     body = file.body
1683     allowed_parameters = '\\paragraph_spacing', '\\noindent', '\\align', '\\labelwidthstring', "\\start_of_appendix"
1684
1685     i = 0
1686     while 1:
1687         i = find_token(file.body, '\\begin_layout', i)
1688         if i == -1:
1689             return
1690
1691         i = i + 1
1692         while 1:
1693             if strip(body[i]) and split(body[i])[0] not in allowed_parameters:
1694                 break
1695
1696             j = find(body[i],'\\', 1)
1697
1698             if j != -1:
1699                 body[i:i+1] = [strip(body[i][:j]), body[i][j:]]
1700
1701             i = i + 1
1702
1703
1704 ##
1705 # Add/remove output_changes parameter
1706 #
1707 def convert_output_changes (file):
1708     i = find_token(file.header, '\\tracking_changes', 0)
1709     if i == -1:
1710         file.warning("Malformed lyx file: Missing '\\tracking_changes'.")
1711         return
1712     file.header.insert(i+1, '\\output_changes true')
1713
1714
1715 def revert_output_changes (file):
1716     i = find_token(file.header, '\\output_changes', 0)
1717     if i == -1:
1718         return
1719     del file.header[i]
1720
1721
1722 ##
1723 # Convert paragraph breaks and sanitize paragraphs
1724 #
1725 def convert_ert_paragraphs(file):
1726     forbidden_settings = [
1727                           # paragraph parameters
1728                           '\\paragraph_spacing', '\\labelwidthstring',
1729                           '\\start_of_appendix', '\\noindent',
1730                           '\\leftindent', '\\align',
1731                           # font settings
1732                           '\\family', '\\series', '\\shape', '\\size',
1733                           '\\emph', '\\numeric', '\\bar', '\\noun',
1734                           '\\color', '\\lang']
1735     i = 0
1736     while 1:
1737         i = find_token(file.body, '\\begin_inset ERT', i)
1738         if i == -1:
1739             return
1740         j = find_end_of_inset(file.body, i)
1741         if j == -1:
1742             file.warning("Malformed lyx file: Missing '\\end_inset'.")
1743             i = i + 1
1744             continue
1745
1746         # convert non-standard paragraphs to standard
1747         k = i
1748         while 1:
1749             k = find_token(file.body, "\\begin_layout", k, j)
1750             if k == -1:
1751                 break
1752             file.body[k] = "\\begin_layout Standard"
1753             k = k + 1
1754
1755         # remove all paragraph parameters and font settings
1756         k = i
1757         while k < j:
1758             if (strip(file.body[k]) and
1759                 split(file.body[k])[0] in forbidden_settings):
1760                 del file.body[k]
1761                 j = j - 1
1762             else:
1763                 k = k + 1
1764
1765         # insert an empty paragraph before each paragraph but the first
1766         k = i
1767         first_pagraph = 1
1768         while 1:
1769             k = find_token(file.body, "\\begin_layout Standard", k, j)
1770             if k == -1:
1771                 break
1772             if first_pagraph:
1773                 first_pagraph = 0
1774                 k = k + 1
1775                 continue
1776             file.body[k:k] = ["\\begin_layout Standard", "",
1777                               "\\end_layout", ""]
1778             k = k + 5
1779             j = j + 4
1780
1781         # convert \\newline to new paragraph
1782         k = i
1783         while 1:
1784             k = find_token(file.body, "\\newline", k, j)
1785             if k == -1:
1786                 break
1787             file.body[k:k+1] = ["\\end_layout", "", "\\begin_layout Standard"]
1788             k = k + 4
1789             j = j + 3
1790         i = i + 1
1791
1792
1793 ##
1794 # Remove double paragraph breaks
1795 #
1796 def revert_ert_paragraphs(file):
1797     i = 0
1798     while 1:
1799         i = find_token(file.body, '\\begin_inset ERT', i)
1800         if i == -1:
1801             return
1802         j = find_end_of_inset(file.body, i)
1803         if j == -1:
1804             file.warning("Malformed lyx file: Missing '\\end_inset'.")
1805             i = i + 1
1806             continue
1807
1808         # replace paragraph breaks with \newline
1809         k = i
1810         while 1:
1811             k = find_token(file.body, "\\end_layout", k, j)
1812             l = find_token(file.body, "\\begin_layout", k, j)
1813             if k == -1 or l == -1:
1814                 break
1815             file.body[k:l+1] = ["\\newline"]
1816             j = j - l + k
1817             k = k + 1
1818
1819         # replace double \newlines with paragraph breaks
1820         k = i
1821         while 1:
1822             k = find_token(file.body, "\\newline", k, j)
1823             if k == -1:
1824                 break
1825             l = k + 1
1826             while file.body[l] == "":
1827                 l = l + 1
1828             if strip(file.body[l]) and split(file.body[l])[0] == "\\newline":
1829                 file.body[k:l+1] = ["\\end_layout", "",
1830                                     "\\begin_layout Standard"]
1831                 j = j - l + k + 2
1832                 k = k + 3
1833             else:
1834                 k = k + 1
1835         i = i + 1
1836
1837
1838 ##
1839 # Convertion hub
1840 #
1841
1842 convert = [[223, [insert_tracking_changes, add_end_header, remove_color_default,
1843                   convert_spaces, convert_bibtex, remove_insetparent]],
1844            [224, [convert_external, convert_comment]],
1845            [225, [add_end_layout, layout2begin_layout, convert_end_document,
1846                   convert_table_valignment_middle, convert_breaks]],
1847            [226, [convert_note]],
1848            [227, [convert_box]],
1849            [228, [convert_collapsable, convert_ert]],
1850            [229, [convert_minipage]],
1851            [230, [convert_jurabib]],
1852            [231, [convert_float]],
1853            [232, [convert_bibtopic]],
1854            [233, [convert_graphics, convert_names]],
1855            [234, [convert_cite_engine]],
1856            [235, [convert_paperpackage]],
1857            [236, [convert_bullets, add_begin_header, add_begin_body,
1858                   normalize_papersize, strip_end_space]],
1859            [237, [use_x_boolean]],
1860            [238, [update_latexaccents]],
1861            [239, [normalize_paragraph_params]],
1862            [240, [convert_output_changes]],
1863            [241, [convert_ert_paragraphs]]]
1864
1865 revert =  [[240, [revert_ert_paragraphs]],
1866            [239, [revert_output_changes]],
1867            [238, []],
1868            [237, []],
1869            [236, [use_x_binary]],
1870            [235, [denormalize_papersize, remove_begin_body,remove_begin_header,
1871                   revert_bullets]],
1872            [234, [revert_paperpackage]],
1873            [233, [revert_cite_engine]],
1874            [232, [revert_names]],
1875            [231, [revert_bibtopic]],
1876            [230, [revert_float]],
1877            [229, [revert_jurabib]],
1878            [228, []],
1879            [227, [revert_collapsable, revert_ert]],
1880            [226, [revert_box, revert_external_2]],
1881            [225, [revert_note]],
1882            [224, [rm_end_layout, begin_layout2layout, revert_end_document,
1883                   revert_valignment_middle, convert_vspace, convert_frameless_box]],
1884            [223, [revert_external_2, revert_comment, revert_eqref]],
1885            [221, [rm_end_header, revert_spaces, revert_bibtex,
1886                   rm_tracking_changes, rm_body_changes]]]
1887
1888
1889 if __name__ == "__main__":
1890     pass