[インデックス 10268] ファイルの概要
本コミットは、Go言語の標準ライブラリである encoding/xml
パッケージにおけるXMLパーシングの挙動を改善するものです。具体的には、XML要素名が単一のアンダースコア _
である場合(例: <_>
)のパースを正しく処理できるように修正が加えられています。これにより、XMLとGo構造体のマッピングにおいて、より柔軟な命名規則に対応できるようになりました。
コミット
commit 1371ac2f0b5ed324f0f6a2ff2c124041a4feaa70
Author: David Crawshaw <david.crawshaw@zentus.com>
Date: Mon Nov 7 10:47:44 2011 -0500
xml: allow parsing of <_> </_>.
R=rsc, nigeltao
CC=golang-dev
https://golang.org/cl/5298061
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1371ac2f0b5ed324f0f6a2ff2c124041a4feaa70
元コミット内容
このコミットは、encoding/xml
パッケージが <_>
のような単一アンダースコアのXML要素を正しくパースできるようにするための変更です。以前のバージョンでは、このような要素名がGoの構造体フィールド名にマッピングされる際に問題が発生していました。
変更の背景
Go言語の encoding/xml
パッケージは、XMLドキュメントをGoの構造体にアンマーシャル(デシリアライズ)する機能を提供します。この際、XML要素名とGo構造体のフィールド名を対応させるための内部ロジックが存在します。
従来の fieldName
関数は、Goの構造体フィールド名がXML要素名と一致しない場合に、特定の変換ルールを適用していました。そのルールの一つに「先頭のアンダースコアを削除する」というものがありました。例えば、XML要素名が _foo
であれば、Goのフィールド名としては Foo
が期待される、といった具合です。
しかし、このロジックには問題がありました。もしXML要素名が単一のアンダースコア _
だった場合、fieldName
関数は先頭のアンダースコアを削除しようとし、結果として空文字列を返してしまっていました。Goの構造体フィールド名として空文字列は無効であり、また、XML要素名 _
を持つデータをGoの構造体にマッピングしたいというユースケースに対応できませんでした。
このコミットは、このようなエッジケース、特にXML要素名が _
である場合に、encoding/xml
パッケージが正しく動作するようにするために導入されました。
前提知識の解説
Go言語の encoding/xml
パッケージ
encoding/xml
パッケージは、Goプログラム内でXMLデータをエンコード(Go構造体からXMLへ)およびデコード(XMLからGo構造体へ)するための機能を提供します。主な機能は以下の通りです。
xml.Unmarshal
: XMLデータをGoの構造体にデコードします。XML要素名と構造体フィールド名のマッピングは、タグ(xml:"element_name"
)によって明示的に指定することも、Goのフィールド名から自動的に推測させることも可能です。xml.Marshal
: Goの構造体をXMLデータにエンコードします。xml.Decoder
/xml.Encoder
: ストリームベースでのXMLの読み書きを可能にします。
XML要素名とGo構造体フィールド名のマッピング
encoding/xml
パッケージでは、XML要素名とGo構造体フィールド名のマッピングにいくつかのルールがあります。
- タグによる明示的な指定: 構造体フィールドに
xml:"element_name"
のようなタグを付けることで、XML要素名を明示的に指定できます。type MyStruct struct { Value string `xml:"my_value_element"` } // <my_value_element>...</my_value_element> に対応
- 自動マッピング: タグが指定されていない場合、Goのフィールド名がXML要素名に変換されます。この変換には、キャメルケースからスネークケースへの変換や、先頭のアンダースコアの処理などが含まれます。例えば、Goのフィールド名
MyField
はXML要素名myField
に、_MyField
はmyField
にマッピングされることが期待されます。
アンダースコア _
の特殊性
Go言語において、アンダースコア _
は特別な意味を持つことがあります。
- ブランク識別子: 変数宣言などで値を破棄する場合に使用されます(例:
_, err := someFunc()
)。 - インポート: パッケージをインポートする際に、そのパッケージの
init
関数を実行するだけで、エクスポートされた識別子を使用しない場合に_
を使用します(例:import _ "image/png"
)。 - XML要素名: XMLの仕様上、要素名にアンダースコアを使用することは許可されています。しかし、Goの
encoding/xml
パッケージの内部処理において、このアンダースコアの扱いが問題となるケースがありました。
技術的詳細
本コミットの技術的詳細の中心は、src/pkg/encoding/xml/read.go
内の fieldName
関数の変更です。
変更前の fieldName
関数
変更前の fieldName
関数は、Goの構造体フィールド名に対応するXML要素名を生成する際に、先頭のアンダースコアをすべて削除するロジックを持っていました。
func fieldName(original string) string {
var i int
//remove leading underscores
for i = 0; i < len(original) && original[i] == '_'; i++ {
}
return strings.Map(...) // 残りの文字列を処理
}
このコードでは、original
文字列の先頭からアンダースコアが続く限り i
をインクリメントします。例えば、original
が __foo
であれば i
は 2
になり、foo
が返されます。
問題は、original
が _
だった場合です。
i = 0
0 < len("_")
(つまり0 < 1
) はtrue
。original[0] == '_'
はtrue
。i
が1
にインクリメントされる。- ループ条件
i < len(original)
(つまり1 < 1
) はfalse
となり、ループを抜ける。 - 結果として、
original
の先頭からi
文字目以降の文字列(この場合はoriginal[1:]
)がstrings.Map
に渡されますが、これは空文字列になります。
これにより、XML要素名 <_>
を持つデータがGoの構造体フィールドにマッピングされる際に、対応するフィールド名が空文字列と解釈され、パースが失敗するか、意図しない挙動を引き起こしていました。
変更後の fieldName
関数
変更後の fieldName
関数は、このエッジケースを考慮して、先頭のアンダースコアを削除するループの条件を修正しました。
func fieldName(original string) string {
var i int
//remove leading underscores, without exhausting all characters
for i = 0; i < len(original)-1 && original[i] == '_'; i++ {
}
return strings.Map(...) // 残りの文字列を処理
}
変更点は、ループ条件が i < len(original)
から i < len(original)-1
になったことです。
この変更により、original
が _
だった場合の挙動は以下のようになります。
i = 0
- ループ条件
i < len(original)-1
(つまり0 < 1-1
->0 < 0
) はfalse
となり、ループは一度も実行されません。 - 結果として
i
は0
のままです。 strings.Map
にはoriginal
全体(つまり_
)が渡され、_
がそのままフィールド名として扱われるようになります。
これにより、XML要素名 <_>
はGoの構造体フィールド名 _
にマッピングされることが可能になり、encoding/xml
パッケージが <_>
要素を正しくパースできるようになりました。
テストケースの追加
変更の正当性を検証するために、src/pkg/encoding/xml/read_test.go
に新しいテストケースが追加されています。
- XMLスニペットの追加:
pathTestString
定数に、<_>
要素を含むXML構造が追加されました。<_> <value>E</value> </_>
- 新しい構造体
PathTestE
の定義: この新しいXML構造に対応するGoの構造体が定義されました。
注目すべきは、type PathTestE struct { Underline string `xml:"items>_>value"` Before, After string }
xml:"items>_>value"
というタグです。これにより、XMLパスitems
の子要素である_
の子要素であるvalue
の内容がUnderline
フィールドにマッピングされることを明示しています。これは、fieldName
関数の変更が正しく機能し、_
が有効な要素名として認識されるようになったことを示しています。 - テストデータの追加:
pathTests
スライスに、PathTestE
のインスタンスが追加されました。
このテストケースは、&PathTestE{Underline: "E", Before: "1", After: "2"},
<_><value>E</value></_>
というXMLが正しくパースされ、PathTestE
構造体のUnderline
フィールドに"E"
が設定されることを検証します。
これらの変更により、encoding/xml
パッケージは、XML要素名が単一のアンダースコアである場合でも、Goの構造体へのアンマーシャルを正しく実行できるようになりました。
コアとなるコードの変更箇所
src/pkg/encoding/xml/read.go
--- a/src/pkg/encoding/xml/read.go
+++ b/src/pkg/encoding/xml/read.go
@@ -201,8 +201,8 @@ func (p *Parser) Unmarshal(val interface{}, start *StartElement) error {
func fieldName(original string) string {
var i int
-//remove leading underscores
- for i = 0; i < len(original) && original[i] == '_'; i++ {
+//remove leading underscores, without exhausting all characters
+ for i = 0; i < len(original)-1 && original[i] == '_'; i++ {
}
return strings.Map(
src/pkg/encoding/xml/read_test.go
--- a/src/pkg/encoding/xml/read_test.go
+++ b/src/pkg/encoding/xml/read_test.go
@@ -245,6 +245,9 @@ const pathTestString = `
<Value>C</Value>
<Value>D</Value>
</Item1>
+ <_>
+ <value>E</value>
+ </_>
</items>
<after>2</after>
</result>
@@ -279,11 +282,17 @@ type PathTestD struct {\n \tBefore, After string\n }\n \n+type PathTestE struct {\n+\tUnderline string `xml:\"items>_>value\"`\n+\tBefore, After string\n+}\n+\n var pathTests = []interface{}{\n \t&PathTestA{Items: []PathTestItem{{\"A\"}, {\"D\"}}, Before: \"1\", After: \"2\"},\n \t&PathTestB{Other: []PathTestItem{{\"A\"}, {\"D\"}}, Before: \"1\", After: \"2\"},\n \t&PathTestC{Values1: []string{\"A\", \"C\", \"D\"}, Values2: []string{\"B\"}, Before: \"1\", After: \"2\"},\n \t&PathTestD{Other: PathTestSet{Item1: []PathTestItem{{\"A\"}, {\"D\"}}}, Before: \"1\", After: \"2\"},\n+\t&PathTestE{Underline: \"E\", Before: \"1\", After: \"2\"},\n }\n \n func TestUnmarshalPaths(t *testing.T) {\n```
## コアとなるコードの解説
### `fieldName` 関数の変更
`fieldName` 関数は、XML要素名からGoの構造体フィールド名を導出する際に使用されるユーティリティ関数です。この関数の目的は、Goの命名規則(エクスポートされたフィールドは大文字で始まるなど)とXMLの命名規則(通常は小文字で始まる)の間のギャップを埋めることです。
変更された行は以下の通りです。
```go
- for i = 0; i < len(original) && original[i] == '_'; i++ {
+ for i = 0; i < len(original)-1 && original[i] == '_'; i++ {
この変更により、ループが original
文字列の最後の文字まで到達する前に停止するようになります。具体的には、original
が _
の場合、len(original)
は 1
なので、len(original)-1
は 0
になります。ループ条件 i < 0
は false
となり、ループは実行されません。その結果、i
は 0
のままで、original
全体(つまり _
)が strings.Map
に渡され、最終的に _
がフィールド名として扱われるようになります。
この修正は、XML要素名が単一のアンダースコアである場合に、encoding/xml
パッケージがその要素を有効なものとして認識し、Goの構造体フィールドに正しくマッピングできるようにするために不可欠です。
テストコードの追加
read_test.go
に追加されたテストコードは、この変更が意図した通りに機能することを確認します。
-
pathTestString
へのXML追加:<_> <value>E</value> </_>
このXMLスニペットは、単一のアンダースコア
_
を要素名とするXML要素が実際に存在し、パースの対象となることを示しています。 -
PathTestE
構造体の定義:type PathTestE struct { Underline string `xml:"items>_>value"` Before, After string }
xml:"items>_>value"
という構造体タグは、XMLパスitems
の子要素である_
の子要素であるvalue
の内容をUnderline
フィールドにマッピングするようにencoding/xml
パッケージに指示します。このタグが正しく機能するためには、_
が有効なXML要素名として認識され、fieldName
関数によって適切に処理される必要があります。 -
pathTests
へのテストケース追加:&PathTestE{Underline: "E", Before: "1", After: "2"},
この行は、
pathTestString
に含まれる<_><value>E</value></_>
の部分がPathTestE
構造体にアンマーシャルされたときに、Underline
フィールドが"E"
に、Before
が"1"
、After
が"2"
になることを期待するテストケースです。このテストが成功することで、fieldName
関数の修正が正しく機能し、<_>
要素のパースが可能になったことが検証されます。
これらの変更とテストの追加により、encoding/xml
パッケージの堅牢性と柔軟性が向上し、より多様なXML構造に対応できるようになりました。
関連リンク
- Go言語
encoding/xml
パッケージのドキュメント: https://pkg.go.dev/encoding/xml - Go言語のChange List (CL) システム: https://go.dev/doc/contribute#_code_review (一般的な情報)
参考にした情報源リンク
- コミット情報:
/home/violet/Project/comemo/commit_data/10268.txt
- GitHubコミットページ: https://github.com/golang/go/commit/1371ac2f0b5ed324f0f6a2ff2c124041a4feaa70
- Go CL 5298061: https://golang.org/cl/5298061 (このコミットのレビューページ)
- Go言語の公式ドキュメント (
encoding/xml
パッケージの挙動に関する一般的な理解のため) - XMLの仕様 (要素名の規則に関する一般的な理解のため)
- Go言語の命名規則 (構造体フィールド名に関する一般的な理解のため)I have provided the detailed explanation in Markdown format, following all the instructions and chapter structure. I have analyzed the commit, explained the background, prerequisite knowledge, technical details, core code changes, and added relevant links.
Let me know if you need any further assistance or another commit explanation.