]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_4.py
lyx2lyx: Minor formatting fix.
[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)
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     return fm
162
163 def convert_fonts(document, fm):
164     " Handle font definition to LaTeX "
165
166     rpkg = re.compile(r'^\\usepackage(\[([^\]]*)\])?\{([^\}]+)\}')
167     rscaleopt = re.compile(r'^scaled?=(.*)')
168
169     i = 0
170     while i < len(document.preamble):
171         i = find_re(document.preamble, rpkg, i)
172         if i == -1:
173             return
174         mo = rpkg.search(document.preamble[i])
175         if mo == None or mo.group(2) == None:
176             options = []
177         else:
178             options = mo.group(2).replace(' ', '').split(",")
179         pkg = mo.group(3)
180         o = 0
181         oscale = 1
182         while o < len(options):
183             mo = rscaleopt.search(options[o])
184             if mo == None:
185                 o += 1
186                 continue
187             oscale = mo.group(1)
188             del options[o]
189             break
190
191         if not pkg in fm.pkginmap:
192             i += 1
193             continue
194         # determine fontname
195         fn = fm.getfontname(pkg, options)
196         if fn == None:
197             i += 1
198             continue
199         del document.preamble[i]
200         fontinfo = fm.font2pkgmap[fn]
201         if fontinfo.scaletype == None:
202             fontscale = None
203         else:
204             fontscale = "\\font_" + fontinfo.scaletype + "_scale"
205             fontinfo.scaleval = oscale
206
207         if i > 0 and document.preamble[i-1] == "% Added by lyx2lyx":
208             del document.preamble[i-1]
209         if fontscale != None:
210             j = find_token(document.header, fontscale, 0)
211             if j != -1:
212                 val = get_value(document.header, fontscale, j)
213                 vals = val.split()
214                 scale = "100"
215                 if oscale != None:
216                     scale = "%03d" % int(float(oscale) * 100)
217                 document.header[j] = fontscale + " " + scale + " " + vals[1]
218         ft = "\\font_" + fontinfo.fonttype
219         j = find_token(document.header, ft, 0)
220         if j != -1:
221             val = get_value(document.header, ft, j)
222             vals = val.split()
223             document.header[j] = ft + ' "' + fn + '" ' + vals[1]
224
225 def revert_fonts(document, fm, fontmap):
226     " Revert native font definition to LaTeX "
227     # fonlist := list of fonts created from the same package
228     # Empty package means that the font-name is the same as the package-name
229     # fontmap (key = package, val += found options) will be filled
230     # and used later in add_preamble_fonts() to be added to user-preamble
231
232     rfontscale = re.compile(r'^\s*(\\font_(roman|sans|typewriter|math))\s+')
233     rscales = re.compile(r'^\s*(\d+)\s+(\d+)')
234     i = 0
235     while i < len(document.header):
236         i = find_re(document.header, rfontscale, i)
237         if (i == -1):
238             break
239         mo = rfontscale.search(document.header[i])
240         if mo == None:
241             i += 1
242             continue
243         ft = mo.group(1)    # 'roman', 'sans', 'typewriter', 'math'
244         val = get_value(document.header, ft, i)
245         words = val.split()
246         font = words[0].replace('"', '')
247         if not font in fm.font2pkgmap:
248             i += 1
249             continue
250         fontinfo = fm.font2pkgmap[font]
251         val = fontinfo.package
252         if not val in fontmap:
253             fontmap[val] = []
254         document.header[i] = ft + ' "default" ' + words[1]
255         if fontinfo.scaleopt != None:
256             xval =  get_value(document.header, "\\font_" + fontinfo.scaletype + "_scale", 0)
257             mo = rscales.search(xval)
258             if mo != None:
259                 xval1 = mo.group(1)
260                 xval2 = mo.group(2)
261                 if xval1 != "100":
262                     # set correct scale option
263                     fontmap[val].extend([fontinfo.scaleopt + "=" + format(float(xval1) / 100, '.2f')])
264         if len(fontinfo.options) > 0:
265             fontmap[val].extend(fontinfo.options)
266         i += 1
267
268 ###############################################################################
269 ###
270 ### Conversion and reversion routines
271 ###
272 ###############################################################################
273
274 def convert_latexFonts(document):
275     " Handle DejaVu and IBMPlex fonts definition to LaTeX "
276
277     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
278         fm = createFontMapping(['DejaVu', 'IBM'])
279         convert_fonts(document, fm)
280
281 def revert_latexFonts(document):
282     " Revert native DejaVu font definition to LaTeX "
283
284     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
285         fontmap = dict()
286         fm = createFontMapping(['DejaVu', 'IBM'])
287         revert_fonts(document, fm, fontmap)
288         add_preamble_fonts(document, fontmap)
289
290 def convert_AdobeFonts(document):
291     " Handle DejaVu and IBMPlex fonts definition to LaTeX "
292
293     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
294         fm = createFontMapping(['Adobe'])
295         convert_fonts(document, fm)
296
297 def revert_AdobeFonts(document):
298     " Revert native DejaVu font definition to LaTeX "
299
300     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
301         fontmap = dict()
302         fm = createFontMapping(['Adobe'])
303         revert_fonts(document, fm, fontmap)
304         add_preamble_fonts(document, fontmap)
305
306 def removeFrontMatterStyles(document):
307     " Remove styles Begin/EndFrontmatter"
308
309     layouts = ['BeginFrontmatter', 'EndFrontmatter']
310     for layout in layouts:
311         i = 0
312         while True:
313             i = find_token(document.body, '\\begin_layout ' + layout, i)
314             if i == -1:
315                 break
316             j = find_end_of_layout(document.body, i)
317             if j == -1:
318                 document.warning("Malformed LyX document: Can't find end of layout at line %d" % i)
319                 i += 1
320                 continue
321             while i > 0 and document.body[i-1].strip() == '':
322                 i -= 1
323             while document.body[j+1].strip() == '':
324                 j = j + 1
325             document.body[i:j+1] = ['']
326
327 def addFrontMatterStyles(document):
328     " Use styles Begin/EndFrontmatter for elsarticle"
329
330     def insertFrontmatter(prefix, line):
331         above = line
332         while above > 0 and document.body[above-1].strip() == '':
333             above -= 1
334         below = line
335         while document.body[below].strip() == '':
336             below += 1
337         document.body[above:below] = ['', '\\begin_layout ' + prefix + 'Frontmatter',
338                                     '\\begin_inset Note Note',
339                                     'status open', '',
340                                     '\\begin_layout Plain Layout',
341                                     'Keep this empty!',
342                                     '\\end_layout', '',
343                                     '\\end_inset', '', '',
344                                     '\\end_layout', '']
345
346     if document.textclass == "elsarticle":
347         layouts = ['Title', 'Title footnote', 'Author', 'Author footnote',
348                    'Corresponding author', 'Address', 'Email', 'Abstract', 'Keywords']
349         first = -1
350         last = -1
351         for layout in layouts:
352             i = 0
353             while True:
354                 i = find_token(document.body, '\\begin_layout ' + layout, i)
355                 if i == -1:
356                     break
357                 k = find_end_of_layout(document.body, i)
358                 if k == -1:
359                     document.warning("Malformed LyX document: Can't find end of layout at line %d" % i)
360                     i += 1;
361                     continue
362                 if first == -1 or i < first:
363                     first = i
364                 if last == -1 or last <= k:
365                     last = k+1
366                 i = k+1
367         if first == -1:
368             return
369         insertFrontmatter('End', last)
370         insertFrontmatter('Begin', first)
371
372 def convert_lst_literalparam(document):
373     " Add param literal to include inset "
374
375     i = 0
376     while True:
377         i = find_token(document.body, '\\begin_inset CommandInset include', i)
378         if i == -1:
379             break
380         j = find_end_of_inset(document.body, i)
381         if j == -1:
382             document.warning("Malformed LyX document: Can't find end of command inset at line %d" % i)
383             i += 1
384             continue
385         while i < j and document.body[i].strip() != '':
386             i += 1
387         document.body.insert(i, "literal \"true\"")
388
389
390 def revert_lst_literalparam(document):
391     " Remove param literal from include inset "
392
393     i = 0
394     while True:
395         i = find_token(document.body, '\\begin_inset CommandInset include', i)
396         if i == -1:
397             break
398         j = find_end_of_inset(document.body, i)
399         if j == -1:
400             document.warning("Malformed LyX document: Can't find end of include inset at line %d" % i)
401             i += 1
402             continue
403         k = find_token(document.body, 'literal', i, j)
404         if k == -1:
405             i += 1
406             continue
407         del document.body[k]
408
409
410 def revert_paratype(document):
411     " Revert ParaType font definitions to LaTeX "
412
413     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
414         preamble = ""
415         i1 = find_token(document.header, "\\font_roman \"PTSerif-TLF\"", 0)
416         i2 = find_token(document.header, "\\font_sans \"default\"", 0)
417         i3 = find_token(document.header, "\\font_typewriter \"default\"", 0)
418         j = find_token(document.header, "\\font_sans \"PTSans-TLF\"", 0)
419         sfval = get_value(document.header, "\\font_sf_scale", 0)
420         # cutoff " 100"
421         sfval = sfval[:-4]
422         sfoption = ""
423         if sfval != "100":
424             sfoption = "scaled=" + format(float(sfval) / 100, '.2f')
425         k = find_token(document.header, "\\font_typewriter \"PTMono-TLF\"", 0)
426         ttval = get_value(document.header, "\\font_tt_scale", 0)
427         # cutoff " 100"
428         ttval = ttval[:-4]
429         ttoption = ""
430         if ttval != "100":
431             ttoption = "scaled=" + format(float(ttval) / 100, '.2f')
432         if i1 != -1 and i2 != -1 and i3!= -1:
433             add_to_preamble(document, ["\\usepackage{paratype}"])
434         else:
435             if i1!= -1:
436                 add_to_preamble(document, ["\\usepackage{PTSerif}"])
437                 document.header[i1] = document.header[i1].replace("PTSerif-TLF", "default")
438             if j!= -1:
439                 if sfoption != "":
440                     add_to_preamble(document, ["\\usepackage[" + sfoption + "]{PTSans}"])
441                 else:
442                     add_to_preamble(document, ["\\usepackage{PTSans}"])
443                 document.header[j] = document.header[j].replace("PTSans-TLF", "default")
444             if k!= -1:
445                 if ttoption != "":
446                     add_to_preamble(document, ["\\usepackage[" + ttoption + "]{PTMono}"])
447                 else:
448                     add_to_preamble(document, ["\\usepackage{PTMono}"])
449                 document.header[k] = document.header[k].replace("PTMono-TLF", "default")
450
451
452 def revert_xcharter(document):
453     " Revert XCharter font definitions to LaTeX "
454
455     i = find_token(document.header, "\\font_roman \"xcharter\"", 0)
456     if i == -1:
457         return
458
459     # replace unsupported font setting
460     document.header[i] = document.header[i].replace("xcharter", "default")
461     # no need for preamble code with system fonts
462     if get_bool_value(document.header, "\\use_non_tex_fonts"):
463         return
464
465     # transfer old style figures setting to package options
466     j = find_token(document.header, "\\font_osf true")
467     if j != -1:
468         options = "[osf]"
469         document.header[j] = "\\font_osf false"
470     else:
471         options = ""
472     if i != -1:
473         add_to_preamble(document, ["\\usepackage%s{XCharter}"%options])
474
475
476 def revert_lscape(document):
477     " Reverts the landscape environment (Landscape module) to TeX-code "
478
479     if not "landscape" in document.get_module_list():
480         return
481
482     i = 0
483     while True:
484         i = find_token(document.body, "\\begin_inset Flex Landscape", i)
485         if i == -1:
486             return
487         j = find_end_of_inset(document.body, i)
488         if j == -1:
489             document.warning("Malformed LyX document: Can't find end of Landscape inset")
490             i += 1
491             continue
492
493         if document.body[i] == "\\begin_inset Flex Landscape (Floating)":
494             document.body[j - 2 : j + 1] = put_cmd_in_ert("\\end{landscape}}")
495             document.body[i : i + 4] = put_cmd_in_ert("\\afterpage{\\begin{landscape}")
496             add_to_preamble(document, ["\\usepackage{afterpage}"])
497         else:
498             document.body[j - 2 : j + 1] = put_cmd_in_ert("\\end{landscape}")
499             document.body[i : i + 4] = put_cmd_in_ert("\\begin{landscape}")
500
501         add_to_preamble(document, ["\\usepackage{pdflscape}"])
502         # no need to reset i
503
504
505 def convert_fontenc(document):
506     " Convert default fontenc setting "
507
508     i = find_token(document.header, "\\fontencoding global", 0)
509     if i == -1:
510         return
511
512     document.header[i] = document.header[i].replace("global", "auto")
513
514
515 def revert_fontenc(document):
516     " Revert default fontenc setting "
517
518     i = find_token(document.header, "\\fontencoding auto", 0)
519     if i == -1:
520         return
521
522     document.header[i] = document.header[i].replace("auto", "global")
523
524
525 def revert_nospellcheck(document):
526     " Remove nospellcheck font info param "
527
528     i = 0
529     while True:
530         i = find_token(document.body, '\\nospellcheck', i)
531         if i == -1:
532             return
533         del document.body[i]
534
535
536 def revert_floatpclass(document):
537     " Remove float placement params 'document' and 'class' "
538
539     i = 0
540     i = find_token(document.header, "\\float_placement class", 0)
541     if i != -1:
542         del document.header[i]
543
544     i = 0
545     while True:
546         i = find_token(document.body, '\\begin_inset Float', i)
547         if i == -1:
548             break
549         j = find_end_of_inset(document.body, i)
550         k = find_token(document.body, 'placement class', i, i + 2)
551         if k == -1:
552             k = find_token(document.body, 'placement document', i, i + 2)
553             if k != -1:
554                 del document.body[k]
555             i += 1
556             continue
557         del document.body[k]
558
559
560 def revert_floatalignment(document):
561     " Remove float alignment params "
562
563     i = 0
564     i = find_token(document.header, "\\float_alignment", 0)
565     galignment = ""
566     if i != -1:
567         galignment = get_value(document.header, "\\float_alignment", i)
568         del document.header[i]
569
570     i = 0
571     while True:
572         i = find_token(document.body, '\\begin_inset Float', i)
573         if i == -1:
574             break
575         j = find_end_of_inset(document.body, i)
576         if j == -1:
577             document.warning("Malformed LyX document: Can't find end of inset at line " + str(i))
578             i += 1
579         k = find_token(document.body, 'alignment', i, i + 4)
580         if k == -1:
581             i = j
582             continue
583         alignment = get_value(document.body, "alignment", k)
584         if alignment == "document":
585             alignment = galignment
586         del document.body[k]
587         l = find_token(document.body, "\\begin_layout Plain Layout", i, j)
588         if l == -1:
589             document.warning("Can't find float layout!")
590             i += 1
591             continue
592         alcmd = []
593         if alignment == "left":
594             alcmd = put_cmd_in_ert("\\raggedright{}")
595         elif alignment == "center":
596             alcmd = put_cmd_in_ert("\\centering{}")
597         elif alignment == "right":
598             alcmd = put_cmd_in_ert("\\raggedleft{}")
599         if len(alcmd) > 0:
600             document.body[l+1:l+1] = alcmd
601         i += 1 
602
603
604 def revert_tuftecite(document):
605     " Revert \cite commands in tufte classes "
606
607     tufte = ["tufte-book", "tufte-handout"]
608     if document.textclass not in tufte:
609         return
610
611     i = 0
612     while (True):
613         i = find_token(document.body, "\\begin_inset CommandInset citation", i)
614         if i == -1:
615             break
616         j = find_end_of_inset(document.body, i)
617         if j == -1:
618             document.warning("Can't find end of citation inset at line %d!!" %(i))
619             i += 1
620             continue
621         k = find_token(document.body, "LatexCommand", i, j)
622         if k == -1:
623             document.warning("Can't find LatexCommand for citation inset at line %d!" %(i))
624             i = j + 1
625             continue
626         cmd = get_value(document.body, "LatexCommand", k)
627         if cmd != "cite":
628             i = j + 1
629             continue
630         pre = get_quoted_value(document.body, "before", i, j)
631         post = get_quoted_value(document.body, "after", i, j)
632         key = get_quoted_value(document.body, "key", i, j)
633         if not key:
634             document.warning("Citation inset at line %d does not have a key!" %(i))
635             key = "???"
636         # Replace command with ERT
637         res = "\\cite"
638         if pre:
639             res += "[" + pre + "]"
640         if post:
641             res += "[" + post + "]"
642         elif pre:
643             res += "[]"
644         res += "{" + key + "}"
645         document.body[i:j+1] = put_cmd_in_ert([res])
646         i = j + 1
647
648
649 def revert_stretchcolumn(document):
650     " We remove the column varwidth flags or everything else will become a mess. "
651     i = 0
652     while True:
653         i = find_token(document.body, "\\begin_inset Tabular", i)
654         if i == -1:
655             return
656         j = find_end_of_inset(document.body, i + 1)
657         if j == -1:
658             document.warning("Malformed LyX document: Could not find end of tabular.")
659             continue
660         for k in range(i, j):
661             if re.search('^<column.*varwidth="[^"]+".*>$', document.body[k]):
662                 document.warning("Converting 'tabularx'/'xltabular' table to normal table.")
663                 document.body[k] = document.body[k].replace(' varwidth="true"', '')
664         i = i + 1
665
666
667 def revert_vcolumns(document):
668     " Revert standard columns with line breaks etc. "
669     i = 0
670     needvarwidth = False
671     needarray = False
672     try:
673         while True:
674             i = find_token(document.body, "\\begin_inset Tabular", i)
675             if i == -1:
676                 return
677             j = find_end_of_inset(document.body, i)
678             if j == -1:
679                 document.warning("Malformed LyX document: Could not find end of tabular.")
680                 i += 1
681                 continue
682
683             # Collect necessary column information
684             m = i + 1
685             nrows = int(document.body[i+1].split('"')[3])
686             ncols = int(document.body[i+1].split('"')[5])
687             col_info = []
688             for k in range(ncols):
689                 m = find_token(document.body, "<column", m)
690                 width = get_option_value(document.body[m], 'width')
691                 varwidth = get_option_value(document.body[m], 'varwidth')
692                 alignment = get_option_value(document.body[m], 'alignment')
693                 special = get_option_value(document.body[m], 'special')
694                 col_info.append([width, varwidth, alignment, special, m])
695
696             # Now parse cells
697             m = i + 1
698             lines = []
699             for row in range(nrows):
700                 for col in range(ncols):
701                     m = find_token(document.body, "<cell", m)
702                     multicolumn = get_option_value(document.body[m], 'multicolumn')
703                     multirow = get_option_value(document.body[m], 'multirow')
704                     width = get_option_value(document.body[m], 'width')
705                     rotate = get_option_value(document.body[m], 'rotate')
706                     # Check for: linebreaks, multipars, non-standard environments
707                     begcell = m
708                     endcell = find_token(document.body, "</cell>", begcell)
709                     vcand = False
710                     if find_token(document.body, "\\begin_inset Newline", begcell, endcell) != -1:
711                         vcand = True
712                     elif count_pars_in_inset(document.body, begcell + 2) > 1:
713                         vcand = True
714                     elif get_value(document.body, "\\begin_layout", begcell) != "Plain Layout":
715                         vcand = True
716                     if vcand and rotate == "" and ((multicolumn == "" and multirow == "") or width == ""):
717                         if col_info[col][0] == "" and col_info[col][1] == "" and col_info[col][3] == "":
718                             needvarwidth = True
719                             alignment = col_info[col][2]
720                             col_line = col_info[col][4]
721                             vval = ""
722                             if alignment == "center":
723                                 vval = ">{\\centering}"
724                             elif  alignment == "left":
725                                 vval = ">{\\raggedright}"
726                             elif alignment == "right":
727                                 vval = ">{\\raggedleft}"
728                             if vval != "":
729                                 needarray = True
730                             vval += "V{\\linewidth}"
731                 
732                             document.body[col_line] = document.body[col_line][:-1] + " special=\"" + vval + "\">"
733                             # ERT newlines and linebreaks (since LyX < 2.4 automatically inserts parboxes
734                             # with newlines, and we do not want that)
735                             while True:
736                                 endcell = find_token(document.body, "</cell>", begcell)
737                                 linebreak = False
738                                 nl = find_token(document.body, "\\begin_inset Newline newline", begcell, endcell)
739                                 if nl == -1:
740                                     nl = find_token(document.body, "\\begin_inset Newline linebreak", begcell, endcell)
741                                     if nl == -1:
742                                          break
743                                     linebreak = True
744                                 nle = find_end_of_inset(document.body, nl)
745                                 del(document.body[nle:nle+1])
746                                 if linebreak:
747                                     document.body[nl:nl+1] = put_cmd_in_ert("\\linebreak{}")
748                                 else:
749                                     document.body[nl:nl+1] = put_cmd_in_ert("\\\\")
750                     m += 1
751
752             i = j + 1
753
754     finally:
755         if needarray == True:
756             add_to_preamble(document, ["\\usepackage{array}"])
757         if needvarwidth == True:
758             add_to_preamble(document, ["\\usepackage{varwidth}"])
759
760
761 def revert_bibencoding(document):
762     " Revert bibliography encoding "
763
764     # Get cite engine
765     engine = "basic"
766     i = find_token(document.header, "\\cite_engine", 0)
767     if i == -1:
768         document.warning("Malformed document! Missing \\cite_engine")
769     else:
770         engine = get_value(document.header, "\\cite_engine", i)
771
772     # Check if biblatex
773     biblatex = False
774     if engine in ["biblatex", "biblatex-natbib"]:
775         biblatex = True
776
777     # Map lyx to latex encoding names 
778     encodings = {
779         "utf8" : "utf8",
780         "utf8x" : "utf8x",
781         "armscii8" : "armscii8",
782         "iso8859-1" : "latin1",
783         "iso8859-2" : "latin2",
784         "iso8859-3" : "latin3",
785         "iso8859-4" : "latin4",
786         "iso8859-5" : "iso88595",
787         "iso8859-6" : "8859-6",
788         "iso8859-7" : "iso-8859-7",
789         "iso8859-8" : "8859-8",
790         "iso8859-9" : "latin5",
791         "iso8859-13" : "latin7",
792         "iso8859-15" : "latin9",
793         "iso8859-16" : "latin10",
794         "applemac" : "applemac",
795         "cp437" : "cp437",
796         "cp437de" : "cp437de",
797         "cp850" : "cp850",
798         "cp852" : "cp852",
799         "cp855" : "cp855",
800         "cp858" : "cp858",
801         "cp862" : "cp862",
802         "cp865" : "cp865",
803         "cp866" : "cp866",
804         "cp1250" : "cp1250",
805         "cp1251" : "cp1251",
806         "cp1252" : "cp1252",
807         "cp1255" : "cp1255",
808         "cp1256" : "cp1256",
809         "cp1257" : "cp1257",
810         "koi8-r" : "koi8-r",
811         "koi8-u" : "koi8-u",
812         "pt154" : "pt154",
813         "utf8-platex" : "utf8",
814         "ascii" : "ascii"
815     }
816
817     i = 0
818     bibresources = []
819     while (True):
820         i = find_token(document.body, "\\begin_inset CommandInset bibtex", i)
821         if i == -1:
822             break
823         j = find_end_of_inset(document.body, i)
824         if j == -1:
825             document.warning("Can't find end of bibtex inset at line %d!!" %(i))
826             i += 1
827             continue
828         encoding = get_quoted_value(document.body, "encoding", i, j)
829         if not encoding:
830             i += 1
831             continue
832         # remove encoding line
833         k = find_token(document.body, "encoding", i, j)
834         if k != -1:
835             del document.body[k]
836         # Re-find inset end line
837         j = find_end_of_inset(document.body, i)
838         if biblatex:
839             biblio_options = ""
840             h = find_token(document.header, "\\biblio_options", 0)
841             if h != -1:
842                 biblio_options = get_value(document.header, "\\biblio_options", h)
843                 if not "bibencoding" in biblio_options:
844                      document.header[h] += ",bibencoding=%s" % encodings[encoding]
845             else:
846                 bs = find_token(document.header, "\\biblatex_bibstyle", 0)
847                 if bs == -1:
848                     # this should not happen
849                     document.warning("Malformed LyX document! No \\biblatex_bibstyle header found!")
850                 else:
851                     document.header[bs-1 : bs-1] = ["\\biblio_options bibencoding=" + encodings[encoding]]
852         else:
853             document.body[j+1:j+1] = put_cmd_in_ert("\\egroup")
854             document.body[i:i] = put_cmd_in_ert("\\bgroup\\inputencoding{" + encodings[encoding] + "}")
855
856         i = j + 1
857
858
859
860 def convert_vcsinfo(document):
861     " Separate vcs Info inset from buffer Info inset. "
862
863     types = {
864         "vcs-revision" : "revision",
865         "vcs-tree-revision" : "tree-revision",
866         "vcs-author" : "author",
867         "vcs-time" : "time",
868         "vcs-date" : "date"
869     }
870     i = 0
871     while True:
872         i = find_token(document.body, "\\begin_inset Info", i)
873         if i == -1:
874             return
875         j = find_end_of_inset(document.body, i + 1)
876         if j == -1:
877             document.warning("Malformed LyX document: Could not find end of Info inset.")
878             i = i + 1
879             continue
880         tp = find_token(document.body, 'type', i, j)
881         tpv = get_quoted_value(document.body, "type", tp)
882         if tpv != "buffer":
883             i = i + 1
884             continue
885         arg = find_token(document.body, 'arg', i, j)
886         argv = get_quoted_value(document.body, "arg", arg)
887         if argv not in list(types.keys()):
888             i = i + 1
889             continue
890         document.body[tp] = "type \"vcs\""
891         document.body[arg] = "arg \"" + types[argv] + "\""
892         i = i + 1
893
894
895 def revert_vcsinfo(document):
896     " Merge vcs Info inset to buffer Info inset. "
897
898     args = ["revision", "tree-revision", "author", "time", "date" ]
899     i = 0
900     while True:
901         i = find_token(document.body, "\\begin_inset Info", i)
902         if i == -1:
903             return
904         j = find_end_of_inset(document.body, i + 1)
905         if j == -1:
906             document.warning("Malformed LyX document: Could not find end of Info inset.")
907             i = i + 1
908             continue
909         tp = find_token(document.body, 'type', i, j)
910         tpv = get_quoted_value(document.body, "type", tp)
911         if tpv != "vcs":
912             i = i + 1
913             continue
914         arg = find_token(document.body, 'arg', i, j)
915         argv = get_quoted_value(document.body, "arg", arg)
916         if argv not in args:
917             document.warning("Malformed Info inset. Invalid vcs arg.")
918             i = i + 1
919             continue
920         document.body[tp] = "type \"buffer\""
921         document.body[arg] = "arg \"vcs-" + argv + "\""
922         i = i + 1
923
924
925 def revert_dateinfo(document):
926     " Revert date info insets to static text. "
927
928 # FIXME This currently only considers the main language and uses the system locale
929 # Ideally, it should honor context languages and switch the locale accordingly.
930
931     # The date formats for each language using strftime syntax:
932     # long, short, loclong, locmedium, locshort
933     dateformats = {
934         "afrikaans" : ["%A, %d %B %Y", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y/%m/%d"],
935         "albanian" : ["%A, %d %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
936         "american" : ["%A, %B %d, %Y", "%m/%d/%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
937         "amharic" : ["%A ፣%d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
938         "ancientgreek" : ["%A, %d %B %Y", "%d %b %Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
939         "arabic_arabi" : ["%A، %d %B، %Y", "%d‏/%m‏/%Y", "%d %B، %Y", "%d/%m/%Y", "%d/%m/%Y"],
940         "arabic_arabtex" : ["%A، %d %B، %Y", "%d‏/%m‏/%Y", "%d %B، %Y", "%d/%m/%Y", "%d/%m/%Y"],
941         "armenian" : ["%Y թ. %B %d, %A", "%d.%m.%y", "%d %B، %Y", "%d %b، %Y", "%d/%m/%Y"],
942         "asturian" : ["%A, %d %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d %b %Y", "%d/%m/%Y"],
943         "australian" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
944         "austrian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
945         "bahasa" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
946         "bahasam" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
947         "basque" : ["%Y(e)ko %B %d, %A", "%y/%m/%d", "%Y %B %d", "%Y %b %d", "%Y/%m/%d"],
948         "belarusian" : ["%A, %d %B %Y г.", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
949         "bosnian" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%Y-%m-%d"],
950         "brazilian" : ["%A, %d de %B de %Y", "%d/%m/%Y", "%d de %B de %Y", "%d de %b de %Y", "%d/%m/%Y"],
951         "breton" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
952         "british" : ["%A, %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
953         "bulgarian" : ["%A, %d %B %Y г.", "%d.%m.%y г.", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
954         "canadian" : ["%A, %B %d, %Y", "%Y-%m-%d", "%B %d, %Y", "%d %b %Y", "%Y-%m-%d"],
955         "canadien" : ["%A %d %B %Y", "%y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
956         "catalan" : ["%A, %d %B de %Y", "%d/%m/%y", "%d / %B / %Y", "%d / %b / %Y", "%d/%m/%Y"],
957         "chinese-simplified" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y-%m-%d", "%y-%m-%d"],
958         "chinese-traditional" : ["%Y年%m月%d日 %A", "%Y/%m/%d", "%Y年%m月%d日", "%Y年%m月%d日", "%y年%m月%d日"],
959         "coptic" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
960         "croatian" : ["%A, %d. %B %Y.", "%d. %m. %Y.", "%d. %B %Y.", "%d. %b. %Y.", "%d.%m.%Y."],
961         "czech" : ["%A %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b. %Y", "%d.%m.%Y"],
962         "danish" : ["%A den %d. %B %Y", "%d/%m/%Y", "%d. %B %Y", "%d. %b %Y", "%d/%m/%Y"],
963         "divehi" : ["%Y %B %d, %A", "%Y-%m-%d", "%Y %B %d", "%Y %b %d", "%d/%m/%Y"],
964         "dutch" : ["%A %d %B %Y", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
965         "english" : ["%A, %B %d, %Y", "%m/%d/%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
966         "esperanto" : ["%A, %d %B %Y", "%d %b %Y", "la %d de %B %Y", "la %d de %b %Y", "%m/%d/%Y"],
967         "estonian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
968         "farsi" : ["%A %d %B %Y", "%Y/%m/%d", "%d %B %Y", "%d %b %Y", "%Y/%m/%d"],
969         "finnish" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
970         "french" : ["%A %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
971         "friulan" : ["%A %d di %B dal %Y", "%d/%m/%y", "%d di %B dal %Y", "%d di %b dal %Y", "%d/%m/%Y"],
972         "galician" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d de %b de %Y", "%d/%m/%Y"],
973         "georgian" : ["%A, %d %B, %Y", "%d.%m.%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
974         "german" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
975         "german-ch" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
976         "german-ch-old" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
977         "greek" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
978         "hebrew" : ["%A, %d ב%B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
979         "hindi" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
980         "icelandic" : ["%A, %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
981         "interlingua" : ["%Y %B %d, %A", "%Y-%m-%d", "le %d de %B %Y", "le %d de %b %Y", "%Y-%m-%d"],
982         "irish" : ["%A %d %B %Y", "%d/%m/%Y", "%d. %B %Y", "%d. %b %Y", "%d/%m/%Y"],
983         "italian" : ["%A %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d/%b/%Y", "%d/%m/%Y"],
984         "japanese" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y/%m/%d", "%y/%m/%d"],
985         "japanese-cjk" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y/%m/%d", "%y/%m/%d"],
986         "kannada" : ["%A, %B %d, %Y", "%d/%m/%y", "%d %B %Y", "%d %B %Y", "%d-%m-%Y"],
987         "kazakh" : ["%Y ж. %d %B, %A", "%d.%m.%y", "%d %B %Y", "%d %B %Y", "%Y-%d-%m"],
988         "khmer" : ["%A %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %B %Y", "%d/%m/%Y"],
989         "korean" : ["%Y년 %m월 %d일 %A", "%y. %m. %d.", "%Y년 %m월 %d일", "%Y. %m. %d.", "%y. %m. %d."],
990         "kurmanji" : ["%A, %d %B %Y", "%d %b %Y", "%d. %B %Y", "%d. %m. %Y", "%Y-%m-%d"],
991         "lao" : ["%A ທີ %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %B %Y", "%d/%m/%Y"],
992         "latin" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
993         "latvian" : ["%A, %Y. gada %d. %B", "%d.%m.%y", "%Y. gada %d. %B", "%Y. gada %d. %b", "%d.%m.%Y"],
994         "lithuanian" : ["%Y m. %B %d d., %A", "%Y-%m-%d", "%Y m. %B %d d.", "%Y m. %B %d d.", "%Y-%m-%d"],
995         "lowersorbian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
996         "macedonian" : ["%A, %d %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
997         "magyar" : ["%Y. %B %d., %A", "%Y. %m. %d.", "%Y. %B %d.", "%Y. %b %d.", "%Y.%m.%d."],
998         "marathi" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
999         "mongolian" : ["%A, %Y оны %m сарын %d", "%Y-%m-%d", "%Y оны %m сарын %d", "%d-%m-%Y", "%d-%m-%Y"],
1000         "naustrian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1001         "newzealand" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1002         "ngerman" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1003         "norsk" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1004         "nynorsk" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1005         "occitan" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1006         "piedmontese" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1007         "polish" : ["%A, %d %B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1008         "polutonikogreek" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1009         "portuguese" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d de %b de %Y", "%Y/%m/%d"],
1010         "romanian" : ["%A, %d %B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1011         "romansh" : ["%A, ils %d da %B %Y", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1012         "russian" : ["%A, %d %B %Y г.", "%d.%m.%Y", "%d %B %Y г.", "%d %b %Y г.", "%d.%m.%Y"],
1013         "samin" : ["%Y %B %d, %A", "%Y-%m-%d", "%B %d. b. %Y", "%b %d. b. %Y", "%d.%m.%Y"],
1014         "sanskrit" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1015         "scottish" : ["%A, %dmh %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1016         "serbian" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1017         "serbian-latin" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1018         "slovak" : ["%A, %d. %B %Y", "%d. %m. %Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1019         "slovene" : ["%A, %d. %B %Y", "%d. %m. %y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1020         "spanish" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B %de %Y", "%d %b %Y", "%d/%m/%Y"],
1021         "spanish-mexico" : ["%A, %d de %B %de %Y", "%d/%m/%y", "%d de %B de %Y", "%d %b %Y", "%d/%m/%Y"],
1022         "swedish" : ["%A %d %B %Y", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1023         "syriac" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1024         "tamil" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1025         "telugu" : ["%d, %B %Y, %A", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1026         "thai" : ["%Aที่ %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1027         "tibetan" : ["%Y %Bའི་ཚེས་%d, %A", "%Y-%m-%d", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1028         "turkish" : ["%d %B %Y %A", "%d.%m.%Y", "%d %B %Y", "%d.%b.%Y", "%d.%m.%Y"],
1029         "turkmen" : ["%d %B %Y %A", "%d.%m.%Y", "%Y ý. %B %d", "%d.%m.%Y ý.", "%d.%m.%y ý."],
1030         "ukrainian" : ["%A, %d %B %Y р.", "%d.%m.%y", "%d %B %Y", "%d %m %Y", "%d.%m.%Y"],
1031         "uppersorbian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1032         "urdu" : ["%A، %d %B، %Y", "%d/%m/%y", "%d %B, %Y", "%d %b %Y", "%d/%m/%Y"],
1033         "vietnamese" : ["%A, %d %B, %Y", "%d/%m/%Y", "%d tháng %B %Y", "%d-%m-%Y", "%d/%m/%Y"],
1034         "welsh" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1035     }
1036
1037     types = ["date", "fixdate", "moddate" ]
1038     i = 0
1039     i = find_token(document.header, "\\language", 0)
1040     if i == -1:
1041         # this should not happen
1042         document.warning("Malformed LyX document! No \\language header found!")
1043         return
1044     lang = get_value(document.header, "\\language", i)
1045
1046     i = 0
1047     while True:
1048         i = find_token(document.body, "\\begin_inset Info", i)
1049         if i == -1:
1050             return
1051         j = find_end_of_inset(document.body, i + 1)
1052         if j == -1:
1053             document.warning("Malformed LyX document: Could not find end of Info inset.")
1054             i = i + 1
1055             continue
1056         tp = find_token(document.body, 'type', i, j)
1057         tpv = get_quoted_value(document.body, "type", tp)
1058         if tpv not in types:
1059             i = i + 1
1060             continue
1061         arg = find_token(document.body, 'arg', i, j)
1062         argv = get_quoted_value(document.body, "arg", arg)
1063         isodate = ""
1064         dte = date.today()
1065         if tpv == "fixdate":
1066             datecomps = argv.split('@')
1067             if len(datecomps) > 1:
1068                 argv = datecomps[0]
1069                 isodate = datecomps[1]
1070                 m = re.search('(\d\d\d\d)-(\d\d)-(\d\d)', isodate)
1071                 if m:
1072                     dte = date(int(m.group(1)), int(m.group(2)), int(m.group(3)))
1073 # FIXME if we had the path to the original document (not the one in the tmp dir),
1074 #        we could use the mtime.
1075 #        elif tpv == "moddate":
1076 #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
1077         result = ""
1078         if argv == "ISO":
1079             result = dte.isodate()
1080         elif argv == "long":
1081             result = dte.strftime(dateformats[lang][0])
1082         elif argv == "short":
1083             result = dte.strftime(dateformats[lang][1])
1084         elif argv == "loclong":
1085             result = dte.strftime(dateformats[lang][2])
1086         elif argv == "locmedium":
1087             result = dte.strftime(dateformats[lang][3])
1088         elif argv == "locshort":
1089             result = dte.strftime(dateformats[lang][4])
1090         else:
1091             fmt = argv.replace("MMMM", "%b").replace("MMM", "%b").replace("MM", "%m").replace("M", "%m")
1092             fmt = fmt.replace("yyyy", "%Y").replace("yy", "%y")
1093             fmt = fmt.replace("dddd", "%A").replace("ddd", "%a").replace("dd", "%d")
1094             fmt = re.sub('[^\'%]d', '%d', fmt)
1095             fmt = fmt.replace("'", "")
1096             result = dte.strftime(fmt)
1097         document.body[i : j+1] = result
1098         i = i + 1
1099
1100
1101 def revert_timeinfo(document):
1102     " Revert time info insets to static text. "
1103
1104 # FIXME This currently only considers the main language and uses the system locale
1105 # Ideally, it should honor context languages and switch the locale accordingly.
1106 # Also, the time object is "naive", i.e., it does not know of timezones (%Z will
1107 # be empty).
1108
1109     # The time formats for each language using strftime syntax:
1110     # long, short
1111     timeformats = {
1112         "afrikaans" : ["%H:%M:%S %Z", "%H:%M"],
1113         "albanian" : ["%I:%M:%S %p, %Z", "%I:%M %p"],
1114         "american" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1115         "amharic" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1116         "ancientgreek" : ["%H:%M:%S %Z", "%H:%M:%S"],
1117         "arabic_arabi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1118         "arabic_arabtex" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1119         "armenian" : ["%H:%M:%S %Z", "%H:%M"],
1120         "asturian" : ["%H:%M:%S %Z", "%H:%M"],
1121         "australian" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1122         "austrian" : ["%H:%M:%S %Z", "%H:%M"],
1123         "bahasa" : ["%H.%M.%S %Z", "%H.%M"],
1124         "bahasam" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1125         "basque" : ["%H:%M:%S (%Z)", "%H:%M"],
1126         "belarusian" : ["%H:%M:%S, %Z", "%H:%M"],
1127         "bosnian" : ["%H:%M:%S %Z", "%H:%M"],
1128         "brazilian" : ["%H:%M:%S %Z", "%H:%M"],
1129         "breton" : ["%H:%M:%S %Z", "%H:%M"],
1130         "british" : ["%H:%M:%S %Z", "%H:%M"],
1131         "bulgarian" : ["%H:%M:%S %Z", "%H:%M"],
1132         "canadian" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1133         "canadien" : ["%H:%M:%S %Z", "%H h %M"],
1134         "catalan" : ["%H:%M:%S %Z", "%H:%M"],
1135         "chinese-simplified" : ["%Z %p%I:%M:%S", "%p%I:%M"],
1136         "chinese-traditional" : ["%p%I:%M:%S [%Z]", "%p%I:%M"],
1137         "coptic" : ["%H:%M:%S %Z", "%H:%M:%S"],
1138         "croatian" : ["%H:%M:%S (%Z)", "%H:%M"],
1139         "czech" : ["%H:%M:%S %Z", "%H:%M"],
1140         "danish" : ["%H.%M.%S %Z", "%H.%M"],
1141         "divehi" : ["%H:%M:%S %Z", "%H:%M"],
1142         "dutch" : ["%H:%M:%S %Z", "%H:%M"],
1143         "english" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1144         "esperanto" : ["%H:%M:%S %Z", "%H:%M:%S"],
1145         "estonian" : ["%H:%M:%S %Z", "%H:%M"],
1146         "farsi" : ["%H:%M:%S (%Z)", "%H:%M"],
1147         "finnish" : ["%H.%M.%S %Z", "%H.%M"],
1148         "french" : ["%H:%M:%S %Z", "%H:%M"],
1149         "friulan" : ["%H:%M:%S %Z", "%H:%M"],
1150         "galician" : ["%H:%M:%S %Z", "%H:%M"],
1151         "georgian" : ["%H:%M:%S %Z", "%H:%M"],
1152         "german" : ["%H:%M:%S %Z", "%H:%M"],
1153         "german-ch" : ["%H:%M:%S %Z", "%H:%M"],
1154         "german-ch-old" : ["%H:%M:%S %Z", "%H:%M"],
1155         "greek" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1156         "hebrew" : ["%H:%M:%S %Z", "%H:%M"],
1157         "hindi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1158         "icelandic" : ["%H:%M:%S %Z", "%H:%M"],
1159         "interlingua" : ["%H:%M:%S %Z", "%H:%M"],
1160         "irish" : ["%H:%M:%S %Z", "%H:%M"],
1161         "italian" : ["%H:%M:%S %Z", "%H:%M"],
1162         "japanese" : ["%H時%M分%S秒 %Z", "%H:%M"],
1163         "japanese-cjk" : ["%H時%M分%S秒 %Z", "%H:%M"],
1164         "kannada" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1165         "kazakh" : ["%H:%M:%S %Z", "%H:%M"],
1166         "khmer" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1167         "korean" : ["%p %I시%M분 %S초 %Z", "%p %I:%M"],
1168         "kurmanji" : ["%H:%M:%S %Z", "%H:%M:%S"],
1169         "lao" : ["%H ໂມງ%M ນາທີ  %S ວິນາທີ %Z", "%H:%M"],
1170         "latin" : ["%H:%M:%S %Z", "%H:%M:%S"],
1171         "latvian" : ["%H:%M:%S %Z", "%H:%M"],
1172         "lithuanian" : ["%H:%M:%S %Z", "%H:%M"],
1173         "lowersorbian" : ["%H:%M:%S %Z", "%H:%M"],
1174         "macedonian" : ["%H:%M:%S %Z", "%H:%M"],
1175         "magyar" : ["%H:%M:%S %Z", "%H:%M"],
1176         "marathi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1177         "mongolian" : ["%H:%M:%S %Z", "%H:%M"],
1178         "naustrian" : ["%H:%M:%S %Z", "%H:%M"],
1179         "newzealand" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1180         "ngerman" : ["%H:%M:%S %Z", "%H:%M"],
1181         "norsk" : ["%H:%M:%S %Z", "%H:%M"],
1182         "nynorsk" : ["kl. %H:%M:%S %Z", "%H:%M"],
1183         "occitan" : ["%H:%M:%S %Z", "%H:%M"],
1184         "piedmontese" : ["%H:%M:%S %Z", "%H:%M:%S"],
1185         "polish" : ["%H:%M:%S %Z", "%H:%M"],
1186         "polutonikogreek" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1187         "portuguese" : ["%H:%M:%S %Z", "%H:%M"],
1188         "romanian" : ["%H:%M:%S %Z", "%H:%M"],
1189         "romansh" : ["%H:%M:%S %Z", "%H:%M"],
1190         "russian" : ["%H:%M:%S %Z", "%H:%M"],
1191         "samin" : ["%H:%M:%S %Z", "%H:%M"],
1192         "sanskrit" : ["%H:%M:%S %Z", "%H:%M"],
1193         "scottish" : ["%H:%M:%S %Z", "%H:%M"],
1194         "serbian" : ["%H:%M:%S %Z", "%H:%M"],
1195         "serbian-latin" : ["%H:%M:%S %Z", "%H:%M"],
1196         "slovak" : ["%H:%M:%S %Z", "%H:%M"],
1197         "slovene" : ["%H:%M:%S %Z", "%H:%M"],
1198         "spanish" : ["%H:%M:%S (%Z)", "%H:%M"],
1199         "spanish-mexico" : ["%H:%M:%S %Z", "%H:%M"],
1200         "swedish" : ["kl. %H:%M:%S %Z", "%H:%M"],
1201         "syriac" : ["%H:%M:%S %Z", "%H:%M"],
1202         "tamil" : ["%p %I:%M:%S %Z", "%p %I:%M"],
1203         "telugu" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1204         "thai" : ["%H นาฬิกา %M นาที  %S วินาที %Z", "%H:%M"],
1205         "tibetan" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1206         "turkish" : ["%H:%M:%S %Z", "%H:%M"],
1207         "turkmen" : ["%H:%M:%S %Z", "%H:%M"],
1208         "ukrainian" : ["%H:%M:%S %Z", "%H:%M"],
1209         "uppersorbian" : ["%H:%M:%S %Z", "%H:%M hodź."],
1210         "urdu" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1211         "vietnamese" : ["%H:%M:%S %Z", "%H:%M"],
1212         "welsh" : ["%H:%M:%S %Z", "%H:%M"]
1213     }
1214
1215     types = ["time", "fixtime", "modtime" ]
1216     i = 0
1217     i = find_token(document.header, "\\language", 0)
1218     if i == -1:
1219         # this should not happen
1220         document.warning("Malformed LyX document! No \\language header found!")
1221         return
1222     lang = get_value(document.header, "\\language", i)
1223
1224     i = 0
1225     while True:
1226         i = find_token(document.body, "\\begin_inset Info", i)
1227         if i == -1:
1228             return
1229         j = find_end_of_inset(document.body, i + 1)
1230         if j == -1:
1231             document.warning("Malformed LyX document: Could not find end of Info inset.")
1232             i = i + 1
1233             continue
1234         tp = find_token(document.body, 'type', i, j)
1235         tpv = get_quoted_value(document.body, "type", tp)
1236         if tpv not in types:
1237             i = i + 1
1238             continue
1239         arg = find_token(document.body, 'arg', i, j)
1240         argv = get_quoted_value(document.body, "arg", arg)
1241         isotime = ""
1242         dtme = datetime.now()
1243         tme = dtme.time()
1244         if tpv == "fixtime":
1245             timecomps = argv.split('@')
1246             if len(timecomps) > 1:
1247                 argv = timecomps[0]
1248                 isotime = timecomps[1]
1249                 m = re.search('(\d\d):(\d\d):(\d\d)', isotime)
1250                 if m:
1251                     tme = time(int(m.group(1)), int(m.group(2)), int(m.group(3)))
1252                 else:
1253                     m = re.search('(\d\d):(\d\d)', isotime)
1254                     if m:
1255                         tme = time(int(m.group(1)), int(m.group(2)))
1256 # FIXME if we had the path to the original document (not the one in the tmp dir),
1257 #        we could use the mtime.
1258 #        elif tpv == "moddate":
1259 #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
1260         result = ""
1261         if argv == "ISO":
1262             result = tme.isoformat()
1263         elif argv == "long":
1264             result = tme.strftime(timeformats[lang][0])
1265         elif argv == "short":
1266             result = tme.strftime(timeformats[lang][1])
1267         else:
1268             fmt = argv.replace("HH", "%H").replace("H", "%H").replace("hh", "%I").replace("h", "%I")
1269             fmt = fmt.replace("mm", "%M").replace("m", "%M").replace("ss", "%S").replace("s", "%S")
1270             fmt = fmt.replace("zzz", "%f").replace("z", "%f").replace("t", "%Z")
1271             fmt = fmt.replace("AP", "%p").replace("ap", "%p").replace("A", "%p").replace("a", "%p")
1272             fmt = fmt.replace("'", "")
1273             result = dte.strftime(fmt)
1274         document.body[i : j+1] = result
1275         i = i + 1
1276
1277
1278 def revert_namenoextinfo(document):
1279     " Merge buffer Info inset type name-noext to name. "
1280
1281     i = 0
1282     while True:
1283         i = find_token(document.body, "\\begin_inset Info", i)
1284         if i == -1:
1285             return
1286         j = find_end_of_inset(document.body, i + 1)
1287         if j == -1:
1288             document.warning("Malformed LyX document: Could not find end of Info inset.")
1289             i = i + 1
1290             continue
1291         tp = find_token(document.body, 'type', i, j)
1292         tpv = get_quoted_value(document.body, "type", tp)
1293         if tpv != "buffer":
1294             i = i + 1
1295             continue
1296         arg = find_token(document.body, 'arg', i, j)
1297         argv = get_quoted_value(document.body, "arg", arg)
1298         if argv != "name-noext":
1299             i = i + 1
1300             continue
1301         document.body[arg] = "arg \"name\""
1302         i = i + 1
1303
1304
1305 def revert_l7ninfo(document):
1306     " Revert l7n Info inset to text. "
1307
1308     i = 0
1309     while True:
1310         i = find_token(document.body, "\\begin_inset Info", i)
1311         if i == -1:
1312             return
1313         j = find_end_of_inset(document.body, i + 1)
1314         if j == -1:
1315             document.warning("Malformed LyX document: Could not find end of Info inset.")
1316             i = i + 1
1317             continue
1318         tp = find_token(document.body, 'type', i, j)
1319         tpv = get_quoted_value(document.body, "type", tp)
1320         if tpv != "l7n":
1321             i = i + 1
1322             continue
1323         arg = find_token(document.body, 'arg', i, j)
1324         argv = get_quoted_value(document.body, "arg", arg)
1325         # remove trailing colons, menu accelerator (|...) and qt accelerator (&), while keeping literal " & " 
1326         argv = argv.rstrip(':').split('|')[0].replace(" & ", "</amp;>").replace("&", "").replace("</amp;>", " & ")
1327         document.body[i : j+1] = argv
1328         i = i + 1
1329
1330
1331 def revert_listpargs(document):
1332     " Reverts listpreamble arguments to TeX-code "
1333     i = 0
1334     while True:
1335         i = find_token(document.body, "\\begin_inset Argument listpreamble:", i)
1336         if i == -1:
1337             return
1338         j = find_end_of_inset(document.body, i)
1339         # Find containing paragraph layout
1340         parent = get_containing_layout(document.body, i)
1341         if parent == False:
1342             document.warning("Malformed LyX document: Can't find parent paragraph layout")
1343             i += 1
1344             continue
1345         parbeg = parent[3]
1346         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1347         endPlain = find_end_of_layout(document.body, beginPlain)
1348         content = document.body[beginPlain + 1 : endPlain]
1349         del document.body[i:j+1]
1350         subst = ["\\begin_inset ERT", "status collapsed", "", "\\begin_layout Plain Layout",
1351                  "{"] + content + ["}", "\\end_layout", "", "\\end_inset", ""]
1352         document.body[parbeg : parbeg] = subst
1353         i += 1
1354
1355
1356 def revert_lformatinfo(document):
1357     " Revert layout format Info inset to text. "
1358
1359     i = 0
1360     while True:
1361         i = find_token(document.body, "\\begin_inset Info", i)
1362         if i == -1:
1363             return
1364         j = find_end_of_inset(document.body, i + 1)
1365         if j == -1:
1366             document.warning("Malformed LyX document: Could not find end of Info inset.")
1367             i = i + 1
1368             continue
1369         tp = find_token(document.body, 'type', i, j)
1370         tpv = get_quoted_value(document.body, "type", tp)
1371         if tpv != "lyxinfo":
1372             i = i + 1
1373             continue
1374         arg = find_token(document.body, 'arg', i, j)
1375         argv = get_quoted_value(document.body, "arg", arg)
1376         if argv != "layoutformat":
1377             i = i + 1
1378             continue
1379         # hardcoded for now
1380         document.body[i : j+1] = "69"
1381         i = i + 1
1382
1383
1384 def convert_hebrew_parentheses(document):
1385     " Don't reverse parentheses in Hebrew text"
1386     current_language = document.language
1387     for i, line in enumerate(document.body):
1388         if line.startswith('\\lang '):
1389             current_language = line[len('\\lang '):]
1390         elif line.startswith('\\end_layout'):
1391             current_language = document.language
1392         elif current_language == 'hebrew' and not line.startswith('\\'):
1393             document.body[i] = line.replace('(','\x00').replace(')','(').replace('\x00',')')
1394
1395
1396 def revert_hebrew_parentheses(document):
1397     " Store parentheses in Hebrew text reversed"
1398     # This only exists to keep the convert/revert naming convention
1399     convert_hebrew_parentheses(document)
1400
1401
1402 ##
1403 # Conversion hub
1404 #
1405
1406 supported_versions = ["2.4.0", "2.4"]
1407 convert = [
1408            [545, [convert_lst_literalparam]],
1409            [546, []],
1410            [547, []],
1411            [548, []],
1412            [549, []],
1413            [550, [convert_fontenc]],
1414            [551, []],
1415            [552, []],
1416            [553, []],
1417            [554, []],
1418            [555, []],
1419            [556, []],
1420            [557, [convert_vcsinfo]],
1421            [558, [removeFrontMatterStyles]],
1422            [559, []],
1423            [560, []],
1424            [561, [convert_latexFonts]], # Handle dejavu, ibmplex fonts in GUI
1425            [562, []],
1426            [563, []],
1427            [564, []],
1428            [565, [convert_AdobeFonts]], # Handle adobe fonts in GUI
1429            [566, [convert_hebrew_parentheses]],
1430           ]
1431
1432 revert =  [
1433            [565, [revert_hebrew_parentheses]],
1434            [564, [revert_AdobeFonts]],
1435            [563, [revert_lformatinfo]],
1436            [562, [revert_listpargs]],
1437            [561, [revert_l7ninfo]],
1438            [560, [revert_latexFonts]], # Handle dejavu, ibmplex fonts in user preamble
1439            [559, [revert_timeinfo, revert_namenoextinfo]],
1440            [558, [revert_dateinfo]],
1441            [557, [addFrontMatterStyles]],
1442            [556, [revert_vcsinfo]],
1443            [555, [revert_bibencoding]],
1444            [554, [revert_vcolumns]],
1445            [553, [revert_stretchcolumn]],
1446            [552, [revert_tuftecite]],
1447            [551, [revert_floatpclass, revert_floatalignment]],
1448            [550, [revert_nospellcheck]],
1449            [549, [revert_fontenc]],
1450            [548, []],# dummy format change
1451            [547, [revert_lscape]],
1452            [546, [revert_xcharter]],
1453            [545, [revert_paratype]],
1454            [544, [revert_lst_literalparam]]
1455           ]
1456
1457
1458 if __name__ == "__main__":
1459     pass