]> git.lyx.org Git - features.git/blob - lib/lyx2lyx/lyx_2_4.py
Fix encoding issue with lyx2lyx conversion of "Date" info-insets.
[features.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         if sys.version_info < (3,0):
1098             # In Python 2, datetime module works with binary strings,
1099             # our dateformat strings are utf8-encoded:
1100             result = result.decode('utf-8')
1101         document.body[i : j+1] = result
1102         i = i + 1
1103
1104
1105 def revert_timeinfo(document):
1106     " Revert time info insets to static text. "
1107
1108 # FIXME This currently only considers the main language and uses the system locale
1109 # Ideally, it should honor context languages and switch the locale accordingly.
1110 # Also, the time object is "naive", i.e., it does not know of timezones (%Z will
1111 # be empty).
1112
1113     # The time formats for each language using strftime syntax:
1114     # long, short
1115     timeformats = {
1116         "afrikaans" : ["%H:%M:%S %Z", "%H:%M"],
1117         "albanian" : ["%I:%M:%S %p, %Z", "%I:%M %p"],
1118         "american" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1119         "amharic" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1120         "ancientgreek" : ["%H:%M:%S %Z", "%H:%M:%S"],
1121         "arabic_arabi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1122         "arabic_arabtex" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1123         "armenian" : ["%H:%M:%S %Z", "%H:%M"],
1124         "asturian" : ["%H:%M:%S %Z", "%H:%M"],
1125         "australian" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1126         "austrian" : ["%H:%M:%S %Z", "%H:%M"],
1127         "bahasa" : ["%H.%M.%S %Z", "%H.%M"],
1128         "bahasam" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1129         "basque" : ["%H:%M:%S (%Z)", "%H:%M"],
1130         "belarusian" : ["%H:%M:%S, %Z", "%H:%M"],
1131         "bosnian" : ["%H:%M:%S %Z", "%H:%M"],
1132         "brazilian" : ["%H:%M:%S %Z", "%H:%M"],
1133         "breton" : ["%H:%M:%S %Z", "%H:%M"],
1134         "british" : ["%H:%M:%S %Z", "%H:%M"],
1135         "bulgarian" : ["%H:%M:%S %Z", "%H:%M"],
1136         "canadian" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1137         "canadien" : ["%H:%M:%S %Z", "%H h %M"],
1138         "catalan" : ["%H:%M:%S %Z", "%H:%M"],
1139         "chinese-simplified" : ["%Z %p%I:%M:%S", "%p%I:%M"],
1140         "chinese-traditional" : ["%p%I:%M:%S [%Z]", "%p%I:%M"],
1141         "coptic" : ["%H:%M:%S %Z", "%H:%M:%S"],
1142         "croatian" : ["%H:%M:%S (%Z)", "%H:%M"],
1143         "czech" : ["%H:%M:%S %Z", "%H:%M"],
1144         "danish" : ["%H.%M.%S %Z", "%H.%M"],
1145         "divehi" : ["%H:%M:%S %Z", "%H:%M"],
1146         "dutch" : ["%H:%M:%S %Z", "%H:%M"],
1147         "english" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1148         "esperanto" : ["%H:%M:%S %Z", "%H:%M:%S"],
1149         "estonian" : ["%H:%M:%S %Z", "%H:%M"],
1150         "farsi" : ["%H:%M:%S (%Z)", "%H:%M"],
1151         "finnish" : ["%H.%M.%S %Z", "%H.%M"],
1152         "french" : ["%H:%M:%S %Z", "%H:%M"],
1153         "friulan" : ["%H:%M:%S %Z", "%H:%M"],
1154         "galician" : ["%H:%M:%S %Z", "%H:%M"],
1155         "georgian" : ["%H:%M:%S %Z", "%H:%M"],
1156         "german" : ["%H:%M:%S %Z", "%H:%M"],
1157         "german-ch" : ["%H:%M:%S %Z", "%H:%M"],
1158         "german-ch-old" : ["%H:%M:%S %Z", "%H:%M"],
1159         "greek" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1160         "hebrew" : ["%H:%M:%S %Z", "%H:%M"],
1161         "hindi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1162         "icelandic" : ["%H:%M:%S %Z", "%H:%M"],
1163         "interlingua" : ["%H:%M:%S %Z", "%H:%M"],
1164         "irish" : ["%H:%M:%S %Z", "%H:%M"],
1165         "italian" : ["%H:%M:%S %Z", "%H:%M"],
1166         "japanese" : ["%H時%M分%S秒 %Z", "%H:%M"],
1167         "japanese-cjk" : ["%H時%M分%S秒 %Z", "%H:%M"],
1168         "kannada" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1169         "kazakh" : ["%H:%M:%S %Z", "%H:%M"],
1170         "khmer" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1171         "korean" : ["%p %I시%M분 %S초 %Z", "%p %I:%M"],
1172         "kurmanji" : ["%H:%M:%S %Z", "%H:%M:%S"],
1173         "lao" : ["%H ໂມງ%M ນາທີ  %S ວິນາທີ %Z", "%H:%M"],
1174         "latin" : ["%H:%M:%S %Z", "%H:%M:%S"],
1175         "latvian" : ["%H:%M:%S %Z", "%H:%M"],
1176         "lithuanian" : ["%H:%M:%S %Z", "%H:%M"],
1177         "lowersorbian" : ["%H:%M:%S %Z", "%H:%M"],
1178         "macedonian" : ["%H:%M:%S %Z", "%H:%M"],
1179         "magyar" : ["%H:%M:%S %Z", "%H:%M"],
1180         "marathi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1181         "mongolian" : ["%H:%M:%S %Z", "%H:%M"],
1182         "naustrian" : ["%H:%M:%S %Z", "%H:%M"],
1183         "newzealand" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1184         "ngerman" : ["%H:%M:%S %Z", "%H:%M"],
1185         "norsk" : ["%H:%M:%S %Z", "%H:%M"],
1186         "nynorsk" : ["kl. %H:%M:%S %Z", "%H:%M"],
1187         "occitan" : ["%H:%M:%S %Z", "%H:%M"],
1188         "piedmontese" : ["%H:%M:%S %Z", "%H:%M:%S"],
1189         "polish" : ["%H:%M:%S %Z", "%H:%M"],
1190         "polutonikogreek" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1191         "portuguese" : ["%H:%M:%S %Z", "%H:%M"],
1192         "romanian" : ["%H:%M:%S %Z", "%H:%M"],
1193         "romansh" : ["%H:%M:%S %Z", "%H:%M"],
1194         "russian" : ["%H:%M:%S %Z", "%H:%M"],
1195         "samin" : ["%H:%M:%S %Z", "%H:%M"],
1196         "sanskrit" : ["%H:%M:%S %Z", "%H:%M"],
1197         "scottish" : ["%H:%M:%S %Z", "%H:%M"],
1198         "serbian" : ["%H:%M:%S %Z", "%H:%M"],
1199         "serbian-latin" : ["%H:%M:%S %Z", "%H:%M"],
1200         "slovak" : ["%H:%M:%S %Z", "%H:%M"],
1201         "slovene" : ["%H:%M:%S %Z", "%H:%M"],
1202         "spanish" : ["%H:%M:%S (%Z)", "%H:%M"],
1203         "spanish-mexico" : ["%H:%M:%S %Z", "%H:%M"],
1204         "swedish" : ["kl. %H:%M:%S %Z", "%H:%M"],
1205         "syriac" : ["%H:%M:%S %Z", "%H:%M"],
1206         "tamil" : ["%p %I:%M:%S %Z", "%p %I:%M"],
1207         "telugu" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1208         "thai" : ["%H นาฬิกา %M นาที  %S วินาที %Z", "%H:%M"],
1209         "tibetan" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1210         "turkish" : ["%H:%M:%S %Z", "%H:%M"],
1211         "turkmen" : ["%H:%M:%S %Z", "%H:%M"],
1212         "ukrainian" : ["%H:%M:%S %Z", "%H:%M"],
1213         "uppersorbian" : ["%H:%M:%S %Z", "%H:%M hodź."],
1214         "urdu" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1215         "vietnamese" : ["%H:%M:%S %Z", "%H:%M"],
1216         "welsh" : ["%H:%M:%S %Z", "%H:%M"]
1217     }
1218
1219     types = ["time", "fixtime", "modtime" ]
1220     i = 0
1221     i = find_token(document.header, "\\language", 0)
1222     if i == -1:
1223         # this should not happen
1224         document.warning("Malformed LyX document! No \\language header found!")
1225         return
1226     lang = get_value(document.header, "\\language", i)
1227
1228     i = 0
1229     while True:
1230         i = find_token(document.body, "\\begin_inset Info", i)
1231         if i == -1:
1232             return
1233         j = find_end_of_inset(document.body, i + 1)
1234         if j == -1:
1235             document.warning("Malformed LyX document: Could not find end of Info inset.")
1236             i = i + 1
1237             continue
1238         tp = find_token(document.body, 'type', i, j)
1239         tpv = get_quoted_value(document.body, "type", tp)
1240         if tpv not in types:
1241             i = i + 1
1242             continue
1243         arg = find_token(document.body, 'arg', i, j)
1244         argv = get_quoted_value(document.body, "arg", arg)
1245         isotime = ""
1246         dtme = datetime.now()
1247         tme = dtme.time()
1248         if tpv == "fixtime":
1249             timecomps = argv.split('@')
1250             if len(timecomps) > 1:
1251                 argv = timecomps[0]
1252                 isotime = timecomps[1]
1253                 m = re.search('(\d\d):(\d\d):(\d\d)', isotime)
1254                 if m:
1255                     tme = time(int(m.group(1)), int(m.group(2)), int(m.group(3)))
1256                 else:
1257                     m = re.search('(\d\d):(\d\d)', isotime)
1258                     if m:
1259                         tme = time(int(m.group(1)), int(m.group(2)))
1260 # FIXME if we had the path to the original document (not the one in the tmp dir),
1261 #        we could use the mtime.
1262 #        elif tpv == "moddate":
1263 #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
1264         result = ""
1265         if argv == "ISO":
1266             result = tme.isoformat()
1267         elif argv == "long":
1268             result = tme.strftime(timeformats[lang][0])
1269         elif argv == "short":
1270             result = tme.strftime(timeformats[lang][1])
1271         else:
1272             fmt = argv.replace("HH", "%H").replace("H", "%H").replace("hh", "%I").replace("h", "%I")
1273             fmt = fmt.replace("mm", "%M").replace("m", "%M").replace("ss", "%S").replace("s", "%S")
1274             fmt = fmt.replace("zzz", "%f").replace("z", "%f").replace("t", "%Z")
1275             fmt = fmt.replace("AP", "%p").replace("ap", "%p").replace("A", "%p").replace("a", "%p")
1276             fmt = fmt.replace("'", "")
1277             result = dte.strftime(fmt)
1278         document.body[i : j+1] = result
1279         i = i + 1
1280
1281
1282 def revert_namenoextinfo(document):
1283     " Merge buffer Info inset type name-noext to name. "
1284
1285     i = 0
1286     while True:
1287         i = find_token(document.body, "\\begin_inset Info", i)
1288         if i == -1:
1289             return
1290         j = find_end_of_inset(document.body, i + 1)
1291         if j == -1:
1292             document.warning("Malformed LyX document: Could not find end of Info inset.")
1293             i = i + 1
1294             continue
1295         tp = find_token(document.body, 'type', i, j)
1296         tpv = get_quoted_value(document.body, "type", tp)
1297         if tpv != "buffer":
1298             i = i + 1
1299             continue
1300         arg = find_token(document.body, 'arg', i, j)
1301         argv = get_quoted_value(document.body, "arg", arg)
1302         if argv != "name-noext":
1303             i = i + 1
1304             continue
1305         document.body[arg] = "arg \"name\""
1306         i = i + 1
1307
1308
1309 def revert_l7ninfo(document):
1310     " Revert l7n Info inset to text. "
1311
1312     i = 0
1313     while True:
1314         i = find_token(document.body, "\\begin_inset Info", i)
1315         if i == -1:
1316             return
1317         j = find_end_of_inset(document.body, i + 1)
1318         if j == -1:
1319             document.warning("Malformed LyX document: Could not find end of Info inset.")
1320             i = i + 1
1321             continue
1322         tp = find_token(document.body, 'type', i, j)
1323         tpv = get_quoted_value(document.body, "type", tp)
1324         if tpv != "l7n":
1325             i = i + 1
1326             continue
1327         arg = find_token(document.body, 'arg', i, j)
1328         argv = get_quoted_value(document.body, "arg", arg)
1329         # remove trailing colons, menu accelerator (|...) and qt accelerator (&), while keeping literal " & " 
1330         argv = argv.rstrip(':').split('|')[0].replace(" & ", "</amp;>").replace("&", "").replace("</amp;>", " & ")
1331         document.body[i : j+1] = argv
1332         i = i + 1
1333
1334
1335 def revert_listpargs(document):
1336     " Reverts listpreamble arguments to TeX-code "
1337     i = 0
1338     while True:
1339         i = find_token(document.body, "\\begin_inset Argument listpreamble:", i)
1340         if i == -1:
1341             return
1342         j = find_end_of_inset(document.body, i)
1343         # Find containing paragraph layout
1344         parent = get_containing_layout(document.body, i)
1345         if parent == False:
1346             document.warning("Malformed LyX document: Can't find parent paragraph layout")
1347             i += 1
1348             continue
1349         parbeg = parent[3]
1350         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1351         endPlain = find_end_of_layout(document.body, beginPlain)
1352         content = document.body[beginPlain + 1 : endPlain]
1353         del document.body[i:j+1]
1354         subst = ["\\begin_inset ERT", "status collapsed", "", "\\begin_layout Plain Layout",
1355                  "{"] + content + ["}", "\\end_layout", "", "\\end_inset", ""]
1356         document.body[parbeg : parbeg] = subst
1357         i += 1
1358
1359
1360 def revert_lformatinfo(document):
1361     " Revert layout format Info inset to text. "
1362
1363     i = 0
1364     while True:
1365         i = find_token(document.body, "\\begin_inset Info", i)
1366         if i == -1:
1367             return
1368         j = find_end_of_inset(document.body, i + 1)
1369         if j == -1:
1370             document.warning("Malformed LyX document: Could not find end of Info inset.")
1371             i = i + 1
1372             continue
1373         tp = find_token(document.body, 'type', i, j)
1374         tpv = get_quoted_value(document.body, "type", tp)
1375         if tpv != "lyxinfo":
1376             i = i + 1
1377             continue
1378         arg = find_token(document.body, 'arg', i, j)
1379         argv = get_quoted_value(document.body, "arg", arg)
1380         if argv != "layoutformat":
1381             i = i + 1
1382             continue
1383         # hardcoded for now
1384         document.body[i : j+1] = "69"
1385         i = i + 1
1386
1387
1388 def convert_hebrew_parentheses(document):
1389     " Don't reverse parentheses in Hebrew text"
1390     current_language = document.language
1391     for i, line in enumerate(document.body):
1392         if line.startswith('\\lang '):
1393             current_language = line[len('\\lang '):]
1394         elif line.startswith('\\end_layout'):
1395             current_language = document.language
1396         elif current_language == 'hebrew' and not line.startswith('\\'):
1397             document.body[i] = line.replace('(','\x00').replace(')','(').replace('\x00',')')
1398
1399
1400 def revert_hebrew_parentheses(document):
1401     " Store parentheses in Hebrew text reversed"
1402     # This only exists to keep the convert/revert naming convention
1403     convert_hebrew_parentheses(document)
1404
1405
1406 ##
1407 # Conversion hub
1408 #
1409
1410 supported_versions = ["2.4.0", "2.4"]
1411 convert = [
1412            [545, [convert_lst_literalparam]],
1413            [546, []],
1414            [547, []],
1415            [548, []],
1416            [549, []],
1417            [550, [convert_fontenc]],
1418            [551, []],
1419            [552, []],
1420            [553, []],
1421            [554, []],
1422            [555, []],
1423            [556, []],
1424            [557, [convert_vcsinfo]],
1425            [558, [removeFrontMatterStyles]],
1426            [559, []],
1427            [560, []],
1428            [561, [convert_latexFonts]], # Handle dejavu, ibmplex fonts in GUI
1429            [562, []],
1430            [563, []],
1431            [564, []],
1432            [565, [convert_AdobeFonts]], # Handle adobe fonts in GUI
1433            [566, [convert_hebrew_parentheses]],
1434           ]
1435
1436 revert =  [
1437            [565, [revert_hebrew_parentheses]],
1438            [564, [revert_AdobeFonts]],
1439            [563, [revert_lformatinfo]],
1440            [562, [revert_listpargs]],
1441            [561, [revert_l7ninfo]],
1442            [560, [revert_latexFonts]], # Handle dejavu, ibmplex fonts in user preamble
1443            [559, [revert_timeinfo, revert_namenoextinfo]],
1444            [558, [revert_dateinfo]],
1445            [557, [addFrontMatterStyles]],
1446            [556, [revert_vcsinfo]],
1447            [555, [revert_bibencoding]],
1448            [554, [revert_vcolumns]],
1449            [553, [revert_stretchcolumn]],
1450            [552, [revert_tuftecite]],
1451            [551, [revert_floatpclass, revert_floatalignment]],
1452            [550, [revert_nospellcheck]],
1453            [549, [revert_fontenc]],
1454            [548, []],# dummy format change
1455            [547, [revert_lscape]],
1456            [546, [revert_xcharter]],
1457            [545, [revert_paratype]],
1458            [544, [revert_lst_literalparam]]
1459           ]
1460
1461
1462 if __name__ == "__main__":
1463     pass