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