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