[Tool] 開発備忘録 WYSIWIGエディタ作る話。#04 HTMLインデント

2025/03/14

Tool 開発

t f B! P L
eyecatch WYSIWIGエディタを使っていると、どうしてもHTMLで編集したくなる場合があります。 ノーコード命という人はおそらく見る機会がないかもしれませんが、HTMLは絶対です。 Webサイトを構築する事において、HTMLを知らないと話になりません。 WYSIWIGエディタで作られたWebコンテンツは、iframe内のinnerHTMLで、内容のHTMLソースコードを取得する事ができます。 だけど、そのHTMLは、改行やインデントがまるでされていない、単行HTMLなので、これはさすがに慣れていても読みにくさ爆発状態です。 というわけで、こうしたHTMLを整形して、PrettyPrint(HTML整形)してみたいと思います。

デモ

ソースコード

<textarea id="html_source" style="width:100%;height:150px;border:1px solid black;padding:10px;resize:none;"></textarea> <button name="pretty_print">Pretty Print(整形)</button> <script type="module"> const button = document.querySelector(`button[name="pretty_print"]`) button.addEventListener("click",((e)=>{ new DomIndent() })) export class DomIndent{ constructor(){ const textarea = document.getElementById("html_source") const html = textarea.value const parser = new DOMParser() const doc = parser.parseFromString(html, "text/html") // HTML をパース this.voidElements = new Set([ "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr" ]) textarea.value = this.format(doc.body).trim() } voidElements = null format(node, indent = 0){ let result = "" const indentStr = " ".repeat(indent) // インデント用スペース node.childNodes.forEach(child => { if (child.nodeType === Node.TEXT_NODE) { // テキストノード(空白のみは無視) const text = child.textContent.replace(/\u00A0/g, "&nbsp;").trim(); // `&nbsp;` を保持 if (text) result += `${indentStr}${this.escapeHTML(text)}\n`; } else if (child.nodeType === Node.ELEMENT_NODE) { // 開始タグの構築 const tagName = child.tagName.toLowerCase(); const attributes = Array.from(child.attributes) .map(attr => `${attr.name}="${this.escapeHTML(attr.value)}"`) // 属性内の `&` もエスケープ .join(" "); // void 要素の場合、自己閉じタグとして処理 if (this.voidElements.has(tagName)) { const selfClosingTag = attributes ? `<${tagName} ${attributes} />` : `<${tagName} />`; result += `${indentStr}${selfClosingTag}\n`; } else { // 通常の要素 (開始タグ + 子要素 + 閉じタグ) const openTag = attributes ? `<${tagName} ${attributes}>` : `<${tagName}>`; result += `${indentStr}${openTag}\n`; result += this.format(child, indent + 1); result += `${indentStr}</${tagName}>\n`; } } }); return result } escapeHTML(text) { // HTML エンティティをエスケープ return text.replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;") .replace(/'/g, "&#39;") .replace(/&amp;nbsp;/g, "&nbsp;"); } } </script>

解説

DomIndentというクラスを作っておきました。 ただ、WYSIWIGフレームワークで使用するため、変換する対象のtextareaは、内部に埋め込み記述にしています。 クラスの送値で、textareaを任意していできる様にすると、汎用出来に使えるコードになるんですけどね。

DOMParser

const parser = new DOMParser() この命令は、DOMのパース構造を処理するもので、内部のタグをそれぞれnodeとして処理したかったので、HTMLテキストをそのままいじる方式じゃないこの方法を使ってみました。

format()

ここで整形処理を行っているんですが、nodeというのは、DOM構造の階層1つずつを処理する仕様になっていて、 nodeの中にさらにnodeが入っている場合を考慮して、このような再帰処理の構造をとっています。 再帰処理って理解して読まないと、意味がわかりにくくなるので、読み慣れていないと難しく感じるかもしれません。

escapeHTML()

HTMLには、特殊文字列と呼ばれる、記号の表記があります。 ※HTMLの特殊文字については、コチラを参照してください。 https://blog.myntinc.com/2015/12/html.html これらを適正に処理しておかないと、記号が表示されなかったり、表示がおかしくなったりするので、主要な特殊文字を置換する処理関数です。

this.voidElements

HTMLには、開始タグと終了(閉じ)タグがあるのが一般的ですが、brやimgなどのように、閉じタグが存在しないものもあります。 それらをちゃんとインデント処理する対象から外すための、定義変数として使っています。

あとがき

肝心の整形処理に関しては、ノード毎に処理して、それをタグ文字として復帰させる処理を愚直に書いています。 とりあえず、ある程度さわってみて、大体のバグは潰している状態なんですが、まだまだ不具合が発症するかもしれません。 引き続き、改修していきながら使うのが重要ですね。 ちなみに、このブログもGoogleBloggerのHTMLエディタのみで作成しているんですよ。 やっぱ、慣れが必要だということも、考えさせられるところですね。

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ