]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/parser_tools.py
Update RELEASE-NOTES
[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 module offers 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 set to True.
36
37 find_tokens(lines, tokens, start[, end[, ignorews]]):
38   Returns the first line i, start <= i < end, on which
39   one 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 find_end_of_sequence(lines, i):
108   Find the end of the sequence of layouts of the same kind.
109   Considers nesting. If the last paragraph in sequence is nested,
110   the position of the last \end_deeper is returned, else
111   the position of the last \end_layout.
112
113 is_in_inset(lines, i, inset):
114   Checks if line i is in an inset of the given type.
115   If so, returns starting and ending lines. Otherwise, 
116   returns False.
117   Example:
118     is_in_inset(document.body, i, "\\begin_inset Tabular")
119   returns False unless i is within a table. If it is, then
120   it returns the line on which the table begins and the one
121   on which it ends. Note that this pair will evaulate to
122   boolean True, so
123     if is_in_inset(...):
124   will do what you expect.
125
126 get_containing_inset(lines, i):
127   Finds out what kind of inset line i is within. Returns a 
128   list containing what follows \begin_inset on the line 
129   on which the inset begins, plus the starting and ending line.
130   Returns False on any kind of error or if it isn't in an inset.
131   So get_containing_inset(document.body, i) might return:
132     ("CommandInset ref", 300, 306)
133   if i is within an InsetRef beginning on line 300 and ending
134   on line 306.
135
136 get_containing_layout(lines, i):
137   As get_containing_inset, but for layout. Additionally returns the
138   position of real paragraph start (after par params) as 4th value.
139
140 find_nonempty_line(lines, start[, end):
141   Finds the next non-empty line.
142
143 check_token(line, token):
144   Does line begin with token?
145
146 is_nonempty_line(line):
147   Does line contain something besides whitespace?
148
149 count_pars_in_inset(lines, i):
150   Counts the paragraphs inside an inset.
151
152 '''
153
154 import re
155
156 # Utilities for one line
157 def check_token(line, token):
158     """ check_token(line, token) -> bool
159
160     Return True if token is present in line and is the first element
161     else returns False."""
162
163     return line[:len(token)] == token
164
165
166 def is_nonempty_line(line):
167     """ is_nonempty_line(line) -> bool
168
169     Return False if line is either empty or it has only whitespaces,
170     else return True."""
171     return line != " "*len(line)
172
173
174 # Utilities for a list of lines
175 def find_token(lines, token, start, end = 0, ignorews = False):
176     """ find_token(lines, token, start[[, end], ignorews]) -> int
177
178     Return the lowest line where token is found, and is the first
179     element, in lines[start, end].
180     
181     If ignorews is True (default is False), then differences in
182     whitespace are ignored, except that there must be no extra
183     whitespace following token itself.
184
185     Return -1 on failure."""
186
187     if end == 0 or end > len(lines):
188         end = len(lines)
189     m = len(token)
190     for i in xrange(start, end):
191         if ignorews:
192             x = lines[i].split()
193             y = token.split()
194             if len(x) < len(y):
195                 continue
196             if x[:len(y)] == y:
197                 return i
198         else:
199             if lines[i][:m] == token:
200                 return i
201     return -1
202
203
204 def find_token_exact(lines, token, start, end = 0):
205     return find_token(lines, token, start, end, True)
206
207
208 def find_tokens(lines, tokens, start, end = 0, ignorews = False):
209     """ find_tokens(lines, tokens, start[[, end], ignorews]) -> int
210
211     Return the lowest line where one token in tokens is found, and is
212     the first element, in lines[start, end].
213
214     Return -1 on failure."""
215     if end == 0 or end > len(lines):
216         end = len(lines)
217
218     for i in xrange(start, end):
219         for token in tokens:
220             if ignorews:
221                 x = lines[i].split()
222                 y = token.split()
223                 if len(x) < len(y):
224                     continue
225                 if x[:len(y)] == y:
226                     return i
227             else:
228                 if lines[i][:len(token)] == token:
229                     return i
230     return -1
231
232
233 def find_tokens_exact(lines, tokens, start, end = 0):
234     return find_tokens(lines, tokens, start, end, True)
235
236
237 def find_re(lines, rexp, start, end = 0):
238     """ find_token_re(lines, rexp, start[, end]) -> int
239
240     Return the lowest line where rexp, a regular expression, is found
241     in lines[start, end].
242
243     Return -1 on failure."""
244
245     if end == 0 or end > len(lines):
246         end = len(lines)
247     for i in xrange(start, end):
248         if rexp.match(lines[i]):
249                 return i
250     return -1
251
252
253 def find_token_backwards(lines, token, start):
254     """ find_token_backwards(lines, token, start) -> int
255
256     Return the highest line where token is found, and is the first
257     element, in lines[start, end].
258
259     Return -1 on failure."""
260     m = len(token)
261     for i in xrange(start, -1, -1):
262         line = lines[i]
263         if line[:m] == token:
264             return i
265     return -1
266
267
268 def find_tokens_backwards(lines, tokens, start):
269     """ find_tokens_backwards(lines, token, start) -> int
270
271     Return the highest line where token is found, and is the first
272     element, in lines[end, start].
273
274     Return -1 on failure."""
275     for i in xrange(start, -1, -1):
276         line = lines[i]
277         for token in tokens:
278             if line[:len(token)] == token:
279                 return i
280     return -1
281
282
283 def get_value(lines, token, start, end = 0, default = ""):
284     """ get_value(lines, token, start[[, end], default]) -> string
285
286     Find the next line that looks like:
287       token followed by other stuff
288     Returns "followed by other stuff" with leading and trailing
289     whitespace removed.
290     """
291
292     i = find_token_exact(lines, token, start, end)
293     if i == -1:
294         return default
295     l = lines[i].split(None, 1)
296     if len(l) > 1:
297         return l[1].strip()
298     return default
299
300
301 def get_quoted_value(lines, token, start, end = 0, default = ""):
302     """ get_quoted_value(lines, token, start[[, end], default]) -> string
303
304     Find the next line that looks like:
305       token "followed by other stuff"
306     Returns "followed by other stuff" with leading and trailing
307     whitespace and quotes removed. If there are no quotes, that is OK too.
308     So use get_value to preserve possible quotes, this one to remove them,
309     if they are there.
310     Note that we will NOT strip quotes from default!
311     """
312     val = get_value(lines, token, start, end, "")
313     if not val:
314       return default
315     return val.strip('"')
316
317
318 def get_option_value(line, option):
319     rx = option + '\s*=\s*"([^"]+)"'
320     rx = re.compile(rx)
321     m = rx.search(line)
322     if not m:
323       return ""
324     return m.group(1)
325
326
327 def set_option_value(line, option, value):
328     rx = '(' + option + '\s*=\s*")[^"]+"'
329     rx = re.compile(rx)
330     m = rx.search(line)
331     if not m:
332         return line
333     return re.sub(rx, '\g<1>' + value + '"', line)
334
335
336 def del_token(lines, token, start, end = 0):
337     """ del_token(lines, token, start, end) -> int
338
339     Find the first line in lines where token is the first element 
340     and delete that line. Returns True if we deleted a line, False
341     if we did not."""
342
343     k = find_token_exact(lines, token, start, end)
344     if k == -1:
345         return False
346     del lines[k]
347     return True
348
349
350 def find_beginning_of(lines, i, start_token, end_token):
351     count = 1
352     while i > 0:
353         i = find_tokens_backwards(lines, [start_token, end_token], i-1)
354         if i == -1:
355             return -1
356         if check_token(lines[i], end_token):
357             count = count+1
358         else:
359             count = count-1
360         if count == 0:
361             return i
362     return -1
363
364
365 def find_end_of(lines, i, start_token, end_token):
366     count = 1
367     n = len(lines)
368     while i < n:
369         i = find_tokens(lines, [end_token, start_token], i+1)
370         if i == -1:
371             return -1
372         if check_token(lines[i], start_token):
373             count = count+1
374         else:
375             count = count-1
376         if count == 0:
377             return i
378     return -1
379
380
381 def find_nonempty_line(lines, start, end = 0):
382     if end == 0:
383         end = len(lines)
384     for i in xrange(start, end):
385         if is_nonempty_line(lines[i]):
386             return i
387     return -1
388
389
390 def find_end_of_inset(lines, i):
391     " Find end of inset, where lines[i] is included."
392     return find_end_of(lines, i, "\\begin_inset", "\\end_inset")
393
394
395 def find_end_of_layout(lines, i):
396     " Find end of layout, where lines[i] is included."
397     return find_end_of(lines, i, "\\begin_layout", "\\end_layout")
398
399
400 def is_in_inset(lines, i, inset):
401     '''
402     Checks if line i is in an inset of the given type.
403     If so, returns starting and ending lines.
404     Otherwise, returns False.
405     Example:
406       is_in_inset(document.body, i, "\\begin_inset Tabular")
407     returns False unless i is within a table. If it is, then
408     it returns the line on which the table begins and the one
409     on which it ends. Note that this pair will evaulate to
410     boolean True, so
411       if is_in_inset(...):
412     will do what you expect.
413     '''
414     defval = (-1, -1)
415     stins = find_token_backwards(lines, inset, i)
416     if stins == -1:
417       return defval
418     endins = find_end_of_inset(lines, stins)
419     # note that this includes the notfound case.
420     if endins < i:
421       return defval
422     return (stins, endins)
423
424
425 def get_containing_inset(lines, i):
426   ''' 
427   Finds out what kind of inset line i is within. Returns a 
428   list containing (i) what follows \begin_inset on the line 
429   on which the inset begins, plus the starting and ending line.
430   Returns False on any kind of error or if it isn't in an inset.
431   '''
432   j = i
433   while True:
434       stins = find_token_backwards(lines, "\\begin_inset", j)
435       if stins == -1:
436           return False
437       endins = find_end_of_inset(lines, stins)
438       if endins > j:
439           break
440       j = stins - 1
441   
442   inset = get_value(lines, "\\begin_inset", stins)
443   if inset == "":
444       # shouldn't happen
445       return False
446   return (inset, stins, endins)
447
448
449 def get_containing_layout(lines, i):
450   ''' 
451   Finds out what kind of layout line i is within. Returns a 
452   list containing what follows \begin_layout on the line 
453   on which the layout begins, plus the starting and ending line
454   and the start of the paragraph (after all params). I.e, returns:
455     (layoutname, layoutstart, layoutend, startofcontent)
456   Returns False on any kind of error.
457   '''
458   j = i
459   while True:
460       stlay = find_token_backwards(lines, "\\begin_layout", j)
461       if stlay == -1:
462           return False
463       endlay = find_end_of_layout(lines, stlay)
464       if endlay > i:
465           break
466       j = stlay - 1
467   
468   lay = get_value(lines, "\\begin_layout", stlay)
469   if lay == "":
470       # shouldn't happen
471       return False
472   par_params = ["\\noindent", "\\indent", "\\indent-toggle", "\\leftindent",
473                 "\\start_of_appendix", "\\paragraph_spacing single",
474                 "\\paragraph_spacing onehalf", "\\paragraph_spacing double",
475                 "\\paragraph_spacing other", "\\align", "\\labelwidthstring"]
476   stpar = stlay
477   while True:
478       stpar += 1
479       if lines[stpar] not in par_params:
480           break
481   return (lay, stlay, endlay, stpar)
482
483
484 def count_pars_in_inset(lines, i):
485   ''' 
486   Counts the paragraphs within this inset
487   '''
488   ins = get_containing_inset(lines, i)
489   if ins == -1:
490       return -1
491   pars = 0
492   for j in range(ins[1], ins[2]):
493       m = re.match(r'\\begin_layout (.*)', lines[j])
494       if m and get_containing_inset(lines, j)[0] == ins[0]:
495           pars += 1
496   
497   return pars
498
499
500 def find_end_of_sequence(lines, i):
501   ''' 
502   Returns the end of a sequence of identical layouts.
503   '''
504   lay = get_containing_layout(lines, i)
505   if lay == False:
506       return -1
507   layout = lay[0]
508   endlay = lay[2]
509   i = endlay
510   while True:
511       m = re.match(r'\\begin_layout (.*)', lines[i])
512       if m and m.group(1) != layout:
513           return endlay
514       elif lines[i] == "\\begin_deeper":
515           j = find_end_of(lines, i, "\\begin_deeper", "\\end_deeper")
516           if j != -1:
517               i = j
518               endlay = j
519               continue
520       if m and m.group(1) == layout:
521           endlay = find_end_of_layout(lines, i)
522           i = endlay
523           continue
524       if i == len(lines) - 1:
525           break
526       i = i + 1
527
528   return endlay
529