]> git.lyx.org Git - lyx.git/blob - lyx_2_4.py
8c0b9c303908fec1f3f9b78f28a81ec0c0889a76
[lyx.git] / 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_complete_lines, del_token,
30      find_end_of, find_end_of_inset, find_end_of_layout, find_token,
31      find_token_backwards, find_token_exact, find_re, get_bool_value,
32      get_containing_inset, get_containing_layout, get_option_value, get_value,
33      get_quoted_value)
34 #    del_value, 
35 #    find_complete_lines,
36 #    find_re, find_substring,
37 #    is_in_inset, set_bool_value
38 #    find_tokens, check_token
39
40 from lyx2lyx_tools import (put_cmd_in_ert, add_to_preamble, insert_to_preamble, lyx2latex,
41                            revert_language, revert_flex_inset, str2bool)
42 #  revert_font_attrs, latex_length
43 #  get_ert, lyx2verbatim, length_in_bp, convert_info_insets
44 #  revert_flex_inset, hex2ratio
45
46 ####################################################################
47 # Private helper functions
48
49 def add_preamble_fonts(document, fontmap):
50     """Add collected font-packages with their option to user-preamble"""
51
52     for pkg in fontmap:
53         if len(fontmap[pkg]) > 0:
54             xoption = "[" + ",".join(fontmap[pkg]) + "]"
55         else:
56             xoption = ""
57         preamble = "\\usepackage%s{%s}" % (xoption, pkg)
58         add_to_preamble(document, [preamble])
59
60
61 def createkey(pkg, options):
62     options.sort()
63     return pkg + ':' + "-".join(options)
64
65 class fontinfo:
66     def __init__(self):
67         self.fontname = None    # key into font2pkgmap
68         self.fonttype = None    # roman,sans,typewriter,math
69         self.scaletype = None   # None,sf,tt
70         self.scaleopt = None    # None, 'scaled', 'scale'
71         self.scaleval = 1
72         self.package = None
73         self.options = []
74         self.pkgkey = None      # key into pkg2fontmap
75         self.osfopt = None      # None, string
76         self.osfdef = "false"   # "false" or "true"
77
78     def addkey(self):
79         self.pkgkey = createkey(self.package, self.options)
80
81 class fontmapping:
82     def __init__(self):
83         self.font2pkgmap = dict()
84         self.pkg2fontmap = dict()
85         self.pkginmap = dict()  # defines, if a map for package exists
86
87     def expandFontMapping(self, font_list, font_type, scale_type, pkg, scaleopt = None, osfopt = None, osfdef = "false"):
88         """Expand fontinfo mapping"""
89         #
90         # fontlist:    list of fontnames, each element
91         #              may contain a ','-separated list of needed options
92         #              like e.g. 'IBMPlexSansCondensed,condensed'
93         # font_type:   one of 'roman', 'sans', 'typewriter', 'math'
94         # scale_type:  one of None, 'sf', 'tt'
95         # pkg:         package defining the font. Defaults to fontname if None
96         # scaleopt:    one of None, 'scale', 'scaled', or some other string
97         #              to be used in scale option (e.g. scaled=0.7)
98         # osfopt:      None or some other string to be used in osf option
99         # osfdef:      "true" if osf is default
100         for fl in font_list:
101             fe = fontinfo()
102             fe.fonttype = font_type
103             fe.scaletype = scale_type
104             flt = fl.split(",")
105             font_name = flt[0]
106             fe.fontname = font_name
107             fe.options = flt[1:]
108             fe.scaleopt = scaleopt
109             fe.osfopt = osfopt
110             fe.osfdef = osfdef
111             if pkg == None:
112                 fe.package = font_name
113             else:
114                 fe.package = pkg
115             fe.addkey()
116             self.font2pkgmap[font_name] = fe
117             if fe.pkgkey in self.pkg2fontmap:
118                 # Repeated the same entry? Check content
119                 if self.pkg2fontmap[fe.pkgkey] != font_name:
120                     document.error("Something is wrong in pkgname+options <-> fontname mapping")
121             self.pkg2fontmap[fe.pkgkey] = font_name
122             self.pkginmap[fe.package] = 1
123
124     def getfontname(self, pkg, options):
125         options.sort()
126         pkgkey = createkey(pkg, options)
127         if not pkgkey in self.pkg2fontmap:
128             return None
129         fontname = self.pkg2fontmap[pkgkey]
130         if not fontname in self.font2pkgmap:
131             document.error("Something is wrong in pkgname+options <-> fontname mapping")
132             return None
133         if pkgkey == self.font2pkgmap[fontname].pkgkey:
134             return fontname
135         return None
136
137 def createFontMapping(fontlist):
138     # Create info for known fonts for the use in
139     #   convert_latexFonts() and
140     #   revert_latexFonts()
141     #
142     # * Would be more handy to parse latexFonts file,
143     #   but the path to this file is unknown
144     # * For now, add DejaVu and IBMPlex only.
145     # * Expand, if desired
146     fm = fontmapping()
147     for font in fontlist:
148         if font == 'DejaVu':
149             fm.expandFontMapping(['DejaVuSerif', 'DejaVuSerifCondensed'], "roman", None, None)
150             fm.expandFontMapping(['DejaVuSans','DejaVuSansCondensed'], "sans", "sf", None, "scaled")
151             fm.expandFontMapping(['DejaVuSansMono'], "typewriter", "tt", None, "scaled")
152         elif font == 'IBM':
153             fm.expandFontMapping(['IBMPlexSerif', 'IBMPlexSerifThin,thin',
154                                   'IBMPlexSerifExtraLight,extralight', 'IBMPlexSerifLight,light',
155                                   'IBMPlexSerifSemibold,semibold'],
156                                  "roman", None, "plex-serif")
157             fm.expandFontMapping(['IBMPlexSans','IBMPlexSansCondensed,condensed',
158                                   'IBMPlexSansThin,thin', 'IBMPlexSansExtraLight,extralight',
159                                   'IBMPlexSansLight,light', 'IBMPlexSansSemibold,semibold'],
160                                  "sans", "sf", "plex-sans", "scale")
161             fm.expandFontMapping(['IBMPlexMono', 'IBMPlexMonoThin,thin',
162                                   'IBMPlexMonoExtraLight,extralight', 'IBMPlexMonoLight,light',
163                                   'IBMPlexMonoSemibold,semibold'],
164                                  "typewriter", "tt", "plex-mono", "scale")
165         elif font == 'Adobe':
166             fm.expandFontMapping(['ADOBESourceSerifPro'], "roman", None, "sourceserifpro", None, "osf")
167             fm.expandFontMapping(['ADOBESourceSansPro'], "sans", "sf", "sourcesanspro", "scaled", "osf")
168             fm.expandFontMapping(['ADOBESourceCodePro'], "typewriter", "tt", "sourcecodepro", "scaled", "osf")
169         elif font == 'Noto':
170             fm.expandFontMapping(['NotoSerifRegular,regular', 'NotoSerifMedium,medium',
171                                   'NotoSerifThin,thin', 'NotoSerifLight,light',
172                                   'NotoSerifExtralight,extralight'],
173                                   "roman", None, "noto-serif", None, "osf")
174             fm.expandFontMapping(['NotoSansRegular,regular', 'NotoSansMedium,medium',
175                                   'NotoSansThin,thin', 'NotoSansLight,light',
176                                   'NotoSansExtralight,extralight'],
177                                   "sans", "sf", "noto-sans", "scaled")
178             fm.expandFontMapping(['NotoMonoRegular,regular'], "typewriter", "tt", "noto-mono", "scaled")
179         elif font == 'Cantarell':
180             fm.expandFontMapping(['cantarell,defaultsans'],
181                                   "sans", "sf", "cantarell", "scaled", "oldstyle")
182         elif font == 'Chivo':
183             fm.expandFontMapping(['ChivoThin,thin', 'ChivoLight,light',
184                                   'Chivo,regular', 'ChivoMedium,medium'],
185                                   "sans", "sf", "Chivo", "scale", "oldstyle")
186         elif font == 'CrimsonPro':
187             fm.expandFontMapping(['CrimsonPro', 'CrimsonProExtraLight,extralight', 'CrimsonProLight,light',
188                                   'CrimsonProMedium,medium'],
189                                   "roman", None, "CrimsonPro", None, "lf", "true")
190         elif font == 'Fira':
191             fm.expandFontMapping(['FiraSans', 'FiraSansBook,book',
192                                   'FiraSansThin,thin', 'FiraSansLight,light',
193                                   'FiraSansExtralight,extralight',
194                                   'FiraSansUltralight,ultralight'],
195                                   "sans", "sf", "FiraSans", "scaled", "lf", "true")
196             fm.expandFontMapping(['FiraMono'], "typewriter", "tt", "FiraMono", "scaled", "lf", "true")
197         elif font == 'libertinus':
198             fm.expandFontMapping(['libertinus,serif'], "roman", None, "libertinus", None, "osf")
199             fm.expandFontMapping(['libertinusmath'], "math", None, "libertinust1math", None, None)
200     return fm
201
202 def convert_fonts(document, fm, osfoption = "osf"):
203     """Handle font definition (LaTeX preamble -> native)"""
204     rpkg = re.compile(r'^\\usepackage(\[([^\]]*)\])?\{([^\}]+)\}')
205     rscaleopt = re.compile(r'^scaled?=(.*)')
206
207     # Check whether we go beyond font option feature introduction
208     haveFontOpts = document.end_format > 580
209
210     i = 0
211     while True:
212         i = find_re(document.preamble, rpkg, i+1)
213         if i == -1:
214             return
215         mo = rpkg.search(document.preamble[i])
216         if mo == None or mo.group(2) == None:
217             options = []
218         else:
219             options = mo.group(2).replace(' ', '').split(",")
220         pkg = mo.group(3)
221         o = 0
222         oscale = 1
223         has_osf = False
224         while o < len(options):
225             if options[o] == osfoption:
226                 has_osf = True
227                 del options[o]
228                 continue
229             mo = rscaleopt.search(options[o])
230             if mo == None:
231                 o += 1
232                 continue
233             oscale = mo.group(1)
234             del options[o]
235             continue
236
237         if not pkg in fm.pkginmap:
238             continue
239         # determine fontname
240         fn = None
241         if haveFontOpts:
242             # Try with name-option combination first
243             # (only one default option supported currently)
244             o = 0
245             while o < len(options):
246                 opt = options[o]
247                 fn = fm.getfontname(pkg, [opt])
248                 if fn != None:
249                     del options[o]
250                     break
251                 o += 1
252                 continue
253             if fn == None:
254                 fn = fm.getfontname(pkg, [])
255         else:
256             fn = fm.getfontname(pkg, options)
257         if fn == None:
258             continue
259         del document.preamble[i]
260         fontinfo = fm.font2pkgmap[fn]
261         if fontinfo.scaletype == None:
262             fontscale = None
263         else:
264             fontscale = "\\font_" + fontinfo.scaletype + "_scale"
265             fontinfo.scaleval = oscale
266         if (has_osf and fontinfo.osfdef == "false") or (not has_osf and fontinfo.osfdef == "true"):
267             if fontinfo.osfopt == None:
268                 options.extend(osfoption)
269                 continue
270             osf = find_token(document.header, "\\font_osf false")
271             osftag = "\\font_osf"
272             if osf == -1 and fontinfo.fonttype != "math":
273                 # Try with newer format
274                 osftag = "\\font_" + fontinfo.fonttype + "_osf"
275                 osf = find_token(document.header, osftag + " false")
276             if osf != -1:
277                 document.header[osf] = osftag + " true"
278         if i > 0 and document.preamble[i-1] == "% Added by lyx2lyx":
279             del document.preamble[i-1]
280             i -= 1
281         if fontscale != None:
282             j = find_token(document.header, fontscale, 0)
283             if j != -1:
284                 val = get_value(document.header, fontscale, j)
285                 vals = val.split()
286                 scale = "100"
287                 if oscale != None:
288                     scale = "%03d" % int(float(oscale) * 100)
289                 document.header[j] = fontscale + " " + scale + " " + vals[1]
290         ft = "\\font_" + fontinfo.fonttype
291         j = find_token(document.header, ft, 0)
292         if j != -1:
293             val = get_value(document.header, ft, j)
294             words = val.split() # ! splits also values like '"DejaVu Sans"'
295             words[0] = '"' + fn + '"'
296             document.header[j] = ft + ' ' + ' '.join(words)
297         if haveFontOpts and fontinfo.fonttype != "math":
298             fotag = "\\font_" + fontinfo.fonttype + "_opts"
299             fo = find_token(document.header, fotag)
300             if fo != -1:
301                 document.header[fo] = fotag + " \"" + ",".join(options) + "\""
302             else:
303                 # Sensible place to insert tag
304                 fo = find_token(document.header, "\\font_sf_scale")
305                 if fo == -1:
306                     document.warning("Malformed LyX document! Missing \\font_sf_scale")
307                 else:
308                     document.header.insert(fo, fotag + " \"" + ",".join(options) + "\"")
309
310
311 def revert_fonts(document, fm, fontmap, OnlyWithXOpts = False, WithXOpts = False):
312     """Revert native font definition to LaTeX"""
313     # fonlist := list of fonts created from the same package
314     # Empty package means that the font-name is the same as the package-name
315     # fontmap (key = package, val += found options) will be filled
316     # and used later in add_preamble_fonts() to be added to user-preamble
317
318     rfontscale = re.compile(r'^\s*(\\font_(roman|sans|typewriter|math))\s+')
319     rscales = re.compile(r'^\s*(\d+)\s+(\d+)')
320     i = 0
321     while i < len(document.header):
322         i = find_re(document.header, rfontscale, i+1)
323         if (i == -1):
324             return True
325         mo = rfontscale.search(document.header[i])
326         if mo == None:
327             continue
328         ft = mo.group(1)    # 'roman', 'sans', 'typewriter', 'math'
329         val = get_value(document.header, ft, i)
330         words = val.split(' ')     # ! splits also values like '"DejaVu Sans"'
331         font = words[0].strip('"') # TeX font name has no whitespace
332         if not font in fm.font2pkgmap:
333             continue
334         fontinfo = fm.font2pkgmap[font]
335         val = fontinfo.package
336         if not val in fontmap:
337             fontmap[val] = []
338         x = -1
339         if OnlyWithXOpts or WithXOpts:
340             if ft == "\\font_math":
341                 return False
342             regexp = re.compile(r'^\s*(\\font_roman_opts)\s+')
343             if ft == "\\font_sans":
344                 regexp = re.compile(r'^\s*(\\font_sans_opts)\s+')
345             elif ft == "\\font_typewriter":
346                 regexp = re.compile(r'^\s*(\\font_typewriter_opts)\s+')
347             x = find_re(document.header, regexp, 0)
348             if x == -1 and OnlyWithXOpts:
349                 return False
350
351             if x != -1:
352                 # We need to use this regex since split() does not handle quote protection
353                 xopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
354                 opts = xopts[1].strip('"').split(",")
355                 fontmap[val].extend(opts)
356                 del document.header[x]
357         words[0] = '"default"'
358         document.header[i] = ft + ' ' + ' '.join(words)
359         if fontinfo.scaleopt != None:
360             xval =  get_value(document.header, "\\font_" + fontinfo.scaletype + "_scale", 0)
361             mo = rscales.search(xval)
362             if mo != None:
363                 xval1 = mo.group(1)
364                 if xval1 != "100":
365                     # set correct scale option
366                     fontmap[val].extend([fontinfo.scaleopt + "=" + format(float(xval1) / 100, '.2f')])
367         if fontinfo.osfopt != None:
368             oldval = "true"
369             if fontinfo.osfdef == "true":
370                 oldval = "false"
371             osf = find_token(document.header, "\\font_osf " + oldval)
372             if osf == -1 and ft != "\\font_math":
373                 # Try with newer format
374                 osftag = "\\font_roman_osf " + oldval
375                 if ft == "\\font_sans":
376                     osftag = "\\font_sans_osf " + oldval
377                 elif ft == "\\font_typewriter":
378                     osftag = "\\font_typewriter_osf " + oldval
379                 osf = find_token(document.header, osftag)
380             if osf != -1:
381                 fontmap[val].extend([fontinfo.osfopt])
382         if len(fontinfo.options) > 0:
383             fontmap[val].extend(fontinfo.options)
384     return True
385
386 ###############################################################################
387 ###
388 ### Conversion and reversion routines
389 ###
390 ###############################################################################
391
392 def convert_inputencoding_namechange(document):
393     """Rename inputencoding settings."""
394     i = find_token(document.header, "\\inputencoding", 0)
395     if i == -1:
396         return
397     s = document.header[i].replace("auto", "auto-legacy")
398     document.header[i] = s.replace("default", "auto-legacy-plain")
399
400 def revert_inputencoding_namechange(document):
401     """Rename inputencoding settings."""
402     i = find_token(document.header, "\\inputencoding", 0)
403     if i == -1:
404         return
405     s = document.header[i].replace("auto-legacy-plain", "default")
406     document.header[i] = s.replace("auto-legacy", "auto")
407
408 def convert_notoFonts(document):
409     """Handle Noto fonts definition to LaTeX"""
410
411     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
412         fm = createFontMapping(['Noto'])
413         convert_fonts(document, fm)
414
415 def revert_notoFonts(document):
416     """Revert native Noto font definition to LaTeX"""
417
418     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
419         fontmap = dict()
420         fm = createFontMapping(['Noto'])
421         if revert_fonts(document, fm, fontmap):
422             add_preamble_fonts(document, fontmap)
423
424 def convert_latexFonts(document):
425     """Handle DejaVu and IBMPlex fonts definition to LaTeX"""
426
427     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
428         fm = createFontMapping(['DejaVu', 'IBM'])
429         convert_fonts(document, fm)
430
431 def revert_latexFonts(document):
432     """Revert native DejaVu font definition to LaTeX"""
433
434     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
435         fontmap = dict()
436         fm = createFontMapping(['DejaVu', 'IBM'])
437         if revert_fonts(document, fm, fontmap):
438             add_preamble_fonts(document, fontmap)
439
440 def convert_AdobeFonts(document):
441     """Handle Adobe Source fonts definition to LaTeX"""
442
443     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
444         fm = createFontMapping(['Adobe'])
445         convert_fonts(document, fm)
446
447 def revert_AdobeFonts(document):
448     """Revert Adobe Source font definition to LaTeX"""
449
450     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
451         fontmap = dict()
452         fm = createFontMapping(['Adobe'])
453         if revert_fonts(document, fm, fontmap):
454             add_preamble_fonts(document, fontmap)
455
456 def removeFrontMatterStyles(document):
457     """Remove styles Begin/EndFrontmatter"""
458
459     layouts = ['BeginFrontmatter', 'EndFrontmatter']
460     tokenend = len('\\begin_layout ')
461     i = 0
462     while True:
463         i = find_token_exact(document.body, '\\begin_layout ', i+1)
464         if i == -1:
465             return
466         layout = document.body[i][tokenend:].strip()
467         if layout not in layouts:
468             continue
469         j = find_end_of_layout(document.body, i)
470         if j == -1:
471             document.warning("Malformed LyX document: Can't find end of layout at line %d" % i)
472             continue
473         while document.body[j+1].strip() == '':
474             j += 1
475         document.body[i:j+1] = []
476
477 def addFrontMatterStyles(document):
478     """Use styles Begin/EndFrontmatter for elsarticle"""
479
480     if document.textclass != "elsarticle":
481         return
482
483     def insertFrontmatter(prefix, line):
484         above = line
485         while above > 0 and document.body[above-1].strip() == '':
486             above -= 1
487         below = line
488         while document.body[below].strip() == '':
489             below += 1
490         document.body[above:below] = ['', '\\begin_layout ' + prefix + 'Frontmatter',
491                                     '\\begin_inset Note Note',
492                                     'status open', '',
493                                     '\\begin_layout Plain Layout',
494                                     'Keep this empty!',
495                                     '\\end_layout', '',
496                                     '\\end_inset', '', '',
497                                     '\\end_layout', '']
498
499     layouts = ['Title', 'Title footnote', 'Author', 'Author footnote',
500                 'Corresponding author', 'Address', 'Email', 'Abstract', 'Keywords']
501     tokenend = len('\\begin_layout ')
502     first = -1
503     i = 0
504     while True:
505         i = find_token_exact(document.body, '\\begin_layout ', i+1)
506         if i == -1:
507             break
508         layout = document.body[i][tokenend:].strip()
509         if layout not in layouts:
510             continue
511         k = find_end_of_layout(document.body, i)
512         if k == -1:
513             document.warning("Malformed LyX document: Can't find end of layout at line %d" % i)
514             continue
515         if first == -1:
516             first = i
517         i = k
518     if first == -1:
519         return
520     insertFrontmatter('End', k+1)
521     insertFrontmatter('Begin', first)
522
523
524 def convert_lst_literalparam(document):
525     """Add param literal to include inset"""
526
527     i = 0
528     while True:
529         i = find_token(document.body, '\\begin_inset CommandInset include', i+1)
530         if i == -1:
531             break
532         j = find_end_of_inset(document.body, i)
533         if j == -1:
534             document.warning("Malformed LyX document: Can't find end of command inset at line %d" % i)
535             continue
536         while i < j and document.body[i].strip() != '':
537             i += 1
538         document.body.insert(i, 'literal "true"')
539
540
541 def revert_lst_literalparam(document):
542     """Remove param literal from include inset"""
543
544     i = 0
545     while True:
546         i = find_token(document.body, '\\begin_inset CommandInset include', i+1)
547         if i == -1:
548             break
549         j = find_end_of_inset(document.body, i)
550         if j == -1:
551             document.warning("Malformed LyX document: Can't find end of include inset at line %d" % i)
552             continue
553         del_token(document.body, 'literal', i, j)
554
555
556 def revert_paratype(document):
557     """Revert ParaType font definitions to LaTeX"""
558
559     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
560         i1 = find_token(document.header, "\\font_roman \"PTSerif-TLF\"", 0)
561         i2 = find_token(document.header, "\\font_sans \"default\"", 0)
562         i3 = find_token(document.header, "\\font_typewriter \"default\"", 0)
563         j = find_token(document.header, "\\font_sans \"PTSans-TLF\"", 0)
564
565         sf_scale = 100.0
566         sfval = find_token(document.header, "\\font_sf_scale", 0)
567         if sfval == -1:
568             document.warning("Malformed LyX document: Missing \\font_sf_scale.")
569         else:
570             sfscale = document.header[sfval].split()
571             val = sfscale[1]
572             sfscale[1] = "100"
573             document.header[sfval] = " ".join(sfscale)
574             try:
575                 # float() can throw
576                 sf_scale = float(val)
577             except:
578                 document.warning("Invalid font_sf_scale value: " + val)
579
580         sfoption = ""
581         if sf_scale != "100.0":
582             sfoption = "scaled=" + str(sf_scale / 100.0)
583         k = find_token(document.header, "\\font_typewriter \"PTMono-TLF\"", 0)
584         ttval = get_value(document.header, "\\font_tt_scale", 0)
585         # cutoff " 100"
586         ttval = ttval[:-4]
587         ttoption = ""
588         if ttval != "100":
589             ttoption = "scaled=" + format(float(ttval) / 100, '.2f')
590         if i1 != -1 and i2 != -1 and i3!= -1:
591             add_to_preamble(document, ["\\usepackage{paratype}"])
592         else:
593             if i1!= -1:
594                 add_to_preamble(document, ["\\usepackage{PTSerif}"])
595                 document.header[i1] = document.header[i1].replace("PTSerif-TLF", "default")
596             if j!= -1:
597                 if sfoption != "":
598                     add_to_preamble(document, ["\\usepackage[" + sfoption + "]{PTSans}"])
599                 else:
600                     add_to_preamble(document, ["\\usepackage{PTSans}"])
601                 document.header[j] = document.header[j].replace("PTSans-TLF", "default")
602             if k!= -1:
603                 if ttoption != "":
604                     add_to_preamble(document, ["\\usepackage[" + ttoption + "]{PTMono}"])
605                 else:
606                     add_to_preamble(document, ["\\usepackage{PTMono}"])
607                 document.header[k] = document.header[k].replace("PTMono-TLF", "default")
608
609
610 def revert_xcharter(document):
611     """Revert XCharter font definitions to LaTeX"""
612
613     i = find_token(document.header, "\\font_roman \"xcharter\"", 0)
614     if i == -1:
615         return
616
617     # replace unsupported font setting
618     document.header[i] = document.header[i].replace("xcharter", "default")
619     # no need for preamble code with system fonts
620     if get_bool_value(document.header, "\\use_non_tex_fonts"):
621         return
622
623     # transfer old style figures setting to package options
624     j = find_token(document.header, "\\font_osf true")
625     if j != -1:
626         options = "[osf]"
627         document.header[j] = "\\font_osf false"
628     else:
629         options = ""
630     if i != -1:
631         add_to_preamble(document, ["\\usepackage%s{XCharter}"%options])
632
633
634 def revert_lscape(document):
635     """Reverts the landscape environment (Landscape module) to TeX-code"""
636
637     if not "landscape" in document.get_module_list():
638         return
639
640     i = 0
641     while True:
642         i = find_token(document.body, "\\begin_inset Flex Landscape", i+1)
643         if i == -1:
644             break
645         j = find_end_of_inset(document.body, i)
646         if j == -1:
647             document.warning("Malformed LyX document: Can't find end of Landscape inset")
648             continue
649
650         if document.body[i] == "\\begin_inset Flex Landscape (Floating)":
651             document.body[j - 2 : j + 1] = put_cmd_in_ert("\\end{landscape}}")
652             document.body[i : i + 4] = put_cmd_in_ert("\\afterpage{\\begin{landscape}")
653             add_to_preamble(document, ["\\usepackage{afterpage}"])
654         else:
655             document.body[j - 2 : j + 1] = put_cmd_in_ert("\\end{landscape}")
656             document.body[i : i + 4] = put_cmd_in_ert("\\begin{landscape}")
657
658         add_to_preamble(document, ["\\usepackage{pdflscape}"])
659     document.del_module("landscape")
660
661
662 def convert_fontenc(document):
663     """Convert default fontenc setting"""
664
665     i = find_token(document.header, "\\fontencoding global", 0)
666     if i == -1:
667         return
668
669     document.header[i] = document.header[i].replace("global", "auto")
670
671
672 def revert_fontenc(document):
673     """Revert default fontenc setting"""
674
675     i = find_token(document.header, "\\fontencoding auto", 0)
676     if i == -1:
677         return
678
679     document.header[i] = document.header[i].replace("auto", "global")
680
681
682 def revert_nospellcheck(document):
683     """Remove nospellcheck font info param"""
684
685     i = 0
686     while True:
687         i = find_token(document.body, '\\nospellcheck', i)
688         if i == -1:
689             return
690         del document.body[i]
691
692
693 def revert_floatpclass(document):
694     """Remove float placement params 'document' and 'class'"""
695
696     del_token(document.header, "\\float_placement class")
697
698     i = 0
699     while True:
700         i = find_token(document.body, '\\begin_inset Float', i + 1)
701         if i == -1:
702             break
703         j = find_end_of_inset(document.body, i)
704         k = find_token(document.body, 'placement class', i, j)
705         if k == -1:
706             k = find_token(document.body, 'placement document', i, j)
707             if k != -1:
708                 del document.body[k]
709             continue
710         del document.body[k]
711
712
713 def revert_floatalignment(document):
714     """Remove float alignment params"""
715
716     galignment = get_value(document.header, "\\float_alignment", delete=True)
717
718     i = 0
719     while True:
720         i = find_token(document.body, '\\begin_inset Float', i + 1)
721         if i == -1:
722             break
723         j = find_end_of_inset(document.body, i)
724         if j == -1:
725             document.warning("Malformed LyX document: Can't find end of inset at line " + str(i))
726             continue
727         k = find_token(document.body, 'alignment', i, j)
728         if k == -1:
729             i = j
730             continue
731         alignment = get_value(document.body, "alignment", k)
732         if alignment == "document":
733             alignment = galignment
734         del document.body[k]
735         l = find_token(document.body, "\\begin_layout Plain Layout", i, j)
736         if l == -1:
737             document.warning("Can't find float layout!")
738             continue
739         alcmd = []
740         if alignment == "left":
741             alcmd = put_cmd_in_ert("\\raggedright{}")
742         elif alignment == "center":
743             alcmd = put_cmd_in_ert("\\centering{}")
744         elif alignment == "right":
745             alcmd = put_cmd_in_ert("\\raggedleft{}")
746         if len(alcmd) > 0:
747             document.body[l+1:l+1] = alcmd
748         # There might be subfloats, so we do not want to move past
749         # the end of the inset.
750         i += 1
751
752 def revert_tuftecite(document):
753     r"""Revert \cite commands in tufte classes"""
754
755     tufte = ["tufte-book", "tufte-handout"]
756     if document.textclass not in tufte:
757         return
758
759     i = 0
760     while (True):
761         i = find_token(document.body, "\\begin_inset CommandInset citation", i+1)
762         if i == -1:
763             break
764         j = find_end_of_inset(document.body, i)
765         if j == -1:
766             document.warning("Can't find end of citation inset at line %d!!" %(i))
767             continue
768         k = find_token(document.body, "LatexCommand", i, j)
769         if k == -1:
770             document.warning("Can't find LatexCommand for citation inset at line %d!" %(i))
771             i = j
772             continue
773         cmd = get_value(document.body, "LatexCommand", k)
774         if cmd != "cite":
775             i = j
776             continue
777         pre = get_quoted_value(document.body, "before", i, j)
778         post = get_quoted_value(document.body, "after", i, j)
779         key = get_quoted_value(document.body, "key", i, j)
780         if not key:
781             document.warning("Citation inset at line %d does not have a key!" %(i))
782             key = "???"
783         # Replace command with ERT
784         res = "\\cite"
785         if pre:
786             res += "[" + pre + "]"
787         if post:
788             res += "[" + post + "]"
789         elif pre:
790             res += "[]"
791         res += "{" + key + "}"
792         document.body[i:j+1] = put_cmd_in_ert([res])
793         i = j
794
795
796
797 def revert_stretchcolumn(document):
798     """We remove the column varwidth flags or everything else will become a mess."""
799     i = 0
800     while True:
801         i = find_token(document.body, "\\begin_inset Tabular", i+1)
802         if i == -1:
803             return
804         j = find_end_of_inset(document.body, i+1)
805         if j == -1:
806             document.warning("Malformed LyX document: Could not find end of tabular.")
807             continue
808         for k in range(i, j):
809             if re.search('^<column.*varwidth="[^"]+".*>$', document.body[k]):
810                 document.warning("Converting 'tabularx'/'xltabular' table to normal table.")
811                 document.body[k] = document.body[k].replace(' varwidth="true"', '')
812
813
814 def revert_vcolumns(document):
815     """Revert standard columns with line breaks etc."""
816     i = 0
817     needvarwidth = False
818     needarray = False
819     try:
820         while True:
821             i = find_token(document.body, "\\begin_inset Tabular", i+1)
822             if i == -1:
823                 return
824             j = find_end_of_inset(document.body, i)
825             if j == -1:
826                 document.warning("Malformed LyX document: Could not find end of tabular.")
827                 continue
828
829             # Collect necessary column information
830             m = i + 1
831             nrows = int(document.body[i+1].split('"')[3])
832             ncols = int(document.body[i+1].split('"')[5])
833             col_info = []
834             for k in range(ncols):
835                 m = find_token(document.body, "<column", m)
836                 width = get_option_value(document.body[m], 'width')
837                 varwidth = get_option_value(document.body[m], 'varwidth')
838                 alignment = get_option_value(document.body[m], 'alignment')
839                 special = get_option_value(document.body[m], 'special')
840                 col_info.append([width, varwidth, alignment, special, m])
841
842             # Now parse cells
843             m = i + 1
844             lines = []
845             for row in range(nrows):
846                 for col in range(ncols):
847                     m = find_token(document.body, "<cell", m)
848                     multicolumn = get_option_value(document.body[m], 'multicolumn')
849                     multirow = get_option_value(document.body[m], 'multirow')
850                     width = get_option_value(document.body[m], 'width')
851                     rotate = get_option_value(document.body[m], 'rotate')
852                     # Check for: linebreaks, multipars, non-standard environments
853                     begcell = m
854                     endcell = find_token(document.body, "</cell>", begcell)
855                     vcand = False
856                     if find_token(document.body, "\\begin_inset Newline", begcell, endcell) != -1:
857                         vcand = True
858                     elif count_pars_in_inset(document.body, begcell + 2) > 1:
859                         vcand = True
860                     elif get_value(document.body, "\\begin_layout", begcell) != "Plain Layout":
861                         vcand = True
862                     if vcand and rotate == "" and ((multicolumn == "" and multirow == "") or width == ""):
863                         if col_info[col][0] == "" and col_info[col][1] == "" and col_info[col][3] == "":
864                             needvarwidth = True
865                             alignment = col_info[col][2]
866                             col_line = col_info[col][4]
867                             vval = ""
868                             if alignment == "center":
869                                 vval = ">{\\centering}"
870                             elif  alignment == "left":
871                                 vval = ">{\\raggedright}"
872                             elif alignment == "right":
873                                 vval = ">{\\raggedleft}"
874                             if vval != "":
875                                 needarray = True
876                             vval += "V{\\linewidth}"
877
878                             document.body[col_line] = document.body[col_line][:-1] + " special=\"" + vval + "\">"
879                             # ERT newlines and linebreaks (since LyX < 2.4 automatically inserts parboxes
880                             # with newlines, and we do not want that)
881                             while True:
882                                 endcell = find_token(document.body, "</cell>", begcell)
883                                 linebreak = False
884                                 nl = find_token(document.body, "\\begin_inset Newline newline", begcell, endcell)
885                                 if nl == -1:
886                                     nl = find_token(document.body, "\\begin_inset Newline linebreak", begcell, endcell)
887                                     if nl == -1:
888                                          break
889                                     linebreak = True
890                                 nle = find_end_of_inset(document.body, nl)
891                                 del(document.body[nle:nle+1])
892                                 if linebreak:
893                                     document.body[nl:nl+1] = put_cmd_in_ert("\\linebreak{}")
894                                 else:
895                                     document.body[nl:nl+1] = put_cmd_in_ert("\\\\")
896                     m += 1
897
898             i = j
899
900     finally:
901         if needarray == True:
902             add_to_preamble(document, ["\\usepackage{array}"])
903         if needvarwidth == True:
904             add_to_preamble(document, ["\\usepackage{varwidth}"])
905
906
907 def revert_bibencoding(document):
908     """Revert bibliography encoding"""
909
910     # Get cite engine
911     engine = "basic"
912     i = find_token(document.header, "\\cite_engine", 0)
913     if i == -1:
914         document.warning("Malformed document! Missing \\cite_engine")
915     else:
916         engine = get_value(document.header, "\\cite_engine", i)
917
918     # Check if biblatex
919     biblatex = False
920     if engine in ["biblatex", "biblatex-natbib"]:
921         biblatex = True
922
923     # Map lyx to latex encoding names
924     encodings = {
925         "utf8" : "utf8",
926         "utf8x" : "utf8x",
927         "armscii8" : "armscii8",
928         "iso8859-1" : "latin1",
929         "iso8859-2" : "latin2",
930         "iso8859-3" : "latin3",
931         "iso8859-4" : "latin4",
932         "iso8859-5" : "iso88595",
933         "iso8859-6" : "8859-6",
934         "iso8859-7" : "iso-8859-7",
935         "iso8859-8" : "8859-8",
936         "iso8859-9" : "latin5",
937         "iso8859-13" : "latin7",
938         "iso8859-15" : "latin9",
939         "iso8859-16" : "latin10",
940         "applemac" : "applemac",
941         "cp437" : "cp437",
942         "cp437de" : "cp437de",
943         "cp850" : "cp850",
944         "cp852" : "cp852",
945         "cp855" : "cp855",
946         "cp858" : "cp858",
947         "cp862" : "cp862",
948         "cp865" : "cp865",
949         "cp866" : "cp866",
950         "cp1250" : "cp1250",
951         "cp1251" : "cp1251",
952         "cp1252" : "cp1252",
953         "cp1255" : "cp1255",
954         "cp1256" : "cp1256",
955         "cp1257" : "cp1257",
956         "koi8-r" : "koi8-r",
957         "koi8-u" : "koi8-u",
958         "pt154" : "pt154",
959         "utf8-platex" : "utf8",
960         "ascii" : "ascii"
961     }
962
963     i = 0
964     while (True):
965         i = find_token(document.body, "\\begin_inset CommandInset bibtex", i+1)
966         if i == -1:
967             break
968         j = find_end_of_inset(document.body, i)
969         if j == -1:
970             document.warning("Can't find end of bibtex inset at line %d!!" %(i))
971             continue
972         encoding = get_quoted_value(document.body, "encoding", i, j)
973         if not encoding:
974             continue
975         # remove encoding line
976         k = find_token(document.body, "encoding", i, j)
977         if k != -1:
978             del document.body[k]
979         if encoding == "default":
980             continue
981         # Re-find inset end line
982         j = find_end_of_inset(document.body, i)
983         if biblatex:
984             biblio_options = ""
985             h = find_token(document.header, "\\biblio_options", 0)
986             if h != -1:
987                 biblio_options = get_value(document.header, "\\biblio_options", h)
988                 if not "bibencoding" in biblio_options:
989                      document.header[h] += ",bibencoding=%s" % encodings[encoding]
990             else:
991                 bs = find_token(document.header, "\\biblatex_bibstyle", 0)
992                 if bs == -1:
993                     # this should not happen
994                     document.warning("Malformed LyX document! No \\biblatex_bibstyle header found!")
995                 else:
996                     document.header[bs-1 : bs-1] = ["\\biblio_options bibencoding=" + encodings[encoding]]
997         else:
998             document.body[j+1:j+1] = put_cmd_in_ert("\\egroup")
999             document.body[i:i] = put_cmd_in_ert("\\bgroup\\inputencoding{" + encodings[encoding] + "}")
1000
1001         i = j
1002
1003
1004
1005 def convert_vcsinfo(document):
1006     """Separate vcs Info inset from buffer Info inset."""
1007
1008     types = {
1009         "vcs-revision" : "revision",
1010         "vcs-tree-revision" : "tree-revision",
1011         "vcs-author" : "author",
1012         "vcs-time" : "time",
1013         "vcs-date" : "date"
1014     }
1015     i = 0
1016     while True:
1017         i = find_token(document.body, "\\begin_inset Info", i+1)
1018         if i == -1:
1019             return
1020         j = find_end_of_inset(document.body, i+1)
1021         if j == -1:
1022             document.warning("Malformed LyX document: Could not find end of Info inset.")
1023             continue
1024         tp = find_token(document.body, 'type', i, j)
1025         tpv = get_quoted_value(document.body, "type", tp)
1026         if tpv != "buffer":
1027             continue
1028         arg = find_token(document.body, 'arg', i, j)
1029         argv = get_quoted_value(document.body, "arg", arg)
1030         if argv not in list(types.keys()):
1031             continue
1032         document.body[tp] = "type \"vcs\""
1033         document.body[arg] = "arg \"" + types[argv] + "\""
1034
1035
1036 def revert_vcsinfo(document):
1037     """Merge vcs Info inset to buffer Info inset."""
1038
1039     args = ["revision", "tree-revision", "author", "time", "date" ]
1040     i = 0
1041     while True:
1042         i = find_token(document.body, "\\begin_inset Info", i+1)
1043         if i == -1:
1044             return
1045         j = find_end_of_inset(document.body, i+1)
1046         if j == -1:
1047             document.warning("Malformed LyX document: Could not find end of Info inset.")
1048             continue
1049         tp = find_token(document.body, 'type', i, j)
1050         tpv = get_quoted_value(document.body, "type", tp)
1051         if tpv != "vcs":
1052             continue
1053         arg = find_token(document.body, 'arg', i, j)
1054         argv = get_quoted_value(document.body, "arg", arg)
1055         if argv not in args:
1056             document.warning("Malformed Info inset. Invalid vcs arg.")
1057             continue
1058         document.body[tp] = "type \"buffer\""
1059         document.body[arg] = "arg \"vcs-" + argv + "\""
1060
1061 def revert_vcsinfo_rev_abbrev(document):
1062     " Convert abbreviated revisions to regular revisions. "
1063
1064     i = 0
1065     while True:
1066         i = find_token(document.body, "\\begin_inset Info", i+1)
1067         if i == -1:
1068             return
1069         j = find_end_of_inset(document.body, i+1)
1070         if j == -1:
1071             document.warning("Malformed LyX document: Could not find end of Info inset.")
1072             continue
1073         tp = find_token(document.body, 'type', i, j)
1074         tpv = get_quoted_value(document.body, "type", tp)
1075         if tpv != "vcs":
1076             continue
1077         arg = find_token(document.body, 'arg', i, j)
1078         argv = get_quoted_value(document.body, "arg", arg)
1079         if( argv == "revision-abbrev" ):
1080             document.body[arg] = "arg \"revision\""
1081
1082 def revert_dateinfo(document):
1083     """Revert date info insets to static text."""
1084
1085 # FIXME This currently only considers the main language and uses the system locale
1086 # Ideally, it should honor context languages and switch the locale accordingly.
1087
1088     # The date formats for each language using strftime syntax:
1089     # long, short, loclong, locmedium, locshort
1090     dateformats = {
1091         "afrikaans" : ["%A, %d %B %Y", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y/%m/%d"],
1092         "albanian" : ["%A, %d %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1093         "american" : ["%A, %B %d, %Y", "%m/%d/%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1094         "amharic" : ["%A ፣%d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1095         "ancientgreek" : ["%A, %d %B %Y", "%d %b %Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1096         "arabic_arabi" : ["%A، %d %B، %Y", "%d‏/%m‏/%Y", "%d %B، %Y", "%d/%m/%Y", "%d/%m/%Y"],
1097         "arabic_arabtex" : ["%A، %d %B، %Y", "%d‏/%m‏/%Y", "%d %B، %Y", "%d/%m/%Y", "%d/%m/%Y"],
1098         "armenian" : ["%Y թ. %B %d, %A", "%d.%m.%y", "%d %B، %Y", "%d %b، %Y", "%d/%m/%Y"],
1099         "asturian" : ["%A, %d %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d %b %Y", "%d/%m/%Y"],
1100         "australian" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1101         "austrian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1102         "bahasa" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1103         "bahasam" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1104         "basque" : ["%Y(e)ko %B %d, %A", "%y/%m/%d", "%Y %B %d", "%Y %b %d", "%Y/%m/%d"],
1105         "belarusian" : ["%A, %d %B %Y г.", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1106         "bosnian" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%Y-%m-%d"],
1107         "brazilian" : ["%A, %d de %B de %Y", "%d/%m/%Y", "%d de %B de %Y", "%d de %b de %Y", "%d/%m/%Y"],
1108         "breton" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1109         "british" : ["%A, %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1110         "bulgarian" : ["%A, %d %B %Y г.", "%d.%m.%y г.", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1111         "canadian" : ["%A, %B %d, %Y", "%Y-%m-%d", "%B %d, %Y", "%d %b %Y", "%Y-%m-%d"],
1112         "canadien" : ["%A %d %B %Y", "%y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1113         "catalan" : ["%A, %d %B de %Y", "%d/%m/%y", "%d / %B / %Y", "%d / %b / %Y", "%d/%m/%Y"],
1114         "chinese-simplified" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y-%m-%d", "%y-%m-%d"],
1115         "chinese-traditional" : ["%Y年%m月%d日 %A", "%Y/%m/%d", "%Y年%m月%d日", "%Y年%m月%d日", "%y年%m月%d日"],
1116         "coptic" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1117         "croatian" : ["%A, %d. %B %Y.", "%d. %m. %Y.", "%d. %B %Y.", "%d. %b. %Y.", "%d.%m.%Y."],
1118         "czech" : ["%A %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b. %Y", "%d.%m.%Y"],
1119         "danish" : ["%A den %d. %B %Y", "%d/%m/%Y", "%d. %B %Y", "%d. %b %Y", "%d/%m/%Y"],
1120         "divehi" : ["%Y %B %d, %A", "%Y-%m-%d", "%Y %B %d", "%Y %b %d", "%d/%m/%Y"],
1121         "dutch" : ["%A %d %B %Y", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1122         "english" : ["%A, %B %d, %Y", "%m/%d/%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1123         "esperanto" : ["%A, %d %B %Y", "%d %b %Y", "la %d de %B %Y", "la %d de %b %Y", "%m/%d/%Y"],
1124         "estonian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1125         "farsi" : ["%A %d %B %Y", "%Y/%m/%d", "%d %B %Y", "%d %b %Y", "%Y/%m/%d"],
1126         "finnish" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1127         "french" : ["%A %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1128         "friulan" : ["%A %d di %B dal %Y", "%d/%m/%y", "%d di %B dal %Y", "%d di %b dal %Y", "%d/%m/%Y"],
1129         "galician" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d de %b de %Y", "%d/%m/%Y"],
1130         "georgian" : ["%A, %d %B, %Y", "%d.%m.%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1131         "german" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1132         "german-ch" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1133         "german-ch-old" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1134         "greek" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1135         "hebrew" : ["%A, %d ב%B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1136         "hindi" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1137         "icelandic" : ["%A, %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1138         "interlingua" : ["%Y %B %d, %A", "%Y-%m-%d", "le %d de %B %Y", "le %d de %b %Y", "%Y-%m-%d"],
1139         "irish" : ["%A %d %B %Y", "%d/%m/%Y", "%d. %B %Y", "%d. %b %Y", "%d/%m/%Y"],
1140         "italian" : ["%A %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d/%b/%Y", "%d/%m/%Y"],
1141         "japanese" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y/%m/%d", "%y/%m/%d"],
1142         "japanese-cjk" : ["%Y年%m月%d日%A", "%Y/%m/%d", "%Y年%m月%d日", "%Y/%m/%d", "%y/%m/%d"],
1143         "kannada" : ["%A, %B %d, %Y", "%d/%m/%y", "%d %B %Y", "%d %B %Y", "%d-%m-%Y"],
1144         "kazakh" : ["%Y ж. %d %B, %A", "%d.%m.%y", "%d %B %Y", "%d %B %Y", "%Y-%d-%m"],
1145         "khmer" : ["%A %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %B %Y", "%d/%m/%Y"],
1146         "korean" : ["%Y년 %m월 %d일 %A", "%y. %m. %d.", "%Y년 %m월 %d일", "%Y. %m. %d.", "%y. %m. %d."],
1147         "kurmanji" : ["%A, %d %B %Y", "%d %b %Y", "%d. %B %Y", "%d. %m. %Y", "%Y-%m-%d"],
1148         "lao" : ["%A ທີ %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %B %Y", "%d/%m/%Y"],
1149         "latin" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1150         "latvian" : ["%A, %Y. gada %d. %B", "%d.%m.%y", "%Y. gada %d. %B", "%Y. gada %d. %b", "%d.%m.%Y"],
1151         "lithuanian" : ["%Y m. %B %d d., %A", "%Y-%m-%d", "%Y m. %B %d d.", "%Y m. %B %d d.", "%Y-%m-%d"],
1152         "lowersorbian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1153         "macedonian" : ["%A, %d %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1154         "magyar" : ["%Y. %B %d., %A", "%Y. %m. %d.", "%Y. %B %d.", "%Y. %b %d.", "%Y.%m.%d."],
1155         "malayalam" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1156         "marathi" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1157         "mongolian" : ["%A, %Y оны %m сарын %d", "%Y-%m-%d", "%Y оны %m сарын %d", "%d-%m-%Y", "%d-%m-%Y"],
1158         "naustrian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1159         "newzealand" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1160         "ngerman" : ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1161         "norsk" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1162         "nynorsk" : ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1163         "occitan" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1164         "piedmontese" : ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1165         "polish" : ["%A, %d %B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1166         "polutonikogreek" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1167         "portuguese" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B de %Y", "%d de %b de %Y", "%Y/%m/%d"],
1168         "romanian" : ["%A, %d %B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1169         "romansh" : ["%A, ils %d da %B %Y", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1170         "russian" : ["%A, %d %B %Y г.", "%d.%m.%Y", "%d %B %Y г.", "%d %b %Y г.", "%d.%m.%Y"],
1171         "samin" : ["%Y %B %d, %A", "%Y-%m-%d", "%B %d. b. %Y", "%b %d. b. %Y", "%d.%m.%Y"],
1172         "sanskrit" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1173         "scottish" : ["%A, %dmh %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1174         "serbian" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1175         "serbian-latin" : ["%A, %d. %B %Y.", "%d.%m.%y.", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1176         "slovak" : ["%A, %d. %B %Y", "%d. %m. %Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1177         "slovene" : ["%A, %d. %B %Y", "%d. %m. %y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1178         "spanish" : ["%A, %d de %B de %Y", "%d/%m/%y", "%d de %B %de %Y", "%d %b %Y", "%d/%m/%Y"],
1179         "spanish-mexico" : ["%A, %d de %B %de %Y", "%d/%m/%y", "%d de %B de %Y", "%d %b %Y", "%d/%m/%Y"],
1180         "swedish" : ["%A %d %B %Y", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1181         "syriac" : ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1182         "tamil" : ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1183         "telugu" : ["%d, %B %Y, %A", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1184         "thai" : ["%Aที่ %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1185         "tibetan" : ["%Y %Bའི་ཚེས་%d, %A", "%Y-%m-%d", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1186         "turkish" : ["%d %B %Y %A", "%d.%m.%Y", "%d %B %Y", "%d.%b.%Y", "%d.%m.%Y"],
1187         "turkmen" : ["%d %B %Y %A", "%d.%m.%Y", "%Y ý. %B %d", "%d.%m.%Y ý.", "%d.%m.%y ý."],
1188         "ukrainian" : ["%A, %d %B %Y р.", "%d.%m.%y", "%d %B %Y", "%d %m %Y", "%d.%m.%Y"],
1189         "uppersorbian" : ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1190         "urdu" : ["%A، %d %B، %Y", "%d/%m/%y", "%d %B, %Y", "%d %b %Y", "%d/%m/%Y"],
1191         "vietnamese" : ["%A, %d %B, %Y", "%d/%m/%Y", "%d tháng %B %Y", "%d-%m-%Y", "%d/%m/%Y"],
1192         "welsh" : ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1193     }
1194
1195     types = ["date", "fixdate", "moddate" ]
1196     lang = get_value(document.header, "\\language")
1197     if lang == "":
1198         document.warning("Malformed LyX document! No \\language header found!")
1199         return
1200
1201     i = 0
1202     while True:
1203         i = find_token(document.body, "\\begin_inset Info", i+1)
1204         if i == -1:
1205             return
1206         j = find_end_of_inset(document.body, i+1)
1207         if j == -1:
1208             document.warning("Malformed LyX document: Could not find end of Info inset.")
1209             continue
1210         tp = find_token(document.body, 'type', i, j)
1211         tpv = get_quoted_value(document.body, "type", tp)
1212         if tpv not in types:
1213             continue
1214         arg = find_token(document.body, 'arg', i, j)
1215         argv = get_quoted_value(document.body, "arg", arg)
1216         isodate = ""
1217         dte = date.today()
1218         if tpv == "fixdate":
1219             datecomps = argv.split('@')
1220             if len(datecomps) > 1:
1221                 argv = datecomps[0]
1222                 isodate = datecomps[1]
1223                 m = re.search(r'(\d\d\d\d)-(\d\d)-(\d\d)', isodate)
1224                 if m:
1225                     dte = date(int(m.group(1)), int(m.group(2)), int(m.group(3)))
1226 # FIXME if we had the path to the original document (not the one in the tmp dir),
1227 #        we could use the mtime.
1228 #        elif tpv == "moddate":
1229 #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
1230         result = ""
1231         if argv == "ISO":
1232             result = dte.isodate()
1233         elif argv == "long":
1234             result = dte.strftime(dateformats[lang][0])
1235         elif argv == "short":
1236             result = dte.strftime(dateformats[lang][1])
1237         elif argv == "loclong":
1238             result = dte.strftime(dateformats[lang][2])
1239         elif argv == "locmedium":
1240             result = dte.strftime(dateformats[lang][3])
1241         elif argv == "locshort":
1242             result = dte.strftime(dateformats[lang][4])
1243         else:
1244             fmt = argv.replace("MMMM", "%b").replace("MMM", "%b").replace("MM", "%m").replace("M", "%m")
1245             fmt = fmt.replace("yyyy", "%Y").replace("yy", "%y")
1246             fmt = fmt.replace("dddd", "%A").replace("ddd", "%a").replace("dd", "%d")
1247             fmt = re.sub('[^\'%]d', '%d', fmt)
1248             fmt = fmt.replace("'", "")
1249             result = dte.strftime(fmt)
1250         if sys.version_info < (3,0):
1251             # In Python 2, datetime module works with binary strings,
1252             # our dateformat strings are utf8-encoded:
1253             result = result.decode('utf-8')
1254         document.body[i : j+1] = [result]
1255
1256
1257 def revert_timeinfo(document):
1258     """Revert time info insets to static text."""
1259
1260 # FIXME This currently only considers the main language and uses the system locale
1261 # Ideally, it should honor context languages and switch the locale accordingly.
1262 # Also, the time object is "naive", i.e., it does not know of timezones (%Z will
1263 # be empty).
1264
1265     # The time formats for each language using strftime syntax:
1266     # long, short
1267     timeformats = {
1268         "afrikaans" : ["%H:%M:%S %Z", "%H:%M"],
1269         "albanian" : ["%I:%M:%S %p, %Z", "%I:%M %p"],
1270         "american" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1271         "amharic" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1272         "ancientgreek" : ["%H:%M:%S %Z", "%H:%M:%S"],
1273         "arabic_arabi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1274         "arabic_arabtex" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1275         "armenian" : ["%H:%M:%S %Z", "%H:%M"],
1276         "asturian" : ["%H:%M:%S %Z", "%H:%M"],
1277         "australian" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1278         "austrian" : ["%H:%M:%S %Z", "%H:%M"],
1279         "bahasa" : ["%H.%M.%S %Z", "%H.%M"],
1280         "bahasam" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1281         "basque" : ["%H:%M:%S (%Z)", "%H:%M"],
1282         "belarusian" : ["%H:%M:%S, %Z", "%H:%M"],
1283         "bosnian" : ["%H:%M:%S %Z", "%H:%M"],
1284         "brazilian" : ["%H:%M:%S %Z", "%H:%M"],
1285         "breton" : ["%H:%M:%S %Z", "%H:%M"],
1286         "british" : ["%H:%M:%S %Z", "%H:%M"],
1287         "bulgarian" : ["%H:%M:%S %Z", "%H:%M"],
1288         "canadian" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1289         "canadien" : ["%H:%M:%S %Z", "%H h %M"],
1290         "catalan" : ["%H:%M:%S %Z", "%H:%M"],
1291         "chinese-simplified" : ["%Z %p%I:%M:%S", "%p%I:%M"],
1292         "chinese-traditional" : ["%p%I:%M:%S [%Z]", "%p%I:%M"],
1293         "coptic" : ["%H:%M:%S %Z", "%H:%M:%S"],
1294         "croatian" : ["%H:%M:%S (%Z)", "%H:%M"],
1295         "czech" : ["%H:%M:%S %Z", "%H:%M"],
1296         "danish" : ["%H.%M.%S %Z", "%H.%M"],
1297         "divehi" : ["%H:%M:%S %Z", "%H:%M"],
1298         "dutch" : ["%H:%M:%S %Z", "%H:%M"],
1299         "english" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1300         "esperanto" : ["%H:%M:%S %Z", "%H:%M:%S"],
1301         "estonian" : ["%H:%M:%S %Z", "%H:%M"],
1302         "farsi" : ["%H:%M:%S (%Z)", "%H:%M"],
1303         "finnish" : ["%H.%M.%S %Z", "%H.%M"],
1304         "french" : ["%H:%M:%S %Z", "%H:%M"],
1305         "friulan" : ["%H:%M:%S %Z", "%H:%M"],
1306         "galician" : ["%H:%M:%S %Z", "%H:%M"],
1307         "georgian" : ["%H:%M:%S %Z", "%H:%M"],
1308         "german" : ["%H:%M:%S %Z", "%H:%M"],
1309         "german-ch" : ["%H:%M:%S %Z", "%H:%M"],
1310         "german-ch-old" : ["%H:%M:%S %Z", "%H:%M"],
1311         "greek" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1312         "hebrew" : ["%H:%M:%S %Z", "%H:%M"],
1313         "hindi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1314         "icelandic" : ["%H:%M:%S %Z", "%H:%M"],
1315         "interlingua" : ["%H:%M:%S %Z", "%H:%M"],
1316         "irish" : ["%H:%M:%S %Z", "%H:%M"],
1317         "italian" : ["%H:%M:%S %Z", "%H:%M"],
1318         "japanese" : ["%H時%M分%S秒 %Z", "%H:%M"],
1319         "japanese-cjk" : ["%H時%M分%S秒 %Z", "%H:%M"],
1320         "kannada" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1321         "kazakh" : ["%H:%M:%S %Z", "%H:%M"],
1322         "khmer" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1323         "korean" : ["%p %I시%M분 %S초 %Z", "%p %I:%M"],
1324         "kurmanji" : ["%H:%M:%S %Z", "%H:%M:%S"],
1325         "lao" : ["%H ໂມງ%M ນາທີ  %S ວິນາທີ %Z", "%H:%M"],
1326         "latin" : ["%H:%M:%S %Z", "%H:%M:%S"],
1327         "latvian" : ["%H:%M:%S %Z", "%H:%M"],
1328         "lithuanian" : ["%H:%M:%S %Z", "%H:%M"],
1329         "lowersorbian" : ["%H:%M:%S %Z", "%H:%M"],
1330         "macedonian" : ["%H:%M:%S %Z", "%H:%M"],
1331         "magyar" : ["%H:%M:%S %Z", "%H:%M"],
1332         "malayalam" : ["%p %I:%M:%S %Z", "%p %I:%M"],
1333         "marathi" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1334         "mongolian" : ["%H:%M:%S %Z", "%H:%M"],
1335         "naustrian" : ["%H:%M:%S %Z", "%H:%M"],
1336         "newzealand" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1337         "ngerman" : ["%H:%M:%S %Z", "%H:%M"],
1338         "norsk" : ["%H:%M:%S %Z", "%H:%M"],
1339         "nynorsk" : ["kl. %H:%M:%S %Z", "%H:%M"],
1340         "occitan" : ["%H:%M:%S %Z", "%H:%M"],
1341         "piedmontese" : ["%H:%M:%S %Z", "%H:%M:%S"],
1342         "polish" : ["%H:%M:%S %Z", "%H:%M"],
1343         "polutonikogreek" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1344         "portuguese" : ["%H:%M:%S %Z", "%H:%M"],
1345         "romanian" : ["%H:%M:%S %Z", "%H:%M"],
1346         "romansh" : ["%H:%M:%S %Z", "%H:%M"],
1347         "russian" : ["%H:%M:%S %Z", "%H:%M"],
1348         "samin" : ["%H:%M:%S %Z", "%H:%M"],
1349         "sanskrit" : ["%H:%M:%S %Z", "%H:%M"],
1350         "scottish" : ["%H:%M:%S %Z", "%H:%M"],
1351         "serbian" : ["%H:%M:%S %Z", "%H:%M"],
1352         "serbian-latin" : ["%H:%M:%S %Z", "%H:%M"],
1353         "slovak" : ["%H:%M:%S %Z", "%H:%M"],
1354         "slovene" : ["%H:%M:%S %Z", "%H:%M"],
1355         "spanish" : ["%H:%M:%S (%Z)", "%H:%M"],
1356         "spanish-mexico" : ["%H:%M:%S %Z", "%H:%M"],
1357         "swedish" : ["kl. %H:%M:%S %Z", "%H:%M"],
1358         "syriac" : ["%H:%M:%S %Z", "%H:%M"],
1359         "tamil" : ["%p %I:%M:%S %Z", "%p %I:%M"],
1360         "telugu" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1361         "thai" : ["%H นาฬิกา %M นาที  %S วินาที %Z", "%H:%M"],
1362         "tibetan" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1363         "turkish" : ["%H:%M:%S %Z", "%H:%M"],
1364         "turkmen" : ["%H:%M:%S %Z", "%H:%M"],
1365         "ukrainian" : ["%H:%M:%S %Z", "%H:%M"],
1366         "uppersorbian" : ["%H:%M:%S %Z", "%H:%M hodź."],
1367         "urdu" : ["%I:%M:%S %p %Z", "%I:%M %p"],
1368         "vietnamese" : ["%H:%M:%S %Z", "%H:%M"],
1369         "welsh" : ["%H:%M:%S %Z", "%H:%M"]
1370     }
1371
1372     types = ["time", "fixtime", "modtime" ]
1373     i = find_token(document.header, "\\language", 0)
1374     if i == -1:
1375         # this should not happen
1376         document.warning("Malformed LyX document! No \\language header found!")
1377         return
1378     lang = get_value(document.header, "\\language", i)
1379
1380     i = 0
1381     while True:
1382         i = find_token(document.body, "\\begin_inset Info", i+1)
1383         if i == -1:
1384             return
1385         j = find_end_of_inset(document.body, i+1)
1386         if j == -1:
1387             document.warning("Malformed LyX document: Could not find end of Info inset.")
1388             continue
1389         tp = find_token(document.body, 'type', i, j)
1390         tpv = get_quoted_value(document.body, "type", tp)
1391         if tpv not in types:
1392             continue
1393         arg = find_token(document.body, 'arg', i, j)
1394         argv = get_quoted_value(document.body, "arg", arg)
1395         isotime = ""
1396         dtme = datetime.now()
1397         tme = dtme.time()
1398         if tpv == "fixtime":
1399             timecomps = argv.split('@')
1400             if len(timecomps) > 1:
1401                 argv = timecomps[0]
1402                 isotime = timecomps[1]
1403                 m = re.search(r'(\d\d):(\d\d):(\d\d)', isotime)
1404                 if m:
1405                     tme = time(int(m.group(1)), int(m.group(2)), int(m.group(3)))
1406                 else:
1407                     m = re.search(r'(\d\d):(\d\d)', isotime)
1408                     if m:
1409                         tme = time(int(m.group(1)), int(m.group(2)))
1410 # FIXME if we had the path to the original document (not the one in the tmp dir),
1411 #        we could use the mtime.
1412 #        elif tpv == "moddate":
1413 #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
1414         result = ""
1415         if argv == "ISO":
1416             result = tme.isoformat()
1417         elif argv == "long":
1418             result = tme.strftime(timeformats[lang][0])
1419         elif argv == "short":
1420             result = tme.strftime(timeformats[lang][1])
1421         else:
1422             fmt = argv.replace("HH", "%H").replace("H", "%H").replace("hh", "%I").replace("h", "%I")
1423             fmt = fmt.replace("mm", "%M").replace("m", "%M").replace("ss", "%S").replace("s", "%S")
1424             fmt = fmt.replace("zzz", "%f").replace("z", "%f").replace("t", "%Z")
1425             fmt = fmt.replace("AP", "%p").replace("ap", "%p").replace("A", "%p").replace("a", "%p")
1426             fmt = fmt.replace("'", "")
1427             result = dte.strftime(fmt)
1428         document.body[i : j+1] = result
1429
1430
1431 def revert_namenoextinfo(document):
1432     """Merge buffer Info inset type name-noext to name."""
1433
1434     i = 0
1435     while True:
1436         i = find_token(document.body, "\\begin_inset Info", i+1)
1437         if i == -1:
1438             return
1439         j = find_end_of_inset(document.body, i+1)
1440         if j == -1:
1441             document.warning("Malformed LyX document: Could not find end of Info inset.")
1442             continue
1443         tp = find_token(document.body, 'type', i, j)
1444         tpv = get_quoted_value(document.body, "type", tp)
1445         if tpv != "buffer":
1446             continue
1447         arg = find_token(document.body, 'arg', i, j)
1448         argv = get_quoted_value(document.body, "arg", arg)
1449         if argv != "name-noext":
1450             continue
1451         document.body[arg] = "arg \"name\""
1452
1453
1454 def revert_l7ninfo(document):
1455     """Revert l7n Info inset to text."""
1456
1457     i = 0
1458     while True:
1459         i = find_token(document.body, "\\begin_inset Info", i+1)
1460         if i == -1:
1461             return
1462         j = find_end_of_inset(document.body, i+1)
1463         if j == -1:
1464             document.warning("Malformed LyX document: Could not find end of Info inset.")
1465             continue
1466         tp = find_token(document.body, 'type', i, j)
1467         tpv = get_quoted_value(document.body, "type", tp)
1468         if tpv != "l7n":
1469             continue
1470         arg = find_token(document.body, 'arg', i, j)
1471         argv = get_quoted_value(document.body, "arg", arg)
1472         # remove trailing colons, menu accelerator (|...) and qt accelerator (&), while keeping literal " & "
1473         argv = argv.rstrip(':').split('|')[0].replace(" & ", "</amp;>").replace("&", "").replace("</amp;>", " & ")
1474         document.body[i : j+1] = argv
1475
1476
1477 def revert_listpargs(document):
1478     """Reverts listpreamble arguments to TeX-code"""
1479     i = 0
1480     while True:
1481         i = find_token(document.body, "\\begin_inset Argument listpreamble:", i+1)
1482         if i == -1:
1483             return
1484         j = find_end_of_inset(document.body, i)
1485         # Find containing paragraph layout
1486         parent = get_containing_layout(document.body, i)
1487         if parent == False:
1488             document.warning("Malformed LyX document: Can't find parent paragraph layout")
1489             continue
1490         parbeg = parent[3]
1491         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1492         endPlain = find_end_of_layout(document.body, beginPlain)
1493         content = document.body[beginPlain + 1 : endPlain]
1494         del document.body[i:j+1]
1495         subst = ["\\begin_inset ERT", "status collapsed", "", "\\begin_layout Plain Layout",
1496                  "{"] + content + ["}", "\\end_layout", "", "\\end_inset", ""]
1497         document.body[parbeg : parbeg] = subst
1498
1499
1500 def revert_lformatinfo(document):
1501     """Revert layout format Info inset to text."""
1502
1503     i = 0
1504     while True:
1505         i = find_token(document.body, "\\begin_inset Info", i+1)
1506         if i == -1:
1507             return
1508         j = find_end_of_inset(document.body, i+1)
1509         if j == -1:
1510             document.warning("Malformed LyX document: Could not find end of Info inset.")
1511             continue
1512         tp = find_token(document.body, 'type', i, j)
1513         tpv = get_quoted_value(document.body, "type", tp)
1514         if tpv != "lyxinfo":
1515             continue
1516         arg = find_token(document.body, 'arg', i, j)
1517         argv = get_quoted_value(document.body, "arg", arg)
1518         if argv != "layoutformat":
1519             continue
1520         # hardcoded for now
1521         document.body[i : j+1] = "69"
1522
1523
1524 def convert_hebrew_parentheses(document):
1525     """ Swap opening/closing parentheses in Hebrew text.
1526
1527     Up to LyX 2.4, "(" was used as closing parenthesis and
1528     ")" as opening parenthesis for Hebrew in the LyX source.
1529     """
1530     # print("convert hebrew parentheses")
1531     current_languages = [document.language]
1532     i = 0
1533     while i < len(document.body):
1534         line = document.body[i]
1535         if line.startswith('\\lang '):
1536             current_languages[-1] = line.lstrip('\\lang ')
1537         elif line.startswith('\\begin_layout'):
1538             current_languages.append(current_languages[-1])
1539             # print (line, current_languages[-1])
1540         elif line.startswith('\\end_layout'):
1541             current_languages.pop()
1542         elif line.startswith('\\begin_inset Formula'):
1543             # In math, parentheses must not be changed
1544             i = find_end_of_inset(document.body, i)
1545             continue
1546         elif current_languages[-1] == 'hebrew' and not line.startswith('\\'):
1547             document.body[i] = line.replace('(','\x00').replace(')','(').replace('\x00',')')
1548         i += 1
1549
1550
1551 def revert_hebrew_parentheses(document):
1552     """Store parentheses in Hebrew text reversed"""
1553     # This only exists to keep the convert/revert naming convention
1554     convert_hebrew_parentheses(document)
1555
1556
1557 def revert_malayalam(document):
1558     """Set the document language to English but assure Malayalam output"""
1559
1560     revert_language(document, "malayalam", "", "malayalam")
1561
1562
1563 def revert_soul(document):
1564     """Revert soul module flex insets to ERT"""
1565
1566     flexes = ["Spaceletters", "Strikethrough", "Underline", "Highlight", "Capitalize"]
1567
1568     for flex in flexes:
1569         i = find_token(document.body, "\\begin_inset Flex %s" % flex, 0)
1570         if i != -1:
1571             add_to_preamble(document, ["\\usepackage{soul}"])
1572             break
1573     i = find_token(document.body, "\\begin_inset Flex Highlight", 0)
1574     if i != -1:
1575         add_to_preamble(document, ["\\usepackage{color}"])
1576
1577     revert_flex_inset(document.body, "Spaceletters", "\\so")
1578     revert_flex_inset(document.body, "Strikethrough", "\\st")
1579     revert_flex_inset(document.body, "Underline", "\\ul")
1580     revert_flex_inset(document.body, "Highlight", "\\hl")
1581     revert_flex_inset(document.body, "Capitalize", "\\caps")
1582
1583
1584 def revert_tablestyle(document):
1585     """Remove tablestyle params"""
1586
1587     i = find_token(document.header, "\\tablestyle")
1588     if i != -1:
1589         del document.header[i]
1590
1591
1592 def revert_bibfileencodings(document):
1593     """Revert individual Biblatex bibliography encodings"""
1594
1595     # Get cite engine
1596     engine = "basic"
1597     i = find_token(document.header, "\\cite_engine", 0)
1598     if i == -1:
1599         document.warning("Malformed document! Missing \\cite_engine")
1600     else:
1601         engine = get_value(document.header, "\\cite_engine", i)
1602
1603     # Check if biblatex
1604     biblatex = False
1605     if engine in ["biblatex", "biblatex-natbib"]:
1606         biblatex = True
1607
1608     # Map lyx to latex encoding names
1609     encodings = {
1610         "utf8" : "utf8",
1611         "utf8x" : "utf8x",
1612         "armscii8" : "armscii8",
1613         "iso8859-1" : "latin1",
1614         "iso8859-2" : "latin2",
1615         "iso8859-3" : "latin3",
1616         "iso8859-4" : "latin4",
1617         "iso8859-5" : "iso88595",
1618         "iso8859-6" : "8859-6",
1619         "iso8859-7" : "iso-8859-7",
1620         "iso8859-8" : "8859-8",
1621         "iso8859-9" : "latin5",
1622         "iso8859-13" : "latin7",
1623         "iso8859-15" : "latin9",
1624         "iso8859-16" : "latin10",
1625         "applemac" : "applemac",
1626         "cp437" : "cp437",
1627         "cp437de" : "cp437de",
1628         "cp850" : "cp850",
1629         "cp852" : "cp852",
1630         "cp855" : "cp855",
1631         "cp858" : "cp858",
1632         "cp862" : "cp862",
1633         "cp865" : "cp865",
1634         "cp866" : "cp866",
1635         "cp1250" : "cp1250",
1636         "cp1251" : "cp1251",
1637         "cp1252" : "cp1252",
1638         "cp1255" : "cp1255",
1639         "cp1256" : "cp1256",
1640         "cp1257" : "cp1257",
1641         "koi8-r" : "koi8-r",
1642         "koi8-u" : "koi8-u",
1643         "pt154" : "pt154",
1644         "utf8-platex" : "utf8",
1645         "ascii" : "ascii"
1646     }
1647
1648     i = 0
1649     while (True):
1650         i = find_token(document.body, "\\begin_inset CommandInset bibtex", i+1)
1651         if i == -1:
1652             break
1653         j = find_end_of_inset(document.body, i)
1654         if j == -1:
1655             document.warning("Can't find end of bibtex inset at line %d!!" %(i))
1656             continue
1657         encodings = get_quoted_value(document.body, "file_encodings", i, j)
1658         if not encodings:
1659             i = j
1660             continue
1661         bibfiles = get_quoted_value(document.body, "bibfiles", i, j).split(",")
1662         opts = get_quoted_value(document.body, "biblatexopts", i, j)
1663         if len(bibfiles) == 0:
1664             document.warning("Bibtex inset at line %d does not have a bibfile!" %(i))
1665         # remove encoding line
1666         k = find_token(document.body, "file_encodings", i, j)
1667         if k != -1:
1668             del document.body[k]
1669         # Re-find inset end line
1670         j = find_end_of_inset(document.body, i)
1671         if biblatex:
1672             enclist = encodings.split("\t")
1673             encmap = dict()
1674             for pp in enclist:
1675                 ppp = pp.split(" ", 1)
1676                 encmap[ppp[0]] = ppp[1]
1677             for bib in bibfiles:
1678                 pr = "\\addbibresource"
1679                 if bib in encmap.keys():
1680                     pr += "[bibencoding=" + encmap[bib] + "]"
1681                 pr += "{" + bib + "}"
1682                 add_to_preamble(document, [pr])
1683             # Insert ERT \\printbibliography and wrap bibtex inset to a Note
1684             pcmd = "printbibliography"
1685             if opts:
1686                 pcmd += "[" + opts + "]"
1687             repl = ["\\begin_inset ERT", "status open", "", "\\begin_layout Plain Layout",\
1688                     "", "", "\\backslash", pcmd, "\\end_layout", "", "\\end_inset", "", "",\
1689                     "\\end_layout", "", "\\begin_layout Standard", "\\begin_inset Note Note",\
1690                     "status open", "", "\\begin_layout Plain Layout" ]
1691             repl += document.body[i:j+1]
1692             repl += ["", "\\end_layout", "", "\\end_inset", "", ""]
1693             document.body[i:j+1] = repl
1694             j += 27
1695
1696         i = j
1697
1698
1699 def revert_cmidruletrimming(document):
1700     """Remove \\cmidrule trimming"""
1701
1702     # FIXME: Revert to TeX code?
1703     i = 0
1704     while True:
1705         # first, let's find out if we need to do anything
1706         i = find_token(document.body, '<cell ', i+1)
1707         if i == -1:
1708             return
1709         j = document.body[i].find('trim="')
1710         if j == -1:
1711              continue
1712         rgx = re.compile(r' (bottom|top)line[lr]trim="true"')
1713         # remove trim option
1714         document.body[i] = rgx.sub('', document.body[i])
1715
1716
1717 ruby_inset_def = [
1718     r'### Inserted by lyx2lyx (ruby inset) ###',
1719     r'InsetLayout Flex:Ruby',
1720     r'  LyxType       charstyle',
1721     r'  LatexType     command',
1722     r'  LatexName     ruby',
1723     r'  HTMLTag       ruby',
1724     r'  HTMLAttr      ""',
1725     r'  HTMLInnerTag  rb',
1726     r'  HTMLInnerAttr ""',
1727     r'  BgColor       none',
1728     r'  LabelString   "Ruby"',
1729     r'  Decoration    Conglomerate',
1730     r'  Preamble',
1731     r'    \ifdefined\kanjiskip',
1732     r'      \IfFileExists{okumacro.sty}{\usepackage{okumacro}}{}',
1733     r'    \else \ifdefined\luatexversion',
1734     r'      \usepackage{luatexja-ruby}',
1735     r'    \else \ifdefined\XeTeXversion',
1736     r'      \usepackage{ruby}%',
1737     r'    \fi\fi\fi',
1738     r'    \providecommand{\ruby}[2]{\shortstack{\tiny #2\\#1}}',
1739     r'  EndPreamble',
1740     r'  Argument  post:1',
1741     r'    LabelString  "ruby text"',
1742     r'    MenuString  "Ruby Text|R"',
1743     r'    Tooltip    "Reading aid (ruby, furigana) for Chinese characters."',
1744     r'    Decoration  Conglomerate',
1745     r'    Font',
1746     r'      Size    tiny',
1747     r'    EndFont',
1748     r'    LabelFont',
1749     r'      Size    tiny',
1750     r'    EndFont',
1751     r'    Mandatory  1',
1752     r'  EndArgument',
1753     r'End',
1754 ]
1755
1756
1757 def convert_ruby_module(document):
1758     """Use ruby module instead of local module definition"""
1759     if document.del_local_layout(ruby_inset_def):
1760         document.add_module("ruby")
1761
1762
1763 def revert_ruby_module(document):
1764     """Replace ruby module with local module definition"""
1765     if document.del_module("ruby"):
1766         document.append_local_layout(ruby_inset_def)
1767
1768
1769 def convert_utf8_japanese(document):
1770     """Use generic utf8 with Japanese documents."""
1771     lang = get_value(document.header, "\\language")
1772     if not lang.startswith("japanese"):
1773         return
1774     inputenc = get_value(document.header, "\\inputencoding")
1775     if ((lang == "japanese" and inputenc == "utf8-platex")
1776         or (lang == "japanese-cjk" and inputenc == "utf8-cjk")):
1777         document.set_parameter("inputencoding", "utf8")
1778
1779
1780 def revert_utf8_japanese(document):
1781     """Use Japanese utf8 variants with Japanese documents."""
1782     inputenc = get_value(document.header, "\\inputencoding")
1783     if inputenc != "utf8":
1784         return
1785     lang = get_value(document.header, "\\language")
1786     if lang == "japanese":
1787         document.set_parameter("inputencoding", "utf8-platex")
1788     if lang == "japanese-cjk":
1789         document.set_parameter("inputencoding", "utf8-cjk")
1790
1791
1792 def revert_lineno(document):
1793     " Replace lineno setting with user-preamble code."
1794
1795     options = get_quoted_value(document.header, "\\lineno_options",
1796                                delete=True)
1797     if not get_bool_value(document.header, "\\use_lineno", delete=True):
1798         return
1799     if options:
1800         options = "[" + options + "]"
1801     add_to_preamble(document, ["\\usepackage%s{lineno}" % options,
1802                                "\\linenumbers"])
1803
1804 def convert_lineno(document):
1805     " Replace user-preamble code with native lineno support."
1806     use_lineno = 0
1807     options = ""
1808     i = find_token(document.preamble, "\\linenumbers", 1)
1809     if i > -1:
1810         usepkg = re.match(r"\\usepackage(.*){lineno}", document.preamble[i-1])
1811         if usepkg:
1812             use_lineno = 1
1813             options = usepkg.group(1).strip("[]")
1814             del(document.preamble[i-1:i+1])
1815             del_token(document.preamble, "% Added by lyx2lyx", i-2, i-1)
1816
1817     k = find_token(document.header, "\\index ")
1818     if options == "":
1819         document.header[k:k] = ["\\use_lineno %d" % use_lineno]
1820     else:
1821         document.header[k:k] = ["\\use_lineno %d" % use_lineno,
1822                                 "\\lineno_options %s" % options]
1823
1824
1825 def convert_aaencoding(document):
1826     " Convert default document option due to encoding change in aa class. "
1827
1828     if document.textclass != "aa":
1829         return
1830
1831     i = find_token(document.header, "\\use_default_options true")
1832     if i == -1:
1833         return
1834     val = get_value(document.header, "\\inputencoding")
1835     if not val:
1836         document.warning("Malformed LyX Document! Missing '\\inputencoding' header.")
1837         return
1838     if val == "auto-legacy" or val == "latin9":
1839         document.header[i] = "\\use_default_options false"
1840         k = find_token(document.header, "\\options")
1841         if k == -1:
1842             document.header.insert(i, "\\options latin9")
1843         else:
1844             document.header[k] += ",latin9"
1845
1846
1847 def revert_aaencoding(document):
1848     " Revert default document option due to encoding change in aa class. "
1849
1850     if document.textclass != "aa":
1851         return
1852
1853     i = find_token(document.header, "\\use_default_options true")
1854     if i == -1:
1855         return
1856     val = get_value(document.header, "\\inputencoding")
1857     if not val:
1858         document.warning("Malformed LyX Document! Missing \\inputencoding header.")
1859         return
1860     if val == "utf8":
1861         document.header[i] = "\\use_default_options false"
1862         k = find_token(document.header, "\\options", 0)
1863         if k == -1:
1864             document.header.insert(i, "\\options utf8")
1865         else:
1866             document.header[k] = document.header[k] + ",utf8"
1867
1868
1869 def revert_new_languages(document):
1870     """Emulate support for Azerbaijani, Bengali, Church Slavonic, Korean,
1871     and Russian (Petrine orthography)."""
1872
1873     #                lyxname:          (babelname, polyglossianame)
1874     new_languages = {"azerbaijani":    ("azerbaijani", ""),
1875                      "bengali":        ("", "bengali"),
1876                      "churchslavonic": ("", "churchslavonic"),
1877                      "oldrussian":     ("", "russian"),
1878                      "korean":         ("", "korean"),
1879                     }
1880     if document.language in new_languages:
1881         used_languages = {document.language}
1882     else:
1883         used_languages = set()
1884     i = 0
1885     while True:
1886         i = find_token(document.body, "\\lang", i+1)
1887         if i == -1:
1888             break
1889         val = get_value(document.body, "\\lang", i)
1890         if val in new_languages:
1891             used_languages.add(val)
1892
1893     # Korean is already supported via CJK, so leave as-is for Babel
1894     if ("korean" in used_languages
1895         and (not get_bool_value(document.header, "\\use_non_tex_fonts")
1896              or get_value(document.header, "\\language_package") == "babel")):
1897         used_languages.discard("korean")
1898
1899     for lang in used_languages:
1900         revert_language(document, lang, *new_languages[lang])
1901
1902
1903 gloss_inset_def = [
1904     r'### Inserted by lyx2lyx (deprecated ling glosses) ###',
1905     r'InsetLayout Flex:Glosse',
1906     r'  LyXType               custom',
1907     r'  LabelString           "Gloss (old version)"',
1908     r'  MenuString            "Gloss (old version)"',
1909     r'  LatexType             environment',
1910     r'  LatexName             linggloss',
1911     r'  Decoration            minimalistic',
1912     r'  LabelFont',
1913     r'    Size                Small',
1914     r'  EndFont',
1915     r'  MultiPar              true',
1916     r'  CustomPars            false',
1917     r'  ForcePlain            true',
1918     r'  ParbreakIsNewline     true',
1919     r'  FreeSpacing           true',
1920     r'  Requires              covington',
1921     r'  Preamble',
1922     r'          \def\glosstr{}',
1923     r'          \@ifundefined{linggloss}{%',
1924     r'          \newenvironment{linggloss}[2][]{',
1925     r'             \def\glosstr{\glt #1}%',
1926     r'             \gll #2}',
1927     r'          {\glosstr\glend}}{}',
1928     r'  EndPreamble',
1929     r'  InToc                 true',
1930     r'  ResetsFont            true',
1931     r'  Argument 1',
1932     r'          Decoration    conglomerate',
1933     r'          LabelString   "Translation"',
1934     r'          MenuString    "Glosse Translation|s"',
1935     r'          Tooltip       "Add a translation for the glosse"',
1936     r'  EndArgument',
1937     r'End'
1938 ]
1939
1940 glosss_inset_def = [
1941     r'### Inserted by lyx2lyx (deprecated ling glosses) ###',
1942     r'InsetLayout Flex:Tri-Glosse',
1943     r'  LyXType               custom',
1944     r'  LabelString           "Tri-Gloss (old version)"',
1945     r'  MenuString            "Tri-Gloss (old version)"',
1946     r'  LatexType             environment',
1947     r'  LatexName             lingglosss',
1948     r'  Decoration            minimalistic',
1949     r'  LabelFont',
1950     r'    Size                Small',
1951     r'  EndFont',
1952     r'  MultiPar              true',
1953     r'  CustomPars            false',
1954     r'  ForcePlain            true',
1955     r'  ParbreakIsNewline     true',
1956     r'  FreeSpacing           true',
1957     r'  InToc                 true',
1958     r'  Requires              covington',
1959     r'  Preamble',
1960     r'          \def\glosstr{}',
1961     r'          \@ifundefined{lingglosss}{%',
1962     r'          \newenvironment{lingglosss}[2][]{',
1963     r'              \def\glosstr{\glt #1}%',
1964     r'              \glll #2}',
1965     r'          {\glosstr\glend}}{}',
1966     r'  EndPreamble',
1967     r'  ResetsFont            true',
1968     r'  Argument 1',
1969     r'          Decoration    conglomerate',
1970     r'          LabelString   "Translation"',
1971     r'          MenuString    "Glosse Translation|s"',
1972     r'          Tooltip       "Add a translation for the glosse"',
1973     r'  EndArgument',
1974     r'End'
1975 ]
1976
1977 def convert_linggloss(document):
1978     " Move old ling glosses to local layout "
1979     if find_token(document.body, '\\begin_inset Flex Glosse', 0) != -1:
1980         document.append_local_layout(gloss_inset_def)
1981     if find_token(document.body, '\\begin_inset Flex Tri-Glosse', 0) != -1:
1982         document.append_local_layout(glosss_inset_def)
1983
1984 def revert_linggloss(document):
1985     " Revert to old ling gloss definitions "
1986     if not "linguistics" in document.get_module_list():
1987         return
1988     document.del_local_layout(gloss_inset_def)
1989     document.del_local_layout(glosss_inset_def)
1990
1991     cov_req = False
1992     glosses = ["\\begin_inset Flex Interlinear Gloss (2 Lines)", "\\begin_inset Flex Interlinear Gloss (3 Lines)"]
1993     for glosse in glosses:
1994         i = 0
1995         while True:
1996             i = find_token(document.body, glosse, i+1)
1997             if i == -1:
1998                 break
1999             j = find_end_of_inset(document.body, i)
2000             if j == -1:
2001                 document.warning("Malformed LyX document: Can't find end of Gloss inset")
2002                 continue
2003
2004             arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
2005             endarg = find_end_of_inset(document.body, arg)
2006             optargcontent = []
2007             if arg != -1:
2008                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2009                 if argbeginPlain == -1:
2010                     document.warning("Malformed LyX document: Can't find optarg plain Layout")
2011                     continue
2012                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2013                 optargcontent = document.body[argbeginPlain + 1 : argendPlain - 2]
2014
2015                 # remove Arg insets and paragraph, if it only contains this inset
2016                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2017                     del document.body[arg - 1 : endarg + 4]
2018                 else:
2019                     del document.body[arg : endarg + 1]
2020
2021             arg = find_token(document.body, "\\begin_inset Argument post:1", i, j)
2022             endarg = find_end_of_inset(document.body, arg)
2023             marg1content = []
2024             if arg != -1:
2025                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2026                 if argbeginPlain == -1:
2027                     document.warning("Malformed LyX document: Can't find arg 1 plain Layout")
2028                     continue
2029                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2030                 marg1content = document.body[argbeginPlain + 1 : argendPlain - 2]
2031
2032                 # remove Arg insets and paragraph, if it only contains this inset
2033                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2034                     del document.body[arg - 1 : endarg + 4]
2035                 else:
2036                     del document.body[arg : endarg + 1]
2037
2038             arg = find_token(document.body, "\\begin_inset Argument post:2", i, j)
2039             endarg = find_end_of_inset(document.body, arg)
2040             marg2content = []
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 arg 2 plain Layout")
2045                     continue
2046                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2047                 marg2content = 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             arg = find_token(document.body, "\\begin_inset Argument post:3", i, j)
2056             endarg = find_end_of_inset(document.body, arg)
2057             marg3content = []
2058             if arg != -1:
2059                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2060                 if argbeginPlain == -1:
2061                     document.warning("Malformed LyX document: Can't find arg 3 plain Layout")
2062                     continue
2063                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2064                 marg3content = document.body[argbeginPlain + 1 : argendPlain - 2]
2065
2066                 # remove Arg insets and paragraph, if it only contains this inset
2067                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2068                     del document.body[arg - 1 : endarg + 4]
2069                 else:
2070                     del document.body[arg : endarg + 1]
2071
2072             cmd = "\\digloss"
2073             if glosse == "\\begin_inset Flex Interlinear Gloss (3 Lines)":
2074                 cmd = "\\trigloss"
2075
2076             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2077             endInset = find_end_of_inset(document.body, i)
2078             endPlain = find_end_of_layout(document.body, beginPlain)
2079             precontent = put_cmd_in_ert(cmd)
2080             if len(optargcontent) > 0:
2081                 precontent += put_cmd_in_ert("[") + optargcontent + put_cmd_in_ert("]")
2082             precontent += put_cmd_in_ert("{")
2083
2084             postcontent = put_cmd_in_ert("}{") + marg1content + put_cmd_in_ert("}{") + marg2content
2085             if cmd == "\\trigloss":
2086                 postcontent += put_cmd_in_ert("}{") + marg3content
2087             postcontent += put_cmd_in_ert("}")
2088
2089             document.body[endPlain:endInset + 1] = postcontent
2090             document.body[beginPlain + 1:beginPlain] = precontent
2091             del document.body[i : beginPlain + 1]
2092             if not cov_req:
2093                 document.append_local_layout("Requires covington")
2094                 cov_req = True
2095             i = beginPlain
2096
2097
2098 def revert_subexarg(document):
2099     " Revert linguistic subexamples with argument to ERT "
2100
2101     if not "linguistics" in document.get_module_list():
2102         return
2103
2104     cov_req = False
2105     i = 0
2106     while True:
2107         i = find_token(document.body, "\\begin_layout Subexample", i+1)
2108         if i == -1:
2109             break
2110         j = find_end_of_layout(document.body, i)
2111         if j == -1:
2112             document.warning("Malformed LyX document: Can't find end of Subexample layout")
2113             continue
2114         while True:
2115             # check for consecutive layouts
2116             k = find_token(document.body, "\\begin_layout", j)
2117             if k == -1 or document.body[k] != "\\begin_layout Subexample":
2118                 break
2119             j = find_end_of_layout(document.body, k)
2120             if j == -1:
2121                  document.warning("Malformed LyX document: Can't find end of Subexample layout")
2122                  continue
2123
2124         arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
2125         if arg == -1:
2126             continue
2127
2128         endarg = find_end_of_inset(document.body, arg)
2129         optargcontent = ""
2130         argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2131         if argbeginPlain == -1:
2132             document.warning("Malformed LyX document: Can't find optarg plain Layout")
2133             continue
2134         argendPlain = find_end_of_inset(document.body, argbeginPlain)
2135         optargcontent = lyx2latex(document, document.body[argbeginPlain + 1 : argendPlain - 2])
2136
2137         # remove Arg insets and paragraph, if it only contains this inset
2138         if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2139             del document.body[arg - 1 : endarg + 4]
2140         else:
2141             del document.body[arg : endarg + 1]
2142
2143         cmd = put_cmd_in_ert("\\begin{subexamples}[" + optargcontent + "]")
2144
2145         # re-find end of layout
2146         j = find_end_of_layout(document.body, i)
2147         if j == -1:
2148             document.warning("Malformed LyX document: Can't find end of Subexample layout")
2149             continue
2150         while True:
2151             # check for consecutive layouts
2152             k = find_token(document.body, "\\begin_layout", j)
2153             if k == -1 or document.body[k] != "\\begin_layout Subexample":
2154                 break
2155             document.body[k : k + 1] = ["\\begin_layout Standard"] + put_cmd_in_ert("\\item ")
2156             j = find_end_of_layout(document.body, k)
2157             if j == -1:
2158                  document.warning("Malformed LyX document: Can't find end of Subexample layout")
2159                  continue
2160
2161         endev = put_cmd_in_ert("\\end{subexamples}")
2162
2163         document.body[j : j] = ["\\end_layout", "", "\\begin_layout Standard"] + endev
2164         document.body[i : i + 1] = ["\\begin_layout Standard"] + cmd \
2165                 + ["\\end_layout", "", "\\begin_layout Standard"] + put_cmd_in_ert("\\item ")
2166         if not cov_req:
2167             document.append_local_layout("Requires covington")
2168             cov_req = True
2169
2170
2171 def revert_drs(document):
2172     " Revert DRS insets (linguistics) to ERT "
2173
2174     if not "linguistics" in document.get_module_list():
2175         return
2176
2177     cov_req = False
2178     drses = ["\\begin_inset Flex DRS", "\\begin_inset Flex DRS*",
2179              "\\begin_inset Flex IfThen-DRS", "\\begin_inset Flex Cond-DRS",
2180              "\\begin_inset Flex QDRS", "\\begin_inset Flex NegDRS",
2181              "\\begin_inset Flex SDRS"]
2182     for drs in drses:
2183         i = 0
2184         while True:
2185             i = find_token(document.body, drs, i+1)
2186             if i == -1:
2187                 break
2188             j = find_end_of_inset(document.body, i)
2189             if j == -1:
2190                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2191                 continue
2192
2193             # Check for arguments
2194             arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
2195             endarg = find_end_of_inset(document.body, arg)
2196             prearg1content = []
2197             if arg != -1:
2198                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2199                 if argbeginPlain == -1:
2200                     document.warning("Malformed LyX document: Can't find Argument 1 plain Layout")
2201                     continue
2202                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2203                 prearg1content = document.body[argbeginPlain + 1 : argendPlain - 2]
2204
2205                 # remove Arg insets and paragraph, if it only contains this inset
2206                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2207                     del document.body[arg - 1 : endarg + 4]
2208                 else:
2209                     del document.body[arg : endarg + 1]
2210
2211             # re-find inset end
2212             j = find_end_of_inset(document.body, i)
2213             if j == -1:
2214                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2215                 continue
2216
2217             arg = find_token(document.body, "\\begin_inset Argument 2", i, j)
2218             endarg = find_end_of_inset(document.body, arg)
2219             prearg2content = []
2220             if arg != -1:
2221                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2222                 if argbeginPlain == -1:
2223                     document.warning("Malformed LyX document: Can't find Argument 2 plain Layout")
2224                     continue
2225                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2226                 prearg2content = document.body[argbeginPlain + 1 : argendPlain - 2]
2227
2228                 # remove Arg insets and paragraph, if it only contains this inset
2229                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2230                     del document.body[arg - 1 : endarg + 4]
2231                 else:
2232                     del document.body[arg : endarg + 1]
2233
2234             # re-find inset end
2235             j = find_end_of_inset(document.body, i)
2236             if j == -1:
2237                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2238                 continue
2239
2240             arg = find_token(document.body, "\\begin_inset Argument post:1", i, j)
2241             endarg = find_end_of_inset(document.body, arg)
2242             postarg1content = []
2243             if arg != -1:
2244                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2245                 if argbeginPlain == -1:
2246                     document.warning("Malformed LyX document: Can't find Argument post:1 plain Layout")
2247                     continue
2248                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2249                 postarg1content = document.body[argbeginPlain + 1 : argendPlain - 2]
2250
2251                 # remove Arg insets and paragraph, if it only contains this inset
2252                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2253                     del document.body[arg - 1 : endarg + 4]
2254                 else:
2255                     del document.body[arg : endarg + 1]
2256
2257             # re-find inset end
2258             j = find_end_of_inset(document.body, i)
2259             if j == -1:
2260                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2261                 continue
2262
2263             arg = find_token(document.body, "\\begin_inset Argument post:2", i, j)
2264             endarg = find_end_of_inset(document.body, arg)
2265             postarg2content = []
2266             if arg != -1:
2267                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2268                 if argbeginPlain == -1:
2269                     document.warning("Malformed LyX document: Can't find Argument post:2 plain Layout")
2270                     continue
2271                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2272                 postarg2content = document.body[argbeginPlain + 1 : argendPlain - 2]
2273
2274                 # remove Arg insets and paragraph, if it only contains this inset
2275                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2276                     del document.body[arg - 1 : endarg + 4]
2277                 else:
2278                     del document.body[arg : endarg + 1]
2279
2280             # re-find inset end
2281             j = find_end_of_inset(document.body, i)
2282             if j == -1:
2283                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2284                 continue
2285
2286             arg = find_token(document.body, "\\begin_inset Argument post:3", i, j)
2287             endarg = find_end_of_inset(document.body, arg)
2288             postarg3content = []
2289             if arg != -1:
2290                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2291                 if argbeginPlain == -1:
2292                     document.warning("Malformed LyX document: Can't find Argument post:3 plain Layout")
2293                     continue
2294                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2295                 postarg3content = document.body[argbeginPlain + 1 : argendPlain - 2]
2296
2297                 # remove Arg insets and paragraph, if it only contains this inset
2298                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2299                     del document.body[arg - 1 : endarg + 4]
2300                 else:
2301                     del document.body[arg : endarg + 1]
2302
2303             # re-find inset end
2304             j = find_end_of_inset(document.body, i)
2305             if j == -1:
2306                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2307                 continue
2308
2309             arg = find_token(document.body, "\\begin_inset Argument post:4", i, j)
2310             endarg = find_end_of_inset(document.body, arg)
2311             postarg4content = []
2312             if arg != -1:
2313                 argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2314                 if argbeginPlain == -1:
2315                     document.warning("Malformed LyX document: Can't find Argument post:4 plain Layout")
2316                     continue
2317                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2318                 postarg4content = document.body[argbeginPlain + 1 : argendPlain - 2]
2319
2320                 # remove Arg insets and paragraph, if it only contains this inset
2321                 if document.body[arg - 1] == "\\begin_layout Plain Layout" and find_end_of_layout(document.body, arg - 1) == endarg + 3:
2322                     del document.body[arg - 1 : endarg + 4]
2323                 else:
2324                     del document.body[arg : endarg + 1]
2325
2326             # The respective LaTeX command
2327             cmd = "\\drs"
2328             if drs == "\\begin_inset Flex DRS*":
2329                 cmd = "\\drs*"
2330             elif drs == "\\begin_inset Flex IfThen-DRS":
2331                 cmd = "\\ifdrs"
2332             elif drs == "\\begin_inset Flex Cond-DRS":
2333                 cmd = "\\condrs"
2334             elif drs == "\\begin_inset Flex QDRS":
2335                 cmd = "\\qdrs"
2336             elif drs == "\\begin_inset Flex NegDRS":
2337                 cmd = "\\negdrs"
2338             elif drs == "\\begin_inset Flex SDRS":
2339                 cmd = "\\sdrs"
2340
2341             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2342             endInset = find_end_of_inset(document.body, i)
2343             endPlain = find_token_backwards(document.body, "\\end_layout", endInset)
2344             precontent = put_cmd_in_ert(cmd)
2345             precontent += put_cmd_in_ert("{") + prearg1content + put_cmd_in_ert("}")
2346             if drs == "\\begin_inset Flex SDRS":
2347                 precontent += put_cmd_in_ert("{") + prearg2content + put_cmd_in_ert("}")
2348             precontent += put_cmd_in_ert("{")
2349
2350             postcontent = []
2351             if cmd == "\\qdrs" or cmd == "\\condrs" or cmd == "\\ifdrs":
2352                 postcontent = put_cmd_in_ert("}{") + postarg1content + put_cmd_in_ert("}{") + postarg2content + put_cmd_in_ert("}")
2353                 if cmd == "\\condrs" or cmd == "\\qdrs":
2354                     postcontent += put_cmd_in_ert("{") + postarg3content + put_cmd_in_ert("}")
2355                 if cmd == "\\qdrs":
2356                     postcontent += put_cmd_in_ert("{") + postarg4content + put_cmd_in_ert("}")
2357             else:
2358                 postcontent = put_cmd_in_ert("}")
2359
2360             document.body[endPlain:endInset + 1] = postcontent
2361             document.body[beginPlain + 1:beginPlain] = precontent
2362             del document.body[i : beginPlain + 1]
2363             if not cov_req:
2364                 document.append_local_layout("Provides covington 1")
2365                 add_to_preamble(document, ["\\usepackage{drs,covington}"])
2366                 cov_req = True
2367             i = beginPlain
2368
2369
2370
2371 def revert_babelfont(document):
2372     " Reverts the use of \\babelfont to user preamble "
2373
2374     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
2375         return
2376
2377     i = find_token(document.header, '\\language_package', 0)
2378     if i == -1:
2379         document.warning("Malformed LyX document: Missing \\language_package.")
2380         return
2381     if get_value(document.header, "\\language_package", 0) != "babel":
2382         return
2383
2384     # check font settings
2385     # defaults
2386     roman = sans = typew = "default"
2387     osf = False
2388     sf_scale = tt_scale = 100.0
2389
2390     j = find_token(document.header, "\\font_roman", 0)
2391     if j == -1:
2392         document.warning("Malformed LyX document: Missing \\font_roman.")
2393     else:
2394         # We need to use this regex since split() does not handle quote protection
2395         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2396         roman = romanfont[2].strip('"')
2397         romanfont[2] = '"default"'
2398         document.header[j] = " ".join(romanfont)
2399
2400     j = find_token(document.header, "\\font_sans", 0)
2401     if j == -1:
2402         document.warning("Malformed LyX document: Missing \\font_sans.")
2403     else:
2404         # We need to use this regex since split() does not handle quote protection
2405         sansfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2406         sans = sansfont[2].strip('"')
2407         sansfont[2] = '"default"'
2408         document.header[j] = " ".join(sansfont)
2409
2410     j = find_token(document.header, "\\font_typewriter", 0)
2411     if j == -1:
2412         document.warning("Malformed LyX document: Missing \\font_typewriter.")
2413     else:
2414         # We need to use this regex since split() does not handle quote protection
2415         ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2416         typew = ttfont[2].strip('"')
2417         ttfont[2] = '"default"'
2418         document.header[j] = " ".join(ttfont)
2419
2420     i = find_token(document.header, "\\font_osf", 0)
2421     if i == -1:
2422         document.warning("Malformed LyX document: Missing \\font_osf.")
2423     else:
2424         osf = str2bool(get_value(document.header, "\\font_osf", i))
2425
2426     j = find_token(document.header, "\\font_sf_scale", 0)
2427     if j == -1:
2428         document.warning("Malformed LyX document: Missing \\font_sf_scale.")
2429     else:
2430         sfscale = document.header[j].split()
2431         val = sfscale[2]
2432         sfscale[2] = "100"
2433         document.header[j] = " ".join(sfscale)
2434         try:
2435             # float() can throw
2436             sf_scale = float(val)
2437         except:
2438             document.warning("Invalid font_sf_scale value: " + val)
2439
2440     j = find_token(document.header, "\\font_tt_scale", 0)
2441     if j == -1:
2442         document.warning("Malformed LyX document: Missing \\font_tt_scale.")
2443     else:
2444         ttscale = document.header[j].split()
2445         val = ttscale[2]
2446         ttscale[2] = "100"
2447         document.header[j] = " ".join(ttscale)
2448         try:
2449             # float() can throw
2450             tt_scale = float(val)
2451         except:
2452             document.warning("Invalid font_tt_scale value: " + val)
2453
2454     # set preamble stuff
2455     pretext = ['%% This document must be processed with xelatex or lualatex!']
2456     pretext.append('\\AtBeginDocument{%')
2457     if roman != "default":
2458         pretext.append('\\babelfont{rm}[Mapping=tex-text]{' + roman + '}')
2459     if sans != "default":
2460         sf = '\\babelfont{sf}['
2461         if sf_scale != 100.0:
2462             sf += 'Scale=' + str(sf_scale / 100.0) + ','
2463         sf += 'Mapping=tex-text]{' + sans + '}'
2464         pretext.append(sf)
2465     if typew != "default":
2466         tw = '\\babelfont{tt}'
2467         if tt_scale != 100.0:
2468             tw += '[Scale=' + str(tt_scale / 100.0) + ']'
2469         tw += '{' + typew + '}'
2470         pretext.append(tw)
2471     if osf:
2472         pretext.append('\\defaultfontfeatures{Numbers=OldStyle}')
2473     pretext.append('}')
2474     insert_to_preamble(document, pretext)
2475
2476
2477 def revert_minionpro(document):
2478     " Revert native MinionPro font definition (with extra options) to LaTeX "
2479
2480     if get_bool_value(document.header, "\\use_non_tex_fonts"):
2481         return
2482
2483     regexp = re.compile(r'(\\font_roman_opts)')
2484     x = find_re(document.header, regexp, 0)
2485     if x == -1:
2486         return
2487
2488     # We need to use this regex since split() does not handle quote protection
2489     romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2490     opts = romanopts[1].strip('"')
2491
2492     i = find_token(document.header, "\\font_roman", 0)
2493     if i == -1:
2494         document.warning("Malformed LyX document: Missing \\font_roman.")
2495         return
2496     else:
2497         # We need to use this regex since split() does not handle quote protection
2498         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2499         roman = romanfont[1].strip('"')
2500         if roman != "minionpro":
2501             return
2502         romanfont[1] = '"default"'
2503         document.header[i] = " ".join(romanfont)
2504         osf = False
2505         j = find_token(document.header, "\\font_osf true", 0)
2506         if j != -1:
2507             osf = True
2508         preamble = "\\usepackage["
2509         if osf:
2510             document.header[j] = "\\font_osf false"
2511         else:
2512             preamble += "lf,"
2513         preamble += opts
2514         preamble += "]{MinionPro}"
2515         add_to_preamble(document, [preamble])
2516         del document.header[x]
2517
2518
2519 def revert_font_opts(document):
2520     " revert font options by outputting \\setxxxfont or \\babelfont to the preamble "
2521
2522     NonTeXFonts = get_bool_value(document.header, "\\use_non_tex_fonts")
2523     Babel = (get_value(document.header, "\\language_package") == "babel")
2524
2525     # 1. Roman
2526     regexp = re.compile(r'(\\font_roman_opts)')
2527     i = find_re(document.header, regexp, 0)
2528     if i != -1:
2529         # We need to use this regex since split() does not handle quote protection
2530         romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2531         opts = romanopts[1].strip('"')
2532         del document.header[i]
2533         if NonTeXFonts:
2534             regexp = re.compile(r'(\\font_roman)')
2535             i = find_re(document.header, regexp, 0)
2536             if i != -1:
2537                 # We need to use this regex since split() does not handle quote protection
2538                 romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2539                 font = romanfont[2].strip('"')
2540                 romanfont[2] = '"default"'
2541                 document.header[i] = " ".join(romanfont)
2542                 if font != "default":
2543                     if Babel:
2544                         preamble = "\\babelfont{rm}["
2545                     else:
2546                         preamble = "\\setmainfont["
2547                     preamble += opts
2548                     preamble += ","
2549                     preamble += "Mapping=tex-text]{"
2550                     preamble += font
2551                     preamble += "}"
2552                     add_to_preamble(document, [preamble])
2553
2554     # 2. Sans
2555     regexp = re.compile(r'(\\font_sans_opts)')
2556     i = find_re(document.header, regexp, 0)
2557     if i != -1:
2558         scaleval = 100
2559         # We need to use this regex since split() does not handle quote protection
2560         sfopts = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2561         opts = sfopts[1].strip('"')
2562         del document.header[i]
2563         if NonTeXFonts:
2564             regexp = re.compile(r'(\\font_sf_scale)')
2565             i = find_re(document.header, regexp, 0)
2566             if i != -1:
2567                 scaleval = get_value(document.header, "\\font_sf_scale" , i).split()[1]
2568             regexp = re.compile(r'(\\font_sans)')
2569             i = find_re(document.header, regexp, 0)
2570             if i != -1:
2571                 # We need to use this regex since split() does not handle quote protection
2572                 sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2573                 font = sffont[2].strip('"')
2574                 sffont[2] = '"default"'
2575                 document.header[i] = " ".join(sffont)
2576                 if font != "default":
2577                     if Babel:
2578                         preamble = "\\babelfont{sf}["
2579                     else:
2580                         preamble = "\\setsansfont["
2581                     preamble += opts
2582                     preamble += ","
2583                     if scaleval != 100:
2584                         preamble += "Scale=0."
2585                         preamble += scaleval
2586                         preamble += ","
2587                     preamble += "Mapping=tex-text]{"
2588                     preamble += font
2589                     preamble += "}"
2590                     add_to_preamble(document, [preamble])
2591
2592     # 3. Typewriter
2593     regexp = re.compile(r'(\\font_typewriter_opts)')
2594     i = find_re(document.header, regexp, 0)
2595     if i != -1:
2596         scaleval = 100
2597         # We need to use this regex since split() does not handle quote protection
2598         ttopts = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2599         opts = ttopts[1].strip('"')
2600         del document.header[i]
2601         if NonTeXFonts:
2602             regexp = re.compile(r'(\\font_tt_scale)')
2603             i = find_re(document.header, regexp, 0)
2604             if i != -1:
2605                 scaleval = get_value(document.header, "\\font_tt_scale" , i).split()[1]
2606             regexp = re.compile(r'(\\font_typewriter)')
2607             i = find_re(document.header, regexp, 0)
2608             if i != -1:
2609                 # We need to use this regex since split() does not handle quote protection
2610                 ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2611                 font = ttfont[2].strip('"')
2612                 ttfont[2] = '"default"'
2613                 document.header[i] = " ".join(ttfont)
2614                 if font != "default":
2615                     if Babel:
2616                         preamble = "\\babelfont{tt}["
2617                     else:
2618                         preamble = "\\setmonofont["
2619                     preamble += opts
2620                     preamble += ","
2621                     if scaleval != 100:
2622                         preamble += "Scale=0."
2623                         preamble += scaleval
2624                         preamble += ","
2625                     preamble += "Mapping=tex-text]{"
2626                     preamble += font
2627                     preamble += "}"
2628                     add_to_preamble(document, [preamble])
2629
2630
2631 def revert_plainNotoFonts_xopts(document):
2632     " Revert native (straight) Noto font definition (with extra options) to LaTeX "
2633
2634     if get_bool_value(document.header, "\\use_non_tex_fonts"):
2635         return
2636
2637     osf = False
2638     y = find_token(document.header, "\\font_osf true", 0)
2639     if y != -1:
2640         osf = True
2641
2642     regexp = re.compile(r'(\\font_roman_opts)')
2643     x = find_re(document.header, regexp, 0)
2644     if x == -1 and not osf:
2645         return
2646
2647     opts = ""
2648     if x != -1:
2649         # We need to use this regex since split() does not handle quote protection
2650         romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2651         opts = romanopts[1].strip('"')
2652     if osf:
2653         if opts != "":
2654             opts += ", "
2655         opts += "osf"
2656
2657     i = find_token(document.header, "\\font_roman", 0)
2658     if i == -1:
2659         return
2660
2661     # We need to use this regex since split() does not handle quote protection
2662     romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2663     roman = romanfont[1].strip('"')
2664     if roman != "NotoSerif-TLF":
2665         return
2666
2667     j = find_token(document.header, "\\font_sans", 0)
2668     if j == -1:
2669         return
2670
2671     # We need to use this regex since split() does not handle quote protection
2672     sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2673     sf = sffont[1].strip('"')
2674     if sf != "default":
2675         return
2676
2677     j = find_token(document.header, "\\font_typewriter", 0)
2678     if j == -1:
2679         return
2680
2681     # We need to use this regex since split() does not handle quote protection
2682     ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
2683     tt = ttfont[1].strip('"')
2684     if tt != "default":
2685         return
2686
2687     # So we have noto as "complete font"
2688     romanfont[1] = '"default"'
2689     document.header[i] = " ".join(romanfont)
2690
2691     preamble = "\\usepackage["
2692     preamble += opts
2693     preamble += "]{noto}"
2694     add_to_preamble(document, [preamble])
2695     if osf:
2696         document.header[y] = "\\font_osf false"
2697     if x != -1:
2698         del document.header[x]
2699
2700
2701 def revert_notoFonts_xopts(document):
2702     " Revert native (extended) Noto font definition (with extra options) to LaTeX "
2703
2704     if get_bool_value(document.header, "\\use_non_tex_fonts"):
2705         return
2706
2707     fontmap = dict()
2708     fm = createFontMapping(['Noto'])
2709     if revert_fonts(document, fm, fontmap, True):
2710         add_preamble_fonts(document, fontmap)
2711
2712
2713 def revert_IBMFonts_xopts(document):
2714     " Revert native IBM font definition (with extra options) to LaTeX "
2715
2716     if get_bool_value(document.header, "\\use_non_tex_fonts"):
2717         return
2718
2719     fontmap = dict()
2720     fm = createFontMapping(['IBM'])
2721     if revert_fonts(document, fm, fontmap, True):
2722         add_preamble_fonts(document, fontmap)
2723
2724
2725 def revert_AdobeFonts_xopts(document):
2726     " Revert native Adobe font definition (with extra options) to LaTeX "
2727
2728     if get_bool_value(document.header, "\\use_non_tex_fonts"):
2729         return
2730
2731     fontmap = dict()
2732     fm = createFontMapping(['Adobe'])
2733     if revert_fonts(document, fm, fontmap, True):
2734         add_preamble_fonts(document, fontmap)
2735
2736
2737 def convert_osf(document):
2738     " Convert \\font_osf param to new format "
2739
2740     NonTeXFonts = get_bool_value(document.header, "\\use_non_tex_fonts")
2741
2742     i = find_token(document.header, '\\font_osf', 0)
2743     if i == -1:
2744         document.warning("Malformed LyX document: Missing \\font_osf.")
2745         return
2746
2747     osfsf = ["biolinum", "ADOBESourceSansPro", "NotoSansRegular", "NotoSansMedium", "NotoSansThin", "NotoSansLight", "NotoSansExtralight" ]
2748     osftt = ["ADOBESourceCodePro", "NotoMonoRegular" ]
2749
2750     osfval = str2bool(get_value(document.header, "\\font_osf", i))
2751     document.header[i] = document.header[i].replace("\\font_osf", "\\font_roman_osf")
2752
2753     if NonTeXFonts:
2754         document.header.insert(i, "\\font_sans_osf false")
2755         document.header.insert(i + 1, "\\font_typewriter_osf false")
2756         return
2757
2758     if osfval:
2759         x = find_token(document.header, "\\font_sans", 0)
2760         if x == -1:
2761             document.warning("Malformed LyX document: Missing \\font_sans.")
2762         else:
2763             # We need to use this regex since split() does not handle quote protection
2764             sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2765             sf = sffont[1].strip('"')
2766             if sf in osfsf:
2767                 document.header.insert(i, "\\font_sans_osf true")
2768             else:
2769                 document.header.insert(i, "\\font_sans_osf false")
2770
2771         x = find_token(document.header, "\\font_typewriter", 0)
2772         if x == -1:
2773             document.warning("Malformed LyX document: Missing \\font_typewriter.")
2774         else:
2775             # We need to use this regex since split() does not handle quote protection
2776             ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2777             tt = ttfont[1].strip('"')
2778             if tt in osftt:
2779                 document.header.insert(i + 1, "\\font_typewriter_osf true")
2780             else:
2781                 document.header.insert(i + 1, "\\font_typewriter_osf false")
2782
2783     else:
2784         document.header.insert(i, "\\font_sans_osf false")
2785         document.header.insert(i + 1, "\\font_typewriter_osf false")
2786
2787
2788 def revert_osf(document):
2789     " Revert \\font_*_osf params "
2790
2791     NonTeXFonts = get_bool_value(document.header, "\\use_non_tex_fonts")
2792
2793     i = find_token(document.header, '\\font_roman_osf', 0)
2794     if i == -1:
2795         document.warning("Malformed LyX document: Missing \\font_roman_osf.")
2796         return
2797
2798     osfval = str2bool(get_value(document.header, "\\font_roman_osf", i))
2799     document.header[i] = document.header[i].replace("\\font_roman_osf", "\\font_osf")
2800
2801     i = find_token(document.header, '\\font_sans_osf', 0)
2802     if i == -1:
2803         document.warning("Malformed LyX document: Missing \\font_sans_osf.")
2804         return
2805
2806     osfval = str2bool(get_value(document.header, "\\font_sans_osf", i))
2807     del document.header[i]
2808
2809     i = find_token(document.header, '\\font_typewriter_osf', 0)
2810     if i == -1:
2811         document.warning("Malformed LyX document: Missing \\font_typewriter_osf.")
2812         return
2813
2814     osfval |= str2bool(get_value(document.header, "\\font_typewriter_osf", i))
2815     del document.header[i]
2816
2817     if osfval:
2818         i = find_token(document.header, '\\font_osf', 0)
2819         if i == -1:
2820             document.warning("Malformed LyX document: Missing \\font_osf.")
2821             return
2822         document.header[i] = "\\font_osf true"
2823
2824
2825 def revert_texfontopts(document):
2826     " Revert native TeX font definitions (with extra options) to LaTeX "
2827
2828     if get_bool_value(document.header, "\\use_non_tex_fonts"):
2829         return
2830
2831     rmfonts = ["ccfonts", "cochineal", "utopia", "garamondx", "libertine", "lmodern", "palatino", "times", "xcharter" ]
2832
2833     # First the sf (biolinum only)
2834     regexp = re.compile(r'(\\font_sans_opts)')
2835     x = find_re(document.header, regexp, 0)
2836     if x != -1:
2837         # We need to use this regex since split() does not handle quote protection
2838         sfopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2839         opts = sfopts[1].strip('"')
2840         i = find_token(document.header, "\\font_sans", 0)
2841         if i == -1:
2842             document.warning("Malformed LyX document: Missing \\font_sans.")
2843         else:
2844             # We need to use this regex since split() does not handle quote protection
2845             sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2846             sans = sffont[1].strip('"')
2847             if sans == "biolinum":
2848                 sf_scale = 100.0
2849                 sffont[1] = '"default"'
2850                 document.header[i] = " ".join(sffont)
2851                 osf = False
2852                 j = find_token(document.header, "\\font_sans_osf true", 0)
2853                 if j != -1:
2854                     osf = True
2855                 k = find_token(document.header, "\\font_sf_scale", 0)
2856                 if k == -1:
2857                     document.warning("Malformed LyX document: Missing \\font_sf_scale.")
2858                 else:
2859                     sfscale = document.header[k].split()
2860                     val = sfscale[1]
2861                     sfscale[1] = "100"
2862                     document.header[k] = " ".join(sfscale)
2863                     try:
2864                         # float() can throw
2865                         sf_scale = float(val)
2866                     except:
2867                         document.warning("Invalid font_sf_scale value: " + val)
2868                 preamble = "\\usepackage["
2869                 if osf:
2870                     document.header[j] = "\\font_sans_osf false"
2871                     preamble += "osf,"
2872                 if sf_scale != 100.0:
2873                     preamble += 'scaled=' + str(sf_scale / 100.0) + ','
2874                 preamble += opts
2875                 preamble += "]{biolinum}"
2876                 add_to_preamble(document, [preamble])
2877                 del document.header[x]
2878
2879     regexp = re.compile(r'(\\font_roman_opts)')
2880     x = find_re(document.header, regexp, 0)
2881     if x == -1:
2882         return
2883
2884     # We need to use this regex since split() does not handle quote protection
2885     romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
2886     opts = romanopts[1].strip('"')
2887
2888     i = find_token(document.header, "\\font_roman", 0)
2889     if i == -1:
2890         document.warning("Malformed LyX document: Missing \\font_roman.")
2891         return
2892     else:
2893         # We need to use this regex since split() does not handle quote protection
2894         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
2895         roman = romanfont[1].strip('"')
2896         if not roman in rmfonts:
2897             return
2898         romanfont[1] = '"default"'
2899         document.header[i] = " ".join(romanfont)
2900         package = roman
2901         if roman == "utopia":
2902             package = "fourier"
2903         elif roman == "palatino":
2904             package = "mathpazo"
2905         elif roman == "times":
2906             package = "mathptmx"
2907         elif roman == "xcharter":
2908             package = "XCharter"
2909         osf = ""
2910         j = find_token(document.header, "\\font_roman_osf true", 0)
2911         if j != -1:
2912             if roman == "cochineal":
2913                 osf = "proportional,osf,"
2914             elif roman == "utopia":
2915                 osf = "oldstyle,"
2916             elif roman == "garamondx":
2917                 osf = "osfI,"
2918             elif roman == "libertine":
2919                 osf = "osf,"
2920             elif roman == "palatino":
2921                 osf = "osf,"
2922             elif roman == "xcharter":
2923                 osf = "osf,"
2924             document.header[j] = "\\font_roman_osf false"
2925         k = find_token(document.header, "\\font_sc true", 0)
2926         if k != -1:
2927             if roman == "utopia":
2928                 osf += "expert,"
2929             if roman == "palatino" and osf == "":
2930                 osf = "sc,"
2931             document.header[k] = "\\font_sc false"
2932         preamble = "\\usepackage["
2933         preamble += osf
2934         preamble += opts
2935         preamble += "]{" + package + "}"
2936         add_to_preamble(document, [preamble])
2937         del document.header[x]
2938
2939
2940 def convert_CantarellFont(document):
2941     " Handle Cantarell font definition to LaTeX "
2942
2943     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
2944         fm = createFontMapping(['Cantarell'])
2945         convert_fonts(document, fm, "oldstyle")
2946
2947 def revert_CantarellFont(document):
2948     " Revert native Cantarell font definition to LaTeX "
2949
2950     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
2951         fontmap = dict()
2952         fm = createFontMapping(['Cantarell'])
2953         if revert_fonts(document, fm, fontmap, False, True):
2954             add_preamble_fonts(document, fontmap)
2955
2956 def convert_ChivoFont(document):
2957     " Handle Chivo font definition to LaTeX "
2958
2959     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
2960         fm = createFontMapping(['Chivo'])
2961         convert_fonts(document, fm, "oldstyle")
2962
2963 def revert_ChivoFont(document):
2964     " Revert native Chivo font definition to LaTeX "
2965
2966     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
2967         fontmap = dict()
2968         fm = createFontMapping(['Chivo'])
2969         if revert_fonts(document, fm, fontmap, False, True):
2970             add_preamble_fonts(document, fontmap)
2971
2972
2973 def convert_FiraFont(document):
2974     " Handle Fira font definition to LaTeX "
2975
2976     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
2977         fm = createFontMapping(['Fira'])
2978         convert_fonts(document, fm, "lf")
2979
2980 def revert_FiraFont(document):
2981     " Revert native Fira font definition to LaTeX "
2982
2983     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
2984         fontmap = dict()
2985         fm = createFontMapping(['Fira'])
2986         if revert_fonts(document, fm, fontmap, False, True):
2987             add_preamble_fonts(document, fontmap)
2988
2989
2990 def convert_Semibolds(document):
2991     " Move semibold options to extraopts "
2992
2993     NonTeXFonts = get_bool_value(document.header, "\\use_non_tex_fonts")
2994
2995     i = find_token(document.header, "\\font_roman", 0)
2996     if i == -1:
2997         document.warning("Malformed LyX document: Missing \\font_roman.")
2998     else:
2999         # We need to use this regex since split() does not handle quote protection
3000         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3001         roman = romanfont[1].strip('"')
3002         if roman == "IBMPlexSerifSemibold":
3003             romanfont[1] = '"IBMPlexSerif"'
3004             document.header[i] = " ".join(romanfont)
3005
3006             if NonTeXFonts == False:
3007                 regexp = re.compile(r'(\\font_roman_opts)')
3008                 x = find_re(document.header, regexp, 0)
3009                 if x == -1:
3010                     # Sensible place to insert tag
3011                     fo = find_token(document.header, "\\font_sf_scale")
3012                     if fo == -1:
3013                         document.warning("Malformed LyX document! Missing \\font_sf_scale")
3014                     else:
3015                         document.header.insert(fo, "\\font_roman_opts \"semibold\"")
3016                 else:
3017                     # We need to use this regex since split() does not handle quote protection
3018                     romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3019                     document.header[x] = "\\font_roman_opts \"semibold, " + romanopts[1].strip('"') + "\""
3020
3021     i = find_token(document.header, "\\font_sans", 0)
3022     if i == -1:
3023         document.warning("Malformed LyX document: Missing \\font_sans.")
3024     else:
3025         # We need to use this regex since split() does not handle quote protection
3026         sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3027         sf = sffont[1].strip('"')
3028         if sf == "IBMPlexSansSemibold":
3029             sffont[1] = '"IBMPlexSans"'
3030             document.header[i] = " ".join(sffont)
3031
3032             if NonTeXFonts == False:
3033                 regexp = re.compile(r'(\\font_sans_opts)')
3034                 x = find_re(document.header, regexp, 0)
3035                 if x == -1:
3036                     # Sensible place to insert tag
3037                     fo = find_token(document.header, "\\font_sf_scale")
3038                     if fo == -1:
3039                         document.warning("Malformed LyX document! Missing \\font_sf_scale")
3040                     else:
3041                         document.header.insert(fo, "\\font_sans_opts \"semibold\"")
3042                 else:
3043                     # We need to use this regex since split() does not handle quote protection
3044                     sfopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3045                     document.header[x] = "\\font_sans_opts \"semibold, " + sfopts[1].strip('"') + "\""
3046
3047     i = find_token(document.header, "\\font_typewriter", 0)
3048     if i == -1:
3049         document.warning("Malformed LyX document: Missing \\font_typewriter.")
3050     else:
3051         # We need to use this regex since split() does not handle quote protection
3052         ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3053         tt = ttfont[1].strip('"')
3054         if tt == "IBMPlexMonoSemibold":
3055             ttfont[1] = '"IBMPlexMono"'
3056             document.header[i] = " ".join(ttfont)
3057
3058             if NonTeXFonts == False:
3059                 regexp = re.compile(r'(\\font_typewriter_opts)')
3060                 x = find_re(document.header, regexp, 0)
3061                 if x == -1:
3062                     # Sensible place to insert tag
3063                     fo = find_token(document.header, "\\font_tt_scale")
3064                     if fo == -1:
3065                         document.warning("Malformed LyX document! Missing \\font_tt_scale")
3066                     else:
3067                         document.header.insert(fo, "\\font_typewriter_opts \"semibold\"")
3068                 else:
3069                     # We need to use this regex since split() does not handle quote protection
3070                     ttopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3071                     document.header[x] = "\\font_typewriter_opts \"semibold, " + ttopts[1].strip('"') + "\""
3072
3073
3074 def convert_NotoRegulars(document):
3075     " Merge diverse noto reagular fonts "
3076
3077     i = find_token(document.header, "\\font_roman", 0)
3078     if i == -1:
3079         document.warning("Malformed LyX document: Missing \\font_roman.")
3080     else:
3081         # We need to use this regex since split() does not handle quote protection
3082         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3083         roman = romanfont[1].strip('"')
3084         if roman == "NotoSerif-TLF":
3085             romanfont[1] = '"NotoSerifRegular"'
3086             document.header[i] = " ".join(romanfont)
3087
3088     i = find_token(document.header, "\\font_sans", 0)
3089     if i == -1:
3090         document.warning("Malformed LyX document: Missing \\font_sans.")
3091     else:
3092         # We need to use this regex since split() does not handle quote protection
3093         sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3094         sf = sffont[1].strip('"')
3095         if sf == "NotoSans-TLF":
3096             sffont[1] = '"NotoSansRegular"'
3097             document.header[i] = " ".join(sffont)
3098
3099     i = find_token(document.header, "\\font_typewriter", 0)
3100     if i == -1:
3101         document.warning("Malformed LyX document: Missing \\font_typewriter.")
3102     else:
3103         # We need to use this regex since split() does not handle quote protection
3104         ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3105         tt = ttfont[1].strip('"')
3106         if tt == "NotoMono-TLF":
3107             ttfont[1] = '"NotoMonoRegular"'
3108             document.header[i] = " ".join(ttfont)
3109
3110
3111 def convert_CrimsonProFont(document):
3112     " Handle CrimsonPro font definition to LaTeX "
3113
3114     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
3115         fm = createFontMapping(['CrimsonPro'])
3116         convert_fonts(document, fm, "lf")
3117
3118 def revert_CrimsonProFont(document):
3119     " Revert native CrimsonPro font definition to LaTeX "
3120
3121     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
3122         fontmap = dict()
3123         fm = createFontMapping(['CrimsonPro'])
3124         if revert_fonts(document, fm, fontmap, False, True):
3125             add_preamble_fonts(document, fontmap)
3126
3127
3128 def revert_pagesizes(document):
3129     " Revert new page sizes in memoir and KOMA to options "
3130
3131     if document.textclass != "memoir" and document.textclass[:2] != "scr":
3132         return
3133
3134     i = find_token(document.header, "\\use_geometry true", 0)
3135     if i != -1:
3136         return
3137
3138     defsizes = ["default", "custom", "letterpaper", "legalpaper", "executivepaper", "a4paper", "a5paper", "b5paper"]
3139
3140     i = find_token(document.header, "\\papersize", 0)
3141     if i == -1:
3142         document.warning("Malformed LyX document! Missing \\papersize header.")
3143         return
3144     val = get_value(document.header, "\\papersize", i)
3145     if val in defsizes:
3146         # nothing to do
3147         return
3148
3149     document.header[i] = "\\papersize default"
3150
3151     i = find_token(document.header, "\\options", 0)
3152     if i == -1:
3153         i = find_token(document.header, "\\textclass", 0)
3154         if i == -1:
3155             document.warning("Malformed LyX document! Missing \\textclass header.")
3156             return
3157         document.header.insert(i, "\\options " + val)
3158         return
3159     document.header[i] = document.header[i] + "," + val
3160
3161
3162 def convert_pagesizes(document):
3163     " Convert to new page sizes in memoir and KOMA to options "
3164
3165     if document.textclass != "memoir" and document.textclass[:3] != "scr":
3166         return
3167
3168     i = find_token(document.header, "\\use_geometry true", 0)
3169     if i != -1:
3170         return
3171
3172     defsizes = ["default", "custom", "letterpaper", "legalpaper", "executivepaper", "a4paper", "a5paper", "b5paper"]
3173
3174     i = find_token(document.header, "\\papersize", 0)
3175     if i == -1:
3176         document.warning("Malformed LyX document! Missing \\papersize header.")
3177         return
3178     val = get_value(document.header, "\\papersize", i)
3179     if val in defsizes:
3180         # nothing to do
3181         return
3182
3183     i = find_token(document.header, "\\use_geometry false", 0)
3184     if i != -1:
3185         # Maintain use of geometry
3186         document.header[1] = "\\use_geometry true"
3187
3188 def revert_komafontsizes(document):
3189     " Revert new font sizes in KOMA to options "
3190
3191     if document.textclass[:3] != "scr":
3192         return
3193
3194     i = find_token(document.header, "\\paperfontsize", 0)
3195     if i == -1:
3196         document.warning("Malformed LyX document! Missing \\paperfontsize header.")
3197         return
3198
3199     defsizes = ["default", "10", "11", "12"]
3200
3201     val = get_value(document.header, "\\paperfontsize", i)
3202     if val in defsizes:
3203         # nothing to do
3204         return
3205
3206     document.header[i] = "\\paperfontsize default"
3207
3208     fsize = "fontsize=" + val
3209
3210     i = find_token(document.header, "\\options", 0)
3211     if i == -1:
3212         i = find_token(document.header, "\\textclass", 0)
3213         if i == -1:
3214             document.warning("Malformed LyX document! Missing \\textclass header.")
3215             return
3216         document.header.insert(i, "\\options " + fsize)
3217         return
3218     document.header[i] = document.header[i] + "," + fsize
3219
3220
3221 def revert_dupqualicites(document):
3222     " Revert qualified citation list commands with duplicate keys to ERT "
3223
3224     # LyX 2.3 only supports qualified citation lists with unique keys. Thus,
3225     # we need to revert those with multiple uses of the same key.
3226
3227     # Get cite engine
3228     engine = "basic"
3229     i = find_token(document.header, "\\cite_engine", 0)
3230     if i == -1:
3231         document.warning("Malformed document! Missing \\cite_engine")
3232     else:
3233         engine = get_value(document.header, "\\cite_engine", i)
3234
3235     if not engine in ["biblatex", "biblatex-natbib"]:
3236         return
3237
3238     # Citation insets that support qualified lists, with their LaTeX code
3239     ql_citations = {
3240         "cite" : "cites",
3241         "Cite" : "Cites",
3242         "citet" : "textcites",
3243         "Citet" : "Textcites",
3244         "citep" : "parencites",
3245         "Citep" : "Parencites",
3246         "Footcite" : "Smartcites",
3247         "footcite" : "smartcites",
3248         "Autocite" : "Autocites",
3249         "autocite" : "autocites",
3250         }
3251
3252     i = 0
3253     while (True):
3254         i = find_token(document.body, "\\begin_inset CommandInset citation", i)
3255         if i == -1:
3256             break
3257         j = find_end_of_inset(document.body, i)
3258         if j == -1:
3259             document.warning("Can't find end of citation inset at line %d!!" %(i))
3260             i += 1
3261             continue
3262
3263         k = find_token(document.body, "LatexCommand", i, j)
3264         if k == -1:
3265             document.warning("Can't find LatexCommand for citation inset at line %d!" %(i))
3266             i = j + 1
3267             continue
3268
3269         cmd = get_value(document.body, "LatexCommand", k)
3270         if not cmd in list(ql_citations.keys()):
3271             i = j + 1
3272             continue
3273
3274         pres = find_token(document.body, "pretextlist", i, j)
3275         posts = find_token(document.body, "posttextlist", i, j)
3276         if pres == -1 and posts == -1:
3277             # nothing to do.
3278             i = j + 1
3279             continue
3280
3281         key = get_quoted_value(document.body, "key", i, j)
3282         if not key:
3283             document.warning("Citation inset at line %d does not have a key!" %(i))
3284             i = j + 1
3285             continue
3286
3287         keys = key.split(",")
3288         ukeys = list(set(keys))
3289         if len(keys) == len(ukeys):
3290             # no duplicates.
3291             i = j + 1
3292             continue
3293
3294         pretexts = get_quoted_value(document.body, "pretextlist", pres)
3295         posttexts = get_quoted_value(document.body, "posttextlist", posts)
3296
3297         pre = get_quoted_value(document.body, "before", i, j)
3298         post = get_quoted_value(document.body, "after", i, j)
3299         prelist = pretexts.split("\t")
3300         premap = dict()
3301         for pp in prelist:
3302             ppp = pp.split(" ", 1)
3303             val = ""
3304             if len(ppp) > 1:
3305                 val = ppp[1]
3306             else:
3307                 val = ""
3308             if ppp[0] in premap:
3309                 premap[ppp[0]] = premap[ppp[0]] + "\t" + val
3310             else:
3311                 premap[ppp[0]] = val
3312         postlist = posttexts.split("\t")
3313         postmap = dict()
3314         for pp in postlist:
3315             ppp = pp.split(" ", 1)
3316             val = ""
3317             if len(ppp) > 1:
3318                 val = ppp[1]
3319             else:
3320                 val = ""
3321             if ppp[0] in postmap:
3322                 postmap[ppp[0]] = postmap[ppp[0]] + "\t" + val
3323             else:
3324                 postmap[ppp[0]] = val
3325         # Replace known new commands with ERT
3326         if "(" in pre or ")" in pre:
3327             pre = "{" + pre + "}"
3328         if "(" in post or ")" in post:
3329             post = "{" + post + "}"
3330         res = "\\" + ql_citations[cmd]
3331         if pre:
3332             res += "(" + pre + ")"
3333         if post:
3334             res += "(" + post + ")"
3335         elif pre:
3336             res += "()"
3337         for kk in keys:
3338             if premap.get(kk, "") != "":
3339                 akeys = premap[kk].split("\t", 1)
3340                 akey = akeys[0]
3341                 if akey != "":
3342                     res += "[" + akey + "]"
3343                 if len(akeys) > 1:
3344                     premap[kk] = "\t".join(akeys[1:])
3345                 else:
3346                     premap[kk] = ""
3347             if postmap.get(kk, "") != "":
3348                 akeys = postmap[kk].split("\t", 1)
3349                 akey = akeys[0]
3350                 if akey != "":
3351                     res += "[" + akey + "]"
3352                 if len(akeys) > 1:
3353                     postmap[kk] = "\t".join(akeys[1:])
3354                 else:
3355                     postmap[kk] = ""
3356             elif premap.get(kk, "") != "":
3357                 res += "[]"
3358             res += "{" + kk + "}"
3359         document.body[i:j+1] = put_cmd_in_ert([res])
3360
3361
3362 def convert_pagesizenames(document):
3363     " Convert LyX page sizes names "
3364
3365     i = find_token(document.header, "\\papersize", 0)
3366     if i == -1:
3367         document.warning("Malformed LyX document! Missing \\papersize header.")
3368         return
3369     oldnames = ["letterpaper", "legalpaper", "executivepaper", \
3370                 "a0paper", "a1paper", "a2paper", "a3paper", "a4paper", "a5paper", "a6paper", \
3371                 "b0paper", "b1paper", "b2paper", "b3paper", "b4paper", "b5paper", "b6paper", \
3372                 "c0paper", "c1paper", "c2paper", "c3paper", "c4paper", "c5paper", "c6paper"]
3373     val = get_value(document.header, "\\papersize", i)
3374     if val in oldnames:
3375         newval = val.replace("paper", "")
3376         document.header[i] = "\\papersize " + newval
3377
3378 def revert_pagesizenames(document):
3379     " Convert LyX page sizes names "
3380
3381     i = find_token(document.header, "\\papersize", 0)
3382     if i == -1:
3383         document.warning("Malformed LyX document! Missing \\papersize header.")
3384         return
3385     newnames = ["letter", "legal", "executive", \
3386                 "a0", "a1", "a2", "a3", "a4", "a5", "a6", \
3387                 "b0", "b1", "b2", "b3", "b4", "b5", "b6", \
3388                 "c0", "c1", "c2", "c3", "c4", "c5", "c6"]
3389     val = get_value(document.header, "\\papersize", i)
3390     if val in newnames:
3391         newval = val + "paper"
3392         document.header[i] = "\\papersize " + newval
3393
3394
3395 def revert_theendnotes(document):
3396     " Reverts native support of \\theendnotes to TeX-code "
3397
3398     if not "endnotes" in document.get_module_list() and not "foottoend" in document.get_module_list():
3399         return
3400
3401     i = 0
3402     while True:
3403         i = find_token(document.body, "\\begin_inset FloatList endnote", i + 1)
3404         if i == -1:
3405             return
3406         j = find_end_of_inset(document.body, i)
3407         if j == -1:
3408             document.warning("Malformed LyX document: Can't find end of FloatList inset")
3409             continue
3410
3411         document.body[i : j + 1] = put_cmd_in_ert("\\theendnotes")
3412
3413
3414 def revert_enotez(document):
3415     " Reverts native support of enotez package to TeX-code "
3416
3417     if not "enotez" in document.get_module_list() and not "foottoenotez" in document.get_module_list():
3418         return
3419
3420     use = False
3421     if find_token(document.body, "\\begin_inset Flex Endnote", 0) != -1:
3422         use = True
3423
3424     revert_flex_inset(document.body, "Endnote", "\\endnote")
3425
3426     i = 0
3427     while True:
3428         i = find_token(document.body, "\\begin_inset FloatList endnote", i + 1)
3429         if i == -1:
3430             break
3431         j = find_end_of_inset(document.body, i)
3432         if j == -1:
3433             document.warning("Malformed LyX document: Can't find end of FloatList inset")
3434             continue
3435
3436         use = True
3437         document.body[i : j + 1] = put_cmd_in_ert("\\printendnotes")
3438
3439     if use:
3440         add_to_preamble(document, ["\\usepackage{enotez}"])
3441     document.del_module("enotez")
3442     document.del_module("foottoenotez")
3443
3444
3445 def revert_memoir_endnotes(document):
3446     " Reverts native support of memoir endnotes to TeX-code "
3447
3448     if document.textclass != "memoir":
3449         return
3450
3451     encommand = "\\pagenote"
3452     modules = document.get_module_list()
3453     if "enotez" in modules or "foottoenotez" in modules or "endnotes" in modules or "foottoend" in modules:
3454         encommand = "\\endnote"
3455
3456     revert_flex_inset(document.body, "Endnote", encommand)
3457
3458     i = 0
3459     while True:
3460         i = find_token(document.body, "\\begin_inset FloatList pagenote", i + 1)
3461         if i == -1:
3462             break
3463         j = find_end_of_inset(document.body, i)
3464         if j == -1:
3465             document.warning("Malformed LyX document: Can't find end of FloatList inset")
3466             continue
3467
3468         if document.body[i] == "\\begin_inset FloatList pagenote*":
3469             document.body[i : j + 1] = put_cmd_in_ert("\\printpagenotes*")
3470         else:
3471             document.body[i : j + 1] = put_cmd_in_ert("\\printpagenotes")
3472         add_to_preamble(document, ["\\makepagenote"])
3473
3474
3475 def revert_totalheight(document):
3476     " Reverts graphics height parameter from totalheight to height "
3477
3478     relative_heights = {
3479         "\\textwidth" : "text%",
3480         "\\columnwidth" : "col%",
3481         "\\paperwidth" : "page%",
3482         "\\linewidth" : "line%",
3483         "\\textheight" : "theight%",
3484         "\\paperheight" : "pheight%",
3485         "\\baselineskip " : "baselineskip%"
3486     }
3487     i = 0
3488     while (True):
3489         i = find_token(document.body, "\\begin_inset Graphics", i)
3490         if i == -1:
3491             break
3492         j = find_end_of_inset(document.body, i)
3493         if j == -1:
3494             document.warning("Can't find end of graphics inset at line %d!!" %(i))
3495             i += 1
3496             continue
3497
3498         rx = re.compile(r'\s*special\s*(\S+)$')
3499         rxx = re.compile(r'(\d*\.*\d+)(\S+)$')
3500         k = find_re(document.body, rx, i, j)
3501         special = ""
3502         oldheight = ""
3503         if k != -1:
3504             m = rx.match(document.body[k])
3505             if m:
3506                 special = m.group(1)
3507             mspecial = special.split(',')
3508             for spc in mspecial:
3509                 if spc.startswith("height="):
3510                     oldheight = spc.split('=')[1]
3511                     ms = rxx.search(oldheight)
3512                     if ms:
3513                         oldunit = ms.group(2)
3514                         if oldunit in list(relative_heights.keys()):
3515                             oldval = str(float(ms.group(1)) * 100)
3516                             oldunit = relative_heights[oldunit]
3517                             oldheight = oldval + oldunit
3518                     mspecial.remove(spc)
3519                     break
3520             if len(mspecial) > 0:
3521                 special = ",".join(mspecial)
3522             else:
3523                 special = ""
3524
3525         rx = re.compile(r'(\s*height\s*)(\S+)$')
3526         kk = find_re(document.body, rx, i, j)
3527         if kk != -1:
3528             m = rx.match(document.body[kk])
3529             val = ""
3530             if m:
3531                 val = m.group(2)
3532                 if k != -1:
3533                     if special != "":
3534                         val = val + "," + special
3535                     document.body[k] = "\tspecial " + "totalheight=" + val
3536                 else:
3537                     document.body.insert(kk, "\tspecial totalheight=" + val)
3538                 if oldheight != "":
3539                     document.body[kk] = m.group(1) + oldheight
3540                 else:
3541                     del document.body[kk]
3542         elif oldheight != "":
3543             if special != "":
3544                 document.body[k] = "\tspecial " + special
3545                 document.body.insert(k, "\theight " + oldheight)
3546             else:
3547                 document.body[k] = "\theight " + oldheight
3548         i = j + 1
3549
3550
3551 def convert_totalheight(document):
3552     " Converts graphics height parameter from totalheight to height "
3553
3554     relative_heights = {
3555         "text%" : "\\textwidth",
3556         "col%"  : "\\columnwidth",
3557         "page%" : "\\paperwidth",
3558         "line%" : "\\linewidth",
3559         "theight%" : "\\textheight",
3560         "pheight%" : "\\paperheight",
3561         "baselineskip%" : "\\baselineskip"
3562     }
3563     i = 0
3564     while (True):
3565         i = find_token(document.body, "\\begin_inset Graphics", i)
3566         if i == -1:
3567             break
3568         j = find_end_of_inset(document.body, i)
3569         if j == -1:
3570             document.warning("Can't find end of graphics inset at line %d!!" %(i))
3571             i += 1
3572             continue
3573
3574         rx = re.compile(r'\s*special\s*(\S+)$')
3575         k = find_re(document.body, rx, i, j)
3576         special = ""
3577         newheight = ""
3578         if k != -1:
3579             m = rx.match(document.body[k])
3580             if m:
3581                 special = m.group(1)
3582             mspecial = special.split(',')
3583             for spc in mspecial:
3584                 if spc[:12] == "totalheight=":
3585                     newheight = spc.split('=')[1]
3586                     mspecial.remove(spc)
3587                     break
3588             if len(mspecial) > 0:
3589                 special = ",".join(mspecial)
3590             else:
3591                 special = ""
3592
3593         rx = re.compile(r'(\s*height\s*)(\d+\.?\d*)(\S+)$')
3594         kk = find_re(document.body, rx, i, j)
3595         if kk != -1:
3596             m = rx.match(document.body[kk])
3597             val = ""
3598             if m:
3599                 val = m.group(2)
3600                 unit = m.group(3)
3601                 if unit in list(relative_heights.keys()):
3602                     val = str(float(val) / 100)
3603                     unit = relative_heights[unit]
3604                 if k != -1:
3605                     if special != "":
3606                         val = val + unit + "," + special
3607                     document.body[k] = "\tspecial " + "height=" + val
3608                 else:
3609                     document.body.insert(kk + 1, "\tspecial height=" + val + unit)
3610                 if newheight != "":
3611                     document.body[kk] = m.group(1) + newheight
3612                 else:
3613                     del document.body[kk]
3614         elif newheight != "":
3615             document.body.insert(k, "\theight " + newheight)
3616         i = j + 1
3617
3618
3619 def convert_changebars(document):
3620     " Converts the changebars module to native solution "
3621
3622     if not "changebars" in document.get_module_list():
3623         return
3624
3625     i = find_token(document.header, "\\output_changes", 0)
3626     if i == -1:
3627         document.warning("Malformed LyX document! Missing \\output_changes header.")
3628         document.del_module("changebars")
3629         return
3630
3631     document.header.insert(i, "\\change_bars true")
3632     document.del_module("changebars")
3633
3634
3635 def revert_changebars(document):
3636     " Converts native changebar param to module "
3637
3638     i = find_token(document.header, "\\change_bars", 0)
3639     if i == -1:
3640         document.warning("Malformed LyX document! Missing \\change_bars header.")
3641         return
3642
3643     val = get_value(document.header, "\\change_bars", i)
3644
3645     if val == "true":
3646         document.add_module("changebars")
3647
3648     del document.header[i]
3649
3650
3651 def convert_postpone_fragile(document):
3652     " Adds false \\postpone_fragile_content buffer param "
3653
3654     i = find_token(document.header, "\\output_changes", 0)
3655     if i == -1:
3656         document.warning("Malformed LyX document! Missing \\output_changes header.")
3657         return
3658     # Set this to false for old documents (see #2154)
3659     document.header.insert(i, "\\postpone_fragile_content false")
3660
3661
3662 def revert_postpone_fragile(document):
3663     " Remove \\postpone_fragile_content buffer param "
3664
3665     i = find_token(document.header, "\\postpone_fragile_content", 0)
3666     if i == -1:
3667         document.warning("Malformed LyX document! Missing \\postpone_fragile_content.")
3668         return
3669
3670     del document.header[i]
3671
3672
3673 def revert_colrow_tracking(document):
3674     " Remove change tag from tabular columns/rows "
3675     i = 0
3676     while True:
3677         i = find_token(document.body, "\\begin_inset Tabular", i+1)
3678         if i == -1:
3679             return
3680         j = find_end_of_inset(document.body, i+1)
3681         if j == -1:
3682             document.warning("Malformed LyX document: Could not find end of tabular.")
3683             continue
3684         for k in range(i, j):
3685             m = re.search('^<column.*change="([^"]+)".*>$', document.body[k])
3686             if m:
3687                 document.body[k] = document.body[k].replace(' change="' + m.group(1) + '"', '')
3688             m = re.search('^<row.*change="([^"]+)".*>$', document.body[k])
3689             if m:
3690                 document.body[k] = document.body[k].replace(' change="' + m.group(1) + '"', '')
3691
3692
3693 def convert_counter_maintenance(document):
3694     " Convert \\maintain_unincluded_children buffer param from boolean value tro tristate "
3695
3696     i = find_token(document.header, "\\maintain_unincluded_children", 0)
3697     if i == -1:
3698         document.warning("Malformed LyX document! Missing \\maintain_unincluded_children.")
3699         return
3700
3701     val = get_value(document.header, "\\maintain_unincluded_children", i)
3702
3703     if val == "true":
3704         document.header[i] = "\\maintain_unincluded_children strict"
3705     else:
3706         document.header[i] = "\\maintain_unincluded_children no"
3707
3708
3709 def revert_counter_maintenance(document):
3710     " Revert \\maintain_unincluded_children buffer param to previous boolean value "
3711
3712     i = find_token(document.header, "\\maintain_unincluded_children", 0)
3713     if i == -1:
3714         document.warning("Malformed LyX document! Missing \\maintain_unincluded_children.")
3715         return
3716
3717     val = get_value(document.header, "\\maintain_unincluded_children", i)
3718
3719     if val == "no":
3720         document.header[i] = "\\maintain_unincluded_children false"
3721     else:
3722         document.header[i] = "\\maintain_unincluded_children true"
3723
3724
3725 def revert_counter_inset(document):
3726     " Revert counter inset to ERT, where possible"
3727     i = 0
3728     needed_counters = {}
3729     while True:
3730         i = find_token(document.body, "\\begin_inset CommandInset counter", i)
3731         if i == -1:
3732             break
3733         j = find_end_of_inset(document.body, i)
3734         if j == -1:
3735             document.warning("Can't find end of counter inset at line %d!" % i)
3736             i += 1
3737             continue
3738         lyx = get_quoted_value(document.body, "lyxonly", i, j)
3739         if lyx == "true":
3740             # there is nothing we can do to affect the LyX counters
3741             document.body[i : j + 1] = []
3742             i = j + 1
3743             continue
3744         cnt = get_quoted_value(document.body, "counter", i, j)
3745         if not cnt:
3746             document.warning("No counter given for inset at line %d!" % i)
3747             i = j + 1
3748             continue
3749
3750         cmd = get_quoted_value(document.body, "LatexCommand", i, j)
3751         document.warning(cmd)
3752         ert = ""
3753         if cmd == "set":
3754             val = get_quoted_value(document.body, "value", i, j)
3755             if not val:
3756                 document.warning("Can't convert counter inset at line %d!" % i)
3757             else:
3758                 ert = put_cmd_in_ert("\\setcounter{%s}{%s}" % (cnt, val))
3759         elif cmd == "addto":
3760             val = get_quoted_value(document.body, "value", i, j)
3761             if not val:
3762                 document.warning("Can't convert counter inset at line %d!" % i)
3763             else:
3764                 ert = put_cmd_in_ert("\\addtocounter{%s}{%s}" % (cnt, val))
3765         elif cmd == "reset":
3766             ert = put_cmd_in_ert("\\setcounter{%s}{0}" % (cnt))
3767         elif cmd == "save":
3768             needed_counters[cnt] = 1
3769             savecnt = "LyXSave" + cnt
3770             ert = put_cmd_in_ert("\\setcounter{%s}{\\value{%s}}" % (savecnt, cnt))
3771         elif cmd == "restore":
3772             needed_counters[cnt] = 1
3773             savecnt = "LyXSave" + cnt
3774             ert = put_cmd_in_ert("\\setcounter{%s}{\\value{%s}}" % (cnt, savecnt))
3775         else:
3776             document.warning("Unknown counter command `%s' in inset at line %d!" % (cnt, i))
3777
3778         if ert:
3779             document.body[i : j + 1] = ert
3780         i += 1
3781         continue
3782
3783     pretext = []
3784     for cnt in needed_counters:
3785         pretext.append("\\newcounter{LyXSave%s}" % (cnt))
3786     if pretext:
3787         add_to_preamble(document, pretext)
3788
3789
3790 def revert_ams_spaces(document):
3791     "Revert InsetSpace medspace and thickspace into their TeX-code counterparts"
3792     Found = False
3793     insets = ["\\medspace{}", "\\thickspace{}"]
3794     for inset in insets:
3795         i = 0
3796         i = find_token(document.body, "\\begin_inset space " + inset, i)
3797         if i == -1:
3798             continue
3799         end = find_end_of_inset(document.body, i)
3800         subst = put_cmd_in_ert(inset)
3801         document.body[i : end + 1] = subst
3802         Found = True
3803
3804     if Found == True:
3805         # load amsmath in the preamble if not already loaded
3806         i = find_token(document.header, "\\use_package amsmath 2", 0)
3807         if i == -1:
3808             add_to_preamble(document, ["\\@ifundefined{thickspace}{\\usepackage{amsmath}}{}"])
3809             return
3810
3811
3812 def convert_parskip(document):
3813     " Move old parskip settings to preamble "
3814
3815     i = find_token(document.header, "\\paragraph_separation skip", 0)
3816     if i == -1:
3817         return
3818
3819     j = find_token(document.header, "\\defskip", 0)
3820     if j == -1:
3821         document.warning("Malformed LyX document! Missing \\defskip.")
3822         return
3823
3824     val = get_value(document.header, "\\defskip", j)
3825
3826     skipval = "\\medskipamount"
3827     if val == "smallskip" or val == "medskip" or val == "bigskip":
3828         skipval = "\\" + val + "amount"
3829     else:
3830         skipval = val
3831
3832     add_to_preamble(document, ["\\setlength{\\parskip}{" + skipval + "}", "\\setlength{\\parindent}{0pt}"])
3833
3834     document.header[i] = "\\paragraph_separation indent"
3835     document.header[j] = "\\paragraph_indentation default"
3836
3837
3838 def revert_parskip(document):
3839     " Revert new parskip settings to preamble "
3840
3841     i = find_token(document.header, "\\paragraph_separation skip", 0)
3842     if i == -1:
3843         return
3844
3845     j = find_token(document.header, "\\defskip", 0)
3846     if j == -1:
3847         document.warning("Malformed LyX document! Missing \\defskip.")
3848         return
3849
3850     val = get_value(document.header, "\\defskip", j)
3851
3852     skipval = ""
3853     if val == "smallskip" or val == "medskip" or val == "bigskip":
3854         skipval = "[skip=\\" + val + "amount]"
3855     elif val == "fullline":
3856         skipval = "[skip=\\baselineskip]"
3857     elif val != "halfline":
3858         skipval = "[skip={" + val + "}]"
3859
3860     add_to_preamble(document, ["\\usepackage" + skipval + "{parskip}"])
3861
3862     document.header[i] = "\\paragraph_separation indent"
3863     document.header[j] = "\\paragraph_indentation default"
3864
3865
3866 def revert_line_vspaces(document):
3867     " Revert fulline and halfline vspaces to TeX "
3868     insets = {
3869         "fullline*" : "\\vspace*{\\baselineskip}",
3870         "fullline" : "\\vspace{\\baselineskip}",
3871         "halfline*" : "\\vspace*{0.5\\baselineskip}",
3872         "halfline" : "\\vspace{0.5\\baselineskip}",
3873         }
3874     for inset in insets.keys():
3875         i = 0
3876         i = find_token(document.body, "\\begin_inset VSpace " + inset, i)
3877         if i == -1:
3878             continue
3879         end = find_end_of_inset(document.body, i)
3880         subst = put_cmd_in_ert(insets[inset])
3881         document.body[i : end + 1] = subst
3882
3883 def convert_libertinus_rm_fonts(document):
3884     """Handle Libertinus serif fonts definition to LaTeX"""
3885
3886     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
3887         fm = createFontMapping(['Libertinus'])
3888         convert_fonts(document, fm)
3889
3890 def revert_libertinus_rm_fonts(document):
3891     """Revert Libertinus serif font definition to LaTeX"""
3892
3893     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
3894         fontmap = dict()
3895         fm = createFontMapping(['libertinus'])
3896         if revert_fonts(document, fm, fontmap):
3897             add_preamble_fonts(document, fontmap)
3898
3899 def revert_libertinus_sftt_fonts(document):
3900     " Revert Libertinus sans and tt font definitions to LaTeX "
3901
3902     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
3903         # first sf font
3904         i = find_token(document.header, "\\font_sans \"LibertinusSans-LF\"", 0)
3905         if i != -1:
3906             j = find_token(document.header, "\\font_sans_osf true", 0)
3907             if j != -1:
3908                 add_to_preamble(document, ["\\renewcommand{\\sfdefault}{LibertinusSans-OsF}"])
3909                 document.header[j] = "\\font_sans_osf false"
3910             else:
3911                 add_to_preamble(document, ["\\renewcommand{\\sfdefault}{LibertinusSans-LF}"])
3912             document.header[i] = document.header[i].replace("LibertinusSans-LF", "default")
3913             sf_scale = 100.0
3914             sfval = find_token(document.header, "\\font_sf_scale", 0)
3915             if sfval == -1:
3916                 document.warning("Malformed LyX document: Missing \\font_sf_scale.")
3917             else:
3918                 sfscale = document.header[sfval].split()
3919                 val = sfscale[1]
3920                 sfscale[1] = "100"
3921                 document.header[sfval] = " ".join(sfscale)
3922                 try:
3923                     # float() can throw
3924                     sf_scale = float(val)
3925                 except:
3926                     document.warning("Invalid font_sf_scale value: " + val)
3927                 if sf_scale != "100.0":
3928                     add_to_preamble(document, ["\\renewcommand*{\\LibertinusSans@scale}{" + str(sf_scale / 100.0) + "}"])
3929         # now tt font
3930         i = find_token(document.header, "\\font_typewriter \"LibertinusMono-TLF\"", 0)
3931         if i != -1:
3932             add_to_preamble(document, ["\\renewcommand{\\ttdefault}{LibertinusMono-TLF}"])
3933             document.header[i] = document.header[i].replace("LibertinusMono-TLF", "default")
3934             tt_scale = 100.0
3935             ttval = find_token(document.header, "\\font_tt_scale", 0)
3936             if ttval == -1:
3937                 document.warning("Malformed LyX document: Missing \\font_tt_scale.")
3938             else:
3939                 ttscale = document.header[ttval].split()
3940                 val = ttscale[1]
3941                 ttscale[1] = "100"
3942                 document.header[ttval] = " ".join(ttscale)
3943                 try:
3944                     # float() can throw
3945                     tt_scale = float(val)
3946                 except:
3947                     document.warning("Invalid font_tt_scale value: " + val)
3948                 if tt_scale != "100.0":
3949                     add_to_preamble(document, ["\\renewcommand*{\\LibertinusMono@scale}{" + str(tt_scale / 100.0) + "}"])
3950
3951
3952 def revert_docbook_table_output(document):
3953     i = find_token(document.header, '\\docbook_table_output')
3954     if i != -1:
3955         del document.header[i]
3956
3957
3958 def revert_nopagebreak(document):
3959     while True:
3960         i = find_token(document.body, "\\begin_inset Newpage nopagebreak")
3961         if i == -1:
3962             return
3963         end = find_end_of_inset(document.body, i)
3964         if end == 1:
3965             document.warning("Malformed LyX document: Could not find end of Newpage inset.")
3966             continue
3967         subst = put_cmd_in_ert("\\nopagebreak{}")
3968         document.body[i : end + 1] = subst
3969
3970
3971 def revert_hrquotes(document):
3972     " Revert Hungarian Quotation marks "
3973     
3974     i = find_token(document.header, "\\quotes_style hungarian", 0)
3975     if i != -1:
3976         document.header[i] = "\\quotes_style polish"
3977
3978     i = 0
3979     while True:
3980         i = find_token(document.body, "\\begin_inset Quotes h")
3981         if i == -1:
3982             return
3983         if document.body[i] == "\\begin_inset Quotes hld":
3984             document.body[i] = "\\begin_inset Quotes pld"
3985         elif document.body[i] == "\\begin_inset Quotes hrd":
3986             document.body[i] = "\\begin_inset Quotes prd"
3987         elif document.body[i] == "\\begin_inset Quotes hls":
3988             document.body[i] = "\\begin_inset Quotes ald"
3989         elif document.body[i] == "\\begin_inset Quotes hrs":
3990             document.body[i] = "\\begin_inset Quotes ard"
3991
3992
3993 def convert_math_refs(document):
3994     i = 0
3995     while True:
3996         i = find_token(document.body, "\\begin_inset Formula", i)
3997         if i == -1:
3998             break
3999         j = find_end_of_inset(document.body, i)
4000         if j == -1:
4001             document.warning("Can't find end of inset at line %d of body!" % i)
4002             i += 1
4003             continue
4004         while i < j:
4005             document.body[i] = document.body[i].replace("\\prettyref", "\\formatted")
4006             i += 1
4007         
4008
4009 def revert_math_refs(document):
4010     i = 0
4011     while True:
4012         i = find_token(document.body, "\\begin_inset Formula", i)
4013         if i == -1:
4014             break
4015         j = find_end_of_inset(document.body, i)
4016         if j == -1:
4017             document.warning("Can't find end of inset at line %d of body!" % i)
4018             i += 1
4019             continue
4020         while i < j:
4021             document.body[i] = document.body[i].replace("\\formatted", "\\prettyref")
4022             if "\\labelonly" in document.body[i]:
4023                 document.body[i] = re.sub("\\\\labelonly{([^}]+?)}", "\\1", document.body[i])
4024             i += 1
4025
4026
4027 def convert_branch_colors(document):
4028     " Convert branch colors to semantic values "
4029
4030     i = 0
4031     while True:
4032         i = find_token(document.header, "\\branch", i)
4033         if i == -1:
4034             break
4035         j = find_token(document.header, "\\end_branch", i)
4036         if j == -1:
4037            document.warning("Malformed LyX document. Can't find end of branch definition!")
4038            break
4039         # We only support the standard LyX background for now
4040         k = find_token(document.header, "\\color #faf0e6", i, j)
4041         if k != -1:
4042            document.header[k] = "\\color background"
4043         i += 1
4044
4045
4046 def revert_branch_colors(document):
4047     " Revert semantic branch colors "
4048
4049     i = 0
4050     while True:
4051         i = find_token(document.header, "\\branch", i)
4052         if i == -1:
4053             break
4054         j = find_token(document.header, "\\end_branch", i)
4055         if j == -1:
4056            document.warning("Malformed LyX document. Can't find end of branch definition!")
4057            break
4058         k = find_token(document.header, "\\color", i, j)
4059         if k != -1:
4060            bcolor = get_value(document.header, "\\color", k)
4061            if bcolor[1] != "#":
4062                # this will be read as background by LyX 2.3
4063                document.header[k] = "\\color none"
4064         i += 1
4065
4066
4067 def revert_darkmode_graphics(document):
4068     " Revert darkModeSensitive InsetGraphics param "
4069
4070     i = 0
4071     while (True):
4072         i = find_token(document.body, "\\begin_inset Graphics", i)
4073         if i == -1:
4074             break
4075         j = find_end_of_inset(document.body, i)
4076         if j == -1:
4077             document.warning("Can't find end of graphics inset at line %d!!" %(i))
4078             i += 1
4079             continue
4080         k = find_token(document.body, "\tdarkModeSensitive", i, j)
4081         if k != -1:
4082             del document.body[k]
4083         i += 1
4084
4085
4086 def revert_branch_darkcols(document):
4087     " Revert dark branch colors "
4088
4089     i = 0
4090     while True:
4091         i = find_token(document.header, "\\branch", i)
4092         if i == -1:
4093             break
4094         j = find_token(document.header, "\\end_branch", i)
4095         if j == -1:
4096            document.warning("Malformed LyX document. Can't find end of branch definition!")
4097            break
4098         k = find_token(document.header, "\\color", i, j)
4099         if k != -1:
4100             m = re.search('\\\\color (\\S+) (\\S+)', document.header[k])
4101             if m:
4102                 document.header[k] = "\\color " + m.group(1)
4103         i += 1
4104
4105
4106 def revert_vcolumns2(document):
4107     """Revert varwidth columns with line breaks etc."""
4108     i = 0
4109     needvarwidth = False
4110     needarray = False
4111     needcellvarwidth = False
4112     try:
4113         while True:
4114             i = find_token(document.body, "\\begin_inset Tabular", i+1)
4115             if i == -1:
4116                 return
4117             j = find_end_of_inset(document.body, i)
4118             if j == -1:
4119                 document.warning("Malformed LyX document: Could not find end of tabular.")
4120                 continue
4121
4122             # Collect necessary column information
4123             m = i + 1
4124             nrows = int(document.body[i+1].split('"')[3])
4125             ncols = int(document.body[i+1].split('"')[5])
4126             col_info = []
4127             for k in range(ncols):
4128                 m = find_token(document.body, "<column", m)
4129                 width = get_option_value(document.body[m], 'width')
4130                 varwidth = get_option_value(document.body[m], 'varwidth')
4131                 alignment = get_option_value(document.body[m], 'alignment')
4132                 valignment = get_option_value(document.body[m], 'valignment')
4133                 special = get_option_value(document.body[m], 'special')
4134                 col_info.append([width, varwidth, alignment, valignment, special, m])
4135                 m += 1
4136
4137             # Now parse cells
4138             m = i + 1
4139             for row in range(nrows):
4140                 for col in range(ncols):
4141                     m = find_token(document.body, "<cell", m)
4142                     multicolumn = get_option_value(document.body[m], 'multicolumn') != ""
4143                     multirow = get_option_value(document.body[m], 'multirow') != ""
4144                     fixedwidth = get_option_value(document.body[m], 'width') != ""
4145                     rotate = get_option_value(document.body[m], 'rotate')
4146                     cellalign = get_option_value(document.body[m], 'alignment')
4147                     cellvalign = get_option_value(document.body[m], 'valignment')
4148                     # Check for: linebreaks, multipars, non-standard environments
4149                     begcell = m
4150                     endcell = find_token(document.body, "</cell>", begcell)
4151                     vcand = False
4152                     if find_token(document.body, "\\begin_inset Newline", begcell, endcell) != -1:
4153                         vcand = not fixedwidth
4154                     elif count_pars_in_inset(document.body, begcell + 2) > 1:
4155                         vcand = not fixedwidth
4156                     elif get_value(document.body, "\\begin_layout", begcell) != "Plain Layout":
4157                         vcand = not fixedwidth
4158                     colalignment = col_info[col][2]
4159                     colvalignment = col_info[col][3]
4160                     if vcand:
4161                         if rotate == "" and ((colalignment == "left" and colvalignment == "top") or (multicolumn == True and cellalign == "left" and cellvalign == "top")):
4162                             if col_info[col][0] == "" and col_info[col][1] == "" and col_info[col][4] == "":
4163                                 needvarwidth = True
4164                                 col_line = col_info[col][5]
4165                                 needarray = True
4166                                 vval = "V{\\linewidth}"
4167                                 if multicolumn:
4168                                     document.body[m] = document.body[m][:-1] + " special=\"" + vval + "\">"
4169                                 else:
4170                                     document.body[col_line] = document.body[col_line][:-1] + " special=\"" + vval + "\">"
4171                         else:
4172                             alarg = ""
4173                             if multicolumn or multirow:
4174                                 if cellvalign == "middle":
4175                                     alarg = "[m]"
4176                                 elif cellvalign == "bottom":
4177                                     alarg = "[b]"
4178                             else:
4179                                 if colvalignment == "middle":
4180                                     alarg = "[m]"
4181                                 elif colvalignment == "bottom":
4182                                     alarg = "[b]"
4183                             flt = find_token(document.body, "\\begin_layout", begcell, endcell)
4184                             elt = find_token_backwards(document.body, "\\end_layout", endcell)
4185                             if flt != -1 and elt != -1:
4186                                 extralines = []
4187                                 # we need to reset character layouts if necessary
4188                                 el = find_token(document.body, '\\emph on', flt, elt)
4189                                 if el != -1:
4190                                     extralines.append("\\emph default")
4191                                 el = find_token(document.body, '\\noun on', flt, elt)
4192                                 if el != -1:
4193                                     extralines.append("\\noun default")
4194                                 el = find_token(document.body, '\\series', flt, elt)
4195                                 if el != -1:
4196                                     extralines.append("\\series default")
4197                                 el = find_token(document.body, '\\family', flt, elt)
4198                                 if el != -1:
4199                                     extralines.append("\\family default")
4200                                 el = find_token(document.body, '\\shape', flt, elt)
4201                                 if el != -1:
4202                                     extralines.append("\\shape default")
4203                                 el = find_token(document.body, '\\color', flt, elt)
4204                                 if el != -1:
4205                                     extralines.append("\\color inherit")
4206                                 el = find_token(document.body, '\\size', flt, elt)
4207                                 if el != -1:
4208                                     extralines.append("\\size default")
4209                                 el = find_token(document.body, '\\bar under', flt, elt)
4210                                 if el != -1:
4211                                     extralines.append("\\bar default")
4212                                 el = find_token(document.body, '\\uuline on', flt, elt)
4213                                 if el != -1:
4214                                     extralines.append("\\uuline default")
4215                                 el = find_token(document.body, '\\uwave on', flt, elt)
4216                                 if el != -1:
4217                                     extralines.append("\\uwave default")
4218                                 el = find_token(document.body, '\\strikeout on', flt, elt)
4219                                 if el != -1:
4220                                     extralines.append("\\strikeout default")
4221                                 document.body[elt:elt+1] = extralines + put_cmd_in_ert("\\end{cellvarwidth}") + [r"\end_layout"]
4222                                 parlang = -1
4223                                 for q in range(flt, elt):
4224                                     if document.body[q] != "" and document.body[q][0] != "\\":
4225                                         break
4226                                     if document.body[q][:5] == "\\lang":
4227                                         parlang = q
4228                                         break
4229                                 if parlang != -1:
4230                                     document.body[parlang+1:parlang+1] = put_cmd_in_ert("\\begin{cellvarwidth}" + alarg)
4231                                 else:
4232                                     document.body[flt+1:flt+1] = put_cmd_in_ert("\\begin{cellvarwidth}" + alarg)
4233                                 needcellvarwidth = True
4234                                 needvarwidth = True
4235                         # ERT newlines and linebreaks (since LyX < 2.4 automatically inserts parboxes
4236                         # with newlines, and we do not want that)
4237                         while True:
4238                             endcell = find_token(document.body, "</cell>", begcell)
4239                             linebreak = False
4240                             nl = find_token(document.body, "\\begin_inset Newline newline", begcell, endcell)
4241                             if nl == -1:
4242                                 nl = find_token(document.body, "\\begin_inset Newline linebreak", begcell, endcell)
4243                                 if nl == -1:
4244                                      break
4245                                 linebreak = True
4246                             nle = find_end_of_inset(document.body, nl)
4247                             del(document.body[nle:nle+1])
4248                             if linebreak:
4249                                 document.body[nl:nl+1] = put_cmd_in_ert("\\linebreak{}")
4250                             else:
4251                                 document.body[nl:nl+1] = put_cmd_in_ert("\\\\")
4252                         # Replace parbreaks in multirow with \\endgraf
4253                         if multirow == True:
4254                             flt = find_token(document.body, "\\begin_layout", begcell, endcell)
4255                             if flt != -1:
4256                                 while True:
4257                                     elt = find_end_of_layout(document.body, flt)
4258                                     if elt == -1:
4259                                         document.warning("Malformed LyX document! Missing layout end.")
4260                                         break
4261                                     endcell = find_token(document.body, "</cell>", begcell)
4262                                     flt = find_token(document.body, "\\begin_layout", elt, endcell)
4263                                     if flt == -1:
4264                                         break
4265                                     document.body[elt : flt + 1] = put_cmd_in_ert("\\endgraf{}")
4266                     m += 1
4267
4268             i = j
4269
4270     finally:
4271         if needarray == True:
4272             add_to_preamble(document, ["\\usepackage{array}"])
4273         if needcellvarwidth == True:
4274             add_to_preamble(document, ["%% Variable width box for table cells",
4275                                        "\\newenvironment{cellvarwidth}[1][t]",
4276                                        "    {\\begin{varwidth}[#1]{\\linewidth}}",
4277                                        "    {\\@finalstrut\\@arstrutbox\\end{varwidth}}"])
4278         if needvarwidth == True:
4279             add_to_preamble(document, ["\\usepackage{varwidth}"])
4280
4281
4282 def convert_vcolumns2(document):
4283     """Convert varwidth ERT to native"""
4284     i = 0
4285     try:
4286         while True:
4287             i = find_token(document.body, "\\begin_inset Tabular", i+1)
4288             if i == -1:
4289                 return
4290             j = find_end_of_inset(document.body, i)
4291             if j == -1:
4292                 document.warning("Malformed LyX document: Could not find end of tabular.")
4293                 continue
4294
4295             # Parse cells
4296             nrows = int(document.body[i+1].split('"')[3])
4297             ncols = int(document.body[i+1].split('"')[5])
4298             m = i + 1
4299             lines = []
4300             for row in range(nrows):
4301                 for col in range(ncols):
4302                     m = find_token(document.body, "<cell", m)
4303                     multirow = get_option_value(document.body[m], 'multirow') != ""
4304                     begcell = m
4305                     endcell = find_token(document.body, "</cell>", begcell)
4306                     vcand = False
4307                     cvw = find_token(document.body, "begin{cellvarwidth}", begcell, endcell)
4308                     if cvw != -1:
4309                         vcand = document.body[cvw - 1] == "\\backslash" and get_containing_inset(document.body, cvw)[0] == "ERT"
4310                     if vcand:
4311                         # Remove ERTs with cellvarwidth env
4312                         ecvw = find_token(document.body, "end{cellvarwidth}", begcell, endcell)
4313                         if ecvw != -1:
4314                             if document.body[ecvw - 1] == "\\backslash":
4315                                 eertins = get_containing_inset(document.body, ecvw)
4316                                 if eertins and eertins[0] == "ERT":
4317                                     del document.body[eertins[1] : eertins[2] + 1]
4318                              
4319                         cvw = find_token(document.body, "begin{cellvarwidth}", begcell, endcell)   
4320                         ertins = get_containing_inset(document.body, cvw)
4321                         if ertins and ertins[0] == "ERT":
4322                             del(document.body[ertins[1] : ertins[2] + 1])
4323                         
4324                         # Convert ERT newlines (as cellvarwidth detection relies on that)
4325                         while True:
4326                             endcell = find_token(document.body, "</cell>", begcell)
4327                             nl = find_token(document.body, "\\backslash", begcell, endcell)
4328                             if nl == -1 or document.body[nl + 2] != "\\backslash":
4329                                 break
4330                             ertins = get_containing_inset(document.body, nl)
4331                             if ertins and ertins[0] == "ERT":
4332                                 document.body[ertins[1] : ertins[2] + 1] = ["\\begin_inset Newline newline", "", "\\end_inset"]
4333
4334                         # Same for linebreaks
4335                         while True:
4336                             endcell = find_token(document.body, "</cell>", begcell)
4337                             nl = find_token(document.body, "linebreak", begcell, endcell)
4338                             if nl == -1 or document.body[nl - 1] != "\\backslash":
4339                                 break
4340                             ertins = get_containing_inset(document.body, nl)
4341                             if ertins and ertins[0] == "ERT":
4342                                 document.body[ertins[1] : ertins[2] + 1] = ["\\begin_inset Newline linebreak", "", "\\end_inset"]
4343
4344                         # And \\endgraf
4345                         if multirow == True:
4346                             endcell = find_token(document.body, "</cell>", begcell)
4347                             nl = find_token(document.body, "endgraf{}", begcell, endcell)
4348                             if nl == -1 or document.body[nl - 1] != "\\backslash":
4349                                 break
4350                             ertins = get_containing_inset(document.body, nl)
4351                             if ertins and ertins[0] == "ERT":
4352                                     document.body[ertins[1] : ertins[2] + 1] = ["\\end_layout", "", "\\begin_layout Plain Layout"]
4353                     m += 1
4354
4355             i += 1
4356
4357     finally:
4358         del_complete_lines(document.preamble,
4359                                 ['% Added by lyx2lyx',
4360                                  '%% Variable width box for table cells',
4361                                  r'\newenvironment{cellvarwidth}[1][t]',
4362                                  r'    {\begin{varwidth}[#1]{\linewidth}}',
4363                                  r'    {\@finalstrut\@arstrutbox\end{varwidth}}'])
4364         del_complete_lines(document.preamble,
4365                                 ['% Added by lyx2lyx',
4366                                  r'\usepackage{varwidth}'])
4367
4368
4369 frontispiece_def = [
4370     r'### Inserted by lyx2lyx (frontispiece layout) ###',
4371     r'Style Frontispiece',
4372     r'  CopyStyle             Titlehead',
4373     r'  LatexName             frontispiece',
4374     r'End',
4375 ]
4376
4377
4378 def convert_koma_frontispiece(document):
4379     """Remove local KOMA frontispiece definition"""
4380     if document.textclass[:3] != "scr":
4381         return
4382
4383     if document.del_local_layout(frontispiece_def):
4384         document.add_module("ruby")
4385
4386
4387 def revert_koma_frontispiece(document):
4388     """Add local KOMA frontispiece definition"""
4389     if document.textclass[:3] != "scr":
4390         return
4391
4392     if find_token(document.body, "\\begin_layout Frontispiece", 0) != -1:
4393         document.append_local_layout(frontispiece_def)
4394
4395
4396 def revert_spellchecker_ignore(document):
4397     """Revert document spellchecker dictionary"""
4398     while True:
4399         i = find_token(document.header, "\\spellchecker_ignore")
4400         if i == -1:
4401             return
4402         del document.header[i]
4403
4404
4405 def revert_docbook_mathml_prefix(document):
4406     """Revert the DocBook parameter to choose the prefix for the MathML name space"""
4407     while True:
4408         i = find_token(document.header, "\\docbook_mathml_prefix")
4409         if i == -1:
4410             return
4411         del document.header[i]
4412
4413
4414 def revert_document_metadata(document):
4415     """Revert document metadata"""
4416     i = 0
4417     while True:
4418         i = find_token(document.header, "\\begin_metadata", i)
4419         if i == -1:
4420             return
4421         j = find_end_of(document.header, i, "\\begin_metadata", "\\end_metadata")
4422         if j == -1:
4423             # this should not happen
4424             break
4425         document.header[i : j + 1] = []
4426
4427
4428 def revert_index_macros(document):
4429     " Revert inset index macros "
4430
4431     i = 0
4432     while True:
4433         # trailing blank needed here to exclude IndexMacro insets
4434         i = find_token(document.body, '\\begin_inset Index ', i+1)
4435         if i == -1:
4436             break
4437         j = find_end_of_inset(document.body, i)
4438         if j == -1:
4439             document.warning("Malformed LyX document: Can't find end of index inset at line %d" % i)
4440             continue
4441         pl = find_token(document.body, '\\begin_layout Plain Layout', i, j)
4442         if pl == -1:
4443             document.warning("Malformed LyX document: Can't find plain layout in index inset at line %d" % i)
4444             continue
4445         # find, store and remove inset params
4446         pr = find_token(document.body, 'range', i, pl)
4447         prval = get_quoted_value(document.body, "range", pr)
4448         pagerange = ""
4449         if prval == "start":
4450             pagerange = "("
4451         elif prval == "end":
4452             pagerange = ")"
4453         pf = find_token(document.body, 'pageformat', i, pl)
4454         pageformat = get_quoted_value(document.body, "pageformat", pf)
4455         del document.body[pr:pf+1]
4456         # Now re-find (potentially moved) inset end again, and search for subinsets
4457         j = find_end_of_inset(document.body, i)
4458         if j == -1:
4459             document.warning("Malformed LyX document: Can't find end of index inset at line %d" % i)
4460             continue
4461         # We search for all possible subentries in turn, store their
4462         # content and delete them
4463         see = []
4464         seealso = []
4465         subentry = []
4466         subentry2 = []
4467         sortkey = []
4468         # Two subentries are allowed, thus the duplication
4469         imacros = ["seealso", "see", "subentry", "subentry", "sortkey"]
4470         for imacro in imacros:
4471             iim = find_token(document.body, "\\begin_inset IndexMacro %s" % imacro, i, j)
4472             if iim == -1:
4473                 continue
4474             iime = find_end_of_inset(document.body, iim)
4475             if iime == -1:
4476                 document.warning("Malformed LyX document: Can't find end of index macro inset at line %d" % i)
4477                 continue
4478             iimpl = find_token(document.body, '\\begin_layout Plain Layout', iim, iime)
4479             if iimpl == -1:
4480                 document.warning("Malformed LyX document: Can't find plain layout in index macro inset at line %d" % i)
4481                 continue
4482             iimple = find_end_of_layout(document.body, iimpl)
4483             if iimple == -1:
4484                 document.warning("Malformed LyX document: Can't find end of index macro inset plain layout at line %d" % i)
4485                 continue
4486             icont = document.body[iimpl:iimple]
4487             if imacro == "seealso":
4488                 seealso = icont[1:]
4489             elif imacro == "see":
4490                 see = icont[1:]
4491             elif imacro == "subentry":
4492                 # subentries might hace their own sortkey!
4493                 xiim = find_token(document.body, "\\begin_inset IndexMacro sortkey", iimpl, iimple)
4494                 if xiim != -1:
4495                     xiime = find_end_of_inset(document.body, xiim)
4496                     if xiime == -1:
4497                         document.warning("Malformed LyX document: Can't find end of index macro inset at line %d" % i)
4498                     else:
4499                         xiimpl = find_token(document.body, '\\begin_layout Plain Layout', xiim, xiime)
4500                         if xiimpl == -1:
4501                             document.warning("Malformed LyX document: Can't find plain layout in index macro inset at line %d" % i)
4502                         else:
4503                             xiimple = find_end_of_layout(document.body, xiimpl)
4504                             if xiimple == -1:
4505                                 document.warning("Malformed LyX document: Can't find end of index macro inset plain layout at line %d" % i)
4506                             else:
4507                                 # the sortkey
4508                                 xicont = document.body[xiimpl+1:xiimple]
4509                                 # everything before ................... or after
4510                                 xxicont = document.body[iimpl+1:xiim] + document.body[xiime+1:iimple]
4511                                 # construct the latex sequence
4512                                 icont = xicont + put_cmd_in_ert("@") + xxicont[1:]
4513                 if len(subentry) > 0:
4514                     if (icont[0] == "\\begin_layout Plain Layout"):
4515                         subentry2 = icont[1:]
4516                     else:
4517                         subentry2 = icont
4518                 else:
4519                     if (icont[0] == "\\begin_layout Plain Layout"):
4520                         subentry = icont[1:]
4521                     else:
4522                         subentry = icont
4523             elif imacro == "sortkey":
4524                 sortkey = icont
4525             # Everything stored. Delete subinset.
4526             del document.body[iim:iime+1]
4527             # Again re-find (potentially moved) index inset end
4528             j = find_end_of_inset(document.body, i)
4529             if j == -1:
4530                 document.warning("Malformed LyX document: Can't find end of index inset at line %d" % i)
4531                 continue
4532         # Now insert all stuff, starting from the inset end
4533         pl = find_token(document.body, '\\begin_layout Plain Layout', i, j)
4534         if pl == -1:
4535             document.warning("Malformed LyX document: Can't find plain layout in index inset at line %d" % i)
4536             continue
4537         ple = find_end_of_layout(document.body, pl)
4538         if ple == -1:
4539             document.warning("Malformed LyX document: Can't find end of index macro inset plain layout at line %d" % i)
4540             continue
4541         if len(see) > 0:
4542             document.body[ple:ple] = put_cmd_in_ert("|" + pagerange + "see{") + see + put_cmd_in_ert("}")
4543         elif len(seealso) > 0:
4544             document.body[ple:ple] = put_cmd_in_ert("|" + pagerange + "seealso{") + seealso + put_cmd_in_ert("}")
4545         elif pageformat != "default":
4546             document.body[ple:ple] = put_cmd_in_ert("|" + pagerange + pageformat)
4547         if len(subentry2) > 0:
4548             document.body[ple:ple] = put_cmd_in_ert("!") + subentry2
4549         if len(subentry) > 0:
4550             document.body[ple:ple] = put_cmd_in_ert("!") + subentry
4551         if len(sortkey) > 0:
4552             document.body[pl:pl+1] = document.body[pl:pl] + sortkey + put_cmd_in_ert("@")
4553             
4554
4555 def revert_starred_refs(document):
4556     " Revert starred refs "
4557     i = find_token(document.header, "\\use_hyperref true", 0)
4558     use_hyperref = (i != -1)
4559     i = 0
4560     in_inset = False
4561     cmd = ref = ""
4562     nolink = False
4563     nolinkline = -1
4564     while True:
4565         if not in_inset:
4566             i = find_token(document.body, "\\begin_inset CommandInset ref", i)
4567             if i == -1:
4568                 break
4569             start = i
4570             end = find_end_of_inset(document.body, i)
4571             if end == -1:
4572                 document.warning("Malformed LyX document: Can't find end of inset at line %d" % i)
4573                 i += 1
4574                 continue
4575             # If we are not using hyperref, then we just need to delete the line
4576             if not use_hyperref:
4577                 k = find_token(document.body, "nolink", i, end)
4578                 if k == -1:
4579                     i = end
4580                     continue
4581                 del document.body[k]
4582                 i = end - 1
4583                 continue
4584             # If we are using hyperref, then we'll need to do more.
4585             in_inset = True
4586             i += 1
4587             continue
4588         # so we are in an InsetRef
4589         if i == end:
4590             in_inset = False
4591             # If nolink is False, just remove that line
4592             if nolink == False or cmd == "formatted" or cmd == "labelonly":
4593                 # document.warning("Skipping " + cmd + " " + ref)
4594                 if nolinkline != -1:
4595                     del document.body[nolinkline]
4596                     nolinkline = -1
4597                 continue
4598             # We need to construct a new command and put it in ERT
4599             newcmd = "\\" + cmd + "*{" + ref + "}"
4600             # document.warning(newcmd)
4601             newlines = put_cmd_in_ert(newcmd)
4602             document.body[start:end+1] = newlines
4603             i += len(newlines) - (end - start) + 1
4604             # reset variables
4605             cmd = ref = ""
4606             nolink = False
4607             nolinkline = -1
4608             continue
4609         l = document.body[i]
4610         if l.startswith("LatexCommand"):
4611             cmd = l[13:]
4612         elif l.startswith("reference"):
4613             ref = l[11:-1]
4614         elif l.startswith("nolink"):
4615             tmp = l[8:-1]
4616             nolink  = (tmp == "true")
4617             nolinkline = i
4618         i += 1
4619
4620
4621 def convert_starred_refs(document):
4622     " Convert starred refs "
4623     i = 0
4624     while True:
4625         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
4626         if i == -1:
4627             break
4628         end = find_end_of_inset(document.body, i)
4629         if end == -1:
4630             document.warning("Malformed LyX document: Can't find end of inset at line %d" % i)
4631             i += 1
4632             continue
4633         newlineat = end - 1
4634         document.body.insert(newlineat, "nolink \"false\"")
4635         i = end + 1
4636
4637
4638 def revert_familydefault(document):
4639     " Revert \\font_default_family for non-TeX fonts "
4640
4641     if find_token(document.header, "\\use_non_tex_fonts true", 0) == -1:
4642         return
4643  
4644     i = find_token(document.header, "\\font_default_family", 0)
4645     if i == -1:
4646         document.warning("Malformed LyX document: Can't find \\font_default_family header")
4647         return
4648  
4649     dfamily = get_value(document.header, "\\font_default_family", i)
4650     if dfamily == "default":
4651         return
4652         
4653     document.header[i] = "\\font_default_family default"
4654     add_to_preamble(document, ["\\renewcommand{\\familydefault}{\\" + dfamily + "}"])
4655
4656
4657 def convert_hyper_other(document):
4658     " Classify \"run:\" links as other "
4659
4660     i = 0
4661     while True:
4662         i = find_token(document.body, "\\begin_inset CommandInset href", i)
4663         if i == -1:
4664             break
4665         j = find_end_of_inset(document.body, i)
4666         if j == -1:
4667             document.warning("Cannot find end of inset at line " << str(i))
4668             i += 1
4669             continue
4670         k = find_token(document.body, "type \"", i, j)
4671         if k != -1:
4672             # not a "Web" type. Continue.
4673             i = j
4674             continue
4675         t = find_token(document.body, "target", i, j)
4676         if t == -1:
4677             document.warning("Malformed hyperlink inset at line " + str(i))
4678             i = j
4679             continue
4680         if document.body[t][8:12] == "run:":
4681             document.body.insert(t, "type \"other\"")
4682         i += 1
4683
4684
4685 def revert_hyper_other(document):
4686     " Revert other link type to ERT and \"run:\" to Web "
4687
4688     i = 0
4689     while True:
4690         i = find_token(document.body, "\\begin_inset CommandInset href", i)
4691         if i == -1:
4692             break
4693         j = find_end_of_inset(document.body, i)
4694         if j == -1:
4695             document.warning("Cannot find end of inset at line " << str(i))
4696             i += 1
4697             continue
4698         k = find_token(document.body, "type \"other\"", i, j)
4699         if k == -1:
4700             i = j
4701             continue
4702         # build command
4703         n = find_token(document.body, "name", i, j)
4704         t = find_token(document.body, "target", i, j)
4705         if n == -1 or t == -1:
4706             document.warning("Malformed hyperlink inset at line " + str(i))
4707             i = j
4708             continue
4709         name = document.body[n][6:-1]
4710         target = document.body[t][8:-1]
4711         if target[:4] == "run:":
4712             del document.body[k]
4713         else:
4714             cmd = "\href{" + target + "}{" + name + "}"
4715             ecmd = put_cmd_in_ert(cmd)
4716             document.body[i:j+1] = ecmd
4717         i += 1
4718
4719
4720 ack_layouts_new = {
4721     "aa" : "Acknowledgments",
4722     "aapaper" : "Acknowledgments",
4723     "aastex" : "Acknowledgments",
4724     "aastex62" : "Acknowledgments",
4725     "achemso" : "Acknowledgments",
4726     "acmart" : "Acknowledgments",
4727     "AEA" : "Acknowledgments",
4728     "apa" : "Acknowledgments",
4729     "copernicus" : "Acknowledgments",
4730     "egs" : "Acknowledgments",# + Acknowledgment
4731     "elsart" : "Acknowledgment",
4732     "isprs" : "Acknowledgments",
4733     "iucr" : "Acknowledgments",
4734     "kluwer" : "Acknowledgments",
4735     "svglobal3" : "Acknowledgments",
4736     "svglobal" : "Acknowledgment",
4737     "svjog" : "Acknowledgment",
4738     "svmono" : "Acknowledgment",
4739     "svmult" : "Acknowledgment",
4740     "svprobth" : "Acknowledgment",
4741 }
4742
4743 ack_layouts_old = {
4744     "aa" : "Acknowledgement",
4745     "aapaper" : "Acknowledgement",
4746     "aastex" : "Acknowledgement",
4747     "aastex62" : "Acknowledgement",
4748     "achemso" : "Acknowledgement",
4749     "acmart" : "Acknowledgements",
4750     "AEA" : "Acknowledgement",
4751     "apa" : "Acknowledgements",
4752     "copernicus" : "Acknowledgements",
4753     "egs" : "Acknowledgements",# + Acknowledgement
4754     "elsart" : "Acknowledegment",
4755     "isprs" : "Acknowledgements",
4756     "iucr" : "Acknowledgements",
4757     "kluwer" : "Acknowledgements",
4758     "svglobal3" : "Acknowledgements",
4759     "svglobal" : "Acknowledgement",
4760     "svjog" : "Acknowledgement",
4761     "svmono" : "Acknowledgement",
4762     "svmult" : "Acknowledgement",
4763     "svprobth" : "Acknowledgement",
4764 }
4765
4766
4767 def convert_acknowledgment(document):
4768     " Fix spelling of acknowledgment styles "
4769
4770     if document.textclass not in list(ack_layouts_old.keys()):
4771         return
4772
4773     i = 0
4774     while True:
4775         i = find_token(document.body, '\\begin_layout ' + ack_layouts_old[document.textclass], i)
4776         if i == -1:
4777             break
4778         document.body[i] = "\\begin_layout " + ack_layouts_new[document.textclass]
4779     if document.textclass != "egs":
4780         return
4781     # egs has two styles
4782     i = 0
4783     while True:
4784         i = find_token(document.body, '\\begin_layout Acknowledgement', i)
4785         if i == -1:
4786             break
4787         document.body[i] = "\\begin_layout Acknowledgment"
4788
4789
4790 def revert_acknowledgment(document):
4791     " Restore old spelling of acknowledgment styles "
4792
4793     if document.textclass not in list(ack_layouts_new.keys()):
4794         return
4795     i = 0
4796     while True:
4797         i = find_token(document.body, '\\begin_layout ' + ack_layouts_new[document.textclass], i)
4798         if i == -1:
4799             break
4800         document.body[i] = "\\begin_layout " + ack_layouts_old[document.textclass]
4801     if document.textclass != "egs":
4802         return
4803     # egs has two styles
4804     i = 0
4805     while True:
4806         i = find_token(document.body, '\\begin_layout Acknowledgment', i)
4807         if i == -1:
4808             break
4809         document.body[i] = "\\begin_layout Acknowledgement"
4810
4811
4812 ack_theorem_def = [
4813     r'### Inserted by lyx2lyx (ams extended theorems) ###',
4814     r'### This requires theorems-ams-extended module to be loaded',
4815     r'Style Acknowledgement',
4816     r'  CopyStyle             Remark',
4817     r'  LatexName             acknowledgement',
4818     r'  LabelString           "Acknowledgement \thetheorem."',
4819     r'  Preamble',
4820     r'    \theoremstyle{remark}',
4821     r'    \newtheorem{acknowledgement}[thm]{\protect\acknowledgementname}',
4822     r'  EndPreamble',
4823     r'  LangPreamble',
4824     r'    \providecommand{\acknowledgementname}{_(Acknowledgement)}',
4825     r'  EndLangPreamble',
4826     r'  BabelPreamble',
4827     r'    \addto\captions$$lang{\renewcommand{\acknowledgementname}{_(Acknowledgement)}}',
4828     r'  EndBabelPreamble',
4829     r'  DocBookTag            para',
4830     r'  DocBookAttr           role="acknowledgement"',
4831     r'  DocBookItemTag        ""',
4832     r'End',
4833 ]
4834
4835 ackStar_theorem_def = [
4836     r'### Inserted by lyx2lyx (ams extended theorems) ###',
4837     r'### This requires a theorems-ams-extended-* module to be loaded',
4838     r'Style Acknowledgement*',
4839     r'  CopyStyle             Remark*',
4840     r'  LatexName             acknowledgement*',
4841     r'  LabelString           "Acknowledgement."',
4842     r'  Preamble',
4843     r'    \theoremstyle{remark}',
4844     r'    \newtheorem*{acknowledgement*}{\protect\acknowledgementname}',
4845     r'  EndPreamble',
4846     r'  LangPreamble',
4847     r'    \providecommand{\acknowledgementname}{_(Acknowledgement)}',
4848     r'  EndLangPreamble',
4849     r'  BabelPreamble',
4850     r'    \addto\captions$$lang{\renewcommand{\acknowledgementname}{_(Acknowledgement)}}',
4851     r'  EndBabelPreamble',
4852     r'  DocBookTag            para',
4853     r'  DocBookAttr           role="acknowledgement"',
4854     r'  DocBookItemTag        ""',
4855     r'End',
4856 ]
4857
4858 ack_bytype_theorem_def = [
4859     r'### Inserted by lyx2lyx (ams extended theorems) ###',
4860     r'### This requires theorems-ams-extended-bytype module to be loaded',
4861     r'Counter acknowledgement',
4862     r'  GuiName Acknowledgment',
4863     r'End',
4864     r'Style Acknowledgement',
4865     r'  CopyStyle             Remark',
4866     r'  LatexName             acknowledgement',
4867     r'  LabelString           "Acknowledgement \theacknowledgement."',
4868     r'  Preamble',
4869     r'    \theoremstyle{remark}',
4870     r'    \newtheorem{acknowledgement}{\protect\acknowledgementname}',
4871     r'  EndPreamble',
4872     r'  LangPreamble',
4873     r'    \providecommand{\acknowledgementname}{_(Acknowledgement)}',
4874     r'  EndLangPreamble',
4875     r'  BabelPreamble',
4876     r'    \addto\captions$$lang{\renewcommand{\acknowledgementname}{_(Acknowledgement)}}',
4877     r'  EndBabelPreamble',
4878     r'  DocBookTag            para',
4879     r'  DocBookAttr           role="acknowledgement"',
4880     r'  DocBookItemTag        ""',
4881     r'End',
4882 ]
4883
4884 ack_chap_bytype_theorem_def = [
4885     r'### Inserted by lyx2lyx (ams extended theorems) ###',
4886     r'### This requires theorems-ams-extended-chap-bytype module to be loaded',
4887     r'Counter acknowledgement',
4888     r'  GuiName Acknowledgment',
4889     r'  Within chapter',
4890     r'End',
4891     r'Style Acknowledgement',
4892     r'  CopyStyle             Remark',
4893     r'  LatexName             acknowledgement',
4894     r'  LabelString           "Acknowledgement \theacknowledgement."',
4895     r'  Preamble',
4896     r'    \theoremstyle{remark}',
4897     r'    \ifx\thechapter\undefined',
4898     r'      \newtheorem{acknowledgement}{\protect\acknowledgementname}',
4899     r'    \else',
4900     r'      \newtheorem{acknowledgement}{\protect\acknowledgementname}[chapter]',
4901     r'    \fi',
4902     r'  EndPreamble',
4903     r'  LangPreamble',
4904     r'    \providecommand{\acknowledgementname}{_(Acknowledgement)}',
4905     r'  EndLangPreamble',
4906     r'  BabelPreamble',
4907     r'    \addto\captions$$lang{\renewcommand{\acknowledgementname}{_(Acknowledgement)}}',
4908     r'  EndBabelPreamble',
4909     r'  DocBookTag            para',
4910     r'  DocBookAttr           role="acknowledgement"',
4911     r'  DocBookItemTag        ""',
4912     r'End',
4913 ]
4914
4915 def convert_ack_theorems(document):
4916     """Put removed acknowledgement theorems to local layout"""
4917
4918     haveAck = False
4919     haveStarAck = False
4920     if "theorems-ams-extended-bytype" in document.get_module_list():
4921         i = 0
4922         while True:
4923             if haveAck and haveStarAck:
4924                 break
4925             i = find_token(document.body, '\\begin_layout Acknowledgement', i)
4926             if i == -1:
4927                 break
4928             if document.body[i] == "\\begin_layout Acknowledgement*" and not haveStarAck:
4929                 document.append_local_layout(ackStar_theorem_def)
4930                 haveStarAck = True
4931             elif not haveAck:
4932                 document.append_local_layout(ack_bytype_theorem_def)
4933                 haveAck = True
4934             i += 1
4935     elif "theorems-ams-extended-chap-bytype" in document.get_module_list():
4936         i = 0
4937         while True:
4938             if haveAck and haveStarAck:
4939                 break
4940             i = find_token(document.body, '\\begin_layout Acknowledgement', i)
4941             if i == -1:
4942                 break
4943             if document.body[i] == "\\begin_layout Acknowledgement*" and not haveStarAck:
4944                 document.append_local_layout(ackStar_theorem_def)
4945                 haveStarAck = True
4946             elif not haveAck:
4947                 document.append_local_layout(ack_chap_bytype_theorem_def)
4948                 haveAck = True
4949             i += 1
4950     elif "theorems-ams-extended" in document.get_module_list():
4951         i = 0
4952         while True:
4953             if haveAck and haveStarAck:
4954                 break
4955             i = find_token(document.body, '\\begin_layout Acknowledgement', i)
4956             if i == -1:
4957                 break
4958             if document.body[i] == "\\begin_layout Acknowledgement*" and not haveStarAck:
4959                 document.append_local_layout(ackStar_theorem_def)
4960                 haveStarAck = True
4961             elif not haveAck:
4962                 document.append_local_layout(ack_theorem_def)
4963                 haveAck = True
4964             i += 1
4965
4966
4967 def revert_ack_theorems(document):
4968     """Remove acknowledgement theorems from local layout"""
4969     if "theorems-ams-extended-bytype" in document.get_module_list():
4970         document.del_local_layout(ackStar_theorem_def)
4971         document.del_local_layout(ack_bytype_theorem_def)
4972     elif "theorems-ams-extended-chap-bytype" in document.get_module_list():
4973         document.del_local_layout(ackStar_theorem_def)
4974         document.del_local_layout(ack_chap_bytype_theorem_def)
4975     elif "theorems-ams-extended" in document.get_module_list():
4976         document.del_local_layout(ackStar_theorem_def)
4977         document.del_local_layout(ack_theorem_def)
4978
4979 def revert_empty_macro(document):
4980     '''Remove macros with empty LaTeX part'''
4981     i = 0
4982     while True:
4983         i = find_token(document.body, '\\begin_inset FormulaMacro', i)
4984         if i == -1:
4985             break
4986         cmd = document.body[i+1]
4987         if cmd[-3:] != "}{}" and cmd[-3:] != "]{}":
4988             i += 1
4989             continue
4990         j = find_end_of_inset(document.body, i)
4991         document.body[i:j+1] = []
4992
4993
4994 def convert_empty_macro(document):
4995     '''In the unlikely event someone defined a macro with empty LaTeX, add {}'''
4996     i = 0
4997     while True:
4998         i = find_token(document.body, '\\begin_inset FormulaMacro', i)
4999         if i == -1:
5000             break
5001         cmd = document.body[i+1]
5002         if cmd[-3:] != "}{}" and cmd[-3:] != "]{}":
5003             i += 1
5004             continue
5005         newstr = cmd[:-2] + "{\\{\\}}"
5006         document.body[i+1] = newstr
5007         i += 1
5008
5009
5010 ##
5011 # Conversion hub
5012 #
5013
5014 supported_versions = ["2.4.0", "2.4"]
5015 convert = [
5016            [545, [convert_lst_literalparam]],
5017            [546, []],
5018            [547, []],
5019            [548, []],
5020            [549, []],
5021            [550, [convert_fontenc]],
5022            [551, []],
5023            [552, []],
5024            [553, []],
5025            [554, []],
5026            [555, []],
5027            [556, []],
5028            [557, [convert_vcsinfo]],
5029            [558, [removeFrontMatterStyles]],
5030            [559, []],
5031            [560, []],
5032            [561, [convert_latexFonts]],  # Handle dejavu, ibmplex fonts in GUI
5033            [562, []],
5034            [563, []],
5035            [564, []],
5036            [565, [convert_AdobeFonts]],  # Handle adobe fonts in GUI
5037            [566, [convert_hebrew_parentheses]],
5038            [567, []],
5039            [568, []],
5040            [569, []],
5041            [570, []],
5042            [571, []],
5043            [572, [convert_notoFonts]],  # Added options thin, light, extralight for Noto
5044            [573, [convert_inputencoding_namechange]],
5045            [574, [convert_ruby_module, convert_utf8_japanese]],
5046            [575, [convert_lineno, convert_aaencoding]],
5047            [576, []],
5048            [577, [convert_linggloss]],
5049            [578, []],
5050            [579, []],
5051            [580, []],
5052            [581, [convert_osf]],
5053            [582, [convert_AdobeFonts,convert_latexFonts,convert_notoFonts,convert_CantarellFont,convert_FiraFont]],# old font re-converterted due to extra options
5054            [583, [convert_ChivoFont,convert_Semibolds,convert_NotoRegulars,convert_CrimsonProFont]],
5055            [584, []],
5056            [585, [convert_pagesizes]],
5057            [586, []],
5058            [587, [convert_pagesizenames]],
5059            [588, []],
5060            [589, [convert_totalheight]],
5061            [590, [convert_changebars]],
5062            [591, [convert_postpone_fragile]],
5063            [592, []],
5064            [593, [convert_counter_maintenance]],
5065            [594, []],
5066            [595, []],
5067            [596, [convert_parskip]],
5068            [597, [convert_libertinus_rm_fonts]],
5069            [598, []],
5070            [599, []],
5071            [600, []],
5072            [601, [convert_math_refs]],
5073            [602, [convert_branch_colors]],
5074            [603, []],
5075            [604, []],
5076            [605, [convert_vcolumns2]],
5077            [606, [convert_koma_frontispiece]],
5078            [607, []],
5079            [608, []],
5080            [609, []],
5081            [610, []],
5082            [611, []],
5083            [612, [convert_starred_refs]],
5084            [613, []],
5085            [614, [convert_hyper_other]],
5086            [615, [convert_acknowledgment,convert_ack_theorems]],
5087            [616, [convert_empty_macro]]
5088           ]
5089
5090 revert =  [[615, [revert_empty_macro]],
5091            [614, [revert_ack_theorems,revert_acknowledgment]],
5092            [613, [revert_hyper_other]],
5093            [612, [revert_familydefault]],
5094            [611, [revert_starred_refs]],
5095            [610, []],
5096            [609, [revert_index_macros]],
5097            [608, [revert_document_metadata]],
5098            [607, [revert_docbook_mathml_prefix]],
5099            [606, [revert_spellchecker_ignore]],
5100            [605, [revert_koma_frontispiece]],
5101            [604, [revert_vcolumns2]],
5102            [603, [revert_branch_darkcols]],
5103            [602, [revert_darkmode_graphics]],
5104            [601, [revert_branch_colors]],
5105            [600, []],
5106            [599, [revert_math_refs]],
5107            [598, [revert_hrquotes]],
5108            [598, [revert_nopagebreak]],
5109            [597, [revert_docbook_table_output]],
5110            [596, [revert_libertinus_rm_fonts,revert_libertinus_sftt_fonts]],
5111            [595, [revert_parskip,revert_line_vspaces]],
5112            [594, [revert_ams_spaces]],
5113            [593, [revert_counter_inset]],
5114            [592, [revert_counter_maintenance]],
5115            [591, [revert_colrow_tracking]],
5116            [590, [revert_postpone_fragile]],
5117            [589, [revert_changebars]],
5118            [588, [revert_totalheight]],
5119            [587, [revert_memoir_endnotes,revert_enotez,revert_theendnotes]],
5120            [586, [revert_pagesizenames]],
5121            [585, [revert_dupqualicites]],
5122            [584, [revert_pagesizes,revert_komafontsizes]],
5123            [583, [revert_vcsinfo_rev_abbrev]],
5124            [582, [revert_ChivoFont,revert_CrimsonProFont]],
5125            [581, [revert_CantarellFont,revert_FiraFont]],
5126            [580, [revert_texfontopts,revert_osf]],
5127            [579, [revert_minionpro, revert_plainNotoFonts_xopts, revert_notoFonts_xopts, revert_IBMFonts_xopts, revert_AdobeFonts_xopts, revert_font_opts]], # keep revert_font_opts last!
5128            [578, [revert_babelfont]],
5129            [577, [revert_drs]],
5130            [576, [revert_linggloss, revert_subexarg]],
5131            [575, [revert_new_languages]],
5132            [574, [revert_lineno, revert_aaencoding]],
5133            [573, [revert_ruby_module, revert_utf8_japanese]],
5134            [572, [revert_inputencoding_namechange]],
5135            [571, [revert_notoFonts]],
5136            [570, [revert_cmidruletrimming]],
5137            [569, [revert_bibfileencodings]],
5138            [568, [revert_tablestyle]],
5139            [567, [revert_soul]],
5140            [566, [revert_malayalam]],
5141            [565, [revert_hebrew_parentheses]],
5142            [564, [revert_AdobeFonts]],
5143            [563, [revert_lformatinfo]],
5144            [562, [revert_listpargs]],
5145            [561, [revert_l7ninfo]],
5146            [560, [revert_latexFonts]],  # Handle dejavu, ibmplex fonts in user preamble
5147            [559, [revert_timeinfo, revert_namenoextinfo]],
5148            [558, [revert_dateinfo]],
5149            [557, [addFrontMatterStyles]],
5150            [556, [revert_vcsinfo]],
5151            [555, [revert_bibencoding]],
5152            [554, [revert_vcolumns]],
5153            [553, [revert_stretchcolumn]],
5154            [552, [revert_tuftecite]],
5155            [551, [revert_floatpclass, revert_floatalignment]],
5156            [550, [revert_nospellcheck]],
5157            [549, [revert_fontenc]],
5158            [548, []],  # dummy format change
5159            [547, [revert_lscape]],
5160            [546, [revert_xcharter]],
5161            [545, [revert_paratype]],
5162            [544, [revert_lst_literalparam]]
5163           ]
5164
5165
5166 if __name__ == "__main__":
5167     pass