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