[インデックス 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検索結果: