@@ -122,12 +122,34 @@ private function evaluateName(string $name, mixed $value): array
122122return \array_key_exists ($ name ,$ value ) ? [$ value [$ name ]] : [];
123123 }
124124
125+ private function splitByOperator (string $ expr ,string $ operator ):array
126+ {
127+ $ normalizedExpr = JsonPathUtils::normalizeWhitespace ($ expr );
128+ $ pattern ='/\s* ' .preg_quote ($ operator ,'/ ' ).'\s*/ ' ;
129+ $ parts =preg_split ($ pattern ,$ normalizedExpr ,2 );
130+
131+ if (2 ===\count ($ parts )) {
132+ return [trim ($ parts [0 ]),trim ($ parts [1 ])];
133+ }
134+
135+ return [];
136+ }
137+
138+ private function containsOperator (string $ expr ,string $ operator ):bool
139+ {
140+ $ normalizedExpr = JsonPathUtils::normalizeWhitespace ($ expr );
141+ $ pattern ='/\s* ' .preg_quote ($ operator ,'/ ' ).'\s*/ ' ;
142+
143+ return 1 ===preg_match ($ pattern ,$ normalizedExpr );
144+ }
145+
125146private function evaluateBracket (string $ expr ,mixed $ value ):array
126147 {
127148if (!\is_array ($ value )) {
128149return [];
129150 }
130151
152+ $ expr = JsonPathUtils::normalizeWhitespace ($ expr );
131153if ('* ' ===$ expr ) {
132154return array_values ($ value );
133155 }
@@ -150,8 +172,8 @@ private function evaluateBracket(string $expr, mixed $value): array
150172 }
151173
152174$ result = [];
153- foreach (explode ( ' , ' ,$ expr )as $ index ) {
154- $ index = (int )trim ($ index );
175+ foreach (preg_split ( ' /\s*,\s*/ ' ,$ expr )as $ indexStr ) {
176+ $ index = (int )trim ($ indexStr );
155177if ($ index <0 ) {
156178$ index =\count ($ value ) +$ index ;
157179 }
@@ -163,13 +185,14 @@ private function evaluateBracket(string $expr, mixed $value): array
163185return $ result ;
164186 }
165187
166- // start, end and step
167- if (preg_match ('/^(-?\d*):(-?\d*)(?::(-?\d+))?$/ ' ,$ expr ,$ matches )) {
188+ if (preg_match ('/^(-?\d*+)\s*+:\s*+(-?\d*+)(?:\s*+:\s*+(-?\d++))?$/ ' ,$ expr ,$ matches )) {
168189if (!array_is_list ($ value )) {
169190return [];
170191 }
171192
172193$ length =\count ($ value );
194+ $ matches =array_map ('trim ' ,$ matches );
195+
173196$ start ='' !==$ matches [1 ] ? (int )$ matches [1 ] :null ;
174197$ end ='' !==$ matches [2 ] ? (int )$ matches [2 ] :null ;
175198$ step =isset ($ matches [3 ]) &&'' !==$ matches [3 ] ? (int )$ matches [3 ] :1 ;
@@ -212,7 +235,7 @@ private function evaluateBracket(string $expr, mixed $value): array
212235
213236// filter expressions
214237if (preg_match ('/^\?(.*)$/ ' ,$ expr ,$ matches )) {
215- $ filterExpr =$ matches [1 ];
238+ $ filterExpr =trim ( $ matches [1 ]) ;
216239
217240if (preg_match ('/^(\w+)\s*\([^()]*\)\s*([<>=!]+.*)?$/ ' ,$ filterExpr )) {
218241$ filterExpr ="( $ filterExpr) " ;
@@ -260,37 +283,39 @@ private function evaluateFilter(string $expr, mixed $value): array
260283
261284private function evaluateFilterExpression (string $ expr ,array $ context ):bool
262285 {
263- $ expr =trim ($ expr );
286+ $ expr =JsonPathUtils:: normalizeWhitespace ($ expr );
264287
265- if (str_contains ($ expr ,'&& ' )) {
266- $ parts =array_map ( ' trim ' , explode ( ' && ' ,$ expr) );
288+ if ($ this -> containsOperator ($ expr ,'&& ' )) {
289+ $ parts =preg_split ( ' /\s*&&\s*/ ' ,$ expr );
267290foreach ($ partsas $ part ) {
268- if (!$ this ->evaluateFilterExpression ($ part ,$ context )) {
291+ if (!$ this ->evaluateFilterExpression (trim ( $ part) ,$ context )) {
269292return false ;
270293 }
271294 }
272295
273296return true ;
274297 }
275298
276- if (str_contains ($ expr ,'|| ' )) {
277- $ parts =array_map ( ' trim ' , explode ( ' || ' ,$ expr) );
299+ if ($ this -> containsOperator ($ expr ,'|| ' )) {
300+ $ parts =preg_split ( ' /\s*\|\|\s*/ ' ,$ expr );
278301$ result =false ;
279302foreach ($ partsas $ part ) {
280- $ result =$ result ||$ this ->evaluateFilterExpression ($ part ,$ context );
303+ $ result =$ result ||$ this ->evaluateFilterExpression (trim ( $ part) ,$ context );
281304 }
282305
283306return $ result ;
284307 }
285308
286309$ operators = ['!= ' ,'== ' ,'>= ' ,'<= ' ,'> ' ,'< ' ];
287310foreach ($ operatorsas $ op ) {
288- if (str_contains ($ expr ,$ op )) {
289- [$ left ,$ right ] =array_map ('trim ' ,explode ($ op ,$ expr ,2 ));
290- $ leftValue =$ this ->evaluateScalar ($ left ,$ context );
291- $ rightValue =$ this ->evaluateScalar ($ right ,$ context );
311+ if ($ this ->containsOperator ($ expr ,$ op )) {
312+ $ parts =$ this ->splitByOperator ($ expr ,$ op );
313+ if (2 ===\count ($ parts )) {
314+ $ leftValue =$ this ->evaluateScalar ($ parts [0 ],$ context );
315+ $ rightValue =$ this ->evaluateScalar ($ parts [1 ],$ context );
292316
293- return $ this ->compare ($ leftValue ,$ rightValue ,$ op );
317+ return $ this ->compare ($ leftValue ,$ rightValue ,$ op );
318+ }
294319 }
295320 }
296321
@@ -301,7 +326,7 @@ private function evaluateFilterExpression(string $expr, array $context): bool
301326 }
302327
303328// function calls
304- if (preg_match ('/^(\w+)\( (.*)\)$/ ' ,$ expr ,$ matches )) {
329+ if (preg_match ('/^(\w+)\s*\(\s* (.*)\s* \)$/ ' ,$ expr ,$ matches )) {
305330$ functionName =$ matches [1 ];
306331if (!isset (self ::RFC9535_FUNCTIONS [$ functionName ])) {
307332throw new JsonCrawlerException ($ expr ,\sprintf ('invalid function "%s" ' ,$ functionName ));
@@ -317,6 +342,8 @@ private function evaluateFilterExpression(string $expr, array $context): bool
317342
318343private function evaluateScalar (string $ expr ,array $ context ):mixed
319344 {
345+ $ expr = JsonPathUtils::normalizeWhitespace ($ expr );
346+
320347if (is_numeric ($ expr )) {
321348return str_contains ($ expr ,'. ' ) ? (float )$ expr : (int )$ expr ;
322349 }
@@ -346,7 +373,7 @@ private function evaluateScalar(string $expr, array $context): mixed
346373 }
347374
348375// function calls
349- if (preg_match ('/^(\w+)\((.*)\)$/ ' ,$ expr ,$ matches )) {
376+ if (preg_match ('/^(\w+)\s*\ ((.*)\)$/ ' ,$ expr ,$ matches )) {
350377$ functionName =$ matches [1 ];
351378if (!isset (self ::RFC9535_FUNCTIONS [$ functionName ])) {
352379throw new JsonCrawlerException ($ expr ,\sprintf ('invalid function "%s" ' ,$ functionName ));
@@ -360,12 +387,15 @@ private function evaluateScalar(string $expr, array $context): mixed
360387
361388private function evaluateFunction (string $ name ,string $ args ,array $ context ):mixed
362389 {
363- $ args =array_map (
364- fn ($ arg ) =>$ this ->evaluateScalar (trim ($ arg ),$ context ),
365- explode (', ' ,$ args )
366- );
390+ $ argList = [];
391+ if (trim ($ args )) {
392+ $ argList =array_map (
393+ fn ($ arg ) =>$ this ->evaluateScalar (trim ($ arg ),$ context ),
394+ preg_split ('/\s*,\s*/ ' ,trim ($ args ))
395+ );
396+ }
367397
368- $ value =$ args [0 ] ??null ;
398+ $ value =$ argList [0 ] ??null ;
369399
370400return match ($ name ) {
371401'length ' =>match (true ) {
@@ -375,11 +405,11 @@ private function evaluateFunction(string $name, string $args, array $context): m
375405 },
376406'count ' =>\is_array ($ value ) ?\count ($ value ) :0 ,
377407'match ' =>match (true ) {
378- \is_string ($ value ) &&\is_string ($ args [1 ] ??null ) => (bool ) @preg_match (\sprintf ('/^%s$/ ' ,$ args [1 ]),$ value ),
408+ \is_string ($ value ) &&\is_string ($ argList [1 ] ??null ) => (bool ) @preg_match (\sprintf ('/^%s$/ ' ,$ argList [1 ]),$ value ),
379409default =>false ,
380410 },
381411'search ' =>match (true ) {
382- \is_string ($ value ) &&\is_string ($ args [1 ] ??null ) => (bool ) @preg_match ("/ $ args [1 ]/ " ,$ value ),
412+ \is_string ($ value ) &&\is_string ($ argList [1 ] ??null ) => (bool ) @preg_match ("/ { $ argList [1 ]} / " ,$ value ),
383413default =>false ,
384414 },
385415'value ' =>$ value ,