# Copyright(C) 2014 Simon Murail
#
# This file is part of woob.
#
# woob is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# woob is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with woob. If not, see <http://www.gnu.org/licenses/>.
import re
from ast import literal_eval
from woob.browser.filters.standard import Filter, Regexp, RegexpError, FormatError, ItemNotFound
__all__ = ['JSPayload', 'JSValue', 'JSVar']
def _quoted(q):
return r'(?<!\\){0}(?:\\{0}|[^{0}])*{0}'.format(q)
[docs]class JSPayload(Filter):
r"""
Get Javascript code from tag's text, cleaned from all comments.
It filters code in a such a way that corner cases are handled, such as
comments in string literals and comments in comments.
The following snippet is borrowed from <https://ostermiller.org/findcomment.html>:
>>> JSPayload.filter('''someString = "An example comment: /* example */";
...
... // The comment around this code has been commented out.
... // /*
... some_code();
... // */''')
'someString = "An example comment: /* example */";\n\nsome_code();\n'
"""
_single_line_comment = r'[ \t\v\f]*//.*\r?(?:\n|$)'
_multi_line_comment = r'/\*(?:.|[\r\n])*?\*/'
_splitter = re.compile('(?:(%s|%s)|%s|%s)' % (_quoted('"'),
_quoted("'"),
_single_line_comment,
_multi_line_comment))
[docs] @classmethod
def filter(cls, value):
return ''.join(filter(bool, cls._splitter.split(value)))
[docs]class JSValue(Regexp):
r"""
Get one or many JavaScript literals.
It only understands literal values, but should parse them well. Values
are converted in python values, quotes and slashes in strings are stripped.
>>> JSValue().filter('boringVar = "boring string"')
u'boring string'
>>> JSValue().filter('somecode(); doConfuse(0xdead, cat);')
57005
>>> JSValue(need_type=int, nth=2).filter('fazboo("3", "5", 7, "9");')
7
>>> JSValue(nth='*').filter('foo([1, 2, 3], "blah", 5.0, true, null]);')
[1, 2, 3, u'blah', 5.0, True, None]
"""
pattern = r"""(?x: # re.X
(?:(?P<float>(?:[-+]\s*)? # float ?
(?:(?:\d+\.\d*|\d*\.\d+)(?:[eE]\d+)?
|\d+[eE]\d+))
|(?P<int>(?:[-+]\s*)?(?:0[bB][01]+ # int ?
|0[oO][0-7]+
|0[xX][0-9a-fA-F]+
|\d+))
|(?:(?:(?:new\s+)?String\()?(?P<str>(?:%s|%s))) # str ?
|(?P<bool>true|false) # bool ?
|(?P<None>null)) # None ?
)""" % (_quoted('"'), _quoted("'"))
[docs] def to_python(self, m):
"Convert MatchObject to python value"
values = m.groupdict()
for t, v in values.items():
if v is not None:
break
if self.need_type and t != self.need_type:
raise ItemNotFound('Value with type %s not found' % self.need_type)
if t in ('int', 'float'):
return literal_eval(v)
if t == 'str':
return literal_eval(v)
if t == 'bool':
return v == 'true'
if t == 'None':
return
if self.default:
return self.default
raise FormatError('Unable to parse %r value' % m.group(0))
def __init__(self, selector=None, need_type=None, **kwargs):
assert 'pattern' not in kwargs and 'flags' not in kwargs, \
"It would be meaningless to define a pattern and/or flags, use Regexp"
assert 'template' not in kwargs, "Can't use a template, use Regexp if you have to"
self.need_type = need_type.__name__ if type(need_type) == type else need_type
super(JSValue, self).__init__(selector, pattern=self.pattern, template=self.to_python, **kwargs)
[docs]class JSVar(JSValue):
r"""
Get assigned value of a variable, either as an initialisation value, either
as an assignement. One can use Regexp's nth parameter to be more specific.
See JSValue for more details about parsed values.
>>> JSVar(var='test').filter("var test = .1;\nsomecode()")
0.1
>>> JSVar(var='test').filter("test = 666;\nsomecode()")
666
>>> JSVar(var='test').filter("test = 'Some \\'string\\' value, isn\\'t it ?';\nsomecode()")
u"Some 'string' value, isn't it ?"
>>> JSVar(var='test').filter('test = "Some \\"string\\" value";\nsomecode()')
u'Some "string" value'
>>> JSVar(var='test').filter("var test = false;\nsomecode()")
False
>>> JSVar(var='test', nth=1).filter("var test = false; test = true;\nsomecode()")
True
"""
pattern_template = r"""(?x: # re.X
(?:var\s+)? # optional var keyword
\b%s # var name
\s*=\s* # equal sign
)""" + JSValue.pattern
def __init__(self, selector=None, var=None, need_type=None, **kwargs):
assert var is not None, 'Please give a var parameter'
self.var = var
self.pattern = self.pattern_template % re.escape(var)
super(JSVar, self).__init__(selector, need_type=need_type, **kwargs)
[docs] def filter(self, txt):
try:
return super(JSVar, self).filter(txt)
except RegexpError:
raise ItemNotFound('Variable %r not found' % self.var)