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

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

このコミットは、src/pkg/exp/html/parse.go ファイルに対して行われた変更です。このファイルは、Go言語の実験的なHTMLパーサーの一部であり、HTML5の仕様に準拠したドキュメントの解析を担当しています。具体的には、1ファイルが変更され、7行が追加され、15行が削除されています。

コミット

  • コミットハッシュ: 4973c1fc7e050da54c3d741ceb57619a0a1ff1f7
  • 作者: Andrew Balholm andybalholm@gmail.com
  • 日付: Sun May 20 14:26:20 2012 +1000

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

https://github.com/golang/go/commit/4973c1fc7e050da54c3d741ceb57619a0a1ff1f7

元コミット内容

exp/html: adjust inRowIM to match spec

Delete cases that just fall down to "anything else" action.

Handle </tbody>, </tfoot>, and </thead>.

R=golang-dev, nigeltao
CC=golang-dev
https://golang.org/cl/6203061

変更の背景

このコミットの背景には、HTML5のパースアルゴリズムの厳密な仕様への準拠があります。HTML5の仕様は非常に詳細であり、ブラウザ間の互換性を保証するために、HTMLドキュメントの解析方法を厳密に定義しています。特に、テーブル要素(<table><tr><td>など)の解析は複雑で、特定の状況下でのトークンの処理方法が細かく規定されています。

inRowIM("in row" insertion mode)は、HTMLパーサーが<tr>(テーブル行)要素の内部にいるときに適用される挿入モードです。このモードでは、<td><th>のようなセル要素が期待されます。元の実装では、一部のトークンタイプ(ErrorTokenTextTokenCommentToken、および特定のStartTagTokenEndTagTokendefaultケース)が、単に「その他のすべて」として処理され、より具体的なHTML5仕様の動作が欠落していました。

このコミットの目的は、これらの「その他のすべて」にフォールバックするだけの冗長なケースを削除し、特に</tbody></tfoot></thead>といったテーブル関連の終了タグがinRowIMで出現した場合の処理をHTML5仕様に正確に合わせることです。これにより、パーサーの堅牢性と仕様への準拠が向上します。

前提知識の解説

HTML5パースアルゴリズム

HTML5のパースアルゴリズムは、ウェブブラウザがHTMLドキュメントを解析し、DOM(Document Object Model)ツリーを構築するための詳細な手順を定めています。このアルゴリズムは、大きく分けて以下の2つの段階で構成されます。

  1. トークン化 (Tokenization): 入力されたHTML文字列を、意味のある単位である「トークン」に分解します。トークンには、開始タグ、終了タグ、テキスト、コメント、DOCTYPEなどが含まれます。
  2. ツリー構築 (Tree Construction): トークン化されたトークンストリームを基に、DOMツリーを構築します。この段階では、パーサーは現在の状態(挿入モード)に基づいて、受け取ったトークンをどのように処理するかを決定します。

挿入モード (Insertion Modes)

ツリー構築段階では、パーサーは常に特定の「挿入モード」にあります。挿入モードは、現在のDOMツリーのコンテキストに基づいて、次のトークンをどのように処理するかを決定します。HTML5の仕様には、多くの挿入モードが定義されており、それぞれが特定のHTML要素の内部でのトークン処理を扱います。例えば、inBodyモードは<body>要素の内部、inTableモードは<table>要素の内部での処理を定義します。

"in row" 挿入モード (inRowIM)

inRowIMは、パーサーが<tr>(テーブル行)要素の内部にいるときにアクティブになる挿入モードです。このモードの主な目的は、<td>(テーブルデータセル)や<th>(テーブルヘッダーセル)といったテーブルセル要素を正しく解析し、DOMツリーに挿入することです。

inRowIMにおける重要な挙動は以下の通りです。

  • <tr>開始タグでの遷移: パーサーが<tr>開始タグトークンを処理すると、このモードに遷移します。
  • <td>または<th>開始タグでの遷移: inRowIM中に<td>または<th>開始タグトークンを検出すると、パーサーは「in cell」挿入モードに遷移し、個々のセルコンテンツの解析を開始します。
  • </tr>終了タグの処理: </tr>終了タグが検出され、スタック上のオープン要素に<tr>要素が存在する場合、パーサーはテーブルスコープ内の<tr>コンテキストまでスタックをクリアします。
  • 暗黙的な</tr>の生成: HTML5の仕様では、特定の状況下で</tr>終了タグが明示的に存在しなくても、パーサーが暗黙的に</tr>を生成し、現在の行を閉じる場合があります。これは、不正なマークアップを許容し、ブラウザの互換性を高めるためのエラー回復メカニズムの一部です。

技術的詳細

このコミットは、src/pkg/exp/html/parse.goファイル内のinRowIM関数のロジックを修正しています。inRowIM関数は、HTMLパーサーが「in row」挿入モードにあるときに、次のトークンをどのように処理するかを決定します。

変更の核心は、switch p.tok.Type文と、その内部のswitch p.tok.Data文のケースの調整にあります。

削除されたケース

以前の実装では、以下の冗長なケースが存在しました。

  • ErrorToken: エラーが発生した場合の処理。
  • TextToken: テキストノードの処理。
  • StartTagTokendefaultケース: 未知の開始タグの処理。
  • EndTagTokendefaultケース: 未知の終了タグの処理。
  • CommentToken: コメントノードの処理。

これらのケースは、多くの場合、単にinTableIM(p)(「in table」挿入モードでの処理)にフォールバックするか、トークンを無視するだけの「TODO」コメントが付いていました。HTML5のパースアルゴリズムでは、特定の挿入モードで明示的に処理されないトークンは、通常、より一般的な挿入モード(この場合はinTableIM)に処理が委譲されるか、無視されることが期待されます。したがって、これらの明示的なケースは冗長であり、削除されました。これにより、コードが簡潔になり、より仕様に沿った動作になります。

追加されたケースとロジック

最も重要な変更は、EndTagTokenswitch p.tok.Data内で、"tbody", "tfoot", "thead"のケースが追加されたことです。

HTML5の仕様では、<tr>要素の内部で</tbody></tfoot></thead>のようなテーブルセクショングループの終了タグが検出された場合、パーサーは現在の<tr>要素を暗黙的に閉じる必要があります。

新しいロジックは以下の通りです。

		case "tbody", "tfoot", "thead":
			if p.elementInScope(tableScope, p.tok.Data) {
				p.parseImpliedToken(EndTagToken, "tr", nil)
				return false
			}
			// Ignore the token.
			return true

このコードブロックは、以下のステップを実行します。

  1. p.elementInScope(tableScope, p.tok.Data): 現在のトークン(tbodytfoottheadのいずれか)がtableScope(テーブルスコープ)内に存在するかどうかを確認します。tableScopeは、テーブル関連の要素が期待されるスコープを指します。このチェックは、終了タグが有効なコンテキストで出現したことを確認するために重要です。
  2. p.parseImpliedToken(EndTagToken, "tr", nil): もしトークンがスコープ内に存在する場合、パーサーは暗黙的に</tr>終了タグを生成し、処理します。これは、現在の<tr>要素を閉じることを意味します。parseImpliedTokenは、実際の入力ストリームには存在しないが、仕様に基づいてパーサーが生成すべきトークンを処理するための内部ヘルパー関数です。
  3. return false: </tr>が暗黙的に処理された後、パーサーは現在の挿入モード(inRowIM)から抜け出し、適切な次の挿入モードに遷移する必要があります。return falseは、現在のトークンが完全に処理され、パーサーが次のトークンを処理する前に挿入モードを変更する必要があることを示唆しています。
  4. // Ignore the token. return true: もしp.elementInScopefalseを返した場合(つまり、tbodytfoottheadの終了タグが現在のテーブルスコープ内に存在しない場合)、そのトークンは無視され、パーサーは引き続きinRowIMに留まります。

この変更により、HTML5の仕様に厳密に準拠したテーブル構造の解析が可能になり、特に不正なマークアップに対するパーサーの回復能力が向上します。

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

src/pkg/exp/html/parse.goファイルのinRowIM関数内での変更です。

--- a/src/pkg/exp/html/parse.go
+++ b/src/pkg/exp/html/parse.go
@@ -1401,10 +1401,6 @@ func inTableBodyIM(p *parser) bool {
 // Section 12.2.5.4.14.
 func inRowIM(p *parser) bool {
 	switch p.tok.Type {
-	case ErrorToken:
-		// TODO.
-	case TextToken:
-		// TODO.
 	case StartTagToken:
 		switch p.tok.Data {
 		case "td", "th":
@@ -1420,8 +1416,6 @@ func inRowIM(p *parser) bool {
 			// Ignore the token.
 			return true
-		default:
-			// TODO.
 		}
 	case EndTagToken:
 		switch p.tok.Data {
@@ -1440,20 +1434,18 @@ func inRowIM(p *parser) bool {
 			// Ignore the token.
 			return true
 		case "tbody", "tfoot", "thead":
-			// TODO.
+			if p.elementInScope(tableScope, p.tok.Data) {
+				p.parseImpliedToken(EndTagToken, "tr", nil)
+				return false
+			}
+			// Ignore the token.
+			return true
 		case "body", "caption", "col", "colgroup", "html", "td", "th":
 			// Ignore the token.
 			return true
-		default:
-			// TODO.
 		}
-	case CommentToken:
-		p.addChild(&Node{
-			Type: CommentNode,
-			Data: p.tok.Data,
-		})
-		return true
 	}
+
 	return inTableIM(p)
 }

コアとなるコードの解説

変更されたinRowIM関数は、HTMLパーサーの現在のトークン(p.tok)のタイプに基づいて処理を分岐します。

  1. 冗長なケースの削除:

    • ErrorTokenTextTokenCommentTokencaseブロックが削除されました。これらのトークンは、inRowIMで特別な処理を必要とせず、通常はより上位の挿入モード(inTableIMなど)に処理が委譲されるか、無視されるためです。
    • StartTagTokenEndTagTokenの内部switch文にあったdefaultケースも削除されました。これも同様に、明示的に処理されないタグは、より一般的なルールに従って処理されるべきであるためです。
  2. EndTagToken"tbody", "tfoot", "thead"ケースの追加:

    • この部分がこのコミットの主要な機能追加です。
    • p.tok.Data"tbody""tfoot"、または"thead"のいずれかであるEndTagTokenが検出された場合、以下のロジックが実行されます。
      • if p.elementInScope(tableScope, p.tok.Data): これは、現在処理している終了タグ(例: </tbody>)が、テーブル関連の要素が期待されるスコープ(tableScope)内に存在するかどうかを確認します。このチェックは、HTML5の仕様で定義されている「要素が特定のスコープにあるか」という概念を実装しています。これにより、終了タグが文脈的に正しい場所にある場合にのみ、特定の動作がトリガーされます。
      • p.parseImpliedToken(EndTagToken, "tr", nil): もしelementInScopetrueを返した場合、パーサーは暗黙的に</tr>終了タグを生成し、処理します。これは、HTML5の仕様で、<tr>要素の内部で<tbody><tfoot><thead>の終了タグが検出された場合、現在の<tr>要素を閉じる必要があると規定されているためです。parseImpliedTokenは、入力ストリームには存在しないが、仕様に基づいてパーサーが「推測」して生成すべきトークンを処理するための内部関数です。
      • return false: 暗黙的な</tr>が処理された後、パーサーは現在の挿入モード(inRowIM)から抜け出す必要があります。return falseは、パーサーが次のトークンを処理する前に挿入モードを変更する必要があることを示します。
      • // Ignore the token. return true: もしelementInScopefalseを返した場合(つまり、終了タグが現在のテーブルスコープ内に存在しない場合)、そのトークンは無視され、パーサーは引き続きinRowIMに留まります。return trueは、現在のトークンが処理され、パーサーが同じ挿入モードで次のトークンを処理できることを示します。

この変更により、inRowIMはHTML5の仕様にさらに厳密に準拠し、特にテーブル構造の解析におけるエラー回復と正確性が向上します。

関連リンク

参考にした情報源リンク