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

[インデックス 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" はフィールドが属性にマッピングされることを示します。

技術的詳細

このコミットの主要な技術的変更点は、UnmarshalerUnmarshalerAttr という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 インターフェースの実装をチェックするようになりました。これにより、*MyTypeMyType の両方が 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 の変更

  • MyCharDataMyAttr というカスタム型が追加され、それぞれ UnmarshalerUnmarshalerAttr インターフェースを実装しています。
  • MyCharDataUnmarshalXML メソッド内で d.Token() を使用して要素内の CharData を結合する例を示しています。
  • MyAttrUnmarshalXMLAttr メソッドで属性値を直接受け取る例を示しています。
  • 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 インターフェースを実装する値のアンマーシャリングを処理。
  • unmarshalerTypeunmarshalerAttrType という reflect.Type 変数を追加し、インターフェースの型情報をキャッシュ。
  • (*Decoder).unmarshal メソッド内で、値が Unmarshaler インターフェースを実装している場合の処理ロジックを追加。
  • (*Decoder).unmarshal メソッド内で、属性のアンマーシャリングに unmarshalAttr を使用するように変更。

src/pkg/encoding/xml/read_test.go

  • MyCharData 構造体と UnmarshalXML メソッドの実装を追加。
  • MyAttr 構造体と UnmarshalXMLAttr メソッドの実装を追加。
  • MyStruct 構造体を追加し、MyCharDataMyAttr をフィールドとして含む。
  • TestUnmarshaler テスト関数を追加し、新しいインターフェースの動作を検証。

src/pkg/encoding/xml/xml.go

  • Decoder 構造体に unmarshalDepth int フィールドを追加。
  • Decoder.Token() メソッド内で、d.nextToken が設定されている場合と d.rawToken() を呼び出すロジックを調整。
  • stkEOF 定数を stackkind に追加。
  • 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の複雑な構造やカスタム要件に対応するための柔軟性を大幅に向上させつつ、デコードプロセスの堅牢性と安全性を維持しています。

関連リンク

参考にした情報源リンク

  • 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言語のコミット履歴 (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の型がインターフェースのすべてのメソッドを実装すると、その型はそのインターフェースを満たします。これにより、ポリモーフィックな振る舞いを実現できます。このコミットでは、UnmarshalerUnmarshalerAttr というインターフェースを定義し、特定の型が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" はフィールドが属性にマッピングされることを示します。

技術的詳細

このコミットの主要な技術的変更点は、UnmarshalerUnmarshalerAttr という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 の変更

  • MyCharDataMyAttr というカスタム型が追加され、それぞれ UnmarshalerUnmarshalerAttr インターフェースを実装しています。
  • MyCharDataUnmarshalXML メソッド内で d.Token() を使用して要素内の CharData を結合する例を示しています。これにより、XML要素内のコメントなどを無視して純粋なテキストコンテンツのみを抽出するカスタム処理が可能です。
  • MyAttrUnmarshalXMLAttr メソッドで属性値を直接受け取る例を示しています。
  • 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 インターフェースを実装する値のアンマーシャリングを処理します。
  • unmarshalerTypeunmarshalerAttrType という reflect.Type 変数を追加し、インターフェースの型情報をキャッシュ。これにより、リフレクションによる型チェックのパフォーマンスが向上します。
  • (*Decoder).unmarshal メソッド内で、値が Unmarshaler インターフェースを実装している場合の処理ロジックを追加。具体的には、ポインタ型と非ポインタ型の両方でインターフェースの実装をチェックし、unmarshalInterface を呼び出します。
  • (*Decoder).unmarshal メソッド内で、属性のアンマーシャリングに unmarshalAttr を使用するように変更。

src/pkg/encoding/xml/read_test.go

  • MyCharData 構造体と UnmarshalXML メソッドの実装を追加。これは、要素内の文字データをカスタムで処理する例です。
  • MyAttr 構造体と UnmarshalXMLAttr メソッドの実装を追加。これは、属性値をカスタムで処理する例です。
  • MyStruct 構造体を追加し、MyCharDataMyAttr をフィールドとして含む。これにより、カスタムアンマーシャリングが組み込まれた構造体のテストが可能になります。
  • TestUnmarshaler テスト関数を追加し、新しいインターフェースの動作を検証。このテストは、XML文字列を定義し、Unmarshal 関数を使用して MyStruct にデコードし、結果が期待通りであることをアサートします。

src/pkg/encoding/xml/xml.go

  • Decoder 構造体に unmarshalDepth int フィールドを追加。これは、UnmarshalXML メソッドのネストの深さを追跡し、RawToken() の使用を制限するために使用されます。
  • Decoder.Token() メソッド内で、d.nextToken が設定されている場合と d.rawToken() を呼び出すロジックを調整。これにより、カスタムアンマーシャリング中に Token() が正しく動作するようにします。
  • stkEOF 定数を stackkind に追加。これは、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#Unmarshaler
    • encoding/xmlUnmarshaler は、encoding/jsonjson.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言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
  • Go言語のGerritコードレビューシステム: https://go-review.googlesource.com/
    • コミットメッセージにある https://golang.org/cl/12556043 は、このGerritシステムにおける変更リスト(Change-List)への直接リンクです。このリンクを辿ることで、このコミットに関する詳細なレビューコメント、パッチセット、および関連する議論を閲覧できます。これは、Go言語の開発プロセスにおける透明性と共同作業の重要な側面です。