Skip to content

Commit

Permalink
refactor(querying): Don't flatten node stack
Browse files Browse the repository at this point in the history
The node stack in query methods is now an array of arrays.
  • Loading branch information
fb55 committed Aug 13, 2023
1 parent 10df36e commit a42d07c
Showing 1 changed file with 62 additions and 20 deletions.
82 changes: 62 additions & 20 deletions src/helpers/querying.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { InternalOptions, Predicate, Adapter } 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 elems - Nodes to query. If a node is an element, its children will be queried.
* @param options - Options for querying the document.
Expand All @@ -17,29 +16,50 @@ export function findAll<Node, ElementNode extends Node>(
): ElementNode[] {
const { adapter, xmlMode = false } = options;
const result: ElementNode[] = [];
const stack = elems.filter(adapter.isTag);
/** Stack of the arrays we are looking at. */
const nodeStack = [elems];
/** 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 elem = nodeStack[0][indexStack[0]++];

if (!adapter.isTag(elem)) continue;
if (query(elem)) result.push(elem);

let elem;
while ((elem = stack.shift())) {
if (xmlMode || adapter.getName(elem) !== "template") {
const children = adapter.getChildren(elem).filter(adapter.isTag);
/*
* Add the children to the stack. We are depth-first, so this is
* the next array we look at.
*/
const children = adapter.getChildren(elem);

if (children.length > 0) {
stack.unshift(...children);
nodeStack.unshift(children);
indexStack.unshift(0);
}
}

if (query(elem)) result.push(elem);
}

return result;
}

/**
* 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 elems - Nodes to query. If a node is an element, its children will be queried.
* @param options - Options for querying the document.
Expand All @@ -51,22 +71,44 @@ export function findOne<Node, ElementNode extends Node>(
options: InternalOptions<Node, ElementNode>,
): ElementNode | null {
const { adapter, xmlMode = false } = options;
const stack = elems.filter(adapter.isTag);
/** Stack of the arrays we are looking at. */
const nodeStack = [elems];
/** 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 elem = nodeStack[0][indexStack[0]++];

if (!adapter.isTag(elem)) continue;
if (query(elem)) return elem;

let elem;
while ((elem = stack.shift())) {
if (xmlMode || adapter.getName(elem) !== "template") {
const children = adapter.getChildren(elem).filter(adapter.isTag);
/*
* Add the children to the stack. We are depth-first, so this is
* the next array we look at.
*/
const children = adapter.getChildren(elem);

if (children.length > 0) {
stack.unshift(...children);
nodeStack.unshift(children);
indexStack.unshift(0);
}
}

if (query(elem)) return elem;
}

return null;
}

export function getNextSiblings<Node, ElementNode extends Node>(
Expand Down

0 comments on commit a42d07c

Please sign in to comment.