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

[インデックス 10081] ファイルの概要

このコミットは、Go言語の html パッケージにおける Tokenizer.ReturnComments オプションの削除に関するものです。このオプションは、HTMLコメントトークンをスキップするかどうかを制御していましたが、WebKit HTMLテストスイートとの互換性を確保するためにコメントのパースが必要となったため、その存在意義がなくなりました。

コミット

commit 18b025d530b2410c74c094c0e78671570c60b7bd
Author: Nigel Tao <nigeltao@golang.org>
Date:   Tue Oct 25 11:28:07 2011 +1100

    html: remove the Tokenizer.ReturnComments option.

    The original intention was to simplify the parser, in making it skip
    all comment tokens. However, checking that the Go html package is
    100% compatible with the WebKit HTML test suite requires parsing the
    comments. There is no longer any real benefit for the option.

    R=gri, andybalholm
    CC=golang-dev
    https://golang.org/cl/5321043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/18b025d530b2410c74c094c0e78671570c60b7bd

元コミット内容

html: remove the Tokenizer.ReturnComments option.

このコミットは、Go言語の標準ライブラリに含まれる html パッケージから Tokenizer.ReturnComments オプションを削除するものです。当初の目的は、パーサーを簡素化し、すべてのコメントトークンをスキップさせることでしたが、Goの html パッケージがWebKit HTMLテストスイートと100%互換性があることを確認するためには、コメントをパースする必要があることが判明しました。そのため、このオプションの実際の利点はもはやありません。

変更の背景

Go言語の html パッケージは、HTMLドキュメントのパースとトークン化を行うための機能を提供します。初期の設計段階では、パーサーの簡素化とパフォーマンスの最適化のため、HTMLコメントをスキップするオプション Tokenizer.ReturnComments が導入されていました。これにより、アプリケーションがコメントの内容に興味がない場合に、不要な処理を省くことができました。

しかし、Web標準への準拠とブラウザとの互換性は、Web関連ライブラリにとって非常に重要です。特に、WebKit HTMLテストスイートのような包括的なテストスイートは、HTMLパーサーが様々なエッジケースや複雑な構造を正しく処理できるかを検証するために用いられます。このテストスイートを実行する過程で、Goの html パッケージがWebKitの挙動と完全に一致するためには、コメントトークンも適切にパースし、処理する必要があることが明らかになりました。

コメントをスキップするオプションが存在すると、テストスイートの要件を満たせない、あるいはテストの実行方法が複雑になるなどの問題が生じます。コメントのパースが必須となったことで、Tokenizer.ReturnComments オプションは冗長となり、むしろコードの複雑性を増す要因となってしまいました。このため、コードベースをクリーンに保ち、将来的なメンテナンスを容易にするために、このオプションを削除する決定がなされました。

前提知識の解説

HTMLの構造とコメント

HTML (HyperText Markup Language) は、ウェブページの構造を定義するためのマークアップ言語です。HTMLドキュメントは、要素、属性、テキスト、コメントなどで構成されます。

  • HTMLコメント: HTMLコメントは <!-- で始まり --> で終わるテキストブロックで、ブラウザには表示されません。主に開発者がコード内にメモを残したり、一時的にコードブロックを無効化したりするために使用されます。例: <!-- This is a comment -->

HTMLパーサーとトークナイザー

HTMLドキュメントをコンピュータが理解できる形式に変換するプロセスを「パース(構文解析)」と呼びます。このパースプロセスは通常、以下の2つの主要な段階に分けられます。

  1. トークン化(Lexing/Tokenizing): 入力されたHTML文字列を、意味のある最小単位である「トークン」のストリームに分解するプロセスです。例えば、<p> は開始タグトークン、</p> は終了タグトークン、Hello はテキストトークン、<!-- comment --> はコメントトークンとして識別されます。この役割を担うのが「トークナイザー(Lexer/Tokenizer)」です。
  2. ツリー構築(Parsing): トークンのストリームを受け取り、それらをDOM(Document Object Model)ツリーと呼ばれる階層的な構造に変換するプロセスです。このツリーは、ウェブページの論理的な構造を表現し、JavaScriptなどのスクリプトからアクセス・操作されます。この役割を担うのが「パーサー(Parser)」です。

Tokenizer.ReturnComments オプションは、このトークン化の段階で、コメントトークンをトークンストリームに含めるか、それとも完全に無視するかを制御するものでした。

WebKit HTMLテストスイート

WebKitは、Apple SafariやGoogle Chrome(初期)などで使用されていたオープンソースのレンダリングエンジンです。WebKit HTMLテストスイートは、HTMLの仕様に準拠しているかを検証するための包括的なテストケースの集合体です。このテストスイートは、様々なHTMLの構文、要素、属性、そしてコメントの処理など、多岐にわたるシナリオをカバーしており、ブラウザやHTMLパーサーの実装がWeb標準にどれだけ忠実であるかを評価するための重要なベンチマークとして機能します。

Goの html パッケージがこのテストスイートとの互換性を目指すということは、単にHTMLをパースできるだけでなく、WebKitがHTMLを解釈するのと全く同じように、コメントを含むすべての要素を正確に処理する必要があることを意味します。

技術的詳細

このコミットの技術的な核心は、HTMLトークナイザーの挙動変更と、それに関連するコードの簡素化にあります。

Tokenizer.ReturnComments オプションの削除

以前の html パッケージの Tokenizer 構造体には、ReturnComments bool というフィールドが存在しました。このフィールドが true に設定されている場合、Next メソッドはコメントトークンを返しましたが、false (デフォルト) の場合はコメントトークンをスキップしていました。

このコミットでは、Tokenizer 構造体から ReturnComments フィールドが完全に削除されました。これにより、Tokenizer は常にコメントトークンを生成するようになります。

Next メソッドの変更

TokenizerNext メソッドは、次のHTMLトークンをスキャンしてそのタイプを返す役割を担っています。変更前は、このメソッド内で ReturnComments の値に基づいてコメントトークンをスキップするロジックが含まれていました。

変更後、Next メソッドはコメントトークンをスキップするロジックを削除し、常にコメントトークンを返すように修正されました。具体的には、token.go 内の Next メソッドから、if z.tt == CommentToken && !z.ReturnComments { continue } というループと条件が削除されています。これにより、コメントトークンが検出された場合でも、それがすぐに返されるようになります。

parse.gotoken_test.go の変更

src/pkg/html/parse.go では、Parse 関数内で p.tokenizer.ReturnComments = true と明示的に設定していた行が削除されました。これは、ReturnComments オプションがなくなったため、もはやこの設定が不要になったためです。

src/pkg/html/token_test.go では、テストケース内で z.ReturnComments = true と設定していた行が削除されました。これも同様に、オプションが削除されたことによる変更です。

doc.go の変更

src/pkg/html/doc.go はパッケージのドキュメントファイルです。このファイルから Tokenizer.ReturnComments オプションに関する説明が削除されました。これは、このオプションがもはや存在しないため、ドキュメントからその記述を削除することで、ユーザーが誤解するのを防ぐためです。

これらの変更により、html パッケージのトークナイザーは、HTMLコメントを常にパースし、トークンストリームの一部として扱うようになりました。これにより、WebKit HTMLテストスイートのような厳格な互換性要件を満たすことが可能になります。

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

このコミットにおける主要なコード変更は、src/pkg/html/token.go ファイルに集中しています。

src/pkg/html/token.go

  • Tokenizer 構造体から ReturnComments bool フィールドが削除されました。
    --- a/src/pkg/html/token.go
    +++ b/src/pkg/html/token.go
    @@ -116,10 +116,6 @@ type span struct {
    
     // A Tokenizer returns a stream of HTML Tokens.
     type Tokenizer struct {
    -	// If ReturnComments is set, Next returns comment tokens;
    -	// otherwise it skips over comments (default).
    -	ReturnComments bool
    -
     	// r is the source of the HTML text.
     	r io.Reader
     	// tt is the TokenType of the current token.
    
  • Next メソッドのロジックが変更され、コメントトークンをスキップする条件が削除されました。また、next という内部関数が Next に統合され、z.tt への代入が各 return ステートメントの直前で行われるようになりました。
    --- a/src/pkg/html/token.go
    +++ b/src/pkg/html/token.go
    @@ -596,11 +594,13 @@ loop:
     	if x := z.raw.end - len("<a"); z.raw.start < x {
     		z.raw.end = x
     		z.data.end = x
    -		return TextToken
    +		z.tt = TextToken
    +		return z.tt
     	}
     	switch tokenType {
     	case StartTagToken:
    -		return z.readStartTag()
    +		z.tt = z.readStartTag()
    +		return z.tt
     	case EndTagToken:
     		c = z.readByte()
     		if z.err != nil {
    @@ -616,39 +616,31 @@ loop:
     		}
     		if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' {
     			z.readEndTag()
    -			return EndTagToken
    +			z.tt = EndTagToken
    +			return z.tt
     		}
     		z.raw.end--
     		z.readUntilCloseAngle()
    -		return CommentToken
    +		z.tt = CommentToken
    +		return z.tt
     	case CommentToken:
     		if c == '!' {
    -			return z.readMarkupDeclaration()
    +			z.tt = z.readMarkupDeclaration()
    +			return z.tt
     		}
     		z.raw.end--
     		z.readUntilCloseAngle()
    -		return CommentToken
    +		z.tt = CommentToken
    +		return z.tt
     	}
     }
     if z.raw.start < z.raw.end {
     	z.data.end = z.raw.end
    -	return TextToken
    -}
    -return ErrorToken
    -}
    -
    -// Next scans the next token and returns its type.
    -func (z *Tokenizer) Next() TokenType {
    -	for {
    -		z.tt = z.next()
    -		// TODO: remove the ReturnComments option. A tokenizer should
    -		// always return comment tags.
    -		if z.tt == CommentToken && !z.ReturnComments {
    -			continue
    -		}
    +	z.tt = TextToken
     	return z.tt
     }
    -	panic("unreachable")
    +	z.tt = ErrorToken
    +	return z.tt
     }
    

その他のファイル

  • src/pkg/html/doc.go: Tokenizer.ReturnComments に関するドキュメントが削除されました。
  • src/pkg/html/parse.go: Parse 関数内で p.tokenizer.ReturnComments = true と設定していた行が削除されました。
  • src/pkg/html/token_test.go: テストケース内で z.ReturnComments = true と設定していた行が削除されました。

コアとなるコードの解説

このコミットの核心は、src/pkg/html/token.go 内の Tokenizer 構造体と Next メソッドの変更にあります。

Tokenizer 構造体からの ReturnComments フィールドの削除

以前は、Tokenizer 構造体には ReturnComments bool というフィールドがありました。これは、トークナイザーがHTMLコメントをトークンとして返すかどうかを制御するためのフラグでした。このフィールドが削除されたことで、Tokenizer はもはやコメントの扱いをオプションとして選択する余地がなくなり、常にコメントをトークンとして処理するようになりました。これは、HTMLパースの挙動を統一し、WebKit HTMLテストスイートのような厳格な互換性要件を満たすための重要な変更です。

Next メソッドの変更

Next メソッドは、Tokenizer の主要なインターフェースであり、入力ストリームから次のHTMLトークンを読み取り、そのタイプを返します。

変更前は、Next メソッドの内部で z.next() というプライベートなヘルパー関数を呼び出し、その結果が CommentToken であり、かつ z.ReturnCommentsfalse の場合に continue して次のトークンを読み込む、というロジックがありました。これは、コメントをスキップするための明示的な処理でした。

変更後、このコメントスキップのロジックが完全に削除されました。Next メソッドは、z.tt フィールドに直接トークンタイプを代入し、その値を返すようになりました。これにより、コメントトークンが検出された場合でも、他のトークンと同様に即座に返されるようになります。

また、変更前は Next メソッドが z.next() を呼び出すループを持っていましたが、変更後は Next メソッド自体が直接トークンをスキャンし、z.tt に結果を格納して返す単一のパスになりました。これにより、コードのフローがより直接的になり、理解しやすくなっています。

この変更は、html パッケージがHTMLのコメントを常にパースし、それらをトークンストリームの一部として扱うことを強制します。これにより、HTMLの仕様に厳密に準拠し、様々なブラウザの実装との互換性を高めることができます。特に、WebKit HTMLテストスイートのような包括的なテストスイートをパスするためには、コメントの正確なパースが不可欠であるため、この変更はパッケージの堅牢性と信頼性を向上させる上で重要な役割を果たします。

関連リンク

  • Go言語の html パッケージのドキュメント (現在のバージョン): https://pkg.go.dev/golang.org/x/net/html (注: コミット当時のパスとは異なる可能性がありますが、現在のパッケージ情報として参考になります)
  • Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージにある https://golang.org/cl/5321043 はGerritのチェンジリストへのリンクです)

参考にした情報源リンク