[インデックス 17241] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/xml
パッケージに Unmarshaler
および UnmarshalerAttr
インターフェースを追加し、XMLのアンマーシャリングプロセスをより柔軟にカスタマイズできるようにするものです。これにより、開発者は特定のXML要素や属性のデコードロジックを独自に定義できるようになります。
コミット
commit 84b0842a59b8bf8e890861f7859869fb73d8681c
Author: Russ Cox <rsc@golang.org>
Date: Wed Aug 14 14:57:45 2013 -0400
encoding/xml: add, support Unmarshaler interface
See golang.org/s/go12xml for design.
R=golang-dev, dominik.honnef, dan.kortschak
CC=golang-dev
https://golang.org/cl/12556043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/84b0842a59b8bf8e890861f7859869fb73d8681c
元コミット内容
encoding/xml
パッケージに Unmarshaler
インターフェースのサポートを追加する。設計については golang.org/s/go12xml
を参照。
変更の背景
Go言語の encoding/xml
パッケージは、XMLデータをGoの構造体にマッピングするための強力な機能を提供します。しかし、従来のバージョンでは、XMLのデコード(アンマーシャリング)プロセスは主にリフレクションと構造体タグに基づいて自動的に行われていました。これにより、一般的なXML構造は簡単に扱える一方で、以下のような特定のユースケースでは柔軟性が不足していました。
- 非標準的なXML構造: XMLには、要素のテキストコンテンツが複雑な形式であったり、属性の値が特殊なエンコーディングを必要とするなど、標準的なGoの型マッピングでは対応しきれないケースが存在します。
- カスタムデコードロジック: 開発者がXMLデータの一部をデコードする際に、独自のバリデーション、変換、または外部サービスとの連携などのカスタムロジックを適用したい場合があります。
- パフォーマンス最適化: 大量のXMLデータを処理する際に、リフレクションベースの自動デコードよりも、手動で最適化されたデコードロジックの方がパフォーマンスが向上する可能性があります。
これらの課題に対処するため、encoding/xml
パッケージに Unmarshaler
インターフェースが導入されました。これにより、Goの型がこのインターフェースを実装することで、XMLデコーダがその型のインスタンスをアンマーシャリングする際に、自動デコードの代わりにカスタムロジックを呼び出すことができるようになります。これは、encoding/json
パッケージにおける json.Unmarshaler
と同様のパターンであり、Goの標準ライブラリにおけるデータシリアライゼーション/デシリアライゼーションの一貫性を保つ上でも重要でした。
コミットメッセージに記載されている golang.org/s/go12xml
は、この変更の設計思想と詳細な仕様を説明するデザインドキュメントへのショートリンクです。このドキュメントは、Go 1.2リリースに向けたXMLパッケージの改善提案の一部として作成されました。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびXMLに関する基本的な知識が必要です。
- Go言語のリフレクション: Goのリフレクションは、実行時にプログラムの構造(型、フィールド、メソッドなど)を検査・操作する機能です。
encoding/xml
パッケージは、Goの構造体とXML要素/属性のマッピングにリフレクションを広く利用しています。 - Go言語のインターフェース: インターフェースは、メソッドのシグネチャの集合を定義する型です。Goの型がインターフェースのすべてのメソッドを実装すると、その型はそのインターフェースを満たします。これにより、ポリモーフィックな振る舞いを実現できます。
- XML (Extensible Markup Language): 構造化されたデータを表現するためのマークアップ言語です。要素、属性、テキストコンテンツなどから構成されます。
- XMLのアンマーシャリング (Unmarshaling): XMLデータをプログラム内のデータ構造(Goの構造体など)に変換するプロセスです。
encoding/xml
パッケージ: Goの標準ライブラリの一部で、XMLデータのエンコード(マーシャリング)とデコード(アンマーシャリング)を提供します。xml.Decoder
: XMLストリームを読み込み、トークン(StartElement
,EndElement
,CharData
など)を生成します。xml.Unmarshal
: XMLバイトスライスをGoのデータ構造にデコードする高レベルな関数です。内部でxml.Decoder
を使用します。xml.StartElement
,xml.EndElement
,xml.CharData
,xml.Attr
: XMLの各構成要素を表す型です。xml:"tag"
構造体タグ: 構造体のフィールドとXML要素/属性のマッピングを制御するために使用されます。例えば、xml:",attr"
はフィールドが属性にマッピングされることを示します。
技術的詳細
このコミットの主要な技術的変更点は、Unmarshaler
と UnmarshalerAttr
という2つの新しいインターフェースの導入と、xml.Decoder
および xml.Unmarshal
がこれらのインターフェースを認識し、利用するように変更された点です。
Unmarshaler
インターフェース
type Unmarshaler interface {
UnmarshalXML(d *Decoder, start StartElement) error
}
- このインターフェースは、XML要素全体をカスタムでアンマーシャリングしたい型が実装します。
UnmarshalXML
メソッドは、*xml.Decoder
のインスタンスと、現在処理中のXML要素の開始タグ (xml.StartElement
) を引数に取ります。- このメソッド内で、実装者は
d.Token()
やd.DecodeElement()
などのDecoder
メソッドを使用して、XMLストリームから必要なデータを読み込み、自身の型にデコードします。 - 重要な制約として、
UnmarshalXML
は正確に1つのXML要素を消費しなければなりません。つまり、開始タグに対応する終了タグまでを読み込む必要があります。もし、要素の途中で処理を停止したり、余分なトークンを読み込んだりすると、xml: %s.UnmarshalXML did not consume entire <%s> element
のようなエラーが発生します。 UnmarshalXML
メソッド内ではd.RawToken()
を使用することはできません。これは、RawToken
が名前空間の解決や要素のマッチングを行わない低レベルなトークン取得メソッドであり、カスタムアンマーシャリングのロジックを複雑にし、予期せぬ動作を引き起こす可能性があるためです。代わりにd.Token()
を使用することが推奨されます。
UnmarshalerAttr
インターフェース
type UnmarshalerAttr interface {
UnmarshalXMLAttr(attr Attr) error
}
- このインターフェースは、XML属性の値をカスタムでアンマーシャリングしたい型が実装します。
UnmarshalXMLAttr
メソッドは、現在処理中のXML属性 (xml.Attr
) を引数に取ります。- このインターフェースは、構造体フィールドに
xml:",attr"
タグが指定されている場合にのみ使用されます。 - 属性は単一の値であるため、
UnmarshalXML
のような複雑なストリーム処理は不要で、直接attr.Value
を処理します。
xml.Decoder
の変更
Decoder
構造体にunmarshalDepth int
フィールドが追加されました。これは、UnmarshalXML
メソッドが呼び出されている深さを追跡するために使用されます。この値が0より大きい場合(つまり、カスタムアンマーシャリングが進行中の場合)、RawToken()
の呼び出しが禁止されます。unmarshalInterface
およびunmarshalAttr
という内部ヘルパー関数が追加されました。これらは、Goの型がUnmarshaler
またはUnmarshalerAttr
インターフェースを実装しているかどうかをリフレクションでチェックし、実装していれば対応するUnmarshalXML
またはUnmarshalXMLAttr
メソッドを呼び出します。unmarshal
メソッド(XML要素のアンマーシャリングの主要ロジック)が変更され、ポインタ型と非ポインタ型の両方でUnmarshaler
インターフェースの実装をチェックするようになりました。これにより、*MyType
とMyType
の両方がUnmarshaler
を実装している場合に適切に処理されます。unmarshalAttr
メソッドも同様に、UnmarshalerAttr
インターフェースの実装をチェックするように変更されました。pushEOF()
とpopEOF()
という新しいメソッドがDecoder
に追加されました。これらは、UnmarshalXML
メソッドがXML要素全体を消費したことを確認するためのメカニズムです。UnmarshalXML
が呼び出される前にpushEOF()
が呼ばれ、対応する終了タグが読み込まれた後にpopEOF()
が呼ばれます。もしpopEOF()
がfalse
を返した場合(つまり、カスタムアンマーシャリングが要素全体を消費しなかった場合)、エラーが報告されます。Token()
メソッドの内部でd.RawToken()
の代わりにd.rawToken()
が呼び出されるようになりました。これは、RawToken()
がunmarshalDepth
のチェックを行う公開APIであるのに対し、rawToken()
はそのチェックを行わない内部ヘルパー関数であるためです。
read_test.go
の変更
MyCharData
とMyAttr
というカスタム型が追加され、それぞれUnmarshaler
とUnmarshalerAttr
インターフェースを実装しています。MyCharData
はUnmarshalXML
メソッド内でd.Token()
を使用して要素内のCharData
を結合する例を示しています。MyAttr
はUnmarshalXMLAttr
メソッドで属性値を直接受け取る例を示しています。MyStruct
という構造体が定義され、これらのカスタム型をフィールドとして含み、xml:",attr"
タグの利用例も示されています。TestUnmarshaler
という新しいテスト関数が追加され、これらのカスタムアンマーシャリングが期待通りに動作することを確認しています。このテストでは、ポインタ型 (*MyCharData
,*MyAttr
) と非ポインタ型 (MyCharData
,MyAttr
) の両方でインターフェースが正しく機能することを確認しています。
コアとなるコードの変更箇所
src/pkg/encoding/xml/read.go
Unmarshaler
インターフェースとUnmarshalXML(d *Decoder, start StartElement) error
メソッドの定義を追加。UnmarshalerAttr
インターフェースとUnmarshalXMLAttr(attr Attr) error
メソッドの定義を追加。receiverType
ヘルパー関数を追加。unmarshalInterface
ヘルパー関数を追加。Unmarshaler
インターフェースを実装する値のアンマーシャリングを処理。unmarshalAttr
ヘルパー関数を追加。UnmarshalerAttr
インターフェースを実装する値のアンマーシャリングを処理。unmarshalerType
とunmarshalerAttrType
というreflect.Type
変数を追加し、インターフェースの型情報をキャッシュ。(*Decoder).unmarshal
メソッド内で、値がUnmarshaler
インターフェースを実装している場合の処理ロジックを追加。(*Decoder).unmarshal
メソッド内で、属性のアンマーシャリングにunmarshalAttr
を使用するように変更。
src/pkg/encoding/xml/read_test.go
MyCharData
構造体とUnmarshalXML
メソッドの実装を追加。MyAttr
構造体とUnmarshalXMLAttr
メソッドの実装を追加。MyStruct
構造体を追加し、MyCharData
とMyAttr
をフィールドとして含む。TestUnmarshaler
テスト関数を追加し、新しいインターフェースの動作を検証。
src/pkg/encoding/xml/xml.go
Decoder
構造体にunmarshalDepth int
フィールドを追加。Decoder.Token()
メソッド内で、d.nextToken
が設定されている場合とd.rawToken()
を呼び出すロジックを調整。stkEOF
定数をstack
のkind
に追加。pushEOF()
とpopEOF()
メソッドをDecoder
に追加。UnmarshalXML
が要素全体を消費したかどうかのチェックに使用。Decoder.popElement()
メソッド内で、スタックをポップする条件にd.stk.kind != stkEOF
を追加。errRawToken
エラー変数を追加。RawToken()
メソッドのロジックを変更し、unmarshalDepth > 0
の場合はerrRawToken
を返すように制限。rawToken()
という内部ヘルパー関数を新設し、元のRawToken()
の実処理を移動。isNameString
ヘルパー関数を追加。(*printer).EscapeString
メソッドを追加。
コアとなるコードの解説
このコミットの核心は、Goの encoding/xml
パッケージが、XMLデータのデコード時に開発者が提供するカスタムロジックを呼び出すためのフックを提供することです。
Unmarshaler
インターフェースの実装例 (MyCharData
)
src/pkg/encoding/xml/read_test.go
にある MyCharData
の例を見てみましょう。
type MyCharData struct {
body string
}
func (m *MyCharData) UnmarshalXML(d *Decoder, start StartElement) error {
for {
t, err := d.Token() // Decoderから次のトークンを取得
if err == io.EOF { // 要素の終わりに達したらループを抜ける
break
}
if err != nil {
return err
}
if char, ok := t.(CharData); ok { // トークンがCharData(テキストコンテンツ)であれば
m.body += string(char) // bodyフィールドに結合
}
}
return nil
}
この UnmarshalXML
メソッドは、MyCharData
型がXML要素としてアンマーシャリングされる際に呼び出されます。メソッド内では、d.Token()
を繰り返し呼び出すことで、XML要素の開始タグと終了タグの間にあるすべてのトークン(この場合は主に CharData
)を読み込み、それらを m.body
フィールドに結合しています。これにより、例えばXML要素内のコメントなどを無視し、純粋なテキストコンテンツのみを抽出するといったカスタム処理が可能になります。
UnmarshalerAttr
インターフェースの実装例 (MyAttr
)
同じく src/pkg/encoding/xml/read_test.go
にある MyAttr
の例です。
type MyAttr struct {
attr string
}
func (m *MyAttr) UnmarshalXMLAttr(attr Attr) error {
m.attr = attr.Value // 属性の値を直接attrフィールドに代入
return nil
}
MyAttr
型が構造体フィールドで xml:",attr"
タグと共に使用されると、この UnmarshalXMLAttr
メソッドが呼び出されます。このメソッドは、XML属性の Attr
構造体を直接受け取るため、その Value
フィールドを m.attr
に代入するだけでカスタム処理が完了します。これは、属性値のパースやバリデーションなど、より複雑なロジックを実装する際に役立ちます。
Decoder
の変更点と安全性
Decoder
に追加された unmarshalDepth
フィールドと pushEOF
/popEOF
メソッドは、カスタムアンマーシャリングの安全性を確保するために重要です。
unmarshalDepth
は、UnmarshalXML
メソッドがネストして呼び出されている深さを追跡します。この値が0より大きい場合、RawToken()
の呼び出しが禁止されます。これは、RawToken()
が名前空間の解決や要素のマッチングを行わないため、カスタムアンマーシャリングロジック内で誤って使用されると、XMLストリームのパース状態を壊してしまう可能性があるためです。pushEOF()
とpopEOF()
は、UnmarshalXML
メソッドが呼び出されたXML要素の開始タグから終了タグまでを正確に消費したことを検証するためのメカニズムです。もしUnmarshalXML
が要素の一部しか消費しなかったり、余分なトークンを読み込んだりした場合、popEOF()
がfalse
を返し、xml: %s.UnmarshalXML did not consume entire <%s> element
のようなエラーが報告されます。これにより、カスタムアンマーシャリングの実装ミスが、後続のXMLパースに悪影響を与えることを防ぎます。
これらの変更により、encoding/xml
パッケージは、XMLの複雑な構造やカスタム要件に対応するための柔軟性を大幅に向上させつつ、デコードプロセスの堅牢性と安全性を維持しています。
関連リンク
- Go言語
encoding/xml
パッケージのドキュメント: https://pkg.go.dev/encoding/xml - Go言語
encoding/json
パッケージのUnmarshaler
インターフェース (参考): https://pkg.go.dev/encoding/json#Unmarshaler
参考にした情報源リンク
golang.org/s/go12xml
(Go 1.2 XMLパッケージ改善デザインドキュメント):- このショートリンクは、Goの公式リポジトリのコミットメッセージでよく使われる形式です。通常、
https://go.dev/s/go12xml
のような形式でアクセスできます。 - 検索結果から、このリンクは
https://go.dev/doc/go1.2#xml
にリダイレクトされることが分かりました。これはGo 1.2のリリースノートのXMLセクションを指しており、encoding/xml
パッケージの変更点について簡潔に説明されています。 - より詳細なデザインドキュメント自体は、Goの提案プロセス (Go Proposal Process) の一部としてGerrit (Goのコードレビューシステム) やGoのIssueトラッカーで議論されたものと思われます。このコミットのGerritリンク
https://golang.org/cl/12556043
を参照すると、より詳細な議論や変更の経緯が確認できます。
- このショートリンクは、Goの公式リポジトリのコミットメッセージでよく使われる形式です。通常、
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go言語のGerritコードレビューシステム: https://go-review.googlesource.com/
- コミットメッセージにある
https://golang.org/cl/12556043
は、このGerritシステムにおける変更リスト(Change-List)への直接リンクです。このリンクを辿ることで、このコミットに関する詳細なレビューコメント、パッチセット、および関連する議論を閲覧できます。
- コミットメッセージにある
[インデックス 17241] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/xml
パッケージに Unmarshaler
および UnmarshalerAttr
インターフェースを追加し、XMLのアンマーシャリングプロセスをより柔軟にカスタマイズできるようにするものです。これにより、開発者は特定のXML要素や属性のデコードロジックを独自に定義できるようになります。
コミット
commit 84b0842a59b8bf8e890861f7859869fb73d8681c
Author: Russ Cox <rsc@golang.org>
Date: Wed Aug 14 14:57:45 2013 -0400
encoding/xml: add, support Unmarshaler interface
See golang.org/s/go12xml for design.
R=golang-dev, dominik.honnef, dan.kortschak
CC=golang-dev
https://golang.org/cl/12556043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/84b0842a59b8bf8e890861f7859869fb73d8681c
元コミット内容
encoding/xml
パッケージに Unmarshaler
インターフェースのサポートを追加する。設計については golang.org/s/go12xml
を参照。
変更の背景
Go言語の encoding/xml
パッケージは、XMLデータをGoの構造体にマッピングするための強力な機能を提供します。しかし、従来のバージョンでは、XMLのデコード(アンマーシャリング)プロセスは主にリフレクションと構造体タグに基づいて自動的に行われていました。これにより、一般的なXML構造は簡単に扱える一方で、以下のような特定のユースケースでは柔軟性が不足していました。
- 非標準的なXML構造: XMLには、要素のテキストコンテンツが複雑な形式であったり、属性の値が特殊なエンコーディングを必要とするなど、標準的なGoの型マッピングでは対応しきれないケースが存在します。例えば、要素の内部にコメントや処理命令が混在している場合、それらを無視して純粋なテキストデータのみを抽出したいといった要求があります。
- カスタムデコードロジック: 開発者がXMLデータの一部をデコードする際に、独自のバリデーション、変換、または外部サービスとの連携などのカスタムロジックを適用したい場合があります。例えば、日付文字列を特定のフォーマットでパースしたり、特定の条件に基づいてデフォルト値を設定したりするケースです。
- パフォーマンス最適化: 大量のXMLデータを処理する際に、リフレクションベースの自動デコードよりも、手動で最適化されたデコードロジックの方がパフォーマンスが向上する可能性があります。特に、XML構造の一部のみが必要な場合や、非常に大きな要素を効率的に処理したい場合に有効です。
これらの課題に対処するため、encoding/xml
パッケージに Unmarshaler
インターフェースが導入されました。これにより、Goの型がこのインターフェースを実装することで、XMLデコーダがその型のインスタンスをアンマーシャリングする際に、自動デコードの代わりにカスタムロジックを呼び出すことができるようになります。これは、encoding/json
パッケージにおける json.Unmarshaler
と同様のパターンであり、Goの標準ライブラリにおけるデータシリアライゼーション/デシリアライゼーションの一貫性を保つ上でも重要でした。
コミットメッセージに記載されている golang.org/s/go12xml
は、この変更の設計思想と詳細な仕様を説明するデザインドキュメントへのショートリンクです。このドキュメントは、Go 1.2リリースに向けたXMLパッケージの改善提案の一部として作成されました。この変更は、Go 1.2のリリースノートにも記載されており、XML処理の柔軟性を高める重要な機能追加として位置づけられています。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびXMLに関する基本的な知識が必要です。
- Go言語のリフレクション: Goのリフレクションは、実行時にプログラムの構造(型、フィールド、メソッドなど)を検査・操作する機能です。
encoding/xml
パッケージは、Goの構造体とXML要素/属性のマッピングにリフレクションを広く利用しています。例えば、構造体タグを読み取ってXML要素名や属性名を決定したり、フィールドの型に基づいて適切なデコード処理を選択したりします。 - Go言語のインターフェース: インターフェースは、メソッドのシグネチャの集合を定義する型です。Goの型がインターフェースのすべてのメソッドを実装すると、その型はそのインターフェースを満たします。これにより、ポリモーフィックな振る舞いを実現できます。このコミットでは、
Unmarshaler
とUnmarshalerAttr
というインターフェースを定義し、特定の型がXMLデコードのカスタムロジックを提供できるようにしています。 - XML (Extensible Markup Language): 構造化されたデータを表現するためのマークアップ言語です。要素(例:
<book>...</book>
)、属性(例:<book id="123">
)、テキストコンテンツなどから構成されます。XMLは、異なるシステム間でデータを交換するための標準的な形式として広く利用されています。 - XMLのアンマーシャリング (Unmarshaling): XMLデータをプログラム内のデータ構造(Goの構造体など)に変換するプロセスです。
encoding/xml
パッケージのxml.Unmarshal
関数やxml.Decoder
を使用して行われます。 encoding/xml
パッケージ: Goの標準ライブラリの一部で、XMLデータのエンコード(マーシャリング)とデコード(アンマーシャリング)を提供します。xml.Decoder
: XMLストリームを読み込み、XMLの各構成要素を表現するトークン(StartElement
,EndElement
,CharData
,Comment
,ProcInst
など)を順次生成します。これは、XMLドキュメントをイベント駆動型で処理する際に使用されます。xml.Unmarshal
: XMLバイトスライスをGoのデータ構造にデコードする高レベルな関数です。内部でxml.Decoder
を使用し、リフレクションと構造体タグに基づいて自動的にマッピングを行います。xml.StartElement
,xml.EndElement
,xml.CharData
,xml.Attr
: XMLの各構成要素を表す型です。StartElement
は開始タグの名前と属性を含み、CharData
は要素内のテキストデータを含みます。xml:"tag"
構造体タグ: 構造体のフィールドとXML要素/属性のマッピングを制御するために使用されます。例えば、xml:"name"
はフィールドがname
という名前のXML要素にマッピングされることを示し、xml:",attr"
はフィールドが属性にマッピングされることを示します。
技術的詳細
このコミットの主要な技術的変更点は、Unmarshaler
と UnmarshalerAttr
という2つの新しいインターフェースの導入と、xml.Decoder
および xml.Unmarshal
がこれらのインターフェースを認識し、利用するように変更された点です。
Unmarshaler
インターフェース
type Unmarshaler interface {
UnmarshalXML(d *Decoder, start StartElement) error
}
- このインターフェースは、XML要素全体をカスタムでアンマーシャリングしたい型が実装します。
UnmarshalXML
メソッドは、*xml.Decoder
のインスタンスと、現在処理中のXML要素の開始タグ (xml.StartElement
) を引数に取ります。- このメソッド内で、実装者は
d.Token()
やd.DecodeElement()
などのDecoder
メソッドを使用して、XMLストリームから必要なデータを読み込み、自身の型にデコードします。d.Token()
はXMLストリームから次のトークンを読み込み、d.DecodeElement()
は現在の要素を特定のGoの型にデコードします。 - 重要な制約として、
UnmarshalXML
は正確に1つのXML要素を消費しなければなりません。つまり、開始タグに対応する終了タグまでを読み込む必要があります。もし、要素の途中で処理を停止したり、余分なトークンを読み込んだりすると、xml: %s.UnmarshalXML did not consume entire <%s> element
のようなエラーが発生します。これは、デコーダの内部状態の一貫性を保ち、後続のXMLパースが正しく行われるようにするために非常に重要です。 UnmarshalXML
メソッド内ではd.RawToken()
を使用することはできません。これは、RawToken
が名前空間の解決や要素のマッチングを行わない低レベルなトークン取得メソッドであり、カスタムアンマーシャリングのロジックを複雑にし、予期せぬ動作を引き起こす可能性があるためです。代わりに、名前空間の解決や要素のマッチングが適切に行われるd.Token()
を使用することが推奨されます。
UnmarshalerAttr
インターフェース
type UnmarshalerAttr interface {
UnmarshalXMLAttr(attr Attr) error
}
- このインターフェースは、XML属性の値をカスタムでアンマーシャリングしたい型が実装します。
UnmarshalXMLAttr
メソッドは、現在処理中のXML属性 (xml.Attr
) を引数に取ります。Attr
構造体には属性の名前 (Name
) と値 (Value
) が含まれます。- このインターフェースは、構造体フィールドに
xml:",attr"
タグが指定されている場合にのみ使用されます。 - 属性は単一の値であるため、
UnmarshalXML
のような複雑なストリーム処理は不要で、直接attr.Value
を処理します。例えば、属性値が特定のフォーマットの文字列である場合に、それをGoの適切な型に変換するなどのカスタムロジックを実装できます。
xml.Decoder
の変更
Decoder
構造体にunmarshalDepth int
フィールドが追加されました。これは、UnmarshalXML
メソッドが呼び出されている深さを追跡するために使用されます。この値が0より大きい場合(つまり、カスタムアンマーシャリングが進行中の場合)、RawToken()
の呼び出しが禁止されます。unmarshalInterface
およびunmarshalAttr
という内部ヘルパー関数が追加されました。これらは、Goの型がUnmarshaler
またはUnmarshalerAttr
インターフェースを実装しているかどうかをリフレクションでチェックし、実装していれば対応するUnmarshalXML
またはUnmarshalXMLAttr
メソッドを呼び出します。このチェックは、ポインタ型 (*MyType
) と非ポインタ型 (MyType
) の両方に対して行われます。unmarshal
メソッド(XML要素のアンマーシャリングの主要ロジック)が変更され、値がUnmarshaler
インターフェースを実装している場合に、そのカスタムロジックを優先的に呼び出すようになりました。unmarshalAttr
メソッドも同様に、UnmarshalerAttr
インターフェースの実装をチェックし、カスタムロジックを呼び出すように変更されました。pushEOF()
とpopEOF()
という新しいメソッドがDecoder
に追加されました。これらは、UnmarshalXML
メソッドがXML要素全体を消費したことを確認するためのメカニズムです。UnmarshalXML
が呼び出される前にpushEOF()
が呼ばれ、対応する終了タグが読み込まれた後にpopEOF()
が呼ばれます。もしpopEOF()
がfalse
を返した場合(つまり、カスタムアンマーシャリングが要素全体を消費しなかった場合)、エラーが報告されます。これにより、カスタムアンマーシャリングの実装ミスによるデコーダの内部状態の不整合を防ぎます。Token()
メソッドの内部でd.RawToken()
の代わりにd.rawToken()
が呼び出されるようになりました。これは、RawToken()
がunmarshalDepth
のチェックを行う公開APIであるのに対し、rawToken()
はそのチェックを行わない内部ヘルパー関数であるためです。
read_test.go
の変更
MyCharData
とMyAttr
というカスタム型が追加され、それぞれUnmarshaler
とUnmarshalerAttr
インターフェースを実装しています。MyCharData
はUnmarshalXML
メソッド内でd.Token()
を使用して要素内のCharData
を結合する例を示しています。これにより、XML要素内のコメントなどを無視して純粋なテキストコンテンツのみを抽出するカスタム処理が可能です。MyAttr
はUnmarshalXMLAttr
メソッドで属性値を直接受け取る例を示しています。MyStruct
という構造体が定義され、これらのカスタム型をフィールドとして含み、xml:",attr"
タグの利用例も示されています。TestUnmarshaler
という新しいテスト関数が追加され、これらのカスタムアンマーシャリングが期待通りに動作することを確認しています。このテストでは、ポインタ型 (*MyCharData
,*MyAttr
) と非ポインタ型 (MyCharData
,MyAttr
) の両方でインターフェースが正しく機能することを確認しています。
これらの変更により、encoding/xml
パッケージは、XMLの複雑な構造やカスタム要件に対応するための柔軟性を大幅に向上させつつ、デコードプロセスの堅牢性と安全性を維持しています。
コアとなるコードの変更箇所
src/pkg/encoding/xml/read.go
Unmarshaler
インターフェースとUnmarshalXML(d *Decoder, start StartElement) error
メソッドの定義を追加。UnmarshalerAttr
インターフェースとUnmarshalXMLAttr(attr Attr) error
メソッドの定義を追加。receiverType
ヘルパー関数を追加。これは、エラーメッセージなどでレシーバの型名を整形するために使用されます。unmarshalInterface
ヘルパー関数を追加。Unmarshaler
インターフェースを実装する値のアンマーシャリングを処理し、pushEOF
/popEOF
を使用して要素の消費を検証します。unmarshalAttr
ヘルパー関数を追加。UnmarshalerAttr
インターフェースを実装する値のアンマーシャリングを処理します。unmarshalerType
とunmarshalerAttrType
というreflect.Type
変数を追加し、インターフェースの型情報をキャッシュ。これにより、リフレクションによる型チェックのパフォーマンスが向上します。(*Decoder).unmarshal
メソッド内で、値がUnmarshaler
インターフェースを実装している場合の処理ロジックを追加。具体的には、ポインタ型と非ポインタ型の両方でインターフェースの実装をチェックし、unmarshalInterface
を呼び出します。(*Decoder).unmarshal
メソッド内で、属性のアンマーシャリングにunmarshalAttr
を使用するように変更。
src/pkg/encoding/xml/read_test.go
MyCharData
構造体とUnmarshalXML
メソッドの実装を追加。これは、要素内の文字データをカスタムで処理する例です。MyAttr
構造体とUnmarshalXMLAttr
メソッドの実装を追加。これは、属性値をカスタムで処理する例です。MyStruct
構造体を追加し、MyCharData
とMyAttr
をフィールドとして含む。これにより、カスタムアンマーシャリングが組み込まれた構造体のテストが可能になります。TestUnmarshaler
テスト関数を追加し、新しいインターフェースの動作を検証。このテストは、XML文字列を定義し、Unmarshal
関数を使用してMyStruct
にデコードし、結果が期待通りであることをアサートします。
src/pkg/encoding/xml/xml.go
Decoder
構造体にunmarshalDepth int
フィールドを追加。これは、UnmarshalXML
メソッドのネストの深さを追跡し、RawToken()
の使用を制限するために使用されます。Decoder.Token()
メソッド内で、d.nextToken
が設定されている場合とd.rawToken()
を呼び出すロジックを調整。これにより、カスタムアンマーシャリング中にToken()
が正しく動作するようにします。stkEOF
定数をstack
のkind
に追加。これは、pushEOF
/popEOF
メカニズムで使用されるスタックエントリの種類を示します。pushEOF()
とpopEOF()
メソッドをDecoder
に追加。これらのメソッドは、UnmarshalXML
が呼び出されたXML要素の開始タグと終了タグの間のトークンを正確に消費したことを検証するために使用されます。Decoder.popElement()
メソッド内で、スタックをポップする条件にd.stk.kind != stkEOF
を追加。これにより、pushEOF
によって挿入されたstkEOF
エントリが適切に処理されるようにします。errRawToken
エラー変数を追加。これは、UnmarshalXML
メソッド内からRawToken()
が呼び出された場合に返されるエラーです。RawToken()
メソッドのロジックを変更し、unmarshalDepth > 0
の場合はerrRawToken
を返すように制限。rawToken()
という内部ヘルパー関数を新設し、元のRawToken()
の実処理を移動。これにより、RawToken()
の公開APIとしての振る舞いと、内部的なトークン取得ロジックを分離します。isNameString
ヘルパー関数を追加。これは、XMLの名前が有効な形式であるかを文字列としてチェックするために使用されます。(*printer).EscapeString
メソッドを追加。これは、XMLテキストを適切にエスケープして出力するためのヘルパーメソッドです。
コアとなるコードの解説
このコミットの核心は、Goの encoding/xml
パッケージが、XMLデータのデコード時に開発者が提供するカスタムロジックを呼び出すためのフックを提供することです。
Unmarshaler
インターフェースの実装例 (MyCharData
)
src/pkg/encoding/xml/read_test.go
にある MyCharData
の例を見てみましょう。
type MyCharData struct {
body string
}
func (m *MyCharData) UnmarshalXML(d *Decoder, start StartElement) error {
for {
t, err := d.Token() // Decoderから次のトークンを取得
if err == io.EOF { // 要素の終わりに達したらループを抜ける
break
}
if err != nil {
return err
}
if char, ok := t.(CharData); ok { // トークンがCharData(テキストコンテンツ)であれば
m.body += string(char) // bodyフィールドに結合
}
}
return nil
}
この UnmarshalXML
メソッドは、MyCharData
型がXML要素としてアンマーシャリングされる際に呼び出されます。メソッド内では、d.Token()
を繰り返し呼び出すことで、XML要素の開始タグと終了タグの間にあるすべてのトークン(この場合は主に CharData
)を読み込み、それらを m.body
フィールドに結合しています。これにより、例えばXML要素内のコメントなどを無視し、純粋なテキストコンテンツのみを抽出するといったカスタム処理が可能になります。この例では、<!-- comment -->
のようなコメントがXMLデータに含まれていても、MyCharData.body
には hello world
のみが格納されます。
UnmarshalerAttr
インターフェースの実装例 (MyAttr
)
同じく src/pkg/encoding/xml/read_test.go
にある MyAttr
の例です。
type MyAttr struct {
attr string
}
func (m *MyAttr) UnmarshalXMLAttr(attr Attr) error {
m.attr = attr.Value // 属性の値を直接attrフィールドに代入
return nil
}
MyAttr
型が構造体フィールドで xml:",attr"
タグと共に使用されると、この UnmarshalXMLAttr
メソッドが呼び出されます。このメソッドは、XML属性の Attr
構造体を直接受け取るため、その Value
フィールドを m.attr
に代入するだけでカスタム処理が完了します。これは、属性値のパースやバリデーションなど、より複雑なロジックを実装する際に役立ちます。例えば、属性値がカンマ区切りの文字列である場合に、それをGoのスライスに変換するなどの処理をこのメソッド内で行うことができます。
Decoder
の変更点と安全性
Decoder
に追加された unmarshalDepth
フィールドと pushEOF
/popEOF
メソッドは、カスタムアンマーシャリングの安全性を確保するために重要です。
unmarshalDepth
は、UnmarshalXML
メソッドがネストして呼び出されている深さを追跡します。この値が0より大きい場合、RawToken()
の呼び出しが禁止されます。これは、RawToken()
が名前空間の解決や要素のマッチングを行わないため、カスタムアンマーシャリングロジック内で誤って使用されると、XMLストリームのパース状態を壊してしまう可能性があるためです。Token()
は名前空間の解決や要素のマッチングを適切に行うため、より安全な選択肢です。pushEOF()
とpopEOF()
は、UnmarshalXML
メソッドが呼び出されたXML要素の開始タグから終了タグまでを正確に消費したことを検証するためのメカニズムです。UnmarshalXML
が呼び出される直前にpushEOF()
がデコーダのスタックに特別なマーカーをプッシュし、UnmarshalXML
の処理が完了した後にpopEOF()
がそのマーカーをチェックします。もしUnmarshalXML
が要素の一部しか消費しなかったり、余分なトークンを読み込んだりした場合、popEOF()
がfalse
を返し、xml: %s.UnmarshalXML did not consume entire <%s> element
のようなエラーが報告されます。これにより、カスタムアンマーシャリングの実装ミスが、後続のXMLパースに悪影響を与えることを防ぎ、デコーダの内部状態の一貫性を保ちます。
これらの変更により、encoding/xml
パッケージは、XMLの複雑な構造やカスタム要件に対応するための柔軟性を大幅に向上させつつ、デコードプロセスの堅牢性と安全性を維持しています。
関連リンク
- Go言語
encoding/xml
パッケージのドキュメント: https://pkg.go.dev/encoding/xml - Go言語
encoding/json
パッケージのUnmarshaler
インターフェース (参考): https://pkg.go.dev/encoding/json#Unmarshalerencoding/xml
のUnmarshaler
は、encoding/json
のjson.Unmarshaler
と同様の設計思想に基づいています。両者とも、標準のデコードロジックでは対応できないカスタムなデコード処理を可能にするためのインターフェースを提供します。
参考にした情報源リンク
golang.org/s/go12xml
(Go 1.2 XMLパッケージ改善デザインドキュメント):- このショートリンクは、Goの公式リポジトリのコミットメッセージでよく使われる形式です。通常、
https://go.dev/s/go12xml
のような形式でアクセスできます。 - Web検索の結果、このリンクは
https://go.dev/doc/go1.2#xml
にリダイレクトされることが分かりました。これはGo 1.2のリリースノートのXMLセクションを指しており、encoding/xml
パッケージの変更点について簡潔に説明されています。 - より詳細なデザインドキュメント自体は、Goの提案プロセス (Go Proposal Process) の一部としてGerrit (Goのコードレビューシステム) やGoのIssueトラッカーで議論されたものと思われます。
- このショートリンクは、Goの公式リポジトリのコミットメッセージでよく使われる形式です。通常、
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go言語のGerritコードレビューシステム: https://go-review.googlesource.com/
- コミットメッセージにある
https://golang.org/cl/12556043
は、このGerritシステムにおける変更リスト(Change-List)への直接リンクです。このリンクを辿ることで、このコミットに関する詳細なレビューコメント、パッチセット、および関連する議論を閲覧できます。これは、Go言語の開発プロセスにおける透明性と共同作業の重要な側面です。
- コミットメッセージにある