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