Skip to content

Commit 90aadcb

Browse files
authored
Support root nodes in querying fns (#1850)
Fixes #1665 Both as an input, as well as as part of the document.
1 parent 3dbfa83 commit 90aadcb

File tree

2 files changed

+43
-35
lines changed

2 files changed

+43
-35
lines changed

src/querying.spec.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { ElementType } from "domelementtype";
1313
describe("querying", () => {
1414
const manyNodesWide = parseDocument(
1515
`<body>${"<div></div>".repeat(200_000)}Text</body>`,
16-
).children;
16+
);
1717

1818
describe("find", () => {
1919
it("should accept many children without RangeError", () =>
@@ -65,7 +65,7 @@ describe("querying", () => {
6565
expect(
6666
filter(
6767
(elem) => elem.type === ElementType.Tag,
68-
manyNodesWide[0],
68+
manyNodesWide.children[0],
6969
),
7070
).toHaveLength(200_001));
7171
});
@@ -75,29 +75,37 @@ describe("querying", () => {
7575
expect(
7676
findOneChild(
7777
(elem) => isTag(elem) && elem.name === "body",
78-
manyNodesWide,
78+
manyNodesWide.children,
7979
),
80-
).toBe(manyNodesWide[0]));
80+
).toBe(manyNodesWide.children[0]));
8181

8282
it("should only query direct children", () =>
8383
expect(
8484
findOneChild(
8585
(elem) => isTag(elem) && elem.name === "div",
86-
manyNodesWide,
86+
manyNodesWide.children,
8787
),
8888
).toBeUndefined());
8989
});
9090

9191
describe("findOne", () => {
9292
it("should find elements", () =>
9393
expect(
94-
findOne((elem) => elem.name === "body", manyNodesWide, true),
95-
).toBe(manyNodesWide[0]));
94+
findOne(
95+
(elem) => elem.name === "body",
96+
manyNodesWide.children,
97+
true,
98+
),
99+
).toBe(manyNodesWide.children[0]));
96100

97101
it("should find elements in children", () =>
98102
expect(
99-
findOne((elem) => elem.name === "div", manyNodesWide, true),
100-
).toBe((manyNodesWide[0] as Element).children[0]));
103+
findOne(
104+
(elem) => elem.name === "div",
105+
manyNodesWide.children,
106+
true,
107+
),
108+
).toBe((manyNodesWide.children[0] as Element).children[0]));
101109

102110
it("should not find elements in children if recurse is false", () =>
103111
expect(
@@ -116,7 +124,10 @@ describe("querying", () => {
116124

117125
it("should find elements in children", () =>
118126
expect(
119-
existsOne((elem) => elem.name === "div", manyNodesWide),
127+
existsOne(
128+
(elem) => elem.name === "div",
129+
manyNodesWide.children,
130+
),
120131
).toBeTruthy());
121132

122133
it("should return `false` if nothing is found", () =>

src/querying.ts

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isTag, hasChildren, Element, AnyNode } from "domhandler";
1+
import { isTag, hasChildren, Element, AnyNode, ParentNode } from "domhandler";
22

33
/**
44
* Search a node and its children for nodes passing a test function. If `node` is not an array, it will be wrapped in one.
@@ -31,13 +31,13 @@ export function filter(
3131
*/
3232
export function find(
3333
test: (elem: AnyNode) => boolean,
34-
nodes: AnyNode[],
34+
nodes: AnyNode[] | ParentNode,
3535
recurse: boolean,
3636
limit: number,
3737
): AnyNode[] {
3838
const result: AnyNode[] = [];
3939
/** Stack of the arrays we are looking at. */
40-
const nodeStack = [nodes];
40+
const nodeStack: AnyNode[][] = [Array.isArray(nodes) ? nodes : [nodes]];
4141
/** Stack of the indices within the arrays. */
4242
const indexStack = [0];
4343

@@ -102,23 +102,21 @@ export function findOneChild<T>(
102102
*/
103103
export function findOne(
104104
test: (elem: Element) => boolean,
105-
nodes: AnyNode[],
105+
nodes: AnyNode[] | ParentNode,
106106
recurse = true,
107107
): Element | null {
108-
let elem = null;
109-
110-
for (let i = 0; i < nodes.length && !elem; i++) {
111-
const node = nodes[i];
112-
if (!isTag(node)) {
113-
continue;
114-
} else if (test(node)) {
115-
elem = node;
116-
} else if (recurse && node.children.length > 0) {
117-
elem = findOne(test, node.children, true);
108+
const searchedNodes = Array.isArray(nodes) ? nodes : [nodes];
109+
for (let i = 0; i < searchedNodes.length; i++) {
110+
const node = searchedNodes[i];
111+
if (isTag(node) && test(node)) {
112+
return node;
113+
}
114+
if (recurse && hasChildren(node) && node.children.length > 0) {
115+
return findOne(test, node.children, true);
118116
}
119117
}
120118

121-
return elem;
119+
return null;
122120
}
123121

124122
/**
@@ -131,12 +129,12 @@ export function findOne(
131129
*/
132130
export function existsOne(
133131
test: (elem: Element) => boolean,
134-
nodes: AnyNode[],
132+
nodes: AnyNode[] | ParentNode,
135133
): boolean {
136-
return nodes.some(
137-
(checked) =>
138-
isTag(checked) &&
139-
(test(checked) || existsOne(test, checked.children)),
134+
return (Array.isArray(nodes) ? nodes : [nodes]).some(
135+
(node) =>
136+
(isTag(node) && test(node)) ||
137+
(hasChildren(node) && existsOne(test, node.children)),
140138
);
141139
}
142140

@@ -152,10 +150,10 @@ export function existsOne(
152150
*/
153151
export function findAll(
154152
test: (elem: Element) => boolean,
155-
nodes: AnyNode[],
153+
nodes: AnyNode[] | ParentNode,
156154
): Element[] {
157155
const result = [];
158-
const nodeStack = [nodes];
156+
const nodeStack = [Array.isArray(nodes) ? nodes : [nodes]];
159157
const indexStack = [0];
160158

161159
for (;;) {
@@ -174,10 +172,9 @@ export function findAll(
174172

175173
const elem = nodeStack[0][indexStack[0]++];
176174

177-
if (!isTag(elem)) continue;
178-
if (test(elem)) result.push(elem);
175+
if (isTag(elem) && test(elem)) result.push(elem);
179176

180-
if (elem.children.length > 0) {
177+
if (hasChildren(elem) && elem.children.length > 0) {
181178
indexStack.unshift(0);
182179
nodeStack.unshift(elem.children);
183180
}

0 commit comments

Comments
 (0)