HTMLの青空文庫パーサーをいじっていたときのメモ。
wholeTextを使っているのでタグ以外の改行も含まれていて、改行が多くなる。ただ、サイトにより改行方法が異なるため、改行が増えたり減ったりする。 なのでtexnode内の改行を除去すれば改行が増えたり減ったりしなくなるのでは。
<div>
<p>テストテキスト1</p>
<p><b>太字テキスト</b></p>
<div>
<p>テスト<sup>テキ</sup>スト2</p>
</div>
<ruby>漢字<rt>かんじ</rt></ruby>
<img src="image.png" />
<hr />
</div>
テストテキスト1
[#ここから太字]太字テキスト[#ここで太字終わり]
テスト[#上付き小文字]テキ[#上付き小文字終わり]スト2
|漢字《かんじ》
[#挿絵(images/image.png)入る]
[#区切り線]
wholeTextとtextContentでは改行が増えるのと、タグ外のスペースや改行がtextnodeとして扱われる。
[#ここから太字][#ここで太字終わり]
[#上付き小文字][#上付き小文字終わり]
|漢字《かんじ》[#挿絵(images/image.png)入る] [#区切り線] innerTextにするとpタグ内の文字が消えた。
function printText(bw, text) {
bw.push(text.replace(/\r?\n/g, ''));
}
HTMLを青空文庫に変換するJS
// Utility functions
function printText(bw, text) {
bw.push(text);
}
// ルビを青空ルビにして出力
function printRuby(bw, ruby) {
// rubyの子ノードをループ
ruby.childNodes.forEach(function(childNode) {
if (childNode.nodeType === Node.ELEMENT_NODE) {
let element = childNode;
switch (element.nodeName.toLowerCase()) {
case 'rp':
// 'rp'タグは無視
break;
case 'rt':
bw.push('《');
printText(bw, element.textContent);
bw.push('》');
break;
case 'rb':
bw.push('|');
printText(bw, element.textContent);
break;
default:
console.log("ruby error");
}
} else if (childNode.nodeType === Node.TEXT_NODE) {
let textNode = childNode;
bw.push('|');
printText(bw, textNode.textContent);
}
});
}
/*
function printImage(bw, elem) {
bw.push(`[#画像注記:${elem.getAttribute('src')}]`);
}
*/
// 画像をキャッシュして相対パスの注記にする
function printImage(bw, img, imageOutFile = null) {
let src = img.getAttribute("src");
if (!src || src.length === 0) return;
let imagePath = src;
if (bw) {
bw.push("[#挿絵(");
bw.push("images/" + imagePath);
bw.push(")入る]\n");
}
}
// 以下は、必要に応じて定義される補助関数
// URLをファイル名にエスケープする関数
function escapeUrlToFile(url) {
// 必要に応じてURLを安全なファイル名に変換するロジックを実装
return url.replace(/[\/:*?"<>|]/g, "_"); // 簡単な例: 一部の特殊文字をアンダースコアに置換
}
// ファイルキャッシュ処理を行う関数
function cacheFile(src, file, referer) {
// 画像のダウンロード処理を実装
// 例えば、fetch APIを使用して画像をダウンロードし、fileに保存する
}
function isBlockNode(node) {
if (node.nodeType === Node.ELEMENT_NODE) {
const tagName = node.tagName.toLowerCase();
return ["br", "div", "p", "hr", "table"].includes(tagName);
}
return false;
}
// Main class
class NodePrinter {
constructor() {
this.startElement = null;
this.endElement = null;
this.noHr = false;
}
// ノードを出力 子ノード内のテキストも出力
printNode(bw, parent, noHr = false) {
this.printNodeWithRange(bw, parent, null, null, noHr);
}
// ノードを出力 子ノード内のテキストも出力
printNodeWithRange(bw, parent, start = null, end = null, noHr = false) {
this.startElement = start;
this.endElement = end;
this.noHr = noHr;
this._printNode(bw, parent);
}
// ノードを出力 再帰用
_printNode(bw, parent) {
const childNodes = Array.from(parent.childNodes);
for (let node of childNodes) {
if (this.startElement) {
if (node === this.startElement) {
this.startElement = null;
continue;
}
if (node.nodeType === Node.ELEMENT_NODE) {
this._printNode(bw, node);
}
continue;
}
if (this.endElement && node === this.endElement) {
return;
}
if (node.nodeType === Node.TEXT_NODE) {
printText(bw, node.wholeText);
} else if (node.nodeType === Node.ELEMENT_NODE) {
const elem = node;
switch (elem.tagName.toLowerCase()) {
case 'br':
if (elem.nextSibling) bw.push('\n');
break;
case 'div':
if (elem.previousSibling && !isBlockNode(elem.previousSibling)) bw.push('\n');
this._printNode(bw, node); // 子を出力
if (elem.nextSibling) bw.push('\n');
break;
case 'p':
this._printNode(bw, node); // 子を出力
if (elem.nextSibling) bw.push('\n');
break;
case 'ruby':
printRuby(bw, elem);
break;
case 'img':
printImage(bw, elem);
break;
case 'hr':
if (!this.noHr) {
bw.push("[#区切り線]\n");
}
break;
case 'b':
bw.push("[#ここから太字]");
this._printNode(bw, node); // 子を出力
bw.push("[#ここで太字終わり]");
break;
case 'em':
bw.push("[#丸傍点]");
this._printNode(bw, node); // 子を出力
bw.push("[#丸傍点終わり]");
break;
case 'sup':
bw.push("[#上付き小文字]");
this._printNode(bw, node); // 子を出力
bw.push("[#上付き小文字終わり]");
break;
case 'sub':
bw.push("[#下付き小文字]");
this._printNode(bw, node); // 子を出力
bw.push("[#下付き小文字終わり]");
break;
case 'strike':
case 's':
bw.push("[#取消線]");
this._printNode(bw, node); // 子を出力
bw.push("[#取消線終わり]");
break;
case 'tr':
this._printNode(bw, node); // 子を出力
bw.push('\n');
break;
default:
this._printNode(bw, node); // 子を出力
}
} else {
console.log(node.nodeType); // デバッグ用
}
}
}
}
// NodePrinter.jsファイルの内容をインポートする必要はありません。
// このテストコードがNodePrinter.jsと同じファイルにあると仮定します。
function testNodePrinter() {
// HTML文書を手動で作成
const dom = document.createElement('div');
dom.innerHTML = `
<div>
<p>テストテキスト1</p>
<p><b>太字テキスト</b></p>
<div>
<p>テスト<sup>テキ</sup>スト2</p>
</div>
<ruby>漢字<rt>かんじ</rt></ruby>
<img src="image.png" />
<hr />
</div>
`;
const printer = new NodePrinter();
const bw = [];
// ルートノードを取得してprintNodeメソッドを呼び出し
const rootNode = dom.querySelector('div');
printer.printNode(bw, rootNode);
// 出力されたテキストを一つの文字列に結合
const output = bw.join('');
// 結果をチェック
console.log(bw.join(''));
}
// テストを実行
testNodePrinter();