[インデックス 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つの主要な修正が導入されました。
-
dumpIndent
関数の導入:dumpLevel
関数内で繰り返し使用されていたインデント処理をdumpIndent
という新しいヘルパー関数として切り出しました。これにより、コードの重複が減り、可読性が向上しました。dumpIndent(w io.Writer, level int)
は、指定されたlevel
に基づいて|
とio.Writer
に書き込みます。
-
属性のダンプロジックの追加:
dumpLevel
関数内のswitch n.Type
文において、ElementNode
の場合の処理が拡張されました。- 要素ノードのデータ(タグ名)を出力した後、その要素が持つすべての属性(
n.Attr
スライス)をループで処理します。 - 各属性について、まず改行(
\n
)を書き込み、次にdumpIndent(w, level+1)
を呼び出して、親要素よりも一段階深いインデントを適用します。 - 最後に、
fmt.Fprintf(w,
%s="%s", a.Key, a.Val)
を使用して、属性のキーと値をkey="value"
の形式で出力します。これにより、WebKitのテストデータが期待する「属性が子ノードのようにインデントされて表示される」形式が実現されます。
-
テストケース数の調整:
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
のケースです。
fmt.Fprintf(w, "<%s>", n.Data)
で要素のタグ名(例:<a>
)を出力します。- その直後に、
for _, a := range n.Attr
ループが追加されました。このループは、現在のHTML要素n
が持つすべての属性(n.Attr
スライスに格納されている)を反復処理します。 - ループ内で、各属性を出力する前に、
io.WriteString(w, "\n")
で改行を挿入し、dumpIndent(w, level+1)
を呼び出して、現在の要素よりも一段階深いインデント(子ノードと同じレベル)を適用します。 - 最後に、
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のテストデータ形式と完全に一致するようになり、テストの自動比較が正確に行えるようになりました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/2f3f3aa2ed298344f03813214d6b8d486b5f113e
- Gerrit Code Review (元の変更リスト): https://golang.org/cl/5322044
参考にした情報源リンク
- Go言語
html
パッケージ (現在の場所): https://pkg.go.dev/golang.org/x/net/html - WebKitプロジェクト: https://webkit.org/
- HTML5仕様: https://html.spec.whatwg.org/multipage/
- Document Object Model (DOM): https://developer.mozilla.org/ja/docs/Web/API/Document_Object_Model
- Go言語の
io
パッケージ: https://pkg.go.dev/io - Go言語の
fmt
パッケージ: https://pkg.go.dev/fmt - Go言語の
os
パッケージ: https://pkg.go.dev/os - Go言語の
ioutil
パッケージ (非推奨、io
とos
に移行): https://pkg.go.dev/io/ioutil (コミット当時のコードで使用されているため記載)