ff-dom 3.0.0-beta.2 → 3.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"xpath.mjs","sources":["../src/utils/xpathHelpers.ts","../src/utils/xpath.ts","../src/utils/cssSelector.ts","../src/utils/getElementsFromHTML.ts","../src/node/xpath.ts"],"sourcesContent":["export const reWhiteSpace = /^[\\S]+( [\\S]+)*$/i;\r\nexport let cspEnabled: boolean = false;\r\nconst xpathCache: { [x: string]: number } = {};\r\n\r\nexport const timeLog = (label: string, start: number) => {\r\n const duration = performance.now() - start;\r\n console.log(`⏱️ ${label}: ${duration.toFixed(2)}ms time`);\r\n};\r\n\r\ntype EvalResult = {\r\n first: Node | null;\r\n second: Node | null;\r\n};\r\n\r\n// cache per context node → avoids cross-context pollution\r\nlet xpathEvalCache = new WeakMap<Node, Map<string, EvalResult>>();\r\n\r\nconst getXPathContext = (docmt: Node) => {\r\n const owner: Document =\r\n docmt.nodeType === 9\r\n ? (docmt as Document)\r\n : docmt.ownerDocument || document;\r\n\r\n // valid XPath context nodes: Element(1), Document(9), DocumentFragment/ShadowRoot(11)\r\n const contextNode: Node =\r\n docmt.nodeType === 1 || docmt.nodeType === 9 || docmt.nodeType === 11\r\n ? docmt\r\n : owner;\r\n\r\n return { owner, contextNode };\r\n};\r\n\r\nconst getMutationCacheContexts = (node: Node): Node[] => {\r\n const contexts: Node[] = [];\r\n const addContext = (candidate: Node | null | undefined) => {\r\n if (!candidate || contexts.includes(candidate)) return;\r\n contexts.push(candidate);\r\n };\r\n\r\n addContext(node);\r\n\r\n const rootNode = node.getRootNode?.();\r\n if (rootNode instanceof Document || rootNode instanceof ShadowRoot) {\r\n addContext(rootNode);\r\n }\r\n\r\n const ownerDocument =\r\n node.nodeType === Node.DOCUMENT_NODE\r\n ? (node as Document)\r\n : node.ownerDocument;\r\n addContext(ownerDocument);\r\n\r\n return contexts;\r\n};\r\n\r\nexport const clearXPathEvalCache = (node?: Node | null) => {\r\n if (!node) {\r\n xpathEvalCache = new WeakMap<Node, Map<string, EvalResult>>();\r\n return;\r\n }\r\n\r\n getMutationCacheContexts(node).forEach((contextNode) => {\r\n xpathEvalCache.delete(contextNode);\r\n });\r\n};\r\n\r\nexport const evaluateXPathOnce = (xpath: string, docmt: Node): EvalResult => {\r\n const { owner, contextNode } = getXPathContext(docmt);\r\n\r\n // get or create cache for this context node\r\n let nodeCache = xpathEvalCache.get(contextNode);\r\n if (!nodeCache) {\r\n nodeCache = new Map<string, EvalResult>();\r\n xpathEvalCache.set(contextNode, nodeCache);\r\n }\r\n\r\n // cache hit\r\n const cached = nodeCache.get(xpath);\r\n if (cached) return cached;\r\n\r\n // evaluate once, stop after 2 nodes\r\n const result = owner.evaluate(\r\n xpath,\r\n contextNode,\r\n null,\r\n XPathResult.ORDERED_NODE_ITERATOR_TYPE,\r\n null\r\n );\r\n\r\n const first = result.iterateNext();\r\n const second = result.iterateNext();\r\n\r\n const evalResult: EvalResult = { first, second };\r\n nodeCache.set(xpath, evalResult);\r\n\r\n return evalResult;\r\n};\r\nlet relativeXPathCache = new Map();\r\nexport let modifiedElementAttributes: {\r\n url: string | null;\r\n attributeName: string | null;\r\n element: HTMLElement | Element;\r\n doc: Document;\r\n}[] = [];\r\nlet mutationObserver: MutationObserver;\r\nconst INTERNAL_CLASS_TOKENS = new Set([\r\n \"marked-element-temp\",\r\n \"removePointers\"\r\n]);\r\n\r\nconst normalizeClassTokens = (\r\n classValue: string | null | undefined\r\n): string[] =>\r\n (classValue || \"\")\r\n .split(/\\s+/)\r\n .map((token) => token.trim())\r\n .filter((token) => token && !INTERNAL_CLASS_TOKENS.has(token))\r\n .sort();\r\n\r\nexport const hasNumericAttributeValue = (value: string | null | undefined) =>\r\n /\\d/.test(value || \"\");\r\n\r\nexport const getClassTokenConditions = (\r\n element: HTMLElement | Element,\r\n classValue: string | null | undefined,\r\n docmt: Document | ShadowRoot,\r\n allowNumericToken = false\r\n): string[] => {\r\n // Class is tokenized: numeric-looking tokens are unstable, but stable\r\n // non-numeric tokens in the same class attribute should all be tried.\r\n const allClassTokens = normalizeClassTokens(classValue);\r\n const classTokens = allClassTokens.filter((token) =>\r\n allowNumericToken ? true : !hasNumericAttributeValue(token)\r\n );\r\n\r\n if (!classTokens.length) return [];\r\n\r\n if (allClassTokens.length === 1 && classTokens.length === 1) {\r\n // Single class value: exact class is more precise and not affected by\r\n // class-token ordering because there is no competing token.\r\n return [`@class=${escapeCharacters(classTokens[0])}`];\r\n }\r\n\r\n return classTokens\r\n .map((token) => {\r\n const xpath = buildPattern(\r\n `contains(@class,${escapeCharacters(token)})`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n\r\n return {\r\n token,\r\n count: getTagOnlyXpathCandidateCount(xpath, element, docmt)\r\n };\r\n })\r\n .sort((left, right) => {\r\n // Multi-token class value: prefer the shortest stable token; uniqueness\r\n // is still validated by the caller/final XPath count.\r\n if (left.token.length !== right.token.length) {\r\n return left.token.length - right.token.length;\r\n }\r\n\r\n return left.count - right.count;\r\n })\r\n .map(({ token }) => `contains(@class,${escapeCharacters(token)})`);\r\n};\r\n\r\nexport const getClassTokenCondition = (\r\n element: HTMLElement | Element,\r\n classValue: string | null | undefined,\r\n docmt: Document | ShadowRoot,\r\n allowNumericToken = false\r\n): string | null => {\r\n return (\r\n getClassTokenConditions(element, classValue, docmt, allowNumericToken)[0] ||\r\n null\r\n );\r\n};\r\n\r\nconst TEST_ID_ATTRIBUTE_NAMES = new Set([\r\n \"data-testid\",\r\n \"data-test-id\",\r\n \"data-test\",\r\n \"data-cy\",\r\n \"testid\"\r\n]);\r\n\r\nconst hasGeneratedNumericSegment = (attributeValue: string): boolean =>\r\n /(?:^|[-_:])\\d{4,}(?:$|[-_:])/.test(attributeValue);\r\n\r\nconst isGeneratedTestIdAttribute = (\r\n attributeName: string,\r\n attributeValue: string\r\n): boolean =>\r\n TEST_ID_ATTRIBUTE_NAMES.has(attributeName.toLowerCase()) &&\r\n hasGeneratedNumericSegment(attributeValue);\r\n\r\nexport const sanitizeAttributeValue = (\r\n attributeName: string,\r\n attributeValue: string | null | undefined\r\n): string => {\r\n if (!attributeValue) {\r\n return \"\";\r\n }\r\n\r\n if (attributeName === \"class\" || attributeName === \"className\") {\r\n return normalizeClassTokens(attributeValue).join(\" \");\r\n }\r\n\r\n return attributeValue.replace(\"removePointers\", \"\").trim();\r\n};\r\n\r\nconst isInternalClassOnlyMutation = (mutation: MutationRecord): boolean => {\r\n if (\r\n mutation.type !== \"attributes\" ||\r\n mutation.attributeName !== \"class\" ||\r\n !(mutation.target instanceof Element)\r\n ) {\r\n return false;\r\n }\r\n\r\n if (\r\n isSvg(mutation.target) &&\r\n mutation.oldValue?.trim() === mutation.target.classList.value?.trim()\r\n ) {\r\n return true;\r\n }\r\n\r\n const previousTokens = normalizeClassTokens(mutation.oldValue);\r\n const currentTokens = normalizeClassTokens(\r\n mutation.target.getAttribute(\"class\")\r\n );\r\n\r\n return previousTokens.join(\" \") === currentTokens.join(\" \");\r\n};\r\n\r\nexport const createObserver = (addedNodeCallBack: Function) => {\r\n mutationObserver = new MutationObserver((mutations) => {\r\n mutations.forEach((mutation) => {\r\n const shouldInvalidateXPathCache =\r\n mutation.type === \"childList\" ||\r\n mutation.type === \"characterData\" ||\r\n (mutation.type === \"attributes\" &&\r\n mutation.attributeName !== \"flndisabled\" &&\r\n !isInternalClassOnlyMutation(mutation));\r\n\r\n if (shouldInvalidateXPathCache) {\r\n clearXPathEvalCache(mutation.target);\r\n }\r\n\r\n if (mutation?.addedNodes?.length) {\r\n addedNodeCallBack(mutation?.addedNodes);\r\n }\r\n\r\n if (mutation.target instanceof HTMLElement) {\r\n if (isInternalClassOnlyMutation(mutation)) {\r\n } else if (\r\n mutation?.type === \"attributes\" &&\r\n ![\"flndisabled\"].includes(mutation.attributeName!)\r\n ) {\r\n modifiedElementAttributes.push({\r\n url: mutation.target.baseURI,\r\n attributeName: mutation.attributeName,\r\n element: mutation.target,\r\n doc: mutation.target.ownerDocument\r\n });\r\n }\r\n }\r\n });\r\n });\r\n};\r\nexport const startObserver = (\r\n target: Node,\r\n options: MutationObserverInit | undefined\r\n) => {\r\n mutationObserver?.observe(target, options);\r\n};\r\n\r\nexport const stopObserver = () => {\r\n mutationObserver?.disconnect();\r\n};\r\n\r\nexport const isNumberExist = (str: string): boolean => {\r\n const hasNumber = /\\d/;\r\n return hasNumber.test(str);\r\n};\r\n\r\nexport const buildPattern = (\r\n pattern: string,\r\n isSvg: boolean,\r\n tagName: string\r\n): string => {\r\n return isSvg\r\n ? `//*[local-name()='${tagName}' and ${pattern}]`\r\n : `//${tagName}[${pattern}]`;\r\n};\r\n\r\nexport const getTextContent = (\r\n targetElement: HTMLElement | Element\r\n): string => {\r\n const textContent = targetElement?.textContent;\r\n if (cspEnabled) {\r\n if (textContent) {\r\n const tooltip = document.querySelector(\".flntooltip\") as HTMLElement;\r\n if (tooltip) {\r\n const lastIndex = textContent.lastIndexOf(tooltip.innerText);\r\n if (\r\n lastIndex &&\r\n textContent.length === lastIndex + tooltip.innerText.length\r\n ) {\r\n return textContent.substring(0, lastIndex);\r\n }\r\n } else {\r\n return textContent;\r\n }\r\n }\r\n } else {\r\n return textContent;\r\n }\r\n\r\n return \"\";\r\n};\r\n\r\nexport const getFilteredText = (element: Node): string => {\r\n return element?.childNodes[0]?.nodeValue || \"\";\r\n};\r\n\r\nexport const getCountOfXPath = (\r\n xpath: string,\r\n element: HTMLElement | Element,\r\n docmt: Node,\r\n multiElementReferenceMode: boolean = false\r\n): number => {\r\n try {\r\n const { first, second } = evaluateXPathOnce(xpath, docmt);\r\n\r\n if (!first) return 0;\r\n\r\n // exactly one match\r\n if (!second) {\r\n return first === element ? 1 : 0;\r\n }\r\n\r\n // multiple matches\r\n if (multiElementReferenceMode && Array.isArray(element)) {\r\n let matchCount = 0;\r\n\r\n if (element.includes(first)) matchCount++;\r\n if (element.includes(second)) matchCount++;\r\n\r\n if (matchCount === 2) return 2;\r\n const owner =\r\n docmt.nodeType === 9 ? (docmt as Document) : docmt.ownerDocument!;\r\n\r\n const result = owner.evaluate(\r\n xpath,\r\n docmt,\r\n null,\r\n XPathResult.ORDERED_NODE_ITERATOR_TYPE,\r\n null\r\n );\r\n\r\n let node: Node | null;\r\n while ((node = result.iterateNext())) {\r\n if (element.includes(node)) {\r\n matchCount++;\r\n if (matchCount === 2) return 2;\r\n }\r\n }\r\n\r\n return 0;\r\n }\r\n\r\n return 2;\r\n } catch (error) {\r\n console.error(`Error evaluating XPath: ${xpath}`, error);\r\n return 0;\r\n }\r\n};\r\n\r\nexport const escapeCharacters = (text: string): string => {\r\n if (text) {\r\n if (!(text.indexOf('\"') === -1)) {\r\n return `'${text}'`;\r\n }\r\n if (!(text.indexOf(\"'\") === -1)) {\r\n return `\"${text}\"`;\r\n }\r\n }\r\n return `'${text}'`;\r\n};\r\n\r\nexport const removeParenthesis = (xpath: string): string => {\r\n const charArr = xpath.split(\"\");\r\n\r\n let count = charArr.length;\r\n const indexArray = [];\r\n\r\n while (charArr[count - 2] !== \"[\") {\r\n indexArray.push(charArr[count - 2]);\r\n count--;\r\n }\r\n\r\n indexArray.reverse();\r\n let finalStr = \"\";\r\n for (let i = 0; i < indexArray.length; i++) {\r\n finalStr += indexArray[i];\r\n }\r\n\r\n const endBracketLength = finalStr.length + 2;\r\n let firstpart = xpath.slice(0, xpath.length - endBracketLength);\r\n\r\n if (firstpart.startsWith(\"(\") && firstpart.endsWith(\")\")) {\r\n firstpart = firstpart.slice(1, -1);\r\n } else {\r\n firstpart = xpath;\r\n }\r\n\r\n return firstpart;\r\n};\r\n\r\nexport const findXpathWithIndex = (\r\n val: string,\r\n node: HTMLElement | Element,\r\n docmt: Node,\r\n count: number\r\n) => {\r\n try {\r\n const owner =\r\n docmt.nodeType === 9 // DOCUMENT_NODE\r\n ? (docmt as Document)\r\n : docmt.ownerDocument!;\r\n\r\n let index = 0;\r\n if (count) {\r\n if (getCountOfXPath(`${val}[${count}]`, node, docmt) === 1) {\r\n return `${val}[${count}]`;\r\n }\r\n\r\n if (getCountOfXPath(`(${val})[${count}]`, node, docmt) === 1) {\r\n return `(${val})[${count}]`;\r\n }\r\n }\r\n\r\n const nodes = owner.evaluate(val, docmt, null, XPathResult.ANY_TYPE, null);\r\n let nodex: Node | null = null;\r\n while ((nodex = nodes.iterateNext())) {\r\n index++;\r\n if (nodex.isSameNode(node)) {\r\n if (getCountOfXPath(`${val}[${index}]`, node, docmt) === 1) {\r\n return `${val}[${index}]`;\r\n }\r\n if (getCountOfXPath(`(${val})[${index}]`, node, docmt) === 1) {\r\n return `(${val})[${index}]`;\r\n }\r\n return;\r\n }\r\n }\r\n } catch (error) {\r\n console.log(error);\r\n }\r\n};\r\n\r\nconst deleteLineGap = (a: string): string => {\r\n a &&= a.split(\"\\n\")[0].length > 0 ? a.split(\"\\n\")[0] : a.split(\"\\n\")[1];\r\n return a;\r\n};\r\n\r\nconst deleteGarbageFromInnerText = (a: string): string => {\r\n a = deleteLineGap(a);\r\n a = a\r\n .split(/[^\\u0000-\\u00ff]/)\r\n .reduce((b, c) => {\r\n return b.length > c.length ? b : c;\r\n }, \"\")\r\n .trim();\r\n return (a = a.split(\"/\")[0].trim());\r\n};\r\n\r\nexport const replaceWhiteSpaces = (str: string): string => {\r\n if (str) {\r\n return str.replace(/\\s\\s+/g, \" \").trim();\r\n }\r\n\r\n return str;\r\n};\r\n\r\nexport const getContainerTextCondition = (\r\n element: HTMLElement | Element,\r\n text: string | null | undefined\r\n): string => {\r\n const normalizedText = replaceWhiteSpaces((text || \"\").trim());\r\n\r\n if (!element.children.length || normalizedText.length <= 50) {\r\n return \"\";\r\n }\r\n\r\n const fragment = normalizedText.split(/\\s+/).slice(0, 3).join(\" \");\r\n\r\n // Long container text is usually flattened descendant text. Use a short\r\n // visible fragment instead of exact matching the whole menu/header content.\r\n return fragment.length > 2\r\n ? `contains(normalize-space(.),${escapeCharacters(fragment)})`\r\n : \"\";\r\n};\r\n\r\nexport const getStableTargetTextCandidates = (\r\n text: string | null | undefined\r\n): { prefix: string; fragment: string } => {\r\n const normalizedText = replaceWhiteSpaces((text || \"\").trim());\r\n\r\n if (!normalizedText || !hasNumericAttributeValue(normalizedText)) {\r\n return { prefix: normalizedText, fragment: normalizedText };\r\n }\r\n\r\n const prefix = normalizedText\r\n .split(/[0-9]/)[0]\r\n .replace(/[₹$€£¥₫₽₩₦₱₲₴₵₡₭₮₺฿៛]+/g, \"\")\r\n .trim();\r\n const fragment = prefix\r\n .split(/[^a-zA-Z]+/)\r\n .map((part) => replaceWhiteSpaces(part).trim())\r\n .filter((part) => part.length > 2 && /[a-zA-Z]/.test(part))\r\n .join(\" \")\r\n .trim();\r\n\r\n // Target text containing numbers is dynamic. Keep only a meaningful\r\n // non-numeric prefix/fragment, so price/count text never becomes exact XPath.\r\n return {\r\n prefix: prefix.length > 2 && /[a-zA-Z]/.test(prefix) ? prefix : \"\",\r\n fragment: fragment.length > 2 ? fragment : \"\"\r\n };\r\n};\r\n\r\nexport const getStableTargetText = (\r\n text: string | null | undefined\r\n): string => getStableTargetTextCandidates(text).fragment;\r\n\r\nexport const getShadowRoot = (el: HTMLElement | Element): Element | null => {\r\n if (!el || !el?.getRootNode) return null;\r\n\r\n const root = el.getRootNode() as ShadowRoot;\r\n return root && root?.host ? (root as unknown as Element) : null;\r\n};\r\n\r\nconst isShadowRootNode = (node: Node): node is ShadowRoot => {\r\n return node?.nodeType === Node.DOCUMENT_FRAGMENT_NODE && \"host\" in node;\r\n};\r\n\r\nconst getElementPathFromRoot = (\r\n root: ShadowRoot,\r\n element: Element\r\n): number[] | null => {\r\n const path: number[] = [];\r\n let current: Element | null = element;\r\n\r\n while (current && current.parentElement) {\r\n const parent: Element = current.parentElement as Element;\r\n path.unshift(Array.from(parent.children).indexOf(current));\r\n current = parent;\r\n }\r\n\r\n if (!current || current.parentNode !== root) {\r\n return null;\r\n }\r\n\r\n path.unshift(Array.from(root.children).indexOf(current));\r\n return path;\r\n};\r\n\r\nconst getElementAtPath = (\r\n container: ParentNode & { children: HTMLCollection },\r\n path: number[]\r\n): Element | null => {\r\n let current: (ParentNode & { children: HTMLCollection }) | Element =\r\n container;\r\n\r\n for (const index of path) {\r\n const next: Element | null = current.children.item(index) as Element | null;\r\n if (!next) {\r\n return null;\r\n }\r\n\r\n current = next;\r\n }\r\n\r\n return current as Element | null;\r\n};\r\n\r\nexport const createShadowEvaluationContext = (\r\n root: ShadowRoot,\r\n element?: HTMLElement | Element | (HTMLElement | Element)[]\r\n) => {\r\n const tempDoc =\r\n root.ownerDocument.implementation.createHTMLDocument(\"shadow-xpath\");\r\n const wrapper = tempDoc.createElement(\"div\");\r\n wrapper.innerHTML = root.innerHTML;\r\n tempDoc.body.appendChild(wrapper);\r\n\r\n const cloneForElement = (candidate?: HTMLElement | Element | null) => {\r\n if (\r\n !candidate ||\r\n !(candidate instanceof root.ownerDocument.defaultView!.Element)\r\n ) {\r\n return null;\r\n }\r\n\r\n const path = getElementPathFromRoot(root, candidate);\r\n return path ? getElementAtPath(wrapper, path) : null;\r\n };\r\n\r\n const cloneElements = Array.isArray(element)\r\n ? element.map((candidate) => cloneForElement(candidate)).filter(Boolean)\r\n : [];\r\n\r\n const cloneElement = Array.isArray(element)\r\n ? null\r\n : cloneForElement(element ?? null);\r\n\r\n return {\r\n owner: tempDoc,\r\n contextNode: wrapper,\r\n cloneElement,\r\n cloneElements\r\n };\r\n};\r\n\r\nexport const checkBlockedAttributes = (\r\n attribute: {\r\n name: string;\r\n value: string;\r\n },\r\n targetElement: HTMLElement | Element,\r\n isTarget: boolean\r\n): boolean => {\r\n const sanitizedValue = sanitizeAttributeValue(\r\n attribute.name,\r\n attribute.value\r\n );\r\n\r\n if (!sanitizedValue || typeof attribute?.value === \"boolean\") {\r\n return false;\r\n }\r\n const blockedValues = [\r\n \"true\",\r\n \"false\",\r\n \"on\",\r\n \"off\",\r\n \"flntooltip\",\r\n \"flutter-highlight-overlay\"\r\n ];\r\n if (blockedValues.some((x) => x === sanitizedValue)) {\r\n return false;\r\n }\r\n const blockedNames = [\"style\", \"locator-data-tooltip\", \"value\"];\r\n if (blockedNames.some((x) => x === attribute.name)) {\r\n return false;\r\n }\r\n\r\n const isModified = modifiedElementAttributes?.find(\r\n (x) =>\r\n x.doc === targetElement.ownerDocument &&\r\n x.element === targetElement &&\r\n x.attributeName === attribute.name\r\n );\r\n if (isModified) {\r\n return false;\r\n }\r\n\r\n if (attribute?.name?.indexOf(\"on\") === 0 && attribute?.name?.length > 3) {\r\n return false;\r\n }\r\n\r\n if (typeof attribute.value === \"function\") {\r\n return false;\r\n }\r\n\r\n if (isGeneratedTestIdAttribute(attribute.name, sanitizedValue)) {\r\n return false;\r\n }\r\n\r\n // Strict numeric-attribute validation: numeric attribute values are treated\r\n // as unstable before candidate creation. Class is handled token-by-token by\r\n // getClassTokenCondition, so only numeric class tokens are rejected there.\r\n if (attribute.name !== \"class\" && hasNumericAttributeValue(sanitizedValue)) {\r\n return false;\r\n }\r\n\r\n return true;\r\n};\r\n\r\nexport const getRelationship = (a: HTMLElement, b: HTMLElement): string => {\r\n let pos = a.compareDocumentPosition(b);\r\n return pos === 2\r\n ? \"preceding\"\r\n : pos === 4\r\n ? \"following\"\r\n : pos === 8\r\n ? \"ancestor\"\r\n : pos === 16\r\n ? \"descendant\"\r\n : pos === 32\r\n ? \"self\"\r\n : \"\";\r\n};\r\n\r\nexport const isSvg = (element: HTMLElement | Element) => {\r\n return element instanceof SVGElement;\r\n};\r\n\r\nexport const replaceTempAttributes = (str: string): string => {\r\n if (!str) return str;\r\n\r\n return str.replace(/\\b[a-zA-Z_]*disabled\\b/gi, \"disabled\");\r\n};\r\n\r\nconst getElementFromXpath = (xpath: string, docmt: Node, multi = false) => {\r\n let contextNode: Node = docmt;\r\n\r\n // XPath does NOT support DocumentFragment / ShadowRoot\r\n if (docmt.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {\r\n contextNode = (docmt as ShadowRoot).host ?? docmt;\r\n }\r\n\r\n const owner =\r\n contextNode.nodeType === Node.DOCUMENT_NODE\r\n ? (contextNode as Document)\r\n : contextNode.ownerDocument!;\r\n\r\n if (multi) {\r\n return owner.evaluate(xpath, contextNode, null, XPathResult.ANY_TYPE, null);\r\n }\r\n\r\n return owner.evaluate(\r\n xpath,\r\n contextNode,\r\n null,\r\n XPathResult.FIRST_ORDERED_NODE_TYPE,\r\n null\r\n ).singleNodeValue;\r\n};\r\n\r\nexport const getPropertyXPath = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n prop: string,\r\n value: string,\r\n isIndex: boolean,\r\n isTarget: boolean\r\n) => {\r\n if (value) {\r\n const { tagName } = element;\r\n let count;\r\n let combinePattern = \"\";\r\n const mergePattern = [];\r\n let pattern;\r\n\r\n const isAttributeProp = prop.startsWith(\"@\");\r\n const isTextProp = prop === \".\" || prop === \"text()\";\r\n const normalizedTextProp = isTextProp ? \".\" : prop;\r\n const hasTextSpacing = isTextProp && /\\s/.test(value.trim());\r\n const stableTargetText = isTextProp ? getStableTargetText(value) : value;\r\n const textValueForPredicate =\r\n isTextProp && hasNumericAttributeValue(value) ? stableTargetText : value;\r\n const containerTextCondition = isTextProp\r\n ? getContainerTextCondition(element, textValueForPredicate)\r\n : \"\";\r\n\r\n if (prop === \"@class\") {\r\n for (const classCondition of getClassTokenConditions(\r\n element,\r\n value,\r\n docmt\r\n )) {\r\n // Single-mode class generation tries every stable token and lets the\r\n // normal uniqueness check choose the first usable XPath.\r\n pattern = buildPattern(\r\n classCondition,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n\r\n count = getCountOfXPath(pattern, element, docmt);\r\n if (count === 1 && !isIndex) {\r\n return pattern;\r\n }\r\n }\r\n\r\n return;\r\n }\r\n\r\n if (isTextProp && hasNumericAttributeValue(value) && !stableTargetText) {\r\n // Target text with only dynamic numeric content should not produce a\r\n // text XPath. Other attribute/relative fallbacks can still run.\r\n return;\r\n }\r\n\r\n if (value && (!isAttributeProp || !isNumberExist(value))) {\r\n if (containerTextCondition) {\r\n pattern = buildPattern(\r\n containerTextCondition,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n } else if (hasTextSpacing) {\r\n pattern = buildPattern(\r\n `normalize-space(${normalizedTextProp})=${escapeCharacters(\r\n replaceWhiteSpaces(textValueForPredicate)\r\n ).trim()}`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n } else if (!reWhiteSpace.test(value)) {\r\n pattern = buildPattern(\r\n `normalize-space(${prop})=${escapeCharacters(\r\n replaceWhiteSpaces(textValueForPredicate)\r\n ).trim()}`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n } else {\r\n pattern = `//${tagName}[${prop}=${escapeCharacters(\r\n textValueForPredicate\r\n )}]`;\r\n }\r\n\r\n count = getCountOfXPath(pattern, element, docmt);\r\n\r\n if (count === 1 && !isIndex) {\r\n return pattern;\r\n }\r\n }\r\n\r\n if (isAttributeProp && hasNumericAttributeValue(value)) {\r\n // Strict numeric-attribute validation: do not split numeric attribute\r\n // values into starts-with/contains fallbacks.\r\n return;\r\n }\r\n\r\n if (value && isTarget) {\r\n const splitText = value.split(\" \");\r\n if (splitText?.length) {\r\n if (splitText.length === 1) {\r\n const contentRes = [...new Set(splitText[0].match(/([^0-9]+)/g))];\r\n if (contentRes?.length >= 1) {\r\n if (\r\n contentRes[0] &&\r\n replaceWhiteSpaces(contentRes[0].trim())?.length > 1\r\n ) {\r\n if (value.startsWith(contentRes[0])) {\r\n if (!reWhiteSpace.test(contentRes[0])) {\r\n combinePattern = `starts-with(${prop},${escapeCharacters(\r\n replaceWhiteSpaces(contentRes[0])\r\n ).trim()})`;\r\n } else {\r\n combinePattern = `starts-with(${prop},${escapeCharacters(\r\n contentRes[0]\r\n ).trim()})`;\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (contentRes?.length > 1) {\r\n if (\r\n contentRes[contentRes.length - 1] &&\r\n replaceWhiteSpaces(contentRes[contentRes.length - 1].trim())\r\n ?.length > 1\r\n ) {\r\n if (value.endsWith(contentRes[contentRes.length - 1])) {\r\n if (!reWhiteSpace.test(contentRes[contentRes.length - 1])) {\r\n combinePattern = `ends-with(${prop},${escapeCharacters(\r\n replaceWhiteSpaces(contentRes[contentRes.length - 1])\r\n ).trim()})`;\r\n } else {\r\n combinePattern = `ends-with(${prop},${escapeCharacters(\r\n contentRes[contentRes.length - 1]\r\n ).trim()})`;\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (combinePattern?.length) {\r\n if (isSvg(element)) {\r\n pattern = `//*[local-name()='${tagName}' and ${combinePattern}]`;\r\n } else {\r\n pattern = `//${tagName}[${combinePattern}]`;\r\n }\r\n count = getCountOfXPath(pattern, element, docmt);\r\n if (count === 1 && !isIndex) {\r\n return pattern;\r\n }\r\n }\r\n } else {\r\n const endIndex =\r\n splitText.length % 2 === 0\r\n ? splitText.length / 2\r\n : splitText.length % 2;\r\n const startIndexString = splitText.slice(0, endIndex).join(\" \");\r\n let contentRes = [...new Set(startIndexString.match(/([^0-9]+)/g))];\r\n if (contentRes?.length) {\r\n if (\r\n contentRes[0] &&\r\n replaceWhiteSpaces(contentRes[0].trim())?.length\r\n ) {\r\n if (value.startsWith(contentRes[0])) {\r\n if (!reWhiteSpace.test(contentRes[0])) {\r\n combinePattern = `starts-with(${prop},${escapeCharacters(\r\n replaceWhiteSpaces(contentRes[0])\r\n ).trim()})`;\r\n } else {\r\n combinePattern = `starts-with(${prop},${escapeCharacters(\r\n contentRes[0]\r\n ).trim()})`;\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (combinePattern?.length) {\r\n if (isSvg(element)) {\r\n pattern = `//*[local-name()='${tagName}' and ${combinePattern}]`;\r\n } else {\r\n pattern = `//${tagName}[${combinePattern}]`;\r\n }\r\n count = getCountOfXPath(pattern, element, docmt);\r\n if (count === 1 && !isIndex) {\r\n return pattern;\r\n }\r\n }\r\n\r\n const endIndexString = splitText\r\n .slice(endIndex, splitText.length - 1)\r\n .join(\" \");\r\n contentRes = [...new Set(endIndexString.match(/([^0-9]+)/g))];\r\n if (contentRes?.length) {\r\n if (\r\n contentRes[0] &&\r\n replaceWhiteSpaces(contentRes[0].trim())?.length > 3\r\n ) {\r\n if (value.endsWith(contentRes[0])) {\r\n if (!reWhiteSpace.test(contentRes[0])) {\r\n combinePattern = `ends-with(${prop},${escapeCharacters(\r\n replaceWhiteSpaces(contentRes[0])\r\n ).trim()})`;\r\n } else {\r\n combinePattern = `ends-with(${prop},${escapeCharacters(\r\n contentRes[0]\r\n ).trim()})`;\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (combinePattern?.length) {\r\n if (isSvg(element)) {\r\n pattern = `//*[local-name()='${tagName}' and ${combinePattern}]`;\r\n } else {\r\n pattern = `//${tagName}[${combinePattern}]`;\r\n }\r\n count = getCountOfXPath(pattern, element, docmt);\r\n if (count === 1 && !isIndex) {\r\n return pattern;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (value && isTarget && isNumberExist(value)) {\r\n const contentRes = [...new Set(value.match(/([^0-9]+)/g))];\r\n if (contentRes?.length) {\r\n for (let i = 0; i < contentRes?.length; i++) {\r\n if (\r\n contentRes[i] &&\r\n replaceWhiteSpaces(contentRes[i].trim())?.length > 1\r\n ) {\r\n if (!reWhiteSpace.test(contentRes[i])) {\r\n mergePattern.push(\r\n `contains(${prop},${escapeCharacters(\r\n replaceWhiteSpaces(contentRes[i])\r\n ).trim()})`\r\n );\r\n } else {\r\n mergePattern.push(\r\n `contains(${prop},${escapeCharacters(\r\n contentRes[i].trim()\r\n ).trim()})`\r\n );\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (mergePattern?.length) {\r\n if (isSvg(element)) {\r\n pattern = `//*[local-name()='${tagName}' and ${mergePattern.join(\r\n \" and \"\r\n )}]`;\r\n } else {\r\n pattern = `//${tagName}[${mergePattern.join(\" and \")}]`;\r\n }\r\n count = getCountOfXPath(pattern, element, docmt);\r\n if (count === 1 && !isIndex) {\r\n return pattern;\r\n }\r\n }\r\n }\r\n\r\n if (isSvg(element)) {\r\n pattern = `//*[local-name()='${tagName}' and text()]`;\r\n } else {\r\n pattern = `//${tagName}[text()]`;\r\n }\r\n\r\n count = getCountOfXPath(pattern, element, docmt);\r\n if (count === 1 && !isIndex) {\r\n return pattern;\r\n }\r\n }\r\n};\r\n\r\nexport const getAbsoluteXPath = (\r\n domNode: HTMLElement | Element | null,\r\n docmt: Document\r\n): string => {\r\n try {\r\n if (!domNode) {\r\n return \"\";\r\n }\r\n\r\n let xpathe = isSvg(domNode)\r\n ? `/*[local-name()='${domNode.tagName}']`\r\n : `/${domNode.tagName}`;\r\n\r\n // // If this node has siblings of the same tagName, get the index of this node\r\n if (domNode.parentElement) {\r\n // Get the siblings\r\n const childNodes = Array.prototype.slice\r\n .call(domNode.parentElement.children, 0)\r\n .filter(\r\n (childNode: HTMLElement) => childNode.tagName === domNode.tagName\r\n );\r\n\r\n // // If there's more than one sibling, append the index\r\n if (childNodes.length > 1) {\r\n const index = childNodes.indexOf(domNode);\r\n xpathe += `[${index + 1}]`;\r\n }\r\n } else if (domNode instanceof HTMLElement) {\r\n if (domNode.offsetParent) {\r\n const childNodes = Array.prototype.slice\r\n .call(domNode.offsetParent.children, 0)\r\n .filter(\r\n (childNode: HTMLElement) => childNode.tagName === domNode.tagName\r\n );\r\n\r\n // // If there's more than one sibling, append the index\r\n if (childNodes.length > 1) {\r\n const index = childNodes.indexOf(domNode);\r\n xpathe += `[${index + 1}]`;\r\n }\r\n }\r\n }\r\n\r\n // // Make a recursive call to this nodes parents and prepend it to this xpath\r\n return getAbsoluteXPath(domNode?.parentElement, docmt) + xpathe;\r\n } catch (error) {\r\n // If there's an unexpected exception, abort and don't get an XPath\r\n console.log(\"xpath\", error);\r\n\r\n return \"\";\r\n }\r\n};\r\n\r\nexport const getRelativeXPath = (\r\n domNode: HTMLElement | Element,\r\n docmt: Document,\r\n isIndex: boolean,\r\n isTarget: boolean = false,\r\n attributesArray: Attr[]\r\n) => {\r\n try {\r\n // Generate a cache key based on the node's identifier, index, and target flag\r\n // Check if the result for this node is already cached\r\n if (relativeXPathCache.has(domNode)) {\r\n return relativeXPathCache.get(domNode);\r\n }\r\n\r\n // Initialize an array to hold parts of the XPath\r\n const xpathParts = [];\r\n let currentNode = domNode;\r\n\r\n // Traverse up the DOM tree iteratively instead of using recursion\r\n while (currentNode) {\r\n let xpathe: string | undefined = \"\";\r\n let hasUniqueAttr = false;\r\n let attributes =\r\n domNode === currentNode\r\n ? (attributesArray ?? currentNode.attributes)\r\n : currentNode.attributes;\r\n\r\n // Loop through attributes to check for unique identifiers\r\n for (const attrName of Array.from(attributes)) {\r\n if (checkBlockedAttributes(attrName, currentNode, isTarget)) {\r\n const attrValue = sanitizeAttributeValue(\r\n attrName.name,\r\n attrName.nodeValue\r\n );\r\n\r\n const elementName = attrName.name;\r\n\r\n // Class can produce multiple stable token candidates; try each\r\n // before moving to text/index fallbacks.\r\n for (const xpathCandidate of getXpathStrings(\r\n currentNode,\r\n elementName,\r\n attrValue\r\n )) {\r\n xpathe = xpathCandidate;\r\n let othersWithAttr: number = 0;\r\n othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);\r\n if (othersWithAttr === 1) {\r\n xpathParts.unshift(replaceTempAttributes(xpathe));\r\n hasUniqueAttr = true;\r\n break;\r\n }\r\n\r\n if (othersWithAttr > 1 && isIndex) {\r\n xpathe = findXpathWithIndex(\r\n xpathe,\r\n currentNode,\r\n docmt,\r\n othersWithAttr\r\n );\r\n if (xpathe) {\r\n xpathParts.unshift(replaceTempAttributes(xpathe));\r\n hasUniqueAttr = true;\r\n break;\r\n }\r\n // return replaceTempAttributes(xpathe);\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (currentNode.textContent) {\r\n if (\r\n !isTarget ||\r\n (isTarget && !isNumberExist(currentNode.textContent))\r\n ) {\r\n let reWhiteSpace = new RegExp(/^[\\S]+( [\\S]+)*$/gi);\r\n const filteredText = getFilteredText(currentNode);\r\n const containerTextCondition = getContainerTextCondition(\r\n currentNode,\r\n filteredText\r\n );\r\n\r\n if (containerTextCondition) {\r\n xpathe = buildPattern(\r\n containerTextCondition,\r\n isSvg(currentNode),\r\n currentNode.tagName || \"*\"\r\n );\r\n } else if (!reWhiteSpace.test(currentNode.textContent)) {\r\n xpathe = isSvg(currentNode)\r\n ? `//*[local-name()='${\r\n currentNode.tagName\r\n }' and normalize-space(.)=${escapeCharacters(\r\n filteredText\r\n )}]`\r\n : `//${\r\n currentNode.tagName || \"*\"\r\n }[normalize-space(.)=${escapeCharacters(\r\n filteredText\r\n )}]`;\r\n } else {\r\n xpathe = isSvg(currentNode)\r\n ? `//*[local-name()='${\r\n currentNode.tagName\r\n }' and .=${escapeCharacters(filteredText)}]`\r\n : `//${currentNode.tagName || \"*\"}[.=${escapeCharacters(\r\n filteredText\r\n )}]`;\r\n }\r\n\r\n let othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);\r\n if (othersWithAttr === 1) {\r\n return xpathe;\r\n }\r\n\r\n if (othersWithAttr > 1 && isIndex) {\r\n xpathe = findXpathWithIndex(\r\n xpathe,\r\n currentNode,\r\n docmt,\r\n othersWithAttr\r\n );\r\n return xpathe;\r\n }\r\n } else {\r\n let combinePattern: string[] = [];\r\n const contentRes = [\r\n ...new Set(getFilteredText(currentNode).match(/([^0-9]+)/g))\r\n ];\r\n let reWhiteSpace = new RegExp(/^[\\S]+( [\\S]+)*$/gi);\r\n if (contentRes?.length) {\r\n for (let i = 0; i < contentRes?.length; i++) {\r\n if (\r\n contentRes[i] &&\r\n replaceWhiteSpaces((contentRes[i] as string).trim())\r\n ) {\r\n if (!reWhiteSpace.test(contentRes[i] as string)) {\r\n combinePattern.push(\r\n `contains(.,${escapeCharacters(\r\n replaceWhiteSpaces(contentRes[i])\r\n ).trim()})`\r\n );\r\n } else {\r\n combinePattern.push(\r\n `contains(.,${escapeCharacters(\r\n (contentRes[i] as string).trim()\r\n ).trim()})`\r\n );\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (combinePattern?.length) {\r\n xpathe = isSvg(currentNode)\r\n ? `//*[local-name()='${\r\n currentNode.tagName\r\n }' and ${combinePattern.join(\" and \")}]`\r\n : `//${currentNode.tagName || \"*\"}[${combinePattern.join(\r\n \" and \"\r\n )}]`;\r\n let othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);\r\n if (othersWithAttr === 1) {\r\n return xpathe;\r\n }\r\n\r\n if (othersWithAttr > 1 && isIndex) {\r\n xpathe = findXpathWithIndex(\r\n xpathe,\r\n currentNode,\r\n docmt,\r\n othersWithAttr\r\n );\r\n return xpathe;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // If no unique attribute was found, construct XPath by tag name\r\n if (!hasUniqueAttr) {\r\n let tagBasedXPath = isSvg(currentNode)\r\n ? `/*[local-name()='${currentNode.tagName}']`\r\n : `/${currentNode.tagName}`;\r\n\r\n // Handle sibling nodes\r\n if (currentNode.parentElement) {\r\n const siblings = Array.from(\r\n currentNode.parentElement.children\r\n ).filter((childNode) => childNode.tagName === currentNode.tagName);\r\n\r\n // Append index to distinguish between siblings\r\n if (siblings.length > 1) {\r\n const index = siblings.indexOf(currentNode);\r\n tagBasedXPath += `[${index + 1}]`;\r\n }\r\n }\r\n\r\n // Add the constructed tag-based XPath to the parts array\r\n xpathParts.unshift(tagBasedXPath);\r\n } else {\r\n break;\r\n }\r\n\r\n // Move up to the parent node for the next iteration\r\n currentNode = currentNode.parentElement!;\r\n }\r\n\r\n // Combine all parts into the final XPath\r\n const finalXPath = `${xpathParts.join(\"\")}`;\r\n\r\n // Cache the final XPath for this node\r\n relativeXPathCache.set(domNode, finalXPath);\r\n return finalXPath;\r\n } catch (error) {\r\n console.log(error);\r\n return null;\r\n }\r\n};\r\n\r\nexport const getCombinationXpath = (\r\n attribute: Attr,\r\n domNode: HTMLElement | Element\r\n) => {\r\n const combinePattern = [];\r\n let pattern: string = \"\";\r\n\r\n if (\r\n attribute &&\r\n typeof attribute.nodeValue !== \"function\" // &&\r\n // !modifiedElementAttributes?.find(\r\n // (x) => x.element === domNode && x.attributeName === attribute.name\r\n // )\r\n ) {\r\n if (attribute.name === \"class\") {\r\n const docmt =\r\n (domNode.getRootNode?.() as Document | ShadowRoot | null) ??\r\n domNode.ownerDocument;\r\n const classConditions = getClassTokenConditions(\r\n domNode,\r\n attribute.value,\r\n docmt\r\n );\r\n\r\n // Class values are handled token-by-token so numeric tokens are blocked\r\n // without throwing away the other stable class tokens.\r\n for (const classCondition of classConditions) {\r\n pattern = isSvg(domNode)\r\n ? `//*[local-name()='${domNode.tagName}' and ${classCondition}]`\r\n : `//${domNode.tagName}[${classCondition}]`;\r\n\r\n if (pattern) {\r\n return pattern;\r\n }\r\n }\r\n\r\n return;\r\n }\r\n\r\n if (isNumberExist(attribute.value)) {\r\n return;\r\n }\r\n\r\n const contentRes = [...new Set(attribute.value.match(/([^0-9]+)/g))];\r\n if (contentRes?.length) {\r\n for (let i = 0; i < contentRes?.length; i++) {\r\n if (\r\n contentRes[i] &&\r\n replaceWhiteSpaces(contentRes[i].trim())?.length > 2\r\n ) {\r\n if (!reWhiteSpace.test(contentRes[i])) {\r\n combinePattern.push(\r\n `contains(@${attribute.name},${escapeCharacters(\r\n replaceWhiteSpaces(contentRes[i])\r\n ).trim()})`\r\n );\r\n } else {\r\n combinePattern.push(\r\n `contains(@${attribute.name},${escapeCharacters(\r\n contentRes[i].trim()\r\n )})`\r\n );\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (combinePattern?.length) {\r\n pattern = isSvg(domNode)\r\n ? `//*[local-name()='${domNode.tagName}' and ${combinePattern.join(\r\n \" and \"\r\n )}]`\r\n : `//${domNode.tagName}[${combinePattern.join(\" and \")}]`;\r\n return pattern;\r\n }\r\n }\r\n};\r\n\r\nexport const getAttributeCombinationXpath = (\r\n domNode: HTMLElement | Element,\r\n docmt: Document | ShadowRoot,\r\n uniqueAttributes: Attr[],\r\n isTarget: boolean\r\n): string | undefined => {\r\n try {\r\n const candidateAttributes = uniqueAttributes.filter((attr) =>\r\n checkBlockedAttributes(attr, domNode, isTarget)\r\n );\r\n\r\n const buildAttributeConditions = (attribute: Attr): string[] => {\r\n const attrValue = sanitizeAttributeValue(\r\n attribute.name,\r\n attribute.nodeValue\r\n );\r\n\r\n if (!attrValue) return [];\r\n\r\n if (attribute.name === \"class\") {\r\n return getClassTokenConditions(\r\n domNode,\r\n attrValue,\r\n docmt\r\n );\r\n }\r\n\r\n // Strict numeric-attribute validation for direct callers.\r\n if (hasNumericAttributeValue(attrValue)) return [];\r\n\r\n if (!reWhiteSpace.test(attrValue)) {\r\n return [`normalize-space(@${attribute.name})=\"${attrValue}\"`];\r\n }\r\n\r\n return [`@${attribute.name}=\"${attrValue}\"`];\r\n };\r\n\r\n const getTextConditions = (): string[] => {\r\n const rawText = replaceWhiteSpaces(getTextContent(domNode)?.trim() || \"\");\r\n const stableTargetText = getStableTargetText(rawText);\r\n if (hasNumericAttributeValue(rawText)) {\r\n return stableTargetText\r\n ? [\r\n `contains(normalize-space(.),${escapeCharacters(\r\n stableTargetText\r\n )})`\r\n ]\r\n : [];\r\n }\r\n\r\n if (!rawText || rawText.length > 80 || /^\\d+$/.test(rawText)) {\r\n const containerTextCondition = getContainerTextCondition(\r\n domNode,\r\n rawText\r\n );\r\n return containerTextCondition ? [containerTextCondition] : [];\r\n }\r\n\r\n const conditions = new Set<string>();\r\n const containerTextCondition = getContainerTextCondition(\r\n domNode,\r\n rawText\r\n );\r\n\r\n if (containerTextCondition) {\r\n conditions.add(containerTextCondition);\r\n return Array.from(conditions);\r\n }\r\n\r\n if (reWhiteSpace.test(rawText) && rawText.length <= 40 && !rawText.includes(\" \")) {\r\n conditions.add(`text()=${escapeCharacters(rawText)}`);\r\n }\r\n\r\n if (rawText.includes(\" \")) {\r\n conditions.add(\r\n `normalize-space(.)=${escapeCharacters(replaceWhiteSpaces(rawText))}`\r\n );\r\n }\r\n\r\n conditions.add(`contains(text(),${escapeCharacters(rawText)})`);\r\n\r\n return Array.from(conditions);\r\n };\r\n\r\n const generateAttributeCombinations = (\r\n attributes: Attr[],\r\n combinationSize: number\r\n ): Attr[][] => {\r\n const results: Attr[][] = [];\r\n\r\n const build = (startIndex: number, currentGroup: Attr[]) => {\r\n if (currentGroup.length === combinationSize) {\r\n results.push([...currentGroup]);\r\n return;\r\n }\r\n\r\n for (let i = startIndex; i < attributes.length; i++) {\r\n currentGroup.push(attributes[i]);\r\n build(i + 1, currentGroup);\r\n currentGroup.pop();\r\n }\r\n };\r\n\r\n build(0, []);\r\n return results;\r\n };\r\n\r\n const buildXPath = (conditions: string[]) =>\r\n isSvg(domNode)\r\n ? `//*[local-name()='${domNode.tagName}' and ${conditions.join(\" and \")}]`\r\n : `//${domNode.tagName}[${conditions.join(\" and \")}]`;\r\n\r\n const tryAttributeOnlyCombinations = () => {\r\n if (candidateAttributes.length < 2) {\r\n return undefined;\r\n }\r\n\r\n for (\r\n let combinationSize = 2;\r\n combinationSize <= candidateAttributes.length;\r\n combinationSize++\r\n ) {\r\n const attributeGroups = generateAttributeCombinations(\r\n candidateAttributes,\r\n combinationSize\r\n );\r\n\r\n for (const attributeGroup of attributeGroups) {\r\n let xpathConditions: string[] = [];\r\n\r\n for (const attribute of attributeGroup) {\r\n const attributeConditions = buildAttributeConditions(attribute);\r\n\r\n if (!attributeConditions.length) continue;\r\n\r\n xpathConditions.push(...attributeConditions);\r\n }\r\n\r\n if (xpathConditions.length < 2) continue;\r\n\r\n const xpath = buildXPath(xpathConditions);\r\n\r\n let matchCount: number;\r\n\r\n try {\r\n matchCount = getCountOfXPath(xpath, domNode, docmt);\r\n } catch {\r\n continue;\r\n }\r\n\r\n if (matchCount === 1) {\r\n return xpath;\r\n }\r\n }\r\n }\r\n\r\n return undefined;\r\n };\r\n\r\n const attributeOnlyXpath = tryAttributeOnlyCombinations();\r\n if (attributeOnlyXpath) {\r\n return attributeOnlyXpath;\r\n }\r\n\r\n const textConditions = getTextConditions();\r\n if (!textConditions.length || !candidateAttributes.length) {\r\n return;\r\n }\r\n\r\n for (\r\n let combinationSize = 1;\r\n combinationSize <= candidateAttributes.length;\r\n combinationSize++\r\n ) {\r\n const attributeGroups = generateAttributeCombinations(\r\n candidateAttributes,\r\n combinationSize\r\n );\r\n\r\n for (const attributeGroup of attributeGroups) {\r\n let attributeConditions: string[] = [];\r\n\r\n for (const attribute of attributeGroup) {\r\n attributeConditions.push(...buildAttributeConditions(attribute));\r\n }\r\n\r\n if (!attributeConditions.length) {\r\n continue;\r\n }\r\n\r\n for (const textCondition of textConditions) {\r\n const xpathConditions = [...attributeConditions, textCondition];\r\n const xpath = buildXPath(xpathConditions);\r\n\r\n try {\r\n if (getCountOfXPath(xpath, domNode, docmt) === 1) {\r\n return xpath;\r\n }\r\n } catch {\r\n continue;\r\n }\r\n }\r\n }\r\n }\r\n } catch (error) {\r\n console.log(`XPath generation error: ${JSON.stringify(error, null, 2)}`);\r\n }\r\n};\r\n\r\nexport const intermediateXpathStep = (\r\n targetElemt: HTMLElement | Element,\r\n attr: { name: string; value: string },\r\n isTarget: boolean\r\n): string => {\r\n return intermediateXpathSteps(targetElemt, attr, isTarget)[0] || \"\";\r\n};\r\n\r\nexport const intermediateXpathSteps = (\r\n targetElemt: HTMLElement | Element,\r\n attr: { name: string; value: string },\r\n isTarget: boolean\r\n): string[] => {\r\n let isSvgElement = isSvg(targetElemt);\r\n let expression: string = \"\";\r\n\r\n if (checkBlockedAttributes(attr, targetElemt, isTarget)) {\r\n let attrValue = sanitizeAttributeValue(attr.name, attr.value);\r\n const elementName = attr.name;\r\n\r\n if (elementName === \"class\") {\r\n const docmt =\r\n (targetElemt.getRootNode?.() as Document | ShadowRoot | null) ??\r\n targetElemt.ownerDocument;\r\n\r\n // Relative child/parent steps follow the same class-token rule as direct\r\n // XPath candidates: try every stable token, never numeric tokens.\r\n return getClassTokenConditions(targetElemt, attrValue, docmt).map(\r\n (classCondition) =>\r\n isSvgElement\r\n ? `*[local-name()='${targetElemt.tagName}' and ${classCondition}]`\r\n : `${targetElemt.tagName || \"*\"}[${classCondition}]`\r\n );\r\n }\r\n\r\n // Strict numeric-attribute validation for direct callers.\r\n if (hasNumericAttributeValue(attrValue)) {\r\n return [];\r\n }\r\n\r\n if (!reWhiteSpace.test(attrValue)) {\r\n expression = isSvgElement\r\n ? `*[local-name()='${\r\n targetElemt.tagName\r\n }' and normalize-space(@${elementName})=${escapeCharacters(\r\n attrValue\r\n )}]`\r\n : `${\r\n targetElemt.tagName || \"*\"\r\n }[normalize-space(@${elementName})=${escapeCharacters(attrValue)}]`;\r\n } else {\r\n expression = isSvgElement\r\n ? `*[local-name()='${\r\n targetElemt.tagName\r\n }' and @${elementName}=${escapeCharacters(attrValue)}]`\r\n : `${targetElemt.tagName || \"*\"}[@${elementName}=${escapeCharacters(\r\n attrValue\r\n )}]`;\r\n }\r\n }\r\n\r\n return expression ? [expression] : [];\r\n};\r\n\r\nexport const getFilteredTextXPath = (\r\n node: HTMLElement | Element,\r\n docmt: Document | ShadowRoot\r\n): string => {\r\n if (!node.textContent) return \"\";\r\n\r\n const filteredText = getFilteredText(node);\r\n const stableTargetText = getStableTargetText(filteredText);\r\n if (hasNumericAttributeValue(filteredText) && !stableTargetText) {\r\n // Do not build text XPath from purely numeric/dynamic target text.\r\n return \"\";\r\n }\r\n\r\n const textForPredicate =\r\n hasNumericAttributeValue(filteredText) ? stableTargetText : filteredText;\r\n const containerTextCondition = getContainerTextCondition(\r\n node,\r\n textForPredicate\r\n );\r\n\r\n let xpathe;\r\n\r\n if (containerTextCondition) {\r\n xpathe = buildPattern(\r\n containerTextCondition,\r\n isSvg(node),\r\n node.tagName || \"*\"\r\n );\r\n } else if (hasNumericAttributeValue(filteredText)) {\r\n xpathe = buildPattern(\r\n `contains(normalize-space(.),${escapeCharacters(textForPredicate)})`,\r\n isSvg(node),\r\n node.tagName || \"*\"\r\n );\r\n } else if (!reWhiteSpace.test(filteredText) || /\\s/.test(filteredText.trim())) {\r\n xpathe = buildPattern(\r\n `normalize-space(.)=${escapeCharacters(textForPredicate)}`,\r\n isSvg(node),\r\n node.tagName || \"*\"\r\n );\r\n } else {\r\n xpathe = isSvg(node)\r\n ? `//*[local-name()='${node.tagName}' and .=${escapeCharacters(\r\n textForPredicate\r\n )}]`\r\n : `//${node.tagName || \"*\"}[.=${escapeCharacters(textForPredicate)}]`;\r\n }\r\n\r\n return xpathe;\r\n};\r\n\r\nexport const getNormalizedPropertyXPath = (\r\n element: HTMLElement | Element,\r\n prop: string,\r\n value: string\r\n): string => {\r\n if ((prop === \".\" || prop === \"text()\") && hasNumericAttributeValue(value)) {\r\n const { fragment } = getStableTargetTextCandidates(value);\r\n\r\n return fragment\r\n ? buildPattern(\r\n `contains(normalize-space(.),${escapeCharacters(fragment)})`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n )\r\n : \"\";\r\n }\r\n\r\n return buildPattern(\r\n `normalize-space(${prop})=${escapeCharacters(replaceWhiteSpaces(value)).trim()}`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n};\r\n\r\nexport const getStartsWithPropertyXPath = (\r\n element: HTMLElement | Element,\r\n prop: string,\r\n value: string\r\n): string => {\r\n if ((prop === \".\" || prop === \"text()\") && hasNumericAttributeValue(value)) {\r\n const { prefix, fragment } = getStableTargetTextCandidates(value);\r\n const stableText = prefix || fragment;\r\n\r\n return stableText\r\n ? buildPattern(\r\n `starts-with(normalize-space(.),${escapeCharacters(stableText)})`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n )\r\n : \"\";\r\n }\r\n\r\n return buildPattern(\r\n `starts-with(${prop},${escapeCharacters(replaceWhiteSpaces(value)).trim()})`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n};\r\n\r\nexport const getContainsPropertyXPath = (\r\n element: HTMLElement | Element,\r\n prop: string,\r\n value: string\r\n): string => {\r\n if ((prop === \".\" || prop === \"text()\") && hasNumericAttributeValue(value)) {\r\n const { fragment } = getStableTargetTextCandidates(value);\r\n\r\n return fragment\r\n ? buildPattern(\r\n `contains(normalize-space(.),${escapeCharacters(fragment)})`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n )\r\n : \"\";\r\n }\r\n\r\n return buildPattern(\r\n `contains(${prop},${escapeCharacters(replaceWhiteSpaces(value)).trim()})`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n};\r\n\r\nexport const getOrAttributesXPath = (\r\n element: HTMLElement | Element,\r\n attributes: Attr[]\r\n): string => {\r\n const buildAttributeConditions = (attribute: Attr): string[] => {\r\n const attrValue = sanitizeAttributeValue(attribute.name, attribute.value);\r\n\r\n if (!attrValue) {\r\n return [];\r\n }\r\n\r\n if (attribute.name === \"class\") {\r\n const docmt =\r\n (element.getRootNode?.() as Document | ShadowRoot | null) ??\r\n element.ownerDocument;\r\n\r\n return getClassTokenConditions(element, attrValue, docmt);\r\n }\r\n\r\n // Strict numeric-attribute validation for direct callers.\r\n if (hasNumericAttributeValue(attrValue)) return [];\r\n\r\n if (!reWhiteSpace.test(attrValue)) {\r\n return [\r\n `normalize-space(@${attribute.name})=${escapeCharacters(attrValue)}`\r\n ];\r\n }\r\n\r\n return [`@${attribute.name}=${escapeCharacters(attrValue)}`];\r\n };\r\n\r\n const buildTextCondition = (): string | null => {\r\n const rawText = replaceWhiteSpaces(getTextContent(element)?.trim() || \"\");\r\n const stableTargetText = getStableTargetText(rawText);\r\n if (hasNumericAttributeValue(rawText)) {\r\n // OR conditions must not reintroduce full dynamic numeric target text.\r\n return stableTargetText\r\n ? `contains(normalize-space(.),${escapeCharacters(stableTargetText)})`\r\n : null;\r\n }\r\n\r\n const containerTextCondition = getContainerTextCondition(element, rawText);\r\n\r\n if (containerTextCondition) {\r\n return containerTextCondition;\r\n }\r\n\r\n if (!rawText || rawText.length > 80 || /^\\d+$/.test(rawText)) {\r\n return null;\r\n }\r\n\r\n if (reWhiteSpace.test(rawText) && rawText.length <= 40 && !rawText.includes(\" \")) {\r\n return `text()=${escapeCharacters(rawText)}`;\r\n }\r\n\r\n if (rawText.includes(\" \")) {\r\n return `normalize-space(.)=${escapeCharacters(rawText)}`;\r\n }\r\n\r\n return `contains(text(),${escapeCharacters(rawText)})`;\r\n };\r\n\r\n const attributeConditions = attributes\r\n .flatMap((attribute) => buildAttributeConditions(attribute))\r\n .filter(Boolean);\r\n\r\n if (attributeConditions.length >= 2) {\r\n return buildPattern(\r\n `${attributeConditions[0]} or ${attributeConditions[1]}`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n }\r\n\r\n const textCondition = buildTextCondition();\r\n if (attributeConditions.length === 1 && textCondition) {\r\n return buildPattern(\r\n `${attributeConditions[0]} or ${textCondition}`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n }\r\n\r\n return \"\";\r\n};\r\n\r\nconst getTagOnlyXpathCandidateCount = (\r\n xpath: string,\r\n element: HTMLElement | Element,\r\n docmt: Document | ShadowRoot\r\n) => {\r\n try {\r\n return getCountOfXPath(xpath, element, docmt);\r\n } catch (_error) {\r\n return 0;\r\n }\r\n};\r\n\r\nconst getAncestorAnchorCandidates = (\r\n node: HTMLElement | Element,\r\n docmt: Document | ShadowRoot\r\n): string[] => {\r\n const anchors: string[] = [];\r\n const seen = new Set<string>();\r\n const attributes = Array.from(node.attributes || []);\r\n const priorityAttrs = [\r\n \"id\",\r\n \"data-testid\",\r\n \"data-test\",\r\n \"data-qa\",\r\n \"name\",\r\n \"aria-label\",\r\n \"role\"\r\n ];\r\n const orderedAttributes = [\r\n ...priorityAttrs\r\n .map((attrName) => attributes.find((attr) => attr.name === attrName))\r\n .filter(Boolean),\r\n ...attributes.filter((attr) => !priorityAttrs.includes(attr.name))\r\n ] as Attr[];\r\n\r\n const pushAnchor = (xpath: string) => {\r\n if (!xpath || seen.has(xpath)) return;\r\n if (isExactUniqueXpath(xpath, node, docmt)) {\r\n seen.add(xpath);\r\n anchors.push(xpath);\r\n }\r\n };\r\n\r\n orderedAttributes.forEach((attr) => {\r\n if (!checkBlockedAttributes(attr, node, false)) return;\r\n\r\n for (const xpath of getXpathStrings(node, attr.name, attr.value)) {\r\n pushAnchor(xpath);\r\n }\r\n });\r\n\r\n const text = node.textContent?.trim();\r\n if (text && text.length < 40 && node.children.length === 0) {\r\n const textXpath = getFilteredTextXPath(node, docmt);\r\n if (textXpath) {\r\n pushAnchor(textXpath);\r\n }\r\n }\r\n\r\n if (attributes.length > 1) {\r\n const combinationXpath = getAttributeCombinationXpath(\r\n node,\r\n docmt,\r\n attributes,\r\n false\r\n );\r\n if (combinationXpath) {\r\n pushAnchor(combinationXpath);\r\n }\r\n }\r\n\r\n return anchors;\r\n};\r\n\r\nconst getStructuralPathFromAncestor = (\r\n ancestor: HTMLElement | Element,\r\n element: HTMLElement | Element\r\n): string => {\r\n const steps: string[] = [];\r\n let current: Element | null = element;\r\n\r\n while (current && current !== ancestor) {\r\n steps.unshift(getAxisNodeTest(current));\r\n current = current.parentElement;\r\n }\r\n\r\n return current === ancestor ? steps.join(\"/\") : \"\";\r\n};\r\n\r\nexport const getTagOnlyXPath = (\r\n element: HTMLElement | Element,\r\n docmt?: Document | ShadowRoot\r\n): string => {\r\n const root =\r\n docmt ??\r\n ((element.getRootNode?.() as Document | ShadowRoot) ||\r\n element.ownerDocument);\r\n const tagName = getAxisNodeTest(element);\r\n const fallbackXpath = isSvg(element)\r\n ? `//*[local-name()='${element.tagName.toLowerCase()}']`\r\n : `//${element.tagName.toLowerCase()}`;\r\n\r\n for (\r\n let ancestor = element.parentElement;\r\n ancestor;\r\n ancestor = ancestor.parentElement\r\n ) {\r\n const ancestorAnchors = getAncestorAnchorCandidates(ancestor, root);\r\n\r\n for (const ancestorXpath of ancestorAnchors) {\r\n console.log(`Trying ancestor XPath: ${ancestorXpath}`);\r\n\r\n const descendantXpath = `${ancestorXpath}/descendant::${tagName}`;\r\n console.log(`Trying descendant XPath: ${descendantXpath}`);\r\n\r\n const count = getTagOnlyXpathCandidateCount(\r\n descendantXpath,\r\n element,\r\n root\r\n );\r\n console.log(`Match count: ${count}`);\r\n\r\n if (\r\n count === 1 &&\r\n getFirstMatchedNode(descendantXpath, root) === element\r\n ) {\r\n console.log(`Selected XPath: ${descendantXpath}`);\r\n return descendantXpath;\r\n }\r\n\r\n const structuralPath = getStructuralPathFromAncestor(ancestor, element);\r\n if (structuralPath) {\r\n const parentChainXpath = `${ancestorXpath}/${structuralPath}`;\r\n console.log(`Trying descendant XPath: ${parentChainXpath}`);\r\n\r\n const parentChainCount = getTagOnlyXpathCandidateCount(\r\n parentChainXpath,\r\n element,\r\n root\r\n );\r\n console.log(`Match count: ${parentChainCount}`);\r\n\r\n if (\r\n parentChainCount === 1 &&\r\n getFirstMatchedNode(parentChainXpath, root) === element\r\n ) {\r\n console.log(`Selected XPath: ${parentChainXpath}`);\r\n return parentChainXpath;\r\n }\r\n }\r\n }\r\n }\r\n\r\n console.log(`Trying descendant XPath: ${fallbackXpath}`);\r\n const fallbackCount = getTagOnlyXpathCandidateCount(\r\n fallbackXpath,\r\n element,\r\n root\r\n );\r\n console.log(`Match count: ${fallbackCount}`);\r\n\r\n if (\r\n fallbackCount === 1 &&\r\n getFirstMatchedNode(fallbackXpath, root) === element\r\n ) {\r\n console.log(`Selected XPath: ${fallbackXpath}`);\r\n return fallbackXpath;\r\n }\r\n\r\n console.log(\"Selected XPath: \");\r\n return \"\";\r\n};\r\n\r\nexport const getFirstMatchedNode = (\r\n xpath: string,\r\n docmt: Document | ShadowRoot\r\n): HTMLElement | Element | null => {\r\n try {\r\n if (isShadowRootNode(docmt)) {\r\n const { owner, contextNode, cloneElement } =\r\n createShadowEvaluationContext(docmt);\r\n const result = owner.evaluate(\r\n xpath,\r\n contextNode,\r\n null,\r\n XPathResult.FIRST_ORDERED_NODE_TYPE,\r\n null\r\n );\r\n\r\n return (result.singleNodeValue || cloneElement) as\r\n | HTMLElement\r\n | Element\r\n | null;\r\n }\r\n\r\n const result = docmt.evaluate(\r\n xpath,\r\n docmt,\r\n null,\r\n docmt.defaultView!.XPathResult.FIRST_ORDERED_NODE_TYPE,\r\n null\r\n );\r\n\r\n return result.singleNodeValue as HTMLElement | Element | null;\r\n } catch (_error) {\r\n return null;\r\n }\r\n};\r\n\r\nexport const isExactUniqueXpath = (\r\n xpath: string,\r\n element: HTMLElement | Element,\r\n docmt: Document | ShadowRoot\r\n): boolean => {\r\n try {\r\n const { first, second } = evaluateXPathOnce(xpath, docmt);\r\n\r\n return !!first && !second && first === element;\r\n } catch {\r\n return false;\r\n }\r\n};\r\n\r\nexport const getAxisNodeTest = (element: HTMLElement | Element): string => {\r\n return isSvg(element)\r\n ? `*[local-name()='${element.tagName.toLowerCase()}']`\r\n : element.tagName.toLowerCase();\r\n};\r\n\r\nconst hasAnyNumber = (value: string) => /\\d/.test(value);\r\n\r\nexport const getUniqueNodeAnchorXpaths = (\r\n node: HTMLElement | Element,\r\n docmt: Document | ShadowRoot,\r\n isTarget: boolean\r\n): { key: string; value: string }[] => {\r\n if (!(node instanceof Element)) return [];\r\n const seen = new Set<string>();\r\n const anchors: { key: string; value: string }[] = [];\r\n\r\n const attributes = Array.from(node.attributes || []).filter(\r\n (attribute) =>\r\n attribute?.value &&\r\n !hasAnyNumber(attribute.value) &&\r\n checkBlockedAttributes(attribute, node, isTarget)\r\n );\r\n\r\n const pushAnchor = (key: string, value: string) => {\r\n if (!value || seen.has(value)) return;\r\n seen.add(value);\r\n anchors.push({ key, value });\r\n };\r\n\r\n // Priority attributes\r\n const priorityAttrs = [\r\n \"id\",\r\n \"data-testid\",\r\n \"data-test\",\r\n \"data-qa\",\r\n \"name\",\r\n \"aria-label\",\r\n \"role\"\r\n ];\r\n\r\n priorityAttrs.forEach((attrName) => {\r\n const attr = attributes.find((a) => a.name === attrName);\r\n if (!attr) return;\r\n\r\n for (const xpath of getXpathStrings(node, attr.name, attr.value)) {\r\n if (xpath && isExactUniqueXpath(xpath, node, docmt)) {\r\n pushAnchor(`anchor by ${attr.name}`, xpath);\r\n }\r\n }\r\n });\r\n\r\n // Other attributes\r\n attributes.forEach((attribute) => {\r\n if (priorityAttrs.includes(attribute.name)) return;\r\n\r\n for (const xpath of getXpathStrings(node, attribute.name, attribute.value)) {\r\n if (xpath && isExactUniqueXpath(xpath, node, docmt)) {\r\n pushAnchor(`anchor by ${attribute.name}`, xpath);\r\n }\r\n }\r\n });\r\n\r\n // Text (controlled)\r\n const text = node.textContent?.trim();\r\n if (text && text.length < 40 && node.children.length === 0) {\r\n const textXpath = getFilteredTextXPath(node, docmt);\r\n if (textXpath && isExactUniqueXpath(textXpath, node, docmt)) {\r\n pushAnchor(\"anchor by text\", textXpath);\r\n }\r\n }\r\n\r\n // Combination\r\n if (attributes.length > 1) {\r\n const combinationXpath = getAttributeCombinationXpath(\r\n node,\r\n docmt,\r\n attributes,\r\n isTarget\r\n );\r\n\r\n if (combinationXpath && isExactUniqueXpath(combinationXpath, node, docmt)) {\r\n pushAnchor(\"anchor by combination\", combinationXpath);\r\n }\r\n }\r\n\r\n // Tag (last fallback)\r\n const tagXpath = getTagOnlyXPath(node, docmt);\r\n if (tagXpath && isExactUniqueXpath(tagXpath, node, docmt)) {\r\n pushAnchor(\"anchor by tag\", tagXpath);\r\n }\r\n\r\n return anchors;\r\n};\r\n\r\nexport const getTextXpathFunction = (\r\n domNode: HTMLElement | Element\r\n): string | undefined => {\r\n const trimmedText = getTextContent(domNode)?.trim();\r\n const stableTargetText = getStableTargetText(trimmedText);\r\n if (trimmedText && hasNumericAttributeValue(trimmedText)) {\r\n return stableTargetText\r\n ? `contains(normalize-space(.),${escapeCharacters(stableTargetText)})`\r\n : undefined;\r\n }\r\n\r\n const containerTextCondition = getContainerTextCondition(\r\n domNode,\r\n trimmedText\r\n );\r\n\r\n if (containerTextCondition) {\r\n return containerTextCondition;\r\n }\r\n\r\n const filteredText = trimmedText\r\n ? escapeCharacters(deleteGarbageFromInnerText(trimmedText))\r\n : trimmedText;\r\n if (filteredText) {\r\n if (filteredText !== `'${trimmedText}'`) {\r\n return `contains(.,${filteredText})`;\r\n }\r\n if (/\\s/.test(trimmedText)) {\r\n return `normalize-space(.)='${replaceWhiteSpaces(trimmedText)}'`;\r\n }\r\n return `normalize-space(.)='${trimmedText}'`;\r\n }\r\n};\r\n\r\nexport const getXpathString = (\r\n node: HTMLElement | Element,\r\n attrName: string,\r\n attrValue: string\r\n): string => {\r\n return getXpathStrings(node, attrName, attrValue)[0] || \"\";\r\n};\r\n\r\nexport const getXpathStrings = (\r\n node: HTMLElement | Element,\r\n attrName: string,\r\n attrValue: string\r\n): string[] => {\r\n const reWhiteSpace = new RegExp(/^[\\S]+( [\\S]+)*$/gi);\r\n let xpathe: string = \"\";\r\n attrValue = sanitizeAttributeValue(attrName, attrValue);\r\n\r\n if (attrValue) {\r\n if (attrName === \"class\") {\r\n const docmt =\r\n (node.getRootNode?.() as Document | ShadowRoot | null) ??\r\n node.ownerDocument;\r\n // Try every stable class token; numeric tokens are filtered by the\r\n // shared class helper before any XPath candidate is created.\r\n return getClassTokenConditions(node, attrValue, docmt).map((condition) =>\r\n isSvg(node)\r\n ? `//*[local-name()='${node.tagName}' and ${condition}]`\r\n : `//${node.tagName || \"*\"}[${condition}]`\r\n );\r\n }\r\n\r\n // Strict numeric-attribute validation: do not derive contains/starts-with\r\n // predicates from numeric attributes.\r\n if (hasNumericAttributeValue(attrValue)) return [];\r\n\r\n if (!reWhiteSpace.test(attrValue)) {\r\n xpathe = isSvg(node)\r\n ? `//*[local-name()='${\r\n node.tagName\r\n }' and contains(@${attrName},${escapeCharacters(attrValue)})]`\r\n : `//${node.tagName || \"*\"}[contains(@${attrName},${escapeCharacters(\r\n attrValue\r\n )})]`;\r\n } else {\r\n xpathe = isSvg(node)\r\n ? `//*[local-name()='${\r\n node.tagName\r\n }' and @${attrName}=${escapeCharacters(attrValue)}]`\r\n : `//${node.tagName || \"*\"}[@${attrName}=${escapeCharacters(\r\n attrValue\r\n )}]`;\r\n }\r\n }\r\n\r\n return xpathe ? [xpathe] : [];\r\n};\r\n\r\nexport const replaceActualAttributes = (\r\n str: string,\r\n element: { attributes: any }\r\n): string => {\r\n if (str) {\r\n return str.replace(/\\bdisabled\\b/gi, \"flndisabled\");\r\n }\r\n return str;\r\n};\r\n\r\nconst addAttributeSplitCombineXpaths = (\r\n attributes: NamedNodeMap,\r\n targetElemt: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean\r\n): { key: string; value: string }[] => {\r\n const attributesArray = Array.prototype.slice.call(attributes);\r\n const xpaths: { key: string; value: string }[] = [];\r\n try {\r\n attributesArray.map((element) => {\r\n if (checkBlockedAttributes(element, targetElemt, isTarget)) {\r\n const xpth = getCombinationXpath(element, targetElemt);\r\n if (xpth) {\r\n xpaths.push({\r\n key: `split xpath by ${element.name}`,\r\n value: xpth\r\n });\r\n }\r\n }\r\n });\r\n } catch (error) {\r\n console.log(error);\r\n }\r\n\r\n return xpaths;\r\n};\r\n\r\nconst trimAttributePart = (value: string): string =>\r\n value.replace(/^[-_:\\s.]+|[-_:\\s.]+$/g, \"\").trim();\r\n\r\nconst getStableAttributePart = (attributeValue: string): string => {\r\n const value = attributeValue.trim();\r\n const prefix = trimAttributePart(value.split(/\\d/)[0] || \"\");\r\n const prefixParts = prefix\r\n .split(/[-_:\\s.]+/)\r\n .filter(Boolean);\r\n\r\n if (prefixParts.length > 1 && prefixParts[prefixParts.length - 1].length <= 2) {\r\n prefixParts.pop();\r\n }\r\n\r\n const stablePrefix = prefixParts.join(\"-\");\r\n if (stablePrefix.length > 2 && /[a-zA-Z]/.test(stablePrefix)) {\r\n return stablePrefix;\r\n }\r\n\r\n const parts = value\r\n .split(/[\\d\\s\\-_:./\\\\]+/)\r\n .map((part) => trimAttributePart(part))\r\n .filter((part) => part.length > 2 && /[a-zA-Z]/.test(part));\r\n\r\n return parts?.sort((left, right) => right.length - left.length)[0] || \"\";\r\n};\r\n\r\nconst getNumericSafeAttributeCondition = (\r\n attributeName: string,\r\n attributeValue: string\r\n): string => {\r\n // Strict numeric-attribute validation: numeric attributes must not be\r\n // converted into \"safe\" attribute predicates.\r\n return \"\";\r\n};\r\n\r\nconst getNumericSafeAttributeXpath = (\r\n domNode: HTMLElement | Element,\r\n docmt: Document,\r\n attributeName: string,\r\n attributeValue: string\r\n): string => {\r\n // Strict numeric-attribute validation: all numeric attribute XPath fallbacks\r\n // are disabled; callers should continue to text/context strategies.\r\n return \"\";\r\n};\r\n\r\nexport const getReferenceElementsXpath = (\r\n domNode: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean\r\n): { key: string; value: string }[] => {\r\n let nodeXpath1;\r\n const xpaths1 = [];\r\n if (\r\n domNode.textContent &&\r\n (!isTarget || (isTarget && !isNumberExist(domNode.textContent)))\r\n ) {\r\n if (!reWhiteSpace.test(domNode.textContent)) {\r\n const textCondition = getTextXpathFunction(domNode);\r\n if (textCondition) {\r\n nodeXpath1 = isSvg(domNode)\r\n ? `*[local-name()='${domNode.tagName}' and ${textCondition}]`\r\n : `${domNode.tagName}[${textCondition}]`;\r\n xpaths1.push({ key: \"getReferenceElementsXpath\", value: nodeXpath1 });\r\n }\r\n } else {\r\n const textContent = getTextContent(domNode);\r\n const containerTextCondition = getContainerTextCondition(\r\n domNode,\r\n textContent\r\n );\r\n const textCondition = containerTextCondition\r\n ? containerTextCondition\r\n : /\\s/.test(textContent.trim())\r\n ? `normalize-space(.)=${escapeCharacters(\r\n replaceWhiteSpaces(textContent)\r\n )}`\r\n : `.=${escapeCharacters(textContent)}`;\r\n nodeXpath1 = isSvg(domNode)\r\n ? `*[local-name()='${domNode.tagName}' and ${textCondition}]`\r\n : `${domNode.tagName}[${textCondition}]`;\r\n if (nodeXpath1) {\r\n xpaths1.push({ key: \"getReferenceElementsXpath\", value: nodeXpath1 });\r\n }\r\n }\r\n }\r\n\r\n if (domNode.attributes) {\r\n const attributes =\r\n domNode.tagName === \"IMG\"\r\n ? Array.from(domNode.attributes).sort((left, right) => {\r\n if (left.name === \"alt\") return -1;\r\n if (right.name === \"alt\") return 1;\r\n return 0;\r\n })\r\n : Array.from(domNode.attributes);\r\n\r\n for (const attrName of attributes) {\r\n if (checkBlockedAttributes(attrName, domNode, isTarget)) {\r\n let attrValue = attrName.nodeValue;\r\n if (attrValue) {\r\n attrValue = sanitizeAttributeValue(attrName.name, attrValue);\r\n const elementName = attrName.name;\r\n // Strict numeric-attribute validation for direct callers that bypass\r\n // checkBlockedAttributes. Class is tokenized below, so only numeric\r\n // class tokens are skipped by getClassTokenCondition.\r\n if (elementName !== \"class\" && hasNumericAttributeValue(attrValue)) {\r\n continue;\r\n }\r\n\r\n if (elementName === \"class\") {\r\n const classConditions = getClassTokenConditions(\r\n domNode,\r\n attrValue,\r\n docmt\r\n );\r\n\r\n if (!classConditions.length) {\r\n continue;\r\n }\r\n\r\n for (const classCondition of classConditions) {\r\n nodeXpath1 = isSvg(domNode)\r\n ? `*[local-name()='${domNode.tagName}' and ${classCondition}]`\r\n : `${domNode.tagName}[${classCondition}]`;\r\n\r\n xpaths1.push({\r\n key: \"getReferenceElementsXpath\",\r\n value: nodeXpath1\r\n });\r\n }\r\n\r\n continue;\r\n } else {\r\n nodeXpath1 = isSvg(domNode)\r\n ? `*[local-name()='${\r\n domNode.tagName\r\n }' and @${elementName}=${escapeCharacters(attrValue)}]`\r\n : `${domNode.tagName}[@${elementName}=${escapeCharacters(\r\n attrValue\r\n )}]`;\r\n }\r\n\r\n if (nodeXpath1) {\r\n xpaths1.push({\r\n key: \"getReferenceElementsXpath\",\r\n value: nodeXpath1\r\n });\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (!xpaths1?.length) {\r\n const attributesArray = Array.prototype.slice.call(domNode.attributes);\r\n if (attributesArray?.length > 1) {\r\n const combinationXpath = getAttributeCombinationXpath(\r\n domNode,\r\n docmt,\r\n Array.prototype.slice.call(domNode.attributes),\r\n isTarget\r\n );\r\n if (combinationXpath) {\r\n xpaths1.push({\r\n key: \"getReferenceElementsXpath\",\r\n value: combinationXpath\r\n });\r\n }\r\n }\r\n }\r\n\r\n if (!xpaths1?.length) {\r\n const combinePattern = [];\r\n let pattern;\r\n const tag = domNode.tagName;\r\n if (domNode.textContent && isTarget && isNumberExist(domNode.textContent)) {\r\n const targetText = replaceWhiteSpaces(getTextContent(domNode)).trim();\r\n if (targetText?.length > 1) {\r\n combinePattern.push(`contains(text(),${escapeCharacters(targetText)})`);\r\n }\r\n\r\n if (combinePattern?.length) {\r\n if (isSvg(domNode)) {\r\n pattern = `*[local-name()='${tag}' and ${combinePattern.join(\r\n \" and \"\r\n )}]`;\r\n } else {\r\n pattern = `${tag}[${combinePattern.join(\" and \")}]`;\r\n }\r\n\r\n if (pattern)\r\n xpaths1.push({ key: \"getReferenceElementsXpath\", value: pattern });\r\n }\r\n }\r\n }\r\n\r\n if (!xpaths1?.length) {\r\n const xpaths = addAttributeSplitCombineXpaths(\r\n domNode.attributes,\r\n domNode,\r\n docmt,\r\n isTarget\r\n );\r\n if (xpaths?.length) {\r\n xpaths1.concat(xpaths);\r\n }\r\n }\r\n\r\n return xpaths1;\r\n};\r\n\r\nexport const parseXml = (\r\n xmlStr: string,\r\n type: DOMParserSupportedType\r\n): Document | null => {\r\n if (window.DOMParser) {\r\n return new window.DOMParser().parseFromString(xmlStr, type);\r\n }\r\n\r\n return null;\r\n};\r\n\r\nexport const normalizeXPath = (xpath: string): string => {\r\n // Replace text() = \"value\" or text()='value'\r\n xpath = xpath.replace(\r\n /text\\(\\)\\s*=\\s*(['\"])(.*?)\\1/g,\r\n \"normalize-space(.)=$1$2$1\"\r\n );\r\n\r\n // Replace . = \"value\" or .='value'\r\n xpath = xpath.replace(/\\.\\s*=\\s*(['\"])(.*?)\\1/g, \"normalize-space(.)=$1$2$1\");\r\n\r\n return xpath;\r\n};\r\n\r\nexport const findMatchingParenthesis = (\r\n text: string,\r\n openPos: number\r\n): number => {\r\n let closePos = openPos;\r\n let counter = 1;\r\n while (counter > 0) {\r\n const c = text[++closePos];\r\n if (c == \"(\") {\r\n counter++;\r\n } else if (c == \")\") {\r\n counter--;\r\n }\r\n }\r\n return closePos;\r\n};\r\n\r\nexport function canonicalizeXPath(xpath: string): string {\r\n return (\r\n xpath\r\n .toLowerCase()\r\n // replace quoted values\r\n .replace(/'[^']*'/g, \"?\")\r\n .replace(/\"[^\"]*\"/g, \"?\")\r\n // replace numbers in predicates\r\n .replace(/\\[\\d+\\]/g, \"[?]\")\r\n // normalize node names\r\n .replace(/\\/\\/[a-z0-9_-]+/g, \"//node\")\r\n .replace(/\\/[a-z0-9_-]+/g, \"/node\")\r\n );\r\n}\r\n\r\nexport function extractXPathSignatureParts(xpath: string) {\r\n const xp = xpath.toLowerCase();\r\n\r\n const axisMatch = xp.match(\r\n /(ancestor-or-self|ancestor|descendant-or-self|descendant|following-sibling|preceding-sibling|following|preceding|parent|child|self)::/\r\n );\r\n const axis = axisMatch?.[1] ?? \"none\";\r\n\r\n const attrMatch = xp.match(/@([a-z0-9:-]+)/);\r\n const attribute = attrMatch?.[1] ?? \"none\";\r\n\r\n const usesNormalize = xp.includes(\"normalize-space\");\r\n\r\n return { axis, attribute, usesNormalize };\r\n}\r\n\r\nexport function getXPathPattern(xpath: string): string {\r\n const canonical = canonicalizeXPath(xpath);\r\n const parts = extractXPathSignatureParts(xpath);\r\n\r\n return [\r\n \"XPATH\",\r\n `axis:${parts.axis}`,\r\n `attr:${parts.attribute}`,\r\n `normalize:${parts.usesNormalize}`,\r\n `shape:${canonical}`\r\n ].join(\"|\");\r\n}\r\n\r\nexport function escapeAttrValue(value: string): string {\r\n return value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/ /g, \"\\\\ \");\r\n}\r\n\r\nexport function shouldUseSnapshot(xpath: string): boolean {\r\n return /\\[(?:\\s*\\.|\\s*contains\\s*\\(\\s*\\.|\\s*normalize-space\\s*\\(\\s*\\.)/.test(\r\n xpath\r\n );\r\n}\r\n\r\nexport function isUniqueInDOM(\r\n docmt: Document,\r\n name: string,\r\n value: string,\r\n element?: Element | null\r\n): boolean {\r\n const root = (element?.getRootNode?.() as ParentNode | null) ?? docmt;\r\n const queryAll = (selector: string): Element[] => {\r\n try {\r\n return Array.from(root.querySelectorAll(selector));\r\n } catch {\r\n return [];\r\n }\r\n };\r\n\r\n try {\r\n switch (name) {\r\n case \"id\":\r\n return queryAll(`#${escapeAttrValue(value)}`).length === 1;\r\n\r\n case \"name\":\r\n return queryAll(`[name=\"${escapeAttrValue(value)}\"]`).length === 1;\r\n\r\n case \"className\":\r\n return queryAll(`.${value}`).length === 1;\r\n\r\n case \"tagName\":\r\n return queryAll(value).length === 1;\r\n\r\n case \"linkText\":\r\n return (\r\n queryAll(\"a\").filter((a) => a.textContent?.trim() === value)\r\n .length === 1\r\n );\r\n case \"partialLinkText\":\r\n return (\r\n queryAll(\"a\").filter((a) => a.textContent?.includes(value)).length ===\r\n 1\r\n );\r\n case \"cssSelector\":\r\n return queryAll(value).length === 1;\r\n default:\r\n return false;\r\n }\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\nexport const xpathUtils = {\r\n parseXml,\r\n getReferenceElementsXpath,\r\n getAbsoluteXPath,\r\n getRelativeXPath,\r\n getCombinationXpath,\r\n getAttributeCombinationXpath,\r\n getElementFromXpath,\r\n isSvg,\r\n findXpathWithIndex,\r\n isNumberExist,\r\n getTextContent,\r\n getCountOfXPath,\r\n normalizeXPath,\r\n getShadowRoot,\r\n escapeCharacters,\r\n removeParenthesis,\r\n checkBlockedAttributes,\r\n getRelationship,\r\n findMatchingParenthesis,\r\n deleteGarbageFromInnerText,\r\n replaceTempAttributes,\r\n createObserver,\r\n startObserver,\r\n stopObserver,\r\n modifiedElementAttributes,\r\n cspEnabled,\r\n getXPathPattern,\r\n getNormalizedPropertyXPath,\r\n getStartsWithPropertyXPath,\r\n getContainsPropertyXPath,\r\n getOrAttributesXPath,\r\n getTagOnlyXPath,\r\n getFirstMatchedNode,\r\n isExactUniqueXpath,\r\n getAxisNodeTest,\r\n getUniqueNodeAnchorXpaths,\r\n escapeAttrValue,\r\n isUniqueInDOM,\r\n sanitizeAttributeValue,\r\n getClassTokenCondition,\r\n getClassTokenConditions,\r\n getXpathStrings,\r\n intermediateXpathSteps,\r\n getContainerTextCondition\r\n};\r\n","import {\r\n isNumberExist,\r\n getCountOfXPath,\r\n escapeCharacters,\r\n checkBlockedAttributes,\r\n replaceWhiteSpaces,\r\n getTextContent,\r\n getPropertyXPath,\r\n findXpathWithIndex,\r\n getFilteredText,\r\n intermediateXpathStep,\r\n intermediateXpathSteps,\r\n getAttributeCombinationXpath,\r\n getFilteredTextXPath,\r\n getNormalizedPropertyXPath,\r\n getStartsWithPropertyXPath,\r\n getContainsPropertyXPath,\r\n getOrAttributesXPath,\r\n getTagOnlyXPath,\r\n isExactUniqueXpath,\r\n getAxisNodeTest,\r\n getUniqueNodeAnchorXpaths,\r\n isSvg,\r\n getXpathStrings,\r\n reWhiteSpace,\r\n sanitizeAttributeValue,\r\n getContainerTextCondition,\r\n getStableTargetText,\r\n hasNumericAttributeValue\r\n // timeLog\r\n} from \"./xpathHelpers.ts\";\r\n\r\nlet xpathData: { key: string; value: string }[] = [];\r\nlet xpathDataWithIndex: { key: string; value: string; count: number }[] = [];\r\nlet referenceElementMode: boolean = false;\r\nlet xpathCache = new Map();\r\nlet cache = new Map();\r\n\r\nconst parentXpathCache = new Map(); // Cache for parent XPaths\r\n\r\nconst STRATEGY_MAP = {\r\n andConditions: \"and\",\r\n orConditions: \"or\",\r\n contains: \"contains\",\r\n startsWith: \"startsWith\",\r\n normalizeSpace: \"normalizeSpace\",\r\n axes: \"axes\",\r\n index: \"index\",\r\n hardCodedText: \"text\",\r\n tillTag: \"tag\"\r\n} as const;\r\n\r\nconst getStrategyName = (strategy: string) => {\r\n return STRATEGY_MAP[strategy as keyof typeof STRATEGY_MAP] || strategy;\r\n};\r\n\r\nconst withStrategyKey = (\r\n strategy: string,\r\n entries: { key: string; value: string }[]\r\n) => {\r\n return entries.map((entry) => ({\r\n ...entry,\r\n key: entry?.key?.includes(strategy)\r\n ? entry.key\r\n : `${strategy} ${entry.key}`.trim()\r\n }));\r\n};\r\n\r\nconst getUniqueXpathEntries = (entries: { key: string; value: string }[]) => {\r\n const seen = new Set<string>();\r\n\r\n return entries.filter((entry) => {\r\n if (!entry?.value || seen.has(entry.value)) {\r\n return false;\r\n }\r\n\r\n seen.add(entry.value);\r\n return true;\r\n });\r\n};\r\n\r\nconst hasPositionalIndex = (xpath: string) =>\r\n /(?:^|\\/)[^/\\[]+\\[\\d+\\](?=\\/|$)/.test(xpath) ||\r\n /^\\(.+\\)\\[\\d+\\]$/.test(xpath);\r\n\r\nconst removePositionalIndexXpaths = (\r\n entries: { key: string; value: string }[],\r\n isIndex: boolean\r\n) =>\r\n isIndex ? entries : entries.filter((entry) => !hasPositionalIndex(entry.value));\r\n\r\nconst getNormalizedText = (element: HTMLElement | Element) => {\r\n return element?.textContent?.replace(/\\s+/g, \" \")?.trim() || \"\";\r\n};\r\n\r\nconst buildTextStrategyXpaths = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n strategy: string\r\n) => {\r\n const text = getNormalizedText(element);\r\n\r\n if (!text) {\r\n return [];\r\n }\r\n\r\n if (strategy === \"text\") {\r\n const textXpath =\r\n getFilteredTextXPath(element, docmt) ||\r\n getTextXPath(element, docmt, false, false);\r\n\r\n return textXpath ? [{ key: \"xpath by text\", value: textXpath }] : [];\r\n }\r\n\r\n return [];\r\n};\r\n\r\nconst buildPropertyStrategyXpaths = (\r\n element: HTMLElement | Element,\r\n isTarget: boolean,\r\n strategy: string\r\n) => {\r\n const xpaths: { key: string; value: string }[] = [];\r\n const attributes = Array.from(element.attributes || []).filter(\r\n (attribute) =>\r\n attribute?.value && checkBlockedAttributes(attribute, element, isTarget)\r\n );\r\n const text = getNormalizedText(element);\r\n const getStrategyXPath =\r\n strategy === \"contains\"\r\n ? getContainsPropertyXPath\r\n : strategy === \"startsWith\"\r\n ? getStartsWithPropertyXPath\r\n : strategy === \"normalizeSpace\"\r\n ? getNormalizedPropertyXPath\r\n : null;\r\n\r\n if (!getStrategyXPath) {\r\n return xpaths;\r\n }\r\n\r\n attributes.forEach((attribute) => {\r\n const xpath = getStrategyXPath(\r\n element,\r\n `@${attribute.name}`,\r\n attribute.value\r\n );\r\n\r\n if (xpath) {\r\n xpaths.push({\r\n key: `xpath by ${strategy} ${attribute.name}`,\r\n value: xpath\r\n });\r\n }\r\n });\r\n\r\n if (text) {\r\n const xpath = getStrategyXPath(element, \".\", text);\r\n\r\n if (xpath) {\r\n xpaths.push({\r\n key: `xpath by ${strategy} text`,\r\n value: xpath\r\n });\r\n }\r\n }\r\n\r\n return xpaths;\r\n};\r\n\r\nconst buildConditionStrategyXpaths = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean,\r\n strategy: string\r\n) => {\r\n const attributes = Array.from(element.attributes || []).filter(\r\n (attribute) =>\r\n attribute?.value && checkBlockedAttributes(attribute, element, isTarget)\r\n );\r\n\r\n if (strategy === \"and\") {\r\n const combinationXpath = getAttributeCombinationXpath(\r\n element,\r\n docmt,\r\n attributes,\r\n isTarget\r\n );\r\n\r\n return combinationXpath\r\n ? [{ key: \"xpath by combination\", value: combinationXpath }]\r\n : [];\r\n }\r\n\r\n if (strategy === \"or\" && attributes.length >= 1) {\r\n const xpath = getOrAttributesXPath(element, attributes);\r\n return xpath ? [{ key: \"xpath by or\", value: xpath }] : [];\r\n }\r\n\r\n return [];\r\n};\r\n\r\nconst collectSubtreeElements = (root: HTMLElement | Element) => {\r\n if (!root || !(root instanceof Element)) return [];\r\n\r\n const nodes: (HTMLElement | Element)[] = [];\r\n const queue = Array.from(root.children || []);\r\n\r\n while (queue.length) {\r\n const currentNode = queue.shift()!;\r\n\r\n if (currentNode.classList?.contains(\"flntooltip\")) {\r\n continue;\r\n }\r\n\r\n nodes.push(currentNode);\r\n queue.push(...Array.from(currentNode.children || []));\r\n }\r\n\r\n return nodes;\r\n};\r\n\r\nconst getSiblingNodes = (\r\n element: HTMLElement | Element,\r\n direction: \"previous\" | \"next\"\r\n) => {\r\n const nodes: (HTMLElement | Element)[] = [];\r\n let currentNode =\r\n direction === \"previous\"\r\n ? element.previousElementSibling\r\n : element.nextElementSibling;\r\n\r\n while (currentNode) {\r\n if (!currentNode.classList?.contains(\"flntooltip\")) {\r\n nodes.push(currentNode);\r\n }\r\n\r\n currentNode =\r\n direction === \"previous\"\r\n ? currentNode.previousElementSibling\r\n : currentNode.nextElementSibling;\r\n }\r\n\r\n return nodes;\r\n};\r\n\r\ntype AxisSeedNode =\r\n | Element\r\n | {\r\n node: Element;\r\n expand: () => Element[];\r\n };\r\n\r\nconst getDirectionalAxisSeedNodes = (\r\n element: HTMLElement | Element,\r\n direction: \"previous\" | \"next\"\r\n): AxisSeedNode[] => {\r\n const nodes: AxisSeedNode[] = [];\r\n\r\n for (\r\n let currentNode: HTMLElement | Element | null = element;\r\n currentNode?.parentElement;\r\n currentNode = currentNode.parentElement\r\n ) {\r\n let sibling =\r\n direction === \"previous\"\r\n ? currentNode.previousElementSibling\r\n : currentNode.nextElementSibling;\r\n\r\n while (sibling) {\r\n if (!sibling.classList?.contains(\"flntooltip\")) {\r\n nodes.push(sibling);\r\n\r\n nodes.push({\r\n node: sibling,\r\n expand: () => collectSubtreeElements(sibling as HTMLElement)\r\n });\r\n }\r\n\r\n sibling =\r\n direction === \"previous\"\r\n ? sibling.previousElementSibling\r\n : sibling.nextElementSibling;\r\n }\r\n }\r\n\r\n return nodes;\r\n};\r\n\r\nconst isLowQualityNode = (node: Element) => {\r\n if (!(node instanceof Element)) return true;\r\n const text = node.textContent?.trim();\r\n\r\n return (\r\n !node.id &&\r\n !node.className &&\r\n node.attributes.length === 0 &&\r\n (!text || text.length > 60) &&\r\n node.children.length > 2\r\n );\r\n};\r\n\r\nconst buildAxisXpathsForNodes = (\r\n nodes: any[],\r\n axis: string,\r\n targetElement: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean,\r\n key: string,\r\n isIndex: boolean\r\n) => {\r\n const targetNodeTest = getAxisNodeTest(targetElement);\r\n const MAX_INDEX_TRY = 3;\r\n const MAX_CANDIDATES = 5;\r\n\r\n const candidates: { key: string; value: string }[] = [];\r\n\r\n let processed = 0;\r\n const MAX_PROCESS = axis.includes(\"descendant\") ? 10 : 20;\r\n\r\n for (const nodeItem of nodes) {\r\n if (processed++ > MAX_PROCESS) break;\r\n if (candidates.length >= MAX_CANDIDATES) break;\r\n\r\n const node = \"node\" in nodeItem ? nodeItem.node : nodeItem;\r\n\r\n if (!(node instanceof Element)) continue;\r\n if (isLowQualityNode(node)) continue;\r\n\r\n const anchorStages = [\r\n () => getUniqueNodeAnchorXpaths(node, docmt, isTarget),\r\n () => {\r\n const anchors: any[] = [];\r\n const attributes = Array.from(node.attributes || []).filter(\r\n (attr) =>\r\n attr?.value &&\r\n !/\\d/.test(attr.value) &&\r\n checkBlockedAttributes(attr, node, isTarget)\r\n );\r\n\r\n for (const attr of attributes) {\r\n const value = attr.value;\r\n if (!value) continue;\r\n\r\n const containsXpath = getContainsPropertyXPath(\r\n node,\r\n `@${attr.name}`,\r\n value\r\n );\r\n if (containsXpath) {\r\n anchors.push({ key: \"contains\", value: containsXpath });\r\n }\r\n\r\n const startsWithXpath = getStartsWithPropertyXPath(\r\n node,\r\n `@${attr.name}`,\r\n value\r\n );\r\n if (startsWithXpath) {\r\n anchors.push({ key: \"starts-with\", value: startsWithXpath });\r\n }\r\n }\r\n\r\n return anchors;\r\n },\r\n () => [{ key: \"tag\", value: getTagOnlyXPath(node, docmt) }]\r\n ];\r\n\r\n for (let stageIndex = 0; stageIndex < anchorStages.length; stageIndex++) {\r\n let anchors = anchorStages[stageIndex]();\r\n if (!anchors.length) continue;\r\n\r\n if (!isIndex) {\r\n anchors = anchors.filter((a) => !/\\[\\d+\\]/.test(a.value));\r\n }\r\n\r\n const seen = new Set<string>();\r\n anchors = anchors.filter((a) => {\r\n if (seen.has(a.value)) return false;\r\n seen.add(a.value);\r\n return true;\r\n });\r\n\r\n anchors = anchors.slice(0, 5);\r\n\r\n for (const anchor of anchors) {\r\n const xpath = `${anchor.value}/${axis}::${targetNodeTest}`;\r\n\r\n if (isExactUniqueXpath(xpath, targetElement, docmt)) {\r\n candidates.push({ key, value: xpath });\r\n\r\n if (candidates.length >= 2) {\r\n return candidates;\r\n }\r\n\r\n if (candidates.length >= MAX_CANDIDATES) {\r\n return candidates;\r\n }\r\n }\r\n }\r\n\r\n if (isIndex && stageIndex === anchorStages.length - 1) {\r\n for (const anchor of anchors) {\r\n const count = getCountOfXPath(anchor.value, node, docmt);\r\n\r\n for (let i = 1; i <= Math.min(count, MAX_INDEX_TRY); i++) {\r\n const xpath = `(${anchor.value})[${i}]/${axis}::${targetNodeTest}`;\r\n\r\n if (isExactUniqueXpath(xpath, targetElement, docmt)) {\r\n candidates.push({ key, value: xpath });\r\n\r\n if (candidates.length >= 2) {\r\n return candidates;\r\n }\r\n\r\n if (candidates.length >= MAX_CANDIDATES) {\r\n return candidates;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (\"expand\" in nodeItem && nodeItem.expand) {\r\n const expanded = nodeItem.expand();\r\n\r\n let childCount = 0;\r\n for (const child of expanded) {\r\n if (childCount++ > 15) break;\r\n if (candidates.length >= MAX_CANDIDATES) break;\r\n\r\n if (!(child instanceof Element)) continue;\r\n if (isLowQualityNode(child)) continue;\r\n\r\n const anchors = getUniqueNodeAnchorXpaths(child, docmt, isTarget);\r\n\r\n for (const anchor of anchors.slice(0, 5)) {\r\n const xpath = `${anchor.value}/${axis}::${targetNodeTest}`;\r\n\r\n if (isExactUniqueXpath(xpath, targetElement, docmt)) {\r\n candidates.push({ key, value: xpath });\r\n\r\n if (candidates.length >= 2) {\r\n return candidates;\r\n }\r\n\r\n if (candidates.length >= MAX_CANDIDATES) {\r\n return candidates;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return candidates;\r\n};\r\n\r\nconst buildAxesStrategyXpaths = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean,\r\n isIndex: boolean\r\n) => {\r\n // const totalStart = performance.now();\r\n\r\n // let t = performance.now();\r\n\r\n const ancestors: (HTMLElement | Element)[] = [];\r\n\r\n for (\r\n let currentNode = element.parentElement;\r\n currentNode;\r\n currentNode = currentNode.parentElement\r\n ) {\r\n ancestors.push(currentNode);\r\n }\r\n // timeLog(\"ancestors collection\", t);\r\n // t = performance.now();\r\n const descendants = collectSubtreeElements(element);\r\n // timeLog(\"descendants collection\", t);\r\n // t = performance.now();\r\n const previousSiblings = getSiblingNodes(element, \"previous\");\r\n // timeLog(\"previous siblings collection\", t);\r\n\r\n const nextSiblings = getSiblingNodes(element, \"next\");\r\n // timeLog(\"next siblings collection\", t);\r\n\r\n const precedingSeeds = getDirectionalAxisSeedNodes(element, \"previous\");\r\n // timeLog(\"preceding seeds collection\", t);\r\n // t = performance.now();\r\n const followingSeeds = getDirectionalAxisSeedNodes(element, \"next\");\r\n // timeLog(\"following seeds collection\", t);\r\n // t = performance.now();\r\n const scoreNode = (node: Element) => {\r\n let score = 0;\r\n if (node.id) score += 100;\r\n if (node.getAttribute(\"data-testid\")) score += 90;\r\n if (node.className) score += 50;\r\n if (node.children.length === 0) score += 30;\r\n\r\n const text = node.textContent?.trim();\r\n if (text && text.length < 40) score += 40;\r\n\r\n return score;\r\n };\r\n\r\n const limitNodes = (nodes: any[], limit = 20) => {\r\n return nodes.length > limit ? nodes.slice(0, limit) : nodes;\r\n };\r\n\r\n const tiers = [\r\n [\r\n {\r\n nodes: limitNodes(nextSiblings),\r\n axis: \"preceding-sibling\",\r\n key: \"preceding-sibling\"\r\n },\r\n {\r\n nodes: limitNodes(previousSiblings),\r\n axis: \"following-sibling\",\r\n key: \"following-sibling\"\r\n }\r\n ],\r\n [\r\n { nodes: limitNodes(ancestors), axis: \"child\", key: \"child\" },\r\n {\r\n nodes: limitNodes(descendants, 15),\r\n axis: \"parent\",\r\n key: \"parent\"\r\n }\r\n ],\r\n [\r\n { nodes: limitNodes(ancestors), axis: \"descendant\", key: \"descendant\" },\r\n {\r\n nodes: limitNodes(ancestors),\r\n axis: \"descendant-or-self\",\r\n key: \"descendant-or-self\"\r\n },\r\n {\r\n nodes: limitNodes(descendants, 10),\r\n axis: \"ancestor\",\r\n key: \"ancestor\"\r\n },\r\n {\r\n nodes: limitNodes(descendants, 10),\r\n axis: \"ancestor-or-self\",\r\n key: \"ancestor-or-self\"\r\n }\r\n ],\r\n [\r\n {\r\n nodes: limitNodes(precedingSeeds),\r\n axis: \"following\",\r\n key: \"following\"\r\n },\r\n { nodes: limitNodes(followingSeeds), axis: \"preceding\", key: \"preceding\" }\r\n ]\r\n ];\r\n // timeLog(\"nodes sorting\", t);\r\n for (const tier of tiers) {\r\n for (const strategy of tier) {\r\n // const t = performance.now();\r\n\r\n const results = buildAxisXpathsForNodes(\r\n strategy.nodes,\r\n strategy.axis,\r\n element,\r\n docmt,\r\n isTarget,\r\n strategy.key,\r\n isIndex\r\n );\r\n\r\n // timeLog(`strategy ${strategy.key}`, t);\r\n if (results.length > 0) {\r\n // timeLog(\"TOTAL buildAxesStrategyXpaths\", totalStart);\r\n return results;\r\n }\r\n }\r\n }\r\n\r\n return [];\r\n};\r\n\r\nexport const generateIndexedXpaths = (element: Element, docmt: Document) => {\r\n const results = [];\r\n const tag = element.tagName.toLowerCase();\r\n\r\n const globalList = Array.from(docmt.querySelectorAll(tag));\r\n const idx = globalList.indexOf(element) + 1;\r\n\r\n if (idx <= 0) return [];\r\n\r\n // global must always exist)\r\n results.push({\r\n key: \"xpath by index\",\r\n value: `(//${tag})[${idx}]`\r\n });\r\n\r\n // Add at most one scoped/indexed XPath anchored by the nearest stable ancestor.\r\n let current: Element | null = element.parentElement;\r\n\r\n while (current && current !== docmt.body) {\r\n if (current.id || current.className) {\r\n const scopeXpaths = current.id\r\n ? getXpathStrings(current, \"id\", current.id)\r\n : getXpathStrings(current, \"class\", current.className.toString());\r\n\r\n // Scoped index anchors should also try every stable class token before\r\n // walking to a higher ancestor.\r\n for (const scopeXpath of scopeXpaths) {\r\n const scopedDescendants = Array.from(current.querySelectorAll(tag));\r\n const scopedIdx = scopedDescendants.indexOf(element) + 1;\r\n\r\n if (scopedIdx > 0) {\r\n results.push({\r\n key: \"xpath by index\",\r\n value: `(${scopeXpath}//${tag})[${scopedIdx}]`\r\n });\r\n break;\r\n }\r\n }\r\n\r\n if (results.length > 1) {\r\n break;\r\n }\r\n }\r\n\r\n current = current.parentElement;\r\n }\r\n\r\n return results;\r\n};\r\n\r\nconst buildStrategyXpaths = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean,\r\n strategy: string,\r\n fallbackXpaths: { key: string; value: string }[]\r\n) => {\r\n const strategyName = getStrategyName(strategy);\r\n\r\n if (strategyName === \"text\") {\r\n return withStrategyKey(\r\n strategy,\r\n buildTextStrategyXpaths(element, docmt, strategyName)\r\n );\r\n }\r\n\r\n if ([\"contains\", \"startsWith\", \"normalizeSpace\"].includes(strategyName)) {\r\n return withStrategyKey(\r\n strategy,\r\n buildPropertyStrategyXpaths(element, isTarget, strategyName)\r\n );\r\n }\r\n\r\n if ([\"and\", \"or\"].includes(strategyName)) {\r\n return withStrategyKey(\r\n strategy,\r\n buildConditionStrategyXpaths(element, docmt, isTarget, strategyName)\r\n );\r\n }\r\n\r\n if (strategyName === \"axes\") {\r\n return withStrategyKey(\r\n strategy,\r\n buildAxesStrategyXpaths(element, docmt, isTarget, false)\r\n );\r\n }\r\n\r\n if (strategyName === \"index\") {\r\n return withStrategyKey(strategy, generateIndexedXpaths(element, docmt));\r\n }\r\n\r\n if (strategyName === \"tag\") {\r\n return withStrategyKey(strategy, [\r\n { key: \"xpath till tag\", value: getTagOnlyXPath(element, docmt) }\r\n ]);\r\n }\r\n\r\n return [];\r\n};\r\n\r\nconst checkRelativeXpathRelation = (\r\n nodeXpath1: string,\r\n nodeXpath2: string,\r\n targetElemt: HTMLElement | Element,\r\n docmt: Document,\r\n isIndex: boolean,\r\n relationType: string\r\n) => {\r\n if (nodeXpath1 && !referenceElementMode) {\r\n let xpaths;\r\n\r\n if (relationType === \"parent\") {\r\n xpaths = [\r\n // `${nodeXpath1}/descendant::${nodeXpath2}`,\r\n `${nodeXpath1}/descendant-or-self::${nodeXpath2}`,\r\n `${nodeXpath1}/following::${nodeXpath2}`\r\n ];\r\n } else {\r\n xpaths = [\r\n // `${nodeXpath1}/descendant::${nodeXpath2}`,\r\n `${nodeXpath1}/ancestor-or-self::${nodeXpath2}`,\r\n `${nodeXpath1}/preceding::${nodeXpath2}`\r\n ];\r\n }\r\n\r\n // Iterate through XPath patterns\r\n for (const xpath of xpaths) {\r\n // Check if result is already cached to avoid recomputation\r\n if (!xpathCache?.get(xpath)) {\r\n // Compute and store result in cache\r\n xpathCache.set(xpath, getCountOfXPath(xpath, targetElemt, docmt));\r\n }\r\n\r\n const count = xpathCache?.get(xpath);\r\n\r\n // Short-circuit: Return the first valid XPath result found\r\n if (count === 1) {\r\n return xpath;\r\n }\r\n if (count > 1) {\r\n if (xpathDataWithIndex.length) {\r\n if (count < xpathDataWithIndex[0].count) {\r\n xpathDataWithIndex.pop();\r\n xpathDataWithIndex.push({\r\n key: `relative xpath by unique parent ${isIndex ? \"index\" : \"\"}`,\r\n value: xpath,\r\n count\r\n });\r\n }\r\n } else {\r\n xpathDataWithIndex.push({\r\n key: `relative xpath by unique parent ${isIndex ? \"index\" : \"\"}`,\r\n value: xpath,\r\n count\r\n });\r\n }\r\n }\r\n\r\n if (count > 1 && isIndex && !xpathData.length) {\r\n // Try finding XPath with index if count is greater than 1\r\n const indexedXpath = findXpathWithIndex(\r\n xpath,\r\n targetElemt,\r\n docmt,\r\n count\r\n );\r\n\r\n // Cache the indexed XPath result\r\n if (\r\n indexedXpath &&\r\n getCountOfXPath(indexedXpath, targetElemt, docmt) === 1\r\n ) {\r\n xpathData.push({\r\n key: `relative xpath by unique parent ${isIndex ? \"index\" : \"\"}`,\r\n value: indexedXpath\r\n });\r\n }\r\n }\r\n }\r\n }\r\n return null;\r\n};\r\n\r\nconst getUniqueParentXpath = (\r\n domNode: HTMLElement,\r\n docmt: Document,\r\n node: HTMLElement | Element,\r\n isTarget: boolean,\r\n nodeXpath: string,\r\n isIndex: boolean\r\n) => {\r\n try {\r\n if (parentXpathCache.has(domNode)) {\r\n return parentXpathCache.get(domNode);\r\n }\r\n\r\n // Direct XPath construction without loops\r\n const xpathParts = [];\r\n let currentNode = domNode;\r\n\r\n while (currentNode && currentNode.nodeType === 1) {\r\n const hasUniqueAttr = false;\r\n for (const attrName of Array.from(currentNode.attributes)) {\r\n if (checkBlockedAttributes(attrName, currentNode, isTarget)) {\r\n const attrValue = sanitizeAttributeValue(\r\n attrName.name,\r\n attrName.nodeValue\r\n );\r\n const elementName = attrName.name;\r\n\r\n for (const xpathe of getXpathStrings(currentNode, elementName, attrValue)) {\r\n let othersWithAttr;\r\n\r\n // If the XPath does not parse, move to the next unique attribute\r\n try {\r\n othersWithAttr = checkRelativeXpathRelation(\r\n xpathe,\r\n nodeXpath,\r\n node,\r\n docmt,\r\n isIndex,\r\n \"parent\"\r\n );\r\n } catch (ign) {\r\n continue;\r\n }\r\n\r\n // If the attribute isn't actually unique, get it's index too\r\n if (othersWithAttr) {\r\n return othersWithAttr;\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (currentNode.textContent && !currentNode.textContent) {\r\n if (\r\n !isTarget ||\r\n (isTarget && !isNumberExist(currentNode.textContent))\r\n ) {\r\n let xpathe;\r\n\r\n if (!reWhiteSpace.test(currentNode.textContent)) {\r\n xpathe = isSvg(currentNode)\r\n ? `//*[local-name()='${\r\n currentNode.tagName\r\n }' and normalize-space(.)=${escapeCharacters(\r\n getFilteredText(currentNode)\r\n )}]`\r\n : `//${\r\n currentNode.tagName || \"*\"\r\n }[normalize-space(.)=${escapeCharacters(\r\n getFilteredText(currentNode)\r\n )}]`;\r\n } else {\r\n xpathe = isSvg(currentNode)\r\n ? `//*[local-name()='${\r\n currentNode.tagName\r\n }' and .=${escapeCharacters(getFilteredText(currentNode))}]`\r\n : `//${currentNode.tagName || \"*\"}[.=${escapeCharacters(\r\n getFilteredText(currentNode)\r\n )}]`;\r\n }\r\n\r\n const othersWithAttr = checkRelativeXpathRelation(\r\n xpathe,\r\n nodeXpath,\r\n node,\r\n docmt,\r\n isIndex,\r\n \"parent\"\r\n );\r\n if (othersWithAttr) {\r\n return othersWithAttr;\r\n }\r\n } else {\r\n const combinePattern = [];\r\n const contentRes = [\r\n ...new Set(getFilteredText(currentNode).match(/([^0-9]+)/g))\r\n ];\r\n const reWhiteSpace = new RegExp(/^[\\S]+( [\\S]+)*$/gi);\r\n if (contentRes?.length) {\r\n for (let i = 0; i < contentRes?.length; i++) {\r\n if (contentRes[i] && replaceWhiteSpaces(contentRes[i].trim())) {\r\n if (!reWhiteSpace.test(contentRes[i])) {\r\n combinePattern.push(\r\n `contains(.,${escapeCharacters(\r\n replaceWhiteSpaces(contentRes[i])\r\n ).trim()})`\r\n );\r\n } else {\r\n combinePattern.push(\r\n `contains(.,${escapeCharacters(\r\n contentRes[i].trim()\r\n ).trim()})`\r\n );\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (combinePattern?.length) {\r\n const xpathe = isSvg(currentNode)\r\n ? `//*[local-name()='${\r\n currentNode.tagName\r\n }' and ${combinePattern.join(\" and \")}]`\r\n : `//${currentNode.tagName || \"*\"}[${combinePattern.join(\r\n \" and \"\r\n )}]`;\r\n const othersWithAttr = checkRelativeXpathRelation(\r\n xpathe,\r\n nodeXpath,\r\n node,\r\n docmt,\r\n isIndex,\r\n \"parent\"\r\n );\r\n if (othersWithAttr) {\r\n return othersWithAttr;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Construct the XPath based on the tag name\r\n if (!hasUniqueAttr) {\r\n const xpathe = isSvg(currentNode)\r\n ? `/*[local-name()='${currentNode.tagName}']`\r\n : `/${currentNode.tagName}`;\r\n\r\n xpathParts.unshift(xpathe);\r\n } else {\r\n break;\r\n }\r\n\r\n // Move to the parent node for the next iteration\r\n currentNode = currentNode.parentElement!;\r\n }\r\n\r\n // Final constructed XPath\r\n const finalXPath = xpathParts.join(\"\") + nodeXpath;\r\n let count = getCountOfXPath(finalXPath, domNode, docmt);\r\n if (count === 1) {\r\n parentXpathCache.set(domNode, finalXPath); // Cache final result\r\n return finalXPath;\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n return null;\r\n }\r\n};\r\n\r\nconst getParentRelativeXpath = (\r\n domNode: HTMLElement,\r\n docmt: Document,\r\n node: HTMLElement | Element,\r\n isTarget: boolean\r\n) => {\r\n const cache = new Map(); // Cache to store computed results\r\n\r\n if (cache.has(domNode)) {\r\n return cache.get(domNode); // Return cached result if available\r\n }\r\n\r\n const xpathParts = []; // Initialize an array to hold parts of the XPath\r\n let currentNode = domNode; // Start with the provided DOM node\r\n\r\n try {\r\n while (currentNode && currentNode.nodeType === 1) {\r\n // BASE CASE #1: If this isn't an element, we're above the root, return empty string\r\n if (!currentNode.tagName) {\r\n return \"\";\r\n }\r\n\r\n // BASE CASE #2: Check for unique attributes\r\n let uniqueAttrFound = false;\r\n for (const attr of Array.from(currentNode.attributes)) {\r\n if (checkBlockedAttributes(attr, currentNode, isTarget)) {\r\n const attrValue = sanitizeAttributeValue(attr.name, attr.nodeValue);\r\n const elementName = attr.name;\r\n\r\n for (const xpathe of getXpathStrings(currentNode, elementName, attrValue)) {\r\n let othersWithAttr;\r\n\r\n // If the XPath does not parse, move to the next unique attribute\r\n try {\r\n othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);\r\n } catch (ign) {\r\n continue;\r\n }\r\n\r\n // If the attribute is unique, return its XPath\r\n if (othersWithAttr === 1) {\r\n xpathParts.unshift(xpathe);\r\n uniqueAttrFound = true; // Mark that we found at least one unique attribute\r\n break;\r\n }\r\n }\r\n }\r\n\r\n if (uniqueAttrFound) break;\r\n }\r\n\r\n // If no unique attributes, check for text content\r\n if (!uniqueAttrFound && currentNode.textContent && !node.textContent) {\r\n const textXPath = getFilteredTextXPath(currentNode, docmt);\r\n if (textXPath) {\r\n const othersWithAttr = getCountOfXPath(textXPath, currentNode, docmt);\r\n if (othersWithAttr === 1) {\r\n uniqueAttrFound = true; // Mark that we found at least one unique attribute\r\n xpathParts.unshift(textXPath);\r\n break;\r\n }\r\n }\r\n }\r\n\r\n if (!uniqueAttrFound) {\r\n // Construct the XPath based on the tag name\r\n const xpathe = isSvg(currentNode)\r\n ? `/*[local-name()='${currentNode.tagName}']`\r\n : `/${currentNode.tagName}`;\r\n\r\n // Prepend the current XPath part to the array\r\n xpathParts.unshift(xpathe);\r\n } else {\r\n break;\r\n }\r\n // Move to the parent node for the next iteration\r\n currentNode = currentNode.parentElement!;\r\n }\r\n\r\n // Combine all parts into the final XPath\r\n const finalXpath = xpathParts.join(\"\");\r\n cache.set(domNode, finalXpath); // Store result in cache\r\n return finalXpath;\r\n } catch (error) {\r\n console.log(error);\r\n return null;\r\n }\r\n};\r\n\r\nconst getChildRelativeXpath = (\r\n domNode: HTMLElement | Element,\r\n docmt: Document,\r\n node: HTMLElement | Element\r\n) => {\r\n const xpathParts = []; // Initialize an array to hold parts of the XPath.\r\n let currentNode: HTMLElement | Element | null;\r\n\r\n const st = [];\r\n if (\r\n domNode.firstElementChild != null &&\r\n domNode.firstElementChild.classList.contains(\"flntooltip\")\r\n ) {\r\n st.unshift(domNode.firstElementChild);\r\n } else if (domNode.nextElementSibling != null)\r\n st.unshift(domNode.nextElementSibling);\r\n\r\n for (\r\n let m = domNode.parentElement;\r\n m != null && m.nodeType === 1;\r\n m = m.parentElement\r\n ) {\r\n if (m.nextElementSibling) st.unshift(m.nextElementSibling);\r\n }\r\n\r\n try {\r\n do {\r\n let uniqueAttrFound = false;\r\n for (currentNode = st.pop()!; currentNode !== null; ) {\r\n for (const attr of Array.from(currentNode.attributes)) {\r\n if (checkBlockedAttributes(attr, currentNode, true)) {\r\n const attrValue = sanitizeAttributeValue(attr.name, attr.nodeValue);\r\n const elementName = attr.name;\r\n\r\n for (const xpathe of getXpathStrings(currentNode, elementName, attrValue)) {\r\n let othersWithAttr;\r\n\r\n // If the XPath does not parse, move to the next unique attribute\r\n try {\r\n othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);\r\n } catch (ign) {\r\n continue;\r\n }\r\n\r\n // If the attribute is unique, return its XPath\r\n if (othersWithAttr === 1) {\r\n uniqueAttrFound = true; // Mark that we found at least one unique attribute\r\n xpathParts.push(xpathe);\r\n break;\r\n }\r\n }\r\n }\r\n\r\n if (uniqueAttrFound) break;\r\n }\r\n\r\n // If no unique attributes, check for text content\r\n if (!uniqueAttrFound && currentNode.textContent && !node.textContent) {\r\n const textXPath = getFilteredTextXPath(currentNode, docmt);\r\n if (textXPath) {\r\n const othersWithAttr = getCountOfXPath(\r\n textXPath,\r\n currentNode,\r\n docmt\r\n );\r\n if (othersWithAttr === 1) {\r\n uniqueAttrFound = true; // Mark that we found at least one unique attribute\r\n xpathParts.push(textXPath);\r\n break;\r\n }\r\n }\r\n }\r\n\r\n if (!uniqueAttrFound) {\r\n // Construct the XPath based on the tag name\r\n const xpathe = isSvg(currentNode)\r\n ? `/*[local-name()='${currentNode.tagName}']`\r\n : `/${currentNode.tagName}`;\r\n\r\n // Prepend the current XPath part to the array\r\n xpathParts.push(xpathe);\r\n\r\n if (currentNode.firstElementChild != null) {\r\n st.push(currentNode.nextElementSibling);\r\n currentNode = currentNode.firstElementChild;\r\n } else {\r\n currentNode = currentNode.nextElementSibling;\r\n }\r\n } else {\r\n break;\r\n }\r\n }\r\n } while (st.length > 0);\r\n\r\n // Combine all parts into the final XPath\r\n const finalXpath = xpathParts.join(\"\");\r\n cache.set(domNode, finalXpath); // Store result in cache\r\n return finalXpath;\r\n } catch (error) {\r\n console.log(error);\r\n return null;\r\n }\r\n};\r\n\r\nconst getSiblingRelativeXPath = (\r\n domNode: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean,\r\n nodeXpath: string\r\n) => {\r\n try {\r\n const markedSpan = document.querySelector(\".flntooltip\");\r\n\r\n for (\r\n let m = domNode.nextElementSibling;\r\n m !== null && m !== markedSpan;\r\n m = m.nextElementSibling\r\n ) {\r\n processSibling(\r\n m,\r\n domNode,\r\n docmt,\r\n nodeXpath,\r\n \"preceding-sibling\",\r\n isTarget\r\n );\r\n }\r\n\r\n for (\r\n let n = domNode.previousElementSibling;\r\n n !== null && n !== markedSpan;\r\n n = n.previousElementSibling\r\n ) {\r\n processSibling(\r\n n,\r\n domNode,\r\n docmt,\r\n nodeXpath,\r\n \"following-sibling\",\r\n isTarget\r\n );\r\n }\r\n } catch (error) {\r\n console.error(\"sibling error\", error);\r\n return null;\r\n }\r\n};\r\n\r\nconst processSibling = (\r\n sibling: Element,\r\n domNode: HTMLElement | Element,\r\n docmt: Document,\r\n nodeXpath: string,\r\n axis: string,\r\n isTarget: boolean\r\n) => {\r\n try {\r\n if (sibling.hasAttributes()) {\r\n for (const attr of Array.from(sibling.attributes)) {\r\n const xpaths = intermediateXpathSteps(\r\n sibling,\r\n {\r\n name: attr.name,\r\n value: attr.value\r\n },\r\n isTarget\r\n );\r\n\r\n for (let xpathe of xpaths) {\r\n xpathe += `/${axis}::${nodeXpath}`;\r\n\r\n const count = getCountOfXPath(xpathe, sibling, docmt);\r\n\r\n if (count === 1) {\r\n xpathData.push({\r\n key: `xpath by ${axis}`,\r\n value: xpathe\r\n });\r\n return;\r\n } else if (count > 1) {\r\n if (xpathDataWithIndex.length) {\r\n if (count < xpathDataWithIndex[0].count) {\r\n xpathDataWithIndex.pop();\r\n xpathDataWithIndex.push({\r\n key: `relative xpath by ${axis}`,\r\n value: xpathe,\r\n count\r\n });\r\n }\r\n } else {\r\n xpathDataWithIndex.push({\r\n key: `relative xpath by ${axis}`,\r\n value: xpathe,\r\n count\r\n });\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (!isTarget) {\r\n let xpathe;\r\n xpathe = intermediateXpathStep(\r\n sibling,\r\n {\r\n name: \"text\",\r\n value: sibling.textContent\r\n },\r\n isTarget\r\n );\r\n\r\n if (xpathe) {\r\n const count = getCountOfXPath(xpathe, sibling, docmt);\r\n\r\n if (count === 1) {\r\n xpathData.push({\r\n key: `xpath by ${axis}`,\r\n value: xpathe\r\n });\r\n return;\r\n } else if (count > 1) {\r\n if (xpathDataWithIndex.length) {\r\n if (count < xpathDataWithIndex[0].count) {\r\n xpathDataWithIndex.pop();\r\n xpathDataWithIndex.push({\r\n key: `relative xpath by ${axis}`,\r\n value: xpathe,\r\n count\r\n });\r\n }\r\n } else {\r\n xpathDataWithIndex.push({\r\n key: `relative xpath by ${axis}`,\r\n value: xpathe,\r\n count\r\n });\r\n }\r\n }\r\n }\r\n }\r\n } catch (error) {\r\n console.log(`${axis} xpath-error`, error);\r\n }\r\n};\r\n\r\nfunction getXPathUsingAttributeAndText(\r\n attributes: Attr[],\r\n targetElemt: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean\r\n) {\r\n const { tagName } = targetElemt;\r\n const textContent = targetElemt.textContent.trim();\r\n if (!textContent) {\r\n return;\r\n }\r\n const normalizedTextContent = replaceWhiteSpaces(textContent);\r\n const stableTargetText = getStableTargetText(normalizedTextContent);\r\n if (hasNumericAttributeValue(normalizedTextContent) && !stableTargetText) {\r\n return;\r\n }\r\n\r\n const textForPredicate = hasNumericAttributeValue(normalizedTextContent)\r\n ? stableTargetText\r\n : normalizedTextContent;\r\n const containerTextCondition = getContainerTextCondition(\r\n targetElemt,\r\n textForPredicate\r\n );\r\n for (const attrName of attributes) {\r\n if (checkBlockedAttributes(attrName, targetElemt, isTarget)) {\r\n let attrValue = sanitizeAttributeValue(attrName.name, attrName.nodeValue);\r\n const elementName = attrName.name;\r\n const textCondition = containerTextCondition\r\n ? containerTextCondition\r\n : hasNumericAttributeValue(normalizedTextContent)\r\n ? `contains(normalize-space(.),${escapeCharacters(textForPredicate)})`\r\n : /\\s/.test(textContent)\r\n ? `normalize-space(.)=${escapeCharacters(textForPredicate)}`\r\n : `text()=${escapeCharacters(textForPredicate)}`;\r\n const xpath = `//${tagName}[@${elementName}='${attrValue}' and ${textCondition}]`;\r\n if (xpath) {\r\n const count = getCountOfXPath(xpath, targetElemt, docmt);\r\n if (count == 1) {\r\n return xpath;\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\nconst addRelativeXpaths = (\r\n targetElemt: HTMLElement | Element,\r\n docmt: Document,\r\n isIndex: boolean,\r\n isTarget: boolean,\r\n attribute: Attr[]\r\n) => {\r\n try {\r\n let nodeXpath: string[] = [];\r\n let relativeXpath, relativeChildXpath;\r\n xpathData = [];\r\n\r\n console.log(attribute);\r\n if (attribute) {\r\n for (const attrName of attribute) {\r\n const expressions = intermediateXpathSteps(\r\n targetElemt,\r\n {\r\n name: attrName.name,\r\n value: attrName.value\r\n },\r\n isTarget\r\n );\r\n\r\n console.log(expressions[0] || \"\");\r\n for (const expression of expressions) {\r\n if (expression) {\r\n nodeXpath.push(expression);\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (targetElemt.textContent) {\r\n let expression = intermediateXpathStep(\r\n targetElemt,\r\n {\r\n name: \"text\",\r\n value: targetElemt.textContent\r\n },\r\n isTarget\r\n );\r\n\r\n console.log(expression);\r\n if (expression) {\r\n nodeXpath.push(expression);\r\n }\r\n }\r\n\r\n nodeXpath.push(targetElemt.tagName);\r\n\r\n if (nodeXpath?.length) {\r\n for (let i = 0; i < nodeXpath.length; i++) {\r\n if (!xpathData.length) {\r\n getSiblingRelativeXPath(targetElemt, docmt, isTarget, nodeXpath[i]);\r\n\r\n if (!xpathData.length) {\r\n if (!relativeXpath) {\r\n relativeXpath = getParentRelativeXpath(\r\n targetElemt.parentElement!,\r\n docmt,\r\n targetElemt,\r\n isTarget\r\n );\r\n }\r\n\r\n console.log(relativeXpath);\r\n\r\n if (\r\n relativeXpath &&\r\n (relativeXpath.includes(\"@\") ||\r\n relativeXpath.includes(\"text()\") ||\r\n relativeXpath.includes(\".=\")) &&\r\n relativeXpath.match(/\\//g)?.length - 2 < 5\r\n ) {\r\n const fullRelativeXpath = relativeXpath + `/${nodeXpath[i]}`;\r\n const count = getCountOfXPath(\r\n fullRelativeXpath,\r\n targetElemt,\r\n docmt\r\n );\r\n\r\n if (count === 1) {\r\n xpathData.push({\r\n key: \"relative xpath by relative parent\",\r\n value: fullRelativeXpath\r\n });\r\n } else if (count > 1 && isIndex) {\r\n const relativeXpathIndex = findXpathWithIndex(\r\n fullRelativeXpath,\r\n targetElemt,\r\n docmt,\r\n count\r\n );\r\n if (\r\n relativeXpathIndex &&\r\n getCountOfXPath(relativeXpathIndex, targetElemt, docmt) === 1\r\n ) {\r\n xpathData.push({\r\n key: `relative xpath by relative parent ${\r\n isIndex ? \"index\" : \"\"\r\n }`,\r\n value: relativeXpathIndex\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (!xpathData.length) {\r\n if (!relativeChildXpath) {\r\n relativeChildXpath = getChildRelativeXpath(\r\n targetElemt,\r\n docmt,\r\n targetElemt\r\n );\r\n }\r\n\r\n if (\r\n relativeChildXpath &&\r\n (relativeChildXpath.includes(\"@\") ||\r\n relativeChildXpath.includes(\"text()\") ||\r\n relativeChildXpath.includes(\".=\"))\r\n ) {\r\n const fullRelativeXpath = `/${\r\n nodeXpath[i] + relativeChildXpath.substring(1)\r\n }`;\r\n const count = getCountOfXPath(\r\n fullRelativeXpath,\r\n targetElemt,\r\n docmt\r\n );\r\n\r\n if (count === 1) {\r\n xpathData.push({\r\n key: \"relative xpath by relative child\",\r\n value: fullRelativeXpath\r\n });\r\n } else if (count > 1 && isIndex) {\r\n const relativeXpathIndex = findXpathWithIndex(\r\n fullRelativeXpath,\r\n targetElemt,\r\n docmt,\r\n count\r\n );\r\n if (\r\n relativeXpathIndex &&\r\n getCountOfXPath(relativeXpathIndex, targetElemt, docmt) === 1\r\n ) {\r\n xpathData.push({\r\n key: `relative xpath by relative parent ${\r\n isIndex ? \"index\" : \"\"\r\n }`,\r\n value: relativeXpathIndex\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (\r\n xpathData?.length === 1 &&\r\n xpathData?.[0]?.value?.match(/\\[([0-9]+)\\]/gm)?.length! > 3 &&\r\n !referenceElementMode\r\n ) {\r\n if (targetElemt.textContent) {\r\n const txtXpath = getTextXPath(targetElemt, docmt, isIndex, false);\r\n if (txtXpath) {\r\n xpathData.unshift({\r\n key: `xpath by text${isIndex ? \"index\" : \"\"}`,\r\n value: txtXpath\r\n });\r\n }\r\n }\r\n }\r\n\r\n if (!xpathData.length) {\r\n let tempRelativeXpath = getUniqueParentXpath(\r\n targetElemt.parentElement!,\r\n docmt,\r\n targetElemt,\r\n isTarget,\r\n nodeXpath[i],\r\n isIndex\r\n );\r\n\r\n if (tempRelativeXpath) {\r\n xpathData.push({\r\n key: \"xpath by unique parent\",\r\n value: tempRelativeXpath\r\n });\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return xpathData;\r\n } catch (error) {\r\n console.log(error);\r\n }\r\n};\r\n\r\nexport const attributesBasedXPath = (\r\n attr: Attr,\r\n targetElemt: HTMLElement | Element,\r\n docmt: Document,\r\n isIndex: boolean,\r\n isTarget: boolean\r\n) => {\r\n let attrName;\r\n\r\n attrName = attr.name;\r\n // Strict numeric-attribute validation for direct callers.\r\n if (!checkBlockedAttributes(attr, targetElemt, isTarget)) {\r\n return;\r\n }\r\n\r\n let xpath = getPropertyXPath(\r\n targetElemt,\r\n docmt,\r\n `@${attrName}`,\r\n attr.value,\r\n isIndex,\r\n isTarget\r\n );\r\n\r\n return xpath;\r\n};\r\n\r\nexport const getUniqueClassName = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n isIndex: boolean,\r\n isTarget: boolean\r\n) => {\r\n let value = element.className;\r\n if (typeof value !== \"string\") {\r\n value = \"\";\r\n }\r\n value = value?.replace(\"flndisabled\", \"disabled\");\r\n value = sanitizeAttributeValue(\"class\", value);\r\n value = value?.trim();\r\n\r\n if (value && checkBlockedAttributes({ name: \"class\", value }, element, isTarget)) {\r\n return getPropertyXPath(element, docmt, `@class`, value, isIndex, isTarget);\r\n }\r\n};\r\n\r\nexport const getTextXPath = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n isIndex: boolean,\r\n isTarget: boolean\r\n) => {\r\n if ((element.textContent ?? \"\").trim() != \"\") {\r\n const text = getTextContent(element);\r\n\r\n if (text) {\r\n return getPropertyXPath(element, docmt, \".\", text, isIndex, isTarget);\r\n }\r\n }\r\n};\r\n\r\nconst addAllXPathAttributes = (\r\n attributes: Attr[],\r\n targetElemt: HTMLElement | Element,\r\n docmt: Document,\r\n isIndex: boolean,\r\n isTarget: boolean\r\n) => {\r\n const attributesArray = attributes;\r\n try {\r\n attributesArray.map((attr) => {\r\n if (!(attr.name === \"className\" || attr.name === \"class\")) {\r\n // Strict numeric-attribute validation is enforced inside\r\n // checkBlockedAttributes before candidate XPath creation.\r\n if (checkBlockedAttributes(attr, targetElemt, isTarget)) {\r\n const xpth = attributesBasedXPath(\r\n attr,\r\n targetElemt,\r\n docmt,\r\n isIndex,\r\n isTarget\r\n );\r\n if (xpth) {\r\n xpathData.push({\r\n key: `xpath by ${attr.name}${isIndex ? \" index\" : \"\"}`,\r\n value: xpth\r\n });\r\n }\r\n }\r\n }\r\n });\r\n\r\n const txtXpath = getTextXPath(targetElemt, docmt, isIndex, isTarget);\r\n if (txtXpath) {\r\n xpathData.push({\r\n key: `xpath by text${isIndex ? \" index\" : \"\"}`,\r\n value: txtXpath\r\n });\r\n }\r\n\r\n if (\r\n attributesArray.find((element) => element.name === \"className\") &&\r\n checkBlockedAttributes(\r\n attributesArray?.find((element) => element.name === \"className\")!,\r\n targetElemt,\r\n isTarget\r\n )\r\n ) {\r\n let xpath = getUniqueClassName(targetElemt, docmt, isIndex, isTarget);\r\n if (xpath) {\r\n xpathData.push({\r\n key: \"xpath by class\",\r\n value: xpath\r\n });\r\n }\r\n }\r\n\r\n if (!xpathData.length && attributesArray.length > 1) {\r\n const combinationXpath = getAttributeCombinationXpath(\r\n targetElemt,\r\n docmt,\r\n attributesArray,\r\n isTarget\r\n );\r\n if (combinationXpath)\r\n xpathData.push({\r\n key: \"xpath by combination\",\r\n value: combinationXpath\r\n });\r\n }\r\n\r\n if (!xpathData.length) {\r\n const textAttribute = getXPathUsingAttributeAndText(\r\n attributes,\r\n targetElemt,\r\n docmt,\r\n isTarget\r\n );\r\n if (textAttribute)\r\n xpathData.push({\r\n key: \"xpath by textAttribute\",\r\n value: textAttribute\r\n });\r\n }\r\n } catch (error) {\r\n console.log(error);\r\n }\r\n};\r\n\r\nexport const parseDOM = (\r\n element: HTMLElement | Element,\r\n doc: Document,\r\n isIndex: boolean,\r\n isTarget: boolean,\r\n includedAttributes: Attr[] = [],\r\n strategies: string[] = []\r\n) => {\r\n xpathData = [];\r\n console.log(element);\r\n const targetElemt = element;\r\n const rootNode = targetElemt?.getRootNode?.();\r\n const isShadowScoped = rootNode?.nodeType === Node.DOCUMENT_FRAGMENT_NODE;\r\n const docmt = (\r\n isShadowScoped ? rootNode : targetElemt?.ownerDocument || doc\r\n ) as Document;\r\n const tag = targetElemt.tagName;\r\n const { attributes } = targetElemt;\r\n const attributesToUse =\r\n includedAttributes.length > 0 ? includedAttributes : Array.from(attributes);\r\n addAllXPathAttributes(attributesToUse, targetElemt, docmt, isIndex, isTarget);\r\n\r\n if (strategies.length) {\r\n const strategyXpaths = strategies.flatMap((strategy) =>\r\n buildStrategyXpaths(targetElemt, docmt, isTarget, strategy, xpathData)\r\n );\r\n\r\n xpathData = getUniqueXpathEntries(\r\n strategyXpaths.filter(\r\n (xpath) => getCountOfXPath(xpath.value, targetElemt, docmt) === 1\r\n )\r\n );\r\n\r\n return removePositionalIndexXpaths(xpathData, isIndex);\r\n }\r\n\r\n if (!referenceElementMode) {\r\n if (xpathData.length) {\r\n const len = xpathData.length;\r\n for (let i = 0; i < len; i++) {\r\n let xpth = xpathData[i].value;\r\n xpth = \"//*\" + xpth.substring(xpth.indexOf(\"//\") + 2 + tag.length);\r\n const count = getCountOfXPath(xpth, element, docmt);\r\n if (count === 1) {\r\n xpathData.push({\r\n key: `${xpathData[i].key} regex`,\r\n value: xpth\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (!xpathData.length && !isShadowScoped) {\r\n xpathData = buildAxesStrategyXpaths(targetElemt, docmt, isTarget, isIndex);\r\n }\r\n\r\n return removePositionalIndexXpaths(xpathData, isIndex);\r\n};\r\n\r\nconst xpath = {\r\n parseDOM,\r\n getTextXPath,\r\n getUniqueClassName,\r\n attributesBasedXPath,\r\n addAllXPathAttributes,\r\n addRelativeXpaths,\r\n getXPathUsingAttributeAndText,\r\n getSiblingRelativeXPath,\r\n getChildRelativeXpath,\r\n getParentRelativeXpath,\r\n getUniqueParentXpath,\r\n checkRelativeXpathRelation,\r\n buildAxesStrategyXpaths\r\n};\r\n\r\nexport default xpath;\r\n","import { SelectorMode } from \"../types/locator.ts\";\r\nimport { isNumberExist } from \"./xpathHelpers.ts\";\r\n\r\nlet modifiedElementAttributes: [] = [];\r\n\r\nconst escapeCssAttributeValue = (value: string): string => {\r\n return String(value).replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\r\n};\r\n\r\nconst isCssSelectorUnique = (\r\n selector: string,\r\n el: Element,\r\n root: Document | ShadowRoot | ParentNode\r\n): boolean => {\r\n try {\r\n const matches = root.querySelectorAll(selector);\r\n return matches.length === 1 && matches[0] === el;\r\n } catch {\r\n return false;\r\n }\r\n};\r\n\r\nexport const parseCssSelectors = (\r\n el: Element,\r\n mode: SelectorMode = \"single\"\r\n) => {\r\n const selectors: { key: string; value: string }[] = [];\r\n const root = el.getRootNode() as Document | ShadowRoot;\r\n\r\n try {\r\n const idPath = getIdCssPath(el);\r\n if (idPath && isCssSelectorUnique(idPath, el, root)) {\r\n selectors.push({ key: \"cssSelector by id\", value: idPath });\r\n if (mode === \"single\") return selectors;\r\n }\r\n const classPath = getClassCssPath(el);\r\n if (classPath && isCssSelectorUnique(classPath, el, root)) {\r\n selectors.push({ key: \"cssSelector by class\", value: classPath });\r\n }\r\n const namePath = getAttributeCssPath(el);\r\n if (namePath) {\r\n selectors.push({ key: \"cssSelector by name\", value: namePath });\r\n }\r\n // commented out absolute path strategy as it is not performing well and causing performance issues in large DOMs\r\n // const absPath = getAbsoluteCssPath(el);\r\n // if (absPath && isCssSelectorUnique(absPath, el, root)) {\r\n // selectors.push({ key: 'Absolute cssSelector', value: absPath });\r\n // }\r\n } catch (e) {\r\n console.error(e);\r\n }\r\n return selectors;\r\n};\r\n\r\nexport const getIdCssPath = (el: HTMLElement | Element) => {\r\n const view = el.ownerDocument?.defaultView;\r\n if (!view || !(el instanceof view.Element)) return;\r\n const tagName = el.tagName.toLowerCase();\r\n if (tagName.includes(\"style\") || tagName.includes(\"script\")) return;\r\n\r\n const path = [];\r\n while (el?.nodeType === Node.ELEMENT_NODE) {\r\n let selector = el.nodeName?.toLowerCase();\r\n if (el.id && !isNumberExist(el.id)) {\r\n selector += `#${CSS.escape(el.id)}`;\r\n path.unshift(selector);\r\n break;\r\n } else {\r\n let sib = el;\r\n let nth = 1;\r\n if (sib.previousElementSibling) {\r\n while ((sib = sib.previousElementSibling)) {\r\n if (sib.nodeName?.toLowerCase() === selector) nth++;\r\n }\r\n }\r\n\r\n if (nth !== 1) {\r\n selector += `:nth-of-type(${nth})`;\r\n }\r\n\r\n if (nth === 1 && sib?.parentElement?.childElementCount! > 1) {\r\n selector += `:nth-child(${nth})`;\r\n }\r\n }\r\n path.unshift(selector);\r\n el = el.parentElement!;\r\n }\r\n return path.join(\" > \");\r\n};\r\n\r\nconst EXCLUDED_ATTRS = new Set([\"id\", \"class\", \"style\"]);\r\nfunction getAttributeSelectors(el: Element): string[] {\r\n return Array.from(el.attributes)\r\n .filter(\r\n (attr) =>\r\n !EXCLUDED_ATTRS.has(attr?.name?.toLowerCase()) &&\r\n attr.value &&\r\n !isNumberExist(attr.value)\r\n )\r\n .map((attr) => `[${attr.name}=\"${escapeCssAttributeValue(attr.value)}\"]`);\r\n}\r\n\r\nexport const getAttributeCssPath = (el: Element): string | undefined => {\r\n const view = el.ownerDocument?.defaultView;\r\n if (!view || !(el instanceof view.Element)) return;\r\n\r\n const root = el.getRootNode() as ParentNode;\r\n const tag = el.tagName.toLowerCase();\r\n\r\n if (tag === \"style\" || tag === \"script\") return;\r\n\r\n const attrSelectors = getAttributeSelectors(el);\r\n\r\n for (const attrSelector of attrSelectors) {\r\n const candidate = `${tag}${attrSelector}`;\r\n\r\n if (isCssSelectorUnique(candidate, el, root)) {\r\n return candidate;\r\n }\r\n }\r\n return;\r\n};\r\n\r\nexport const getClassCssPath = (el: HTMLElement | Element) => {\r\n const view = el.ownerDocument?.defaultView;\r\n if (!view || !(el instanceof view.Element)) return;\r\n const tagName = el.tagName.toLowerCase();\r\n if (tagName.includes(\"style\") || tagName.includes(\"script\")) return;\r\n\r\n const path = [];\r\n while (el?.nodeType === Node.ELEMENT_NODE) {\r\n let selector = el.nodeName?.toLowerCase();\r\n\r\n if (\r\n typeof el.className === \"string\" &&\r\n el.className &&\r\n !isNumberExist(el.className) &&\r\n !modifiedElementAttributes?.find(\r\n (x: { element: HTMLElement | Element; attributeName: string }) =>\r\n x.element === el && x.attributeName === \"class\"\r\n )\r\n ) {\r\n el.classList.remove(\"marked-element-temp\");\r\n el.classList.remove(\"removePointers\");\r\n if (el.className) {\r\n selector += `.${el.className.trim().replace(/\\s+/g, \".\")}`;\r\n path.unshift(selector);\r\n break;\r\n }\r\n } else {\r\n let sib = el;\r\n let nth = 1;\r\n if (sib.previousElementSibling) {\r\n while ((sib = sib.previousElementSibling)) {\r\n if (sib.nodeName?.toLowerCase() === selector) nth++;\r\n }\r\n }\r\n\r\n if (nth !== 1) {\r\n selector += `:nth-of-type(${nth})`;\r\n }\r\n\r\n if (nth === 1 && sib?.parentElement?.childElementCount! > 1) {\r\n selector += `:nth-child(${nth})`;\r\n }\r\n }\r\n path.unshift(selector);\r\n el = el.parentElement!;\r\n }\r\n return path.join(\" > \");\r\n};\r\n\r\nexport const getAbsoluteCssPath = (el: Element) => {\r\n const view = el.ownerDocument?.defaultView;\r\n if (!view || !(el instanceof view.Element)) return;\r\n\r\n const path: string[] = [];\r\n\r\n while (el && el.nodeType === Node.ELEMENT_NODE) {\r\n const tagName = el.tagName.toLowerCase();\r\n\r\n if (tagName === \"style\" || tagName === \"script\") return;\r\n\r\n let selector = tagName;\r\n\r\n const parent = el.parentNode;\r\n\r\n if (parent) {\r\n const siblings = Array.from(parent.children).filter(\r\n (c) => c.tagName === el.tagName\r\n );\r\n\r\n if (siblings.length > 1) {\r\n selector += `:nth-of-type(${siblings.indexOf(el) + 1})`;\r\n }\r\n }\r\n\r\n path.unshift(selector);\r\n\r\n el = el.parentElement!;\r\n }\r\n\r\n return path.join(\" > \");\r\n};\r\n\r\nexport const cssSelectors = {\r\n parseCssSelectors,\r\n getIdCssPath,\r\n getAttributeCssPath,\r\n getClassCssPath,\r\n getAbsoluteCssPath\r\n};\r\n","import { ElementRecord, Locator } from \"../types/locator.ts\";\r\nimport { parseDOM } from \"./xpath.ts\";\r\nimport { parseCssSelectors } from \"./cssSelector.ts\";\r\nimport {\r\n isNumberExist,\r\n normalizeXPath,\r\n getXPathPattern,\r\n escapeAttrValue,\r\n isUniqueInDOM,\r\n shouldUseSnapshot\r\n} from \"./xpathHelpers.ts\";\r\n\r\nconst isSameXPathStillValid = (\r\n xpath: string,\r\n docmt: Document,\r\n target: Element\r\n): boolean => {\r\n const normalized = normalizeXPath(xpath);\r\n const found = getElementFromXPath(docmt, normalized);\r\n if (found === target) return true;\r\n\r\n if (shouldUseSnapshot(normalized)) {\r\n const result = docmt.evaluate(\r\n normalized,\r\n docmt,\r\n null,\r\n XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,\r\n null\r\n );\r\n\r\n for (let i = 0; i < result.snapshotLength; i++) {\r\n if (result.snapshotItem(i) === target) return true;\r\n }\r\n }\r\n\r\n return false;\r\n};\r\n\r\nconst getElementFromCssSelector = (\r\n docmt: Document,\r\n selector: string\r\n): Element | null => {\r\n try {\r\n const found = docmt.querySelector(selector);\r\n if (found) return found;\r\n } catch (error) {\r\n console.error(\"Invalid CSS selector:\", selector, error);\r\n return null;\r\n }\r\n\r\n return getElementFromShadowRoot(docmt.body, selector);\r\n};\r\n\r\nconst isSameCssSelectorStillValid = (\r\n selector: string,\r\n docmt: Document,\r\n target: Element\r\n): boolean => {\r\n return getElementFromCssSelector(docmt, selector) === target;\r\n};\r\n\r\nconst resolveIsSelfHealed = (\r\n locName: string,\r\n oldValue: string | null | undefined,\r\n newValue: string | null | undefined\r\n): \"Y\" | null => {\r\n if (!oldValue || !newValue) return \"Y\";\r\n return oldValue === newValue ? null : \"Y\";\r\n};\r\n\r\nconst isCssSelectorLocator = (locator: { name?: string | null }): boolean => {\r\n return locator.name?.toLowerCase().includes(\"cssselector\") ?? false;\r\n};\r\n\r\ntype ElementDetails = {\r\n id?: string | null;\r\n className?: string | null;\r\n xpathByText?: string | null;\r\n xpathById?: string | null;\r\n xpathByClass?: string | null;\r\n xpathAbsolute?: string | null;\r\n xpathByName?: string | null;\r\n xpathByPlaceholder?: string | null;\r\n xpathByType?: string | null;\r\n visibleText?: string | null;\r\n relativeXpath?: string | null;\r\n [key: string]: any;\r\n};\r\n\r\nconst getElementFromShadowRoot = (\r\n el: Element | ShadowRoot,\r\n selector: string\r\n): Element | null => {\r\n // const shadowRoot = (element as HTMLElement).shadowRoot;\r\n // if (shadowRoot && !selector.includes(\"dynamic\")) {\r\n // return shadowRoot.querySelector(selector);\r\n // }\r\n\r\n const elements = Array.from(el.querySelectorAll(\"*\"));\r\n\r\n try {\r\n for (let i = 0; i < elements.length; i++) {\r\n if (elements[i].shadowRoot) {\r\n const { shadowRoot } = elements[i];\r\n if (shadowRoot) {\r\n const nestedElement = getElementFromShadowRoot(shadowRoot, selector);\r\n if (nestedElement) {\r\n return nestedElement;\r\n }\r\n if (shadowRoot && !selector.includes(\"dynamic\")) {\r\n return shadowRoot.querySelector(selector);\r\n }\r\n }\r\n }\r\n }\r\n } catch (error) {\r\n console.log(error);\r\n }\r\n return null;\r\n};\r\n\r\nconst getId = (element: Element | null): string | null => {\r\n return element?.id || null;\r\n};\r\n\r\nconst getClassName = (element: Element): string | null => {\r\n return (element as HTMLElement).className || null;\r\n};\r\n\r\nconst getVisibleText = (element: Element): string | null => {\r\n return element.textContent?.trim() || null;\r\n};\r\n\r\nconst getName = (element: Element): string | null => {\r\n const elementEl = element as HTMLElement;\r\n\r\n if (elementEl.hasAttribute(\"name\")) {\r\n const attrValue = elementEl.getAttribute(\"name\");\r\n const name = `${attrValue}`;\r\n return name || null;\r\n }\r\n return null;\r\n};\r\n\r\nconst relations: string[] = [\r\n \"/preceding-sibling\",\r\n \"/following-sibling\",\r\n \"/parent\",\r\n \"/descendant\",\r\n \"/ancestor\",\r\n \"/self\",\r\n \"/ancestor-or-self\",\r\n \"/child\",\r\n \"/preceding\",\r\n \"/following\"\r\n];\r\n\r\nfunction getElementFromXPath(docmt: Document, xpath: string): Element | null {\r\n const window = docmt.defaultView;\r\n if (!window) return null;\r\n\r\n const xpathEvaluator = new window.XPathEvaluator();\r\n const xpathResult = xpathEvaluator.evaluate(\r\n xpath,\r\n docmt,\r\n null,\r\n window.XPathResult.FIRST_ORDERED_NODE_TYPE,\r\n null\r\n );\r\n return xpathResult.singleNodeValue as Element | null;\r\n}\r\n\r\nfunction checkReferenceElementIsValid(\r\n locator: string,\r\n relation: string,\r\n docmt: Document\r\n): string | null {\r\n if (locator.includes(relation)) {\r\n const locatotSplitArray: string[] = locator.split(relation);\r\n const sourceLoc = locatotSplitArray[0].trim();\r\n const window = docmt.defaultView;\r\n if (!window) return null;\r\n if (!locator.includes(\"dynamic\")) {\r\n const xpathEvaluator = new window.XPathEvaluator();\r\n const xpathResult = xpathEvaluator.evaluate(\r\n sourceLoc,\r\n docmt,\r\n null,\r\n window.XPathResult.FIRST_ORDERED_NODE_TYPE,\r\n null\r\n );\r\n\r\n const sourceElement = xpathResult.singleNodeValue;\r\n if (sourceElement) {\r\n const xpathResultComplete = xpathEvaluator.evaluate(\r\n locator,\r\n docmt,\r\n null,\r\n window.XPathResult.FIRST_ORDERED_NODE_TYPE,\r\n null\r\n );\r\n const completeElement = xpathResultComplete.singleNodeValue;\r\n let relativeXpath: string;\r\n if (completeElement) {\r\n relativeXpath = locator;\r\n return relativeXpath;\r\n } else {\r\n console.error(\"Complete Locator is Invalid:\", locator);\r\n relativeXpath = locator;\r\n return relativeXpath;\r\n }\r\n } else {\r\n console.error(\"Source Locator Not Found:\", sourceLoc);\r\n }\r\n }\r\n }\r\n return null;\r\n}\r\n\r\nconst getElementsFromHTML = (\r\n record: ElementRecord,\r\n docmt: Document\r\n): ElementDetails | null => {\r\n const elementsToRemove = docmt.querySelectorAll(\r\n \"script, style, link[rel='stylesheet'], meta, noscript, embed, object, param, source, svg\"\r\n );\r\n\r\n if (elementsToRemove) {\r\n elementsToRemove.forEach((tag) => {\r\n (tag as Element).remove();\r\n });\r\n }\r\n\r\n const finalLocatorsSet: Set<string> = new Set();\r\n let finalLocators: any[] = [];\r\n\r\n function createLocator(base: any, overrides: Partial<any> = {}) {\r\n const oldValue = base?.value;\r\n const newValue = overrides.value ?? base?.value;\r\n const newLocator: any = {\r\n name: overrides.name ?? base?.name,\r\n type: overrides.type ?? base?.type,\r\n value: overrides.value ?? base?.value,\r\n reference: overrides.reference ?? base?.reference,\r\n status: overrides.status ?? base?.status,\r\n isRecorded: overrides.isRecorded ?? base?.isRecorded\r\n };\r\n\r\n const previousSelfHealed = base?.isSelfHealed === \"Y\";\r\n\r\n newLocator.isSelfHealed = previousSelfHealed\r\n ? \"Y\"\r\n : overrides.hasOwnProperty(\"isSelfHealed\")\r\n ? overrides.isSelfHealed\r\n : resolveIsSelfHealed(newLocator.name, oldValue, newValue);\r\n\r\n pushUniqueLocator(newLocator);\r\n }\r\n\r\n function resolveElement(\r\n ctx: Document,\r\n locator: any,\r\n selector: string\r\n ): Element | null {\r\n if (isCssSelectorLocator(locator)) {\r\n return getElementFromCssSelector(ctx, selector);\r\n } else if (locator.name.includes(\"id\") || selector.startsWith(\"#\")) {\r\n return ctx.querySelector(\"#\" + escapeAttrValue(selector));\r\n } else if (locator.name.includes(\"className\") || selector.startsWith(\".\")) {\r\n return ctx.querySelector(\".\" + selector);\r\n } else if (locator.name === \"name\") {\r\n const safeName = escapeAttrValue(selector);\r\n return ctx.querySelector(`[name=\"${safeName}\"]`);\r\n } else if (locator.name === \"tagName\") {\r\n return ctx.querySelector(selector);\r\n } else if (locator.name === \"linkText\") {\r\n return (\r\n Array.from(ctx.querySelectorAll(\"a\")).find(\r\n (a) => a.textContent?.trim() === selector\r\n ) || null\r\n );\r\n } else if (locator.name === \"partialLinkText\") {\r\n return (\r\n Array.from(ctx.querySelectorAll(\"a\")).find((a) =>\r\n a.textContent?.includes(selector)\r\n ) || null\r\n );\r\n } else if (\r\n (locator.name.includes(\"xpath\") || selector.startsWith(\"//\")) &&\r\n !locator.type.match(\"dynamic\")\r\n ) {\r\n const normalizedXPath = normalizeXPath(selector);\r\n const el = getElementFromXPath(ctx, normalizedXPath);\r\n\r\n if (el) {\r\n createLocator(locator, {\r\n value: selector,\r\n isRecorded: String(locator.isRecorded).includes(\"N\") ? \"N\" : \"Y\"\r\n });\r\n }\r\n\r\n return el;\r\n } else {\r\n return ctx.querySelector(selector);\r\n }\r\n }\r\n\r\n function findInIframes(\r\n docmt: Document,\r\n locator: any,\r\n selector: string\r\n ): Element | null {\r\n const iframes = docmt.querySelectorAll(\"iframe\");\r\n\r\n for (const iframe of iframes) {\r\n try {\r\n const iframeDoc =\r\n iframe.contentDocument || iframe.contentWindow?.document;\r\n\r\n if (!iframeDoc) continue;\r\n\r\n const el = resolveElement(iframeDoc, locator, selector);\r\n if (el) return el;\r\n } catch {\r\n continue;\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n function pushUniqueLocator(obj: any) {\r\n const key = `${obj.name}:${obj.value}`;\r\n if (!finalLocatorsSet.has(key)) {\r\n finalLocatorsSet.add(key);\r\n finalLocators.push(obj);\r\n }\r\n }\r\n\r\n /** Locator Value Cleaner (Handles Special Scenarios) **/\r\n const cleanLocatorValue = (\r\n val: string | null | undefined,\r\n type?: string,\r\n isRecorded?: string\r\n ): string | null => {\r\n if (!val) return null;\r\n\r\n let cleaned = val.trim();\r\n\r\n // Return null for empty or literal \"null\"\r\n if (!cleaned || cleaned.toLowerCase() === \"null\") return null;\r\n\r\n // Unescape any escaped quotes\r\n cleaned = cleaned.replace(/\\\\\"/g, '\"').replace(/\\\\'/g, \"'\");\r\n\r\n // Remove surrounding single or double quotes\r\n cleaned = cleaned.replace(/^['\"](.+?)['\"]$/, \"$1\");\r\n\r\n // Replace double single quotes with a single quote inside XPath\r\n cleaned = cleaned.replace(/''/g, \"'\");\r\n\r\n // Normalize double quotes in XPath attribute selectors [@id=\"\" -> [@id='']\r\n cleaned = cleaned.replace(\r\n /\\[@(id|name)=['\"]{2}(.+?)['\"]{2}\\]/g,\r\n \"[@$1='$2']\"\r\n );\r\n\r\n // For DOM selectors (id or name), remove ALL quotes\r\n if (type === \"id\" || type === \"name\") {\r\n cleaned = cleaned.replace(/['\"]/g, \"\").trim();\r\n }\r\n\r\n if (type === \"xpath\" && isRecorded === \"Y\" && !val.startsWith(\"//\"))\r\n return null;\r\n\r\n // Final check for empty strings\r\n if (!cleaned || /^['\"]{2}$/.test(cleaned)) return null;\r\n\r\n return cleaned;\r\n };\r\n\r\n locators: for (const locator of record.locators) {\r\n try {\r\n const isRecorded = String(locator.isRecorded || \"\");\r\n const recordedNLocators = record.locators.filter(\r\n (l) => l.isRecorded === \"N\"\r\n );\r\n\r\n if (recordedNLocators.length > 0) {\r\n for (const locator of recordedNLocators) {\r\n createLocator(locator);\r\n }\r\n }\r\n\r\n const isDynamic = String(locator.value || locator.type || \"\");\r\n if (\r\n isDynamic.includes(\"dynamic\") ||\r\n isDynamic.match(\"dynamic\") ||\r\n isDynamic.includes(\"{\") ||\r\n isDynamic.includes(\"}\")\r\n ) {\r\n createLocator(locator);\r\n continue;\r\n }\r\n\r\n if (record.isShared.includes(\"Y\")) {\r\n break locators;\r\n }\r\n\r\n try {\r\n let targetElement: Element | null = null;\r\n const selectors = locator.value.split(\">>>\");\r\n\r\n for (const selector of selectors) {\r\n if (!docmt) {\r\n console.error(\"Element not found at:\", selector);\r\n break;\r\n }\r\n\r\n const trimmedSelector = selector.trim();\r\n\r\n //normal DOM\r\n targetElement = resolveElement(docmt, locator, trimmedSelector);\r\n\r\n //iframe (if not found)\r\n if (!targetElement) {\r\n targetElement = findInIframes(docmt, locator, trimmedSelector);\r\n }\r\n\r\n //shadow DOM (if still not found)\r\n if (!targetElement) {\r\n targetElement = getElementFromShadowRoot(\r\n docmt.body,\r\n trimmedSelector\r\n );\r\n }\r\n\r\n if (!targetElement) {\r\n console.error(\"Element not found at:\", trimmedSelector);\r\n break;\r\n }\r\n }\r\n\r\n const locatorExists = (name: string, value: string): boolean => {\r\n const key = `${name}:${value}`;\r\n return finalLocatorsSet.has(key);\r\n };\r\n\r\n if (targetElement) {\r\n const payloadXPaths = record.locators.filter(\r\n (l) => l.name === \"xpath\" && l.value\r\n );\r\n const payloadCssSelectors = record.locators.filter(\r\n (l) => isCssSelectorLocator(l) && l.value\r\n );\r\n\r\n for (const px of payloadXPaths) {\r\n if (isSameXPathStillValid(px.value, docmt, targetElement))\r\n createLocator(px, {\r\n isSelfHealed: null\r\n });\r\n }\r\n\r\n for (const cssLocator of payloadCssSelectors) {\r\n if (\r\n isSameCssSelectorStillValid(\r\n cssLocator.value,\r\n docmt,\r\n targetElement\r\n )\r\n )\r\n createLocator(cssLocator, {\r\n isSelfHealed: null\r\n });\r\n }\r\n\r\n const existingXPaths = finalLocators.filter(\r\n (l) => l.name === \"xpath\" && l.value\r\n );\r\n\r\n // Track XPath patterns already used\r\n const usedXPathPatterns = new Set<string>(\r\n existingXPaths.map((x) => getXPathPattern(x.value))\r\n );\r\n\r\n const excludedAttributes: string[] = [];\r\n const idValue = getId(targetElement);\r\n if (\r\n idValue &&\r\n !locatorExists(\"id\", idValue) &&\r\n !isNumberExist(idValue)\r\n ) {\r\n const prevId = record.locators.find((l) => l.name === \"id\");\r\n if (isUniqueInDOM(docmt, \"id\", idValue, targetElement)) {\r\n excludedAttributes.push(\"id\");\r\n createLocator(prevId, {\r\n name: \"id\",\r\n type: \"static\",\r\n isRecorded: \"Y\",\r\n value: idValue\r\n });\r\n }\r\n }\r\n\r\n const tagName = targetElement.tagName;\r\n if (tagName && !locatorExists(\"tagName\", tagName)) {\r\n const prevTag = record.locators.find((l) => l.name === \"tagName\");\r\n if (isUniqueInDOM(docmt, \"tagName\", tagName, targetElement)) {\r\n excludedAttributes.push(\"tagName\");\r\n createLocator(prevTag, {\r\n name: \"tagName\",\r\n type: \"static\",\r\n isRecorded: \"Y\",\r\n value: tagName\r\n });\r\n }\r\n }\r\n\r\n const textValue = getVisibleText(targetElement);\r\n if (textValue && !isNumberExist(textValue)) {\r\n const prevLinkText = record.locators.find(\r\n (l) => l.name === \"linkText\"\r\n );\r\n if (isUniqueInDOM(docmt, \"linkText\", textValue, targetElement)) {\r\n excludedAttributes.push(\"linkText\");\r\n createLocator(prevLinkText, {\r\n name: \"linkText\",\r\n type: \"static\",\r\n isRecorded: \"Y\",\r\n value: textValue\r\n });\r\n }\r\n }\r\n\r\n const nameLocator = getName(targetElement);\r\n if (\r\n nameLocator &&\r\n !locatorExists(\"name\", nameLocator) &&\r\n !isNumberExist(nameLocator)\r\n ) {\r\n const prevName = record.locators.find((l) => l.name === \"name\");\r\n if (isUniqueInDOM(docmt, \"name\", nameLocator, targetElement)) {\r\n excludedAttributes.push(\"name\");\r\n createLocator(prevName, {\r\n name: \"name\",\r\n type: \"static\",\r\n isRecorded: \"Y\",\r\n value: nameLocator\r\n });\r\n }\r\n }\r\n\r\n const classValue = getClassName(targetElement);\r\n if (\r\n classValue &&\r\n classValue.trim() !== \"\" &&\r\n !classValue.includes(\" \") &&\r\n !locatorExists(\"className\", classValue) &&\r\n !isNumberExist(classValue)\r\n ) {\r\n const prevClassLocator = record.locators.find(\r\n (l) => l.name === \"className\"\r\n );\r\n if (isUniqueInDOM(docmt, \"className\", classValue, targetElement)) {\r\n excludedAttributes.push(\"className\");\r\n createLocator(prevClassLocator, {\r\n name: \"className\",\r\n type: \"static\",\r\n isRecorded: \"Y\",\r\n value: classValue\r\n });\r\n }\r\n }\r\n parseCssSelectors(targetElement, \"single\").forEach(\r\n (cssSelector) => {\r\n if (\r\n cssSelector.value &&\r\n !locatorExists(\"cssSelector\", cssSelector.value)\r\n ) {\r\n createLocator(undefined, {\r\n name: \"cssSelector\",\r\n value: cssSelector.value,\r\n type: \"static\",\r\n isRecorded: \"Y\"\r\n });\r\n }\r\n }\r\n );\r\n const allAttributes = Array.from(targetElement.attributes);\r\n const includedAttributes = allAttributes.filter(\r\n (attr) => !excludedAttributes.includes(attr.name)\r\n );\r\n\r\n //If any direct locator is broken then we consider it as broken xpath\r\n\r\n let xpathResults: any[] = [];\r\n try {\r\n xpathResults =\r\n parseDOM(targetElement, docmt, false, true, includedAttributes) ??\r\n [];\r\n } catch (error) {\r\n console.error(\"Error generating XPath candidates:\", error);\r\n }\r\n\r\n if (xpathResults?.length !== 0) {\r\n const brokenPayloadXPaths = payloadXPaths.filter(\r\n (px) => !isSameXPathStillValid(px.value, docmt, targetElement)\r\n );\r\n\r\n let xpathAdded = 0;\r\n\r\n for (const brokenPx of brokenPayloadXPaths) {\r\n if (xpathAdded >= brokenPayloadXPaths.length) break;\r\n\r\n const originalPattern = getXPathPattern(brokenPx.value);\r\n if (usedXPathPatterns.has(originalPattern)) continue;\r\n\r\n const match = xpathResults.find(\r\n (r) => r.value && getXPathPattern(r.value) === originalPattern\r\n );\r\n\r\n if (match?.value) {\r\n createLocator(brokenPx, {\r\n name: \"xpath\",\r\n value: match.value,\r\n type: \"static\",\r\n isRecorded: \"Y\",\r\n isSelfHealed: \"Y\"\r\n });\r\n\r\n usedXPathPatterns.add(originalPattern);\r\n xpathAdded++;\r\n }\r\n }\r\n if (xpathAdded < brokenPayloadXPaths.length) {\r\n for (const result of xpathResults) {\r\n if (xpathAdded >= brokenPayloadXPaths.length) break;\r\n if (!result.value) continue;\r\n\r\n const pattern = getXPathPattern(result.value);\r\n if (usedXPathPatterns.has(pattern)) continue;\r\n\r\n createLocator(result, {\r\n name: \"xpath\",\r\n value: result.value,\r\n type: \"static\",\r\n isRecorded: \"Y\",\r\n isSelfHealed: \"Y\"\r\n });\r\n\r\n usedXPathPatterns.add(pattern);\r\n xpathAdded++;\r\n }\r\n }\r\n }\r\n for (const locator of record.locators) {\r\n try {\r\n for (const loc of record.locators) {\r\n if (!loc.value) continue;\r\n\r\n for (const relation of relations) {\r\n if (loc.value.includes(relation)) {\r\n const relativeXpath = checkReferenceElementIsValid(\r\n loc.value,\r\n relation,\r\n docmt\r\n );\r\n if (relativeXpath) {\r\n createLocator(loc, {\r\n name: \"xpath\",\r\n value: relativeXpath,\r\n isRecorded:\r\n locator.isRecorded !== \"\" &&\r\n locator.isRecorded !== null\r\n ? locator.isRecorded\r\n : \"Y\"\r\n });\r\n break;\r\n }\r\n }\r\n }\r\n }\r\n } catch (error) {\r\n console.error(\"Error processing locator:\", locator, error);\r\n }\r\n }\r\n if (finalLocators.length < 5) {\r\n const fallbackCandidates = [\r\n { name: \"id\", value: getId(targetElement) },\r\n { name: \"name\", value: getName(targetElement) },\r\n { name: \"className\", value: getClassName(targetElement) },\r\n { name: \"tagName\", value: targetElement.tagName },\r\n { name: \"linkText\", value: getVisibleText(targetElement) }\r\n ];\r\n\r\n for (const candidate of fallbackCandidates) {\r\n if (finalLocators.length > 4) break;\r\n\r\n const { name, value } = candidate;\r\n if (!value) continue;\r\n if (isNumberExist(value)) continue;\r\n if (locatorExists(name, value)) continue;\r\n if (name === \"className\" && value.includes(\" \")) continue;\r\n if (isUniqueInDOM(docmt, name, value, targetElement)) {\r\n createLocator(undefined, {\r\n name,\r\n type: \"static\",\r\n value,\r\n isRecorded: \"Y\",\r\n isSelfHealed: \"Y\"\r\n });\r\n }\r\n }\r\n\r\n if (finalLocators.length < 5) {\r\n parseCssSelectors(targetElement, \"multiple\").forEach(\r\n (cssSelector) => {\r\n if (finalLocators.length > 4) return;\r\n if (!cssSelector.value) return;\r\n if (locatorExists(\"cssSelector\", cssSelector.value)) return;\r\n if (\r\n !isUniqueInDOM(\r\n docmt,\r\n \"cssSelector\",\r\n cssSelector.value,\r\n targetElement\r\n )\r\n ) {\r\n return;\r\n }\r\n\r\n createLocator(undefined, {\r\n name: \"cssSelector\",\r\n type: \"static\",\r\n value: cssSelector.value,\r\n isRecorded: \"Y\",\r\n isSelfHealed: \"Y\"\r\n });\r\n }\r\n );\r\n }\r\n }\r\n\r\n const finalAutoHealedLocators = finalLocators.map((obj) => ({\r\n ...obj,\r\n value: cleanLocatorValue(obj.value, obj.name, obj.isRecorded)\r\n }));\r\n\r\n const jsonResult = [\r\n {\r\n name: `${record.name}`,\r\n desc: `${record.desc}`,\r\n type: `${record.type}`,\r\n locators: finalAutoHealedLocators.filter(\r\n (locator) => locator?.value != null && locator.value !== \"\"\r\n ),\r\n isShared: `${record.isShared}`,\r\n projectId: `${record.projectId}`,\r\n projectType: `${record.projectType}`,\r\n isRecorded: `${record.isRecorded}`,\r\n folder: `${record.folder}`,\r\n parentId: `${record.parentId}`,\r\n parentName: `${record.parentName}`,\r\n platform: `${record.platform}`,\r\n licenseId: `${record.licenseId}`,\r\n licenseType: `${record.licenseType}`,\r\n userId: `${record.userId}`\r\n }\r\n ];\r\n\r\n return jsonResult;\r\n }\r\n } catch (error) {\r\n console.error(\"Error processing locator:\", locator, error);\r\n continue;\r\n }\r\n } catch (error) {\r\n console.error(\"Error processing locator:\", locator, error);\r\n continue;\r\n }\r\n }\r\n return null;\r\n};\r\n\r\nexport { getElementsFromHTML };\r\n","import { JSDOM, VirtualConsole } from \"jsdom\";\r\nimport { ElementRecord } from \"../types/locator\";\r\nimport { getElementsFromHTML } from \"../utils/getElementsFromHTML\";\r\n\r\nasync function createXPathAPI() {\r\n const fromHTML = async (html: string) => {\r\n const virtualConsole = new VirtualConsole();\r\n\r\n const dom = new JSDOM(\r\n html\r\n .replace(/\\\\\"/g, '\"')\r\n .replace(/\\\\r/g, \"\\r\")\r\n .replace(/\\\\n/g, \"\\n\")\r\n .replace(/\\\\t/g, \"\\t\"),\r\n {\r\n pretendToBeVisual: true,\r\n runScripts: \"dangerously\",\r\n resources: \"usable\",\r\n virtualConsole\r\n }\r\n );\r\n\r\n const { window } = dom;\r\n\r\n // ✅ Browser globals parity (REQUIRED)\r\n // @ts-ignore\r\n global.document = window.document;\r\n // @ts-ignore\r\n global.Node = window.Node;\r\n // @ts-ignore\r\n global.Element = window.Element;\r\n // @ts-ignore\r\n global.HTMLElement = window.HTMLElement;\r\n // @ts-ignore\r\n global.SVGElement = window.SVGElement;\r\n\r\n const cssApi = window.CSS ?? {};\r\n if (!cssApi.escape) {\r\n cssApi.escape = (value: string) =>\r\n String(value).replace(/[^a-zA-Z0-9_-]/g, \"\\\\$&\");\r\n }\r\n // @ts-ignore\r\n global.CSS = cssApi;\r\n\r\n // @ts-ignore\r\n global.XPathResult = window.XPathResult;\r\n // @ts-ignore\r\n global.XPathEvaluator = window.XPathEvaluator;\r\n\r\n return {\r\n autoHeal: async (data: ElementRecord) => {\r\n return getElementsFromHTML(data, window.document);\r\n }\r\n };\r\n };\r\n\r\n return { fromHTML };\r\n}\r\n\r\nexport default createXPathAPI;\r\n"],"names":["reWhiteSpace","xpathEvalCache","WeakMap","evaluateXPathOnce","xpath","docmt","owner","contextNode","nodeType","ownerDocument","document","getXPathContext","nodeCache","get","Map","set","cached","result","evaluate","XPathResult","ORDERED_NODE_ITERATOR_TYPE","evalResult","first","iterateNext","second","modifiedElementAttributes","INTERNAL_CLASS_TOKENS","Set","normalizeClassTokens","classValue","split","map","token","trim","filter","has","sort","hasNumericAttributeValue","value","test","getClassTokenConditions","element","allowNumericToken","allClassTokens","classTokens","length","escapeCharacters","buildPattern","isSvg","tagName","toLowerCase","count","getTagOnlyXpathCandidateCount","left","right","TEST_ID_ATTRIBUTE_NAMES","sanitizeAttributeValue","attributeName","attributeValue","join","replace","isNumberExist","str","pattern","getTextContent","targetElement","textContent","getCountOfXPath","multiElementReferenceMode","Array","isArray","matchCount","includes","node","error","console","text","indexOf","replaceWhiteSpaces","getContainerTextCondition","normalizedText","children","fragment","slice","getStableTargetTextCandidates","prefix","part","getStableTargetText","createShadowEvaluationContext","root","tempDoc","implementation","createHTMLDocument","wrapper","createElement","innerHTML","body","appendChild","cloneForElement","candidate","defaultView","Element","path","current","parentElement","parent","unshift","from","parentNode","getElementPathFromRoot","container","index","next","item","getElementAtPath","cloneElements","Boolean","cloneElement","checkBlockedAttributes","attribute","isTarget","sanitizedValue","name","some","x","isModified","find","doc","hasGeneratedNumericSegment","SVGElement","getPropertyXPath","prop","isIndex","combinePattern","mergePattern","isAttributeProp","startsWith","isTextProp","normalizedTextProp","hasTextSpacing","stableTargetText","textValueForPredicate","containerTextCondition","classCondition","splitText","contentRes","match","endsWith","endIndex","startIndexString","endIndexString","i","push","getAttributeCombinationXpath","domNode","uniqueAttributes","candidateAttributes","attr","buildAttributeConditions","attrValue","nodeValue","getTextConditions","rawText","conditions","add","generateAttributeCombinations","attributes","combinationSize","results","build","startIndex","currentGroup","pop","buildXPath","attributeOnlyXpath","attributeGroups","attributeGroup","xpathConditions","attributeConditions","tryAttributeOnlyCombinations","textConditions","textCondition","log","JSON","stringify","getFilteredTextXPath","filteredText","childNodes","textForPredicate","xpathe","getNormalizedPropertyXPath","getStartsWithPropertyXPath","stableText","getContainsPropertyXPath","getOrAttributesXPath","flatMap","getRootNode","buildTextCondition","_error","getAncestorAnchorCandidates","anchors","seen","priorityAttrs","orderedAttributes","attrName","pushAnchor","isExactUniqueXpath","forEach","getXpathStrings","textXpath","combinationXpath","getStructuralPathFromAncestor","ancestor","steps","getAxisNodeTest","getTagOnlyXPath","fallbackXpath","ancestorAnchors","ancestorXpath","descendantXpath","getFirstMatchedNode","structuralPath","parentChainXpath","parentChainCount","fallbackCount","Node","DOCUMENT_FRAGMENT_NODE","FIRST_ORDERED_NODE_TYPE","singleNodeValue","getUniqueNodeAnchorXpaths","key","a","tagXpath","RegExp","condition","normalizeXPath","getXPathPattern","canonical","canonicalizeXPath","parts","xp","axisMatch","axis","attrMatch","usesNormalize","extractXPathSignatureParts","escapeAttrValue","isUniqueInDOM","queryAll","selector","querySelectorAll","xpathData","STRATEGY_MAP","andConditions","orConditions","contains","normalizeSpace","axes","hardCodedText","tillTag","withStrategyKey","strategy","entries","entry","removePositionalIndexXpaths","getNormalizedText","collectSubtreeElements","nodes","queue","currentNode","shift","classList","getSiblingNodes","direction","previousElementSibling","nextElementSibling","getDirectionalAxisSeedNodes","sibling","expand","isLowQualityNode","id","className","buildAxisXpathsForNodes","targetNodeTest","candidates","processed","MAX_PROCESS","nodeItem","anchorStages","containsXpath","startsWithXpath","stageIndex","anchor","Math","min","expanded","childCount","child","buildAxesStrategyXpaths","ancestors","descendants","previousSiblings","nextSiblings","precedingSeeds","followingSeeds","limitNodes","limit","tiers","tier","buildStrategyXpaths","fallbackXpaths","strategyName","getStrategyName","getTextXPath","buildTextStrategyXpaths","xpaths","getStrategyXPath","buildPropertyStrategyXpaths","buildConditionStrategyXpaths","tag","idx","scopeXpaths","toString","scopeXpath","scopedIdx","generateIndexedXpaths","addAllXPathAttributes","targetElemt","attributesArray","xpth","attributesBasedXPath","txtXpath","getUniqueClassName","textAttribute","normalizedTextContent","getXPathUsingAttributeAndText","parseDOM","includedAttributes","strategies","rootNode","isShadowScoped","attributesToUse","strategyXpaths","getUniqueXpathEntries","len","substring","isCssSelectorUnique","el","matches","parseCssSelectors","mode","selectors","idPath","getIdCssPath","classPath","getClassCssPath","namePath","getAttributeCssPath","e","view","ELEMENT_NODE","nodeName","CSS","escape","sib","nth","childElementCount","EXCLUDED_ATTRS","getAttributeSelectors","String","attrSelectors","attrSelector","remove","isSameXPathStillValid","target","normalized","getElementFromXPath","shouldUseSnapshot","ORDERED_NODE_SNAPSHOT_TYPE","snapshotLength","snapshotItem","getElementFromCssSelector","found","querySelector","getElementFromShadowRoot","isSameCssSelectorStillValid","isCssSelectorLocator","locator","elements","shadowRoot","nestedElement","getId","getClassName","getVisibleText","getName","elementEl","hasAttribute","getAttribute","relations","window","XPathEvaluator","checkReferenceElementIsValid","relation","sourceLoc","xpathEvaluator","relativeXpath","getElementsFromHTML","record","elementsToRemove","finalLocatorsSet","finalLocators","createLocator","base","overrides","oldValue","newValue","newLocator","type","reference","status","isRecorded","previousSelfHealed","isSelfHealed","hasOwnProperty","locName","resolveIsSelfHealed","obj","pushUniqueLocator","resolveElement","ctx","safeName","findInIframes","iframes","iframe","iframeDoc","contentDocument","contentWindow","cleanLocatorValue","val","cleaned","locators","recordedNLocators","l","isDynamic","isShared","trimmedSelector","locatorExists","payloadXPaths","payloadCssSelectors","px","cssLocator","existingXPaths","usedXPathPatterns","excludedAttributes","idValue","prevId","prevTag","textValue","prevLinkText","nameLocator","prevName","prevClassLocator","cssSelector","undefined","xpathResults","brokenPayloadXPaths","xpathAdded","brokenPx","originalPattern","r","loc","fallbackCandidates","finalAutoHealedLocators","jsonResult","desc","projectId","projectType","folder","parentId","parentName","platform","licenseId","licenseType","userId","async","createXPathAPI","fromHTML","html","virtualConsole","VirtualConsole","dom","JSDOM","pretendToBeVisual","runScripts","resources","global","HTMLElement","cssApi","autoHeal","data"],"mappings":"kDAAO,MAAMA,EAAe,oBAe5B,IAAIC,EAAiB,IAAIC,QAEzB,MAiDaC,EAAoB,CAACC,EAAeC,KAC/C,MAAMC,MAAEA,EAAKC,YAAEA,GAlDO,CAACF,IACvB,MAAMC,EACe,IAAnBD,EAAMG,SACDH,EACDA,EAAMI,eAAiBC,SAQ7B,MAAO,CAAEJ,QAAOC,YAJK,IAAnBF,EAAMG,UAAqC,IAAnBH,EAAMG,UAAqC,KAAnBH,EAAMG,SAClDH,EACAC,IAwCyBK,CAAgBN,GAG/C,IAAIO,EAAYX,EAAeY,IAAIN,GAC9BK,IACHA,EAAY,IAAIE,IAChBb,EAAec,IAAIR,EAAaK,IAIlC,MAAMI,EAASJ,EAAUC,IAAIT,GAC7B,GAAIY,EAAQ,OAAOA,EAGnB,MAAMC,EAASX,EAAMY,SACnBd,EACAG,EACA,KACAY,YAAYC,2BACZ,MAMIC,EAAyB,CAAEC,MAHnBL,EAAOM,cAGmBC,OAFzBP,EAAOM,eAKtB,OAFAX,EAAUG,IAAIX,EAAOiB,GAEdA,GAGF,IAAII,EAKL,GAEN,MAAMC,EAAwB,IAAIC,IAAI,CACpC,sBACA,mBAGIC,EACJC,IAECA,GAAc,IACZC,MAAM,OACNC,IAAKC,GAAUA,EAAMC,QACrBC,OAAQF,GAAUA,IAAUN,EAAsBS,IAAIH,IACtDI,OAEQC,EAA4BC,GACvC,KAAKC,KAAKD,GAAS,IAERE,EAA0B,CACrCC,EACAZ,EACAxB,EACAqC,GAAoB,KAIpB,MAAMC,EAAiBf,EAAqBC,GACtCe,EAAcD,EAAeT,OAAQF,KACzCU,IAA4BL,EAAyBL,IAGvD,OAAKY,EAAYC,OAEa,IAA1BF,EAAeE,QAAuC,IAAvBD,EAAYC,OAGtC,CAAC,UAAUC,EAAiBF,EAAY,OAG1CA,EACJb,IAAKC,IACJ,MAAM5B,EAAQ2C,EACZ,mBAAmBD,EAAiBd,MACpCgB,EAAMP,GACNA,EAAQQ,QAAQC,eAGlB,MAAO,CACLlB,QACAmB,MAAOC,EAA8BhD,EAAOqC,EAASpC,MAGxD+B,KAAK,CAACiB,EAAMC,IAGPD,EAAKrB,MAAMa,SAAWS,EAAMtB,MAAMa,OAC7BQ,EAAKrB,MAAMa,OAASS,EAAMtB,MAAMa,OAGlCQ,EAAKF,MAAQG,EAAMH,OAE3BpB,IAAI,EAAGC,WAAY,mBAAmBc,EAAiBd,OA9B1B,IA6C5BuB,EAA0B,IAAI5B,IAAI,CACtC,cACA,eACA,YACA,UACA,WAaW6B,EAAyB,CACpCC,EACAC,IAEKA,EAIiB,UAAlBD,GAA+C,cAAlBA,EACxB7B,EAAqB8B,GAAgBC,KAAK,KAG5CD,EAAeE,QAAQ,iBAAkB,IAAI3B,OAP3C,GAgFE4B,EAAiBC,GACV,KACDvB,KAAKuB,GAGXf,EAAe,CAC1BgB,EACAf,EACAC,IAEOD,EACH,qBAAqBC,UAAgBc,KACrC,KAAKd,KAAWc,KAGTC,EACXC,IAEA,MAAMC,EAAcD,GAAeC,YAiBjC,OAAOA,GAUEC,EAAkB,CAC7B/D,EACAqC,EACApC,EACA+D,GAAqC,KAErC,IACE,MAAM9C,MAAEA,EAAKE,OAAEA,GAAWrB,EAAkBC,EAAOC,GAEnD,IAAKiB,EAAO,OAAO,EAGnB,IAAKE,EACH,OAAOF,IAAUmB,EAAU,EAAI,EAIjC,GAAI2B,GAA6BC,MAAMC,QAAQ7B,GAAU,CACvD,IAAI8B,EAAa,EAKjB,GAHI9B,EAAQ+B,SAASlD,IAAQiD,IACzB9B,EAAQ+B,SAAShD,IAAS+C,IAEX,IAAfA,EAAkB,OAAO,EAC7B,MAGMtD,GAFe,IAAnBZ,EAAMG,SAAkBH,EAAqBA,EAAMI,eAEhCS,SACnBd,EACAC,EACA,KACAc,YAAYC,2BACZ,MAGF,IAAIqD,EACJ,KAAQA,EAAOxD,EAAOM,eACpB,GAAIkB,EAAQ+B,SAASC,KACnBF,IACmB,IAAfA,GAAkB,OAAO,EAIjC,OAAO,CACT,CAEA,OAAO,CACT,CAAE,MAAOG,GAEP,OADAC,QAAQD,MAAM,2BAA2BtE,IAASsE,GAC3C,CACT,GAGW5B,EAAoB8B,IAC/B,GAAIA,EAAM,CACR,IAA4B,IAAtBA,EAAKC,QAAQ,KACjB,MAAO,IAAID,KAEb,IAA4B,IAAtBA,EAAKC,QAAQ,KACjB,MAAO,IAAID,IAEf,CACA,MAAO,IAAIA,MA0FAE,EAAsBhB,GAC7BA,EACKA,EAAIF,QAAQ,SAAU,KAAK3B,OAG7B6B,EAGIiB,EAA4B,CACvCtC,EACAmC,KAEA,MAAMI,EAAiBF,GAAoBF,GAAQ,IAAI3C,QAEvD,IAAKQ,EAAQwC,SAASpC,QAAUmC,EAAenC,QAAU,GACvD,MAAO,GAGT,MAAMqC,EAAWF,EAAelD,MAAM,OAAOqD,MAAM,EAAG,GAAGxB,KAAK,KAI9D,OAAOuB,EAASrC,OAAS,EACrB,+BAA+BC,EAAiBoC,MAChD,IAGOE,EACXR,IAEA,MAAMI,EAAiBF,GAAoBF,GAAQ,IAAI3C,QAEvD,IAAK+C,IAAmB3C,EAAyB2C,GAC/C,MAAO,CAAEK,OAAQL,EAAgBE,SAAUF,GAG7C,MAAMK,EAASL,EACZlD,MAAM,SAAS,GACf8B,QAAQ,0BAA2B,IACnC3B,OACGiD,EAAWG,EACdvD,MAAM,cACNC,IAAKuD,GAASR,EAAmBQ,GAAMrD,QACvCC,OAAQoD,GAASA,EAAKzC,OAAS,GAAK,WAAWN,KAAK+C,IACpD3B,KAAK,KACL1B,OAIH,MAAO,CACLoD,OAAQA,EAAOxC,OAAS,GAAK,WAAWN,KAAK8C,GAAUA,EAAS,GAChEH,SAAUA,EAASrC,OAAS,EAAIqC,EAAW,KAIlCK,EACXX,GACWQ,EAA8BR,GAAMM,SAqDpCM,EAAgC,CAC3CC,EACAhD,KAEA,MAAMiD,EACJD,EAAKhF,cAAckF,eAAeC,mBAAmB,gBACjDC,EAAUH,EAAQI,cAAc,OACtCD,EAAQE,UAAYN,EAAKM,UACzBL,EAAQM,KAAKC,YAAYJ,GAEzB,MAAMK,EAAmBC,IACvB,KACGA,GACCA,aAAqBV,EAAKhF,cAAc2F,YAAaC,SAEvD,OAAO,KAGT,MAAMC,EA1DqB,EAC7Bb,EACAhD,KAEA,MAAM6D,EAAiB,GACvB,IAAIC,EAA0B9D,EAE9B,KAAO8D,GAAWA,EAAQC,eAAe,CACvC,MAAMC,EAAkBF,EAAQC,cAChCF,EAAKI,QAAQrC,MAAMsC,KAAKF,EAAOxB,UAAUJ,QAAQ0B,IACjDA,EAAUE,CACZ,CAEA,OAAKF,GAAWA,EAAQK,aAAenB,GAIvCa,EAAKI,QAAQrC,MAAMsC,KAAKlB,EAAKR,UAAUJ,QAAQ0B,IACxCD,GAJE,MA4CMO,CAAuBpB,EAAMU,GAC1C,OAAOG,EAtCc,EACvBQ,EACAR,KAEA,IAAIC,EACFO,EAEF,IAAK,MAAMC,KAAST,EAAM,CACxB,MAAMU,EAAuBT,EAAQtB,SAASgC,KAAKF,GACnD,IAAKC,EACH,OAAO,KAGTT,EAAUS,CACZ,CAEA,OAAOT,GAsBSW,CAAiBrB,EAASS,GAAQ,MAG5Ca,EAAgB9C,MAAMC,QAAQ7B,GAChCA,EAAQV,IAAKoE,GAAcD,EAAgBC,IAAYjE,OAAOkF,SAC9D,GAEEC,EAAehD,MAAMC,QAAQ7B,GAC/B,KACAyD,EAA2B,MAE/B,MAAO,CACL5F,MAAOoF,EACPnF,YAAasF,EACbwB,eACAF,kBAISG,EAAyB,CACpCC,EAIAtD,EACAuD,KAEA,MAAMC,EAAiBjE,EACrB+D,EAAUG,KACVH,EAAUjF,OAGZ,IAAKmF,GAA8C,kBAArBF,GAAWjF,MACvC,OAAO,EAUT,GARsB,CACpB,OACA,QACA,KACA,MACA,aACA,6BAEgBqF,KAAMC,GAAMA,IAAMH,GAClC,OAAO,EAGT,GADqB,CAAC,QAAS,uBAAwB,SACtCE,KAAMC,GAAMA,IAAML,EAAUG,MAC3C,OAAO,EAGT,MAAMG,EAAapG,GAA2BqG,KAC3CF,GACCA,EAAEG,MAAQ9D,EAAcxD,eACxBmH,EAAEnF,UAAYwB,GACd2D,EAAEnE,gBAAkB8D,EAAUG,MAElC,OAAIG,MAImC,IAAnCN,GAAWG,MAAM7C,QAAQ,OAAe0C,GAAWG,MAAM7E,OAAS,KAIvC,mBAApB0E,EAAUjF,QAlerBmB,EAse+B8D,EAAUG,KArezChE,EAqe+C+D,IAne/ClE,EAAwBpB,IAAIsB,EAAcP,iBAPT,CAACQ,GAClC,+BAA+BnB,KAAKmB,GAOpCsE,CAA2BtE,MAyeJ,UAAnB6D,EAAUG,OAAoBrF,EAAyBoF,OA9e1B,IACjChE,EACAC,GAkgBWV,EAASP,GACbA,aAAmBwF,WAmCfC,EAAmB,CAC9BzF,EACApC,EACA8H,EACA7F,EACA8F,EACAZ,KAEA,GAAIlF,EAAO,CACT,MAAMW,QAAEA,GAAYR,EACpB,IAAIU,EACAkF,EAAiB,GACrB,MAAMC,EAAe,GACrB,IAAIvE,EAEJ,MAAMwE,EAAkBJ,EAAKK,WAAW,KAClCC,EAAsB,MAATN,GAAyB,WAATA,EAC7BO,EAAqBD,EAAa,IAAMN,EACxCQ,EAAiBF,GAAc,KAAKlG,KAAKD,EAAML,QAC/C2G,EAAmBH,EAAalD,EAAoBjD,GAASA,EAC7DuG,EACJJ,GAAcpG,EAAyBC,GAASsG,EAAmBtG,EAC/DwG,EAAyBL,EAC3B1D,EAA0BtC,EAASoG,GACnC,GAEJ,GAAa,WAATV,EAAmB,CACrB,IAAK,MAAMY,KAAkBvG,EAC3BC,EACAH,EACAjC,GAWA,GAPA0D,EAAUhB,EACRgG,EACA/F,EAAMP,GACNA,EAAQQ,QAAQC,eAGlBC,EAAQgB,EAAgBJ,EAAStB,EAASpC,GAC5B,IAAV8C,IAAgBiF,EAClB,OAAOrE,EAIX,MACF,CAEA,GAAI0E,GAAcpG,EAAyBC,KAAWsG,EAGpD,OAGF,GAAItG,KAAWiG,IAAoB1E,EAAcvB,MAE7CyB,EADE+E,EACQ/F,EACR+F,EACA9F,EAAMP,GACNA,EAAQQ,QAAQC,eAETyF,EACC5F,EACR,mBAAmB2F,MAAuB5F,EACxCgC,EAAmB+D,IACnB5G,SACFe,EAAMP,GACNA,EAAQQ,QAAQC,eAERlD,EAAauC,KAAKD,GASlB,KAAKW,KAAWkF,KAAQrF,EAChC+F,MATQ9F,EACR,mBAAmBoF,MAASrF,EAC1BgC,EAAmB+D,IACnB5G,SACFe,EAAMP,GACNA,EAAQQ,QAAQC,eAQpBC,EAAQgB,EAAgBJ,EAAStB,EAASpC,GAE5B,IAAV8C,IAAgBiF,GAClB,OAAOrE,EAIX,GAAIwE,GAAmBlG,EAAyBC,GAG9C,OAGF,GAAIA,GAASkF,EAAU,CACrB,MAAMwB,EAAY1G,EAAMR,MAAM,KAC9B,GAAIkH,GAAWnG,OACb,GAAyB,IAArBmG,EAAUnG,OAAc,CAC1B,MAAMoG,EAAa,IAAI,IAAItH,IAAIqH,EAAU,GAAGE,MAAM,gBAwClD,GAvCID,GAAYpG,QAAU,GAEtBoG,EAAW,IACXnE,EAAmBmE,EAAW,GAAGhH,SAASY,OAAS,GAE/CP,EAAMkG,WAAWS,EAAW,MAM5BZ,EALGrI,EAAauC,KAAK0G,EAAW,IAKf,eAAed,KAAQrF,EACtCmG,EAAW,IACXhH,UANe,eAAekG,KAAQrF,EACtCgC,EAAmBmE,EAAW,KAC9BhH,WAUNgH,GAAYpG,OAAS,GAErBoG,EAAWA,EAAWpG,OAAS,IAC/BiC,EAAmBmE,EAAWA,EAAWpG,OAAS,GAAGZ,SACjDY,OAAS,GAETP,EAAM6G,SAASF,EAAWA,EAAWpG,OAAS,MAM9CwF,EALGrI,EAAauC,KAAK0G,EAAWA,EAAWpG,OAAS,IAKnC,aAAasF,KAAQrF,EACpCmG,EAAWA,EAAWpG,OAAS,IAC/BZ,UANe,aAAakG,KAAQrF,EACpCgC,EAAmBmE,EAAWA,EAAWpG,OAAS,KAClDZ,WAUNoG,GAAgBxF,SAEhBkB,EADEf,EAAMP,GACE,qBAAqBQ,UAAgBoF,KAErC,KAAKpF,KAAWoF,KAE5BlF,EAAQgB,EAAgBJ,EAAStB,EAASpC,GAC5B,IAAV8C,IAAgBiF,GAClB,OAAOrE,CAGb,KAAO,CACL,MAAMqF,EACJJ,EAAUnG,OAAS,GAAM,EACrBmG,EAAUnG,OAAS,EACnBmG,EAAUnG,OAAS,EACnBwG,EAAmBL,EAAU7D,MAAM,EAAGiE,GAAUzF,KAAK,KAC3D,IAAIsF,EAAa,IAAI,IAAItH,IAAI0H,EAAiBH,MAAM,gBAoBpD,GAnBID,GAAYpG,QAEZoG,EAAW,IACXnE,EAAmBmE,EAAW,GAAGhH,SAASY,QAEtCP,EAAMkG,WAAWS,EAAW,MAM5BZ,EALGrI,EAAauC,KAAK0G,EAAW,IAKf,eAAed,KAAQrF,EACtCmG,EAAW,IACXhH,UANe,eAAekG,KAAQrF,EACtCgC,EAAmBmE,EAAW,KAC9BhH,WAUNoG,GAAgBxF,SAEhBkB,EADEf,EAAMP,GACE,qBAAqBQ,UAAgBoF,KAErC,KAAKpF,KAAWoF,KAE5BlF,EAAQgB,EAAgBJ,EAAStB,EAASpC,GAC5B,IAAV8C,IAAgBiF,GAClB,OAAOrE,EAIX,MAAMuF,EAAiBN,EACpB7D,MAAMiE,EAAUJ,EAAUnG,OAAS,GACnCc,KAAK,KAqBR,GApBAsF,EAAa,IAAI,IAAItH,IAAI2H,EAAeJ,MAAM,gBAC1CD,GAAYpG,QAEZoG,EAAW,IACXnE,EAAmBmE,EAAW,GAAGhH,SAASY,OAAS,GAE/CP,EAAM6G,SAASF,EAAW,MAM1BZ,EALGrI,EAAauC,KAAK0G,EAAW,IAKf,aAAad,KAAQrF,EACpCmG,EAAW,IACXhH,UANe,aAAakG,KAAQrF,EACpCgC,EAAmBmE,EAAW,KAC9BhH,WAUNoG,GAAgBxF,SAEhBkB,EADEf,EAAMP,GACE,qBAAqBQ,UAAgBoF,KAErC,KAAKpF,KAAWoF,KAE5BlF,EAAQgB,EAAgBJ,EAAStB,EAASpC,GAC5B,IAAV8C,IAAgBiF,GAClB,OAAOrE,CAGb,CAEJ,CAEA,GAAIzB,GAASkF,GAAY3D,EAAcvB,GAAQ,CAC7C,MAAM2G,EAAa,IAAI,IAAItH,IAAIW,EAAM4G,MAAM,gBAC3C,GAAID,GAAYpG,OACd,IAAK,IAAI0G,EAAI,EAAGA,EAAIN,GAAYpG,OAAQ0G,IAEpCN,EAAWM,IACXzE,EAAmBmE,EAAWM,GAAGtH,SAASY,OAAS,IAE9C7C,EAAauC,KAAK0G,EAAWM,IAOhCjB,EAAakB,KACX,YAAYrB,KAAQrF,EAClBmG,EAAWM,GAAGtH,QACdA,WATJqG,EAAakB,KACX,YAAYrB,KAAQrF,EAClBgC,EAAmBmE,EAAWM,KAC9BtH,YAaZ,GAAIqG,GAAczF,SAEdkB,EADEf,EAAMP,GACE,qBAAqBQ,UAAgBqF,EAAa3E,KAC1D,YAGQ,KAAKV,KAAWqF,EAAa3E,KAAK,YAE9CR,EAAQgB,EAAgBJ,EAAStB,EAASpC,GAC5B,IAAV8C,IAAgBiF,GAClB,OAAOrE,CAGb,CASA,GANEA,EADEf,EAAMP,GACE,qBAAqBQ,iBAErB,KAAKA,YAGjBE,EAAQgB,EAAgBJ,EAAStB,EAASpC,GAC5B,IAAV8C,IAAgBiF,EAClB,OAAOrE,CAEX,GAmWW0F,EAA+B,CAC1CC,EACArJ,EACAsJ,EACAnC,KAEA,IACE,MAAMoC,EAAsBD,EAAiBzH,OAAQ2H,GACnDvC,EAAuBuC,EAAMH,IAGzBI,EAA4BvC,IAChC,MAAMwC,EAAYvG,EAChB+D,EAAUG,KACVH,EAAUyC,WAGZ,OAAKD,EAEkB,UAAnBxC,EAAUG,KACLlF,EACLkH,EACAK,EACA1J,GAKAgC,EAAyB0H,GAAmB,GAE3C/J,EAAauC,KAAKwH,GAIhB,CAAC,IAAIxC,EAAUG,SAASqC,MAHtB,CAAC,oBAAoBxC,EAAUG,UAAUqC,MAd3B,IAoBnBE,EAAoB,KACxB,MAAMC,EAAUpF,EAAmBd,EAAe0F,IAAUzH,QAAU,IAChE2G,EAAmBrD,EAAoB2E,GAC7C,GAAI7H,EAAyB6H,GAC3B,OAAOtB,EACH,CACE,+BAA+B9F,EAC7B8F,OAGJ,GAGN,IAAKsB,GAAWA,EAAQrH,OAAS,IAAM,QAAQN,KAAK2H,GAAU,CAC5D,MAAMpB,EAAyB/D,EAC7B2E,EACAQ,GAEF,OAAOpB,EAAyB,CAACA,GAA0B,EAC7D,CAEA,MAAMqB,EAAa,IAAIxI,IACjBmH,EAAyB/D,EAC7B2E,EACAQ,GAGF,OAAIpB,GACFqB,EAAWC,IAAItB,GACRzE,MAAMsC,KAAKwD,KAGhBnK,EAAauC,KAAK2H,IAAYA,EAAQrH,QAAU,KAAOqH,EAAQ1F,SAAS,MAC1E2F,EAAWC,IAAI,UAAUtH,EAAiBoH,MAGxCA,EAAQ1F,SAAS,MACnB2F,EAAWC,IACT,sBAAsBtH,EAAiBgC,EAAmBoF,OAI9DC,EAAWC,IAAI,mBAAmBtH,EAAiBoH,OAE5C7F,MAAMsC,KAAKwD,KAGdE,EAAgC,CACpCC,EACAC,KAEA,MAAMC,EAAoB,GAEpBC,EAAQ,CAACC,EAAoBC,KACjC,GAAIA,EAAa9H,SAAW0H,EAK5B,IAAK,IAAIhB,EAAImB,EAAYnB,EAAIe,EAAWzH,OAAQ0G,IAC9CoB,EAAanB,KAAKc,EAAWf,IAC7BkB,EAAMlB,EAAI,EAAGoB,GACbA,EAAaC,WAPbJ,EAAQhB,KAAK,IAAImB,KAYrB,OADAF,EAAM,EAAG,IACFD,GAGHK,EAAcV,GAClBnH,EAAM0G,GACF,qBAAqBA,EAAQzG,gBAAgBkH,EAAWxG,KAAK,YAC7D,KAAK+F,EAAQzG,WAAWkH,EAAWxG,KAAK,YAiDxCmH,EA/C+B,MACnC,KAAIlB,EAAoB/G,OAAS,GAIjC,IACE,IAAI0H,EAAkB,EACtBA,GAAmBX,EAAoB/G,OACvC0H,IACA,CACA,MAAMQ,EAAkBV,EACtBT,EACAW,GAGF,IAAK,MAAMS,KAAkBD,EAAiB,CAC5C,IAAIE,EAA4B,GAEhC,IAAK,MAAM1D,KAAayD,EAAgB,CACtC,MAAME,EAAsBpB,EAAyBvC,GAEhD2D,EAAoBrI,QAEzBoI,EAAgBzB,QAAQ0B,EAC1B,CAEA,GAAID,EAAgBpI,OAAS,EAAG,SAEhC,MAAMzC,EAAQyK,EAAWI,GAEzB,IAAI1G,EAEJ,IACEA,EAAaJ,EAAgB/D,EAAOsJ,EAASrJ,EAC/C,CAAE,MACA,QACF,CAEA,GAAmB,IAAfkE,EACF,OAAOnE,CAEX,CACF,GAKyB+K,GAC3B,GAAIL,EACF,OAAOA,EAGT,MAAMM,EAAiBnB,IACvB,IAAKmB,EAAevI,SAAW+G,EAAoB/G,OACjD,OAGF,IACE,IAAI0H,EAAkB,EACtBA,GAAmBX,EAAoB/G,OACvC0H,IACA,CACA,MAAMQ,EAAkBV,EACtBT,EACAW,GAGF,IAAK,MAAMS,KAAkBD,EAAiB,CAC5C,IAAIG,EAAgC,GAEpC,IAAK,MAAM3D,KAAayD,EACtBE,EAAoB1B,QAAQM,EAAyBvC,IAGvD,GAAK2D,EAAoBrI,OAIzB,IAAK,MAAMwI,KAAiBD,EAAgB,CAC1C,MAAMH,EAAkB,IAAIC,EAAqBG,GAC3CjL,EAAQyK,EAAWI,GAEzB,IACE,GAA+C,IAA3C9G,EAAgB/D,EAAOsJ,EAASrJ,GAClC,OAAOD,CAEX,CAAE,MACA,QACF,CACF,CACF,CACF,CACF,CAAE,MAAOsE,GACPC,QAAQ2G,IAAI,2BAA2BC,KAAKC,UAAU9G,EAAO,KAAM,KACrE,GAmEW+G,EAAuB,CAClChH,EACApE,KAEA,IAAKoE,EAAKP,YAAa,MAAO,GAE9B,MAAMwH,GAnzCwBjJ,EAmzCOgC,EAlzC9BhC,GAASkJ,WAAW,IAAI3B,WAAa,IADf,IAACvH,EAozC9B,MAAMmG,EAAmBrD,EAAoBmG,GAC7C,GAAIrJ,EAAyBqJ,KAAkB9C,EAE7C,MAAO,GAGT,MAAMgD,EACJvJ,EAAyBqJ,GAAgB9C,EAAmB8C,EACxD5C,EAAyB/D,EAC7BN,EACAmH,GAGF,IAAIC,EA4BJ,OAzBEA,EADE/C,EACO/F,EACP+F,EACA9F,EAAMyB,GACNA,EAAKxB,SAAW,KAETZ,EAAyBqJ,GACzB3I,EACP,+BAA+BD,EAAiB8I,MAChD5I,EAAMyB,GACNA,EAAKxB,SAAW,MAERjD,EAAauC,KAAKmJ,IAAiB,KAAKnJ,KAAKmJ,EAAazJ,QAC3Dc,EACP,sBAAsBD,EAAiB8I,KACvC5I,EAAMyB,GACNA,EAAKxB,SAAW,KAGTD,EAAMyB,GACX,qBAAqBA,EAAKxB,kBAAkBH,EAC1C8I,MAEF,KAAKnH,EAAKxB,SAAW,SAASH,EAAiB8I,MAG9CC,GAGIC,EAA6B,CACxCrJ,EACA0F,EACA7F,KAEA,IAAc,MAAT6F,GAAyB,WAATA,IAAsB9F,EAAyBC,GAAQ,CAC1E,MAAM4C,SAAEA,GAAaE,EAA8B9C,GAEnD,OAAO4C,EACHnC,EACE,+BAA+BD,EAAiBoC,MAChDlC,EAAMP,GACNA,EAAQQ,QAAQC,eAElB,EACN,CAEA,OAAOH,EACL,mBAAmBoF,MAASrF,EAAiBgC,EAAmBxC,IAAQL,SACxEe,EAAMP,GACNA,EAAQQ,QAAQC,gBAIP6I,EAA6B,CACxCtJ,EACA0F,EACA7F,KAEA,IAAc,MAAT6F,GAAyB,WAATA,IAAsB9F,EAAyBC,GAAQ,CAC1E,MAAM+C,OAAEA,EAAMH,SAAEA,GAAaE,EAA8B9C,GACrD0J,EAAa3G,GAAUH,EAE7B,OAAO8G,EACHjJ,EACE,kCAAkCD,EAAiBkJ,MACnDhJ,EAAMP,GACNA,EAAQQ,QAAQC,eAElB,EACN,CAEA,OAAOH,EACL,eAAeoF,KAAQrF,EAAiBgC,EAAmBxC,IAAQL,UACnEe,EAAMP,GACNA,EAAQQ,QAAQC,gBAIP+I,EAA2B,CACtCxJ,EACA0F,EACA7F,KAEA,IAAc,MAAT6F,GAAyB,WAATA,IAAsB9F,EAAyBC,GAAQ,CAC1E,MAAM4C,SAAEA,GAAaE,EAA8B9C,GAEnD,OAAO4C,EACHnC,EACE,+BAA+BD,EAAiBoC,MAChDlC,EAAMP,GACNA,EAAQQ,QAAQC,eAElB,EACN,CAEA,OAAOH,EACL,YAAYoF,KAAQrF,EAAiBgC,EAAmBxC,IAAQL,UAChEe,EAAMP,GACNA,EAAQQ,QAAQC,gBAIPgJ,EAAuB,CAClCzJ,EACA6H,KAEA,MA0DMY,EAAsBZ,EACzB6B,QAAS5E,GA3DqB,CAACA,IAChC,MAAMwC,EAAYvG,EAAuB+D,EAAUG,KAAMH,EAAUjF,OAEnE,IAAKyH,EACH,MAAO,GAGT,GAAuB,UAAnBxC,EAAUG,KAAkB,CAC9B,MAAMrH,EACHoC,EAAQ2J,iBACT3J,EAAQhC,cAEV,OAAO+B,EAAwBC,EAASsH,EAAW1J,EACrD,CAGA,OAAIgC,EAAyB0H,GAAmB,GAE3C/J,EAAauC,KAAKwH,GAMhB,CAAC,IAAIxC,EAAUG,QAAQ5E,EAAiBiH,MALtC,CACL,oBAAoBxC,EAAUG,SAAS5E,EAAiBiH,OAuCpCD,CAAyBvC,IAChDrF,OAAOkF,SAEV,GAAI8D,EAAoBrI,QAAU,EAChC,OAAOE,EACL,GAAGmI,EAAoB,SAASA,EAAoB,KACpDlI,EAAMP,GACNA,EAAQQ,QAAQC,eAIpB,MAAMmI,EA3CqB,MACzB,MAAMnB,EAAUpF,EAAmBd,EAAevB,IAAUR,QAAU,IAChE2G,EAAmBrD,EAAoB2E,GAC7C,GAAI7H,EAAyB6H,GAE3B,OAAOtB,EACH,+BAA+B9F,EAAiB8F,MAChD,KAGN,MAAME,EAAyB/D,EAA0BtC,EAASyH,GAElE,OAAIpB,KAICoB,GAAWA,EAAQrH,OAAS,IAAM,QAAQN,KAAK2H,GAC3C,KAGLlK,EAAauC,KAAK2H,IAAYA,EAAQrH,QAAU,KAAOqH,EAAQ1F,SAAS,KACnE,UAAU1B,EAAiBoH,KAGhCA,EAAQ1F,SAAS,KACZ,sBAAsB1B,EAAiBoH,KAGzC,mBAAmBpH,EAAiBoH,QAevBmC,GACtB,OAAmC,IAA/BnB,EAAoBrI,QAAgBwI,EAC/BtI,EACL,GAAGmI,EAAoB,SAASG,IAChCrI,EAAMP,GACNA,EAAQQ,QAAQC,eAIb,IAGHE,EAAgC,CACpChD,EACAqC,EACApC,KAEA,IACE,OAAO8D,EAAgB/D,EAAOqC,EAASpC,EACzC,CAAE,MAAOiM,GACP,OAAO,CACT,GAGIC,EAA8B,CAClC9H,EACApE,KAEA,MAAMmM,EAAoB,GACpBC,EAAO,IAAI9K,IACX2I,EAAajG,MAAMsC,KAAKlC,EAAK6F,YAAc,IAC3CoC,EAAgB,CACpB,KACA,cACA,YACA,UACA,OACA,aACA,QAEIC,EAAoB,IACrBD,EACA3K,IAAK6K,GAAatC,EAAWxC,KAAM+B,GAASA,EAAKnC,OAASkF,IAC1D1K,OAAOkF,YACPkD,EAAWpI,OAAQ2H,IAAU6C,EAAclI,SAASqF,EAAKnC,QAGxDmF,EAAczM,IACbA,IAASqM,EAAKtK,IAAI/B,IACnB0M,EAAmB1M,EAAOqE,EAAMpE,KAClCoM,EAAKrC,IAAIhK,GACToM,EAAQhD,KAAKpJ,KAIjBuM,EAAkBI,QAASlD,IACzB,GAAKvC,EAAuBuC,EAAMpF,GAElC,IAAK,MAAMrE,KAAS4M,EAAgBvI,EAAMoF,EAAKnC,KAAMmC,EAAKvH,OACxDuK,EAAWzM,KAIf,MAAMwE,EAAOH,EAAKP,aAAajC,OAC/B,GAAI2C,GAAQA,EAAK/B,OAAS,IAA+B,IAAzB4B,EAAKQ,SAASpC,OAAc,CAC1D,MAAMoK,EAAYxB,EAAqBhH,GACnCwI,GACFJ,EAAWI,EAEf,CAEA,GAAI3C,EAAWzH,OAAS,EAAG,CACzB,MAAMqK,EAAmBzD,EACvBhF,EACApE,EACAiK,GAGE4C,GACFL,EAAWK,EAEf,CAEA,OAAOV,GAGHW,EAAgC,CACpCC,EACA3K,KAEA,MAAM4K,EAAkB,GACxB,IAAI9G,EAA0B9D,EAE9B,KAAO8D,GAAWA,IAAY6G,GAC5BC,EAAM3G,QAAQ4G,EAAgB/G,IAC9BA,EAAUA,EAAQC,cAGpB,OAAOD,IAAY6G,EAAWC,EAAM1J,KAAK,KAAO,IAGrC4J,EAAkB,CAC7B9K,EACApC,KAEA,MAAMoF,EACJpF,IACEoC,EAAQ2J,iBACR3J,EAAQhC,eACNwC,EAAUqK,EAAgB7K,GAC1B+K,EAAgBxK,EAAMP,GACxB,qBAAqBA,EAAQQ,QAAQC,kBACrC,KAAKT,EAAQQ,QAAQC,gBAEzB,IACE,IAAIkK,EAAW3K,EAAQ+D,cACvB4G,EACAA,EAAWA,EAAS5G,cACpB,CACA,MAAMiH,EAAkBlB,EAA4Ba,EAAU3H,GAE9D,IAAK,MAAMiI,KAAiBD,EAAiB,CAC3C9I,QAAQ2G,IAAI,0BAA0BoC,KAEtC,MAAMC,EAAkB,GAAGD,iBAA6BzK,IACxD0B,QAAQ2G,IAAI,4BAA4BqC,KAExC,MAAMxK,EAAQC,EACZuK,EACAlL,EACAgD,GAIF,GAFAd,QAAQ2G,IAAI,gBAAgBnI,KAGhB,IAAVA,GACAyK,EAAoBD,EAAiBlI,KAAUhD,EAG/C,OADAkC,QAAQ2G,IAAI,mBAAmBqC,KACxBA,EAGT,MAAME,EAAiBV,EAA8BC,EAAU3K,GAC/D,GAAIoL,EAAgB,CAClB,MAAMC,EAAmB,GAAGJ,KAAiBG,IAC7ClJ,QAAQ2G,IAAI,4BAA4BwC,KAExC,MAAMC,EAAmB3K,EACvB0K,EACArL,EACAgD,GAIF,GAFAd,QAAQ2G,IAAI,gBAAgByC,KAGL,IAArBA,GACAH,EAAoBE,EAAkBrI,KAAUhD,EAGhD,OADAkC,QAAQ2G,IAAI,mBAAmBwC,KACxBA,CAEX,CACF,CACF,CAEAnJ,QAAQ2G,IAAI,4BAA4BkC,KACxC,MAAMQ,EAAgB5K,EACpBoK,EACA/K,EACAgD,GAIF,OAFAd,QAAQ2G,IAAI,gBAAgB0C,KAGR,IAAlBA,GACAJ,EAAoBJ,EAAe/H,KAAUhD,GAE7CkC,QAAQ2G,IAAI,mBAAmBkC,KACxBA,IAGT7I,QAAQ2G,IAAI,oBACL,KAGIsC,EAAsB,CACjCxN,EACAC,KAEA,IACE,GAn9CsBoE,EAm9CDpE,EAl9ChBoE,GAAMjE,WAAayN,KAAKC,wBAA0B,SAAUzJ,EAk9CpC,CAC3B,MAAMnE,MAAEA,EAAKC,YAAEA,EAAW8G,aAAEA,GAC1B7B,EAA8BnF,GAShC,OAReC,EAAMY,SACnBd,EACAG,EACA,KACAY,YAAYgN,wBACZ,MAGaC,iBAAmB/G,CAIpC,CAUA,OARehH,EAAMa,SACnBd,EACAC,EACA,KACAA,EAAM+F,YAAajF,YAAYgN,wBAC/B,MAGYC,eAChB,CAAE,MAAO9B,GACP,OAAO,IACT,CA/+CuB,IAAC7H,GAk/CbqI,EAAqB,CAChC1M,EACAqC,EACApC,KAEA,IACE,MAAMiB,MAAEA,EAAKE,OAAEA,GAAWrB,EAAkBC,EAAOC,GAEnD,QAASiB,IAAUE,GAAUF,IAAUmB,CACzC,CAAE,MACA,OAAO,CACT,GAGW6K,EAAmB7K,GACvBO,EAAMP,GACT,mBAAmBA,EAAQQ,QAAQC,kBACnCT,EAAQQ,QAAQC,cAKTmL,EAA4B,CACvC5J,EACApE,EACAmH,KAEA,KAAM/C,aAAgB4B,SAAU,MAAO,GACvC,MAAMoG,EAAO,IAAI9K,IACX6K,EAA4C,GAE5ClC,EAAajG,MAAMsC,KAAKlC,EAAK6F,YAAc,IAAIpI,OAClDqF,IACCA,UAAWjF,QAbKA,EAcFiF,EAAUjF,OAdU,KAAKC,KAAKD,KAe5CgF,EAAuBC,EAAW9C,GAfnB,IAACnC,IAkBduK,EAAa,CAACyB,EAAahM,KAC1BA,IAASmK,EAAKtK,IAAIG,KACvBmK,EAAKrC,IAAI9H,GACTkK,EAAQhD,KAAK,CAAE8E,MAAKhM,YAIhBoK,EAAgB,CACpB,KACA,cACA,YACA,UACA,OACA,aACA,QAGFA,EAAcK,QAASH,IACrB,MAAM/C,EAAOS,EAAWxC,KAAMyG,GAAMA,EAAE7G,OAASkF,GAC/C,GAAK/C,EAEL,IAAK,MAAMzJ,KAAS4M,EAAgBvI,EAAMoF,EAAKnC,KAAMmC,EAAKvH,OACpDlC,GAAS0M,EAAmB1M,EAAOqE,EAAMpE,IAC3CwM,EAAW,aAAahD,EAAKnC,OAAQtH,KAM3CkK,EAAWyC,QAASxF,IAClB,IAAImF,EAAclI,SAAS+C,EAAUG,MAErC,IAAK,MAAMtH,KAAS4M,EAAgBvI,EAAM8C,EAAUG,KAAMH,EAAUjF,OAC9DlC,GAAS0M,EAAmB1M,EAAOqE,EAAMpE,IAC3CwM,EAAW,aAAatF,EAAUG,OAAQtH,KAMhD,MAAMwE,EAAOH,EAAKP,aAAajC,OAC/B,GAAI2C,GAAQA,EAAK/B,OAAS,IAA+B,IAAzB4B,EAAKQ,SAASpC,OAAc,CAC1D,MAAMoK,EAAYxB,EAAqBhH,GACnCwI,GAAaH,EAAmBG,EAAWxI,EAAMpE,IACnDwM,EAAW,iBAAkBI,EAEjC,CAGA,GAAI3C,EAAWzH,OAAS,EAAG,CACzB,MAAMqK,EAAmBzD,EACvBhF,EACApE,EACAiK,GAIE4C,GAAoBJ,EAAmBI,EAAkBzI,EAAMpE,IACjEwM,EAAW,wBAAyBK,EAExC,CAGA,MAAMsB,EAAWjB,EAAgB9I,EAAMpE,GAKvC,OAJImO,GAAY1B,EAAmB0B,EAAU/J,EAAMpE,IACjDwM,EAAW,gBAAiB2B,GAGvBhC,GA6CIQ,EAAkB,CAC7BvI,EACAmI,EACA7C,KAEA,MAAM/J,EAAe,IAAIyO,OAAO,sBAChC,IAAI5C,EAAiB,GAGrB,GAFA9B,EAAYvG,EAAuBoJ,EAAU7C,GAE9B,CACb,GAAiB,UAAb6C,EAAsB,CACxB,MAAMvM,EACHoE,EAAK2H,iBACN3H,EAAKhE,cAGP,OAAO+B,EAAwBiC,EAAMsF,EAAW1J,GAAO0B,IAAK2M,GAC1D1L,EAAMyB,GACF,qBAAqBA,EAAKxB,gBAAgByL,KAC1C,KAAKjK,EAAKxB,SAAW,OAAOyL,KAEpC,CAIA,GAAIrM,EAAyB0H,GAAY,MAAO,GAW9C8B,EATG7L,EAAauC,KAAKwH,GASZ/G,EAAMyB,GACX,qBACEA,EAAKxB,iBACG2J,KAAY9J,EAAiBiH,MACvC,KAAKtF,EAAKxB,SAAW,QAAQ2J,KAAY9J,EACvCiH,MAbG/G,EAAMyB,GACX,qBACEA,EAAKxB,0BACY2J,KAAY9J,EAAiBiH,OAChD,KAAKtF,EAAKxB,SAAW,iBAAiB2J,KAAY9J,EAChDiH,MAWV,CAEA,OAAO8B,EAAS,CAACA,GAAU,IAwQhB8C,EAAkBvO,GAQ7BA,GANAA,EAAQA,EAAMwD,QACZ,gCACA,8BAIYA,QAAQ,0BAA2B,6BAqD7C,SAAUgL,EAAgBxO,GAC9B,MAAMyO,EAhCF,SAA4BzO,GAChC,OACEA,EACG8C,cAEAU,QAAQ,WAAY,KACpBA,QAAQ,WAAY,KAEpBA,QAAQ,WAAY,OAEpBA,QAAQ,mBAAoB,UAC5BA,QAAQ,iBAAkB,QAEjC,CAmBoBkL,CAAkB1O,GAC9B2O,EAlBF,SAAqC3O,GACzC,MAAM4O,EAAK5O,EAAM8C,cAEX+L,EAAYD,EAAG9F,MACnB,yIAEIgG,EAAOD,IAAY,IAAM,OAEzBE,EAAYH,EAAG9F,MAAM,kBAK3B,MAAO,CAAEgG,OAAM3H,UAJG4H,IAAY,IAAM,OAIVC,cAFJJ,EAAGxK,SAAS,mBAGpC,CAIgB6K,CAA2BjP,GAEzC,MAAO,CACL,QACA,QAAQ2O,EAAMG,OACd,QAAQH,EAAMxH,YACd,aAAawH,EAAMK,gBACnB,SAASP,KACTlL,KAAK,IACT,CAEM,SAAU2L,EAAgBhN,GAC9B,OAAOA,EAAMsB,QAAQ,MAAO,QAAQA,QAAQ,KAAM,OAAOA,QAAQ,KAAM,MACzE,CAQM,SAAU2L,EACdlP,EACAqH,EACApF,EACAG,GAEA,MAAMgD,EAAQhD,GAAS2J,iBAAyC/L,EAC1DmP,EAAYC,IAChB,IACE,OAAOpL,MAAMsC,KAAKlB,EAAKiK,iBAAiBD,GAC1C,CAAE,MACA,MAAO,EACT,GAGF,IACE,OAAQ/H,GACN,IAAK,KACH,OAAyD,IAAlD8H,EAAS,IAAIF,EAAgBhN,MAAUO,OAEhD,IAAK,OACH,OAAiE,IAA1D2M,EAAS,UAAUF,EAAgBhN,QAAYO,OAExD,IAAK,YACH,OAAwC,IAAjC2M,EAAS,IAAIlN,KAASO,OAE/B,IAAK,UAaL,IAAK,cACH,OAAkC,IAA3B2M,EAASlN,GAAOO,OAXzB,IAAK,WACH,OAEgB,IADd2M,EAAS,KAAKtN,OAAQqM,GAAMA,EAAErK,aAAajC,SAAWK,GACnDO,OAEP,IAAK,kBACH,OAEE,IADA2M,EAAS,KAAKtN,OAAQqM,GAAMA,EAAErK,aAAaM,SAASlC,IAAQO,OAKhE,QACE,OAAO,EAEb,CAAE,MACA,OAAO,CACT,CACF,CCpkFA,IAAI8M,EAA8C,GAQlD,MAAMC,EAAe,CACnBC,cAAe,MACfC,aAAc,KACdC,SAAU,WACVvH,WAAY,aACZwH,eAAgB,iBAChBC,KAAM,OACNlJ,MAAO,QACPmJ,cAAe,OACfC,QAAS,OAOLC,EAAkB,CACtBC,EACAC,IAEOA,EAAQvO,IAAKwO,IAAK,IACpBA,EACHjC,IAAKiC,GAAOjC,KAAK9J,SAAS6L,GACtBE,EAAMjC,IACN,GAAG+B,KAAYE,EAAMjC,MAAMrM,UAqB7BuO,EAA8B,CAClCF,EACAlI,IAEAA,EAAUkI,EAAUA,EAAQpO,OAAQqO,IAAU,OARpBnQ,EAQwCmQ,EAAMjO,QAPxE,iCAAiCC,KAAKnC,IACtC,kBAAkBmC,KAAKnC,IAFE,IAACA,IAUtBqQ,EAAqBhO,GAClBA,GAASyB,aAAaN,QAAQ,OAAQ,MAAM3B,QAAU,GA8GzDyO,EAA0BjL,IAC9B,KAAKA,GAAUA,aAAgBY,SAAU,MAAO,GAEhD,MAAMsK,EAAmC,GACnCC,EAAQvM,MAAMsC,KAAKlB,EAAKR,UAAY,IAE1C,KAAO2L,EAAM/N,QAAQ,CACnB,MAAMgO,EAAcD,EAAME,QAEtBD,EAAYE,WAAWhB,SAAS,gBAIpCY,EAAMnH,KAAKqH,GACXD,EAAMpH,QAAQnF,MAAMsC,KAAKkK,EAAY5L,UAAY,KACnD,CAEA,OAAO0L,GAGHK,EAAkB,CACtBvO,EACAwO,KAEA,MAAMN,EAAmC,GACzC,IAAIE,EACY,aAAdI,EACIxO,EAAQyO,uBACRzO,EAAQ0O,mBAEd,KAAON,GACAA,EAAYE,WAAWhB,SAAS,eACnCY,EAAMnH,KAAKqH,GAGbA,EACgB,aAAdI,EACIJ,EAAYK,uBACZL,EAAYM,mBAGpB,OAAOR,GAUHS,EAA8B,CAClC3O,EACAwO,KAEA,MAAMN,EAAwB,GAE9B,IACE,IAAIE,EAA4CpO,EAChDoO,GAAarK,cACbqK,EAAcA,EAAYrK,cAC1B,CACA,IAAI6K,EACY,aAAdJ,EACIJ,EAAYK,uBACZL,EAAYM,mBAElB,KAAOE,GACAA,EAAQN,WAAWhB,SAAS,gBAC/BY,EAAMnH,KAAK6H,GAEXV,EAAMnH,KAAK,CACT/E,KAAM4M,EACNC,OAAQ,IAAMZ,EAAuBW,MAIzCA,EACgB,aAAdJ,EACII,EAAQH,uBACRG,EAAQF,kBAElB,CAEA,OAAOR,GAGHY,EAAoB9M,IACxB,KAAMA,aAAgB4B,SAAU,OAAO,EACvC,MAAMzB,EAAOH,EAAKP,aAAajC,OAE/B,OACGwC,EAAK+M,KACL/M,EAAKgN,WACqB,IAA3BhN,EAAK6F,WAAWzH,UACd+B,GAAQA,EAAK/B,OAAS,KACxB4B,EAAKQ,SAASpC,OAAS,GAIrB6O,EAA0B,CAC9Bf,EACAzB,EACAjL,EACA5D,EACAmH,EACA8G,EACAlG,KAEA,MAAMuJ,EAAiBrE,EAAgBrJ,GAIjC2N,EAA+C,GAErD,IAAIC,EAAY,EAChB,MAAMC,EAAc5C,EAAK1K,SAAS,cAAgB,GAAK,GAEvD,IAAK,MAAMuN,KAAYpB,EAAO,CAC5B,GAAIkB,IAAcC,EAAa,MAC/B,GAAIF,EAAW/O,QATM,EASoB,MAEzC,MAAM4B,EAAO,SAAUsN,EAAWA,EAAStN,KAAOsN,EAElD,KAAMtN,aAAgB4B,SAAU,SAChC,GAAIkL,EAAiB9M,GAAO,SAE5B,MAAMuN,EAAe,CACnB,IAAM3D,EAA0B5J,EAAMpE,GACtC,KACE,MAAMmM,EAAiB,GACjBlC,EAAajG,MAAMsC,KAAKlC,EAAK6F,YAAc,IAAIpI,OAClD2H,GACCA,GAAMvH,QACL,KAAKC,KAAKsH,EAAKvH,QAChBgF,EAAuBuC,EAAMpF,IAGjC,IAAK,MAAMoF,KAAQS,EAAY,CAC7B,MAAMhI,EAAQuH,EAAKvH,MACnB,IAAKA,EAAO,SAEZ,MAAM2P,EAAgBhG,EACpBxH,EACA,IAAIoF,EAAKnC,OACTpF,GAEE2P,GACFzF,EAAQhD,KAAK,CAAE8E,IAAK,WAAYhM,MAAO2P,IAGzC,MAAMC,EAAkBnG,EACtBtH,EACA,IAAIoF,EAAKnC,OACTpF,GAEE4P,GACF1F,EAAQhD,KAAK,CAAE8E,IAAK,cAAehM,MAAO4P,GAE9C,CAEA,OAAO1F,GAET,IAAM,CAAC,CAAE8B,IAAK,MAAOhM,MAAOiL,EAAgB9I,EAAMpE,MAGpD,IAAK,IAAI8R,EAAa,EAAGA,EAAaH,EAAanP,OAAQsP,IAAc,CACvE,IAAI3F,EAAUwF,EAAaG,KAC3B,IAAK3F,EAAQ3J,OAAQ,SAEhBuF,IACHoE,EAAUA,EAAQtK,OAAQqM,IAAO,UAAUhM,KAAKgM,EAAEjM,SAGpD,MAAMmK,EAAO,IAAI9K,IACjB6K,EAAUA,EAAQtK,OAAQqM,IACpB9B,EAAKtK,IAAIoM,EAAEjM,SACfmK,EAAKrC,IAAImE,EAAEjM,QACJ,IAGTkK,EAAUA,EAAQrH,MAAM,EAAG,GAE3B,IAAK,MAAMiN,KAAU5F,EAAS,CAC5B,MAAMpM,EAAQ,GAAGgS,EAAO9P,SAAS4M,MAASyC,IAE1C,GAAI7E,EAAmB1M,EAAO6D,EAAe5D,GAAQ,CAGnD,GAFAuR,EAAWpI,KAAK,CAAE8E,MAAKhM,MAAOlC,IAE1BwR,EAAW/O,QAAU,EACvB,OAAO+O,EAGT,GAAIA,EAAW/O,QAlFA,EAmFb,OAAO+O,CAEX,CACF,CAEA,GAAIxJ,GAAW+J,IAAeH,EAAanP,OAAS,EAClD,IAAK,MAAMuP,KAAU5F,EAAS,CAC5B,MAAMrJ,EAAQgB,EAAgBiO,EAAO9P,MAAOmC,EAAMpE,GAElD,IAAK,IAAIkJ,EAAI,EAAGA,GAAK8I,KAAKC,IAAInP,EA7FhB,GA6FuCoG,IAAK,CACxD,MAAMnJ,EAAQ,IAAIgS,EAAO9P,UAAUiH,MAAM2F,MAASyC,IAElD,GAAI7E,EAAmB1M,EAAO6D,EAAe5D,GAAQ,CAGnD,GAFAuR,EAAWpI,KAAK,CAAE8E,MAAKhM,MAAOlC,IAE1BwR,EAAW/O,QAAU,EACvB,OAAO+O,EAGT,GAAIA,EAAW/O,QAtGJ,EAuGT,OAAO+O,CAEX,CACF,CACF,CAEJ,CAEA,GAAI,WAAYG,GAAYA,EAAST,OAAQ,CAC3C,MAAMiB,EAAWR,EAAST,SAE1B,IAAIkB,EAAa,EACjB,IAAK,MAAMC,KAASF,EAAU,CAC5B,GAAIC,IAAe,GAAI,MACvB,GAAIZ,EAAW/O,QArHE,EAqHwB,MAEzC,KAAM4P,aAAiBpM,SAAU,SACjC,GAAIkL,EAAiBkB,GAAQ,SAE7B,MAAMjG,EAAU6B,EAA0BoE,EAAOpS,GAEjD,IAAK,MAAM+R,KAAU5F,EAAQrH,MAAM,EAAG,GAAI,CACxC,MAAM/E,EAAQ,GAAGgS,EAAO9P,SAAS4M,MAASyC,IAE1C,GAAI7E,EAAmB1M,EAAO6D,EAAe5D,GAAQ,CAGnD,GAFAuR,EAAWpI,KAAK,CAAE8E,MAAKhM,MAAOlC,IAE1BwR,EAAW/O,QAAU,EACvB,OAAO+O,EAGT,GAAIA,EAAW/O,QAtIF,EAuIX,OAAO+O,CAEX,CACF,CACF,CACF,CACF,CAEA,OAAOA,GAGHc,GAA0B,CAC9BjQ,EACApC,EACAmH,EACAY,KAMA,MAAMuK,EAAuC,GAE7C,IACE,IAAI9B,EAAcpO,EAAQ+D,cAC1BqK,EACAA,EAAcA,EAAYrK,cAE1BmM,EAAUnJ,KAAKqH,GAIjB,MAAM+B,EAAclC,EAAuBjO,GAGrCoQ,EAAmB7B,EAAgBvO,EAAS,YAG5CqQ,EAAe9B,EAAgBvO,EAAS,QAGxCsQ,EAAiB3B,EAA4B3O,EAAS,YAGtDuQ,EAAiB5B,EAA4B3O,EAAS,QAgBtDwQ,EAAa,CAACtC,EAAcuC,EAAQ,KACjCvC,EAAM9N,OAASqQ,EAAQvC,EAAMxL,MAAM,EAAG+N,GAASvC,EAGlDwC,EAAQ,CACZ,CACE,CACExC,MAAOsC,EAAWH,GAClB5D,KAAM,oBACNZ,IAAK,qBAEP,CACEqC,MAAOsC,EAAWJ,GAClB3D,KAAM,oBACNZ,IAAK,sBAGT,CACE,CAAEqC,MAAOsC,EAAWN,GAAYzD,KAAM,QAASZ,IAAK,SACpD,CACEqC,MAAOsC,EAAWL,EAAa,IAC/B1D,KAAM,SACNZ,IAAK,WAGT,CACE,CAAEqC,MAAOsC,EAAWN,GAAYzD,KAAM,aAAcZ,IAAK,cACzD,CACEqC,MAAOsC,EAAWN,GAClBzD,KAAM,qBACNZ,IAAK,sBAEP,CACEqC,MAAOsC,EAAWL,EAAa,IAC/B1D,KAAM,WACNZ,IAAK,YAEP,CACEqC,MAAOsC,EAAWL,EAAa,IAC/B1D,KAAM,mBACNZ,IAAK,qBAGT,CACE,CACEqC,MAAOsC,EAAWF,GAClB7D,KAAM,YACNZ,IAAK,aAEP,CAAEqC,MAAOsC,EAAWD,GAAiB9D,KAAM,YAAaZ,IAAK,eAIjE,IAAK,MAAM8E,KAAQD,EACjB,IAAK,MAAM9C,KAAY+C,EAAM,CAG3B,MAAM5I,EAAUkH,EACdrB,EAASM,MACTN,EAASnB,KACTzM,EACApC,EACAmH,EACA6I,EAAS/B,IACTlG,GAIF,GAAIoC,EAAQ3H,OAAS,EAEnB,OAAO2H,CAEX,CAGF,MAAO,IAqDH6I,GAAsB,CAC1B5Q,EACApC,EACAmH,EACA6I,EACAiD,KAEA,MAAMC,EA/kBgB,CAAClD,GAChBT,EAAaS,IAA0CA,EA8kBzCmD,CAAgBnD,GAErC,MAAqB,SAAjBkD,EACKnD,EACLC,EAxiB0B,EAC9B5N,EACApC,EACAgQ,KAIA,IAFaI,EAAkBhO,GAG7B,MAAO,GAGT,GAAiB,SAAb4N,EAAqB,CACvB,MAAMpD,EACJxB,EAAqBhJ,IACrBgR,GAAahR,EAASpC,GAAO,GAAO,GAEtC,OAAO4M,EAAY,CAAC,CAAEqB,IAAK,gBAAiBhM,MAAO2K,IAAe,EACpE,CAEA,MAAO,IAshBHyG,CAAwBjR,EAASpC,EAAOkT,IAIxC,CAAC,WAAY,aAAc,kBAAkB/O,SAAS+O,GACjDnD,EACLC,EAzhB8B,EAClC5N,EACA+E,EACA6I,KAEA,MAAMsD,EAA2C,GAC3CrJ,EAAajG,MAAMsC,KAAKlE,EAAQ6H,YAAc,IAAIpI,OACrDqF,GACCA,GAAWjF,OAASgF,EAAuBC,EAAW9E,IAEpDmC,EAAO6L,EAAkBhO,GACzBmR,EACS,aAAbvD,EACIpE,EACa,eAAboE,EACEtE,EACa,mBAAbsE,EACEvE,EACA,KAEV,IAAK8H,EACH,OAAOD,EAkBT,GAfArJ,EAAWyC,QAASxF,IAClB,MAAMnH,EAAQwT,EACZnR,EACA,IAAI8E,EAAUG,OACdH,EAAUjF,OAGRlC,GACFuT,EAAOnK,KAAK,CACV8E,IAAK,YAAY+B,KAAY9I,EAAUG,OACvCpF,MAAOlC,MAKTwE,EAAM,CACR,MAAMxE,EAAQwT,EAAiBnR,EAAS,IAAKmC,GAEzCxE,GACFuT,EAAOnK,KAAK,CACV8E,IAAK,YAAY+B,SACjB/N,MAAOlC,GAGb,CAEA,OAAOuT,GAweHE,CAA4BpR,EAAS+E,EAAU+L,IAI/C,CAAC,MAAO,MAAM/O,SAAS+O,GAClBnD,EACLC,EA3e+B,EACnC5N,EACApC,EACAmH,EACA6I,KAEA,MAAM/F,EAAajG,MAAMsC,KAAKlE,EAAQ6H,YAAc,IAAIpI,OACrDqF,GACCA,GAAWjF,OAASgF,EAAuBC,EAAW9E,IAG1D,GAAiB,QAAb4N,EAAoB,CACtB,MAAMnD,EAAmBzD,EACvBhH,EACApC,EACAiK,GAIF,OAAO4C,EACH,CAAC,CAAEoB,IAAK,uBAAwBhM,MAAO4K,IACvC,EACN,CAEA,GAAiB,OAAbmD,GAAqB/F,EAAWzH,QAAU,EAAG,CAC/C,MAAMzC,EAAQ8L,EAAqBzJ,EAAS6H,GAC5C,OAAOlK,EAAQ,CAAC,CAAEkO,IAAK,cAAehM,MAAOlC,IAAW,EAC1D,CAEA,MAAO,IA+cH0T,CAA6BrR,EAASpC,EAAOmH,EAAU+L,IAItC,SAAjBA,EACKnD,EACLC,EACAqC,GAAwBjQ,EAASpC,EAAOmH,GAAU,IAIjC,UAAjB+L,EACKnD,EAAgBC,EAxFU,EAAC5N,EAAkBpC,KACtD,MAAMmK,EAAU,GACVuJ,EAAMtR,EAAQQ,QAAQC,cAGtB8Q,EADa3P,MAAMsC,KAAKtG,EAAMqP,iBAAiBqE,IAC9BlP,QAAQpC,GAAW,EAE1C,GAAIuR,GAAO,EAAG,MAAO,GAGrBxJ,EAAQhB,KAAK,CACX8E,IAAK,iBACLhM,MAAO,MAAMyR,MAAQC,OAIvB,IAAIzN,EAA0B9D,EAAQ+D,cAEtC,KAAOD,GAAWA,IAAYlG,EAAM2F,MAAM,CACxC,GAAIO,EAAQiL,IAAMjL,EAAQkL,UAAW,CACnC,MAAMwC,EAAc1N,EAAQiL,GACxBxE,EAAgBzG,EAAS,KAAMA,EAAQiL,IACvCxE,EAAgBzG,EAAS,QAASA,EAAQkL,UAAUyC,YAIxD,IAAK,MAAMC,KAAcF,EAAa,CACpC,MACMG,EADoB/P,MAAMsC,KAAKJ,EAAQmJ,iBAAiBqE,IAC1BlP,QAAQpC,GAAW,EAEvD,GAAI2R,EAAY,EAAG,CACjB5J,EAAQhB,KAAK,CACX8E,IAAK,iBACLhM,MAAO,IAAI6R,MAAeJ,MAAQK,OAEpC,KACF,CACF,CAEA,GAAI5J,EAAQ3H,OAAS,EACnB,KAEJ,CAEA0D,EAAUA,EAAQC,aACpB,CAEA,OAAOgE,GAyC4B6J,CAAsB5R,EAASpC,IAG7C,QAAjBkT,EACKnD,EAAgBC,EAAU,CAC/B,CAAE/B,IAAK,iBAAkBhM,MAAOiL,EAAgB9K,EAASpC,MAItD,IAu0BF,MA8CMoT,GAAe,CAC1BhR,EACApC,EACA+H,EACAZ,KAEA,GAA0C,KAArC/E,EAAQyB,aAAe,IAAIjC,OAAc,CAC5C,MAAM2C,EAAOZ,EAAevB,GAE5B,GAAImC,EACF,OAAOsD,EAAiBzF,EAASpC,EAAO,IAAKuE,EAAMwD,EAASZ,EAEhE,GAGI8M,GAAwB,CAC5BhK,EACAiK,EACAlU,EACA+H,EACAZ,KAEA,MAAMgN,EAAkBlK,EACxB,IACEkK,EAAgBzS,IAAK8H,IACnB,GAAoB,cAAdA,EAAKnC,MAAsC,UAAdmC,EAAKnC,MAGlCJ,EAAuBuC,EAAM0K,GAAwB,CACvD,MAAME,EA3EoB,EAClC5K,EACA0K,EACAlU,EACA+H,EACAZ,KAEA,IAAIoF,EAIJ,GAFAA,EAAW/C,EAAKnC,KAEXJ,EAAuBuC,EAAM0K,GAalC,OATYrM,EACVqM,EACAlU,EACA,IAAIuM,IACJ/C,EAAKvH,MACL8F,EACAZ,IAsDmBkN,CACX7K,EACA0K,EACAlU,EACA+H,EACAZ,GAEEiN,GACF9E,EAAUnG,KAAK,CACb8E,IAAK,YAAYzE,EAAKnC,OAAOU,EAAU,SAAW,KAClD9F,MAAOmS,GAGb,IAIJ,MAAME,EAAWlB,GAAac,EAAalU,EAAO+H,EAASZ,GAQ3D,GAPImN,GACFhF,EAAUnG,KAAK,CACb8E,IAAK,iBAAgBlG,EAAU,SAAW,IAC1C9F,MAAOqS,IAKTH,EAAgB1M,KAAMrF,GAA6B,cAAjBA,EAAQiF,OAC1CJ,EACEkN,GAAiB1M,KAAMrF,GAA6B,cAAjBA,EAAQiF,MAC3C6M,GAGF,CACA,IAAInU,EAjFwB,EAChCqC,EACApC,EACA+H,EACAZ,KAEA,IAAIlF,EAAQG,EAAQgP,UAQpB,GAPqB,iBAAVnP,IACTA,EAAQ,IAEVA,EAAQA,GAAOsB,QAAQ,cAAe,YACtCtB,EAAQkB,EAAuB,QAASlB,GACxCA,EAAQA,GAAOL,OAEXK,GAASgF,EAAuB,CAAEI,KAAM,QAASpF,SAASG,GAC5D,OAAOyF,EAAiBzF,EAASpC,EAAO,SAAUiC,EAAO8F,EAASZ,IAkEpDoN,CAAmBL,EAAalU,EAAO+H,EAASZ,GACxDpH,GACFuP,EAAUnG,KAAK,CACb8E,IAAK,iBACLhM,MAAOlC,GAGb,CAEA,IAAKuP,EAAU9M,QAAU2R,EAAgB3R,OAAS,EAAG,CACnD,MAAMqK,EAAmBzD,EACvB8K,EACAlU,EACAmU,GAGEtH,GACFyC,EAAUnG,KAAK,CACb8E,IAAK,uBACLhM,MAAO4K,GAEb,CAEA,IAAKyC,EAAU9M,OAAQ,CACrB,MAAMgS,EA7XZ,SACEvK,EACAiK,EACAlU,GAGA,MAAM4C,QAAEA,GAAYsR,EACdrQ,EAAcqQ,EAAYrQ,YAAYjC,OAC5C,IAAKiC,EACH,OAEF,MAAM4Q,EAAwBhQ,EAAmBZ,GAC3C0E,EAAmBrD,EAAoBuP,GAC7C,GAAIzS,EAAyByS,KAA2BlM,EACtD,OAGF,MAAMgD,EAAmBvJ,EAAyByS,GAC9ClM,EACAkM,EACEhM,EAAyB/D,EAC7BwP,EACA3I,GAEF,IAAK,MAAMgB,KAAYtC,EACrB,GAAIhD,EAAuBsF,EAAU2H,GAAwB,CAC3D,IAAIxK,EAAYvG,EAAuBoJ,EAASlF,KAAMkF,EAAS5C,WAC/D,MAQM5J,EAAQ,KAAK6C,MARC2J,EAASlF,SAQkBqC,UAPzBjB,IAElBzG,EAAyByS,GACvB,+BAA+BhS,EAAiB8I,MAChD,KAAKrJ,KAAK2B,GACR,sBAAsBpB,EAAiB8I,KACvC,UAAU9I,EAAiB8I,SAEnC,GAAIxL,GAEW,GADC+D,EAAgB/D,EAAOmU,EAAalU,GAEhD,OAAOD,CAGb,CAEJ,CAiV4B2U,CACpBzK,EACAiK,EACAlU,GAGEwU,GACFlF,EAAUnG,KAAK,CACb8E,IAAK,yBACLhM,MAAOuS,GAEb,CACF,CAAE,MAAOnQ,GACPC,QAAQ2G,IAAI5G,EACd,GAGWsQ,GAAW,CACtBvS,EACAsF,EACAK,EACAZ,EACAyN,EAA6B,GAC7BC,EAAuB,MAEvBvF,EAAY,GACZhL,QAAQ2G,IAAI7I,GACZ,MAAM8R,EAAc9R,EACd0S,EAAWZ,GAAanI,gBACxBgJ,EAAiBD,GAAU3U,WAAayN,KAAKC,uBAC7C7N,EACJ+U,EAAiBD,EAAWZ,GAAa9T,eAAiBsH,EAEtDgM,EAAMQ,EAAYtR,SAClBqH,WAAEA,GAAeiK,EACjBc,EACJJ,EAAmBpS,OAAS,EAAIoS,EAAqB5Q,MAAMsC,KAAK2D,GAGlE,GAFAgK,GAAsBe,EAAiBd,EAAalU,EAAO+H,EAASZ,GAEhE0N,EAAWrS,OAAQ,CACrB,MAAMyS,EAAiBJ,EAAW/I,QAASkE,GACzCgD,GAAoBkB,EAAalU,EAAOmH,EAAU6I,IASpD,OANAV,EA9lD0B,CAACW,IAC7B,MAAM7D,EAAO,IAAI9K,IAEjB,OAAO2O,EAAQpO,OAAQqO,MAChBA,GAAOjO,OAASmK,EAAKtK,IAAIoO,EAAMjO,SAIpCmK,EAAKrC,IAAImG,EAAMjO,OACR,MAqlDKiT,CACVD,EAAepT,OACZ9B,GAA+D,IAArD+D,EAAgB/D,EAAMkC,MAAOiS,EAAalU,KAIlDmQ,EAA4Bb,EAAWvH,EAChD,CAGE,GAAIuH,EAAU9M,OAAQ,CACpB,MAAM2S,EAAM7F,EAAU9M,OACtB,IAAK,IAAI0G,EAAI,EAAGA,EAAIiM,EAAKjM,IAAK,CAC5B,IAAIkL,EAAO9E,EAAUpG,GAAGjH,MACxBmS,EAAO,MAAQA,EAAKgB,UAAUhB,EAAK5P,QAAQ,MAAQ,EAAIkP,EAAIlR,QAE7C,IADAsB,EAAgBsQ,EAAMhS,EAASpC,IAE3CsP,EAAUnG,KAAK,CACb8E,IAAK,GAAGqB,EAAUpG,GAAG+E,YACrBhM,MAAOmS,GAGb,CACF,CAOF,OAJK9E,EAAU9M,QAAWuS,IACxBzF,EAAY+C,GAAwB6B,EAAalU,EAAOmH,EAAUY,IAG7DoI,EAA4Bb,EAAWvH,IC7rDhD,IAAI3G,GAAgC,GAEpC,MAIMiU,GAAsB,CAC1BjG,EACAkG,EACAlQ,KAEA,IACE,MAAMmQ,EAAUnQ,EAAKiK,iBAAiBD,GACtC,OAA0B,IAAnBmG,EAAQ/S,QAAgB+S,EAAQ,KAAOD,CAChD,CAAE,MACA,OAAO,CACT,GAGWE,GAAoB,CAC/BF,EACAG,EAAqB,YAErB,MAAMC,EAA8C,GAC9CtQ,EAAOkQ,EAAGvJ,cAEhB,IACE,MAAM4J,EAASC,GAAaN,GAC5B,GAAIK,GAAUN,GAAoBM,EAAQL,EAAIlQ,KAC5CsQ,EAAUvM,KAAK,CAAE8E,IAAK,oBAAqBhM,MAAO0T,IACrC,WAATF,GAAmB,OAAOC,EAEhC,MAAMG,EAAYC,GAAgBR,GAC9BO,GAAaR,GAAoBQ,EAAWP,EAAIlQ,IAClDsQ,EAAUvM,KAAK,CAAE8E,IAAK,uBAAwBhM,MAAO4T,IAEvD,MAAME,EAAWC,GAAoBV,GACjCS,GACFL,EAAUvM,KAAK,CAAE8E,IAAK,sBAAuBhM,MAAO8T,GAOxD,CAAE,MAAOE,GACP3R,QAAQD,MAAM4R,EAChB,CACA,OAAOP,GAGIE,GAAgBN,IAC3B,MAAMY,EAAOZ,EAAGlV,eAAe2F,YAC/B,KAAKmQ,GAAUZ,aAAcY,EAAKlQ,SAAU,OAC5C,MAAMpD,EAAU0S,EAAG1S,QAAQC,cAC3B,GAAID,EAAQuB,SAAS,UAAYvB,EAAQuB,SAAS,UAAW,OAE7D,MAAM8B,EAAO,GACb,KAAOqP,GAAInV,WAAayN,KAAKuI,cAAc,CACzC,IAAI/G,EAAWkG,EAAGc,UAAUvT,cAC5B,GAAIyS,EAAGnE,KAAO3N,EAAc8R,EAAGnE,IAAK,CAClC/B,GAAY,IAAIiH,IAAIC,OAAOhB,EAAGnE,MAC9BlL,EAAKI,QAAQ+I,GACb,KACF,CAAO,CACL,IAAImH,EAAMjB,EACNkB,EAAM,EACV,GAAID,EAAI1F,uBACN,KAAQ0F,EAAMA,EAAI1F,wBACZ0F,EAAIH,UAAUvT,gBAAkBuM,GAAUoH,IAItC,IAARA,IACFpH,GAAY,gBAAgBoH,MAGlB,IAARA,GAAaD,GAAKpQ,eAAesQ,kBAAqB,IACxDrH,GAAY,cAAcoH,KAE9B,CACAvQ,EAAKI,QAAQ+I,GACbkG,EAAKA,EAAGnP,aACV,CACA,OAAOF,EAAK3C,KAAK,QAGboT,GAAiB,IAAIpV,IAAI,CAAC,KAAM,QAAS,UAC/C,SAASqV,GAAsBrB,GAC7B,OAAOtR,MAAMsC,KAAKgP,EAAGrL,YAClBpI,OACE2H,IACEkN,GAAe5U,IAAI0H,GAAMnC,MAAMxE,gBAChC2G,EAAKvH,QACJuB,EAAcgG,EAAKvH,QAEvBP,IAAK8H,IAAS,UAAIA,EAAKnC,SA9FKpF,EA8F4BuH,EAAKvH,MA7FzD2U,OAAO3U,GAAOsB,QAAQ,MAAO,QAAQA,QAAQ,KAAM,WAD5B,IAACtB,GA+FjC,CAEO,MAAM+T,GAAuBV,IAClC,MAAMY,EAAOZ,EAAGlV,eAAe2F,YAC/B,KAAKmQ,GAAUZ,aAAcY,EAAKlQ,SAAU,OAE5C,MAAMZ,EAAOkQ,EAAGvJ,cACV2H,EAAM4B,EAAG1S,QAAQC,cAEvB,GAAY,UAAR6Q,GAA2B,WAARA,EAAkB,OAEzC,MAAMmD,EAAgBF,GAAsBrB,GAE5C,IAAK,MAAMwB,KAAgBD,EAAe,CACxC,MAAM/Q,EAAY,GAAG4N,IAAMoD,IAE3B,GAAIzB,GAAoBvP,EAAWwP,EAAIlQ,GACrC,OAAOU,CAEX,GAIWgQ,GAAmBR,IAC9B,MAAMY,EAAOZ,EAAGlV,eAAe2F,YAC/B,KAAKmQ,GAAUZ,aAAcY,EAAKlQ,SAAU,OAC5C,MAAMpD,EAAU0S,EAAG1S,QAAQC,cAC3B,GAAID,EAAQuB,SAAS,UAAYvB,EAAQuB,SAAS,UAAW,OAE7D,MAAM8B,EAAO,GACb,KAAOqP,GAAInV,WAAayN,KAAKuI,cAAc,CACzC,IAAI/G,EAAWkG,EAAGc,UAAUvT,cAE5B,GAC0B,iBAAjByS,EAAGlE,YACVkE,EAAGlE,WACF5N,EAAc8R,EAAGlE,YACjBhQ,IAA2BqG,KACzBF,GACCA,EAAEnF,UAAYkT,GAA0B,UAApB/N,EAAEnE,eAUrB,CACL,IAAImT,EAAMjB,EACNkB,EAAM,EACV,GAAID,EAAI1F,uBACN,KAAQ0F,EAAMA,EAAI1F,wBACZ0F,EAAIH,UAAUvT,gBAAkBuM,GAAUoH,IAItC,IAARA,IACFpH,GAAY,gBAAgBoH,MAGlB,IAARA,GAAaD,GAAKpQ,eAAesQ,kBAAqB,IACxDrH,GAAY,cAAcoH,KAE9B,MArBE,GAFAlB,EAAG5E,UAAUqG,OAAO,uBACpBzB,EAAG5E,UAAUqG,OAAO,kBAChBzB,EAAGlE,UAAW,CAChBhC,GAAY,IAAIkG,EAAGlE,UAAUxP,OAAO2B,QAAQ,OAAQ,OACpD0C,EAAKI,QAAQ+I,GACb,KACF,CAkBFnJ,EAAKI,QAAQ+I,GACbkG,EAAKA,EAAGnP,aACV,CACA,OAAOF,EAAK3C,KAAK,QC7Jb0T,GAAwB,CAC5BjX,EACAC,EACAiX,KAEA,MAAMC,EAAa5I,EAAevO,GAElC,GADcoX,GAAoBnX,EAAOkX,KAC3BD,EAAQ,OAAO,EAE7B,GH0hFI,SAA4BlX,GAChC,MAAO,iEAAiEmC,KACtEnC,EAEJ,CG9hFMqX,CAAkBF,GAAa,CACjC,MAAMtW,EAASZ,EAAMa,SACnBqW,EACAlX,EACA,KACAc,YAAYuW,2BACZ,MAGF,IAAK,IAAInO,EAAI,EAAGA,EAAItI,EAAO0W,eAAgBpO,IACzC,GAAItI,EAAO2W,aAAarO,KAAO+N,EAAQ,OAAO,CAElD,CAEA,OAAO,GAGHO,GAA4B,CAChCxX,EACAoP,KAEA,IACE,MAAMqI,EAAQzX,EAAM0X,cAActI,GAClC,GAAIqI,EAAO,OAAOA,CACpB,CAAE,MAAOpT,GAEP,OADAC,QAAQD,MAAM,wBAAyB+K,EAAU/K,GAC1C,IACT,CAEA,OAAOsT,GAAyB3X,EAAM2F,KAAMyJ,IAGxCwI,GAA8B,CAClCxI,EACApP,EACAiX,IAEOO,GAA0BxX,EAAOoP,KAAc6H,EAYlDY,GAAwBC,GACrBA,EAAQzQ,MAAMxE,cAAcsB,SAAS,iBAAkB,EAkB1DwT,GAA2B,CAC/BrC,EACAlG,KAOA,MAAM2I,EAAW/T,MAAMsC,KAAKgP,EAAGjG,iBAAiB,MAEhD,IACE,IAAK,IAAInG,EAAI,EAAGA,EAAI6O,EAASvV,OAAQ0G,IACnC,GAAI6O,EAAS7O,GAAG8O,WAAY,CAC1B,MAAMA,WAAEA,GAAeD,EAAS7O,GAChC,GAAI8O,EAAY,CACd,MAAMC,EAAgBN,GAAyBK,EAAY5I,GAC3D,GAAI6I,EACF,OAAOA,EAET,GAAID,IAAe5I,EAASjL,SAAS,WACnC,OAAO6T,EAAWN,cAActI,EAEpC,CACF,CAEJ,CAAE,MAAO/K,GACPC,QAAQ2G,IAAI5G,EACd,CACA,OAAO,MAGH6T,GAAS9V,GACNA,GAAS+O,IAAM,KAGlBgH,GAAgB/V,GACZA,EAAwBgP,WAAa,KAGzCgH,GAAkBhW,GACfA,EAAQyB,aAAajC,QAAU,KAGlCyW,GAAWjW,IACf,MAAMkW,EAAYlW,EAElB,GAAIkW,EAAUC,aAAa,QAAS,CAGlC,MADa,GADKD,EAAUE,aAAa,WAE1B,IACjB,CACA,OAAO,MAGHC,GAAsB,CAC1B,qBACA,qBACA,UACA,cACA,YACA,QACA,oBACA,SACA,aACA,cAGF,SAAStB,GAAoBnX,EAAiBD,GAC5C,MAAM2Y,EAAS1Y,EAAM+F,YACrB,IAAK2S,EAAQ,OAAO,KAUpB,OARuB,IAAIA,EAAOC,gBACC9X,SACjCd,EACAC,EACA,KACA0Y,EAAO5X,YAAYgN,wBACnB,MAEiBC,eACrB,CAEA,SAAS6K,GACPd,EACAe,EACA7Y,GAEA,GAAI8X,EAAQ3T,SAAS0U,GAAW,CAC9B,MACMC,EAD8BhB,EAAQrW,MAAMoX,GACd,GAAGjX,OACjC8W,EAAS1Y,EAAM+F,YACrB,IAAK2S,EAAQ,OAAO,KACpB,IAAKZ,EAAQ3T,SAAS,WAAY,CAChC,MAAM4U,EAAiB,IAAIL,EAAOC,eAUlC,GAToBI,EAAelY,SACjCiY,EACA9Y,EACA,KACA0Y,EAAO5X,YAAYgN,wBACnB,MAGgCC,gBACf,CASjB,IAAIiL,EACJ,OAT4BD,EAAelY,SACzCiX,EACA9X,EACA,KACA0Y,EAAO5X,YAAYgN,wBACnB,MAE0CC,iBAG1CiL,EAAgBlB,EACTkB,IAEP1U,QAAQD,MAAM,+BAAgCyT,GAC9CkB,EAAgBlB,EACTkB,EAEX,CACE1U,QAAQD,MAAM,4BAA6ByU,EAE/C,CACF,CACA,OAAO,IACT,CAEA,MAAMG,GAAsB,CAC1BC,EACAlZ,KAEA,MAAMmZ,EAAmBnZ,EAAMqP,iBAC7B,4FAGE8J,GACFA,EAAiBzM,QAASgH,IACvBA,EAAgBqD,WAIrB,MAAMqC,EAAgC,IAAI9X,IAC1C,IAAI+X,EAAuB,GAE3B,SAASC,EAAcC,EAAWC,EAA0B,IAC1D,MAAMC,EAAWF,GAAMtX,MACjByX,EAAWF,EAAUvX,OAASsX,GAAMtX,MACpC0X,EAAkB,CACtBtS,KAAMmS,EAAUnS,MAAQkS,GAAMlS,KAC9BuS,KAAMJ,EAAUI,MAAQL,GAAMK,KAC9B3X,MAAOuX,EAAUvX,OAASsX,GAAMtX,MAChC4X,UAAWL,EAAUK,WAAaN,GAAMM,UACxCC,OAAQN,EAAUM,QAAUP,GAAMO,OAClCC,WAAYP,EAAUO,YAAcR,GAAMQ,YAGtCC,EAA4C,MAAvBT,GAAMU,aAEjCN,EAAWM,aAAeD,EACtB,IACAR,EAAUU,eAAe,gBACvBV,EAAUS,aAhMQ,EAC1BE,EACAV,EACAC,IAEKD,GAAaC,GACXD,IAAaC,EAAW,KADI,IA4L3BU,CAAoBT,EAAWtS,KAAMoS,EAAUC,GA6EvD,SAA2BW,GACzB,MAAMpM,EAAM,GAAGoM,EAAIhT,QAAQgT,EAAIpY,QAC1BmX,EAAiBtX,IAAImM,KACxBmL,EAAiBrP,IAAIkE,GACrBoL,EAAclQ,KAAKkR,GAEvB,CAjFEC,CAAkBX,EACpB,CAEA,SAASY,EACPC,EACA1C,EACA1I,GAEA,GAAIyI,GAAqBC,GACvB,OAAON,GAA0BgD,EAAKpL,GACjC,GAAI0I,EAAQzQ,KAAKlD,SAAS,OAASiL,EAASjH,WAAW,KAC5D,OAAOqS,EAAI9C,cAAc,IAAMzI,EAAgBG,IAC1C,GAAI0I,EAAQzQ,KAAKlD,SAAS,cAAgBiL,EAASjH,WAAW,KACnE,OAAOqS,EAAI9C,cAAc,IAAMtI,GAC1B,GAAqB,SAAjB0I,EAAQzQ,KAAiB,CAClC,MAAMoT,EAAWxL,EAAgBG,GACjC,OAAOoL,EAAI9C,cAAc,UAAU+C,MACrC,CAAO,GAAqB,YAAjB3C,EAAQzQ,KACjB,OAAOmT,EAAI9C,cAActI,GACpB,GAAqB,aAAjB0I,EAAQzQ,KACjB,OACErD,MAAMsC,KAAKkU,EAAInL,iBAAiB,MAAM5H,KACnCyG,GAAMA,EAAErK,aAAajC,SAAWwN,IAC9B,KAEF,GAAqB,oBAAjB0I,EAAQzQ,KACjB,OACErD,MAAMsC,KAAKkU,EAAInL,iBAAiB,MAAM5H,KAAMyG,GAC1CA,EAAErK,aAAaM,SAASiL,KACrB,KAEF,IACJ0I,EAAQzQ,KAAKlD,SAAS,WAAYiL,EAASjH,WAAW,OACtD2P,EAAQ8B,KAAK/Q,MAAM,WAcpB,OAAO2R,EAAI9C,cAActI,GAbzB,CACA,MACMkG,EAAK6B,GAAoBqD,EADPlM,EAAec,IAUvC,OAPIkG,GACFgE,EAAcxB,EAAS,CACrB7V,MAAOmN,EACP2K,WAAYnD,OAAOkB,EAAQiC,YAAY5V,SAAS,KAAO,IAAM,MAI1DmR,CACT,CAGF,CAEA,SAASoF,EACP1a,EACA8X,EACA1I,GAEA,MAAMuL,EAAU3a,EAAMqP,iBAAiB,UAEvC,IAAK,MAAMuL,KAAUD,EACnB,IACE,MAAME,EACJD,EAAOE,iBAAmBF,EAAOG,eAAe1a,SAElD,IAAKwa,EAAW,SAEhB,MAAMvF,EAAKiF,EAAeM,EAAW/C,EAAS1I,GAC9C,GAAIkG,EAAI,OAAOA,CACjB,CAAE,MACA,QACF,CAGF,OAAO,IACT,CAWA,MAAM0F,EAAoB,CACxBC,EACArB,EACAG,KAEA,IAAKkB,EAAK,OAAO,KAEjB,IAAIC,EAAUD,EAAIrZ,OAGlB,OAAKsZ,GAAqC,SAA1BA,EAAQrY,eAGxBqY,EAAUA,EAAQ3X,QAAQ,OAAQ,KAAKA,QAAQ,OAAQ,KAGvD2X,EAAUA,EAAQ3X,QAAQ,kBAAmB,MAG7C2X,EAAUA,EAAQ3X,QAAQ,MAAO,KAGjC2X,EAAUA,EAAQ3X,QAChB,sCACA,cAIW,OAATqW,GAA0B,SAATA,IACnBsB,EAAUA,EAAQ3X,QAAQ,QAAS,IAAI3B,QAG5B,UAATgY,GAAmC,MAAfG,GAAuBkB,EAAI9S,WAAW,OAIzD+S,GAAW,YAAYhZ,KAAKgZ,GAAiB,KAE3CA,EALE,MAvBgD,MA+B3DC,EAAU,IAAK,MAAMrD,KAAWoB,EAAOiC,SACrC,IACqBvE,OAAOkB,EAAQiC,YAAc,IAAhD,MACMqB,EAAoBlC,EAAOiC,SAAStZ,OACvCwZ,GAAuB,MAAjBA,EAAEtB,YAGX,GAAIqB,EAAkB5Y,OAAS,EAC7B,IAAK,MAAMsV,KAAWsD,EACpB9B,EAAcxB,GAIlB,MAAMwD,EAAY1E,OAAOkB,EAAQ7V,OAAS6V,EAAQ8B,MAAQ,IAC1D,GACE0B,EAAUnX,SAAS,YACnBmX,EAAUzS,MAAM,YAChByS,EAAUnX,SAAS,MACnBmX,EAAUnX,SAAS,KACnB,CACAmV,EAAcxB,GACd,QACF,CAEA,GAAIoB,EAAOqC,SAASpX,SAAS,KAC3B,MAAMgX,EAGR,IACE,IAAIvX,EAAgC,KACpC,MAAM8R,EAAYoC,EAAQ7V,MAAMR,MAAM,OAEtC,IAAK,MAAM2N,KAAYsG,EAAW,CAChC,IAAK1V,EAAO,CACVsE,QAAQD,MAAM,wBAAyB+K,GACvC,KACF,CAEA,MAAMoM,EAAkBpM,EAASxN,OAkBjC,GAfAgC,EAAgB2W,EAAeva,EAAO8X,EAAS0D,GAG1C5X,IACHA,EAAgB8W,EAAc1a,EAAO8X,EAAS0D,IAI3C5X,IACHA,EAAgB+T,GACd3X,EAAM2F,KACN6V,KAIC5X,EAAe,CAClBU,QAAQD,MAAM,wBAAyBmX,GACvC,KACF,CACF,CAEA,MAAMC,EAAgB,CAACpU,EAAcpF,KACnC,MAAMgM,EAAM,GAAG5G,KAAQpF,IACvB,OAAOmX,EAAiBtX,IAAImM,IAG9B,GAAIrK,EAAe,CACjB,MAAM8X,EAAgBxC,EAAOiC,SAAStZ,OACnCwZ,GAAiB,UAAXA,EAAEhU,MAAoBgU,EAAEpZ,OAE3B0Z,EAAsBzC,EAAOiC,SAAStZ,OACzCwZ,GAAMxD,GAAqBwD,IAAMA,EAAEpZ,OAGtC,IAAK,MAAM2Z,KAAMF,EACX1E,GAAsB4E,EAAG3Z,MAAOjC,EAAO4D,IACzC0V,EAAcsC,EAAI,CAChB3B,aAAc,OAIpB,IAAK,MAAM4B,KAAcF,EAErB/D,GACEiE,EAAW5Z,MACXjC,EACA4D,IAGF0V,EAAcuC,EAAY,CACxB5B,aAAc,OAIpB,MAAM6B,EAAiBzC,EAAcxX,OAClCwZ,GAAiB,UAAXA,EAAEhU,MAAoBgU,EAAEpZ,OAI3B8Z,EAAoB,IAAIza,IAC5Bwa,EAAepa,IAAK6F,GAAMgH,EAAgBhH,EAAEtF,SAGxC+Z,EAA+B,GAC/BC,EAAU/D,GAAMtU,GACtB,GACEqY,IACCR,EAAc,KAAMQ,KACpBzY,EAAcyY,GACf,CACA,MAAMC,EAAShD,EAAOiC,SAAS1T,KAAM4T,GAAiB,OAAXA,EAAEhU,MACzC6H,EAAclP,EAAO,KAAMic,EAASrY,KACtCoY,EAAmB7S,KAAK,MACxBmQ,EAAc4C,EAAQ,CACpB7U,KAAM,KACNuS,KAAM,SACNG,WAAY,IACZ9X,MAAOga,IAGb,CAEA,MAAMrZ,EAAUgB,EAAchB,QAC9B,GAAIA,IAAY6Y,EAAc,UAAW7Y,GAAU,CACjD,MAAMuZ,EAAUjD,EAAOiC,SAAS1T,KAAM4T,GAAiB,YAAXA,EAAEhU,MAC1C6H,EAAclP,EAAO,UAAW4C,EAASgB,KAC3CoY,EAAmB7S,KAAK,WACxBmQ,EAAc6C,EAAS,CACrB9U,KAAM,UACNuS,KAAM,SACNG,WAAY,IACZ9X,MAAOW,IAGb,CAEA,MAAMwZ,EAAYhE,GAAexU,GACjC,GAAIwY,IAAc5Y,EAAc4Y,GAAY,CAC1C,MAAMC,EAAenD,EAAOiC,SAAS1T,KAClC4T,GAAiB,aAAXA,EAAEhU,MAEP6H,EAAclP,EAAO,WAAYoc,EAAWxY,KAC9CoY,EAAmB7S,KAAK,YACxBmQ,EAAc+C,EAAc,CAC1BhV,KAAM,WACNuS,KAAM,SACNG,WAAY,IACZ9X,MAAOma,IAGb,CAEA,MAAME,EAAcjE,GAAQzU,GAC5B,GACE0Y,IACCb,EAAc,OAAQa,KACtB9Y,EAAc8Y,GACf,CACA,MAAMC,EAAWrD,EAAOiC,SAAS1T,KAAM4T,GAAiB,SAAXA,EAAEhU,MAC3C6H,EAAclP,EAAO,OAAQsc,EAAa1Y,KAC5CoY,EAAmB7S,KAAK,QACxBmQ,EAAciD,EAAU,CACtBlV,KAAM,OACNuS,KAAM,SACNG,WAAY,IACZ9X,MAAOqa,IAGb,CAEA,MAAM9a,EAAa2W,GAAavU,GAChC,GACEpC,GACsB,KAAtBA,EAAWI,SACVJ,EAAW2C,SAAS,OACpBsX,EAAc,YAAaja,KAC3BgC,EAAchC,GACf,CACA,MAAMgb,EAAmBtD,EAAOiC,SAAS1T,KACtC4T,GAAiB,cAAXA,EAAEhU,MAEP6H,EAAclP,EAAO,YAAawB,EAAYoC,KAChDoY,EAAmB7S,KAAK,aACxBmQ,EAAckD,EAAkB,CAC9BnV,KAAM,YACNuS,KAAM,SACNG,WAAY,IACZ9X,MAAOT,IAGb,CACAgU,GAAkB5R,EAAe,UAAU8I,QACxC+P,IAEGA,EAAYxa,QACXwZ,EAAc,cAAegB,EAAYxa,QAE1CqX,OAAcoD,EAAW,CACvBrV,KAAM,cACNpF,MAAOwa,EAAYxa,MACnB2X,KAAM,SACNG,WAAY,QAKpB,MACMnF,EADgB5Q,MAAMsC,KAAK1C,EAAcqG,YACNpI,OACtC2H,IAAUwS,EAAmB7X,SAASqF,EAAKnC,OAK9C,IAAIsV,EAAsB,GAC1B,IACEA,EACEhI,GAAS/Q,EAAe5D,GAAO,GAAO,EAAM4U,IAC5C,EACJ,CAAE,MAAOvQ,GACPC,QAAQD,MAAM,qCAAsCA,EACtD,CAEA,GAA6B,IAAzBsY,GAAcna,OAAc,CAC9B,MAAMoa,EAAsBlB,EAAc7Z,OACvC+Z,IAAQ5E,GAAsB4E,EAAG3Z,MAAOjC,EAAO4D,IAGlD,IAAIiZ,EAAa,EAEjB,IAAK,MAAMC,KAAYF,EAAqB,CAC1C,GAAIC,GAAcD,EAAoBpa,OAAQ,MAE9C,MAAMua,EAAkBxO,EAAgBuO,EAAS7a,OACjD,GAAI8Z,EAAkBja,IAAIib,GAAkB,SAE5C,MAAMlU,EAAQ8T,EAAalV,KACxBuV,GAAMA,EAAE/a,OAASsM,EAAgByO,EAAE/a,SAAW8a,GAG7ClU,GAAO5G,QACTqX,EAAcwD,EAAU,CACtBzV,KAAM,QACNpF,MAAO4G,EAAM5G,MACb2X,KAAM,SACNG,WAAY,IACZE,aAAc,MAGhB8B,EAAkBhS,IAAIgT,GACtBF,IAEJ,CACA,GAAIA,EAAaD,EAAoBpa,OACnC,IAAK,MAAM5B,KAAU+b,EAAc,CACjC,GAAIE,GAAcD,EAAoBpa,OAAQ,MAC9C,IAAK5B,EAAOqB,MAAO,SAEnB,MAAMyB,EAAU6K,EAAgB3N,EAAOqB,OACnC8Z,EAAkBja,IAAI4B,KAE1B4V,EAAc1Y,EAAQ,CACpByG,KAAM,QACNpF,MAAOrB,EAAOqB,MACd2X,KAAM,SACNG,WAAY,IACZE,aAAc,MAGhB8B,EAAkBhS,IAAIrG,GACtBmZ,IACF,CAEJ,CACA,IAAK,MAAM/E,KAAWoB,EAAOiC,SAC3B,IACE,IAAK,MAAM8B,KAAO/D,EAAOiC,SACvB,GAAK8B,EAAIhb,MAET,IAAK,MAAM4W,KAAYJ,GACrB,GAAIwE,EAAIhb,MAAMkC,SAAS0U,GAAW,CAChC,MAAMG,EAAgBJ,GACpBqE,EAAIhb,MACJ4W,EACA7Y,GAEF,GAAIgZ,EAAe,CACjBM,EAAc2D,EAAK,CACjB5V,KAAM,QACNpF,MAAO+W,EACPe,WACyB,KAAvBjC,EAAQiC,YACe,OAAvBjC,EAAQiC,WACJjC,EAAQiC,WACR,MAER,KACF,CACF,CAGN,CAAE,MAAO1V,GACPC,QAAQD,MAAM,4BAA6ByT,EAASzT,EACtD,CAEF,GAAIgV,EAAc7W,OAAS,EAAG,CAC5B,MAAM0a,EAAqB,CACzB,CAAE7V,KAAM,KAAMpF,MAAOiW,GAAMtU,IAC3B,CAAEyD,KAAM,OAAQpF,MAAOoW,GAAQzU,IAC/B,CAAEyD,KAAM,YAAapF,MAAOkW,GAAavU,IACzC,CAAEyD,KAAM,UAAWpF,MAAO2B,EAAchB,SACxC,CAAEyE,KAAM,WAAYpF,MAAOmW,GAAexU,KAG5C,IAAK,MAAMkC,KAAaoX,EAAoB,CAC1C,GAAI7D,EAAc7W,OAAS,EAAG,MAE9B,MAAM6E,KAAEA,EAAIpF,MAAEA,GAAU6D,EACnB7D,IACDuB,EAAcvB,IACdwZ,EAAcpU,EAAMpF,IACX,cAAToF,GAAwBpF,EAAMkC,SAAS,MACvC+K,EAAclP,EAAOqH,EAAMpF,EAAO2B,IACpC0V,OAAcoD,EAAW,CACvBrV,OACAuS,KAAM,SACN3X,QACA8X,WAAY,IACZE,aAAc,MAGpB,CAEIZ,EAAc7W,OAAS,GACzBgT,GAAkB5R,EAAe,YAAY8I,QAC1C+P,IACKpD,EAAc7W,OAAS,GACtBia,EAAYxa,QACbwZ,EAAc,cAAegB,EAAYxa,QAE1CiN,EACClP,EACA,cACAyc,EAAYxa,MACZ2B,IAMJ0V,OAAcoD,EAAW,CACvBrV,KAAM,cACNuS,KAAM,SACN3X,MAAOwa,EAAYxa,MACnB8X,WAAY,IACZE,aAAc,QAKxB,CAEA,MAAMkD,EAA0B9D,EAAc3X,IAAK2Y,IAAG,IACjDA,EACHpY,MAAO+Y,EAAkBX,EAAIpY,MAAOoY,EAAIhT,KAAMgT,EAAIN,eAG9CqD,EAAa,CACjB,CACE/V,KAAM,GAAG6R,EAAO7R,OAChBgW,KAAM,GAAGnE,EAAOmE,OAChBzD,KAAM,GAAGV,EAAOU,OAChBuB,SAAUgC,EAAwBtb,OAC/BiW,GAA8B,MAAlBA,GAAS7V,OAAmC,KAAlB6V,EAAQ7V,OAEjDsZ,SAAU,GAAGrC,EAAOqC,WACpB+B,UAAW,GAAGpE,EAAOoE,YACrBC,YAAa,GAAGrE,EAAOqE,cACvBxD,WAAY,GAAGb,EAAOa,aACtByD,OAAQ,GAAGtE,EAAOsE,SAClBC,SAAU,GAAGvE,EAAOuE,WACpBC,WAAY,GAAGxE,EAAOwE,aACtBC,SAAU,GAAGzE,EAAOyE,WACpBC,UAAW,GAAG1E,EAAO0E,YACrBC,YAAa,GAAG3E,EAAO2E,cACvBC,OAAQ,GAAG5E,EAAO4E,WAItB,OAAOV,CACT,CACF,CAAE,MAAO/Y,GACPC,QAAQD,MAAM,4BAA6ByT,EAASzT,GACpD,QACF,CACF,CAAE,MAAOA,GACPC,QAAQD,MAAM,4BAA6ByT,EAASzT,GACpD,QACF,CAEF,OAAO,MCzwBT0Z,eAAeC,KAoDb,MAAO,CAAEC,SAnDQF,MAAOG,IACtB,MAAMC,EAAiB,IAAIC,EAErBC,EAAM,IAAIC,EACdJ,EACG3a,QAAQ,OAAQ,KAChBA,QAAQ,OAAQ,MAChBA,QAAQ,OAAQ,MAChBA,QAAQ,OAAQ,MACnB,CACEgb,mBAAmB,EACnBC,WAAY,cACZC,UAAW,SACXN,oBAIEzF,OAAEA,GAAW2F,EAInBK,OAAOre,SAAWqY,EAAOrY,SAEzBqe,OAAO9Q,KAAO8K,EAAO9K,KAErB8Q,OAAO1Y,QAAU0S,EAAO1S,QAExB0Y,OAAOC,YAAcjG,EAAOiG,YAE5BD,OAAO9W,WAAa8Q,EAAO9Q,WAE3B,MAAMgX,EAASlG,EAAOrC,KAAO,CAAA,EAa7B,OAZKuI,EAAOtI,SACVsI,EAAOtI,OAAUrU,GACf2U,OAAO3U,GAAOsB,QAAQ,kBAAmB,SAG7Cmb,OAAOrI,IAAMuI,EAGbF,OAAO5d,YAAc4X,EAAO5X,YAE5B4d,OAAO/F,eAAiBD,EAAOC,eAExB,CACLkG,SAAUd,MAAOe,GACR7F,GAAoB6F,EAAMpG,EAAOrY,YAMhD"}
1
+ {"version":3,"file":"xpath.mjs","sources":["../src/utils/xpathHelpers.ts","../src/utils/xpath.ts","../src/utils/cssSelector.ts","../src/utils/getElementsFromHTML.ts","../src/node/xpath.ts"],"sourcesContent":["export const reWhiteSpace = /^[\\S]+( [\\S]+)*$/i;\r\nexport let cspEnabled: boolean = false;\r\nconst xpathCache: { [x: string]: number } = {};\r\n\r\nexport const timeLog = (label: string, start: number) => {\r\n const duration = performance.now() - start;\r\n console.log(`⏱️ ${label}: ${duration.toFixed(2)}ms time`);\r\n};\r\n\r\ntype EvalResult = {\r\n first: Node | null;\r\n second: Node | null;\r\n};\r\n\r\n// cache per context node → avoids cross-context pollution\r\nlet xpathEvalCache = new WeakMap<Node, Map<string, EvalResult>>();\r\n\r\nconst getXPathContext = (docmt: Node) => {\r\n const owner: Document =\r\n docmt.nodeType === 9\r\n ? (docmt as Document)\r\n : docmt.ownerDocument || document;\r\n\r\n // valid XPath context nodes: Element(1), Document(9), DocumentFragment/ShadowRoot(11)\r\n const contextNode: Node =\r\n docmt.nodeType === 1 || docmt.nodeType === 9 || docmt.nodeType === 11\r\n ? docmt\r\n : owner;\r\n\r\n return { owner, contextNode };\r\n};\r\n\r\nconst getMutationCacheContexts = (node: Node): Node[] => {\r\n const contexts: Node[] = [];\r\n const addContext = (candidate: Node | null | undefined) => {\r\n if (!candidate || contexts.includes(candidate)) return;\r\n contexts.push(candidate);\r\n };\r\n\r\n addContext(node);\r\n\r\n const rootNode = node.getRootNode?.();\r\n if (rootNode instanceof Document || rootNode instanceof ShadowRoot) {\r\n addContext(rootNode);\r\n }\r\n\r\n const ownerDocument =\r\n node.nodeType === Node.DOCUMENT_NODE\r\n ? (node as Document)\r\n : node.ownerDocument;\r\n addContext(ownerDocument);\r\n\r\n return contexts;\r\n};\r\n\r\nexport const clearXPathEvalCache = (node?: Node | null) => {\r\n if (!node) {\r\n xpathEvalCache = new WeakMap<Node, Map<string, EvalResult>>();\r\n return;\r\n }\r\n\r\n getMutationCacheContexts(node).forEach((contextNode) => {\r\n xpathEvalCache.delete(contextNode);\r\n });\r\n};\r\n\r\nexport const evaluateXPathOnce = (xpath: string, docmt: Node): EvalResult => {\r\n const { owner, contextNode } = getXPathContext(docmt);\r\n\r\n // get or create cache for this context node\r\n let nodeCache = xpathEvalCache.get(contextNode);\r\n if (!nodeCache) {\r\n nodeCache = new Map<string, EvalResult>();\r\n xpathEvalCache.set(contextNode, nodeCache);\r\n }\r\n\r\n // cache hit\r\n const cached = nodeCache.get(xpath);\r\n if (cached) return cached;\r\n\r\n // evaluate once, stop after 2 nodes\r\n const result = owner.evaluate(\r\n xpath,\r\n contextNode,\r\n null,\r\n XPathResult.ORDERED_NODE_ITERATOR_TYPE,\r\n null\r\n );\r\n\r\n const first = result.iterateNext();\r\n const second = result.iterateNext();\r\n\r\n const evalResult: EvalResult = { first, second };\r\n nodeCache.set(xpath, evalResult);\r\n\r\n return evalResult;\r\n};\r\nlet relativeXPathCache = new Map();\r\nexport let modifiedElementAttributes: {\r\n url: string | null;\r\n attributeName: string | null;\r\n element: HTMLElement | Element;\r\n doc: Document;\r\n}[] = [];\r\nconst modifiedElementAttributeNames = new WeakMap<Element, Set<string>>();\r\nconst modifiedElementClassTokens = new WeakMap<Element, Set<string>>();\r\nlet mutationObserver: MutationObserver;\r\nconst INTERNAL_CLASS_TOKENS = new Set([\r\n \"marked-element-temp\",\r\n \"marked-element\",\r\n \"mark-successfully\",\r\n \"flntooltip\",\r\n \"flntooltip-bottom\",\r\n \"fln-shield\",\r\n \"removePointers\"\r\n]);\r\nconst INTERNAL_ELEMENT_CLASS_TOKENS = new Set([\r\n \"flntooltip\",\r\n \"flntooltip-bottom\",\r\n \"fln-shield\"\r\n]);\r\nconst UNSTABLE_FRAMEWORK_CLASS_TOKEN_PATTERN =\r\n /^(?:(?:ng|ion)-(?:pristine|dirty|valid|invalid|touched|untouched|pending|submitted|focused)|(?:item-)?has-(?:focus|value)|ng-star-inserted|hydrated|md|ios)$/;\r\n\r\nconst normalizeClassTokens = (\r\n classValue: string | null | undefined\r\n): string[] =>\r\n (classValue || \"\")\r\n .split(/\\s+/)\r\n .map((token) => token.trim())\r\n .filter((token) => token && !INTERNAL_CLASS_TOKENS.has(token))\r\n .sort();\r\n\r\nexport const hasNumericAttributeValue = (value: string | null | undefined) =>\r\n /\\d/.test(value || \"\");\r\n\r\nconst isUnstableFrameworkClassToken = (token: string): boolean =>\r\n UNSTABLE_FRAMEWORK_CLASS_TOKEN_PATTERN.test(token);\r\n\r\nexport const getClassTokenConditions = (\r\n element: HTMLElement | Element,\r\n classValue: string | null | undefined,\r\n docmt: Document | ShadowRoot,\r\n allowNumericToken = false\r\n): string[] => {\r\n // Class tokens from framework state/lifecycle changes are not stable\r\n // locators, even when they were already present before observation started.\r\n const allClassTokens = normalizeClassTokens(classValue);\r\n const classTokens = getStableClassTokens(\r\n element,\r\n classValue,\r\n allowNumericToken\r\n );\r\n\r\n if (!classTokens.length) return [];\r\n\r\n if (allClassTokens.length === 1 && classTokens.length === 1) {\r\n // Single class value: exact class is more precise and not affected by\r\n // class-token ordering because there is no competing token.\r\n return [`@class=${escapeCharacters(classTokens[0])}`];\r\n }\r\n\r\n return classTokens\r\n .map((token) => {\r\n const xpath = buildPattern(\r\n `contains(@class,${escapeCharacters(token)})`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n\r\n return {\r\n token,\r\n count: getTagOnlyXpathCandidateCount(xpath, element, docmt)\r\n };\r\n })\r\n .sort((left, right) => {\r\n // Multi-token class value: prefer the shortest stable token; uniqueness\r\n // is still validated by the caller/final XPath count.\r\n if (left.token.length !== right.token.length) {\r\n return left.token.length - right.token.length;\r\n }\r\n\r\n return left.count - right.count;\r\n })\r\n .map(({ token }) => `contains(@class,${escapeCharacters(token)})`);\r\n};\r\n\r\nexport const getStableClassTokens = (\r\n element: HTMLElement | Element,\r\n classValue: string | null | undefined,\r\n allowNumericToken = false\r\n): string[] => {\r\n const blockedClassTokens = modifiedElementClassTokens.get(element);\r\n\r\n return normalizeClassTokens(classValue).filter(\r\n (token) =>\r\n !blockedClassTokens?.has(token) &&\r\n !isUnstableFrameworkClassToken(token) &&\r\n (allowNumericToken ? true : !hasNumericAttributeValue(token))\r\n );\r\n};\r\n\r\nexport const getClassTokenCondition = (\r\n element: HTMLElement | Element,\r\n classValue: string | null | undefined,\r\n docmt: Document | ShadowRoot,\r\n allowNumericToken = false\r\n): string | null => {\r\n return (\r\n getClassTokenConditions(element, classValue, docmt, allowNumericToken)[0] ||\r\n null\r\n );\r\n};\r\n\r\nconst TEST_ID_ATTRIBUTE_NAMES = new Set([\r\n \"data-testid\",\r\n \"data-test-id\",\r\n \"data-test\",\r\n \"data-cy\",\r\n \"testid\"\r\n]);\r\n\r\nconst hasGeneratedNumericSegment = (attributeValue: string): boolean =>\r\n /(?:^|[-_:])\\d{4,}(?:$|[-_:])/.test(attributeValue);\r\n\r\nconst isGeneratedTestIdAttribute = (\r\n attributeName: string,\r\n attributeValue: string\r\n): boolean =>\r\n TEST_ID_ATTRIBUTE_NAMES.has(attributeName.toLowerCase()) &&\r\n hasGeneratedNumericSegment(attributeValue);\r\n\r\nexport const sanitizeAttributeValue = (\r\n attributeName: string,\r\n attributeValue: string | null | undefined\r\n): string => {\r\n if (!attributeValue) {\r\n return \"\";\r\n }\r\n\r\n if (attributeName === \"class\" || attributeName === \"className\") {\r\n return normalizeClassTokens(attributeValue).join(\" \");\r\n }\r\n\r\n return attributeValue.replace(\"removePointers\", \"\").trim();\r\n};\r\n\r\nconst isInternalClassOnlyMutation = (mutation: MutationRecord): boolean => {\r\n if (\r\n mutation.type !== \"attributes\" ||\r\n mutation.attributeName !== \"class\" ||\r\n !(mutation.target instanceof Element)\r\n ) {\r\n return false;\r\n }\r\n\r\n if (\r\n isSvg(mutation.target) &&\r\n mutation.oldValue?.trim() === mutation.target.classList.value?.trim()\r\n ) {\r\n return true;\r\n }\r\n\r\n const previousTokens = normalizeClassTokens(mutation.oldValue);\r\n const currentTokens = normalizeClassTokens(\r\n mutation.target.getAttribute(\"class\")\r\n );\r\n\r\n return previousTokens.join(\" \") === currentTokens.join(\" \");\r\n};\r\n\r\nconst hasInternalElementClassToken = (\r\n classValue: string | null | undefined\r\n): boolean =>\r\n (classValue || \"\")\r\n .split(/\\s+/)\r\n .some((token) => INTERNAL_ELEMENT_CLASS_TOKENS.has(token));\r\n\r\nconst isFireFlinkInternalElementMutation = (\r\n mutation: MutationRecord\r\n): boolean => {\r\n if (mutation.type !== \"attributes\" || !(mutation.target instanceof Element)) {\r\n return false;\r\n }\r\n\r\n return (\r\n hasInternalElementClassToken(mutation.oldValue) ||\r\n hasInternalElementClassToken(mutation.target.getAttribute(\"class\")) ||\r\n mutation.target.hasAttribute(\"locator-data-tooltip\")\r\n );\r\n};\r\n\r\nconst trackModifiedClassTokens = (mutation: MutationRecord): void => {\r\n if (\r\n mutation.type !== \"attributes\" ||\r\n mutation.attributeName !== \"class\" ||\r\n !(mutation.target instanceof Element)\r\n ) {\r\n return;\r\n }\r\n\r\n const previousTokens = new Set(normalizeClassTokens(mutation.oldValue));\r\n const currentTokens = normalizeClassTokens(\r\n mutation.target.getAttribute(\"class\")\r\n );\r\n const addedTokens = currentTokens.filter((token) => !previousTokens.has(token));\r\n\r\n if (!addedTokens.length) {\r\n return;\r\n }\r\n\r\n let blockedClassTokens = modifiedElementClassTokens.get(mutation.target);\r\n\r\n if (!blockedClassTokens) {\r\n blockedClassTokens = new Set();\r\n modifiedElementClassTokens.set(mutation.target, blockedClassTokens);\r\n }\r\n\r\n const newlyBlockedTokens = addedTokens.filter(\r\n (token) => !blockedClassTokens?.has(token)\r\n );\r\n\r\n if (!newlyBlockedTokens.length) {\r\n return;\r\n }\r\n\r\n newlyBlockedTokens.forEach((token) => blockedClassTokens?.add(token));\r\n console.log(\"[ff-dom] blocked modified class tokens\", {\r\n tokens: newlyBlockedTokens,\r\n element: mutation.target,\r\n currentValue: mutation.target.getAttribute(\"class\"),\r\n url: mutation.target.baseURI\r\n });\r\n};\r\n\r\nexport const createObserver = (addedNodeCallBack: Function) => {\r\n mutationObserver = new MutationObserver((mutations) => {\r\n mutations.forEach((mutation) => {\r\n const shouldInvalidateXPathCache =\r\n mutation.type === \"childList\" ||\r\n mutation.type === \"characterData\" ||\r\n (mutation.type === \"attributes\" &&\r\n mutation.attributeName !== \"flndisabled\" &&\r\n !isInternalClassOnlyMutation(mutation));\r\n\r\n if (shouldInvalidateXPathCache) {\r\n clearXPathEvalCache(mutation.target);\r\n }\r\n\r\n if (mutation?.addedNodes?.length) {\r\n addedNodeCallBack(mutation?.addedNodes);\r\n }\r\n\r\n if (mutation.target instanceof HTMLElement) {\r\n if (\r\n isInternalClassOnlyMutation(mutation) ||\r\n isFireFlinkInternalElementMutation(mutation)\r\n ) {\r\n } else if (\r\n mutation?.type === \"attributes\" &&\r\n mutation.attributeName === \"class\"\r\n ) {\r\n trackModifiedClassTokens(mutation);\r\n } else if (\r\n mutation?.type === \"attributes\" &&\r\n ![\"flndisabled\"].includes(mutation.attributeName!)\r\n ) {\r\n const modifiedAttribute = {\r\n url: mutation.target.baseURI,\r\n attributeName: mutation.attributeName,\r\n element: mutation.target,\r\n doc: mutation.target.ownerDocument\r\n };\r\n let attributeNames = modifiedElementAttributeNames.get(\r\n mutation.target\r\n );\r\n\r\n if (!attributeNames) {\r\n attributeNames = new Set();\r\n modifiedElementAttributeNames.set(mutation.target, attributeNames);\r\n }\r\n\r\n if (!attributeNames.has(mutation.attributeName!)) {\r\n attributeNames.add(mutation.attributeName!);\r\n modifiedElementAttributes.push(modifiedAttribute);\r\n console.log(\"[ff-dom] blocked modified attribute\", {\r\n ...modifiedAttribute,\r\n currentValue: mutation.attributeName\r\n ? mutation.target.getAttribute(mutation.attributeName)\r\n : null\r\n });\r\n }\r\n }\r\n }\r\n });\r\n });\r\n};\r\nexport const startObserver = (\r\n target: Node,\r\n options: MutationObserverInit | undefined\r\n) => {\r\n mutationObserver?.observe(target, options);\r\n};\r\n\r\nexport const stopObserver = () => {\r\n mutationObserver?.disconnect();\r\n};\r\n\r\nexport const isNumberExist = (str: string): boolean => {\r\n const hasNumber = /\\d/;\r\n return hasNumber.test(str);\r\n};\r\n\r\nexport const buildPattern = (\r\n pattern: string,\r\n isSvg: boolean,\r\n tagName: string\r\n): string => {\r\n return isSvg\r\n ? `//*[local-name()='${tagName}' and ${pattern}]`\r\n : `//${tagName}[${pattern}]`;\r\n};\r\n\r\nexport const getTextContent = (\r\n targetElement: HTMLElement | Element\r\n): string => {\r\n const textContent = targetElement?.textContent;\r\n if (cspEnabled) {\r\n if (textContent) {\r\n const tooltip = document.querySelector(\".flntooltip\") as HTMLElement;\r\n if (tooltip) {\r\n const lastIndex = textContent.lastIndexOf(tooltip.innerText);\r\n if (\r\n lastIndex &&\r\n textContent.length === lastIndex + tooltip.innerText.length\r\n ) {\r\n return textContent.substring(0, lastIndex);\r\n }\r\n } else {\r\n return textContent;\r\n }\r\n }\r\n } else {\r\n return textContent;\r\n }\r\n\r\n return \"\";\r\n};\r\n\r\nexport const getFilteredText = (element: Node): string => {\r\n return element?.childNodes[0]?.nodeValue || \"\";\r\n};\r\n\r\nexport const getCountOfXPath = (\r\n xpath: string,\r\n element: HTMLElement | Element,\r\n docmt: Node,\r\n multiElementReferenceMode: boolean = false\r\n): number => {\r\n try {\r\n if (isShadowRootNode(docmt)) {\r\n // XPath cannot evaluate directly against ShadowRoot/DocumentFragment, so\r\n // count against the cloned evaluation context used for shadow DOM.\r\n const { owner, contextNode, cloneElement, cloneElements } =\r\n createShadowEvaluationContext(docmt, element);\r\n const result = owner.evaluate(\r\n xpath,\r\n contextNode,\r\n null,\r\n XPathResult.ORDERED_NODE_ITERATOR_TYPE,\r\n null\r\n );\r\n const first = result.iterateNext();\r\n const second = result.iterateNext();\r\n\r\n if (!first) return 0;\r\n\r\n if (!second) {\r\n return first === cloneElement ? 1 : 0;\r\n }\r\n\r\n if (multiElementReferenceMode && Array.isArray(element)) {\r\n let matchCount = 0;\r\n\r\n if (cloneElements.includes(first as Element)) matchCount++;\r\n if (cloneElements.includes(second as Element)) matchCount++;\r\n\r\n if (matchCount === 2) return 2;\r\n\r\n let node: Node | null;\r\n while ((node = result.iterateNext())) {\r\n if (cloneElements.includes(node as Element)) {\r\n matchCount++;\r\n if (matchCount === 2) return 2;\r\n }\r\n }\r\n\r\n return 0;\r\n }\r\n\r\n return 2;\r\n }\r\n\r\n const { first, second } = evaluateXPathOnce(xpath, docmt);\r\n\r\n if (!first) return 0;\r\n\r\n // exactly one match\r\n if (!second) {\r\n return first === element ? 1 : 0;\r\n }\r\n\r\n // multiple matches\r\n if (multiElementReferenceMode && Array.isArray(element)) {\r\n let matchCount = 0;\r\n\r\n if (element.includes(first)) matchCount++;\r\n if (element.includes(second)) matchCount++;\r\n\r\n if (matchCount === 2) return 2;\r\n const owner =\r\n docmt.nodeType === 9 ? (docmt as Document) : docmt.ownerDocument!;\r\n\r\n const result = owner.evaluate(\r\n xpath,\r\n docmt,\r\n null,\r\n XPathResult.ORDERED_NODE_ITERATOR_TYPE,\r\n null\r\n );\r\n\r\n let node: Node | null;\r\n while ((node = result.iterateNext())) {\r\n if (element.includes(node)) {\r\n matchCount++;\r\n if (matchCount === 2) return 2;\r\n }\r\n }\r\n\r\n return 0;\r\n }\r\n\r\n return 2;\r\n } catch (error) {\r\n console.error(`Error evaluating XPath: ${xpath}`, error);\r\n return 0;\r\n }\r\n};\r\n\r\nexport const escapeCharacters = (text: string): string => {\r\n if (text) {\r\n if (!(text.indexOf('\"') === -1)) {\r\n return `'${text}'`;\r\n }\r\n if (!(text.indexOf(\"'\") === -1)) {\r\n return `\"${text}\"`;\r\n }\r\n }\r\n return `'${text}'`;\r\n};\r\n\r\nexport const removeParenthesis = (xpath: string): string => {\r\n const charArr = xpath.split(\"\");\r\n\r\n let count = charArr.length;\r\n const indexArray = [];\r\n\r\n while (charArr[count - 2] !== \"[\") {\r\n indexArray.push(charArr[count - 2]);\r\n count--;\r\n }\r\n\r\n indexArray.reverse();\r\n let finalStr = \"\";\r\n for (let i = 0; i < indexArray.length; i++) {\r\n finalStr += indexArray[i];\r\n }\r\n\r\n const endBracketLength = finalStr.length + 2;\r\n let firstpart = xpath.slice(0, xpath.length - endBracketLength);\r\n\r\n if (firstpart.startsWith(\"(\") && firstpart.endsWith(\")\")) {\r\n firstpart = firstpart.slice(1, -1);\r\n } else {\r\n firstpart = xpath;\r\n }\r\n\r\n return firstpart;\r\n};\r\n\r\nexport const findXpathWithIndex = (\r\n val: string,\r\n node: HTMLElement | Element,\r\n docmt: Node,\r\n count: number\r\n) => {\r\n try {\r\n const owner =\r\n docmt.nodeType === 9 // DOCUMENT_NODE\r\n ? (docmt as Document)\r\n : docmt.ownerDocument!;\r\n\r\n let index = 0;\r\n if (count) {\r\n if (getCountOfXPath(`${val}[${count}]`, node, docmt) === 1) {\r\n return `${val}[${count}]`;\r\n }\r\n\r\n if (getCountOfXPath(`(${val})[${count}]`, node, docmt) === 1) {\r\n return `(${val})[${count}]`;\r\n }\r\n }\r\n\r\n const nodes = owner.evaluate(val, docmt, null, XPathResult.ANY_TYPE, null);\r\n let nodex: Node | null = null;\r\n while ((nodex = nodes.iterateNext())) {\r\n index++;\r\n if (nodex.isSameNode(node)) {\r\n if (getCountOfXPath(`${val}[${index}]`, node, docmt) === 1) {\r\n return `${val}[${index}]`;\r\n }\r\n if (getCountOfXPath(`(${val})[${index}]`, node, docmt) === 1) {\r\n return `(${val})[${index}]`;\r\n }\r\n return;\r\n }\r\n }\r\n } catch (error) {\r\n console.log(error);\r\n }\r\n};\r\n\r\nconst deleteLineGap = (a: string): string => {\r\n a &&= a.split(\"\\n\")[0].length > 0 ? a.split(\"\\n\")[0] : a.split(\"\\n\")[1];\r\n return a;\r\n};\r\n\r\nconst deleteGarbageFromInnerText = (a: string): string => {\r\n a = deleteLineGap(a);\r\n a = a\r\n .split(/[^\\u0000-\\u00ff]/)\r\n .reduce((b, c) => {\r\n return b.length > c.length ? b : c;\r\n }, \"\")\r\n .trim();\r\n return (a = a.split(\"/\")[0].trim());\r\n};\r\n\r\nexport const replaceWhiteSpaces = (str: string): string => {\r\n if (str) {\r\n return str.replace(/\\s\\s+/g, \" \").trim();\r\n }\r\n\r\n return str;\r\n};\r\n\r\nexport const getContainerTextCondition = (\r\n element: HTMLElement | Element,\r\n text: string | null | undefined\r\n): string => {\r\n const normalizedText = replaceWhiteSpaces((text || \"\").trim());\r\n\r\n if (!element.children.length || normalizedText.length <= 50) {\r\n return \"\";\r\n }\r\n\r\n const fragment = normalizedText.split(/\\s+/).slice(0, 3).join(\" \");\r\n\r\n // Long container text is usually flattened descendant text. Use a short\r\n // visible fragment instead of exact matching the whole menu/header content.\r\n return fragment.length > 2\r\n ? `contains(normalize-space(.),${escapeCharacters(fragment)})`\r\n : \"\";\r\n};\r\n\r\nexport const getStableTargetTextCandidates = (\r\n text: string | null | undefined\r\n): { prefix: string; fragment: string } => {\r\n const normalizedText = replaceWhiteSpaces((text || \"\").trim());\r\n\r\n if (!normalizedText || !hasNumericAttributeValue(normalizedText)) {\r\n return { prefix: normalizedText, fragment: normalizedText };\r\n }\r\n\r\n const prefix = normalizedText\r\n .split(/[0-9]/)[0]\r\n .replace(/[₹$€£¥₫₽₩₦₱₲₴₵₡₭₮₺฿៛]+/g, \"\")\r\n .trim();\r\n const fragment = prefix\r\n .split(/[^a-zA-Z]+/)\r\n .map((part) => replaceWhiteSpaces(part).trim())\r\n .filter((part) => part.length > 2 && /[a-zA-Z]/.test(part))\r\n .join(\" \")\r\n .trim();\r\n\r\n // Target text containing numbers is dynamic. Keep only a meaningful\r\n // non-numeric prefix/fragment, so price/count text never becomes exact XPath.\r\n return {\r\n prefix: prefix.length > 2 && /[a-zA-Z]/.test(prefix) ? prefix : \"\",\r\n fragment: fragment.length > 2 ? fragment : \"\"\r\n };\r\n};\r\n\r\nexport const getStableTargetText = (\r\n text: string | null | undefined\r\n): string => getStableTargetTextCandidates(text).fragment;\r\n\r\nexport const getShadowRoot = (el: HTMLElement | Element): Element | null => {\r\n if (!el || !el?.getRootNode) return null;\r\n\r\n const root = el.getRootNode() as ShadowRoot;\r\n return root && root?.host ? (root as unknown as Element) : null;\r\n};\r\n\r\nconst isShadowRootNode = (node: Node): node is ShadowRoot => {\r\n return node?.nodeType === Node.DOCUMENT_FRAGMENT_NODE && \"host\" in node;\r\n};\r\n\r\nconst getElementPathFromRoot = (\r\n root: ShadowRoot,\r\n element: Element\r\n): number[] | null => {\r\n const path: number[] = [];\r\n let current: Element | null = element;\r\n\r\n while (current && current.parentElement) {\r\n const parent: Element = current.parentElement as Element;\r\n path.unshift(Array.from(parent.children).indexOf(current));\r\n current = parent;\r\n }\r\n\r\n if (!current || current.parentNode !== root) {\r\n return null;\r\n }\r\n\r\n path.unshift(Array.from(root.children).indexOf(current));\r\n return path;\r\n};\r\n\r\nconst getElementAtPath = (\r\n container: ParentNode & { children: HTMLCollection },\r\n path: number[]\r\n): Element | null => {\r\n let current: (ParentNode & { children: HTMLCollection }) | Element =\r\n container;\r\n\r\n for (const index of path) {\r\n const next: Element | null = current.children.item(index) as Element | null;\r\n if (!next) {\r\n return null;\r\n }\r\n\r\n current = next;\r\n }\r\n\r\n return current as Element | null;\r\n};\r\n\r\nexport const createShadowEvaluationContext = (\r\n root: ShadowRoot,\r\n element?: HTMLElement | Element | (HTMLElement | Element)[]\r\n) => {\r\n const tempDoc =\r\n root.ownerDocument.implementation.createHTMLDocument(\"shadow-xpath\");\r\n const wrapper = tempDoc.createElement(\"div\");\r\n wrapper.innerHTML = root.innerHTML;\r\n tempDoc.body.appendChild(wrapper);\r\n\r\n const cloneForElement = (candidate?: HTMLElement | Element | null) => {\r\n if (\r\n !candidate ||\r\n !(candidate instanceof root.ownerDocument.defaultView!.Element)\r\n ) {\r\n return null;\r\n }\r\n\r\n const path = getElementPathFromRoot(root, candidate);\r\n return path ? getElementAtPath(wrapper, path) : null;\r\n };\r\n\r\n const cloneElements = Array.isArray(element)\r\n ? element.map((candidate) => cloneForElement(candidate)).filter(Boolean)\r\n : [];\r\n\r\n const cloneElement = Array.isArray(element)\r\n ? null\r\n : cloneForElement(element ?? null);\r\n\r\n return {\r\n owner: tempDoc,\r\n contextNode: wrapper,\r\n cloneElement,\r\n cloneElements\r\n };\r\n};\r\n\r\nexport const checkBlockedAttributes = (\r\n attribute: {\r\n name: string;\r\n value: string;\r\n },\r\n targetElement: HTMLElement | Element,\r\n isTarget: boolean\r\n): boolean => {\r\n const sanitizedValue = sanitizeAttributeValue(\r\n attribute.name,\r\n attribute.value\r\n );\r\n\r\n if (!sanitizedValue || typeof attribute?.value === \"boolean\") {\r\n return false;\r\n }\r\n const blockedValues = [\r\n \"true\",\r\n \"false\",\r\n \"on\",\r\n \"off\",\r\n \"flntooltip\",\r\n \"flutter-highlight-overlay\"\r\n ];\r\n if (blockedValues.some((x) => x === sanitizedValue)) {\r\n return false;\r\n }\r\n const blockedNames = [\"style\", \"locator-data-tooltip\", \"value\"];\r\n if (blockedNames.some((x) => x === attribute.name)) {\r\n return false;\r\n }\r\n\r\n if (attribute.name === \"class\" || attribute.name === \"className\") {\r\n const blockedClassTokens = modifiedElementClassTokens.get(targetElement);\r\n const usableClassTokens = normalizeClassTokens(attribute.value).filter(\r\n (token) =>\r\n !blockedClassTokens?.has(token) &&\r\n !isUnstableFrameworkClassToken(token) &&\r\n !hasNumericAttributeValue(token)\r\n );\r\n\r\n return usableClassTokens.length > 0;\r\n }\r\n\r\n const isModified = modifiedElementAttributes?.find(\r\n (x) =>\r\n x.doc === targetElement.ownerDocument &&\r\n x.element === targetElement &&\r\n x.attributeName === attribute.name\r\n );\r\n console.log(\"[ff-dom] checking attribute\",modifiedElementAttributes , { attribute, isModified });\r\n if (isModified) {\r\n return false;\r\n }\r\n\r\n if (attribute?.name?.indexOf(\"on\") === 0 && attribute?.name?.length > 3) {\r\n return false;\r\n }\r\n\r\n if (typeof attribute.value === \"function\") {\r\n return false;\r\n }\r\n\r\n if (isGeneratedTestIdAttribute(attribute.name, sanitizedValue)) {\r\n return false;\r\n }\r\n\r\n // Strict numeric-attribute validation: numeric attribute values are treated\r\n // as unstable before candidate creation. Class is handled token-by-token by\r\n // getClassTokenCondition, so only numeric class tokens are rejected there.\r\n if (attribute.name !== \"class\" && hasNumericAttributeValue(sanitizedValue)) {\r\n return false;\r\n }\r\n\r\n return true;\r\n};\r\n\r\nexport const getRelationship = (a: HTMLElement, b: HTMLElement): string => {\r\n let pos = a.compareDocumentPosition(b);\r\n return pos === 2\r\n ? \"preceding\"\r\n : pos === 4\r\n ? \"following\"\r\n : pos === 8\r\n ? \"ancestor\"\r\n : pos === 16\r\n ? \"descendant\"\r\n : pos === 32\r\n ? \"self\"\r\n : \"\";\r\n};\r\n\r\nexport const isSvg = (element: HTMLElement | Element) => {\r\n return element instanceof SVGElement;\r\n};\r\n\r\nexport const replaceTempAttributes = (str: string): string => {\r\n if (!str) return str;\r\n\r\n return str.replace(/\\b[a-zA-Z_]*disabled\\b/gi, \"disabled\");\r\n};\r\n\r\nconst getElementFromXpath = (xpath: string, docmt: Node, multi = false) => {\r\n let contextNode: Node = docmt;\r\n\r\n // XPath does NOT support DocumentFragment / ShadowRoot\r\n if (docmt.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {\r\n contextNode = (docmt as ShadowRoot).host ?? docmt;\r\n }\r\n\r\n const owner =\r\n contextNode.nodeType === Node.DOCUMENT_NODE\r\n ? (contextNode as Document)\r\n : contextNode.ownerDocument!;\r\n\r\n if (multi) {\r\n return owner.evaluate(xpath, contextNode, null, XPathResult.ANY_TYPE, null);\r\n }\r\n\r\n return owner.evaluate(\r\n xpath,\r\n contextNode,\r\n null,\r\n XPathResult.FIRST_ORDERED_NODE_TYPE,\r\n null\r\n ).singleNodeValue;\r\n};\r\n\r\nexport const getPropertyXPath = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n prop: string,\r\n value: string,\r\n isIndex: boolean,\r\n isTarget: boolean\r\n) => {\r\n if (value) {\r\n const { tagName } = element;\r\n let count;\r\n let combinePattern = \"\";\r\n const mergePattern = [];\r\n let pattern;\r\n\r\n const isAttributeProp = prop.startsWith(\"@\");\r\n const isTextProp = prop === \".\" || prop === \"text()\";\r\n const normalizedTextProp = isTextProp ? \".\" : prop;\r\n const hasTextSpacing = isTextProp && /\\s/.test(value.trim());\r\n const stableTargetText = isTextProp ? getStableTargetText(value) : value;\r\n const textValueForPredicate =\r\n isTextProp && hasNumericAttributeValue(value) ? stableTargetText : value;\r\n const containerTextCondition = isTextProp\r\n ? getContainerTextCondition(element, textValueForPredicate)\r\n : \"\";\r\n\r\n if (prop === \"@class\") {\r\n for (const classCondition of getClassTokenConditions(\r\n element,\r\n value,\r\n docmt\r\n )) {\r\n // Single-mode class generation tries every stable token and lets the\r\n // normal uniqueness check choose the first usable XPath.\r\n pattern = buildPattern(\r\n classCondition,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n\r\n count = getCountOfXPath(pattern, element, docmt);\r\n if (count === 1 && !isIndex) {\r\n return pattern;\r\n }\r\n }\r\n\r\n return;\r\n }\r\n\r\n if (isTextProp && hasNumericAttributeValue(value) && !stableTargetText) {\r\n // Target text with only dynamic numeric content should not produce a\r\n // text XPath. Other attribute/relative fallbacks can still run.\r\n return;\r\n }\r\n\r\n if (value && (!isAttributeProp || !isNumberExist(value))) {\r\n if (containerTextCondition) {\r\n pattern = buildPattern(\r\n containerTextCondition,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n } else if (hasTextSpacing) {\r\n pattern = buildPattern(\r\n `normalize-space(${normalizedTextProp})=${escapeCharacters(\r\n replaceWhiteSpaces(textValueForPredicate)\r\n ).trim()}`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n } else if (!reWhiteSpace.test(value)) {\r\n pattern = buildPattern(\r\n `normalize-space(${prop})=${escapeCharacters(\r\n replaceWhiteSpaces(textValueForPredicate)\r\n ).trim()}`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n } else {\r\n pattern = `//${tagName}[${prop}=${escapeCharacters(\r\n textValueForPredicate\r\n )}]`;\r\n }\r\n\r\n count = getCountOfXPath(pattern, element, docmt);\r\n\r\n if (count === 1 && !isIndex) {\r\n return pattern;\r\n }\r\n }\r\n\r\n if (isAttributeProp && hasNumericAttributeValue(value)) {\r\n // Strict numeric-attribute validation: do not split numeric attribute\r\n // values into starts-with/contains fallbacks.\r\n return;\r\n }\r\n\r\n if (value && isTarget) {\r\n const splitText = value.split(\" \");\r\n if (splitText?.length) {\r\n if (splitText.length === 1) {\r\n const contentRes = [...new Set(splitText[0].match(/([^0-9]+)/g))];\r\n if (contentRes?.length >= 1) {\r\n if (\r\n contentRes[0] &&\r\n replaceWhiteSpaces(contentRes[0].trim())?.length > 1\r\n ) {\r\n if (value.startsWith(contentRes[0])) {\r\n if (!reWhiteSpace.test(contentRes[0])) {\r\n combinePattern = `starts-with(${prop},${escapeCharacters(\r\n replaceWhiteSpaces(contentRes[0])\r\n ).trim()})`;\r\n } else {\r\n combinePattern = `starts-with(${prop},${escapeCharacters(\r\n contentRes[0]\r\n ).trim()})`;\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (contentRes?.length > 1) {\r\n if (\r\n contentRes[contentRes.length - 1] &&\r\n replaceWhiteSpaces(contentRes[contentRes.length - 1].trim())\r\n ?.length > 1\r\n ) {\r\n if (value.endsWith(contentRes[contentRes.length - 1])) {\r\n if (!reWhiteSpace.test(contentRes[contentRes.length - 1])) {\r\n combinePattern = `ends-with(${prop},${escapeCharacters(\r\n replaceWhiteSpaces(contentRes[contentRes.length - 1])\r\n ).trim()})`;\r\n } else {\r\n combinePattern = `ends-with(${prop},${escapeCharacters(\r\n contentRes[contentRes.length - 1]\r\n ).trim()})`;\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (combinePattern?.length) {\r\n if (isSvg(element)) {\r\n pattern = `//*[local-name()='${tagName}' and ${combinePattern}]`;\r\n } else {\r\n pattern = `//${tagName}[${combinePattern}]`;\r\n }\r\n count = getCountOfXPath(pattern, element, docmt);\r\n if (count === 1 && !isIndex) {\r\n return pattern;\r\n }\r\n }\r\n } else {\r\n const endIndex =\r\n splitText.length % 2 === 0\r\n ? splitText.length / 2\r\n : splitText.length % 2;\r\n const startIndexString = splitText.slice(0, endIndex).join(\" \");\r\n let contentRes = [...new Set(startIndexString.match(/([^0-9]+)/g))];\r\n if (contentRes?.length) {\r\n if (\r\n contentRes[0] &&\r\n replaceWhiteSpaces(contentRes[0].trim())?.length\r\n ) {\r\n if (value.startsWith(contentRes[0])) {\r\n if (!reWhiteSpace.test(contentRes[0])) {\r\n combinePattern = `starts-with(${prop},${escapeCharacters(\r\n replaceWhiteSpaces(contentRes[0])\r\n ).trim()})`;\r\n } else {\r\n combinePattern = `starts-with(${prop},${escapeCharacters(\r\n contentRes[0]\r\n ).trim()})`;\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (combinePattern?.length) {\r\n if (isSvg(element)) {\r\n pattern = `//*[local-name()='${tagName}' and ${combinePattern}]`;\r\n } else {\r\n pattern = `//${tagName}[${combinePattern}]`;\r\n }\r\n count = getCountOfXPath(pattern, element, docmt);\r\n if (count === 1 && !isIndex) {\r\n return pattern;\r\n }\r\n }\r\n\r\n const endIndexString = splitText\r\n .slice(endIndex, splitText.length - 1)\r\n .join(\" \");\r\n contentRes = [...new Set(endIndexString.match(/([^0-9]+)/g))];\r\n if (contentRes?.length) {\r\n if (\r\n contentRes[0] &&\r\n replaceWhiteSpaces(contentRes[0].trim())?.length > 3\r\n ) {\r\n if (value.endsWith(contentRes[0])) {\r\n if (!reWhiteSpace.test(contentRes[0])) {\r\n combinePattern = `ends-with(${prop},${escapeCharacters(\r\n replaceWhiteSpaces(contentRes[0])\r\n ).trim()})`;\r\n } else {\r\n combinePattern = `ends-with(${prop},${escapeCharacters(\r\n contentRes[0]\r\n ).trim()})`;\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (combinePattern?.length) {\r\n if (isSvg(element)) {\r\n pattern = `//*[local-name()='${tagName}' and ${combinePattern}]`;\r\n } else {\r\n pattern = `//${tagName}[${combinePattern}]`;\r\n }\r\n count = getCountOfXPath(pattern, element, docmt);\r\n if (count === 1 && !isIndex) {\r\n return pattern;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (value && isTarget && isNumberExist(value)) {\r\n const contentRes = [...new Set(value.match(/([^0-9]+)/g))];\r\n if (contentRes?.length) {\r\n for (let i = 0; i < contentRes?.length; i++) {\r\n if (\r\n contentRes[i] &&\r\n replaceWhiteSpaces(contentRes[i].trim())?.length > 1\r\n ) {\r\n if (!reWhiteSpace.test(contentRes[i])) {\r\n mergePattern.push(\r\n `contains(${prop},${escapeCharacters(\r\n replaceWhiteSpaces(contentRes[i])\r\n ).trim()})`\r\n );\r\n } else {\r\n mergePattern.push(\r\n `contains(${prop},${escapeCharacters(\r\n contentRes[i].trim()\r\n ).trim()})`\r\n );\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (mergePattern?.length) {\r\n if (isSvg(element)) {\r\n pattern = `//*[local-name()='${tagName}' and ${mergePattern.join(\r\n \" and \"\r\n )}]`;\r\n } else {\r\n pattern = `//${tagName}[${mergePattern.join(\" and \")}]`;\r\n }\r\n count = getCountOfXPath(pattern, element, docmt);\r\n if (count === 1 && !isIndex) {\r\n return pattern;\r\n }\r\n }\r\n }\r\n\r\n if (isSvg(element)) {\r\n pattern = `//*[local-name()='${tagName}' and text()]`;\r\n } else {\r\n pattern = `//${tagName}[text()]`;\r\n }\r\n\r\n count = getCountOfXPath(pattern, element, docmt);\r\n if (count === 1 && !isIndex) {\r\n return pattern;\r\n }\r\n }\r\n};\r\n\r\nexport const getAbsoluteXPath = (\r\n domNode: HTMLElement | Element | null,\r\n docmt: Document\r\n): string => {\r\n try {\r\n if (!domNode) {\r\n return \"\";\r\n }\r\n\r\n let xpathe = isSvg(domNode)\r\n ? `/*[local-name()='${domNode.tagName}']`\r\n : `/${domNode.tagName}`;\r\n\r\n // // If this node has siblings of the same tagName, get the index of this node\r\n if (domNode.parentElement) {\r\n // Get the siblings\r\n const childNodes = Array.prototype.slice\r\n .call(domNode.parentElement.children, 0)\r\n .filter(\r\n (childNode: HTMLElement) => childNode.tagName === domNode.tagName\r\n );\r\n\r\n // // If there's more than one sibling, append the index\r\n if (childNodes.length > 1) {\r\n const index = childNodes.indexOf(domNode);\r\n xpathe += `[${index + 1}]`;\r\n }\r\n } else if (domNode instanceof HTMLElement) {\r\n if (domNode.offsetParent) {\r\n const childNodes = Array.prototype.slice\r\n .call(domNode.offsetParent.children, 0)\r\n .filter(\r\n (childNode: HTMLElement) => childNode.tagName === domNode.tagName\r\n );\r\n\r\n // // If there's more than one sibling, append the index\r\n if (childNodes.length > 1) {\r\n const index = childNodes.indexOf(domNode);\r\n xpathe += `[${index + 1}]`;\r\n }\r\n }\r\n }\r\n\r\n // // Make a recursive call to this nodes parents and prepend it to this xpath\r\n return getAbsoluteXPath(domNode?.parentElement, docmt) + xpathe;\r\n } catch (error) {\r\n // If there's an unexpected exception, abort and don't get an XPath\r\n console.log(\"xpath\", error);\r\n\r\n return \"\";\r\n }\r\n};\r\n\r\nexport const getRelativeXPath = (\r\n domNode: HTMLElement | Element,\r\n docmt: Document,\r\n isIndex: boolean,\r\n isTarget: boolean = false,\r\n attributesArray: Attr[]\r\n) => {\r\n try {\r\n // Generate a cache key based on the node's identifier, index, and target flag\r\n // Check if the result for this node is already cached\r\n if (relativeXPathCache.has(domNode)) {\r\n return relativeXPathCache.get(domNode);\r\n }\r\n\r\n // Initialize an array to hold parts of the XPath\r\n const xpathParts = [];\r\n let currentNode = domNode;\r\n\r\n // Traverse up the DOM tree iteratively instead of using recursion\r\n while (currentNode) {\r\n let xpathe: string | undefined = \"\";\r\n let hasUniqueAttr = false;\r\n let attributes =\r\n domNode === currentNode\r\n ? (attributesArray ?? currentNode.attributes)\r\n : currentNode.attributes;\r\n\r\n // Loop through attributes to check for unique identifiers\r\n for (const attrName of Array.from(attributes)) {\r\n if (checkBlockedAttributes(attrName, currentNode, isTarget)) {\r\n const attrValue = sanitizeAttributeValue(\r\n attrName.name,\r\n attrName.nodeValue\r\n );\r\n\r\n const elementName = attrName.name;\r\n\r\n // Class can produce multiple stable token candidates; try each\r\n // before moving to text/index fallbacks.\r\n for (const xpathCandidate of getXpathStrings(\r\n currentNode,\r\n elementName,\r\n attrValue\r\n )) {\r\n xpathe = xpathCandidate;\r\n let othersWithAttr: number = 0;\r\n othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);\r\n if (othersWithAttr === 1) {\r\n xpathParts.unshift(replaceTempAttributes(xpathe));\r\n hasUniqueAttr = true;\r\n break;\r\n }\r\n\r\n if (othersWithAttr > 1 && isIndex) {\r\n xpathe = findXpathWithIndex(\r\n xpathe,\r\n currentNode,\r\n docmt,\r\n othersWithAttr\r\n );\r\n if (xpathe) {\r\n xpathParts.unshift(replaceTempAttributes(xpathe));\r\n hasUniqueAttr = true;\r\n break;\r\n }\r\n // return replaceTempAttributes(xpathe);\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (currentNode.textContent) {\r\n if (\r\n !isTarget ||\r\n (isTarget && !isNumberExist(currentNode.textContent))\r\n ) {\r\n let reWhiteSpace = new RegExp(/^[\\S]+( [\\S]+)*$/gi);\r\n const filteredText = getFilteredText(currentNode);\r\n const containerTextCondition = getContainerTextCondition(\r\n currentNode,\r\n filteredText\r\n );\r\n\r\n if (containerTextCondition) {\r\n xpathe = buildPattern(\r\n containerTextCondition,\r\n isSvg(currentNode),\r\n currentNode.tagName || \"*\"\r\n );\r\n } else if (!reWhiteSpace.test(currentNode.textContent)) {\r\n xpathe = isSvg(currentNode)\r\n ? `//*[local-name()='${\r\n currentNode.tagName\r\n }' and normalize-space(.)=${escapeCharacters(\r\n filteredText\r\n )}]`\r\n : `//${\r\n currentNode.tagName || \"*\"\r\n }[normalize-space(.)=${escapeCharacters(\r\n filteredText\r\n )}]`;\r\n } else {\r\n xpathe = isSvg(currentNode)\r\n ? `//*[local-name()='${\r\n currentNode.tagName\r\n }' and .=${escapeCharacters(filteredText)}]`\r\n : `//${currentNode.tagName || \"*\"}[.=${escapeCharacters(\r\n filteredText\r\n )}]`;\r\n }\r\n\r\n let othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);\r\n if (othersWithAttr === 1) {\r\n return xpathe;\r\n }\r\n\r\n if (othersWithAttr > 1 && isIndex) {\r\n xpathe = findXpathWithIndex(\r\n xpathe,\r\n currentNode,\r\n docmt,\r\n othersWithAttr\r\n );\r\n return xpathe;\r\n }\r\n } else {\r\n let combinePattern: string[] = [];\r\n const contentRes = [\r\n ...new Set(getFilteredText(currentNode).match(/([^0-9]+)/g))\r\n ];\r\n let reWhiteSpace = new RegExp(/^[\\S]+( [\\S]+)*$/gi);\r\n if (contentRes?.length) {\r\n for (let i = 0; i < contentRes?.length; i++) {\r\n if (\r\n contentRes[i] &&\r\n replaceWhiteSpaces((contentRes[i] as string).trim())\r\n ) {\r\n if (!reWhiteSpace.test(contentRes[i] as string)) {\r\n combinePattern.push(\r\n `contains(.,${escapeCharacters(\r\n replaceWhiteSpaces(contentRes[i])\r\n ).trim()})`\r\n );\r\n } else {\r\n combinePattern.push(\r\n `contains(.,${escapeCharacters(\r\n (contentRes[i] as string).trim()\r\n ).trim()})`\r\n );\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (combinePattern?.length) {\r\n xpathe = isSvg(currentNode)\r\n ? `//*[local-name()='${\r\n currentNode.tagName\r\n }' and ${combinePattern.join(\" and \")}]`\r\n : `//${currentNode.tagName || \"*\"}[${combinePattern.join(\r\n \" and \"\r\n )}]`;\r\n let othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);\r\n if (othersWithAttr === 1) {\r\n return xpathe;\r\n }\r\n\r\n if (othersWithAttr > 1 && isIndex) {\r\n xpathe = findXpathWithIndex(\r\n xpathe,\r\n currentNode,\r\n docmt,\r\n othersWithAttr\r\n );\r\n return xpathe;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // If no unique attribute was found, construct XPath by tag name\r\n if (!hasUniqueAttr) {\r\n let tagBasedXPath = isSvg(currentNode)\r\n ? `/*[local-name()='${currentNode.tagName}']`\r\n : `/${currentNode.tagName}`;\r\n\r\n // Handle sibling nodes\r\n if (currentNode.parentElement) {\r\n const siblings = Array.from(\r\n currentNode.parentElement.children\r\n ).filter((childNode) => childNode.tagName === currentNode.tagName);\r\n\r\n // Append index to distinguish between siblings\r\n if (siblings.length > 1) {\r\n const index = siblings.indexOf(currentNode);\r\n tagBasedXPath += `[${index + 1}]`;\r\n }\r\n }\r\n\r\n // Add the constructed tag-based XPath to the parts array\r\n xpathParts.unshift(tagBasedXPath);\r\n } else {\r\n break;\r\n }\r\n\r\n // Move up to the parent node for the next iteration\r\n currentNode = currentNode.parentElement!;\r\n }\r\n\r\n // Combine all parts into the final XPath\r\n const finalXPath = `${xpathParts.join(\"\")}`;\r\n\r\n // Cache the final XPath for this node\r\n relativeXPathCache.set(domNode, finalXPath);\r\n return finalXPath;\r\n } catch (error) {\r\n console.log(error);\r\n return null;\r\n }\r\n};\r\n\r\nexport const getCombinationXpath = (\r\n attribute: Attr,\r\n domNode: HTMLElement | Element\r\n) => {\r\n const combinePattern = [];\r\n let pattern: string = \"\";\r\n\r\n if (\r\n attribute &&\r\n typeof attribute.nodeValue !== \"function\" // &&\r\n // !modifiedElementAttributes?.find(\r\n // (x) => x.element === domNode && x.attributeName === attribute.name\r\n // )\r\n ) {\r\n if (attribute.name === \"class\") {\r\n const docmt =\r\n (domNode.getRootNode?.() as Document | ShadowRoot | null) ??\r\n domNode.ownerDocument;\r\n const classConditions = getClassTokenConditions(\r\n domNode,\r\n attribute.value,\r\n docmt\r\n );\r\n\r\n // Class values are handled token-by-token so numeric tokens are blocked\r\n // without throwing away the other stable class tokens.\r\n for (const classCondition of classConditions) {\r\n pattern = isSvg(domNode)\r\n ? `//*[local-name()='${domNode.tagName}' and ${classCondition}]`\r\n : `//${domNode.tagName}[${classCondition}]`;\r\n\r\n if (pattern) {\r\n return pattern;\r\n }\r\n }\r\n\r\n return;\r\n }\r\n\r\n if (isNumberExist(attribute.value)) {\r\n return;\r\n }\r\n\r\n const contentRes = [...new Set(attribute.value.match(/([^0-9]+)/g))];\r\n if (contentRes?.length) {\r\n for (let i = 0; i < contentRes?.length; i++) {\r\n if (\r\n contentRes[i] &&\r\n replaceWhiteSpaces(contentRes[i].trim())?.length > 2\r\n ) {\r\n if (!reWhiteSpace.test(contentRes[i])) {\r\n combinePattern.push(\r\n `contains(@${attribute.name},${escapeCharacters(\r\n replaceWhiteSpaces(contentRes[i])\r\n ).trim()})`\r\n );\r\n } else {\r\n combinePattern.push(\r\n `contains(@${attribute.name},${escapeCharacters(\r\n contentRes[i].trim()\r\n )})`\r\n );\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (combinePattern?.length) {\r\n pattern = isSvg(domNode)\r\n ? `//*[local-name()='${domNode.tagName}' and ${combinePattern.join(\r\n \" and \"\r\n )}]`\r\n : `//${domNode.tagName}[${combinePattern.join(\" and \")}]`;\r\n return pattern;\r\n }\r\n }\r\n};\r\n\r\nexport const getAttributeCombinationXpath = (\r\n domNode: HTMLElement | Element,\r\n docmt: Document | ShadowRoot,\r\n uniqueAttributes: Attr[],\r\n isTarget: boolean\r\n): string | undefined => {\r\n try {\r\n const candidateAttributes = uniqueAttributes.filter((attr) =>\r\n checkBlockedAttributes(attr, domNode, isTarget)\r\n );\r\n\r\n const buildAttributeConditions = (attribute: Attr): string[] => {\r\n const attrValue = sanitizeAttributeValue(\r\n attribute.name,\r\n attribute.nodeValue\r\n );\r\n\r\n if (!attrValue) return [];\r\n\r\n if (attribute.name === \"class\") {\r\n return getClassTokenConditions(\r\n domNode,\r\n attrValue,\r\n docmt\r\n );\r\n }\r\n\r\n // Strict numeric-attribute validation for direct callers.\r\n if (hasNumericAttributeValue(attrValue)) return [];\r\n\r\n if (!reWhiteSpace.test(attrValue)) {\r\n return [`normalize-space(@${attribute.name})=\"${attrValue}\"`];\r\n }\r\n\r\n return [`@${attribute.name}=\"${attrValue}\"`];\r\n };\r\n\r\n const buildAttributeConditionAlternatives = (\r\n attribute: Attr\r\n ): string[][] => {\r\n const conditions = buildAttributeConditions(attribute);\r\n\r\n if (attribute.name === \"class\") {\r\n // Class tokens are alternatives; chaining every stable class token can\r\n // over-constrain generated combination XPath.\r\n return conditions.map((condition) => [condition]);\r\n }\r\n\r\n return conditions.length ? [conditions] : [];\r\n };\r\n\r\n const buildConditionGroups = (attributes: Attr[]): string[][] => {\r\n // Expand attribute alternatives into condition groups before uniqueness\r\n // checks, especially for class-token combinations.\r\n return attributes.reduce<string[][]>((groups, attribute) => {\r\n const alternatives = buildAttributeConditionAlternatives(attribute);\r\n\r\n if (!alternatives.length) return [];\r\n\r\n return groups.flatMap((group) =>\r\n alternatives.map((alternative) => [...group, ...alternative])\r\n );\r\n }, [[]]);\r\n };\r\n\r\n const getTextConditions = (): string[] => {\r\n const rawText = replaceWhiteSpaces(getTextContent(domNode)?.trim() || \"\");\r\n const stableTargetText = getStableTargetText(rawText);\r\n if (hasNumericAttributeValue(rawText)) {\r\n return stableTargetText\r\n ? [\r\n `contains(normalize-space(.),${escapeCharacters(\r\n stableTargetText\r\n )})`\r\n ]\r\n : [];\r\n }\r\n\r\n if (!rawText || rawText.length > 80 || /^\\d+$/.test(rawText)) {\r\n const containerTextCondition = getContainerTextCondition(\r\n domNode,\r\n rawText\r\n );\r\n return containerTextCondition ? [containerTextCondition] : [];\r\n }\r\n\r\n const conditions = new Set<string>();\r\n const containerTextCondition = getContainerTextCondition(\r\n domNode,\r\n rawText\r\n );\r\n\r\n if (containerTextCondition) {\r\n conditions.add(containerTextCondition);\r\n return Array.from(conditions);\r\n }\r\n\r\n if (reWhiteSpace.test(rawText) && rawText.length <= 40 && !rawText.includes(\" \")) {\r\n conditions.add(`text()=${escapeCharacters(rawText)}`);\r\n }\r\n\r\n if (rawText.includes(\" \")) {\r\n conditions.add(\r\n `normalize-space(.)=${escapeCharacters(replaceWhiteSpaces(rawText))}`\r\n );\r\n }\r\n\r\n conditions.add(`contains(text(),${escapeCharacters(rawText)})`);\r\n\r\n return Array.from(conditions);\r\n };\r\n\r\n const generateAttributeCombinations = (\r\n attributes: Attr[],\r\n combinationSize: number\r\n ): Attr[][] => {\r\n const results: Attr[][] = [];\r\n\r\n const build = (startIndex: number, currentGroup: Attr[]) => {\r\n if (currentGroup.length === combinationSize) {\r\n results.push([...currentGroup]);\r\n return;\r\n }\r\n\r\n for (let i = startIndex; i < attributes.length; i++) {\r\n currentGroup.push(attributes[i]);\r\n build(i + 1, currentGroup);\r\n currentGroup.pop();\r\n }\r\n };\r\n\r\n build(0, []);\r\n return results;\r\n };\r\n\r\n const buildXPath = (conditions: string[]) =>\r\n isSvg(domNode)\r\n ? `//*[local-name()='${domNode.tagName}' and ${conditions.join(\" and \")}]`\r\n : `//${domNode.tagName}[${conditions.join(\" and \")}]`;\r\n\r\n const tryAttributeOnlyCombinations = () => {\r\n if (candidateAttributes.length < 2) {\r\n return undefined;\r\n }\r\n\r\n for (\r\n let combinationSize = 2;\r\n combinationSize <= candidateAttributes.length;\r\n combinationSize++\r\n ) {\r\n const attributeGroups = generateAttributeCombinations(\r\n candidateAttributes,\r\n combinationSize\r\n );\r\n\r\n for (const attributeGroup of attributeGroups) {\r\n const conditionGroups = buildConditionGroups(attributeGroup);\r\n\r\n for (const xpathConditions of conditionGroups) {\r\n if (xpathConditions.length < 2) continue;\r\n\r\n const xpath = buildXPath(xpathConditions);\r\n\r\n let matchCount: number;\r\n\r\n try {\r\n matchCount = getCountOfXPath(xpath, domNode, docmt);\r\n } catch {\r\n continue;\r\n }\r\n\r\n if (matchCount === 1) {\r\n return xpath;\r\n }\r\n }\r\n }\r\n }\r\n\r\n return undefined;\r\n };\r\n\r\n const attributeOnlyXpath = tryAttributeOnlyCombinations();\r\n if (attributeOnlyXpath) {\r\n return attributeOnlyXpath;\r\n }\r\n\r\n const textConditions = getTextConditions();\r\n if (!textConditions.length || !candidateAttributes.length) {\r\n return;\r\n }\r\n\r\n for (\r\n let combinationSize = 1;\r\n combinationSize <= candidateAttributes.length;\r\n combinationSize++\r\n ) {\r\n const attributeGroups = generateAttributeCombinations(\r\n candidateAttributes,\r\n combinationSize\r\n );\r\n\r\n for (const attributeGroup of attributeGroups) {\r\n const conditionGroups = buildConditionGroups(attributeGroup);\r\n\r\n for (const attributeConditions of conditionGroups) {\r\n if (!attributeConditions.length) {\r\n continue;\r\n }\r\n\r\n for (const textCondition of textConditions) {\r\n const xpathConditions = [...attributeConditions, textCondition];\r\n const xpath = buildXPath(xpathConditions);\r\n\r\n try {\r\n if (getCountOfXPath(xpath, domNode, docmt) === 1) {\r\n return xpath;\r\n }\r\n } catch {\r\n continue;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n } catch (error) {\r\n console.log(`XPath generation error: ${JSON.stringify(error, null, 2)}`);\r\n }\r\n};\r\n\r\nexport const intermediateXpathStep = (\r\n targetElemt: HTMLElement | Element,\r\n attr: { name: string; value: string },\r\n isTarget: boolean\r\n): string => {\r\n return intermediateXpathSteps(targetElemt, attr, isTarget)[0] || \"\";\r\n};\r\n\r\nexport const intermediateXpathSteps = (\r\n targetElemt: HTMLElement | Element,\r\n attr: { name: string; value: string },\r\n isTarget: boolean\r\n): string[] => {\r\n let isSvgElement = isSvg(targetElemt);\r\n let expression: string = \"\";\r\n\r\n if (checkBlockedAttributes(attr, targetElemt, isTarget)) {\r\n let attrValue = sanitizeAttributeValue(attr.name, attr.value);\r\n const elementName = attr.name;\r\n\r\n if (elementName === \"class\") {\r\n const docmt =\r\n (targetElemt.getRootNode?.() as Document | ShadowRoot | null) ??\r\n targetElemt.ownerDocument;\r\n\r\n // Relative child/parent steps follow the same class-token rule as direct\r\n // XPath candidates: try every stable token, never numeric tokens.\r\n return getClassTokenConditions(targetElemt, attrValue, docmt).map(\r\n (classCondition) =>\r\n isSvgElement\r\n ? `*[local-name()='${targetElemt.tagName}' and ${classCondition}]`\r\n : `${targetElemt.tagName || \"*\"}[${classCondition}]`\r\n );\r\n }\r\n\r\n // Strict numeric-attribute validation for direct callers.\r\n if (hasNumericAttributeValue(attrValue)) {\r\n return [];\r\n }\r\n\r\n if (!reWhiteSpace.test(attrValue)) {\r\n expression = isSvgElement\r\n ? `*[local-name()='${\r\n targetElemt.tagName\r\n }' and normalize-space(@${elementName})=${escapeCharacters(\r\n attrValue\r\n )}]`\r\n : `${\r\n targetElemt.tagName || \"*\"\r\n }[normalize-space(@${elementName})=${escapeCharacters(attrValue)}]`;\r\n } else {\r\n expression = isSvgElement\r\n ? `*[local-name()='${\r\n targetElemt.tagName\r\n }' and @${elementName}=${escapeCharacters(attrValue)}]`\r\n : `${targetElemt.tagName || \"*\"}[@${elementName}=${escapeCharacters(\r\n attrValue\r\n )}]`;\r\n }\r\n }\r\n\r\n return expression ? [expression] : [];\r\n};\r\n\r\nexport const getFilteredTextXPath = (\r\n node: HTMLElement | Element,\r\n docmt: Document | ShadowRoot\r\n): string => {\r\n if (!node.textContent) return \"\";\r\n\r\n const filteredText = getFilteredText(node);\r\n const stableTargetText = getStableTargetText(filteredText);\r\n if (hasNumericAttributeValue(filteredText) && !stableTargetText) {\r\n // Do not build text XPath from purely numeric/dynamic target text.\r\n return \"\";\r\n }\r\n\r\n const textForPredicate =\r\n hasNumericAttributeValue(filteredText) ? stableTargetText : filteredText;\r\n const containerTextCondition = getContainerTextCondition(\r\n node,\r\n textForPredicate\r\n );\r\n\r\n let xpathe;\r\n\r\n if (containerTextCondition) {\r\n xpathe = buildPattern(\r\n containerTextCondition,\r\n isSvg(node),\r\n node.tagName || \"*\"\r\n );\r\n } else if (hasNumericAttributeValue(filteredText)) {\r\n xpathe = buildPattern(\r\n `contains(normalize-space(.),${escapeCharacters(textForPredicate)})`,\r\n isSvg(node),\r\n node.tagName || \"*\"\r\n );\r\n } else if (!reWhiteSpace.test(filteredText) || /\\s/.test(filteredText.trim())) {\r\n xpathe = buildPattern(\r\n `normalize-space(.)=${escapeCharacters(textForPredicate)}`,\r\n isSvg(node),\r\n node.tagName || \"*\"\r\n );\r\n } else {\r\n xpathe = isSvg(node)\r\n ? `//*[local-name()='${node.tagName}' and .=${escapeCharacters(\r\n textForPredicate\r\n )}]`\r\n : `//${node.tagName || \"*\"}[.=${escapeCharacters(textForPredicate)}]`;\r\n }\r\n\r\n return xpathe;\r\n};\r\n\r\nexport const getNormalizedPropertyXPath = (\r\n element: HTMLElement | Element,\r\n prop: string,\r\n value: string\r\n): string => {\r\n if (prop === \"@class\") {\r\n const classTokens = getStableClassTokens(element, value);\r\n\r\n if (normalizeClassTokens(value).length === 1 && classTokens.length === 1) {\r\n return buildPattern(\r\n `normalize-space(@class)=${escapeCharacters(classTokens[0])}`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n }\r\n\r\n return \"\";\r\n }\r\n\r\n if (prop.startsWith(\"@\") && hasNumericAttributeValue(value)) return \"\";\r\n\r\n if ((prop === \".\" || prop === \"text()\") && hasNumericAttributeValue(value)) {\r\n const { fragment } = getStableTargetTextCandidates(value);\r\n\r\n return fragment\r\n ? buildPattern(\r\n `contains(normalize-space(.),${escapeCharacters(fragment)})`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n )\r\n : \"\";\r\n }\r\n\r\n return buildPattern(\r\n `normalize-space(${prop})=${escapeCharacters(replaceWhiteSpaces(value)).trim()}`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n};\r\n\r\nexport const getStartsWithPropertyXPath = (\r\n element: HTMLElement | Element,\r\n prop: string,\r\n value: string\r\n): string => {\r\n if (prop === \"@class\") {\r\n const classTokens = getStableClassTokens(element, value);\r\n\r\n if (classTokens.length === 1) {\r\n return buildPattern(\r\n `starts-with(normalize-space(@class),${escapeCharacters(classTokens[0])})`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n }\r\n\r\n return \"\";\r\n }\r\n\r\n if (prop.startsWith(\"@\") && hasNumericAttributeValue(value)) return \"\";\r\n\r\n if ((prop === \".\" || prop === \"text()\") && hasNumericAttributeValue(value)) {\r\n const { prefix, fragment } = getStableTargetTextCandidates(value);\r\n const stableText = prefix || fragment;\r\n\r\n return stableText\r\n ? buildPattern(\r\n `starts-with(normalize-space(.),${escapeCharacters(stableText)})`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n )\r\n : \"\";\r\n }\r\n\r\n return buildPattern(\r\n `starts-with(${prop},${escapeCharacters(replaceWhiteSpaces(value)).trim()})`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n};\r\n\r\nexport const getContainsPropertyXPath = (\r\n element: HTMLElement | Element,\r\n prop: string,\r\n value: string\r\n): string => {\r\n if (prop === \"@class\") {\r\n const classTokens = getStableClassTokens(element, value);\r\n\r\n if (classTokens.length === 1) {\r\n return buildPattern(\r\n `contains(@class,${escapeCharacters(classTokens[0])})`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n }\r\n\r\n return \"\";\r\n }\r\n\r\n if (prop.startsWith(\"@\") && hasNumericAttributeValue(value)) return \"\";\r\n\r\n if ((prop === \".\" || prop === \"text()\") && hasNumericAttributeValue(value)) {\r\n const { fragment } = getStableTargetTextCandidates(value);\r\n\r\n return fragment\r\n ? buildPattern(\r\n `contains(normalize-space(.),${escapeCharacters(fragment)})`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n )\r\n : \"\";\r\n }\r\n\r\n return buildPattern(\r\n `contains(${prop},${escapeCharacters(replaceWhiteSpaces(value)).trim()})`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n};\r\n\r\nexport const getOrAttributesXPath = (\r\n element: HTMLElement | Element,\r\n attributes: Attr[]\r\n): string => {\r\n const buildAttributeConditions = (attribute: Attr): string[] => {\r\n const attrValue = sanitizeAttributeValue(attribute.name, attribute.value);\r\n\r\n if (!attrValue) {\r\n return [];\r\n }\r\n\r\n if (attribute.name === \"class\") {\r\n const docmt =\r\n (element.getRootNode?.() as Document | ShadowRoot | null) ??\r\n element.ownerDocument;\r\n\r\n return getClassTokenConditions(element, attrValue, docmt);\r\n }\r\n\r\n // Strict numeric-attribute validation for direct callers.\r\n if (hasNumericAttributeValue(attrValue)) return [];\r\n\r\n if (!reWhiteSpace.test(attrValue)) {\r\n return [\r\n `normalize-space(@${attribute.name})=${escapeCharacters(attrValue)}`\r\n ];\r\n }\r\n\r\n return [`@${attribute.name}=${escapeCharacters(attrValue)}`];\r\n };\r\n\r\n const buildTextCondition = (): string | null => {\r\n const rawText = replaceWhiteSpaces(getTextContent(element)?.trim() || \"\");\r\n const stableTargetText = getStableTargetText(rawText);\r\n if (hasNumericAttributeValue(rawText)) {\r\n // OR conditions must not reintroduce full dynamic numeric target text.\r\n return stableTargetText\r\n ? `contains(normalize-space(.),${escapeCharacters(stableTargetText)})`\r\n : null;\r\n }\r\n\r\n const containerTextCondition = getContainerTextCondition(element, rawText);\r\n\r\n if (containerTextCondition) {\r\n return containerTextCondition;\r\n }\r\n\r\n if (!rawText || rawText.length > 80 || /^\\d+$/.test(rawText)) {\r\n return null;\r\n }\r\n\r\n if (reWhiteSpace.test(rawText) && rawText.length <= 40 && !rawText.includes(\" \")) {\r\n return `text()=${escapeCharacters(rawText)}`;\r\n }\r\n\r\n if (rawText.includes(\" \")) {\r\n return `normalize-space(.)=${escapeCharacters(rawText)}`;\r\n }\r\n\r\n return `contains(text(),${escapeCharacters(rawText)})`;\r\n };\r\n\r\n const attributeConditions = attributes\r\n .flatMap((attribute) => buildAttributeConditions(attribute))\r\n .filter(Boolean);\r\n\r\n if (attributeConditions.length >= 2) {\r\n return buildPattern(\r\n `${attributeConditions[0]} or ${attributeConditions[1]}`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n }\r\n\r\n const textCondition = buildTextCondition();\r\n if (attributeConditions.length === 1 && textCondition) {\r\n return buildPattern(\r\n `${attributeConditions[0]} or ${textCondition}`,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n }\r\n\r\n return \"\";\r\n};\r\n\r\nconst getTagOnlyXpathCandidateCount = (\r\n xpath: string,\r\n element: HTMLElement | Element,\r\n docmt: Document | ShadowRoot\r\n) => {\r\n try {\r\n return getCountOfXPath(xpath, element, docmt);\r\n } catch (_error) {\r\n return 0;\r\n }\r\n};\r\n\r\nconst getAncestorAnchorCandidates = (\r\n node: HTMLElement | Element,\r\n docmt: Document | ShadowRoot\r\n): string[] => {\r\n const anchors: string[] = [];\r\n const seen = new Set<string>();\r\n const attributes = Array.from(node.attributes || []);\r\n const priorityAttrs = [\r\n \"id\",\r\n \"data-testid\",\r\n \"data-test\",\r\n \"data-qa\",\r\n \"name\",\r\n \"formcontrolname\",\r\n \"aria-label\",\r\n \"role\"\r\n ];\r\n const orderedAttributes = [\r\n ...priorityAttrs\r\n .map((attrName) => attributes.find((attr) => attr.name === attrName))\r\n .filter(Boolean),\r\n ...attributes.filter((attr) => !priorityAttrs.includes(attr.name))\r\n ] as Attr[];\r\n\r\n const pushAnchor = (xpath: string) => {\r\n if (!xpath || seen.has(xpath)) return;\r\n if (isExactUniqueXpath(xpath, node, docmt)) {\r\n seen.add(xpath);\r\n anchors.push(xpath);\r\n }\r\n };\r\n\r\n orderedAttributes.forEach((attr) => {\r\n if (!checkBlockedAttributes(attr, node, false)) return;\r\n\r\n for (const xpath of getXpathStrings(node, attr.name, attr.value)) {\r\n pushAnchor(xpath);\r\n }\r\n });\r\n\r\n const text = node.textContent?.trim();\r\n if (text && text.length < 40 && node.children.length === 0) {\r\n const textXpath = getFilteredTextXPath(node, docmt);\r\n if (textXpath) {\r\n pushAnchor(textXpath);\r\n }\r\n }\r\n\r\n if (attributes.length > 1) {\r\n const combinationXpath = getAttributeCombinationXpath(\r\n node,\r\n docmt,\r\n attributes,\r\n false\r\n );\r\n if (combinationXpath) {\r\n pushAnchor(combinationXpath);\r\n }\r\n }\r\n\r\n return anchors;\r\n};\r\n\r\nconst getStructuralPathFromAncestor = (\r\n ancestor: HTMLElement | Element,\r\n element: HTMLElement | Element\r\n): string => {\r\n const steps: string[] = [];\r\n let current: Element | null = element;\r\n\r\n while (current && current !== ancestor) {\r\n steps.unshift(getAxisNodeTest(current));\r\n current = current.parentElement;\r\n }\r\n\r\n return current === ancestor ? steps.join(\"/\") : \"\";\r\n};\r\n\r\nexport const getTagOnlyXPath = (\r\n element: HTMLElement | Element,\r\n docmt?: Document | ShadowRoot\r\n): string => {\r\n const root =\r\n docmt ??\r\n ((element.getRootNode?.() as Document | ShadowRoot) ||\r\n element.ownerDocument);\r\n const tagName = getAxisNodeTest(element);\r\n const fallbackXpath = isSvg(element)\r\n ? `//*[local-name()='${element.tagName.toLowerCase()}']`\r\n : `//${element.tagName.toLowerCase()}`;\r\n\r\n for (\r\n let ancestor = element.parentElement;\r\n ancestor;\r\n ancestor = ancestor.parentElement\r\n ) {\r\n const ancestorAnchors = getAncestorAnchorCandidates(ancestor, root);\r\n\r\n for (const ancestorXpath of ancestorAnchors) {\r\n console.log(`Trying ancestor XPath: ${ancestorXpath}`);\r\n\r\n const descendantXpath = `${ancestorXpath}/descendant::${tagName}`;\r\n console.log(`Trying descendant XPath: ${descendantXpath}`);\r\n\r\n const count = getTagOnlyXpathCandidateCount(\r\n descendantXpath,\r\n element,\r\n root\r\n );\r\n console.log(`Match count: ${count}`);\r\n\r\n if (\r\n count === 1 &&\r\n getFirstMatchedNode(descendantXpath, root) === element\r\n ) {\r\n console.log(`Selected XPath: ${descendantXpath}`);\r\n return descendantXpath;\r\n }\r\n\r\n const structuralPath = getStructuralPathFromAncestor(ancestor, element);\r\n if (structuralPath) {\r\n const parentChainXpath = `${ancestorXpath}/${structuralPath}`;\r\n console.log(`Trying descendant XPath: ${parentChainXpath}`);\r\n\r\n const parentChainCount = getTagOnlyXpathCandidateCount(\r\n parentChainXpath,\r\n element,\r\n root\r\n );\r\n console.log(`Match count: ${parentChainCount}`);\r\n\r\n if (\r\n parentChainCount === 1 &&\r\n getFirstMatchedNode(parentChainXpath, root) === element\r\n ) {\r\n console.log(`Selected XPath: ${parentChainXpath}`);\r\n return parentChainXpath;\r\n }\r\n }\r\n }\r\n }\r\n\r\n console.log(`Trying descendant XPath: ${fallbackXpath}`);\r\n const fallbackCount = getTagOnlyXpathCandidateCount(\r\n fallbackXpath,\r\n element,\r\n root\r\n );\r\n console.log(`Match count: ${fallbackCount}`);\r\n\r\n if (\r\n fallbackCount === 1 &&\r\n getFirstMatchedNode(fallbackXpath, root) === element\r\n ) {\r\n console.log(`Selected XPath: ${fallbackXpath}`);\r\n return fallbackXpath;\r\n }\r\n\r\n console.log(\"Selected XPath: \");\r\n return \"\";\r\n};\r\n\r\nexport const getFirstMatchedNode = (\r\n xpath: string,\r\n docmt: Document | ShadowRoot\r\n): HTMLElement | Element | null => {\r\n try {\r\n if (isShadowRootNode(docmt)) {\r\n const { owner, contextNode, cloneElement } =\r\n createShadowEvaluationContext(docmt);\r\n const result = owner.evaluate(\r\n xpath,\r\n contextNode,\r\n null,\r\n XPathResult.FIRST_ORDERED_NODE_TYPE,\r\n null\r\n );\r\n\r\n return (result.singleNodeValue || cloneElement) as\r\n | HTMLElement\r\n | Element\r\n | null;\r\n }\r\n\r\n const result = docmt.evaluate(\r\n xpath,\r\n docmt,\r\n null,\r\n docmt.defaultView!.XPathResult.FIRST_ORDERED_NODE_TYPE,\r\n null\r\n );\r\n\r\n return result.singleNodeValue as HTMLElement | Element | null;\r\n } catch (_error) {\r\n return null;\r\n }\r\n};\r\n\r\nexport const isExactUniqueXpath = (\r\n xpath: string,\r\n element: HTMLElement | Element,\r\n docmt: Document | ShadowRoot\r\n): boolean => {\r\n try {\r\n const { first, second } = evaluateXPathOnce(xpath, docmt);\r\n\r\n return !!first && !second && first === element;\r\n } catch {\r\n return false;\r\n }\r\n};\r\n\r\nexport const getAxisNodeTest = (element: HTMLElement | Element): string => {\r\n return isSvg(element)\r\n ? `*[local-name()='${element.tagName.toLowerCase()}']`\r\n : element.tagName.toLowerCase();\r\n};\r\n\r\nconst hasAnyNumber = (value: string) => /\\d/.test(value);\r\n\r\nexport const getUniqueNodeAnchorXpaths = (\r\n node: HTMLElement | Element,\r\n docmt: Document | ShadowRoot,\r\n isTarget: boolean\r\n): { key: string; value: string }[] => {\r\n if (!(node instanceof Element)) return [];\r\n const seen = new Set<string>();\r\n const anchors: { key: string; value: string }[] = [];\r\n\r\n const attributes = Array.from(node.attributes || []).filter(\r\n (attribute) =>\r\n attribute?.value &&\r\n !hasAnyNumber(attribute.value) &&\r\n checkBlockedAttributes(attribute, node, isTarget)\r\n );\r\n\r\n const pushAnchor = (key: string, value: string) => {\r\n if (!value || seen.has(value)) return;\r\n seen.add(value);\r\n anchors.push({ key, value });\r\n };\r\n\r\n // Priority attributes\r\n const priorityAttrs = [\r\n \"id\",\r\n \"data-testid\",\r\n \"data-test\",\r\n \"data-qa\",\r\n \"name\",\r\n \"formcontrolname\",\r\n \"aria-label\",\r\n \"role\"\r\n ];\r\n\r\n priorityAttrs.forEach((attrName) => {\r\n const attr = attributes.find((a) => a.name === attrName);\r\n if (!attr) return;\r\n\r\n for (const xpath of getXpathStrings(node, attr.name, attr.value)) {\r\n if (xpath && isExactUniqueXpath(xpath, node, docmt)) {\r\n pushAnchor(`anchor by ${attr.name}`, xpath);\r\n }\r\n }\r\n });\r\n\r\n // Other attributes\r\n attributes.forEach((attribute) => {\r\n if (priorityAttrs.includes(attribute.name)) return;\r\n\r\n for (const xpath of getXpathStrings(node, attribute.name, attribute.value)) {\r\n if (xpath && isExactUniqueXpath(xpath, node, docmt)) {\r\n pushAnchor(`anchor by ${attribute.name}`, xpath);\r\n }\r\n }\r\n });\r\n\r\n // Text (controlled)\r\n const text = node.textContent?.trim();\r\n if (text && text.length < 40 && node.children.length === 0) {\r\n const textXpath = getFilteredTextXPath(node, docmt);\r\n if (textXpath && isExactUniqueXpath(textXpath, node, docmt)) {\r\n pushAnchor(\"anchor by text\", textXpath);\r\n }\r\n }\r\n\r\n // Combination\r\n if (attributes.length > 1) {\r\n const combinationXpath = getAttributeCombinationXpath(\r\n node,\r\n docmt,\r\n attributes,\r\n isTarget\r\n );\r\n\r\n if (combinationXpath && isExactUniqueXpath(combinationXpath, node, docmt)) {\r\n pushAnchor(\"anchor by combination\", combinationXpath);\r\n }\r\n }\r\n\r\n // Tag (last fallback)\r\n const tagXpath = getTagOnlyXPath(node, docmt);\r\n if (tagXpath && isExactUniqueXpath(tagXpath, node, docmt)) {\r\n pushAnchor(\"anchor by tag\", tagXpath);\r\n }\r\n\r\n return anchors;\r\n};\r\n\r\nexport const getTextXpathFunction = (\r\n domNode: HTMLElement | Element\r\n): string | undefined => {\r\n const trimmedText = getTextContent(domNode)?.trim();\r\n const stableTargetText = getStableTargetText(trimmedText);\r\n if (trimmedText && hasNumericAttributeValue(trimmedText)) {\r\n return stableTargetText\r\n ? `contains(normalize-space(.),${escapeCharacters(stableTargetText)})`\r\n : undefined;\r\n }\r\n\r\n const containerTextCondition = getContainerTextCondition(\r\n domNode,\r\n trimmedText\r\n );\r\n\r\n if (containerTextCondition) {\r\n return containerTextCondition;\r\n }\r\n\r\n const filteredText = trimmedText\r\n ? escapeCharacters(deleteGarbageFromInnerText(trimmedText))\r\n : trimmedText;\r\n if (filteredText) {\r\n if (filteredText !== `'${trimmedText}'`) {\r\n return `contains(.,${filteredText})`;\r\n }\r\n if (/\\s/.test(trimmedText)) {\r\n return `normalize-space(.)='${replaceWhiteSpaces(trimmedText)}'`;\r\n }\r\n return `normalize-space(.)='${trimmedText}'`;\r\n }\r\n};\r\n\r\nexport const getXpathString = (\r\n node: HTMLElement | Element,\r\n attrName: string,\r\n attrValue: string\r\n): string => {\r\n return getXpathStrings(node, attrName, attrValue)[0] || \"\";\r\n};\r\n\r\nexport const getXpathStrings = (\r\n node: HTMLElement | Element,\r\n attrName: string,\r\n attrValue: string\r\n): string[] => {\r\n const reWhiteSpace = new RegExp(/^[\\S]+( [\\S]+)*$/gi);\r\n let xpathe: string = \"\";\r\n attrValue = sanitizeAttributeValue(attrName, attrValue);\r\n\r\n if (attrValue) {\r\n if (attrName === \"class\") {\r\n const docmt =\r\n (node.getRootNode?.() as Document | ShadowRoot | null) ??\r\n node.ownerDocument;\r\n // Try every stable class token; numeric tokens are filtered by the\r\n // shared class helper before any XPath candidate is created.\r\n return getClassTokenConditions(node, attrValue, docmt).map((condition) =>\r\n isSvg(node)\r\n ? `//*[local-name()='${node.tagName}' and ${condition}]`\r\n : `//${node.tagName || \"*\"}[${condition}]`\r\n );\r\n }\r\n\r\n // Strict numeric-attribute validation: do not derive contains/starts-with\r\n // predicates from numeric attributes.\r\n if (hasNumericAttributeValue(attrValue)) return [];\r\n\r\n if (!reWhiteSpace.test(attrValue)) {\r\n xpathe = isSvg(node)\r\n ? `//*[local-name()='${\r\n node.tagName\r\n }' and contains(@${attrName},${escapeCharacters(attrValue)})]`\r\n : `//${node.tagName || \"*\"}[contains(@${attrName},${escapeCharacters(\r\n attrValue\r\n )})]`;\r\n } else {\r\n xpathe = isSvg(node)\r\n ? `//*[local-name()='${\r\n node.tagName\r\n }' and @${attrName}=${escapeCharacters(attrValue)}]`\r\n : `//${node.tagName || \"*\"}[@${attrName}=${escapeCharacters(\r\n attrValue\r\n )}]`;\r\n }\r\n }\r\n\r\n return xpathe ? [xpathe] : [];\r\n};\r\n\r\nexport const replaceActualAttributes = (\r\n str: string,\r\n element: { attributes: any }\r\n): string => {\r\n if (str) {\r\n return str.replace(/\\bdisabled\\b/gi, \"flndisabled\");\r\n }\r\n return str;\r\n};\r\n\r\nconst addAttributeSplitCombineXpaths = (\r\n attributes: NamedNodeMap,\r\n targetElemt: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean\r\n): { key: string; value: string }[] => {\r\n const attributesArray = Array.prototype.slice.call(attributes);\r\n const xpaths: { key: string; value: string }[] = [];\r\n try {\r\n attributesArray.map((element) => {\r\n if (checkBlockedAttributes(element, targetElemt, isTarget)) {\r\n const xpth = getCombinationXpath(element, targetElemt);\r\n if (xpth) {\r\n xpaths.push({\r\n key: `split xpath by ${element.name}`,\r\n value: xpth\r\n });\r\n }\r\n }\r\n });\r\n } catch (error) {\r\n console.log(error);\r\n }\r\n\r\n return xpaths;\r\n};\r\n\r\nconst trimAttributePart = (value: string): string =>\r\n value.replace(/^[-_:\\s.]+|[-_:\\s.]+$/g, \"\").trim();\r\n\r\nconst getStableAttributePart = (attributeValue: string): string => {\r\n const value = attributeValue.trim();\r\n const prefix = trimAttributePart(value.split(/\\d/)[0] || \"\");\r\n const prefixParts = prefix\r\n .split(/[-_:\\s.]+/)\r\n .filter(Boolean);\r\n\r\n if (prefixParts.length > 1 && prefixParts[prefixParts.length - 1].length <= 2) {\r\n prefixParts.pop();\r\n }\r\n\r\n const stablePrefix = prefixParts.join(\"-\");\r\n if (stablePrefix.length > 2 && /[a-zA-Z]/.test(stablePrefix)) {\r\n return stablePrefix;\r\n }\r\n\r\n const parts = value\r\n .split(/[\\d\\s\\-_:./\\\\]+/)\r\n .map((part) => trimAttributePart(part))\r\n .filter((part) => part.length > 2 && /[a-zA-Z]/.test(part));\r\n\r\n return parts?.sort((left, right) => right.length - left.length)[0] || \"\";\r\n};\r\n\r\nconst getNumericSafeAttributeCondition = (\r\n attributeName: string,\r\n attributeValue: string\r\n): string => {\r\n // Strict numeric-attribute validation: numeric attributes must not be\r\n // converted into \"safe\" attribute predicates.\r\n return \"\";\r\n};\r\n\r\nconst getNumericSafeAttributeXpath = (\r\n domNode: HTMLElement | Element,\r\n docmt: Document,\r\n attributeName: string,\r\n attributeValue: string\r\n): string => {\r\n // Strict numeric-attribute validation: all numeric attribute XPath fallbacks\r\n // are disabled; callers should continue to text/context strategies.\r\n return \"\";\r\n};\r\n\r\nexport const getReferenceElementsXpath = (\r\n domNode: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean\r\n): { key: string; value: string }[] => {\r\n let nodeXpath1;\r\n const xpaths1 = [];\r\n if (domNode.textContent && isTarget && isNumberExist(domNode.textContent)) {\r\n // Numeric target text can still yield stable starts-with/contains text\r\n // candidates, e.g. SPRING SUMMER 26 -> SPRING SUMMER.\r\n const { prefix, fragment } = getStableTargetTextCandidates(\r\n getTextContent(domNode)\r\n );\r\n const textConditions = [\r\n prefix\r\n ? `starts-with(normalize-space(.),${escapeCharacters(prefix)})`\r\n : \"\",\r\n fragment\r\n ? `contains(normalize-space(.),${escapeCharacters(fragment)})`\r\n : \"\"\r\n ].filter((condition, index, conditions) =>\r\n condition && conditions.indexOf(condition) === index\r\n );\r\n\r\n for (const textCondition of textConditions) {\r\n nodeXpath1 = isSvg(domNode)\r\n ? `*[local-name()='${domNode.tagName}' and ${textCondition}]`\r\n : `${domNode.tagName}[${textCondition}]`;\r\n xpaths1.push({ key: \"getReferenceElementsXpath\", value: nodeXpath1 });\r\n }\r\n }\r\n\r\n if (\r\n domNode.textContent &&\r\n (!isTarget || (isTarget && !isNumberExist(domNode.textContent)))\r\n ) {\r\n if (!reWhiteSpace.test(domNode.textContent)) {\r\n const textCondition = getTextXpathFunction(domNode);\r\n if (textCondition) {\r\n nodeXpath1 = isSvg(domNode)\r\n ? `*[local-name()='${domNode.tagName}' and ${textCondition}]`\r\n : `${domNode.tagName}[${textCondition}]`;\r\n xpaths1.push({ key: \"getReferenceElementsXpath\", value: nodeXpath1 });\r\n }\r\n } else {\r\n const textContent = getTextContent(domNode);\r\n const containerTextCondition = getContainerTextCondition(\r\n domNode,\r\n textContent\r\n );\r\n const textCondition = containerTextCondition\r\n ? containerTextCondition\r\n : /\\s/.test(textContent.trim())\r\n ? `normalize-space(.)=${escapeCharacters(\r\n replaceWhiteSpaces(textContent)\r\n )}`\r\n : `.=${escapeCharacters(textContent)}`;\r\n nodeXpath1 = isSvg(domNode)\r\n ? `*[local-name()='${domNode.tagName}' and ${textCondition}]`\r\n : `${domNode.tagName}[${textCondition}]`;\r\n if (nodeXpath1) {\r\n xpaths1.push({ key: \"getReferenceElementsXpath\", value: nodeXpath1 });\r\n }\r\n }\r\n }\r\n\r\n if (domNode.attributes) {\r\n const attributes =\r\n domNode.tagName === \"IMG\"\r\n ? Array.from(domNode.attributes).sort((left, right) => {\r\n if (left.name === \"alt\") return -1;\r\n if (right.name === \"alt\") return 1;\r\n return 0;\r\n })\r\n : Array.from(domNode.attributes);\r\n\r\n for (const attrName of attributes) {\r\n if (checkBlockedAttributes(attrName, domNode, isTarget)) {\r\n let attrValue = attrName.nodeValue;\r\n if (attrValue) {\r\n attrValue = sanitizeAttributeValue(attrName.name, attrValue);\r\n const elementName = attrName.name;\r\n // Strict numeric-attribute validation for direct callers that bypass\r\n // checkBlockedAttributes. Class is tokenized below, so only numeric\r\n // class tokens are skipped by getClassTokenCondition.\r\n if (elementName !== \"class\" && hasNumericAttributeValue(attrValue)) {\r\n continue;\r\n }\r\n\r\n if (elementName === \"class\") {\r\n const classConditions = getClassTokenConditions(\r\n domNode,\r\n attrValue,\r\n docmt\r\n );\r\n\r\n if (!classConditions.length) {\r\n continue;\r\n }\r\n\r\n for (const classCondition of classConditions) {\r\n nodeXpath1 = isSvg(domNode)\r\n ? `*[local-name()='${domNode.tagName}' and ${classCondition}]`\r\n : `${domNode.tagName}[${classCondition}]`;\r\n\r\n xpaths1.push({\r\n key: \"getReferenceElementsXpath\",\r\n value: nodeXpath1\r\n });\r\n }\r\n\r\n continue;\r\n } else {\r\n nodeXpath1 = isSvg(domNode)\r\n ? `*[local-name()='${\r\n domNode.tagName\r\n }' and @${elementName}=${escapeCharacters(attrValue)}]`\r\n : `${domNode.tagName}[@${elementName}=${escapeCharacters(\r\n attrValue\r\n )}]`;\r\n }\r\n\r\n if (nodeXpath1) {\r\n xpaths1.push({\r\n key: \"getReferenceElementsXpath\",\r\n value: nodeXpath1\r\n });\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (!xpaths1?.length) {\r\n const attributesArray = Array.prototype.slice.call(domNode.attributes);\r\n if (attributesArray?.length > 1) {\r\n const combinationXpath = getAttributeCombinationXpath(\r\n domNode,\r\n docmt,\r\n Array.prototype.slice.call(domNode.attributes),\r\n isTarget\r\n );\r\n if (combinationXpath) {\r\n xpaths1.push({\r\n key: \"getReferenceElementsXpath\",\r\n value: combinationXpath\r\n });\r\n }\r\n }\r\n }\r\n\r\n if (!xpaths1?.length) {\r\n const combinePattern = [];\r\n let pattern;\r\n const tag = domNode.tagName;\r\n if (domNode.textContent && isTarget && isNumberExist(domNode.textContent)) {\r\n const targetText = replaceWhiteSpaces(getTextContent(domNode)).trim();\r\n if (targetText?.length > 1) {\r\n combinePattern.push(`contains(text(),${escapeCharacters(targetText)})`);\r\n }\r\n\r\n if (combinePattern?.length) {\r\n if (isSvg(domNode)) {\r\n pattern = `*[local-name()='${tag}' and ${combinePattern.join(\r\n \" and \"\r\n )}]`;\r\n } else {\r\n pattern = `${tag}[${combinePattern.join(\" and \")}]`;\r\n }\r\n\r\n if (pattern)\r\n xpaths1.push({ key: \"getReferenceElementsXpath\", value: pattern });\r\n }\r\n }\r\n }\r\n\r\n if (!xpaths1?.length) {\r\n const xpaths = addAttributeSplitCombineXpaths(\r\n domNode.attributes,\r\n domNode,\r\n docmt,\r\n isTarget\r\n );\r\n if (xpaths?.length) {\r\n xpaths1.concat(xpaths);\r\n }\r\n }\r\n\r\n return xpaths1;\r\n};\r\n\r\nexport const parseXml = (\r\n xmlStr: string,\r\n type: DOMParserSupportedType\r\n): Document | null => {\r\n if (window.DOMParser) {\r\n return new window.DOMParser().parseFromString(xmlStr, type);\r\n }\r\n\r\n return null;\r\n};\r\n\r\nexport const normalizeXPath = (xpath: string): string => {\r\n // Replace text() = \"value\" or text()='value'\r\n xpath = xpath.replace(\r\n /text\\(\\)\\s*=\\s*(['\"])(.*?)\\1/g,\r\n \"normalize-space(.)=$1$2$1\"\r\n );\r\n\r\n // Replace . = \"value\" or .='value'\r\n xpath = xpath.replace(/\\.\\s*=\\s*(['\"])(.*?)\\1/g, \"normalize-space(.)=$1$2$1\");\r\n\r\n return xpath;\r\n};\r\n\r\nexport const findMatchingParenthesis = (\r\n text: string,\r\n openPos: number\r\n): number => {\r\n let closePos = openPos;\r\n let counter = 1;\r\n while (counter > 0) {\r\n const c = text[++closePos];\r\n if (c == \"(\") {\r\n counter++;\r\n } else if (c == \")\") {\r\n counter--;\r\n }\r\n }\r\n return closePos;\r\n};\r\n\r\nexport function canonicalizeXPath(xpath: string): string {\r\n return (\r\n xpath\r\n .toLowerCase()\r\n // replace quoted values\r\n .replace(/'[^']*'/g, \"?\")\r\n .replace(/\"[^\"]*\"/g, \"?\")\r\n // replace numbers in predicates\r\n .replace(/\\[\\d+\\]/g, \"[?]\")\r\n // normalize node names\r\n .replace(/\\/\\/[a-z0-9_-]+/g, \"//node\")\r\n .replace(/\\/[a-z0-9_-]+/g, \"/node\")\r\n );\r\n}\r\n\r\nexport function extractXPathSignatureParts(xpath: string) {\r\n const xp = xpath.toLowerCase();\r\n\r\n const axisMatch = xp.match(\r\n /(ancestor-or-self|ancestor|descendant-or-self|descendant|following-sibling|preceding-sibling|following|preceding|parent|child|self)::/\r\n );\r\n const axis = axisMatch?.[1] ?? \"none\";\r\n\r\n const attrMatch = xp.match(/@([a-z0-9:-]+)/);\r\n const attribute = attrMatch?.[1] ?? \"none\";\r\n\r\n const usesNormalize = xp.includes(\"normalize-space\");\r\n\r\n return { axis, attribute, usesNormalize };\r\n}\r\n\r\nexport function getXPathPattern(xpath: string): string {\r\n const canonical = canonicalizeXPath(xpath);\r\n const parts = extractXPathSignatureParts(xpath);\r\n\r\n return [\r\n \"XPATH\",\r\n `axis:${parts.axis}`,\r\n `attr:${parts.attribute}`,\r\n `normalize:${parts.usesNormalize}`,\r\n `shape:${canonical}`\r\n ].join(\"|\");\r\n}\r\n\r\nexport function escapeAttrValue(value: string): string {\r\n return value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/ /g, \"\\\\ \");\r\n}\r\n\r\nexport function shouldUseSnapshot(xpath: string): boolean {\r\n return /\\[(?:\\s*\\.|\\s*contains\\s*\\(\\s*\\.|\\s*normalize-space\\s*\\(\\s*\\.)/.test(\r\n xpath\r\n );\r\n}\r\n\r\nexport function isUniqueInDOM(\r\n docmt: Document,\r\n name: string,\r\n value: string,\r\n element?: Element | null\r\n): boolean {\r\n const root = (element?.getRootNode?.() as ParentNode | null) ?? docmt;\r\n const queryAll = (selector: string): Element[] => {\r\n try {\r\n return Array.from(root.querySelectorAll(selector));\r\n } catch {\r\n return [];\r\n }\r\n };\r\n\r\n try {\r\n switch (name) {\r\n case \"id\":\r\n return queryAll(`#${escapeAttrValue(value)}`).length === 1;\r\n\r\n case \"name\":\r\n return queryAll(`[name=\"${escapeAttrValue(value)}\"]`).length === 1;\r\n\r\n case \"className\":\r\n return queryAll(`.${value}`).length === 1;\r\n\r\n case \"tagName\":\r\n return queryAll(value).length === 1;\r\n\r\n case \"linkText\":\r\n return (\r\n queryAll(\"a\").filter((a) => a.textContent?.trim() === value)\r\n .length === 1\r\n );\r\n case \"partialLinkText\":\r\n return (\r\n queryAll(\"a\").filter((a) => a.textContent?.includes(value)).length ===\r\n 1\r\n );\r\n case \"cssSelector\":\r\n return queryAll(value).length === 1;\r\n default:\r\n return false;\r\n }\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\nexport const xpathUtils = {\r\n parseXml,\r\n getReferenceElementsXpath,\r\n getAbsoluteXPath,\r\n getRelativeXPath,\r\n getCombinationXpath,\r\n getAttributeCombinationXpath,\r\n getElementFromXpath,\r\n isSvg,\r\n findXpathWithIndex,\r\n isNumberExist,\r\n getTextContent,\r\n getCountOfXPath,\r\n normalizeXPath,\r\n getShadowRoot,\r\n escapeCharacters,\r\n removeParenthesis,\r\n checkBlockedAttributes,\r\n getRelationship,\r\n findMatchingParenthesis,\r\n deleteGarbageFromInnerText,\r\n replaceTempAttributes,\r\n createObserver,\r\n startObserver,\r\n stopObserver,\r\n modifiedElementAttributes,\r\n cspEnabled,\r\n getXPathPattern,\r\n getNormalizedPropertyXPath,\r\n getStartsWithPropertyXPath,\r\n getContainsPropertyXPath,\r\n getOrAttributesXPath,\r\n getTagOnlyXPath,\r\n getFirstMatchedNode,\r\n isExactUniqueXpath,\r\n getAxisNodeTest,\r\n getUniqueNodeAnchorXpaths,\r\n escapeAttrValue,\r\n isUniqueInDOM,\r\n sanitizeAttributeValue,\r\n getClassTokenCondition,\r\n getClassTokenConditions,\r\n getXpathStrings,\r\n intermediateXpathSteps,\r\n getContainerTextCondition\r\n};\r\n","import {\r\n isNumberExist,\r\n getCountOfXPath,\r\n escapeCharacters,\r\n checkBlockedAttributes,\r\n replaceWhiteSpaces,\r\n getTextContent,\r\n getPropertyXPath,\r\n findXpathWithIndex,\r\n getFilteredText,\r\n intermediateXpathStep,\r\n intermediateXpathSteps,\r\n getAttributeCombinationXpath,\r\n getFilteredTextXPath,\r\n getNormalizedPropertyXPath,\r\n getStartsWithPropertyXPath,\r\n getContainsPropertyXPath,\r\n getOrAttributesXPath,\r\n getTagOnlyXPath,\r\n isExactUniqueXpath,\r\n getAxisNodeTest,\r\n getTextXpathFunction,\r\n getUniqueNodeAnchorXpaths,\r\n isSvg,\r\n getXpathStrings,\r\n reWhiteSpace,\r\n sanitizeAttributeValue,\r\n getContainerTextCondition,\r\n getStableTargetText,\r\n hasNumericAttributeValue,\r\n getClassTokenConditions,\r\n buildPattern\r\n // timeLog\r\n} from \"./xpathHelpers.ts\";\r\n\r\nlet xpathData: { key: string; value: string }[] = [];\r\nlet xpathDataWithIndex: { key: string; value: string; count: number }[] = [];\r\nlet referenceElementMode: boolean = false;\r\nlet xpathCache = new Map();\r\nlet cache = new Map();\r\n\r\nconst parentXpathCache = new Map(); // Cache for parent XPaths\r\n\r\nconst STRATEGY_MAP = {\r\n andConditions: \"and\",\r\n orConditions: \"or\",\r\n contains: \"contains\",\r\n startsWith: \"startsWith\",\r\n normalizeSpace: \"normalizeSpace\",\r\n axes: \"axes\",\r\n index: \"index\",\r\n hardCodedText: \"text\",\r\n tillTag: \"tag\"\r\n} as const;\r\n\r\nconst getStrategyName = (strategy: string) => {\r\n return STRATEGY_MAP[strategy as keyof typeof STRATEGY_MAP] || strategy;\r\n};\r\n\r\nconst includesIndexStrategy = (strategies: string[] = []) =>\r\n strategies.some((strategy) => getStrategyName(strategy) === \"index\");\r\n\r\nconst withStrategyKey = (\r\n strategy: string,\r\n entries: { key: string; value: string }[]\r\n) => {\r\n return entries.map((entry) => ({\r\n ...entry,\r\n key: entry?.key?.includes(strategy)\r\n ? entry.key\r\n : `${strategy} ${entry.key}`.trim()\r\n }));\r\n};\r\n\r\nconst getUniqueXpathEntries = (entries: { key: string; value: string }[]) => {\r\n const seen = new Set<string>();\r\n\r\n return entries.filter((entry) => {\r\n if (!entry?.value || seen.has(entry.value)) {\r\n return false;\r\n }\r\n\r\n seen.add(entry.value);\r\n return true;\r\n });\r\n};\r\n\r\nconst hasPositionalIndex = (xpath: string) => {\r\n // Ignore numeric text inside quotes; only bare [n] predicates are indexes.\r\n let quote: string | null = null;\r\n\r\n for (let i = 0; i < xpath.length; i++) {\r\n const char = xpath[i];\r\n\r\n if (quote) {\r\n if (char === quote) {\r\n quote = null;\r\n }\r\n continue;\r\n }\r\n\r\n if (char === \"'\" || char === '\"') {\r\n quote = char;\r\n continue;\r\n }\r\n\r\n if (char !== \"[\") {\r\n continue;\r\n }\r\n\r\n const endIndex = xpath.indexOf(\"]\", i + 1);\r\n if (endIndex === -1) {\r\n return false;\r\n }\r\n\r\n if (/^\\s*\\d+\\s*$/.test(xpath.slice(i + 1, endIndex))) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n};\r\n\r\nconst removePositionalIndexXpaths = (\r\n entries: { key: string; value: string }[],\r\n isIndex: boolean\r\n) =>\r\n isIndex\r\n ? entries\r\n : entries.filter((entry) => !hasPositionalIndex(entry.value));\r\n\r\nconst getNormalizedText = (element: HTMLElement | Element) => {\r\n return element?.textContent?.replace(/\\s+/g, \" \")?.trim() || \"\";\r\n};\r\n\r\nconst buildTextStrategyXpaths = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n strategy: string\r\n) => {\r\n const text = getNormalizedText(element);\r\n\r\n if (!text) {\r\n return [];\r\n }\r\n\r\n if (strategy === \"text\") {\r\n const textXpath =\r\n getFilteredTextXPath(element, docmt) ||\r\n getTextXPath(element, docmt, false, false);\r\n\r\n return textXpath ? [{ key: \"xpath by text\", value: textXpath }] : [];\r\n }\r\n\r\n return [];\r\n};\r\n\r\nconst buildPropertyStrategyXpaths = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean,\r\n strategy: string\r\n) => {\r\n const xpaths: { key: string; value: string }[] = [];\r\n const attributes = Array.from(element.attributes || []).filter(\r\n (attribute) =>\r\n attribute?.value && checkBlockedAttributes(attribute, element, isTarget)\r\n );\r\n const text = getNormalizedText(element);\r\n const getStrategyXPath =\r\n strategy === \"contains\"\r\n ? getContainsPropertyXPath\r\n : strategy === \"startsWith\"\r\n ? getStartsWithPropertyXPath\r\n : strategy === \"normalizeSpace\"\r\n ? getNormalizedPropertyXPath\r\n : null;\r\n\r\n if (!getStrategyXPath) {\r\n return xpaths;\r\n }\r\n\r\n if (strategy === \"contains\" && text) {\r\n // In single mode, prefer one unique text contains XPath.\r\n for (const textProp of [\"text()\", \".\"]) {\r\n const xpath = getStrategyXPath(element, textProp, text);\r\n if (xpath && getCountOfXPath(xpath, element, docmt) === 1) {\r\n return [{ key: `xpath by ${strategy} text`, value: xpath }];\r\n }\r\n }\r\n }\r\n\r\n attributes.forEach((attribute) => {\r\n if (attribute.name === \"class\") {\r\n getClassTokenConditions(element, attribute.value, docmt).forEach(\r\n (classCondition) => {\r\n const xpath = buildPattern(\r\n classCondition,\r\n isSvg(element),\r\n element.tagName.toLowerCase()\r\n );\r\n\r\n if (xpath) {\r\n xpaths.push({\r\n key: `xpath by ${strategy} class`,\r\n value: xpath\r\n });\r\n }\r\n }\r\n );\r\n\r\n return;\r\n }\r\n\r\n const xpath = getStrategyXPath(\r\n element,\r\n `@${attribute.name}`,\r\n attribute.value\r\n );\r\n\r\n if (xpath) {\r\n xpaths.push({\r\n key: `xpath by ${strategy} ${attribute.name}`,\r\n value: xpath\r\n });\r\n }\r\n });\r\n\r\n if (strategy === \"contains\" && xpaths.length) {\r\n const xpath = xpaths.find(\r\n (candidate) => getCountOfXPath(candidate.value, element, docmt) === 1\r\n );\r\n return xpath ? [xpath] : [];\r\n }\r\n\r\n if (text) {\r\n const xpath = getStrategyXPath(element, \".\", text);\r\n\r\n if (xpath) {\r\n xpaths.push({\r\n key: `xpath by ${strategy} text`,\r\n value: xpath\r\n });\r\n }\r\n }\r\n\r\n return xpaths;\r\n};\r\n\r\nconst buildConditionStrategyXpaths = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean,\r\n strategy: string\r\n) => {\r\n const attributes = Array.from(element.attributes || []).filter(\r\n (attribute) =>\r\n attribute?.value && checkBlockedAttributes(attribute, element, isTarget)\r\n );\r\n\r\n if (strategy === \"and\") {\r\n const combinationXpath = getAttributeCombinationXpath(\r\n element,\r\n docmt,\r\n attributes,\r\n isTarget\r\n );\r\n\r\n return combinationXpath\r\n ? [{ key: \"xpath by combination\", value: combinationXpath }]\r\n : [];\r\n }\r\n\r\n if (strategy === \"or\" && attributes.length >= 1) {\r\n const xpath = getOrAttributesXPath(element, attributes);\r\n return xpath ? [{ key: \"xpath by or\", value: xpath }] : [];\r\n }\r\n\r\n return [];\r\n};\r\n\r\nconst collectSubtreeElements = (root: HTMLElement | Element) => {\r\n if (!root || !(root instanceof Element)) return [];\r\n\r\n const nodes: (HTMLElement | Element)[] = [];\r\n const queue = Array.from(root.children || []);\r\n\r\n while (queue.length) {\r\n const currentNode = queue.shift()!;\r\n\r\n if (currentNode.classList?.contains(\"flntooltip\")) {\r\n continue;\r\n }\r\n\r\n nodes.push(currentNode);\r\n queue.push(...Array.from(currentNode.children || []));\r\n }\r\n\r\n return nodes;\r\n};\r\n\r\nconst getSiblingNodes = (\r\n element: HTMLElement | Element,\r\n direction: \"previous\" | \"next\"\r\n) => {\r\n const nodes: (HTMLElement | Element)[] = [];\r\n let currentNode =\r\n direction === \"previous\"\r\n ? element.previousElementSibling\r\n : element.nextElementSibling;\r\n\r\n while (currentNode) {\r\n if (!currentNode.classList?.contains(\"flntooltip\")) {\r\n nodes.push(currentNode);\r\n }\r\n\r\n currentNode =\r\n direction === \"previous\"\r\n ? currentNode.previousElementSibling\r\n : currentNode.nextElementSibling;\r\n }\r\n\r\n return nodes;\r\n};\r\n\r\ntype AxisSeedNode =\r\n | Element\r\n | {\r\n node: Element;\r\n expand: () => Element[];\r\n };\r\n\r\nconst getDirectionalAxisSeedNodes = (\r\n element: HTMLElement | Element,\r\n direction: \"previous\" | \"next\"\r\n): AxisSeedNode[] => {\r\n const nodes: AxisSeedNode[] = [];\r\n\r\n for (\r\n let currentNode: HTMLElement | Element | null = element;\r\n currentNode?.parentElement;\r\n currentNode = currentNode.parentElement\r\n ) {\r\n let sibling =\r\n direction === \"previous\"\r\n ? currentNode.previousElementSibling\r\n : currentNode.nextElementSibling;\r\n\r\n while (sibling) {\r\n if (!sibling.classList?.contains(\"flntooltip\")) {\r\n nodes.push(sibling);\r\n\r\n nodes.push({\r\n node: sibling,\r\n expand: () => collectSubtreeElements(sibling as HTMLElement)\r\n });\r\n }\r\n\r\n sibling =\r\n direction === \"previous\"\r\n ? sibling.previousElementSibling\r\n : sibling.nextElementSibling;\r\n }\r\n }\r\n\r\n return nodes;\r\n};\r\n\r\nconst isLowQualityNode = (node: Element) => {\r\n if (!(node instanceof Element)) return true;\r\n const text = node.textContent?.trim();\r\n\r\n return (\r\n !node.id &&\r\n !node.className &&\r\n node.attributes.length === 0 &&\r\n (!text || text.length > 60) &&\r\n node.children.length > 2\r\n );\r\n};\r\n\r\nconst scoreNode = (node: Element) => {\r\n // Prefer stable/readable nearby anchors when building chained axes fallbacks.\r\n let score = 0;\r\n if (node.id) score += 100;\r\n if (node.getAttribute(\"data-testid\")) score += 90;\r\n if (node.className) score += 50;\r\n if (node.children.length === 0) score += 30;\r\n\r\n const text = node.textContent?.trim();\r\n if (text && text.length < 40) score += 40;\r\n\r\n return score;\r\n};\r\n\r\nconst buildAxisXpathsForNodes = (\r\n nodes: any[],\r\n axis: string,\r\n targetElement: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean,\r\n key: string,\r\n isIndex: boolean\r\n) => {\r\n const textPredicate = getTextXpathFunction(targetElement);\r\n const targetNodeTest = textPredicate\r\n ? `${getAxisNodeTest(targetElement)}[${textPredicate}]`\r\n : getAxisNodeTest(targetElement);\r\n const MAX_INDEX_TRY = 3;\r\n const MAX_CANDIDATES = 5;\r\n\r\n const candidates: { key: string; value: string }[] = [];\r\n\r\n let processed = 0;\r\n const MAX_PROCESS = axis.includes(\"descendant\") ? 10 : 20;\r\n\r\n for (const nodeItem of nodes) {\r\n if (processed++ > MAX_PROCESS) break;\r\n if (candidates.length >= MAX_CANDIDATES) break;\r\n\r\n const node = \"node\" in nodeItem ? nodeItem.node : nodeItem;\r\n\r\n if (!(node instanceof Element)) continue;\r\n if (isLowQualityNode(node)) continue;\r\n\r\n const anchorStages = [\r\n () => getUniqueNodeAnchorXpaths(node, docmt, isTarget),\r\n () => {\r\n const anchors: any[] = [];\r\n const attributes = Array.from(node.attributes || []).filter(\r\n (attr) => attr?.value && checkBlockedAttributes(attr, node, isTarget)\r\n );\r\n\r\n for (const attr of attributes) {\r\n const value = attr.value;\r\n if (!value) continue;\r\n\r\n const containsXpath = getContainsPropertyXPath(\r\n node,\r\n `@${attr.name}`,\r\n value\r\n );\r\n if (containsXpath) {\r\n anchors.push({ key: \"contains\", value: containsXpath });\r\n }\r\n\r\n const startsWithXpath = getStartsWithPropertyXPath(\r\n node,\r\n `@${attr.name}`,\r\n value\r\n );\r\n if (startsWithXpath) {\r\n anchors.push({ key: \"starts-with\", value: startsWithXpath });\r\n }\r\n }\r\n\r\n return anchors;\r\n },\r\n () => [{ key: \"tag\", value: getTagOnlyXPath(node, docmt) }]\r\n ];\r\n\r\n for (let stageIndex = 0; stageIndex < anchorStages.length; stageIndex++) {\r\n let anchors = anchorStages[stageIndex]();\r\n if (!anchors.length) continue;\r\n\r\n if (!isIndex) {\r\n anchors = anchors.filter((a) => !/\\[\\d+\\]/.test(a.value));\r\n }\r\n\r\n const seen = new Set<string>();\r\n anchors = anchors.filter((a) => {\r\n if (seen.has(a.value)) return false;\r\n seen.add(a.value);\r\n return true;\r\n });\r\n\r\n anchors = anchors.slice(0, 5);\r\n\r\n for (const anchor of anchors) {\r\n const xpath = `${anchor.value}/${axis}::${targetNodeTest}`;\r\n\r\n if (isExactUniqueXpath(xpath, targetElement, docmt)) {\r\n candidates.push({ key, value: xpath });\r\n\r\n if (candidates.length >= 2) {\r\n return candidates;\r\n }\r\n\r\n if (candidates.length >= MAX_CANDIDATES) {\r\n return candidates;\r\n }\r\n }\r\n }\r\n\r\n if (isIndex && stageIndex === anchorStages.length - 1) {\r\n for (const anchor of anchors) {\r\n const count = getCountOfXPath(anchor.value, node, docmt);\r\n\r\n for (let i = 1; i <= Math.min(count, MAX_INDEX_TRY); i++) {\r\n const xpath = `(${anchor.value})[${i}]/${axis}::${targetNodeTest}`;\r\n\r\n if (isExactUniqueXpath(xpath, targetElement, docmt)) {\r\n candidates.push({ key, value: xpath });\r\n\r\n if (candidates.length >= 2) {\r\n return candidates;\r\n }\r\n\r\n if (candidates.length >= MAX_CANDIDATES) {\r\n return candidates;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (\"expand\" in nodeItem && nodeItem.expand) {\r\n const expanded = nodeItem.expand();\r\n\r\n let childCount = 0;\r\n for (const child of expanded) {\r\n if (childCount++ > 15) break;\r\n if (candidates.length >= MAX_CANDIDATES) break;\r\n\r\n if (!(child instanceof Element)) continue;\r\n if (isLowQualityNode(child)) continue;\r\n\r\n const anchors = getUniqueNodeAnchorXpaths(child, docmt, isTarget);\r\n\r\n for (const anchor of anchors.slice(0, 5)) {\r\n const xpath = `${anchor.value}/${axis}::${targetNodeTest}`;\r\n\r\n if (isExactUniqueXpath(xpath, targetElement, docmt)) {\r\n candidates.push({ key, value: xpath });\r\n\r\n if (candidates.length >= 2) {\r\n return candidates;\r\n }\r\n\r\n if (candidates.length >= MAX_CANDIDATES) {\r\n return candidates;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return candidates;\r\n};\r\n\r\nconst getLimitedAncestors = (node: Element | null, limit: number) => {\r\n // Keep fallback scope search bounded.\r\n const ancestors: Element[] = [];\r\n let currentNode = node;\r\n\r\n while (currentNode && ancestors.length < limit) {\r\n ancestors.push(currentNode);\r\n currentNode = currentNode.parentElement;\r\n }\r\n\r\n return ancestors;\r\n};\r\n\r\nconst getSiblingAxis = (from: Element, to: Element) => {\r\n for (\r\n let node = from.nextElementSibling;\r\n node;\r\n node = node.nextElementSibling\r\n ) {\r\n if (node === to) return \"following-sibling\";\r\n }\r\n\r\n for (\r\n let node = from.previousElementSibling;\r\n node;\r\n node = node.previousElementSibling\r\n ) {\r\n if (node === to) return \"preceding-sibling\";\r\n }\r\n\r\n return \"\";\r\n};\r\n\r\nconst getParentAxisPath = (from: Element, to: Element) => {\r\n const parts: string[] = [];\r\n\r\n for (let node = from; node && node !== to; node = node.parentElement!) {\r\n const parent = node.parentElement;\r\n if (!parent) return \"\";\r\n\r\n parts.push(`/parent::${getAxisNodeTest(parent)}`);\r\n }\r\n\r\n return parts.join(\"\");\r\n};\r\n\r\nconst getTargetPathFromScope = (\r\n scope: Element,\r\n targetElement: HTMLElement | Element\r\n) => {\r\n const targetNodeTest = getAxisNodeTest(targetElement);\r\n\r\n return targetElement.parentElement === scope\r\n ? [`/${targetNodeTest}`, `/descendant::${targetNodeTest}`]\r\n : [`/descendant::${targetNodeTest}`];\r\n};\r\n\r\nconst collectChainedAxisAnchorNodes = (element: HTMLElement | Element) => {\r\n // Collect nearby sibling-side anchors for cases where direct axes cannot\r\n // reach the target cleanly.\r\n const anchors: Element[] = [];\r\n const seen = new Set<Element>();\r\n const MAX_SCOPE_DEPTH = 4;\r\n const MAX_SIBLINGS_PER_SCOPE = 4;\r\n const MAX_DESCENDANTS_PER_SIBLING = 8;\r\n const MAX_ANCHORS = 20;\r\n\r\n const push = (node: Element | null) => {\r\n if (!node || node === element || seen.has(node)) return;\r\n if (node.contains(element) || element.contains(node)) return;\r\n\r\n seen.add(node);\r\n anchors.push(node);\r\n };\r\n\r\n for (\r\n let scope: Element | null = element.parentElement, depth = 0;\r\n scope && depth < MAX_SCOPE_DEPTH && anchors.length < MAX_ANCHORS;\r\n scope = scope.parentElement, depth++\r\n ) {\r\n const siblingGroups = [\r\n getSiblingNodes(scope, \"previous\").slice(0, MAX_SIBLINGS_PER_SCOPE),\r\n getSiblingNodes(scope, \"next\").slice(0, MAX_SIBLINGS_PER_SCOPE)\r\n ];\r\n\r\n for (const siblings of siblingGroups) {\r\n for (const sibling of siblings) {\r\n push(sibling);\r\n\r\n let descendantCount = 0;\r\n for (const descendant of collectSubtreeElements(sibling).sort(\r\n (left, right) => scoreNode(right) - scoreNode(left)\r\n )) {\r\n if (descendantCount++ >= MAX_DESCENDANTS_PER_SIBLING) break;\r\n push(descendant);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return anchors\r\n .sort((left, right) => scoreNode(right) - scoreNode(left))\r\n .slice(0, MAX_ANCHORS);\r\n};\r\n\r\nconst buildChainedAxesFallbackXpaths = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean\r\n) => {\r\n // Last axes fallback: anchor on a nearby node, hop to a sibling scope, then\r\n // descend to the target.\r\n const candidates: { key: string; value: string }[] = [];\r\n const seen = new Set<string>();\r\n const anchorNodes = collectChainedAxisAnchorNodes(element);\r\n const targetScopes = getLimitedAncestors(element.parentElement, 3);\r\n\r\n for (const anchorNode of anchorNodes) {\r\n const anchorXpaths = getUniqueNodeAnchorXpaths(anchorNode, docmt, isTarget)\r\n .filter((anchor) => anchor.key !== \"anchor by tag\")\r\n .slice(0, 2);\r\n\r\n if (!anchorXpaths.length) continue;\r\n\r\n const anchorScopes = getLimitedAncestors(anchorNode, 3);\r\n\r\n for (const anchorXpath of anchorXpaths) {\r\n for (const anchorScope of anchorScopes) {\r\n const anchorToScopePath = getParentAxisPath(anchorNode, anchorScope);\r\n if (anchorScope !== anchorNode && !anchorToScopePath) continue;\r\n\r\n for (const targetScope of targetScopes) {\r\n if (!targetScope || !targetScope.contains(element)) continue;\r\n\r\n const siblingAxis = getSiblingAxis(anchorScope, targetScope);\r\n if (!siblingAxis) continue;\r\n\r\n const siblingStep = `/${siblingAxis}::${getAxisNodeTest(targetScope)}`;\r\n\r\n for (const targetPath of getTargetPathFromScope(\r\n targetScope,\r\n element\r\n )) {\r\n const xpath = `${anchorXpath.value}${anchorToScopePath}${siblingStep}${targetPath}`;\r\n\r\n if (seen.has(xpath)) continue;\r\n seen.add(xpath);\r\n\r\n if (isExactUniqueXpath(xpath, element, docmt)) {\r\n candidates.push({\r\n key: \"chained axes fallback\",\r\n value: xpath\r\n });\r\n\r\n if (candidates.length >= 2) {\r\n return candidates;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return candidates;\r\n};\r\n\r\nconst getStableBridgeText = (text: string | null | undefined) => {\r\n const stableText = getStableTargetText(text);\r\n if (stableText) return stableText;\r\n\r\n return (text || \"\")\r\n .split(/[^a-zA-Z]+/)\r\n .map((part) => part.trim())\r\n .filter((part) => part.length > 2)\r\n .pop();\r\n};\r\n\r\nconst getBridgeTargetNodeTests = (targetElement: HTMLElement | Element) => {\r\n // Build target-side node tests for the final descendant step.\r\n const tagName = getAxisNodeTest(targetElement);\r\n const text = targetElement.textContent?.trim();\r\n\r\n // Prefer a stable alphabetic fragment when target text mixes dynamic values\r\n // with a reusable label/unit.\r\n const stableText = getStableBridgeText(text);\r\n const textPredicate = getTextXpathFunction(targetElement);\r\n const nodeTests: string[] = [];\r\n\r\n // When target text contains dynamic numeric parts, prefer the stable text\r\n // fragment on the direct text node before falling back to generic text logic.\r\n if (stableText && stableText !== text) {\r\n nodeTests.push(\r\n `${tagName}[contains(text(),${escapeCharacters(stableText)})]`\r\n );\r\n }\r\n\r\n if (textPredicate) {\r\n nodeTests.push(`${tagName}[${textPredicate}]`);\r\n }\r\n\r\n nodeTests.push(tagName);\r\n return Array.from(new Set(nodeTests));\r\n};\r\n\r\nconst collectBridgeAxisAnchorNodes = (element: HTMLElement | Element) => {\r\n // Walk nearby sibling scopes in document order so readable text anchors are\r\n // considered before less descriptive structural anchors.\r\n const anchors: Element[] = [];\r\n const seen = new Set<Element>();\r\n const MAX_SCOPE_DEPTH = 4;\r\n const MAX_ANCHORS = 30;\r\n\r\n const push = (node: Element | null) => {\r\n // Avoid target/self/container anchors; the bridge must start from a\r\n // separate nearby element.\r\n if (!node || node === element || seen.has(node)) return;\r\n if (node.contains(element) || element.contains(node)) return;\r\n\r\n seen.add(node);\r\n anchors.push(node);\r\n };\r\n\r\n const pushStableTextLeaves = (sibling: Element) => {\r\n // Stable leaf text gives the bridge a strong nearby anchor.\r\n for (const node of [sibling, ...collectSubtreeElements(sibling)]) {\r\n const text = getTextContent(node)?.trim();\r\n if (\r\n text &&\r\n text.length < 40 &&\r\n !hasNumericAttributeValue(text) &&\r\n node.children.length === 0\r\n ) {\r\n push(node);\r\n }\r\n }\r\n };\r\n\r\n for (\r\n let scope: Element | null = element.parentElement, depth = 0;\r\n scope && depth < MAX_SCOPE_DEPTH && anchors.length < MAX_ANCHORS;\r\n scope = scope.parentElement, depth++\r\n ) {\r\n // At each parent scope, inspect surrounding siblings while preserving\r\n // document order for deterministic candidate generation.\r\n const previousSiblings = getSiblingNodes(scope, \"previous\").reverse();\r\n const nextSiblings = getSiblingNodes(scope, \"next\");\r\n\r\n for (const sibling of [...previousSiblings, ...nextSiblings]) {\r\n pushStableTextLeaves(sibling);\r\n push(sibling);\r\n }\r\n }\r\n\r\n return anchors.slice(0, MAX_ANCHORS);\r\n};\r\n\r\nconst getBridgeAnchorXpaths = (\r\n anchorNode: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean\r\n) => {\r\n const text = getTextContent(anchorNode)?.trim();\r\n const anchors: { key: string; value: string }[] = [];\r\n\r\n // Prefer exact text syntax for readable bridge anchors. The final bridge\r\n // XPath is still validated against the target, so this anchor does not need\r\n // to be unique on its own.\r\n if (\r\n text &&\r\n text.length > 2 &&\r\n text.length < 40 &&\r\n !hasNumericAttributeValue(text)\r\n ) {\r\n const xpath = `//${anchorNode.tagName}[.=${escapeCharacters(text)}]`;\r\n anchors.push({ key: \"anchor by text\", value: xpath });\r\n }\r\n\r\n // Keep existing unique attribute/text anchors as secondary options.\r\n return [\r\n ...anchors,\r\n ...getUniqueNodeAnchorXpaths(anchorNode, docmt, isTarget).filter(\r\n (anchor) => anchor.key !== \"anchor by tag\"\r\n )\r\n ];\r\n};\r\n\r\nconst buildAncestorDescendantBridgeAxesXpaths = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean\r\n) => {\r\n // Late fallback: nearby anchor -> its ancestor scope -> target descendant.\r\n const candidates: { key: string; value: string }[] = [];\r\n const seen = new Set<string>();\r\n const anchorNodes = collectBridgeAxisAnchorNodes(element);\r\n const targetNodeTests = getBridgeTargetNodeTests(element);\r\n\r\n for (const anchorNode of anchorNodes) {\r\n const anchorXpaths = getBridgeAnchorXpaths(\r\n anchorNode,\r\n docmt,\r\n isTarget\r\n ).slice(0, 2);\r\n\r\n if (!anchorXpaths.length) continue;\r\n\r\n for (const anchorXpath of anchorXpaths) {\r\n // Walk up from the anchor to find a shared scope that contains the\r\n // target.\r\n for (const scope of getLimitedAncestors(anchorNode.parentElement, 5)) {\r\n if (!scope.contains(element)) continue;\r\n\r\n const scopeStep = `/ancestor::${getAxisNodeTest(scope)}`;\r\n\r\n for (const targetNodeTest of targetNodeTests) {\r\n // Final shape:\r\n // anchor/ancestor::scope/descendant::targetWithPreferredText\r\n const xpath = `${anchorXpath.value}${scopeStep}/descendant::${targetNodeTest}`;\r\n\r\n if (seen.has(xpath)) continue;\r\n seen.add(xpath);\r\n\r\n if (isExactUniqueXpath(xpath, element, docmt)) {\r\n // Return the first unique bridge to avoid pushing equivalent\r\n // variants for the same target in single mode.\r\n candidates.push({\r\n key: \"ancestor descendant bridge\",\r\n value: xpath\r\n });\r\n\r\n if (candidates.length >= 1) {\r\n return candidates;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return candidates;\r\n};\r\n\r\nconst getChildAxisPathFromAncestor = (\r\n ancestor: Element,\r\n element: HTMLElement | Element\r\n) => {\r\n // Build the child-axis tail from an anchored ancestor down to the target.\r\n const steps: string[] = [];\r\n\r\n for (\r\n let currentNode: HTMLElement | Element | null = element;\r\n currentNode && currentNode !== ancestor;\r\n currentNode = currentNode.parentElement\r\n ) {\r\n steps.unshift(getAxisNodeTest(currentNode));\r\n }\r\n\r\n if (!steps.length) return \"\";\r\n\r\n const finalStep = steps.pop();\r\n const intermediatePath = steps.length ? `/${steps.join(\"/\")}` : \"\";\r\n\r\n return `${intermediatePath}/child::${finalStep}`;\r\n};\r\n\r\nconst getStableAxisAttributeValue = (attributeValue: string) => {\r\n // Single-mode axis anchors may use a stable prefix before a numeric suffix.\r\n const valueBeforeNumber = attributeValue.split(/\\d/)[0] || \"\";\r\n const stableValue = valueBeforeNumber.replace(/[-_:\\s.]+$/g, \"\").trim();\r\n\r\n return stableValue.length > 2 && /[a-zA-Z]/.test(stableValue)\r\n ? stableValue\r\n : \"\";\r\n};\r\n\r\nconst canUseStableAxisAttribute = (attribute: Attr) => {\r\n const blockedNames = [\"style\", \"locator-data-tooltip\", \"value\"];\r\n\r\n return (\r\n !!attribute?.value &&\r\n !blockedNames.includes(attribute.name) &&\r\n !(attribute.name.indexOf(\"on\") === 0 && attribute.name.length > 3)\r\n );\r\n};\r\n\r\nconst getChildChainAnchorXpaths = (\r\n ancestor: Element,\r\n docmt: Document,\r\n isTarget: boolean,\r\n isIndex: boolean\r\n) => {\r\n // Use normal unique anchors plus stable numeric-prefix anchors for child\r\n // chain generation.\r\n const seen = new Set<string>();\r\n const anchors: { key: string; value: string }[] = [];\r\n const addAnchor = (key: string, value: string) => {\r\n if (!value || seen.has(value)) return;\r\n if (!isIndex && /\\[\\d+\\]/.test(value)) return;\r\n\r\n seen.add(value);\r\n anchors.push({ key, value });\r\n };\r\n\r\n getUniqueNodeAnchorXpaths(ancestor, docmt, isTarget)\r\n .filter((anchor) => anchor.key !== \"anchor by tag\")\r\n .forEach((anchor) => addAnchor(anchor.key, anchor.value));\r\n\r\n Array.from(ancestor.attributes || []).forEach((attribute) => {\r\n if (\r\n !canUseStableAxisAttribute(attribute) ||\r\n !hasNumericAttributeValue(attribute.value) ||\r\n attribute.name === \"class\"\r\n ) {\r\n return;\r\n }\r\n\r\n const stableValue = getStableAxisAttributeValue(attribute.value);\r\n if (!stableValue) return;\r\n\r\n const xpath = getContainsPropertyXPath(\r\n ancestor,\r\n `@${attribute.name}`,\r\n stableValue\r\n );\r\n addAnchor(`anchor by stable ${attribute.name}`, xpath);\r\n });\r\n\r\n return anchors;\r\n};\r\n\r\nconst buildAnchoredChildChainAxesXpaths = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean,\r\n isIndex: boolean\r\n) => {\r\n // Prefer direct anchored child-chain paths before broader chained axes.\r\n const candidates: { key: string; value: string }[] = [];\r\n const seen = new Set<string>();\r\n const MAX_ANCESTORS = 8;\r\n\r\n for (\r\n let ancestor = element.parentElement, depth = 0;\r\n ancestor && depth < MAX_ANCESTORS;\r\n ancestor = ancestor.parentElement, depth++\r\n ) {\r\n const childAxisPath = getChildAxisPathFromAncestor(ancestor, element);\r\n if (!childAxisPath) continue;\r\n\r\n const anchors = getChildChainAnchorXpaths(\r\n ancestor,\r\n docmt,\r\n isTarget,\r\n isIndex\r\n );\r\n\r\n for (const anchor of anchors.slice(0, 3)) {\r\n const xpath = `${anchor.value}${childAxisPath}`;\r\n\r\n if (seen.has(xpath)) continue;\r\n seen.add(xpath);\r\n\r\n if (isExactUniqueXpath(xpath, element, docmt)) {\r\n candidates.push({\r\n key: \"child chain\",\r\n value: xpath\r\n });\r\n\r\n if (candidates.length >= 2) {\r\n return candidates;\r\n }\r\n }\r\n }\r\n }\r\n\r\n return candidates;\r\n};\r\n\r\nconst buildAxesStrategyXpaths = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean,\r\n isIndex: boolean\r\n) => {\r\n // const totalStart = performance.now();\r\n\r\n // let t = performance.now();\r\n\r\n const ancestors: (HTMLElement | Element)[] = [];\r\n\r\n for (\r\n let currentNode = element.parentElement;\r\n currentNode;\r\n currentNode = currentNode.parentElement\r\n ) {\r\n ancestors.push(currentNode);\r\n }\r\n // timeLog(\"ancestors collection\", t);\r\n // t = performance.now();\r\n const descendants = collectSubtreeElements(element);\r\n // timeLog(\"descendants collection\", t);\r\n // t = performance.now();\r\n const previousSiblings = getSiblingNodes(element, \"previous\");\r\n // timeLog(\"previous siblings collection\", t);\r\n\r\n const nextSiblings = getSiblingNodes(element, \"next\");\r\n // timeLog(\"next siblings collection\", t);\r\n\r\n const precedingSeeds = getDirectionalAxisSeedNodes(element, \"previous\");\r\n // timeLog(\"preceding seeds collection\", t);\r\n // t = performance.now();\r\n const followingSeeds = getDirectionalAxisSeedNodes(element, \"next\");\r\n // timeLog(\"following seeds collection\", t);\r\n // t = performance.now();\r\n const limitNodes = (nodes: any[], limit = 20) => {\r\n return nodes.length > limit ? nodes.slice(0, limit) : nodes;\r\n };\r\n\r\n const tiers = [\r\n [\r\n {\r\n nodes: limitNodes(nextSiblings),\r\n axis: \"preceding-sibling\",\r\n key: \"preceding-sibling\"\r\n },\r\n {\r\n nodes: limitNodes(previousSiblings),\r\n axis: \"following-sibling\",\r\n key: \"following-sibling\"\r\n }\r\n ],\r\n [\r\n { nodes: limitNodes(ancestors), axis: \"child\", key: \"child\" },\r\n {\r\n nodes: limitNodes(descendants, 15),\r\n axis: \"parent\",\r\n key: \"parent\"\r\n }\r\n ],\r\n [\r\n { nodes: limitNodes(ancestors), axis: \"descendant\", key: \"descendant\" },\r\n {\r\n nodes: limitNodes(ancestors),\r\n axis: \"descendant-or-self\",\r\n key: \"descendant-or-self\"\r\n },\r\n {\r\n nodes: limitNodes(descendants, 10),\r\n axis: \"ancestor\",\r\n key: \"ancestor\"\r\n },\r\n {\r\n nodes: limitNodes(descendants, 10),\r\n axis: \"ancestor-or-self\",\r\n key: \"ancestor-or-self\"\r\n }\r\n ],\r\n [\r\n {\r\n nodes: limitNodes(precedingSeeds),\r\n axis: \"following\",\r\n key: \"following\"\r\n },\r\n { nodes: limitNodes(followingSeeds), axis: \"preceding\", key: \"preceding\" }\r\n ]\r\n ];\r\n // timeLog(\"nodes sorting\", t);\r\n const childChainResults = buildAnchoredChildChainAxesXpaths(\r\n element,\r\n docmt,\r\n isTarget,\r\n isIndex\r\n );\r\n if (childChainResults.length) {\r\n return childChainResults;\r\n }\r\n\r\n for (const tier of tiers) {\r\n for (const strategy of tier) {\r\n // const t = performance.now();\r\n\r\n const results = buildAxisXpathsForNodes(\r\n strategy.nodes,\r\n strategy.axis,\r\n element,\r\n docmt,\r\n isTarget,\r\n strategy.key,\r\n isIndex\r\n );\r\n\r\n // timeLog(`strategy ${strategy.key}`, t);\r\n if (results.length > 0) {\r\n // timeLog(\"TOTAL buildAxesStrategyXpaths\", totalStart);\r\n return results;\r\n }\r\n }\r\n }\r\n\r\n // If direct axes cannot make a unique XPath, try a semantic bridge through a\r\n // nearby text anchor and a shared ancestor scope before positional fallbacks.\r\n const ancestorBridgeResults = buildAncestorDescendantBridgeAxesXpaths(\r\n element,\r\n docmt,\r\n isTarget\r\n );\r\n if (ancestorBridgeResults.length) {\r\n return ancestorBridgeResults;\r\n }\r\n\r\n return buildChainedAxesFallbackXpaths(element, docmt, isTarget) || [];\r\n};\r\n\r\nexport const generateIndexedXpaths = (element: Element, docmt: Document) => {\r\n const results = [];\r\n const tag = element.tagName.toLowerCase();\r\n\r\n const globalList = Array.from(docmt.querySelectorAll(tag));\r\n const idx = globalList.indexOf(element) + 1;\r\n\r\n if (idx <= 0) return [];\r\n\r\n // global must always exist)\r\n results.push({\r\n key: \"xpath by index\",\r\n value: `(//${tag})[${idx}]`\r\n });\r\n\r\n // Add at most one scoped/indexed XPath anchored by the nearest stable ancestor.\r\n let current: Element | null = element.parentElement;\r\n\r\n while (current && current !== docmt.body) {\r\n if (current.id || current.className) {\r\n const scopeXpaths = current.id\r\n ? getXpathStrings(current, \"id\", current.id)\r\n : getXpathStrings(current, \"class\", current.className.toString());\r\n\r\n // Scoped index anchors should also try every stable class token before\r\n // walking to a higher ancestor.\r\n for (const scopeXpath of scopeXpaths) {\r\n const scopedDescendants = Array.from(current.querySelectorAll(tag));\r\n const scopedIdx = scopedDescendants.indexOf(element) + 1;\r\n\r\n if (scopedIdx > 0) {\r\n results.push({\r\n key: \"xpath by index\",\r\n value: `(${scopeXpath}//${tag})[${scopedIdx}]`\r\n });\r\n break;\r\n }\r\n }\r\n\r\n if (results.length > 1) {\r\n break;\r\n }\r\n }\r\n\r\n current = current.parentElement;\r\n }\r\n\r\n return results;\r\n};\r\n\r\nconst buildStrategyXpaths = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean,\r\n strategy: string,\r\n fallbackXpaths: { key: string; value: string }[]\r\n) => {\r\n const strategyName = getStrategyName(strategy);\r\n\r\n if (strategyName === \"text\") {\r\n return withStrategyKey(\r\n strategy,\r\n buildTextStrategyXpaths(element, docmt, strategyName)\r\n );\r\n }\r\n\r\n if ([\"contains\", \"startsWith\", \"normalizeSpace\"].includes(strategyName)) {\r\n return withStrategyKey(\r\n strategy,\r\n buildPropertyStrategyXpaths(element, docmt, isTarget, strategyName)\r\n );\r\n }\r\n\r\n if ([\"and\", \"or\"].includes(strategyName)) {\r\n return withStrategyKey(\r\n strategy,\r\n buildConditionStrategyXpaths(element, docmt, isTarget, strategyName)\r\n );\r\n }\r\n\r\n if (strategyName === \"axes\") {\r\n return withStrategyKey(\r\n strategy,\r\n buildAxesStrategyXpaths(element, docmt, isTarget, false)\r\n );\r\n }\r\n\r\n if (strategyName === \"index\") {\r\n return withStrategyKey(strategy, generateIndexedXpaths(element, docmt));\r\n }\r\n\r\n if (strategyName === \"tag\") {\r\n return withStrategyKey(strategy, [\r\n { key: \"xpath till tag\", value: getTagOnlyXPath(element, docmt) }\r\n ]);\r\n }\r\n\r\n return [];\r\n};\r\n\r\nconst checkRelativeXpathRelation = (\r\n nodeXpath1: string,\r\n nodeXpath2: string,\r\n targetElemt: HTMLElement | Element,\r\n docmt: Document,\r\n isIndex: boolean,\r\n relationType: string\r\n) => {\r\n if (nodeXpath1 && !referenceElementMode) {\r\n let xpaths;\r\n\r\n if (relationType === \"parent\") {\r\n xpaths = [\r\n // `${nodeXpath1}/descendant::${nodeXpath2}`,\r\n `${nodeXpath1}/descendant-or-self::${nodeXpath2}`,\r\n `${nodeXpath1}/following::${nodeXpath2}`\r\n ];\r\n } else {\r\n xpaths = [\r\n // `${nodeXpath1}/descendant::${nodeXpath2}`,\r\n `${nodeXpath1}/ancestor-or-self::${nodeXpath2}`,\r\n `${nodeXpath1}/preceding::${nodeXpath2}`\r\n ];\r\n }\r\n\r\n // Iterate through XPath patterns\r\n for (const xpath of xpaths) {\r\n // Check if result is already cached to avoid recomputation\r\n if (!xpathCache?.get(xpath)) {\r\n // Compute and store result in cache\r\n xpathCache.set(xpath, getCountOfXPath(xpath, targetElemt, docmt));\r\n }\r\n\r\n const count = xpathCache?.get(xpath);\r\n\r\n // Short-circuit: Return the first valid XPath result found\r\n if (count === 1) {\r\n return xpath;\r\n }\r\n if (count > 1) {\r\n if (xpathDataWithIndex.length) {\r\n if (count < xpathDataWithIndex[0].count) {\r\n xpathDataWithIndex.pop();\r\n xpathDataWithIndex.push({\r\n key: `relative xpath by unique parent ${isIndex ? \"index\" : \"\"}`,\r\n value: xpath,\r\n count\r\n });\r\n }\r\n } else {\r\n xpathDataWithIndex.push({\r\n key: `relative xpath by unique parent ${isIndex ? \"index\" : \"\"}`,\r\n value: xpath,\r\n count\r\n });\r\n }\r\n }\r\n\r\n if (count > 1 && isIndex && !xpathData.length) {\r\n // Try finding XPath with index if count is greater than 1\r\n const indexedXpath = findXpathWithIndex(\r\n xpath,\r\n targetElemt,\r\n docmt,\r\n count\r\n );\r\n\r\n // Cache the indexed XPath result\r\n if (\r\n indexedXpath &&\r\n getCountOfXPath(indexedXpath, targetElemt, docmt) === 1\r\n ) {\r\n xpathData.push({\r\n key: `relative xpath by unique parent ${isIndex ? \"index\" : \"\"}`,\r\n value: indexedXpath\r\n });\r\n }\r\n }\r\n }\r\n }\r\n return null;\r\n};\r\n\r\nconst getUniqueParentXpath = (\r\n domNode: HTMLElement,\r\n docmt: Document,\r\n node: HTMLElement | Element,\r\n isTarget: boolean,\r\n nodeXpath: string,\r\n isIndex: boolean\r\n) => {\r\n try {\r\n if (parentXpathCache.has(domNode)) {\r\n return parentXpathCache.get(domNode);\r\n }\r\n\r\n // Direct XPath construction without loops\r\n const xpathParts = [];\r\n let currentNode = domNode;\r\n\r\n while (currentNode && currentNode.nodeType === 1) {\r\n const hasUniqueAttr = false;\r\n for (const attrName of Array.from(currentNode.attributes)) {\r\n if (checkBlockedAttributes(attrName, currentNode, isTarget)) {\r\n const attrValue = sanitizeAttributeValue(\r\n attrName.name,\r\n attrName.nodeValue\r\n );\r\n const elementName = attrName.name;\r\n\r\n for (const xpathe of getXpathStrings(\r\n currentNode,\r\n elementName,\r\n attrValue\r\n )) {\r\n let othersWithAttr;\r\n\r\n // If the XPath does not parse, move to the next unique attribute\r\n try {\r\n othersWithAttr = checkRelativeXpathRelation(\r\n xpathe,\r\n nodeXpath,\r\n node,\r\n docmt,\r\n isIndex,\r\n \"parent\"\r\n );\r\n } catch (ign) {\r\n continue;\r\n }\r\n\r\n // If the attribute isn't actually unique, get it's index too\r\n if (othersWithAttr) {\r\n return othersWithAttr;\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (currentNode.textContent && !currentNode.textContent) {\r\n if (\r\n !isTarget ||\r\n (isTarget && !isNumberExist(currentNode.textContent))\r\n ) {\r\n let xpathe;\r\n\r\n if (!reWhiteSpace.test(currentNode.textContent)) {\r\n xpathe = isSvg(currentNode)\r\n ? `//*[local-name()='${\r\n currentNode.tagName\r\n }' and normalize-space(.)=${escapeCharacters(\r\n getFilteredText(currentNode)\r\n )}]`\r\n : `//${\r\n currentNode.tagName || \"*\"\r\n }[normalize-space(.)=${escapeCharacters(\r\n getFilteredText(currentNode)\r\n )}]`;\r\n } else {\r\n xpathe = isSvg(currentNode)\r\n ? `//*[local-name()='${\r\n currentNode.tagName\r\n }' and .=${escapeCharacters(getFilteredText(currentNode))}]`\r\n : `//${currentNode.tagName || \"*\"}[.=${escapeCharacters(\r\n getFilteredText(currentNode)\r\n )}]`;\r\n }\r\n\r\n const othersWithAttr = checkRelativeXpathRelation(\r\n xpathe,\r\n nodeXpath,\r\n node,\r\n docmt,\r\n isIndex,\r\n \"parent\"\r\n );\r\n if (othersWithAttr) {\r\n return othersWithAttr;\r\n }\r\n } else {\r\n const combinePattern = [];\r\n const contentRes = [\r\n ...new Set(getFilteredText(currentNode).match(/([^0-9]+)/g))\r\n ];\r\n const reWhiteSpace = new RegExp(/^[\\S]+( [\\S]+)*$/gi);\r\n if (contentRes?.length) {\r\n for (let i = 0; i < contentRes?.length; i++) {\r\n if (contentRes[i] && replaceWhiteSpaces(contentRes[i].trim())) {\r\n if (!reWhiteSpace.test(contentRes[i])) {\r\n combinePattern.push(\r\n `contains(.,${escapeCharacters(\r\n replaceWhiteSpaces(contentRes[i])\r\n ).trim()})`\r\n );\r\n } else {\r\n combinePattern.push(\r\n `contains(.,${escapeCharacters(\r\n contentRes[i].trim()\r\n ).trim()})`\r\n );\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (combinePattern?.length) {\r\n const xpathe = isSvg(currentNode)\r\n ? `//*[local-name()='${\r\n currentNode.tagName\r\n }' and ${combinePattern.join(\" and \")}]`\r\n : `//${currentNode.tagName || \"*\"}[${combinePattern.join(\r\n \" and \"\r\n )}]`;\r\n const othersWithAttr = checkRelativeXpathRelation(\r\n xpathe,\r\n nodeXpath,\r\n node,\r\n docmt,\r\n isIndex,\r\n \"parent\"\r\n );\r\n if (othersWithAttr) {\r\n return othersWithAttr;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Construct the XPath based on the tag name\r\n if (!hasUniqueAttr) {\r\n const xpathe = isSvg(currentNode)\r\n ? `/*[local-name()='${currentNode.tagName}']`\r\n : `/${currentNode.tagName}`;\r\n\r\n xpathParts.unshift(xpathe);\r\n } else {\r\n break;\r\n }\r\n\r\n // Move to the parent node for the next iteration\r\n currentNode = currentNode.parentElement!;\r\n }\r\n\r\n // Final constructed XPath\r\n const finalXPath = xpathParts.join(\"\") + nodeXpath;\r\n let count = getCountOfXPath(finalXPath, domNode, docmt);\r\n if (count === 1) {\r\n parentXpathCache.set(domNode, finalXPath); // Cache final result\r\n return finalXPath;\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n return null;\r\n }\r\n};\r\n\r\nconst getParentRelativeXpath = (\r\n domNode: HTMLElement,\r\n docmt: Document,\r\n node: HTMLElement | Element,\r\n isTarget: boolean\r\n) => {\r\n const cache = new Map(); // Cache to store computed results\r\n\r\n if (cache.has(domNode)) {\r\n return cache.get(domNode); // Return cached result if available\r\n }\r\n\r\n const xpathParts = []; // Initialize an array to hold parts of the XPath\r\n let currentNode = domNode; // Start with the provided DOM node\r\n\r\n try {\r\n while (currentNode && currentNode.nodeType === 1) {\r\n // BASE CASE #1: If this isn't an element, we're above the root, return empty string\r\n if (!currentNode.tagName) {\r\n return \"\";\r\n }\r\n\r\n // BASE CASE #2: Check for unique attributes\r\n let uniqueAttrFound = false;\r\n for (const attr of Array.from(currentNode.attributes)) {\r\n if (checkBlockedAttributes(attr, currentNode, isTarget)) {\r\n const attrValue = sanitizeAttributeValue(attr.name, attr.nodeValue);\r\n const elementName = attr.name;\r\n\r\n for (const xpathe of getXpathStrings(\r\n currentNode,\r\n elementName,\r\n attrValue\r\n )) {\r\n let othersWithAttr;\r\n\r\n // If the XPath does not parse, move to the next unique attribute\r\n try {\r\n othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);\r\n } catch (ign) {\r\n continue;\r\n }\r\n\r\n // If the attribute is unique, return its XPath\r\n if (othersWithAttr === 1) {\r\n xpathParts.unshift(xpathe);\r\n uniqueAttrFound = true; // Mark that we found at least one unique attribute\r\n break;\r\n }\r\n }\r\n }\r\n\r\n if (uniqueAttrFound) break;\r\n }\r\n\r\n // If no unique attributes, check for text content\r\n if (!uniqueAttrFound && currentNode.textContent && !node.textContent) {\r\n const textXPath = getFilteredTextXPath(currentNode, docmt);\r\n if (textXPath) {\r\n const othersWithAttr = getCountOfXPath(textXPath, currentNode, docmt);\r\n if (othersWithAttr === 1) {\r\n uniqueAttrFound = true; // Mark that we found at least one unique attribute\r\n xpathParts.unshift(textXPath);\r\n break;\r\n }\r\n }\r\n }\r\n\r\n if (!uniqueAttrFound) {\r\n // Construct the XPath based on the tag name\r\n const xpathe = isSvg(currentNode)\r\n ? `/*[local-name()='${currentNode.tagName}']`\r\n : `/${currentNode.tagName}`;\r\n\r\n // Prepend the current XPath part to the array\r\n xpathParts.unshift(xpathe);\r\n } else {\r\n break;\r\n }\r\n // Move to the parent node for the next iteration\r\n currentNode = currentNode.parentElement!;\r\n }\r\n\r\n // Combine all parts into the final XPath\r\n const finalXpath = xpathParts.join(\"\");\r\n cache.set(domNode, finalXpath); // Store result in cache\r\n return finalXpath;\r\n } catch (error) {\r\n console.log(error);\r\n return null;\r\n }\r\n};\r\n\r\nconst getChildRelativeXpath = (\r\n domNode: HTMLElement | Element,\r\n docmt: Document,\r\n node: HTMLElement | Element\r\n) => {\r\n const xpathParts = []; // Initialize an array to hold parts of the XPath.\r\n let currentNode: HTMLElement | Element | null;\r\n\r\n const st = [];\r\n if (\r\n domNode.firstElementChild != null &&\r\n domNode.firstElementChild.classList.contains(\"flntooltip\")\r\n ) {\r\n st.unshift(domNode.firstElementChild);\r\n } else if (domNode.nextElementSibling != null)\r\n st.unshift(domNode.nextElementSibling);\r\n\r\n for (\r\n let m = domNode.parentElement;\r\n m != null && m.nodeType === 1;\r\n m = m.parentElement\r\n ) {\r\n if (m.nextElementSibling) st.unshift(m.nextElementSibling);\r\n }\r\n\r\n try {\r\n do {\r\n let uniqueAttrFound = false;\r\n for (currentNode = st.pop()!; currentNode !== null; ) {\r\n for (const attr of Array.from(currentNode.attributes)) {\r\n if (checkBlockedAttributes(attr, currentNode, true)) {\r\n const attrValue = sanitizeAttributeValue(attr.name, attr.nodeValue);\r\n const elementName = attr.name;\r\n\r\n for (const xpathe of getXpathStrings(\r\n currentNode,\r\n elementName,\r\n attrValue\r\n )) {\r\n let othersWithAttr;\r\n\r\n // If the XPath does not parse, move to the next unique attribute\r\n try {\r\n othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);\r\n } catch (ign) {\r\n continue;\r\n }\r\n\r\n // If the attribute is unique, return its XPath\r\n if (othersWithAttr === 1) {\r\n uniqueAttrFound = true; // Mark that we found at least one unique attribute\r\n xpathParts.push(xpathe);\r\n break;\r\n }\r\n }\r\n }\r\n\r\n if (uniqueAttrFound) break;\r\n }\r\n\r\n // If no unique attributes, check for text content\r\n if (!uniqueAttrFound && currentNode.textContent && !node.textContent) {\r\n const textXPath = getFilteredTextXPath(currentNode, docmt);\r\n if (textXPath) {\r\n const othersWithAttr = getCountOfXPath(\r\n textXPath,\r\n currentNode,\r\n docmt\r\n );\r\n if (othersWithAttr === 1) {\r\n uniqueAttrFound = true; // Mark that we found at least one unique attribute\r\n xpathParts.push(textXPath);\r\n break;\r\n }\r\n }\r\n }\r\n\r\n if (!uniqueAttrFound) {\r\n // Construct the XPath based on the tag name\r\n const xpathe = isSvg(currentNode)\r\n ? `/*[local-name()='${currentNode.tagName}']`\r\n : `/${currentNode.tagName}`;\r\n\r\n // Prepend the current XPath part to the array\r\n xpathParts.push(xpathe);\r\n\r\n if (currentNode.firstElementChild != null) {\r\n st.push(currentNode.nextElementSibling);\r\n currentNode = currentNode.firstElementChild;\r\n } else {\r\n currentNode = currentNode.nextElementSibling;\r\n }\r\n } else {\r\n break;\r\n }\r\n }\r\n } while (st.length > 0);\r\n\r\n // Combine all parts into the final XPath\r\n const finalXpath = xpathParts.join(\"\");\r\n cache.set(domNode, finalXpath); // Store result in cache\r\n return finalXpath;\r\n } catch (error) {\r\n console.log(error);\r\n return null;\r\n }\r\n};\r\n\r\nconst getSiblingRelativeXPath = (\r\n domNode: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean,\r\n nodeXpath: string\r\n) => {\r\n try {\r\n const markedSpan = document.querySelector(\".flntooltip\");\r\n\r\n for (\r\n let m = domNode.nextElementSibling;\r\n m !== null && m !== markedSpan;\r\n m = m.nextElementSibling\r\n ) {\r\n processSibling(\r\n m,\r\n domNode,\r\n docmt,\r\n nodeXpath,\r\n \"preceding-sibling\",\r\n isTarget\r\n );\r\n }\r\n\r\n for (\r\n let n = domNode.previousElementSibling;\r\n n !== null && n !== markedSpan;\r\n n = n.previousElementSibling\r\n ) {\r\n processSibling(\r\n n,\r\n domNode,\r\n docmt,\r\n nodeXpath,\r\n \"following-sibling\",\r\n isTarget\r\n );\r\n }\r\n } catch (error) {\r\n console.error(\"sibling error\", error);\r\n return null;\r\n }\r\n};\r\n\r\nconst processSibling = (\r\n sibling: Element,\r\n domNode: HTMLElement | Element,\r\n docmt: Document,\r\n nodeXpath: string,\r\n axis: string,\r\n isTarget: boolean\r\n) => {\r\n try {\r\n if (sibling.hasAttributes()) {\r\n for (const attr of Array.from(sibling.attributes)) {\r\n const xpaths = intermediateXpathSteps(\r\n sibling,\r\n {\r\n name: attr.name,\r\n value: attr.value\r\n },\r\n isTarget\r\n );\r\n\r\n for (let xpathe of xpaths) {\r\n xpathe += `/${axis}::${nodeXpath}`;\r\n\r\n const count = getCountOfXPath(xpathe, sibling, docmt);\r\n\r\n if (count === 1) {\r\n xpathData.push({\r\n key: `xpath by ${axis}`,\r\n value: xpathe\r\n });\r\n return;\r\n } else if (count > 1) {\r\n if (xpathDataWithIndex.length) {\r\n if (count < xpathDataWithIndex[0].count) {\r\n xpathDataWithIndex.pop();\r\n xpathDataWithIndex.push({\r\n key: `relative xpath by ${axis}`,\r\n value: xpathe,\r\n count\r\n });\r\n }\r\n } else {\r\n xpathDataWithIndex.push({\r\n key: `relative xpath by ${axis}`,\r\n value: xpathe,\r\n count\r\n });\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (!isTarget) {\r\n let xpathe;\r\n xpathe = intermediateXpathStep(\r\n sibling,\r\n {\r\n name: \"text\",\r\n value: sibling.textContent\r\n },\r\n isTarget\r\n );\r\n\r\n if (xpathe) {\r\n const count = getCountOfXPath(xpathe, sibling, docmt);\r\n\r\n if (count === 1) {\r\n xpathData.push({\r\n key: `xpath by ${axis}`,\r\n value: xpathe\r\n });\r\n return;\r\n } else if (count > 1) {\r\n if (xpathDataWithIndex.length) {\r\n if (count < xpathDataWithIndex[0].count) {\r\n xpathDataWithIndex.pop();\r\n xpathDataWithIndex.push({\r\n key: `relative xpath by ${axis}`,\r\n value: xpathe,\r\n count\r\n });\r\n }\r\n } else {\r\n xpathDataWithIndex.push({\r\n key: `relative xpath by ${axis}`,\r\n value: xpathe,\r\n count\r\n });\r\n }\r\n }\r\n }\r\n }\r\n } catch (error) {\r\n console.log(`${axis} xpath-error`, error);\r\n }\r\n};\r\n\r\nfunction getXPathUsingAttributeAndText(\r\n attributes: Attr[],\r\n targetElemt: HTMLElement | Element,\r\n docmt: Document,\r\n isTarget: boolean\r\n) {\r\n const { tagName } = targetElemt;\r\n const textContent = targetElemt.textContent.trim();\r\n if (!textContent) {\r\n return;\r\n }\r\n const normalizedTextContent = replaceWhiteSpaces(textContent);\r\n const stableTargetText = getStableTargetText(normalizedTextContent);\r\n if (hasNumericAttributeValue(normalizedTextContent) && !stableTargetText) {\r\n return;\r\n }\r\n\r\n const textForPredicate = hasNumericAttributeValue(normalizedTextContent)\r\n ? stableTargetText\r\n : normalizedTextContent;\r\n const containerTextCondition = getContainerTextCondition(\r\n targetElemt,\r\n textForPredicate\r\n );\r\n for (const attrName of attributes) {\r\n if (checkBlockedAttributes(attrName, targetElemt, isTarget)) {\r\n let attrValue = sanitizeAttributeValue(attrName.name, attrName.nodeValue);\r\n const elementName = attrName.name;\r\n const textCondition = containerTextCondition\r\n ? containerTextCondition\r\n : hasNumericAttributeValue(normalizedTextContent)\r\n ? `contains(normalize-space(.),${escapeCharacters(textForPredicate)})`\r\n : /\\s/.test(textContent)\r\n ? `normalize-space(.)=${escapeCharacters(textForPredicate)}`\r\n : `text()=${escapeCharacters(textForPredicate)}`;\r\n const xpath = `//${tagName}[@${elementName}='${attrValue}' and ${textCondition}]`;\r\n if (xpath) {\r\n const count = getCountOfXPath(xpath, targetElemt, docmt);\r\n if (count == 1) {\r\n return xpath;\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\nconst addRelativeXpaths = (\r\n targetElemt: HTMLElement | Element,\r\n docmt: Document,\r\n isIndex: boolean,\r\n isTarget: boolean,\r\n attribute: Attr[]\r\n) => {\r\n try {\r\n let nodeXpath: string[] = [];\r\n let relativeXpath, relativeChildXpath;\r\n xpathData = [];\r\n\r\n console.log(attribute);\r\n if (attribute) {\r\n for (const attrName of attribute) {\r\n const expressions = intermediateXpathSteps(\r\n targetElemt,\r\n {\r\n name: attrName.name,\r\n value: attrName.value\r\n },\r\n isTarget\r\n );\r\n\r\n console.log(expressions[0] || \"\");\r\n for (const expression of expressions) {\r\n if (expression) {\r\n nodeXpath.push(expression);\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (targetElemt.textContent) {\r\n let expression = intermediateXpathStep(\r\n targetElemt,\r\n {\r\n name: \"text\",\r\n value: targetElemt.textContent\r\n },\r\n isTarget\r\n );\r\n\r\n console.log(expression);\r\n if (expression) {\r\n nodeXpath.push(expression);\r\n }\r\n }\r\n\r\n nodeXpath.push(targetElemt.tagName);\r\n\r\n if (nodeXpath?.length) {\r\n for (let i = 0; i < nodeXpath.length; i++) {\r\n if (!xpathData.length) {\r\n getSiblingRelativeXPath(targetElemt, docmt, isTarget, nodeXpath[i]);\r\n\r\n if (!xpathData.length) {\r\n if (!relativeXpath) {\r\n relativeXpath = getParentRelativeXpath(\r\n targetElemt.parentElement!,\r\n docmt,\r\n targetElemt,\r\n isTarget\r\n );\r\n }\r\n\r\n console.log(relativeXpath);\r\n\r\n if (\r\n relativeXpath &&\r\n (relativeXpath.includes(\"@\") ||\r\n relativeXpath.includes(\"text()\") ||\r\n relativeXpath.includes(\".=\")) &&\r\n relativeXpath.match(/\\//g)?.length - 2 < 5\r\n ) {\r\n const fullRelativeXpath = relativeXpath + `/${nodeXpath[i]}`;\r\n const count = getCountOfXPath(\r\n fullRelativeXpath,\r\n targetElemt,\r\n docmt\r\n );\r\n\r\n if (count === 1) {\r\n xpathData.push({\r\n key: \"relative xpath by relative parent\",\r\n value: fullRelativeXpath\r\n });\r\n } else if (count > 1 && isIndex) {\r\n const relativeXpathIndex = findXpathWithIndex(\r\n fullRelativeXpath,\r\n targetElemt,\r\n docmt,\r\n count\r\n );\r\n if (\r\n relativeXpathIndex &&\r\n getCountOfXPath(relativeXpathIndex, targetElemt, docmt) === 1\r\n ) {\r\n xpathData.push({\r\n key: `relative xpath by relative parent ${\r\n isIndex ? \"index\" : \"\"\r\n }`,\r\n value: relativeXpathIndex\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (!xpathData.length) {\r\n if (!relativeChildXpath) {\r\n relativeChildXpath = getChildRelativeXpath(\r\n targetElemt,\r\n docmt,\r\n targetElemt\r\n );\r\n }\r\n\r\n if (\r\n relativeChildXpath &&\r\n (relativeChildXpath.includes(\"@\") ||\r\n relativeChildXpath.includes(\"text()\") ||\r\n relativeChildXpath.includes(\".=\"))\r\n ) {\r\n // Preserve the step separator between the target descriptor and child tail.\r\n const childRelativeXpath = relativeChildXpath.replace(/^\\/+/, \"\");\r\n const fullRelativeXpath = `/${nodeXpath[i]}/${childRelativeXpath}`;\r\n const count = getCountOfXPath(\r\n fullRelativeXpath,\r\n targetElemt,\r\n docmt\r\n );\r\n\r\n if (count === 1) {\r\n xpathData.push({\r\n key: \"relative xpath by relative child\",\r\n value: fullRelativeXpath\r\n });\r\n } else if (count > 1 && isIndex) {\r\n const relativeXpathIndex = findXpathWithIndex(\r\n fullRelativeXpath,\r\n targetElemt,\r\n docmt,\r\n count\r\n );\r\n if (\r\n relativeXpathIndex &&\r\n getCountOfXPath(relativeXpathIndex, targetElemt, docmt) === 1\r\n ) {\r\n xpathData.push({\r\n key: `relative xpath by relative parent ${\r\n isIndex ? \"index\" : \"\"\r\n }`,\r\n value: relativeXpathIndex\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (\r\n xpathData?.length === 1 &&\r\n xpathData?.[0]?.value?.match(/\\[([0-9]+)\\]/gm)?.length! > 3 &&\r\n !referenceElementMode\r\n ) {\r\n if (targetElemt.textContent) {\r\n const txtXpath = getTextXPath(targetElemt, docmt, isIndex, false);\r\n if (txtXpath) {\r\n xpathData.unshift({\r\n key: `xpath by text${isIndex ? \"index\" : \"\"}`,\r\n value: txtXpath\r\n });\r\n }\r\n }\r\n }\r\n\r\n if (!xpathData.length) {\r\n let tempRelativeXpath = getUniqueParentXpath(\r\n targetElemt.parentElement!,\r\n docmt,\r\n targetElemt,\r\n isTarget,\r\n nodeXpath[i],\r\n isIndex\r\n );\r\n\r\n if (tempRelativeXpath) {\r\n xpathData.push({\r\n key: \"xpath by unique parent\",\r\n value: tempRelativeXpath\r\n });\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return xpathData;\r\n } catch (error) {\r\n console.log(error);\r\n }\r\n};\r\n\r\nexport const attributesBasedXPath = (\r\n attr: Attr,\r\n targetElemt: HTMLElement | Element,\r\n docmt: Document,\r\n isIndex: boolean,\r\n isTarget: boolean\r\n) => {\r\n let attrName;\r\n\r\n attrName = attr.name;\r\n // Strict numeric-attribute validation for direct callers.\r\n if (!checkBlockedAttributes(attr, targetElemt, isTarget)) {\r\n return;\r\n }\r\n\r\n let xpath = getPropertyXPath(\r\n targetElemt,\r\n docmt,\r\n `@${attrName}`,\r\n attr.value,\r\n isIndex,\r\n isTarget\r\n );\r\n\r\n return xpath;\r\n};\r\n\r\nexport const getUniqueClassName = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n isIndex: boolean,\r\n isTarget: boolean\r\n) => {\r\n let value = element.className;\r\n if (typeof value !== \"string\") {\r\n value = \"\";\r\n }\r\n value = value?.replace(\"flndisabled\", \"disabled\");\r\n value = sanitizeAttributeValue(\"class\", value);\r\n value = value?.trim();\r\n\r\n if (\r\n value &&\r\n checkBlockedAttributes({ name: \"class\", value }, element, isTarget)\r\n ) {\r\n return getPropertyXPath(element, docmt, `@class`, value, isIndex, isTarget);\r\n }\r\n};\r\n\r\nexport const getTextXPath = (\r\n element: HTMLElement | Element,\r\n docmt: Document,\r\n isIndex: boolean,\r\n isTarget: boolean\r\n) => {\r\n if ((element.textContent ?? \"\").trim() != \"\") {\r\n const text = getTextContent(element);\r\n\r\n if (text) {\r\n return (\r\n getPropertyXPath(element, docmt, \"text()\", text, isIndex, isTarget) ||\r\n getPropertyXPath(element, docmt, \".\", text, isIndex, isTarget)\r\n );\r\n }\r\n }\r\n};\r\n\r\nconst addAllXPathAttributes = (\r\n attributes: Attr[],\r\n targetElemt: HTMLElement | Element,\r\n docmt: Document,\r\n isIndex: boolean,\r\n isTarget: boolean\r\n) => {\r\n const attributesArray = attributes;\r\n try {\r\n attributesArray.map((attr) => {\r\n if (!(attr.name === \"className\" || attr.name === \"class\")) {\r\n // Strict numeric-attribute validation is enforced inside\r\n // checkBlockedAttributes before candidate XPath creation.\r\n if (checkBlockedAttributes(attr, targetElemt, isTarget)) {\r\n const xpth = attributesBasedXPath(\r\n attr,\r\n targetElemt,\r\n docmt,\r\n isIndex,\r\n isTarget\r\n );\r\n if (xpth) {\r\n xpathData.push({\r\n key: `xpath by ${attr.name}${isIndex ? \" index\" : \"\"}`,\r\n value: xpth\r\n });\r\n }\r\n }\r\n }\r\n });\r\n\r\n const txtXpath = getTextXPath(targetElemt, docmt, isIndex, isTarget);\r\n if (txtXpath) {\r\n xpathData.push({\r\n key: `xpath by text${isIndex ? \" index\" : \"\"}`,\r\n value: txtXpath\r\n });\r\n }\r\n\r\n if (\r\n attributesArray.find((element) =>\r\n [\"className\", \"class\"].includes(element.name)\r\n ) &&\r\n checkBlockedAttributes(\r\n attributesArray?.find((element) =>\r\n [\"className\", \"class\"].includes(element.name)\r\n )!,\r\n targetElemt,\r\n isTarget\r\n )\r\n ) {\r\n let xpath = getUniqueClassName(targetElemt, docmt, isIndex, isTarget);\r\n if (xpath) {\r\n xpathData.push({\r\n key: \"xpath by class\",\r\n value: xpath\r\n });\r\n }\r\n }\r\n\r\n if (!xpathData.length && attributesArray.length > 1) {\r\n const combinationXpath = getAttributeCombinationXpath(\r\n targetElemt,\r\n docmt,\r\n attributesArray,\r\n isTarget\r\n );\r\n if (combinationXpath)\r\n xpathData.push({\r\n key: \"xpath by combination\",\r\n value: combinationXpath\r\n });\r\n }\r\n\r\n if (!xpathData.length) {\r\n const textAttribute = getXPathUsingAttributeAndText(\r\n attributes,\r\n targetElemt,\r\n docmt,\r\n isTarget\r\n );\r\n if (textAttribute)\r\n xpathData.push({\r\n key: \"xpath by textAttribute\",\r\n value: textAttribute\r\n });\r\n }\r\n } catch (error) {\r\n console.log(error);\r\n }\r\n};\r\n\r\nexport const parseDOM = (\r\n element: HTMLElement | Element,\r\n doc: Document,\r\n isIndex: boolean,\r\n isTarget: boolean,\r\n includedAttributes: Attr[] = [],\r\n strategies: string[] = []\r\n) => {\r\n xpathData = [];\r\n console.log(element);\r\n const targetElemt = element;\r\n const rootNode = targetElemt?.getRootNode?.();\r\n const isShadowScoped = rootNode?.nodeType === Node.DOCUMENT_FRAGMENT_NODE;\r\n const docmt = (\r\n isShadowScoped ? rootNode : targetElemt?.ownerDocument || doc\r\n ) as Document;\r\n const tag = targetElemt.tagName;\r\n const { attributes } = targetElemt;\r\n const attributesToUse =\r\n includedAttributes.length > 0 ? includedAttributes : Array.from(attributes);\r\n addAllXPathAttributes(attributesToUse, targetElemt, docmt, isIndex, isTarget);\r\n\r\n if (strategies.length) {\r\n // Explicit index strategy should preserve positional candidates even when\r\n // isIndex is false.\r\n const allowPositionalIndex = isIndex || includesIndexStrategy(strategies);\r\n const strategyXpaths = strategies.flatMap((strategy) =>\r\n buildStrategyXpaths(targetElemt, docmt, isTarget, strategy, xpathData)\r\n );\r\n\r\n xpathData = getUniqueXpathEntries(\r\n strategyXpaths.filter(\r\n (xpath) => getCountOfXPath(xpath.value, targetElemt, docmt) === 1\r\n )\r\n );\r\n\r\n return removePositionalIndexXpaths(xpathData, allowPositionalIndex);\r\n }\r\n\r\n if (!referenceElementMode) {\r\n if (xpathData.length) {\r\n const len = xpathData.length;\r\n for (let i = 0; i < len; i++) {\r\n let xpth = xpathData[i].value;\r\n xpth = \"//*\" + xpth.substring(xpth.indexOf(\"//\") + 2 + tag.length);\r\n const count = getCountOfXPath(xpth, element, docmt);\r\n if (count === 1) {\r\n xpathData.push({\r\n key: `${xpathData[i].key} regex`,\r\n value: xpth\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (!xpathData.length && !isShadowScoped) {\r\n xpathData = buildAxesStrategyXpaths(targetElemt, docmt, isTarget, isIndex);\r\n }\r\n\r\n return removePositionalIndexXpaths(getUniqueXpathEntries(xpathData), isIndex);\r\n};\r\n\r\nconst xpath = {\r\n parseDOM,\r\n getTextXPath,\r\n getUniqueClassName,\r\n attributesBasedXPath,\r\n addAllXPathAttributes,\r\n addRelativeXpaths,\r\n getXPathUsingAttributeAndText,\r\n getSiblingRelativeXPath,\r\n getChildRelativeXpath,\r\n getParentRelativeXpath,\r\n getUniqueParentXpath,\r\n checkRelativeXpathRelation,\r\n buildAxesStrategyXpaths\r\n};\r\n\r\nexport default xpath;\r\n","import { SelectorMode } from \"../types/locator.ts\";\r\nimport {\r\n checkBlockedAttributes,\r\n getStableClassTokens,\r\n modifiedElementAttributes\r\n} from \"./xpathHelpers.ts\";\r\n\r\nconst PREFERRED_ATTRS = [\r\n \"data-testid\",\r\n \"data-test-id\",\r\n \"data-test\",\r\n \"data-cy\",\r\n \"data-qa\",\r\n \"name\",\r\n \"aria-label\",\r\n \"aria-labelledby\",\r\n \"placeholder\",\r\n \"title\",\r\n \"alt\",\r\n \"role\"\r\n];\r\n\r\nconst EXCLUDED_ATTRS = new Set([\"id\", \"class\", \"style\"]);\r\n\r\nconst escapeCssAttributeValue = (value: string): string => {\r\n return String(value).replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\r\n};\r\n\r\nconst escapeCssIdentifier = (value: string, el?: Element): string => {\r\n const css = el?.ownerDocument?.defaultView?.CSS ?? globalThis.CSS;\r\n if (css?.escape) return css.escape(value);\r\n\r\n return String(value).replace(/[^a-zA-Z0-9_-]/g, \"\\\\$&\");\r\n};\r\n\r\nconst isElementNode = (el: Element | null | undefined): el is Element => {\r\n if (!el) return false;\r\n const view = el.ownerDocument?.defaultView;\r\n return Boolean(view && el instanceof view.Element);\r\n};\r\n\r\nconst isIgnoredTag = (el: Element): boolean => {\r\n const tagName = el.tagName.toLowerCase();\r\n return tagName === \"style\" || tagName === \"script\";\r\n};\r\n\r\nconst isStableAttributeValue = (value: string): boolean => {\r\n const trimmed = value.trim();\r\n return Boolean(trimmed) && trimmed.length <= 120 && !/^\\d+$/.test(trimmed);\r\n};\r\n\r\nconst isCssSelectorUnique = (\r\n selector: string,\r\n el: Element,\r\n root: Document | ShadowRoot | ParentNode\r\n): boolean => {\r\n try {\r\n const matches = root.querySelectorAll(selector);\r\n return matches.length === 1 && matches[0] === el;\r\n } catch {\r\n return false;\r\n }\r\n};\r\n\r\nconst addUniqueSelector = (\r\n selectors: { key: string; value: string }[],\r\n key: string,\r\n value: string | undefined\r\n) => {\r\n if (!value || selectors.some((selector) => selector.value === value)) return;\r\n selectors.push({ key, value });\r\n};\r\n\r\nexport const parseCssSelectors = (\r\n el: Element,\r\n mode: SelectorMode = \"single\"\r\n) => {\r\n const selectors: { key: string; value: string }[] = [];\r\n const root = el.getRootNode() as Document | ShadowRoot;\r\n\r\n try {\r\n // Step 1: prefer the target element's own stable id.\r\n const idPath = getIdCssPath(el);\r\n if (idPath && isCssSelectorUnique(idPath, el, root)) {\r\n addUniqueSelector(selectors, \"cssSelector by id\", idPath);\r\n if (mode === \"single\") return selectors;\r\n }\r\n\r\n // Step 2: try stable direct attributes such as data-testid, name, or aria-label.\r\n const attributePath = getAttributeCssPath(el);\r\n if (attributePath) {\r\n addUniqueSelector(selectors, \"cssSelector by attribute\", attributePath);\r\n if (mode === \"single\") return selectors;\r\n }\r\n\r\n // Step 3: use stable class tokens only when they uniquely identify the target.\r\n const classPath = getClassCssPath(el);\r\n if (classPath && isCssSelectorUnique(classPath, el, root)) {\r\n addUniqueSelector(selectors, \"cssSelector by class\", classPath);\r\n if (mode === \"single\") return selectors;\r\n }\r\n\r\n // Step 4: if the direct selector is not enough, scope it with a stable parent.\r\n const scopedPath = getScopedStableCssPath(el);\r\n if (scopedPath && isCssSelectorUnique(scopedPath, el, root)) {\r\n addUniqueSelector(selectors, \"cssSelector by stable parent\", scopedPath);\r\n if (mode === \"single\") return selectors;\r\n }\r\n\r\n // Step 5: use positional selectors only as the last fallback.\r\n if (mode === \"multiple\" && selectors.length === 0) {\r\n const positionalPath = getShortestPositionalCssPath(el);\r\n if (positionalPath && isCssSelectorUnique(positionalPath, el, root)) {\r\n addUniqueSelector(\r\n selectors,\r\n \"cssSelector by position fallback\",\r\n positionalPath\r\n );\r\n }\r\n }\r\n } catch (e) {\r\n console.error(e);\r\n }\r\n return selectors;\r\n};\r\n\r\nexport const getIdCssPath = (el: HTMLElement | Element) => {\r\n if (!isElementNode(el) || isIgnoredTag(el)) return;\r\n\r\n const idAttribute = el.getAttributeNode(\"id\");\r\n if (\r\n !idAttribute ||\r\n !isStableAttributeValue(idAttribute.value) ||\r\n !checkBlockedAttributes(idAttribute, el, true)\r\n ) {\r\n return;\r\n }\r\n\r\n return `#${escapeCssIdentifier(el.id, el)}`;\r\n};\r\n\r\nfunction getAttributeSelectors(el: Element, preferredOnly = false): string[] {\r\n const attributes = Array.from(el.attributes).filter((attr) => {\r\n const attrName = attr.name.toLowerCase();\r\n if (EXCLUDED_ATTRS.has(attrName)) return false;\r\n if (preferredOnly && !PREFERRED_ATTRS.includes(attrName)) return false;\r\n return (\r\n isStableAttributeValue(attr.value) &&\r\n checkBlockedAttributes(attr, el, true)\r\n );\r\n });\r\n\r\n attributes.sort((a, b) => {\r\n const aIndex = PREFERRED_ATTRS.indexOf(a.name.toLowerCase());\r\n const bIndex = PREFERRED_ATTRS.indexOf(b.name.toLowerCase());\r\n return (aIndex === -1 ? 999 : aIndex) - (bIndex === -1 ? 999 : bIndex);\r\n });\r\n\r\n return attributes.map(\r\n (attr) => `[${attr.name}=\"${escapeCssAttributeValue(attr.value.trim())}\"]`\r\n );\r\n}\r\n\r\nexport const getAttributeCssPath = (el: Element): string | undefined => {\r\n if (!isElementNode(el) || isIgnoredTag(el)) return;\r\n\r\n const root = el.getRootNode() as ParentNode;\r\n const tag = el.tagName.toLowerCase();\r\n\r\n const attrSelectors = getAttributeSelectors(el);\r\n\r\n for (const attrSelector of attrSelectors) {\r\n if (isCssSelectorUnique(attrSelector, el, root)) {\r\n return attrSelector;\r\n }\r\n\r\n const candidate = `${tag}${attrSelector}`;\r\n\r\n if (isCssSelectorUnique(candidate, el, root)) {\r\n return candidate;\r\n }\r\n }\r\n return;\r\n};\r\n\r\nexport const getClassCssPath = (el: HTMLElement | Element) => {\r\n if (!isElementNode(el) || isIgnoredTag(el)) return;\r\n\r\n const classSelector = getClassSelector(el);\r\n if (!classSelector) return;\r\n\r\n const root = el.getRootNode() as ParentNode;\r\n const tagSelector = `${el.tagName.toLowerCase()}${classSelector}`;\r\n\r\n if (isCssSelectorUnique(classSelector, el, root)) return classSelector;\r\n if (isCssSelectorUnique(tagSelector, el, root)) return tagSelector;\r\n\r\n return;\r\n};\r\n\r\nconst getClassSelector = (el: Element): string | undefined => {\r\n if (\r\n typeof el.className !== \"string\" ||\r\n !el.className ||\r\n modifiedElementAttributes?.find(\r\n (x) => x.element === el && x.attributeName === \"class\"\r\n )\r\n ) {\r\n return;\r\n }\r\n\r\n const classTokens = getStableClassTokens(el, el.className).filter(\r\n isStableAttributeValue\r\n );\r\n\r\n if (!classTokens.length) return;\r\n\r\n return classTokens\r\n .slice(0, 3)\r\n .map((className) => `.${escapeCssIdentifier(className, el)}`)\r\n .join(\"\");\r\n};\r\n\r\nconst getDirectStableSelectors = (\r\n el: Element,\r\n options: { includeTag?: boolean; preferredAttributesOnly?: boolean } = {}\r\n): string[] => {\r\n if (!isElementNode(el) || isIgnoredTag(el)) return [];\r\n\r\n const selectors: string[] = [];\r\n const tag = el.tagName.toLowerCase();\r\n\r\n const idSelector = getIdCssPath(el);\r\n if (idSelector) {\r\n selectors.push(idSelector);\r\n }\r\n\r\n for (const attrSelector of getAttributeSelectors(\r\n el,\r\n options.preferredAttributesOnly\r\n )) {\r\n selectors.push(attrSelector);\r\n if (options.includeTag) selectors.push(`${tag}${attrSelector}`);\r\n }\r\n\r\n const classSelector = getClassSelector(el);\r\n if (classSelector) {\r\n selectors.push(classSelector);\r\n if (options.includeTag) selectors.push(`${tag}${classSelector}`);\r\n }\r\n\r\n return Array.from(new Set(selectors));\r\n};\r\n\r\nconst getScopedStableCssPath = (el: Element): string | undefined => {\r\n if (!isElementNode(el) || isIgnoredTag(el)) return;\r\n\r\n const root = el.getRootNode() as ParentNode;\r\n const childSelectors = getDirectStableSelectors(el, {\r\n includeTag: true,\r\n preferredAttributesOnly: false\r\n });\r\n\r\n let parent = el.parentElement;\r\n\r\n while (parent && isElementNode(parent) && !isIgnoredTag(parent)) {\r\n const parentSelectors = getDirectStableSelectors(parent, {\r\n includeTag: true,\r\n preferredAttributesOnly: true\r\n });\r\n\r\n for (const parentSelector of parentSelectors) {\r\n if (!isCssSelectorUnique(parentSelector, parent, root)) continue;\r\n\r\n for (const childSelector of childSelectors) {\r\n const scopedSelector = `${parentSelector} ${childSelector}`;\r\n if (isCssSelectorUnique(scopedSelector, el, root)) return scopedSelector;\r\n\r\n const directScopedSelector = `${parentSelector} > ${childSelector}`;\r\n if (isCssSelectorUnique(directScopedSelector, el, root)) {\r\n return directScopedSelector;\r\n }\r\n }\r\n }\r\n\r\n parent = parent.parentElement;\r\n }\r\n\r\n return;\r\n};\r\n\r\nconst getNthOfType = (el: Element): number => {\r\n let index = 1;\r\n let sibling = el.previousElementSibling;\r\n\r\n while (sibling) {\r\n if (sibling.tagName === el.tagName) index++;\r\n sibling = sibling.previousElementSibling;\r\n }\r\n\r\n return index;\r\n};\r\n\r\nconst getShortestPositionalCssPath = (el: Element): string | undefined => {\r\n if (!isElementNode(el) || isIgnoredTag(el)) return;\r\n\r\n const root = el.getRootNode() as ParentNode;\r\n const tag = el.tagName.toLowerCase();\r\n\r\n let parent = el.parentElement;\r\n\r\n while (parent && isElementNode(parent) && !isIgnoredTag(parent)) {\r\n const parentSelectors = getDirectStableSelectors(parent, {\r\n includeTag: true,\r\n preferredAttributesOnly: true\r\n });\r\n\r\n for (const parentSelector of parentSelectors) {\r\n if (!isCssSelectorUnique(parentSelector, parent, root)) continue;\r\n\r\n const positionalSelector = `${parentSelector} > ${tag}:nth-of-type(${getNthOfType(\r\n el\r\n )})`;\r\n\r\n if (isCssSelectorUnique(positionalSelector, el, root)) {\r\n return positionalSelector;\r\n }\r\n }\r\n\r\n parent = parent.parentElement;\r\n }\r\n\r\n return getAbsoluteCssPath(el);\r\n};\r\n\r\nexport const getAbsoluteCssPath = (el: Element) => {\r\n const view = el.ownerDocument?.defaultView;\r\n if (!view || !(el instanceof view.Element)) return;\r\n\r\n const path: string[] = [];\r\n\r\n while (el && el.nodeType === view.Node.ELEMENT_NODE) {\r\n const tagName = el.tagName.toLowerCase();\r\n\r\n if (tagName === \"style\" || tagName === \"script\") return;\r\n\r\n let selector = tagName;\r\n\r\n const parent = el.parentNode;\r\n\r\n if (parent) {\r\n const siblings = Array.from(parent.children).filter(\r\n (c) => c.tagName === el.tagName\r\n );\r\n\r\n if (siblings.length > 1) {\r\n selector += `:nth-of-type(${siblings.indexOf(el) + 1})`;\r\n }\r\n }\r\n\r\n path.unshift(selector);\r\n\r\n el = el.parentElement!;\r\n }\r\n\r\n return path.join(\" > \");\r\n};\r\n\r\nexport const cssSelectors = {\r\n parseCssSelectors,\r\n getIdCssPath,\r\n getAttributeCssPath,\r\n getClassCssPath,\r\n getAbsoluteCssPath\r\n};\r\n","import { ElementRecord, Locator } from \"../types/locator.ts\";\r\nimport { parseDOM } from \"./xpath.ts\";\r\nimport { parseCssSelectors } from \"./cssSelector.ts\";\r\nimport {\r\n isNumberExist,\r\n normalizeXPath,\r\n getXPathPattern,\r\n escapeAttrValue,\r\n isUniqueInDOM,\r\n shouldUseSnapshot\r\n} from \"./xpathHelpers.ts\";\r\n\r\nconst isSameXPathStillValid = (\r\n xpath: string,\r\n docmt: Document,\r\n target: Element\r\n): boolean => {\r\n const normalized = normalizeXPath(xpath);\r\n const found = getElementFromXPath(docmt, normalized);\r\n if (found === target) return true;\r\n\r\n if (shouldUseSnapshot(normalized)) {\r\n const result = docmt.evaluate(\r\n normalized,\r\n docmt,\r\n null,\r\n XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,\r\n null\r\n );\r\n\r\n for (let i = 0; i < result.snapshotLength; i++) {\r\n if (result.snapshotItem(i) === target) return true;\r\n }\r\n }\r\n\r\n return false;\r\n};\r\n\r\nconst getElementFromCssSelector = (\r\n docmt: Document,\r\n selector: string\r\n): Element | null => {\r\n try {\r\n const found = docmt.querySelector(selector);\r\n if (found) return found;\r\n } catch (error) {\r\n console.error(\"Invalid CSS selector:\", selector, error);\r\n return null;\r\n }\r\n\r\n return getElementFromShadowRoot(docmt.body, selector);\r\n};\r\n\r\nconst isSameCssSelectorStillValid = (\r\n selector: string,\r\n docmt: Document,\r\n target: Element\r\n): boolean => {\r\n return getElementFromCssSelector(docmt, selector) === target;\r\n};\r\n\r\nconst resolveIsSelfHealed = (\r\n locName: string,\r\n oldValue: string | null | undefined,\r\n newValue: string | null | undefined\r\n): \"Y\" | null => {\r\n if (!oldValue || !newValue) return \"Y\";\r\n return oldValue === newValue ? null : \"Y\";\r\n};\r\n\r\nconst isCssSelectorLocator = (locator: { name?: string | null }): boolean => {\r\n return locator.name?.toLowerCase().includes(\"cssselector\") ?? false;\r\n};\r\n\r\ntype ElementDetails = {\r\n id?: string | null;\r\n className?: string | null;\r\n xpathByText?: string | null;\r\n xpathById?: string | null;\r\n xpathByClass?: string | null;\r\n xpathAbsolute?: string | null;\r\n xpathByName?: string | null;\r\n xpathByPlaceholder?: string | null;\r\n xpathByType?: string | null;\r\n visibleText?: string | null;\r\n relativeXpath?: string | null;\r\n [key: string]: any;\r\n};\r\n\r\nconst getElementFromShadowRoot = (\r\n el: Element | ShadowRoot,\r\n selector: string\r\n): Element | null => {\r\n // const shadowRoot = (element as HTMLElement).shadowRoot;\r\n // if (shadowRoot && !selector.includes(\"dynamic\")) {\r\n // return shadowRoot.querySelector(selector);\r\n // }\r\n\r\n const elements = Array.from(el.querySelectorAll(\"*\"));\r\n\r\n try {\r\n for (let i = 0; i < elements.length; i++) {\r\n if (elements[i].shadowRoot) {\r\n const { shadowRoot } = elements[i];\r\n if (shadowRoot) {\r\n const nestedElement = getElementFromShadowRoot(shadowRoot, selector);\r\n if (nestedElement) {\r\n return nestedElement;\r\n }\r\n if (shadowRoot && !selector.includes(\"dynamic\")) {\r\n return shadowRoot.querySelector(selector);\r\n }\r\n }\r\n }\r\n }\r\n } catch (error) {\r\n console.log(error);\r\n }\r\n return null;\r\n};\r\n\r\nconst getId = (element: Element | null): string | null => {\r\n return element?.id || null;\r\n};\r\n\r\nconst getClassName = (element: Element): string | null => {\r\n return (element as HTMLElement).className || null;\r\n};\r\n\r\nconst getVisibleText = (element: Element): string | null => {\r\n return element.textContent?.trim() || null;\r\n};\r\n\r\nconst getName = (element: Element): string | null => {\r\n const elementEl = element as HTMLElement;\r\n\r\n if (elementEl.hasAttribute(\"name\")) {\r\n const attrValue = elementEl.getAttribute(\"name\");\r\n const name = `${attrValue}`;\r\n return name || null;\r\n }\r\n return null;\r\n};\r\n\r\nconst relations: string[] = [\r\n \"/preceding-sibling\",\r\n \"/following-sibling\",\r\n \"/parent\",\r\n \"/descendant\",\r\n \"/ancestor\",\r\n \"/self\",\r\n \"/ancestor-or-self\",\r\n \"/child\",\r\n \"/preceding\",\r\n \"/following\"\r\n];\r\n\r\nfunction getElementFromXPath(docmt: Document, xpath: string): Element | null {\r\n const window = docmt.defaultView;\r\n if (!window) return null;\r\n\r\n const xpathEvaluator = new window.XPathEvaluator();\r\n const xpathResult = xpathEvaluator.evaluate(\r\n xpath,\r\n docmt,\r\n null,\r\n window.XPathResult.FIRST_ORDERED_NODE_TYPE,\r\n null\r\n );\r\n return xpathResult.singleNodeValue as Element | null;\r\n}\r\n\r\nfunction checkReferenceElementIsValid(\r\n locator: string,\r\n relation: string,\r\n docmt: Document\r\n): string | null {\r\n if (locator.includes(relation)) {\r\n const locatotSplitArray: string[] = locator.split(relation);\r\n const sourceLoc = locatotSplitArray[0].trim();\r\n const window = docmt.defaultView;\r\n if (!window) return null;\r\n if (!locator.includes(\"dynamic\")) {\r\n const xpathEvaluator = new window.XPathEvaluator();\r\n const xpathResult = xpathEvaluator.evaluate(\r\n sourceLoc,\r\n docmt,\r\n null,\r\n window.XPathResult.FIRST_ORDERED_NODE_TYPE,\r\n null\r\n );\r\n\r\n const sourceElement = xpathResult.singleNodeValue;\r\n if (sourceElement) {\r\n const xpathResultComplete = xpathEvaluator.evaluate(\r\n locator,\r\n docmt,\r\n null,\r\n window.XPathResult.FIRST_ORDERED_NODE_TYPE,\r\n null\r\n );\r\n const completeElement = xpathResultComplete.singleNodeValue;\r\n let relativeXpath: string;\r\n if (completeElement) {\r\n relativeXpath = locator;\r\n return relativeXpath;\r\n } else {\r\n console.error(\"Complete Locator is Invalid:\", locator);\r\n relativeXpath = locator;\r\n return relativeXpath;\r\n }\r\n } else {\r\n console.error(\"Source Locator Not Found:\", sourceLoc);\r\n }\r\n }\r\n }\r\n return null;\r\n}\r\n\r\nconst getElementsFromHTML = (\r\n record: ElementRecord,\r\n docmt: Document\r\n): ElementDetails | null => {\r\n const elementsToRemove = docmt.querySelectorAll(\r\n \"script, style, link[rel='stylesheet'], meta, noscript, embed, object, param, source, svg\"\r\n );\r\n\r\n if (elementsToRemove) {\r\n elementsToRemove.forEach((tag) => {\r\n (tag as Element).remove();\r\n });\r\n }\r\n\r\n const finalLocatorsSet: Set<string> = new Set();\r\n let finalLocators: any[] = [];\r\n\r\n function createLocator(base: any, overrides: Partial<any> = {}) {\r\n const oldValue = base?.value;\r\n const newValue = overrides.value ?? base?.value;\r\n const newLocator: any = {\r\n name: overrides.name ?? base?.name,\r\n type: overrides.type ?? base?.type,\r\n value: overrides.value ?? base?.value,\r\n reference: overrides.reference ?? base?.reference,\r\n status: overrides.status ?? base?.status,\r\n isRecorded: overrides.isRecorded ?? base?.isRecorded\r\n };\r\n\r\n const previousSelfHealed = base?.isSelfHealed === \"Y\";\r\n\r\n newLocator.isSelfHealed = previousSelfHealed\r\n ? \"Y\"\r\n : overrides.hasOwnProperty(\"isSelfHealed\")\r\n ? overrides.isSelfHealed\r\n : resolveIsSelfHealed(newLocator.name, oldValue, newValue);\r\n\r\n pushUniqueLocator(newLocator);\r\n }\r\n\r\n function resolveElement(\r\n ctx: Document,\r\n locator: any,\r\n selector: string\r\n ): Element | null {\r\n if (isCssSelectorLocator(locator)) {\r\n return getElementFromCssSelector(ctx, selector);\r\n } else if (locator.name.includes(\"id\") || selector.startsWith(\"#\")) {\r\n return ctx.querySelector(\"#\" + escapeAttrValue(selector));\r\n } else if (locator.name.includes(\"className\") || selector.startsWith(\".\")) {\r\n return ctx.querySelector(\".\" + selector);\r\n } else if (locator.name === \"name\") {\r\n const safeName = escapeAttrValue(selector);\r\n return ctx.querySelector(`[name=\"${safeName}\"]`);\r\n } else if (locator.name === \"tagName\") {\r\n return ctx.querySelector(selector);\r\n } else if (locator.name === \"linkText\") {\r\n return (\r\n Array.from(ctx.querySelectorAll(\"a\")).find(\r\n (a) => a.textContent?.trim() === selector\r\n ) || null\r\n );\r\n } else if (locator.name === \"partialLinkText\") {\r\n return (\r\n Array.from(ctx.querySelectorAll(\"a\")).find((a) =>\r\n a.textContent?.includes(selector)\r\n ) || null\r\n );\r\n } else if (\r\n (locator.name.includes(\"xpath\") || selector.startsWith(\"//\")) &&\r\n !locator.type.match(\"dynamic\")\r\n ) {\r\n const normalizedXPath = normalizeXPath(selector);\r\n const el = getElementFromXPath(ctx, normalizedXPath);\r\n\r\n if (el) {\r\n createLocator(locator, {\r\n value: selector,\r\n isRecorded: String(locator.isRecorded).includes(\"N\") ? \"N\" : \"Y\"\r\n });\r\n }\r\n\r\n return el;\r\n } else {\r\n return ctx.querySelector(selector);\r\n }\r\n }\r\n\r\n function findInIframes(\r\n docmt: Document,\r\n locator: any,\r\n selector: string\r\n ): Element | null {\r\n const iframes = docmt.querySelectorAll(\"iframe\");\r\n\r\n for (const iframe of iframes) {\r\n try {\r\n const iframeDoc =\r\n iframe.contentDocument || iframe.contentWindow?.document;\r\n\r\n if (!iframeDoc) continue;\r\n\r\n const el = resolveElement(iframeDoc, locator, selector);\r\n if (el) return el;\r\n } catch {\r\n continue;\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n function pushUniqueLocator(obj: any) {\r\n const key = `${obj.name}:${obj.value}`;\r\n if (!finalLocatorsSet.has(key)) {\r\n finalLocatorsSet.add(key);\r\n finalLocators.push(obj);\r\n }\r\n }\r\n\r\n /** Locator Value Cleaner (Handles Special Scenarios) **/\r\n const cleanLocatorValue = (\r\n val: string | null | undefined,\r\n type?: string,\r\n isRecorded?: string\r\n ): string | null => {\r\n if (!val) return null;\r\n\r\n let cleaned = val.trim();\r\n\r\n // Return null for empty or literal \"null\"\r\n if (!cleaned || cleaned.toLowerCase() === \"null\") return null;\r\n\r\n // Unescape any escaped quotes\r\n cleaned = cleaned.replace(/\\\\\"/g, '\"').replace(/\\\\'/g, \"'\");\r\n\r\n // Remove surrounding single or double quotes\r\n cleaned = cleaned.replace(/^['\"](.+?)['\"]$/, \"$1\");\r\n\r\n // Replace double single quotes with a single quote inside XPath\r\n cleaned = cleaned.replace(/''/g, \"'\");\r\n\r\n // Normalize double quotes in XPath attribute selectors [@id=\"\" -> [@id='']\r\n cleaned = cleaned.replace(\r\n /\\[@(id|name)=['\"]{2}(.+?)['\"]{2}\\]/g,\r\n \"[@$1='$2']\"\r\n );\r\n\r\n // For DOM selectors (id or name), remove ALL quotes\r\n if (type === \"id\" || type === \"name\") {\r\n cleaned = cleaned.replace(/['\"]/g, \"\").trim();\r\n }\r\n\r\n if (type === \"xpath\" && isRecorded === \"Y\" && !val.startsWith(\"//\"))\r\n return null;\r\n\r\n // Final check for empty strings\r\n if (!cleaned || /^['\"]{2}$/.test(cleaned)) return null;\r\n\r\n return cleaned;\r\n };\r\n\r\n locators: for (const locator of record.locators) {\r\n try {\r\n const isRecorded = String(locator.isRecorded || \"\");\r\n const recordedNLocators = record.locators.filter(\r\n (l) => l.isRecorded === \"N\"\r\n );\r\n\r\n if (recordedNLocators.length > 0) {\r\n for (const locator of recordedNLocators) {\r\n createLocator(locator);\r\n }\r\n }\r\n\r\n const isDynamic = String(locator.value || locator.type || \"\");\r\n if (\r\n isDynamic.includes(\"dynamic\") ||\r\n isDynamic.match(\"dynamic\") ||\r\n isDynamic.includes(\"{\") ||\r\n isDynamic.includes(\"}\")\r\n ) {\r\n createLocator(locator);\r\n continue;\r\n }\r\n\r\n if (record.isShared.includes(\"Y\")) {\r\n break locators;\r\n }\r\n\r\n try {\r\n let targetElement: Element | null = null;\r\n const selectors = locator.value.split(\">>>\");\r\n\r\n for (const selector of selectors) {\r\n if (!docmt) {\r\n console.error(\"Element not found at:\", selector);\r\n break;\r\n }\r\n\r\n const trimmedSelector = selector.trim();\r\n\r\n //normal DOM\r\n targetElement = resolveElement(docmt, locator, trimmedSelector);\r\n\r\n //iframe (if not found)\r\n if (!targetElement) {\r\n targetElement = findInIframes(docmt, locator, trimmedSelector);\r\n }\r\n\r\n //shadow DOM (if still not found)\r\n if (!targetElement) {\r\n targetElement = getElementFromShadowRoot(\r\n docmt.body,\r\n trimmedSelector\r\n );\r\n }\r\n\r\n if (!targetElement) {\r\n console.error(\"Element not found at:\", trimmedSelector);\r\n break;\r\n }\r\n }\r\n\r\n const locatorExists = (name: string, value: string): boolean => {\r\n const key = `${name}:${value}`;\r\n return finalLocatorsSet.has(key);\r\n };\r\n\r\n if (targetElement) {\r\n const payloadXPaths = record.locators.filter(\r\n (l) => l.name === \"xpath\" && l.value\r\n );\r\n const payloadCssSelectors = record.locators.filter(\r\n (l) => isCssSelectorLocator(l) && l.value\r\n );\r\n\r\n for (const px of payloadXPaths) {\r\n if (isSameXPathStillValid(px.value, docmt, targetElement))\r\n createLocator(px, {\r\n isSelfHealed: null\r\n });\r\n }\r\n\r\n for (const cssLocator of payloadCssSelectors) {\r\n if (\r\n isSameCssSelectorStillValid(\r\n cssLocator.value,\r\n docmt,\r\n targetElement\r\n )\r\n )\r\n createLocator(cssLocator, {\r\n isSelfHealed: null\r\n });\r\n }\r\n\r\n const existingXPaths = finalLocators.filter(\r\n (l) => l.name === \"xpath\" && l.value\r\n );\r\n\r\n // Track XPath patterns already used\r\n const usedXPathPatterns = new Set<string>(\r\n existingXPaths.map((x) => getXPathPattern(x.value))\r\n );\r\n\r\n const excludedAttributes: string[] = [];\r\n const idValue = getId(targetElement);\r\n if (\r\n idValue &&\r\n !locatorExists(\"id\", idValue) &&\r\n !isNumberExist(idValue)\r\n ) {\r\n const prevId = record.locators.find((l) => l.name === \"id\");\r\n if (isUniqueInDOM(docmt, \"id\", idValue, targetElement)) {\r\n excludedAttributes.push(\"id\");\r\n createLocator(prevId, {\r\n name: \"id\",\r\n type: \"static\",\r\n isRecorded: \"Y\",\r\n value: idValue\r\n });\r\n }\r\n }\r\n\r\n const tagName = targetElement.tagName;\r\n if (tagName && !locatorExists(\"tagName\", tagName)) {\r\n const prevTag = record.locators.find((l) => l.name === \"tagName\");\r\n if (isUniqueInDOM(docmt, \"tagName\", tagName, targetElement)) {\r\n excludedAttributes.push(\"tagName\");\r\n createLocator(prevTag, {\r\n name: \"tagName\",\r\n type: \"static\",\r\n isRecorded: \"Y\",\r\n value: tagName\r\n });\r\n }\r\n }\r\n\r\n const textValue = getVisibleText(targetElement);\r\n if (textValue && !isNumberExist(textValue)) {\r\n const prevLinkText = record.locators.find(\r\n (l) => l.name === \"linkText\"\r\n );\r\n if (isUniqueInDOM(docmt, \"linkText\", textValue, targetElement)) {\r\n excludedAttributes.push(\"linkText\");\r\n createLocator(prevLinkText, {\r\n name: \"linkText\",\r\n type: \"static\",\r\n isRecorded: \"Y\",\r\n value: textValue\r\n });\r\n }\r\n }\r\n\r\n const nameLocator = getName(targetElement);\r\n if (\r\n nameLocator &&\r\n !locatorExists(\"name\", nameLocator) &&\r\n !isNumberExist(nameLocator)\r\n ) {\r\n const prevName = record.locators.find((l) => l.name === \"name\");\r\n if (isUniqueInDOM(docmt, \"name\", nameLocator, targetElement)) {\r\n excludedAttributes.push(\"name\");\r\n createLocator(prevName, {\r\n name: \"name\",\r\n type: \"static\",\r\n isRecorded: \"Y\",\r\n value: nameLocator\r\n });\r\n }\r\n }\r\n\r\n const classValue = getClassName(targetElement);\r\n if (\r\n classValue &&\r\n classValue.trim() !== \"\" &&\r\n !classValue.includes(\" \") &&\r\n !locatorExists(\"className\", classValue) &&\r\n !isNumberExist(classValue)\r\n ) {\r\n const prevClassLocator = record.locators.find(\r\n (l) => l.name === \"className\"\r\n );\r\n if (isUniqueInDOM(docmt, \"className\", classValue, targetElement)) {\r\n excludedAttributes.push(\"className\");\r\n createLocator(prevClassLocator, {\r\n name: \"className\",\r\n type: \"static\",\r\n isRecorded: \"Y\",\r\n value: classValue\r\n });\r\n }\r\n }\r\n parseCssSelectors(targetElement, \"single\").forEach(\r\n (cssSelector) => {\r\n if (\r\n cssSelector.value &&\r\n !locatorExists(\"cssSelector\", cssSelector.value)\r\n ) {\r\n createLocator(undefined, {\r\n name: \"cssSelector\",\r\n value: cssSelector.value,\r\n type: \"static\",\r\n isRecorded: \"Y\"\r\n });\r\n }\r\n }\r\n );\r\n const allAttributes = Array.from(targetElement.attributes);\r\n const includedAttributes = allAttributes.filter(\r\n (attr) => !excludedAttributes.includes(attr.name)\r\n );\r\n\r\n //If any direct locator is broken then we consider it as broken xpath\r\n\r\n let xpathResults: any[] = [];\r\n try {\r\n xpathResults =\r\n parseDOM(targetElement, docmt, false, true, includedAttributes) ??\r\n [];\r\n } catch (error) {\r\n console.error(\"Error generating XPath candidates:\", error);\r\n }\r\n\r\n if (xpathResults?.length !== 0) {\r\n const brokenPayloadXPaths = payloadXPaths.filter(\r\n (px) => !isSameXPathStillValid(px.value, docmt, targetElement)\r\n );\r\n\r\n let xpathAdded = 0;\r\n\r\n for (const brokenPx of brokenPayloadXPaths) {\r\n if (xpathAdded >= brokenPayloadXPaths.length) break;\r\n\r\n const originalPattern = getXPathPattern(brokenPx.value);\r\n if (usedXPathPatterns.has(originalPattern)) continue;\r\n\r\n const match = xpathResults.find(\r\n (r) => r.value && getXPathPattern(r.value) === originalPattern\r\n );\r\n\r\n if (match?.value) {\r\n createLocator(brokenPx, {\r\n name: \"xpath\",\r\n value: match.value,\r\n type: \"static\",\r\n isRecorded: \"Y\",\r\n isSelfHealed: \"Y\"\r\n });\r\n\r\n usedXPathPatterns.add(originalPattern);\r\n xpathAdded++;\r\n }\r\n }\r\n if (xpathAdded < brokenPayloadXPaths.length) {\r\n for (const result of xpathResults) {\r\n if (xpathAdded >= brokenPayloadXPaths.length) break;\r\n if (!result.value) continue;\r\n\r\n const pattern = getXPathPattern(result.value);\r\n if (usedXPathPatterns.has(pattern)) continue;\r\n\r\n createLocator(result, {\r\n name: \"xpath\",\r\n value: result.value,\r\n type: \"static\",\r\n isRecorded: \"Y\",\r\n isSelfHealed: \"Y\"\r\n });\r\n\r\n usedXPathPatterns.add(pattern);\r\n xpathAdded++;\r\n }\r\n }\r\n }\r\n for (const locator of record.locators) {\r\n try {\r\n for (const loc of record.locators) {\r\n if (!loc.value) continue;\r\n\r\n for (const relation of relations) {\r\n if (loc.value.includes(relation)) {\r\n const relativeXpath = checkReferenceElementIsValid(\r\n loc.value,\r\n relation,\r\n docmt\r\n );\r\n if (relativeXpath) {\r\n createLocator(loc, {\r\n name: \"xpath\",\r\n value: relativeXpath,\r\n isRecorded:\r\n locator.isRecorded !== \"\" &&\r\n locator.isRecorded !== null\r\n ? locator.isRecorded\r\n : \"Y\"\r\n });\r\n break;\r\n }\r\n }\r\n }\r\n }\r\n } catch (error) {\r\n console.error(\"Error processing locator:\", locator, error);\r\n }\r\n }\r\n if (finalLocators.length < 5) {\r\n const fallbackCandidates = [\r\n { name: \"id\", value: getId(targetElement) },\r\n { name: \"name\", value: getName(targetElement) },\r\n { name: \"className\", value: getClassName(targetElement) },\r\n { name: \"tagName\", value: targetElement.tagName },\r\n { name: \"linkText\", value: getVisibleText(targetElement) }\r\n ];\r\n\r\n for (const candidate of fallbackCandidates) {\r\n if (finalLocators.length > 4) break;\r\n\r\n const { name, value } = candidate;\r\n if (!value) continue;\r\n if (isNumberExist(value)) continue;\r\n if (locatorExists(name, value)) continue;\r\n if (name === \"className\" && value.includes(\" \")) continue;\r\n if (isUniqueInDOM(docmt, name, value, targetElement)) {\r\n createLocator(undefined, {\r\n name,\r\n type: \"static\",\r\n value,\r\n isRecorded: \"Y\",\r\n isSelfHealed: \"Y\"\r\n });\r\n }\r\n }\r\n\r\n if (finalLocators.length < 5) {\r\n parseCssSelectors(targetElement, \"multiple\").forEach(\r\n (cssSelector) => {\r\n if (finalLocators.length > 4) return;\r\n if (!cssSelector.value) return;\r\n if (locatorExists(\"cssSelector\", cssSelector.value)) return;\r\n if (\r\n !isUniqueInDOM(\r\n docmt,\r\n \"cssSelector\",\r\n cssSelector.value,\r\n targetElement\r\n )\r\n ) {\r\n return;\r\n }\r\n\r\n createLocator(undefined, {\r\n name: \"cssSelector\",\r\n type: \"static\",\r\n value: cssSelector.value,\r\n isRecorded: \"Y\",\r\n isSelfHealed: \"Y\"\r\n });\r\n }\r\n );\r\n }\r\n }\r\n\r\n const finalAutoHealedLocators = finalLocators.map((obj) => ({\r\n ...obj,\r\n value: cleanLocatorValue(obj.value, obj.name, obj.isRecorded)\r\n }));\r\n\r\n const jsonResult = [\r\n {\r\n name: `${record.name}`,\r\n desc: `${record.desc}`,\r\n type: `${record.type}`,\r\n locators: finalAutoHealedLocators.filter(\r\n (locator) => locator?.value != null && locator.value !== \"\"\r\n ),\r\n isShared: `${record.isShared}`,\r\n projectId: `${record.projectId}`,\r\n projectType: `${record.projectType}`,\r\n isRecorded: `${record.isRecorded}`,\r\n folder: `${record.folder}`,\r\n parentId: `${record.parentId}`,\r\n parentName: `${record.parentName}`,\r\n platform: `${record.platform}`,\r\n licenseId: `${record.licenseId}`,\r\n licenseType: `${record.licenseType}`,\r\n userId: `${record.userId}`\r\n }\r\n ];\r\n\r\n return jsonResult;\r\n }\r\n } catch (error) {\r\n console.error(\"Error processing locator:\", locator, error);\r\n continue;\r\n }\r\n } catch (error) {\r\n console.error(\"Error processing locator:\", locator, error);\r\n continue;\r\n }\r\n }\r\n return null;\r\n};\r\n\r\nexport { getElementsFromHTML };\r\n","import { JSDOM, VirtualConsole } from \"jsdom\";\r\nimport { ElementRecord } from \"../types/locator\";\r\nimport { getElementsFromHTML } from \"../utils/getElementsFromHTML\";\r\n\r\nasync function createXPathAPI() {\r\n const fromHTML = async (html: string) => {\r\n const virtualConsole = new VirtualConsole();\r\n\r\n const dom = new JSDOM(\r\n html\r\n .replace(/\\\\\"/g, '\"')\r\n .replace(/\\\\r/g, \"\\r\")\r\n .replace(/\\\\n/g, \"\\n\")\r\n .replace(/\\\\t/g, \"\\t\"),\r\n {\r\n pretendToBeVisual: true,\r\n runScripts: \"dangerously\",\r\n resources: \"usable\",\r\n virtualConsole\r\n }\r\n );\r\n\r\n const { window } = dom;\r\n\r\n // ✅ Browser globals parity (REQUIRED)\r\n // @ts-ignore\r\n global.document = window.document;\r\n // @ts-ignore\r\n global.Node = window.Node;\r\n // @ts-ignore\r\n global.Element = window.Element;\r\n // @ts-ignore\r\n global.HTMLElement = window.HTMLElement;\r\n // @ts-ignore\r\n global.SVGElement = window.SVGElement;\r\n\r\n const cssApi = window.CSS ?? {};\r\n if (!cssApi.escape) {\r\n cssApi.escape = (value: string) =>\r\n String(value).replace(/[^a-zA-Z0-9_-]/g, \"\\\\$&\");\r\n }\r\n // @ts-ignore\r\n global.CSS = cssApi;\r\n\r\n // @ts-ignore\r\n global.XPathResult = window.XPathResult;\r\n // @ts-ignore\r\n global.XPathEvaluator = window.XPathEvaluator;\r\n\r\n return {\r\n autoHeal: async (data: ElementRecord) => {\r\n return getElementsFromHTML(data, window.document);\r\n }\r\n };\r\n };\r\n\r\n return { fromHTML };\r\n}\r\n\r\nexport default createXPathAPI;\r\n"],"names":["reWhiteSpace","xpathEvalCache","WeakMap","evaluateXPathOnce","xpath","docmt","owner","contextNode","nodeType","ownerDocument","document","getXPathContext","nodeCache","get","Map","set","cached","result","evaluate","XPathResult","ORDERED_NODE_ITERATOR_TYPE","evalResult","first","iterateNext","second","modifiedElementAttributes","modifiedElementClassTokens","INTERNAL_CLASS_TOKENS","Set","UNSTABLE_FRAMEWORK_CLASS_TOKEN_PATTERN","normalizeClassTokens","classValue","split","map","token","trim","filter","has","sort","hasNumericAttributeValue","value","test","isUnstableFrameworkClassToken","getClassTokenConditions","element","allowNumericToken","allClassTokens","classTokens","getStableClassTokens","length","escapeCharacters","buildPattern","isSvg","tagName","toLowerCase","count","getTagOnlyXpathCandidateCount","left","right","blockedClassTokens","TEST_ID_ATTRIBUTE_NAMES","sanitizeAttributeValue","attributeName","attributeValue","join","replace","isNumberExist","str","pattern","getTextContent","targetElement","textContent","getCountOfXPath","multiElementReferenceMode","isShadowRootNode","cloneElement","cloneElements","createShadowEvaluationContext","Array","isArray","node","matchCount","includes","error","console","text","indexOf","replaceWhiteSpaces","getContainerTextCondition","normalizedText","children","fragment","slice","getStableTargetTextCandidates","prefix","part","getStableTargetText","Node","DOCUMENT_FRAGMENT_NODE","root","tempDoc","implementation","createHTMLDocument","wrapper","createElement","innerHTML","body","appendChild","cloneForElement","candidate","defaultView","Element","path","current","parentElement","parent","unshift","from","parentNode","getElementPathFromRoot","container","index","next","item","getElementAtPath","Boolean","checkBlockedAttributes","attribute","isTarget","sanitizedValue","name","some","x","isModified","find","doc","log","hasGeneratedNumericSegment","SVGElement","getPropertyXPath","prop","isIndex","combinePattern","mergePattern","isAttributeProp","startsWith","isTextProp","normalizedTextProp","hasTextSpacing","stableTargetText","textValueForPredicate","containerTextCondition","classCondition","splitText","contentRes","match","endsWith","endIndex","startIndexString","endIndexString","i","push","getAttributeCombinationXpath","domNode","uniqueAttributes","candidateAttributes","attr","buildAttributeConditions","attrValue","nodeValue","buildAttributeConditionAlternatives","conditions","condition","buildConditionGroups","attributes","reduce","groups","alternatives","flatMap","group","alternative","getTextConditions","rawText","add","generateAttributeCombinations","combinationSize","results","build","startIndex","currentGroup","pop","buildXPath","attributeOnlyXpath","attributeGroups","attributeGroup","conditionGroups","xpathConditions","tryAttributeOnlyCombinations","textConditions","attributeConditions","textCondition","JSON","stringify","getFilteredTextXPath","filteredText","childNodes","textForPredicate","xpathe","getNormalizedPropertyXPath","getStartsWithPropertyXPath","stableText","getContainsPropertyXPath","getOrAttributesXPath","getRootNode","buildTextCondition","_error","getAncestorAnchorCandidates","anchors","seen","priorityAttrs","orderedAttributes","attrName","pushAnchor","isExactUniqueXpath","forEach","getXpathStrings","textXpath","combinationXpath","getStructuralPathFromAncestor","ancestor","steps","getAxisNodeTest","getTagOnlyXPath","fallbackXpath","ancestorAnchors","ancestorXpath","descendantXpath","getFirstMatchedNode","structuralPath","parentChainXpath","parentChainCount","fallbackCount","FIRST_ORDERED_NODE_TYPE","singleNodeValue","getUniqueNodeAnchorXpaths","key","a","tagXpath","getTextXpathFunction","trimmedText","undefined","deleteLineGap","b","c","RegExp","normalizeXPath","getXPathPattern","canonical","canonicalizeXPath","parts","xp","axisMatch","axis","attrMatch","usesNormalize","extractXPathSignatureParts","escapeAttrValue","isUniqueInDOM","queryAll","selector","querySelectorAll","xpathData","STRATEGY_MAP","andConditions","orConditions","contains","normalizeSpace","axes","hardCodedText","tillTag","getStrategyName","strategy","withStrategyKey","entries","entry","getUniqueXpathEntries","removePositionalIndexXpaths","quote","char","hasPositionalIndex","getNormalizedText","collectSubtreeElements","nodes","queue","currentNode","shift","classList","getSiblingNodes","direction","previousElementSibling","nextElementSibling","getDirectionalAxisSeedNodes","sibling","expand","isLowQualityNode","id","className","scoreNode","score","getAttribute","buildAxisXpathsForNodes","textPredicate","targetNodeTest","candidates","processed","MAX_PROCESS","nodeItem","anchorStages","containsXpath","startsWithXpath","stageIndex","anchor","Math","min","expanded","childCount","child","getLimitedAncestors","limit","ancestors","getSiblingAxis","to","getParentAxisPath","getTargetPathFromScope","scope","buildChainedAxesFallbackXpaths","anchorNodes","depth","siblingGroups","siblings","descendantCount","descendant","collectChainedAxisAnchorNodes","targetScopes","anchorNode","anchorXpaths","anchorScopes","anchorXpath","anchorScope","anchorToScopePath","targetScope","siblingAxis","siblingStep","targetPath","getBridgeTargetNodeTests","getStableBridgeText","nodeTests","getBridgeAnchorXpaths","buildAncestorDescendantBridgeAxesXpaths","pushStableTextLeaves","previousSiblings","reverse","nextSiblings","collectBridgeAxisAnchorNodes","targetNodeTests","scopeStep","getChildAxisPathFromAncestor","finalStep","getChildChainAnchorXpaths","addAnchor","canUseStableAxisAttribute","stableValue","getStableAxisAttributeValue","buildAxesStrategyXpaths","descendants","precedingSeeds","followingSeeds","limitNodes","tiers","childChainResults","childAxisPath","buildAnchoredChildChainAxesXpaths","tier","ancestorBridgeResults","buildStrategyXpaths","fallbackXpaths","strategyName","getTextXPath","buildTextStrategyXpaths","xpaths","getStrategyXPath","textProp","buildPropertyStrategyXpaths","buildConditionStrategyXpaths","tag","idx","scopeXpaths","toString","scopeXpath","scopedIdx","generateIndexedXpaths","addAllXPathAttributes","targetElemt","attributesArray","xpth","attributesBasedXPath","txtXpath","getUniqueClassName","textAttribute","normalizedTextContent","getXPathUsingAttributeAndText","parseDOM","includedAttributes","strategies","rootNode","isShadowScoped","attributesToUse","allowPositionalIndex","includesIndexStrategy","strategyXpaths","len","substring","PREFERRED_ATTRS","EXCLUDED_ATTRS","escapeCssIdentifier","el","css","CSS","globalThis","escape","String","isElementNode","view","isIgnoredTag","isStableAttributeValue","trimmed","isCssSelectorUnique","matches","addUniqueSelector","selectors","parseCssSelectors","mode","idPath","getIdCssPath","attributePath","getAttributeCssPath","classPath","getClassCssPath","scopedPath","getScopedStableCssPath","positionalPath","getShortestPositionalCssPath","e","idAttribute","getAttributeNode","getAttributeSelectors","preferredOnly","aIndex","bIndex","attrSelectors","attrSelector","classSelector","getClassSelector","tagSelector","getDirectStableSelectors","options","idSelector","preferredAttributesOnly","includeTag","childSelectors","parentSelectors","parentSelector","childSelector","scopedSelector","directScopedSelector","getNthOfType","positionalSelector","getAbsoluteCssPath","ELEMENT_NODE","isSameXPathStillValid","target","normalized","getElementFromXPath","shouldUseSnapshot","ORDERED_NODE_SNAPSHOT_TYPE","snapshotLength","snapshotItem","getElementFromCssSelector","found","querySelector","getElementFromShadowRoot","isSameCssSelectorStillValid","isCssSelectorLocator","locator","elements","shadowRoot","nestedElement","getId","getClassName","getVisibleText","getName","elementEl","hasAttribute","relations","window","XPathEvaluator","checkReferenceElementIsValid","relation","sourceLoc","xpathEvaluator","relativeXpath","getElementsFromHTML","record","elementsToRemove","remove","finalLocatorsSet","finalLocators","createLocator","base","overrides","oldValue","newValue","newLocator","type","reference","status","isRecorded","previousSelfHealed","isSelfHealed","hasOwnProperty","locName","resolveIsSelfHealed","obj","pushUniqueLocator","resolveElement","ctx","safeName","findInIframes","iframes","iframe","iframeDoc","contentDocument","contentWindow","cleanLocatorValue","val","cleaned","locators","recordedNLocators","l","isDynamic","isShared","trimmedSelector","locatorExists","payloadXPaths","payloadCssSelectors","px","cssLocator","existingXPaths","usedXPathPatterns","excludedAttributes","idValue","prevId","prevTag","textValue","prevLinkText","nameLocator","prevName","prevClassLocator","cssSelector","xpathResults","brokenPayloadXPaths","xpathAdded","brokenPx","originalPattern","r","loc","fallbackCandidates","finalAutoHealedLocators","jsonResult","desc","projectId","projectType","folder","parentId","parentName","platform","licenseId","licenseType","userId","async","createXPathAPI","fromHTML","html","virtualConsole","VirtualConsole","dom","JSDOM","pretendToBeVisual","runScripts","resources","global","HTMLElement","cssApi","autoHeal","data"],"mappings":"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"}