webforai 4.2.0 → 4.3.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.
Files changed (44) hide show
  1. package/THIRD_PARTY_NOTICES.md +42 -0
  2. package/dist/adapters/sites/hackernews.js +3 -3
  3. package/dist/adapters/sites/youtube.js +4 -2
  4. package/dist/bin.js +738 -474
  5. package/dist/cjs/adapters/sites/hackernews.js +3 -3
  6. package/dist/cjs/adapters/sites/youtube.js +5 -2
  7. package/dist/cjs/detect-client-shell.js +2 -0
  8. package/dist/cjs/extractors/lib/layout-tables.js +48 -9
  9. package/dist/cjs/extractors/lib/sanitize.js +38 -2
  10. package/dist/cjs/extractors/lib/terminators.js +2 -0
  11. package/dist/cjs/index.js +3 -0
  12. package/dist/cjs/mdast-handlers/custom-code-handler.js +31 -2
  13. package/dist/cjs/normalize/index.js +97 -4
  14. package/dist/cjs/utils/detect-code-lang.js +4 -5
  15. package/dist/cjs/utils/parse-html.js +73 -5
  16. package/dist/detect-client-shell.js +1 -0
  17. package/dist/extractors/lib/layout-tables.js +48 -9
  18. package/dist/extractors/lib/sanitize.js +38 -2
  19. package/dist/extractors/lib/terminators.js +2 -0
  20. package/dist/index.js +2 -0
  21. package/dist/mdast-handlers/custom-code-handler.js +31 -2
  22. package/dist/normalize/index.js +94 -3
  23. package/dist/types/adapters/sites/hackernews.js.map +1 -1
  24. package/dist/types/adapters/sites/youtube.d.ts +2 -0
  25. package/dist/types/adapters/sites/youtube.js.map +1 -1
  26. package/dist/types/detect-client-shell.d.ts +2 -0
  27. package/dist/types/detect-client-shell.js.map +1 -1
  28. package/dist/types/extractors/lib/layout-tables.d.ts +9 -2
  29. package/dist/types/extractors/lib/layout-tables.js.map +1 -1
  30. package/dist/types/extractors/lib/sanitize.js.map +1 -1
  31. package/dist/types/extractors/lib/terminators.js.map +1 -1
  32. package/dist/types/index.d.ts +1 -0
  33. package/dist/types/index.js.map +1 -1
  34. package/dist/types/mdast-handlers/custom-code-handler.d.ts +5 -0
  35. package/dist/types/mdast-handlers/custom-code-handler.js.map +1 -1
  36. package/dist/types/normalize/index.d.ts +21 -0
  37. package/dist/types/normalize/index.js.map +1 -1
  38. package/dist/types/utils/detect-code-lang.d.ts +7 -1
  39. package/dist/types/utils/detect-code-lang.js.map +1 -1
  40. package/dist/types/utils/parse-html.d.ts +12 -6
  41. package/dist/types/utils/parse-html.js.map +1 -1
  42. package/dist/utils/detect-code-lang.js +4 -5
  43. package/dist/utils/parse-html.js +73 -5
  44. package/package.json +3 -2
@@ -45,11 +45,11 @@ const commentDepth = (row) => {
45
45
  return width === void 0 ? 0 : Math.round(width / 40);
46
46
  };
47
47
  const nest = (content, depth) => {
48
- let current = content;
48
+ let current = element("div", content);
49
49
  for (let level = 0; level < Math.min(depth, MAX_QUOTE_DEPTH); level++) {
50
- current = [element("blockquote", current)];
50
+ current = element("blockquote", [current]);
51
51
  }
52
- return current[0];
52
+ return current;
53
53
  };
54
54
  const storyNodes = (root) => {
55
55
  const nodes = [];
@@ -18,6 +18,7 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var youtube_exports = {};
20
20
  __export(youtube_exports, {
21
+ PLAYER_RESPONSE_VARIABLE: () => PLAYER_RESPONSE_VARIABLE,
21
22
  extractAssignedJson: () => extractAssignedJson,
22
23
  isYoutubeElement: () => isYoutubeElement,
23
24
  youtubeAdapter: () => youtubeAdapter,
@@ -69,12 +70,13 @@ const extractAssignedJson = (source, name) => {
69
70
  const scriptText = (root) => (0, import_hast_fast.collectElements)(root, (element2) => element2.tagName === "script").map(
70
71
  (script) => script.children.map((child) => child.type === "text" ? child.value : "").join("")
71
72
  );
73
+ const PLAYER_RESPONSE_VARIABLE = "ytInitialPlayerResponse";
72
74
  const findPlayerResponse = (root) => {
73
75
  for (const source of scriptText(root)) {
74
- if (!source.includes("ytInitialPlayerResponse")) {
76
+ if (!source.includes(PLAYER_RESPONSE_VARIABLE)) {
75
77
  continue;
76
78
  }
77
- const parsed = extractAssignedJson(source, "ytInitialPlayerResponse");
79
+ const parsed = extractAssignedJson(source, PLAYER_RESPONSE_VARIABLE);
78
80
  if (parsed && typeof parsed === "object") {
79
81
  return parsed;
80
82
  }
@@ -184,6 +186,7 @@ const youtubeAdapter = {
184
186
  const isYoutubeElement = import_hast_fast.isElement;
185
187
  // Annotate the CommonJS export names for ESM import in node:
186
188
  0 && (module.exports = {
189
+ PLAYER_RESPONSE_VARIABLE,
187
190
  extractAssignedJson,
188
191
  isYoutubeElement,
189
192
  youtubeAdapter,
@@ -18,6 +18,7 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var detect_client_shell_exports = {};
20
20
  __export(detect_client_shell_exports, {
21
+ CHALLENGE_PATTERN: () => CHALLENGE_PATTERN,
21
22
  SHELL_EMPTY_THRESHOLD: () => SHELL_EMPTY_THRESHOLD,
22
23
  SHELL_SUSPICION_THRESHOLD: () => SHELL_SUSPICION_THRESHOLD,
23
24
  detectClientShell: () => detectClientShell
@@ -72,6 +73,7 @@ const detectClientShell = (html) => {
72
73
  };
73
74
  // Annotate the CommonJS export names for ESM import in node:
74
75
  0 && (module.exports = {
76
+ CHALLENGE_PATTERN,
75
77
  SHELL_EMPTY_THRESHOLD,
76
78
  SHELL_SUSPICION_THRESHOLD,
77
79
  detectClientShell
@@ -27,10 +27,51 @@ const DATA_MARKERS = /* @__PURE__ */ new Set(["caption", "thead", "tfoot", "th",
27
27
  const BLOCK_IN_CELL = /* @__PURE__ */ new Set(["h1", "h2", "h3", "h4", "h5", "h6", "table", "form"]);
28
28
  const TABLE_PARTS = /* @__PURE__ */ new Set(["tbody", "thead", "tfoot", "tr", "td", "th"]);
29
29
  const TABLE_CONTEXT = /* @__PURE__ */ new Set(["table", "tbody", "thead", "tfoot", "tr"]);
30
+ const MAX_SMALL_TABLE_CELL = 150;
31
+ const MAX_SMALL_TABLE_LINK_SHARE = 0.5;
32
+ const collapsedLength = (text) => text.replace(/\s+/g, " ").trim().length;
33
+ const cellText = (cell) => {
34
+ let text = "";
35
+ let linkText = "";
36
+ const visit = (node, inLink) => {
37
+ for (const child of node.children) {
38
+ if (child.type === "text") {
39
+ text += child.value;
40
+ if (inLink) {
41
+ linkText += child.value;
42
+ }
43
+ } else if ((0, import_hast_fast.isElement)(child) && child.tagName !== "table") {
44
+ visit(child, inLink || child.tagName === "a");
45
+ }
46
+ }
47
+ };
48
+ visit(cell, false);
49
+ return { text: collapsedLength(text), linkText: collapsedLength(linkText) };
50
+ };
30
51
  const cellsOf = (row) => row.children.filter((cell) => (0, import_hast_fast.isElement)(cell) && (cell.tagName === "td" || cell.tagName === "th"));
31
52
  const HEADING = /^h[1-6]$/;
53
+ const measureRow = (shape, row) => {
54
+ const cells = cellsOf(row);
55
+ shape.rows += 1;
56
+ shape.columns = Math.max(shape.columns, cells.length);
57
+ for (const cell of cells) {
58
+ const { text, linkText } = cellText(cell);
59
+ shape.longestCell = Math.max(shape.longestCell, text);
60
+ shape.text += text;
61
+ shape.linkText += linkText;
62
+ }
63
+ return cells.length === 1;
64
+ };
32
65
  const shapeOf = (table) => {
33
- const shape = { rows: 0, columns: 0, dataMarker: false, blockInCell: false };
66
+ const shape = {
67
+ rows: 0,
68
+ columns: 0,
69
+ dataMarker: false,
70
+ blockInCell: false,
71
+ longestCell: 0,
72
+ text: 0,
73
+ linkText: 0
74
+ };
34
75
  let captionHeadings = 0;
35
76
  const noteBlockInCell = (tag, aloneInRow) => {
36
77
  if (HEADING.test(tag) && aloneInRow) {
@@ -52,13 +93,7 @@ const shapeOf = (table) => {
52
93
  if (DATA_MARKERS.has(tag)) {
53
94
  shape.dataMarker = true;
54
95
  }
55
- let alone = aloneInRow;
56
- if (tag === "tr") {
57
- shape.rows += 1;
58
- const cells = cellsOf(child).length;
59
- shape.columns = Math.max(shape.columns, cells);
60
- alone = cells === 1;
61
- }
96
+ const alone = tag === "tr" ? measureRow(shape, child) : aloneInRow;
62
97
  if (inCell && BLOCK_IN_CELL.has(tag)) {
63
98
  noteBlockInCell(tag, aloneInRow);
64
99
  }
@@ -89,7 +124,11 @@ const isLayoutTable = (table) => {
89
124
  if (shape.rows >= 10 || shape.columns > 4) {
90
125
  return false;
91
126
  }
92
- return shape.rows * shape.columns <= 10;
127
+ if (shape.rows * shape.columns > 10) {
128
+ return false;
129
+ }
130
+ const linkShare = shape.text === 0 ? 1 : shape.linkText / shape.text;
131
+ return shape.longestCell > MAX_SMALL_TABLE_CELL || linkShare > MAX_SMALL_TABLE_LINK_SHARE;
93
132
  };
94
133
  const unwrap = (table) => {
95
134
  const cells = [];
@@ -189,16 +189,52 @@ const containsMath = (element) => {
189
189
  }
190
190
  return false;
191
191
  };
192
+ const collapsedText = (element) => {
193
+ let text = "";
194
+ (0, import_hast_fast.walk)(element, (node) => {
195
+ if (node.type === "text") {
196
+ text += node.value;
197
+ }
198
+ });
199
+ return text.replace(/\s+/g, " ").trim();
200
+ };
201
+ const visibleSiblingText = (parent, cache) => {
202
+ let texts = cache.get(parent);
203
+ if (!texts) {
204
+ texts = /* @__PURE__ */ new Set();
205
+ for (const child of parent.children) {
206
+ if ((0, import_hast_fast.isElement)(child) && !(0, import_hast_fast.isTruthyAttribute)(child, "aria-hidden") && !looksHidden(child)) {
207
+ texts.add(collapsedText(child));
208
+ }
209
+ }
210
+ cache.set(parent, texts);
211
+ }
212
+ return texts;
213
+ };
214
+ const isAriaHiddenDuplicate = (element, parent, cache) => {
215
+ if (!(0, import_hast_fast.isTruthyAttribute)(element, "aria-hidden") || containsMath(element)) {
216
+ return false;
217
+ }
218
+ const text = collapsedText(element);
219
+ return text.length > 0 && visibleSiblingText(parent, cache).has(text);
220
+ };
192
221
  const stripNonContent = (tree) => {
193
222
  const tabs = new import_code_tabs.CodeTabCollector((element) => looksHidden(element) || import_constants.NON_CONTENT_TAGS.has(element.tagName));
194
- hoistNoscriptImages(tree, (element, depth, parent) => tabs.visit(element, depth, parent));
223
+ const duplicates = /* @__PURE__ */ new Set();
224
+ const siblingText = /* @__PURE__ */ new WeakMap();
225
+ hoistNoscriptImages(tree, (element, depth, parent) => {
226
+ tabs.visit(element, depth, parent);
227
+ if (isAriaHiddenDuplicate(element, parent, siblingText)) {
228
+ duplicates.add(element);
229
+ }
230
+ });
195
231
  tabs.apply();
196
232
  const siblingCode = /* @__PURE__ */ new WeakMap();
197
233
  return (0, import_hast_fast.pruneInPlace)(tree, (node, parent) => {
198
234
  if (isNonContent(node)) {
199
235
  return false;
200
236
  }
201
- if ((0, import_hast_fast.isElement)(node) && isHidden(node, parent, siblingCode)) {
237
+ if ((0, import_hast_fast.isElement)(node) && (duplicates.has(node) || isHidden(node, parent, siblingCode))) {
202
238
  return false;
203
239
  }
204
240
  return true;
@@ -26,6 +26,8 @@ var import_hast_fast = require("../../utils/hast-fast");
26
26
  const TERMINATOR_PATTERNS = [
27
27
  /^was this (page|article|document) helpful/i,
28
28
  /^thanks for the feedback/i,
29
+ // Site-generator credit closing the page ("Powered by Docsify.js", "Powered by GitBook").
30
+ /^powered by\b/i,
29
31
  /^(this page was )?last (modified|updated) (on|at|:)?\s/i,
30
32
  /^learn how to contribute/i,
31
33
  /^help (us )?improve\b/i,
package/dist/cjs/index.js CHANGED
@@ -49,6 +49,7 @@ __export(src_exports, {
49
49
  readabilityExtractor: () => import_extractors.readabilityExtractor,
50
50
  resolveAdapter: () => import_adapters.resolveAdapter,
51
51
  runAdapters: () => import_adapters.runAdapters,
52
+ stripScriptBodies: () => import_parse_html.stripScriptBodies,
52
53
  takumiExtractor: () => import_extractors.takumiExtractor,
53
54
  toFrontmatter: () => import_metadata.toFrontmatter,
54
55
  youtubeCaptionTracks: () => import_adapters.youtubeCaptionTracks
@@ -64,6 +65,7 @@ var import_detect_client_shell = require("./detect-client-shell");
64
65
  var import_extract_meta_refresh = require("./extract-meta-refresh");
65
66
  var import_metadata = require("./metadata");
66
67
  var import_normalize = require("./normalize");
68
+ var import_parse_html = require("./utils/parse-html");
67
69
  // Annotate the CommonJS export names for ESM import in node:
68
70
  0 && (module.exports = {
69
71
  BUILTIN_ADAPTERS,
@@ -97,6 +99,7 @@ var import_normalize = require("./normalize");
97
99
  readabilityExtractor,
98
100
  resolveAdapter,
99
101
  runAdapters,
102
+ stripScriptBodies,
100
103
  takumiExtractor,
101
104
  toFrontmatter,
102
105
  youtubeCaptionTracks
@@ -29,17 +29,46 @@ var import_hast_fast = require("../utils/hast-fast");
29
29
  var import_pre_text = require("../utils/pre-text");
30
30
  var import_twoslash = require("./twoslash");
31
31
  const LANGUAGE_MATCH_REGEX = [/^language-(\S+)$/, /^highlight-source-(\S+)$/, /^CodeBlock--language-(\S+)$/];
32
+ const LANGUAGE_TOKEN = /^[\w+#.-]+$/;
33
+ const NO_LANGUAGE = /* @__PURE__ */ new Set(["plain", "plaintext", "text", "none", "nohighlight"]);
32
34
  const LANGUAGE_ATTRIBUTES = ["dataLanguage", "dataLang"];
33
35
  const attributeLanguage = (element) => {
34
36
  for (const name of LANGUAGE_ATTRIBUTES) {
35
37
  const value = element.properties?.[name];
36
- if (typeof value === "string" && /^[\w+#.-]+$/.test(value.trim()) && value.trim() !== "plaintext") {
38
+ if (typeof value === "string" && LANGUAGE_TOKEN.test(value.trim()) && value.trim() !== "plaintext") {
37
39
  return value.trim();
38
40
  }
39
41
  }
40
42
  return void 0;
41
43
  };
42
- const codeLanguage = (...elements) => elements.flatMap(import_hast_fast.classList).map((className) => LANGUAGE_MATCH_REGEX.map((regex) => className.match(regex)).find(Boolean)?.[1]).find(Boolean) ?? elements.map(attributeLanguage).find(Boolean);
44
+ const SANDPACK_LANGUAGES = /* @__PURE__ */ new Set([
45
+ "javascript",
46
+ "typescript",
47
+ "jsx",
48
+ "tsx",
49
+ "html",
50
+ "css",
51
+ "less",
52
+ "scss",
53
+ "sass",
54
+ "json",
55
+ "markdown",
56
+ "python",
57
+ "vue",
58
+ "svelte"
59
+ ]);
60
+ const conventionLanguage = (element) => {
61
+ const classes = (0, import_hast_fast.classList)(element);
62
+ const brushAt = classes.findIndex((name) => name.startsWith("brush:"));
63
+ if (brushAt >= 0) {
64
+ const brush = classes[brushAt] === "brush:" ? classes[brushAt + 1] : classes[brushAt].slice("brush:".length);
65
+ const lang = brush?.toLowerCase();
66
+ return lang && LANGUAGE_TOKEN.test(lang) && !NO_LANGUAGE.has(lang) ? lang : void 0;
67
+ }
68
+ const sandpack = classes.find((name) => name.startsWith("sp-") && SANDPACK_LANGUAGES.has(name.slice(3)));
69
+ return sandpack?.slice(3);
70
+ };
71
+ const codeLanguage = (...elements) => elements.flatMap(import_hast_fast.classList).map((className) => LANGUAGE_MATCH_REGEX.map((regex) => className.match(regex)).find(Boolean)?.[1]).find(Boolean) ?? elements.map(attributeLanguage).find(Boolean) ?? elements.map(conventionLanguage).find(Boolean);
43
72
  const codeMeta = (pre) => {
44
73
  const title = pre.properties?.dataTitle;
45
74
  if (typeof title !== "string") {
@@ -20,11 +20,13 @@ var normalize_exports = {};
20
20
  __export(normalize_exports, {
21
21
  bestFromSrcset: () => bestFromSrcset,
22
22
  dedupeRenderedMath: () => dedupeRenderedMath,
23
+ foldCodeLabels: () => foldCodeLabels,
23
24
  normalizeHast: () => normalizeHast,
24
25
  promoteAriaHeadings: () => promoteAriaHeadings,
25
26
  removeIconFonts: () => removeIconFonts,
26
27
  resolveImageSources: () => resolveImageSources,
27
- separateAdjacentInlines: () => separateAdjacentInlines
28
+ separateAdjacentInlines: () => separateAdjacentInlines,
29
+ unwrapHeadingAnchors: () => unwrapHeadingAnchors
28
30
  });
29
31
  module.exports = __toCommonJS(normalize_exports);
30
32
  var import_hast_fast = require("../utils/hast-fast");
@@ -164,6 +166,82 @@ const removeIconFonts = (tree) => {
164
166
  (0, import_hast_fast.pruneInPlace)(tree, (node) => !isIcon(node));
165
167
  }
166
168
  };
169
+ const textOfNode = (node) => node.type === "text" ? node.value : "children" in node ? node.children.map(textOfNode).join("") : "";
170
+ const unwrapHeadingAnchors = (tree) => {
171
+ (0, import_hast_fast.walk)(tree, (node) => {
172
+ if (!((0, import_hast_fast.isElement)(node) && /^h[1-6]$/.test(node.tagName))) {
173
+ return;
174
+ }
175
+ const title = textOfNode(node).trim();
176
+ if (title === "") {
177
+ return;
178
+ }
179
+ const anchorAt = node.children.findIndex(
180
+ (child) => (0, import_hast_fast.isElement)(child) && child.tagName === "a" && (0, import_hast_fast.stringProperty)(child, "href")?.startsWith("#") && textOfNode(child).trim() === title
181
+ );
182
+ const anchor = node.children[anchorAt];
183
+ if ((0, import_hast_fast.isElement)(anchor)) {
184
+ node.children.splice(anchorAt, 1, ...anchor.children);
185
+ }
186
+ });
187
+ };
188
+ const LABEL_LANGUAGES = /* @__PURE__ */ new Set([
189
+ "js",
190
+ "javascript",
191
+ "ts",
192
+ "typescript",
193
+ "jsx",
194
+ "tsx",
195
+ "html",
196
+ "css",
197
+ "scss",
198
+ "json",
199
+ "bash",
200
+ "sh",
201
+ "shell",
202
+ "console",
203
+ "python",
204
+ "py",
205
+ "sql",
206
+ "xml",
207
+ "yaml",
208
+ "toml",
209
+ "http",
210
+ "rust",
211
+ "go",
212
+ "java",
213
+ "kotlin",
214
+ "swift",
215
+ "c",
216
+ "cpp",
217
+ "md",
218
+ "markdown",
219
+ "diff"
220
+ ]);
221
+ const LABEL_NO_LANGUAGE = /* @__PURE__ */ new Set(["plain", "plaintext", "text"]);
222
+ const foldCodeLabels = (tree) => {
223
+ (0, import_hast_fast.walk)(tree, (node) => {
224
+ if (!(0, import_hast_fast.isElement)(node) || node.tagName === "pre") {
225
+ return;
226
+ }
227
+ const elements = node.children.filter(import_hast_fast.isElement);
228
+ if (elements.length !== 2 || node.children.some((child) => child.type === "text" && child.value.trim() !== "")) {
229
+ return;
230
+ }
231
+ const [label, pre] = elements;
232
+ if (pre.tagName !== "pre" || /^(h[1-6]|pre)$/.test(label.tagName) || (0, import_hast_fast.findElement)(label, (el) => el.tagName === "pre")) {
233
+ return;
234
+ }
235
+ const name = textOfNode(label).trim().toLowerCase();
236
+ if (!(LABEL_LANGUAGES.has(name) || LABEL_NO_LANGUAGE.has(name))) {
237
+ return;
238
+ }
239
+ if (LABEL_LANGUAGES.has(name) && pre.properties.dataLanguage === void 0) {
240
+ pre.properties.dataLanguage = name;
241
+ }
242
+ node.children = node.children.filter((child) => child !== label);
243
+ });
244
+ };
167
245
  const SPACED_INLINE = /* @__PURE__ */ new Set(["span", "time", "label"]);
168
246
  const SCRIPTS = [
169
247
  ["latin", /\p{Script=Latin}/u],
@@ -180,7 +258,6 @@ const needsSpace = (left, right) => {
180
258
  return a !== void 0 && b !== void 0 && a !== b;
181
259
  };
182
260
  const NO_SPACING = /* @__PURE__ */ new Set(["pre", "code", "kbd", "samp", "ruby", "math", "svg"]);
183
- const textOfNode = (node) => node.type === "text" ? node.value : "children" in node ? node.children.map(textOfNode).join("") : "";
184
261
  const separateAdjacentInlines = (tree) => {
185
262
  const visit = (node) => {
186
263
  if (!("children" in node) || (0, import_hast_fast.isElement)(node) && NO_SPACING.has(node.tagName)) {
@@ -201,7 +278,15 @@ const separateAdjacentInlines = (tree) => {
201
278
  visit(tree);
202
279
  };
203
280
  const normalizeHast = (tree, options = {}) => {
204
- const { images = true, math = true, ariaHeadings = true, iconFonts = true, inlineSpacing = true } = options;
281
+ const {
282
+ images = true,
283
+ math = true,
284
+ ariaHeadings = true,
285
+ iconFonts = true,
286
+ headingAnchors = true,
287
+ codeLabels = true,
288
+ inlineSpacing = true
289
+ } = options;
205
290
  if (images) {
206
291
  resolveImageSources(tree);
207
292
  }
@@ -214,6 +299,12 @@ const normalizeHast = (tree, options = {}) => {
214
299
  if (iconFonts) {
215
300
  removeIconFonts(tree);
216
301
  }
302
+ if (headingAnchors) {
303
+ unwrapHeadingAnchors(tree);
304
+ }
305
+ if (codeLabels) {
306
+ foldCodeLabels(tree);
307
+ }
217
308
  if (inlineSpacing) {
218
309
  separateAdjacentInlines(tree);
219
310
  }
@@ -223,9 +314,11 @@ const normalizeHast = (tree, options = {}) => {
223
314
  0 && (module.exports = {
224
315
  bestFromSrcset,
225
316
  dedupeRenderedMath,
317
+ foldCodeLabels,
226
318
  normalizeHast,
227
319
  promoteAriaHeadings,
228
320
  removeIconFonts,
229
321
  resolveImageSources,
230
- separateAdjacentInlines
322
+ separateAdjacentInlines,
323
+ unwrapHeadingAnchors
231
324
  });
@@ -62,11 +62,10 @@ const languages = [
62
62
  ["json", [/\b(true|false|null|\{})\b|\"[^"]+\":/g, 10]],
63
63
  ["yaml", [/^(\s+)?[a-z][a-z0-9]*:/gim, 10]]
64
64
  ];
65
- const detectLanguage = (code) => {
66
- return languages.map(
67
- ([lang, ...features]) => [lang, features.reduce((acc, [match, score]) => acc + [...code.matchAll(match)].length * score, 0)]
68
- ).filter(([_, score]) => score > 20).sort((a, b) => b[1] - a[1])[0]?.[0] || "plain";
69
- };
65
+ const MIN_CONFIDENT_SCORE = 400;
66
+ const detectLanguage = (code) => languages.map(
67
+ ([lang, ...features]) => [lang, features.reduce((acc, [match, score]) => acc + [...code.matchAll(match)].length * score, 0)]
68
+ ).filter(([_, score]) => score >= MIN_CONFIDENT_SCORE).sort((a, b) => b[1] - a[1])[0]?.[0];
70
69
  // Annotate the CommonJS export names for ESM import in node:
71
70
  0 && (module.exports = {
72
71
  detectLanguage
@@ -26,6 +26,8 @@ __export(parse_html_exports, {
26
26
  module.exports = __toCommonJS(parse_html_exports);
27
27
  var import_hast_util_from_parse5 = require("hast-util-from-parse5");
28
28
  var import_parse5 = require("parse5");
29
+ var import_youtube = require("../adapters/sites/youtube");
30
+ var import_detect_client_shell = require("../detect-client-shell");
29
31
  const MAX_ELEMENT_DEPTH = 256;
30
32
  const isParent = (node) => "childNodes" in node;
31
33
  const LINE_ELEMENTS = /* @__PURE__ */ new Set([
@@ -92,12 +94,78 @@ const flattenDeep = (root, maxDepth) => {
92
94
  }
93
95
  }
94
96
  };
95
- const SCRIPT_OR_STYLE = /<(script|style)\b([^>]*)>[\s\S]*?<\/\1\s*>/gi;
97
+ const SCRIPT_OR_STYLE_START = /<!--|<(script|style)(?=[\s/>])/gi;
98
+ const CLOSING_TAG = {
99
+ script: /<\/script[\s/>]/gi,
100
+ style: /<\/style[\s/>]/gi
101
+ };
102
+ const startTagEnd = (html, from) => {
103
+ let index = from;
104
+ while (index < html.length) {
105
+ const char = html[index];
106
+ if (char === ">") {
107
+ return index + 1;
108
+ }
109
+ if (char === "=") {
110
+ let value = index + 1;
111
+ while (value < html.length && /\s/.test(html[value] ?? "")) {
112
+ value++;
113
+ }
114
+ const quote = html[value];
115
+ if (quote === '"' || quote === "'") {
116
+ const close = html.indexOf(quote, value + 1);
117
+ if (close < 0) {
118
+ return -1;
119
+ }
120
+ index = close + 1;
121
+ continue;
122
+ }
123
+ index = value;
124
+ continue;
125
+ }
126
+ index++;
127
+ }
128
+ return -1;
129
+ };
130
+ const isReadScript = (attributes, element) => /application\/ld\+json/i.test(attributes) || element.includes(import_youtube.PLAYER_RESPONSE_VARIABLE) || import_detect_client_shell.CHALLENGE_PATTERN.test(element);
96
131
  const LARGE_DOCUMENT_CHARS = 2e6;
97
- const stripScriptBodies = (html) => html.replace(
98
- SCRIPT_OR_STYLE,
99
- (match, tag, attributes) => /application\/ld\+json/i.test(attributes) ? match : `<${tag}${attributes}></${tag}>`
100
- );
132
+ const stripScriptBodies = (html) => {
133
+ const out = [];
134
+ let copied = 0;
135
+ const start = new RegExp(SCRIPT_OR_STYLE_START);
136
+ for (let match = start.exec(html); match; match = start.exec(html)) {
137
+ if (match[1] === void 0) {
138
+ const commentEnd = html.indexOf("-->", match.index + 4);
139
+ if (commentEnd < 0) {
140
+ break;
141
+ }
142
+ start.lastIndex = commentEnd + 3;
143
+ continue;
144
+ }
145
+ const tag = match[1].toLowerCase();
146
+ const bodyStart = startTagEnd(html, match.index + match[0].length);
147
+ if (bodyStart < 0) {
148
+ break;
149
+ }
150
+ const closing = CLOSING_TAG[tag];
151
+ closing.lastIndex = bodyStart;
152
+ const close = closing.exec(html);
153
+ if (!close) {
154
+ break;
155
+ }
156
+ const tagEnd = html.indexOf(">", close.index);
157
+ const elementEnd = tagEnd < 0 ? html.length : tagEnd + 1;
158
+ const element = html.slice(match.index, elementEnd);
159
+ const openingTag = html.slice(match.index, bodyStart);
160
+ if (!(tag === "script" && isReadScript(openingTag, element))) {
161
+ out.push(html.slice(copied, bodyStart), html.slice(close.index, elementEnd));
162
+ copied = elementEnd;
163
+ }
164
+ start.lastIndex = elementEnd;
165
+ }
166
+ out.push(html.slice(copied));
167
+ return out.join("");
168
+ };
101
169
  const parseHtml = (html, options = {}) => {
102
170
  const source = html.length > LARGE_DOCUMENT_CHARS ? stripScriptBodies(html) : html;
103
171
  const document = options.fragment ? (0, import_parse5.parseFragment)(source, { scriptingEnabled: false }) : (0, import_parse5.parse)(source, { scriptingEnabled: false });
@@ -47,6 +47,7 @@ var detectClientShell = (html) => {
47
47
  return { isShell: false, visibleTextLength };
48
48
  };
49
49
  export {
50
+ CHALLENGE_PATTERN,
50
51
  SHELL_EMPTY_THRESHOLD,
51
52
  SHELL_SUSPICION_THRESHOLD,
52
53
  detectClientShell
@@ -4,10 +4,51 @@ var DATA_MARKERS = /* @__PURE__ */ new Set(["caption", "thead", "tfoot", "th", "
4
4
  var BLOCK_IN_CELL = /* @__PURE__ */ new Set(["h1", "h2", "h3", "h4", "h5", "h6", "table", "form"]);
5
5
  var TABLE_PARTS = /* @__PURE__ */ new Set(["tbody", "thead", "tfoot", "tr", "td", "th"]);
6
6
  var TABLE_CONTEXT = /* @__PURE__ */ new Set(["table", "tbody", "thead", "tfoot", "tr"]);
7
+ var MAX_SMALL_TABLE_CELL = 150;
8
+ var MAX_SMALL_TABLE_LINK_SHARE = 0.5;
9
+ var collapsedLength = (text) => text.replace(/\s+/g, " ").trim().length;
10
+ var cellText = (cell) => {
11
+ let text = "";
12
+ let linkText = "";
13
+ const visit = (node, inLink) => {
14
+ for (const child of node.children) {
15
+ if (child.type === "text") {
16
+ text += child.value;
17
+ if (inLink) {
18
+ linkText += child.value;
19
+ }
20
+ } else if (isElement(child) && child.tagName !== "table") {
21
+ visit(child, inLink || child.tagName === "a");
22
+ }
23
+ }
24
+ };
25
+ visit(cell, false);
26
+ return { text: collapsedLength(text), linkText: collapsedLength(linkText) };
27
+ };
7
28
  var cellsOf = (row) => row.children.filter((cell) => isElement(cell) && (cell.tagName === "td" || cell.tagName === "th"));
8
29
  var HEADING = /^h[1-6]$/;
30
+ var measureRow = (shape, row) => {
31
+ const cells = cellsOf(row);
32
+ shape.rows += 1;
33
+ shape.columns = Math.max(shape.columns, cells.length);
34
+ for (const cell of cells) {
35
+ const { text, linkText } = cellText(cell);
36
+ shape.longestCell = Math.max(shape.longestCell, text);
37
+ shape.text += text;
38
+ shape.linkText += linkText;
39
+ }
40
+ return cells.length === 1;
41
+ };
9
42
  var shapeOf = (table) => {
10
- const shape = { rows: 0, columns: 0, dataMarker: false, blockInCell: false };
43
+ const shape = {
44
+ rows: 0,
45
+ columns: 0,
46
+ dataMarker: false,
47
+ blockInCell: false,
48
+ longestCell: 0,
49
+ text: 0,
50
+ linkText: 0
51
+ };
11
52
  let captionHeadings = 0;
12
53
  const noteBlockInCell = (tag, aloneInRow) => {
13
54
  if (HEADING.test(tag) && aloneInRow) {
@@ -29,13 +70,7 @@ var shapeOf = (table) => {
29
70
  if (DATA_MARKERS.has(tag)) {
30
71
  shape.dataMarker = true;
31
72
  }
32
- let alone = aloneInRow;
33
- if (tag === "tr") {
34
- shape.rows += 1;
35
- const cells = cellsOf(child).length;
36
- shape.columns = Math.max(shape.columns, cells);
37
- alone = cells === 1;
38
- }
73
+ const alone = tag === "tr" ? measureRow(shape, child) : aloneInRow;
39
74
  if (inCell && BLOCK_IN_CELL.has(tag)) {
40
75
  noteBlockInCell(tag, aloneInRow);
41
76
  }
@@ -66,7 +101,11 @@ var isLayoutTable = (table) => {
66
101
  if (shape.rows >= 10 || shape.columns > 4) {
67
102
  return false;
68
103
  }
69
- return shape.rows * shape.columns <= 10;
104
+ if (shape.rows * shape.columns > 10) {
105
+ return false;
106
+ }
107
+ const linkShare = shape.text === 0 ? 1 : shape.linkText / shape.text;
108
+ return shape.longestCell > MAX_SMALL_TABLE_CELL || linkShare > MAX_SMALL_TABLE_LINK_SHARE;
70
109
  };
71
110
  var unwrap = (table) => {
72
111
  const cells = [];