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