]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_4.py
2d6caabaa46597d476ba36996abd3cee826d3b76
[lyx.git] / lib / lyx2lyx / lyx_2_4.py
1 # -*- coding: utf-8 -*-
2 # This file is part of lyx2lyx
3 # Copyright (C) 2018 The LyX team
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 """ Convert files to the file format generated by lyx 2.4"""
20
21 import re, string
22 import unicodedata
23 import sys, os
24
25 from datetime import (datetime, date, time)
26
27 # Uncomment only what you need to import, please.
28
29 from parser_tools import (count_pars_in_inset, find_end_of_inset, find_end_of_layout,
30                           find_token, find_re, get_bool_value, get_option_value, get_value, get_quoted_value)
31 #    del_token, del_value, del_complete_lines,
32 #    find_complete_lines, find_end_of,
33 #    find_re, find_substring, find_token_backwards,
34 #    get_containing_inset, get_containing_layout,
35 #    is_in_inset, set_bool_value
36 #    find_tokens, find_token_exact, check_token
37
38 from lyx2lyx_tools import (put_cmd_in_ert, add_to_preamble)
39 #  revert_font_attrs, insert_to_preamble, latex_length
40 #  get_ert, lyx2latex, lyx2verbatim, length_in_bp, convert_info_insets
41 #  revert_flex_inset, hex2ratio, str2bool
42
43 ####################################################################
44 # Private helper functions
45
46 def convert_fonts(document, font_list, font_type, scale_type):
47     " Handle font definition to LaTeX "
48
49     rpkg = re.compile(r'^\\usepackage(\[scaled=([^\]]*)\])?\{([^\}]+)\}')
50     ft = font_type
51     if scale_type == None:
52         fontscale = None
53     else:
54         fontscale = "\\font_" + scale_type + "_scale"
55     i = 0
56     while i < len(document.preamble):
57         i = find_re(document.preamble, rpkg, i)
58         if i == -1:
59             return
60         mo = rpkg.search(document.preamble[i])
61         option = mo.group(2)
62         pkg = mo.group(3)
63         if not pkg in font_list:
64             i += 1
65             continue
66         del document.preamble[i]
67         if i > 0 and document.preamble[i-1] == "% Added by lyx2lyx":
68             del document.preamble[i-1]
69         if fontscale != None:
70             j = find_token(document.header, fontscale, 0)
71             if j != -1:
72                 val = get_value(document.header, fontscale, j)
73                 vals = val.split()
74                 scale = "100"
75                 if option != None:
76                     scale = "%03d" % int(float(option) * 100)
77                 document.header[j] = fontscale + " " + scale + " " + vals[1]
78         j = find_token(document.header, ft, 0)
79         if j != -1:
80             val = get_value(document.header, ft, j)
81             vals = val.split()
82             document.header[j] = ft + ' "' + pkg + '" ' + vals[1]
83
84 def revert_fonts(document, font_list):
85     " Revert native font definition to LaTeX "
86
87     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
88         font_types = ["\\font_roman", "\\font_sans,sf", "\\font_typewriter,tt"]
89         for ft1 in font_types:
90             fts = ft1.split(",")
91             ft = fts[0]
92             i = find_token(document.header, ft, 0)
93             if i != -1:
94                 val = get_value(document.header, ft, i)
95                 words = val.split()
96                 val = words[0].replace('"', '')
97                 if val in font_list:
98                     xoption = ""
99                     document.header[i] = ft + ' "default" ' + words[1]
100                     if len(fts) > 1:
101                         xval =  get_value(document.header, "\\font_" + fts[1] + "_scale", 0)
102                         # cutoff " 100"
103                         xval = xval[:-4]
104                         if xval != "100":
105                             xoption = "[scaled=" + format(float(xval) / 100, '.2f') + "]"
106                     preamble = "\\usepackage" + xoption + "{%s}" % val
107                     add_to_preamble(document, [preamble])
108
109 ###############################################################################
110 ###
111 ### Conversion and reversion routines
112 ###
113 ###############################################################################
114
115 def convert_dejavu(document):
116     " Handle DejaVu font definition to LaTeX "
117
118     dejavu_fonts_roman = ['DejaVuSerif', 'DejaVuSerifCondensed']
119     dejavu_fonts_sans = ['DejaVuSans','DejaVuSansCondensed']
120     dejavu_fonts_typewriter = ['DejaVuSansMono']
121
122     convert_fonts(document, dejavu_fonts_roman, "\\font_roman", None)
123     convert_fonts(document, dejavu_fonts_sans, "\\font_sans", "sf")
124     convert_fonts(document, dejavu_fonts_typewriter, "\\font_typewriter", "tt")
125
126 def revert_dejavu(document):
127     " Revert native DejaVu font definition to LaTeX "
128
129     dejavu_fonts = ['DejaVuSerif', 'DejaVuSerifCondensed', 'DejaVuSans',
130                     'DejaVuSansMono', 'DejaVuSansCondensed']
131     revert_fonts(document, dejavu_fonts)
132
133 def removeFrontMatterStyles(document):
134     " Remove styles Begin/EndFrontmatter"
135
136     layouts = ['BeginFrontmatter', 'EndFrontmatter']
137     for layout in layouts:
138         i = 0
139         while True:
140             i = find_token(document.body, '\\begin_layout ' + layout, i)
141             if i == -1:
142                 break
143             j = find_end_of_layout(document.body, i)
144             if j == -1:
145                 document.warning("Malformed LyX document: Can't find end of layout at line %d" % i)
146                 i += 1
147                 continue
148             while i > 0 and document.body[i-1].strip() == '':
149                 i -= 1
150             while document.body[j+1].strip() == '':
151                 j = j + 1
152             document.body[i:j+1] = ['']
153
154 def addFrontMatterStyles(document):
155     " Use styles Begin/EndFrontmatter for elsarticle"
156
157     def insertFrontmatter(prefix, line):
158         above = line
159         while above > 0 and document.body[above-1].strip() == '':
160             above -= 1
161         below = line
162         while document.body[below].strip() == '':
163             below += 1
164         document.body[above:below] = ['', '\\begin_layout ' + prefix + 'Frontmatter',
165                                     '\\begin_inset Note Note',
166                                     'status open', '',
167                                     '\\begin_layout Plain Layout',
168                                     'Keep this empty!',
169                                     '\\end_layout', '',
170                                     '\\end_inset', '', '',
171                                     '\\end_layout', '']
172
173     if document.textclass == "elsarticle":
174         layouts = ['Title', 'Title footnote', 'Author', 'Author footnote',
175                    'Corresponding author', 'Address', 'Email', 'Abstract', 'Keywords']
176         first = -1
177         last = -1
178         for layout in layouts:
179             i = 0
180             while True:
181                 i = find_token(document.body, '\\begin_layout ' + layout, i)
182                 if i == -1:
183                     break
184                 k = find_end_of_layout(document.body, i)
185                 if k == -1:
186                     document.warning("Malformed LyX document: Can't find end of layout at line %d" % i)
187                     i += 1;
188                     continue
189                 if first == -1 or i < first:
190                     first = i
191                 if last == -1 or last <= k:
192                     last = k+1
193                 i = k+1
194         if first == -1:
195             return
196         insertFrontmatter('End', last)
197         insertFrontmatter('Begin', first)
198
199 def convert_lst_literalparam(document):
200     " Add param literal to include inset "
201
202     i = 0
203     while True:
204         i = find_token(document.body, '\\begin_inset CommandInset include', i)
205         if i == -1:
206             break
207         j = find_end_of_inset(document.body, i)
208         if j == -1:
209             document.warning("Malformed LyX document: Can't find end of command inset at line %d" % i)
210             i += 1
211             continue
212         while i < j and document.body[i].strip() != '':
213             i += 1
214         document.body.insert(i, "literal \"true\"")
215
216
217 def revert_lst_literalparam(document):
218     " Remove param literal from include inset "
219
220     i = 0
221     while True:
222         i = find_token(document.body, '\\begin_inset CommandInset include', i)
223         if i == -1:
224             break
225         j = find_end_of_inset(document.body, i)
226         if j == -1:
227             document.warning("Malformed LyX document: Can't find end of include inset at line %d" % i)
228             i += 1
229             continue
230         k = find_token(document.body, 'literal', i, j)
231         if k == -1:
232             i += 1
233             continue
234         del document.body[k]
235
236
237 def revert_paratype(document):
238     " Revert ParaType font definitions to LaTeX "
239
240     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
241         preamble = ""
242         i1 = find_token(document.header, "\\font_roman \"PTSerif-TLF\"", 0)
243         i2 = find_token(document.header, "\\font_sans \"default\"", 0)
244         i3 = find_token(document.header, "\\font_typewriter \"default\"", 0)
245         j = find_token(document.header, "\\font_sans \"PTSans-TLF\"", 0)
246         sfval = get_value(document.header, "\\font_sf_scale", 0)
247         # cutoff " 100"
248         sfval = sfval[:-4]
249         sfoption = ""
250         if sfval != "100":
251             sfoption = "scaled=" + format(float(sfval) / 100, '.2f')
252         k = find_token(document.header, "\\font_typewriter \"PTMono-TLF\"", 0)
253         ttval = get_value(document.header, "\\font_tt_scale", 0)
254         # cutoff " 100"
255         ttval = ttval[:-4]
256         ttoption = ""
257         if ttval != "100":
258             ttoption = "scaled=" + format(float(ttval) / 100, '.2f')
259         if i1 != -1 and i2 != -1 and i3!= -1:
260             add_to_preamble(document, ["\\usepackage{paratype}"])
261         else:
262             if i1!= -1:
263                 add_to_preamble(document, ["\\usepackage{PTSerif}"])
264                 document.header[i1] = document.header[i1].replace("PTSerif-TLF", "default")
265             if j!= -1:
266                 if sfoption != "":
267                     add_to_preamble(document, ["\\usepackage[" + sfoption + "]{PTSans}"])
268                 else:
269                     add_to_preamble(document, ["\\usepackage{PTSans}"])
270                 document.header[j] = document.header[j].replace("PTSans-TLF", "default")
271             if k!= -1:
272                 if ttoption != "":
273                     add_to_preamble(document, ["\\usepackage[" + ttoption + "]{PTMono}"])
274                 else:
275                     add_to_preamble(document, ["\\usepackage{PTMono}"])
276                 document.header[k] = document.header[k].replace("PTMono-TLF", "default")
277
278
279 def revert_xcharter(document):
280     " Revert XCharter font definitions to LaTeX "
281
282     i = find_token(document.header, "\\font_roman \"xcharter\"", 0)
283     if i == -1:
284         return
285
286     # replace unsupported font setting
287     document.header[i] = document.header[i].replace("xcharter", "default")
288     # no need for preamble code with system fonts
289     if get_bool_value(document.header, "\\use_non_tex_fonts"):
290         return
291
292     # transfer old style figures setting to package options
293     j = find_token(document.header, "\\font_osf true")
294     if j != -1:
295         options = "[osf]"
296         document.header[j] = "\\font_osf false"
297     else:
298         options = ""
299     if i != -1:
300         add_to_preamble(document, ["\\usepackage%s{XCharter}"%options])
301
302
303 def revert_lscape(document):
304     " Reverts the landscape environment (Landscape module) to TeX-code "
305
306     if not "landscape" in document.get_module_list():
307         return
308
309     i = 0
310     while True:
311         i = find_token(document.body, "\\begin_inset Flex Landscape", i)
312         if i == -1:
313             return
314         j = find_end_of_inset(document.body, i)
315         if j == -1:
316             document.warning("Malformed LyX document: Can't find end of Landscape inset")
317             i += 1
318             continue
319
320         if document.body[i] == "\\begin_inset Flex Landscape (Floating)":
321             document.body[j - 2 : j + 1] = put_cmd_in_ert("\\end{landscape}}")
322             document.body[i : i + 4] = put_cmd_in_ert("\\afterpage{\\begin{landscape}")
323             add_to_preamble(document, ["\\usepackage{afterpage}"])
324         else:
325             document.body[j - 2 : j + 1] = put_cmd_in_ert("\\end{landscape}")
326             document.body[i : i + 4] = put_cmd_in_ert("\\begin{landscape}")
327
328         add_to_preamble(document, ["\\usepackage{pdflscape}"])
329         # no need to reset i
330
331
332 def convert_fontenc(document):
333     " Convert default fontenc setting "
334
335     i = find_token(document.header, "\\fontencoding global", 0)
336     if i == -1:
337         return
338
339     document.header[i] = document.header[i].replace("global", "auto")
340
341
342 def revert_fontenc(document):
343     " Revert default fontenc setting "
344
345     i = find_token(document.header, "\\fontencoding auto", 0)
346     if i == -1:
347         return
348
349     document.header[i] = document.header[i].replace("auto", "global")
350
351
352 def revert_nospellcheck(document):
353     " Remove nospellcheck font info param "
354
355     i = 0
356     while True:
357         i = find_token(document.body, '\\nospellcheck', i)
358         if i == -1:
359             return
360         del document.body[i]
361
362
363 def revert_floatpclass(document):
364     " Remove float placement params 'document' and 'class' "
365
366     i = 0
367     i = find_token(document.header, "\\float_placement class", 0)
368     if i != -1:
369         del document.header[i]
370
371     i = 0
372     while True:
373         i = find_token(document.body, '\\begin_inset Float', i)
374         if i == -1:
375             break
376         j = find_end_of_inset(document.body, i)
377         k = find_token(document.body, 'placement class', i, i + 2)
378         if k == -1:
379             k = find_token(document.body, 'placement document', i, i + 2)
380             if k != -1:
381                 del document.body[k]
382             i = j
383             continue
384         del document.body[k]
385
386
387 def revert_floatalignment(document):
388     " Remove float alignment params "
389
390     i = 0
391     i = find_token(document.header, "\\float_alignment", 0)
392     galignment = ""
393     if i != -1:
394         galignment = get_value(document.header, "\\float_alignment", i)
395         del document.header[i]
396
397     i = 0
398     while True:
399         i = find_token(document.body, '\\begin_inset Float', i)
400         if i == -1:
401             break
402         j = find_end_of_inset(document.body, i)
403         if j == -1:
404             document.warning("Malformed LyX document: Can't find end of inset at line " + str(i))
405             i += 1
406         k = find_token(document.body, 'alignment', i, i + 4)
407         if k == -1:
408             i = j
409             continue
410         alignment = get_value(document.body, "alignment", k)
411         if alignment == "document":
412             alignment = galignment
413         del document.body[k]
414         l = find_token(document.body, "\\begin_layout Plain Layout", i, j)
415         if l == -1:
416             document.warning("Can't find float layout!")
417             i = j
418             continue
419         alcmd = []
420         if alignment == "left":
421             alcmd = put_cmd_in_ert("\\raggedright{}")
422         elif alignment == "center":
423             alcmd = put_cmd_in_ert("\\centering{}")
424         elif alignment == "right":
425             alcmd = put_cmd_in_ert("\\raggedleft{}")
426         if len(alcmd) > 0:
427             document.body[l+1:l+1] = alcmd
428         i = j 
429
430
431 def revert_tuftecite(document):
432     " Revert \cite commands in tufte classes "
433
434     tufte = ["tufte-book", "tufte-handout"]
435     if document.textclass not in tufte:
436         return
437
438     i = 0
439     while (True):
440         i = find_token(document.body, "\\begin_inset CommandInset citation", i)
441         if i == -1:
442             break
443         j = find_end_of_inset(document.body, i)
444         if j == -1:
445             document.warning("Can't find end of citation inset at line %d!!" %(i))
446             i += 1
447             continue
448         k = find_token(document.body, "LatexCommand", i, j)
449         if k == -1:
450             document.warning("Can't find LatexCommand for citation inset at line %d!" %(i))
451             i = j + 1
452             continue
453         cmd = get_value(document.body, "LatexCommand", k)
454         if cmd != "cite":
455             i = j + 1
456             continue
457         pre = get_quoted_value(document.body, "before", i, j)
458         post = get_quoted_value(document.body, "after", i, j)
459         key = get_quoted_value(document.body, "key", i, j)
460         if not key:
461             document.warning("Citation inset at line %d does not have a key!" %(i))
462             key = "???"
463         # Replace command with ERT
464         res = "\\cite"
465         if pre:
466             res += "[" + pre + "]"
467         if post:
468             res += "[" + post + "]"
469         elif pre:
470             res += "[]"
471         res += "{" + key + "}"
472         document.body[i:j+1] = put_cmd_in_ert([res])
473         i = j + 1
474
475
476 def revert_stretchcolumn(document):
477     " We remove the column varwidth flags or everything else will become a mess. "
478     i = 0
479     while True:
480         i = find_token(document.body, "\\begin_inset Tabular", i)
481         if i == -1:
482             return
483         j = find_end_of_inset(document.body, i + 1)
484         if j == -1:
485             document.warning("Malformed LyX document: Could not find end of tabular.")
486             continue
487         for k in range(i, j):
488             if re.search('^<column.*varwidth="[^"]+".*>$', document.body[k]):
489                 document.warning("Converting 'tabularx'/'xltabular' table to normal table.")
490                 document.body[k] = document.body[k].replace(' varwidth="true"', '')
491         i = i + 1
492
493
494 def revert_vcolumns(document):
495     " Revert standard columns with line breaks etc. "
496     i = 0
497     needvarwidth = False
498     needarray = False
499     try:
500         while True:
501             i = find_token(document.body, "\\begin_inset Tabular", i)
502             if i == -1:
503                 return
504             j = find_end_of_inset(document.body, i)
505             if j == -1:
506                 document.warning("Malformed LyX document: Could not find end of tabular.")
507                 i += 1
508                 continue
509
510             # Collect necessary column information
511             m = i + 1
512             nrows = int(document.body[i+1].split('"')[3])
513             ncols = int(document.body[i+1].split('"')[5])
514             col_info = []
515             for k in range(ncols):
516                 m = find_token(document.body, "<column", m)
517                 width = get_option_value(document.body[m], 'width')
518                 varwidth = get_option_value(document.body[m], 'varwidth')
519                 alignment = get_option_value(document.body[m], 'alignment')
520                 special = get_option_value(document.body[m], 'special')
521                 col_info.append([width, varwidth, alignment, special, m])
522
523             # Now parse cells
524             m = i + 1
525             lines = []
526             for row in range(nrows):
527                 for col in range(ncols):
528                     m = find_token(document.body, "<cell", m)
529                     multicolumn = get_option_value(document.body[m], 'multicolumn')
530                     multirow = get_option_value(document.body[m], 'multirow')
531                     width = get_option_value(document.body[m], 'width')
532                     rotate = get_option_value(document.body[m], 'rotate')
533                     # Check for: linebreaks, multipars, non-standard environments
534                     begcell = m
535                     endcell = find_token(document.body, "</cell>", begcell)
536                     vcand = False
537                     if find_token(document.body, "\\begin_inset Newline", begcell, endcell) != -1:
538                         vcand = True
539                     elif count_pars_in_inset(document.body, begcell + 2) > 1:
540                         vcand = True
541                     elif get_value(document.body, "\\begin_layout", begcell) != "Plain Layout":
542                         vcand = True
543                     if vcand and rotate == "" and ((multicolumn == "" and multirow == "") or width == ""):
544                         if col_info[col][0] == "" and col_info[col][1] == "" and col_info[col][3] == "":
545                             needvarwidth = True
546                             alignment = col_info[col][2]
547                             col_line = col_info[col][4]
548                             vval = ""
549                             if alignment == "center":
550                                 vval = ">{\\centering}"
551                             elif  alignment == "left":
552                                 vval = ">{\\raggedright}"
553                             elif alignment == "right":
554                                 vval = ">{\\raggedleft}"
555                             if vval != "":
556                                 needarray = True
557                             vval += "V{\\linewidth}"
558                 
559                             document.body[col_line] = document.body[col_line][:-1] + " special=\"" + vval + "\">"
560                             # ERT newlines and linebreaks (since LyX < 2.4 automatically inserts parboxes
561                             # with newlines, and we do not want that)
562                             while True:
563                                 endcell = find_token(document.body, "</cell>", begcell)
564                                 linebreak = False
565                                 nl = find_token(document.body, "\\begin_inset Newline newline", begcell, endcell)
566                                 if nl == -1:
567                                     nl = find_token(document.body, "\\begin_inset Newline linebreak", begcell, endcell)
568                                     if nl == -1:
569                                          break
570                                     linebreak = True
571                                 nle = find_end_of_inset(document.body, nl)
572                                 del(document.body[nle:nle+1])
573                                 if linebreak:
574                                     document.body[nl:nl+1] = put_cmd_in_ert("\\linebreak{}")
575                                 else:
576                                     document.body[nl:nl+1] = put_cmd_in_ert("\\\\")
577                     m += 1
578
579             i = j + 1
580
581     finally:
582         if needarray == True:
583             add_to_preamble(document, ["\\usepackage{array}"])
584         if needvarwidth == True:
585             add_to_preamble(document, ["\\usepackage{varwidth}"])
586
587
588 def revert_bibencoding(document):
589     " Revert bibliography encoding "
590
591     # Get cite engine
592     engine = "basic"
593     i = find_token(document.header, "\\cite_engine", 0)
594     if i == -1:
595         document.warning("Malformed document! Missing \\cite_engine")
596     else:
597         engine = get_value(document.header, "\\cite_engine", i)
598
599     # Check if biblatex
600     biblatex = False
601     if engine in ["biblatex", "biblatex-natbib"]:
602         biblatex = True
603
604     # Map lyx to latex encoding names 
605     encodings = {
606         "utf8" : "utf8",
607         "utf8x" : "utf8x",
608         "armscii8" : "armscii8",
609         "iso8859-1" : "latin1",
610         "iso8859-2" : "latin2",
611         "iso8859-3" : "latin3",
612         "iso8859-4" : "latin4",
613         "iso8859-5" : "iso88595",
614         "iso8859-6" : "8859-6",
615         "iso8859-7" : "iso-8859-7",
616         "iso8859-8" : "8859-8",
617         "iso8859-9" : "latin5",
618         "iso8859-13" : "latin7",
619         "iso8859-15" : "latin9",
620         "iso8859-16" : "latin10",
621         "applemac" : "applemac",
622         "cp437" : "cp437",
623         "cp437de" : "cp437de",
624         "cp850" : "cp850",
625         "cp852" : "cp852",
626         "cp855" : "cp855",
627         "cp858" : "cp858",
628         "cp862" : "cp862",
629         "cp865" : "cp865",
630         "cp866" : "cp866",
631         "cp1250" : "cp1250",
632         "cp1251" : "cp1251",
633         "cp1252" : "cp1252",
634         "cp1255" : "cp1255",
635         "cp1256" : "cp1256",
636         "cp1257" : "cp1257",
637         "koi8-r" : "koi8-r",
638         "koi8-u" : "koi8-u",
639         "pt154" : "pt154",
640         "utf8-platex" : "utf8",
641         "ascii" : "ascii"
642     }
643
644     i = 0
645     bibresources = []
646     while (True):
647         i = find_token(document.body, "\\begin_inset CommandInset bibtex", i)
648         if i == -1:
649             break
650         j = find_end_of_inset(document.body, i)
651         if j == -1:
652             document.warning("Can't find end of bibtex inset at line %d!!" %(i))
653             i += 1
654             continue
655         encoding = get_quoted_value(document.body, "encoding", i, j)
656         if not encoding:
657             i += 1
658             continue
659         # remove encoding line
660         k = find_token(document.body, "encoding", i, j)
661         if k != -1:
662             del document.body[k]
663         # Re-find inset end line
664         j = find_end_of_inset(document.body, i)
665         if biblatex:
666             biblio_options = ""
667             h = find_token(document.header, "\\biblio_options", 0)
668             if h != -1:
669                 biblio_options = get_value(document.header, "\\biblio_options", h)
670                 if not "bibencoding" in biblio_options:
671                      document.header[h] += ",bibencoding=%s" % encodings[encoding]
672             else:
673                 bs = find_token(document.header, "\\biblatex_bibstyle", 0)
674                 if bs == -1:
675                     # this should not happen
676                     document.warning("Malformed LyX document! No \\biblatex_bibstyle header found!")
677                 else:
678                     document.header[bs-1 : bs-1] = ["\\biblio_options bibencoding=" + encodings[encoding]]
679         else:
680             document.body[j+1:j+1] = put_cmd_in_ert("\\egroup")
681             document.body[i:i] = put_cmd_in_ert("\\bgroup\\inputencoding{" + encodings[encoding] + "}")
682
683         i = j + 1
684
685
686
687 def convert_vcsinfo(document):
688     " Separate vcs Info inset from buffer Info inset. "
689
690     types = {
691         "vcs-revision" : "revision",
692         "vcs-tree-revision" : "tree-revision",
693         "vcs-author" : "author",
694         "vcs-time" : "time",
695         "vcs-date" : "date"
696     }
697     i = 0
698     while True:
699         i = find_token(document.body, "\\begin_inset Info", i)
700         if i == -1:
701             return
702         j = find_end_of_inset(document.body, i + 1)
703         if j == -1:
704             document.warning("Malformed LyX document: Could not find end of Info inset.")
705             i = i + 1
706             continue
707         tp = find_token(document.body, 'type', i, j)
708         tpv = get_quoted_value(document.body, "type", tp)
709         if tpv != "buffer":
710             i = i + 1
711             continue
712         arg = find_token(document.body, 'arg', i, j)
713         argv = get_quoted_value(document.body, "arg", arg)
714         if argv not in list(types.keys()):
715             i = i + 1
716             continue
717         document.body[tp] = "type \"vcs\""
718         document.body[arg] = "arg \"" + types[argv] + "\""
719         i = i + 1
720
721
722 def revert_vcsinfo(document):
723     " Merge vcs Info inset to buffer Info inset. "
724
725     args = ["revision", "tree-revision", "author", "time", "date" ]
726     i = 0
727     while True:
728         i = find_token(document.body, "\\begin_inset Info", i)
729         if i == -1:
730             return
731         j = find_end_of_inset(document.body, i + 1)
732         if j == -1:
733             document.warning("Malformed LyX document: Could not find end of Info inset.")
734             i = i + 1
735             continue
736         tp = find_token(document.body, 'type', i, j)
737         tpv = get_quoted_value(document.body, "type", tp)
738         if tpv != "vcs":
739             i = i + 1
740             continue
741         arg = find_token(document.body, 'arg', i, j)
742         argv = get_quoted_value(document.body, "arg", arg)
743         if argv not in args:
744             document.warning("Malformed Info inset. Invalid vcs arg.")
745             i = i + 1
746             continue
747         document.body[tp] = "type \"buffer\""
748         document.body[arg] = "arg \"vcs-" + argv + "\""
749         i = i + 1
750
751
752 def revert_dateinfo(document):
753     " Revert date info insets to static text. "
754
755 # FIXME This currently only considers the main language and uses the system locale
756 # Ideally, it should honor context languages and switch the locale accordingly.
757
758     # The date formats for each language using strftime syntax:
759     # long, short, loclong, locmedium, locshort
760     dateformats = {
761         "afrikaans" : ["%A, %d %B %Y", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y/%m/%d"],
762         "albanian" : ["%A, %d %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
763         "american" : ["%A, %B %d, %Y", "%m/%d/%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
764         "amharic" : ["%A ፣%d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
765         "ancientgreek" : ["%A, %d %B %Y", "%d %b %Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
766         "arabic_arabi" : ["%A، %d %B، %Y", "%d‏/%m‏/%Y", "%d %B، %Y", "%d/%m/%Y", "%d/%m/%Y"],
767         "arabic_arabtex" : ["%A، %d %B، %Y", "%d‏/%m‏/%Y", "%d %B، %Y", "%d/%m/%Y", "%d/%m/%Y"],
768         "armenian" : ["%Y թ. %B %d, %A", "%d.%m.%y", "%d %B، %Y", "%d %b، %Y", "%d/%m/%Y"],
769         "asturian" : ["%A, %d %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d %b %Y", "%d/%m/%Y"],
770         "australian" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
771         "austrian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
772         "bahasa" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
773         "bahasam" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
774         "basque" : ["%Y(e)ko %B %d, %A", "%y/%m/%d", "%Y %B %d", "%Y %b %d", "%Y/%m/%d"],
775         "belarusian" : ["%A, %d %B %Y г.", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
776         "bosnian" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%Y-%m-%d"],
777         "brazilian" : ["%A, %d de %B de %Y", "%d/%m/%Y", "%d de %B de %Y", "%d de %b de %Y", "%d/%m/%Y"],
778         "breton" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
779         "british" : ["%A, %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
780         "bulgarian" : ["%A, %d %B %Y г.", "%d.%m.%y г.", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
781         "canadian" : ["%A, %B %d, %Y", "%Y-%m-%d", "%B %d, %Y", "%d %b %Y", "%Y-%m-%d"],
782         "canadien" : ["%A %d %B %Y", "%y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
783         "catalan" : ["%A, %d %B de %Y", "%d/%m/%y", "%d / %B / %Y", "%d / %b / %Y", "%d/%m/%Y"],
784         "chinese-simplified" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y-%m-%d", "%y-%m-%d"],
785         "chinese-traditional" : ["%Y年%m月%d日 %A", "%Y/%m/%d", "%Y年%m月%d日", "%Y年%m月%d日", "%y年%m月%d日"],
786         "coptic" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
787         "croatian" : ["%A, %d. %B %Y.", "%d. %m. %Y.", "%d. %B %Y.", "%d. %b. %Y.", "%d.%m.%Y."],
788         "czech" : ["%A %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b. %Y", "%d.%m.%Y"],
789         "danish" : ["%A den %d. %B %Y", "%d/%m/%Y", "%d. %B %Y", "%d. %b %Y", "%d/%m/%Y"],
790         "divehi" : ["%Y %B %d, %A", "%Y-%m-%d", "%Y %B %d", "%Y %b %d", "%d/%m/%Y"],
791         "dutch" : ["%A %d %B %Y", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
792         "english" : ["%A, %B %d, %Y", "%m/%d/%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
793         "esperanto" : ["%A, %d %B %Y", "%d %b %Y", "la %d de %B %Y", "la %d de %b %Y", "%m/%d/%Y"],
794         "estonian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
795         "farsi" : ["%A %d %B %Y", "%Y/%m/%d", "%d %B %Y", "%d %b %Y", "%Y/%m/%d"],
796         "finnish" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
797         "french" : ["%A %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
798         "friulan" : ["%A %d di %B dal %Y", "%d/%m/%y", "%d di %B dal %Y", "%d di %b dal %Y", "%d/%m/%Y"],
799         "galician" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d de %b de %Y", "%d/%m/%Y"],
800         "georgian" : ["%A, %d %B, %Y", "%d.%m.%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
801         "german" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
802         "german-ch" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
803         "german-ch-old" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
804         "greek" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
805         "hebrew" : ["%A, %d ב%B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
806         "hindi" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
807         "icelandic" : ["%A, %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
808         "interlingua" : ["%Y %B %d, %A", "%Y-%m-%d", "le %d de %B %Y", "le %d de %b %Y", "%Y-%m-%d"],
809         "irish" : ["%A %d %B %Y", "%d/%m/%Y", "%d. %B %Y", "%d. %b %Y", "%d/%m/%Y"],
810         "italian" : ["%A %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d/%b/%Y", "%d/%m/%Y"],
811         "japanese" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y/%m/%d", "%y/%m/%d"],
812         "japanese-cjk" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y/%m/%d", "%y/%m/%d"],
813         "kannada" : ["%A, %B %d, %Y", "%d/%m/%y", "%d %B %Y", "%d %B %Y", "%d-%m-%Y"],
814         "kazakh" : ["%Y ж. %d %B, %A", "%d.%m.%y", "%d %B %Y", "%d %B %Y", "%Y-%d-%m"],
815         "khmer" : ["%A %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %B %Y", "%d/%m/%Y"],
816         "korean" : ["%Y년 %m월 %d일 %A", "%y. %m. %d.", "%Y년 %m월 %d일", "%Y. %m. %d.", "%y. %m. %d."],
817         "kurmanji" : ["%A, %d %B %Y", "%d %b %Y", "%d. %B %Y", "%d. %m. %Y", "%Y-%m-%d"],
818         "lao" : ["%A ທີ %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %B %Y", "%d/%m/%Y"],
819         "latin" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
820         "latvian" : ["%A, %Y. gada %d. %B", "%d.%m.%y", "%Y. gada %d. %B", "%Y. gada %d. %b", "%d.%m.%Y"],
821         "lithuanian" : ["%Y m. %B %d d., %A", "%Y-%m-%d", "%Y m. %B %d d.", "%Y m. %B %d d.", "%Y-%m-%d"],
822         "lowersorbian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
823         "macedonian" : ["%A, %d %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
824         "magyar" : ["%Y. %B %d., %A", "%Y. %m. %d.", "%Y. %B %d.", "%Y. %b %d.", "%Y.%m.%d."],
825         "marathi" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
826         "mongolian" : ["%A, %Y оны %m сарын %d", "%Y-%m-%d", "%Y оны %m сарын %d", "%d-%m-%Y", "%d-%m-%Y"],
827         "naustrian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
828         "newzealand" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
829         "ngerman" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
830         "norsk" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
831         "nynorsk" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
832         "occitan" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
833         "piedmontese" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
834         "polish" : ["%A, %d %B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
835         "polutonikogreek" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
836         "portuguese" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d de %b de %Y", "%Y/%m/%d"],
837         "romanian" : ["%A, %d %B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
838         "romansh" : ["%A, ils %d da %B %Y", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
839         "russian" : ["%A, %d %B %Y г.", "%d.%m.%Y", "%d %B %Y г.", "%d %b %Y г.", "%d.%m.%Y"],
840         "samin" : ["%Y %B %d, %A", "%Y-%m-%d", "%B %d. b. %Y", "%b %d. b. %Y", "%d.%m.%Y"],
841         "sanskrit" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
842         "scottish" : ["%A, %dmh %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
843         "serbian" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
844         "serbian-latin" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
845         "slovak" : ["%A, %d. %B %Y", "%d. %m. %Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
846         "slovene" : ["%A, %d. %B %Y", "%d. %m. %y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
847         "spanish" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B %de %Y", "%d %b %Y", "%d/%m/%Y"],
848         "spanish-mexico" : ["%A, %d de %B %de %Y", "%d/%m/%y", "%d de %B de %Y", "%d %b %Y", "%d/%m/%Y"],
849         "swedish" : ["%A %d %B %Y", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
850         "syriac" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
851         "tamil" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
852         "telugu" : ["%d, %B %Y, %A", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
853         "thai" : ["%Aที่ %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
854         "tibetan" : ["%Y %Bའི་ཚེས་%d, %A", "%Y-%m-%d", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
855         "turkish" : ["%d %B %Y %A", "%d.%m.%Y", "%d %B %Y", "%d.%b.%Y", "%d.%m.%Y"],
856         "turkmen" : ["%d %B %Y %A", "%d.%m.%Y", "%Y ý. %B %d", "%d.%m.%Y ý.", "%d.%m.%y ý."],
857         "ukrainian" : ["%A, %d %B %Y р.", "%d.%m.%y", "%d %B %Y", "%d %m %Y", "%d.%m.%Y"],
858         "uppersorbian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
859         "urdu" : ["%A، %d %B، %Y", "%d/%m/%y", "%d %B, %Y", "%d %b %Y", "%d/%m/%Y"],
860         "vietnamese" : ["%A, %d %B, %Y", "%d/%m/%Y", "%d tháng %B %Y", "%d-%m-%Y", "%d/%m/%Y"],
861         "welsh" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
862     }
863
864     types = ["date", "fixdate", "moddate" ]
865     i = 0
866     i = find_token(document.header, "\\language", 0)
867     if i == -1:
868         # this should not happen
869         document.warning("Malformed LyX document! No \\language header found!")
870         return
871     lang = get_value(document.header, "\\language", i)
872
873     i = 0
874     while True:
875         i = find_token(document.body, "\\begin_inset Info", i)
876         if i == -1:
877             return
878         j = find_end_of_inset(document.body, i + 1)
879         if j == -1:
880             document.warning("Malformed LyX document: Could not find end of Info inset.")
881             i = i + 1
882             continue
883         tp = find_token(document.body, 'type', i, j)
884         tpv = get_quoted_value(document.body, "type", tp)
885         if tpv not in types:
886             i = i + 1
887             continue
888         arg = find_token(document.body, 'arg', i, j)
889         argv = get_quoted_value(document.body, "arg", arg)
890         isodate = ""
891         dte = date.today()
892         if tpv == "fixdate":
893             datecomps = argv.split('@')
894             if len(datecomps) > 1:
895                 argv = datecomps[0]
896                 isodate = datecomps[1]
897                 m = re.search('(\d\d\d\d)-(\d\d)-(\d\d)', isodate)
898                 if m:
899                     dte = date(int(m.group(1)), int(m.group(2)), int(m.group(3)))
900 # FIXME if we had the path to the original document (not the one in the tmp dir),
901 #        we could use the mtime.
902 #        elif tpv == "moddate":
903 #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
904         result = ""
905         if argv == "ISO":
906             result = dte.isodate()
907         elif argv == "long":
908             result = dte.strftime(dateformats[lang][0])
909         elif argv == "short":
910             result = dte.strftime(dateformats[lang][1])
911         elif argv == "loclong":
912             result = dte.strftime(dateformats[lang][2])
913         elif argv == "locmedium":
914             result = dte.strftime(dateformats[lang][3])
915         elif argv == "locshort":
916             result = dte.strftime(dateformats[lang][4])
917         else:
918             fmt = argv.replace("MMMM", "%b").replace("MMM", "%b").replace("MM", "%m").replace("M", "%m")
919             fmt = fmt.replace("yyyy", "%Y").replace("yy", "%y")
920             fmt = fmt.replace("dddd", "%A").replace("ddd", "%a").replace("dd", "%d")
921             fmt = re.sub('[^\'%]d', '%d', fmt)
922             fmt = fmt.replace("'", "")
923             result = dte.strftime(fmt)
924         document.body[i : j+1] = result
925         i = i + 1
926
927
928 def revert_timeinfo(document):
929     " Revert time info insets to static text. "
930
931 # FIXME This currently only considers the main language and uses the system locale
932 # Ideally, it should honor context languages and switch the locale accordingly.
933 # Also, the time object is "naive", i.e., it does not know of timezones (%Z will
934 # be empty).
935
936     # The time formats for each language using strftime syntax:
937     # long, short
938     timeformats = {
939         "afrikaans" : ["%H:%M:%S %Z", "%H:%M"],
940         "albanian" : ["%I:%M:%S %p, %Z", "%I:%M %p"],
941         "american" : ["%I:%M:%S %p %Z", "%I:%M %p"],
942         "amharic" : ["%I:%M:%S %p %Z", "%I:%M %p"],
943         "ancientgreek" : ["%H:%M:%S %Z", "%H:%M:%S"],
944         "arabic_arabi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
945         "arabic_arabtex" : ["%I:%M:%S %p %Z", "%I:%M %p"],
946         "armenian" : ["%H:%M:%S %Z", "%H:%M"],
947         "asturian" : ["%H:%M:%S %Z", "%H:%M"],
948         "australian" : ["%I:%M:%S %p %Z", "%I:%M %p"],
949         "austrian" : ["%H:%M:%S %Z", "%H:%M"],
950         "bahasa" : ["%H.%M.%S %Z", "%H.%M"],
951         "bahasam" : ["%I:%M:%S %p %Z", "%I:%M %p"],
952         "basque" : ["%H:%M:%S (%Z)", "%H:%M"],
953         "belarusian" : ["%H:%M:%S, %Z", "%H:%M"],
954         "bosnian" : ["%H:%M:%S %Z", "%H:%M"],
955         "brazilian" : ["%H:%M:%S %Z", "%H:%M"],
956         "breton" : ["%H:%M:%S %Z", "%H:%M"],
957         "british" : ["%H:%M:%S %Z", "%H:%M"],
958         "bulgarian" : ["%H:%M:%S %Z", "%H:%M"],
959         "canadian" : ["%I:%M:%S %p %Z", "%I:%M %p"],
960         "canadien" : ["%H:%M:%S %Z", "%H h %M"],
961         "catalan" : ["%H:%M:%S %Z", "%H:%M"],
962         "chinese-simplified" : ["%Z %p%I:%M:%S", "%p%I:%M"],
963         "chinese-traditional" : ["%p%I:%M:%S [%Z]", "%p%I:%M"],
964         "coptic" : ["%H:%M:%S %Z", "%H:%M:%S"],
965         "croatian" : ["%H:%M:%S (%Z)", "%H:%M"],
966         "czech" : ["%H:%M:%S %Z", "%H:%M"],
967         "danish" : ["%H.%M.%S %Z", "%H.%M"],
968         "divehi" : ["%H:%M:%S %Z", "%H:%M"],
969         "dutch" : ["%H:%M:%S %Z", "%H:%M"],
970         "english" : ["%I:%M:%S %p %Z", "%I:%M %p"],
971         "esperanto" : ["%H:%M:%S %Z", "%H:%M:%S"],
972         "estonian" : ["%H:%M:%S %Z", "%H:%M"],
973         "farsi" : ["%H:%M:%S (%Z)", "%H:%M"],
974         "finnish" : ["%H.%M.%S %Z", "%H.%M"],
975         "french" : ["%H:%M:%S %Z", "%H:%M"],
976         "friulan" : ["%H:%M:%S %Z", "%H:%M"],
977         "galician" : ["%H:%M:%S %Z", "%H:%M"],
978         "georgian" : ["%H:%M:%S %Z", "%H:%M"],
979         "german" : ["%H:%M:%S %Z", "%H:%M"],
980         "german-ch" : ["%H:%M:%S %Z", "%H:%M"],
981         "german-ch-old" : ["%H:%M:%S %Z", "%H:%M"],
982         "greek" : ["%I:%M:%S %p %Z", "%I:%M %p"],
983         "hebrew" : ["%H:%M:%S %Z", "%H:%M"],
984         "hindi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
985         "icelandic" : ["%H:%M:%S %Z", "%H:%M"],
986         "interlingua" : ["%H:%M:%S %Z", "%H:%M"],
987         "irish" : ["%H:%M:%S %Z", "%H:%M"],
988         "italian" : ["%H:%M:%S %Z", "%H:%M"],
989         "japanese" : ["%H時%M分%S秒 %Z", "%H:%M"],
990         "japanese-cjk" : ["%H時%M分%S秒 %Z", "%H:%M"],
991         "kannada" : ["%I:%M:%S %p %Z", "%I:%M %p"],
992         "kazakh" : ["%H:%M:%S %Z", "%H:%M"],
993         "khmer" : ["%I:%M:%S %p %Z", "%I:%M %p"],
994         "korean" : ["%p %I시%M분 %S초 %Z", "%p %I:%M"],
995         "kurmanji" : ["%H:%M:%S %Z", "%H:%M:%S"],
996         "lao" : ["%H ໂມງ%M ນາທີ  %S ວິນາທີ %Z", "%H:%M"],
997         "latin" : ["%H:%M:%S %Z", "%H:%M:%S"],
998         "latvian" : ["%H:%M:%S %Z", "%H:%M"],
999         "lithuanian" : ["%H:%M:%S %Z", "%H:%M"],
1000         "lowersorbian" : ["%H:%M:%S %Z", "%H:%M"],
1001         "macedonian" : ["%H:%M:%S %Z", "%H:%M"],
1002         "magyar" : ["%H:%M:%S %Z", "%H:%M"],
1003         "marathi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1004         "mongolian" : ["%H:%M:%S %Z", "%H:%M"],
1005         "naustrian" : ["%H:%M:%S %Z", "%H:%M"],
1006         "newzealand" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1007         "ngerman" : ["%H:%M:%S %Z", "%H:%M"],
1008         "norsk" : ["%H:%M:%S %Z", "%H:%M"],
1009         "nynorsk" : ["kl. %H:%M:%S %Z", "%H:%M"],
1010         "occitan" : ["%H:%M:%S %Z", "%H:%M"],
1011         "piedmontese" : ["%H:%M:%S %Z", "%H:%M:%S"],
1012         "polish" : ["%H:%M:%S %Z", "%H:%M"],
1013         "polutonikogreek" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1014         "portuguese" : ["%H:%M:%S %Z", "%H:%M"],
1015         "romanian" : ["%H:%M:%S %Z", "%H:%M"],
1016         "romansh" : ["%H:%M:%S %Z", "%H:%M"],
1017         "russian" : ["%H:%M:%S %Z", "%H:%M"],
1018         "samin" : ["%H:%M:%S %Z", "%H:%M"],
1019         "sanskrit" : ["%H:%M:%S %Z", "%H:%M"],
1020         "scottish" : ["%H:%M:%S %Z", "%H:%M"],
1021         "serbian" : ["%H:%M:%S %Z", "%H:%M"],
1022         "serbian-latin" : ["%H:%M:%S %Z", "%H:%M"],
1023         "slovak" : ["%H:%M:%S %Z", "%H:%M"],
1024         "slovene" : ["%H:%M:%S %Z", "%H:%M"],
1025         "spanish" : ["%H:%M:%S (%Z)", "%H:%M"],
1026         "spanish-mexico" : ["%H:%M:%S %Z", "%H:%M"],
1027         "swedish" : ["kl. %H:%M:%S %Z", "%H:%M"],
1028         "syriac" : ["%H:%M:%S %Z", "%H:%M"],
1029         "tamil" : ["%p %I:%M:%S %Z", "%p %I:%M"],
1030         "telugu" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1031         "thai" : ["%H นาฬิกา %M นาที  %S วินาที %Z", "%H:%M"],
1032         "tibetan" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1033         "turkish" : ["%H:%M:%S %Z", "%H:%M"],
1034         "turkmen" : ["%H:%M:%S %Z", "%H:%M"],
1035         "ukrainian" : ["%H:%M:%S %Z", "%H:%M"],
1036         "uppersorbian" : ["%H:%M:%S %Z", "%H:%M hodź."],
1037         "urdu" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1038         "vietnamese" : ["%H:%M:%S %Z", "%H:%M"],
1039         "welsh" : ["%H:%M:%S %Z", "%H:%M"]
1040     }
1041
1042     types = ["time", "fixtime", "modtime" ]
1043     i = 0
1044     i = find_token(document.header, "\\language", 0)
1045     if i == -1:
1046         # this should not happen
1047         document.warning("Malformed LyX document! No \\language header found!")
1048         return
1049     lang = get_value(document.header, "\\language", i)
1050
1051     i = 0
1052     while True:
1053         i = find_token(document.body, "\\begin_inset Info", i)
1054         if i == -1:
1055             return
1056         j = find_end_of_inset(document.body, i + 1)
1057         if j == -1:
1058             document.warning("Malformed LyX document: Could not find end of Info inset.")
1059             i = i + 1
1060             continue
1061         tp = find_token(document.body, 'type', i, j)
1062         tpv = get_quoted_value(document.body, "type", tp)
1063         if tpv not in types:
1064             i = i + 1
1065             continue
1066         arg = find_token(document.body, 'arg', i, j)
1067         argv = get_quoted_value(document.body, "arg", arg)
1068         isotime = ""
1069         dtme = datetime.now()
1070         tme = dtme.time()
1071         if tpv == "fixtime":
1072             timecomps = argv.split('@')
1073             if len(timecomps) > 1:
1074                 argv = timecomps[0]
1075                 isotime = timecomps[1]
1076                 m = re.search('(\d\d):(\d\d):(\d\d)', isotime)
1077                 if m:
1078                     tme = time(int(m.group(1)), int(m.group(2)), int(m.group(3)))
1079                 else:
1080                     m = re.search('(\d\d):(\d\d)', isotime)
1081                     if m:
1082                         tme = time(int(m.group(1)), int(m.group(2)))
1083 # FIXME if we had the path to the original document (not the one in the tmp dir),
1084 #        we could use the mtime.
1085 #        elif tpv == "moddate":
1086 #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
1087         result = ""
1088         if argv == "ISO":
1089             result = tme.isoformat()
1090         elif argv == "long":
1091             result = tme.strftime(timeformats[lang][0])
1092         elif argv == "short":
1093             result = tme.strftime(timeformats[lang][1])
1094         else:
1095             fmt = argv.replace("HH", "%H").replace("H", "%H").replace("hh", "%I").replace("h", "%I")
1096             fmt = fmt.replace("mm", "%M").replace("m", "%M").replace("ss", "%S").replace("s", "%S")
1097             fmt = fmt.replace("zzz", "%f").replace("z", "%f").replace("t", "%Z")
1098             fmt = fmt.replace("AP", "%p").replace("ap", "%p").replace("A", "%p").replace("a", "%p")
1099             fmt = fmt.replace("'", "")
1100             result = dte.strftime(fmt)
1101         document.body[i : j+1] = result
1102         i = i + 1
1103
1104
1105 def revert_namenoextinfo(document):
1106     " Merge buffer Info inset type name-noext to name. "
1107
1108     i = 0
1109     while True:
1110         i = find_token(document.body, "\\begin_inset Info", i)
1111         if i == -1:
1112             return
1113         j = find_end_of_inset(document.body, i + 1)
1114         if j == -1:
1115             document.warning("Malformed LyX document: Could not find end of Info inset.")
1116             i = i + 1
1117             continue
1118         tp = find_token(document.body, 'type', i, j)
1119         tpv = get_quoted_value(document.body, "type", tp)
1120         if tpv != "buffer":
1121             i = i + 1
1122             continue
1123         arg = find_token(document.body, 'arg', i, j)
1124         argv = get_quoted_value(document.body, "arg", arg)
1125         if argv != "name-noext":
1126             i = i + 1
1127             continue
1128         document.body[arg] = "arg \"name\""
1129         i = i + 1
1130
1131
1132 def revert_l7ninfo(document):
1133     " Revert l7n Info inset to text. "
1134
1135     i = 0
1136     while True:
1137         i = find_token(document.body, "\\begin_inset Info", i)
1138         if i == -1:
1139             return
1140         j = find_end_of_inset(document.body, i + 1)
1141         if j == -1:
1142             document.warning("Malformed LyX document: Could not find end of Info inset.")
1143             i = i + 1
1144             continue
1145         tp = find_token(document.body, 'type', i, j)
1146         tpv = get_quoted_value(document.body, "type", tp)
1147         if tpv != "l7n":
1148             i = i + 1
1149             continue
1150         arg = find_token(document.body, 'arg', i, j)
1151         argv = get_quoted_value(document.body, "arg", arg)
1152         # remove trailing colons, menu accelerator (|...) and qt accelerator (&), while keeping literal " & " 
1153         argv = argv.rstrip(':').split('|')[0].replace(" & ", "</amp;>").replace("&", "").replace("</amp;>", " & ")
1154         document.body[i : j+1] = argv
1155         i = i + 1
1156
1157
1158 ##
1159 # Conversion hub
1160 #
1161
1162 supported_versions = ["2.4.0", "2.4"]
1163 convert = [
1164            [545, [convert_lst_literalparam]],
1165            [546, []],
1166            [547, []],
1167            [548, []],
1168            [549, []],
1169            [550, [convert_fontenc]],
1170            [551, []],
1171            [552, []],
1172            [553, []],
1173            [554, []],
1174            [555, []],
1175            [556, []],
1176            [557, [convert_vcsinfo]],
1177            [558, [removeFrontMatterStyles]],
1178            [559, []],
1179            [560, []],
1180            [561, [convert_dejavu]],
1181            [562, []]
1182           ]
1183
1184 revert =  [
1185            [561, [revert_l7ninfo]],
1186            [560, [revert_dejavu]],
1187            [559, [revert_timeinfo, revert_namenoextinfo]],
1188            [558, [revert_dateinfo]],
1189            [557, [addFrontMatterStyles]],
1190            [556, [revert_vcsinfo]],
1191            [555, [revert_bibencoding]],
1192            [554, [revert_vcolumns]],
1193            [553, [revert_stretchcolumn]],
1194            [552, [revert_tuftecite]],
1195            [551, [revert_floatpclass, revert_floatalignment]],
1196            [550, [revert_nospellcheck]],
1197            [549, [revert_fontenc]],
1198            [548, []],# dummy format change
1199            [547, [revert_lscape]],
1200            [546, [revert_xcharter]],
1201            [545, [revert_paratype]],
1202            [544, [revert_lst_literalparam]]
1203           ]
1204
1205
1206 if __name__ == "__main__":
1207     pass