[インデックス 10341] ファイルの概要
このコミットは、Go言語のhtml
パッケージにおけるHTMLパーサーの挙動を修正し、<table>
要素の外部に誤って配置された<col>
タグを適切に無視するように変更したものです。これにより、不正なHTML構造に対するパーサーの堅牢性が向上し、Webブラウザの挙動により近づきました。
コミット
commit 0a61c846ef36dc43437e37c6494a40b47824124f
Author: Andrew Balholm <andybalholm@gmail.com>
Date: Fri Nov 11 21:44:01 2011 +1100
html: ignore <col> tag outside tables
Pass tests1.dat, test 109:
<table><col><tbody><col><tr><col><td><col></table><col>
| <html>
| <head>
| <body>
| <table>
| <colgroup>
| <col>
| <tbody>
| <colgroup>
| <col>
| <tbody>
| <tr>
| <colgroup>
| <col>
| <tbody>
| <tr>
| <td>
| <colgroup>
| <col>
Also pass test 110:
<table><colgroup><tbody><colgroup><tr><colgroup><td><colgroup></table><colgroup>
R=nigeltao
CC=golang-dev
https://golang.org/cl/5369069
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0a61c846ef36dc43437e37c6494a40b47824124f
元コミット内容
このコミットは、HTMLパーサーが<table>
要素の外部に現れる<col>
タグを無視するように修正します。これにより、tests1.dat
のテストケース109と110がパスするようになります。これらのテストケースは、不正なHTML構造、特に<col>
タグがテーブルの正しいコンテキスト外に配置された場合のパーサーの挙動を検証するものです。
変更の背景
HTMLの仕様では、<col>
タグは<colgroup>
タグの子要素として、または直接<table>
タグの子要素としてのみ配置されることが許されています。これは、テーブルの列のプロパティ(幅、スタイルなど)を定義するために使用されるためです。しかし、現実のWebページでは、開発者の誤りや動的なコンテンツ生成の過程で、この仕様に準拠しない不正なHTMLが生成されることがあります。
Webブラウザは、このような不正なHTMLに対しても可能な限りレンダリングを試みるため、厳密なエラーで処理を中断するのではなく、エラー回復メカニズム(エラーハンドリング)を備えています。多くのブラウザは、テーブル構造の外部に現れる<col>
タグを単に無視する挙動を示します。
Go言語のhtml
パッケージは、HTML5の仕様に準拠した堅牢なHTMLパーサーを提供することを目指しています。このコミット以前は、パーサーが不正な<col>
タグを適切に処理せず、予期しない結果を招く可能性がありました。この変更は、GoのHTMLパーサーが実際のWebブラウザの挙動に近づき、より多くの不正なHTML入力に対して安定した出力を提供できるようにすることを目的としています。具体的には、tests1.dat
のテスト109と110が示すような、テーブル構造外の<col>
タグの扱いを修正することが喫緊の課題でした。
前提知識の解説
HTMLのテーブル構造と関連タグ
HTMLのテーブルは、行と列でデータを整理するための構造です。主要なタグとその役割は以下の通りです。
<table>
: テーブル全体を定義するコンテナ要素。<colgroup>
: テーブル内の1つ以上の列のグループを定義します。この要素は<table>
の直接の子要素として配置され、列の共通のプロパティ(例: 幅)を設定するために使用されます。<col>
:<colgroup>
要素の内部、または<table>
要素の直接の子要素として配置され、個々の列のプロパティを定義します。例えば、<col span="2" style="width:100px;">
は2つの列にわたって幅を設定します。<tbody>
: テーブルの本体部分を定義します。通常、<tr>
(テーブルの行)要素を含みます。<thead>
: テーブルのヘッダー部分を定義します。通常、ヘッダー行(<tr>
)を含みます。<tfoot>
: テーブルのフッター部分を定義します。通常、フッター行(<tr>
)を含みます。<tr>
: テーブルの行を定義します。<td>
: テーブルのデータセル(通常のセル)を定義します。<th>
: テーブルのヘッダーセルを定義します。
これらのタグは、HTMLの仕様によって厳密な親子関係と配置ルールが定められています。<col>
タグが<table>
または<colgroup>
の外部に現れることは、仕様違反となります。
HTMLパーシングとエラー回復
HTMLパーシングとは、HTMLドキュメントを読み込み、その構造を解析して、ブラウザがレンダリングできるような内部表現(DOMツリーなど)に変換するプロセスです。HTMLは非常に寛容な言語であり、多くのWebページには仕様に厳密に準拠しない「不正な」HTMLが含まれています。
WebブラウザのHTMLパーサーは、このような不正なHTMLに遭遇した場合でも、エラーで処理を停止するのではなく、可能な限りエラーを回復し、DOMツリーを構築しようとします。このプロセスを「エラー回復(Error Recovery)」と呼びます。エラー回復の挙動は、HTML5の仕様で詳細に定義されており、主要なブラウザ間で一貫した動作が期待されます。
例えば、閉じタグが欠落している場合、ブラウザは自動的に閉じタグを補完することがあります。また、不適切な場所に配置されたタグは、無視されたり、DOMツリーの別の場所に移動されたりすることがあります。このコミットのケースでは、テーブル構造外の<col>
タグが無視されるというブラウザの一般的なエラー回復挙動にGoのパーサーを合わせるものです。
Go言語のhtml
パッケージ
Go言語の標準ライブラリには、HTMLの解析と生成を行うためのhtml
パッケージが含まれています。このパッケージは、HTML5の仕様に準拠したパーサーを提供し、Webスクレイピング、HTMLテンプレートの処理、HTMLコンテンツのサニタイズなど、様々な用途で利用されます。
html
パッケージのパーサーは、入力されたHTMLをトークン(タグ、テキスト、コメントなど)に分解し、それらのトークンを基にDOMツリーを構築します。パーシングの過程では、「挿入モード(Insertion Mode)」と呼ばれる状態機械が使用され、現在のコンテキスト(例: <body>
内、<table>
内など)に基づいて、どのタグが有効で、どのように処理すべきかを決定します。
このコミットで変更されるinBodyIM
関数は、<body>
要素の内部での挿入モードを処理する部分です。
技術的詳細
このコミットの核心は、Go言語のhtml
パッケージ内のHTMLパーサーが、<body>
要素の挿入モード(inBodyIM
)において、特定のHTMLタグが予期しない場所で出現した場合に、それらを無視するように変更された点です。
HTMLパーサーは、入力ストリームからトークンを読み込み、現在の「挿入モード」に基づいてそのトークンをどのように処理するかを決定します。inBodyIM
関数は、パーサーが<body>
要素の内部にいるときに呼び出される主要な挿入モードハンドラの一つです。
変更前は、inBodyIM
関数内で未知のタグや特定のコンテキスト外のタグに遭遇した場合のデフォルトの挙動は、p.addElement(p.tok.Data, p.tok.Attr)
を呼び出して、その要素をDOMツリーに追加しようとすることでした。しかし、これはHTMLの仕様に違反するタグ(特に<col>
のようなテーブル関連タグがテーブル外にある場合)に対して、ブラウザの挙動と異なるDOMツリーを生成する可能性がありました。
このコミットでは、inBodyIM
関数内のswitch
文に新しいcase
が追加されました。このcase
は、以下のタグが<body>
要素の内部で出現した場合に、それらを「無視」するように明示的に指示します。
caption
col
colgroup
frame
head
tbody
td
tfoot
th
thead
tr
これらのタグは、HTMLの仕様上、特定の親要素(主に<table>
関連の要素)の内部にのみ出現することが許されています。例えば、<col>
は<colgroup>
または<table>
の子要素であるべきです。<body>
の直下や他の一般的な要素の内部にこれらのタグが出現することは不正なHTMLです。
この変更により、パーサーはこれらの不正なタグを読み込んだ際に、DOMツリーにそれらを追加せず、単にスキップします。これは、多くのWebブラウザが不正なHTMLに対して行うエラー回復の挙動と一致します。結果として、GoのHTMLパーサーは、より堅牢になり、不正なHTML入力に対してもブラウザと互換性のあるDOM構造を生成できるようになります。
テストファイルsrc/pkg/html/parse_test.go
の変更は、この新しい挙動を検証するためのものです。tests1.dat
のテストケース109と110は、まさにテーブル構造外に<col>
や<colgroup>
などのタグが配置されたシナリオを扱っており、この修正によってこれらのテストがパスするようになりました。テストケースのインデックスが109
から111
に更新されているのは、おそらく新しいテストケースが追加されたか、既存のテストケースの番号が変更されたため、テストスイート全体で期待されるテストケースの総数が調整されたことを示唆しています。
コアとなるコードの変更箇所
src/pkg/html/parse.go
--- a/src/pkg/html/parse.go
+++ b/src/pkg/html/parse.go
@@ -667,6 +667,8 @@ func inBodyIM(p *parser) (insertionMode, bool) {
case "image":
p.tok.Data = "img"
return inBodyIM, false
+ case "caption", "col", "colgroup", "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr":
+ // Ignore the token.
default:
// TODO.
p.addElement(p.tok.Data, p.tok.Attr)
src/pkg/html/parse_test.go
--- a/src/pkg/html/parse_test.go
+++ b/src/pkg/html/parse_test.go
@@ -133,7 +133,7 @@ func TestParser(t *testing.T) {
tn int
}{
// TODO(nigeltao): Process all the test cases from all the .dat files.
- {"tests1.dat", 109},
+ {"tests1.dat", 111},
{"tests2.dat", 0},
{"tests3.dat", 0},
}
コアとなるコードの解説
src/pkg/html/parse.go
の変更
この変更は、inBodyIM
関数、すなわちHTMLパーサーが<body>
要素の内部にいるときの挿入モードのロジックに影響を与えます。
-
追加された
case
文:case "caption", "col", "colgroup", "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr": // Ignore the token.
この行が追加されたことで、パーサーが
<body>
要素の内部で、上記のいずれかのタグ(caption
,col
,colgroup
,frame
,head
,tbody
,td
,tfoot
,th
,thead
,tr
)の開始タグに遭遇した場合、そのトークンは無視されるようになりました。これらのタグは、HTMLの仕様上、特定のコンテキスト(主にテーブル関連の要素内)でのみ有効です。例えば、
<col>
は<colgroup>
または<table>
の子要素としてのみ意味を持ちます。<body>
の直下や他の一般的な要素の内部にこれらのタグが出現することは、不正なHTML構造です。以前は、これらの不正なタグは
default
ケースで処理され、p.addElement
が呼び出されてDOMツリーに追加されようとしていました。しかし、これはブラウザの挙動と異なり、不正なDOM構造を生成する原因となっていました。この変更により、GoのHTMLパーサーは、ブラウザが不正なHTMLに対して行うエラー回復の挙動(これらのタグを無視する)に近づき、より堅牢で互換性のあるDOMツリーを生成できるようになります。
src/pkg/html/parse_test.go
の変更
このファイルは、HTMLパーサーのテストスイートを定義しています。
- テストケース数の更新:
この変更は、- {"tests1.dat", 109}, + {"tests1.dat", 111},
tests1.dat
というテストデータファイルに対して実行されるテストケースの期待される総数が109
から111
に更新されたことを示しています。これは、このコミットで修正された問題に関連する新しいテストケース(おそらくテスト109と110)がtests1.dat
に追加されたか、既存のテストケースの番号付けが変更されたことを意味します。この変更により、パーサーの修正が正しく機能し、関連するテストがパスすることを確認できます。
関連リンク
- Go CL (Code Review) リンク: https://golang.org/cl/5369069
- GitHubコミットページ: https://github.com/golang/go/commit/0a61c846ef36dc43437e37c6494a40b47824124f
参考にした情報源リンク
- HTML Living Standard - 13.2.6.4.5 The "in body" insertion mode: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inbody
- HTML Living Standard - 4.9 Tables: https://html.spec.whatwg.org/multipage/tables.html
- MDN Web Docs -
<table>
: https://developer.mozilla.org/ja/docs/Web/HTML/Element/table - MDN Web Docs -
<col>
: https://developer.mozilla.org/ja/docs/Web/HTML/Element/col - MDN Web Docs -
<colgroup>
: https://developer.mozilla.org/ja/docs/Web/HTML/Element/colgroup - Go html package documentation: https://pkg.go.dev/golang.org/x/net/html (Note: The
html
package was originally part of the standard librarysrc/pkg/html
but later moved togolang.org/x/net/html
.)