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

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

このコミットは、Go言語の実験的なHTMLパーサー (exp/html パッケージ) において、SVG (Scalable Vector Graphics) および MathML (Mathematical Markup Language) コンテンツ内の「統合点 (integration points)」の検出ロジックを改善するものです。これにより、特定の条件下でこれらの外部コンテンツがHTMLとして正しくパースされるようになり、Web標準への準拠が向上します。

コミット

commit 82e22725666f0ee41c2ea67065ae8ea0792ab400
Author: Andrew Balholm <andybalholm@gmail.com>
Date:   Thu May 24 13:46:41 2012 +1000

    exp/html: detect "integration points" in SVG and MathML content
    
    Detect HTML integration points and MathML text integration points.
    At these points, process tokens as HTML, not as foreign content.
    
    Pass 33 more tests.
    
    R=nigeltao
    CC=golang-dev
    https://golang.org/cl/6249044

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

https://github.com/golang/go/commit/82e22725666f0ee41c2ea67065ae8ea0792ab400

元コミット内容

exp/html: detect "integration points" in SVG and MathML content

このコミットは、SVGおよびMathMLコンテンツ内の「HTML統合点」と「MathMLテキスト統合点」を検出するようにパーサーを修正します。これらの統合点では、トークンが外部コンテンツとしてではなく、HTMLとして処理されるようになります。この変更により、33のテストが追加でパスするようになりました。

変更の背景

Webブラウザは、HTML、SVG、MathMLといった異なるマークアップ言語を組み合わせて表示する能力を持っています。これらの言語はそれぞれ独自の構文とパースルールを持っていますが、HTML5の仕様では、特定の要素が別のマークアップ言語のコンテキスト内で出現した場合でも、その内部のコンテンツをHTMLとしてパースし直す必要があると定義されています。これが「統合点 (integration points)」の概念です。

具体的には、SVGの <foreignObject><desc><title> 要素や、MathMLの <annotation-xml> 要素(特定の encoding 属性を持つ場合)の内部では、HTMLコンテンツが埋め込まれることが想定されます。従来のパーサーがこれらのケースを適切に処理できない場合、埋め込まれたHTMLが「外部コンテンツ (foreign content)」として誤って扱われ、DOMツリーが正しく構築されなかったり、レンダリングが意図通りに行われなかったりする問題が発生します。

このコミットは、Go言語のHTMLパーサーがこれらのWeb標準の挙動に準拠し、より堅牢で正確なHTMLパースを実現するために導入されました。これにより、複雑なWebページ、特にSVGやMathMLを多用するページでの互換性と正確性が向上します。

前提知識の解説

HTML5のパースアルゴリズム

HTML5のパースアルゴリズムは、非常に複雑で状態遷移に基づいています。これは、HTMLが非常に寛容な構文を持つため、エラーリカバリや異なるマークアップ言語の埋め込みを考慮する必要があるためです。パーサーは、現在の「挿入モード (insertion mode)」に基づいてトークンを処理し、DOMツリーを構築します。

外部コンテンツ (Foreign Content)

HTMLのコンテキスト内でSVGやMathMLのようなXML名前空間を持つ要素が出現した場合、それらは「外部コンテンツ」として扱われます。外部コンテンツの内部では、HTMLのパースルールではなく、XMLのパースルールが適用されます。これは、HTML要素が外部コンテンツの内部で出現しても、通常はHTMLとして解釈されないことを意味します。

HTML統合点 (HTML Integration Point)

HTML統合点とは、外部コンテンツ(SVGやMathML)の特定の要素内で、その子孫要素がHTMLとしてパースされるべき場所を指します。HTML5仕様の「12.2.5.5. The rules for parsing tokens in foreign content」セクションで定義されています。

主なHTML統合点は以下の通りです。

  • MathML:
    • <annotation-xml> 要素で、encoding 属性の値が "text/html" または "application/xhtml+xml" の場合。
  • SVG:
    • <foreignObject> 要素
    • <desc> 要素
    • <title> 要素

これらの要素の内部では、パーサーは一時的にHTMLパースモードに切り替わり、HTMLトークンを処理します。

MathMLテキスト統合点 (MathML Text Integration Point)

MathMLテキスト統合点とは、MathMLの特定の要素内で、その内部のテキストコンテンツがHTMLのテキストとして扱われるべき場所を指します。これは、MathMLの要素がHTMLのテキストノードを直接含むことができることを意味します。

主なMathMLテキスト統合点は以下の通りです。

  • <mi> (identifier)
  • <mo> (operator)
  • <mn> (number)
  • <ms> (string literal)
  • <mtext> (text)

これらの要素の内部では、HTMLのテキストトークンが許容されます。

技術的詳細

このコミットの主要な変更は、src/pkg/exp/html/foreign.gosrc/pkg/exp/html/parse.go の2つのファイルに集中しています。

src/pkg/exp/html/foreign.go の変更

このファイルでは、htmlIntegrationPoint 関数が拡張され、新たに mathMLTextIntegrationPoint 関数が追加されています。

  • htmlIntegrationPoint(n *Node) bool 関数の拡張:

    • MathMLの名前空間 (n.Namespace == "math") の場合、<annotation-xml> 要素の encoding 属性が "text/html" または "application/xhtml+xml" であるかをチェックするロジックが追加されました。これにより、これらの条件を満たす <annotation-xml> 要素がHTML統合点として正しく識別されます。
    • SVGの名前空間 (n.Namespace == "svg") の場合、既存の desc, foreignObject, title 要素のチェックはそのままです。
  • mathMLTextIntegrationPoint(n *Node) bool 関数の追加:

    • この新しい関数は、与えられたノードがMathMLの名前空間に属し、かつそのデータが mi, mo, mn, ms, mtext のいずれかである場合に true を返します。これにより、これらのMathML要素がMathMLテキスト統合点として識別されます。

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

このファイルでは、inForeignContent() メソッドが修正され、統合点の検出ロジックが組み込まれました。inForeignContent() は、現在のパーサーの状態が外部コンテンツ内にあるかどうかを判断する重要なメソッドです。

変更点:

  • MathMLテキスト統合点の処理:

    • mathMLTextIntegrationPoint(n)true を返す場合、つまり現在のノードがMathMLテキスト統合点である場合、以下の条件で外部コンテンツではないと判断されます(HTMLとして処理されるべきと判断されます)。
      • 現在のトークンが StartTagToken であり、かつそのデータが "mglyph" または "malignmark" ではない場合。
      • 現在のトークンが TextToken の場合。
    • これは、MathMLテキスト統合点内では、特定のMathML要素(mglyph, malignmark)以外の開始タグやテキストはHTMLとして扱われるべきという仕様に準拠しています。
  • HTML統合点の処理:

    • htmlIntegrationPoint(n)true を返し、かつ現在のトークンが StartTagToken または TextToken の場合、外部コンテンツではないと判断されます。
    • これは、HTML統合点内では、開始タグやテキストはHTMLとして扱われるべきという仕様に準拠しています。
  • MathML <annotation-xml> と SVG の組み合わせ:

    • 現在のノードがMathMLの名前空間に属する <annotation-xml> であり、かつ現在のトークンが StartTagToken でデータが "svg" の場合、外部コンテンツではないと判断されます。これは、MathMLの <annotation-xml> 内にSVGが埋め込まれるケースを特別に処理するためのものです。
  • エラー状態の処理:

    • 現在のトークンが ErrorToken の場合、外部コンテンツではないと判断されます。これは、パースエラーが発生した場合に、外部コンテンツの処理を中断し、HTMLのパースモードに戻るためのエラーリカバリの一環と考えられます。

これらの変更により、パーサーはHTML5の仕様に厳密に従い、SVGやMathML内部に埋め込まれたHTMLコンテンツを正しく識別し、適切なパースモードで処理できるようになりました。これにより、DOMツリーの正確性が保証され、Webページのレンダリングがより正確になります。

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

src/pkg/exp/html/foreign.go

func htmlIntegrationPoint(n *Node) bool {
	// ... (既存のコード) ...
	switch n.Namespace {
	case "math":
		// TODO: annotation-xml elements whose start tags have "text/html" or
		// "application/xhtml+xml" encodings.
		if n.Data == "annotation-xml" {
			for _, a := range n.Attr {
				if a.Key == "encoding" {
					val := strings.ToLower(a.Val)
					if val == "text/html" || val == "application/xhtml+xml" {
						return true
					}
				}
			}
		}
	case "svg":
		switch n.Data {
		case "desc", "foreignObject", "title":
			return true
		}
	}
	return false
}

func mathMLTextIntegrationPoint(n *Node) bool {
	if n.Namespace != "math" {
		return false
	}
	switch n.Data {
	case "mi", "mo", "mn", "ms", "mtext":
		return true
	}
	return false
}

src/pkg/exp/html/parse.go

func (p *parser) inForeignContent() bool {
	n := p.top()
	if n == nil || n.Namespace == "" {
		return false
	}
	// TODO: MathML, HTML integration points.
	// TODO: MathML's annotation-xml combining with SVG's svg.
	if mathMLTextIntegrationPoint(n) {
		if p.tok.Type == StartTagToken && p.tok.Data != "mglyph" && p.tok.Data != "malignmark" {
			return false
		}
		if p.tok.Type == TextToken {
			return false
		}
	}
	if n.Namespace == "math" && n.Data == "annotation-xml" && p.tok.Type == StartTagToken && p.tok.Data == "svg" {
		return false
	}
	if htmlIntegrationPoint(n) && (p.tok.Type == StartTagToken || p.tok.Type == TextToken) {
		return false
	}
	if p.tok.Type == ErrorToken {
		return false
	}
	return true
}

コアとなるコードの解説

foreign.go の変更点

  • htmlIntegrationPoint 関数は、ノードがHTML統合点であるかどうかを判断します。変更前はSVGの特定の要素のみをチェックしていましたが、このコミットでMathMLの <annotation-xml> 要素が encoding 属性に "text/html" または "application/xhtml+xml" を持つ場合に true を返すロジックが追加されました。これは、HTML5仕様で定義されているMathMLのHTML統合点に準拠するためです。
  • mathMLTextIntegrationPoint 関数は新しく追加されました。この関数は、MathMLの名前空間に属するノードが mi, mo, mn, ms, mtext のいずれかである場合に true を返します。これらの要素はMathMLテキスト統合点であり、その内部のテキストコンテンツはHTMLとして扱われるべきです。

parse.go の変更点

  • inForeignContent() メソッドは、現在のパーサーが外部コンテンツのコンテキストにいるかどうかを判断します。このメソッドは、HTML5のパースアルゴリズムにおいて、外部コンテンツとHTMLコンテンツの間のモード切り替えを管理する上で非常に重要です。
  • 追加されたロジックは、以下の条件で false を返す(つまり、外部コンテンツではないと判断する)ことで、HTMLパースモードへの切り替えを促します。
    • MathMLテキスト統合点の場合: mathMLTextIntegrationPoint(n)true で、かつ現在のトークンが特定のMathML要素(mglyph, malignmark)以外の開始タグ、またはテキストトークンである場合。これは、これらのMathML要素内でHTMLのテキストや要素が許容されるためです。
    • MathML <annotation-xml> 内のSVGの場合: 現在のノードがMathMLの <annotation-xml> で、かつ現在のトークンがSVGの開始タグである場合。これは、MathMLの <annotation-xml> 内にSVGが埋め込まれる特殊なケースを処理します。
    • HTML統合点の場合: htmlIntegrationPoint(n)true で、かつ現在のトークンが開始タグまたはテキストトークンである場合。これは、HTML統合点内でHTMLコンテンツが期待されるためです。
    • エラー状態の場合: 現在のトークンが ErrorToken の場合。これは、パースエラーが発生した際に、外部コンテンツモードから抜け出し、HTMLのパースモードに戻るためのエラーリカバリメカニズムです。

これらの変更により、GoのHTMLパーサーは、Web標準に則った複雑なHTML、SVG、MathMLの組み合わせをより正確にパースできるようになり、互換性と堅牢性が向上しました。

関連リンク

参考にした情報源リンク