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

[インデックス 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パーサーの仕様準拠性を向上させるために実装されました。具体的には以下の課題を解決しています:

  1. 複数のbodyタグの処理問題: HTMLドキュメント内で複数の<body>タグが現れる場合の処理が不適切でした
  2. headタグ内要素の誤った配置: 本来<head>内に配置されるべき要素(<base><link><meta><title>など)が<body>内に現れた場合の処理が不十分でした
  3. 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(主要な変更)

  1. useTheRulesFor関数の修正:Line 42-44

    • originalIMの適切な更新処理を追加
  2. copyAttributes関数の追加:Line 52-67

    • 属性のコピー処理を実装
  3. inBodyIM関数の拡張:Line 76-85

    • bodyタグの重複処理を追加
    • headタグ内要素の処理を追加

src/pkg/html/parse_test.go(テストの更新)

  1. テストケース数の更新: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要素内に配置されます。

関連リンク

参考にした情報源リンク