]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_4.py
Move DrawStrategy enum to update_flags.h.
[lyx.git] / lib / lyx2lyx / lyx_2_4.py
1 # This file is part of lyx2lyx
2 # Copyright (C) 2018 The LyX team
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17
18 """Convert files to the file format generated by lyx 2.4"""
19
20 import re
21 from datetime import date, datetime, time
22
23 from lyx2lyx_tools import (
24     add_to_preamble,
25     insert_to_preamble,
26     lyx2latex,
27     put_cmd_in_ert,
28     revert_flex_inset,
29     revert_language,
30     str2bool,
31 )
32 from parser_tools import (
33     count_pars_in_inset,
34     del_complete_lines,
35     del_token,
36     find_end_of,
37     find_end_of_inset,
38     find_end_of_layout,
39     find_re,
40     find_token,
41     find_token_backwards,
42     find_token_exact,
43     get_bool_value,
44     get_containing_inset,
45     get_containing_layout,
46     get_option_value,
47     get_quoted_value,
48     get_value,
49     is_in_inset,
50 )
51
52 ####################################################################
53 # Private helper functions
54
55
56 def add_preamble_fonts(document, fontmap):
57     """Add collected font-packages with their option to user-preamble"""
58
59     for pkg in fontmap:
60         if len(fontmap[pkg]) > 0:
61             xoption = "[" + ",".join(fontmap[pkg]) + "]"
62         else:
63             xoption = ""
64         preamble = f"\\usepackage{xoption}{{{pkg}}}"
65         add_to_preamble(document, [preamble])
66
67
68 def createkey(pkg, options):
69     options.sort()
70     return pkg + ":" + "-".join(options)
71
72
73 class fontinfo:
74     def __init__(self):
75         self.fontname = None  # key into font2pkgmap
76         self.fonttype = None  # roman,sans,typewriter,math
77         self.scaletype = None  # None,sf,tt
78         self.scaleopt = None  # None, 'scaled', 'scale'
79         self.scaleval = 1
80         self.package = None
81         self.options = []
82         self.pkgkey = None  # key into pkg2fontmap
83         self.osfopt = None  # None, string
84         self.osfdef = "false"  # "false" or "true"
85
86     def addkey(self):
87         self.pkgkey = createkey(self.package, self.options)
88
89
90 class fontmapping:
91     def __init__(self):
92         self.font2pkgmap = dict()
93         self.pkg2fontmap = dict()
94         self.pkginmap = dict()  # defines, if a map for package exists
95
96     def expandFontMapping(
97         self,
98         font_list,
99         font_type,
100         scale_type,
101         pkg,
102         scaleopt=None,
103         osfopt=None,
104         osfdef="false",
105     ):
106         """Expand fontinfo mapping"""
107         #
108         # fontlist:    list of fontnames, each element
109         #              may contain a ','-separated list of needed options
110         #              like e.g. 'IBMPlexSansCondensed,condensed'
111         # font_type:   one of 'roman', 'sans', 'typewriter', 'math'
112         # scale_type:  one of None, 'sf', 'tt'
113         # pkg:         package defining the font. Defaults to fontname if None
114         # scaleopt:    one of None, 'scale', 'scaled', or some other string
115         #              to be used in scale option (e.g. scaled=0.7)
116         # osfopt:      None or some other string to be used in osf option
117         # osfdef:      "true" if osf is default
118         for fl in font_list:
119             fe = fontinfo()
120             fe.fonttype = font_type
121             fe.scaletype = scale_type
122             flt = fl.split(",")
123             font_name = flt[0]
124             fe.fontname = font_name
125             fe.options = flt[1:]
126             fe.scaleopt = scaleopt
127             fe.osfopt = osfopt
128             fe.osfdef = osfdef
129             if pkg == None:
130                 fe.package = font_name
131             else:
132                 fe.package = pkg
133             fe.addkey()
134             self.font2pkgmap[font_name] = fe
135             if fe.pkgkey in self.pkg2fontmap:
136                 # Repeated the same entry? Check content
137                 if self.pkg2fontmap[fe.pkgkey] != font_name:
138                     document.error("Something is wrong in pkgname+options <-> fontname mapping")
139             self.pkg2fontmap[fe.pkgkey] = font_name
140             self.pkginmap[fe.package] = 1
141
142     def getfontname(self, pkg, options):
143         options.sort()
144         pkgkey = createkey(pkg, options)
145         if pkgkey not in self.pkg2fontmap:
146             return None
147         fontname = self.pkg2fontmap[pkgkey]
148         if fontname not in self.font2pkgmap:
149             document.error("Something is wrong in pkgname+options <-> fontname mapping")
150             return None
151         if pkgkey == self.font2pkgmap[fontname].pkgkey:
152             return fontname
153         return None
154
155
156 def createFontMapping(fontlist):
157     # Create info for known fonts for the use in
158     #   convert_latexFonts() and
159     #   revert_latexFonts()
160     #
161     # * Would be more handy to parse latexFonts file,
162     #   but the path to this file is unknown
163     # * For now, add DejaVu and IBMPlex only.
164     # * Expand, if desired
165     fm = fontmapping()
166     for font in fontlist:
167         if font == "DejaVu":
168             fm.expandFontMapping(["DejaVuSerif", "DejaVuSerifCondensed"], "roman", None, None)
169             fm.expandFontMapping(
170                 ["DejaVuSans", "DejaVuSansCondensed"], "sans", "sf", None, "scaled"
171             )
172             fm.expandFontMapping(["DejaVuSansMono"], "typewriter", "tt", None, "scaled")
173         elif font == "IBM":
174             fm.expandFontMapping(
175                 [
176                     "IBMPlexSerif",
177                     "IBMPlexSerifThin,thin",
178                     "IBMPlexSerifExtraLight,extralight",
179                     "IBMPlexSerifLight,light",
180                     "IBMPlexSerifSemibold,semibold",
181                 ],
182                 "roman",
183                 None,
184                 "plex-serif",
185             )
186             fm.expandFontMapping(
187                 [
188                     "IBMPlexSans",
189                     "IBMPlexSansCondensed,condensed",
190                     "IBMPlexSansThin,thin",
191                     "IBMPlexSansExtraLight,extralight",
192                     "IBMPlexSansLight,light",
193                     "IBMPlexSansSemibold,semibold",
194                 ],
195                 "sans",
196                 "sf",
197                 "plex-sans",
198                 "scale",
199             )
200             fm.expandFontMapping(
201                 [
202                     "IBMPlexMono",
203                     "IBMPlexMonoThin,thin",
204                     "IBMPlexMonoExtraLight,extralight",
205                     "IBMPlexMonoLight,light",
206                     "IBMPlexMonoSemibold,semibold",
207                 ],
208                 "typewriter",
209                 "tt",
210                 "plex-mono",
211                 "scale",
212             )
213         elif font == "Adobe":
214             fm.expandFontMapping(
215                 ["ADOBESourceSerifPro"], "roman", None, "sourceserifpro", None, "osf"
216             )
217             fm.expandFontMapping(
218                 ["ADOBESourceSansPro"], "sans", "sf", "sourcesanspro", "scaled", "osf"
219             )
220             fm.expandFontMapping(
221                 ["ADOBESourceCodePro"],
222                 "typewriter",
223                 "tt",
224                 "sourcecodepro",
225                 "scaled",
226                 "osf",
227             )
228         elif font == "Noto":
229             fm.expandFontMapping(
230                 [
231                     "NotoSerifRegular,regular",
232                     "NotoSerifMedium,medium",
233                     "NotoSerifThin,thin",
234                     "NotoSerifLight,light",
235                     "NotoSerifExtralight,extralight",
236                 ],
237                 "roman",
238                 None,
239                 "noto-serif",
240                 None,
241                 "osf",
242             )
243             fm.expandFontMapping(
244                 [
245                     "NotoSansRegular,regular",
246                     "NotoSansMedium,medium",
247                     "NotoSansThin,thin",
248                     "NotoSansLight,light",
249                     "NotoSansExtralight,extralight",
250                 ],
251                 "sans",
252                 "sf",
253                 "noto-sans",
254                 "scaled",
255             )
256             fm.expandFontMapping(
257                 ["NotoMonoRegular,regular"], "typewriter", "tt", "noto-mono", "scaled"
258             )
259         elif font == "Cantarell":
260             fm.expandFontMapping(
261                 ["cantarell,defaultsans"],
262                 "sans",
263                 "sf",
264                 "cantarell",
265                 "scaled",
266                 "oldstyle",
267             )
268         elif font == "Chivo":
269             fm.expandFontMapping(
270                 [
271                     "ChivoThin,thin",
272                     "ChivoLight,light",
273                     "Chivo,regular",
274                     "ChivoMedium,medium",
275                 ],
276                 "sans",
277                 "sf",
278                 "Chivo",
279                 "scale",
280                 "oldstyle",
281             )
282         elif font == "CrimsonPro":
283             fm.expandFontMapping(
284                 [
285                     "CrimsonPro",
286                     "CrimsonProExtraLight,extralight",
287                     "CrimsonProLight,light",
288                     "CrimsonProMedium,medium",
289                 ],
290                 "roman",
291                 None,
292                 "CrimsonPro",
293                 None,
294                 "lf",
295                 "true",
296             )
297         elif font == "Fira":
298             fm.expandFontMapping(
299                 [
300                     "FiraSans",
301                     "FiraSansBook,book",
302                     "FiraSansThin,thin",
303                     "FiraSansLight,light",
304                     "FiraSansExtralight,extralight",
305                     "FiraSansUltralight,ultralight",
306                 ],
307                 "sans",
308                 "sf",
309                 "FiraSans",
310                 "scaled",
311                 "lf",
312                 "true",
313             )
314             fm.expandFontMapping(
315                 ["FiraMono"], "typewriter", "tt", "FiraMono", "scaled", "lf", "true"
316             )
317         elif font == "libertinus":
318             fm.expandFontMapping(["libertinus,serif"], "roman", None, "libertinus", None, "osf")
319             fm.expandFontMapping(
320                 ["libertinusmath"], "math", None, "libertinust1math", None, None
321             )
322     return fm
323
324
325 def convert_fonts(document, fm, osfoption="osf"):
326     """Handle font definition (LaTeX preamble -> native)"""
327     rpkg = re.compile(r"^\\usepackage(\[([^\]]*)\])?\{([^\}]+)\}")
328     rscaleopt = re.compile(r"^scaled?=(.*)")
329
330     # Check whether we go beyond font option feature introduction
331     haveFontOpts = document.end_format > 580
332
333     i = 0
334     while True:
335         i = find_re(document.preamble, rpkg, i + 1)
336         if i == -1:
337             return
338         mo = rpkg.search(document.preamble[i])
339         if mo == None or mo.group(2) == None:
340             options = []
341         else:
342             options = mo.group(2).replace(" ", "").split(",")
343         pkg = mo.group(3)
344         o = 0
345         oscale = 1
346         has_osf = False
347         while o < len(options):
348             if options[o] == osfoption:
349                 has_osf = True
350                 del options[o]
351                 continue
352             mo = rscaleopt.search(options[o])
353             if mo == None:
354                 o += 1
355                 continue
356             oscale = mo.group(1)
357             del options[o]
358             continue
359
360         if pkg not in fm.pkginmap:
361             continue
362         # determine fontname
363         fn = None
364         if haveFontOpts:
365             # Try with name-option combination first
366             # (only one default option supported currently)
367             o = 0
368             while o < len(options):
369                 opt = options[o]
370                 fn = fm.getfontname(pkg, [opt])
371                 if fn != None:
372                     del options[o]
373                     break
374                 o += 1
375                 continue
376             if fn == None:
377                 fn = fm.getfontname(pkg, [])
378         else:
379             fn = fm.getfontname(pkg, options)
380         if fn == None:
381             continue
382         del document.preamble[i]
383         fontinfo = fm.font2pkgmap[fn]
384         if fontinfo.scaletype == None:
385             fontscale = None
386         else:
387             fontscale = "\\font_" + fontinfo.scaletype + "_scale"
388             fontinfo.scaleval = oscale
389         if (has_osf and fontinfo.osfdef == "false") or (
390             not has_osf and fontinfo.osfdef == "true"
391         ):
392             if fontinfo.osfopt == None:
393                 options.extend(osfoption)
394                 continue
395             osf = find_token(document.header, "\\font_osf false")
396             osftag = "\\font_osf"
397             if osf == -1 and fontinfo.fonttype != "math":
398                 # Try with newer format
399                 osftag = "\\font_" + fontinfo.fonttype + "_osf"
400                 osf = find_token(document.header, osftag + " false")
401             if osf != -1:
402                 document.header[osf] = osftag + " true"
403         if i > 0 and document.preamble[i - 1] == "% Added by lyx2lyx":
404             del document.preamble[i - 1]
405             i -= 1
406         if fontscale != None:
407             j = find_token(document.header, fontscale, 0)
408             if j != -1:
409                 val = get_value(document.header, fontscale, j)
410                 vals = val.split()
411                 scale = "100"
412                 if oscale != None:
413                     scale = "%03d" % int(float(oscale) * 100)
414                 document.header[j] = fontscale + " " + scale + " " + vals[1]
415         ft = "\\font_" + fontinfo.fonttype
416         j = find_token(document.header, ft, 0)
417         if j != -1:
418             val = get_value(document.header, ft, j)
419             words = val.split()  # ! splits also values like '"DejaVu Sans"'
420             words[0] = '"' + fn + '"'
421             document.header[j] = ft + " " + " ".join(words)
422         if haveFontOpts and fontinfo.fonttype != "math":
423             fotag = "\\font_" + fontinfo.fonttype + "_opts"
424             fo = find_token(document.header, fotag)
425             if fo != -1:
426                 document.header[fo] = fotag + ' "' + ",".join(options) + '"'
427             else:
428                 # Sensible place to insert tag
429                 fo = find_token(document.header, "\\font_sf_scale")
430                 if fo == -1:
431                     document.warning("Malformed LyX document! Missing \\font_sf_scale")
432                 else:
433                     document.header.insert(fo, fotag + ' "' + ",".join(options) + '"')
434
435
436 def revert_fonts(document, fm, fontmap, OnlyWithXOpts=False, WithXOpts=False):
437     """Revert native font definition to LaTeX"""
438     # fonlist := list of fonts created from the same package
439     # Empty package means that the font-name is the same as the package-name
440     # fontmap (key = package, val += found options) will be filled
441     # and used later in add_preamble_fonts() to be added to user-preamble
442
443     rfontscale = re.compile(r"^\s*(\\font_(roman|sans|typewriter|math))\s+")
444     rscales = re.compile(r"^\s*(\d+)\s+(\d+)")
445     i = 0
446     while i < len(document.header):
447         i = find_re(document.header, rfontscale, i + 1)
448         if i == -1:
449             return True
450         mo = rfontscale.search(document.header[i])
451         if mo == None:
452             continue
453         ft = mo.group(1)  # 'roman', 'sans', 'typewriter', 'math'
454         val = get_value(document.header, ft, i)
455         words = val.split(" ")  # ! splits also values like '"DejaVu Sans"'
456         font = words[0].strip('"')  # TeX font name has no whitespace
457         if font not in fm.font2pkgmap:
458             continue
459         fontinfo = fm.font2pkgmap[font]
460         val = fontinfo.package
461         if val not in fontmap:
462             fontmap[val] = []
463         x = -1
464         if OnlyWithXOpts or WithXOpts:
465             if ft == "\\font_math":
466                 return False
467             regexp = re.compile(r"^\s*(\\font_roman_opts)\s+")
468             if ft == "\\font_sans":
469                 regexp = re.compile(r"^\s*(\\font_sans_opts)\s+")
470             elif ft == "\\font_typewriter":
471                 regexp = re.compile(r"^\s*(\\font_typewriter_opts)\s+")
472             x = find_re(document.header, regexp, 0)
473             if x == -1 and OnlyWithXOpts:
474                 return False
475
476             if x != -1:
477                 # We need to use this regex since split() does not handle quote protection
478                 xopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
479                 opts = xopts[1].strip('"').split(",")
480                 fontmap[val].extend(opts)
481                 del document.header[x]
482         words[0] = '"default"'
483         document.header[i] = ft + " " + " ".join(words)
484         if fontinfo.scaleopt != None:
485             xval = get_value(document.header, "\\font_" + fontinfo.scaletype + "_scale", 0)
486             mo = rscales.search(xval)
487             if mo != None:
488                 xval1 = mo.group(1)
489                 if xval1 != "100":
490                     # set correct scale option
491                     fontmap[val].extend(
492                         [fontinfo.scaleopt + "=" + format(float(xval1) / 100, ".2f")]
493                     )
494         if fontinfo.osfopt != None:
495             oldval = "true"
496             if fontinfo.osfdef == "true":
497                 oldval = "false"
498             osf = find_token(document.header, "\\font_osf " + oldval)
499             if osf == -1 and ft != "\\font_math":
500                 # Try with newer format
501                 osftag = "\\font_roman_osf " + oldval
502                 if ft == "\\font_sans":
503                     osftag = "\\font_sans_osf " + oldval
504                 elif ft == "\\font_typewriter":
505                     osftag = "\\font_typewriter_osf " + oldval
506                 osf = find_token(document.header, osftag)
507             if osf != -1:
508                 fontmap[val].extend([fontinfo.osfopt])
509         if len(fontinfo.options) > 0:
510             fontmap[val].extend(fontinfo.options)
511     return True
512
513
514 ###############################################################################
515 ###
516 ### Conversion and reversion routines
517 ###
518 ###############################################################################
519
520
521 def convert_inputencoding_namechange(document):
522     """Rename inputencoding settings."""
523     i = find_token(document.header, "\\inputencoding", 0)
524     if i == -1:
525         return
526     s = document.header[i].replace("auto", "auto-legacy")
527     document.header[i] = s.replace("default", "auto-legacy-plain")
528
529
530 def revert_inputencoding_namechange(document):
531     """Rename inputencoding settings."""
532     i = find_token(document.header, "\\inputencoding", 0)
533     if i == -1:
534         return
535     s = document.header[i].replace("auto-legacy-plain", "default")
536     document.header[i] = s.replace("auto-legacy", "auto")
537
538
539 def convert_notoFonts(document):
540     """Handle Noto fonts definition to LaTeX"""
541
542     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
543         fm = createFontMapping(["Noto"])
544         convert_fonts(document, fm)
545
546
547 def revert_notoFonts(document):
548     """Revert native Noto font definition to LaTeX"""
549
550     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
551         fontmap = dict()
552         fm = createFontMapping(["Noto"])
553         if revert_fonts(document, fm, fontmap):
554             add_preamble_fonts(document, fontmap)
555
556
557 def convert_latexFonts(document):
558     """Handle DejaVu and IBMPlex fonts definition to LaTeX"""
559
560     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
561         fm = createFontMapping(["DejaVu", "IBM"])
562         convert_fonts(document, fm)
563
564
565 def revert_latexFonts(document):
566     """Revert native DejaVu font definition to LaTeX"""
567
568     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
569         fontmap = dict()
570         fm = createFontMapping(["DejaVu", "IBM"])
571         if revert_fonts(document, fm, fontmap):
572             add_preamble_fonts(document, fontmap)
573
574
575 def convert_AdobeFonts(document):
576     """Handle Adobe Source fonts definition to LaTeX"""
577
578     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
579         fm = createFontMapping(["Adobe"])
580         convert_fonts(document, fm)
581
582
583 def revert_AdobeFonts(document):
584     """Revert Adobe Source font definition to LaTeX"""
585
586     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
587         fontmap = dict()
588         fm = createFontMapping(["Adobe"])
589         if revert_fonts(document, fm, fontmap):
590             add_preamble_fonts(document, fontmap)
591
592
593 def removeFrontMatterStyles(document):
594     """Remove styles Begin/EndFrontmatter"""
595
596     layouts = ["BeginFrontmatter", "EndFrontmatter"]
597     tokenend = len("\\begin_layout ")
598     i = 0
599     while True:
600         i = find_token_exact(document.body, "\\begin_layout ", i + 1)
601         if i == -1:
602             return
603         layout = document.body[i][tokenend:].strip()
604         if layout not in layouts:
605             continue
606         j = find_end_of_layout(document.body, i)
607         if j == -1:
608             document.warning("Malformed LyX document: Can't find end of layout at line %d" % i)
609             continue
610         while document.body[j + 1].strip() == "":
611             j += 1
612         document.body[i : j + 1] = []
613
614
615 def addFrontMatterStyles(document):
616     """Use styles Begin/EndFrontmatter for elsarticle"""
617
618     if document.textclass != "elsarticle":
619         return
620
621     def insertFrontmatter(prefix, line):
622         above = line
623         while above > 0 and document.body[above - 1].strip() == "":
624             above -= 1
625         below = line
626         while document.body[below].strip() == "":
627             below += 1
628         document.body[above:below] = [
629             "",
630             "\\begin_layout " + prefix + "Frontmatter",
631             "\\begin_inset Note Note",
632             "status open",
633             "",
634             "\\begin_layout Plain Layout",
635             "Keep this empty!",
636             "\\end_layout",
637             "",
638             "\\end_inset",
639             "",
640             "",
641             "\\end_layout",
642             "",
643         ]
644
645     layouts = [
646         "Title",
647         "Title footnote",
648         "Author",
649         "Author footnote",
650         "Corresponding author",
651         "Address",
652         "Email",
653         "Abstract",
654         "Keywords",
655     ]
656     tokenend = len("\\begin_layout ")
657     first = -1
658     i = 0
659     while True:
660         i = find_token_exact(document.body, "\\begin_layout ", i + 1)
661         if i == -1:
662             break
663         layout = document.body[i][tokenend:].strip()
664         if layout not in layouts:
665             continue
666         k = find_end_of_layout(document.body, i)
667         if k == -1:
668             document.warning("Malformed LyX document: Can't find end of layout at line %d" % i)
669             continue
670         if first == -1:
671             first = i
672         i = k
673     if first == -1:
674         return
675     insertFrontmatter("End", k + 1)
676     insertFrontmatter("Begin", first)
677
678
679 def convert_lst_literalparam(document):
680     """Add param literal to include inset"""
681
682     i = 0
683     while True:
684         i = find_token(document.body, "\\begin_inset CommandInset include", i + 1)
685         if i == -1:
686             break
687         j = find_end_of_inset(document.body, i)
688         if j == -1:
689             document.warning(
690                 "Malformed LyX document: Can't find end of command inset at line %d" % i
691             )
692             continue
693         while i < j and document.body[i].strip() != "":
694             i += 1
695         document.body.insert(i, 'literal "true"')
696
697
698 def revert_lst_literalparam(document):
699     """Remove param literal from include inset"""
700
701     i = 0
702     while True:
703         i = find_token(document.body, "\\begin_inset CommandInset include", i + 1)
704         if i == -1:
705             break
706         j = find_end_of_inset(document.body, i)
707         if j == -1:
708             document.warning(
709                 "Malformed LyX document: Can't find end of include inset at line %d" % i
710             )
711             continue
712         del_token(document.body, "literal", i, j)
713
714
715 def revert_paratype(document):
716     """Revert ParaType font definitions to LaTeX"""
717
718     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
719         i1 = find_token(document.header, '\\font_roman "PTSerif-TLF"', 0)
720         i2 = find_token(document.header, '\\font_sans "default"', 0)
721         i3 = find_token(document.header, '\\font_typewriter "default"', 0)
722         j = find_token(document.header, '\\font_sans "PTSans-TLF"', 0)
723
724         sf_scale = 100.0
725         sfval = find_token(document.header, "\\font_sf_scale", 0)
726         if sfval == -1:
727             document.warning("Malformed LyX document: Missing \\font_sf_scale.")
728         else:
729             sfscale = document.header[sfval].split()
730             val = sfscale[1]
731             sfscale[1] = "100"
732             document.header[sfval] = " ".join(sfscale)
733             try:
734                 # float() can throw
735                 sf_scale = float(val)
736             except:
737                 document.warning("Invalid font_sf_scale value: " + val)
738
739         sfoption = ""
740         if sf_scale != "100.0":
741             sfoption = "scaled=" + str(sf_scale / 100.0)
742         k = find_token(document.header, '\\font_typewriter "PTMono-TLF"', 0)
743         ttval = get_value(document.header, "\\font_tt_scale", 0)
744         # cutoff " 100"
745         ttval = ttval[:-4]
746         ttoption = ""
747         if ttval != "100":
748             ttoption = "scaled=" + format(float(ttval) / 100, ".2f")
749         if i1 != -1 and i2 != -1 and i3 != -1:
750             add_to_preamble(document, ["\\usepackage{paratype}"])
751         else:
752             if i1 != -1:
753                 add_to_preamble(document, ["\\usepackage{PTSerif}"])
754                 document.header[i1] = document.header[i1].replace("PTSerif-TLF", "default")
755             if j != -1:
756                 if sfoption != "":
757                     add_to_preamble(document, ["\\usepackage[" + sfoption + "]{PTSans}"])
758                 else:
759                     add_to_preamble(document, ["\\usepackage{PTSans}"])
760                 document.header[j] = document.header[j].replace("PTSans-TLF", "default")
761             if k != -1:
762                 if ttoption != "":
763                     add_to_preamble(document, ["\\usepackage[" + ttoption + "]{PTMono}"])
764                 else:
765                     add_to_preamble(document, ["\\usepackage{PTMono}"])
766                 document.header[k] = document.header[k].replace("PTMono-TLF", "default")
767
768
769 def revert_xcharter(document):
770     """Revert XCharter font definitions to LaTeX"""
771
772     i = find_token(document.header, '\\font_roman "xcharter"', 0)
773     if i == -1:
774         return
775
776     # replace unsupported font setting
777     document.header[i] = document.header[i].replace("xcharter", "default")
778     # no need for preamble code with system fonts
779     if get_bool_value(document.header, "\\use_non_tex_fonts"):
780         return
781
782     # transfer old style figures setting to package options
783     j = find_token(document.header, "\\font_osf true")
784     if j != -1:
785         options = "[osf]"
786         document.header[j] = "\\font_osf false"
787     else:
788         options = ""
789     if i != -1:
790         add_to_preamble(document, ["\\usepackage%s{XCharter}" % options])
791
792
793 def revert_lscape(document):
794     """Reverts the landscape environment (Landscape module) to TeX-code"""
795
796     if "landscape" not in document.get_module_list():
797         return
798
799     i = 0
800     while True:
801         i = find_token(document.body, "\\begin_inset Flex Landscape", i + 1)
802         if i == -1:
803             break
804         j = find_end_of_inset(document.body, i)
805         if j == -1:
806             document.warning("Malformed LyX document: Can't find end of Landscape inset")
807             continue
808
809         if document.body[i] == "\\begin_inset Flex Landscape (Floating)":
810             document.body[j - 2 : j + 1] = put_cmd_in_ert("\\end{landscape}}")
811             document.body[i : i + 4] = put_cmd_in_ert("\\afterpage{\\begin{landscape}")
812             add_to_preamble(document, ["\\usepackage{afterpage}"])
813         else:
814             document.body[j - 2 : j + 1] = put_cmd_in_ert("\\end{landscape}")
815             document.body[i : i + 4] = put_cmd_in_ert("\\begin{landscape}")
816
817         add_to_preamble(document, ["\\usepackage{pdflscape}"])
818     document.del_module("landscape")
819
820
821 def convert_fontenc(document):
822     """Convert default fontenc setting"""
823
824     i = find_token(document.header, "\\fontencoding global", 0)
825     if i == -1:
826         return
827
828     document.header[i] = document.header[i].replace("global", "auto")
829
830
831 def revert_fontenc(document):
832     """Revert default fontenc setting"""
833
834     i = find_token(document.header, "\\fontencoding auto", 0)
835     if i == -1:
836         return
837
838     document.header[i] = document.header[i].replace("auto", "global")
839
840
841 def revert_nospellcheck(document):
842     """Remove nospellcheck font info param"""
843
844     i = 0
845     while True:
846         i = find_token(document.body, "\\nospellcheck", i)
847         if i == -1:
848             return
849         del document.body[i]
850
851
852 def revert_floatpclass(document):
853     """Remove float placement params 'document' and 'class'"""
854
855     del_token(document.header, "\\float_placement class")
856
857     i = 0
858     while True:
859         i = find_token(document.body, "\\begin_inset Float", i + 1)
860         if i == -1:
861             break
862         j = find_end_of_inset(document.body, i)
863         k = find_token(document.body, "placement class", i, j)
864         if k == -1:
865             k = find_token(document.body, "placement document", i, j)
866             if k != -1:
867                 del document.body[k]
868             continue
869         del document.body[k]
870
871
872 def revert_floatalignment(document):
873     """Remove float alignment params"""
874
875     galignment = get_value(document.header, "\\float_alignment", delete=True)
876
877     i = 0
878     while True:
879         i = find_token(document.body, "\\begin_inset Float", i + 1)
880         if i == -1:
881             break
882         j = find_end_of_inset(document.body, i)
883         if j == -1:
884             document.warning(
885                 "Malformed LyX document: Can't find end of inset at line " + str(i)
886             )
887             continue
888         k = find_token(document.body, "alignment", i, j)
889         if k == -1:
890             i = j
891             continue
892         alignment = get_value(document.body, "alignment", k)
893         if alignment == "document":
894             alignment = galignment
895         del document.body[k]
896         l = find_token(document.body, "\\begin_layout Plain Layout", i, j)
897         if l == -1:
898             document.warning("Can't find float layout!")
899             continue
900         alcmd = []
901         if alignment == "left":
902             alcmd = put_cmd_in_ert("\\raggedright{}")
903         elif alignment == "center":
904             alcmd = put_cmd_in_ert("\\centering{}")
905         elif alignment == "right":
906             alcmd = put_cmd_in_ert("\\raggedleft{}")
907         if len(alcmd) > 0:
908             document.body[l + 1 : l + 1] = alcmd
909         # There might be subfloats, so we do not want to move past
910         # the end of the inset.
911         i += 1
912
913
914 def revert_tuftecite(document):
915     r"""Revert \cite commands in tufte classes"""
916
917     tufte = ["tufte-book", "tufte-handout"]
918     if document.textclass not in tufte:
919         return
920
921     i = 0
922     while True:
923         i = find_token(document.body, "\\begin_inset CommandInset citation", i + 1)
924         if i == -1:
925             break
926         j = find_end_of_inset(document.body, i)
927         if j == -1:
928             document.warning("Can't find end of citation inset at line %d!!" % (i))
929             continue
930         k = find_token(document.body, "LatexCommand", i, j)
931         if k == -1:
932             document.warning("Can't find LatexCommand for citation inset at line %d!" % (i))
933             i = j
934             continue
935         cmd = get_value(document.body, "LatexCommand", k)
936         if cmd != "cite":
937             i = j
938             continue
939         pre = get_quoted_value(document.body, "before", i, j)
940         post = get_quoted_value(document.body, "after", i, j)
941         key = get_quoted_value(document.body, "key", i, j)
942         if not key:
943             document.warning("Citation inset at line %d does not have a key!" % (i))
944             key = "???"
945         # Replace command with ERT
946         res = "\\cite"
947         if pre:
948             res += "[" + pre + "]"
949         if post:
950             res += "[" + post + "]"
951         elif pre:
952             res += "[]"
953         res += "{" + key + "}"
954         document.body[i : j + 1] = put_cmd_in_ert([res])
955         i = j
956
957
958 def revert_stretchcolumn(document):
959     """We remove the column varwidth flags or everything else will become a mess."""
960     i = 0
961     while True:
962         i = find_token(document.body, "\\begin_inset Tabular", i + 1)
963         if i == -1:
964             return
965         j = find_end_of_inset(document.body, i + 1)
966         if j == -1:
967             document.warning("Malformed LyX document: Could not find end of tabular.")
968             continue
969         for k in range(i, j):
970             if re.search('^<column.*varwidth="[^"]+".*>$', document.body[k]):
971                 document.warning("Converting 'tabularx'/'xltabular' table to normal table.")
972                 document.body[k] = document.body[k].replace(' varwidth="true"', "")
973
974
975 def revert_vcolumns(document):
976     """Revert standard columns with line breaks etc."""
977     i = 0
978     needvarwidth = False
979     needarray = False
980     try:
981         while True:
982             i = find_token(document.body, "\\begin_inset Tabular", i + 1)
983             if i == -1:
984                 return
985             j = find_end_of_inset(document.body, i)
986             if j == -1:
987                 document.warning("Malformed LyX document: Could not find end of tabular.")
988                 continue
989
990             # Collect necessary column information
991             m = i + 1
992             nrows = int(document.body[i + 1].split('"')[3])
993             ncols = int(document.body[i + 1].split('"')[5])
994             col_info = []
995             for k in range(ncols):
996                 m = find_token(document.body, "<column", m)
997                 width = get_option_value(document.body[m], "width")
998                 varwidth = get_option_value(document.body[m], "varwidth")
999                 alignment = get_option_value(document.body[m], "alignment")
1000                 special = get_option_value(document.body[m], "special")
1001                 col_info.append([width, varwidth, alignment, special, m])
1002
1003             # Now parse cells
1004             m = i + 1
1005             lines = []
1006             for row in range(nrows):
1007                 for col in range(ncols):
1008                     m = find_token(document.body, "<cell", m)
1009                     multicolumn = get_option_value(document.body[m], "multicolumn")
1010                     multirow = get_option_value(document.body[m], "multirow")
1011                     width = get_option_value(document.body[m], "width")
1012                     rotate = get_option_value(document.body[m], "rotate")
1013                     # Check for: linebreaks, multipars, non-standard environments
1014                     begcell = m
1015                     endcell = find_token(document.body, "</cell>", begcell)
1016                     vcand = False
1017                     if (
1018                         find_token(document.body, "\\begin_inset Newline", begcell, endcell)
1019                         != -1
1020                     ):
1021                         vcand = True
1022                     elif count_pars_in_inset(document.body, begcell + 2) > 1:
1023                         vcand = True
1024                     elif get_value(document.body, "\\begin_layout", begcell) != "Plain Layout":
1025                         vcand = True
1026                     if (
1027                         vcand
1028                         and rotate == ""
1029                         and ((multicolumn == "" and multirow == "") or width == "")
1030                     ):
1031                         if (
1032                             col_info[col][0] == ""
1033                             and col_info[col][1] == ""
1034                             and col_info[col][3] == ""
1035                         ):
1036                             needvarwidth = True
1037                             alignment = col_info[col][2]
1038                             col_line = col_info[col][4]
1039                             vval = ""
1040                             if alignment == "center":
1041                                 vval = ">{\\centering}"
1042                             elif alignment == "left":
1043                                 vval = ">{\\raggedright}"
1044                             elif alignment == "right":
1045                                 vval = ">{\\raggedleft}"
1046                             if vval != "":
1047                                 needarray = True
1048                             vval += "V{\\linewidth}"
1049
1050                             document.body[col_line] = (
1051                                 document.body[col_line][:-1] + ' special="' + vval + '">'
1052                             )
1053                             # ERT newlines and linebreaks (since LyX < 2.4 automatically inserts parboxes
1054                             # with newlines, and we do not want that)
1055                             while True:
1056                                 endcell = find_token(document.body, "</cell>", begcell)
1057                                 linebreak = False
1058                                 nl = find_token(
1059                                     document.body,
1060                                     "\\begin_inset Newline newline",
1061                                     begcell,
1062                                     endcell,
1063                                 )
1064                                 if nl == -1:
1065                                     nl = find_token(
1066                                         document.body,
1067                                         "\\begin_inset Newline linebreak",
1068                                         begcell,
1069                                         endcell,
1070                                     )
1071                                     if nl == -1:
1072                                         break
1073                                     linebreak = True
1074                                 nle = find_end_of_inset(document.body, nl)
1075                                 del document.body[nle : nle + 1]
1076                                 if linebreak:
1077                                     document.body[nl : nl + 1] = put_cmd_in_ert("\\linebreak{}")
1078                                 else:
1079                                     document.body[nl : nl + 1] = put_cmd_in_ert("\\\\")
1080                     m += 1
1081
1082             i = j
1083
1084     finally:
1085         if needarray == True:
1086             add_to_preamble(document, ["\\usepackage{array}"])
1087         if needvarwidth == True:
1088             add_to_preamble(document, ["\\usepackage{varwidth}"])
1089
1090
1091 def revert_bibencoding(document):
1092     """Revert bibliography encoding"""
1093
1094     # Get cite engine
1095     engine = "basic"
1096     i = find_token(document.header, "\\cite_engine", 0)
1097     if i == -1:
1098         document.warning("Malformed document! Missing \\cite_engine")
1099     else:
1100         engine = get_value(document.header, "\\cite_engine", i)
1101
1102     # Check if biblatex
1103     biblatex = False
1104     if engine in ["biblatex", "biblatex-natbib"]:
1105         biblatex = True
1106
1107     # Map lyx to latex encoding names
1108     encodings = {
1109         "utf8": "utf8",
1110         "utf8x": "utf8x",
1111         "armscii8": "armscii8",
1112         "iso8859-1": "latin1",
1113         "iso8859-2": "latin2",
1114         "iso8859-3": "latin3",
1115         "iso8859-4": "latin4",
1116         "iso8859-5": "iso88595",
1117         "iso8859-6": "8859-6",
1118         "iso8859-7": "iso-8859-7",
1119         "iso8859-8": "8859-8",
1120         "iso8859-9": "latin5",
1121         "iso8859-13": "latin7",
1122         "iso8859-15": "latin9",
1123         "iso8859-16": "latin10",
1124         "applemac": "applemac",
1125         "cp437": "cp437",
1126         "cp437de": "cp437de",
1127         "cp850": "cp850",
1128         "cp852": "cp852",
1129         "cp855": "cp855",
1130         "cp858": "cp858",
1131         "cp862": "cp862",
1132         "cp865": "cp865",
1133         "cp866": "cp866",
1134         "cp1250": "cp1250",
1135         "cp1251": "cp1251",
1136         "cp1252": "cp1252",
1137         "cp1255": "cp1255",
1138         "cp1256": "cp1256",
1139         "cp1257": "cp1257",
1140         "koi8-r": "koi8-r",
1141         "koi8-u": "koi8-u",
1142         "pt154": "pt154",
1143         "utf8-platex": "utf8",
1144         "ascii": "ascii",
1145     }
1146
1147     i = 0
1148     while True:
1149         i = find_token(document.body, "\\begin_inset CommandInset bibtex", i + 1)
1150         if i == -1:
1151             break
1152         j = find_end_of_inset(document.body, i)
1153         if j == -1:
1154             document.warning("Can't find end of bibtex inset at line %d!!" % (i))
1155             continue
1156         encoding = get_quoted_value(document.body, "encoding", i, j)
1157         if not encoding:
1158             continue
1159         # remove encoding line
1160         k = find_token(document.body, "encoding", i, j)
1161         if k != -1:
1162             del document.body[k]
1163         if encoding == "default":
1164             continue
1165         # Re-find inset end line
1166         j = find_end_of_inset(document.body, i)
1167         if biblatex:
1168             biblio_options = ""
1169             h = find_token(document.header, "\\biblio_options", 0)
1170             if h != -1:
1171                 biblio_options = get_value(document.header, "\\biblio_options", h)
1172                 if "bibencoding" not in biblio_options:
1173                     document.header[h] += ",bibencoding=%s" % encodings[encoding]
1174             else:
1175                 bs = find_token(document.header, "\\biblatex_bibstyle", 0)
1176                 if bs == -1:
1177                     # this should not happen
1178                     document.warning(
1179                         "Malformed LyX document! No \\biblatex_bibstyle header found!"
1180                     )
1181                 else:
1182                     document.header[bs - 1 : bs - 1] = [
1183                         "\\biblio_options bibencoding=" + encodings[encoding]
1184                     ]
1185         else:
1186             document.body[j + 1 : j + 1] = put_cmd_in_ert("\\egroup")
1187             document.body[i:i] = put_cmd_in_ert(
1188                 "\\bgroup\\inputencoding{" + encodings[encoding] + "}"
1189             )
1190
1191         i = j
1192
1193
1194 def convert_vcsinfo(document):
1195     """Separate vcs Info inset from buffer Info inset."""
1196
1197     types = {
1198         "vcs-revision": "revision",
1199         "vcs-tree-revision": "tree-revision",
1200         "vcs-author": "author",
1201         "vcs-time": "time",
1202         "vcs-date": "date",
1203     }
1204     i = 0
1205     while True:
1206         i = find_token(document.body, "\\begin_inset Info", i + 1)
1207         if i == -1:
1208             return
1209         j = find_end_of_inset(document.body, i + 1)
1210         if j == -1:
1211             document.warning("Malformed LyX document: Could not find end of Info inset.")
1212             continue
1213         tp = find_token(document.body, "type", i, j)
1214         tpv = get_quoted_value(document.body, "type", tp)
1215         if tpv != "buffer":
1216             continue
1217         arg = find_token(document.body, "arg", i, j)
1218         argv = get_quoted_value(document.body, "arg", arg)
1219         if argv not in list(types.keys()):
1220             continue
1221         document.body[tp] = 'type "vcs"'
1222         document.body[arg] = 'arg "' + types[argv] + '"'
1223
1224
1225 def revert_vcsinfo(document):
1226     """Merge vcs Info inset to buffer Info inset."""
1227
1228     args = ["revision", "tree-revision", "author", "time", "date"]
1229     i = 0
1230     while True:
1231         i = find_token(document.body, "\\begin_inset Info", i + 1)
1232         if i == -1:
1233             return
1234         j = find_end_of_inset(document.body, i + 1)
1235         if j == -1:
1236             document.warning("Malformed LyX document: Could not find end of Info inset.")
1237             continue
1238         tp = find_token(document.body, "type", i, j)
1239         tpv = get_quoted_value(document.body, "type", tp)
1240         if tpv != "vcs":
1241             continue
1242         arg = find_token(document.body, "arg", i, j)
1243         argv = get_quoted_value(document.body, "arg", arg)
1244         if argv not in args:
1245             document.warning("Malformed Info inset. Invalid vcs arg.")
1246             continue
1247         document.body[tp] = 'type "buffer"'
1248         document.body[arg] = 'arg "vcs-' + argv + '"'
1249
1250
1251 def revert_vcsinfo_rev_abbrev(document):
1252     "Convert abbreviated revisions to regular revisions."
1253
1254     i = 0
1255     while True:
1256         i = find_token(document.body, "\\begin_inset Info", i + 1)
1257         if i == -1:
1258             return
1259         j = find_end_of_inset(document.body, i + 1)
1260         if j == -1:
1261             document.warning("Malformed LyX document: Could not find end of Info inset.")
1262             continue
1263         tp = find_token(document.body, "type", i, j)
1264         tpv = get_quoted_value(document.body, "type", tp)
1265         if tpv != "vcs":
1266             continue
1267         arg = find_token(document.body, "arg", i, j)
1268         argv = get_quoted_value(document.body, "arg", arg)
1269         if argv == "revision-abbrev":
1270             document.body[arg] = 'arg "revision"'
1271
1272
1273 def revert_dateinfo(document):
1274     """Revert date info insets to static text."""
1275
1276     # FIXME This currently only considers the main language and uses the system locale
1277     # Ideally, it should honor context languages and switch the locale accordingly.
1278
1279     # The date formats for each language using strftime syntax:
1280     # long, short, loclong, locmedium, locshort
1281     dateformats = {
1282         "afrikaans": ["%A, %d %B %Y", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y/%m/%d"],
1283         "albanian": ["%A, %d %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1284         "american": ["%A, %B %d, %Y", "%m/%d/%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1285         "amharic": ["%A ፣%d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1286         "ancientgreek": [
1287             "%A, %d %B %Y",
1288             "%d %b %Y",
1289             "%d %B %Y",
1290             "%d %b %Y",
1291             "%d/%m/%Y",
1292         ],
1293         "arabic_arabi": [
1294             "%A، %d %B، %Y",
1295             "%d‏/%m‏/%Y",
1296             "%d %B، %Y",
1297             "%d/%m/%Y",
1298             "%d/%m/%Y",
1299         ],
1300         "arabic_arabtex": [
1301             "%A، %d %B، %Y",
1302             "%d‏/%m‏/%Y",
1303             "%d %B، %Y",
1304             "%d/%m/%Y",
1305             "%d/%m/%Y",
1306         ],
1307         "armenian": [
1308             "%Y թ. %B %d, %A",
1309             "%d.%m.%y",
1310             "%d %B، %Y",
1311             "%d %b، %Y",
1312             "%d/%m/%Y",
1313         ],
1314         "asturian": [
1315             "%A, %d %B de %Y",
1316             "%d/%m/%y",
1317             "%d de %B de %Y",
1318             "%d %b %Y",
1319             "%d/%m/%Y",
1320         ],
1321         "australian": ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1322         "austrian": ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1323         "bahasa": ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1324         "bahasam": ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1325         "basque": ["%Y(e)ko %B %d, %A", "%y/%m/%d", "%Y %B %d", "%Y %b %d", "%Y/%m/%d"],
1326         "belarusian": [
1327             "%A, %d %B %Y г.",
1328             "%d.%m.%y",
1329             "%d %B %Y",
1330             "%d %b %Y",
1331             "%d.%m.%Y",
1332         ],
1333         "bosnian": [
1334             "%A, %d. %B %Y.",
1335             "%d.%m.%y.",
1336             "%d. %B %Y",
1337             "%d. %b %Y",
1338             "%Y-%m-%d",
1339         ],
1340         "brazilian": [
1341             "%A, %d de %B de %Y",
1342             "%d/%m/%Y",
1343             "%d de %B de %Y",
1344             "%d de %b de %Y",
1345             "%d/%m/%Y",
1346         ],
1347         "breton": ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1348         "british": ["%A, %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1349         "bulgarian": [
1350             "%A, %d %B %Y г.",
1351             "%d.%m.%y г.",
1352             "%d %B %Y",
1353             "%d %b %Y",
1354             "%Y-%m-%d",
1355         ],
1356         "canadian": ["%A, %B %d, %Y", "%Y-%m-%d", "%B %d, %Y", "%d %b %Y", "%Y-%m-%d"],
1357         "canadien": ["%A %d %B %Y", "%y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1358         "catalan": [
1359             "%A, %d %B de %Y",
1360             "%d/%m/%y",
1361             "%d / %B / %Y",
1362             "%d / %b / %Y",
1363             "%d/%m/%Y",
1364         ],
1365         "chinese-simplified": [
1366             "%Y年%m月%d日%A",
1367             "%Y/%m/%d",
1368             "%Y年%m月%d日",
1369             "%Y-%m-%d",
1370             "%y-%m-%d",
1371         ],
1372         "chinese-traditional": [
1373             "%Y年%m月%d日 %A",
1374             "%Y/%m/%d",
1375             "%Y年%m月%d日",
1376             "%Y年%m月%d日",
1377             "%y年%m月%d日",
1378         ],
1379         "coptic": ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1380         "croatian": [
1381             "%A, %d. %B %Y.",
1382             "%d. %m. %Y.",
1383             "%d. %B %Y.",
1384             "%d. %b. %Y.",
1385             "%d.%m.%Y.",
1386         ],
1387         "czech": ["%A %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b. %Y", "%d.%m.%Y"],
1388         "danish": [
1389             "%A den %d. %B %Y",
1390             "%d/%m/%Y",
1391             "%d. %B %Y",
1392             "%d. %b %Y",
1393             "%d/%m/%Y",
1394         ],
1395         "divehi": ["%Y %B %d, %A", "%Y-%m-%d", "%Y %B %d", "%Y %b %d", "%d/%m/%Y"],
1396         "dutch": ["%A %d %B %Y", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1397         "english": ["%A, %B %d, %Y", "%m/%d/%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1398         "esperanto": [
1399             "%A, %d %B %Y",
1400             "%d %b %Y",
1401             "la %d de %B %Y",
1402             "la %d de %b %Y",
1403             "%m/%d/%Y",
1404         ],
1405         "estonian": ["%A, %d. %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1406         "farsi": ["%A %d %B %Y", "%Y/%m/%d", "%d %B %Y", "%d %b %Y", "%Y/%m/%d"],
1407         "finnish": ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1408         "french": ["%A %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1409         "friulan": [
1410             "%A %d di %B dal %Y",
1411             "%d/%m/%y",
1412             "%d di %B dal %Y",
1413             "%d di %b dal %Y",
1414             "%d/%m/%Y",
1415         ],
1416         "galician": [
1417             "%A, %d de %B de %Y",
1418             "%d/%m/%y",
1419             "%d de %B de %Y",
1420             "%d de %b de %Y",
1421             "%d/%m/%Y",
1422         ],
1423         "georgian": ["%A, %d %B, %Y", "%d.%m.%y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1424         "german": ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1425         "german-ch": [
1426             "%A, %d. %B %Y",
1427             "%d.%m.%y",
1428             "%d. %B %Y",
1429             "%d. %b %Y",
1430             "%d.%m.%Y",
1431         ],
1432         "german-ch-old": [
1433             "%A, %d. %B %Y",
1434             "%d.%m.%y",
1435             "%d. %B %Y",
1436             "%d. %b %Y",
1437             "%d.%m.%Y",
1438         ],
1439         "greek": ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1440         "hebrew": ["%A, %d ב%B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1441         "hindi": ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1442         "icelandic": [
1443             "%A, %d. %B %Y",
1444             "%d.%m.%Y",
1445             "%d. %B %Y",
1446             "%d. %b %Y",
1447             "%d.%m.%Y",
1448         ],
1449         "interlingua": [
1450             "%Y %B %d, %A",
1451             "%Y-%m-%d",
1452             "le %d de %B %Y",
1453             "le %d de %b %Y",
1454             "%Y-%m-%d",
1455         ],
1456         "irish": ["%A %d %B %Y", "%d/%m/%Y", "%d. %B %Y", "%d. %b %Y", "%d/%m/%Y"],
1457         "italian": ["%A %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d/%b/%Y", "%d/%m/%Y"],
1458         "japanese": [
1459             "%Y年%m月%d日%A",
1460             "%Y/%m/%d",
1461             "%Y年%m月%d日",
1462             "%Y/%m/%d",
1463             "%y/%m/%d",
1464         ],
1465         "japanese-cjk": [
1466             "%Y年%m月%d日%A",
1467             "%Y/%m/%d",
1468             "%Y年%m月%d日",
1469             "%Y/%m/%d",
1470             "%y/%m/%d",
1471         ],
1472         "kannada": ["%A, %B %d, %Y", "%d/%m/%y", "%d %B %Y", "%d %B %Y", "%d-%m-%Y"],
1473         "kazakh": ["%Y ж. %d %B, %A", "%d.%m.%y", "%d %B %Y", "%d %B %Y", "%Y-%d-%m"],
1474         "khmer": ["%A %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %B %Y", "%d/%m/%Y"],
1475         "korean": [
1476             "%Y년 %m월 %d일 %A",
1477             "%y. %m. %d.",
1478             "%Y년 %m월 %d일",
1479             "%Y. %m. %d.",
1480             "%y. %m. %d.",
1481         ],
1482         "kurmanji": ["%A, %d %B %Y", "%d %b %Y", "%d. %B %Y", "%d. %m. %Y", "%Y-%m-%d"],
1483         "lao": ["%A ທີ %d %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %B %Y", "%d/%m/%Y"],
1484         "latin": ["%A, %d %B %Y", "%d %b %Y", "%B %d, %Y", "%b %d, %Y", "%m/%d/%Y"],
1485         "latvian": [
1486             "%A, %Y. gada %d. %B",
1487             "%d.%m.%y",
1488             "%Y. gada %d. %B",
1489             "%Y. gada %d. %b",
1490             "%d.%m.%Y",
1491         ],
1492         "lithuanian": [
1493             "%Y m. %B %d d., %A",
1494             "%Y-%m-%d",
1495             "%Y m. %B %d d.",
1496             "%Y m. %B %d d.",
1497             "%Y-%m-%d",
1498         ],
1499         "lowersorbian": [
1500             "%A, %d. %B %Y",
1501             "%d.%m.%y",
1502             "%d %B %Y",
1503             "%d %b %Y",
1504             "%d.%m.%Y",
1505         ],
1506         "macedonian": ["%A, %d %B %Y", "%d.%m.%y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1507         "magyar": [
1508             "%Y. %B %d., %A",
1509             "%Y. %m. %d.",
1510             "%Y. %B %d.",
1511             "%Y. %b %d.",
1512             "%Y.%m.%d.",
1513         ],
1514         "malayalam": ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1515         "marathi": ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1516         "mongolian": [
1517             "%A, %Y оны %m сарын %d",
1518             "%Y-%m-%d",
1519             "%Y оны %m сарын %d",
1520             "%d-%m-%Y",
1521             "%d-%m-%Y",
1522         ],
1523         "naustrian": [
1524             "%A, %d. %B %Y",
1525             "%d.%m.%y",
1526             "%d. %B %Y",
1527             "%d. %b %Y",
1528             "%d.%m.%Y",
1529         ],
1530         "newzealand": ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1531         "ngerman": ["%A, %d. %B %Y", "%d.%m.%y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1532         "norsk": ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1533         "nynorsk": ["%A %d. %B %Y", "%d.%m.%Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1534         "occitan": ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1535         "piedmontese": [
1536             "%A, %d %B %Y",
1537             "%d %b %Y",
1538             "%B %d, %Y",
1539             "%b %d, %Y",
1540             "%m/%d/%Y",
1541         ],
1542         "polish": ["%A, %d %B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1543         "polutonikogreek": [
1544             "%A, %d %B %Y",
1545             "%d/%m/%y",
1546             "%d %B %Y",
1547             "%d %b %Y",
1548             "%d/%m/%Y",
1549         ],
1550         "portuguese": [
1551             "%A, %d de %B de %Y",
1552             "%d/%m/%y",
1553             "%d de %B de %Y",
1554             "%d de %b de %Y",
1555             "%Y/%m/%d",
1556         ],
1557         "romanian": ["%A, %d %B %Y", "%d.%m.%Y", "%d %B %Y", "%d %b %Y", "%d.%m.%Y"],
1558         "romansh": [
1559             "%A, ils %d da %B %Y",
1560             "%d-%m-%y",
1561             "%d %B %Y",
1562             "%d %b %Y",
1563             "%d.%m.%Y",
1564         ],
1565         "russian": [
1566             "%A, %d %B %Y г.",
1567             "%d.%m.%Y",
1568             "%d %B %Y г.",
1569             "%d %b %Y г.",
1570             "%d.%m.%Y",
1571         ],
1572         "samin": [
1573             "%Y %B %d, %A",
1574             "%Y-%m-%d",
1575             "%B %d. b. %Y",
1576             "%b %d. b. %Y",
1577             "%d.%m.%Y",
1578         ],
1579         "sanskrit": ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1580         "scottish": ["%A, %dmh %B %Y", "%d/%m/%Y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1581         "serbian": [
1582             "%A, %d. %B %Y.",
1583             "%d.%m.%y.",
1584             "%d. %B %Y",
1585             "%d. %b %Y",
1586             "%d.%m.%Y",
1587         ],
1588         "serbian-latin": [
1589             "%A, %d. %B %Y.",
1590             "%d.%m.%y.",
1591             "%d. %B %Y",
1592             "%d. %b %Y",
1593             "%d.%m.%Y",
1594         ],
1595         "slovak": ["%A, %d. %B %Y", "%d. %m. %Y", "%d. %B %Y", "%d. %b %Y", "%d.%m.%Y"],
1596         "slovene": [
1597             "%A, %d. %B %Y",
1598             "%d. %m. %y",
1599             "%d. %B %Y",
1600             "%d. %b %Y",
1601             "%d.%m.%Y",
1602         ],
1603         "spanish": [
1604             "%A, %d de %B de %Y",
1605             "%d/%m/%y",
1606             "%d de %B %de %Y",
1607             "%d %b %Y",
1608             "%d/%m/%Y",
1609         ],
1610         "spanish-mexico": [
1611             "%A, %d de %B %de %Y",
1612             "%d/%m/%y",
1613             "%d de %B de %Y",
1614             "%d %b %Y",
1615             "%d/%m/%Y",
1616         ],
1617         "swedish": ["%A %d %B %Y", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%Y-%m-%d"],
1618         "syriac": ["%Y %B %d, %A", "%Y-%m-%d", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1619         "tamil": ["%A, %d %B, %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1620         "telugu": ["%d, %B %Y, %A", "%d-%m-%y", "%d %B %Y", "%d %b %Y", "%d-%m-%Y"],
1621         "thai": ["%Aที่ %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1622         "tibetan": [
1623             "%Y %Bའི་ཚེས་%d, %A",
1624             "%Y-%m-%d",
1625             "%B %d, %Y",
1626             "%b %d, %Y",
1627             "%m/%d/%Y",
1628         ],
1629         "turkish": ["%d %B %Y %A", "%d.%m.%Y", "%d %B %Y", "%d.%b.%Y", "%d.%m.%Y"],
1630         "turkmen": [
1631             "%d %B %Y %A",
1632             "%d.%m.%Y",
1633             "%Y ý. %B %d",
1634             "%d.%m.%Y ý.",
1635             "%d.%m.%y ý.",
1636         ],
1637         "ukrainian": [
1638             "%A, %d %B %Y р.",
1639             "%d.%m.%y",
1640             "%d %B %Y",
1641             "%d %m %Y",
1642             "%d.%m.%Y",
1643         ],
1644         "uppersorbian": [
1645             "%A, %d. %B %Y",
1646             "%d.%m.%y",
1647             "%d %B %Y",
1648             "%d %b %Y",
1649             "%d.%m.%Y",
1650         ],
1651         "urdu": ["%A، %d %B، %Y", "%d/%m/%y", "%d %B, %Y", "%d %b %Y", "%d/%m/%Y"],
1652         "vietnamese": [
1653             "%A, %d %B, %Y",
1654             "%d/%m/%Y",
1655             "%d tháng %B %Y",
1656             "%d-%m-%Y",
1657             "%d/%m/%Y",
1658         ],
1659         "welsh": ["%A, %d %B %Y", "%d/%m/%y", "%d %B %Y", "%d %b %Y", "%d/%m/%Y"],
1660     }
1661
1662     types = ["date", "fixdate", "moddate"]
1663     lang = get_value(document.header, "\\language")
1664     if lang == "":
1665         document.warning("Malformed LyX document! No \\language header found!")
1666         return
1667
1668     i = 0
1669     while True:
1670         i = find_token(document.body, "\\begin_inset Info", i + 1)
1671         if i == -1:
1672             return
1673         j = find_end_of_inset(document.body, i + 1)
1674         if j == -1:
1675             document.warning("Malformed LyX document: Could not find end of Info inset.")
1676             continue
1677         tp = find_token(document.body, "type", i, j)
1678         tpv = get_quoted_value(document.body, "type", tp)
1679         if tpv not in types:
1680             continue
1681         arg = find_token(document.body, "arg", i, j)
1682         argv = get_quoted_value(document.body, "arg", arg)
1683         isodate = ""
1684         dte = date.today()
1685         if tpv == "fixdate":
1686             datecomps = argv.split("@")
1687             if len(datecomps) > 1:
1688                 argv = datecomps[0]
1689                 isodate = datecomps[1]
1690                 m = re.search(r"(\d\d\d\d)-(\d\d)-(\d\d)", isodate)
1691                 if m:
1692                     dte = date(int(m.group(1)), int(m.group(2)), int(m.group(3)))
1693         # FIXME if we had the path to the original document (not the one in the tmp dir),
1694         #        we could use the mtime.
1695         #        elif tpv == "moddate":
1696         #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
1697         result = ""
1698         if argv == "ISO":
1699             result = dte.isodate()
1700         elif argv == "long":
1701             result = dte.strftime(dateformats[lang][0])
1702         elif argv == "short":
1703             result = dte.strftime(dateformats[lang][1])
1704         elif argv == "loclong":
1705             result = dte.strftime(dateformats[lang][2])
1706         elif argv == "locmedium":
1707             result = dte.strftime(dateformats[lang][3])
1708         elif argv == "locshort":
1709             result = dte.strftime(dateformats[lang][4])
1710         else:
1711             fmt = (
1712                 argv.replace("MMMM", "%b")
1713                 .replace("MMM", "%b")
1714                 .replace("MM", "%m")
1715                 .replace("M", "%m")
1716             )
1717             fmt = fmt.replace("yyyy", "%Y").replace("yy", "%y")
1718             fmt = fmt.replace("dddd", "%A").replace("ddd", "%a").replace("dd", "%d")
1719             fmt = re.sub("[^'%]d", "%d", fmt)
1720             fmt = fmt.replace("'", "")
1721             result = dte.strftime(fmt)
1722         document.body[i : j + 1] = [result]
1723
1724
1725 def revert_timeinfo(document):
1726     """Revert time info insets to static text."""
1727
1728     # FIXME This currently only considers the main language and uses the system locale
1729     # Ideally, it should honor context languages and switch the locale accordingly.
1730     # Also, the time object is "naive", i.e., it does not know of timezones (%Z will
1731     # be empty).
1732
1733     # The time formats for each language using strftime syntax:
1734     # long, short
1735     timeformats = {
1736         "afrikaans": ["%H:%M:%S %Z", "%H:%M"],
1737         "albanian": ["%I:%M:%S %p, %Z", "%I:%M %p"],
1738         "american": ["%I:%M:%S %p %Z", "%I:%M %p"],
1739         "amharic": ["%I:%M:%S %p %Z", "%I:%M %p"],
1740         "ancientgreek": ["%H:%M:%S %Z", "%H:%M:%S"],
1741         "arabic_arabi": ["%I:%M:%S %p %Z", "%I:%M %p"],
1742         "arabic_arabtex": ["%I:%M:%S %p %Z", "%I:%M %p"],
1743         "armenian": ["%H:%M:%S %Z", "%H:%M"],
1744         "asturian": ["%H:%M:%S %Z", "%H:%M"],
1745         "australian": ["%I:%M:%S %p %Z", "%I:%M %p"],
1746         "austrian": ["%H:%M:%S %Z", "%H:%M"],
1747         "bahasa": ["%H.%M.%S %Z", "%H.%M"],
1748         "bahasam": ["%I:%M:%S %p %Z", "%I:%M %p"],
1749         "basque": ["%H:%M:%S (%Z)", "%H:%M"],
1750         "belarusian": ["%H:%M:%S, %Z", "%H:%M"],
1751         "bosnian": ["%H:%M:%S %Z", "%H:%M"],
1752         "brazilian": ["%H:%M:%S %Z", "%H:%M"],
1753         "breton": ["%H:%M:%S %Z", "%H:%M"],
1754         "british": ["%H:%M:%S %Z", "%H:%M"],
1755         "bulgarian": ["%H:%M:%S %Z", "%H:%M"],
1756         "canadian": ["%I:%M:%S %p %Z", "%I:%M %p"],
1757         "canadien": ["%H:%M:%S %Z", "%H h %M"],
1758         "catalan": ["%H:%M:%S %Z", "%H:%M"],
1759         "chinese-simplified": ["%Z %p%I:%M:%S", "%p%I:%M"],
1760         "chinese-traditional": ["%p%I:%M:%S [%Z]", "%p%I:%M"],
1761         "coptic": ["%H:%M:%S %Z", "%H:%M:%S"],
1762         "croatian": ["%H:%M:%S (%Z)", "%H:%M"],
1763         "czech": ["%H:%M:%S %Z", "%H:%M"],
1764         "danish": ["%H.%M.%S %Z", "%H.%M"],
1765         "divehi": ["%H:%M:%S %Z", "%H:%M"],
1766         "dutch": ["%H:%M:%S %Z", "%H:%M"],
1767         "english": ["%I:%M:%S %p %Z", "%I:%M %p"],
1768         "esperanto": ["%H:%M:%S %Z", "%H:%M:%S"],
1769         "estonian": ["%H:%M:%S %Z", "%H:%M"],
1770         "farsi": ["%H:%M:%S (%Z)", "%H:%M"],
1771         "finnish": ["%H.%M.%S %Z", "%H.%M"],
1772         "french": ["%H:%M:%S %Z", "%H:%M"],
1773         "friulan": ["%H:%M:%S %Z", "%H:%M"],
1774         "galician": ["%H:%M:%S %Z", "%H:%M"],
1775         "georgian": ["%H:%M:%S %Z", "%H:%M"],
1776         "german": ["%H:%M:%S %Z", "%H:%M"],
1777         "german-ch": ["%H:%M:%S %Z", "%H:%M"],
1778         "german-ch-old": ["%H:%M:%S %Z", "%H:%M"],
1779         "greek": ["%I:%M:%S %p %Z", "%I:%M %p"],
1780         "hebrew": ["%H:%M:%S %Z", "%H:%M"],
1781         "hindi": ["%I:%M:%S %p %Z", "%I:%M %p"],
1782         "icelandic": ["%H:%M:%S %Z", "%H:%M"],
1783         "interlingua": ["%H:%M:%S %Z", "%H:%M"],
1784         "irish": ["%H:%M:%S %Z", "%H:%M"],
1785         "italian": ["%H:%M:%S %Z", "%H:%M"],
1786         "japanese": ["%H時%M分%S秒 %Z", "%H:%M"],
1787         "japanese-cjk": ["%H時%M分%S秒 %Z", "%H:%M"],
1788         "kannada": ["%I:%M:%S %p %Z", "%I:%M %p"],
1789         "kazakh": ["%H:%M:%S %Z", "%H:%M"],
1790         "khmer": ["%I:%M:%S %p %Z", "%I:%M %p"],
1791         "korean": ["%p %I시%M분 %S초 %Z", "%p %I:%M"],
1792         "kurmanji": ["%H:%M:%S %Z", "%H:%M:%S"],
1793         "lao": ["%H ໂມງ%M ນາທີ  %S ວິນາທີ %Z", "%H:%M"],
1794         "latin": ["%H:%M:%S %Z", "%H:%M:%S"],
1795         "latvian": ["%H:%M:%S %Z", "%H:%M"],
1796         "lithuanian": ["%H:%M:%S %Z", "%H:%M"],
1797         "lowersorbian": ["%H:%M:%S %Z", "%H:%M"],
1798         "macedonian": ["%H:%M:%S %Z", "%H:%M"],
1799         "magyar": ["%H:%M:%S %Z", "%H:%M"],
1800         "malayalam": ["%p %I:%M:%S %Z", "%p %I:%M"],
1801         "marathi": ["%I:%M:%S %p %Z", "%I:%M %p"],
1802         "mongolian": ["%H:%M:%S %Z", "%H:%M"],
1803         "naustrian": ["%H:%M:%S %Z", "%H:%M"],
1804         "newzealand": ["%I:%M:%S %p %Z", "%I:%M %p"],
1805         "ngerman": ["%H:%M:%S %Z", "%H:%M"],
1806         "norsk": ["%H:%M:%S %Z", "%H:%M"],
1807         "nynorsk": ["kl. %H:%M:%S %Z", "%H:%M"],
1808         "occitan": ["%H:%M:%S %Z", "%H:%M"],
1809         "piedmontese": ["%H:%M:%S %Z", "%H:%M:%S"],
1810         "polish": ["%H:%M:%S %Z", "%H:%M"],
1811         "polutonikogreek": ["%I:%M:%S %p %Z", "%I:%M %p"],
1812         "portuguese": ["%H:%M:%S %Z", "%H:%M"],
1813         "romanian": ["%H:%M:%S %Z", "%H:%M"],
1814         "romansh": ["%H:%M:%S %Z", "%H:%M"],
1815         "russian": ["%H:%M:%S %Z", "%H:%M"],
1816         "samin": ["%H:%M:%S %Z", "%H:%M"],
1817         "sanskrit": ["%H:%M:%S %Z", "%H:%M"],
1818         "scottish": ["%H:%M:%S %Z", "%H:%M"],
1819         "serbian": ["%H:%M:%S %Z", "%H:%M"],
1820         "serbian-latin": ["%H:%M:%S %Z", "%H:%M"],
1821         "slovak": ["%H:%M:%S %Z", "%H:%M"],
1822         "slovene": ["%H:%M:%S %Z", "%H:%M"],
1823         "spanish": ["%H:%M:%S (%Z)", "%H:%M"],
1824         "spanish-mexico": ["%H:%M:%S %Z", "%H:%M"],
1825         "swedish": ["kl. %H:%M:%S %Z", "%H:%M"],
1826         "syriac": ["%H:%M:%S %Z", "%H:%M"],
1827         "tamil": ["%p %I:%M:%S %Z", "%p %I:%M"],
1828         "telugu": ["%I:%M:%S %p %Z", "%I:%M %p"],
1829         "thai": ["%H นาฬิกา %M นาที  %S วินาที %Z", "%H:%M"],
1830         "tibetan": ["%I:%M:%S %p %Z", "%I:%M %p"],
1831         "turkish": ["%H:%M:%S %Z", "%H:%M"],
1832         "turkmen": ["%H:%M:%S %Z", "%H:%M"],
1833         "ukrainian": ["%H:%M:%S %Z", "%H:%M"],
1834         "uppersorbian": ["%H:%M:%S %Z", "%H:%M hodź."],
1835         "urdu": ["%I:%M:%S %p %Z", "%I:%M %p"],
1836         "vietnamese": ["%H:%M:%S %Z", "%H:%M"],
1837         "welsh": ["%H:%M:%S %Z", "%H:%M"],
1838     }
1839
1840     types = ["time", "fixtime", "modtime"]
1841     i = find_token(document.header, "\\language", 0)
1842     if i == -1:
1843         # this should not happen
1844         document.warning("Malformed LyX document! No \\language header found!")
1845         return
1846     lang = get_value(document.header, "\\language", i)
1847
1848     i = 0
1849     while True:
1850         i = find_token(document.body, "\\begin_inset Info", i + 1)
1851         if i == -1:
1852             return
1853         j = find_end_of_inset(document.body, i + 1)
1854         if j == -1:
1855             document.warning("Malformed LyX document: Could not find end of Info inset.")
1856             continue
1857         tp = find_token(document.body, "type", i, j)
1858         tpv = get_quoted_value(document.body, "type", tp)
1859         if tpv not in types:
1860             continue
1861         arg = find_token(document.body, "arg", i, j)
1862         argv = get_quoted_value(document.body, "arg", arg)
1863         isotime = ""
1864         dtme = datetime.now()
1865         tme = dtme.time()
1866         if tpv == "fixtime":
1867             timecomps = argv.split("@")
1868             if len(timecomps) > 1:
1869                 argv = timecomps[0]
1870                 isotime = timecomps[1]
1871                 m = re.search(r"(\d\d):(\d\d):(\d\d)", isotime)
1872                 if m:
1873                     tme = time(int(m.group(1)), int(m.group(2)), int(m.group(3)))
1874                 else:
1875                     m = re.search(r"(\d\d):(\d\d)", isotime)
1876                     if m:
1877                         tme = time(int(m.group(1)), int(m.group(2)))
1878         # FIXME if we had the path to the original document (not the one in the tmp dir),
1879         #        we could use the mtime.
1880         #        elif tpv == "moddate":
1881         #            dte = date.fromtimestamp(os.path.getmtime(document.dir))
1882         result = ""
1883         if argv == "ISO":
1884             result = tme.isoformat()
1885         elif argv == "long":
1886             result = tme.strftime(timeformats[lang][0])
1887         elif argv == "short":
1888             result = tme.strftime(timeformats[lang][1])
1889         else:
1890             fmt = (
1891                 argv.replace("HH", "%H")
1892                 .replace("H", "%H")
1893                 .replace("hh", "%I")
1894                 .replace("h", "%I")
1895             )
1896             fmt = (
1897                 fmt.replace("mm", "%M")
1898                 .replace("m", "%M")
1899                 .replace("ss", "%S")
1900                 .replace("s", "%S")
1901             )
1902             fmt = fmt.replace("zzz", "%f").replace("z", "%f").replace("t", "%Z")
1903             fmt = (
1904                 fmt.replace("AP", "%p")
1905                 .replace("ap", "%p")
1906                 .replace("A", "%p")
1907                 .replace("a", "%p")
1908             )
1909             fmt = fmt.replace("'", "")
1910             result = dte.strftime(fmt)
1911         document.body[i : j + 1] = result
1912
1913
1914 def revert_namenoextinfo(document):
1915     """Merge buffer Info inset type name-noext to name."""
1916
1917     i = 0
1918     while True:
1919         i = find_token(document.body, "\\begin_inset Info", i + 1)
1920         if i == -1:
1921             return
1922         j = find_end_of_inset(document.body, i + 1)
1923         if j == -1:
1924             document.warning("Malformed LyX document: Could not find end of Info inset.")
1925             continue
1926         tp = find_token(document.body, "type", i, j)
1927         tpv = get_quoted_value(document.body, "type", tp)
1928         if tpv != "buffer":
1929             continue
1930         arg = find_token(document.body, "arg", i, j)
1931         argv = get_quoted_value(document.body, "arg", arg)
1932         if argv != "name-noext":
1933             continue
1934         document.body[arg] = 'arg "name"'
1935
1936
1937 def revert_l7ninfo(document):
1938     """Revert l7n Info inset to text."""
1939
1940     i = 0
1941     while True:
1942         i = find_token(document.body, "\\begin_inset Info", i + 1)
1943         if i == -1:
1944             return
1945         j = find_end_of_inset(document.body, i + 1)
1946         if j == -1:
1947             document.warning("Malformed LyX document: Could not find end of Info inset.")
1948             continue
1949         tp = find_token(document.body, "type", i, j)
1950         tpv = get_quoted_value(document.body, "type", tp)
1951         if tpv != "l7n":
1952             continue
1953         arg = find_token(document.body, "arg", i, j)
1954         argv = get_quoted_value(document.body, "arg", arg)
1955         # remove trailing colons, menu accelerator (|...) and qt accelerator (&), while keeping literal " & "
1956         argv = (
1957             argv.rstrip(":")
1958             .split("|")[0]
1959             .replace(" & ", "</amp;>")
1960             .replace("&", "")
1961             .replace("</amp;>", " & ")
1962         )
1963         document.body[i : j + 1] = argv
1964
1965
1966 def revert_listpargs(document):
1967     """Reverts listpreamble arguments to TeX-code"""
1968     i = 0
1969     while True:
1970         i = find_token(document.body, "\\begin_inset Argument listpreamble:", i + 1)
1971         if i == -1:
1972             return
1973         j = find_end_of_inset(document.body, i)
1974         # Find containing paragraph layout
1975         parent = get_containing_layout(document.body, i)
1976         if parent == False:
1977             document.warning("Malformed LyX document: Can't find parent paragraph layout")
1978             continue
1979         parbeg = parent[3]
1980         beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
1981         endPlain = find_end_of_layout(document.body, beginPlain)
1982         content = document.body[beginPlain + 1 : endPlain]
1983         del document.body[i : j + 1]
1984         subst = (
1985             [
1986                 "\\begin_inset ERT",
1987                 "status collapsed",
1988                 "",
1989                 "\\begin_layout Plain Layout",
1990                 "{",
1991             ]
1992             + content
1993             + ["}", "\\end_layout", "", "\\end_inset", ""]
1994         )
1995         document.body[parbeg:parbeg] = subst
1996
1997
1998 def revert_lformatinfo(document):
1999     """Revert layout format Info inset to text."""
2000
2001     i = 0
2002     while True:
2003         i = find_token(document.body, "\\begin_inset Info", i + 1)
2004         if i == -1:
2005             return
2006         j = find_end_of_inset(document.body, i + 1)
2007         if j == -1:
2008             document.warning("Malformed LyX document: Could not find end of Info inset.")
2009             continue
2010         tp = find_token(document.body, "type", i, j)
2011         tpv = get_quoted_value(document.body, "type", tp)
2012         if tpv != "lyxinfo":
2013             continue
2014         arg = find_token(document.body, "arg", i, j)
2015         argv = get_quoted_value(document.body, "arg", arg)
2016         if argv != "layoutformat":
2017             continue
2018         # hardcoded for now
2019         document.body[i : j + 1] = "69"
2020
2021
2022 def convert_hebrew_parentheses(document):
2023     """Swap opening/closing parentheses in Hebrew text.
2024
2025     Up to LyX 2.4, "(" was used as closing parenthesis and
2026     ")" as opening parenthesis for Hebrew in the LyX source.
2027     """
2028     current_languages = [document.language]
2029     current_layouts = []
2030     current_insets = []
2031     # pass thru argument insets
2032     skip_layouts_arguments = {}
2033     skip_insets_arguments = {}
2034     # pass thru insets
2035     skip_insets = ["Formula", "ERT", "listings", "Flex URL"]
2036     # pass thru insets per document class
2037     if document.textclass in [
2038         "beamer",
2039         "scrarticle-beamer",
2040         "beamerposter",
2041         "article-beamer",
2042     ]:
2043         skip_layouts_arguments.update(
2044             {
2045                 "Itemize": ["1", "item:2"],
2046                 "Enumerate": ["1", "item:2"],
2047                 "Description": ["1", "item:1"],
2048                 "Part": ["1"],
2049                 "Section": ["1"],
2050                 "Section*": ["1"],
2051                 "Subsection": ["1"],
2052                 "Subsection*": ["1"],
2053                 "Subsubsection": ["1"],
2054                 "Subsubsection*": ["1"],
2055                 "Frame": ["1", "2"],
2056                 "AgainFrame": ["1", "2"],
2057                 "PlainFrame": ["1", "2"],
2058                 "FragileFrame": ["1", "2"],
2059                 "FrameTitle": ["1"],
2060                 "FrameSubtitle": ["1"],
2061                 "Overprint": ["item:1"],
2062                 "Uncover": ["1"],
2063                 "Only": ["1"],
2064                 "Block": ["1"],
2065                 "ExampleBlock": ["1"],
2066                 "AlertBlock": ["1"],
2067                 "Quotation": ["1"],
2068                 "Quote": ["1"],
2069                 "Verse": ["1"],
2070                 "Corollary": ["1"],
2071                 "Corollary": ["1"],
2072                 "Definition": ["1"],
2073                 "Definitions": ["1"],
2074                 "Example": ["1"],
2075                 "Examples": ["1"],
2076                 "Fact": ["1"],
2077                 "Lemma": ["1"],
2078                 "proof": ["1"],
2079                 "Theorem": ["1"],
2080                 "NoteItem": ["1"],
2081             }
2082         )
2083         skip_insets_arguments.update(
2084             {
2085                 "Flex Bold": ["1"],
2086                 "Flex Emphasize": ["1"],
2087                 "Flex Alert": ["1"],
2088                 "Flex Structure": ["1"],
2089                 "Flex Only": ["1"],
2090                 "Flex Uncover": ["1"],
2091                 "Flex Visible": ["1"],
2092                 "Flex Invisible": ["1"],
2093                 "Flex Alternative": ["1"],
2094                 "Flex Beamer Note": ["1"],
2095             }
2096         )
2097     elif document.textclass == "europecv":
2098         skip_layouts_arguments.update({"Picture": ["1"], "Item": ["1"], "MotherTongue": ["1"]})
2099     elif document.textclass in ["acmsiggraph", "acmsiggraph-0-92"]:
2100         skip_insets_arguments.update({"Flex CRcat": ["1", "2", "3"]})
2101     elif document.textclass in ["aastex", "aastex6", "aastex62"]:
2102         skip_layouts_arguments.update(
2103             {
2104                 "Altaffilation": ["1"],
2105             }
2106         )
2107     elif document.textclass == "jss":
2108         skip_insets.append("Flex Code Chunk")
2109     elif document.textclass == "moderncv":
2110         skip_layouts_arguments.update(
2111             {
2112                 "Photo": ["1", "2"],
2113             }
2114         )
2115         skip_insets_arguments.update({"Flex Column": ["1"]})
2116     elif document.textclass == "agutex":
2117         skip_layouts_arguments.update({"Author affiliation": ["1"]})
2118     elif document.textclass in ["ijmpd", "ijmpc"]:
2119         skip_layouts_arguments.update({"RomanList": ["1"]})
2120     elif document.textclass in ["jlreq-book", "jlreq-report", "jlreq-article"]:
2121         skip_insets.append("Flex Warichu*")
2122     # pathru insets per module
2123     if "hpstatement" in document.get_module_list():
2124         skip_insets.append("Flex H-P number")
2125     if "tcolorbox" in document.get_module_list():
2126         skip_layouts_arguments.update({"New Color Box Type": ["3"]})
2127     if "sweave" in document.get_module_list():
2128         skip_insets.extend(
2129             [
2130                 "Flex Sweave Options",
2131                 "Flex S/R expression",
2132                 "Flex Sweave Input File",
2133                 "Flex Chunk",
2134             ]
2135         )
2136     if "knitr" in document.get_module_list():
2137         skip_insets.extend(["Flex Sweave Options", "Flex S/R expression", "Flex Chunk"])
2138     if "linguistics" in document.get_module_list():
2139         skip_layouts_arguments.update(
2140             {
2141                 "Numbered Example (multiline)": ["1"],
2142                 "Numbered Examples (consecutive)": ["1"],
2143                 "Subexample": ["1"],
2144             }
2145         )
2146     if "chessboard" in document.get_module_list():
2147         skip_insets.append("Flex Mainline")
2148         skip_layouts_arguments.update({"NewChessGame": ["1"]})
2149         skip_insets_arguments.update({"Flex ChessBoard": ["1"]})
2150     if "lilypond" in document.get_module_list():
2151         skip_insets.append("Flex LilyPond")
2152     if "noweb" in document.get_module_list():
2153         skip_insets.append("Flex Chunk")
2154     if "multicol" in document.get_module_list():
2155         skip_insets_arguments.update({"Flex Multiple Columns": ["1"]})
2156     i = 0
2157     inset_is_arg = False
2158     while i < len(document.body):
2159         line = document.body[i]
2160         if line.startswith("\\lang "):
2161             tokenend = len("\\lang ")
2162             lang = line[tokenend:].strip()
2163             current_languages[-1] = lang
2164         elif line.startswith("\\begin_layout "):
2165             current_languages.append(current_languages[-1])
2166             tokenend = len("\\begin_layout ")
2167             layout = line[tokenend:].strip()
2168             current_layouts.append(layout)
2169         elif line.startswith("\\end_layout"):
2170             current_languages.pop()
2171             current_layouts.pop()
2172         elif line.startswith("\\begin_inset Argument "):
2173             tokenend = len("\\begin_inset Argument ")
2174             Argument = line[tokenend:].strip()
2175             # all listpreamble:1 arguments are pass thru
2176             listpreamble = Argument == "listpreamble:1"
2177             layout_arg = current_layouts and Argument in skip_layouts_arguments.get(
2178                 current_layouts[-1], []
2179             )
2180             inset_arg = current_insets and Argument in skip_insets_arguments.get(
2181                 current_insets[-1], []
2182             )
2183             if layout_arg or inset_arg or listpreamble:
2184                 # In these arguments, parentheses must not be changed
2185                 i = find_end_of_inset(document.body, i) + 1
2186                 continue
2187             else:
2188                 inset_is_arg = True
2189         elif line.startswith("\\begin_inset "):
2190             tokenend = len("\\begin_inset ")
2191             inset = line[tokenend:].strip()
2192             current_insets.append(inset)
2193             if inset in skip_insets:
2194                 # In these insets, parentheses must not be changed
2195                 i = find_end_of_inset(document.body, i)
2196                 continue
2197         elif line.startswith("\\end_inset"):
2198             if inset_is_arg:
2199                 inset_is_arg = is_in_inset(document.body, i, "\\begin_inset Argument")[0] != -1
2200             else:
2201                 current_insets.pop()
2202         elif current_languages[-1] == "hebrew" and not line.startswith("\\"):
2203             document.body[i] = line.replace("(", "\x00").replace(")", "(").replace("\x00", ")")
2204         i += 1
2205
2206
2207 def revert_hebrew_parentheses(document):
2208     """Store parentheses in Hebrew text reversed"""
2209     # This only exists to keep the convert/revert naming convention
2210     convert_hebrew_parentheses(document)
2211
2212
2213 def revert_malayalam(document):
2214     """Set the document language to English but assure Malayalam output"""
2215
2216     revert_language(document, "malayalam", "", "malayalam")
2217
2218
2219 def revert_soul(document):
2220     """Revert soul module flex insets to ERT"""
2221
2222     flexes = ["Spaceletters", "Strikethrough", "Underline", "Highlight", "Capitalize"]
2223
2224     for flex in flexes:
2225         i = find_token(document.body, "\\begin_inset Flex %s" % flex, 0)
2226         if i != -1:
2227             add_to_preamble(document, ["\\usepackage{soul}"])
2228             break
2229     i = find_token(document.body, "\\begin_inset Flex Highlight", 0)
2230     if i != -1:
2231         add_to_preamble(document, ["\\usepackage{color}"])
2232
2233     revert_flex_inset(document, "Spaceletters", "\\so")
2234     revert_flex_inset(document, "Strikethrough", "\\st")
2235     revert_flex_inset(document, "Underline", "\\ul")
2236     revert_flex_inset(document, "Highlight", "\\hl")
2237     revert_flex_inset(document, "Capitalize", "\\caps")
2238
2239
2240 def revert_tablestyle(document):
2241     """Remove tablestyle params"""
2242
2243     i = find_token(document.header, "\\tablestyle")
2244     if i != -1:
2245         del document.header[i]
2246
2247
2248 def revert_bibfileencodings(document):
2249     """Revert individual Biblatex bibliography encodings"""
2250
2251     # Get cite engine
2252     engine = "basic"
2253     i = find_token(document.header, "\\cite_engine", 0)
2254     if i == -1:
2255         document.warning("Malformed document! Missing \\cite_engine")
2256     else:
2257         engine = get_value(document.header, "\\cite_engine", i)
2258
2259     # Check if biblatex
2260     biblatex = False
2261     if engine in ["biblatex", "biblatex-natbib"]:
2262         biblatex = True
2263
2264     # Map lyx to latex encoding names
2265     encodings = {
2266         "utf8": "utf8",
2267         "utf8x": "utf8x",
2268         "armscii8": "armscii8",
2269         "iso8859-1": "latin1",
2270         "iso8859-2": "latin2",
2271         "iso8859-3": "latin3",
2272         "iso8859-4": "latin4",
2273         "iso8859-5": "iso88595",
2274         "iso8859-6": "8859-6",
2275         "iso8859-7": "iso-8859-7",
2276         "iso8859-8": "8859-8",
2277         "iso8859-9": "latin5",
2278         "iso8859-13": "latin7",
2279         "iso8859-15": "latin9",
2280         "iso8859-16": "latin10",
2281         "applemac": "applemac",
2282         "cp437": "cp437",
2283         "cp437de": "cp437de",
2284         "cp850": "cp850",
2285         "cp852": "cp852",
2286         "cp855": "cp855",
2287         "cp858": "cp858",
2288         "cp862": "cp862",
2289         "cp865": "cp865",
2290         "cp866": "cp866",
2291         "cp1250": "cp1250",
2292         "cp1251": "cp1251",
2293         "cp1252": "cp1252",
2294         "cp1255": "cp1255",
2295         "cp1256": "cp1256",
2296         "cp1257": "cp1257",
2297         "koi8-r": "koi8-r",
2298         "koi8-u": "koi8-u",
2299         "pt154": "pt154",
2300         "utf8-platex": "utf8",
2301         "ascii": "ascii",
2302     }
2303
2304     i = 0
2305     while True:
2306         i = find_token(document.body, "\\begin_inset CommandInset bibtex", i + 1)
2307         if i == -1:
2308             break
2309         j = find_end_of_inset(document.body, i)
2310         if j == -1:
2311             document.warning("Can't find end of bibtex inset at line %d!!" % (i))
2312             continue
2313         encodings = get_quoted_value(document.body, "file_encodings", i, j)
2314         if not encodings:
2315             i = j
2316             continue
2317         bibfiles = get_quoted_value(document.body, "bibfiles", i, j).split(",")
2318         opts = get_quoted_value(document.body, "biblatexopts", i, j)
2319         if len(bibfiles) == 0:
2320             document.warning("Bibtex inset at line %d does not have a bibfile!" % (i))
2321         # remove encoding line
2322         k = find_token(document.body, "file_encodings", i, j)
2323         if k != -1:
2324             del document.body[k]
2325         # Re-find inset end line
2326         j = find_end_of_inset(document.body, i)
2327         if biblatex:
2328             enclist = encodings.split("\t")
2329             encmap = dict()
2330             for pp in enclist:
2331                 ppp = pp.split(" ", 1)
2332                 encmap[ppp[0]] = ppp[1]
2333             for bib in bibfiles:
2334                 pr = "\\addbibresource"
2335                 if bib in encmap.keys():
2336                     pr += "[bibencoding=" + encmap[bib] + "]"
2337                 pr += "{" + bib + "}"
2338                 add_to_preamble(document, [pr])
2339             # Insert ERT \\printbibliography and wrap bibtex inset to a Note
2340             pcmd = "printbibliography"
2341             if opts:
2342                 pcmd += "[" + opts + "]"
2343             repl = [
2344                 "\\begin_inset ERT",
2345                 "status open",
2346                 "",
2347                 "\\begin_layout Plain Layout",
2348                 "",
2349                 "",
2350                 "\\backslash",
2351                 pcmd,
2352                 "\\end_layout",
2353                 "",
2354                 "\\end_inset",
2355                 "",
2356                 "",
2357                 "\\end_layout",
2358                 "",
2359                 "\\begin_layout Standard",
2360                 "\\begin_inset Note Note",
2361                 "status open",
2362                 "",
2363                 "\\begin_layout Plain Layout",
2364             ]
2365             repl += document.body[i : j + 1]
2366             repl += ["", "\\end_layout", "", "\\end_inset", "", ""]
2367             document.body[i : j + 1] = repl
2368             j += 27
2369
2370         i = j
2371
2372
2373 def revert_cmidruletrimming(document):
2374     """Remove \\cmidrule trimming"""
2375
2376     # FIXME: Revert to TeX code?
2377     i = 0
2378     while True:
2379         # first, let's find out if we need to do anything
2380         i = find_token(document.body, "<cell ", i + 1)
2381         if i == -1:
2382             return
2383         j = document.body[i].find('trim="')
2384         if j == -1:
2385             continue
2386         rgx = re.compile(r' (bottom|top)line[lr]trim="true"')
2387         # remove trim option
2388         document.body[i] = rgx.sub("", document.body[i])
2389
2390
2391 ruby_inset_def = [
2392     r"### Inserted by lyx2lyx (ruby inset) ###",
2393     r"InsetLayout Flex:Ruby",
2394     r"  LyxType       charstyle",
2395     r"  LatexType     command",
2396     r"  LatexName     ruby",
2397     r"  HTMLTag       ruby",
2398     r'  HTMLAttr      ""',
2399     r"  HTMLInnerTag  rb",
2400     r'  HTMLInnerAttr ""',
2401     r"  BgColor       none",
2402     r'  LabelString   "Ruby"',
2403     r"  Decoration    Conglomerate",
2404     r"  Preamble",
2405     r"    \ifdefined\kanjiskip",
2406     r"      \IfFileExists{okumacro.sty}{\usepackage{okumacro}}{}",
2407     r"    \else \ifdefined\luatexversion",
2408     r"      \usepackage{luatexja-ruby}",
2409     r"    \else \ifdefined\XeTeXversion",
2410     r"      \usepackage{ruby}%",
2411     r"    \fi\fi\fi",
2412     r"    \providecommand{\ruby}[2]{\shortstack{\tiny #2\\#1}}",
2413     r"  EndPreamble",
2414     r"  Argument  post:1",
2415     r'    LabelString  "ruby text"',
2416     r'    MenuString  "Ruby Text|R"',
2417     r'    Tooltip    "Reading aid (ruby, furigana) for Chinese characters."',
2418     r"    Decoration  Conglomerate",
2419     r"    Font",
2420     r"      Size    tiny",
2421     r"    EndFont",
2422     r"    LabelFont",
2423     r"      Size    tiny",
2424     r"    EndFont",
2425     r"    Mandatory  1",
2426     r"  EndArgument",
2427     r"End",
2428 ]
2429
2430
2431 def convert_ruby_module(document):
2432     """Use ruby module instead of local module definition"""
2433     if document.del_local_layout(ruby_inset_def):
2434         document.add_module("ruby")
2435
2436
2437 def revert_ruby_module(document):
2438     """Replace ruby module with local module definition"""
2439     if document.del_module("ruby"):
2440         document.append_local_layout(ruby_inset_def)
2441
2442
2443 def convert_utf8_japanese(document):
2444     """Use generic utf8 with Japanese documents."""
2445     lang = get_value(document.header, "\\language")
2446     if not lang.startswith("japanese"):
2447         return
2448     inputenc = get_value(document.header, "\\inputencoding")
2449     if (lang == "japanese" and inputenc == "utf8-platex") or (
2450         lang == "japanese-cjk" and inputenc == "utf8-cjk"
2451     ):
2452         document.set_parameter("inputencoding", "utf8")
2453
2454
2455 def revert_utf8_japanese(document):
2456     """Use Japanese utf8 variants with Japanese documents."""
2457     inputenc = get_value(document.header, "\\inputencoding")
2458     if inputenc != "utf8":
2459         return
2460     lang = get_value(document.header, "\\language")
2461     if lang == "japanese":
2462         document.set_parameter("inputencoding", "utf8-platex")
2463     if lang == "japanese-cjk":
2464         document.set_parameter("inputencoding", "utf8-cjk")
2465
2466
2467 def revert_lineno(document):
2468     "Replace lineno setting with user-preamble code."
2469
2470     options = get_quoted_value(document.header, "\\lineno_options", delete=True)
2471     if not get_bool_value(document.header, "\\use_lineno", delete=True):
2472         return
2473     if options:
2474         options = "[" + options + "]"
2475     add_to_preamble(document, ["\\usepackage%s{lineno}" % options, "\\linenumbers"])
2476
2477
2478 def convert_lineno(document):
2479     "Replace user-preamble code with native lineno support."
2480     use_lineno = 0
2481     options = ""
2482     i = find_token(document.preamble, "\\linenumbers", 1)
2483     if i > 0:
2484         usepkg = re.match(r"\\usepackage(.*){lineno}", document.preamble[i - 1])
2485         if usepkg:
2486             use_lineno = 1
2487             options = usepkg.group(1).strip("[]")
2488             del document.preamble[i - 1 : i + 1]
2489             if i > 1:
2490                 del_token(document.preamble, "% Added by lyx2lyx", i - 2, i - 1)
2491
2492     k = find_token(document.header, "\\index ")
2493     if options == "":
2494         document.header[k:k] = ["\\use_lineno %d" % use_lineno]
2495     else:
2496         document.header[k:k] = [
2497             "\\use_lineno %d" % use_lineno,
2498             "\\lineno_options %s" % options,
2499         ]
2500
2501
2502 def convert_aaencoding(document):
2503     "Convert default document option due to encoding change in aa class."
2504
2505     if document.textclass != "aa":
2506         return
2507
2508     i = find_token(document.header, "\\use_default_options true")
2509     if i == -1:
2510         return
2511     val = get_value(document.header, "\\inputencoding")
2512     if not val:
2513         document.warning("Malformed LyX Document! Missing '\\inputencoding' header.")
2514         return
2515     if val == "auto-legacy" or val == "latin9":
2516         document.header[i] = "\\use_default_options false"
2517         k = find_token(document.header, "\\options")
2518         if k == -1:
2519             document.header.insert(i, "\\options latin9")
2520         else:
2521             document.header[k] += ",latin9"
2522
2523
2524 def revert_aaencoding(document):
2525     "Revert default document option due to encoding change in aa class."
2526
2527     if document.textclass != "aa":
2528         return
2529
2530     i = find_token(document.header, "\\use_default_options true")
2531     if i == -1:
2532         return
2533     val = get_value(document.header, "\\inputencoding")
2534     if not val:
2535         document.warning("Malformed LyX Document! Missing \\inputencoding header.")
2536         return
2537     if val == "utf8":
2538         document.header[i] = "\\use_default_options false"
2539         k = find_token(document.header, "\\options", 0)
2540         if k == -1:
2541             document.header.insert(i, "\\options utf8")
2542         else:
2543             document.header[k] = document.header[k] + ",utf8"
2544
2545
2546 def revert_new_languages(document):
2547     """Emulate support for Azerbaijani, Bengali, Church Slavonic, Korean,
2548     and Russian (Petrine orthography)."""
2549
2550     #                lyxname:          (babelname, polyglossianame)
2551     new_languages = {
2552         "azerbaijani": ("azerbaijani", ""),
2553         "bengali": ("", "bengali"),
2554         "churchslavonic": ("", "churchslavonic"),
2555         "oldrussian": ("", "russian"),
2556         "korean": ("", "korean"),
2557     }
2558     if document.language in new_languages:
2559         used_languages = {document.language}
2560     else:
2561         used_languages = set()
2562     i = 0
2563     while True:
2564         i = find_token(document.body, "\\lang", i + 1)
2565         if i == -1:
2566             break
2567         val = get_value(document.body, "\\lang", i)
2568         if val in new_languages:
2569             used_languages.add(val)
2570
2571     # Korean is already supported via CJK, so leave as-is for Babel
2572     if "korean" in used_languages and (
2573         not get_bool_value(document.header, "\\use_non_tex_fonts")
2574         or get_value(document.header, "\\language_package") == "babel"
2575     ):
2576         used_languages.discard("korean")
2577
2578     for lang in used_languages:
2579         revert_language(document, lang, *new_languages[lang])
2580
2581
2582 gloss_inset_def = [
2583     r"### Inserted by lyx2lyx (deprecated ling glosses) ###",
2584     r"InsetLayout Flex:Glosse",
2585     r"  LyXType               custom",
2586     r'  LabelString           "Gloss (old version)"',
2587     r'  MenuString            "Gloss (old version)"',
2588     r"  LatexType             environment",
2589     r"  LatexName             linggloss",
2590     r"  Decoration            minimalistic",
2591     r"  LabelFont",
2592     r"    Size                Small",
2593     r"  EndFont",
2594     r"  MultiPar              true",
2595     r"  CustomPars            false",
2596     r"  ForcePlain            true",
2597     r"  ParbreakIsNewline     true",
2598     r"  FreeSpacing           true",
2599     r"  Requires              covington",
2600     r"  Preamble",
2601     r"          \def\glosstr{}",
2602     r"          \@ifundefined{linggloss}{%",
2603     r"          \newenvironment{linggloss}[2][]{",
2604     r"             \def\glosstr{\glt #1}%",
2605     r"             \gll #2}",
2606     r"          {\glosstr\glend}}{}",
2607     r"  EndPreamble",
2608     r"  InToc                 true",
2609     r"  ResetsFont            true",
2610     r"  Argument 1",
2611     r"          Decoration    conglomerate",
2612     r'          LabelString   "Translation"',
2613     r'          MenuString    "Glosse Translation|s"',
2614     r'          Tooltip       "Add a translation for the glosse"',
2615     r"  EndArgument",
2616     r"End",
2617 ]
2618
2619 glosss_inset_def = [
2620     r"### Inserted by lyx2lyx (deprecated ling glosses) ###",
2621     r"InsetLayout Flex:Tri-Glosse",
2622     r"  LyXType               custom",
2623     r'  LabelString           "Tri-Gloss (old version)"',
2624     r'  MenuString            "Tri-Gloss (old version)"',
2625     r"  LatexType             environment",
2626     r"  LatexName             lingglosss",
2627     r"  Decoration            minimalistic",
2628     r"  LabelFont",
2629     r"    Size                Small",
2630     r"  EndFont",
2631     r"  MultiPar              true",
2632     r"  CustomPars            false",
2633     r"  ForcePlain            true",
2634     r"  ParbreakIsNewline     true",
2635     r"  FreeSpacing           true",
2636     r"  InToc                 true",
2637     r"  Requires              covington",
2638     r"  Preamble",
2639     r"          \def\glosstr{}",
2640     r"          \@ifundefined{lingglosss}{%",
2641     r"          \newenvironment{lingglosss}[2][]{",
2642     r"              \def\glosstr{\glt #1}%",
2643     r"              \glll #2}",
2644     r"          {\glosstr\glend}}{}",
2645     r"  EndPreamble",
2646     r"  ResetsFont            true",
2647     r"  Argument 1",
2648     r"          Decoration    conglomerate",
2649     r'          LabelString   "Translation"',
2650     r'          MenuString    "Glosse Translation|s"',
2651     r'          Tooltip       "Add a translation for the glosse"',
2652     r"  EndArgument",
2653     r"End",
2654 ]
2655
2656
2657 def convert_linggloss(document):
2658     "Move old ling glosses to local layout"
2659     if find_token(document.body, "\\begin_inset Flex Glosse", 0) != -1:
2660         document.append_local_layout(gloss_inset_def)
2661     if find_token(document.body, "\\begin_inset Flex Tri-Glosse", 0) != -1:
2662         document.append_local_layout(glosss_inset_def)
2663
2664
2665 def revert_linggloss(document):
2666     "Revert to old ling gloss definitions"
2667     if "linguistics" not in document.get_module_list():
2668         return
2669     document.del_local_layout(gloss_inset_def)
2670     document.del_local_layout(glosss_inset_def)
2671
2672     cov_req = False
2673     glosses = [
2674         "\\begin_inset Flex Interlinear Gloss (2 Lines)",
2675         "\\begin_inset Flex Interlinear Gloss (3 Lines)",
2676     ]
2677     for glosse in glosses:
2678         i = 0
2679         while True:
2680             i = find_token(document.body, glosse, i + 1)
2681             if i == -1:
2682                 break
2683             j = find_end_of_inset(document.body, i)
2684             if j == -1:
2685                 document.warning("Malformed LyX document: Can't find end of Gloss inset")
2686                 continue
2687
2688             arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
2689             endarg = find_end_of_inset(document.body, arg)
2690             optargcontent = []
2691             if arg != -1:
2692                 argbeginPlain = find_token(
2693                     document.body, "\\begin_layout Plain Layout", arg, endarg
2694                 )
2695                 if argbeginPlain == -1:
2696                     document.warning("Malformed LyX document: Can't find optarg plain Layout")
2697                     continue
2698                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2699                 optargcontent = document.body[argbeginPlain + 1 : argendPlain - 2]
2700
2701                 # remove Arg insets and paragraph, if it only contains this inset
2702                 if (
2703                     document.body[arg - 1] == "\\begin_layout Plain Layout"
2704                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
2705                 ):
2706                     del document.body[arg - 1 : endarg + 4]
2707                 else:
2708                     del document.body[arg : endarg + 1]
2709
2710             arg = find_token(document.body, "\\begin_inset Argument post:1", i, j)
2711             endarg = find_end_of_inset(document.body, arg)
2712             marg1content = []
2713             if arg != -1:
2714                 argbeginPlain = find_token(
2715                     document.body, "\\begin_layout Plain Layout", arg, endarg
2716                 )
2717                 if argbeginPlain == -1:
2718                     document.warning("Malformed LyX document: Can't find arg 1 plain Layout")
2719                     continue
2720                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2721                 marg1content = document.body[argbeginPlain + 1 : argendPlain - 2]
2722
2723                 # remove Arg insets and paragraph, if it only contains this inset
2724                 if (
2725                     document.body[arg - 1] == "\\begin_layout Plain Layout"
2726                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
2727                 ):
2728                     del document.body[arg - 1 : endarg + 4]
2729                 else:
2730                     del document.body[arg : endarg + 1]
2731
2732             arg = find_token(document.body, "\\begin_inset Argument post:2", i, j)
2733             endarg = find_end_of_inset(document.body, arg)
2734             marg2content = []
2735             if arg != -1:
2736                 argbeginPlain = find_token(
2737                     document.body, "\\begin_layout Plain Layout", arg, endarg
2738                 )
2739                 if argbeginPlain == -1:
2740                     document.warning("Malformed LyX document: Can't find arg 2 plain Layout")
2741                     continue
2742                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2743                 marg2content = document.body[argbeginPlain + 1 : argendPlain - 2]
2744
2745                 # remove Arg insets and paragraph, if it only contains this inset
2746                 if (
2747                     document.body[arg - 1] == "\\begin_layout Plain Layout"
2748                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
2749                 ):
2750                     del document.body[arg - 1 : endarg + 4]
2751                 else:
2752                     del document.body[arg : endarg + 1]
2753
2754             arg = find_token(document.body, "\\begin_inset Argument post:3", i, j)
2755             endarg = find_end_of_inset(document.body, arg)
2756             marg3content = []
2757             if arg != -1:
2758                 argbeginPlain = find_token(
2759                     document.body, "\\begin_layout Plain Layout", arg, endarg
2760                 )
2761                 if argbeginPlain == -1:
2762                     document.warning("Malformed LyX document: Can't find arg 3 plain Layout")
2763                     continue
2764                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2765                 marg3content = document.body[argbeginPlain + 1 : argendPlain - 2]
2766
2767                 # remove Arg insets and paragraph, if it only contains this inset
2768                 if (
2769                     document.body[arg - 1] == "\\begin_layout Plain Layout"
2770                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
2771                 ):
2772                     del document.body[arg - 1 : endarg + 4]
2773                 else:
2774                     del document.body[arg : endarg + 1]
2775
2776             cmd = "\\digloss"
2777             if glosse == "\\begin_inset Flex Interlinear Gloss (3 Lines)":
2778                 cmd = "\\trigloss"
2779
2780             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
2781             endInset = find_end_of_inset(document.body, i)
2782             endPlain = find_end_of_layout(document.body, beginPlain)
2783             precontent = put_cmd_in_ert(cmd)
2784             if len(optargcontent) > 0:
2785                 precontent += put_cmd_in_ert("[") + optargcontent + put_cmd_in_ert("]")
2786             precontent += put_cmd_in_ert("{")
2787
2788             postcontent = (
2789                 put_cmd_in_ert("}{") + marg1content + put_cmd_in_ert("}{") + marg2content
2790             )
2791             if cmd == "\\trigloss":
2792                 postcontent += put_cmd_in_ert("}{") + marg3content
2793             postcontent += put_cmd_in_ert("}")
2794
2795             document.body[endPlain : endInset + 1] = postcontent
2796             document.body[beginPlain + 1 : beginPlain] = precontent
2797             del document.body[i : beginPlain + 1]
2798             if not cov_req:
2799                 document.append_local_layout("Requires covington")
2800                 cov_req = True
2801             i = beginPlain
2802
2803
2804 def revert_subexarg(document):
2805     "Revert linguistic subexamples with argument to ERT"
2806
2807     if "linguistics" not in document.get_module_list():
2808         return
2809
2810     cov_req = False
2811     i = 0
2812     while True:
2813         i = find_token(document.body, "\\begin_layout Subexample", i + 1)
2814         if i == -1:
2815             break
2816         j = find_end_of_layout(document.body, i)
2817         if j == -1:
2818             document.warning("Malformed LyX document: Can't find end of Subexample layout")
2819             continue
2820         while True:
2821             # check for consecutive layouts
2822             k = find_token(document.body, "\\begin_layout", j)
2823             if k == -1 or document.body[k] != "\\begin_layout Subexample":
2824                 break
2825             j = find_end_of_layout(document.body, k)
2826             if j == -1:
2827                 document.warning("Malformed LyX document: Can't find end of Subexample layout")
2828                 continue
2829
2830         arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
2831         if arg == -1:
2832             continue
2833
2834         endarg = find_end_of_inset(document.body, arg)
2835         optargcontent = ""
2836         argbeginPlain = find_token(document.body, "\\begin_layout Plain Layout", arg, endarg)
2837         if argbeginPlain == -1:
2838             document.warning("Malformed LyX document: Can't find optarg plain Layout")
2839             continue
2840         argendPlain = find_end_of_inset(document.body, argbeginPlain)
2841         optargcontent = lyx2latex(document, document.body[argbeginPlain + 1 : argendPlain - 2])
2842
2843         # remove Arg insets and paragraph, if it only contains this inset
2844         if (
2845             document.body[arg - 1] == "\\begin_layout Plain Layout"
2846             and find_end_of_layout(document.body, arg - 1) == endarg + 3
2847         ):
2848             del document.body[arg - 1 : endarg + 4]
2849         else:
2850             del document.body[arg : endarg + 1]
2851
2852         cmd = put_cmd_in_ert("\\begin{subexamples}[" + optargcontent + "]")
2853
2854         # re-find end of layout
2855         j = find_end_of_layout(document.body, i)
2856         if j == -1:
2857             document.warning("Malformed LyX document: Can't find end of Subexample layout")
2858             continue
2859         while True:
2860             # check for consecutive layouts
2861             k = find_token(document.body, "\\begin_layout", j)
2862             if k == -1 or document.body[k] != "\\begin_layout Subexample":
2863                 break
2864             document.body[k : k + 1] = ["\\begin_layout Standard"] + put_cmd_in_ert("\\item ")
2865             j = find_end_of_layout(document.body, k)
2866             if j == -1:
2867                 document.warning("Malformed LyX document: Can't find end of Subexample layout")
2868                 continue
2869
2870         endev = put_cmd_in_ert("\\end{subexamples}")
2871
2872         document.body[j:j] = ["\\end_layout", "", "\\begin_layout Standard"] + endev
2873         document.body[i : i + 1] = (
2874             ["\\begin_layout Standard"]
2875             + cmd
2876             + ["\\end_layout", "", "\\begin_layout Standard"]
2877             + put_cmd_in_ert("\\item ")
2878         )
2879         if not cov_req:
2880             document.append_local_layout("Requires covington")
2881             cov_req = True
2882
2883
2884 def revert_drs(document):
2885     "Revert DRS insets (linguistics) to ERT"
2886
2887     if "linguistics" not in document.get_module_list():
2888         return
2889
2890     cov_req = False
2891     drses = [
2892         "\\begin_inset Flex DRS",
2893         "\\begin_inset Flex DRS*",
2894         "\\begin_inset Flex IfThen-DRS",
2895         "\\begin_inset Flex Cond-DRS",
2896         "\\begin_inset Flex QDRS",
2897         "\\begin_inset Flex NegDRS",
2898         "\\begin_inset Flex SDRS",
2899     ]
2900     for drs in drses:
2901         i = 0
2902         while True:
2903             i = find_token(document.body, drs, i + 1)
2904             if i == -1:
2905                 break
2906             j = find_end_of_inset(document.body, i)
2907             if j == -1:
2908                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2909                 continue
2910
2911             # Check for arguments
2912             arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
2913             endarg = find_end_of_inset(document.body, arg)
2914             prearg1content = []
2915             if arg != -1:
2916                 argbeginPlain = find_token(
2917                     document.body, "\\begin_layout Plain Layout", arg, endarg
2918                 )
2919                 if argbeginPlain == -1:
2920                     document.warning(
2921                         "Malformed LyX document: Can't find Argument 1 plain Layout"
2922                     )
2923                     continue
2924                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2925                 prearg1content = document.body[argbeginPlain + 1 : argendPlain - 2]
2926
2927                 # remove Arg insets and paragraph, if it only contains this inset
2928                 if (
2929                     document.body[arg - 1] == "\\begin_layout Plain Layout"
2930                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
2931                 ):
2932                     del document.body[arg - 1 : endarg + 4]
2933                 else:
2934                     del document.body[arg : endarg + 1]
2935
2936             # re-find inset end
2937             j = find_end_of_inset(document.body, i)
2938             if j == -1:
2939                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2940                 continue
2941
2942             arg = find_token(document.body, "\\begin_inset Argument 2", i, j)
2943             endarg = find_end_of_inset(document.body, arg)
2944             prearg2content = []
2945             if arg != -1:
2946                 argbeginPlain = find_token(
2947                     document.body, "\\begin_layout Plain Layout", arg, endarg
2948                 )
2949                 if argbeginPlain == -1:
2950                     document.warning(
2951                         "Malformed LyX document: Can't find Argument 2 plain Layout"
2952                     )
2953                     continue
2954                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2955                 prearg2content = document.body[argbeginPlain + 1 : argendPlain - 2]
2956
2957                 # remove Arg insets and paragraph, if it only contains this inset
2958                 if (
2959                     document.body[arg - 1] == "\\begin_layout Plain Layout"
2960                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
2961                 ):
2962                     del document.body[arg - 1 : endarg + 4]
2963                 else:
2964                     del document.body[arg : endarg + 1]
2965
2966             # re-find inset end
2967             j = find_end_of_inset(document.body, i)
2968             if j == -1:
2969                 document.warning("Malformed LyX document: Can't find end of DRS inset")
2970                 continue
2971
2972             arg = find_token(document.body, "\\begin_inset Argument post:1", i, j)
2973             endarg = find_end_of_inset(document.body, arg)
2974             postarg1content = []
2975             if arg != -1:
2976                 argbeginPlain = find_token(
2977                     document.body, "\\begin_layout Plain Layout", arg, endarg
2978                 )
2979                 if argbeginPlain == -1:
2980                     document.warning(
2981                         "Malformed LyX document: Can't find Argument post:1 plain Layout"
2982                     )
2983                     continue
2984                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
2985                 postarg1content = document.body[argbeginPlain + 1 : argendPlain - 2]
2986
2987                 # remove Arg insets and paragraph, if it only contains this inset
2988                 if (
2989                     document.body[arg - 1] == "\\begin_layout Plain Layout"
2990                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
2991                 ):
2992                     del document.body[arg - 1 : endarg + 4]
2993                 else:
2994                     del document.body[arg : endarg + 1]
2995
2996             # re-find inset end
2997             j = find_end_of_inset(document.body, i)
2998             if j == -1:
2999                 document.warning("Malformed LyX document: Can't find end of DRS inset")
3000                 continue
3001
3002             arg = find_token(document.body, "\\begin_inset Argument post:2", i, j)
3003             endarg = find_end_of_inset(document.body, arg)
3004             postarg2content = []
3005             if arg != -1:
3006                 argbeginPlain = find_token(
3007                     document.body, "\\begin_layout Plain Layout", arg, endarg
3008                 )
3009                 if argbeginPlain == -1:
3010                     document.warning(
3011                         "Malformed LyX document: Can't find Argument post:2 plain Layout"
3012                     )
3013                     continue
3014                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
3015                 postarg2content = document.body[argbeginPlain + 1 : argendPlain - 2]
3016
3017                 # remove Arg insets and paragraph, if it only contains this inset
3018                 if (
3019                     document.body[arg - 1] == "\\begin_layout Plain Layout"
3020                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
3021                 ):
3022                     del document.body[arg - 1 : endarg + 4]
3023                 else:
3024                     del document.body[arg : endarg + 1]
3025
3026             # re-find inset end
3027             j = find_end_of_inset(document.body, i)
3028             if j == -1:
3029                 document.warning("Malformed LyX document: Can't find end of DRS inset")
3030                 continue
3031
3032             arg = find_token(document.body, "\\begin_inset Argument post:3", i, j)
3033             endarg = find_end_of_inset(document.body, arg)
3034             postarg3content = []
3035             if arg != -1:
3036                 argbeginPlain = find_token(
3037                     document.body, "\\begin_layout Plain Layout", arg, endarg
3038                 )
3039                 if argbeginPlain == -1:
3040                     document.warning(
3041                         "Malformed LyX document: Can't find Argument post:3 plain Layout"
3042                     )
3043                     continue
3044                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
3045                 postarg3content = document.body[argbeginPlain + 1 : argendPlain - 2]
3046
3047                 # remove Arg insets and paragraph, if it only contains this inset
3048                 if (
3049                     document.body[arg - 1] == "\\begin_layout Plain Layout"
3050                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
3051                 ):
3052                     del document.body[arg - 1 : endarg + 4]
3053                 else:
3054                     del document.body[arg : endarg + 1]
3055
3056             # re-find inset end
3057             j = find_end_of_inset(document.body, i)
3058             if j == -1:
3059                 document.warning("Malformed LyX document: Can't find end of DRS inset")
3060                 continue
3061
3062             arg = find_token(document.body, "\\begin_inset Argument post:4", i, j)
3063             endarg = find_end_of_inset(document.body, arg)
3064             postarg4content = []
3065             if arg != -1:
3066                 argbeginPlain = find_token(
3067                     document.body, "\\begin_layout Plain Layout", arg, endarg
3068                 )
3069                 if argbeginPlain == -1:
3070                     document.warning(
3071                         "Malformed LyX document: Can't find Argument post:4 plain Layout"
3072                     )
3073                     continue
3074                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
3075                 postarg4content = document.body[argbeginPlain + 1 : argendPlain - 2]
3076
3077                 # remove Arg insets and paragraph, if it only contains this inset
3078                 if (
3079                     document.body[arg - 1] == "\\begin_layout Plain Layout"
3080                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
3081                 ):
3082                     del document.body[arg - 1 : endarg + 4]
3083                 else:
3084                     del document.body[arg : endarg + 1]
3085
3086             # The respective LaTeX command
3087             cmd = "\\drs"
3088             if drs == "\\begin_inset Flex DRS*":
3089                 cmd = "\\drs*"
3090             elif drs == "\\begin_inset Flex IfThen-DRS":
3091                 cmd = "\\ifdrs"
3092             elif drs == "\\begin_inset Flex Cond-DRS":
3093                 cmd = "\\condrs"
3094             elif drs == "\\begin_inset Flex QDRS":
3095                 cmd = "\\qdrs"
3096             elif drs == "\\begin_inset Flex NegDRS":
3097                 cmd = "\\negdrs"
3098             elif drs == "\\begin_inset Flex SDRS":
3099                 cmd = "\\sdrs"
3100
3101             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
3102             endInset = find_end_of_inset(document.body, i)
3103             endPlain = find_token_backwards(document.body, "\\end_layout", endInset)
3104             precontent = put_cmd_in_ert(cmd)
3105             precontent += put_cmd_in_ert("{") + prearg1content + put_cmd_in_ert("}")
3106             if drs == "\\begin_inset Flex SDRS":
3107                 precontent += put_cmd_in_ert("{") + prearg2content + put_cmd_in_ert("}")
3108             precontent += put_cmd_in_ert("{")
3109
3110             postcontent = []
3111             if cmd == "\\qdrs" or cmd == "\\condrs" or cmd == "\\ifdrs":
3112                 postcontent = (
3113                     put_cmd_in_ert("}{")
3114                     + postarg1content
3115                     + put_cmd_in_ert("}{")
3116                     + postarg2content
3117                     + put_cmd_in_ert("}")
3118                 )
3119                 if cmd == "\\condrs" or cmd == "\\qdrs":
3120                     postcontent += put_cmd_in_ert("{") + postarg3content + put_cmd_in_ert("}")
3121                 if cmd == "\\qdrs":
3122                     postcontent += put_cmd_in_ert("{") + postarg4content + put_cmd_in_ert("}")
3123             else:
3124                 postcontent = put_cmd_in_ert("}")
3125
3126             document.body[endPlain : endInset + 1] = postcontent
3127             document.body[beginPlain + 1 : beginPlain] = precontent
3128             del document.body[i : beginPlain + 1]
3129             if not cov_req:
3130                 document.append_local_layout("Provides covington 1")
3131                 add_to_preamble(document, ["\\usepackage{drs,covington}"])
3132                 cov_req = True
3133             i = beginPlain
3134
3135
3136 def revert_babelfont(document):
3137     "Reverts the use of \\babelfont to user preamble"
3138
3139     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
3140         return
3141
3142     i = find_token(document.header, "\\language_package", 0)
3143     if i == -1:
3144         document.warning("Malformed LyX document: Missing \\language_package.")
3145         return
3146     if get_value(document.header, "\\language_package", 0) != "babel":
3147         return
3148
3149     # check font settings
3150     # defaults
3151     roman = sans = typew = "default"
3152     osf = False
3153     sf_scale = tt_scale = 100.0
3154
3155     j = find_token(document.header, "\\font_roman", 0)
3156     if j == -1:
3157         document.warning("Malformed LyX document: Missing \\font_roman.")
3158     else:
3159         # We need to use this regex since split() does not handle quote protection
3160         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
3161         roman = romanfont[2].strip('"')
3162         romanfont[2] = '"default"'
3163         document.header[j] = " ".join(romanfont)
3164
3165     j = find_token(document.header, "\\font_sans", 0)
3166     if j == -1:
3167         document.warning("Malformed LyX document: Missing \\font_sans.")
3168     else:
3169         # We need to use this regex since split() does not handle quote protection
3170         sansfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
3171         sans = sansfont[2].strip('"')
3172         sansfont[2] = '"default"'
3173         document.header[j] = " ".join(sansfont)
3174
3175     j = find_token(document.header, "\\font_typewriter", 0)
3176     if j == -1:
3177         document.warning("Malformed LyX document: Missing \\font_typewriter.")
3178     else:
3179         # We need to use this regex since split() does not handle quote protection
3180         ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
3181         typew = ttfont[2].strip('"')
3182         ttfont[2] = '"default"'
3183         document.header[j] = " ".join(ttfont)
3184
3185     i = find_token(document.header, "\\font_osf", 0)
3186     if i == -1:
3187         document.warning("Malformed LyX document: Missing \\font_osf.")
3188     else:
3189         osf = str2bool(get_value(document.header, "\\font_osf", i))
3190
3191     j = find_token(document.header, "\\font_sf_scale", 0)
3192     if j == -1:
3193         document.warning("Malformed LyX document: Missing \\font_sf_scale.")
3194     else:
3195         sfscale = document.header[j].split()
3196         val = sfscale[2]
3197         sfscale[2] = "100"
3198         document.header[j] = " ".join(sfscale)
3199         try:
3200             # float() can throw
3201             sf_scale = float(val)
3202         except:
3203             document.warning("Invalid font_sf_scale value: " + val)
3204
3205     j = find_token(document.header, "\\font_tt_scale", 0)
3206     if j == -1:
3207         document.warning("Malformed LyX document: Missing \\font_tt_scale.")
3208     else:
3209         ttscale = document.header[j].split()
3210         val = ttscale[2]
3211         ttscale[2] = "100"
3212         document.header[j] = " ".join(ttscale)
3213         try:
3214             # float() can throw
3215             tt_scale = float(val)
3216         except:
3217             document.warning("Invalid font_tt_scale value: " + val)
3218
3219     # set preamble stuff
3220     pretext = ["%% This document must be processed with xelatex or lualatex!"]
3221     pretext.append("\\AtBeginDocument{%")
3222     if roman != "default":
3223         pretext.append("\\babelfont{rm}[Mapping=tex-text]{" + roman + "}")
3224     if sans != "default":
3225         sf = "\\babelfont{sf}["
3226         if sf_scale != 100.0:
3227             sf += "Scale=" + str(sf_scale / 100.0) + ","
3228         sf += "Mapping=tex-text]{" + sans + "}"
3229         pretext.append(sf)
3230     if typew != "default":
3231         tw = "\\babelfont{tt}"
3232         if tt_scale != 100.0:
3233             tw += "[Scale=" + str(tt_scale / 100.0) + "]"
3234         tw += "{" + typew + "}"
3235         pretext.append(tw)
3236     if osf:
3237         pretext.append("\\defaultfontfeatures{Numbers=OldStyle}")
3238     pretext.append("}")
3239     insert_to_preamble(document, pretext)
3240
3241
3242 def revert_minionpro(document):
3243     "Revert native MinionPro font definition (with extra options) to LaTeX"
3244
3245     if get_bool_value(document.header, "\\use_non_tex_fonts"):
3246         return
3247
3248     regexp = re.compile(r"(\\font_roman_opts)")
3249     x = find_re(document.header, regexp, 0)
3250     if x == -1:
3251         return
3252
3253     # We need to use this regex since split() does not handle quote protection
3254     romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3255     opts = romanopts[1].strip('"')
3256
3257     i = find_token(document.header, "\\font_roman", 0)
3258     if i == -1:
3259         document.warning("Malformed LyX document: Missing \\font_roman.")
3260         return
3261     else:
3262         # We need to use this regex since split() does not handle quote protection
3263         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3264         roman = romanfont[1].strip('"')
3265         if roman != "minionpro":
3266             return
3267         romanfont[1] = '"default"'
3268         document.header[i] = " ".join(romanfont)
3269         osf = False
3270         j = find_token(document.header, "\\font_osf true", 0)
3271         if j != -1:
3272             osf = True
3273         preamble = "\\usepackage["
3274         if osf:
3275             document.header[j] = "\\font_osf false"
3276         else:
3277             preamble += "lf,"
3278         preamble += opts
3279         preamble += "]{MinionPro}"
3280         add_to_preamble(document, [preamble])
3281         del document.header[x]
3282
3283
3284 def revert_font_opts(document):
3285     "revert font options by outputting \\setxxxfont or \\babelfont to the preamble"
3286
3287     NonTeXFonts = get_bool_value(document.header, "\\use_non_tex_fonts")
3288     Babel = get_value(document.header, "\\language_package") == "babel"
3289
3290     # 1. Roman
3291     regexp = re.compile(r"(\\font_roman_opts)")
3292     i = find_re(document.header, regexp, 0)
3293     if i != -1:
3294         # We need to use this regex since split() does not handle quote protection
3295         romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3296         opts = romanopts[1].strip('"')
3297         del document.header[i]
3298         if NonTeXFonts:
3299             regexp = re.compile(r"(\\font_roman)")
3300             i = find_re(document.header, regexp, 0)
3301             if i != -1:
3302                 # We need to use this regex since split() does not handle quote protection
3303                 romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3304                 font = romanfont[2].strip('"')
3305                 romanfont[2] = '"default"'
3306                 document.header[i] = " ".join(romanfont)
3307                 if font != "default":
3308                     if Babel:
3309                         preamble = "\\babelfont{rm}["
3310                     else:
3311                         preamble = "\\setmainfont["
3312                     preamble += opts
3313                     preamble += ","
3314                     preamble += "Mapping=tex-text]{"
3315                     preamble += font
3316                     preamble += "}"
3317                     add_to_preamble(document, [preamble])
3318
3319     # 2. Sans
3320     regexp = re.compile(r"(\\font_sans_opts)")
3321     i = find_re(document.header, regexp, 0)
3322     if i != -1:
3323         scaleval = 100
3324         # We need to use this regex since split() does not handle quote protection
3325         sfopts = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3326         opts = sfopts[1].strip('"')
3327         del document.header[i]
3328         if NonTeXFonts:
3329             regexp = re.compile(r"(\\font_sf_scale)")
3330             i = find_re(document.header, regexp, 0)
3331             if i != -1:
3332                 scaleval = get_value(document.header, "\\font_sf_scale", i).split()[1]
3333             regexp = re.compile(r"(\\font_sans)")
3334             i = find_re(document.header, regexp, 0)
3335             if i != -1:
3336                 # We need to use this regex since split() does not handle quote protection
3337                 sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3338                 font = sffont[2].strip('"')
3339                 sffont[2] = '"default"'
3340                 document.header[i] = " ".join(sffont)
3341                 if font != "default":
3342                     if Babel:
3343                         preamble = "\\babelfont{sf}["
3344                     else:
3345                         preamble = "\\setsansfont["
3346                     preamble += opts
3347                     preamble += ","
3348                     if scaleval != 100:
3349                         preamble += "Scale=0."
3350                         preamble += scaleval
3351                         preamble += ","
3352                     preamble += "Mapping=tex-text]{"
3353                     preamble += font
3354                     preamble += "}"
3355                     add_to_preamble(document, [preamble])
3356
3357     # 3. Typewriter
3358     regexp = re.compile(r"(\\font_typewriter_opts)")
3359     i = find_re(document.header, regexp, 0)
3360     if i != -1:
3361         scaleval = 100
3362         # We need to use this regex since split() does not handle quote protection
3363         ttopts = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3364         opts = ttopts[1].strip('"')
3365         del document.header[i]
3366         if NonTeXFonts:
3367             regexp = re.compile(r"(\\font_tt_scale)")
3368             i = find_re(document.header, regexp, 0)
3369             if i != -1:
3370                 scaleval = get_value(document.header, "\\font_tt_scale", i).split()[1]
3371             regexp = re.compile(r"(\\font_typewriter)")
3372             i = find_re(document.header, regexp, 0)
3373             if i != -1:
3374                 # We need to use this regex since split() does not handle quote protection
3375                 ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3376                 font = ttfont[2].strip('"')
3377                 ttfont[2] = '"default"'
3378                 document.header[i] = " ".join(ttfont)
3379                 if font != "default":
3380                     if Babel:
3381                         preamble = "\\babelfont{tt}["
3382                     else:
3383                         preamble = "\\setmonofont["
3384                     preamble += opts
3385                     preamble += ","
3386                     if scaleval != 100:
3387                         preamble += "Scale=0."
3388                         preamble += scaleval
3389                         preamble += ","
3390                     preamble += "Mapping=tex-text]{"
3391                     preamble += font
3392                     preamble += "}"
3393                     add_to_preamble(document, [preamble])
3394
3395
3396 def revert_plainNotoFonts_xopts(document):
3397     "Revert native (straight) Noto font definition (with extra options) to LaTeX"
3398
3399     if get_bool_value(document.header, "\\use_non_tex_fonts"):
3400         return
3401
3402     osf = False
3403     y = find_token(document.header, "\\font_osf true", 0)
3404     if y != -1:
3405         osf = True
3406
3407     regexp = re.compile(r"(\\font_roman_opts)")
3408     x = find_re(document.header, regexp, 0)
3409     if x == -1 and not osf:
3410         return
3411
3412     opts = ""
3413     if x != -1:
3414         # We need to use this regex since split() does not handle quote protection
3415         romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3416         opts = romanopts[1].strip('"')
3417     if osf:
3418         if opts != "":
3419             opts += ", "
3420         opts += "osf"
3421
3422     i = find_token(document.header, "\\font_roman", 0)
3423     if i == -1:
3424         return
3425
3426     # We need to use this regex since split() does not handle quote protection
3427     romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3428     roman = romanfont[1].strip('"')
3429     if roman != "NotoSerif-TLF":
3430         return
3431
3432     j = find_token(document.header, "\\font_sans", 0)
3433     if j == -1:
3434         return
3435
3436     # We need to use this regex since split() does not handle quote protection
3437     sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
3438     sf = sffont[1].strip('"')
3439     if sf != "default":
3440         return
3441
3442     j = find_token(document.header, "\\font_typewriter", 0)
3443     if j == -1:
3444         return
3445
3446     # We need to use this regex since split() does not handle quote protection
3447     ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[j])
3448     tt = ttfont[1].strip('"')
3449     if tt != "default":
3450         return
3451
3452     # So we have noto as "complete font"
3453     romanfont[1] = '"default"'
3454     document.header[i] = " ".join(romanfont)
3455
3456     preamble = "\\usepackage["
3457     preamble += opts
3458     preamble += "]{noto}"
3459     add_to_preamble(document, [preamble])
3460     if osf:
3461         document.header[y] = "\\font_osf false"
3462     if x != -1:
3463         del document.header[x]
3464
3465
3466 def revert_notoFonts_xopts(document):
3467     "Revert native (extended) Noto font definition (with extra options) to LaTeX"
3468
3469     if get_bool_value(document.header, "\\use_non_tex_fonts"):
3470         return
3471
3472     fontmap = dict()
3473     fm = createFontMapping(["Noto"])
3474     if revert_fonts(document, fm, fontmap, True):
3475         add_preamble_fonts(document, fontmap)
3476
3477
3478 def revert_IBMFonts_xopts(document):
3479     "Revert native IBM font definition (with extra options) to LaTeX"
3480
3481     if get_bool_value(document.header, "\\use_non_tex_fonts"):
3482         return
3483
3484     fontmap = dict()
3485     fm = createFontMapping(["IBM"])
3486     if revert_fonts(document, fm, fontmap, True):
3487         add_preamble_fonts(document, fontmap)
3488
3489
3490 def revert_AdobeFonts_xopts(document):
3491     "Revert native Adobe font definition (with extra options) to LaTeX"
3492
3493     if get_bool_value(document.header, "\\use_non_tex_fonts"):
3494         return
3495
3496     fontmap = dict()
3497     fm = createFontMapping(["Adobe"])
3498     if revert_fonts(document, fm, fontmap, True):
3499         add_preamble_fonts(document, fontmap)
3500
3501
3502 def convert_osf(document):
3503     "Convert \\font_osf param to new format"
3504
3505     NonTeXFonts = get_bool_value(document.header, "\\use_non_tex_fonts")
3506
3507     i = find_token(document.header, "\\font_osf", 0)
3508     if i == -1:
3509         document.warning("Malformed LyX document: Missing \\font_osf.")
3510         return
3511
3512     osfsf = [
3513         "biolinum",
3514         "ADOBESourceSansPro",
3515         "NotoSansRegular",
3516         "NotoSansMedium",
3517         "NotoSansThin",
3518         "NotoSansLight",
3519         "NotoSansExtralight",
3520     ]
3521     osftt = ["ADOBESourceCodePro", "NotoMonoRegular"]
3522
3523     osfval = str2bool(get_value(document.header, "\\font_osf", i))
3524     document.header[i] = document.header[i].replace("\\font_osf", "\\font_roman_osf")
3525
3526     if NonTeXFonts:
3527         document.header.insert(i, "\\font_sans_osf false")
3528         document.header.insert(i + 1, "\\font_typewriter_osf false")
3529         return
3530
3531     if osfval:
3532         x = find_token(document.header, "\\font_sans", 0)
3533         if x == -1:
3534             document.warning("Malformed LyX document: Missing \\font_sans.")
3535         else:
3536             # We need to use this regex since split() does not handle quote protection
3537             sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3538             sf = sffont[1].strip('"')
3539             if sf in osfsf:
3540                 document.header.insert(i, "\\font_sans_osf true")
3541             else:
3542                 document.header.insert(i, "\\font_sans_osf false")
3543
3544         x = find_token(document.header, "\\font_typewriter", 0)
3545         if x == -1:
3546             document.warning("Malformed LyX document: Missing \\font_typewriter.")
3547         else:
3548             # We need to use this regex since split() does not handle quote protection
3549             ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3550             tt = ttfont[1].strip('"')
3551             if tt in osftt:
3552                 document.header.insert(i + 1, "\\font_typewriter_osf true")
3553             else:
3554                 document.header.insert(i + 1, "\\font_typewriter_osf false")
3555
3556     else:
3557         document.header.insert(i, "\\font_sans_osf false")
3558         document.header.insert(i + 1, "\\font_typewriter_osf false")
3559
3560
3561 def revert_osf(document):
3562     "Revert \\font_*_osf params"
3563
3564     NonTeXFonts = get_bool_value(document.header, "\\use_non_tex_fonts")
3565
3566     i = find_token(document.header, "\\font_roman_osf", 0)
3567     if i == -1:
3568         document.warning("Malformed LyX document: Missing \\font_roman_osf.")
3569         return
3570
3571     osfval = str2bool(get_value(document.header, "\\font_roman_osf", i))
3572     document.header[i] = document.header[i].replace("\\font_roman_osf", "\\font_osf")
3573
3574     i = find_token(document.header, "\\font_sans_osf", 0)
3575     if i == -1:
3576         document.warning("Malformed LyX document: Missing \\font_sans_osf.")
3577         return
3578
3579     osfval = str2bool(get_value(document.header, "\\font_sans_osf", i))
3580     del document.header[i]
3581
3582     i = find_token(document.header, "\\font_typewriter_osf", 0)
3583     if i == -1:
3584         document.warning("Malformed LyX document: Missing \\font_typewriter_osf.")
3585         return
3586
3587     osfval |= str2bool(get_value(document.header, "\\font_typewriter_osf", i))
3588     del document.header[i]
3589
3590     if osfval:
3591         i = find_token(document.header, "\\font_osf", 0)
3592         if i == -1:
3593             document.warning("Malformed LyX document: Missing \\font_osf.")
3594             return
3595         document.header[i] = "\\font_osf true"
3596
3597
3598 def revert_texfontopts(document):
3599     "Revert native TeX font definitions (with extra options) to LaTeX"
3600
3601     if get_bool_value(document.header, "\\use_non_tex_fonts"):
3602         return
3603
3604     rmfonts = [
3605         "ccfonts",
3606         "cochineal",
3607         "utopia",
3608         "garamondx",
3609         "libertine",
3610         "lmodern",
3611         "palatino",
3612         "times",
3613         "xcharter",
3614     ]
3615
3616     # First the sf (biolinum only)
3617     regexp = re.compile(r"(\\font_sans_opts)")
3618     x = find_re(document.header, regexp, 0)
3619     if x != -1:
3620         # We need to use this regex since split() does not handle quote protection
3621         sfopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3622         opts = sfopts[1].strip('"')
3623         i = find_token(document.header, "\\font_sans", 0)
3624         if i == -1:
3625             document.warning("Malformed LyX document: Missing \\font_sans.")
3626         else:
3627             # We need to use this regex since split() does not handle quote protection
3628             sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3629             sans = sffont[1].strip('"')
3630             if sans == "biolinum":
3631                 sf_scale = 100.0
3632                 sffont[1] = '"default"'
3633                 document.header[i] = " ".join(sffont)
3634                 osf = False
3635                 j = find_token(document.header, "\\font_sans_osf true", 0)
3636                 if j != -1:
3637                     osf = True
3638                 k = find_token(document.header, "\\font_sf_scale", 0)
3639                 if k == -1:
3640                     document.warning("Malformed LyX document: Missing \\font_sf_scale.")
3641                 else:
3642                     sfscale = document.header[k].split()
3643                     val = sfscale[1]
3644                     sfscale[1] = "100"
3645                     document.header[k] = " ".join(sfscale)
3646                     try:
3647                         # float() can throw
3648                         sf_scale = float(val)
3649                     except:
3650                         document.warning("Invalid font_sf_scale value: " + val)
3651                 preamble = "\\usepackage["
3652                 if osf:
3653                     document.header[j] = "\\font_sans_osf false"
3654                     preamble += "osf,"
3655                 if sf_scale != 100.0:
3656                     preamble += "scaled=" + str(sf_scale / 100.0) + ","
3657                 preamble += opts
3658                 preamble += "]{biolinum}"
3659                 add_to_preamble(document, [preamble])
3660                 del document.header[x]
3661
3662     regexp = re.compile(r"(\\font_roman_opts)")
3663     x = find_re(document.header, regexp, 0)
3664     if x == -1:
3665         return
3666
3667     # We need to use this regex since split() does not handle quote protection
3668     romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3669     opts = romanopts[1].strip('"')
3670
3671     i = find_token(document.header, "\\font_roman", 0)
3672     if i == -1:
3673         document.warning("Malformed LyX document: Missing \\font_roman.")
3674         return
3675     else:
3676         # We need to use this regex since split() does not handle quote protection
3677         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3678         roman = romanfont[1].strip('"')
3679         if roman not in rmfonts:
3680             return
3681         romanfont[1] = '"default"'
3682         document.header[i] = " ".join(romanfont)
3683         package = roman
3684         if roman == "utopia":
3685             package = "fourier"
3686         elif roman == "palatino":
3687             package = "mathpazo"
3688         elif roman == "times":
3689             package = "mathptmx"
3690         elif roman == "xcharter":
3691             package = "XCharter"
3692         osf = ""
3693         j = find_token(document.header, "\\font_roman_osf true", 0)
3694         if j != -1:
3695             if roman == "cochineal":
3696                 osf = "proportional,osf,"
3697             elif roman == "utopia":
3698                 osf = "oldstyle,"
3699             elif roman == "garamondx":
3700                 osf = "osfI,"
3701             elif roman == "libertine":
3702                 osf = "osf,"
3703             elif roman == "palatino":
3704                 osf = "osf,"
3705             elif roman == "xcharter":
3706                 osf = "osf,"
3707             document.header[j] = "\\font_roman_osf false"
3708         k = find_token(document.header, "\\font_sc true", 0)
3709         if k != -1:
3710             if roman == "utopia":
3711                 osf += "expert,"
3712             if roman == "palatino" and osf == "":
3713                 osf = "sc,"
3714             document.header[k] = "\\font_sc false"
3715         preamble = "\\usepackage["
3716         preamble += osf
3717         preamble += opts
3718         preamble += "]{" + package + "}"
3719         add_to_preamble(document, [preamble])
3720         del document.header[x]
3721
3722
3723 def convert_CantarellFont(document):
3724     "Handle Cantarell font definition to LaTeX"
3725
3726     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
3727         fm = createFontMapping(["Cantarell"])
3728         convert_fonts(document, fm, "oldstyle")
3729
3730
3731 def revert_CantarellFont(document):
3732     "Revert native Cantarell font definition to LaTeX"
3733
3734     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
3735         fontmap = dict()
3736         fm = createFontMapping(["Cantarell"])
3737         if revert_fonts(document, fm, fontmap, False, True):
3738             add_preamble_fonts(document, fontmap)
3739
3740
3741 def convert_ChivoFont(document):
3742     "Handle Chivo font definition to LaTeX"
3743
3744     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
3745         fm = createFontMapping(["Chivo"])
3746         convert_fonts(document, fm, "oldstyle")
3747
3748
3749 def revert_ChivoFont(document):
3750     "Revert native Chivo font definition to LaTeX"
3751
3752     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
3753         fontmap = dict()
3754         fm = createFontMapping(["Chivo"])
3755         if revert_fonts(document, fm, fontmap, False, True):
3756             add_preamble_fonts(document, fontmap)
3757
3758
3759 def convert_FiraFont(document):
3760     "Handle Fira font definition to LaTeX"
3761
3762     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
3763         fm = createFontMapping(["Fira"])
3764         convert_fonts(document, fm, "lf")
3765
3766
3767 def revert_FiraFont(document):
3768     "Revert native Fira font definition to LaTeX"
3769
3770     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
3771         fontmap = dict()
3772         fm = createFontMapping(["Fira"])
3773         if revert_fonts(document, fm, fontmap, False, True):
3774             add_preamble_fonts(document, fontmap)
3775
3776
3777 def convert_Semibolds(document):
3778     "Move semibold options to extraopts"
3779
3780     NonTeXFonts = get_bool_value(document.header, "\\use_non_tex_fonts")
3781
3782     i = find_token(document.header, "\\font_roman", 0)
3783     if i == -1:
3784         document.warning("Malformed LyX document: Missing \\font_roman.")
3785     else:
3786         # We need to use this regex since split() does not handle quote protection
3787         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3788         roman = romanfont[1].strip('"')
3789         if roman == "IBMPlexSerifSemibold":
3790             romanfont[1] = '"IBMPlexSerif"'
3791             document.header[i] = " ".join(romanfont)
3792
3793             if NonTeXFonts == False:
3794                 regexp = re.compile(r"(\\font_roman_opts)")
3795                 x = find_re(document.header, regexp, 0)
3796                 if x == -1:
3797                     # Sensible place to insert tag
3798                     fo = find_token(document.header, "\\font_sf_scale")
3799                     if fo == -1:
3800                         document.warning("Malformed LyX document! Missing \\font_sf_scale")
3801                     else:
3802                         document.header.insert(fo, '\\font_roman_opts "semibold"')
3803                 else:
3804                     # We need to use this regex since split() does not handle quote protection
3805                     romanopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3806                     document.header[x] = (
3807                         '\\font_roman_opts "semibold, ' + romanopts[1].strip('"') + '"'
3808                     )
3809
3810     i = find_token(document.header, "\\font_sans", 0)
3811     if i == -1:
3812         document.warning("Malformed LyX document: Missing \\font_sans.")
3813     else:
3814         # We need to use this regex since split() does not handle quote protection
3815         sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3816         sf = sffont[1].strip('"')
3817         if sf == "IBMPlexSansSemibold":
3818             sffont[1] = '"IBMPlexSans"'
3819             document.header[i] = " ".join(sffont)
3820
3821             if NonTeXFonts == False:
3822                 regexp = re.compile(r"(\\font_sans_opts)")
3823                 x = find_re(document.header, regexp, 0)
3824                 if x == -1:
3825                     # Sensible place to insert tag
3826                     fo = find_token(document.header, "\\font_sf_scale")
3827                     if fo == -1:
3828                         document.warning("Malformed LyX document! Missing \\font_sf_scale")
3829                     else:
3830                         document.header.insert(fo, '\\font_sans_opts "semibold"')
3831                 else:
3832                     # We need to use this regex since split() does not handle quote protection
3833                     sfopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3834                     document.header[x] = (
3835                         '\\font_sans_opts "semibold, ' + sfopts[1].strip('"') + '"'
3836                     )
3837
3838     i = find_token(document.header, "\\font_typewriter", 0)
3839     if i == -1:
3840         document.warning("Malformed LyX document: Missing \\font_typewriter.")
3841     else:
3842         # We need to use this regex since split() does not handle quote protection
3843         ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3844         tt = ttfont[1].strip('"')
3845         if tt == "IBMPlexMonoSemibold":
3846             ttfont[1] = '"IBMPlexMono"'
3847             document.header[i] = " ".join(ttfont)
3848
3849             if NonTeXFonts == False:
3850                 regexp = re.compile(r"(\\font_typewriter_opts)")
3851                 x = find_re(document.header, regexp, 0)
3852                 if x == -1:
3853                     # Sensible place to insert tag
3854                     fo = find_token(document.header, "\\font_tt_scale")
3855                     if fo == -1:
3856                         document.warning("Malformed LyX document! Missing \\font_tt_scale")
3857                     else:
3858                         document.header.insert(fo, '\\font_typewriter_opts "semibold"')
3859                 else:
3860                     # We need to use this regex since split() does not handle quote protection
3861                     ttopts = re.findall(r'[^"\s]\S*|".+?"', document.header[x])
3862                     document.header[x] = (
3863                         '\\font_typewriter_opts "semibold, ' + ttopts[1].strip('"') + '"'
3864                     )
3865
3866
3867 def convert_NotoRegulars(document):
3868     "Merge diverse noto reagular fonts"
3869
3870     i = find_token(document.header, "\\font_roman", 0)
3871     if i == -1:
3872         document.warning("Malformed LyX document: Missing \\font_roman.")
3873     else:
3874         # We need to use this regex since split() does not handle quote protection
3875         romanfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3876         roman = romanfont[1].strip('"')
3877         if roman == "NotoSerif-TLF":
3878             romanfont[1] = '"NotoSerifRegular"'
3879             document.header[i] = " ".join(romanfont)
3880
3881     i = find_token(document.header, "\\font_sans", 0)
3882     if i == -1:
3883         document.warning("Malformed LyX document: Missing \\font_sans.")
3884     else:
3885         # We need to use this regex since split() does not handle quote protection
3886         sffont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3887         sf = sffont[1].strip('"')
3888         if sf == "NotoSans-TLF":
3889             sffont[1] = '"NotoSansRegular"'
3890             document.header[i] = " ".join(sffont)
3891
3892     i = find_token(document.header, "\\font_typewriter", 0)
3893     if i == -1:
3894         document.warning("Malformed LyX document: Missing \\font_typewriter.")
3895     else:
3896         # We need to use this regex since split() does not handle quote protection
3897         ttfont = re.findall(r'[^"\s]\S*|".+?"', document.header[i])
3898         tt = ttfont[1].strip('"')
3899         if tt == "NotoMono-TLF":
3900             ttfont[1] = '"NotoMonoRegular"'
3901             document.header[i] = " ".join(ttfont)
3902
3903
3904 def convert_CrimsonProFont(document):
3905     "Handle CrimsonPro font definition to LaTeX"
3906
3907     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
3908         fm = createFontMapping(["CrimsonPro"])
3909         convert_fonts(document, fm, "lf")
3910
3911
3912 def revert_CrimsonProFont(document):
3913     "Revert native CrimsonPro font definition to LaTeX"
3914
3915     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
3916         fontmap = dict()
3917         fm = createFontMapping(["CrimsonPro"])
3918         if revert_fonts(document, fm, fontmap, False, True):
3919             add_preamble_fonts(document, fontmap)
3920
3921
3922 def revert_pagesizes(document):
3923     "Revert new page sizes in memoir and KOMA to options"
3924
3925     if document.textclass != "memoir" and document.textclass[:2] != "scr":
3926         return
3927
3928     i = find_token(document.header, "\\use_geometry true", 0)
3929     if i != -1:
3930         return
3931
3932     defsizes = [
3933         "default",
3934         "custom",
3935         "letterpaper",
3936         "legalpaper",
3937         "executivepaper",
3938         "a4paper",
3939         "a5paper",
3940         "b5paper",
3941     ]
3942
3943     i = find_token(document.header, "\\papersize", 0)
3944     if i == -1:
3945         document.warning("Malformed LyX document! Missing \\papersize header.")
3946         return
3947     val = get_value(document.header, "\\papersize", i)
3948     if val in defsizes:
3949         # nothing to do
3950         return
3951
3952     document.header[i] = "\\papersize default"
3953
3954     i = find_token(document.header, "\\options", 0)
3955     if i == -1:
3956         i = find_token(document.header, "\\textclass", 0)
3957         if i == -1:
3958             document.warning("Malformed LyX document! Missing \\textclass header.")
3959             return
3960         document.header.insert(i, "\\options " + val)
3961         return
3962     document.header[i] = document.header[i] + "," + val
3963
3964
3965 def convert_pagesizes(document):
3966     "Convert to new page sizes in memoir and KOMA to options"
3967
3968     if document.textclass != "memoir" and document.textclass[:3] != "scr":
3969         return
3970
3971     i = find_token(document.header, "\\use_geometry true", 0)
3972     if i != -1:
3973         return
3974
3975     defsizes = [
3976         "default",
3977         "custom",
3978         "letterpaper",
3979         "legalpaper",
3980         "executivepaper",
3981         "a4paper",
3982         "a5paper",
3983         "b5paper",
3984     ]
3985
3986     i = find_token(document.header, "\\papersize", 0)
3987     if i == -1:
3988         document.warning("Malformed LyX document! Missing \\papersize header.")
3989         return
3990     val = get_value(document.header, "\\papersize", i)
3991     if val in defsizes:
3992         # nothing to do
3993         return
3994
3995     i = find_token(document.header, "\\use_geometry false", 0)
3996     if i != -1:
3997         # Maintain use of geometry
3998         document.header[1] = "\\use_geometry true"
3999
4000
4001 def revert_komafontsizes(document):
4002     "Revert new font sizes in KOMA to options"
4003
4004     if document.textclass[:3] != "scr":
4005         return
4006
4007     i = find_token(document.header, "\\paperfontsize", 0)
4008     if i == -1:
4009         document.warning("Malformed LyX document! Missing \\paperfontsize header.")
4010         return
4011
4012     defsizes = ["default", "10", "11", "12"]
4013
4014     val = get_value(document.header, "\\paperfontsize", i)
4015     if val in defsizes:
4016         # nothing to do
4017         return
4018
4019     document.header[i] = "\\paperfontsize default"
4020
4021     fsize = "fontsize=" + val
4022
4023     i = find_token(document.header, "\\options", 0)
4024     if i == -1:
4025         i = find_token(document.header, "\\textclass", 0)
4026         if i == -1:
4027             document.warning("Malformed LyX document! Missing \\textclass header.")
4028             return
4029         document.header.insert(i, "\\options " + fsize)
4030         return
4031     document.header[i] = document.header[i] + "," + fsize
4032
4033
4034 def revert_dupqualicites(document):
4035     "Revert qualified citation list commands with duplicate keys to ERT"
4036
4037     # LyX 2.3 only supports qualified citation lists with unique keys. Thus,
4038     # we need to revert those with multiple uses of the same key.
4039
4040     # Get cite engine
4041     engine = "basic"
4042     i = find_token(document.header, "\\cite_engine", 0)
4043     if i == -1:
4044         document.warning("Malformed document! Missing \\cite_engine")
4045     else:
4046         engine = get_value(document.header, "\\cite_engine", i)
4047
4048     if engine not in ["biblatex", "biblatex-natbib"]:
4049         return
4050
4051     # Citation insets that support qualified lists, with their LaTeX code
4052     ql_citations = {
4053         "cite": "cites",
4054         "Cite": "Cites",
4055         "citet": "textcites",
4056         "Citet": "Textcites",
4057         "citep": "parencites",
4058         "Citep": "Parencites",
4059         "Footcite": "Smartcites",
4060         "footcite": "smartcites",
4061         "Autocite": "Autocites",
4062         "autocite": "autocites",
4063     }
4064
4065     i = 0
4066     while True:
4067         i = find_token(document.body, "\\begin_inset CommandInset citation", i)
4068         if i == -1:
4069             break
4070         j = find_end_of_inset(document.body, i)
4071         if j == -1:
4072             document.warning("Can't find end of citation inset at line %d!!" % (i))
4073             i += 1
4074             continue
4075
4076         k = find_token(document.body, "LatexCommand", i, j)
4077         if k == -1:
4078             document.warning("Can't find LatexCommand for citation inset at line %d!" % (i))
4079             i = j + 1
4080             continue
4081
4082         cmd = get_value(document.body, "LatexCommand", k)
4083         if cmd not in list(ql_citations.keys()):
4084             i = j + 1
4085             continue
4086
4087         pres = find_token(document.body, "pretextlist", i, j)
4088         posts = find_token(document.body, "posttextlist", i, j)
4089         if pres == -1 and posts == -1:
4090             # nothing to do.
4091             i = j + 1
4092             continue
4093
4094         key = get_quoted_value(document.body, "key", i, j)
4095         if not key:
4096             document.warning("Citation inset at line %d does not have a key!" % (i))
4097             i = j + 1
4098             continue
4099
4100         keys = key.split(",")
4101         ukeys = list(set(keys))
4102         if len(keys) == len(ukeys):
4103             # no duplicates.
4104             i = j + 1
4105             continue
4106
4107         pretexts = get_quoted_value(document.body, "pretextlist", pres)
4108         posttexts = get_quoted_value(document.body, "posttextlist", posts)
4109
4110         pre = get_quoted_value(document.body, "before", i, j)
4111         post = get_quoted_value(document.body, "after", i, j)
4112         prelist = pretexts.split("\t")
4113         premap = dict()
4114         for pp in prelist:
4115             ppp = pp.split(" ", 1)
4116             val = ""
4117             if len(ppp) > 1:
4118                 val = ppp[1]
4119             else:
4120                 val = ""
4121             if ppp[0] in premap:
4122                 premap[ppp[0]] = premap[ppp[0]] + "\t" + val
4123             else:
4124                 premap[ppp[0]] = val
4125         postlist = posttexts.split("\t")
4126         postmap = dict()
4127         for pp in postlist:
4128             ppp = pp.split(" ", 1)
4129             val = ""
4130             if len(ppp) > 1:
4131                 val = ppp[1]
4132             else:
4133                 val = ""
4134             if ppp[0] in postmap:
4135                 postmap[ppp[0]] = postmap[ppp[0]] + "\t" + val
4136             else:
4137                 postmap[ppp[0]] = val
4138         # Replace known new commands with ERT
4139         if "(" in pre or ")" in pre:
4140             pre = "{" + pre + "}"
4141         if "(" in post or ")" in post:
4142             post = "{" + post + "}"
4143         res = "\\" + ql_citations[cmd]
4144         if pre:
4145             res += "(" + pre + ")"
4146         if post:
4147             res += "(" + post + ")"
4148         elif pre:
4149             res += "()"
4150         for kk in keys:
4151             if premap.get(kk, "") != "":
4152                 akeys = premap[kk].split("\t", 1)
4153                 akey = akeys[0]
4154                 if akey != "":
4155                     res += "[" + akey + "]"
4156                 if len(akeys) > 1:
4157                     premap[kk] = "\t".join(akeys[1:])
4158                 else:
4159                     premap[kk] = ""
4160             if postmap.get(kk, "") != "":
4161                 akeys = postmap[kk].split("\t", 1)
4162                 akey = akeys[0]
4163                 if akey != "":
4164                     res += "[" + akey + "]"
4165                 if len(akeys) > 1:
4166                     postmap[kk] = "\t".join(akeys[1:])
4167                 else:
4168                     postmap[kk] = ""
4169             elif premap.get(kk, "") != "":
4170                 res += "[]"
4171             res += "{" + kk + "}"
4172         document.body[i : j + 1] = put_cmd_in_ert([res])
4173
4174
4175 def convert_pagesizenames(document):
4176     "Convert LyX page sizes names"
4177
4178     i = find_token(document.header, "\\papersize", 0)
4179     if i == -1:
4180         document.warning("Malformed LyX document! Missing \\papersize header.")
4181         return
4182     oldnames = [
4183         "letterpaper",
4184         "legalpaper",
4185         "executivepaper",
4186         "a0paper",
4187         "a1paper",
4188         "a2paper",
4189         "a3paper",
4190         "a4paper",
4191         "a5paper",
4192         "a6paper",
4193         "b0paper",
4194         "b1paper",
4195         "b2paper",
4196         "b3paper",
4197         "b4paper",
4198         "b5paper",
4199         "b6paper",
4200         "c0paper",
4201         "c1paper",
4202         "c2paper",
4203         "c3paper",
4204         "c4paper",
4205         "c5paper",
4206         "c6paper",
4207     ]
4208     val = get_value(document.header, "\\papersize", i)
4209     if val in oldnames:
4210         newval = val.replace("paper", "")
4211         document.header[i] = "\\papersize " + newval
4212
4213
4214 def revert_pagesizenames(document):
4215     "Convert LyX page sizes names"
4216
4217     i = find_token(document.header, "\\papersize", 0)
4218     if i == -1:
4219         document.warning("Malformed LyX document! Missing \\papersize header.")
4220         return
4221     newnames = [
4222         "letter",
4223         "legal",
4224         "executive",
4225         "a0",
4226         "a1",
4227         "a2",
4228         "a3",
4229         "a4",
4230         "a5",
4231         "a6",
4232         "b0",
4233         "b1",
4234         "b2",
4235         "b3",
4236         "b4",
4237         "b5",
4238         "b6",
4239         "c0",
4240         "c1",
4241         "c2",
4242         "c3",
4243         "c4",
4244         "c5",
4245         "c6",
4246     ]
4247     val = get_value(document.header, "\\papersize", i)
4248     if val in newnames:
4249         newval = val + "paper"
4250         document.header[i] = "\\papersize " + newval
4251
4252
4253 def revert_theendnotes(document):
4254     "Reverts native support of \\theendnotes to TeX-code"
4255
4256     if (
4257         "endnotes" not in document.get_module_list()
4258         and "foottoend" not in document.get_module_list()
4259     ):
4260         return
4261
4262     i = 0
4263     while True:
4264         i = find_token(document.body, "\\begin_inset FloatList endnote", i + 1)
4265         if i == -1:
4266             return
4267         j = find_end_of_inset(document.body, i)
4268         if j == -1:
4269             document.warning("Malformed LyX document: Can't find end of FloatList inset")
4270             continue
4271
4272         document.body[i : j + 1] = put_cmd_in_ert("\\theendnotes")
4273
4274
4275 def revert_enotez(document):
4276     "Reverts native support of enotez package to TeX-code"
4277
4278     if (
4279         "enotez" not in document.get_module_list()
4280         and "foottoenotez" not in document.get_module_list()
4281     ):
4282         return
4283
4284     use = False
4285     if find_token(document.body, "\\begin_inset Flex Endnote", 0) != -1:
4286         use = True
4287
4288     revert_flex_inset(document, "Endnote", "\\endnote")
4289
4290     i = 0
4291     while True:
4292         i = find_token(document.body, "\\begin_inset FloatList endnote", i + 1)
4293         if i == -1:
4294             break
4295         j = find_end_of_inset(document.body, i)
4296         if j == -1:
4297             document.warning("Malformed LyX document: Can't find end of FloatList inset")
4298             continue
4299
4300         use = True
4301         document.body[i : j + 1] = put_cmd_in_ert("\\printendnotes")
4302
4303     if use:
4304         add_to_preamble(document, ["\\usepackage{enotez}"])
4305     document.del_module("enotez")
4306     document.del_module("foottoenotez")
4307
4308
4309 def revert_memoir_endnotes(document):
4310     "Reverts native support of memoir endnotes to TeX-code"
4311
4312     if document.textclass != "memoir":
4313         return
4314
4315     encommand = "\\pagenote"
4316     modules = document.get_module_list()
4317     if (
4318         "enotez" in modules
4319         or "foottoenotez" in modules
4320         or "endnotes" in modules
4321         or "foottoend" in modules
4322     ):
4323         encommand = "\\endnote"
4324
4325     revert_flex_inset(document, "Endnote", encommand)
4326
4327     i = 0
4328     while True:
4329         i = find_token(document.body, "\\begin_inset FloatList pagenote", i + 1)
4330         if i == -1:
4331             break
4332         j = find_end_of_inset(document.body, i)
4333         if j == -1:
4334             document.warning("Malformed LyX document: Can't find end of FloatList inset")
4335             continue
4336
4337         if document.body[i] == "\\begin_inset FloatList pagenote*":
4338             document.body[i : j + 1] = put_cmd_in_ert("\\printpagenotes*")
4339         else:
4340             document.body[i : j + 1] = put_cmd_in_ert("\\printpagenotes")
4341         add_to_preamble(document, ["\\makepagenote"])
4342
4343
4344 def revert_totalheight(document):
4345     "Reverts graphics height parameter from totalheight to height"
4346
4347     relative_heights = {
4348         "\\textwidth": "text%",
4349         "\\columnwidth": "col%",
4350         "\\paperwidth": "page%",
4351         "\\linewidth": "line%",
4352         "\\textheight": "theight%",
4353         "\\paperheight": "pheight%",
4354         "\\baselineskip ": "baselineskip%",
4355     }
4356     i = 0
4357     while True:
4358         i = find_token(document.body, "\\begin_inset Graphics", i)
4359         if i == -1:
4360             break
4361         j = find_end_of_inset(document.body, i)
4362         if j == -1:
4363             document.warning("Can't find end of graphics inset at line %d!!" % (i))
4364             i += 1
4365             continue
4366
4367         rx = re.compile(r"\s*special\s*(\S+)$")
4368         rxx = re.compile(r"(\d*\.*\d+)(\S+)$")
4369         k = find_re(document.body, rx, i, j)
4370         special = ""
4371         oldheight = ""
4372         if k != -1:
4373             m = rx.match(document.body[k])
4374             if m:
4375                 special = m.group(1)
4376             mspecial = special.split(",")
4377             for spc in mspecial:
4378                 if spc.startswith("height="):
4379                     oldheight = spc.split("=")[1]
4380                     ms = rxx.search(oldheight)
4381                     if ms:
4382                         oldunit = ms.group(2)
4383                         if oldunit in list(relative_heights.keys()):
4384                             oldval = str(float(ms.group(1)) * 100)
4385                             oldunit = relative_heights[oldunit]
4386                             oldheight = oldval + oldunit
4387                     mspecial.remove(spc)
4388                     break
4389             if len(mspecial) > 0:
4390                 special = ",".join(mspecial)
4391             else:
4392                 special = ""
4393
4394         rx = re.compile(r"(\s*height\s*)(\S+)$")
4395         kk = find_re(document.body, rx, i, j)
4396         if kk != -1:
4397             m = rx.match(document.body[kk])
4398             val = ""
4399             if m:
4400                 val = m.group(2)
4401                 if k != -1:
4402                     if special != "":
4403                         val = val + "," + special
4404                     document.body[k] = "\tspecial " + "totalheight=" + val
4405                 else:
4406                     document.body.insert(kk, "\tspecial totalheight=" + val)
4407                 if oldheight != "":
4408                     document.body[kk] = m.group(1) + oldheight
4409                 else:
4410                     del document.body[kk]
4411         elif oldheight != "":
4412             if special != "":
4413                 document.body[k] = "\tspecial " + special
4414                 document.body.insert(k, "\theight " + oldheight)
4415             else:
4416                 document.body[k] = "\theight " + oldheight
4417         i = j + 1
4418
4419
4420 def convert_totalheight(document):
4421     "Converts graphics height parameter from totalheight to height"
4422
4423     relative_heights = {
4424         "text%": "\\textwidth",
4425         "col%": "\\columnwidth",
4426         "page%": "\\paperwidth",
4427         "line%": "\\linewidth",
4428         "theight%": "\\textheight",
4429         "pheight%": "\\paperheight",
4430         "baselineskip%": "\\baselineskip",
4431     }
4432     i = 0
4433     while True:
4434         i = find_token(document.body, "\\begin_inset Graphics", i)
4435         if i == -1:
4436             break
4437         j = find_end_of_inset(document.body, i)
4438         if j == -1:
4439             document.warning("Can't find end of graphics inset at line %d!!" % (i))
4440             i += 1
4441             continue
4442
4443         rx = re.compile(r"\s*special\s*(\S+)$")
4444         k = find_re(document.body, rx, i, j)
4445         special = ""
4446         newheight = ""
4447         if k != -1:
4448             m = rx.match(document.body[k])
4449             if m:
4450                 special = m.group(1)
4451             mspecial = special.split(",")
4452             for spc in mspecial:
4453                 if spc[:12] == "totalheight=":
4454                     newheight = spc.split("=")[1]
4455                     mspecial.remove(spc)
4456                     break
4457             if len(mspecial) > 0:
4458                 special = ",".join(mspecial)
4459             else:
4460                 special = ""
4461
4462         rx = re.compile(r"(\s*height\s*)(\d+\.?\d*)(\S+)$")
4463         kk = find_re(document.body, rx, i, j)
4464         if kk != -1:
4465             m = rx.match(document.body[kk])
4466             val = ""
4467             if m:
4468                 val = m.group(2)
4469                 unit = m.group(3)
4470                 if unit in list(relative_heights.keys()):
4471                     val = str(float(val) / 100)
4472                     unit = relative_heights[unit]
4473                 if k != -1:
4474                     if special != "":
4475                         val = val + unit + "," + special
4476                     document.body[k] = "\tspecial " + "height=" + val
4477                 else:
4478                     document.body.insert(kk + 1, "\tspecial height=" + val + unit)
4479                 if newheight != "":
4480                     document.body[kk] = m.group(1) + newheight
4481                 else:
4482                     del document.body[kk]
4483         elif newheight != "":
4484             document.body.insert(k, "\theight " + newheight)
4485         i = j + 1
4486
4487
4488 def convert_changebars(document):
4489     "Converts the changebars module to native solution"
4490
4491     if "changebars" not in document.get_module_list():
4492         return
4493
4494     i = find_token(document.header, "\\output_changes", 0)
4495     if i == -1:
4496         document.warning("Malformed LyX document! Missing \\output_changes header.")
4497         document.del_module("changebars")
4498         return
4499
4500     document.header.insert(i, "\\change_bars true")
4501     document.del_module("changebars")
4502
4503
4504 def revert_changebars(document):
4505     "Converts native changebar param to module"
4506
4507     i = find_token(document.header, "\\change_bars", 0)
4508     if i == -1:
4509         document.warning("Malformed LyX document! Missing \\change_bars header.")
4510         return
4511
4512     val = get_value(document.header, "\\change_bars", i)
4513
4514     if val == "true":
4515         document.add_module("changebars")
4516
4517     del document.header[i]
4518
4519
4520 def convert_postpone_fragile(document):
4521     "Adds false \\postpone_fragile_content buffer param"
4522
4523     i = find_token(document.header, "\\output_changes", 0)
4524     if i == -1:
4525         document.warning("Malformed LyX document! Missing \\output_changes header.")
4526         return
4527     # Set this to false for old documents (see #2154)
4528     document.header.insert(i, "\\postpone_fragile_content false")
4529
4530
4531 def revert_postpone_fragile(document):
4532     "Remove \\postpone_fragile_content buffer param"
4533
4534     i = find_token(document.header, "\\postpone_fragile_content", 0)
4535     if i == -1:
4536         document.warning("Malformed LyX document! Missing \\postpone_fragile_content.")
4537         return
4538
4539     del document.header[i]
4540
4541
4542 def revert_colrow_tracking(document):
4543     "Remove change tag from tabular columns/rows"
4544     i = 0
4545     while True:
4546         i = find_token(document.body, "\\begin_inset Tabular", i + 1)
4547         if i == -1:
4548             return
4549         j = find_end_of_inset(document.body, i + 1)
4550         if j == -1:
4551             document.warning("Malformed LyX document: Could not find end of tabular.")
4552             continue
4553         for k in range(i, j):
4554             m = re.search('^<column.*change="([^"]+)".*>$', document.body[k])
4555             if m:
4556                 document.body[k] = document.body[k].replace(' change="' + m.group(1) + '"', "")
4557             m = re.search('^<row.*change="([^"]+)".*>$', document.body[k])
4558             if m:
4559                 document.body[k] = document.body[k].replace(' change="' + m.group(1) + '"', "")
4560
4561
4562 def convert_counter_maintenance(document):
4563     "Convert \\maintain_unincluded_children buffer param from boolean value tro tristate"
4564
4565     i = find_token(document.header, "\\maintain_unincluded_children", 0)
4566     if i == -1:
4567         document.warning("Malformed LyX document! Missing \\maintain_unincluded_children.")
4568         return
4569
4570     val = get_value(document.header, "\\maintain_unincluded_children", i)
4571
4572     if val == "true":
4573         document.header[i] = "\\maintain_unincluded_children strict"
4574     else:
4575         document.header[i] = "\\maintain_unincluded_children no"
4576
4577
4578 def revert_counter_maintenance(document):
4579     "Revert \\maintain_unincluded_children buffer param to previous boolean value"
4580
4581     i = find_token(document.header, "\\maintain_unincluded_children", 0)
4582     if i == -1:
4583         document.warning("Malformed LyX document! Missing \\maintain_unincluded_children.")
4584         return
4585
4586     val = get_value(document.header, "\\maintain_unincluded_children", i)
4587
4588     if val == "no":
4589         document.header[i] = "\\maintain_unincluded_children false"
4590     else:
4591         document.header[i] = "\\maintain_unincluded_children true"
4592
4593
4594 def revert_counter_inset(document):
4595     "Revert counter inset to ERT, where possible"
4596     i = 0
4597     needed_counters = {}
4598     while True:
4599         i = find_token(document.body, "\\begin_inset CommandInset counter", i)
4600         if i == -1:
4601             break
4602         j = find_end_of_inset(document.body, i)
4603         if j == -1:
4604             document.warning("Can't find end of counter inset at line %d!" % i)
4605             i += 1
4606             continue
4607         lyx = get_quoted_value(document.body, "lyxonly", i, j)
4608         if lyx == "true":
4609             # there is nothing we can do to affect the LyX counters
4610             document.body[i : j + 1] = []
4611             i = j + 1
4612             continue
4613         cnt = get_quoted_value(document.body, "counter", i, j)
4614         if not cnt:
4615             document.warning("No counter given for inset at line %d!" % i)
4616             i = j + 1
4617             continue
4618
4619         cmd = get_quoted_value(document.body, "LatexCommand", i, j)
4620         document.warning(cmd)
4621         ert = ""
4622         if cmd == "set":
4623             val = get_quoted_value(document.body, "value", i, j)
4624             if not val:
4625                 document.warning("Can't convert counter inset at line %d!" % i)
4626             else:
4627                 ert = put_cmd_in_ert(f"\\setcounter{{{cnt}}}{{{val}}}")
4628         elif cmd == "addto":
4629             val = get_quoted_value(document.body, "value", i, j)
4630             if not val:
4631                 document.warning("Can't convert counter inset at line %d!" % i)
4632             else:
4633                 ert = put_cmd_in_ert(f"\\addtocounter{{{cnt}}}{{{val}}}")
4634         elif cmd == "reset":
4635             ert = put_cmd_in_ert("\\setcounter{%s}{0}" % (cnt))
4636         elif cmd == "save":
4637             needed_counters[cnt] = 1
4638             savecnt = "LyXSave" + cnt
4639             ert = put_cmd_in_ert(f"\\setcounter{{{savecnt}}}{{\\value{{{cnt}}}}}")
4640         elif cmd == "restore":
4641             needed_counters[cnt] = 1
4642             savecnt = "LyXSave" + cnt
4643             ert = put_cmd_in_ert(f"\\setcounter{{{cnt}}}{{\\value{{{savecnt}}}}}")
4644         else:
4645             document.warning("Unknown counter command `%s' in inset at line %d!" % (cnt, i))
4646
4647         if ert:
4648             document.body[i : j + 1] = ert
4649         i += 1
4650         continue
4651
4652     pretext = []
4653     for cnt in needed_counters:
4654         pretext.append("\\newcounter{LyXSave%s}" % (cnt))
4655     if pretext:
4656         add_to_preamble(document, pretext)
4657
4658
4659 def revert_ams_spaces(document):
4660     "Revert InsetSpace medspace and thickspace into their TeX-code counterparts"
4661     Found = False
4662     insets = ["\\medspace{}", "\\thickspace{}"]
4663     for inset in insets:
4664         i = 0
4665         i = find_token(document.body, "\\begin_inset space " + inset, i)
4666         if i == -1:
4667             continue
4668         end = find_end_of_inset(document.body, i)
4669         subst = put_cmd_in_ert(inset)
4670         document.body[i : end + 1] = subst
4671         Found = True
4672
4673     if Found == True:
4674         # load amsmath in the preamble if not already loaded
4675         i = find_token(document.header, "\\use_package amsmath 2", 0)
4676         if i == -1:
4677             add_to_preamble(document, ["\\@ifundefined{thickspace}{\\usepackage{amsmath}}{}"])
4678             return
4679
4680
4681 def convert_parskip(document):
4682     "Move old parskip settings to preamble"
4683
4684     i = find_token(document.header, "\\paragraph_separation skip", 0)
4685     if i == -1:
4686         return
4687
4688     j = find_token(document.header, "\\defskip", 0)
4689     if j == -1:
4690         document.warning("Malformed LyX document! Missing \\defskip.")
4691         return
4692
4693     val = get_value(document.header, "\\defskip", j)
4694
4695     skipval = "\\medskipamount"
4696     if val == "smallskip" or val == "medskip" or val == "bigskip":
4697         skipval = "\\" + val + "amount"
4698     else:
4699         skipval = val
4700
4701     add_to_preamble(
4702         document,
4703         ["\\setlength{\\parskip}{" + skipval + "}", "\\setlength{\\parindent}{0pt}"],
4704     )
4705
4706     document.header[i] = "\\paragraph_separation indent"
4707     document.header[j] = "\\paragraph_indentation default"
4708
4709
4710 def revert_parskip(document):
4711     "Revert new parskip settings to preamble"
4712
4713     i = find_token(document.header, "\\paragraph_separation skip", 0)
4714     if i == -1:
4715         return
4716
4717     j = find_token(document.header, "\\defskip", 0)
4718     if j == -1:
4719         document.warning("Malformed LyX document! Missing \\defskip.")
4720         return
4721
4722     val = get_value(document.header, "\\defskip", j)
4723
4724     skipval = ""
4725     if val == "smallskip" or val == "medskip" or val == "bigskip":
4726         skipval = "[skip=\\" + val + "amount]"
4727     elif val == "fullline":
4728         skipval = "[skip=\\baselineskip]"
4729     elif val != "halfline":
4730         skipval = "[skip={" + val + "}]"
4731
4732     add_to_preamble(document, ["\\usepackage" + skipval + "{parskip}"])
4733
4734     document.header[i] = "\\paragraph_separation indent"
4735     document.header[j] = "\\paragraph_indentation default"
4736
4737
4738 def revert_line_vspaces(document):
4739     "Revert fulline and halfline vspaces to TeX"
4740     insets = {
4741         "fullline*": "\\vspace*{\\baselineskip}",
4742         "fullline": "\\vspace{\\baselineskip}",
4743         "halfline*": "\\vspace*{0.5\\baselineskip}",
4744         "halfline": "\\vspace{0.5\\baselineskip}",
4745     }
4746     for inset in insets.keys():
4747         i = 0
4748         i = find_token(document.body, "\\begin_inset VSpace " + inset, i)
4749         if i == -1:
4750             continue
4751         end = find_end_of_inset(document.body, i)
4752         subst = put_cmd_in_ert(insets[inset])
4753         document.body[i : end + 1] = subst
4754
4755
4756 def convert_libertinus_rm_fonts(document):
4757     """Handle Libertinus serif fonts definition to LaTeX"""
4758
4759     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
4760         fm = createFontMapping(["Libertinus"])
4761         convert_fonts(document, fm)
4762
4763
4764 def revert_libertinus_rm_fonts(document):
4765     """Revert Libertinus serif font definition to LaTeX"""
4766
4767     if not get_bool_value(document.header, "\\use_non_tex_fonts"):
4768         fontmap = dict()
4769         fm = createFontMapping(["libertinus"])
4770         if revert_fonts(document, fm, fontmap):
4771             add_preamble_fonts(document, fontmap)
4772
4773
4774 def revert_libertinus_sftt_fonts(document):
4775     "Revert Libertinus sans and tt font definitions to LaTeX"
4776
4777     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
4778         # first sf font
4779         i = find_token(document.header, '\\font_sans "LibertinusSans-LF"', 0)
4780         if i != -1:
4781             j = find_token(document.header, "\\font_sans_osf true", 0)
4782             if j != -1:
4783                 add_to_preamble(document, ["\\renewcommand{\\sfdefault}{LibertinusSans-OsF}"])
4784                 document.header[j] = "\\font_sans_osf false"
4785             else:
4786                 add_to_preamble(document, ["\\renewcommand{\\sfdefault}{LibertinusSans-LF}"])
4787             document.header[i] = document.header[i].replace("LibertinusSans-LF", "default")
4788             sf_scale = 100.0
4789             sfval = find_token(document.header, "\\font_sf_scale", 0)
4790             if sfval == -1:
4791                 document.warning("Malformed LyX document: Missing \\font_sf_scale.")
4792             else:
4793                 sfscale = document.header[sfval].split()
4794                 val = sfscale[1]
4795                 sfscale[1] = "100"
4796                 document.header[sfval] = " ".join(sfscale)
4797                 try:
4798                     # float() can throw
4799                     sf_scale = float(val)
4800                 except:
4801                     document.warning("Invalid font_sf_scale value: " + val)
4802                 if sf_scale != "100.0":
4803                     add_to_preamble(
4804                         document,
4805                         [
4806                             "\\renewcommand*{\\LibertinusSans@scale}{"
4807                             + str(sf_scale / 100.0)
4808                             + "}"
4809                         ],
4810                     )
4811         # now tt font
4812         i = find_token(document.header, '\\font_typewriter "LibertinusMono-TLF"', 0)
4813         if i != -1:
4814             add_to_preamble(document, ["\\renewcommand{\\ttdefault}{LibertinusMono-TLF}"])
4815             document.header[i] = document.header[i].replace("LibertinusMono-TLF", "default")
4816             tt_scale = 100.0
4817             ttval = find_token(document.header, "\\font_tt_scale", 0)
4818             if ttval == -1:
4819                 document.warning("Malformed LyX document: Missing \\font_tt_scale.")
4820             else:
4821                 ttscale = document.header[ttval].split()
4822                 val = ttscale[1]
4823                 ttscale[1] = "100"
4824                 document.header[ttval] = " ".join(ttscale)
4825                 try:
4826                     # float() can throw
4827                     tt_scale = float(val)
4828                 except:
4829                     document.warning("Invalid font_tt_scale value: " + val)
4830                 if tt_scale != "100.0":
4831                     add_to_preamble(
4832                         document,
4833                         [
4834                             "\\renewcommand*{\\LibertinusMono@scale}{"
4835                             + str(tt_scale / 100.0)
4836                             + "}"
4837                         ],
4838                     )
4839
4840
4841 def revert_docbook_table_output(document):
4842     i = find_token(document.header, "\\docbook_table_output")
4843     if i != -1:
4844         del document.header[i]
4845
4846
4847 def revert_nopagebreak(document):
4848     while True:
4849         i = find_token(document.body, "\\begin_inset Newpage nopagebreak")
4850         if i == -1:
4851             return
4852         end = find_end_of_inset(document.body, i)
4853         if end == 1:
4854             document.warning("Malformed LyX document: Could not find end of Newpage inset.")
4855             continue
4856         subst = put_cmd_in_ert("\\nopagebreak{}")
4857         document.body[i : end + 1] = subst
4858
4859
4860 def revert_hrquotes(document):
4861     "Revert Hungarian Quotation marks"
4862
4863     i = find_token(document.header, "\\quotes_style hungarian", 0)
4864     if i != -1:
4865         document.header[i] = "\\quotes_style polish"
4866
4867     i = 0
4868     while True:
4869         i = find_token(document.body, "\\begin_inset Quotes h")
4870         if i == -1:
4871             return
4872         if document.body[i] == "\\begin_inset Quotes hld":
4873             document.body[i] = "\\begin_inset Quotes pld"
4874         elif document.body[i] == "\\begin_inset Quotes hrd":
4875             document.body[i] = "\\begin_inset Quotes prd"
4876         elif document.body[i] == "\\begin_inset Quotes hls":
4877             document.body[i] = "\\begin_inset Quotes ald"
4878         elif document.body[i] == "\\begin_inset Quotes hrs":
4879             document.body[i] = "\\begin_inset Quotes ard"
4880
4881
4882 def convert_math_refs(document):
4883     i = 0
4884     while True:
4885         i = find_token(document.body, "\\begin_inset Formula", i)
4886         if i == -1:
4887             break
4888         j = find_end_of_inset(document.body, i)
4889         if j == -1:
4890             document.warning("Can't find end of inset at line %d of body!" % i)
4891             i += 1
4892             continue
4893         while i < j:
4894             document.body[i] = document.body[i].replace("\\prettyref", "\\formatted")
4895             i += 1
4896
4897
4898 def revert_math_refs(document):
4899     i = 0
4900     while True:
4901         i = find_token(document.body, "\\begin_inset Formula", i)
4902         if i == -1:
4903             break
4904         j = find_end_of_inset(document.body, i)
4905         if j == -1:
4906             document.warning("Can't find end of inset at line %d of body!" % i)
4907             i += 1
4908             continue
4909         while i < j:
4910             document.body[i] = document.body[i].replace("\\formatted", "\\prettyref")
4911             if "\\labelonly" in document.body[i]:
4912                 document.body[i] = re.sub("\\\\labelonly{([^}]+?)}", "\\1", document.body[i])
4913             i += 1
4914
4915
4916 def convert_branch_colors(document):
4917     "Convert branch colors to semantic values"
4918
4919     i = 0
4920     while True:
4921         i = find_token(document.header, "\\branch", i)
4922         if i == -1:
4923             break
4924         j = find_token(document.header, "\\end_branch", i)
4925         if j == -1:
4926             document.warning("Malformed LyX document. Can't find end of branch definition!")
4927             break
4928         # We only support the standard LyX background for now
4929         k = find_token(document.header, "\\color #faf0e6", i, j)
4930         if k != -1:
4931             document.header[k] = "\\color background"
4932         i += 1
4933
4934
4935 def revert_branch_colors(document):
4936     "Revert semantic branch colors"
4937
4938     i = 0
4939     while True:
4940         i = find_token(document.header, "\\branch", i)
4941         if i == -1:
4942             break
4943         j = find_token(document.header, "\\end_branch", i)
4944         if j == -1:
4945             document.warning("Malformed LyX document. Can't find end of branch definition!")
4946             break
4947         k = find_token(document.header, "\\color", i, j)
4948         if k != -1:
4949             bcolor = get_value(document.header, "\\color", k)
4950             if bcolor[1] != "#":
4951                 # this will be read as background by LyX 2.3
4952                 document.header[k] = "\\color none"
4953         i += 1
4954
4955
4956 def revert_darkmode_graphics(document):
4957     "Revert darkModeSensitive InsetGraphics param"
4958
4959     i = 0
4960     while True:
4961         i = find_token(document.body, "\\begin_inset Graphics", i)
4962         if i == -1:
4963             break
4964         j = find_end_of_inset(document.body, i)
4965         if j == -1:
4966             document.warning("Can't find end of graphics inset at line %d!!" % (i))
4967             i += 1
4968             continue
4969         k = find_token(document.body, "\tdarkModeSensitive", i, j)
4970         if k != -1:
4971             del document.body[k]
4972         i += 1
4973
4974
4975 def revert_branch_darkcols(document):
4976     "Revert dark branch colors"
4977
4978     i = 0
4979     while True:
4980         i = find_token(document.header, "\\branch", i)
4981         if i == -1:
4982             break
4983         j = find_token(document.header, "\\end_branch", i)
4984         if j == -1:
4985             document.warning("Malformed LyX document. Can't find end of branch definition!")
4986             break
4987         k = find_token(document.header, "\\color", i, j)
4988         if k != -1:
4989             m = re.search("\\\\color (\\S+) (\\S+)", document.header[k])
4990             if m:
4991                 document.header[k] = "\\color " + m.group(1)
4992         i += 1
4993
4994
4995 def revert_vcolumns2(document):
4996     """Revert varwidth columns with line breaks etc."""
4997     i = 0
4998     needvarwidth = False
4999     needarray = False
5000     needcellvarwidth = False
5001     try:
5002         while True:
5003             i = find_token(document.body, "\\begin_inset Tabular", i + 1)
5004             if i == -1:
5005                 return
5006             j = find_end_of_inset(document.body, i)
5007             if j == -1:
5008                 document.warning("Malformed LyX document: Could not find end of tabular.")
5009                 continue
5010
5011             # Collect necessary column information
5012             m = i + 1
5013             nrows = int(document.body[i + 1].split('"')[3])
5014             ncols = int(document.body[i + 1].split('"')[5])
5015             col_info = []
5016             for k in range(ncols):
5017                 m = find_token(document.body, "<column", m)
5018                 width = get_option_value(document.body[m], "width")
5019                 varwidth = get_option_value(document.body[m], "varwidth")
5020                 alignment = get_option_value(document.body[m], "alignment")
5021                 valignment = get_option_value(document.body[m], "valignment")
5022                 special = get_option_value(document.body[m], "special")
5023                 col_info.append([width, varwidth, alignment, valignment, special, m])
5024                 m += 1
5025
5026             # Now parse cells
5027             m = i + 1
5028             for row in range(nrows):
5029                 for col in range(ncols):
5030                     m = find_token(document.body, "<cell", m)
5031                     multicolumn = get_option_value(document.body[m], "multicolumn") != ""
5032                     multirow = get_option_value(document.body[m], "multirow") != ""
5033                     fixedwidth = get_option_value(document.body[m], "width") != ""
5034                     rotate = get_option_value(document.body[m], "rotate")
5035                     cellalign = get_option_value(document.body[m], "alignment")
5036                     cellvalign = get_option_value(document.body[m], "valignment")
5037                     # Check for: linebreaks, multipars, non-standard environments
5038                     begcell = m
5039                     endcell = find_token(document.body, "</cell>", begcell)
5040                     vcand = False
5041                     if (
5042                         find_token(document.body, "\\begin_inset Newline", begcell, endcell)
5043                         != -1
5044                     ):
5045                         vcand = not fixedwidth
5046                     elif count_pars_in_inset(document.body, begcell + 2) > 1:
5047                         vcand = not fixedwidth
5048                     elif get_value(document.body, "\\begin_layout", begcell) != "Plain Layout":
5049                         vcand = not fixedwidth
5050                     colalignment = col_info[col][2]
5051                     colvalignment = col_info[col][3]
5052                     if vcand:
5053                         if rotate == "" and (
5054                             (colalignment == "left" and colvalignment == "top")
5055                             or (
5056                                 multicolumn == True
5057                                 and cellalign == "left"
5058                                 and cellvalign == "top"
5059                             )
5060                         ):
5061                             if (
5062                                 col_info[col][0] == ""
5063                                 and col_info[col][1] == ""
5064                                 and col_info[col][4] == ""
5065                             ):
5066                                 needvarwidth = True
5067                                 col_line = col_info[col][5]
5068                                 needarray = True
5069                                 vval = "V{\\linewidth}"
5070                                 if multicolumn:
5071                                     document.body[m] = (
5072                                         document.body[m][:-1] + ' special="' + vval + '">'
5073                                     )
5074                                 else:
5075                                     document.body[col_line] = (
5076                                         document.body[col_line][:-1]
5077                                         + ' special="'
5078                                         + vval
5079                                         + '">'
5080                                     )
5081                         else:
5082                             alarg = ""
5083                             if multicolumn or multirow:
5084                                 if cellvalign == "middle":
5085                                     alarg = "[m]"
5086                                 elif cellvalign == "bottom":
5087                                     alarg = "[b]"
5088                             else:
5089                                 if colvalignment == "middle":
5090                                     alarg = "[m]"
5091                                 elif colvalignment == "bottom":
5092                                     alarg = "[b]"
5093                             flt = find_token(document.body, "\\begin_layout", begcell, endcell)
5094                             elt = find_token_backwards(document.body, "\\end_layout", endcell)
5095                             if flt != -1 and elt != -1:
5096                                 extralines = []
5097                                 # we need to reset character layouts if necessary
5098                                 el = find_token(document.body, "\\emph on", flt, elt)
5099                                 if el != -1:
5100                                     extralines.append("\\emph default")
5101                                 el = find_token(document.body, "\\noun on", flt, elt)
5102                                 if el != -1:
5103                                     extralines.append("\\noun default")
5104                                 el = find_token(document.body, "\\series", flt, elt)
5105                                 if el != -1:
5106                                     extralines.append("\\series default")
5107                                 el = find_token(document.body, "\\family", flt, elt)
5108                                 if el != -1:
5109                                     extralines.append("\\family default")
5110                                 el = find_token(document.body, "\\shape", flt, elt)
5111                                 if el != -1:
5112                                     extralines.append("\\shape default")
5113                                 el = find_token(document.body, "\\color", flt, elt)
5114                                 if el != -1:
5115                                     extralines.append("\\color inherit")
5116                                 el = find_token(document.body, "\\size", flt, elt)
5117                                 if el != -1:
5118                                     extralines.append("\\size default")
5119                                 el = find_token(document.body, "\\bar under", flt, elt)
5120                                 if el != -1:
5121                                     extralines.append("\\bar default")
5122                                 el = find_token(document.body, "\\uuline on", flt, elt)
5123                                 if el != -1:
5124                                     extralines.append("\\uuline default")
5125                                 el = find_token(document.body, "\\uwave on", flt, elt)
5126                                 if el != -1:
5127                                     extralines.append("\\uwave default")
5128                                 el = find_token(document.body, "\\strikeout on", flt, elt)
5129                                 if el != -1:
5130                                     extralines.append("\\strikeout default")
5131                                 document.body[elt : elt + 1] = (
5132                                     extralines
5133                                     + put_cmd_in_ert("\\end{cellvarwidth}")
5134                                     + [r"\end_layout"]
5135                                 )
5136                                 parlang = -1
5137                                 for q in range(flt, elt):
5138                                     if document.body[q] != "" and document.body[q][0] != "\\":
5139                                         break
5140                                     if document.body[q][:5] == "\\lang":
5141                                         parlang = q
5142                                         break
5143                                 if parlang != -1:
5144                                     document.body[parlang + 1 : parlang + 1] = put_cmd_in_ert(
5145                                         "\\begin{cellvarwidth}" + alarg
5146                                     )
5147                                 else:
5148                                     document.body[flt + 1 : flt + 1] = put_cmd_in_ert(
5149                                         "\\begin{cellvarwidth}" + alarg
5150                                     )
5151                                 needcellvarwidth = True
5152                                 needvarwidth = True
5153                         # ERT newlines and linebreaks (since LyX < 2.4 automatically inserts parboxes
5154                         # with newlines, and we do not want that)
5155                         while True:
5156                             endcell = find_token(document.body, "</cell>", begcell)
5157                             linebreak = False
5158                             nl = find_token(
5159                                 document.body,
5160                                 "\\begin_inset Newline newline",
5161                                 begcell,
5162                                 endcell,
5163                             )
5164                             if nl == -1:
5165                                 nl = find_token(
5166                                     document.body,
5167                                     "\\begin_inset Newline linebreak",
5168                                     begcell,
5169                                     endcell,
5170                                 )
5171                                 if nl == -1:
5172                                     break
5173                                 linebreak = True
5174                             nle = find_end_of_inset(document.body, nl)
5175                             del document.body[nle : nle + 1]
5176                             if linebreak:
5177                                 document.body[nl : nl + 1] = put_cmd_in_ert("\\linebreak{}")
5178                             else:
5179                                 document.body[nl : nl + 1] = put_cmd_in_ert("\\\\")
5180                         # Replace parbreaks in multirow with \\endgraf
5181                         if multirow == True:
5182                             flt = find_token(document.body, "\\begin_layout", begcell, endcell)
5183                             if flt != -1:
5184                                 while True:
5185                                     elt = find_end_of_layout(document.body, flt)
5186                                     if elt == -1:
5187                                         document.warning(
5188                                             "Malformed LyX document! Missing layout end."
5189                                         )
5190                                         break
5191                                     endcell = find_token(document.body, "</cell>", begcell)
5192                                     flt = find_token(
5193                                         document.body, "\\begin_layout", elt, endcell
5194                                     )
5195                                     if flt == -1:
5196                                         break
5197                                     document.body[elt : flt + 1] = put_cmd_in_ert("\\endgraf{}")
5198                     m += 1
5199
5200             i = j
5201
5202     finally:
5203         if needarray == True:
5204             add_to_preamble(document, ["\\usepackage{array}"])
5205         if needcellvarwidth == True:
5206             add_to_preamble(
5207                 document,
5208                 [
5209                     "%% Variable width box for table cells",
5210                     "\\newenvironment{cellvarwidth}[1][t]",
5211                     "    {\\begin{varwidth}[#1]{\\linewidth}}",
5212                     "    {\\@finalstrut\\@arstrutbox\\end{varwidth}}",
5213                 ],
5214             )
5215         if needvarwidth == True:
5216             add_to_preamble(document, ["\\usepackage{varwidth}"])
5217
5218
5219 def convert_vcolumns2(document):
5220     """Convert varwidth ERT to native"""
5221     i = 0
5222     try:
5223         while True:
5224             i = find_token(document.body, "\\begin_inset Tabular", i + 1)
5225             if i == -1:
5226                 return
5227             j = find_end_of_inset(document.body, i)
5228             if j == -1:
5229                 document.warning("Malformed LyX document: Could not find end of tabular.")
5230                 continue
5231
5232             # Parse cells
5233             nrows = int(document.body[i + 1].split('"')[3])
5234             ncols = int(document.body[i + 1].split('"')[5])
5235             m = i + 1
5236             lines = []
5237             for row in range(nrows):
5238                 for col in range(ncols):
5239                     m = find_token(document.body, "<cell", m)
5240                     multirow = get_option_value(document.body[m], "multirow") != ""
5241                     begcell = m
5242                     endcell = find_token(document.body, "</cell>", begcell)
5243                     vcand = False
5244                     cvw = find_token(document.body, "begin{cellvarwidth}", begcell, endcell)
5245                     if cvw != -1:
5246                         vcand = (
5247                             document.body[cvw - 1] == "\\backslash"
5248                             and get_containing_inset(document.body, cvw)[0] == "ERT"
5249                         )
5250                     if vcand:
5251                         # Remove ERTs with cellvarwidth env
5252                         ecvw = find_token(document.body, "end{cellvarwidth}", begcell, endcell)
5253                         if ecvw != -1:
5254                             if document.body[ecvw - 1] == "\\backslash":
5255                                 eertins = get_containing_inset(document.body, ecvw)
5256                                 if eertins and eertins[0] == "ERT":
5257                                     del document.body[eertins[1] : eertins[2] + 1]
5258
5259                         cvw = find_token(document.body, "begin{cellvarwidth}", begcell, endcell)
5260                         ertins = get_containing_inset(document.body, cvw)
5261                         if ertins and ertins[0] == "ERT":
5262                             del document.body[ertins[1] : ertins[2] + 1]
5263
5264                         # Convert ERT newlines (as cellvarwidth detection relies on that)
5265                         while True:
5266                             endcell = find_token(document.body, "</cell>", begcell)
5267                             nl = find_token(document.body, "\\backslash", begcell, endcell)
5268                             if nl == -1 or document.body[nl + 2] != "\\backslash":
5269                                 break
5270                             ertins = get_containing_inset(document.body, nl)
5271                             if ertins and ertins[0] == "ERT":
5272                                 document.body[ertins[1] : ertins[2] + 1] = [
5273                                     "\\begin_inset Newline newline",
5274                                     "",
5275                                     "\\end_inset",
5276                                 ]
5277
5278                         # Same for linebreaks
5279                         while True:
5280                             endcell = find_token(document.body, "</cell>", begcell)
5281                             nl = find_token(document.body, "linebreak", begcell, endcell)
5282                             if nl == -1 or document.body[nl - 1] != "\\backslash":
5283                                 break
5284                             ertins = get_containing_inset(document.body, nl)
5285                             if ertins and ertins[0] == "ERT":
5286                                 document.body[ertins[1] : ertins[2] + 1] = [
5287                                     "\\begin_inset Newline linebreak",
5288                                     "",
5289                                     "\\end_inset",
5290                                 ]
5291
5292                         # And \\endgraf
5293                         if multirow == True:
5294                             endcell = find_token(document.body, "</cell>", begcell)
5295                             nl = find_token(document.body, "endgraf{}", begcell, endcell)
5296                             if nl == -1 or document.body[nl - 1] != "\\backslash":
5297                                 break
5298                             ertins = get_containing_inset(document.body, nl)
5299                             if ertins and ertins[0] == "ERT":
5300                                 document.body[ertins[1] : ertins[2] + 1] = [
5301                                     "\\end_layout",
5302                                     "",
5303                                     "\\begin_layout Plain Layout",
5304                                 ]
5305                     m += 1
5306
5307             i += 1
5308
5309     finally:
5310         del_complete_lines(
5311             document.preamble,
5312             [
5313                 "% Added by lyx2lyx",
5314                 "%% Variable width box for table cells",
5315                 r"\newenvironment{cellvarwidth}[1][t]",
5316                 r"    {\begin{varwidth}[#1]{\linewidth}}",
5317                 r"    {\@finalstrut\@arstrutbox\end{varwidth}}",
5318             ],
5319         )
5320         del_complete_lines(document.preamble, ["% Added by lyx2lyx", r"\usepackage{varwidth}"])
5321
5322
5323 frontispiece_def = [
5324     r"### Inserted by lyx2lyx (frontispiece layout) ###",
5325     r"Style Frontispiece",
5326     r"  CopyStyle             Titlehead",
5327     r"  LatexName             frontispiece",
5328     r"End",
5329 ]
5330
5331
5332 def convert_koma_frontispiece(document):
5333     """Remove local KOMA frontispiece definition"""
5334     if document.textclass[:3] != "scr":
5335         return
5336
5337     if document.del_local_layout(frontispiece_def):
5338         document.add_module("ruby")
5339
5340
5341 def revert_koma_frontispiece(document):
5342     """Add local KOMA frontispiece definition"""
5343     if document.textclass[:3] != "scr":
5344         return
5345
5346     if find_token(document.body, "\\begin_layout Frontispiece", 0) != -1:
5347         document.append_local_layout(frontispiece_def)
5348
5349
5350 def revert_spellchecker_ignore(document):
5351     """Revert document spellchecker dictionary"""
5352     while True:
5353         i = find_token(document.header, "\\spellchecker_ignore")
5354         if i == -1:
5355             return
5356         del document.header[i]
5357
5358
5359 def revert_docbook_mathml_prefix(document):
5360     """Revert the DocBook parameter to choose the prefix for the MathML name space"""
5361     while True:
5362         i = find_token(document.header, "\\docbook_mathml_prefix")
5363         if i == -1:
5364             return
5365         del document.header[i]
5366
5367
5368 def revert_document_metadata(document):
5369     """Revert document metadata"""
5370     i = 0
5371     while True:
5372         i = find_token(document.header, "\\begin_metadata", i)
5373         if i == -1:
5374             return
5375         j = find_end_of(document.header, i, "\\begin_metadata", "\\end_metadata")
5376         if j == -1:
5377             # this should not happen
5378             break
5379         document.header[i : j + 1] = []
5380
5381
5382 def revert_index_macros(document):
5383     "Revert inset index macros"
5384
5385     i = 0
5386     while True:
5387         # trailing blank needed here to exclude IndexMacro insets
5388         i = find_token(document.body, "\\begin_inset Index ", i + 1)
5389         if i == -1:
5390             break
5391         j = find_end_of_inset(document.body, i)
5392         if j == -1:
5393             document.warning(
5394                 "Malformed LyX document: Can't find end of index inset at line %d" % i
5395             )
5396             continue
5397         pl = find_token(document.body, "\\begin_layout Plain Layout", i, j)
5398         if pl == -1:
5399             document.warning(
5400                 "Malformed LyX document: Can't find plain layout in index inset at line %d" % i
5401             )
5402             continue
5403         # find, store and remove inset params
5404         pr = find_token(document.body, "range", i, pl)
5405         prval = get_quoted_value(document.body, "range", pr)
5406         pagerange = ""
5407         if prval == "start":
5408             pagerange = "("
5409         elif prval == "end":
5410             pagerange = ")"
5411         pf = find_token(document.body, "pageformat", i, pl)
5412         pageformat = get_quoted_value(document.body, "pageformat", pf)
5413         del document.body[pr : pf + 1]
5414         # Now re-find (potentially moved) inset end again, and search for subinsets
5415         j = find_end_of_inset(document.body, i)
5416         if j == -1:
5417             document.warning(
5418                 "Malformed LyX document: Can't find end of index inset at line %d" % i
5419             )
5420             continue
5421         # We search for all possible subentries in turn, store their
5422         # content and delete them
5423         see = []
5424         seealso = []
5425         subentry = []
5426         subentry2 = []
5427         sortkey = []
5428         # Two subentries are allowed, thus the duplication
5429         imacros = ["seealso", "see", "subentry", "subentry", "sortkey"]
5430         for imacro in imacros:
5431             iim = find_token(document.body, "\\begin_inset IndexMacro %s" % imacro, i, j)
5432             if iim == -1:
5433                 continue
5434             iime = find_end_of_inset(document.body, iim)
5435             if iime == -1:
5436                 document.warning(
5437                     "Malformed LyX document: Can't find end of index macro inset at line %d" % i
5438                 )
5439                 continue
5440             iimpl = find_token(document.body, "\\begin_layout Plain Layout", iim, iime)
5441             if iimpl == -1:
5442                 document.warning(
5443                     "Malformed LyX document: Can't find plain layout in index macro inset at line %d"
5444                     % i
5445                 )
5446                 continue
5447             iimple = find_end_of_layout(document.body, iimpl)
5448             if iimple == -1:
5449                 document.warning(
5450                     "Malformed LyX document: Can't find end of index macro inset plain layout at line %d"
5451                     % i
5452                 )
5453                 continue
5454             icont = document.body[iimpl:iimple]
5455             if imacro == "seealso":
5456                 seealso = icont[1:]
5457             elif imacro == "see":
5458                 see = icont[1:]
5459             elif imacro == "subentry":
5460                 # subentries might hace their own sortkey!
5461                 xiim = find_token(
5462                     document.body, "\\begin_inset IndexMacro sortkey", iimpl, iimple
5463                 )
5464                 if xiim != -1:
5465                     xiime = find_end_of_inset(document.body, xiim)
5466                     if xiime == -1:
5467                         document.warning(
5468                             "Malformed LyX document: Can't find end of index macro inset at line %d"
5469                             % i
5470                         )
5471                     else:
5472                         xiimpl = find_token(
5473                             document.body, "\\begin_layout Plain Layout", xiim, xiime
5474                         )
5475                         if xiimpl == -1:
5476                             document.warning(
5477                                 "Malformed LyX document: Can't find plain layout in index macro inset at line %d"
5478                                 % i
5479                             )
5480                         else:
5481                             xiimple = find_end_of_layout(document.body, xiimpl)
5482                             if xiimple == -1:
5483                                 document.warning(
5484                                     "Malformed LyX document: Can't find end of index macro inset plain layout at line %d"
5485                                     % i
5486                                 )
5487                             else:
5488                                 # the sortkey
5489                                 xicont = document.body[xiimpl + 1 : xiimple]
5490                                 # everything before ................... or after
5491                                 xxicont = (
5492                                     document.body[iimpl + 1 : xiim]
5493                                     + document.body[xiime + 1 : iimple]
5494                                 )
5495                                 # construct the latex sequence
5496                                 icont = xicont + put_cmd_in_ert("@") + xxicont[1:]
5497                 if len(subentry) > 0:
5498                     if icont[0] == "\\begin_layout Plain Layout":
5499                         subentry2 = icont[1:]
5500                     else:
5501                         subentry2 = icont
5502                 else:
5503                     if icont[0] == "\\begin_layout Plain Layout":
5504                         subentry = icont[1:]
5505                     else:
5506                         subentry = icont
5507             elif imacro == "sortkey":
5508                 sortkey = icont
5509             # Everything stored. Delete subinset.
5510             del document.body[iim : iime + 1]
5511             # Again re-find (potentially moved) index inset end
5512             j = find_end_of_inset(document.body, i)
5513             if j == -1:
5514                 document.warning(
5515                     "Malformed LyX document: Can't find end of index inset at line %d" % i
5516                 )
5517                 continue
5518         # Now insert all stuff, starting from the inset end
5519         pl = find_token(document.body, "\\begin_layout Plain Layout", i, j)
5520         if pl == -1:
5521             document.warning(
5522                 "Malformed LyX document: Can't find plain layout in index inset at line %d" % i
5523             )
5524             continue
5525         ple = find_end_of_layout(document.body, pl)
5526         if ple == -1:
5527             document.warning(
5528                 "Malformed LyX document: Can't find end of index macro inset plain layout at line %d"
5529                 % i
5530             )
5531             continue
5532         if len(see) > 0:
5533             document.body[ple:ple] = (
5534                 put_cmd_in_ert("|" + pagerange + "see{") + see + put_cmd_in_ert("}")
5535             )
5536         elif len(seealso) > 0:
5537             document.body[ple:ple] = (
5538                 put_cmd_in_ert("|" + pagerange + "seealso{") + seealso + put_cmd_in_ert("}")
5539             )
5540         elif pageformat != "default":
5541             document.body[ple:ple] = put_cmd_in_ert("|" + pagerange + pageformat)
5542         if len(subentry2) > 0:
5543             document.body[ple:ple] = put_cmd_in_ert("!") + subentry2
5544         if len(subentry) > 0:
5545             document.body[ple:ple] = put_cmd_in_ert("!") + subentry
5546         if len(sortkey) > 0:
5547             document.body[pl : pl + 1] = document.body[pl:pl] + sortkey + put_cmd_in_ert("@")
5548
5549
5550 def revert_starred_refs(document):
5551     "Revert starred refs"
5552     i = find_token(document.header, "\\use_hyperref true", 0)
5553     use_hyperref = i != -1
5554     i = 0
5555     in_inset = False
5556     cmd = ref = ""
5557     nolink = False
5558     nolinkline = -1
5559     while True:
5560         if not in_inset:
5561             i = find_token(document.body, "\\begin_inset CommandInset ref", i)
5562             if i == -1:
5563                 break
5564             start = i
5565             end = find_end_of_inset(document.body, i)
5566             if end == -1:
5567                 document.warning(
5568                     "Malformed LyX document: Can't find end of inset at line %d" % i
5569                 )
5570                 i += 1
5571                 continue
5572             # If we are not using hyperref, then we just need to delete the line
5573             if not use_hyperref:
5574                 k = find_token(document.body, "nolink", i, end)
5575                 if k == -1:
5576                     i = end
5577                     continue
5578                 del document.body[k]
5579                 i = end - 1
5580                 continue
5581             # If we are using hyperref, then we'll need to do more.
5582             in_inset = True
5583             i += 1
5584             continue
5585         # so we are in an InsetRef
5586         if i == end:
5587             in_inset = False
5588             # If nolink is False, just remove that line
5589             if nolink == False or cmd == "formatted" or cmd == "labelonly":
5590                 # document.warning("Skipping " + cmd + " " + ref)
5591                 if nolinkline != -1:
5592                     del document.body[nolinkline]
5593                     nolinkline = -1
5594                 continue
5595             # We need to construct a new command and put it in ERT
5596             newcmd = "\\" + cmd + "*{" + ref + "}"
5597             # document.warning(newcmd)
5598             newlines = put_cmd_in_ert(newcmd)
5599             document.body[start : end + 1] = newlines
5600             i += len(newlines) - (end - start) + 1
5601             # reset variables
5602             cmd = ref = ""
5603             nolink = False
5604             nolinkline = -1
5605             continue
5606         l = document.body[i]
5607         if l.startswith("LatexCommand"):
5608             cmd = l[13:]
5609         elif l.startswith("reference"):
5610             ref = l[11:-1]
5611         elif l.startswith("nolink"):
5612             tmp = l[8:-1]
5613             nolink = tmp == "true"
5614             nolinkline = i
5615         i += 1
5616
5617
5618 def convert_starred_refs(document):
5619     "Convert starred refs"
5620     i = 0
5621     while True:
5622         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
5623         if i == -1:
5624             break
5625         end = find_end_of_inset(document.body, i)
5626         if end == -1:
5627             document.warning("Malformed LyX document: Can't find end of inset at line %d" % i)
5628             i += 1
5629             continue
5630         newlineat = end - 1
5631         document.body.insert(newlineat, 'nolink "false"')
5632         i = end + 1
5633
5634
5635 def revert_familydefault(document):
5636     "Revert \\font_default_family for non-TeX fonts"
5637
5638     if find_token(document.header, "\\use_non_tex_fonts true", 0) == -1:
5639         return
5640
5641     i = find_token(document.header, "\\font_default_family", 0)
5642     if i == -1:
5643         document.warning("Malformed LyX document: Can't find \\font_default_family header")
5644         return
5645
5646     dfamily = get_value(document.header, "\\font_default_family", i)
5647     if dfamily == "default":
5648         return
5649
5650     document.header[i] = "\\font_default_family default"
5651     add_to_preamble(document, ["\\renewcommand{\\familydefault}{\\" + dfamily + "}"])
5652
5653
5654 def convert_hyper_other(document):
5655     'Classify "run:" links as other'
5656
5657     i = 0
5658     while True:
5659         i = find_token(document.body, "\\begin_inset CommandInset href", i)
5660         if i == -1:
5661             break
5662         j = find_end_of_inset(document.body, i)
5663         if j == -1:
5664             document.warning("Cannot find end of inset at line " << str(i))
5665             i += 1
5666             continue
5667         k = find_token(document.body, 'type "', i, j)
5668         if k != -1:
5669             # not a "Web" type. Continue.
5670             i = j
5671             continue
5672         t = find_token(document.body, "target", i, j)
5673         if t == -1:
5674             document.warning("Malformed hyperlink inset at line " + str(i))
5675             i = j
5676             continue
5677         if document.body[t][8:12] == "run:":
5678             document.body.insert(t, 'type "other"')
5679         i += 1
5680
5681
5682 def revert_hyper_other(document):
5683     'Revert other link type to ERT and "run:" to Web'
5684
5685     i = 0
5686     while True:
5687         i = find_token(document.body, "\\begin_inset CommandInset href", i)
5688         if i == -1:
5689             break
5690         j = find_end_of_inset(document.body, i)
5691         if j == -1:
5692             document.warning("Cannot find end of inset at line " << str(i))
5693             i += 1
5694             continue
5695         k = find_token(document.body, 'type "other"', i, j)
5696         if k == -1:
5697             i = j
5698             continue
5699         # build command
5700         n = find_token(document.body, "name", i, j)
5701         t = find_token(document.body, "target", i, j)
5702         if n == -1 or t == -1:
5703             document.warning("Malformed hyperlink inset at line " + str(i))
5704             i = j
5705             continue
5706         name = document.body[n][6:-1]
5707         target = document.body[t][8:-1]
5708         if target[:4] == "run:":
5709             del document.body[k]
5710         else:
5711             cmd = r"\href{" + target + "}{" + name + "}"
5712             ecmd = put_cmd_in_ert(cmd)
5713             document.body[i : j + 1] = ecmd
5714         i += 1
5715
5716
5717 ack_layouts_new = {
5718     "aa": "Acknowledgments",
5719     "aapaper": "Acknowledgments",
5720     "aastex": "Acknowledgments",
5721     "aastex62": "Acknowledgments",
5722     "achemso": "Acknowledgments",
5723     "acmart": "Acknowledgments",
5724     "AEA": "Acknowledgments",
5725     "apa": "Acknowledgments",
5726     "copernicus": "Acknowledgments",
5727     "egs": "Acknowledgments",  # + Acknowledgment
5728     "elsart": "Acknowledgment",
5729     "isprs": "Acknowledgments",
5730     "iucr": "Acknowledgments",
5731     "kluwer": "Acknowledgments",
5732     "svglobal3": "Acknowledgments",
5733     "svglobal": "Acknowledgment",
5734     "svjog": "Acknowledgment",
5735     "svmono": "Acknowledgment",
5736     "svmult": "Acknowledgment",
5737     "svprobth": "Acknowledgment",
5738 }
5739
5740 ack_layouts_old = {
5741     "aa": "Acknowledgement",
5742     "aapaper": "Acknowledgement",
5743     "aastex": "Acknowledgement",
5744     "aastex62": "Acknowledgement",
5745     "achemso": "Acknowledgement",
5746     "acmart": "Acknowledgements",
5747     "AEA": "Acknowledgement",
5748     "apa": "Acknowledgements",
5749     "copernicus": "Acknowledgements",
5750     "egs": "Acknowledgements",  # + Acknowledgement
5751     "elsart": "Acknowledegment",
5752     "isprs": "Acknowledgements",
5753     "iucr": "Acknowledgements",
5754     "kluwer": "Acknowledgements",
5755     "svglobal3": "Acknowledgements",
5756     "svglobal": "Acknowledgement",
5757     "svjog": "Acknowledgement",
5758     "svmono": "Acknowledgement",
5759     "svmult": "Acknowledgement",
5760     "svprobth": "Acknowledgement",
5761 }
5762
5763
5764 def convert_acknowledgment(document):
5765     "Fix spelling of acknowledgment styles"
5766
5767     if document.textclass not in list(ack_layouts_old.keys()):
5768         return
5769
5770     i = 0
5771     while True:
5772         i = find_token(
5773             document.body, "\\begin_layout " + ack_layouts_old[document.textclass], i
5774         )
5775         if i == -1:
5776             break
5777         document.body[i] = "\\begin_layout " + ack_layouts_new[document.textclass]
5778     if document.textclass != "egs":
5779         return
5780     # egs has two styles
5781     i = 0
5782     while True:
5783         i = find_token(document.body, "\\begin_layout Acknowledgement", i)
5784         if i == -1:
5785             break
5786         document.body[i] = "\\begin_layout Acknowledgment"
5787
5788
5789 def revert_acknowledgment(document):
5790     "Restore old spelling of acknowledgment styles"
5791
5792     if document.textclass not in list(ack_layouts_new.keys()):
5793         return
5794     i = 0
5795     while True:
5796         i = find_token(
5797             document.body, "\\begin_layout " + ack_layouts_new[document.textclass], i
5798         )
5799         if i == -1:
5800             break
5801         document.body[i] = "\\begin_layout " + ack_layouts_old[document.textclass]
5802     if document.textclass != "egs":
5803         return
5804     # egs has two styles
5805     i = 0
5806     while True:
5807         i = find_token(document.body, "\\begin_layout Acknowledgment", i)
5808         if i == -1:
5809             break
5810         document.body[i] = "\\begin_layout Acknowledgement"
5811
5812
5813 ack_theorem_def = [
5814     r"### Inserted by lyx2lyx (ams extended theorems) ###",
5815     r"### This requires theorems-ams-extended module to be loaded",
5816     r"Style Acknowledgement",
5817     r"  CopyStyle             Remark",
5818     r"  LatexName             acknowledgement",
5819     r'  LabelString           "Acknowledgement \thetheorem."',
5820     r"  Preamble",
5821     r"    \theoremstyle{remark}",
5822     r"    \newtheorem{acknowledgement}[thm]{\protect\acknowledgementname}",
5823     r"  EndPreamble",
5824     r"  LangPreamble",
5825     r"    \providecommand{\acknowledgementname}{_(Acknowledgement)}",
5826     r"  EndLangPreamble",
5827     r"  BabelPreamble",
5828     r"    \addto\captions$$lang{\renewcommand{\acknowledgementname}{_(Acknowledgement)}}",
5829     r"  EndBabelPreamble",
5830     r"  DocBookTag            para",
5831     r'  DocBookAttr           role="acknowledgement"',
5832     r'  DocBookItemTag        ""',
5833     r"End",
5834 ]
5835
5836 ackStar_theorem_def = [
5837     r"### Inserted by lyx2lyx (ams extended theorems) ###",
5838     r"### This requires a theorems-ams-extended-* module to be loaded",
5839     r"Style Acknowledgement*",
5840     r"  CopyStyle             Remark*",
5841     r"  LatexName             acknowledgement*",
5842     r'  LabelString           "Acknowledgement."',
5843     r"  Preamble",
5844     r"    \theoremstyle{remark}",
5845     r"    \newtheorem*{acknowledgement*}{\protect\acknowledgementname}",
5846     r"  EndPreamble",
5847     r"  LangPreamble",
5848     r"    \providecommand{\acknowledgementname}{_(Acknowledgement)}",
5849     r"  EndLangPreamble",
5850     r"  BabelPreamble",
5851     r"    \addto\captions$$lang{\renewcommand{\acknowledgementname}{_(Acknowledgement)}}",
5852     r"  EndBabelPreamble",
5853     r"  DocBookTag            para",
5854     r'  DocBookAttr           role="acknowledgement"',
5855     r'  DocBookItemTag        ""',
5856     r"End",
5857 ]
5858
5859 ack_bytype_theorem_def = [
5860     r"### Inserted by lyx2lyx (ams extended theorems) ###",
5861     r"### This requires theorems-ams-extended-bytype module to be loaded",
5862     r"Counter acknowledgement",
5863     r"  GuiName Acknowledgment",
5864     r"End",
5865     r"Style Acknowledgement",
5866     r"  CopyStyle             Remark",
5867     r"  LatexName             acknowledgement",
5868     r'  LabelString           "Acknowledgement \theacknowledgement."',
5869     r"  Preamble",
5870     r"    \theoremstyle{remark}",
5871     r"    \newtheorem{acknowledgement}{\protect\acknowledgementname}",
5872     r"  EndPreamble",
5873     r"  LangPreamble",
5874     r"    \providecommand{\acknowledgementname}{_(Acknowledgement)}",
5875     r"  EndLangPreamble",
5876     r"  BabelPreamble",
5877     r"    \addto\captions$$lang{\renewcommand{\acknowledgementname}{_(Acknowledgement)}}",
5878     r"  EndBabelPreamble",
5879     r"  DocBookTag            para",
5880     r'  DocBookAttr           role="acknowledgement"',
5881     r'  DocBookItemTag        ""',
5882     r"End",
5883 ]
5884
5885 ack_chap_bytype_theorem_def = [
5886     r"### Inserted by lyx2lyx (ams extended theorems) ###",
5887     r"### This requires theorems-ams-extended-chap-bytype module to be loaded",
5888     r"Counter acknowledgement",
5889     r"  GuiName Acknowledgment",
5890     r"  Within chapter",
5891     r"End",
5892     r"Style Acknowledgement",
5893     r"  CopyStyle             Remark",
5894     r"  LatexName             acknowledgement",
5895     r'  LabelString           "Acknowledgement \theacknowledgement."',
5896     r"  Preamble",
5897     r"    \theoremstyle{remark}",
5898     r"    \ifx\thechapter\undefined",
5899     r"      \newtheorem{acknowledgement}{\protect\acknowledgementname}",
5900     r"    \else",
5901     r"      \newtheorem{acknowledgement}{\protect\acknowledgementname}[chapter]",
5902     r"    \fi",
5903     r"  EndPreamble",
5904     r"  LangPreamble",
5905     r"    \providecommand{\acknowledgementname}{_(Acknowledgement)}",
5906     r"  EndLangPreamble",
5907     r"  BabelPreamble",
5908     r"    \addto\captions$$lang{\renewcommand{\acknowledgementname}{_(Acknowledgement)}}",
5909     r"  EndBabelPreamble",
5910     r"  DocBookTag            para",
5911     r'  DocBookAttr           role="acknowledgement"',
5912     r'  DocBookItemTag        ""',
5913     r"End",
5914 ]
5915
5916
5917 def convert_ack_theorems(document):
5918     """Put removed acknowledgement theorems to local layout"""
5919
5920     haveAck = False
5921     haveStarAck = False
5922     if "theorems-ams-extended-bytype" in document.get_module_list():
5923         i = 0
5924         while True:
5925             if haveAck and haveStarAck:
5926                 break
5927             i = find_token(document.body, "\\begin_layout Acknowledgement", i)
5928             if i == -1:
5929                 break
5930             if document.body[i] == "\\begin_layout Acknowledgement*" and not haveStarAck:
5931                 document.append_local_layout(ackStar_theorem_def)
5932                 haveStarAck = True
5933             elif not haveAck:
5934                 document.append_local_layout(ack_bytype_theorem_def)
5935                 haveAck = True
5936             i += 1
5937     elif "theorems-ams-extended-chap-bytype" in document.get_module_list():
5938         i = 0
5939         while True:
5940             if haveAck and haveStarAck:
5941                 break
5942             i = find_token(document.body, "\\begin_layout Acknowledgement", i)
5943             if i == -1:
5944                 break
5945             if document.body[i] == "\\begin_layout Acknowledgement*" and not haveStarAck:
5946                 document.append_local_layout(ackStar_theorem_def)
5947                 haveStarAck = True
5948             elif not haveAck:
5949                 document.append_local_layout(ack_chap_bytype_theorem_def)
5950                 haveAck = True
5951             i += 1
5952     elif "theorems-ams-extended" in document.get_module_list():
5953         i = 0
5954         while True:
5955             if haveAck and haveStarAck:
5956                 break
5957             i = find_token(document.body, "\\begin_layout Acknowledgement", i)
5958             if i == -1:
5959                 break
5960             if document.body[i] == "\\begin_layout Acknowledgement*" and not haveStarAck:
5961                 document.append_local_layout(ackStar_theorem_def)
5962                 haveStarAck = True
5963             elif not haveAck:
5964                 document.append_local_layout(ack_theorem_def)
5965                 haveAck = True
5966             i += 1
5967
5968
5969 def revert_ack_theorems(document):
5970     """Remove acknowledgement theorems from local layout"""
5971     if "theorems-ams-extended-bytype" in document.get_module_list():
5972         document.del_local_layout(ackStar_theorem_def)
5973         document.del_local_layout(ack_bytype_theorem_def)
5974     elif "theorems-ams-extended-chap-bytype" in document.get_module_list():
5975         document.del_local_layout(ackStar_theorem_def)
5976         document.del_local_layout(ack_chap_bytype_theorem_def)
5977     elif "theorems-ams-extended" in document.get_module_list():
5978         document.del_local_layout(ackStar_theorem_def)
5979         document.del_local_layout(ack_theorem_def)
5980
5981
5982 def revert_empty_macro(document):
5983     """Remove macros with empty LaTeX part"""
5984     i = 0
5985     while True:
5986         i = find_token(document.body, "\\begin_inset FormulaMacro", i)
5987         if i == -1:
5988             break
5989         cmd = document.body[i + 1]
5990         if cmd[-3:] != "}{}" and cmd[-3:] != "]{}":
5991             i += 1
5992             continue
5993         j = find_end_of_inset(document.body, i)
5994         document.body[i : j + 1] = []
5995
5996
5997 def convert_empty_macro(document):
5998     """In the unlikely event someone defined a macro with empty LaTeX, add {}"""
5999     i = 0
6000     while True:
6001         i = find_token(document.body, "\\begin_inset FormulaMacro", i)
6002         if i == -1:
6003             break
6004         cmd = document.body[i + 1]
6005         if cmd[-3:] != "}{}" and cmd[-3:] != "]{}":
6006             i += 1
6007             continue
6008         newstr = cmd[:-2] + "{\\{\\}}"
6009         document.body[i + 1] = newstr
6010         i += 1
6011
6012
6013 def convert_cov_options(document):
6014     """Update examples item argument structure"""
6015
6016     if "linguistics" not in document.get_module_list():
6017         return
6018
6019     layouts = ["Numbered Examples (consecutive)", "Subexample"]
6020
6021     for layout in layouts:
6022         i = 0
6023         while True:
6024             i = find_token(document.body, "\\begin_layout %s" % layout, i)
6025             if i == -1:
6026                 break
6027             j = find_end_of_layout(document.body, i)
6028             if j == -1:
6029                 document.warning(
6030                     "Malformed LyX document: Can't find end of example layout at line %d" % i
6031                 )
6032                 i += 1
6033                 continue
6034             k = find_token(document.body, "\\begin_inset Argument item:1", i, j)
6035             if k != -1:
6036                 document.body[k] = "\\begin_inset Argument item:2"
6037             i += 1
6038     # Shift gloss arguments
6039     i = 0
6040     while True:
6041         i = find_token(document.body, "\\begin_inset Flex Interlinear Gloss (2 Lines)", i)
6042         if i == -1:
6043             break
6044         j = find_end_of_inset(document.body, i)
6045         if j == -1:
6046             document.warning(
6047                 "Malformed LyX document: Can't find end of gloss inset at line %d" % i
6048             )
6049             i += 1
6050             continue
6051         k = find_token(document.body, "\\begin_inset Argument post:2", i, j)
6052         if k != -1:
6053             document.body[k] = "\\begin_inset Argument post:4"
6054         k = find_token(document.body, "\\begin_inset Argument post:1", i, j)
6055         if k != -1:
6056             document.body[k] = "\\begin_inset Argument post:2"
6057         i += 1
6058
6059     i = 0
6060     while True:
6061         i = find_token(document.body, "\\begin_inset Flex Interlinear Gloss (3 Lines)", i)
6062         if i == -1:
6063             break
6064         j = find_end_of_inset(document.body, i)
6065         if j == -1:
6066             document.warning(
6067                 "Malformed LyX document: Can't find end of gloss inset at line %d" % i
6068             )
6069             i += 1
6070             continue
6071         k = find_token(document.body, "\\begin_inset Argument post:3", i, j)
6072         if k != -1:
6073             document.body[k] = "\\begin_inset Argument post:6"
6074         k = find_token(document.body, "\\begin_inset Argument post:2", i, j)
6075         if k != -1:
6076             document.body[k] = "\\begin_inset Argument post:4"
6077         k = find_token(document.body, "\\begin_inset Argument post:1", i, j)
6078         if k != -1:
6079             document.body[k] = "\\begin_inset Argument post:2"
6080         i += 1
6081
6082
6083 def revert_linggloss2(document):
6084     "Revert gloss with new args to ERT"
6085
6086     if "linguistics" not in document.get_module_list():
6087         return
6088
6089     cov_req = False
6090     glosses = [
6091         "\\begin_inset Flex Interlinear Gloss (2 Lines)",
6092         "\\begin_inset Flex Interlinear Gloss (3 Lines)",
6093     ]
6094     for glosse in glosses:
6095         i = 0
6096         while True:
6097             i = find_token(document.body, glosse, i + 1)
6098             if i == -1:
6099                 break
6100             j = find_end_of_inset(document.body, i)
6101             if j == -1:
6102                 document.warning("Malformed LyX document: Can't find end of Gloss inset")
6103                 continue
6104
6105             # Check if we have new options
6106             arg = find_token(document.body, "\\begin_inset Argument post:1", i, j)
6107             if arg == -1:
6108                 arg = find_token(document.body, "\\begin_inset Argument post:3", i, j)
6109                 if arg == -1:
6110                     arg = find_token(document.body, "\\begin_inset Argument post:5", i, j)
6111             if arg == -1:
6112                 # nothing to do
6113                 continue
6114
6115             arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
6116             endarg = find_end_of_inset(document.body, arg)
6117             optargcontent = []
6118             if arg != -1:
6119                 argbeginPlain = find_token(
6120                     document.body, "\\begin_layout Plain Layout", arg, endarg
6121                 )
6122                 if argbeginPlain == -1:
6123                     document.warning("Malformed LyX document: Can't find optarg plain Layout")
6124                     continue
6125                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
6126                 optargcontent = document.body[argbeginPlain + 1 : argendPlain - 2]
6127
6128                 # remove Arg insets and paragraph, if it only contains this inset
6129                 if (
6130                     document.body[arg - 1] == "\\begin_layout Plain Layout"
6131                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
6132                 ):
6133                     del document.body[arg - 1 : endarg + 4]
6134                 else:
6135                     del document.body[arg : endarg + 1]
6136
6137             arg = find_token(document.body, "\\begin_inset Argument post:1", i, j)
6138             endarg = find_end_of_inset(document.body, arg)
6139             marg1content = []
6140             if arg != -1:
6141                 argbeginPlain = find_token(
6142                     document.body, "\\begin_layout Plain Layout", arg, endarg
6143                 )
6144                 if argbeginPlain == -1:
6145                     document.warning("Malformed LyX document: Can't find arg 1 plain Layout")
6146                     continue
6147                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
6148                 marg1content = document.body[argbeginPlain + 1 : argendPlain - 2]
6149
6150                 # remove Arg insets and paragraph, if it only contains this inset
6151                 if (
6152                     document.body[arg - 1] == "\\begin_layout Plain Layout"
6153                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
6154                 ):
6155                     del document.body[arg - 1 : endarg + 4]
6156                 else:
6157                     del document.body[arg : endarg + 1]
6158
6159             arg = find_token(document.body, "\\begin_inset Argument post:2", i, j)
6160             endarg = find_end_of_inset(document.body, arg)
6161             marg2content = []
6162             if arg != -1:
6163                 argbeginPlain = find_token(
6164                     document.body, "\\begin_layout Plain Layout", arg, endarg
6165                 )
6166                 if argbeginPlain == -1:
6167                     document.warning("Malformed LyX document: Can't find arg 2 plain Layout")
6168                     continue
6169                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
6170                 marg2content = document.body[argbeginPlain + 1 : argendPlain - 2]
6171
6172                 # remove Arg insets and paragraph, if it only contains this inset
6173                 if (
6174                     document.body[arg - 1] == "\\begin_layout Plain Layout"
6175                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
6176                 ):
6177                     del document.body[arg - 1 : endarg + 4]
6178                 else:
6179                     del document.body[arg : endarg + 1]
6180
6181             arg = find_token(document.body, "\\begin_inset Argument post:3", i, j)
6182             endarg = find_end_of_inset(document.body, arg)
6183             marg3content = []
6184             if arg != -1:
6185                 argbeginPlain = find_token(
6186                     document.body, "\\begin_layout Plain Layout", arg, endarg
6187                 )
6188                 if argbeginPlain == -1:
6189                     document.warning("Malformed LyX document: Can't find arg 3 plain Layout")
6190                     continue
6191                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
6192                 marg3content = document.body[argbeginPlain + 1 : argendPlain - 2]
6193
6194                 # remove Arg insets and paragraph, if it only contains this inset
6195                 if (
6196                     document.body[arg - 1] == "\\begin_layout Plain Layout"
6197                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
6198                 ):
6199                     del document.body[arg - 1 : endarg + 4]
6200                 else:
6201                     del document.body[arg : endarg + 1]
6202
6203             arg = find_token(document.body, "\\begin_inset Argument post:4", i, j)
6204             endarg = find_end_of_inset(document.body, arg)
6205             marg4content = []
6206             if arg != -1:
6207                 argbeginPlain = find_token(
6208                     document.body, "\\begin_layout Plain Layout", arg, endarg
6209                 )
6210                 if argbeginPlain == -1:
6211                     document.warning("Malformed LyX document: Can't find arg 4 plain Layout")
6212                     continue
6213                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
6214                 marg4content = document.body[argbeginPlain + 1 : argendPlain - 2]
6215
6216                 # remove Arg insets and paragraph, if it only contains this inset
6217                 if (
6218                     document.body[arg - 1] == "\\begin_layout Plain Layout"
6219                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
6220                 ):
6221                     del document.body[arg - 1 : endarg + 4]
6222                 else:
6223                     del document.body[arg : endarg + 1]
6224
6225             arg = find_token(document.body, "\\begin_inset Argument post:5", i, j)
6226             endarg = find_end_of_inset(document.body, arg)
6227             marg5content = []
6228             if arg != -1:
6229                 argbeginPlain = find_token(
6230                     document.body, "\\begin_layout Plain Layout", arg, endarg
6231                 )
6232                 if argbeginPlain == -1:
6233                     document.warning("Malformed LyX document: Can't find arg 5 plain Layout")
6234                     continue
6235                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
6236                 marg5content = document.body[argbeginPlain + 1 : argendPlain - 2]
6237
6238                 # remove Arg insets and paragraph, if it only contains this inset
6239                 if (
6240                     document.body[arg - 1] == "\\begin_layout Plain Layout"
6241                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
6242                 ):
6243                     del document.body[arg - 1 : endarg + 4]
6244                 else:
6245                     del document.body[arg : endarg + 1]
6246
6247             arg = find_token(document.body, "\\begin_inset Argument post:6", i, j)
6248             endarg = find_end_of_inset(document.body, arg)
6249             marg6content = []
6250             if arg != -1:
6251                 argbeginPlain = find_token(
6252                     document.body, "\\begin_layout Plain Layout", arg, endarg
6253                 )
6254                 if argbeginPlain == -1:
6255                     document.warning("Malformed LyX document: Can't find arg 6 plain Layout")
6256                     continue
6257                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
6258                 marg6content = document.body[argbeginPlain + 1 : argendPlain - 2]
6259
6260                 # remove Arg insets and paragraph, if it only contains this inset
6261                 if (
6262                     document.body[arg - 1] == "\\begin_layout Plain Layout"
6263                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
6264                 ):
6265                     del document.body[arg - 1 : endarg + 4]
6266                 else:
6267                     del document.body[arg : endarg + 1]
6268
6269             cmd = "\\digloss"
6270             if glosse == "\\begin_inset Flex Interlinear Gloss (3 Lines)":
6271                 cmd = "\\trigloss"
6272
6273             beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
6274             endInset = find_end_of_inset(document.body, i)
6275             endPlain = find_end_of_layout(document.body, beginPlain)
6276             precontent = put_cmd_in_ert(cmd)
6277             if len(optargcontent) > 0:
6278                 precontent += put_cmd_in_ert("[") + optargcontent + put_cmd_in_ert("]")
6279             precontent += put_cmd_in_ert("{")
6280
6281             postcontent = put_cmd_in_ert("}")
6282             if len(marg1content) > 0:
6283                 postcontent += put_cmd_in_ert("[") + marg1content + put_cmd_in_ert("]")
6284             postcontent += put_cmd_in_ert("{") + marg2content + put_cmd_in_ert("}")
6285             if len(marg3content) > 0:
6286                 postcontent += put_cmd_in_ert("[") + marg3content + put_cmd_in_ert("]")
6287             postcontent += put_cmd_in_ert("{") + marg4content + put_cmd_in_ert("}")
6288             if cmd == "\\trigloss":
6289                 if len(marg5content) > 0:
6290                     postcontent += put_cmd_in_ert("[") + marg5content + put_cmd_in_ert("]")
6291                 postcontent += put_cmd_in_ert("{") + marg6content + put_cmd_in_ert("}")
6292
6293             document.body[endPlain : endInset + 1] = postcontent
6294             document.body[beginPlain + 1 : beginPlain] = precontent
6295             del document.body[i : beginPlain + 1]
6296             if not cov_req:
6297                 document.append_local_layout("Requires covington")
6298                 cov_req = True
6299             i = beginPlain
6300
6301
6302 def revert_exarg2(document):
6303     "Revert linguistic examples with new arguments to ERT"
6304
6305     if "linguistics" not in document.get_module_list():
6306         return
6307
6308     cov_req = False
6309
6310     layouts = ["Numbered Example", "Subexample"]
6311
6312     for layout in layouts:
6313         i = 0
6314         while True:
6315             i = find_token(document.body, "\\begin_layout %s" % layout, i + 1)
6316             if i == -1:
6317                 break
6318             j = find_end_of_layout(document.body, i)
6319             if j == -1:
6320                 document.warning("Malformed LyX document: Can't find end of example layout")
6321                 continue
6322             consecex = document.body[i] == "\\begin_layout Numbered Examples (consecutive)"
6323             subexpl = document.body[i] == "\\begin_layout Subexample"
6324             singleex = document.body[i] == "\\begin_layout Numbered Examples (multiline)"
6325             layouttype = "\\begin_layout Numbered Examples (multiline)"
6326             if consecex:
6327                 layouttype = "\\begin_layout Numbered Examples (consecutive)"
6328             elif subexpl:
6329                 layouttype = "\\begin_layout Subexample"
6330             k = i
6331             l = j
6332             while True:
6333                 if singleex:
6334                     break
6335                 m = find_end_of_layout(document.body, k)
6336                 # check for consecutive layouts
6337                 k = find_token(document.body, "\\begin_layout", m)
6338                 if k == -1 or document.body[k] != layouttype:
6339                     break
6340                 l = find_end_of_layout(document.body, k)
6341                 if l == -1:
6342                     document.warning("Malformed LyX document: Can't find end of example layout")
6343                     continue
6344
6345             arg = find_token(document.body, "\\begin_inset Argument 1", i, l)
6346             if (
6347                 arg != -1
6348                 and layouttype
6349                 != "\\begin_layout " + get_containing_layout(document.body, arg)[0]
6350             ):
6351                 # this is not our argument!
6352                 arg = -1
6353             if subexpl or arg == -1:
6354                 iarg = find_token(document.body, "\\begin_inset Argument item:1", i, l)
6355                 if iarg == -1:
6356                     continue
6357
6358             if arg != -1:
6359                 endarg = find_end_of_inset(document.body, arg)
6360                 optargcontent = ""
6361                 argbeginPlain = find_token(
6362                     document.body, "\\begin_layout Plain Layout", arg, endarg
6363                 )
6364                 if argbeginPlain == -1:
6365                     document.warning("Malformed LyX document: Can't find optarg plain Layout")
6366                     continue
6367                 argendPlain = find_end_of_inset(document.body, argbeginPlain)
6368                 optargcontent = lyx2latex(
6369                     document, document.body[argbeginPlain + 1 : argendPlain - 2]
6370                 )
6371                 # This is a verbatim argument
6372                 optargcontent = re.sub(r"textbackslash{}", r"", optargcontent)
6373
6374             itemarg = ""
6375             iarg = find_token(document.body, "\\begin_inset Argument item:1", i, j)
6376             if iarg != -1:
6377                 endiarg = find_end_of_inset(document.body, iarg)
6378                 iargcontent = ""
6379                 iargbeginPlain = find_token(
6380                     document.body, "\\begin_layout Plain Layout", iarg, endiarg
6381                 )
6382                 if iargbeginPlain == -1:
6383                     document.warning("Malformed LyX document: Can't find optarg plain Layout")
6384                     continue
6385                 iargendPlain = find_end_of_inset(document.body, iargbeginPlain)
6386                 itemarg = (
6387                     "<" + lyx2latex(document, document.body[iargbeginPlain:iargendPlain]) + ">"
6388                 )
6389
6390             iarg2 = find_token(document.body, "\\begin_inset Argument item:2", i, j)
6391             if iarg2 != -1:
6392                 endiarg2 = find_end_of_inset(document.body, iarg2)
6393                 iarg2content = ""
6394                 iarg2beginPlain = find_token(
6395                     document.body, "\\begin_layout Plain Layout", iarg2, endiarg2
6396                 )
6397                 if iarg2beginPlain == -1:
6398                     document.warning("Malformed LyX document: Can't find optarg plain Layout")
6399                     continue
6400                 iarg2endPlain = find_end_of_inset(document.body, iarg2beginPlain)
6401                 itemarg += (
6402                     "["
6403                     + lyx2latex(document, document.body[iarg2beginPlain:iarg2endPlain])
6404                     + "]"
6405                 )
6406
6407             if itemarg == "":
6408                 itemarg = " "
6409
6410             # remove Arg insets and paragraph, if it only contains this inset
6411             if arg != -1:
6412                 if (
6413                     document.body[arg - 1] == "\\begin_layout Plain Layout"
6414                     and find_end_of_layout(document.body, arg - 1) == endarg + 3
6415                 ):
6416                     del document.body[arg - 1 : endarg + 4]
6417                 else:
6418                     del document.body[arg : endarg + 1]
6419             if iarg != -1:
6420                 iarg = find_token(document.body, "\\begin_inset Argument item:1", i, j)
6421                 if iarg == -1:
6422                     document.warning("Unable to re-find item:1 Argument")
6423                 else:
6424                     endiarg = find_end_of_inset(document.body, iarg)
6425                     if (
6426                         document.body[iarg - 1] == "\\begin_layout Plain Layout"
6427                         and find_end_of_layout(document.body, iarg - 1) == endiarg + 3
6428                     ):
6429                         del document.body[iarg - 1 : endiarg + 4]
6430                     else:
6431                         del document.body[iarg : endiarg + 1]
6432             if iarg2 != -1:
6433                 iarg2 = find_token(document.body, "\\begin_inset Argument item:2", i, j)
6434                 if iarg2 == -1:
6435                     document.warning("Unable to re-find item:2 Argument")
6436                 else:
6437                     endiarg2 = find_end_of_inset(document.body, iarg2)
6438                     if (
6439                         document.body[iarg2 - 1] == "\\begin_layout Plain Layout"
6440                         and find_end_of_layout(document.body, iarg2 - 1) == endiarg2 + 3
6441                     ):
6442                         del document.body[iarg2 - 1 : endiarg2 + 4]
6443                     else:
6444                         del document.body[iarg2 : endiarg2 + 1]
6445
6446             envname = "example"
6447             if consecex:
6448                 envname = "examples"
6449             elif subexpl:
6450                 envname = "subexamples"
6451
6452             cmd = put_cmd_in_ert("\\begin{" + envname + "}[" + optargcontent + "]")
6453
6454             # re-find end of layout
6455             j = find_end_of_layout(document.body, i)
6456             if j == -1:
6457                 document.warning("Malformed LyX document: Can't find end of Subexample layout")
6458                 continue
6459             l = j
6460             while True:
6461                 # check for consecutive layouts
6462                 k = find_token(document.body, "\\begin_layout", l)
6463                 if k == -1 or document.body[k] != layouttype:
6464                     break
6465                 if not singleex:
6466                     subitemarg = ""
6467                     m = find_end_of_layout(document.body, k)
6468                     iarg = find_token(document.body, "\\begin_inset Argument item:1", k, m)
6469                     if iarg != -1:
6470                         endiarg = find_end_of_inset(document.body, iarg)
6471                         iargcontent = ""
6472                         iargbeginPlain = find_token(
6473                             document.body, "\\begin_layout Plain Layout", iarg, endiarg
6474                         )
6475                         if iargbeginPlain == -1:
6476                             document.warning(
6477                                 "Malformed LyX document: Can't find optarg plain Layout"
6478                             )
6479                             continue
6480                         iargendPlain = find_end_of_inset(document.body, iargbeginPlain)
6481                         subitemarg = (
6482                             "<"
6483                             + lyx2latex(document, document.body[iargbeginPlain:iargendPlain])
6484                             + ">"
6485                         )
6486
6487                     iarg2 = find_token(document.body, "\\begin_inset Argument item:2", k, m)
6488                     if iarg2 != -1:
6489                         endiarg2 = find_end_of_inset(document.body, iarg2)
6490                         iarg2content = ""
6491                         iarg2beginPlain = find_token(
6492                             document.body,
6493                             "\\begin_layout Plain Layout",
6494                             iarg2,
6495                             endiarg2,
6496                         )
6497                         if iarg2beginPlain == -1:
6498                             document.warning(
6499                                 "Malformed LyX document: Can't find optarg plain Layout"
6500                             )
6501                             continue
6502                         iarg2endPlain = find_end_of_inset(document.body, iarg2beginPlain)
6503                         subitemarg += (
6504                             "["
6505                             + lyx2latex(document, document.body[iarg2beginPlain:iarg2endPlain])
6506                             + "]"
6507                         )
6508
6509                     if subitemarg == "":
6510                         subitemarg = " "
6511                     document.body[k : k + 1] = ["\\begin_layout Standard"] + put_cmd_in_ert(
6512                         "\\item" + subitemarg
6513                     )
6514                     # Refind and remove arg insets
6515                     if iarg != -1:
6516                         iarg = find_token(document.body, "\\begin_inset Argument item:1", k, m)
6517                         if iarg == -1:
6518                             document.warning("Unable to re-find item:1 Argument")
6519                         else:
6520                             endiarg = find_end_of_inset(document.body, iarg)
6521                             if (
6522                                 document.body[iarg - 1] == "\\begin_layout Plain Layout"
6523                                 and find_end_of_layout(document.body, iarg - 1) == endiarg + 3
6524                             ):
6525                                 del document.body[iarg - 1 : endiarg + 4]
6526                             else:
6527                                 del document.body[iarg : endiarg + 1]
6528                     if iarg2 != -1:
6529                         iarg2 = find_token(document.body, "\\begin_inset Argument item:2", k, m)
6530                         if iarg2 == -1:
6531                             document.warning("Unable to re-find item:2 Argument")
6532                         else:
6533                             endiarg2 = find_end_of_inset(document.body, iarg2)
6534                             if (
6535                                 document.body[iarg2 - 1] == "\\begin_layout Plain Layout"
6536                                 and find_end_of_layout(document.body, iarg2 - 1) == endiarg2 + 3
6537                             ):
6538                                 del document.body[iarg2 - 1 : endiarg2 + 4]
6539                             else:
6540                                 del document.body[iarg2 : endiarg2 + 1]
6541                 else:
6542                     document.body[k : k + 1] = ["\\begin_layout Standard"]
6543                 l = find_end_of_layout(document.body, k)
6544                 if l == -1:
6545                     document.warning("Malformed LyX document: Can't find end of example layout")
6546                     continue
6547
6548             endev = put_cmd_in_ert("\\end{" + envname + "}")
6549
6550             document.body[l:l] = ["\\end_layout", "", "\\begin_layout Standard"] + endev
6551             document.body[i : i + 1] = (
6552                 ["\\begin_layout Standard"]
6553                 + cmd
6554                 + ["\\end_layout", "", "\\begin_layout Standard"]
6555                 + put_cmd_in_ert("\\item" + itemarg)
6556             )
6557             if not cov_req:
6558                 document.append_local_layout("Requires covington")
6559                 cov_req = True
6560
6561
6562 def revert_cov_options(document):
6563     """Revert examples item argument structure"""
6564
6565     if "linguistics" not in document.get_module_list():
6566         return
6567
6568     layouts = ["Numbered Examples (consecutive)", "Subexample"]
6569
6570     for layout in layouts:
6571         i = 0
6572         while True:
6573             i = find_token(document.body, "\\begin_layout %s" % layout, i)
6574             if i == -1:
6575                 break
6576             j = find_end_of_layout(document.body, i)
6577             if j == -1:
6578                 document.warning(
6579                     "Malformed LyX document: Can't find end of example layout at line %d" % i
6580                 )
6581                 i += 1
6582                 continue
6583             k = find_token(document.body, "\\begin_inset Argument item:2", i, j)
6584             if k != -1:
6585                 document.body[k] = "\\begin_inset Argument item:1"
6586             i += 1
6587     # Shift gloss arguments
6588     i = 0
6589     while True:
6590         i = find_token(document.body, "\\begin_inset Flex Interlinear Gloss (2 Lines)", i)
6591         if i == -1:
6592             break
6593         j = find_end_of_inset(document.body, i)
6594         if j == -1:
6595             document.warning(
6596                 "Malformed LyX document: Can't find end of gloss inset at line %d" % i
6597             )
6598             i += 1
6599             continue
6600         k = find_token(document.body, "\\begin_inset Argument post:2", i, j)
6601         if k != -1:
6602             document.body[k] = "\\begin_inset Argument post:1"
6603         k = find_token(document.body, "\\begin_inset Argument post:4", i, j)
6604         if k != -1:
6605             document.body[k] = "\\begin_inset Argument post:2"
6606         i += 1
6607
6608     i = 0
6609     while True:
6610         i = find_token(document.body, "\\begin_inset Flex Interlinear Gloss (3 Lines)", i)
6611         if i == -1:
6612             break
6613         j = find_end_of_inset(document.body, i)
6614         if j == -1:
6615             document.warning(
6616                 "Malformed LyX document: Can't find end of gloss inset at line %d" % i
6617             )
6618             i += 1
6619             continue
6620         k = find_token(document.body, "\\begin_inset Argument post:2", i, j)
6621         if k != -1:
6622             document.body[k] = "\\begin_inset Argument post:1"
6623         k = find_token(document.body, "\\begin_inset Argument post:4", i, j)
6624         if k != -1:
6625             document.body[k] = "\\begin_inset Argument post:2"
6626         k = find_token(document.body, "\\begin_inset Argument post:6", i, j)
6627         if k != -1:
6628             document.body[k] = "\\begin_inset Argument post:3"
6629         i += 1
6630
6631
6632 def revert_expreambles(document):
6633     """Revert covington example preamble flex insets to ERT"""
6634
6635     revert_flex_inset(document, "Example Preamble", "\\expreamble")
6636     revert_flex_inset(document, "Subexample Preamble", "\\subexpreamble")
6637     revert_flex_inset(document, "Example Postamble", "\\expostamble")
6638     revert_flex_inset(document, "Subexample Postamble", "\\subexpostamble")
6639
6640
6641 def revert_hequotes(document):
6642     "Revert Hebrew Quotation marks"
6643
6644     i = find_token(document.header, "\\quotes_style hebrew", 0)
6645     if i != -1:
6646         document.header[i] = "\\quotes_style english"
6647
6648     i = 0
6649     while True:
6650         i = find_token(document.body, "\\begin_inset Quotes d")
6651         if i == -1:
6652             return
6653         if document.body[i] == "\\begin_inset Quotes dld":
6654             document.body[i] = "\\begin_inset Quotes prd"
6655         elif document.body[i] == "\\begin_inset Quotes drd":
6656             document.body[i] = "\\begin_inset Quotes pld"
6657         elif document.body[i] == "\\begin_inset Quotes dls":
6658             document.body[i] = "\\begin_inset Quotes prd"
6659         elif document.body[i] == "\\begin_inset Quotes drs":
6660             document.body[i] = "\\begin_inset Quotes pld"
6661
6662
6663 def revert_formatted_refs(document):
6664     i = find_token(document.header, "\\use_formatted_ref", 0)
6665     if i != -1:
6666         del document.header[i]
6667
6668
6669 def revert_box_fcolor(document):
6670     i = 0
6671     while True:
6672         i = find_token(document.body, "\\begin_inset Box Boxed", i + 1)
6673         if i == -1:
6674             break
6675         j = find_end_of_inset(document.body, i)
6676         if j == -1:
6677             document.warning(
6678                 "Malformed LyX document: Can't find end of framed box inset at line %d" % i
6679             )
6680             continue
6681         k = find_token(document.body, 'framecolor "default"', i, j)
6682         if k != -1:
6683             document.body[k] = 'framecolor "black"'
6684
6685
6686 ##
6687 # Conversion hub
6688 #
6689
6690 supported_versions = ["2.4.0", "2.4"]
6691 convert = [
6692     [545, [convert_lst_literalparam]],
6693     [546, []],
6694     [547, []],
6695     [548, []],
6696     [549, []],
6697     [550, [convert_fontenc]],
6698     [551, []],
6699     [552, []],
6700     [553, []],
6701     [554, []],
6702     [555, []],
6703     [556, []],
6704     [557, [convert_vcsinfo]],
6705     [558, [removeFrontMatterStyles]],
6706     [559, []],
6707     [560, []],
6708     [561, [convert_latexFonts]],  # Handle dejavu, ibmplex fonts in GUI
6709     [562, []],
6710     [563, []],
6711     [564, []],
6712     [565, [convert_AdobeFonts]],  # Handle adobe fonts in GUI
6713     [566, [convert_hebrew_parentheses]],
6714     [567, []],
6715     [568, []],
6716     [569, []],
6717     [570, []],
6718     [571, []],
6719     [572, [convert_notoFonts]],  # Added options thin, light, extralight for Noto
6720     [573, [convert_inputencoding_namechange]],
6721     [574, [convert_ruby_module, convert_utf8_japanese]],
6722     [575, [convert_lineno, convert_aaencoding]],
6723     [576, []],
6724     [577, [convert_linggloss]],
6725     [578, []],
6726     [579, []],
6727     [580, []],
6728     [581, [convert_osf]],
6729     [
6730         582,
6731         [
6732             convert_AdobeFonts,
6733             convert_latexFonts,
6734             convert_notoFonts,
6735             convert_CantarellFont,
6736             convert_FiraFont,
6737         ],
6738     ],  # old font re-converterted due to extra options
6739     [
6740         583,
6741         [
6742             convert_ChivoFont,
6743             convert_Semibolds,
6744             convert_NotoRegulars,
6745             convert_CrimsonProFont,
6746         ],
6747     ],
6748     [584, []],
6749     [585, [convert_pagesizes]],
6750     [586, []],
6751     [587, [convert_pagesizenames]],
6752     [588, []],
6753     [589, [convert_totalheight]],
6754     [590, [convert_changebars]],
6755     [591, [convert_postpone_fragile]],
6756     [592, []],
6757     [593, [convert_counter_maintenance]],
6758     [594, []],
6759     [595, []],
6760     [596, [convert_parskip]],
6761     [597, [convert_libertinus_rm_fonts]],
6762     [598, []],
6763     [599, []],
6764     [600, []],
6765     [601, [convert_math_refs]],
6766     [602, [convert_branch_colors]],
6767     [603, []],
6768     [604, []],
6769     [605, [convert_vcolumns2]],
6770     [606, [convert_koma_frontispiece]],
6771     [607, []],
6772     [608, []],
6773     [609, []],
6774     [610, []],
6775     [611, []],
6776     [612, [convert_starred_refs]],
6777     [613, []],
6778     [614, [convert_hyper_other]],
6779     [615, [convert_acknowledgment, convert_ack_theorems]],
6780     [616, [convert_empty_macro]],
6781     [617, [convert_cov_options]],
6782     [618, []],
6783     [619, []],
6784     [620, []],
6785 ]
6786
6787
6788 revert = [
6789     [619, [revert_box_fcolor]],
6790     [618, [revert_formatted_refs]],
6791     [617, [revert_hequotes]],
6792     [616, [revert_expreambles, revert_exarg2, revert_linggloss2, revert_cov_options]],
6793     [615, [revert_empty_macro]],
6794     [614, [revert_ack_theorems, revert_acknowledgment]],
6795     [613, [revert_hyper_other]],
6796     [612, [revert_familydefault]],
6797     [611, [revert_starred_refs]],
6798     [610, []],
6799     [609, [revert_index_macros]],
6800     [608, [revert_document_metadata]],
6801     [607, [revert_docbook_mathml_prefix]],
6802     [606, [revert_spellchecker_ignore]],
6803     [605, [revert_koma_frontispiece]],
6804     [604, [revert_vcolumns2]],
6805     [603, [revert_branch_darkcols]],
6806     [602, [revert_darkmode_graphics]],
6807     [601, [revert_branch_colors]],
6808     [600, []],
6809     [599, [revert_math_refs]],
6810     [598, [revert_hrquotes]],
6811     [598, [revert_nopagebreak]],
6812     [597, [revert_docbook_table_output]],
6813     [596, [revert_libertinus_rm_fonts, revert_libertinus_sftt_fonts]],
6814     [595, [revert_parskip, revert_line_vspaces]],
6815     [594, [revert_ams_spaces]],
6816     [593, [revert_counter_inset]],
6817     [592, [revert_counter_maintenance]],
6818     [591, [revert_colrow_tracking]],
6819     [590, [revert_postpone_fragile]],
6820     [589, [revert_changebars]],
6821     [588, [revert_totalheight]],
6822     [587, [revert_memoir_endnotes, revert_enotez, revert_theendnotes]],
6823     [586, [revert_pagesizenames]],
6824     [585, [revert_dupqualicites]],
6825     [584, [revert_pagesizes, revert_komafontsizes]],
6826     [583, [revert_vcsinfo_rev_abbrev]],
6827     [582, [revert_ChivoFont, revert_CrimsonProFont]],
6828     [581, [revert_CantarellFont, revert_FiraFont]],
6829     [580, [revert_texfontopts, revert_osf]],
6830     [
6831         579,
6832         [
6833             revert_minionpro,
6834             revert_plainNotoFonts_xopts,
6835             revert_notoFonts_xopts,
6836             revert_IBMFonts_xopts,
6837             revert_AdobeFonts_xopts,
6838             revert_font_opts,
6839         ],
6840     ],  # keep revert_font_opts last!
6841     [578, [revert_babelfont]],
6842     [577, [revert_drs]],
6843     [576, [revert_linggloss, revert_subexarg]],
6844     [575, [revert_new_languages]],
6845     [574, [revert_lineno, revert_aaencoding]],
6846     [573, [revert_ruby_module, revert_utf8_japanese]],
6847     [572, [revert_inputencoding_namechange]],
6848     [571, [revert_notoFonts]],
6849     [570, [revert_cmidruletrimming]],
6850     [569, [revert_bibfileencodings]],
6851     [568, [revert_tablestyle]],
6852     [567, [revert_soul]],
6853     [566, [revert_malayalam]],
6854     [565, [revert_hebrew_parentheses]],
6855     [564, [revert_AdobeFonts]],
6856     [563, [revert_lformatinfo]],
6857     [562, [revert_listpargs]],
6858     [561, [revert_l7ninfo]],
6859     [560, [revert_latexFonts]],  # Handle dejavu, ibmplex fonts in user preamble
6860     [559, [revert_timeinfo, revert_namenoextinfo]],
6861     [558, [revert_dateinfo]],
6862     [557, [addFrontMatterStyles]],
6863     [556, [revert_vcsinfo]],
6864     [555, [revert_bibencoding]],
6865     [554, [revert_vcolumns]],
6866     [553, [revert_stretchcolumn]],
6867     [552, [revert_tuftecite]],
6868     [551, [revert_floatpclass, revert_floatalignment]],
6869     [550, [revert_nospellcheck]],
6870     [549, [revert_fontenc]],
6871     [548, []],  # dummy format change
6872     [547, [revert_lscape]],
6873     [546, [revert_xcharter]],
6874     [545, [revert_paratype]],
6875     [544, [revert_lst_literalparam]],
6876 ]
6877
6878
6879 if __name__ == "__main__":
6880     pass