]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/parser_tools.py
Check path of Qt tools if qtchooser is detected
[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 range(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 range(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 range(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 range(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 range(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_bool_value(lines, token, start, end = 0, default = None):
319     """ get_value(lines, token, start[[, end], default]) -> string
320
321     Find the next line that looks like:
322       token bool_value
323
324     Returns True if bool_value is 1 or true and
325     False if bool_value is 0 or false
326     """
327
328     val = get_value(lines, token, start, end, "")
329
330     if val == "1" or val == "true":
331         return True
332     if val == "0" or val == "false":
333         return False
334     return default
335
336
337 def get_option_value(line, option):
338     rx = option + '\s*=\s*"([^"]+)"'
339     rx = re.compile(rx)
340     m = rx.search(line)
341     if not m:
342       return ""
343     return m.group(1)
344
345
346 def set_option_value(line, option, value):
347     rx = '(' + option + '\s*=\s*")[^"]+"'
348     rx = re.compile(rx)
349     m = rx.search(line)
350     if not m:
351         return line
352     return re.sub(rx, '\g<1>' + value + '"', line)
353
354
355 def del_token(lines, token, start, end = 0):
356     """ del_token(lines, token, start, end) -> int
357
358     Find the first line in lines where token is the first element 
359     and delete that line. Returns True if we deleted a line, False
360     if we did not."""
361
362     k = find_token_exact(lines, token, start, end)
363     if k == -1:
364         return False
365     del lines[k]
366     return True
367
368
369 def find_beginning_of(lines, i, start_token, end_token):
370     count = 1
371     while i > 0:
372         i = find_tokens_backwards(lines, [start_token, end_token], i-1)
373         if i == -1:
374             return -1
375         if check_token(lines[i], end_token):
376             count = count+1
377         else:
378             count = count-1
379         if count == 0:
380             return i
381     return -1
382
383
384 def find_end_of(lines, i, start_token, end_token):
385     count = 1
386     n = len(lines)
387     while i < n:
388         i = find_tokens(lines, [end_token, start_token], i+1)
389         if i == -1:
390             return -1
391         if check_token(lines[i], start_token):
392             count = count+1
393         else:
394             count = count-1
395         if count == 0:
396             return i
397     return -1
398
399
400 def find_nonempty_line(lines, start, end = 0):
401     if end == 0:
402         end = len(lines)
403     for i in range(start, end):
404         if is_nonempty_line(lines[i]):
405             return i
406     return -1
407
408
409 def find_end_of_inset(lines, i):
410     " Find end of inset, where lines[i] is included."
411     return find_end_of(lines, i, "\\begin_inset", "\\end_inset")
412
413
414 def find_end_of_layout(lines, i):
415     " Find end of layout, where lines[i] is included."
416     return find_end_of(lines, i, "\\begin_layout", "\\end_layout")
417
418
419 def is_in_inset(lines, i, inset):
420     '''
421     Checks if line i is in an inset of the given type.
422     If so, returns starting and ending lines.
423     Otherwise, returns False.
424     Example:
425       is_in_inset(document.body, i, "\\begin_inset Tabular")
426     returns False unless i is within a table. If it is, then
427     it returns the line on which the table begins and the one
428     on which it ends. Note that this pair will evaulate to
429     boolean True, so
430       if is_in_inset(...):
431     will do what you expect.
432     '''
433     defval = (-1, -1)
434     stins = find_token_backwards(lines, inset, i)
435     if stins == -1:
436       return defval
437     endins = find_end_of_inset(lines, stins)
438     # note that this includes the notfound case.
439     if endins < i:
440       return defval
441     return (stins, endins)
442
443
444 def get_containing_inset(lines, i):
445   ''' 
446   Finds out what kind of inset line i is within. Returns a 
447   list containing (i) what follows \begin_inset on the line
448   on which the inset begins, plus the starting and ending line.
449   Returns False on any kind of error or if it isn't in an inset.
450   '''
451   j = i
452   while True:
453       stins = find_token_backwards(lines, "\\begin_inset", j)
454       if stins == -1:
455           return False
456       endins = find_end_of_inset(lines, stins)
457       if endins > j:
458           break
459       j = stins - 1
460
461   if endins < i:
462       return False
463
464   inset = get_value(lines, "\\begin_inset", stins)
465   if inset == "":
466       # shouldn't happen
467       return False
468   return (inset, stins, endins)
469
470
471 def get_containing_layout(lines, i):
472   ''' 
473   Finds out what kind of layout line i is within. Returns a 
474   list containing what follows \begin_layout on the line
475   on which the layout begins, plus the starting and ending line
476   and the start of the paragraph (after all params). I.e, returns:
477     (layoutname, layoutstart, layoutend, startofcontent)
478   Returns False on any kind of error.
479   '''
480   j = i
481   while True:
482       stlay = find_token_backwards(lines, "\\begin_layout", j)
483       if stlay == -1:
484           return False
485       endlay = find_end_of_layout(lines, stlay)
486       if endlay > i:
487           break
488       j = stlay - 1
489
490   if endlay < i:
491       return False
492
493   lay = get_value(lines, "\\begin_layout", stlay)
494   if lay == "":
495       # shouldn't happen
496       return False
497   par_params = ["\\noindent", "\\indent", "\\indent-toggle", "\\leftindent",
498                 "\\start_of_appendix", "\\paragraph_spacing", "\\align",
499                 "\\labelwidthstring"]
500   stpar = stlay
501   while True:
502       stpar += 1
503       if lines[stpar].split(' ', 1)[0] not in par_params:
504           break
505   return (lay, stlay, endlay, stpar)
506
507
508 def count_pars_in_inset(lines, i):
509   '''
510   Counts the paragraphs within this inset
511   '''
512   ins = get_containing_inset(lines, i)
513   if ins == -1:
514       return -1
515   pars = 0
516   for j in range(ins[1], ins[2]):
517       m = re.match(r'\\begin_layout (.*)', lines[j])
518       if m and get_containing_inset(lines, j)[0] == ins[0]:
519           pars += 1
520
521   return pars
522
523
524 def find_end_of_sequence(lines, i):
525   '''
526   Returns the end of a sequence of identical layouts.
527   '''
528   lay = get_containing_layout(lines, i)
529   if lay == False:
530       return -1
531   layout = lay[0]
532   endlay = lay[2]
533   i = endlay
534   while True:
535       m = re.match(r'\\begin_layout (.*)', lines[i])
536       if m and m.group(1) != layout:
537           return endlay
538       elif lines[i] == "\\begin_deeper":
539           j = find_end_of(lines, i, "\\begin_deeper", "\\end_deeper")
540           if j != -1:
541               i = j
542               endlay = j
543               continue
544       if m and m.group(1) == layout:
545           endlay = find_end_of_layout(lines, i)
546           i = endlay
547           continue
548       if i == len(lines) - 1:
549           break
550       i = i + 1
551
552   return endlay
553