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