]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_3.py
Reformat lyx2lyx code using ruff
[lyx.git] / lib / lyx2lyx / lyx_2_3.py
1 # This file is part of lyx2lyx
2 # Copyright (C) 2016 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.3"""
19
20 import re
21
22 # Uncomment only what you need to import, please.
23
24 from parser_tools import (
25     del_token,
26     del_value,
27     del_complete_lines,
28     find_complete_lines,
29     find_end_of,
30     find_end_of_layout,
31     find_end_of_inset,
32     find_re,
33     find_substring,
34     find_token,
35     find_token_backwards,
36     find_across_lines,
37     get_containing_inset,
38     get_containing_layout,
39     get_bool_value,
40     get_value,
41     get_quoted_value,
42     is_in_inset,
43     set_bool_value,
44 )
45
46 from lyx2lyx_tools import (
47     add_to_preamble,
48     put_cmd_in_ert,
49     revert_font_attrs,
50     insert_to_preamble,
51     latex_length,
52     is_document_option,
53     insert_document_option,
54     remove_document_option,
55     revert_language,
56 )
57
58 ####################################################################
59 # Private helper functions
60
61
62 ###############################################################################
63 ###
64 ### Conversion and reversion routines
65 ###
66 ###############################################################################
67
68
69 def convert_microtype(document):
70     "Add microtype settings."
71     i = find_token(document.header, "\\font_tt_scale")
72     j = find_token(document.preamble, "\\usepackage{microtype}")
73     if j == -1:
74         document.header.insert(i + 1, "\\use_microtype false")
75     else:
76         document.header.insert(i + 1, "\\use_microtype true")
77         del document.preamble[j]
78         if j and document.preamble[j - 1] == "% Added by lyx2lyx":
79             del document.preamble[j - 1]
80
81
82 def revert_microtype(document):
83     "Remove microtype settings."
84     use_microtype = get_bool_value(document.header, "\\use_microtype", delete=True)
85     if use_microtype:
86         add_to_preamble(document, ["\\usepackage{microtype}"])
87
88
89 def convert_dateinset(document):
90     "Convert date external inset to ERT"
91     i = 0
92     while True:
93         i = find_token(document.body, "\\begin_inset External", i + 1)
94         if i == -1:
95             return
96         j = find_end_of_inset(document.body, i)
97         if j == -1:
98             document.warning(
99                 "Malformed lyx document: Missing '\\end_inset' in convert_dateinset."
100             )
101             continue
102         if get_value(document.body, "template", i, j) == "Date":
103             document.body[i : j + 1] = put_cmd_in_ert("\\today ")
104         i = j  # skip inset
105
106
107 def convert_inputenc(document):
108     """Replace no longer supported input encoding setting."""
109     i = find_token(document.header, "\\inputencoding pt254")
110     if i != -1:
111         document.header[i] = "\\inputencoding pt154"
112
113
114 def convert_ibranches(document):
115     'Add "inverted 0" to branch insets'
116     i = 0
117     while True:
118         i = find_token(document.body, "\\begin_inset Branch", i + 1)
119         if i == -1:
120             return
121         document.body.insert(i + 1, "inverted 0")
122
123
124 def revert_ibranches(document):
125     "Convert inverted branches to explicit anti-branches"
126     # Get list of branches
127     ourbranches = {}
128     i = 0
129     while True:
130         i = find_token(document.header, "\\branch", i + 1)
131         if i == -1:
132             break
133         branch = document.header[i][8:].strip()
134         selected = get_bool_value(document.header, "\\selected", i + 1, i + 2)
135         if selected is None:
136             document.warning(
137                 "Malformed LyX document: No selection indicator " "for branch %s." % branch
138             )
139             selected = True
140         # the value tells us whether the branch is selected
141         ourbranches[branch] = selected
142
143     # Find branch insets, remove "inverted" tag and
144     # convert inverted insets to "Anti-OldBranch" insets
145     antibranches = {}
146     i = 0
147     while True:
148         i = find_token(document.body, "\\begin_inset Branch", i + 1)
149         if i == -1:
150             break
151         inverted = get_bool_value(document.body, "inverted", i + 1, i + 2, delete=True)
152         if inverted is None:
153             document.warning("Malformed LyX document: Missing 'inverted' tag in branch inset.")
154             continue
155         if inverted:
156             branch = document.body[i][20:].strip()
157             if not branch in antibranches:
158                 antibranch = "Anti-" + branch
159                 while antibranch in antibranches:
160                     antibranch = "x" + antibranch
161                 antibranches[branch] = antibranch
162             else:
163                 antibranch = antibranches[branch]
164             document.body[i] = "\\begin_inset Branch " + antibranch
165
166     # now we need to add the new branches to the header
167     for old, new in antibranches.items():
168         i = find_token(document.header, "\\branch " + old, 0)
169         if i == -1:
170             document.warning("Can't find branch %s even though we found it before!" % (old))
171             continue
172         j = find_token(document.header, "\\end_branch", i)
173         if j == -1:
174             document.warning("Malformed LyX document! Can't find end of branch " + old)
175             continue
176         lines = ["\\branch " + new, "\\selected %d" % (not ourbranches[old])]
177         # these are the old lines telling us color, etc.
178         lines += document.header[i + 2 : j + 1]
179         document.header[i:i] = lines
180
181
182 beamer_article_styles = [
183     "### Inserted by lyx2lyx (more [scr]article styles) ###",
184     "Input article.layout",
185     "Input beamer.layout",
186     "Provides geometry 0",
187     "Provides hyperref 0",
188     "DefaultFont",
189     "     Family                Roman",
190     "     Series                Medium",
191     "     Shape                 Up",
192     "     Size                  Normal",
193     "     Color                 None",
194     "EndFont",
195     "Preamble",
196     "     \\usepackage{beamerarticle,pgf}",
197     "     % this default might be overridden by plain title style",
198     "     \\newcommand\\makebeamertitle{\\frame{\\maketitle}}%",
199     "     \\AtBeginDocument{",
200     "             \\let\\origtableofcontents=\\tableofcontents",
201     "             \\def\\tableofcontents{\\@ifnextchar[{\\origtableofcontents}{\\gobbletableofcontents}}",
202     "             \\def\\gobbletableofcontents#1{\\origtableofcontents}",
203     "     }",
204     "EndPreamble",
205     "### End of insertion by lyx2lyx (more [scr]article styles) ###",
206 ]
207
208
209 def revert_beamer_article_styles(document):
210     "Include (scr)article styles in beamer article"
211
212     beamer_articles = ["article-beamer", "scrarticle-beamer"]
213     if document.textclass not in beamer_articles:
214         return
215
216     if document.textclass == "scrarticle-beamer":
217         beamer_article_styles[1] = "Input scrartcl.layout"
218     document.append_local_layout(beamer_article_styles)
219
220
221 def convert_beamer_article_styles(document):
222     "Remove included (scr)article styles in beamer article"
223
224     beamer_articles = ["article-beamer", "scrarticle-beamer"]
225     if document.textclass not in beamer_articles:
226         return
227
228     if document.textclass == "scrarticle-beamer":
229         beamer_article_styles[1] = "Input scrartcl.layout"
230     document.del_local_layout(beamer_article_styles)
231
232
233 def revert_new_babel_languages(document):
234     """Revert "bosnian", "friulan", "macedonian", "piedmontese", "romansh".
235
236     Set the document language to English but use correct babel setting.
237     """
238
239     nblanguages = ["bosnian", "friulan", "macedonian", "piedmontese", "romansh"]
240
241     for lang in nblanguages:
242         if lang == "bosnian" or lang == "macedonian":
243             # These are only supported by babel
244             revert_language(document, lang, lang, "")
245         else:
246             # These are supported by babel and polyglossia
247             revert_language(document, lang, lang, lang)
248
249
250 # TODO:
251 # def convert_new_babel_languages(document)
252 # set to native support if get_value(document.header, "\\options") in
253 # ["bosnian", "friulan", "macedonian", "piedmontese", "romansh"]
254 # and Babel is used.
255
256
257 def revert_amharic(document):
258     "Set the document language to English but assure Amharic output"
259
260     revert_language(document, "amharic", "", "amharic")
261
262
263 def revert_asturian(document):
264     "Set the document language to English but assure Asturian output"
265
266     revert_language(document, "asturian", "", "asturian")
267
268
269 def revert_kannada(document):
270     "Set the document language to English but assure Kannada output"
271
272     revert_language(document, "kannada", "", "kannada")
273
274
275 def revert_khmer(document):
276     "Set the document language to English but assure Khmer output"
277
278     revert_language(document, "khmer", "", "khmer")
279
280
281 def revert_urdu(document):
282     "Set the document language to English but assure Urdu output"
283
284     revert_language(document, "urdu", "", "urdu")
285
286
287 def revert_syriac(document):
288     "Set the document language to English but assure Syriac output"
289
290     revert_language(document, "syriac", "", "syriac")
291
292
293 def revert_quotes(document):
294     "Revert Quote Insets in verbatim or Hebrew context to plain quotes"
295
296     # First handle verbatim insets
297     i = 0
298     j = 0
299     while i < len(document.body):
300         words = document.body[i].split()
301         if (
302             len(words) > 1
303             and words[0] == "\\begin_inset"
304             and (
305                 words[1] in ["ERT", "listings"]
306                 or (len(words) > 2 and words[2] in ["URL", "Chunk", "Sweave", "S/R"])
307             )
308         ):
309             j = find_end_of_inset(document.body, i)
310
311             if j == -1:
312                 document.warning(
313                     "Malformed LyX document: Can't find end of "
314                     + words[1]
315                     + " inset at line "
316                     + str(i)
317                 )
318                 i += 1
319                 continue
320             while True:
321                 k = find_token(document.body, "\\begin_inset Quotes", i, j)
322                 if k == -1:
323                     i += 1
324                     break
325                 l = find_end_of_inset(document.body, k)
326                 if l == -1:
327                     document.warning(
328                         "Malformed LyX document: Can't find end of Quote inset at line "
329                         + str(k)
330                     )
331                     i = k
332                     continue
333                 replace = '"'
334                 if document.body[k].endswith("s"):
335                     replace = "'"
336                 document.body[k : l + 2] = [replace]
337         else:
338             i += 1
339             continue
340
341     # Now verbatim layouts
342     i = 0
343     j = 0
344     while i < len(document.body):
345         words = document.body[i].split()
346         if (
347             len(words) > 1
348             and words[0] == "\\begin_layout"
349             and words[1] in ["Verbatim", "Verbatim*", "Code", "Author_Email", "Author_URL"]
350         ):
351             j = find_end_of_layout(document.body, i)
352             if j == -1:
353                 document.warning(
354                     "Malformed LyX document: Can't find end of "
355                     + words[1]
356                     + " layout at line "
357                     + str(i)
358                 )
359                 i += 1
360                 continue
361             while True:
362                 k = find_token(document.body, "\\begin_inset Quotes", i, j)
363                 if k == -1:
364                     i += 1
365                     break
366                 l = find_end_of_inset(document.body, k)
367                 if l == -1:
368                     document.warning(
369                         "Malformed LyX document: Can't find end of Quote inset at line "
370                         + str(k)
371                     )
372                     i = k
373                     continue
374                 replace = '"'
375                 if document.body[k].endswith("s"):
376                     replace = "'"
377                 document.body[k : l + 2] = [replace]
378         else:
379             i += 1
380             continue
381
382     # Now handle Hebrew
383     if (
384         not document.language == "hebrew"
385         and find_token(document.body, "\\lang hebrew", 0) == -1
386     ):
387         return
388
389     i = 0
390     j = 0
391     while True:
392         k = find_token(document.body, "\\begin_inset Quotes", i)
393         if k == -1:
394             return
395         l = find_end_of_inset(document.body, k)
396         if l == -1:
397             document.warning(
398                 "Malformed LyX document: Can't find end of Quote inset at line " + str(k)
399             )
400             i = k
401             continue
402         hebrew = False
403         parent = get_containing_layout(document.body, k)
404         ql = find_token_backwards(document.body, "\\lang", k)
405         if ql == -1 or ql < parent[1]:
406             hebrew = document.language == "hebrew"
407         elif document.body[ql] == "\\lang hebrew":
408             hebrew = True
409         if hebrew:
410             replace = '"'
411             if document.body[k].endswith("s"):
412                 replace = "'"
413             document.body[k : l + 2] = [replace]
414         i = l
415
416
417 iopart_local_layout = [
418     "### Inserted by lyx2lyx (stdlayouts) ###",
419     "Input stdlayouts.inc",
420     "### End of insertion by lyx2lyx (stdlayouts) ###" "",
421 ]
422
423
424 def revert_iopart(document):
425     "Input new styles via local layout"
426     if document.textclass != "iopart":
427         return
428     document.append_local_layout(iopart_local_layout)
429
430
431 def convert_iopart(document):
432     "Remove local layout we added, if it is there"
433     if document.textclass != "iopart":
434         return
435     document.del_local_layout(iopart_local_layout)
436
437
438 def convert_quotestyle(document):
439     "Convert \\quotes_language to \\quotes_style"
440     i = find_token(document.header, "\\quotes_language", 0)
441     if i == -1:
442         document.warning("Malformed LyX document! Can't find \\quotes_language!")
443         return
444     val = get_value(document.header, "\\quotes_language", i)
445     document.header[i] = "\\quotes_style " + val
446
447
448 def revert_quotestyle(document):
449     "Revert \\quotes_style to \\quotes_language"
450     i = find_token(document.header, "\\quotes_style", 0)
451     if i == -1:
452         document.warning("Malformed LyX document! Can't find \\quotes_style!")
453         return
454     val = get_value(document.header, "\\quotes_style", i)
455     document.header[i] = "\\quotes_language " + val
456
457
458 def revert_plainquote(document):
459     "Revert plain quote insets"
460
461     # First, revert style setting
462     i = find_token(document.header, "\\quotes_style plain", 0)
463     if i != -1:
464         document.header[i] = "\\quotes_style english"
465
466     # now the insets
467     i = 0
468     j = 0
469     while True:
470         k = find_token(document.body, "\\begin_inset Quotes q", i)
471         if k == -1:
472             return
473         l = find_end_of_inset(document.body, k)
474         if l == -1:
475             document.warning(
476                 "Malformed LyX document: Can't find end of Quote inset at line " + str(k)
477             )
478             i = k
479             continue
480         replace = '"'
481         if document.body[k].endswith("s"):
482             replace = "'"
483         document.body[k : l + 2] = [replace]
484         i = l
485
486
487 def convert_frenchquotes(document):
488     "Convert french quote insets to swiss"
489
490     # First, revert style setting
491     i = find_token(document.header, "\\quotes_style french", 0)
492     if i != -1:
493         document.header[i] = "\\quotes_style swiss"
494
495     # now the insets
496     i = 0
497     while True:
498         i = find_token(document.body, "\\begin_inset Quotes f", i)
499         if i == -1:
500             return
501         val = get_value(document.body, "\\begin_inset Quotes", i)[7:]
502         newval = val.replace("f", "c", 1)
503         document.body[i] = document.body[i].replace(val, newval)
504         i += 1
505
506
507 def revert_swissquotes(document):
508     "Revert swiss quote insets to french"
509
510     # First, revert style setting
511     i = find_token(document.header, "\\quotes_style swiss", 0)
512     if i != -1:
513         document.header[i] = "\\quotes_style french"
514
515     # now the insets
516     i = 0
517     while True:
518         i = find_token(document.body, "\\begin_inset Quotes c", i)
519         if i == -1:
520             return
521         val = get_value(document.body, "\\begin_inset Quotes", i)[7:]
522         newval = val.replace("c", "f", 1)
523         document.body[i] = document.body[i].replace(val, newval)
524         i += 1
525
526
527 def revert_britishquotes(document):
528     "Revert british quote insets to english"
529
530     # First, revert style setting
531     i = find_token(document.header, "\\quotes_style british", 0)
532     if i != -1:
533         document.header[i] = "\\quotes_style english"
534
535     # now the insets
536     i = 0
537     while True:
538         i = find_token(document.body, "\\begin_inset Quotes b", i)
539         if i == -1:
540             return
541         val = get_value(document.body, "\\begin_inset Quotes", i)[7:]
542         newval = val.replace("b", "e", 1)
543         if val[2] == "d":
544             # opening mark
545             newval = newval.replace("d", "s")
546         else:
547             # closing mark
548             newval = newval.replace("s", "d")
549         document.body[i] = document.body[i].replace(val, newval)
550         i += 1
551
552
553 def revert_swedishgquotes(document):
554     "Revert swedish quote insets"
555
556     # First, revert style setting
557     i = find_token(document.header, "\\quotes_style swedishg", 0)
558     if i != -1:
559         document.header[i] = "\\quotes_style danish"
560
561     # now the insets
562     i = 0
563     while True:
564         i = find_token(document.body, "\\begin_inset Quotes w", i)
565         if i == -1:
566             return
567         val = get_value(document.body, "\\begin_inset Quotes", i)[7:]
568         if val[2] == "d":
569             # outer marks
570             newval = val.replace("w", "a", 1).replace("r", "l")
571         else:
572             # inner marks
573             newval = val.replace("w", "s", 1)
574         document.body[i] = document.body[i].replace(val, newval)
575         i += 1
576
577
578 def revert_frenchquotes(document):
579     "Revert french inner quote insets"
580
581     i = 0
582     while True:
583         i = find_token(document.body, "\\begin_inset Quotes f", i)
584         if i == -1:
585             return
586         val = get_value(document.body, "\\begin_inset Quotes", i)[7:]
587         if val[2] == "s":
588             # inner marks
589             newval = val.replace("f", "e", 1).replace("s", "d")
590             document.body[i] = document.body[i].replace(val, newval)
591         i += 1
592
593
594 def revert_frenchinquotes(document):
595     "Revert inner frenchin quote insets"
596
597     # First, revert style setting
598     i = find_token(document.header, "\\quotes_style frenchin", 0)
599     if i != -1:
600         document.header[i] = "\\quotes_style french"
601
602     # now the insets
603     i = 0
604     while True:
605         i = find_token(document.body, "\\begin_inset Quotes i", i)
606         if i == -1:
607             return
608         val = get_value(document.body, "\\begin_inset Quotes", i)[7:]
609         newval = val.replace("i", "f", 1)
610         if val[2] == "s":
611             # inner marks
612             newval = newval.replace("s", "d")
613         document.body[i] = document.body[i].replace(val, newval)
614         i += 1
615
616
617 def revert_russianquotes(document):
618     "Revert russian quote insets"
619
620     # First, revert style setting
621     i = find_token(document.header, "\\quotes_style russian", 0)
622     if i != -1:
623         document.header[i] = "\\quotes_style french"
624
625     # now the insets
626     i = 0
627     while True:
628         i = find_token(document.body, "\\begin_inset Quotes r", i)
629         if i == -1:
630             return
631         val = get_value(document.body, "\\begin_inset Quotes", i)[7:]
632         newval = val
633         if val[2] == "s":
634             # inner marks
635             newval = val.replace("r", "g", 1).replace("s", "d")
636         else:
637             # outer marks
638             newval = val.replace("r", "f", 1)
639         document.body[i] = document.body[i].replace(val, newval)
640         i += 1
641
642
643 def revert_dynamicquotes(document):
644     "Revert dynamic quote insets"
645
646     # First, revert header
647     i = find_token(document.header, "\\dynamic_quotes", 0)
648     if i != -1:
649         del document.header[i]
650
651     # Get global style
652     style = "english"
653     i = find_token(document.header, "\\quotes_style", 0)
654     if i == -1:
655         document.warning("Malformed document! Missing \\quotes_style")
656     else:
657         style = get_value(document.header, "\\quotes_style", i)
658
659     s = "e"
660     if style == "english":
661         s = "e"
662     elif style == "swedish":
663         s = "s"
664     elif style == "german":
665         s = "g"
666     elif style == "polish":
667         s = "p"
668     elif style == "swiss":
669         s = "c"
670     elif style == "danish":
671         s = "a"
672     elif style == "plain":
673         s = "q"
674     elif style == "british":
675         s = "b"
676     elif style == "swedishg":
677         s = "w"
678     elif style == "french":
679         s = "f"
680     elif style == "frenchin":
681         s = "i"
682     elif style == "russian":
683         s = "r"
684
685     # now transform the insets
686     i = 0
687     while True:
688         i = find_token(document.body, "\\begin_inset Quotes x", i)
689         if i == -1:
690             return
691         document.body[i] = document.body[i].replace("x", s)
692         i += 1
693
694
695 def revert_cjkquotes(document):
696     "Revert cjk quote insets"
697
698     # Get global style
699     style = "english"
700     i = find_token(document.header, "\\quotes_style", 0)
701     if i == -1:
702         document.warning("Malformed document! Missing \\quotes_style")
703     else:
704         style = get_value(document.header, "\\quotes_style", i)
705
706     global_cjk = style.find("cjk") != -1
707
708     if global_cjk:
709         document.header[i] = "\\quotes_style english"
710         # transform dynamic insets
711         s = "j"
712         if style == "cjkangle":
713             s = "k"
714         i = 0
715         while True:
716             i = find_token(document.body, "\\begin_inset Quotes x", i)
717             if i == -1:
718                 break
719             document.body[i] = document.body[i].replace("x", s)
720             i += 1
721
722     cjk_langs = [
723         "chinese-simplified",
724         "chinese-traditional",
725         "japanese",
726         "japanese-cjk",
727         "korean",
728     ]
729
730     i = 0
731     j = 0
732     while True:
733         k = find_token(document.body, "\\begin_inset Quotes j", i)
734         if k == -1:
735             break
736         l = find_end_of_inset(document.body, k)
737         if l == -1:
738             document.warning(
739                 "Malformed LyX document: Can't find end of Quote inset at line " + str(k)
740             )
741             i = k
742             continue
743         cjk = False
744         parent = get_containing_layout(document.body, k)
745         ql = find_token_backwards(document.body, "\\lang", k)
746         if ql == -1 or ql < parent[1]:
747             cjk = document.language in cjk_langs
748         elif document.body[ql].split()[1] in cjk_langs:
749             cjk = True
750         val = get_value(document.body, "\\begin_inset Quotes", i)[7:]
751         replace = []
752         if val[2] == "s":
753             # inner marks
754             if val[1] == "l":
755                 # inner opening mark
756                 if cjk:
757                     replace = ["\u300e"]
758                 else:
759                     replace = ["\\begin_inset Formula $\\llceil$", "\\end_inset"]
760             else:
761                 # inner closing mark
762                 if cjk:
763                     replace = ["\u300f"]
764                 else:
765                     replace = ["\\begin_inset Formula $\\rrfloor$", "\\end_inset"]
766         else:
767             # outer marks
768             if val[1] == "l":
769                 # outer opening mark
770                 if cjk:
771                     replace = ["\u300c"]
772                 else:
773                     replace = ["\\begin_inset Formula $\\lceil$", "\\end_inset"]
774             else:
775                 # outer closing mark
776                 if cjk:
777                     replace = ["\u300d"]
778                 else:
779                     replace = ["\\begin_inset Formula $\\rfloor$", "\\end_inset"]
780
781         document.body[k : l + 1] = replace
782         i = l
783
784     i = 0
785     j = 0
786     while True:
787         k = find_token(document.body, "\\begin_inset Quotes k", i)
788         if k == -1:
789             return
790         l = find_end_of_inset(document.body, k)
791         if l == -1:
792             document.warning(
793                 "Malformed LyX document: Can't find end of Quote inset at line " + str(k)
794             )
795             i = k
796             continue
797         cjk = False
798         parent = get_containing_layout(document.body, k)
799         ql = find_token_backwards(document.body, "\\lang", k)
800         if ql == -1 or ql < parent[1]:
801             cjk = document.language in cjk_langs
802         elif document.body[ql].split()[1] in cjk_langs:
803             cjk = True
804         val = get_value(document.body, "\\begin_inset Quotes", i)[7:]
805         replace = []
806         if val[2] == "s":
807             # inner marks
808             if val[1] == "l":
809                 # inner opening mark
810                 if cjk:
811                     replace = ["\u3008"]
812                 else:
813                     replace = ["\\begin_inset Formula $\\langle$", "\\end_inset"]
814             else:
815                 # inner closing mark
816                 if cjk:
817                     replace = ["\u3009"]
818                 else:
819                     replace = ["\\begin_inset Formula $\\rangle$", "\\end_inset"]
820         else:
821             # outer marks
822             if val[1] == "l":
823                 # outer opening mark
824                 if cjk:
825                     replace = ["\u300a"]
826                 else:
827                     replace = [
828                         "\\begin_inset Formula $\\langle\\kern -2.5pt\\langle$",
829                         "\\end_inset",
830                     ]
831             else:
832                 # outer closing mark
833                 if cjk:
834                     replace = ["\u300b"]
835                 else:
836                     replace = [
837                         "\\begin_inset Formula $\\rangle\\kern -2.5pt\\rangle$",
838                         "\\end_inset",
839                     ]
840
841         document.body[k : l + 1] = replace
842         i = l
843
844
845 def convert_crimson(document):
846     """Transform preamble code to native font setting."""
847     # Quick-check:
848     i = find_substring(document.preamble, "{cochineal}")
849     if i == -1:
850         return
851     # Find and delete user-preamble code:
852     if document.preamble[i] == "\\usepackage[proportional,osf]{cochineal}":
853         osf = True
854     elif document.preamble[i] == "\\usepackage{cochineal}":
855         osf = False
856     else:
857         return
858     del document.preamble[i]
859     if i and document.preamble[i - 1] == "% Added by lyx2lyx":
860         del document.preamble[i - 1]
861
862     # Convert to native font setting:
863     j = find_token(document.header, "\\font_roman")
864     if j == -1:
865         romanfont = ["\font_roman", '"cochineal"', '"default"']
866     else:
867         romanfont = document.header[j].split()
868         romanfont[1] = '"cochineal"'
869     document.header[j] = " ".join(romanfont)
870     try:
871         set_bool_value(document.header, "\\font_osf", osf)
872     except ValueError:  # no \\font_osf setting in document.header
873         if osf:
874             document.header.insert(-1, "\\font_osf true")
875
876
877 def revert_crimson(document):
878     "Revert native Cochineal/Crimson font definition to LaTeX"
879
880     i = find_token(document.header, '\\font_roman "cochineal"')
881     if i == -1:
882         return
883     # replace unsupported font setting
884     document.header[i] = document.header[i].replace("cochineal", "default")
885     # no need for preamble code with system fonts
886     if get_bool_value(document.header, "\\use_non_tex_fonts"):
887         return
888     # transfer old style figures setting to package options
889     j = find_token(document.header, "\\font_osf true")
890     if j != -1:
891         options = "[proportional,osf]"
892         document.header[j] = "\\font_osf false"
893     else:
894         options = ""
895     add_to_preamble(document, ["\\usepackage%s{cochineal}" % options])
896
897
898 def revert_cochinealmath(document):
899     "Revert cochineal newtxmath definitions to LaTeX"
900
901     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
902         i = find_token(document.header, '\\font_math "cochineal-ntxm"', 0)
903         if i != -1:
904             add_to_preamble(document, "\\usepackage[cochineal]{newtxmath}")
905             document.header[i] = document.header[i].replace("cochineal-ntxm", "auto")
906
907
908 def revert_labelonly(document):
909     "Revert labelonly tag for InsetRef"
910     i = 0
911     while True:
912         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
913         if i == -1:
914             return
915         j = find_end_of_inset(document.body, i)
916         if j == -1:
917             document.warning("Can't find end of reference inset at line %d!!" % (i))
918             i += 1
919             continue
920         k = find_token(document.body, "LatexCommand labelonly", i, j)
921         if k == -1:
922             i = j
923             continue
924         label = get_quoted_value(document.body, "reference", i, j)
925         if not label:
926             document.warning("Can't find label for reference at line %d!" % (i))
927             i = j + 1
928             continue
929         document.body[i : j + 1] = put_cmd_in_ert([label])
930         i += 1
931
932
933 def revert_plural_refs(document):
934     "Revert plural and capitalized references"
935     i = find_token(document.header, "\\use_refstyle 1", 0)
936     use_refstyle = i != 0
937
938     i = 0
939     while True:
940         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
941         if i == -1:
942             return
943         j = find_end_of_inset(document.body, i)
944         if j == -1:
945             document.warning("Can't find end of reference inset at line %d!!" % (i))
946             i += 1
947             continue
948
949         plural = caps = suffix = False
950         k = find_token(document.body, "LaTeXCommand formatted", i, j)
951         if k != -1 and use_refstyle:
952             plural = get_bool_value(document.body, "plural", i, j, False)
953             caps = get_bool_value(document.body, "caps", i, j, False)
954             label = get_quoted_value(document.body, "reference", i, j)
955             if label:
956                 try:
957                     (prefix, suffix) = label.split(":", 1)
958                 except:
959                     document.warning(
960                         "No `:' separator in formatted reference at line %d!" % (i)
961                     )
962             else:
963                 document.warning("Can't find label for reference at line %d!" % (i))
964
965         # this effectively tests also for use_refstyle and a formatted reference
966         # we do this complicated test because we would otherwise do this erasure
967         # over and over and over
968         if not ((plural or caps) and suffix):
969             del_token(document.body, "plural", i, j)
970             del_token(document.body, "caps", i, j - 1)  # since we deleted a line
971             i = j - 1
972             continue
973
974         if caps:
975             prefix = prefix[0].title() + prefix[1:]
976         cmd = "\\" + prefix + "ref"
977         if plural:
978             cmd += "[s]"
979         cmd += "{" + suffix + "}"
980         document.body[i : j + 1] = put_cmd_in_ert([cmd])
981         i += 1
982
983
984 def revert_noprefix(document):
985     "Revert labelonly tags with 'noprefix' set"
986     i = 0
987     while True:
988         i = find_token(document.body, "\\begin_inset CommandInset ref", i)
989         if i == -1:
990             return
991         j = find_end_of_inset(document.body, i)
992         if j == -1:
993             document.warning("Can't find end of reference inset at line %d!!" % (i))
994             i += 1
995             continue
996         k = find_token(document.body, "LatexCommand labelonly", i, j)
997         noprefix = False
998         if k != -1:
999             noprefix = get_bool_value(document.body, "noprefix", i, j)
1000         if not noprefix:
1001             # either it was not a labelonly command, or else noprefix was not set.
1002             # in that case, we just delete the option.
1003             del_token(document.body, "noprefix", i, j)
1004             i = j
1005             continue
1006         label = get_quoted_value(document.body, "reference", i, j)
1007         if not label:
1008             document.warning("Can't find label for reference at line %d!" % (i))
1009             i = j + 1
1010             continue
1011         try:
1012             (prefix, suffix) = label.split(":", 1)
1013         except:
1014             document.warning("No `:' separator in formatted reference at line %d!" % (i))
1015             # we'll leave this as an ordinary labelonly reference
1016             del_token(document.body, "noprefix", i, j)
1017             i = j
1018             continue
1019         document.body[i : j + 1] = put_cmd_in_ert([suffix])
1020         i += 1
1021
1022
1023 def revert_biblatex(document):
1024     "Revert biblatex support"
1025
1026     #
1027     # Header
1028     #
1029
1030     # 1. Get cite engine
1031     engine = "basic"
1032     i = find_token(document.header, "\\cite_engine", 0)
1033     if i == -1:
1034         document.warning("Malformed document! Missing \\cite_engine")
1035     else:
1036         engine = get_value(document.header, "\\cite_engine", i)
1037
1038     # 2. Store biblatex state and revert to natbib
1039     biblatex = False
1040     if engine in ["biblatex", "biblatex-natbib"]:
1041         biblatex = True
1042         document.header[i] = "\\cite_engine natbib"
1043
1044     # 3. Store and remove new document headers
1045     bibstyle = ""
1046     i = find_token(document.header, "\\biblatex_bibstyle", 0)
1047     if i != -1:
1048         bibstyle = get_value(document.header, "\\biblatex_bibstyle", i)
1049         del document.header[i]
1050
1051     citestyle = ""
1052     i = find_token(document.header, "\\biblatex_citestyle", 0)
1053     if i != -1:
1054         citestyle = get_value(document.header, "\\biblatex_citestyle", i)
1055         del document.header[i]
1056
1057     biblio_options = ""
1058     i = find_token(document.header, "\\biblio_options", 0)
1059     if i != -1:
1060         biblio_options = get_value(document.header, "\\biblio_options", i)
1061         del document.header[i]
1062
1063     if biblatex:
1064         bbxopts = "[natbib=true"
1065         if bibstyle != "":
1066             bbxopts += ",bibstyle=" + bibstyle
1067         if citestyle != "":
1068             bbxopts += ",citestyle=" + citestyle
1069         if biblio_options != "":
1070             bbxopts += "," + biblio_options
1071         bbxopts += "]"
1072         add_to_preamble(document, "\\usepackage" + bbxopts + "{biblatex}")
1073
1074     #
1075     # Body
1076     #
1077
1078     # 1. Bibtex insets
1079     i = 0
1080     bibresources = []
1081     while True:
1082         i = find_token(document.body, "\\begin_inset CommandInset bibtex", i)
1083         if i == -1:
1084             break
1085         j = find_end_of_inset(document.body, i)
1086         if j == -1:
1087             document.warning("Can't find end of bibtex inset at line %d!!" % (i))
1088             i += 1
1089             continue
1090         bibs = get_quoted_value(document.body, "bibfiles", i, j)
1091         opts = get_quoted_value(document.body, "biblatexopts", i, j)
1092         # store resources
1093         if bibs:
1094             bibresources += bibs.split(",")
1095         else:
1096             document.warning("Can't find bibfiles for bibtex inset at line %d!" % (i))
1097         # remove biblatexopts line
1098         k = find_token(document.body, "biblatexopts", i, j)
1099         if k != -1:
1100             del document.body[k]
1101         # Re-find inset end line
1102         j = find_end_of_inset(document.body, i)
1103         # Insert ERT \\printbibliography and wrap bibtex inset to a Note
1104         if biblatex:
1105             pcmd = "printbibliography"
1106             if opts:
1107                 pcmd += "[" + opts + "]"
1108             repl = [
1109                 "\\begin_inset ERT",
1110                 "status open",
1111                 "",
1112                 "\\begin_layout Plain Layout",
1113                 "",
1114                 "",
1115                 "\\backslash",
1116                 pcmd,
1117                 "\\end_layout",
1118                 "",
1119                 "\\end_inset",
1120                 "",
1121                 "",
1122                 "\\end_layout",
1123                 "",
1124                 "\\begin_layout Standard",
1125                 "\\begin_inset Note Note",
1126                 "status open",
1127                 "",
1128                 "\\begin_layout Plain Layout",
1129             ]
1130             repl += document.body[i : j + 1]
1131             repl += ["", "\\end_layout", "", "\\end_inset", "", ""]
1132             document.body[i : j + 1] = repl
1133             j += 27
1134
1135         i = j + 1
1136
1137     if biblatex:
1138         for b in bibresources:
1139             add_to_preamble(document, "\\addbibresource{" + b + ".bib}")
1140
1141     # 2. Citation insets
1142
1143     # Specific citation insets used in biblatex that need to be reverted to ERT
1144     new_citations = {
1145         "Cite": "Cite",
1146         "citebyear": "citeyear",
1147         "citeyear": "cite*",
1148         "Footcite": "Smartcite",
1149         "footcite": "smartcite",
1150         "Autocite": "Autocite",
1151         "autocite": "autocite",
1152         "citetitle": "citetitle",
1153         "citetitle*": "citetitle*",
1154         "fullcite": "fullcite",
1155         "footfullcite": "footfullcite",
1156         "supercite": "supercite",
1157         "citeauthor": "citeauthor",
1158         "citeauthor*": "citeauthor*",
1159         "Citeauthor": "Citeauthor",
1160         "Citeauthor*": "Citeauthor*",
1161     }
1162
1163     # All commands accepted by LyX < 2.3. Everything else throws an error.
1164     old_citations = [
1165         "cite",
1166         "nocite",
1167         "citet",
1168         "citep",
1169         "citealt",
1170         "citealp",
1171         "citeauthor",
1172         "citeyear",
1173         "citeyearpar",
1174         "citet*",
1175         "citep*",
1176         "citealt*",
1177         "citealp*",
1178         "citeauthor*",
1179         "Citet",
1180         "Citep",
1181         "Citealt",
1182         "Citealp",
1183         "Citeauthor",
1184         "Citet*",
1185         "Citep*",
1186         "Citealt*",
1187         "Citealp*",
1188         "Citeauthor*",
1189         "fullcite",
1190         "footcite",
1191         "footcitet",
1192         "footcitep",
1193         "footcitealt",
1194         "footcitealp",
1195         "footciteauthor",
1196         "footciteyear",
1197         "footciteyearpar",
1198         "citefield",
1199         "citetitle",
1200         "cite*",
1201     ]
1202
1203     i = 0
1204     while True:
1205         i = find_token(document.body, "\\begin_inset CommandInset citation", i)
1206         if i == -1:
1207             break
1208         j = find_end_of_inset(document.body, i)
1209         if j == -1:
1210             document.warning("Can't find end of citation inset at line %d!!" % (i))
1211             i += 1
1212             continue
1213         k = find_token(document.body, "LatexCommand", i, j)
1214         if k == -1:
1215             document.warning("Can't find LatexCommand for citation inset at line %d!" % (i))
1216             i = j + 1
1217             continue
1218         cmd = get_value(document.body, "LatexCommand", k)
1219         if biblatex and cmd in list(new_citations.keys()):
1220             pre = get_quoted_value(document.body, "before", i, j)
1221             post = get_quoted_value(document.body, "after", i, j)
1222             key = get_quoted_value(document.body, "key", i, j)
1223             if not key:
1224                 document.warning("Citation inset at line %d does not have a key!" % (i))
1225                 key = "???"
1226             # Replace known new commands with ERT
1227             res = "\\" + new_citations[cmd]
1228             if pre:
1229                 res += "[" + pre + "]"
1230             if post:
1231                 res += "[" + post + "]"
1232             elif pre:
1233                 res += "[]"
1234             res += "{" + key + "}"
1235             document.body[i : j + 1] = put_cmd_in_ert([res])
1236         elif cmd not in old_citations:
1237             # Reset unknown commands to cite. This is what LyX does as well
1238             # (but LyX 2.2 would break on unknown commands)
1239             document.body[k] = "LatexCommand cite"
1240             document.warning("Reset unknown cite command '%s' with cite" % cmd)
1241         i = j + 1
1242
1243     # Emulate the old biblatex-workaround (pretend natbib in order to use the styles)
1244     if biblatex:
1245         biblatex_emulation = [
1246             "### Inserted by lyx2lyx (biblatex emulation) ###",
1247             "Provides natbib 1",
1248             "### End of insertion by lyx2lyx (biblatex emulation) ###",
1249         ]
1250         document.append_local_layout(biblatex_emulation)
1251
1252
1253 def revert_citekeyonly(document):
1254     "Revert keyonly cite command to ERT"
1255
1256     i = 0
1257     while True:
1258         i = find_token(document.body, "\\begin_inset CommandInset citation", i)
1259         if i == -1:
1260             break
1261         j = find_end_of_inset(document.body, i)
1262         if j == -1:
1263             document.warning("Can't find end of citation inset at line %d!!" % (i))
1264             i += 1
1265             continue
1266         k = find_token(document.body, "LatexCommand", i, j)
1267         if k == -1:
1268             document.warning("Can't find LatexCommand for citation inset at line %d!" % (i))
1269             i = j + 1
1270             continue
1271         cmd = get_value(document.body, "LatexCommand", k)
1272         if cmd != "keyonly":
1273             i = j + 1
1274             continue
1275
1276         key = get_quoted_value(document.body, "key", i, j)
1277         if not key:
1278             document.warning("Citation inset at line %d does not have a key!" % (i))
1279         # Replace known new commands with ERT
1280         document.body[i : j + 1] = put_cmd_in_ert([key])
1281         i = j + 1
1282
1283
1284 def revert_bibpackopts(document):
1285     "Revert support for natbib/jurabib package options"
1286
1287     engine = "basic"
1288     i = find_token(document.header, "\\cite_engine", 0)
1289     if i == -1:
1290         document.warning("Malformed document! Missing \\cite_engine")
1291     else:
1292         engine = get_value(document.header, "\\cite_engine", i)
1293
1294     biblatex = False
1295     if engine not in ["natbib", "jurabib"]:
1296         return
1297
1298     i = find_token(document.header, "\\biblio_options", 0)
1299     if i == -1:
1300         # Nothing to do if we have no options
1301         return
1302
1303     biblio_options = get_value(document.header, "\\biblio_options", i)
1304     del document.header[i]
1305
1306     if not biblio_options:
1307         # Nothing to do for empty options
1308         return
1309
1310     bibliography_package_options = [
1311         "### Inserted by lyx2lyx (bibliography package options) ###",
1312         "PackageOptions " + engine + " " + biblio_options,
1313         "### End of insertion by lyx2lyx (bibliography package options) ###",
1314     ]
1315     document.append_local_layout(bibliography_package_options)
1316
1317
1318 def revert_qualicites(document):
1319     "Revert qualified citation list commands to ERT"
1320
1321     # Citation insets that support qualified lists, with their LaTeX code
1322     ql_citations = {
1323         "cite": "cites",
1324         "Cite": "Cites",
1325         "citet": "textcites",
1326         "Citet": "Textcites",
1327         "citep": "parencites",
1328         "Citep": "Parencites",
1329         "Footcite": "Smartcites",
1330         "footcite": "smartcites",
1331         "Autocite": "Autocites",
1332         "autocite": "autocites",
1333     }
1334
1335     # Get cite engine
1336     engine = "basic"
1337     i = find_token(document.header, "\\cite_engine", 0)
1338     if i == -1:
1339         document.warning("Malformed document! Missing \\cite_engine")
1340     else:
1341         engine = get_value(document.header, "\\cite_engine", i)
1342
1343     biblatex = engine in ["biblatex", "biblatex-natbib"]
1344
1345     i = 0
1346     while True:
1347         i = find_token(document.body, "\\begin_inset CommandInset citation", i)
1348         if i == -1:
1349             break
1350         j = find_end_of_inset(document.body, i)
1351         if j == -1:
1352             document.warning("Can't find end of citation inset at line %d!!" % (i))
1353             i += 1
1354             continue
1355         pres = find_token(document.body, "pretextlist", i, j)
1356         posts = find_token(document.body, "posttextlist", i, j)
1357         if pres == -1 and posts == -1:
1358             # nothing to do.
1359             i = j + 1
1360             continue
1361         pretexts = get_quoted_value(document.body, "pretextlist", pres)
1362         posttexts = get_quoted_value(document.body, "posttextlist", posts)
1363         k = find_token(document.body, "LatexCommand", i, j)
1364         if k == -1:
1365             document.warning("Can't find LatexCommand for citation inset at line %d!" % (i))
1366             i = j + 1
1367             continue
1368         cmd = get_value(document.body, "LatexCommand", k)
1369         if biblatex and cmd in list(ql_citations.keys()):
1370             pre = get_quoted_value(document.body, "before", i, j)
1371             post = get_quoted_value(document.body, "after", i, j)
1372             key = get_quoted_value(document.body, "key", i, j)
1373             if not key:
1374                 document.warning("Citation inset at line %d does not have a key!" % (i))
1375                 key = "???"
1376             keys = key.split(",")
1377             prelist = pretexts.split("\t")
1378             premap = dict()
1379             for pp in prelist:
1380                 ppp = pp.split(" ", 1)
1381                 premap[ppp[0]] = ppp[1]
1382             postlist = posttexts.split("\t")
1383             postmap = dict()
1384             for pp in postlist:
1385                 ppp = pp.split(" ", 1)
1386                 postmap[ppp[0]] = ppp[1]
1387             # Replace known new commands with ERT
1388             if "(" in pre or ")" in pre:
1389                 pre = "{" + pre + "}"
1390             if "(" in post or ")" in post:
1391                 post = "{" + post + "}"
1392             res = "\\" + ql_citations[cmd]
1393             if pre:
1394                 res += "(" + pre + ")"
1395             if post:
1396                 res += "(" + post + ")"
1397             elif pre:
1398                 res += "()"
1399             for kk in keys:
1400                 if premap.get(kk, "") != "":
1401                     res += "[" + premap[kk] + "]"
1402                 if postmap.get(kk, "") != "":
1403                     res += "[" + postmap[kk] + "]"
1404                 elif premap.get(kk, "") != "":
1405                     res += "[]"
1406                 res += "{" + kk + "}"
1407             document.body[i : j + 1] = put_cmd_in_ert([res])
1408         else:
1409             # just remove the params
1410             del document.body[posttexts]
1411             del document.body[pretexts]
1412             i += 1
1413
1414
1415 command_insets = ["bibitem", "citation", "href", "index_print", "nomenclature"]
1416
1417
1418 def convert_literalparam(document):
1419     "Add param literal"
1420
1421     pos = len("\\begin_inset CommandInset ")
1422     i = 0
1423     while True:
1424         i = find_token(document.body, "\\begin_inset CommandInset", i)
1425         if i == -1:
1426             break
1427         inset = document.body[i][pos:].strip()
1428         if not inset in command_insets:
1429             i += 1
1430             continue
1431         j = find_end_of_inset(document.body, i)
1432         if j == -1:
1433             document.warning(
1434                 "Malformed LyX document: Can't find end of %s inset at line %d" % (inset, i)
1435             )
1436             i += 1
1437             continue
1438         while i < j and document.body[i].strip() != "":
1439             i += 1
1440         # href is already fully latexified. Here we can switch off literal.
1441         if inset == "href":
1442             document.body.insert(i, 'literal "false"')
1443         else:
1444             document.body.insert(i, 'literal "true"')
1445         i = j + 1
1446
1447
1448 def revert_literalparam(document):
1449     "Remove param literal"
1450
1451     for inset in command_insets:
1452         i = 0
1453         while True:
1454             i = find_token(document.body, "\\begin_inset CommandInset %s" % inset, i + 1)
1455             if i == -1:
1456                 break
1457             j = find_end_of_inset(document.body, i)
1458             if j == -1:
1459                 document.warning(
1460                     "Malformed LyX document: Can't find end of %s inset at line %d" % (inset, i)
1461                 )
1462                 continue
1463             del_token(document.body, "literal", i, j)
1464
1465
1466 def revert_multibib(document):
1467     "Revert multibib support"
1468
1469     # 1. Get cite engine
1470     engine = "basic"
1471     i = find_token(document.header, "\\cite_engine", 0)
1472     if i == -1:
1473         document.warning("Malformed document! Missing \\cite_engine")
1474     else:
1475         engine = get_value(document.header, "\\cite_engine", i)
1476
1477     # 2. Do we use biblatex?
1478     biblatex = False
1479     if engine in ["biblatex", "biblatex-natbib"]:
1480         biblatex = True
1481
1482     # 3. Store and remove multibib document header
1483     multibib = ""
1484     i = find_token(document.header, "\\multibib", 0)
1485     if i != -1:
1486         multibib = get_value(document.header, "\\multibib", i)
1487         del document.header[i]
1488
1489     if not multibib:
1490         return
1491
1492     # 4. The easy part: Biblatex
1493     if biblatex:
1494         i = find_token(document.header, "\\biblio_options", 0)
1495         if i == -1:
1496             k = find_token(document.header, "\\use_bibtopic", 0)
1497             if k == -1:
1498                 # this should not happen
1499                 document.warning("Malformed LyX document! No \\use_bibtopic header found!")
1500                 return
1501             document.header[k - 1 : k - 1] = ["\\biblio_options " + "refsection=" + multibib]
1502         else:
1503             biblio_options = get_value(document.header, "\\biblio_options", i)
1504             if biblio_options:
1505                 biblio_options += ","
1506             biblio_options += "refsection=" + multibib
1507             document.header[i] = "\\biblio_options " + biblio_options
1508
1509         # Bibtex insets
1510         i = 0
1511         while True:
1512             i = find_token(document.body, "\\begin_inset CommandInset bibtex", i)
1513             if i == -1:
1514                 break
1515             j = find_end_of_inset(document.body, i)
1516             if j == -1:
1517                 document.warning("Can't find end of bibtex inset at line %d!!" % (i))
1518                 i += 1
1519                 continue
1520             btprint = get_quoted_value(document.body, "btprint", i, j)
1521             if btprint != "bibbysection":
1522                 i += 1
1523                 continue
1524             opts = get_quoted_value(document.body, "biblatexopts", i, j)
1525             # change btprint line
1526             k = find_token(document.body, "btprint", i, j)
1527             if k != -1:
1528                 document.body[k] = 'btprint "btPrintCited"'
1529             # Insert ERT \\bibbysection and wrap bibtex inset to a Note
1530             pcmd = "bibbysection"
1531             if opts:
1532                 pcmd += "[" + opts + "]"
1533             repl = [
1534                 "\\begin_inset ERT",
1535                 "status open",
1536                 "",
1537                 "\\begin_layout Plain Layout",
1538                 "",
1539                 "",
1540                 "\\backslash",
1541                 pcmd,
1542                 "\\end_layout",
1543                 "",
1544                 "\\end_inset",
1545                 "",
1546                 "",
1547                 "\\end_layout",
1548                 "",
1549                 "\\begin_layout Standard",
1550                 "\\begin_inset Note Note",
1551                 "status open",
1552                 "",
1553                 "\\begin_layout Plain Layout",
1554             ]
1555             repl += document.body[i : j + 1]
1556             repl += ["", "\\end_layout", "", "\\end_inset", "", ""]
1557             document.body[i : j + 1] = repl
1558             j += 27
1559
1560             i = j + 1
1561         return
1562
1563     # 5. More tricky: Bibtex/Bibtopic
1564     k = find_token(document.header, "\\use_bibtopic", 0)
1565     if k == -1:
1566         # this should not happen
1567         document.warning("Malformed LyX document! No \\use_bibtopic header found!")
1568         return
1569     document.header[k] = "\\use_bibtopic true"
1570
1571     # Possible units. This assumes that the LyX name follows the std,
1572     # which might not always be the case. But it's as good as we can get.
1573     units = {
1574         "part": "Part",
1575         "chapter": "Chapter",
1576         "section": "Section",
1577         "subsection": "Subsection",
1578     }
1579
1580     if multibib not in units.keys():
1581         document.warning("Unknown multibib value `%s'!" % multibib)
1582         return
1583     unit = units[multibib]
1584     btunit = False
1585     i = 0
1586     while True:
1587         i = find_token(document.body, "\\begin_layout " + unit, i)
1588         if i == -1:
1589             break
1590         if btunit:
1591             document.body[i - 1 : i - 1] = [
1592                 "\\begin_layout Standard",
1593                 "\\begin_inset ERT",
1594                 "status open",
1595                 "",
1596                 "\\begin_layout Plain Layout",
1597                 "",
1598                 "",
1599                 "\\backslash",
1600                 "end{btUnit}",
1601                 "\\end_layout",
1602                 "\\begin_layout Plain Layout",
1603                 "",
1604                 "\\backslash",
1605                 "begin{btUnit}" "\\end_layout",
1606                 "",
1607                 "\\end_inset",
1608                 "",
1609                 "",
1610                 "\\end_layout",
1611                 "",
1612             ]
1613             i += 21
1614         else:
1615             document.body[i - 1 : i - 1] = [
1616                 "\\begin_layout Standard",
1617                 "\\begin_inset ERT",
1618                 "status open",
1619                 "",
1620                 "\\begin_layout Plain Layout",
1621                 "",
1622                 "",
1623                 "\\backslash",
1624                 "begin{btUnit}" "\\end_layout",
1625                 "",
1626                 "\\end_inset",
1627                 "",
1628                 "",
1629                 "\\end_layout",
1630                 "",
1631             ]
1632             i += 16
1633         btunit = True
1634         i += 1
1635
1636     if btunit:
1637         i = find_token(document.body, "\\end_body", i)
1638         document.body[i - 1 : i - 1] = [
1639             "\\begin_layout Standard",
1640             "\\begin_inset ERT",
1641             "status open",
1642             "",
1643             "\\begin_layout Plain Layout",
1644             "",
1645             "",
1646             "\\backslash",
1647             "end{btUnit}" "\\end_layout",
1648             "",
1649             "\\end_inset",
1650             "",
1651             "",
1652             "\\end_layout",
1653             "",
1654         ]
1655
1656
1657 def revert_chapterbib(document):
1658     "Revert chapterbib support"
1659
1660     # 1. Get cite engine
1661     engine = "basic"
1662     i = find_token(document.header, "\\cite_engine", 0)
1663     if i == -1:
1664         document.warning("Malformed document! Missing \\cite_engine")
1665     else:
1666         engine = get_value(document.header, "\\cite_engine", i)
1667
1668     # 2. Do we use biblatex?
1669     biblatex = False
1670     if engine in ["biblatex", "biblatex-natbib"]:
1671         biblatex = True
1672
1673     # 3. Store multibib document header value
1674     multibib = ""
1675     i = find_token(document.header, "\\multibib", 0)
1676     if i != -1:
1677         multibib = get_value(document.header, "\\multibib", i)
1678
1679     if not multibib or multibib != "child":
1680         # nothing to do
1681         return
1682
1683     # 4. remove multibib header
1684     del document.header[i]
1685
1686     # 5. Biblatex
1687     if biblatex:
1688         # find include insets
1689         i = 0
1690         while True:
1691             i = find_token(document.body, "\\begin_inset CommandInset include", i)
1692             if i == -1:
1693                 break
1694             j = find_end_of_inset(document.body, i)
1695             if j == -1:
1696                 document.warning("Can't find end of bibtex inset at line %d!!" % (i))
1697                 i += 1
1698                 continue
1699             parent = get_containing_layout(document.body, i)
1700             parbeg = parent[1]
1701
1702             # Insert ERT \\newrefsection before inset
1703             beg = [
1704                 "\\begin_layout Standard",
1705                 "\\begin_inset ERT",
1706                 "status open",
1707                 "",
1708                 "\\begin_layout Plain Layout",
1709                 "",
1710                 "",
1711                 "\\backslash",
1712                 "newrefsection" "\\end_layout",
1713                 "",
1714                 "\\end_inset",
1715                 "",
1716                 "",
1717                 "\\end_layout",
1718                 "",
1719             ]
1720             document.body[parbeg - 1 : parbeg - 1] = beg
1721             j += len(beg)
1722             i = j + 1
1723         return
1724
1725     # 6. Bibtex/Bibtopic
1726     i = find_token(document.header, "\\use_bibtopic", 0)
1727     if i == -1:
1728         # this should not happen
1729         document.warning("Malformed LyX document! No \\use_bibtopic header found!")
1730         return
1731     if get_value(document.header, "\\use_bibtopic", i) == "true":
1732         # find include insets
1733         i = 0
1734         while True:
1735             i = find_token(document.body, "\\begin_inset CommandInset include", i)
1736             if i == -1:
1737                 break
1738             j = find_end_of_inset(document.body, i)
1739             if j == -1:
1740                 document.warning("Can't find end of bibtex inset at line %d!!" % (i))
1741                 i += 1
1742                 continue
1743             parent = get_containing_layout(document.body, i)
1744             parbeg = parent[1]
1745             parend = parent[2]
1746
1747             # Insert wrap inset into \\begin{btUnit}...\\end{btUnit}
1748             beg = [
1749                 "\\begin_layout Standard",
1750                 "\\begin_inset ERT",
1751                 "status open",
1752                 "",
1753                 "\\begin_layout Plain Layout",
1754                 "",
1755                 "",
1756                 "\\backslash",
1757                 "begin{btUnit}" "\\end_layout",
1758                 "",
1759                 "\\end_inset",
1760                 "",
1761                 "",
1762                 "\\end_layout",
1763                 "",
1764             ]
1765             end = [
1766                 "\\begin_layout Standard",
1767                 "\\begin_inset ERT",
1768                 "status open",
1769                 "",
1770                 "\\begin_layout Plain Layout",
1771                 "",
1772                 "",
1773                 "\\backslash",
1774                 "end{btUnit}" "\\end_layout",
1775                 "",
1776                 "\\end_inset",
1777                 "",
1778                 "",
1779                 "\\end_layout",
1780                 "",
1781             ]
1782             document.body[parend + 1 : parend + 1] = end
1783             document.body[parbeg - 1 : parbeg - 1] = beg
1784             j += len(beg) + len(end)
1785             i = j + 1
1786         return
1787
1788     # 7. Chapterbib proper
1789     add_to_preamble(document, ["\\usepackage{chapterbib}"])
1790
1791
1792 def convert_dashligatures(document):
1793     """Set 'use_dash_ligatures' according to content."""
1794     # Look for and remove dashligatures workaround from 2.3->2.2 reversion,
1795     # set use_dash_ligatures to True if found, to None else.
1796     use_dash_ligatures = (
1797         del_complete_lines(
1798             document.preamble,
1799             [
1800                 "% Added by lyx2lyx",
1801                 r"\renewcommand{\textendash}{--}",
1802                 r"\renewcommand{\textemdash}{---}",
1803             ],
1804         )
1805         or None
1806     )
1807
1808     if use_dash_ligatures is None:
1809         # Look for dashes (Documents by LyX 2.1 or older have "\twohyphens\n"
1810         # or "\threehyphens\n" as interim representation for -- an ---.)
1811         lines = document.body
1812         has_literal_dashes = has_ligature_dashes = False
1813         dash_pattern = re.compile(".*[\u2013\u2014]|\\twohyphens|\\threehyphens")
1814         i = j = 0
1815         while True:
1816             # skip lines without dashes:
1817             i = find_re(lines, dash_pattern, i + 1)
1818             if i == -1:
1819                 break
1820             line = lines[i]
1821             # skip label width string (see bug 10243):
1822             if line.startswith("\\labelwidthstring"):
1823                 continue
1824             # do not touch hyphens in some insets (cf. lyx_2_2.convert_dashes):
1825             try:
1826                 inset_type, start, end = get_containing_inset(lines, i)
1827             except TypeError:  # no containing inset
1828                 inset_type, start, end = "no inset", -1, -1
1829             if (
1830                 inset_type.split()[0]
1831                 in [
1832                     "CommandInset",
1833                     "ERT",
1834                     "External",
1835                     "Formula",
1836                     "FormulaMacro",
1837                     "Graphics",
1838                     "IPA",
1839                     "listings",
1840                 ]
1841                 or inset_type == "Flex Code"
1842             ):
1843                 i = end
1844                 continue
1845             try:
1846                 layoutname, start, end, j = get_containing_layout(lines, i)
1847             except TypeError:  # no (or malformed) containing layout
1848                 document.warning("Malformed LyX document: " "Can't find layout at line %d" % i)
1849                 continue
1850             if not layoutname:
1851                 document.warning(
1852                     "Malformed LyX document: " "Missing layout name on line %d" % start
1853                 )
1854             if layoutname == "LyX-Code":
1855                 i = end
1856                 continue
1857
1858             # literal dash followed by a non-white-character or no-break space:
1859             if re.search("[\u2013\u2014]([\\S\u00a0\u202f\u2060]|$)", line, flags=re.UNICODE):
1860                 has_literal_dashes = True
1861             # ligature dash followed by non-white-char or no-break space on next line:
1862             if re.search(r"(\\twohyphens|\\threehyphens)", line) and re.match(
1863                 "[\\S\u00a0\u202f\u2060]", lines[i + 1], flags=re.UNICODE
1864             ):
1865                 has_ligature_dashes = True
1866             if has_literal_dashes and has_ligature_dashes:
1867                 # TODO: insert a warning note in the document?
1868                 document.warning(
1869                     "This document contained both literal and "
1870                     '"ligature" dashes.\n Line breaks may have changed. '
1871                     "See UserGuide chapter 3.9.1 for details."
1872                 )
1873                 break
1874
1875         if has_literal_dashes and not has_ligature_dashes:
1876             use_dash_ligatures = False
1877         elif has_ligature_dashes and not has_literal_dashes:
1878             use_dash_ligatures = True
1879
1880     # insert the setting if there is a preferred value
1881     if use_dash_ligatures is True:
1882         document.header.insert(-1, "\\use_dash_ligatures true")
1883     elif use_dash_ligatures is False:
1884         document.header.insert(-1, "\\use_dash_ligatures false")
1885
1886
1887 def revert_dashligatures(document):
1888     """Remove font ligature settings for en- and em-dashes.
1889     Revert conversion of \twodashes or \threedashes to literal dashes.
1890     """
1891     use_dash_ligatures = del_value(document.header, "\\use_dash_ligatures")
1892     if use_dash_ligatures != "true" or document.backend != "latex":
1893         return
1894     i = 0
1895     dash_pattern = re.compile(".*[\u2013\u2014]")
1896     while True:
1897         # skip lines without dashes:
1898         i = find_re(document.body, dash_pattern, i + 1)
1899         if i == -1:
1900             break
1901         line = document.body[i]
1902         # skip label width string (see bug 10243):
1903         if line.startswith("\\labelwidthstring"):
1904             continue
1905         # do not touch hyphens in some insets (cf. lyx_2_2.convert_dashes):
1906         try:
1907             inset_type, start, end = get_containing_inset(document.body, i)
1908         except TypeError:  # no containing inset
1909             inset_type, start, end = "no inset", -1, -1
1910         if (
1911             inset_type.split()[0]
1912             in [
1913                 "CommandInset",
1914                 "ERT",
1915                 "External",
1916                 "Formula",
1917                 "FormulaMacro",
1918                 "Graphics",
1919                 "IPA",
1920                 "listings",
1921             ]
1922             or inset_type == "Flex Code"
1923         ):
1924             i = end
1925             continue
1926         try:
1927             layoutname, start, end, j = get_containing_layout(document.body, i)
1928         except TypeError:  # no (or malformed) containing layout
1929             document.warning("Malformed LyX document: " "Can't find layout at body line %d" % i)
1930             continue
1931         if layoutname == "LyX-Code":
1932             i = end
1933             continue
1934         # TODO: skip replacement in typewriter fonts
1935         line = line.replace("\u2013", "\\twohyphens\n")
1936         line = line.replace("\u2014", "\\threehyphens\n")
1937         document.body[i : i + 1] = line.split("\n")
1938     # redefine the dash LICRs to use ligature dashes:
1939     add_to_preamble(
1940         document,
1941         [r"\renewcommand{\textendash}{--}", r"\renewcommand{\textemdash}{---}"],
1942     )
1943
1944
1945 def revert_noto(document):
1946     "Revert Noto font definitions to LaTeX"
1947
1948     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
1949         preamble = ""
1950         i = find_token(document.header, '\\font_roman "NotoSerif-TLF"', 0)
1951         if i != -1:
1952             add_to_preamble(document, ["\\renewcommand{\\rmdefault}{NotoSerif-TLF}"])
1953             document.header[i] = document.header[i].replace("NotoSerif-TLF", "default")
1954         i = find_token(document.header, '\\font_sans "NotoSans-TLF"', 0)
1955         if i != -1:
1956             add_to_preamble(document, ["\\renewcommand{\\sfdefault}{NotoSans-TLF}"])
1957             document.header[i] = document.header[i].replace("NotoSans-TLF", "default")
1958         i = find_token(document.header, '\\font_typewriter "NotoMono-TLF"', 0)
1959         if i != -1:
1960             add_to_preamble(document, ["\\renewcommand{\\ttdefault}{NotoMono-TLF}"])
1961             document.header[i] = document.header[i].replace("NotoMono-TLF", "default")
1962
1963
1964 def revert_xout(document):
1965     "Reverts \\xout font attribute"
1966     changed = revert_font_attrs(document.body, "\\xout", "\\xout")
1967     if changed == True:
1968         insert_to_preamble(
1969             document,
1970             [
1971                 "%  for proper cross-out",
1972                 "\\PassOptionsToPackage{normalem}{ulem}",
1973                 "\\usepackage{ulem}",
1974             ],
1975         )
1976
1977
1978 def convert_mathindent(document):
1979     """Add the \\is_math_indent tag."""
1980     k = find_token(document.header, "\\quotes_style")  # where to insert
1981     # check if the document uses the class option "fleqn"
1982     options = get_value(document.header, "\\options")
1983     if "fleqn" in options:
1984         document.header.insert(k, "\\is_math_indent 1")
1985         # delete the fleqn option
1986         i = find_token(document.header, "\\options")
1987         options = [option for option in options.split(",") if option.strip() != "fleqn"]
1988         if options:
1989             document.header[i] = "\\options " + ",".join(options)
1990         else:
1991             del document.header[i]
1992     else:
1993         document.header.insert(k, "\\is_math_indent 0")
1994
1995
1996 def revert_mathindent(document):
1997     "Define mathindent if set in the document"
1998     # emulate and delete \math_indentation
1999     value = get_value(document.header, "\\math_indentation", default="default", delete=True)
2000     if value != "default":
2001         add_to_preamble(document, [r"\setlength{\mathindent}{%s}" % value])
2002     # delete \is_math_indent and emulate via document class option
2003     if not get_bool_value(document.header, "\\is_math_indent", delete=True):
2004         return
2005     i = find_token(document.header, "\\options")
2006     if i != -1:
2007         document.header[i] = document.header[i].replace("\\options ", "\\options fleqn,")
2008     else:
2009         l = find_token(document.header, "\\use_default_options")
2010         document.header.insert(l, "\\options fleqn")
2011
2012
2013 def revert_baselineskip(document):
2014     "Revert baselineskips to TeX code"
2015     i = 0
2016     while True:
2017         i = find_substring(document.body, "baselineskip%", i + 1)
2018         if i == -1:
2019             return
2020         if document.body[i].startswith("\\begin_inset VSpace"):
2021             # output VSpace inset as TeX code
2022             end = find_end_of_inset(document.body, i)
2023             if end == -1:
2024                 document.warning(
2025                     "Malformed LyX document: " "Can't find end of VSpace inset at line %d." % i
2026                 )
2027                 continue
2028             # read out the value
2029             baselineskip = document.body[i].split()[-1]
2030             # check if it is the starred version
2031             star = "*" if "*" in document.body[i] else ""
2032             # now output TeX code
2033             cmd = "\\vspace%s{%s}" % (star, latex_length(baselineskip)[1])
2034             document.body[i : end + 1] = put_cmd_in_ert(cmd)
2035             i += 8
2036             continue
2037         begin, end = is_in_inset(document.body, i, "\\begin_inset space \\hspace")
2038         if begin != -1:
2039             # output space inset as TeX code
2040             baselineskip = document.body[i].split()[-1]
2041             star = "*" if "*" in document.body[i - 1] else ""
2042             cmd = "\\hspace%s{%s}" % (star, latex_length(baselineskip)[1])
2043             document.body[begin : end + 1] = put_cmd_in_ert(cmd)
2044
2045
2046 def revert_rotfloat(document):
2047     "Revert placement options for rotated floats"
2048     i = 0
2049     j = 0
2050     k = 0
2051     while True:
2052         i = find_token(document.body, "sideways true", i)
2053         if i == -1:
2054             return
2055         if not document.body[i - 2].startswith("placement "):
2056             i = i + 1
2057             continue
2058         # we found a sideways float with placement options
2059         # at first store the placement
2060         beg = document.body[i - 2].rfind(" ")
2061         placement = document.body[i - 2][beg + 1 :]
2062         # check if the option'H' is used
2063         if placement.find("H") != -1:
2064             add_to_preamble(document, ["\\usepackage{float}"])
2065         # now check if it is a starred type
2066         if document.body[i - 1].find("wide true") != -1:
2067             star = "*"
2068         else:
2069             star = ""
2070         # store the float type
2071         beg = document.body[i - 3].rfind(" ")
2072         fType = document.body[i - 3][beg + 1 :]
2073         # now output TeX code
2074         endInset = find_end_of_inset(document.body, i - 3)
2075         if endInset == -1:
2076             document.warning("Malformed LyX document: Missing '\\end_inset' of Float inset.")
2077             return
2078         else:
2079             document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert(
2080                 "\\end{sideways" + fType + star + "}"
2081             )
2082             document.body[i - 3 : i + 2] = put_cmd_in_ert(
2083                 "\\begin{sideways" + fType + star + "}[" + placement + "]"
2084             )
2085             add_to_preamble(document, ["\\usepackage{rotfloat}"])
2086
2087         i = i + 1
2088
2089
2090 allowbreak_emulation = [
2091     r"\begin_inset space \hspace{}",
2092     r"\length 0dd",
2093     r"\end_inset",
2094     r"",
2095 ]
2096
2097
2098 def convert_allowbreak(document):
2099     r"Zero widths Space-inset -> \SpecialChar allowbreak."
2100     lines = document.body
2101     i = find_complete_lines(lines, allowbreak_emulation, 2)
2102     while i != -1:
2103         lines[i - 1 : i + 4] = [lines[i - 1] + r"\SpecialChar allowbreak"]
2104         i = find_complete_lines(lines, allowbreak_emulation, i + 3)
2105
2106
2107 def revert_allowbreak(document):
2108     r"\SpecialChar allowbreak -> Zero widths Space-inset."
2109     i = 1
2110     lines = document.body
2111     while i < len(lines):
2112         if lines[i].endswith(r"\SpecialChar allowbreak"):
2113             lines[i : i + 1] = [
2114                 lines[i].replace(r"\SpecialChar allowbreak", "")
2115             ] + allowbreak_emulation
2116             i += 5
2117         else:
2118             i += 1
2119
2120
2121 def convert_mathnumberpos(document):
2122     "add the \\math_number_before tag"
2123     i = find_token(document.header, "\\quotes_style")
2124     # check if the document uses the class option "leqno"
2125     if is_document_option(document, "leqno"):
2126         remove_document_option(document, "leqno")
2127         document.header.insert(i, "\\math_number_before 1")
2128     else:
2129         document.header.insert(i, "\\math_number_before 0")
2130
2131
2132 def revert_mathnumberpos(document):
2133     """Remove \\math_number_before tag,
2134     add the document class option leqno if required.
2135     """
2136     math_number_before = get_bool_value(document.header, "\\math_number_before", delete=True)
2137     if math_number_before:
2138         insert_document_option(document, "leqno")
2139
2140
2141 def convert_mathnumberingname(document):
2142     "rename the \\math_number_before tag to \\math_numbering_side"
2143     i = find_token(document.header, "\\math_number_before")
2144     math_number_before = get_bool_value(document.header, "\\math_number_before", i)
2145     if math_number_before:
2146         document.header[i] = "\\math_numbering_side left"
2147         return
2148     # check if the document uses the class option "reqno"
2149     k = find_token(document.header, "\\options")
2150     if "reqno" in document.header[k]:
2151         document.header[i] = "\\math_numbering_side right"
2152         # delete the found option
2153         document.header[k] = document.header[k].replace(",reqno", "")
2154         document.header[k] = document.header[k].replace(", reqno", "")
2155         document.header[k] = document.header[k].replace("reqno,", "")
2156         if "reqno" in document.header[k]:
2157             # then we have reqno as the only option
2158             del document.header[k]
2159     else:
2160         document.header[i] = "\\math_numbering_side default"
2161
2162
2163 def revert_mathnumberingname(document):
2164     "rename the \\math_numbering_side tag back to \\math_number_before"
2165     i = find_token(document.header, "\\math_numbering_side")
2166     math_numbering_side = get_value(document.header, "\\math_numbering_side", i)
2167     # rename tag and set boolean value:
2168     if math_numbering_side == "left":
2169         document.header[i] = "\\math_number_before 1"
2170     elif math_numbering_side == "right":
2171         # also add the option reqno:
2172         document.header[i] = "\\math_number_before 0"
2173         k = find_token(document.header, "\\options")
2174         if k != -1 and "reqno" not in document.header[k]:
2175             document.header[k] = document.header[k].replace("\\options", "\\options reqno,")
2176         else:
2177             l = find_token(document.header, "\\use_default_options", 0)
2178             document.header.insert(l, "\\options reqno")
2179     else:
2180         document.header[i] = "\\math_number_before 0"
2181
2182
2183 def convert_minted(document):
2184     "add the \\use_minted tag"
2185     i = find_token(document.header, "\\index ")
2186     document.header.insert(i, "\\use_minted 0")
2187
2188
2189 def revert_minted(document):
2190     "remove the \\use_minted tag"
2191     del_token(document.header, "\\use_minted")
2192
2193
2194 def revert_longtable_lscape(document):
2195     "revert the longtable landcape mode to ERT"
2196     i = 0
2197     regexp = re.compile(r"^<features rotate=\"90\"\s.*islongtable=\"true\"\s.*$", re.IGNORECASE)
2198     while True:
2199         i = find_re(document.body, regexp, i)
2200         if i == -1:
2201             return
2202
2203         document.body[i] = document.body[i].replace(' rotate="90"', "")
2204         lay = get_containing_layout(document.body, i)
2205         if lay == False:
2206             document.warning("Longtable has not layout!")
2207             i += 1
2208             continue
2209         begcmd = put_cmd_in_ert("\\begin{landscape}")
2210         endcmd = put_cmd_in_ert("\\end{landscape}")
2211         document.body[lay[2] : lay[2]] = endcmd + ["\\end_layout"]
2212         document.body[lay[1] : lay[1]] = ["\\begin_layout " + lay[0], ""] + begcmd
2213
2214         add_to_preamble(document, ["\\usepackage{pdflscape}"])
2215         i = lay[2]
2216
2217
2218 ##
2219 # Conversion hub
2220 #
2221
2222 supported_versions = ["2.3.0", "2.3"]
2223 convert = [
2224     [509, [convert_microtype]],
2225     [510, [convert_dateinset]],
2226     [511, [convert_ibranches]],
2227     [512, [convert_beamer_article_styles]],
2228     [513, []],
2229     [514, []],
2230     [515, []],
2231     [516, [convert_inputenc]],
2232     [517, []],
2233     [518, [convert_iopart]],
2234     [519, [convert_quotestyle]],
2235     [520, []],
2236     [521, [convert_frenchquotes]],
2237     [522, []],
2238     [523, []],
2239     [524, [convert_crimson]],
2240     [525, []],
2241     [526, []],
2242     [527, []],
2243     [528, []],
2244     [529, []],
2245     [530, []],
2246     [531, []],
2247     [532, [convert_literalparam]],
2248     [533, []],
2249     [534, []],
2250     [535, [convert_dashligatures]],
2251     [536, []],
2252     [537, []],
2253     [538, [convert_mathindent]],
2254     [539, []],
2255     [540, []],
2256     [541, [convert_allowbreak]],
2257     [542, [convert_mathnumberpos]],
2258     [543, [convert_mathnumberingname]],
2259     [544, [convert_minted]],
2260 ]
2261
2262 revert = [
2263     [543, [revert_minted, revert_longtable_lscape]],
2264     [542, [revert_mathnumberingname]],
2265     [541, [revert_mathnumberpos]],
2266     [540, [revert_allowbreak]],
2267     [539, [revert_rotfloat]],
2268     [538, [revert_baselineskip]],
2269     [537, [revert_mathindent]],
2270     [536, [revert_xout]],
2271     [535, [revert_noto]],
2272     [534, [revert_dashligatures]],
2273     [533, [revert_chapterbib]],
2274     [532, [revert_multibib]],
2275     [531, [revert_literalparam]],
2276     [530, [revert_qualicites]],
2277     [529, [revert_bibpackopts]],
2278     [528, [revert_citekeyonly]],
2279     [527, [revert_biblatex]],
2280     [526, [revert_noprefix]],
2281     [525, [revert_plural_refs]],
2282     [524, [revert_labelonly]],
2283     [523, [revert_crimson, revert_cochinealmath]],
2284     [522, [revert_cjkquotes]],
2285     [521, [revert_dynamicquotes]],
2286     [
2287         520,
2288         [
2289             revert_britishquotes,
2290             revert_swedishgquotes,
2291             revert_frenchquotes,
2292             revert_frenchinquotes,
2293             revert_russianquotes,
2294             revert_swissquotes,
2295         ],
2296     ],
2297     [519, [revert_plainquote]],
2298     [518, [revert_quotestyle]],
2299     [517, [revert_iopart]],
2300     [516, [revert_quotes]],
2301     [515, []],
2302     [514, [revert_urdu, revert_syriac]],
2303     [513, [revert_amharic, revert_asturian, revert_kannada, revert_khmer]],
2304     [512, [revert_new_babel_languages]],
2305     [511, [revert_beamer_article_styles]],
2306     [510, [revert_ibranches]],
2307     [509, []],
2308     [508, [revert_microtype]],
2309 ]
2310
2311
2312 if __name__ == "__main__":
2313     pass