[インデックス 11066] ファイルの概要
このコミットは、Go言語のhtml
パッケージにおけるHTMLパーサーの改善を目的としています。特に、HTMLドキュメント内に埋め込まれたMathML (Mathematical Markup Language) やSVG (Scalable Vector Graphics) といった「外部要素 (foreign elements)」のパース処理の正確性を向上させるための変更が含まれています。具体的には、HTML統合点 (HTML integration points) の導入、タグ名の調整、MathML名前空間の内部的な短縮名変更が行われています。これにより、HTML5仕様に準拠したより堅牢なパース動作が実現されています。
コミット
- コミットハッシュ:
748fab9d11e23b8f8c17cd583f995252ec86bfd3
- 作者: Nigel Tao nigeltao@golang.org
- 日付: Tue Jan 10 11:06:09 2012 +1100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/748fab9d11e23b8f8c17cd583f995252ec86bfd3
元コミット内容
html: foreign element HTML integration points, tag name adjustment,
shorten the MathML namespace abbreviation from "mathml" to "math".
Python's html5lib uses "mathml", but I think that that is an internal
implementation detail; the test cases use "math".
Pass tests10.dat, test 30:
<div><svg><path><foreignObject><math></div>a
| <html>
| <head>
| <body>
| <div>
| <svg svg>
| <svg path>
| <svg foreignObject>
| <math math>
| "a"
R=andybalholm
CC=golang-dev
https://golang.org/cl/5529044
変更の背景
HTML5の仕様では、HTMLドキュメント内にSVGやMathMLといったXMLベースの言語を埋め込むことが可能です。これらの埋め込みコンテンツは「外部要素 (foreign content)」と呼ばれ、HTMLパーサーは通常のHTMLパースルールとは異なるルールでこれらを処理する必要があります。特に、外部要素内には特定の「HTML統合点 (HTML integration points)」が存在し、これらの点では再びHTMLのパースルールが適用されることになります。
このコミットの背景には、Go言語のhtml
パッケージがHTML5の複雑なパース仕様、特に外部要素とHTMLの相互作用をより正確に実装する必要があったことが挙げられます。元の実装では、これらの特殊なケースが十分に考慮されておらず、特定のHTML構造(例: <div><svg><path><foreignObject><math></div>a
)が正しくパースされない問題がありました。
具体的には、以下の点が課題となっていました。
- HTML統合点の不正確な処理: SVGの
<foreignObject>
、<desc>
、<title>
などの要素は、その内部にHTMLコンテンツを持つことができるため、パーサーはこれらの要素に遭遇した際にHTMLパースモードに切り替える必要があります。これが正しく行われていなかった可能性があります。 - SVGタグ名のケースセンシティブな扱い: SVGのタグ名はXMLと同様にケースセンシティブですが、HTMLパーサーは通常、タグ名を小文字に変換して処理します。これにより、SVG要素が正しく認識されない問題が発生していました。
- MathML名前空間の内部表現: MathMLの名前空間の内部的な短縮名が、他の実装(Pythonのhtml5lib)と異なっていたり、テストケースとの整合性が取れていなかったりする可能性がありました。
これらの問題を解決し、HTML5のパース仕様に厳密に準拠することで、より多くのHTMLドキュメントを正確に処理できるようになることが、この変更の主な動機です。
前提知識の解説
このコミットを理解するためには、以下の前提知識が役立ちます。
-
HTML5パースアルゴリズム:
- HTML5のパースは、トークナイザーとツリー構築器の2つの主要なフェーズに分かれます。トークナイザーは入力ストリームをトークン(開始タグ、終了タグ、テキストなど)に変換し、ツリー構築器はこれらのトークンを受け取ってDOMツリーを構築します。
- スタック上のオープン要素 (Stack of open elements): ツリー構築器は、現在開いている要素のスタックを維持します。新しい要素が開始されるとスタックにプッシュされ、要素が閉じられるとスタックからポップされます。
- 挿入モード (Insertion modes): HTML5のパースアルゴリズムは、現在のコンテキストに基づいて異なる「挿入モード」で動作します。例えば、
inBodyIM
(in body insertion mode) やinTableIM
(in table insertion mode) などがあります。各モードは、特定のトークンが検出されたときにどのようにDOMツリーを操作するかを定義します。 - スコープ (Scope): 特定の要素が「スコープ内にある」とは、その要素がスタック上のオープン要素の中に存在し、かつ特定の「停止タグ (stop tags)」よりも下位に位置することを意味します。
popUntil
やelementInScope
のような関数は、このスコープの概念に基づいて要素を検索したり、スタックからポップしたりします。
-
外部要素 (Foreign Content):
- HTMLドキュメント内に埋め込まれたXML名前空間の要素(例: SVG、MathML)を指します。これらの要素はHTMLのパースルールとは異なるXMLのパースルールに従います。
- 名前空間 (Namespace): XMLベースの言語では、要素や属性は名前空間に属します。例えば、SVG要素は
http://www.w3.org/2000/svg
名前空間に、MathML要素はhttp://www.w3.org/1998/Math/MathML
名前空間に属します。HTML要素は通常、XHTML名前空間(http://www.w3.org/1999/xhtml
)に属すると見なされますが、HTML5のパースではデフォルトの名前空間として扱われます。
-
HTML統合点 (HTML Integration Points):
- 外部要素(SVGやMathML)の特定の要素内にHTMLコンテンツを埋め込むことができる場所を指します。これらの要素に遭遇すると、パーサーは一時的にHTMLパースモードに戻り、内部のHTMLコンテンツを処理します。
- SVGにおけるHTML統合点の例:
<foreignObject>
,<desc>
,<title>
。 - MathMLにおけるHTML統合点の例:
<annotation-xml>
要素で、encoding
属性がtext/html
またはapplication/xhtml+xml
である場合。
-
タグ名の調整 (Tag Name Adjustment):
- SVGのタグ名はケースセンシティブです(例:
foreignObject
)。しかし、HTMLパーサーは通常、入力されたタグ名を小文字に変換して処理します。そのため、SVG要素を正しくDOMツリーに構築するためには、パーサーが特定のSVGタグ名を正しいケースに調整する必要があります。
- SVGのタグ名はケースセンシティブです(例:
技術的詳細
このコミットは、主にsrc/pkg/html/foreign.go
、src/pkg/html/node.go
、src/pkg/html/parse.go
の3つのファイルにわたる変更を通じて、HTML5の外部要素とHTML統合点の処理を改善しています。
-
src/pkg/html/foreign.go
の変更:htmlIntegrationPoint
関数の追加: この関数は、与えられたノードがHTML統合点であるかどうかを判定します。具体的には、MathML名前空間の要素(annotation-xml
の特定のエンコーディングを持つもの)や、SVG名前空間のdesc
、foreignObject
、title
要素をHTML統合点として識別します。これにより、パーサーがこれらの要素に遭遇した際に、適切なパースモードに切り替えるためのトリガーとなります。svgTagNameAdjustments
マップの追加: SVGのタグ名はケースセンシティブであるため、パーサーが小文字で受け取ったタグ名を正しいケースに変換するためのマップが導入されました。例えば、foreignobject
はforeignObject
に、clippath
はclipPath
に調整されます。これは、SVG要素がDOMツリーで正しく表現されるために重要です。
-
src/pkg/html/node.go
の変更:Node
構造体のコメントが更新され、名前空間の短縮形に関する説明が追加されました。特に、空の名前空間がXHTMLを意味すること、math
がMathMLの名前空間の短縮形であること、svg
がSVGの名前空間の短縮形であることが明記されました。これは、parse.go
でのMathML名前空間の短縮名変更と関連しています。
-
src/pkg/html/parse.go
の変更:- MathML名前空間の短縮名変更: 以前は内部的にMathMLの名前空間を
"mathml"
と略していましたが、これを"math"
に変更しました。コミットメッセージによると、Pythonのhtml5libは内部で"mathml"
を使用しているものの、テストケースでは"math"
が使われているため、整合性を取るための変更です。これにより、math
要素が検出された際に、p.top().Namespace
に"math"
が設定されるようになります。 scope
列挙型の導入とpopUntil
/indexOfElementInScope
/clearStackToContext
の改善:- 以前は
popUntil
やindexOfElementInScope
関数がstopTags []string
という文字列スライスを直接引数として受け取っていましたが、これをscope
という新しい列挙型(defaultScope
,listItemScope
,buttonScope
,tableScope
,tableRowScope
)に置き換えました。 - これにより、各スコープに対応する停止タグのセットが
defaultScopeStopTags
マップに一元的に定義され、コードの可読性と保守性が向上しました。また、特定のスコープ(例:listItemScope
やbuttonScope
)における追加の停止条件がindexOfElementInScope
関数内で明示的に処理されるようになりました。 clearStackToContext
関数も同様にscope
列挙型を使用するように変更され、テーブル関連のスコープ(tableScope
,tableRowScope
)におけるスタッククリアのロジックがより明確になりました。
- 以前は
parseForeignContent
関数の改善:- 外部コンテンツのパース中に開始タグが検出された際、
htmlIntegrationPoint(p.top())
が真であれば、inBodyIM(p)
を呼び出してHTMLパースモードに切り替えるロジックが追加されました。これにより、SVGの<foreignObject>
などの内部にHTMLコンテンツが正しくパースされるようになります。 - SVG名前空間の要素に対して、
svgTagNameAdjustments
マップを使用してタグ名を調整するロジックが追加されました。これにより、SVGのケースセンシティブなタグ名が正しく処理されます。
- 外部コンテンツのパース中に開始タグが検出された際、
inBodyIM
、inTableIM
などの挿入モード関数の更新:popUntil
やelementInScope
の呼び出しが、新しいscope
列挙型を使用するように変更されました。これにより、HTML5のパースアルゴリズムの各ステップが、より正確なスコープ定義に基づいて実行されるようになります。
- MathML名前空間の短縮名変更: 以前は内部的にMathMLの名前空間を
これらの変更は、HTML5の複雑なパース仕様、特に外部要素とHTMLの相互作用に関する部分を、より正確かつ堅牢に実装するためのものです。
コアとなるコードの変更箇所
src/pkg/html/foreign.go
--- a/src/pkg/html/foreign.go
+++ b/src/pkg/html/foreign.go
@@ -23,6 +23,23 @@ func adjustForeignAttributes(aa []Attribute) {
}
}
+func htmlIntegrationPoint(n *Node) bool {
+ if n.Type != ElementNode {
+ return false
+ }
+ switch n.Namespace {
+ case "math":
+ // TODO: annotation-xml elements whose start tags have "text/html" or
+ // "application/xhtml+xml" encodings.
+ case "svg":
+ switch n.Data {
+ case "desc", "foreignObject", "title":
+ return true
+ }
+ }
+ return false
+}
+
// Section 12.2.5.5.
var breakout = map[string]bool{
"b": true,
@@ -72,4 +89,44 @@ var breakout = map[string]bool{
"var": true,
}
-// TODO: add look-up tables for MathML and SVG adjustments.
+// Section 12.2.5.5.
+var svgTagNameAdjustments = map[string]string{
+ "altglyph": "altGlyph",
+ "altglyphdef": "altGlyphDef",
+ "altglyphitem": "altGlyphItem",
+ "animatecolor": "animateColor",
+ "animatemotion": "animateMotion",
+ "animatetransform": "animateTransform",
+ "clippath": "clipPath",
+ "feblend": "feBlend",
+ "fecolormatrix": "feColorMatrix",
+ "fecomponenttransfer": "feComponentTransfer",
+ "fecomposite": "feComposite",
+ "feconvolvematrix": "feConvolveMatrix",
+ "fediffuselighting": "feDiffuseLighting",
+ "fedisplacementmap": "feDisplacementMap",
+ "fedistantlight": "feDistantLight",
+ "feflood": "feFlood",
+ "fefunca": "feFuncA",
+ "fefuncb": "feFuncB",
+ "fefuncg": "feFuncG",
+ "fefuncr": "feFuncR",
+ "fegaussianblur": "feGaussianBlur",
+ "feimage": "feImage",
+ "femerge": "feMerge",
+ "femergenode": "feMergeNode",
+ "femorphology": "feMorphology",
+ "feoffset": "feOffset",
+ "fepointlight": "fePointLight",
+ "fespecularlighting": "feSpecularLighting",
+ "fespotlight": "feSpotLight",
+ "fetile": "feTile",
+ "feturbulence": "feTurbulence",
+ "foreignobject": "foreignObject",
+ "glyphref": "glyphRef",
+ "lineargradient": "linearGradient",
+ "radialgradient": "radialGradient",
+ "textpath": "textPath",
+}
+
+// TODO: add look-up tables for MathML and SVG attribute adjustments.
src/pkg/html/node.go
--- a/src/pkg/html/node.go
+++ b/src/pkg/html/node.go
@@ -26,6 +26,10 @@ var scopeMarker = Node{Type: scopeMarkerNode}\n // content for text) and are part of a tree of Nodes. Element nodes may also\n // have a Namespace and contain a slice of Attributes. Data is unescaped, so\n // that it looks like "a<b" rather than "a<b".\n+//\n+// An empty Namespace implies a "http://www.w3.org/1999/xhtml" namespace.\n+// Similarly, "math" is short for "http://www.w3.org/1998/Math/MathML", and\n+// "svg" is short for "http://www.w3.org/2000/svg".
type Node struct {\n Parent *Node\n Child []*Node
src/pkg/html/parse.go
--- a/src/pkg/html/parse.go
+++ b/src/pkg/html/parse.go
@@ -51,58 +51,87 @@ func (p *parser) top() *Node {
return p.doc
}
-// stopTags for use in popUntil. These come from section 12.2.3.2.
+// Stop tags for use in popUntil. These come from section 12.2.3.2.
var (
-\tdefaultScopeStopTags = []string{"applet", "caption", "html", "table", "td", "th", "marquee", "object"}
-\tlistItemScopeStopTags = []string{"applet", "caption", "html", "table", "td", "th", "marquee", "object", "ol", "ul"}
-\tbuttonScopeStopTags = []string{"applet", "caption", "html", "table", "td", "th", "marquee", "object", "button"}
-\ttableScopeStopTags = []string{"html", "table"}
+\tdefaultScopeStopTags = map[string][]string{
+\t\t"": {"applet", "caption", "html", "table", "td", "th", "marquee", "object"},
+\t\t"math": {"annotation-xml", "mi", "mn", "mo", "ms", "mtext"},
+\t\t"svg": {"desc", "foreignObject", "title"},
+\t}
)
-// stopTags for use in clearStackToContext.
-var (
-\ttableRowContextStopTags = []string{"tr", "html"}
+type scope int
+
+const (
+\tdefaultScope scope = iota
+\tlistItemScope
+\tbuttonScope
+\ttableScope
+\ttableRowScope
)
// popUntil pops the stack of open elements at the highest element whose tag
-// is in matchTags, provided there is no higher element in stopTags. It returns
-// whether or not there was such an element. If there was not, popUntil leaves
-// the stack unchanged.\n//\n-// For example, if the stack was:\n+// is in matchTags, provided there is no higher element in the scope\'s stop
+// tags (as defined in section 12.2.3.2). It returns whether or not there was
+// such an element. If there was not, popUntil leaves the stack unchanged.
+//
+// For example, the set of stop tags for table scope is: "html", "table". If
+// the stack was:
// ["html", "body", "font", "table", "b", "i", "u"]
-// then popUntil([]string{"html, "table"}, "font") would return false, but
-// popUntil([]string{"html, "table"}, "i") would return true and the resultant
-// stack would be:\n// ["html", "body", "font", "table", "b"]
+// then popUntil(tableScope, "font") would return false, but
+// popUntil(tableScope, "i") would return true and the stack would become:
+// ["html", "body", "font", "table", "b"]
//
-// If an element\'s tag is in both stopTags and matchTags, then the stack will
-// be popped and the function returns true (provided, of course, there was no
-// higher element in the stack that was also in stopTags). For example,\n-// popUntil([]string{"html, "table"}, "table") would return true and leave:\n+// If an element\'s tag is in both the stop tags and matchTags, then the stack
+// will be popped and the function returns true (provided, of course, there was
+// no higher element in the stack that was also in the stop tags). For example,
+// popUntil(tableScope, "table") returns true and leaves:
// ["html", "body", "font"]
-func (p *parser) popUntil(stopTags []string, matchTags ...string) bool {
-\tif i := p.indexOfElementInScope(stopTags, matchTags...); i != -1 {\n+\tfunc (p *parser) popUntil(s scope, matchTags ...string) bool {
+\tif i := p.indexOfElementInScope(s, matchTags...); i != -1 {
\t\tp.oe = p.oe[:i]\n \t\treturn true\n \t}\n \treturn false\n }\n \n-// indexOfElementInScope returns the index in p.oe of the highest element\n-// whose tag is in matchTags that is in scope according to stopTags.\n-// If no matching element is in scope, it returns -1.\n-func (p *parser) indexOfElementInScope(stopTags []string, matchTags ...string) int {\n+// indexOfElementInScope returns the index in p.oe of the highest element whose
+// tag is in matchTags that is in scope. If no matching element is in scope, it
+// returns -1.
+func (p *parser) indexOfElementInScope(s scope, matchTags ...string) int {
\tfor i := len(p.oe) - 1; i >= 0; i-- {\n \t\ttag := p.oe[i].Data\n-\t\tfor _, t := range matchTags {\n-\t\t\tif t == tag {\n-\t\t\t\treturn i\n+\t\tif p.oe[i].Namespace == "" {
+\t\t\tfor _, t := range matchTags {
+\t\t\t\tif t == tag {
+\t\t\t\t\treturn i
+\t\t\t\t}
+\t\t\t}
+\t\t\tswitch s {
+\t\t\tcase defaultScope:
+\t\t\t\t// No-op.
+\t\t\tcase listItemScope:
+\t\t\t\tif tag == "ol" || tag == "ul" {
+\t\t\t\t\treturn -1
+\t\t\t\t}
+\t\t\tcase buttonScope:
+\t\t\t\tif tag == "button" {
+\t\t\t\t\treturn -1
+\t\t\t\t}
+\t\t\tcase tableScope:
+\t\t\t\tif tag == "html" || tag == "table" {
+\t\t\t\t\treturn -1
+\t\t\t\t}
+\t\t\tdefault:
+\t\t\t\tpanic("unreachable")
\t\t\t}\n \t\t}\n-\t\tfor _, t := range stopTags {\n-\t\t\tif t == tag {\n-\t\t\t\treturn -1\n+\t\tswitch s {
+\t\tcase defaultScope, listItemScope, buttonScope:
+\t\t\tfor _, t := range defaultScopeStopTags[p.oe[i].Namespace] {
+\t\t\t\tif t == tag {
+\t\t\t\t\treturn -1
+\t\t\t\t}
\t\t\t}\n \t\t}\n \t}\n@@ -111,8 +140,30 @@ func (p *parser) indexOfElementInScope(stopTags []string, matchTags ...string) i\n \n // elementInScope is like popUntil, except that it doesn\'t modify the stack of\n // open elements.\n-func (p *parser) elementInScope(stopTags []string, matchTags ...string) bool {\n-\treturn p.indexOfElementInScope(stopTags, matchTags...) != -1\n+func (p *parser) elementInScope(s scope, matchTags ...string) bool {\n+\treturn p.indexOfElementInScope(s, matchTags...) != -1\n+}\n+\n+// clearStackToContext pops elements off the stack of open elements until a
+// scope-defined element is found.
+func (p *parser) clearStackToContext(s scope) {
+\tfor i := len(p.oe) - 1; i >= 0; i-- {
+\t\ttag := p.oe[i].Data
+\t\tswitch s {
+\t\tcase tableScope:
+\t\t\tif tag == "html" || tag == "table" {
+\t\t\t\tp.oe = p.oe[:i+1]
+\t\t\t\treturn
+\t\t\t}
+\t\tcase tableRowScope:
+\t\t\tif tag == "html" || tag == "tr" {
+\t\t\t\tp.oe = p.oe[:i+1]
+\t\t\t\treturn
+\t\t\t}
+\t\tdefault:
+\t\t\tpanic("unreachable")
+\t\t}
+\t}
}\n \n // addChild adds a child node n to the top element, and pushes n onto the stack
@@ -624,10 +675,10 @@ func inBodyIM(p *parser) bool {
\t\t\tData: p.tok.Data,\n \t\t})\n \tcase StartTagToken:\n+\t\tif htmlIntegrationPoint(p.top()) {
+\t\t\tinBodyIM(p)
+\t\t\tp.resetInsertionMode()
+\t\t\treturn true
+\t\t}
\t\tif breakout[p.tok.Data] {
\t\t\tfor i := len(p.oe) - 1; i >= 0; i-- {
\t\t\t\t// TODO: HTML, MathML integration points.\n@@ -1683,10 +1723,14 @@ func parseForeignContent(p *parser) bool {\n \t\t\treturn false\n \t\t}\n \t\tswitch p.top().Namespace {\n-\t\tcase \"mathml\":
+\t\tcase \"math\":
\t\t\t// TODO: adjust MathML attributes.\n \t\tcase \"svg\":
-\t\t\t// TODO: adjust SVG tag names.\n+\t\t\t// Adjust SVG tag names. The tokenizer lower-cases tag names, but
+\t\t\t// SVG wants e.g. "foreignObject" with a capital second "O".
+\t\t\tif x := svgTagNameAdjustments[p.tok.Data]; x != "" {
+\t\t\t\tp.tok.Data = x
+\t\t\t}\n \t\t\t// TODO: adjust SVG attributes.\n \t\tdefault:\n \t\t\tpanic("html: bad parser state: unexpected namespace")
src/pkg/html/parse_test.go
--- a/src/pkg/html/parse_test.go
+++ b/src/pkg/html/parse_test.go
@@ -184,7 +184,7 @@ func TestParser(t *testing.T) {
\t\t{"tests4.dat", -1},
\t\t{"tests5.dat", -1},
\t\t{"tests6.dat", -1},
-\t\t\t{"tests10.dat", 30},
+\t\t\t{"tests10.dat", 31},
\t}\n \tfor _, tf := range testFiles {
\t\tf, err := os.Open("testdata/webkit/" + tf.filename)
コアとなるコードの解説
htmlIntegrationPoint
関数の追加 (src/pkg/html/foreign.go
)
この関数は、現在のノードがHTML統合点であるかを判定します。SVGの名前空間に属するdesc
、foreignObject
、title
要素がHTML統合点として明示的に定義されました。これにより、パーサーはこれらの要素の内部でHTMLパースモードに切り替えるべきタイミングを正確に判断できるようになります。これは、HTML5仕様の外部要素の処理において非常に重要な部分です。
svgTagNameAdjustments
マップの追加 (src/pkg/html/foreign.go
)
このマップは、SVGのタグ名がHTMLパーサーによって小文字に変換されてしまう問題に対処するためのものです。SVGのタグ名(例: foreignObject
)はケースセンシティブであるため、パーサーが受け取った小文字のタグ名を、このマップを使って正しいケースに調整します。これにより、DOMツリー上でSVG要素が正しいタグ名で表現され、後続の処理やレンダリングが正しく行われるようになります。
Node
構造体の名前空間コメント更新 (src/pkg/html/node.go
)
Node
構造体のコメントに、名前空間の短縮形に関する説明が追加されました。特に、"math"
がMathMLの名前空間の短縮形として使用されることが明記され、parse.go
でのMathML名前空間の内部表現の変更と整合性が取られています。
scope
列挙型の導入とpopUntil
/indexOfElementInScope
/clearStackToContext
の改善 (src/pkg/html/parse.go
)
これは、HTMLパースアルゴリズムの「スタック上のオープン要素」の操作に関する重要なリファクタリングです。
- 以前は、
popUntil
やindexOfElementInScope
関数が、停止タグのリストを直接文字列スライスとして受け取っていました。これは、コードの可読性を低下させ、異なるスコープでの停止タグの管理を複雑にしていました。 - 新しい
scope
列挙型(defaultScope
,listItemScope
,buttonScope
,tableScope
,tableRowScope
)を導入することで、各パースコンテキストにおける停止タグのセットが明確に定義され、コードがより構造化されました。 defaultScopeStopTags
マップは、名前空間ごとに停止タグを定義し、特にMathMLとSVGの名前空間におけるHTML統合点となる要素(annotation-xml
,mi
,mn
,mo
,ms
,mtext
for MathML;desc
,foreignObject
,title
for SVG)が停止タグとして追加されました。- これにより、パーサーは特定のスコープ内で要素を検索したり、スタックからポップしたりする際に、より正確かつ効率的に停止条件を適用できるようになりました。これは、HTML5の複雑なパースルール、特に要素のネストと暗黙的な閉じ方を正確に実装するために不可欠です。
parseForeignContent
関数の改善 (src/pkg/html/parse.go
)
- 外部コンテンツのパース中に開始タグが検出された際、
htmlIntegrationPoint(p.top())
がtrue
を返す場合(つまり、現在の要素がHTML統合点である場合)、inBodyIM(p)
を呼び出してHTMLパースモードに切り替えるロジックが追加されました。これにより、SVGの<foreignObject>
などの内部に埋め込まれたHTMLコンテンツが正しくパースされるようになります。 - SVG名前空間の要素に対して、
svgTagNameAdjustments
マップを使用してタグ名を調整する処理が追加されました。これにより、SVGのケースセンシティブなタグ名が正しくDOMツリーに反映されます。
MathML名前空間の短縮名変更 (src/pkg/html/parse.go
)
math
要素が検出された際に、その名前空間を"mathml"
ではなく"math"
として設定するように変更されました。これは、内部的な整合性と、他のHTML5パーサー実装(特にテストケース)との互換性を高めるための調整です。
これらの変更は、Go言語のhtml
パッケージがHTML5の仕様に、より厳密に準拠し、特に外部要素のパースにおける正確性と堅牢性を向上させるための重要なステップです。
関連リンク
- Go CL 5529044: https://golang.org/cl/5529044
参考にした情報源リンク
- HTML5 Parsing algorithm (W3C Recommendation): https://html.spec.whatwg.org/multipage/parsing.html
- HTML5 Foreign content (W3C Recommendation): https://html.spec.whatwg.org/multipage/syntax.html#foreign-content
- HTML5 HTML integration points (W3C Recommendation): https://html.spec.whatwg.org/multipage/syntax.html#html-integration-point
- HTML5 Adjust SVG attributes (W3C Recommendation): https://html.spec.whatwg.org/multipage/parsing.html#adjust-svg-attributes
- HTML5 Adjust MathML attributes (W3C Recommendation): https://html.spec.whatwg.org/multipage/parsing.html#adjust-mathml-attributes
- HTML5 The stack of open elements (W3C Recommendation): https://html.spec.whatwg.org/multipage/parsing.html#the-stack-of-open-elements
- HTML5 Insertion modes (W3C Recommendation): https://html.spec.whatwg.org/multipage/parsing.html#insertion-modes
- HTML5 In scope (W3C Recommendation): https://html.spec.whatwg.org/multipage/parsing.html#has-an-element-in-the-specified-scope
- HTML5 Clear the stack of open elements up to one of the scopes (W3C Recommendation): https://html.spec.whatwg.org/multipage/parsing.html#clear-the-stack-of-open-elements-up-to-one-of-the-scopes