22581b2f创建于 2025年12月16日历史提交
/*
 * -------------------------------------------------------------------------
 * This file is part of the MindStudio project.
 * Copyright (c) 2025 Huawei Technologies Co.,Ltd.
 *
 * MindStudio is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *          http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 * -------------------------------------------------------------------------
 */
export default class DOMIterator {
    #ctx;
    #exclude;

    constructor(ctx, exclude = []) {
        this.#ctx = ctx;
        this.#exclude = exclude;
    }

    static #getMatcher(element) {
        return (
            element.matches ??
            element.matchesSelector ??
            element.msMatchesSelector ??
            element.mozMatchesSelector ??
            element.oMatchesSelector ??
            element.webkitMatchesSelector
        );
    }

    static matches(element, selector) {
        const selectors = [].concat(selector);
        const matcher = this.#getMatcher(element);

        if (!matcher) {
            return false;
        }

        return selectors.some(sel =>
            matcher.call(element, sel),
        );
    }

    #normalizeContext() {
        if (this.#ctx === undefined) {
            return [];
        }

        if (this.#ctx instanceof NodeList) {
            return [...this.#ctx];
        }

        if (Array.isArray(this.#ctx)) {
            return this.#ctx;
        }

        if (typeof this.#ctx === 'string') {
            return [...document.querySelectorAll(this.#ctx)];
        }

        return [this.#ctx];
    }

    getContexts() {
        const contexts = this.#normalizeContext();
        const uniqueContexts = [];

        contexts.forEach(current => {
            const isExisting = uniqueContexts.some(existing =>
                existing.contains?.(current),
            );

            if (!isExisting && !uniqueContexts.includes(current)) {
                uniqueContexts.push(current);
            }
        });

        return uniqueContexts;
    }

    createIterator(context, whatToShow, filter) {
        return document.createNodeIterator(
            context,
            whatToShow,
            filter,
            false,
        );
    }

    #getIteratorState(iterator) {
        const prevNode = iterator.previousNode();
        const node = prevNode === null
            ? iterator.nextNode()
            : Boolean(iterator.nextNode()) && iterator.nextNode();

        return { prevNode, node };
    }

    #processNodes(whatToShow, context, eachCallback, filterCallback, doneCallback) {
        const iterator = this.createIterator(context, whatToShow, filterCallback);
        const nodes = [];

        let state;
        while (Boolean((state = this.#getIteratorState(iterator)).node) === true) {
            nodes.push(state.node);
        }

        nodes.forEach(eachCallback);
        doneCallback();
    }

    forEachNode(whatToShow, each, filter, done = () => {}) {
        const contexts = this.getContexts();
        let remaining = contexts.length;

        if (!remaining) {
            done();
            return;
        }

        contexts.forEach(context => {
            this.#processNodes(
                whatToShow,
                context,
                each,
                filter,
                () => --remaining <= 0 && done(),
            );
        });
    }
}