]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_1_5.py
unicodesymbols: add some missing punctuation characters again
[lyx.git] / lib / lyx2lyx / lyx_1_5.py
1 # This file is part of lyx2lyx
2 # -*- coding: utf-8 -*-
3 # Copyright (C) 2006 José Matos <jamatos@lyx.org>
4 # Copyright (C) 2004-2006 Georg Baum <Georg.Baum@post.rwth-aachen.de>
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 """ Convert files to the file format generated by lyx 1.5"""
21
22 import re
23 import unicodedata
24
25 from parser_tools import find_re, find_token, find_token_backwards, find_token_exact, find_tokens, find_end_of, get_value
26 from LyX import get_encoding
27
28
29 ####################################################################
30 # Private helper functions
31
32 def find_end_of_inset(lines, i):
33     " Find end of inset, where lines[i] is included."
34     return find_end_of(lines, i, "\\begin_inset", "\\end_inset")
35
36 def find_end_of_layout(lines, i):
37     " Find end of layout, where lines[i] is included."
38     return find_end_of(lines, i, "\\begin_layout", "\\end_layout")
39
40 # End of helper functions
41 ####################################################################
42
43
44 ##
45 #  Notes: Framed/Shaded
46 #
47
48 def revert_framed(document):
49     "Revert framed notes. "
50     i = 0
51     while 1:
52         i = find_tokens(document.body, ["\\begin_inset Note Framed", "\\begin_inset Note Shaded"], i)
53
54         if i == -1:
55             return
56         document.body[i] = "\\begin_inset Note"
57         i = i + 1
58
59
60 ##
61 #  Fonts
62 #
63
64 roman_fonts      = {'default' : 'default', 'ae'       : 'ae',
65                     'times'   : 'times',   'palatino' : 'palatino',
66                     'helvet'  : 'default', 'avant'    : 'default',
67                     'newcent' : 'newcent', 'bookman'  : 'bookman',
68                     'pslatex' : 'times'}
69 sans_fonts       = {'default' : 'default', 'ae'       : 'default',
70                     'times'   : 'default', 'palatino' : 'default',
71                     'helvet'  : 'helvet',  'avant'    : 'avant',
72                     'newcent' : 'default', 'bookman'  : 'default',
73                     'pslatex' : 'helvet'}
74 typewriter_fonts = {'default' : 'default', 'ae'       : 'default',
75                     'times'   : 'default', 'palatino' : 'default',
76                     'helvet'  : 'default', 'avant'    : 'default',
77                     'newcent' : 'default', 'bookman'  : 'default',
78                     'pslatex' : 'courier'}
79
80 def convert_font_settings(document):
81     " Convert font settings. "
82     i = 0
83     i = find_token_exact(document.header, "\\fontscheme", i)
84     if i == -1:
85         document.warning("Malformed LyX document: Missing `\\fontscheme'.")
86         return
87     font_scheme = get_value(document.header, "\\fontscheme", i, i + 1)
88     if font_scheme == '':
89         document.warning("Malformed LyX document: Empty `\\fontscheme'.")
90         font_scheme = 'default'
91     if not font_scheme in roman_fonts.keys():
92         document.warning("Malformed LyX document: Unknown `\\fontscheme' `%s'." % font_scheme)
93         font_scheme = 'default'
94     document.header[i:i+1] = ['\\font_roman %s' % roman_fonts[font_scheme],
95                           '\\font_sans %s' % sans_fonts[font_scheme],
96                           '\\font_typewriter %s' % typewriter_fonts[font_scheme],
97                           '\\font_default_family default',
98                           '\\font_sc false',
99                           '\\font_osf false',
100                           '\\font_sf_scale 100',
101                           '\\font_tt_scale 100']
102
103
104 def revert_font_settings(document):
105     " Revert font settings. "
106     i = 0
107     insert_line = -1
108     fonts = {'roman' : 'default', 'sans' : 'default', 'typewriter' : 'default'}
109     for family in 'roman', 'sans', 'typewriter':
110         name = '\\font_%s' % family
111         i = find_token_exact(document.header, name, i)
112         if i == -1:
113             document.warning("Malformed LyX document: Missing `%s'." % name)
114             i = 0
115         else:
116             if (insert_line < 0):
117                 insert_line = i
118             fonts[family] = get_value(document.header, name, i, i + 1)
119             del document.header[i]
120     i = find_token_exact(document.header, '\\font_default_family', i)
121     if i == -1:
122         document.warning("Malformed LyX document: Missing `\\font_default_family'.")
123         font_default_family = 'default'
124     else:
125         font_default_family = get_value(document.header, "\\font_default_family", i, i + 1)
126         del document.header[i]
127     i = find_token_exact(document.header, '\\font_sc', i)
128     if i == -1:
129         document.warning("Malformed LyX document: Missing `\\font_sc'.")
130         font_sc = 'false'
131     else:
132         font_sc = get_value(document.header, '\\font_sc', i, i + 1)
133         del document.header[i]
134     if font_sc != 'false':
135         document.warning("Conversion of '\\font_sc' not yet implemented.")
136     i = find_token_exact(document.header, '\\font_osf', i)
137     if i == -1:
138         document.warning("Malformed LyX document: Missing `\\font_osf'.")
139         font_osf = 'false'
140     else:
141         font_osf = get_value(document.header, '\\font_osf', i, i + 1)
142         del document.header[i]
143     i = find_token_exact(document.header, '\\font_sf_scale', i)
144     if i == -1:
145         document.warning("Malformed LyX document: Missing `\\font_sf_scale'.")
146         font_sf_scale = '100'
147     else:
148         font_sf_scale = get_value(document.header, '\\font_sf_scale', i, i + 1)
149         del document.header[i]
150     if font_sf_scale != '100':
151         document.warning("Conversion of '\\font_sf_scale' not yet implemented.")
152     i = find_token_exact(document.header, '\\font_tt_scale', i)
153     if i == -1:
154         document.warning("Malformed LyX document: Missing `\\font_tt_scale'.")
155         font_tt_scale = '100'
156     else:
157         font_tt_scale = get_value(document.header, '\\font_tt_scale', i, i + 1)
158         del document.header[i]
159     if font_tt_scale != '100':
160         document.warning("Conversion of '\\font_tt_scale' not yet implemented.")
161     for font_scheme in roman_fonts.keys():
162         if (roman_fonts[font_scheme] == fonts['roman'] and
163             sans_fonts[font_scheme] == fonts['sans'] and
164             typewriter_fonts[font_scheme] == fonts['typewriter']):
165             document.header.insert(insert_line, '\\fontscheme %s' % font_scheme)
166             if font_default_family != 'default':
167                 document.preamble.append('\\renewcommand{\\familydefault}{\\%s}' % font_default_family)
168             if font_osf == 'true':
169                 document.warning("Ignoring `\\font_osf = true'")
170             return
171     font_scheme = 'default'
172     document.header.insert(insert_line, '\\fontscheme %s' % font_scheme)
173     if fonts['roman'] == 'cmr':
174         document.preamble.append('\\renewcommand{\\rmdefault}{cmr}')
175         if font_osf == 'true':
176             document.preamble.append('\\usepackage{eco}')
177             font_osf = 'false'
178     for font in 'lmodern', 'charter', 'utopia', 'beraserif', 'ccfonts', 'chancery':
179         if fonts['roman'] == font:
180             document.preamble.append('\\usepackage{%s}' % font)
181     for font in 'cmss', 'lmss', 'cmbr':
182         if fonts['sans'] == font:
183             document.preamble.append('\\renewcommand{\\sfdefault}{%s}' % font)
184     for font in 'berasans':
185         if fonts['sans'] == font:
186             document.preamble.append('\\usepackage{%s}' % font)
187     for font in 'cmtt', 'lmtt', 'cmtl':
188         if fonts['typewriter'] == font:
189             document.preamble.append('\\renewcommand{\\ttdefault}{%s}' % font)
190     for font in 'courier', 'beramono', 'luximono':
191         if fonts['typewriter'] == font:
192             document.preamble.append('\\usepackage{%s}' % font)
193     if font_default_family != 'default':
194         document.preamble.append('\\renewcommand{\\familydefault}{\\%s}' % font_default_family)
195     if font_osf == 'true':
196         document.warning("Ignoring `\\font_osf = true'")
197
198
199 def revert_booktabs(document):
200     " We remove the booktabs flag or everything else will become a mess. "
201     re_row = re.compile(r'^<row.*space="[^"]+".*>$')
202     re_tspace = re.compile(r'\s+topspace="[^"]+"')
203     re_bspace = re.compile(r'\s+bottomspace="[^"]+"')
204     re_ispace = re.compile(r'\s+interlinespace="[^"]+"')
205     i = 0
206     while 1:
207         i = find_token(document.body, "\\begin_inset Tabular", i)
208         if i == -1:
209             return
210         j = find_end_of_inset(document.body, i + 1)
211         if j == -1:
212             document.warning("Malformed LyX document: Could not find end of tabular.")
213             continue
214         for k in range(i, j):
215             if re.search('^<features.* booktabs="true".*>$', document.body[k]):
216                 document.warning("Converting 'booktabs' table to normal table.")
217                 document.body[k] = document.body[k].replace(' booktabs="true"', '')
218             if re.search(re_row, document.body[k]):
219                 document.warning("Removing extra row space.")
220                 document.body[k] = re_tspace.sub('', document.body[k])
221                 document.body[k] = re_bspace.sub('', document.body[k])
222                 document.body[k] = re_ispace.sub('', document.body[k])
223         i = i + 1
224
225
226 def convert_multiencoding(document, forward):
227     """ Fix files with multiple encodings.
228 Files with an inputencoding of "auto" or "default" and multiple languages
229 where at least two languages have different default encodings are encoded
230 in multiple encodings for file formats < 249. These files are incorrectly
231 read and written (as if the whole file was in the encoding of the main
232 language).
233 This is not true for files written by CJK-LyX, they are always in the locale
234 encoding.
235
236 This function
237 - converts from fake unicode values to true unicode if forward is true, and
238 - converts from true unicode values to fake unicode if forward is false.
239 document.encoding must be set to the old value (format 248) in both cases.
240
241 We do this here and not in LyX.py because it is far easier to do the
242 necessary parsing in modern formats than in ancient ones.
243 """
244     if document.cjk_encoding != '':
245         return
246     encoding_stack = [document.encoding]
247     lang_re = re.compile(r"^\\lang\s(\S+)")
248     if document.inputencoding == "auto" or document.inputencoding == "default":
249         for i in range(len(document.body)):
250             result = lang_re.match(document.body[i])
251             if result:
252                 language = result.group(1)
253                 if language == "default":
254                     document.warning("Resetting encoding from %s to %s." % (encoding_stack[-1], document.encoding), 3)
255                     encoding_stack[-1] = document.encoding
256                 else:
257                     from lyx2lyx_lang import lang
258                     document.warning("Setting encoding from %s to %s." % (encoding_stack[-1], lang[language][3]), 3)
259                     encoding_stack[-1] = lang[language][3]
260             elif find_token(document.body, "\\begin_layout", i, i + 1) == i:
261                 document.warning("Adding nested encoding %s." % encoding_stack[-1], 3)
262                 encoding_stack.append(encoding_stack[-1])
263             elif find_token(document.body, "\\end_layout", i, i + 1) == i:
264                 document.warning("Removing nested encoding %s." % encoding_stack[-1], 3)
265                 if len(encoding_stack) == 1:
266                     # Don't remove the document encoding from the stack
267                     document.warning("Malformed LyX document: Unexpected `\\end_layout'.")
268                 else:
269                     del encoding_stack[-1]
270             if encoding_stack[-1] != document.encoding:
271                 if forward:
272                     # This line has been incorrectly interpreted as if it was
273                     # encoded in 'encoding'.
274                     # Convert back to the 8bit string that was in the file.
275                     orig = document.body[i].encode(document.encoding)
276                     # Convert the 8bit string that was in the file to unicode
277                     # with the correct encoding.
278                     document.body[i] = orig.decode(encoding_stack[-1])
279                 else:
280                     # Convert unicode to the 8bit string that will be written
281                     # to the file with the correct encoding.
282                     orig = document.body[i].encode(encoding_stack[-1])
283                     # Convert the 8bit string that will be written to the
284                     # file to fake unicode with the encoding that will later
285                     # be used when writing to the file.
286                     document.body[i] = orig.decode(document.encoding)
287
288
289 def convert_utf8(document):
290     " Set document encoding to UTF-8. "
291     convert_multiencoding(document, True)
292     document.encoding = "utf8"
293
294
295 def revert_utf8(document):
296     " Set document encoding to the value corresponding to inputencoding. "
297     i = find_token(document.header, "\\inputencoding", 0)
298     if i == -1:
299         document.header.append("\\inputencoding auto")
300     elif get_value(document.header, "\\inputencoding", i) == "utf8":
301         document.header[i] = "\\inputencoding auto"
302     document.inputencoding = get_value(document.header, "\\inputencoding", 0)
303     document.encoding = get_encoding(document.language, document.inputencoding, 248, document.cjk_encoding)
304     convert_multiencoding(document, False)
305
306
307 def revert_cs_label(document):
308     " Remove status flag of charstyle label. "
309     i = 0
310     while 1:
311         i = find_token(document.body, "\\begin_inset CharStyle", i)
312         if i == -1:
313             return
314         # Seach for a line starting 'show_label'
315         # If it is not there, break with a warning message
316         i = i + 1
317         while 1:
318             if (document.body[i][:10] == "show_label"):
319                 del document.body[i]
320                 break
321             elif (document.body[i][:13] == "\\begin_layout"):
322                 document.warning("Malformed LyX document: Missing 'show_label'.")
323                 break
324             i = i + 1
325
326         i = i + 1
327
328
329 def convert_bibitem(document):
330     """ Convert
331 \bibitem [option]{argument}
332
333 to
334
335 \begin_inset LatexCommand bibitem
336 label "option"
337 key "argument"
338
339 \end_inset
340
341 This must be called after convert_commandparams.
342 """
343     i = 0
344     while 1:
345         i = find_token(document.body, "\\bibitem", i)
346         if i == -1:
347             break
348         j = document.body[i].find('[') + 1
349         k = document.body[i].rfind(']')
350         if j == 0: # No optional argument found
351             option = None
352         else:
353             option = document.body[i][j:k]
354         j = document.body[i].rfind('{') + 1
355         k = document.body[i].rfind('}')
356         argument = document.body[i][j:k]
357         lines = ['\\begin_inset LatexCommand bibitem']
358         if option != None:
359             lines.append('label "%s"' % option.replace('"', '\\"'))
360         lines.append('key "%s"' % argument.replace('"', '\\"'))
361         lines.append('')
362         lines.append('\\end_inset')
363         document.body[i:i+1] = lines
364         i = i + 1
365
366
367 commandparams_info = {
368     # command : [option1, option2, argument]
369     "bibitem" : ["label", "", "key"],
370     "bibtex" : ["options", "btprint", "bibfiles"],
371     "cite"        : ["after", "before", "key"],
372     "citet"       : ["after", "before", "key"],
373     "citep"       : ["after", "before", "key"],
374     "citealt"     : ["after", "before", "key"],
375     "citealp"     : ["after", "before", "key"],
376     "citeauthor"  : ["after", "before", "key"],
377     "citeyear"    : ["after", "before", "key"],
378     "citeyearpar" : ["after", "before", "key"],
379     "citet*"      : ["after", "before", "key"],
380     "citep*"      : ["after", "before", "key"],
381     "citealt*"    : ["after", "before", "key"],
382     "citealp*"    : ["after", "before", "key"],
383     "citeauthor*" : ["after", "before", "key"],
384     "Citet"       : ["after", "before", "key"],
385     "Citep"       : ["after", "before", "key"],
386     "Citealt"     : ["after", "before", "key"],
387     "Citealp"     : ["after", "before", "key"],
388     "Citeauthor"  : ["after", "before", "key"],
389     "Citet*"      : ["after", "before", "key"],
390     "Citep*"      : ["after", "before", "key"],
391     "Citealt*"    : ["after", "before", "key"],
392     "Citealp*"    : ["after", "before", "key"],
393     "Citeauthor*" : ["after", "before", "key"],
394     "citefield"   : ["after", "before", "key"],
395     "citetitle"   : ["after", "before", "key"],
396     "cite*"       : ["after", "before", "key"],
397     "hfill" : ["", "", ""],
398     "index"      : ["", "", "name"],
399     "printindex" : ["", "", "name"],
400     "label" : ["", "", "name"],
401     "eqref"     : ["name", "", "reference"],
402     "pageref"   : ["name", "", "reference"],
403     "prettyref" : ["name", "", "reference"],
404     "ref"       : ["name", "", "reference"],
405     "vpageref"  : ["name", "", "reference"],
406     "vref"      : ["name", "", "reference"],
407     "tableofcontents" : ["", "", "type"],
408     "htmlurl" : ["name", "", "target"],
409     "url"     : ["name", "", "target"]}
410
411
412 def convert_commandparams(document):
413     """ Convert
414
415  \begin_inset LatexCommand \cmdname[opt1][opt2]{arg}
416  \end_inset
417
418  to
419
420  \begin_inset LatexCommand cmdname
421  name1 "opt1"
422  name2 "opt2"
423  name3 "arg"
424  \end_inset
425
426  name1, name2 and name3 can be different for each command.
427 """
428     # \begin_inset LatexCommand bibitem was not the official version (see
429     # convert_bibitem()), but could be read in, so we convert it here, too.
430
431     i = 0
432     while 1:
433         i = find_token(document.body, "\\begin_inset LatexCommand", i)
434         if i == -1:
435             break
436         command = document.body[i][26:].strip()
437         if command == "":
438             document.warning("Malformed LyX document: Missing LatexCommand name.")
439             i = i + 1
440             continue
441
442         # The following parser is taken from the original InsetCommandParams::scanCommand
443         name = ""
444         option1 = ""
445         option2 = ""
446         argument = ""
447         state = "WS"
448         # Used to handle things like \command[foo[bar]]{foo{bar}}
449         nestdepth = 0
450         b = 0
451         for c in command:
452             if ((state == "CMDNAME" and c == ' ') or
453                 (state == "CMDNAME" and c == '[') or
454                 (state == "CMDNAME" and c == '{')):
455                 state = "WS"
456             if ((state == "OPTION" and c == ']') or
457                 (state == "SECOPTION" and c == ']') or
458                 (state == "CONTENT" and c == '}')):
459                 if nestdepth == 0:
460                     state = "WS"
461                 else:
462                     nestdepth = nestdepth - 1
463             if ((state == "OPTION" and c == '[') or
464                 (state == "SECOPTION" and c == '[') or
465                 (state == "CONTENT" and c == '{')):
466                 nestdepth = nestdepth + 1
467             if state == "CMDNAME":
468                     name += c
469             elif state == "OPTION":
470                     option1 += c
471             elif state == "SECOPTION":
472                     option2 += c
473             elif state == "CONTENT":
474                     argument += c
475             elif state == "WS":
476                 if c == '\\':
477                     state = "CMDNAME"
478                 elif c == '[' and b != ']':
479                     state = "OPTION"
480                     nestdepth = 0 # Just to be sure
481                 elif c == '[' and b == ']':
482                     state = "SECOPTION"
483                     nestdepth = 0 # Just to be sure
484                 elif c == '{':
485                     state = "CONTENT"
486                     nestdepth = 0 # Just to be sure
487             b = c
488
489         # Now we have parsed the command, output the parameters
490         lines = ["\\begin_inset LatexCommand %s" % name]
491         if option1 != "":
492             if commandparams_info[name][0] == "":
493                 document.warning("Ignoring invalid option `%s' of command `%s'." % (option1, name))
494             else:
495                 lines.append('%s "%s"' % (commandparams_info[name][0], option1.replace('"', '\\"')))
496         if option2 != "":
497             if commandparams_info[name][1] == "":
498                 document.warning("Ignoring invalid second option `%s' of command `%s'." % (option2, name))
499             else:
500                 lines.append('%s "%s"' % (commandparams_info[name][1], option2.replace('"', '\\"')))
501         if argument != "":
502             if commandparams_info[name][2] == "":
503                 document.warning("Ignoring invalid argument `%s' of command `%s'." % (argument, name))
504             else:
505                 lines.append('%s "%s"' % (commandparams_info[name][2], argument.replace('"', '\\"')))
506         document.body[i:i+1] = lines
507         i = i + 1
508
509
510 def revert_commandparams(document):
511     regex = re.compile(r'(\S+)\s+(.+)')
512     i = 0
513     while 1:
514         i = find_token(document.body, "\\begin_inset LatexCommand", i)
515         if i == -1:
516             break
517         name = document.body[i].split()[2]
518         j = find_end_of_inset(document.body, i + 1)
519         preview_line = ""
520         option1 = ""
521         option2 = ""
522         argument = ""
523         for k in range(i + 1, j):
524             match = re.match(regex, document.body[k])
525             if match:
526                 pname = match.group(1)
527                 pvalue = match.group(2)
528                 if pname == "preview":
529                     preview_line = document.body[k]
530                 elif (commandparams_info[name][0] != "" and
531                       pname == commandparams_info[name][0]):
532                     option1 = pvalue.strip('"').replace('\\"', '"')
533                 elif (commandparams_info[name][1] != "" and
534                       pname == commandparams_info[name][1]):
535                     option2 = pvalue.strip('"').replace('\\"', '"')
536                 elif (commandparams_info[name][2] != "" and
537                       pname == commandparams_info[name][2]):
538                     argument = pvalue.strip('"').replace('\\"', '"')
539             elif document.body[k].strip() != "":
540                 document.warning("Ignoring unknown contents `%s' in command inset %s." % (document.body[k], name))
541         if name == "bibitem":
542             if option1 == "":
543                 lines = ["\\bibitem {%s}" % argument]
544             else:
545                 lines = ["\\bibitem [%s]{%s}" % (option1, argument)]
546         else:
547             if option1 == "":
548                 if option2 == "":
549                     lines = ["\\begin_inset LatexCommand \\%s{%s}" % (name, argument)]
550                 else:
551                     lines = ["\\begin_inset LatexCommand \\%s[][%s]{%s}" % (name, option2, argument)]
552             else:
553                 if option2 == "":
554                     lines = ["\\begin_inset LatexCommand \\%s[%s]{%s}" % (name, option1, argument)]
555                 else:
556                     lines = ["\\begin_inset LatexCommand \\%s[%s][%s]{%s}" % (name, option1, option2, argument)]
557         if name != "bibitem":
558             if preview_line != "":
559                 lines.append(preview_line)
560             lines.append('')
561             lines.append('\\end_inset')
562         document.body[i:j+1] = lines
563         i = j + 1
564
565
566 def revert_nomenclature(document):
567     " Convert nomenclature entry to ERT. "
568     regex = re.compile(r'(\S+)\s+(.+)')
569     i = 0
570     use_nomencl = 0
571     while 1:
572         i = find_token(document.body, "\\begin_inset LatexCommand nomenclature", i)
573         if i == -1:
574             break
575         use_nomencl = 1
576         j = find_end_of_inset(document.body, i + 1)
577         preview_line = ""
578         symbol = ""
579         description = ""
580         prefix = ""
581         for k in range(i + 1, j):
582             match = re.match(regex, document.body[k])
583             if match:
584                 name = match.group(1)
585                 value = match.group(2)
586                 if name == "preview":
587                     preview_line = document.body[k]
588                 elif name == "symbol":
589                     symbol = value.strip('"').replace('\\"', '"')
590                 elif name == "description":
591                     description = value.strip('"').replace('\\"', '"')
592                 elif name == "prefix":
593                     prefix = value.strip('"').replace('\\"', '"')
594             elif document.body[k].strip() != "":
595                 document.warning("Ignoring unknown contents `%s' in nomenclature inset." % document.body[k])
596         if prefix == "":
597             command = 'nomenclature{%s}{%s}' % (symbol, description)
598         else:
599             command = 'nomenclature[%s]{%s}{%s}' % (prefix, symbol, description)
600         document.body[i:j+1] = ['\\begin_inset ERT',
601                                 'status collapsed',
602                                 '',
603                                 '\\begin_layout %s' % document.default_layout,
604                                 '',
605                                 '',
606                                 '\\backslash',
607                                 command,
608                                 '\\end_layout',
609                                 '',
610                                 '\\end_inset']
611         i = i + 11
612     if use_nomencl and find_token(document.preamble, '\\usepackage{nomencl}[2005/09/22]', 0) == -1:
613         document.preamble.append('\\usepackage{nomencl}[2005/09/22]')
614         document.preamble.append('\\makenomenclature')
615
616
617 def revert_printnomenclature(document):
618     " Convert printnomenclature to ERT. "
619     regex = re.compile(r'(\S+)\s+(.+)')
620     i = 0
621     use_nomencl = 0
622     while 1:
623         i = find_token(document.body, "\\begin_inset LatexCommand printnomenclature", i)
624         if i == -1:
625             break
626         use_nomencl = 1
627         j = find_end_of_inset(document.body, i + 1)
628         preview_line = ""
629         labelwidth = ""
630         for k in range(i + 1, j):
631             match = re.match(regex, document.body[k])
632             if match:
633                 name = match.group(1)
634                 value = match.group(2)
635                 if name == "preview":
636                     preview_line = document.body[k]
637                 elif name == "labelwidth":
638                     labelwidth = value.strip('"').replace('\\"', '"')
639             elif document.body[k].strip() != "":
640                 document.warning("Ignoring unknown contents `%s' in printnomenclature inset." % document.body[k])
641         if labelwidth == "":
642             command = 'nomenclature{}'
643         else:
644             command = 'nomenclature[%s]' % labelwidth
645         document.body[i:j+1] = ['\\begin_inset ERT',
646                                 'status collapsed',
647                                 '',
648                                 '\\begin_layout %s' % document.default_layout,
649                                 '',
650                                 '',
651                                 '\\backslash',
652                                 command,
653                                 '\\end_layout',
654                                 '',
655                                 '\\end_inset']
656         i = i + 11
657     if use_nomencl and find_token(document.preamble, '\\usepackage{nomencl}[2005/09/22]', 0) == -1:
658         document.preamble.append('\\usepackage{nomencl}[2005/09/22]')
659         document.preamble.append('\\makenomenclature')
660
661
662 def convert_esint(document):
663     " Add \\use_esint setting to header. "
664     i = find_token(document.header, "\\cite_engine", 0)
665     if i == -1:
666         document.warning("Malformed LyX document: Missing `\\cite_engine'.")
667         return
668     # 0 is off, 1 is auto, 2 is on.
669     document.header.insert(i, '\\use_esint 0')
670
671
672 def revert_esint(document):
673     " Remove \\use_esint setting from header. "
674     i = find_token(document.header, "\\use_esint", 0)
675     if i == -1:
676         document.warning("Malformed LyX document: Missing `\\use_esint'.")
677         return
678     use_esint = document.header[i].split()[1]
679     del document.header[i]
680     # 0 is off, 1 is auto, 2 is on.
681     if (use_esint == 2):
682         document.preamble.append('\\usepackage{esint}')
683
684
685 def revert_clearpage(document):
686     " clearpage -> ERT "
687     i = 0
688     while 1:
689         i = find_token(document.body, "\\clearpage", i)
690         if i == -1:
691             break
692         document.body[i:i+1] =  ['\\begin_inset ERT',
693                                 'status collapsed',
694                                 '',
695                                 '\\begin_layout %s' % document.default_layout,
696                                 '',
697                                 '',
698                                 '\\backslash',
699                                 'clearpage',
700                                 '\\end_layout',
701                                 '',
702                                 '\\end_inset']
703     i = i + 1
704
705
706 def revert_cleardoublepage(document):
707     " cleardoublepage -> ERT "
708     i = 0
709     while 1:
710         i = find_token(document.body, "\\cleardoublepage", i)
711         if i == -1:
712             break
713         document.body[i:i+1] =  ['\\begin_inset ERT',
714                                 'status collapsed',
715                                 '',
716                                 '\\begin_layout %s' % document.default_layout,
717                                 '',
718                                 '',
719                                 '\\backslash',
720                                 'cleardoublepage',
721                                 '\\end_layout',
722                                 '',
723                                 '\\end_inset']
724     i = i + 1
725
726
727 def convert_lyxline(document):
728     " remove fontsize commands for \lyxline "
729     # The problematic is: The old \lyxline definition doesn't handle the fontsize
730     # to change the line thickness. The new definiton does this so that imported
731     # \lyxlines would have a different line thickness. The eventual fontsize command
732     # before \lyxline is therefore removed to get the same output.
733     fontsizes = ["tiny", "scriptsize", "footnotesize", "small", "normalsize",
734                  "large", "Large", "LARGE", "huge", "Huge"]
735     for n in range(0, len(fontsizes)):
736         i = 0
737         k = 0
738         while i < len(document.body):
739             i = find_token(document.body, "\\size " + fontsizes[n], i)
740             k = find_token(document.body, "\\lyxline", i)
741             # the corresponding fontsize command is always 2 lines before the \lyxline
742             if (i != -1 and k == i+2):
743                 document.body[i:i+1] = []
744             else:
745                 break
746         i = i + 1
747
748
749 def revert_encodings(document):
750     " Set new encodings to auto. "
751     encodings = ["8859-6", "8859-8", "cp437", "cp437de", "cp850", "cp852",
752                  "cp855", "cp858", "cp862", "cp865", "cp866", "cp1250",
753                  "cp1252", "cp1256", "cp1257", "latin10", "pt254", "tis620-0"]
754     i = find_token(document.header, "\\inputencoding", 0)
755     if i == -1:
756         document.header.append("\\inputencoding auto")
757     else:
758         inputenc = get_value(document.header, "\\inputencoding", i)
759         if inputenc in encodings:
760             document.header[i] = "\\inputencoding auto"
761     document.inputencoding = get_value(document.header, "\\inputencoding", 0)
762
763
764 def convert_caption(document):
765     " Convert caption layouts to caption insets. "
766     i = 0
767     while 1:
768         i = find_token(document.body, "\\begin_layout Caption", i)
769         if i == -1:
770             return
771         j = find_end_of_layout(document.body, i)
772         if j == -1:
773             document.warning("Malformed LyX document: Missing `\\end_layout'.")
774             return
775
776         document.body[j:j] = ["\\end_layout", "", "\\end_inset", "", ""]
777         document.body[i:i+1] = ["\\begin_layout %s" % document.default_layout,
778                             "\\begin_inset Caption", "",
779                             "\\begin_layout %s" % document.default_layout]
780         i = i + 1
781
782
783 def revert_caption(document):
784     " Convert caption insets to caption layouts. "
785     " This assumes that the text class has a caption style. "
786     i = 0
787     while 1:
788         i = find_token(document.body, "\\begin_inset Caption", i)
789         if i == -1:
790             return
791
792         # We either need to delete the previous \begin_layout line, or we
793         # need to end the previous layout if this inset is not in the first
794         # position of the paragraph.
795         layout_before = find_token_backwards(document.body, "\\begin_layout", i)
796         if layout_before == -1:
797             document.warning("Malformed LyX document: Missing `\\begin_layout'.")
798             return
799         layout_line = document.body[layout_before]
800         del_layout_before = True
801         l = layout_before + 1
802         while l < i:
803             if document.body[l] != "":
804                 del_layout_before = False
805                 break
806             l = l + 1
807         if del_layout_before:
808             del document.body[layout_before:i]
809             i = layout_before
810         else:
811             document.body[i:i] = ["\\end_layout", ""]
812             i = i + 2
813
814         # Find start of layout in the inset and end of inset
815         j = find_token(document.body, "\\begin_layout", i)
816         if j == -1:
817             document.warning("Malformed LyX document: Missing `\\begin_layout'.")
818             return
819         k = find_end_of_inset(document.body, i)
820         if k == -1:
821             document.warning("Malformed LyX document: Missing `\\end_inset'.")
822             return
823
824         # We either need to delete the following \end_layout line, or we need
825         # to restart the old layout if this inset is not at the paragraph end.
826         layout_after = find_token(document.body, "\\end_layout", k)
827         if layout_after == -1:
828             document.warning("Malformed LyX document: Missing `\\end_layout'.")
829             return
830         del_layout_after = True
831         l = k + 1
832         while l < layout_after:
833             if document.body[l] != "":
834                 del_layout_after = False
835                 break
836             l = l + 1
837         if del_layout_after:
838             del document.body[k+1:layout_after+1]
839         else:
840             document.body[k+1:k+1] = [layout_line, ""]
841
842         # delete \begin_layout and \end_inset and replace \begin_inset with
843         # "\begin_layout Caption". This works because we can only have one
844         # paragraph in the caption inset: The old \end_layout will be recycled.
845         del document.body[k]
846         if document.body[k] == "":
847             del document.body[k]
848         del document.body[j]
849         if document.body[j] == "":
850             del document.body[j]
851         document.body[i] = "\\begin_layout Caption"
852         if document.body[i+1] == "":
853             del document.body[i+1]
854         i = i + 1
855
856
857 # Accents of InsetLaTeXAccent
858 accent_map = {
859     "`" : u'\u0300', # grave
860     "'" : u'\u0301', # acute
861     "^" : u'\u0302', # circumflex
862     "~" : u'\u0303', # tilde
863     "=" : u'\u0304', # macron
864     "u" : u'\u0306', # breve
865     "." : u'\u0307', # dot above
866     "\"": u'\u0308', # diaresis
867     "r" : u'\u030a', # ring above
868     "H" : u'\u030b', # double acute
869     "v" : u'\u030c', # caron
870     "b" : u'\u0320', # minus sign below
871     "d" : u'\u0323', # dot below
872     "c" : u'\u0327', # cedilla
873     "k" : u'\u0328', # ogonek
874     "t" : u'\u0361'  # tie. This is special: It spans two characters, but
875                      # only one is given as argument, so we don't need to
876                      # treat it differently.
877 }
878
879
880 # special accents of InsetLaTeXAccent without argument
881 special_accent_map = {
882     'i' : u'\u0131', # dotless i
883     'j' : u'\u0237', # dotless j
884     'l' : u'\u0142', # l with stroke
885     'L' : u'\u0141'  # L with stroke
886 }
887
888
889 # special accent arguments of InsetLaTeXAccent
890 accented_map = {
891     '\\i' : u'\u0131', # dotless i
892     '\\j' : u'\u0237'  # dotless j
893 }
894
895
896 def _convert_accent(accent, accented_char):
897     type = accent
898     char = accented_char
899     if char == '':
900         if type in special_accent_map:
901             return special_accent_map[type]
902         # a missing char is treated as space by LyX
903         char = ' '
904     elif type == 'q' and char in ['t', 'd', 'l', 'L']:
905         # Special caron, only used with t, d, l and L.
906         # It is not in the map because we convert it to the same unicode
907         # character as the normal caron: \q{} is only defined if babel with
908         # the czech or slovak language is used, and the normal caron
909         # produces the correct output if the T1 font encoding is used.
910         # For the same reason we never convert to \q{} in the other direction.
911         type = 'v'
912     elif char in accented_map:
913         char = accented_map[char]
914     elif (len(char) > 1):
915         # We can only convert accents on a single char
916         return ''
917     a = accent_map.get(type)
918     if a:
919         return unicodedata.normalize("NFKC", "%s%s" % (char, a))
920     return ''
921
922
923 def convert_ertbackslash(body, i, ert, default_layout):
924     r""" -------------------------------------------------------------------------------------------
925     Convert backslashes and '\n' into valid ERT code, append the converted
926     text to body[i] and return the (maybe incremented) line index i"""
927
928     for c in ert:
929         if c == '\\':
930             body[i] = body[i] + '\\backslash '
931             i = i + 1
932             body.insert(i, '')
933         elif c == '\n':
934             body[i+1:i+1] = ['\\end_layout', '', '\\begin_layout %s' % default_layout, '']
935             i = i + 4
936         else:
937             body[i] = body[i] + c
938     return i
939
940
941 def convert_accent(document):
942     # The following forms are supported by LyX:
943     # '\i \"{a}' (standard form, as written by LyX)
944     # '\i \"{}' (standard form, as written by LyX if the accented char is a space)
945     # '\i \"{ }' (also accepted if the accented char is a space)
946     # '\i \" a'  (also accepted)
947     # '\i \"'    (also accepted)
948     re_wholeinset = re.compile(r'^(.*)(\\i\s+)(.*)$')
949     re_contents = re.compile(r'^([^\s{]+)(.*)$')
950     re_accentedcontents = re.compile(r'^\s*{?([^{}]*)}?\s*$')
951     i = 0
952     while 1:
953         i = find_re(document.body, re_wholeinset, i)
954         if i == -1:
955             return
956         match = re_wholeinset.match(document.body[i])
957         prefix = match.group(1)
958         contents = match.group(3).strip()
959         match = re_contents.match(contents)
960         if match:
961             # Strip first char (always \)
962             accent = match.group(1)[1:]
963             accented_contents = match.group(2).strip()
964             match = re_accentedcontents.match(accented_contents)
965             accented_char = match.group(1)
966             converted = _convert_accent(accent, accented_char)
967             if converted == '':
968                 # Normalize contents
969                 contents = '%s{%s}' % (accent, accented_char),
970             else:
971                 document.body[i] = '%s%s' % (prefix, converted)
972                 i += 1
973                 continue
974         document.warning("Converting unknown InsetLaTeXAccent `\\i %s' to ERT." % contents)
975         document.body[i] = prefix
976         document.body[i+1:i+1] = ['\\begin_inset ERT',
977                                   'status collapsed',
978                                   '',
979                                   '\\begin_layout %s' % document.default_layout,
980                                   '',
981                                   '',
982                                   '']
983         i = convert_ertbackslash(document.body, i + 7,
984                                  '\\%s' % contents,
985                                  document.default_layout)
986         document.body[i+1:i+1] = ['\\end_layout',
987                                   '',
988                                   '\\end_inset']
989         i += 3
990
991
992 def revert_accent(document):
993     inverse_accent_map = {}
994     for k in accent_map:
995         inverse_accent_map[accent_map[k]] = k
996     inverse_special_accent_map = {}
997     for k in special_accent_map:
998         inverse_special_accent_map[special_accent_map[k]] = k
999     inverse_accented_map = {}
1000     for k in accented_map:
1001         inverse_accented_map[accented_map[k]] = k
1002
1003     # Since LyX may insert a line break within a word we must combine all
1004     # words before unicode normalization.
1005     # We do this only if the next line starts with an accent, otherwise we
1006     # would create things like '\begin_inset ERTstatus'.
1007     numberoflines = len(document.body)
1008     for i in range(numberoflines-1):
1009         if document.body[i] == '' or document.body[i+1] == '' or document.body[i][-1] == ' ':
1010             continue
1011         if (document.body[i+1][0] in inverse_accent_map):
1012             # the last character of this line and the first of the next line
1013             # form probably a surrogate pair.
1014             while (len(document.body[i+1]) > 0 and document.body[i+1][0] != ' '):
1015                 document.body[i] += document.body[i+1][0]
1016                 document.body[i+1] = document.body[i+1][1:]
1017
1018     # Normalize to "Normal form D" (NFD, also known as canonical decomposition).
1019     # This is needed to catch all accented characters.
1020     for i in range(numberoflines):
1021         # Unfortunately we have a mixture of unicode strings and plain strings,
1022         # because we never use u'xxx' for string literals, but 'xxx'.
1023         # Therefore we may have to try two times to normalize the data.
1024         try:
1025             document.body[i] = unicodedata.normalize("NFKD", document.body[i])
1026         except TypeError:
1027             document.body[i] = unicodedata.normalize("NFKD", unicode(document.body[i], 'utf-8'))
1028
1029     # Replace accented characters with InsetLaTeXAccent
1030     # Do not convert characters that can be represented in the chosen
1031     # encoding.
1032     encoding_stack = [get_encoding(document.language, document.inputencoding, 248, document.cjk_encoding)]
1033     lang_re = re.compile(r"^\\lang\s(\S+)")
1034     for i in range(len(document.body)):
1035
1036         if (document.inputencoding == "auto" or document.inputencoding == "default") and document.cjk_encoding != '':
1037             # Track the encoding of the current line
1038             result = lang_re.match(document.body[i])
1039             if result:
1040                 language = result.group(1)
1041                 if language == "default":
1042                     encoding_stack[-1] = document.encoding
1043                 else:
1044                     from lyx2lyx_lang import lang
1045                     encoding_stack[-1] = lang[language][3]
1046                 continue
1047             elif find_token(document.body, "\\begin_layout", i, i + 1) == i:
1048                 encoding_stack.append(encoding_stack[-1])
1049                 continue
1050             elif find_token(document.body, "\\end_layout", i, i + 1) == i:
1051                 del encoding_stack[-1]
1052                 continue
1053
1054         for j in range(len(document.body[i])):
1055             # dotless i and dotless j are both in special_accent_map and can
1056             # occur as an accented character, so we need to test that the
1057             # following character is no accent
1058             if (document.body[i][j] in inverse_special_accent_map and
1059                 (j == len(document.body[i]) - 1 or document.body[i][j+1] not in inverse_accent_map)):
1060                 accent = document.body[i][j]
1061                 try:
1062                     dummy = accent.encode(encoding_stack[-1])
1063                 except UnicodeEncodeError:
1064                     # Insert the rest of the line as new line
1065                     if j < len(document.body[i]) - 1:
1066                         document.body[i+1:i+1] = document.body[i][j+1:]
1067                     # Delete the accented character
1068                     if j > 0:
1069                         document.body[i] = document.body[i][:j-1]
1070                     else:
1071                         document.body[i] = u''
1072                     # Finally add the InsetLaTeXAccent
1073                     document.body[i] += "\\i \\%s{}" % inverse_special_accent_map[accent]
1074                     break
1075             elif j > 0 and document.body[i][j] in inverse_accent_map:
1076                 accented_char = document.body[i][j-1]
1077                 if accented_char == ' ':
1078                     # Conform to LyX output
1079                     accented_char = ''
1080                 elif accented_char in inverse_accented_map:
1081                     accented_char = inverse_accented_map[accented_char]
1082                 accent = document.body[i][j]
1083                 try:
1084                     dummy = unicodedata.normalize("NFKC", accented_char + accent).encode(encoding_stack[-1])
1085                 except UnicodeEncodeError:
1086                     # Insert the rest of the line as new line
1087                     if j < len(document.body[i]) - 1:
1088                         document.body[i+1:i+1] = document.body[i][j+1:]
1089                     # Delete the accented characters
1090                     if j > 1:
1091                         document.body[i] = document.body[i][:j-2]
1092                     else:
1093                         document.body[i] = u''
1094                     # Finally add the InsetLaTeXAccent
1095                     document.body[i] += "\\i \\%s{%s}" % (inverse_accent_map[accent], accented_char)
1096                     break
1097     # Normalize to "Normal form C" (NFC, pre-composed characters) again
1098     for i in range(numberoflines):
1099         document.body[i] = unicodedata.normalize("NFKC", document.body[i])
1100
1101
1102 def normalize_font_whitespace(document):
1103     """ Before format 259 the font changes were ignored if a
1104     whitespace was the first or last character in the sequence, this function
1105     transfers the whitespace outside."""
1106
1107     if document.backend != "latex":
1108         return
1109
1110     lines = document.body
1111
1112     char_properties = {"\\series": "default",
1113                        "\\emph": "default",
1114                        "\\color": "none",
1115                        "\\shape": "default",
1116                        "\\bar": "default",
1117                        "\\family": "default"}
1118     changes = {}
1119
1120     i = 0
1121     while i < len(lines):
1122         words = lines[i].split()
1123
1124         if len(words) > 0 and words[0] == "\\begin_layout":
1125             # a new paragraph resets all font changes
1126             changes.clear()
1127
1128         elif len(words) > 1 and words[0] in char_properties.keys():
1129             # we have a font change
1130             if char_properties[words[0]] == words[1]:
1131                 # property gets reset
1132                 if words[0] in changes.keys():
1133                     del changes[words[0]]
1134                 defaultproperty = True
1135             else:
1136                 # property gets set
1137                 changes[words[0]] = words[1]
1138                 defaultproperty = False
1139
1140             # We need to explicitly reset all changed properties if we find
1141             # a space below, because LyX 1.4 would output the space after
1142             # closing the previous change and before starting the new one,
1143             # and closing a font change means to close all properties, not
1144             # just the changed one.
1145
1146             if lines[i-1] and lines[i-1][-1] == " ":
1147                 lines[i-1] = lines[i-1][:-1]
1148                 # a space before the font change
1149                 added_lines = [" "]
1150                 for k in changes.keys():
1151                     # exclude property k because that is already in lines[i]
1152                     if k != words[0]:
1153                         added_lines[1:1] = ["%s %s" % (k, changes[k])]
1154                 for k in changes.keys():
1155                     # exclude property k because that must be added below anyway
1156                     if k != words[0]:
1157                         added_lines[0:0] = ["%s %s" % (k, char_properties[k])]
1158                 if defaultproperty:
1159                     # Property is reset in lines[i], so add the new stuff afterwards
1160                     lines[i+1:i+1] = added_lines
1161                 else:
1162                     # Reset property for the space
1163                     added_lines[0:0] = ["%s %s" % (words[0], char_properties[words[0]])]
1164                     lines[i:i] = added_lines
1165                 i = i + len(added_lines)
1166
1167             elif lines[i+1] and lines[i+1][0] == " " and (len(changes) > 0 or not defaultproperty):
1168                 # a space after the font change
1169                 if (lines[i+1] == " " and lines[i+2]):
1170                     next_words = lines[i+2].split()
1171                     if len(next_words) > 0 and next_words[0] == words[0]:
1172                         # a single blank with a property different from the
1173                         # previous and the next line must not be changed
1174                         i = i + 2
1175                         continue
1176                 lines[i+1] = lines[i+1][1:]
1177                 added_lines = [" "]
1178                 for k in changes.keys():
1179                     # exclude property k because that is already in lines[i]
1180                     if k != words[0]:
1181                         added_lines[1:1] = ["%s %s" % (k, changes[k])]
1182                 for k in changes.keys():
1183                     # exclude property k because that must be added below anyway
1184                     if k != words[0]:
1185                         added_lines[0:0] = ["%s %s" % (k, char_properties[k])]
1186                 # Reset property for the space
1187                 added_lines[0:0] = ["%s %s" % (words[0], char_properties[words[0]])]
1188                 lines[i:i] = added_lines
1189                 i = i + len(added_lines)
1190
1191         i = i + 1
1192
1193
1194 def revert_utf8x(document):
1195     " Set utf8x encoding to utf8. "
1196     i = find_token(document.header, "\\inputencoding", 0)
1197     if i == -1:
1198         document.header.append("\\inputencoding auto")
1199     else:
1200         inputenc = get_value(document.header, "\\inputencoding", i)
1201         if inputenc == "utf8x":
1202             document.header[i] = "\\inputencoding utf8"
1203     document.inputencoding = get_value(document.header, "\\inputencoding", 0)
1204
1205
1206 def revert_utf8plain(document):
1207     " Set utf8plain encoding to utf8. "
1208     i = find_token(document.header, "\\inputencoding", 0)
1209     if i == -1:
1210         document.header.append("\\inputencoding auto")
1211     else:
1212         inputenc = get_value(document.header, "\\inputencoding", i)
1213         if inputenc == "utf8-plain":
1214             document.header[i] = "\\inputencoding utf8"
1215     document.inputencoding = get_value(document.header, "\\inputencoding", 0)
1216
1217
1218 def revert_beamer_alert(document):
1219     " Revert beamer's \\alert inset back to ERT. "
1220     i = 0
1221     while 1:
1222         i = find_token(document.body, "\\begin_inset CharStyle Alert", i)
1223         if i == -1:
1224             return
1225         document.body[i] = "\\begin_inset ERT"
1226         i = i + 1
1227         while 1:
1228             if (document.body[i][:13] == "\\begin_layout"):
1229                 # Insert the \alert command
1230                 document.body[i + 1] = "\\alert{" + document.body[i + 1] + '}'
1231                 break
1232             i = i + 1
1233
1234         i = i + 1
1235
1236
1237 def revert_beamer_structure(document):
1238     " Revert beamer's \\structure inset back to ERT. "
1239     i = 0
1240     while 1:
1241         i = find_token(document.body, "\\begin_inset CharStyle Structure", i)
1242         if i == -1:
1243             return
1244         document.body[i] = "\\begin_inset ERT"
1245         i = i + 1
1246         while 1:
1247             if (document.body[i][:13] == "\\begin_layout"):
1248                 document.body[i + 1] = "\\structure{" + document.body[i + 1] + '}'
1249                 break
1250             i = i + 1
1251
1252         i = i + 1
1253
1254
1255 def convert_changes(document):
1256     " Switch output_changes off if tracking_changes is off. "
1257     i = find_token(document.header, '\\tracking_changes', 0)
1258     if i == -1:
1259         document.warning("Malformed lyx document: Missing '\\tracking_changes'.")
1260         return
1261     j = find_token(document.header, '\\output_changes', 0)
1262     if j == -1:
1263         document.warning("Malformed lyx document: Missing '\\output_changes'.")
1264         return
1265     tracking_changes = get_value(document.header, "\\tracking_changes", i)
1266     output_changes = get_value(document.header, "\\output_changes", j)
1267     if tracking_changes == "false" and output_changes == "true":
1268         document.header[j] = "\\output_changes false"
1269
1270
1271 def revert_ascii(document):
1272     " Set ascii encoding to auto. "
1273     i = find_token(document.header, "\\inputencoding", 0)
1274     if i == -1:
1275         document.header.append("\\inputencoding auto")
1276     else:
1277         inputenc = get_value(document.header, "\\inputencoding", i)
1278         if inputenc == "ascii":
1279             document.header[i] = "\\inputencoding auto"
1280     document.inputencoding = get_value(document.header, "\\inputencoding", 0)
1281
1282
1283 def normalize_language_name(document):
1284     lang = { "brazil": "brazilian",
1285              "portuges": "portuguese"}
1286
1287     if document.language in lang:
1288         document.language = lang[document.language]
1289         i = find_token(document.header, "\\language", 0)
1290         document.header[i] = "\\language %s" % document.language
1291
1292
1293 def revert_language_name(document):
1294     lang = { "brazilian": "brazil",
1295              "portuguese": "portuges"}
1296
1297     if document.language in lang:
1298         document.language = lang[document.language]
1299         i = find_token(document.header, "\\language", 0)
1300         document.header[i] = "\\language %s" % document.language
1301
1302 #
1303 #  \textclass cv -> \textclass simplecv
1304 def convert_cv_textclass(document):
1305     if document.textclass == "cv":
1306         document.textclass = "simplecv"
1307
1308
1309 def revert_cv_textclass(document):
1310     if document.textclass == "simplecv":
1311         document.textclass = "cv"
1312
1313
1314 def convert_tableborder(document):
1315     # The problematic is: LyX double the table cell border as it ignores the "|" character in
1316     # the cell arguments. A fix takes care of this and therefore the "|" has to be removed
1317     i = 0
1318     while i < len(document.body):
1319         h = document.body[i].find("leftline=\"true\"", 0, len(document.body[i]))
1320         k = document.body[i].find("|>{", 0, len(document.body[i]))
1321         # the two tokens have to be in one line
1322         if (h != -1 and k != -1):
1323             # delete the "|"
1324             document.body[i] = document.body[i][:k] + document.body[i][k+1:len(document.body[i])-1]
1325         i = i + 1
1326
1327
1328 def revert_tableborder(document):
1329     i = 0
1330     while i < len(document.body):
1331         h = document.body[i].find("leftline=\"true\"", 0, len(document.body[i]))
1332         k = document.body[i].find(">{", 0, len(document.body[i]))
1333         # the two tokens have to be in one line
1334         if (h != -1 and k != -1):
1335             # add the "|"
1336             document.body[i] = document.body[i][:k] + '|' + document.body[i][k:]
1337         i = i + 1
1338
1339
1340 def revert_armenian(document):
1341     
1342     # set inputencoding from armscii8 to auto 
1343     if document.inputencoding == "armscii8":
1344         i = find_token(document.header, "\\inputencoding", 0)
1345         if i != -1:
1346             document.header[i] = "\\inputencoding auto"
1347     # check if preamble exists, if not k is set to -1 
1348     i = 0
1349     k = -1
1350     while i < len(document.preamble):
1351         if k == -1:
1352             k = document.preamble[i].find("\\", 0, len(document.preamble[i]))
1353         if k == -1:
1354             k = document.preamble[i].find("%", 0, len(document.preamble[i]))
1355         i = i + 1
1356     # add the entry \usepackage{armtex} to the document preamble
1357     if document.language == "armenian":
1358         # set the armtex entry as the first preamble line
1359         if k != -1:
1360             document.preamble[0:0] = ["\\usepackage{armtex}"]
1361         # create the preamble when it doesn't exist
1362         else:
1363             document.preamble.append('\\usepackage{armtex}')
1364     # Set document language from armenian to english 
1365     if document.language == "armenian":
1366         document.language = "english"
1367         i = find_token(document.header, "\\language", 0)
1368         if i != -1:
1369             document.header[i] = "\\language english"
1370
1371
1372 def revert_CJK(document):
1373     " Set CJK encodings to default and languages chinese, japanese and korean to english. "
1374     encodings = ["Bg5", "Bg5+", "GB", "GBt", "GBK", "JIS",
1375                  "KS", "SJIS", "UTF8", "EUC-TW", "EUC-JP"]
1376     i = find_token(document.header, "\\inputencoding", 0)
1377     if i == -1:
1378         document.header.append("\\inputencoding auto")
1379     else:
1380         inputenc = get_value(document.header, "\\inputencoding", i)
1381         if inputenc in encodings:
1382             document.header[i] = "\\inputencoding default"
1383     document.inputencoding = get_value(document.header, "\\inputencoding", 0)
1384
1385     if document.language == "chinese-simplified" or \
1386        document.language == "chinese-traditional" or \
1387        document.language == "japanese" or document.language == "korean":
1388         document.language = "english"
1389         i = find_token(document.header, "\\language", 0)
1390         if i != -1:
1391             document.header[i] = "\\language english"
1392
1393
1394 def revert_preamble_listings_params(document):
1395     " Revert preamble option \listings_params "
1396     i = find_token(document.header, "\\listings_params", 0)
1397     if i != -1:
1398         document.preamble.append('\\usepackage{listings}')
1399         document.preamble.append('\\lstset{%s}' % document.header[i].split()[1].strip('"'))
1400         document.header.pop(i);
1401
1402
1403 def revert_listings_inset(document):
1404     r''' Revert listings inset to \lstinline or \begin, \end lstlisting, translate 
1405 FROM
1406
1407 \begin_inset 
1408 lstparams "language=Delphi"
1409 inline true
1410 status open
1411
1412 \begin_layout Standard
1413 var i = 10;
1414 \end_layout
1415
1416 \end_inset
1417
1418 TO
1419
1420 \begin_inset ERT
1421 status open
1422 \begin_layout Standard
1423
1424
1425 \backslash
1426 lstinline[language=Delphi]{var i = 10;}
1427 \end_layout
1428
1429 \end_inset
1430
1431 There can be an caption inset in this inset
1432
1433 \begin_layout Standard
1434 \begin_inset Caption
1435
1436 \begin_layout Standard
1437 before label
1438 \begin_inset LatexCommand label
1439 name "lst:caption"
1440
1441 \end_inset
1442
1443 after label
1444 \end_layout
1445
1446 \end_inset
1447
1448
1449 \end_layout
1450
1451 '''
1452     i = 0
1453     while True:
1454         i = find_token(document.body, '\\begin_inset listings', i)
1455         if i == -1:
1456             break
1457         else:
1458             if not '\\usepackage{listings}' in document.preamble:
1459                 document.preamble.append('\\usepackage{listings}')
1460         j = find_end_of_inset(document.body, i + 1)
1461         if j == -1:
1462             # this should not happen
1463             break
1464         inline = 'false'
1465         params = ''
1466         status = 'open'
1467         # first three lines
1468         for line in range(i + 1, i + 4):
1469             if document.body[line].startswith('inline'):
1470                 inline = document.body[line].split()[1]
1471             if document.body[line].startswith('lstparams'):
1472                 params = document.body[line].split()[1].strip('"')
1473             if document.body[line].startswith('status'):
1474                 status = document.body[line].split()[1].strip()
1475                 k = line + 1
1476         # caption?
1477         caption = ''
1478         label = ''
1479         cap = find_token(document.body, '\\begin_inset Caption', i)
1480         if cap != -1:
1481             cap_end = find_end_of_inset(document.body, cap + 1)
1482             if cap_end == -1:
1483                 # this should not happen
1484                 break
1485             # label?
1486             lbl = find_token(document.body, '\\begin_inset LatexCommand label', cap + 1)
1487             if lbl != -1:
1488                 lbl_end = find_end_of_inset(document.body, lbl + 1)
1489                 if lbl_end == -1:
1490                     # this should not happen
1491                     break
1492             else:
1493                 lbl = cap_end
1494                 lbl_end = cap_end
1495             for line in document.body[lbl : lbl_end + 1]:
1496                 if line.startswith('name '):
1497                     label = line.split()[1].strip('"')
1498                     break
1499             for line in document.body[cap : lbl ] + document.body[lbl_end + 1 : cap_end + 1]:
1500                 if not line.startswith('\\'):
1501                     caption += line.strip()
1502             k = cap_end + 1
1503         inlinecode = ''
1504         # looking for the oneline code for lstinline
1505         inlinecode = document.body[find_end_of_layout(document.body, 
1506             find_token(document.body, '\\begin_layout Standard', i + 1) +1 ) - 1]
1507         if len(caption) > 0:
1508             if len(params) == 0:
1509                 params = 'caption={%s}' % caption
1510             else:
1511                 params += ',caption={%s}' % caption
1512         if len(label) > 0:
1513             if len(params) == 0:
1514                 params = 'label={%s}' % label
1515             else:
1516                 params += ',label={%s}' % label
1517         if len(params) > 0:
1518             params = '[%s]' % params
1519             params = params.replace('\\', '\\backslash\n')
1520         if inline == 'true':
1521             document.body[i:(j+1)] = [r'\begin_inset ERT',
1522                                       'status %s' % status,
1523                                       r'\begin_layout Standard',
1524                                       '', 
1525                                       '',
1526                                       r'\backslash',
1527                                       'lstinline%s{%s}' % (params, inlinecode),
1528                                       r'\end_layout',
1529                                       '',
1530                                       r'\end_inset']
1531         else:
1532             document.body[i: j+1] =  [r'\begin_inset ERT',
1533                                       'status %s' % status,
1534                                       '',
1535                                       r'\begin_layout Standard',
1536                                       '',
1537                                       '',
1538                                       r'\backslash',
1539                                       r'begin{lstlisting}%s' % params,
1540                                       r'\end_layout'
1541                                     ] + document.body[k : j - 1] + \
1542                                      ['',
1543                                       r'\begin_layout Standard',
1544                                       '',
1545                                       r'\backslash',
1546                                       'end{lstlisting}',
1547                                       r'\end_layout',
1548                                       '',
1549                                       r'\end_inset']
1550             
1551
1552 def revert_include_listings(document):
1553     r''' Revert lstinputlisting Include option , translate
1554 \begin_inset Include \lstinputlisting{file}[opt]
1555 preview false
1556
1557 \end_inset
1558
1559 TO
1560
1561 \begin_inset ERT
1562 status open
1563
1564 \begin_layout Standard
1565
1566
1567 \backslash
1568 lstinputlisting{file}[opt]
1569 \end_layout
1570
1571 \end_inset
1572     '''
1573
1574     i = 0
1575     while True:
1576         i = find_token(document.body, r'\begin_inset Include \lstinputlisting', i)
1577         if i == -1:
1578             break
1579         else:
1580             if not '\\usepackage{listings}' in document.preamble:
1581                 document.preamble.append('\\usepackage{listings}')
1582         j = find_end_of_inset(document.body, i + 1)
1583         if j == -1:
1584             # this should not happen
1585             break
1586         # find command line lstinputlisting{file}[options]
1587         cmd, file, option = '', '', ''
1588         if re.match(r'\\(lstinputlisting){([.\w]*)}(.*)', document.body[i].split()[2]):
1589             cmd, file, option = re.match(r'\\(lstinputlisting){([.\w]*)}(.*)', document.body[i].split()[2]).groups()            
1590         option = option.replace('\\', '\\backslash\n')
1591         document.body[i : j + 1] = [r'\begin_inset ERT',
1592                                     'status open',
1593                                     '',
1594                                     r'\begin_layout Standard',
1595                                     '',
1596                                     '',
1597                                     r'\backslash',
1598                                     '%s%s{%s}' % (cmd, option, file),
1599                                     r'\end_layout',
1600                                     '',
1601                                     r'\end_inset']
1602
1603
1604 def revert_ext_font_sizes(document):
1605     if document.backend != "latex": return
1606     if not document.textclass.startswith("ext"): return
1607
1608     fontsize = get_value(document.header, '\\paperfontsize', 0)
1609     if fontsize not in ('10', '11', '12'): return
1610     fontsize += 'pt'
1611
1612     i = find_token(document.header, '\\paperfontsize', 0)
1613     document.header[i] = '\\paperfontsize default'
1614
1615     i = find_token(document.header, '\\options', 0)
1616     if i == -1:
1617         i = find_token(document.header, '\\textclass', 0) + 1
1618         document.header[i:i] = ['\\options %s' % fontsize]
1619     else:
1620         document.header[i] += ',%s' % fontsize
1621
1622
1623 def convert_ext_font_sizes(document):
1624     if document.backend != "latex": return
1625     if not document.textclass.startswith("ext"): return
1626
1627     fontsize = get_value(document.header, '\\paperfontsize', 0)
1628     if fontsize != 'default': return
1629
1630     i = find_token(document.header, '\\options', 0)
1631     if i == -1: return
1632
1633     options = get_value(document.header, '\\options', i)
1634
1635     fontsizes = '10pt', '11pt', '12pt'
1636     for fs in fontsizes:
1637         if options.find(fs) != -1:
1638             break
1639     else: # this else will only be attained if the for cycle had no match
1640         return
1641
1642     options = options.split(',')
1643     for j, opt in enumerate(options):
1644         if opt in fontsizes:
1645             fontsize = opt[:-2]
1646             del options[j]
1647             break
1648     else:
1649         return
1650
1651     k = find_token(document.header, '\\paperfontsize', 0)
1652     document.header[k] = '\\paperfontsize %s' % fontsize
1653
1654     if options:
1655         document.header[i] = '\\options %s' % ','.join(options)
1656     else:
1657         del document.header[i]
1658
1659
1660 ##
1661 # Conversion hub
1662 #
1663
1664 supported_versions = ["1.5.0","1.5"]
1665 convert = [[246, []],
1666            [247, [convert_font_settings]],
1667            [248, []],
1668            [249, [convert_utf8]],
1669            [250, []],
1670            [251, []],
1671            [252, [convert_commandparams, convert_bibitem]],
1672            [253, []],
1673            [254, [convert_esint]],
1674            [255, []],
1675            [256, []],
1676            [257, [convert_caption]],
1677            [258, [convert_lyxline]],
1678            [259, [convert_accent, normalize_font_whitespace]],
1679            [260, []],
1680            [261, [convert_changes]],
1681            [262, []],
1682            [263, [normalize_language_name]],
1683            [264, [convert_cv_textclass]],
1684            [265, [convert_tableborder]],
1685            [266, []],
1686            [267, []],
1687            [268, []],
1688            [269, []],
1689            [270, []],
1690            [271, [convert_ext_font_sizes]]
1691           ]
1692
1693 revert =  [
1694            [270, [revert_ext_font_sizes]],
1695            [269, [revert_beamer_alert, revert_beamer_structure]],
1696            [268, [revert_preamble_listings_params, revert_listings_inset, revert_include_listings]],
1697            [267, [revert_CJK]],
1698            [266, [revert_utf8plain]],
1699            [265, [revert_armenian]],
1700            [264, [revert_tableborder]],
1701            [263, [revert_cv_textclass]],
1702            [262, [revert_language_name]],
1703            [261, [revert_ascii]],
1704            [260, []],
1705            [259, [revert_utf8x]],
1706            [258, []],
1707            [257, []],
1708            [256, [revert_caption]],
1709            [255, [revert_encodings]],
1710            [254, [revert_clearpage, revert_cleardoublepage]],
1711            [253, [revert_esint]],
1712            [252, [revert_nomenclature, revert_printnomenclature]],
1713            [251, [revert_commandparams]],
1714            [250, [revert_cs_label]],
1715            [249, []],
1716            [248, [revert_accent, revert_utf8]],
1717            [247, [revert_booktabs]],
1718            [246, [revert_font_settings]],
1719            [245, [revert_framed]]]
1720
1721
1722 if __name__ == "__main__":
1723     pass
1724
1725