As part of this system, I've attached a find-&-replace script for LyX's native file format, & associated files:
findrepl.py -- the script that does the finding & replacing. findrepl_help.py -- a help script.pLyXFindReplace(compressed).lyx -- an explanatory LyX document that needs to be saved in uncompressed format for the examples in it to work.
In the document I show how the script can be used to tackle some queries that have appeared on the users list over the past year:
document-wide changing the width of figures document-wide centering of figuresconverting chemical formulae written in math insets (for the sake of sub- and superscripts) to \mathrm clearing up "debris" after importing documents from, e.g., OpenOffice or Abiword, that have been exported as LaTeX (in other words getting rid of all those irritating left & right braces in ERT insets).
One way of tackling such problems is to open the document in a text editor and make appropriate changes there. The find-&-replace script means that is no longer necessary. It can be done from the comfort of the LyX GUI. There are usually other ways of tackling such matters (and perhaps more insightful in a LaTeX sense) but for someone pressed for time, this may provide a convenient fallback.
The script has a simple mode capable of finding & replacing possibly multiple lines of native LyX format code, and a powerful regular expression mode similarly capable, but also of condensing multiple simple searches into one regexp search (and, of course, with a health warning about crashing LyX, given the obscurity of regexps, but it's great fun).
Andrew
# Find & replace elements of the LyX source. # Part of the pLyX.py system. # # Andrew Parsloe ([email protected]) # version 0.1 (19 November 2012) # # findrepl.py # import argparse, re, sys re_backslash = re.compile(r'\n?\\backslash\n') flex_fr = r'\begin_inset Flex .find & repl' flex_arg = r'\begin_inset Flex .[argument]' begin_layout = r'\begin_layout' end_layout = r'\end_layout' begin_std = r'\begin_layout Standard' begin_inset = r'\begin_inset' end_inset = r'\end_inset' begin_note = r'\begin_inset Note' st_open = 'status open\n' st_coll = 'status collapsed\n' end_body = r'\end_body' end_document = r'\end_document' backslash = r'\backslash' begin_msg = r'''\begin_inset Note Note status open \begin_layout Plain Layout ''' end_msg = r''' \end_layout \end_inset ''' ###################################################### def main(infl, outfl, options, guff): def strip_outers(stuff): '''Strip enclosing layout statements.''' stuff = stuff.partition('\n')[2] stuff = stuff.rpartition(end_layout)[0] return stuff def inset_contents(): '''Get contents of inset minus LyX paragraphing.''' contents = lines = '' layouts, insets = 0, 1 status = True newpara = False for line in infl: lines += line if line == '\n': continue # assumes "status open|collapsed" is last status line elif status: if st_open == line or st_coll == line: status = False continue elif begin_layout in line: # exclude LyX paragraphing of contents layouts += 1 if layouts > 1: contents += line else: newpara = True elif begin_inset in line: insets += 1 contents += line elif backslash in line: if newpara: contents += '\n' + line else: contents += line elif end_layout in line: newpara = False # exclude LyX paragraphing of contents if layouts > 1: contents += line layouts -= 1 elif end_inset in line: newpara = False insets -= 1 if insets == 0: return contents, lines else: contents += line else: newpara = False contents += line def get_lines(n): temp = '' i = 0 if n == 0: return '' else: for line in infl: if line != '\n': temp += line i += 1 if i == n: break return temp def write_msg(msg): '''Write a yellow note error message''' outfl.write(begin_msg) outfl.write(msg) outfl.write(end_msg) def write_last_count(flines, count, suppress): if not suppress: temp = iter(flines.splitlines(True)) for line in temp: if end_body in line: outfl.write(begin_std + '\n') write_msg(count_msg(count)) outfl.write(end_layout + '\n') outfl.write(line) else: outfl.write(flines) for line in infl: outfl.write(line) def count_msg(count): '''Build occurrence/replacement message.''' msg = str(count) + ' ' if replacing: msg += 'replacement' if count != 1: msg += 's' msg += ' of "' + find0 + '" by "' + repl0 + '"\n' else: msg += 'occurrence' if count != 1: msg += 's' msg += ' of "' + find0 + '"\n' return msg def re_flags(params): '''Return reg. exp. flag value.''' fgs = count = 0 for char in params.upper(): if char == 'I': # ignorecase fgs = fgs|re.I elif char == 'L': # locale fgs = fgs|re.L elif char == 'M': # mulitline fgs = fgs|re.M elif char in 'SD': # dotall fgs = fgs|re.S elif char == 'U': # unicode fgs = fgs|re.U elif char.isdigit(): count = 10*count + int(char) return fgs, count ###################################################### # write the prelims outfl.write(guff) guff = '' # get the options parser = argparse.ArgumentParser(description='Find & replace') parser.add_argument('-r', '--regexp', dest = 'r', action ='store_true', \ default = False, help='Use regular expressions') parser.add_argument('-s', '--suppress', dest = 's', action ='store_true', \ default = False, help='Suppress occurrence/replacement messages') regexp0 = parser.parse_args(options).r suppress = parser.parse_args(options).s lines = flines = params = '' count = status = 0 finding = replacing = False for line in infl: if line in '\n': continue # scanning text, looking for f-&-r inset elif status == 0: if flex_fr in line: if finding: outfl.write(flines) flines = '' # show count & turn off current search if suppress == False: msg = count_msg(count) write_msg(msg) count = 0 finding = False outfl.write(line) params, temp = inset_contents() if '-r' in params.lower(): regexp = True params = re.sub(r'\-\-regexp', '', params, flags = re.I) params = re.sub(r'\-r', '', params, flags = re.I) else: regexp = regexp0 outfl.write(temp) status += 1 elif finding: flines += line lenfl += 1 print '*',lenfl if end_body in line: write_last_count(flines, count, suppress) elif (not regexp and find in flines) or \ (regexp and re_find.search(flines)): count += 1 if replacing: if regexp: outfl.write(re_find.sub(repl, flines)) else: outfl.write(flines.replace(find, repl)) else: outfl.write(flines) if flen > 1: flines = get_lines(flen - 1) lenfl = flen - 1 if end_body in flines: write_last_count(flines, count, suppress) else: flines = '' lenfl = 0 else: if lenfl >= flenmax: temp = flines.partition('\n') outfl.write(temp[0] + '\n') flines = temp[2] lenfl -= 1 # otherwise write to file else: outfl.write(line) # looking for an argument inset (find) elif status == 1: outfl.write(line) if flex_arg in line: find0, temp = inset_contents() outfl.write(temp) find = re_backslash.sub(r'\\', find0).strip() if find == '' and finding == False: # nothing to find write_msg('Nothing to find!') status -= 1 elif find == '' and finding == True: # turn off current search finding = False status -= 1 else: finding = True re_find = re.compile(find, flags = re_flags(params)[0]) # check for replace inset status += 1 else: status -= 1 finding = False # looking for an argument inset (replace) elif status == 2: outfl.write(line) # get replacement string if flex_arg in line: repl0, temp = inset_contents() print repl0 repl = re_backslash.sub(r'\\', repl0).strip() outfl.write(temp) replacing = True else: # no replacement inset replacing = False status = 0 flen = len(find.splitlines(True)) flenmax = max(re_flags(params)[1], flen) print flenmax if flen > 1: flines = get_lines(flen - 1) lenfl = flen - 1 else: lenfl = 0 return 1
def helpnote(hv):
if hv > 1:
return header + version
else:
return header + tail
header = r'''\begin_LyX-Code
\family roman
\series bold
.find & replace
\end_layout
'''
version = r'''\begin_layout LyX-Code
\family roman
Version 1.0 (19 Jan 2013) RE flags & search block size spec.
\end_layout
\begin_layout LyX-Code
\family roman
Version 0.2 (13 Jan 2013) Regular expression searches.
\end_layout
\begin_layout LyX-Code
\family roman
Version 0.1 (8 Jan 2013)
\end_layout
'''
tail = r'''\begin_layout LyX-Code
\family roman
Find & replace, or count the number of occurrences of,
elements of LyX format code.
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
\family roman
\series bold
Global options
\end_layout
\begin_layout LyX-Code
\family roman
\series bold
-h --help
\series default
show this help note.
\end_layout
\begin_layout LyX-Code
\family roman
\series bold
-v --version
\series default
show version information.
\end_layout
\begin_layout LyX-Code
\family roman
\series bold
-r
\series default
use regular expressions.
\end_layout
\begin_layout LyX-Code
\family roman
\series bold
-s
\series default
suppress occurrence/replacement messages.
\end_layout
\begin_layout Itemize
\family roman
To count the number of occurrences of some (possibly multi-line) element of
LyX format code, enter the code in an
\family sans
.[argument]
\family roman
inset immediately following a
\family sans
.find & replace (LyX format)
\family roman
inset inserted in the document.
This is the point at which the count will begin.
(You can copy code from the
\family sans
View Source
\family roman
window and use
\family sans
Paste Special
\family roman
, in the
\family sans
Edit
\family roman
menu, to paste it into the
\family sans
.[argument]
\family roman
inset.)
Another
\family sans
.find & replace (LyX format)
\family roman
inset inserted later in the document will stop the count
at that point, where the total will be displayed in a (yellow) Note and,
should there be a following
\family sans
.[argument]
\family roman
inset with content, start a new count.
If there is no further inset, this new count will be displayed at the end
of the document.
\end_layout
\begin_layout Itemize
\family roman
To find & replace some (possibly multi-line) element of LyX format code,
enter the code to be found in an
\family sans
.[argument]
\family roman
inset immediately following a
\family sans
.find & replace (LyX format)
\family roman
inset inserted in the document.
The replacement code is entered in another
\family sans
.[argument]
\family roman
inset immediately following the first. (Again, you can copy code from the
\family sans
View Source
\family roman
window and use
\family sans
Paste Special
\family roman
, in the
\family sans
Edit
\family roman
menu, to paste it into either inset.)
Another
\family sans
.find & replace (LyX format)
\family roman
inset inserted later in the document will stop the find & replace
at that point, where the total number of replacements will be displayed
in a (yellow) Note.
Depending on whether this second
\family sans
.find & replace (LyX format)
\family roman
inset is followed by one or two
\family sans
.[argument]
\family roman
insets with content, a new count or a new find & replace may be initiated
at this point.
\end_layout
\begin_layout Itemize
\family roman
The yellow Note messages detailing how many occurrences or replacements
have been made can be suppressed with the global
\series bold
-s
\series default
option.
The default is to show them.
\end_layout
\begin_layout Itemize
\family roman
Because LyX inserts line breaks into ordinary text (including sometimes in
the middle of words), both count and find-&-replace for ordinary text may
not give expected results (but LyX has its built-in find-&-replace
for this context).
\end_layout
\begin_layout Itemize
\family roman
Regular expression searches can be conducted by using the global
\series bold
-r
\series default
(or
\series bold
--regexp
\series default
) option, or by inserting this option in a local instance of a
\family sans
.find & replace (LyX format)
\family roman
inset. Regular expression flags and a search block size specification
can also be inserted in this inset. See the accompanying documentation:
\emph on
The pLyX system: find & replace (& count) LyX format code
\emph default
.
\end_layout
'''
pLyXFindReplace(compressed).lyx
Description: Binary data
