@@ -122,12 +122,34 @@ private function evaluateName(string $name, mixed $value): array
122
122
return \array_key_exists ($ name , $ value ) ? [$ value [$ name ]] : [];
123
123
}
124
124
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
+
125
146
private function evaluateBracket (string $ expr , mixed $ value ): array
126
147
{
127
148
if (!\is_array ($ value )) {
128
149
return [];
129
150
}
130
151
152
+ $ expr = JsonPathUtils::normalizeWhitespace ($ expr );
131
153
if ('* ' === $ expr ) {
132
154
return array_values ($ value );
133
155
}
@@ -150,8 +172,8 @@ private function evaluateBracket(string $expr, mixed $value): array
150
172
}
151
173
152
174
$ 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 );
155
177
if ($ index < 0 ) {
156
178
$ index = \count ($ value ) + $ index ;
157
179
}
@@ -163,13 +185,14 @@ private function evaluateBracket(string $expr, mixed $value): array
163
185
return $ result ;
164
186
}
165
187
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 )) {
168
189
if (!array_is_list ($ value )) {
169
190
return [];
170
191
}
171
192
172
193
$ length = \count ($ value );
194
+ $ matches = array_map ('trim ' , $ matches );
195
+
173
196
$ start = '' !== $ matches [1 ] ? (int ) $ matches [1 ] : null ;
174
197
$ end = '' !== $ matches [2 ] ? (int ) $ matches [2 ] : null ;
175
198
$ step = isset ($ matches [3 ]) && '' !== $ matches [3 ] ? (int ) $ matches [3 ] : 1 ;
@@ -211,8 +234,8 @@ private function evaluateBracket(string $expr, mixed $value): array
211
234
}
212
235
213
236
// filter expressions
214
- if (preg_match ('/^\?(.*)$/ ' , $ expr , $ matches )) {
215
- $ filterExpr = $ matches [1 ];
237
+ if (preg_match ('/^\?\s* (.*)$/ ' , $ expr , $ matches )) {
238
+ $ filterExpr = trim ( $ matches [1 ]) ;
216
239
217
240
if (preg_match ('/^(\w+)\s*\([^()]*\)\s*([<>=!]+.*)?$/ ' , $ filterExpr )) {
218
241
$ filterExpr = "( $ filterExpr) " ;
@@ -260,37 +283,39 @@ private function evaluateFilter(string $expr, mixed $value): array
260
283
261
284
private function evaluateFilterExpression (string $ expr , array $ context ): bool
262
285
{
263
- $ expr = trim ($ expr );
286
+ $ expr = JsonPathUtils:: normalizeWhitespace ($ expr );
264
287
265
- if (str_contains ($ expr , '&& ' )) {
266
- $ parts = array_map ( ' trim ' , explode ( ' && ' , $ expr) );
288
+ if ($ this -> containsOperator ($ expr , '&& ' )) {
289
+ $ parts = preg_split ( ' /\s*&&\s*/ ' , $ expr );
267
290
foreach ($ parts as $ part ) {
268
- if (!$ this ->evaluateFilterExpression ($ part , $ context )) {
291
+ if (!$ this ->evaluateFilterExpression (trim ( $ part) , $ context )) {
269
292
return false ;
270
293
}
271
294
}
272
295
273
296
return true ;
274
297
}
275
298
276
- if (str_contains ($ expr , '|| ' )) {
277
- $ parts = array_map ( ' trim ' , explode ( ' || ' , $ expr) );
299
+ if ($ this -> containsOperator ($ expr , '|| ' )) {
300
+ $ parts = preg_split ( ' /\s*\|\|\s*/ ' , $ expr );
278
301
$ result = false ;
279
302
foreach ($ parts as $ part ) {
280
- $ result = $ result || $ this ->evaluateFilterExpression ($ part , $ context );
303
+ $ result = $ result || $ this ->evaluateFilterExpression (trim ( $ part) , $ context );
281
304
}
282
305
283
306
return $ result ;
284
307
}
285
308
286
309
$ operators = ['!= ' , '== ' , '>= ' , '<= ' , '> ' , '< ' ];
287
310
foreach ($ operators as $ 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 );
292
316
293
- return $ this ->compare ($ leftValue , $ rightValue , $ op );
317
+ return $ this ->compare ($ leftValue , $ rightValue , $ op );
318
+ }
294
319
}
295
320
}
296
321
@@ -301,7 +326,7 @@ private function evaluateFilterExpression(string $expr, array $context): bool
301
326
}
302
327
303
328
// function calls
304
- if (preg_match ('/^(\w+)\( (.*)\)$/ ' , $ expr , $ matches )) {
329
+ if (preg_match ('/^(\w+)\s*\(\s* (.*)\s* \)$/ ' , $ expr , $ matches )) {
305
330
$ functionName = $ matches [1 ];
306
331
if (!isset (self ::RFC9535_FUNCTIONS [$ functionName ])) {
307
332
throw new JsonCrawlerException ($ expr , \sprintf ('invalid function "%s" ' , $ functionName ));
@@ -317,6 +342,8 @@ private function evaluateFilterExpression(string $expr, array $context): bool
317
342
318
343
private function evaluateScalar (string $ expr , array $ context ): mixed
319
344
{
345
+ $ expr = JsonPathUtils::normalizeWhitespace ($ expr );
346
+
320
347
if (is_numeric ($ expr )) {
321
348
return str_contains ($ expr , '. ' ) ? (float ) $ expr : (int ) $ expr ;
322
349
}
@@ -346,7 +373,7 @@ private function evaluateScalar(string $expr, array $context): mixed
346
373
}
347
374
348
375
// function calls
349
- if (preg_match ('/^(\w+)\( (.*)\)$/ ' , $ expr , $ matches )) {
376
+ if (preg_match ('/^(\w+)\s*\(\s* (.*)\s* \)$/ ' , $ expr , $ matches )) {
350
377
$ functionName = $ matches [1 ];
351
378
if (!isset (self ::RFC9535_FUNCTIONS [$ functionName ])) {
352
379
throw new JsonCrawlerException ($ expr , \sprintf ('invalid function "%s" ' , $ functionName ));
@@ -360,12 +387,15 @@ private function evaluateScalar(string $expr, array $context): mixed
360
387
361
388
private function evaluateFunction (string $ name , string $ args , array $ context ): mixed
362
389
{
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
+ }
367
397
368
- $ value = $ args [0 ] ?? null ;
398
+ $ value = $ argList [0 ] ?? null ;
369
399
370
400
return match ($ name ) {
371
401
'length ' => match (true ) {
@@ -375,11 +405,11 @@ private function evaluateFunction(string $name, string $args, array $context): m
375
405
},
376
406
'count ' => \is_array ($ value ) ? \count ($ value ) : 0 ,
377
407
'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 ),
379
409
default => false ,
380
410
},
381
411
'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 ),
383
413
default => false ,
384
414
},
385
415
'value ' => $ value ,
0 commit comments