[インデックス 10283] HTMLパーサーの改良: タグの処理の最適化
コミット
コミットハッシュ: f2b602ed4252ca0f37cf1ff0494342b75f0b6bfc 作成者: Andrew Balholm andybalholm@gmail.com 日付: 2011年11月8日 (Tuesday November 8, 2011) 17:55:17 +1100 レビュー: golang.org/cl/5364047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f2b602ed4252ca0f37cf1ff0494342b75f0b6bfc
元コミット内容
このコミットでは、HTML5パーサーの改良により、ページ本文内で出現する<body>
、<base>
、<link>
、<meta>
、<title>
タグの処理が最適化されています。
コミットメッセージ詳細:
- html5lib-testsのtests1.datからテスト87をパス
- 複数の
<body>
タグの処理を改善 - 最後の
<body>
タグの処理で、useTheRulesFor関数内でoriginalIMの修正が必要 - tests1.datのテスト88も同時にパス(
<textarea><p></textarea>
) - テストケースの処理対象を87から89に拡張
変更の背景
このコミットは、HTML5パーサーの仕様準拠性を向上させるために実装されました。具体的には以下の課題を解決しています:
- 複数のbodyタグの処理問題: HTMLドキュメント内で複数の
<body>
タグが現れる場合の処理が不適切でした - headタグ内要素の誤った配置: 本来
<head>
内に配置されるべき要素(<base>
、<link>
、<meta>
、<title>
など)が<body>
内に現れた場合の処理が不十分でした - insertion modeの状態管理: HTMLパーサーのinsertion mode状態管理において、元のinsertion modeの追跡が不正確でした
前提知識の解説
HTML5パーサーの基本概念
HTML5パーサーは、HTMLドキュメントを解析する際に「insertion mode」という概念を使用します。これは、現在のパーサーの状態を表し、トークンをどのように処理するかを決定します。
主要なinsertion mode:
beforeHtml
: HTML要素の前beforeHead
: head要素の前inHead
: head要素内afterHead
: head要素の後inBody
: body要素内afterBody
: body要素の後
useTheRulesFor関数の概念
useTheRulesFor
関数は、HTML5仕様の「using the rules for」メカニズムを実装しています。これは、一時的に異なるinsertion modeのルールを適用する機能です。
例えば、<body>
内に<title>
タグが現れた場合、パーサーは一時的にinHead
モードのルールを適用し、そのタグを適切に処理します。
originalIMの役割
originalIM
は、一時的なinsertion mode変更の際に、元のinsertion modeを記録するために使用されます。これにより、一時的な処理が完了した後、適切な状態に戻ることができます。
技術的詳細
1. originalIMの修正(parse.go:42-44行)
if p.originalIM == delegate {
p.originalIM = actual
}
この変更により、useTheRulesFor
関数が呼び出された際に、元のinsertion modeが正しく更新されるようになりました。これは、複数の<body>
タグが現れる場合の処理で特に重要です。
2. copyAttributes関数の追加(parse.go:52-67行)
func copyAttributes(dst *Node, src Token) {
// 実装詳細
}
この関数は、既存のノードに新しい属性をコピーするために追加されました。同じ名前の属性が既に存在する場合は、既存の属性を保持します。
3. bodyタグの処理改善(parse.go:76-84行)
case "body":
if len(p.oe) >= 2 {
body := p.oe[1]
if body.Type == ElementNode && body.Data == "body" {
p.framesetOK = false
copyAttributes(body, p.tok)
}
}
複数の<body>
タグが現れた場合、2番目以降のタグは新しいbody要素を作成せず、既存のbody要素に属性をコピーします。
4. headタグ内要素の処理(parse.go:85行)
case "base", "basefont", "bgsound", "command", "link", "meta", "noframes", "script", "style", "title":
return useTheRulesFor(p, inBodyIM, inHeadIM)
body内で<base>
、<link>
、<meta>
、<title>
などのタグが現れた場合、inHead
モードのルールを一時的に適用して処理します。
コアとなるコードの変更箇所
src/pkg/html/parse.go(主要な変更)
-
useTheRulesFor関数の修正:Line 42-44
- originalIMの適切な更新処理を追加
-
copyAttributes関数の追加:Line 52-67
- 属性のコピー処理を実装
-
inBodyIM関数の拡張:Line 76-85
- bodyタグの重複処理を追加
- headタグ内要素の処理を追加
src/pkg/html/parse_test.go(テストの更新)
- テストケース数の更新:Line 97
{"tests1.dat", 87}
から{"tests1.dat", 89}
に変更- 2つの新しいテストケースが追加されたことを示す
コアとなるコードの解説
1. useTheRulesFor関数の改良
func useTheRulesFor(p *parser, actual, delegate insertionMode) (insertionMode, bool) {
im, consumed := delegate(p)
if p.originalIM == delegate {
p.originalIM = actual
}
if im != delegate {
return im, consumed
}
return actual, consumed
}
この改良により、delegation処理中に正しいoriginalIMが維持されます。これは、複数の<body>
タグが現れる複雑なHTMLドキュメントの処理で重要です。
2. copyAttributes関数の実装
func copyAttributes(dst *Node, src Token) {
if len(src.Attr) == 0 {
return
}
attr := map[string]string{}
for _, a := range dst.Attr {
attr[a.Key] = a.Val
}
for _, a := range src.Attr {
if _, ok := attr[a.Key]; !ok {
dst.Attr = append(dst.Attr, a)
attr[a.Key] = a.Val
}
}
}
この関数は、既存の属性と競合しない新しい属性のみをコピーします。これは、HTML5仕様の「既存の属性は保持される」という要件を満たします。
3. body要素の重複処理
case "body":
if len(p.oe) >= 2 {
body := p.oe[1]
if body.Type == ElementNode && body.Data == "body" {
p.framesetOK = false
copyAttributes(body, p.tok)
}
}
この処理により、複数の<body>
タグが現れた場合、2番目以降のタグは新しいbody要素を作成せず、既存のbody要素に属性をマージします。
4. head要素内タグの処理
case "base", "basefont", "bgsound", "command", "link", "meta", "noframes", "script", "style", "title":
return useTheRulesFor(p, inBodyIM, inHeadIM)
body内でhead要素内タグが現れた場合、一時的にinHead
モードのルールを適用して処理します。これにより、これらのタグが適切にhead要素内に配置されます。