1# 2# Licensed under the Apache License, Version 2.0 (the "License"); you may 3# not use this file except in compliance with the License. You may obtain 4# a copy of the License at 5# 6# http://www.apache.org/licenses/LICENSE-2.0 7# 8# Unless required by applicable law or agreed to in writing, software 9# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 10# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 11# License for the specific language governing permissions and limitations 12# under the License. 13 14from .. import lexer 15from .. import parser 16from .. import Fields, This, Child 17 18from . import arithmetic as _arithmetic 19from . import filter as _filter 20from . import iterable as _iterable 21from . import string as _string 22 23 24class ExtendedJsonPathLexer(lexer.JsonPathLexer): 25 """Custom LALR-lexer for JsonPath""" 26 literals = lexer.JsonPathLexer.literals + ['?', '@', '+', '*', '/', '-'] 27 tokens = (['BOOL'] + 28 parser.JsonPathLexer.tokens + 29 ['FILTER_OP', 'SORT_DIRECTION', 'FLOAT']) 30 31 t_FILTER_OP = r'=~|==?|<=|>=|!=|<|>' 32 33 def t_BOOL(self, t): 34 r'true|false' 35 t.value = True if t.value == 'true' else False 36 return t 37 38 def t_SORT_DIRECTION(self, t): 39 r',?\s*(/|\\)' 40 t.value = t.value[-1] 41 return t 42 43 def t_ID(self, t): 44 r'@?[a-zA-Z_][a-zA-Z0-9_@\-]*' 45 # NOTE(sileht): This fixes the ID expression to be 46 # able to use @ for `This` like any json query 47 t.type = self.reserved_words.get(t.value, 'ID') 48 return t 49 50 def t_FLOAT(self, t): 51 r'-?\d+\.\d+' 52 t.value = float(t.value) 53 return t 54 55 56class ExtentedJsonPathParser(parser.JsonPathParser): 57 """Custom LALR-parser for JsonPath""" 58 59 tokens = ExtendedJsonPathLexer.tokens 60 61 def __init__(self, debug=False, lexer_class=None): 62 lexer_class = lexer_class or ExtendedJsonPathLexer 63 super(ExtentedJsonPathParser, self).__init__(debug, lexer_class) 64 65 def p_jsonpath_operator_jsonpath(self, p): 66 """jsonpath : NUMBER operator NUMBER 67 | FLOAT operator FLOAT 68 | ID operator ID 69 | NUMBER operator jsonpath 70 | FLOAT operator jsonpath 71 | jsonpath operator NUMBER 72 | jsonpath operator FLOAT 73 | jsonpath operator jsonpath 74 """ 75 76 # NOTE(sileht): If we have choice between a field or a string we 77 # always choice string, because field can be full qualified 78 # like $.foo == foo and where string can't. 79 for i in [1, 3]: 80 if (isinstance(p[i], Fields) and len(p[i].fields) == 1): # noqa 81 p[i] = p[i].fields[0] 82 83 p[0] = _arithmetic.Operation(p[1], p[2], p[3]) 84 85 def p_operator(self, p): 86 """operator : '+' 87 | '-' 88 | '*' 89 | '/' 90 """ 91 p[0] = p[1] 92 93 def p_jsonpath_named_operator(self, p): 94 "jsonpath : NAMED_OPERATOR" 95 if p[1] == 'len': 96 p[0] = _iterable.Len() 97 elif p[1] == 'sorted': 98 p[0] = _iterable.SortedThis() 99 elif p[1].startswith("split("): 100 p[0] = _string.Split(p[1]) 101 elif p[1].startswith("sub("): 102 p[0] = _string.Sub(p[1]) 103 elif p[1].startswith("str("): 104 p[0] = _string.Str(p[1]) 105 else: 106 super(ExtentedJsonPathParser, self).p_jsonpath_named_operator(p) 107 108 def p_expression(self, p): 109 """expression : jsonpath 110 | jsonpath FILTER_OP ID 111 | jsonpath FILTER_OP FLOAT 112 | jsonpath FILTER_OP NUMBER 113 | jsonpath FILTER_OP BOOL 114 """ 115 if len(p) == 2: 116 left, op, right = p[1], None, None 117 else: 118 __, left, op, right = p 119 p[0] = _filter.Expression(left, op, right) 120 121 def p_expressions_expression(self, p): 122 "expressions : expression" 123 p[0] = [p[1]] 124 125 def p_expressions_and(self, p): 126 "expressions : expressions '&' expressions" 127 # TODO(sileht): implements '|' 128 p[0] = p[1] + p[3] 129 130 def p_expressions_parens(self, p): 131 "expressions : '(' expressions ')'" 132 p[0] = p[2] 133 134 def p_filter(self, p): 135 "filter : '?' expressions " 136 p[0] = _filter.Filter(p[2]) 137 138 def p_jsonpath_filter(self, p): 139 "jsonpath : jsonpath '[' filter ']'" 140 p[0] = Child(p[1], p[3]) 141 142 def p_sort(self, p): 143 "sort : SORT_DIRECTION jsonpath" 144 p[0] = (p[2], p[1] != "/") 145 146 def p_sorts_sort(self, p): 147 "sorts : sort" 148 p[0] = [p[1]] 149 150 def p_sorts_comma(self, p): 151 "sorts : sorts sorts" 152 p[0] = p[1] + p[2] 153 154 def p_jsonpath_sort(self, p): 155 "jsonpath : jsonpath '[' sorts ']'" 156 sort = _iterable.SortedThis(p[3]) 157 p[0] = Child(p[1], sort) 158 159 def p_jsonpath_this(self, p): 160 "jsonpath : '@'" 161 p[0] = This() 162 163 precedence = [ 164 ('left', '+', '-'), 165 ('left', '*', '/'), 166 ] + parser.JsonPathParser.precedence + [ 167 ('nonassoc', 'ID'), 168 ] 169 170 171def parse(path, debug=False): 172 return ExtentedJsonPathParser(debug=debug).parse(path) 173