@aiquants/html-to-markdown 0.1.3 → 0.1.4
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 +1 -1
- package/dist/{core-DUdcm-c8.cjs → core-CVMYjtlI.cjs} +1 -1
- package/dist/{core-0q2nkcWH.js → core-DOzMLPij.js} +156 -21
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/main.cjs +1 -1
- package/dist/main.js +6 -2
- package/dist/src/core.d.ts +5 -2
- package/dist/src/plugins/index.d.ts +2 -0
- package/dist/src/plugins/rehype-named-anchors.d.ts +2 -0
- package/dist/src/plugins/rehype-paragraph-wrapper.d.ts +6 -0
- package/dist/tests/rehype-named-anchors.test.d.ts +1 -0
- package/package.json +4 -1
|
@@ -20384,6 +20384,123 @@ const rehypeAbsoluteLinks = (options) => {
|
|
|
20384
20384
|
});
|
|
20385
20385
|
};
|
|
20386
20386
|
};
|
|
20387
|
+
const blockLevelTags = /* @__PURE__ */ new Set([
|
|
20388
|
+
"address",
|
|
20389
|
+
"article",
|
|
20390
|
+
"aside",
|
|
20391
|
+
"blockquote",
|
|
20392
|
+
"br",
|
|
20393
|
+
"details",
|
|
20394
|
+
"dialog",
|
|
20395
|
+
"dd",
|
|
20396
|
+
"div",
|
|
20397
|
+
"dl",
|
|
20398
|
+
"dt",
|
|
20399
|
+
"fieldset",
|
|
20400
|
+
"figcaption",
|
|
20401
|
+
"figure",
|
|
20402
|
+
"footer",
|
|
20403
|
+
"form",
|
|
20404
|
+
// "h1", // # 見出し<span id="custom-id"></span> となってしまうのを回避
|
|
20405
|
+
// "h2", // ## 見出し<span id="custom-id"></span> となってしまうのを回避
|
|
20406
|
+
// "h3", // ### 見出し<span id="custom-id"></span> となってしまうのを回避
|
|
20407
|
+
// "h4", // #### 見出し<span id="custom-id"></span> となってしまうのを回避
|
|
20408
|
+
// "h5", // ##### 見出し<span id="custom-id"></span> となってしまうのを回避
|
|
20409
|
+
// "h6", // ###### 見出し<span id="custom-id"></span> となってしまうのを回避
|
|
20410
|
+
"header",
|
|
20411
|
+
"hgroup",
|
|
20412
|
+
"hr",
|
|
20413
|
+
"li",
|
|
20414
|
+
"main",
|
|
20415
|
+
"nav",
|
|
20416
|
+
"ol",
|
|
20417
|
+
"p",
|
|
20418
|
+
"pre",
|
|
20419
|
+
"section",
|
|
20420
|
+
"table",
|
|
20421
|
+
"ul"
|
|
20422
|
+
]);
|
|
20423
|
+
function hasBlockElement(node2) {
|
|
20424
|
+
let found = false;
|
|
20425
|
+
visit(node2, "element", (el) => {
|
|
20426
|
+
if (blockLevelTags.has(el.tagName)) {
|
|
20427
|
+
found = true;
|
|
20428
|
+
return EXIT;
|
|
20429
|
+
}
|
|
20430
|
+
});
|
|
20431
|
+
return found;
|
|
20432
|
+
}
|
|
20433
|
+
const slugger = new BananaSlug();
|
|
20434
|
+
const rehypeNamedAnchors = () => (tree) => {
|
|
20435
|
+
visit(tree, "element", (node2, index2, parent) => {
|
|
20436
|
+
if (parent && typeof index2 === "number" && node2.properties && node2.properties.id) {
|
|
20437
|
+
const id = String(node2.properties.id);
|
|
20438
|
+
if (id && ["h1", "h2", "h3", "h4", "h5", "h6"].includes(node2.tagName)) {
|
|
20439
|
+
const customId = slugger.slug(id);
|
|
20440
|
+
node2.children.push({
|
|
20441
|
+
type: "text",
|
|
20442
|
+
value: ` {#${customId}}`
|
|
20443
|
+
});
|
|
20444
|
+
return;
|
|
20445
|
+
}
|
|
20446
|
+
if (node2.tagName === "span" && id && Object.keys(node2.properties || {}).length === 1) {
|
|
20447
|
+
return;
|
|
20448
|
+
}
|
|
20449
|
+
const anchorNode = {
|
|
20450
|
+
type: "element",
|
|
20451
|
+
tagName: "span",
|
|
20452
|
+
properties: { id },
|
|
20453
|
+
children: []
|
|
20454
|
+
};
|
|
20455
|
+
if (hasBlockElement(node2)) {
|
|
20456
|
+
let firstInlineInfo = null;
|
|
20457
|
+
visit(node2, (child, childIndex, childParent) => {
|
|
20458
|
+
if (childParent && typeof childIndex === "number") {
|
|
20459
|
+
if (child.type === "element" && !blockLevelTags.has(child.tagName) || child.type === "text" && child.value.trim() !== "") {
|
|
20460
|
+
firstInlineInfo = {
|
|
20461
|
+
node: node2,
|
|
20462
|
+
parent: childParent,
|
|
20463
|
+
index: childIndex
|
|
20464
|
+
};
|
|
20465
|
+
return EXIT;
|
|
20466
|
+
}
|
|
20467
|
+
}
|
|
20468
|
+
});
|
|
20469
|
+
if (firstInlineInfo) {
|
|
20470
|
+
const inline = firstInlineInfo;
|
|
20471
|
+
inline.parent.children.splice(inline.index + 1, 0, anchorNode);
|
|
20472
|
+
delete node2.properties.id;
|
|
20473
|
+
}
|
|
20474
|
+
return;
|
|
20475
|
+
} else {
|
|
20476
|
+
parent.children.splice(index2 + 1, 0, anchorNode);
|
|
20477
|
+
delete node2.properties.id;
|
|
20478
|
+
return index2 + 1;
|
|
20479
|
+
}
|
|
20480
|
+
}
|
|
20481
|
+
});
|
|
20482
|
+
};
|
|
20483
|
+
const rehypeParagraphWrapper = () => (tree) => {
|
|
20484
|
+
visit(tree, "element", (node2, index2, parent) => {
|
|
20485
|
+
if (parent && typeof index2 === "number") {
|
|
20486
|
+
const nextNode = parent.children[index2 + 1];
|
|
20487
|
+
if (nextNode && nextNode.type === "element" && nextNode.tagName === "span" && nextNode.properties?.id && Object.keys(nextNode.properties).length === 1) {
|
|
20488
|
+
const parentIsBlock = !parent || parent.type === "root" || ["body", "div", "section", "article", "aside", "footer", "header", "nav", "td", "th", "li"].includes(parent.tagName);
|
|
20489
|
+
if (parentIsBlock) {
|
|
20490
|
+
const paragraph2 = {
|
|
20491
|
+
type: "element",
|
|
20492
|
+
tagName: "p",
|
|
20493
|
+
properties: {},
|
|
20494
|
+
children: [node2, nextNode]
|
|
20495
|
+
// 現在のノードと次の span ノードを子として含める
|
|
20496
|
+
};
|
|
20497
|
+
parent.children.splice(index2, 2, paragraph2);
|
|
20498
|
+
return index2 + 1;
|
|
20499
|
+
}
|
|
20500
|
+
}
|
|
20501
|
+
}
|
|
20502
|
+
});
|
|
20503
|
+
};
|
|
20387
20504
|
const rehypeSanitizeHtml = () => {
|
|
20388
20505
|
return (tree) => {
|
|
20389
20506
|
visit(tree, (node2, index2, parent) => {
|
|
@@ -20477,7 +20594,7 @@ const rehypeWikipediaFootnotes = () => {
|
|
|
20477
20594
|
};
|
|
20478
20595
|
};
|
|
20479
20596
|
const handleTableCell = (_state, node2, _parent) => {
|
|
20480
|
-
const allowedTags = ["br", "hr", "h1", "h2", "h3", "h4", "h5", "h6", "strong", "em", "b", "i", "u", "s", "strike", "del", "ins", "code", "ul", "ol", "li", "a", "img", "blockquote", "pre", "table", "thead", "tbody", "tfoot", "tr", "th", "td"];
|
|
20597
|
+
const allowedTags = ["br", "hr", "h1", "h2", "h3", "h4", "h5", "h6", "strong", "em", "b", "i", "u", "s", "strike", "del", "ins", "code", "ul", "ol", "li", "a", "img", "blockquote", "pre", "table", "thead", "tbody", "tfoot", "tr", "th", "td", "style"];
|
|
20481
20598
|
const allowedAttributes = ["style", "class", "id", "colspan", "rowspan", "width", "height", "align", "href", "src", "title", "alt"];
|
|
20482
20599
|
visit(node2, "element", (elementNode, index2, parent) => {
|
|
20483
20600
|
if (!parent || typeof index2 !== "number") {
|
|
@@ -20535,22 +20652,37 @@ const handleTableCell = (_state, node2, _parent) => {
|
|
|
20535
20652
|
};
|
|
20536
20653
|
const convertHtmlToMarkdown = async (html2, baseUrl, locale) => {
|
|
20537
20654
|
console.log(getMessage(locale, "convert_start"));
|
|
20538
|
-
const
|
|
20539
|
-
const file = await unified().use(rehypeParse, { fragment: true }).use(rehypeRaw).use(rehypeSanitizeHtml).use(rehypeSlug).use(rehypeAbsoluteLinks, { baseUrl }).use(
|
|
20655
|
+
const slugger2 = new BananaSlug();
|
|
20656
|
+
const file = await unified().use(rehypeParse, { fragment: true }).use(rehypeRaw).use(rehypeSanitizeHtml).use(rehypeWikipediaFootnotes).use(rehypeNamedAnchors).use(rehypeSlug).use(rehypeParagraphWrapper).use(rehypeAbsoluteLinks, { baseUrl }).use(rehypeRemark, {
|
|
20540
20657
|
handlers: {
|
|
20658
|
+
span(state, node2) {
|
|
20659
|
+
const id = node2.properties?.id;
|
|
20660
|
+
if (id && Object.keys(node2.properties || {}).length === 1) {
|
|
20661
|
+
const children = state.all(node2);
|
|
20662
|
+
if (children.length === 0) {
|
|
20663
|
+
return { type: "html", value: `<span id="${String(id)}"></span>` };
|
|
20664
|
+
}
|
|
20665
|
+
return [
|
|
20666
|
+
{ type: "html", value: `<span id="${String(id)}">` },
|
|
20667
|
+
...children,
|
|
20668
|
+
{ type: "html", value: "</span>" }
|
|
20669
|
+
];
|
|
20670
|
+
}
|
|
20671
|
+
return state.all(node2);
|
|
20672
|
+
},
|
|
20541
20673
|
a(state, node2) {
|
|
20542
20674
|
const hasComplexChildren = node2.children.some((child) => child.type === "element" && ["dl", "ul", "ol", "table", "div"].includes(child.tagName));
|
|
20543
20675
|
if (hasComplexChildren && node2.properties?.href) {
|
|
20544
20676
|
const linkText2 = String(node2.properties.ariaLabel || "");
|
|
20545
20677
|
if (linkText2) {
|
|
20546
|
-
const
|
|
20547
|
-
const
|
|
20678
|
+
const url = String(node2.properties.href);
|
|
20679
|
+
const linkNode2 = {
|
|
20548
20680
|
type: "link",
|
|
20549
|
-
url
|
|
20681
|
+
url,
|
|
20550
20682
|
children: [{ type: "text", value: linkText2 }]
|
|
20551
20683
|
};
|
|
20552
20684
|
const otherContent = state.all(node2);
|
|
20553
|
-
return [
|
|
20685
|
+
return [linkNode2, { type: "text", value: "\n" }, ...otherContent];
|
|
20554
20686
|
}
|
|
20555
20687
|
}
|
|
20556
20688
|
const extractTextFromHast = (n) => {
|
|
@@ -20584,24 +20716,27 @@ const convertHtmlToMarkdown = async (html2, baseUrl, locale) => {
|
|
|
20584
20716
|
return false;
|
|
20585
20717
|
});
|
|
20586
20718
|
};
|
|
20719
|
+
let linkNode;
|
|
20587
20720
|
if (containsImage(node2)) {
|
|
20588
|
-
|
|
20721
|
+
linkNode = {
|
|
20589
20722
|
type: "link",
|
|
20590
20723
|
url: String(node2.properties?.href || ""),
|
|
20591
20724
|
title: node2.properties?.title ? String(node2.properties.title) : null,
|
|
20592
20725
|
children: state.all(node2)
|
|
20593
20726
|
};
|
|
20727
|
+
} else {
|
|
20728
|
+
let url = String(node2.properties?.href || "");
|
|
20729
|
+
if (url.startsWith("#")) {
|
|
20730
|
+
url = `#${slugger2.slug(url.substring(1))}`;
|
|
20731
|
+
}
|
|
20732
|
+
linkNode = {
|
|
20733
|
+
type: "link",
|
|
20734
|
+
url,
|
|
20735
|
+
title: node2.properties?.title ? String(node2.properties.title) : null,
|
|
20736
|
+
children: [{ type: "text", value: linkText }]
|
|
20737
|
+
};
|
|
20594
20738
|
}
|
|
20595
|
-
|
|
20596
|
-
if (url.startsWith("#")) {
|
|
20597
|
-
url = `#${slugger.slug(url.substring(1))}`;
|
|
20598
|
-
}
|
|
20599
|
-
return {
|
|
20600
|
-
type: "link",
|
|
20601
|
-
url,
|
|
20602
|
-
title: node2.properties?.title ? String(node2.properties.title) : null,
|
|
20603
|
-
children: [{ type: "text", value: linkText }]
|
|
20604
|
-
};
|
|
20739
|
+
return linkNode;
|
|
20605
20740
|
},
|
|
20606
20741
|
td(state, node2, parent) {
|
|
20607
20742
|
return handleTableCell(state, node2);
|
|
@@ -20632,13 +20767,13 @@ const htmlToMarkdown = async (url, options = {}) => {
|
|
|
20632
20767
|
const htmlContent = await getHtmlWithPlaywright(url, locale);
|
|
20633
20768
|
if (!htmlContent) {
|
|
20634
20769
|
console.error(`No HTML content found for URL: ${url}`);
|
|
20635
|
-
return "";
|
|
20770
|
+
return { html: "", markdown: "" };
|
|
20636
20771
|
}
|
|
20637
20772
|
const markdown = await convertHtmlToMarkdown(htmlContent, url, locale);
|
|
20638
|
-
return markdown;
|
|
20773
|
+
return { html: htmlContent, markdown };
|
|
20639
20774
|
} catch (error) {
|
|
20640
20775
|
console.error(`An error occurred during the conversion process for URL: ${url}`, error);
|
|
20641
|
-
return "";
|
|
20776
|
+
return { html: "", markdown: "" };
|
|
20642
20777
|
}
|
|
20643
20778
|
};
|
|
20644
20779
|
export {
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=new(require("happy-dom").Window);Object.assign(global,{document:e.document,window:e,self:e}),Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("./core-
|
|
1
|
+
const e=new(require("happy-dom").Window);Object.assign(global,{document:e.document,window:e,self:e}),Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("./core-CVMYjtlI.cjs");exports.getMessage=o.getMessage,exports.htmlToMarkdown=o.htmlToMarkdown;
|
package/dist/index.js
CHANGED
package/dist/main.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
Object.create,Object.defineProperty,Object.getOwnPropertyDescriptor,Object.getOwnPropertyNames,Object.getPrototypeOf,Object.prototype.hasOwnProperty;const e=new(require("happy-dom").Window);Object.assign(global,{document:e.document,window:e,self:e});const t=require("./core-
|
|
2
|
+
Object.create,Object.defineProperty,Object.getOwnPropertyDescriptor,Object.getOwnPropertyNames,Object.getPrototypeOf,Object.prototype.hasOwnProperty;const e=new(require("happy-dom").Window);Object.assign(global,{document:e.document,window:e,self:e});const t=require("./core-CVMYjtlI.cjs");var r="undefined"!=typeof document?document.currentScript:null;(async()=>{(("undefined"==typeof document?require("url").pathToFileURL(__filename).href:r&&"SCRIPT"===r.tagName.toUpperCase()&&r.src||new URL("main.cjs",document.baseURI).href).endsWith(process.argv[1])||process.argv[1]&&process.argv[1].includes("vite-node"))&&(async()=>{const e=(await import("yargs-parser")).default,r=await import("node:fs"),o=await import("node:path"),c=e(process.argv.slice(2),{string:["locale","output"],alias:{output:["o"]},default:{locale:"en-US"}}),s=c._[0],n=c.locale,a=c.output;s||process.exit(1);try{new URL(s)}catch(i){process.exit(1)}try{const{markdown:e,html:c}=await t.htmlToMarkdown(s,{locale:n});let i;if(a){i=o.resolve(process.cwd(),a);const e=o.dirname(i);r.existsSync(e)||r.mkdirSync(e,{recursive:!0})}else{const e=`${s.replace(/https?:\/\//,"").replace(/[^a-zA-Z0-9]/g,"_")}_${Date.now()}.md`,t=o.join(process.cwd(),".outputs","raw");r.existsSync(t)||r.mkdirSync(t,{recursive:!0}),i=o.join(t,e)}r.writeFileSync(i,e);const p=o.parse(i),l=o.join(p.dir,`${p.name}.html`);r.writeFileSync(l,c)}catch(i){process.exit(1)}})()})();
|
package/dist/main.js
CHANGED
|
@@ -6,7 +6,7 @@ Object.assign(global, {
|
|
|
6
6
|
window,
|
|
7
7
|
self: window
|
|
8
8
|
});
|
|
9
|
-
import { g as getMessage, h as htmlToMarkdown } from "./core-
|
|
9
|
+
import { g as getMessage, h as htmlToMarkdown } from "./core-DOzMLPij.js";
|
|
10
10
|
const runCli = async () => {
|
|
11
11
|
const yargsParser = (await import("yargs-parser")).default;
|
|
12
12
|
const fs = await import("node:fs");
|
|
@@ -44,7 +44,7 @@ ${getMessage(locale, "cli_start")}`);
|
|
|
44
44
|
if (outputFile) {
|
|
45
45
|
console.info(getMessage(locale, "cli_output", { path: outputFile }));
|
|
46
46
|
}
|
|
47
|
-
const markdown = await htmlToMarkdown(targetUrl, { locale });
|
|
47
|
+
const { markdown, html } = await htmlToMarkdown(targetUrl, { locale });
|
|
48
48
|
let filePath;
|
|
49
49
|
if (outputFile) {
|
|
50
50
|
filePath = path.resolve(process.cwd(), outputFile);
|
|
@@ -63,9 +63,13 @@ ${getMessage(locale, "cli_start")}`);
|
|
|
63
63
|
filePath = path.join(outputDir, fileName);
|
|
64
64
|
}
|
|
65
65
|
fs.writeFileSync(filePath, markdown);
|
|
66
|
+
const parsedPath = path.parse(filePath);
|
|
67
|
+
const htmlFilePath = path.join(parsedPath.dir, `${parsedPath.name}.html`);
|
|
68
|
+
fs.writeFileSync(htmlFilePath, html);
|
|
66
69
|
console.info(`
|
|
67
70
|
${getMessage(locale, "success_save")}`);
|
|
68
71
|
console.info(getMessage(locale, "success_path", { path: filePath }));
|
|
72
|
+
console.info(getMessage(locale, "success_path", { path: htmlFilePath }));
|
|
69
73
|
} catch (error) {
|
|
70
74
|
console.error(`
|
|
71
75
|
${getMessage(locale, "error_app")}`, error);
|
package/dist/src/core.d.ts
CHANGED
|
@@ -5,6 +5,9 @@ import { HtmlToMarkdownOptions } from './types.js';
|
|
|
5
5
|
*
|
|
6
6
|
* @param url The URL of the web page to convert. / 変換対象のWebページのURL。
|
|
7
7
|
* @param options Options for the conversion process. / 変換プロセスのオプション。
|
|
8
|
-
* @returns A promise that resolves to the Markdown
|
|
8
|
+
* @returns A promise that resolves to an object containing the HTML and Markdown strings. / HTMLとMarkdown文字列を含むオブジェクトを解決するPromise。
|
|
9
9
|
*/
|
|
10
|
-
export declare const htmlToMarkdown: (url: string, options?: HtmlToMarkdownOptions) => Promise<
|
|
10
|
+
export declare const htmlToMarkdown: (url: string, options?: HtmlToMarkdownOptions) => Promise<{
|
|
11
|
+
html: string;
|
|
12
|
+
markdown: string;
|
|
13
|
+
}>;
|
|
@@ -3,5 +3,7 @@
|
|
|
3
3
|
* Rehype プラグインのインデックス
|
|
4
4
|
*/
|
|
5
5
|
export { rehypeAbsoluteLinks } from './rehype-absolute-links.js';
|
|
6
|
+
export { rehypeNamedAnchors } from './rehype-named-anchors.js';
|
|
7
|
+
export { rehypeParagraphWrapper } from './rehype-paragraph-wrapper.js';
|
|
6
8
|
export { rehypeSanitizeHtml } from './rehype-sanitize-html.js';
|
|
7
9
|
export { rehypeWikipediaFootnotes } from './rehype-wikipedia-footnotes.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiquants/html-to-markdown",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "HTML to Markdown converter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,12 +20,15 @@
|
|
|
20
20
|
"LICENSE"
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
|
+
"dev": "NODE_OPTIONS='--enable-source-maps' vite-node --inspect=9229 --watch main.ts --",
|
|
24
|
+
"debug": "NODE_OPTIONS='--enable-source-maps' vite-node --inspect-brk=9229 main.ts --",
|
|
23
25
|
"start": "vite-node main.ts",
|
|
24
26
|
"build": "vite build",
|
|
25
27
|
"lint": "biome lint app/",
|
|
26
28
|
"format": "biome format --write",
|
|
27
29
|
"check": "biome check --fix src/",
|
|
28
30
|
"test": "vitest run",
|
|
31
|
+
"test1": "pnpm -F @aiquants/html-to-markdown test --test-name-pattern 'rehypeNamedAnchors plugin'",
|
|
29
32
|
"coverage": "vitest run --coverage"
|
|
30
33
|
},
|
|
31
34
|
"keywords": [
|