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