]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_2_3.py
Fix lyx2lyx dash conversion and make it faster.
[lyx.git] / lib / lyx2lyx / lyx_2_3.py
1 # -*- coding: utf-8 -*-
2 # This file is part of lyx2lyx
3 # Copyright (C) 2016 The LyX team
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 """ Convert files to the file format generated by lyx 2.3"""
20
21 import re, string
22 import unicodedata
23 import sys, os
24
25 # Uncomment only what you need to import, please.
26
27 from parser_tools import (del_token, del_value, del_complete_lines,
28     find_complete_lines, find_end_of, find_end_of_layout, find_end_of_inset,
29     find_re, find_token, find_token_backwards, get_containing_inset,
30     get_containing_layout, 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         lines = document.body
1857         has_literal_dashes = has_ligature_dashes = False
1858         i = j = 0
1859         while i+1 < len(lines):
1860             i += 1
1861             line = lines[i]
1862             # skip lines without any dashes:
1863             if not re.search(u"[\u2013\u2014]|\\twohyphens|\\threehyphens", line):
1864                 continue
1865             # skip label width string (see bug 10243):
1866             if line.startswith("\\labelwidthstring"):
1867                 continue
1868             # do not touch hyphens in some insets (cf. lyx_2_2.convert_dashes):
1869             try:
1870                 value, start, end = get_containing_inset(lines, i)
1871             except TypeError: # no containing inset
1872                 value, start, end = "no inset", -1, -1
1873             if (value.split()[0] in
1874                 ["CommandInset", "ERT", "External", "Formula",
1875                  "FormulaMacro", "Graphics", "IPA", "listings"]
1876                 or value == "Flex Code"):
1877                 i = end
1878                 continue
1879             try:
1880                 layout, start, end, j = get_containing_layout(lines, i)
1881             except TypeError: # no (or malformed) containing layout
1882                 document.warning("Malformed LyX document: "
1883                                 "Can't find layout at line %d" % i)
1884                 continue
1885             if layout == "LyX-Code":
1886                 i = end
1887                 continue
1888
1889             # literal dash followed by a word or no-break space:
1890             if re.search(u"[\u2013\u2014]([\w\u00A0]|$)", line,
1891                          flags=re.UNICODE):
1892                 has_literal_dashes = True
1893             # ligature dash followed by word or no-break space on next line:
1894             if (re.search(r"(\\twohyphens|\\threehyphens)", line) and
1895                 re.match(u"[\w\u00A0]", lines[i+1], flags=re.UNICODE)):
1896                 has_ligature_dashes = True
1897             if has_literal_dashes and has_ligature_dashes:
1898                 # TODO: insert a warning note in the document?
1899                 document.warning('This document contained both literal and '
1900                                  '"ligature" dashes.\n Line breaks may have changed. '
1901                                  'See UserGuide chapter 3.9.1 for details.')
1902                 break
1903         if has_literal_dashes:
1904             use_dash_ligatures = False
1905         elif has_ligature_dashes:
1906             use_dash_ligatures = True
1907     # insert the setting if there is a preferred value
1908     if use_dash_ligatures is not None:
1909         i = find_token(document.header, "\\graphics")
1910         document.header.insert(i, "\\use_dash_ligatures %s"
1911                                % str(use_dash_ligatures).lower())
1912
1913
1914 def revert_dashligatures(document):
1915     """Remove font ligature settings for en- and em-dashes.
1916     Revert conversion of \twodashes or \threedashes to literal dashes."""
1917     use_dash_ligatures = del_value(document.header, "\\use_dash_ligatures")
1918     if use_dash_ligatures != "true" or document.backend != "latex":
1919         return
1920     j = 0
1921     new_body = []
1922     for i, line in enumerate(document.body):
1923         # Skip some document parts where dashes are not converted
1924         if (i < j) or line.startswith("\\labelwidthstring"):
1925             new_body.append(line)
1926             continue
1927         if (line.startswith("\\begin_inset ") and
1928             line[13:].split()[0] in ["CommandInset", "ERT", "External",
1929                 "Formula", "FormulaMacro", "Graphics", "IPA", "listings"]
1930             or line == "\\begin_inset Flex Code"):
1931             j = find_end_of_inset(document.body, i)
1932             if j == -1:
1933                 document.warning("Malformed LyX document: Can't find end of "
1934                                  + words[1] + " inset at line " + str(i))
1935             new_body.append(line)
1936             continue
1937         if line == "\\begin_layout LyX-Code":
1938             j = find_end_of_layout(document.body, i)
1939             if j == -1:
1940                 document.warning("Malformed LyX document: "
1941                     "Can't find end of %s layout at line %d" % (words[1],i))
1942             new_body.append(line)
1943             continue
1944         # TODO: skip replacement in typewriter fonts
1945         line = line.replace(u'\u2013', '\\twohyphens\n')
1946         line = line.replace(u'\u2014', '\\threehyphens\n')
1947         lines = line.split('\n')
1948         new_body.extend(line.split('\n'))
1949     document.body = new_body
1950     # redefine the dash LICRs to use ligature dashes:
1951     add_to_preamble(document, [r'\renewcommand{\textendash}{--}',
1952                                r'\renewcommand{\textemdash}{---}'])
1953
1954
1955 def revert_noto(document):
1956     " Revert Noto font definitions to LaTeX "
1957
1958     if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
1959         preamble = ""
1960         i = find_token(document.header, "\\font_roman \"NotoSerif-TLF\"", 0)
1961         if i != -1:
1962             add_to_preamble(document, ["\\renewcommand{\\rmdefault}{NotoSerif-TLF}"])
1963             document.header[i] = document.header[i].replace("NotoSerif-TLF", "default")
1964         i = find_token(document.header, "\\font_sans \"NotoSans-TLF\"", 0)
1965         if i != -1:
1966             add_to_preamble(document, ["\\renewcommand{\\sfdefault}{NotoSans-TLF}"])
1967             document.header[i] = document.header[i].replace("NotoSans-TLF", "default")
1968         i = find_token(document.header, "\\font_typewriter \"NotoMono-TLF\"", 0)
1969         if i != -1:
1970             add_to_preamble(document, ["\\renewcommand{\\ttdefault}{NotoMono-TLF}"])
1971             document.header[i] = document.header[i].replace("NotoMono-TLF", "default")
1972
1973
1974 def revert_xout(document):
1975   " Reverts \\xout font attribute "
1976   changed = revert_font_attrs(document.body, "\\xout", "\\xout")
1977   if changed == True:
1978     insert_to_preamble(document, \
1979         ['%  for proper cross-out',
1980         '\\PassOptionsToPackage{normalem}{ulem}',
1981         '\\usepackage{ulem}'])
1982
1983
1984 def convert_mathindent(document):
1985     """Add the \\is_math_indent tag.
1986     """
1987     k = find_token(document.header, "\\quotes_style") # where to insert
1988     # check if the document uses the class option "fleqn"
1989     options = get_value(document.header, "\\options")
1990     if 'fleqn' in options:
1991         document.header.insert(k, "\\is_math_indent 1")
1992         # delete the fleqn option
1993         i = find_token(document.header, "\\options")
1994         options = [option for option in options.split(",")
1995                    if option.strip() != "fleqn"]
1996         if options:
1997             document.header[i] = "\\options " + ",".join(options)
1998         else:
1999             del document.header[i]
2000     else:
2001         document.header.insert(k, "\\is_math_indent 0")
2002
2003 def revert_mathindent(document):
2004     " Define mathindent if set in the document "
2005     # emulate and delete \math_indentation
2006     value = get_value(document.header, "\\math_indentation",
2007                       default="default", delete=True)
2008     if value != "default":
2009         add_to_preamble(document, [r"\setlength{\mathindent}{%s}"%value])
2010     # delete \is_math_indent and emulate via document class option
2011     if not get_bool_value(document.header, "\\is_math_indent", delete=True):
2012         return
2013     i = find_token(document.header, "\\options")
2014     if i != -1:
2015         document.header[i] = document.header[i].replace("\\options ",
2016                                                         "\\options fleqn,")
2017     else:
2018         l = find_token(document.header, "\\use_default_options")
2019         document.header.insert(l, "\\options fleqn")
2020
2021
2022 def revert_baselineskip(document):
2023   " Revert baselineskips to TeX code "
2024   i = 0
2025   vspaceLine = 0
2026   hspaceLine = 0
2027   while True:
2028     regexp = re.compile(r'^.*baselineskip%.*$')
2029     i = find_re(document.body, regexp, i)
2030     if i == -1:
2031       return
2032     vspaceLine = find_token(document.body, "\\begin_inset VSpace", i)
2033     if  vspaceLine == i:
2034       # output VSpace inset as TeX code
2035       # first read out the values
2036       beg = document.body[i].rfind("VSpace ");
2037       end = document.body[i].rfind("baselineskip%");
2038       baselineskip = float(document.body[i][beg + 7:end]);
2039       # we store the value in percent, thus divide by 100
2040       baselineskip = baselineskip/100;
2041       baselineskip = str(baselineskip);
2042       # check if it is the starred version
2043       if document.body[i].find('*') != -1:
2044         star = '*'
2045       else:
2046         star = ''
2047       # now output TeX code
2048       endInset = find_end_of_inset(document.body, i)
2049       if endInset == -1:
2050         document.warning("Malformed LyX document: Missing '\\end_inset' of VSpace inset.")
2051         return
2052       else:
2053         document.body[vspaceLine: endInset + 1] = put_cmd_in_ert("\\vspace" + star + '{' + baselineskip + "\\baselineskip}")
2054     hspaceLine = find_token(document.body, "\\begin_inset space \\hspace", i - 1)
2055     document.warning("hspaceLine: " + str(hspaceLine))
2056     document.warning("i: " + str(i))
2057     if  hspaceLine == i - 1:
2058       # output space inset as TeX code
2059       # first read out the values
2060       beg = document.body[i].rfind("\\length ");
2061       end = document.body[i].rfind("baselineskip%");
2062       baselineskip = float(document.body[i][beg + 7:end]);
2063       document.warning("baselineskip: " + str(baselineskip))
2064       # we store the value in percent, thus divide by 100
2065       baselineskip = baselineskip/100;
2066       baselineskip = str(baselineskip);
2067       # check if it is the starred version
2068       if document.body[i-1].find('*') != -1:
2069         star = '*'
2070       else:
2071         star = ''
2072       # now output TeX code
2073       endInset = find_end_of_inset(document.body, i)
2074       if endInset == -1:
2075         document.warning("Malformed LyX document: Missing '\\end_inset' of space inset.")
2076         return
2077       else:
2078         document.body[hspaceLine: endInset + 1] = put_cmd_in_ert("\\hspace" + star + '{' + baselineskip + "\\baselineskip}")
2079
2080     i = i + 1
2081
2082
2083 def revert_rotfloat(document):
2084   " Revert placement options for rotated floats "
2085   i = 0
2086   j = 0
2087   k = 0
2088   while True:
2089     i = find_token(document.body, "sideways true", i)
2090     if i != -1:
2091       regexp = re.compile(r'^.*placement.*$')
2092       j = find_re(document.body, regexp, i-2)
2093       if j == -1:
2094           return
2095       if j != i-2:
2096           i = i + 1
2097           continue
2098     else:
2099       return
2100     # we found a sideways float with placement options
2101     # at first store the placement
2102     beg = document.body[i-2].rfind(" ");
2103     placement = document.body[i-2][beg+1:]
2104     # check if the option'H' is used
2105     if placement.find("H") != -1:
2106       add_to_preamble(document, ["\\usepackage{float}"])
2107     # now check if it is a starred type
2108     if document.body[i-1].find("wide true") != -1:
2109       star = '*'
2110     else:
2111       star = ''
2112     # store the float type
2113     beg = document.body[i-3].rfind(" ");
2114     fType = document.body[i-3][beg+1:]
2115     # now output TeX code
2116     endInset = find_end_of_inset(document.body, i-3)
2117     if endInset == -1:
2118       document.warning("Malformed LyX document: Missing '\\end_inset' of Float inset.")
2119       return
2120     else:
2121       document.body[endInset-2: endInset+1] = put_cmd_in_ert("\\end{sideways" + fType + star + '}')
2122       document.body[i-3: i+2] = put_cmd_in_ert("\\begin{sideways" + fType + star + "}[" + placement + ']')
2123       add_to_preamble(document, ["\\usepackage{rotfloat}"])
2124
2125     i = i + 1
2126
2127
2128 allowbreak_emulation =  [r"\begin_inset space \hspace{}",
2129                          r"\length 0dd",
2130                          r"\end_inset",
2131                          r""]
2132
2133 def convert_allowbreak(document):
2134     " Zero widths Space-inset -> \SpecialChar allowbreak. "
2135     lines = document.body
2136     i = find_complete_lines(lines, allowbreak_emulation, 2)
2137     while i != -1:
2138         lines[i-1:i+4] = [lines[i-1] + r"\SpecialChar allowbreak"]
2139         i = find_complete_lines(lines, allowbreak_emulation, i)
2140
2141
2142 def revert_allowbreak(document):
2143     " \SpecialChar allowbreak -> Zero widths Space-inset. "
2144     i = 1
2145     lines = document.body
2146     while i < len(lines):
2147         if lines[i].endswith(r"\SpecialChar allowbreak"):
2148             lines[i:i+1] = [lines[i].replace(r"\SpecialChar allowbreak", "")
2149                            ] + allowbreak_emulation
2150             i += 5
2151         else:
2152             i += 1
2153
2154
2155 def convert_mathnumberpos(document):
2156     " add the \\math_number_before tag "
2157     # check if the document uses the class option "leqno"
2158     k = find_token(document.header, "\\quotes_style", 0)
2159     m = find_token(document.header, "\\options", 0)
2160     regexp = re.compile(r'^.*leqno.*')
2161     i = find_re(document.header, regexp, 0)
2162     if i != -1 and i == m:
2163         document.header.insert(k, "\\math_number_before 1")
2164         # delete the found option
2165         document.header[i] = document.header[i].replace(",leqno", "")
2166         document.header[i] = document.header[i].replace(", leqno", "")
2167         document.header[i] = document.header[i].replace("leqno,", "")
2168         j = find_re(document.header, regexp, 0)
2169         if i == j:
2170             # then we have leqno as the only option
2171             del document.header[i]
2172     else:
2173         document.header.insert(k, "\\math_number_before 0")
2174
2175
2176 def revert_mathnumberpos(document):
2177     " add the document class option leqno"
2178     regexp = re.compile(r'(\\math_number_before 1)')
2179     i = find_re(document.header, regexp, 0)
2180     if i == -1:
2181         regexp = re.compile(r'(\\math_number_before)')
2182         j = find_re(document.header, regexp, 0)
2183         del document.header[j]
2184     else:
2185         k = find_token(document.header, "\\options", 0)
2186         if k != -1:
2187             document.header[k] = document.header[k].replace("\\options", "\\options leqno,")
2188             del document.header[i]
2189         else:
2190             l = find_token(document.header, "\\use_default_options", 0)
2191             document.header.insert(l, "\\options leqno")
2192             del document.header[i + 1]
2193
2194
2195 def convert_mathnumberingname(document):
2196     " rename the \\math_number_before tag to \\math_numbering_side "
2197     regexp = re.compile(r'(\\math_number_before 1)')
2198     i = find_re(document.header, regexp, 0)
2199     if i != -1:
2200         document.header[i] = "\\math_numbering_side left"
2201     regexp = re.compile(r'(\\math_number_before 0)')
2202     i = find_re(document.header, regexp, 0)
2203     if i != -1:
2204         document.header[i] = "\\math_numbering_side default"
2205     # check if the document uses the class option "reqno"
2206     k = find_token(document.header, "\\math_numbering_side", 0)
2207     m = find_token(document.header, "\\options", 0)
2208     regexp = re.compile(r'^.*reqno.*')
2209     i = find_re(document.header, regexp, 0)
2210     if i != -1 and i == m:
2211         document.header[k] = "\\math_numbering_side right"
2212         # delete the found option
2213         document.header[i] = document.header[i].replace(",reqno", "")
2214         document.header[i] = document.header[i].replace(", reqno", "")
2215         document.header[i] = document.header[i].replace("reqno,", "")
2216         j = find_re(document.header, regexp, 0)
2217         if i == j:
2218             # then we have reqno as the only option
2219             del document.header[i]
2220
2221
2222 def revert_mathnumberingname(document):
2223     " rename the \\math_numbering_side tag back to \\math_number_before "
2224     # just rename
2225     regexp = re.compile(r'(\\math_numbering_side left)')
2226     i = find_re(document.header, regexp, 0)
2227     if i != -1:
2228         document.header[i] = "\\math_number_before 1"
2229     # add the option reqno and delete the tag
2230     regexp = re.compile(r'(\\math_numbering_side right)')
2231     i = find_re(document.header, regexp, 0)
2232     if i != -1:
2233         document.header[i] = "\\math_number_before 0"
2234         k = find_token(document.header, "\\options", 0)
2235         if k != -1:
2236             document.header[k] = document.header[k].replace("\\options", "\\options reqno,")
2237         else:
2238             l = find_token(document.header, "\\use_default_options", 0)
2239             document.header.insert(l, "\\options reqno")
2240     # add the math_number_before tag
2241     regexp = re.compile(r'(\\math_numbering_side default)')
2242     i = find_re(document.header, regexp, 0)
2243     if i != -1:
2244         document.header[i] = "\\math_number_before 0"
2245
2246
2247 def convert_minted(document):
2248     " add the \\use_minted tag "
2249     i = find_token(document.header, "\\index ")
2250     document.header.insert(i, "\\use_minted 0")
2251
2252
2253 def revert_minted(document):
2254     " remove the \\use_minted tag "
2255     i = find_token(document.header, "\\use_minted", 0)
2256     if i != -1:
2257         document.header.pop(i)
2258
2259
2260 ##
2261 # Conversion hub
2262 #
2263
2264 supported_versions = ["2.3.0", "2.3"]
2265 convert = [
2266            [509, [convert_microtype]],
2267            [510, [convert_dateinset]],
2268            [511, [convert_ibranches]],
2269            [512, [convert_beamer_article_styles]],
2270            [513, []],
2271            [514, []],
2272            [515, []],
2273            [516, [convert_inputenc]],
2274            [517, []],
2275            [518, [convert_iopart]],
2276            [519, [convert_quotestyle]],
2277            [520, []],
2278            [521, [convert_frenchquotes]],
2279            [522, []],
2280            [523, []],
2281            [524, []],
2282            [525, []],
2283            [526, []],
2284            [527, []],
2285            [528, []],
2286            [529, []],
2287            [530, []],
2288            [531, []],
2289            [532, [convert_literalparam]],
2290            [533, []],
2291            [534, []],
2292            [535, [convert_dashligatures]],
2293            [536, []],
2294            [537, []],
2295            [538, [convert_mathindent]],
2296            [539, []],
2297            [540, []],
2298            [541, [convert_allowbreak]],
2299            [542, [convert_mathnumberpos]],
2300            [543, [convert_mathnumberingname]],
2301            [544, [convert_minted]]
2302           ]
2303
2304 revert =  [
2305            [543, [revert_minted]],
2306            [542, [revert_mathnumberingname]],
2307            [541, [revert_mathnumberpos]],
2308            [540, [revert_allowbreak]],
2309            [539, [revert_rotfloat]],
2310            [538, [revert_baselineskip]],
2311            [537, [revert_mathindent]],
2312            [536, [revert_xout]],
2313            [535, [revert_noto]],
2314            [534, [revert_dashligatures]],
2315            [533, [revert_chapterbib]],
2316            [532, [revert_multibib]],
2317            [531, [revert_literalparam]],
2318            [530, [revert_qualicites]],
2319            [529, [revert_bibpackopts]],
2320            [528, [revert_citekeyonly]],
2321            [527, [revert_biblatex]],
2322            [526, [revert_noprefix]],
2323            [525, [revert_plural_refs]],
2324            [524, [revert_labelonly]],
2325            [523, [revert_crimson, revert_cochinealmath]],
2326            [522, [revert_cjkquotes]],
2327            [521, [revert_dynamicquotes]],
2328            [520, [revert_britishquotes, revert_swedishgquotes, revert_frenchquotes, revert_frenchinquotes, revert_russianquotes, revert_swissquotes]],
2329            [519, [revert_plainquote]],
2330            [518, [revert_quotestyle]],
2331            [517, [revert_iopart]],
2332            [516, [revert_quotes]],
2333            [515, []],
2334            [514, [revert_urdu, revert_syriac]],
2335            [513, [revert_amharic, revert_asturian, revert_kannada, revert_khmer]],
2336            [512, [revert_bosnian, revert_friulan, revert_macedonian, revert_piedmontese, revert_romansh]],
2337            [511, [revert_beamer_article_styles]],
2338            [510, [revert_ibranches]],
2339            [509, []],
2340            [508, [revert_microtype]]
2341           ]
2342
2343
2344 if __name__ == "__main__":
2345     pass