]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/parser_tools.py
did anybody test this?
[lyx.git] / lib / lyx2lyx / parser_tools.py
1 # This file is part of lyx2lyx
2 # -*- coding: utf-8 -*-
3 # Copyright (C) 2002-2011 Dekel Tsur <dekel@lyx.org>, 
4 # José Matos <jamatos@lyx.org>, Richard Heck <rgheck@comcast.net>
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19
20
21 ''' 
22 This modules offer several free functions to help parse lines.
23 More documentaton is below, but here is a quick guide to what 
24 they do. Optional arguments are marked by brackets.
25
26 find_token(lines, token, start[, end[, ignorews]]):
27   Returns the first line i, start <= i < end, on which
28   token is found at the beginning. Returns -1 if not 
29   found. 
30   If ignorews is (given and) True, then differences
31   in whitespace do not count, except that there must be no 
32   extra whitespace following token itself.
33
34 find_token_exact(lines, token, start[, end]):
35   As find_token, but with ignorews True.
36
37 find_tokens(lines, tokens, start[, end[, ignorews]]):
38   Returns the first line i, start <= i < end, on which
39   oen of the tokens in tokens is found at the beginning. 
40   Returns -1 if not found. 
41   If ignorews is (given and) True, then differences
42   in whitespace do not count, except that there must be no 
43   extra whitespace following token itself.
44
45 find_tokens_exact(lines, token, start[, end]):
46   As find_tokens, but with ignorews True.
47   
48 find_token_backwards(lines, token, start):
49 find_tokens_backwards(lines, tokens, start):
50   As before, but look backwards.
51
52 find_re(lines, rexp, start[, end]):
53   As find_token, but rexp is a regular expression object,
54   so it has to be passed as e.g.: re.compile(r'...').
55
56 get_value(lines, token, start[, end[, default]):
57   Similar to find_token, but it returns what follows the 
58   token on the found line. Example:
59     get_value(document.header, "\use_xetex", 0)
60   will find a line like:
61     \use_xetex true
62   and, in that case, return "true". (Note that whitespace
63   is stripped.) The final argument, default, defaults to "", 
64   and is what is returned if we do not find anything. So you
65   can use that to set a default.
66   
67 get_quoted_value(lines, token, start[, end[, default]):
68   Similar to get_value, but it will strip quotes off the
69   value, if they are present. So use this one for cases
70   where the value is normally quoted.
71
72 get_option_value(line, option):
73   This assumes we have a line with something like:
74       option="value"
75   and returns value. Returns "" if not found.
76
77 del_token(lines, token, start[, end]):
78   Like find_token, but deletes the line if it finds one.
79   Returns True if a line got deleted, otherwise False.
80
81 find_beginning_of(lines, i, start_token, end_token):
82   Here, start_token and end_token are meant to be a matching 
83   pair, like "\begin_layout" and "\end_layout". We look for 
84   the start_token that pairs with the end_token that occurs
85   on or after line i. Returns -1 if not found.
86   So, in the layout case, this would find the \begin_layout 
87   for the layout line i is in. 
88   Example:
89     ec = find_token(document.body, "</cell", i)
90     bc = find_beginning_of(document.body, ec, \
91         "<cell", "</cell")
92   Now, assuming no -1s, bc-ec wraps the cell for line i.
93
94 find_end_of(lines, i, start_token, end_token):
95   Like find_beginning_of, but looking for the matching 
96   end_token. This might look like:
97     bc = find_token_(document.body, "<cell", i)
98     ec = find_end_of(document.body, bc,  "<cell", "</cell")
99   Now, assuming no -1s, bc-ec wrap the next cell.
100
101 find_end_of_inset(lines, i):
102   Specialization of find_end_of for insets.
103
104 find_end_of_layout(lines, i):
105   Specialization of find_end_of for layouts.
106
107 is_in_inset(lines, i, inset):
108   Checks if line i is in an inset of the given type.
109   If so, returns starting and ending lines. Otherwise, 
110   returns False.
111   Example:
112     is_in_inset(document.body, i, "\\begin_inset Tabular")
113   returns False unless i is within a table. If it is, then
114   it returns the line on which the table begins and the one
115   on which it ends. Note that this pair will evaulate to
116   boolean True, so
117     if is_in_inset(...):
118   will do what you expect.
119
120 get_containing_inset(lines, i):
121   Finds out what kind of inset line i is within. Returns a 
122   list containing what follows \begin_inset on the the line 
123   on which the inset begins, plus the starting and ending line.
124   Returns False on any kind of error or if it isn't in an inset.
125   So get_containing_inset(document.body, i) might return:
126     ("CommandInset ref", 300, 306)
127   if i is within an InsetRef beginning on line 300 and ending
128   on line 306.
129
130 get_containing_layout(lines, i):
131   As get_containing_inset, but for layout.
132
133
134 find_nonempty_line(lines, start[, end):
135   Finds the next non-empty line.
136
137 check_token(line, token):
138   Does line begin with token?
139
140 is_nonempty_line(line):
141   Does line contain something besides whitespace?
142
143 '''
144
145 import re
146
147 # Utilities for one line
148 def check_token(line, token):
149     """ check_token(line, token) -> bool
150
151     Return True if token is present in line and is the first element
152     else returns False."""
153
154     return line[:len(token)] == token
155
156
157 def is_nonempty_line(line):
158     """ is_nonempty_line(line) -> bool
159
160     Return False if line is either empty or it has only whitespaces,
161     else return True."""
162     return line != " "*len(line)
163
164
165 # Utilities for a list of lines
166 def find_token(lines, token, start, end = 0, ignorews = False):
167     """ find_token(lines, token, start[[, end], ignorews]) -> int
168
169     Return the lowest line where token is found, and is the first
170     element, in lines[start, end].
171     
172     If ignorews is True (default is False), then differences in
173     whitespace are ignored, except that there must be no extra
174     whitespace following token itself.
175
176     Return -1 on failure."""
177
178     if end == 0 or end > len(lines):
179         end = len(lines)
180     m = len(token)
181     for i in xrange(start, end):
182         if ignorews:
183             x = lines[i].split()
184             y = token.split()
185             if len(x) < len(y):
186                 continue
187             if x[:len(y)] == y:
188                 return i
189         else:
190             if lines[i][:m] == token:
191                 return i
192     return -1
193
194
195 def find_token_exact(lines, token, start, end = 0):
196     return find_token(lines, token, start, end, True)
197
198
199 def find_tokens(lines, tokens, start, end = 0, ignorews = False):
200     """ find_tokens(lines, tokens, start[[, end], ignorews]) -> int
201
202     Return the lowest line where one token in tokens is found, and is
203     the first element, in lines[start, end].
204
205     Return -1 on failure."""
206     if end == 0 or end > len(lines):
207         end = len(lines)
208
209     for i in xrange(start, end):
210         for token in tokens:
211             if ignorews:
212                 x = lines[i].split()
213                 y = token.split()
214                 if len(x) < len(y):
215                     continue
216                 if x[:len(y)] == y:
217                     return i
218             else:
219                 if lines[i][:len(token)] == token:
220                     return i
221     return -1
222
223
224 def find_tokens_exact(lines, tokens, start, end = 0):
225     return find_tokens(lines, tokens, start, end, True)
226
227
228 def find_re(lines, rexp, start, end = 0):
229     """ find_token_re(lines, rexp, start[, end]) -> int
230
231     Return the lowest line where rexp, a regular expression, is found
232     in lines[start, end].
233
234     Return -1 on failure."""
235
236     if end == 0 or end > len(lines):
237         end = len(lines)
238     for i in xrange(start, end):
239         if rexp.match(lines[i]):
240                 return i
241     return -1
242
243
244 def find_token_backwards(lines, token, start):
245     """ find_token_backwards(lines, token, start) -> int
246
247     Return the highest line where token is found, and is the first
248     element, in lines[start, end].
249
250     Return -1 on failure."""
251     m = len(token)
252     for i in xrange(start, -1, -1):
253         line = lines[i]
254         if line[:m] == token:
255             return i
256     return -1
257
258
259 def find_tokens_backwards(lines, tokens, start):
260     """ find_tokens_backwards(lines, token, start) -> int
261
262     Return the highest line where token is found, and is the first
263     element, in lines[end, start].
264
265     Return -1 on failure."""
266     for i in xrange(start, -1, -1):
267         line = lines[i]
268         for token in tokens:
269             if line[:len(token)] == token:
270                 return i
271     return -1
272
273
274 def get_value(lines, token, start, end = 0, default = ""):
275     """ get_value(lines, token, start[[, end], default]) -> string
276
277     Find the next line that looks like:
278       token followed by other stuff
279     Returns "followed by other stuff" with leading and trailing
280     whitespace removed.
281     """
282
283     i = find_token_exact(lines, token, start, end)
284     if i == -1:
285         return default
286     l = lines[i].split(None, 1)
287     if len(l) > 1:
288         return l[1].strip()
289     return default
290
291
292 def get_quoted_value(lines, token, start, end = 0, default = ""):
293     """ get_quoted_value(lines, token, start[[, end], default]) -> string
294
295     Find the next line that looks like:
296       token "followed by other stuff"
297     Returns "followed by other stuff" with leading and trailing
298     whitespace and quotes removed. If there are no quotes, that is OK too.
299     So use get_value to preserve possible quotes, this one to remove them,
300     if they are there.
301     Note that we will NOT strip quotes from default!
302     """
303     val = get_value(lines, token, start, end, "")
304     if not val:
305       return default
306     return val.strip('"')
307
308
309 def get_option_value(line, option):
310     rx = option + '\s*=\s*"([^"]+)"'
311     rx = re.compile(rx)
312     m = rx.search(line)
313     if not m:
314       return ""
315     return m.group(1)
316
317
318 def del_token(lines, token, start, end = 0):
319     """ del_token(lines, token, start, end) -> int
320
321     Find the first line in lines where token is the first element 
322     and delete that line. Returns True if we deleted a line, False
323     if we did not."""
324
325     k = find_token_exact(lines, token, start, end)
326     if k == -1:
327         return False
328     del lines[k]
329     return True
330
331
332 def find_beginning_of(lines, i, start_token, end_token):
333     count = 1
334     while i > 0:
335         i = find_tokens_backwards(lines, [start_token, end_token], i-1)
336         if i == -1:
337             return -1
338         if check_token(lines[i], end_token):
339             count = count+1
340         else:
341             count = count-1
342         if count == 0:
343             return i
344     return -1
345
346
347 def find_end_of(lines, i, start_token, end_token):
348     count = 1
349     n = len(lines)
350     while i < n:
351         i = find_tokens(lines, [end_token, start_token], i+1)
352         if i == -1:
353             return -1
354         if check_token(lines[i], start_token):
355             count = count+1
356         else:
357             count = count-1
358         if count == 0:
359             return i
360     return -1
361
362
363 def find_nonempty_line(lines, start, end = 0):
364     if end == 0:
365         end = len(lines)
366     for i in xrange(start, end):
367         if is_nonempty_line(lines[i]):
368             return i
369     return -1
370
371
372 def find_end_of_inset(lines, i):
373     " Find end of inset, where lines[i] is included."
374     return find_end_of(lines, i, "\\begin_inset", "\\end_inset")
375
376
377 def find_end_of_layout(lines, i):
378     " Find end of layout, where lines[i] is included."
379     return find_end_of(lines, i, "\\begin_layout", "\\end_layout")
380
381
382 def is_in_inset(lines, i, inset):
383     '''
384     Checks if line i is in an inset of the given type.
385     If so, returns starting and ending lines.
386     Otherwise, returns False.
387     Example:
388       is_in_inset(document.body, i, "\\begin_inset Tabular")
389     returns False unless i is within a table. If it is, then
390     it returns the line on which the table begins and the one
391     on which it ends. Note that this pair will evaulate to
392     boolean True, so
393       if is_in_inset(...):
394     will do what you expect.
395     '''
396     defval = (-1, -1)
397     stins = find_token_backwards(lines, inset, i)
398     if stins == -1:
399       return defval
400     endins = find_end_of_inset(lines, stins)
401     # note that this includes the notfound case.
402     if endins < i:
403       return defval
404     return (stins, endins)
405
406
407 def get_containing_inset(lines, i):
408   ''' 
409   Finds out what kind of inset line i is within. Returns a 
410   list containing (i) what follows \begin_inset on the the line 
411   on which the inset begins, plus the starting and ending line.
412   Returns False on any kind of error or if it isn't in an inset.
413   '''
414   stins = find_token_backwards(lines, i, "\\begin_inset")
415   if stins == -1:
416       return False
417   endins = find_end_of_inset(lines, stins)
418   if endins < i:
419       return False
420   inset = get_value(lines, "\\begin_inset", stins)
421   if inset == "":
422       # shouldn't happen
423       return False
424   return (inset, stins, endins)
425
426
427 def get_containing_layout(lines, i):
428   ''' 
429   Finds out what kind of layout line i is within. Returns a 
430   list containing (i) what follows \begin_layout on the the line 
431   on which the layout begins, plus the starting and ending line.
432   Returns False on any kind of error.
433   '''
434   stins = find_token_backwards(lines, i, "\\begin_layout")
435   if stins == -1:
436       return False
437   endins = find_end_of_layout(lines, stins)
438   if endins < i:
439       return False
440   lay = get_value(lines, "\\begin_layout", stins)
441   if lay == "":
442       # shouldn't happen
443       return False
444   return (lay, stins, endins)