@rpascene/shared 0.30.8
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/README.md +9 -0
- package/dist/es/baseDB.mjs +109 -0
- package/dist/es/build/copy-static.mjs +29 -0
- package/dist/es/common.mjs +37 -0
- package/dist/es/constants/example-code.mjs +202 -0
- package/dist/es/constants/index.mjs +74 -0
- package/dist/es/env/basic.mjs +6 -0
- package/dist/es/env/constants.mjs +97 -0
- package/dist/es/env/decide-model-config.mjs +172 -0
- package/dist/es/env/global-config-manager.mjs +82 -0
- package/dist/es/env/helper.mjs +45 -0
- package/dist/es/env/index.mjs +5 -0
- package/dist/es/env/init-debug.mjs +18 -0
- package/dist/es/env/model-config-manager.mjs +99 -0
- package/dist/es/env/parse.mjs +69 -0
- package/dist/es/env/types.mjs +265 -0
- package/dist/es/env/utils.mjs +18 -0
- package/dist/es/extractor/constants.mjs +2 -0
- package/dist/es/extractor/cs_postmessage.mjs +61 -0
- package/dist/es/extractor/customLocator.mjs +646 -0
- package/dist/es/extractor/debug.mjs +6 -0
- package/dist/es/extractor/dom-util.mjs +92 -0
- package/dist/es/extractor/index.mjs +7 -0
- package/dist/es/extractor/locator.mjs +95 -0
- package/dist/es/extractor/tree.mjs +81 -0
- package/dist/es/extractor/util.mjs +244 -0
- package/dist/es/extractor/web-extractor.mjs +361 -0
- package/dist/es/img/box-select.mjs +184 -0
- package/dist/es/img/draw-box.mjs +42 -0
- package/dist/es/img/get-jimp.mjs +10 -0
- package/dist/es/img/get-photon.mjs +19 -0
- package/dist/es/img/get-sharp.mjs +11 -0
- package/dist/es/img/index.mjs +5 -0
- package/dist/es/img/info.mjs +32 -0
- package/dist/es/img/transform.mjs +192 -0
- package/dist/es/index.mjs +3 -0
- package/dist/es/logger.mjs +61 -0
- package/dist/es/node/fs.mjs +44 -0
- package/dist/es/node/index.mjs +1 -0
- package/dist/es/polyfills/async-hooks.mjs +2 -0
- package/dist/es/polyfills/index.mjs +1 -0
- package/dist/es/types/index.mjs +3 -0
- package/dist/es/us-keyboard-layout.mjs +1414 -0
- package/dist/es/us-keyboard-layout.mjs.LICENSE.txt +5 -0
- package/dist/es/utils.mjs +66 -0
- package/dist/lib/baseDB.js +149 -0
- package/dist/lib/build/copy-static.js +77 -0
- package/dist/lib/common.js +93 -0
- package/dist/lib/constants/example-code.js +239 -0
- package/dist/lib/constants/index.js +153 -0
- package/dist/lib/env/basic.js +40 -0
- package/dist/lib/env/constants.js +143 -0
- package/dist/lib/env/decide-model-config.js +212 -0
- package/dist/lib/env/global-config-manager.js +116 -0
- package/dist/lib/env/helper.js +85 -0
- package/dist/lib/env/index.js +94 -0
- package/dist/lib/env/init-debug.js +52 -0
- package/dist/lib/env/model-config-manager.js +133 -0
- package/dist/lib/env/parse.js +106 -0
- package/dist/lib/env/types.js +650 -0
- package/dist/lib/env/utils.js +61 -0
- package/dist/lib/extractor/constants.js +42 -0
- package/dist/lib/extractor/cs_postmessage.js +98 -0
- package/dist/lib/extractor/customLocator.js +698 -0
- package/dist/lib/extractor/debug.js +12 -0
- package/dist/lib/extractor/dom-util.js +150 -0
- package/dist/lib/extractor/index.js +153 -0
- package/dist/lib/extractor/locator.js +141 -0
- package/dist/lib/extractor/tree.js +127 -0
- package/dist/lib/extractor/util.js +335 -0
- package/dist/lib/extractor/web-extractor.js +407 -0
- package/dist/lib/img/box-select.js +232 -0
- package/dist/lib/img/draw-box.js +89 -0
- package/dist/lib/img/get-jimp.js +72 -0
- package/dist/lib/img/get-photon.js +76 -0
- package/dist/lib/img/get-sharp.js +63 -0
- package/dist/lib/img/index.js +102 -0
- package/dist/lib/img/info.js +86 -0
- package/dist/lib/img/transform.js +279 -0
- package/dist/lib/index.js +43 -0
- package/dist/lib/logger.js +114 -0
- package/dist/lib/node/fs.js +97 -0
- package/dist/lib/node/index.js +60 -0
- package/dist/lib/polyfills/async-hooks.js +36 -0
- package/dist/lib/polyfills/index.js +60 -0
- package/dist/lib/types/index.js +37 -0
- package/dist/lib/us-keyboard-layout.js +1457 -0
- package/dist/lib/us-keyboard-layout.js.LICENSE.txt +5 -0
- package/dist/lib/utils.js +136 -0
- package/dist/types/baseDB.d.ts +25 -0
- package/dist/types/build/copy-static.d.ts +31 -0
- package/dist/types/common.d.ts +12 -0
- package/dist/types/constants/example-code.d.ts +2 -0
- package/dist/types/constants/index.d.ts +23 -0
- package/dist/types/env/basic.d.ts +6 -0
- package/dist/types/env/constants.d.ts +40 -0
- package/dist/types/env/decide-model-config.d.ts +14 -0
- package/dist/types/env/global-config-manager.d.ts +32 -0
- package/dist/types/env/helper.d.ts +6 -0
- package/dist/types/env/index.d.ts +4 -0
- package/dist/types/env/init-debug.d.ts +1 -0
- package/dist/types/env/model-config-manager.d.ts +24 -0
- package/dist/types/env/parse.d.ts +12 -0
- package/dist/types/env/types.d.ts +295 -0
- package/dist/types/env/utils.d.ts +7 -0
- package/dist/types/extractor/constants.d.ts +1 -0
- package/dist/types/extractor/cs_postmessage.d.ts +2 -0
- package/dist/types/extractor/customLocator.d.ts +69 -0
- package/dist/types/extractor/debug.d.ts +1 -0
- package/dist/types/extractor/dom-util.d.ts +26 -0
- package/dist/types/extractor/index.d.ts +36 -0
- package/dist/types/extractor/locator.d.ts +7 -0
- package/dist/types/extractor/tree.d.ts +9 -0
- package/dist/types/extractor/util.d.ts +43 -0
- package/dist/types/extractor/web-extractor.d.ts +19 -0
- package/dist/types/img/box-select.d.ts +25 -0
- package/dist/types/img/draw-box.d.ts +15 -0
- package/dist/types/img/get-jimp.d.ts +2 -0
- package/dist/types/img/get-photon.d.ts +8 -0
- package/dist/types/img/get-sharp.d.ts +3 -0
- package/dist/types/img/index.d.ts +4 -0
- package/dist/types/img/info.d.ts +29 -0
- package/dist/types/img/transform.d.ts +88 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/logger.d.ts +4 -0
- package/dist/types/node/fs.d.ts +15 -0
- package/dist/types/node/index.d.ts +1 -0
- package/dist/types/polyfills/async-hooks.d.ts +6 -0
- package/dist/types/polyfills/index.d.ts +4 -0
- package/dist/types/types/index.d.ts +37 -0
- package/dist/types/us-keyboard-layout.d.ts +32 -0
- package/dist/types/utils.d.ts +22 -0
- package/package.json +102 -0
- package/src/baseDB.ts +158 -0
- package/src/build/copy-static.ts +62 -0
- package/src/common.ts +67 -0
- package/src/constants/example-code.ts +202 -0
- package/src/constants/index.ts +81 -0
- package/src/env/basic.ts +12 -0
- package/src/env/constants.ts +291 -0
- package/src/env/decide-model-config.ts +319 -0
- package/src/env/global-config-manager.ts +174 -0
- package/src/env/helper.ts +80 -0
- package/src/env/index.ts +4 -0
- package/src/env/init-debug.ts +29 -0
- package/src/env/model-config-manager.ts +145 -0
- package/src/env/parse.ts +131 -0
- package/src/env/types.ts +573 -0
- package/src/env/utils.ts +39 -0
- package/src/extractor/constants.ts +5 -0
- package/src/extractor/cs_postmessage.ts +101 -0
- package/src/extractor/customLocator.ts +1138 -0
- package/src/extractor/debug.ts +10 -0
- package/src/extractor/dom-util.ts +141 -0
- package/src/extractor/index.ts +54 -0
- package/src/extractor/locator.ts +179 -0
- package/src/extractor/tree.ts +179 -0
- package/src/extractor/util.ts +468 -0
- package/src/extractor/web-extractor.ts +559 -0
- package/src/img/box-select.ts +346 -0
- package/src/img/draw-box.ts +60 -0
- package/src/img/get-jimp.ts +12 -0
- package/src/img/get-photon.ts +48 -0
- package/src/img/get-sharp.ts +18 -0
- package/src/img/index.ts +24 -0
- package/src/img/info.ts +79 -0
- package/src/img/jimp.d.ts +4 -0
- package/src/img/transform.ts +396 -0
- package/src/index.ts +6 -0
- package/src/logger.ts +93 -0
- package/src/node/fs.ts +84 -0
- package/src/node/index.ts +1 -0
- package/src/polyfills/async-hooks.ts +6 -0
- package/src/polyfills/index.ts +4 -0
- package/src/types/index.ts +53 -0
- package/src/us-keyboard-layout.ts +723 -0
- package/src/utils.ts +127 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { webExtractTextWithPosition } from '.';
|
|
2
|
+
import {
|
|
3
|
+
setExtractTextWithPositionOnWindow,
|
|
4
|
+
setRpasceneVisibleRectOnWindow,
|
|
5
|
+
} from './util';
|
|
6
|
+
|
|
7
|
+
console.log(webExtractTextWithPosition(document.body, true));
|
|
8
|
+
console.log(JSON.stringify(webExtractTextWithPosition(document.body, true)));
|
|
9
|
+
setExtractTextWithPositionOnWindow();
|
|
10
|
+
setRpasceneVisibleRectOnWindow();
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { NodeType } from '../constants';
|
|
2
|
+
import { generateHashId } from '../utils';
|
|
3
|
+
// 是否为表单元素
|
|
4
|
+
export function isFormElement(node: globalThis.Node, currentWindow = globalThis) {
|
|
5
|
+
return (
|
|
6
|
+
node instanceof currentWindow.HTMLElement &&
|
|
7
|
+
(node.tagName.toLowerCase() === 'input' ||
|
|
8
|
+
node.tagName.toLowerCase() === 'textarea' ||
|
|
9
|
+
node.tagName.toLowerCase() === 'select' ||
|
|
10
|
+
node.tagName.toLowerCase() === 'option')
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
// 是否是按钮
|
|
14
|
+
export function isButtonElement(node: globalThis.Node, currentWindow = globalThis): node is globalThis.HTMLButtonElement {
|
|
15
|
+
return node instanceof currentWindow.HTMLElement && node.tagName.toLowerCase() === 'button';
|
|
16
|
+
}
|
|
17
|
+
// 是否a元素
|
|
18
|
+
export function isAElement(node: globalThis.Node, currentWindow = globalThis): node is globalThis.HTMLButtonElement {
|
|
19
|
+
return node instanceof currentWindow.HTMLElement && node.tagName.toLowerCase() === 'a';
|
|
20
|
+
}
|
|
21
|
+
// 是否svg元素
|
|
22
|
+
export function isSvgElement(node: globalThis.Node, currentWindow = globalThis): node is globalThis.SVGSVGElement {
|
|
23
|
+
return node instanceof currentWindow.SVGElement;
|
|
24
|
+
}
|
|
25
|
+
// 是否img元素
|
|
26
|
+
export function isImgElement(node: globalThis.Node, currentWindow = globalThis): node is globalThis.HTMLImageElement {
|
|
27
|
+
// check if the node is an image element
|
|
28
|
+
if (!includeBaseElement(node) && node instanceof currentWindow.Element) {
|
|
29
|
+
const computedStyle = window.getComputedStyle(node);
|
|
30
|
+
const backgroundImage = computedStyle.getPropertyValue('background-image');
|
|
31
|
+
if (backgroundImage !== 'none') {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (isIconfont(node)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
(node instanceof currentWindow.HTMLElement && node.tagName.toLowerCase() === 'img') ||
|
|
42
|
+
(node instanceof currentWindow.SVGElement && node.tagName.toLowerCase() === 'svg')
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
// 是否iconfont图标库
|
|
46
|
+
function isIconfont(node: globalThis.Node, currentWindow = globalThis): boolean {
|
|
47
|
+
if (node instanceof currentWindow.Element) {
|
|
48
|
+
const computedStyle = window.getComputedStyle(node);
|
|
49
|
+
const fontFamilyValue = computedStyle.fontFamily || '';
|
|
50
|
+
return fontFamilyValue.toLowerCase().indexOf('iconfont') >= 0;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
// 不是容器元素
|
|
55
|
+
export function isNotContainerElement(node: globalThis.Node, currentWindow = globalThis) {
|
|
56
|
+
return (
|
|
57
|
+
isTextElement(node, currentWindow) ||
|
|
58
|
+
isIconfont(node, currentWindow) ||
|
|
59
|
+
isImgElement(node, currentWindow) ||
|
|
60
|
+
isButtonElement(node, currentWindow) ||
|
|
61
|
+
isAElement(node, currentWindow) ||
|
|
62
|
+
isFormElement(node, currentWindow)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
// 是否文本元素
|
|
66
|
+
export function isTextElement(node: globalThis.Node, currentWindow = globalThis): node is globalThis.HTMLTextAreaElement {
|
|
67
|
+
if (node instanceof currentWindow.Element) {
|
|
68
|
+
if (node?.childNodes?.length === 1 && node?.childNodes[0] instanceof currentWindow.Text) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return node.nodeName?.toLowerCase?.() === '#text' && !isIconfont(node);
|
|
74
|
+
}
|
|
75
|
+
// 是否容器元素
|
|
76
|
+
export function isContainerElement(node: globalThis.Node, currentWindow = globalThis): node is globalThis.HTMLElement {
|
|
77
|
+
if (!(node instanceof currentWindow.HTMLElement)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// include other base elements
|
|
82
|
+
if (includeBaseElement(node, currentWindow)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const computedStyle = window.getComputedStyle(node);
|
|
87
|
+
const backgroundColor = computedStyle.getPropertyValue('background-color');
|
|
88
|
+
if (backgroundColor) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
// dom元素内部是否包含有基本元素
|
|
95
|
+
function includeBaseElement(node: globalThis.Node, currentWindow = globalThis) {
|
|
96
|
+
if (!(node instanceof currentWindow.HTMLElement)) return false;
|
|
97
|
+
|
|
98
|
+
// include text
|
|
99
|
+
if (node.innerText) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const includeList = [
|
|
104
|
+
'svg',
|
|
105
|
+
'button',
|
|
106
|
+
'input',
|
|
107
|
+
'textarea',
|
|
108
|
+
'select',
|
|
109
|
+
'option',
|
|
110
|
+
'img',
|
|
111
|
+
'a',
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
for (const tagName of includeList) {
|
|
115
|
+
const element = node.querySelectorAll(tagName);
|
|
116
|
+
if (element.length > 0) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
// 根据坐标生成元素
|
|
124
|
+
export function generateElementByPosition(position: { x: number; y: number }) {
|
|
125
|
+
const rect = {
|
|
126
|
+
left: Math.max(position.x - 4, 0),
|
|
127
|
+
top: Math.max(position.y - 4, 0),
|
|
128
|
+
width: 8,
|
|
129
|
+
height: 8,
|
|
130
|
+
};
|
|
131
|
+
const id = generateHashId(rect);
|
|
132
|
+
const element = {
|
|
133
|
+
id,
|
|
134
|
+
attributes: { nodeType: NodeType.POSITION },
|
|
135
|
+
rect,
|
|
136
|
+
content: '',
|
|
137
|
+
center: [position.x, position.y],
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
return element;
|
|
141
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { NodeType } from '../constants/index';
|
|
2
|
+
|
|
3
|
+
export interface ElementInfo {
|
|
4
|
+
id: string;
|
|
5
|
+
indexId: number;
|
|
6
|
+
nodeHashId: string;
|
|
7
|
+
xpaths?: string[];
|
|
8
|
+
attributes: {
|
|
9
|
+
nodeType: NodeType;
|
|
10
|
+
[key: string]: string;
|
|
11
|
+
};
|
|
12
|
+
nodeType: NodeType;
|
|
13
|
+
content: string;
|
|
14
|
+
rect: { left: number; top: number; width: number; height: number };
|
|
15
|
+
center: [number, number];
|
|
16
|
+
isVisible: boolean;
|
|
17
|
+
allPaths?: any;
|
|
18
|
+
containerPaths?: any;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ElementNode {
|
|
22
|
+
node: ElementInfo | null;
|
|
23
|
+
children: ElementNode[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
descriptionOfTree,
|
|
28
|
+
traverseTree,
|
|
29
|
+
treeToList,
|
|
30
|
+
truncateText,
|
|
31
|
+
trimAttributes,
|
|
32
|
+
} from './tree';
|
|
33
|
+
|
|
34
|
+
export { extractTextWithPosition as webExtractTextWithPosition } from './web-extractor';
|
|
35
|
+
|
|
36
|
+
export { extractTreeNode as webExtractNodeTree } from './web-extractor';
|
|
37
|
+
|
|
38
|
+
export { extractTreeNodeAsString as webExtractNodeTreeAsString } from './web-extractor';
|
|
39
|
+
|
|
40
|
+
export { setNodeHashCacheListOnWindow, getNodeFromCacheList } from './util';
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
getXpathsById,
|
|
44
|
+
getXpathsByPoint,
|
|
45
|
+
getNodeInfoByXpath,
|
|
46
|
+
getElementInfoByXpath,
|
|
47
|
+
getElementXpath,
|
|
48
|
+
} from './locator';
|
|
49
|
+
|
|
50
|
+
export { generateElementByPosition } from './dom-util';
|
|
51
|
+
|
|
52
|
+
export { isNotContainerElement } from './dom-util';
|
|
53
|
+
|
|
54
|
+
export * from './customLocator'
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { ElementInfo } from '.';
|
|
2
|
+
import type { Point } from '../types';
|
|
3
|
+
import { isSvgElement } from './dom-util';
|
|
4
|
+
import { getNodeFromCacheList } from './util';
|
|
5
|
+
import { getRect, isElementPartiallyInViewport } from './util';
|
|
6
|
+
import { collectElementInfo } from './web-extractor';
|
|
7
|
+
|
|
8
|
+
const getElementXpathIndex = (element: Element): number => {
|
|
9
|
+
let index = 1;
|
|
10
|
+
let prev = element.previousElementSibling;
|
|
11
|
+
|
|
12
|
+
while (prev) {
|
|
13
|
+
if (prev.nodeName.toLowerCase() === element.nodeName.toLowerCase()) {
|
|
14
|
+
index++;
|
|
15
|
+
}
|
|
16
|
+
prev = prev.previousElementSibling;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return index;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const normalizeXpathText = (text: string): string => {
|
|
23
|
+
if (typeof text !== 'string') {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const buildCurrentElementXpath = (
|
|
31
|
+
element: Element,
|
|
32
|
+
isOrderSensitive: boolean,
|
|
33
|
+
isLeafElement: boolean,
|
|
34
|
+
): string => {
|
|
35
|
+
// Build parent path - inline the buildParentXpath logic
|
|
36
|
+
const parentPath = element.parentNode
|
|
37
|
+
? getElementXpath(element.parentNode, isOrderSensitive)
|
|
38
|
+
: '';
|
|
39
|
+
const prefix = parentPath ? `${parentPath}/` : '/';
|
|
40
|
+
const tagName = element.nodeName.toLowerCase();
|
|
41
|
+
const textContent = element.textContent?.trim();
|
|
42
|
+
|
|
43
|
+
// Order-sensitive mode: always use index
|
|
44
|
+
if (isOrderSensitive) {
|
|
45
|
+
const index = getElementXpathIndex(element);
|
|
46
|
+
return `${prefix}${tagName}[${index}]`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Order-insensitive mode:
|
|
50
|
+
// - Leaf elements: try text first, fallback to index if no text
|
|
51
|
+
// - Non-leaf elements: always use index
|
|
52
|
+
if (isLeafElement && textContent) {
|
|
53
|
+
return `${prefix}${tagName}[normalize-space()="${normalizeXpathText(textContent)}"]`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Fallback to index (for non-leaf elements or leaf elements without text)
|
|
57
|
+
const index = getElementXpathIndex(element);
|
|
58
|
+
return `${prefix}${tagName}[${index}]`;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const getElementXpath = (
|
|
62
|
+
element: Node,
|
|
63
|
+
isOrderSensitive = false,
|
|
64
|
+
isLeafElement = false,
|
|
65
|
+
): string => {
|
|
66
|
+
// process text node
|
|
67
|
+
if (element.nodeType === Node.TEXT_NODE) {
|
|
68
|
+
const parentNode = element.parentNode;
|
|
69
|
+
if (parentNode && parentNode.nodeType === Node.ELEMENT_NODE) {
|
|
70
|
+
// For text nodes, treat parent as leaf element to enable text matching
|
|
71
|
+
const parentXPath = getElementXpath(parentNode, isOrderSensitive, true);
|
|
72
|
+
const textContent = element.textContent?.trim();
|
|
73
|
+
if (textContent) {
|
|
74
|
+
return `${parentXPath}/text()[normalize-space()="${normalizeXpathText(textContent)}"]`;
|
|
75
|
+
}
|
|
76
|
+
return `${parentXPath}/text()`;
|
|
77
|
+
}
|
|
78
|
+
return '';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// process element node
|
|
82
|
+
if (element.nodeType !== Node.ELEMENT_NODE) return '';
|
|
83
|
+
|
|
84
|
+
// process element node - at this point, element should be an Element
|
|
85
|
+
const el = element as Element;
|
|
86
|
+
|
|
87
|
+
// special element handling
|
|
88
|
+
if (el === document.documentElement) return '/html';
|
|
89
|
+
if (el === document.body) return '/html/body';
|
|
90
|
+
|
|
91
|
+
// if the element is any SVG element, find the nearest non-SVG ancestor
|
|
92
|
+
if (isSvgElement(el)) {
|
|
93
|
+
let parent = el.parentNode;
|
|
94
|
+
while (parent && parent.nodeType === Node.ELEMENT_NODE) {
|
|
95
|
+
if (!isSvgElement(parent)) {
|
|
96
|
+
return getElementXpath(parent, isOrderSensitive, isLeafElement);
|
|
97
|
+
}
|
|
98
|
+
parent = parent.parentNode;
|
|
99
|
+
}
|
|
100
|
+
// fallback if no non-SVG parent found
|
|
101
|
+
return getElementXpath(el.parentNode!, isOrderSensitive, isLeafElement);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// decide which format to use
|
|
105
|
+
return buildCurrentElementXpath(el, isOrderSensitive, isLeafElement);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export function getXpathsById(id: string): string[] | null {
|
|
109
|
+
const node = getNodeFromCacheList(id);
|
|
110
|
+
|
|
111
|
+
if (!node) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const fullXPath = getElementXpath(node, false, true);
|
|
116
|
+
return [fullXPath];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function getXpathsByPoint(
|
|
120
|
+
point: Point,
|
|
121
|
+
isOrderSensitive: boolean,
|
|
122
|
+
): string[] | null {
|
|
123
|
+
const element = document.elementFromPoint(point.left, point.top);
|
|
124
|
+
|
|
125
|
+
if (!element) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const fullXPath = getElementXpath(element, isOrderSensitive, true);
|
|
130
|
+
return [fullXPath];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function getNodeInfoByXpath(xpath: string): Node | null {
|
|
134
|
+
const xpathResult = document.evaluate(
|
|
135
|
+
xpath,
|
|
136
|
+
document,
|
|
137
|
+
null,
|
|
138
|
+
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
|
|
139
|
+
null,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
if (xpathResult.snapshotLength !== 1) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const node = xpathResult.snapshotItem(0);
|
|
147
|
+
|
|
148
|
+
return node;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function getElementInfoByXpath(xpath: string): ElementInfo | null {
|
|
152
|
+
const node = getNodeInfoByXpath(xpath);
|
|
153
|
+
|
|
154
|
+
if (!node) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (node instanceof HTMLElement) {
|
|
159
|
+
// only when the element is not completely in the viewport, call scrollIntoView
|
|
160
|
+
const rect = getRect(node, 1, window);
|
|
161
|
+
const isVisible = isElementPartiallyInViewport(rect, window, document, 1);
|
|
162
|
+
|
|
163
|
+
if (!isVisible) {
|
|
164
|
+
node.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return collectElementInfo(
|
|
169
|
+
node,
|
|
170
|
+
window,
|
|
171
|
+
document,
|
|
172
|
+
1,
|
|
173
|
+
{
|
|
174
|
+
left: 0,
|
|
175
|
+
top: 0,
|
|
176
|
+
},
|
|
177
|
+
true,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { BaseElement, ElementTreeNode } from '../types';
|
|
2
|
+
|
|
3
|
+
export function truncateText(
|
|
4
|
+
text: string | number | object | undefined,
|
|
5
|
+
maxLength = 150,
|
|
6
|
+
) {
|
|
7
|
+
if (typeof text === 'undefined') {
|
|
8
|
+
return '';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (typeof text === 'object') {
|
|
12
|
+
text = JSON.stringify(text);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (typeof text === 'number') {
|
|
16
|
+
return text.toString();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (typeof text === 'string' && text.length > maxLength) {
|
|
20
|
+
return `${text.slice(0, maxLength)}...`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (typeof text === 'string') {
|
|
24
|
+
return text.trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function trimAttributes(
|
|
31
|
+
attributes: Record<string, any>,
|
|
32
|
+
truncateTextLength?: number,
|
|
33
|
+
) {
|
|
34
|
+
const tailorAttributes = Object.keys(attributes).reduce(
|
|
35
|
+
(res, currentKey: string) => {
|
|
36
|
+
const attributeVal = (attributes as any)[currentKey];
|
|
37
|
+
if (
|
|
38
|
+
currentKey === 'style' ||
|
|
39
|
+
currentKey === 'htmlTagName' ||
|
|
40
|
+
currentKey === 'nodeType'
|
|
41
|
+
) {
|
|
42
|
+
return res;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
res[currentKey] = truncateText(attributeVal, truncateTextLength);
|
|
46
|
+
return res;
|
|
47
|
+
},
|
|
48
|
+
{} as BaseElement['attributes'],
|
|
49
|
+
);
|
|
50
|
+
return tailorAttributes;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const nodeSizeThreshold = 4;
|
|
54
|
+
export function descriptionOfTree<
|
|
55
|
+
ElementType extends BaseElement = BaseElement,
|
|
56
|
+
>(
|
|
57
|
+
tree: ElementTreeNode<ElementType>,
|
|
58
|
+
truncateTextLength?: number,
|
|
59
|
+
filterNonTextContent = false,
|
|
60
|
+
visibleOnly = true,
|
|
61
|
+
) {
|
|
62
|
+
const attributesString = (kv: Record<string, any>) => {
|
|
63
|
+
return Object.entries(kv)
|
|
64
|
+
.map(
|
|
65
|
+
([key, value]) => `${key}="${truncateText(value, truncateTextLength)}"`,
|
|
66
|
+
)
|
|
67
|
+
.join(' ');
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function buildContentTree(
|
|
71
|
+
node: ElementTreeNode<ElementType>,
|
|
72
|
+
indent = 0,
|
|
73
|
+
visibleOnly = true,
|
|
74
|
+
): string {
|
|
75
|
+
let before = '';
|
|
76
|
+
let contentWithIndent = '';
|
|
77
|
+
let after = '';
|
|
78
|
+
let emptyNode = true;
|
|
79
|
+
const indentStr = ' '.repeat(indent);
|
|
80
|
+
|
|
81
|
+
let children = '';
|
|
82
|
+
for (let i = 0; i < (node.children || []).length; i++) {
|
|
83
|
+
const childContent = buildContentTree(
|
|
84
|
+
node.children[i],
|
|
85
|
+
indent + 1,
|
|
86
|
+
visibleOnly,
|
|
87
|
+
);
|
|
88
|
+
if (childContent) {
|
|
89
|
+
children += `\n${childContent}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
node.node &&
|
|
95
|
+
node.node.rect.width > nodeSizeThreshold &&
|
|
96
|
+
node.node.rect.height > nodeSizeThreshold &&
|
|
97
|
+
(!filterNonTextContent || (filterNonTextContent && node.node.content)) &&
|
|
98
|
+
(!visibleOnly || (visibleOnly && node.node.isVisible))
|
|
99
|
+
) {
|
|
100
|
+
emptyNode = false;
|
|
101
|
+
let nodeTypeString: string;
|
|
102
|
+
if (node.node.attributes?.htmlTagName) {
|
|
103
|
+
nodeTypeString = node.node.attributes.htmlTagName.replace(/[<>]/g, '');
|
|
104
|
+
} else {
|
|
105
|
+
nodeTypeString = node.node.attributes.nodeType
|
|
106
|
+
.replace(/\sNode$/, '')
|
|
107
|
+
.toLowerCase();
|
|
108
|
+
}
|
|
109
|
+
const markerId = node.node.indexId;
|
|
110
|
+
const markerIdString = markerId ? `markerId="${markerId}"` : '';
|
|
111
|
+
const rectAttribute = node.node.rect
|
|
112
|
+
? {
|
|
113
|
+
left: node.node.rect.left,
|
|
114
|
+
top: node.node.rect.top,
|
|
115
|
+
width: node.node.rect.width,
|
|
116
|
+
height: node.node.rect.height,
|
|
117
|
+
}
|
|
118
|
+
: {};
|
|
119
|
+
before = `<${nodeTypeString} id="${node.node.id}" ${markerIdString} ${attributesString(trimAttributes(node.node.attributes || {}, truncateTextLength))} ${attributesString(rectAttribute)}>`;
|
|
120
|
+
const content = truncateText(node.node.content, truncateTextLength);
|
|
121
|
+
contentWithIndent = content ? `\n${indentStr} ${content}` : '';
|
|
122
|
+
after = `</${nodeTypeString}>`;
|
|
123
|
+
} else if (!filterNonTextContent) {
|
|
124
|
+
if (!children.trim().startsWith('<>')) {
|
|
125
|
+
before = '<>';
|
|
126
|
+
contentWithIndent = '';
|
|
127
|
+
after = '</>';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (emptyNode && !children.trim()) {
|
|
132
|
+
return '';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const result = `${indentStr}${before}${contentWithIndent}${children}\n${indentStr}${after}`;
|
|
136
|
+
if (result.trim()) {
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
return '';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const result = buildContentTree(tree, 0, visibleOnly);
|
|
143
|
+
return result.replace(/^\s*\n/gm, '');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function treeToList<T extends BaseElement>(
|
|
147
|
+
tree: ElementTreeNode<T>,
|
|
148
|
+
): T[] {
|
|
149
|
+
const result: T[] = [];
|
|
150
|
+
function dfs(node: ElementTreeNode<T>) {
|
|
151
|
+
if (node.node) {
|
|
152
|
+
result.push(node.node);
|
|
153
|
+
}
|
|
154
|
+
for (const child of node.children) {
|
|
155
|
+
dfs(child);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
dfs(tree);
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function traverseTree<
|
|
163
|
+
T extends BaseElement,
|
|
164
|
+
ReturnNodeType extends BaseElement,
|
|
165
|
+
>(
|
|
166
|
+
tree: ElementTreeNode<T>,
|
|
167
|
+
onNode: (node: T) => ReturnNodeType,
|
|
168
|
+
): ElementTreeNode<ReturnNodeType> {
|
|
169
|
+
function dfs(node: ElementTreeNode<T>) {
|
|
170
|
+
if (node.node) {
|
|
171
|
+
node.node = onNode(node.node) as any;
|
|
172
|
+
}
|
|
173
|
+
for (const child of node.children) {
|
|
174
|
+
dfs(child);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
dfs(tree);
|
|
178
|
+
return tree as any;
|
|
179
|
+
}
|