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

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

このコミットは、Go言語の encoding/xml パッケージにおけるXMLエンティティ名の解析ロジックを拡張し、より広範な文字セットと特定の記号(ハイフンなど)をエンティティ名として許可するように修正したものです。これにより、以前は許可されていなかったマルチバイト文字や特定のシングルバイト文字を含むエンティティのパースが可能になり、XML仕様への準拠が向上しました。

コミット

commit 2e67dd861d339ea2b8e59ebc479f37cc1840814a
Author: Patrick Smith <pat42smith@gmail.com>
Date:   Sun Oct 21 20:33:24 2012 -0400

    encoding/xml: expand allowed entity names
    
    Previously, multi-byte characters were not allowed. Also certain single-byte
    characters, such as '-', were disallowed.
    Fixes #3813.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6641052

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

https://github.com/golang/go/commit/2e67dd861d339ea2b8e59ebc479f37cc1840814a

元コミット内容

encoding/xml パッケージにおいて、許可されるエンティティ名を拡張しました。 以前は、マルチバイト文字が許可されていませんでした。また、ハイフン('-')のような特定のシングルバイト文字も許可されていませんでした。 この変更は、Issue #3813 を修正します。

変更の背景

この変更の背景には、Go言語の encoding/xml パッケージがXML仕様で定義されているエンティティ名の規則を完全にサポートしていなかったという問題があります。具体的には、以下の2つの主要な制約がありました。

  1. マルチバイト文字の不許可: XMLエンティティ名には、Unicode文字(マルチバイト文字を含む)を使用できますが、Goのパーサーはこれを正しく扱えず、ASCII範囲外の文字を含むエンティティ名を拒否していました。
  2. 特定のシングルバイト文字の不許可: XMLのName文字セットには、ハイフン(-)やピリオド(.)などの特定の記号が含まれますが、Goのパーサーはこれらの文字をエンティティ名の一部として認識していませんでした。

これらの制約により、GoのXMLパーサーは、XML仕様に完全に準拠したドキュメントを正しく解析できない場合がありました。特に、外部のXMLデータソースを扱う際に、エンティティ参照が正しく解決されず、パースエラーが発生する可能性がありました。コミットメッセージで言及されている Fixes #3813 は、この問題がGoのIssueトラッカーで報告され、修正が求められていたことを示しています。

前提知識の解説

XMLエンティティとは

XMLエンティティは、XMLドキュメント内で特定のテキストや構造を再利用するためのメカニズムです。これらは、特殊文字(例: <&lt; で表現)、繰り返し使用されるテキスト、または外部ファイルのコンテンツを参照するために使用されます。

XMLエンティティには主に以下の種類があります。

  • 組み込みエンティティ: XMLに標準で定義されているエンティティで、&lt; (<), &gt; (>), &amp; (&), &apos; ('), &quot; (") などがあります。これらは常に認識されます。
  • 文字参照: 特定のUnicode文字を数値で参照する方法です。&#DDDD;(10進数)または &#xHHHH;(16進数)の形式で記述されます。例: &#x767d; は「白」を表します。
  • 一般エンティティ: ユーザーがDTD(Document Type Definition)内で定義するエンティティです。<!ENTITY name "replacement text"> の形式で定義され、&name; の形式で参照されます。
  • パラメータエンティティ: DTD内でDTDのコンテンツを再利用するために使用されるエンティティです。%name; の形式で参照されます。

このコミットは、特に一般エンティティと文字参照の解析ロジックに影響を与えます。

XMLのName文字セット

XMLの仕様では、「Name」として使用できる文字の厳密な規則が定義されています。これは、要素名、属性名、エンティティ名などに適用されます。XML 1.0 (Fifth Edition) の仕様では、Nameは以下の規則に従います。

  • Nameは、NameStartCharで始まり、その後にNameCharが続く必要があります。
  • NameStartChar: 文字、_(アンダースコア)、:(コロン)
  • NameChar: NameStartChar、数字、-(ハイフン)、.(ピリオド)、結合文字、拡張子

ここで重要なのは、ハイフン(-)やピリオド(.)がNameCharに含まれることです。また、NameStartCharとNameCharは、Unicodeの特定の文字範囲(Letter、Digitなど)に基づいて定義されており、ASCII文字だけでなく、様々な言語の文字(マルチバイト文字)も含まれます。

Go言語の encoding/xml パッケージ

Go言語の標準ライブラリである encoding/xml パッケージは、XMLドキュメントのエンコードとデコードを提供します。このパッケージは、XMLパーサーを実装しており、XMLドキュメントをGoのデータ構造にマッピングしたり、Goのデータ構造からXMLドキュメントを生成したりする機能を提供します。

XMLパーサーは、入力ストリームを読み込み、XMLの構文規則に従ってトークン(要素の開始タグ、終了タグ、文字データ、コメント、処理命令など)に分割します。エンティティ参照もこのトークン化の過程で解決されます。

技術的詳細

このコミットの技術的詳細は、encoding/xml パッケージ内のXMLデコーダがエンティティ参照を解析する方法の変更に集約されます。

以前の実装では、エンティティ名を解析する際に、許可される文字の範囲が非常に限定的でした。特に、d.tmp という固定サイズのバイト配列を使用してエンティティ名を読み込み、ASCII文字の特定の範囲(a-z, A-Z, 0-9, _, #)のみを許可していました。これにより、XML仕様で許可されているハイフン(-)やマルチバイト文字を含むエンティティ名が正しく処理されませんでした。

新しい実装では、以下の点が改善されています。

  1. エンティティ名の読み込みロジックの改善:

    • d.tmp のような固定バッファの使用を廃止し、d.buf (バイトバッファ) を使用してエンティティ名を動的に読み込むようになりました。これにより、エンティティ名の長さに柔軟に対応できます。
    • readName() という新しいヘルパー関数が導入され、XMLのName文字セットの規則に従って名前を読み込むようになりました。この関数は、単一バイト文字だけでなく、マルチバイト文字も正しく読み込みます。
    • エンティティ名の終端をセミコロン(;)で判断するだけでなく、XMLのName文字セットに属さない文字が出現した場合にも名前の終わりと判断するようになりました。
  2. 文字参照の解析の強化:

    • &#DDDD;&#xHHHH; 形式の文字参照の解析ロジックが改善されました。特に、16進数形式の文字参照(&#x...;)の処理がより堅牢になりました。
    • strconv.ParseUint を使用して数値エンティティを解析し、unicode.MaxRune を超えない有効なUnicodeコードポイントであることを確認します。
  3. 一般エンティティの解決ロジックの改善:

    • entity マップ(組み込みエンティティ用)と d.Entity マップ(ユーザー定義エンティティ用)を使用して、エンティティ名を解決するロジックがより明確になりました。
    • isName 関数が導入され、読み込んだバイト列がXMLのName規則に準拠しているかを厳密にチェックするようになりました。これにより、不正なエンティティ名が検出されます。
  4. エラーハンドリングと厳格モード:

    • 不正なエンティティ参照が検出された場合のエラーメッセージが改善されました。
    • d.Strict モード(厳格モード)が導入され、厳格モードでない場合は、未知のエンティティ参照をそのまま出力する(つまり、エラーとしない)という挙動が追加されました。これにより、より寛容なパースが可能になります。

これらの変更により、encoding/xml パッケージはXML仕様のエンティティ名に関する規則に、より正確に準拠するようになりました。

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

src/pkg/encoding/xml/xml.go

Decoder 構造体からの tmp フィールドの削除

--- a/src/pkg/encoding/xml/xml.go
+++ b/src/pkg/encoding/xml/xml.go
@@ -181,7 +181,6 @@ type Decoder struct {
 	ns        map[string]string
 	err       error
 	line      int
-	tmp       [32]byte
 }

エンティティ解析ロジックの変更(Input ラベル内の case '&': 部分)

この部分が最も大きく変更されています。

--- a/src/pkg/encoding/xml/xml.go
+++ b/src/pkg/encoding/xml/xml.go
@@ -877,92 +876,92 @@ Input:
 			// XML in all its glory allows a document to define and use
 			// its own character names with <!ENTITY ...> directives.
 			// Parsers are required to recognize lt, gt, amp, apos, and quot
-			// even if they have not been declared.  That's all we allow.
-			var i int
-			var semicolon bool
-			var valid bool
-			for i = 0; i < len(d.tmp); i++ {
-				var ok bool
-				d.tmp[i], ok = d.getc()
-				if !ok {
-					if d.err == io.EOF {
-						d.err = d.syntaxError("unexpected EOF")
-					}
-					return nil
-				}
-				c := d.tmp[i]
-				if c == ';' {
-					semicolon = true
-					valid = i > 0
-					break
-				}
-				if 'a' <= c && c <= 'z' ||
-					'A' <= c && c <= 'Z' ||
-					'0' <= c && c <= '9' ||
-					c == '_' || c == '#' {
-					continue
-				}
-				d.ungetc(c)
-				break
-			}
-			s := string(d.tmp[0:i])
-			if !valid {
-				if !d.Strict {
-					b0, b1 = 0, 0
-					d.buf.WriteByte('&')
-					d.buf.Write(d.tmp[0:i])
-					if semicolon {
-						d.buf.WriteByte(';')
-					}
-					continue Input
-				}
-				semi := ";"
-				if !semicolon {
-					semi = " (no semicolon)"
-				}
-				if i < len(d.tmp) {
-					d.err = d.syntaxError("invalid character entity &" + s + semi)
-				} else {
-					d.err = d.syntaxError("invalid character entity &" + s + "... too long")
-				}
-				return nil
-			}
-			var haveText bool
-			var text string
-			if i >= 2 && s[0] == '#' {
-				var n uint64
-				var err error
-				if i >= 3 && s[1] == 'x' {
-					n, err = strconv.ParseUint(s[2:], 16, 64)
-				} else {
-					n, err = strconv.ParseUint(s[1:], 10, 64)
-				}
-				if err == nil && n <= unicode.MaxRune {
-					text = string(n)
-					haveText = true
-				}
-			} else {
-				if r, ok := entity[s]; ok {
-					text = string(r)
-					haveText = true
-				} else if d.Entity != nil {
-					text, haveText = d.Entity[s]
-				}
-			}
-			if !haveText {
-				if !d.Strict {
-					b0, b1 = 0, 0
-					d.buf.WriteByte('&')
-					d.buf.Write(d.tmp[0:i])
-					d.buf.WriteByte(';')
-					continue Input
-				}
-				d.err = d.syntaxError("invalid character entity &" + s + ";")
-				return nil
-			}
-			d.buf.Write([]byte(text))
-			b0, b1 = 0, 0
-			continue Input
+			// even if they have not been declared.
+			before := d.buf.Len()
+			d.buf.WriteByte('&')
+			var ok bool
+			var text string
+			var haveText bool
+			if b, ok = d.mustgetc(); !ok {
+				return nil
+			}
+			if b == '#' {
+				d.buf.WriteByte(b)
+				if b, ok = d.mustgetc(); !ok {
+					return nil
+				}
+				base := 10
+				if b == 'x' {
+					base = 16
+					d.buf.WriteByte(b)
+					if b, ok = d.mustgetc(); !ok {
+						return nil
+					}
+				}
+				start := d.buf.Len()
+				for '0' <= b && b <= '9' ||
+					base == 16 && 'a' <= b && b <= 'f' ||
+					base == 16 && 'A' <= b && b <= 'F' {
+					d.buf.WriteByte(b)
+					if b, ok = d.mustgetc(); !ok {
+						return nil
+					}
+				}
+				if b != ';' {
+					d.ungetc(b)
+				} else {
+					s := string(d.buf.Bytes()[start:])
+					d.buf.WriteByte(';')
+					n, err := strconv.ParseUint(s, base, 64)
+					if err == nil && n <= unicode.MaxRune {
+						text = string(n)
+						haveText = true
+					}
+				}
+			} else {
+				d.ungetc(b)
+				if !d.readName() {
+					if d.err != nil {
+						return nil
+					}
+					ok = false
+				}
+				if b, ok = d.mustgetc(); !ok {
+					return nil
+				}
+				if b != ';' {
+					d.ungetc(b)
+				} else {
+					name := d.buf.Bytes()[before+1:]
+					d.buf.WriteByte(';')
+					if isName(name) {
+						s := string(name)
+						if r, ok := entity[s]; ok {
+							text = string(r)
+							haveText = true
+						} else if d.Entity != nil {
+							text, haveText = d.Entity[s]
+						}
+					}
+				}
+			}
+
+			if haveText {
+				d.buf.Truncate(before)
+				d.buf.Write([]byte(text))
+				b0, b1 = 0, 0
+				continue Input
+			}
+			if !d.Strict {
+				b0, b1 = 0, 0
+				continue Input
+			}
+			ent := string(d.buf.Bytes()[before])
+			if ent[len(ent)-1] != ';' {
+				ent += " (no semicolon)"
+			}
+			d.err = d.syntaxError("invalid character entity " + ent)
+			return nil
 		}

name() 関数の変更と readName() 関数の追加

name() 関数が readName() を呼び出すように変更され、readName() が実際の名前の読み込みロジックを担当するようになりました。

--- a/src/pkg/encoding/xml/xml.go
+++ b/src/pkg/encoding/xml/xml.go
@@ -1030,18 +1029,34 @@ func (d *Decoder) nsname() (name Name, ok bool) {
 // Do not set d.err if the name is missing (unless unexpected EOF is received):
 // let the caller provide better context.
 func (d *Decoder) name() (s string, ok bool) {
+\td.buf.Reset()
+\tif !d.readName() {
+\t\treturn "", false
+\t}
+\n+\t// Now we check the characters.
+\ts = d.buf.String()
+\tif !isName([]byte(s)) {
+\t\td.err = d.syntaxError("invalid XML name: " + s)
+\t\treturn "", false
+\t}\n+\treturn s, true
+}
+\n+// Read a name and append its bytes to d.buf.
+// The name is delimited by any single-byte character not valid in names.
+// All multi-byte characters are accepted; the caller must check their validity.
+func (d *Decoder) readName() (ok bool) {
 	var b byte
 	if b, ok = d.mustgetc(); !ok {
 		return
 	}
-\n-\t// As a first approximation, we gather the bytes [A-Za-z_:.-\x80-\xFF]*
 	if b < utf8.RuneSelf && !isNameByte(b) {
 		d.ungetc(b)
-\t\treturn "", false
+\t\treturn false
 	}
-\td.buf.Reset()
 	d.buf.WriteByte(b)
+\n 	for {
 		if b, ok = d.mustgetc(); !ok {
 			return
 		}
@@ -1052,16 +1067,7 @@ func (d *Decoder) name() (s string, ok bool) {
 		}
 		d.buf.WriteByte(b)
 	}
-\n-\t// Then we check the characters.\n-\ts = d.buf.String()
-\tfor i, c := range s {\n-\t\tif !unicode.Is(first, c) && (i == 0 || !unicode.Is(second, c)) {\n-\t\t\td.err = d.syntaxError("invalid XML name: " + s)\n-\t\t\treturn "", false
-\t\t}\n-\t}\n-\treturn s, true
+\treturn true
 }

isName 関数の追加

XMLのName規則に準拠しているかをチェックする新しい関数です。

--- a/src/pkg/encoding/xml/xml.go
+++ b/src/pkg/encoding/xml/xml.go
@@ -1071,6 +1077,30 @@ func isNameByte(c byte) bool {
 	\tc == '_' || c == ':' || c == '.' || c == '-'
 }
 
+func isName(s []byte) bool {
+	if len(s) == 0 {
+		return false
+	}
+	c, n := utf8.DecodeRune(s)
+	if c == utf8.RuneError && n == 1 {
+		return false
+	}
+	if !unicode.Is(first, c) {
+		return false
+	}
+	for n < len(s) {
+		s = s[n:]
+		c, n = utf8.DecodeRune(s)
+		if c == utf8.RuneError && n == 1 {
+			return false
+		}
+		if !unicode.Is(first, c) && !unicode.Is(second, c) {
+			return false
+		}
+	}
+	return true
+}
+
 // These tables were generated by cut and paste from Appendix B of
 // the XML spec at http://www.xml.com/axml/testaxml.htm
 // and then reformatting.  First corresponds to (Letter | '_' | ':')

src/pkg/encoding/xml/xml_test.go

新しいテストケースの追加

testInput に新しい query 要素が追加され、カスタムエンティティ &何;&is-it; が含まれています。

--- a/src/pkg/encoding/xml/xml_test.go
+++ b/src/pkg/encoding/xml/xml_test.go
@@ -19,6 +19,7 @@ const testInput = `
  <body xmlns:foo="ns1" xmlns="ns2" xmlns:tag="ns3" ` +\
  	"\r\n\t" + `  >
    <hello lang="en">World &lt;&gt;&apos;&quot; &#x767d;&#40300;翔</hello>
+  <query>&何; &is-it;</query>
    <goodbye />
    <outer foo:attr="value" xmlns:tag="ns4">
      <inner/>
@@ -28,6 +29,8 @@ const testInput = `
    </tag:name>
  </body><!-- missing final newline -->`
 
+var testEntity = map[string]string{"何": "What", "is-it": "is it?"}
+
 var rawTokens = []Token{
  	CharData("\n"),
  	ProcInst{"xml", []byte(`version="1.0" encoding="UTF-8"`)},
@@ -41,6 +44,10 @@ var rawTokens = []Token{
  	CharData("World <>'\" 白鵬翔"),
  	EndElement{Name{"", "hello"}},
  	CharData("\n  "),
+	StartElement{Name{"", "query"}, []Attr{}},
+	CharData("What is it?"),
+	EndElement{Name{"", "query"}},
+	CharData("\n  "),
  	StartElement{Name{"", "goodbye"}, []Attr{}},
  	EndElement{Name{"", "goodbye"}},
  	CharData("\n  "),
@@ -74,6 +81,10 @@ var cookedTokens = []Token{
  	CharData("World <>'\" 白鵬翔"),
  	EndElement{Name{"ns2", "hello"}},
  	CharData("\n  "),
+	StartElement{Name{"ns2", "query"}, []Attr{}},
+	CharData("What is it?"),
+	EndElement{Name{"ns2", "query"}},
+	CharData("\n  "),
  	StartElement{Name{"ns2", "goodbye"}, []Attr{}},
  	EndElement{Name{"ns2", "goodbye"}},
  	CharData("\n  "),
@@ -156,6 +167,7 @@ var xmlInput = []string{
  
  func TestRawToken(t *testing.T) {
  	d := NewDecoder(strings.NewReader(testInput))
+\td.Entity = testEntity
  	testRawToken(t, d, rawTokens)
  }
  
@@ -164,8 +176,14 @@ const nonStrictInput = `
  <tag>&unknown;entity</tag>
  <tag>&#123</tag>
  <tag>&#zzz;</tag>
+<tag>&なまえ3;</tag>
+<tag>&lt-gt;</tag>
+<tag>&;</tag>
+<tag>&0a;</tag>
  `
  
+var nonStringEntity = map[string]string{"": "oops!", "0a": "oops!"}
+
 var nonStrictTokens = []Token{
  	CharData("\n"),
  	StartElement{Name{"", "tag"}, []Attr{}},
@@ -184,6 +202,22 @@ var nonStrictTokens = []Token{
  	CharData("&#zzz;"),
  	EndElement{Name{"", "tag"}},
  	CharData("\n"),
+\tStartElement{Name{"", "tag"}, []Attr{}},\n+\tCharData("&なまえ3;"),\n+\tEndElement{Name{"", "tag"}},\n+\tCharData("\n"),\n+\tStartElement{Name{"", "tag"}, []Attr{}},\n+\tCharData("&lt-gt;"),\n+\tEndElement{Name{"", "tag"}},\n+\tCharData("\n"),\n+\tStartElement{Name{"", "tag"}, []Attr{}},\n+\tCharData("&;"),\n+\tEndElement{Name{"", "tag"}},\n+\tCharData("\n"),\n+\tStartElement{Name{"", "tag"}, []Attr{}},\n+\tCharData("&0a;"),\n+\tEndElement{Name{"", "tag"}},\n+\tCharData("\n"),\
 }
  
  func TestNonStrictRawToken(t *testing.T) {
@@ -317,6 +351,7 @@ func TestNestedDirectives(t *testing.T) {\n 
  func TestToken(t *testing.T) {\n  	d := NewDecoder(strings.NewReader(testInput))\n+\td.Entity = testEntity
  
  	for i, want := range cookedTokens {\n  	\thave, err := d.Token()\n```

## コアとなるコードの解説

### `Decoder` 構造体からの `tmp` フィールドの削除

*   **変更前**: `d.tmp [32]byte` は、エンティティ名を一時的に格納するための固定サイズのバッファでした。
*   **変更後**: このフィールドが削除されました。
*   **解説**: 固定サイズのバッファを使用すると、エンティティ名がそのサイズを超えた場合に問題が発生したり、不必要なメモリ割り当てが発生したりする可能性があります。この削除は、エンティティ名の読み込みに `bytes.Buffer` (`d.buf`) を使用する新しい、より柔軟なアプローチへの移行を示しています。

### エンティティ解析ロジックの変更(`Input` ラベル内の `case '&':` 部分)

このセクションは、XMLデコーダが `&` 文字(エンティティ参照の開始)を検出したときの動作を定義しています。

*   **変更前**:
    *   エンティティ名を `d.tmp` に1バイトずつ読み込み、セミコロン(`;`)または許可されていない文字が見つかるまでループしていました。
    *   許可される文字は、`a-z`, `A-Z`, `0-9`, `_`, `#` に限定されていました。
    *   文字参照(`&#...;`)と一般エンティティの解析ロジックが混在しており、複雑でした。
    *   不正なエンティティ名に対するエラーハンドリングが限定的でした。
*   **変更後**:
    *   `d.buf.Len()` を `before` として保存し、エンティティ参照の開始位置を記録します。
    *   `d.buf.WriteByte('&')` で `&` をバッファに書き込みます。
    *   `b == '#'` で文字参照(`&#...;` または `&#x...;`)を検出します。
        *   `base` を10または16に設定し、数値の解析を行います。
        *   `strconv.ParseUint` を使用して数値を解析し、`unicode.MaxRune` を超えないことを確認します。
        *   解析された数値からUnicode文字を生成し、`text` に格納します。
    *   `else` ブロックで一般エンティティの解析を行います。
        *   `d.ungetc(b)` で現在の文字を戻し、`d.readName()` を呼び出してエンティティ名を読み込みます。`readName` はXMLのName規則に従って名前を読み込み、`d.buf` に追加します。
        *   セミコロン(`;`)が見つかった場合、`d.buf.Bytes()[before+1:]` でエンティティ名を取得し、`isName` でXMLのName規則に準拠しているかを確認します。
        *   `entity` マップ(組み込みエンティティ)または `d.Entity` マップ(ユーザー定義エンティティ)から対応するテキストを検索します。
    *   `haveText` が `true` の場合(エンティティが正常に解決された場合)、`d.buf` を `before` の位置まで切り詰め、解決されたテキストを書き込みます。
    *   `!d.Strict` の場合(厳格モードでない場合)、解決できなかったエンティティ参照をそのまま出力し、エラーとしません。
    *   それ以外の場合(厳格モードで解決できなかった場合)、`d.syntaxError` を呼び出してエラーを報告します。
*   **解説**: この変更により、エンティティ解析ロジックが大幅に改善され、XML仕様に準拠したエンティティ名(マルチバイト文字やハイフンを含む)を正しく処理できるようになりました。文字参照と一般エンティティの解析が明確に分離され、エラーハンドリングも強化されています。

### `name()` 関数の変更と `readName()` 関数の追加

*   **変更前 (`name()`):**
    *   `getc()` で1バイトずつ文字を読み込み、`isNameByte` でName文字セットに属するかをチェックしていました。
    *   その後、読み込んだ文字列全体に対して `unicode.Is` を使用してXMLのName規則(NameStartCharとNameChar)に準拠しているかをチェックしていました。
    *   このアプローチは、マルチバイト文字のNameを正しく処理する上で非効率的でした。
*   **変更後 (`name()` と `readName()`):**
    *   `name()` は `d.buf.Reset()` でバッファをクリアし、`d.readName()` を呼び出して実際の名前の読み込みを行います。
    *   `d.readName()` は、`d.mustgetc()` で文字を読み込み、`isNameByte` で単一バイト文字のNameCharをチェックします。マルチバイト文字は無条件に読み込みます。
    *   `name()` は `readName()` が成功した後、`d.buf.String()` で読み込んだ名前を取得し、`isName([]byte(s))` を呼び出して、読み込んだバイト列全体がXMLのName規則に準拠しているかを最終的にチェックします。
*   **解説**: `readName()` を導入することで、名前の読み込みとName規則の検証が分離されました。`readName()` は、Nameを構成する可能性のあるすべてのバイト(マルチバイト文字を含む)を効率的に読み込みます。その後の `isName` 関数で、読み込んだバイト列がXMLのName規則(NameStartCharで始まり、NameCharが続く)に厳密に準拠しているかをUnicodeレベルで検証します。これにより、より正確で効率的なXML Nameの解析が可能になりました。

### `isName` 関数の追加

*   **変更前**: この関数は存在しませんでした。Nameの検証は `name()` 関数内で直接行われていました。
*   **変更後**:
    *   `isName(s []byte)` は、与えられたバイトスライス `s` がXMLのName規則に準拠しているかをチェックします。
    *   `utf8.DecodeRune` を使用して、バイトスライスからUnicodeルーンをデコードします。
    *   最初のルーンが `unicode.Is(first, c)`(NameStartChar)に準拠しているかを確認します。
    *   残りのルーンが `unicode.Is(first, c) || unicode.Is(second, c)`(NameChar)に準拠しているかを確認します。
    *   不正なUTF-8シーケンスや、XMLのName規則に合致しない文字が見つかった場合は `false` を返します。
*   **解説**: この関数は、XMLのName規則をUnicodeレベルで厳密に適用するための中心的な役割を果たします。これにより、マルチバイト文字を含む複雑なNameも正確に検証できるようになり、パーサーの堅牢性が向上しました。

### `src/pkg/encoding/xml/xml_test.go` の変更

*   **`testInput` の拡張**: `&何;` (マルチバイト文字) と `&is-it;` (ハイフンを含む) という新しいカスタムエンティティを含む `<query>` 要素が追加されました。
*   **`testEntity` マップの追加**: これらのカスタムエンティティとその解決結果 (`"What"`, `"is it?"`) を定義するマップが追加されました。
*   **`NewDecoder` への `d.Entity = testEntity` の設定**: テストデコーダにカスタムエンティティマップを設定することで、新しいエンティティ解析ロジックが正しく機能するかを検証します。
*   **`rawTokens` と `cookedTokens` の更新**: 新しいエンティティが正しくトークン化され、解決されることを期待する結果が追加されました。
*   **`nonStrictInput` と `nonStringEntity` の追加**: 厳格モードでない場合の挙動や、不正なエンティティ名(例: `&なまえ3;`, `&lt-gt;`, `&;`, `&0a;`)がどのように処理されるかをテストするためのケースが追加されました。
*   **解説**: これらのテストケースは、コミットの目的である「許可されるエンティティ名の拡張」が正しく実装され、マルチバイト文字やハイフンを含むエンティティが期待通りに解析されることを検証します。また、厳格モードと非厳格モードでの挙動の違いもカバーしています。

## 関連リンク

*   Go CL 6641052: [https://golang.org/cl/6641052](https://golang.org/cl/6641052)
*   Go Issue 3813: [https://golang.org/issue/3813](https://golang.org/issue/3813)

## 参考にした情報源リンク

*   XML 1.0 (Fifth Edition) Specification: [https://www.w3.org/TR/xml/](https://www.w3.org/TR/xml/) (特に "Names and Tokens" のセクション)
*   Unicode Standard: [https://www.unicode.org/versions/latest/](https://www.unicode.org/versions/latest/)
*   Go `encoding/xml` package documentation: [https://pkg.go.dev/encoding/xml](https://pkg.go.dev/encoding/xml)
*   Go `unicode` package documentation: [https://pkg.go.dev/unicode](https://pkg.go.dev/unicode)
*   Go `strconv` package documentation: [https://pkg.go.dev/strconv](https://pkg.go.dev/strconv)