Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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準拠のトークナイザーとパーサーを実装するための基盤が整備されていました。

主な背景:

  1. コールチェーンの複雑化問題: 以前の実装では、NextメソッドがnextTextまたはnextTagを呼び出し、さらにnextTagnextTextを呼び出すという複雑な依存関係が存在していました
  2. 責任の分散: 終了タグ(</a)やコメント(<!)の検出責任が複数の場所に分散しており、保守性が低下していました
  3. "</>"の正しい解析: 不正な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)。このパッケージは:

  1. WHATWG HTML5仕様準拠: HTML5の標準仕様に従った実装
  2. 2段階処理: トークナイゼーション(字句解析)とパーシング(構文解析)
  3. ストリーミング対応: io.Readerからの入力に対応

トークナイザーの動作原理

for {
    tt := z.Next()
    if tt == html.ErrorToken {
        break
    }
    // トークンの処理
}

HTMLの終了タグとコメントの検出

  • 終了タグ: </tagname> の形式で、開始タグとペアになる
  • コメント: <!-- で開始し --> で終了
  • 不正なHTML: </>のような構文的に正しくないタグ

技術的詳細

リファクタリング前の問題点

  1. 複雑なコールチェーン:

    Next() → nextText() or nextTag()
          → nextTag() → nextText() (循環的依存)
    
  2. 責任の重複:

    • nextTextが終了タグとコメントを検出
    • nextTagも同様の検出を実行
    • 同じロジックが複数箇所に散在
  3. 保守性の低下:

    • バグの修正が複数箇所に必要
    • 新機能追加時の影響範囲が予測困難

リファクタリング後の改善点

  1. 単一責任の原則: 終了タグとコメントの検出を一箇所に集約
  2. シンプルなコールチェーン: 直線的な呼び出し関係に変更
  3. "</>"の正しい解析: 不正なHTMLタグの適切な処理を実装

コアとなるコードの変更箇所

主要な変更ファイル

src/pkg/html/token.go (273行の変更):

  • Nextメソッドの実装変更
  • nextTextnextTagの関係性整理
  • 終了タグとコメント検出ロジックの統合

src/pkg/html/token_test.go (41行の変更):

  • 新しい実装に対応したテストケース
  • </>の解析テスト追加
  • リファクタリング後の動作確認

コアとなるコードの解説

リファクタリングのポイント

  1. 責任の明確化:

    • 終了タグ(</a)の検出
    • コメント(<!)の検出
    • これらの処理を単一の場所で実行
  2. コールチェーンの簡素化:

    // 変更前: 複雑な依存関係
    Next() → nextText() ← nextTag()
    
    // 変更後: 明確な階層構造
    Next() → 統一された検出ロジック → nextText() or nextTag()
    
  3. "</>"の処理:

    • 技術的には不正なHTMLタグ
    • しかし実際のWebページでは出現する可能性
    • 適切にエラー処理またはスキップ処理を実装

実装の詳細

リファクタリングにより、以下の処理が改善されました:

  • 統一された文字検査: <で始まるトークンの種類判定
  • 効率的な文字列処理: 重複した文字列操作の削減
  • エラー処理の一元化: 不正なHTMLに対する一貫した処理

関連リンク

参考にした情報源リンク