diff --git a/package.json b/package.json index 783303f..dfc756d 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "type": "module", "scripts": { - "build": "deno build.ts --tagName 1.71.1b0 > ubo.js", + "build": "deno build.ts --tagName 1.71.1b9 > ubo.js", "test": "node --test" }, "author": { diff --git a/ubo.js b/ubo.js index be9a497..4be8f44 100644 --- a/ubo.js +++ b/ubo.js @@ -246,18 +246,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -564,18 +552,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -774,18 +750,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -872,6 +836,18 @@ function runAt(fn, when) { const args = [ 'readystatechange', onStateChange, { capture: true } ]; safe.addEventListener.apply(document, args); } +function onIdleFn(fn, options) { + if ( self.requestIdleCallback ) { + return self.requestIdleCallback(fn, options); + } + return self.requestAnimationFrame(fn); +} +function offIdleFn(id) { + if ( self.requestIdleCallback ) { + return self.cancelIdleCallback(id); + } + return self.cancelAnimationFrame(id); +} function removeAttr( rawToken = '', rawSelector = '', @@ -892,14 +868,14 @@ function removeAttr( let timerId; const rmattrAsync = ( ) => { if ( timerId !== undefined ) { return; } - timerId = safe.onIdle(( ) => { + timerId = onIdleFn(( ) => { timerId = undefined; rmattr(); }, { timeout: 17 }); }; const rmattr = ( ) => { if ( timerId !== undefined ) { - safe.offIdle(timerId); + offIdleFn(timerId); timerId = undefined; } try { @@ -1073,18 +1049,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -1472,18 +1436,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -1570,6 +1522,12 @@ function runAt(fn, when) { const args = [ 'readystatechange', onStateChange, { capture: true } ]; safe.addEventListener.apply(document, args); } +function onIdleFn(fn, options) { + if ( self.requestIdleCallback ) { + return self.requestIdleCallback(fn, options); + } + return self.requestAnimationFrame(fn); +} function hrefSanitizer( selector = '', source = '' @@ -1652,7 +1610,7 @@ function hrefSanitizer( if ( shouldSanitize ) { break; } } if ( shouldSanitize === false ) { return; } - timer = safe.onIdle(( ) => { + timer = onIdleFn(( ) => { timer = undefined; sanitize(); }); @@ -1797,18 +1755,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -1972,6 +1918,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -1984,14 +1934,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -2002,11 +1955,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -2020,6 +1974,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -2046,9 +2001,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -2084,24 +2042,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -2138,12 +2126,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -2154,61 +2156,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -2236,7 +2249,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -2250,7 +2263,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -2259,12 +2272,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -2278,17 +2291,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -2320,22 +2340,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -2343,34 +2368,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -2386,11 +2407,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -2398,10 +2431,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -2572,18 +2604,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -2747,6 +2767,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -2759,14 +2783,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -2777,11 +2804,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -2795,6 +2823,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -2821,9 +2850,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -2859,24 +2891,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -2913,12 +2975,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -2929,61 +3005,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; + } + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -3011,7 +3098,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -3025,7 +3112,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -3034,12 +3121,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -3053,17 +3140,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -3095,22 +3189,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -3118,34 +3217,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -3161,11 +3256,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -3173,10 +3280,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -3347,18 +3453,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -3522,6 +3616,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -3534,14 +3632,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -3552,11 +3653,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -3570,6 +3672,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -3596,9 +3699,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -3634,24 +3740,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -3688,12 +3824,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -3704,61 +3854,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; + } + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -3786,7 +3947,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -3800,7 +3961,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -3809,12 +3970,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -3828,17 +3989,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -3870,22 +4038,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -3893,34 +4066,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -3936,11 +4105,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -3948,10 +4129,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -4122,18 +4302,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -4297,6 +4465,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -4309,14 +4481,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -4327,11 +4502,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -4345,6 +4521,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -4371,9 +4548,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -4409,24 +4589,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -4463,12 +4673,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -4479,61 +4703,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -4561,7 +4796,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -4575,7 +4810,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -4584,12 +4819,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -4603,17 +4838,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -4645,22 +4887,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -4668,34 +4915,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -4711,11 +4954,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -4723,10 +4978,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -4897,18 +5151,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -5072,6 +5314,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -5084,14 +5330,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -5102,11 +5351,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -5120,6 +5370,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -5146,9 +5397,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -5184,24 +5438,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -5238,12 +5522,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -5254,61 +5552,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; + } + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -5336,7 +5645,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -5350,7 +5659,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -5359,12 +5668,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -5378,17 +5687,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -5420,22 +5736,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -5443,34 +5764,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -5486,11 +5803,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -5498,10 +5827,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -5699,18 +6027,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -5874,6 +6190,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -5886,14 +6206,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -5904,11 +6227,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -5922,6 +6246,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -5948,9 +6273,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -5986,24 +6314,6871 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; + mv = this.#UNDEFINED; + continue; + } + // Bracket accessor syntax + if ( query.startsWith('[?', i) ) { + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; + const r = this.#compile(query, j); + if ( r === undefined ) { return; } + if ( query.startsWith(']', r.i) === false ) { return; } + if ( not ) { r.steps.at(-1).not = true; } + steps.push({ mv: mv || this.#CHILDREN, steps: r.steps }); + i = r.i + 1; + mv = this.#UNDEFINED; + continue; + } + if ( query.startsWith('[*]', i) ) { + mv ||= this.#CHILDREN; + steps.push({ mv, k: '*' }); + i += 3; + mv = this.#UNDEFINED; + continue; + } + const r = this.#consumeIdentifier(query, i+1); + if ( r === undefined ) { return; } + mv ||= this.#CHILDREN; + steps.push({ mv, k: r.s }); + i = r.i + 1; + mv = this.#UNDEFINED; + } + if ( steps.length === 0 ) { return; } + if ( mv !== this.#UNDEFINED ) { return; } + return { steps, i }; + } + #evaluate(steps, pathin) { + let resultset = []; + if ( Array.isArray(steps) === false ) { return resultset; } + for ( const step of steps ) { + switch ( step.mv ) { + case this.#ROOT: + resultset = [ [ '$' ] ]; + break; + case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } + resultset = [ pathin ]; + break; + case this.#CHILDREN: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } + resultset = this.#getMatches(resultset, step); + break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } + default: + break; + } + } + return resultset; + } + #getMatches(listin, step) { + const listout = []; + for ( const pathin of listin ) { + const { value: owner } = this.#resolvePath(pathin); + if ( owner === undefined ) { continue; } + if ( step.steps ) { + this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } + } + } + return listout; + } + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; + } + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; + } + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); + } + return out; + } + return [ k ]; + } + #getMatchesFromExpr(pathin, step, owner, out) { + const recursive = step.mv === this.#DESCENDANTS; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; + const r = this.#evaluate(step.steps, q); + if ( Boolean(r?.length) === false ) { continue; } + out.push(q); + if ( v2 === false ) { break; } + } + } + #getDescendants(v, recursive) { + const iterator = { + next() { + const n = this.stack.length; + if ( n === 0 ) { + this.value = undefined; + this.done = true; + return this; + } + const details = this.stack[n-1]; + const entry = details.keys.next(); + if ( entry.done ) { + this.stack.pop(); + this.path.pop(); + return this.next(); + } + this.path[n-1] = entry.value; + this.value = { + obj: details.obj, + key: entry.value, + path: this.path.slice(), + }; + const v = this.value.obj[this.value.key]; + if ( recursive ) { + if ( Array.isArray(v) ) { + this.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); + } + } + return this; + }, + path: [], + value: undefined, + done: false, + stack: [], + [Symbol.iterator]() { return this; }, + }; + if ( Array.isArray(v) ) { + iterator.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); + } + return iterator; + } + #consumeIdentifier(query, i) { + const keys = []; + for (;;) { + const c0 = query.charCodeAt(i); + if ( c0 === 0x5D /* ] */ ) { break; } + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { + i += 1; + continue; + } + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + continue; + } + if ( c0 === 0x2D /* - */ || c0 >= 0x30 && c0 <= 0x39 ) { + const match = this.#reIndice.exec(query.slice(i)); + if ( match === null ) { return; } + const indice = parseInt(query.slice(i), 10); + keys.push(indice); + i += match[0].length; + continue; + } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + } + return { s: keys.length === 1 ? keys[0] : keys, i }; + } + #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } + const match = this.#reUnquotedIdentifier.exec(query.slice(i)); + if ( match === null ) { return; } + return { s: match[0], i: i + match[0].length }; + } + #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 === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + break; + } + if ( c === 0x5C /* \ */ && (end+1) < len ) { + const d = query.charCodeAt(end+1); + if ( d === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + beg = end; + } + } + end += 1; + } + return { s: parts.join(''), i: end }; + } + #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 JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } + 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; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } + } + return i + match[0].length - 1; + } + #resolvePath(path) { + if ( path.length === 0 ) { return { value: this.#root }; } + const key = path.at(-1); + let obj = this.#root + for ( let i = 0, n = path.length-1; i < n; i++ ) { + obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } + } + return { obj, key, value: obj[key] }; + } + #evaluateExpr(step, owner, k) { + if ( owner === undefined || owner === null ) { return; } + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); + if ( step.op !== undefined && hasOwn === false ) { return; } + const target = step.not !== true; + const v = owner[k]; + switch ( step.op ) { + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; + } + #modifyVal(obj, key) { + let { modify, rval } = this.#compiled; + if ( typeof rval === 'string' ) { + rval = rval.replace('${now}', `${Date.now()}`); + } + switch ( modify ) { + case undefined: + obj[key] = rval; + break; + case '+': { + if ( rval instanceof Object === false ) { return; } + const lval = obj[key]; + if ( lval instanceof Object === false ) { return; } + if ( Array.isArray(lval) ) { return; } + for ( const [ k, v ] of JSONPath.entries(rval) ) { + lval[k] = v; + } + break; + } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } + case 'repl': { + const lval = obj[key]; + if ( typeof lval !== 'string' ) { return; } + if ( this.#compiled.re === undefined ) { + this.#compiled.re = null; + try { + this.#compiled.re = rval.regex !== undefined + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } + } + if ( this.#compiled.re === null ) { return; } + obj[key] = lval.replace(this.#compiled.re, rval.replacement); + break; + } + default: + break; + } + } +} +function editInboundObjectFn( + trusted = false, + propChain = '', + argPosRaw = '', + jsonq = '', +) { + if ( propChain === '' ) { return; } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix( + `${trusted ? 'trusted-' : ''}edit-inbound-object`, + propChain, + jsonq + ); + const jsonp = JSONPath.create(jsonq); + if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { + return safe.uboLog(logPrefix, 'Bad JSONPath query'); + } + const argPos = parseInt(argPosRaw, 10); + if ( isNaN(argPos) ) { return; } + const getArgPos = args => { + if ( Array.isArray(args) === false ) { return; } + if ( argPos >= 0 ) { + if ( args.length <= argPos ) { return; } + return argPos; + } + if ( args.length < -argPos ) { return; } + return args.length + argPos; + }; + const editObj = obj => { + let clone; + try { + clone = safe.JSON_parse(safe.JSON_stringify(obj)); + } catch { + } + if ( typeof clone !== 'object' || clone === null ) { return; } + const objAfter = jsonp.apply(clone); + if ( objAfter === undefined ) { return; } + safe.uboLog(logPrefix, 'Edited'); + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); + } + return objAfter; + }; + proxyApplyFn(propChain, function(context) { + const i = getArgPos(context.callArgs); + if ( i !== undefined ) { + const obj = editObj(context.callArgs[i]); + if ( obj ) { + context.callArgs[i] = obj; + } + } + return context.reflect(); + }); +} +function trustedEditInboundObject(propChain = '', argPos = '', jsonq = '') { + editInboundObjectFn(true, propChain, argPos, jsonq); +}; +trustedEditInboundObject(...args); +}, +}; + + +scriptlets['edit-this-object.js'] = { +aliases: [], + +requiresTrust: false, +func: function (scriptletGlobals = {}, ...args) { +function safeSelf() { + if ( scriptletGlobals.safeSelf ) { + return scriptletGlobals.safeSelf; + } + const self = globalThis; + const safe = { + 'Array_from': Array.from, + 'Error': self.Error, + 'Function_toStringFn': self.Function.prototype.toString, + 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Math_floor': Math.floor, + 'Math_max': Math.max, + 'Math_min': Math.min, + 'Math_random': Math.random, + 'Object': Object, + 'Object_defineProperty': Object.defineProperty.bind(Object), + 'Object_defineProperties': Object.defineProperties.bind(Object), + 'Object_fromEntries': Object.fromEntries.bind(Object), + 'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object), + 'Object_hasOwn': Object.hasOwn.bind(Object), + 'Object_toString': Object.prototype.toString, + 'RegExp': self.RegExp, + 'RegExp_test': self.RegExp.prototype.test, + 'RegExp_exec': self.RegExp.prototype.exec, + 'Request_clone': self.Request.prototype.clone, + 'String': self.String, + 'String_fromCharCode': String.fromCharCode, + 'String_split': String.prototype.split, + 'XMLHttpRequest': self.XMLHttpRequest, + 'addEventListener': self.EventTarget.prototype.addEventListener, + 'removeEventListener': self.EventTarget.prototype.removeEventListener, + 'fetch': self.fetch, + 'JSON': self.JSON, + 'JSON_parseFn': self.JSON.parse, + 'JSON_stringifyFn': self.JSON.stringify, + 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), + 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + 'log': console.log.bind(console), + // Properties + logLevel: 0, + // Methods + makeLogPrefix(...args) { + return this.sendToLogger && `[${args.join(' \u205D ')}]` || ''; + }, + uboLog(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('info', ...args); + + }, + uboErr(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('error', ...args); + }, + escapeRegexChars(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + }, + initPattern(pattern, options = {}) { + if ( pattern === '' ) { + return { matchAll: true, expect: true }; + } + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + if ( expect === false ) { + pattern = pattern.slice(1); + } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match !== null ) { + return { + re: new this.RegExp( + match[1], + match[2] || options.flags + ), + expect, + }; + } + if ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; + } + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test.call(details.re, haystack) === details.expect; + } + return haystack.includes(details.pattern) === details.expect; + }, + patternToRegex(pattern, flags = undefined, verbatim = false) { + if ( pattern === '' ) { return /^/; } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match === null ) { + const reStr = this.escapeRegexChars(pattern); + return new RegExp(verbatim ? `^${reStr}$` : reStr, flags); + } + try { + return new RegExp(match[1], match[2] || undefined); + } + catch { + } + return /^/; + }, + getExtraArgs(args, offset = 0) { + const entries = args.slice(offset).reduce((out, v, i, a) => { + if ( (i & 1) === 0 ) { + const rawValue = a[i+1]; + const value = /^\d+$/.test(rawValue) + ? parseInt(rawValue, 10) + : rawValue; + out.push([ a[i], value ]); + } + return out; + }, []); + return this.Object_fromEntries(entries); + }, + }; + scriptletGlobals.safeSelf = safe; + if ( scriptletGlobals.bcSecret === undefined ) { return safe; } + // This is executed only when the logger is opened + safe.logLevel = scriptletGlobals.logLevel || 1; + let lastLogType = ''; + let lastLogText = ''; + let lastLogTime = 0; + safe.toLogText = (type, ...args) => { + if ( args.length === 0 ) { return; } + const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; + if ( text === lastLogText && type === lastLogType ) { + if ( (Date.now() - lastLogTime) < 5000 ) { return; } + } + lastLogType = type; + lastLogText = text; + lastLogTime = Date.now(); + return text; + }; + try { + const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); + let bcBuffer = []; + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + if ( bcBuffer === undefined ) { + return bc.postMessage({ what: 'messageToLogger', type, text }); + } + bcBuffer.push({ type, text }); + }; + bc.onmessage = ev => { + const msg = ev.data; + switch ( msg ) { + case 'iamready!': + if ( bcBuffer === undefined ) { break; } + bcBuffer.forEach(({ type, text }) => + bc.postMessage({ what: 'messageToLogger', type, text }) + ); + bcBuffer = undefined; + break; + case 'setScriptletLogLevelToOne': + safe.logLevel = 1; + break; + case 'setScriptletLogLevelToTwo': + safe.logLevel = 2; + break; + } + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; + } + return safe; +} +function proxyApplyFn( + target = '', + handler = '' +) { + let context = globalThis; + let prop = target; + for (;;) { + const pos = prop.indexOf('.'); + if ( pos === -1 ) { break; } + context = context[prop.slice(0, pos)]; + if ( context instanceof Object === false ) { return; } + prop = prop.slice(pos+1); + } + const fn = context[prop]; + if ( typeof fn !== 'function' ) { return; } + if ( proxyApplyFn.CtorContext === undefined ) { + proxyApplyFn.ctorContexts = []; + proxyApplyFn.CtorContext = class { + constructor(...args) { + this.init(...args); + } + init(callFn, callArgs) { + this.callFn = callFn; + this.callArgs = callArgs; + return this; + } + reflect() { + const r = Reflect.construct(this.callFn, this.callArgs); + this.callFn = this.callArgs = this.private = undefined; + proxyApplyFn.ctorContexts.push(this); + return r; + } + static factory(...args) { + return proxyApplyFn.ctorContexts.length !== 0 + ? proxyApplyFn.ctorContexts.pop().init(...args) + : new proxyApplyFn.CtorContext(...args); + } + }; + proxyApplyFn.applyContexts = []; + proxyApplyFn.ApplyContext = class { + constructor(...args) { + this.init(...args); + } + init(callFn, thisArg, callArgs) { + this.callFn = callFn; + this.thisArg = thisArg; + this.callArgs = callArgs; + return this; + } + reflect() { + const r = Reflect.apply(this.callFn, this.thisArg, this.callArgs); + this.callFn = this.thisArg = this.callArgs = this.private = undefined; + proxyApplyFn.applyContexts.push(this); + return r; + } + static factory(...args) { + return proxyApplyFn.applyContexts.length !== 0 + ? proxyApplyFn.applyContexts.pop().init(...args) + : new proxyApplyFn.ApplyContext(...args); + } + }; + proxyApplyFn.isCtor = new Map(); + proxyApplyFn.proxies = new WeakMap(); + proxyApplyFn.nativeToString = Function.prototype.toString; + const proxiedToString = new Proxy(Function.prototype.toString, { + apply(target, thisArg) { + let proxied = thisArg; + for(;;) { + const fn = proxyApplyFn.proxies.get(proxied); + if ( fn === undefined ) { break; } + proxied = fn; + } + return proxyApplyFn.nativeToString.call(proxied); + } + }); + proxyApplyFn.proxies.set(proxiedToString, proxyApplyFn.nativeToString); + Function.prototype.toString = proxiedToString; + } + if ( proxyApplyFn.isCtor.has(target) === false ) { + proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); + } + const proxyDetails = { + apply(target, thisArg, args) { + return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args)); + } + }; + if ( proxyApplyFn.isCtor.get(target) ) { + proxyDetails.construct = function(target, args) { + return handler(proxyApplyFn.CtorContext.factory(target, args)); + }; + } + const proxiedTarget = new Proxy(fn, proxyDetails); + proxyApplyFn.proxies.set(proxiedTarget, fn); + context[prop] = proxiedTarget; +} +class JSONPath { + static create(query) { + const jsonp = new JSONPath(); + jsonp.compile(query); + return jsonp; + } + static toJSON(obj, stringifier, ...args) { + return (stringifier || JSON.stringify)(obj, ...args) + .replace(/\//g, '\\/'); + } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; + get value() { + return this.#compiled && this.#compiled.rval; + } + set value(v) { + if ( this.#compiled === undefined ) { return; } + this.#compiled.rval = v; + } + get valid() { + return this.#compiled !== undefined; + } + compile(query) { + this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } + const r = this.#compile(query, 0); + if ( r === undefined ) { return; } + if ( r.i !== query.length ) { + let val; + if ( query.startsWith('=', r.i) ) { + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; + } else { + val = query.slice(r.i+1); + } + } else if ( query.startsWith('+=', r.i) ) { + r.modify = '+'; + val = query.slice(r.i+2); + } + try { r.rval = JSON.parse(val); } + catch { return; } + } + r.v2 = v2; + this.#compiled = r; + } + evaluate(root) { + if ( this.valid === false ) { return []; } + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + this.#root = null; + return paths; + } + apply(root) { + if ( this.valid === false ) { return; } + const { rval } = this.#compiled; + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + let i = paths.length + if ( i === 0 ) { this.#root = null; return; } + while ( i-- ) { + const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } + if ( rval !== undefined ) { + this.#modifyVal(obj, key); + } else if ( Array.isArray(obj) && typeof key === 'number' ) { + obj.splice(key, 1); + } else { + delete obj[key]; + } + } + const result = this.#root['$'] ?? null; + this.#root = null; + return result; + } + dump() { + return JSON.stringify(this.#compiled); + } + toJSON(obj, ...args) { + return JSONPath.toJSON(obj, null, ...args) + } + get [Symbol.toStringTag]() { + return 'JSONPath'; + } + #UNDEFINED = 0; + #ROOT = 1; + #CURRENT = 2; + #CHILDREN = 3; + #DESCENDANTS = 4; + #QUANTIFIER = 5; + #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; + #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; + #root; + #compiled; + #compile(query, i) { + if ( query.length === 0 ) { return; } + const steps = []; + let c = query.charCodeAt(i); + if ( c === 0x24 /* $ */ ) { + steps.push({ mv: this.#ROOT }); + i += 1; + } else if ( c === 0x40 /* @ */ ) { + steps.push({ mv: this.#CURRENT }); + i += 1; + } else { + steps.push({ mv: i === 0 ? this.#ROOT : this.#CURRENT }); + } + let mv = this.#UNDEFINED; + for (;;) { + if ( i === query.length ) { break; } + c = query.charCodeAt(i); + if ( c === 0x20 /* whitespace */ ) { + i += 1; + continue; + } + // Dot accessor syntax + if ( c === 0x2E /* . */ ) { + if ( mv !== this.#UNDEFINED ) { return; } + if ( query.startsWith('..', i) ) { + mv = this.#DESCENDANTS; + i += 2; + } else { + mv = this.#CHILDREN; + i += 1; + } + continue; + } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } + if ( c !== 0x5B /* [ */ ) { + if ( mv === this.#UNDEFINED ) { + const step = steps.at(-1); + if ( step === undefined ) { return; } + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } + break; + } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; + mv = this.#UNDEFINED; + continue; + } + // Bracket accessor syntax + if ( query.startsWith('[?', i) ) { + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; + const r = this.#compile(query, j); + if ( r === undefined ) { return; } + if ( query.startsWith(']', r.i) === false ) { return; } + if ( not ) { r.steps.at(-1).not = true; } + steps.push({ mv: mv || this.#CHILDREN, steps: r.steps }); + i = r.i + 1; + mv = this.#UNDEFINED; + continue; + } + if ( query.startsWith('[*]', i) ) { + mv ||= this.#CHILDREN; + steps.push({ mv, k: '*' }); + i += 3; + mv = this.#UNDEFINED; + continue; + } + const r = this.#consumeIdentifier(query, i+1); + if ( r === undefined ) { return; } + mv ||= this.#CHILDREN; + steps.push({ mv, k: r.s }); + i = r.i + 1; + mv = this.#UNDEFINED; + } + if ( steps.length === 0 ) { return; } + if ( mv !== this.#UNDEFINED ) { return; } + return { steps, i }; + } + #evaluate(steps, pathin) { + let resultset = []; + if ( Array.isArray(steps) === false ) { return resultset; } + for ( const step of steps ) { + switch ( step.mv ) { + case this.#ROOT: + resultset = [ [ '$' ] ]; + break; + case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } + resultset = [ pathin ]; + break; + case this.#CHILDREN: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } + resultset = this.#getMatches(resultset, step); + break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } + default: + break; + } + } + return resultset; + } + #getMatches(listin, step) { + const listout = []; + for ( const pathin of listin ) { + const { value: owner } = this.#resolvePath(pathin); + if ( owner === undefined ) { continue; } + if ( step.steps ) { + this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } + } + } + return listout; + } + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; + } + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; + } + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); + } + return out; + } + return [ k ]; + } + #getMatchesFromExpr(pathin, step, owner, out) { + const recursive = step.mv === this.#DESCENDANTS; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; + const r = this.#evaluate(step.steps, q); + if ( Boolean(r?.length) === false ) { continue; } + out.push(q); + if ( v2 === false ) { break; } + } + } + #getDescendants(v, recursive) { + const iterator = { + next() { + const n = this.stack.length; + if ( n === 0 ) { + this.value = undefined; + this.done = true; + return this; + } + const details = this.stack[n-1]; + const entry = details.keys.next(); + if ( entry.done ) { + this.stack.pop(); + this.path.pop(); + return this.next(); + } + this.path[n-1] = entry.value; + this.value = { + obj: details.obj, + key: entry.value, + path: this.path.slice(), + }; + const v = this.value.obj[this.value.key]; + if ( recursive ) { + if ( Array.isArray(v) ) { + this.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); + } + } + return this; + }, + path: [], + value: undefined, + done: false, + stack: [], + [Symbol.iterator]() { return this; }, + }; + if ( Array.isArray(v) ) { + iterator.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); + } + return iterator; + } + #consumeIdentifier(query, i) { + const keys = []; + for (;;) { + const c0 = query.charCodeAt(i); + if ( c0 === 0x5D /* ] */ ) { break; } + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { + i += 1; + continue; + } + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + continue; + } + if ( c0 === 0x2D /* - */ || c0 >= 0x30 && c0 <= 0x39 ) { + const match = this.#reIndice.exec(query.slice(i)); + if ( match === null ) { return; } + const indice = parseInt(query.slice(i), 10); + keys.push(indice); + i += match[0].length; + continue; + } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + } + return { s: keys.length === 1 ? keys[0] : keys, i }; + } + #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } + const match = this.#reUnquotedIdentifier.exec(query.slice(i)); + if ( match === null ) { return; } + return { s: match[0], i: i + match[0].length }; + } + #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 === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + break; + } + if ( c === 0x5C /* \ */ && (end+1) < len ) { + const d = query.charCodeAt(end+1); + if ( d === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + beg = end; + } + } + end += 1; + } + return { s: parts.join(''), i: end }; + } + #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 JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } + 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; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } + } + return i + match[0].length - 1; + } + #resolvePath(path) { + if ( path.length === 0 ) { return { value: this.#root }; } + const key = path.at(-1); + let obj = this.#root + for ( let i = 0, n = path.length-1; i < n; i++ ) { + obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } + } + return { obj, key, value: obj[key] }; + } + #evaluateExpr(step, owner, k) { + if ( owner === undefined || owner === null ) { return; } + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); + if ( step.op !== undefined && hasOwn === false ) { return; } + const target = step.not !== true; + const v = owner[k]; + switch ( step.op ) { + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; + } + #modifyVal(obj, key) { + let { modify, rval } = this.#compiled; + if ( typeof rval === 'string' ) { + rval = rval.replace('${now}', `${Date.now()}`); + } + switch ( modify ) { + case undefined: + obj[key] = rval; + break; + case '+': { + if ( rval instanceof Object === false ) { return; } + const lval = obj[key]; + if ( lval instanceof Object === false ) { return; } + if ( Array.isArray(lval) ) { return; } + for ( const [ k, v ] of JSONPath.entries(rval) ) { + lval[k] = v; + } + break; + } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } + case 'repl': { + const lval = obj[key]; + if ( typeof lval !== 'string' ) { return; } + if ( this.#compiled.re === undefined ) { + this.#compiled.re = null; + try { + this.#compiled.re = rval.regex !== undefined + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } + } + if ( this.#compiled.re === null ) { return; } + obj[key] = lval.replace(this.#compiled.re, rval.replacement); + break; + } + default: + break; + } + } +} +function editThisObjectFn( + trusted = false, + propChain = '', + jsonq = '', + order = '' +) { + if ( propChain === '' ) { return; } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix( + `${trusted ? 'trusted-' : ''}edit-this-object`, + propChain, + jsonq + ); + const jsonp = JSONPath.create(jsonq); + if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { + return safe.uboLog(logPrefix, 'Bad JSONPath query'); + } + const editObj = objBefore => { + const objAfter = jsonp.apply(objBefore); + if ( objAfter === undefined ) { return; } + safe.uboLog(logPrefix, 'Edited'); + if ( safe.logLevel <= 1 ) { return; } + safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); + }; + proxyApplyFn(propChain, function(context) { + const { thisArg } = context; + let r; + if ( order === 'after' ) { + r = context.reflect(); + } + editObj(thisArg); + if ( order !== 'after' ) { + r = context.reflect(); + } + return r; + }); +} +function editThisObject(...args) { + editThisObjectFn(false, ...args); +}; +editThisObject(...args); +}, +}; + + +scriptlets['trusted-edit-this-object.js'] = { +aliases: [], + +requiresTrust: true, +func: function (scriptletGlobals = {}, ...args) { +function safeSelf() { + if ( scriptletGlobals.safeSelf ) { + return scriptletGlobals.safeSelf; + } + const self = globalThis; + const safe = { + 'Array_from': Array.from, + 'Error': self.Error, + 'Function_toStringFn': self.Function.prototype.toString, + 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Math_floor': Math.floor, + 'Math_max': Math.max, + 'Math_min': Math.min, + 'Math_random': Math.random, + 'Object': Object, + 'Object_defineProperty': Object.defineProperty.bind(Object), + 'Object_defineProperties': Object.defineProperties.bind(Object), + 'Object_fromEntries': Object.fromEntries.bind(Object), + 'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object), + 'Object_hasOwn': Object.hasOwn.bind(Object), + 'Object_toString': Object.prototype.toString, + 'RegExp': self.RegExp, + 'RegExp_test': self.RegExp.prototype.test, + 'RegExp_exec': self.RegExp.prototype.exec, + 'Request_clone': self.Request.prototype.clone, + 'String': self.String, + 'String_fromCharCode': String.fromCharCode, + 'String_split': String.prototype.split, + 'XMLHttpRequest': self.XMLHttpRequest, + 'addEventListener': self.EventTarget.prototype.addEventListener, + 'removeEventListener': self.EventTarget.prototype.removeEventListener, + 'fetch': self.fetch, + 'JSON': self.JSON, + 'JSON_parseFn': self.JSON.parse, + 'JSON_stringifyFn': self.JSON.stringify, + 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), + 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + 'log': console.log.bind(console), + // Properties + logLevel: 0, + // Methods + makeLogPrefix(...args) { + return this.sendToLogger && `[${args.join(' \u205D ')}]` || ''; + }, + uboLog(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('info', ...args); + + }, + uboErr(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('error', ...args); + }, + escapeRegexChars(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + }, + initPattern(pattern, options = {}) { + if ( pattern === '' ) { + return { matchAll: true, expect: true }; + } + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + if ( expect === false ) { + pattern = pattern.slice(1); + } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match !== null ) { + return { + re: new this.RegExp( + match[1], + match[2] || options.flags + ), + expect, + }; + } + if ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; + } + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test.call(details.re, haystack) === details.expect; + } + return haystack.includes(details.pattern) === details.expect; + }, + patternToRegex(pattern, flags = undefined, verbatim = false) { + if ( pattern === '' ) { return /^/; } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match === null ) { + const reStr = this.escapeRegexChars(pattern); + return new RegExp(verbatim ? `^${reStr}$` : reStr, flags); + } + try { + return new RegExp(match[1], match[2] || undefined); + } + catch { + } + return /^/; + }, + getExtraArgs(args, offset = 0) { + const entries = args.slice(offset).reduce((out, v, i, a) => { + if ( (i & 1) === 0 ) { + const rawValue = a[i+1]; + const value = /^\d+$/.test(rawValue) + ? parseInt(rawValue, 10) + : rawValue; + out.push([ a[i], value ]); + } + return out; + }, []); + return this.Object_fromEntries(entries); + }, + }; + scriptletGlobals.safeSelf = safe; + if ( scriptletGlobals.bcSecret === undefined ) { return safe; } + // This is executed only when the logger is opened + safe.logLevel = scriptletGlobals.logLevel || 1; + let lastLogType = ''; + let lastLogText = ''; + let lastLogTime = 0; + safe.toLogText = (type, ...args) => { + if ( args.length === 0 ) { return; } + const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; + if ( text === lastLogText && type === lastLogType ) { + if ( (Date.now() - lastLogTime) < 5000 ) { return; } + } + lastLogType = type; + lastLogText = text; + lastLogTime = Date.now(); + return text; + }; + try { + const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); + let bcBuffer = []; + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + if ( bcBuffer === undefined ) { + return bc.postMessage({ what: 'messageToLogger', type, text }); + } + bcBuffer.push({ type, text }); + }; + bc.onmessage = ev => { + const msg = ev.data; + switch ( msg ) { + case 'iamready!': + if ( bcBuffer === undefined ) { break; } + bcBuffer.forEach(({ type, text }) => + bc.postMessage({ what: 'messageToLogger', type, text }) + ); + bcBuffer = undefined; + break; + case 'setScriptletLogLevelToOne': + safe.logLevel = 1; + break; + case 'setScriptletLogLevelToTwo': + safe.logLevel = 2; + break; + } + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; + } + return safe; +} +function proxyApplyFn( + target = '', + handler = '' +) { + let context = globalThis; + let prop = target; + for (;;) { + const pos = prop.indexOf('.'); + if ( pos === -1 ) { break; } + context = context[prop.slice(0, pos)]; + if ( context instanceof Object === false ) { return; } + prop = prop.slice(pos+1); + } + const fn = context[prop]; + if ( typeof fn !== 'function' ) { return; } + if ( proxyApplyFn.CtorContext === undefined ) { + proxyApplyFn.ctorContexts = []; + proxyApplyFn.CtorContext = class { + constructor(...args) { + this.init(...args); + } + init(callFn, callArgs) { + this.callFn = callFn; + this.callArgs = callArgs; + return this; + } + reflect() { + const r = Reflect.construct(this.callFn, this.callArgs); + this.callFn = this.callArgs = this.private = undefined; + proxyApplyFn.ctorContexts.push(this); + return r; + } + static factory(...args) { + return proxyApplyFn.ctorContexts.length !== 0 + ? proxyApplyFn.ctorContexts.pop().init(...args) + : new proxyApplyFn.CtorContext(...args); + } + }; + proxyApplyFn.applyContexts = []; + proxyApplyFn.ApplyContext = class { + constructor(...args) { + this.init(...args); + } + init(callFn, thisArg, callArgs) { + this.callFn = callFn; + this.thisArg = thisArg; + this.callArgs = callArgs; + return this; + } + reflect() { + const r = Reflect.apply(this.callFn, this.thisArg, this.callArgs); + this.callFn = this.thisArg = this.callArgs = this.private = undefined; + proxyApplyFn.applyContexts.push(this); + return r; + } + static factory(...args) { + return proxyApplyFn.applyContexts.length !== 0 + ? proxyApplyFn.applyContexts.pop().init(...args) + : new proxyApplyFn.ApplyContext(...args); + } + }; + proxyApplyFn.isCtor = new Map(); + proxyApplyFn.proxies = new WeakMap(); + proxyApplyFn.nativeToString = Function.prototype.toString; + const proxiedToString = new Proxy(Function.prototype.toString, { + apply(target, thisArg) { + let proxied = thisArg; + for(;;) { + const fn = proxyApplyFn.proxies.get(proxied); + if ( fn === undefined ) { break; } + proxied = fn; + } + return proxyApplyFn.nativeToString.call(proxied); + } + }); + proxyApplyFn.proxies.set(proxiedToString, proxyApplyFn.nativeToString); + Function.prototype.toString = proxiedToString; + } + if ( proxyApplyFn.isCtor.has(target) === false ) { + proxyApplyFn.isCtor.set(target, fn.prototype?.constructor === fn); + } + const proxyDetails = { + apply(target, thisArg, args) { + return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args)); + } + }; + if ( proxyApplyFn.isCtor.get(target) ) { + proxyDetails.construct = function(target, args) { + return handler(proxyApplyFn.CtorContext.factory(target, args)); + }; + } + const proxiedTarget = new Proxy(fn, proxyDetails); + proxyApplyFn.proxies.set(proxiedTarget, fn); + context[prop] = proxiedTarget; +} +class JSONPath { + static create(query) { + const jsonp = new JSONPath(); + jsonp.compile(query); + return jsonp; + } + static toJSON(obj, stringifier, ...args) { + return (stringifier || JSON.stringify)(obj, ...args) + .replace(/\//g, '\\/'); + } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; + get value() { + return this.#compiled && this.#compiled.rval; + } + set value(v) { + if ( this.#compiled === undefined ) { return; } + this.#compiled.rval = v; + } + get valid() { + return this.#compiled !== undefined; + } + compile(query) { + this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } + const r = this.#compile(query, 0); + if ( r === undefined ) { return; } + if ( r.i !== query.length ) { + let val; + if ( query.startsWith('=', r.i) ) { + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; + } else { + val = query.slice(r.i+1); + } + } else if ( query.startsWith('+=', r.i) ) { + r.modify = '+'; + val = query.slice(r.i+2); + } + try { r.rval = JSON.parse(val); } + catch { return; } + } + r.v2 = v2; + this.#compiled = r; + } + evaluate(root) { + if ( this.valid === false ) { return []; } + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + this.#root = null; + return paths; + } + apply(root) { + if ( this.valid === false ) { return; } + const { rval } = this.#compiled; + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + let i = paths.length + if ( i === 0 ) { this.#root = null; return; } + while ( i-- ) { + const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } + if ( rval !== undefined ) { + this.#modifyVal(obj, key); + } else if ( Array.isArray(obj) && typeof key === 'number' ) { + obj.splice(key, 1); + } else { + delete obj[key]; + } + } + const result = this.#root['$'] ?? null; + this.#root = null; + return result; + } + dump() { + return JSON.stringify(this.#compiled); + } + toJSON(obj, ...args) { + return JSONPath.toJSON(obj, null, ...args) + } + get [Symbol.toStringTag]() { + return 'JSONPath'; + } + #UNDEFINED = 0; + #ROOT = 1; + #CURRENT = 2; + #CHILDREN = 3; + #DESCENDANTS = 4; + #QUANTIFIER = 5; + #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; + #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; + #root; + #compiled; + #compile(query, i) { + if ( query.length === 0 ) { return; } + const steps = []; + let c = query.charCodeAt(i); + if ( c === 0x24 /* $ */ ) { + steps.push({ mv: this.#ROOT }); + i += 1; + } else if ( c === 0x40 /* @ */ ) { + steps.push({ mv: this.#CURRENT }); + i += 1; + } else { + steps.push({ mv: i === 0 ? this.#ROOT : this.#CURRENT }); + } + let mv = this.#UNDEFINED; + for (;;) { + if ( i === query.length ) { break; } + c = query.charCodeAt(i); + if ( c === 0x20 /* whitespace */ ) { + i += 1; + continue; + } + // Dot accessor syntax + if ( c === 0x2E /* . */ ) { + if ( mv !== this.#UNDEFINED ) { return; } + if ( query.startsWith('..', i) ) { + mv = this.#DESCENDANTS; + i += 2; + } else { + mv = this.#CHILDREN; + i += 1; + } + continue; + } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } + if ( c !== 0x5B /* [ */ ) { + if ( mv === this.#UNDEFINED ) { + const step = steps.at(-1); + if ( step === undefined ) { return; } + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } + break; + } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; + mv = this.#UNDEFINED; + continue; + } + // Bracket accessor syntax + if ( query.startsWith('[?', i) ) { + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; + const r = this.#compile(query, j); + if ( r === undefined ) { return; } + if ( query.startsWith(']', r.i) === false ) { return; } + if ( not ) { r.steps.at(-1).not = true; } + steps.push({ mv: mv || this.#CHILDREN, steps: r.steps }); + i = r.i + 1; + mv = this.#UNDEFINED; + continue; + } + if ( query.startsWith('[*]', i) ) { + mv ||= this.#CHILDREN; + steps.push({ mv, k: '*' }); + i += 3; + mv = this.#UNDEFINED; + continue; + } + const r = this.#consumeIdentifier(query, i+1); + if ( r === undefined ) { return; } + mv ||= this.#CHILDREN; + steps.push({ mv, k: r.s }); + i = r.i + 1; + mv = this.#UNDEFINED; + } + if ( steps.length === 0 ) { return; } + if ( mv !== this.#UNDEFINED ) { return; } + return { steps, i }; + } + #evaluate(steps, pathin) { + let resultset = []; + if ( Array.isArray(steps) === false ) { return resultset; } + for ( const step of steps ) { + switch ( step.mv ) { + case this.#ROOT: + resultset = [ [ '$' ] ]; + break; + case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } + resultset = [ pathin ]; + break; + case this.#CHILDREN: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } + resultset = this.#getMatches(resultset, step); + break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } + default: + break; + } + } + return resultset; + } + #getMatches(listin, step) { + const listout = []; + for ( const pathin of listin ) { + const { value: owner } = this.#resolvePath(pathin); + if ( owner === undefined ) { continue; } + if ( step.steps ) { + this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } + } + } + return listout; + } + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; + } + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; + } + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); + } + return out; + } + return [ k ]; + } + #getMatchesFromExpr(pathin, step, owner, out) { + const recursive = step.mv === this.#DESCENDANTS; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; + const r = this.#evaluate(step.steps, q); + if ( Boolean(r?.length) === false ) { continue; } + out.push(q); + if ( v2 === false ) { break; } + } + } + #getDescendants(v, recursive) { + const iterator = { + next() { + const n = this.stack.length; + if ( n === 0 ) { + this.value = undefined; + this.done = true; + return this; + } + const details = this.stack[n-1]; + const entry = details.keys.next(); + if ( entry.done ) { + this.stack.pop(); + this.path.pop(); + return this.next(); + } + this.path[n-1] = entry.value; + this.value = { + obj: details.obj, + key: entry.value, + path: this.path.slice(), + }; + const v = this.value.obj[this.value.key]; + if ( recursive ) { + if ( Array.isArray(v) ) { + this.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); + } + } + return this; + }, + path: [], + value: undefined, + done: false, + stack: [], + [Symbol.iterator]() { return this; }, + }; + if ( Array.isArray(v) ) { + iterator.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); + } + return iterator; + } + #consumeIdentifier(query, i) { + const keys = []; + for (;;) { + const c0 = query.charCodeAt(i); + if ( c0 === 0x5D /* ] */ ) { break; } + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { + i += 1; + continue; + } + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + continue; + } + if ( c0 === 0x2D /* - */ || c0 >= 0x30 && c0 <= 0x39 ) { + const match = this.#reIndice.exec(query.slice(i)); + if ( match === null ) { return; } + const indice = parseInt(query.slice(i), 10); + keys.push(indice); + i += match[0].length; + continue; + } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + } + return { s: keys.length === 1 ? keys[0] : keys, i }; + } + #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } + const match = this.#reUnquotedIdentifier.exec(query.slice(i)); + if ( match === null ) { return; } + return { s: match[0], i: i + match[0].length }; + } + #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 === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + break; + } + if ( c === 0x5C /* \ */ && (end+1) < len ) { + const d = query.charCodeAt(end+1); + if ( d === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + beg = end; + } + } + end += 1; + } + return { s: parts.join(''), i: end }; + } + #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 JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } + 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; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } + } + return i + match[0].length - 1; + } + #resolvePath(path) { + if ( path.length === 0 ) { return { value: this.#root }; } + const key = path.at(-1); + let obj = this.#root + for ( let i = 0, n = path.length-1; i < n; i++ ) { + obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } + } + return { obj, key, value: obj[key] }; + } + #evaluateExpr(step, owner, k) { + if ( owner === undefined || owner === null ) { return; } + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); + if ( step.op !== undefined && hasOwn === false ) { return; } + const target = step.not !== true; + const v = owner[k]; + switch ( step.op ) { + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; + } + #modifyVal(obj, key) { + let { modify, rval } = this.#compiled; + if ( typeof rval === 'string' ) { + rval = rval.replace('${now}', `${Date.now()}`); + } + switch ( modify ) { + case undefined: + obj[key] = rval; + break; + case '+': { + if ( rval instanceof Object === false ) { return; } + const lval = obj[key]; + if ( lval instanceof Object === false ) { return; } + if ( Array.isArray(lval) ) { return; } + for ( const [ k, v ] of JSONPath.entries(rval) ) { + lval[k] = v; + } + break; + } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } + case 'repl': { + const lval = obj[key]; + if ( typeof lval !== 'string' ) { return; } + if ( this.#compiled.re === undefined ) { + this.#compiled.re = null; + try { + this.#compiled.re = rval.regex !== undefined + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } + } + if ( this.#compiled.re === null ) { return; } + obj[key] = lval.replace(this.#compiled.re, rval.replacement); + break; + } + default: + break; + } + } +} +function editThisObjectFn( + trusted = false, + propChain = '', + jsonq = '', + order = '' +) { + if ( propChain === '' ) { return; } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix( + `${trusted ? 'trusted-' : ''}edit-this-object`, + propChain, + jsonq + ); + const jsonp = JSONPath.create(jsonq); + if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { + return safe.uboLog(logPrefix, 'Bad JSONPath query'); + } + const editObj = objBefore => { + const objAfter = jsonp.apply(objBefore); + if ( objAfter === undefined ) { return; } + safe.uboLog(logPrefix, 'Edited'); + if ( safe.logLevel <= 1 ) { return; } + safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); + }; + proxyApplyFn(propChain, function(context) { + const { thisArg } = context; + let r; + if ( order === 'after' ) { + r = context.reflect(); + } + editObj(thisArg); + if ( order !== 'after' ) { + r = context.reflect(); + } + return r; + }); +} +function trustedEditThisObject(...args) { + editThisObjectFn(true, ...args); +}; +trustedEditThisObject(...args); +}, +}; + + +scriptlets['edit-object-on-getter.js'] = { +aliases: [], + +requiresTrust: false, +func: function (scriptletGlobals = {}, ...args) { +function trapPropertyFn(propChain, handler) { + if ( propChain === '' ) { return; } + let owner = self; + let prop = propChain; + for (;;) { + const pos = prop.indexOf('.'); + if ( pos === -1 ) { break; } + owner = owner[prop.slice(0, pos)]; + if ( owner instanceof Object === false ) { return; } + prop = prop.slice(pos + 1); + } + const safe = safeSelf(); + if ( trapPropertyFn.db === undefined ) { + trapPropertyFn.db = new WeakMap(); + trapPropertyFn.entryFromContext = (owner, prop) => { + const handlers = trapPropertyFn.db.get(owner); + return handlers?.get(prop); + }; + trapPropertyFn.getter = (owner, prop) => { + const entry = trapPropertyFn.entryFromContext(owner, prop); + if ( entry === undefined ) { return; } + let r = entry.value; + for ( const desc of entry.stack ) { + try { r = desc.get(); } catch { } + } + return r; + }; + trapPropertyFn.setter = (owner, prop, value) => { + const entry = trapPropertyFn.entryFromContext(owner, prop); + if ( entry === undefined ) { return; } + entry.value = value; + for ( const desc of entry.stack ) { + try { desc.set(value); } catch { } + } + }; + } + const { db } = trapPropertyFn; + const handlers = db.get(owner) || new Map(); + if ( handlers.size === 0 ) { + db.set(owner, handlers); + } + const entry = handlers.get(prop) || { + value: owner[prop], + stack: [], + }; + entry.stack.push(handler); + if ( entry.stack.length > 1 ) { return entry.value; } + handlers.set(prop, entry); + const desc = safe.Object_getOwnPropertyDescriptor(owner, prop); + if ( desc instanceof safe.Object ) { + if ( desc.get || desc.set ) { + entry.stack.push(desc); + } + } + try { + safe.Object_defineProperty(owner, prop, { + get() { + return trapPropertyFn.getter(this, prop); + }, + set(value) { + trapPropertyFn.setter(this, prop, value); + } + }); + } catch { + } + return entry.value; +} +function safeSelf() { + if ( scriptletGlobals.safeSelf ) { + return scriptletGlobals.safeSelf; + } + const self = globalThis; + const safe = { + 'Array_from': Array.from, + 'Error': self.Error, + 'Function_toStringFn': self.Function.prototype.toString, + 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Math_floor': Math.floor, + 'Math_max': Math.max, + 'Math_min': Math.min, + 'Math_random': Math.random, + 'Object': Object, + 'Object_defineProperty': Object.defineProperty.bind(Object), + 'Object_defineProperties': Object.defineProperties.bind(Object), + 'Object_fromEntries': Object.fromEntries.bind(Object), + 'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object), + 'Object_hasOwn': Object.hasOwn.bind(Object), + 'Object_toString': Object.prototype.toString, + 'RegExp': self.RegExp, + 'RegExp_test': self.RegExp.prototype.test, + 'RegExp_exec': self.RegExp.prototype.exec, + 'Request_clone': self.Request.prototype.clone, + 'String': self.String, + 'String_fromCharCode': String.fromCharCode, + 'String_split': String.prototype.split, + 'XMLHttpRequest': self.XMLHttpRequest, + 'addEventListener': self.EventTarget.prototype.addEventListener, + 'removeEventListener': self.EventTarget.prototype.removeEventListener, + 'fetch': self.fetch, + 'JSON': self.JSON, + 'JSON_parseFn': self.JSON.parse, + 'JSON_stringifyFn': self.JSON.stringify, + 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), + 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + 'log': console.log.bind(console), + // Properties + logLevel: 0, + // Methods + makeLogPrefix(...args) { + return this.sendToLogger && `[${args.join(' \u205D ')}]` || ''; + }, + uboLog(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('info', ...args); + + }, + uboErr(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('error', ...args); + }, + escapeRegexChars(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + }, + initPattern(pattern, options = {}) { + if ( pattern === '' ) { + return { matchAll: true, expect: true }; + } + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + if ( expect === false ) { + pattern = pattern.slice(1); + } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match !== null ) { + return { + re: new this.RegExp( + match[1], + match[2] || options.flags + ), + expect, + }; + } + if ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; + } + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test.call(details.re, haystack) === details.expect; + } + return haystack.includes(details.pattern) === details.expect; + }, + patternToRegex(pattern, flags = undefined, verbatim = false) { + if ( pattern === '' ) { return /^/; } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match === null ) { + const reStr = this.escapeRegexChars(pattern); + return new RegExp(verbatim ? `^${reStr}$` : reStr, flags); + } + try { + return new RegExp(match[1], match[2] || undefined); + } + catch { + } + return /^/; + }, + getExtraArgs(args, offset = 0) { + const entries = args.slice(offset).reduce((out, v, i, a) => { + if ( (i & 1) === 0 ) { + const rawValue = a[i+1]; + const value = /^\d+$/.test(rawValue) + ? parseInt(rawValue, 10) + : rawValue; + out.push([ a[i], value ]); + } + return out; + }, []); + return this.Object_fromEntries(entries); + }, + }; + scriptletGlobals.safeSelf = safe; + if ( scriptletGlobals.bcSecret === undefined ) { return safe; } + // This is executed only when the logger is opened + safe.logLevel = scriptletGlobals.logLevel || 1; + let lastLogType = ''; + let lastLogText = ''; + let lastLogTime = 0; + safe.toLogText = (type, ...args) => { + if ( args.length === 0 ) { return; } + const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; + if ( text === lastLogText && type === lastLogType ) { + if ( (Date.now() - lastLogTime) < 5000 ) { return; } + } + lastLogType = type; + lastLogText = text; + lastLogTime = Date.now(); + return text; + }; + try { + const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); + let bcBuffer = []; + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + if ( bcBuffer === undefined ) { + return bc.postMessage({ what: 'messageToLogger', type, text }); + } + bcBuffer.push({ type, text }); + }; + bc.onmessage = ev => { + const msg = ev.data; + switch ( msg ) { + case 'iamready!': + if ( bcBuffer === undefined ) { break; } + bcBuffer.forEach(({ type, text }) => + bc.postMessage({ what: 'messageToLogger', type, text }) + ); + bcBuffer = undefined; + break; + case 'setScriptletLogLevelToOne': + safe.logLevel = 1; + break; + case 'setScriptletLogLevelToTwo': + safe.logLevel = 2; + break; + } + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; + } + return safe; +} +class JSONPath { + static create(query) { + const jsonp = new JSONPath(); + jsonp.compile(query); + return jsonp; + } + static toJSON(obj, stringifier, ...args) { + return (stringifier || JSON.stringify)(obj, ...args) + .replace(/\//g, '\\/'); + } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; + get value() { + return this.#compiled && this.#compiled.rval; + } + set value(v) { + if ( this.#compiled === undefined ) { return; } + this.#compiled.rval = v; + } + get valid() { + return this.#compiled !== undefined; + } + compile(query) { + this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } + const r = this.#compile(query, 0); + if ( r === undefined ) { return; } + if ( r.i !== query.length ) { + let val; + if ( query.startsWith('=', r.i) ) { + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; + } else { + val = query.slice(r.i+1); + } + } else if ( query.startsWith('+=', r.i) ) { + r.modify = '+'; + val = query.slice(r.i+2); + } + try { r.rval = JSON.parse(val); } + catch { return; } + } + r.v2 = v2; + this.#compiled = r; + } + evaluate(root) { + if ( this.valid === false ) { return []; } + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + this.#root = null; + return paths; + } + apply(root) { + if ( this.valid === false ) { return; } + const { rval } = this.#compiled; + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + let i = paths.length + if ( i === 0 ) { this.#root = null; return; } + while ( i-- ) { + const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } + if ( rval !== undefined ) { + this.#modifyVal(obj, key); + } else if ( Array.isArray(obj) && typeof key === 'number' ) { + obj.splice(key, 1); + } else { + delete obj[key]; + } + } + const result = this.#root['$'] ?? null; + this.#root = null; + return result; + } + dump() { + return JSON.stringify(this.#compiled); + } + toJSON(obj, ...args) { + return JSONPath.toJSON(obj, null, ...args) + } + get [Symbol.toStringTag]() { + return 'JSONPath'; + } + #UNDEFINED = 0; + #ROOT = 1; + #CURRENT = 2; + #CHILDREN = 3; + #DESCENDANTS = 4; + #QUANTIFIER = 5; + #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; + #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; + #root; + #compiled; + #compile(query, i) { + if ( query.length === 0 ) { return; } + const steps = []; + let c = query.charCodeAt(i); + if ( c === 0x24 /* $ */ ) { + steps.push({ mv: this.#ROOT }); + i += 1; + } else if ( c === 0x40 /* @ */ ) { + steps.push({ mv: this.#CURRENT }); + i += 1; + } else { + steps.push({ mv: i === 0 ? this.#ROOT : this.#CURRENT }); + } + let mv = this.#UNDEFINED; + for (;;) { + if ( i === query.length ) { break; } + c = query.charCodeAt(i); + if ( c === 0x20 /* whitespace */ ) { + i += 1; + continue; + } + // Dot accessor syntax + if ( c === 0x2E /* . */ ) { + if ( mv !== this.#UNDEFINED ) { return; } + if ( query.startsWith('..', i) ) { + mv = this.#DESCENDANTS; + i += 2; + } else { + mv = this.#CHILDREN; + i += 1; + } + continue; + } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } + if ( c !== 0x5B /* [ */ ) { + if ( mv === this.#UNDEFINED ) { + const step = steps.at(-1); + if ( step === undefined ) { return; } + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } + break; + } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; + mv = this.#UNDEFINED; + continue; + } + // Bracket accessor syntax + if ( query.startsWith('[?', i) ) { + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; + const r = this.#compile(query, j); + if ( r === undefined ) { return; } + if ( query.startsWith(']', r.i) === false ) { return; } + if ( not ) { r.steps.at(-1).not = true; } + steps.push({ mv: mv || this.#CHILDREN, steps: r.steps }); + i = r.i + 1; + mv = this.#UNDEFINED; + continue; + } + if ( query.startsWith('[*]', i) ) { + mv ||= this.#CHILDREN; + steps.push({ mv, k: '*' }); + i += 3; + mv = this.#UNDEFINED; + continue; + } + const r = this.#consumeIdentifier(query, i+1); + if ( r === undefined ) { return; } + mv ||= this.#CHILDREN; + steps.push({ mv, k: r.s }); + i = r.i + 1; + mv = this.#UNDEFINED; + } + if ( steps.length === 0 ) { return; } + if ( mv !== this.#UNDEFINED ) { return; } + return { steps, i }; + } + #evaluate(steps, pathin) { + let resultset = []; + if ( Array.isArray(steps) === false ) { return resultset; } + for ( const step of steps ) { + switch ( step.mv ) { + case this.#ROOT: + resultset = [ [ '$' ] ]; + break; + case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } + resultset = [ pathin ]; + break; + case this.#CHILDREN: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } + resultset = this.#getMatches(resultset, step); + break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } + default: + break; + } + } + return resultset; + } + #getMatches(listin, step) { + const listout = []; + for ( const pathin of listin ) { + const { value: owner } = this.#resolvePath(pathin); + if ( owner === undefined ) { continue; } + if ( step.steps ) { + this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } + } + } + return listout; + } + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; + } + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; + } + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); + } + return out; + } + return [ k ]; + } + #getMatchesFromExpr(pathin, step, owner, out) { + const recursive = step.mv === this.#DESCENDANTS; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; + const r = this.#evaluate(step.steps, q); + if ( Boolean(r?.length) === false ) { continue; } + out.push(q); + if ( v2 === false ) { break; } + } + } + #getDescendants(v, recursive) { + const iterator = { + next() { + const n = this.stack.length; + if ( n === 0 ) { + this.value = undefined; + this.done = true; + return this; + } + const details = this.stack[n-1]; + const entry = details.keys.next(); + if ( entry.done ) { + this.stack.pop(); + this.path.pop(); + return this.next(); + } + this.path[n-1] = entry.value; + this.value = { + obj: details.obj, + key: entry.value, + path: this.path.slice(), + }; + const v = this.value.obj[this.value.key]; + if ( recursive ) { + if ( Array.isArray(v) ) { + this.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); + } + } + return this; + }, + path: [], + value: undefined, + done: false, + stack: [], + [Symbol.iterator]() { return this; }, + }; + if ( Array.isArray(v) ) { + iterator.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); + } + return iterator; + } + #consumeIdentifier(query, i) { + const keys = []; + for (;;) { + const c0 = query.charCodeAt(i); + if ( c0 === 0x5D /* ] */ ) { break; } + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { + i += 1; + continue; + } + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + continue; + } + if ( c0 === 0x2D /* - */ || c0 >= 0x30 && c0 <= 0x39 ) { + const match = this.#reIndice.exec(query.slice(i)); + if ( match === null ) { return; } + const indice = parseInt(query.slice(i), 10); + keys.push(indice); + i += match[0].length; + continue; + } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + } + return { s: keys.length === 1 ? keys[0] : keys, i }; + } + #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } + const match = this.#reUnquotedIdentifier.exec(query.slice(i)); + if ( match === null ) { return; } + return { s: match[0], i: i + match[0].length }; + } + #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 === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + break; + } + if ( c === 0x5C /* \ */ && (end+1) < len ) { + const d = query.charCodeAt(end+1); + if ( d === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + beg = end; + } + } + end += 1; + } + return { s: parts.join(''), i: end }; + } + #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 JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } + 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; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } + } + return i + match[0].length - 1; + } + #resolvePath(path) { + if ( path.length === 0 ) { return { value: this.#root }; } + const key = path.at(-1); + let obj = this.#root + for ( let i = 0, n = path.length-1; i < n; i++ ) { + obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } + } + return { obj, key, value: obj[key] }; + } + #evaluateExpr(step, owner, k) { + if ( owner === undefined || owner === null ) { return; } + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); + if ( step.op !== undefined && hasOwn === false ) { return; } + const target = step.not !== true; + const v = owner[k]; + switch ( step.op ) { + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; + } + #modifyVal(obj, key) { + let { modify, rval } = this.#compiled; + if ( typeof rval === 'string' ) { + rval = rval.replace('${now}', `${Date.now()}`); + } + switch ( modify ) { + case undefined: + obj[key] = rval; + break; + case '+': { + if ( rval instanceof Object === false ) { return; } + const lval = obj[key]; + if ( lval instanceof Object === false ) { return; } + if ( Array.isArray(lval) ) { return; } + for ( const [ k, v ] of JSONPath.entries(rval) ) { + lval[k] = v; + } + break; + } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } + case 'repl': { + const lval = obj[key]; + if ( typeof lval !== 'string' ) { return; } + if ( this.#compiled.re === undefined ) { + this.#compiled.re = null; + try { + this.#compiled.re = rval.regex !== undefined + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } + } + if ( this.#compiled.re === null ) { return; } + obj[key] = lval.replace(this.#compiled.re, rval.replacement); + break; + } + default: + break; + } + } +} +function editObjectOnGetterFn( + trusted = false, + propChain = '', + jsonq = '', +) { + if ( propChain === '' ) { return; } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix( + `${trusted ? 'trusted-' : ''}edit-object-on-getter`, + propChain, + jsonq + ); + const jsonp = JSONPath.create(jsonq); + if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { + return safe.uboLog(logPrefix, 'Bad JSONPath query'); + } + const editObj = objBefore => { + const objAfter = jsonp.apply(objBefore); + if ( objAfter === undefined ) { return; } + safe.uboLog(logPrefix, 'Edited'); + if ( safe.logLevel <= 1 ) { return; } + safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); + }; + let currentValue = trapPropertyFn(propChain, { + get: function() { + if ( currentValue instanceof Object ) { + editObj(currentValue); + } + return currentValue; + }, + set: function(a) { + currentValue = a; + } + }); +} +function editObjectOnGetter(...args) { + editObjectOnGetterFn(false, ...args); +}; +editObjectOnGetter(...args); +}, +}; + + +scriptlets['trusted-edit-object-on-getter.js'] = { +aliases: [], + +requiresTrust: true, +func: function (scriptletGlobals = {}, ...args) { +function trapPropertyFn(propChain, handler) { + if ( propChain === '' ) { return; } + let owner = self; + let prop = propChain; + for (;;) { + const pos = prop.indexOf('.'); + if ( pos === -1 ) { break; } + owner = owner[prop.slice(0, pos)]; + if ( owner instanceof Object === false ) { return; } + prop = prop.slice(pos + 1); + } + const safe = safeSelf(); + if ( trapPropertyFn.db === undefined ) { + trapPropertyFn.db = new WeakMap(); + trapPropertyFn.entryFromContext = (owner, prop) => { + const handlers = trapPropertyFn.db.get(owner); + return handlers?.get(prop); + }; + trapPropertyFn.getter = (owner, prop) => { + const entry = trapPropertyFn.entryFromContext(owner, prop); + if ( entry === undefined ) { return; } + let r = entry.value; + for ( const desc of entry.stack ) { + try { r = desc.get(); } catch { } + } + return r; + }; + trapPropertyFn.setter = (owner, prop, value) => { + const entry = trapPropertyFn.entryFromContext(owner, prop); + if ( entry === undefined ) { return; } + entry.value = value; + for ( const desc of entry.stack ) { + try { desc.set(value); } catch { } + } + }; + } + const { db } = trapPropertyFn; + const handlers = db.get(owner) || new Map(); + if ( handlers.size === 0 ) { + db.set(owner, handlers); + } + const entry = handlers.get(prop) || { + value: owner[prop], + stack: [], + }; + entry.stack.push(handler); + if ( entry.stack.length > 1 ) { return entry.value; } + handlers.set(prop, entry); + const desc = safe.Object_getOwnPropertyDescriptor(owner, prop); + if ( desc instanceof safe.Object ) { + if ( desc.get || desc.set ) { + entry.stack.push(desc); + } + } + try { + safe.Object_defineProperty(owner, prop, { + get() { + return trapPropertyFn.getter(this, prop); + }, + set(value) { + trapPropertyFn.setter(this, prop, value); + } + }); + } catch { + } + return entry.value; +} +function safeSelf() { + if ( scriptletGlobals.safeSelf ) { + return scriptletGlobals.safeSelf; + } + const self = globalThis; + const safe = { + 'Array_from': Array.from, + 'Error': self.Error, + 'Function_toStringFn': self.Function.prototype.toString, + 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Math_floor': Math.floor, + 'Math_max': Math.max, + 'Math_min': Math.min, + 'Math_random': Math.random, + 'Object': Object, + 'Object_defineProperty': Object.defineProperty.bind(Object), + 'Object_defineProperties': Object.defineProperties.bind(Object), + 'Object_fromEntries': Object.fromEntries.bind(Object), + 'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object), + 'Object_hasOwn': Object.hasOwn.bind(Object), + 'Object_toString': Object.prototype.toString, + 'RegExp': self.RegExp, + 'RegExp_test': self.RegExp.prototype.test, + 'RegExp_exec': self.RegExp.prototype.exec, + 'Request_clone': self.Request.prototype.clone, + 'String': self.String, + 'String_fromCharCode': String.fromCharCode, + 'String_split': String.prototype.split, + 'XMLHttpRequest': self.XMLHttpRequest, + 'addEventListener': self.EventTarget.prototype.addEventListener, + 'removeEventListener': self.EventTarget.prototype.removeEventListener, + 'fetch': self.fetch, + 'JSON': self.JSON, + 'JSON_parseFn': self.JSON.parse, + 'JSON_stringifyFn': self.JSON.stringify, + 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), + 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + 'log': console.log.bind(console), + // Properties + logLevel: 0, + // Methods + makeLogPrefix(...args) { + return this.sendToLogger && `[${args.join(' \u205D ')}]` || ''; + }, + uboLog(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('info', ...args); + + }, + uboErr(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('error', ...args); + }, + escapeRegexChars(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + }, + initPattern(pattern, options = {}) { + if ( pattern === '' ) { + return { matchAll: true, expect: true }; + } + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + if ( expect === false ) { + pattern = pattern.slice(1); + } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match !== null ) { + return { + re: new this.RegExp( + match[1], + match[2] || options.flags + ), + expect, + }; + } + if ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; + } + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test.call(details.re, haystack) === details.expect; + } + return haystack.includes(details.pattern) === details.expect; + }, + patternToRegex(pattern, flags = undefined, verbatim = false) { + if ( pattern === '' ) { return /^/; } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match === null ) { + const reStr = this.escapeRegexChars(pattern); + return new RegExp(verbatim ? `^${reStr}$` : reStr, flags); + } + try { + return new RegExp(match[1], match[2] || undefined); + } + catch { + } + return /^/; + }, + getExtraArgs(args, offset = 0) { + const entries = args.slice(offset).reduce((out, v, i, a) => { + if ( (i & 1) === 0 ) { + const rawValue = a[i+1]; + const value = /^\d+$/.test(rawValue) + ? parseInt(rawValue, 10) + : rawValue; + out.push([ a[i], value ]); + } + return out; + }, []); + return this.Object_fromEntries(entries); + }, + }; + scriptletGlobals.safeSelf = safe; + if ( scriptletGlobals.bcSecret === undefined ) { return safe; } + // This is executed only when the logger is opened + safe.logLevel = scriptletGlobals.logLevel || 1; + let lastLogType = ''; + let lastLogText = ''; + let lastLogTime = 0; + safe.toLogText = (type, ...args) => { + if ( args.length === 0 ) { return; } + const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; + if ( text === lastLogText && type === lastLogType ) { + if ( (Date.now() - lastLogTime) < 5000 ) { return; } + } + lastLogType = type; + lastLogText = text; + lastLogTime = Date.now(); + return text; + }; + try { + const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); + let bcBuffer = []; + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + if ( bcBuffer === undefined ) { + return bc.postMessage({ what: 'messageToLogger', type, text }); + } + bcBuffer.push({ type, text }); + }; + bc.onmessage = ev => { + const msg = ev.data; + switch ( msg ) { + case 'iamready!': + if ( bcBuffer === undefined ) { break; } + bcBuffer.forEach(({ type, text }) => + bc.postMessage({ what: 'messageToLogger', type, text }) + ); + bcBuffer = undefined; + break; + case 'setScriptletLogLevelToOne': + safe.logLevel = 1; + break; + case 'setScriptletLogLevelToTwo': + safe.logLevel = 2; + break; + } + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; + } + return safe; +} +class JSONPath { + static create(query) { + const jsonp = new JSONPath(); + jsonp.compile(query); + return jsonp; + } + static toJSON(obj, stringifier, ...args) { + return (stringifier || JSON.stringify)(obj, ...args) + .replace(/\//g, '\\/'); + } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; + get value() { + return this.#compiled && this.#compiled.rval; + } + set value(v) { + if ( this.#compiled === undefined ) { return; } + this.#compiled.rval = v; + } + get valid() { + return this.#compiled !== undefined; + } + compile(query) { + this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } + const r = this.#compile(query, 0); + if ( r === undefined ) { return; } + if ( r.i !== query.length ) { + let val; + if ( query.startsWith('=', r.i) ) { + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; + } else { + val = query.slice(r.i+1); + } + } else if ( query.startsWith('+=', r.i) ) { + r.modify = '+'; + val = query.slice(r.i+2); + } + try { r.rval = JSON.parse(val); } + catch { return; } + } + r.v2 = v2; + this.#compiled = r; + } + evaluate(root) { + if ( this.valid === false ) { return []; } + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + this.#root = null; + return paths; + } + apply(root) { + if ( this.valid === false ) { return; } + const { rval } = this.#compiled; + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + let i = paths.length + if ( i === 0 ) { this.#root = null; return; } + while ( i-- ) { + const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } + if ( rval !== undefined ) { + this.#modifyVal(obj, key); + } else if ( Array.isArray(obj) && typeof key === 'number' ) { + obj.splice(key, 1); + } else { + delete obj[key]; + } + } + const result = this.#root['$'] ?? null; + this.#root = null; + return result; + } + dump() { + return JSON.stringify(this.#compiled); + } + toJSON(obj, ...args) { + return JSONPath.toJSON(obj, null, ...args) + } + get [Symbol.toStringTag]() { + return 'JSONPath'; + } + #UNDEFINED = 0; + #ROOT = 1; + #CURRENT = 2; + #CHILDREN = 3; + #DESCENDANTS = 4; + #QUANTIFIER = 5; + #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; + #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; + #root; + #compiled; + #compile(query, i) { + if ( query.length === 0 ) { return; } + const steps = []; + let c = query.charCodeAt(i); + if ( c === 0x24 /* $ */ ) { + steps.push({ mv: this.#ROOT }); + i += 1; + } else if ( c === 0x40 /* @ */ ) { + steps.push({ mv: this.#CURRENT }); + i += 1; + } else { + steps.push({ mv: i === 0 ? this.#ROOT : this.#CURRENT }); + } + let mv = this.#UNDEFINED; + for (;;) { + if ( i === query.length ) { break; } + c = query.charCodeAt(i); + if ( c === 0x20 /* whitespace */ ) { + i += 1; + continue; + } + // Dot accessor syntax + if ( c === 0x2E /* . */ ) { + if ( mv !== this.#UNDEFINED ) { return; } + if ( query.startsWith('..', i) ) { + mv = this.#DESCENDANTS; + i += 2; + } else { + mv = this.#CHILDREN; + i += 1; + } + continue; + } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } + if ( c !== 0x5B /* [ */ ) { + if ( mv === this.#UNDEFINED ) { + const step = steps.at(-1); + if ( step === undefined ) { return; } + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } + break; + } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; + mv = this.#UNDEFINED; + continue; + } + // Bracket accessor syntax + if ( query.startsWith('[?', i) ) { + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; + const r = this.#compile(query, j); + if ( r === undefined ) { return; } + if ( query.startsWith(']', r.i) === false ) { return; } + if ( not ) { r.steps.at(-1).not = true; } + steps.push({ mv: mv || this.#CHILDREN, steps: r.steps }); + i = r.i + 1; + mv = this.#UNDEFINED; + continue; + } + if ( query.startsWith('[*]', i) ) { + mv ||= this.#CHILDREN; + steps.push({ mv, k: '*' }); + i += 3; + mv = this.#UNDEFINED; + continue; + } + const r = this.#consumeIdentifier(query, i+1); + if ( r === undefined ) { return; } + mv ||= this.#CHILDREN; + steps.push({ mv, k: r.s }); + i = r.i + 1; + mv = this.#UNDEFINED; + } + if ( steps.length === 0 ) { return; } + if ( mv !== this.#UNDEFINED ) { return; } + return { steps, i }; + } + #evaluate(steps, pathin) { + let resultset = []; + if ( Array.isArray(steps) === false ) { return resultset; } + for ( const step of steps ) { + switch ( step.mv ) { + case this.#ROOT: + resultset = [ [ '$' ] ]; + break; + case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } + resultset = [ pathin ]; + break; + case this.#CHILDREN: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } + resultset = this.#getMatches(resultset, step); + break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } + default: + break; + } + } + return resultset; + } + #getMatches(listin, step) { + const listout = []; + for ( const pathin of listin ) { + const { value: owner } = this.#resolvePath(pathin); + if ( owner === undefined ) { continue; } + if ( step.steps ) { + this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } + } + } + return listout; + } + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; + } + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; + } + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); + } + return out; + } + return [ k ]; + } + #getMatchesFromExpr(pathin, step, owner, out) { + const recursive = step.mv === this.#DESCENDANTS; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; + const r = this.#evaluate(step.steps, q); + if ( Boolean(r?.length) === false ) { continue; } + out.push(q); + if ( v2 === false ) { break; } + } + } + #getDescendants(v, recursive) { + const iterator = { + next() { + const n = this.stack.length; + if ( n === 0 ) { + this.value = undefined; + this.done = true; + return this; + } + const details = this.stack[n-1]; + const entry = details.keys.next(); + if ( entry.done ) { + this.stack.pop(); + this.path.pop(); + return this.next(); + } + this.path[n-1] = entry.value; + this.value = { + obj: details.obj, + key: entry.value, + path: this.path.slice(), + }; + const v = this.value.obj[this.value.key]; + if ( recursive ) { + if ( Array.isArray(v) ) { + this.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); + } + } + return this; + }, + path: [], + value: undefined, + done: false, + stack: [], + [Symbol.iterator]() { return this; }, + }; + if ( Array.isArray(v) ) { + iterator.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); + } + return iterator; + } + #consumeIdentifier(query, i) { + const keys = []; + for (;;) { + const c0 = query.charCodeAt(i); + if ( c0 === 0x5D /* ] */ ) { break; } + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { + i += 1; + continue; + } + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + continue; + } + if ( c0 === 0x2D /* - */ || c0 >= 0x30 && c0 <= 0x39 ) { + const match = this.#reIndice.exec(query.slice(i)); + if ( match === null ) { return; } + const indice = parseInt(query.slice(i), 10); + keys.push(indice); + i += match[0].length; + continue; + } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + } + return { s: keys.length === 1 ? keys[0] : keys, i }; + } + #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } + const match = this.#reUnquotedIdentifier.exec(query.slice(i)); + if ( match === null ) { return; } + return { s: match[0], i: i + match[0].length }; + } + #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 === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + break; + } + if ( c === 0x5C /* \ */ && (end+1) < len ) { + const d = query.charCodeAt(end+1); + if ( d === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + beg = end; + } + } + end += 1; + } + return { s: parts.join(''), i: end }; + } + #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 JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } + 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; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } + } + return i + match[0].length - 1; + } + #resolvePath(path) { + if ( path.length === 0 ) { return { value: this.#root }; } + const key = path.at(-1); + let obj = this.#root + for ( let i = 0, n = path.length-1; i < n; i++ ) { + obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } + } + return { obj, key, value: obj[key] }; + } + #evaluateExpr(step, owner, k) { + if ( owner === undefined || owner === null ) { return; } + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); + if ( step.op !== undefined && hasOwn === false ) { return; } + const target = step.not !== true; + const v = owner[k]; + switch ( step.op ) { + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; + } + #modifyVal(obj, key) { + let { modify, rval } = this.#compiled; + if ( typeof rval === 'string' ) { + rval = rval.replace('${now}', `${Date.now()}`); + } + switch ( modify ) { + case undefined: + obj[key] = rval; + break; + case '+': { + if ( rval instanceof Object === false ) { return; } + const lval = obj[key]; + if ( lval instanceof Object === false ) { return; } + if ( Array.isArray(lval) ) { return; } + for ( const [ k, v ] of JSONPath.entries(rval) ) { + lval[k] = v; + } + break; + } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } + case 'repl': { + const lval = obj[key]; + if ( typeof lval !== 'string' ) { return; } + if ( this.#compiled.re === undefined ) { + this.#compiled.re = null; + try { + this.#compiled.re = rval.regex !== undefined + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } + } + if ( this.#compiled.re === null ) { return; } + obj[key] = lval.replace(this.#compiled.re, rval.replacement); + break; + } + default: + break; + } + } +} +function editObjectOnGetterFn( + trusted = false, + propChain = '', + jsonq = '', +) { + if ( propChain === '' ) { return; } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix( + `${trusted ? 'trusted-' : ''}edit-object-on-getter`, + propChain, + jsonq + ); + const jsonp = JSONPath.create(jsonq); + if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { + return safe.uboLog(logPrefix, 'Bad JSONPath query'); + } + const editObj = objBefore => { + const objAfter = jsonp.apply(objBefore); + if ( objAfter === undefined ) { return; } + safe.uboLog(logPrefix, 'Edited'); + if ( safe.logLevel <= 1 ) { return; } + safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); + }; + let currentValue = trapPropertyFn(propChain, { + get: function() { + if ( currentValue instanceof Object ) { + editObj(currentValue); + } + return currentValue; + }, + set: function(a) { + currentValue = a; + } + }); +} +function trustedEditObjectOnGetter(...args) { + editObjectOnGetterFn(true, ...args); +}; +trustedEditObjectOnGetter(...args); +}, +}; + + +scriptlets['edit-object-on-setter.js'] = { +aliases: [], + +requiresTrust: false, +func: function (scriptletGlobals = {}, ...args) { +function trapPropertyFn(propChain, handler) { + if ( propChain === '' ) { return; } + let owner = self; + let prop = propChain; + for (;;) { + const pos = prop.indexOf('.'); + if ( pos === -1 ) { break; } + owner = owner[prop.slice(0, pos)]; + if ( owner instanceof Object === false ) { return; } + prop = prop.slice(pos + 1); + } + const safe = safeSelf(); + if ( trapPropertyFn.db === undefined ) { + trapPropertyFn.db = new WeakMap(); + trapPropertyFn.entryFromContext = (owner, prop) => { + const handlers = trapPropertyFn.db.get(owner); + return handlers?.get(prop); + }; + trapPropertyFn.getter = (owner, prop) => { + const entry = trapPropertyFn.entryFromContext(owner, prop); + if ( entry === undefined ) { return; } + let r = entry.value; + for ( const desc of entry.stack ) { + try { r = desc.get(); } catch { } + } + return r; + }; + trapPropertyFn.setter = (owner, prop, value) => { + const entry = trapPropertyFn.entryFromContext(owner, prop); + if ( entry === undefined ) { return; } + entry.value = value; + for ( const desc of entry.stack ) { + try { desc.set(value); } catch { } + } + }; + } + const { db } = trapPropertyFn; + const handlers = db.get(owner) || new Map(); + if ( handlers.size === 0 ) { + db.set(owner, handlers); + } + const entry = handlers.get(prop) || { + value: owner[prop], + stack: [], + }; + entry.stack.push(handler); + if ( entry.stack.length > 1 ) { return entry.value; } + handlers.set(prop, entry); + const desc = safe.Object_getOwnPropertyDescriptor(owner, prop); + if ( desc instanceof safe.Object ) { + if ( desc.get || desc.set ) { + entry.stack.push(desc); + } + } + try { + safe.Object_defineProperty(owner, prop, { + get() { + return trapPropertyFn.getter(this, prop); + }, + set(value) { + trapPropertyFn.setter(this, prop, value); + } + }); + } catch { + } + return entry.value; +} +function safeSelf() { + if ( scriptletGlobals.safeSelf ) { + return scriptletGlobals.safeSelf; + } + const self = globalThis; + const safe = { + 'Array_from': Array.from, + 'Error': self.Error, + 'Function_toStringFn': self.Function.prototype.toString, + 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Math_floor': Math.floor, + 'Math_max': Math.max, + 'Math_min': Math.min, + 'Math_random': Math.random, + 'Object': Object, + 'Object_defineProperty': Object.defineProperty.bind(Object), + 'Object_defineProperties': Object.defineProperties.bind(Object), + 'Object_fromEntries': Object.fromEntries.bind(Object), + 'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object), + 'Object_hasOwn': Object.hasOwn.bind(Object), + 'Object_toString': Object.prototype.toString, + 'RegExp': self.RegExp, + 'RegExp_test': self.RegExp.prototype.test, + 'RegExp_exec': self.RegExp.prototype.exec, + 'Request_clone': self.Request.prototype.clone, + 'String': self.String, + 'String_fromCharCode': String.fromCharCode, + 'String_split': String.prototype.split, + 'XMLHttpRequest': self.XMLHttpRequest, + 'addEventListener': self.EventTarget.prototype.addEventListener, + 'removeEventListener': self.EventTarget.prototype.removeEventListener, + 'fetch': self.fetch, + 'JSON': self.JSON, + 'JSON_parseFn': self.JSON.parse, + 'JSON_stringifyFn': self.JSON.stringify, + 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), + 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + 'log': console.log.bind(console), + // Properties + logLevel: 0, + // Methods + makeLogPrefix(...args) { + return this.sendToLogger && `[${args.join(' \u205D ')}]` || ''; + }, + uboLog(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('info', ...args); + + }, + uboErr(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('error', ...args); + }, + escapeRegexChars(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + }, + initPattern(pattern, options = {}) { + if ( pattern === '' ) { + return { matchAll: true, expect: true }; + } + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + if ( expect === false ) { + pattern = pattern.slice(1); + } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match !== null ) { + return { + re: new this.RegExp( + match[1], + match[2] || options.flags + ), + expect, + }; + } + if ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; + } + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test.call(details.re, haystack) === details.expect; + } + return haystack.includes(details.pattern) === details.expect; + }, + patternToRegex(pattern, flags = undefined, verbatim = false) { + if ( pattern === '' ) { return /^/; } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match === null ) { + const reStr = this.escapeRegexChars(pattern); + return new RegExp(verbatim ? `^${reStr}$` : reStr, flags); + } + try { + return new RegExp(match[1], match[2] || undefined); + } + catch { + } + return /^/; + }, + getExtraArgs(args, offset = 0) { + const entries = args.slice(offset).reduce((out, v, i, a) => { + if ( (i & 1) === 0 ) { + const rawValue = a[i+1]; + const value = /^\d+$/.test(rawValue) + ? parseInt(rawValue, 10) + : rawValue; + out.push([ a[i], value ]); + } + return out; + }, []); + return this.Object_fromEntries(entries); + }, + }; + scriptletGlobals.safeSelf = safe; + if ( scriptletGlobals.bcSecret === undefined ) { return safe; } + // This is executed only when the logger is opened + safe.logLevel = scriptletGlobals.logLevel || 1; + let lastLogType = ''; + let lastLogText = ''; + let lastLogTime = 0; + safe.toLogText = (type, ...args) => { + if ( args.length === 0 ) { return; } + const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; + if ( text === lastLogText && type === lastLogType ) { + if ( (Date.now() - lastLogTime) < 5000 ) { return; } + } + lastLogType = type; + lastLogText = text; + lastLogTime = Date.now(); + return text; + }; + try { + const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); + let bcBuffer = []; + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + if ( bcBuffer === undefined ) { + return bc.postMessage({ what: 'messageToLogger', type, text }); + } + bcBuffer.push({ type, text }); + }; + bc.onmessage = ev => { + const msg = ev.data; + switch ( msg ) { + case 'iamready!': + if ( bcBuffer === undefined ) { break; } + bcBuffer.forEach(({ type, text }) => + bc.postMessage({ what: 'messageToLogger', type, text }) + ); + bcBuffer = undefined; + break; + case 'setScriptletLogLevelToOne': + safe.logLevel = 1; + break; + case 'setScriptletLogLevelToTwo': + safe.logLevel = 2; + break; + } + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; + } + return safe; +} +class JSONPath { + static create(query) { + const jsonp = new JSONPath(); + jsonp.compile(query); + return jsonp; + } + static toJSON(obj, stringifier, ...args) { + return (stringifier || JSON.stringify)(obj, ...args) + .replace(/\//g, '\\/'); + } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; + get value() { + return this.#compiled && this.#compiled.rval; + } + set value(v) { + if ( this.#compiled === undefined ) { return; } + this.#compiled.rval = v; + } + get valid() { + return this.#compiled !== undefined; + } + compile(query) { + this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } + const r = this.#compile(query, 0); + if ( r === undefined ) { return; } + if ( r.i !== query.length ) { + let val; + if ( query.startsWith('=', r.i) ) { + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; + } else { + val = query.slice(r.i+1); + } + } else if ( query.startsWith('+=', r.i) ) { + r.modify = '+'; + val = query.slice(r.i+2); + } + try { r.rval = JSON.parse(val); } + catch { return; } + } + r.v2 = v2; + this.#compiled = r; + } + evaluate(root) { + if ( this.valid === false ) { return []; } + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + this.#root = null; + return paths; + } + apply(root) { + if ( this.valid === false ) { return; } + const { rval } = this.#compiled; + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + let i = paths.length + if ( i === 0 ) { this.#root = null; return; } + while ( i-- ) { + const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } + if ( rval !== undefined ) { + this.#modifyVal(obj, key); + } else if ( Array.isArray(obj) && typeof key === 'number' ) { + obj.splice(key, 1); + } else { + delete obj[key]; + } + } + const result = this.#root['$'] ?? null; + this.#root = null; + return result; + } + dump() { + return JSON.stringify(this.#compiled); + } + toJSON(obj, ...args) { + return JSONPath.toJSON(obj, null, ...args) + } + get [Symbol.toStringTag]() { + return 'JSONPath'; + } + #UNDEFINED = 0; + #ROOT = 1; + #CURRENT = 2; + #CHILDREN = 3; + #DESCENDANTS = 4; + #QUANTIFIER = 5; + #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; + #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; + #root; + #compiled; + #compile(query, i) { + if ( query.length === 0 ) { return; } + const steps = []; + let c = query.charCodeAt(i); + if ( c === 0x24 /* $ */ ) { + steps.push({ mv: this.#ROOT }); + i += 1; + } else if ( c === 0x40 /* @ */ ) { + steps.push({ mv: this.#CURRENT }); + i += 1; + } else { + steps.push({ mv: i === 0 ? this.#ROOT : this.#CURRENT }); + } + let mv = this.#UNDEFINED; + for (;;) { + if ( i === query.length ) { break; } + c = query.charCodeAt(i); + if ( c === 0x20 /* whitespace */ ) { + i += 1; + continue; + } + // Dot accessor syntax + if ( c === 0x2E /* . */ ) { + if ( mv !== this.#UNDEFINED ) { return; } + if ( query.startsWith('..', i) ) { + mv = this.#DESCENDANTS; + i += 2; + } else { + mv = this.#CHILDREN; + i += 1; + } + continue; + } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } + if ( c !== 0x5B /* [ */ ) { + if ( mv === this.#UNDEFINED ) { + const step = steps.at(-1); + if ( step === undefined ) { return; } + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } + break; + } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; + mv = this.#UNDEFINED; + continue; + } + // Bracket accessor syntax + if ( query.startsWith('[?', i) ) { + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; + const r = this.#compile(query, j); + if ( r === undefined ) { return; } + if ( query.startsWith(']', r.i) === false ) { return; } + if ( not ) { r.steps.at(-1).not = true; } + steps.push({ mv: mv || this.#CHILDREN, steps: r.steps }); + i = r.i + 1; + mv = this.#UNDEFINED; + continue; + } + if ( query.startsWith('[*]', i) ) { + mv ||= this.#CHILDREN; + steps.push({ mv, k: '*' }); + i += 3; + mv = this.#UNDEFINED; + continue; + } + const r = this.#consumeIdentifier(query, i+1); + if ( r === undefined ) { return; } + mv ||= this.#CHILDREN; + steps.push({ mv, k: r.s }); + i = r.i + 1; + mv = this.#UNDEFINED; + } + if ( steps.length === 0 ) { return; } + if ( mv !== this.#UNDEFINED ) { return; } + return { steps, i }; + } + #evaluate(steps, pathin) { + let resultset = []; + if ( Array.isArray(steps) === false ) { return resultset; } + for ( const step of steps ) { + switch ( step.mv ) { + case this.#ROOT: + resultset = [ [ '$' ] ]; + break; + case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } + resultset = [ pathin ]; + break; + case this.#CHILDREN: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } + resultset = this.#getMatches(resultset, step); + break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } + default: + break; + } + } + return resultset; + } + #getMatches(listin, step) { + const listout = []; + for ( const pathin of listin ) { + const { value: owner } = this.#resolvePath(pathin); + if ( owner === undefined ) { continue; } + if ( step.steps ) { + this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } + } + } + return listout; + } + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; + } + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; + } + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); + } + return out; + } + return [ k ]; + } + #getMatchesFromExpr(pathin, step, owner, out) { + const recursive = step.mv === this.#DESCENDANTS; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; + const r = this.#evaluate(step.steps, q); + if ( Boolean(r?.length) === false ) { continue; } + out.push(q); + if ( v2 === false ) { break; } + } + } + #getDescendants(v, recursive) { + const iterator = { + next() { + const n = this.stack.length; + if ( n === 0 ) { + this.value = undefined; + this.done = true; + return this; + } + const details = this.stack[n-1]; + const entry = details.keys.next(); + if ( entry.done ) { + this.stack.pop(); + this.path.pop(); + return this.next(); + } + this.path[n-1] = entry.value; + this.value = { + obj: details.obj, + key: entry.value, + path: this.path.slice(), + }; + const v = this.value.obj[this.value.key]; + if ( recursive ) { + if ( Array.isArray(v) ) { + this.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); + } + } + return this; + }, + path: [], + value: undefined, + done: false, + stack: [], + [Symbol.iterator]() { return this; }, + }; + if ( Array.isArray(v) ) { + iterator.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); + } + return iterator; + } + #consumeIdentifier(query, i) { + const keys = []; + for (;;) { + const c0 = query.charCodeAt(i); + if ( c0 === 0x5D /* ] */ ) { break; } + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { + i += 1; + continue; + } + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + continue; + } + if ( c0 === 0x2D /* - */ || c0 >= 0x30 && c0 <= 0x39 ) { + const match = this.#reIndice.exec(query.slice(i)); + if ( match === null ) { return; } + const indice = parseInt(query.slice(i), 10); + keys.push(indice); + i += match[0].length; + continue; + } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + } + return { s: keys.length === 1 ? keys[0] : keys, i }; + } + #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } + const match = this.#reUnquotedIdentifier.exec(query.slice(i)); + if ( match === null ) { return; } + return { s: match[0], i: i + match[0].length }; + } + #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 === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + break; + } + if ( c === 0x5C /* \ */ && (end+1) < len ) { + const d = query.charCodeAt(end+1); + if ( d === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + beg = end; + } + } + end += 1; + } + return { s: parts.join(''), i: end }; + } + #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 JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } + 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; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } + } + return i + match[0].length - 1; + } + #resolvePath(path) { + if ( path.length === 0 ) { return { value: this.#root }; } + const key = path.at(-1); + let obj = this.#root + for ( let i = 0, n = path.length-1; i < n; i++ ) { + obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } + } + return { obj, key, value: obj[key] }; + } + #evaluateExpr(step, owner, k) { + if ( owner === undefined || owner === null ) { return; } + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); + if ( step.op !== undefined && hasOwn === false ) { return; } + const target = step.not !== true; + const v = owner[k]; + switch ( step.op ) { + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; + } + #modifyVal(obj, key) { + let { modify, rval } = this.#compiled; + if ( typeof rval === 'string' ) { + rval = rval.replace('${now}', `${Date.now()}`); + } + switch ( modify ) { + case undefined: + obj[key] = rval; + break; + case '+': { + if ( rval instanceof Object === false ) { return; } + const lval = obj[key]; + if ( lval instanceof Object === false ) { return; } + if ( Array.isArray(lval) ) { return; } + for ( const [ k, v ] of JSONPath.entries(rval) ) { + lval[k] = v; + } + break; + } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } + case 'repl': { + const lval = obj[key]; + if ( typeof lval !== 'string' ) { return; } + if ( this.#compiled.re === undefined ) { + this.#compiled.re = null; + try { + this.#compiled.re = rval.regex !== undefined + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } + } + if ( this.#compiled.re === null ) { return; } + obj[key] = lval.replace(this.#compiled.re, rval.replacement); + break; + } + default: + break; + } + } +} +function editObjectOnSetterFn( + trusted = false, + propChain = '', + jsonq = '', +) { + if ( propChain === '' ) { return; } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix( + `${trusted ? 'trusted-' : ''}edit-object-on-setter`, + propChain, + jsonq + ); + const jsonp = JSONPath.create(jsonq); + if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { + return safe.uboLog(logPrefix, 'Bad JSONPath query'); + } + const editObj = objBefore => { + const objAfter = jsonp.apply(objBefore); + if ( objAfter === undefined ) { return; } + safe.uboLog(logPrefix, 'Edited'); + if ( safe.logLevel <= 1 ) { return; } + safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); + }; + let currentValue = trapPropertyFn(propChain, { + get: function() { + return currentValue; + }, + set: function(a) { + currentValue = a; + if ( currentValue instanceof Object ) { + editObj(currentValue); + } + } + }); + if ( currentValue instanceof Object ) { + editObj(currentValue); + } +} +function editObjectOnSetter(...args) { + editObjectOnSetterFn(false, ...args); +}; +editObjectOnSetter(...args); +}, +}; + + +scriptlets['trusted-edit-object-on-setter.js'] = { +aliases: [], + +requiresTrust: true, +func: function (scriptletGlobals = {}, ...args) { +function trapPropertyFn(propChain, handler) { + if ( propChain === '' ) { return; } + let owner = self; + let prop = propChain; + for (;;) { + const pos = prop.indexOf('.'); + if ( pos === -1 ) { break; } + owner = owner[prop.slice(0, pos)]; + if ( owner instanceof Object === false ) { return; } + prop = prop.slice(pos + 1); + } + const safe = safeSelf(); + if ( trapPropertyFn.db === undefined ) { + trapPropertyFn.db = new WeakMap(); + trapPropertyFn.entryFromContext = (owner, prop) => { + const handlers = trapPropertyFn.db.get(owner); + return handlers?.get(prop); + }; + trapPropertyFn.getter = (owner, prop) => { + const entry = trapPropertyFn.entryFromContext(owner, prop); + if ( entry === undefined ) { return; } + let r = entry.value; + for ( const desc of entry.stack ) { + try { r = desc.get(); } catch { } + } + return r; + }; + trapPropertyFn.setter = (owner, prop, value) => { + const entry = trapPropertyFn.entryFromContext(owner, prop); + if ( entry === undefined ) { return; } + entry.value = value; + for ( const desc of entry.stack ) { + try { desc.set(value); } catch { } + } + }; + } + const { db } = trapPropertyFn; + const handlers = db.get(owner) || new Map(); + if ( handlers.size === 0 ) { + db.set(owner, handlers); + } + const entry = handlers.get(prop) || { + value: owner[prop], + stack: [], + }; + entry.stack.push(handler); + if ( entry.stack.length > 1 ) { return entry.value; } + handlers.set(prop, entry); + const desc = safe.Object_getOwnPropertyDescriptor(owner, prop); + if ( desc instanceof safe.Object ) { + if ( desc.get || desc.set ) { + entry.stack.push(desc); + } + } + try { + safe.Object_defineProperty(owner, prop, { + get() { + return trapPropertyFn.getter(this, prop); + }, + set(value) { + trapPropertyFn.setter(this, prop, value); + } + }); + } catch { + } + return entry.value; +} +function safeSelf() { + if ( scriptletGlobals.safeSelf ) { + return scriptletGlobals.safeSelf; + } + const self = globalThis; + const safe = { + 'Array_from': Array.from, + 'Error': self.Error, + 'Function_toStringFn': self.Function.prototype.toString, + 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Math_floor': Math.floor, + 'Math_max': Math.max, + 'Math_min': Math.min, + 'Math_random': Math.random, + 'Object': Object, + 'Object_defineProperty': Object.defineProperty.bind(Object), + 'Object_defineProperties': Object.defineProperties.bind(Object), + 'Object_fromEntries': Object.fromEntries.bind(Object), + 'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object), + 'Object_hasOwn': Object.hasOwn.bind(Object), + 'Object_toString': Object.prototype.toString, + 'RegExp': self.RegExp, + 'RegExp_test': self.RegExp.prototype.test, + 'RegExp_exec': self.RegExp.prototype.exec, + 'Request_clone': self.Request.prototype.clone, + 'String': self.String, + 'String_fromCharCode': String.fromCharCode, + 'String_split': String.prototype.split, + 'XMLHttpRequest': self.XMLHttpRequest, + 'addEventListener': self.EventTarget.prototype.addEventListener, + 'removeEventListener': self.EventTarget.prototype.removeEventListener, + 'fetch': self.fetch, + 'JSON': self.JSON, + 'JSON_parseFn': self.JSON.parse, + 'JSON_stringifyFn': self.JSON.stringify, + 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), + 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + 'log': console.log.bind(console), + // Properties + logLevel: 0, + // Methods + makeLogPrefix(...args) { + return this.sendToLogger && `[${args.join(' \u205D ')}]` || ''; + }, + uboLog(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('info', ...args); + + }, + uboErr(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('error', ...args); + }, + escapeRegexChars(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + }, + initPattern(pattern, options = {}) { + if ( pattern === '' ) { + return { matchAll: true, expect: true }; + } + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + if ( expect === false ) { + pattern = pattern.slice(1); + } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match !== null ) { + return { + re: new this.RegExp( + match[1], + match[2] || options.flags + ), + expect, + }; + } + if ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; + } + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test.call(details.re, haystack) === details.expect; + } + return haystack.includes(details.pattern) === details.expect; + }, + patternToRegex(pattern, flags = undefined, verbatim = false) { + if ( pattern === '' ) { return /^/; } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match === null ) { + const reStr = this.escapeRegexChars(pattern); + return new RegExp(verbatim ? `^${reStr}$` : reStr, flags); + } + try { + return new RegExp(match[1], match[2] || undefined); + } + catch { + } + return /^/; + }, + getExtraArgs(args, offset = 0) { + const entries = args.slice(offset).reduce((out, v, i, a) => { + if ( (i & 1) === 0 ) { + const rawValue = a[i+1]; + const value = /^\d+$/.test(rawValue) + ? parseInt(rawValue, 10) + : rawValue; + out.push([ a[i], value ]); + } + return out; + }, []); + return this.Object_fromEntries(entries); + }, + }; + scriptletGlobals.safeSelf = safe; + if ( scriptletGlobals.bcSecret === undefined ) { return safe; } + // This is executed only when the logger is opened + safe.logLevel = scriptletGlobals.logLevel || 1; + let lastLogType = ''; + let lastLogText = ''; + let lastLogTime = 0; + safe.toLogText = (type, ...args) => { + if ( args.length === 0 ) { return; } + const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; + if ( text === lastLogText && type === lastLogType ) { + if ( (Date.now() - lastLogTime) < 5000 ) { return; } + } + lastLogType = type; + lastLogText = text; + lastLogTime = Date.now(); + return text; + }; + try { + const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); + let bcBuffer = []; + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + if ( bcBuffer === undefined ) { + return bc.postMessage({ what: 'messageToLogger', type, text }); + } + bcBuffer.push({ type, text }); + }; + bc.onmessage = ev => { + const msg = ev.data; + switch ( msg ) { + case 'iamready!': + if ( bcBuffer === undefined ) { break; } + bcBuffer.forEach(({ type, text }) => + bc.postMessage({ what: 'messageToLogger', type, text }) + ); + bcBuffer = undefined; + break; + case 'setScriptletLogLevelToOne': + safe.logLevel = 1; + break; + case 'setScriptletLogLevelToTwo': + safe.logLevel = 2; + break; + } + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; + } + return safe; +} +class JSONPath { + static create(query) { + const jsonp = new JSONPath(); + jsonp.compile(query); + return jsonp; + } + static toJSON(obj, stringifier, ...args) { + return (stringifier || JSON.stringify)(obj, ...args) + .replace(/\//g, '\\/'); + } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; + get value() { + return this.#compiled && this.#compiled.rval; + } + set value(v) { + if ( this.#compiled === undefined ) { return; } + this.#compiled.rval = v; + } + get valid() { + return this.#compiled !== undefined; + } + compile(query) { + this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } + const r = this.#compile(query, 0); + if ( r === undefined ) { return; } + if ( r.i !== query.length ) { + let val; + if ( query.startsWith('=', r.i) ) { + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; + } else { + val = query.slice(r.i+1); + } + } else if ( query.startsWith('+=', r.i) ) { + r.modify = '+'; + val = query.slice(r.i+2); + } + try { r.rval = JSON.parse(val); } + catch { return; } + } + r.v2 = v2; + this.#compiled = r; + } + evaluate(root) { + if ( this.valid === false ) { return []; } + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + this.#root = null; + return paths; + } + apply(root) { + if ( this.valid === false ) { return; } + const { rval } = this.#compiled; + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + let i = paths.length + if ( i === 0 ) { this.#root = null; return; } + while ( i-- ) { + const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } + if ( rval !== undefined ) { + this.#modifyVal(obj, key); + } else if ( Array.isArray(obj) && typeof key === 'number' ) { + obj.splice(key, 1); + } else { + delete obj[key]; + } + } + const result = this.#root['$'] ?? null; + this.#root = null; + return result; + } + dump() { + return JSON.stringify(this.#compiled); + } + toJSON(obj, ...args) { + return JSONPath.toJSON(obj, null, ...args) + } + get [Symbol.toStringTag]() { + return 'JSONPath'; + } + #UNDEFINED = 0; + #ROOT = 1; + #CURRENT = 2; + #CHILDREN = 3; + #DESCENDANTS = 4; + #QUANTIFIER = 5; + #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; + #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; + #root; + #compiled; + #compile(query, i) { + if ( query.length === 0 ) { return; } + const steps = []; + let c = query.charCodeAt(i); + if ( c === 0x24 /* $ */ ) { + steps.push({ mv: this.#ROOT }); + i += 1; + } else if ( c === 0x40 /* @ */ ) { + steps.push({ mv: this.#CURRENT }); + i += 1; + } else { + steps.push({ mv: i === 0 ? this.#ROOT : this.#CURRENT }); + } + let mv = this.#UNDEFINED; + for (;;) { + if ( i === query.length ) { break; } + c = query.charCodeAt(i); + if ( c === 0x20 /* whitespace */ ) { + i += 1; + continue; + } + // Dot accessor syntax + if ( c === 0x2E /* . */ ) { + if ( mv !== this.#UNDEFINED ) { return; } + if ( query.startsWith('..', i) ) { + mv = this.#DESCENDANTS; + i += 2; + } else { + mv = this.#CHILDREN; + i += 1; + } + continue; + } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } + if ( c !== 0x5B /* [ */ ) { + if ( mv === this.#UNDEFINED ) { + const step = steps.at(-1); + if ( step === undefined ) { return; } + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } + break; + } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; + mv = this.#UNDEFINED; + continue; + } + // Bracket accessor syntax + if ( query.startsWith('[?', i) ) { + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; + const r = this.#compile(query, j); + if ( r === undefined ) { return; } + if ( query.startsWith(']', r.i) === false ) { return; } + if ( not ) { r.steps.at(-1).not = true; } + steps.push({ mv: mv || this.#CHILDREN, steps: r.steps }); + i = r.i + 1; + mv = this.#UNDEFINED; + continue; + } + if ( query.startsWith('[*]', i) ) { + mv ||= this.#CHILDREN; + steps.push({ mv, k: '*' }); + i += 3; + mv = this.#UNDEFINED; + continue; + } + const r = this.#consumeIdentifier(query, i+1); + if ( r === undefined ) { return; } + mv ||= this.#CHILDREN; + steps.push({ mv, k: r.s }); + i = r.i + 1; + mv = this.#UNDEFINED; + } + if ( steps.length === 0 ) { return; } + if ( mv !== this.#UNDEFINED ) { return; } + return { steps, i }; + } + #evaluate(steps, pathin) { + let resultset = []; + if ( Array.isArray(steps) === false ) { return resultset; } + for ( const step of steps ) { + switch ( step.mv ) { + case this.#ROOT: + resultset = [ [ '$' ] ]; + break; + case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } + resultset = [ pathin ]; + break; + case this.#CHILDREN: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } + resultset = this.#getMatches(resultset, step); + break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } + default: + break; + } + } + return resultset; + } + #getMatches(listin, step) { + const listout = []; + for ( const pathin of listin ) { + const { value: owner } = this.#resolvePath(pathin); + if ( owner === undefined ) { continue; } + if ( step.steps ) { + this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } + } + } + return listout; + } + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; + } + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; + } + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); + } + return out; + } + return [ k ]; + } + #getMatchesFromExpr(pathin, step, owner, out) { + const recursive = step.mv === this.#DESCENDANTS; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; + const r = this.#evaluate(step.steps, q); + if ( Boolean(r?.length) === false ) { continue; } + out.push(q); + if ( v2 === false ) { break; } + } + } + #getDescendants(v, recursive) { + const iterator = { + next() { + const n = this.stack.length; + if ( n === 0 ) { + this.value = undefined; + this.done = true; + return this; + } + const details = this.stack[n-1]; + const entry = details.keys.next(); + if ( entry.done ) { + this.stack.pop(); + this.path.pop(); + return this.next(); + } + this.path[n-1] = entry.value; + this.value = { + obj: details.obj, + key: entry.value, + path: this.path.slice(), + }; + const v = this.value.obj[this.value.key]; + if ( recursive ) { + if ( Array.isArray(v) ) { + this.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); + } + } + return this; + }, + path: [], + value: undefined, + done: false, + stack: [], + [Symbol.iterator]() { return this; }, + }; + if ( Array.isArray(v) ) { + iterator.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); + } + return iterator; + } + #consumeIdentifier(query, i) { + const keys = []; + for (;;) { + const c0 = query.charCodeAt(i); + if ( c0 === 0x5D /* ] */ ) { break; } + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { + i += 1; + continue; + } + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + continue; + } + if ( c0 === 0x2D /* - */ || c0 >= 0x30 && c0 <= 0x39 ) { + const match = this.#reIndice.exec(query.slice(i)); + if ( match === null ) { return; } + const indice = parseInt(query.slice(i), 10); + keys.push(indice); + i += match[0].length; + continue; + } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + } + return { s: keys.length === 1 ? keys[0] : keys, i }; + } + #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } + const match = this.#reUnquotedIdentifier.exec(query.slice(i)); + if ( match === null ) { return; } + return { s: match[0], i: i + match[0].length }; + } + #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 === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + break; + } + if ( c === 0x5C /* \ */ && (end+1) < len ) { + const d = query.charCodeAt(end+1); + if ( d === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + beg = end; + } + } + end += 1; + } + return { s: parts.join(''), i: end }; + } + #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 JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } + 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; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } + } + return i + match[0].length - 1; + } + #resolvePath(path) { + if ( path.length === 0 ) { return { value: this.#root }; } + const key = path.at(-1); + let obj = this.#root + for ( let i = 0, n = path.length-1; i < n; i++ ) { + obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } + } + return { obj, key, value: obj[key] }; + } + #evaluateExpr(step, owner, k) { + if ( owner === undefined || owner === null ) { return; } + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); + if ( step.op !== undefined && hasOwn === false ) { return; } + const target = step.not !== true; + const v = owner[k]; + switch ( step.op ) { + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; + } + #modifyVal(obj, key) { + let { modify, rval } = this.#compiled; + if ( typeof rval === 'string' ) { + rval = rval.replace('${now}', `${Date.now()}`); + } + switch ( modify ) { + case undefined: + obj[key] = rval; + break; + case '+': { + if ( rval instanceof Object === false ) { return; } + const lval = obj[key]; + if ( lval instanceof Object === false ) { return; } + if ( Array.isArray(lval) ) { return; } + for ( const [ k, v ] of JSONPath.entries(rval) ) { + lval[k] = v; + } + break; + } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } + case 'repl': { + const lval = obj[key]; + if ( typeof lval !== 'string' ) { return; } + if ( this.#compiled.re === undefined ) { + this.#compiled.re = null; + try { + this.#compiled.re = rval.regex !== undefined + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } + } + if ( this.#compiled.re === null ) { return; } + obj[key] = lval.replace(this.#compiled.re, rval.replacement); + break; + } + default: + break; + } + } +} +function editObjectOnSetterFn( + trusted = false, + propChain = '', + jsonq = '', +) { + if ( propChain === '' ) { return; } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix( + `${trusted ? 'trusted-' : ''}edit-object-on-setter`, + propChain, + jsonq + ); + const jsonp = JSONPath.create(jsonq); + if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { + return safe.uboLog(logPrefix, 'Bad JSONPath query'); + } + const editObj = objBefore => { + const objAfter = jsonp.apply(objBefore); + if ( objAfter === undefined ) { return; } + safe.uboLog(logPrefix, 'Edited'); + if ( safe.logLevel <= 1 ) { return; } + safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); + }; + let currentValue = trapPropertyFn(propChain, { + get: function() { + return currentValue; + }, + set: function(a) { + currentValue = a; + if ( currentValue instanceof Object ) { + editObj(currentValue); + } + } + }); + if ( currentValue instanceof Object ) { + editObj(currentValue); + } +} +function trustedEditObjectOnSetter(...args) { + editObjectOnSetterFn(true, ...args); +}; +trustedEditObjectOnSetter(...args); +}, +}; + + +scriptlets['edit-element-object.js'] = { +aliases: [], + +requiresTrust: false, +func: function (scriptletGlobals = {}, ...args) { +function sleepFn(ms = 0) { + const nap = ( ) => { + return new Promise(resolve => { + self.requestAnimationFrame(resolve); + }); + } + const until = Date.now() + ms; + const sleep = async resolve => { + do { + await nap(); + } while ( Date.now() < until ); + resolve(); + }; + return new Promise(resolve => { sleep(resolve); }); +} +function safeSelf() { + if ( scriptletGlobals.safeSelf ) { + return scriptletGlobals.safeSelf; + } + const self = globalThis; + const safe = { + 'Array_from': Array.from, + 'Error': self.Error, + 'Function_toStringFn': self.Function.prototype.toString, + 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Math_floor': Math.floor, + 'Math_max': Math.max, + 'Math_min': Math.min, + 'Math_random': Math.random, + 'Object': Object, + 'Object_defineProperty': Object.defineProperty.bind(Object), + 'Object_defineProperties': Object.defineProperties.bind(Object), + 'Object_fromEntries': Object.fromEntries.bind(Object), + 'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object), + 'Object_hasOwn': Object.hasOwn.bind(Object), + 'Object_toString': Object.prototype.toString, + 'RegExp': self.RegExp, + 'RegExp_test': self.RegExp.prototype.test, + 'RegExp_exec': self.RegExp.prototype.exec, + 'Request_clone': self.Request.prototype.clone, + 'String': self.String, + 'String_fromCharCode': String.fromCharCode, + 'String_split': String.prototype.split, + 'XMLHttpRequest': self.XMLHttpRequest, + 'addEventListener': self.EventTarget.prototype.addEventListener, + 'removeEventListener': self.EventTarget.prototype.removeEventListener, + 'fetch': self.fetch, + 'JSON': self.JSON, + 'JSON_parseFn': self.JSON.parse, + 'JSON_stringifyFn': self.JSON.stringify, + 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), + 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + 'log': console.log.bind(console), + // Properties + logLevel: 0, + // Methods + makeLogPrefix(...args) { + return this.sendToLogger && `[${args.join(' \u205D ')}]` || ''; + }, + uboLog(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('info', ...args); + + }, + uboErr(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('error', ...args); + }, + escapeRegexChars(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + }, + initPattern(pattern, options = {}) { + if ( pattern === '' ) { + return { matchAll: true, expect: true }; + } + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + if ( expect === false ) { + pattern = pattern.slice(1); + } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match !== null ) { + return { + re: new this.RegExp( + match[1], + match[2] || options.flags + ), + expect, + }; + } + if ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; + } + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test.call(details.re, haystack) === details.expect; + } + return haystack.includes(details.pattern) === details.expect; + }, + patternToRegex(pattern, flags = undefined, verbatim = false) { + if ( pattern === '' ) { return /^/; } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match === null ) { + const reStr = this.escapeRegexChars(pattern); + return new RegExp(verbatim ? `^${reStr}$` : reStr, flags); + } + try { + return new RegExp(match[1], match[2] || undefined); + } + catch { + } + return /^/; + }, + getExtraArgs(args, offset = 0) { + const entries = args.slice(offset).reduce((out, v, i, a) => { + if ( (i & 1) === 0 ) { + const rawValue = a[i+1]; + const value = /^\d+$/.test(rawValue) + ? parseInt(rawValue, 10) + : rawValue; + out.push([ a[i], value ]); + } + return out; + }, []); + return this.Object_fromEntries(entries); + }, + }; + scriptletGlobals.safeSelf = safe; + if ( scriptletGlobals.bcSecret === undefined ) { return safe; } + // This is executed only when the logger is opened + safe.logLevel = scriptletGlobals.logLevel || 1; + let lastLogType = ''; + let lastLogText = ''; + let lastLogTime = 0; + safe.toLogText = (type, ...args) => { + if ( args.length === 0 ) { return; } + const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; + if ( text === lastLogText && type === lastLogType ) { + if ( (Date.now() - lastLogTime) < 5000 ) { return; } + } + lastLogType = type; + lastLogText = text; + lastLogTime = Date.now(); + return text; + }; + try { + const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); + let bcBuffer = []; + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + if ( bcBuffer === undefined ) { + return bc.postMessage({ what: 'messageToLogger', type, text }); + } + bcBuffer.push({ type, text }); + }; + bc.onmessage = ev => { + const msg = ev.data; + switch ( msg ) { + case 'iamready!': + if ( bcBuffer === undefined ) { break; } + bcBuffer.forEach(({ type, text }) => + bc.postMessage({ what: 'messageToLogger', type, text }) + ); + bcBuffer = undefined; + break; + case 'setScriptletLogLevelToOne': + safe.logLevel = 1; + break; + case 'setScriptletLogLevelToTwo': + safe.logLevel = 2; + break; + } + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; + } + return safe; +} +function onIdleFn(fn, options) { + if ( self.requestIdleCallback ) { + return self.requestIdleCallback(fn, options); + } + return self.requestAnimationFrame(fn); +} +function offIdleFn(id) { + if ( self.requestIdleCallback ) { + return self.cancelIdleCallback(id); + } + return self.cancelAnimationFrame(id); +} +function lookupElementsFn(directive, until = 0) { + if ( lookupElementsFn.querySelectorEx === undefined ) { + lookupElementsFn.getShadowRoot = elem => { + if ( elem.openOrClosedShadowRoot ) { // Firefox + return elem.openOrClosedShadowRoot; + } + if ( typeof chrome === 'object' ) { // Chromium + if ( chrome.dom && chrome.dom.openOrClosedShadowRoot ) { + return chrome.dom.openOrClosedShadowRoot(elem); + } + } + return elem.shadowRoot; + }; + lookupElementsFn.queryOrEvaluateSelector = (selector, context) => { + if ( selector.startsWith('xpath:') === false ) { + return Array.from(context.querySelectorAll(selector)); + } + const result = document.evaluate(selector.slice(6), context, null, 7, null); + const out = []; + if ( result.resultType === 7 ) { + for ( let i = 0; i < result.snapshotLength; i++ ) { + out[i] = result.snapshotItem(i); + } + } + return out; + } + lookupElementsFn.querySelectorEx = (selector, context = document) => { + const pos = selector.indexOf(' >>> '); + if ( pos === -1 ) { + return lookupElementsFn.queryOrEvaluateSelector(selector, context); + } + const outside = selector.slice(0, pos).trim(); + const inside = selector.slice(pos + 5).trim(); + const elems = lookupElementsFn.queryOrEvaluateSelector(outside, context); + const out = []; + for ( let i = 0; i < elems.length; i++ ) { + const shadowRoot = lookupElementsFn.getShadowRoot(elems[i]); + if ( Boolean(shadowRoot) === false ) { continue; } + lookupElementsFn.querySelectorEx(inside, shadowRoot).forEach(a => out.push(a)); + } + return out; + }; + lookupElementsFn.lookup = directive => { + const beVisible = directive.startsWith('when-visible:'); + const selector = beVisible ? directive.slice(13) : directive; + const elems = lookupElementsFn.querySelectorEx(selector); + if ( beVisible !== true ) { return elems; } + return elems.filter(a => a.checkVisibility({ + opacityProperty: true, + visibilityProperty: true, + })); + }; + lookupElementsFn.lookupAsync = details => { + const elems = lookupElementsFn.lookup(details.directive); + if ( elems.length || Date.now() >= details.until ) { + if ( details.observer ) { + details.observer.disconnect(); + details.observer = undefined; + } + if ( details.timer ) { + offIdleFn(details.timer); + details.timer = undefined; + } + return details.resolve(elems); + } + if ( details.observer === undefined ) { + details.observer = new MutationObserver(( ) => { + lookupElementsFn.lookupAsync(details); + }); + details.observer.observe(document, { + attributes: true, + childList: true, + subtree: true, + }); + } + if ( details.timer === undefined ) { + details.timer = onIdleFn(( ) => { + details.timer = undefined; + lookupElementsFn.lookupAsync(details); + }, { timeout: 151 }); + } + }; + } + if ( until === 0 ) { + return lookupElementsFn.lookup(directive); + } + return new Promise(resolve => { + lookupElementsFn.lookupAsync({ directive, until, resolve }); + }); +} +class JSONPath { + static create(query) { + const jsonp = new JSONPath(); + jsonp.compile(query); + return jsonp; + } + static toJSON(obj, stringifier, ...args) { + return (stringifier || JSON.stringify)(obj, ...args) + .replace(/\//g, '\\/'); + } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; + get value() { + return this.#compiled && this.#compiled.rval; + } + set value(v) { + if ( this.#compiled === undefined ) { return; } + this.#compiled.rval = v; + } + get valid() { + return this.#compiled !== undefined; + } + compile(query) { + this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } + const r = this.#compile(query, 0); + if ( r === undefined ) { return; } + if ( r.i !== query.length ) { + let val; + if ( query.startsWith('=', r.i) ) { + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; + } else { + val = query.slice(r.i+1); + } + } else if ( query.startsWith('+=', r.i) ) { + r.modify = '+'; + val = query.slice(r.i+2); + } + try { r.rval = JSON.parse(val); } + catch { return; } + } + r.v2 = v2; + this.#compiled = r; + } + evaluate(root) { + if ( this.valid === false ) { return []; } + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + this.#root = null; + return paths; + } + apply(root) { + if ( this.valid === false ) { return; } + const { rval } = this.#compiled; + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + let i = paths.length + if ( i === 0 ) { this.#root = null; return; } + while ( i-- ) { + const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } + if ( rval !== undefined ) { + this.#modifyVal(obj, key); + } else if ( Array.isArray(obj) && typeof key === 'number' ) { + obj.splice(key, 1); + } else { + delete obj[key]; + } + } + const result = this.#root['$'] ?? null; + this.#root = null; + return result; + } + dump() { + return JSON.stringify(this.#compiled); + } + toJSON(obj, ...args) { + return JSONPath.toJSON(obj, null, ...args) + } + get [Symbol.toStringTag]() { + return 'JSONPath'; + } + #UNDEFINED = 0; + #ROOT = 1; + #CURRENT = 2; + #CHILDREN = 3; + #DESCENDANTS = 4; + #QUANTIFIER = 5; + #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; + #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; + #root; + #compiled; + #compile(query, i) { + if ( query.length === 0 ) { return; } + const steps = []; + let c = query.charCodeAt(i); + if ( c === 0x24 /* $ */ ) { + steps.push({ mv: this.#ROOT }); + i += 1; + } else if ( c === 0x40 /* @ */ ) { + steps.push({ mv: this.#CURRENT }); + i += 1; + } else { + steps.push({ mv: i === 0 ? this.#ROOT : this.#CURRENT }); + } + let mv = this.#UNDEFINED; + for (;;) { + if ( i === query.length ) { break; } + c = query.charCodeAt(i); + if ( c === 0x20 /* whitespace */ ) { + i += 1; + continue; + } + // Dot accessor syntax + if ( c === 0x2E /* . */ ) { + if ( mv !== this.#UNDEFINED ) { return; } + if ( query.startsWith('..', i) ) { + mv = this.#DESCENDANTS; + i += 2; + } else { + mv = this.#CHILDREN; + i += 1; + } + continue; + } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } + if ( c !== 0x5B /* [ */ ) { + if ( mv === this.#UNDEFINED ) { + const step = steps.at(-1); + if ( step === undefined ) { return; } + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } + break; + } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; + mv = this.#UNDEFINED; + continue; + } + // Bracket accessor syntax + if ( query.startsWith('[?', i) ) { + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; + const r = this.#compile(query, j); + if ( r === undefined ) { return; } + if ( query.startsWith(']', r.i) === false ) { return; } + if ( not ) { r.steps.at(-1).not = true; } + steps.push({ mv: mv || this.#CHILDREN, steps: r.steps }); + i = r.i + 1; + mv = this.#UNDEFINED; + continue; + } + if ( query.startsWith('[*]', i) ) { + mv ||= this.#CHILDREN; + steps.push({ mv, k: '*' }); + i += 3; + mv = this.#UNDEFINED; + continue; + } + const r = this.#consumeIdentifier(query, i+1); + if ( r === undefined ) { return; } + mv ||= this.#CHILDREN; + steps.push({ mv, k: r.s }); + i = r.i + 1; + mv = this.#UNDEFINED; + } + if ( steps.length === 0 ) { return; } + if ( mv !== this.#UNDEFINED ) { return; } + return { steps, i }; + } + #evaluate(steps, pathin) { + let resultset = []; + if ( Array.isArray(steps) === false ) { return resultset; } + for ( const step of steps ) { + switch ( step.mv ) { + case this.#ROOT: + resultset = [ [ '$' ] ]; + break; + case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } + resultset = [ pathin ]; + break; + case this.#CHILDREN: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } + resultset = this.#getMatches(resultset, step); + break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } + default: + break; + } + } + return resultset; + } + #getMatches(listin, step) { + const listout = []; + for ( const pathin of listin ) { + const { value: owner } = this.#resolvePath(pathin); + if ( owner === undefined ) { continue; } + if ( step.steps ) { + this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } + } + } + return listout; + } + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; + } + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; + } + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); + } + return out; + } + return [ k ]; + } + #getMatchesFromExpr(pathin, step, owner, out) { + const recursive = step.mv === this.#DESCENDANTS; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; + const r = this.#evaluate(step.steps, q); + if ( Boolean(r?.length) === false ) { continue; } + out.push(q); + if ( v2 === false ) { break; } + } + } + #getDescendants(v, recursive) { + const iterator = { + next() { + const n = this.stack.length; + if ( n === 0 ) { + this.value = undefined; + this.done = true; + return this; + } + const details = this.stack[n-1]; + const entry = details.keys.next(); + if ( entry.done ) { + this.stack.pop(); + this.path.pop(); + return this.next(); + } + this.path[n-1] = entry.value; + this.value = { + obj: details.obj, + key: entry.value, + path: this.path.slice(), + }; + const v = this.value.obj[this.value.key]; + if ( recursive ) { + if ( Array.isArray(v) ) { + this.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); + } + } + return this; + }, + path: [], + value: undefined, + done: false, + stack: [], + [Symbol.iterator]() { return this; }, + }; + if ( Array.isArray(v) ) { + iterator.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); + } + return iterator; + } + #consumeIdentifier(query, i) { + const keys = []; + for (;;) { + const c0 = query.charCodeAt(i); + if ( c0 === 0x5D /* ] */ ) { break; } + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { + i += 1; + continue; + } + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + continue; + } + if ( c0 === 0x2D /* - */ || c0 >= 0x30 && c0 <= 0x39 ) { + const match = this.#reIndice.exec(query.slice(i)); + if ( match === null ) { return; } + const indice = parseInt(query.slice(i), 10); + keys.push(indice); + i += match[0].length; + continue; + } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; + } + return { s: keys.length === 1 ? keys[0] : keys, i }; + } + #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } + const match = this.#reUnquotedIdentifier.exec(query.slice(i)); + if ( match === null ) { return; } + return { s: match[0], i: i + match[0].length }; + } + #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 === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + break; + } + if ( c === 0x5C /* \ */ && (end+1) < len ) { + const d = query.charCodeAt(end+1); + if ( d === targetCharCode ) { + parts.push(query.slice(beg, end)); + end += 1; + beg = end; + } + } + end += 1; + } + return { s: parts.join(''), i: end }; + } + #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 JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } + 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; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } + } + return i + match[0].length - 1; + } + #resolvePath(path) { + if ( path.length === 0 ) { return { value: this.#root }; } + const key = path.at(-1); + let obj = this.#root + for ( let i = 0, n = path.length-1; i < n; i++ ) { + obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } + } + return { obj, key, value: obj[key] }; + } + #evaluateExpr(step, owner, k) { + if ( owner === undefined || owner === null ) { return; } + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); + if ( step.op !== undefined && hasOwn === false ) { return; } + const target = step.not !== true; + const v = owner[k]; + switch ( step.op ) { + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; + } + #modifyVal(obj, key) { + let { modify, rval } = this.#compiled; + if ( typeof rval === 'string' ) { + rval = rval.replace('${now}', `${Date.now()}`); + } + switch ( modify ) { + case undefined: + obj[key] = rval; + break; + case '+': { + if ( rval instanceof Object === false ) { return; } + const lval = obj[key]; + if ( lval instanceof Object === false ) { return; } + if ( Array.isArray(lval) ) { return; } + for ( const [ k, v ] of JSONPath.entries(rval) ) { + lval[k] = v; + } + break; + } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } + case 'repl': { + const lval = obj[key]; + if ( typeof lval !== 'string' ) { return; } + if ( this.#compiled.re === undefined ) { + this.#compiled.re = null; + try { + this.#compiled.re = rval.regex !== undefined + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } + } + if ( this.#compiled.re === null ) { return; } + obj[key] = lval.replace(this.#compiled.re, rval.replacement); + break; + } + default: + break; + } + } +} +async function editElementObjectFn( + trusted = false, + selector = '', + jsonq = '', + timeout = '' +) { + if ( selector === '' ) { return; } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix( + `${trusted ? 'trusted-' : ''}edit-element-object`, + selector, jsonq, timeout + ); + const jsonp = JSONPath.create(jsonq); + if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { + return safe.uboLog(logPrefix, 'Bad JSONPath query'); + } + const process = elems => { + for ( const elem of elems ) { + const r = jsonp.apply(elem); + if ( r === undefined ) { continue; } + safe.uboLog(logPrefix, 'Edited'); + } + }; + const until = timeout !== '' ? Date.now() + parseInt(timeout, 10) : 0; + do { + const r = lookupElementsFn(selector, until); + if ( Array.isArray(r) ) { + process(r); + } else if ( r instanceof Promise ) { + const elems = await r; + process(elems); + } + await sleepFn(); + } while ( Date.now() < until ); +} +function editElementObject(...args) { + editElementObjectFn(false, ...args); +}; +editElementObject(...args); +}, +}; + + +scriptlets['trusted-edit-element-object.js'] = { +aliases: [], + +requiresTrust: true, +func: function (scriptletGlobals = {}, ...args) { +function sleepFn(ms = 0) { + const nap = ( ) => { + return new Promise(resolve => { + self.requestAnimationFrame(resolve); + }); + } + const until = Date.now() + ms; + const sleep = async resolve => { + do { + await nap(); + } while ( Date.now() < until ); + resolve(); + }; + return new Promise(resolve => { sleep(resolve); }); +} +function safeSelf() { + if ( scriptletGlobals.safeSelf ) { + return scriptletGlobals.safeSelf; + } + const self = globalThis; + const safe = { + 'Array_from': Array.from, + 'Error': self.Error, + 'Function_toStringFn': self.Function.prototype.toString, + 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Math_floor': Math.floor, + 'Math_max': Math.max, + 'Math_min': Math.min, + 'Math_random': Math.random, + 'Object': Object, + 'Object_defineProperty': Object.defineProperty.bind(Object), + 'Object_defineProperties': Object.defineProperties.bind(Object), + 'Object_fromEntries': Object.fromEntries.bind(Object), + 'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object), + 'Object_hasOwn': Object.hasOwn.bind(Object), + 'Object_toString': Object.prototype.toString, + 'RegExp': self.RegExp, + 'RegExp_test': self.RegExp.prototype.test, + 'RegExp_exec': self.RegExp.prototype.exec, + 'Request_clone': self.Request.prototype.clone, + 'String': self.String, + 'String_fromCharCode': String.fromCharCode, + 'String_split': String.prototype.split, + 'XMLHttpRequest': self.XMLHttpRequest, + 'addEventListener': self.EventTarget.prototype.addEventListener, + 'removeEventListener': self.EventTarget.prototype.removeEventListener, + 'fetch': self.fetch, + 'JSON': self.JSON, + 'JSON_parseFn': self.JSON.parse, + 'JSON_stringifyFn': self.JSON.stringify, + 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), + 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + 'log': console.log.bind(console), + // Properties + logLevel: 0, + // Methods + makeLogPrefix(...args) { + return this.sendToLogger && `[${args.join(' \u205D ')}]` || ''; + }, + uboLog(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('info', ...args); + + }, + uboErr(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('error', ...args); + }, + escapeRegexChars(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + }, + initPattern(pattern, options = {}) { + if ( pattern === '' ) { + return { matchAll: true, expect: true }; + } + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + if ( expect === false ) { + pattern = pattern.slice(1); + } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match !== null ) { + return { + re: new this.RegExp( + match[1], + match[2] || options.flags + ), + expect, + }; + } + if ( options.flags !== undefined ) { + return { + re: new this.RegExp(this.escapeRegexChars(pattern), + options.flags + ), + expect, + }; + } + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test.call(details.re, haystack) === details.expect; + } + return haystack.includes(details.pattern) === details.expect; + }, + patternToRegex(pattern, flags = undefined, verbatim = false) { + if ( pattern === '' ) { return /^/; } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match === null ) { + const reStr = this.escapeRegexChars(pattern); + return new RegExp(verbatim ? `^${reStr}$` : reStr, flags); + } + try { + return new RegExp(match[1], match[2] || undefined); + } + catch { + } + return /^/; + }, + getExtraArgs(args, offset = 0) { + const entries = args.slice(offset).reduce((out, v, i, a) => { + if ( (i & 1) === 0 ) { + const rawValue = a[i+1]; + const value = /^\d+$/.test(rawValue) + ? parseInt(rawValue, 10) + : rawValue; + out.push([ a[i], value ]); + } + return out; + }, []); + return this.Object_fromEntries(entries); + }, + }; + scriptletGlobals.safeSelf = safe; + if ( scriptletGlobals.bcSecret === undefined ) { return safe; } + // This is executed only when the logger is opened + safe.logLevel = scriptletGlobals.logLevel || 1; + let lastLogType = ''; + let lastLogText = ''; + let lastLogTime = 0; + safe.toLogText = (type, ...args) => { + if ( args.length === 0 ) { return; } + const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; + if ( text === lastLogText && type === lastLogType ) { + if ( (Date.now() - lastLogTime) < 5000 ) { return; } + } + lastLogType = type; + lastLogText = text; + lastLogTime = Date.now(); + return text; + }; + try { + const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); + let bcBuffer = []; + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + if ( bcBuffer === undefined ) { + return bc.postMessage({ what: 'messageToLogger', type, text }); + } + bcBuffer.push({ type, text }); + }; + bc.onmessage = ev => { + const msg = ev.data; + switch ( msg ) { + case 'iamready!': + if ( bcBuffer === undefined ) { break; } + bcBuffer.forEach(({ type, text }) => + bc.postMessage({ what: 'messageToLogger', type, text }) + ); + bcBuffer = undefined; + break; + case 'setScriptletLogLevelToOne': + safe.logLevel = 1; + break; + case 'setScriptletLogLevelToTwo': + safe.logLevel = 2; + break; + } + }; + bc.postMessage('areyouready?'); + } catch { + safe.sendToLogger = (type, ...args) => { + const text = safe.toLogText(type, ...args); + if ( text === undefined ) { return; } + safe.log(`uBO ${text}`); + }; + } + return safe; +} +function onIdleFn(fn, options) { + if ( self.requestIdleCallback ) { + return self.requestIdleCallback(fn, options); + } + return self.requestAnimationFrame(fn); +} +function offIdleFn(id) { + if ( self.requestIdleCallback ) { + return self.cancelIdleCallback(id); + } + return self.cancelAnimationFrame(id); +} +function lookupElementsFn(directive, until = 0) { + if ( lookupElementsFn.querySelectorEx === undefined ) { + lookupElementsFn.getShadowRoot = elem => { + if ( elem.openOrClosedShadowRoot ) { // Firefox + return elem.openOrClosedShadowRoot; + } + if ( typeof chrome === 'object' ) { // Chromium + if ( chrome.dom && chrome.dom.openOrClosedShadowRoot ) { + return chrome.dom.openOrClosedShadowRoot(elem); + } + } + return elem.shadowRoot; + }; + lookupElementsFn.queryOrEvaluateSelector = (selector, context) => { + if ( selector.startsWith('xpath:') === false ) { + return Array.from(context.querySelectorAll(selector)); + } + const result = document.evaluate(selector.slice(6), context, null, 7, null); + const out = []; + if ( result.resultType === 7 ) { + for ( let i = 0; i < result.snapshotLength; i++ ) { + out[i] = result.snapshotItem(i); + } + } + return out; + } + lookupElementsFn.querySelectorEx = (selector, context = document) => { + const pos = selector.indexOf(' >>> '); + if ( pos === -1 ) { + return lookupElementsFn.queryOrEvaluateSelector(selector, context); + } + const outside = selector.slice(0, pos).trim(); + const inside = selector.slice(pos + 5).trim(); + const elems = lookupElementsFn.queryOrEvaluateSelector(outside, context); + const out = []; + for ( let i = 0; i < elems.length; i++ ) { + const shadowRoot = lookupElementsFn.getShadowRoot(elems[i]); + if ( Boolean(shadowRoot) === false ) { continue; } + lookupElementsFn.querySelectorEx(inside, shadowRoot).forEach(a => out.push(a)); + } + return out; + }; + lookupElementsFn.lookup = directive => { + const beVisible = directive.startsWith('when-visible:'); + const selector = beVisible ? directive.slice(13) : directive; + const elems = lookupElementsFn.querySelectorEx(selector); + if ( beVisible !== true ) { return elems; } + return elems.filter(a => a.checkVisibility({ + opacityProperty: true, + visibilityProperty: true, + })); + }; + lookupElementsFn.lookupAsync = details => { + const elems = lookupElementsFn.lookup(details.directive); + if ( elems.length || Date.now() >= details.until ) { + if ( details.observer ) { + details.observer.disconnect(); + details.observer = undefined; + } + if ( details.timer ) { + offIdleFn(details.timer); + details.timer = undefined; + } + return details.resolve(elems); + } + if ( details.observer === undefined ) { + details.observer = new MutationObserver(( ) => { + lookupElementsFn.lookupAsync(details); + }); + details.observer.observe(document, { + attributes: true, + childList: true, + subtree: true, + }); + } + if ( details.timer === undefined ) { + details.timer = onIdleFn(( ) => { + details.timer = undefined; + lookupElementsFn.lookupAsync(details); + }, { timeout: 151 }); + } + }; + } + if ( until === 0 ) { + return lookupElementsFn.lookup(directive); + } + return new Promise(resolve => { + lookupElementsFn.lookupAsync({ directive, until, resolve }); + }); +} +class JSONPath { + static create(query) { + const jsonp = new JSONPath(); + jsonp.compile(query); + return jsonp; + } + static toJSON(obj, stringifier, ...args) { + return (stringifier || JSON.stringify)(obj, ...args) + .replace(/\//g, '\\/'); + } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; + get value() { + return this.#compiled && this.#compiled.rval; + } + set value(v) { + if ( this.#compiled === undefined ) { return; } + this.#compiled.rval = v; + } + get valid() { + return this.#compiled !== undefined; + } + compile(query) { + this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } + const r = this.#compile(query, 0); + if ( r === undefined ) { return; } + if ( r.i !== query.length ) { + let val; + if ( query.startsWith('=', r.i) ) { + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; + } else { + val = query.slice(r.i+1); + } + } else if ( query.startsWith('+=', r.i) ) { + r.modify = '+'; + val = query.slice(r.i+2); + } + try { r.rval = JSON.parse(val); } + catch { return; } + } + r.v2 = v2; + this.#compiled = r; + } + evaluate(root) { + if ( this.valid === false ) { return []; } + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + this.#root = null; + return paths; + } + apply(root) { + if ( this.valid === false ) { return; } + const { rval } = this.#compiled; + this.#root = { '$': root }; + const paths = this.#evaluate(this.#compiled.steps, []); + let i = paths.length + if ( i === 0 ) { this.#root = null; return; } + while ( i-- ) { + const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } + if ( rval !== undefined ) { + this.#modifyVal(obj, key); + } else if ( Array.isArray(obj) && typeof key === 'number' ) { + obj.splice(key, 1); + } else { + delete obj[key]; + } + } + const result = this.#root['$'] ?? null; + this.#root = null; + return result; + } + dump() { + return JSON.stringify(this.#compiled); + } + toJSON(obj, ...args) { + return JSONPath.toJSON(obj, null, ...args) + } + get [Symbol.toStringTag]() { + return 'JSONPath'; + } + #UNDEFINED = 0; + #ROOT = 1; + #CURRENT = 2; + #CHILDREN = 3; + #DESCENDANTS = 4; + #QUANTIFIER = 5; + #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; + #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; + #root; + #compiled; + #compile(query, i) { + if ( query.length === 0 ) { return; } + const steps = []; + let c = query.charCodeAt(i); + if ( c === 0x24 /* $ */ ) { + steps.push({ mv: this.#ROOT }); + i += 1; + } else if ( c === 0x40 /* @ */ ) { + steps.push({ mv: this.#CURRENT }); + i += 1; + } else { + steps.push({ mv: i === 0 ? this.#ROOT : this.#CURRENT }); + } + let mv = this.#UNDEFINED; + for (;;) { + if ( i === query.length ) { break; } + c = query.charCodeAt(i); + if ( c === 0x20 /* whitespace */ ) { + i += 1; + continue; + } + // Dot accessor syntax + if ( c === 0x2E /* . */ ) { + if ( mv !== this.#UNDEFINED ) { return; } + if ( query.startsWith('..', i) ) { + mv = this.#DESCENDANTS; + i += 2; + } else { + mv = this.#CHILDREN; + i += 1; + } + continue; + } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } + if ( c !== 0x5B /* [ */ ) { + if ( mv === this.#UNDEFINED ) { + const step = steps.at(-1); + if ( step === undefined ) { return; } + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } + break; + } + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -6040,12 +13215,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -6056,61 +13245,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; + } + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -6138,7 +13338,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -6152,7 +13352,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -6161,12 +13361,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -6180,17 +13380,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -6222,22 +13429,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -6245,34 +13457,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -6288,11 +13496,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -6300,10 +13520,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -6314,64 +13533,45 @@ class JSONPath { } } } -function editInboundObjectFn( +async function editElementObjectFn( trusted = false, - propChain = '', - argPosRaw = '', + selector = '', jsonq = '', + timeout = '' ) { - if ( propChain === '' ) { return; } + if ( selector === '' ) { return; } const safe = safeSelf(); const logPrefix = safe.makeLogPrefix( - `${trusted ? 'trusted-' : ''}edit-inbound-object`, - propChain, - jsonq + `${trusted ? 'trusted-' : ''}edit-element-object`, + selector, jsonq, timeout ); const jsonp = JSONPath.create(jsonq); if ( jsonp.valid === false || jsonp.value !== undefined && trusted !== true ) { return safe.uboLog(logPrefix, 'Bad JSONPath query'); } - const argPos = parseInt(argPosRaw, 10); - if ( isNaN(argPos) ) { return; } - const getArgPos = args => { - if ( Array.isArray(args) === false ) { return; } - if ( argPos >= 0 ) { - if ( args.length <= argPos ) { return; } - return argPos; - } - if ( args.length < -argPos ) { return; } - return args.length + argPos; - }; - const editObj = obj => { - let clone; - try { - clone = safe.JSON_parse(safe.JSON_stringify(obj)); - } catch { - } - if ( typeof clone !== 'object' || clone === null ) { return; } - const objAfter = jsonp.apply(clone); - if ( objAfter === undefined ) { return; } - safe.uboLog(logPrefix, 'Edited'); - if ( safe.logLevel > 1 ) { - safe.uboLog(logPrefix, `After edit:\n${safe.JSON_stringify(objAfter, null, 2)}`); + const process = elems => { + for ( const elem of elems ) { + const r = jsonp.apply(elem); + if ( r === undefined ) { continue; } + safe.uboLog(logPrefix, 'Edited'); } - return objAfter; }; - proxyApplyFn(propChain, function(context) { - const i = getArgPos(context.callArgs); - if ( i !== undefined ) { - const obj = editObj(context.callArgs[i]); - if ( obj ) { - context.callArgs[i] = obj; - } + const until = timeout !== '' ? Date.now() + parseInt(timeout, 10) : 0; + do { + const r = lookupElementsFn(selector, until); + if ( Array.isArray(r) ) { + process(r); + } else if ( r instanceof Promise ) { + const elems = await r; + process(elems); } - return context.reflect(); - }); + await sleepFn(); + } while ( Date.now() < until ); } -function trustedEditInboundObject(propChain = '', argPos = '', jsonq = '') { - editInboundObjectFn(true, propChain, argPos, jsonq); +function trustedEditElementObject(...args) { + editElementObjectFn(true, ...args); }; -trustedEditInboundObject(...args); +trustedEditElementObject(...args); }, }; @@ -6521,18 +13721,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -6620,6 +13808,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -6632,14 +13824,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -6650,11 +13845,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -6668,6 +13864,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -6694,9 +13891,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -6732,24 +13932,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -6786,12 +14016,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -6802,61 +14046,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -6884,7 +14139,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -6898,7 +14153,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -6907,12 +14162,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -6926,17 +14181,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -6968,22 +14230,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -6991,34 +14258,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -7034,11 +14297,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -7046,10 +14321,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -7280,18 +14554,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -7379,6 +14641,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -7391,14 +14657,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -7409,11 +14678,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -7427,6 +14697,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -7453,9 +14724,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -7491,24 +14765,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -7545,12 +14849,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -7561,61 +14879,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -7643,7 +14972,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -7657,7 +14986,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -7666,12 +14995,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -7685,17 +15014,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -7727,22 +15063,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -7750,34 +15091,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -7793,11 +15130,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -7805,10 +15154,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -8039,18 +15387,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -8138,6 +15474,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -8150,14 +15490,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -8168,11 +15511,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -8186,6 +15530,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -8212,9 +15557,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -8250,24 +15598,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -8304,12 +15682,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -8320,61 +15712,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -8402,7 +15805,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -8416,7 +15819,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -8425,12 +15828,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -8444,17 +15847,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -8486,22 +15896,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -8509,34 +15924,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -8552,11 +15963,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -8564,10 +15987,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -8781,18 +16203,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -8880,6 +16290,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -8892,14 +16306,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -8910,11 +16327,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -8928,6 +16346,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -8954,9 +16373,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -8992,24 +16414,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -9046,12 +16498,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -9062,61 +16528,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -9144,7 +16621,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -9158,7 +16635,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -9167,12 +16644,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -9186,17 +16663,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -9228,22 +16712,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -9251,34 +16740,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -9294,11 +16779,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -9306,10 +16803,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -9527,6 +17023,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -9539,14 +17039,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -9557,11 +17060,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -9575,6 +17079,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -9601,9 +17106,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -9639,24 +17147,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -9693,12 +17231,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -9709,61 +17261,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; + } + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -9791,7 +17354,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -9805,7 +17368,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -9814,12 +17377,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -9833,17 +17396,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -9875,22 +17445,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -9898,34 +17473,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -9941,11 +17512,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -9953,10 +17536,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -10087,18 +17669,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -10400,6 +17970,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -10412,14 +17986,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -10430,11 +18007,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -10448,6 +18026,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -10474,9 +18053,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -10512,24 +18094,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -10566,12 +18178,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -10582,61 +18208,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -10664,7 +18301,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -10678,7 +18315,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -10687,12 +18324,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -10706,17 +18343,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -10748,22 +18392,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -10771,34 +18420,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -10814,11 +18459,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -10826,10 +18483,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -10960,18 +18616,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -11273,6 +18917,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -11285,14 +18933,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -11303,11 +18954,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -11321,6 +18973,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -11347,9 +19000,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -11385,24 +19041,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -11439,12 +19125,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -11455,61 +19155,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -11537,7 +19248,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -11551,7 +19262,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -11560,12 +19271,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -11579,17 +19290,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -11621,22 +19339,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -11644,34 +19367,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -11687,11 +19406,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -11699,10 +19430,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -11833,18 +19563,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -12143,6 +19861,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -12155,14 +19877,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -12173,11 +19898,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -12191,6 +19917,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -12217,9 +19944,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -12255,24 +19985,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -12309,12 +20069,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -12325,61 +20099,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; + } + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -12407,7 +20192,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -12421,7 +20206,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -12430,12 +20215,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -12449,17 +20234,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -12491,22 +20283,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -12514,34 +20311,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -12557,11 +20350,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -12569,10 +20374,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -12703,18 +20507,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -13028,18 +20820,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -13130,6 +20910,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -13142,14 +20926,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -13160,11 +20947,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -13178,6 +20966,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -13204,9 +20993,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -13242,24 +21034,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -13296,12 +21118,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -13312,61 +21148,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; + } + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -13394,7 +21241,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -13408,7 +21255,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -13417,12 +21264,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -13436,17 +21283,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -13478,22 +21332,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -13501,34 +21360,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -13544,11 +21399,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -13556,10 +21423,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -13801,18 +21667,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -13903,6 +21757,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -13915,14 +21773,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -13933,11 +21794,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -13951,6 +21813,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -13977,9 +21840,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -14015,24 +21881,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -14069,12 +21965,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -14085,61 +21995,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -14167,7 +22088,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -14181,7 +22102,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -14190,12 +22111,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -14209,17 +22130,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -14251,22 +22179,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -14274,34 +22207,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -14317,11 +22246,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -14329,10 +22270,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -14581,6 +22521,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -14593,14 +22537,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -14611,11 +22558,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -14629,6 +22577,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -14655,9 +22604,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -14693,24 +22645,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -14747,12 +22729,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -14763,61 +22759,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; + } + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -14845,7 +22852,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -14859,7 +22866,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -14868,12 +22875,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -14887,17 +22894,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -14929,22 +22943,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -14952,34 +22971,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -14995,11 +23010,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -15007,10 +23034,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -15141,18 +23167,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -15481,6 +23495,10 @@ class JSONPath { return (stringifier || JSON.stringify)(obj, ...args) .replace(/\//g, '\\/'); } + static keys = Object.keys; + static entries = Object.entries; + static hasOwn = Object.hasOwn; + static Regex = RegExp; get value() { return this.#compiled && this.#compiled.rval; } @@ -15493,14 +23511,17 @@ class JSONPath { } compile(query) { this.#compiled = undefined; + const v2 = query.startsWith('v2:'); + if ( v2 ) { query = query.slice(3); } const r = this.#compile(query, 0); if ( r === undefined ) { return; } if ( r.i !== query.length ) { let val; if ( query.startsWith('=', r.i) ) { - if ( /^=repl\(.+\)$/.test(query.slice(r.i)) ) { - r.modify = 'repl'; - val = query.slice(r.i+6, -1); + const match = this.#reRval.exec(query.slice(r.i)); + if ( match ) { + r.modify = match[1]; + val = match[2]; } else { val = query.slice(r.i+1); } @@ -15511,11 +23532,12 @@ class JSONPath { try { r.rval = JSON.parse(val); } catch { return; } } + r.v2 = v2; this.#compiled = r; } evaluate(root) { if ( this.valid === false ) { return []; } - this.#root = root; + this.#root = { '$': root }; const paths = this.#evaluate(this.#compiled.steps, []); this.#root = null; return paths; @@ -15529,6 +23551,7 @@ class JSONPath { if ( i === 0 ) { this.#root = null; return; } while ( i-- ) { const { obj, key } = this.#resolvePath(paths[i]); + if ( obj === undefined ) { continue; } if ( rval !== undefined ) { this.#modifyVal(obj, key); } else if ( Array.isArray(obj) && typeof key === 'number' ) { @@ -15555,9 +23578,12 @@ class JSONPath { #CURRENT = 2; #CHILDREN = 3; #DESCENDANTS = 4; + #QUANTIFIER = 5; #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; - #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\]/; + #reExpr = /^\s*([!=^$*]=|[<>]=?)\s*(.+?)\]/; #reIndice = /^-?\d+/; + #reRval = /^=([a-z]+)\((.+)\)$/; + #reQuantifier = /^\{(\d+|\d+,\d+|\d+,|,\d+)\};\$/; #root; #compiled; #compile(query, i) { @@ -15593,24 +23619,54 @@ class JSONPath { } continue; } + if ( c === 0x3B /* ; */ ) { + if ( query.startsWith(';$', i) === false ) { return; } + steps.push( + { mv: this.#QUANTIFIER, min: 1, max: 1e6 }, + { mv: this.#ROOT } + ); + i += 2; + mv = this.#UNDEFINED; + continue; + } + if ( c === 0x7B /* { */ ) { + const match = this.#reQuantifier.exec(query.slice(i)); + if ( match === null ) { return; } + const comma = match[1].indexOf(','); + let min, max; + if ( comma === -1 ) { + min = max = parseInt(match[1]); + } else { + min = parseInt(match[1].slice(0, comma)) || 0; + max = parseInt(match[1].slice(comma+1)) || 1e6; + } + steps.push( + { mv: this.#QUANTIFIER, min, max }, + { mv: this.#ROOT } + ); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } if ( c !== 0x5B /* [ */ ) { if ( mv === this.#UNDEFINED ) { const step = steps.at(-1); if ( step === undefined ) { return; } - i = this.#compileExpr(query, step, i); + const j = this.#compileExpr(query, step, i); + if ( j ) { i = j; } break; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - steps.push({ mv, k: s }); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + steps.push({ mv, k: r.s }); + i = r.i; mv = this.#UNDEFINED; continue; } // Bracket accessor syntax if ( query.startsWith('[?', i) ) { - const not = query.charCodeAt(i+2) === 0x21 /* ! */; - const j = i + 2 + (not ? 1 : 0); + const not = query.charCodeAt(i+2) === 0x21 /* ! */ ? 1 : 0; + const j = i + 2 + not; const r = this.#compile(query, j); if ( r === undefined ) { return; } if ( query.startsWith(']', r.i) === false ) { return; } @@ -15647,12 +23703,26 @@ class JSONPath { resultset = [ [ '$' ] ]; break; case this.#CURRENT: + if ( step.op ) { + const { obj, key } = this.#resolvePath(pathin); + if ( obj === undefined ) { return []; } + const outcome = this.#evaluateExpr(step, obj, key); + if ( outcome !== true ) { break; } + } resultset = [ pathin ]; break; case this.#CHILDREN: - case this.#DESCENDANTS: + case this.#DESCENDANTS: { + if ( resultset.length === 0 ) { break; } resultset = this.#getMatches(resultset, step); break; + } + case this.#QUANTIFIER: { + const { length } = resultset; + if ( length < step.min || length > step.max ) { return []; } + resultset = []; + break; + } default: break; } @@ -15663,61 +23733,72 @@ class JSONPath { const listout = []; for ( const pathin of listin ) { const { value: owner } = this.#resolvePath(pathin); - if ( step.k === '*' ) { - this.#getMatchesFromAll(pathin, step, owner, listout); - } else if ( step.k !== undefined ) { - this.#getMatchesFromKeys(pathin, step, owner, listout); - } else if ( step.steps ) { + if ( owner === undefined ) { continue; } + if ( step.steps ) { this.#getMatchesFromExpr(pathin, step, owner, listout); + continue; + } + const iter = this.#expandKey(owner, step.k); + if ( iter ) { + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, owner, k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, k ]); + } + } + if ( step.mv !== this.#DESCENDANTS ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { + const iter = this.#expandKey(obj[key], step.k); + if ( iter === undefined ) { continue; } + for ( const k of iter ) { + const outcome = this.#evaluateExpr(step, obj[key], k); + if ( outcome !== true ) { continue; } + listout.push([ ...pathin, ...path, k ]); + } } } return listout; } - #getMatchesFromAll(pathin, step, owner, out) { - const recursive = step.mv === this.#DESCENDANTS; - for ( const { path } of this.#getDescendants(owner, recursive) ) { - out.push([ ...pathin, ...path ]); + #expandKey(owner, k) { + if ( typeof owner !== 'object' ) { return; } + if ( Array.isArray(k) ) { + const out = []; + for ( const a of k ) { + const iter = this.#expandKey(owner, a); + if ( iter === undefined ) { continue; } + out.push(...iter); + } + return out; } - } - #getMatchesFromKeys(pathin, step, owner, out) { - const kk = Array.isArray(step.k) ? step.k : [ step.k ]; - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, owner, k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, normalized ]); + if ( typeof k === 'number' ) { + if ( Array.isArray(owner) === false ) { return; } + return [ k >= 0 ? k : owner.length + k ]; } - if ( step.mv !== this.#DESCENDANTS ) { return; } - for ( const { obj, key, path } of this.#getDescendants(owner, true) ) { - for ( const k of kk ) { - const normalized = this.#evaluateExpr(step, obj[key], k); - if ( normalized === undefined ) { continue; } - out.push([ ...pathin, ...path, normalized ]); + if ( k === '*' ) { + if ( Array.isArray(owner) ) { return owner.keys(); } + return JSONPath.keys(owner); + } + if ( k instanceof JSONPath.Regex ) { + const out = []; + for ( const key of JSONPath.keys(owner) ) { + if ( k.test(key) === false ) { continue; } + out.push(key); } + return out; } + return [ k ]; } #getMatchesFromExpr(pathin, step, owner, out) { const recursive = step.mv === this.#DESCENDANTS; - if ( Array.isArray(owner) === false ) { - const r = this.#evaluate(step.steps, pathin); - if ( r.length !== 0 ) { out.push(pathin); } - if ( recursive !== true ) { return; } - } - for ( const { obj, key, path } of this.#getDescendants(owner, recursive) ) { - if ( Array.isArray(obj[key]) ) { continue; } - const q = [ ...pathin, ...path ]; + const v2 = this.#compiled.v2 || recursive || Array.isArray(owner); + for ( const { path } of this.#getDescendants(owner, recursive) ) { + const q = v2 ? [ ...pathin, ...path ] : pathin; const r = this.#evaluate(step.steps, q); - if ( r.length === 0 ) { continue; } + if ( Boolean(r?.length) === false ) { continue; } out.push(q); + if ( v2 === false ) { break; } } } - #normalizeKey(owner, key) { - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) ) { - return key >= 0 ? key : owner.length + key; - } - } - return key; - } #getDescendants(v, recursive) { const iterator = { next() { @@ -15745,7 +23826,7 @@ class JSONPath { if ( Array.isArray(v) ) { this.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - this.stack.push({ obj: v, keys: Object.keys(v).values() }); + this.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } } return this; @@ -15759,7 +23840,7 @@ class JSONPath { if ( Array.isArray(v) ) { iterator.stack.push({ obj: v, keys: v.keys() }); } else if ( typeof v === 'object' && v !== null ) { - iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + iterator.stack.push({ obj: v, keys: JSONPath.keys(v).values() }); } return iterator; } @@ -15768,12 +23849,12 @@ class JSONPath { for (;;) { const c0 = query.charCodeAt(i); if ( c0 === 0x5D /* ] */ ) { break; } - if ( c0 === 0x2C /* , */ ) { + if ( c0 === 0x2C /* , */ || c0 === 0x20 /* SPACE */) { i += 1; continue; } - if ( c0 === 0x27 /* ' */ ) { - const r = this.#untilChar(query, 0x27 /* ' */, i+1) + if ( c0 === 0x22 /* " */ || c0 === 0x27 /* ' */ ) { + const r = this.#untilChar(query, c0, i+1); if ( r === undefined ) { return; } keys.push(r.s); i = r.i; @@ -15787,17 +23868,24 @@ class JSONPath { i += match[0].length; continue; } - const s = this.#consumeUnquotedIdentifier(query, i); - if ( s === undefined ) { return; } - keys.push(s); - i += s.length; + const r = this.#consumeUnquotedIdentifier(query, i); + if ( r === undefined ) { return; } + keys.push(r.s); + i = r.i; } return { s: keys.length === 1 ? keys[0] : keys, i }; } #consumeUnquotedIdentifier(query, i) { + if ( query.charCodeAt(i) === 0x2F /* / */ ) { + const r = this.#untilChar(query, 0x2F, i+1); + if ( r === undefined ) { return; } + let re; + try { re = new JSONPath.Regex(r.s); } catch { return; } + return { s: re, i: r.i }; + } const match = this.#reUnquotedIdentifier.exec(query.slice(i)); if ( match === null ) { return; } - return match[0]; + return { s: match[0], i: i + match[0].length }; } #untilChar(query, targetCharCode, i) { const len = query.length; @@ -15829,22 +23917,27 @@ class JSONPath { 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.rval = new JSONPath.Regex(r.s, match && match[0] || undefined); + } catch { return; } 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 { - step.rval = JSON.parse(match[2]); - step.op = match[1]; - } catch { + if ( match === null ) { return; } + const op = match[1], rval = match[2]; + if ( rval.charCodeAt(0) === 0x27 /* ' */ ) { + const r = this.#untilChar(rval, 0x27, 1); + if ( r === undefined ) { return; } + step.rval = r.s; + step.op = op; + } else { + try { + step.rval = JSON.parse(rval); + step.op = op; + } catch { return; } } - return i + match[1].length + match[2].length; + return i + match[0].length - 1; } #resolvePath(path) { if ( path.length === 0 ) { return { value: this.#root }; } @@ -15852,34 +23945,30 @@ class JSONPath { let obj = this.#root for ( let i = 0, n = path.length-1; i < n; i++ ) { obj = obj[path[i]]; + if ( obj instanceof Object === false ) { return {}; } } return { obj, key, value: obj[key] }; } - #evaluateExpr(step, owner, key) { + #evaluateExpr(step, owner, k) { if ( owner === undefined || owner === null ) { return; } - if ( typeof key === 'number' ) { - if ( Array.isArray(owner) === false ) { return; } - } - const k = this.#normalizeKey(owner, key); - const hasOwn = Object.hasOwn(owner, k); + const hasOwn = owner[k] !== undefined || JSONPath.hasOwn(owner, k); if ( step.op !== undefined && hasOwn === false ) { return; } const target = step.not !== true; const v = owner[k]; - let outcome = false; switch ( step.op ) { - case '==': outcome = (v === step.rval) === target; break; - case '!=': outcome = (v !== step.rval) === target; break; - case '<': outcome = (v < step.rval) === target; break; - case '<=': outcome = (v <= step.rval) === target; break; - case '>': outcome = (v > step.rval) === target; break; - case '>=': outcome = (v >= step.rval) === target; break; - 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; } + case '==': return (v === step.rval) === target; + case '!=': return (v !== step.rval) === target; + case '<': return (v < step.rval) === target; + case '<=': return (v <= step.rval) === target; + case '>': return (v > step.rval) === target; + case '>=': return (v >= step.rval) === target; + case '^=': return `${v}`.startsWith(step.rval) === target; + case '$=': return `${v}`.endsWith(step.rval) === target; + case '*=': return `${v}`.includes(step.rval) === target; + case 're': return step.rval.test(`${v}`); + default: break; + } + return hasOwn === target; } #modifyVal(obj, key) { let { modify, rval } = this.#compiled; @@ -15895,11 +23984,23 @@ class JSONPath { const lval = obj[key]; if ( lval instanceof Object === false ) { return; } if ( Array.isArray(lval) ) { return; } - for ( const [ k, v ] of Object.entries(rval) ) { + for ( const [ k, v ] of JSONPath.entries(rval) ) { lval[k] = v; } break; } + case 'call': { + const entries = rval.slice(); + if ( entries.length < 2 ) { break; } + entries.forEach((a, i, aa) => { + if ( a === '${obj}' ) { aa[i] = obj; } + else if ( a === '${key}' ) { aa[i] = key; } + else if ( a === '${val}' ) { aa[i] = obj[key]; } + }); + const instance = entries[0] ?? self; + instance[entries[1]](...entries.slice(2)); + break; + } case 'repl': { const lval = obj[key]; if ( typeof lval !== 'string' ) { return; } @@ -15907,10 +24008,9 @@ class JSONPath { this.#compiled.re = null; try { this.#compiled.re = rval.regex !== undefined - ? new RegExp(rval.regex, rval.flags) - : new RegExp(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - } catch { - } + ? new JSONPath.Regex(rval.regex, rval.flags) + : new JSONPath.Regex(rval.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + } catch { } } if ( this.#compiled.re === null ) { return; } obj[key] = lval.replace(this.#compiled.re, rval.replacement); @@ -16041,18 +24141,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -16379,18 +24467,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -16749,18 +24825,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -17239,18 +25303,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -17720,18 +25772,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -18254,18 +26294,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -18735,18 +26763,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -19194,18 +27210,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -19507,18 +27511,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -19822,18 +27814,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -20236,18 +28216,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -20661,18 +28629,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -21177,18 +29133,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -21494,18 +29438,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -21746,18 +29678,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -22004,18 +29924,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -22224,18 +30132,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -22575,18 +30471,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -22926,18 +30810,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -23376,18 +31248,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -23945,18 +31805,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -24431,18 +32279,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -24864,18 +32700,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -25297,18 +33121,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -25601,7 +33413,7 @@ function trustedReplaceArgument( replacer = ( ) => value; } const reCondition = extraArgs.condition - ? safe.patternToRegex(extraArgs.condition) + ? safe.patternToRegex(`${extraArgs.condition}`) : /^/; const getArg = context => { if ( argposRaw === 'this' ) { return context.thisArg; } @@ -25769,18 +33581,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -26163,18 +33963,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -26497,18 +34285,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -26834,18 +34610,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -27159,18 +34923,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -27410,18 +35162,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -27784,18 +35524,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -28092,18 +35820,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -28400,18 +36116,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -28708,18 +36412,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -28928,18 +36620,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -29255,18 +36935,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -29517,18 +37185,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -29757,18 +37413,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -29984,18 +37628,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -30211,18 +37843,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -30436,18 +38056,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -30534,6 +38142,12 @@ function runAt(fn, when) { const args = [ 'readystatechange', onStateChange, { capture: true } ]; safe.addEventListener.apply(document, args); } +function onIdleFn(fn, options) { + if ( self.requestIdleCallback ) { + return self.requestIdleCallback(fn, options); + } + return self.requestAnimationFrame(fn); +} function removeClass( rawToken = '', rawSelector = '', @@ -30580,7 +38194,7 @@ function removeClass( } } if ( skip ) { return; } - timer = safe.onIdle(rmclass, { timeout: 67 }); + timer = onIdleFn(rmclass, { timeout: 67 }); }; const observer = new MutationObserver(mutationHandler); const start = ( ) => { @@ -30726,18 +38340,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -30984,18 +38586,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -31364,18 +38954,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -31751,18 +39329,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -32089,18 +39655,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -32542,18 +40096,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -32798,18 +40340,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -33111,18 +40641,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -33326,7 +40844,7 @@ function proxyApplyFn( context[prop] = proxiedTarget; } function breakOnCall(target) { - proxyApplyFn(target, function fetch(context) { + proxyApplyFn(target, function(context) { debugger; // eslint-disable-line no-debugger return context.reflect(); }); @@ -33489,18 +41007,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -33842,18 +41348,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -34157,18 +41651,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -34336,6 +41818,108 @@ function runAtHtmlElementFn(fn) { }); observer.observe(document, { childList: true }); } +function onIdleFn(fn, options) { + if ( self.requestIdleCallback ) { + return self.requestIdleCallback(fn, options); + } + return self.requestAnimationFrame(fn); +} +function offIdleFn(id) { + if ( self.requestIdleCallback ) { + return self.cancelIdleCallback(id); + } + return self.cancelAnimationFrame(id); +} +function lookupElementsFn(directive, until = 0) { + if ( lookupElementsFn.querySelectorEx === undefined ) { + lookupElementsFn.getShadowRoot = elem => { + if ( elem.openOrClosedShadowRoot ) { // Firefox + return elem.openOrClosedShadowRoot; + } + if ( typeof chrome === 'object' ) { // Chromium + if ( chrome.dom && chrome.dom.openOrClosedShadowRoot ) { + return chrome.dom.openOrClosedShadowRoot(elem); + } + } + return elem.shadowRoot; + }; + lookupElementsFn.queryOrEvaluateSelector = (selector, context) => { + if ( selector.startsWith('xpath:') === false ) { + return Array.from(context.querySelectorAll(selector)); + } + const result = document.evaluate(selector.slice(6), context, null, 7, null); + const out = []; + if ( result.resultType === 7 ) { + for ( let i = 0; i < result.snapshotLength; i++ ) { + out[i] = result.snapshotItem(i); + } + } + return out; + } + lookupElementsFn.querySelectorEx = (selector, context = document) => { + const pos = selector.indexOf(' >>> '); + if ( pos === -1 ) { + return lookupElementsFn.queryOrEvaluateSelector(selector, context); + } + const outside = selector.slice(0, pos).trim(); + const inside = selector.slice(pos + 5).trim(); + const elems = lookupElementsFn.queryOrEvaluateSelector(outside, context); + const out = []; + for ( let i = 0; i < elems.length; i++ ) { + const shadowRoot = lookupElementsFn.getShadowRoot(elems[i]); + if ( Boolean(shadowRoot) === false ) { continue; } + lookupElementsFn.querySelectorEx(inside, shadowRoot).forEach(a => out.push(a)); + } + return out; + }; + lookupElementsFn.lookup = directive => { + const beVisible = directive.startsWith('when-visible:'); + const selector = beVisible ? directive.slice(13) : directive; + const elems = lookupElementsFn.querySelectorEx(selector); + if ( beVisible !== true ) { return elems; } + return elems.filter(a => a.checkVisibility({ + opacityProperty: true, + visibilityProperty: true, + })); + }; + lookupElementsFn.lookupAsync = details => { + const elems = lookupElementsFn.lookup(details.directive); + if ( elems.length || Date.now() >= details.until ) { + if ( details.observer ) { + details.observer.disconnect(); + details.observer = undefined; + } + if ( details.timer ) { + offIdleFn(details.timer); + details.timer = undefined; + } + return details.resolve(elems); + } + if ( details.observer === undefined ) { + details.observer = new MutationObserver(( ) => { + lookupElementsFn.lookupAsync(details); + }); + details.observer.observe(document, { + attributes: true, + childList: true, + subtree: true, + }); + } + if ( details.timer === undefined ) { + details.timer = onIdleFn(( ) => { + details.timer = undefined; + lookupElementsFn.lookupAsync(details); + }, { timeout: 151 }); + } + }; + } + if ( until === 0 ) { + return lookupElementsFn.lookup(directive); + } + return new Promise(resolve => { + lookupElementsFn.lookupAsync({ directive, until, resolve }); + }); +} function getAllLocalStorageFn(which = 'localStorage') { const storage = self[which]; const out = []; @@ -34466,18 +42050,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -34599,31 +42171,6 @@ function trustedClickElement( } } - const getShadowRoot = elem => { - // Firefox - if ( elem.openOrClosedShadowRoot ) { - return elem.openOrClosedShadowRoot; - } - // Chromium - if ( typeof chrome === 'object' ) { - if ( chrome.dom && chrome.dom.openOrClosedShadowRoot ) { - return chrome.dom.openOrClosedShadowRoot(elem); - } - } - return elem.shadowRoot; - }; - - const querySelectorEx = (selector, context = document) => { - const pos = selector.indexOf(' >>> '); - if ( pos === -1 ) { return context.querySelector(selector); } - const outside = selector.slice(0, pos).trim(); - const inside = selector.slice(pos + 5).trim(); - const elem = context.querySelector(outside); - if ( elem === null ) { return null; } - const shadowRoot = getShadowRoot(elem); - return shadowRoot && querySelectorEx(inside, shadowRoot); - }; - const steps = safe.String_split.call(selectors, /\s*,\s*/).map(a => { if ( /^\d+$/.test(a) ) { return parseInt(a, 10); } return a; @@ -34639,9 +42186,11 @@ function trustedClickElement( steps.unshift(clickDelay); } if ( typeof steps.at(-1) !== 'number' ) { - steps.push(10000); + steps.push(11000); } + const timeout = steps.pop(); + const waitForTime = ms => { return new Promise(resolve => { safe.uboLog(logPrefix, `Waiting for ${ms} ms`); @@ -34651,66 +42200,18 @@ function trustedClickElement( }, ms); }); }; - waitForTime.cancel = ( ) => { - const { timer } = waitForTime; - if ( timer === undefined ) { return; } - clearTimeout(timer); - waitForTime.timer = undefined; - }; - const waitForElement = selector => { - return new Promise(resolve => { - const elem = querySelectorEx(selector); - if ( elem !== null ) { - elem.click(); - resolve(); - return; - } - safe.uboLog(logPrefix, `Waiting for ${selector}`); - const observer = new MutationObserver(( ) => { - const elem = querySelectorEx(selector); - if ( elem === null ) { return; } - waitForElement.cancel(); - elem.click(); - resolve(); - }); - observer.observe(document, { - attributes: true, - childList: true, - subtree: true, - }); - waitForElement.observer = observer; + const waitForElement = directive => { + safe.uboLog(logPrefix, `Waiting for ${directive}`); + return lookupElementsFn(directive, Date.now() + timeout).then(elems => { + if ( elems.length === 0 ) { return false; } + elems[0].click(); + safe.uboLog(logPrefix, `Clicked ${directive}`); + return true; }); }; - waitForElement.cancel = ( ) => { - const { observer } = waitForElement; - if ( observer === undefined ) { return; } - waitForElement.observer = undefined; - observer.disconnect(); - }; - - const waitForTimeout = ms => { - waitForTimeout.cancel(); - waitForTimeout.timer = setTimeout(( ) => { - waitForTimeout.timer = undefined; - terminate(); - safe.uboLog(logPrefix, `Timed out after ${ms} ms`); - }, ms); - }; - waitForTimeout.cancel = ( ) => { - if ( waitForTimeout.timer === undefined ) { return; } - clearTimeout(waitForTimeout.timer); - waitForTimeout.timer = undefined; - }; - - const terminate = ( ) => { - waitForTime.cancel(); - waitForElement.cancel(); - waitForTimeout.cancel(); - }; const process = async ( ) => { - waitForTimeout(steps.pop()); while ( steps.length !== 0 ) { const step = steps.shift(); if ( step === undefined ) { break; } @@ -34720,10 +42221,11 @@ function trustedClickElement( continue; } if ( step.startsWith('!') ) { continue; } - await waitForElement(step); - safe.uboLog(logPrefix, `Clicked ${step}`); + const clicked = await waitForElement(step); + if ( clicked ) { continue; } + safe.uboLog(logPrefix, `Timed out waiting on ${step}`); + break; } - terminate(); }; runAtHtmlElementFn(process); @@ -34858,18 +42360,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -35289,18 +42779,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -35616,18 +43094,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; } @@ -36002,18 +43468,6 @@ function safeSelf() { }, []); return this.Object_fromEntries(entries); }, - onIdle(fn, options) { - if ( self.requestIdleCallback ) { - return self.requestIdleCallback(fn, options); - } - return self.requestAnimationFrame(fn); - }, - offIdle(id) { - if ( self.requestIdleCallback ) { - return self.cancelIdleCallback(id); - } - return self.cancelAnimationFrame(id); - } }; scriptletGlobals.safeSelf = safe; if ( scriptletGlobals.bcSecret === undefined ) { return safe; }