Skip to content

Commit f36d2b8

Browse files
committed
Add ability to test against regex in JSONPath expressions
Related discussion: uBlockOrigin/uAssets#30157 (comment)
1 parent 2b8ef3e commit f36d2b8

File tree

1 file changed

+25
-10
lines changed

1 file changed

+25
-10
lines changed

src/js/jsonpath.js

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
* - ^=: stringified value starts with
5050
* - $=: stringified value ends with
5151
* - *=: stringified value includes
52+
* - =/.../: regular expression matches
5253
*
5354
* - Examples (from "JSONPath examples" at reference link)
5455
* - .store.book[*].author
@@ -376,7 +377,7 @@ export class JSONPath {
376377
continue;
377378
}
378379
if ( c0 === 0x27 /* ' */ ) {
379-
const r = this.#consumeQuotedIdentifier(query, i+1);
380+
const r = this.#untilChar(query, 0x27 /* ' */, i+1)
380381
if ( r === undefined ) { return; }
381382
keys.push(r.s);
382383
i = r.i;
@@ -397,22 +398,27 @@ export class JSONPath {
397398
}
398399
return { s: keys.length === 1 ? keys[0] : keys, i };
399400
}
400-
#consumeQuotedIdentifier(query, i) {
401+
#consumeUnquotedIdentifier(query, i) {
402+
const match = this.#reUnquotedIdentifier.exec(query.slice(i));
403+
if ( match === null ) { return; }
404+
return match[0];
405+
}
406+
#untilChar(query, targetCharCode, i) {
401407
const len = query.length;
402408
const parts = [];
403409
let beg = i, end = i;
404410
for (;;) {
405411
if ( end === len ) { return; }
406412
const c = query.charCodeAt(end);
407-
if ( c === 0x27 /* ' */ ) {
413+
if ( c === targetCharCode ) {
408414
parts.push(query.slice(beg, end));
409415
end += 1;
410416
break;
411417
}
412418
if ( c === 0x5C /* \ */ && (end+1) < len ) {
413419
parts.push(query.slice(beg, end));
414-
const d = query.chatCodeAt(end+1);
415-
if ( d === 0x27 || d === 0x5C ) {
420+
const d = query.charCodeAt(end+1);
421+
if ( d === targetCharCode || d === 0x5C ) {
416422
end += 1;
417423
beg = end;
418424
}
@@ -421,12 +427,20 @@ export class JSONPath {
421427
}
422428
return { s: parts.join(''), i: end };
423429
}
424-
#consumeUnquotedIdentifier(query, i) {
425-
const match = this.#reUnquotedIdentifier.exec(query.slice(i));
426-
if ( match === null ) { return; }
427-
return match[0];
428-
}
429430
#compileExpr(query, step, i) {
431+
if ( query.startsWith('=/', i) ) {
432+
const r = this.#untilChar(query, 0x2F /* / */, i+2);
433+
if ( r === undefined ) { return i; }
434+
const match = /^[i]/.exec(query.slice(r.i));
435+
try {
436+
step.rval = new RegExp(r.s, match && match[0] || undefined);
437+
} catch {
438+
return i;
439+
}
440+
step.op = 're';
441+
if ( match ) { r.i += match[0].length; }
442+
return r.i;
443+
}
430444
const match = this.#reExpr.exec(query.slice(i));
431445
if ( match === null ) { return i; }
432446
try {
@@ -466,6 +480,7 @@ export class JSONPath {
466480
case '^=': outcome = `${v}`.startsWith(step.rval) === target; break;
467481
case '$=': outcome = `${v}`.endsWith(step.rval) === target; break;
468482
case '*=': outcome = `${v}`.includes(step.rval) === target; break;
483+
case 're': outcome = step.rval.test(`${v}`); break;
469484
default: outcome = hasOwn === target; break;
470485
}
471486
if ( outcome ) { return k; }

0 commit comments

Comments
 (0)