|
2575 | 2575 | xpath += `[${classConditions}]`; |
2576 | 2576 | } |
2577 | 2577 |
|
| 2578 | + let semanticParent = 'unknown'; |
| 2579 | + let isNavOrFooter = false; |
| 2580 | + let parent = group.representative.parentElement; |
| 2581 | + let parentDepth = 0; |
| 2582 | + while (parent && parentDepth < 20) { |
| 2583 | + const pTag = parent.tagName.toLowerCase(); |
| 2584 | + if (['main', 'article', 'section', 'nav', 'aside', 'footer', 'header'].includes(pTag)) { |
| 2585 | + semanticParent = pTag; |
| 2586 | + if (['nav', 'footer', 'header'].includes(pTag)) { |
| 2587 | + isNavOrFooter = true; |
| 2588 | + } |
| 2589 | + break; |
| 2590 | + } |
| 2591 | + parent = parent.parentElement; |
| 2592 | + parentDepth++; |
| 2593 | + } |
| 2594 | + |
| 2595 | + const allChildren = Array.from(group.representative.querySelectorAll('*')); |
| 2596 | + const uniqueTags = new Set(allChildren.map(el => el.tagName.toLowerCase())); |
| 2597 | + const childTagCount = uniqueTags.size; |
| 2598 | + |
| 2599 | + const attributeCount = allChildren.reduce((count, el) => { |
| 2600 | + if (el.hasAttribute('href')) count++; |
| 2601 | + if (el.hasAttribute('src')) count++; |
| 2602 | + if (el.hasAttribute('data-src')) count++; |
| 2603 | + return count; |
| 2604 | + }, 0); |
| 2605 | + |
2578 | 2606 | const sampleTexts = group.elements.slice(0, 3).map((el) => { |
2579 | | - return (el.textContent || '').trim().substring(0, 200); |
| 2607 | + return (el.textContent || '').replace(/\s+/g, ' ').trim().substring(0, 300); |
2580 | 2608 | }); |
2581 | 2609 |
|
2582 | 2610 | const sampleHTML = group.representative.outerHTML.substring(0, 500); |
2583 | 2611 |
|
| 2612 | + let ariaRole = null; |
| 2613 | + let roleAncestor = group.representative; |
| 2614 | + let roleDepth = 0; |
| 2615 | + while (roleAncestor && roleDepth < 5) { |
| 2616 | + const r = roleAncestor.getAttribute && roleAncestor.getAttribute('role'); |
| 2617 | + if (r) { |
| 2618 | + const norm = r.toLowerCase(); |
| 2619 | + if (['main', 'navigation', 'contentinfo', 'banner', 'complementary', 'article', 'region', 'search'].includes(norm)) { |
| 2620 | + ariaRole = norm; |
| 2621 | + break; |
| 2622 | + } |
| 2623 | + } |
| 2624 | + roleAncestor = roleAncestor.parentElement; |
| 2625 | + roleDepth++; |
| 2626 | + } |
| 2627 | + |
| 2628 | + const textLengths = group.elements.slice(0, 10).map(el => { |
| 2629 | + return (el.textContent || '').replace(/\s+/g, ' ').trim().length; |
| 2630 | + }); |
| 2631 | + const avgTextLength = textLengths.length > 0 |
| 2632 | + ? Math.round(textLengths.reduce((a, b) => a + b, 0) / textLengths.length) |
| 2633 | + : 0; |
| 2634 | + |
| 2635 | + const repText = (group.representative.textContent || '').replace(/\s+/g, ' ').trim(); |
| 2636 | + const linkTextChars = Array.from(group.representative.querySelectorAll('a')) |
| 2637 | + .reduce((sum, a) => sum + (a.textContent || '').replace(/\s+/g, ' ').trim().length, 0); |
| 2638 | + const linkTextRatio = repText.length > 0 |
| 2639 | + ? Math.min(1, linkTextChars / repText.length) |
| 2640 | + : 0; |
| 2641 | + |
| 2642 | + const headingCount = group.representative.querySelectorAll('h1, h2, h3, h4, h5, h6').length; |
| 2643 | + |
2584 | 2644 | uniqueGroups.set(signature, { |
2585 | 2645 | fingerprint: group.fingerprint, |
2586 | 2646 | count: group.elements.length, |
2587 | 2647 | xpath: xpath, |
2588 | 2648 | sampleTexts: sampleTexts, |
2589 | 2649 | sampleHTML: sampleHTML, |
| 2650 | + semanticParent: semanticParent, |
| 2651 | + isNavOrFooter: isNavOrFooter, |
| 2652 | + childTagCount: childTagCount, |
| 2653 | + attributeCount: attributeCount, |
| 2654 | + ariaRole: ariaRole, |
| 2655 | + avgTextLength: avgTextLength, |
| 2656 | + linkTextRatio: Math.round(linkTextRatio * 100) / 100, |
| 2657 | + headingCount: headingCount |
2590 | 2658 | }); |
2591 | 2659 | } |
2592 | 2660 | }); |
|
0 commit comments