Greasy Fork is available in English.

XPath工具

按Shift+X在鼠标位置显示输入框,并提供实时反馈和高级配置选项操作XPath元素

// ==UserScript==
// @name         XPath工具
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  按Shift+X在鼠标位置显示输入框,并提供实时反馈和高级配置选项操作XPath元素
// @author       Ace
// @match        https://*/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    let toolbar = null;
    let currentElement = null;
    let isShiftPressed = false;
    let originalBackgroundColor = '';

    const SHOW_KEY = 'KeyX';
    const SELECT_KEY = 'ShiftLeft';

    function getXPath(element) {
        if (element.id) {
            return `//*[@id="${element.id}"]`;
        }
        if (element === document.body) {
            return '/html/body';
        }

        let ix = 0;
        const siblings = element.parentNode.childNodes;
        for (let i = 0; siblings[i]; i++) {
            const sibling = siblings[i];
            if (sibling === element) {
                return `${getXPath(element.parentNode)}/${element.tagName.toLowerCase()}[${ix + 1}]`;
            }
            if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {
                ix++;
            }
        }
    }

    function highlightElement(element) {
        if (currentElement) {
            currentElement.style.backgroundColor = originalBackgroundColor;
        }
        originalBackgroundColor = element.style.backgroundColor;
        element.style.backgroundColor = '#FFF59D';
        currentElement = element;
    }

    function clearHighlight() {
        if (currentElement) {
            currentElement.style.backgroundColor = originalBackgroundColor;
            currentElement = null;
        }
    }

    document.addEventListener('keydown', (event) => {
        if (event.key === 'Shift') {
            isShiftPressed = true;
        }
    });

    document.addEventListener('keyup', (event) => {
        if (event.key === 'Shift') {
            isShiftPressed = false;
            clearHighlight();
        }
    });

    document.addEventListener('mouseover', (event) => {
        if (isShiftPressed) {
            highlightElement(event.target);
        }
    });

    document.addEventListener('mouseout', (event) => {
        if (isShiftPressed && currentElement === event.target) {
            clearHighlight();
        }
    });

    document.addEventListener('click', (event) => {
        if (isShiftPressed) {
            event.preventDefault();
            const xpath = getXPath(event.target);
            document.getElementById('custom-xpath-input').value = xpath;
        }
    });

    function createToolbar() {
        if (document.getElementById('custom-toolbar')) return;

        toolbar = document.createElement('div');
        toolbar.id = 'custom-toolbar';
        toolbar.style.position = 'fixed';
        toolbar.style.zIndex = '9999';
        toolbar.style.backgroundColor = 'rgba(33, 33, 33, 0.95)';
        toolbar.style.color = '#fff';
        toolbar.style.padding = '15px';
        toolbar.style.borderRadius = '8px';
        toolbar.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
        toolbar.style.display = 'none';

        const input = document.createElement('input');
        input.id = 'custom-xpath-input';
        input.type = 'text';
        input.placeholder = '输入XPath';
        input.style.width = '250px';
        input.style.padding = '8px';
        input.style.border = '1px solid #ccc';
        input.style.borderRadius = '4px';
        input.style.color = '#000';
        input.style.backgroundColor = '#fff';
        toolbar.appendChild(input);

        const button = document.createElement('button');
        button.innerText = '删除';
        button.style.marginLeft = '10px';
        button.style.padding = '8px 12px';
        button.style.backgroundColor = '#e74c3c';
        button.style.color = '#fff';
        button.style.border = 'none';
        button.style.borderRadius = '4px';
        button.style.cursor = 'pointer';
        button.style.transition = 'background-color 0.3s';
        button.onmouseover = function () {
            button.style.backgroundColor = '#c0392b';
        };
        button.onmouseout = function () {
            button.style.backgroundColor = '#e74c3c';
        };
        button.onclick = function () {
            const userXPath = input.value;
            if (!userXPath) {
                alert('请输入XPath');
                return;
            }

            const element = document.evaluate(
                userXPath,
                document,
                null,
                XPathResult.FIRST_ORDERED_NODE_TYPE,
                null
            ).singleNodeValue;

            if (element) {
                if (document.getElementById('style-checkbox').checked) {
                    element.style.display = 'none';
                    alert(`已隐藏元素: ${userXPath}`);
                } else {
                    element.remove();
                    alert(`已删除元素: ${userXPath}`);
                }
            } else {
                alert('未找到匹配的元素');
            }
        };
        toolbar.appendChild(button);

        const styleCheckbox = document.createElement('div');
        styleCheckbox.innerHTML = `
            <label style="display: flex; align-items: center; margin-top: 10px;">
                <input type="checkbox" id="style-checkbox" style="margin-right: 5px;"> 隐藏元素而不是删除
            </label>
        `;
        styleCheckbox.querySelector('input').addEventListener('change', (event) => {
            button.innerText = event.target.checked ? '隐藏' : '删除';
        });
        toolbar.appendChild(styleCheckbox);

        const highlightCheckbox = document.createElement('div');
        highlightCheckbox.innerHTML = `
            <label style="display: flex; align-items: center; margin-top: 5px;">
                <input type="checkbox" id="highlight-checkbox" style="margin-right: 5px;"> 高亮匹配的元素
            </label>
        `;
        toolbar.appendChild(highlightCheckbox);

        input.addEventListener('input', () => {
            if (document.getElementById('highlight-checkbox').checked) {
                highlightMatchingElements(input.value);
            }
        });

        highlightCheckbox.addEventListener('change', () => {
            const userXPath = input.value;
            if (highlightCheckbox.querySelector('input').checked && userXPath) {
                highlightMatchingElements(userXPath);
            } else {
                document.querySelectorAll('.highlighted-element').forEach((el) => {
                    el.style.backgroundColor = '';
                    el.classList.remove('highlighted-element');
                });
            }
        });

        document.body.appendChild(toolbar);

        function toggleToolbarAtMouse(event) {
            const mouseX = event.clientX;
            const mouseY = event.clientY;

            toolbar.style.left = `${mouseX}px`;
            toolbar.style.top = `${mouseY}px`;

            toolbar.style.display = toolbar.style.display === 'none' ? 'block' : 'none';
        }

        document.addEventListener('keydown', (event) => {
            if (event.shiftKey && event.code === 'KeyX') {
                document.addEventListener('mousemove', (mouseEvent) => {
                    toggleToolbarAtMouse(mouseEvent);
                }, { once: true });
            }
        });
        const observer = new MutationObserver((mutations) => {
            const userXPath = input.value;
            if (document.getElementById('highlight-checkbox').checked && userXPath) {
                highlightMatchingElements(userXPath);
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }

    function highlightMatchingElements(xpath) {
        const elements = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
        for (let i = 0; i < elements.snapshotLength; i++) {
            const element = elements.snapshotItem(i);
            element.style.backgroundColor = 'yellow';
            element.classList.add('highlighted-element');
        }
    }
    createToolbar();
})();