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

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

このコミットは、Go言語の実験的なHTMLパーサーライブラリ exp/html における inBodyIM (in body insertion mode) の実装を、HTML5の仕様に厳密に準拠させるための修正です。具体的には、<rp> および <rt> タグの処理、SVGおよびMathML要素の属性名の調整、そして「その他の開始タグ」ケースにおけるアクティブフォーマット要素の再構築に関する差異を解消し、関連するテストをパスするように改善されています。

コミット

  • コミットハッシュ: b885633d62e5001b11e65f92db85ee9ed74b383c
  • Author: Andrew Balholm andybalholm@gmail.com
  • Date: Tue Apr 24 15:27:48 2012 +1000

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

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

元コミット内容

exp/html: make inBodyIM match spec

This CL corrects the remaining differences that I could find between the
implementation of inBodyIM and the spec:

Handle <rp> and <rt>.

Adjust SVG and MathML attributes.

Reconstruct active formatting elements in the "any other start tag" case.

Pass 7 additional tests.

R=nigeltao
CC=golang-dev
https://golang.org/cl/6101055

変更の背景

HTML5の仕様は、ウェブブラウザがHTMLドキュメントをどのように解析し、DOMツリーを構築するかについて非常に詳細なアルゴリズムを定義しています。このアルゴリズムは、様々な「挿入モード (insertion mode)」と呼ばれる状態遷移に基づいており、それぞれのモードで特定のタグがどのように処理されるかが厳密に定められています。

exp/html ライブラリは、Go言語でHTML5のパース仕様を正確に実装することを目指していました。しかし、初期の実装では、特に inBodyIM (in body insertion mode) において、いくつかの細かい点で仕様との乖離がありました。この乖離は、特定のHTML構造、特にルビ関連要素(<rp>, <rt>)、SVGやMathMLの属性の扱い、そしてアクティブフォーマット要素の再構築において、パーサーが期待通りのDOMツリーを生成しない原因となっていました。

このコミットは、これらの残存する仕様との差異を特定し、修正することで、exp/html パーサーの堅牢性と正確性を向上させ、より多くのHTMLドキュメントを仕様通りに解析できるようにすることを目的としています。特に、既存のテストスイートで失敗していたケースをパスさせることで、その正確性が検証されています。

前提知識の解説

HTML5パーシングアルゴリズム

HTML5のパーシングアルゴリズムは、トークナイゼーションとツリー構築の2つの主要なフェーズに分かれます。

  1. トークナイゼーション: 入力ストリーム(HTML文字列)をトークン(開始タグ、終了タグ、テキスト、コメントなど)に変換します。
  2. ツリー構築: トークナイザーから受け取ったトークンに基づいて、DOMツリーを構築します。このフェーズは、現在のパーサーの状態を示す「挿入モード (insertion mode)」によって動作が大きく異なります。

挿入モード (Insertion Mode)

挿入モードは、HTMLドキュメントのどの部分を解析しているかに応じて変化するパーサーの状態です。例えば、<html> タグが解析されると「in html insertion mode」に、<body> タグが解析されると「in body insertion mode」に遷移します。それぞれのモードには、特定のタグが検出された際の詳細な処理ルールが定義されています。

inBodyIM (In Body Insertion Mode)

inBodyIM は、HTMLドキュメントの <body> 要素の内容を解析する際の主要な挿入モードです。ほとんどのHTMLコンテンツは、このモードで処理されます。このモードでは、様々なタグに対して複雑なルールが適用され、DOMツリーへの要素の追加、アクティブフォーマット要素リストの操作、特定の要素の自動クローズなどが実行されます。

アクティブフォーマット要素 (Active Formatting Elements)

HTML5パーシングにおいて、「アクティブフォーマット要素」リストは、現在開いているがまだ閉じられていないフォーマット関連の要素(例: <b>, <i>, <a>, <span> など)を追跡するために使用されます。これは、ネストされたフォーマット要素が正しく処理され、DOMツリーが期待通りに構築されることを保証するために重要です。例えば、<b><i>テキスト</b></i> のような不正なネストがあった場合でも、ブラウザはこれを正しく解釈し、DOMツリーを構築しようとします。この際、アクティブフォーマット要素リストが重要な役割を果たします。

アクティブフォーマット要素の再構築 (Reconstruct Active Formatting Elements)

このプロセスは、新しい要素が挿入される前に、アクティブフォーマット要素リスト内の要素がDOMツリーに正しく反映されていることを保証するために実行されます。これは、特にパーサーが予期せぬタグのシーケンスに遭遇した場合に、DOMツリーの整合性を保つために必要です。

<rp><rt> タグ

これらはHTML5のルビ注釈 (<ruby>) に関連する要素です。

  • <ruby>: ルビベースとルビテキストを囲む要素。
  • <rt>: ルビテキスト(ルビベースの上に表示されるテキスト)を表します。
  • <rp>: ルビテキストをサポートしないブラウザ向けに、ルビテキストの開始と終了を括弧で囲むための要素です。これにより、ルビテキストが括弧付きで表示され、コンテンツの可読性が保たれます。

これらのタグは、特定のコンテキスト(特に <ruby> 要素内)で特殊なパーシングルールを持ちます。

MathML と SVG 属性

MathML (Mathematical Markup Language) と SVG (Scalable Vector Graphics) は、HTMLドキュメント内に埋め込むことができるXMLベースのマークアップ言語です。これらの言語は独自の要素と属性を持ち、HTMLとは異なる命名規則(例: キャメルケース)を持つことがあります。HTMLパーサーは、これらの外部コンテンツを解析する際に、それぞれの仕様に合わせた属性名の調整を行う必要があります。例えば、SVGの viewbox 属性は、HTMLの属性としては viewbox と小文字で記述されても、SVGのコンテキストでは viewBox とキャメルケースに変換される必要があります。

技術的詳細

このコミットは、exp/html パーサーの inBodyIM 関数における以下の3つの主要な修正に焦点を当てています。

  1. <rp> および <rt> タグの処理の追加:

    • inBodyIM 内で <rp> または <rt> 開始タグが検出された場合、パーサーはまず、現在のスコープ内に ruby 要素が存在するかどうかを確認します。
    • もし ruby 要素が存在すれば、generateImpliedEndTags() を呼び出して、暗黙的に終了すべきタグ(例: rtrp の前の rtrp)を生成します。
    • その後、検出された <rp> または <rt> 要素をDOMツリーに追加します。
    • これにより、ルビ注釈がHTML5の仕様に従って正しく解析され、DOMツリーに反映されるようになります。
  2. SVG および MathML 属性の調整:

    • inBodyIM 内で math または svg 開始タグが検出された場合、パーサーは reconstructActiveFormattingElements() を呼び出した後、その要素の属性を調整する新しいロジックを適用します。
    • foreign.goadjustAttributeNames という新しいヘルパー関数が追加されました。この関数は、属性のリストと、元の属性名から正しい属性名へのマッピング(nameMap)を受け取ります。
    • mathMLAttributeAdjustmentssvgAttributeAdjustments という2つの新しいマップが foreign.go に定義されました。これらは、MathMLおよびSVGの属性名がHTMLのパーサーによってどのように正規化されるべきか(例: definitionurldefinitionURL に、attributenameattributeName に)を定義しています。
    • math 要素に対しては mathMLAttributeAdjustments を、svg 要素に対しては svgAttributeAdjustments を使用して、属性名が仕様に沿って調整されます。
    • この修正により、SVGやMathMLのコンテンツがHTMLドキュメントに埋め込まれた際に、その属性が正しく解釈され、DOMツリーに反映されるようになります。
  3. 「その他の開始タグ」ケースにおけるアクティブフォーマット要素の再構築:

    • inBodyIMdefault ケース(つまり、特定のタグに特化した処理がない「その他の開始タグ」の場合)において、reconstructActiveFormattingElements() が呼び出されるようになりました。
    • これは、HTML5のパーシング仕様において、多くの開始タグが検出された際にアクティブフォーマット要素リストを再構築する必要があるというルールに準拠するための重要な変更です。これにより、DOMツリーの整合性が保たれ、特にフォーマット関連の要素が正しくネストされることが保証されます。

これらの変更は、src/pkg/exp/html/parse.gosrc/pkg/exp/html/foreign.go のコードに反映され、関連するテストログ (tests11.dat.log, tests16.dat.log, tests19.dat.log) が FAIL から PASS に変更されていることから、これらの修正が期待通りに機能していることが確認できます。

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

このコミットによる主要なコード変更は以下のファイルに集中しています。

  1. src/pkg/exp/html/foreign.go:

    • adjustAttributeNames 関数が追加されました。
    • mathMLAttributeAdjustments マップと svgAttributeAdjustments マップが追加され、MathMLおよびSVGの属性名の調整ルールが定義されました。
    • 既存の // TODO: add look-up tables for MathML and SVG attribute adjustments. コメントが削除され、具体的な実装に置き換えられました。
  2. src/pkg/exp/html/parse.go:

    • inBodyIM 関数内で、select, optgroup, option の処理が移動され、より一般的な処理フローに統合されました。
    • inBodyIM 内に <rp><rt> タグの処理ロジックが追加されました。
    • inBodyIM 内の math および svg タグの処理において、adjustAttributeNames 関数が呼び出され、属性調整が適用されるようになりました。
    • inBodyIMdefault ケース(「その他の開始タグ」)において、p.reconstructActiveFormattingElements() が追加されました。
    • parseForeignContent 関数内でも、MathMLおよびSVGの属性調整が適用されるようになりました。
  3. src/pkg/exp/html/testlogs/*.log:

    • tests11.dat.log, tests16.dat.log, tests19.dat.log の複数のテストケースが FAIL から PASS に変更されました。これは、上記のコード変更がHTML5の仕様に準拠し、以前は失敗していたテストケースを正しく処理できるようになったことを示しています。

コアとなるコードの解説

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

func adjustAttributeNames(aa []Attribute, nameMap map[string]string) {
	for i := range aa {
		if newName, ok := nameMap[aa[i].Key]; ok {
			aa[i].Key = newName
		}
	}
}

// Section 12.2.5.1
var mathMLAttributeAdjustments = map[string]string{
	"definitionurl": "definitionURL",
}

var svgAttributeAdjustments = map[string]string{
	"attributename":             "attributeName",
	"attributetype":             "attributeType",
	// ... (他のSVG属性も同様に定義)
}

adjustAttributeNames 関数は、与えられた属性のリスト aa をループし、nameMap にキーが存在すれば、そのキーに対応する値(正しい属性名)で属性のキーを更新します。これにより、HTMLパーサーが受け取った属性名を、MathMLやSVGの仕様で定義されている正しいキャメルケースなどの形式に変換できます。mathMLAttributeAdjustmentssvgAttributeAdjustments は、それぞれの言語で調整が必要な属性名とその正しい形式をマッピングしています。

src/pkg/exp/html/parse.goinBodyIM 関数の変更

func inBodyIM(p *parser) bool {
	// ... 既存のコード ...

	case "rp", "rt":
		if p.elementInScope(defaultScope, "ruby") {
			p.generateImpliedEndTags()
		}
		p.addElement(p.tok.Data, p.tok.Attr)

	case "math", "svg":
		p.reconstructActiveFormattingElements()
		if p.tok.Data == "math" {
			adjustAttributeNames(p.tok.Attr, mathMLAttributeAdjustments)
		} else {
			adjustAttributeNames(p.tok.Attr, svgAttributeAdjustments)
		}
		adjustForeignAttributes(p.tok.Attr) // 既存の外部属性調整
		p.addElement(p.tok.Data, p.tok.Attr)

	// ... 既存のコード ...

	default: // "any other start tag" case
		p.reconstructActiveFormattingElements()
		p.addElement(p.tok.Data, p.tok.Attr)
	}
	// ... 既存のコード ...
}
  • <rp>, <rt> の処理: p.elementInScope(defaultScope, "ruby")ruby 要素がスコープ内にあるかを確認し、あれば p.generateImpliedEndTags() を呼び出してから要素を追加します。これは、ルビ関連要素の特殊なパーシングルールに対応するためです。
  • math, svg の処理: p.reconstructActiveFormattingElements() の呼び出しに加えて、adjustAttributeNames 関数を使って、それぞれの属性マップに基づいて属性名を調整しています。これにより、MathMLやSVGの属性が正しくDOMツリーに反映されます。
  • default ケース: default ブロックに p.reconstructActiveFormattingElements() が追加されました。これは、HTML5の仕様で多くの開始タグがアクティブフォーマット要素の再構築をトリガーするという要件を満たすためのものです。これにより、パーサーはより堅牢になり、複雑なHTML構造でも正しいDOMツリーを構築できるようになります。

これらの変更は、HTML5のパーシング仕様の複雑な詳細を正確に実装し、パーサーの互換性と正確性を向上させるためのものです。

関連リンク

参考にした情報源リンク