UNPKG

syphonx-core

Version:

SyphonX is a template-driven solution for extracting data from HTML in a highly efficient way. It combines the power of jQuery, Regular Expressions, and Javascript into a declarative template-driven format that extracts and reshapes HTML data into JSON.

1 lines 63.1 kB
var syphonx=function(exports){"use strict";const errorCodeMessageMap={"app-error":"An application defined error occured.","click-timeout":"Timeout waiting for click result.","click-required":"Could not find click target on the page.","error-limit":"Error limit exceeded.","eval-error":"Error evaluating formula.","fatal-error":"An unexpected error occurred.","invalid-select":"Invalid selector query found.","invalid-operator":"Invalid jQuery formula.","invalid-operand":"Invalid argument to a jQuery formula.","select-required":"Could not find target of a required selector on the page.","waitfor-timeout":"Timeout waiting for selector."};function parseBoolean(value){return"boolean"==typeof value?value:"string"==typeof value?""!==value&&"false"!==value.trim().toLowerCase()&&"0"!==value.trim():void 0}function parseNumber(value){if("number"==typeof value)return isNaN(value)?void 0:value;if("string"==typeof value){let[,text]=/([0-9.,]+)/.exec(value)||[];/\.\d+$/.test(text)&&(text=text.replace(/,/g,"")),/,\d+$/.test(text)&&(text=text.replace(/\./g,""));const result=parseFloat(text);return isNaN(result)?void 0:result}}function coerce(value,type){return"number"===type?parseNumber(value):"boolean"===type?parseBoolean(value):value}function coerceSelectValue(value,type,repeated){return repeated?value instanceof Array?value.map((v=>coerceSelectValue(v,type,!1))):[coerceSelectValue(value,type,!1)]:"string"===type?"string"==typeof value?value:"number"==typeof value||"boolean"==typeof value?value.toString():null:"number"===type?"number"==typeof value?value:"string"==typeof value?parseNumber(value):null:"boolean"===type?"boolean"==typeof value?value:"string"==typeof value?value.trim().length>0:"number"!=typeof value||isNaN(value)?null:0!==value:value}function isCoercibleTo(value,type){return"number"===type&&"string"==typeof value&&void 0!==parseNumber(value)||"boolean"===type&&"string"==typeof value&&void 0!==parseBoolean(value)}function collapseWhitespace(text,newlines=!0){return"string"==typeof text&&0===text.trim().length?null:"string"==typeof text&&newlines?text.replace(/\s*\n\s*/gm,"\n").replace(/\n{2,}/gm,"\n").replace(/\s{2,}/gm," ").trim():"string"!=typeof text||newlines?text:text.replace(/\n/gm," ").replace(/\s{2,}/gm," ").trim()}function cut(text,splitter,n,limit){if("string"==typeof text){const a=text.split(splitter,limit).map((value=>value.trim())).filter((value=>value.length>0)),i=n>=0?n:a.length+n;return i>=0&&i<a.length?a[i]:null}return null}function filterQueryResult($,result,predicate){if(result.value instanceof Array){const input={elements:result.nodes.toArray(),values:result.value},output={elements:[],values:[]},n=input.elements.length;for(let i=0;i<n;++i){predicate(input.values[i],i,input.values)&&(output.elements.push(input.elements[i]),output.values.push(input.values[i]))}result.nodes=$(output.elements),result.value=output.values}}function formatHTML(value){return"string"==typeof value?value.replace(/(<[a-z0-9:._-]+>)[ ]*/gi,"$1").replace(/[ ]*<\//g,"</").trim():value instanceof Array&&value.every((obj=>"string"==typeof obj))?value.map((obj=>formatHTML(obj))):value}function isEmpty(obj){return null==obj||(obj instanceof Array||"string"==typeof obj)&&0===obj.length}function isFormula(value){return"string"==typeof value&&value.startsWith("{")&&value.endsWith("}")}function isRegexp(value){return"string"==typeof value&&(value.startsWith("/")||value.startsWith("!/"))}function isJQueryObject(obj){return!("object"!=typeof obj||null===obj||!obj.jquery&&!obj.cheerio)}function isObject$1(obj){return!("object"!=typeof obj||null===obj||obj instanceof Array||obj instanceof Date)}function isNullOrUndefined(obj){return null==obj}function formatStringValue(value,format,origin){return"href"!==format||"string"!=typeof value||!origin||((url=value).startsWith("http://")||url.startsWith("https://"))?"multiline"===format?collapseWhitespace(value,!0):"singleline"===format?collapseWhitespace(value,!1):value:(base=origin,(relative=value)?base?new URL(relative,base).toString():relative:base);var base,relative,url}function tryParseJson(value){try{return JSON.parse(value)}catch(err){return}}function merge(source,target){if(source instanceof Array&&target instanceof Array)return[...source,...target];if(isObject$1(source)&&isObject$1(target)){const obj={},keys=Array.from(new Set([...Object.keys(source),...Object.keys(target)]));for(const key of keys)obj[key]=merge(source[key],target[key]);return obj}return target||source}function mergeElements(source,target){for(const targetAttr of Array.from(target[0].attributes)){const sourceAttr=Array.from(source[0].attributes).find((attr=>attr.name===targetAttr.name));if(sourceAttr&&"class"===targetAttr.name){const value=Array.from(new Set([...sourceAttr.value.split(" "),...targetAttr.value.split(" ")])).join(" ");source.attr("class",value)}else sourceAttr||source.attr(targetAttr.name,targetAttr.value)}}function createRegExp(value){if("string"==typeof value&&value.startsWith("/")){const i=value.lastIndexOf("/"),pattern=value.substring(1,i),flags=value.length>i?value.substring(i+1):"m";return new RegExp(pattern,flags)}}function regexpExtract(text,regexp){if("string"!=typeof text)return null;if("string"==typeof regexp&&!(regexp=createRegExp(regexp)))return null;const match=regexp.exec(text);return match&&match[1]?match[1]:null}function regexpExtractAll(text,regexp){if("string"!=typeof text)return null;if("string"==typeof regexp&&(regexp.endsWith("/")&&(regexp+="g"),!(regexp=createRegExp(regexp))))return null;regexp.global||(regexp=new RegExp(regexp.source,regexp.flags+"g"));const matches=Array.from(text.matchAll(regexp)||[]),result=[];for(const match of matches)match[1]&&result.push(match[1]);return result.length>0?result:null}function regexpReplace(text,regexp,replace){return"string"==typeof text?text.replace(regexp,replace):text}function waitForScrollEnd(){return new Promise((resolve=>{let timer=setTimeout((()=>resolve()),3e3);addEventListener("scroll",(function onScroll(){clearTimeout(timer),timer=setTimeout((()=>{removeEventListener("scroll",onScroll),resolve()}),200)}))}))}function selectorStatement(query){if(!(query instanceof Array&&query.length>0&&"string"==typeof query[0]&&query.slice(1).every((op=>op instanceof Array))))return`INVALID: ${JSON.stringify(query)}`;return[`$("${query[0]}")`,...query.slice(1).map((op=>`${op[0]}(${op.slice(1).map((param=>JSON.stringify(param))).join(", ")})`))].join(".")}function selectorStatements(query){return query&&query.length>0?`${selectorStatement(query[0])}${query.length>1?` (+${query.length-1} more))`:""}`:"(none)"}function sleep(ms){return new Promise((resolve=>setTimeout(resolve,ms)))}class Timer{t0;constructor(){this.t0=Date.now()}elapsed(){return Date.now()-this.t0}}function trim(text,pattern=" "){return function(text,pattern=" "){if("string"==typeof text)if("string"==typeof pattern)for(;text.startsWith(pattern);)text=text.slice(pattern.length);else{const hits=pattern.exec(text)||[];let hit=hits.find((hit=>text.startsWith(hit)));for(;hit;)text=text.slice(hit.length),hit=hits.find((hit=>text.startsWith(hit)))}return text}(function(text,pattern=" "){if("string"==typeof text)if("string"==typeof pattern)for(;text.endsWith(pattern);)text=text.slice(0,-1*pattern.length);else{const hits=pattern.exec(text)||[];let hit=hits.find((hit=>text.endsWith(hit)));for(;hit;)text=text.slice(0,-1*hit.length),hit=hits.find((hit=>text.endsWith(hit)))}return text}(text,pattern))}function trunc(obj,max=300){if(obj){const text=JSON.stringify(obj);if("string"==typeof text)return text.length<=max?text:`${text[0]}${text.slice(1,max)}…${text[text.length-1]}`}return"(empty)"}function typeName(obj){return null===obj?"null":void 0===obj?"undefined":"string"==typeof obj?"string":"number"==typeof obj?"number":obj instanceof Array?obj.length>0?`Array<${Array.from(new Set(obj.map((value=>typeName(value))))).join("|")}>`:"[]":obj instanceof Date?"date":"object"==typeof obj?"object":"unknown"}function unpatch(keys){const iframe=document.createElement("iframe");if(iframe.style.display="none",document.body.appendChild(iframe),iframe.contentWindow){const contentWindow=iframe.contentWindow;for(const key of keys)if(key.includes(".")){const parts=key.split("."),objectType=parts.slice(0,-1).join("."),method=parts[parts.length-1];let obj=contentWindow;for(const part of objectType.split("."))obj=obj[part];if(obj){const descriptor=Object.getOwnPropertyDescriptor(obj,method);if(descriptor){let targetObj=window;for(const part of objectType.split("."))targetObj=targetObj[part];Object.defineProperty(targetObj,method,descriptor)}}}else{if(contentWindow[key]){const descriptor=Object.getOwnPropertyDescriptor(contentWindow,key);descriptor&&Object.defineProperty(window,key,descriptor)}}}document.body.removeChild(iframe)}function unwrap(obj){if(function(obj){return isObject(obj)&&obj.hasOwnProperty("value")&&obj.hasOwnProperty("nodes")}(obj))return unwrap(obj.value);if(isObject(obj)){const source=obj,target={},keys=Object.keys(obj);for(const key of keys)target[key]=unwrap(source[key]);return target}return obj instanceof Array?obj.map((item=>unwrap(item))):obj}function isObject(obj){return!("object"!=typeof obj||null===obj||obj instanceof Array||obj instanceof Date)}class Controller{jquery;state;online;lastLogLine="";lastLogLength=0;lastLogTimestamp=0;lastStep=[];step=[];constructor(state){this.jquery=state.root||$,this.online="function"==typeof this.jquery.noConflict,this.online&&(state.url=window.location.href);const{domain:domain,origin:origin}=function(url){if("string"==typeof url&&/^https?:\/\//.test(url)){const[protocol,,host]=url.split("/"),a=host.split(":")[0].split(".").reverse();return{domain:a.length>=3&&2===a[0].length&&2===a[1].length?`${a[2]}.${a[1]}.${a[0]}`:a.length>=2?`${a[1]}.${a[0]}`:void 0,origin:protocol&&host?`${protocol}//${host}`:void 0}}return{}}(state.url);state.__locator&&(state.data=merge(state.data,state.__locator),state.__locator=void 0);if(this.state={params:{},errors:[],log:"",...state,yield:void 0,vars:{__instance:0,__context:[],__metrics:{},__repeat:{},__t0:Date.now(),__timeout:state.timeout||30,...state.vars,__step:[],__yield:state.yield?.step},domain:domain,origin:origin,version:"1.2.78"},this.state.vars.__metrics={actions:0,clicks:0,elapsed:0,errors:0,navigate:0,queries:0,renavigations:0,retries:0,skipped:0,snooze:0,steps:0,timeouts:0,waitfor:0,yields:0,...state.vars?.__metrics},this.state.context){const nodes=(0,this.jquery)(this.state.context),value=this.text(nodes);this.state.vars.__context=[{name:"context",nodes:nodes,value:value}]}}appendError(code,message,level,stack){const key=this.contextKey();this.state.errors.push({code:code,message:message,key:key,level:level,stack:stack});const text=`ERROR ${key?`${key}: `:""}${message} code=${code} level=${level}${stack?`\n${stack}`:""}`;this.log(text)}break({name:name="",query:query,on:on="any",pattern:pattern,when:when}){if(name&&(name=" "+name),this.online)if(this.when(when,"BREAK")){if(this.state.vars.__metrics.steps+=1,!query)return this.log(`BREAK${name} ${when||""}`),!0;{this.log(`BREAK${name} WAITFOR QUERY ${trunc($)} on=${on}, pattern=${pattern}`);const[pass,result]=this.queryCheck(query,on,pattern);if(this.log(`BREAK${name} QUERY ${selectorStatements(query)} -> ${trunc(result?.value)}${pattern?` (valid=${result?.valid})`:""} -> on=${on} -> ${pass}`),pass)return this.log(`BREAK${name} ${when||""}`),!0}}else this.state.vars.__metrics.skipped+=1,this.log(`BREAK${name} SKIPPED ${when}`);else this.log(`BREAK${name?` ${name}`:""} BYPASSED ${when}`);return!1}async click({name:name,query:query,waitfor:waitfor,snooze:snooze,required:required,retry:retry,when:when,...options}){if(this.online)if(this.when(when,"CLICK"+(name?` ${name}`:""))){const mode=snooze?snooze[2]||"before":void 0;if(snooze&&("before"===mode||"before-and-after"===mode)){const duration=this.maxTimeout(snooze[0]);this.log(`CLICK${name?` ${name}`:""} SNOOZE BEFORE (${duration.toFixed(1)}s) ${selectorStatements(query)}`),await sleep(duration),this.state.vars.__metrics.snooze+=duration}const result=this.query({query:query});if(!(result&&result.nodes.length>0))return required&&this.appendError("click-required",`Required click target not found. ${trunc(query)}`,1),"not-found";if(this.clickElement(result.nodes[0],selectorStatements(query))){if(waitfor){const code=await this.waitfor(waitfor,"CLICK");if(code){if("timeout"===code)return this.appendError("click-timeout",`Timeout waiting for CLICK${name?` ${name}`:""} result. ${trunc(waitfor.query)}${waitfor.pattern?`, pattern=${waitfor.pattern}`:""}`,1),"timeout"}else if(snooze&&("after"===mode||"before-and-after"===mode)){const duration=this.maxTimeout(snooze[0]);this.log(`CLICK${name?` ${name}`:""} SNOOZE AFTER (${duration.toFixed(1)}s) ${selectorStatements(query)}`),await sleep(duration),this.state.vars.__metrics.snooze+=duration}}else options.yield&&this.yield({name:"CLICK "+(name?` ${name}`:""),params:{click:{},waitUntil:options.waitUntil}});this.state.vars.__metrics.clicks+=1,this.state.vars.__metrics.steps+=1}}else this.state.vars.__metrics.skipped+=1,this.log(`CLICK${name?` ${name}`:""} SKIPPED ${selectorStatements(query)}`);else this.log(`CLICK${name?` ${name}`:""} IGNORED ${selectorStatements(query)}`);return null}clickElement(element,context){return element instanceof HTMLElement?(element instanceof HTMLOptionElement&&element.parentElement instanceof HTMLSelectElement?(this.log(`CLICK ${context} <select> "${element.parentElement.value}" -> "${element.value}"`),element.parentElement.value=element.value,element.parentElement.dispatchEvent(new Event("change",{bubbles:!0,cancelable:!1})),element.parentElement.dispatchEvent(new Event("input",{bubbles:!0,cancelable:!1}))):(this.log(`CLICK ${context}`),element.click()),!0):(this.log(`CLICK ${context} not insanceof HTMLElement`),!1)}context(){const stack=this.state.vars.__context;let j=stack.length-1;const context={...stack[j]};let subcontext=context;for(;--j>=0;)subcontext.parent={...stack[j]},subcontext=subcontext.parent;return context}contextKey(){const stack=this.state.vars.__context;let key="";for(const{name:name,index:index}of stack)name&&(key&&(key+="."),key+=name,void 0!==index&&(key+=`[${index}]`));return key}contextKeyInfo(){const key=this.contextKey(),stack=this.state.vars.__context;let info="";if(stack.length>0){const[top]=stack.slice(-1);void 0!==top.pivot?info=`PIVOT(${top.pivot})`:void 0!==top.union?info=`UNION(${top.union})`:void 0!==top.action&&(info=`${top.action.toUpperCase()}`)}return info?`${key} ${info}`:key}async dispatch(action){if(action.hasOwnProperty("select")){const select=action.select;if("timeout"===await this.selectWaitfor(select))return"timeout";const data=this.select(select);this.state.data=merge(this.state.data,data)}else if(action.hasOwnProperty("break")){if(this.break(action.break))return"break"}else if(action.hasOwnProperty("click")){const required=action.click.required,code=await this.click(action.click);if("timeout"===code&&required)return"timeout";if("not-found"===code&&required)return"required"}else if(action.hasOwnProperty("each"))await this.each(action.each);else if(action.hasOwnProperty("error"))this.error(action.error);else if(action.hasOwnProperty("goback"))await this.goback(action.goback);else if(action.hasOwnProperty("keypress"))this.keypress(action.keypress);else if(action.hasOwnProperty("locator"))await this.locator(action.locator);else if(action.hasOwnProperty("navigate"))await this.navigate(action.navigate);else if(action.hasOwnProperty("reload"))await this.reload(action.reload);else if(action.hasOwnProperty("repeat"))await this.repeat(action.repeat);else if(action.hasOwnProperty("screenshot"))await this.screenshot(action.screenshot);else if(action.hasOwnProperty("scroll"))await this.scroll(action.scroll);else if(action.hasOwnProperty("snooze"))await this.snooze(action.snooze);else if(action.hasOwnProperty("switch"))await this.switch(action.switch);else if(action.hasOwnProperty("transform"))this.transform(action.transform);else if(action.hasOwnProperty("waitfor")){const required=action.waitfor.required;if("timeout"===await this.waitfor(action.waitfor)&&required)return"timeout"}else action.hasOwnProperty("yield")&&this.yield(action.yield||{});return null}async each({name:name,query:query,actions:actions,context:context,when:when}){const $=this.jquery,label="EACH"+(name?` ${name}`:"");if(this.when(when,label)){const result=this.query({query:query,repeated:!0});if(result&&result.nodes.length>0){const elements=result.nodes.toArray();for(const element of elements){const nodes=$(element);this.pushContext({nodes:nodes,value:this.text(nodes),action:"each",index:elements.indexOf(element)},context);const code=await this.run(actions,label,!0);if(this.popContext(),"break"===code)break}}this.state.vars.__metrics.steps+=1}else this.state.vars.__metrics.skipped+=1}eachNode({nodes:nodes,value:value},callback){const $=this.jquery;if(nodes){const elements=nodes.toArray();for(let i=0;i<elements.length;++i){callback($(elements[i]),value instanceof Array?value[i]:value)}}}elapsed(){return Date.now()-this.state.vars.__t0}error({query:query,code:code="app-error",message:message,level:level=1,negate:negate,stop:stop,when:when}){if(message||(message=errorCodeMessageMap[code]||"Unknown error."),query){const result=this.query({query:query,type:"boolean",repeated:!1});if((negate?!0===result?.value:!1===result?.value)&&(this.appendError(code,String(this.evaluate(message)),level),this.state.vars.__metrics.steps+=1,!0===stop||void 0===stop&&0===level))throw"STOP"}else if(this.when(when,"ERROR")){if(this.appendError(code,String(this.evaluate(message)),level),this.state.vars.__metrics.steps+=1,!0===stop||void 0===stop&&0===level)throw"STOP"}else this.state.vars.__metrics.skipped+=1,this.log(`ERROR ${code} SKIPPED ${when}`)}evaluate(input,params={}){if(!isFormula(input)){if(isRegexp(input)){return regexpExtract(params.value,input)}return input}{const{data:data,...extra}=params,context=this.context(),args={...this.state.vars,...this.state,...context,data:unwrap(merge(this.state.data,data)),...extra};try{return function(formula,scope={}){const keys=Object.keys(scope),values=keys.map((key=>scope[key]));return new Function(...keys,`return ${formula}`)(...values)}(input.slice(1,-1).trim(),args)}catch(err){this.appendError("eval-error",`Error evaluating formula "${input}": ${err instanceof Error?err.message:JSON.stringify(err)}`,0)}}}evaluateBoolean(input,params={}){if(null!=input){if(isRegexp(input)){const result=function(text,pattern){const negate=pattern.startsWith("!");negate&&(pattern=pattern.slice(1));const regexp=createRegExp(pattern);if(!regexp)return null;let result=regexp?.test(text);return void 0===result?null:negate?!result:result}(params.value,input);return null!==result?result:void 0}{const result=this.evaluate(input,params);if("boolean"==typeof result)return result}}}evaluateNumber(input,params={}){if(null!=input){const result=this.evaluate(input,params);if("number"==typeof result)return result}}evaluateString(input,params={}){if(null!=input){const result=this.evaluate(input,params);if("string"==typeof result)return result}}formatResult(result,type,all,limit,format="multiline",pattern,distinct,negate,removeNulls){const $=this.jquery,regexp=createRegExp(pattern);if(result.raw)return result;if(!type){const defaultType=result.value instanceof Array?typeof result.value[0]:typeof result.value;type=["string","number","boolean"].includes(defaultType)?defaultType:"string"}return null!=limit&&result.value instanceof Array&&(result.nodes=$(result.nodes.toArray().slice(0,limit)),result.value=result.value.slice(0,limit)),"string"===type&&result.value instanceof Array?(result.formatted||(result.value=result.value.map((value=>formatStringValue(coerceSelectValue(value,"string"),format,this.state.origin)))),regexp&&!isEmpty(result.value)&&(result.valid=result.value.every((value=>regexp.test(value))))):"string"===type?(result.formatted||(result.value=formatStringValue(coerceSelectValue(result.value,"string"),format,this.state.origin)),regexp&&!isEmpty(result.value)&&(result.valid=regexp.test(result.value))):"boolean"===type&&result.value instanceof Array&&0===result.value.length?result.value=!1:"boolean"===type&&result.value instanceof Array&&all?result.value=result.value.every((value=>!0===coerceSelectValue(value,"boolean"))):"boolean"===type&&result.value instanceof Array&&!all?result.value=result.value.some((value=>!0===coerceSelectValue(value,"boolean"))):"boolean"===type?result.value=coerceSelectValue(result.value,"boolean"):"number"===type&&result.value instanceof Array?result.value=result.value.map((value=>coerceSelectValue(value,"number"))):"number"===type&&(result.value=coerceSelectValue(result.value,"number")),negate&&("boolean"==typeof result.value?result.value=!result.value:result.value instanceof Array&&result.value.every((value=>"boolean"==typeof value))&&(result.value=result.value.map((value=>!value)))),removeNulls&&result.value instanceof Array&&filterQueryResult($,result,(value=>null!==value)),distinct&&result.value instanceof Array&&filterQueryResult($,result,((value,index,array)=>array.indexOf(value)===index)),result}goback({name:name,when:when}){this.yield({name:"GOBACK "+(name?` ${name}`:""),params:{goback:{},action:"goBack"},when:when})}keypress({name:name="",key:key,shift:shift,control:control,alt:alt,when:when}){if(name&&(name=" "+name),this.online)if(this.when(when,"KEYPRESS")){const event=new KeyboardEvent("keydown",{key:key,code:"Key"+key.toUpperCase(),keyCode:key.charCodeAt(0),which:key.charCodeAt(0),shiftKey:shift,ctrlKey:control,altKey:alt});document.dispatchEvent(event)}else this.state.vars.__metrics.skipped+=1,this.log(`KEYPRESS${name} SKIPPED ${when}`);else this.log(`KEYPRESS${name?` ${name}`:""} BYPASSED ${when}`)}log(text){if(this.state.debug)if(this.lastLogLine===text){const elapsed=(Date.now()-this.lastLogTimestamp)/1e3;this.state.log=`${this.state.log.slice(0,this.lastLogLength)}${text} (${elapsed.toFixed(1)}s)\n`}else this.lastLogLine=text,this.lastLogLength=this.state.log.length,this.lastLogTimestamp=Date.now(),this.state.log+=`${String(this.elapsed()).padStart(8,"0")} ${text}\n`}maxTimeout(seconds=30){const elapsed=this.elapsed()/1e3,remaining=Math.max(this.state.vars.__timeout-elapsed,0);return Math.min(seconds,remaining)}mergeQueryResult(source,target){const $=this.jquery;if(source&&target){const nodes=source.nodes&&target.nodes?$([...source.nodes.toArray(),...target.nodes.toArray()]):target.nodes||source.nodes;let value;return value=source.value instanceof Array&&target.value instanceof Array?[...source.value,...target.value]:source.value instanceof Array&&!isNullOrUndefined(target.value)?[...source.value,target.value]:target.value instanceof Array&&!isNullOrUndefined(source.value)?[source.value,...target.value]:isNullOrUndefined(source.value)||isNullOrUndefined(target.value)?target.value??source.value:[source.value,target.value],{nodes:nodes,key:this.contextKey(),value:value,valid:target.valid??source.valid}}return target||source}nodeKey(node){const $=this.jquery,path=[],elements=$(node&&node.length>1?node[0]:node).parents().addBack().not("html").toArray().reverse();for(const element of elements){const $parent=$(element).parent(),tag=element.tagName?.toLowerCase(),id=$(element).attr("id")||"",className=$(element).attr("class")?.split(" ")[0]||"",n=$(element).index()+1,uniqueId=!!/^[A-Za-z0-9_-]+$/.test(id)&&1===$(`#${id}`).length,uniqueClassName=!(!tag||!/^[A-Za-z0-9_-]+$/.test(className))&&1===$(`${tag}.${className}`).length,onlyTag=1===$parent.children(tag).length,onlyClassName=!(!tag||!/^[A-Za-z0-9_-]+$/.test(className))&&1===$parent.children(`${tag}.${className}`).length;if(uniqueId?path.push(`#${id}`):uniqueClassName?path.push(`${tag}.${className}`):onlyTag?path.push(tag):onlyClassName?path.push(`${tag}.${className}`):path.push(`${tag}:nth-child(${n})`),uniqueId||uniqueClassName)break}return path.reverse().join(" > ")}nodeKeys(nodes){return"object"==typeof nodes&&"function"==typeof nodes.toArray?nodes.toArray().map((node=>this.nodeKey(node))):[]}pokeContext(context){const stack=this.state.vars.__context,i=stack.length-1;i>=0&&(stack[i]={...stack[i],...context}),context.nodes&&this.log(`--\x3e ${this.contextKeyInfo()} [${this.nodeKey(stack[stack.length-1].nodes)}] ${trunc(stack[stack.length-1].value)}`)}popContext(){const stack=this.state.vars.__context,[top]=stack.slice(-1);this.log(`<<< ${this.contextKeyInfo()} [${this.nodeKey(top?.nodes)}] ${stack.length-1}`),stack.pop()}pushContext(context,inherit){const stack=this.state.vars.__context;if(void 0===inherit){const[parent]=stack.slice(-1);stack.push({nodes:parent?.nodes,value:parent?.value,...context})}else if(null===inherit)stack.push({...context,nodes:void 0});else if(inherit>0&&inherit<=stack.length){const[parent]=stack.slice(-1*inherit);stack.push({...context,nodes:parent?.nodes,value:parent?.value})}else stack.push(context),this.appendError("eval-error",`Undefined context #${inherit}`,1);this.log(`>>> ${this.contextKeyInfo()} [${this.nodeKey(stack[stack.length-1].nodes)}] ${trunc(stack[stack.length-1].value)} ${stack.length}`)}query({query:query,type:type,repeated:repeated=!1,all:all=!1,format:format,pattern:pattern,limit:limit,hits:hits,distinct:distinct,negate:negate,removeNulls:removeNulls}){if(query instanceof Array&&query.every((stage=>stage instanceof Array))&&query[0].length>0&&query[0][0]){void 0!==limit||"string"!==type||repeated||all||(limit=1),null==hits&&(hits=query.length);let result,hit=0;for(const stage of query){const subresult=this.resolveQuery({query:stage,type:type,repeated:repeated,all:all,limit:limit,format:format,pattern:pattern,distinct:distinct,negate:negate,removeNulls:removeNulls,result:result});if(subresult&&(result=this.mergeQueryResult(result,subresult),subresult.nodes.length>0)){if(!all){this.log(`[${query.indexOf(stage)+1}/${query.length}] STOP (first hit)`);break}if(++hit===hits){this.log(`[${query.indexOf(stage)+1}/${query.length}] STOP (${hits} hits)`);break}}}return result&&!result.raw&&(repeated&&!Array.isArray(result.value)?result.value=[result.value]:!repeated&&Array.isArray(result.value)&&result.value.every((value=>"string"==typeof value))?result.value=result.value.length>0?result.value.join("singleline"===format?" ":"\n"):null:!repeated&&"boolean"===type&&!negate&&Array.isArray(result.value)&&result.value.every((value=>"boolean"==typeof value))?result.value=result.value.some((value=>!0===value)):!repeated&&"boolean"===type&&negate&&Array.isArray(result.value)&&result.value.every((value=>"boolean"==typeof value))?result.value=!result.value.some((value=>!1===value)):!repeated&&Array.isArray(result.value)&&(result.value=result.value[0])),result}}queryCheck(query,on,pattern){const type=pattern?"string":"boolean",all="all"===on,result=this.query({query:query,type:type,pattern:pattern,all:all,repeated:all});let pass=!1;return result&&("boolean"===type?"any"===on?pass=!0===result.value:"all"===on?pass=result.value.every((value=>!0===value)):"none"===on&&(pass=!1===result.value):"string"===type&&("any"===on?pass=!isEmpty(trim(result.value))&&!1!==result.valid:"all"===on?pass=result.value.every((value=>!isEmpty(trim(value))))&&!1!==result.valid:"none"===on&&(pass=!(!isEmpty(trim(result.value))&&!1!==result.valid)))),[pass,result]}reload({name:name,waitUntil:waitUntil,when:when}){this.yield({name:"RELOAD "+(name?` ${name}`:""),params:{reload:{},action:"reload",waitUntil:waitUntil},when:when})}async repeat({name:name="",actions:actions,limit:limit,errors:errors=1,when:when}){if(name&&(name=" "+name),void 0===(limit=this.evaluateNumber(limit))&&(limit=1),this.when(when,`REPEAT${name}`)){const state=this.acquireRepeatState();let errorOffset=0;for(;state.index<limit;){const label=`REPEAT${name} #${++state.index}`;this.log(`${label} (limit=${limit})`);if(await this.run(actions,label,!0))break;if(this.log(`${label} -> ${actions.length} steps completed`),errorOffset=this.state.errors.length-state.errors,errorOffset>=errors){this.appendError("error-limit",`${errorOffset} errors in repeat (error limit of ${errors} exceeded)`,1);break}}this.clearRepeatState(),this.state.vars.__metrics.steps+=1,this.log(`REPEAT${name} ${state.index} iterations completed (limit=${limit}, errors=${errorOffset}/${errors})`)}else this.state.vars.__metrics.skipped+=1,this.log(`REPEAT${name} SKIPPED ${when}`)}acquireRepeatState(){const depth=this.state.vars.__step.length;return this.state.vars.__repeat[depth]||(this.state.vars.__repeat[depth]={index:0,errors:this.state.errors.length}),this.state.vars.__repeat[depth]}clearRepeatState(){const depth=this.state.vars.__step.length;this.state.vars.__repeat[depth]=void 0}locator(locators){const activeLocators=locators.filter((locator=>this.when(locator.when,"LOCATOR"+(locator.name?` ${locator.name}`:""))));if(activeLocators.length>0){const[locator]=activeLocators;this.yield({name:`LOCATOR ${locator.name?` ${locator.name}`:""}${activeLocators.length>1?` (+${activeLocators.length-1} more)`:""}`,params:{locators:activeLocators.map((locator=>({name:this.evaluateString(locator.name)||"_value",selector:this.evaluateString(locator.selector),method:this.evaluateString(locator.method),params:locator.params?.map((param=>this.evaluate(param))),frame:this.evaluateString(locator.frame),promote:locator.promote,chain:locator.chain}))),action:"locator",name:locator.name,frame:locator.frame,selector:locator.selector,promote:locator.promote,method:locator.method,arg0:locator.params?locator.params[0]:void 0}})}}navigate({name:name,url:url,waitUntil:waitUntil,when:when}){this.yield({name:`NAVIGATE ${name?` ${name}`:""} ${url}`,params:{navigate:{url:this.evaluateString(url)},action:"navigate",url:url,waitUntil:waitUntil},when:when})}resolveOperands(operands,result){for(let i=0;i<operands.length;++i)(isFormula(operands[i])||isRegexp(operands[i]))&&this.eachNode(result,((node,value)=>{const resolvedValue=String(this.evaluate(operands[i],{value:value}));resolvedValue!==operands[i]&&(operands[i]=resolvedValue)}))}resolveQuery({query:query,type:type,repeated:repeated,all:all,limit:limit,format:format,pattern:pattern,distinct:distinct,negate:negate,removeNulls:removeNulls,result:result}){if(!(query instanceof Array))return void this.appendError("eval-error","Invalid selector query, query is not an array",0);const $=this.jquery;let selector=query[0];const ops=query.slice(1),context=this.context();let nodes,value;if("string"!=typeof selector)nodes=$(),value=null,this.appendError("eval-error","Invalid selector query, first element is not a string",0);else if("."===selector&&context)nodes=$(context.nodes),value=context.value,this.state.vars.__metrics.queries+=1,this.log(`QUERY $(".", [${this.nodeKey(context.nodes)}]) -> ${trunc(value)} (${nodes.length} nodes)`);else if(".."===selector&&context?.parent)nodes=$(context.parent.nodes),value=context.parent.value,this.state.vars.__metrics.queries+=1,this.log(`QUERY $("..", [${this.nodeKey(context.nodes)}]) -> ${trunc(value)} (${nodes.length} nodes)`);else if(selector.startsWith("^")){let n=parseInt(selector.slice(1)),subcontext=n>0?context:void 0;for(;subcontext&&n-- >=0;)subcontext=context.parent;if(!subcontext)return void this.appendError("eval-error",`Invalid context for selector "${selector}"`,0);nodes=$(subcontext.nodes),value=subcontext.value,this.state.vars.__metrics.queries+=1,this.log(`QUERY $(${selector}, [${this.nodeKey(context.nodes)}]) -> ${trunc(value)} (${nodes.length} nodes)`)}else if("{window}"===selector)nodes=this.online?$(window):$(),value=null,this.state.vars.__metrics.queries+=1;else if("{document}"===selector)nodes=this.online?$(document):$(),value=null,this.state.vars.__metrics.queries+=1;else if(selector.startsWith("/")||selector.toLowerCase().startsWith("xpath:")){if(!this.online)return void this.appendError("eval-error","XPATH selectors are only valid online",0);const eval_result=function(xpath,nodes){const result={nodes:[],value:[]};for(const node of nodes){const eval_result=document.evaluate(xpath,node,null,XPathResult.ANY_TYPE,null);if(eval_result.resultType===XPathResult.STRING_TYPE)result.nodes.push(node),result.value.push(eval_result.stringValue);else if(eval_result.resultType===XPathResult.NUMBER_TYPE)result.nodes.push(node),result.value.push(eval_result.numberValue);else if(eval_result.resultType===XPathResult.BOOLEAN_TYPE)result.nodes.push(node),result.value.push(eval_result.booleanValue);else{if(eval_result.resultType===XPathResult.UNORDERED_NODE_ITERATOR_TYPE){let node=eval_result.iterateNext();for(;node;)result.nodes.push(node),result.value.push(node.textContent),node=eval_result.iterateNext();return result}if(eval_result.resultType===XPathResult.ORDERED_NODE_ITERATOR_TYPE){const result={nodes:[],value:[]};let node=eval_result.iterateNext();for(;node;)result.nodes.push(node),result.value.push(node.textContent||null),node=eval_result.iterateNext();return result}}}return result}(selector.toLowerCase().startsWith("xpath:")?selector.slice(6):selector,context.nodes?context.nodes.toArray():[document]);nodes=$(eval_result.nodes),value=eval_result.value}else try{const _selector=String(this.evaluate(selector));nodes=this.resolveQueryNodes($(_selector,context?.nodes),result?.nodes),value=this.text(nodes,format),selector!==_selector&&this.log(`EVALUATE "${selector}" >>> "${_selector}"`),this.state.vars.__metrics.queries+=1,this.log(`QUERY $("${_selector}", [${this.nodeKey(context.nodes)}]) -> ${trunc(value)} (${nodes.length} nodes)`)}catch(err){return void this.appendError("eval-error",`Failed to resolve selector for "${selectorStatement(query)}": ${err instanceof Error?err.message:JSON.stringify(err)}`,0)}if(!(ops.length>0&&nodes.length>0)){if("boolean"===type){let value=repeated?[nodes.length>0]:nodes.length>0;return negate&&(value=!value),{nodes:nodes,key:this.contextKey(),value:value}}return nodes.length>0?this.formatResult({nodes:nodes,key:this.contextKey(),value:value},type,all,limit,format,pattern,distinct,negate,removeNulls):void 0}try{return this.resolveQueryOps({ops:ops,nodes:nodes,type:type,repeated:repeated,all:all,limit:limit,format:format,pattern:pattern,distinct:distinct,negate:negate,removeNulls:removeNulls,value:value})}catch(err){return void this.appendError("eval-error",`Failed to resolve operation for "${selectorStatement(query)}": ${err instanceof Error?err.message:JSON.stringify(err)}`,0)}}resolveQueryNodes(target,result){const $=this.jquery;if(result){const source=result.toArray();return $(target.toArray().filter((node=>!source.includes(node))))}return target}resolveQueryOps({ops:ops,nodes:nodes,type:type,repeated:repeated,all:all,limit:limit,format:format,pattern:pattern,distinct:distinct,negate:negate,removeNulls:removeNulls,value:value}){const $=this.jquery,result={nodes:nodes,key:this.contextKey(),value:value};if(!this.validateOperators(ops))return result;const a=ops.slice(0);for(;a.length>0;){const[operator,...operands]=a.shift();if("blank"===operator)result.nodes=$(result.nodes.toArray().filter((element=>0===$(element).text().trim().length))),result.value=this.text(result.nodes,format);else if("cut"===operator){if(!this.validateOperands(operator,operands,["string","number"],["number"]))break;const splitter=operands[0],n=operands[1],limit=operands[2];"string"==typeof result.value?result.value=cut(result.value,splitter,n,limit):result.value instanceof Array&&result.value.every((value=>"string"==typeof value))?result.value=result.value.map((value=>cut(value,splitter,n,limit))):result.value=null}else if("extract"===operator){if(!this.validateOperands(operator,operands,["string"]))break;const regexp=createRegExp(operands[0]);regexp?result.value instanceof Array&&result.value.every((value=>"string"==typeof value))?(result.value=result.value.map((value=>regexpExtract(value.trim(),regexp))),filterQueryResult($,result,(value=>null!==value))):"string"==typeof result.value?result.value=regexpExtract(result.value.trim(),regexp):result.value=null:this.appendError("invalid-operand",'Invalid regular expression for "extract"',0)}else if("extractAll"===operator){if(!this.validateOperands(operator,operands,["string"],["string"]))break;const regexp=createRegExp(operands[0]),delim=operands[1]??"\n";regexp?result.value instanceof Array&&result.value.every((value=>"string"==typeof value))?(result.value=result.value.map((value=>regexpExtractAll(value.trim(),regexp)?.join(delim)||null)),filterQueryResult($,result,(value=>null!==value))):"string"==typeof result.value?(result.value=regexpExtractAll(result.value.trim(),regexp),trim||(result.formatted=!0)):result.value=null:this.appendError("invalid-operand",'Invalid regular expression for "extractAll"',0)}else if("filter"===operator&&(isFormula(operands[0])||isRegexp(operands[0]))){if(!this.validateOperands(operator,operands,["string"]))break;if(result.value instanceof Array){const input={elements:result.nodes.toArray(),values:result.value},output={elements:[],values:[]},n=input.elements.length;for(let i=0;i<n;++i){this.evaluateBoolean(operands[0],{value:input.values[i],index:i,count:n})&&(output.elements.push(input.elements[i]),output.values.push(input.values[i]))}result.nodes=$(output.elements),result.value=output.values}else{this.evaluateBoolean(operands[0],{value:result.value})||(result.nodes=$([]),result.value=null)}}else if("html"!==operator||"outer"!==operands[0]&&void 0!==operands[0])if("html"===operator&&"inner"===operands[0])result.value=result.nodes.toArray().map((element=>$(element).html())),("boolean"!=typeof operands[1]||operands[1])&&(result.value=formatHTML(result.value),result.formatted=!0);else if("json"===operator){if(!this.validateOperands(operator,operands,[],["string"]))break;const formula=operands[0];if(formula&&!isFormula(formula)){this.appendError("invalid-operand",'Invalid formula for "json"',0);break}const input={elements:result.nodes.toArray(),values:result.value instanceof Array?result.value:new Array(result.nodes.length).fill(result.value)},output={elements:[],values:[]},n=input.elements.length;for(let i=0;i<n;++i){const json=tryParseJson(input.values[i]);if(void 0!==json){const value=formula?this.evaluate(formula,{value:json,index:i,count:n}):json;null!=value&&(output.elements.push(input.elements[i]),output.values.push(value))}}0===output.elements.length?(result.nodes=$([]),result.value=null):repeated||all?(result.nodes=$(output.elements),result.value=output.values):(result.nodes=$(output.elements[0]),result.value=output.values[0]),result.raw=!0}else if("map"===operator){if(!this.validateOperands(operator,operands,["string"]))break;const input={elements:result.nodes.toArray(),values:result.value instanceof Array?result.value:new Array(result.nodes.length).fill(result.value)},output={elements:[],values:[]},n=input.elements.length;for(let i=0;i<n;++i){const value=this.evaluate(operands[0],{value:input.values[i],index:i,count:n});null!=value&&(output.elements.push(input.elements[i]),output.values.push(value))}result.nodes=$(output.elements),result.value=output.values}else if("nonblank"===operator)result.nodes=$(result.nodes.toArray().filter((element=>$(element).text().trim().length>0))),result.value=this.text(result.nodes,format);else if("replace"===operator){if(!this.validateOperands(operator,operands,["string","string"]))break;const regexp=createRegExp(operands[0]);regexp?result.value instanceof Array&&result.value.every((value=>"string"==typeof value))?result.value=result.value.map((value=>regexpReplace(value,regexp,operands[1]))):"string"==typeof result.value&&(result.value=regexpReplace(result.value,regexp,operands[1])):this.appendError("invalid-operand",'Invalid regular expression for "replace"',0)}else if("replaceHTML"===operator){if(!this.validateOperands(operator,operands,["string"]))break;this.eachNode(result,((node,value)=>{const content=String(this.evaluate(operands[0],{value:value}));node.html(content)})),result.value=null}else if("replaceTag"===operator){if(!this.validateOperands(operator,operands,["string"],["boolean"]))break;const newTag=String(this.evaluate(operands[0],{value:operands[0]})),keepProps=operands[1]??!0;this.eachNode(result,(node=>{const newNode=$(newTag);keepProps&&mergeElements(newNode,node),node.wrapAll(newNode),node.contents().unwrap()})),result.value=null}else if("replaceText"===operator){if(!this.validateOperands(operator,operands,["string"]))break;this.eachNode(result,((node,value)=>{const content=String(this.evaluate(operands[0],{value:value}));node.text(content)})),result.value=null}else if("replaceWith"===operator){if(!this.validateOperands(operator,operands,["string"]))break;this.eachNode(result,((node,value)=>{const content=String(this.evaluate(operands[0],{value:value}));node.replaceWith(content)})),result.value=null}else if("reverse"===operator){if(!this.validateOperands(operator,operands,[],[]))break;result.nodes=$(result.nodes.toArray().reverse()),result.value=this.text(result.nodes,format)}else if("scrollBottom"===operator){if(this.online){const y=result.nodes.scrollTop()+result.nodes.height();this.log(`scrollBottom ${result.nodes.scrollTop()} ${result.nodes.height()} ${y}`),window.scrollTo(0,y)}}else if("size"===operator)result.value=result.nodes.length;else if("split"===operator){if(!this.validateOperands(operator,operands,[],["string","number"]))break;const text=result.value instanceof Array?result.value.join("\n"):result.value,separator=operands[0]||"\n",limit="number"==typeof operands[1]&&operands[1]>=0?operands[1]:void 0;result.value=text.split(separator,limit)}else if("shadow"===operator)result.nodes=$(result.nodes.toArray().map((element=>element.shadowRoot)).filter((obj=>!!obj)));else if("slot"===operator){if(!this.validateOperands(operator,operands,[],["boolean"]))break;const flatten=parseBoolean(operands[0]);result.nodes=$(result.nodes.toArray().map((element=>element.assignedElements({flatten:flatten}))).filter((obj=>!!obj)))}else if("text"===operator&&"inline"===operands[0])result.value=result.nodes.toArray().map((element=>Array.from(element.childNodes).filter((node=>3===node.nodeType)).map((node=>node.textContent??node.data)).join(" ").trim().replace(/[ ]{2,}/," "))),result.formatted=!1;else if(["appendTo","each","prependTo","insertBefore","insertAfter","replaceAll"].includes(operator))this.appendError("invalid-operator",`Operator "${operator}" not supported`,0);else{if(obj=result.nodes,method=operator,null===obj||"object"!=typeof obj||"function"!=typeof obj[method]){this.appendError("invalid-operator",`Operator '${operator}' not found`,0);break}{this.resolveOperands(operands,result);const obj=result.nodes[operator](...operands);isJQueryObject(obj)?(result.nodes=obj,result.value=this.text(result.nodes,format),result.formatted=!1):repeated?(result.value=result.nodes.toArray().map((element=>$(element)[operator](...operands))),result.formatted=!1):(result.value=obj,result.formatted=!1)}}else this.online?result.value=result.nodes.toArray().map((element=>element.outerHTML.trim())):result.value=result.nodes.toArray().map((element=>$.html(element).toString().trim())),("boolean"!=typeof operands[1]||operands[1])&&(result.value=formatHTML(result.value),result.formatted=!0)}var obj,method;return this.formatResult(result,type,all,limit,format,pattern,distinct,negate,removeNulls)}async run(actions,label="",wraparound=!1){this.step.unshift(0),this.lastStep.unshift(actions.length),label&&(label+=" "),this.state.vars.__step.unshift(0);for(let i=this.skipSteps(actions,label,wraparound);i<actions.length;i++){const action=actions[i],status=this.runExtractStatus(action);this.step[0]=i+1;const step=this.step.reverse().join(".");this.log(`${label}STEP ${step}/${actions.length} {${status.action}}`),this.online&&this.state.debug&&window.postMessage({direction:"syphonx",message:{...status,key:"extract-status",step:step,of:this.lastStep.reverse().join(".")}}),this.state.vars.__step[0]=i+1;const code=await this.dispatch(action);if(code)return this.log(`${label}BREAK at STEP ${step} [${code}]`),this.state.vars.__step.shift(),this.step.shift(),this.lastStep.shift(),code}this.state.vars.__step.shift(),this.log(`${label}${actions.length} steps completed`),this.step.shift(),this.lastStep.shift()}runExtractStatus(action){const[key]=Object.keys(action),status={step:"",of:"",action:key,name:action[key].name};if(action.hasOwnProperty("snooze")){const obj=action.snooze;status.timeout=obj instanceof Array?obj[1]||obj[0]:"number"==typeof obj?obj:obj.interval[1]||obj.interval[0]}else if(action.hasOwnProperty("waitfor")){const obj=action.waitfor;status.timeout=obj.timeout||30}else if(action.hasOwnProperty("click")){const obj=action.click;status.timeout=0,obj.snooze&&(status.timeout+=obj.snooze[1]||obj.snooze[0]),obj.waitfor?.timeout&&(status.timeout+=obj.waitfor.timeout)}return status}screenshot({name:name,selector:selector,params:params,when:when}){this.yield({name:"SCREENSHOT "+(name?` ${name}`:""),params:{screenshot:{...params,name:name,selector:this.evaluateString(selector)},action:"screenshot",selector:selector},when:when})}async scroll({name:name,query:query,target:target,behavior:behavior="smooth",block:block,inline:inline,when:when}){if(this.online)if(this.when(when,"SCROLL"+(name?` ${name}`:""))){if("top"===target||"bottom"===target){const top="bottom"===target?document.body.scrollHeight:0,left=0;window.scrollTo({top:top,left:left,behavior:behavior});const w0=[Math.floor(window.scrollX),Math.floor(window.scrollY)];await waitForScrollEnd();const w1=[Math.floor(window.scrollX),Math.floor(window.scrollY)];this.log(`SCROLL${name?` ${name}`:""} ${when||"(default)"} scroll to ${target} from [${w0}] to [${w1}]`)}else if(query){const result=this.query({query:query,repeated:!1});if(result&&result.nodes.length>0){const element=result.nodes[0],{top:top,left:left}=element.getBoundingClientRect(),elementPos=[window.scrollX+Math.floor(left),window.scrollY+Math.floor(top)],w0=[Math.floor(window.scrollX),Math.floor(window.scrollY)];element.scrollIntoView({behavior:behavior,block:block,inline:inline}),await waitForScrollEnd();const w1=[Math.floor(window.scrollX),Math.floor(window.scrollY)];this.log(`SCROLL${name?` ${name}`:""} ${when||"(default)"} -> [${this.nodeKey(result.nodes)}] scroll to element at [${elementPos}] from [${w0}] to [${w1}]`)}}else this.log(`SCROLL${name?` ${name}`:""} ${when||"(default)"} INVALID TARGET`);this.state.vars.__metrics.steps+=1}else this.state.vars.__metrics.skipped+=1,this.log(`SCROLL${name?` ${name}`:""} SKIPPED ${when}`);else this.log(`SCROLL${name?` ${name}`:""} IGNORED`)}select(selects,pivot=!1){const data={};for(const select of selects){let item=null;if(this.when(select.when)&&(select.pivot?item=this.selectResolvePivot(select,item):(pivot||this.pushContext({name:select.name},select.context),select.union?item=this.selectResolveUnion(select,item,data):(select.query&&(item=this.selectResolveSelector(select,item)),void 0!==select.value&&(item=this.selectResolveValue(select,{data:data,value:item?.value}))),pivot||(isEmpty(item?.value)&&select.required&&this.appendError("select-required",`Required select '${this.contextKey()}' not found`,0),this.popContext()))),select.name?.startsWith("_")&&item)this.state.vars[select.name]=item.value;else{if(!select.name)return item;data[select.name]=item}}return this.state.vars.__metrics.steps+=1,data}async selectWaitfor(selects){selects=selects.filter((select=>select.waitfor&&select.query));for(const select of selects){const on=select.all?"all":"any";this.pushContext({name:select.name},select.context);const code=await this.waitforQuery(select.query,on,this.state.vars.__timeout,!0,void 0,"SELECT");if(this.popContext(),"timeout"===code&&select.required)return"timeout"}return null}selectResolvePivot(select,item){const $=this.jquery,{pivot:pivot,...superselect}=select;if(pivot){const result=this.query(select);if(result&&result.nodes&&result.nodes.length>0){this.pushContext({name:select.name,nodes:result.nodes,value:result.value,action:"pivot"},select.context);const elements=result.nodes.toArray();for(const element of elements){const nodes=$(element);this.pushContext({nodes:$(element),value:this.text(nodes,select.format),pivot:elements.indexOf(element)},select.context),item=this.selectResolveSelector({...superselect,...pivot},item,!0),this.log(`PIVOT ${this.contextKey()} -> ${typeName(item?.value)}`),this.popContext()}isEmpty(item?.value)&&select.required&&this.appendError("select-required",`Required select '${this.contextKey()}' not found`,0),this.popContext()}else this.log(`PIVOT ${this.contextKey()} EMPTY`)}return item}selectResolveSelector(select,item,pivot=!1){const $=this.jquery;let subitem=null;void 0===select.type&&select.select&&(select.type="object");const result=this.query(select);if(result)if(select.select){this.pushContext({action:"subselect"},select.context);const n=result.value instanceof Array?result.value.length:0;if(select.repeated&&result.nodes.length===n&&!select.collate)subitem={nodes:this.nodeKeys(result.nodes),key:result.key,value:result.nodes.toArray().map(((node,index)=>(this.pokeContext({nodes:$(node),value:result.value instanceof Array?result.value[index]:result.value,index:index}),this.select(select.select,pivot))))};else if(select.repeated&&result.value instanceof Array&&!select.collate)subitem={nodes:this.nodeKeys(result.nodes),key:result.key,value:result.value.map(((value,index)=>(this.pokeContext({nodes:result.nodes,value:value,index:index}),this.select(select.select,pivot))))};else{this.pokeContext({nodes:result.nodes,value:result.value});const subselect=select.collate?select.select.map((obj=>({...obj,all:!0}))):select.select,value=this.select(subselect,pivot);subitem={nodes:this.nodeKeys(result.nodes),key:result.key,value:select.repeated?[value]:value}}this.popContext()}else subitem={nodes:this.nodeKeys(result.nodes),key:result.key,value:result.value};return this.log(`SELECT ${this.contextKey()} -> ${selectorStatements(select.query)} -> ${subitem?trunc(subitem.value):"(none)"}${item?` merge(${typeName(i