]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx_1_6.py
Reformat lyx2lyx code using ruff
[lyx.git] / lib / lyx2lyx / lyx_1_6.py
1 # This file is part of lyx2lyx
2 # Copyright (C) 2007-2008 The LyX Team <lyx-devel@lists.lyx.org>
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17
18 """Convert files to the file format generated by lyx 1.6"""
19
20 import re
21 import unicodedata
22 import sys, os
23
24 from parser_tools import find_token, find_end_of, find_tokens, get_value
25 from unicode_symbols import unicode_reps
26
27 ####################################################################
28 # Private helper functions
29
30
31 def get_value_string(lines, token, start, end=0, trim=False, default=""):
32     """get_value_string(lines, token, start[[, end], trim, default]) -> string
33
34     Return tokens after token as string, in lines, where
35     token is the first element. When trim is used, the first and last character
36     of the string is trimmed."""
37
38     val = get_value(lines, token, start, end, "")
39     if not val:
40         return default
41     if trim:
42         return val[1:-1]
43     return val
44
45
46 def find_end_of_inset(lines, i):
47     "Find end of inset, where lines[i] is included."
48     return find_end_of(lines, i, "\\begin_inset", "\\end_inset")
49
50
51 # WARNING!
52 # DO NOT do this:
53 #   document.body[i] = wrap_insert_ert(...)
54 # wrap_into_ert may returns a multiline string, which should NOT appear
55 # in document.body. Instead, do something like this:
56 #   subst = wrap_inset_ert(...)
57 #   subst = subst.split('\n')
58 #   document.body[i:i+1] = subst
59 #   i+= len(subst) - 1
60 # where the last statement resets the counter to accord with the added
61 # lines.
62 def wrap_into_ert(string, src, dst):
63     """Within string, replace occurrences of src with dst, wrapped into ERT
64     E.g.: wrap_into_ert('sch\"on', "\\", "\\backslash") is:
65     sch<ERT>\\backslash</ERT>"on"""
66     return string.replace(
67         src,
68         "\n\\begin_inset ERT\nstatus collapsed\n\\begin_layout Standard\n"
69         + dst
70         + "\n\\end_layout\n\\end_inset\n",
71     )
72
73
74 def put_cmd_in_ert(string):
75     for rep in unicode_reps:
76         string = string.replace(rep[1], rep[0].replace("\\\\", "\\"))
77     string = string.replace("\\", "\\backslash\n")
78     string = (
79         "\\begin_inset ERT\nstatus collapsed\n\\begin_layout Standard\n"
80         + string
81         + "\n\\end_layout\n\\end_inset"
82     )
83     return string
84
85
86 def add_to_preamble(document, text):
87     """Add text to the preamble if it is not already there.
88     Only the first line is checked!"""
89
90     if find_token(document.preamble, text[0], 0) != -1:
91         return
92
93     document.preamble.extend(text)
94
95
96 def insert_to_preamble(index, document, text):
97     """Insert text to the preamble at a given line"""
98
99     document.preamble.insert(index, text)
100
101
102 # Convert a LyX length into a LaTeX length
103 def convert_len(len):
104     units = {
105         "text%": "\\backslash\ntextwidth",
106         "col%": "\\backslash\ncolumnwidth",
107         "page%": "\\backslash\npagewidth",
108         "line%": "\\backslash\nlinewidth",
109         "theight%": "\\backslash\ntextheight",
110         "pheight%": "\\backslash\npageheight",
111     }
112
113     # Convert LyX units to LaTeX units
114     for unit in list(units.keys()):
115         if len.find(unit) != -1:
116             len = "%f" % (len2value(len) / 100)
117             len = len.strip("0") + units[unit]
118             break
119
120     return len
121
122
123 # Return the value of len without the unit in numerical form.
124 def len2value(len):
125     result = re.search("([+-]?[0-9.]+)", len)
126     if result:
127         return float(result.group(1))
128     # No number means 1.0
129     return 1.0
130
131
132 # Unfortunately, this doesn't really work, since Standard isn't always default.
133 # But it's as good as we can do right now.
134 def find_default_layout(document, start, end):
135     l = find_token(document.body, "\\begin_layout Standard", start, end)
136     if l == -1:
137         l = find_token(document.body, "\\begin_layout PlainLayout", start, end)
138     if l == -1:
139         l = find_token(document.body, "\\begin_layout Plain Layout", start, end)
140     return l
141
142
143 def get_option(document, m, option, default):
144     l = document.body[m].find(option)
145     val = default
146     if l != -1:
147         val = document.body[m][l:].split('"')[1]
148     return val
149
150
151 def remove_option(document, m, option):
152     l = document.body[m].find(option)
153     if l != -1:
154         val = document.body[m][l:].split('"')[1]
155         document.body[m] = (
156             document.body[m][: l - 1] + document.body[m][l + len(option + '="' + val + '"') :]
157         )
158     return l
159
160
161 def set_option(document, m, option, value):
162     l = document.body[m].find(option)
163     if l != -1:
164         oldval = document.body[m][l:].split('"')[1]
165         l = l + len(option + '="')
166         document.body[m] = document.body[m][:l] + value + document.body[m][l + len(oldval) :]
167     else:
168         document.body[m] = document.body[m][:-1] + " " + option + '="' + value + '">'
169     return l
170
171
172 def extract_argument(line):
173     "Extracts a LaTeX argument from the start of line. Returns (arg, rest)."
174
175     if not line:
176         return (None, "")
177
178     bracere = re.compile(r"(\s*)(.*)")
179     n = bracere.match(line)
180     whitespace = n.group(1)
181     stuff = n.group(2)
182     brace = stuff[:1]
183     if brace != "[" and brace != "{":
184         return (None, line)
185
186     # find closing brace
187     remain = stuff[1:]
188     pos = 0
189     num = 1
190     term = "}"
191     if brace == "[":
192         term = "]"
193     skip = False
194     for c in remain:
195         if skip:
196             skip = False
197         elif c == "\\":
198             skip = True
199         elif c == brace:
200             num += 1
201         elif c == term:
202             num -= 1
203         if c == 0:
204             break
205         pos += 1
206     if num != 0:
207         # We never found the matching brace
208         # So, to be on the safe side, let's just return everything
209         # which will then get wrapped as ERT
210         return (line, "")
211     return (line[: pos + 1], line[pos + 1 :])
212
213
214 def latex2ert(line, isindex):
215     """Converts LaTeX commands into ERT. line may well be a multi-line
216     string when it is returned."""
217     if not line:
218         return line
219
220     retval = ""
221     ## FIXME Escaped \ ??
222     # This regex looks for a LaTeX command---i.e., something of the form
223     # "\alPhaStuFF", or "\X", where X is any character---where the command
224     # may also be preceded by an additional backslash, which is how it would
225     # appear (e.g.) in an InsetIndex.
226     labelre = re.compile(r"(.*?)\\?(\\(?:[a-zA-Z]+|.))(.*)")
227
228     m = labelre.match(line)
229     while m != None:
230         retval += m.group(1)
231         cmd = m.group(2)
232         end = m.group(3)
233
234         while True:
235             (arg, rest) = extract_argument(end)
236             if arg == None:
237                 break
238             cmd += arg
239             end = rest
240         # If we wanted to put labels into an InsetLabel, for example, then we
241         # would just need to test here for cmd == "label" and then take some
242         # appropriate action, i.e., to use arg to get the content and then
243         # wrap it appropriately.
244         cmd = put_cmd_in_ert(cmd)
245         retval += "\n" + cmd + "\n"
246         line = end
247         m = labelre.match(line)
248     # put all remaining braces in ERT
249     line = wrap_into_ert(line, "}", "}")
250     line = wrap_into_ert(line, "{", "{")
251     if isindex:
252         # active character that is not available in all font encodings
253         line = wrap_into_ert(line, "|", "|")
254     retval += line
255     return retval
256
257
258 # Bug 5022....
259 # Might should do latex2ert first, then deal with stuff that DOESN'T
260 # end up inside ERT. That routine could be modified so that it returned
261 # a list of lines, and we could then skip ERT bits and only deal with
262 # the other bits.
263 def latex2lyx(data, isindex):
264     """Takes a string, possibly multi-line, and returns the result of
265     converting LaTeX constructs into LyX constructs. Returns a list of
266     lines, suitable for insertion into document.body.
267     The bool isindex specifies whether we are in an index macro (which
268     has some specific active characters that need to be ERTed)."""
269
270     if not data:
271         return [""]
272     retval = []
273
274     # Convert LaTeX to Unicode
275     # Commands of this sort need to be checked to make sure they are
276     # followed by a non-alpha character, lest we replace too much.
277     hardone = re.compile(r"^\\\\[a-zA-Z]+$")
278
279     for rep in unicode_reps:
280         if hardone.match(rep[0]):
281             pos = 0
282             while True:
283                 pos = data.find(rep[0], pos)
284                 if pos == -1:
285                     break
286                 nextpos = pos + len(rep[0])
287                 if nextpos < len(data) and data[nextpos].isalpha():
288                     # not the end of that command
289                     pos = nextpos
290                     continue
291                 data = data[:pos] + rep[1] + data[nextpos:]
292                 pos = nextpos
293         else:
294             data = data.replace(rep[0], rep[1])
295
296     # Generic
297     # \" -> ":
298     data = wrap_into_ert(data, r"\"", '"')
299     # \\ -> \:
300     data = data.replace("\\\\", "\\")
301
302     # Math:
303     mathre = re.compile(r"^(.*?)(\$.*?\$)(.*)")
304     lines = data.split("\n")
305     for line in lines:
306         # document.warning("LINE: " + line)
307         # document.warning(str(i) + ":" + document.body[i])
308         # document.warning("LAST: " + document.body[-1])
309         g = line
310         m = mathre.match(g)
311         while m != None:
312             s = m.group(1)
313             f = m.group(2).replace("\\\\", "\\")
314             g = m.group(3)
315             if s:
316                 # this is non-math!
317                 s = latex2ert(s, isindex)
318                 subst = s.split("\n")
319                 retval += subst
320             retval.append("\\begin_inset Formula " + f)
321             retval.append("\\end_inset")
322             m = mathre.match(g)
323         # Handle whatever is left, which is just text
324         g = latex2ert(g, isindex)
325         subst = g.split("\n")
326         retval += subst
327     return retval
328
329
330 def lyxline2latex(document, line, inert):
331     "Convert some LyX stuff into corresponding LaTeX stuff line-wise, as best we can."
332     if line.startswith("\\begin_inset Formula"):
333         line = line[20:]
334     elif line.startswith("\\begin_inset Quotes"):
335         # For now, we do a very basic reversion. Someone who understands
336         # quotes is welcome to fix it up.
337         qtype = line[20:].strip()
338         # lang = qtype[0]
339         side = qtype[1]
340         dbls = qtype[2]
341         if side == "l":
342             if dbls == "d":
343                 line = "``"
344             else:
345                 line = "`"
346         else:
347             if dbls == "d":
348                 line = "''"
349             else:
350                 line = "'"
351     elif (
352         line.isspace()
353         or line.startswith("\\begin_layout")
354         or line.startswith("\\end_layout")
355         or line.startswith("\\begin_inset")
356         or line.startswith("\\end_inset")
357         or line.startswith("\\lang")
358         or line.strip() == "status collapsed"
359         or line.strip() == "status open"
360     ):
361         # skip all that stuff
362         return ""
363
364     # this needs to be added to the preamble because of cases like
365     # \textmu, \textbackslash, etc.
366     add_to_preamble(
367         document,
368         [
369             "% added by lyx2lyx for converted entries",
370             "\\@ifundefined{textmu}",
371             " {\\usepackage{textcomp}}{}",
372         ],
373     )
374     # a lossless reversion is not possible
375     # try at least to handle some common insets and settings
376     if inert:
377         line = line.replace(r"\backslash", "\\")
378     else:
379         line = line.replace("&", "\\&{}")
380         line = line.replace("#", "\\#{}")
381         line = line.replace("^", "\\^{}")
382         line = line.replace("%", "\\%{}")
383         line = line.replace("_", "\\_{}")
384         line = line.replace("$", "\\${}")
385
386         # Do the LyX text --> LaTeX conversion
387         for rep in unicode_reps:
388             line = line.replace(rep[1], rep[0].replace("\\\\", "\\") + "{}")
389             line = line.replace(r"\backslash", r"\textbackslash{}")
390             line = line.replace(r"\series bold", r"\bfseries{}").replace(
391                 r"\series default", r"\mdseries{}"
392             )
393             line = line.replace(r"\shape italic", r"\itshape{}").replace(
394                 r"\shape smallcaps", r"\scshape{}"
395             )
396             line = line.replace(r"\shape slanted", r"\slshape{}").replace(
397                 r"\shape default", r"\upshape{}"
398             )
399             line = line.replace(r"\emph on", r"\em{}").replace(r"\emph default", r"\em{}")
400             line = line.replace(r"\noun on", r"\scshape{}").replace(
401                 r"\noun default", r"\upshape{}"
402             )
403             line = line.replace(r"\bar under", r"\underbar{").replace(r"\bar default", r"}")
404             line = line.replace(r"\family sans", r"\sffamily{}").replace(
405                 r"\family default", r"\normalfont{}"
406             )
407             line = line.replace(r"\family typewriter", r"\ttfamily{}").replace(
408                 r"\family roman", r"\rmfamily{}"
409             )
410             line = line.replace(r"\InsetSpace ", r"").replace(r"\SpecialChar ", r"")
411     return line
412
413
414 def lyx2latex(document, lines):
415     "Convert some LyX stuff into corresponding LaTeX stuff, as best we can."
416     # clean up multiline stuff
417     content = ""
418     ert_end = 0
419
420     for curline in range(len(lines)):
421         line = lines[curline]
422         if line.startswith("\\begin_inset ERT"):
423             # We don't want to replace things inside ERT, so figure out
424             # where the end of the inset is.
425             ert_end = find_end_of_inset(lines, curline + 1)
426             continue
427         inert = ert_end >= curline
428         content += lyxline2latex(document, lines[curline], inert)
429
430     return content
431
432
433 ####################################################################
434
435
436 def convert_ltcaption(document):
437     i = 0
438     while True:
439         i = find_token(document.body, "\\begin_inset Tabular", i)
440         if i == -1:
441             return
442         j = find_end_of_inset(document.body, i + 1)
443         if j == -1:
444             document.warning("Malformed LyX document: Could not find end of tabular.")
445             i += 1
446             continue
447
448         nrows = int(document.body[i + 1].split('"')[3])
449         ncols = int(document.body[i + 1].split('"')[5])
450
451         m = i + 1
452         for k in range(nrows):
453             m = find_token(document.body, "<row", m)
454             r = m
455             caption = "false"
456             for k in range(ncols):
457                 m = find_token(document.body, "<cell", m)
458                 if k == 0:
459                     mend = find_token(document.body, "</cell>", m + 1)
460                     # first look for caption insets
461                     mcap = find_token(document.body, "\\begin_inset Caption", m + 1, mend)
462                     # then look for ERT captions
463                     if mcap == -1:
464                         mcap = find_token(document.body, "caption", m + 1, mend)
465                         if mcap > -1:
466                             mcap = find_token(document.body, "\\backslash", mcap - 1, mcap)
467                     if mcap > -1:
468                         caption = "true"
469                 if caption == "true":
470                     if k == 0:
471                         set_option(document, r, "caption", "true")
472                         set_option(document, m, "multicolumn", "1")
473                         set_option(document, m, "bottomline", "false")
474                         set_option(document, m, "topline", "false")
475                         set_option(document, m, "rightline", "false")
476                         set_option(document, m, "leftline", "false")
477                         # j = find_end_of_inset(document.body, j + 1)
478                     else:
479                         set_option(document, m, "multicolumn", "2")
480                 m = m + 1
481             m = m + 1
482
483         i = j + 1
484
485
486 # FIXME Use of wrap_into_ert can confuse lyx2lyx
487 def revert_ltcaption(document):
488     i = 0
489     while True:
490         i = find_token(document.body, "\\begin_inset Tabular", i)
491         if i == -1:
492             return
493         j = find_end_of_inset(document.body, i + 1)
494         if j == -1:
495             document.warning("Malformed LyX document: Could not find end of tabular.")
496             i += 1
497             continue
498
499         m = i + 1
500         nrows = int(document.body[i + 1].split('"')[3])
501         ncols = int(document.body[i + 1].split('"')[5])
502
503         for k in range(nrows):
504             m = find_token(document.body, "<row", m)
505             caption = get_option(document, m, "caption", "false")
506             if caption == "true":
507                 remove_option(document, m, "caption")
508                 for k in range(ncols):
509                     m = find_token(document.body, "<cell", m)
510                     remove_option(document, m, "multicolumn")
511                     if k == 0:
512                         m = find_token(document.body, "\\begin_inset Caption", m)
513                         if m == -1:
514                             return
515                         m = find_end_of_inset(document.body, m + 1)
516                         document.body[m] += wrap_into_ert("", "", "\\backslash\n\\backslash\n%")
517                     m = m + 1
518             m = m + 1
519         i = j + 1
520
521
522 def convert_tablines(document):
523     i = 0
524     while True:
525         i = find_token(document.body, "\\begin_inset Tabular", i)
526         if i == -1:
527             # LyX 1.3 inserted an extra space between \begin_inset
528             # and Tabular so let us try if this is the case and fix it.
529             i = find_token(document.body, "\\begin_inset  Tabular", i)
530             if i == -1:
531                 return
532             else:
533                 document.body[i] = "\\begin_inset Tabular"
534         j = find_end_of_inset(document.body, i + 1)
535         if j == -1:
536             document.warning("Malformed LyX document: Could not find end of tabular.")
537             i += 1
538             continue
539
540         m = i + 1
541         nrows = int(document.body[i + 1].split('"')[3])
542         ncols = int(document.body[i + 1].split('"')[5])
543
544         col_info = []
545         for k in range(ncols):
546             m = find_token(document.body, "<column", m)
547             left = get_option(document, m, "leftline", "false")
548             right = get_option(document, m, "rightline", "false")
549             col_info.append([left, right])
550             remove_option(document, m, "leftline")
551             remove_option(document, m, "rightline")
552             m = m + 1
553
554         row_info = []
555         for k in range(nrows):
556             m = find_token(document.body, "<row", m)
557             top = get_option(document, m, "topline", "false")
558             bottom = get_option(document, m, "bottomline", "false")
559             row_info.append([top, bottom])
560             remove_option(document, m, "topline")
561             remove_option(document, m, "bottomline")
562             m = m + 1
563
564         m = i + 1
565         mc_info = []
566         for k in range(nrows * ncols):
567             m = find_token(document.body, "<cell", m)
568             mc_info.append(get_option(document, m, "multicolumn", "0"))
569             m = m + 1
570         m = i + 1
571         for l in range(nrows):
572             for k in range(ncols):
573                 m = find_token(document.body, "<cell", m)
574                 if mc_info[l * ncols + k] == "0":
575                     r = set_option(document, m, "topline", row_info[l][0])
576                     r = set_option(document, m, "bottomline", row_info[l][1])
577                     r = set_option(document, m, "leftline", col_info[k][0])
578                     r = set_option(document, m, "rightline", col_info[k][1])
579                 elif mc_info[l * ncols + k] == "1":
580                     s = k + 1
581                     while s < ncols and mc_info[l * ncols + s] == "2":
582                         s = s + 1
583                     if s < ncols and mc_info[l * ncols + s] != "1":
584                         r = set_option(document, m, "rightline", col_info[k][1])
585                     if k > 0 and mc_info[l * ncols + k - 1] == "0":
586                         r = set_option(document, m, "leftline", col_info[k][0])
587                 m = m + 1
588         i = j + 1
589
590
591 def revert_tablines(document):
592     i = 0
593     while True:
594         i = find_token(document.body, "\\begin_inset Tabular", i)
595         if i == -1:
596             return
597         j = find_end_of_inset(document.body, i)
598         if j == -1:
599             document.warning("Malformed LyX document: Could not find end of tabular.")
600             i += 1
601             continue
602
603         m = i + 1
604         nrows = int(document.body[i + 1].split('"')[3])
605         ncols = int(document.body[i + 1].split('"')[5])
606
607         lines = []
608         for k in range(nrows * ncols):
609             m = find_token(document.body, "<cell", m)
610             top = get_option(document, m, "topline", "false")
611             bottom = get_option(document, m, "bottomline", "false")
612             left = get_option(document, m, "leftline", "false")
613             right = get_option(document, m, "rightline", "false")
614             lines.append([top, bottom, left, right])
615             m = m + 1
616
617         # we will want to ignore longtable captions
618         m = i + 1
619         caption_info = []
620         for k in range(nrows):
621             m = find_token(document.body, "<row", m)
622             caption = get_option(document, m, "caption", "false")
623             caption_info.append([caption])
624             m = m + 1
625
626         m = i + 1
627         col_info = []
628         for k in range(ncols):
629             m = find_token(document.body, "<column", m)
630             left = "true"
631             for l in range(nrows):
632                 left = lines[l * ncols + k][2]
633                 if left == "false" and caption_info[l] == "false":
634                     break
635             set_option(document, m, "leftline", left)
636             right = "true"
637             for l in range(nrows):
638                 right = lines[l * ncols + k][3]
639                 if right == "false" and caption_info[l] == "false":
640                     break
641             set_option(document, m, "rightline", right)
642             m = m + 1
643
644         row_info = []
645         for k in range(nrows):
646             m = find_token(document.body, "<row", m)
647             top = "true"
648             for l in range(ncols):
649                 top = lines[k * ncols + l][0]
650                 if top == "false":
651                     break
652             if caption_info[k] == "false":
653                 top = "false"
654             set_option(document, m, "topline", top)
655             bottom = "true"
656             for l in range(ncols):
657                 bottom = lines[k * ncols + l][1]
658                 if bottom == "false":
659                     break
660             if caption_info[k] == "false":
661                 bottom = "false"
662             set_option(document, m, "bottomline", bottom)
663             m = m + 1
664
665         i = j + 1
666
667
668 def fix_wrong_tables(document):
669     i = 0
670     while True:
671         i = find_token(document.body, "\\begin_inset Tabular", i)
672         if i == -1:
673             return
674         j = find_end_of_inset(document.body, i + 1)
675         if j == -1:
676             document.warning("Malformed LyX document: Could not find end of tabular.")
677             i += 1
678             continue
679
680         m = i + 1
681         nrows = int(document.body[i + 1].split('"')[3])
682         ncols = int(document.body[i + 1].split('"')[5])
683
684         for l in range(nrows):
685             prev_multicolumn = 0
686             for k in range(ncols):
687                 m = find_token(document.body, "<cell", m)
688
689                 if document.body[m].find("multicolumn") != -1:
690                     multicol_cont = int(document.body[m].split('"')[1])
691
692                     if multicol_cont == 2 and (k == 0 or prev_multicolumn == 0):
693                         document.body[m] = document.body[m][:5] + document.body[m][21:]
694                         prev_multicolumn = 0
695                     else:
696                         prev_multicolumn = multicol_cont
697                 else:
698                     prev_multicolumn = 0
699
700         i = j + 1
701
702
703 def close_begin_deeper(document):
704     i = 0
705     depth = 0
706     while True:
707         i = find_tokens(document.body, ["\\begin_deeper", "\\end_deeper"], i)
708
709         if i == -1:
710             break
711
712         if document.body[i][:13] == "\\begin_deeper":
713             depth += 1
714         else:
715             depth -= 1
716
717         i += 1
718
719     document.body[-2:-2] = ["\\end_deeper" for i in range(depth)]
720
721
722 def long_charstyle_names(document):
723     i = 0
724     while True:
725         i = find_token(document.body, "\\begin_inset CharStyle", i)
726         if i == -1:
727             return
728         document.body[i] = document.body[i].replace("CharStyle ", "CharStyle CharStyle:")
729         i += 1
730
731
732 def revert_long_charstyle_names(document):
733     i = 0
734     while True:
735         i = find_token(document.body, "\\begin_inset CharStyle", i)
736         if i == -1:
737             return
738         document.body[i] = document.body[i].replace("CharStyle CharStyle:", "CharStyle ")
739         i += 1
740
741
742 def axe_show_label(document):
743     i = 0
744     while True:
745         i = find_token(document.body, "\\begin_inset CharStyle", i)
746         if i == -1:
747             return
748         if document.body[i + 1].find("show_label") != -1:
749             if document.body[i + 1].find("true") != -1:
750                 document.body[i + 1] = "status open"
751                 del document.body[i + 2]
752             else:
753                 if document.body[i + 1].find("false") != -1:
754                     document.body[i + 1] = "status collapsed"
755                     del document.body[i + 2]
756                 else:
757                     document.warning(
758                         "Malformed LyX document: show_label neither false nor true."
759                     )
760         else:
761             document.warning("Malformed LyX document: show_label missing in CharStyle.")
762
763         i += 1
764
765
766 def revert_show_label(document):
767     i = 0
768     while True:
769         i = find_token(document.body, "\\begin_inset CharStyle", i)
770         if i == -1:
771             return
772         if document.body[i + 1].find("status open") != -1:
773             document.body.insert(i + 1, "show_label true")
774         else:
775             if document.body[i + 1].find("status collapsed") != -1:
776                 document.body.insert(i + 1, "show_label false")
777             else:
778                 document.warning("Malformed LyX document: no legal status line in CharStyle.")
779         i += 1
780
781
782 def revert_begin_modules(document):
783     i = 0
784     while True:
785         i = find_token(document.header, "\\begin_modules", i)
786         if i == -1:
787             return
788         j = find_end_of(document.header, i, "\\begin_modules", "\\end_modules")
789         if j == -1:
790             # this should not happen
791             break
792         document.header[i : j + 1] = []
793
794
795 def convert_flex(document):
796     "Convert CharStyle to Flex"
797     i = 0
798     while True:
799         i = find_token(document.body, "\\begin_inset CharStyle", i)
800         if i == -1:
801             return
802         document.body[i] = document.body[i].replace(
803             "\\begin_inset CharStyle", "\\begin_inset Flex"
804         )
805
806
807 def revert_flex(document):
808     "Revert Flex to CharStyle"
809     i = 0
810     while True:
811         i = find_token(document.body, "\\begin_inset Flex", i)
812         if i == -1:
813             return
814         document.body[i] = document.body[i].replace(
815             "\\begin_inset Flex", "\\begin_inset CharStyle"
816         )
817
818
819 def revert_pdf_options(document):
820     "Revert PDF options for hyperref."
821     # store the PDF options and delete the entries from the Lyx file
822     i = 0
823     hyperref = False
824     title = ""
825     author = ""
826     subject = ""
827     keywords = ""
828     bookmarks = ""
829     bookmarksnumbered = ""
830     bookmarksopen = ""
831     bookmarksopenlevel = ""
832     breaklinks = ""
833     pdfborder = ""
834     colorlinks = ""
835     backref = ""
836     pagebackref = ""
837     pagemode = ""
838     otheroptions = ""
839     i = find_token(document.header, "\\use_hyperref", i)
840     if i != -1:
841         hyperref = get_value(document.header, "\\use_hyperref", i) == "true"
842         del document.header[i]
843     i = find_token(document.header, "\\pdf_store_options", i)
844     if i != -1:
845         del document.header[i]
846     i = find_token(document.header, "\\pdf_title", 0)
847     if i != -1:
848         title = get_value_string(document.header, "\\pdf_title", 0, 0, True)
849         title = " pdftitle={" + title + "}"
850         del document.header[i]
851     i = find_token(document.header, "\\pdf_author", 0)
852     if i != -1:
853         author = get_value_string(document.header, "\\pdf_author", 0, 0, True)
854         if title == "":
855             author = " pdfauthor={" + author + "}"
856         else:
857             author = ",\n pdfauthor={" + author + "}"
858         del document.header[i]
859     i = find_token(document.header, "\\pdf_subject", 0)
860     if i != -1:
861         subject = get_value_string(document.header, "\\pdf_subject", 0, 0, True)
862         if title == "" and author == "":
863             subject = " pdfsubject={" + subject + "}"
864         else:
865             subject = ",\n pdfsubject={" + subject + "}"
866         del document.header[i]
867     i = find_token(document.header, "\\pdf_keywords", 0)
868     if i != -1:
869         keywords = get_value_string(document.header, "\\pdf_keywords", 0, 0, True)
870         if title == "" and author == "" and subject == "":
871             keywords = " pdfkeywords={" + keywords + "}"
872         else:
873             keywords = ",\n pdfkeywords={" + keywords + "}"
874         del document.header[i]
875     i = find_token(document.header, "\\pdf_bookmarks", 0)
876     if i != -1:
877         bookmarks = get_value_string(document.header, "\\pdf_bookmarks", 0)
878         bookmarks = ",\n bookmarks=" + bookmarks
879         del document.header[i]
880     i = find_token(document.header, "\\pdf_bookmarksnumbered", i)
881     if i != -1:
882         bookmarksnumbered = get_value_string(document.header, "\\pdf_bookmarksnumbered", 0)
883         bookmarksnumbered = ",\n bookmarksnumbered=" + bookmarksnumbered
884         del document.header[i]
885     i = find_token(document.header, "\\pdf_bookmarksopen", i)
886     if i != -1:
887         bookmarksopen = get_value_string(document.header, "\\pdf_bookmarksopen", 0)
888         bookmarksopen = ",\n bookmarksopen=" + bookmarksopen
889         del document.header[i]
890     i = find_token(document.header, "\\pdf_bookmarksopenlevel", i)
891     if i != -1:
892         bookmarksopenlevel = get_value_string(
893             document.header, "\\pdf_bookmarksopenlevel", 0, 0, True
894         )
895         bookmarksopenlevel = ",\n bookmarksopenlevel=" + bookmarksopenlevel
896         del document.header[i]
897     i = find_token(document.header, "\\pdf_breaklinks", i)
898     if i != -1:
899         breaklinks = get_value_string(document.header, "\\pdf_breaklinks", 0)
900         breaklinks = ",\n breaklinks=" + breaklinks
901         del document.header[i]
902     i = find_token(document.header, "\\pdf_pdfborder", i)
903     if i != -1:
904         pdfborder = get_value_string(document.header, "\\pdf_pdfborder", 0)
905         if pdfborder == "true":
906             pdfborder = ",\n pdfborder={0 0 0}"
907         else:
908             pdfborder = ",\n pdfborder={0 0 1}"
909         del document.header[i]
910     i = find_token(document.header, "\\pdf_colorlinks", i)
911     if i != -1:
912         colorlinks = get_value_string(document.header, "\\pdf_colorlinks", 0)
913         colorlinks = ",\n colorlinks=" + colorlinks
914         del document.header[i]
915     i = find_token(document.header, "\\pdf_backref", i)
916     if i != -1:
917         backref = get_value_string(document.header, "\\pdf_backref", 0)
918         backref = ",\n backref=" + backref
919         del document.header[i]
920     i = find_token(document.header, "\\pdf_pagebackref", i)
921     if i != -1:
922         pagebackref = get_value_string(document.header, "\\pdf_pagebackref", 0)
923         pagebackref = ",\n pagebackref=" + pagebackref
924         del document.header[i]
925     i = find_token(document.header, "\\pdf_pagemode", 0)
926     if i != -1:
927         pagemode = get_value_string(document.header, "\\pdf_pagemode", 0)
928         pagemode = ",\n pdfpagemode=" + pagemode
929         del document.header[i]
930     i = find_token(document.header, "\\pdf_quoted_options", 0)
931     if i != -1:
932         otheroptions = get_value_string(document.header, "\\pdf_quoted_options", 0, 0, True)
933         if title == "" and author == "" and subject == "" and keywords == "":
934             otheroptions = " " + otheroptions
935         else:
936             otheroptions = ",\n " + otheroptions
937         del document.header[i]
938
939     # write to the preamble when hyperref was used
940     if hyperref == True:
941         # preamble write preparations
942         # bookmark numbers are only output when they are turned on
943         if bookmarksopen == ",\n bookmarksopen=true":
944             bookmarksopen = bookmarksopen + bookmarksopenlevel
945         if bookmarks == ",\n bookmarks=true":
946             bookmarks = bookmarks + bookmarksnumbered + bookmarksopen
947         else:
948             bookmarks = bookmarks
949         # hypersetup is only output when there are things to be set up
950         setupstart = "\\hypersetup{%\n"
951         setupend = " }\n"
952         if (
953             otheroptions == ""
954             and title == ""
955             and author == ""
956             and subject == ""
957             and keywords == ""
958         ):
959             setupstart = ""
960             setupend = ""
961         # write the preamble
962         # babel must be loaded before hyperref and hyperref the first part
963         # of the preamble, like in LyX 1.6
964         insert_to_preamble(
965             0,
966             document,
967             "% Commands inserted by lyx2lyx for PDF properties\n"
968             + "\\usepackage{babel}\n"
969             + "\\usepackage[unicode=true"
970             + bookmarks
971             + breaklinks
972             + pdfborder
973             + backref
974             + pagebackref
975             + colorlinks
976             + pagemode
977             + "]\n"
978             + " {hyperref}\n"
979             + setupstart
980             + title
981             + author
982             + subject
983             + keywords
984             + otheroptions
985             + setupend,
986         )
987
988
989 def remove_inzip_options(document):
990     "Remove inzipName and embed options from the Graphics inset"
991     i = 0
992     while True:
993         i = find_token(document.body, "\\begin_inset Graphics", i)
994         if i == -1:
995             return
996         j = find_end_of_inset(document.body, i + 1)
997         if j == -1:
998             # should not happen
999             document.warning("Malformed LyX document: Could not find end of graphics inset.")
1000             i += 1
1001             continue
1002         # If there's a inzip param, just remove that
1003         k = find_token(document.body, "\tinzipName", i + 1, j)
1004         if k != -1:
1005             del document.body[k]
1006             # embed option must follow the inzipName option
1007             del document.body[k + 1]
1008         i = i + 1
1009
1010
1011 def convert_inset_command(document):
1012     r"""
1013     Convert:
1014         \begin_inset LatexCommand cmd
1015     to
1016         \begin_inset CommandInset InsetType
1017         LatexCommand cmd
1018     """
1019     i = 0
1020     while True:
1021         i = find_token(document.body, "\\begin_inset LatexCommand", i)
1022         if i == -1:
1023             return
1024         line = document.body[i]
1025         r = re.compile(r"\\begin_inset LatexCommand (.*)$")
1026         m = r.match(line)
1027         cmdName = m.group(1)
1028         insetName = ""
1029         # this is adapted from factory.cpp
1030         if cmdName[0:4].lower() == "cite":
1031             insetName = "citation"
1032         elif cmdName == "url" or cmdName == "htmlurl":
1033             insetName = "url"
1034         elif cmdName[-3:] == "ref":
1035             insetName = "ref"
1036         elif cmdName == "tableofcontents":
1037             insetName = "toc"
1038         elif cmdName == "printnomenclature":
1039             insetName = "nomencl_print"
1040         elif cmdName == "printindex":
1041             insetName = "index_print"
1042         else:
1043             insetName = cmdName
1044         insertion = [
1045             "\\begin_inset CommandInset " + insetName,
1046             "LatexCommand " + cmdName,
1047         ]
1048         document.body[i : i + 1] = insertion
1049
1050
1051 def revert_inset_command(document):
1052     r"""
1053     Convert:
1054         \begin_inset CommandInset InsetType
1055         LatexCommand cmd
1056     to
1057         \begin_inset LatexCommand cmd
1058     Some insets may end up being converted to insets earlier versions of LyX
1059     will not be able to recognize. Not sure what to do about that.
1060     """
1061     i = 0
1062     while True:
1063         i = find_token(document.body, "\\begin_inset CommandInset", i)
1064         if i == -1:
1065             return
1066         nextline = document.body[i + 1]
1067         r = re.compile(r"LatexCommand\s+(.*)$")
1068         m = r.match(nextline)
1069         if not m:
1070             document.warning(
1071                 "Malformed LyX document: Missing LatexCommand in " + document.body[i] + "."
1072             )
1073             i += 1
1074             continue
1075         cmdName = m.group(1)
1076         insertion = ["\\begin_inset LatexCommand " + cmdName]
1077         document.body[i : i + 2] = insertion
1078
1079
1080 def convert_wrapfig_options(document):
1081     "Convert optional options for wrap floats (wrapfig)."
1082     # adds the tokens "lines", "placement", and "overhang"
1083     i = 0
1084     while True:
1085         i = find_token(document.body, "\\begin_inset Wrap figure", i)
1086         if i == -1:
1087             return
1088         document.body.insert(i + 1, "lines 0")
1089         j = find_token(document.body, "placement", i)
1090         # placement can be already set or not; if not, set it
1091         if j == i + 2:
1092             document.body.insert(i + 3, "overhang 0col%")
1093         else:
1094             document.body.insert(i + 2, "placement o")
1095             document.body.insert(i + 3, "overhang 0col%")
1096         i = i + 1
1097
1098
1099 def revert_wrapfig_options(document):
1100     "Revert optional options for wrap floats (wrapfig)."
1101     i = 0
1102     while True:
1103         i = find_token(document.body, "\\begin_inset Wrap figure", i)
1104         if i == -1:
1105             return
1106         j = find_end_of_inset(document.body, i)
1107         if j == -1:
1108             document.warning("Can't find end of Wrap inset at line " + str(i))
1109             i += 1
1110             continue
1111         k = find_default_layout(document, i, j)
1112         if k == -1:
1113             document.warning("Can't find default layout for Wrap figure!")
1114             i = j
1115             continue
1116         # Options should be between i and k now
1117         l = find_token(document.body, "lines", i, k)
1118         if l == -1:
1119             document.warning("Can't find lines option for Wrap figure!")
1120             i = k
1121             continue
1122         m = find_token(document.body, "overhang", i + 1, k)
1123         if m == -1:
1124             document.warning(
1125                 "Malformed LyX document: Couldn't find overhang parameter of wrap float!"
1126             )
1127             i = k
1128             continue
1129         # Do these in reverse order
1130         del document.body[m]
1131         del document.body[l]
1132         i = k
1133
1134
1135 def convert_latexcommand_index(document):
1136     "Convert from LatexCommand form to collapsible form."
1137     i = 0
1138     r1 = re.compile('name "(.*)"')
1139     while True:
1140         i = find_token(document.body, "\\begin_inset CommandInset index", i)
1141         if i == -1:
1142             return
1143         if document.body[i + 1] != "LatexCommand index":  # Might also be index_print
1144             i += 1
1145             continue
1146         j = find_end_of_inset(document.body, i + 1)
1147         if j == -1:
1148             document.warning("Unable to find end of index inset at line " + str(i) + "!")
1149             i += 2
1150             continue
1151         m = r1.match(document.body[i + 2])
1152         if m == None:
1153             document.warning("Unable to match: " + document.body[i + 2])
1154             # this can happen with empty index insets!
1155             linelist = [""]
1156         else:
1157             fullcontent = m.group(1)
1158             linelist = latex2lyx(fullcontent, True)
1159         # document.warning(fullcontent)
1160
1161         linelist = (
1162             ["\\begin_inset Index", "status collapsed", "\\begin_layout Standard", ""]
1163             + linelist
1164             + ["\\end_layout"]
1165         )
1166         document.body[i:j] = linelist
1167         i += len(linelist) - (j - i)
1168
1169
1170 def revert_latexcommand_index(document):
1171     "Revert from collapsible form to LatexCommand form."
1172     i = 0
1173     while True:
1174         i = find_token(document.body, "\\begin_inset Index", i)
1175         if i == -1:
1176             return
1177         j = find_end_of_inset(document.body, i + 1)
1178         if j == -1:
1179             return
1180
1181         content = lyx2latex(document, document.body[i:j])
1182         # escape quotes
1183         content = content.replace('"', r"\"")
1184         document.body[i:j] = [
1185             "\\begin_inset CommandInset index",
1186             "LatexCommand index",
1187             "name " + '"' + content + '"',
1188             "",
1189         ]
1190         i += 5
1191
1192
1193 def revert_wraptable(document):
1194     "Revert wrap table to wrap figure."
1195     i = 0
1196     while True:
1197         i = find_token(document.body, "\\begin_inset Wrap table", i)
1198         if i == -1:
1199             return
1200         document.body[i] = document.body[i].replace(
1201             "\\begin_inset Wrap table", "\\begin_inset Wrap figure"
1202         )
1203         i = i + 1
1204
1205
1206 def revert_vietnamese(document):
1207     "Set language Vietnamese to English"
1208     # Set document language from Vietnamese to English
1209     i = 0
1210     if document.language == "vietnamese":
1211         document.language = "english"
1212         i = find_token(document.header, "\\language", 0)
1213         if i != -1:
1214             document.header[i] = "\\language english"
1215     j = 0
1216     while True:
1217         j = find_token(document.body, "\\lang vietnamese", j)
1218         if j == -1:
1219             return
1220         document.body[j] = document.body[j].replace("\\lang vietnamese", "\\lang english")
1221         j = j + 1
1222
1223
1224 def convert_japanese_cjk(document):
1225     "Set language japanese to japanese-cjk"
1226     # Set document language from japanese-plain to japanese
1227     i = 0
1228     if document.language == "japanese":
1229         document.language = "japanese-cjk"
1230         i = find_token(document.header, "\\language", 0)
1231         if i != -1:
1232             document.header[i] = "\\language japanese-cjk"
1233     j = 0
1234     while True:
1235         j = find_token(document.body, "\\lang japanese", j)
1236         if j == -1:
1237             return
1238         document.body[j] = document.body[j].replace("\\lang japanese", "\\lang japanese-cjk")
1239         j = j + 1
1240
1241
1242 def revert_japanese(document):
1243     "Set language japanese-plain to japanese"
1244     # Set document language from japanese-plain to japanese
1245     i = 0
1246     if document.language == "japanese-plain":
1247         document.language = "japanese"
1248         i = find_token(document.header, "\\language", 0)
1249         if i != -1:
1250             document.header[i] = "\\language japanese"
1251     j = 0
1252     while True:
1253         j = find_token(document.body, "\\lang japanese-plain", j)
1254         if j == -1:
1255             return
1256         document.body[j] = document.body[j].replace("\\lang japanese-plain", "\\lang japanese")
1257         j = j + 1
1258
1259
1260 def revert_japanese_cjk(document):
1261     "Set language japanese-cjk to japanese"
1262     # Set document language from japanese-plain to japanese
1263     i = 0
1264     if document.language == "japanese-cjk":
1265         document.language = "japanese"
1266         i = find_token(document.header, "\\language", 0)
1267         if i != -1:
1268             document.header[i] = "\\language japanese"
1269     j = 0
1270     while True:
1271         j = find_token(document.body, "\\lang japanese-cjk", j)
1272         if j == -1:
1273             return
1274         document.body[j] = document.body[j].replace("\\lang japanese-cjk", "\\lang japanese")
1275         j = j + 1
1276
1277
1278 def revert_japanese_encoding(document):
1279     "Set input encoding form EUC-JP-plain to EUC-JP etc."
1280     # Set input encoding form EUC-JP-plain to EUC-JP etc.
1281     i = 0
1282     i = find_token(document.header, "\\inputencoding EUC-JP-plain", 0)
1283     if i != -1:
1284         document.header[i] = "\\inputencoding EUC-JP"
1285     j = 0
1286     j = find_token(document.header, "\\inputencoding JIS-plain", 0)
1287     if j != -1:
1288         document.header[j] = "\\inputencoding JIS"
1289     k = 0
1290     k = find_token(document.header, "\\inputencoding SJIS-plain", 0)
1291     if k != -1:  # convert to UTF8 since there is currently no SJIS encoding
1292         document.header[k] = "\\inputencoding UTF8"
1293
1294
1295 def revert_inset_info(document):
1296     "Replace info inset with its content"
1297     i = 0
1298     while True:
1299         i = find_token(document.body, "\\begin_inset Info", i)
1300         if i == -1:
1301             return
1302         j = find_end_of_inset(document.body, i + 1)
1303         if j == -1:
1304             # should not happen
1305             document.warning("Malformed LyX document: Could not find end of Info inset.")
1306             i += 1
1307             continue
1308         type = "unknown"
1309         arg = ""
1310         for k in range(i, j + 1):
1311             if document.body[k].startswith("arg"):
1312                 arg = document.body[k][3:].strip()
1313                 # remove embracing quotation marks
1314                 if arg[0] == '"':
1315                     arg = arg[1:]
1316                 if arg[len(arg) - 1] == '"':
1317                     arg = arg[: len(arg) - 1]
1318                 # \" to straight quote
1319                 arg = arg.replace(r"\"", '"')
1320                 # \ to \backslash
1321                 arg = arg.replace(r"\\", "\\backslash\n")
1322             if document.body[k].startswith("type"):
1323                 type = document.body[k][4:].strip().strip('"')
1324         # I think there is a newline after \\end_inset, which should be removed.
1325         if document.body[j + 1].strip() == "":
1326             document.body[i : (j + 2)] = [type + ":" + arg]
1327         else:
1328             document.body[i : (j + 1)] = [type + ":" + arg]
1329
1330
1331 def convert_pdf_options(document):
1332     # Set the pdfusetitle tag, delete the pdf_store_options,
1333     # set quotes for bookmarksopenlevel"
1334     has_hr = get_value(document.header, "\\use_hyperref", 0, default="0")
1335     if has_hr == "1":
1336         k = find_token(document.header, "\\use_hyperref", 0)
1337         document.header.insert(k + 1, "\\pdf_pdfusetitle true")
1338     k = find_token(document.header, "\\pdf_store_options", 0)
1339     if k != -1:
1340         del document.header[k]
1341     i = find_token(document.header, "\\pdf_bookmarksopenlevel", k)
1342     if i == -1:
1343         return
1344     document.header[i] = document.header[i].replace('"', "")
1345
1346
1347 def revert_pdf_options_2(document):
1348     # reset the pdfusetitle tag, set quotes for bookmarksopenlevel"
1349     k = find_token(document.header, "\\use_hyperref", 0)
1350     i = find_token(document.header, "\\pdf_pdfusetitle", k)
1351     if i != -1:
1352         del document.header[i]
1353     i = find_token(document.header, "\\pdf_bookmarksopenlevel", k)
1354     if i == -1:
1355         return
1356     values = document.header[i].split()
1357     values[1] = ' "' + values[1] + '"'
1358     document.header[i] = "".join(values)
1359
1360
1361 def convert_htmlurl(document):
1362     'Convert "htmlurl" to "href" insets for docbook'
1363     if document.backend != "docbook":
1364         return
1365     i = 0
1366     while True:
1367         i = find_token(document.body, "\\begin_inset CommandInset url", i)
1368         if i == -1:
1369             return
1370         document.body[i] = "\\begin_inset CommandInset href"
1371         document.body[i + 1] = "LatexCommand href"
1372         i = i + 1
1373
1374
1375 def convert_url(document):
1376     "Convert url insets to url charstyles"
1377     if document.backend == "docbook":
1378         return
1379     i = 0
1380     while True:
1381         i = find_token(document.body, "\\begin_inset CommandInset url", i)
1382         if i == -1:
1383             break
1384         n = find_token(document.body, "name", i)
1385         if n == i + 2:
1386             # place the URL name in typewriter before the new URL insert
1387             # grab the name 'bla' from the e.g. the line 'name "bla"',
1388             # therefore start with the 6th character
1389             name = document.body[n][6:-1]
1390             newname = [name + " "]
1391             document.body[i:i] = newname
1392             i = i + 1
1393         j = find_token(document.body, "target", i)
1394         if j == -1:
1395             document.warning("Malformed LyX document: Can't find target for url inset")
1396             i += 1
1397             continue
1398         target = document.body[j][8:-1]
1399         k = find_token(document.body, "\\end_inset", j)
1400         if k == -1:
1401             document.warning("Malformed LyX document: Can't find end of url inset")
1402             i = j
1403             continue
1404         newstuff = [
1405             "\\begin_inset Flex URL",
1406             "status collapsed",
1407             "",
1408             "\\begin_layout Standard",
1409             "",
1410             target,
1411             "\\end_layout",
1412             "",
1413         ]
1414         document.body[i:k] = newstuff
1415         i = i + len(newstuff)
1416
1417
1418 def convert_ams_classes(document):
1419     tc = document.textclass
1420     if tc != "amsart" and tc != "amsart-plain" and tc != "amsart-seq" and tc != "amsbook":
1421         return
1422     if tc == "amsart-plain":
1423         document.textclass = "amsart"
1424         document.set_textclass()
1425         document.add_module("Theorems (Starred)")
1426         return
1427     if tc == "amsart-seq":
1428         document.textclass = "amsart"
1429         document.set_textclass()
1430     document.add_module("Theorems (AMS)")
1431
1432     # Now we want to see if any of the environments in the extended theorems
1433     # module were used in this document. If so, we'll add that module, too.
1434     layouts = [
1435         "Criterion",
1436         "Algorithm",
1437         "Axiom",
1438         "Condition",
1439         "Note",
1440         "Notation",
1441         "Summary",
1442         "Acknowledgement",
1443         "Conclusion",
1444         "Fact",
1445         "Assumption",
1446     ]
1447
1448     r = re.compile(r"^\\begin_layout (.*?)\*?\s*$")
1449     i = 0
1450     while True:
1451         i = find_token(document.body, "\\begin_layout", i)
1452         if i == -1:
1453             return
1454         m = r.match(document.body[i])
1455         if m == None:
1456             # This is an empty layout
1457             # document.warning("Weirdly formed \\begin_layout at line %d of body!" % i)
1458             i += 1
1459             continue
1460         m = m.group(1)
1461         if layouts.count(m) != 0:
1462             document.add_module("Theorems (AMS-Extended)")
1463             return
1464         i += 1
1465
1466
1467 def revert_href(document):
1468     "Reverts hyperlink insets (href) to url insets (url)"
1469     i = 0
1470     while True:
1471         i = find_token(document.body, "\\begin_inset CommandInset href", i)
1472         if i == -1:
1473             return
1474         document.body[i : i + 2] = [
1475             "\\begin_inset CommandInset url",
1476             "LatexCommand url",
1477         ]
1478         i = i + 2
1479
1480
1481 def revert_url(document):
1482     "Reverts Flex URL insets to old-style URL insets"
1483     i = 0
1484     while True:
1485         i = find_token(document.body, "\\begin_inset Flex URL", i)
1486         if i == -1:
1487             return
1488         j = find_end_of_inset(document.body, i)
1489         if j == -1:
1490             document.warning("Can't find end of inset in revert_url!")
1491             return
1492         k = find_default_layout(document, i, j)
1493         if k == -1:
1494             document.warning("Can't find default layout in revert_url!")
1495             i = j
1496             continue
1497         l = find_end_of(document.body, k, "\\begin_layout", "\\end_layout")
1498         if l == -1 or l >= j:
1499             document.warning("Can't find end of default layout in revert_url!")
1500             i = j
1501             continue
1502         # OK, so the inset's data is between lines k and l.
1503         data = " ".join(document.body[k + 1 : l])
1504         data = data.strip()
1505         newinset = [
1506             "\\begin_inset LatexCommand url",
1507             'target "' + data + '"',
1508             "",
1509             "\\end_inset",
1510         ]
1511         document.body[i : j + 1] = newinset
1512         i = i + len(newinset)
1513
1514
1515 def convert_include(document):
1516     "Converts include insets to new format."
1517     i = 0
1518     r = re.compile(r"\\begin_inset Include\s+\\([^{]+){([^}]*)}(?:\[(.*)\])?")
1519     while True:
1520         i = find_token(document.body, "\\begin_inset Include", i)
1521         if i == -1:
1522             return
1523         line = document.body[i]
1524         previewline = document.body[i + 1]
1525         m = r.match(line)
1526         if m == None:
1527             document.warning("Unable to match line " + str(i) + " of body!")
1528             i += 1
1529             continue
1530         cmd = m.group(1)
1531         fn = m.group(2)
1532         opt = m.group(3)
1533         insertion = [
1534             "\\begin_inset CommandInset include",
1535             "LatexCommand " + cmd,
1536             previewline,
1537             'filename "' + fn + '"',
1538         ]
1539         newlines = 2
1540         if opt:
1541             insertion.append("lstparams " + '"' + opt + '"')
1542             newlines += 1
1543         document.body[i : i + 2] = insertion
1544         i += newlines
1545
1546
1547 def revert_include(document):
1548     "Reverts include insets to old format."
1549     i = 0
1550     r0 = re.compile("preview.*")
1551     r1 = re.compile("LatexCommand (.+)")
1552     r2 = re.compile('filename "(.+)"')
1553     r3 = re.compile('lstparams "(.*)"')
1554     while True:
1555         i = find_token(document.body, "\\begin_inset CommandInset include", i)
1556         if i == -1:
1557             return
1558         nextline = i + 1
1559         m = r1.match(document.body[nextline])
1560         if m == None:
1561             document.warning(
1562                 "Malformed LyX document: No LatexCommand line for `"
1563                 + document.body[i]
1564                 + "' on line "
1565                 + str(i)
1566                 + "."
1567             )
1568             i += 1
1569             continue
1570         cmd = m.group(1)
1571         nextline += 1
1572         if r0.match(document.body[nextline]):
1573             previewline = document.body[nextline]
1574             nextline += 1
1575         else:
1576             previewline = ""
1577         m = r2.match(document.body[nextline])
1578         if m == None:
1579             document.warning(
1580                 "Malformed LyX document: No filename line for `"
1581                 + document.body[i]
1582                 + "' on line "
1583                 + str(i)
1584                 + "."
1585             )
1586             i += 2
1587             continue
1588         fn = m.group(1)
1589         nextline += 1
1590         options = ""
1591         if cmd == "lstinputlisting":
1592             m = r3.match(document.body[nextline])
1593             if m != None:
1594                 options = m.group(1)
1595                 numlines = 5
1596                 nextline += 1
1597         newline = "\\begin_inset Include \\" + cmd + "{" + fn + "}"
1598         if options:
1599             newline += "[" + options + "]"
1600         insertion = [newline]
1601         if previewline != "":
1602             insertion.append(previewline)
1603         document.body[i:nextline] = insertion
1604         i += 2
1605
1606
1607 def revert_albanian(document):
1608     "Set language Albanian to English"
1609     i = 0
1610     if document.language == "albanian":
1611         document.language = "english"
1612         i = find_token(document.header, "\\language", 0)
1613         if i != -1:
1614             document.header[i] = "\\language english"
1615     j = 0
1616     while True:
1617         j = find_token(document.body, "\\lang albanian", j)
1618         if j == -1:
1619             return
1620         document.body[j] = document.body[j].replace("\\lang albanian", "\\lang english")
1621         j = j + 1
1622
1623
1624 def revert_lowersorbian(document):
1625     "Set language lower Sorbian to English"
1626     i = 0
1627     if document.language == "lowersorbian":
1628         document.language = "english"
1629         i = find_token(document.header, "\\language", 0)
1630         if i != -1:
1631             document.header[i] = "\\language english"
1632     j = 0
1633     while True:
1634         j = find_token(document.body, "\\lang lowersorbian", j)
1635         if j == -1:
1636             return
1637         document.body[j] = document.body[j].replace("\\lang lowersorbian", "\\lang english")
1638         j = j + 1
1639
1640
1641 def revert_uppersorbian(document):
1642     "Set language uppersorbian to usorbian as this was used in LyX 1.5"
1643     i = 0
1644     if document.language == "uppersorbian":
1645         document.language = "usorbian"
1646         i = find_token(document.header, "\\language", 0)
1647         if i != -1:
1648             document.header[i] = "\\language usorbian"
1649     j = 0
1650     while True:
1651         j = find_token(document.body, "\\lang uppersorbian", j)
1652         if j == -1:
1653             return
1654         document.body[j] = document.body[j].replace("\\lang uppersorbian", "\\lang usorbian")
1655         j = j + 1
1656
1657
1658 def convert_usorbian(document):
1659     "Set language usorbian to uppersorbian"
1660     i = 0
1661     if document.language == "usorbian":
1662         document.language = "uppersorbian"
1663         i = find_token(document.header, "\\language", 0)
1664         if i != -1:
1665             document.header[i] = "\\language uppersorbian"
1666     j = 0
1667     while True:
1668         j = find_token(document.body, "\\lang usorbian", j)
1669         if j == -1:
1670             return
1671         document.body[j] = document.body[j].replace("\\lang usorbian", "\\lang uppersorbian")
1672         j = j + 1
1673
1674
1675 def convert_macro_global(document):
1676     r"Remove TeX code command \global when it is in front of a macro"
1677     # math macros are nowadays already defined \global, so that an additional
1678     # \global would make the document uncompilable, see
1679     # http://www.lyx.org/trac/ticket/5371
1680     # We're looking for something like this:
1681     # \begin_inset ERT
1682     # status collapsed
1683     #
1684     # \begin_layout Plain Layout
1685     #
1686     #
1687     # \backslash
1688     # global
1689     # \end_layout
1690     #
1691     # \end_inset
1692     #
1693     #
1694     # \begin_inset FormulaMacro
1695     # \renewcommand{\foo}{123}
1696     # \end_inset
1697     i = 0
1698     while True:
1699         i = find_token(document.body, "\\begin_inset FormulaMacro", i)
1700         if i == -1:
1701             return
1702         # if i <= 13, then there isn't enough room for the ERT
1703         if i <= 12:
1704             i += 1
1705             continue
1706         if document.body[i - 6] == "global":
1707             del document.body[i - 13 : i]
1708             i = i - 12
1709         else:
1710             i += 1
1711
1712
1713 def revert_macro_optional_params(document):
1714     "Convert macro definitions with optional parameters into ERTs"
1715     # Stub to convert macro definitions with one or more optional parameters
1716     # into uninterpreted ERT insets
1717
1718
1719 def revert_hyperlinktype(document):
1720     "Reverts hyperlink type"
1721     i = 0
1722     j = 0
1723     while True:
1724         i = find_token(document.body, "target", i)
1725         if i == -1:
1726             return
1727         j = find_token(document.body, "type", i)
1728         if j == -1:
1729             return
1730         if j == i + 1:
1731             del document.body[j]
1732         i = i + 1
1733
1734
1735 def revert_pagebreak(document):
1736     "Reverts pagebreak to ERT"
1737     i = 0
1738     while True:
1739         i = find_token(document.body, "\\pagebreak", i)
1740         if i == -1:
1741             return
1742         document.body[i] = (
1743             "\\begin_inset ERT\nstatus collapsed\n\n"
1744             "\\begin_layout Standard\n\n\n\\backslash\n"
1745             "pagebreak{}\n\\end_layout\n\n\\end_inset\n\n"
1746         )
1747         i = i + 1
1748
1749
1750 def revert_linebreak(document):
1751     "Reverts linebreak to ERT"
1752     i = 0
1753     while True:
1754         i = find_token(document.body, "\\linebreak", i)
1755         if i == -1:
1756             return
1757         document.body[i] = (
1758             "\\begin_inset ERT\nstatus collapsed\n\n"
1759             "\\begin_layout Standard\n\n\n\\backslash\n"
1760             "linebreak{}\n\\end_layout\n\n\\end_inset\n\n"
1761         )
1762         i = i + 1
1763
1764
1765 def revert_latin(document):
1766     "Set language Latin to English"
1767     i = 0
1768     if document.language == "latin":
1769         document.language = "english"
1770         i = find_token(document.header, "\\language", 0)
1771         if i != -1:
1772             document.header[i] = "\\language english"
1773     j = 0
1774     while True:
1775         j = find_token(document.body, "\\lang latin", j)
1776         if j == -1:
1777             return
1778         document.body[j] = document.body[j].replace("\\lang latin", "\\lang english")
1779         j = j + 1
1780
1781
1782 def revert_samin(document):
1783     "Set language North Sami to English"
1784     i = 0
1785     if document.language == "samin":
1786         document.language = "english"
1787         i = find_token(document.header, "\\language", 0)
1788         if i != -1:
1789             document.header[i] = "\\language english"
1790     j = 0
1791     while True:
1792         j = find_token(document.body, "\\lang samin", j)
1793         if j == -1:
1794             return
1795         document.body[j] = document.body[j].replace("\\lang samin", "\\lang english")
1796         j = j + 1
1797
1798
1799 def convert_serbocroatian(document):
1800     "Set language Serbocroatian to Croatian as this was really Croatian in LyX 1.5"
1801     i = 0
1802     if document.language == "serbocroatian":
1803         document.language = "croatian"
1804         i = find_token(document.header, "\\language", 0)
1805         if i != -1:
1806             document.header[i] = "\\language croatian"
1807     j = 0
1808     while True:
1809         j = find_token(document.body, "\\lang serbocroatian", j)
1810         if j == -1:
1811             return
1812         document.body[j] = document.body[j].replace("\\lang serbocroatian", "\\lang croatian")
1813         j = j + 1
1814
1815
1816 def convert_framed_notes(document):
1817     "Convert framed notes to boxes."
1818     i = 0
1819     while True:
1820         i = find_tokens(
1821             document.body, ["\\begin_inset Note Framed", "\\begin_inset Note Shaded"], i
1822         )
1823         if i == -1:
1824             return
1825         subst = [
1826             document.body[i].replace("\\begin_inset Note", "\\begin_inset Box"),
1827             'position "t"',
1828             'hor_pos "c"',
1829             "has_inner_box 0",
1830             'inner_pos "t"',
1831             "use_parbox 0",
1832             'width "100col%"',
1833             'special "none"',
1834             'height "1in"',
1835             'height_special "totalheight"',
1836         ]
1837         document.body[i : i + 1] = subst
1838         i = i + 9
1839
1840
1841 def convert_module_names(document):
1842     modulemap = {
1843         "Braille": "braille",
1844         "Endnote": "endnotes",
1845         "Foot to End": "foottoend",
1846         "Hanging": "hanging",
1847         "Linguistics": "linguistics",
1848         "Logical Markup": "logicalmkup",
1849         "Theorems (AMS-Extended)": "theorems-ams-extended",
1850         "Theorems (AMS)": "theorems-ams",
1851         "Theorems (Order By Chapter)": "theorems-chap",
1852         "Theorems (Order By Section)": "theorems-sec",
1853         "Theorems (Starred)": "theorems-starred",
1854         "Theorems": "theorems-std",
1855     }
1856     modlist = document.get_module_list()
1857     if len(modlist) == 0:
1858         return
1859     newmodlist = []
1860     for mod in modlist:
1861         if mod in modulemap:
1862             newmodlist.append(modulemap[mod])
1863         else:
1864             document.warning("Can't find module %s in the module map!" % mod)
1865             newmodlist.append(mod)
1866     document.set_module_list(newmodlist)
1867
1868
1869 def revert_module_names(document):
1870     modulemap = {
1871         "braille": "Braille",
1872         "endnotes": "Endnote",
1873         "foottoend": "Foot to End",
1874         "hanging": "Hanging",
1875         "linguistics": "Linguistics",
1876         "logicalmkup": "Logical Markup",
1877         "theorems-ams-extended": "Theorems (AMS-Extended)",
1878         "theorems-ams": "Theorems (AMS)",
1879         "theorems-chap": "Theorems (Order By Chapter)",
1880         "theorems-sec": "Theorems (Order By Section)",
1881         "theorems-starred": "Theorems (Starred)",
1882         "theorems-std": "Theorems",
1883     }
1884     modlist = document.get_module_list()
1885     if len(modlist) == 0:
1886         return
1887     newmodlist = []
1888     for mod in modlist:
1889         if mod in modulemap:
1890             newmodlist.append(modulemap[mod])
1891         else:
1892             document.warning("Can't find module %s in the module map!" % mod)
1893             newmodlist.append(mod)
1894     document.set_module_list(newmodlist)
1895
1896
1897 def revert_colsep(document):
1898     i = find_token(document.header, "\\columnsep", 0)
1899     if i == -1:
1900         return
1901     colsepline = document.header[i]
1902     r = re.compile(r"\\columnsep (.*)")
1903     m = r.match(colsepline)
1904     if not m:
1905         document.warning("Malformed column separation line!")
1906         return
1907     colsep = m.group(1)
1908     del document.header[i]
1909     # it seems to be safe to add the package even if it is already used
1910     pretext = ["\\usepackage{geometry}", "\\geometry{columnsep=" + colsep + "}"]
1911
1912     add_to_preamble(document, pretext)
1913
1914
1915 def revert_framed_notes(document):
1916     "Revert framed boxes to notes."
1917     i = 0
1918     while True:
1919         i = find_tokens(
1920             document.body, ["\\begin_inset Box Framed", "\\begin_inset Box Shaded"], i
1921         )
1922
1923         if i == -1:
1924             return
1925         j = find_end_of_inset(document.body, i + 1)
1926         if j == -1:
1927             # should not happen
1928             document.warning("Malformed LyX document: Could not find end of Box inset.")
1929             i += 1
1930             continue
1931         k = find_token(document.body, "status", i + 1, j)
1932         if k == -1:
1933             document.warning("Malformed LyX document: Missing `status' tag in Box inset.")
1934             i = j
1935             continue
1936         status = document.body[k]
1937         l = find_default_layout(document, i + 1, j)
1938         if l == -1:
1939             document.warning("Malformed LyX document: Missing `\\begin_layout' in Box inset.")
1940             i = j
1941             continue
1942         m = find_token(document.body, "\\end_layout", i + 1, j)
1943         if m == -1:
1944             document.warning("Malformed LyX document: Missing `\\end_layout' in Box inset.")
1945             i = j
1946             continue
1947         ibox = find_token(document.body, "has_inner_box 1", i + 1, k)
1948         pbox = find_token(document.body, "use_parbox 1", i + 1, k)
1949         if ibox == -1 and pbox == -1:
1950             document.body[i] = document.body[i].replace(
1951                 "\\begin_inset Box", "\\begin_inset Note"
1952             )
1953             del document.body[i + 1 : k]
1954         else:
1955             document.body[i] = document.body[i].replace(
1956                 "\\begin_inset Box Shaded", "\\begin_inset Box Frameless"
1957             )
1958             subst1 = [
1959                 document.body[l],
1960                 "\\begin_inset Note Shaded",
1961                 status,
1962                 "\\begin_layout Standard",
1963             ]
1964             document.body[l : l + 1] = subst1
1965             subst2 = [document.body[m], "\\end_layout", "\\end_inset"]
1966             document.body[m : m + 1] = subst2
1967         i = i + 1
1968
1969
1970 def revert_slash(document):
1971     "Revert \\SpecialChar \\slash{} to ERT"
1972     i = 0
1973     while i < len(document.body):
1974         m = re.match(r"(.*)\\SpecialChar \\slash{}(.*)", document.body[i])
1975         if m:
1976             before = m.group(1)
1977             after = m.group(2)
1978             subst = [
1979                 before,
1980                 "\\begin_inset ERT",
1981                 "status collapsed",
1982                 "",
1983                 "\\begin_layout Standard",
1984                 "",
1985                 "",
1986                 "\\backslash",
1987                 "slash{}",
1988                 "\\end_layout",
1989                 "",
1990                 "\\end_inset",
1991                 "",
1992                 after,
1993             ]
1994             document.body[i : i + 1] = subst
1995             i = i + len(subst)
1996         else:
1997             i = i + 1
1998
1999
2000 def revert_nobreakdash(document):
2001     "Revert \\SpecialChar \\nobreakdash- to ERT"
2002     i = 0
2003     while i < len(document.body):
2004         m = re.match(r"(.*)\\SpecialChar \\nobreakdash-(.*)", document.body[i])
2005         if m:
2006             before = m.group(1)
2007             after = m.group(2)
2008             subst = [
2009                 before,
2010                 "\\begin_inset ERT",
2011                 "status collapsed",
2012                 "",
2013                 "\\begin_layout Standard",
2014                 "",
2015                 "",
2016                 "\\backslash",
2017                 "nobreakdash-",
2018                 "\\end_layout",
2019                 "",
2020                 "\\end_inset",
2021                 "",
2022                 after,
2023             ]
2024             document.body[i : i + 1] = subst
2025             i = i + len(subst)
2026             j = find_token(document.header, "\\use_amsmath", 0)
2027             if j == -1:
2028                 document.warning("Malformed LyX document: Missing '\\use_amsmath'.")
2029                 i += 1
2030                 continue
2031             document.header[j] = "\\use_amsmath 2"
2032         else:
2033             i = i + 1
2034
2035
2036 # Returns number of lines added/removed
2037 def revert_nocite_key(body, start, end):
2038     'key "..." -> \nocite{...}'
2039     r = re.compile(r'^key "(.*)"')
2040     i = start
2041     j = end
2042     while i < j:
2043         m = r.match(body[i])
2044         if m:
2045             body[i : i + 1] = ["\\backslash", "nocite{" + m.group(1) + "}"]
2046             j += 1  # because we added a line
2047             i += 2  # skip that line
2048         else:
2049             del body[i]
2050             j -= 1  # because we deleted a line
2051             # no need to change i, since it now points to the next line
2052     return j - end
2053
2054
2055 def revert_nocite(document):
2056     "Revert LatexCommand nocite to ERT"
2057     i = 0
2058     while True:
2059         i = find_token(document.body, "\\begin_inset CommandInset citation", i)
2060         if i == -1:
2061             return
2062         if document.body[i + 1] != "LatexCommand nocite":
2063             # note that we already incremented i
2064             i = i + 1
2065             continue
2066         insetEnd = find_end_of_inset(document.body, i)
2067         if insetEnd == -1:
2068             # this should not happen
2069             document.warning("End of CommandInset citation not found in revert_nocite!")
2070             return
2071
2072         paramLocation = i + 2  # start of the inset's parameters
2073         addedLines = 0
2074         document.body[i : i + 2] = [
2075             "\\begin_inset ERT",
2076             "status collapsed",
2077             "",
2078             "\\begin_layout Standard",
2079         ]
2080         # that added two lines
2081         paramLocation += 2
2082         insetEnd += 2
2083         # print insetEnd, document.body[i: insetEnd + 1]
2084         insetEnd += revert_nocite_key(document.body, paramLocation, insetEnd)
2085         # print insetEnd, document.body[i: insetEnd + 1]
2086         document.body.insert(insetEnd, "\\end_layout")
2087         document.body.insert(insetEnd + 1, "")
2088         i = insetEnd + 1
2089
2090
2091 def revert_btprintall(document):
2092     "Revert (non-bibtopic) btPrintAll option to ERT \nocite{*}"
2093     i = find_token(document.header, "\\use_bibtopic", 0)
2094     if i == -1:
2095         document.warning("Malformed lyx document: Missing '\\use_bibtopic'.")
2096         return
2097     if get_value(document.header, "\\use_bibtopic", 0) == "false":
2098         i = 0
2099         while i < len(document.body):
2100             i = find_token(document.body, "\\begin_inset CommandInset bibtex", i)
2101             if i == -1:
2102                 return
2103             j = find_end_of_inset(document.body, i + 1)
2104             if j == -1:
2105                 # this should not happen
2106                 document.warning("End of CommandInset bibtex not found in revert_btprintall!")
2107                 j = len(document.body)
2108             # this range isn't really right, but it should be OK, since we shouldn't
2109             # see more than one matching line in each inset
2110             addedlines = 0
2111             for k in range(i, j):
2112                 if document.body[k] == 'btprint "btPrintAll"':
2113                     del document.body[k]
2114                     subst = [
2115                         "\\begin_inset ERT",
2116                         "status collapsed",
2117                         "",
2118                         "\\begin_layout Standard",
2119                         "",
2120                         "\\backslash",
2121                         "nocite{*}",
2122                         "\\end_layout",
2123                         "\\end_inset",
2124                     ]
2125                     document.body[i:i] = subst
2126                     addlines = addedlines + len(subst) - 1
2127             i = j + addedlines
2128
2129
2130 def revert_bahasam(document):
2131     "Set language Bahasa Malaysia to Bahasa Indonesia"
2132     i = 0
2133     if document.language == "bahasam":
2134         document.language = "bahasa"
2135         i = find_token(document.header, "\\language", 0)
2136         if i != -1:
2137             document.header[i] = "\\language bahasa"
2138     j = 0
2139     while True:
2140         j = find_token(document.body, "\\lang bahasam", j)
2141         if j == -1:
2142             return
2143         document.body[j] = document.body[j].replace("\\lang bahasam", "\\lang bahasa")
2144         j = j + 1
2145
2146
2147 def revert_interlingua(document):
2148     "Set language Interlingua to English"
2149     i = 0
2150     if document.language == "interlingua":
2151         document.language = "english"
2152         i = find_token(document.header, "\\language", 0)
2153         if i != -1:
2154             document.header[i] = "\\language english"
2155     j = 0
2156     while True:
2157         j = find_token(document.body, "\\lang interlingua", j)
2158         if j == -1:
2159             return
2160         document.body[j] = document.body[j].replace("\\lang interlingua", "\\lang english")
2161         j = j + 1
2162
2163
2164 def revert_serbianlatin(document):
2165     "Set language Serbian-Latin to Croatian"
2166     i = 0
2167     if document.language == "serbian-latin":
2168         document.language = "croatian"
2169         i = find_token(document.header, "\\language", 0)
2170         if i != -1:
2171             document.header[i] = "\\language croatian"
2172     j = 0
2173     while True:
2174         j = find_token(document.body, "\\lang serbian-latin", j)
2175         if j == -1:
2176             return
2177         document.body[j] = document.body[j].replace("\\lang serbian-latin", "\\lang croatian")
2178         j = j + 1
2179
2180
2181 def revert_rotfloat(document):
2182     "Revert sideways custom floats."
2183     i = 0
2184     while True:
2185         # whitespace intended (exclude \\begin_inset FloatList)
2186         i = find_token(document.body, "\\begin_inset Float ", i)
2187         if i == -1:
2188             return
2189         line = document.body[i]
2190         r = re.compile(r"\\begin_inset Float (.*)$")
2191         m = r.match(line)
2192         if m == None:
2193             document.warning("Unable to match line " + str(i) + " of body!")
2194             i += 1
2195             continue
2196         floattype = m.group(1)
2197         if floattype == "figure" or floattype == "table":
2198             i += 1
2199             continue
2200         j = find_end_of_inset(document.body, i)
2201         if j == -1:
2202             document.warning(
2203                 "Malformed lyx document: Missing '\\end_inset' in revert_rotfloat."
2204             )
2205             i += 1
2206             continue
2207         addedLines = 0
2208         if get_value(document.body, "sideways", i, j) == "false":
2209             i += 1
2210             continue
2211         l = find_default_layout(document, i + 1, j)
2212         if l == -1:
2213             document.warning("Malformed LyX document: Missing `\\begin_layout' in Float inset.")
2214             i = j
2215             continue
2216         subst = [
2217             "\\begin_layout Standard",
2218             "\\begin_inset ERT",
2219             "status collapsed",
2220             "",
2221             "\\begin_layout Standard",
2222             "",
2223             "",
2224             "\\backslash",
2225             "",
2226             "end{sideways" + floattype + "}",
2227             "\\end_layout",
2228             "",
2229             "\\end_inset",
2230         ]
2231         document.body[j : j + 1] = subst
2232         addedLines = len(subst) - 1
2233         del document.body[i + 1 : l]
2234         addedLines -= (l - 1) - (i + 1)
2235         subst = [
2236             "\\begin_inset ERT",
2237             "status collapsed",
2238             "",
2239             "\\begin_layout Standard",
2240             "",
2241             "",
2242             "\\backslash",
2243             "begin{sideways" + floattype + "}",
2244             "\\end_layout",
2245             "",
2246             "\\end_inset",
2247             "",
2248             "\\end_layout",
2249             "",
2250         ]
2251         document.body[i : i + 1] = subst
2252         addedLines += len(subst) - 1
2253         if floattype == "algorithm":
2254             add_to_preamble(
2255                 document,
2256                 [
2257                     "% Commands inserted by lyx2lyx for sideways algorithm float",
2258                     "\\usepackage{rotfloat}",
2259                     "\\floatstyle{ruled}",
2260                     "\\newfloat{algorithm}{tbp}{loa}",
2261                     "\\floatname{algorithm}{Algorithm}",
2262                 ],
2263             )
2264         else:
2265             document.warning(
2266                 "Cannot create preamble definition for custom float" + floattype + "."
2267             )
2268         i += addedLines + 1
2269
2270
2271 def revert_widesideways(document):
2272     "Revert wide sideways floats."
2273     i = 0
2274     while True:
2275         # whitespace intended (exclude \\begin_inset FloatList)
2276         i = find_token(document.body, "\\begin_inset Float ", i)
2277         if i == -1:
2278             return
2279         line = document.body[i]
2280         r = re.compile(r"\\begin_inset Float (.*)$")
2281         m = r.match(line)
2282         if m == None:
2283             document.warning("Unable to match line " + str(i) + " of body!")
2284             i += 1
2285             continue
2286         floattype = m.group(1)
2287         if floattype != "figure" and floattype != "table":
2288             i += 1
2289             continue
2290         j = find_end_of_inset(document.body, i)
2291         if j == -1:
2292             document.warning(
2293                 "Malformed lyx document: Missing '\\end_inset' in revert_widesideways."
2294             )
2295             i += 1
2296             continue
2297         if (
2298             get_value(document.body, "sideways", i, j) == "false"
2299             or get_value(document.body, "wide", i, j) == "false"
2300         ):
2301             i += 1
2302             continue
2303         l = find_default_layout(document, i + 1, j)
2304         if l == -1:
2305             document.warning("Malformed LyX document: Missing `\\begin_layout' in Float inset.")
2306             i = j
2307             continue
2308         subst = [
2309             "\\begin_layout Standard",
2310             "\\begin_inset ERT",
2311             "status collapsed",
2312             "",
2313             "\\begin_layout Standard",
2314             "",
2315             "",
2316             "\\backslash",
2317             "end{sideways" + floattype + "*}",
2318             "\\end_layout",
2319             "",
2320             "\\end_inset",
2321         ]
2322         document.body[j : j + 1] = subst
2323         addedLines = len(subst) - 1
2324         del document.body[i + 1 : l - 1]
2325         addedLines -= (l - 1) - (i + 1)
2326         subst = [
2327             "\\begin_inset ERT",
2328             "status collapsed",
2329             "",
2330             "\\begin_layout Standard",
2331             "",
2332             "",
2333             "\\backslash",
2334             "begin{sideways" + floattype + "*}",
2335             "\\end_layout",
2336             "",
2337             "\\end_inset",
2338             "",
2339             "\\end_layout",
2340             "",
2341         ]
2342         document.body[i : i + 1] = subst
2343         addedLines += len(subst) - 1
2344         add_to_preamble(document, ["\\usepackage{rotfloat}\n"])
2345         i += addedLines + 1
2346
2347
2348 def revert_inset_embedding(document, type):
2349     "Remove embed tag from certain type of insets"
2350     i = 0
2351     while True:
2352         i = find_token(document.body, "\\begin_inset %s" % type, i)
2353         if i == -1:
2354             return
2355         j = find_end_of_inset(document.body, i)
2356         if j == -1:
2357             document.warning(
2358                 "Malformed lyx document: Missing '\\end_inset' in revert_inset_embedding."
2359             )
2360             i = i + 1
2361             continue
2362         k = find_token(document.body, "\tembed", i, j)
2363         if k == -1:
2364             k = find_token(document.body, "embed", i, j)
2365         if k != -1:
2366             del document.body[k]
2367         i = i + 1
2368
2369
2370 def revert_external_embedding(document):
2371     "Remove embed tag from external inset"
2372     revert_inset_embedding(document, "External")
2373
2374
2375 def convert_subfig(document):
2376     "Convert subfigures to subfloats."
2377     i = 0
2378     while True:
2379         addedLines = 0
2380         i = find_token(document.body, "\\begin_inset Graphics", i)
2381         if i == -1:
2382             return
2383         endInset = find_end_of_inset(document.body, i)
2384         if endInset == -1:
2385             document.warning("Malformed lyx document: Missing '\\end_inset' in convert_subfig.")
2386             i += 1
2387             continue
2388         k = find_token(document.body, "\tsubcaption", i, endInset)
2389         if k == -1:
2390             i = endInset
2391             continue
2392         l = find_token(document.body, "\tsubcaptionText", i, endInset)
2393         if l == -1:
2394             caption = ""
2395         else:
2396             caption = document.body[l][16:].strip('"')
2397             del document.body[l]
2398             addedLines -= 1
2399         del document.body[k]
2400         addedLines -= 1
2401         subst = (
2402             [
2403                 "\\begin_inset Float figure",
2404                 "wide false",
2405                 "sideways false",
2406                 "status open",
2407                 "",
2408                 "\\begin_layout Plain Layout",
2409                 "\\begin_inset Caption",
2410                 "",
2411                 "\\begin_layout Plain Layout",
2412             ]
2413             + latex2lyx(caption, False)
2414             + [
2415                 "\\end_layout",
2416                 "",
2417                 "\\end_inset",
2418                 "",
2419                 "\\end_layout",
2420                 "",
2421                 "\\begin_layout Plain Layout",
2422             ]
2423         )
2424         document.body[i:i] = subst
2425         addedLines += len(subst)
2426         endInset += addedLines
2427         subst = ["", "\\end_inset", "", "\\end_layout"]
2428         document.body[endInset:endInset] = subst
2429         addedLines += len(subst)
2430         i += addedLines + 1
2431
2432
2433 def revert_subfig(document):
2434     "Revert subfloats."
2435     i = 0
2436     while True:
2437         # whitespace intended (exclude \\begin_inset FloatList)
2438         i = find_tokens(document.body, ["\\begin_inset Float ", "\\begin_inset Wrap"], i)
2439         if i == -1:
2440             return
2441         j = 0
2442         addedLines = 0
2443         while j != -1:
2444             j = find_end_of_inset(document.body, i)
2445             if j == -1:
2446                 document.warning(
2447                     "Malformed lyx document: Missing '\\end_inset' (float) at line "
2448                     + str(i + len(document.header))
2449                     + ".\n\t"
2450                     + document.body[i]
2451                 )
2452                 # document.warning(document.body[i-1] + "\n" + document.body[i+1])
2453                 i += 1
2454                 continue  # this will get us back to the outer loop, since j == -1
2455             # look for embedded float (= subfloat)
2456             # whitespace intended (exclude \\begin_inset FloatList)
2457             k = find_token(document.body, "\\begin_inset Float ", i + 1, j)
2458             if k == -1:
2459                 break
2460             # is the subfloat aligned?
2461             al = find_token(document.body, "\\align ", k - 1, j)
2462             alignment_beg = ""
2463             alignment_end = ""
2464             if al != -1:
2465                 if get_value(document.body, "\\align", al) == "center":
2466                     alignment_beg = "\\backslash\nbegin{centering}"
2467                     alignment_end = "\\backslash\npar\\backslash\nend{centering}"
2468                 elif get_value(document.body, "\\align", al) == "left":
2469                     alignment_beg = "\\backslash\nbegin{raggedright}"
2470                     alignment_end = "\\backslash\npar\\backslash\nend{raggedright}"
2471                 elif get_value(document.body, "\\align", al) == "right":
2472                     alignment_beg = "\\backslash\nbegin{raggedleft}"
2473                     alignment_end = "\\backslash\npar\\backslash\nend{raggedleft}"
2474             l = find_end_of_inset(document.body, k)
2475             if l == -1:
2476                 document.warning(
2477                     "Malformed lyx document: Missing '\\end_inset' (embedded float)."
2478                 )
2479                 i += 1
2480                 j = -1
2481                 continue  # escape to the outer loop
2482             m = find_default_layout(document, k + 1, l)
2483             # caption?
2484             cap = find_token(document.body, "\\begin_inset Caption", k + 1, l)
2485             caption = ""
2486             shortcap = ""
2487             capend = cap
2488             if cap != -1:
2489                 capend = find_end_of_inset(document.body, cap)
2490                 if capend == -1:
2491                     document.warning("Malformed lyx document: Missing '\\end_inset' (caption).")
2492                     return
2493                 # label?
2494                 label = ""
2495                 lbl = find_token(document.body, "\\begin_inset CommandInset label", cap, capend)
2496                 if lbl != -1:
2497                     lblend = find_end_of_inset(document.body, lbl + 1)
2498                     if lblend == -1:
2499                         document.warning(
2500                             "Malformed lyx document: Missing '\\end_inset' (label)."
2501                         )
2502                         return
2503                     for line in document.body[lbl : lblend + 1]:
2504                         if line.startswith("name "):
2505                             label = line.split()[1].strip('"')
2506                             break
2507                 else:
2508                     lbl = capend
2509                     lblend = capend
2510                     label = ""
2511                 # opt arg?
2512                 opt = find_token(document.body, "\\begin_inset OptArg", cap, capend)
2513                 if opt != -1:
2514                     optend = find_end_of_inset(document.body, opt)
2515                     if optend == -1:
2516                         document.warning(
2517                             "Malformed LyX document: Missing '\\end_inset' (OptArg)."
2518                         )
2519                         return
2520                     optc = find_default_layout(document, opt, optend)
2521                     if optc == -1:
2522                         document.warning(
2523                             "Malformed LyX document: Missing `\\begin_layout' in Float inset."
2524                         )
2525                         return
2526                     optcend = find_end_of(document.body, optc, "\\begin_layout", "\\end_layout")
2527                     for line in document.body[optc:optcend]:
2528                         if not line.startswith("\\"):
2529                             shortcap += line.strip()
2530                 else:
2531                     opt = capend
2532                     optend = capend
2533                 for line in document.body[cap:capend]:
2534                     if line in document.body[lbl:lblend]:
2535                         continue
2536                     elif line in document.body[opt:optend]:
2537                         continue
2538                     else:
2539                         inert = True
2540                         caption += lyxline2latex(document, line, inert)
2541                 if len(label) > 0:
2542                     caption += "\n\\backslash\nlabel{" + label + "}"
2543             subst = (
2544                 "\\begin_layout PlainLayout\n\\begin_inset ERT\nstatus collapsed\n\n"
2545                 "\\begin_layout PlainLayout\n\n}"
2546                 + alignment_end
2547                 + "\n\\end_layout\n\n\\end_inset\n\n"
2548                 "\\end_layout\n\n\\begin_layout PlainLayout\n"
2549             )
2550             subst = subst.split("\n")
2551             document.body[l : l + 1] = subst
2552             addedLines = len(subst) - 1
2553             # this is before l and so is unchanged by the multiline insertion
2554             if cap != capend:
2555                 del document.body[cap : capend + 1]
2556                 addedLines -= capend + 1 - cap
2557             del document.body[k + 1 : m - 1]
2558             addedLines -= m - 1 - (k + 1)
2559             insertion = (
2560                 "\\begin_inset ERT\nstatus collapsed\n\n"
2561                 "\\begin_layout PlainLayout\n\n" + alignment_beg + "\n\\backslash\n"
2562                 "subfloat"
2563             )
2564             if len(shortcap) > 0:
2565                 insertion = insertion + "[" + shortcap + "]"
2566             if len(caption) > 0:
2567                 insertion = insertion + "[" + caption + "]"
2568             insertion = insertion + "{%\n\\end_layout\n\n\\end_inset\n\n\\end_layout\n"
2569             insertion = insertion.split("\n")
2570             document.body[k : k + 1] = insertion
2571             addedLines += len(insertion) - 1
2572             al = find_token(document.body, "\\align ", k - 1, j + addedLines)
2573             if al != -1:
2574                 del document.body[al]
2575                 addedLines -= 1
2576             add_to_preamble(document, ["\\usepackage{subfig}\n"])
2577         i += addedLines + 1
2578
2579
2580 def revert_wrapplacement(document):
2581     "Revert placement options wrap floats (wrapfig)."
2582     i = 0
2583     while True:
2584         i = find_token(document.body, "\\begin_inset Wrap figure", i)
2585         if i == -1:
2586             return
2587         e = find_end_of_inset(document.body, i)
2588         j = find_token(document.body, "placement", i + 1, e)
2589         if j == -1:
2590             document.warning(
2591                 "Malformed LyX document: Couldn't find placement parameter of wrap float."
2592             )
2593             i += 1
2594             continue
2595         r = re.compile("placement (o|i|l|r|O|I|L|R)")
2596         m = r.match(document.body[j])
2597         if m == None:
2598             document.warning("Malformed LyX document: Placement option isn't O|I|R|L!")
2599         else:
2600             document.body[j] = "placement " + m.group(1).lower()
2601         i = j
2602
2603
2604 def remove_extra_embedded_files(document):
2605     r"Remove \extra_embedded_files from buffer params"
2606     i = find_token(document.header, "\\extra_embedded_files", 0)
2607     if i == -1:
2608         return
2609     document.header.pop(i)
2610
2611
2612 def convert_spaceinset(document):
2613     "Convert '\\InsetSpace foo' to '\\begin_inset Space foo\n\\end_inset'"
2614     i = 0
2615     while i < len(document.body):
2616         m = re.match(r"(.*)\\InsetSpace (.*)", document.body[i])
2617         if m:
2618             before = m.group(1)
2619             after = m.group(2)
2620             subst = [before, "\\begin_inset Space " + after, "\\end_inset"]
2621             document.body[i : i + 1] = subst
2622             i = i + len(subst)
2623         else:
2624             i = i + 1
2625
2626
2627 def revert_spaceinset(document):
2628     "Revert '\\begin_inset Space foo\n\\end_inset' to '\\InsetSpace foo'"
2629     i = 0
2630     while True:
2631         i = find_token(document.body, "\\begin_inset Space", i)
2632         if i == -1:
2633             return
2634         j = find_end_of_inset(document.body, i)
2635         if j == -1:
2636             document.warning("Malformed LyX document: Could not find end of space inset.")
2637             i += 1
2638             continue
2639         document.body[i] = document.body[i].replace("\\begin_inset Space", "\\InsetSpace")
2640         del document.body[j]
2641
2642
2643 def convert_hfill(document):
2644     "Convert hfill to space inset"
2645     i = 0
2646     while True:
2647         i = find_token(document.body, "\\hfill", i)
2648         if i == -1:
2649             return
2650         subst = document.body[i].replace(
2651             "\\hfill", "\n\\begin_inset Space \\hfill{}\n\\end_inset"
2652         )
2653         subst = subst.split("\n")
2654         document.body[i : i + 1] = subst
2655         i += len(subst)
2656
2657
2658 def revert_hfills(document):
2659     "Revert \\hfill commands"
2660     hfill = re.compile(r"\\hfill")
2661     dotfill = re.compile(r"\\dotfill")
2662     hrulefill = re.compile(r"\\hrulefill")
2663     i = 0
2664     while True:
2665         i = find_token(document.body, "\\InsetSpace", i)
2666         if i == -1:
2667             return
2668         if hfill.search(document.body[i]):
2669             document.body[i] = document.body[i].replace("\\InsetSpace \\hfill{}", "\\hfill")
2670             i += 1
2671             continue
2672         if dotfill.search(document.body[i]):
2673             subst = document.body[i].replace(
2674                 "\\InsetSpace \\dotfill{}",
2675                 "\\begin_inset ERT\nstatus collapsed\n\n"
2676                 "\\begin_layout Standard\n\n\n\\backslash\n"
2677                 "dotfill{}\n\\end_layout\n\n\\end_inset\n\n",
2678             )
2679             subst = subst.split("\n")
2680             document.body[i : i + 1] = subst
2681             i += len(subst)
2682             continue
2683         if hrulefill.search(document.body[i]):
2684             subst = document.body[i].replace(
2685                 "\\InsetSpace \\hrulefill{}",
2686                 "\\begin_inset ERT\nstatus collapsed\n\n"
2687                 "\\begin_layout Standard\n\n\n\\backslash\n"
2688                 "hrulefill{}\n\\end_layout\n\n\\end_inset\n\n",
2689             )
2690             subst = subst.split("\n")
2691             document.body[i : i + 1] = subst
2692             i += len(subst)
2693             continue
2694         i += 1
2695
2696
2697 def revert_hspace(document):
2698     "Revert \\InsetSpace \\hspace{} to ERT"
2699     i = 0
2700     hspace = re.compile(r"\\hspace{}")
2701     hstar = re.compile(r"\\hspace\*{}")
2702     while True:
2703         i = find_token(document.body, "\\InsetSpace \\hspace", i)
2704         if i == -1:
2705             return
2706         length = get_value(document.body, "\\length", i + 1)
2707         if length == "":
2708             document.warning("Malformed lyx document: Missing '\\length' in Space inset.")
2709             return
2710         del document.body[i + 1]
2711         addedLines = -1
2712         if hstar.search(document.body[i]):
2713             subst = document.body[i].replace(
2714                 "\\InsetSpace \\hspace*{}",
2715                 "\\begin_inset ERT\nstatus collapsed\n\n"
2716                 "\\begin_layout Standard\n\n\n\\backslash\n"
2717                 "hspace*{" + length + "}\n\\end_layout\n\n\\end_inset\n\n",
2718             )
2719             subst = subst.split("\n")
2720             document.body[i : i + 1] = subst
2721             addedLines += len(subst) - 1
2722             i += addedLines + 1
2723             continue
2724         if hspace.search(document.body[i]):
2725             subst = document.body[i].replace(
2726                 "\\InsetSpace \\hspace{}",
2727                 "\\begin_inset ERT\nstatus collapsed\n\n"
2728                 "\\begin_layout Standard\n\n\n\\backslash\n"
2729                 "hspace{" + length + "}\n\\end_layout\n\n\\end_inset\n\n",
2730             )
2731             subst = subst.split("\n")
2732             document.body[i : i + 1] = subst
2733             addedLines += len(subst) - 1
2734             i += addedLines + 1
2735             continue
2736         i += 1
2737
2738
2739 def revert_protected_hfill(document):
2740     "Revert \\begin_inset Space \\hspace*{\\fill} to ERT"
2741     i = 0
2742     while True:
2743         i = find_token(document.body, "\\begin_inset Space \\hspace*{\\fill}", i)
2744         if i == -1:
2745             return
2746         j = find_end_of_inset(document.body, i)
2747         if j == -1:
2748             document.warning("Malformed LyX document: Could not find end of space inset.")
2749             i += 1
2750             continue
2751         del document.body[j]
2752         subst = document.body[i].replace(
2753             "\\begin_inset Space \\hspace*{\\fill}",
2754             "\\begin_inset ERT\nstatus collapsed\n\n"
2755             "\\begin_layout Standard\n\n\n\\backslash\n"
2756             "hspace*{\n\\backslash\nfill}\n\\end_layout\n\n\\end_inset\n\n",
2757         )
2758         subst = subst.split("\n")
2759         document.body[i : i + 1] = subst
2760         i += len(subst)
2761
2762
2763 def revert_leftarrowfill(document):
2764     "Revert \\begin_inset Space \\leftarrowfill{} to ERT"
2765     i = 0
2766     while True:
2767         i = find_token(document.body, "\\begin_inset Space \\leftarrowfill{}", i)
2768         if i == -1:
2769             return
2770         j = find_end_of_inset(document.body, i)
2771         if j == -1:
2772             document.warning("Malformed LyX document: Could not find end of space inset.")
2773             i += 1
2774             continue
2775         del document.body[j]
2776         subst = document.body[i].replace(
2777             "\\begin_inset Space \\leftarrowfill{}",
2778             "\\begin_inset ERT\nstatus collapsed\n\n"
2779             "\\begin_layout Standard\n\n\n\\backslash\n"
2780             "leftarrowfill{}\n\\end_layout\n\n\\end_inset\n\n",
2781         )
2782         subst = subst.split("\n")
2783         document.body[i : i + 1] = subst
2784         i += len(subst)
2785
2786
2787 def revert_rightarrowfill(document):
2788     "Revert \\begin_inset Space \\rightarrowfill{} to ERT"
2789     i = 0
2790     while True:
2791         i = find_token(document.body, "\\begin_inset Space \\rightarrowfill{}", i)
2792         if i == -1:
2793             return
2794         j = find_end_of_inset(document.body, i)
2795         if j == -1:
2796             document.warning("Malformed LyX document: Could not find end of space inset.")
2797             i += 1
2798             continue
2799         del document.body[j]
2800         subst = document.body[i].replace(
2801             "\\begin_inset Space \\rightarrowfill{}",
2802             "\\begin_inset ERT\nstatus collapsed\n\n"
2803             "\\begin_layout Standard\n\n\n\\backslash\n"
2804             "rightarrowfill{}\n\\end_layout\n\n\\end_inset\n\n",
2805         )
2806         subst = subst.split("\n")
2807         document.body[i : i + 1] = subst
2808         i += len(subst)
2809
2810
2811 def revert_upbracefill(document):
2812     "Revert \\begin_inset Space \\upbracefill{} to ERT"
2813     i = 0
2814     while True:
2815         i = find_token(document.body, "\\begin_inset Space \\upbracefill{}", i)
2816         if i == -1:
2817             return
2818         j = find_end_of_inset(document.body, i)
2819         if j == -1:
2820             document.warning("Malformed LyX document: Could not find end of space inset.")
2821             i += 1
2822             continue
2823         del document.body[j]
2824         subst = document.body[i].replace(
2825             "\\begin_inset Space \\upbracefill{}",
2826             "\\begin_inset ERT\nstatus collapsed\n\n"
2827             "\\begin_layout Standard\n\n\n\\backslash\n"
2828             "upbracefill{}\n\\end_layout\n\n\\end_inset\n\n",
2829         )
2830         subst = subst.split("\n")
2831         document.body[i : i + 1] = subst
2832         i += len(subst)
2833
2834
2835 def revert_downbracefill(document):
2836     "Revert \\begin_inset Space \\downbracefill{} to ERT"
2837     i = 0
2838     while True:
2839         i = find_token(document.body, "\\begin_inset Space \\downbracefill{}", i)
2840         if i == -1:
2841             return
2842         j = find_end_of_inset(document.body, i)
2843         if j == -1:
2844             document.warning("Malformed LyX document: Could not find end of space inset.")
2845             i += 1
2846             continue
2847         del document.body[j]
2848         subst = document.body[i].replace(
2849             "\\begin_inset Space \\downbracefill{}",
2850             "\\begin_inset ERT\nstatus collapsed\n\n"
2851             "\\begin_layout Standard\n\n\n\\backslash\n"
2852             "downbracefill{}\n\\end_layout\n\n\\end_inset\n\n",
2853         )
2854         subst = subst.split("\n")
2855         document.body[i : i + 1] = subst
2856         i += len(subst)
2857
2858
2859 def revert_local_layout(document):
2860     "Revert local layout headers."
2861     i = 0
2862     while True:
2863         i = find_token(document.header, "\\begin_local_layout", i)
2864         if i == -1:
2865             return
2866         j = find_end_of(document.header, i, "\\begin_local_layout", "\\end_local_layout")
2867         if j == -1:
2868             # this should not happen
2869             break
2870         document.header[i : j + 1] = []
2871
2872
2873 def convert_pagebreaks(document):
2874     "Convert inline Newpage insets to new format"
2875     i = 0
2876     while True:
2877         i = find_token(document.body, "\\newpage", i)
2878         if i == -1:
2879             break
2880         document.body[i : i + 1] = ["\\begin_inset Newpage newpage", "\\end_inset"]
2881     i = 0
2882     while True:
2883         i = find_token(document.body, "\\pagebreak", i)
2884         if i == -1:
2885             break
2886         document.body[i : i + 1] = ["\\begin_inset Newpage pagebreak", "\\end_inset"]
2887     i = 0
2888     while True:
2889         i = find_token(document.body, "\\clearpage", i)
2890         if i == -1:
2891             break
2892         document.body[i : i + 1] = ["\\begin_inset Newpage clearpage", "\\end_inset"]
2893     i = 0
2894     while True:
2895         i = find_token(document.body, "\\cleardoublepage", i)
2896         if i == -1:
2897             break
2898         document.body[i : i + 1] = [
2899             "\\begin_inset Newpage cleardoublepage",
2900             "\\end_inset",
2901         ]
2902
2903
2904 def revert_pagebreaks(document):
2905     "Revert \\begin_inset Newpage to previous inline format"
2906     i = 0
2907     while True:
2908         i = find_token(document.body, "\\begin_inset Newpage", i)
2909         if i == -1:
2910             return
2911         j = find_end_of_inset(document.body, i)
2912         if j == -1:
2913             document.warning("Malformed LyX document: Could not find end of Newpage inset.")
2914             i += 1
2915             continue
2916         del document.body[j]
2917         document.body[i] = document.body[i].replace(
2918             "\\begin_inset Newpage newpage", "\\newpage"
2919         )
2920         document.body[i] = document.body[i].replace(
2921             "\\begin_inset Newpage pagebreak", "\\pagebreak"
2922         )
2923         document.body[i] = document.body[i].replace(
2924             "\\begin_inset Newpage clearpage", "\\clearpage"
2925         )
2926         document.body[i] = document.body[i].replace(
2927             "\\begin_inset Newpage cleardoublepage", "\\cleardoublepage"
2928         )
2929
2930
2931 def convert_linebreaks(document):
2932     "Convert inline Newline insets to new format"
2933     i = 0
2934     while True:
2935         i = find_token(document.body, "\\newline", i)
2936         if i == -1:
2937             break
2938         document.body[i : i + 1] = ["\\begin_inset Newline newline", "\\end_inset"]
2939     i = 0
2940     while True:
2941         i = find_token(document.body, "\\linebreak", i)
2942         if i == -1:
2943             break
2944         document.body[i : i + 1] = ["\\begin_inset Newline linebreak", "\\end_inset"]
2945
2946
2947 def revert_linebreaks(document):
2948     "Revert \\begin_inset Newline to previous inline format"
2949     i = 0
2950     while True:
2951         i = find_token(document.body, "\\begin_inset Newline", i)
2952         if i == -1:
2953             return
2954         j = find_end_of_inset(document.body, i)
2955         if j == -1:
2956             document.warning("Malformed LyX document: Could not find end of Newline inset.")
2957             i += 1
2958             continue
2959         del document.body[j]
2960         document.body[i] = document.body[i].replace(
2961             "\\begin_inset Newline newline", "\\newline"
2962         )
2963         document.body[i] = document.body[i].replace(
2964             "\\begin_inset Newline linebreak", "\\linebreak"
2965         )
2966
2967
2968 def convert_japanese_plain(document):
2969     "Set language japanese-plain to japanese"
2970     i = 0
2971     if document.language == "japanese-plain":
2972         document.language = "japanese"
2973         i = find_token(document.header, "\\language", 0)
2974         if i != -1:
2975             document.header[i] = "\\language japanese"
2976     j = 0
2977     while True:
2978         j = find_token(document.body, "\\lang japanese-plain", j)
2979         if j == -1:
2980             return
2981         document.body[j] = document.body[j].replace("\\lang japanese-plain", "\\lang japanese")
2982         j = j + 1
2983
2984
2985 def revert_pdfpages(document):
2986     "Revert pdfpages external inset to ERT"
2987     i = 0
2988     while True:
2989         i = find_token(document.body, "\\begin_inset External", i)
2990         if i == -1:
2991             return
2992         j = find_end_of_inset(document.body, i)
2993         if j == -1:
2994             document.warning(
2995                 "Malformed lyx document: Missing '\\end_inset' in revert_pdfpages."
2996             )
2997             i = i + 1
2998             continue
2999         if get_value(document.body, "template", i, j) == "PDFPages":
3000             filename = get_value(document.body, "filename", i, j)
3001             extra = ""
3002             r = re.compile(r"\textra PDFLaTeX \"(.*)\"$")
3003             for k in range(i, j):
3004                 m = r.match(document.body[k])
3005                 if m:
3006                     extra = m.group(1)
3007             angle = get_value(document.body, "rotateAngle", i, j)
3008             width = get_value(document.body, "width", i, j)
3009             height = get_value(document.body, "height", i, j)
3010             scale = get_value(document.body, "scale", i, j)
3011             keepAspectRatio = find_token(document.body, "\tkeepAspectRatio", i, j)
3012             options = extra
3013             if angle != "":
3014                 if options != "":
3015                     options += ",angle=" + angle
3016                 else:
3017                     options += "angle=" + angle
3018             if width != "":
3019                 if options != "":
3020                     options += ",width=" + convert_len(width)
3021                 else:
3022                     options += "width=" + convert_len(width)
3023             if height != "":
3024                 if options != "":
3025                     options += ",height=" + convert_len(height)
3026                 else:
3027                     options += "height=" + convert_len(height)
3028             if scale != "":
3029                 if options != "":
3030                     options += ",scale=" + scale
3031                 else:
3032                     options += "scale=" + scale
3033             if keepAspectRatio != "":
3034                 if options != "":
3035                     options += ",keepaspectratio"
3036                 else:
3037                     options += "keepaspectratio"
3038             if options != "":
3039                 options = "[" + options + "]"
3040             del document.body[i + 1 : j + 1]
3041             document.body[i : i + 1] = [
3042                 "\\begin_inset ERT",
3043                 "status collapsed",
3044                 "",
3045                 "\\begin_layout Standard",
3046                 "",
3047                 "\\backslash",
3048                 "includepdf" + options + "{" + filename + "}",
3049                 "\\end_layout",
3050                 "",
3051                 "\\end_inset",
3052             ]
3053             add_to_preamble(document, ["\\usepackage{pdfpages}\n"])
3054             i = i + 1
3055             continue
3056         i = i + 1
3057
3058
3059 def revert_mexican(document):
3060     "Set language Spanish(Mexico) to Spanish"
3061     i = 0
3062     if document.language == "spanish-mexico":
3063         document.language = "spanish"
3064         i = find_token(document.header, "\\language", 0)
3065         if i != -1:
3066             document.header[i] = "\\language spanish"
3067     j = 0
3068     while True:
3069         j = find_token(document.body, "\\lang spanish-mexico", j)
3070         if j == -1:
3071             return
3072         document.body[j] = document.body[j].replace("\\lang spanish-mexico", "\\lang spanish")
3073         j = j + 1
3074
3075
3076 def remove_embedding(document):
3077     "Remove embed tag from all insets"
3078     revert_inset_embedding(document, "Graphics")
3079     revert_inset_embedding(document, "External")
3080     revert_inset_embedding(document, "CommandInset include")
3081     revert_inset_embedding(document, "CommandInset bibtex")
3082
3083
3084 def revert_master(document):
3085     "Remove master param"
3086     i = find_token(document.header, "\\master", 0)
3087     if i != -1:
3088         del document.header[i]
3089
3090
3091 def revert_graphics_group(document):
3092     "Revert group information from graphics insets"
3093     i = 0
3094     while True:
3095         i = find_token(document.body, "\\begin_inset Graphics", i)
3096         if i == -1:
3097             return
3098         j = find_end_of_inset(document.body, i)
3099         if j == -1:
3100             document.warning(
3101                 "Malformed lyx document: Missing '\\end_inset' in revert_graphics_group."
3102             )
3103             i = i + 1
3104             continue
3105         k = find_token(document.body, " groupId", i, j)
3106         if k == -1:
3107             i = i + 1
3108             continue
3109         del document.body[k]
3110         i = i + 1
3111
3112
3113 def update_apa_styles(document):
3114     "Replace obsolete styles"
3115
3116     if document.textclass != "apa":
3117         return
3118
3119     obsoletedby = {
3120         "Acknowledgments": "Acknowledgements",
3121         "Section*": "Section",
3122         "Subsection*": "Subsection",
3123         "Subsubsection*": "Subsubsection",
3124         "Paragraph*": "Paragraph",
3125         "Subparagraph*": "Subparagraph",
3126     }
3127     i = 0
3128     while True:
3129         i = find_token(document.body, "\\begin_layout", i)
3130         if i == -1:
3131             return
3132
3133         layout = document.body[i][14:]
3134         if layout in obsoletedby:
3135             document.body[i] = "\\begin_layout " + obsoletedby[layout]
3136
3137         i += 1
3138
3139
3140 def convert_paper_sizes(document):
3141     "exchange size options legalpaper and executivepaper to correct order"
3142     # routine is needed to fix http://www.lyx.org/trac/ticket/4868
3143     i = 0
3144     j = 0
3145     i = find_token(document.header, "\\papersize executivepaper", 0)
3146     if i != -1:
3147         document.header[i] = "\\papersize legalpaper"
3148         return
3149     j = find_token(document.header, "\\papersize legalpaper", 0)
3150     if j != -1:
3151         document.header[j] = "\\papersize executivepaper"
3152
3153
3154 def revert_paper_sizes(document):
3155     "exchange size options legalpaper and executivepaper to correct order"
3156     i = 0
3157     j = 0
3158     i = find_token(document.header, "\\papersize executivepaper", 0)
3159     if i != -1:
3160         document.header[i] = "\\papersize legalpaper"
3161         return
3162     j = find_token(document.header, "\\papersize legalpaper", 0)
3163     if j != -1:
3164         document.header[j] = "\\papersize executivepaper"
3165
3166
3167 def convert_InsetSpace(document):
3168     "Convert '\\begin_inset Space foo' to '\\begin_inset space foo'"
3169     i = 0
3170     while True:
3171         i = find_token(document.body, "\\begin_inset Space", i)
3172         if i == -1:
3173             return
3174         document.body[i] = document.body[i].replace(
3175             "\\begin_inset Space", "\\begin_inset space"
3176         )
3177
3178
3179 def revert_InsetSpace(document):
3180     "Revert '\\begin_inset space foo' to '\\begin_inset Space foo'"
3181     i = 0
3182     while True:
3183         i = find_token(document.body, "\\begin_inset space", i)
3184         if i == -1:
3185             return
3186         document.body[i] = document.body[i].replace(
3187             "\\begin_inset space", "\\begin_inset Space"
3188         )
3189
3190
3191 def convert_display_enum(document):
3192     "Convert 'display foo' to 'display false/true'"
3193     i = 0
3194     while True:
3195         i = find_token(document.body, "\tdisplay", i)
3196         if i == -1:
3197             return
3198         val = get_value(document.body, "display", i)
3199         if val == "none":
3200             document.body[i] = document.body[i].replace("none", "false")
3201         if val == "default":
3202             document.body[i] = document.body[i].replace("default", "true")
3203         if val == "monochrome":
3204             document.body[i] = document.body[i].replace("monochrome", "true")
3205         if val == "grayscale":
3206             document.body[i] = document.body[i].replace("grayscale", "true")
3207         if val == "color":
3208             document.body[i] = document.body[i].replace("color", "true")
3209         if val == "preview":
3210             document.body[i] = document.body[i].replace("preview", "true")
3211         i += 1
3212
3213
3214 def revert_display_enum(document):
3215     "Revert 'display false/true' to 'display none/color'"
3216     i = 0
3217     while True:
3218         i = find_token(document.body, "\tdisplay", i)
3219         if i == -1:
3220             return
3221         val = get_value(document.body, "display", i)
3222         if val == "false":
3223             document.body[i] = document.body[i].replace("false", "none")
3224         if val == "true":
3225             document.body[i] = document.body[i].replace("true", "default")
3226         i += 1
3227
3228
3229 def remove_fontsCJK(document):
3230     "Remove font_cjk param"
3231     i = find_token(document.header, "\\font_cjk", 0)
3232     if i != -1:
3233         del document.header[i]
3234
3235
3236 def convert_plain_layout(document):
3237     "Convert 'PlainLayout' to 'Plain Layout'"
3238     i = 0
3239     while True:
3240         i = find_token(document.body, "\\begin_layout PlainLayout", i)
3241         if i == -1:
3242             return
3243         document.body[i] = document.body[i].replace(
3244             "\\begin_layout PlainLayout", "\\begin_layout Plain Layout"
3245         )
3246         i += 1
3247
3248
3249 def revert_plain_layout(document):
3250     "Revert 'Plain Layout' to 'PlainLayout'"
3251     i = 0
3252     while True:
3253         i = find_token(document.body, "\\begin_layout Plain Layout", i)
3254         if i == -1:
3255             return
3256         document.body[i] = document.body[i].replace(
3257             "\\begin_layout Plain Layout", "\\begin_layout PlainLayout"
3258         )
3259         i += 1
3260
3261
3262 def revert_plainlayout(document):
3263     "Revert 'PlainLayout' to 'Standard'"
3264     i = 0
3265     while True:
3266         i = find_token(document.body, "\\begin_layout PlainLayout", i)
3267         if i == -1:
3268             return
3269         # This will be incorrect for some document classes, since Standard is not always
3270         # the default. But (a) it is probably the best we can do and (b) it will actually
3271         # work, in fact, since an unknown layout will be converted to default.
3272         document.body[i] = document.body[i].replace(
3273             "\\begin_layout PlainLayout", "\\begin_layout Standard"
3274         )
3275         i += 1
3276
3277
3278 def revert_polytonicgreek(document):
3279     "Set language polytonic Greek to Greek"
3280     i = 0
3281     if document.language == "polutonikogreek":
3282         document.language = "greek"
3283         i = find_token(document.header, "\\language", 0)
3284         if i != -1:
3285             document.header[i] = "\\language greek"
3286     j = 0
3287     while True:
3288         j = find_token(document.body, "\\lang polutonikogreek", j)
3289         if j == -1:
3290             return
3291         document.body[j] = document.body[j].replace("\\lang polutonikogreek", "\\lang greek")
3292         j = j + 1
3293
3294
3295 def revert_removed_modules(document):
3296     i = 0
3297     while True:
3298         i = find_token(document.header, "\\begin_remove_modules", i)
3299         if i == -1:
3300             return
3301         j = find_end_of(document.header, i, "\\begin_remove_modules", "\\end_remove_modules")
3302         if j == -1:
3303             # this should not happen
3304             break
3305         document.header[i : j + 1] = []
3306
3307
3308 def add_plain_layout(document):
3309     i = 0
3310     while True:
3311         i = find_token(document.body, "\\begin_layout", i)
3312         if i == -1:
3313             return
3314         if len(document.body[i].split()) == 1:
3315             document.body[i] = "\\begin_layout Plain Layout"
3316         i += 1
3317
3318
3319 def revert_tabulators(document):
3320     "Revert tabulators to 4 spaces"
3321     i = 0
3322     while True:
3323         i = find_token(document.body, "\t", i)
3324         if i == -1:
3325             return
3326         document.body[i] = document.body[i].replace("\t", "    ")
3327         i += 1
3328
3329
3330 def revert_tabsize(document):
3331     "Revert the tabsize parameter of listings"
3332     i = 0
3333     j = 0
3334     while True:
3335         # either it is the only parameter
3336         i = find_token(document.body, 'lstparams "tabsize=4"', i)
3337         if i != -1:
3338             del document.body[i]
3339         # or the last one
3340         j = find_token(document.body, "lstparams", j)
3341         if j == -1:
3342             return
3343         pos = document.body[j].find(",tabsize=")
3344         document.body[j] = document.body[j][:pos] + '"'
3345         i += 1
3346         j += 1
3347
3348
3349 def revert_mongolian(document):
3350     "Set language Mongolian to English"
3351     i = 0
3352     if document.language == "mongolian":
3353         document.language = "english"
3354         i = find_token(document.header, "\\language", 0)
3355         if i != -1:
3356             document.header[i] = "\\language english"
3357     j = 0
3358     while True:
3359         j = find_token(document.body, "\\lang mongolian", j)
3360         if j == -1:
3361             return
3362         document.body[j] = document.body[j].replace("\\lang mongolian", "\\lang english")
3363         j = j + 1
3364
3365
3366 def revert_default_options(document):
3367     "Remove param use_default_options"
3368     i = find_token(document.header, "\\use_default_options", 0)
3369     if i != -1:
3370         del document.header[i]
3371
3372
3373 def convert_default_options(document):
3374     "Add param use_default_options and set it to false"
3375     i = find_token(document.header, "\\textclass", 0)
3376     if i == -1:
3377         document.warning("Malformed LyX document: Missing `\\textclass'.")
3378         return
3379     document.header.insert(i, "\\use_default_options false")
3380
3381
3382 def revert_backref_options(document):
3383     "Revert option pdf_backref=page to pagebackref"
3384     i = find_token(document.header, "\\pdf_backref page", 0)
3385     if i != -1:
3386         document.header[i] = "\\pdf_pagebackref true"
3387
3388
3389 def convert_backref_options(document):
3390     "We have changed the option pagebackref to backref=true"
3391     i = find_token(document.header, "\\pdf_pagebackref true", 0)
3392     if i != -1:
3393         document.header[i] = "\\pdf_backref page"
3394     j = find_token(document.header, "\\pdf_pagebackref false", 0)
3395     if j != -1:
3396         del document.header[j]
3397     # backref=true was not a valid option, we meant backref=section
3398     k = find_token(document.header, "\\pdf_backref true", 0)
3399     if k != -1 and i != -1:
3400         del document.header[k]
3401     elif k != -1 and j != -1:
3402         document.header[k] = "\\pdf_backref section"
3403
3404
3405 def convert_charstyle_element(document):
3406     "Convert CharStyle to Element for docbook backend"
3407     if document.backend != "docbook":
3408         return
3409     i = 0
3410     while True:
3411         i = find_token(document.body, "\\begin_inset Flex CharStyle:", i)
3412         if i == -1:
3413             return
3414         document.body[i] = document.body[i].replace(
3415             "\\begin_inset Flex CharStyle:", "\\begin_inset Flex Element:"
3416         )
3417
3418
3419 def revert_charstyle_element(document):
3420     "Convert Element to CharStyle for docbook backend"
3421     if document.backend != "docbook":
3422         return
3423     i = 0
3424     while True:
3425         i = find_token(document.body, "\\begin_inset Flex Element:", i)
3426         if i == -1:
3427             return
3428         document.body[i] = document.body[i].replace(
3429             "\\begin_inset Flex Element:", "\\begin_inset Flex CharStyle:"
3430         )
3431
3432
3433 ##
3434 # Conversion hub
3435 #
3436
3437 supported_versions = ["1.6.0", "1.6"]
3438 convert = [
3439     [277, [fix_wrong_tables]],
3440     [278, [close_begin_deeper]],
3441     [279, [long_charstyle_names]],
3442     [280, [axe_show_label]],
3443     [281, []],
3444     [282, []],
3445     [283, [convert_flex]],
3446     [284, []],
3447     [285, []],
3448     [286, []],
3449     [287, [convert_wrapfig_options]],
3450     [288, [convert_inset_command]],
3451     [289, [convert_latexcommand_index]],
3452     [290, []],
3453     [291, []],
3454     [292, [convert_japanese_cjk]],
3455     [293, []],
3456     [294, [convert_pdf_options]],
3457     [295, [convert_htmlurl, convert_url]],
3458     [296, [convert_include]],
3459     [297, [convert_usorbian]],
3460     [298, [convert_macro_global]],
3461     [299, []],
3462     [300, []],
3463     [301, []],
3464     [302, []],
3465     [303, [convert_serbocroatian]],
3466     [304, [convert_framed_notes]],
3467     [305, []],
3468     [306, []],
3469     [307, []],
3470     [308, []],
3471     [309, []],
3472     [310, []],
3473     [311, [convert_ams_classes]],
3474     [312, []],
3475     [313, [convert_module_names]],
3476     [314, []],
3477     [315, []],
3478     [316, [convert_subfig]],
3479     [317, []],
3480     [318, []],
3481     [319, [convert_spaceinset, convert_hfill]],
3482     [320, []],
3483     [321, [convert_tablines]],
3484     [322, [convert_plain_layout]],
3485     [323, [convert_pagebreaks]],
3486     [324, [convert_linebreaks]],
3487     [325, [convert_japanese_plain]],
3488     [326, []],
3489     [327, []],
3490     [328, [remove_embedding, remove_extra_embedded_files, remove_inzip_options]],
3491     [329, []],
3492     [330, []],
3493     [331, [convert_ltcaption]],
3494     [332, []],
3495     [333, [update_apa_styles]],
3496     [334, [convert_paper_sizes]],
3497     [335, [convert_InsetSpace]],
3498     [336, []],
3499     [337, [convert_display_enum]],
3500     [338, []],
3501     [339, []],
3502     [340, [add_plain_layout]],
3503     [341, []],
3504     [342, []],
3505     [343, [convert_default_options]],
3506     [344, [convert_backref_options]],
3507     [345, [convert_charstyle_element]],
3508 ]
3509
3510 revert = [
3511     [344, [revert_charstyle_element]],
3512     [343, [revert_backref_options]],
3513     [342, [revert_default_options]],
3514     [341, [revert_mongolian]],
3515     [340, [revert_tabulators, revert_tabsize]],
3516     [339, []],
3517     [338, [revert_removed_modules]],
3518     [337, [revert_polytonicgreek]],
3519     [336, [revert_display_enum]],
3520     [335, [remove_fontsCJK]],
3521     [334, [revert_InsetSpace]],
3522     [333, [revert_paper_sizes]],
3523     [332, []],
3524     [331, [revert_graphics_group]],
3525     [330, [revert_ltcaption]],
3526     [
3527         329,
3528         [
3529             revert_leftarrowfill,
3530             revert_rightarrowfill,
3531             revert_upbracefill,
3532             revert_downbracefill,
3533         ],
3534     ],
3535     [328, [revert_master]],
3536     [327, []],
3537     [326, [revert_mexican]],
3538     [325, [revert_pdfpages]],
3539     [324, []],
3540     [323, [revert_linebreaks]],
3541     [322, [revert_pagebreaks]],
3542     [321, [revert_local_layout, revert_plain_layout]],
3543     [320, [revert_tablines]],
3544     [319, [revert_protected_hfill]],
3545     [318, [revert_spaceinset, revert_hfills, revert_hspace]],
3546     [317, [remove_extra_embedded_files]],
3547     [316, [revert_wrapplacement]],
3548     [315, [revert_subfig]],
3549     [314, [revert_colsep, revert_plainlayout]],
3550     [313, []],
3551     [312, [revert_module_names]],
3552     [311, [revert_rotfloat, revert_widesideways]],
3553     [310, [revert_external_embedding]],
3554     [309, [revert_btprintall]],
3555     [308, [revert_nocite]],
3556     [307, [revert_serbianlatin]],
3557     [306, [revert_slash, revert_nobreakdash]],
3558     [305, [revert_interlingua]],
3559     [304, [revert_bahasam]],
3560     [303, [revert_framed_notes]],
3561     [302, []],
3562     [301, [revert_latin, revert_samin]],
3563     [300, [revert_linebreak]],
3564     [299, [revert_pagebreak]],
3565     [298, [revert_hyperlinktype]],
3566     [297, [revert_macro_optional_params]],
3567     [296, [revert_albanian, revert_lowersorbian, revert_uppersorbian]],
3568     [295, [revert_include]],
3569     [294, [revert_href, revert_url]],
3570     [293, [revert_pdf_options_2]],
3571     [292, [revert_inset_info]],
3572     [291, [revert_japanese, revert_japanese_encoding, revert_japanese_cjk]],
3573     [290, [revert_vietnamese]],
3574     [289, [revert_wraptable]],
3575     [288, [revert_latexcommand_index]],
3576     [287, [revert_inset_command]],
3577     [286, [revert_wrapfig_options]],
3578     [285, [revert_pdf_options]],
3579     [284, [remove_inzip_options]],
3580     [283, []],
3581     [282, [revert_flex]],
3582     [281, []],
3583     [280, [revert_begin_modules]],
3584     [279, [revert_show_label]],
3585     [278, [revert_long_charstyle_names]],
3586     [277, []],
3587     [276, []],
3588 ]
3589
3590
3591 if __name__ == "__main__":
3592     pass