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