[インデックス 10883] Go HTML パーサーのテーブル解析改善
コミット
コミットハッシュ: a0bd46e70fa09e8fe4749399440a2dc52e80f84a
作成者: Andrew Balholm andybalholm@gmail.com
日付: 2011年12月20日 10:57:06 +1100
コミットメッセージ: html: ignore
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a0bd46e70fa09e8fe4749399440a2dc52e80f84a
元コミット内容
このコミットでは、GoのHTMLパーサーにおいて、テーブルフラグメントの解析時に特定のテーブル要素(<caption>
, <col>
, <colgroup>
, <tbody>
, <tfoot>
, <thead>
)を適切に処理する機能が追加されました。
主な変更点:
inTableBodyIM
関数に新しいケースを追加- テーブル関連要素の処理ロジックを改善
- テストケースの進捗を36から45に拡張
具体的な変更内容:
src/pkg/html/parse.go
に7行の新しいコードを追加src/pkg/html/parse_test.go
でテスト範囲を拡張- html5libテストスイートの
tests6.dat
のテスト36を通過
変更の背景
この変更は、GoのHTMLパーサーをHTML5仕様により準拠させるために行われました。HTML5仕様では、テーブル要素の解析において「in table body」挿入モードで特定のテーブル要素に遭遇した場合の処理が厳密に定義されています。
特に、<tbody>
, <thead>
, <tfoot>
内で<caption>
, <col>
, <colgroup>
, <tbody>
, <tfoot>
, <thead>
要素に遭遇した場合、現在のテーブル構造を適切に処理し、パーサーの状態を正しく遷移させる必要がありました。
このコミットの背景には、html5libテストスイートに合格するという重要な目標がありました。html5libテストスイートは、HTMLパーサーの実装がHTML5仕様に準拠していることを検証するためのデファクトスタンダードなテストセットです。
前提知識の解説
HTML5パーサーの挿入モード
HTML5パーサーは、状態機械(ステートマシン)として動作し、現在の「挿入モード」に基づいて各トークンの処理方法を決定します。主要な挿入モードには以下があります:
- initial: 初期状態
- before html:
<html>
要素の前 - before head:
<head>
要素の前 - in head:
<head>
要素内 - in body:
<body>
要素内 - in table: テーブル内
- in table body: テーブル本体内(
<tbody>
,<thead>
,<tfoot>
内) - in row: テーブル行内
- in cell: テーブルセル内
テーブル解析の複雑さ
HTMLテーブルの解析は、HTMLパーサーの中でも最も複雑な部分の一つです。これは以下の理由によります:
- ネストした構造: テーブルは多層の要素(table > tbody > tr > td)からなる
- 暗黙的な要素生成: 省略された要素(例:
<tbody>
)を自動的に生成する必要がある - エラー処理: 不正な構造を適切に修正する必要がある
- スコープ管理: 各要素のスコープを正確に管理する必要がある
html5libテストスイート
html5libテストスイートは、HTMLパーサーの実装を検証するための包括的なテストセットです。以下の特徴があります:
- 標準化されたテスト形式: 各テストは
#data
,#errors
,#document
セクションで構成 - 網羅的なカバレッジ: HTML5仕様のすべての側面をカバー
- 多言語対応: 複数のプログラミング言語での実装検証に使用
技術的詳細
inTableBodyIM
関数の役割
inTableBodyIM
(in Table Body Insertion Mode)関数は、パーサーが<tbody>
, <thead>
, <tfoot>
要素内にいる際のトークン処理を担当します。HTML5仕様では、この状態で特定の要素に遭遇した場合の処理が厳密に定義されています。
追加された処理ロジック
今回追加されたコードは、以下の要素に対する処理を実装しています:
case "caption", "col", "colgroup", "tbody", "tfoot", "thead":
if !p.popUntil(tableScopeStopTags, "tbody", "thead", "tfoot") {
// Ignore the token.
return true
}
p.im = inTableIM
return false
この処理の詳細:
- スコープチェック:
popUntil
メソッドを使用してテーブルスコープ内の要素をポップ - 状態遷移: パーサーの挿入モードを
inTableIM
に変更 - トークン再処理:
false
を返すことで、同じトークンを新しい挿入モードで再処理
popUntil
メソッドの動作
popUntil
メソッドは、指定された要素がスコープ内に見つかるまで、パーサーのスタックから要素をポップします。これにより、テーブル構造の一貫性が保たれます。
コアとなるコードの変更箇所
parse.go の変更
ファイル: src/pkg/html/parse.go
関数: inTableBodyIM
行数: 1206行目付近
func inTableBodyIM(p *parser) bool {
// 既存のコード...
// 新しく追加された部分
case "caption", "col", "colgroup", "tbody", "tfoot", "thead":
if !p.popUntil(tableScopeStopTags, "tbody", "thead", "tfoot") {
// Ignore the token.
return true
}
p.im = inTableIM
return false
// 既存のコード...
}
parse_test.go の変更
ファイル: src/pkg/html/parse_test.go
変更内容: テスト範囲の拡張
// 変更前
{"tests6.dat", 36},
// 変更後
{"tests6.dat", 45},
コアとなるコードの解説
新しい処理ロジックの詳細分析
追加されたコードは、HTML5仕様のテーブル解析ルールを正確に実装しています:
- 要素の識別:
caption
,col
,colgroup
,tbody
,tfoot
,thead
要素を識別 - スコープ確認: 現在のテーブルボディスコープをチェック
- 構造修正: 必要に応じてスタックを調整
- 状態遷移: 適切な挿入モードに遷移
エラー処理の戦略
このコードは、不正なHTML構造に対して寛容でありながら、仕様に準拠した結果を生成します:
- 無視戦略: スコープ外の要素は無視
- 再処理戦略: 状態遷移後に同じトークンを再処理
- 構造保持: テーブルの論理構造を維持
パフォーマンスへの配慮
実装では、以下の点でパフォーマンスが考慮されています:
- 効率的なスコープチェック:
popUntil
メソッドの最適化 - 最小限の状態変更: 必要な場合のみ状態を変更
- 早期リターン: 不要な処理を回避