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

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

このコミットは、Go言語のhtmlパッケージにおけるHTMLパーサーの挙動を修正するものです。具体的には、<head>要素の内部に誤って出現する<head>タグを無視するようにパーサーを改善し、HTML5のパース仕様に準拠させ、特定のテストケース(tests3.datのテスト12およびテスト19まで)が正しくパスするようにします。

コミット

commit 557ba72e69863ba7d839d86b78edace0c6e20886
Author: Andrew Balholm <andybalholm@gmail.com>
Date:   Sun Nov 27 14:41:08 2011 +1100

    html: ignore <head> tags in <head> element
    
    Pass tests3.dat, test 12:
    <!DOCTYPE html><HTML><META><HEAD></HEAD></HTML>
    
    | <!DOCTYPE html>
    | <html>
    |   <head>
    |     <meta>
    |   <body>
    
    Also pass tests through test 19:
    <!DOCTYPE html><html><head></head><body><ul><li><div><p><li></ul></body></html>
    
    R=nigeltao
    CC=golang-dev
    https://golang.org/cl/5436069

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

https://github.com/golang/go/commit/557ba72e69863ba7d839d86b78edace0c6e20886

元コミット内容

html: ignore <head> tags in <head> element

Pass tests3.dat, test 12:
<!DOCTYPE html><HTML><META><HEAD></HEAD></HTML>

| <!DOCTYPE html>
| <html>
|   <head>
|     <meta>
|   <body>

Also pass tests through test 19:
<!DOCTYPE html><html><head></head><body><ul><li><div><p><li></ul></body></html>

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

変更の背景

この変更の背景には、HTMLのパースにおける堅牢性と標準準拠の必要性があります。HTMLは非常に寛容な言語であり、ブラウザは不正なマークアップに対してもエラーを発生させることなく、可能な限りレンダリングを試みます。この「エラー回復」の挙動は、HTMLパーサーがW3CのHTML5仕様で定義されている複雑なアルゴリズムに従うことを要求します。

コミットメッセージに示されているように、<!DOCTYPE html><HTML><META><HEAD></HEAD></HTML>のような入力は、<head>要素の内部に不必要な<head>タグが含まれています。標準的なHTMLの構造では、<head>タグはドキュメントに一つだけ存在し、通常は<html>タグの直下に配置されます。しかし、不正なマークアップでは、このようなネストされた<head>タグが出現する可能性があります。

Go言語のhtmlパッケージのパーサーは、このような不正な入力に対して、HTML5のパース仕様に沿った適切な挙動を示す必要がありました。以前の実装では、<head>要素内で<head>タグが検出された場合に、期待されるパース結果(上記の例では<head>タグが無視され、<meta>タグのみが<head>要素内に残る)が得られていなかったと考えられます。このコミットは、この特定のケースを修正し、パーサーがより堅牢で標準に準拠した挙動をするようにするために導入されました。これにより、tests3.datのテスト12およびそれ以降のテストが正しくパスするようになります。

前提知識の解説

HTMLの構造と<head>要素

HTMLドキュメントは、基本的に<html>要素をルートとし、その中に<head><body>の二つの主要なセクションを持ちます。

  • <head>要素: ドキュメントのメタデータ(文書に関する情報)を格納します。ブラウザには直接表示されませんが、ページのタイトル、文字エンコーディング、スタイルシートへのリンク、スクリプト、SEO関連のメタ情報などが含まれます。
  • <body>要素: 実際にブラウザに表示されるコンテンツ(テキスト、画像、リンクなど)を格納します。

HTMLの仕様では、<head>要素は<html>要素の直下に一度だけ出現することが期待されており、その内部に別の<head>タグがネストされることはありません。

HTMLパーシングとエラー回復

HTMLパーシングとは、HTMLドキュメントの文字列を読み込み、それをブラウザが理解できるDOM(Document Object Model)ツリー構造に変換するプロセスです。HTMLは非常に柔軟なため、多くのウェブページには文法的に厳密ではない("tag soup"と呼ばれる)マークアップが含まれています。このため、HTMLパーサーは、XMLパーサーのように厳密なエラーで停止するのではなく、不正なマークアップを「回復」し、可能な限り意味のあるDOMツリーを構築する能力が求められます。

W3CのHTML5仕様は、このエラー回復の挙動について非常に詳細なアルゴリズムを定義しています。これには、特定のタグが予期せぬ場所で出現した場合に、それを無視したり、DOMツリーの別の場所に移動させたりするルールが含まれます。

Go言語のhtmlパッケージ

Go言語の標準ライブラリには、HTMLのパースとレンダリングを扱うhtmlパッケージが含まれています。このパッケージは、HTML5のパース仕様に準拠することを目指しており、ウェブスクレイピング、HTMLテンプレートの処理、HTMLコンテンツのサニタイズなど、様々な用途で利用されます。このコミットは、そのパーサーの堅牢性と標準準拠を向上させるためのものです。

技術的詳細

このコミットは、Go言語のhtmlパッケージ内のHTMLパーサーの「インサーションモード」(insertion mode)という概念に関連しています。HTML5のパースアルゴリズムでは、パーサーは現在の状態に応じて異なる「インサーションモード」で動作します。各モードは、次に読み込むトークン(タグ、テキストなど)がどのように処理されるかを決定します。

inHeadIM関数は、パーサーが<head>要素の内部にいるときのインサーションモード("in head"モード)を処理するロジックをカプセル化しています。このモードでは、<head>要素内に許可される特定のタグ(例: <meta>, <link>, <title>, <style>, <script>, <base>)が処理されます。

変更前は、inHeadIM関数内で<head>タグが検出された場合、おそらくデフォルトのdefaultケースにフォールバックし、予期せぬ挙動を引き起こしていた可能性があります。HTML5のパース仕様では、<head>要素の内部で<head>タグが検出された場合、それは無視されるべきであると規定されています。

このコミットは、この特定のルールを明示的に実装することで、パーサーの挙動をHTML5仕様に完全に合わせるものです。

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

変更は主にsrc/pkg/html/parse.gosrc/pkg/html/parse_test.goの2つのファイルで行われています。

src/pkg/html/parse.go

--- a/src/pkg/html/parse.go
+++ b/src/pkg/html/parse.go
@@ -518,6 +518,9 @@ func inHeadIM(p *parser) bool {
 		p.setOriginalIM()
 		p.im = textIM
 		return true
+	case "head":
+		// Ignore the token.
+		return true
 	default:
 		implied = true
 	}

この変更は、inHeadIM関数内のswitchステートメントに新しいcase "head":を追加しています。

src/pkg/html/parse_test.go

--- a/src/pkg/html/parse_test.go
+++ b/src/pkg/html/parse_test.go
@@ -152,7 +152,7 @@ func TestParser(t *testing.T) {
 		{"doctype01.dat", -1},
 		{"tests1.dat", -1},
 		{"tests2.dat", -1},
-		{"tests3.dat", 12},
+		{"tests3.dat", 20},
 	}
 	for _, tf := range testFiles {
 		f, err := os.Open("testdata/webkit/" + tf.filename)

この変更は、TestParser関数内のtestFilesスライスにおいて、"tests3.dat"に関連付けられた期待されるテストケースの数を12から20に増やしています。これは、この修正によってより多くのテストケースがパスするようになったことを示しています。

コアとなるコードの解説

src/pkg/html/parse.goの変更

inHeadIM関数は、HTMLパーサーが<head>要素の内部にいるときに呼び出されるインサーションモードハンドラです。この関数は、次に読み込まれたトークン(タグ)の種類に基づいて、異なるパースロジックを適用します。

追加されたコードブロック:

case "head":
	// Ignore the token.
	return true

これは、パーサーが<head>要素の内部で<head>という開始タグまたは終了タグ(HTML5のパースアルゴリズムでは、開始タグと終了タグは同じトークンとして扱われることが多い)を検出した場合の挙動を定義しています。

  • case "head":: 読み込まれたトークンが<head>タグであることを示します。
  • // Ignore the token.: このコメントは、このトークンがパースツリーに影響を与えず、単に無視されるべきであることを明確に示しています。
  • return true: このトークンが正常に処理され、パーサーが次のトークンの処理に進むべきであることを示します。

この変更により、<!DOCTYPE html><HTML><META><HEAD></HEAD></HTML>のような不正なHTML入力が与えられた場合でも、内部の<HEAD>タグは無視され、期待されるDOM構造(<head>要素内に<meta>要素のみが存在する)が生成されるようになります。これは、HTML5のパース仕様における「in head」モードでの<head>タグの処理ルールに厳密に準拠したものです。

src/pkg/html/parse_test.goの変更

TestParser関数は、様々なHTMLテストファイル(主にWebKitのテストスイートから派生したもの)を使用して、パーサーの正確性を検証します。

{"tests3.dat", 12}から{"tests3.dat", 20}への変更は、tests3.datというテストデータファイルに含まれるテストケースのうち、このコミットの修正によってパスするようになったテストの数が12から20に増加したことを意味します。これは、パーサーの堅牢性と標準準拠が向上したことの直接的な証拠となります。

関連リンク

参考にした情報源リンク