[インデックス 10064] GoのHTMLパーサーへのコメントノード解析・レンダリング機能の追加
コミット
- コミットハッシュ: 64306c9fd076c78b4e443f641561124f103854d5
- 作成者: Nigel Tao nigeltao@golang.org
- 日付: 2011年10月20日(木)11:45:30 +1100
- メッセージ: html: parse and render comment nodes.
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/64306c9fd076c78b4e443f641561124f103854d5
元コミット内容
html: parse and render comment nodes.
The first additional test case in parse_test.go is:
<!--><div>--<!-->
The second one is unrelated to the comment change, but also passes:
<p><hr></p>
R=andybalholm
CC=golang-dev
https://golang.org/cl/5299047
変更の背景
2011年当時、GoのHTMLパーサーはHTML5仕様に準拠した実装を目指していましたが、HTMLコメントの処理機能が不完全でした。当時のパーサーは、HTMLコメント(<!--...-->
)を無視するか、適切に処理できない状態でした。
HTML5仕様では、コメントノードは重要な要素として扱われ、DOMツリーの一部として保持される必要があります。特に、HTMLの動的生成や変換処理において、コメントノードの保持は重要な機能です。
このコミットは、GoのHTMLパーサーを真のHTML5準拠実装に近づけるための重要な一歩でした。Nigel Taoは、Goプロジェクトのコア開発者として、HTMLパーサーの完全性を向上させる取り組みを行っていました。
前提知識の解説
HTMLコメントとは
HTMLコメントは、HTMLドキュメント内で<!--
で始まり-->
で終わるテキストです。これらは通常、ブラウザでの表示には使用されませんが、DOM(Document Object Model)の一部として扱われます。
HTML5パーサーの挿入モード
HTML5仕様では、パーサーは文書の解析中に様々な「挿入モード」を遷移します。主要な挿入モードには以下があります:
- initialIM: 初期状態
- beforeHTMLIM:
<html>
タグ前 - beforeHeadIM:
<head>
タグ前 - inHeadIM:
<head>
タグ内 - afterHeadIM:
<head>
タグ後 - inBodyIM:
<body>
タグ内 - inTableIM:
<table>
タグ内 - inTableBodyIM:
<tbody>
等のテーブル本体内 - inRowIM:
<tr>
タグ内 - inCellIM:
<td>
や<th>
タグ内 - afterBodyIM:
<body>
タグ後 - afterAfterBodyIM: 文書終了後
各モードでは、トークンの処理方法が異なります。
GoのHTMLパーサーアーキテクチャ
GoのHTMLパーサーは以下の主要コンポーネントで構成されています:
- Tokenizer: HTML文書をトークンに分割
- Parser: トークンを受け取り、DOM木を構築
- Node: DOM木のノードを表現
- Renderer: DOM木をHTMLとして出力
技術的詳細
主要な変更点
このコミットでは、以下の3つのファイルが変更されました:
- parse.go: 84行の追加(コメントノード処理の実装)
- parse_test.go: 4行の変更(テストケースの追加)
- render.go: 12行の追加(コメントノードのレンダリング実装)
コメントトークンの処理
各挿入モードでコメントトークンが発生した場合、以下のような処理が行われます:
case CommentToken:
p.addChild(&Node{
Type: CommentNode,
Data: p.tok.Data,
})
return [現在のモード], true
この処理により、コメントデータを持つCommentNodeが作成され、現在の位置に子ノードとして追加されます。
特別な処理が必要なケース
afterBodyIMモードでは、コメントは特別な処理が必要です:
case CommentToken:
// The comment is attached to the <html> element.
if len(p.oe) < 1 || p.oe[0].Data != "html" {
panic("html: bad parser state: <html> element not found, in the after-body insertion mode")
}
p.oe[0].Add(&Node{
Type: CommentNode,
Data: p.tok.Data,
})
return afterBodyIM, true
この場合、コメントは<html>
要素に直接アタッチされます。
コアとなるコードの変更箇所
1. パーサーの初期化(parse.go:202)
p.tokenizer.ReturnComments = true
トークナイザーにコメントトークンを返すよう指示します。
2. 各挿入モードでのコメント処理
以下のような処理が各挿入モードに追加されました:
initialIM (parse.go:33-38):
case CommentToken:
p.doc.Add(&Node{
Type: CommentNode,
Data: p.tok.Data,
})
return initialIM, true
beforeHTMLIM (parse.go:48-53):
case CommentToken:
p.doc.Add(&Node{
Type: CommentNode,
Data: p.tok.Data,
})
return beforeHTMLIM, true
同様のパターンが他のすべての挿入モードに追加されています。
3. レンダリング機能の追加(render.go:245-254)
case CommentNode:
if _, err := w.WriteString("<!--"); err != nil {
return err
}
if _, err := w.WriteString(n.Data); err != nil {
return err
}
if _, err := w.WriteString("-->"); err != nil {
return err
}
return nil
4. テストケースの修正(parse_test.go:214-215)
case CommentNode:
fmt.Fprintf(w, "<!-- %s -->", n.Data)
以前はreturn os.NewError("COMMENT")
でエラーを返していましたが、適切にコメントを出力するように変更されました。
コアとなるコードの解説
トークナイザーの設定
p.tokenizer.ReturnComments = true
により、トークナイザーがコメントトークンを生成するようになります。これまでは、コメントは単に無視されていました。
状態マシンベースの処理
HTML5パーサーは状態マシンとして実装されており、各状態(挿入モード)で異なる処理を行います。コメントトークンの処理は、すべての挿入モードで一貫性を保つ必要があります。
エラーハンドリングの改善
afterBodyIMモードでは、パーサーの状態が期待通りでない場合に、より詳細なエラーメッセージが出力されるようになりました:
panic("html: bad parser state: <head> element not found, in the in-head insertion mode")
以前は単にpanic("html: bad parser state")
でした。
レンダリングの双方向性
parse.goでコメントノードを作成し、render.goでそれらを適切にHTML形式で出力することにより、解析→レンダリングの双方向性が実現されています。
テストケースの追加
コミットメッセージに示されているテストケースには、特殊な形式のコメントが含まれています:
<!--><div>--<!-->
これは、コメントの境界処理が正しく動作することを確認するためのテストケースです。