#LyX 2.0.0svn created this file. For more info see http://www.lyx.org/ \lyxformat 405 \begin_document \begin_header \textclass article \use_default_options true \begin_modules logicalmkup \end_modules \maintain_unincluded_children false \language english \inputencoding auto \fontencoding global \font_roman default \font_sans default \font_typewriter default \font_default_family default \use_xetex false \font_sc false \font_osf false \font_sf_scale 100 \font_tt_scale 100 \graphics default \default_output_format default \output_sync 0 \bibtex_command default \index_command default \paperfontsize default \spacing single \use_hyperref false \papersize default \use_geometry false \use_amsmath 1 \use_esint 1 \use_mhchem 1 \use_mathdots 1 \cite_engine basic \use_bibtopic false \use_indices false \paperorientation portrait \suppress_date false \use_refstyle 1 \index Index \shortcut idx \color #008000 \end_index \secnumdepth 3 \tocdepth 3 \paragraph_separation indent \paragraph_indentation default \quotes_language english \papercolumns 1 \papersides 1 \paperpagestyle default \tracking_changes false \output_changes false \html_math_output 0 \html_be_strict false \end_header \begin_body \begin_layout Title Programming lyx2lyx \end_layout \begin_layout Author Richard Heck \end_layout \begin_layout Standard Contained herein are some observations and suggestions about how to write \begin_inset Flex Code status collapsed \begin_layout Plain Layout lyx2lyx \end_layout \end_inset routines, including some thoughts about common pitfalls. \end_layout \begin_layout Section The LyX_base Class \end_layout \begin_layout Standard Conversion and reversion routines will always be defined as functions that take an object of type \begin_inset Flex Code status collapsed \begin_layout Plain Layout LyX_base \end_layout \end_inset as argument. This argument, conventionally called \begin_inset Flex Code status collapsed \begin_layout Plain Layout document \end_layout \end_inset , represents the LyX document being converted. The \begin_inset Flex Code status collapsed \begin_layout Plain Layout LyX_base \end_layout \end_inset class is defined in the file \begin_inset Flex Code status collapsed \begin_layout Plain Layout LyX.py \end_layout \end_inset , and it has several properties and a number of methods. \end_layout \begin_layout Standard Some of the most important properties are: \end_layout \begin_layout Description backend Either \begin_inset Flex Code status collapsed \begin_layout Plain Layout linuxdoc \end_layout \end_inset , \begin_inset Flex Code status collapsed \begin_layout Plain Layout docbook \end_layout \end_inset , or \begin_inset Flex Code status collapsed \begin_layout Plain Layout latex \end_layout \end_inset , depending upon the document class \end_layout \begin_layout Description textclass The layout file for this document, e.g., \begin_inset Flex Code status collapsed \begin_layout Plain Layout article \end_layout \end_inset . \end_layout \begin_layout Description default_layout The default layout style for the class. \begin_inset Newline newline \end_inset Note that this is all \begin_inset Flex Code status collapsed \begin_layout Plain Layout lyx2lyx \end_layout \end_inset knows about the layout. It does not know what paragraph styles are available, for example, let alone what their properties might be. \end_layout \begin_layout Description encoding The document encoding. \end_layout \begin_layout Description language The document language. \end_layout \begin_layout Standard These three represent the content of the document. \end_layout \begin_layout Description header The document header, meaning the lines that come before \begin_inset Flex Code status collapsed \begin_layout Plain Layout \backslash begin_body \end_layout \end_inset , \emph on except \emph default for the LaTeX preamble. \end_layout \begin_layout Description preamble The LaTeX preamble. \end_layout \begin_layout Description body The document body. \end_layout \begin_layout Standard All three of these are lists of strings. The importance of this point will be discussed later. \end_layout \begin_layout Standard Important methods include: \end_layout \begin_layout Description warning Writes its argument to the console as a warning. (Also takes an optional argument, the debug level, which can be used to suppress output below a certain debug level, but this is rarely used.) \end_layout \begin_layout Description error Writes the warning and exits, unless we are in try_hard mode, which is set with a command-line option. Rarely used in converter code, but I shall mention times it might be used below. \end_layout \begin_layout Description set_parameter Sets the value of a header parameter. This needs to be a parameter already present in the header or nothing will happen. \end_layout \begin_layout Description set_textclass This writes the value of the \begin_inset Flex Code status collapsed \begin_layout Plain Layout textclass \end_layout \end_inset member variable to the header. So, for example, one might have something like this in a reversion routine: \end_layout \begin_layout LyX-Code if document.textclass = 'fancy_new_class': \end_layout \begin_layout LyX-Code document.textclass = 'old_class' \end_layout \begin_layout LyX-Code document.setclass() \end_layout \begin_layout Description add_module Adds a LyX module to the list of modules to be loaded with the document. \end_layout \begin_layout Description get_module_list Returns the list of modules to be loaded. \end_layout \begin_layout Description set_module_list Takes a list as argument and replaces the existing list of modules. \end_layout \begin_layout Standard There are some other methods, too, such as \begin_inset Flex Code status collapsed \begin_layout Plain Layout read() \end_layout \end_inset , but those are more for `internal' use. \end_layout \begin_layout Standard It is extremely important to understand that \begin_inset Flex Code status collapsed \begin_layout Plain Layout lyx2lyx \end_layout \end_inset is \emph on line-oriented \emph default . That is, \begin_inset Flex Code status collapsed \begin_layout Plain Layout lyx2lyx \end_layout \end_inset represents the content of a LyX file---the header, preamble, and body---as lists of lines. It is critical that one maintain this structure when modifying the document. Since Python is not type-safe, one can easily fail to do so if one is not careful, and this will cause problems. \end_layout \begin_layout Standard For example, one must absolutely never do anything like this: \end_layout \begin_layout LyX-Code newstuff = ' \backslash \backslash begin_inset ERT \backslash n, status collapsed \backslash n \backslash \end_layout \begin_layout LyX-Code \backslash \backslash begin_layout Plain Layout \backslash n \backslash nI am in ERT \backslash n \backslash \end_layout \begin_layout LyX-Code \backslash \backslash end_layout \backslash n \backslash n \backslash \backslash end_inset \backslash n \backslash n' \end_layout \begin_layout LyX-Code document.body[i:i] = newstuff \end_layout \begin_layout Standard This is supposed to insert an InsetERT at line i of the document, and in a sense it will. But it has the potential to confuse \begin_inset Flex Code status collapsed \begin_layout Plain Layout lyx2lyx \end_layout \end_inset very badly. Suppose at some later point in the conversion we want to change \begin_inset Flex Code status collapsed \begin_layout Plain Layout \backslash begin_layout Plain Layout \end_layout \end_inset to \begin_inset Flex Code status collapsed \begin_layout Plain Layout \backslash begin_layout PlainLayout \end_layout \end_inset . (In fact, this is actually done.) Then we are going to have code that looks like: \end_layout \begin_layout LyX-Code i = find_token(document.body, ' \backslash \backslash begin_layout Plain Layout', i) \end_layout \begin_layout Standard This will not find the occurence of \begin_inset Flex Code status collapsed \begin_layout Plain Layout \backslash begin_layout Plain Layout \end_layout \end_inset that we just inserted. This is because \begin_inset Flex Code status collapsed \begin_layout Plain Layout find_token \end_layout \end_inset looks for things at the beginning of lines, and \begin_inset Flex Code status collapsed \begin_layout Plain Layout \backslash begin_layout Plain Layout \end_layout \end_inset is not at the beginning of the long string \begin_inset Flex Code status collapsed \begin_layout Plain Layout newstuff \end_layout \end_inset . It follows a newline, to be sure, but that is different. So what one should do instead is: \end_layout \begin_layout LyX-Code newstuff = [' \backslash \backslash begin_inset ERT', 'status collapsed', \end_layout \begin_layout LyX-Code ' \backslash \backslash begin_layout Plain Layout', '', 'I am in ERT', \end_layout \begin_layout LyX-Code ' \backslash \backslash end_layout', '', ' \backslash \backslash end_inset', ''] \end_layout \begin_layout LyX-Code document.body[i:i] = newstuff \end_layout \begin_layout Standard That inserts a bunch of lines. \end_layout \begin_layout Section Utility Functions \end_layout \begin_layout Standard There are two Python modules that provide commonly used functions for parsing the file and for modifying it. The parsing functions are in \begin_inset Flex Code status collapsed \begin_layout Plain Layout parser_tools \end_layout \end_inset and the modifying functions are in \begin_inset Flex Code status collapsed \begin_layout Plain Layout lyx2lyx_tools \end_layout \end_inset . Both of these files have extensive documentation at the beginning that lists the functions that are available and explains what they do. Those writing \begin_inset Flex Code status collapsed \begin_layout Plain Layout lyx2lyx \end_layout \end_inset code should familiarize themselves with these functions. \end_layout \begin_layout Section Common Code Structures and Pitfalls \end_layout \begin_layout Standard As said, reversion routines receive an argument of type \begin_inset Flex Code status collapsed \begin_layout Plain Layout LyX_base \end_layout \end_inset , and they almost always have one of two sorts of structure, depending upon whether it is the header or the body that one is modifying. \end_layout \begin_layout Standard If it is the header, then the routine can be quite simple, because items usually occur in the header only once. So the structure will typically be: \end_layout \begin_layout LyX-Code def revert_header_stuff(document): \end_layout \begin_layout LyX-Code i = find_token(document.header, ' \backslash use_xetex', 0) \end_layout \begin_layout LyX-Code if i == -1: \end_layout \begin_layout LyX-Code # not found \end_layout \begin_layout LyX-Code document.warning('Hmm') \end_layout \begin_layout LyX-Code else: \end_layout \begin_layout LyX-Code # do something with line i \end_layout \begin_layout Standard How complex such routines become depends of course on the case. \end_layout \begin_layout Standard If the changes will be made to the body, then the routine usually has this sort of structure: \end_layout \begin_layout LyX-Code def revert_something(document): \end_layout \begin_layout LyX-Code i = 0 \end_layout \begin_layout LyX-Code while True: \end_layout \begin_layout LyX-Code i = find_token(document.body, ' \backslash begin_inset Funky', i) \end_layout \begin_layout LyX-Code if i == -1: \end_layout \begin_layout LyX-Code break \end_layout \begin_layout LyX-Code # do something ... \end_layout \begin_layout LyX-Code i += 1 # or other appropriate reset \end_layout \begin_layout Standard In some cases, one may need both sorts of routines together. \end_layout \begin_layout Subsection Where Am I? \end_layout \begin_layout Standard In the course of doing something in this last case, one will often want to look for content in the inset or layout (or whatever) that one has found. Suppose, for example, that one is trying to remove the option \begin_inset Flex Code status collapsed \begin_layout Plain Layout newoption \end_layout \end_inset from Funky insets. Then one might think to use code like this in place of the comment. \end_layout \begin_layout LyX-Code j = find_token(document.body, 'newoption', i) \end_layout \begin_layout LyX-Code if j == -1: \end_layout \begin_layout LyX-Code document.warning('UnFunky inset!') \end_layout \begin_layout LyX-Code break \end_layout \begin_layout LyX-Code del document.body[j] \end_layout \begin_layout Standard This is terrible code, for several reasons. \end_layout \begin_layout Standard First, it is wrong to break on the error here. The LyX file is corrupted, yes. But that does not necessarily mean that it is unusable---LyX is pretty forgiving---and just because we have failed to find this one option does not mean we should give up. We at least need to try to remove the option from other Funky insets. So the right think to do here is instead: \end_layout \begin_layout LyX-Code j = find_token(document.body, 'newoption', i) \end_layout \begin_layout LyX-Code if j == -1: \end_layout \begin_layout LyX-Code document.warning('UnFunky inset!') \end_layout \begin_layout LyX-Code i += 1 \end_layout \begin_layout LyX-Code continue \end_layout \begin_layout LyX-Code del document.body[j] \end_layout \begin_layout --Separator-- \end_layout \begin_layout Standard The second problem is that we have no way of knowing that the line we find here is actually a line containing an option for the Funky inset on line i. Suppose this inset is missing its \begin_inset Flex Code status collapsed \begin_layout Plain Layout newoption \end_layout \end_inset . There might be a later one that has a \begin_inset Flex Code status collapsed \begin_layout Plain Layout newoption \end_layout \end_inset . Then \begin_inset Flex Code status collapsed \begin_layout Plain Layout find_token \end_layout \end_inset will find the \begin_inset Flex Code status collapsed \begin_layout Plain Layout newoption \end_layout \end_inset for the later one. If we're just removing it, that might not be so bad. But if we were doing something more extensive, it could be. So, at the very least, we need to find the end of this inset and make sure the option comes before that: \end_layout \begin_layout LyX-Code k = find_end_of_inset(document.body, i) \end_layout \begin_layout LyX-Code if k == -1: \end_layout \begin_layout LyX-Code document.warning('No end to Funky inset!') \end_layout \begin_layout LyX-Code i += 1 \end_layout \begin_layout LyX-Code continue \end_layout \begin_layout LyX-Code j = find_token(document.body, 'newoption', i, k) \end_layout \begin_layout LyX-Code if j == -1: \end_layout \begin_layout LyX-Code document.warning('UnFunky inset!') \end_layout \begin_layout LyX-Code i = k \end_layout \begin_layout LyX-Code continue \end_layout \begin_layout LyX-Code del document.body[j] \end_layout \begin_layout Standard Note that we can reset \begin_inset Flex Code status collapsed \begin_layout Plain Layout i \end_layout \end_inset to \begin_inset Flex Code status collapsed \begin_layout Plain Layout k \end_layout \end_inset here only if we know that no Funky inset can occur inside a Funky inset. Otherwise, it should have been \begin_inset Flex Code status collapsed \begin_layout Plain Layout i += 1 \end_layout \end_inset , again. \end_layout \begin_layout Standard By the way, although it is not often done, there are definitely cases where we should use \begin_inset Flex Code status collapsed \begin_layout Plain Layout document.error() \end_layout \end_inset rather than \begin_inset Flex Code status collapsed \begin_layout Plain Layout document.warning() \end_layout \end_inset . In particular, suppose that we are actually planning to remove Funky insets altogether, or to replace them with ERT. Then, if the file is so corrupt that we cannot find the end of the inset, we cannot do this work, so we \emph on know \emph default we cannot produce a LyX file an older version will be able to load. In that case, it seems right just to abort, and if the user wants to \begin_inset Quotes eld \end_inset try hard \begin_inset Quotes erd \end_inset , she can run \begin_inset Flex Code status collapsed \begin_layout Plain Layout lyx2lyx \end_layout \end_inset from the command line and pass the appropriate opttion. \end_layout \begin_layout Standard The routine above may still fail to do the right thing, however. Suppose again that \begin_inset Flex Code status collapsed \begin_layout Plain Layout newoption \end_layout \end_inset is missing, but, due to a strange typo, one of the lines of text in the inset happens to begin with \begin_inset Quotes eld \end_inset newoption \begin_inset Quotes erd \end_inset . Then \begin_inset Flex Code status collapsed \begin_layout Plain Layout find_token \end_layout \end_inset will find that line and we will remove text from the document! This will not generally happen with command insets, but it can easily happen with text insets. In that case, one has to make sure the option comes before the content of the inset, and to do that, we must find the first layout in the inset, thus: \end_layout \begin_layout LyX-Code k = find_end_of_inset(document.body, i) \end_layout \begin_layout LyX-Code if k == -1: \end_layout \begin_layout LyX-Code document.warning('No end to Funky inset!') \end_layout \begin_layout LyX-Code i += 1 \end_layout \begin_layout LyX-Code continue \end_layout \begin_layout LyX-Code m = find_token(document.body, ' \backslash \backslash begin_layout', i, k) \end_layout \begin_layout LyX-Code if m == -1: \end_layout \begin_layout LyX-Code document.warning('No layout! Hope for the best!') \end_layout \begin_layout LyX-Code m = k \end_layout \begin_layout LyX-Code j = find_token(document.body, 'newoption', i, m) \end_layout \begin_layout LyX-Code if j == -1: \end_layout \begin_layout LyX-Code document.warning('UnFunky inset!') \end_layout \begin_layout LyX-Code i = k \end_layout \begin_layout LyX-Code continue \end_layout \begin_layout LyX-Code del document.body[j] \end_layout \begin_layout Standard Note the response here to \begin_inset Flex Code status collapsed \begin_layout Plain Layout m != 1 \end_layout \end_inset . There is not necessarily a need to give up trying to remove the option. What the right response is will depend upon the specific case. \end_layout \begin_layout Standard The last problem, though it would be unlikely in this case, is that we might find not \begin_inset Flex Code status collapsed \begin_layout Plain Layout newoption \end_layout \end_inset but \begin_inset Flex Code status collapsed \begin_layout Plain Layout newoptions \end_layout \end_inset , because \begin_inset Flex Code status collapsed \begin_layout Plain Layout find_token \end_layout \end_inset only looks to see if the beginning of the line matches. Typically, then, what one really wants is \begin_inset Flex Code status collapsed \begin_layout Plain Layout find_token_exact \end_layout \end_inset , which makes sure that we are finding a complete token. \begin_inset Foot status collapsed \begin_layout Plain Layout In the implementation in LyX 2.0svn and earlier, this function also ignores other differences in whitespace. This needs to be fixed and will be once 2.0 is out. \end_layout \end_inset So what we really want, for the entire function, is: \end_layout \begin_layout LyX-Code def revert_something(document): \end_layout \begin_layout LyX-Code i = 0 \end_layout \begin_layout LyX-Code while True: \end_layout \begin_layout LyX-Code i = find_token(document.body, ' \backslash begin_inset Funky', i) \end_layout \begin_layout LyX-Code if i == -1: \end_layout \begin_layout LyX-Code break \end_layout \begin_layout LyX-Code k = find_end_of_inset(document.body, i) \end_layout \begin_layout LyX-Code if k == -1: \end_layout \begin_layout LyX-Code document.warning('No end to Funky inset!') \end_layout \begin_layout LyX-Code i += 1 \end_layout \begin_layout LyX-Code continue \end_layout \begin_layout LyX-Code m = find_token(document.body, ' \backslash \backslash begin_layout', i, k) \end_layout \begin_layout LyX-Code if m == -1: \end_layout \begin_layout LyX-Code document.warning('No layout! Hope for the best!') \end_layout \begin_layout LyX-Code m = k \end_layout \begin_layout LyX-Code j = find_token(document.body, 'newoption', i, m) \end_layout \begin_layout LyX-Code if j == -1: \end_layout \begin_layout LyX-Code document.warning('UnFunky inset!') \end_layout \begin_layout LyX-Code i = k \end_layout \begin_layout LyX-Code continue \end_layout \begin_layout LyX-Code del document.body[j] \end_layout \begin_layout LyX-Code i += 1 \end_layout \begin_layout Standard This is much more complicated than what we had before, but it is much more reliable. (Probably, much of this logic should be wrapped in a function.) \end_layout \begin_layout Subsection Comments and Coding Style \end_layout \begin_layout Standard I've written the previous routine in the style in which most \begin_inset Flex Code status collapsed \begin_layout Plain Layout lyx2lyx \end_layout \end_inset routines have generally been written: There are no comments, and all variable names are completely uninformative. For all the usual reasons, this is bad. It will take us a bit of effort to change this practice, but it is worth doing. The people who have to fix \begin_inset Flex Code status collapsed \begin_layout Plain Layout lyx2lyx \end_layout \end_inset bugs are not always the ones who wrote the code, and even the ones who did may not remember what it was supposed to do. So let's write something like this: \end_layout \begin_layout LyX-Code def revert_something(document): \end_layout \begin_layout LyX-Code i = 0 \end_layout \begin_layout LyX-Code while True: \end_layout \begin_layout LyX-Code i = find_token(document.body, ' \backslash begin_inset Funky', i) \end_layout \begin_layout LyX-Code if i == -1: \end_layout \begin_layout LyX-Code break \end_layout \begin_layout LyX-Code endins = find_end_of_inset(document.body, i) \end_layout \begin_layout LyX-Code if endins == -1: \end_layout \begin_layout LyX-Code document.warning('No end to Funky inset!') \end_layout \begin_layout LyX-Code i += 1 \end_layout \begin_layout LyX-Code continue \end_layout \begin_layout LyX-Code blay = find_token(document.body, ' \backslash \backslash begin_layout', i, endins) \end_layout \begin_layout LyX-Code if blay == -1: \end_layout \begin_layout LyX-Code document.warning('No layout! Hope for the best!') \end_layout \begin_layout LyX-Code blay = endins \end_layout \begin_layout LyX-Code optline = find_token(document.body, 'newoption', i, blay) \end_layout \begin_layout LyX-Code if optline == -1: \end_layout \begin_layout LyX-Code document.warning('UnFunky inset!') \end_layout \begin_layout LyX-Code i = endins \end_layout \begin_layout LyX-Code continue \end_layout \begin_layout LyX-Code del document.body[optline] \end_layout \begin_layout LyX-Code i += 1 \end_layout \begin_layout Standard No comments really needed in that one, I suppose. \end_layout \begin_layout Subsection Magic Numbers \end_layout \begin_layout Standard Another common error is relying too much on assumptions about the structure of a valid LyX file. Here is an example. Suppose we want to add a \begin_inset Flex Code status collapsed \begin_layout Plain Layout \backslash noindent \end_layout \end_inset flag to the first paragraph of any Funky inset. Then it is tempting to do something like this: \end_layout \begin_layout LyX-Code def add_noindent(document): \end_layout \begin_layout LyX-Code i = 0 \end_layout \begin_layout LyX-Code while True: \end_layout \begin_layout LyX-Code i = find_token(document.body, ' \backslash begin_inset Funky', i) \end_layout \begin_layout LyX-Code if i == -1: \end_layout \begin_layout LyX-Code break \end_layout \begin_layout LyX-Code document.body.insert(i+4, ' \backslash \backslash noindent') \end_layout \begin_layout LyX-Code i += 4 \end_layout \begin_layout Standard Experienced programmers will know that this is bad. Where does the magic number 4 come from? The answer is that it comes from examining the LyX file. One looks at a typical file containing a Funky inset and sees: \end_layout \begin_layout LyX-Code \backslash begin_inset Funky \end_layout \begin_layout LyX-Code status collapsed \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code \backslash begin_layout Standard \end_layout \begin_layout LyX-Code here is some content \end_layout \begin_layout LyX-Code \backslash end_layout \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code \backslash end_inset \end_layout \begin_layout Standard So \begin_inset Flex Code status collapsed \begin_layout Plain Layout \backslash noindent \end_layout \end_inset goes four lines after the inset, as we might confirm by adding it in LyX and looking at that file. \end_layout \begin_layout Standard Much of the time, this will work, but there is no guarantee that it will be correct, and the same goes for any assumption of this sort. It is not enough even to study the LyX source code and make very sure that the output routine produces what one thinks it does. The problem is that the empty line before \begin_inset Flex Code status collapsed \begin_layout Plain Layout \backslash begin_layout \end_layout \end_inset could easily disappear, without any change to the semantics. Or another line could appear. There are several reasons for this. \end_layout \begin_layout Standard First, looking at the source code of the current version of LyX tells you nothing about how the file might have been created by some other version. Maybe we get tired of blank lines and decide to remove them. This is not going to be accounted for in some reversion routine in \begin_inset Flex Code status collapsed \begin_layout Plain Layout lyx2lyx \end_layout \end_inset . The semantics of the file matters, and LyX's ability to read it matters. Blank lines here and there do not matter. \end_layout \begin_layout Standard Second, LyX files are not always produced by LyX. Some of them are produced by external scripts (sed, perl, etc) that people write to do search and replace operations that are not possible inside LyX (and this will still be true once advanced search and replace is available). Such files may end up having slightly different structures than are usual, and yet be perfectly good files. \end_layout \begin_layout Standard Third, and most importantly, the file you are modifying has almost certainly been through several other conversion routines before it gets to yours. A quick look at some of these routines will make it very clear how difficult it is to get all the blank lines in the right places, and people rarely check for this: They check to make sure the file opens correctly and that its output is right, but who cares how many blank lines there are? Again, it is the semantics that matters, not the fine details of file structure. \end_layout \begin_layout Standard Or consider this possibility: Someone else wrote a routine to remove \begin_inset Flex Code status collapsed \begin_layout Plain Layout newoption \end_layout \end_inset , but, since they failed to read this document, their routine has all the bugs we discussed before. As a result, \begin_inset Flex Code status collapsed \begin_layout Plain Layout newoption \end_layout \end_inset is still there in one of the Funky insets in the document, now that it has gotten to your routine. So what you actually have is: \end_layout \begin_layout LyX-Code \backslash begin_inset Funky \end_layout \begin_layout LyX-Code status collapsed \end_layout \begin_layout LyX-Code newoption false \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code \backslash begin_layout Standard \end_layout \begin_layout LyX-Code here is some content \end_layout \begin_layout LyX-Code \backslash end_layout \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code \backslash end_inset \end_layout \begin_layout Standard This is not a valid LyX document of the format on which you are operating. But surely you do not really want to produce this: \end_layout \begin_layout LyX-Code \backslash begin_inset Funky \end_layout \begin_layout LyX-Code status collapsed \end_layout \begin_layout LyX-Code newoption false \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code \backslash noindent \end_layout \begin_layout LyX-Code \backslash begin_layout Standard \end_layout \begin_layout LyX-Code here is some content \end_layout \begin_layout LyX-Code \backslash end_layout \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code \backslash end_inset \end_layout \begin_layout Standard If you do, you will have made matters worse, and also failed to unindent the paragraph. The file will still open, probably, though with warnings. \end_layout \begin_layout Standard But things can (and do) get much worse. Suppose you had meant, for some reason, to change the layout, whatever it was, to \begin_inset Flex Code status collapsed \begin_layout Plain Layout Plain Layout \end_layout \end_inset . So you do: \end_layout \begin_layout LyX-Code def make_funky_plain(document): \end_layout \begin_layout LyX-Code i = 0 \end_layout \begin_layout LyX-Code while True: \end_layout \begin_layout LyX-Code i = find_token(document.body, ' \backslash begin_inset Funky', i) \end_layout \begin_layout LyX-Code if i == -1: \end_layout \begin_layout LyX-Code break \end_layout \begin_layout LyX-Code document.body[i+4] = ' \backslash begin_layout Plain Layout' \end_layout \begin_layout LyX-Code i += 4 \end_layout \begin_layout Standard Now you've produced this: \end_layout \begin_layout LyX-Code \backslash begin_inset Funky \end_layout \begin_layout LyX-Code status collapsed \end_layout \begin_layout LyX-Code newoption false \end_layout \begin_layout LyX-Code \backslash begin_layout Plain Layout \end_layout \begin_layout LyX-Code \backslash begin_layout Standard \end_layout \begin_layout LyX-Code here is some content \end_layout \begin_layout LyX-Code \backslash end_layout \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code \backslash end_inset \end_layout \begin_layout Standard LyX will abort the parse when it hits \begin_inset Flex Code status collapsed \begin_layout Plain Layout \backslash end_inset \end_layout \end_inset , complaining about a missing \begin_inset Flex Code status collapsed \begin_layout Plain Layout \backslash end_layout \end_layout \end_inset . \end_layout \begin_layout Standard The solution is very simple: \end_layout \begin_layout LyX-Code def make_funky_plain(document): \end_layout \begin_layout LyX-Code i = 0 \end_layout \begin_layout LyX-Code while True: \end_layout \begin_layout LyX-Code i = find_token(document.body, ' \backslash begin_inset Funky', i) \end_layout \begin_layout LyX-Code if i == -1: \end_layout \begin_layout LyX-Code break \end_layout \begin_layout LyX-Code endins = find_end_of_inset(document.body, i) \end_layout \begin_layout LyX-Code if endins == -1: \end_layout \begin_layout LyX-Code ... \end_layout \begin_layout LyX-Code lay = find_token(document.body, ' \backslash begin_layout', i, endins) \end_layout \begin_layout LyX-Code if lay == -1: \end_layout \begin_layout LyX-Code ... \end_layout \begin_layout LyX-Code document.body[lay] = ' \backslash begin_layout Plain Layout' \end_layout \begin_layout LyX-Code i = endins \end_layout \begin_layout Standard Again, a bit more complex, but reliable. \end_layout \end_body \end_document