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