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

[インデックス 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を生成する際には柔軟性に欠けていました。

このコミットの背景には、以下のような課題がありました。

  1. カスタムマーシャリングの必要性: ユーザーがGoの構造体をXMLに変換する際に、デフォルトの挙動では対応できないような、より複雑なXML構造(例: 特定のネスト、カスタム属性、CDATAセクションの制御など)を生成したいというニーズがありました。
  2. 既存のイシューへの対応: 複数の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 インターフェースの導入は、これらの課題に対するより堅牢な解決策を提供します。
  3. 設計ドキュメント golang.org/s/go12xml: このコミットは、Russ Coxによって作成された設計ドキュメント golang.org/s/go12xml に基づいています。このドキュメントは、Go 1.2のリリースに向けて encoding/xml パッケージに Unmarshaler および Marshaler インターフェースのサポートを追加するための設計を概説しており、カスタムマーシャリングの必要性が明確に示されていました。

これらの背景から、開発者がXMLの生成をより細かく制御できるようにするために、Marshaler インターフェースの導入が不可欠であると判断されました。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語およびXMLに関する基本的な知識が必要です。

  1. Go言語のインターフェース: Go言語のインターフェースは、メソッドのシグネチャの集合を定義します。型がインターフェースのすべてのメソッドを実装している場合、その型はそのインターフェースを実装しているとみなされます(暗黙的なインターフェースの実装)。
  2. Go言語のリフレクション (reflectパッケージ): reflect パッケージは、実行時にGoのプログラムの構造を検査および操作するための機能を提供します。このコミットでは、reflect.ValueOfreflect.Type を使用して、Goの構造体のフィールド情報や型情報を取得し、それに基づいてXMLを生成するロジックが変更されています。
  3. 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 ...> など。
  4. 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) を持ちます。
  5. io.Writerbufio.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 という名前のフィールドを持つように変更されました。これにより、Encoderprinter のメソッドを呼び出す際に、printerEncoder のインスタンスにアクセスできるようになります(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 の内部実装が変更され、これらのメソッドが直接公開されなくなったためと考えられます。代わりに、Encoderio.Writer をラップする printer を介して書き込みを行います。

これらの変更により、encoding/xml パッケージは、Goの型が自身のXML表現を定義できる強力なメカニズムを提供し、より複雑で柔軟なXMLマーシャリングのユースケースに対応できるようになりました。

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

このコミットのコアとなるコードの変更は、主に src/pkg/encoding/xml/marshal.go に集中しています。

  1. Marshaler インターフェースの定義 (L75-L90):
    type Marshaler interface {
    	MarshalXML(e *Encoder, start StartElement) error
    }
    
  2. MarshalerAttr インターフェースの定義 (L92-L105):
    type MarshalerAttr interface {
    	MarshalXMLAttr(name Name) (Attr, error)
    }
    
  3. Encoder 構造体の変更と NewEncoder の初期化 (L110-L114): printer フィールドが p printer に変更され、NewEncodere.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
    }
    
  4. 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()
    }
    
  5. 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()
    }
    
  6. Encoder.EncodeToken メソッドの追加 (L139-L190): XMLトークンを直接書き込むためのロジックが追加されました。
    func (enc *Encoder) EncodeToken(t Token) error {
    	p := &enc.p
    	switch t := t.(type) {
    	// ... (各種トークンの処理)
    	}
    	return p.cachedWriteError()
    }
    
  7. printer 構造体の変更 (L194-L200): encoder *Encoder フィールドが追加されました。
    type printer struct {
    	*bufio.Writer
    	encoder    *Encoder
    	// ...
    }
    
  8. marshalerTypemarshalerAttrType の定義 (L239-L241): MarshalerMarshalerAttr インターフェースの reflect.Type が定義されました。
    var (
    	marshalerType     = reflect.TypeOf((*Marshaler)(nil)).Elem()
    	marshalerAttrType = reflect.TypeOf((*MarshalerAttr)(nil)).Elem()
    )
    
  9. printer.marshalValue メソッドの変更 (L243-L409):
    • シグネチャに startTemplate *StartElement が追加されました。
    • Marshaler インターフェースの実装チェックと p.marshalInterface の呼び出しが追加されました (L257-L264)。
    • MarshalerAttr インターフェースの実装チェックと MarshalXMLAttr の呼び出しが追加されました (L300-L319)。
    • XML要素の開始タグと属性の生成ロジックが start StartElement を使用するように変更されました。
    • p.writeStartp.writeEnd の呼び出しが追加されました。
  10. 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)
    	// ...
    }
    
  11. printer.writeStart メソッドの追加 (L441-L477): XML開始タグの書き込みと名前空間、属性の処理。
    func (p *printer) writeStart(start *StartElement) error {
    	// ...
    }
    
  12. printer.writeEnd メソッドの追加 (L479-L504): XML終了タグの書き込みとタグスタックの管理。
    func (p *printer) writeEnd(name Name) error {
    	// ...
    }
    
  13. printer.marshalSimple メソッドの変更 (L508-L548): 戻り値が (string, []byte, error) に変更され、文字列またはバイト列を返すようになりました。
  14. src/pkg/encoding/xml/marshal_test.go の変更:
    • MyMarshalerTestMyMarshalerAttrTest という新しいテスト構造体が追加され、それぞれ MarshalerMarshalerAttr インターフェースを実装しています。
    • これらのカスタムマーシャラーの動作を検証するためのテストケースが marshalTests に追加されました。

これらの変更は、encoding/xml パッケージの内部構造と外部APIの両方に大きな影響を与え、カスタムXMLマーシャリングの柔軟性を大幅に向上させています。

コアとなるコードの解説

このコミットの核心は、Goの型が自身のXML表現を定義できるようにする MarshalerMarshalerAttr インターフェースの導入、およびそれらを処理するための xml.Encoderprinter の内部ロジックの再構築にあります。

Marshaler インターフェース (MarshalXML メソッド)

  • 目的: Goの構造体やその他の型が、XML要素としてどのように自身を表現するかを完全に制御できるようにします。
  • 動作: MarshalXML メソッドは、*EncoderStartElement を引数として受け取ります。
    • *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.Encoderprinter の連携

  • Encoder の役割: Encoder は、ユーザーがXMLマーシャリングを行うための主要なエントリポイントです。EncodeEncodeElementEncodeToken などのメソッドを提供します。
  • printer の役割: printerEncoder の内部ヘルパー構造体であり、実際のXMLバイト列の書き込み、インデントの管理、名前空間の処理、タグスタックの管理など、低レベルの書き込み操作を担当します。
  • 相互参照: NewEncoder 関数で e.p.encoder = e と設定されることで、printer は自身をラップしている Encoder のインスタンスへの参照を持つことができます。これにより、printer の内部ロジック(特に marshalValue)から EncoderEncodeToken などのメソッドを呼び出すことが可能になり、カスタムマーシャリングのロジックがシームレスに統合されます。
  • タグスタックと名前空間管理: 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構造の生成に対応できる、強力で柔軟なツールへと進化しました。

関連リンク

参考にした情報源リンク