]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_4.py
6e69e3605e5a32d344340f51f4fd733b8c4089fb
[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_containing_layout,
31                           get_option_value, get_value, get_quoted_value)
32 #    del_token, del_value, del_complete_lines,
33 #    find_complete_lines, find_end_of,
34 #    find_re, find_substring, find_token_backwards,
35 #    get_containing_inset,
36 #    is_in_inset, set_bool_value
37 #    find_tokens, find_token_exact, check_token
38
39 from lyx2lyx_tools import (put_cmd_in_ert, add_to_preamble, revert_language, revert_flex_inset)
40 #  revert_font_attrs, insert_to_preamble, latex_length
41 #  get_ert, lyx2latex, lyx2verbatim, length_in_bp, convert_info_insets
42 #  revert_flex_inset, hex2ratio, str2bool
43
44 ####################################################################
45 # Private helper functions
46
47 def add_preamble_fonts(document, fontmap):
48     " Add collected font-packages with their option to user-preamble"
49
50     for pkg in fontmap:
51         if len(fontmap[pkg]) > 0:
52             xoption = "[" + ",".join(fontmap[pkg]) + "]"
53         else:
54             xoption = ""
55         preamble = "\\usepackage" + xoption + "{%s}" % pkg
56         add_to_preamble(document, [preamble])
57
58
59 def createkey(pkg, options):
60     options.sort()
61     return pkg + ':' + "-".join(options)
62
63 class fontinfo:
64     def __init__(self):
65         self.fontname = None    # key into font2pkgmap
66         self.fonttype = None    # roman,sans,typewriter,math
67         self.scaletype = None   # None,sf,tt
68         self.scaleopt = None    # None, 'scaled', 'scale'
69         self.scaleval = 1
70         self.package = None
71         self.options = []
72         self.pkgkey = None      # key into pkg2fontmap
73
74     def addkey(self):
75         self.pkgkey = createkey(self.package, self.options)
76
77 class fontmapping:
78     def __init__(self):
79         self.font2pkgmap = dict()
80         self.pkg2fontmap = dict()
81         self.pkginmap = dict()  # defines, if a map for package exists
82
83     def expandFontMapping(self, font_list, font_type, scale_type, pkg, scaleopt = None):
84         " Expand fontinfo mapping"
85         #
86         # fontlist:    list of fontnames, each element
87         #              may contain a ','-separated list of needed options
88         #              like e.g. 'IBMPlexSansCondensed,condensed'
89         # font_type:   one of 'roman', 'sans', 'typewriter', 'math'
90         # scale_type:  one of None, 'sf', 'tt'
91         # pkg:         package defining the font. Defaults to fontname if None
92         # scaleopt:    one of None, 'scale', 'scaled', or some other string
93         #              to be used in scale option (e.g. scaled=0.7)
94         for fl in font_list:
95             fe = fontinfo()
96             fe.fonttype = font_type
97             fe.scaletype = scale_type
98             flt = fl.split(",")
99             font_name = flt[0]
100             fe.fontname = font_name
101             fe.options = flt[1:]
102             fe.scaleopt = scaleopt
103             if pkg == None:
104                 fe.package = font_name
105             else:
106                 fe.package = pkg
107             fe.addkey()
108             self.font2pkgmap[font_name] = fe
109             if fe.pkgkey in self.pkg2fontmap:
110                 # Repeated the same entry? Check content
111                 if self.pkg2fontmap[fe.pkgkey] != font_name:
112                     document.error("Something is wrong in pkgname+options <-> fontname mapping")
113             self.pkg2fontmap[fe.pkgkey] = font_name
114             self.pkginmap[fe.package] = 1
115
116     def getfontname(self, pkg, options):
117         options.sort()
118         pkgkey = createkey(pkg, options)
119         if not pkgkey in self.pkg2fontmap:
120             return None
121         fontname = self.pkg2fontmap[pkgkey]
122         if not fontname in self.font2pkgmap:
123             document.error("Something is wrong in pkgname+options <-> fontname mapping")
124             return None
125         if pkgkey == self.font2pkgmap[fontname].pkgkey:
126             return fontname
127         return None
128
129 def createFontMapping(fontlist):
130     # Create info for known fonts for the use in
131     #   convert_latexFonts() and
132     #   revert_latexFonts()
133     #
134     # * Would be more handy to parse latexFonts file,
135     #   but the path to this file is unknown
136     # * For now, add DejaVu and IBMPlex only.
137     # * Expand, if desired
138     fm = fontmapping()
139     for font in fontlist:
140         if font == 'DejaVu':
141             fm.expandFontMapping(['DejaVuSerif', 'DejaVuSerifCondensed'], "roman", None, None)
142             fm.expandFontMapping(['DejaVuSans','DejaVuSansCondensed'], "sans", "sf", None, "scaled")
143             fm.expandFontMapping(['DejaVuSansMono'], "typewriter", "tt", None, "scaled")
144         elif font == 'IBM':
145             fm.expandFontMapping(['IBMPlexSerif', 'IBMPlexSerifThin,thin',
146                                   'IBMPlexSerifExtraLight,extralight', 'IBMPlexSerifLight,light',
147                                   'IBMPlexSerifSemibold,semibold'],
148                                  "roman", None, "plex-serif")
149             fm.expandFontMapping(['IBMPlexSans','IBMPlexSansCondensed,condensed',
150                                   'IBMPlexSansThin,thin', 'IBMPlexSansExtraLight,extralight',
151                                   'IBMPlexSansLight,light', 'IBMPlexSansSemibold,semibold'],
152                                  "sans", "sf", "plex-sans", "scale")
153             fm.expandFontMapping(['IBMPlexMono', 'IBMPlexMonoThin,thin',
154                                   'IBMPlexMonoExtraLight,extralight', 'IBMPlexMonoLight,light',
155                                   'IBMPlexMonoSemibold,semibold'],
156                                  "typewriter", "tt", "plex-mono", "scale")
157         elif font == 'Adobe':
158             fm.expandFontMapping(['ADOBESourceSerifPro'], "roman", None, "sourceserifpro")
159             fm.expandFontMapping(['ADOBESourceSansPro'], "sans", "sf", "sourcesanspro", "scaled")
160             fm.expandFontMapping(['ADOBESourceCodePro'], "typewriter", "tt", "sourcecodepro", "scaled")
161         elif font == 'Noto':
162             fm.expandFontMapping(['NotoSerifRegular,regular', 'NotoSerifMedium,medium',
163                                   'NotoSerifThin,thin', 'NotoSerifLight,light',
164                                   'NotoSerifExtralight,extralight'],
165                                   "roman", None, "noto-serif")
166             fm.expandFontMapping(['NotoSansRegular,regular', 'NotoSansMedium,medium',
167                                   'NotoSansThin,thin', 'NotoSansLight,light',
168                                   'NotoSansExtralight,extralight'],
169                                   "sans", "sf", "noto-sans", "scaled")
170             fm.expandFontMapping(['NotoMonoRegular'], "typewriter", "tt", "noto-mono", "scaled")
171     return fm
172
173 def convert_fonts(document, fm):
174     " Handle font definition to LaTeX "
175
176     rpkg = re.compile(r'^\\usepackage(\[([^\]]*)\])?\{([^\}]+)\}')
177     rscaleopt = re.compile(r'^scaled?=(.*)')
178
179     i = 0
180     while i < len(document.preamble):
181         i = find_re(document.preamble, rpkg, i)
182         if i == -1:
183             return
184         mo = rpkg.search(document.preamble[i])
185         if mo == None or mo.group(2) == None:
186             options = []
187         else:
188             options = mo.group(2).replace(' ', '').split(",")
189         pkg = mo.group(3)
190         o = 0
191         oscale = 1
192         while o < len(options):
193             mo = rscaleopt.search(options[o])
194             if mo == None:
195                 o += 1
196                 continue
197             oscale = mo.group(1)
198             del options[o]
199             break
200
201         if not pkg in fm.pkginmap:
202             i += 1
203             continue
204         # determine fontname
205         fn = fm.getfontname(pkg, options)
206         if fn == None:
207             i += 1
208             continue
209         del document.preamble[i]
210         fontinfo = fm.font2pkgmap[fn]
211         if fontinfo.scaletype == None:
212             fontscale = None
213         else:
214             fontscale = "\\font_" + fontinfo.scaletype + "_scale"
215             fontinfo.scaleval = oscale
216
217         if i > 0 and document.preamble[i-1] == "% Added by lyx2lyx":
218             del document.preamble[i-1]
219         if fontscale != None:
220             j = find_token(document.header, fontscale, 0)
221             if j != -1:
222                 val = get_value(document.header, fontscale, j)
223                 vals = val.split()
224                 scale = "100"
225                 if oscale != None:
226                     scale = "%03d" % int(float(oscale) * 100)
227                 document.header[j] = fontscale + " " + scale + " " + vals[1]
228         ft = "\\font_" + fontinfo.fonttype
229         j = find_token(document.header, ft, 0)
230         if j != -1:
231             val = get_value(document.header, ft, j)
232             words = val.split() # ! splits also values like '"DejaVu Sans"'
233             words[0] = '"' + fn + '"'
234             document.header[j] = ft + ' ' + ' '.join(words)
235
236 def revert_fonts(document, fm, fontmap):
237     " Revert native font definition to LaTeX "
238     # fonlist := list of fonts created from the same package
239     # Empty package means that the font-name is the same as the package-name
240     # fontmap (key = package, val += found options) will be filled
241     # and used later in add_preamble_fonts() to be added to user-preamble
242
243     rfontscale = re.compile(r'^\s*(\\font_(roman|sans|typewriter|math))\s+')
244     rscales = re.compile(r'^\s*(\d+)\s+(\d+)')
245     i = 0
246     while i < len(document.header):
247         i = find_re(document.header, rfontscale, i)
248         if (i == -1):
249             break
250         mo = rfontscale.search(document.header[i])
251         if mo == None:
252             i += 1
253             continue
254         ft = mo.group(1)    # 'roman', 'sans', 'typewriter', 'math'
255         val = get_value(document.header, ft, i)
256         words = val.split(' ')     # ! splits also values like '"DejaVu Sans"'
257         font = words[0].strip('"') # TeX font name has no whitespace
258         if not font in fm.font2pkgmap:
259             i += 1
260             continue
261         fontinfo = fm.font2pkgmap[font]
262         val = fontinfo.package
263         if not val in fontmap:
264             fontmap[val] = []
265         words[0] = '"default"'
266         document.header[i] = ft + ' ' + ' '.join(words)
267         if fontinfo.scaleopt != None:
268             xval =  get_value(document.header, "\\font_" + fontinfo.scaletype + "_scale", 0)
269             mo = rscales.search(xval)
270             if mo != None:
271                 xval1 = mo.group(1)
272                 xval2 = mo.group(2)
273                 if xval1 != "100":
274                     # set correct scale option
275                     fontmap[val].extend([fontinfo.scaleopt + "=" + format(float(xval1) / 100, '.2f')])
276         if len(fontinfo.options) > 0:
277             fontmap[val].extend(fontinfo.options)
278         i += 1
279
280 ###############################################################################
281 ###
282 ### Conversion and reversion routines
283 ###
284 ###############################################################################
285
286 def convert_notoFonts(document):
287     " Handle Noto fonts definition to LaTeX "
288
289     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
290         fm = createFontMapping(['Noto'])
291         convert_fonts(document, fm)
292
293 def revert_notoFonts(document):
294     " Revert native Noto font definition to LaTeX "
295
296     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
297         fontmap = dict()
298         fm = createFontMapping(['Noto'])
299         revert_fonts(document, fm, fontmap)
300         add_preamble_fonts(document, fontmap)
301
302 def convert_latexFonts(document):
303     " Handle DejaVu and IBMPlex fonts definition to LaTeX "
304
305     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
306         fm = createFontMapping(['DejaVu', 'IBM'])
307         convert_fonts(document, fm)
308
309 def revert_latexFonts(document):
310     " Revert native DejaVu font definition to LaTeX "
311
312     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
313         fontmap = dict()
314         fm = createFontMapping(['DejaVu', 'IBM'])
315         revert_fonts(document, fm, fontmap)
316         add_preamble_fonts(document, fontmap)
317
318 def convert_AdobeFonts(document):
319     " Handle DejaVu and IBMPlex fonts definition to LaTeX "
320
321     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
322         fm = createFontMapping(['Adobe'])
323         convert_fonts(document, fm)
324
325 def revert_AdobeFonts(document):
326     " Revert native DejaVu font definition to LaTeX "
327
328     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
329         fontmap = dict()
330         fm = createFontMapping(['Adobe'])
331         revert_fonts(document, fm, fontmap)
332         add_preamble_fonts(document, fontmap)
333
334 def removeFrontMatterStyles(document):
335     " Remove styles Begin/EndFrontmatter"
336
337     layouts = ['BeginFrontmatter', 'EndFrontmatter']
338     for layout in layouts:
339         i = 0
340         while True:
341             i = find_token(document.body, '\\begin_layout ' + layout, i)
342             if i == -1:
343                 break
344             j = find_end_of_layout(document.body, i)
345             if j == -1:
346                 document.warning("Malformed LyX document: Can't find end of layout at line %d" % i)
347                 i += 1
348                 continue
349             while i > 0 and document.body[i-1].strip() == '':
350                 i -= 1
351             while document.body[j+1].strip() == '':
352                 j = j + 1
353             document.body[i:j+1] = ['']
354
355 def addFrontMatterStyles(document):
356     " Use styles Begin/EndFrontmatter for elsarticle"
357
358     def insertFrontmatter(prefix, line):
359         above = line
360         while above > 0 and document.body[above-1].strip() == '':
361             above -= 1
362         below = line
363         while document.body[below].strip() == '':
364             below += 1
365         document.body[above:below] = ['', '\\begin_layout ' + prefix + 'Frontmatter',
366                                     '\\begin_inset Note Note',
367                                     'status open', '',
368                                     '\\begin_layout Plain Layout',
369                                     'Keep this empty!',
370                                     '\\end_layout', '',
371                                     '\\end_inset', '', '',
372                                     '\\end_layout', '']
373
374     if document.textclass == "elsarticle":
375         layouts = ['Title', 'Title footnote', 'Author', 'Author footnote',
376                    'Corresponding author', 'Address', 'Email', 'Abstract', 'Keywords']
377         first = -1
378         last = -1
379         for layout in layouts:
380             i = 0
381             while True:
382                 i = find_token(document.body, '\\begin_layout ' + layout, i)
383                 if i == -1:
384                     break
385                 k = find_end_of_layout(document.body, i)
386                 if k == -1:
387                     document.warning("Malformed LyX document: Can't find end of layout at line %d" % i)
388                     i += 1;
389                     continue
390                 if first == -1 or i < first:
391                     first = i
392                 if last == -1 or last <= k:
393                     last = k+1
394                 i = k+1
395         if first == -1:
396             return
397         insertFrontmatter('End', last)
398         insertFrontmatter('Begin', first)
399
400 def convert_lst_literalparam(document):
401     " Add param literal to include inset "
402
403     i = 0
404     while True:
405         i = find_token(document.body, '\\begin_inset CommandInset include', i)
406         if i == -1:
407             break
408         j = find_end_of_inset(document.body, i)
409         if j == -1:
410             document.warning("Malformed LyX document: Can't find end of command inset at line %d" % i)
411             i += 1
412             continue
413         while i < j and document.body[i].strip() != '':
414             i += 1
415         document.body.insert(i, "literal \"true\"")
416
417
418 def revert_lst_literalparam(document):
419     " Remove param literal from include inset "
420
421     i = 0
422     while True:
423         i = find_token(document.body, '\\begin_inset CommandInset include', i)
424         if i == -1:
425             break
426         j = find_end_of_inset(document.body, i)
427         if j == -1:
428             document.warning("Malformed LyX document: Can't find end of include inset at line %d" % i)
429             i += 1
430             continue
431         k = find_token(document.body, 'literal', i, j)
432         if k == -1:
433             i += 1
434             continue
435         del document.body[k]
436
437
438 def revert_paratype(document):
439     " Revert ParaType font definitions to LaTeX "
440
441     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
442         preamble = ""
443         i1 = find_token(document.header, "\\font_roman \"PTSerif-TLF\"", 0)
444         i2 = find_token(document.header, "\\font_sans \"default\"", 0)
445         i3 = find_token(document.header, "\\font_typewriter \"default\"", 0)
446         j = find_token(document.header, "\\font_sans \"PTSans-TLF\"", 0)
447         sfval = get_value(document.header, "\\font_sf_scale", 0)
448         # cutoff " 100"
449         sfval = sfval[:-4]
450         sfoption = ""
451         if sfval != "100":
452             sfoption = "scaled=" + format(float(sfval) / 100, '.2f')
453         k = find_token(document.header, "\\font_typewriter \"PTMono-TLF\"", 0)
454         ttval = get_value(document.header, "\\font_tt_scale", 0)
455         # cutoff " 100"
456         ttval = ttval[:-4]
457         ttoption = ""
458         if ttval != "100":
459             ttoption = "scaled=" + format(float(ttval) / 100, '.2f')
460         if i1 != -1 and i2 != -1 and i3!= -1:
461             add_to_preamble(document, ["\\usepackage{paratype}"])
462         else:
463             if i1!= -1:
464                 add_to_preamble(document, ["\\usepackage{PTSerif}"])
465                 document.header[i1] = document.header[i1].replace("PTSerif-TLF", "default")
466             if j!= -1:
467                 if sfoption != "":
468                     add_to_preamble(document, ["\\usepackage[" + sfoption + "]{PTSans}"])
469                 else:
470                     add_to_preamble(document, ["\\usepackage{PTSans}"])
471                 document.header[j] = document.header[j].replace("PTSans-TLF", "default")
472             if k!= -1:
473                 if ttoption != "":
474                     add_to_preamble(document, ["\\usepackage[" + ttoption + "]{PTMono}"])
475                 else:
476                     add_to_preamble(document, ["\\usepackage{PTMono}"])
477                 document.header[k] = document.header[k].replace("PTMono-TLF", "default")
478
479
480 def revert_xcharter(document):
481     " Revert XCharter font definitions to LaTeX "
482
483     i = find_token(document.header, "\\font_roman \"xcharter\"", 0)
484     if i == -1:
485         return
486
487     # replace unsupported font setting
488     document.header[i] = document.header[i].replace("xcharter", "default")
489     # no need for preamble code with system fonts
490     if get_bool_value(document.header, "\\use_non_tex_fonts"):
491         return
492
493     # transfer old style figures setting to package options
494     j = find_token(document.header, "\\font_osf true")
495     if j != -1:
496         options = "[osf]"
497         document.header[j] = "\\font_osf false"
498     else:
499         options = ""
500     if i != -1:
501         add_to_preamble(document, ["\\usepackage%s{XCharter}"%options])
502
503
504 def revert_lscape(document):
505     " Reverts the landscape environment (Landscape module) to TeX-code "
506
507     if not "landscape" in document.get_module_list():
508         return
509
510     i = 0
511     while True:
512         i = find_token(document.body, "\\begin_inset Flex Landscape", i)
513         if i == -1:
514             return
515         j = find_end_of_inset(document.body, i)
516         if j == -1:
517             document.warning("Malformed LyX document: Can't find end of Landscape inset")
518             i += 1
519             continue
520
521         if document.body[i] == "\\begin_inset Flex Landscape (Floating)":
522             document.body[j - 2 : j + 1] = put_cmd_in_ert("\\end{landscape}}")
523             document.body[i : i + 4] = put_cmd_in_ert("\\afterpage{\\begin{landscape}")
524             add_to_preamble(document, ["\\usepackage{afterpage}"])
525         else:
526             document.body[j - 2 : j + 1] = put_cmd_in_ert("\\end{landscape}")
527             document.body[i : i + 4] = put_cmd_in_ert("\\begin{landscape}")
528
529         add_to_preamble(document, ["\\usepackage{pdflscape}"])
530         # no need to reset i
531
532
533 def convert_fontenc(document):
534     " Convert default fontenc setting "
535
536     i = find_token(document.header, "\\fontencoding global", 0)
537     if i == -1:
538         return
539
540     document.header[i] = document.header[i].replace("global", "auto")
541
542
543 def revert_fontenc(document):
544     " Revert default fontenc setting "
545
546     i = find_token(document.header, "\\fontencoding auto", 0)
547     if i == -1:
548         return
549
550     document.header[i] = document.header[i].replace("auto", "global")
551
552
553 def revert_nospellcheck(document):
554     " Remove nospellcheck font info param "
555
556     i = 0
557     while True:
558         i = find_token(document.body, '\\nospellcheck', i)
559         if i == -1:
560             return
561         del document.body[i]
562
563
564 def revert_floatpclass(document):
565     " Remove float placement params 'document' and 'class' "
566
567     i = 0
568     i = find_token(document.header, "\\float_placement class", 0)
569     if i != -1:
570         del document.header[i]
571
572     i = 0
573     while True:
574         i = find_token(document.body, '\\begin_inset Float', i)
575         if i == -1:
576             break
577         j = find_end_of_inset(document.body, i)
578         k = find_token(document.body, 'placement class', i, i + 2)
579         if k == -1:
580             k = find_token(document.body, 'placement document', i, i + 2)
581             if k != -1:
582                 del document.body[k]
583             i += 1
584             continue
585         del document.body[k]
586
587
588 def revert_floatalignment(document):
589     " Remove float alignment params "
590
591     i = 0
592     i = find_token(document.header, "\\float_alignment", 0)
593     galignment = ""
594     if i != -1:
595         galignment = get_value(document.header, "\\float_alignment", i)
596         del document.header[i]
597
598     i = 0
599     while True:
600         i = find_token(document.body, '\\begin_inset Float', i)
601         if i == -1:
602             break
603         j = find_end_of_inset(document.body, i)
604         if j == -1:
605             document.warning("Malformed LyX document: Can't find end of inset at line " + str(i))
606             i += 1
607         k = find_token(document.body, 'alignment', i, i + 4)
608         if k == -1:
609             i = j
610             continue
611         alignment = get_value(document.body, "alignment", k)
612         if alignment == "document":
613             alignment = galignment
614         del document.body[k]
615         l = find_token(document.body, "\\begin_layout Plain Layout", i, j)
616         if l == -1:
617             document.warning("Can't find float layout!")
618             i += 1
619             continue
620         alcmd = []
621         if alignment == "left":
622             alcmd = put_cmd_in_ert("\\raggedright{}")
623         elif alignment == "center":
624             alcmd = put_cmd_in_ert("\\centering{}")
625         elif alignment == "right":
626             alcmd = put_cmd_in_ert("\\raggedleft{}")
627         if len(alcmd) > 0:
628             document.body[l+1:l+1] = alcmd
629         i += 1
630
631
632 def revert_tuftecite(document):
633     " Revert \cite commands in tufte classes "
634
635     tufte = ["tufte-book", "tufte-handout"]
636     if document.textclass not in tufte:
637         return
638
639     i = 0
640     while (True):
641         i = find_token(document.body, "\\begin_inset CommandInset citation", i)
642         if i == -1:
643             break
644         j = find_end_of_inset(document.body, i)
645         if j == -1:
646             document.warning("Can't find end of citation inset at line %d!!" %(i))
647             i += 1
648             continue
649         k = find_token(document.body, "LatexCommand", i, j)
650         if k == -1:
651             document.warning("Can't find LatexCommand for citation inset at line %d!" %(i))
652             i = j + 1
653             continue
654         cmd = get_value(document.body, "LatexCommand", k)
655         if cmd != "cite":
656             i = j + 1
657             continue
658         pre = get_quoted_value(document.body, "before", i, j)
659         post = get_quoted_value(document.body, "after", i, j)
660         key = get_quoted_value(document.body, "key", i, j)
661         if not key:
662             document.warning("Citation inset at line %d does not have a key!" %(i))
663             key = "???"
664         # Replace command with ERT
665         res = "\\cite"
666         if pre:
667             res += "[" + pre + "]"
668         if post:
669             res += "[" + post + "]"
670         elif pre:
671             res += "[]"
672         res += "{" + key + "}"
673         document.body[i:j+1] = put_cmd_in_ert([res])
674         i = j + 1
675
676
677 def revert_stretchcolumn(document):
678     " We remove the column varwidth flags or everything else will become a mess. "
679     i = 0
680     while True:
681         i = find_token(document.body, "\\begin_inset Tabular", i)
682         if i == -1:
683             return
684         j = find_end_of_inset(document.body, i + 1)
685         if j == -1:
686             document.warning("Malformed LyX document: Could not find end of tabular.")
687             continue
688         for k in range(i, j):
689             if re.search('^<column.*varwidth="[^"]+".*>$', document.body[k]):
690                 document.warning("Converting 'tabularx'/'xltabular' table to normal table.")
691                 document.body[k] = document.body[k].replace(' varwidth="true"', '')
692         i = i + 1
693
694
695 def revert_vcolumns(document):
696     " Revert standard columns with line breaks etc. "
697     i = 0
698     needvarwidth = False
699     needarray = False
700     try:
701         while True:
702             i = find_token(document.body, "\\begin_inset Tabular", i)
703             if i == -1:
704                 return
705             j = find_end_of_inset(document.body, i)
706             if j == -1:
707                 document.warning("Malformed LyX document: Could not find end of tabular.")
708                 i += 1
709                 continue
710
711             # Collect necessary column information
712             m = i + 1
713             nrows = int(document.body[i+1].split('"')[3])
714             ncols = int(document.body[i+1].split('"')[5])
715             col_info = []
716             for k in range(ncols):
717                 m = find_token(document.body, "<column", m)
718                 width = get_option_value(document.body[m], 'width')
719                 varwidth = get_option_value(document.body[m], 'varwidth')
720                 alignment = get_option_value(document.body[m], 'alignment')
721                 special = get_option_value(document.body[m], 'special')
722                 col_info.append([width, varwidth, alignment, special, m])
723
724             # Now parse cells
725             m = i + 1
726             lines = []
727             for row in range(nrows):
728                 for col in range(ncols):
729                     m = find_token(document.body, "<cell", m)
730                     multicolumn = get_option_value(document.body[m], 'multicolumn')
731                     multirow = get_option_value(document.body[m], 'multirow')
732                     width = get_option_value(document.body[m], 'width')
733                     rotate = get_option_value(document.body[m], 'rotate')
734                     # Check for: linebreaks, multipars, non-standard environments
735                     begcell = m
736                     endcell = find_token(document.body, "</cell>", begcell)
737                     vcand = False
738                     if find_token(document.body, "\\begin_inset Newline", begcell, endcell) != -1:
739                         vcand = True
740                     elif count_pars_in_inset(document.body, begcell + 2) > 1:
741                         vcand = True
742                     elif get_value(document.body, "\\begin_layout", begcell) != "Plain Layout":
743                         vcand = True
744                     if vcand and rotate == "" and ((multicolumn == "" and multirow == "") or width == ""):
745                         if col_info[col][0] == "" and col_info[col][1] == "" and col_info[col][3] == "":
746                             needvarwidth = True
747                             alignment = col_info[col][2]
748                             col_line = col_info[col][4]
749                             vval = ""
750                             if alignment == "center":
751                                 vval = ">{\\centering}"
752                             elif  alignment == "left":
753                                 vval = ">{\\raggedright}"
754                             elif alignment == "right":
755                                 vval = ">{\\raggedleft}"
756                             if vval != "":
757                                 needarray = True
758                             vval += "V{\\linewidth}"
759
760                             document.body[col_line] = document.body[col_line][:-1] + " special=\"" + vval + "\">"
761                             # ERT newlines and linebreaks (since LyX < 2.4 automatically inserts parboxes
762                             # with newlines, and we do not want that)
763                             while True:
764                                 endcell = find_token(document.body, "</cell>", begcell)
765                                 linebreak = False
766                                 nl = find_token(document.body, "\\begin_inset Newline newline", begcell, endcell)
767                                 if nl == -1:
768                                     nl = find_token(document.body, "\\begin_inset Newline linebreak", begcell, endcell)
769                                     if nl == -1:
770                                          break
771                                     linebreak = True
772                                 nle = find_end_of_inset(document.body, nl)
773                                 del(document.body[nle:nle+1])
774                                 if linebreak:
775                                     document.body[nl:nl+1] = put_cmd_in_ert("\\linebreak{}")
776                                 else:
777                                     document.body[nl:nl+1] = put_cmd_in_ert("\\\\")
778                     m += 1
779
780             i = j + 1
781
782     finally:
783         if needarray == True:
784             add_to_preamble(document, ["\\usepackage{array}"])
785         if needvarwidth == True:
786             add_to_preamble(document, ["\\usepackage{varwidth}"])
787
788
789 def revert_bibencoding(document):
790     " Revert bibliography encoding "
791
792     # Get cite engine
793     engine = "basic"
794     i = find_token(document.header, "\\cite_engine", 0)
795     if i == -1:
796         document.warning("Malformed document! Missing \\cite_engine")
797     else:
798         engine = get_value(document.header, "\\cite_engine", i)
799
800     # Check if biblatex
801     biblatex = False
802     if engine in ["biblatex", "biblatex-natbib"]:
803         biblatex = True
804
805     # Map lyx to latex encoding names
806     encodings = {
807         "utf8" : "utf8",
808         "utf8x" : "utf8x",
809         "armscii8" : "armscii8",
810         "iso8859-1" : "latin1",
811         "iso8859-2" : "latin2",
812         "iso8859-3" : "latin3",
813         "iso8859-4" : "latin4",
814         "iso8859-5" : "iso88595",
815         "iso8859-6" : "8859-6",
816         "iso8859-7" : "iso-8859-7",
817         "iso8859-8" : "8859-8",
818         "iso8859-9" : "latin5",
819         "iso8859-13" : "latin7",
820         "iso8859-15" : "latin9",
821         "iso8859-16" : "latin10",
822         "applemac" : "applemac",
823         "cp437" : "cp437",
824         "cp437de" : "cp437de",
825         "cp850" : "cp850",
826         "cp852" : "cp852",
827         "cp855" : "cp855",
828         "cp858" : "cp858",
829         "cp862" : "cp862",
830         "cp865" : "cp865",
831         "cp866" : "cp866",
832         "cp1250" : "cp1250",
833         "cp1251" : "cp1251",
834         "cp1252" : "cp1252",
835         "cp1255" : "cp1255",
836         "cp1256" : "cp1256",
837         "cp1257" : "cp1257",
838         "koi8-r" : "koi8-r",
839         "koi8-u" : "koi8-u",
840         "pt154" : "pt154",
841         "utf8-platex" : "utf8",
842         "ascii" : "ascii"
843     }
844
845     i = 0
846     bibresources = []
847     while (True):
848         i = find_token(document.body, "\\begin_inset CommandInset bibtex", i)
849         if i == -1:
850             break
851         j = find_end_of_inset(document.body, i)
852         if j == -1:
853             document.warning("Can't find end of bibtex inset at line %d!!" %(i))
854             i += 1
855             continue
856         encoding = get_quoted_value(document.body, "encoding", i, j)
857         if not encoding:
858             i += 1
859             continue
860         # remove encoding line
861         k = find_token(document.body, "encoding", i, j)
862         if k != -1:
863             del document.body[k]
864         if encoding == "default":
865             i += 1
866             continue
867         # Re-find inset end line
868         j = find_end_of_inset(document.body, i)
869         if biblatex:
870             biblio_options = ""
871             h = find_token(document.header, "\\biblio_options", 0)
872             if h != -1:
873                 biblio_options = get_value(document.header, "\\biblio_options", h)
874                 if not "bibencoding" in biblio_options:
875                      document.header[h] += ",bibencoding=%s" % encodings[encoding]
876             else:
877                 bs = find_token(document.header, "\\biblatex_bibstyle", 0)
878                 if bs == -1:
879                     # this should not happen
880                     document.warning("Malformed LyX document! No \\biblatex_bibstyle header found!")
881                 else:
882                     document.header[bs-1 : bs-1] = ["\\biblio_options bibencoding=" + encodings[encoding]]
883         else:
884             document.body[j+1:j+1] = put_cmd_in_ert("\\egroup")
885             document.body[i:i] = put_cmd_in_ert("\\bgroup\\inputencoding{" + encodings[encoding] + "}")
886
887         i = j + 1
888
889
890
891 def convert_vcsinfo(document):
892     " Separate vcs Info inset from buffer Info inset. "
893
894     types = {
895         "vcs-revision" : "revision",
896         "vcs-tree-revision" : "tree-revision",
897         "vcs-author" : "author",
898         "vcs-time" : "time",
899         "vcs-date" : "date"
900     }
901     i = 0
902     while True:
903         i = find_token(document.body, "\\begin_inset Info", i)
904         if i == -1:
905             return
906         j = find_end_of_inset(document.body, i + 1)
907         if j == -1:
908             document.warning("Malformed LyX document: Could not find end of Info inset.")
909             i = i + 1
910             continue
911         tp = find_token(document.body, 'type', i, j)
912         tpv = get_quoted_value(document.body, "type", tp)
913         if tpv != "buffer":
914             i = i + 1
915             continue
916         arg = find_token(document.body, 'arg', i, j)
917         argv = get_quoted_value(document.body, "arg", arg)
918         if argv not in list(types.keys()):
919             i = i + 1
920             continue
921         document.body[tp] = "type \"vcs\""
922         document.body[arg] = "arg \"" + types[argv] + "\""
923         i = i + 1
924
925
926 def revert_vcsinfo(document):
927     " Merge vcs Info inset to buffer Info inset. "
928
929     args = ["revision", "tree-revision", "author", "time", "date" ]
930     i = 0
931     while True:
932         i = find_token(document.body, "\\begin_inset Info", i)
933         if i == -1:
934             return
935         j = find_end_of_inset(document.body, i + 1)
936         if j == -1:
937             document.warning("Malformed LyX document: Could not find end of Info inset.")
938             i = i + 1
939             continue
940         tp = find_token(document.body, 'type', i, j)
941         tpv = get_quoted_value(document.body, "type", tp)
942         if tpv != "vcs":
943             i = i + 1
944             continue
945         arg = find_token(document.body, 'arg', i, j)
946         argv = get_quoted_value(document.body, "arg", arg)
947         if argv not in args:
948             document.warning("Malformed Info inset. Invalid vcs arg.")
949             i = i + 1
950             continue
951         document.body[tp] = "type \"buffer\""
952         document.body[arg] = "arg \"vcs-" + argv + "\""
953         i = i + 1
954
955
956 def revert_dateinfo(document):
957     " Revert date info insets to static text. "
958
959 # FIXME This currently only considers the main language and uses the system locale
960 # Ideally, it should honor context languages and switch the locale accordingly.
961
962     # The date formats for each language using strftime syntax:
963     # long, short, loclong, locmedium, locshort
964     dateformats = {
965         "afrikaans" : ["%A, %d %B %Y", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y/%m/%d"],
966         "albanian" : ["%A, %d %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
967         "american" : ["%A, %B %d, %Y", "%m/%d/%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
968         "amharic" : ["%A ፣%d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
969         "ancientgreek" : ["%A, %d %B %Y", "%d %b %Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
970         "arabic_arabi" : ["%A، %d %B، %Y", "%d‏/%m‏/%Y", "%d %B، %Y", "%d/%m/%Y", "%d/%m/%Y"],
971         "arabic_arabtex" : ["%A، %d %B، %Y", "%d‏/%m‏/%Y", "%d %B، %Y", "%d/%m/%Y", "%d/%m/%Y"],
972         "armenian" : ["%Y թ. %B %d, %A", "%d.%m.%y", "%d %B، %Y", "%d %b، %Y", "%d/%m/%Y"],
973         "asturian" : ["%A, %d %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d %b %Y", "%d/%m/%Y"],
974         "australian" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
975         "austrian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
976         "bahasa" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
977         "bahasam" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
978         "basque" : ["%Y(e)ko %B %d, %A", "%y/%m/%d", "%Y %B %d", "%Y %b %d", "%Y/%m/%d"],
979         "belarusian" : ["%A, %d %B %Y г.", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
980         "bosnian" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%Y-%m-%d"],
981         "brazilian" : ["%A, %d de %B de %Y", "%d/%m/%Y", "%d de %B de %Y", "%d de %b de %Y", "%d/%m/%Y"],
982         "breton" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
983         "british" : ["%A, %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
984         "bulgarian" : ["%A, %d %B %Y г.", "%d.%m.%y г.", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
985         "canadian" : ["%A, %B %d, %Y", "%Y-%m-%d", "%B %d, %Y", "%d %b %Y", "%Y-%m-%d"],
986         "canadien" : ["%A %d %B %Y", "%y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
987         "catalan" : ["%A, %d %B de %Y", "%d/%m/%y", "%d / %B / %Y", "%d / %b / %Y", "%d/%m/%Y"],
988         "chinese-simplified" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y-%m-%d", "%y-%m-%d"],
989         "chinese-traditional" : ["%Y年%m月%d日 %A", "%Y/%m/%d", "%Y年%m月%d日", "%Y年%m月%d日", "%y年%m月%d日"],
990         "coptic" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
991         "croatian" : ["%A, %d. %B %Y.", "%d. %m. %Y.", "%d. %B %Y.", "%d. %b. %Y.", "%d.%m.%Y."],
992         "czech" : ["%A %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b. %Y", "%d.%m.%Y"],
993         "danish" : ["%A den %d. %B %Y", "%d/%m/%Y", "%d. %B %Y", "%d. %b %Y", "%d/%m/%Y"],
994         "divehi" : ["%Y %B %d, %A", "%Y-%m-%d", "%Y %B %d", "%Y %b %d", "%d/%m/%Y"],
995         "dutch" : ["%A %d %B %Y", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
996         "english" : ["%A, %B %d, %Y", "%m/%d/%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
997         "esperanto" : ["%A, %d %B %Y", "%d %b %Y", "la %d de %B %Y", "la %d de %b %Y", "%m/%d/%Y"],
998         "estonian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
999         "farsi" : ["%A %d %B %Y", "%Y/%m/%d", "%d %B %Y", "%d %b %Y", "%Y/%m/%d"],
1000         "finnish" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1001         "french" : ["%A %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1002         "friulan" : ["%A %d di %B dal %Y", "%d/%m/%y", "%d di %B dal %Y", "%d di %b dal %Y", "%d/%m/%Y"],
1003         "galician" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d de %b de %Y", "%d/%m/%Y"],
1004         "georgian" : ["%A, %d %B, %Y", "%d.%m.%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1005         "german" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1006         "german-ch" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1007         "german-ch-old" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1008         "greek" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1009         "hebrew" : ["%A, %d ב%B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1010         "hindi" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1011         "icelandic" : ["%A, %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1012         "interlingua" : ["%Y %B %d, %A", "%Y-%m-%d", "le %d de %B %Y", "le %d de %b %Y", "%Y-%m-%d"],
1013         "irish" : ["%A %d %B %Y", "%d/%m/%Y", "%d. %B %Y", "%d. %b %Y", "%d/%m/%Y"],
1014         "italian" : ["%A %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d/%b/%Y", "%d/%m/%Y"],
1015         "japanese" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y/%m/%d", "%y/%m/%d"],
1016         "japanese-cjk" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y/%m/%d", "%y/%m/%d"],
1017         "kannada" : ["%A, %B %d, %Y", "%d/%m/%y", "%d %B %Y", "%d %B %Y", "%d-%m-%Y"],
1018         "kazakh" : ["%Y ж. %d %B, %A", "%d.%m.%y", "%d %B %Y", "%d %B %Y", "%Y-%d-%m"],
1019         "khmer" : ["%A %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %B %Y", "%d/%m/%Y"],
1020         "korean" : ["%Y년 %m월 %d일 %A", "%y. %m. %d.", "%Y년 %m월 %d일", "%Y. %m. %d.", "%y. %m. %d."],
1021         "kurmanji" : ["%A, %d %B %Y", "%d %b %Y", "%d. %B %Y", "%d. %m. %Y", "%Y-%m-%d"],
1022         "lao" : ["%A ທີ %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %B %Y", "%d/%m/%Y"],
1023         "latin" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1024         "latvian" : ["%A, %Y. gada %d. %B", "%d.%m.%y", "%Y. gada %d. %B", "%Y. gada %d. %b", "%d.%m.%Y"],
1025         "lithuanian" : ["%Y m. %B %d d., %A", "%Y-%m-%d", "%Y m. %B %d d.", "%Y m. %B %d d.", "%Y-%m-%d"],
1026         "lowersorbian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1027         "macedonian" : ["%A, %d %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1028         "magyar" : ["%Y. %B %d., %A", "%Y. %m. %d.", "%Y. %B %d.", "%Y. %b %d.", "%Y.%m.%d."],
1029         "malayalam" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1030         "marathi" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1031         "mongolian" : ["%A, %Y оны %m сарын %d", "%Y-%m-%d", "%Y оны %m сарын %d", "%d-%m-%Y", "%d-%m-%Y"],
1032         "naustrian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1033         "newzealand" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1034         "ngerman" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1035         "norsk" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1036         "nynorsk" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1037         "occitan" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1038         "piedmontese" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1039         "polish" : ["%A, %d %B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1040         "polutonikogreek" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1041         "portuguese" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d de %b de %Y", "%Y/%m/%d"],
1042         "romanian" : ["%A, %d %B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1043         "romansh" : ["%A, ils %d da %B %Y", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1044         "russian" : ["%A, %d %B %Y г.", "%d.%m.%Y", "%d %B %Y г.", "%d %b %Y г.", "%d.%m.%Y"],
1045         "samin" : ["%Y %B %d, %A", "%Y-%m-%d", "%B %d. b. %Y", "%b %d. b. %Y", "%d.%m.%Y"],
1046         "sanskrit" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1047         "scottish" : ["%A, %dmh %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1048         "serbian" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1049         "serbian-latin" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1050         "slovak" : ["%A, %d. %B %Y", "%d. %m. %Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1051         "slovene" : ["%A, %d. %B %Y", "%d. %m. %y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1052         "spanish" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B %de %Y", "%d %b %Y", "%d/%m/%Y"],
1053         "spanish-mexico" : ["%A, %d de %B %de %Y", "%d/%m/%y", "%d de %B de %Y", "%d %b %Y", "%d/%m/%Y"],
1054         "swedish" : ["%A %d %B %Y", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1055         "syriac" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1056         "tamil" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1057         "telugu" : ["%d, %B %Y, %A", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1058         "thai" : ["%Aที่ %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1059         "tibetan" : ["%Y %Bའི་ཚེས་%d, %A", "%Y-%m-%d", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1060         "turkish" : ["%d %B %Y %A", "%d.%m.%Y", "%d %B %Y", "%d.%b.%Y", "%d.%m.%Y"],
1061         "turkmen" : ["%d %B %Y %A", "%d.%m.%Y", "%Y ý. %B %d", "%d.%m.%Y ý.", "%d.%m.%y ý."],
1062         "ukrainian" : ["%A, %d %B %Y р.", "%d.%m.%y", "%d %B %Y", "%d %m %Y", "%d.%m.%Y"],
1063         "uppersorbian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1064         "urdu" : ["%A، %d %B، %Y", "%d/%m/%y", "%d %B, %Y", "%d %b %Y", "%d/%m/%Y"],
1065         "vietnamese" : ["%A, %d %B, %Y", "%d/%m/%Y", "%d tháng %B %Y", "%d-%m-%Y", "%d/%m/%Y"],
1066         "welsh" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1067     }
1068
1069     types = ["date", "fixdate", "moddate" ]
1070     i = 0
1071     i = find_token(document.header, "\\language", 0)
1072     if i == -1:
1073         # this should not happen
1074         document.warning("Malformed LyX document! No \\language header found!")
1075         return
1076     lang = get_value(document.header, "\\language", i)
1077
1078     i = 0
1079     while True:
1080         i = find_token(document.body, "\\begin_inset Info", i)
1081         if i == -1:
1082             return
1083         j = find_end_of_inset(document.body, i + 1)
1084         if j == -1:
1085             document.warning("Malformed LyX document: Could not find end of Info inset.")
1086             i = i + 1
1087             continue
1088         tp = find_token(document.body, 'type', i, j)
1089         tpv = get_quoted_value(document.body, "type", tp)
1090         if tpv not in types:
1091             i = i + 1
1092             continue
1093         arg = find_token(document.body, 'arg', i, j)
1094         argv = get_quoted_value(document.body, "arg", arg)
1095         isodate = ""
1096         dte = date.today()
1097         if tpv == "fixdate":
1098             datecomps = argv.split('@')
1099             if len(datecomps) > 1:
1100                 argv = datecomps[0]
1101                 isodate = datecomps[1]
1102                 m = re.search('(\d\d\d\d)-(\d\d)-(\d\d)', isodate)
1103                 if m:
1104                     dte = date(int(m.group(1)), int(m.group(2)), int(m.group(3)))
1105 # FIXME if we had the path to the original document (not the one in the tmp dir),
1106 #        we could use the mtime.
1107 #        elif tpv == "moddate":
1108 #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
1109         result = ""
1110         if argv == "ISO":
1111             result = dte.isodate()
1112         elif argv == "long":
1113             result = dte.strftime(dateformats[lang][0])
1114         elif argv == "short":
1115             result = dte.strftime(dateformats[lang][1])
1116         elif argv == "loclong":
1117             result = dte.strftime(dateformats[lang][2])
1118         elif argv == "locmedium":
1119             result = dte.strftime(dateformats[lang][3])
1120         elif argv == "locshort":
1121             result = dte.strftime(dateformats[lang][4])
1122         else:
1123             fmt = argv.replace("MMMM", "%b").replace("MMM", "%b").replace("MM", "%m").replace("M", "%m")
1124             fmt = fmt.replace("yyyy", "%Y").replace("yy", "%y")
1125             fmt = fmt.replace("dddd", "%A").replace("ddd", "%a").replace("dd", "%d")
1126             fmt = re.sub('[^\'%]d', '%d', fmt)
1127             fmt = fmt.replace("'", "")
1128             result = dte.strftime(fmt)
1129         if sys.version_info < (3,0):
1130             # In Python 2, datetime module works with binary strings,
1131             # our dateformat strings are utf8-encoded:
1132             result = result.decode('utf-8')
1133         document.body[i : j+1] = result
1134         i = i + 1
1135
1136
1137 def revert_timeinfo(document):
1138     " Revert time info insets to static text. "
1139
1140 # FIXME This currently only considers the main language and uses the system locale
1141 # Ideally, it should honor context languages and switch the locale accordingly.
1142 # Also, the time object is "naive", i.e., it does not know of timezones (%Z will
1143 # be empty).
1144
1145     # The time formats for each language using strftime syntax:
1146     # long, short
1147     timeformats = {
1148         "afrikaans" : ["%H:%M:%S %Z", "%H:%M"],
1149         "albanian" : ["%I:%M:%S %p, %Z", "%I:%M %p"],
1150         "american" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1151         "amharic" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1152         "ancientgreek" : ["%H:%M:%S %Z", "%H:%M:%S"],
1153         "arabic_arabi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1154         "arabic_arabtex" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1155         "armenian" : ["%H:%M:%S %Z", "%H:%M"],
1156         "asturian" : ["%H:%M:%S %Z", "%H:%M"],
1157         "australian" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1158         "austrian" : ["%H:%M:%S %Z", "%H:%M"],
1159         "bahasa" : ["%H.%M.%S %Z", "%H.%M"],
1160         "bahasam" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1161         "basque" : ["%H:%M:%S (%Z)", "%H:%M"],
1162         "belarusian" : ["%H:%M:%S, %Z", "%H:%M"],
1163         "bosnian" : ["%H:%M:%S %Z", "%H:%M"],
1164         "brazilian" : ["%H:%M:%S %Z", "%H:%M"],
1165         "breton" : ["%H:%M:%S %Z", "%H:%M"],
1166         "british" : ["%H:%M:%S %Z", "%H:%M"],
1167         "bulgarian" : ["%H:%M:%S %Z", "%H:%M"],
1168         "canadian" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1169         "canadien" : ["%H:%M:%S %Z", "%H h %M"],
1170         "catalan" : ["%H:%M:%S %Z", "%H:%M"],
1171         "chinese-simplified" : ["%Z %p%I:%M:%S", "%p%I:%M"],
1172         "chinese-traditional" : ["%p%I:%M:%S [%Z]", "%p%I:%M"],
1173         "coptic" : ["%H:%M:%S %Z", "%H:%M:%S"],
1174         "croatian" : ["%H:%M:%S (%Z)", "%H:%M"],
1175         "czech" : ["%H:%M:%S %Z", "%H:%M"],
1176         "danish" : ["%H.%M.%S %Z", "%H.%M"],
1177         "divehi" : ["%H:%M:%S %Z", "%H:%M"],
1178         "dutch" : ["%H:%M:%S %Z", "%H:%M"],
1179         "english" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1180         "esperanto" : ["%H:%M:%S %Z", "%H:%M:%S"],
1181         "estonian" : ["%H:%M:%S %Z", "%H:%M"],
1182         "farsi" : ["%H:%M:%S (%Z)", "%H:%M"],
1183         "finnish" : ["%H.%M.%S %Z", "%H.%M"],
1184         "french" : ["%H:%M:%S %Z", "%H:%M"],
1185         "friulan" : ["%H:%M:%S %Z", "%H:%M"],
1186         "galician" : ["%H:%M:%S %Z", "%H:%M"],
1187         "georgian" : ["%H:%M:%S %Z", "%H:%M"],
1188         "german" : ["%H:%M:%S %Z", "%H:%M"],
1189         "german-ch" : ["%H:%M:%S %Z", "%H:%M"],
1190         "german-ch-old" : ["%H:%M:%S %Z", "%H:%M"],
1191         "greek" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1192         "hebrew" : ["%H:%M:%S %Z", "%H:%M"],
1193         "hindi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1194         "icelandic" : ["%H:%M:%S %Z", "%H:%M"],
1195         "interlingua" : ["%H:%M:%S %Z", "%H:%M"],
1196         "irish" : ["%H:%M:%S %Z", "%H:%M"],
1197         "italian" : ["%H:%M:%S %Z", "%H:%M"],
1198         "japanese" : ["%H時%M分%S秒 %Z", "%H:%M"],
1199         "japanese-cjk" : ["%H時%M分%S秒 %Z", "%H:%M"],
1200         "kannada" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1201         "kazakh" : ["%H:%M:%S %Z", "%H:%M"],
1202         "khmer" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1203         "korean" : ["%p %I시%M분 %S초 %Z", "%p %I:%M"],
1204         "kurmanji" : ["%H:%M:%S %Z", "%H:%M:%S"],
1205         "lao" : ["%H ໂມງ%M ນາທີ  %S ວິນາທີ %Z", "%H:%M"],
1206         "latin" : ["%H:%M:%S %Z", "%H:%M:%S"],
1207         "latvian" : ["%H:%M:%S %Z", "%H:%M"],
1208         "lithuanian" : ["%H:%M:%S %Z", "%H:%M"],
1209         "lowersorbian" : ["%H:%M:%S %Z", "%H:%M"],
1210         "macedonian" : ["%H:%M:%S %Z", "%H:%M"],
1211         "magyar" : ["%H:%M:%S %Z", "%H:%M"],
1212         "malayalam" : ["%p %I:%M:%S %Z", "%p %I:%M"],
1213         "marathi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1214         "mongolian" : ["%H:%M:%S %Z", "%H:%M"],
1215         "naustrian" : ["%H:%M:%S %Z", "%H:%M"],
1216         "newzealand" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1217         "ngerman" : ["%H:%M:%S %Z", "%H:%M"],
1218         "norsk" : ["%H:%M:%S %Z", "%H:%M"],
1219         "nynorsk" : ["kl. %H:%M:%S %Z", "%H:%M"],
1220         "occitan" : ["%H:%M:%S %Z", "%H:%M"],
1221         "piedmontese" : ["%H:%M:%S %Z", "%H:%M:%S"],
1222         "polish" : ["%H:%M:%S %Z", "%H:%M"],
1223         "polutonikogreek" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1224         "portuguese" : ["%H:%M:%S %Z", "%H:%M"],
1225         "romanian" : ["%H:%M:%S %Z", "%H:%M"],
1226         "romansh" : ["%H:%M:%S %Z", "%H:%M"],
1227         "russian" : ["%H:%M:%S %Z", "%H:%M"],
1228         "samin" : ["%H:%M:%S %Z", "%H:%M"],
1229         "sanskrit" : ["%H:%M:%S %Z", "%H:%M"],
1230         "scottish" : ["%H:%M:%S %Z", "%H:%M"],
1231         "serbian" : ["%H:%M:%S %Z", "%H:%M"],
1232         "serbian-latin" : ["%H:%M:%S %Z", "%H:%M"],
1233         "slovak" : ["%H:%M:%S %Z", "%H:%M"],
1234         "slovene" : ["%H:%M:%S %Z", "%H:%M"],
1235         "spanish" : ["%H:%M:%S (%Z)", "%H:%M"],
1236         "spanish-mexico" : ["%H:%M:%S %Z", "%H:%M"],
1237         "swedish" : ["kl. %H:%M:%S %Z", "%H:%M"],
1238         "syriac" : ["%H:%M:%S %Z", "%H:%M"],
1239         "tamil" : ["%p %I:%M:%S %Z", "%p %I:%M"],
1240         "telugu" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1241         "thai" : ["%H นาฬิกา %M นาที  %S วินาที %Z", "%H:%M"],
1242         "tibetan" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1243         "turkish" : ["%H:%M:%S %Z", "%H:%M"],
1244         "turkmen" : ["%H:%M:%S %Z", "%H:%M"],
1245         "ukrainian" : ["%H:%M:%S %Z", "%H:%M"],
1246         "uppersorbian" : ["%H:%M:%S %Z", "%H:%M hodź."],
1247         "urdu" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1248         "vietnamese" : ["%H:%M:%S %Z", "%H:%M"],
1249         "welsh" : ["%H:%M:%S %Z", "%H:%M"]
1250     }
1251
1252     types = ["time", "fixtime", "modtime" ]
1253     i = 0
1254     i = find_token(document.header, "\\language", 0)
1255     if i == -1:
1256         # this should not happen
1257         document.warning("Malformed LyX document! No \\language header found!")
1258         return
1259     lang = get_value(document.header, "\\language", i)
1260
1261     i = 0
1262     while True:
1263         i = find_token(document.body, "\\begin_inset Info", i)
1264         if i == -1:
1265             return
1266         j = find_end_of_inset(document.body, i + 1)
1267         if j == -1:
1268             document.warning("Malformed LyX document: Could not find end of Info inset.")
1269             i = i + 1
1270             continue
1271         tp = find_token(document.body, 'type', i, j)
1272         tpv = get_quoted_value(document.body, "type", tp)
1273         if tpv not in types:
1274             i = i + 1
1275             continue
1276         arg = find_token(document.body, 'arg', i, j)
1277         argv = get_quoted_value(document.body, "arg", arg)
1278         isotime = ""
1279         dtme = datetime.now()
1280         tme = dtme.time()
1281         if tpv == "fixtime":
1282             timecomps = argv.split('@')
1283             if len(timecomps) > 1:
1284                 argv = timecomps[0]
1285                 isotime = timecomps[1]
1286                 m = re.search('(\d\d):(\d\d):(\d\d)', isotime)
1287                 if m:
1288                     tme = time(int(m.group(1)), int(m.group(2)), int(m.group(3)))
1289                 else:
1290                     m = re.search('(\d\d):(\d\d)', isotime)
1291                     if m:
1292                         tme = time(int(m.group(1)), int(m.group(2)))
1293 # FIXME if we had the path to the original document (not the one in the tmp dir),
1294 #        we could use the mtime.
1295 #        elif tpv == "moddate":
1296 #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
1297         result = ""
1298         if argv == "ISO":
1299             result = tme.isoformat()
1300         elif argv == "long":
1301             result = tme.strftime(timeformats[lang][0])
1302         elif argv == "short":
1303             result = tme.strftime(timeformats[lang][1])
1304         else:
1305             fmt = argv.replace("HH", "%H").replace("H", "%H").replace("hh", "%I").replace("h", "%I")
1306             fmt = fmt.replace("mm", "%M").replace("m", "%M").replace("ss", "%S").replace("s", "%S")
1307             fmt = fmt.replace("zzz", "%f").replace("z", "%f").replace("t", "%Z")
1308             fmt = fmt.replace("AP", "%p").replace("ap", "%p").replace("A", "%p").replace("a", "%p")
1309             fmt = fmt.replace("'", "")
1310             result = dte.strftime(fmt)
1311         document.body[i : j+1] = result
1312         i = i + 1
1313
1314
1315 def revert_namenoextinfo(document):
1316     " Merge buffer Info inset type name-noext to name. "
1317
1318     i = 0
1319     while True:
1320         i = find_token(document.body, "\\begin_inset Info", i)
1321         if i == -1:
1322             return
1323         j = find_end_of_inset(document.body, i + 1)
1324         if j == -1:
1325             document.warning("Malformed LyX document: Could not find end of Info inset.")
1326             i = i + 1
1327             continue
1328         tp = find_token(document.body, 'type', i, j)
1329         tpv = get_quoted_value(document.body, "type", tp)
1330         if tpv != "buffer":
1331             i = i + 1
1332             continue
1333         arg = find_token(document.body, 'arg', i, j)
1334         argv = get_quoted_value(document.body, "arg", arg)
1335         if argv != "name-noext":
1336             i = i + 1
1337             continue
1338         document.body[arg] = "arg \"name\""
1339         i = i + 1
1340
1341
1342 def revert_l7ninfo(document):
1343     " Revert l7n Info inset to text. "
1344
1345     i = 0
1346     while True:
1347         i = find_token(document.body, "\\begin_inset Info", i)
1348         if i == -1:
1349             return
1350         j = find_end_of_inset(document.body, i + 1)
1351         if j == -1:
1352             document.warning("Malformed LyX document: Could not find end of Info inset.")
1353             i = i + 1
1354             continue
1355         tp = find_token(document.body, 'type', i, j)
1356         tpv = get_quoted_value(document.body, "type", tp)
1357         if tpv != "l7n":
1358             i = i + 1
1359             continue
1360         arg = find_token(document.body, 'arg', i, j)
1361         argv = get_quoted_value(document.body, "arg", arg)
1362         # remove trailing colons, menu accelerator (|...) and qt accelerator (&), while keeping literal " & "
1363         argv = argv.rstrip(':').split('|')[0].replace(" & ", "</amp;>").replace("&", "").replace("</amp;>", " & ")
1364         document.body[i : j+1] = argv
1365         i = i + 1
1366
1367
1368 def revert_listpargs(document):
1369     " Reverts listpreamble arguments to TeX-code "
1370     i = 0
1371     while True:
1372         i = find_token(document.body, "\\begin_inset Argument listpreamble:", i)
1373         if i == -1:
1374             return
1375         j = find_end_of_inset(document.body, i)
1376         # Find containing paragraph layout
1377         parent = get_containing_layout(document.body, i)
1378         if parent == False:
1379             document.warning("Malformed LyX document: Can't find parent paragraph layout")
1380             i += 1
1381             continue
1382         parbeg = parent[3]
1383         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1384         endPlain = find_end_of_layout(document.body, beginPlain)
1385         content = document.body[beginPlain + 1 : endPlain]
1386         del document.body[i:j+1]
1387         subst = ["\\begin_inset ERT", "status collapsed", "", "\\begin_layout Plain Layout",
1388                  "{"] + content + ["}", "\\end_layout", "", "\\end_inset", ""]
1389         document.body[parbeg : parbeg] = subst
1390         i += 1
1391
1392
1393 def revert_lformatinfo(document):
1394     " Revert layout format Info inset to text. "
1395
1396     i = 0
1397     while True:
1398         i = find_token(document.body, "\\begin_inset Info", i)
1399         if i == -1:
1400             return
1401         j = find_end_of_inset(document.body, i + 1)
1402         if j == -1:
1403             document.warning("Malformed LyX document: Could not find end of Info inset.")
1404             i = i + 1
1405             continue
1406         tp = find_token(document.body, 'type', i, j)
1407         tpv = get_quoted_value(document.body, "type", tp)
1408         if tpv != "lyxinfo":
1409             i = i + 1
1410             continue
1411         arg = find_token(document.body, 'arg', i, j)
1412         argv = get_quoted_value(document.body, "arg", arg)
1413         if argv != "layoutformat":
1414             i = i + 1
1415             continue
1416         # hardcoded for now
1417         document.body[i : j+1] = "69"
1418         i = i + 1
1419
1420
1421 def convert_hebrew_parentheses(document):
1422     " Don't reverse parentheses in Hebrew text"
1423     current_language = document.language
1424     for i, line in enumerate(document.body):
1425         if line.startswith('\\lang '):
1426             current_language = line[len('\\lang '):]
1427         elif line.startswith('\\end_layout'):
1428             current_language = document.language
1429         elif current_language == 'hebrew' and not line.startswith('\\'):
1430             document.body[i] = line.replace('(','\x00').replace(')','(').replace('\x00',')')
1431
1432
1433 def revert_hebrew_parentheses(document):
1434     " Store parentheses in Hebrew text reversed"
1435     # This only exists to keep the convert/revert naming convention
1436     convert_hebrew_parentheses(document)
1437
1438
1439 def revert_malayalam(document):
1440     " Set the document language to English but assure Malayalam output "
1441
1442     revert_language(document, "malayalam", "", "malayalam")
1443
1444
1445 def revert_soul(document):
1446     " Revert soul module flex insets to ERT "
1447
1448     flexes = ["Spaceletters", "Strikethrough", "Underline", "Highlight", "Capitalize"]
1449
1450     for flex in flexes:
1451         i = find_token(document.body, "\\begin_inset Flex %s" % flex, 0)
1452         if i != -1:
1453             add_to_preamble(document, ["\\usepackage{soul}"])
1454             break
1455     i = find_token(document.body, "\\begin_inset Flex Highlight", 0)
1456     if i != -1:
1457         add_to_preamble(document, ["\\usepackage{color}"])
1458     
1459     revert_flex_inset(document.body, "Spaceletters", "\\so")
1460     revert_flex_inset(document.body, "Strikethrough", "\\st")
1461     revert_flex_inset(document.body, "Underline", "\\ul")
1462     revert_flex_inset(document.body, "Highlight", "\\hl")
1463     revert_flex_inset(document.body, "Capitalize", "\\caps")
1464
1465
1466 def revert_tablestyle(document):
1467     " Remove tablestyle params "
1468
1469     i = 0
1470     i = find_token(document.header, "\\tablestyle", 0)
1471     if i != -1:
1472         del document.header[i]
1473
1474
1475 def revert_bibfileencodings(document):
1476     " Revert individual Biblatex bibliography encodings "
1477
1478     # Get cite engine
1479     engine = "basic"
1480     i = find_token(document.header, "\\cite_engine", 0)
1481     if i == -1:
1482         document.warning("Malformed document! Missing \\cite_engine")
1483     else:
1484         engine = get_value(document.header, "\\cite_engine", i)
1485
1486     # Check if biblatex
1487     biblatex = False
1488     if engine in ["biblatex", "biblatex-natbib"]:
1489         biblatex = True
1490
1491     # Map lyx to latex encoding names
1492     encodings = {
1493         "utf8" : "utf8",
1494         "utf8x" : "utf8x",
1495         "armscii8" : "armscii8",
1496         "iso8859-1" : "latin1",
1497         "iso8859-2" : "latin2",
1498         "iso8859-3" : "latin3",
1499         "iso8859-4" : "latin4",
1500         "iso8859-5" : "iso88595",
1501         "iso8859-6" : "8859-6",
1502         "iso8859-7" : "iso-8859-7",
1503         "iso8859-8" : "8859-8",
1504         "iso8859-9" : "latin5",
1505         "iso8859-13" : "latin7",
1506         "iso8859-15" : "latin9",
1507         "iso8859-16" : "latin10",
1508         "applemac" : "applemac",
1509         "cp437" : "cp437",
1510         "cp437de" : "cp437de",
1511         "cp850" : "cp850",
1512         "cp852" : "cp852",
1513         "cp855" : "cp855",
1514         "cp858" : "cp858",
1515         "cp862" : "cp862",
1516         "cp865" : "cp865",
1517         "cp866" : "cp866",
1518         "cp1250" : "cp1250",
1519         "cp1251" : "cp1251",
1520         "cp1252" : "cp1252",
1521         "cp1255" : "cp1255",
1522         "cp1256" : "cp1256",
1523         "cp1257" : "cp1257",
1524         "koi8-r" : "koi8-r",
1525         "koi8-u" : "koi8-u",
1526         "pt154" : "pt154",
1527         "utf8-platex" : "utf8",
1528         "ascii" : "ascii"
1529     }
1530
1531     i = 0
1532     bibresources = []
1533     while (True):
1534         i = find_token(document.body, "\\begin_inset CommandInset bibtex", i)
1535         if i == -1:
1536             break
1537         j = find_end_of_inset(document.body, i)
1538         if j == -1:
1539             document.warning("Can't find end of bibtex inset at line %d!!" %(i))
1540             i += 1
1541             continue
1542         encodings = get_quoted_value(document.body, "file_encodings", i, j)
1543         if not encodings:
1544             i += 1
1545             continue
1546         bibfiles = get_quoted_value(document.body, "bibfiles", i, j).split(",")
1547         opts = get_quoted_value(document.body, "biblatexopts", i, j)
1548         if len(bibfiles) == 0:
1549             document.warning("Bibtex inset at line %d does not have a bibfile!" %(i))
1550         # remove encoding line
1551         k = find_token(document.body, "file_encodings", i, j)
1552         if k != -1:
1553             del document.body[k]
1554         # Re-find inset end line
1555         j = find_end_of_inset(document.body, i)
1556         if biblatex:
1557             enclist = encodings.split("\t")
1558             encmap = dict()
1559             for pp in enclist:
1560                 ppp = pp.split(" ", 1)
1561                 encmap[ppp[0]] = ppp[1]
1562             for bib in bibfiles:
1563                 pr = "\\addbibresource"
1564                 if bib in encmap.keys():
1565                     pr += "[bibencoding=" + encmap[bib] + "]"
1566                 pr += "{" + bib + "}"
1567                 add_to_preamble(document, [pr])
1568             # Insert ERT \\printbibliography and wrap bibtex inset to a Note
1569             pcmd = "printbibliography"
1570             if opts:
1571                 pcmd += "[" + opts + "]"
1572             repl = ["\\begin_inset ERT", "status open", "", "\\begin_layout Plain Layout",\
1573                     "", "", "\\backslash", pcmd, "\\end_layout", "", "\\end_inset", "", "",\
1574                     "\\end_layout", "", "\\begin_layout Standard", "\\begin_inset Note Note",\
1575                     "status open", "", "\\begin_layout Plain Layout" ]
1576             repl += document.body[i:j+1]
1577             repl += ["", "\\end_layout", "", "\\end_inset", "", ""]
1578             document.body[i:j+1] = repl
1579             j += 27
1580
1581         i = j + 1
1582
1583
1584 def revert_cmidruletrimming(document):
1585     " Remove \\cmidrule trimming "
1586
1587     # FIXME: Revert to TeX code?
1588     i = 0
1589     while True:
1590         # first, let's find out if we need to do anything
1591         i = find_token(document.body, '<cell ', i)
1592         if i == -1:
1593             return
1594         j = document.body[i].find('trim="')
1595         if j == -1:
1596              i += 1
1597              continue
1598         rgx = re.compile(r' (bottom|top)line[lr]trim="true"')
1599         # remove trim option
1600         document.body[i] = rgx.sub('', document.body[i])
1601
1602         i += 1
1603
1604
1605 ##
1606 # Conversion hub
1607 #
1608
1609 supported_versions = ["2.4.0", "2.4"]
1610 convert = [
1611            [545, [convert_lst_literalparam]],
1612            [546, []],
1613            [547, []],
1614            [548, []],
1615            [549, []],
1616            [550, [convert_fontenc]],
1617            [551, []],
1618            [552, []],
1619            [553, []],
1620            [554, []],
1621            [555, []],
1622            [556, []],
1623            [557, [convert_vcsinfo]],
1624            [558, [removeFrontMatterStyles]],
1625            [559, []],
1626            [560, []],
1627            [561, [convert_latexFonts]], # Handle dejavu, ibmplex fonts in GUI
1628            [562, []],
1629            [563, []],
1630            [564, []],
1631            [565, [convert_AdobeFonts]], # Handle adobe fonts in GUI
1632            [566, [convert_hebrew_parentheses]],
1633            [567, []],
1634            [568, []],
1635            [569, []],
1636            [570, []],
1637            [571, []],
1638            [572, [convert_notoFonts]]  # Added options thin, light, extralight for Noto
1639           ]
1640
1641 revert =  [
1642            [571, [revert_notoFonts]],
1643            [570, [revert_cmidruletrimming]],
1644            [569, [revert_bibfileencodings]],
1645            [568, [revert_tablestyle]],
1646            [567, [revert_soul]],
1647            [566, [revert_malayalam]],
1648            [565, [revert_hebrew_parentheses]],
1649            [564, [revert_AdobeFonts]],
1650            [563, [revert_lformatinfo]],
1651            [562, [revert_listpargs]],
1652            [561, [revert_l7ninfo]],
1653            [560, [revert_latexFonts]], # Handle dejavu, ibmplex fonts in user preamble
1654            [559, [revert_timeinfo, revert_namenoextinfo]],
1655            [558, [revert_dateinfo]],
1656            [557, [addFrontMatterStyles]],
1657            [556, [revert_vcsinfo]],
1658            [555, [revert_bibencoding]],
1659            [554, [revert_vcolumns]],
1660            [553, [revert_stretchcolumn]],
1661            [552, [revert_tuftecite]],
1662            [551, [revert_floatpclass, revert_floatalignment]],
1663            [550, [revert_nospellcheck]],
1664            [549, [revert_fontenc]],
1665            [548, []],# dummy format change
1666            [547, [revert_lscape]],
1667            [546, [revert_xcharter]],
1668            [545, [revert_paratype]],
1669            [544, [revert_lst_literalparam]]
1670           ]
1671
1672
1673 if __name__ == "__main__":
1674     pass