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