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