@citation-media/html-parser 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clean.js +4 -0
- package/dist/parse.js +34 -12
- package/package.json +1 -1
package/dist/clean.js
CHANGED
|
@@ -19,6 +19,10 @@ const remove = {
|
|
|
19
19
|
};
|
|
20
20
|
/** The cleaned page as HTML. */
|
|
21
21
|
export const cleanHtml = (input, options) => {
|
|
22
|
+
// An empty page has nothing to stream; some runtimes never finish transforming an empty body.
|
|
23
|
+
if (!(input instanceof Response) && input.trim() === "") {
|
|
24
|
+
return Promise.resolve("");
|
|
25
|
+
}
|
|
22
26
|
const base = new URL(options.url);
|
|
23
27
|
const removed = options.remove ?? {};
|
|
24
28
|
let rewriter = new HTMLRewriter();
|
package/dist/parse.js
CHANGED
|
@@ -58,16 +58,27 @@ const hiddenFromMarkdown = [
|
|
|
58
58
|
const maxHeadings = 200;
|
|
59
59
|
const maxImages = 500;
|
|
60
60
|
const maxResources = 500;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Follows an element to its end tag. HTMLRewriter keeps only the last end-tag handler an element
|
|
63
|
+
* gets, so every handler adds its action to the element's list and registers the whole list; the
|
|
64
|
+
* last registration then runs them all. Void elements such as <img> have no end tag, and
|
|
65
|
+
* HTMLRewriter throws for them; elements closed implicitly, such as <li> without </li>, end with
|
|
66
|
+
* their parent.
|
|
67
|
+
*/
|
|
68
|
+
const enter = (pass, element, onEnd) => {
|
|
69
|
+
const actions = [...pass.endActions, onEnd];
|
|
64
70
|
try {
|
|
65
|
-
element.onEndTag(
|
|
66
|
-
|
|
71
|
+
element.onEndTag(() => {
|
|
72
|
+
for (const action of actions) {
|
|
73
|
+
action();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
67
76
|
}
|
|
68
77
|
catch {
|
|
69
78
|
return false;
|
|
70
79
|
}
|
|
80
|
+
pass.endActions = actions;
|
|
81
|
+
return true;
|
|
71
82
|
};
|
|
72
83
|
const clean = (text) => collapseWhitespace(removeHyphenation(decodeEntities(text))).trim();
|
|
73
84
|
const addLink = (links, found, base) => {
|
|
@@ -96,7 +107,7 @@ const collecting = (pass) => pass.inContext > 0;
|
|
|
96
107
|
const writing = (pass) => collecting(pass) && pass.hidden === 0 && pass.markdown !== undefined;
|
|
97
108
|
const hide = (pass) => ({
|
|
98
109
|
element(element) {
|
|
99
|
-
if (enter(element, () => (pass.hidden -= 1))) {
|
|
110
|
+
if (enter(pass, element, () => (pass.hidden -= 1))) {
|
|
100
111
|
pass.hidden += 1;
|
|
101
112
|
}
|
|
102
113
|
},
|
|
@@ -108,7 +119,7 @@ const onStructure = (rewriter, pass) => {
|
|
|
108
119
|
next = next.on(context, {
|
|
109
120
|
element(element) {
|
|
110
121
|
pass.contextFound = true;
|
|
111
|
-
if (enter(element, () => (pass.inContext -= 1))) {
|
|
122
|
+
if (enter(pass, element, () => (pass.inContext -= 1))) {
|
|
112
123
|
pass.inContext += 1;
|
|
113
124
|
}
|
|
114
125
|
},
|
|
@@ -138,7 +149,7 @@ const onStructure = (rewriter, pass) => {
|
|
|
138
149
|
return;
|
|
139
150
|
}
|
|
140
151
|
pass.markdown?.open(tag);
|
|
141
|
-
enter(element, () => {
|
|
152
|
+
enter(pass, element, () => {
|
|
142
153
|
if (writing(pass)) {
|
|
143
154
|
pass.markdown?.close(tag);
|
|
144
155
|
}
|
|
@@ -165,7 +176,7 @@ const onLinks = (rewriter, pass) => rewriter.on("a[href]", {
|
|
|
165
176
|
if (written) {
|
|
166
177
|
pass.markdown?.openLink(url);
|
|
167
178
|
}
|
|
168
|
-
enter(element, () => {
|
|
179
|
+
enter(pass, element, () => {
|
|
169
180
|
if (written) {
|
|
170
181
|
pass.markdown?.closeLink();
|
|
171
182
|
}
|
|
@@ -251,7 +262,7 @@ const onMeta = (rewriter, pass) => rewriter
|
|
|
251
262
|
.on("title", {
|
|
252
263
|
element(element) {
|
|
253
264
|
pass.title = [];
|
|
254
|
-
enter(element, () => {
|
|
265
|
+
enter(pass, element, () => {
|
|
255
266
|
pass.meta.title = clean((pass.title ?? []).join(""));
|
|
256
267
|
pass.title = undefined;
|
|
257
268
|
});
|
|
@@ -282,7 +293,7 @@ const onHeadings = (rewriter, pass) => rewriter.on("h1, h2, h3, h4, h5, h6", {
|
|
|
282
293
|
const level = Number(element.tagName.slice(1));
|
|
283
294
|
pass.headingCounts[level - 1] = (pass.headingCounts[level - 1] ?? 0) + 1;
|
|
284
295
|
pass.heading = { level, text: [] };
|
|
285
|
-
enter(element, () => {
|
|
296
|
+
enter(pass, element, () => {
|
|
286
297
|
const text = clean(pass.heading?.text.join("") ?? "");
|
|
287
298
|
if (pass.heading && text && pass.headings.length < maxHeadings) {
|
|
288
299
|
pass.headings.push({ level: pass.heading.level, text });
|
|
@@ -338,6 +349,7 @@ export const parseHtml = async (input, options) => {
|
|
|
338
349
|
base: new URL(options.url),
|
|
339
350
|
classCounts: Object.fromEntries((options.classPrefixes ?? []).map((prefix) => [prefix, 0])),
|
|
340
351
|
contextFound: false,
|
|
352
|
+
endActions: [],
|
|
341
353
|
heading: undefined,
|
|
342
354
|
headingCounts: [0, 0, 0, 0, 0, 0],
|
|
343
355
|
headings: [],
|
|
@@ -356,7 +368,17 @@ export const parseHtml = async (input, options) => {
|
|
|
356
368
|
resources: { frames: [], links: [], scripts: [] },
|
|
357
369
|
title: undefined,
|
|
358
370
|
};
|
|
359
|
-
|
|
371
|
+
// An empty page has nothing to stream; some runtimes never finish transforming an empty body.
|
|
372
|
+
if (!(input instanceof Response) && input.trim() === "") {
|
|
373
|
+
return resultOf(pass);
|
|
374
|
+
}
|
|
375
|
+
// Registered first, so every element starts with an empty list of end-tag actions.
|
|
376
|
+
const first = new HTMLRewriter().on("*", {
|
|
377
|
+
element() {
|
|
378
|
+
pass.endActions = [];
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
let rewriter = onImages(onLinks(onStructure(first, pass), pass), pass);
|
|
360
382
|
if (options.resources) {
|
|
361
383
|
rewriter = onResources(rewriter, pass);
|
|
362
384
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@citation-media/html-parser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Streaming HTML parser for Cloudflare Workers: links, images, resources, and Markdown without a DOM, built on HTMLRewriter.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare-workers",
|