[インデックス 13617] ファイルの概要
このコミットでは、Go言語の実験的なHTMLパーサーであるexp/html
パッケージ内のファイルが変更されています。具体的には、HTMLパーシングのロジックを定義するsrc/pkg/exp/html/parse.go
と、パーサーの挙動を検証するためのテストログファイルsrc/pkg/exp/html/testlogs/webkit01.dat.log
が更新されています。
コミット
- コミットハッシュ:
c5038c85933a69a1ddeae812d601eb11e71cdc58
- Author: Andrew Balholm andybalholm@gmail.com
- Date: Fri Aug 10 09:34:10 2012 +1000
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c5038c85933a69a1ddeae812d601eb11e71cdc58
元コミット内容
exp/html: ignore self-closing flag except in SVG and MathML
In HTML content, having a self-closing tag is a parse error unless
the tag would be self-closing anyway (like <img>). The only place a
self-closing tag actually makes a difference is in XML-based foreign
content.
Pass 1 additional test.
R=nigeltao
CC=golang-dev
https://golang.org/cl/6450109
変更の背景
このコミットの背景には、HTML5のパース仕様における「自己終了タグ(self-closing tag)」の扱いの複雑さがあります。HTMLでは、<img>
や<br>
のような一部の要素(void要素)のみが自己終了形式(例: <img />
)を許容します。しかし、<div>
や<p>
のような通常の要素(非void要素)に自己終了形式を使用することは、HTMLの構文エラーと見なされます。
従来のパーサーは、HTMLコンテンツ内で非void要素に自己終了フラグが付与された場合、それを誤って解釈し、暗黙的な終了タグが存在するかのように処理していた可能性があります。これはHTMLの仕様に反しており、特にXMLベースのコンテンツ(SVGやMathML)とHTMLコンテンツの間の挙動の不整合を引き起こしていました。
XMLベースのコンテンツ(SVGやMathML)では、自己終了タグは意味を持ち、要素がそこで閉じられることを示します。したがって、パーサーはこれらの「外部コンテンツ(foreign content)」においては自己終了フラグを尊重し、HTMLコンテンツにおいては無視するという、異なる挙動を実装する必要がありました。
このコミットは、この不整合を解消し、HTML5のパース仕様に厳密に準拠するために行われました。具体的には、HTMLコンテンツにおける自己終了フラグをパースエラーとして扱い、暗黙的な終了タグの生成を停止し、同時にSVGやMathMLのような外部コンテンツにおいては自己終了フラグを正しく処理するように修正しています。これにより、関連するテストケースがPASSするようになりました。
前提知識の解説
1. HTMLパーシングの基本
HTMLパーシングは、ブラウザがHTMLドキュメントを読み込み、それをDOM(Document Object Model)ツリーに変換するプロセスです。このプロセスは主に以下の段階に分けられます。
- トークナイゼーション(Tokenization): 入力されたHTML文字列を、タグ、属性、テキストなどの意味のある「トークン」に分解します。
- ツリー構築(Tree Construction): トークンストリームを処理し、DOMツリーを構築します。この段階では、「オープン要素スタック(stack of open elements)」というデータ構造が重要な役割を果たします。これは、現在開いている要素(まだ終了タグが来ていない要素)を追跡するために使用されます。
2. 自己終了タグ(Self-Closing Tags)
自己終了タグは、開始タグと終了タグを一つにまとめた形式のタグです。XMLでは一般的な形式で、<element />
のように記述されます。
3. HTMLにおける自己終了タグの扱い
HTML5の仕様では、自己終了タグの扱いは厳格です。
- Void要素:
<img>
,<br>
,<input>
,<link>
,<meta>
などの要素は「void要素」と呼ばれ、内容を持たず、終了タグも持ちません。これらの要素は、<img src="foo.jpg">
のように記述するのが標準ですが、XHTML互換のために<img src="foo.jpg" />
のように自己終了形式で記述することも許容されます。ただし、この/>
はHTMLパーサーによって単に無視されます。 - 非Void要素:
<div>
,<p>
,<span>
などのほとんどの要素は「非void要素」であり、内容を持ち、必ず開始タグと終了タグのペアが必要です。これらの要素に自己終了形式(例:<div />
)を使用することは、HTMLの構文エラーです。HTMLパーサーは、このような記述を見ても、その要素が自己終了したとは解釈せず、単に/>
を無視し、要素がまだ開いているものとして扱います。
4. XML(XHTML, SVG, MathML)における自己終了タグの扱い
XMLベースの言語(XHTML、SVG、MathMLなど)では、自己終了タグは要素がそこで閉じられることを明示的に示します。例えば、SVGの<circle />
は、そのcircle
要素がそこで終了することを意味します。HTMLパーサーがこれらの「外部コンテンツ(foreign content)」をパースする際には、XMLのルールに従って自己終了タグを処理する必要があります。
5. Go言語のexp/html
パッケージ
exp/html
は、Go言語の標準ライブラリの一部として提案されていた(現在はgolang.org/x/net/html
に移動)HTML5準拠のパーサーです。このパッケージは、HTMLドキュメントをパースしてDOMツリーを構築するための機能を提供します。ウェブブラウザのHTMLレンダリングエンジンが内部で行うパース処理をGo言語で実現するための基盤となります。
技術的詳細
このコミットの核心は、HTML5のパースアルゴリズムにおける「自己終了タグ」と「外部コンテンツ(foreign content)」の処理の厳密な分離と実装です。
HTML5のパースアルゴリズムと自己終了タグ
HTML5のパースアルゴリズムは、非常に詳細に定義されており、エラー処理についても規定されています。非void要素に自己終了フラグ(/>
)が付与された場合、これは「パースエラー」として扱われますが、その要素が即座に閉じられるわけではありません。パーサーは単に/>
を無視し、要素は引き続きオープンな状態であると見なします。
外部コンテンツ(Foreign Content)の扱い
SVG(Scalable Vector Graphics)やMathML(Mathematical Markup Language)は、HTMLドキュメント内に埋め込むことができるXMLベースの言語です。これらは「外部コンテンツ」として扱われ、HTMLのパースルールとは異なるXMLのパースルールが適用されます。外部コンテンツ内では、自己終了タグはXMLのセマンティクスに従って、要素がそこで閉じられることを意味します。
p.hasSelfClosingToken
フラグの役割
p.hasSelfClosingToken
は、現在のトークンが自己終了フラグ(/>
)を持っているかどうかを示すパーサー内部のフラグです。このフラグは、トークナイザーによって設定され、ツリー構築段階で利用されます。
p.oe.pop()
とp.acknowledgeSelfClosingTag()
p.oe.pop()
:p.oe
は「オープン要素スタック(stack of open elements)」を表します。pop()
メソッドは、スタックの最上位にある要素を削除します。これは、要素が正常に閉じられたことを意味します。p.acknowledgeSelfClosingTag()
: このメソッドは、自己終了タグが正しく処理されたことをパーサーに認識させます。具体的には、p.hasSelfClosingToken
フラグをリセットするなどの内部処理を行います。
parseImpliedToken
の削除
以前のコードでは、HTMLコンテンツで自己終了フラグが検出された際にp.parseImpliedToken(EndTagToken, ...)
が呼び出されていました。これは、自己終了フラグをあたかも暗黙的な終了タグが存在するかのように解釈し、要素を閉じてしまう挙動です。しかし、HTMLの仕様では非void要素の自己終了フラグは無視されるべきであり、要素を閉じるべきではありません。このため、この誤った挙動を引き起こすparseImpliedToken
の呼び出しが削除されました。
コアとなるコードの変更箇所
src/pkg/exp/html/parse.go
--- a/src/pkg/exp/html/parse.go
+++ b/src/pkg/exp/html/parse.go
@@ -999,6 +999,10 @@ func inBodyIM(p *parser) bool {
adjustForeignAttributes(p.tok.Attr)
p.addElement()
p.top().Namespace = p.tok.Data
+ if p.hasSelfClosingToken {
+ p.oe.pop()
+ p.acknowledgeSelfClosingTag()
+ }
return true
case a.Caption, a.Col, a.Colgroup, a.Frame, a.Head, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
// Ignore the token.
@@ -2011,8 +2015,8 @@ func (p *parser) parseCurrentToken() {
}
if p.hasSelfClosingToken {
+ // This is a parse error, but ignore it.
p.hasSelfClosingToken = false
- p.parseImpliedToken(EndTagToken, p.tok.DataAtom, p.tok.Data)
}
}
src/pkg/exp/html/testlogs/webkit01.dat.log
--- a/src/pkg/exp/html/testlogs/webkit01.dat.log
+++ b/src/pkg/exp/html/testlogs/webkit01.dat.log
@@ -40,7 +40,7 @@ PASS "<svg><title><div>"
PASS "<svg><title><rect><div>"
PASS "<svg><title><svg><div>"
PASS "<img <=\"\" FAIL>"
-FAIL "<ul><li><div id='foo'/>A</li><li>B<div>C</div></li></ul>"
+PASS "<ul><li><div id='foo'/>A</li><li>B<div>C</div></li></ul>"
PASS "<svg><em><desc></em>"
PASS "<table><tr><td><svg><desc><td></desc><circle>"
PASS "<svg><tfoot></mi><td>"
コアとなるコードの解説
src/pkg/exp/html/parse.go
の変更点
-
外部コンテンツ(SVG/MathMLなど)の自己終了タグ処理の追加:
inBodyIM
関数内の、外部コンテンツ(p.top().Namespace = p.tok.Data
の行がそのコンテキストを示唆)を処理する部分に新しいコードブロックが追加されました。if p.hasSelfClosingToken { p.oe.pop() p.acknowledgeSelfClosingTag() }
この変更により、パーサーは外部コンテンツ内で自己終了フラグを持つトークンを検出した場合、オープン要素スタックから現在の要素をポップし(つまり、要素を閉じ)、自己終了タグが正しく処理されたことを認識します。これは、XMLベースの言語における自己終了タグのセマンティクスに合致する挙動です。
-
HTMLコンテンツにおける自己終了フラグの無視:
parseCurrentToken
関数内の、自己終了フラグを持つトークンを処理する部分が変更されました。- p.parseImpliedToken(EndTagToken, p.tok.DataAtom, p.tok.Data) + // This is a parse error, but ignore it.
以前は、
p.hasSelfClosingToken
がtrue
の場合にp.parseImpliedToken
を呼び出し、暗黙的な終了タグを生成していました。これは、HTMLの非void要素(例:<div>
)に自己終了フラグが付与された場合に、誤ってその要素を閉じてしまう原因となっていました。 変更後、この行は削除され、代わりにコメント// This is a parse error, but ignore it.
が追加されました。これにより、HTMLコンテンツにおいて非void要素に自己終了フラグが付与されても、パーサーは単にそのフラグを無視し、要素を閉じないようになりました。これはHTML5の仕様に準拠した正しい挙動です。p.hasSelfClosingToken = false
は、フラグをリセットするために残されています。
src/pkg/exp/html/testlogs/webkit01.dat.log
の変更点
-FAIL "<ul><li><div id='foo'/>A</li><li>B<div>C</div></li></ul>"
+PASS "<ul><li><div id='foo'/>A</li><li>B<div>C</div></li></ul>"
この変更は、上記のパースロジックの修正が正しく機能したことを示しています。以前は、<ul><li><div id='foo'/>A</li>...
というHTMLスニペットがテストでFAIL
していました。これは、div
要素が非void要素であるにもかかわらず自己終了形式で記述されており、パーサーがこれを誤って解釈していたためと考えられます。今回の修正により、div
の自己終了フラグが正しく無視され、HTML5の仕様に沿ったパース結果が得られるようになったため、テストがPASS
に変わりました。
関連リンク
参考にした情報源リンク
- HTML Standard - 13.2.5.7 The "in body" insertion mode (特に"A start tag whose tag name is one of: "の部分)
- HTML Standard - 13.2.5.1 The "initial" insertion mode (HTMLパーシングの全体的な流れ)
- HTML Standard - 13.2.5.10 Foreign content
- HTML Standard - 13.2.5.1 The "initial" insertion mode - Self-closing start tag token
- HTML Standard - 13.2.5.1 The "initial" insertion mode - Acknowledging the self-closing flag
- HTML Standard - 13.2.5.1 The "initial" insertion mode - Parse error
- HTML Standard - 13.2.5.1 The "initial" insertion mode - Void elements
- HTML Standard - 13.2.5.1 The "initial" insertion mode - Stack of open elements
- W3C - HTML5: A vocabulary and associated APIs for HTML and XHTML - 8.1.2.1.1 Start tags (特に自己終了タグに関する記述)
- W3C - HTML5: A vocabulary and associated APIs for HTML and XHTML - 8.1.2.1.2 End tags
- W3C - HTML5: A vocabulary and associated APIs for HTML and XHTML - 8.1.2.6 Void elements
- W3C - HTML5: A vocabulary and associated APIs for HTML and XHTML - 8.2.5.4.7 The "in body" insertion mode
- W3C - HTML5: A vocabulary and associated APIs for HTML and XHTML - 8.2.5.4.10 Foreign content
- MDN Web Docs - HTML elements reference (各HTML要素のカテゴリ(void/non-void)を確認するのに役立つ)
- MDN Web Docs - SVG
- MDN Web Docs - MathML