ff-dom 1.0.17 → 1.0.18-beta.2

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.
@@ -0,0 +1,1105 @@
1
+ import {
2
+ isNumberExist,
3
+ getCountOfXPath,
4
+ escapeCharacters,
5
+ checkBlockedAttributes,
6
+ replaceWhiteSpaces,
7
+ getTextContent,
8
+ getPropertyXPath,
9
+ findXpathWithIndex,
10
+ getFilteredText,
11
+ intermediateXpathStep,
12
+ getAttributeCombinationXpath,
13
+ getFilteredTextXPath,
14
+ isSvg,
15
+ getXpathString,
16
+ reWhiteSpace,
17
+ modifiedElementAttributes,
18
+ } from "./xpathHelpers.ts";
19
+
20
+ let xpathData: { key: string; value: string }[] = [];
21
+ let xpathDataWithIndex: { key: string; value: string; count: number }[] = [];
22
+ let referenceElementMode: boolean = false;
23
+ let xpathCache = new Map();
24
+ let cache = new Map();
25
+
26
+ const parentXpathCache = new Map(); // Cache for parent XPaths
27
+
28
+ const checkRelativeXpathRelation = (
29
+ nodeXpath1: string,
30
+ nodeXpath2: string,
31
+ targetElemt: HTMLElement | Element,
32
+ docmt: Document,
33
+ isIndex: boolean,
34
+ relationType: string
35
+ ) => {
36
+ if (nodeXpath1 && !referenceElementMode) {
37
+ let xpaths;
38
+
39
+ if (relationType === "parent") {
40
+ xpaths = [
41
+ // `${nodeXpath1}/descendant::${nodeXpath2}`,
42
+ `${nodeXpath1}/descendant-or-self::${nodeXpath2}`,
43
+ `${nodeXpath1}/following::${nodeXpath2}`,
44
+ ];
45
+ } else {
46
+ xpaths = [
47
+ // `${nodeXpath1}/descendant::${nodeXpath2}`,
48
+ `${nodeXpath1}/ancestor-or-self::${nodeXpath2}`,
49
+ `${nodeXpath1}/preceding::${nodeXpath2}`,
50
+ ];
51
+ }
52
+
53
+ // Iterate through XPath patterns
54
+ for (const xpath of xpaths) {
55
+ // Check if result is already cached to avoid recomputation
56
+ if (!xpathCache?.get(xpath)) {
57
+ // Compute and store result in cache
58
+ xpathCache.set(xpath, getCountOfXPath(xpath, targetElemt, docmt));
59
+ }
60
+
61
+ const count = xpathCache?.get(xpath);
62
+
63
+ // Short-circuit: Return the first valid XPath result found
64
+ if (count === 1) {
65
+ return xpath;
66
+ }
67
+ if (count > 1) {
68
+ if (xpathDataWithIndex.length) {
69
+ if (count < xpathDataWithIndex[0].count) {
70
+ xpathDataWithIndex.pop();
71
+ xpathDataWithIndex.push({
72
+ key: `relative xpath by unique parent ${isIndex ? "index" : ""}`,
73
+ value: xpath,
74
+ count,
75
+ });
76
+ }
77
+ } else {
78
+ xpathDataWithIndex.push({
79
+ key: `relative xpath by unique parent ${isIndex ? "index" : ""}`,
80
+ value: xpath,
81
+ count,
82
+ });
83
+ }
84
+ }
85
+
86
+ if (count > 1 && isIndex && !xpathData.length) {
87
+ // Try finding XPath with index if count is greater than 1
88
+ const indexedXpath = findXpathWithIndex(
89
+ xpath,
90
+ targetElemt,
91
+ docmt,
92
+ count
93
+ );
94
+
95
+ // Cache the indexed XPath result
96
+ if (
97
+ indexedXpath &&
98
+ getCountOfXPath(indexedXpath, targetElemt, docmt) === 1
99
+ ) {
100
+ xpathData.push({
101
+ key: `relative xpath by unique parent ${isIndex ? "index" : ""}`,
102
+ value: indexedXpath,
103
+ });
104
+ }
105
+ }
106
+ }
107
+ }
108
+ return null;
109
+ };
110
+
111
+ const getUniqueParentXpath = (
112
+ domNode: HTMLElement,
113
+ docmt: Document,
114
+ node: HTMLElement | Element,
115
+ isTarget: boolean,
116
+ nodeXpath: string,
117
+ isIndex: boolean
118
+ ) => {
119
+ try {
120
+ if (parentXpathCache.has(domNode)) {
121
+ return parentXpathCache.get(domNode);
122
+ }
123
+
124
+ // Direct XPath construction without loops
125
+ const xpathParts = [];
126
+ let currentNode = domNode;
127
+
128
+ while (currentNode && currentNode.nodeType === 1) {
129
+ const hasUniqueAttr = false;
130
+ for (const attrName of Array.from(currentNode.attributes)) {
131
+ if (checkBlockedAttributes(attrName, currentNode, isTarget)) {
132
+ let attrValue = attrName.nodeValue;
133
+
134
+ attrValue = attrValue?.replace("removePointers", "") ?? "";
135
+ const elementName = attrName.name;
136
+ const xpathe = getXpathString(currentNode, elementName, attrValue);
137
+
138
+ let othersWithAttr;
139
+
140
+ // If the XPath does not parse, move to the next unique attribute
141
+ try {
142
+ othersWithAttr = checkRelativeXpathRelation(
143
+ xpathe,
144
+ nodeXpath,
145
+ node,
146
+ docmt,
147
+ isIndex,
148
+ "parent"
149
+ );
150
+ } catch (ign) {
151
+ continue;
152
+ }
153
+
154
+ // If the attribute isn't actually unique, get it's index too
155
+ if (othersWithAttr) {
156
+ return othersWithAttr;
157
+ }
158
+ }
159
+ }
160
+
161
+ if (currentNode.textContent && !currentNode.textContent) {
162
+ if (
163
+ !isTarget ||
164
+ (isTarget && !isNumberExist(currentNode.textContent))
165
+ ) {
166
+ let xpathe;
167
+
168
+ if (!reWhiteSpace.test(currentNode.textContent)) {
169
+ xpathe = isSvg(currentNode)
170
+ ? `//*[local-name()='${
171
+ currentNode.tagName
172
+ }' and normalize-space(.)=${escapeCharacters(
173
+ getFilteredText(currentNode)
174
+ )}]`
175
+ : `//${
176
+ currentNode.tagName || "*"
177
+ }[normalize-space(.)=${escapeCharacters(
178
+ getFilteredText(currentNode)
179
+ )}]`;
180
+ } else {
181
+ xpathe = isSvg(currentNode)
182
+ ? `//*[local-name()='${
183
+ currentNode.tagName
184
+ }' and .=${escapeCharacters(getFilteredText(currentNode))}]`
185
+ : `//${currentNode.tagName || "*"}[.=${escapeCharacters(
186
+ getFilteredText(currentNode)
187
+ )}]`;
188
+ }
189
+
190
+ const othersWithAttr = checkRelativeXpathRelation(
191
+ xpathe,
192
+ nodeXpath,
193
+ node,
194
+ docmt,
195
+ isIndex,
196
+ "parent"
197
+ );
198
+ if (othersWithAttr) {
199
+ return othersWithAttr;
200
+ }
201
+ } else {
202
+ const combinePattern = [];
203
+ const contentRes = [
204
+ ...new Set(getFilteredText(currentNode).match(/([^0-9]+)/g)),
205
+ ];
206
+ const reWhiteSpace = new RegExp(/^[\S]+( [\S]+)*$/gi);
207
+ if (contentRes?.length) {
208
+ for (let i = 0; i < contentRes?.length; i++) {
209
+ if (contentRes[i] && replaceWhiteSpaces(contentRes[i].trim())) {
210
+ if (!reWhiteSpace.test(contentRes[i])) {
211
+ combinePattern.push(
212
+ `contains(.,${escapeCharacters(
213
+ replaceWhiteSpaces(contentRes[i])
214
+ ).trim()})`
215
+ );
216
+ } else {
217
+ combinePattern.push(
218
+ `contains(.,${escapeCharacters(
219
+ contentRes[i].trim()
220
+ ).trim()})`
221
+ );
222
+ }
223
+ }
224
+ }
225
+ }
226
+
227
+ if (combinePattern?.length) {
228
+ const xpathe = isSvg(currentNode)
229
+ ? `//*[local-name()='${
230
+ currentNode.tagName
231
+ }' and ${combinePattern.join(" and ")}]`
232
+ : `//${currentNode.tagName || "*"}[${combinePattern.join(
233
+ " and "
234
+ )}]`;
235
+ const othersWithAttr = checkRelativeXpathRelation(
236
+ xpathe,
237
+ nodeXpath,
238
+ node,
239
+ docmt,
240
+ isIndex,
241
+ "parent"
242
+ );
243
+ if (othersWithAttr) {
244
+ return othersWithAttr;
245
+ }
246
+ }
247
+ }
248
+ }
249
+
250
+ // Construct the XPath based on the tag name
251
+ if (!hasUniqueAttr) {
252
+ const xpathe = isSvg(currentNode)
253
+ ? `/*[local-name()='${currentNode.tagName}']`
254
+ : `/${currentNode.tagName}`;
255
+
256
+ xpathParts.unshift(xpathe);
257
+ } else {
258
+ break;
259
+ }
260
+
261
+ // Move to the parent node for the next iteration
262
+ currentNode = currentNode.parentElement!;
263
+ }
264
+
265
+ // Final constructed XPath
266
+ const finalXPath = xpathParts.join("");
267
+ let count = getCountOfXPath(finalXPath, domNode, docmt);
268
+ if (count === 1) {
269
+ parentXpathCache.set(domNode, finalXPath); // Cache final result
270
+ return finalXPath;
271
+ }
272
+ } catch (error) {
273
+ console.error(error);
274
+ return null;
275
+ }
276
+ };
277
+
278
+ const getParentRelativeXpath = (
279
+ domNode: HTMLElement,
280
+ docmt: Document,
281
+ node: HTMLElement | Element,
282
+ isTarget: boolean
283
+ ) => {
284
+ const cache = new Map(); // Cache to store computed results
285
+
286
+ if (cache.has(domNode)) {
287
+ return cache.get(domNode); // Return cached result if available
288
+ }
289
+
290
+ const xpathParts = []; // Initialize an array to hold parts of the XPath
291
+ let currentNode = domNode; // Start with the provided DOM node
292
+
293
+ try {
294
+ while (currentNode && currentNode.nodeType === 1) {
295
+ // BASE CASE #1: If this isn't an element, we're above the root, return empty string
296
+ if (!currentNode.tagName) {
297
+ return "";
298
+ }
299
+
300
+ // BASE CASE #2: Check for unique attributes
301
+ let uniqueAttrFound = false;
302
+ for (const attr of Array.from(currentNode.attributes)) {
303
+ if (checkBlockedAttributes(attr, currentNode, isTarget)) {
304
+ let attrValue = attr.nodeValue;
305
+
306
+ attrValue = attrValue?.replace("removePointers", "") ?? "";
307
+ const elementName = attr.name;
308
+
309
+ const xpathe = getXpathString(currentNode, elementName, attrValue);
310
+
311
+ let othersWithAttr;
312
+
313
+ // If the XPath does not parse, move to the next unique attribute
314
+ try {
315
+ othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);
316
+ } catch (ign) {
317
+ continue;
318
+ }
319
+
320
+ // If the attribute is unique, return its XPath
321
+ if (othersWithAttr === 1) {
322
+ xpathParts.unshift(xpathe);
323
+ uniqueAttrFound = true; // Mark that we found at least one unique attribute
324
+ break;
325
+ }
326
+ }
327
+ }
328
+
329
+ // If no unique attributes, check for text content
330
+ if (!uniqueAttrFound && currentNode.textContent && !node.textContent) {
331
+ const textXPath = getFilteredTextXPath(currentNode, docmt);
332
+ if (textXPath) {
333
+ const othersWithAttr = getCountOfXPath(textXPath, currentNode, docmt);
334
+ if (othersWithAttr === 1) {
335
+ uniqueAttrFound = true; // Mark that we found at least one unique attribute
336
+ xpathParts.unshift(textXPath);
337
+ break;
338
+ }
339
+ }
340
+ }
341
+
342
+ if (!uniqueAttrFound) {
343
+ // Construct the XPath based on the tag name
344
+ const xpathe = isSvg(currentNode)
345
+ ? `/*[local-name()='${currentNode.tagName}']`
346
+ : `/${currentNode.tagName}`;
347
+
348
+ // Prepend the current XPath part to the array
349
+ xpathParts.unshift(xpathe);
350
+ } else {
351
+ break;
352
+ }
353
+ // Move to the parent node for the next iteration
354
+ currentNode = currentNode.parentElement!;
355
+ }
356
+
357
+ // Combine all parts into the final XPath
358
+ const finalXpath = xpathParts.join("");
359
+ cache.set(domNode, finalXpath); // Store result in cache
360
+ return finalXpath;
361
+ } catch (error) {
362
+ console.log(error);
363
+ return null;
364
+ }
365
+ };
366
+
367
+ const getChildRelativeXpath = (
368
+ domNode: HTMLElement | Element,
369
+ docmt: Document,
370
+ node: HTMLElement | Element
371
+ ) => {
372
+ const xpathParts = []; // Initialize an array to hold parts of the XPath.
373
+ let currentNode: HTMLElement | Element | null;
374
+
375
+ const st = [];
376
+ if (
377
+ domNode.firstElementChild != null &&
378
+ domNode.firstElementChild.classList.contains("flntooltip")
379
+ ) {
380
+ st.unshift(domNode.firstElementChild);
381
+ } else if (domNode.nextElementSibling != null)
382
+ st.unshift(domNode.nextElementSibling);
383
+
384
+ for (
385
+ let m = domNode.parentElement;
386
+ m != null && m.nodeType === 1;
387
+ m = m.parentElement
388
+ ) {
389
+ if (m.nextElementSibling) st.unshift(m.nextElementSibling);
390
+ }
391
+
392
+ try {
393
+ do {
394
+ let uniqueAttrFound = false;
395
+ for (currentNode = st.pop()!; currentNode !== null; ) {
396
+ for (const attr of Array.from(currentNode.attributes)) {
397
+ if (checkBlockedAttributes(attr, currentNode, true)) {
398
+ let attrValue = attr.nodeValue;
399
+
400
+ attrValue = attrValue?.replace("removePointers", "") ?? "";
401
+ const elementName = attr.name;
402
+
403
+ const xpathe = getXpathString(currentNode, elementName, attrValue);
404
+
405
+ let othersWithAttr;
406
+
407
+ // If the XPath does not parse, move to the next unique attribute
408
+ try {
409
+ othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);
410
+ } catch (ign) {
411
+ continue;
412
+ }
413
+
414
+ // If the attribute is unique, return its XPath
415
+ if (othersWithAttr === 1) {
416
+ uniqueAttrFound = true; // Mark that we found at least one unique attribute
417
+ xpathParts.push(xpathe);
418
+ break;
419
+ }
420
+ }
421
+ }
422
+
423
+ // If no unique attributes, check for text content
424
+ if (!uniqueAttrFound && currentNode.textContent && !node.textContent) {
425
+ const textXPath = getFilteredTextXPath(currentNode, docmt);
426
+ if (textXPath) {
427
+ const othersWithAttr = getCountOfXPath(
428
+ textXPath,
429
+ currentNode,
430
+ docmt
431
+ );
432
+ if (othersWithAttr === 1) {
433
+ uniqueAttrFound = true; // Mark that we found at least one unique attribute
434
+ xpathParts.push(textXPath);
435
+ break;
436
+ }
437
+ }
438
+ }
439
+
440
+ if (!uniqueAttrFound) {
441
+ // Construct the XPath based on the tag name
442
+ const xpathe = isSvg(currentNode)
443
+ ? `/*[local-name()='${currentNode.tagName}']`
444
+ : `/${currentNode.tagName}`;
445
+
446
+ // Prepend the current XPath part to the array
447
+ xpathParts.push(xpathe);
448
+
449
+ if (currentNode.firstElementChild != null) {
450
+ st.push(currentNode.nextElementSibling);
451
+ currentNode = currentNode.firstElementChild;
452
+ } else {
453
+ currentNode = currentNode.nextElementSibling;
454
+ }
455
+ } else {
456
+ break;
457
+ }
458
+ }
459
+ } while (st.length > 0);
460
+
461
+ // Combine all parts into the final XPath
462
+ const finalXpath = xpathParts.join("");
463
+ cache.set(domNode, finalXpath); // Store result in cache
464
+ return finalXpath;
465
+ } catch (error) {
466
+ console.log(error);
467
+ return null;
468
+ }
469
+ };
470
+
471
+ const getSiblingRelativeXPath = (
472
+ domNode: HTMLElement | Element,
473
+ docmt: Document,
474
+ isTarget: boolean,
475
+ nodeXpath: string
476
+ ) => {
477
+ try {
478
+ const markedSpan = document.querySelector(".flntooltip");
479
+
480
+ for (
481
+ let m = domNode.nextElementSibling;
482
+ m !== null && m !== markedSpan;
483
+ m = m.nextElementSibling
484
+ ) {
485
+ processSibling(
486
+ m,
487
+ domNode,
488
+ docmt,
489
+ nodeXpath,
490
+ "preceding-sibling",
491
+ isTarget
492
+ );
493
+ }
494
+
495
+ for (
496
+ let n = domNode.previousElementSibling;
497
+ n !== null && n !== markedSpan;
498
+ n = n.previousElementSibling
499
+ ) {
500
+ processSibling(
501
+ n,
502
+ domNode,
503
+ docmt,
504
+ nodeXpath,
505
+ "following-sibling",
506
+ isTarget
507
+ );
508
+ }
509
+ } catch (error) {
510
+ console.error("sibling error", error);
511
+ return null;
512
+ }
513
+ };
514
+
515
+ const processSibling = (
516
+ sibling: Element,
517
+ domNode: HTMLElement | Element,
518
+ docmt: Document,
519
+ nodeXpath: string,
520
+ axis: string,
521
+ isTarget: boolean
522
+ ) => {
523
+ try {
524
+ if (sibling.hasAttributes()) {
525
+ for (const attr of Array.from(sibling.attributes)) {
526
+ let xpathe = intermediateXpathStep(
527
+ sibling,
528
+ {
529
+ name: attr.name,
530
+ value: attr.value,
531
+ },
532
+ isTarget
533
+ );
534
+ if (xpathe) {
535
+ xpathe += `/${axis}::${nodeXpath}`;
536
+
537
+ const count = getCountOfXPath(xpathe, sibling, docmt);
538
+
539
+ if (count === 1) {
540
+ xpathData.push({
541
+ key: `xpath by ${axis}`,
542
+ value: xpathe,
543
+ });
544
+ return;
545
+ } else if (count > 1) {
546
+ if (xpathDataWithIndex.length) {
547
+ if (count < xpathDataWithIndex[0].count) {
548
+ xpathDataWithIndex.pop();
549
+ xpathDataWithIndex.push({
550
+ key: `relative xpath by ${axis}`,
551
+ value: xpathe,
552
+ count,
553
+ });
554
+ }
555
+ } else {
556
+ xpathDataWithIndex.push({
557
+ key: `relative xpath by ${axis}`,
558
+ value: xpathe,
559
+ count,
560
+ });
561
+ }
562
+ }
563
+ }
564
+ }
565
+ }
566
+
567
+ if (!isTarget) {
568
+ let xpathe;
569
+ xpathe = intermediateXpathStep(
570
+ sibling,
571
+ {
572
+ name: "text",
573
+ value: sibling.textContent,
574
+ },
575
+ isTarget
576
+ );
577
+
578
+ if (xpathe) {
579
+ const count = getCountOfXPath(xpathe, sibling, docmt);
580
+
581
+ if (count === 1) {
582
+ xpathData.push({
583
+ key: `xpath by ${axis}`,
584
+ value: xpathe,
585
+ });
586
+ return;
587
+ } else if (count > 1) {
588
+ if (xpathDataWithIndex.length) {
589
+ if (count < xpathDataWithIndex[0].count) {
590
+ xpathDataWithIndex.pop();
591
+ xpathDataWithIndex.push({
592
+ key: `relative xpath by ${axis}`,
593
+ value: xpathe,
594
+ count,
595
+ });
596
+ }
597
+ } else {
598
+ xpathDataWithIndex.push({
599
+ key: `relative xpath by ${axis}`,
600
+ value: xpathe,
601
+ count,
602
+ });
603
+ }
604
+ }
605
+ }
606
+ }
607
+ } catch (error) {
608
+ console.log(`${axis} xpath-error`, error);
609
+ }
610
+ };
611
+
612
+ function getXPathUsingAttributeAndText(
613
+ attributes: Attr[],
614
+ targetElemt: HTMLElement | Element,
615
+ docmt: Document,
616
+ isTarget: boolean
617
+ ) {
618
+ const { tagName } = targetElemt;
619
+ const textContent = targetElemt.textContent.trim();
620
+ for (const attrName of attributes) {
621
+ if (checkBlockedAttributes(attrName, targetElemt, isTarget)) {
622
+ let attrValue = attrName.nodeValue;
623
+
624
+ attrValue = attrValue?.replace("removePointers", "") ?? "";
625
+ const elementName = attrName.name;
626
+ const xpath = `//${tagName}[@${elementName}='${attrValue}' and text()='${textContent}']`;
627
+ if (xpath) {
628
+ const count = getCountOfXPath(xpath, targetElemt, docmt);
629
+ if (count == 1) {
630
+ return xpath;
631
+ }
632
+ }
633
+ }
634
+ }
635
+ }
636
+
637
+ const addRelativeXpaths = (
638
+ targetElemt: HTMLElement | Element,
639
+ docmt: Document,
640
+ isIndex: boolean,
641
+ isTarget: boolean,
642
+ attribute: Attr[]
643
+ ) => {
644
+ try {
645
+ let nodeXpath: string[] = [];
646
+ let relativeXpath, relativeChildXpath;
647
+
648
+ if (!xpathData.length && isIndex) {
649
+ if (xpathDataWithIndex.length) {
650
+ const xpathWithIndex = findXpathWithIndex(
651
+ xpathDataWithIndex[0].value,
652
+ targetElemt,
653
+ docmt,
654
+ xpathDataWithIndex[0].count
655
+ );
656
+ if (xpathWithIndex) {
657
+ xpathData.push({
658
+ key: xpathDataWithIndex[0].key,
659
+ value: xpathWithIndex,
660
+ });
661
+ xpathDataWithIndex.pop();
662
+ }
663
+ }
664
+ }
665
+
666
+ console.log(attribute);
667
+ if (!xpathData.length) {
668
+ if (targetElemt.attributes) {
669
+ for (const attrName of attribute) {
670
+ let expression = intermediateXpathStep(
671
+ targetElemt,
672
+ {
673
+ name: attrName.name,
674
+ value: attrName.value,
675
+ },
676
+ isTarget
677
+ );
678
+
679
+ console.log(expression);
680
+ if (expression) {
681
+ nodeXpath.push(expression);
682
+ }
683
+ }
684
+ }
685
+
686
+ if (targetElemt.textContent) {
687
+ let expression = intermediateXpathStep(
688
+ targetElemt,
689
+ {
690
+ name: "text",
691
+ value: targetElemt.textContent,
692
+ },
693
+ isTarget
694
+ );
695
+
696
+ console.log(expression);
697
+ if (expression) {
698
+ nodeXpath.push(expression);
699
+ }
700
+ }
701
+
702
+ if (nodeXpath?.length) {
703
+ for (let i = 0; i < nodeXpath.length; i++) {
704
+ if (!xpathData.length) {
705
+ getSiblingRelativeXPath(targetElemt, docmt, isTarget, nodeXpath[i]);
706
+
707
+ if (!xpathData.length) {
708
+ if (!relativeXpath) {
709
+ relativeXpath = getParentRelativeXpath(
710
+ targetElemt.parentElement!,
711
+ docmt,
712
+ targetElemt,
713
+ isTarget
714
+ );
715
+ }
716
+
717
+ console.log(relativeXpath);
718
+
719
+ if (
720
+ relativeXpath &&
721
+ (relativeXpath.includes("@") ||
722
+ relativeXpath.includes("text()") ||
723
+ relativeXpath.includes(".=")) &&
724
+ relativeXpath.match(/\//g)?.length - 2 < 5
725
+ ) {
726
+ const fullRelativeXpath = relativeXpath + `/${nodeXpath[i]}`;
727
+ const count = getCountOfXPath(
728
+ fullRelativeXpath,
729
+ targetElemt,
730
+ docmt
731
+ );
732
+
733
+ if (count === 1) {
734
+ xpathData.push({
735
+ key: "relative xpath by relative parent",
736
+ value: fullRelativeXpath,
737
+ });
738
+ } else if (count > 1 && isIndex) {
739
+ const relativeXpathIndex = findXpathWithIndex(
740
+ fullRelativeXpath,
741
+ targetElemt,
742
+ docmt,
743
+ count
744
+ );
745
+ if (
746
+ relativeXpathIndex &&
747
+ getCountOfXPath(relativeXpathIndex, targetElemt, docmt) ===
748
+ 1
749
+ ) {
750
+ xpathData.push({
751
+ key: `relative xpath by relative parent ${
752
+ isIndex ? "index" : ""
753
+ }`,
754
+ value: relativeXpathIndex,
755
+ });
756
+ }
757
+ } else if (count > 1) {
758
+ if (xpathDataWithIndex.length) {
759
+ if (count < xpathDataWithIndex[0].count) {
760
+ xpathDataWithIndex.pop();
761
+ xpathDataWithIndex.push({
762
+ key: `relative xpath by relative parent ${
763
+ isIndex ? "index" : ""
764
+ }`,
765
+ value: fullRelativeXpath,
766
+ count,
767
+ });
768
+ }
769
+ } else {
770
+ xpathDataWithIndex.push({
771
+ key: `relative xpath by relative parent ${
772
+ isIndex ? "index" : ""
773
+ }`,
774
+ value: fullRelativeXpath,
775
+ count,
776
+ });
777
+ }
778
+ }
779
+ }
780
+ }
781
+
782
+ if (!xpathData.length) {
783
+ if (!relativeChildXpath) {
784
+ relativeChildXpath = getChildRelativeXpath(
785
+ targetElemt,
786
+ docmt,
787
+ targetElemt
788
+ );
789
+ }
790
+
791
+ if (
792
+ relativeChildXpath &&
793
+ (relativeChildXpath.includes("@") ||
794
+ relativeChildXpath.includes("text()") ||
795
+ relativeChildXpath.includes(".="))
796
+ ) {
797
+ const fullRelativeXpath = `/${
798
+ nodeXpath[i] + relativeChildXpath.substring(1)
799
+ }`;
800
+ const count = getCountOfXPath(
801
+ fullRelativeXpath,
802
+ targetElemt,
803
+ docmt
804
+ );
805
+
806
+ if (count === 1) {
807
+ xpathData.push({
808
+ key: "relative xpath by relative child",
809
+ value: fullRelativeXpath,
810
+ });
811
+ } else if (count > 1 && isIndex) {
812
+ const relativeXpathIndex = findXpathWithIndex(
813
+ fullRelativeXpath,
814
+ targetElemt,
815
+ docmt,
816
+ count
817
+ );
818
+ if (
819
+ relativeXpathIndex &&
820
+ getCountOfXPath(relativeXpathIndex, targetElemt, docmt) ===
821
+ 1
822
+ ) {
823
+ xpathData.push({
824
+ key: `relative xpath by relative parent ${
825
+ isIndex ? "index" : ""
826
+ }`,
827
+ value: relativeXpathIndex,
828
+ });
829
+ }
830
+ } else if (count > 1) {
831
+ if (xpathDataWithIndex.length) {
832
+ if (count < xpathDataWithIndex[0].count) {
833
+ xpathDataWithIndex.pop();
834
+ xpathDataWithIndex.push({
835
+ key: `relative xpath by relative child ${
836
+ isIndex ? "index" : ""
837
+ }`,
838
+ value: fullRelativeXpath,
839
+ count,
840
+ });
841
+ }
842
+ } else {
843
+ xpathDataWithIndex.push({
844
+ key: `relative xpath by relative child ${
845
+ isIndex ? "index" : ""
846
+ }`,
847
+ value: fullRelativeXpath,
848
+ count,
849
+ });
850
+ }
851
+ }
852
+ }
853
+ }
854
+
855
+ if (
856
+ xpathData?.length === 1 &&
857
+ xpathData?.[0]?.value?.match(/\[([0-9]+)\]/gm)?.length! > 3 &&
858
+ !referenceElementMode
859
+ ) {
860
+ if (targetElemt.textContent) {
861
+ const txtXpath = getTextXPath(
862
+ targetElemt,
863
+ docmt,
864
+ isIndex,
865
+ false
866
+ );
867
+ if (txtXpath) {
868
+ xpathData.unshift({
869
+ key: `xpath by text${isIndex ? "index" : ""}`,
870
+ value: txtXpath,
871
+ });
872
+ }
873
+ }
874
+ }
875
+
876
+ if (!xpathData.length) {
877
+ let tempRelativeXpath = getUniqueParentXpath(
878
+ targetElemt.parentElement!,
879
+ docmt,
880
+ targetElemt,
881
+ isTarget,
882
+ nodeXpath[i],
883
+ isIndex
884
+ );
885
+
886
+ if (tempRelativeXpath) {
887
+ xpathData.push({
888
+ key: "xpath by unique parent",
889
+ value: tempRelativeXpath,
890
+ });
891
+ }
892
+ }
893
+ }
894
+ }
895
+ }
896
+ }
897
+
898
+ return xpathData;
899
+ } catch (error) {
900
+ console.log(error);
901
+ }
902
+ };
903
+
904
+ export const attributesBasedXPath = (
905
+ attr: Attr,
906
+ targetElemt: HTMLElement | Element,
907
+ docmt: Document,
908
+ isIndex: boolean,
909
+ isTarget: boolean
910
+ ) => {
911
+ let attrName;
912
+
913
+ attrName = attr.name;
914
+ let xpath = getPropertyXPath(
915
+ targetElemt,
916
+ docmt,
917
+ `@${attrName}`,
918
+ attr.value,
919
+ isIndex,
920
+ isTarget
921
+ );
922
+
923
+ return xpath;
924
+ };
925
+
926
+ export const getUniqueClassName = (
927
+ element: HTMLElement | Element,
928
+ docmt: Document,
929
+ isIndex: boolean,
930
+ isTarget: boolean
931
+ ) => {
932
+ let value = element.className;
933
+ if (typeof value !== "string") {
934
+ value = "";
935
+ }
936
+ value = value?.replace("flndisabled", "disabled");
937
+ value = value?.replace("removePointers", "");
938
+ value = value?.trim();
939
+
940
+ if (value) {
941
+ return getPropertyXPath(element, docmt, `@class`, value, isIndex, isTarget);
942
+ }
943
+ };
944
+
945
+ export const getTextXPath = (
946
+ element: HTMLElement | Element,
947
+ docmt: Document,
948
+ isIndex: boolean,
949
+ isTarget: boolean
950
+ ) => {
951
+ if (element.textContent != "") {
952
+ const text = getTextContent(element);
953
+
954
+ if (text) {
955
+ return getPropertyXPath(element, docmt, ".", text, isIndex, isTarget);
956
+ }
957
+ }
958
+ };
959
+
960
+ const addAllXPathAttributes = (
961
+ attributes: Attr[],
962
+ targetElemt: HTMLElement | Element,
963
+ docmt: Document,
964
+ isIndex: boolean,
965
+ isTarget: boolean
966
+ ) => {
967
+ const attributesArray = attributes;
968
+ try {
969
+ attributesArray.map((attr) => {
970
+ if (!(attr.name === "className" || attr.name === "class")) {
971
+ if (checkBlockedAttributes(attr, targetElemt, isTarget)) {
972
+ const xpth = attributesBasedXPath(
973
+ attr,
974
+ targetElemt,
975
+ docmt,
976
+ isIndex,
977
+ isTarget
978
+ );
979
+ if (xpth) {
980
+ xpathData.push({
981
+ key: `xpath by ${attr.name}${isIndex ? " index" : ""}`,
982
+ value: xpth,
983
+ });
984
+ }
985
+ }
986
+ }
987
+ });
988
+
989
+ const txtXpath = getTextXPath(targetElemt, docmt, isIndex, isTarget);
990
+ if (txtXpath) {
991
+ xpathData.push({
992
+ key: `xpath by text${isIndex ? " index" : ""}`,
993
+ value: txtXpath,
994
+ });
995
+ }
996
+
997
+ if (
998
+ attributesArray.find((element) => element.name === "className") &&
999
+ checkBlockedAttributes(
1000
+ attributesArray?.find((element) => element.name === "className")!,
1001
+ targetElemt,
1002
+ isTarget
1003
+ )
1004
+ ) {
1005
+ let xpath = getUniqueClassName(targetElemt, docmt, isIndex, isTarget);
1006
+ if (xpath) {
1007
+ xpathData.push({
1008
+ key: "xpath by class",
1009
+ value: xpath,
1010
+ });
1011
+ }
1012
+ }
1013
+
1014
+ if (!xpathData.length) {
1015
+ const textAttribute = getXPathUsingAttributeAndText(
1016
+ attributes,
1017
+ targetElemt,
1018
+ docmt,
1019
+ isTarget
1020
+ );
1021
+ if (textAttribute)
1022
+ xpathData.push({
1023
+ key: "xpath by textAttribute",
1024
+ value: textAttribute,
1025
+ });
1026
+ }
1027
+
1028
+ if (!xpathData.length && attributesArray.length > 1) {
1029
+ const combinationXpath = getAttributeCombinationXpath(
1030
+ targetElemt,
1031
+ docmt,
1032
+ attributesArray,
1033
+ isTarget
1034
+ );
1035
+ if (combinationXpath)
1036
+ xpathData.push({
1037
+ key: "xpath by combination",
1038
+ value: combinationXpath,
1039
+ });
1040
+ }
1041
+ } catch (error) {
1042
+ console.log(error);
1043
+ }
1044
+ };
1045
+
1046
+ export const parseDOM = (
1047
+ element: HTMLElement | Element,
1048
+ doc: Document,
1049
+ isIndex: boolean,
1050
+ isTarget: boolean
1051
+ ) => {
1052
+ xpathData = [];
1053
+ console.log(element);
1054
+ const targetElemt = element;
1055
+ const docmt = targetElemt?.ownerDocument || doc;
1056
+ const tag = targetElemt.tagName;
1057
+ const { attributes } = targetElemt;
1058
+ addAllXPathAttributes(Array.from(attributes), targetElemt, docmt, isIndex, isTarget);
1059
+ if (!referenceElementMode) {
1060
+ if (xpathData.length) {
1061
+ const len = xpathData.length;
1062
+ for (let i = 0; i < len; i++) {
1063
+ let xpth = xpathData[i].value;
1064
+ xpth = xpth.replace(tag, "*");
1065
+ const count = getCountOfXPath(xpth, element, docmt);
1066
+ if (count === 1) {
1067
+ xpathData.push({
1068
+ key: `${xpathData[i].key} regex`,
1069
+ value: xpth,
1070
+ });
1071
+ }
1072
+ }
1073
+ }
1074
+ }
1075
+
1076
+ console.log(xpathData);
1077
+ if (!xpathData.length) {
1078
+ addRelativeXpaths(
1079
+ targetElemt,
1080
+ docmt,
1081
+ isIndex,
1082
+ isTarget,
1083
+ Array.from(targetElemt.attributes)
1084
+ );
1085
+ }
1086
+
1087
+ return xpathData;
1088
+ };
1089
+
1090
+ const xpath = {
1091
+ parseDOM,
1092
+ getTextXPath,
1093
+ getUniqueClassName,
1094
+ attributesBasedXPath,
1095
+ addAllXPathAttributes,
1096
+ addRelativeXpaths,
1097
+ getXPathUsingAttributeAndText,
1098
+ getSiblingRelativeXPath,
1099
+ getChildRelativeXpath,
1100
+ getParentRelativeXpath,
1101
+ getUniqueParentXpath,
1102
+ checkRelativeXpathRelation,
1103
+ };
1104
+
1105
+ export default xpath;