]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_4.py
b26f941991a7055643d0302e88eaebe4a8a38b82
[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
77     def addkey(self):
78         self.pkgkey = createkey(self.package, self.options)
79
80 class fontmapping:
81     def __init__(self):
82         self.font2pkgmap = dict()
83         self.pkg2fontmap = dict()
84         self.pkginmap = dict()  # defines, if a map for package exists
85
86     def expandFontMapping(self, font_list, font_type, scale_type, pkg, scaleopt = None, osfopt = None):
87         " Expand fontinfo mapping"
88         #
89         # fontlist:    list of fontnames, each element
90         #              may contain a ','-separated list of needed options
91         #              like e.g. 'IBMPlexSansCondensed,condensed'
92         # font_type:   one of 'roman', 'sans', 'typewriter', 'math'
93         # scale_type:  one of None, 'sf', 'tt'
94         # pkg:         package defining the font. Defaults to fontname if None
95         # scaleopt:    one of None, 'scale', 'scaled', or some other string
96         #              to be used in scale option (e.g. scaled=0.7)
97         # osfopt:      None or some other string to be used in osf option
98         for fl in font_list:
99             fe = fontinfo()
100             fe.fonttype = font_type
101             fe.scaletype = scale_type
102             flt = fl.split(",")
103             font_name = flt[0]
104             fe.fontname = font_name
105             fe.options = flt[1:]
106             fe.scaleopt = scaleopt
107             fe.osfopt = osfopt
108             if pkg == None:
109                 fe.package = font_name
110             else:
111                 fe.package = pkg
112             fe.addkey()
113             self.font2pkgmap[font_name] = fe
114             if fe.pkgkey in self.pkg2fontmap:
115                 # Repeated the same entry? Check content
116                 if self.pkg2fontmap[fe.pkgkey] != font_name:
117                     document.error("Something is wrong in pkgname+options <-> fontname mapping")
118             self.pkg2fontmap[fe.pkgkey] = font_name
119             self.pkginmap[fe.package] = 1
120
121     def getfontname(self, pkg, options):
122         options.sort()
123         pkgkey = createkey(pkg, options)
124         if not pkgkey in self.pkg2fontmap:
125             return None
126         fontname = self.pkg2fontmap[pkgkey]
127         if not fontname in self.font2pkgmap:
128             document.error("Something is wrong in pkgname+options <-> fontname mapping")
129             return None
130         if pkgkey == self.font2pkgmap[fontname].pkgkey:
131             return fontname
132         return None
133
134 def createFontMapping(fontlist):
135     # Create info for known fonts for the use in
136     #   convert_latexFonts() and
137     #   revert_latexFonts()
138     #
139     # * Would be more handy to parse latexFonts file,
140     #   but the path to this file is unknown
141     # * For now, add DejaVu and IBMPlex only.
142     # * Expand, if desired
143     fm = fontmapping()
144     for font in fontlist:
145         if font == 'DejaVu':
146             fm.expandFontMapping(['DejaVuSerif', 'DejaVuSerifCondensed'], "roman", None, None)
147             fm.expandFontMapping(['DejaVuSans','DejaVuSansCondensed'], "sans", "sf", None, "scaled")
148             fm.expandFontMapping(['DejaVuSansMono'], "typewriter", "tt", None, "scaled")
149         elif font == 'IBM':
150             fm.expandFontMapping(['IBMPlexSerif', 'IBMPlexSerifThin,thin',
151                                   'IBMPlexSerifExtraLight,extralight', 'IBMPlexSerifLight,light',
152                                   'IBMPlexSerifSemibold,semibold'],
153                                  "roman", None, "plex-serif")
154             fm.expandFontMapping(['IBMPlexSans','IBMPlexSansCondensed,condensed',
155                                   'IBMPlexSansThin,thin', 'IBMPlexSansExtraLight,extralight',
156                                   'IBMPlexSansLight,light', 'IBMPlexSansSemibold,semibold'],
157                                  "sans", "sf", "plex-sans", "scale")
158             fm.expandFontMapping(['IBMPlexMono', 'IBMPlexMonoThin,thin',
159                                   'IBMPlexMonoExtraLight,extralight', 'IBMPlexMonoLight,light',
160                                   'IBMPlexMonoSemibold,semibold'],
161                                  "typewriter", "tt", "plex-mono", "scale")
162         elif font == 'Adobe':
163             fm.expandFontMapping(['ADOBESourceSerifPro'], "roman", None, "sourceserifpro", None, "osf")
164             fm.expandFontMapping(['ADOBESourceSansPro'], "sans", "sf", "sourcesanspro", "scaled")
165             fm.expandFontMapping(['ADOBESourceCodePro'], "typewriter", "tt", "sourcecodepro", "scaled")
166         elif font == 'Noto':
167             fm.expandFontMapping(['NotoSerifRegular,regular', 'NotoSerifMedium,medium',
168                                   'NotoSerifThin,thin', 'NotoSerifLight,light',
169                                   'NotoSerifExtralight,extralight'],
170                                   "roman", None, "noto-serif", None, "osf")
171             fm.expandFontMapping(['NotoSansRegular,regular', 'NotoSansMedium,medium',
172                                   'NotoSansThin,thin', 'NotoSansLight,light',
173                                   'NotoSansExtralight,extralight'],
174                                   "sans", "sf", "noto-sans", "scaled")
175             fm.expandFontMapping(['NotoMonoRegular,regular'], "typewriter", "tt", "noto-mono", "scaled")
176     return fm
177
178 def convert_fonts(document, fm):
179     " Handle font definition (LaTeX preamble -> native) "
180
181     rpkg = re.compile(r'^\\usepackage(\[([^\]]*)\])?\{([^\}]+)\}')
182     rscaleopt = re.compile(r'^scaled?=(.*)')
183
184     i = 0
185     while i < len(document.preamble):
186         i = find_re(document.preamble, rpkg, i+1)
187         if i == -1:
188             return
189         mo = rpkg.search(document.preamble[i])
190         if mo == None or mo.group(2) == None:
191             options = []
192         else:
193             options = mo.group(2).replace(' ', '').split(",")
194         pkg = mo.group(3)
195         o = 0
196         oscale = 1
197         osfoption = "osf"
198         has_osf = False
199         while o < len(options):
200             if options[o] == osfoption:
201                 has_osf = True
202                 del options[o]
203                 continue
204             mo = rscaleopt.search(options[o])
205             if mo == None:
206                 o += 1
207                 continue
208             oscale = mo.group(1)
209             del options[o]
210             continue
211
212         if not pkg in fm.pkginmap:
213             continue
214         # determine fontname
215         fn = fm.getfontname(pkg, options)
216         if fn == None:
217             continue
218         del document.preamble[i]
219         fontinfo = fm.font2pkgmap[fn]
220         if fontinfo.scaletype == None:
221             fontscale = None
222         else:
223             fontscale = "\\font_" + fontinfo.scaletype + "_scale"
224             fontinfo.scaleval = oscale
225         if has_osf:
226              if fontinfo.osfopt == None:
227                  options.extend("osf")
228                  continue
229              osf = find_token(document.header, "\\font_osf false")
230              if osf != -1:
231                  document.header[osf] = "\\font_osf true"
232         if i > 0 and document.preamble[i-1] == "% Added by lyx2lyx":
233             del document.preamble[i-1]
234             i -= 1
235         if fontscale != None:
236             j = find_token(document.header, fontscale, 0)
237             if j != -1:
238                 val = get_value(document.header, fontscale, j)
239                 vals = val.split()
240                 scale = "100"
241                 if oscale != None:
242                     scale = "%03d" % int(float(oscale) * 100)
243                 document.header[j] = fontscale + " " + scale + " " + vals[1]
244         ft = "\\font_" + fontinfo.fonttype
245         j = find_token(document.header, ft, 0)
246         if j != -1:
247             val = get_value(document.header, ft, j)
248             words = val.split() # ! splits also values like '"DejaVu Sans"'
249             words[0] = '"' + fn + '"'
250             document.header[j] = ft + ' ' + ' '.join(words)
251
252 def revert_fonts(document, fm, fontmap, OnlyWithXOpts = False):
253     " Revert native font definition to LaTeX "
254     # fonlist := list of fonts created from the same package
255     # Empty package means that the font-name is the same as the package-name
256     # fontmap (key = package, val += found options) will be filled
257     # and used later in add_preamble_fonts() to be added to user-preamble
258
259     rfontscale = re.compile(r'^\s*(\\font_(roman|sans|typewriter|math))\s+')
260     rscales = re.compile(r'^\s*(\d+)\s+(\d+)')
261     i = 0
262     while i < len(document.header):
263         i = find_re(document.header, rfontscale, i+1)
264         if (i == -1):
265             return True
266         mo = rfontscale.search(document.header[i])
267         if mo == None:
268             continue
269         ft = mo.group(1)    # 'roman', 'sans', 'typewriter', 'math'
270         val = get_value(document.header, ft, i)
271         words = val.split(' ')     # ! splits also values like '"DejaVu Sans"'
272         font = words[0].strip('"') # TeX font name has no whitespace
273         if not font in fm.font2pkgmap:
274             continue
275         fontinfo = fm.font2pkgmap[font]
276         val = fontinfo.package
277         if not val in fontmap:
278             fontmap[val] = []
279         x = -1
280         if OnlyWithXOpts:
281             if ft == "\\font_math":
282                 return False
283             regexp = re.compile(r'^\s*(\\font_roman_opts)\s+')
284             if ft == "\\font_sans":
285                 regexp = re.compile(r'^\s*(\\font_sans_opts)\s+')
286             elif ft == "\\font_typewriter":
287                 regexp = re.compile(r'^\s*(\\font_typewriter_opts)\s+')
288             x = find_re(document.header, regexp, 0)
289             if x == -1:
290                 return False
291
292             # We need to use this regex since split() does not handle quote protection
293             xopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
294             opts = xopts[1].strip('"').split(",")
295             fontmap[val].extend(opts)
296             del document.header[x]
297         words[0] = '"default"'
298         document.header[i] = ft + ' ' + ' '.join(words)
299         if fontinfo.scaleopt != None:
300             xval =  get_value(document.header, "\\font_" + fontinfo.scaletype + "_scale", 0)
301             mo = rscales.search(xval)
302             if mo != None:
303                 xval1 = mo.group(1)
304                 xval2 = mo.group(2)
305                 if xval1 != "100":
306                     # set correct scale option
307                     fontmap[val].extend([fontinfo.scaleopt + "=" + format(float(xval1) / 100, '.2f')])
308         if fontinfo.osfopt != None and fontinfo.fonttype == "roman":
309             osf = find_token(document.header, "\\font_osf true")
310             if osf != -1:
311                 document.header[osf] = "\\font_osf false"
312                 fontmap[val].extend([fontinfo.osfopt])
313         if len(fontinfo.options) > 0:
314             fontmap[val].extend(fontinfo.options)
315     return True
316
317 ###############################################################################
318 ###
319 ### Conversion and reversion routines
320 ###
321 ###############################################################################
322
323 def convert_inputencoding_namechange(document):
324     " Rename inputencoding settings. "
325     i = find_token(document.header, "\\inputencoding", 0)
326     if i == -1:
327         return
328     s = document.header[i].replace("auto", "auto-legacy")
329     document.header[i] = s.replace("default", "auto-legacy-plain")
330
331 def revert_inputencoding_namechange(document):
332     " Rename inputencoding settings. "
333     i = find_token(document.header, "\\inputencoding", 0)
334     if i == -1:
335         return
336     s = document.header[i].replace("auto-legacy-plain", "default")
337     document.header[i] = s.replace("auto-legacy", "auto")
338
339 def convert_notoFonts(document):
340     " Handle Noto fonts definition to LaTeX "
341
342     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
343         fm = createFontMapping(['Noto'])
344         convert_fonts(document, fm)
345
346 def revert_notoFonts(document):
347     " Revert native Noto font definition to LaTeX "
348
349     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
350         fontmap = dict()
351         fm = createFontMapping(['Noto'])
352         if revert_fonts(document, fm, fontmap):
353             add_preamble_fonts(document, fontmap)
354
355 def convert_latexFonts(document):
356     " Handle DejaVu and IBMPlex fonts definition to LaTeX "
357
358     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
359         fm = createFontMapping(['DejaVu', 'IBM'])
360         convert_fonts(document, fm)
361
362 def revert_latexFonts(document):
363     " Revert native DejaVu font definition to LaTeX "
364
365     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
366         fontmap = dict()
367         fm = createFontMapping(['DejaVu', 'IBM'])
368         if revert_fonts(document, fm, fontmap):
369             add_preamble_fonts(document, fontmap)
370
371 def convert_AdobeFonts(document):
372     " Handle Adobe Source fonts definition to LaTeX "
373
374     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
375         fm = createFontMapping(['Adobe'])
376         convert_fonts(document, fm)
377
378 def revert_AdobeFonts(document):
379     " Revert Adobe Source font definition to LaTeX "
380
381     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
382         fontmap = dict()
383         fm = createFontMapping(['Adobe'])
384         if revert_fonts(document, fm, fontmap):
385             add_preamble_fonts(document, fontmap)
386
387 def removeFrontMatterStyles(document):
388     " Remove styles Begin/EndFrontmatter"
389
390     layouts = ['BeginFrontmatter', 'EndFrontmatter']
391     tokenend = len('\\begin_layout ')
392     i = 0
393     while True:
394         i = find_token_exact(document.body, '\\begin_layout ', i+1)
395         if i == -1:
396             return
397         layout = document.body[i][tokenend:].strip()
398         if layout not in layouts:
399             continue
400         j = find_end_of_layout(document.body, i)
401         if j == -1:
402             document.warning("Malformed LyX document: Can't find end of layout at line %d" % i)
403             continue
404         while document.body[j+1].strip() == '':
405             j += 1
406         document.body[i:j+1] = []
407
408 def addFrontMatterStyles(document):
409     " Use styles Begin/EndFrontmatter for elsarticle"
410
411     if document.textclass != "elsarticle":
412         return
413
414     def insertFrontmatter(prefix, line):
415         above = line
416         while above > 0 and document.body[above-1].strip() == '':
417             above -= 1
418         below = line
419         while document.body[below].strip() == '':
420             below += 1
421         document.body[above:below] = ['', '\\begin_layout ' + prefix + 'Frontmatter',
422                                     '\\begin_inset Note Note',
423                                     'status open', '',
424                                     '\\begin_layout Plain Layout',
425                                     'Keep this empty!',
426                                     '\\end_layout', '',
427                                     '\\end_inset', '', '',
428                                     '\\end_layout', '']
429
430     layouts = ['Title', 'Title footnote', 'Author', 'Author footnote',
431                 'Corresponding author', 'Address', 'Email', 'Abstract', 'Keywords']
432     tokenend = len('\\begin_layout ')
433     first = -1
434     i = 0
435     while True:
436         i = find_token_exact(document.body, '\\begin_layout ', i+1)
437         if i == -1:
438             break
439         layout = document.body[i][tokenend:].strip()
440         if layout not in layouts:
441             continue
442         k = find_end_of_layout(document.body, i)
443         if k == -1:
444             document.warning("Malformed LyX document: Can't find end of layout at line %d" % i)
445             continue
446         if first == -1:
447             first = i
448         i = k
449     if first == -1:
450         return
451     insertFrontmatter('End', k+1)
452     insertFrontmatter('Begin', first)
453
454
455 def convert_lst_literalparam(document):
456     " Add param literal to include inset "
457
458     i = 0
459     while True:
460         i = find_token(document.body, '\\begin_inset CommandInset include', i+1)
461         if i == -1:
462             break
463         j = find_end_of_inset(document.body, i)
464         if j == -1:
465             document.warning("Malformed LyX document: Can't find end of command inset at line %d" % i)
466             continue
467         while i < j and document.body[i].strip() != '':
468             i += 1
469         document.body.insert(i, 'literal "true"')
470
471
472 def revert_lst_literalparam(document):
473     " Remove param literal from include inset "
474
475     i = 0
476     while True:
477         i = find_token(document.body, '\\begin_inset CommandInset include', i+1)
478         if i == -1:
479             break
480         j = find_end_of_inset(document.body, i)
481         if j == -1:
482             document.warning("Malformed LyX document: Can't find end of include inset at line %d" % i)
483             continue
484         del_token(document.body, 'literal', i, j)
485
486
487 def revert_paratype(document):
488     " Revert ParaType font definitions to LaTeX "
489
490     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
491         preamble = ""
492         i1 = find_token(document.header, "\\font_roman \"PTSerif-TLF\"", 0)
493         i2 = find_token(document.header, "\\font_sans \"default\"", 0)
494         i3 = find_token(document.header, "\\font_typewriter \"default\"", 0)
495         j = find_token(document.header, "\\font_sans \"PTSans-TLF\"", 0)
496         sfval = get_value(document.header, "\\font_sf_scale", 0)
497         # cutoff " 100"
498         sfval = sfval[:-4]
499         sfoption = ""
500         if sfval != "100":
501             sfoption = "scaled=" + format(float(sfval) / 100, '.2f')
502         k = find_token(document.header, "\\font_typewriter \"PTMono-TLF\"", 0)
503         ttval = get_value(document.header, "\\font_tt_scale", 0)
504         # cutoff " 100"
505         ttval = ttval[:-4]
506         ttoption = ""
507         if ttval != "100":
508             ttoption = "scaled=" + format(float(ttval) / 100, '.2f')
509         if i1 != -1 and i2 != -1 and i3!= -1:
510             add_to_preamble(document, ["\\usepackage{paratype}"])
511         else:
512             if i1!= -1:
513                 add_to_preamble(document, ["\\usepackage{PTSerif}"])
514                 document.header[i1] = document.header[i1].replace("PTSerif-TLF", "default")
515             if j!= -1:
516                 if sfoption != "":
517                     add_to_preamble(document, ["\\usepackage[" + sfoption + "]{PTSans}"])
518                 else:
519                     add_to_preamble(document, ["\\usepackage{PTSans}"])
520                 document.header[j] = document.header[j].replace("PTSans-TLF", "default")
521             if k!= -1:
522                 if ttoption != "":
523                     add_to_preamble(document, ["\\usepackage[" + ttoption + "]{PTMono}"])
524                 else:
525                     add_to_preamble(document, ["\\usepackage{PTMono}"])
526                 document.header[k] = document.header[k].replace("PTMono-TLF", "default")
527
528
529 def revert_xcharter(document):
530     " Revert XCharter font definitions to LaTeX "
531
532     i = find_token(document.header, "\\font_roman \"xcharter\"", 0)
533     if i == -1:
534         return
535
536     # replace unsupported font setting
537     document.header[i] = document.header[i].replace("xcharter", "default")
538     # no need for preamble code with system fonts
539     if get_bool_value(document.header, "\\use_non_tex_fonts"):
540         return
541
542     # transfer old style figures setting to package options
543     j = find_token(document.header, "\\font_osf true")
544     if j != -1:
545         options = "[osf]"
546         document.header[j] = "\\font_osf false"
547     else:
548         options = ""
549     if i != -1:
550         add_to_preamble(document, ["\\usepackage%s{XCharter}"%options])
551
552
553 def revert_lscape(document):
554     " Reverts the landscape environment (Landscape module) to TeX-code "
555
556     if not "landscape" in document.get_module_list():
557         return
558
559     i = 0
560     while True:
561         i = find_token(document.body, "\\begin_inset Flex Landscape", i+1)
562         if i == -1:
563             return
564         j = find_end_of_inset(document.body, i)
565         if j == -1:
566             document.warning("Malformed LyX document: Can't find end of Landscape inset")
567             continue
568
569         if document.body[i] == "\\begin_inset Flex Landscape (Floating)":
570             document.body[j - 2 : j + 1] = put_cmd_in_ert("\\end{landscape}}")
571             document.body[i : i + 4] = put_cmd_in_ert("\\afterpage{\\begin{landscape}")
572             add_to_preamble(document, ["\\usepackage{afterpage}"])
573         else:
574             document.body[j - 2 : j + 1] = put_cmd_in_ert("\\end{landscape}")
575             document.body[i : i + 4] = put_cmd_in_ert("\\begin{landscape}")
576
577         add_to_preamble(document, ["\\usepackage{pdflscape}"])
578
579
580 def convert_fontenc(document):
581     " Convert default fontenc setting "
582
583     i = find_token(document.header, "\\fontencoding global", 0)
584     if i == -1:
585         return
586
587     document.header[i] = document.header[i].replace("global", "auto")
588
589
590 def revert_fontenc(document):
591     " Revert default fontenc setting "
592
593     i = find_token(document.header, "\\fontencoding auto", 0)
594     if i == -1:
595         return
596
597     document.header[i] = document.header[i].replace("auto", "global")
598
599
600 def revert_nospellcheck(document):
601     " Remove nospellcheck font info param "
602
603     i = 0
604     while True:
605         i = find_token(document.body, '\\nospellcheck', i)
606         if i == -1:
607             return
608         del document.body[i]
609
610
611 def revert_floatpclass(document):
612     " Remove float placement params 'document' and 'class' "
613
614     del_token(document.header, "\\float_placement class")
615
616     i = 0
617     while True:
618         i = find_token(document.body, '\\begin_inset Float', i+1)
619         if i == -1:
620             break
621         j = find_end_of_inset(document.body, i)
622         k = find_token(document.body, 'placement class', i, i + 2)
623         if k == -1:
624             k = find_token(document.body, 'placement document', i, i + 2)
625             if k != -1:
626                 del document.body[k]
627             continue
628         del document.body[k]
629
630
631 def revert_floatalignment(document):
632     " Remove float alignment params "
633
634     galignment = get_value(document.header, "\\float_alignment", delete=True)
635
636     i = 0
637     while True:
638         i = find_token(document.body, '\\begin_inset Float', i+1)
639         if i == -1:
640             break
641         j = find_end_of_inset(document.body, i)
642         if j == -1:
643             document.warning("Malformed LyX document: Can't find end of inset at line " + str(i))
644             continue
645         k = find_token(document.body, 'alignment', i, i+4)
646         if k == -1:
647             i = j
648             continue
649         alignment = get_value(document.body, "alignment", k)
650         if alignment == "document":
651             alignment = galignment
652         del document.body[k]
653         l = find_token(document.body, "\\begin_layout Plain Layout", i, j)
654         if l == -1:
655             document.warning("Can't find float layout!")
656             continue
657         alcmd = []
658         if alignment == "left":
659             alcmd = put_cmd_in_ert("\\raggedright{}")
660         elif alignment == "center":
661             alcmd = put_cmd_in_ert("\\centering{}")
662         elif alignment == "right":
663             alcmd = put_cmd_in_ert("\\raggedleft{}")
664         if len(alcmd) > 0:
665             document.body[l+1:l+1] = alcmd
666         i = j
667
668 def revert_tuftecite(document):
669     " Revert \cite commands in tufte classes "
670
671     tufte = ["tufte-book", "tufte-handout"]
672     if document.textclass not in tufte:
673         return
674
675     i = 0
676     while (True):
677         i = find_token(document.body, "\\begin_inset CommandInset citation", i+1)
678         if i == -1:
679             break
680         j = find_end_of_inset(document.body, i)
681         if j == -1:
682             document.warning("Can't find end of citation inset at line %d!!" %(i))
683             continue
684         k = find_token(document.body, "LatexCommand", i, j)
685         if k == -1:
686             document.warning("Can't find LatexCommand for citation inset at line %d!" %(i))
687             i = j
688             continue
689         cmd = get_value(document.body, "LatexCommand", k)
690         if cmd != "cite":
691             i = j
692             continue
693         pre = get_quoted_value(document.body, "before", i, j)
694         post = get_quoted_value(document.body, "after", i, j)
695         key = get_quoted_value(document.body, "key", i, j)
696         if not key:
697             document.warning("Citation inset at line %d does not have a key!" %(i))
698             key = "???"
699         # Replace command with ERT
700         res = "\\cite"
701         if pre:
702             res += "[" + pre + "]"
703         if post:
704             res += "[" + post + "]"
705         elif pre:
706             res += "[]"
707         res += "{" + key + "}"
708         document.body[i:j+1] = put_cmd_in_ert([res])
709         i = j
710
711
712 def revert_stretchcolumn(document):
713     " We remove the column varwidth flags or everything else will become a mess. "
714     i = 0
715     while True:
716         i = find_token(document.body, "\\begin_inset Tabular", i+1)
717         if i == -1:
718             return
719         j = find_end_of_inset(document.body, i+1)
720         if j == -1:
721             document.warning("Malformed LyX document: Could not find end of tabular.")
722             continue
723         for k in range(i, j):
724             if re.search('^<column.*varwidth="[^"]+".*>$', document.body[k]):
725                 document.warning("Converting 'tabularx'/'xltabular' table to normal table.")
726                 document.body[k] = document.body[k].replace(' varwidth="true"', '')
727
728
729 def revert_vcolumns(document):
730     " Revert standard columns with line breaks etc. "
731     i = 0
732     needvarwidth = False
733     needarray = False
734     try:
735         while True:
736             i = find_token(document.body, "\\begin_inset Tabular", i+1)
737             if i == -1:
738                 return
739             j = find_end_of_inset(document.body, i)
740             if j == -1:
741                 document.warning("Malformed LyX document: Could not find end of tabular.")
742                 continue
743
744             # Collect necessary column information
745             m = i + 1
746             nrows = int(document.body[i+1].split('"')[3])
747             ncols = int(document.body[i+1].split('"')[5])
748             col_info = []
749             for k in range(ncols):
750                 m = find_token(document.body, "<column", m)
751                 width = get_option_value(document.body[m], 'width')
752                 varwidth = get_option_value(document.body[m], 'varwidth')
753                 alignment = get_option_value(document.body[m], 'alignment')
754                 special = get_option_value(document.body[m], 'special')
755                 col_info.append([width, varwidth, alignment, special, m])
756
757             # Now parse cells
758             m = i + 1
759             lines = []
760             for row in range(nrows):
761                 for col in range(ncols):
762                     m = find_token(document.body, "<cell", m)
763                     multicolumn = get_option_value(document.body[m], 'multicolumn')
764                     multirow = get_option_value(document.body[m], 'multirow')
765                     width = get_option_value(document.body[m], 'width')
766                     rotate = get_option_value(document.body[m], 'rotate')
767                     # Check for: linebreaks, multipars, non-standard environments
768                     begcell = m
769                     endcell = find_token(document.body, "</cell>", begcell)
770                     vcand = False
771                     if find_token(document.body, "\\begin_inset Newline", begcell, endcell) != -1:
772                         vcand = True
773                     elif count_pars_in_inset(document.body, begcell + 2) > 1:
774                         vcand = True
775                     elif get_value(document.body, "\\begin_layout", begcell) != "Plain Layout":
776                         vcand = True
777                     if vcand and rotate == "" and ((multicolumn == "" and multirow == "") or width == ""):
778                         if col_info[col][0] == "" and col_info[col][1] == "" and col_info[col][3] == "":
779                             needvarwidth = True
780                             alignment = col_info[col][2]
781                             col_line = col_info[col][4]
782                             vval = ""
783                             if alignment == "center":
784                                 vval = ">{\\centering}"
785                             elif  alignment == "left":
786                                 vval = ">{\\raggedright}"
787                             elif alignment == "right":
788                                 vval = ">{\\raggedleft}"
789                             if vval != "":
790                                 needarray = True
791                             vval += "V{\\linewidth}"
792
793                             document.body[col_line] = document.body[col_line][:-1] + " special=\"" + vval + "\">"
794                             # ERT newlines and linebreaks (since LyX < 2.4 automatically inserts parboxes
795                             # with newlines, and we do not want that)
796                             while True:
797                                 endcell = find_token(document.body, "</cell>", begcell)
798                                 linebreak = False
799                                 nl = find_token(document.body, "\\begin_inset Newline newline", begcell, endcell)
800                                 if nl == -1:
801                                     nl = find_token(document.body, "\\begin_inset Newline linebreak", begcell, endcell)
802                                     if nl == -1:
803                                          break
804                                     linebreak = True
805                                 nle = find_end_of_inset(document.body, nl)
806                                 del(document.body[nle:nle+1])
807                                 if linebreak:
808                                     document.body[nl:nl+1] = put_cmd_in_ert("\\linebreak{}")
809                                 else:
810                                     document.body[nl:nl+1] = put_cmd_in_ert("\\\\")
811                     m += 1
812
813             i = j
814
815     finally:
816         if needarray == True:
817             add_to_preamble(document, ["\\usepackage{array}"])
818         if needvarwidth == True:
819             add_to_preamble(document, ["\\usepackage{varwidth}"])
820
821
822 def revert_bibencoding(document):
823     " Revert bibliography encoding "
824
825     # Get cite engine
826     engine = "basic"
827     i = find_token(document.header, "\\cite_engine", 0)
828     if i == -1:
829         document.warning("Malformed document! Missing \\cite_engine")
830     else:
831         engine = get_value(document.header, "\\cite_engine", i)
832
833     # Check if biblatex
834     biblatex = False
835     if engine in ["biblatex", "biblatex-natbib"]:
836         biblatex = True
837
838     # Map lyx to latex encoding names
839     encodings = {
840         "utf8" : "utf8",
841         "utf8x" : "utf8x",
842         "armscii8" : "armscii8",
843         "iso8859-1" : "latin1",
844         "iso8859-2" : "latin2",
845         "iso8859-3" : "latin3",
846         "iso8859-4" : "latin4",
847         "iso8859-5" : "iso88595",
848         "iso8859-6" : "8859-6",
849         "iso8859-7" : "iso-8859-7",
850         "iso8859-8" : "8859-8",
851         "iso8859-9" : "latin5",
852         "iso8859-13" : "latin7",
853         "iso8859-15" : "latin9",
854         "iso8859-16" : "latin10",
855         "applemac" : "applemac",
856         "cp437" : "cp437",
857         "cp437de" : "cp437de",
858         "cp850" : "cp850",
859         "cp852" : "cp852",
860         "cp855" : "cp855",
861         "cp858" : "cp858",
862         "cp862" : "cp862",
863         "cp865" : "cp865",
864         "cp866" : "cp866",
865         "cp1250" : "cp1250",
866         "cp1251" : "cp1251",
867         "cp1252" : "cp1252",
868         "cp1255" : "cp1255",
869         "cp1256" : "cp1256",
870         "cp1257" : "cp1257",
871         "koi8-r" : "koi8-r",
872         "koi8-u" : "koi8-u",
873         "pt154" : "pt154",
874         "utf8-platex" : "utf8",
875         "ascii" : "ascii"
876     }
877
878     i = 0
879     bibresources = []
880     while (True):
881         i = find_token(document.body, "\\begin_inset CommandInset bibtex", i+1)
882         if i == -1:
883             break
884         j = find_end_of_inset(document.body, i)
885         if j == -1:
886             document.warning("Can't find end of bibtex inset at line %d!!" %(i))
887             continue
888         encoding = get_quoted_value(document.body, "encoding", i, j)
889         if not encoding:
890             continue
891         # remove encoding line
892         k = find_token(document.body, "encoding", i, j)
893         if k != -1:
894             del document.body[k]
895         if encoding == "default":
896             continue
897         # Re-find inset end line
898         j = find_end_of_inset(document.body, i)
899         if biblatex:
900             biblio_options = ""
901             h = find_token(document.header, "\\biblio_options", 0)
902             if h != -1:
903                 biblio_options = get_value(document.header, "\\biblio_options", h)
904                 if not "bibencoding" in biblio_options:
905                      document.header[h] += ",bibencoding=%s" % encodings[encoding]
906             else:
907                 bs = find_token(document.header, "\\biblatex_bibstyle", 0)
908                 if bs == -1:
909                     # this should not happen
910                     document.warning("Malformed LyX document! No \\biblatex_bibstyle header found!")
911                 else:
912                     document.header[bs-1 : bs-1] = ["\\biblio_options bibencoding=" + encodings[encoding]]
913         else:
914             document.body[j+1:j+1] = put_cmd_in_ert("\\egroup")
915             document.body[i:i] = put_cmd_in_ert("\\bgroup\\inputencoding{" + encodings[encoding] + "}")
916
917         i = j
918
919
920
921 def convert_vcsinfo(document):
922     " Separate vcs Info inset from buffer Info inset. "
923
924     types = {
925         "vcs-revision" : "revision",
926         "vcs-tree-revision" : "tree-revision",
927         "vcs-author" : "author",
928         "vcs-time" : "time",
929         "vcs-date" : "date"
930     }
931     i = 0
932     while True:
933         i = find_token(document.body, "\\begin_inset Info", i+1)
934         if i == -1:
935             return
936         j = find_end_of_inset(document.body, i+1)
937         if j == -1:
938             document.warning("Malformed LyX document: Could not find end of Info inset.")
939             continue
940         tp = find_token(document.body, 'type', i, j)
941         tpv = get_quoted_value(document.body, "type", tp)
942         if tpv != "buffer":
943             continue
944         arg = find_token(document.body, 'arg', i, j)
945         argv = get_quoted_value(document.body, "arg", arg)
946         if argv not in list(types.keys()):
947             continue
948         document.body[tp] = "type \"vcs\""
949         document.body[arg] = "arg \"" + types[argv] + "\""
950
951
952 def revert_vcsinfo(document):
953     " Merge vcs Info inset to buffer Info inset. "
954
955     args = ["revision", "tree-revision", "author", "time", "date" ]
956     i = 0
957     while True:
958         i = find_token(document.body, "\\begin_inset Info", i+1)
959         if i == -1:
960             return
961         j = find_end_of_inset(document.body, i+1)
962         if j == -1:
963             document.warning("Malformed LyX document: Could not find end of Info inset.")
964             continue
965         tp = find_token(document.body, 'type', i, j)
966         tpv = get_quoted_value(document.body, "type", tp)
967         if tpv != "vcs":
968             continue
969         arg = find_token(document.body, 'arg', i, j)
970         argv = get_quoted_value(document.body, "arg", arg)
971         if argv not in args:
972             document.warning("Malformed Info inset. Invalid vcs arg.")
973             continue
974         document.body[tp] = "type \"buffer\""
975         document.body[arg] = "arg \"vcs-" + argv + "\""
976
977
978 def revert_dateinfo(document):
979     " Revert date info insets to static text. "
980
981 # FIXME This currently only considers the main language and uses the system locale
982 # Ideally, it should honor context languages and switch the locale accordingly.
983
984     # The date formats for each language using strftime syntax:
985     # long, short, loclong, locmedium, locshort
986     dateformats = {
987         "afrikaans" : ["%A, %d %B %Y", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y/%m/%d"],
988         "albanian" : ["%A, %d %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
989         "american" : ["%A, %B %d, %Y", "%m/%d/%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
990         "amharic" : ["%A ፣%d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
991         "ancientgreek" : ["%A, %d %B %Y", "%d %b %Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
992         "arabic_arabi" : ["%A، %d %B، %Y", "%d‏/%m‏/%Y", "%d %B، %Y", "%d/%m/%Y", "%d/%m/%Y"],
993         "arabic_arabtex" : ["%A، %d %B، %Y", "%d‏/%m‏/%Y", "%d %B، %Y", "%d/%m/%Y", "%d/%m/%Y"],
994         "armenian" : ["%Y թ. %B %d, %A", "%d.%m.%y", "%d %B، %Y", "%d %b، %Y", "%d/%m/%Y"],
995         "asturian" : ["%A, %d %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d %b %Y", "%d/%m/%Y"],
996         "australian" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
997         "austrian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
998         "bahasa" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
999         "bahasam" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1000         "basque" : ["%Y(e)ko %B %d, %A", "%y/%m/%d", "%Y %B %d", "%Y %b %d", "%Y/%m/%d"],
1001         "belarusian" : ["%A, %d %B %Y г.", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1002         "bosnian" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%Y-%m-%d"],
1003         "brazilian" : ["%A, %d de %B de %Y", "%d/%m/%Y", "%d de %B de %Y", "%d de %b de %Y", "%d/%m/%Y"],
1004         "breton" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1005         "british" : ["%A, %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1006         "bulgarian" : ["%A, %d %B %Y г.", "%d.%m.%y г.", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1007         "canadian" : ["%A, %B %d, %Y", "%Y-%m-%d", "%B %d, %Y", "%d %b %Y", "%Y-%m-%d"],
1008         "canadien" : ["%A %d %B %Y", "%y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1009         "catalan" : ["%A, %d %B de %Y", "%d/%m/%y", "%d / %B / %Y", "%d / %b / %Y", "%d/%m/%Y"],
1010         "chinese-simplified" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y-%m-%d", "%y-%m-%d"],
1011         "chinese-traditional" : ["%Y年%m月%d日 %A", "%Y/%m/%d", "%Y年%m月%d日", "%Y年%m月%d日", "%y年%m月%d日"],
1012         "coptic" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1013         "croatian" : ["%A, %d. %B %Y.", "%d. %m. %Y.", "%d. %B %Y.", "%d. %b. %Y.", "%d.%m.%Y."],
1014         "czech" : ["%A %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b. %Y", "%d.%m.%Y"],
1015         "danish" : ["%A den %d. %B %Y", "%d/%m/%Y", "%d. %B %Y", "%d. %b %Y", "%d/%m/%Y"],
1016         "divehi" : ["%Y %B %d, %A", "%Y-%m-%d", "%Y %B %d", "%Y %b %d", "%d/%m/%Y"],
1017         "dutch" : ["%A %d %B %Y", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1018         "english" : ["%A, %B %d, %Y", "%m/%d/%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1019         "esperanto" : ["%A, %d %B %Y", "%d %b %Y", "la %d de %B %Y", "la %d de %b %Y", "%m/%d/%Y"],
1020         "estonian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1021         "farsi" : ["%A %d %B %Y", "%Y/%m/%d", "%d %B %Y", "%d %b %Y", "%Y/%m/%d"],
1022         "finnish" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1023         "french" : ["%A %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1024         "friulan" : ["%A %d di %B dal %Y", "%d/%m/%y", "%d di %B dal %Y", "%d di %b dal %Y", "%d/%m/%Y"],
1025         "galician" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d de %b de %Y", "%d/%m/%Y"],
1026         "georgian" : ["%A, %d %B, %Y", "%d.%m.%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1027         "german" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1028         "german-ch" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1029         "german-ch-old" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1030         "greek" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1031         "hebrew" : ["%A, %d ב%B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1032         "hindi" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1033         "icelandic" : ["%A, %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1034         "interlingua" : ["%Y %B %d, %A", "%Y-%m-%d", "le %d de %B %Y", "le %d de %b %Y", "%Y-%m-%d"],
1035         "irish" : ["%A %d %B %Y", "%d/%m/%Y", "%d. %B %Y", "%d. %b %Y", "%d/%m/%Y"],
1036         "italian" : ["%A %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d/%b/%Y", "%d/%m/%Y"],
1037         "japanese" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y/%m/%d", "%y/%m/%d"],
1038         "japanese-cjk" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y/%m/%d", "%y/%m/%d"],
1039         "kannada" : ["%A, %B %d, %Y", "%d/%m/%y", "%d %B %Y", "%d %B %Y", "%d-%m-%Y"],
1040         "kazakh" : ["%Y ж. %d %B, %A", "%d.%m.%y", "%d %B %Y", "%d %B %Y", "%Y-%d-%m"],
1041         "khmer" : ["%A %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %B %Y", "%d/%m/%Y"],
1042         "korean" : ["%Y년 %m월 %d일 %A", "%y. %m. %d.", "%Y년 %m월 %d일", "%Y. %m. %d.", "%y. %m. %d."],
1043         "kurmanji" : ["%A, %d %B %Y", "%d %b %Y", "%d. %B %Y", "%d. %m. %Y", "%Y-%m-%d"],
1044         "lao" : ["%A ທີ %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %B %Y", "%d/%m/%Y"],
1045         "latin" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1046         "latvian" : ["%A, %Y. gada %d. %B", "%d.%m.%y", "%Y. gada %d. %B", "%Y. gada %d. %b", "%d.%m.%Y"],
1047         "lithuanian" : ["%Y m. %B %d d., %A", "%Y-%m-%d", "%Y m. %B %d d.", "%Y m. %B %d d.", "%Y-%m-%d"],
1048         "lowersorbian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1049         "macedonian" : ["%A, %d %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1050         "magyar" : ["%Y. %B %d., %A", "%Y. %m. %d.", "%Y. %B %d.", "%Y. %b %d.", "%Y.%m.%d."],
1051         "malayalam" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1052         "marathi" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1053         "mongolian" : ["%A, %Y оны %m сарын %d", "%Y-%m-%d", "%Y оны %m сарын %d", "%d-%m-%Y", "%d-%m-%Y"],
1054         "naustrian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1055         "newzealand" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1056         "ngerman" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1057         "norsk" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1058         "nynorsk" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1059         "occitan" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1060         "piedmontese" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1061         "polish" : ["%A, %d %B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1062         "polutonikogreek" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1063         "portuguese" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d de %b de %Y", "%Y/%m/%d"],
1064         "romanian" : ["%A, %d %B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1065         "romansh" : ["%A, ils %d da %B %Y", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1066         "russian" : ["%A, %d %B %Y г.", "%d.%m.%Y", "%d %B %Y г.", "%d %b %Y г.", "%d.%m.%Y"],
1067         "samin" : ["%Y %B %d, %A", "%Y-%m-%d", "%B %d. b. %Y", "%b %d. b. %Y", "%d.%m.%Y"],
1068         "sanskrit" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1069         "scottish" : ["%A, %dmh %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1070         "serbian" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1071         "serbian-latin" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1072         "slovak" : ["%A, %d. %B %Y", "%d. %m. %Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1073         "slovene" : ["%A, %d. %B %Y", "%d. %m. %y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1074         "spanish" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B %de %Y", "%d %b %Y", "%d/%m/%Y"],
1075         "spanish-mexico" : ["%A, %d de %B %de %Y", "%d/%m/%y", "%d de %B de %Y", "%d %b %Y", "%d/%m/%Y"],
1076         "swedish" : ["%A %d %B %Y", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1077         "syriac" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1078         "tamil" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1079         "telugu" : ["%d, %B %Y, %A", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1080         "thai" : ["%Aที่ %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1081         "tibetan" : ["%Y %Bའི་ཚེས་%d, %A", "%Y-%m-%d", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1082         "turkish" : ["%d %B %Y %A", "%d.%m.%Y", "%d %B %Y", "%d.%b.%Y", "%d.%m.%Y"],
1083         "turkmen" : ["%d %B %Y %A", "%d.%m.%Y", "%Y ý. %B %d", "%d.%m.%Y ý.", "%d.%m.%y ý."],
1084         "ukrainian" : ["%A, %d %B %Y р.", "%d.%m.%y", "%d %B %Y", "%d %m %Y", "%d.%m.%Y"],
1085         "uppersorbian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1086         "urdu" : ["%A، %d %B، %Y", "%d/%m/%y", "%d %B, %Y", "%d %b %Y", "%d/%m/%Y"],
1087         "vietnamese" : ["%A, %d %B, %Y", "%d/%m/%Y", "%d tháng %B %Y", "%d-%m-%Y", "%d/%m/%Y"],
1088         "welsh" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1089     }
1090
1091     types = ["date", "fixdate", "moddate" ]
1092     lang = get_value(document.header, "\\language")
1093     if lang == "":
1094         document.warning("Malformed LyX document! No \\language header found!")
1095         return
1096
1097     i = 0
1098     while True:
1099         i = find_token(document.body, "\\begin_inset Info", i+1)
1100         if i == -1:
1101             return
1102         j = find_end_of_inset(document.body, i+1)
1103         if j == -1:
1104             document.warning("Malformed LyX document: Could not find end of Info inset.")
1105             continue
1106         tp = find_token(document.body, 'type', i, j)
1107         tpv = get_quoted_value(document.body, "type", tp)
1108         if tpv not in types:
1109             continue
1110         arg = find_token(document.body, 'arg', i, j)
1111         argv = get_quoted_value(document.body, "arg", arg)
1112         isodate = ""
1113         dte = date.today()
1114         if tpv == "fixdate":
1115             datecomps = argv.split('@')
1116             if len(datecomps) > 1:
1117                 argv = datecomps[0]
1118                 isodate = datecomps[1]
1119                 m = re.search('(\d\d\d\d)-(\d\d)-(\d\d)', isodate)
1120                 if m:
1121                     dte = date(int(m.group(1)), int(m.group(2)), int(m.group(3)))
1122 # FIXME if we had the path to the original document (not the one in the tmp dir),
1123 #        we could use the mtime.
1124 #        elif tpv == "moddate":
1125 #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
1126         result = ""
1127         if argv == "ISO":
1128             result = dte.isodate()
1129         elif argv == "long":
1130             result = dte.strftime(dateformats[lang][0])
1131         elif argv == "short":
1132             result = dte.strftime(dateformats[lang][1])
1133         elif argv == "loclong":
1134             result = dte.strftime(dateformats[lang][2])
1135         elif argv == "locmedium":
1136             result = dte.strftime(dateformats[lang][3])
1137         elif argv == "locshort":
1138             result = dte.strftime(dateformats[lang][4])
1139         else:
1140             fmt = argv.replace("MMMM", "%b").replace("MMM", "%b").replace("MM", "%m").replace("M", "%m")
1141             fmt = fmt.replace("yyyy", "%Y").replace("yy", "%y")
1142             fmt = fmt.replace("dddd", "%A").replace("ddd", "%a").replace("dd", "%d")
1143             fmt = re.sub('[^\'%]d', '%d', fmt)
1144             fmt = fmt.replace("'", "")
1145             result = dte.strftime(fmt)
1146         if sys.version_info < (3,0):
1147             # In Python 2, datetime module works with binary strings,
1148             # our dateformat strings are utf8-encoded:
1149             result = result.decode('utf-8')
1150         document.body[i : j+1] = [result]
1151
1152
1153 def revert_timeinfo(document):
1154     " Revert time info insets to static text. "
1155
1156 # FIXME This currently only considers the main language and uses the system locale
1157 # Ideally, it should honor context languages and switch the locale accordingly.
1158 # Also, the time object is "naive", i.e., it does not know of timezones (%Z will
1159 # be empty).
1160
1161     # The time formats for each language using strftime syntax:
1162     # long, short
1163     timeformats = {
1164         "afrikaans" : ["%H:%M:%S %Z", "%H:%M"],
1165         "albanian" : ["%I:%M:%S %p, %Z", "%I:%M %p"],
1166         "american" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1167         "amharic" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1168         "ancientgreek" : ["%H:%M:%S %Z", "%H:%M:%S"],
1169         "arabic_arabi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1170         "arabic_arabtex" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1171         "armenian" : ["%H:%M:%S %Z", "%H:%M"],
1172         "asturian" : ["%H:%M:%S %Z", "%H:%M"],
1173         "australian" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1174         "austrian" : ["%H:%M:%S %Z", "%H:%M"],
1175         "bahasa" : ["%H.%M.%S %Z", "%H.%M"],
1176         "bahasam" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1177         "basque" : ["%H:%M:%S (%Z)", "%H:%M"],
1178         "belarusian" : ["%H:%M:%S, %Z", "%H:%M"],
1179         "bosnian" : ["%H:%M:%S %Z", "%H:%M"],
1180         "brazilian" : ["%H:%M:%S %Z", "%H:%M"],
1181         "breton" : ["%H:%M:%S %Z", "%H:%M"],
1182         "british" : ["%H:%M:%S %Z", "%H:%M"],
1183         "bulgarian" : ["%H:%M:%S %Z", "%H:%M"],
1184         "canadian" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1185         "canadien" : ["%H:%M:%S %Z", "%H h %M"],
1186         "catalan" : ["%H:%M:%S %Z", "%H:%M"],
1187         "chinese-simplified" : ["%Z %p%I:%M:%S", "%p%I:%M"],
1188         "chinese-traditional" : ["%p%I:%M:%S [%Z]", "%p%I:%M"],
1189         "coptic" : ["%H:%M:%S %Z", "%H:%M:%S"],
1190         "croatian" : ["%H:%M:%S (%Z)", "%H:%M"],
1191         "czech" : ["%H:%M:%S %Z", "%H:%M"],
1192         "danish" : ["%H.%M.%S %Z", "%H.%M"],
1193         "divehi" : ["%H:%M:%S %Z", "%H:%M"],
1194         "dutch" : ["%H:%M:%S %Z", "%H:%M"],
1195         "english" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1196         "esperanto" : ["%H:%M:%S %Z", "%H:%M:%S"],
1197         "estonian" : ["%H:%M:%S %Z", "%H:%M"],
1198         "farsi" : ["%H:%M:%S (%Z)", "%H:%M"],
1199         "finnish" : ["%H.%M.%S %Z", "%H.%M"],
1200         "french" : ["%H:%M:%S %Z", "%H:%M"],
1201         "friulan" : ["%H:%M:%S %Z", "%H:%M"],
1202         "galician" : ["%H:%M:%S %Z", "%H:%M"],
1203         "georgian" : ["%H:%M:%S %Z", "%H:%M"],
1204         "german" : ["%H:%M:%S %Z", "%H:%M"],
1205         "german-ch" : ["%H:%M:%S %Z", "%H:%M"],
1206         "german-ch-old" : ["%H:%M:%S %Z", "%H:%M"],
1207         "greek" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1208         "hebrew" : ["%H:%M:%S %Z", "%H:%M"],
1209         "hindi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1210         "icelandic" : ["%H:%M:%S %Z", "%H:%M"],
1211         "interlingua" : ["%H:%M:%S %Z", "%H:%M"],
1212         "irish" : ["%H:%M:%S %Z", "%H:%M"],
1213         "italian" : ["%H:%M:%S %Z", "%H:%M"],
1214         "japanese" : ["%H時%M分%S秒 %Z", "%H:%M"],
1215         "japanese-cjk" : ["%H時%M分%S秒 %Z", "%H:%M"],
1216         "kannada" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1217         "kazakh" : ["%H:%M:%S %Z", "%H:%M"],
1218         "khmer" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1219         "korean" : ["%p %I시%M분 %S초 %Z", "%p %I:%M"],
1220         "kurmanji" : ["%H:%M:%S %Z", "%H:%M:%S"],
1221         "lao" : ["%H ໂມງ%M ນາທີ  %S ວິນາທີ %Z", "%H:%M"],
1222         "latin" : ["%H:%M:%S %Z", "%H:%M:%S"],
1223         "latvian" : ["%H:%M:%S %Z", "%H:%M"],
1224         "lithuanian" : ["%H:%M:%S %Z", "%H:%M"],
1225         "lowersorbian" : ["%H:%M:%S %Z", "%H:%M"],
1226         "macedonian" : ["%H:%M:%S %Z", "%H:%M"],
1227         "magyar" : ["%H:%M:%S %Z", "%H:%M"],
1228         "malayalam" : ["%p %I:%M:%S %Z", "%p %I:%M"],
1229         "marathi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1230         "mongolian" : ["%H:%M:%S %Z", "%H:%M"],
1231         "naustrian" : ["%H:%M:%S %Z", "%H:%M"],
1232         "newzealand" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1233         "ngerman" : ["%H:%M:%S %Z", "%H:%M"],
1234         "norsk" : ["%H:%M:%S %Z", "%H:%M"],
1235         "nynorsk" : ["kl. %H:%M:%S %Z", "%H:%M"],
1236         "occitan" : ["%H:%M:%S %Z", "%H:%M"],
1237         "piedmontese" : ["%H:%M:%S %Z", "%H:%M:%S"],
1238         "polish" : ["%H:%M:%S %Z", "%H:%M"],
1239         "polutonikogreek" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1240         "portuguese" : ["%H:%M:%S %Z", "%H:%M"],
1241         "romanian" : ["%H:%M:%S %Z", "%H:%M"],
1242         "romansh" : ["%H:%M:%S %Z", "%H:%M"],
1243         "russian" : ["%H:%M:%S %Z", "%H:%M"],
1244         "samin" : ["%H:%M:%S %Z", "%H:%M"],
1245         "sanskrit" : ["%H:%M:%S %Z", "%H:%M"],
1246         "scottish" : ["%H:%M:%S %Z", "%H:%M"],
1247         "serbian" : ["%H:%M:%S %Z", "%H:%M"],
1248         "serbian-latin" : ["%H:%M:%S %Z", "%H:%M"],
1249         "slovak" : ["%H:%M:%S %Z", "%H:%M"],
1250         "slovene" : ["%H:%M:%S %Z", "%H:%M"],
1251         "spanish" : ["%H:%M:%S (%Z)", "%H:%M"],
1252         "spanish-mexico" : ["%H:%M:%S %Z", "%H:%M"],
1253         "swedish" : ["kl. %H:%M:%S %Z", "%H:%M"],
1254         "syriac" : ["%H:%M:%S %Z", "%H:%M"],
1255         "tamil" : ["%p %I:%M:%S %Z", "%p %I:%M"],
1256         "telugu" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1257         "thai" : ["%H นาฬิกา %M นาที  %S วินาที %Z", "%H:%M"],
1258         "tibetan" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1259         "turkish" : ["%H:%M:%S %Z", "%H:%M"],
1260         "turkmen" : ["%H:%M:%S %Z", "%H:%M"],
1261         "ukrainian" : ["%H:%M:%S %Z", "%H:%M"],
1262         "uppersorbian" : ["%H:%M:%S %Z", "%H:%M hodź."],
1263         "urdu" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1264         "vietnamese" : ["%H:%M:%S %Z", "%H:%M"],
1265         "welsh" : ["%H:%M:%S %Z", "%H:%M"]
1266     }
1267
1268     types = ["time", "fixtime", "modtime" ]
1269     i = 0
1270     i = find_token(document.header, "\\language", 0)
1271     if i == -1:
1272         # this should not happen
1273         document.warning("Malformed LyX document! No \\language header found!")
1274         return
1275     lang = get_value(document.header, "\\language", i)
1276
1277     i = 0
1278     while True:
1279         i = find_token(document.body, "\\begin_inset Info", i+1)
1280         if i == -1:
1281             return
1282         j = find_end_of_inset(document.body, i+1)
1283         if j == -1:
1284             document.warning("Malformed LyX document: Could not find end of Info inset.")
1285             continue
1286         tp = find_token(document.body, 'type', i, j)
1287         tpv = get_quoted_value(document.body, "type", tp)
1288         if tpv not in types:
1289             continue
1290         arg = find_token(document.body, 'arg', i, j)
1291         argv = get_quoted_value(document.body, "arg", arg)
1292         isotime = ""
1293         dtme = datetime.now()
1294         tme = dtme.time()
1295         if tpv == "fixtime":
1296             timecomps = argv.split('@')
1297             if len(timecomps) > 1:
1298                 argv = timecomps[0]
1299                 isotime = timecomps[1]
1300                 m = re.search('(\d\d):(\d\d):(\d\d)', isotime)
1301                 if m:
1302                     tme = time(int(m.group(1)), int(m.group(2)), int(m.group(3)))
1303                 else:
1304                     m = re.search('(\d\d):(\d\d)', isotime)
1305                     if m:
1306                         tme = time(int(m.group(1)), int(m.group(2)))
1307 # FIXME if we had the path to the original document (not the one in the tmp dir),
1308 #        we could use the mtime.
1309 #        elif tpv == "moddate":
1310 #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
1311         result = ""
1312         if argv == "ISO":
1313             result = tme.isoformat()
1314         elif argv == "long":
1315             result = tme.strftime(timeformats[lang][0])
1316         elif argv == "short":
1317             result = tme.strftime(timeformats[lang][1])
1318         else:
1319             fmt = argv.replace("HH", "%H").replace("H", "%H").replace("hh", "%I").replace("h", "%I")
1320             fmt = fmt.replace("mm", "%M").replace("m", "%M").replace("ss", "%S").replace("s", "%S")
1321             fmt = fmt.replace("zzz", "%f").replace("z", "%f").replace("t", "%Z")
1322             fmt = fmt.replace("AP", "%p").replace("ap", "%p").replace("A", "%p").replace("a", "%p")
1323             fmt = fmt.replace("'", "")
1324             result = dte.strftime(fmt)
1325         document.body[i : j+1] = result
1326
1327
1328 def revert_namenoextinfo(document):
1329     " Merge buffer Info inset type name-noext to name. "
1330
1331     i = 0
1332     while True:
1333         i = find_token(document.body, "\\begin_inset Info", i+1)
1334         if i == -1:
1335             return
1336         j = find_end_of_inset(document.body, i+1)
1337         if j == -1:
1338             document.warning("Malformed LyX document: Could not find end of Info inset.")
1339             continue
1340         tp = find_token(document.body, 'type', i, j)
1341         tpv = get_quoted_value(document.body, "type", tp)
1342         if tpv != "buffer":
1343             continue
1344         arg = find_token(document.body, 'arg', i, j)
1345         argv = get_quoted_value(document.body, "arg", arg)
1346         if argv != "name-noext":
1347             continue
1348         document.body[arg] = "arg \"name\""
1349
1350
1351 def revert_l7ninfo(document):
1352     " Revert l7n Info inset to text. "
1353
1354     i = 0
1355     while True:
1356         i = find_token(document.body, "\\begin_inset Info", i+1)
1357         if i == -1:
1358             return
1359         j = find_end_of_inset(document.body, i+1)
1360         if j == -1:
1361             document.warning("Malformed LyX document: Could not find end of Info inset.")
1362             continue
1363         tp = find_token(document.body, 'type', i, j)
1364         tpv = get_quoted_value(document.body, "type", tp)
1365         if tpv != "l7n":
1366             continue
1367         arg = find_token(document.body, 'arg', i, j)
1368         argv = get_quoted_value(document.body, "arg", arg)
1369         # remove trailing colons, menu accelerator (|...) and qt accelerator (&), while keeping literal " & "
1370         argv = argv.rstrip(':').split('|')[0].replace(" & ", "</amp;>").replace("&", "").replace("</amp;>", " & ")
1371         document.body[i : j+1] = argv
1372
1373
1374 def revert_listpargs(document):
1375     " Reverts listpreamble arguments to TeX-code "
1376     i = 0
1377     while True:
1378         i = find_token(document.body, "\\begin_inset Argument listpreamble:", i+1)
1379         if i == -1:
1380             return
1381         j = find_end_of_inset(document.body, i)
1382         # Find containing paragraph layout
1383         parent = get_containing_layout(document.body, i)
1384         if parent == False:
1385             document.warning("Malformed LyX document: Can't find parent paragraph layout")
1386             continue
1387         parbeg = parent[3]
1388         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1389         endPlain = find_end_of_layout(document.body, beginPlain)
1390         content = document.body[beginPlain + 1 : endPlain]
1391         del document.body[i:j+1]
1392         subst = ["\\begin_inset ERT", "status collapsed", "", "\\begin_layout Plain Layout",
1393                  "{"] + content + ["}", "\\end_layout", "", "\\end_inset", ""]
1394         document.body[parbeg : parbeg] = subst
1395
1396
1397 def revert_lformatinfo(document):
1398     " Revert layout format Info inset to text. "
1399
1400     i = 0
1401     while True:
1402         i = find_token(document.body, "\\begin_inset Info", i+1)
1403         if i == -1:
1404             return
1405         j = find_end_of_inset(document.body, i+1)
1406         if j == -1:
1407             document.warning("Malformed LyX document: Could not find end of Info inset.")
1408             continue
1409         tp = find_token(document.body, 'type', i, j)
1410         tpv = get_quoted_value(document.body, "type", tp)
1411         if tpv != "lyxinfo":
1412             continue
1413         arg = find_token(document.body, 'arg', i, j)
1414         argv = get_quoted_value(document.body, "arg", arg)
1415         if argv != "layoutformat":
1416             continue
1417         # hardcoded for now
1418         document.body[i : j+1] = "69"
1419
1420
1421 def convert_hebrew_parentheses(document):
1422     """ Swap opening/closing parentheses in Hebrew text.
1423
1424     Up to LyX 2.4, "(" was used as closing parenthesis and
1425     ")" as opening parenthesis for Hebrew in the LyX source.
1426     """
1427     # print("convert hebrew parentheses")
1428     current_languages = [document.language]
1429     for i, line in enumerate(document.body):
1430         if line.startswith('\\lang '):
1431             current_languages[-1] = line.lstrip('\\lang ')
1432         elif line.startswith('\\begin_layout'):
1433             current_languages.append(current_languages[-1])
1434             # print (line, current_languages[-1])
1435         elif line.startswith('\\end_layout'):
1436             current_languages.pop()
1437         elif current_languages[-1] == 'hebrew' and not line.startswith('\\'):
1438             document.body[i] = line.replace('(','\x00').replace(')','(').replace('\x00',')')
1439
1440
1441 def revert_hebrew_parentheses(document):
1442     " Store parentheses in Hebrew text reversed"
1443     # This only exists to keep the convert/revert naming convention
1444     convert_hebrew_parentheses(document)
1445
1446
1447 def revert_malayalam(document):
1448     " Set the document language to English but assure Malayalam output "
1449
1450     revert_language(document, "malayalam", "", "malayalam")
1451
1452
1453 def revert_soul(document):
1454     " Revert soul module flex insets to ERT "
1455
1456     flexes = ["Spaceletters", "Strikethrough", "Underline", "Highlight", "Capitalize"]
1457
1458     for flex in flexes:
1459         i = find_token(document.body, "\\begin_inset Flex %s" % flex, 0)
1460         if i != -1:
1461             add_to_preamble(document, ["\\usepackage{soul}"])
1462             break
1463     i = find_token(document.body, "\\begin_inset Flex Highlight", 0)
1464     if i != -1:
1465         add_to_preamble(document, ["\\usepackage{color}"])
1466
1467     revert_flex_inset(document.body, "Spaceletters", "\\so")
1468     revert_flex_inset(document.body, "Strikethrough", "\\st")
1469     revert_flex_inset(document.body, "Underline", "\\ul")
1470     revert_flex_inset(document.body, "Highlight", "\\hl")
1471     revert_flex_inset(document.body, "Capitalize", "\\caps")
1472
1473
1474 def revert_tablestyle(document):
1475     " Remove tablestyle params "
1476
1477     i = 0
1478     i = find_token(document.header, "\\tablestyle")
1479     if i != -1:
1480         del document.header[i]
1481
1482
1483 def revert_bibfileencodings(document):
1484     " Revert individual Biblatex bibliography encodings "
1485
1486     # Get cite engine
1487     engine = "basic"
1488     i = find_token(document.header, "\\cite_engine", 0)
1489     if i == -1:
1490         document.warning("Malformed document! Missing \\cite_engine")
1491     else:
1492         engine = get_value(document.header, "\\cite_engine", i)
1493
1494     # Check if biblatex
1495     biblatex = False
1496     if engine in ["biblatex", "biblatex-natbib"]:
1497         biblatex = True
1498
1499     # Map lyx to latex encoding names
1500     encodings = {
1501         "utf8" : "utf8",
1502         "utf8x" : "utf8x",
1503         "armscii8" : "armscii8",
1504         "iso8859-1" : "latin1",
1505         "iso8859-2" : "latin2",
1506         "iso8859-3" : "latin3",
1507         "iso8859-4" : "latin4",
1508         "iso8859-5" : "iso88595",
1509         "iso8859-6" : "8859-6",
1510         "iso8859-7" : "iso-8859-7",
1511         "iso8859-8" : "8859-8",
1512         "iso8859-9" : "latin5",
1513         "iso8859-13" : "latin7",
1514         "iso8859-15" : "latin9",
1515         "iso8859-16" : "latin10",
1516         "applemac" : "applemac",
1517         "cp437" : "cp437",
1518         "cp437de" : "cp437de",
1519         "cp850" : "cp850",
1520         "cp852" : "cp852",
1521         "cp855" : "cp855",
1522         "cp858" : "cp858",
1523         "cp862" : "cp862",
1524         "cp865" : "cp865",
1525         "cp866" : "cp866",
1526         "cp1250" : "cp1250",
1527         "cp1251" : "cp1251",
1528         "cp1252" : "cp1252",
1529         "cp1255" : "cp1255",
1530         "cp1256" : "cp1256",
1531         "cp1257" : "cp1257",
1532         "koi8-r" : "koi8-r",
1533         "koi8-u" : "koi8-u",
1534         "pt154" : "pt154",
1535         "utf8-platex" : "utf8",
1536         "ascii" : "ascii"
1537     }
1538
1539     i = 0
1540     bibresources = []
1541     while (True):
1542         i = find_token(document.body, "\\begin_inset CommandInset bibtex", i+1)
1543         if i == -1:
1544             break
1545         j = find_end_of_inset(document.body, i)
1546         if j == -1:
1547             document.warning("Can't find end of bibtex inset at line %d!!" %(i))
1548             continue
1549         encodings = get_quoted_value(document.body, "file_encodings", i, j)
1550         if not encodings:
1551             i = j
1552             continue
1553         bibfiles = get_quoted_value(document.body, "bibfiles", i, j).split(",")
1554         opts = get_quoted_value(document.body, "biblatexopts", i, j)
1555         if len(bibfiles) == 0:
1556             document.warning("Bibtex inset at line %d does not have a bibfile!" %(i))
1557         # remove encoding line
1558         k = find_token(document.body, "file_encodings", i, j)
1559         if k != -1:
1560             del document.body[k]
1561         # Re-find inset end line
1562         j = find_end_of_inset(document.body, i)
1563         if biblatex:
1564             enclist = encodings.split("\t")
1565             encmap = dict()
1566             for pp in enclist:
1567                 ppp = pp.split(" ", 1)
1568                 encmap[ppp[0]] = ppp[1]
1569             for bib in bibfiles:
1570                 pr = "\\addbibresource"
1571                 if bib in encmap.keys():
1572                     pr += "[bibencoding=" + encmap[bib] + "]"
1573                 pr += "{" + bib + "}"
1574                 add_to_preamble(document, [pr])
1575             # Insert ERT \\printbibliography and wrap bibtex inset to a Note
1576             pcmd = "printbibliography"
1577             if opts:
1578                 pcmd += "[" + opts + "]"
1579             repl = ["\\begin_inset ERT", "status open", "", "\\begin_layout Plain Layout",\
1580                     "", "", "\\backslash", pcmd, "\\end_layout", "", "\\end_inset", "", "",\
1581                     "\\end_layout", "", "\\begin_layout Standard", "\\begin_inset Note Note",\
1582                     "status open", "", "\\begin_layout Plain Layout" ]
1583             repl += document.body[i:j+1]
1584             repl += ["", "\\end_layout", "", "\\end_inset", "", ""]
1585             document.body[i:j+1] = repl
1586             j += 27
1587
1588         i = j
1589
1590
1591 def revert_cmidruletrimming(document):
1592     " Remove \\cmidrule trimming "
1593
1594     # FIXME: Revert to TeX code?
1595     i = 0
1596     while True:
1597         # first, let's find out if we need to do anything
1598         i = find_token(document.body, '<cell ', i+1)
1599         if i == -1:
1600             return
1601         j = document.body[i].find('trim="')
1602         if j == -1:
1603              continue
1604         rgx = re.compile(r' (bottom|top)line[lr]trim="true"')
1605         # remove trim option
1606         document.body[i] = rgx.sub('', document.body[i])
1607
1608
1609 ruby_inset_def = [
1610     r'### Inserted by lyx2lyx (ruby inset) ###',
1611     r'InsetLayout Flex:Ruby',
1612     r'  LyxType       charstyle',
1613     r'  LatexType     command',
1614     r'  LatexName     ruby',
1615     r'  HTMLTag       ruby',
1616     r'  HTMLAttr      ""',
1617     r'  HTMLInnerTag  rb',
1618     r'  HTMLInnerAttr ""',
1619     r'  BgColor       none',
1620     r'  LabelString   "Ruby"',
1621     r'  Decoration    Conglomerate',
1622     r'  Preamble',
1623     r'    \ifdefined\kanjiskip',
1624     r'      \IfFileExists{okumacro.sty}{\usepackage{okumacro}}{}',
1625     r'    \else \ifdefined\luatexversion',
1626     r'      \usepackage{luatexja-ruby}',
1627     r'    \else \ifdefined\XeTeXversion',
1628     r'      \usepackage{ruby}%',
1629     r'    \fi\fi\fi',
1630     r'    \providecommand{\ruby}[2]{\shortstack{\tiny #2\\#1}}',
1631     r'  EndPreamble',
1632     r'  Argument  post:1',
1633     r'    LabelString  "ruby text"',
1634     r'    MenuString  "Ruby Text|R"',
1635     r'    Tooltip    "Reading aid (ruby, furigana) for Chinese characters."',
1636     r'    Decoration  Conglomerate',
1637     r'    Font',
1638     r'      Size    tiny',
1639     r'    EndFont',
1640     r'    LabelFont',
1641     r'      Size    tiny',
1642     r'    EndFont',
1643     r'    Mandatory  1',
1644     r'  EndArgument',
1645     r'End',
1646 ]
1647
1648 def convert_ruby_module(document):
1649     " Use ruby module instead of local module definition "
1650     if document.del_local_layout(ruby_inset_def):
1651         document.add_module("ruby")
1652
1653 def revert_ruby_module(document):
1654     " Replace ruby module with local module definition "
1655     if document.del_module("ruby"):
1656         document.append_local_layout(ruby_inset_def)
1657
1658
1659 def convert_utf8_japanese(document):
1660     " Use generic utf8 with Japanese documents."
1661     lang = get_value(document.header, "\\language")
1662     if not lang.startswith("japanese"):
1663         return
1664     inputenc = get_value(document.header, "\\inputencoding")
1665     if ((lang == "japanese" and inputenc == "utf8-platex")
1666         or (lang == "japanese-cjk" and inputenc == "utf8-cjk")):
1667         document.set_parameter("inputencoding", "utf8")
1668
1669 def revert_utf8_japanese(document):
1670     " Use Japanese utf8 variants with Japanese documents."
1671     inputenc = get_value(document.header, "\\inputencoding")
1672     if inputenc != "utf8":
1673         return
1674     lang = get_value(document.header, "\\language")
1675     if lang == "japanese":
1676         document.set_parameter("inputencoding", "utf8-platex")
1677     if lang == "japanese-cjk":
1678         document.set_parameter("inputencoding", "utf8-cjk")
1679
1680
1681 def revert_lineno(document):
1682     " Replace lineno setting with user-preamble code."
1683
1684     options = get_quoted_value(document.header, "\\lineno_options",
1685                                delete=True)
1686     if not get_bool_value(document.header, "\\use_lineno", delete=True):
1687         return
1688     if options:
1689         options = "[" + options + "]"
1690     add_to_preamble(document, ["\\usepackage%s{lineno}" % options,
1691                                "\\linenumbers"])
1692
1693 def convert_lineno(document):
1694     " Replace user-preamble code with native lineno support."
1695     use_lineno = 0
1696     options = ""
1697     i = find_token(document.preamble, "\\linenumbers", 1)
1698     if i > -1:
1699         usepkg = re.match(r"\\usepackage(.*){lineno}", document.preamble[i-1])
1700         if usepkg:
1701             use_lineno = 1
1702             options = usepkg.group(1).strip("[]")
1703             del(document.preamble[i-1:i+1])
1704             del_token(document.preamble, "% Added by lyx2lyx", i-2, i-1)
1705
1706     k = find_token(document.header, "\\index ")
1707     if options == "":
1708         document.header[k:k] = ["\\use_lineno %d" % use_lineno]
1709     else:
1710         document.header[k:k] = ["\\use_lineno %d" % use_lineno,
1711                                 "\\lineno_options %s" % options]
1712
1713
1714 def revert_new_languages(document):
1715     """Emulate support for Azerbaijani, Bengali, Church Slavonic, Korean,
1716     and Russian (Petrine orthography)."""
1717
1718     #                lyxname:          (babelname, polyglossianame)
1719     new_languages = {"azerbaijani":    ("azerbaijani", ""),
1720                      "bengali":        ("", "bengali"),
1721                      "churchslavonic": ("", "churchslavonic"),
1722                      "oldrussian":     ("", "russian"),
1723                      "korean":         ("", "korean"),
1724                     }
1725     used_languages = set()
1726     if document.language in new_languages:
1727         used_languages.add(document.language)
1728     i = 0
1729     while True:
1730         i = find_token(document.body, "\\lang", i+1)
1731         if i == -1:
1732             break
1733         if document.body[i][6:].strip() in new_languages:
1734             used_languages.add(document.language)
1735
1736     # Korean is already supported via CJK, so leave as-is for Babel
1737     if ("korean" in used_languages
1738         and get_bool_value(document.header, "\\use_non_tex_fonts")
1739         and get_value(document.header, "\\language_package") in ("default", "auto")):
1740         revert_language(document, "korean", "", "korean")
1741     used_languages.discard("korean")
1742
1743     for lang in used_languages:
1744         revert(lang, *new_languages[lang])
1745
1746
1747 gloss_inset_def = [
1748     r'### Inserted by lyx2lyx (deprecated ling glosses) ###',
1749     r'InsetLayout Flex:Glosse',
1750     r'  LyXType               custom',
1751     r'  LabelString           "Gloss (old version)"',
1752     r'  MenuString            "Gloss (old version)"',
1753     r'  LatexType             environment',
1754     r'  LatexName             linggloss',
1755     r'  Decoration            minimalistic',
1756     r'  LabelFont',
1757     r'    Size                Small',
1758     r'  EndFont',
1759     r'  MultiPar              true',
1760     r'  CustomPars            false',
1761     r'  ForcePlain            true',
1762     r'  ParbreakIsNewline     true',
1763     r'  FreeSpacing           true',
1764     r'  Requires              covington',
1765     r'  Preamble',
1766     r'          \def\glosstr{}',
1767     r'          \@ifundefined{linggloss}{%',
1768     r'          \newenvironment{linggloss}[2][]{',
1769     r'             \def\glosstr{\glt #1}%',
1770     r'             \gll #2}',
1771     r'          {\glosstr\glend}}{}',
1772     r'  EndPreamble',
1773     r'  InToc                 true',
1774     r'  ResetsFont            true',
1775     r'  Argument 1',
1776     r'          Decoration    conglomerate',
1777     r'          LabelString   "Translation"',
1778     r'          MenuString    "Glosse Translation|s"',
1779     r'          Tooltip       "Add a translation for the glosse"',
1780     r'  EndArgument',
1781     r'End'
1782 ]
1783
1784 glosss_inset_def = [
1785     r'### Inserted by lyx2lyx (deprecated ling glosses) ###',
1786     r'InsetLayout Flex:Tri-Glosse',
1787     r'  LyXType               custom',
1788     r'  LabelString           "Tri-Gloss (old version)"',
1789     r'  MenuString            "Tri-Gloss (old version)"',
1790     r'  LatexType             environment',
1791     r'  LatexName             lingglosss',
1792     r'  Decoration            minimalistic',
1793     r'  LabelFont',
1794     r'    Size                Small',
1795     r'  EndFont',
1796     r'  MultiPar              true',
1797     r'  CustomPars            false',
1798     r'  ForcePlain            true',
1799     r'  ParbreakIsNewline     true',
1800     r'  FreeSpacing           true',
1801     r'  InToc                 true',
1802     r'  Requires              covington',
1803     r'  Preamble',
1804     r'          \def\glosstr{}',
1805     r'          \@ifundefined{lingglosss}{%',
1806     r'          \newenvironment{lingglosss}[2][]{',
1807     r'              \def\glosstr{\glt #1}%',
1808     r'              \glll #2}',
1809     r'          {\glosstr\glend}}{}',
1810     r'  EndPreamble',
1811     r'  ResetsFont            true',
1812     r'  Argument 1',
1813     r'          Decoration    conglomerate',
1814     r'          LabelString   "Translation"',
1815     r'          MenuString    "Glosse Translation|s"',
1816     r'          Tooltip       "Add a translation for the glosse"',
1817     r'  EndArgument',
1818     r'End'
1819 ]
1820
1821 def convert_linggloss(document):
1822     " Move old ling glosses to local layout "
1823     if find_token(document.body, '\\begin_inset Flex Glosse', 0) != -1:
1824         document.append_local_layout(gloss_inset_def)
1825     if find_token(document.body, '\\begin_inset Flex Tri-Glosse', 0) != -1:
1826         document.append_local_layout(glosss_inset_def)
1827
1828 def revert_linggloss(document):
1829     " Revert to old ling gloss definitions "
1830     if not "linguistics" in document.get_module_list():
1831         return
1832     document.del_local_layout(gloss_inset_def)
1833     document.del_local_layout(glosss_inset_def)
1834
1835     cov_req = False
1836     glosses = ["\\begin_inset Flex Interlinear Gloss (2 Lines)", "\\begin_inset Flex Interlinear Gloss (3 Lines)"]
1837     for glosse in glosses:
1838         i = 0
1839         while True:
1840             i = find_token(document.body, glosse, i+1)
1841             if i == -1:
1842                 break
1843             j = find_end_of_inset(document.body, i)
1844             if j == -1:
1845                 document.warning("Malformed LyX document: Can't find end of Gloss inset")
1846                 continue
1847
1848             arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
1849             endarg = find_end_of_inset(document.body, arg)
1850             optargcontent = []
1851             if arg != -1:
1852                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
1853                 if argbeginPlain == -1:
1854                     document.warning("Malformed LyX document: Can't find optarg plain Layout")
1855                     continue
1856                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
1857                 optargcontent = document.body[argbeginPlain + 1 : argendPlain - 2]
1858
1859                 # remove Arg insets and paragraph, if it only contains this inset
1860                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
1861                     del document.body[arg - 1 : endarg + 4]
1862                 else:
1863                     del document.body[arg : endarg + 1]
1864
1865             arg = find_token(document.body, "\\begin_inset Argument post:1", i, j)
1866             endarg = find_end_of_inset(document.body, arg)
1867             marg1content = []
1868             if arg != -1:
1869                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
1870                 if argbeginPlain == -1:
1871                     document.warning("Malformed LyX document: Can't find arg 1 plain Layout")
1872                     continue
1873                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
1874                 marg1content = document.body[argbeginPlain + 1 : argendPlain - 2]
1875
1876                 # remove Arg insets and paragraph, if it only contains this inset
1877                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
1878                     del document.body[arg - 1 : endarg + 4]
1879                 else:
1880                     del document.body[arg : endarg + 1]
1881
1882             arg = find_token(document.body, "\\begin_inset Argument post:2", i, j)
1883             endarg = find_end_of_inset(document.body, arg)
1884             marg2content = []
1885             if arg != -1:
1886                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
1887                 if argbeginPlain == -1:
1888                     document.warning("Malformed LyX document: Can't find arg 2 plain Layout")
1889                     continue
1890                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
1891                 marg2content = document.body[argbeginPlain + 1 : argendPlain - 2]
1892
1893                 # remove Arg insets and paragraph, if it only contains this inset
1894                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
1895                     del document.body[arg - 1 : endarg + 4]
1896                 else:
1897                     del document.body[arg : endarg + 1]
1898
1899             arg = find_token(document.body, "\\begin_inset Argument post:3", i, j)
1900             endarg = find_end_of_inset(document.body, arg)
1901             marg3content = []
1902             if arg != -1:
1903                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
1904                 if argbeginPlain == -1:
1905                     document.warning("Malformed LyX document: Can't find arg 3 plain Layout")
1906                     continue
1907                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
1908                 marg3content = document.body[argbeginPlain + 1 : argendPlain - 2]
1909
1910                 # remove Arg insets and paragraph, if it only contains this inset
1911                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
1912                     del document.body[arg - 1 : endarg + 4]
1913                 else:
1914                     del document.body[arg : endarg + 1]
1915
1916             cmd = "\\digloss"
1917             if glosse == "\\begin_inset Flex Interlinear Gloss (3 Lines)":
1918                 cmd = "\\trigloss"
1919
1920             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1921             endInset = find_end_of_inset(document.body, i)
1922             endPlain = find_token_backwards(document.body, "\\end_layout", endInset)
1923             precontent = put_cmd_in_ert(cmd)
1924             if len(optargcontent) > 0:
1925                 precontent += put_cmd_in_ert("[") + optargcontent + put_cmd_in_ert("]")
1926             precontent += put_cmd_in_ert("{")
1927
1928             postcontent = put_cmd_in_ert("}{") + marg1content + put_cmd_in_ert("}{") + marg2content
1929             if cmd == "\\trigloss":
1930                 postcontent += put_cmd_in_ert("}{") + marg3content
1931             postcontent += put_cmd_in_ert("}")
1932
1933             document.body[endPlain:endInset + 1] = postcontent
1934             document.body[beginPlain + 1:beginPlain] = precontent
1935             del document.body[i : beginPlain + 1]
1936             if not cov_req:
1937                 document.append_local_layout("Requires covington")
1938                 cov_req = True
1939             i = beginPlain
1940
1941
1942 def revert_subexarg(document):
1943     " Revert linguistic subexamples with argument to ERT "
1944
1945     if not "linguistics" in document.get_module_list():
1946         return
1947
1948     cov_req = False
1949     i = 0
1950     while True:
1951         i = find_token(document.body, "\\begin_layout Subexample", i+1)
1952         if i == -1:
1953             break
1954         j = find_end_of_layout(document.body, i)
1955         if j == -1:
1956             document.warning("Malformed LyX document: Can't find end of Subexample layout")
1957             continue
1958         while True:
1959             # check for consecutive layouts
1960             k = find_token(document.body, "\\begin_layout", j)
1961             if k == -1 or document.body[k] != "\\begin_layout Subexample":
1962                 break
1963             j = find_end_of_layout(document.body, k)
1964             if j == -1:
1965                  document.warning("Malformed LyX document: Can't find end of Subexample layout")
1966                  continue
1967
1968         arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
1969         if arg == -1:
1970             continue
1971
1972         endarg = find_end_of_inset(document.body, arg)
1973         optargcontent = ""
1974         argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
1975         if argbeginPlain == -1:
1976             document.warning("Malformed LyX document: Can't find optarg plain Layout")
1977             continue
1978         argendPlain = find_end_of_inset(document.body, argbeginPlain)
1979         optargcontent = lyx2latex(document, document.body[argbeginPlain + 1 : argendPlain - 2])
1980
1981         # remove Arg insets and paragraph, if it only contains this inset
1982         if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
1983             del document.body[arg - 1 : endarg + 4]
1984         else:
1985             del document.body[arg : endarg + 1]
1986
1987         cmd = put_cmd_in_ert("\\begin{subexamples}[" + optargcontent + "]")
1988
1989         # re-find end of layout
1990         j = find_end_of_layout(document.body, i)
1991         if j == -1:
1992             document.warning("Malformed LyX document: Can't find end of Subexample layout")
1993             continue
1994         while True:
1995             # check for consecutive layouts
1996             k = find_token(document.body, "\\begin_layout", j)
1997             if k == -1 or document.body[k] != "\\begin_layout Subexample":
1998                 break
1999             document.body[k : k + 1] = ["\\begin_layout Standard"] + put_cmd_in_ert("\\item ")
2000             j = find_end_of_layout(document.body, k)
2001             if j == -1:
2002                  document.warning("Malformed LyX document: Can't find end of Subexample layout")
2003                  continue
2004
2005         endev = put_cmd_in_ert("\\end{subexamples}")
2006
2007         document.body[j : j] = ["\\end_layout", "", "\\begin_layout Standard"] + endev
2008         document.body[i : i + 1] = ["\\begin_layout Standard"] + cmd \
2009                 + ["\\end_layout", "", "\\begin_layout Standard"] + put_cmd_in_ert("\\item ")
2010         if not cov_req:
2011             document.append_local_layout("Requires covington")
2012             cov_req = True
2013
2014
2015 def revert_drs(document):
2016     " Revert DRS insets (linguistics) to ERT "
2017
2018     if not "linguistics" in document.get_module_list():
2019         return
2020
2021     cov_req = False
2022     drses = ["\\begin_inset Flex DRS", "\\begin_inset Flex DRS*",
2023              "\\begin_inset Flex IfThen-DRS", "\\begin_inset Flex Cond-DRS",
2024              "\\begin_inset Flex QDRS", "\\begin_inset Flex NegDRS",
2025              "\\begin_inset Flex SDRS"]
2026     for drs in drses:
2027         i = 0
2028         while True:
2029             i = find_token(document.body, drs, i+1)
2030             if i == -1:
2031                 break
2032             j = find_end_of_inset(document.body, i)
2033             if j == -1:
2034                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2035                 continue
2036
2037             # Check for arguments
2038             arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
2039             endarg = find_end_of_inset(document.body, arg)
2040             prearg1content = []
2041             if arg != -1:
2042                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2043                 if argbeginPlain == -1:
2044                     document.warning("Malformed LyX document: Can't find Argument 1 plain Layout")
2045                     continue
2046                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2047                 prearg1content = document.body[argbeginPlain + 1 : argendPlain - 2]
2048
2049                 # remove Arg insets and paragraph, if it only contains this inset
2050                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2051                     del document.body[arg - 1 : endarg + 4]
2052                 else:
2053                     del document.body[arg : endarg + 1]
2054
2055             # re-find inset end
2056             j = find_end_of_inset(document.body, i)
2057             if j == -1:
2058                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2059                 continue
2060
2061             arg = find_token(document.body, "\\begin_inset Argument 2", i, j)
2062             endarg = find_end_of_inset(document.body, arg)
2063             prearg2content = []
2064             if arg != -1:
2065                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2066                 if argbeginPlain == -1:
2067                     document.warning("Malformed LyX document: Can't find Argument 2 plain Layout")
2068                     continue
2069                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2070                 prearg2content = document.body[argbeginPlain + 1 : argendPlain - 2]
2071
2072                 # remove Arg insets and paragraph, if it only contains this inset
2073                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2074                     del document.body[arg - 1 : endarg + 4]
2075                 else:
2076                     del document.body[arg : endarg + 1]
2077
2078             # re-find inset end
2079             j = find_end_of_inset(document.body, i)
2080             if j == -1:
2081                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2082                 continue
2083
2084             arg = find_token(document.body, "\\begin_inset Argument post:1", i, j)
2085             endarg = find_end_of_inset(document.body, arg)
2086             postarg1content = []
2087             if arg != -1:
2088                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2089                 if argbeginPlain == -1:
2090                     document.warning("Malformed LyX document: Can't find Argument post:1 plain Layout")
2091                     continue
2092                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2093                 postarg1content = document.body[argbeginPlain + 1 : argendPlain - 2]
2094
2095                 # remove Arg insets and paragraph, if it only contains this inset
2096                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2097                     del document.body[arg - 1 : endarg + 4]
2098                 else:
2099                     del document.body[arg : endarg + 1]
2100
2101             # re-find inset end
2102             j = find_end_of_inset(document.body, i)
2103             if j == -1:
2104                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2105                 continue
2106
2107             arg = find_token(document.body, "\\begin_inset Argument post:2", i, j)
2108             endarg = find_end_of_inset(document.body, arg)
2109             postarg2content = []
2110             if arg != -1:
2111                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2112                 if argbeginPlain == -1:
2113                     document.warning("Malformed LyX document: Can't find Argument post:2 plain Layout")
2114                     continue
2115                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2116                 postarg2content = document.body[argbeginPlain + 1 : argendPlain - 2]
2117
2118                 # remove Arg insets and paragraph, if it only contains this inset
2119                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2120                     del document.body[arg - 1 : endarg + 4]
2121                 else:
2122                     del document.body[arg : endarg + 1]
2123
2124             # re-find inset end
2125             j = find_end_of_inset(document.body, i)
2126             if j == -1:
2127                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2128                 continue
2129
2130             arg = find_token(document.body, "\\begin_inset Argument post:3", i, j)
2131             endarg = find_end_of_inset(document.body, arg)
2132             postarg3content = []
2133             if arg != -1:
2134                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2135                 if argbeginPlain == -1:
2136                     document.warning("Malformed LyX document: Can't find Argument post:3 plain Layout")
2137                     continue
2138                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2139                 postarg3content = document.body[argbeginPlain + 1 : argendPlain - 2]
2140
2141                 # remove Arg insets and paragraph, if it only contains this inset
2142                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2143                     del document.body[arg - 1 : endarg + 4]
2144                 else:
2145                     del document.body[arg : endarg + 1]
2146
2147             # re-find inset end
2148             j = find_end_of_inset(document.body, i)
2149             if j == -1:
2150                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2151                 continue
2152
2153             arg = find_token(document.body, "\\begin_inset Argument post:4", i, j)
2154             endarg = find_end_of_inset(document.body, arg)
2155             postarg4content = []
2156             if arg != -1:
2157                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2158                 if argbeginPlain == -1:
2159                     document.warning("Malformed LyX document: Can't find Argument post:4 plain Layout")
2160                     continue
2161                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2162                 postarg4content = document.body[argbeginPlain + 1 : argendPlain - 2]
2163
2164                 # remove Arg insets and paragraph, if it only contains this inset
2165                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2166                     del document.body[arg - 1 : endarg + 4]
2167                 else:
2168                     del document.body[arg : endarg + 1]
2169
2170             # The respective LaTeX command
2171             cmd = "\\drs"
2172             if drs == "\\begin_inset Flex DRS*":
2173                 cmd = "\\drs*"
2174             elif drs == "\\begin_inset Flex IfThen-DRS":
2175                 cmd = "\\ifdrs"
2176             elif drs == "\\begin_inset Flex Cond-DRS":
2177                 cmd = "\\condrs"
2178             elif drs == "\\begin_inset Flex QDRS":
2179                 cmd = "\\qdrs"
2180             elif drs == "\\begin_inset Flex NegDRS":
2181                 cmd = "\\negdrs"
2182             elif drs == "\\begin_inset Flex SDRS":
2183                 cmd = "\\sdrs"
2184
2185             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2186             endInset = find_end_of_inset(document.body, i)
2187             endPlain = find_token_backwards(document.body, "\\end_layout", endInset)
2188             precontent = put_cmd_in_ert(cmd)
2189             precontent += put_cmd_in_ert("{") + prearg1content + put_cmd_in_ert("}")
2190             if drs == "\\begin_inset Flex SDRS":
2191                 precontent += put_cmd_in_ert("{") + prearg2content + put_cmd_in_ert("}")
2192             precontent += put_cmd_in_ert("{")
2193
2194             postcontent = []
2195             if cmd == "\\qdrs" or cmd == "\\condrs" or cmd == "\\ifdrs":
2196                 postcontent = put_cmd_in_ert("}{") + postarg1content + put_cmd_in_ert("}{") + postarg2content + put_cmd_in_ert("}")
2197                 if cmd == "\\condrs" or cmd == "\\qdrs":
2198                     postcontent += put_cmd_in_ert("{") + postarg3content + put_cmd_in_ert("}")
2199                 if cmd == "\\qdrs":
2200                     postcontent += put_cmd_in_ert("{") + postarg4content + put_cmd_in_ert("}")
2201             else:
2202                 postcontent = put_cmd_in_ert("}")
2203
2204             document.body[endPlain:endInset + 1] = postcontent
2205             document.body[beginPlain + 1:beginPlain] = precontent
2206             del document.body[i : beginPlain + 1]
2207             if not cov_req:
2208                 document.append_local_layout("Provides covington 1")
2209                 add_to_preamble(document, ["\\usepackage{drs,covington}"])
2210                 cov_req = True
2211             i = beginPlain
2212
2213
2214
2215 def revert_babelfont(document):
2216     " Reverts the use of \\babelfont to user preamble "
2217
2218     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2219     if i == -1:
2220         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2221         return
2222     if not str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2223         return
2224     i = find_token(document.header, '\\language_package', 0)
2225     if i == -1:
2226         document.warning("Malformed LyX document: Missing \\language_package.")
2227         return
2228     if get_value(document.header, "\\language_package", 0) != "babel":
2229         return
2230
2231     # check font settings
2232     # defaults
2233     roman = sans = typew = "default"
2234     osf = False
2235     sf_scale = tt_scale = 100.0
2236
2237     j = find_token(document.header, "\\font_roman", 0)
2238     if j == -1:
2239         document.warning("Malformed LyX document: Missing \\font_roman.")
2240     else:
2241         # We need to use this regex since split() does not handle quote protection
2242         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2243         roman = romanfont[2].strip('"')
2244         romanfont[2] = '"default"'
2245         document.header[j] = " ".join(romanfont)
2246
2247     j = find_token(document.header, "\\font_sans", 0)
2248     if j == -1:
2249         document.warning("Malformed LyX document: Missing \\font_sans.")
2250     else:
2251         # We need to use this regex since split() does not handle quote protection
2252         sansfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2253         sans = sansfont[2].strip('"')
2254         sansfont[2] = '"default"'
2255         document.header[j] = " ".join(sansfont)
2256
2257     j = find_token(document.header, "\\font_typewriter", 0)
2258     if j == -1:
2259         document.warning("Malformed LyX document: Missing \\font_typewriter.")
2260     else:
2261         # We need to use this regex since split() does not handle quote protection
2262         ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2263         typew = ttfont[2].strip('"')
2264         ttfont[2] = '"default"'
2265         document.header[j] = " ".join(ttfont)
2266
2267     i = find_token(document.header, "\\font_osf", 0)
2268     if i == -1:
2269         document.warning("Malformed LyX document: Missing \\font_osf.")
2270     else:
2271         osf = str2bool(get_value(document.header, "\\font_osf", i))
2272
2273     j = find_token(document.header, "\\font_sf_scale", 0)
2274     if j == -1:
2275         document.warning("Malformed LyX document: Missing \\font_sf_scale.")
2276     else:
2277         sfscale = document.header[j].split()
2278         val = sfscale[2]
2279         sfscale[2] = "100"
2280         document.header[j] = " ".join(sfscale)
2281         try:
2282             # float() can throw
2283             sf_scale = float(val)
2284         except:
2285             document.warning("Invalid font_sf_scale value: " + val)
2286
2287     j = find_token(document.header, "\\font_tt_scale", 0)
2288     if j == -1:
2289         document.warning("Malformed LyX document: Missing \\font_tt_scale.")
2290     else:
2291         ttscale = document.header[j].split()
2292         val = ttscale[2]
2293         ttscale[2] = "100"
2294         document.header[j] = " ".join(ttscale)
2295         try:
2296             # float() can throw
2297             tt_scale = float(val)
2298         except:
2299             document.warning("Invalid font_tt_scale value: " + val)
2300
2301     # set preamble stuff
2302     pretext = ['%% This document must be processed with xelatex or lualatex!']
2303     pretext.append('\\AtBeginDocument{%')
2304     if roman != "default":
2305         pretext.append('\\babelfont{rm}[Mapping=tex-text]{' + roman + '}')
2306     if sans != "default":
2307         sf = '\\babelfont{sf}['
2308         if sf_scale != 100.0:
2309             sf += 'Scale=' + str(sf_scale / 100.0) + ','
2310         sf += 'Mapping=tex-text]{' + sans + '}'
2311         pretext.append(sf)
2312     if typew != "default":
2313         tw = '\\babelfont{tt}'
2314         if tt_scale != 100.0:
2315             tw += '[Scale=' + str(tt_scale / 100.0) + ']'
2316         tw += '{' + typew + '}'
2317         pretext.append(tw)
2318     if osf:
2319         pretext.append('\\defaultfontfeatures{Numbers=OldStyle}')
2320     pretext.append('}')
2321     insert_to_preamble(document, pretext)
2322
2323
2324 def revert_minionpro(document):
2325     " Revert native MinionPro font definition (with extra options) to LaTeX "
2326
2327     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2328     if i == -1:
2329         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2330         return
2331     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2332         return
2333
2334     regexp = re.compile(r'(\\font_roman_opts)')
2335     x = find_re(document.header, regexp, 0)
2336     if x == -1:
2337         return
2338
2339     # We need to use this regex since split() does not handle quote protection
2340     romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2341     opts = romanopts[1].strip('"')
2342
2343     i = find_token(document.header, "\\font_roman", 0)
2344     if i == -1:
2345         document.warning("Malformed LyX document: Missing \\font_roman.")
2346         return
2347     else:
2348         # We need to use this regex since split() does not handle quote protection
2349         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2350         roman = romanfont[1].strip('"')
2351         if roman != "minionpro":
2352             return
2353         romanfont[1] = '"default"'
2354         document.header[i] = " ".join(romanfont)
2355         osf = False
2356         j = find_token(document.header, "\\font_osf true", 0)
2357         if j != -1:
2358             osf = True
2359         preamble = "\\usepackage["
2360         if osf:
2361             document.header[j] = "\\font_osf false"
2362         else:
2363             preamble += "lf,"
2364         preamble += opts
2365         preamble += "]{MinionPro}"
2366         add_to_preamble(document, [preamble])
2367         del document.header[x]
2368
2369
2370 def revert_font_opts(document):
2371     " revert font options by outputting \\setxxxfont or \\babelfont to the preamble "
2372
2373     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2374     if i == -1:
2375         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2376         return
2377     NonTeXFonts = str2bool(get_value(document.header, "\\use_non_tex_fonts", i))
2378     i = find_token(document.header, '\\language_package', 0)
2379     if i == -1:
2380         document.warning("Malformed LyX document: Missing \\language_package.")
2381         return
2382     Babel = (get_value(document.header, "\\language_package", 0) == "babel")
2383
2384     # 1. Roman
2385     regexp = re.compile(r'(\\font_roman_opts)')
2386     i = find_re(document.header, regexp, 0)
2387     if i != -1:
2388         # We need to use this regex since split() does not handle quote protection
2389         romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2390         opts = romanopts[1].strip('"')
2391         del document.header[i]
2392         if NonTeXFonts:
2393             regexp = re.compile(r'(\\font_roman)')
2394             i = find_re(document.header, regexp, 0)
2395             if i != -1:
2396                 # We need to use this regex since split() does not handle quote protection
2397                 romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2398                 font = romanfont[2].strip('"')
2399                 romanfont[2] = '"default"'
2400                 document.header[i] = " ".join(romanfont)
2401                 if font != "default":
2402                     if Babel:
2403                         preamble = "\\babelfont{rm}["
2404                     else:
2405                         preamble = "\\setmainfont["
2406                     preamble += opts
2407                     preamble += ","
2408                     preamble += "Mapping=tex-text]{"
2409                     preamble += font
2410                     preamble += "}"
2411                     add_to_preamble(document, [preamble])
2412
2413     # 2. Sans
2414     regexp = re.compile(r'(\\font_sans_opts)')
2415     i = find_re(document.header, regexp, 0)
2416     if i != -1:
2417         scaleval = 100
2418         # We need to use this regex since split() does not handle quote protection
2419         sfopts = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2420         opts = sfopts[1].strip('"')
2421         del document.header[i]
2422         if NonTeXFonts:
2423             regexp = re.compile(r'(\\font_sf_scale)')
2424             i = find_re(document.header, regexp, 0)
2425             if i != -1:
2426                 scaleval = get_value(document.header, "\\font_sf_scale" , i).split()[1]
2427             regexp = re.compile(r'(\\font_sans)')
2428             i = find_re(document.header, regexp, 0)
2429             if i != -1:
2430                 # We need to use this regex since split() does not handle quote protection
2431                 sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2432                 font = sffont[2].strip('"')
2433                 sffont[2] = '"default"'
2434                 document.header[i] = " ".join(sffont)
2435                 if font != "default":
2436                     if Babel:
2437                         preamble = "\\babelfont{sf}["
2438                     else:
2439                         preamble = "\\setsansfont["
2440                     preamble += opts
2441                     preamble += ","
2442                     if scaleval != 100:
2443                         preamble += "Scale=0."
2444                         preamble += scaleval
2445                         preamble += ","
2446                     preamble += "Mapping=tex-text]{"
2447                     preamble += font
2448                     preamble += "}"
2449                     add_to_preamble(document, [preamble])
2450
2451     # 3. Typewriter
2452     regexp = re.compile(r'(\\font_typewriter_opts)')
2453     i = find_re(document.header, regexp, 0)
2454     if i != -1:
2455         scaleval = 100
2456         # We need to use this regex since split() does not handle quote protection
2457         ttopts = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2458         opts = ttopts[1].strip('"')
2459         del document.header[i]
2460         if NonTeXFonts:
2461             regexp = re.compile(r'(\\font_tt_scale)')
2462             i = find_re(document.header, regexp, 0)
2463             if i != -1:
2464                 scaleval = get_value(document.header, "\\font_tt_scale" , i).split()[1]
2465             regexp = re.compile(r'(\\font_typewriter)')
2466             i = find_re(document.header, regexp, 0)
2467             if i != -1:
2468                 # We need to use this regex since split() does not handle quote protection
2469                 ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2470                 font = ttfont[2].strip('"')
2471                 ttfont[2] = '"default"'
2472                 document.header[i] = " ".join(ttfont)
2473                 if font != "default":
2474                     if Babel:
2475                         preamble = "\\babelfont{tt}["
2476                     else:
2477                         preamble = "\\setmonofont["
2478                     preamble += opts
2479                     preamble += ","
2480                     if scaleval != 100:
2481                         preamble += "Scale=0."
2482                         preamble += scaleval
2483                         preamble += ","
2484                     preamble += "Mapping=tex-text]{"
2485                     preamble += font
2486                     preamble += "}"
2487                     add_to_preamble(document, [preamble])
2488
2489
2490 def revert_plainNotoFonts_xopts(document):
2491     " Revert native (straight) Noto font definition (with extra options) to LaTeX "
2492
2493     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2494     if i == -1:
2495         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2496         return
2497     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2498         return
2499
2500     osf = False
2501     y = find_token(document.header, "\\font_osf true", 0)
2502     if y != -1:
2503         osf = True
2504
2505     regexp = re.compile(r'(\\font_roman_opts)')
2506     x = find_re(document.header, regexp, 0)
2507     if x == -1 and not osf:
2508         return
2509
2510     opts = ""
2511     if x != -1:
2512         # We need to use this regex since split() does not handle quote protection
2513         romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2514         opts = romanopts[1].strip('"')
2515     if osf:
2516         if opts != "":
2517             opts += ", "
2518         opts += "osf"
2519
2520     i = find_token(document.header, "\\font_roman", 0)
2521     if i == -1:
2522         return
2523
2524     # We need to use this regex since split() does not handle quote protection
2525     romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2526     roman = romanfont[1].strip('"')
2527     if roman != "NotoSerif-TLF":
2528         return
2529
2530     j = find_token(document.header, "\\font_sans", 0)
2531     if j == -1:
2532         return
2533
2534     # We need to use this regex since split() does not handle quote protection
2535     sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2536     sf = sffont[1].strip('"')
2537     if sf != "default":
2538         return
2539
2540     j = find_token(document.header, "\\font_typewriter", 0)
2541     if j == -1:
2542         return
2543
2544     # We need to use this regex since split() does not handle quote protection
2545     ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2546     tt = ttfont[1].strip('"')
2547     if tt != "default":
2548         return
2549
2550     # So we have noto as "complete font"
2551     romanfont[1] = '"default"'
2552     document.header[i] = " ".join(romanfont)
2553
2554     preamble = "\\usepackage["
2555     preamble += opts
2556     preamble += "]{noto}"
2557     add_to_preamble(document, [preamble])
2558     if osf:
2559         document.header[y] = "\\font_osf false"
2560     if x != -1:
2561         del document.header[x]
2562
2563
2564 def revert_notoFonts_xopts(document):
2565     " Revert native (extended) Noto font definition (with extra options) to LaTeX "
2566
2567     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2568     if i == -1:
2569         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2570         return
2571     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2572         return
2573
2574     fontmap = dict()
2575     fm = createFontMapping(['Noto'])
2576     if revert_fonts(document, fm, fontmap, True):
2577         add_preamble_fonts(document, fontmap)
2578
2579
2580 def revert_IBMFonts_xopts(document):
2581     " Revert native IBM font definition (with extra options) to LaTeX "
2582
2583
2584     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2585     if i == -1:
2586         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2587         return
2588     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2589         return
2590
2591     fontmap = dict()
2592     fm = createFontMapping(['IBM'])
2593     ft = ""
2594     if revert_fonts(document, fm, fontmap, True):
2595         add_preamble_fonts(document, fontmap)
2596
2597
2598 def revert_AdobeFonts_xopts(document):
2599     " Revert native Adobe font definition (with extra options) to LaTeX "
2600
2601     i = find_token(document.header, '\\use_non_tex_fonts', 0)
2602     if i == -1:
2603         document.warning("Malformed LyX document: Missing \\use_non_tex_fonts.")
2604         return
2605     if str2bool(get_value(document.header, "\\use_non_tex_fonts", i)):
2606         return
2607
2608     fontmap = dict()
2609     fm = createFontMapping(['Adobe'])
2610     ft = ""
2611     if revert_fonts(document, fm, fontmap, True):
2612         add_preamble_fonts(document, fontmap)
2613
2614
2615 ##
2616 # Conversion hub
2617 #
2618
2619 supported_versions = ["2.4.0", "2.4"]
2620 convert = [
2621            [545, [convert_lst_literalparam]],
2622            [546, []],
2623            [547, []],
2624            [548, []],
2625            [549, []],
2626            [550, [convert_fontenc]],
2627            [551, []],
2628            [552, []],
2629            [553, []],
2630            [554, []],
2631            [555, []],
2632            [556, []],
2633            [557, [convert_vcsinfo]],
2634            [558, [removeFrontMatterStyles]],
2635            [559, []],
2636            [560, []],
2637            [561, [convert_latexFonts]], # Handle dejavu, ibmplex fonts in GUI
2638            [562, []],
2639            [563, []],
2640            [564, []],
2641            [565, [convert_AdobeFonts]], # Handle adobe fonts in GUI
2642            [566, [convert_hebrew_parentheses]],
2643            [567, []],
2644            [568, []],
2645            [569, []],
2646            [570, []],
2647            [571, []],
2648            [572, [convert_notoFonts]],  # Added options thin, light, extralight for Noto
2649            [573, [convert_inputencoding_namechange]],
2650            [574, [convert_ruby_module, convert_utf8_japanese]],
2651            [575, [convert_lineno]],
2652            [576, []],
2653            [577, [convert_linggloss]],
2654            [578, []],
2655            [579, []],
2656            [580, []]
2657           ]
2658
2659 revert =  [[579, [revert_minionpro, revert_plainNotoFonts_xopts, revert_notoFonts_xopts, revert_IBMFonts_xopts, revert_AdobeFonts_xopts, revert_font_opts]], # keep revert_font_opts last!
2660            [578, [revert_babelfont]],
2661            [577, [revert_drs]],
2662            [576, [revert_linggloss, revert_subexarg]],
2663            [575, [revert_new_languages]],
2664            [574, [revert_lineno]],
2665            [573, [revert_ruby_module, revert_utf8_japanese]],
2666            [572, [revert_inputencoding_namechange]],
2667            [571, [revert_notoFonts]],
2668            [570, [revert_cmidruletrimming]],
2669            [569, [revert_bibfileencodings]],
2670            [568, [revert_tablestyle]],
2671            [567, [revert_soul]],
2672            [566, [revert_malayalam]],
2673            [565, [revert_hebrew_parentheses]],
2674            [564, [revert_AdobeFonts]],
2675            [563, [revert_lformatinfo]],
2676            [562, [revert_listpargs]],
2677            [561, [revert_l7ninfo]],
2678            [560, [revert_latexFonts]], # Handle dejavu, ibmplex fonts in user preamble
2679            [559, [revert_timeinfo, revert_namenoextinfo]],
2680            [558, [revert_dateinfo]],
2681            [557, [addFrontMatterStyles]],
2682            [556, [revert_vcsinfo]],
2683            [555, [revert_bibencoding]],
2684            [554, [revert_vcolumns]],
2685            [553, [revert_stretchcolumn]],
2686            [552, [revert_tuftecite]],
2687            [551, [revert_floatpclass, revert_floatalignment]],
2688            [550, [revert_nospellcheck]],
2689            [549, [revert_fontenc]],
2690            [548, []],# dummy format change
2691            [547, [revert_lscape]],
2692            [546, [revert_xcharter]],
2693            [545, [revert_paratype]],
2694            [544, [revert_lst_literalparam]]
2695           ]
2696
2697
2698 if __name__ == "__main__":
2699     pass