]> git.lyx.org Git - features.git/blob - lib/lyx2lyx/lyx_2_4.py
5cdcc3c692ce83f0bb1277a1a995ad522735e66b
[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, del_token, find_end_of_inset,
30     find_end_of_layout, find_token, find_token_backwards, find_token_exact,
31     find_re, get_bool_value,
32     get_containing_layout, get_option_value, get_value, get_quoted_value)
33 #    del_value, del_complete_lines,
34 #    find_complete_lines, find_end_of,
35 #    find_re, find_substring,
36 #    get_containing_inset,
37 #    is_in_inset, set_bool_value
38 #    find_tokens, check_token
39
40 from lyx2lyx_tools import (put_cmd_in_ert, add_to_preamble, insert_to_preamble, lyx2latex,
41                            revert_language, revert_flex_inset, str2bool)
42 #  revert_font_attrs, latex_length
43 #  get_ert, lyx2verbatim, length_in_bp, convert_info_insets
44 #  revert_flex_inset, hex2ratio
45
46 ####################################################################
47 # Private helper functions
48
49 def add_preamble_fonts(document, fontmap):
50     " Add collected font-packages with their option to user-preamble"
51
52     for pkg in fontmap:
53         if len(fontmap[pkg]) > 0:
54             xoption = "[" + ",".join(fontmap[pkg]) + "]"
55         else:
56             xoption = ""
57         preamble = "\\usepackage%s{%s}" % (xoption, pkg)
58         add_to_preamble(document, [preamble])
59
60
61 def createkey(pkg, options):
62     options.sort()
63     return pkg + ':' + "-".join(options)
64
65 class fontinfo:
66     def __init__(self):
67         self.fontname = None    # key into font2pkgmap
68         self.fonttype = None    # roman,sans,typewriter,math
69         self.scaletype = None   # None,sf,tt
70         self.scaleopt = None    # None, 'scaled', 'scale'
71         self.scaleval = 1
72         self.package = None
73         self.options = []
74         self.pkgkey = None      # key into pkg2fontmap
75         self.osfopt = None      # None, string
76         self.osfdef = "false"   # "false" or "true"
77
78     def addkey(self):
79         self.pkgkey = createkey(self.package, self.options)
80
81 class fontmapping:
82     def __init__(self):
83         self.font2pkgmap = dict()
84         self.pkg2fontmap = dict()
85         self.pkginmap = dict()  # defines, if a map for package exists
86
87     def expandFontMapping(self, font_list, font_type, scale_type, pkg, scaleopt = None, osfopt = None, osfdef = "false"):
88         " Expand fontinfo mapping"
89         #
90         # fontlist:    list of fontnames, each element
91         #              may contain a ','-separated list of needed options
92         #              like e.g. 'IBMPlexSansCondensed,condensed'
93         # font_type:   one of 'roman', 'sans', 'typewriter', 'math'
94         # scale_type:  one of None, 'sf', 'tt'
95         # pkg:         package defining the font. Defaults to fontname if None
96         # scaleopt:    one of None, 'scale', 'scaled', or some other string
97         #              to be used in scale option (e.g. scaled=0.7)
98         # osfopt:      None or some other string to be used in osf option
99         # osfdef:      "true" if osf is default
100         for fl in font_list:
101             fe = fontinfo()
102             fe.fonttype = font_type
103             fe.scaletype = scale_type
104             flt = fl.split(",")
105             font_name = flt[0]
106             fe.fontname = font_name
107             fe.options = flt[1:]
108             fe.scaleopt = scaleopt
109             fe.osfopt = osfopt
110             fe.osfdef = osfdef
111             if pkg == None:
112                 fe.package = font_name
113             else:
114                 fe.package = pkg
115             fe.addkey()
116             self.font2pkgmap[font_name] = fe
117             if fe.pkgkey in self.pkg2fontmap:
118                 # Repeated the same entry? Check content
119                 if self.pkg2fontmap[fe.pkgkey] != font_name:
120                     document.error("Something is wrong in pkgname+options <-> fontname mapping")
121             self.pkg2fontmap[fe.pkgkey] = font_name
122             self.pkginmap[fe.package] = 1
123
124     def getfontname(self, pkg, options):
125         options.sort()
126         pkgkey = createkey(pkg, options)
127         if not pkgkey in self.pkg2fontmap:
128             return None
129         fontname = self.pkg2fontmap[pkgkey]
130         if not fontname in self.font2pkgmap:
131             document.error("Something is wrong in pkgname+options <-> fontname mapping")
132             return None
133         if pkgkey == self.font2pkgmap[fontname].pkgkey:
134             return fontname
135         return None
136
137 def createFontMapping(fontlist):
138     # Create info for known fonts for the use in
139     #   convert_latexFonts() and
140     #   revert_latexFonts()
141     #
142     # * Would be more handy to parse latexFonts file,
143     #   but the path to this file is unknown
144     # * For now, add DejaVu and IBMPlex only.
145     # * Expand, if desired
146     fm = fontmapping()
147     for font in fontlist:
148         if font == 'DejaVu':
149             fm.expandFontMapping(['DejaVuSerif', 'DejaVuSerifCondensed'], "roman", None, None)
150             fm.expandFontMapping(['DejaVuSans','DejaVuSansCondensed'], "sans", "sf", None, "scaled")
151             fm.expandFontMapping(['DejaVuSansMono'], "typewriter", "tt", None, "scaled")
152         elif font == 'IBM':
153             fm.expandFontMapping(['IBMPlexSerif', 'IBMPlexSerifThin,thin',
154                                   'IBMPlexSerifExtraLight,extralight', 'IBMPlexSerifLight,light',
155                                   'IBMPlexSerifSemibold,semibold'],
156                                  "roman", None, "plex-serif")
157             fm.expandFontMapping(['IBMPlexSans','IBMPlexSansCondensed,condensed',
158                                   'IBMPlexSansThin,thin', 'IBMPlexSansExtraLight,extralight',
159                                   'IBMPlexSansLight,light', 'IBMPlexSansSemibold,semibold'],
160                                  "sans", "sf", "plex-sans", "scale")
161             fm.expandFontMapping(['IBMPlexMono', 'IBMPlexMonoThin,thin',
162                                   'IBMPlexMonoExtraLight,extralight', 'IBMPlexMonoLight,light',
163                                   'IBMPlexMonoSemibold,semibold'],
164                                  "typewriter", "tt", "plex-mono", "scale")
165         elif font == 'Adobe':
166             fm.expandFontMapping(['ADOBESourceSerifPro'], "roman", None, "sourceserifpro", None, "osf")
167             fm.expandFontMapping(['ADOBESourceSansPro'], "sans", "sf", "sourcesanspro", "scaled", "osf")
168             fm.expandFontMapping(['ADOBESourceCodePro'], "typewriter", "tt", "sourcecodepro", "scaled", "osf")
169         elif font == 'Noto':
170             fm.expandFontMapping(['NotoSerifRegular,regular', 'NotoSerifMedium,medium',
171                                   'NotoSerifThin,thin', 'NotoSerifLight,light',
172                                   'NotoSerifExtralight,extralight'],
173                                   "roman", None, "noto-serif", None, "osf")
174             fm.expandFontMapping(['NotoSansRegular,regular', 'NotoSansMedium,medium',
175                                   'NotoSansThin,thin', 'NotoSansLight,light',
176                                   'NotoSansExtralight,extralight'],
177                                   "sans", "sf", "noto-sans", "scaled")
178             fm.expandFontMapping(['NotoMonoRegular,regular'], "typewriter", "tt", "noto-mono", "scaled")
179         elif font == 'Cantarell':
180             fm.expandFontMapping(['cantarell,defaultsans'],
181                                   "sans", "sf", "cantarell", "scaled", "oldstyle")
182         elif font == 'Chivo':
183             fm.expandFontMapping(['ChivoThin,thin', 'ChivoLight,light',
184                                   'Chivo,regular', 'ChivoMedium,medium'],
185                                   "sans", "sf", "Chivo", "scale", "oldstyle")
186         elif font == 'CrimsonPro':
187             fm.expandFontMapping(['CrimsonPro', 'CrimsonProExtraLight,extralight', 'CrimsonProLight,light',
188                                   'CrimsonProMedium,medium'],
189                                   "roman", None, "CrimsonPro", None, "lf", "true")
190         elif font == 'Fira':
191             fm.expandFontMapping(['FiraSans', 'FiraSansBook,book',
192                                   'FiraSansThin,thin', 'FiraSansLight,light',
193                                   'FiraSansExtralight,extralight',
194                                   'FiraSansUltralight,ultralight'],
195                                   "sans", "sf", "FiraSans", "scaled", "lf", "true")
196             fm.expandFontMapping(['FiraMono'], "typewriter", "tt", "FiraMono", "scaled", "lf", "true")
197     return fm
198
199 def convert_fonts(document, fm, osfoption = "osf"):
200     " Handle font definition (LaTeX preamble -> native) "
201
202     rpkg = re.compile(r'^\\usepackage(\[([^\]]*)\])?\{([^\}]+)\}')
203     rscaleopt = re.compile(r'^scaled?=(.*)')
204
205     # Check whether we go beyond font option feature introduction
206     haveFontOpts = document.end_format > 580
207
208     i = 0
209     while i < len(document.preamble):
210         i = find_re(document.preamble, rpkg, i+1)
211         if i == -1:
212             return
213         mo = rpkg.search(document.preamble[i])
214         if mo == None or mo.group(2) == None:
215             options = []
216         else:
217             options = mo.group(2).replace(' ', '').split(",")
218         pkg = mo.group(3)
219         o = 0
220         oscale = 1
221         has_osf = False
222         while o < len(options):
223             if options[o] == osfoption:
224                 has_osf = True
225                 del options[o]
226                 continue
227             mo = rscaleopt.search(options[o])
228             if mo == None:
229                 o += 1
230                 continue
231             oscale = mo.group(1)
232             del options[o]
233             continue
234
235         if not pkg in fm.pkginmap:
236             continue
237         # determine fontname
238         fn = None
239         if haveFontOpts:
240             # Try with name-option combination first
241             # (only one default option supported currently)
242             o = 0
243             while o < len(options):
244                 opt = options[o]
245                 fn = fm.getfontname(pkg, [opt])
246                 if fn != None:
247                     del options[o]
248                     break
249                 o += 1
250                 continue
251             if fn == None:
252                 fn = fm.getfontname(pkg, [])
253         else:
254             fn = fm.getfontname(pkg, options)
255         if fn == None:
256             continue
257         del document.preamble[i]
258         fontinfo = fm.font2pkgmap[fn]
259         if fontinfo.scaletype == None:
260             fontscale = None
261         else:
262             fontscale = "\\font_" + fontinfo.scaletype + "_scale"
263             fontinfo.scaleval = oscale
264         if (has_osf and fontinfo.osfdef == "false") or (not has_osf and fontinfo.osfdef == "true"):
265             if fontinfo.osfopt == None:
266                 options.extend(osfoption)
267                 continue
268             osf = find_token(document.header, "\\font_osf false")
269             osftag = "\\font_osf"
270             if osf == -1 and fontinfo.fonttype != "math":
271                 # Try with newer format
272                 osftag = "\\font_" + fontinfo.fonttype + "_osf"
273                 osf = find_token(document.header, osftag + " false")
274             if osf != -1:
275                 document.header[osf] = osftag + " true"
276         if i > 0 and document.preamble[i-1] == "% Added by lyx2lyx":
277             del document.preamble[i-1]
278             i -= 1
279         if fontscale != None:
280             j = find_token(document.header, fontscale, 0)
281             if j != -1:
282                 val = get_value(document.header, fontscale, j)
283                 vals = val.split()
284                 scale = "100"
285                 if oscale != None:
286                     scale = "%03d" % int(float(oscale) * 100)
287                 document.header[j] = fontscale + " " + scale + " " + vals[1]
288         ft = "\\font_" + fontinfo.fonttype
289         j = find_token(document.header, ft, 0)
290         if j != -1:
291             val = get_value(document.header, ft, j)
292             words = val.split() # ! splits also values like '"DejaVu Sans"'
293             words[0] = '"' + fn + '"'
294             document.header[j] = ft + ' ' + ' '.join(words)
295         if haveFontOpts and fontinfo.fonttype != "math":
296             fotag = "\\font_" + fontinfo.fonttype + "_opts"
297             fo = find_token(document.header, fotag)
298             if fo != -1:
299                 document.header[fo] = fotag + " \"" + ",".join(options) + "\""
300             else:
301                 # Sensible place to insert tag
302                 fo = find_token(document.header, "\\font_sf_scale")
303                 if fo == -1:
304                     document.warning("Malformed LyX document! Missing \\font_sf_scale")
305                 else:
306                     document.header.insert(fo, fotag + " \"" + ",".join(options) + "\"")
307
308
309
310 def revert_fonts(document, fm, fontmap, OnlyWithXOpts = False, WithXOpts = False):
311     " Revert native font definition to LaTeX "
312     # fonlist := list of fonts created from the same package
313     # Empty package means that the font-name is the same as the package-name
314     # fontmap (key = package, val += found options) will be filled
315     # and used later in add_preamble_fonts() to be added to user-preamble
316
317     rfontscale = re.compile(r'^\s*(\\font_(roman|sans|typewriter|math))\s+')
318     rscales = re.compile(r'^\s*(\d+)\s+(\d+)')
319     i = 0
320     while i < len(document.header):
321         i = find_re(document.header, rfontscale, i+1)
322         if (i == -1):
323             return True
324         mo = rfontscale.search(document.header[i])
325         if mo == None:
326             continue
327         ft = mo.group(1)    # 'roman', 'sans', 'typewriter', 'math'
328         val = get_value(document.header, ft, i)
329         words = val.split(' ')     # ! splits also values like '"DejaVu Sans"'
330         font = words[0].strip('"') # TeX font name has no whitespace
331         if not font in fm.font2pkgmap:
332             continue
333         fontinfo = fm.font2pkgmap[font]
334         val = fontinfo.package
335         if not val in fontmap:
336             fontmap[val] = []
337         x = -1
338         if OnlyWithXOpts or WithXOpts:
339             if ft == "\\font_math":
340                 return False
341             regexp = re.compile(r'^\s*(\\font_roman_opts)\s+')
342             if ft == "\\font_sans":
343                 regexp = re.compile(r'^\s*(\\font_sans_opts)\s+')
344             elif ft == "\\font_typewriter":
345                 regexp = re.compile(r'^\s*(\\font_typewriter_opts)\s+')
346             x = find_re(document.header, regexp, 0)
347             if x == -1 and OnlyWithXOpts:
348                 return False
349
350             if x != -1:
351                 # We need to use this regex since split() does not handle quote protection
352                 xopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
353                 opts = xopts[1].strip('"').split(",")
354                 fontmap[val].extend(opts)
355                 del document.header[x]
356         words[0] = '"default"'
357         document.header[i] = ft + ' ' + ' '.join(words)
358         if fontinfo.scaleopt != None:
359             xval =  get_value(document.header, "\\font_" + fontinfo.scaletype + "_scale", 0)
360             mo = rscales.search(xval)
361             if mo != None:
362                 xval1 = mo.group(1)
363                 xval2 = mo.group(2)
364                 if xval1 != "100":
365                     # set correct scale option
366                     fontmap[val].extend([fontinfo.scaleopt + "=" + format(float(xval1) / 100, '.2f')])
367         if fontinfo.osfopt != None:
368             oldval = "true"
369             if fontinfo.osfdef == "true":
370                 oldval = "false"
371             osf = find_token(document.header, "\\font_osf " + oldval)
372             if osf == -1 and ft != "\\font_math":
373                 # Try with newer format
374                 osftag = "\\font_roman_osf " + oldval
375                 if ft == "\\font_sans":
376                     osftag = "\\font_sans_osf " + oldval
377                 elif ft == "\\font_typewriter":
378                     osftag = "\\font_typewriter_osf " + oldval
379                 osf = find_token(document.header, osftag)
380             if osf != -1:
381                 fontmap[val].extend([fontinfo.osfopt])
382         if len(fontinfo.options) > 0:
383             fontmap[val].extend(fontinfo.options)
384     return True
385
386 ###############################################################################
387 ###
388 ### Conversion and reversion routines
389 ###
390 ###############################################################################
391
392 def convert_inputencoding_namechange(document):
393     " Rename inputencoding settings. "
394     i = find_token(document.header, "\\inputencoding", 0)
395     if i == -1:
396         return
397     s = document.header[i].replace("auto", "auto-legacy")
398     document.header[i] = s.replace("default", "auto-legacy-plain")
399
400 def revert_inputencoding_namechange(document):
401     " Rename inputencoding settings. "
402     i = find_token(document.header, "\\inputencoding", 0)
403     if i == -1:
404         return
405     s = document.header[i].replace("auto-legacy-plain", "default")
406     document.header[i] = s.replace("auto-legacy", "auto")
407
408 def convert_notoFonts(document):
409     " Handle Noto fonts definition to LaTeX "
410
411     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
412         fm = createFontMapping(['Noto'])
413         convert_fonts(document, fm)
414
415 def revert_notoFonts(document):
416     " Revert native Noto font definition to LaTeX "
417
418     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
419         fontmap = dict()
420         fm = createFontMapping(['Noto'])
421         if revert_fonts(document, fm, fontmap):
422             add_preamble_fonts(document, fontmap)
423
424 def convert_latexFonts(document):
425     " Handle DejaVu and IBMPlex fonts definition to LaTeX "
426
427     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
428         fm = createFontMapping(['DejaVu', 'IBM'])
429         convert_fonts(document, fm)
430
431 def revert_latexFonts(document):
432     " Revert native DejaVu font definition to LaTeX "
433
434     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
435         fontmap = dict()
436         fm = createFontMapping(['DejaVu', 'IBM'])
437         if revert_fonts(document, fm, fontmap):
438             add_preamble_fonts(document, fontmap)
439
440 def convert_AdobeFonts(document):
441     " Handle Adobe Source fonts definition to LaTeX "
442
443     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
444         fm = createFontMapping(['Adobe'])
445         convert_fonts(document, fm)
446
447 def revert_AdobeFonts(document):
448     " Revert Adobe Source font definition to LaTeX "
449
450     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
451         fontmap = dict()
452         fm = createFontMapping(['Adobe'])
453         if revert_fonts(document, fm, fontmap):
454             add_preamble_fonts(document, fontmap)
455
456 def removeFrontMatterStyles(document):
457     " Remove styles Begin/EndFrontmatter"
458
459     layouts = ['BeginFrontmatter', 'EndFrontmatter']
460     tokenend = len('\\begin_layout ')
461     i = 0
462     while True:
463         i = find_token_exact(document.body, '\\begin_layout ', i+1)
464         if i == -1:
465             return
466         layout = document.body[i][tokenend:].strip()
467         if layout not in layouts:
468             continue
469         j = find_end_of_layout(document.body, i)
470         if j == -1:
471             document.warning("Malformed LyX document: Can't find end of layout at line %d" % i)
472             continue
473         while document.body[j+1].strip() == '':
474             j += 1
475         document.body[i:j+1] = []
476
477 def addFrontMatterStyles(document):
478     " Use styles Begin/EndFrontmatter for elsarticle"
479
480     if document.textclass != "elsarticle":
481         return
482
483     def insertFrontmatter(prefix, line):
484         above = line
485         while above > 0 and document.body[above-1].strip() == '':
486             above -= 1
487         below = line
488         while document.body[below].strip() == '':
489             below += 1
490         document.body[above:below] = ['', '\\begin_layout ' + prefix + 'Frontmatter',
491                                     '\\begin_inset Note Note',
492                                     'status open', '',
493                                     '\\begin_layout Plain Layout',
494                                     'Keep this empty!',
495                                     '\\end_layout', '',
496                                     '\\end_inset', '', '',
497                                     '\\end_layout', '']
498
499     layouts = ['Title', 'Title footnote', 'Author', 'Author footnote',
500                 'Corresponding author', 'Address', 'Email', 'Abstract', 'Keywords']
501     tokenend = len('\\begin_layout ')
502     first = -1
503     i = 0
504     while True:
505         i = find_token_exact(document.body, '\\begin_layout ', i+1)
506         if i == -1:
507             break
508         layout = document.body[i][tokenend:].strip()
509         if layout not in layouts:
510             continue
511         k = find_end_of_layout(document.body, i)
512         if k == -1:
513             document.warning("Malformed LyX document: Can't find end of layout at line %d" % i)
514             continue
515         if first == -1:
516             first = i
517         i = k
518     if first == -1:
519         return
520     insertFrontmatter('End', k+1)
521     insertFrontmatter('Begin', first)
522
523
524 def convert_lst_literalparam(document):
525     " Add param literal to include inset "
526
527     i = 0
528     while True:
529         i = find_token(document.body, '\\begin_inset CommandInset include', i+1)
530         if i == -1:
531             break
532         j = find_end_of_inset(document.body, i)
533         if j == -1:
534             document.warning("Malformed LyX document: Can't find end of command inset at line %d" % i)
535             continue
536         while i < j and document.body[i].strip() != '':
537             i += 1
538         document.body.insert(i, 'literal "true"')
539
540
541 def revert_lst_literalparam(document):
542     " Remove param literal from include inset "
543
544     i = 0
545     while True:
546         i = find_token(document.body, '\\begin_inset CommandInset include', i+1)
547         if i == -1:
548             break
549         j = find_end_of_inset(document.body, i)
550         if j == -1:
551             document.warning("Malformed LyX document: Can't find end of include inset at line %d" % i)
552             continue
553         del_token(document.body, 'literal', i, j)
554
555
556 def revert_paratype(document):
557     " Revert ParaType font definitions to LaTeX "
558
559     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
560         preamble = ""
561         i1 = find_token(document.header, "\\font_roman \"PTSerif-TLF\"", 0)
562         i2 = find_token(document.header, "\\font_sans \"default\"", 0)
563         i3 = find_token(document.header, "\\font_typewriter \"default\"", 0)
564         j = find_token(document.header, "\\font_sans \"PTSans-TLF\"", 0)
565
566         sf_scale = 100.0
567         sfval = find_token(document.header, "\\font_sf_scale", 0)
568         if sfval == -1:
569             document.warning("Malformed LyX document: Missing \\font_sf_scale.")
570         else:
571             sfscale = document.header[sfval].split()
572             val = sfscale[1]
573             sfscale[1] = "100"
574             document.header[sfval] = " ".join(sfscale)
575             try:
576                 # float() can throw
577                 sf_scale = float(val)
578             except:
579                 document.warning("Invalid font_sf_scale value: " + val)
580
581         sfoption = ""
582         if sf_scale != "100.0":
583             sfoption = "scaled=" + str(sf_scale / 100.0)
584         k = find_token(document.header, "\\font_typewriter \"PTMono-TLF\"", 0)
585         ttval = get_value(document.header, "\\font_tt_scale", 0)
586         # cutoff " 100"
587         ttval = ttval[:-4]
588         ttoption = ""
589         if ttval != "100":
590             ttoption = "scaled=" + format(float(ttval) / 100, '.2f')
591         if i1 != -1 and i2 != -1 and i3!= -1:
592             add_to_preamble(document, ["\\usepackage{paratype}"])
593         else:
594             if i1!= -1:
595                 add_to_preamble(document, ["\\usepackage{PTSerif}"])
596                 document.header[i1] = document.header[i1].replace("PTSerif-TLF", "default")
597             if j!= -1:
598                 if sfoption != "":
599                     add_to_preamble(document, ["\\usepackage[" + sfoption + "]{PTSans}"])
600                 else:
601                     add_to_preamble(document, ["\\usepackage{PTSans}"])
602                 document.header[j] = document.header[j].replace("PTSans-TLF", "default")
603             if k!= -1:
604                 if ttoption != "":
605                     add_to_preamble(document, ["\\usepackage[" + ttoption + "]{PTMono}"])
606                 else:
607                     add_to_preamble(document, ["\\usepackage{PTMono}"])
608                 document.header[k] = document.header[k].replace("PTMono-TLF", "default")
609
610
611 def revert_xcharter(document):
612     " Revert XCharter font definitions to LaTeX "
613
614     i = find_token(document.header, "\\font_roman \"xcharter\"", 0)
615     if i == -1:
616         return
617
618     # replace unsupported font setting
619     document.header[i] = document.header[i].replace("xcharter", "default")
620     # no need for preamble code with system fonts
621     if get_bool_value(document.header, "\\use_non_tex_fonts"):
622         return
623
624     # transfer old style figures setting to package options
625     j = find_token(document.header, "\\font_osf true")
626     if j != -1:
627         options = "[osf]"
628         document.header[j] = "\\font_osf false"
629     else:
630         options = ""
631     if i != -1:
632         add_to_preamble(document, ["\\usepackage%s{XCharter}"%options])
633
634
635 def revert_lscape(document):
636     " Reverts the landscape environment (Landscape module) to TeX-code "
637
638     if not "landscape" in document.get_module_list():
639         return
640
641     i = 0
642     while True:
643         i = find_token(document.body, "\\begin_inset Flex Landscape", i+1)
644         if i == -1:
645             break
646         j = find_end_of_inset(document.body, i)
647         if j == -1:
648             document.warning("Malformed LyX document: Can't find end of Landscape inset")
649             continue
650
651         if document.body[i] == "\\begin_inset Flex Landscape (Floating)":
652             document.body[j - 2 : j + 1] = put_cmd_in_ert("\\end{landscape}}")
653             document.body[i : i + 4] = put_cmd_in_ert("\\afterpage{\\begin{landscape}")
654             add_to_preamble(document, ["\\usepackage{afterpage}"])
655         else:
656             document.body[j - 2 : j + 1] = put_cmd_in_ert("\\end{landscape}")
657             document.body[i : i + 4] = put_cmd_in_ert("\\begin{landscape}")
658
659         add_to_preamble(document, ["\\usepackage{pdflscape}"])
660     document.del_module("landscape")
661
662
663 def convert_fontenc(document):
664     " Convert default fontenc setting "
665
666     i = find_token(document.header, "\\fontencoding global", 0)
667     if i == -1:
668         return
669
670     document.header[i] = document.header[i].replace("global", "auto")
671
672
673 def revert_fontenc(document):
674     " Revert default fontenc setting "
675
676     i = find_token(document.header, "\\fontencoding auto", 0)
677     if i == -1:
678         return
679
680     document.header[i] = document.header[i].replace("auto", "global")
681
682
683 def revert_nospellcheck(document):
684     " Remove nospellcheck font info param "
685
686     i = 0
687     while True:
688         i = find_token(document.body, '\\nospellcheck', i)
689         if i == -1:
690             return
691         del document.body[i]
692
693
694 def revert_floatpclass(document):
695     " Remove float placement params 'document' and 'class' "
696
697     del_token(document.header, "\\float_placement class")
698
699     i = 0
700     while True:
701         i = find_token(document.body, '\\begin_inset Float', i+1)
702         if i == -1:
703             break
704         j = find_end_of_inset(document.body, i)
705         k = find_token(document.body, 'placement class', i, i + 2)
706         if k == -1:
707             k = find_token(document.body, 'placement document', i, i + 2)
708             if k != -1:
709                 del document.body[k]
710             continue
711         del document.body[k]
712
713
714 def revert_floatalignment(document):
715     " Remove float alignment params "
716
717     galignment = get_value(document.header, "\\float_alignment", delete=True)
718
719     i = 0
720     while True:
721         i = find_token(document.body, '\\begin_inset Float', i+1)
722         if i == -1:
723             break
724         j = find_end_of_inset(document.body, i)
725         if j == -1:
726             document.warning("Malformed LyX document: Can't find end of inset at line " + str(i))
727             continue
728         k = find_token(document.body, 'alignment', i, i+4)
729         if k == -1:
730             i = j
731             continue
732         alignment = get_value(document.body, "alignment", k)
733         if alignment == "document":
734             alignment = galignment
735         del document.body[k]
736         l = find_token(document.body, "\\begin_layout Plain Layout", i, j)
737         if l == -1:
738             document.warning("Can't find float layout!")
739             continue
740         alcmd = []
741         if alignment == "left":
742             alcmd = put_cmd_in_ert("\\raggedright{}")
743         elif alignment == "center":
744             alcmd = put_cmd_in_ert("\\centering{}")
745         elif alignment == "right":
746             alcmd = put_cmd_in_ert("\\raggedleft{}")
747         if len(alcmd) > 0:
748             document.body[l+1:l+1] = alcmd
749         i = j
750
751 def revert_tuftecite(document):
752     " Revert \cite commands in tufte classes "
753
754     tufte = ["tufte-book", "tufte-handout"]
755     if document.textclass not in tufte:
756         return
757
758     i = 0
759     while (True):
760         i = find_token(document.body, "\\begin_inset CommandInset citation", i+1)
761         if i == -1:
762             break
763         j = find_end_of_inset(document.body, i)
764         if j == -1:
765             document.warning("Can't find end of citation inset at line %d!!" %(i))
766             continue
767         k = find_token(document.body, "LatexCommand", i, j)
768         if k == -1:
769             document.warning("Can't find LatexCommand for citation inset at line %d!" %(i))
770             i = j
771             continue
772         cmd = get_value(document.body, "LatexCommand", k)
773         if cmd != "cite":
774             i = j
775             continue
776         pre = get_quoted_value(document.body, "before", i, j)
777         post = get_quoted_value(document.body, "after", i, j)
778         key = get_quoted_value(document.body, "key", i, j)
779         if not key:
780             document.warning("Citation inset at line %d does not have a key!" %(i))
781             key = "???"
782         # Replace command with ERT
783         res = "\\cite"
784         if pre:
785             res += "[" + pre + "]"
786         if post:
787             res += "[" + post + "]"
788         elif pre:
789             res += "[]"
790         res += "{" + key + "}"
791         document.body[i:j+1] = put_cmd_in_ert([res])
792         i = j
793
794
795 def convert_aaencoding(document):
796     " Convert default document option due to encoding change in aa class. "
797
798     if document.textclass != "aa":
799         return
800
801     i = 0
802
803     i = find_token(document.header, "\\use_default_options true", i)
804     if i == -1:
805         return
806     j = find_token(document.header, "\\inputencoding", 0)
807     if j == -1:
808         document.warning("Malformed LyX Document! Missing \\inputencoding header.")
809         return
810     val = get_value(document.header, "\\inputencoding", j)
811     if val == "auto" or val == "latin9":
812         document.header[i] = "\\use_default_options false"
813         k = find_token(document.header, "\\options", 0)
814         if k == -1:
815             document.header.insert(i, "\\options latin9")
816         else:
817             document.header[k] = document.header[k] + ",latin9"
818
819
820 def revert_aaencoding(document):
821     " Revert default document option due to encoding change in aa class. "
822
823     if document.textclass != "aa":
824         return
825
826     i = 0
827
828     i = find_token(document.header, "\\use_default_options true", i)
829     if i == -1:
830         return
831     j = find_token(document.header, "\\inputencoding", 0)
832     if j == -1:
833         document.warning("Malformed LyX Document! Missing \\inputencoding header.")
834         return
835     val = get_value(document.header, "\\inputencoding", j)
836     if val == "utf8":
837         document.header[i] = "\\use_default_options false"
838         k = find_token(document.header, "\\options", 0)
839         if k == -1:
840             document.header.insert(i, "\\options utf8")
841         else:
842             document.header[k] = document.header[k] + ",utf8"
843             
844
845
846 def revert_stretchcolumn(document):
847     " We remove the column varwidth flags or everything else will become a mess. "
848     i = 0
849     while True:
850         i = find_token(document.body, "\\begin_inset Tabular", i+1)
851         if i == -1:
852             return
853         j = find_end_of_inset(document.body, i+1)
854         if j == -1:
855             document.warning("Malformed LyX document: Could not find end of tabular.")
856             continue
857         for k in range(i, j):
858             if re.search('^<column.*varwidth="[^"]+".*>$', document.body[k]):
859                 document.warning("Converting 'tabularx'/'xltabular' table to normal table.")
860                 document.body[k] = document.body[k].replace(' varwidth="true"', '')
861
862
863 def revert_vcolumns(document):
864     " Revert standard columns with line breaks etc. "
865     i = 0
866     needvarwidth = False
867     needarray = False
868     try:
869         while True:
870             i = find_token(document.body, "\\begin_inset Tabular", i+1)
871             if i == -1:
872                 return
873             j = find_end_of_inset(document.body, i)
874             if j == -1:
875                 document.warning("Malformed LyX document: Could not find end of tabular.")
876                 continue
877
878             # Collect necessary column information
879             m = i + 1
880             nrows = int(document.body[i+1].split('"')[3])
881             ncols = int(document.body[i+1].split('"')[5])
882             col_info = []
883             for k in range(ncols):
884                 m = find_token(document.body, "<column", m)
885                 width = get_option_value(document.body[m], 'width')
886                 varwidth = get_option_value(document.body[m], 'varwidth')
887                 alignment = get_option_value(document.body[m], 'alignment')
888                 special = get_option_value(document.body[m], 'special')
889                 col_info.append([width, varwidth, alignment, special, m])
890
891             # Now parse cells
892             m = i + 1
893             lines = []
894             for row in range(nrows):
895                 for col in range(ncols):
896                     m = find_token(document.body, "<cell", m)
897                     multicolumn = get_option_value(document.body[m], 'multicolumn')
898                     multirow = get_option_value(document.body[m], 'multirow')
899                     width = get_option_value(document.body[m], 'width')
900                     rotate = get_option_value(document.body[m], 'rotate')
901                     # Check for: linebreaks, multipars, non-standard environments
902                     begcell = m
903                     endcell = find_token(document.body, "</cell>", begcell)
904                     vcand = False
905                     if find_token(document.body, "\\begin_inset Newline", begcell, endcell) != -1:
906                         vcand = True
907                     elif count_pars_in_inset(document.body, begcell + 2) > 1:
908                         vcand = True
909                     elif get_value(document.body, "\\begin_layout", begcell) != "Plain Layout":
910                         vcand = True
911                     if vcand and rotate == "" and ((multicolumn == "" and multirow == "") or width == ""):
912                         if col_info[col][0] == "" and col_info[col][1] == "" and col_info[col][3] == "":
913                             needvarwidth = True
914                             alignment = col_info[col][2]
915                             col_line = col_info[col][4]
916                             vval = ""
917                             if alignment == "center":
918                                 vval = ">{\\centering}"
919                             elif  alignment == "left":
920                                 vval = ">{\\raggedright}"
921                             elif alignment == "right":
922                                 vval = ">{\\raggedleft}"
923                             if vval != "":
924                                 needarray = True
925                             vval += "V{\\linewidth}"
926
927                             document.body[col_line] = document.body[col_line][:-1] + " special=\"" + vval + "\">"
928                             # ERT newlines and linebreaks (since LyX < 2.4 automatically inserts parboxes
929                             # with newlines, and we do not want that)
930                             while True:
931                                 endcell = find_token(document.body, "</cell>", begcell)
932                                 linebreak = False
933                                 nl = find_token(document.body, "\\begin_inset Newline newline", begcell, endcell)
934                                 if nl == -1:
935                                     nl = find_token(document.body, "\\begin_inset Newline linebreak", begcell, endcell)
936                                     if nl == -1:
937                                          break
938                                     linebreak = True
939                                 nle = find_end_of_inset(document.body, nl)
940                                 del(document.body[nle:nle+1])
941                                 if linebreak:
942                                     document.body[nl:nl+1] = put_cmd_in_ert("\\linebreak{}")
943                                 else:
944                                     document.body[nl:nl+1] = put_cmd_in_ert("\\\\")
945                     m += 1
946
947             i = j
948
949     finally:
950         if needarray == True:
951             add_to_preamble(document, ["\\usepackage{array}"])
952         if needvarwidth == True:
953             add_to_preamble(document, ["\\usepackage{varwidth}"])
954
955
956 def revert_bibencoding(document):
957     " Revert bibliography encoding "
958
959     # Get cite engine
960     engine = "basic"
961     i = find_token(document.header, "\\cite_engine", 0)
962     if i == -1:
963         document.warning("Malformed document! Missing \\cite_engine")
964     else:
965         engine = get_value(document.header, "\\cite_engine", i)
966
967     # Check if biblatex
968     biblatex = False
969     if engine in ["biblatex", "biblatex-natbib"]:
970         biblatex = True
971
972     # Map lyx to latex encoding names
973     encodings = {
974         "utf8" : "utf8",
975         "utf8x" : "utf8x",
976         "armscii8" : "armscii8",
977         "iso8859-1" : "latin1",
978         "iso8859-2" : "latin2",
979         "iso8859-3" : "latin3",
980         "iso8859-4" : "latin4",
981         "iso8859-5" : "iso88595",
982         "iso8859-6" : "8859-6",
983         "iso8859-7" : "iso-8859-7",
984         "iso8859-8" : "8859-8",
985         "iso8859-9" : "latin5",
986         "iso8859-13" : "latin7",
987         "iso8859-15" : "latin9",
988         "iso8859-16" : "latin10",
989         "applemac" : "applemac",
990         "cp437" : "cp437",
991         "cp437de" : "cp437de",
992         "cp850" : "cp850",
993         "cp852" : "cp852",
994         "cp855" : "cp855",
995         "cp858" : "cp858",
996         "cp862" : "cp862",
997         "cp865" : "cp865",
998         "cp866" : "cp866",
999         "cp1250" : "cp1250",
1000         "cp1251" : "cp1251",
1001         "cp1252" : "cp1252",
1002         "cp1255" : "cp1255",
1003         "cp1256" : "cp1256",
1004         "cp1257" : "cp1257",
1005         "koi8-r" : "koi8-r",
1006         "koi8-u" : "koi8-u",
1007         "pt154" : "pt154",
1008         "utf8-platex" : "utf8",
1009         "ascii" : "ascii"
1010     }
1011
1012     i = 0
1013     bibresources = []
1014     while (True):
1015         i = find_token(document.body, "\\begin_inset CommandInset bibtex", i+1)
1016         if i == -1:
1017             break
1018         j = find_end_of_inset(document.body, i)
1019         if j == -1:
1020             document.warning("Can't find end of bibtex inset at line %d!!" %(i))
1021             continue
1022         encoding = get_quoted_value(document.body, "encoding", i, j)
1023         if not encoding:
1024             continue
1025         # remove encoding line
1026         k = find_token(document.body, "encoding", i, j)
1027         if k != -1:
1028             del document.body[k]
1029         if encoding == "default":
1030             continue
1031         # Re-find inset end line
1032         j = find_end_of_inset(document.body, i)
1033         if biblatex:
1034             biblio_options = ""
1035             h = find_token(document.header, "\\biblio_options", 0)
1036             if h != -1:
1037                 biblio_options = get_value(document.header, "\\biblio_options", h)
1038                 if not "bibencoding" in biblio_options:
1039                      document.header[h] += ",bibencoding=%s" % encodings[encoding]
1040             else:
1041                 bs = find_token(document.header, "\\biblatex_bibstyle", 0)
1042                 if bs == -1:
1043                     # this should not happen
1044                     document.warning("Malformed LyX document! No \\biblatex_bibstyle header found!")
1045                 else:
1046                     document.header[bs-1 : bs-1] = ["\\biblio_options bibencoding=" + encodings[encoding]]
1047         else:
1048             document.body[j+1:j+1] = put_cmd_in_ert("\\egroup")
1049             document.body[i:i] = put_cmd_in_ert("\\bgroup\\inputencoding{" + encodings[encoding] + "}")
1050
1051         i = j
1052
1053
1054
1055 def convert_vcsinfo(document):
1056     " Separate vcs Info inset from buffer Info inset. "
1057
1058     types = {
1059         "vcs-revision" : "revision",
1060         "vcs-tree-revision" : "tree-revision",
1061         "vcs-author" : "author",
1062         "vcs-time" : "time",
1063         "vcs-date" : "date"
1064     }
1065     i = 0
1066     while True:
1067         i = find_token(document.body, "\\begin_inset Info", i+1)
1068         if i == -1:
1069             return
1070         j = find_end_of_inset(document.body, i+1)
1071         if j == -1:
1072             document.warning("Malformed LyX document: Could not find end of Info inset.")
1073             continue
1074         tp = find_token(document.body, 'type', i, j)
1075         tpv = get_quoted_value(document.body, "type", tp)
1076         if tpv != "buffer":
1077             continue
1078         arg = find_token(document.body, 'arg', i, j)
1079         argv = get_quoted_value(document.body, "arg", arg)
1080         if argv not in list(types.keys()):
1081             continue
1082         document.body[tp] = "type \"vcs\""
1083         document.body[arg] = "arg \"" + types[argv] + "\""
1084
1085
1086 def revert_vcsinfo(document):
1087     " Merge vcs Info inset to buffer Info inset. "
1088
1089     args = ["revision", "tree-revision", "author", "time", "date" ]
1090     i = 0
1091     while True:
1092         i = find_token(document.body, "\\begin_inset Info", i+1)
1093         if i == -1:
1094             return
1095         j = find_end_of_inset(document.body, i+1)
1096         if j == -1:
1097             document.warning("Malformed LyX document: Could not find end of Info inset.")
1098             continue
1099         tp = find_token(document.body, 'type', i, j)
1100         tpv = get_quoted_value(document.body, "type", tp)
1101         if tpv != "vcs":
1102             continue
1103         arg = find_token(document.body, 'arg', i, j)
1104         argv = get_quoted_value(document.body, "arg", arg)
1105         if argv not in args:
1106             document.warning("Malformed Info inset. Invalid vcs arg.")
1107             continue
1108         document.body[tp] = "type \"buffer\""
1109         document.body[arg] = "arg \"vcs-" + argv + "\""
1110
1111 def revert_vcsinfo_rev_abbrev(document):
1112     " Convert abbreviated revisions to regular revisions. "
1113
1114     i = 0
1115     while True:
1116         i = find_token(document.body, "\\begin_inset Info", i+1)
1117         if i == -1:
1118             return
1119         j = find_end_of_inset(document.body, i+1)
1120         if j == -1:
1121             document.warning("Malformed LyX document: Could not find end of Info inset.")
1122             continue
1123         tp = find_token(document.body, 'type', i, j)
1124         tpv = get_quoted_value(document.body, "type", tp)
1125         if tpv != "vcs":
1126             continue
1127         arg = find_token(document.body, 'arg', i, j)
1128         argv = get_quoted_value(document.body, "arg", arg)
1129         if( argv == "revision-abbrev" ):
1130             document.body[arg] = "arg \"revision\""
1131
1132 def revert_dateinfo(document):
1133     " Revert date info insets to static text. "
1134
1135 # FIXME This currently only considers the main language and uses the system locale
1136 # Ideally, it should honor context languages and switch the locale accordingly.
1137
1138     # The date formats for each language using strftime syntax:
1139     # long, short, loclong, locmedium, locshort
1140     dateformats = {
1141         "afrikaans" : ["%A, %d %B %Y", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y/%m/%d"],
1142         "albanian" : ["%A, %d %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1143         "american" : ["%A, %B %d, %Y", "%m/%d/%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1144         "amharic" : ["%A ፣%d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1145         "ancientgreek" : ["%A, %d %B %Y", "%d %b %Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1146         "arabic_arabi" : ["%A، %d %B، %Y", "%d‏/%m‏/%Y", "%d %B، %Y", "%d/%m/%Y", "%d/%m/%Y"],
1147         "arabic_arabtex" : ["%A، %d %B، %Y", "%d‏/%m‏/%Y", "%d %B، %Y", "%d/%m/%Y", "%d/%m/%Y"],
1148         "armenian" : ["%Y թ. %B %d, %A", "%d.%m.%y", "%d %B، %Y", "%d %b، %Y", "%d/%m/%Y"],
1149         "asturian" : ["%A, %d %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d %b %Y", "%d/%m/%Y"],
1150         "australian" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1151         "austrian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1152         "bahasa" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1153         "bahasam" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1154         "basque" : ["%Y(e)ko %B %d, %A", "%y/%m/%d", "%Y %B %d", "%Y %b %d", "%Y/%m/%d"],
1155         "belarusian" : ["%A, %d %B %Y г.", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1156         "bosnian" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%Y-%m-%d"],
1157         "brazilian" : ["%A, %d de %B de %Y", "%d/%m/%Y", "%d de %B de %Y", "%d de %b de %Y", "%d/%m/%Y"],
1158         "breton" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1159         "british" : ["%A, %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1160         "bulgarian" : ["%A, %d %B %Y г.", "%d.%m.%y г.", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1161         "canadian" : ["%A, %B %d, %Y", "%Y-%m-%d", "%B %d, %Y", "%d %b %Y", "%Y-%m-%d"],
1162         "canadien" : ["%A %d %B %Y", "%y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1163         "catalan" : ["%A, %d %B de %Y", "%d/%m/%y", "%d / %B / %Y", "%d / %b / %Y", "%d/%m/%Y"],
1164         "chinese-simplified" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y-%m-%d", "%y-%m-%d"],
1165         "chinese-traditional" : ["%Y年%m月%d日 %A", "%Y/%m/%d", "%Y年%m月%d日", "%Y年%m月%d日", "%y年%m月%d日"],
1166         "coptic" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1167         "croatian" : ["%A, %d. %B %Y.", "%d. %m. %Y.", "%d. %B %Y.", "%d. %b. %Y.", "%d.%m.%Y."],
1168         "czech" : ["%A %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b. %Y", "%d.%m.%Y"],
1169         "danish" : ["%A den %d. %B %Y", "%d/%m/%Y", "%d. %B %Y", "%d. %b %Y", "%d/%m/%Y"],
1170         "divehi" : ["%Y %B %d, %A", "%Y-%m-%d", "%Y %B %d", "%Y %b %d", "%d/%m/%Y"],
1171         "dutch" : ["%A %d %B %Y", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1172         "english" : ["%A, %B %d, %Y", "%m/%d/%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1173         "esperanto" : ["%A, %d %B %Y", "%d %b %Y", "la %d de %B %Y", "la %d de %b %Y", "%m/%d/%Y"],
1174         "estonian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1175         "farsi" : ["%A %d %B %Y", "%Y/%m/%d", "%d %B %Y", "%d %b %Y", "%Y/%m/%d"],
1176         "finnish" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1177         "french" : ["%A %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1178         "friulan" : ["%A %d di %B dal %Y", "%d/%m/%y", "%d di %B dal %Y", "%d di %b dal %Y", "%d/%m/%Y"],
1179         "galician" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d de %b de %Y", "%d/%m/%Y"],
1180         "georgian" : ["%A, %d %B, %Y", "%d.%m.%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1181         "german" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1182         "german-ch" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1183         "german-ch-old" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1184         "greek" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1185         "hebrew" : ["%A, %d ב%B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1186         "hindi" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1187         "icelandic" : ["%A, %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1188         "interlingua" : ["%Y %B %d, %A", "%Y-%m-%d", "le %d de %B %Y", "le %d de %b %Y", "%Y-%m-%d"],
1189         "irish" : ["%A %d %B %Y", "%d/%m/%Y", "%d. %B %Y", "%d. %b %Y", "%d/%m/%Y"],
1190         "italian" : ["%A %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d/%b/%Y", "%d/%m/%Y"],
1191         "japanese" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y/%m/%d", "%y/%m/%d"],
1192         "japanese-cjk" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y/%m/%d", "%y/%m/%d"],
1193         "kannada" : ["%A, %B %d, %Y", "%d/%m/%y", "%d %B %Y", "%d %B %Y", "%d-%m-%Y"],
1194         "kazakh" : ["%Y ж. %d %B, %A", "%d.%m.%y", "%d %B %Y", "%d %B %Y", "%Y-%d-%m"],
1195         "khmer" : ["%A %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %B %Y", "%d/%m/%Y"],
1196         "korean" : ["%Y년 %m월 %d일 %A", "%y. %m. %d.", "%Y년 %m월 %d일", "%Y. %m. %d.", "%y. %m. %d."],
1197         "kurmanji" : ["%A, %d %B %Y", "%d %b %Y", "%d. %B %Y", "%d. %m. %Y", "%Y-%m-%d"],
1198         "lao" : ["%A ທີ %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %B %Y", "%d/%m/%Y"],
1199         "latin" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1200         "latvian" : ["%A, %Y. gada %d. %B", "%d.%m.%y", "%Y. gada %d. %B", "%Y. gada %d. %b", "%d.%m.%Y"],
1201         "lithuanian" : ["%Y m. %B %d d., %A", "%Y-%m-%d", "%Y m. %B %d d.", "%Y m. %B %d d.", "%Y-%m-%d"],
1202         "lowersorbian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1203         "macedonian" : ["%A, %d %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1204         "magyar" : ["%Y. %B %d., %A", "%Y. %m. %d.", "%Y. %B %d.", "%Y. %b %d.", "%Y.%m.%d."],
1205         "malayalam" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1206         "marathi" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1207         "mongolian" : ["%A, %Y оны %m сарын %d", "%Y-%m-%d", "%Y оны %m сарын %d", "%d-%m-%Y", "%d-%m-%Y"],
1208         "naustrian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1209         "newzealand" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1210         "ngerman" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1211         "norsk" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1212         "nynorsk" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1213         "occitan" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1214         "piedmontese" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1215         "polish" : ["%A, %d %B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1216         "polutonikogreek" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1217         "portuguese" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d de %b de %Y", "%Y/%m/%d"],
1218         "romanian" : ["%A, %d %B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1219         "romansh" : ["%A, ils %d da %B %Y", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1220         "russian" : ["%A, %d %B %Y г.", "%d.%m.%Y", "%d %B %Y г.", "%d %b %Y г.", "%d.%m.%Y"],
1221         "samin" : ["%Y %B %d, %A", "%Y-%m-%d", "%B %d. b. %Y", "%b %d. b. %Y", "%d.%m.%Y"],
1222         "sanskrit" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1223         "scottish" : ["%A, %dmh %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1224         "serbian" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1225         "serbian-latin" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1226         "slovak" : ["%A, %d. %B %Y", "%d. %m. %Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1227         "slovene" : ["%A, %d. %B %Y", "%d. %m. %y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1228         "spanish" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B %de %Y", "%d %b %Y", "%d/%m/%Y"],
1229         "spanish-mexico" : ["%A, %d de %B %de %Y", "%d/%m/%y", "%d de %B de %Y", "%d %b %Y", "%d/%m/%Y"],
1230         "swedish" : ["%A %d %B %Y", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1231         "syriac" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1232         "tamil" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1233         "telugu" : ["%d, %B %Y, %A", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1234         "thai" : ["%Aที่ %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1235         "tibetan" : ["%Y %Bའི་ཚེས་%d, %A", "%Y-%m-%d", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1236         "turkish" : ["%d %B %Y %A", "%d.%m.%Y", "%d %B %Y", "%d.%b.%Y", "%d.%m.%Y"],
1237         "turkmen" : ["%d %B %Y %A", "%d.%m.%Y", "%Y ý. %B %d", "%d.%m.%Y ý.", "%d.%m.%y ý."],
1238         "ukrainian" : ["%A, %d %B %Y р.", "%d.%m.%y", "%d %B %Y", "%d %m %Y", "%d.%m.%Y"],
1239         "uppersorbian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1240         "urdu" : ["%A، %d %B، %Y", "%d/%m/%y", "%d %B, %Y", "%d %b %Y", "%d/%m/%Y"],
1241         "vietnamese" : ["%A, %d %B, %Y", "%d/%m/%Y", "%d tháng %B %Y", "%d-%m-%Y", "%d/%m/%Y"],
1242         "welsh" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1243     }
1244
1245     types = ["date", "fixdate", "moddate" ]
1246     lang = get_value(document.header, "\\language")
1247     if lang == "":
1248         document.warning("Malformed LyX document! No \\language header found!")
1249         return
1250
1251     i = 0
1252     while True:
1253         i = find_token(document.body, "\\begin_inset Info", i+1)
1254         if i == -1:
1255             return
1256         j = find_end_of_inset(document.body, i+1)
1257         if j == -1:
1258             document.warning("Malformed LyX document: Could not find end of Info inset.")
1259             continue
1260         tp = find_token(document.body, 'type', i, j)
1261         tpv = get_quoted_value(document.body, "type", tp)
1262         if tpv not in types:
1263             continue
1264         arg = find_token(document.body, 'arg', i, j)
1265         argv = get_quoted_value(document.body, "arg", arg)
1266         isodate = ""
1267         dte = date.today()
1268         if tpv == "fixdate":
1269             datecomps = argv.split('@')
1270             if len(datecomps) > 1:
1271                 argv = datecomps[0]
1272                 isodate = datecomps[1]
1273                 m = re.search('(\d\d\d\d)-(\d\d)-(\d\d)', isodate)
1274                 if m:
1275                     dte = date(int(m.group(1)), int(m.group(2)), int(m.group(3)))
1276 # FIXME if we had the path to the original document (not the one in the tmp dir),
1277 #        we could use the mtime.
1278 #        elif tpv == "moddate":
1279 #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
1280         result = ""
1281         if argv == "ISO":
1282             result = dte.isodate()
1283         elif argv == "long":
1284             result = dte.strftime(dateformats[lang][0])
1285         elif argv == "short":
1286             result = dte.strftime(dateformats[lang][1])
1287         elif argv == "loclong":
1288             result = dte.strftime(dateformats[lang][2])
1289         elif argv == "locmedium":
1290             result = dte.strftime(dateformats[lang][3])
1291         elif argv == "locshort":
1292             result = dte.strftime(dateformats[lang][4])
1293         else:
1294             fmt = argv.replace("MMMM", "%b").replace("MMM", "%b").replace("MM", "%m").replace("M", "%m")
1295             fmt = fmt.replace("yyyy", "%Y").replace("yy", "%y")
1296             fmt = fmt.replace("dddd", "%A").replace("ddd", "%a").replace("dd", "%d")
1297             fmt = re.sub('[^\'%]d', '%d', fmt)
1298             fmt = fmt.replace("'", "")
1299             result = dte.strftime(fmt)
1300         if sys.version_info < (3,0):
1301             # In Python 2, datetime module works with binary strings,
1302             # our dateformat strings are utf8-encoded:
1303             result = result.decode('utf-8')
1304         document.body[i : j+1] = [result]
1305
1306
1307 def revert_timeinfo(document):
1308     " Revert time info insets to static text. "
1309
1310 # FIXME This currently only considers the main language and uses the system locale
1311 # Ideally, it should honor context languages and switch the locale accordingly.
1312 # Also, the time object is "naive", i.e., it does not know of timezones (%Z will
1313 # be empty).
1314
1315     # The time formats for each language using strftime syntax:
1316     # long, short
1317     timeformats = {
1318         "afrikaans" : ["%H:%M:%S %Z", "%H:%M"],
1319         "albanian" : ["%I:%M:%S %p, %Z", "%I:%M %p"],
1320         "american" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1321         "amharic" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1322         "ancientgreek" : ["%H:%M:%S %Z", "%H:%M:%S"],
1323         "arabic_arabi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1324         "arabic_arabtex" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1325         "armenian" : ["%H:%M:%S %Z", "%H:%M"],
1326         "asturian" : ["%H:%M:%S %Z", "%H:%M"],
1327         "australian" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1328         "austrian" : ["%H:%M:%S %Z", "%H:%M"],
1329         "bahasa" : ["%H.%M.%S %Z", "%H.%M"],
1330         "bahasam" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1331         "basque" : ["%H:%M:%S (%Z)", "%H:%M"],
1332         "belarusian" : ["%H:%M:%S, %Z", "%H:%M"],
1333         "bosnian" : ["%H:%M:%S %Z", "%H:%M"],
1334         "brazilian" : ["%H:%M:%S %Z", "%H:%M"],
1335         "breton" : ["%H:%M:%S %Z", "%H:%M"],
1336         "british" : ["%H:%M:%S %Z", "%H:%M"],
1337         "bulgarian" : ["%H:%M:%S %Z", "%H:%M"],
1338         "canadian" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1339         "canadien" : ["%H:%M:%S %Z", "%H h %M"],
1340         "catalan" : ["%H:%M:%S %Z", "%H:%M"],
1341         "chinese-simplified" : ["%Z %p%I:%M:%S", "%p%I:%M"],
1342         "chinese-traditional" : ["%p%I:%M:%S [%Z]", "%p%I:%M"],
1343         "coptic" : ["%H:%M:%S %Z", "%H:%M:%S"],
1344         "croatian" : ["%H:%M:%S (%Z)", "%H:%M"],
1345         "czech" : ["%H:%M:%S %Z", "%H:%M"],
1346         "danish" : ["%H.%M.%S %Z", "%H.%M"],
1347         "divehi" : ["%H:%M:%S %Z", "%H:%M"],
1348         "dutch" : ["%H:%M:%S %Z", "%H:%M"],
1349         "english" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1350         "esperanto" : ["%H:%M:%S %Z", "%H:%M:%S"],
1351         "estonian" : ["%H:%M:%S %Z", "%H:%M"],
1352         "farsi" : ["%H:%M:%S (%Z)", "%H:%M"],
1353         "finnish" : ["%H.%M.%S %Z", "%H.%M"],
1354         "french" : ["%H:%M:%S %Z", "%H:%M"],
1355         "friulan" : ["%H:%M:%S %Z", "%H:%M"],
1356         "galician" : ["%H:%M:%S %Z", "%H:%M"],
1357         "georgian" : ["%H:%M:%S %Z", "%H:%M"],
1358         "german" : ["%H:%M:%S %Z", "%H:%M"],
1359         "german-ch" : ["%H:%M:%S %Z", "%H:%M"],
1360         "german-ch-old" : ["%H:%M:%S %Z", "%H:%M"],
1361         "greek" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1362         "hebrew" : ["%H:%M:%S %Z", "%H:%M"],
1363         "hindi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1364         "icelandic" : ["%H:%M:%S %Z", "%H:%M"],
1365         "interlingua" : ["%H:%M:%S %Z", "%H:%M"],
1366         "irish" : ["%H:%M:%S %Z", "%H:%M"],
1367         "italian" : ["%H:%M:%S %Z", "%H:%M"],
1368         "japanese" : ["%H時%M分%S秒 %Z", "%H:%M"],
1369         "japanese-cjk" : ["%H時%M分%S秒 %Z", "%H:%M"],
1370         "kannada" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1371         "kazakh" : ["%H:%M:%S %Z", "%H:%M"],
1372         "khmer" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1373         "korean" : ["%p %I시%M분 %S초 %Z", "%p %I:%M"],
1374         "kurmanji" : ["%H:%M:%S %Z", "%H:%M:%S"],
1375         "lao" : ["%H ໂມງ%M ນາທີ  %S ວິນາທີ %Z", "%H:%M"],
1376         "latin" : ["%H:%M:%S %Z", "%H:%M:%S"],
1377         "latvian" : ["%H:%M:%S %Z", "%H:%M"],
1378         "lithuanian" : ["%H:%M:%S %Z", "%H:%M"],
1379         "lowersorbian" : ["%H:%M:%S %Z", "%H:%M"],
1380         "macedonian" : ["%H:%M:%S %Z", "%H:%M"],
1381         "magyar" : ["%H:%M:%S %Z", "%H:%M"],
1382         "malayalam" : ["%p %I:%M:%S %Z", "%p %I:%M"],
1383         "marathi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1384         "mongolian" : ["%H:%M:%S %Z", "%H:%M"],
1385         "naustrian" : ["%H:%M:%S %Z", "%H:%M"],
1386         "newzealand" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1387         "ngerman" : ["%H:%M:%S %Z", "%H:%M"],
1388         "norsk" : ["%H:%M:%S %Z", "%H:%M"],
1389         "nynorsk" : ["kl. %H:%M:%S %Z", "%H:%M"],
1390         "occitan" : ["%H:%M:%S %Z", "%H:%M"],
1391         "piedmontese" : ["%H:%M:%S %Z", "%H:%M:%S"],
1392         "polish" : ["%H:%M:%S %Z", "%H:%M"],
1393         "polutonikogreek" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1394         "portuguese" : ["%H:%M:%S %Z", "%H:%M"],
1395         "romanian" : ["%H:%M:%S %Z", "%H:%M"],
1396         "romansh" : ["%H:%M:%S %Z", "%H:%M"],
1397         "russian" : ["%H:%M:%S %Z", "%H:%M"],
1398         "samin" : ["%H:%M:%S %Z", "%H:%M"],
1399         "sanskrit" : ["%H:%M:%S %Z", "%H:%M"],
1400         "scottish" : ["%H:%M:%S %Z", "%H:%M"],
1401         "serbian" : ["%H:%M:%S %Z", "%H:%M"],
1402         "serbian-latin" : ["%H:%M:%S %Z", "%H:%M"],
1403         "slovak" : ["%H:%M:%S %Z", "%H:%M"],
1404         "slovene" : ["%H:%M:%S %Z", "%H:%M"],
1405         "spanish" : ["%H:%M:%S (%Z)", "%H:%M"],
1406         "spanish-mexico" : ["%H:%M:%S %Z", "%H:%M"],
1407         "swedish" : ["kl. %H:%M:%S %Z", "%H:%M"],
1408         "syriac" : ["%H:%M:%S %Z", "%H:%M"],
1409         "tamil" : ["%p %I:%M:%S %Z", "%p %I:%M"],
1410         "telugu" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1411         "thai" : ["%H นาฬิกา %M นาที  %S วินาที %Z", "%H:%M"],
1412         "tibetan" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1413         "turkish" : ["%H:%M:%S %Z", "%H:%M"],
1414         "turkmen" : ["%H:%M:%S %Z", "%H:%M"],
1415         "ukrainian" : ["%H:%M:%S %Z", "%H:%M"],
1416         "uppersorbian" : ["%H:%M:%S %Z", "%H:%M hodź."],
1417         "urdu" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1418         "vietnamese" : ["%H:%M:%S %Z", "%H:%M"],
1419         "welsh" : ["%H:%M:%S %Z", "%H:%M"]
1420     }
1421
1422     types = ["time", "fixtime", "modtime" ]
1423     i = 0
1424     i = find_token(document.header, "\\language", 0)
1425     if i == -1:
1426         # this should not happen
1427         document.warning("Malformed LyX document! No \\language header found!")
1428         return
1429     lang = get_value(document.header, "\\language", i)
1430
1431     i = 0
1432     while True:
1433         i = find_token(document.body, "\\begin_inset Info", i+1)
1434         if i == -1:
1435             return
1436         j = find_end_of_inset(document.body, i+1)
1437         if j == -1:
1438             document.warning("Malformed LyX document: Could not find end of Info inset.")
1439             continue
1440         tp = find_token(document.body, 'type', i, j)
1441         tpv = get_quoted_value(document.body, "type", tp)
1442         if tpv not in types:
1443             continue
1444         arg = find_token(document.body, 'arg', i, j)
1445         argv = get_quoted_value(document.body, "arg", arg)
1446         isotime = ""
1447         dtme = datetime.now()
1448         tme = dtme.time()
1449         if tpv == "fixtime":
1450             timecomps = argv.split('@')
1451             if len(timecomps) > 1:
1452                 argv = timecomps[0]
1453                 isotime = timecomps[1]
1454                 m = re.search('(\d\d):(\d\d):(\d\d)', isotime)
1455                 if m:
1456                     tme = time(int(m.group(1)), int(m.group(2)), int(m.group(3)))
1457                 else:
1458                     m = re.search('(\d\d):(\d\d)', isotime)
1459                     if m:
1460                         tme = time(int(m.group(1)), int(m.group(2)))
1461 # FIXME if we had the path to the original document (not the one in the tmp dir),
1462 #        we could use the mtime.
1463 #        elif tpv == "moddate":
1464 #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
1465         result = ""
1466         if argv == "ISO":
1467             result = tme.isoformat()
1468         elif argv == "long":
1469             result = tme.strftime(timeformats[lang][0])
1470         elif argv == "short":
1471             result = tme.strftime(timeformats[lang][1])
1472         else:
1473             fmt = argv.replace("HH", "%H").replace("H", "%H").replace("hh", "%I").replace("h", "%I")
1474             fmt = fmt.replace("mm", "%M").replace("m", "%M").replace("ss", "%S").replace("s", "%S")
1475             fmt = fmt.replace("zzz", "%f").replace("z", "%f").replace("t", "%Z")
1476             fmt = fmt.replace("AP", "%p").replace("ap", "%p").replace("A", "%p").replace("a", "%p")
1477             fmt = fmt.replace("'", "")
1478             result = dte.strftime(fmt)
1479         document.body[i : j+1] = result
1480
1481
1482 def revert_namenoextinfo(document):
1483     " Merge buffer Info inset type name-noext to name. "
1484
1485     i = 0
1486     while True:
1487         i = find_token(document.body, "\\begin_inset Info", i+1)
1488         if i == -1:
1489             return
1490         j = find_end_of_inset(document.body, i+1)
1491         if j == -1:
1492             document.warning("Malformed LyX document: Could not find end of Info inset.")
1493             continue
1494         tp = find_token(document.body, 'type', i, j)
1495         tpv = get_quoted_value(document.body, "type", tp)
1496         if tpv != "buffer":
1497             continue
1498         arg = find_token(document.body, 'arg', i, j)
1499         argv = get_quoted_value(document.body, "arg", arg)
1500         if argv != "name-noext":
1501             continue
1502         document.body[arg] = "arg \"name\""
1503
1504
1505 def revert_l7ninfo(document):
1506     " Revert l7n Info inset to text. "
1507
1508     i = 0
1509     while True:
1510         i = find_token(document.body, "\\begin_inset Info", i+1)
1511         if i == -1:
1512             return
1513         j = find_end_of_inset(document.body, i+1)
1514         if j == -1:
1515             document.warning("Malformed LyX document: Could not find end of Info inset.")
1516             continue
1517         tp = find_token(document.body, 'type', i, j)
1518         tpv = get_quoted_value(document.body, "type", tp)
1519         if tpv != "l7n":
1520             continue
1521         arg = find_token(document.body, 'arg', i, j)
1522         argv = get_quoted_value(document.body, "arg", arg)
1523         # remove trailing colons, menu accelerator (|...) and qt accelerator (&), while keeping literal " & "
1524         argv = argv.rstrip(':').split('|')[0].replace(" & ", "</amp;>").replace("&", "").replace("</amp;>", " & ")
1525         document.body[i : j+1] = argv
1526
1527
1528 def revert_listpargs(document):
1529     " Reverts listpreamble arguments to TeX-code "
1530     i = 0
1531     while True:
1532         i = find_token(document.body, "\\begin_inset Argument listpreamble:", i+1)
1533         if i == -1:
1534             return
1535         j = find_end_of_inset(document.body, i)
1536         # Find containing paragraph layout
1537         parent = get_containing_layout(document.body, i)
1538         if parent == False:
1539             document.warning("Malformed LyX document: Can't find parent paragraph layout")
1540             continue
1541         parbeg = parent[3]
1542         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1543         endPlain = find_end_of_layout(document.body, beginPlain)
1544         content = document.body[beginPlain + 1 : endPlain]
1545         del document.body[i:j+1]
1546         subst = ["\\begin_inset ERT", "status collapsed", "", "\\begin_layout Plain Layout",
1547                  "{"] + content + ["}", "\\end_layout", "", "\\end_inset", ""]
1548         document.body[parbeg : parbeg] = subst
1549
1550
1551 def revert_lformatinfo(document):
1552     " Revert layout format Info inset to text. "
1553
1554     i = 0
1555     while True:
1556         i = find_token(document.body, "\\begin_inset Info", i+1)
1557         if i == -1:
1558             return
1559         j = find_end_of_inset(document.body, i+1)
1560         if j == -1:
1561             document.warning("Malformed LyX document: Could not find end of Info inset.")
1562             continue
1563         tp = find_token(document.body, 'type', i, j)
1564         tpv = get_quoted_value(document.body, "type", tp)
1565         if tpv != "lyxinfo":
1566             continue
1567         arg = find_token(document.body, 'arg', i, j)
1568         argv = get_quoted_value(document.body, "arg", arg)
1569         if argv != "layoutformat":
1570             continue
1571         # hardcoded for now
1572         document.body[i : j+1] = "69"
1573
1574
1575 def convert_hebrew_parentheses(document):
1576     """ Swap opening/closing parentheses in Hebrew text.
1577
1578     Up to LyX 2.4, "(" was used as closing parenthesis and
1579     ")" as opening parenthesis for Hebrew in the LyX source.
1580     """
1581     # print("convert hebrew parentheses")
1582     current_languages = [document.language]
1583     for i, line in enumerate(document.body):
1584         if line.startswith('\\lang '):
1585             current_languages[-1] = line.lstrip('\\lang ')
1586         elif line.startswith('\\begin_layout'):
1587             current_languages.append(current_languages[-1])
1588             # print (line, current_languages[-1])
1589         elif line.startswith('\\end_layout'):
1590             current_languages.pop()
1591         elif current_languages[-1] == 'hebrew' and not line.startswith('\\'):
1592             document.body[i] = line.replace('(','\x00').replace(')','(').replace('\x00',')')
1593
1594
1595 def revert_hebrew_parentheses(document):
1596     " Store parentheses in Hebrew text reversed"
1597     # This only exists to keep the convert/revert naming convention
1598     convert_hebrew_parentheses(document)
1599
1600
1601 def revert_malayalam(document):
1602     " Set the document language to English but assure Malayalam output "
1603
1604     revert_language(document, "malayalam", "", "malayalam")
1605
1606
1607 def revert_soul(document):
1608     " Revert soul module flex insets to ERT "
1609
1610     flexes = ["Spaceletters", "Strikethrough", "Underline", "Highlight", "Capitalize"]
1611
1612     for flex in flexes:
1613         i = find_token(document.body, "\\begin_inset Flex %s" % flex, 0)
1614         if i != -1:
1615             add_to_preamble(document, ["\\usepackage{soul}"])
1616             break
1617     i = find_token(document.body, "\\begin_inset Flex Highlight", 0)
1618     if i != -1:
1619         add_to_preamble(document, ["\\usepackage{color}"])
1620
1621     revert_flex_inset(document.body, "Spaceletters", "\\so")
1622     revert_flex_inset(document.body, "Strikethrough", "\\st")
1623     revert_flex_inset(document.body, "Underline", "\\ul")
1624     revert_flex_inset(document.body, "Highlight", "\\hl")
1625     revert_flex_inset(document.body, "Capitalize", "\\caps")
1626
1627
1628 def revert_tablestyle(document):
1629     " Remove tablestyle params "
1630
1631     i = 0
1632     i = find_token(document.header, "\\tablestyle")
1633     if i != -1:
1634         del document.header[i]
1635
1636
1637 def revert_bibfileencodings(document):
1638     " Revert individual Biblatex bibliography encodings "
1639
1640     # Get cite engine
1641     engine = "basic"
1642     i = find_token(document.header, "\\cite_engine", 0)
1643     if i == -1:
1644         document.warning("Malformed document! Missing \\cite_engine")
1645     else:
1646         engine = get_value(document.header, "\\cite_engine", i)
1647
1648     # Check if biblatex
1649     biblatex = False
1650     if engine in ["biblatex", "biblatex-natbib"]:
1651         biblatex = True
1652
1653     # Map lyx to latex encoding names
1654     encodings = {
1655         "utf8" : "utf8",
1656         "utf8x" : "utf8x",
1657         "armscii8" : "armscii8",
1658         "iso8859-1" : "latin1",
1659         "iso8859-2" : "latin2",
1660         "iso8859-3" : "latin3",
1661         "iso8859-4" : "latin4",
1662         "iso8859-5" : "iso88595",
1663         "iso8859-6" : "8859-6",
1664         "iso8859-7" : "iso-8859-7",
1665         "iso8859-8" : "8859-8",
1666         "iso8859-9" : "latin5",
1667         "iso8859-13" : "latin7",
1668         "iso8859-15" : "latin9",
1669         "iso8859-16" : "latin10",
1670         "applemac" : "applemac",
1671         "cp437" : "cp437",
1672         "cp437de" : "cp437de",
1673         "cp850" : "cp850",
1674         "cp852" : "cp852",
1675         "cp855" : "cp855",
1676         "cp858" : "cp858",
1677         "cp862" : "cp862",
1678         "cp865" : "cp865",
1679         "cp866" : "cp866",
1680         "cp1250" : "cp1250",
1681         "cp1251" : "cp1251",
1682         "cp1252" : "cp1252",
1683         "cp1255" : "cp1255",
1684         "cp1256" : "cp1256",
1685         "cp1257" : "cp1257",
1686         "koi8-r" : "koi8-r",
1687         "koi8-u" : "koi8-u",
1688         "pt154" : "pt154",
1689         "utf8-platex" : "utf8",
1690         "ascii" : "ascii"
1691     }
1692
1693     i = 0
1694     bibresources = []
1695     while (True):
1696         i = find_token(document.body, "\\begin_inset CommandInset bibtex", i+1)
1697         if i == -1:
1698             break
1699         j = find_end_of_inset(document.body, i)
1700         if j == -1:
1701             document.warning("Can't find end of bibtex inset at line %d!!" %(i))
1702             continue
1703         encodings = get_quoted_value(document.body, "file_encodings", i, j)
1704         if not encodings:
1705             i = j
1706             continue
1707         bibfiles = get_quoted_value(document.body, "bibfiles", i, j).split(",")
1708         opts = get_quoted_value(document.body, "biblatexopts", i, j)
1709         if len(bibfiles) == 0:
1710             document.warning("Bibtex inset at line %d does not have a bibfile!" %(i))
1711         # remove encoding line
1712         k = find_token(document.body, "file_encodings", i, j)
1713         if k != -1:
1714             del document.body[k]
1715         # Re-find inset end line
1716         j = find_end_of_inset(document.body, i)
1717         if biblatex:
1718             enclist = encodings.split("\t")
1719             encmap = dict()
1720             for pp in enclist:
1721                 ppp = pp.split(" ", 1)
1722                 encmap[ppp[0]] = ppp[1]
1723             for bib in bibfiles:
1724                 pr = "\\addbibresource"
1725                 if bib in encmap.keys():
1726                     pr += "[bibencoding=" + encmap[bib] + "]"
1727                 pr += "{" + bib + "}"
1728                 add_to_preamble(document, [pr])
1729             # Insert ERT \\printbibliography and wrap bibtex inset to a Note
1730             pcmd = "printbibliography"
1731             if opts:
1732                 pcmd += "[" + opts + "]"
1733             repl = ["\\begin_inset ERT", "status open", "", "\\begin_layout Plain Layout",\
1734                     "", "", "\\backslash", pcmd, "\\end_layout", "", "\\end_inset", "", "",\
1735                     "\\end_layout", "", "\\begin_layout Standard", "\\begin_inset Note Note",\
1736                     "status open", "", "\\begin_layout Plain Layout" ]
1737             repl += document.body[i:j+1]
1738             repl += ["", "\\end_layout", "", "\\end_inset", "", ""]
1739             document.body[i:j+1] = repl
1740             j += 27
1741
1742         i = j
1743
1744
1745 def revert_cmidruletrimming(document):
1746     " Remove \\cmidrule trimming "
1747
1748     # FIXME: Revert to TeX code?
1749     i = 0
1750     while True:
1751         # first, let's find out if we need to do anything
1752         i = find_token(document.body, '<cell ', i+1)
1753         if i == -1:
1754             return
1755         j = document.body[i].find('trim="')
1756         if j == -1:
1757              continue
1758         rgx = re.compile(r' (bottom|top)line[lr]trim="true"')
1759         # remove trim option
1760         document.body[i] = rgx.sub('', document.body[i])
1761
1762
1763 ruby_inset_def = [
1764     r'### Inserted by lyx2lyx (ruby inset) ###',
1765     r'InsetLayout Flex:Ruby',
1766     r'  LyxType       charstyle',
1767     r'  LatexType     command',
1768     r'  LatexName     ruby',
1769     r'  HTMLTag       ruby',
1770     r'  HTMLAttr      ""',
1771     r'  HTMLInnerTag  rb',
1772     r'  HTMLInnerAttr ""',
1773     r'  BgColor       none',
1774     r'  LabelString   "Ruby"',
1775     r'  Decoration    Conglomerate',
1776     r'  Preamble',
1777     r'    \ifdefined\kanjiskip',
1778     r'      \IfFileExists{okumacro.sty}{\usepackage{okumacro}}{}',
1779     r'    \else \ifdefined\luatexversion',
1780     r'      \usepackage{luatexja-ruby}',
1781     r'    \else \ifdefined\XeTeXversion',
1782     r'      \usepackage{ruby}%',
1783     r'    \fi\fi\fi',
1784     r'    \providecommand{\ruby}[2]{\shortstack{\tiny #2\\#1}}',
1785     r'  EndPreamble',
1786     r'  Argument  post:1',
1787     r'    LabelString  "ruby text"',
1788     r'    MenuString  "Ruby Text|R"',
1789     r'    Tooltip    "Reading aid (ruby, furigana) for Chinese characters."',
1790     r'    Decoration  Conglomerate',
1791     r'    Font',
1792     r'      Size    tiny',
1793     r'    EndFont',
1794     r'    LabelFont',
1795     r'      Size    tiny',
1796     r'    EndFont',
1797     r'    Mandatory  1',
1798     r'  EndArgument',
1799     r'End',
1800 ]
1801
1802 def convert_ruby_module(document):
1803     " Use ruby module instead of local module definition "
1804     if document.del_local_layout(ruby_inset_def):
1805         document.add_module("ruby")
1806
1807 def revert_ruby_module(document):
1808     " Replace ruby module with local module definition "
1809     if document.del_module("ruby"):
1810         document.append_local_layout(ruby_inset_def)
1811
1812
1813 def convert_utf8_japanese(document):
1814     " Use generic utf8 with Japanese documents."
1815     lang = get_value(document.header, "\\language")
1816     if not lang.startswith("japanese"):
1817         return
1818     inputenc = get_value(document.header, "\\inputencoding")
1819     if ((lang == "japanese" and inputenc == "utf8-platex")
1820         or (lang == "japanese-cjk" and inputenc == "utf8-cjk")):
1821         document.set_parameter("inputencoding", "utf8")
1822
1823 def revert_utf8_japanese(document):
1824     " Use Japanese utf8 variants with Japanese documents."
1825     inputenc = get_value(document.header, "\\inputencoding")
1826     if inputenc != "utf8":
1827         return
1828     lang = get_value(document.header, "\\language")
1829     if lang == "japanese":
1830         document.set_parameter("inputencoding", "utf8-platex")
1831     if lang == "japanese-cjk":
1832         document.set_parameter("inputencoding", "utf8-cjk")
1833
1834
1835 def revert_lineno(document):
1836     " Replace lineno setting with user-preamble code."
1837
1838     options = get_quoted_value(document.header, "\\lineno_options",
1839                                delete=True)
1840     if not get_bool_value(document.header, "\\use_lineno", delete=True):
1841         return
1842     if options:
1843         options = "[" + options + "]"
1844     add_to_preamble(document, ["\\usepackage%s{lineno}" % options,
1845                                "\\linenumbers"])
1846
1847 def convert_lineno(document):
1848     " Replace user-preamble code with native lineno support."
1849     use_lineno = 0
1850     options = ""
1851     i = find_token(document.preamble, "\\linenumbers", 1)
1852     if i > -1:
1853         usepkg = re.match(r"\\usepackage(.*){lineno}", document.preamble[i-1])
1854         if usepkg:
1855             use_lineno = 1
1856             options = usepkg.group(1).strip("[]")
1857             del(document.preamble[i-1:i+1])
1858             del_token(document.preamble, "% Added by lyx2lyx", i-2, i-1)
1859
1860     k = find_token(document.header, "\\index ")
1861     if options == "":
1862         document.header[k:k] = ["\\use_lineno %d" % use_lineno]
1863     else:
1864         document.header[k:k] = ["\\use_lineno %d" % use_lineno,
1865                                 "\\lineno_options %s" % options]
1866
1867
1868 def revert_new_languages(document):
1869     """Emulate support for Azerbaijani, Bengali, Church Slavonic, Korean,
1870     and Russian (Petrine orthography)."""
1871
1872     #                lyxname:          (babelname, polyglossianame)
1873     new_languages = {"azerbaijani":    ("azerbaijani", ""),
1874                      "bengali":        ("", "bengali"),
1875                      "churchslavonic": ("", "churchslavonic"),
1876                      "oldrussian":     ("", "russian"),
1877                      "korean":         ("", "korean"),
1878                     }
1879     used_languages = set()
1880     if document.language in new_languages:
1881         used_languages.add(document.language)
1882     i = 0
1883     while True:
1884         i = find_token(document.body, "\\lang", i+1)
1885         if i == -1:
1886             break
1887         if document.body[i][6:].strip() in new_languages:
1888             used_languages.add(document.language)
1889
1890     # Korean is already supported via CJK, so leave as-is for Babel
1891     if ("korean" in used_languages
1892         and get_bool_value(document.header, "\\use_non_tex_fonts")
1893         and get_value(document.header, "\\language_package") in ("default", "auto")):
1894         revert_language(document, "korean", "", "korean")
1895     used_languages.discard("korean")
1896
1897     for lang in used_languages:
1898         revert(lang, *new_languages[lang])
1899
1900
1901 gloss_inset_def = [
1902     r'### Inserted by lyx2lyx (deprecated ling glosses) ###',
1903     r'InsetLayout Flex:Glosse',
1904     r'  LyXType               custom',
1905     r'  LabelString           "Gloss (old version)"',
1906     r'  MenuString            "Gloss (old version)"',
1907     r'  LatexType             environment',
1908     r'  LatexName             linggloss',
1909     r'  Decoration            minimalistic',
1910     r'  LabelFont',
1911     r'    Size                Small',
1912     r'  EndFont',
1913     r'  MultiPar              true',
1914     r'  CustomPars            false',
1915     r'  ForcePlain            true',
1916     r'  ParbreakIsNewline     true',
1917     r'  FreeSpacing           true',
1918     r'  Requires              covington',
1919     r'  Preamble',
1920     r'          \def\glosstr{}',
1921     r'          \@ifundefined{linggloss}{%',
1922     r'          \newenvironment{linggloss}[2][]{',
1923     r'             \def\glosstr{\glt #1}%',
1924     r'             \gll #2}',
1925     r'          {\glosstr\glend}}{}',
1926     r'  EndPreamble',
1927     r'  InToc                 true',
1928     r'  ResetsFont            true',
1929     r'  Argument 1',
1930     r'          Decoration    conglomerate',
1931     r'          LabelString   "Translation"',
1932     r'          MenuString    "Glosse Translation|s"',
1933     r'          Tooltip       "Add a translation for the glosse"',
1934     r'  EndArgument',
1935     r'End'
1936 ]
1937
1938 glosss_inset_def = [
1939     r'### Inserted by lyx2lyx (deprecated ling glosses) ###',
1940     r'InsetLayout Flex:Tri-Glosse',
1941     r'  LyXType               custom',
1942     r'  LabelString           "Tri-Gloss (old version)"',
1943     r'  MenuString            "Tri-Gloss (old version)"',
1944     r'  LatexType             environment',
1945     r'  LatexName             lingglosss',
1946     r'  Decoration            minimalistic',
1947     r'  LabelFont',
1948     r'    Size                Small',
1949     r'  EndFont',
1950     r'  MultiPar              true',
1951     r'  CustomPars            false',
1952     r'  ForcePlain            true',
1953     r'  ParbreakIsNewline     true',
1954     r'  FreeSpacing           true',
1955     r'  InToc                 true',
1956     r'  Requires              covington',
1957     r'  Preamble',
1958     r'          \def\glosstr{}',
1959     r'          \@ifundefined{lingglosss}{%',
1960     r'          \newenvironment{lingglosss}[2][]{',
1961     r'              \def\glosstr{\glt #1}%',
1962     r'              \glll #2}',
1963     r'          {\glosstr\glend}}{}',
1964     r'  EndPreamble',
1965     r'  ResetsFont            true',
1966     r'  Argument 1',
1967     r'          Decoration    conglomerate',
1968     r'          LabelString   "Translation"',
1969     r'          MenuString    "Glosse Translation|s"',
1970     r'          Tooltip       "Add a translation for the glosse"',
1971     r'  EndArgument',
1972     r'End'
1973 ]
1974
1975 def convert_linggloss(document):
1976     " Move old ling glosses to local layout "
1977     if find_token(document.body, '\\begin_inset Flex Glosse', 0) != -1:
1978         document.append_local_layout(gloss_inset_def)
1979     if find_token(document.body, '\\begin_inset Flex Tri-Glosse', 0) != -1:
1980         document.append_local_layout(glosss_inset_def)
1981
1982 def revert_linggloss(document):
1983     " Revert to old ling gloss definitions "
1984     if not "linguistics" in document.get_module_list():
1985         return
1986     document.del_local_layout(gloss_inset_def)
1987     document.del_local_layout(glosss_inset_def)
1988
1989     cov_req = False
1990     glosses = ["\\begin_inset Flex Interlinear Gloss (2 Lines)", "\\begin_inset Flex Interlinear Gloss (3 Lines)"]
1991     for glosse in glosses:
1992         i = 0
1993         while True:
1994             i = find_token(document.body, glosse, i+1)
1995             if i == -1:
1996                 break
1997             j = find_end_of_inset(document.body, i)
1998             if j == -1:
1999                 document.warning("Malformed LyX document: Can't find end of Gloss inset")
2000                 continue
2001
2002             arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
2003             endarg = find_end_of_inset(document.body, arg)
2004             optargcontent = []
2005             if arg != -1:
2006                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2007                 if argbeginPlain == -1:
2008                     document.warning("Malformed LyX document: Can't find optarg plain Layout")
2009                     continue
2010                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2011                 optargcontent = document.body[argbeginPlain + 1 : argendPlain - 2]
2012
2013                 # remove Arg insets and paragraph, if it only contains this inset
2014                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2015                     del document.body[arg - 1 : endarg + 4]
2016                 else:
2017                     del document.body[arg : endarg + 1]
2018
2019             arg = find_token(document.body, "\\begin_inset Argument post:1", i, j)
2020             endarg = find_end_of_inset(document.body, arg)
2021             marg1content = []
2022             if arg != -1:
2023                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2024                 if argbeginPlain == -1:
2025                     document.warning("Malformed LyX document: Can't find arg 1 plain Layout")
2026                     continue
2027                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2028                 marg1content = document.body[argbeginPlain + 1 : argendPlain - 2]
2029
2030                 # remove Arg insets and paragraph, if it only contains this inset
2031                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2032                     del document.body[arg - 1 : endarg + 4]
2033                 else:
2034                     del document.body[arg : endarg + 1]
2035
2036             arg = find_token(document.body, "\\begin_inset Argument post:2", i, j)
2037             endarg = find_end_of_inset(document.body, arg)
2038             marg2content = []
2039             if arg != -1:
2040                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2041                 if argbeginPlain == -1:
2042                     document.warning("Malformed LyX document: Can't find arg 2 plain Layout")
2043                     continue
2044                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2045                 marg2content = document.body[argbeginPlain + 1 : argendPlain - 2]
2046
2047                 # remove Arg insets and paragraph, if it only contains this inset
2048                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2049                     del document.body[arg - 1 : endarg + 4]
2050                 else:
2051                     del document.body[arg : endarg + 1]
2052
2053             arg = find_token(document.body, "\\begin_inset Argument post:3", i, j)
2054             endarg = find_end_of_inset(document.body, arg)
2055             marg3content = []
2056             if arg != -1:
2057                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2058                 if argbeginPlain == -1:
2059                     document.warning("Malformed LyX document: Can't find arg 3 plain Layout")
2060                     continue
2061                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2062                 marg3content = document.body[argbeginPlain + 1 : argendPlain - 2]
2063
2064                 # remove Arg insets and paragraph, if it only contains this inset
2065                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2066                     del document.body[arg - 1 : endarg + 4]
2067                 else:
2068                     del document.body[arg : endarg + 1]
2069
2070             cmd = "\\digloss"
2071             if glosse == "\\begin_inset Flex Interlinear Gloss (3 Lines)":
2072                 cmd = "\\trigloss"
2073
2074             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2075             endInset = find_end_of_inset(document.body, i)
2076             endPlain = find_end_of_layout(document.body, beginPlain)
2077             precontent = put_cmd_in_ert(cmd)
2078             if len(optargcontent) > 0:
2079                 precontent += put_cmd_in_ert("[") + optargcontent + put_cmd_in_ert("]")
2080             precontent += put_cmd_in_ert("{")
2081
2082             postcontent = put_cmd_in_ert("}{") + marg1content + put_cmd_in_ert("}{") + marg2content
2083             if cmd == "\\trigloss":
2084                 postcontent += put_cmd_in_ert("}{") + marg3content
2085             postcontent += put_cmd_in_ert("}")
2086
2087             document.body[endPlain:endInset + 1] = postcontent
2088             document.body[beginPlain + 1:beginPlain] = precontent
2089             del document.body[i : beginPlain + 1]
2090             if not cov_req:
2091                 document.append_local_layout("Requires covington")
2092                 cov_req = True
2093             i = beginPlain
2094
2095
2096 def revert_subexarg(document):
2097     " Revert linguistic subexamples with argument to ERT "
2098
2099     if not "linguistics" in document.get_module_list():
2100         return
2101
2102     cov_req = False
2103     i = 0
2104     while True:
2105         i = find_token(document.body, "\\begin_layout Subexample", i+1)
2106         if i == -1:
2107             break
2108         j = find_end_of_layout(document.body, i)
2109         if j == -1:
2110             document.warning("Malformed LyX document: Can't find end of Subexample layout")
2111             continue
2112         while True:
2113             # check for consecutive layouts
2114             k = find_token(document.body, "\\begin_layout", j)
2115             if k == -1 or document.body[k] != "\\begin_layout Subexample":
2116                 break
2117             j = find_end_of_layout(document.body, k)
2118             if j == -1:
2119                  document.warning("Malformed LyX document: Can't find end of Subexample layout")
2120                  continue
2121
2122         arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
2123         if arg == -1:
2124             continue
2125
2126         endarg = find_end_of_inset(document.body, arg)
2127         optargcontent = ""
2128         argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2129         if argbeginPlain == -1:
2130             document.warning("Malformed LyX document: Can't find optarg plain Layout")
2131             continue
2132         argendPlain = find_end_of_inset(document.body, argbeginPlain)
2133         optargcontent = lyx2latex(document, document.body[argbeginPlain + 1 : argendPlain - 2])
2134
2135         # remove Arg insets and paragraph, if it only contains this inset
2136         if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2137             del document.body[arg - 1 : endarg + 4]
2138         else:
2139             del document.body[arg : endarg + 1]
2140
2141         cmd = put_cmd_in_ert("\\begin{subexamples}[" + optargcontent + "]")
2142
2143         # re-find end of layout
2144         j = find_end_of_layout(document.body, i)
2145         if j == -1:
2146             document.warning("Malformed LyX document: Can't find end of Subexample layout")
2147             continue
2148         while True:
2149             # check for consecutive layouts
2150             k = find_token(document.body, "\\begin_layout", j)
2151             if k == -1 or document.body[k] != "\\begin_layout Subexample":
2152                 break
2153             document.body[k : k + 1] = ["\\begin_layout Standard"] + put_cmd_in_ert("\\item ")
2154             j = find_end_of_layout(document.body, k)
2155             if j == -1:
2156                  document.warning("Malformed LyX document: Can't find end of Subexample layout")
2157                  continue
2158
2159         endev = put_cmd_in_ert("\\end{subexamples}")
2160
2161         document.body[j : j] = ["\\end_layout", "", "\\begin_layout Standard"] + endev
2162         document.body[i : i + 1] = ["\\begin_layout Standard"] + cmd \
2163                 + ["\\end_layout", "", "\\begin_layout Standard"] + put_cmd_in_ert("\\item ")
2164         if not cov_req:
2165             document.append_local_layout("Requires covington")
2166             cov_req = True
2167
2168
2169 def revert_drs(document):
2170     " Revert DRS insets (linguistics) to ERT "
2171
2172     if not "linguistics" in document.get_module_list():
2173         return
2174
2175     cov_req = False
2176     drses = ["\\begin_inset Flex DRS", "\\begin_inset Flex DRS*",
2177              "\\begin_inset Flex IfThen-DRS", "\\begin_inset Flex Cond-DRS",
2178              "\\begin_inset Flex QDRS", "\\begin_inset Flex NegDRS",
2179              "\\begin_inset Flex SDRS"]
2180     for drs in drses:
2181         i = 0
2182         while True:
2183             i = find_token(document.body, drs, i+1)
2184             if i == -1:
2185                 break
2186             j = find_end_of_inset(document.body, i)
2187             if j == -1:
2188                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2189                 continue
2190
2191             # Check for arguments
2192             arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
2193             endarg = find_end_of_inset(document.body, arg)
2194             prearg1content = []
2195             if arg != -1:
2196                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2197                 if argbeginPlain == -1:
2198                     document.warning("Malformed LyX document: Can't find Argument 1 plain Layout")
2199                     continue
2200                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2201                 prearg1content = document.body[argbeginPlain + 1 : argendPlain - 2]
2202
2203                 # remove Arg insets and paragraph, if it only contains this inset
2204                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2205                     del document.body[arg - 1 : endarg + 4]
2206                 else:
2207                     del document.body[arg : endarg + 1]
2208
2209             # re-find inset end
2210             j = find_end_of_inset(document.body, i)
2211             if j == -1:
2212                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2213                 continue
2214
2215             arg = find_token(document.body, "\\begin_inset Argument 2", i, j)
2216             endarg = find_end_of_inset(document.body, arg)
2217             prearg2content = []
2218             if arg != -1:
2219                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2220                 if argbeginPlain == -1:
2221                     document.warning("Malformed LyX document: Can't find Argument 2 plain Layout")
2222                     continue
2223                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2224                 prearg2content = document.body[argbeginPlain + 1 : argendPlain - 2]
2225
2226                 # remove Arg insets and paragraph, if it only contains this inset
2227                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2228                     del document.body[arg - 1 : endarg + 4]
2229                 else:
2230                     del document.body[arg : endarg + 1]
2231
2232             # re-find inset end
2233             j = find_end_of_inset(document.body, i)
2234             if j == -1:
2235                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2236                 continue
2237
2238             arg = find_token(document.body, "\\begin_inset Argument post:1", i, j)
2239             endarg = find_end_of_inset(document.body, arg)
2240             postarg1content = []
2241             if arg != -1:
2242                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2243                 if argbeginPlain == -1:
2244                     document.warning("Malformed LyX document: Can't find Argument post:1 plain Layout")
2245                     continue
2246                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2247                 postarg1content = document.body[argbeginPlain + 1 : argendPlain - 2]
2248
2249                 # remove Arg insets and paragraph, if it only contains this inset
2250                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2251                     del document.body[arg - 1 : endarg + 4]
2252                 else:
2253                     del document.body[arg : endarg + 1]
2254
2255             # re-find inset end
2256             j = find_end_of_inset(document.body, i)
2257             if j == -1:
2258                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2259                 continue
2260
2261             arg = find_token(document.body, "\\begin_inset Argument post:2", i, j)
2262             endarg = find_end_of_inset(document.body, arg)
2263             postarg2content = []
2264             if arg != -1:
2265                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2266                 if argbeginPlain == -1:
2267                     document.warning("Malformed LyX document: Can't find Argument post:2 plain Layout")
2268                     continue
2269                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2270                 postarg2content = document.body[argbeginPlain + 1 : argendPlain - 2]
2271
2272                 # remove Arg insets and paragraph, if it only contains this inset
2273                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2274                     del document.body[arg - 1 : endarg + 4]
2275                 else:
2276                     del document.body[arg : endarg + 1]
2277
2278             # re-find inset end
2279             j = find_end_of_inset(document.body, i)
2280             if j == -1:
2281                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2282                 continue
2283
2284             arg = find_token(document.body, "\\begin_inset Argument post:3", i, j)
2285             endarg = find_end_of_inset(document.body, arg)
2286             postarg3content = []
2287             if arg != -1:
2288                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2289                 if argbeginPlain == -1:
2290                     document.warning("Malformed LyX document: Can't find Argument post:3 plain Layout")
2291                     continue
2292                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2293                 postarg3content = document.body[argbeginPlain + 1 : argendPlain - 2]
2294
2295                 # remove Arg insets and paragraph, if it only contains this inset
2296                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2297                     del document.body[arg - 1 : endarg + 4]
2298                 else:
2299                     del document.body[arg : endarg + 1]
2300
2301             # re-find inset end
2302             j = find_end_of_inset(document.body, i)
2303             if j == -1:
2304                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2305                 continue
2306
2307             arg = find_token(document.body, "\\begin_inset Argument post:4", i, j)
2308             endarg = find_end_of_inset(document.body, arg)
2309             postarg4content = []
2310             if arg != -1:
2311                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2312                 if argbeginPlain == -1:
2313                     document.warning("Malformed LyX document: Can't find Argument post:4 plain Layout")
2314                     continue
2315                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2316                 postarg4content = document.body[argbeginPlain + 1 : argendPlain - 2]
2317
2318                 # remove Arg insets and paragraph, if it only contains this inset
2319                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2320                     del document.body[arg - 1 : endarg + 4]
2321                 else:
2322                     del document.body[arg : endarg + 1]
2323
2324             # The respective LaTeX command
2325             cmd = "\\drs"
2326             if drs == "\\begin_inset Flex DRS*":
2327                 cmd = "\\drs*"
2328             elif drs == "\\begin_inset Flex IfThen-DRS":
2329                 cmd = "\\ifdrs"
2330             elif drs == "\\begin_inset Flex Cond-DRS":
2331                 cmd = "\\condrs"
2332             elif drs == "\\begin_inset Flex QDRS":
2333                 cmd = "\\qdrs"
2334             elif drs == "\\begin_inset Flex NegDRS":
2335                 cmd = "\\negdrs"
2336             elif drs == "\\begin_inset Flex SDRS":
2337                 cmd = "\\sdrs"
2338
2339             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2340             endInset = find_end_of_inset(document.body, i)
2341             endPlain = find_token_backwards(document.body, "\\end_layout", endInset)
2342             precontent = put_cmd_in_ert(cmd)
2343             precontent += put_cmd_in_ert("{") + prearg1content + put_cmd_in_ert("}")
2344             if drs == "\\begin_inset Flex SDRS":
2345                 precontent += put_cmd_in_ert("{") + prearg2content + put_cmd_in_ert("}")
2346             precontent += put_cmd_in_ert("{")
2347
2348             postcontent = []
2349             if cmd == "\\qdrs" or cmd == "\\condrs" or cmd == "\\ifdrs":
2350                 postcontent = put_cmd_in_ert("}{") + postarg1content + put_cmd_in_ert("}{") + postarg2content + put_cmd_in_ert("}")
2351                 if cmd == "\\condrs" or cmd == "\\qdrs":
2352                     postcontent += put_cmd_in_ert("{") + postarg3content + put_cmd_in_ert("}")
2353                 if cmd == "\\qdrs":
2354                     postcontent += put_cmd_in_ert("{") + postarg4content + put_cmd_in_ert("}")
2355             else:
2356                 postcontent = put_cmd_in_ert("}")
2357
2358             document.body[endPlain:endInset + 1] = postcontent
2359             document.body[beginPlain + 1:beginPlain] = precontent
2360             del document.body[i : beginPlain + 1]
2361             if not cov_req:
2362                 document.append_local_layout("Provides covington 1")
2363                 add_to_preamble(document, ["\\usepackage{drs,covington}"])
2364                 cov_req = True
2365             i = beginPlain
2366
2367
2368
2369 def revert_babelfont(document):
2370     " Reverts the use of \\babelfont to user preamble "
2371
2372     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2373     if i == -1:
2374         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2375         return
2376     if not str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2377         return
2378     i = find_token(document.header, '\\language_package', 0)
2379     if i == -1:
2380         document.warning("Malformed LyX document: Missing \\language_package.")
2381         return
2382     if get_value(document.header, "\\language_package", 0) != "babel":
2383         return
2384
2385     # check font settings
2386     # defaults
2387     roman = sans = typew = "default"
2388     osf = False
2389     sf_scale = tt_scale = 100.0
2390
2391     j = find_token(document.header, "\\font_roman", 0)
2392     if j == -1:
2393         document.warning("Malformed LyX document: Missing \\font_roman.")
2394     else:
2395         # We need to use this regex since split() does not handle quote protection
2396         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2397         roman = romanfont[2].strip('"')
2398         romanfont[2] = '"default"'
2399         document.header[j] = " ".join(romanfont)
2400
2401     j = find_token(document.header, "\\font_sans", 0)
2402     if j == -1:
2403         document.warning("Malformed LyX document: Missing \\font_sans.")
2404     else:
2405         # We need to use this regex since split() does not handle quote protection
2406         sansfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2407         sans = sansfont[2].strip('"')
2408         sansfont[2] = '"default"'
2409         document.header[j] = " ".join(sansfont)
2410
2411     j = find_token(document.header, "\\font_typewriter", 0)
2412     if j == -1:
2413         document.warning("Malformed LyX document: Missing \\font_typewriter.")
2414     else:
2415         # We need to use this regex since split() does not handle quote protection
2416         ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2417         typew = ttfont[2].strip('"')
2418         ttfont[2] = '"default"'
2419         document.header[j] = " ".join(ttfont)
2420
2421     i = find_token(document.header, "\\font_osf", 0)
2422     if i == -1:
2423         document.warning("Malformed LyX document: Missing \\font_osf.")
2424     else:
2425         osf = str2bool(get_value(document.header, "\\font_osf", i))
2426
2427     j = find_token(document.header, "\\font_sf_scale", 0)
2428     if j == -1:
2429         document.warning("Malformed LyX document: Missing \\font_sf_scale.")
2430     else:
2431         sfscale = document.header[j].split()
2432         val = sfscale[2]
2433         sfscale[2] = "100"
2434         document.header[j] = " ".join(sfscale)
2435         try:
2436             # float() can throw
2437             sf_scale = float(val)
2438         except:
2439             document.warning("Invalid font_sf_scale value: " + val)
2440
2441     j = find_token(document.header, "\\font_tt_scale", 0)
2442     if j == -1:
2443         document.warning("Malformed LyX document: Missing \\font_tt_scale.")
2444     else:
2445         ttscale = document.header[j].split()
2446         val = ttscale[2]
2447         ttscale[2] = "100"
2448         document.header[j] = " ".join(ttscale)
2449         try:
2450             # float() can throw
2451             tt_scale = float(val)
2452         except:
2453             document.warning("Invalid font_tt_scale value: " + val)
2454
2455     # set preamble stuff
2456     pretext = ['%% This document must be processed with xelatex or lualatex!']
2457     pretext.append('\\AtBeginDocument{%')
2458     if roman != "default":
2459         pretext.append('\\babelfont{rm}[Mapping=tex-text]{' + roman + '}')
2460     if sans != "default":
2461         sf = '\\babelfont{sf}['
2462         if sf_scale != 100.0:
2463             sf += 'Scale=' + str(sf_scale / 100.0) + ','
2464         sf += 'Mapping=tex-text]{' + sans + '}'
2465         pretext.append(sf)
2466     if typew != "default":
2467         tw = '\\babelfont{tt}'
2468         if tt_scale != 100.0:
2469             tw += '[Scale=' + str(tt_scale / 100.0) + ']'
2470         tw += '{' + typew + '}'
2471         pretext.append(tw)
2472     if osf:
2473         pretext.append('\\defaultfontfeatures{Numbers=OldStyle}')
2474     pretext.append('}')
2475     insert_to_preamble(document, pretext)
2476
2477
2478 def revert_minionpro(document):
2479     " Revert native MinionPro font definition (with extra options) to LaTeX "
2480
2481     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2482     if i == -1:
2483         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2484         return
2485     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2486         return
2487
2488     regexp = re.compile(r'(\\font_roman_opts)')
2489     x = find_re(document.header, regexp, 0)
2490     if x == -1:
2491         return
2492
2493     # We need to use this regex since split() does not handle quote protection
2494     romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2495     opts = romanopts[1].strip('"')
2496
2497     i = find_token(document.header, "\\font_roman", 0)
2498     if i == -1:
2499         document.warning("Malformed LyX document: Missing \\font_roman.")
2500         return
2501     else:
2502         # We need to use this regex since split() does not handle quote protection
2503         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2504         roman = romanfont[1].strip('"')
2505         if roman != "minionpro":
2506             return
2507         romanfont[1] = '"default"'
2508         document.header[i] = " ".join(romanfont)
2509         osf = False
2510         j = find_token(document.header, "\\font_osf true", 0)
2511         if j != -1:
2512             osf = True
2513         preamble = "\\usepackage["
2514         if osf:
2515             document.header[j] = "\\font_osf false"
2516         else:
2517             preamble += "lf,"
2518         preamble += opts
2519         preamble += "]{MinionPro}"
2520         add_to_preamble(document, [preamble])
2521         del document.header[x]
2522
2523
2524 def revert_font_opts(document):
2525     " revert font options by outputting \\setxxxfont or \\babelfont to the preamble "
2526
2527     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2528     if i == -1:
2529         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2530         return
2531     NonTeXFonts = str2bool(get_value(document.header, "\\use_non_tex_fonts", i))
2532     i = find_token(document.header, '\\language_package', 0)
2533     if i == -1:
2534         document.warning("Malformed LyX document: Missing \\language_package.")
2535         return
2536     Babel = (get_value(document.header, "\\language_package", 0) == "babel")
2537
2538     # 1. Roman
2539     regexp = re.compile(r'(\\font_roman_opts)')
2540     i = find_re(document.header, regexp, 0)
2541     if i != -1:
2542         # We need to use this regex since split() does not handle quote protection
2543         romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2544         opts = romanopts[1].strip('"')
2545         del document.header[i]
2546         if NonTeXFonts:
2547             regexp = re.compile(r'(\\font_roman)')
2548             i = find_re(document.header, regexp, 0)
2549             if i != -1:
2550                 # We need to use this regex since split() does not handle quote protection
2551                 romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2552                 font = romanfont[2].strip('"')
2553                 romanfont[2] = '"default"'
2554                 document.header[i] = " ".join(romanfont)
2555                 if font != "default":
2556                     if Babel:
2557                         preamble = "\\babelfont{rm}["
2558                     else:
2559                         preamble = "\\setmainfont["
2560                     preamble += opts
2561                     preamble += ","
2562                     preamble += "Mapping=tex-text]{"
2563                     preamble += font
2564                     preamble += "}"
2565                     add_to_preamble(document, [preamble])
2566
2567     # 2. Sans
2568     regexp = re.compile(r'(\\font_sans_opts)')
2569     i = find_re(document.header, regexp, 0)
2570     if i != -1:
2571         scaleval = 100
2572         # We need to use this regex since split() does not handle quote protection
2573         sfopts = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2574         opts = sfopts[1].strip('"')
2575         del document.header[i]
2576         if NonTeXFonts:
2577             regexp = re.compile(r'(\\font_sf_scale)')
2578             i = find_re(document.header, regexp, 0)
2579             if i != -1:
2580                 scaleval = get_value(document.header, "\\font_sf_scale" , i).split()[1]
2581             regexp = re.compile(r'(\\font_sans)')
2582             i = find_re(document.header, regexp, 0)
2583             if i != -1:
2584                 # We need to use this regex since split() does not handle quote protection
2585                 sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2586                 font = sffont[2].strip('"')
2587                 sffont[2] = '"default"'
2588                 document.header[i] = " ".join(sffont)
2589                 if font != "default":
2590                     if Babel:
2591                         preamble = "\\babelfont{sf}["
2592                     else:
2593                         preamble = "\\setsansfont["
2594                     preamble += opts
2595                     preamble += ","
2596                     if scaleval != 100:
2597                         preamble += "Scale=0."
2598                         preamble += scaleval
2599                         preamble += ","
2600                     preamble += "Mapping=tex-text]{"
2601                     preamble += font
2602                     preamble += "}"
2603                     add_to_preamble(document, [preamble])
2604
2605     # 3. Typewriter
2606     regexp = re.compile(r'(\\font_typewriter_opts)')
2607     i = find_re(document.header, regexp, 0)
2608     if i != -1:
2609         scaleval = 100
2610         # We need to use this regex since split() does not handle quote protection
2611         ttopts = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2612         opts = ttopts[1].strip('"')
2613         del document.header[i]
2614         if NonTeXFonts:
2615             regexp = re.compile(r'(\\font_tt_scale)')
2616             i = find_re(document.header, regexp, 0)
2617             if i != -1:
2618                 scaleval = get_value(document.header, "\\font_tt_scale" , i).split()[1]
2619             regexp = re.compile(r'(\\font_typewriter)')
2620             i = find_re(document.header, regexp, 0)
2621             if i != -1:
2622                 # We need to use this regex since split() does not handle quote protection
2623                 ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2624                 font = ttfont[2].strip('"')
2625                 ttfont[2] = '"default"'
2626                 document.header[i] = " ".join(ttfont)
2627                 if font != "default":
2628                     if Babel:
2629                         preamble = "\\babelfont{tt}["
2630                     else:
2631                         preamble = "\\setmonofont["
2632                     preamble += opts
2633                     preamble += ","
2634                     if scaleval != 100:
2635                         preamble += "Scale=0."
2636                         preamble += scaleval
2637                         preamble += ","
2638                     preamble += "Mapping=tex-text]{"
2639                     preamble += font
2640                     preamble += "}"
2641                     add_to_preamble(document, [preamble])
2642
2643
2644 def revert_plainNotoFonts_xopts(document):
2645     " Revert native (straight) Noto font definition (with extra options) to LaTeX "
2646
2647     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2648     if i == -1:
2649         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2650         return
2651     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2652         return
2653
2654     osf = False
2655     y = find_token(document.header, "\\font_osf true", 0)
2656     if y != -1:
2657         osf = True
2658
2659     regexp = re.compile(r'(\\font_roman_opts)')
2660     x = find_re(document.header, regexp, 0)
2661     if x == -1 and not osf:
2662         return
2663
2664     opts = ""
2665     if x != -1:
2666         # We need to use this regex since split() does not handle quote protection
2667         romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2668         opts = romanopts[1].strip('"')
2669     if osf:
2670         if opts != "":
2671             opts += ", "
2672         opts += "osf"
2673
2674     i = find_token(document.header, "\\font_roman", 0)
2675     if i == -1:
2676         return
2677
2678     # We need to use this regex since split() does not handle quote protection
2679     romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2680     roman = romanfont[1].strip('"')
2681     if roman != "NotoSerif-TLF":
2682         return
2683
2684     j = find_token(document.header, "\\font_sans", 0)
2685     if j == -1:
2686         return
2687
2688     # We need to use this regex since split() does not handle quote protection
2689     sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2690     sf = sffont[1].strip('"')
2691     if sf != "default":
2692         return
2693
2694     j = find_token(document.header, "\\font_typewriter", 0)
2695     if j == -1:
2696         return
2697
2698     # We need to use this regex since split() does not handle quote protection
2699     ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2700     tt = ttfont[1].strip('"')
2701     if tt != "default":
2702         return
2703
2704     # So we have noto as "complete font"
2705     romanfont[1] = '"default"'
2706     document.header[i] = " ".join(romanfont)
2707
2708     preamble = "\\usepackage["
2709     preamble += opts
2710     preamble += "]{noto}"
2711     add_to_preamble(document, [preamble])
2712     if osf:
2713         document.header[y] = "\\font_osf false"
2714     if x != -1:
2715         del document.header[x]
2716
2717
2718 def revert_notoFonts_xopts(document):
2719     " Revert native (extended) Noto font definition (with extra options) to LaTeX "
2720
2721     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2722     if i == -1:
2723         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2724         return
2725     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2726         return
2727
2728     fontmap = dict()
2729     fm = createFontMapping(['Noto'])
2730     if revert_fonts(document, fm, fontmap, True):
2731         add_preamble_fonts(document, fontmap)
2732
2733
2734 def revert_IBMFonts_xopts(document):
2735     " Revert native IBM font definition (with extra options) to LaTeX "
2736
2737     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2738     if i == -1:
2739         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2740         return
2741     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2742         return
2743
2744     fontmap = dict()
2745     fm = createFontMapping(['IBM'])
2746     ft = ""
2747     if revert_fonts(document, fm, fontmap, True):
2748         add_preamble_fonts(document, fontmap)
2749
2750
2751 def revert_AdobeFonts_xopts(document):
2752     " Revert native Adobe font definition (with extra options) to LaTeX "
2753
2754     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2755     if i == -1:
2756         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2757         return
2758     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2759         return
2760
2761     fontmap = dict()
2762     fm = createFontMapping(['Adobe'])
2763     ft = ""
2764     if revert_fonts(document, fm, fontmap, True):
2765         add_preamble_fonts(document, fontmap)
2766
2767
2768 def convert_osf(document):
2769     " Convert \\font_osf param to new format "
2770
2771     NonTeXFonts = False
2772     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2773     if i == -1:
2774         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2775     else:
2776         NonTeXFonts = str2bool(get_value(document.header, "\\use_non_tex_fonts", i))
2777
2778     i = find_token(document.header, '\\font_osf', 0)
2779     if i == -1:
2780         document.warning("Malformed LyX document: Missing \\font_osf.")
2781         return
2782
2783     osfsf = ["biolinum", "ADOBESourceSansPro", "NotoSansRegular", "NotoSansMedium", "NotoSansThin", "NotoSansLight", "NotoSansExtralight" ]
2784     osftt = ["ADOBESourceCodePro", "NotoMonoRegular" ]
2785
2786     osfval = str2bool(get_value(document.header, "\\font_osf", i))
2787     document.header[i] = document.header[i].replace("\\font_osf", "\\font_roman_osf")
2788
2789     if NonTeXFonts:
2790         document.header.insert(i, "\\font_sans_osf false")
2791         document.header.insert(i + 1, "\\font_typewriter_osf false")
2792         return
2793
2794     if osfval:
2795         x = find_token(document.header, "\\font_sans", 0)
2796         if x == -1:
2797             document.warning("Malformed LyX document: Missing \\font_sans.")
2798         else:
2799             # We need to use this regex since split() does not handle quote protection
2800             sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2801             sf = sffont[1].strip('"')
2802             if sf in osfsf:
2803                 document.header.insert(i, "\\font_sans_osf true")
2804             else:
2805                 document.header.insert(i, "\\font_sans_osf false")
2806
2807         x = find_token(document.header, "\\font_typewriter", 0)
2808         if x == -1:
2809             document.warning("Malformed LyX document: Missing \\font_typewriter.")
2810         else:
2811             # We need to use this regex since split() does not handle quote protection
2812             ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2813             tt = ttfont[1].strip('"')
2814             if tt in osftt:
2815                 document.header.insert(i + 1, "\\font_typewriter_osf true")
2816             else:
2817                 document.header.insert(i + 1, "\\font_typewriter_osf false")
2818
2819     else:
2820         document.header.insert(i, "\\font_sans_osf false")
2821         document.header.insert(i + 1, "\\font_typewriter_osf false")
2822
2823
2824 def revert_osf(document):
2825     " Revert \\font_*_osf params "
2826
2827     NonTeXFonts = False
2828     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2829     if i == -1:
2830         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2831     else:
2832         NonTeXFonts = str2bool(get_value(document.header, "\\use_non_tex_fonts", i))
2833
2834     i = find_token(document.header, '\\font_roman_osf', 0)
2835     if i == -1:
2836         document.warning("Malformed LyX document: Missing \\font_roman_osf.")
2837         return
2838
2839     osfval = str2bool(get_value(document.header, "\\font_roman_osf", i))
2840     document.header[i] = document.header[i].replace("\\font_roman_osf", "\\font_osf")
2841
2842     i = find_token(document.header, '\\font_sans_osf', 0)
2843     if i == -1:
2844         document.warning("Malformed LyX document: Missing \\font_sans_osf.")
2845         return
2846
2847     osfval = str2bool(get_value(document.header, "\\font_sans_osf", i))
2848     del document.header[i]
2849
2850     i = find_token(document.header, '\\font_typewriter_osf', 0)
2851     if i == -1:
2852         document.warning("Malformed LyX document: Missing \\font_typewriter_osf.")
2853         return
2854
2855     osfval |= str2bool(get_value(document.header, "\\font_typewriter_osf", i))
2856     del document.header[i]
2857
2858     if osfval:
2859         i = find_token(document.header, '\\font_osf', 0)
2860         if i == -1:
2861             document.warning("Malformed LyX document: Missing \\font_osf.")
2862             return
2863         document.header[i] = "\\font_osf true"
2864
2865
2866 def revert_texfontopts(document):
2867     " Revert native TeX font definitions (with extra options) to LaTeX "
2868
2869     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2870     if i == -1:
2871         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2872         return
2873     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2874         return
2875
2876     rmfonts = ["ccfonts", "cochineal", "utopia", "garamondx", "libertine", "lmodern", "palatino", "times", "xcharter" ]
2877
2878     # First the sf (biolinum only)
2879     regexp = re.compile(r'(\\font_sans_opts)')
2880     x = find_re(document.header, regexp, 0)
2881     if x != -1:
2882         # We need to use this regex since split() does not handle quote protection
2883         sfopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2884         opts = sfopts[1].strip('"')
2885         i = find_token(document.header, "\\font_sans", 0)
2886         if i == -1:
2887             document.warning("Malformed LyX document: Missing \\font_sans.")
2888         else:
2889             # We need to use this regex since split() does not handle quote protection
2890             sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2891             sans = sffont[1].strip('"')
2892             if sans == "biolinum":
2893                 sf_scale = 100.0
2894                 sffont[1] = '"default"'
2895                 document.header[i] = " ".join(sffont)
2896                 osf = False
2897                 j = find_token(document.header, "\\font_sans_osf true", 0)
2898                 if j != -1:
2899                     osf = True
2900                 k = find_token(document.header, "\\font_sf_scale", 0)
2901                 if k == -1:
2902                     document.warning("Malformed LyX document: Missing \\font_sf_scale.")
2903                 else:
2904                     sfscale = document.header[k].split()
2905                     val = sfscale[1]
2906                     sfscale[1] = "100"
2907                     document.header[k] = " ".join(sfscale)
2908                     try:
2909                         # float() can throw
2910                         sf_scale = float(val)
2911                     except:
2912                         document.warning("Invalid font_sf_scale value: " + val)
2913                 preamble = "\\usepackage["
2914                 if osf:
2915                     document.header[j] = "\\font_sans_osf false"
2916                     preamble += "osf,"
2917                 if sf_scale != 100.0:
2918                     preamble += 'scaled=' + str(sf_scale / 100.0) + ','
2919                 preamble += opts
2920                 preamble += "]{biolinum}"
2921                 add_to_preamble(document, [preamble])
2922                 del document.header[x]
2923
2924     regexp = re.compile(r'(\\font_roman_opts)')
2925     x = find_re(document.header, regexp, 0)
2926     if x == -1:
2927         return
2928
2929     # We need to use this regex since split() does not handle quote protection
2930     romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2931     opts = romanopts[1].strip('"')
2932
2933     i = find_token(document.header, "\\font_roman", 0)
2934     if i == -1:
2935         document.warning("Malformed LyX document: Missing \\font_roman.")
2936         return
2937     else:
2938         # We need to use this regex since split() does not handle quote protection
2939         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2940         roman = romanfont[1].strip('"')
2941         if not roman in rmfonts:
2942             return
2943         romanfont[1] = '"default"'
2944         document.header[i] = " ".join(romanfont)
2945         package = roman
2946         if roman == "utopia":
2947             package = "fourier"
2948         elif roman == "palatino":
2949             package = "mathpazo"
2950         elif roman == "times":
2951             package = "mathptmx"
2952         elif roman == "xcharter":
2953             package = "XCharter"
2954         osf = ""
2955         j = find_token(document.header, "\\font_roman_osf true", 0)
2956         if j != -1:
2957             if roman == "cochineal":
2958                 osf = "proportional,osf,"
2959             elif roman == "utopia":
2960                 osf = "oldstyle,"
2961             elif roman == "garamondx":
2962                 osf = "osfI,"
2963             elif roman == "libertine":
2964                 osf = "osf,"
2965             elif roman == "palatino":
2966                 osf = "osf,"
2967             elif roman == "xcharter":
2968                 osf = "osf,"
2969             document.header[j] = "\\font_roman_osf false"
2970         k = find_token(document.header, "\\font_sc true", 0)
2971         if k != -1:
2972             if roman == "utopia":
2973                 osf += "expert,"
2974             if roman == "palatino" and osf == "":
2975                 osf = "sc,"
2976             document.header[k] = "\\font_sc false"
2977         preamble = "\\usepackage["
2978         preamble += osf
2979         preamble += opts
2980         preamble += "]{" + package + "}"
2981         add_to_preamble(document, [preamble])
2982         del document.header[x]
2983
2984
2985 def convert_CantarellFont(document):
2986     " Handle Cantarell font definition to LaTeX "
2987
2988     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
2989         fm = createFontMapping(['Cantarell'])
2990         convert_fonts(document, fm, "oldstyle")
2991
2992 def revert_CantarellFont(document):
2993     " Revert native Cantarell font definition to LaTeX "
2994
2995     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
2996         fontmap = dict()
2997         fm = createFontMapping(['Cantarell'])
2998         if revert_fonts(document, fm, fontmap, False, True):
2999             add_preamble_fonts(document, fontmap)
3000
3001 def convert_ChivoFont(document):
3002     " Handle Chivo font definition to LaTeX "
3003
3004     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
3005         fm = createFontMapping(['Chivo'])
3006         convert_fonts(document, fm, "oldstyle")
3007
3008 def revert_ChivoFont(document):
3009     " Revert native Chivo font definition to LaTeX "
3010
3011     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
3012         fontmap = dict()
3013         fm = createFontMapping(['Chivo'])
3014         if revert_fonts(document, fm, fontmap, False, True):
3015             add_preamble_fonts(document, fontmap)
3016
3017
3018 def convert_FiraFont(document):
3019     " Handle Fira font definition to LaTeX "
3020
3021     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
3022         fm = createFontMapping(['Fira'])
3023         convert_fonts(document, fm, "lf")
3024
3025 def revert_FiraFont(document):
3026     " Revert native Fira font definition to LaTeX "
3027
3028     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
3029         fontmap = dict()
3030         fm = createFontMapping(['Fira'])
3031         if revert_fonts(document, fm, fontmap, False, True):
3032             add_preamble_fonts(document, fontmap)
3033
3034
3035 def convert_Semibolds(document):
3036     " Move semibold options to extraopts "
3037
3038     NonTeXFonts = False
3039     i = find_token(document.header, '\\use_non_tex_fonts', 0)
3040     if i == -1:
3041         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
3042     else:
3043         NonTeXFonts = str2bool(get_value(document.header, "\\use_non_tex_fonts", i))
3044
3045     i = find_token(document.header, "\\font_roman", 0)
3046     if i == -1:
3047         document.warning("Malformed LyX document: Missing \\font_roman.")
3048     else:
3049         # We need to use this regex since split() does not handle quote protection
3050         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3051         roman = romanfont[1].strip('"')
3052         if roman == "IBMPlexSerifSemibold":
3053             romanfont[1] = '"IBMPlexSerif"'
3054             document.header[i] = " ".join(romanfont)
3055
3056             if NonTeXFonts == False:
3057                 regexp = re.compile(r'(\\font_roman_opts)')
3058                 x = find_re(document.header, regexp, 0)
3059                 if x == -1:
3060                     # Sensible place to insert tag
3061                     fo = find_token(document.header, "\\font_sf_scale")
3062                     if fo == -1:
3063                         document.warning("Malformed LyX document! Missing \\font_sf_scale")
3064                     else:
3065                         document.header.insert(fo, "\\font_roman_opts \"semibold\"")
3066                 else:
3067                     # We need to use this regex since split() does not handle quote protection
3068                     romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3069                     document.header[x] = "\\font_roman_opts \"semibold, " + romanopts[1].strip('"') + "\""
3070
3071     i = find_token(document.header, "\\font_sans", 0)
3072     if i == -1:
3073         document.warning("Malformed LyX document: Missing \\font_sans.")
3074     else:
3075         # We need to use this regex since split() does not handle quote protection
3076         sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3077         sf = sffont[1].strip('"')
3078         if sf == "IBMPlexSansSemibold":
3079             sffont[1] = '"IBMPlexSans"'
3080             document.header[i] = " ".join(sffont)
3081
3082             if NonTeXFonts == False:
3083                 regexp = re.compile(r'(\\font_sans_opts)')
3084                 x = find_re(document.header, regexp, 0)
3085                 if x == -1:
3086                     # Sensible place to insert tag
3087                     fo = find_token(document.header, "\\font_sf_scale")
3088                     if fo == -1:
3089                         document.warning("Malformed LyX document! Missing \\font_sf_scale")
3090                     else:
3091                         document.header.insert(fo, "\\font_sans_opts \"semibold\"")
3092                 else:
3093                     # We need to use this regex since split() does not handle quote protection
3094                     sfopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3095                     document.header[x] = "\\font_sans_opts \"semibold, " + sfopts[1].strip('"') + "\""
3096
3097     i = find_token(document.header, "\\font_typewriter", 0)
3098     if i == -1:
3099         document.warning("Malformed LyX document: Missing \\font_typewriter.")
3100     else:
3101         # We need to use this regex since split() does not handle quote protection
3102         ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3103         tt = ttfont[1].strip('"')
3104         if tt == "IBMPlexMonoSemibold":
3105             ttfont[1] = '"IBMPlexMono"'
3106             document.header[i] = " ".join(ttfont)
3107
3108             if NonTeXFonts == False:
3109                 regexp = re.compile(r'(\\font_typewriter_opts)')
3110                 x = find_re(document.header, regexp, 0)
3111                 if x == -1:
3112                     # Sensible place to insert tag
3113                     fo = find_token(document.header, "\\font_tt_scale")
3114                     if fo == -1:
3115                         document.warning("Malformed LyX document! Missing \\font_tt_scale")
3116                     else:
3117                         document.header.insert(fo, "\\font_typewriter_opts \"semibold\"")
3118                 else:
3119                     # We need to use this regex since split() does not handle quote protection
3120                     ttopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3121                     document.header[x] = "\\font_typewriter_opts \"semibold, " + sfopts[1].strip('"') + "\""
3122
3123
3124 def convert_NotoRegulars(document):
3125     " Merge diverse noto reagular fonts "
3126
3127     i = find_token(document.header, "\\font_roman", 0)
3128     if i == -1:
3129         document.warning("Malformed LyX document: Missing \\font_roman.")
3130     else:
3131         # We need to use this regex since split() does not handle quote protection
3132         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3133         roman = romanfont[1].strip('"')
3134         if roman == "NotoSerif-TLF":
3135             romanfont[1] = '"NotoSerifRegular"'
3136             document.header[i] = " ".join(romanfont)
3137
3138     i = find_token(document.header, "\\font_sans", 0)
3139     if i == -1:
3140         document.warning("Malformed LyX document: Missing \\font_sans.")
3141     else:
3142         # We need to use this regex since split() does not handle quote protection
3143         sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3144         sf = sffont[1].strip('"')
3145         if sf == "NotoSans-TLF":
3146             sffont[1] = '"NotoSansRegular"'
3147             document.header[i] = " ".join(sffont)
3148
3149     i = find_token(document.header, "\\font_typewriter", 0)
3150     if i == -1:
3151         document.warning("Malformed LyX document: Missing \\font_typewriter.")
3152     else:
3153         # We need to use this regex since split() does not handle quote protection
3154         ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3155         tt = ttfont[1].strip('"')
3156         if tt == "NotoMono-TLF":
3157             ttfont[1] = '"NotoMonoRegular"'
3158             document.header[i] = " ".join(ttfont)
3159
3160
3161 def convert_CrimsonProFont(document):
3162     " Handle CrimsonPro font definition to LaTeX "
3163
3164     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
3165         fm = createFontMapping(['CrimsonPro'])
3166         convert_fonts(document, fm, "lf")
3167
3168 def revert_CrimsonProFont(document):
3169     " Revert native CrimsonPro font definition to LaTeX "
3170
3171     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
3172         fontmap = dict()
3173         fm = createFontMapping(['CrimsonPro'])
3174         if revert_fonts(document, fm, fontmap, False, True):
3175             add_preamble_fonts(document, fontmap)
3176
3177
3178 def revert_pagesizes(document):
3179     " Revert new page sizes in memoir and KOMA to options "
3180
3181     if document.textclass != "memoir" and document.textclass[:2] != "scr":
3182         return
3183
3184     i = find_token(document.header, "\\use_geometry true", 0)
3185     if i != -1:
3186         return
3187
3188     defsizes = ["default", "custom", "letterpaper", "legalpaper", "executivepaper", "a4paper", "a5paper", "b5paper"]
3189
3190     i = find_token(document.header, "\\papersize", 0)
3191     if i == -1:
3192         document.warning("Malformed LyX document! Missing \\papersize header.")
3193         return
3194     val = get_value(document.header, "\\papersize", i)
3195     if val in defsizes:
3196         # nothing to do
3197         return
3198
3199     document.header[i] = "\\papersize default"
3200
3201     i = find_token(document.header, "\\options", 0)
3202     if i == -1:
3203         i = find_token(document.header, "\\textclass", 0)
3204         if i == -1:
3205             document.warning("Malformed LyX document! Missing \\textclass header.")
3206             return
3207         document.header.insert(i, "\\options " + val)
3208         return
3209     document.header[i] = document.header[i] + "," + val
3210
3211
3212 def convert_pagesizes(document):
3213     " Convert to new page sizes in memoir and KOMA to options "
3214
3215     if document.textclass != "memoir" and document.textclass[:3] != "scr":
3216         return
3217
3218     i = find_token(document.header, "\\use_geometry true", 0)
3219     if i != -1:
3220         return
3221
3222     defsizes = ["default", "custom", "letterpaper", "legalpaper", "executivepaper", "a4paper", "a5paper", "b5paper"]
3223
3224     i = find_token(document.header, "\\papersize", 0)
3225     if i == -1:
3226         document.warning("Malformed LyX document! Missing \\papersize header.")
3227         return
3228     val = get_value(document.header, "\\papersize", i)
3229     if val in defsizes:
3230         # nothing to do
3231         return
3232
3233     i = find_token(document.header, "\\use_geometry false", 0)
3234     if i != -1:
3235         # Maintain use of geometry
3236         document.header[1] = "\\use_geometry true"
3237
3238 def revert_komafontsizes(document):
3239     " Revert new font sizes in KOMA to options "
3240
3241     if document.textclass[:3] != "scr":
3242         return
3243
3244     i = find_token(document.header, "\\paperfontsize", 0)
3245     if i == -1:
3246         document.warning("Malformed LyX document! Missing \\paperfontsize header.")
3247         return
3248
3249     defsizes = ["default", "10", "11", "12"]
3250
3251     val = get_value(document.header, "\\paperfontsize", i)
3252     if val in defsizes:
3253         # nothing to do
3254         return
3255
3256     document.header[i] = "\\paperfontsize default"
3257
3258     fsize = "fontsize=" + val
3259
3260     i = find_token(document.header, "\\options", 0)
3261     if i == -1:
3262         i = find_token(document.header, "\\textclass", 0)
3263         if i == -1:
3264             document.warning("Malformed LyX document! Missing \\textclass header.")
3265             return
3266         document.header.insert(i, "\\options " + fsize)
3267         return
3268     document.header[i] = document.header[i] + "," + fsize
3269
3270
3271 def revert_dupqualicites(document):
3272     " Revert qualified citation list commands with duplicate keys to ERT "
3273
3274     # LyX 2.3 only supports qualified citation lists with unique keys. Thus,
3275     # we need to revert those with multiple uses of the same key.
3276
3277     # Get cite engine
3278     engine = "basic"
3279     i = find_token(document.header, "\\cite_engine", 0)
3280     if i == -1:
3281         document.warning("Malformed document! Missing \\cite_engine")
3282     else:
3283         engine = get_value(document.header, "\\cite_engine", i)
3284
3285     if not engine in ["biblatex", "biblatex-natbib"]:
3286         return
3287
3288     # Citation insets that support qualified lists, with their LaTeX code
3289     ql_citations = {
3290         "cite" : "cites",
3291         "Cite" : "Cites",
3292         "citet" : "textcites",
3293         "Citet" : "Textcites",
3294         "citep" : "parencites",
3295         "Citep" : "Parencites",
3296         "Footcite" : "Smartcites",
3297         "footcite" : "smartcites",
3298         "Autocite" : "Autocites",
3299         "autocite" : "autocites",
3300         }
3301
3302     i = 0
3303     while (True):
3304         i = find_token(document.body, "\\begin_inset CommandInset citation", i)
3305         if i == -1:
3306             break
3307         j = find_end_of_inset(document.body, i)
3308         if j == -1:
3309             document.warning("Can't find end of citation inset at line %d!!" %(i))
3310             i += 1
3311             continue
3312
3313         k = find_token(document.body, "LatexCommand", i, j)
3314         if k == -1:
3315             document.warning("Can't find LatexCommand for citation inset at line %d!" %(i))
3316             i = j + 1
3317             continue
3318
3319         cmd = get_value(document.body, "LatexCommand", k)
3320         if not cmd in list(ql_citations.keys()):
3321             i = j + 1
3322             continue
3323
3324         pres = find_token(document.body, "pretextlist", i, j)
3325         posts = find_token(document.body, "posttextlist", i, j)
3326         if pres == -1 and posts == -1:
3327             # nothing to do.
3328             i = j + 1
3329             continue
3330
3331         key = get_quoted_value(document.body, "key", i, j)
3332         if not key:
3333             document.warning("Citation inset at line %d does not have a key!" %(i))
3334             i = j + 1
3335             continue
3336
3337         keys = key.split(",")
3338         ukeys = list(set(keys))
3339         if len(keys) == len(ukeys):
3340             # no duplicates.
3341             i = j + 1
3342             continue
3343
3344         pretexts = get_quoted_value(document.body, "pretextlist", pres)
3345         posttexts = get_quoted_value(document.body, "posttextlist", posts)
3346
3347         pre = get_quoted_value(document.body, "before", i, j)
3348         post = get_quoted_value(document.body, "after", i, j)
3349         prelist = pretexts.split("\t")
3350         premap = dict()
3351         for pp in prelist:
3352             ppp = pp.split(" ", 1)
3353             val = ""
3354             if len(ppp) > 1:
3355                 val = ppp[1]
3356             else:
3357                 val = ""
3358             if ppp[0] in premap:
3359                 premap[ppp[0]] = premap[ppp[0]] + "\t" + val
3360             else:
3361                 premap[ppp[0]] = val
3362         postlist = posttexts.split("\t")
3363         postmap = dict()
3364         num = 1
3365         for pp in postlist:
3366             ppp = pp.split(" ", 1)
3367             val = ""
3368             if len(ppp) > 1:
3369                 val = ppp[1]
3370             else:
3371                 val = ""
3372             if ppp[0] in postmap:
3373                 postmap[ppp[0]] = postmap[ppp[0]] + "\t" + val
3374             else:
3375                 postmap[ppp[0]] = val
3376         # Replace known new commands with ERT
3377         if "(" in pre or ")" in pre:
3378             pre = "{" + pre + "}"
3379         if "(" in post or ")" in post:
3380             post = "{" + post + "}"
3381         res = "\\" + ql_citations[cmd]
3382         if pre:
3383             res += "(" + pre + ")"
3384         if post:
3385             res += "(" + post + ")"
3386         elif pre:
3387             res += "()"
3388         for kk in keys:
3389             if premap.get(kk, "") != "":
3390                 akeys = premap[kk].split("\t", 1)
3391                 akey = akeys[0]
3392                 if akey != "":
3393                     res += "[" + akey + "]"
3394                 if len(akeys) > 1:
3395                     premap[kk] = "\t".join(akeys[1:])
3396                 else:
3397                     premap[kk] = ""
3398             if postmap.get(kk, "") != "":
3399                 akeys = postmap[kk].split("\t", 1)
3400                 akey = akeys[0]
3401                 if akey != "":
3402                     res += "[" + akey + "]"
3403                 if len(akeys) > 1:
3404                     postmap[kk] = "\t".join(akeys[1:])
3405                 else:
3406                     postmap[kk] = ""
3407             elif premap.get(kk, "") != "":
3408                 res += "[]"
3409             res += "{" + kk + "}"
3410         document.body[i:j+1] = put_cmd_in_ert([res])
3411
3412
3413 def convert_pagesizenames(document):
3414     " Convert LyX page sizes names "
3415
3416     i = find_token(document.header, "\\papersize", 0)
3417     if i == -1:
3418         document.warning("Malformed LyX document! Missing \\papersize header.")
3419         return
3420     oldnames = ["letterpaper", "legalpaper", "executivepaper", \
3421                 "a0paper", "a1paper", "a2paper", "a3paper", "a4paper", "a5paper", "a6paper", \
3422                 "b0paper", "b1paper", "b2paper", "b3paper", "b4paper", "b5paper", "b6paper", \
3423                 "c0paper", "c1paper", "c2paper", "c3paper", "c4paper", "c5paper", "c6paper"]
3424     val = get_value(document.header, "\\papersize", i)
3425     if val in oldnames:
3426         newval = val.replace("paper", "")
3427         document.header[i] = "\\papersize " + newval
3428
3429 def revert_pagesizenames(document):
3430     " Convert LyX page sizes names "
3431
3432     i = find_token(document.header, "\\papersize", 0)
3433     if i == -1:
3434         document.warning("Malformed LyX document! Missing \\papersize header.")
3435         return
3436     newnames = ["letter", "legal", "executive", \
3437                 "a0", "a1", "a2", "a3", "a4", "a5", "a6", \
3438                 "b0", "b1", "b2", "b3", "b4", "b5", "b6", \
3439                 "c0", "c1", "c2", "c3", "c4", "c5", "c6"]
3440     val = get_value(document.header, "\\papersize", i)
3441     if val in newnames:
3442         newval = val + "paper"
3443         document.header[i] = "\\papersize " + newval
3444
3445
3446 def revert_theendnotes(document):
3447     " Reverts native support of \\theendnotes to TeX-code "
3448
3449     if not "endnotes" in document.get_module_list() and not "foottoend" in document.get_module_list():
3450         return
3451
3452     i = 0
3453     while True:
3454         i = find_token(document.body, "\\begin_inset FloatList endnote", i + 1)
3455         if i == -1:
3456             return
3457         j = find_end_of_inset(document.body, i)
3458         if j == -1:
3459             document.warning("Malformed LyX document: Can't find end of FloatList inset")
3460             continue
3461
3462         document.body[i : j + 1] = put_cmd_in_ert("\\theendnotes")
3463
3464
3465 def revert_enotez(document):
3466     " Reverts native support of enotez package to TeX-code "
3467
3468     if not "enotez" in document.get_module_list() and not "foottoenotez" in document.get_module_list():
3469         return
3470
3471     use = False
3472     if find_token(document.body, "\\begin_inset Flex Endnote", 0) != -1:
3473         use = True
3474
3475     revert_flex_inset(document.body, "Endnote", "\\endnote")
3476
3477     i = 0
3478     while True:
3479         i = find_token(document.body, "\\begin_inset FloatList endnote", i + 1)
3480         if i == -1:
3481             break
3482         j = find_end_of_inset(document.body, i)
3483         if j == -1:
3484             document.warning("Malformed LyX document: Can't find end of FloatList inset")
3485             continue
3486
3487         use = True
3488         document.body[i : j + 1] = put_cmd_in_ert("\\printendnotes")
3489
3490     if use:
3491         add_to_preamble(document, ["\\usepackage{enotez}"])
3492     document.del_module("enotez")
3493     document.del_module("foottoenotez")
3494
3495
3496 def revert_memoir_endnotes(document):
3497     " Reverts native support of memoir endnotes to TeX-code "
3498
3499     if document.textclass != "memoir":
3500         return
3501
3502     encommand = "\\pagenote"
3503     modules = document.get_module_list()
3504     if "enotez" in modules or "foottoenotez" in modules or "endnotes" in modules or "foottoend" in modules:
3505         encommand = "\\endnote"
3506
3507     revert_flex_inset(document.body, "Endnote", encommand)
3508
3509     i = 0
3510     while True:
3511         i = find_token(document.body, "\\begin_inset FloatList pagenote", i + 1)
3512         if i == -1:
3513             break
3514         j = find_end_of_inset(document.body, i)
3515         if j == -1:
3516             document.warning("Malformed LyX document: Can't find end of FloatList inset")
3517             continue
3518
3519         if document.body[i] == "\\begin_inset FloatList pagenote*":
3520             document.body[i : j + 1] = put_cmd_in_ert("\\printpagenotes*")
3521         else:
3522             document.body[i : j + 1] = put_cmd_in_ert("\\printpagenotes")
3523         add_to_preamble(document, ["\\makepagenote"])
3524
3525
3526 def revert_totalheight(document):
3527     " Reverts graphics height parameter from totalheight to height "
3528
3529     i = 0
3530     while (True):
3531         i = find_token(document.body, "\\begin_inset Graphics", i)
3532         if i == -1:
3533             break
3534         j = find_end_of_inset(document.body, i)
3535         if j == -1:
3536             document.warning("Can't find end of graphics inset at line %d!!" %(i))
3537             i += 1
3538             continue
3539
3540         rx = re.compile(r'\s*special\s*(\S+)$')
3541         k = find_re(document.body, rx, i, j)
3542         special = ""
3543         oldheight = ""
3544         if k != -1:
3545             m = rx.match(document.body[k])
3546             if m:
3547                 special = m.group(1)
3548             mspecial = special.split(',')
3549             for spc in mspecial:
3550                 if spc[:7] == "height=":
3551                     oldheight = spc.split('=')[1]
3552                     mspecial.remove(spc)
3553                     break
3554             if len(mspecial) > 0:
3555                 special = ",".join(mspecial)
3556             else:
3557                 special = ""
3558
3559         rx = re.compile(r'(\s*height\s*)(\S+)$')
3560         kk = find_re(document.body, rx, i, j)
3561         if kk != -1:
3562             m = rx.match(document.body[kk])
3563             val = ""
3564             if m:
3565                 val = m.group(2)
3566                 if k != -1:
3567                     if special != "":
3568                         val = val + "," + special
3569                     document.body[k] = "\tspecial " + "totalheight=" + val
3570                 else:
3571                     document.body.insert(kk, "\tspecial totalheight=" + val) 
3572                 if oldheight != "":
3573                     document.body[kk] = m.group(1) + oldheight
3574                 else:
3575                     del document.body[kk]
3576         elif oldheight != "":
3577             if special != "":
3578                 document.body[k] = "\tspecial " + special
3579                 document.body.insert(k, "\theight " + oldheight)
3580             else:
3581                 document.body[k] = "\theight " + oldheight
3582         i = j + 1
3583
3584
3585 def convert_totalheight(document):
3586     " Converts graphics height parameter from totalheight to height "
3587
3588     i = 0
3589     while (True):
3590         i = find_token(document.body, "\\begin_inset Graphics", i)
3591         if i == -1:
3592             break
3593         j = find_end_of_inset(document.body, i)
3594         if j == -1:
3595             document.warning("Can't find end of graphics inset at line %d!!" %(i))
3596             i += 1
3597             continue
3598
3599         rx = re.compile(r'\s*special\s*(\S+)$')
3600         k = find_re(document.body, rx, i, j)
3601         special = ""
3602         newheight = ""
3603         if k != -1:
3604             m = rx.match(document.body[k])
3605             if m:
3606                 special = m.group(1)
3607             mspecial = special.split(',')
3608             for spc in mspecial:
3609                 if spc[:12] == "totalheight=":
3610                     newheight = spc.split('=')[1]
3611                     mspecial.remove(spc)
3612                     break
3613             if len(mspecial) > 0:
3614                 special = ",".join(mspecial)
3615             else:
3616                 special = ""
3617
3618         rx = re.compile(r'(\s*height\s*)(\S+)$')
3619         kk = find_re(document.body, rx, i, j)
3620         if kk != -1:
3621             m = rx.match(document.body[kk])
3622             val = ""
3623             if m:
3624                 val = m.group(2)
3625                 if k != -1:
3626                     if special != "":
3627                         val = val + "," + special
3628                     document.body[k] = "\tspecial " + "height=" + val
3629                 else:
3630                     document.body.insert(kk + 1, "\tspecial height=" + val) 
3631                 if newheight != "":
3632                     document.body[kk] = m.group(1) + newheight
3633                 else:
3634                     del document.body[kk]
3635         elif newheight != "":
3636             document.body.insert(k, "\theight " + newheight) 
3637         i = j + 1
3638
3639 ##
3640 # Conversion hub
3641 #
3642
3643 supported_versions = ["2.4.0", "2.4"]
3644 convert = [
3645            [545, [convert_lst_literalparam]],
3646            [546, []],
3647            [547, []],
3648            [548, []],
3649            [549, []],
3650            [550, [convert_fontenc]],
3651            [551, []],
3652            [552, [convert_aaencoding]],
3653            [553, []],
3654            [554, []],
3655            [555, []],
3656            [556, []],
3657            [557, [convert_vcsinfo]],
3658            [558, [removeFrontMatterStyles]],
3659            [559, []],
3660            [560, []],
3661            [561, [convert_latexFonts]], # Handle dejavu, ibmplex fonts in GUI
3662            [562, []],
3663            [563, []],
3664            [564, []],
3665            [565, [convert_AdobeFonts]], # Handle adobe fonts in GUI
3666            [566, [convert_hebrew_parentheses]],
3667            [567, []],
3668            [568, []],
3669            [569, []],
3670            [570, []],
3671            [571, []],
3672            [572, [convert_notoFonts]],  # Added options thin, light, extralight for Noto
3673            [573, [convert_inputencoding_namechange]],
3674            [574, [convert_ruby_module, convert_utf8_japanese]],
3675            [575, [convert_lineno]],
3676            [576, []],
3677            [577, [convert_linggloss]],
3678            [578, []],
3679            [579, []],
3680            [580, []],
3681            [581, [convert_osf]],
3682            [582, [convert_AdobeFonts,convert_latexFonts,convert_notoFonts,convert_CantarellFont,convert_FiraFont]],# old font re-converterted due to extra options
3683            [583, [convert_ChivoFont,convert_Semibolds,convert_NotoRegulars,convert_CrimsonProFont]],
3684            [584, []],
3685            [585, [convert_pagesizes]],
3686            [586, []],
3687            [587, [convert_pagesizenames]],
3688            [588, []],
3689            [589, [convert_totalheight]]
3690           ]
3691
3692 revert =  [[588, [revert_totalheight]],
3693            [587, [revert_memoir_endnotes,revert_enotez,revert_theendnotes]],
3694            [586, [revert_pagesizenames]],
3695            [585, [revert_dupqualicites]],
3696            [584, [revert_pagesizes,revert_komafontsizes]],
3697            [583, [revert_vcsinfo_rev_abbrev]],
3698            [582, [revert_ChivoFont,revert_CrimsonProFont]],
3699            [581, [revert_CantarellFont,revert_FiraFont]],
3700            [580, [revert_texfontopts,revert_osf]],
3701            [579, [revert_minionpro, revert_plainNotoFonts_xopts, revert_notoFonts_xopts, revert_IBMFonts_xopts, revert_AdobeFonts_xopts, revert_font_opts]], # keep revert_font_opts last!
3702            [578, [revert_babelfont]],
3703            [577, [revert_drs]],
3704            [576, [revert_linggloss, revert_subexarg]],
3705            [575, [revert_new_languages]],
3706            [574, [revert_lineno]],
3707            [573, [revert_ruby_module, revert_utf8_japanese]],
3708            [572, [revert_inputencoding_namechange]],
3709            [571, [revert_notoFonts]],
3710            [570, [revert_cmidruletrimming]],
3711            [569, [revert_bibfileencodings]],
3712            [568, [revert_tablestyle]],
3713            [567, [revert_soul]],
3714            [566, [revert_malayalam]],
3715            [565, [revert_hebrew_parentheses]],
3716            [564, [revert_AdobeFonts]],
3717            [563, [revert_lformatinfo]],
3718            [562, [revert_listpargs]],
3719            [561, [revert_l7ninfo]],
3720            [560, [revert_latexFonts]], # Handle dejavu, ibmplex fonts in user preamble
3721            [559, [revert_timeinfo, revert_namenoextinfo]],
3722            [558, [revert_dateinfo]],
3723            [557, [addFrontMatterStyles]],
3724            [556, [revert_vcsinfo]],
3725            [555, [revert_bibencoding]],
3726            [554, [revert_vcolumns]],
3727            [553, [revert_stretchcolumn]],
3728            [552, [revert_tuftecite]],
3729            [551, [revert_floatpclass, revert_floatalignment, revert_aaencoding]],
3730            [550, [revert_nospellcheck]],
3731            [549, [revert_fontenc]],
3732            [548, []],# dummy format change
3733            [547, [revert_lscape]],
3734            [546, [revert_xcharter]],
3735            [545, [revert_paratype]],
3736            [544, [revert_lst_literalparam]]
3737           ]
3738
3739
3740 if __name__ == "__main__":
3741     pass