[インデックス 12909] ファイルの概要
このコミットは、Go言語の実験的なHTMLパーサーパッケージ exp/html
における beforeHeadIM
(before head insertion mode) のロジック改善に関するものです。具体的には、HTML5の仕様に沿って、DoctypeToken
の扱いを無視するように変更し、コードの制御フローをより明確にすることで、パーサーの堅牢性と正確性を向上させています。
コミット
commit b65c9a633ef594b171cb11b823f3d96f47d9f4e3
Author: Andrew Balholm <andybalholm@gmail.com>
Date: Wed Apr 18 22:45:36 2012 +1000
exp/html: improve beforeHeadIM
Add a case to ignore doctype tokens.
Clean up the flow of control to more clearly match the spec.
Pass one more test.
R=nigeltao
CC=golang-dev
https://golang.org/cl/6062047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b65c9a633ef594b171cb11b823f3d96f47d9f4e3
元コミット内容
exp/html: improve beforeHeadIM
Add a case to ignore doctype tokens.
Clean up the flow of control to more clearly match the spec.
Pass one more test.
変更の背景
このコミットは、Go言語のHTMLパーサー exp/html
がHTML5の仕様に厳密に準拠するための改善の一環です。特に、HTMLドキュメントの <head>
要素が始まる前の状態 (before head insertion mode
) におけるトークンの処理ロジックに焦点を当てています。
HTML5の仕様では、パーサーが特定の状態にあるときに、特定のトークン(例えば、コメント、DOCTYPE宣言、開始タグ、終了タグ、テキストなど)をどのように処理すべきかが詳細に定義されています。以前の実装では、DoctypeToken
が beforeHeadIM
で適切に無視されていなかった可能性があり、また、他のトークン処理における制御フローが仕様の記述と完全に一致していなかった可能性があります。
この不一致は、特定の不正なHTML構造を持つドキュメントをパースする際に、予期せぬ結果やエラーを引き起こす可能性がありました。そのため、仕様への準拠を強化し、より多くのテストケースをパスできるようにするために、この改善が行われました。
前提知識の解説
HTML5パーシングアルゴリズム
HTML5のパースは、非常に複雑なステートマシンとして定義されています。これは、ブラウザがHTMLドキュメントをどのように解析し、DOMツリーを構築するかを厳密に規定したものです。パーサーは、入力ストリームからトークンを読み込み、現在の「挿入モード (insertion mode)」に基づいて、そのトークンを処理します。挿入モードは、パーサーがドキュメントのどの部分を処理しているかに応じて変化します。
挿入モード (Insertion Mode)
挿入モードは、HTMLパーサーの現在の状態を示します。各挿入モードには、特定のトークンタイプ(開始タグ、終了タグ、テキスト、コメント、DOCTYPEなど)が到着したときに実行すべき一連のルールが定義されています。これにより、不正なHTMLであっても、ブラウザが一貫した方法でDOMツリーを構築できるようになっています。
before head
挿入モード (Section 12.2.5.4.3)
このコミットが対象としているのは、HTML5仕様の「12.2.5.4.3 The rules for parsing tokens in the "before head" insertion mode」セクションです。このモードは、HTMLドキュメントのルート要素 (<html>
) がパースされ、次に <head>
要素が期待される状態です。このモードでは、以下のようなトークンが到着した場合の処理が定義されています。
- DOCTYPEトークン: このモードでは、DOCTYPEトークンは無視されるべきです。
- コメントトークン: コメントノードとして現在のノード(通常は
<html>
要素)の子として追加されます。 - テキストトークン: 先頭の空白文字は無視され、残りのテキストが存在する場合、暗黙的に
<head>
要素が生成され、その中にテキストが挿入されるか、またはテキストが無視される場合があります。 - 開始タグトークン:
<html>
タグ:in body
挿入モードに切り替わります。<body>
タグ: 暗黙的に<head>
タグが生成され、in body
挿入モードに切り替わります。head
タグ:<head>
要素が生成され、in head
挿入モードに切り替わります。- その他のタグ: 暗黙的に
<head>
タグが生成され、in head
挿入モードに切り替わります。
- 終了タグトークン:
head
,body
,html
,br
タグ: 暗黙的に<head>
タグが生成され、in head
挿入モードに切り替わります。- その他のタグ: 無視されます。
- EOFトークン: 暗黙的に
<head>
タグが生成され、in head
挿入モードに切り替わり、EOFトークンが再処理されます。
exp/html
パッケージ
exp/html
は、Go言語の標準ライブラリの一部として提供されている html
パッケージの初期段階の実験的なバージョンでした。このパッケージは、HTML5の仕様に準拠したHTMLパーサーとレンダラーを提供することを目的としていました。最終的に、この実験的な成果は golang.org/x/net/html
パッケージとして提供され、Goの標準的なHTML処理ライブラリとなっています。このコミットが行われた2012年時点では、まだ exp/html
として開発が進められていました。
技術的詳細
このコミットの主要な目的は、src/pkg/exp/html/parse.go
ファイル内の beforeHeadIM
関数をHTML5仕様の「12.2.5.4.3 The rules for parsing tokens in the "before head" insertion mode」に厳密に合わせることです。
変更点は以下の通りです。
DoctypeToken
の無視: 以前のバージョンではDoctypeToken
の明示的な処理がありませんでしたが、HTML5仕様ではこのモードでDoctypeToken
は無視されるべきとされています。このコミットでは、case DoctypeToken:
を追加し、return true
(トークンを消費して次のトークンに進む) ことで、これを無視するように修正しています。- 制御フローの簡素化と明確化:
- 以前のコードでは
add
とimplied
というブール変数を使用して、<head>
タグを明示的に追加するか、暗黙的に追加するかを制御していました。 - 新しいコードでは、
StartTagToken
のhead
ケースで直接p.addElement("head", p.tok.Attr)
を呼び出し、p.im = inHeadIM
に遷移し、return true
することで、明示的な<head>
タグの処理を簡潔にしています。 TextToken
やStartTagToken
のhtml
以外のケース、EndTagToken
のhead
,body
,html
,br
以外のケースでは、以前はimplied = true
を設定していましたが、新しいコードではこれらのケースで直接return true
(トークンを無視) するか、または後続の暗黙的な<head>
タグの追加ロジックにフォールスルーするように変更されています。- 最終的な
<head>
タグの暗黙的な追加ロジック (p.addElement("head", nil)
) は、switch
ステートメントの後に移動され、return false
(トークンを再処理しない) とすることで、より仕様に沿ったフローになっています。これにより、特定のトークンが処理された後に、暗黙的な<head>
タグの生成と挿入モードの変更が適切に行われるようになります。
- 以前のコードでは
- テストケースの修正:
src/pkg/exp/html/testlogs/tests19.dat.log
ファイルのテスト結果が修正されています。具体的には、FAIL "<!doctype html><html></p><!--foo-->"
がPASS "<!doctype html><html></p><!--foo-->"
に変更されており、これはbeforeHeadIM
の改善によって、この特定のHTMLスニペットが正しくパースされるようになったことを示しています。このテストケースは、<!doctype html>
の後に<html>
が続き、その後に不正な</p>
終了タグとコメントが続くというもので、before head
モードでのDOCTYPE
と不正な終了タグの処理が正しく行われるようになったことを確認しています。
これらの変更により、パーサーはHTML5の仕様にさらに厳密に準拠し、より多くのエッジケースや不正なHTML構造を正しく処理できるようになります。
コアとなるコードの変更箇所
src/pkg/exp/html/parse.go
ファイルの beforeHeadIM
関数が主な変更箇所です。
--- a/src/pkg/exp/html/parse.go
+++ b/src/pkg/exp/html/parse.go
@@ -446,37 +446,30 @@ func beforeHTMLIM(p *parser) bool {
// Section 12.2.5.4.3.
func beforeHeadIM(p *parser) bool {
- var (
- add bool
- attr []Attribute
- implied bool
- )
switch p.tok.Type {
- case ErrorToken:
- implied = true
case TextToken:
p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace)
if len(p.tok.Data) == 0 {
// It was all whitespace, so ignore it.
return true
}
- implied = true
case StartTagToken:
switch p.tok.Data {
case "head":
- add = true
- attr = p.tok.Attr
+ p.addElement(p.tok.Data, p.tok.Attr)
+ p.head = p.top()
+ p.im = inHeadIM
+ return true
case "html":
return inBodyIM(p)
- default:
- implied = true
}
case EndTagToken:
switch p.tok.Data {
case "head", "body", "html", "br":
- implied = true
+ // Drop down to adding an implied <head> tag.
default:
// Ignore the token.
+ return true
}
case CommentToken:
p.addChild(&Node{
@@ -484,13 +477,15 @@ func beforeHeadIM(p *parser) bool {
Data: p.tok.Data,\n \t\t})\n \t\treturn true
+ case DoctypeToken:
+ // Ignore the token.
+ return true
}\n- if add || implied {
- p.addElement("head", attr)
- p.head = p.top()
- }\n+
+ p.addElement("head", nil)
+ p.head = p.top()
p.im = inHeadIM
- return !implied
+ return false
}\n
// Section 12.2.5.4.4.
また、src/pkg/exp/html/testlogs/tests19.dat.log
ファイルのテスト結果も変更されています。
--- a/src/pkg/exp/html/testlogs/tests19.dat.log
+++ b/src/pkg/exp/html/testlogs/tests19.dat.log
@@ -1,5 +1,5 @@
FAIL "<!doctype html><math><mn DefinitionUrl=\"foo\">"
-FAIL "<!doctype html><html></p><!--foo-->"
+PASS "<!doctype html><html></p><!--foo-->"
PASS "<!doctype html><head></head></p><!--foo-->"
FAIL "<!doctype html><body><p><pre>"
FAIL "<!doctype html><body><p><listing>"
コアとなるコードの解説
beforeHeadIM
関数は、HTMLパーサーが「before head」挿入モードにあるときに、次のトークンをどのように処理するかを決定します。
変更前は、add
と implied
というフラグ変数を使って、<head>
要素を明示的に追加するか(add
)、または暗黙的に追加するか(implied
)を制御していました。このロジックは、switch
ステートメントの後に続く if add || implied
ブロックで処理されていました。
変更後では、このフラグ変数を廃止し、各 case
ブロック内で直接、またはフォールスルーによって、HTML5仕様に沿った処理を行うように改善されています。
-
TextToken
の処理:- 先頭の空白文字を削除し、残りのテキストが空であれば
return true
でトークンを無視します。 - テキストが残っている場合は、以前は
implied = true
となっていましたが、変更後はswitch
の後に続く暗黙的な<head>
追加ロジックにフォールスルーします。これは、仕様でテキストトークンがこのモードで現れた場合、暗黙的に<head>
が生成されるというルールに合致します。
- 先頭の空白文字を削除し、残りのテキストが空であれば
-
StartTagToken
の処理:head
タグの場合: 以前はadd = true
とattr = p.tok.Attr
を設定していましたが、変更後はp.addElement("head", p.tok.Attr)
を直接呼び出し、p.head
を設定し、挿入モードをinHeadIM
に変更してreturn true
します。これにより、明示的な<head>
タグの処理がその場で完結します。html
タグの場合:inBodyIM(p)
を呼び出してin body
モードに遷移し、return
します。これは変更ありません。- その他の開始タグの場合: 以前は
implied = true
となっていましたが、変更後はswitch
の後に続く暗黙的な<head>
追加ロジックにフォールスルーします。これは、仕様でこれらのタグが暗黙的に<head>
を生成するというルールに合致します。
-
EndTagToken
の処理:head
,body
,html
,br
タグの場合: 以前はimplied = true
となっていましたが、変更後はコメント// Drop down to adding an implied <head> tag.
が示唆するように、暗黙的な<head>
追加ロジックにフォールスルーします。これは、仕様でこれらの終了タグが暗黙的に<head>
を生成するというルールに合致します。- その他の終了タグの場合:
return true
でトークンを無視します。これは、仕様でこれらのタグが無視されるというルールに合致します。
-
CommentToken
の処理:- コメントノードを追加し、
return true
でトークンを無視します。これは変更ありません。
- コメントノードを追加し、
-
DoctypeToken
の追加:- 新しく
case DoctypeToken:
が追加され、return true
でトークンを無視するように明示的に指定されています。これは、HTML5仕様でこのモードではDoctypeToken
が無視されるべきであるというルールに直接対応しています。
- 新しく
-
最終的な
<head>
追加ロジック:switch
ステートメントの後に、p.addElement("head", nil)
とp.head = p.top()
が移動され、p.im = inHeadIM
に設定されます。- 最後に
return false
が追加されています。これは、この関数がfalse
を返した場合、現在のトークンが再処理されることを意味します。しかし、この文脈では、暗黙的な<head>
が追加された後に、次のトークンがin head
モードで処理されることを意図していると考えられます。
これらの変更により、beforeHeadIM
関数はHTML5の仕様にさらに忠実になり、パーサーの動作がより予測可能で正確になりました。特に、DoctypeToken
の無視と、制御フローの簡素化は、コードの可読性と保守性を向上させています。
関連リンク
- HTML5仕様: https://html.spec.whatwg.org/multipage/parsing.html
- HTML5パーシングアルゴリズムの「before head」挿入モード (Section 12.2.5.4.3): https://html.spec.whatwg.org/multipage/parsing.html#the-before-head-insertion-mode
- Go言語の
golang.org/x/net/html
パッケージ: https://pkg.go.dev/golang.org/x/net/html
参考にした情報源リンク
- Go言語のHTMLパーサーに関する議論やドキュメント (当時の
exp/html
や現在のgolang.org/x/net/html
の背景を理解するため) - HTML5仕様書 (特にパーシングアルゴリズムのセクション)
- Go言語のコミット履歴と関連するコードレビュー (CL: Change List)
- Go言語の
html
パッケージのソースコード (現在の実装との比較のため)