@citation-media/html-parser 0.1.0 → 0.1.2
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 +6 -2
- package/dist/parse.js +62 -39
- package/dist/text.d.ts +5 -0
- package/dist/text.js +256 -25
- package/package.json +1 -1
package/dist/clean.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { looksBinary } from "./text.js";
|
|
1
|
+
import { looksBinary, readAttribute } from "./text.js";
|
|
2
2
|
import { absoluteUrl, isResource, linkKind } from "./urls.js";
|
|
3
3
|
const cookieConsent = [
|
|
4
4
|
"[id^='brlbs-cmpnt']",
|
|
@@ -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();
|
|
@@ -122,7 +126,7 @@ export const cleanHtml = (input, options) => {
|
|
|
122
126
|
if (links) {
|
|
123
127
|
rewriter = rewriter.on("a", {
|
|
124
128
|
element(element) {
|
|
125
|
-
const href = element
|
|
129
|
+
const href = readAttribute(element, "href");
|
|
126
130
|
if (!href) {
|
|
127
131
|
return;
|
|
128
132
|
}
|
package/dist/parse.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MarkdownWriter } from "./markdown.js";
|
|
2
|
-
import { collapseWhitespace, decodeEntities, removeHyphenation, } from "./text.js";
|
|
2
|
+
import { collapseWhitespace, decodeEntities, readAttribute, removeHyphenation, } from "./text.js";
|
|
3
3
|
import { absoluteUrl, isResource, linkKind } from "./urls.js";
|
|
4
4
|
const blockTags = new Set([
|
|
5
5
|
"br",
|
|
@@ -56,18 +56,28 @@ const hiddenFromMarkdown = [
|
|
|
56
56
|
".visually-hidden",
|
|
57
57
|
];
|
|
58
58
|
const maxHeadings = 200;
|
|
59
|
-
const maxImages = 500;
|
|
60
59
|
const maxResources = 500;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Follows an element to its end tag. HTMLRewriter keeps only the last end-tag handler an element
|
|
62
|
+
* gets, so every handler adds its action to the element's list and registers the whole list; the
|
|
63
|
+
* last registration then runs them all. Void elements such as <img> have no end tag, and
|
|
64
|
+
* HTMLRewriter throws for them; elements closed implicitly, such as <li> without </li>, end with
|
|
65
|
+
* their parent.
|
|
66
|
+
*/
|
|
67
|
+
const enter = (pass, element, onEnd) => {
|
|
68
|
+
const actions = [...pass.endActions, onEnd];
|
|
64
69
|
try {
|
|
65
|
-
element.onEndTag(
|
|
66
|
-
|
|
70
|
+
element.onEndTag(() => {
|
|
71
|
+
for (const action of actions) {
|
|
72
|
+
action();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
67
75
|
}
|
|
68
76
|
catch {
|
|
69
77
|
return false;
|
|
70
78
|
}
|
|
79
|
+
pass.endActions = actions;
|
|
80
|
+
return true;
|
|
71
81
|
};
|
|
72
82
|
const clean = (text) => collapseWhitespace(removeHyphenation(decodeEntities(text))).trim();
|
|
73
83
|
const addLink = (links, found, base) => {
|
|
@@ -96,7 +106,7 @@ const collecting = (pass) => pass.inContext > 0;
|
|
|
96
106
|
const writing = (pass) => collecting(pass) && pass.hidden === 0 && pass.markdown !== undefined;
|
|
97
107
|
const hide = (pass) => ({
|
|
98
108
|
element(element) {
|
|
99
|
-
if (enter(element, () => (pass.hidden -= 1))) {
|
|
109
|
+
if (enter(pass, element, () => (pass.hidden -= 1))) {
|
|
100
110
|
pass.hidden += 1;
|
|
101
111
|
}
|
|
102
112
|
},
|
|
@@ -108,7 +118,7 @@ const onStructure = (rewriter, pass) => {
|
|
|
108
118
|
next = next.on(context, {
|
|
109
119
|
element(element) {
|
|
110
120
|
pass.contextFound = true;
|
|
111
|
-
if (enter(element, () => (pass.inContext -= 1))) {
|
|
121
|
+
if (enter(pass, element, () => (pass.inContext -= 1))) {
|
|
112
122
|
pass.inContext += 1;
|
|
113
123
|
}
|
|
114
124
|
},
|
|
@@ -127,7 +137,7 @@ const onStructure = (rewriter, pass) => {
|
|
|
127
137
|
if (!collecting(pass)) {
|
|
128
138
|
return;
|
|
129
139
|
}
|
|
130
|
-
const classes = element
|
|
140
|
+
const classes = readAttribute(element, "class")?.split(/\s+/u) ?? [];
|
|
131
141
|
for (const prefix of classPrefixes) {
|
|
132
142
|
if (classes.some((name) => name.startsWith(prefix))) {
|
|
133
143
|
pass.classCounts[prefix] = (pass.classCounts[prefix] ?? 0) + 1;
|
|
@@ -138,7 +148,7 @@ const onStructure = (rewriter, pass) => {
|
|
|
138
148
|
return;
|
|
139
149
|
}
|
|
140
150
|
pass.markdown?.open(tag);
|
|
141
|
-
enter(element, () => {
|
|
151
|
+
enter(pass, element, () => {
|
|
142
152
|
if (writing(pass)) {
|
|
143
153
|
pass.markdown?.close(tag);
|
|
144
154
|
}
|
|
@@ -148,7 +158,7 @@ const onStructure = (rewriter, pass) => {
|
|
|
148
158
|
};
|
|
149
159
|
const onLinks = (rewriter, pass) => rewriter.on("a[href]", {
|
|
150
160
|
element(element) {
|
|
151
|
-
const href = element
|
|
161
|
+
const href = readAttribute(element, "href") ?? "";
|
|
152
162
|
if (!collecting(pass) || !href) {
|
|
153
163
|
return;
|
|
154
164
|
}
|
|
@@ -156,16 +166,16 @@ const onLinks = (rewriter, pass) => rewriter.on("a[href]", {
|
|
|
156
166
|
const text = [];
|
|
157
167
|
pass.link = text;
|
|
158
168
|
// Attributes are readable only while this handler runs, not in the end-tag handler.
|
|
159
|
-
const label = element
|
|
160
|
-
const rel = element
|
|
161
|
-
const target = element
|
|
169
|
+
const label = readAttribute(element, "aria-label") ?? undefined;
|
|
170
|
+
const rel = readAttribute(element, "rel") ?? undefined;
|
|
171
|
+
const target = readAttribute(element, "target") ?? undefined;
|
|
162
172
|
const written = writing(pass) &&
|
|
163
173
|
pass.markdownOptions?.links !== false &&
|
|
164
174
|
!href.startsWith("#");
|
|
165
175
|
if (written) {
|
|
166
176
|
pass.markdown?.openLink(url);
|
|
167
177
|
}
|
|
168
|
-
enter(element, () => {
|
|
178
|
+
enter(pass, element, () => {
|
|
169
179
|
if (written) {
|
|
170
180
|
pass.markdown?.closeLink();
|
|
171
181
|
}
|
|
@@ -178,23 +188,25 @@ const onLinks = (rewriter, pass) => rewriter.on("a[href]", {
|
|
|
178
188
|
});
|
|
179
189
|
const onImages = (rewriter, pass) => rewriter.on("img", {
|
|
180
190
|
element(element) {
|
|
181
|
-
// Lazy-loading scripts
|
|
182
|
-
const src =
|
|
183
|
-
|
|
191
|
+
// Lazy-loading scripts keep the real address in data-src and a placeholder in src.
|
|
192
|
+
const src = [
|
|
193
|
+
readAttribute(element, "src"),
|
|
194
|
+
readAttribute(element, "data-src"),
|
|
195
|
+
readAttribute(element, "data-lazy-src"),
|
|
196
|
+
].find((value) => value && !value.startsWith("data:"));
|
|
197
|
+
if (!collecting(pass) || !src) {
|
|
184
198
|
return;
|
|
185
199
|
}
|
|
186
200
|
const url = absoluteUrl(src, pass.base);
|
|
187
|
-
const alt = element
|
|
188
|
-
if (pass.options.images &&
|
|
189
|
-
pass.images.size < maxImages &&
|
|
190
|
-
!pass.images.has(url)) {
|
|
201
|
+
const alt = readAttribute(element, "alt");
|
|
202
|
+
if (pass.options.images && !pass.images.has(url)) {
|
|
191
203
|
pass.images.set(url, {
|
|
192
204
|
alt: alt === null ? null : clean(alt),
|
|
193
|
-
height: element
|
|
194
|
-
loading: element
|
|
205
|
+
height: readAttribute(element, "height") ?? undefined,
|
|
206
|
+
loading: readAttribute(element, "loading") ?? undefined,
|
|
195
207
|
srcset: element.hasAttribute("srcset"),
|
|
196
208
|
url,
|
|
197
|
-
width: element
|
|
209
|
+
width: readAttribute(element, "width") ?? undefined,
|
|
198
210
|
});
|
|
199
211
|
}
|
|
200
212
|
if (writing(pass) && pass.markdownOptions?.images) {
|
|
@@ -208,11 +220,11 @@ const onResources = (rewriter, pass) => {
|
|
|
208
220
|
.on("script", {
|
|
209
221
|
element(element) {
|
|
210
222
|
if (resources.scripts.length < maxResources) {
|
|
211
|
-
const src = element
|
|
223
|
+
const src = readAttribute(element, "src");
|
|
212
224
|
resources.scripts.push({
|
|
213
225
|
attributes: [...element.attributes].flatMap(([name]) => name ? [name] : []),
|
|
214
226
|
src: src ? absoluteUrl(src, base) : undefined,
|
|
215
|
-
type: element
|
|
227
|
+
type: readAttribute(element, "type") ?? undefined,
|
|
216
228
|
});
|
|
217
229
|
}
|
|
218
230
|
},
|
|
@@ -220,7 +232,7 @@ const onResources = (rewriter, pass) => {
|
|
|
220
232
|
.on("iframe[src]", {
|
|
221
233
|
element(element) {
|
|
222
234
|
if (resources.frames.length < maxResources) {
|
|
223
|
-
resources.frames.push(absoluteUrl(element
|
|
235
|
+
resources.frames.push(absoluteUrl(readAttribute(element, "src") ?? "", base));
|
|
224
236
|
}
|
|
225
237
|
},
|
|
226
238
|
})
|
|
@@ -228,8 +240,8 @@ const onResources = (rewriter, pass) => {
|
|
|
228
240
|
element(element) {
|
|
229
241
|
if (resources.links.length < maxResources) {
|
|
230
242
|
resources.links.push({
|
|
231
|
-
href: absoluteUrl(element
|
|
232
|
-
rel: element
|
|
243
|
+
href: absoluteUrl(readAttribute(element, "href") ?? "", base),
|
|
244
|
+
rel: readAttribute(element, "rel") ?? "",
|
|
233
245
|
});
|
|
234
246
|
}
|
|
235
247
|
},
|
|
@@ -245,13 +257,13 @@ const metaFields = new Map([
|
|
|
245
257
|
const onMeta = (rewriter, pass) => rewriter
|
|
246
258
|
.on("html", {
|
|
247
259
|
element(element) {
|
|
248
|
-
pass.meta.language = element
|
|
260
|
+
pass.meta.language = readAttribute(element, "lang") ?? undefined;
|
|
249
261
|
},
|
|
250
262
|
})
|
|
251
263
|
.on("title", {
|
|
252
264
|
element(element) {
|
|
253
265
|
pass.title = [];
|
|
254
|
-
enter(element, () => {
|
|
266
|
+
enter(pass, element, () => {
|
|
255
267
|
pass.meta.title = clean((pass.title ?? []).join(""));
|
|
256
268
|
pass.title = undefined;
|
|
257
269
|
});
|
|
@@ -259,18 +271,18 @@ const onMeta = (rewriter, pass) => rewriter
|
|
|
259
271
|
})
|
|
260
272
|
.on("meta", {
|
|
261
273
|
element(element) {
|
|
262
|
-
const name = element
|
|
263
|
-
element
|
|
274
|
+
const name = readAttribute(element, "name") ??
|
|
275
|
+
readAttribute(element, "property") ??
|
|
264
276
|
"";
|
|
265
277
|
const field = metaFields.get(name.toLowerCase());
|
|
266
278
|
if (field) {
|
|
267
|
-
pass.meta[field] = element
|
|
279
|
+
pass.meta[field] = readAttribute(element, "content") ?? undefined;
|
|
268
280
|
}
|
|
269
281
|
},
|
|
270
282
|
})
|
|
271
283
|
.on("link[rel='canonical']", {
|
|
272
284
|
element(element) {
|
|
273
|
-
const href = element
|
|
285
|
+
const href = readAttribute(element, "href");
|
|
274
286
|
pass.meta.canonical = href ? absoluteUrl(href, pass.base) : undefined;
|
|
275
287
|
},
|
|
276
288
|
});
|
|
@@ -282,7 +294,7 @@ const onHeadings = (rewriter, pass) => rewriter.on("h1, h2, h3, h4, h5, h6", {
|
|
|
282
294
|
const level = Number(element.tagName.slice(1));
|
|
283
295
|
pass.headingCounts[level - 1] = (pass.headingCounts[level - 1] ?? 0) + 1;
|
|
284
296
|
pass.heading = { level, text: [] };
|
|
285
|
-
enter(element, () => {
|
|
297
|
+
enter(pass, element, () => {
|
|
286
298
|
const text = clean(pass.heading?.text.join("") ?? "");
|
|
287
299
|
if (pass.heading && text && pass.headings.length < maxHeadings) {
|
|
288
300
|
pass.headings.push({ level: pass.heading.level, text });
|
|
@@ -338,6 +350,7 @@ export const parseHtml = async (input, options) => {
|
|
|
338
350
|
base: new URL(options.url),
|
|
339
351
|
classCounts: Object.fromEntries((options.classPrefixes ?? []).map((prefix) => [prefix, 0])),
|
|
340
352
|
contextFound: false,
|
|
353
|
+
endActions: [],
|
|
341
354
|
heading: undefined,
|
|
342
355
|
headingCounts: [0, 0, 0, 0, 0, 0],
|
|
343
356
|
headings: [],
|
|
@@ -356,7 +369,17 @@ export const parseHtml = async (input, options) => {
|
|
|
356
369
|
resources: { frames: [], links: [], scripts: [] },
|
|
357
370
|
title: undefined,
|
|
358
371
|
};
|
|
359
|
-
|
|
372
|
+
// An empty page has nothing to stream; some runtimes never finish transforming an empty body.
|
|
373
|
+
if (!(input instanceof Response) && input.trim() === "") {
|
|
374
|
+
return resultOf(pass);
|
|
375
|
+
}
|
|
376
|
+
// Registered first, so every element starts with an empty list of end-tag actions.
|
|
377
|
+
const first = new HTMLRewriter().on("*", {
|
|
378
|
+
element() {
|
|
379
|
+
pass.endActions = [];
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
let rewriter = onImages(onLinks(onStructure(first, pass), pass), pass);
|
|
360
383
|
if (options.resources) {
|
|
361
384
|
rewriter = onResources(rewriter, pass);
|
|
362
385
|
}
|
package/dist/text.d.ts
CHANGED
|
@@ -14,3 +14,8 @@ export declare const collapseWhitespace: (text: string) => string;
|
|
|
14
14
|
* conservative so umlauts and other normal characters never trigger it.
|
|
15
15
|
*/
|
|
16
16
|
export declare const looksBinary: (text: string) => boolean;
|
|
17
|
+
/**
|
|
18
|
+
* An attribute's value with its entities decoded. HTMLRewriter returns attributes as written in the
|
|
19
|
+
* source, so `href="?a=1&b=2"` would otherwise keep its `&`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const readAttribute: (element: Element, name: string) => string | null;
|
package/dist/text.js
CHANGED
|
@@ -3,38 +3,261 @@
|
|
|
3
3
|
* parser collects chunks per run of text and decodes them here, so an entity split across two
|
|
4
4
|
* chunks is still decoded correctly.
|
|
5
5
|
*/
|
|
6
|
+
// The named entities of HTML 4, which cover what pages use in practice; numeric ones work anyway.
|
|
6
7
|
const named = new Map(Object.entries({
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
AElig: "\u00C6",
|
|
9
|
+
Aacute: "\u00C1",
|
|
10
|
+
Acirc: "\u00C2",
|
|
11
|
+
Agrave: "\u00C0",
|
|
12
|
+
Alpha: "\u0391",
|
|
13
|
+
Aring: "\u00C5",
|
|
14
|
+
Atilde: "\u00C3",
|
|
15
|
+
Auml: "\u00C4",
|
|
16
|
+
Beta: "\u0392",
|
|
17
|
+
Ccedil: "\u00C7",
|
|
18
|
+
Chi: "\u03A7",
|
|
19
|
+
Dagger: "\u2021",
|
|
20
|
+
Delta: "\u0394",
|
|
21
|
+
ETH: "\u00D0",
|
|
22
|
+
Eacute: "\u00C9",
|
|
23
|
+
Ecirc: "\u00CA",
|
|
24
|
+
Egrave: "\u00C8",
|
|
25
|
+
Epsilon: "\u0395",
|
|
26
|
+
Eta: "\u0397",
|
|
27
|
+
Euml: "\u00CB",
|
|
28
|
+
Gamma: "\u0393",
|
|
29
|
+
Iacute: "\u00CD",
|
|
30
|
+
Icirc: "\u00CE",
|
|
31
|
+
Igrave: "\u00CC",
|
|
32
|
+
Iota: "\u0399",
|
|
33
|
+
Iuml: "\u00CF",
|
|
34
|
+
Kappa: "\u039A",
|
|
35
|
+
Lambda: "\u039B",
|
|
36
|
+
Mu: "\u039C",
|
|
37
|
+
Ntilde: "\u00D1",
|
|
38
|
+
Nu: "\u039D",
|
|
39
|
+
OElig: "\u0152",
|
|
40
|
+
Oacute: "\u00D3",
|
|
41
|
+
Ocirc: "\u00D4",
|
|
42
|
+
Ograve: "\u00D2",
|
|
43
|
+
Omega: "\u03A9",
|
|
44
|
+
Omicron: "\u039F",
|
|
45
|
+
Oslash: "\u00D8",
|
|
46
|
+
Otilde: "\u00D5",
|
|
47
|
+
Ouml: "\u00D6",
|
|
48
|
+
Phi: "\u03A6",
|
|
49
|
+
Pi: "\u03A0",
|
|
50
|
+
Prime: "\u2033",
|
|
51
|
+
Psi: "\u03A8",
|
|
52
|
+
Rho: "\u03A1",
|
|
53
|
+
Scaron: "\u0160",
|
|
54
|
+
Sigma: "\u03A3",
|
|
55
|
+
THORN: "\u00DE",
|
|
56
|
+
Tau: "\u03A4",
|
|
57
|
+
Theta: "\u0398",
|
|
58
|
+
Uacute: "\u00DA",
|
|
59
|
+
Ucirc: "\u00DB",
|
|
60
|
+
Ugrave: "\u00D9",
|
|
61
|
+
Upsilon: "\u03A5",
|
|
62
|
+
Uuml: "\u00DC",
|
|
63
|
+
Xi: "\u039E",
|
|
64
|
+
Yacute: "\u00DD",
|
|
65
|
+
Yuml: "\u0178",
|
|
66
|
+
Zeta: "\u0396",
|
|
67
|
+
aacute: "\u00E1",
|
|
68
|
+
acirc: "\u00E2",
|
|
69
|
+
acute: "\u00B4",
|
|
70
|
+
aelig: "\u00E6",
|
|
71
|
+
agrave: "\u00E0",
|
|
72
|
+
alefsym: "\u2135",
|
|
73
|
+
alpha: "\u03B1",
|
|
10
74
|
amp: "&",
|
|
75
|
+
and: "\u2227",
|
|
76
|
+
ang: "\u2220",
|
|
11
77
|
apos: "'",
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
78
|
+
aring: "\u00E5",
|
|
79
|
+
asymp: "\u2248",
|
|
80
|
+
atilde: "\u00E3",
|
|
81
|
+
auml: "\u00E4",
|
|
82
|
+
bdquo: "\u201E",
|
|
83
|
+
beta: "\u03B2",
|
|
84
|
+
brvbar: "\u00A6",
|
|
85
|
+
bull: "\u2022",
|
|
86
|
+
cap: "\u2229",
|
|
87
|
+
ccedil: "\u00E7",
|
|
88
|
+
cedil: "\u00B8",
|
|
89
|
+
cent: "\u00A2",
|
|
90
|
+
chi: "\u03C7",
|
|
91
|
+
circ: "\u02C6",
|
|
92
|
+
clubs: "\u2663",
|
|
93
|
+
cong: "\u2245",
|
|
94
|
+
copy: "\u00A9",
|
|
95
|
+
crarr: "\u21B5",
|
|
96
|
+
cup: "\u222A",
|
|
97
|
+
curren: "\u00A4",
|
|
98
|
+
dArr: "\u21D3",
|
|
99
|
+
dagger: "\u2020",
|
|
100
|
+
darr: "\u2193",
|
|
101
|
+
deg: "\u00B0",
|
|
102
|
+
delta: "\u03B4",
|
|
103
|
+
diams: "\u2666",
|
|
104
|
+
divide: "\u00F7",
|
|
105
|
+
eacute: "\u00E9",
|
|
106
|
+
ecirc: "\u00EA",
|
|
107
|
+
egrave: "\u00E8",
|
|
108
|
+
empty: "\u2205",
|
|
109
|
+
emsp: "\u2003",
|
|
110
|
+
ensp: "\u2002",
|
|
111
|
+
epsilon: "\u03B5",
|
|
112
|
+
equiv: "\u2261",
|
|
113
|
+
eta: "\u03B7",
|
|
114
|
+
eth: "\u00F0",
|
|
115
|
+
euml: "\u00EB",
|
|
116
|
+
euro: "\u20AC",
|
|
117
|
+
exist: "\u2203",
|
|
118
|
+
fnof: "\u0192",
|
|
119
|
+
forall: "\u2200",
|
|
120
|
+
frac12: "\u00BD",
|
|
121
|
+
frac14: "\u00BC",
|
|
122
|
+
frac34: "\u00BE",
|
|
123
|
+
frasl: "\u2044",
|
|
124
|
+
gamma: "\u03B3",
|
|
125
|
+
ge: "\u2265",
|
|
17
126
|
gt: ">",
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
127
|
+
hArr: "\u21D4",
|
|
128
|
+
harr: "\u2194",
|
|
129
|
+
hearts: "\u2665",
|
|
130
|
+
hellip: "\u2026",
|
|
131
|
+
iacute: "\u00ED",
|
|
132
|
+
icirc: "\u00EE",
|
|
133
|
+
iexcl: "\u00A1",
|
|
134
|
+
igrave: "\u00EC",
|
|
135
|
+
image: "\u2111",
|
|
136
|
+
infin: "\u221E",
|
|
137
|
+
int: "\u222B",
|
|
138
|
+
iota: "\u03B9",
|
|
139
|
+
iquest: "\u00BF",
|
|
140
|
+
isin: "\u2208",
|
|
141
|
+
iuml: "\u00EF",
|
|
142
|
+
kappa: "\u03BA",
|
|
143
|
+
lArr: "\u21D0",
|
|
144
|
+
lambda: "\u03BB",
|
|
145
|
+
lang: "\u2329",
|
|
146
|
+
laquo: "\u00AB",
|
|
147
|
+
larr: "\u2190",
|
|
148
|
+
lceil: "\u2308",
|
|
149
|
+
ldquo: "\u201C",
|
|
150
|
+
le: "\u2264",
|
|
151
|
+
lfloor: "\u230A",
|
|
152
|
+
lowast: "\u2217",
|
|
153
|
+
loz: "\u25CA",
|
|
154
|
+
lrm: "\u200E",
|
|
155
|
+
lsaquo: "\u2039",
|
|
156
|
+
lsquo: "\u2018",
|
|
22
157
|
lt: "<",
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
158
|
+
macr: "\u00AF",
|
|
159
|
+
mdash: "\u2014",
|
|
160
|
+
micro: "\u00B5",
|
|
161
|
+
middot: "\u00B7",
|
|
162
|
+
minus: "\u2212",
|
|
163
|
+
mu: "\u03BC",
|
|
164
|
+
nabla: "\u2207",
|
|
165
|
+
nbsp: "\u00A0",
|
|
166
|
+
ndash: "\u2013",
|
|
167
|
+
ne: "\u2260",
|
|
168
|
+
ni: "\u220B",
|
|
169
|
+
not: "\u00AC",
|
|
170
|
+
notin: "\u2209",
|
|
171
|
+
nsub: "\u2284",
|
|
172
|
+
ntilde: "\u00F1",
|
|
173
|
+
nu: "\u03BD",
|
|
174
|
+
oacute: "\u00F3",
|
|
175
|
+
ocirc: "\u00F4",
|
|
176
|
+
oelig: "\u0153",
|
|
177
|
+
ograve: "\u00F2",
|
|
178
|
+
oline: "\u203E",
|
|
179
|
+
omega: "\u03C9",
|
|
180
|
+
omicron: "\u03BF",
|
|
181
|
+
oplus: "\u2295",
|
|
182
|
+
or: "\u2228",
|
|
183
|
+
ordf: "\u00AA",
|
|
184
|
+
ordm: "\u00BA",
|
|
185
|
+
oslash: "\u00F8",
|
|
186
|
+
otilde: "\u00F5",
|
|
187
|
+
otimes: "\u2297",
|
|
188
|
+
ouml: "\u00F6",
|
|
189
|
+
para: "\u00B6",
|
|
190
|
+
part: "\u2202",
|
|
191
|
+
permil: "\u2030",
|
|
192
|
+
perp: "\u22A5",
|
|
193
|
+
phi: "\u03C6",
|
|
194
|
+
pi: "\u03C0",
|
|
195
|
+
piv: "\u03D6",
|
|
196
|
+
plusmn: "\u00B1",
|
|
197
|
+
pound: "\u00A3",
|
|
198
|
+
prime: "\u2032",
|
|
199
|
+
prod: "\u220F",
|
|
200
|
+
prop: "\u221D",
|
|
201
|
+
psi: "\u03C8",
|
|
28
202
|
quot: '"',
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
203
|
+
rArr: "\u21D2",
|
|
204
|
+
radic: "\u221A",
|
|
205
|
+
rang: "\u232A",
|
|
206
|
+
raquo: "\u00BB",
|
|
207
|
+
rarr: "\u2192",
|
|
208
|
+
rceil: "\u2309",
|
|
209
|
+
rdquo: "\u201D",
|
|
210
|
+
real: "\u211C",
|
|
211
|
+
reg: "\u00AE",
|
|
212
|
+
rfloor: "\u230B",
|
|
213
|
+
rho: "\u03C1",
|
|
214
|
+
rlm: "\u200F",
|
|
215
|
+
rsaquo: "\u203A",
|
|
216
|
+
rsquo: "\u2019",
|
|
217
|
+
sbquo: "\u201A",
|
|
218
|
+
scaron: "\u0161",
|
|
219
|
+
sdot: "\u22C5",
|
|
220
|
+
sect: "\u00A7",
|
|
34
221
|
shy: "",
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
222
|
+
sigma: "\u03C3",
|
|
223
|
+
sigmaf: "\u03C2",
|
|
224
|
+
sim: "\u223C",
|
|
225
|
+
spades: "\u2660",
|
|
226
|
+
sub: "\u2282",
|
|
227
|
+
sube: "\u2286",
|
|
228
|
+
sum: "\u2211",
|
|
229
|
+
sup: "\u2283",
|
|
230
|
+
sup1: "\u00B9",
|
|
231
|
+
sup2: "\u00B2",
|
|
232
|
+
sup3: "\u00B3",
|
|
233
|
+
supe: "\u2287",
|
|
234
|
+
szlig: "\u00DF",
|
|
235
|
+
tau: "\u03C4",
|
|
236
|
+
there4: "\u2234",
|
|
237
|
+
theta: "\u03B8",
|
|
238
|
+
thetasym: "\u03D1",
|
|
239
|
+
thinsp: "\u2009",
|
|
240
|
+
thorn: "\u00FE",
|
|
241
|
+
tilde: "\u02DC",
|
|
242
|
+
times: "\u00D7",
|
|
243
|
+
trade: "\u2122",
|
|
244
|
+
uArr: "\u21D1",
|
|
245
|
+
uacute: "\u00FA",
|
|
246
|
+
uarr: "\u2191",
|
|
247
|
+
ucirc: "\u00FB",
|
|
248
|
+
ugrave: "\u00F9",
|
|
249
|
+
uml: "\u00A8",
|
|
250
|
+
upsih: "\u03D2",
|
|
251
|
+
upsilon: "\u03C5",
|
|
252
|
+
uuml: "\u00FC",
|
|
253
|
+
weierp: "\u2118",
|
|
254
|
+
xi: "\u03BE",
|
|
255
|
+
yacute: "\u00FD",
|
|
256
|
+
yen: "\u00A5",
|
|
257
|
+
yuml: "\u00FF",
|
|
258
|
+
zeta: "\u03B6",
|
|
259
|
+
zwj: "\u200D",
|
|
260
|
+
zwnj: "\u200C",
|
|
38
261
|
}));
|
|
39
262
|
const fromCodePoint = (value) => {
|
|
40
263
|
try {
|
|
@@ -75,3 +298,11 @@ export const looksBinary = (text) => {
|
|
|
75
298
|
const control = text.match(controlCharacters)?.length ?? 0;
|
|
76
299
|
return (text.includes("\u0000") || control > 20 || control / text.length > 0.05);
|
|
77
300
|
};
|
|
301
|
+
/**
|
|
302
|
+
* An attribute's value with its entities decoded. HTMLRewriter returns attributes as written in the
|
|
303
|
+
* source, so `href="?a=1&b=2"` would otherwise keep its `&`.
|
|
304
|
+
*/
|
|
305
|
+
export const readAttribute = (element, name) => {
|
|
306
|
+
const value = element.getAttribute(name);
|
|
307
|
+
return value === null ? null : decodeEntities(value);
|
|
308
|
+
};
|
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.2",
|
|
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",
|