]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_3.py
Fix parentheses with Hebrew
[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     pos = len("\\begin_inset CommandInset ")
1506     i = 0
1507     while True:
1508         i = find_token(document.body, '\\begin_inset CommandInset', i)
1509         if i == -1:
1510             break
1511         inset = document.body[i][pos:].strip()
1512         if not inset in command_insets:
1513             i += 1
1514             continue
1515         j = find_end_of_inset(document.body, i)
1516         if j == -1:
1517             document.warning("Malformed LyX document: Can't find end of %s inset at line %d" % (inset, i))
1518             i += 1
1519             continue
1520         while i < j and document.body[i].strip() != '':
1521             i += 1
1522         # href is already fully latexified. Here we can switch off literal.
1523         if inset == "href":
1524             document.body.insert(i, "literal \"false\"")
1525         else:
1526             document.body.insert(i, "literal \"true\"")
1527         i = j + 1
1528
1529
1530 def revert_literalparam(document):
1531     " Remove param literal "
1532
1533     for inset in command_insets:
1534         i = 0
1535         while True:
1536             i = find_token(document.body, '\\begin_inset CommandInset %s' % inset, i)
1537             if i == -1:
1538                 break
1539             j = find_end_of_inset(document.body, i)
1540             if j == -1:
1541                 document.warning("Malformed LyX document: Can't find end of %s inset at line %d" % (inset, i))
1542                 i += 1
1543                 continue
1544             k = find_token(document.body, 'literal', i, j)
1545             if k == -1:
1546                 i += 1
1547                 continue
1548             del document.body[k]
1549
1550
1551 def revert_multibib(document):
1552     " Revert multibib support "
1553
1554     # 1. Get cite engine
1555     engine = "basic"
1556     i = find_token(document.header, "\\cite_engine", 0)
1557     if i == -1:
1558         document.warning("Malformed document! Missing \\cite_engine")
1559     else:
1560         engine = get_value(document.header, "\\cite_engine", i)
1561
1562     # 2. Do we use biblatex?
1563     biblatex = False
1564     if engine in ["biblatex", "biblatex-natbib"]:
1565         biblatex = True
1566
1567     # 3. Store and remove multibib document header
1568     multibib = ""
1569     i = find_token(document.header, "\\multibib", 0)
1570     if i != -1:
1571         multibib = get_value(document.header, "\\multibib", i)
1572         del document.header[i]
1573
1574     if not multibib:
1575         return
1576
1577     # 4. The easy part: Biblatex
1578     if biblatex:
1579         i = find_token(document.header, "\\biblio_options", 0)
1580         if i == -1:
1581             k = find_token(document.header, "\\use_bibtopic", 0)
1582             if k == -1:
1583                 # this should not happen
1584                 document.warning("Malformed LyX document! No \\use_bibtopic header found!")
1585                 return
1586             document.header[k-1 : k-1] = ["\\biblio_options " + "refsection=" + multibib]
1587         else:
1588             biblio_options = get_value(document.header, "\\biblio_options", i)
1589             if biblio_options:
1590                 biblio_options += ","
1591             biblio_options += "refsection=" + multibib
1592             document.header[i] = "\\biblio_options " + biblio_options
1593
1594         # Bibtex insets
1595         i = 0
1596         while (True):
1597             i = find_token(document.body, "\\begin_inset CommandInset bibtex", i)
1598             if i == -1:
1599                 break
1600             j = find_end_of_inset(document.body, i)
1601             if j == -1:
1602                 document.warning("Can't find end of bibtex inset at line %d!!" %(i))
1603                 i += 1
1604                 continue
1605             btprint = get_quoted_value(document.body, "btprint", i, j)
1606             if btprint != "bibbysection":
1607                 i += 1
1608                 continue
1609             opts = get_quoted_value(document.body, "biblatexopts", i, j)
1610             # change btprint line
1611             k = find_token(document.body, "btprint", i, j)
1612             if k != -1:
1613                 document.body[k] = "btprint \"btPrintCited\""
1614             # Insert ERT \\bibbysection and wrap bibtex inset to a Note
1615             pcmd = "bibbysection"
1616             if opts:
1617                 pcmd += "[" + opts + "]"
1618             repl = ["\\begin_inset ERT", "status open", "", "\\begin_layout Plain Layout",\
1619                     "", "", "\\backslash", pcmd, "\\end_layout", "", "\\end_inset", "", "",\
1620                     "\\end_layout", "", "\\begin_layout Standard", "\\begin_inset Note Note",\
1621                     "status open", "", "\\begin_layout Plain Layout" ]
1622             repl += document.body[i:j+1]
1623             repl += ["", "\\end_layout", "", "\\end_inset", "", ""]
1624             document.body[i:j+1] = repl
1625             j += 27
1626
1627             i = j + 1
1628         return
1629
1630     # 5. More tricky: Bibtex/Bibtopic
1631     k = find_token(document.header, "\\use_bibtopic", 0)
1632     if k == -1:
1633         # this should not happen
1634         document.warning("Malformed LyX document! No \\use_bibtopic header found!")
1635         return
1636     document.header[k] = "\\use_bibtopic true"
1637
1638     # Possible units. This assumes that the LyX name follows the std,
1639     # which might not always be the case. But it's as good as we can get.
1640     units = {
1641         "part" : "Part",
1642         "chapter" : "Chapter",
1643         "section" : "Section",
1644         "subsection" : "Subsection",
1645         }
1646
1647     if multibib not in units.keys():
1648         document.warning("Unknown multibib value `%s'!" % nultibib)
1649         return
1650     unit = units[multibib]
1651     btunit = False
1652     i = 0
1653     while (True):
1654         i = find_token(document.body, "\\begin_layout " + unit, i)
1655         if i == -1:
1656             break
1657         if btunit:
1658             document.body[i-1 : i-1] = ["\\begin_layout Standard",
1659                                 "\\begin_inset ERT", "status open", "",
1660                                 "\\begin_layout Plain Layout", "", "",
1661                                 "\\backslash",
1662                                 "end{btUnit}", "\\end_layout",
1663                                 "\\begin_layout Plain Layout", "",
1664                                 "\\backslash",
1665                                 "begin{btUnit}"
1666                                 "\\end_layout", "", "\\end_inset", "", "",
1667                                 "\\end_layout", ""]
1668             i += 21
1669         else:
1670             document.body[i-1 : i-1] = ["\\begin_layout Standard",
1671                                 "\\begin_inset ERT", "status open", "",
1672                                 "\\begin_layout Plain Layout", "", "",
1673                                 "\\backslash",
1674                                 "begin{btUnit}"
1675                                 "\\end_layout", "", "\\end_inset", "", "",
1676                                 "\\end_layout", ""]
1677             i += 16
1678         btunit = True
1679         i += 1
1680
1681     if btunit:
1682         i = find_token(document.body, "\\end_body", i)
1683         document.body[i-1 : i-1] = ["\\begin_layout Standard",
1684                                 "\\begin_inset ERT", "status open", "",
1685                                 "\\begin_layout Plain Layout", "", "",
1686                                 "\\backslash",
1687                                 "end{btUnit}"
1688                                 "\\end_layout", "", "\\end_inset", "", "",
1689                                 "\\end_layout", ""]
1690
1691
1692 def revert_chapterbib(document):
1693     " Revert chapterbib support "
1694
1695     # 1. Get cite engine
1696     engine = "basic"
1697     i = find_token(document.header, "\\cite_engine", 0)
1698     if i == -1:
1699         document.warning("Malformed document! Missing \\cite_engine")
1700     else:
1701         engine = get_value(document.header, "\\cite_engine", i)
1702
1703     # 2. Do we use biblatex?
1704     biblatex = False
1705     if engine in ["biblatex", "biblatex-natbib"]:
1706         biblatex = True
1707
1708     # 3. Store multibib document header value
1709     multibib = ""
1710     i = find_token(document.header, "\\multibib", 0)
1711     if i != -1:
1712         multibib = get_value(document.header, "\\multibib", i)
1713
1714     if not multibib or multibib != "child":
1715         # nothing to do
1716         return
1717
1718     # 4. remove multibib header
1719     del document.header[i]
1720
1721     # 5. Biblatex
1722     if biblatex:
1723         # find include insets
1724         i = 0
1725         while (True):
1726             i = find_token(document.body, "\\begin_inset CommandInset include", i)
1727             if i == -1:
1728                 break
1729             j = find_end_of_inset(document.body, i)
1730             if j == -1:
1731                 document.warning("Can't find end of bibtex inset at line %d!!" %(i))
1732                 i += 1
1733                 continue
1734             parent = get_containing_layout(document.body, i)
1735             parbeg = parent[1]
1736
1737             # Insert ERT \\newrefsection before inset
1738             beg = ["\\begin_layout Standard",
1739                    "\\begin_inset ERT", "status open", "",
1740                    "\\begin_layout Plain Layout", "", "",
1741                    "\\backslash",
1742                    "newrefsection"
1743                    "\\end_layout", "", "\\end_inset", "", "",
1744                    "\\end_layout", ""]
1745             document.body[parbeg-1:parbeg-1] = beg
1746             j += len(beg)
1747             i = j + 1
1748         return
1749
1750     # 6. Bibtex/Bibtopic
1751     i = find_token(document.header, "\\use_bibtopic", 0)
1752     if i == -1:
1753         # this should not happen
1754         document.warning("Malformed LyX document! No \\use_bibtopic header found!")
1755         return
1756     if get_value(document.header, "\\use_bibtopic", i) == "true":
1757         # find include insets
1758         i = 0
1759         while (True):
1760             i = find_token(document.body, "\\begin_inset CommandInset include", i)
1761             if i == -1:
1762                 break
1763             j = find_end_of_inset(document.body, i)
1764             if j == -1:
1765                 document.warning("Can't find end of bibtex inset at line %d!!" %(i))
1766                 i += 1
1767                 continue
1768             parent = get_containing_layout(document.body, i)
1769             parbeg = parent[1]
1770             parend = parent[2]
1771
1772             # Insert wrap inset into \\begin{btUnit}...\\end{btUnit}
1773             beg = ["\\begin_layout Standard",
1774                    "\\begin_inset ERT", "status open", "",
1775                    "\\begin_layout Plain Layout", "", "",
1776                    "\\backslash",
1777                    "begin{btUnit}"
1778                    "\\end_layout", "", "\\end_inset", "", "",
1779                    "\\end_layout", ""]
1780             end = ["\\begin_layout Standard",
1781                    "\\begin_inset ERT", "status open", "",
1782                    "\\begin_layout Plain Layout", "", "",
1783                    "\\backslash",
1784                    "end{btUnit}"
1785                    "\\end_layout", "", "\\end_inset", "", "",
1786                    "\\end_layout", ""]
1787             document.body[parend+1:parend+1] = end
1788             document.body[parbeg-1:parbeg-1] = beg
1789             j += len(beg) + len(end)
1790             i = j + 1
1791         return
1792
1793     # 7. Chapterbib proper
1794     add_to_preamble(document, ["\\usepackage{chapterbib}"])
1795
1796
1797 def convert_dashligatures(document):
1798     """Set 'use_dash_ligatures' according to content.
1799     """
1800     # Look for and remove dashligatures workaround from 2.3->2.2 reversion,
1801     # set use_dash_ligatures to True if found, to None else.
1802     use_dash_ligatures = del_complete_lines(document.preamble,
1803                                 ['% Added by lyx2lyx',
1804                                  r'\renewcommand{\textendash}{--}',
1805                                  r'\renewcommand{\textemdash}{---}']) or None
1806
1807     if use_dash_ligatures is None:
1808         # Look for dashes (Documents by LyX 2.1 or older have "\twohyphens\n"
1809         # or "\threehyphens\n" as interim representation for -- an ---.)
1810         lines = document.body
1811         has_literal_dashes = has_ligature_dashes = False
1812         dash_pattern = re.compile(u".*[\u2013\u2014]|\\twohyphens|\\threehyphens")
1813         i = j = 0
1814         while True:
1815             # skip lines without dashes:
1816             i = find_re(lines, dash_pattern, i+1)
1817             if i == -1:
1818                 break
1819             line = lines[i]
1820             # skip label width string (see bug 10243):
1821             if line.startswith("\\labelwidthstring"):
1822                 continue
1823             # do not touch hyphens in some insets (cf. lyx_2_2.convert_dashes):
1824             try:
1825                 inset_type, start, end = get_containing_inset(lines, i)
1826             except TypeError: # no containing inset
1827                 inset_type, start, end = "no inset", -1, -1
1828             if (inset_type.split()[0] in
1829                 ["CommandInset", "ERT", "External", "Formula",
1830                  "FormulaMacro", "Graphics", "IPA", "listings"]
1831                 or inset_type == "Flex Code"):
1832                 i = end
1833                 continue
1834             try:
1835                 layoutname, start, end, j = get_containing_layout(lines, i)
1836             except TypeError: # no (or malformed) containing layout
1837                 document.warning("Malformed LyX document: "
1838                                 "Can't find layout at line %d" % i)
1839                 continue
1840             if layoutname == "LyX-Code":
1841                 i = end
1842                 continue
1843
1844             # literal dash followed by a non-white-character or no-break space:
1845             if re.search(u"[\u2013\u2014]([\S\u00A0\u202F\u2060]|$)",
1846                          line, flags=re.UNICODE):
1847                 has_literal_dashes = True
1848             # ligature dash followed by non-white-char or no-break space on next line:
1849             if (re.search(r"(\\twohyphens|\\threehyphens)", line) and
1850                 re.match(u"[\S\u00A0\u202F\u2060]", lines[i+1], flags=re.UNICODE)):
1851                 has_ligature_dashes = True
1852             if has_literal_dashes and has_ligature_dashes:
1853                 # TODO: insert a warning note in the document?
1854                 document.warning('This document contained both literal and '
1855                                  '"ligature" dashes.\n Line breaks may have changed. '
1856                                  'See UserGuide chapter 3.9.1 for details.')
1857                 break
1858
1859         if has_literal_dashes and not has_ligature_dashes:
1860             use_dash_ligatures = False
1861         elif has_ligature_dashes and not has_literal_dashes:
1862             use_dash_ligatures = True
1863
1864     # insert the setting if there is a preferred value
1865     if use_dash_ligatures is True:
1866         document.header.insert(-1, "\\use_dash_ligatures true")
1867     elif use_dash_ligatures is False:
1868         document.header.insert(-1, "\\use_dash_ligatures false")
1869
1870
1871 def revert_dashligatures(document):
1872     """Remove font ligature settings for en- and em-dashes.
1873     Revert conversion of \twodashes or \threedashes to literal dashes.
1874     """
1875     use_dash_ligatures = del_value(document.header, "\\use_dash_ligatures")
1876     if use_dash_ligatures != "true" or document.backend != "latex":
1877         return
1878     i = 0
1879     dash_pattern = re.compile(u".*[\u2013\u2014]")
1880     while True:
1881         # skip lines without dashes:
1882         i = find_re(document.body, dash_pattern, i+1)
1883         if i == -1:
1884             break
1885         line = document.body[i]
1886         # skip label width string (see bug 10243):
1887         if line.startswith("\\labelwidthstring"):
1888             continue
1889         # do not touch hyphens in some insets (cf. lyx_2_2.convert_dashes):
1890         try:
1891             inset_type, start, end = get_containing_inset(document.body, i)
1892         except TypeError: # no containing inset
1893             inset_type, start, end = "no inset", -1, -1
1894         if (inset_type.split()[0] in
1895             ["CommandInset", "ERT", "External", "Formula",
1896                 "FormulaMacro", "Graphics", "IPA", "listings"]
1897             or inset_type == "Flex Code"):
1898             i = end
1899             continue
1900         try:
1901             layoutname, start, end, j = get_containing_layout(document.body, i)
1902         except TypeError: # no (or malformed) containing layout
1903             document.warning("Malformed LyX document: "
1904                             "Can't find layout at body line %d" % i)
1905             continue
1906         if layoutname == "LyX-Code":
1907             i = end
1908             continue
1909         # TODO: skip replacement in typewriter fonts
1910         line = line.replace(u'\u2013', '\\twohyphens\n')
1911         line = line.replace(u'\u2014', '\\threehyphens\n')
1912         document.body[i:i+1] = line.split('\n')
1913     # redefine the dash LICRs to use ligature dashes:
1914     add_to_preamble(document, [r'\renewcommand{\textendash}{--}',
1915                                r'\renewcommand{\textemdash}{---}'])
1916
1917
1918 def revert_noto(document):
1919     " Revert Noto font definitions to LaTeX "
1920
1921     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
1922         preamble = ""
1923         i = find_token(document.header, "\\font_roman \"NotoSerif-TLF\"", 0)
1924         if i != -1:
1925             add_to_preamble(document, ["\\renewcommand{\\rmdefault}{NotoSerif-TLF}"])
1926             document.header[i] = document.header[i].replace("NotoSerif-TLF", "default")
1927         i = find_token(document.header, "\\font_sans \"NotoSans-TLF\"", 0)
1928         if i != -1:
1929             add_to_preamble(document, ["\\renewcommand{\\sfdefault}{NotoSans-TLF}"])
1930             document.header[i] = document.header[i].replace("NotoSans-TLF", "default")
1931         i = find_token(document.header, "\\font_typewriter \"NotoMono-TLF\"", 0)
1932         if i != -1:
1933             add_to_preamble(document, ["\\renewcommand{\\ttdefault}{NotoMono-TLF}"])
1934             document.header[i] = document.header[i].replace("NotoMono-TLF", "default")
1935
1936
1937 def revert_xout(document):
1938   " Reverts \\xout font attribute "
1939   changed = revert_font_attrs(document.body, "\\xout", "\\xout")
1940   if changed == True:
1941     insert_to_preamble(document, \
1942         ['%  for proper cross-out',
1943         '\\PassOptionsToPackage{normalem}{ulem}',
1944         '\\usepackage{ulem}'])
1945
1946
1947 def convert_mathindent(document):
1948     """Add the \\is_math_indent tag.
1949     """
1950     k = find_token(document.header, "\\quotes_style") # where to insert
1951     # check if the document uses the class option "fleqn"
1952     options = get_value(document.header, "\\options")
1953     if 'fleqn' in options:
1954         document.header.insert(k, "\\is_math_indent 1")
1955         # delete the fleqn option
1956         i = find_token(document.header, "\\options")
1957         options = [option for option in options.split(",")
1958                    if option.strip() != "fleqn"]
1959         if options:
1960             document.header[i] = "\\options " + ",".join(options)
1961         else:
1962             del document.header[i]
1963     else:
1964         document.header.insert(k, "\\is_math_indent 0")
1965
1966 def revert_mathindent(document):
1967     " Define mathindent if set in the document "
1968     # emulate and delete \math_indentation
1969     value = get_value(document.header, "\\math_indentation",
1970                       default="default", delete=True)
1971     if value != "default":
1972         add_to_preamble(document, [r"\setlength{\mathindent}{%s}"%value])
1973     # delete \is_math_indent and emulate via document class option
1974     if not get_bool_value(document.header, "\\is_math_indent", delete=True):
1975         return
1976     i = find_token(document.header, "\\options")
1977     if i != -1:
1978         document.header[i] = document.header[i].replace("\\options ",
1979                                                         "\\options fleqn,")
1980     else:
1981         l = find_token(document.header, "\\use_default_options")
1982         document.header.insert(l, "\\options fleqn")
1983
1984
1985 def revert_baselineskip(document):
1986     " Revert baselineskips to TeX code "
1987     i = 0
1988     while True:
1989         i = find_substring(document.body, "baselineskip%", i+1)
1990         if i == -1:
1991             return
1992         if  document.body[i].startswith("\\begin_inset VSpace"):
1993             # output VSpace inset as TeX code
1994             end = find_end_of_inset(document.body, i)
1995             if end == -1:
1996                 document.warning("Malformed LyX document: "
1997                         "Can't find end of VSpace inset at line %d." % i)
1998                 continue
1999             # read out the value
2000             baselineskip = document.body[i].split()[-1]
2001             # check if it is the starred version
2002             star = '*' if '*' in document.body[i] else ''
2003             # now output TeX code
2004             cmd = "\\vspace%s{%s}" %(star, latex_length(baselineskip)[1])
2005             document.body[i:end+1] = put_cmd_in_ert(cmd)
2006             i += 8
2007             continue
2008         begin, end = is_in_inset(document.body, i, "\\begin_inset space \\hspace")
2009         if  begin != - 1:
2010             # output space inset as TeX code
2011             baselineskip = document.body[i].split()[-1]
2012             star = '*' if '*' in document.body[i-1] else ''
2013             cmd = "\\hspace%s{%s}" %(star, latex_length(baselineskip)[1])
2014             document.body[begin:end+1] = put_cmd_in_ert(cmd)
2015
2016
2017 def revert_rotfloat(document):
2018   " Revert placement options for rotated floats "
2019   i = 0
2020   j = 0
2021   k = 0
2022   while True:
2023     i = find_token(document.body, "sideways true", i)
2024     if i == -1:
2025       return
2026     if not document.body[i-2].startswith('placement '):
2027         i = i + 1
2028         continue
2029     # we found a sideways float with placement options
2030     # at first store the placement
2031     beg = document.body[i-2].rfind(" ");
2032     placement = document.body[i-2][beg+1:]
2033     # check if the option'H' is used
2034     if placement.find("H") != -1:
2035       add_to_preamble(document, ["\\usepackage{float}"])
2036     # now check if it is a starred type
2037     if document.body[i-1].find("wide true") != -1:
2038       star = '*'
2039     else:
2040       star = ''
2041     # store the float type
2042     beg = document.body[i-3].rfind(" ");
2043     fType = document.body[i-3][beg+1:]
2044     # now output TeX code
2045     endInset = find_end_of_inset(document.body, i-3)
2046     if endInset == -1:
2047       document.warning("Malformed LyX document: Missing '\\end_inset' of Float inset.")
2048       return
2049     else:
2050       document.body[endInset-2: endInset+1] = put_cmd_in_ert("\\end{sideways" + fType + star + '}')
2051       document.body[i-3: i+2] = put_cmd_in_ert("\\begin{sideways" + fType + star + "}[" + placement + ']')
2052       add_to_preamble(document, ["\\usepackage{rotfloat}"])
2053
2054     i = i + 1
2055
2056
2057 allowbreak_emulation =  [r"\begin_inset space \hspace{}",
2058                          r"\length 0dd",
2059                          r"\end_inset",
2060                          r""]
2061
2062 def convert_allowbreak(document):
2063     " Zero widths Space-inset -> \SpecialChar allowbreak. "
2064     lines = document.body
2065     i = find_complete_lines(lines, allowbreak_emulation, 2)
2066     while i != -1:
2067         lines[i-1:i+4] = [lines[i-1] + r"\SpecialChar allowbreak"]
2068         i = find_complete_lines(lines, allowbreak_emulation, i+3)
2069
2070
2071 def revert_allowbreak(document):
2072     " \SpecialChar allowbreak -> Zero widths Space-inset. "
2073     i = 1
2074     lines = document.body
2075     while i < len(lines):
2076         if lines[i].endswith(r"\SpecialChar allowbreak"):
2077             lines[i:i+1] = [lines[i].replace(r"\SpecialChar allowbreak", "")
2078                            ] + allowbreak_emulation
2079             i += 5
2080         else:
2081             i += 1
2082
2083
2084 def convert_mathnumberpos(document):
2085     " add the \\math_number_before tag "
2086     # check if the document uses the class option "leqno"
2087     i = find_token(document.header, "\\options")
2088     k = find_token(document.header, "\\quotes_style")
2089     if 'leqno' in document.header[i]:
2090         document.header.insert(k, "\\math_number_before 1")
2091         # delete the found option
2092         document.header[i] = document.header[i].replace(",leqno", "")
2093         document.header[i] = document.header[i].replace(", leqno", "")
2094         document.header[i] = document.header[i].replace("leqno,", "")
2095         if 'leqno' in document.header[i]:
2096             # then we have leqno as the only option
2097             del document.header[i]
2098     else:
2099         document.header.insert(k, "\\math_number_before 0")
2100
2101
2102 def revert_mathnumberpos(document):
2103     """Remove \\math_number_before tag,
2104     add the document class option leqno if required.
2105     """
2106     math_number_before = get_bool_value(document.header,
2107                                         '\\math_number_before', delete=True)
2108     if math_number_before:
2109         i = find_token(document.header, "\\options")
2110         if i != -1 and 'leqno' not in document.header[i]:
2111             document.header[i] = document.header[i].replace("\\options", "\\options leqno,")
2112         else:
2113             i = find_token(document.header, "\\use_default_options")
2114             document.header.insert(i, "\\options leqno")
2115
2116
2117 def convert_mathnumberingname(document):
2118     " rename the \\math_number_before tag to \\math_numbering_side "
2119     i = find_token(document.header, "\\math_number_before")
2120     math_number_before = get_bool_value(document.header, '\\math_number_before', i)
2121     if math_number_before:
2122         document.header[i] = "\\math_numbering_side left"
2123         return
2124     # check if the document uses the class option "reqno"
2125     k = find_token(document.header, "\\options")
2126     if 'reqno' in document.header[k]:
2127         document.header[i] = "\\math_numbering_side right"
2128         # delete the found option
2129         document.header[k] = document.header[k].replace(",reqno", "")
2130         document.header[k] = document.header[k].replace(", reqno", "")
2131         document.header[k] = document.header[k].replace("reqno,", "")
2132         if 'reqno' in document.header[k]:
2133             # then we have reqno as the only option
2134             del document.header[k]
2135     else:
2136         document.header[i] = "\\math_numbering_side default"
2137
2138
2139 def revert_mathnumberingname(document):
2140     " rename the \\math_numbering_side tag back to \\math_number_before "
2141     i = find_token(document.header, "\\math_numbering_side")
2142     math_numbering_side = get_value(document.header, '\\math_numbering_side', i)
2143     # rename tag and set boolean value:
2144     if math_numbering_side == "left":
2145         document.header[i] = "\\math_number_before 1"
2146     elif math_numbering_side == "right":
2147         # also add the option reqno:
2148         document.header[i] = "\\math_number_before 0"
2149         k = find_token(document.header, "\\options")
2150         if k != -1  and 'reqno' not in document.header[k]:
2151             document.header[k] = document.header[k].replace("\\options", "\\options reqno,")
2152         else:
2153             l = find_token(document.header, "\\use_default_options", 0)
2154             document.header.insert(l, "\\options reqno")
2155     else:
2156         document.header[i] = "\\math_number_before 0"
2157
2158
2159 def convert_minted(document):
2160     " add the \\use_minted tag "
2161     i = find_token(document.header, "\\index ")
2162     document.header.insert(i, "\\use_minted 0")
2163
2164
2165 def revert_minted(document):
2166     " remove the \\use_minted tag "
2167     del_token(document.header, "\\use_minted")
2168
2169
2170 def revert_longtable_lscape(document):
2171     " revert the longtable landcape mode to ERT "
2172     i = 0
2173     regexp = re.compile(r'^<features rotate=\"90\"\s.*islongtable=\"true\"\s.*$', re.IGNORECASE)
2174     while True:
2175         i = find_re(document.body, regexp, i)
2176         if i == -1:
2177             return
2178
2179         document.body[i] = document.body[i].replace(" rotate=\"90\"", "")
2180         lay = get_containing_layout(document.body, i)
2181         if lay == False:
2182             document.warning("Longtable has not layout!")
2183             i += 1
2184             continue
2185         begcmd = put_cmd_in_ert("\\begin{landscape}")
2186         endcmd = put_cmd_in_ert("\\end{landscape}")
2187         document.body[lay[2] : lay[2]] = endcmd + ["\\end_layout"]
2188         document.body[lay[1] : lay[1]] = ["\\begin_layout " + lay[0], ""] + begcmd
2189
2190         add_to_preamble(document, ["\\usepackage{pdflscape}"])
2191         i = lay[2]
2192
2193
2194 ##
2195 # Conversion hub
2196 #
2197
2198 supported_versions = ["2.3.0", "2.3"]
2199 convert = [
2200            [509, [convert_microtype]],
2201            [510, [convert_dateinset]],
2202            [511, [convert_ibranches]],
2203            [512, [convert_beamer_article_styles]],
2204            [513, []],
2205            [514, []],
2206            [515, []],
2207            [516, [convert_inputenc]],
2208            [517, []],
2209            [518, [convert_iopart]],
2210            [519, [convert_quotestyle]],
2211            [520, []],
2212            [521, [convert_frenchquotes]],
2213            [522, []],
2214            [523, []],
2215            [524, [convert_crimson]],
2216            [525, []],
2217            [526, []],
2218            [527, []],
2219            [528, []],
2220            [529, []],
2221            [530, []],
2222            [531, []],
2223            [532, [convert_literalparam]],
2224            [533, []],
2225            [534, []],
2226            [535, [convert_dashligatures]],
2227            [536, []],
2228            [537, []],
2229            [538, [convert_mathindent]],
2230            [539, []],
2231            [540, []],
2232            [541, [convert_allowbreak]],
2233            [542, [convert_mathnumberpos]],
2234            [543, [convert_mathnumberingname]],
2235            [544, [convert_minted]]
2236           ]
2237
2238 revert =  [
2239            [543, [revert_minted, revert_longtable_lscape]],
2240            [542, [revert_mathnumberingname]],
2241            [541, [revert_mathnumberpos]],
2242            [540, [revert_allowbreak]],
2243            [539, [revert_rotfloat]],
2244            [538, [revert_baselineskip]],
2245            [537, [revert_mathindent]],
2246            [536, [revert_xout]],
2247            [535, [revert_noto]],
2248            [534, [revert_dashligatures]],
2249            [533, [revert_chapterbib]],
2250            [532, [revert_multibib]],
2251            [531, [revert_literalparam]],
2252            [530, [revert_qualicites]],
2253            [529, [revert_bibpackopts]],
2254            [528, [revert_citekeyonly]],
2255            [527, [revert_biblatex]],
2256            [526, [revert_noprefix]],
2257            [525, [revert_plural_refs]],
2258            [524, [revert_labelonly]],
2259            [523, [revert_crimson, revert_cochinealmath]],
2260            [522, [revert_cjkquotes]],
2261            [521, [revert_dynamicquotes]],
2262            [520, [revert_britishquotes, revert_swedishgquotes, revert_frenchquotes, revert_frenchinquotes, revert_russianquotes, revert_swissquotes]],
2263            [519, [revert_plainquote]],
2264            [518, [revert_quotestyle]],
2265            [517, [revert_iopart]],
2266            [516, [revert_quotes]],
2267            [515, []],
2268            [514, [revert_urdu, revert_syriac]],
2269            [513, [revert_amharic, revert_asturian, revert_kannada, revert_khmer]],
2270            [512, [revert_new_babel_languages]],
2271            [511, [revert_beamer_article_styles]],
2272            [510, [revert_ibranches]],
2273            [509, []],
2274            [508, [revert_microtype]]
2275           ]
2276
2277
2278 if __name__ == "__main__":
2279     pass