mirror of
https://github.com/guoriyue/AutoMouser.git
synced 2026-06-03 21:02:31 +08:00
201 lines
6.6 KiB
JavaScript
201 lines
6.6 KiB
JavaScript
// XPath generation utilities - Multiple algorithms for robust element identification
|
|
|
|
/**
|
|
* Generate multiple XPath expressions for an element
|
|
* Returns array of unique, validated XPaths
|
|
*/
|
|
export function getXpaths(element) {
|
|
if (!element || !element.tagName) return [];
|
|
|
|
var paths = [];
|
|
paths.push(getElementInfo_Custom(element, false));
|
|
paths.push(getElementInfo_Custom(element, true));
|
|
paths.push(getElementInfo_Moz(element));
|
|
|
|
// Deduplicate paths
|
|
var unique = paths.filter(function(path, index, self) {
|
|
return path && self.indexOf(path) === index;
|
|
});
|
|
|
|
// Validate each XPath
|
|
unique = unique.filter(function(xpath) {
|
|
try {
|
|
var result = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null);
|
|
var count = 0;
|
|
var node = result.iterateNext();
|
|
while (node) {
|
|
count++;
|
|
node = result.iterateNext();
|
|
}
|
|
return count === 1; // Only return unique XPaths
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
});
|
|
|
|
return unique;
|
|
}
|
|
|
|
/**
|
|
* Custom XPath generation with optional attribute-based targeting
|
|
*/
|
|
export function getElementInfo_Custom(element, useAttributes) {
|
|
var getSiblingInfo = function(element, useAttributes) {
|
|
var info = { num: 0, index: 1 };
|
|
var prevCount = 0;
|
|
var nextCount = 0;
|
|
var prevSibling = element.previousSibling;
|
|
var nextSibling = element.nextSibling;
|
|
|
|
if (useAttributes) {
|
|
while (prevSibling !== null) {
|
|
if (prevSibling.id === element.id &&
|
|
prevSibling.tagName === element.tagName &&
|
|
prevSibling.name === element.name &&
|
|
prevSibling.className === element.className &&
|
|
prevSibling.type === element.type) {
|
|
prevCount += 1;
|
|
}
|
|
prevSibling = prevSibling.previousSibling;
|
|
}
|
|
while (nextSibling !== null) {
|
|
if (nextSibling.id === element.id &&
|
|
nextSibling.tagName === element.tagName &&
|
|
nextSibling.name === element.name &&
|
|
nextSibling.className === element.className &&
|
|
nextSibling.type === element.type) {
|
|
nextCount += 1;
|
|
}
|
|
nextSibling = nextSibling.nextSibling;
|
|
}
|
|
} else {
|
|
while (prevSibling !== null) {
|
|
if (prevSibling.tagName === element.tagName) {
|
|
prevCount += 1;
|
|
}
|
|
prevSibling = prevSibling.previousSibling;
|
|
}
|
|
while (nextSibling !== null) {
|
|
if (nextSibling.tagName === element.tagName) {
|
|
nextCount += 1;
|
|
}
|
|
nextSibling = nextSibling.nextSibling;
|
|
}
|
|
}
|
|
|
|
info.num = prevCount + nextCount;
|
|
info.index += prevCount;
|
|
return info;
|
|
};
|
|
|
|
var getElementString = function(element, useAttributes) {
|
|
// Check if element has tagName property
|
|
if (!element || !element.tagName) {
|
|
return '';
|
|
}
|
|
var tagName = element.tagName.toLowerCase();
|
|
var siblingInfo = getSiblingInfo(element, useAttributes);
|
|
|
|
if (useAttributes) {
|
|
var attributes = [];
|
|
var attrNames = ["@type=", "@class=", "@name=", "@id="];
|
|
var attrValues = [element.type, element.className, element.name, element.id];
|
|
|
|
for (var i = attrNames.length - 1; i >= 0; i--) {
|
|
if (typeof attrValues[i] !== "undefined" && attrValues[i] !== "") {
|
|
attributes.push(attrNames[i] + "'" + attrValues[i] + "'");
|
|
}
|
|
}
|
|
|
|
if (attributes.length > 0) {
|
|
tagName += "[" + attributes.join(" and ") + "]";
|
|
}
|
|
}
|
|
|
|
if (siblingInfo.num > 0) {
|
|
tagName += "[" + siblingInfo.index + "]";
|
|
}
|
|
|
|
return tagName;
|
|
};
|
|
|
|
var buildPath = function(element, useAttributes) {
|
|
var path = getElementString(element, useAttributes);
|
|
if (!path) {
|
|
return '';
|
|
}
|
|
var parent = element.parentNode;
|
|
|
|
while (parent && parent.tagName) {
|
|
if (!useAttributes) {
|
|
if (element.id !== "") {
|
|
return "//" + element.tagName.toLowerCase() + "[@id='" + element.id + "']";
|
|
}
|
|
if (parent.id !== "") {
|
|
return "//" + parent.tagName.toLowerCase() + "[@id='" + parent.id + "']/" + path;
|
|
}
|
|
}
|
|
path = getElementString(parent, useAttributes) + "/" + path;
|
|
parent = parent.parentNode;
|
|
}
|
|
|
|
return path.replace(/^((\/){0,2}html)/, "/html");
|
|
};
|
|
|
|
return buildPath(element, useAttributes);
|
|
}
|
|
|
|
/**
|
|
* Mozilla-style XPath generation (more standardized approach)
|
|
*/
|
|
export function getElementInfo_Moz(element) {
|
|
if (element && element.id) {
|
|
return '//*[@id="' + element.id + '"]';
|
|
}
|
|
|
|
var paths = [];
|
|
for (; element && element.nodeType === Node.ELEMENT_NODE; element = element.parentNode) {
|
|
var index = 0;
|
|
var hasFollowingSiblings = false;
|
|
|
|
for (var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling) {
|
|
if (sibling.nodeType === Node.DOCUMENT_TYPE_NODE) continue;
|
|
if (sibling.nodeName === element.nodeName) ++index;
|
|
}
|
|
|
|
for (var sibling = element.nextSibling; sibling && !hasFollowingSiblings; sibling = sibling.nextSibling) {
|
|
if (sibling.nodeName === element.nodeName) hasFollowingSiblings = true;
|
|
}
|
|
|
|
var tagName = (element.prefix ? element.prefix + ":" : "") + element.localName;
|
|
var pathIndex = "[" + (index + 1) + "]";
|
|
paths.splice(0, 0, tagName + pathIndex);
|
|
}
|
|
|
|
return paths.length ? "/" + paths.join("/") : null;
|
|
}
|
|
|
|
/**
|
|
* Validate XPath syntax and uniqueness
|
|
*/
|
|
export function validateXPath(xpath) {
|
|
try {
|
|
const result = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null);
|
|
let count = 0;
|
|
let node = result.iterateNext();
|
|
while (node) {
|
|
count++;
|
|
node = result.iterateNext();
|
|
}
|
|
return {
|
|
valid: true,
|
|
count: count,
|
|
isUnique: count === 1
|
|
};
|
|
} catch (e) {
|
|
return {
|
|
valid: false,
|
|
error: e.message
|
|
};
|
|
}
|
|
} |