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