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

[インデックス 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構造体フィールド名のマッピングにいくつかのルールがあります。

  1. タグによる明示的な指定: 構造体フィールドに xml:"element_name" のようなタグを付けることで、XML要素名を明示的に指定できます。
    type MyStruct struct {
        Value string `xml:"my_value_element"`
    }
    // <my_value_element>...</my_value_element> に対応
    
  2. 自動マッピング: タグが指定されていない場合、Goのフィールド名がXML要素名に変換されます。この変換には、キャメルケースからスネークケースへの変換や、先頭のアンダースコアの処理などが含まれます。例えば、Goのフィールド名 MyField はXML要素名 myField に、_MyFieldmyField にマッピングされることが期待されます。

アンダースコア _ の特殊性

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 であれば i2 になり、foo が返されます。

問題は、original_ だった場合です。

  1. i = 0
  2. 0 < len("_") (つまり 0 < 1) は true
  3. original[0] == '_'true
  4. i1 にインクリメントされる。
  5. ループ条件 i < len(original) (つまり 1 < 1) は false となり、ループを抜ける。
  6. 結果として、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_ だった場合の挙動は以下のようになります。

  1. i = 0
  2. ループ条件 i < len(original)-1 (つまり 0 < 1-1 -> 0 < 0) は false となり、ループは一度も実行されません。
  3. 結果として i0 のままです。
  4. 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)-10 になります。ループ条件 i < 0false となり、ループは実行されません。その結果、i0 のままで、original 全体(つまり _)が strings.Map に渡され、最終的に _ がフィールド名として扱われるようになります。

この修正は、XML要素名が単一のアンダースコアである場合に、encoding/xml パッケージがその要素を有効なものとして認識し、Goの構造体フィールドに正しくマッピングできるようにするために不可欠です。

テストコードの追加

read_test.go に追加されたテストコードは、この変更が意図した通りに機能することを確認します。

  1. pathTestString へのXML追加:

            <_>
                <value>E</value>
            </_>
    

    このXMLスニペットは、単一のアンダースコア _ を要素名とするXML要素が実際に存在し、パースの対象となることを示しています。

  2. PathTestE 構造体の定義:

    type PathTestE struct {
    	Underline     string `xml:"items>_>value"`
    	Before, After string
    }
    

    xml:"items>_>value" という構造体タグは、XMLパス items の子要素である _ の子要素である value の内容を Underline フィールドにマッピングするように encoding/xml パッケージに指示します。このタグが正しく機能するためには、_ が有効なXML要素名として認識され、fieldName 関数によって適切に処理される必要があります。

  3. pathTests へのテストケース追加:

    	&PathTestE{Underline: "E", Before: "1", After: "2"},
    

    この行は、pathTestString に含まれる <_><value>E</value></_> の部分が PathTestE 構造体にアンマーシャルされたときに、Underline フィールドが "E" に、Before"1"After"2" になることを期待するテストケースです。このテストが成功することで、fieldName 関数の修正が正しく機能し、<_> 要素のパースが可能になったことが検証されます。

これらの変更とテストの追加により、encoding/xml パッケージの堅牢性と柔軟性が向上し、より多様なXML構造に対応できるようになりました。

関連リンク

参考にした情報源リンク

  • コミット情報: /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.