ff-dom 3.0.3-beta.1 → 3.0.3-beta.3
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.
- package/dist/index.browser.cjs +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.cdn.js +225 -119
- package/dist/types/browser/browser/xpath.d.ts +1 -0
- package/dist/types/browser/utils/cssSelector.d.ts +1 -0
- package/dist/types/node/utils/cssSelector.d.ts +1 -0
- package/dist/xpath.mjs +1 -1
- package/package.json +1 -1
package/dist/index.cdn.js
CHANGED
|
@@ -540,6 +540,22 @@
|
|
|
540
540
|
String(attribute.value),
|
|
541
541
|
].join("::");
|
|
542
542
|
const computeBlockedAttributeDecision = (attribute, targetElement, isTarget) => {
|
|
543
|
+
if (attribute.name === "class" &&
|
|
544
|
+
attribute.value
|
|
545
|
+
.split(/\s+/)
|
|
546
|
+
.some((token) => INTERNAL_CLASS_TOKENS.has(token))) {
|
|
547
|
+
const alreadyTracked = modifiedElementAttributes.some((modifiedAttribute) => modifiedAttribute.doc === targetElement.ownerDocument &&
|
|
548
|
+
modifiedAttribute.element === targetElement &&
|
|
549
|
+
modifiedAttribute.attributeName === "class");
|
|
550
|
+
if (!alreadyTracked) {
|
|
551
|
+
modifiedElementAttributes.push({
|
|
552
|
+
url: targetElement.baseURI,
|
|
553
|
+
attributeName: "class",
|
|
554
|
+
element: targetElement,
|
|
555
|
+
doc: targetElement.ownerDocument,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
}
|
|
543
559
|
const sanitizedValue = sanitizeAttributeValue(attribute.name, attribute.value);
|
|
544
560
|
if (!sanitizedValue || typeof attribute?.value === "boolean") {
|
|
545
561
|
return false;
|
|
@@ -662,6 +678,12 @@
|
|
|
662
678
|
if (count === 1 && !isIndex) {
|
|
663
679
|
return pattern;
|
|
664
680
|
}
|
|
681
|
+
else {
|
|
682
|
+
const indexedXPath = findXpathWithIndex(pattern, element, docmt, count);
|
|
683
|
+
if (indexedXPath) {
|
|
684
|
+
return indexedXPath;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
665
687
|
}
|
|
666
688
|
return;
|
|
667
689
|
}
|
|
@@ -687,6 +709,12 @@
|
|
|
687
709
|
if (count === 1 && !isIndex) {
|
|
688
710
|
return pattern;
|
|
689
711
|
}
|
|
712
|
+
else {
|
|
713
|
+
const indexedXPath = findXpathWithIndex(pattern, element, docmt, count);
|
|
714
|
+
if (indexedXPath) {
|
|
715
|
+
return indexedXPath;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
690
718
|
}
|
|
691
719
|
if (isAttributeProp && hasNumericAttributeValue(value)) {
|
|
692
720
|
// Strict numeric-attribute validation: do not split numeric attribute
|
|
@@ -1021,7 +1049,13 @@
|
|
|
1021
1049
|
}
|
|
1022
1050
|
// Combine all parts into the final XPath
|
|
1023
1051
|
const finalXPath = `${xpathParts.join("")}`;
|
|
1024
|
-
const
|
|
1052
|
+
const finalXPathCount = getCountOfXPath(finalXPath, domNode, docmt);
|
|
1053
|
+
const indexedXPath = finalXPathCount > 1 && isIndex ?
|
|
1054
|
+
findXpathWithIndex(finalXPath, domNode, docmt, finalXPathCount)
|
|
1055
|
+
: undefined;
|
|
1056
|
+
const xpathToReturn = finalXPathCount === 1 ? finalXPath
|
|
1057
|
+
: indexedXPath ? indexedXPath
|
|
1058
|
+
: getAbsoluteXPath(domNode, docmt);
|
|
1025
1059
|
// Cache the final XPath for this node
|
|
1026
1060
|
nodeCache.set(cacheKey, xpathToReturn);
|
|
1027
1061
|
return xpathToReturn;
|
|
@@ -1841,7 +1875,7 @@
|
|
|
1841
1875
|
if (!xpaths1?.length) {
|
|
1842
1876
|
const xpaths = addAttributeSplitCombineXpaths(domNode.attributes, domNode, docmt, isTarget);
|
|
1843
1877
|
if (xpaths?.length) {
|
|
1844
|
-
xpaths1.
|
|
1878
|
+
xpaths1.push(...xpaths);
|
|
1845
1879
|
}
|
|
1846
1880
|
}
|
|
1847
1881
|
return xpaths1;
|
|
@@ -1974,7 +2008,12 @@
|
|
|
1974
2008
|
createObserver,
|
|
1975
2009
|
startObserver,
|
|
1976
2010
|
stopObserver,
|
|
1977
|
-
modifiedElementAttributes
|
|
2011
|
+
get modifiedElementAttributes() {
|
|
2012
|
+
return modifiedElementAttributes;
|
|
2013
|
+
},
|
|
2014
|
+
set modifiedElementAttributes(attributes) {
|
|
2015
|
+
modifiedElementAttributes = Array.isArray(attributes) ? attributes : [];
|
|
2016
|
+
},
|
|
1978
2017
|
cspEnabled,
|
|
1979
2018
|
getXPathPattern,
|
|
1980
2019
|
getNormalizedPropertyXPath,
|
|
@@ -2001,13 +2040,13 @@
|
|
|
2001
2040
|
let referenceElementMode = false;
|
|
2002
2041
|
let parentXpathCache = new WeakMap();
|
|
2003
2042
|
let childRelativeXpathCache = new WeakMap();
|
|
2004
|
-
const getElementSignature = (element) => element
|
|
2005
|
-
|
|
2043
|
+
const getElementSignature = (element) => element ?
|
|
2044
|
+
[
|
|
2006
2045
|
element.tagName,
|
|
2007
2046
|
Array.from(element.attributes || [])
|
|
2008
2047
|
.map((attribute) => `${attribute.name}=${attribute.value}`)
|
|
2009
2048
|
.join("|"),
|
|
2010
|
-
element.textContent || ""
|
|
2049
|
+
element.textContent || "",
|
|
2011
2050
|
].join("::")
|
|
2012
2051
|
: "";
|
|
2013
2052
|
const clearXPathModuleCaches = () => {
|
|
@@ -2025,7 +2064,7 @@
|
|
|
2025
2064
|
axes: "axes",
|
|
2026
2065
|
index: "index",
|
|
2027
2066
|
hardCodedText: "text",
|
|
2028
|
-
tillTag: "tag"
|
|
2067
|
+
tillTag: "tag",
|
|
2029
2068
|
};
|
|
2030
2069
|
const getStrategyName = (strategy) => {
|
|
2031
2070
|
return STRATEGY_MAP[strategy] || strategy;
|
|
@@ -2034,9 +2073,9 @@
|
|
|
2034
2073
|
const withStrategyKey = (strategy, entries) => {
|
|
2035
2074
|
return entries.map((entry) => ({
|
|
2036
2075
|
...entry,
|
|
2037
|
-
key: entry?.key?.includes(strategy)
|
|
2038
|
-
|
|
2039
|
-
: `${strategy} ${entry.key}`.trim()
|
|
2076
|
+
key: entry?.key?.includes(strategy) ?
|
|
2077
|
+
entry.key
|
|
2078
|
+
: `${strategy} ${entry.key}`.trim(),
|
|
2040
2079
|
}));
|
|
2041
2080
|
};
|
|
2042
2081
|
const getUniqueXpathEntries = (entries) => {
|
|
@@ -2077,9 +2116,7 @@
|
|
|
2077
2116
|
}
|
|
2078
2117
|
return false;
|
|
2079
2118
|
};
|
|
2080
|
-
const removePositionalIndexXpaths = (entries, isIndex) => isIndex
|
|
2081
|
-
? entries
|
|
2082
|
-
: entries.filter((entry) => !hasPositionalIndex(entry.value));
|
|
2119
|
+
const removePositionalIndexXpaths = (entries, isIndex) => isIndex ? entries : (entries.filter((entry) => !hasPositionalIndex(entry.value)));
|
|
2083
2120
|
const getNormalizedText = (element) => {
|
|
2084
2121
|
return element?.textContent?.replace(/\s+/g, " ")?.trim() || "";
|
|
2085
2122
|
};
|
|
@@ -2099,12 +2136,9 @@
|
|
|
2099
2136
|
const xpaths = [];
|
|
2100
2137
|
const attributes = Array.from(element.attributes || []).filter((attribute) => attribute?.value && checkBlockedAttributes(attribute, element, isTarget));
|
|
2101
2138
|
const text = getNormalizedText(element);
|
|
2102
|
-
const getStrategyXPath = strategy === "contains"
|
|
2103
|
-
?
|
|
2104
|
-
|
|
2105
|
-
? getStartsWithPropertyXPath
|
|
2106
|
-
: strategy === "normalizeSpace"
|
|
2107
|
-
? getNormalizedPropertyXPath
|
|
2139
|
+
const getStrategyXPath = strategy === "contains" ? getContainsPropertyXPath
|
|
2140
|
+
: strategy === "startsWith" ? getStartsWithPropertyXPath
|
|
2141
|
+
: strategy === "normalizeSpace" ? getNormalizedPropertyXPath
|
|
2108
2142
|
: null;
|
|
2109
2143
|
if (!getStrategyXPath) {
|
|
2110
2144
|
return xpaths;
|
|
@@ -2125,7 +2159,7 @@
|
|
|
2125
2159
|
if (xpath) {
|
|
2126
2160
|
xpaths.push({
|
|
2127
2161
|
key: `xpath by ${strategy} class`,
|
|
2128
|
-
value: xpath
|
|
2162
|
+
value: xpath,
|
|
2129
2163
|
});
|
|
2130
2164
|
}
|
|
2131
2165
|
});
|
|
@@ -2135,7 +2169,7 @@
|
|
|
2135
2169
|
if (xpath) {
|
|
2136
2170
|
xpaths.push({
|
|
2137
2171
|
key: `xpath by ${strategy} ${attribute.name}`,
|
|
2138
|
-
value: xpath
|
|
2172
|
+
value: xpath,
|
|
2139
2173
|
});
|
|
2140
2174
|
}
|
|
2141
2175
|
});
|
|
@@ -2148,7 +2182,7 @@
|
|
|
2148
2182
|
if (xpath) {
|
|
2149
2183
|
xpaths.push({
|
|
2150
2184
|
key: `xpath by ${strategy} text`,
|
|
2151
|
-
value: xpath
|
|
2185
|
+
value: xpath,
|
|
2152
2186
|
});
|
|
2153
2187
|
}
|
|
2154
2188
|
}
|
|
@@ -2158,8 +2192,8 @@
|
|
|
2158
2192
|
const attributes = Array.from(element.attributes || []).filter((attribute) => attribute?.value && checkBlockedAttributes(attribute, element, isTarget));
|
|
2159
2193
|
if (strategy === "and") {
|
|
2160
2194
|
const combinationXpath = getAttributeCombinationXpath(element, docmt, attributes, isTarget);
|
|
2161
|
-
return combinationXpath
|
|
2162
|
-
|
|
2195
|
+
return combinationXpath ?
|
|
2196
|
+
[{ key: "xpath by combination", value: combinationXpath }]
|
|
2163
2197
|
: [];
|
|
2164
2198
|
}
|
|
2165
2199
|
if (strategy === "or" && attributes.length >= 1) {
|
|
@@ -2185,16 +2219,16 @@
|
|
|
2185
2219
|
};
|
|
2186
2220
|
const getSiblingNodes = (element, direction) => {
|
|
2187
2221
|
const nodes = [];
|
|
2188
|
-
let currentNode = direction === "previous"
|
|
2189
|
-
|
|
2222
|
+
let currentNode = direction === "previous" ?
|
|
2223
|
+
element.previousElementSibling
|
|
2190
2224
|
: element.nextElementSibling;
|
|
2191
2225
|
while (currentNode) {
|
|
2192
2226
|
if (!currentNode.classList?.contains("flntooltip")) {
|
|
2193
2227
|
nodes.push(currentNode);
|
|
2194
2228
|
}
|
|
2195
2229
|
currentNode =
|
|
2196
|
-
direction === "previous"
|
|
2197
|
-
|
|
2230
|
+
direction === "previous" ?
|
|
2231
|
+
currentNode.previousElementSibling
|
|
2198
2232
|
: currentNode.nextElementSibling;
|
|
2199
2233
|
}
|
|
2200
2234
|
return nodes;
|
|
@@ -2202,20 +2236,20 @@
|
|
|
2202
2236
|
const getDirectionalAxisSeedNodes = (element, direction) => {
|
|
2203
2237
|
const nodes = [];
|
|
2204
2238
|
for (let currentNode = element; currentNode?.parentElement; currentNode = currentNode.parentElement) {
|
|
2205
|
-
let sibling = direction === "previous"
|
|
2206
|
-
|
|
2239
|
+
let sibling = direction === "previous" ?
|
|
2240
|
+
currentNode.previousElementSibling
|
|
2207
2241
|
: currentNode.nextElementSibling;
|
|
2208
2242
|
while (sibling) {
|
|
2209
2243
|
if (!sibling.classList?.contains("flntooltip")) {
|
|
2210
2244
|
nodes.push(sibling);
|
|
2211
2245
|
nodes.push({
|
|
2212
2246
|
node: sibling,
|
|
2213
|
-
expand: () => collectSubtreeElements(sibling)
|
|
2247
|
+
expand: () => collectSubtreeElements(sibling),
|
|
2214
2248
|
});
|
|
2215
2249
|
}
|
|
2216
2250
|
sibling =
|
|
2217
|
-
direction === "previous"
|
|
2218
|
-
|
|
2251
|
+
direction === "previous" ?
|
|
2252
|
+
sibling.previousElementSibling
|
|
2219
2253
|
: sibling.nextElementSibling;
|
|
2220
2254
|
}
|
|
2221
2255
|
}
|
|
@@ -2249,8 +2283,8 @@
|
|
|
2249
2283
|
};
|
|
2250
2284
|
const buildAxisXpathsForNodes = (nodes, axis, targetElement, docmt, isTarget, key, isIndex) => {
|
|
2251
2285
|
const textPredicate = getTextXpathFunction(targetElement);
|
|
2252
|
-
const targetNodeTest = textPredicate
|
|
2253
|
-
|
|
2286
|
+
const targetNodeTest = textPredicate ?
|
|
2287
|
+
`${getAxisNodeTest(targetElement)}[${textPredicate}]`
|
|
2254
2288
|
: getAxisNodeTest(targetElement);
|
|
2255
2289
|
const MAX_INDEX_TRY = 3;
|
|
2256
2290
|
const MAX_CANDIDATES = 5;
|
|
@@ -2286,7 +2320,7 @@
|
|
|
2286
2320
|
}
|
|
2287
2321
|
}
|
|
2288
2322
|
return anchors;
|
|
2289
|
-
}
|
|
2323
|
+
},
|
|
2290
2324
|
];
|
|
2291
2325
|
for (let stageIndex = 0; stageIndex < anchorStages.length; stageIndex++) {
|
|
2292
2326
|
let anchors = anchorStages[stageIndex]();
|
|
@@ -2396,8 +2430,8 @@
|
|
|
2396
2430
|
};
|
|
2397
2431
|
const getTargetPathFromScope = (scope, targetElement) => {
|
|
2398
2432
|
const targetNodeTest = getAxisNodeTest(targetElement);
|
|
2399
|
-
return targetElement.parentElement === scope
|
|
2400
|
-
|
|
2433
|
+
return targetElement.parentElement === scope ?
|
|
2434
|
+
[`/${targetNodeTest}`, `/descendant::${targetNodeTest}`]
|
|
2401
2435
|
: [`/descendant::${targetNodeTest}`];
|
|
2402
2436
|
};
|
|
2403
2437
|
const collectChainedAxisAnchorNodes = (element) => {
|
|
@@ -2420,7 +2454,7 @@
|
|
|
2420
2454
|
for (let scope = element.parentElement, depth = 0; scope && depth < MAX_SCOPE_DEPTH && anchors.length < MAX_ANCHORS; scope = scope.parentElement, depth++) {
|
|
2421
2455
|
const siblingGroups = [
|
|
2422
2456
|
getSiblingNodes(scope, "previous").slice(0, MAX_SIBLINGS_PER_SCOPE),
|
|
2423
|
-
getSiblingNodes(scope, "next").slice(0, MAX_SIBLINGS_PER_SCOPE)
|
|
2457
|
+
getSiblingNodes(scope, "next").slice(0, MAX_SIBLINGS_PER_SCOPE),
|
|
2424
2458
|
];
|
|
2425
2459
|
for (const siblings of siblingGroups) {
|
|
2426
2460
|
for (const sibling of siblings) {
|
|
@@ -2470,7 +2504,7 @@
|
|
|
2470
2504
|
if (isExactUniqueXpath(xpath, element, docmt)) {
|
|
2471
2505
|
candidates.push({
|
|
2472
2506
|
key: "chained axes fallback",
|
|
2473
|
-
value: xpath
|
|
2507
|
+
value: xpath,
|
|
2474
2508
|
});
|
|
2475
2509
|
if (candidates.length >= 2) {
|
|
2476
2510
|
return candidates;
|
|
@@ -2570,7 +2604,7 @@
|
|
|
2570
2604
|
// Keep existing unique attribute/text anchors as secondary options.
|
|
2571
2605
|
return [
|
|
2572
2606
|
...anchors,
|
|
2573
|
-
...getUniqueNodeAnchorXpaths(anchorNode, docmt, isTarget)
|
|
2607
|
+
...getUniqueNodeAnchorXpaths(anchorNode, docmt, isTarget),
|
|
2574
2608
|
];
|
|
2575
2609
|
};
|
|
2576
2610
|
const buildAncestorDescendantBridgeAxesXpaths = (element, docmt, isTarget) => {
|
|
@@ -2602,7 +2636,7 @@
|
|
|
2602
2636
|
// variants for the same target in single mode.
|
|
2603
2637
|
candidates.push({
|
|
2604
2638
|
key: "ancestor descendant bridge",
|
|
2605
|
-
value: xpath
|
|
2639
|
+
value: xpath,
|
|
2606
2640
|
});
|
|
2607
2641
|
if (candidates.length >= 1) {
|
|
2608
2642
|
return candidates;
|
|
@@ -2630,8 +2664,8 @@
|
|
|
2630
2664
|
// Single-mode axis anchors may use a stable prefix before a numeric suffix.
|
|
2631
2665
|
const valueBeforeNumber = attributeValue.split(/\d/)[0] || "";
|
|
2632
2666
|
const stableValue = valueBeforeNumber.replace(/[-_:\s.]+$/g, "").trim();
|
|
2633
|
-
return stableValue.length > 2 && /[a-zA-Z]/.test(stableValue)
|
|
2634
|
-
|
|
2667
|
+
return stableValue.length > 2 && /[a-zA-Z]/.test(stableValue) ?
|
|
2668
|
+
stableValue
|
|
2635
2669
|
: "";
|
|
2636
2670
|
};
|
|
2637
2671
|
const canUseStableAxisAttribute = (attribute) => {
|
|
@@ -2686,7 +2720,7 @@
|
|
|
2686
2720
|
if (isExactUniqueXpath(xpath, element, docmt)) {
|
|
2687
2721
|
candidates.push({
|
|
2688
2722
|
key: "child chain",
|
|
2689
|
-
value: xpath
|
|
2723
|
+
value: xpath,
|
|
2690
2724
|
});
|
|
2691
2725
|
if (candidates.length >= 2) {
|
|
2692
2726
|
return candidates;
|
|
@@ -2726,48 +2760,52 @@
|
|
|
2726
2760
|
{
|
|
2727
2761
|
nodes: limitNodes(nextSiblings),
|
|
2728
2762
|
axis: "preceding-sibling",
|
|
2729
|
-
key: "preceding-sibling"
|
|
2763
|
+
key: "preceding-sibling",
|
|
2730
2764
|
},
|
|
2731
2765
|
{
|
|
2732
2766
|
nodes: limitNodes(previousSiblings),
|
|
2733
2767
|
axis: "following-sibling",
|
|
2734
|
-
key: "following-sibling"
|
|
2735
|
-
}
|
|
2768
|
+
key: "following-sibling",
|
|
2769
|
+
},
|
|
2736
2770
|
],
|
|
2737
2771
|
[
|
|
2738
2772
|
{ nodes: limitNodes(ancestors), axis: "child", key: "child" },
|
|
2739
2773
|
{
|
|
2740
2774
|
nodes: limitNodes(descendants, 15),
|
|
2741
2775
|
axis: "parent",
|
|
2742
|
-
key: "parent"
|
|
2743
|
-
}
|
|
2776
|
+
key: "parent",
|
|
2777
|
+
},
|
|
2744
2778
|
],
|
|
2745
2779
|
[
|
|
2746
2780
|
{ nodes: limitNodes(ancestors), axis: "descendant", key: "descendant" },
|
|
2747
2781
|
{
|
|
2748
2782
|
nodes: limitNodes(ancestors),
|
|
2749
2783
|
axis: "descendant-or-self",
|
|
2750
|
-
key: "descendant-or-self"
|
|
2784
|
+
key: "descendant-or-self",
|
|
2751
2785
|
},
|
|
2752
2786
|
{
|
|
2753
2787
|
nodes: limitNodes(descendants, 10),
|
|
2754
2788
|
axis: "ancestor",
|
|
2755
|
-
key: "ancestor"
|
|
2789
|
+
key: "ancestor",
|
|
2756
2790
|
},
|
|
2757
2791
|
{
|
|
2758
2792
|
nodes: limitNodes(descendants, 10),
|
|
2759
2793
|
axis: "ancestor-or-self",
|
|
2760
|
-
key: "ancestor-or-self"
|
|
2761
|
-
}
|
|
2794
|
+
key: "ancestor-or-self",
|
|
2795
|
+
},
|
|
2762
2796
|
],
|
|
2763
2797
|
[
|
|
2764
2798
|
{
|
|
2765
2799
|
nodes: limitNodes(precedingSeeds),
|
|
2766
2800
|
axis: "following",
|
|
2767
|
-
key: "following"
|
|
2801
|
+
key: "following",
|
|
2802
|
+
},
|
|
2803
|
+
{
|
|
2804
|
+
nodes: limitNodes(followingSeeds),
|
|
2805
|
+
axis: "preceding",
|
|
2806
|
+
key: "preceding",
|
|
2768
2807
|
},
|
|
2769
|
-
|
|
2770
|
-
]
|
|
2808
|
+
],
|
|
2771
2809
|
];
|
|
2772
2810
|
// timeLog("nodes sorting", t);
|
|
2773
2811
|
const childChainResults = buildAnchoredChildChainAxesXpaths(element, docmt, isTarget, isIndex);
|
|
@@ -2803,14 +2841,14 @@
|
|
|
2803
2841
|
// global must always exist)
|
|
2804
2842
|
results.push({
|
|
2805
2843
|
key: "xpath by index",
|
|
2806
|
-
value: `(//${tag})[${idx}]
|
|
2844
|
+
value: `(//${tag})[${idx}]`,
|
|
2807
2845
|
});
|
|
2808
2846
|
// Add at most one scoped/indexed XPath anchored by the nearest stable ancestor.
|
|
2809
2847
|
let current = element.parentElement;
|
|
2810
2848
|
while (current && current !== docmt.body) {
|
|
2811
2849
|
if (current.id || current.className) {
|
|
2812
|
-
const scopeXpaths = current.id
|
|
2813
|
-
|
|
2850
|
+
const scopeXpaths = current.id ?
|
|
2851
|
+
getXpathStrings(current, "id", current.id)
|
|
2814
2852
|
: getXpathStrings(current, "class", current.className.toString());
|
|
2815
2853
|
// Scoped index anchors should also try every stable class token before
|
|
2816
2854
|
// walking to a higher ancestor.
|
|
@@ -2820,7 +2858,7 @@
|
|
|
2820
2858
|
if (scopedIdx > 0) {
|
|
2821
2859
|
results.push({
|
|
2822
2860
|
key: "xpath by index",
|
|
2823
|
-
value: `(${scopeXpath}//${tag})[${scopedIdx}]
|
|
2861
|
+
value: `(${scopeXpath}//${tag})[${scopedIdx}]`,
|
|
2824
2862
|
});
|
|
2825
2863
|
break;
|
|
2826
2864
|
}
|
|
@@ -2852,7 +2890,7 @@
|
|
|
2852
2890
|
}
|
|
2853
2891
|
if (strategyName === "tag") {
|
|
2854
2892
|
return withStrategyKey(strategy, [
|
|
2855
|
-
{ key: "xpath till tag", value: getTagOnlyXPath(element, docmt) }
|
|
2893
|
+
{ key: "xpath till tag", value: getTagOnlyXPath(element, docmt) },
|
|
2856
2894
|
]);
|
|
2857
2895
|
}
|
|
2858
2896
|
return [];
|
|
@@ -2864,14 +2902,14 @@
|
|
|
2864
2902
|
xpaths = [
|
|
2865
2903
|
// `${nodeXpath1}/descendant::${nodeXpath2}`,
|
|
2866
2904
|
`${nodeXpath1}/descendant-or-self::${nodeXpath2}`,
|
|
2867
|
-
`${nodeXpath1}/following::${nodeXpath2}
|
|
2905
|
+
`${nodeXpath1}/following::${nodeXpath2}`,
|
|
2868
2906
|
];
|
|
2869
2907
|
}
|
|
2870
2908
|
else {
|
|
2871
2909
|
xpaths = [
|
|
2872
2910
|
// `${nodeXpath1}/descendant::${nodeXpath2}`,
|
|
2873
2911
|
`${nodeXpath1}/ancestor-or-self::${nodeXpath2}`,
|
|
2874
|
-
`${nodeXpath1}/preceding::${nodeXpath2}
|
|
2912
|
+
`${nodeXpath1}/preceding::${nodeXpath2}`,
|
|
2875
2913
|
];
|
|
2876
2914
|
}
|
|
2877
2915
|
// Iterate through XPath patterns
|
|
@@ -2888,7 +2926,7 @@
|
|
|
2888
2926
|
xpathDataWithIndex$1.push({
|
|
2889
2927
|
key: `relative xpath by unique parent ${isIndex ? "index" : ""}`,
|
|
2890
2928
|
value: xpath,
|
|
2891
|
-
count
|
|
2929
|
+
count,
|
|
2892
2930
|
});
|
|
2893
2931
|
}
|
|
2894
2932
|
}
|
|
@@ -2896,7 +2934,7 @@
|
|
|
2896
2934
|
xpathDataWithIndex$1.push({
|
|
2897
2935
|
key: `relative xpath by unique parent ${isIndex ? "index" : ""}`,
|
|
2898
2936
|
value: xpath,
|
|
2899
|
-
count
|
|
2937
|
+
count,
|
|
2900
2938
|
});
|
|
2901
2939
|
}
|
|
2902
2940
|
}
|
|
@@ -2908,7 +2946,7 @@
|
|
|
2908
2946
|
getCountOfXPath(indexedXpath, targetElemt, docmt) === 1) {
|
|
2909
2947
|
xpathData$1.push({
|
|
2910
2948
|
key: `relative xpath by unique parent ${isIndex ? "index" : ""}`,
|
|
2911
|
-
value: indexedXpath
|
|
2949
|
+
value: indexedXpath,
|
|
2912
2950
|
});
|
|
2913
2951
|
}
|
|
2914
2952
|
}
|
|
@@ -2922,7 +2960,7 @@
|
|
|
2922
2960
|
isTarget ? "target" : "reference",
|
|
2923
2961
|
isIndex ? "index" : "no-index",
|
|
2924
2962
|
nodeXpath,
|
|
2925
|
-
getElementSignature(node)
|
|
2963
|
+
getElementSignature(node),
|
|
2926
2964
|
].join("::");
|
|
2927
2965
|
let nodeCache = parentXpathCache.get(domNode);
|
|
2928
2966
|
if (!nodeCache) {
|
|
@@ -2962,14 +3000,16 @@
|
|
|
2962
3000
|
(isTarget && !isNumberExist(currentNode.textContent))) {
|
|
2963
3001
|
let xpathe;
|
|
2964
3002
|
if (!reWhiteSpace$1.test(currentNode.textContent)) {
|
|
2965
|
-
xpathe =
|
|
2966
|
-
|
|
2967
|
-
|
|
3003
|
+
xpathe =
|
|
3004
|
+
isSvg(currentNode) ?
|
|
3005
|
+
`//*[local-name()='${currentNode.tagName}' and normalize-space(.)=${escapeCharacters(getFilteredText(currentNode))}]`
|
|
3006
|
+
: `//${currentNode.tagName || "*"}[normalize-space(.)=${escapeCharacters(getFilteredText(currentNode))}]`;
|
|
2968
3007
|
}
|
|
2969
3008
|
else {
|
|
2970
|
-
xpathe =
|
|
2971
|
-
|
|
2972
|
-
|
|
3009
|
+
xpathe =
|
|
3010
|
+
isSvg(currentNode) ?
|
|
3011
|
+
`//*[local-name()='${currentNode.tagName}' and .=${escapeCharacters(getFilteredText(currentNode))}]`
|
|
3012
|
+
: `//${currentNode.tagName || "*"}[.=${escapeCharacters(getFilteredText(currentNode))}]`;
|
|
2973
3013
|
}
|
|
2974
3014
|
const othersWithAttr = checkRelativeXpathRelation(xpathe, nodeXpath, node, docmt, isIndex, "parent");
|
|
2975
3015
|
if (othersWithAttr) {
|
|
@@ -2979,7 +3019,7 @@
|
|
|
2979
3019
|
else {
|
|
2980
3020
|
const combinePattern = [];
|
|
2981
3021
|
const contentRes = [
|
|
2982
|
-
...new Set(getFilteredText(currentNode).match(/([^0-9]+)/g))
|
|
3022
|
+
...new Set(getFilteredText(currentNode).match(/([^0-9]+)/g)),
|
|
2983
3023
|
];
|
|
2984
3024
|
const reWhiteSpace = new RegExp(/^[\S]+( [\S]+)*$/gi);
|
|
2985
3025
|
if (contentRes?.length) {
|
|
@@ -2995,8 +3035,8 @@
|
|
|
2995
3035
|
}
|
|
2996
3036
|
}
|
|
2997
3037
|
if (combinePattern?.length) {
|
|
2998
|
-
const xpathe = isSvg(currentNode)
|
|
2999
|
-
|
|
3038
|
+
const xpathe = isSvg(currentNode) ?
|
|
3039
|
+
`//*[local-name()='${currentNode.tagName}' and ${combinePattern.join(" and ")}]`
|
|
3000
3040
|
: `//${currentNode.tagName || "*"}[${combinePattern.join(" and ")}]`;
|
|
3001
3041
|
const othersWithAttr = checkRelativeXpathRelation(xpathe, nodeXpath, node, docmt, isIndex, "parent");
|
|
3002
3042
|
if (othersWithAttr) {
|
|
@@ -3007,8 +3047,8 @@
|
|
|
3007
3047
|
}
|
|
3008
3048
|
// Construct the XPath based on the tag name
|
|
3009
3049
|
if (!hasUniqueAttr) {
|
|
3010
|
-
const xpathe = isSvg(currentNode)
|
|
3011
|
-
|
|
3050
|
+
const xpathe = isSvg(currentNode) ?
|
|
3051
|
+
`/*[local-name()='${currentNode.tagName}']`
|
|
3012
3052
|
: `/${currentNode.tagName}`;
|
|
3013
3053
|
xpathParts.unshift(xpathe);
|
|
3014
3054
|
}
|
|
@@ -3078,8 +3118,8 @@
|
|
|
3078
3118
|
}
|
|
3079
3119
|
if (!uniqueAttrFound) {
|
|
3080
3120
|
// Construct the XPath based on the tag name
|
|
3081
|
-
const xpathe = isSvg(currentNode)
|
|
3082
|
-
|
|
3121
|
+
const xpathe = isSvg(currentNode) ?
|
|
3122
|
+
`/*[local-name()='${currentNode.tagName}']`
|
|
3083
3123
|
: `/${currentNode.tagName}`;
|
|
3084
3124
|
// Prepend the current XPath part to the array
|
|
3085
3125
|
xpathParts.unshift(xpathe);
|
|
@@ -3164,8 +3204,8 @@
|
|
|
3164
3204
|
}
|
|
3165
3205
|
if (!uniqueAttrFound) {
|
|
3166
3206
|
// Construct the XPath based on the tag name
|
|
3167
|
-
const xpathe = isSvg(currentNode)
|
|
3168
|
-
|
|
3207
|
+
const xpathe = isSvg(currentNode) ?
|
|
3208
|
+
`/*[local-name()='${currentNode.tagName}']`
|
|
3169
3209
|
: `/${currentNode.tagName}`;
|
|
3170
3210
|
// Prepend the current XPath part to the array
|
|
3171
3211
|
xpathParts.push(xpathe);
|
|
@@ -3214,7 +3254,7 @@
|
|
|
3214
3254
|
for (const attr of Array.from(sibling.attributes)) {
|
|
3215
3255
|
const xpaths = intermediateXpathSteps(sibling, {
|
|
3216
3256
|
name: attr.name,
|
|
3217
|
-
value: attr.value
|
|
3257
|
+
value: attr.value,
|
|
3218
3258
|
}, isTarget);
|
|
3219
3259
|
for (let xpathe of xpaths) {
|
|
3220
3260
|
xpathe += `/${axis}::${nodeXpath}`;
|
|
@@ -3222,7 +3262,7 @@
|
|
|
3222
3262
|
if (count === 1) {
|
|
3223
3263
|
xpathData$1.push({
|
|
3224
3264
|
key: `xpath by ${axis}`,
|
|
3225
|
-
value: xpathe
|
|
3265
|
+
value: xpathe,
|
|
3226
3266
|
});
|
|
3227
3267
|
return;
|
|
3228
3268
|
}
|
|
@@ -3233,7 +3273,7 @@
|
|
|
3233
3273
|
xpathDataWithIndex$1.push({
|
|
3234
3274
|
key: `relative xpath by ${axis}`,
|
|
3235
3275
|
value: xpathe,
|
|
3236
|
-
count
|
|
3276
|
+
count,
|
|
3237
3277
|
});
|
|
3238
3278
|
}
|
|
3239
3279
|
}
|
|
@@ -3241,7 +3281,7 @@
|
|
|
3241
3281
|
xpathDataWithIndex$1.push({
|
|
3242
3282
|
key: `relative xpath by ${axis}`,
|
|
3243
3283
|
value: xpathe,
|
|
3244
|
-
count
|
|
3284
|
+
count,
|
|
3245
3285
|
});
|
|
3246
3286
|
}
|
|
3247
3287
|
}
|
|
@@ -3252,14 +3292,14 @@
|
|
|
3252
3292
|
let xpathe;
|
|
3253
3293
|
xpathe = intermediateXpathStep(sibling, {
|
|
3254
3294
|
name: "text",
|
|
3255
|
-
value: sibling.textContent
|
|
3295
|
+
value: sibling.textContent,
|
|
3256
3296
|
}, isTarget);
|
|
3257
3297
|
if (xpathe) {
|
|
3258
3298
|
const count = getCountOfXPath(xpathe, sibling, docmt);
|
|
3259
3299
|
if (count === 1) {
|
|
3260
3300
|
xpathData$1.push({
|
|
3261
3301
|
key: `xpath by ${axis}`,
|
|
3262
|
-
value: xpathe
|
|
3302
|
+
value: xpathe,
|
|
3263
3303
|
});
|
|
3264
3304
|
return;
|
|
3265
3305
|
}
|
|
@@ -3270,7 +3310,7 @@
|
|
|
3270
3310
|
xpathDataWithIndex$1.push({
|
|
3271
3311
|
key: `relative xpath by ${axis}`,
|
|
3272
3312
|
value: xpathe,
|
|
3273
|
-
count
|
|
3313
|
+
count,
|
|
3274
3314
|
});
|
|
3275
3315
|
}
|
|
3276
3316
|
}
|
|
@@ -3278,7 +3318,7 @@
|
|
|
3278
3318
|
xpathDataWithIndex$1.push({
|
|
3279
3319
|
key: `relative xpath by ${axis}`,
|
|
3280
3320
|
value: xpathe,
|
|
3281
|
-
count
|
|
3321
|
+
count,
|
|
3282
3322
|
});
|
|
3283
3323
|
}
|
|
3284
3324
|
}
|
|
@@ -3300,20 +3340,17 @@
|
|
|
3300
3340
|
if (hasNumericAttributeValue(normalizedTextContent) && !stableTargetText) {
|
|
3301
3341
|
return;
|
|
3302
3342
|
}
|
|
3303
|
-
const textForPredicate = hasNumericAttributeValue(normalizedTextContent)
|
|
3304
|
-
? stableTargetText
|
|
3305
|
-
: normalizedTextContent;
|
|
3343
|
+
const textForPredicate = hasNumericAttributeValue(normalizedTextContent) ? stableTargetText : (normalizedTextContent);
|
|
3306
3344
|
const containerTextCondition = getContainerTextCondition(targetElemt, textForPredicate);
|
|
3307
3345
|
for (const attrName of attributes) {
|
|
3308
3346
|
if (checkBlockedAttributes(attrName, targetElemt, isTarget)) {
|
|
3309
3347
|
let attrValue = sanitizeAttributeValue(attrName.name, attrName.nodeValue);
|
|
3310
3348
|
const elementName = attrName.name;
|
|
3311
|
-
const textCondition = containerTextCondition
|
|
3312
|
-
?
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
? `normalize-space(.)=${escapeCharacters(textForPredicate)}`
|
|
3349
|
+
const textCondition = containerTextCondition ? containerTextCondition
|
|
3350
|
+
: hasNumericAttributeValue(normalizedTextContent) ?
|
|
3351
|
+
`contains(normalize-space(.),${escapeCharacters(textForPredicate)})`
|
|
3352
|
+
: /\s/.test(textContent) ?
|
|
3353
|
+
`normalize-space(.)=${escapeCharacters(textForPredicate)}`
|
|
3317
3354
|
: `text()=${escapeCharacters(textForPredicate)}`;
|
|
3318
3355
|
const xpath = `//${tagName}[@${elementName}='${attrValue}' and ${textCondition}]`;
|
|
3319
3356
|
if (xpath) {
|
|
@@ -3335,7 +3372,7 @@
|
|
|
3335
3372
|
for (const attrName of attribute) {
|
|
3336
3373
|
const expressions = intermediateXpathSteps(targetElemt, {
|
|
3337
3374
|
name: attrName.name,
|
|
3338
|
-
value: attrName.value
|
|
3375
|
+
value: attrName.value,
|
|
3339
3376
|
}, isTarget);
|
|
3340
3377
|
for (const expression of expressions) {
|
|
3341
3378
|
if (expression) {
|
|
@@ -3347,7 +3384,7 @@
|
|
|
3347
3384
|
if (targetElemt.textContent) {
|
|
3348
3385
|
let expression = intermediateXpathStep(targetElemt, {
|
|
3349
3386
|
name: "text",
|
|
3350
|
-
value: targetElemt.textContent
|
|
3387
|
+
value: targetElemt.textContent,
|
|
3351
3388
|
}, isTarget);
|
|
3352
3389
|
if (expression) {
|
|
3353
3390
|
nodeXpath.push(expression);
|
|
@@ -3372,7 +3409,7 @@
|
|
|
3372
3409
|
if (count === 1) {
|
|
3373
3410
|
xpathData$1.push({
|
|
3374
3411
|
key: "relative xpath by relative parent",
|
|
3375
|
-
value: fullRelativeXpath
|
|
3412
|
+
value: fullRelativeXpath,
|
|
3376
3413
|
});
|
|
3377
3414
|
}
|
|
3378
3415
|
else if (count > 1 && isIndex) {
|
|
@@ -3381,7 +3418,7 @@
|
|
|
3381
3418
|
getCountOfXPath(relativeXpathIndex, targetElemt, docmt) === 1) {
|
|
3382
3419
|
xpathData$1.push({
|
|
3383
3420
|
key: `relative xpath by relative parent ${isIndex ? "index" : ""}`,
|
|
3384
|
-
value: relativeXpathIndex
|
|
3421
|
+
value: relativeXpathIndex,
|
|
3385
3422
|
});
|
|
3386
3423
|
}
|
|
3387
3424
|
}
|
|
@@ -3402,7 +3439,7 @@
|
|
|
3402
3439
|
if (count === 1) {
|
|
3403
3440
|
xpathData$1.push({
|
|
3404
3441
|
key: "relative xpath by relative child",
|
|
3405
|
-
value: fullRelativeXpath
|
|
3442
|
+
value: fullRelativeXpath,
|
|
3406
3443
|
});
|
|
3407
3444
|
}
|
|
3408
3445
|
else if (count > 1 && isIndex) {
|
|
@@ -3411,7 +3448,7 @@
|
|
|
3411
3448
|
getCountOfXPath(relativeXpathIndex, targetElemt, docmt) === 1) {
|
|
3412
3449
|
xpathData$1.push({
|
|
3413
3450
|
key: `relative xpath by relative parent ${isIndex ? "index" : ""}`,
|
|
3414
|
-
value: relativeXpathIndex
|
|
3451
|
+
value: relativeXpathIndex,
|
|
3415
3452
|
});
|
|
3416
3453
|
}
|
|
3417
3454
|
}
|
|
@@ -3425,7 +3462,7 @@
|
|
|
3425
3462
|
if (txtXpath) {
|
|
3426
3463
|
xpathData$1.unshift({
|
|
3427
3464
|
key: `xpath by text${isIndex ? "index" : ""}`,
|
|
3428
|
-
value: txtXpath
|
|
3465
|
+
value: txtXpath,
|
|
3429
3466
|
});
|
|
3430
3467
|
}
|
|
3431
3468
|
}
|
|
@@ -3435,7 +3472,7 @@
|
|
|
3435
3472
|
if (tempRelativeXpath) {
|
|
3436
3473
|
xpathData$1.push({
|
|
3437
3474
|
key: "xpath by unique parent",
|
|
3438
|
-
value: tempRelativeXpath
|
|
3475
|
+
value: tempRelativeXpath,
|
|
3439
3476
|
});
|
|
3440
3477
|
}
|
|
3441
3478
|
}
|
|
@@ -3492,7 +3529,7 @@
|
|
|
3492
3529
|
if (xpth) {
|
|
3493
3530
|
xpathData$1.push({
|
|
3494
3531
|
key: `xpath by ${attr.name}${isIndex ? " index" : ""}`,
|
|
3495
|
-
value: xpth
|
|
3532
|
+
value: xpth,
|
|
3496
3533
|
});
|
|
3497
3534
|
}
|
|
3498
3535
|
}
|
|
@@ -3502,7 +3539,7 @@
|
|
|
3502
3539
|
if (txtXpath) {
|
|
3503
3540
|
xpathData$1.push({
|
|
3504
3541
|
key: `xpath by text${isIndex ? " index" : ""}`,
|
|
3505
|
-
value: txtXpath
|
|
3542
|
+
value: txtXpath,
|
|
3506
3543
|
});
|
|
3507
3544
|
}
|
|
3508
3545
|
if (attributesArray.find((element) => ["className", "class"].includes(element.name)) &&
|
|
@@ -3511,16 +3548,16 @@
|
|
|
3511
3548
|
if (xpath) {
|
|
3512
3549
|
xpathData$1.push({
|
|
3513
3550
|
key: "xpath by class",
|
|
3514
|
-
value: xpath
|
|
3551
|
+
value: xpath,
|
|
3515
3552
|
});
|
|
3516
3553
|
}
|
|
3517
3554
|
}
|
|
3518
|
-
if (
|
|
3555
|
+
if (attributesArray.length > 1) {
|
|
3519
3556
|
const combinationXpath = getAttributeCombinationXpath(targetElemt, docmt, attributesArray, isTarget);
|
|
3520
3557
|
if (combinationXpath)
|
|
3521
3558
|
xpathData$1.push({
|
|
3522
3559
|
key: "xpath by combination",
|
|
3523
|
-
value: combinationXpath
|
|
3560
|
+
value: combinationXpath,
|
|
3524
3561
|
});
|
|
3525
3562
|
}
|
|
3526
3563
|
if (!xpathData$1.length) {
|
|
@@ -3528,7 +3565,7 @@
|
|
|
3528
3565
|
if (textAttribute)
|
|
3529
3566
|
xpathData$1.push({
|
|
3530
3567
|
key: "xpath by textAttribute",
|
|
3531
|
-
value: textAttribute
|
|
3568
|
+
value: textAttribute,
|
|
3532
3569
|
});
|
|
3533
3570
|
}
|
|
3534
3571
|
}
|
|
@@ -3566,7 +3603,7 @@
|
|
|
3566
3603
|
if (count === 1) {
|
|
3567
3604
|
xpathData$1.push({
|
|
3568
3605
|
key: `${xpathData$1[i].key} regex`,
|
|
3569
|
-
value: xpth
|
|
3606
|
+
value: xpth,
|
|
3570
3607
|
});
|
|
3571
3608
|
}
|
|
3572
3609
|
}
|
|
@@ -3590,7 +3627,7 @@
|
|
|
3590
3627
|
getParentRelativeXpath,
|
|
3591
3628
|
getUniqueParentXpath,
|
|
3592
3629
|
checkRelativeXpathRelation,
|
|
3593
|
-
buildAxesStrategyXpaths
|
|
3630
|
+
buildAxesStrategyXpaths,
|
|
3594
3631
|
};
|
|
3595
3632
|
|
|
3596
3633
|
let xpathDataWithIndex = [];
|
|
@@ -3744,7 +3781,9 @@
|
|
|
3744
3781
|
// Share target candidate work across all relation attempts in this run.
|
|
3745
3782
|
const targetCandidateContext = createTargetCandidateContext(element2, docmt, xpaths2);
|
|
3746
3783
|
let finalXpaths = [];
|
|
3747
|
-
const relations = allowedRelations
|
|
3784
|
+
const relations = allowedRelations?.length ?
|
|
3785
|
+
allowedRelations
|
|
3786
|
+
: getSmartRelations(element1, element2);
|
|
3748
3787
|
if (!isIndex) {
|
|
3749
3788
|
// Fresh non-index runs should start from a clean candidate list for this
|
|
3750
3789
|
// reference/target pair; only candidates found in this pass are reusable.
|
|
@@ -5205,6 +5244,19 @@
|
|
|
5205
5244
|
const trimmed = value.trim();
|
|
5206
5245
|
return Boolean(trimmed) && trimmed.length <= 120 && !/^\d+$/.test(trimmed);
|
|
5207
5246
|
};
|
|
5247
|
+
const isStableSemanticId = (el, idAttribute) => {
|
|
5248
|
+
const value = idAttribute.value.trim();
|
|
5249
|
+
if (!isStableAttributeValue(value))
|
|
5250
|
+
return false;
|
|
5251
|
+
const wasModified = modifiedElementAttributes.some((modifiedAttribute) => modifiedAttribute.doc === el.ownerDocument &&
|
|
5252
|
+
modifiedAttribute.element === el &&
|
|
5253
|
+
modifiedAttribute.attributeName === "id");
|
|
5254
|
+
if (wasModified)
|
|
5255
|
+
return false;
|
|
5256
|
+
// Keep semantic levels/versions such as "level2-shadow-host", but reject
|
|
5257
|
+
// IDs containing long numeric runs, which are more likely generated.
|
|
5258
|
+
return !/\d{4,}/.test(value);
|
|
5259
|
+
};
|
|
5208
5260
|
const isCssSelectorUnique = (selector, el, root) => {
|
|
5209
5261
|
try {
|
|
5210
5262
|
const matches = root.querySelectorAll(selector);
|
|
@@ -5262,8 +5314,8 @@
|
|
|
5262
5314
|
return;
|
|
5263
5315
|
const idAttribute = el.getAttributeNode("id");
|
|
5264
5316
|
if (!idAttribute ||
|
|
5265
|
-
!
|
|
5266
|
-
|
|
5317
|
+
(!checkBlockedAttributes(idAttribute, el, true) &&
|
|
5318
|
+
!isStableSemanticId(el, idAttribute))) {
|
|
5267
5319
|
return;
|
|
5268
5320
|
}
|
|
5269
5321
|
return `#${escapeCssIdentifier(el.id, el)}`;
|
|
@@ -5383,6 +5435,59 @@
|
|
|
5383
5435
|
}
|
|
5384
5436
|
return;
|
|
5385
5437
|
};
|
|
5438
|
+
const getNthOfType = (el) => {
|
|
5439
|
+
let index = 1;
|
|
5440
|
+
let sibling = el.previousElementSibling;
|
|
5441
|
+
while (sibling) {
|
|
5442
|
+
if (sibling.tagName === el.tagName)
|
|
5443
|
+
index++;
|
|
5444
|
+
sibling = sibling.previousElementSibling;
|
|
5445
|
+
}
|
|
5446
|
+
return index;
|
|
5447
|
+
};
|
|
5448
|
+
const getRelativeCssSegment = (el) => {
|
|
5449
|
+
const parent = el.parentElement;
|
|
5450
|
+
const tag = el.tagName.toLowerCase();
|
|
5451
|
+
if (parent) {
|
|
5452
|
+
const stableSelectors = getDirectStableSelectors(el, {
|
|
5453
|
+
includeTag: true,
|
|
5454
|
+
preferredAttributesOnly: true
|
|
5455
|
+
})
|
|
5456
|
+
.filter((selector) => isCssSelectorUnique(selector, el, parent))
|
|
5457
|
+
.sort((a, b) => a.length - b.length);
|
|
5458
|
+
if (stableSelectors.length)
|
|
5459
|
+
return stableSelectors[0];
|
|
5460
|
+
const sameTagSiblings = Array.from(parent.children).filter((child) => child.tagName === el.tagName);
|
|
5461
|
+
if (sameTagSiblings.length > 1) {
|
|
5462
|
+
return `${tag}:nth-of-type(${getNthOfType(el)})`;
|
|
5463
|
+
}
|
|
5464
|
+
}
|
|
5465
|
+
return tag;
|
|
5466
|
+
};
|
|
5467
|
+
const getShortestPositionalCssPath = (el) => {
|
|
5468
|
+
if (!isElementNode(el) || isIgnoredTag(el))
|
|
5469
|
+
return;
|
|
5470
|
+
const root = el.getRootNode();
|
|
5471
|
+
const descendantSegments = [];
|
|
5472
|
+
let current = el;
|
|
5473
|
+
while (current && isElementNode(current) && !isIgnoredTag(current)) {
|
|
5474
|
+
const anchorSelectors = getDirectStableSelectors(current, {
|
|
5475
|
+
includeTag: true,
|
|
5476
|
+
preferredAttributesOnly: true
|
|
5477
|
+
});
|
|
5478
|
+
for (const anchorSelector of anchorSelectors) {
|
|
5479
|
+
if (!isCssSelectorUnique(anchorSelector, current, root))
|
|
5480
|
+
continue;
|
|
5481
|
+
const candidate = [anchorSelector, ...descendantSegments].join(" > ");
|
|
5482
|
+
if (isCssSelectorUnique(candidate, el, root)) {
|
|
5483
|
+
return candidate;
|
|
5484
|
+
}
|
|
5485
|
+
}
|
|
5486
|
+
descendantSegments.unshift(getRelativeCssSegment(current));
|
|
5487
|
+
current = current.parentElement;
|
|
5488
|
+
}
|
|
5489
|
+
return getAbsoluteCssPath(el);
|
|
5490
|
+
};
|
|
5386
5491
|
const getAbsoluteCssPath = (el) => {
|
|
5387
5492
|
const view = el.ownerDocument?.defaultView;
|
|
5388
5493
|
if (!view || !(el instanceof view.Element))
|
|
@@ -5410,6 +5515,7 @@
|
|
|
5410
5515
|
getIdCssPath,
|
|
5411
5516
|
getAttributeCssPath,
|
|
5412
5517
|
getClassCssPath,
|
|
5518
|
+
getShortestPositionalCssPath,
|
|
5413
5519
|
getAbsoluteCssPath
|
|
5414
5520
|
};
|
|
5415
5521
|
|