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