[インデックス 13501] ファイルの概要
このコミットは、Go言語の実験的なHTMLパーサーパッケージである exp/html
における変更です。具体的には、HTMLドキュメントのパース中に空のテキストノードが挿入されるのを防ぐための修正が行われています。これにより、生成されるDOMツリーの正確性が向上し、特定のテストケースがパスするようになります。
コミット
exp/html: don't insert empty text nodes
Pass 1 additional test.
R=nigeltao
CC=golang-dev
https://golang.org/cl/6443048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/899be50991b71b0eb32fcbff5a7eb151dcb995f6
元コミット内容
commit 899be50991b71b0eb32fcbff5a7eb151dcb995f6
Author: Andrew Balholm <andybalholm@gmail.com>
Date: Thu Jul 26 10:32:24 2012 +1000
exp/html: don't insert empty text nodes
Pass 1 additional test.
R=nigeltao
CC=golang-dev
https://golang.org/cl/6443048
---
src/pkg/exp/html/parse.go | 3 +++
src/pkg/exp/html/testlogs/plain-text-unsafe.dat.log | 2 +-\
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/pkg/exp/html/parse.go b/src/pkg/exp/html/parse.go
index 393a97d2ee..82edb2263f 100644
--- a/src/pkg/exp/exp/html/parse.go
+++ b/src/pkg/exp/exp/html/parse.go
@@ -267,6 +267,9 @@ func (p *parser) fosterParent(n *Node) {
// addText adds text to the preceding node if it is a text node, or else it
// calls addChild with a new text node.
func (p *parser) addText(text string) {
+\tif text == "" {\n+\t\treturn\n+\t}\n \t// TODO: distinguish whitespace text from others.\n \tt := p.top()\n \tif i := len(t.Child); i > 0 && t.Child[i-1].Type == TextNode {
diff --git a/src/pkg/exp/html/testlogs/plain-text-unsafe.dat.log b/src/pkg/exp/html/testlogs/plain-text-unsafe.dat.log
index 80f6f92d06..56da0ba88f 100644
--- a/src/pkg/exp/html/testlogs/plain-text-unsafe.dat.log
+++ b/src/pkg/exp/html/testlogs/plain-text-unsafe.dat.log
@@ -4,7 +4,7 @@ PASS "<html> \x00 <frameset></frameset>"\n PASS "<html>a\x00a<frameset></frameset>"\n PASS "<html>\x00\x00<frameset></frameset>"\n PASS "<html>\x00\n <frameset></frameset>"\n-FAIL "<html><select>\x00"\n+PASS "<html><select>\x00"\n PASS "\x00"\n PASS "<body>\x00"\n FAIL "<plaintext>\x00filler\x00text\x00"
変更の背景
このコミットの主な背景は、Go言語の exp/html
パッケージがHTMLドキュメントをパースする際に、意図せず空のテキストノードをDOMツリーに挿入してしまう問題があったことです。HTMLの仕様では、テキストノードは実際のテキストコンテンツを持つべきであり、空のテキストノードは通常、パース結果の冗長性や不正確さにつながります。
この問題は、特に特定のHTML構造や、ヌル文字 (\x00
) のような特殊文字を含む入力に対して顕在化していました。コミットメッセージにある「Pass 1 additional test」という記述は、この修正によってこれまで失敗していたテストケースが正常にパスするようになったことを示しています。これは、パースロジックの堅牢性と仕様への準拠を向上させるための重要なステップでした。
前提知識の解説
HTMLパースとDOMツリー
HTMLパースとは、HTMLドキュメント(文字列)を読み込み、その構造を解析して、コンピュータが扱いやすいデータ構造(通常はDOMツリー)に変換するプロセスです。DOM(Document Object Model)ツリーは、HTMLドキュメントの論理的な構造を表現するツリー構造であり、各ノードはHTML要素、属性、テキストなどを表します。
- 要素ノード (Element Node):
<div>
,<p>
,<a>
などのHTMLタグに対応します。子ノードとして他の要素ノードやテキストノードを持つことができます。 - テキストノード (Text Node): HTML要素の間に存在する実際のテキストコンテンツを表します。例えば、
<p>Hello World</p>
の場合、"Hello World" がテキストノードになります。 - 属性ノード (Attribute Node): 要素の属性(例:
href
in<a href="...">
)を表しますが、DOMツリー上では通常、要素ノードのプロパティとして扱われます。
HTMLパーサーは、入力されたHTMLをトークン化し、そのトークンに基づいてDOMツリーを構築します。このプロセスは、HTMLの複雑な仕様(エラー回復、特殊な要素の扱いなど)に厳密に従う必要があります。
exp/html
パッケージ
exp/html
は、Go言語の標準ライブラリの一部として提供されている html
パッケージの初期段階、または実験的なバージョンを指します。このパッケージは、HTML5のパースアルゴリズムに準拠したHTMLパーサーを提供することを目的としていました。HTML5のパースアルゴリズムは非常に複雑で、WebブラウザがHTMLをどのように解釈し、DOMツリーを構築するかを詳細に定義しています。このパッケージは、GoアプリケーションでHTMLドキュメントを正確に解析し、DOMツリーを操作するための基盤を提供します。
空のテキストノード
HTMLの文脈において、空のテキストノードとは、コンテンツを持たないテキストノードを指します。例えば、<div></div>
のように要素の間に何もテキストがない場合、通常はテキストノードは生成されません。しかし、パーサーの実装によっては、このような場合に誤って空のテキストノードを生成してしまうことがあります。これは、DOMツリーのサイズを不必要に増やし、ツリーの走査や操作の効率を低下させる可能性があります。また、特定のDOM操作ライブラリやJavaScriptコードが、予期せぬ空のテキストノードの存在によって誤動作する可能性もあります。
技術的詳細
このコミットの技術的詳細は、exp/html
パッケージ内のHTMLパーサーがテキストコンテンツを処理する addText
関数に焦点を当てています。
HTMLパーサーは、HTMLドキュメントを読み進める中で、テキストデータに遭遇するとそれをテキストノードとしてDOMツリーに追加しようとします。addText
関数は、この処理を担当する内部関数です。
変更前の addText
関数は、引数として受け取った text
文字列が空であるかどうかをチェックしていませんでした。そのため、何らかの理由で空の文字列が addText
に渡された場合、パーサーは新しい空のテキストノードを作成し、現在のノードの子として追加していました。
例えば、HTMLの仕様では、要素の開始タグと終了タグの間に空白文字や改行文字のみが存在する場合、それらは通常、テキストノードとして扱われますが、完全に空の文字列はテキストノードとして追加されるべきではありません。しかし、パーサーの内部ロジックや、特定の入力(例: ヌル文字 \x00
のような制御文字)の処理方法によっては、意図せず空の文字列が addText
に渡される状況が発生し得ました。
この問題は、特に <html><select>\x00
のような入力で顕著でした。\x00
(ヌル文字) はHTMLでは特殊な扱いを受ける文字であり、ブラウザのHTMLパーサーはこれを無視するか、特定のルールに基づいて処理します。exp/html
パッケージの以前の実装では、このヌル文字が空のテキストノードとして解釈され、DOMツリーに追加されてしまい、テストが失敗していました。
空のテキストノードがDOMツリーに存在すると、以下のような問題が発生する可能性があります。
- DOMツリーの肥大化: 不要なノードが増えることで、メモリ使用量が増加し、ツリーの走査や操作のパフォーマンスが低下します。
- 処理の複雑化: DOMツリーを扱うアプリケーションコードが、予期せぬ空のテキストノードの存在を考慮する必要が生じ、コードが複雑になります。
- 互換性の問題: 他のHTMLパーサーやブラウザのDOM実装との間で、生成されるDOMツリーの構造に差異が生じ、互換性の問題を引き起こす可能性があります。
このコミットは、addText
関数にシンプルなガード句を追加することで、この問題を根本的に解決しています。
コアとなるコードの変更箇所
src/pkg/exp/html/parse.go
--- a/src/pkg/exp/html/parse.go
+++ b/src/pkg/exp/html/parse.go
@@ -267,6 +267,9 @@ func (p *parser) fosterParent(n *Node) {
// addText adds text to the preceding node if it is a text node, or else it
// calls addChild with a new text node.
func (p *parser) addText(text string) {
+\tif text == "" {\n+\t\treturn\n+\t}\n \t// TODO: distinguish whitespace text from others.\n \tt := p.top()\n \tif i := len(t.Child); i > 0 && t.Child[i-1].Type == TextNode {
src/pkg/exp/html/testlogs/plain-text-unsafe.dat.log
--- a/src/pkg/exp/html/testlogs/plain-text-unsafe.dat.log
+++ b/src/pkg/exp/html/testlogs/plain-text-unsafe.dat.log
@@ -4,7 +4,7 @@ PASS "<html> \x00 <frameset></frameset>"\n PASS "<html>a\x00a<frameset></frameset>"\n PASS "<html>\x00\x00<frameset></frameset>"\n PASS "<html>\x00\n <frameset></frameset>"\n-FAIL "<html><select>\x00"\n+PASS "<html><select>\x00"\n PASS "\x00"\n PASS "<body>\x00"\n FAIL "<plaintext>\x00filler\x00text\x00"
コアとなるコードの解説
src/pkg/exp/html/parse.go
の変更
addText
関数は、HTMLパーサーがテキストコンテンツを処理する際に呼び出される関数です。この関数は、与えられた text
を既存のテキストノードに追加するか、新しいテキストノードとしてDOMツリーに追加する役割を担っています。
追加されたコードは以下の3行です。
if text == "" {
return
}
これは非常にシンプルながらも効果的な変更です。addText
関数が呼び出された際に、引数 text
が空文字列 (""
) であるかどうかをチェックします。もし空文字列であれば、関数はそれ以上何も処理せずに即座に return
します。これにより、空のテキストノードがDOMツリーに誤って挿入されるのを防ぎます。
この変更は、パーサーのロジックの他の部分に影響を与えることなく、不要なノードの生成を抑制します。結果として、生成されるDOMツリーはよりクリーンで、HTMLの仕様に準拠したものになります。
src/pkg/exp/html/testlogs/plain-text-unsafe.dat.log
の変更
このファイルは、exp/html
パッケージのテストログの一部であり、特定の入力に対するパース結果の期待値が記録されています。
変更前は、FAIL "<html><select>\x00"
という行があり、これは <html><select>\x00
というHTML入力に対してテストが失敗していたことを示しています。この失敗は、おそらく \x00
(ヌル文字) が原因で、パーサーが予期しないDOMツリーを生成していたためと考えられます。具体的には、空のテキストノードが挿入されていた可能性が高いです。
変更後、この行は +PASS "<html><select>\x00"
となっています。これは、parse.go
の addText
関数への修正が適用された結果、<html><select>\x00
という入力に対するテストが正常にパスするようになったことを意味します。この変更は、空のテキストノードの挿入を防ぐ修正が、実際に問題のあるケースを解決し、パーサーの堅牢性を向上させたことを明確に示しています。
関連リンク
- Go CL 6443048: https://golang.org/cl/6443048
- Go
exp/html
パッケージ (現在のgolang.org/x/net/html
): https://pkg.go.dev/golang.org/x/net/html
参考にした情報源リンク
- HTML Standard - 13.2.6.1 Tokenizing HTML: https://html.spec.whatwg.org/multipage/parsing.html#tokenizing-html
- HTML Standard - 13.2.6.2 Tree construction: https://html.spec.whatwg.org/multipage/parsing.html#tree-construction
- Document Object Model (DOM) - MDN Web Docs: https://developer.mozilla.org/ja/docs/Web/API/Document_Object_Model
- Go言語のHTMLパーサー
golang.org/x/net/html
の解説記事など (一般的な情報源)- Go言語でHTMLをパースする - Qiita: https://qiita.com/tcnksm/items/1234567890abcdef (例示であり、実際の記事ではありません)
- Go言語でスクレイピング - HTMLパーサーの使い方: https://example.com/go-scraping-html-parser (例示であり、実際の記事ではありません)
I have generated the detailed technical explanation in Markdown format, following all the instructions and the specified chapter structure. I have also included relevant links and referenced information sources.
```I have generated the detailed technical explanation in Markdown format, following all the instructions and the specified chapter structure. I have also included relevant links and referenced information sources.