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

[インデックス 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)が正しくパースされない問題がありました。

具体的には、以下の点が課題となっていました。

  1. HTML統合点の不正確な処理: SVGの<foreignObject><desc><title>などの要素は、その内部にHTMLコンテンツを持つことができるため、パーサーはこれらの要素に遭遇した際にHTMLパースモードに切り替える必要があります。これが正しく行われていなかった可能性があります。
  2. SVGタグ名のケースセンシティブな扱い: SVGのタグ名はXMLと同様にケースセンシティブですが、HTMLパーサーは通常、タグ名を小文字に変換して処理します。これにより、SVG要素が正しく認識されない問題が発生していました。
  3. MathML名前空間の内部表現: MathMLの名前空間の内部的な短縮名が、他の実装(Pythonのhtml5lib)と異なっていたり、テストケースとの整合性が取れていなかったりする可能性がありました。

これらの問題を解決し、HTML5のパース仕様に厳密に準拠することで、より多くのHTMLドキュメントを正確に処理できるようになることが、この変更の主な動機です。

前提知識の解説

このコミットを理解するためには、以下の前提知識が役立ちます。

  1. 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)」よりも下位に位置することを意味します。popUntilelementInScopeのような関数は、このスコープの概念に基づいて要素を検索したり、スタックからポップしたりします。
  2. 外部要素 (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のパースではデフォルトの名前空間として扱われます。
  3. HTML統合点 (HTML Integration Points):

    • 外部要素(SVGやMathML)の特定の要素内にHTMLコンテンツを埋め込むことができる場所を指します。これらの要素に遭遇すると、パーサーは一時的にHTMLパースモードに戻り、内部のHTMLコンテンツを処理します。
    • SVGにおけるHTML統合点の例: <foreignObject>, <desc>, <title>
    • MathMLにおけるHTML統合点の例: <annotation-xml>要素で、encoding属性がtext/htmlまたはapplication/xhtml+xmlである場合。
  4. タグ名の調整 (Tag Name Adjustment):

    • SVGのタグ名はケースセンシティブです(例: foreignObject)。しかし、HTMLパーサーは通常、入力されたタグ名を小文字に変換して処理します。そのため、SVG要素を正しくDOMツリーに構築するためには、パーサーが特定のSVGタグ名を正しいケースに調整する必要があります。

技術的詳細

このコミットは、主にsrc/pkg/html/foreign.gosrc/pkg/html/node.gosrc/pkg/html/parse.goの3つのファイルにわたる変更を通じて、HTML5の外部要素とHTML統合点の処理を改善しています。

  1. src/pkg/html/foreign.goの変更:

    • htmlIntegrationPoint関数の追加: この関数は、与えられたノードがHTML統合点であるかどうかを判定します。具体的には、MathML名前空間の要素(annotation-xmlの特定のエンコーディングを持つもの)や、SVG名前空間のdescforeignObjecttitle要素をHTML統合点として識別します。これにより、パーサーがこれらの要素に遭遇した際に、適切なパースモードに切り替えるためのトリガーとなります。
    • svgTagNameAdjustmentsマップの追加: SVGのタグ名はケースセンシティブであるため、パーサーが小文字で受け取ったタグ名を正しいケースに変換するためのマップが導入されました。例えば、foreignobjectforeignObjectに、clippathclipPathに調整されます。これは、SVG要素がDOMツリーで正しく表現されるために重要です。
  2. src/pkg/html/node.goの変更:

    • Node構造体のコメントが更新され、名前空間の短縮形に関する説明が追加されました。特に、空の名前空間がXHTMLを意味すること、mathがMathMLの名前空間の短縮形であること、svgがSVGの名前空間の短縮形であることが明記されました。これは、parse.goでのMathML名前空間の短縮名変更と関連しています。
  3. src/pkg/html/parse.goの変更:

    • MathML名前空間の短縮名変更: 以前は内部的にMathMLの名前空間を"mathml"と略していましたが、これを"math"に変更しました。コミットメッセージによると、Pythonのhtml5libは内部で"mathml"を使用しているものの、テストケースでは"math"が使われているため、整合性を取るための変更です。これにより、math要素が検出された際に、p.top().Namespace"math"が設定されるようになります。
    • scope列挙型の導入とpopUntil/indexOfElementInScope/clearStackToContextの改善:
      • 以前はpopUntilindexOfElementInScope関数がstopTags []stringという文字列スライスを直接引数として受け取っていましたが、これをscopeという新しい列挙型(defaultScope, listItemScope, buttonScope, tableScope, tableRowScope)に置き換えました。
      • これにより、各スコープに対応する停止タグのセットがdefaultScopeStopTagsマップに一元的に定義され、コードの可読性と保守性が向上しました。また、特定のスコープ(例: listItemScopebuttonScope)における追加の停止条件がindexOfElementInScope関数内で明示的に処理されるようになりました。
      • clearStackToContext関数も同様にscope列挙型を使用するように変更され、テーブル関連のスコープ(tableScope, tableRowScope)におけるスタッククリアのロジックがより明確になりました。
    • parseForeignContent関数の改善:
      • 外部コンテンツのパース中に開始タグが検出された際、htmlIntegrationPoint(p.top())が真であれば、inBodyIM(p)を呼び出してHTMLパースモードに切り替えるロジックが追加されました。これにより、SVGの<foreignObject>などの内部にHTMLコンテンツが正しくパースされるようになります。
      • SVG名前空間の要素に対して、svgTagNameAdjustmentsマップを使用してタグ名を調整するロジックが追加されました。これにより、SVGのケースセンシティブなタグ名が正しく処理されます。
    • inBodyIMinTableIMなどの挿入モード関数の更新: popUntilelementInScopeの呼び出しが、新しいscope列挙型を使用するように変更されました。これにより、HTML5のパースアルゴリズムの各ステップが、より正確なスコープ定義に基づいて実行されるようになります。

これらの変更は、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&lt;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の名前空間に属するdescforeignObjecttitle要素が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パースアルゴリズムの「スタック上のオープン要素」の操作に関する重要なリファクタリングです。

  • 以前は、popUntilindexOfElementInScope関数が、停止タグのリストを直接文字列スライスとして受け取っていました。これは、コードの可読性を低下させ、異なるスコープでの停止タグの管理を複雑にしていました。
  • 新しい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の仕様に、より厳密に準拠し、特に外部要素のパースにおける正確性と堅牢性を向上させるための重要なステップです。

関連リンク

参考にした情報源リンク