1
1
mirror of https://github.com/gorhill/uBlock.git synced 2025-10-05 21:32:39 +02:00

Add ability to test against regex in JSONPath expressions

Related discussion:
https://github.com/uBlockOrigin/uAssets/issues/30157#issuecomment-3356048999
This commit is contained in:
Raymond Hill
2025-10-01 11:35:15 -04:00
parent 2b8ef3ee8e
commit f36d2b8496

View File

@@ -49,6 +49,7 @@
* - ^=: stringified value starts with
* - $=: stringified value ends with
* - *=: stringified value includes
* - =/.../: regular expression matches
*
* - Examples (from "JSONPath examples" at reference link)
* - .store.book[*].author
@@ -376,7 +377,7 @@ export class JSONPath {
continue;
}
if ( c0 === 0x27 /* ' */ ) {
const r = this.#consumeQuotedIdentifier(query, i+1);
const r = this.#untilChar(query, 0x27 /* ' */, i+1)
if ( r === undefined ) { return; }
keys.push(r.s);
i = r.i;
@@ -397,22 +398,27 @@ export class JSONPath {
}
return { s: keys.length === 1 ? keys[0] : keys, i };
}
#consumeQuotedIdentifier(query, i) {
#consumeUnquotedIdentifier(query, i) {
const match = this.#reUnquotedIdentifier.exec(query.slice(i));
if ( match === null ) { return; }
return match[0];
}
#untilChar(query, targetCharCode, i) {
const len = query.length;
const parts = [];
let beg = i, end = i;
for (;;) {
if ( end === len ) { return; }
const c = query.charCodeAt(end);
if ( c === 0x27 /* ' */ ) {
if ( c === targetCharCode ) {
parts.push(query.slice(beg, end));
end += 1;
break;
}
if ( c === 0x5C /* \ */ && (end+1) < len ) {
parts.push(query.slice(beg, end));
const d = query.chatCodeAt(end+1);
if ( d === 0x27 || d === 0x5C ) {
const d = query.charCodeAt(end+1);
if ( d === targetCharCode || d === 0x5C ) {
end += 1;
beg = end;
}
@@ -421,12 +427,20 @@ export class JSONPath {
}
return { s: parts.join(''), i: end };
}
#consumeUnquotedIdentifier(query, i) {
const match = this.#reUnquotedIdentifier.exec(query.slice(i));
if ( match === null ) { return; }
return match[0];
}
#compileExpr(query, step, i) {
if ( query.startsWith('=/', i) ) {
const r = this.#untilChar(query, 0x2F /* / */, i+2);
if ( r === undefined ) { return i; }
const match = /^[i]/.exec(query.slice(r.i));
try {
step.rval = new RegExp(r.s, match && match[0] || undefined);
} catch {
return i;
}
step.op = 're';
if ( match ) { r.i += match[0].length; }
return r.i;
}
const match = this.#reExpr.exec(query.slice(i));
if ( match === null ) { return i; }
try {
@@ -466,6 +480,7 @@ export class JSONPath {
case '^=': outcome = `${v}`.startsWith(step.rval) === target; break;
case '$=': outcome = `${v}`.endsWith(step.rval) === target; break;
case '*=': outcome = `${v}`.includes(step.rval) === target; break;
case 're': outcome = step.rval.test(`${v}`); break;
default: outcome = hasOwn === target; break;
}
if ( outcome ) { return k; }