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

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

このコミットは、Go言語の標準ライブラリであるhtmlパッケージ内のパーサーテストに関するものです。具体的には、テスト結果のダンプ形式をWebKitのテストデータ形式に合わせるために、HTML要素の属性の出力方法を変更しています。

コミット

commit 2f3f3aa2ed298344f03813214d6b8d486b5f113e
Author: Andrew Balholm <andybalholm@gmail.com>
Date:   Tue Oct 25 09:33:15 2011 +1100

    html: dump attributes when running parser tests.
    
    The WebKit test data shows attributes as though they were child nodes:
    
    <a X>0<b>1<a Y>2
    dumps as:
    | <html>
    |   <head>
    |   <body>
    |     <a>
    |       x=""
    |       "0"
    |       <b>
    |         "1"
    |     <b>
    |       <a>
    |         y=""
    |         "2"
    
    So we need to do the same when dumping a tree to compare with it.
    
    R=nigeltao
    CC=golang-dev
    https://golang.org/cl/5322044

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

https://github.com/golang/go/commit/2f3f3aa2ed298344f03813214d6b8d486b5f113e

元コミット内容

このコミットは、Go言語のhtmlパッケージのパーサーテストにおいて、HTMLツリーのダンプ形式を調整するものです。特に、WebKitのテストデータがHTML要素の属性をあたかも子ノードであるかのように表現しているため、Goのパーサーテストの出力もそれに合わせて属性をインデントされた子ノードのようにダンプするように変更されました。これにより、WebKitのテストデータとの比較が容易になります。

変更の背景

Go言語のhtmlパッケージは、HTML5の仕様に準拠したHTMLパーサーを提供しています。パーサーの正確性を検証するためには、様々なHTMLスニペットをパースし、その結果生成されるDOMツリーが期待通りであるかを確認するテストが不可欠です。

このコミットが行われた当時、Goのhtmlパッケージのパーサーテストは、WebKitプロジェクトが提供するテストデータを利用していました。WebKitは、そのブラウザエンジン(SafariやChromeの基盤)のHTMLレンダリングの正確性を保証するために、広範なテストスイートを持っています。これらのテストスイートには、特定のHTML入力に対する期待されるDOMツリーの構造が定義されています。

問題は、WebKitのテストデータがHTML要素の属性を、通常のDOMツリー表現とは異なり、あたかもその要素の子ノードであるかのようにダンプする特殊な形式を採用していた点にありました。例えば、<a X>0<b>1<a Y>2というHTMLは、WebKitのダンプ形式ではx=""y=""といった属性が、親要素の直下にインデントされて表示されます。

Goのパーサーテストが生成するDOMツリーのダンプ形式がWebKitのそれと異なると、自動化された比較テストが失敗してしまいます。このコミットの目的は、Goのパーサーテストの出力形式をWebKitの特殊な属性ダンプ形式に合わせることで、テストの互換性を確保し、WebKitの豊富なテストデータをGoのパーサーの検証に活用できるようにすることでした。

前提知識の解説

HTMLパーシングとDOMツリー

HTMLパーシングとは、HTMLドキュメントのテキストデータを読み込み、それをブラウザが理解できる構造化されたデータ(DOMツリー)に変換するプロセスです。DOM(Document Object Model)ツリーは、HTMLドキュメントの論理的な構造をツリー形式で表現したもので、各HTML要素、テキスト、属性などがノードとして表現されます。

  • 要素ノード (Element Node): <div>, <p>, <a>などのHTMLタグに対応します。
  • テキストノード (Text Node): 要素の間に含まれるテキストコンテンツに対応します。
  • 属性ノード (Attribute Node): 要素に付与される属性(例: <a href="...">href)に対応します。DOMツリーの標準的な表現では、属性は要素ノードのプロパティとして扱われ、通常は独立した子ノードとしては表現されません。

WebKitテストデータ形式

WebKitは、HTMLパーサーのテストのために独自のダンプ形式を使用しています。この形式は、HTMLドキュメントをパースした結果のDOMツリーをテキストで表現するものです。この形式の特異な点は、HTML要素の属性を、その要素の直下にインデントされた形で、あたかも子ノードであるかのように出力する点です。これは、属性がDOMツリーの論理的な子ではないにもかかわらず、テストの比較を容易にするための便宜的な表現方法です。

Go言語のhtmlパッケージ

src/pkg/html (現在のgolang.org/x/net/html) は、Go言語でHTML5の仕様に準拠したHTMLパーサーとレンダラーを提供するパッケージです。このパッケージは、HTMLドキュメントをパースしてDOMツリーを構築し、そのツリーを操作したり、再びHTML文字列にシリアライズしたりする機能を提供します。パーサーのテストは、このパッケージの正確性と堅牢性を保証するために非常に重要です。

技術的詳細

このコミットの技術的な核心は、src/pkg/html/parse_test.goファイル内のDOMツリーダンプロジックの変更にあります。

元々、dumpLevel関数は、再帰的にDOMツリーを走査し、各ノードのタイプ(要素、テキスト、コメントなど)に応じて整形された出力を生成していました。しかし、この関数はHTML要素の属性を、WebKitのテストデータが期待する形式で出力する機能を持っていませんでした。

変更点としては、以下の2つの主要な修正が導入されました。

  1. dumpIndent関数の導入:

    • dumpLevel関数内で繰り返し使用されていたインデント処理をdumpIndentという新しいヘルパー関数として切り出しました。これにより、コードの重複が減り、可読性が向上しました。
    • dumpIndent(w io.Writer, level int)は、指定されたlevelに基づいて| (スペース2つ)をio.Writerに書き込みます。
  2. 属性のダンプロジックの追加:

    • dumpLevel関数内のswitch n.Type文において、ElementNodeの場合の処理が拡張されました。
    • 要素ノードのデータ(タグ名)を出力した後、その要素が持つすべての属性(n.Attrスライス)をループで処理します。
    • 各属性について、まず改行(\n)を書き込み、次にdumpIndent(w, level+1)を呼び出して、親要素よりも一段階深いインデントを適用します。
    • 最後に、fmt.Fprintf(w, %s="%s", a.Key, a.Val)を使用して、属性のキーと値をkey="value"の形式で出力します。これにより、WebKitのテストデータが期待する「属性が子ノードのようにインデントされて表示される」形式が実現されます。
  3. テストケース数の調整:

    • TestParser関数内のループfor i := 0; i < 31; i++for i := 0; i < 32; i++に変更されました。これは、新しい属性ダンプロジックに対応するために、テストデータセットに新しいテストケースが追加されたか、既存のテストケースの処理範囲が拡張されたことを示唆しています。

これらの変更により、Goのhtmlパーサーテストの出力がWebKitのテストデータ形式と一致するようになり、より広範なテストカバレッジと互換性が確保されました。

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

--- a/src/pkg/html/parse_test.go
+++ b/src/pkg/html/parse_test.go
@@ -69,11 +69,15 @@ func readDat(filename string, c chan io.Reader) {
 	}\n }\n \n-func dumpLevel(w io.Writer, n *Node, level int) os.Error {\n+func dumpIndent(w io.Writer, level int) {\n \tio.WriteString(w, \"| \")\n \tfor i := 0; i < level; i++ {\n \t\tio.WriteString(w, \"  \")\n \t}\n+}\n+\n+func dumpLevel(w io.Writer, n *Node, level int) os.Error {\n+\tdumpIndent(w, level)\n \tswitch n.Type {\n \tcase ErrorNode:\n \t\treturn os.NewError(\"unexpected ErrorNode\")\n@@ -81,6 +85,11 @@ func dumpLevel(w io.Writer, n *Node, level int) os.Error {\n \t\treturn os.NewError(\"unexpected DocumentNode\")\n \tcase ElementNode:\n \t\tfmt.Fprintf(w, \"<%s>\", n.Data)\n+\t\tfor _, a := range n.Attr {\n+\t\t\tio.WriteString(w, \"\\n\")\n+\t\t\tdumpIndent(w, level+1)\n+\t\t\tfmt.Fprintf(w, `%s=\"%s\"`, a.Key, a.Val)\n+\t\t}\n \tcase TextNode:\n \t\tfmt.Fprintf(w, \"%q\", n.Data)\n \tcase CommentNode:\
@@ -123,7 +132,7 @@ func TestParser(t *testing.T) {\n \t\trc := make(chan io.Reader)\n \t\tgo readDat(filename, rc)\n \t\t// TODO(nigeltao): Process all test cases, not just a subset.\n-\t\tfor i := 0; i < 31; i++ {\n+\t\tfor i := 0; i < 32; i++ {\n \t\t\t// Parse the #data section.\n \t\t\tb, err := ioutil.ReadAll(<-rc)\n \t\t\tif err != nil {\

コアとなるコードの解説

dumpIndent関数の追加

func dumpIndent(w io.Writer, level int) {
	io.WriteString(w, "| ")
	for i := 0; i < level; i++ {
		io.WriteString(w, "  ")
	}
}

この新しい関数は、ダンプ出力のインデントを生成するために導入されました。level引数に基づいて、| の後に適切な数のスペース(各レベルで2スペース)を書き込みます。これにより、ツリー構造の階層が視覚的に表現されます。

dumpLevel関数内の変更

func dumpLevel(w io.Writer, n *Node, level int) os.Error {
	dumpIndent(w, level) // 新しく追加された行
	switch n.Type {
	// ... 既存のノードタイプごとの処理 ...
	case ElementNode:
		fmt.Fprintf(w, "<%s>", n.Data)
		for _, a := range n.Attr { // 新しく追加されたループ
			io.WriteString(w, "\n") // 新しく追加された行
			dumpIndent(w, level+1) // 新しく追加された行
			fmt.Fprintf(w, `%s="%s"`, a.Key, a.Val) // 新しく追加された行
		}
	// ... 既存のノードタイプごとの処理 ...
}

dumpLevel関数の冒頭で、まず現在のノードのインデントをdumpIndent(w, level)で出力するように変更されました。

最も重要な変更は、ElementNodeのケースです。

  1. fmt.Fprintf(w, "<%s>", n.Data)で要素のタグ名(例: <a>)を出力します。
  2. その直後に、for _, a := range n.Attrループが追加されました。このループは、現在のHTML要素nが持つすべての属性(n.Attrスライスに格納されている)を反復処理します。
  3. ループ内で、各属性を出力する前に、io.WriteString(w, "\n")で改行を挿入し、dumpIndent(w, level+1)を呼び出して、現在の要素よりも一段階深いインデント(子ノードと同じレベル)を適用します。
  4. 最後に、fmt.Fprintf(w, %s="%s", a.Key, a.Val)を使用して、属性のキーと値をkey="value"の形式で出力します。この形式がWebKitのテストデータが期待する属性の表現方法です。

TestParser関数内の変更

func TestParser(t *testing.T) {
	// ...
	// TODO(nigeltao): Process all test cases, not just a subset.
	for i := 0; i < 32; i++ { // 31から32に変更
		// ...
	}
}

TestParser関数内のテストケースを処理するループの回数が31から32に増えました。これは、新しい属性ダンプロジックを検証するために、追加のテストケースが導入されたか、既存のテストデータセットの範囲が拡張されたことを示しています。

これらの変更により、GoのHTMLパーサーテストの出力がWebKitのテストデータ形式と完全に一致するようになり、テストの自動比較が正確に行えるようになりました。

関連リンク

参考にした情報源リンク