]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_3.py
Allow an 'other' type for hyperlinks. Format change.
[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+1)
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                 continue
1330             del_token(document.body, 'literal', i, j)
1331
1332
1333 def revert_multibib(document):
1334     " Revert multibib support "
1335
1336     # 1. Get cite engine
1337     engine = "basic"
1338     i = find_token(document.header, "\\cite_engine", 0)
1339     if i == -1:
1340         document.warning("Malformed document! Missing \\cite_engine")
1341     else:
1342         engine = get_value(document.header, "\\cite_engine", i)
1343
1344     # 2. Do we use biblatex?
1345     biblatex = False
1346     if engine in ["biblatex", "biblatex-natbib"]:
1347         biblatex = True
1348
1349     # 3. Store and remove multibib document header
1350     multibib = ""
1351     i = find_token(document.header, "\\multibib", 0)
1352     if i != -1:
1353         multibib = get_value(document.header, "\\multibib", i)
1354         del document.header[i]
1355
1356     if not multibib:
1357         return
1358
1359     # 4. The easy part: Biblatex
1360     if biblatex:
1361         i = find_token(document.header, "\\biblio_options", 0)
1362         if i == -1:
1363             k = find_token(document.header, "\\use_bibtopic", 0)
1364             if k == -1:
1365                 # this should not happen
1366                 document.warning("Malformed LyX document! No \\use_bibtopic header found!")
1367                 return
1368             document.header[k-1 : k-1] = ["\\biblio_options " + "refsection=" + multibib]
1369         else:
1370             biblio_options = get_value(document.header, "\\biblio_options", i)
1371             if biblio_options:
1372                 biblio_options += ","
1373             biblio_options += "refsection=" + multibib
1374             document.header[i] = "\\biblio_options " + biblio_options
1375
1376         # Bibtex insets
1377         i = 0
1378         while (True):
1379             i = find_token(document.body, "\\begin_inset CommandInset bibtex", i)
1380             if i == -1:
1381                 break
1382             j = find_end_of_inset(document.body, i)
1383             if j == -1:
1384                 document.warning("Can't find end of bibtex inset at line %d!!" %(i))
1385                 i += 1
1386                 continue
1387             btprint = get_quoted_value(document.body, "btprint", i, j)
1388             if btprint != "bibbysection":
1389                 i += 1
1390                 continue
1391             opts = get_quoted_value(document.body, "biblatexopts", i, j)
1392             # change btprint line
1393             k = find_token(document.body, "btprint", i, j)
1394             if k != -1:
1395                 document.body[k] = "btprint \"btPrintCited\""
1396             # Insert ERT \\bibbysection and wrap bibtex inset to a Note
1397             pcmd = "bibbysection"
1398             if opts:
1399                 pcmd += "[" + opts + "]"
1400             repl = ["\\begin_inset ERT", "status open", "", "\\begin_layout Plain Layout",\
1401                     "", "", "\\backslash", pcmd, "\\end_layout", "", "\\end_inset", "", "",\
1402                     "\\end_layout", "", "\\begin_layout Standard", "\\begin_inset Note Note",\
1403                     "status open", "", "\\begin_layout Plain Layout" ]
1404             repl += document.body[i:j+1]
1405             repl += ["", "\\end_layout", "", "\\end_inset", "", ""]
1406             document.body[i:j+1] = repl
1407             j += 27
1408
1409             i = j + 1
1410         return
1411
1412     # 5. More tricky: Bibtex/Bibtopic
1413     k = find_token(document.header, "\\use_bibtopic", 0)
1414     if k == -1:
1415         # this should not happen
1416         document.warning("Malformed LyX document! No \\use_bibtopic header found!")
1417         return
1418     document.header[k] = "\\use_bibtopic true"
1419
1420     # Possible units. This assumes that the LyX name follows the std,
1421     # which might not always be the case. But it's as good as we can get.
1422     units = {
1423         "part" : "Part",
1424         "chapter" : "Chapter",
1425         "section" : "Section",
1426         "subsection" : "Subsection",
1427         }
1428
1429     if multibib not in units.keys():
1430         document.warning("Unknown multibib value `%s'!" % nultibib)
1431         return
1432     unit = units[multibib]
1433     btunit = False
1434     i = 0
1435     while (True):
1436         i = find_token(document.body, "\\begin_layout " + unit, i)
1437         if i == -1:
1438             break
1439         if btunit:
1440             document.body[i-1 : i-1] = ["\\begin_layout Standard",
1441                                 "\\begin_inset ERT", "status open", "",
1442                                 "\\begin_layout Plain Layout", "", "",
1443                                 "\\backslash",
1444                                 "end{btUnit}", "\\end_layout",
1445                                 "\\begin_layout Plain Layout", "",
1446                                 "\\backslash",
1447                                 "begin{btUnit}"
1448                                 "\\end_layout", "", "\\end_inset", "", "",
1449                                 "\\end_layout", ""]
1450             i += 21
1451         else:
1452             document.body[i-1 : i-1] = ["\\begin_layout Standard",
1453                                 "\\begin_inset ERT", "status open", "",
1454                                 "\\begin_layout Plain Layout", "", "",
1455                                 "\\backslash",
1456                                 "begin{btUnit}"
1457                                 "\\end_layout", "", "\\end_inset", "", "",
1458                                 "\\end_layout", ""]
1459             i += 16
1460         btunit = True
1461         i += 1
1462
1463     if btunit:
1464         i = find_token(document.body, "\\end_body", i)
1465         document.body[i-1 : i-1] = ["\\begin_layout Standard",
1466                                 "\\begin_inset ERT", "status open", "",
1467                                 "\\begin_layout Plain Layout", "", "",
1468                                 "\\backslash",
1469                                 "end{btUnit}"
1470                                 "\\end_layout", "", "\\end_inset", "", "",
1471                                 "\\end_layout", ""]
1472
1473
1474 def revert_chapterbib(document):
1475     " Revert chapterbib support "
1476
1477     # 1. Get cite engine
1478     engine = "basic"
1479     i = find_token(document.header, "\\cite_engine", 0)
1480     if i == -1:
1481         document.warning("Malformed document! Missing \\cite_engine")
1482     else:
1483         engine = get_value(document.header, "\\cite_engine", i)
1484
1485     # 2. Do we use biblatex?
1486     biblatex = False
1487     if engine in ["biblatex", "biblatex-natbib"]:
1488         biblatex = True
1489
1490     # 3. Store multibib document header value
1491     multibib = ""
1492     i = find_token(document.header, "\\multibib", 0)
1493     if i != -1:
1494         multibib = get_value(document.header, "\\multibib", i)
1495
1496     if not multibib or multibib != "child":
1497         # nothing to do
1498         return
1499
1500     # 4. remove multibib header
1501     del document.header[i]
1502
1503     # 5. Biblatex
1504     if biblatex:
1505         # find include insets
1506         i = 0
1507         while (True):
1508             i = find_token(document.body, "\\begin_inset CommandInset include", i)
1509             if i == -1:
1510                 break
1511             j = find_end_of_inset(document.body, i)
1512             if j == -1:
1513                 document.warning("Can't find end of bibtex inset at line %d!!" %(i))
1514                 i += 1
1515                 continue
1516             parent = get_containing_layout(document.body, i)
1517             parbeg = parent[1]
1518
1519             # Insert ERT \\newrefsection before inset
1520             beg = ["\\begin_layout Standard",
1521                    "\\begin_inset ERT", "status open", "",
1522                    "\\begin_layout Plain Layout", "", "",
1523                    "\\backslash",
1524                    "newrefsection"
1525                    "\\end_layout", "", "\\end_inset", "", "",
1526                    "\\end_layout", ""]
1527             document.body[parbeg-1:parbeg-1] = beg
1528             j += len(beg)
1529             i = j + 1
1530         return
1531
1532     # 6. Bibtex/Bibtopic
1533     i = find_token(document.header, "\\use_bibtopic", 0)
1534     if i == -1:
1535         # this should not happen
1536         document.warning("Malformed LyX document! No \\use_bibtopic header found!")
1537         return
1538     if get_value(document.header, "\\use_bibtopic", i) == "true":
1539         # find include insets
1540         i = 0
1541         while (True):
1542             i = find_token(document.body, "\\begin_inset CommandInset include", i)
1543             if i == -1:
1544                 break
1545             j = find_end_of_inset(document.body, i)
1546             if j == -1:
1547                 document.warning("Can't find end of bibtex inset at line %d!!" %(i))
1548                 i += 1
1549                 continue
1550             parent = get_containing_layout(document.body, i)
1551             parbeg = parent[1]
1552             parend = parent[2]
1553
1554             # Insert wrap inset into \\begin{btUnit}...\\end{btUnit}
1555             beg = ["\\begin_layout Standard",
1556                    "\\begin_inset ERT", "status open", "",
1557                    "\\begin_layout Plain Layout", "", "",
1558                    "\\backslash",
1559                    "begin{btUnit}"
1560                    "\\end_layout", "", "\\end_inset", "", "",
1561                    "\\end_layout", ""]
1562             end = ["\\begin_layout Standard",
1563                    "\\begin_inset ERT", "status open", "",
1564                    "\\begin_layout Plain Layout", "", "",
1565                    "\\backslash",
1566                    "end{btUnit}"
1567                    "\\end_layout", "", "\\end_inset", "", "",
1568                    "\\end_layout", ""]
1569             document.body[parend+1:parend+1] = end
1570             document.body[parbeg-1:parbeg-1] = beg
1571             j += len(beg) + len(end)
1572             i = j + 1
1573         return
1574
1575     # 7. Chapterbib proper
1576     add_to_preamble(document, ["\\usepackage{chapterbib}"])
1577
1578
1579 def convert_dashligatures(document):
1580     """Set 'use_dash_ligatures' according to content.
1581     """
1582     # Look for and remove dashligatures workaround from 2.3->2.2 reversion,
1583     # set use_dash_ligatures to True if found, to None else.
1584     use_dash_ligatures = del_complete_lines(document.preamble,
1585                                 ['% Added by lyx2lyx',
1586                                  r'\renewcommand{\textendash}{--}',
1587                                  r'\renewcommand{\textemdash}{---}']) or None
1588
1589     if use_dash_ligatures is None:
1590         # Look for dashes (Documents by LyX 2.1 or older have "\twohyphens\n"
1591         # or "\threehyphens\n" as interim representation for -- an ---.)
1592         lines = document.body
1593         has_literal_dashes = has_ligature_dashes = False
1594         dash_pattern = re.compile(u".*[\u2013\u2014]|\\twohyphens|\\threehyphens")
1595         i = j = 0
1596         while True:
1597             # skip lines without dashes:
1598             i = find_re(lines, dash_pattern, i+1)
1599             if i == -1:
1600                 break
1601             line = lines[i]
1602             # skip label width string (see bug 10243):
1603             if line.startswith("\\labelwidthstring"):
1604                 continue
1605             # do not touch hyphens in some insets (cf. lyx_2_2.convert_dashes):
1606             try:
1607                 inset_type, start, end = get_containing_inset(lines, i)
1608             except TypeError: # no containing inset
1609                 inset_type, start, end = "no inset", -1, -1
1610             if (inset_type.split()[0] in
1611                 ["CommandInset", "ERT", "External", "Formula",
1612                  "FormulaMacro", "Graphics", "IPA", "listings"]
1613                 or inset_type == "Flex Code"):
1614                 i = end
1615                 continue
1616             try:
1617                 layoutname, start, end, j = get_containing_layout(lines, i)
1618             except TypeError: # no (or malformed) containing layout
1619                 document.warning("Malformed LyX document: "
1620                                 "Can't find layout at line %d" % i)
1621                 continue
1622             if not layoutname:
1623                 document.warning("Malformed LyX document: "
1624                                  "Missing layout name on line %d"%start)
1625             if layoutname == "LyX-Code":
1626                 i = end
1627                 continue
1628
1629             # literal dash followed by a non-white-character or no-break space:
1630             if re.search(u"[\u2013\u2014]([\\S\u00A0\u202F\u2060]|$)",
1631                          line, flags=re.UNICODE):
1632                 has_literal_dashes = True
1633             # ligature dash followed by non-white-char or no-break space on next line:
1634             if (re.search(r"(\\twohyphens|\\threehyphens)", line) and
1635                 re.match(u"[\\S\u00A0\u202F\u2060]", lines[i+1], flags=re.UNICODE)):
1636                 has_ligature_dashes = True
1637             if has_literal_dashes and has_ligature_dashes:
1638                 # TODO: insert a warning note in the document?
1639                 document.warning('This document contained both literal and '
1640                                  '"ligature" dashes.\n Line breaks may have changed. '
1641                                  'See UserGuide chapter 3.9.1 for details.')
1642                 break
1643
1644         if has_literal_dashes and not has_ligature_dashes:
1645             use_dash_ligatures = False
1646         elif has_ligature_dashes and not has_literal_dashes:
1647             use_dash_ligatures = True
1648
1649     # insert the setting if there is a preferred value
1650     if use_dash_ligatures is True:
1651         document.header.insert(-1, "\\use_dash_ligatures true")
1652     elif use_dash_ligatures is False:
1653         document.header.insert(-1, "\\use_dash_ligatures false")
1654
1655
1656 def revert_dashligatures(document):
1657     """Remove font ligature settings for en- and em-dashes.
1658     Revert conversion of \twodashes or \threedashes to literal dashes.
1659     """
1660     use_dash_ligatures = del_value(document.header, "\\use_dash_ligatures")
1661     if use_dash_ligatures != "true" or document.backend != "latex":
1662         return
1663     i = 0
1664     dash_pattern = re.compile(u".*[\u2013\u2014]")
1665     while True:
1666         # skip lines without dashes:
1667         i = find_re(document.body, dash_pattern, i+1)
1668         if i == -1:
1669             break
1670         line = document.body[i]
1671         # skip label width string (see bug 10243):
1672         if line.startswith("\\labelwidthstring"):
1673             continue
1674         # do not touch hyphens in some insets (cf. lyx_2_2.convert_dashes):
1675         try:
1676             inset_type, start, end = get_containing_inset(document.body, i)
1677         except TypeError: # no containing inset
1678             inset_type, start, end = "no inset", -1, -1
1679         if (inset_type.split()[0] in
1680             ["CommandInset", "ERT", "External", "Formula",
1681                 "FormulaMacro", "Graphics", "IPA", "listings"]
1682             or inset_type == "Flex Code"):
1683             i = end
1684             continue
1685         try:
1686             layoutname, start, end, j = get_containing_layout(document.body, i)
1687         except TypeError: # no (or malformed) containing layout
1688             document.warning("Malformed LyX document: "
1689                             "Can't find layout at body line %d" % i)
1690             continue
1691         if layoutname == "LyX-Code":
1692             i = end
1693             continue
1694         # TODO: skip replacement in typewriter fonts
1695         line = line.replace(u'\u2013', '\\twohyphens\n')
1696         line = line.replace(u'\u2014', '\\threehyphens\n')
1697         document.body[i:i+1] = line.split('\n')
1698     # redefine the dash LICRs to use ligature dashes:
1699     add_to_preamble(document, [r'\renewcommand{\textendash}{--}',
1700                                r'\renewcommand{\textemdash}{---}'])
1701
1702
1703 def revert_noto(document):
1704     " Revert Noto font definitions to LaTeX "
1705
1706     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
1707         preamble = ""
1708         i = find_token(document.header, "\\font_roman \"NotoSerif-TLF\"", 0)
1709         if i != -1:
1710             add_to_preamble(document, ["\\renewcommand{\\rmdefault}{NotoSerif-TLF}"])
1711             document.header[i] = document.header[i].replace("NotoSerif-TLF", "default")
1712         i = find_token(document.header, "\\font_sans \"NotoSans-TLF\"", 0)
1713         if i != -1:
1714             add_to_preamble(document, ["\\renewcommand{\\sfdefault}{NotoSans-TLF}"])
1715             document.header[i] = document.header[i].replace("NotoSans-TLF", "default")
1716         i = find_token(document.header, "\\font_typewriter \"NotoMono-TLF\"", 0)
1717         if i != -1:
1718             add_to_preamble(document, ["\\renewcommand{\\ttdefault}{NotoMono-TLF}"])
1719             document.header[i] = document.header[i].replace("NotoMono-TLF", "default")
1720
1721
1722 def revert_xout(document):
1723   " Reverts \\xout font attribute "
1724   changed = revert_font_attrs(document.body, "\\xout", "\\xout")
1725   if changed == True:
1726     insert_to_preamble(document, \
1727         ['%  for proper cross-out',
1728         '\\PassOptionsToPackage{normalem}{ulem}',
1729         '\\usepackage{ulem}'])
1730
1731
1732 def convert_mathindent(document):
1733     """Add the \\is_math_indent tag.
1734     """
1735     k = find_token(document.header, "\\quotes_style") # where to insert
1736     # check if the document uses the class option "fleqn"
1737     options = get_value(document.header, "\\options")
1738     if 'fleqn' in options:
1739         document.header.insert(k, "\\is_math_indent 1")
1740         # delete the fleqn option
1741         i = find_token(document.header, "\\options")
1742         options = [option for option in options.split(",")
1743                    if option.strip() != "fleqn"]
1744         if options:
1745             document.header[i] = "\\options " + ",".join(options)
1746         else:
1747             del document.header[i]
1748     else:
1749         document.header.insert(k, "\\is_math_indent 0")
1750
1751 def revert_mathindent(document):
1752     " Define mathindent if set in the document "
1753     # emulate and delete \math_indentation
1754     value = get_value(document.header, "\\math_indentation",
1755                       default="default", delete=True)
1756     if value != "default":
1757         add_to_preamble(document, [r"\setlength{\mathindent}{%s}"%value])
1758     # delete \is_math_indent and emulate via document class option
1759     if not get_bool_value(document.header, "\\is_math_indent", delete=True):
1760         return
1761     i = find_token(document.header, "\\options")
1762     if i != -1:
1763         document.header[i] = document.header[i].replace("\\options ",
1764                                                         "\\options fleqn,")
1765     else:
1766         l = find_token(document.header, "\\use_default_options")
1767         document.header.insert(l, "\\options fleqn")
1768
1769
1770 def revert_baselineskip(document):
1771     " Revert baselineskips to TeX code "
1772     i = 0
1773     while True:
1774         i = find_substring(document.body, "baselineskip%", i+1)
1775         if i == -1:
1776             return
1777         if  document.body[i].startswith("\\begin_inset VSpace"):
1778             # output VSpace inset as TeX code
1779             end = find_end_of_inset(document.body, i)
1780             if end == -1:
1781                 document.warning("Malformed LyX document: "
1782                         "Can't find end of VSpace inset at line %d." % i)
1783                 continue
1784             # read out the value
1785             baselineskip = document.body[i].split()[-1]
1786             # check if it is the starred version
1787             star = '*' if '*' in document.body[i] else ''
1788             # now output TeX code
1789             cmd = "\\vspace%s{%s}" %(star, latex_length(baselineskip)[1])
1790             document.body[i:end+1] = put_cmd_in_ert(cmd)
1791             i += 8
1792             continue
1793         begin, end = is_in_inset(document.body, i, "\\begin_inset space \\hspace")
1794         if  begin != - 1:
1795             # output space inset as TeX code
1796             baselineskip = document.body[i].split()[-1]
1797             star = '*' if '*' in document.body[i-1] else ''
1798             cmd = "\\hspace%s{%s}" %(star, latex_length(baselineskip)[1])
1799             document.body[begin:end+1] = put_cmd_in_ert(cmd)
1800
1801
1802 def revert_rotfloat(document):
1803   " Revert placement options for rotated floats "
1804   i = 0
1805   j = 0
1806   k = 0
1807   while True:
1808     i = find_token(document.body, "sideways true", i)
1809     if i == -1:
1810       return
1811     if not document.body[i-2].startswith('placement '):
1812         i = i + 1
1813         continue
1814     # we found a sideways float with placement options
1815     # at first store the placement
1816     beg = document.body[i-2].rfind(" ");
1817     placement = document.body[i-2][beg+1:]
1818     # check if the option'H' is used
1819     if placement.find("H") != -1:
1820       add_to_preamble(document, ["\\usepackage{float}"])
1821     # now check if it is a starred type
1822     if document.body[i-1].find("wide true") != -1:
1823       star = '*'
1824     else:
1825       star = ''
1826     # store the float type
1827     beg = document.body[i-3].rfind(" ");
1828     fType = document.body[i-3][beg+1:]
1829     # now output TeX code
1830     endInset = find_end_of_inset(document.body, i-3)
1831     if endInset == -1:
1832       document.warning("Malformed LyX document: Missing '\\end_inset' of Float inset.")
1833       return
1834     else:
1835       document.body[endInset-2: endInset+1] = put_cmd_in_ert("\\end{sideways" + fType + star + '}')
1836       document.body[i-3: i+2] = put_cmd_in_ert("\\begin{sideways" + fType + star + "}[" + placement + ']')
1837       add_to_preamble(document, ["\\usepackage{rotfloat}"])
1838
1839     i = i + 1
1840
1841
1842 allowbreak_emulation =  [r"\begin_inset space \hspace{}",
1843                          r"\length 0dd",
1844                          r"\end_inset",
1845                          r""]
1846
1847 def convert_allowbreak(document):
1848     r" Zero widths Space-inset -> \SpecialChar allowbreak. "
1849     lines = document.body
1850     i = find_complete_lines(lines, allowbreak_emulation, 2)
1851     while i != -1:
1852         lines[i-1:i+4] = [lines[i-1] + r"\SpecialChar allowbreak"]
1853         i = find_complete_lines(lines, allowbreak_emulation, i+3)
1854
1855
1856 def revert_allowbreak(document):
1857     r" \SpecialChar allowbreak -> Zero widths Space-inset. "
1858     i = 1
1859     lines = document.body
1860     while i < len(lines):
1861         if lines[i].endswith(r"\SpecialChar allowbreak"):
1862             lines[i:i+1] = [lines[i].replace(r"\SpecialChar allowbreak", "")
1863                            ] + allowbreak_emulation
1864             i += 5
1865         else:
1866             i += 1
1867
1868
1869 def convert_mathnumberpos(document):
1870     " add the \\math_number_before tag "
1871     i = find_token(document.header, "\\quotes_style")
1872     # check if the document uses the class option "leqno"
1873     if is_document_option(document, "leqno"):
1874         remove_document_option(document, "leqno")
1875         document.header.insert(i, "\\math_number_before 1")
1876     else:
1877         document.header.insert(i, "\\math_number_before 0")
1878
1879
1880 def revert_mathnumberpos(document):
1881     """Remove \\math_number_before tag,
1882     add the document class option leqno if required.
1883     """
1884     math_number_before = get_bool_value(document.header, '\\math_number_before',
1885                                         delete=True)
1886     if math_number_before:
1887         insert_document_option(document, "leqno")
1888
1889
1890 def convert_mathnumberingname(document):
1891     " rename the \\math_number_before tag to \\math_numbering_side "
1892     i = find_token(document.header, "\\math_number_before")
1893     math_number_before = get_bool_value(document.header, '\\math_number_before', i)
1894     if math_number_before:
1895         document.header[i] = "\\math_numbering_side left"
1896         return
1897     # check if the document uses the class option "reqno"
1898     k = find_token(document.header, "\\options")
1899     if 'reqno' in document.header[k]:
1900         document.header[i] = "\\math_numbering_side right"
1901         # delete the found option
1902         document.header[k] = document.header[k].replace(",reqno", "")
1903         document.header[k] = document.header[k].replace(", reqno", "")
1904         document.header[k] = document.header[k].replace("reqno,", "")
1905         if 'reqno' in document.header[k]:
1906             # then we have reqno as the only option
1907             del document.header[k]
1908     else:
1909         document.header[i] = "\\math_numbering_side default"
1910
1911
1912 def revert_mathnumberingname(document):
1913     " rename the \\math_numbering_side tag back to \\math_number_before "
1914     i = find_token(document.header, "\\math_numbering_side")
1915     math_numbering_side = get_value(document.header, '\\math_numbering_side', i)
1916     # rename tag and set boolean value:
1917     if math_numbering_side == "left":
1918         document.header[i] = "\\math_number_before 1"
1919     elif math_numbering_side == "right":
1920         # also add the option reqno:
1921         document.header[i] = "\\math_number_before 0"
1922         k = find_token(document.header, "\\options")
1923         if k != -1  and 'reqno' not in document.header[k]:
1924             document.header[k] = document.header[k].replace("\\options", "\\options reqno,")
1925         else:
1926             l = find_token(document.header, "\\use_default_options", 0)
1927             document.header.insert(l, "\\options reqno")
1928     else:
1929         document.header[i] = "\\math_number_before 0"
1930
1931
1932 def convert_minted(document):
1933     " add the \\use_minted tag "
1934     i = find_token(document.header, "\\index ")
1935     document.header.insert(i, "\\use_minted 0")
1936
1937
1938 def revert_minted(document):
1939     " remove the \\use_minted tag "
1940     del_token(document.header, "\\use_minted")
1941
1942
1943 def revert_longtable_lscape(document):
1944     " revert the longtable landcape mode to ERT "
1945     i = 0
1946     regexp = re.compile(r'^<features rotate=\"90\"\s.*islongtable=\"true\"\s.*$', re.IGNORECASE)
1947     while True:
1948         i = find_re(document.body, regexp, i)
1949         if i == -1:
1950             return
1951
1952         document.body[i] = document.body[i].replace(" rotate=\"90\"", "")
1953         lay = get_containing_layout(document.body, i)
1954         if lay == False:
1955             document.warning("Longtable has not layout!")
1956             i += 1
1957             continue
1958         begcmd = put_cmd_in_ert("\\begin{landscape}")
1959         endcmd = put_cmd_in_ert("\\end{landscape}")
1960         document.body[lay[2] : lay[2]] = endcmd + ["\\end_layout"]
1961         document.body[lay[1] : lay[1]] = ["\\begin_layout " + lay[0], ""] + begcmd
1962
1963         add_to_preamble(document, ["\\usepackage{pdflscape}"])
1964         i = lay[2]
1965
1966
1967 ##
1968 # Conversion hub
1969 #
1970
1971 supported_versions = ["2.3.0", "2.3"]
1972 convert = [
1973            [509, [convert_microtype]],
1974            [510, [convert_dateinset]],
1975            [511, [convert_ibranches]],
1976            [512, [convert_beamer_article_styles]],
1977            [513, []],
1978            [514, []],
1979            [515, []],
1980            [516, [convert_inputenc]],
1981            [517, []],
1982            [518, [convert_iopart]],
1983            [519, [convert_quotestyle]],
1984            [520, []],
1985            [521, [convert_frenchquotes]],
1986            [522, []],
1987            [523, []],
1988            [524, [convert_crimson]],
1989            [525, []],
1990            [526, []],
1991            [527, []],
1992            [528, []],
1993            [529, []],
1994            [530, []],
1995            [531, []],
1996            [532, [convert_literalparam]],
1997            [533, []],
1998            [534, []],
1999            [535, [convert_dashligatures]],
2000            [536, []],
2001            [537, []],
2002            [538, [convert_mathindent]],
2003            [539, []],
2004            [540, []],
2005            [541, [convert_allowbreak]],
2006            [542, [convert_mathnumberpos]],
2007            [543, [convert_mathnumberingname]],
2008            [544, [convert_minted]]
2009           ]
2010
2011 revert =  [
2012            [543, [revert_minted, revert_longtable_lscape]],
2013            [542, [revert_mathnumberingname]],
2014            [541, [revert_mathnumberpos]],
2015            [540, [revert_allowbreak]],
2016            [539, [revert_rotfloat]],
2017            [538, [revert_baselineskip]],
2018            [537, [revert_mathindent]],
2019            [536, [revert_xout]],
2020            [535, [revert_noto]],
2021            [534, [revert_dashligatures]],
2022            [533, [revert_chapterbib]],
2023            [532, [revert_multibib]],
2024            [531, [revert_literalparam]],
2025            [530, [revert_qualicites]],
2026            [529, [revert_bibpackopts]],
2027            [528, [revert_citekeyonly]],
2028            [527, [revert_biblatex]],
2029            [526, [revert_noprefix]],
2030            [525, [revert_plural_refs]],
2031            [524, [revert_labelonly]],
2032            [523, [revert_crimson, revert_cochinealmath]],
2033            [522, [revert_cjkquotes]],
2034            [521, [revert_dynamicquotes]],
2035            [520, [revert_britishquotes, revert_swedishgquotes, revert_frenchquotes, revert_frenchinquotes, revert_russianquotes, revert_swissquotes]],
2036            [519, [revert_plainquote]],
2037            [518, [revert_quotestyle]],
2038            [517, [revert_iopart]],
2039            [516, [revert_quotes]],
2040            [515, []],
2041            [514, [revert_urdu, revert_syriac]],
2042            [513, [revert_amharic, revert_asturian, revert_kannada, revert_khmer]],
2043            [512, [revert_new_babel_languages]],
2044            [511, [revert_beamer_article_styles]],
2045            [510, [revert_ibranches]],
2046            [509, []],
2047            [508, [revert_microtype]]
2048           ]
2049
2050
2051 if __name__ == "__main__":
2052     pass