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