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