]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/parser_tools.py
More sensible longtable caption handling (needed for bug #7412)
[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 set_option_value(line, option, value):
319     rx = '(' + option + '\s*=\s*")[^"]+"'
320     rx = re.compile(rx)
321     m = rx.search(line)
322     if not m:
323         return line
324     return re.sub(rx, '\g<1>' + value + '"', line)
325
326
327 def del_token(lines, token, start, end = 0):
328     """ del_token(lines, token, start, end) -> int
329
330     Find the first line in lines where token is the first element 
331     and delete that line. Returns True if we deleted a line, False
332     if we did not."""
333
334     k = find_token_exact(lines, token, start, end)
335     if k == -1:
336         return False
337     del lines[k]
338     return True
339
340
341 def find_beginning_of(lines, i, start_token, end_token):
342     count = 1
343     while i > 0:
344         i = find_tokens_backwards(lines, [start_token, end_token], i-1)
345         if i == -1:
346             return -1
347         if check_token(lines[i], end_token):
348             count = count+1
349         else:
350             count = count-1
351         if count == 0:
352             return i
353     return -1
354
355
356 def find_end_of(lines, i, start_token, end_token):
357     count = 1
358     n = len(lines)
359     while i < n:
360         i = find_tokens(lines, [end_token, start_token], i+1)
361         if i == -1:
362             return -1
363         if check_token(lines[i], start_token):
364             count = count+1
365         else:
366             count = count-1
367         if count == 0:
368             return i
369     return -1
370
371
372 def find_nonempty_line(lines, start, end = 0):
373     if end == 0:
374         end = len(lines)
375     for i in xrange(start, end):
376         if is_nonempty_line(lines[i]):
377             return i
378     return -1
379
380
381 def find_end_of_inset(lines, i):
382     " Find end of inset, where lines[i] is included."
383     return find_end_of(lines, i, "\\begin_inset", "\\end_inset")
384
385
386 def find_end_of_layout(lines, i):
387     " Find end of layout, where lines[i] is included."
388     return find_end_of(lines, i, "\\begin_layout", "\\end_layout")
389
390
391 def is_in_inset(lines, i, inset):
392     '''
393     Checks if line i is in an inset of the given type.
394     If so, returns starting and ending lines.
395     Otherwise, returns False.
396     Example:
397       is_in_inset(document.body, i, "\\begin_inset Tabular")
398     returns False unless i is within a table. If it is, then
399     it returns the line on which the table begins and the one
400     on which it ends. Note that this pair will evaulate to
401     boolean True, so
402       if is_in_inset(...):
403     will do what you expect.
404     '''
405     defval = (-1, -1)
406     stins = find_token_backwards(lines, inset, i)
407     if stins == -1:
408       return defval
409     endins = find_end_of_inset(lines, stins)
410     # note that this includes the notfound case.
411     if endins < i:
412       return defval
413     return (stins, endins)
414
415
416 def get_containing_inset(lines, i):
417   ''' 
418   Finds out what kind of inset line i is within. Returns a 
419   list containing (i) what follows \begin_inset on the the line 
420   on which the inset begins, plus the starting and ending line.
421   Returns False on any kind of error or if it isn't in an inset.
422   '''
423   stins = find_token_backwards(lines, i, "\\begin_inset")
424   if stins == -1:
425       return False
426   endins = find_end_of_inset(lines, stins)
427   if endins < i:
428       return False
429   inset = get_value(lines, "\\begin_inset", stins)
430   if inset == "":
431       # shouldn't happen
432       return False
433   return (inset, stins, endins)
434
435
436 def get_containing_layout(lines, i):
437   ''' 
438   Finds out what kind of layout line i is within. Returns a 
439   list containing (i) what follows \begin_layout on the the line 
440   on which the layout begins, plus the starting and ending line.
441   Returns False on any kind of error.
442   '''
443   stins = find_token_backwards(lines, i, "\\begin_layout")
444   if stins == -1:
445       return False
446   endins = find_end_of_layout(lines, stins)
447   if endins < i:
448       return False
449   lay = get_value(lines, "\\begin_layout", stins)
450   if lay == "":
451       # shouldn't happen
452       return False
453   return (lay, stins, endins)