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