]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_4.py
Add more page sizes to KOMA and memoir
[lyx.git] / lib / lyx2lyx / lyx_2_4.py
1 # -*- coding: utf-8 -*-
2 # This file is part of lyx2lyx
3 # Copyright (C) 2018 The LyX team
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 """ Convert files to the file format generated by lyx 2.4"""
20
21 import re, string
22 import unicodedata
23 import sys, os
24
25 from datetime import (datetime, date, time)
26
27 # Uncomment only what you need to import, please.
28
29 from parser_tools import (count_pars_in_inset, 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             return
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
661
662 def convert_fontenc(document):
663     " Convert default fontenc setting "
664
665     i = find_token(document.header, "\\fontencoding global", 0)
666     if i == -1:
667         return
668
669     document.header[i] = document.header[i].replace("global", "auto")
670
671
672 def revert_fontenc(document):
673     " Revert default fontenc setting "
674
675     i = find_token(document.header, "\\fontencoding auto", 0)
676     if i == -1:
677         return
678
679     document.header[i] = document.header[i].replace("auto", "global")
680
681
682 def revert_nospellcheck(document):
683     " Remove nospellcheck font info param "
684
685     i = 0
686     while True:
687         i = find_token(document.body, '\\nospellcheck', i)
688         if i == -1:
689             return
690         del document.body[i]
691
692
693 def revert_floatpclass(document):
694     " Remove float placement params 'document' and 'class' "
695
696     del_token(document.header, "\\float_placement class")
697
698     i = 0
699     while True:
700         i = find_token(document.body, '\\begin_inset Float', i+1)
701         if i == -1:
702             break
703         j = find_end_of_inset(document.body, i)
704         k = find_token(document.body, 'placement class', i, i + 2)
705         if k == -1:
706             k = find_token(document.body, 'placement document', i, i + 2)
707             if k != -1:
708                 del document.body[k]
709             continue
710         del document.body[k]
711
712
713 def revert_floatalignment(document):
714     " Remove float alignment params "
715
716     galignment = get_value(document.header, "\\float_alignment", delete=True)
717
718     i = 0
719     while True:
720         i = find_token(document.body, '\\begin_inset Float', i+1)
721         if i == -1:
722             break
723         j = find_end_of_inset(document.body, i)
724         if j == -1:
725             document.warning("Malformed LyX document: Can't find end of inset at line " + str(i))
726             continue
727         k = find_token(document.body, 'alignment', i, i+4)
728         if k == -1:
729             i = j
730             continue
731         alignment = get_value(document.body, "alignment", k)
732         if alignment == "document":
733             alignment = galignment
734         del document.body[k]
735         l = find_token(document.body, "\\begin_layout Plain Layout", i, j)
736         if l == -1:
737             document.warning("Can't find float layout!")
738             continue
739         alcmd = []
740         if alignment == "left":
741             alcmd = put_cmd_in_ert("\\raggedright{}")
742         elif alignment == "center":
743             alcmd = put_cmd_in_ert("\\centering{}")
744         elif alignment == "right":
745             alcmd = put_cmd_in_ert("\\raggedleft{}")
746         if len(alcmd) > 0:
747             document.body[l+1:l+1] = alcmd
748         i = j
749
750 def revert_tuftecite(document):
751     " Revert \cite commands in tufte classes "
752
753     tufte = ["tufte-book", "tufte-handout"]
754     if document.textclass not in tufte:
755         return
756
757     i = 0
758     while (True):
759         i = find_token(document.body, "\\begin_inset CommandInset citation", i+1)
760         if i == -1:
761             break
762         j = find_end_of_inset(document.body, i)
763         if j == -1:
764             document.warning("Can't find end of citation inset at line %d!!" %(i))
765             continue
766         k = find_token(document.body, "LatexCommand", i, j)
767         if k == -1:
768             document.warning("Can't find LatexCommand for citation inset at line %d!" %(i))
769             i = j
770             continue
771         cmd = get_value(document.body, "LatexCommand", k)
772         if cmd != "cite":
773             i = j
774             continue
775         pre = get_quoted_value(document.body, "before", i, j)
776         post = get_quoted_value(document.body, "after", i, j)
777         key = get_quoted_value(document.body, "key", i, j)
778         if not key:
779             document.warning("Citation inset at line %d does not have a key!" %(i))
780             key = "???"
781         # Replace command with ERT
782         res = "\\cite"
783         if pre:
784             res += "[" + pre + "]"
785         if post:
786             res += "[" + post + "]"
787         elif pre:
788             res += "[]"
789         res += "{" + key + "}"
790         document.body[i:j+1] = put_cmd_in_ert([res])
791         i = j
792
793
794 def revert_stretchcolumn(document):
795     " We remove the column varwidth flags or everything else will become a mess. "
796     i = 0
797     while True:
798         i = find_token(document.body, "\\begin_inset Tabular", i+1)
799         if i == -1:
800             return
801         j = find_end_of_inset(document.body, i+1)
802         if j == -1:
803             document.warning("Malformed LyX document: Could not find end of tabular.")
804             continue
805         for k in range(i, j):
806             if re.search('^<column.*varwidth="[^"]+".*>$', document.body[k]):
807                 document.warning("Converting 'tabularx'/'xltabular' table to normal table.")
808                 document.body[k] = document.body[k].replace(' varwidth="true"', '')
809
810
811 def revert_vcolumns(document):
812     " Revert standard columns with line breaks etc. "
813     i = 0
814     needvarwidth = False
815     needarray = False
816     try:
817         while True:
818             i = find_token(document.body, "\\begin_inset Tabular", i+1)
819             if i == -1:
820                 return
821             j = find_end_of_inset(document.body, i)
822             if j == -1:
823                 document.warning("Malformed LyX document: Could not find end of tabular.")
824                 continue
825
826             # Collect necessary column information
827             m = i + 1
828             nrows = int(document.body[i+1].split('"')[3])
829             ncols = int(document.body[i+1].split('"')[5])
830             col_info = []
831             for k in range(ncols):
832                 m = find_token(document.body, "<column", m)
833                 width = get_option_value(document.body[m], 'width')
834                 varwidth = get_option_value(document.body[m], 'varwidth')
835                 alignment = get_option_value(document.body[m], 'alignment')
836                 special = get_option_value(document.body[m], 'special')
837                 col_info.append([width, varwidth, alignment, special, m])
838
839             # Now parse cells
840             m = i + 1
841             lines = []
842             for row in range(nrows):
843                 for col in range(ncols):
844                     m = find_token(document.body, "<cell", m)
845                     multicolumn = get_option_value(document.body[m], 'multicolumn')
846                     multirow = get_option_value(document.body[m], 'multirow')
847                     width = get_option_value(document.body[m], 'width')
848                     rotate = get_option_value(document.body[m], 'rotate')
849                     # Check for: linebreaks, multipars, non-standard environments
850                     begcell = m
851                     endcell = find_token(document.body, "</cell>", begcell)
852                     vcand = False
853                     if find_token(document.body, "\\begin_inset Newline", begcell, endcell) != -1:
854                         vcand = True
855                     elif count_pars_in_inset(document.body, begcell + 2) > 1:
856                         vcand = True
857                     elif get_value(document.body, "\\begin_layout", begcell) != "Plain Layout":
858                         vcand = True
859                     if vcand and rotate == "" and ((multicolumn == "" and multirow == "") or width == ""):
860                         if col_info[col][0] == "" and col_info[col][1] == "" and col_info[col][3] == "":
861                             needvarwidth = True
862                             alignment = col_info[col][2]
863                             col_line = col_info[col][4]
864                             vval = ""
865                             if alignment == "center":
866                                 vval = ">{\\centering}"
867                             elif  alignment == "left":
868                                 vval = ">{\\raggedright}"
869                             elif alignment == "right":
870                                 vval = ">{\\raggedleft}"
871                             if vval != "":
872                                 needarray = True
873                             vval += "V{\\linewidth}"
874
875                             document.body[col_line] = document.body[col_line][:-1] + " special=\"" + vval + "\">"
876                             # ERT newlines and linebreaks (since LyX < 2.4 automatically inserts parboxes
877                             # with newlines, and we do not want that)
878                             while True:
879                                 endcell = find_token(document.body, "</cell>", begcell)
880                                 linebreak = False
881                                 nl = find_token(document.body, "\\begin_inset Newline newline", begcell, endcell)
882                                 if nl == -1:
883                                     nl = find_token(document.body, "\\begin_inset Newline linebreak", begcell, endcell)
884                                     if nl == -1:
885                                          break
886                                     linebreak = True
887                                 nle = find_end_of_inset(document.body, nl)
888                                 del(document.body[nle:nle+1])
889                                 if linebreak:
890                                     document.body[nl:nl+1] = put_cmd_in_ert("\\linebreak{}")
891                                 else:
892                                     document.body[nl:nl+1] = put_cmd_in_ert("\\\\")
893                     m += 1
894
895             i = j
896
897     finally:
898         if needarray == True:
899             add_to_preamble(document, ["\\usepackage{array}"])
900         if needvarwidth == True:
901             add_to_preamble(document, ["\\usepackage{varwidth}"])
902
903
904 def revert_bibencoding(document):
905     " Revert bibliography encoding "
906
907     # Get cite engine
908     engine = "basic"
909     i = find_token(document.header, "\\cite_engine", 0)
910     if i == -1:
911         document.warning("Malformed document! Missing \\cite_engine")
912     else:
913         engine = get_value(document.header, "\\cite_engine", i)
914
915     # Check if biblatex
916     biblatex = False
917     if engine in ["biblatex", "biblatex-natbib"]:
918         biblatex = True
919
920     # Map lyx to latex encoding names
921     encodings = {
922         "utf8" : "utf8",
923         "utf8x" : "utf8x",
924         "armscii8" : "armscii8",
925         "iso8859-1" : "latin1",
926         "iso8859-2" : "latin2",
927         "iso8859-3" : "latin3",
928         "iso8859-4" : "latin4",
929         "iso8859-5" : "iso88595",
930         "iso8859-6" : "8859-6",
931         "iso8859-7" : "iso-8859-7",
932         "iso8859-8" : "8859-8",
933         "iso8859-9" : "latin5",
934         "iso8859-13" : "latin7",
935         "iso8859-15" : "latin9",
936         "iso8859-16" : "latin10",
937         "applemac" : "applemac",
938         "cp437" : "cp437",
939         "cp437de" : "cp437de",
940         "cp850" : "cp850",
941         "cp852" : "cp852",
942         "cp855" : "cp855",
943         "cp858" : "cp858",
944         "cp862" : "cp862",
945         "cp865" : "cp865",
946         "cp866" : "cp866",
947         "cp1250" : "cp1250",
948         "cp1251" : "cp1251",
949         "cp1252" : "cp1252",
950         "cp1255" : "cp1255",
951         "cp1256" : "cp1256",
952         "cp1257" : "cp1257",
953         "koi8-r" : "koi8-r",
954         "koi8-u" : "koi8-u",
955         "pt154" : "pt154",
956         "utf8-platex" : "utf8",
957         "ascii" : "ascii"
958     }
959
960     i = 0
961     bibresources = []
962     while (True):
963         i = find_token(document.body, "\\begin_inset CommandInset bibtex", i+1)
964         if i == -1:
965             break
966         j = find_end_of_inset(document.body, i)
967         if j == -1:
968             document.warning("Can't find end of bibtex inset at line %d!!" %(i))
969             continue
970         encoding = get_quoted_value(document.body, "encoding", i, j)
971         if not encoding:
972             continue
973         # remove encoding line
974         k = find_token(document.body, "encoding", i, j)
975         if k != -1:
976             del document.body[k]
977         if encoding == "default":
978             continue
979         # Re-find inset end line
980         j = find_end_of_inset(document.body, i)
981         if biblatex:
982             biblio_options = ""
983             h = find_token(document.header, "\\biblio_options", 0)
984             if h != -1:
985                 biblio_options = get_value(document.header, "\\biblio_options", h)
986                 if not "bibencoding" in biblio_options:
987                      document.header[h] += ",bibencoding=%s" % encodings[encoding]
988             else:
989                 bs = find_token(document.header, "\\biblatex_bibstyle", 0)
990                 if bs == -1:
991                     # this should not happen
992                     document.warning("Malformed LyX document! No \\biblatex_bibstyle header found!")
993                 else:
994                     document.header[bs-1 : bs-1] = ["\\biblio_options bibencoding=" + encodings[encoding]]
995         else:
996             document.body[j+1:j+1] = put_cmd_in_ert("\\egroup")
997             document.body[i:i] = put_cmd_in_ert("\\bgroup\\inputencoding{" + encodings[encoding] + "}")
998
999         i = j
1000
1001
1002
1003 def convert_vcsinfo(document):
1004     " Separate vcs Info inset from buffer Info inset. "
1005
1006     types = {
1007         "vcs-revision" : "revision",
1008         "vcs-tree-revision" : "tree-revision",
1009         "vcs-author" : "author",
1010         "vcs-time" : "time",
1011         "vcs-date" : "date"
1012     }
1013     i = 0
1014     while True:
1015         i = find_token(document.body, "\\begin_inset Info", i+1)
1016         if i == -1:
1017             return
1018         j = find_end_of_inset(document.body, i+1)
1019         if j == -1:
1020             document.warning("Malformed LyX document: Could not find end of Info inset.")
1021             continue
1022         tp = find_token(document.body, 'type', i, j)
1023         tpv = get_quoted_value(document.body, "type", tp)
1024         if tpv != "buffer":
1025             continue
1026         arg = find_token(document.body, 'arg', i, j)
1027         argv = get_quoted_value(document.body, "arg", arg)
1028         if argv not in list(types.keys()):
1029             continue
1030         document.body[tp] = "type \"vcs\""
1031         document.body[arg] = "arg \"" + types[argv] + "\""
1032
1033
1034 def revert_vcsinfo(document):
1035     " Merge vcs Info inset to buffer Info inset. "
1036
1037     args = ["revision", "tree-revision", "author", "time", "date" ]
1038     i = 0
1039     while True:
1040         i = find_token(document.body, "\\begin_inset Info", i+1)
1041         if i == -1:
1042             return
1043         j = find_end_of_inset(document.body, i+1)
1044         if j == -1:
1045             document.warning("Malformed LyX document: Could not find end of Info inset.")
1046             continue
1047         tp = find_token(document.body, 'type', i, j)
1048         tpv = get_quoted_value(document.body, "type", tp)
1049         if tpv != "vcs":
1050             continue
1051         arg = find_token(document.body, 'arg', i, j)
1052         argv = get_quoted_value(document.body, "arg", arg)
1053         if argv not in args:
1054             document.warning("Malformed Info inset. Invalid vcs arg.")
1055             continue
1056         document.body[tp] = "type \"buffer\""
1057         document.body[arg] = "arg \"vcs-" + argv + "\""
1058
1059 def revert_vcsinfo_rev_abbrev(document):
1060     " Convert abbreviated revisions to regular revisions. "
1061
1062     i = 0
1063     while True:
1064         i = find_token(document.body, "\\begin_inset Info", i+1)
1065         if i == -1:
1066             return
1067         j = find_end_of_inset(document.body, i+1)
1068         if j == -1:
1069             document.warning("Malformed LyX document: Could not find end of Info inset.")
1070             continue
1071         tp = find_token(document.body, 'type', i, j)
1072         tpv = get_quoted_value(document.body, "type", tp)
1073         if tpv != "vcs":
1074             continue
1075         arg = find_token(document.body, 'arg', i, j)
1076         argv = get_quoted_value(document.body, "arg", arg)
1077         if( argv == "revision-abbrev" ):
1078             document.body[arg] = "arg \"revision\""
1079
1080 def revert_dateinfo(document):
1081     " Revert date info insets to static text. "
1082
1083 # FIXME This currently only considers the main language and uses the system locale
1084 # Ideally, it should honor context languages and switch the locale accordingly.
1085
1086     # The date formats for each language using strftime syntax:
1087     # long, short, loclong, locmedium, locshort
1088     dateformats = {
1089         "afrikaans" : ["%A, %d %B %Y", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y/%m/%d"],
1090         "albanian" : ["%A, %d %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1091         "american" : ["%A, %B %d, %Y", "%m/%d/%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1092         "amharic" : ["%A ፣%d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1093         "ancientgreek" : ["%A, %d %B %Y", "%d %b %Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1094         "arabic_arabi" : ["%A، %d %B، %Y", "%d‏/%m‏/%Y", "%d %B، %Y", "%d/%m/%Y", "%d/%m/%Y"],
1095         "arabic_arabtex" : ["%A، %d %B، %Y", "%d‏/%m‏/%Y", "%d %B، %Y", "%d/%m/%Y", "%d/%m/%Y"],
1096         "armenian" : ["%Y թ. %B %d, %A", "%d.%m.%y", "%d %B، %Y", "%d %b، %Y", "%d/%m/%Y"],
1097         "asturian" : ["%A, %d %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d %b %Y", "%d/%m/%Y"],
1098         "australian" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1099         "austrian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1100         "bahasa" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1101         "bahasam" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1102         "basque" : ["%Y(e)ko %B %d, %A", "%y/%m/%d", "%Y %B %d", "%Y %b %d", "%Y/%m/%d"],
1103         "belarusian" : ["%A, %d %B %Y г.", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1104         "bosnian" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%Y-%m-%d"],
1105         "brazilian" : ["%A, %d de %B de %Y", "%d/%m/%Y", "%d de %B de %Y", "%d de %b de %Y", "%d/%m/%Y"],
1106         "breton" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1107         "british" : ["%A, %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1108         "bulgarian" : ["%A, %d %B %Y г.", "%d.%m.%y г.", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1109         "canadian" : ["%A, %B %d, %Y", "%Y-%m-%d", "%B %d, %Y", "%d %b %Y", "%Y-%m-%d"],
1110         "canadien" : ["%A %d %B %Y", "%y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1111         "catalan" : ["%A, %d %B de %Y", "%d/%m/%y", "%d / %B / %Y", "%d / %b / %Y", "%d/%m/%Y"],
1112         "chinese-simplified" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y-%m-%d", "%y-%m-%d"],
1113         "chinese-traditional" : ["%Y年%m月%d日 %A", "%Y/%m/%d", "%Y年%m月%d日", "%Y年%m月%d日", "%y年%m月%d日"],
1114         "coptic" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1115         "croatian" : ["%A, %d. %B %Y.", "%d. %m. %Y.", "%d. %B %Y.", "%d. %b. %Y.", "%d.%m.%Y."],
1116         "czech" : ["%A %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b. %Y", "%d.%m.%Y"],
1117         "danish" : ["%A den %d. %B %Y", "%d/%m/%Y", "%d. %B %Y", "%d. %b %Y", "%d/%m/%Y"],
1118         "divehi" : ["%Y %B %d, %A", "%Y-%m-%d", "%Y %B %d", "%Y %b %d", "%d/%m/%Y"],
1119         "dutch" : ["%A %d %B %Y", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1120         "english" : ["%A, %B %d, %Y", "%m/%d/%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1121         "esperanto" : ["%A, %d %B %Y", "%d %b %Y", "la %d de %B %Y", "la %d de %b %Y", "%m/%d/%Y"],
1122         "estonian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1123         "farsi" : ["%A %d %B %Y", "%Y/%m/%d", "%d %B %Y", "%d %b %Y", "%Y/%m/%d"],
1124         "finnish" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1125         "french" : ["%A %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1126         "friulan" : ["%A %d di %B dal %Y", "%d/%m/%y", "%d di %B dal %Y", "%d di %b dal %Y", "%d/%m/%Y"],
1127         "galician" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d de %b de %Y", "%d/%m/%Y"],
1128         "georgian" : ["%A, %d %B, %Y", "%d.%m.%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1129         "german" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1130         "german-ch" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1131         "german-ch-old" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1132         "greek" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1133         "hebrew" : ["%A, %d ב%B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1134         "hindi" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1135         "icelandic" : ["%A, %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1136         "interlingua" : ["%Y %B %d, %A", "%Y-%m-%d", "le %d de %B %Y", "le %d de %b %Y", "%Y-%m-%d"],
1137         "irish" : ["%A %d %B %Y", "%d/%m/%Y", "%d. %B %Y", "%d. %b %Y", "%d/%m/%Y"],
1138         "italian" : ["%A %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d/%b/%Y", "%d/%m/%Y"],
1139         "japanese" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y/%m/%d", "%y/%m/%d"],
1140         "japanese-cjk" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y/%m/%d", "%y/%m/%d"],
1141         "kannada" : ["%A, %B %d, %Y", "%d/%m/%y", "%d %B %Y", "%d %B %Y", "%d-%m-%Y"],
1142         "kazakh" : ["%Y ж. %d %B, %A", "%d.%m.%y", "%d %B %Y", "%d %B %Y", "%Y-%d-%m"],
1143         "khmer" : ["%A %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %B %Y", "%d/%m/%Y"],
1144         "korean" : ["%Y년 %m월 %d일 %A", "%y. %m. %d.", "%Y년 %m월 %d일", "%Y. %m. %d.", "%y. %m. %d."],
1145         "kurmanji" : ["%A, %d %B %Y", "%d %b %Y", "%d. %B %Y", "%d. %m. %Y", "%Y-%m-%d"],
1146         "lao" : ["%A ທີ %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %B %Y", "%d/%m/%Y"],
1147         "latin" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1148         "latvian" : ["%A, %Y. gada %d. %B", "%d.%m.%y", "%Y. gada %d. %B", "%Y. gada %d. %b", "%d.%m.%Y"],
1149         "lithuanian" : ["%Y m. %B %d d., %A", "%Y-%m-%d", "%Y m. %B %d d.", "%Y m. %B %d d.", "%Y-%m-%d"],
1150         "lowersorbian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1151         "macedonian" : ["%A, %d %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1152         "magyar" : ["%Y. %B %d., %A", "%Y. %m. %d.", "%Y. %B %d.", "%Y. %b %d.", "%Y.%m.%d."],
1153         "malayalam" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1154         "marathi" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1155         "mongolian" : ["%A, %Y оны %m сарын %d", "%Y-%m-%d", "%Y оны %m сарын %d", "%d-%m-%Y", "%d-%m-%Y"],
1156         "naustrian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1157         "newzealand" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1158         "ngerman" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1159         "norsk" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1160         "nynorsk" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1161         "occitan" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1162         "piedmontese" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1163         "polish" : ["%A, %d %B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1164         "polutonikogreek" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1165         "portuguese" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d de %b de %Y", "%Y/%m/%d"],
1166         "romanian" : ["%A, %d %B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1167         "romansh" : ["%A, ils %d da %B %Y", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1168         "russian" : ["%A, %d %B %Y г.", "%d.%m.%Y", "%d %B %Y г.", "%d %b %Y г.", "%d.%m.%Y"],
1169         "samin" : ["%Y %B %d, %A", "%Y-%m-%d", "%B %d. b. %Y", "%b %d. b. %Y", "%d.%m.%Y"],
1170         "sanskrit" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1171         "scottish" : ["%A, %dmh %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1172         "serbian" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1173         "serbian-latin" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1174         "slovak" : ["%A, %d. %B %Y", "%d. %m. %Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1175         "slovene" : ["%A, %d. %B %Y", "%d. %m. %y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1176         "spanish" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B %de %Y", "%d %b %Y", "%d/%m/%Y"],
1177         "spanish-mexico" : ["%A, %d de %B %de %Y", "%d/%m/%y", "%d de %B de %Y", "%d %b %Y", "%d/%m/%Y"],
1178         "swedish" : ["%A %d %B %Y", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1179         "syriac" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1180         "tamil" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1181         "telugu" : ["%d, %B %Y, %A", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1182         "thai" : ["%Aที่ %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1183         "tibetan" : ["%Y %Bའི་ཚེས་%d, %A", "%Y-%m-%d", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1184         "turkish" : ["%d %B %Y %A", "%d.%m.%Y", "%d %B %Y", "%d.%b.%Y", "%d.%m.%Y"],
1185         "turkmen" : ["%d %B %Y %A", "%d.%m.%Y", "%Y ý. %B %d", "%d.%m.%Y ý.", "%d.%m.%y ý."],
1186         "ukrainian" : ["%A, %d %B %Y р.", "%d.%m.%y", "%d %B %Y", "%d %m %Y", "%d.%m.%Y"],
1187         "uppersorbian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1188         "urdu" : ["%A، %d %B، %Y", "%d/%m/%y", "%d %B, %Y", "%d %b %Y", "%d/%m/%Y"],
1189         "vietnamese" : ["%A, %d %B, %Y", "%d/%m/%Y", "%d tháng %B %Y", "%d-%m-%Y", "%d/%m/%Y"],
1190         "welsh" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1191     }
1192
1193     types = ["date", "fixdate", "moddate" ]
1194     lang = get_value(document.header, "\\language")
1195     if lang == "":
1196         document.warning("Malformed LyX document! No \\language header found!")
1197         return
1198
1199     i = 0
1200     while True:
1201         i = find_token(document.body, "\\begin_inset Info", i+1)
1202         if i == -1:
1203             return
1204         j = find_end_of_inset(document.body, i+1)
1205         if j == -1:
1206             document.warning("Malformed LyX document: Could not find end of Info inset.")
1207             continue
1208         tp = find_token(document.body, 'type', i, j)
1209         tpv = get_quoted_value(document.body, "type", tp)
1210         if tpv not in types:
1211             continue
1212         arg = find_token(document.body, 'arg', i, j)
1213         argv = get_quoted_value(document.body, "arg", arg)
1214         isodate = ""
1215         dte = date.today()
1216         if tpv == "fixdate":
1217             datecomps = argv.split('@')
1218             if len(datecomps) > 1:
1219                 argv = datecomps[0]
1220                 isodate = datecomps[1]
1221                 m = re.search('(\d\d\d\d)-(\d\d)-(\d\d)', isodate)
1222                 if m:
1223                     dte = date(int(m.group(1)), int(m.group(2)), int(m.group(3)))
1224 # FIXME if we had the path to the original document (not the one in the tmp dir),
1225 #        we could use the mtime.
1226 #        elif tpv == "moddate":
1227 #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
1228         result = ""
1229         if argv == "ISO":
1230             result = dte.isodate()
1231         elif argv == "long":
1232             result = dte.strftime(dateformats[lang][0])
1233         elif argv == "short":
1234             result = dte.strftime(dateformats[lang][1])
1235         elif argv == "loclong":
1236             result = dte.strftime(dateformats[lang][2])
1237         elif argv == "locmedium":
1238             result = dte.strftime(dateformats[lang][3])
1239         elif argv == "locshort":
1240             result = dte.strftime(dateformats[lang][4])
1241         else:
1242             fmt = argv.replace("MMMM", "%b").replace("MMM", "%b").replace("MM", "%m").replace("M", "%m")
1243             fmt = fmt.replace("yyyy", "%Y").replace("yy", "%y")
1244             fmt = fmt.replace("dddd", "%A").replace("ddd", "%a").replace("dd", "%d")
1245             fmt = re.sub('[^\'%]d', '%d', fmt)
1246             fmt = fmt.replace("'", "")
1247             result = dte.strftime(fmt)
1248         if sys.version_info < (3,0):
1249             # In Python 2, datetime module works with binary strings,
1250             # our dateformat strings are utf8-encoded:
1251             result = result.decode('utf-8')
1252         document.body[i : j+1] = [result]
1253
1254
1255 def revert_timeinfo(document):
1256     " Revert time info insets to static text. "
1257
1258 # FIXME This currently only considers the main language and uses the system locale
1259 # Ideally, it should honor context languages and switch the locale accordingly.
1260 # Also, the time object is "naive", i.e., it does not know of timezones (%Z will
1261 # be empty).
1262
1263     # The time formats for each language using strftime syntax:
1264     # long, short
1265     timeformats = {
1266         "afrikaans" : ["%H:%M:%S %Z", "%H:%M"],
1267         "albanian" : ["%I:%M:%S %p, %Z", "%I:%M %p"],
1268         "american" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1269         "amharic" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1270         "ancientgreek" : ["%H:%M:%S %Z", "%H:%M:%S"],
1271         "arabic_arabi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1272         "arabic_arabtex" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1273         "armenian" : ["%H:%M:%S %Z", "%H:%M"],
1274         "asturian" : ["%H:%M:%S %Z", "%H:%M"],
1275         "australian" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1276         "austrian" : ["%H:%M:%S %Z", "%H:%M"],
1277         "bahasa" : ["%H.%M.%S %Z", "%H.%M"],
1278         "bahasam" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1279         "basque" : ["%H:%M:%S (%Z)", "%H:%M"],
1280         "belarusian" : ["%H:%M:%S, %Z", "%H:%M"],
1281         "bosnian" : ["%H:%M:%S %Z", "%H:%M"],
1282         "brazilian" : ["%H:%M:%S %Z", "%H:%M"],
1283         "breton" : ["%H:%M:%S %Z", "%H:%M"],
1284         "british" : ["%H:%M:%S %Z", "%H:%M"],
1285         "bulgarian" : ["%H:%M:%S %Z", "%H:%M"],
1286         "canadian" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1287         "canadien" : ["%H:%M:%S %Z", "%H h %M"],
1288         "catalan" : ["%H:%M:%S %Z", "%H:%M"],
1289         "chinese-simplified" : ["%Z %p%I:%M:%S", "%p%I:%M"],
1290         "chinese-traditional" : ["%p%I:%M:%S [%Z]", "%p%I:%M"],
1291         "coptic" : ["%H:%M:%S %Z", "%H:%M:%S"],
1292         "croatian" : ["%H:%M:%S (%Z)", "%H:%M"],
1293         "czech" : ["%H:%M:%S %Z", "%H:%M"],
1294         "danish" : ["%H.%M.%S %Z", "%H.%M"],
1295         "divehi" : ["%H:%M:%S %Z", "%H:%M"],
1296         "dutch" : ["%H:%M:%S %Z", "%H:%M"],
1297         "english" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1298         "esperanto" : ["%H:%M:%S %Z", "%H:%M:%S"],
1299         "estonian" : ["%H:%M:%S %Z", "%H:%M"],
1300         "farsi" : ["%H:%M:%S (%Z)", "%H:%M"],
1301         "finnish" : ["%H.%M.%S %Z", "%H.%M"],
1302         "french" : ["%H:%M:%S %Z", "%H:%M"],
1303         "friulan" : ["%H:%M:%S %Z", "%H:%M"],
1304         "galician" : ["%H:%M:%S %Z", "%H:%M"],
1305         "georgian" : ["%H:%M:%S %Z", "%H:%M"],
1306         "german" : ["%H:%M:%S %Z", "%H:%M"],
1307         "german-ch" : ["%H:%M:%S %Z", "%H:%M"],
1308         "german-ch-old" : ["%H:%M:%S %Z", "%H:%M"],
1309         "greek" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1310         "hebrew" : ["%H:%M:%S %Z", "%H:%M"],
1311         "hindi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1312         "icelandic" : ["%H:%M:%S %Z", "%H:%M"],
1313         "interlingua" : ["%H:%M:%S %Z", "%H:%M"],
1314         "irish" : ["%H:%M:%S %Z", "%H:%M"],
1315         "italian" : ["%H:%M:%S %Z", "%H:%M"],
1316         "japanese" : ["%H時%M分%S秒 %Z", "%H:%M"],
1317         "japanese-cjk" : ["%H時%M分%S秒 %Z", "%H:%M"],
1318         "kannada" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1319         "kazakh" : ["%H:%M:%S %Z", "%H:%M"],
1320         "khmer" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1321         "korean" : ["%p %I시%M분 %S초 %Z", "%p %I:%M"],
1322         "kurmanji" : ["%H:%M:%S %Z", "%H:%M:%S"],
1323         "lao" : ["%H ໂມງ%M ນາທີ  %S ວິນາທີ %Z", "%H:%M"],
1324         "latin" : ["%H:%M:%S %Z", "%H:%M:%S"],
1325         "latvian" : ["%H:%M:%S %Z", "%H:%M"],
1326         "lithuanian" : ["%H:%M:%S %Z", "%H:%M"],
1327         "lowersorbian" : ["%H:%M:%S %Z", "%H:%M"],
1328         "macedonian" : ["%H:%M:%S %Z", "%H:%M"],
1329         "magyar" : ["%H:%M:%S %Z", "%H:%M"],
1330         "malayalam" : ["%p %I:%M:%S %Z", "%p %I:%M"],
1331         "marathi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1332         "mongolian" : ["%H:%M:%S %Z", "%H:%M"],
1333         "naustrian" : ["%H:%M:%S %Z", "%H:%M"],
1334         "newzealand" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1335         "ngerman" : ["%H:%M:%S %Z", "%H:%M"],
1336         "norsk" : ["%H:%M:%S %Z", "%H:%M"],
1337         "nynorsk" : ["kl. %H:%M:%S %Z", "%H:%M"],
1338         "occitan" : ["%H:%M:%S %Z", "%H:%M"],
1339         "piedmontese" : ["%H:%M:%S %Z", "%H:%M:%S"],
1340         "polish" : ["%H:%M:%S %Z", "%H:%M"],
1341         "polutonikogreek" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1342         "portuguese" : ["%H:%M:%S %Z", "%H:%M"],
1343         "romanian" : ["%H:%M:%S %Z", "%H:%M"],
1344         "romansh" : ["%H:%M:%S %Z", "%H:%M"],
1345         "russian" : ["%H:%M:%S %Z", "%H:%M"],
1346         "samin" : ["%H:%M:%S %Z", "%H:%M"],
1347         "sanskrit" : ["%H:%M:%S %Z", "%H:%M"],
1348         "scottish" : ["%H:%M:%S %Z", "%H:%M"],
1349         "serbian" : ["%H:%M:%S %Z", "%H:%M"],
1350         "serbian-latin" : ["%H:%M:%S %Z", "%H:%M"],
1351         "slovak" : ["%H:%M:%S %Z", "%H:%M"],
1352         "slovene" : ["%H:%M:%S %Z", "%H:%M"],
1353         "spanish" : ["%H:%M:%S (%Z)", "%H:%M"],
1354         "spanish-mexico" : ["%H:%M:%S %Z", "%H:%M"],
1355         "swedish" : ["kl. %H:%M:%S %Z", "%H:%M"],
1356         "syriac" : ["%H:%M:%S %Z", "%H:%M"],
1357         "tamil" : ["%p %I:%M:%S %Z", "%p %I:%M"],
1358         "telugu" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1359         "thai" : ["%H นาฬิกา %M นาที  %S วินาที %Z", "%H:%M"],
1360         "tibetan" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1361         "turkish" : ["%H:%M:%S %Z", "%H:%M"],
1362         "turkmen" : ["%H:%M:%S %Z", "%H:%M"],
1363         "ukrainian" : ["%H:%M:%S %Z", "%H:%M"],
1364         "uppersorbian" : ["%H:%M:%S %Z", "%H:%M hodź."],
1365         "urdu" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1366         "vietnamese" : ["%H:%M:%S %Z", "%H:%M"],
1367         "welsh" : ["%H:%M:%S %Z", "%H:%M"]
1368     }
1369
1370     types = ["time", "fixtime", "modtime" ]
1371     i = 0
1372     i = find_token(document.header, "\\language", 0)
1373     if i == -1:
1374         # this should not happen
1375         document.warning("Malformed LyX document! No \\language header found!")
1376         return
1377     lang = get_value(document.header, "\\language", i)
1378
1379     i = 0
1380     while True:
1381         i = find_token(document.body, "\\begin_inset Info", i+1)
1382         if i == -1:
1383             return
1384         j = find_end_of_inset(document.body, i+1)
1385         if j == -1:
1386             document.warning("Malformed LyX document: Could not find end of Info inset.")
1387             continue
1388         tp = find_token(document.body, 'type', i, j)
1389         tpv = get_quoted_value(document.body, "type", tp)
1390         if tpv not in types:
1391             continue
1392         arg = find_token(document.body, 'arg', i, j)
1393         argv = get_quoted_value(document.body, "arg", arg)
1394         isotime = ""
1395         dtme = datetime.now()
1396         tme = dtme.time()
1397         if tpv == "fixtime":
1398             timecomps = argv.split('@')
1399             if len(timecomps) > 1:
1400                 argv = timecomps[0]
1401                 isotime = timecomps[1]
1402                 m = re.search('(\d\d):(\d\d):(\d\d)', isotime)
1403                 if m:
1404                     tme = time(int(m.group(1)), int(m.group(2)), int(m.group(3)))
1405                 else:
1406                     m = re.search('(\d\d):(\d\d)', isotime)
1407                     if m:
1408                         tme = time(int(m.group(1)), int(m.group(2)))
1409 # FIXME if we had the path to the original document (not the one in the tmp dir),
1410 #        we could use the mtime.
1411 #        elif tpv == "moddate":
1412 #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
1413         result = ""
1414         if argv == "ISO":
1415             result = tme.isoformat()
1416         elif argv == "long":
1417             result = tme.strftime(timeformats[lang][0])
1418         elif argv == "short":
1419             result = tme.strftime(timeformats[lang][1])
1420         else:
1421             fmt = argv.replace("HH", "%H").replace("H", "%H").replace("hh", "%I").replace("h", "%I")
1422             fmt = fmt.replace("mm", "%M").replace("m", "%M").replace("ss", "%S").replace("s", "%S")
1423             fmt = fmt.replace("zzz", "%f").replace("z", "%f").replace("t", "%Z")
1424             fmt = fmt.replace("AP", "%p").replace("ap", "%p").replace("A", "%p").replace("a", "%p")
1425             fmt = fmt.replace("'", "")
1426             result = dte.strftime(fmt)
1427         document.body[i : j+1] = result
1428
1429
1430 def revert_namenoextinfo(document):
1431     " Merge buffer Info inset type name-noext to name. "
1432
1433     i = 0
1434     while True:
1435         i = find_token(document.body, "\\begin_inset Info", i+1)
1436         if i == -1:
1437             return
1438         j = find_end_of_inset(document.body, i+1)
1439         if j == -1:
1440             document.warning("Malformed LyX document: Could not find end of Info inset.")
1441             continue
1442         tp = find_token(document.body, 'type', i, j)
1443         tpv = get_quoted_value(document.body, "type", tp)
1444         if tpv != "buffer":
1445             continue
1446         arg = find_token(document.body, 'arg', i, j)
1447         argv = get_quoted_value(document.body, "arg", arg)
1448         if argv != "name-noext":
1449             continue
1450         document.body[arg] = "arg \"name\""
1451
1452
1453 def revert_l7ninfo(document):
1454     " Revert l7n Info inset to text. "
1455
1456     i = 0
1457     while True:
1458         i = find_token(document.body, "\\begin_inset Info", i+1)
1459         if i == -1:
1460             return
1461         j = find_end_of_inset(document.body, i+1)
1462         if j == -1:
1463             document.warning("Malformed LyX document: Could not find end of Info inset.")
1464             continue
1465         tp = find_token(document.body, 'type', i, j)
1466         tpv = get_quoted_value(document.body, "type", tp)
1467         if tpv != "l7n":
1468             continue
1469         arg = find_token(document.body, 'arg', i, j)
1470         argv = get_quoted_value(document.body, "arg", arg)
1471         # remove trailing colons, menu accelerator (|...) and qt accelerator (&), while keeping literal " & "
1472         argv = argv.rstrip(':').split('|')[0].replace(" & ", "</amp;>").replace("&", "").replace("</amp;>", " & ")
1473         document.body[i : j+1] = argv
1474
1475
1476 def revert_listpargs(document):
1477     " Reverts listpreamble arguments to TeX-code "
1478     i = 0
1479     while True:
1480         i = find_token(document.body, "\\begin_inset Argument listpreamble:", i+1)
1481         if i == -1:
1482             return
1483         j = find_end_of_inset(document.body, i)
1484         # Find containing paragraph layout
1485         parent = get_containing_layout(document.body, i)
1486         if parent == False:
1487             document.warning("Malformed LyX document: Can't find parent paragraph layout")
1488             continue
1489         parbeg = parent[3]
1490         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1491         endPlain = find_end_of_layout(document.body, beginPlain)
1492         content = document.body[beginPlain + 1 : endPlain]
1493         del document.body[i:j+1]
1494         subst = ["\\begin_inset ERT", "status collapsed", "", "\\begin_layout Plain Layout",
1495                  "{"] + content + ["}", "\\end_layout", "", "\\end_inset", ""]
1496         document.body[parbeg : parbeg] = subst
1497
1498
1499 def revert_lformatinfo(document):
1500     " Revert layout format Info inset to text. "
1501
1502     i = 0
1503     while True:
1504         i = find_token(document.body, "\\begin_inset Info", i+1)
1505         if i == -1:
1506             return
1507         j = find_end_of_inset(document.body, i+1)
1508         if j == -1:
1509             document.warning("Malformed LyX document: Could not find end of Info inset.")
1510             continue
1511         tp = find_token(document.body, 'type', i, j)
1512         tpv = get_quoted_value(document.body, "type", tp)
1513         if tpv != "lyxinfo":
1514             continue
1515         arg = find_token(document.body, 'arg', i, j)
1516         argv = get_quoted_value(document.body, "arg", arg)
1517         if argv != "layoutformat":
1518             continue
1519         # hardcoded for now
1520         document.body[i : j+1] = "69"
1521
1522
1523 def convert_hebrew_parentheses(document):
1524     """ Swap opening/closing parentheses in Hebrew text.
1525
1526     Up to LyX 2.4, "(" was used as closing parenthesis and
1527     ")" as opening parenthesis for Hebrew in the LyX source.
1528     """
1529     # print("convert hebrew parentheses")
1530     current_languages = [document.language]
1531     for i, line in enumerate(document.body):
1532         if line.startswith('\\lang '):
1533             current_languages[-1] = line.lstrip('\\lang ')
1534         elif line.startswith('\\begin_layout'):
1535             current_languages.append(current_languages[-1])
1536             # print (line, current_languages[-1])
1537         elif line.startswith('\\end_layout'):
1538             current_languages.pop()
1539         elif current_languages[-1] == 'hebrew' and not line.startswith('\\'):
1540             document.body[i] = line.replace('(','\x00').replace(')','(').replace('\x00',')')
1541
1542
1543 def revert_hebrew_parentheses(document):
1544     " Store parentheses in Hebrew text reversed"
1545     # This only exists to keep the convert/revert naming convention
1546     convert_hebrew_parentheses(document)
1547
1548
1549 def revert_malayalam(document):
1550     " Set the document language to English but assure Malayalam output "
1551
1552     revert_language(document, "malayalam", "", "malayalam")
1553
1554
1555 def revert_soul(document):
1556     " Revert soul module flex insets to ERT "
1557
1558     flexes = ["Spaceletters", "Strikethrough", "Underline", "Highlight", "Capitalize"]
1559
1560     for flex in flexes:
1561         i = find_token(document.body, "\\begin_inset Flex %s" % flex, 0)
1562         if i != -1:
1563             add_to_preamble(document, ["\\usepackage{soul}"])
1564             break
1565     i = find_token(document.body, "\\begin_inset Flex Highlight", 0)
1566     if i != -1:
1567         add_to_preamble(document, ["\\usepackage{color}"])
1568
1569     revert_flex_inset(document.body, "Spaceletters", "\\so")
1570     revert_flex_inset(document.body, "Strikethrough", "\\st")
1571     revert_flex_inset(document.body, "Underline", "\\ul")
1572     revert_flex_inset(document.body, "Highlight", "\\hl")
1573     revert_flex_inset(document.body, "Capitalize", "\\caps")
1574
1575
1576 def revert_tablestyle(document):
1577     " Remove tablestyle params "
1578
1579     i = 0
1580     i = find_token(document.header, "\\tablestyle")
1581     if i != -1:
1582         del document.header[i]
1583
1584
1585 def revert_bibfileencodings(document):
1586     " Revert individual Biblatex bibliography encodings "
1587
1588     # Get cite engine
1589     engine = "basic"
1590     i = find_token(document.header, "\\cite_engine", 0)
1591     if i == -1:
1592         document.warning("Malformed document! Missing \\cite_engine")
1593     else:
1594         engine = get_value(document.header, "\\cite_engine", i)
1595
1596     # Check if biblatex
1597     biblatex = False
1598     if engine in ["biblatex", "biblatex-natbib"]:
1599         biblatex = True
1600
1601     # Map lyx to latex encoding names
1602     encodings = {
1603         "utf8" : "utf8",
1604         "utf8x" : "utf8x",
1605         "armscii8" : "armscii8",
1606         "iso8859-1" : "latin1",
1607         "iso8859-2" : "latin2",
1608         "iso8859-3" : "latin3",
1609         "iso8859-4" : "latin4",
1610         "iso8859-5" : "iso88595",
1611         "iso8859-6" : "8859-6",
1612         "iso8859-7" : "iso-8859-7",
1613         "iso8859-8" : "8859-8",
1614         "iso8859-9" : "latin5",
1615         "iso8859-13" : "latin7",
1616         "iso8859-15" : "latin9",
1617         "iso8859-16" : "latin10",
1618         "applemac" : "applemac",
1619         "cp437" : "cp437",
1620         "cp437de" : "cp437de",
1621         "cp850" : "cp850",
1622         "cp852" : "cp852",
1623         "cp855" : "cp855",
1624         "cp858" : "cp858",
1625         "cp862" : "cp862",
1626         "cp865" : "cp865",
1627         "cp866" : "cp866",
1628         "cp1250" : "cp1250",
1629         "cp1251" : "cp1251",
1630         "cp1252" : "cp1252",
1631         "cp1255" : "cp1255",
1632         "cp1256" : "cp1256",
1633         "cp1257" : "cp1257",
1634         "koi8-r" : "koi8-r",
1635         "koi8-u" : "koi8-u",
1636         "pt154" : "pt154",
1637         "utf8-platex" : "utf8",
1638         "ascii" : "ascii"
1639     }
1640
1641     i = 0
1642     bibresources = []
1643     while (True):
1644         i = find_token(document.body, "\\begin_inset CommandInset bibtex", i+1)
1645         if i == -1:
1646             break
1647         j = find_end_of_inset(document.body, i)
1648         if j == -1:
1649             document.warning("Can't find end of bibtex inset at line %d!!" %(i))
1650             continue
1651         encodings = get_quoted_value(document.body, "file_encodings", i, j)
1652         if not encodings:
1653             i = j
1654             continue
1655         bibfiles = get_quoted_value(document.body, "bibfiles", i, j).split(",")
1656         opts = get_quoted_value(document.body, "biblatexopts", i, j)
1657         if len(bibfiles) == 0:
1658             document.warning("Bibtex inset at line %d does not have a bibfile!" %(i))
1659         # remove encoding line
1660         k = find_token(document.body, "file_encodings", i, j)
1661         if k != -1:
1662             del document.body[k]
1663         # Re-find inset end line
1664         j = find_end_of_inset(document.body, i)
1665         if biblatex:
1666             enclist = encodings.split("\t")
1667             encmap = dict()
1668             for pp in enclist:
1669                 ppp = pp.split(" ", 1)
1670                 encmap[ppp[0]] = ppp[1]
1671             for bib in bibfiles:
1672                 pr = "\\addbibresource"
1673                 if bib in encmap.keys():
1674                     pr += "[bibencoding=" + encmap[bib] + "]"
1675                 pr += "{" + bib + "}"
1676                 add_to_preamble(document, [pr])
1677             # Insert ERT \\printbibliography and wrap bibtex inset to a Note
1678             pcmd = "printbibliography"
1679             if opts:
1680                 pcmd += "[" + opts + "]"
1681             repl = ["\\begin_inset ERT", "status open", "", "\\begin_layout Plain Layout",\
1682                     "", "", "\\backslash", pcmd, "\\end_layout", "", "\\end_inset", "", "",\
1683                     "\\end_layout", "", "\\begin_layout Standard", "\\begin_inset Note Note",\
1684                     "status open", "", "\\begin_layout Plain Layout" ]
1685             repl += document.body[i:j+1]
1686             repl += ["", "\\end_layout", "", "\\end_inset", "", ""]
1687             document.body[i:j+1] = repl
1688             j += 27
1689
1690         i = j
1691
1692
1693 def revert_cmidruletrimming(document):
1694     " Remove \\cmidrule trimming "
1695
1696     # FIXME: Revert to TeX code?
1697     i = 0
1698     while True:
1699         # first, let's find out if we need to do anything
1700         i = find_token(document.body, '<cell ', i+1)
1701         if i == -1:
1702             return
1703         j = document.body[i].find('trim="')
1704         if j == -1:
1705              continue
1706         rgx = re.compile(r' (bottom|top)line[lr]trim="true"')
1707         # remove trim option
1708         document.body[i] = rgx.sub('', document.body[i])
1709
1710
1711 ruby_inset_def = [
1712     r'### Inserted by lyx2lyx (ruby inset) ###',
1713     r'InsetLayout Flex:Ruby',
1714     r'  LyxType       charstyle',
1715     r'  LatexType     command',
1716     r'  LatexName     ruby',
1717     r'  HTMLTag       ruby',
1718     r'  HTMLAttr      ""',
1719     r'  HTMLInnerTag  rb',
1720     r'  HTMLInnerAttr ""',
1721     r'  BgColor       none',
1722     r'  LabelString   "Ruby"',
1723     r'  Decoration    Conglomerate',
1724     r'  Preamble',
1725     r'    \ifdefined\kanjiskip',
1726     r'      \IfFileExists{okumacro.sty}{\usepackage{okumacro}}{}',
1727     r'    \else \ifdefined\luatexversion',
1728     r'      \usepackage{luatexja-ruby}',
1729     r'    \else \ifdefined\XeTeXversion',
1730     r'      \usepackage{ruby}%',
1731     r'    \fi\fi\fi',
1732     r'    \providecommand{\ruby}[2]{\shortstack{\tiny #2\\#1}}',
1733     r'  EndPreamble',
1734     r'  Argument  post:1',
1735     r'    LabelString  "ruby text"',
1736     r'    MenuString  "Ruby Text|R"',
1737     r'    Tooltip    "Reading aid (ruby, furigana) for Chinese characters."',
1738     r'    Decoration  Conglomerate',
1739     r'    Font',
1740     r'      Size    tiny',
1741     r'    EndFont',
1742     r'    LabelFont',
1743     r'      Size    tiny',
1744     r'    EndFont',
1745     r'    Mandatory  1',
1746     r'  EndArgument',
1747     r'End',
1748 ]
1749
1750 def convert_ruby_module(document):
1751     " Use ruby module instead of local module definition "
1752     if document.del_local_layout(ruby_inset_def):
1753         document.add_module("ruby")
1754
1755 def revert_ruby_module(document):
1756     " Replace ruby module with local module definition "
1757     if document.del_module("ruby"):
1758         document.append_local_layout(ruby_inset_def)
1759
1760
1761 def convert_utf8_japanese(document):
1762     " Use generic utf8 with Japanese documents."
1763     lang = get_value(document.header, "\\language")
1764     if not lang.startswith("japanese"):
1765         return
1766     inputenc = get_value(document.header, "\\inputencoding")
1767     if ((lang == "japanese" and inputenc == "utf8-platex")
1768         or (lang == "japanese-cjk" and inputenc == "utf8-cjk")):
1769         document.set_parameter("inputencoding", "utf8")
1770
1771 def revert_utf8_japanese(document):
1772     " Use Japanese utf8 variants with Japanese documents."
1773     inputenc = get_value(document.header, "\\inputencoding")
1774     if inputenc != "utf8":
1775         return
1776     lang = get_value(document.header, "\\language")
1777     if lang == "japanese":
1778         document.set_parameter("inputencoding", "utf8-platex")
1779     if lang == "japanese-cjk":
1780         document.set_parameter("inputencoding", "utf8-cjk")
1781
1782
1783 def revert_lineno(document):
1784     " Replace lineno setting with user-preamble code."
1785
1786     options = get_quoted_value(document.header, "\\lineno_options",
1787                                delete=True)
1788     if not get_bool_value(document.header, "\\use_lineno", delete=True):
1789         return
1790     if options:
1791         options = "[" + options + "]"
1792     add_to_preamble(document, ["\\usepackage%s{lineno}" % options,
1793                                "\\linenumbers"])
1794
1795 def convert_lineno(document):
1796     " Replace user-preamble code with native lineno support."
1797     use_lineno = 0
1798     options = ""
1799     i = find_token(document.preamble, "\\linenumbers", 1)
1800     if i > -1:
1801         usepkg = re.match(r"\\usepackage(.*){lineno}", document.preamble[i-1])
1802         if usepkg:
1803             use_lineno = 1
1804             options = usepkg.group(1).strip("[]")
1805             del(document.preamble[i-1:i+1])
1806             del_token(document.preamble, "% Added by lyx2lyx", i-2, i-1)
1807
1808     k = find_token(document.header, "\\index ")
1809     if options == "":
1810         document.header[k:k] = ["\\use_lineno %d" % use_lineno]
1811     else:
1812         document.header[k:k] = ["\\use_lineno %d" % use_lineno,
1813                                 "\\lineno_options %s" % options]
1814
1815
1816 def revert_new_languages(document):
1817     """Emulate support for Azerbaijani, Bengali, Church Slavonic, Korean,
1818     and Russian (Petrine orthography)."""
1819
1820     #                lyxname:          (babelname, polyglossianame)
1821     new_languages = {"azerbaijani":    ("azerbaijani", ""),
1822                      "bengali":        ("", "bengali"),
1823                      "churchslavonic": ("", "churchslavonic"),
1824                      "oldrussian":     ("", "russian"),
1825                      "korean":         ("", "korean"),
1826                     }
1827     used_languages = set()
1828     if document.language in new_languages:
1829         used_languages.add(document.language)
1830     i = 0
1831     while True:
1832         i = find_token(document.body, "\\lang", i+1)
1833         if i == -1:
1834             break
1835         if document.body[i][6:].strip() in new_languages:
1836             used_languages.add(document.language)
1837
1838     # Korean is already supported via CJK, so leave as-is for Babel
1839     if ("korean" in used_languages
1840         and get_bool_value(document.header, "\\use_non_tex_fonts")
1841         and get_value(document.header, "\\language_package") in ("default", "auto")):
1842         revert_language(document, "korean", "", "korean")
1843     used_languages.discard("korean")
1844
1845     for lang in used_languages:
1846         revert(lang, *new_languages[lang])
1847
1848
1849 gloss_inset_def = [
1850     r'### Inserted by lyx2lyx (deprecated ling glosses) ###',
1851     r'InsetLayout Flex:Glosse',
1852     r'  LyXType               custom',
1853     r'  LabelString           "Gloss (old version)"',
1854     r'  MenuString            "Gloss (old version)"',
1855     r'  LatexType             environment',
1856     r'  LatexName             linggloss',
1857     r'  Decoration            minimalistic',
1858     r'  LabelFont',
1859     r'    Size                Small',
1860     r'  EndFont',
1861     r'  MultiPar              true',
1862     r'  CustomPars            false',
1863     r'  ForcePlain            true',
1864     r'  ParbreakIsNewline     true',
1865     r'  FreeSpacing           true',
1866     r'  Requires              covington',
1867     r'  Preamble',
1868     r'          \def\glosstr{}',
1869     r'          \@ifundefined{linggloss}{%',
1870     r'          \newenvironment{linggloss}[2][]{',
1871     r'             \def\glosstr{\glt #1}%',
1872     r'             \gll #2}',
1873     r'          {\glosstr\glend}}{}',
1874     r'  EndPreamble',
1875     r'  InToc                 true',
1876     r'  ResetsFont            true',
1877     r'  Argument 1',
1878     r'          Decoration    conglomerate',
1879     r'          LabelString   "Translation"',
1880     r'          MenuString    "Glosse Translation|s"',
1881     r'          Tooltip       "Add a translation for the glosse"',
1882     r'  EndArgument',
1883     r'End'
1884 ]
1885
1886 glosss_inset_def = [
1887     r'### Inserted by lyx2lyx (deprecated ling glosses) ###',
1888     r'InsetLayout Flex:Tri-Glosse',
1889     r'  LyXType               custom',
1890     r'  LabelString           "Tri-Gloss (old version)"',
1891     r'  MenuString            "Tri-Gloss (old version)"',
1892     r'  LatexType             environment',
1893     r'  LatexName             lingglosss',
1894     r'  Decoration            minimalistic',
1895     r'  LabelFont',
1896     r'    Size                Small',
1897     r'  EndFont',
1898     r'  MultiPar              true',
1899     r'  CustomPars            false',
1900     r'  ForcePlain            true',
1901     r'  ParbreakIsNewline     true',
1902     r'  FreeSpacing           true',
1903     r'  InToc                 true',
1904     r'  Requires              covington',
1905     r'  Preamble',
1906     r'          \def\glosstr{}',
1907     r'          \@ifundefined{lingglosss}{%',
1908     r'          \newenvironment{lingglosss}[2][]{',
1909     r'              \def\glosstr{\glt #1}%',
1910     r'              \glll #2}',
1911     r'          {\glosstr\glend}}{}',
1912     r'  EndPreamble',
1913     r'  ResetsFont            true',
1914     r'  Argument 1',
1915     r'          Decoration    conglomerate',
1916     r'          LabelString   "Translation"',
1917     r'          MenuString    "Glosse Translation|s"',
1918     r'          Tooltip       "Add a translation for the glosse"',
1919     r'  EndArgument',
1920     r'End'
1921 ]
1922
1923 def convert_linggloss(document):
1924     " Move old ling glosses to local layout "
1925     if find_token(document.body, '\\begin_inset Flex Glosse', 0) != -1:
1926         document.append_local_layout(gloss_inset_def)
1927     if find_token(document.body, '\\begin_inset Flex Tri-Glosse', 0) != -1:
1928         document.append_local_layout(glosss_inset_def)
1929
1930 def revert_linggloss(document):
1931     " Revert to old ling gloss definitions "
1932     if not "linguistics" in document.get_module_list():
1933         return
1934     document.del_local_layout(gloss_inset_def)
1935     document.del_local_layout(glosss_inset_def)
1936
1937     cov_req = False
1938     glosses = ["\\begin_inset Flex Interlinear Gloss (2 Lines)", "\\begin_inset Flex Interlinear Gloss (3 Lines)"]
1939     for glosse in glosses:
1940         i = 0
1941         while True:
1942             i = find_token(document.body, glosse, i+1)
1943             if i == -1:
1944                 break
1945             j = find_end_of_inset(document.body, i)
1946             if j == -1:
1947                 document.warning("Malformed LyX document: Can't find end of Gloss inset")
1948                 continue
1949
1950             arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
1951             endarg = find_end_of_inset(document.body, arg)
1952             optargcontent = []
1953             if arg != -1:
1954                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
1955                 if argbeginPlain == -1:
1956                     document.warning("Malformed LyX document: Can't find optarg plain Layout")
1957                     continue
1958                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
1959                 optargcontent = document.body[argbeginPlain + 1 : argendPlain - 2]
1960
1961                 # remove Arg insets and paragraph, if it only contains this inset
1962                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
1963                     del document.body[arg - 1 : endarg + 4]
1964                 else:
1965                     del document.body[arg : endarg + 1]
1966
1967             arg = find_token(document.body, "\\begin_inset Argument post:1", i, j)
1968             endarg = find_end_of_inset(document.body, arg)
1969             marg1content = []
1970             if arg != -1:
1971                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
1972                 if argbeginPlain == -1:
1973                     document.warning("Malformed LyX document: Can't find arg 1 plain Layout")
1974                     continue
1975                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
1976                 marg1content = document.body[argbeginPlain + 1 : argendPlain - 2]
1977
1978                 # remove Arg insets and paragraph, if it only contains this inset
1979                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
1980                     del document.body[arg - 1 : endarg + 4]
1981                 else:
1982                     del document.body[arg : endarg + 1]
1983
1984             arg = find_token(document.body, "\\begin_inset Argument post:2", i, j)
1985             endarg = find_end_of_inset(document.body, arg)
1986             marg2content = []
1987             if arg != -1:
1988                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
1989                 if argbeginPlain == -1:
1990                     document.warning("Malformed LyX document: Can't find arg 2 plain Layout")
1991                     continue
1992                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
1993                 marg2content = document.body[argbeginPlain + 1 : argendPlain - 2]
1994
1995                 # remove Arg insets and paragraph, if it only contains this inset
1996                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
1997                     del document.body[arg - 1 : endarg + 4]
1998                 else:
1999                     del document.body[arg : endarg + 1]
2000
2001             arg = find_token(document.body, "\\begin_inset Argument post:3", i, j)
2002             endarg = find_end_of_inset(document.body, arg)
2003             marg3content = []
2004             if arg != -1:
2005                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2006                 if argbeginPlain == -1:
2007                     document.warning("Malformed LyX document: Can't find arg 3 plain Layout")
2008                     continue
2009                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2010                 marg3content = document.body[argbeginPlain + 1 : argendPlain - 2]
2011
2012                 # remove Arg insets and paragraph, if it only contains this inset
2013                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2014                     del document.body[arg - 1 : endarg + 4]
2015                 else:
2016                     del document.body[arg : endarg + 1]
2017
2018             cmd = "\\digloss"
2019             if glosse == "\\begin_inset Flex Interlinear Gloss (3 Lines)":
2020                 cmd = "\\trigloss"
2021
2022             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2023             endInset = find_end_of_inset(document.body, i)
2024             endPlain = find_token_backwards(document.body, "\\end_layout", endInset)
2025             precontent = put_cmd_in_ert(cmd)
2026             if len(optargcontent) > 0:
2027                 precontent += put_cmd_in_ert("[") + optargcontent + put_cmd_in_ert("]")
2028             precontent += put_cmd_in_ert("{")
2029
2030             postcontent = put_cmd_in_ert("}{") + marg1content + put_cmd_in_ert("}{") + marg2content
2031             if cmd == "\\trigloss":
2032                 postcontent += put_cmd_in_ert("}{") + marg3content
2033             postcontent += put_cmd_in_ert("}")
2034
2035             document.body[endPlain:endInset + 1] = postcontent
2036             document.body[beginPlain + 1:beginPlain] = precontent
2037             del document.body[i : beginPlain + 1]
2038             if not cov_req:
2039                 document.append_local_layout("Requires covington")
2040                 cov_req = True
2041             i = beginPlain
2042
2043
2044 def revert_subexarg(document):
2045     " Revert linguistic subexamples with argument to ERT "
2046
2047     if not "linguistics" in document.get_module_list():
2048         return
2049
2050     cov_req = False
2051     i = 0
2052     while True:
2053         i = find_token(document.body, "\\begin_layout Subexample", i+1)
2054         if i == -1:
2055             break
2056         j = find_end_of_layout(document.body, i)
2057         if j == -1:
2058             document.warning("Malformed LyX document: Can't find end of Subexample layout")
2059             continue
2060         while True:
2061             # check for consecutive layouts
2062             k = find_token(document.body, "\\begin_layout", j)
2063             if k == -1 or document.body[k] != "\\begin_layout Subexample":
2064                 break
2065             j = find_end_of_layout(document.body, k)
2066             if j == -1:
2067                  document.warning("Malformed LyX document: Can't find end of Subexample layout")
2068                  continue
2069
2070         arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
2071         if arg == -1:
2072             continue
2073
2074         endarg = find_end_of_inset(document.body, arg)
2075         optargcontent = ""
2076         argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2077         if argbeginPlain == -1:
2078             document.warning("Malformed LyX document: Can't find optarg plain Layout")
2079             continue
2080         argendPlain = find_end_of_inset(document.body, argbeginPlain)
2081         optargcontent = lyx2latex(document, document.body[argbeginPlain + 1 : argendPlain - 2])
2082
2083         # remove Arg insets and paragraph, if it only contains this inset
2084         if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2085             del document.body[arg - 1 : endarg + 4]
2086         else:
2087             del document.body[arg : endarg + 1]
2088
2089         cmd = put_cmd_in_ert("\\begin{subexamples}[" + optargcontent + "]")
2090
2091         # re-find end of layout
2092         j = find_end_of_layout(document.body, i)
2093         if j == -1:
2094             document.warning("Malformed LyX document: Can't find end of Subexample layout")
2095             continue
2096         while True:
2097             # check for consecutive layouts
2098             k = find_token(document.body, "\\begin_layout", j)
2099             if k == -1 or document.body[k] != "\\begin_layout Subexample":
2100                 break
2101             document.body[k : k + 1] = ["\\begin_layout Standard"] + put_cmd_in_ert("\\item ")
2102             j = find_end_of_layout(document.body, k)
2103             if j == -1:
2104                  document.warning("Malformed LyX document: Can't find end of Subexample layout")
2105                  continue
2106
2107         endev = put_cmd_in_ert("\\end{subexamples}")
2108
2109         document.body[j : j] = ["\\end_layout", "", "\\begin_layout Standard"] + endev
2110         document.body[i : i + 1] = ["\\begin_layout Standard"] + cmd \
2111                 + ["\\end_layout", "", "\\begin_layout Standard"] + put_cmd_in_ert("\\item ")
2112         if not cov_req:
2113             document.append_local_layout("Requires covington")
2114             cov_req = True
2115
2116
2117 def revert_drs(document):
2118     " Revert DRS insets (linguistics) to ERT "
2119
2120     if not "linguistics" in document.get_module_list():
2121         return
2122
2123     cov_req = False
2124     drses = ["\\begin_inset Flex DRS", "\\begin_inset Flex DRS*",
2125              "\\begin_inset Flex IfThen-DRS", "\\begin_inset Flex Cond-DRS",
2126              "\\begin_inset Flex QDRS", "\\begin_inset Flex NegDRS",
2127              "\\begin_inset Flex SDRS"]
2128     for drs in drses:
2129         i = 0
2130         while True:
2131             i = find_token(document.body, drs, i+1)
2132             if i == -1:
2133                 break
2134             j = find_end_of_inset(document.body, i)
2135             if j == -1:
2136                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2137                 continue
2138
2139             # Check for arguments
2140             arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
2141             endarg = find_end_of_inset(document.body, arg)
2142             prearg1content = []
2143             if arg != -1:
2144                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2145                 if argbeginPlain == -1:
2146                     document.warning("Malformed LyX document: Can't find Argument 1 plain Layout")
2147                     continue
2148                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2149                 prearg1content = document.body[argbeginPlain + 1 : argendPlain - 2]
2150
2151                 # remove Arg insets and paragraph, if it only contains this inset
2152                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2153                     del document.body[arg - 1 : endarg + 4]
2154                 else:
2155                     del document.body[arg : endarg + 1]
2156
2157             # re-find inset end
2158             j = find_end_of_inset(document.body, i)
2159             if j == -1:
2160                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2161                 continue
2162
2163             arg = find_token(document.body, "\\begin_inset Argument 2", i, j)
2164             endarg = find_end_of_inset(document.body, arg)
2165             prearg2content = []
2166             if arg != -1:
2167                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2168                 if argbeginPlain == -1:
2169                     document.warning("Malformed LyX document: Can't find Argument 2 plain Layout")
2170                     continue
2171                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2172                 prearg2content = document.body[argbeginPlain + 1 : argendPlain - 2]
2173
2174                 # remove Arg insets and paragraph, if it only contains this inset
2175                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2176                     del document.body[arg - 1 : endarg + 4]
2177                 else:
2178                     del document.body[arg : endarg + 1]
2179
2180             # re-find inset end
2181             j = find_end_of_inset(document.body, i)
2182             if j == -1:
2183                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2184                 continue
2185
2186             arg = find_token(document.body, "\\begin_inset Argument post:1", i, j)
2187             endarg = find_end_of_inset(document.body, arg)
2188             postarg1content = []
2189             if arg != -1:
2190                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2191                 if argbeginPlain == -1:
2192                     document.warning("Malformed LyX document: Can't find Argument post:1 plain Layout")
2193                     continue
2194                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2195                 postarg1content = document.body[argbeginPlain + 1 : argendPlain - 2]
2196
2197                 # remove Arg insets and paragraph, if it only contains this inset
2198                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2199                     del document.body[arg - 1 : endarg + 4]
2200                 else:
2201                     del document.body[arg : endarg + 1]
2202
2203             # re-find inset end
2204             j = find_end_of_inset(document.body, i)
2205             if j == -1:
2206                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2207                 continue
2208
2209             arg = find_token(document.body, "\\begin_inset Argument post:2", i, j)
2210             endarg = find_end_of_inset(document.body, arg)
2211             postarg2content = []
2212             if arg != -1:
2213                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2214                 if argbeginPlain == -1:
2215                     document.warning("Malformed LyX document: Can't find Argument post:2 plain Layout")
2216                     continue
2217                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2218                 postarg2content = document.body[argbeginPlain + 1 : argendPlain - 2]
2219
2220                 # remove Arg insets and paragraph, if it only contains this inset
2221                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2222                     del document.body[arg - 1 : endarg + 4]
2223                 else:
2224                     del document.body[arg : endarg + 1]
2225
2226             # re-find inset end
2227             j = find_end_of_inset(document.body, i)
2228             if j == -1:
2229                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2230                 continue
2231
2232             arg = find_token(document.body, "\\begin_inset Argument post:3", i, j)
2233             endarg = find_end_of_inset(document.body, arg)
2234             postarg3content = []
2235             if arg != -1:
2236                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2237                 if argbeginPlain == -1:
2238                     document.warning("Malformed LyX document: Can't find Argument post:3 plain Layout")
2239                     continue
2240                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2241                 postarg3content = document.body[argbeginPlain + 1 : argendPlain - 2]
2242
2243                 # remove Arg insets and paragraph, if it only contains this inset
2244                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2245                     del document.body[arg - 1 : endarg + 4]
2246                 else:
2247                     del document.body[arg : endarg + 1]
2248
2249             # re-find inset end
2250             j = find_end_of_inset(document.body, i)
2251             if j == -1:
2252                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2253                 continue
2254
2255             arg = find_token(document.body, "\\begin_inset Argument post:4", i, j)
2256             endarg = find_end_of_inset(document.body, arg)
2257             postarg4content = []
2258             if arg != -1:
2259                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2260                 if argbeginPlain == -1:
2261                     document.warning("Malformed LyX document: Can't find Argument post:4 plain Layout")
2262                     continue
2263                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2264                 postarg4content = document.body[argbeginPlain + 1 : argendPlain - 2]
2265
2266                 # remove Arg insets and paragraph, if it only contains this inset
2267                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2268                     del document.body[arg - 1 : endarg + 4]
2269                 else:
2270                     del document.body[arg : endarg + 1]
2271
2272             # The respective LaTeX command
2273             cmd = "\\drs"
2274             if drs == "\\begin_inset Flex DRS*":
2275                 cmd = "\\drs*"
2276             elif drs == "\\begin_inset Flex IfThen-DRS":
2277                 cmd = "\\ifdrs"
2278             elif drs == "\\begin_inset Flex Cond-DRS":
2279                 cmd = "\\condrs"
2280             elif drs == "\\begin_inset Flex QDRS":
2281                 cmd = "\\qdrs"
2282             elif drs == "\\begin_inset Flex NegDRS":
2283                 cmd = "\\negdrs"
2284             elif drs == "\\begin_inset Flex SDRS":
2285                 cmd = "\\sdrs"
2286
2287             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2288             endInset = find_end_of_inset(document.body, i)
2289             endPlain = find_token_backwards(document.body, "\\end_layout", endInset)
2290             precontent = put_cmd_in_ert(cmd)
2291             precontent += put_cmd_in_ert("{") + prearg1content + put_cmd_in_ert("}")
2292             if drs == "\\begin_inset Flex SDRS":
2293                 precontent += put_cmd_in_ert("{") + prearg2content + put_cmd_in_ert("}")
2294             precontent += put_cmd_in_ert("{")
2295
2296             postcontent = []
2297             if cmd == "\\qdrs" or cmd == "\\condrs" or cmd == "\\ifdrs":
2298                 postcontent = put_cmd_in_ert("}{") + postarg1content + put_cmd_in_ert("}{") + postarg2content + put_cmd_in_ert("}")
2299                 if cmd == "\\condrs" or cmd == "\\qdrs":
2300                     postcontent += put_cmd_in_ert("{") + postarg3content + put_cmd_in_ert("}")
2301                 if cmd == "\\qdrs":
2302                     postcontent += put_cmd_in_ert("{") + postarg4content + put_cmd_in_ert("}")
2303             else:
2304                 postcontent = put_cmd_in_ert("}")
2305
2306             document.body[endPlain:endInset + 1] = postcontent
2307             document.body[beginPlain + 1:beginPlain] = precontent
2308             del document.body[i : beginPlain + 1]
2309             if not cov_req:
2310                 document.append_local_layout("Provides covington 1")
2311                 add_to_preamble(document, ["\\usepackage{drs,covington}"])
2312                 cov_req = True
2313             i = beginPlain
2314
2315
2316
2317 def revert_babelfont(document):
2318     " Reverts the use of \\babelfont to user preamble "
2319
2320     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2321     if i == -1:
2322         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2323         return
2324     if not str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2325         return
2326     i = find_token(document.header, '\\language_package', 0)
2327     if i == -1:
2328         document.warning("Malformed LyX document: Missing \\language_package.")
2329         return
2330     if get_value(document.header, "\\language_package", 0) != "babel":
2331         return
2332
2333     # check font settings
2334     # defaults
2335     roman = sans = typew = "default"
2336     osf = False
2337     sf_scale = tt_scale = 100.0
2338
2339     j = find_token(document.header, "\\font_roman", 0)
2340     if j == -1:
2341         document.warning("Malformed LyX document: Missing \\font_roman.")
2342     else:
2343         # We need to use this regex since split() does not handle quote protection
2344         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2345         roman = romanfont[2].strip('"')
2346         romanfont[2] = '"default"'
2347         document.header[j] = " ".join(romanfont)
2348
2349     j = find_token(document.header, "\\font_sans", 0)
2350     if j == -1:
2351         document.warning("Malformed LyX document: Missing \\font_sans.")
2352     else:
2353         # We need to use this regex since split() does not handle quote protection
2354         sansfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2355         sans = sansfont[2].strip('"')
2356         sansfont[2] = '"default"'
2357         document.header[j] = " ".join(sansfont)
2358
2359     j = find_token(document.header, "\\font_typewriter", 0)
2360     if j == -1:
2361         document.warning("Malformed LyX document: Missing \\font_typewriter.")
2362     else:
2363         # We need to use this regex since split() does not handle quote protection
2364         ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2365         typew = ttfont[2].strip('"')
2366         ttfont[2] = '"default"'
2367         document.header[j] = " ".join(ttfont)
2368
2369     i = find_token(document.header, "\\font_osf", 0)
2370     if i == -1:
2371         document.warning("Malformed LyX document: Missing \\font_osf.")
2372     else:
2373         osf = str2bool(get_value(document.header, "\\font_osf", i))
2374
2375     j = find_token(document.header, "\\font_sf_scale", 0)
2376     if j == -1:
2377         document.warning("Malformed LyX document: Missing \\font_sf_scale.")
2378     else:
2379         sfscale = document.header[j].split()
2380         val = sfscale[2]
2381         sfscale[2] = "100"
2382         document.header[j] = " ".join(sfscale)
2383         try:
2384             # float() can throw
2385             sf_scale = float(val)
2386         except:
2387             document.warning("Invalid font_sf_scale value: " + val)
2388
2389     j = find_token(document.header, "\\font_tt_scale", 0)
2390     if j == -1:
2391         document.warning("Malformed LyX document: Missing \\font_tt_scale.")
2392     else:
2393         ttscale = document.header[j].split()
2394         val = ttscale[2]
2395         ttscale[2] = "100"
2396         document.header[j] = " ".join(ttscale)
2397         try:
2398             # float() can throw
2399             tt_scale = float(val)
2400         except:
2401             document.warning("Invalid font_tt_scale value: " + val)
2402
2403     # set preamble stuff
2404     pretext = ['%% This document must be processed with xelatex or lualatex!']
2405     pretext.append('\\AtBeginDocument{%')
2406     if roman != "default":
2407         pretext.append('\\babelfont{rm}[Mapping=tex-text]{' + roman + '}')
2408     if sans != "default":
2409         sf = '\\babelfont{sf}['
2410         if sf_scale != 100.0:
2411             sf += 'Scale=' + str(sf_scale / 100.0) + ','
2412         sf += 'Mapping=tex-text]{' + sans + '}'
2413         pretext.append(sf)
2414     if typew != "default":
2415         tw = '\\babelfont{tt}'
2416         if tt_scale != 100.0:
2417             tw += '[Scale=' + str(tt_scale / 100.0) + ']'
2418         tw += '{' + typew + '}'
2419         pretext.append(tw)
2420     if osf:
2421         pretext.append('\\defaultfontfeatures{Numbers=OldStyle}')
2422     pretext.append('}')
2423     insert_to_preamble(document, pretext)
2424
2425
2426 def revert_minionpro(document):
2427     " Revert native MinionPro font definition (with extra options) to LaTeX "
2428
2429     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2430     if i == -1:
2431         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2432         return
2433     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2434         return
2435
2436     regexp = re.compile(r'(\\font_roman_opts)')
2437     x = find_re(document.header, regexp, 0)
2438     if x == -1:
2439         return
2440
2441     # We need to use this regex since split() does not handle quote protection
2442     romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2443     opts = romanopts[1].strip('"')
2444
2445     i = find_token(document.header, "\\font_roman", 0)
2446     if i == -1:
2447         document.warning("Malformed LyX document: Missing \\font_roman.")
2448         return
2449     else:
2450         # We need to use this regex since split() does not handle quote protection
2451         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2452         roman = romanfont[1].strip('"')
2453         if roman != "minionpro":
2454             return
2455         romanfont[1] = '"default"'
2456         document.header[i] = " ".join(romanfont)
2457         osf = False
2458         j = find_token(document.header, "\\font_osf true", 0)
2459         if j != -1:
2460             osf = True
2461         preamble = "\\usepackage["
2462         if osf:
2463             document.header[j] = "\\font_osf false"
2464         else:
2465             preamble += "lf,"
2466         preamble += opts
2467         preamble += "]{MinionPro}"
2468         add_to_preamble(document, [preamble])
2469         del document.header[x]
2470
2471
2472 def revert_font_opts(document):
2473     " revert font options by outputting \\setxxxfont or \\babelfont to the preamble "
2474
2475     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2476     if i == -1:
2477         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2478         return
2479     NonTeXFonts = str2bool(get_value(document.header, "\\use_non_tex_fonts", i))
2480     i = find_token(document.header, '\\language_package', 0)
2481     if i == -1:
2482         document.warning("Malformed LyX document: Missing \\language_package.")
2483         return
2484     Babel = (get_value(document.header, "\\language_package", 0) == "babel")
2485
2486     # 1. Roman
2487     regexp = re.compile(r'(\\font_roman_opts)')
2488     i = find_re(document.header, regexp, 0)
2489     if i != -1:
2490         # We need to use this regex since split() does not handle quote protection
2491         romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2492         opts = romanopts[1].strip('"')
2493         del document.header[i]
2494         if NonTeXFonts:
2495             regexp = re.compile(r'(\\font_roman)')
2496             i = find_re(document.header, regexp, 0)
2497             if i != -1:
2498                 # We need to use this regex since split() does not handle quote protection
2499                 romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2500                 font = romanfont[2].strip('"')
2501                 romanfont[2] = '"default"'
2502                 document.header[i] = " ".join(romanfont)
2503                 if font != "default":
2504                     if Babel:
2505                         preamble = "\\babelfont{rm}["
2506                     else:
2507                         preamble = "\\setmainfont["
2508                     preamble += opts
2509                     preamble += ","
2510                     preamble += "Mapping=tex-text]{"
2511                     preamble += font
2512                     preamble += "}"
2513                     add_to_preamble(document, [preamble])
2514
2515     # 2. Sans
2516     regexp = re.compile(r'(\\font_sans_opts)')
2517     i = find_re(document.header, regexp, 0)
2518     if i != -1:
2519         scaleval = 100
2520         # We need to use this regex since split() does not handle quote protection
2521         sfopts = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2522         opts = sfopts[1].strip('"')
2523         del document.header[i]
2524         if NonTeXFonts:
2525             regexp = re.compile(r'(\\font_sf_scale)')
2526             i = find_re(document.header, regexp, 0)
2527             if i != -1:
2528                 scaleval = get_value(document.header, "\\font_sf_scale" , i).split()[1]
2529             regexp = re.compile(r'(\\font_sans)')
2530             i = find_re(document.header, regexp, 0)
2531             if i != -1:
2532                 # We need to use this regex since split() does not handle quote protection
2533                 sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2534                 font = sffont[2].strip('"')
2535                 sffont[2] = '"default"'
2536                 document.header[i] = " ".join(sffont)
2537                 if font != "default":
2538                     if Babel:
2539                         preamble = "\\babelfont{sf}["
2540                     else:
2541                         preamble = "\\setsansfont["
2542                     preamble += opts
2543                     preamble += ","
2544                     if scaleval != 100:
2545                         preamble += "Scale=0."
2546                         preamble += scaleval
2547                         preamble += ","
2548                     preamble += "Mapping=tex-text]{"
2549                     preamble += font
2550                     preamble += "}"
2551                     add_to_preamble(document, [preamble])
2552
2553     # 3. Typewriter
2554     regexp = re.compile(r'(\\font_typewriter_opts)')
2555     i = find_re(document.header, regexp, 0)
2556     if i != -1:
2557         scaleval = 100
2558         # We need to use this regex since split() does not handle quote protection
2559         ttopts = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2560         opts = ttopts[1].strip('"')
2561         del document.header[i]
2562         if NonTeXFonts:
2563             regexp = re.compile(r'(\\font_tt_scale)')
2564             i = find_re(document.header, regexp, 0)
2565             if i != -1:
2566                 scaleval = get_value(document.header, "\\font_tt_scale" , i).split()[1]
2567             regexp = re.compile(r'(\\font_typewriter)')
2568             i = find_re(document.header, regexp, 0)
2569             if i != -1:
2570                 # We need to use this regex since split() does not handle quote protection
2571                 ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2572                 font = ttfont[2].strip('"')
2573                 ttfont[2] = '"default"'
2574                 document.header[i] = " ".join(ttfont)
2575                 if font != "default":
2576                     if Babel:
2577                         preamble = "\\babelfont{tt}["
2578                     else:
2579                         preamble = "\\setmonofont["
2580                     preamble += opts
2581                     preamble += ","
2582                     if scaleval != 100:
2583                         preamble += "Scale=0."
2584                         preamble += scaleval
2585                         preamble += ","
2586                     preamble += "Mapping=tex-text]{"
2587                     preamble += font
2588                     preamble += "}"
2589                     add_to_preamble(document, [preamble])
2590
2591
2592 def revert_plainNotoFonts_xopts(document):
2593     " Revert native (straight) Noto font definition (with extra options) to LaTeX "
2594
2595     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2596     if i == -1:
2597         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2598         return
2599     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2600         return
2601
2602     osf = False
2603     y = find_token(document.header, "\\font_osf true", 0)
2604     if y != -1:
2605         osf = True
2606
2607     regexp = re.compile(r'(\\font_roman_opts)')
2608     x = find_re(document.header, regexp, 0)
2609     if x == -1 and not osf:
2610         return
2611
2612     opts = ""
2613     if x != -1:
2614         # We need to use this regex since split() does not handle quote protection
2615         romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2616         opts = romanopts[1].strip('"')
2617     if osf:
2618         if opts != "":
2619             opts += ", "
2620         opts += "osf"
2621
2622     i = find_token(document.header, "\\font_roman", 0)
2623     if i == -1:
2624         return
2625
2626     # We need to use this regex since split() does not handle quote protection
2627     romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2628     roman = romanfont[1].strip('"')
2629     if roman != "NotoSerif-TLF":
2630         return
2631
2632     j = find_token(document.header, "\\font_sans", 0)
2633     if j == -1:
2634         return
2635
2636     # We need to use this regex since split() does not handle quote protection
2637     sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2638     sf = sffont[1].strip('"')
2639     if sf != "default":
2640         return
2641
2642     j = find_token(document.header, "\\font_typewriter", 0)
2643     if j == -1:
2644         return
2645
2646     # We need to use this regex since split() does not handle quote protection
2647     ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2648     tt = ttfont[1].strip('"')
2649     if tt != "default":
2650         return
2651
2652     # So we have noto as "complete font"
2653     romanfont[1] = '"default"'
2654     document.header[i] = " ".join(romanfont)
2655
2656     preamble = "\\usepackage["
2657     preamble += opts
2658     preamble += "]{noto}"
2659     add_to_preamble(document, [preamble])
2660     if osf:
2661         document.header[y] = "\\font_osf false"
2662     if x != -1:
2663         del document.header[x]
2664
2665
2666 def revert_notoFonts_xopts(document):
2667     " Revert native (extended) Noto font definition (with extra options) to LaTeX "
2668
2669     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2670     if i == -1:
2671         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2672         return
2673     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2674         return
2675
2676     fontmap = dict()
2677     fm = createFontMapping(['Noto'])
2678     if revert_fonts(document, fm, fontmap, True):
2679         add_preamble_fonts(document, fontmap)
2680
2681
2682 def revert_IBMFonts_xopts(document):
2683     " Revert native IBM font definition (with extra options) to LaTeX "
2684
2685     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2686     if i == -1:
2687         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2688         return
2689     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2690         return
2691
2692     fontmap = dict()
2693     fm = createFontMapping(['IBM'])
2694     ft = ""
2695     if revert_fonts(document, fm, fontmap, True):
2696         add_preamble_fonts(document, fontmap)
2697
2698
2699 def revert_AdobeFonts_xopts(document):
2700     " Revert native Adobe font definition (with extra options) to LaTeX "
2701
2702     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2703     if i == -1:
2704         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2705         return
2706     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2707         return
2708
2709     fontmap = dict()
2710     fm = createFontMapping(['Adobe'])
2711     ft = ""
2712     if revert_fonts(document, fm, fontmap, True):
2713         add_preamble_fonts(document, fontmap)
2714
2715
2716 def convert_osf(document):
2717     " Convert \\font_osf param to new format "
2718
2719     NonTeXFonts = False
2720     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2721     if i == -1:
2722         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2723     else:
2724         NonTeXFonts = str2bool(get_value(document.header, "\\use_non_tex_fonts", i))
2725
2726     i = find_token(document.header, '\\font_osf', 0)
2727     if i == -1:
2728         document.warning("Malformed LyX document: Missing \\font_osf.")
2729         return
2730
2731     osfsf = ["biolinum", "ADOBESourceSansPro", "NotoSansRegular", "NotoSansMedium", "NotoSansThin", "NotoSansLight", "NotoSansExtralight" ]
2732     osftt = ["ADOBESourceCodePro", "NotoMonoRegular" ]
2733
2734     osfval = str2bool(get_value(document.header, "\\font_osf", i))
2735     document.header[i] = document.header[i].replace("\\font_osf", "\\font_roman_osf")
2736
2737     if NonTeXFonts:
2738         document.header.insert(i, "\\font_sans_osf false")
2739         document.header.insert(i + 1, "\\font_typewriter_osf false")
2740         return
2741
2742     if osfval:
2743         x = find_token(document.header, "\\font_sans", 0)
2744         if x == -1:
2745             document.warning("Malformed LyX document: Missing \\font_sans.")
2746         else:
2747             # We need to use this regex since split() does not handle quote protection
2748             sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2749             sf = sffont[1].strip('"')
2750             if sf in osfsf:
2751                 document.header.insert(i, "\\font_sans_osf true")
2752             else:
2753                 document.header.insert(i, "\\font_sans_osf false")
2754
2755         x = find_token(document.header, "\\font_typewriter", 0)
2756         if x == -1:
2757             document.warning("Malformed LyX document: Missing \\font_typewriter.")
2758         else:
2759             # We need to use this regex since split() does not handle quote protection
2760             ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2761             tt = ttfont[1].strip('"')
2762             if tt in osftt:
2763                 document.header.insert(i + 1, "\\font_typewriter_osf true")
2764             else:
2765                 document.header.insert(i + 1, "\\font_typewriter_osf false")
2766
2767     else:
2768         document.header.insert(i, "\\font_sans_osf false")
2769         document.header.insert(i + 1, "\\font_typewriter_osf false")
2770
2771
2772 def revert_osf(document):
2773     " Revert \\font_*_osf params "
2774
2775     NonTeXFonts = False
2776     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2777     if i == -1:
2778         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2779     else:
2780         NonTeXFonts = str2bool(get_value(document.header, "\\use_non_tex_fonts", i))
2781
2782     i = find_token(document.header, '\\font_roman_osf', 0)
2783     if i == -1:
2784         document.warning("Malformed LyX document: Missing \\font_roman_osf.")
2785         return
2786
2787     osfval = str2bool(get_value(document.header, "\\font_roman_osf", i))
2788     document.header[i] = document.header[i].replace("\\font_roman_osf", "\\font_osf")
2789
2790     i = find_token(document.header, '\\font_sans_osf', 0)
2791     if i == -1:
2792         document.warning("Malformed LyX document: Missing \\font_sans_osf.")
2793         return
2794
2795     osfval = str2bool(get_value(document.header, "\\font_sans_osf", i))
2796     del document.header[i]
2797
2798     i = find_token(document.header, '\\font_typewriter_osf', 0)
2799     if i == -1:
2800         document.warning("Malformed LyX document: Missing \\font_typewriter_osf.")
2801         return
2802
2803     osfval |= str2bool(get_value(document.header, "\\font_typewriter_osf", i))
2804     del document.header[i]
2805
2806     if osfval:
2807         i = find_token(document.header, '\\font_osf', 0)
2808         if i == -1:
2809             document.warning("Malformed LyX document: Missing \\font_osf.")
2810             return
2811         document.header[i] = "\\font_osf true"
2812
2813
2814 def revert_texfontopts(document):
2815     " Revert native TeX font definitions (with extra options) to LaTeX "
2816
2817     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2818     if i == -1:
2819         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2820         return
2821     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2822         return
2823
2824     rmfonts = ["ccfonts", "cochineal", "utopia", "garamondx", "libertine", "lmodern", "palatino", "times", "xcharter" ]
2825
2826     # First the sf (biolinum only)
2827     regexp = re.compile(r'(\\font_sans_opts)')
2828     x = find_re(document.header, regexp, 0)
2829     if x != -1:
2830         # We need to use this regex since split() does not handle quote protection
2831         sfopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2832         opts = sfopts[1].strip('"')
2833         i = find_token(document.header, "\\font_sans", 0)
2834         if i == -1:
2835             document.warning("Malformed LyX document: Missing \\font_sans.")
2836         else:
2837             # We need to use this regex since split() does not handle quote protection
2838             sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2839             sans = sffont[1].strip('"')
2840             if sans == "biolinum":
2841                 sf_scale = 100.0
2842                 sffont[1] = '"default"'
2843                 document.header[i] = " ".join(sffont)
2844                 osf = False
2845                 j = find_token(document.header, "\\font_sans_osf true", 0)
2846                 if j != -1:
2847                     osf = True
2848                 k = find_token(document.header, "\\font_sf_scale", 0)
2849                 if k == -1:
2850                     document.warning("Malformed LyX document: Missing \\font_sf_scale.")
2851                 else:
2852                     sfscale = document.header[k].split()
2853                     val = sfscale[1]
2854                     sfscale[1] = "100"
2855                     document.header[k] = " ".join(sfscale)
2856                     try:
2857                         # float() can throw
2858                         sf_scale = float(val)
2859                     except:
2860                         document.warning("Invalid font_sf_scale value: " + val)
2861                 preamble = "\\usepackage["
2862                 if osf:
2863                     document.header[j] = "\\font_sans_osf false"
2864                     preamble += "osf,"
2865                 if sf_scale != 100.0:
2866                     preamble += 'scaled=' + str(sf_scale / 100.0) + ','
2867                 preamble += opts
2868                 preamble += "]{biolinum}"
2869                 add_to_preamble(document, [preamble])
2870                 del document.header[x]
2871
2872     regexp = re.compile(r'(\\font_roman_opts)')
2873     x = find_re(document.header, regexp, 0)
2874     if x == -1:
2875         return
2876
2877     # We need to use this regex since split() does not handle quote protection
2878     romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2879     opts = romanopts[1].strip('"')
2880
2881     i = find_token(document.header, "\\font_roman", 0)
2882     if i == -1:
2883         document.warning("Malformed LyX document: Missing \\font_roman.")
2884         return
2885     else:
2886         # We need to use this regex since split() does not handle quote protection
2887         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2888         roman = romanfont[1].strip('"')
2889         if not roman in rmfonts:
2890             return
2891         romanfont[1] = '"default"'
2892         document.header[i] = " ".join(romanfont)
2893         package = roman
2894         if roman == "utopia":
2895             package = "fourier"
2896         elif roman == "palatino":
2897             package = "mathpazo"
2898         elif roman == "times":
2899             package = "mathptmx"
2900         elif roman == "xcharter":
2901             package = "XCharter"
2902         osf = ""
2903         j = find_token(document.header, "\\font_roman_osf true", 0)
2904         if j != -1:
2905             if roman == "cochineal":
2906                 osf = "proportional,osf,"
2907             elif roman == "utopia":
2908                 osf = "oldstyle,"
2909             elif roman == "garamondx":
2910                 osf = "osfI,"
2911             elif roman == "libertine":
2912                 osf = "osf,"
2913             elif roman == "palatino":
2914                 osf = "osf,"
2915             elif roman == "xcharter":
2916                 osf = "osf,"
2917             document.header[j] = "\\font_roman_osf false"
2918         k = find_token(document.header, "\\font_sc true", 0)
2919         if k != -1:
2920             if roman == "utopia":
2921                 osf += "expert,"
2922             if roman == "palatino" and osf == "":
2923                 osf = "sc,"
2924             document.header[k] = "\\font_sc false"
2925         preamble = "\\usepackage["
2926         preamble += osf
2927         preamble += opts
2928         preamble += "]{" + package + "}"
2929         add_to_preamble(document, [preamble])
2930         del document.header[x]
2931
2932
2933 def convert_CantarellFont(document):
2934     " Handle Cantarell font definition to LaTeX "
2935
2936     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
2937         fm = createFontMapping(['Cantarell'])
2938         convert_fonts(document, fm, "oldstyle")
2939
2940 def revert_CantarellFont(document):
2941     " Revert native Cantarell font definition to LaTeX "
2942
2943     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
2944         fontmap = dict()
2945         fm = createFontMapping(['Cantarell'])
2946         if revert_fonts(document, fm, fontmap, False, True):
2947             add_preamble_fonts(document, fontmap)
2948
2949 def convert_ChivoFont(document):
2950     " Handle Chivo font definition to LaTeX "
2951
2952     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
2953         fm = createFontMapping(['Chivo'])
2954         convert_fonts(document, fm, "oldstyle")
2955
2956 def revert_ChivoFont(document):
2957     " Revert native Chivo font definition to LaTeX "
2958
2959     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
2960         fontmap = dict()
2961         fm = createFontMapping(['Chivo'])
2962         if revert_fonts(document, fm, fontmap, False, True):
2963             add_preamble_fonts(document, fontmap)
2964
2965
2966 def convert_FiraFont(document):
2967     " Handle Fira font definition to LaTeX "
2968
2969     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
2970         fm = createFontMapping(['Fira'])
2971         convert_fonts(document, fm, "lf")
2972
2973 def revert_FiraFont(document):
2974     " Revert native Fira font definition to LaTeX "
2975
2976     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
2977         fontmap = dict()
2978         fm = createFontMapping(['Fira'])
2979         if revert_fonts(document, fm, fontmap, False, True):
2980             add_preamble_fonts(document, fontmap)
2981
2982
2983 def convert_Semibolds(document):
2984     " Move semibold options to extraopts "
2985
2986     NonTeXFonts = False
2987     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2988     if i == -1:
2989         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2990     else:
2991         NonTeXFonts = str2bool(get_value(document.header, "\\use_non_tex_fonts", i))
2992
2993     i = find_token(document.header, "\\font_roman", 0)
2994     if i == -1:
2995         document.warning("Malformed LyX document: Missing \\font_roman.")
2996     else:
2997         # We need to use this regex since split() does not handle quote protection
2998         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2999         roman = romanfont[1].strip('"')
3000         if roman == "IBMPlexSerifSemibold":
3001             romanfont[1] = '"IBMPlexSerif"'
3002             document.header[i] = " ".join(romanfont)
3003
3004             if NonTeXFonts == False:
3005                 regexp = re.compile(r'(\\font_roman_opts)')
3006                 x = find_re(document.header, regexp, 0)
3007                 if x == -1:
3008                     # Sensible place to insert tag
3009                     fo = find_token(document.header, "\\font_sf_scale")
3010                     if fo == -1:
3011                         document.warning("Malformed LyX document! Missing \\font_sf_scale")
3012                     else:
3013                         document.header.insert(fo, "\\font_roman_opts \"semibold\"")
3014                 else:
3015                     # We need to use this regex since split() does not handle quote protection
3016                     romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3017                     document.header[x] = "\\font_roman_opts \"semibold, " + romanopts[1].strip('"') + "\""
3018
3019     i = find_token(document.header, "\\font_sans", 0)
3020     if i == -1:
3021         document.warning("Malformed LyX document: Missing \\font_sans.")
3022     else:
3023         # We need to use this regex since split() does not handle quote protection
3024         sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3025         sf = sffont[1].strip('"')
3026         if sf == "IBMPlexSansSemibold":
3027             sffont[1] = '"IBMPlexSans"'
3028             document.header[i] = " ".join(sffont)
3029
3030             if NonTeXFonts == False:
3031                 regexp = re.compile(r'(\\font_sans_opts)')
3032                 x = find_re(document.header, regexp, 0)
3033                 if x == -1:
3034                     # Sensible place to insert tag
3035                     fo = find_token(document.header, "\\font_sf_scale")
3036                     if fo == -1:
3037                         document.warning("Malformed LyX document! Missing \\font_sf_scale")
3038                     else:
3039                         document.header.insert(fo, "\\font_sans_opts \"semibold\"")
3040                 else:
3041                     # We need to use this regex since split() does not handle quote protection
3042                     sfopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3043                     document.header[x] = "\\font_sans_opts \"semibold, " + sfopts[1].strip('"') + "\""
3044
3045     i = find_token(document.header, "\\font_typewriter", 0)
3046     if i == -1:
3047         document.warning("Malformed LyX document: Missing \\font_typewriter.")
3048     else:
3049         # We need to use this regex since split() does not handle quote protection
3050         ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3051         tt = ttfont[1].strip('"')
3052         if tt == "IBMPlexMonoSemibold":
3053             ttfont[1] = '"IBMPlexMono"'
3054             document.header[i] = " ".join(ttfont)
3055
3056             if NonTeXFonts == False:
3057                 regexp = re.compile(r'(\\font_typewriter_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_tt_scale")
3062                     if fo == -1:
3063                         document.warning("Malformed LyX document! Missing \\font_tt_scale")
3064                     else:
3065                         document.header.insert(fo, "\\font_typewriter_opts \"semibold\"")
3066                 else:
3067                     # We need to use this regex since split() does not handle quote protection
3068                     ttopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3069                     document.header[x] = "\\font_typewriter_opts \"semibold, " + sfopts[1].strip('"') + "\""
3070
3071
3072 def convert_NotoRegulars(document):
3073     " Merge diverse noto reagular fonts "
3074
3075     i = find_token(document.header, "\\font_roman", 0)
3076     if i == -1:
3077         document.warning("Malformed LyX document: Missing \\font_roman.")
3078     else:
3079         # We need to use this regex since split() does not handle quote protection
3080         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3081         roman = romanfont[1].strip('"')
3082         if roman == "NotoSerif-TLF":
3083             romanfont[1] = '"NotoSerifRegular"'
3084             document.header[i] = " ".join(romanfont)
3085
3086     i = find_token(document.header, "\\font_sans", 0)
3087     if i == -1:
3088         document.warning("Malformed LyX document: Missing \\font_sans.")
3089     else:
3090         # We need to use this regex since split() does not handle quote protection
3091         sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3092         sf = sffont[1].strip('"')
3093         if sf == "NotoSans-TLF":
3094             sffont[1] = '"NotoSansRegular"'
3095             document.header[i] = " ".join(sffont)
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 == "NotoMono-TLF":
3105             ttfont[1] = '"NotoMonoRegular"'
3106             document.header[i] = " ".join(ttfont)
3107
3108
3109 def convert_CrimsonProFont(document):
3110     " Handle CrimsonPro font definition to LaTeX "
3111
3112     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
3113         fm = createFontMapping(['CrimsonPro'])
3114         convert_fonts(document, fm, "lf")
3115
3116 def revert_CrimsonProFont(document):
3117     " Revert native CrimsonPro font definition to LaTeX "
3118
3119     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
3120         fontmap = dict()
3121         fm = createFontMapping(['CrimsonPro'])
3122         if revert_fonts(document, fm, fontmap, False, True):
3123             add_preamble_fonts(document, fontmap)
3124
3125
3126 def revert_pagesizes(document):
3127     " Revert new page sizes in memoir and KOMA to options "
3128
3129     if document.textclass != "memoir" and document.textclass[:2] != "scr":
3130         return
3131
3132     i = find_token(document.header, "\\use_geometry true", 0)
3133     if i != -1:
3134         return
3135
3136     defsizes = ["default", "custom", "letterpaper", "legalpaper", "executivepaper", "a4paper", "a5paper", "b5paper"]
3137
3138     i = find_token(document.header, "\\papersize", 0)
3139     if i == -1:
3140         document.warning("Malformed LyX document! Missing \\papersize header.")
3141         return
3142     val = get_value(document.header, "\\papersize", i)
3143     if val in defsizes:
3144         # nothing to do
3145         return
3146
3147     document.header[i] = "\\papersize default"
3148
3149     i = find_token(document.header, "\\options", 0)
3150     if i == -1:
3151         i = find_token(document.header, "\\textclass", 0)
3152         if i == -1:
3153             document.warning("Malformed LyX document! Missing \\textclass header.")
3154             return
3155         document.header.insert(i, "\\options " + val)
3156         return
3157     document.header[i] = document.header[i] + "," + val
3158
3159
3160 def convert_pagesizes(document):
3161     " Convert to new page sizes in memoir and KOMA to options "
3162
3163     if document.textclass != "memoir" and document.textclass[:2] != "scr":
3164         return
3165
3166     i = find_token(document.header, "\\use_geometry true", 0)
3167     if i != -1:
3168         return
3169
3170     defsizes = ["default", "custom", "letterpaper", "legalpaper", "executivepaper", "a4paper", "a5paper", "b5paper"]
3171
3172     i = find_token(document.header, "\\papersize", 0)
3173     if i == -1:
3174         document.warning("Malformed LyX document! Missing \\papersize header.")
3175         return
3176     val = get_value(document.header, "\\papersize", i)
3177     if val in defsizes:
3178         # nothing to do
3179         return
3180
3181     i = find_token(document.header, "\\use_geometry false", 0)
3182     if i != -1:
3183         # Maintain use of geometry
3184         document.header[1] = "\\use_geometry true"
3185
3186     
3187
3188 ##
3189 # Conversion hub
3190 #
3191
3192 supported_versions = ["2.4.0", "2.4"]
3193 convert = [
3194            [545, [convert_lst_literalparam]],
3195            [546, []],
3196            [547, []],
3197            [548, []],
3198            [549, []],
3199            [550, [convert_fontenc]],
3200            [551, []],
3201            [552, []],
3202            [553, []],
3203            [554, []],
3204            [555, []],
3205            [556, []],
3206            [557, [convert_vcsinfo]],
3207            [558, [removeFrontMatterStyles]],
3208            [559, []],
3209            [560, []],
3210            [561, [convert_latexFonts]], # Handle dejavu, ibmplex fonts in GUI
3211            [562, []],
3212            [563, []],
3213            [564, []],
3214            [565, [convert_AdobeFonts]], # Handle adobe fonts in GUI
3215            [566, [convert_hebrew_parentheses]],
3216            [567, []],
3217            [568, []],
3218            [569, []],
3219            [570, []],
3220            [571, []],
3221            [572, [convert_notoFonts]],  # Added options thin, light, extralight for Noto
3222            [573, [convert_inputencoding_namechange]],
3223            [574, [convert_ruby_module, convert_utf8_japanese]],
3224            [575, [convert_lineno]],
3225            [576, []],
3226            [577, [convert_linggloss]],
3227            [578, []],
3228            [579, []],
3229            [580, []],
3230            [581, [convert_osf]],
3231            [582, [convert_AdobeFonts,convert_latexFonts,convert_notoFonts,convert_CantarellFont,convert_FiraFont]],# old font re-converterted due to extra options
3232            [583, [convert_ChivoFont,convert_Semibolds,convert_NotoRegulars,convert_CrimsonProFont]],
3233            [584, []],
3234            [585, [convert_pagesizes]]
3235           ]
3236
3237 revert =  [[584, [revert_pagesizes]],
3238            [583, [revert_vcsinfo_rev_abbrev]],
3239            [582, [revert_ChivoFont,revert_CrimsonProFont]],
3240            [581, [revert_CantarellFont,revert_FiraFont]],
3241            [580, [revert_texfontopts,revert_osf]],
3242            [579, [revert_minionpro, revert_plainNotoFonts_xopts, revert_notoFonts_xopts, revert_IBMFonts_xopts, revert_AdobeFonts_xopts, revert_font_opts]], # keep revert_font_opts last!
3243            [578, [revert_babelfont]],
3244            [577, [revert_drs]],
3245            [576, [revert_linggloss, revert_subexarg]],
3246            [575, [revert_new_languages]],
3247            [574, [revert_lineno]],
3248            [573, [revert_ruby_module, revert_utf8_japanese]],
3249            [572, [revert_inputencoding_namechange]],
3250            [571, [revert_notoFonts]],
3251            [570, [revert_cmidruletrimming]],
3252            [569, [revert_bibfileencodings]],
3253            [568, [revert_tablestyle]],
3254            [567, [revert_soul]],
3255            [566, [revert_malayalam]],
3256            [565, [revert_hebrew_parentheses]],
3257            [564, [revert_AdobeFonts]],
3258            [563, [revert_lformatinfo]],
3259            [562, [revert_listpargs]],
3260            [561, [revert_l7ninfo]],
3261            [560, [revert_latexFonts]], # Handle dejavu, ibmplex fonts in user preamble
3262            [559, [revert_timeinfo, revert_namenoextinfo]],
3263            [558, [revert_dateinfo]],
3264            [557, [addFrontMatterStyles]],
3265            [556, [revert_vcsinfo]],
3266            [555, [revert_bibencoding]],
3267            [554, [revert_vcolumns]],
3268            [553, [revert_stretchcolumn]],
3269            [552, [revert_tuftecite]],
3270            [551, [revert_floatpclass, revert_floatalignment]],
3271            [550, [revert_nospellcheck]],
3272            [549, [revert_fontenc]],
3273            [548, []],# dummy format change
3274            [547, [revert_lscape]],
3275            [546, [revert_xcharter]],
3276            [545, [revert_paratype]],
3277            [544, [revert_lst_literalparam]]
3278           ]
3279
3280
3281 if __name__ == "__main__":
3282     pass