webforai 2.1.1 → 3.0.0
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/README.md +63 -1
- package/dist/adapters/index.js +58 -0
- package/dist/adapters/registry.js +149 -0
- package/dist/adapters/sites/declarative.js +178 -0
- package/dist/adapters/sites/hackernews.js +94 -0
- package/dist/adapters/sites/youtube.js +165 -0
- package/dist/adapters/types.js +0 -0
- package/dist/bin.js +3197 -402
- package/dist/cjs/adapters/index.js +86 -0
- package/dist/cjs/adapters/registry.js +178 -0
- package/dist/cjs/adapters/sites/declarative.js +202 -0
- package/dist/cjs/adapters/sites/hackernews.js +117 -0
- package/dist/cjs/adapters/sites/youtube.js +191 -0
- package/dist/cjs/adapters/types.js +16 -0
- package/dist/cjs/constants.js +2 -2
- package/dist/cjs/extractors/index.js +5 -0
- package/dist/cjs/extractors/lib/constants.js +186 -0
- package/dist/cjs/extractors/lib/sanitize.js +425 -0
- package/dist/cjs/extractors/lib/score.js +264 -0
- package/dist/cjs/extractors/lib/terminators.js +131 -0
- package/dist/cjs/extractors/lib/ui-chrome.js +119 -0
- package/dist/cjs/extractors/pipeExtractors.js +12 -9
- package/dist/cjs/extractors/presets/auto.js +44 -0
- package/dist/cjs/extractors/presets/minimal-filter.js +27 -48
- package/dist/cjs/extractors/presets/takumi.js +84 -122
- package/dist/cjs/html-to-markdown.js +44 -7
- package/dist/cjs/html-to-mdast.js +9 -1
- package/dist/cjs/index.js +31 -2
- package/dist/cjs/mdast-handlers/custom-a-handler.js +25 -9
- package/dist/cjs/mdast-handlers/custom-img-handler.js +23 -1
- package/dist/cjs/mdast-handlers/custom-table-handler.js +16 -0
- package/dist/cjs/mdast-handlers/definition-list-handler.js +99 -0
- package/dist/cjs/mdast-handlers/math-handler.js +12 -3
- package/dist/cjs/mdast-handlers/sup-sub-handler.js +39 -0
- package/dist/cjs/metadata/index.js +223 -0
- package/dist/cjs/normalize/index.js +168 -0
- package/dist/cjs/platform/client.js +147 -0
- package/dist/cjs/platform/error.js +36 -0
- package/dist/cjs/platform/fetch.js +37 -0
- package/dist/cjs/platform/index.js +40 -0
- package/dist/cjs/platform/types.js +34 -0
- package/dist/cjs/utils/hast-fast.js +284 -0
- package/dist/constants.js +2 -2
- package/dist/extractors/index.js +3 -0
- package/dist/extractors/lib/constants.js +150 -0
- package/dist/extractors/lib/sanitize.js +415 -0
- package/dist/extractors/lib/score.js +242 -0
- package/dist/extractors/lib/terminators.js +107 -0
- package/dist/extractors/lib/ui-chrome.js +96 -0
- package/dist/extractors/pipeExtractors.js +12 -9
- package/dist/extractors/presets/auto.js +20 -0
- package/dist/extractors/presets/minimal-filter.js +35 -47
- package/dist/extractors/presets/takumi.js +91 -122
- package/dist/html-to-markdown.js +42 -6
- package/dist/html-to-mdast.js +9 -1
- package/dist/index.js +31 -2
- package/dist/mdast-handlers/custom-a-handler.js +25 -9
- package/dist/mdast-handlers/custom-img-handler.js +23 -1
- package/dist/mdast-handlers/custom-table-handler.js +16 -0
- package/dist/mdast-handlers/definition-list-handler.js +76 -0
- package/dist/mdast-handlers/math-handler.js +12 -3
- package/dist/mdast-handlers/sup-sub-handler.js +16 -0
- package/dist/metadata/index.js +198 -0
- package/dist/normalize/index.js +149 -0
- package/dist/platform/client.js +125 -0
- package/dist/platform/error.js +13 -0
- package/dist/platform/fetch.js +14 -0
- package/dist/platform/index.js +19 -0
- package/dist/platform/types.js +9 -0
- package/dist/types/adapters/index.d.ts +24 -0
- package/dist/types/adapters/index.js.map +1 -0
- package/dist/types/adapters/registry.d.ts +35 -0
- package/dist/types/adapters/registry.js.map +1 -0
- package/dist/types/adapters/sites/declarative.d.ts +13 -0
- package/dist/types/adapters/sites/declarative.js.map +1 -0
- package/dist/types/adapters/sites/hackernews.d.ts +13 -0
- package/dist/types/adapters/sites/hackernews.js.map +1 -0
- package/dist/types/adapters/sites/youtube.d.ts +31 -0
- package/dist/types/adapters/sites/youtube.js.map +1 -0
- package/dist/types/adapters/types.d.ts +99 -0
- package/dist/types/adapters/types.js.map +1 -0
- package/dist/types/cli/bin.js.map +1 -1
- package/dist/types/cli/commands/skill/content.d.ts +9 -0
- package/dist/types/cli/commands/skill/content.js.map +1 -0
- package/dist/types/cli/commands/skill/index.d.ts +19 -0
- package/dist/types/cli/commands/skill/index.js.map +1 -0
- package/dist/types/cli/commands/webforai/interactive.d.ts +7 -0
- package/dist/types/cli/commands/webforai/interactive.js.map +1 -0
- package/dist/types/cli/commands/webforai/loadHtml.js.map +1 -1
- package/dist/types/cli/commands/webforai/options.d.ts +55 -0
- package/dist/types/cli/commands/webforai/options.js.map +1 -0
- package/dist/types/cli/commands/webforai/run.d.ts +10 -0
- package/dist/types/cli/commands/webforai/run.js.map +1 -0
- package/dist/types/cli/constants.d.ts +14 -4
- package/dist/types/cli/constants.js.map +1 -1
- package/dist/types/cli/helpers/selectExtractMode.js.map +1 -1
- package/dist/types/cli/helpers/selectLoader.js.map +1 -1
- package/dist/types/constants.d.ts +7 -1
- package/dist/types/constants.js.map +1 -1
- package/dist/types/extractors/index.d.ts +1 -0
- package/dist/types/extractors/index.js.map +1 -1
- package/dist/types/extractors/lib/constants.d.ts +81 -0
- package/dist/types/extractors/lib/constants.js.map +1 -0
- package/dist/types/extractors/lib/sanitize.d.ts +66 -0
- package/dist/types/extractors/lib/sanitize.js.map +1 -0
- package/dist/types/extractors/lib/score.d.ts +85 -0
- package/dist/types/extractors/lib/score.js.map +1 -0
- package/dist/types/extractors/lib/terminators.d.ts +26 -0
- package/dist/types/extractors/lib/terminators.js.map +1 -0
- package/dist/types/extractors/lib/ui-chrome.d.ts +18 -0
- package/dist/types/extractors/lib/ui-chrome.js.map +1 -0
- package/dist/types/extractors/pipeExtractors.d.ts +5 -0
- package/dist/types/extractors/pipeExtractors.js.map +1 -1
- package/dist/types/extractors/presets/auto.d.ts +30 -0
- package/dist/types/extractors/presets/auto.js.map +1 -0
- package/dist/types/extractors/presets/minimal-filter.d.ts +10 -2
- package/dist/types/extractors/presets/minimal-filter.js.map +1 -1
- package/dist/types/extractors/presets/takumi.d.ts +19 -3
- package/dist/types/extractors/presets/takumi.js.map +1 -1
- package/dist/types/extractors/types.d.ts +8 -0
- package/dist/types/html-to-markdown.d.ts +38 -0
- package/dist/types/html-to-markdown.js.map +1 -1
- package/dist/types/html-to-mdast.d.ts +11 -0
- package/dist/types/html-to-mdast.js.map +1 -1
- package/dist/types/index.d.ts +6 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/mdast-handlers/custom-a-handler.js.map +1 -1
- package/dist/types/mdast-handlers/custom-img-handler.js.map +1 -1
- package/dist/types/mdast-handlers/custom-table-handler.js.map +1 -1
- package/dist/types/mdast-handlers/definition-list-handler.d.ts +13 -0
- package/dist/types/mdast-handlers/definition-list-handler.js.map +1 -0
- package/dist/types/mdast-handlers/math-handler.js.map +1 -1
- package/dist/types/mdast-handlers/sup-sub-handler.d.ts +10 -0
- package/dist/types/mdast-handlers/sup-sub-handler.js.map +1 -0
- package/dist/types/metadata/index.d.ts +47 -0
- package/dist/types/metadata/index.js.map +1 -0
- package/dist/types/normalize/index.d.ts +53 -0
- package/dist/types/normalize/index.js.map +1 -0
- package/dist/types/platform/client.d.ts +54 -0
- package/dist/types/platform/client.js.map +1 -0
- package/dist/types/platform/error.d.ts +20 -0
- package/dist/types/platform/error.js.map +1 -0
- package/dist/types/platform/fetch.d.ts +34 -0
- package/dist/types/platform/fetch.js.map +1 -0
- package/dist/types/platform/index.d.ts +4 -0
- package/dist/types/platform/index.js.map +1 -0
- package/dist/types/platform/types.d.ts +135 -0
- package/dist/types/platform/types.js.map +1 -0
- package/dist/types/utils/hast-fast.d.ts +101 -0
- package/dist/types/utils/hast-fast.js.map +1 -0
- package/dist/utils/hast-fast.js +246 -0
- package/package.json +10 -3
- package/dist/types/cli/commands/webforai/index.d.ts +0 -6
- package/dist/types/cli/commands/webforai/index.js.map +0 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// src/extractors/lib/terminators.ts
|
|
2
|
+
import { isElement, isParent, pruneInPlace } from "../../utils/hast-fast.js";
|
|
3
|
+
var TERMINATOR_PATTERNS = [
|
|
4
|
+
/^was this (page|article|document) helpful/i,
|
|
5
|
+
/^thanks for the feedback/i,
|
|
6
|
+
/^(this page was )?last (modified|updated) (on|at|:)?\s/i,
|
|
7
|
+
/^learn how to contribute/i,
|
|
8
|
+
/^help (us )?improve\b/i,
|
|
9
|
+
// The stranded Yes/No pair of a feedback widget whose question was removed separately.
|
|
10
|
+
/^yes ?no$/i,
|
|
11
|
+
/^(related (articles?|posts?|stories|content)|you might also like|recommended for you|more from|read next)$/i,
|
|
12
|
+
/^(comments?|leave a (comment|reply)|join the (conversation|discussion))$/i,
|
|
13
|
+
/^(この(ページ|記事)は(参考|役に立ち)ました?か)/,
|
|
14
|
+
/^(関連記事|あわせて読みたい|こちらもおすすめ|おすすめ記事)$/,
|
|
15
|
+
/^(コメントを(残す|書く)|コメント一覧)$/,
|
|
16
|
+
/^最終更新日?[::\s]/
|
|
17
|
+
];
|
|
18
|
+
var TAIL_START_FRACTION = 0.8;
|
|
19
|
+
var MAX_TERMINATOR_BLOCK_LENGTH = 500;
|
|
20
|
+
var BLOCK_TAGS = /* @__PURE__ */ new Set(["p", "div", "section", "footer", "aside", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol"]);
|
|
21
|
+
var isTerminatorBlock = (element, start, tailStart) => {
|
|
22
|
+
if (!BLOCK_TAGS.has(element.tagName) || start < tailStart) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const text = textOf(element).replace(/\s+/g, " ").trim();
|
|
26
|
+
if (text.length === 0 || text.length > MAX_TERMINATOR_BLOCK_LENGTH) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return TERMINATOR_PATTERNS.some((pattern) => pattern.test(text));
|
|
30
|
+
};
|
|
31
|
+
var textOf = (node) => {
|
|
32
|
+
if (node.type === "text") {
|
|
33
|
+
return node.value;
|
|
34
|
+
}
|
|
35
|
+
if (!isParent(node)) {
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
let text = "";
|
|
39
|
+
for (const child of node.children) {
|
|
40
|
+
text += textOf(child);
|
|
41
|
+
}
|
|
42
|
+
return text;
|
|
43
|
+
};
|
|
44
|
+
var truncateTrailingBoilerplate = (tree) => {
|
|
45
|
+
const starts = /* @__PURE__ */ new Map();
|
|
46
|
+
let total = 0;
|
|
47
|
+
const measure = (node) => {
|
|
48
|
+
starts.set(node, total);
|
|
49
|
+
if (node.type === "text") {
|
|
50
|
+
total += node.value.trim().length;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (isParent(node)) {
|
|
54
|
+
for (const child of node.children) {
|
|
55
|
+
measure(child);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
measure(tree);
|
|
60
|
+
if (total === 0) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const tailStart = total * TAIL_START_FRACTION;
|
|
64
|
+
let cutoff = -1;
|
|
65
|
+
const find = (node) => {
|
|
66
|
+
if (cutoff !== -1 || !isParent(node)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
for (const child of node.children) {
|
|
70
|
+
if (cutoff !== -1) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!isElement(child)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const start = starts.get(child) ?? 0;
|
|
77
|
+
if (isTerminatorBlock(child, start, tailStart)) {
|
|
78
|
+
cutoff = start;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
find(child);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
find(tree);
|
|
85
|
+
if (cutoff === -1) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
pruneInPlace(tree, (node) => {
|
|
89
|
+
if (node === tree) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
const start = starts.get(node);
|
|
93
|
+
if (start === void 0) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
if (start >= cutoff) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
});
|
|
101
|
+
return true;
|
|
102
|
+
};
|
|
103
|
+
var TERMINATOR_TAIL_FRACTION = TAIL_START_FRACTION;
|
|
104
|
+
export {
|
|
105
|
+
TERMINATOR_TAIL_FRACTION,
|
|
106
|
+
truncateTrailingBoilerplate
|
|
107
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// src/extractors/lib/ui-chrome.ts
|
|
2
|
+
var UI_PHRASES = /* @__PURE__ */ new Set([
|
|
3
|
+
// Documentation and article controls
|
|
4
|
+
"copy",
|
|
5
|
+
"copy page",
|
|
6
|
+
"copy link",
|
|
7
|
+
"copy code",
|
|
8
|
+
"copied",
|
|
9
|
+
"edit this page",
|
|
10
|
+
"edit on github",
|
|
11
|
+
"on this page",
|
|
12
|
+
"in this article",
|
|
13
|
+
"table of contents",
|
|
14
|
+
"contents",
|
|
15
|
+
"print",
|
|
16
|
+
"print this page",
|
|
17
|
+
"back to top",
|
|
18
|
+
"scroll to top",
|
|
19
|
+
"skip to content",
|
|
20
|
+
"skip to main content",
|
|
21
|
+
"permalink",
|
|
22
|
+
"anchor",
|
|
23
|
+
// Feedback widgets
|
|
24
|
+
"was this page helpful",
|
|
25
|
+
"was this helpful",
|
|
26
|
+
"was this article helpful",
|
|
27
|
+
"yes no",
|
|
28
|
+
"feedback",
|
|
29
|
+
"send feedback",
|
|
30
|
+
"report an issue",
|
|
31
|
+
"rate this page",
|
|
32
|
+
// Sharing and subscription
|
|
33
|
+
"share",
|
|
34
|
+
"share this",
|
|
35
|
+
"share this article",
|
|
36
|
+
"share on twitter",
|
|
37
|
+
"share on facebook",
|
|
38
|
+
"subscribe",
|
|
39
|
+
"sign up",
|
|
40
|
+
"sign in",
|
|
41
|
+
"log in",
|
|
42
|
+
"login",
|
|
43
|
+
"newsletter",
|
|
44
|
+
// Advertising and promotion
|
|
45
|
+
"advertisement",
|
|
46
|
+
"advertisements",
|
|
47
|
+
"sponsored",
|
|
48
|
+
"sponsored content",
|
|
49
|
+
"promoted",
|
|
50
|
+
// Navigation affordances
|
|
51
|
+
"read more",
|
|
52
|
+
"show more",
|
|
53
|
+
"show less",
|
|
54
|
+
"see more",
|
|
55
|
+
"load more",
|
|
56
|
+
"previous",
|
|
57
|
+
"next",
|
|
58
|
+
"previous page",
|
|
59
|
+
"next page",
|
|
60
|
+
"menu",
|
|
61
|
+
"close",
|
|
62
|
+
"search",
|
|
63
|
+
// Japanese equivalents
|
|
64
|
+
"\u76EE\u6B21",
|
|
65
|
+
"\u5E83\u544A",
|
|
66
|
+
"\u30B7\u30A7\u30A2",
|
|
67
|
+
"\u5171\u6709",
|
|
68
|
+
"\u3053\u306E\u8A18\u4E8B\u3092\u30B7\u30A7\u30A2",
|
|
69
|
+
"\u7D9A\u304D\u3092\u8AAD\u3080",
|
|
70
|
+
"\u3082\u3063\u3068\u898B\u308B",
|
|
71
|
+
"\u95A2\u9023\u8A18\u4E8B",
|
|
72
|
+
"\u30B9\u30DD\u30F3\u30B5\u30FC\u30EA\u30F3\u30AF",
|
|
73
|
+
"\u30DA\u30FC\u30B8\u306E\u5148\u982D\u3078",
|
|
74
|
+
"\u30C8\u30C3\u30D7\u3078\u623B\u308B",
|
|
75
|
+
"\u524D\u3078",
|
|
76
|
+
"\u6B21\u3078",
|
|
77
|
+
"\u9589\u3058\u308B",
|
|
78
|
+
"\u691C\u7D22",
|
|
79
|
+
"\u30ED\u30B0\u30A4\u30F3",
|
|
80
|
+
"\u4F1A\u54E1\u767B\u9332"
|
|
81
|
+
]);
|
|
82
|
+
var MAX_PHRASE_LENGTH = 40;
|
|
83
|
+
var normalize = (text) => text.toLowerCase().replace(/[?!.,:;·・…"'“”‘’()[\]{}<>|/\\—–-]/g, " ").replace(/\s+/g, " ").trim();
|
|
84
|
+
var isUiChromeText = (text) => {
|
|
85
|
+
if (text.length === 0 || text.length > MAX_PHRASE_LENGTH) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const normalized = normalize(text);
|
|
89
|
+
if (normalized.length === 0) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return UI_PHRASES.has(normalized);
|
|
93
|
+
};
|
|
94
|
+
export {
|
|
95
|
+
isUiChromeText
|
|
96
|
+
};
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
// src/extractors/pipeExtractors.ts
|
|
2
2
|
import { DEFAULT_EXTRACTORS } from "../constants.js";
|
|
3
3
|
var pipeExtractors = (params, extractors = DEFAULT_EXTRACTORS) => {
|
|
4
|
-
const { hast, lang } = params;
|
|
5
|
-
const
|
|
6
|
-
|
|
4
|
+
const { hast, lang, url, owned } = params;
|
|
5
|
+
const list = Array.isArray(extractors) ? extractors : [extractors];
|
|
6
|
+
let current = hast;
|
|
7
|
+
let currentOwned = owned ?? false;
|
|
8
|
+
for (const extractor of list) {
|
|
7
9
|
if (extractor === false) {
|
|
8
|
-
|
|
10
|
+
continue;
|
|
9
11
|
}
|
|
10
|
-
if (typeof extractor
|
|
11
|
-
|
|
12
|
+
if (typeof extractor !== "function") {
|
|
13
|
+
throw new Error(`Invalid extractor: ${String(extractor)}`);
|
|
12
14
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
current = extractor({ hast: current, lang, url, owned: currentOwned });
|
|
16
|
+
currentOwned = true;
|
|
17
|
+
}
|
|
18
|
+
return current;
|
|
16
19
|
};
|
|
17
20
|
export {
|
|
18
21
|
pipeExtractors
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/extractors/presets/auto.ts
|
|
2
|
+
import { runAdapters } from "../../adapters/index.js";
|
|
3
|
+
import { takumiExtractor } from "./takumi.js";
|
|
4
|
+
var createAutoExtractor = (options = {}) => {
|
|
5
|
+
const { adapters } = options;
|
|
6
|
+
return (params) => {
|
|
7
|
+
if (adapters !== false) {
|
|
8
|
+
const extracted = runAdapters({ hast: params.hast, url: params.url, lang: params.lang }, adapters ?? void 0);
|
|
9
|
+
if (extracted) {
|
|
10
|
+
return extracted;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return takumiExtractor(params);
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
var autoExtractor = createAutoExtractor();
|
|
17
|
+
export {
|
|
18
|
+
autoExtractor,
|
|
19
|
+
createAutoExtractor
|
|
20
|
+
};
|
|
@@ -1,63 +1,51 @@
|
|
|
1
1
|
// src/extractors/presets/minimal-filter.ts
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
14
|
-
var
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (node.type !== "element") {
|
|
2
|
+
import {
|
|
3
|
+
MetricsCollector,
|
|
4
|
+
cloneHast,
|
|
5
|
+
findElement,
|
|
6
|
+
isElement,
|
|
7
|
+
isTruthyAttribute,
|
|
8
|
+
matchString,
|
|
9
|
+
pruneInPlace,
|
|
10
|
+
stringProperty
|
|
11
|
+
} from "../../utils/hast-fast.js";
|
|
12
|
+
import { REGEXPS, UNLIKELY_ROLES } from "../lib/constants.js";
|
|
13
|
+
import { stripNonContent } from "../lib/sanitize.js";
|
|
14
|
+
var MIN_RETAINED_FRACTION = 1 / 3;
|
|
15
|
+
var ALWAYS_ACCEPT_LENGTH = 5e3;
|
|
16
|
+
var isChrome = (element) => {
|
|
17
|
+
if (element.tagName === "aside" || element.tagName === "nav" || element.tagName === "dialog") {
|
|
19
18
|
return true;
|
|
20
19
|
}
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
23
|
-
return
|
|
24
|
-
}
|
|
25
|
-
if (["hidden", "aria-hidden"].some((key) => element.properties[key])) {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
if (classnames(element).some((classname) => REGEXPS.hidden.test(classname))) {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
if (element.tagName === "dialog") {
|
|
32
|
-
return false;
|
|
20
|
+
const role = stringProperty(element, "role");
|
|
21
|
+
if (role && UNLIKELY_ROLES.has(role)) {
|
|
22
|
+
return true;
|
|
33
23
|
}
|
|
34
|
-
if (
|
|
35
|
-
return
|
|
24
|
+
if (role === "dialog" && isTruthyAttribute(element, "aria-modal")) {
|
|
25
|
+
return true;
|
|
36
26
|
}
|
|
37
|
-
if (element
|
|
38
|
-
return
|
|
27
|
+
if (stringProperty(element, "rel") === "author") {
|
|
28
|
+
return true;
|
|
39
29
|
}
|
|
40
30
|
if (REGEXPS.byline.test(matchString(element))) {
|
|
41
|
-
return
|
|
42
|
-
}
|
|
43
|
-
if (element.properties.role && UNLIKELY_ROLES.includes(element.properties.role)) {
|
|
44
|
-
return false;
|
|
31
|
+
return true;
|
|
45
32
|
}
|
|
46
|
-
return
|
|
33
|
+
return false;
|
|
47
34
|
};
|
|
48
35
|
var minimalFilter = (params) => {
|
|
49
36
|
const { hast } = params;
|
|
50
|
-
const body =
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
37
|
+
const body = cloneHast(findElement(hast, (element) => element.tagName === "body") ?? hast);
|
|
38
|
+
stripNonContent(body);
|
|
39
|
+
const collector = new MetricsCollector();
|
|
40
|
+
const baseText = collector.textLength(body);
|
|
41
|
+
if (baseText === 0) {
|
|
54
42
|
return body;
|
|
55
43
|
}
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
return
|
|
44
|
+
const filtered = cloneHast(body);
|
|
45
|
+
pruneInPlace(filtered, (node) => !(isElement(node) && isChrome(node)));
|
|
46
|
+
const filteredText = new MetricsCollector().textLength(filtered);
|
|
47
|
+
const isOverExtracted = filteredText < baseText * MIN_RETAINED_FRACTION && filteredText < ALWAYS_ACCEPT_LENGTH;
|
|
48
|
+
return isOverExtracted ? body : filtered;
|
|
61
49
|
};
|
|
62
50
|
export {
|
|
63
51
|
minimalFilter
|
|
@@ -1,134 +1,103 @@
|
|
|
1
1
|
// src/extractors/presets/takumi.ts
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
14
|
-
var
|
|
15
|
-
var
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
var
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
if (element.tagName === "dialog") {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
if (element.properties.role === "dialog" && element.properties["aria-modal"]) {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
if (element.properties.rel === "author" && isStrInclude(element.properties.itemprop, "author")) {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
if (REGEXPS.byline.test(matchString(element))) {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
if (element.properties.role && UNLIKELY_ROLES.includes(element.properties.role)) {
|
|
49
|
-
return false;
|
|
2
|
+
import {
|
|
3
|
+
MetricsCollector,
|
|
4
|
+
cloneHast,
|
|
5
|
+
findElement,
|
|
6
|
+
isElement,
|
|
7
|
+
pruneInPlace,
|
|
8
|
+
stringProperty
|
|
9
|
+
} from "../../utils/hast-fast.js";
|
|
10
|
+
import { minContentLength } from "../lib/constants.js";
|
|
11
|
+
import { cleanContent, findCleanupTargets, findUnlikelyElements, stripNonContent } from "../lib/sanitize.js";
|
|
12
|
+
import { buildScoringContext, climbToArticleBoundary, collectArticleNodes, scoreCandidates } from "../lib/score.js";
|
|
13
|
+
import { truncateTrailingBoilerplate } from "../lib/terminators.js";
|
|
14
|
+
var MIN_RETAINED_FRACTION = 0.25;
|
|
15
|
+
var SEMANTIC_SELECTORS = [
|
|
16
|
+
(element) => stringProperty(element, "itemprop")?.includes("articleBody") ?? false,
|
|
17
|
+
(element) => element.tagName === "article",
|
|
18
|
+
(element) => element.tagName === "main",
|
|
19
|
+
(element) => stringProperty(element, "role") === "main"
|
|
20
|
+
];
|
|
21
|
+
var findBody = (hast) => findElement(hast, (element) => element.tagName === "body") ?? hast;
|
|
22
|
+
var semanticShortcut = (tree, collector, minLength) => {
|
|
23
|
+
for (const matches of SEMANTIC_SELECTORS) {
|
|
24
|
+
const found = [];
|
|
25
|
+
collectMatching(tree, matches, found);
|
|
26
|
+
if (found.length !== 1) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const candidate = found[0];
|
|
30
|
+
const metrics = collector.metrics(candidate);
|
|
31
|
+
if (metrics.text >= minLength && collector.linkDensity(candidate) < 0.5) {
|
|
32
|
+
return candidate;
|
|
33
|
+
}
|
|
50
34
|
}
|
|
51
|
-
return
|
|
35
|
+
return void 0;
|
|
52
36
|
};
|
|
53
|
-
var
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return false;
|
|
37
|
+
var collectMatching = (tree, matches, into) => {
|
|
38
|
+
const stack = [tree];
|
|
39
|
+
while (stack.length > 0 && into.length < 2) {
|
|
40
|
+
const node = stack.pop();
|
|
41
|
+
if (isElement(node) && matches(node)) {
|
|
42
|
+
into.push(node);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if ("children" in node) {
|
|
46
|
+
for (const child of node.children) {
|
|
47
|
+
stack.push(child);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
67
50
|
}
|
|
68
|
-
return true;
|
|
69
51
|
};
|
|
70
|
-
var
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
76
|
-
return
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
return true;
|
|
52
|
+
var asRootOf = (nodes) => ({ type: "root", children: nodes });
|
|
53
|
+
var scoredSelection = (tree, collector, lang) => {
|
|
54
|
+
const context = buildScoringContext(tree, collector, lang);
|
|
55
|
+
const candidates = scoreCandidates(tree, context);
|
|
56
|
+
const top = candidates[0];
|
|
57
|
+
if (!top) {
|
|
58
|
+
return void 0;
|
|
59
|
+
}
|
|
60
|
+
const promoted = climbToArticleBoundary(top, candidates, context);
|
|
61
|
+
const anchor = promoted === top.element ? top : { element: promoted, score: top.score };
|
|
62
|
+
const selected = collectArticleNodes(anchor, candidates, context);
|
|
63
|
+
return selected.length === 1 ? selected[0] : asRootOf(selected);
|
|
85
64
|
};
|
|
86
65
|
var takumiExtractor = (params) => {
|
|
87
|
-
const { hast, lang
|
|
88
|
-
const body =
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
66
|
+
const { hast, lang, owned } = params;
|
|
67
|
+
const body = owned ? findBody(hast) : cloneHast(findBody(hast));
|
|
68
|
+
stripNonContent(body);
|
|
69
|
+
let collector = new MetricsCollector();
|
|
70
|
+
const baseText = collector.textLength(body);
|
|
71
|
+
if (baseText === 0) {
|
|
92
72
|
return body;
|
|
93
73
|
}
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const finalFilteredTree = filter(extractedTree, (node) => {
|
|
122
|
-
if (!removeEmptyFilter(node, lang)) {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
if (!unlikelyElementFilter(node)) {
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
return true;
|
|
129
|
-
});
|
|
130
|
-
const finalTree = hastToString(finalFilteredTree).length > extractedText.length / 3 ? finalFilteredTree : extractedTree;
|
|
131
|
-
return finalTree;
|
|
74
|
+
const minLength = Math.min(minContentLength(lang), Math.max(0, baseText - 200));
|
|
75
|
+
const shortcut = semanticShortcut(body, collector, minLength);
|
|
76
|
+
const searchRoot = shortcut ?? body;
|
|
77
|
+
const searchText = collector.textLength(searchRoot);
|
|
78
|
+
const doomed = findUnlikelyElements(searchRoot);
|
|
79
|
+
const doomedText = doomed.reduce((sum, element) => sum + collector.metrics(element).text, 0);
|
|
80
|
+
if (searchText - doomedText > Math.max(minLength, searchText * MIN_RETAINED_FRACTION)) {
|
|
81
|
+
const doomedSet = new Set(doomed);
|
|
82
|
+
pruneInPlace(searchRoot, (node) => !(isElement(node) && doomedSet.has(node)));
|
|
83
|
+
collector = new MetricsCollector();
|
|
84
|
+
}
|
|
85
|
+
const scoringRoot = searchRoot;
|
|
86
|
+
const scoringRootText = collector.textLength(scoringRoot);
|
|
87
|
+
const selection = shortcut ? scoringRoot : scoredSelection(scoringRoot, collector, lang) ?? scoringRoot;
|
|
88
|
+
const selectionText = collector.textLength(selection);
|
|
89
|
+
const selectionIsSound = selectionText >= minLength || selectionText > scoringRootText * MIN_RETAINED_FRACTION;
|
|
90
|
+
const chosen = selectionIsSound ? selection : scoringRoot;
|
|
91
|
+
truncateTrailingBoilerplate(chosen);
|
|
92
|
+
const cleanupCollector = new MetricsCollector();
|
|
93
|
+
const chosenText = cleanupCollector.textLength(chosen);
|
|
94
|
+
const widgets = findCleanupTargets(chosen, cleanupCollector);
|
|
95
|
+
const widgetText = widgets.reduce((sum, element) => sum + cleanupCollector.metrics(element).text, 0);
|
|
96
|
+
const remainingText = chosenText - widgetText;
|
|
97
|
+
if (remainingText < chosenText * MIN_RETAINED_FRACTION && remainingText < minLength) {
|
|
98
|
+
return chosen;
|
|
99
|
+
}
|
|
100
|
+
return cleanContent(chosen, cleanupCollector);
|
|
132
101
|
};
|
|
133
102
|
export {
|
|
134
103
|
takumiExtractor
|
package/dist/html-to-markdown.js
CHANGED
|
@@ -1,12 +1,48 @@
|
|
|
1
1
|
// src/html-to-markdown.ts
|
|
2
|
+
import { fromHtml } from "hast-util-from-html";
|
|
2
3
|
import { htmlToMdast } from "./html-to-mdast.js";
|
|
3
4
|
import { mdastToMarkdown } from "./mdast-to-markdown.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
5
|
+
import { extractMetadata, toFrontmatter } from "./metadata/index.js";
|
|
6
|
+
import { getLangFromStr } from "./utils/hast-utils.js";
|
|
7
|
+
var htmlToMarkdownWithMetadata = (htmlOrHast, options) => {
|
|
8
|
+
const { baseUrl, frontmatter, title, formatting: toMarkdownOptions, ...toMdastOptions } = options || {};
|
|
9
|
+
const hast = typeof htmlOrHast === "string" ? fromHtml(htmlOrHast, { fragment: true }) : htmlOrHast;
|
|
10
|
+
const isOwned = typeof htmlOrHast === "string";
|
|
11
|
+
const declaredLang = typeof htmlOrHast === "string" ? getLangFromStr(htmlOrHast) : void 0;
|
|
12
|
+
const metadata = extractMetadata(hast);
|
|
13
|
+
if (declaredLang && !metadata.lang) {
|
|
14
|
+
metadata.lang = declaredLang;
|
|
15
|
+
}
|
|
16
|
+
const mdast = htmlToMdast(hast, {
|
|
17
|
+
...toMdastOptions,
|
|
18
|
+
owned: isOwned,
|
|
19
|
+
lang: toMdastOptions.lang ?? metadata.lang,
|
|
20
|
+
url: toMdastOptions.url ?? metadata.canonicalUrl
|
|
21
|
+
});
|
|
22
|
+
const body = mdastToMarkdown(mdast, { baseUrl, ...toMarkdownOptions });
|
|
23
|
+
const titled = title === false ? body : withTitle(body, metadata.title);
|
|
24
|
+
const markdown = frontmatter ? `${toFrontmatter(metadata)}${titled}` : titled;
|
|
25
|
+
return { markdown, metadata };
|
|
9
26
|
};
|
|
27
|
+
var TITLE_LOOKAHEAD = 400;
|
|
28
|
+
var withTitle = (body, title) => {
|
|
29
|
+
if (!title) {
|
|
30
|
+
return body;
|
|
31
|
+
}
|
|
32
|
+
const opening = body.slice(0, TITLE_LOOKAHEAD);
|
|
33
|
+
if (/^#\s+\S/m.test(opening)) {
|
|
34
|
+
return body;
|
|
35
|
+
}
|
|
36
|
+
if (normalizeForCompare(opening).includes(normalizeForCompare(title))) {
|
|
37
|
+
return body;
|
|
38
|
+
}
|
|
39
|
+
return `# ${title}
|
|
40
|
+
|
|
41
|
+
${body}`;
|
|
42
|
+
};
|
|
43
|
+
var normalizeForCompare = (value) => value.replace(/\s+/g, " ").trim().toLowerCase();
|
|
44
|
+
var htmlToMarkdown = (htmlOrHast, options) => htmlToMarkdownWithMetadata(htmlOrHast, options).markdown;
|
|
10
45
|
export {
|
|
11
|
-
htmlToMarkdown
|
|
46
|
+
htmlToMarkdown,
|
|
47
|
+
htmlToMarkdownWithMetadata
|
|
12
48
|
};
|