[インデックス 17242] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/xml
パッケージに、カスタムXMLマーシャリングを可能にする Marshaler
および MarshalerAttr
インターフェースを追加するものです。これにより、開発者はGoの構造体をXMLにエンコードする際の挙動をより細かく制御できるようになります。
コミット
commit 54bdfc007f78d448642d3b2c73a63f41f4c245f9
Author: Russ Cox <rsc@golang.org>
Date: Wed Aug 14 14:58:28 2013 -0400
encoding/xml: add, support Marshaler interface
See golang.org/s/go12xml for design.
Repeat of CL 12603044, which was submitted accidentally
and then rolled back.
Fixes #2771.
Fixes #4169.
Fixes #5975.
Fixes #6125.
R=golang-dev
CC=golang-dev
https://golang.org/cl/12919043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/54bdfc007f78d448642d3b2c73a63f41f4c245f9
元コミット内容
encoding/xml: add, support Marshaler interface
このコミットは、encoding/xml
パッケージに Marshaler
インターフェースのサポートを追加するものです。これは、以前誤ってコミットされ、その後ロールバックされた変更 (CL 12603044) の再提出です。この変更は、golang.org/s/go12xml
で提案された設計に基づいています。また、以下の複数の既存のGoイシューを解決します。
- Fixes #2771
- Fixes #4169
- Fixes #5975
- Fixes #6125
変更の背景
Goの encoding/xml
パッケージは、Goの構造体とXMLの間でデータを変換するための機能を提供します。しかし、従来のGoのXMLマーシャリングは、構造体のフィールドタグに基づいて自動的にXML要素や属性を生成するため、複雑なXML構造や特定の要件を持つXMLを生成する際には柔軟性に欠けていました。
このコミットの背景には、以下のような課題がありました。
- カスタムマーシャリングの必要性: ユーザーがGoの構造体をXMLに変換する際に、デフォルトの挙動では対応できないような、より複雑なXML構造(例: 特定のネスト、カスタム属性、CDATAセクションの制御など)を生成したいというニーズがありました。
- 既存のイシューへの対応: 複数のGoイシュー(#2771、#4169、#5975、#6125)が、
encoding/xml
パッケージの既存の制限やバグに関連しており、特にカスタムマーシャリングの欠如が指摘されていました。- Issue #2771:
encoding/xml
パッケージにおける潜在的なバグについて議論されており、カスタムマーシャリングの必要性が示唆されていました。 - Issue #4169: 特定のGoイシュー番号4169は直接見つかりませんでしたが、
encoding/xml
パッケージが不正なXMLをパースする際の挙動や、より厳密なXMLアンマーシャリングの要望に関する議論が散見されます。このコミットは、マーシャリングの柔軟性を高めることで、間接的にこれらの問題の一部を解決する可能性があります。 - Issue #5975: 特定のGoイシュー番号5975は直接見つかりませんでしたが、
xml.Name
、名前空間、アンマーシャリングの挙動に関する課題が頻繁に議論されています。カスタムマーシャリングは、これらの名前空間や属性の制御を改善する手段となります。 - Issue #6125: 特定のGoイシュー番号6125は直接見つかりませんでしたが、
encoding/xml
の互換性のない変更、未知のフィールドの扱い、不正なXMLの厳密性、名前空間と属性の処理に関する課題が挙げられています。Marshaler
インターフェースの導入は、これらの課題に対するより堅牢な解決策を提供します。
- Issue #2771:
- 設計ドキュメント
golang.org/s/go12xml
: このコミットは、Russ Coxによって作成された設計ドキュメントgolang.org/s/go12xml
に基づいています。このドキュメントは、Go 1.2のリリースに向けてencoding/xml
パッケージにUnmarshaler
およびMarshaler
インターフェースのサポートを追加するための設計を概説しており、カスタムマーシャリングの必要性が明確に示されていました。
これらの背景から、開発者がXMLの生成をより細かく制御できるようにするために、Marshaler
インターフェースの導入が不可欠であると判断されました。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語およびXMLに関する基本的な知識が必要です。
- Go言語のインターフェース: Go言語のインターフェースは、メソッドのシグネチャの集合を定義します。型がインターフェースのすべてのメソッドを実装している場合、その型はそのインターフェースを実装しているとみなされます(暗黙的なインターフェースの実装)。
- Go言語のリフレクション (reflectパッケージ):
reflect
パッケージは、実行時にGoのプログラムの構造を検査および操作するための機能を提供します。このコミットでは、reflect.ValueOf
やreflect.Type
を使用して、Goの構造体のフィールド情報や型情報を取得し、それに基づいてXMLを生成するロジックが変更されています。 - XML (Extensible Markup Language): XMLは、構造化されたデータを表現するためのマークアップ言語です。
- 要素 (Element):
<tag>content</tag>
のように、開始タグと終了タグで囲まれた構造。 - 属性 (Attribute): 開始タグ内に
name="value"
の形式で記述される、要素に関する追加情報。 - 名前空間 (Namespace): XML要素や属性の名前の衝突を避けるために使用されるメカニズム。
xmlns:prefix="URI"
の形式で宣言されます。 - CDATAセクション: XMLパーサーによって解析されないテキストブロック。
< ![CDATA[...]] >
の形式で記述されます。 - 処理命令 (Processing Instruction - PI): XML文書にアプリケーション固有の命令を埋め込むためのメカニズム。
<?target instruction?>
の形式で記述されます。 - コメント (Comment): XML文書内のコメント。
<!-- comment -->
の形式で記述されます。 - ディレクティブ (Directive): XML文書の構造に関する情報を提供する。
<!DOCTYPE ...>
など。
- 要素 (Element):
encoding/xml
パッケージの基本:xml.Marshal(v interface{}) ([]byte, error)
: Goの値をXMLバイト列にマーシャリングします。xml.Unmarshal(data []byte, v interface{}) error
: XMLバイト列をGoの値にアンマーシャリングします。xml.Encoder
: XMLデータをストリームに書き込むための構造体。xml.StartElement
,xml.EndElement
,xml.CharData
,xml.Comment
,xml.ProcInst
,xml.Directive
: XMLの各構成要素を表すトークン。xml.Name
: XML要素や属性の名前を表す構造体で、名前空間 (Space
) とローカル名 (Local
) を持ちます。xml.Attr
: XML属性を表す構造体で、名前 (Name
) と値 (Value
) を持ちます。
io.Writer
とbufio.Writer
:io.Writer
はバイト列を書き込むためのインターフェースで、bufio.Writer
はバッファリングされたI/Oを提供し、書き込み効率を向上させます。
これらの知識があることで、コミットが導入する新しいインターフェースがどのように機能し、既存のXMLマーシャリングロジックとどのように統合されるかを深く理解できます。
技術的詳細
このコミットの主要な技術的変更点は、encoding/xml
パッケージに Marshaler
および MarshalerAttr
という2つの新しいインターフェースを導入し、それらを xml.Encoder
が認識して利用するように変更したことです。
1. Marshaler
インターフェースの導入
// Marshaler is the interface implemented by objects that can marshal
// themselves into valid XML elements.
//
// MarshalXML encodes the receiver as zero or more XML elements.
// By convention, arrays or slices are typically encoded as a sequence
// of elements, one per entry.
// Using start as the element tag is not required, but doing so
// will enable Unmarshal to match the XML elements to the correct
// struct field.
// One common implementation strategy is to construct a separate
// value with a layout corresponding to the desired XML and then
// to encode it using e.EncodeElement.
// Another common strategy is to use repeated calls to e.EncodeToken
// to generate the XML output one token at a time.
// The sequence of encoded tokens must make up zero or more valid
// XML elements.
type Marshaler interface {
MarshalXML(e *Encoder, start StartElement) error
}
Marshaler
インターフェースは、MarshalXML(e *Encoder, start StartElement) error
メソッドを定義します。- このインターフェースを実装する型は、自身のXML表現を完全に制御できます。
MarshalXML
メソッドは、xml.Encoder
のインスタンスe
と、エンコードされる要素の開始タグ情報start
を受け取ります。- 実装者は、
e.EncodeToken
を繰り返し呼び出すことでXMLトークンを直接書き込むか、またはe.EncodeElement
を使用して別のGoの値をエンコードすることができます。 - これにより、Goの構造体のデフォルトのマーシャリングでは表現できない複雑なXML構造(例: 異なる名前空間、カスタムのネスト、特定の順序での要素出力など)を生成することが可能になります。
2. MarshalerAttr
インターフェースの導入
// MarshalerAttr is the interface implemented by objects that can marshal
// themselves into valid XML attributes.
//
// MarshalXMLAttr returns an XML attribute with the encoded value of the receiver.
// Using name as the attribute name is not required, but doing so
// will enable Unmarshal to match the attribute to the correct
// struct field.
// If MarshalXMLAttr returns the zero attribute Attr{}, no attribute
// will be generated in the output.
// MarshalXMLAttr is used only for struct fields with the
// "attr" option in the field tag.
type MarshalerAttr interface {
MarshalXMLAttr(name Name) (Attr, error)
}
MarshalerAttr
インターフェースは、MarshalXMLAttr(name Name) (Attr, error)
メソッドを定義します。- このインターフェースは、構造体のフィールドがXML属性としてマーシャリングされる際に、その属性の値をカスタムで生成するために使用されます。
MarshalXMLAttr
メソッドは、属性の名前情報name
を受け取り、xml.Attr
型の属性を返します。- フィールドタグに
xml:",attr"
オプションが指定されている場合にのみ、このインターフェースが考慮されます。 Attr{}
を返すと、その属性は出力されません。これにより、特定の条件に基づいて属性を省略するなどの制御が可能になります。
3. xml.Encoder
の変更
Encoder
構造体は、内部のprinter
フィールドを直接持つのではなく、p printer
という名前のフィールドを持つように変更されました。これにより、Encoder
がprinter
のメソッドを呼び出す際に、printer
がEncoder
のインスタンスにアクセスできるようになります(e.p.encoder = e
の設定)。Encode(v interface{}) error
メソッドのロジックが変更され、値がMarshaler
インターフェースを実装しているかどうかをチェックするようになりました。実装している場合、marshalInterface
メソッドが呼び出されます。EncodeElement(v interface{}, start StartElement) error
メソッドの追加: この新しいメソッドは、指定された開始要素start
を使用してGoの値をXMLにエンコードします。これは、Marshaler
の実装内で特定の要素タグを指定して値をエンコードする際に特に有用です。EncodeToken(t Token) error
メソッドの追加: このメソッドは、xml.StartElement
,xml.EndElement
,xml.CharData
,xml.Comment
,xml.ProcInst
,xml.Directive
などのXMLトークンを直接ストリームに書き込むことを可能にします。これにより、Marshaler
の実装者は、XML出力をトークンレベルで細かく制御できます。
4. printer
構造体の変更
printer
構造体は、encoder *Encoder
フィールドを持つようになりました。これにより、printer
の内部ロジックからEncoder
のメソッド(特にEncodeToken
)を呼び出すことが可能になります。marshalValue
メソッドのシグネチャが変更され、startTemplate *StartElement
引数が追加されました。これは、EncodeElement
メソッドから呼び出される際に、外側の要素の開始タグ情報を提供するために使用されます。marshalValue
メソッド内で、値がMarshaler
またはMarshalerAttr
インターフェースを実装しているかどうかのチェックが追加され、該当する場合はそれぞれのカスタムマーシャリングロジックが呼び出されるようになりました。- XML名前空間の処理ロジックが変更され、
createAttrPrefix
メソッドがisNew
の戻り値を削除し、popPrefix
メソッドが名前空間のスタックを管理するようになりました。これにより、名前空間の宣言とスコープがより正確に管理されます。 writeStart
およびwriteEnd
メソッドが導入され、XML要素の開始タグと終了タグの書き込みロジックがカプセル化されました。これらのメソッドは、名前空間の処理、属性の書き込み、およびタグスタックの管理を行います。
5. API変更の反映
api/go1.1.txt
およびapi/go1.txt
から、encoding/xml
パッケージのEncoder
に関連するいくつかのメソッド(ReadFrom
,Available
,Buffered
,Flush
,Write
,WriteByte
,WriteRune
,WriteString
)が削除されています。これは、Encoder
の内部実装が変更され、これらのメソッドが直接公開されなくなったためと考えられます。代わりに、Encoder
はio.Writer
をラップするprinter
を介して書き込みを行います。
これらの変更により、encoding/xml
パッケージは、Goの型が自身のXML表現を定義できる強力なメカニズムを提供し、より複雑で柔軟なXMLマーシャリングのユースケースに対応できるようになりました。
コアとなるコードの変更箇所
このコミットのコアとなるコードの変更は、主に src/pkg/encoding/xml/marshal.go
に集中しています。
Marshaler
インターフェースの定義 (L75-L90):type Marshaler interface { MarshalXML(e *Encoder, start StartElement) error }
MarshalerAttr
インターフェースの定義 (L92-L105):type MarshalerAttr interface { MarshalXMLAttr(name Name) (Attr, error) }
Encoder
構造体の変更とNewEncoder
の初期化 (L110-L114):printer
フィールドがp printer
に変更され、NewEncoder
でe.p.encoder = e
が設定されるようになりました。type Encoder struct { p printer } func NewEncoder(w io.Writer) *Encoder { e := &Encoder{printer{Writer: bufio.NewWriter(w)}} e.p.encoder = e return e }
Encoder.Encode
メソッドの変更 (L119-L124):enc.marshalValue
の呼び出しがenc.p.marshalValue
に変更され、引数が増えました。func (enc *Encoder) Encode(v interface{}) error { err := enc.p.marshalValue(reflect.ValueOf(v), nil, nil) if err != nil { return err } return enc.p.Flush() }
Encoder.EncodeElement
メソッドの追加 (L126-L133):func (enc *Encoder) EncodeElement(v interface{}, start StartElement) error { err := enc.p.marshalValue(reflect.ValueOf(v), nil, &start) if err != nil { return err } return enc.p.Flush() }
Encoder.EncodeToken
メソッドの追加 (L139-L190): XMLトークンを直接書き込むためのロジックが追加されました。func (enc *Encoder) EncodeToken(t Token) error { p := &enc.p switch t := t.(type) { // ... (各種トークンの処理) } return p.cachedWriteError() }
printer
構造体の変更 (L194-L200):encoder *Encoder
フィールドが追加されました。type printer struct { *bufio.Writer encoder *Encoder // ... }
marshalerType
とmarshalerAttrType
の定義 (L239-L241):Marshaler
とMarshalerAttr
インターフェースのreflect.Type
が定義されました。var ( marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() marshalerAttrType = reflect.TypeOf((*MarshalerAttr)(nil)).Elem() )
printer.marshalValue
メソッドの変更 (L243-L409):- シグネチャに
startTemplate *StartElement
が追加されました。 Marshaler
インターフェースの実装チェックとp.marshalInterface
の呼び出しが追加されました (L257-L264)。MarshalerAttr
インターフェースの実装チェックとMarshalXMLAttr
の呼び出しが追加されました (L300-L319)。- XML要素の開始タグと属性の生成ロジックが
start StartElement
を使用するように変更されました。 p.writeStart
とp.writeEnd
の呼び出しが追加されました。
- シグネチャに
printer.marshalInterface
メソッドの追加 (L411-L439):Marshaler
インターフェースを実装する値をマーシャリングするための新しいメソッド。func (p *printer) marshalInterface(val Marshaler, typ reflect.Type, finfo *fieldInfo, startTemplate *StartElement) error { // ... err := val.MarshalXML(p.encoder, start) // ... }
printer.writeStart
メソッドの追加 (L441-L477): XML開始タグの書き込みと名前空間、属性の処理。func (p *printer) writeStart(start *StartElement) error { // ... }
printer.writeEnd
メソッドの追加 (L479-L504): XML終了タグの書き込みとタグスタックの管理。func (p *printer) writeEnd(name Name) error { // ... }
printer.marshalSimple
メソッドの変更 (L508-L548): 戻り値が(string, []byte, error)
に変更され、文字列またはバイト列を返すようになりました。src/pkg/encoding/xml/marshal_test.go
の変更:MyMarshalerTest
とMyMarshalerAttrTest
という新しいテスト構造体が追加され、それぞれMarshaler
とMarshalerAttr
インターフェースを実装しています。- これらのカスタムマーシャラーの動作を検証するためのテストケースが
marshalTests
に追加されました。
これらの変更は、encoding/xml
パッケージの内部構造と外部APIの両方に大きな影響を与え、カスタムXMLマーシャリングの柔軟性を大幅に向上させています。
コアとなるコードの解説
このコミットの核心は、Goの型が自身のXML表現を定義できるようにする Marshaler
と MarshalerAttr
インターフェースの導入、およびそれらを処理するための xml.Encoder
と printer
の内部ロジックの再構築にあります。
Marshaler
インターフェース (MarshalXML
メソッド)
- 目的: Goの構造体やその他の型が、XML要素としてどのように自身を表現するかを完全に制御できるようにします。
- 動作:
MarshalXML
メソッドは、*Encoder
とStartElement
を引数として受け取ります。*Encoder
(e
): XML出力を生成するための主要なツールです。特に、新しく追加されたe.EncodeToken
メソッドを通じて、XMLの各構成要素(開始タグ、終了タグ、文字データ、コメント、処理命令など)を細かく書き込むことができます。また、e.EncodeElement
を使用して、別のGoの値を指定されたタグでエンコードすることも可能です。StartElement
(start
): 現在マーシャリングされている要素のデフォルトの開始タグ情報(名前、属性など)を提供します。Marshaler
の実装者は、この情報を使用することも、完全に無視して独自のタグを生成することもできます。
- 柔軟性: このインターフェースを実装することで、開発者は以下のような高度なXMLマーシャリングを実現できます。
- 特定の条件に基づいて要素を省略したり、追加したりする。
- デフォルトのフィールド名やタグとは異なるXML要素名を生成する。
- 複雑なネスト構造や、Goの構造体では直接表現できないXML階層を構築する。
- CDATAセクションや処理命令など、特殊なXMLノードを挿入する。
MarshalerAttr
インターフェース (MarshalXMLAttr
メソッド)
- 目的: Goの型が、XML属性としてどのように自身を表現するかを制御できるようにします。
- 動作:
MarshalXMLAttr
メソッドは、属性の名前 (Name
) を引数として受け取り、xml.Attr
型の値を返します。- このインターフェースは、構造体のフィールドタグに
xml:",attr"
が指定されている場合にのみ呼び出されます。 - 実装者は、属性の値を動的に生成したり、特定の条件に基づいて属性を完全に省略したり(
Attr{}
を返すことで)することができます。
- このインターフェースは、構造体のフィールドタグに
- 柔軟性: 属性の値をカスタムフォーマットで出力したり、Goの型が持つ複雑な情報を単一の属性値に変換したりする際に有用です。
xml.Encoder
と printer
の連携
Encoder
の役割:Encoder
は、ユーザーがXMLマーシャリングを行うための主要なエントリポイントです。Encode
、EncodeElement
、EncodeToken
などのメソッドを提供します。printer
の役割:printer
はEncoder
の内部ヘルパー構造体であり、実際のXMLバイト列の書き込み、インデントの管理、名前空間の処理、タグスタックの管理など、低レベルの書き込み操作を担当します。- 相互参照:
NewEncoder
関数でe.p.encoder = e
と設定されることで、printer
は自身をラップしているEncoder
のインスタンスへの参照を持つことができます。これにより、printer
の内部ロジック(特にmarshalValue
)からEncoder
のEncodeToken
などのメソッドを呼び出すことが可能になり、カスタムマーシャリングのロジックがシームレスに統合されます。 - タグスタックと名前空間管理:
printer
にはtags
(XML要素のスタック) とprefixes
(名前空間プレフィックスのスタック) が導入されました。tags
スタックは、XML要素の開始タグと終了タグが正しくネストされているかを追跡し、MarshalXML
の実装が不正なXMLを生成しないように検証する役割も果たします。prefixes
スタックと関連するattrNS
,attrPrefix
マップは、XML名前空間の宣言とスコープを正確に管理するために使用されます。これにより、必要な場合にのみxmlns
属性が生成され、名前空間の衝突が回避されます。
marshalValue
メソッドの再構築
marshalValue
は、Goの値をXMLに変換する中心的な再帰関数です。- このコミットでは、
marshalValue
の冒頭で、渡された値がMarshaler
インターフェースを実装しているかどうかがチェックされます。実装している場合、通常のGoの型に基づくマーシャリングロジックをスキップし、marshalInterface
メソッドを呼び出してカスタムマーシャリングに処理を委譲します。 - 同様に、属性のマーシャリング時には
MarshalerAttr
インターフェースのチェックが行われます。 - これにより、GoのデフォルトのXMLマーシャリングとカスタムマーシャリングのメカニズムが、優先順位付けされた形で共存できるようになりました。
これらの変更により、Goの encoding/xml
パッケージは、より複雑なXML構造の生成に対応できる、強力で柔軟なツールへと進化しました。
関連リンク
- Go言語
encoding/xml
パッケージのドキュメント: https://pkg.go.dev/encoding/xml - Go言語のインターフェースに関する公式ドキュメント: https://go.dev/tour/methods/10
- Go言語のリフレクションに関する公式ドキュメント: https://go.dev/blog/laws-of-reflection
参考にした情報源リンク
golang.org/s/go12xml
デザインドキュメントに関するWeb検索結果:- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHyftKQSoSnevO6kiVTz3E_GwIpi2fvHzXm91oB1sbSWlriQESFFWcG-5ONy6wiSrXgmMoEQl6ukPUmivWRlZL6Vw_EThhhfm7O6yP2QbJS4khFjcKC2NmSTzhVSIb953Ye
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHunJcsfqsh6gd_HV6iuCayj82tw1tj5BCZ7R_XVfWmUd2hTdoK00yne91XHSQO7LGT12unLw7L_OzHDzT_5nX1whq8C-R8BTCeTIeTz1XBrOy5Z9l8ugIBX_UfHnU_ut-w
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF-GwRrEwTSvCz6XS4wwwTWbic8sfEA3ixtwXjLeEUBVAgI5ylREN2rRKteBxWmRT_3R9n79555es7QQUinIhdExK0jwe6JYGIvJ1HVRdY-rt5Btd3mf74piJLy-TFyMV1CKA==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEsJSyO_FNolsTzv8kchH3KDMsFr4HlWr8ZSVP69RZXZL1GE8ZXgbhl8Xs7oqvVw3WwSlnI1I2HKUUj3Jcc71s6GmBLbqnza8RCK0uRQyfCtUSQGRcPQ0i6lGKo2fD9YA==
- Go issue 2771に関するWeb検索結果:
- Go issue 4169に関するWeb検索結果:
- Go issue 5975に関するWeb検索結果:
- Go issue 6125に関するWeb検索結果: