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

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

このコミットは、Go言語の実験的なHTMLパーサーパッケージ exp/html における変更です。具体的には、src/pkg/exp/html/parse.go ファイルと、関連するテストログファイル src/pkg/exp/html/testlogs/tests7.dat.log が変更されています。

コミット

commit fca45719a4365a0dc709202aa3efc2c58bbe473b
Author: Andrew Balholm <andybalholm@gmail.com>
Date:   Wed Aug 8 10:00:57 2012 +1000

    exp/html: foster-parent text correctly
    
    If a table contained whitespace, text nodes would not get foster parented
    correctly.
    
    Pass 1 additional test.
    
    R=nigeltao
    CC=golang-dev
    https://golang.org/cl/6459054

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

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

元コミット内容

exp/html: foster-parent text correctly

テーブル内に空白が含まれている場合、テキストノードが正しくフォスターペアレントされませんでした。

追加で1つのテストがパスするようになりました。

変更の背景

このコミットの背景には、HTMLのパースにおける「フォスターペアレンティング (foster parenting)」という特殊なルールが関係しています。HTMLの仕様では、特定の状況下で要素が本来の親要素ではなく、別の場所(フォスターペアレント要素)に挿入されることがあります。

元の実装では、テーブル要素(<table>, <tbody>, <tfoot>, <thead>, <tr>)の内部に空白文字(テキストノード)が存在する場合、これらのテキストノードがフォスターペアレンティングのルールに従って正しく処理されないという問題がありました。これにより、HTMLドキュメントの構造が意図しない形で構築され、レンダリング結果に影響を与える可能性がありました。

このコミットは、この問題を修正し、テーブル内の空白テキストノードも適切にフォスターペアレントされるようにすることで、HTMLパーサーの堅牢性と仕様への準拠を向上させることを目的としています。

前提知識の解説

HTMLパーシングとDOMツリー

HTMLパーシングとは、HTMLドキュメントの文字列を解析し、ブラウザが理解できる構造化されたデータ(DOMツリー)に変換するプロセスです。DOM(Document Object Model)ツリーは、HTML要素、属性、テキストなどをノードとして表現し、それらの親子関係をツリー構造で表します。

HTML5のパースアルゴリズムとフォスターペアレンティング

HTML5の仕様では、エラー耐性のあるパースアルゴリズムが詳細に定義されています。これは、不正なHTMLマークアップに対しても、ブラウザが予測可能な方法でDOMツリーを構築できるようにするためです。

「フォスターペアレンティング (foster parenting)」は、このHTML5パースアルゴリズムにおける重要な概念の一つです。これは、特定の状況下で、本来の親要素に属すべきノードが、DOMツリー内の別の場所(「フォスターペアレント要素」と呼ばれる)に挿入されるメカニズムを指します。

具体的には、HTMLのテーブル構造(<table>, <tbody>, <tfoot>, <thead>, <tr>など)の内部に、テーブルコンテンツモデルに適合しない要素(例えば、<div>や直接のテキストノードなど)が出現した場合に発生します。これらの「不適切な」ノードは、テーブル構造を破壊しないように、テーブルの直前の適切な親要素(フォスターペアレント要素)に「養子縁組」されるように挿入されます。

このメカニズムは、ブラウザが不正なHTMLをどのように解釈し、DOMツリーを構築するかを標準化するために非常に重要です。

Go言語の exp/html パッケージ

exp/html は、Go言語の標準ライブラリの一部として提供されているHTMLパーサーパッケージです。このパッケージは、HTML5のパースアルゴリズムに厳密に従ってHTMLドキュメントを解析し、DOMツリーを構築するための機能を提供します。実験的なパッケージとして開始されましたが、後にGoの標準ライブラリの一部として安定化されました。

技術的詳細

このコミットの技術的な核心は、HTMLパーサーがテキストノードを処理する際に、フォスターペアレンティングのルールを正しく適用するように修正した点にあります。

元のコードでは、addChild メソッド内でフォスターペアレンティングの条件を直接チェックしていました。このチェックは、要素ノードの追加時には機能していましたが、テキストノードの追加時には考慮されていませんでした。特に、テーブル要素の内部に空白文字(テキストノード)が存在する場合、このテキストノードはテーブルのコンテンツモデルに違反するため、フォスターペアレンティングの対象となるべきでした。しかし、テキストノードの追加パスではこのロジックが欠けていたため、正しく処理されませんでした。

この修正では、フォスターペアレンティングの条件チェックを shouldFosterParent() という新しいヘルパー関数に切り出しました。この関数は、現在のトップ要素がテーブル関連の要素であり、かつフォスターペアレンティングが有効な場合に true を返します。

そして、この shouldFosterParent() 関数を addChild メソッドだけでなく、addText メソッド(テキストノードを追加するメソッド)からも呼び出すように変更しました。これにより、テーブル内部の空白テキストノードも shouldFosterParent() の条件に合致すれば、fosterParent() メソッドを通じて適切にフォスターペアレントされるようになりました。

この変更により、HTML5のパース仕様におけるフォスターペアレンティングのルールが、テキストノードに対しても一貫して適用されるようになり、パーサーの正確性が向上しました。

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

src/pkg/exp/html/parse.go

  1. addChild メソッドの変更:

    • フォスターペアレンティングの条件チェックロジックが、新しい p.shouldFosterParent() 関数呼び出しに置き換えられました。
    • 変更前:
      fp := false
      if p.fosterParenting {
          switch p.top().DataAtom {
          case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
              fp = true
          }
      }
      if fp {
          p.fosterParent(n)
      } else {
          p.top().Add(n)
      }
      
    • 変更後:
      if p.shouldFosterParent() {
          p.fosterParent(n)
      } else {
          p.top().Add(n)
      }
      
  2. shouldFosterParent ヘルパー関数の追加:

    • フォスターペアレンティングの条件を判断するための新しい関数が追加されました。
    • // shouldFosterParent returns whether the next node to be added should be
      // foster parented.
      func (p *parser) shouldFosterParent() bool {
          if p.fosterParenting {
              switch p.top().DataAtom {
              case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
                  return true
              }
          }
          return false
      }
      
  3. addText メソッドの変更:

    • テキストノードを追加する際に、フォスターペアレンティングの条件をチェックし、必要に応じて fosterParent を呼び出すロジックが追加されました。
    • 変更前:
      if text == "" {
          return
      }
      // TODO: distinguish whitespace text from others.
      t := p.top()
      if i := len(t.Child); i > 0 && t.Child[i-1].Type == TextNode {
          t.Child[i-1].Data += text
      }
      
    • 変更後:
      if text == "" {
          return
      }
      if p.shouldFosterParent() {
          p.fosterParent(&Node{
              Type: TextNode,
              Data: text,
          })
          return
      }
      t := p.top()
      if i := len(t.Child); i > 0 && t.Child[i-1].Type == TextNode {
          t.Child[i-1].Data += text
      }
      

src/pkg/exp/html/testlogs/tests7.dat.log

  • テスト結果のログが更新され、以前 FAIL だった特定のテストケースが PASS に変更されました。
    • 変更前: FAIL "A<table><tr> B</tr> </em>C</table>"
    • 変更後: PASS "A<table><tr> B</tr> </em>C</table>" これは、修正によって問題が解決され、テストが成功したことを示しています。

コアとなるコードの解説

このコミットのコアとなる変更は、フォスターペアレンティングのロジックを shouldFosterParent() 関数にカプセル化し、それを addChild()addText() の両方から呼び出すようにした点です。

  1. shouldFosterParent() 関数:

    • この関数は、現在のパーサーの状態に基づいて、次に挿入されるノードがフォスターペアレントされるべきかどうかを判断します。
    • p.fosterParentingtrue(フォスターペアレンティングモードが有効)であり、かつ現在のスタックトップの要素が <table>, <tbody>, <tfoot>, <thead>, または <tr> のいずれかである場合に true を返します。
    • これにより、フォスターペアレンティングの条件が明確に定義され、再利用可能になりました。
  2. addChild() メソッドの変更:

    • 以前は addChild() 内に直接記述されていたフォスターペアレンティングの条件チェックが、p.shouldFosterParent() の呼び出しに置き換えられました。これにより、コードが簡潔になり、ロジックの重複がなくなりました。
  3. addText() メソッドの変更:

    • これが最も重要な変更点です。以前の addText() メソッドは、テキストノードがフォスターペアレントされるべきかどうかを考慮していませんでした。
    • 新しいコードでは、テキストノードを追加する前に p.shouldFosterParent() を呼び出します。
    • もし shouldFosterParent()true を返した場合、新しいテキストノードは通常の Add() メソッドではなく、p.fosterParent() メソッドを通じてDOMツリーに挿入されます。これにより、テーブル内部の空白テキストノードがHTML5の仕様に従って正しく「養子縁組」されるようになります。
    • Node{Type: TextNode, Data: text} のように、新しいテキストノードをその場で作成して fosterParent に渡している点も注目に値します。

この修正により、HTMLパーサーはテーブル内の空白文字のような、一見無害に見えるテキストノードに対しても、HTML5の複雑なフォスターペアレンティングルールを正確に適用できるようになり、より堅牢で仕様に準拠したDOMツリーを構築することが可能になりました。

関連リンク

参考にした情報源リンク

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

このコミットは、Go言語の実験的なHTMLパーサーパッケージ exp/html における変更です。具体的には、src/pkg/exp/html/parse.go ファイルと、関連するテストログファイル src/pkg/exp/html/testlogs/tests7.dat.log が変更されています。

コミット

commit fca45719a4365a0dc709202aa3efc2c58bbe473b
Author: Andrew Balholm <andybalholm@gmail.com>
Date:   Wed Aug 8 10:00:57 2012 +1000

    exp/html: foster-parent text correctly
    
    If a table contained whitespace, text nodes would not get foster parented
    correctly.
    
    Pass 1 additional test.
    
    R=nigeltao
    CC=golang-dev
    https://golang.org/cl/6459054

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

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

元コミット内容

exp/html: foster-parent text correctly

テーブル内に空白が含まれている場合、テキストノードが正しくフォスターペアレントされませんでした。

追加で1つのテストがパスするようになりました。

変更の背景

このコミットの背景には、HTMLのパースにおける「フォスターペアレンティング (foster parenting)」という特殊なルールが関係しています。HTMLの仕様では、特定の状況下で要素が本来の親要素ではなく、別の場所(フォスターペアレント要素)に挿入されることがあります。

元の実装では、テーブル要素(<table>, <tbody>, <tfoot>, <thead>, <tr>)の内部に空白文字(テキストノード)が存在する場合、これらのテキストノードがフォスターペアレンティングのルールに従って正しく処理されないという問題がありました。これにより、HTMLドキュメントの構造が意図しない形で構築され、レンダリング結果に影響を与える可能性がありました。

このコミットは、この問題を修正し、テーブル内の空白テキストノードも適切にフォスターペアレントされるようにすることで、HTMLパーサーの堅牢性と仕様への準拠を向上させることを目的としています。

前提知識の解説

HTMLパーシングとDOMツリー

HTMLパーシングとは、HTMLドキュメントの文字列を解析し、ブラウザが理解できる構造化されたデータ(DOMツリー)に変換するプロセスです。DOM(Document Object Model)ツリーは、HTML要素、属性、テキストなどをノードとして表現し、それらの親子関係をツリー構造で表します。

HTML5のパースアルゴリズムとフォスターペアレンティング

HTML5の仕様では、エラー耐性のあるパースアルゴリズムが詳細に定義されています。これは、不正なHTMLマークアップに対しても、ブラウザが予測可能な方法でDOMツリーを構築できるようにするためです。

「フォスターペアレンティング (foster parenting)」は、このHTML5パースアルゴリズムにおける重要な概念の一つです。これは、特定の状況下で、本来の親要素に属すべきノードが、DOMツリー内の別の場所(「フォスターペアレント要素」と呼ばれる)に挿入されるメカニズムを指します。

具体的には、HTMLのテーブル構造(<table>, <tbody>, <tfoot>, <thead>, <tr>など)の内部に、テーブルコンテンツモデルに適合しない要素(例えば、<div>や直接のテキストノードなど)が出現した場合に発生します。これらの「不適切な」ノードは、テーブル構造を破壊しないように、テーブルの直前の適切な親要素(フォスターペアレント要素)に「養子縁組」されるように挿入されます。

このメカニズムは、ブラウザが不正なHTMLをどのように解釈し、DOMツリーを構築するかを標準化するために非常に重要です。

Go言語の exp/html パッケージ

exp/html は、Go言語の標準ライブラリの一部として提供されているHTMLパーサーパッケージです。このパッケージは、HTML5のパースアルゴリズムに厳密に従ってHTMLドキュメントを解析し、DOMツリーを構築するための機能を提供します。実験的なパッケージとして開始されましたが、後にGoの標準ライブラリの一部として安定化されました。

技術的詳細

このコミットの技術的な核心は、HTMLパーサーがテキストノードを処理する際に、フォスターペアレンティングのルールを正しく適用するように修正した点にあります。

元のコードでは、addChild メソッド内でフォスターペアレンティングの条件を直接チェックしていました。このチェックは、要素ノードの追加時には機能していましたが、テキストノードの追加時には考慮されていませんでした。特に、テーブル要素の内部に空白文字(テキストノード)が存在する場合、このテキストノードはテーブルのコンテンツモデルに違反するため、フォスターペアレンティングの対象となるべきでした。しかし、テキストノードの追加パスではこのロジックが欠けていたため、正しく処理されませんでした。

この修正では、フォスターペアレンティングの条件チェックを shouldFosterParent() という新しいヘルパー関数に切り出しました。この関数は、現在のトップ要素がテーブル関連の要素であり、かつフォスターペアレンティングが有効な場合に true を返します。

そして、この shouldFosterParent() 関数を addChild メソッドだけでなく、addText メソッド(テキストノードを追加するメソッド)からも呼び出すように変更しました。これにより、テーブル内部の空白テキストノードも shouldFosterParent() の条件に合致すれば、fosterParent() メソッドを通じて適切にフォスターペアレントされるようになりました。

この変更により、HTML5のパース仕様におけるフォスターペアレンティングのルールが、テキストノードに対しても一貫して適用されるようになり、パーサーの正確性が向上しました。

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

src/pkg/exp/html/parse.go

  1. addChild メソッドの変更:

    • フォスターペアレンティングの条件チェックロジックが、新しい p.shouldFosterParent() 関数呼び出しに置き換えられました。
    • 変更前:
      fp := false
      if p.fosterParenting {
          switch p.top().DataAtom {
          case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
              fp = true
          }
      }
      if fp {
          p.fosterParent(n)
      } else {
          p.top().Add(n)
      }
      
    • 変更後:
      if p.shouldFosterParent() {
          p.fosterParent(n)
      } else {
          p.top().Add(n)
      }
      
  2. shouldFosterParent ヘルパー関数の追加:

    • フォスターペアレンティングの条件を判断するための新しい関数が追加されました。
    • // shouldFosterParent returns whether the next node to be added should be
      // foster parented.
      func (p *parser) shouldFosterParent() bool {
          if p.fosterParenting {
              switch p.top().DataAtom {
              case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
                  return true
              }
          }
          return false
      }
      
  3. addText メソッドの変更:

    • テキストノードを追加する際に、フォスターペアレンティングの条件をチェックし、必要に応じて fosterParent を呼び出すロジックが追加されました。
    • 変更前:
      if text == "" {
          return
      }
      // TODO: distinguish whitespace text from others.
      t := p.top()
      if i := len(t.Child); i > 0 && t.Child[i-1].Type == TextNode {
          t.Child[i-1].Data += text
      }
      
    • 変更後:
      if text == "" {
          return
      }
      if p.shouldFosterParent() {
          p.fosterParent(&Node{
              Type: TextNode,
              Data: text,
          })
          return
      }
      t := p.top()
      if i := len(t.Child); i > 0 && t.Child[i-1].Type == TextNode {
          t.Child[i-1].Data += text
      }
      

src/pkg/exp/html/testlogs/tests7.dat.log

  • テスト結果のログが更新され、以前 FAIL だった特定のテストケースが PASS に変更されました。
    • 変更前: FAIL "A<table><tr> B</tr> </em>C</table>"
    • 変更後: PASS "A<table><tr> B</tr> </em>C</table>" これは、修正によって問題が解決され、テストが成功したことを示しています。

コアとなるコードの解説

このコミットのコアとなる変更は、フォスターペアレンティングのロジックを shouldFosterParent() 関数にカプセル化し、それを addChild()addText() の両方から呼び出すようにした点です。

  1. shouldFosterParent() 関数:

    • この関数は、現在のパーサーの状態に基づいて、次に挿入されるノードがフォスターペアレントされるべきかどうかを判断します。
    • p.fosterParentingtrue(フォスターペアレンティングモードが有効)であり、かつ現在のスタックトップの要素が <table>, <tbody>, <tfoot>, <thead>, または <tr> のいずれかである場合に true を返します。
    • これにより、フォスターペアレンティングの条件が明確に定義され、再利用可能になりました。
  2. addChild() メソッドの変更:

    • 以前は addChild() 内に直接記述されていたフォスターペアレンティングの条件チェックが、p.shouldFosterParent() の呼び出しに置き換えられました。これにより、コードが簡潔になり、ロジックの重複がなくなりました。
  3. addText() メソッドの変更:

    • これが最も重要な変更点です。以前の addText() メソッドは、テキストノードがフォスターペアレントされるべきかどうかを考慮していませんでした。
    • 新しいコードでは、テキストノードを追加する前に p.shouldFosterParent() を呼び出します。
    • もし shouldFosterParent()true を返した場合、新しいテキストノードは通常の Add() メソッドではなく、p.fosterParent() メソッドを通じてDOMツリーに挿入されます。これにより、テーブル内部の空白テキストノードがHTML5の仕様に従って正しく「養子縁組」されるようになります。
    • Node{Type: TextNode, Data: text} のように、新しいテキストノードをその場で作成して fosterParent に渡している点も注目に値します。

この修正により、HTMLパーサーはテーブル内の空白文字のような、一見無害に見えるテキストノードに対しても、HTML5の複雑なフォスターペアレンティングルールを正確に適用できるようになり、より堅牢で仕様に準拠したDOMツリーを構築することが可能になりました。

関連リンク

参考にした情報源リンク