UNPKG

css-select

Version:

a CSS selector compiler/engine

152 lines (133 loc) 4.97 kB
import type { Adapter, InternalOptions, Predicate } from "../types.js"; /** * Find all elements matching the query. If not in XML mode, the query will ignore * the contents of `<template>` elements. * @param query - Function that returns true if the element matches the query. * @param nodes - Nodes to query. If a node is an element, its children will be queried. * @param options - Options for querying the document. * @returns All matching elements. */ export function findAll<Node, ElementNode extends Node>( query: Predicate<ElementNode>, nodes: Node[], options: InternalOptions<Node, ElementNode>, ): ElementNode[] { const { adapter, xmlMode = false } = options; const result: ElementNode[] = []; /** Stack of the arrays we are looking at. */ const nodeStack = [nodes]; /** Stack of the indices within the arrays. */ const indexStack = [0]; for (;;) { // First, check if the current array has any more elements to look at. if (indexStack[0] >= nodeStack[0].length) { // If we have no more arrays to look at, we are done. if (nodeStack.length === 1) { return result; } nodeStack.shift(); indexStack.shift(); // Loop back to the start to continue with the next array. continue; } const element = nodeStack[0][indexStack[0]++]; if (!adapter.isTag(element)) { continue; } if (query(element)) { result.push(element); } if (xmlMode || adapter.getName(element) !== "template") { /* * Add the children to the stack. We are depth-first, so this is * the next array we look at. */ const children = adapter.getChildren(element); if (children.length > 0) { nodeStack.unshift(children); indexStack.unshift(0); } } } } /** * Find the first element matching the query. If not in XML mode, the query will ignore * the contents of `<template>` elements. * @param query - Function that returns true if the element matches the query. * @param nodes - Nodes to query. If a node is an element, its children will be queried. * @param options - Options for querying the document. * @returns The first matching element, or null if there was no match. */ export function findOne<Node, ElementNode extends Node>( query: Predicate<ElementNode>, nodes: Node[], options: InternalOptions<Node, ElementNode>, ): ElementNode | null { const { adapter, xmlMode = false } = options; /** Stack of the arrays we are looking at. */ const nodeStack = [nodes]; /** Stack of the indices within the arrays. */ const indexStack = [0]; for (;;) { // First, check if the current array has any more elements to look at. if (indexStack[0] >= nodeStack[0].length) { // If we have no more arrays to look at, we are done. if (nodeStack.length === 1) { return null; } nodeStack.shift(); indexStack.shift(); // Loop back to the start to continue with the next array. continue; } const element = nodeStack[0][indexStack[0]++]; if (!adapter.isTag(element)) { continue; } if (query(element)) { return element; } if (xmlMode || adapter.getName(element) !== "template") { /* * Add the children to the stack. We are depth-first, so this is * the next array we look at. */ const children = adapter.getChildren(element); if (children.length > 0) { nodeStack.unshift(children); indexStack.unshift(0); } } } } /** * Get all element siblings after the provided node. * @param element Element candidate being tested. * @param adapter Adapter implementation used for DOM operations. */ export function getNextSiblings<Node, ElementNode extends Node>( element: Node, adapter: Adapter<Node, ElementNode>, ): ElementNode[] { const siblings = adapter.getSiblings(element); if (siblings.length <= 1) { return []; } const elementIndex = siblings.indexOf(element); if (elementIndex === -1 || elementIndex === siblings.length - 1) { return []; } return siblings.slice(elementIndex + 1).filter(adapter.isTag); } /** * Get the parent element of a node. * @param node Node to inspect. * @param adapter Adapter implementation used for DOM operations. */ export function getElementParent<Node, ElementNode extends Node>( node: ElementNode, adapter: Adapter<Node, ElementNode>, ): ElementNode | null { const parent = adapter.getParent(node); return parent != null && adapter.isTag(parent) ? parent : null; }