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

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

このコミットは、Go言語の標準ライブラリ encoding/xml パッケージにおいて、XMLマーシャリング(Goの構造体をXMLに変換する処理)の際に、構造体フィールドに付与された ",any" タグが正しく処理されるように修正するものです。これまでは ",any" タグが付いたフィールドは xml.Marshal 関数によって無視されていましたが、この変更により、xml.Unmarshal 関数(XMLをGoの構造体に変換する処理)との対称性が向上し、",any" フィールドが適切にXML出力に含まれるようになります。

コミット

commit a9121a19f0a2904e1b7a62d4bb07eb82093bb93a
Author: Chris Jones <chris@cjones.org>
Date:   Sat Dec 22 10:00:36 2012 -0500

    encoding/xml: Marshal ",any" fields
    
    Fixes #3559.
    
    This makes Marshal handle fields marked ",any" instead of ignoring
    them. That makes Marshal more symmetrical with Unmarshal, which seems
    to have been a design goal.
    
    Note some test cases were changed, because this patch changes
    marshalling behavior. I think the previous behavior was buggy, but
    there's still a backward-compatibility question to consider.
    
    R=rsc
    CC=golang-dev, n13m3y3r
    https://golang.org/cl/6938068

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

https://github.com/golang/go/commit/a9121a19f0a2904e1b7a62d4bb07eb82093bb93a

元コミット内容

encoding/xml: ,any フィールドをマーシャルする

これは、Marshal,any とマークされたフィールドを無視する代わりに処理するようにします。これにより、MarshalUnmarshal とより対称的になり、それが設計目標であったようです。

このパッチはマーシャリングの動作を変更するため、いくつかのテストケースが変更されました。以前の動作はバグがあったと考えていますが、後方互換性の問題も考慮する必要があります。

変更の背景

このコミットは、Go言語の encoding/xml パッケージにおける既存の不整合を修正するために導入されました。具体的には、Goの構造体をXMLに変換する xml.Marshal 関数が、構造体フィールドに付与された xml:",any" タグを適切に処理せず、そのフィールドをXML出力から無視してしまうという問題がありました。

一方で、XMLをGoの構造体に変換する xml.Unmarshal 関数は、xml:",any" タグが付与されたフィールドを正しく解釈し、未知のXML要素や任意のXMLコンテンツをそのフィールドに格納することができました。この非対称性は、開発者がXMLのラウンドトリップ(マーシャルしてアンマーシャル、またはその逆)を行う際に予期せぬ動作を引き起こす可能性がありました。

コミットメッセージにある Fixes #3559 は、この問題がGoのIssueトラッカーで報告されていたことを示しています。この変更の主な目的は、MarshalUnmarshal の動作を対称的にし、",any" タグの意図されたセマンティクスを両方向で尊重することでした。これにより、encoding/xml パッケージの予測可能性と堅牢性が向上します。

前提知識の解説

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

encoding/xml パッケージは、Goの構造体とXMLドキュメントの間でデータを変換するための機能を提供します。主な関数は以下の通りです。

  • xml.Marshal(v interface{}) ([]byte, error): Goの任意の値をXML形式のバイトスライスに変換します。構造体のフィールドタグ(例: xml:"name,attr", xml:",innerxml", xml:",chardata", xml:",omitempty" など)を解釈して、XML要素名、属性、コンテンツなどを決定します。
  • xml.Unmarshal(data []byte, v interface{}) error: XML形式のバイトスライスをGoの任意の構造体に変換します。

XMLフィールドタグ xml:",any"

encoding/xml パッケージでは、構造体のフィールドにタグを付与することで、XMLとのマッピング方法を制御できます。xml:",any" タグは特殊なタグで、主に xml.Unmarshal のコンテキストで使用されていました。

  • xml.Unmarshal における ",any": このタグが付与されたフィールドは、XMLドキュメント内でそのフィールドに対応する要素が見つからなかった場合、またはその要素が予期しない構造を持っていた場合に、その未知のXMLコンテンツ(要素、属性、テキストなど)を格納するために使用されます。これにより、XMLスキーマに厳密に準拠しないXMLや、拡張可能なXMLを柔軟に処理できるようになります。通常、interface{} 型や []byte 型、または特定の構造体型にマッピングされます。

マーシャリングとアンマーシャリングの対称性

理想的には、Marshal(Unmarshal(data)) が元の data に近い結果を生成し、Unmarshal(Marshal(v)) が元の v に近い結果を生成することが望ましいです。これを「ラウンドトリップ」の対称性と呼びます。",any" フィールドが Unmarshal では処理されるが Marshal では無視されるという非対称性は、このラウンドトリップの期待を裏切るものでした。

技術的詳細

このコミットは、encoding/xml パッケージ内の4つのファイルに影響を与えています。

  1. src/pkg/encoding/xml/marshal.go:

    • marshalStruct 関数は、Goの構造体をXMLにマーシャルする主要なロジックを含んでいます。
    • 変更前は、finfo.flags&(fAttr|fAny) != 0 という条件で、属性 (fAttr) または ",any" (fAny) のフラグを持つフィールドがスキップされていました。これは、",any" フィールドがマーシャリング時に無視される原因でした。
    • 変更後、この条件は finfo.flags&(fAttr) != 0 となり、fAny フラグを持つフィールドはスキップされなくなりました。
    • さらに、case fElement: の部分が case fElement, fElement | fAny: に変更され、fAny フラグが fElement フラグと組み合わされた場合(つまり、",any" フィールドが要素として扱われる場合)も適切に処理されるようになりました。これにより、",any" フィールドがXML要素として出力される道が開かれました。
  2. src/pkg/encoding/xml/marshal_test.go:

    • このファイルには、xml.Marshal の動作を検証するためのテストケースが含まれています。
    • AnyTest 構造体(xml:",any" フィールドを持つ)のテストケースが大幅に修正されました。
    • 変更前は、AnyTest のマーシャリング結果が <a><nested><value>known</value></nested></a> となっており、AnyField の内容が完全に無視されていました。また、UnmarshalOnly: trueMarshalOnly: true といったフラグが設定されているテストケースもあり、特定の方向でのみテストされていたことが伺えます。
    • 変更後、AnyTestExpectXML<a><nested><value>known</value></nested><AnyField><unknown/></AnyField></a> となり、AnyField の内容がXML出力に含まれるようになりました。
    • AnyOmitTestAnySliceTest という新しいテスト構造体が追加され、",any,omitempty"",any" とスライス型の組み合わせが正しくマーシャルされることを検証しています。
    • 既存のテストケースも、",any" フィールドがマーシャルされるようになった新しい動作に合わせて ExpectXML が更新されました。
  3. src/pkg/encoding/xml/read.go:

    • unmarshal 関数は、XMLをGoの構造体にアンマーシャルする主要なロジックを含んでいます。
    • case fAny: の部分が case fAny, fAny | fElement: に変更されました。これは、Unmarshal",any" フィールドを処理する際に、それが単なる fAny フラグを持つ場合でも、fElement フラグと組み合わされた場合でも、同様に saveAny 変数に値を格納するように調整されたことを意味します。この変更は、主に marshal.go での変更との整合性を保つためのものです。
  4. src/pkg/encoding/xml/typeinfo.go:

    • structFieldInfo 関数は、構造体のフィールド情報(タグ、型など)を解析し、それに対応する fieldInfo 構造体を生成します。
    • if finfo.flags&fMode == fAny { finfo.flags |= fElement } という行が追加されました。これは、フィールドが ",any" タグを持つ場合 (fAny フラグが設定されている場合)、自動的に fElement フラグも設定されるようにします。これにより、",any" フィールドがデフォルトでXML要素として扱われるようになり、マーシャリング時の動作がより予測可能になります。

これらの変更により、encoding/xml パッケージは ",any" タグを持つフィールドを Marshal 時にも適切に処理し、Unmarshal との対称性を大幅に改善しました。

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

src/pkg/encoding/xml/marshal.go

--- a/src/pkg/encoding/xml/marshal.go
+++ b/src/pkg/encoding/xml/marshal.go
@@ -273,7 +273,7 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
 	s := parentStack{printer: p}
 	for i := range tinfo.fields {
 		finfo := &tinfo.fields[i]
-		if finfo.flags&(fAttr|fAny) != 0 {
+		if finfo.flags&(fAttr) != 0 {
 			continue
 		}
 		vf := finfo.value(val)
@@ -340,7 +340,7 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
 				continue
 			}
 
-		case fElement:
+		case fElement, fElement | fAny:
 			s.trim(finfo.parents)
 			if vf.Kind() != reflect.Ptr && vf.Kind() != reflect.Interface || !vf.IsNil() {

src/pkg/encoding/xml/typeinfo.go

--- a/src/pkg/encoding/xml/typeinfo.go
+++ b/src/pkg/encoding/xml/typeinfo.go
@@ -154,6 +154,9 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro
 			// This will also catch multiple modes in a single field.
 			valid = false
 		}
+		if finfo.flags&fMode == fAny {
+			finfo.flags |= fElement
+		}
 		if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 {
 			valid = false
 		}

コアとなるコードの解説

src/pkg/encoding/xml/marshal.go の変更

  1. if finfo.flags&(fAttr|fAny) != 0 { continue } から if finfo.flags&(fAttr) != 0 { continue } への変更:

    • これは xml.Marshal が構造体のフィールドを処理するループの初期段階で行われるチェックです。
    • 変更前は、フィールドがXML属性 (fAttr) であるか、または ",any" (fAny) タグを持つ場合に、そのフィールドの処理をスキップしていました。これにより、",any" フィールドはXML出力に現れませんでした。
    • 変更後、fAny フラグを持つフィールドはスキップされなくなりました。これにより、",any" フィールドも通常の要素としてマーシャリングの対象となるようになります。
  2. case fElement: から case fElement, fElement | fAny: への変更:

    • これは、フィールドのタイプ(要素、属性、文字データなど)に基づいて異なるマーシャリングロジックを適用する switch ステートメントの一部です。
    • 変更前は、フィールドが純粋なXML要素 (fElement) としてマークされている場合にのみ、要素のマーシャリングロジックが実行されていました。
    • 変更後、フィールドがXML要素 (fElement) であるか、または fElementfAny の両方のフラグを持つ場合(つまり、",any" フィールドが要素として扱われる場合)にも、同じ要素マーシャリングロジックが適用されるようになりました。これは、",any" フィールドがXML要素として出力されることを保証します。

src/pkg/encoding/xml/typeinfo.go の変更

  1. if finfo.flags&fMode == fAny { finfo.flags |= fElement } の追加:
    • このコードは、構造体のフィールド情報を解析する際に実行されます。
    • フィールドが ",any" タグを持つ場合(fAny フラグが設定されている場合)、そのフィールドのフラグに fElement を追加します。
    • これにより、",any" フィールドは、明示的に要素として指定されていなくても、デフォルトでXML要素として扱われるようになります。これは、marshal.go での変更と連携し、",any" フィールドがXML出力に適切に現れるようにするための重要なステップです。

これらの変更の組み合わせにより、encoding/xml.Marshal",any" タグが付与されたフィールドを認識し、それらをXML要素として出力するようになります。これにより、MarshalUnmarshal の間の対称性が確立され、",any" タグの意図された機能が両方向でサポートされることになります。

関連リンク

参考にした情報源リンク