]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx2lyx_tools.py
revert_language: fix document language assignment
[lyx.git] / lib / lyx2lyx / lyx2lyx_tools.py
1 # This file is part of lyx2lyx
2 # -*- coding: utf-8 -*-
3 # Copyright (C) 2011 The LyX team
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18
19 '''
20 This module offers several free functions to help with lyx2lyx'ing.
21 More documentaton is below, but here is a quick guide to what
22 they do. Optional arguments are marked by brackets.
23
24 add_to_preamble(document, text):
25   Here, text can be either a single line or a list of lines. It
26   is bad practice to pass something with embedded newlines, but
27   we will handle that properly.
28   The routine checks to see whether the provided material is
29   already in the preamble. If not, it adds it.
30   Prepends a comment "% Added by lyx2lyx" to text.
31
32 insert_to_preamble(document, text[, index]):
33   Here, text can be either a single line or a list of lines. It
34   is bad practice to pass something with embedded newlines, but
35   we will handle that properly.
36   The routine inserts text at document.preamble[index], where by
37   default index is 0, so the material is inserted at the beginning.
38   Prepends a comment "% Added by lyx2lyx" to text.
39
40 put_cmd_in_ert(cmd):
41   Here cmd should be a list of strings (lines), which we want to
42   wrap in ERT. Returns a list of strings so wrapped.
43   A call to this routine will often go something like this:
44     i = find_token('\\begin_inset FunkyInset', ...)
45     j = find_end_of_inset(document.body, i)
46     content = lyx2latex(document[i:j + 1])
47     ert = put_cmd_in_ert(content)
48     document.body[i:j+1] = ert
49
50 get_ert(lines, i[, verbatim]):
51   Here, lines is a list of lines of LyX material containing an ERT inset,
52   whose content we want to convert to LaTeX. The ERT starts at index i.
53   If the optional (by default: False) bool verbatim is True, the content
54   of the ERT is returned verbatim, that is in LyX syntax (not LaTeX syntax)
55   for the use in verbatim insets.
56
57 lyx2latex(document, lines):
58   Here, lines is a list of lines of LyX material we want to convert
59   to LaTeX. We do the best we can and return a string containing
60   the translated material.
61
62 lyx2verbatim(document, lines):
63   Here, lines is a list of lines of LyX material we want to convert
64   to verbatim material (used in ERT an the like). We do the best we
65   can and return a string containing the translated material.
66
67 latex_length(slen):
68   Convert lengths (in LyX form) to their LaTeX representation. Returns
69   (bool, length), where the bool tells us if it was a percentage, and
70   the length is the LaTeX representation.
71
72 convert_info_insets(document, type, func):
73   Applies func to the argument of all info insets matching certain types
74   type : the type to match. This can be a regular expression.
75   func : function from string to string to apply to the "arg" field of
76          the info insets.
77
78 is_document_option(document, option):
79   Find if _option_ is a document option (\\options in the header).
80
81 insert_document_option(document, option):
82   Insert _option_ as a document option.
83
84 remove_document_option(document, option):
85   Remove _option_ as a document option.
86
87 revert_language(document, lyxname, babelname, polyglossianame):
88   Reverts native language support to ERT
89   If babelname or polyglossianame is empty, it is assumed
90   this language package is not supported for the given language.
91 '''
92
93 import re
94 from parser_tools import find_token, find_end_of_inset, get_containing_layout, get_value, get_bool_value
95 from unicode_symbols import unicode_reps
96
97 # This will accept either a list of lines or a single line.
98 # It is bad practice to pass something with embedded newlines,
99 # though we will handle that.
100 def add_to_preamble(document, text):
101     " Add text to the preamble if it is not already there. "
102
103     if not type(text) is list:
104       # split on \n just in case
105       # it'll give us the one element list we want
106       # if there's no \n, too
107       text = text.split('\n')
108
109     i = 0
110     prelen = len(document.preamble)
111     while True:
112       i = find_token(document.preamble, text[0], i)
113       if i == -1:
114         break
115       # we need a perfect match
116       matched = True
117       for line in text:
118         if i >= prelen or line != document.preamble[i]:
119           matched = False
120           break
121         i += 1
122       if matched:
123         return
124
125     document.preamble.extend(["% Added by lyx2lyx"])
126     document.preamble.extend(text)
127
128
129 # Note that text can be either a list of lines or a single line.
130 # It should really be a list.
131 def insert_to_preamble(document, text, index = 0):
132     """ Insert text to the preamble at a given line"""
133
134     if not type(text) is list:
135       # split on \n just in case
136       # it'll give us the one element list we want
137       # if there's no \n, too
138       text = text.split('\n')
139
140     text.insert(0, "% Added by lyx2lyx")
141     document.preamble[index:index] = text
142
143
144 # A dictionary of Unicode->LICR mappings for use in a Unicode string's translate() method
145 # Created from the reversed list to keep the first of alternative definitions.
146 licr_table = dict((ord(ch), cmd) for cmd, ch in unicode_reps[::-1])
147
148 def put_cmd_in_ert(cmd):
149     """
150     Return ERT inset wrapping `cmd` as a list of strings.
151
152     `cmd` can be a string or list of lines. Non-ASCII characters are converted
153     to the respective LICR macros if defined in unicodesymbols.
154     """
155     ret = ["\\begin_inset ERT", "status collapsed", "", "\\begin_layout Plain Layout", ""]
156     # It will be faster to work with a single string internally.
157     if isinstance(cmd, list):
158         cmd = u"\n".join(cmd)
159     else:
160         cmd = u"%s" % cmd # ensure it is an unicode instance
161     cmd = cmd.translate(licr_table)
162     cmd = cmd.replace("\\", "\n\\backslash\n")
163     ret += cmd.splitlines()
164     ret += ["\\end_layout", "", "\\end_inset"]
165     return ret
166
167
168 def get_ert(lines, i, verbatim = False):
169     'Convert an ERT inset into LaTeX.'
170     if not lines[i].startswith("\\begin_inset ERT"):
171         return ""
172     j = find_end_of_inset(lines, i)
173     if j == -1:
174         return ""
175     while i < j and not lines[i].startswith("status"):
176         i = i + 1
177     i = i + 1
178     ret = ""
179     first = True
180     while i < j:
181         if lines[i] == "\\begin_layout Plain Layout":
182             if first:
183                 first = False
184             else:
185                 ret = ret + "\n"
186             while i + 1 < j and lines[i+1] == "":
187                 i = i + 1
188         elif lines[i] == "\\end_layout":
189             while i + 1 < j and lines[i+1] == "":
190                 i = i + 1
191         elif lines[i] == "\\backslash":
192             if verbatim:
193                 ret = ret + "\n" + lines[i] + "\n"
194             else:
195                 ret = ret + "\\"
196         else:
197             ret = ret + lines[i]
198         i = i + 1
199     return ret
200
201
202 def lyx2latex(document, lines):
203     'Convert some LyX stuff into corresponding LaTeX stuff, as best we can.'
204
205     content = ""
206     ert_end = 0
207     note_end = 0
208     hspace = ""
209
210     for curline in range(len(lines)):
211       line = lines[curline]
212       if line.startswith("\\begin_inset Note Note"):
213           # We want to skip LyX notes, so remember where the inset ends
214           note_end = find_end_of_inset(lines, curline + 1)
215           continue
216       elif note_end >= curline:
217           # Skip LyX notes
218           continue
219       elif line.startswith("\\begin_inset ERT"):
220           # We don't want to replace things inside ERT, so figure out
221           # where the end of the inset is.
222           ert_end = find_end_of_inset(lines, curline + 1)
223           continue
224       elif line.startswith("\\begin_inset Formula"):
225           line = line[20:]
226       elif line.startswith("\\begin_inset Quotes"):
227           # For now, we do a very basic reversion. Someone who understands
228           # quotes is welcome to fix it up.
229           qtype = line[20:].strip()
230           # lang = qtype[0]
231           side = qtype[1]
232           dbls = qtype[2]
233           if side == "l":
234               if dbls == "d":
235                   line = "``"
236               else:
237                   line = "`"
238           else:
239               if dbls == "d":
240                   line = "''"
241               else:
242                   line = "'"
243       elif line.startswith("\\begin_inset Newline newline"):
244           line = "\\\\ "
245       elif line.startswith("\\noindent"):
246           line = "\\noindent " # we need the space behind the command
247       elif line.startswith("\\begin_inset space"):
248           line = line[18:].strip()
249           if line.startswith("\\hspace"):
250               # Account for both \hspace and \hspace*
251               hspace = line[:-2]
252               continue
253           elif line == "\\space{}":
254               line = "\\ "
255           elif line == "\\thinspace{}":
256               line = "\\,"
257       elif hspace != "":
258           # The LyX length is in line[8:], after the \length keyword
259           length = latex_length(line[8:])[1]
260           line = hspace + "{" + length + "}"
261           hspace = ""
262       elif line.isspace() or \
263             line.startswith("\\begin_layout") or \
264             line.startswith("\\end_layout") or \
265             line.startswith("\\begin_inset") or \
266             line.startswith("\\end_inset") or \
267             line.startswith("\\lang") or \
268             line.strip() == "status collapsed" or \
269             line.strip() == "status open":
270           #skip all that stuff
271           continue
272
273       # this needs to be added to the preamble because of cases like
274       # \textmu, \textbackslash, etc.
275       add_to_preamble(document, ['% added by lyx2lyx for converted index entries',
276                                  '\\@ifundefined{textmu}',
277                                  ' {\\usepackage{textcomp}}{}'])
278       # a lossless reversion is not possible
279       # try at least to handle some common insets and settings
280       if ert_end >= curline:
281           line = line.replace(r'\backslash', '\\')
282       else:
283           # No need to add "{}" after single-nonletter macros
284           line = line.replace('&', '\\&')
285           line = line.replace('#', '\\#')
286           line = line.replace('^', '\\textasciicircum{}')
287           line = line.replace('%', '\\%')
288           line = line.replace('_', '\\_')
289           line = line.replace('$', '\\$')
290
291           # Do the LyX text --> LaTeX conversion
292           for rep in unicode_reps:
293               line = line.replace(rep[1], rep[0])
294           line = line.replace(r'\backslash', r'\textbackslash{}')
295           line = line.replace(r'\series bold', r'\bfseries{}').replace(r'\series default', r'\mdseries{}')
296           line = line.replace(r'\shape italic', r'\itshape{}').replace(r'\shape smallcaps', r'\scshape{}')
297           line = line.replace(r'\shape slanted', r'\slshape{}').replace(r'\shape default', r'\upshape{}')
298           line = line.replace(r'\emph on', r'\em{}').replace(r'\emph default', r'\em{}')
299           line = line.replace(r'\noun on', r'\scshape{}').replace(r'\noun default', r'\upshape{}')
300           line = line.replace(r'\bar under', r'\underbar{').replace(r'\bar default', r'}')
301           line = line.replace(r'\family sans', r'\sffamily{}').replace(r'\family default', r'\normalfont{}')
302           line = line.replace(r'\family typewriter', r'\ttfamily{}').replace(r'\family roman', r'\rmfamily{}')
303           line = line.replace(r'\InsetSpace ', r'').replace(r'\SpecialChar ', r'')
304       content += line
305     return content
306
307
308 def lyx2verbatim(document, lines):
309     'Convert some LyX stuff into corresponding verbatim stuff, as best we can.'
310
311     content = lyx2latex(document, lines)
312     content = re.sub(r'\\(?!backslash)', r'\n\\backslash\n', content)
313
314     return content
315
316
317 def latex_length(slen):
318     '''
319     Convert lengths to their LaTeX representation. Returns (bool, length),
320     where the bool tells us if it was a percentage, and the length is the
321     LaTeX representation.
322     '''
323     i = 0
324     percent = False
325     # the slen has the form
326     # ValueUnit+ValueUnit-ValueUnit or
327     # ValueUnit+-ValueUnit
328     # the + and - (glue lengths) are optional
329     # the + always precedes the -
330
331     # Convert relative lengths to LaTeX units
332     units = {"col%": "\\columnwidth",
333              "text%": "\\textwidth",
334              "page%": "\\paperwidth",
335              "line%": "\\linewidth",
336              "theight%": "\\textheight",
337              "pheight%": "\\paperheight",
338              "baselineskip%": "\\baselineskip"
339             }
340     for unit in list(units.keys()):
341         i = slen.find(unit)
342         if i == -1:
343             continue
344         percent = True
345         minus = slen.rfind("-", 1, i)
346         plus = slen.rfind("+", 0, i)
347         latex_unit = units[unit]
348         if plus == -1 and minus == -1:
349             value = slen[:i]
350             value = str(float(value)/100)
351             end = slen[i + len(unit):]
352             slen = value + latex_unit + end
353         if plus > minus:
354             value = slen[plus + 1:i]
355             value = str(float(value)/100)
356             begin = slen[:plus + 1]
357             end = slen[i+len(unit):]
358             slen = begin + value + latex_unit + end
359         if plus < minus:
360             value = slen[minus + 1:i]
361             value = str(float(value)/100)
362             begin = slen[:minus + 1]
363             slen = begin + value + latex_unit
364
365     # replace + and -, but only if the - is not the first character
366     slen = slen[0] + slen[1:].replace("+", " plus ").replace("-", " minus ")
367     # handle the case where "+-1mm" was used, because LaTeX only understands
368     # "plus 1mm minus 1mm"
369     if slen.find("plus  minus"):
370         lastvaluepos = slen.rfind(" ")
371         lastvalue = slen[lastvaluepos:]
372         slen = slen.replace("  ", lastvalue + " ")
373     return (percent, slen)
374
375
376 def length_in_bp(length):
377     " Convert a length in LyX format to its value in bp units "
378
379     em_width = 10.0 / 72.27 # assume 10pt font size
380     text_width = 8.27 / 1.7 # assume A4 with default margins
381     # scale factors are taken from Length::inInch()
382     scales = {"bp"       : 1.0,
383               "cc"       : (72.0 / (72.27 / (12.0 * 0.376 * 2.845))),
384               "cm"       : (72.0 / 2.54),
385               "dd"       : (72.0 / (72.27 / (0.376 * 2.845))),
386               "em"       : (72.0 * em_width),
387               "ex"       : (72.0 * em_width * 0.4305),
388               "in"       : 72.0,
389               "mm"       : (72.0 / 25.4),
390               "mu"       : (72.0 * em_width / 18.0),
391               "pc"       : (72.0 / (72.27 / 12.0)),
392               "pt"       : (72.0 / (72.27)),
393               "sp"       : (72.0 / (72.27 * 65536.0)),
394               "text%"    : (72.0 * text_width / 100.0),
395               "col%"     : (72.0 * text_width / 100.0), # assume 1 column
396               "page%"    : (72.0 * text_width * 1.7 / 100.0),
397               "line%"    : (72.0 * text_width / 100.0),
398               "theight%" : (72.0 * text_width * 1.787 / 100.0),
399               "pheight%" : (72.0 * text_width * 2.2 / 100.0)}
400
401     rx = re.compile(r'^\s*([^a-zA-Z%]+)([a-zA-Z%]+)\s*$')
402     m = rx.match(length)
403     if not m:
404         document.warning("Invalid length value: " + length + ".")
405         return 0
406     value = m.group(1)
407     unit = m.group(2)
408     if not unit in scales.keys():
409         document.warning("Unknown length unit: " + unit + ".")
410         return value
411     return "%g" % (float(value) * scales[unit])
412
413
414 def revert_flex_inset(lines, name, LaTeXname):
415   " Convert flex insets to TeX code "
416   i = 0
417   while True:
418     i = find_token(lines, '\\begin_inset Flex ' + name, i)
419     if i == -1:
420       return
421     z = find_end_of_inset(lines, i)
422     if z == -1:
423       document.warning("Can't find end of Flex " + name + " inset.")
424       i += 1
425       continue
426     # remove the \end_inset
427     lines[z - 2:z + 1] = put_cmd_in_ert("}")
428     # we need to reset character layouts if necessary
429     j = find_token(lines, '\\emph on', i, z)
430     k = find_token(lines, '\\noun on', i, z)
431     l = find_token(lines, '\\series', i, z)
432     m = find_token(lines, '\\family', i, z)
433     n = find_token(lines, '\\shape', i, z)
434     o = find_token(lines, '\\color', i, z)
435     p = find_token(lines, '\\size', i, z)
436     q = find_token(lines, '\\bar under', i, z)
437     r = find_token(lines, '\\uuline on', i, z)
438     s = find_token(lines, '\\uwave on', i, z)
439     t = find_token(lines, '\\strikeout on', i, z)
440     if j != -1:
441       lines.insert(z - 2, "\\emph default")
442     if k != -1:
443       lines.insert(z - 2, "\\noun default")
444     if l != -1:
445       lines.insert(z - 2, "\\series default")
446     if m != -1:
447       lines.insert(z - 2, "\\family default")
448     if n != -1:
449       lines.insert(z - 2, "\\shape default")
450     if o != -1:
451       lines.insert(z - 2, "\\color inherit")
452     if p != -1:
453       lines.insert(z - 2, "\\size default")
454     if q != -1:
455       lines.insert(z - 2, "\\bar default")
456     if r != -1:
457       lines.insert(z - 2, "\\uuline default")
458     if s != -1:
459       lines.insert(z - 2, "\\uwave default")
460     if t != -1:
461       lines.insert(z - 2, "\\strikeout default")
462     lines[i:i + 4] = put_cmd_in_ert(LaTeXname + "{")
463     i += 1
464
465
466 def revert_font_attrs(lines, name, LaTeXname):
467   " Reverts font changes to TeX code "
468   i = 0
469   changed = False
470   while True:
471     i = find_token(lines, name + ' on', i)
472     if i == -1:
473       break
474     j = find_token(lines, name + ' default', i)
475     k = find_token(lines, name + ' on', i + 1)
476     # if there is no default set, the style ends with the layout
477     # assure hereby that we found the correct layout end
478     if j != -1 and (j < k or k == -1):
479       lines[j:j + 1] = put_cmd_in_ert("}")
480     else:
481       j = find_token(lines, '\\end_layout', i)
482       lines[j:j] = put_cmd_in_ert("}")
483     lines[i:i + 1] = put_cmd_in_ert(LaTeXname + "{")
484     changed = True
485     i += 1
486
487   # now delete all remaining lines that manipulate this attribute
488   i = 0
489   while True:
490     i = find_token(lines, name, i)
491     if i == -1:
492       break
493     del lines[i]
494
495   return changed
496
497
498 def revert_layout_command(lines, name, LaTeXname):
499   " Reverts a command from a layout to TeX code "
500   i = 0
501   while True:
502     i = find_token(lines, '\\begin_layout ' + name, i)
503     if i == -1:
504       return
505     k = -1
506     # find the next layout
507     j = i + 1
508     while k == -1:
509       j = find_token(lines, '\\begin_layout', j)
510       l = len(lines)
511       # if nothing was found it was the last layout of the document
512       if j == -1:
513         lines[l - 4:l - 4] = put_cmd_in_ert("}")
514         k = 0
515       # exclude plain layout because this can be TeX code or another inset
516       elif lines[j] != '\\begin_layout Plain Layout':
517         lines[j - 2:j - 2] = put_cmd_in_ert("}")
518         k = 0
519       else:
520         j += 1
521     lines[i] = '\\begin_layout Standard'
522     lines[i + 1:i + 1] = put_cmd_in_ert(LaTeXname + "{")
523     i += 1
524
525
526 def hex2ratio(s):
527   " Converts an RRGGBB-type hexadecimal string to a float in [0.0,1.0] "
528   try:
529     val = int(s, 16)
530   except:
531     val = 0
532   if val != 0:
533     val += 1
534   return str(val / 256.0)
535
536
537 def str2bool(s):
538   "'true' goes to True, case-insensitively, and we strip whitespace."
539   s = s.strip().lower()
540   return s == "true"
541
542
543 def convert_info_insets(document, type, func):
544     "Convert info insets matching type using func."
545     i = 0
546     type_re = re.compile(r'^type\s+"(%s)"$' % type)
547     arg_re = re.compile(r'^arg\s+"(.*)"$')
548     while True:
549         i = find_token(document.body, "\\begin_inset Info", i)
550         if i == -1:
551             return
552         t = type_re.match(document.body[i + 1])
553         if t:
554             arg = arg_re.match(document.body[i + 2])
555             if arg:
556                 new_arg = func(arg.group(1))
557                 document.body[i + 2] = 'arg   "%s"' % new_arg
558         i += 3
559
560
561 def insert_document_option(document, option):
562     "Insert _option_ as a document option."
563
564     # Find \options in the header
565     options_line = find_token(document.header, "\\options", 0)
566
567     # if the options does not exists add it after the textclass
568     if options_line == -1:
569         textclass_line = find_token(document.header, "\\textclass", 0)
570         document.header.insert(textclass_line +1,
571                                r"\options %s" % option)
572         return
573
574     # add it to the end of the options
575     document.header[options_line] += ",%s" % option
576
577
578 def remove_document_option(document, option):
579     """ Remove _option_ as a document option.
580
581     It is assumed that option belongs to the \options.
582     That can be done running is_document_option(document, option)."""
583
584     options_line = find_token(document.header, "\\options", 0)
585     option_pos = document.header[options_line].find(option)
586
587     # Remove option from \options
588     comma_before_pos = document.header[options_line].rfind(',', 0, option_pos)
589     comma_after_pos  = document.header[options_line].find(',', option_pos)
590
591     # if there are no commas then it is the single option
592     # and the options line should be removed since it will be empty
593     if comma_before_pos == comma_after_pos == -1:
594         del document.header[options_line]
595         return
596
597     # last option
598     options = document.header[options_line]
599     if comma_after_pos == -1:
600         document.header[options_line] = options[:comma_before_pos].rsplit()
601         return
602
603     document.header[options_line] = options[comma_before_pos: comma_after_pos]
604
605
606 def is_document_option(document, option):
607     "Find if _option_ is a document option"
608
609     # Find \options in the header
610     options_line = find_token(document.header, "\\options", 0)
611
612     # \options is not present in the header
613     if options_line == -1:
614         return False
615
616     option_pos = document.header[options_line].find(option)
617     # option is not present in the \options
618     if option_pos == -1:
619         return False
620
621     return True
622
623
624 def revert_language(document, lyxname, babelname, polyglossianame):
625     " Revert native language support "
626
627     # Are we using polyglossia?
628     use_polyglossia = False
629     if get_bool_value(document.header, "\\use_non_tex_fonts"):
630         i = find_token(document.header, "\\language_package")
631         if i == -1:
632             document.warning("Malformed document! Missing \\language_package")
633         else:
634             pack = get_value(document.header, "\\language_package", i)
635             if pack == "default" or pack == "auto":
636                 use_polyglossia = True
637
638     # Do we use this language with polyglossia?
639     with_polyglossia = use_polyglossia and polyglossianame != ""
640     # Do we use this language with babel?
641     with_babel = with_polyglossia == False and babelname != ""
642
643     # Are we dealing with a primary or secondary language?
644     primary = False
645     secondary = False
646
647     orig_doc_language = document.language
648     # Main language first
649     if document.language == lyxname:
650         primary = True
651         document.language = "english"
652         i = find_token(document.header, "\\language %s" % lyxname, 0)
653         if i != -1:
654             document.header[i] = "\\language english"
655         j = find_token(document.header, "\\language_package default", 0)
656         if j != -1:
657             document.header[j] = "\\language_package default"
658         if with_polyglossia:
659             add_to_preamble(document, ["\\AtBeginDocument{\setotherlanguage{%s}}" % polyglossianame])
660             document.body[2 : 2] = ["\\begin_layout Standard",
661                                     "\\begin_inset ERT", "status open", "",
662                                     "\\begin_layout Plain Layout", "", "",
663                                     "\\backslash",
664                                     "resetdefaultlanguage{%s}" % polyglossianame,
665                                     "\\end_layout", "", "\\end_inset", "", "",
666                                     "\\end_layout", ""]
667
668     # Now secondary languages
669     i = 0
670     while True:
671         i = find_token(document.body, '\\lang', i)
672         if i == -1:
673             break
674         if document.body[i].startswith('\\lang %s' % lyxname):
675             secondary = True
676             endlang = get_containing_layout(document.body, i)[2]
677             langswitch = find_token(document.body, '\\lang', i + 1, endlang)
678             startlayout = "\\begin_layout Standard"
679             endlayout = "\\end_layout"
680             if langswitch != -1:
681                 endlang = langswitch
682                 startlayout = ""
683                 endlayout = ""
684             if with_polyglossia:
685                 add_to_preamble(document, ["\\AtBeginDocument{\setotherlanguage{%s}}" % polyglossianame])     
686                 document.body[endlang : endlang] = [startlayout,
687                                         "\\begin_inset ERT", "status open", "",
688                                         "\\begin_layout Plain Layout", "", "",
689                                         "\\backslash",
690                                         "end{%s}" % polyglossianame,
691                                         "\\end_layout", "", "\\end_inset", "", "",
692                                         endlayout, ""]
693             elif with_babel:
694                 document.body[endlang : endlang] = [startlayout,
695                                         "\\begin_inset ERT", "status open", "",
696                                         "\\begin_layout Plain Layout", "", "",
697                                         "\\backslash",
698                                         "end{otherlanguage}",
699                                         "\\end_layout", "", "\\end_inset", "", "",
700                                         endlayout, ""]
701             del document.body[i]
702             if with_polyglossia:
703                 document.body[i : i] = ["\\begin_inset ERT", "status open", "",
704                                         "\\begin_layout Plain Layout", "", "",
705                                         "\\backslash",
706                                         "begin{%s}" % polyglossianame,
707                                         "\\end_layout", "", "\\end_inset", "", "",
708                                         ""]
709             elif with_babel:
710                 document.body[i : i] = ["\\begin_inset ERT", "status open", "",
711                                         "\\begin_layout Plain Layout", "", "",
712                                         "\\backslash",
713                                         "begin{otherlanguage}{%s}" % babelname,
714                                         "\\end_layout", "", "\\end_inset", "", "",
715                                         ""]
716         elif primary and document.body[i].startswith('\\lang english'):
717             # Since we switched the main language manually, English parts need to be marked
718             endlang = get_containing_layout(document.body, i)[2]
719             langswitch = find_token(document.body, '\\lang', i + 1, endlang)
720             startlayout = "\\begin_layout Standard"
721             endlayout = "\\end_layout"
722             if langswitch != -1:
723                 endlang = langswitch
724                 startlayout = ""
725                 endlayout = ""
726             if with_polyglossia:
727                 parent = get_containing_layout(document.body, i)
728                 document.body[endlang : endlang] = [startlayout,
729                                         "\\begin_inset ERT", "status open", "",
730                                         "\\begin_layout Plain Layout", "", "",
731                                         "\\backslash",
732                                         "end{english}",
733                                         "\\end_layout", "", "\\end_inset", "", "",
734                                         endlayout, ""]
735             elif with_babel:
736                 parent = get_containing_layout(document.body, i)
737                 document.body[endlang : endlang] = [startlayout,
738                                         "\\begin_inset ERT", "status open", "",
739                                         "\\begin_layout Plain Layout", "", "",
740                                         "\\backslash",
741                                         "end{otherlanguage}",
742                                         "\\end_layout", "", "\\end_inset", "", "",
743                                         endlayout, ""]
744             del document.body[i]
745             if with_polyglossia:
746                 document.body[i : i] = ["\\begin_inset ERT", "status open", "",
747                                         "\\begin_layout Plain Layout", "", "",
748                                         "\\backslash",
749                                         "begin{english}",
750                                         "\\end_layout", "", "\\end_inset", "", "",
751                                         ""]
752             elif with_babel:
753                 document.body[i : i] = ["\\begin_inset ERT", "status open", "",
754                                         "\\begin_layout Plain Layout", "", "",
755                                         "\\backslash",
756                                         "begin{otherlanguage}{english}",
757                                         "\\end_layout", "", "\\end_inset", "", "",
758                                         ""]
759         else:
760             i += 1
761
762     # With babel, we need to add the language options
763     if with_babel and (primary or secondary):
764         insert_document_option(document, babelname)
765         if secondary and document.body[10] != "selectlanguage{%s}" % orig_doc_language:
766             # Since the user options are always placed after the babel options,
767             # we need to reset the main language
768             document.body[2 : 2] = ["\\begin_layout Standard",
769                                     "\\begin_inset ERT", "status open", "",
770                                     "\\begin_layout Plain Layout", "", "",
771                                     "\\backslash",
772                                     "selectlanguage{%s}" % orig_doc_language,
773                                     "\\end_layout", "", "\\end_inset", "", "",
774                                     "\\end_layout", ""]
775