HTML を極めるって、どういうことなんですかね?
勉強で、簡単な HTML Parser を読んでみていたので、そのメモ。
間違いなどありましたら、ご指摘お願いします。
HTML パーサーとは?
HTML ドキュメント は、そのままの形式では描画されません。描画されるには、一度 HTML ドキュメントを機械が読み込める形式に変えてから、それをレンダリングエンジンというプログラムに通すことで、描画が行われます。
ここでは、「HTML ドキュメントを機械が読み込める形式に変える」部分を簡単なコードに落とした例があったので、そのソースコードを読んでみます (ファイルはこの1ファイルだけです)。また、このコードを書いた Googler は preact という 軽量版の react を書いた人でもあります。
ソースコード
では、早速読んでみましょう。
このコードでは継承などが多いので、まずは、そのデータ型の整理からしてみましょう。
まず、78行目から80行目にある
function HTMLElement() {
this.childNodes = [];
}
が、基本となる HTMLElement の型です。
81行目からある extend の部分 (コードの12行目見れば分かるように上書きしている) で、prototype 部分に、textContent (要素に含まれる文字列) や getElementsByTagName・getElementById や getAttribute・setAttribute などが加わります。
次に、Document という最初の親要素になる要素です。
function Document(){
HTMLElement.call(this);
}
これも、HTMLElement を継承し、{ nodeType : 9, nodeName: '#document'} で上書きされています。
次は、Node という HTMLElement の中身などを表すデータ型です。
function Node(){}
util.extend(Node.prototype, {
toString : function(){ return this.textContent; }
});
prototype の toString で、代わりに textContent という内容を返すようにしています。
この Node は、TextNode, CommentNode, CDATASectionNode の3つの型で継承されています。
では、データ型も分かったので、パースしている部分を読んでみましょう。
パースをする関数が始まっているのは 189行目になっていますが、216行目の部分の、
while ( (token=domParserTokenizer.exec(str)) ) {
から、実際に 解析を行なっています。
この正規表現の domParserTokenizer を読んでみると、
domParserTokenizer = /(?:<(\/?)([a-zA-Z][a-zA-Z0-9\:]*)(?:\s([^>]*?))?((?:\s*\/)?)>|(<\!\-\-)([\s\S]*?)(\-\->)|(<\!\[CDATA\[)([\s\S]*?)(\]\]>))/gm;
のようになっていて、グループ分けした配列にするとようは、["マッチした文字列", "/があるかどうか", "タグの名前", "Attribute", "/があるかどうか", "コメントアウト始まり", "コメントアウト内容", "コメントアウト終わり", "CDATA始まり", "CDATA始まり", "CDATA終わり"] にマッチします。
ここでマッチした内容が、while文 で繰り返し処理されています。
217行目からの部分は、コメントアウト・CDATA 用のもので、前に出た CommentNode か CDATASectionNode の textContent などを上書きしています。
上書きができたら、text があったら parent.ChildrenNode に追加し 、tags に タグ内容を push して、parentNode の ChildrenNode に追加します。
次は、コメントアウトではない場合かつ タグの始めに "/" が含まれない場合です (244行目)。
tag = util.extend(new HTMLElement(), {
nodeName : (token[2]+'').toLowerCase(),
attributes : util.splitAttrs(token[3]),
parentNode : parent,
documentPosition : {
openTag : {
start : domParserTokenizer.lastIndex - token[0].length,
end : domParserTokenizer.lastIndex
}
}
});
tag.className = tag.attributes['class'];
tag.id = tag.attributes.id;
tag.name = tag.attributes.name;
ここでは、nodeName や attributes などが上書きされた HTMLElement を作っています。終わった後は、コメントアウトと同じように、text があったら parent.ChildrenNode に追加し 、tags に タグ内容を push して、parentNode の ChildrenNode に追加しています。
その後は、img タグなど自分で閉じている形だったら closeTag を設定などして、それ以外だと parent を tag に 設定します。
最後に、タグが閉じるケースを書いているのが、272行目以降です。
まず 現在のタグの名前が parent の nodeName と一致した場合は、parent を tag の親要素にして tag に closeTag などを設定します。
次に 上の 自分で閉じている形でありながら、もう一度閉じてしまった場合は、tag の closeTag を上書きします。
それ以外の場合は、閉じられていないため、警告を出します。
内容は意外と読めたので、次は今度は実際に公開され利用されている HTML Parser を読んでみたいと思います。