メモ。
間違いなどありましたら、ご指摘お願いします
簡単な HTML Parser を書いてみた
HTML Parser の説明は、こちらなどを参考にしてください。
ファイルは、githubにあります。
現状は、空白が完全に除去された HTML ファイル (<p><div>hoge</div></p>
のような形式 ) で SelfClosingタグ (<img />
や <br />
のように、閉じるタグがないが /
で閉じているタグ) を認識できる形になっています (コメントアウトは実装していません) 。
では、簡単にコードを説明します。
説明 (型)
type MyHTMLElement = {
nodeName: string;
childrenNode: MyHTMLElement[];
parentNode: MyHTMLElement | undefined;
textContent: string;
attributes: MyAttribute | undefined;
DocumentPosition: MyDocumentPosition | undefined;
isSelfClosingTag: boolean;
}
type MyAttribute = {
[key in string]: string
}
type MyDocumentPosition = {
openTag: { start: number | undefined; end: number | undefined; }
closeTag: { start: number | undefined; end: number | undefined; }
}
前の記事 の型説明は分かりづらかったと思うんですが、今回は nodeName, childrenNode, parentNode などがあって、分かりやすい形になっていると思います。
説明 (実装)
実装に当たっては、ここのコードにある、正規表現を使いました。
let domParserTokenizer = /(?:<(\/?)([a-zA-Z][a-zA-Z0-9\:]*)(?:\s([^>]*?))?((?:\s*\/)?)>)/gm;
// 1 : / 2 : tag_name 3 : attribute 4 : /
let splitAttrsTokenizer = /([a-z0-9_\:\-]*)\s*?=\s*?(['"]?)(.*?)\2\s+/gim
// 1 : name 2 : "' 3 : value 4 : 2
1つ目の domParserTokenizer は <div>
などのタグを認識し、2つ目の splitAttrsTokenizer は class="hoge"
などの attribute を認識します。
パースする方法は、まず上の正規表現でマッチした部分を while文で回し、各ループで 上で書いた型に沿って HTMLElement を定義していく方法です。
その時の最初の parentNode になるのが、
let parent: MyHTMLElement = {
nodeName: "document",
childrenNode: [],
parentNode: undefined,
textContent: "",
attributes: undefined,
DocumentPosition: undefined,
isSelfClosingTag: false
}
の部分です。
では、while文の中身を見てみましょう。
まず、
if (token[1] !== "/") {
からの条件式では、通常の <div>
のように /
で区切られていないタグを HTMLElement として登録します。
そして、<img />
のように 自分で閉じた形のタグの場合は、closeTag を設定し、それ以外の場合は parent をそのタグに変更します。
if (token[4] && token[4].indexOf("/") != -1 && tag.DocumentPosition != undefined) {
tag.DocumentPosition.closeTag = tag.DocumentPosition.openTag
tag.isSelfClosingTag = true
} else {
parent = tag
}
では、次に </div>
のように /
で区切られたタグ を見てみましょう。条件式は以下のようになっています。
} else {
if (token[2].toLowerCase() == parent.nodeName) {
ここでは、tag.DocumentPosition で抜けている closeTag を追加しています。
これで、なんとか 簡単な HTML Parser を実装できました。
ヒントを多く与えてくれたコード には感謝です。