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