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

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

このコミットは、Go言語のHTMLパーサーにおける外部コンテンツ(Foreign Content、例えばSVGやMathML)の属性処理を改善するものです。特に、xlink:href のような名前空間プレフィックスを持つ属性の解析と表現を修正し、HTML5仕様に準拠させることを目的としています。これにより、外部コンテンツ内の属性が正しく解釈され、DOMツリーに反映されるようになります。

コミット

commit d5e45e3a8a9b3316f5b8c40804388f695fddb41f
Author: Nigel Tao <nigeltao@golang.org>
Date:   Sun Dec 25 12:42:47 2011 +1100

    html: adjust foreign attributes.
    
    Pass tests10.dat, test 22:
    <!DOCTYPE html><body xlink:href=foo><svg xlink:href=foo></svg>
    
    | <!DOCTYPE html>
    | <html>
    |   <head>
    |   <body>
    |     xlink:href="foo"
    |     <svg svg>
    |       xlink href="foo"
    
    Also pass tests through test 29:
    <div><svg><path></svg><path>
    
    R=andybalholm
    CC=golang-dev
    https://golang.org/cl/5489117

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

https://github.com/golang/go/commit/d5e45e3a8a9b3316f5b8c40804388f695fddb41f

元コミット内容

html: adjust foreign attributes.

Pass tests10.dat, test 22:
<!DOCTYPE html><body xlink:href=foo><svg xlink:href=foo></svg>

| <!DOCTYPE html>
| <html>
|   <head>
|   <body>
|     xlink:href="foo"
|     <svg svg>
|       xlink href="foo"

Also pass tests through test 29:
<div><svg><path></svg><path>

R=andybalholm
CC=golang-dev
https://golang.org/cl/5489117

変更の背景

この変更の主な背景は、HTML5のパース仕様における外部コンテンツ(Foreign Content)の属性処理の正確性を向上させることにあります。特に、SVGやMathMLのようなXML名前空間を使用するコンテンツがHTMLドキュメント内に埋め込まれた場合、それらの要素に付与される属性(例: xlink:href)は、通常のHTML属性とは異なる方法で扱われる必要があります。

以前の実装では、xlink:href のような属性が単一のキーとして扱われていた可能性があります。しかし、HTML5の仕様では、これらの属性は名前空間(xlink)とローカル名(href)に分割して解釈されるべきです。この不正確な処理は、tests10.dat のテスト22のような特定のテストケースで失敗を引き起こしていました。このテストケースは、xlink:href 属性が正しくパースされ、DOMツリーに反映されることを検証するものです。

このコミットは、これらのテストをパスし、HTML5の相互運用性要件を満たすために、外部属性の解析ロジックを調整する必要があるという認識から生まれました。

前提知識の解説

1. HTML5における外部コンテンツ (Foreign Content)

HTML5の仕様では、HTML構文内にXMLベースのコンテンツ(特にSVGとMathML)を直接埋め込むことが許可されています。これを「外部コンテンツ (Foreign Content)」と呼びます。通常のHTML要素とは異なり、これらの外部コンテンツ内の要素や属性は、XMLの名前空間のルールに従って解釈される必要があります。

2. XML名前空間 (XML Namespaces)

XML名前空間は、XMLドキュメント内で要素や属性の名前の衝突を避けるためのメカニズムです。URI(Uniform Resource Identifier)を使用して名前空間を識別し、通常はプレフィックス(例: xlink)を名前の前に付けて使用します(例: xlink:href)。

  • プレフィックス (Prefix): 名前空間URIにマッピングされる短い識別子(例: xlink)。
  • ローカル名 (Local Name): プレフィックスの後に続く実際の名前(例: href)。
  • 名前空間URI (Namespace URI): 名前空間を一意に識別するURI(例: http://www.w3.org/1999/xlink)。

xlink:href の場合、xlink はXLink名前空間のプレフィックスであり、href はその名前空間内のローカル名です。HTMLパーサーは、このような属性を単一の文字列としてではなく、名前空間とローカル名に分解して処理する必要があります。

3. HTML5パーシングアルゴリズムと外部コンテンツ

HTML5のパーシングアルゴリズムは非常に複雑で、特定の状態遷移やトークン処理ルールが定義されています。外部コンテンツをパースする際には、通常のHTMLパースモードから「外部コンテンツモード」に切り替わり、XMLの名前空間ルールが適用されます。このモードでは、属性の名前空間解決が重要になります。

特に、HTML5仕様の「Parsing HTML fragments」や「The rules for parsing tokens in foreign content」のセクションには、外部コンテンツ内の属性処理に関する詳細なルールが記述されています。このコミットは、これらのルール、特に属性の名前空間とローカル名の分離に関する部分を実装していると考えられます。

技術的詳細

このコミットの技術的な核心は、HTMLパーサーが外部コンテンツの属性を、その名前空間とローカル名に正しく分割して内部的に表現する能力を獲得した点にあります。

  1. Attribute 構造体の変更 (src/pkg/html/token.go):

    • 以前は Key, Val string の2つのフィールドしか持たなかった Attribute 構造体に、新たに Namespace string フィールドが追加されました。
    • これにより、xlink:href のような属性は、Namespace="xlink", Key="href", Val="foo" のように、名前空間情報を持つ形で表現できるようになりました。これは、HTML5仕様が要求するDOM表現に近づけるための重要な変更です。
  2. adjustForeignAttributes 関数の導入 (src/pkg/html/foreign.go):

    • この新しい関数は、Attribute のスライスを受け取り、その中の特定の属性(xlink:xml: で始まる属性)を走査します。
    • 属性の Keyx で始まり、かつ xlink:xml: のような既知の名前空間プレフィックスを持つ場合、strings.Index を使用して : の位置を見つけ、プレフィックス部分を Namespace フィールドに、残りの部分を Key フィールドに設定します。
    • これにより、パーサーが受け取った生の属性キー(例: xlink:href)が、名前空間とローカル名に分解され、Attribute 構造体に格納されるようになります。
  3. パーサーでの adjustForeignAttributes の呼び出し (src/pkg/html/parse.go):

    • parse.go 内の要素追加ロジック(inBodyIM 関数と parseForeignContent 関数)において、トークンから属性を取得した後、adjustForeignAttributes(p.tok.Attr) が呼び出されるようになりました。
    • これは、要素がDOMツリーに追加される直前に、その属性がHTML5仕様に沿って「調整」されることを意味します。特に、外部コンテンツモードでパースされる要素(例: <svg>)の属性に対してこの調整が適用されます。
  4. レンダラーでの名前空間付き属性の出力 (src/pkg/html/render.go):

    • render.go は、パースされたDOMツリーをHTML文字列として再構築する役割を担います。
    • 属性をレンダリングする際、a.Namespace != "" であれば、名前空間と : をキーの前に付加して出力するようになりました(例: xlink:href="foo")。
    • これにより、パース時に名前空間情報が正しく抽出された属性が、元の形式で(または仕様に準拠した形式で)出力されることが保証されます。
  5. テストの更新 (src/pkg/html/parse_test.go):

    • dumpLevel 関数が変更され、属性を出力する際に a.Namespace が空でなければ、%s %s="%s" の形式(例: xlink href="foo")で名前空間とキーを別々に出力するようになりました。これは、テストの出力が新しい内部表現と一致するようにするためです。
    • また、tests10.dat のテストケースの範囲が22から30に拡張され、この変更がより多くのテストをパスすることを示しています。

これらの変更により、GoのHTMLパーサーは、外部コンテンツ内の属性をより正確に処理し、HTML5の相互運用性要件を満たすことができるようになりました。

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

このコミットにおけるコアとなるコードの変更箇所は以下の通りです。

  1. src/pkg/html/token.go: Attribute 構造体に Namespace フィールドが追加された点。
    type Attribute struct {
    	Namespace, Key, Val string
    }
    
  2. src/pkg/html/foreign.go: 新しい関数 adjustForeignAttributes の追加。
    func adjustForeignAttributes(aa []Attribute) {
    	for i, a := range aa {
    		if a.Key == "" || a.Key[0] != 'x' {
    			continue
    		}
    		switch a.Key {
    		case "xlink:actuate", "xlink:arcrole", "xlink:href", "xlink:role", "xlink:show",
    			"xlink:title", "xlink:type", "xml:base", "xml:lang", "xml:space", "xmlns:xlink":
    			j := strings.Index(a.Key, ":")
    			aa[i].Namespace = a.Key[:j]
    			aa[i].Key = a.Key[j+1:]
    		}
    	}
    }
    
  3. src/pkg/html/parse.go: adjustForeignAttributes 関数の呼び出しが追加された点。
    // inBodyIM 関数内
    // TODO: adjust foreign attributes.
    adjustForeignAttributes(p.tok.Attr)
    
    // parseForeignContent 関数内
    // TODO: adjust foreign attributes.
    adjustForeignAttributes(p.tok.Attr)
    
  4. src/pkg/html/render.go: 属性レンダリング時に名前空間を考慮するロジックが追加された点。
    if a.Namespace != "" {
    	if _, err := w.WriteString(a.Namespace); err != nil {
    		return err
    	}
    	if err := w.WriteByte(':'); err != nil {
    		return err
    	}
    }
    

コアとなるコードの解説

src/pkg/html/token.goAttribute 構造体

type Attribute struct {
	Namespace, Key, Val string
}

この変更は、属性の内部表現の根本的な変更です。以前は属性名(例: xlink:href)全体が Key フィールドに格納されていましたが、この変更により、属性が名前空間(Namespace)、ローカル名(Key)、値(Val)の3つの要素に分解されて格納されるようになりました。これは、XML名前空間の概念をHTMLパーサーの内部モデルに導入する上で不可欠なステップです。

src/pkg/html/foreign.goadjustForeignAttributes 関数

func adjustForeignAttributes(aa []Attribute) {
	for i, a := range aa {
		if a.Key == "" || a.Key[0] != 'x' {
			continue // キーが空か 'x' で始まらない場合はスキップ
		}
		switch a.Key {
		case "xlink:actuate", "xlink:arcrole", "xlink:href", "xlink:role", "xlink:show",
			"xlink:title", "xlink:type", "xml:base", "xml:lang", "xml:space", "xmlns:xlink":
			j := strings.Index(a.Key, ":") // ':' の位置を検索
			aa[i].Namespace = a.Key[:j]   // ':' より前を名前空間に設定
			aa[i].Key = a.Key[j+1:]        // ':' より後をキーに設定
		}
	}
}

この関数は、外部コンテンツの属性リストを反復処理し、特定の名前空間プレフィックス(xlink:xml:)を持つ属性を識別します。識別された属性に対して、: を区切り文字として名前空間プレフィックスとローカル名を抽出し、それぞれ NamespaceKey フィールドに割り当てます。これにより、パーサーは属性のセマンティクスをより正確に理解し、HTML5仕様に準拠したDOMツリーを構築できます。

src/pkg/html/parse.go での adjustForeignAttributes の呼び出し

// inBodyIM 関数内
adjustForeignAttributes(p.tok.Attr)

// parseForeignContent 関数内
adjustForeignAttributes(p.tok.Attr)

これらの呼び出しは、HTMLパーサーが新しい要素を構築し、その属性を処理する重要なポイントに挿入されています。特に、外部コンテンツ(SVGやMathML)の要素がパースされる際に、その属性が adjustForeignAttributes によって調整されることを保証します。これにより、生の入力から得られた属性が、DOMツリーに格納される前に正しい名前空間情報を持つようになります。

src/pkg/html/render.go での属性レンダリングロジック

if a.Namespace != "" {
	if _, err := w.WriteString(a.Namespace); err != nil {
		return err
	}
	if err := w.WriteByte(':'); err != nil {
		return err
	}
}

このコードスニペットは、パースされたDOMツリーをHTML文字列として出力する際に、名前空間情報が正しく再構築されることを保証します。Attribute 構造体に Namespace フィールドが設定されている場合、レンダラーは名前空間プレフィックスと : を属性キーの前に付加して出力します。これにより、xlink:href のような属性が、元の形式で(または仕様に準拠した形式で)正確にレンダリングされます。

これらの変更は連携して機能し、GoのHTMLパーサーが外部コンテンツの属性を、HTML5仕様に厳密に従って、より堅牢かつ正確に処理できるようにします。

関連リンク

参考にした情報源リンク