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();