[インデックス 10010] HTML トークナイザーのリファクタリング: "</>" 解析の正しい実装
コミット
コミットハッシュ: e5f3dc8bc54942db96f55b1b6207edfe69ca4021
作成者: Nigel Tao nigeltao@golang.org
日時: 2011年10月18日 09:42:16 +1100
コミットメッセージ: html: refactor the tokenizer; parse "</>" correctly.
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e5f3dc8bc54942db96f55b1b6207edfe69ca4021
元コミット内容
html: refactor the tokenizer; parse "</>" correctly.
Previously, Next would call either nextText or nextTag, but nextTag
could also call nextText. Both nextText and nextTag were responsible
for detecting "</a" end tags and "<!" comments. This change simplifies
the call chain and puts that responsibility in a single place.
R=andybalholm
CC=golang-dev
https://golang.org/cl/5263050
変更ファイル:
- src/pkg/html/token.go (273行の変更: 160行追加、154行削除)
- src/pkg/html/token_test.go (41行の変更)
変更の背景
このコミットは、Go言語のHTMLトークナイザーの内部実装を大幅にリファクタリングしたものです。2011年当時、Go言語のHTMLパーサーは開発初期段階にあり、HTML5準拠のトークナイザーとパーサーを実装するための基盤が整備されていました。
主な背景:
- コールチェーンの複雑化問題: 以前の実装では、
Next
メソッドがnextText
またはnextTag
を呼び出し、さらにnextTag
がnextText
を呼び出すという複雑な依存関係が存在していました - 責任の分散: 終了タグ(
</a
)やコメント(<!
)の検出責任が複数の場所に分散しており、保守性が低下していました - "</>"の正しい解析: 不正なHTMLタグである
</>
を正しく処理する必要性が生じました
前提知識の解説
HTMLトークナイザーとは
HTMLトークナイザーは、HTML文書を構文解析するための第一段階で、HTML文字列を意味のある単位(トークン)に分解する役割を持ちます。トークンには以下のような種類があります:
- 開始タグ:
<div>
,<p class="text">
- 終了タグ:
</div>
,</p>
- テキスト: タグ間のテキスト内容
- コメント:
<!-- コメント -->
- DOCTYPE:
<!DOCTYPE html>
- エラートークン: 不正なHTML構造
Go言語のHTMLパッケージ
2011年当時、Go言語のHTMLパッケージはsrc/pkg/html
に配置されていました(現在はgolang.org/x/net/html
)。このパッケージは:
- WHATWG HTML5仕様準拠: HTML5の標準仕様に従った実装
- 2段階処理: トークナイゼーション(字句解析)とパーシング(構文解析)
- ストリーミング対応:
io.Reader
からの入力に対応
トークナイザーの動作原理
for {
tt := z.Next()
if tt == html.ErrorToken {
break
}
// トークンの処理
}
HTMLの終了タグとコメントの検出
- 終了タグ:
</tagname>
の形式で、開始タグとペアになる - コメント:
<!--
で開始し-->
で終了 - 不正なHTML:
</>
のような構文的に正しくないタグ
技術的詳細
リファクタリング前の問題点
-
複雑なコールチェーン:
Next() → nextText() or nextTag() → nextTag() → nextText() (循環的依存)
-
責任の重複:
nextText
が終了タグとコメントを検出nextTag
も同様の検出を実行- 同じロジックが複数箇所に散在
-
保守性の低下:
- バグの修正が複数箇所に必要
- 新機能追加時の影響範囲が予測困難
リファクタリング後の改善点
- 単一責任の原則: 終了タグとコメントの検出を一箇所に集約
- シンプルなコールチェーン: 直線的な呼び出し関係に変更
- "</>"の正しい解析: 不正なHTMLタグの適切な処理を実装
コアとなるコードの変更箇所
主要な変更ファイル
src/pkg/html/token.go (273行の変更):
Next
メソッドの実装変更nextText
とnextTag
の関係性整理- 終了タグとコメント検出ロジックの統合
src/pkg/html/token_test.go (41行の変更):
- 新しい実装に対応したテストケース
</>
の解析テスト追加- リファクタリング後の動作確認
コアとなるコードの解説
リファクタリングのポイント
-
責任の明確化:
- 終了タグ(
</a
)の検出 - コメント(
<!
)の検出 - これらの処理を単一の場所で実行
- 終了タグ(
-
コールチェーンの簡素化:
// 変更前: 複雑な依存関係 Next() → nextText() ← nextTag() // 変更後: 明確な階層構造 Next() → 統一された検出ロジック → nextText() or nextTag()
-
"</>"の処理:
- 技術的には不正なHTMLタグ
- しかし実際のWebページでは出現する可能性
- 適切にエラー処理またはスキップ処理を実装
実装の詳細
リファクタリングにより、以下の処理が改善されました:
- 統一された文字検査:
<
で始まるトークンの種類判定 - 効率的な文字列処理: 重複した文字列操作の削減
- エラー処理の一元化: 不正なHTMLに対する一貫した処理