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

[インデックス 17253] ファイルの概要

このコミットは、Go言語の標準ライブラリである encoding/xml パッケージに対する変更です。encoding/xml パッケージは、Goのデータ構造とXMLドキュメントの間でエンコード(GoからXMLへ)およびデコード(XMLからGoへ)を行う機能を提供します。このパッケージは、XMLデータのマーシャリング(GoオブジェクトからXMLへの変換)とアンマーシャリング(XMLからGoオブジェクトへの変換)を扱うための主要なツールです。

コミット

encoding/xml: support generic encoding interfaces

Remove custom support for time.Time. No new tests: the tests for the time.Time special case now test the general case.

R=golang-dev, bradfitz CC=golang-dev https://golang.org/cl/12751045

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/904e11361581a5e0f3ffa3576489fe994bfeff6a

元コミット内容

commit 904e11361581a5e0f3ffa3576489fe994bfeff6a
Author: Russ Cox <rsc@golang.org>
Date:   Wed Aug 14 18:52:09 2013 -0400

    encoding/xml: support generic encoding interfaces
    
    Remove custom support for time.Time.
    No new tests: the tests for the time.Time special case
    now test the general case.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/12751045
---
 src/pkg/encoding/xml/marshal.go | 116 +++++++++++++++++++++++++++++-----------\n src/pkg/encoding/xml/read.go    |  90 ++++++++++++++++++++++++-------\n src/pkg/encoding/xml/xml.go     |   5 ++\n src/pkg/go/build/deps_test.go   |   2 +-\n 4 files changed, 163 insertions(+), 50 deletions(-)

変更の背景

このコミットの主な背景は、encoding/xml パッケージが time.Time 型をXMLにエンコード・デコードする際に持っていた特殊な処理を削除し、より汎用的なインターフェースベースのメカニズムに移行することです。

以前の encoding/xml パッケージでは、time.Time 型はXMLへのマーシャリング(GoオブジェクトからXMLへの変換)およびアンマーシャリング(XMLからGoオブジェクトへの変換)において、特別な扱いを受けていました。具体的には、time.Time 型は time.RFC3339Nano フォーマットで文字列としてXMLに変換され、その逆も同様でした。

このような特殊な扱いは、特定の型(この場合は time.Time)に依存したコードパスを生み出し、以下のような問題を引き起こす可能性がありました。

  1. 拡張性の欠如: time.Time 以外のカスタム型や、同様に文字列としてエンコード・デコードしたい他の型に対して、同様の特殊処理を実装する必要が生じた場合、コードの重複や複雑さが増します。
  2. 一貫性の欠如: encoding/xml パッケージが提供する MarshalerUnmarshaler といったインターフェースによる汎用的なエンコーディングメカニズムと、time.Time の特殊処理との間に一貫性がありませんでした。
  3. 保守性の低下: 特定の型に特化したロジックは、その型が変更された場合や、新しいエンコーディング要件が生じた場合に、コードの変更が広範囲に及ぶ可能性があります。

このコミットでは、encoding パッケージで定義されている encoding.TextMarshaler および encoding.TextUnmarshaler インターフェースを encoding/xml パッケージに統合することで、これらの問題を解決しようとしています。これにより、time.Time 型だけでなく、これらのインターフェースを実装する任意の型が、XMLのテキストコンテンツや属性値として適切にエンコード・デコードされるようになります。結果として、コードはより汎用的で、拡張性が高く、保守しやすくなります。

前提知識の解説

Go言語の encoding/xml パッケージ

encoding/xml パッケージは、Goの構造体とXMLドキュメントの間でデータを変換するための機能を提供します。主な機能は以下の通りです。

  • マーシャリング (Marshaling): Goの構造体やその他の値をXML形式のバイト列に変換します。xml.Marshal 関数がこれを行います。
  • アンマーシャリング (Unmarshaling): XML形式のバイト列をGoの構造体やその他の値に変換します。xml.Unmarshal 関数がこれを行います。
  • XMLタグ: 構造体のフィールドタグ(例: xml:"name,attr")を使用して、XML要素名、属性、CDATA、ネストされた要素などを制御できます。
  • インターフェース: xml.Marshaler および xml.Unmarshaler インターフェースを実装することで、カスタムのXMLエンコード・デコードロジックを定義できます。

Go言語の reflect パッケージ

reflect パッケージは、Goプログラムが実行時に自身の構造を検査・操作するための機能(リフレクション)を提供します。このコミットでは、特に以下の機能が利用されています。

  • reflect.TypeOf: 変数の動的な型情報を取得します。
  • reflect.ValueOf: 変数の動的な値情報を取得します。
  • reflect.Kind: 型の基本的なカテゴリ(例: struct, int, string, interface, ptr など)を識別します。
  • reflect.Value.CanInterface(): reflect.Value がインターフェース値として安全に変換できるかを確認します。
  • reflect.Value.Implements(reflect.Type): reflect.Value が特定のインターフェースを実装しているかを確認します。
  • reflect.Value.CanAddr(): reflect.Value がアドレス可能(ポインタを取得できる)かを確認します。これは、ポインタレシーバを持つメソッド(インターフェースの実装を含む)を呼び出すために重要です。

encoding/xml のような汎用的なエンコーディングパッケージでは、入力されるGoの型が事前にわからないため、リフレクションを多用して実行時に型の情報を取得し、適切なエンコード・デコード処理を適用します。

Go言語の encoding パッケージと汎用インターフェース

Goの標準ライブラリには、encoding というパッケージがあり、様々なエンコーディング形式(JSON, XML, Gobなど)で共通して利用できる汎用的なインターフェースが定義されています。このコミットで特に重要となるのは以下の二つのインターフェースです。

  • encoding.TextMarshaler インターフェース:

    type TextMarshaler interface {
        MarshalText() (text []byte, err error)
    }
    

    このインターフェースを実装する型は、自身をテキスト形式のバイト列に変換する方法を定義できます。例えば、time.Time 型は内部的にこのインターフェースを実装しており、日付時刻情報を特定の文字列フォーマット(例: RFC3339)で表現します。

  • encoding.TextUnmarshaler インターフェース:

    type TextUnmarshaler interface {
        UnmarshalText(text []byte) error
    }
    

    このインターフェースを実装する型は、テキスト形式のバイト列から自身を再構築する方法を定義できます。encoding.TextMarshaler と対になり、テキスト表現から元の値に変換します。

これらのインターフェースは、特定の型に依存しない汎用的なテキストエンコーディング・デコーディングメカニズムを提供し、encoding/xml のようなパッケージが、カスタム型をXMLのテキストコンテンツや属性値として扱うための標準的な方法を提供します。

time.Time 型の特殊扱い

このコミット以前の encoding/xml パッケージでは、time.Time 型は encoding.TextMarshalerencoding.TextUnmarshaler インターフェースを介してではなく、encoding/xml パッケージ内部で直接 time.Time 型であるかをチェックし、time.RFC3339Nano フォーマットで文字列変換を行うという特殊なロジックが組み込まれていました。これは、time.Time が非常に一般的な型であり、XMLとの間で標準的な表現を持つべきだという設計判断によるものですが、結果的にコードの汎用性を損ねていました。

技術的詳細

このコミットの技術的な核心は、encoding/xml パッケージが time.Time 型を特別扱いするロジックを削除し、代わりに encoding.TextMarshaler および encoding.TextUnmarshaler インターフェースを汎用的に利用するように変更した点にあります。

time.Time の特殊処理の削除

  • src/pkg/encoding/xml/marshal.go および src/pkg/encoding/xml/read.go から time パッケージのインポートが削除されました。
  • timeType = reflect.TypeOf(time.Time{}) のような time.Time 型を識別するためのリフレクション変数が削除されました。
  • marshalSimplemarshalStructcopyValue といった関数内で、val.Type() == timeType のような time.Time 型に特化した条件分岐や処理がすべて削除されました。これにより、encoding/xmltime.Time を他のGoの組み込み型や構造体と同様に、そのインターフェース実装に基づいて処理するようになります。

encoding.TextMarshaler および encoding.TextUnmarshaler の統合

  • インターフェース型の定義: src/pkg/encoding/xml/marshal.gotextMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() が追加され、encoding.TextMarshaler インターフェースの reflect.Type が取得できるようになりました。 同様に、src/pkg/encoding/xml/read.gotextUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() が追加されました。

  • マーシャリングロジックの変更 (marshal.go):

    • printer.marshalValue 関数内で、値が xml.Marshaler を実装しているかどうかのチェックに加えて、encoding.TextMarshaler を実装しているかどうかのチェックが追加されました。
    • val.CanInterface() && typ.Implements(textMarshalerType) または val.CanAddr() && pv.Type().Implements(textMarshalerType) の条件で encoding.TextMarshaler インターフェースが検出されると、新しく追加された p.marshalTextInterface 関数が呼び出されます。
    • 属性のマーシャリングにおいても、フィールドが encoding.TextMarshaler を実装している場合、その MarshalText メソッドが呼び出され、結果が属性値として使用されるようになりました。
    • fCharData フラグを持つフィールド(XML要素のテキストコンテンツとなるフィールド)の処理においても、encoding.TextMarshaler の実装が優先的に利用されるようになりました。
  • アンマーシャリングロジックの変更 (read.go):

    • Decoder.unmarshal 関数内で、値が xml.Unmarshaler を実装しているかどうかのチェックに加えて、encoding.TextUnmarshaler を実装しているかどうかのチェックが追加されました。
    • val.CanInterface() && val.Type().Implements(textUnmarshalerType) または val.CanAddr() && pv.Type().Implements(textUnmarshalerType) の条件で encoding.TextUnmarshaler インターフェースが検出されると、新しく追加された p.unmarshalTextInterface 関数が呼び出されます。
    • 属性のアンマーシャリングにおいても、xml.UnmarshalerAttr のチェックに加えて、encoding.TextUnmarshaler の実装がチェックされ、属性値がその UnmarshalText メソッドに渡されるようになりました。
    • copyValue 関数においても、encoding.TextUnmarshaler の実装が優先的に利用されるようになりました。

新しいヘルパー関数の導入

  • printer.marshalTextInterface(val encoding.TextMarshaler, start StartElement) error: この新しい関数は、encoding.TextMarshaler インターフェースを実装する値をXML要素としてマーシャリングする責任を負います。要素の開始タグを書き込み、val.MarshalText() を呼び出してテキストデータを取得し、そのデータをエスケープしてXMLコンテンツとして書き込み、最後に終了タグを書き込みます。

  • Decoder.unmarshalTextInterface(val encoding.TextUnmarshaler, start *StartElement) error: この新しい関数は、encoding.TextUnmarshaler インターフェースを実装する値をXML要素からアンマーシャリングする責任を負います。要素内の文字データ(CharData)を収集し、そのデータを val.UnmarshalText() メソッドに渡して値を再構築します。

src/pkg/encoding/xml/xml.go の変更

  • StartElement 型に End() EndElement メソッドが追加されました。これは、与えられた開始要素に対応する終了要素を簡単に生成するためのユーティリティメソッドです。この変更は、encoding.TextMarshaler / TextUnmarshaler のサポートとは直接的な関連はありませんが、XML構造を扱う上での利便性を向上させます。

src/pkg/go/build/deps_test.go の変更

  • encoding/xml パッケージの依存関係に encoding パッケージが追加されました。これは、encoding/xmlencoding.TextMarshalerencoding.TextUnmarshaler といった encoding パッケージのインターフェースに依存するようになったため、ビルドシステムがその依存関係を正しく認識するように更新されたものです。

これらの変更により、encoding/xml パッケージは、特定の型に依存することなく、encoding.TextMarshaler および encoding.TextUnmarshaler インターフェースを実装する任意の型を、XMLのテキストコンテンツや属性値として透過的に扱うことができるようになりました。これにより、パッケージの汎用性と拡張性が大幅に向上しました。

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

src/pkg/encoding/xml/marshal.go

--- a/src/pkg/encoding/xml/marshal.go
+++ b/src/pkg/encoding/xml/marshal.go
@@ -7,12 +7,12 @@ package xml
 import (
 	"bufio"
 	"bytes"
+	"encoding"
 	"fmt"
 	"io"
 	"reflect"
 	"strconv"
 	"strings"
-	"time"
 )
 
 const (
@@ -319,6 +319,7 @@ func (p *printer) popPrefix() {
 var (
 	marshalerType     = reflect.TypeOf((*Marshaler)(nil)).Elem()
 	marshalerAttrType = reflect.TypeOf((*MarshalerAttr)(nil)).Elem()
+	textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
 )
 
 // marshalValue writes one or more XML elements representing val.
@@ -348,14 +349,25 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat
 	}
 
 	// Check for marshaler.
-	if typ.Name() != "" && val.CanAddr() {
+	if val.CanInterface() && typ.Implements(marshalerType) {
+		return p.marshalInterface(val.Interface().(Marshaler), defaultStart(typ, finfo, startTemplate))
+	}
+	if val.CanAddr() {
 		pv := val.Addr()
 		if pv.CanInterface() && pv.Type().Implements(marshalerType) {
-			return p.marshalInterface(pv.Interface().(Marshaler), pv.Type(), finfo, startTemplate)
+			return p.marshalInterface(pv.Interface().(Marshaler), defaultStart(pv.Type(), finfo, startTemplate))
 		}
 	}
-	if val.CanInterface() && typ.Implements(marshalerType) {
-		return p.marshalInterface(val.Interface().(Marshaler), typ, finfo, startTemplate)
+
+	// Check for text marshaler.
+	if val.CanInterface() && typ.Implements(textMarshalerType) {
+		return p.marshalTextInterface(val.Interface().(encoding.TextMarshaler), defaultStart(typ, finfo, startTemplate))
+	}
+	if val.CanAddr() {
+		pv := val.Addr()
+		if pv.CanInterface() && pv.Type().Implements(textMarshalerType) {
+			return p.marshalTextInterface(pv.Interface().(encoding.TextMarshaler), defaultStart(pv.Type(), finfo, startTemplate))
+		}
 	}
 
 	// Slices and arrays iterate over the elements. They do not have an enclosing tag.
@@ -416,6 +428,21 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat
 			continue
 		}
 
+		if fv.Kind() == reflect.Interface && fv.IsNil() {
+			continue
+		}
+
+		if fv.CanInterface() && fv.Type().Implements(marshalerAttrType) {
+			attr, err := fv.Interface().(MarshalerAttr).MarshalXMLAttr(name)
+			if err != nil {
+				return err
+			}
+			if attr.Name.Local != "" {
+				start.Attr = append(start.Attr, attr)
+			}
+			continue
+		}
+
 		if fv.CanAddr() {
 			pv := fv.Addr()
 			if pv.CanInterface() && pv.Type().Implements(marshalerAttrType) {
@@ -430,20 +457,27 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat
 			}
 		}
 
-		if fv.CanInterface() && fv.Type().Implements(marshalerAttrType) {
-			if fv.Kind() == reflect.Interface && fv.IsNil() {
-				continue
-			}
-			attr, err := fv.Interface().(MarshalerAttr).MarshalXMLAttr(name)
+		if fv.CanInterface() && fv.Type().Implements(textMarshalerType) {
+			text, err := fv.Interface().(encoding.TextMarshaler).MarshalText()
 			if err != nil {
 				return err
 			}
-			if attr.Name.Local != "" {
-				start.Attr = append(start.Attr, attr)
-			}
+			start.Attr = append(start.Attr, Attr{name, string(text)})
 			continue
 		}
 
+		if fv.CanAddr() {
+			pv := fv.Addr()
+			if pv.CanInterface() && pv.Type().Implements(textMarshalerType) {
+				text, err := pv.Interface().(encoding.TextMarshaler).MarshalText()
+				if err != nil {
+					return err
+				}
+				start.Attr = append(start.Attr, Attr{name, string(text)})
+				continue
+			}
+		}
+
 		// Dereference or skip nil pointer, interface values.
 		switch fv.Kind() {
 		case reflect.Ptr, reflect.Interface:
@@ -490,10 +524,10 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat
 	return p.cachedWriteError()
 }
 
-// marshalInterface marshals a Marshaler interface value.\n-func (p *printer) marshalInterface(val Marshaler, typ reflect.Type, finfo *fieldInfo, startTemplate *StartElement) error {
+// defaultStart returns the default start element to use,
+// given the reflect type, field info, and start template.
+func defaultStart(typ reflect.Type, finfo *fieldInfo, startTemplate *StartElement) StartElement {
 	var start StartElement
-\n 	// Precedence for the XML element name is as above,\n 	// except that we do not look inside structs for the first field.\n 	if startTemplate != nil {
@@ -509,7 +543,11 @@ func (p *printer) marshalInterface(val Marshaler, typ reflect.Type, finfo *field
 		// since it has the Marshaler methods.
 		start.Name.Local = typ.Elem().Name()
 	}\n+	return start
+}\n 
+// marshalInterface marshals a Marshaler interface value.
+func (p *printer) marshalInterface(val Marshaler, start StartElement) error {
 	// Push a marker onto the tag stack so that MarshalXML
 	// cannot close the XML tags that it did not open.
 	p.tags = append(p.tags, Name{})
@@ -528,6 +566,19 @@ func (p *printer) marshalInterface(val Marshaler, typ reflect.Type, finfo *field
 	return nil
 }
 
+// marshalTextInterface marshals a TextMarshaler interface value.
+func (p *printer) marshalTextInterface(val encoding.TextMarshaler, start StartElement) error {
+	if err := p.writeStart(&start); err != nil {
+		return err
+	}
+	text, err := val.MarshalText()
+	if err != nil {
+		return err
+	}
+	EscapeText(p, text)
+	return p.writeEnd(start.Name)
+}
+
 // writeStart writes the given start element.
 func (p *printer) writeStart(start *StartElement) error {
 	if start.Name.Local == "" {
@@ -591,13 +642,7 @@ func (p *printer) writeEnd(name Name) error {
 	return nil
 }
 
-var timeType = reflect.TypeOf(time.Time{})
-
 func (p *printer) marshalSimple(typ reflect.Type, val reflect.Value) (string, []byte, error) {
-	// Normally we don't see structs, but this can happen for an attribute.
-	if val.Type() == timeType {
-		return val.Interface().(time.Time).Format(time.RFC3339Nano), nil, nil
-	}
 	switch val.Kind() {
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 		return strconv.FormatInt(val.Int(), 10), nil, nil
@@ -629,10 +674,6 @@ func (p *printer) marshalSimple(typ reflect.Type, val reflect.Value) (string, []
 var ddBytes = []byte("--")
 
 func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
-	if val.Type() == timeType {
-		_, err := p.WriteString(val.Interface().(time.Time).Format(time.RFC3339Nano))
-		return err
-	}
 	s := parentStack{p: p}
 	for i := range tinfo.fields {
 		finfo := &tinfo.fields[i]
@@ -651,6 +692,25 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
 
 		switch finfo.flags & fMode {
 		case fCharData:
+			if vf.CanInterface() && vf.Type().Implements(textMarshalerType) {
+				data, err := vf.Interface().(encoding.TextMarshaler).MarshalText()
+				if err != nil {
+					return err
+				}
+				Escape(p, data)
+				continue
+			}
+			if vf.CanAddr() {
+				pv := vf.Addr()
+				if pv.CanInterface() && pv.Type().Implements(textMarshalerType) {
+					data, err := pv.Interface().(encoding.TextMarshaler).MarshalText()
+					if err != nil {
+						return err
+					}
+					Escape(p, data)
+					continue
+				}
+			}
 			var scratch [64]byte
 			switch vf.Kind() {
 			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@@ -671,10 +731,6 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
 						return err
 					}
 				}
-			case reflect.Struct:
-				if vf.Type() == timeType {
-					Escape(p, []byte(vf.Interface().(time.Time).Format(time.RFC3339Nano)))
-				}
 			}
 			continue
 

src/pkg/encoding/xml/read.go

--- a/src/pkg/encoding/xml/read.go
+++ b/src/pkg/encoding/xml/read.go
@@ -6,12 +6,12 @@ package xml
 
 import (
 	"bytes"
+	"encoding"
 	"errors"
 	"fmt"
 	"reflect"
 	"strconv"
 	"strings"
-	"time"
 )
 
 // BUG(rsc): Mapping between XML elements and data structures is inherently flawed:
@@ -178,8 +178,7 @@ func receiverType(val interface{}) string {
 	return "(" + t.String() + ")"
 }
 
-// unmarshalInterface unmarshals a single XML element into val,\n-// which is known to implement Unmarshaler.
+// unmarshalInterface unmarshals a single XML element into val.
 // start is the opening tag of the element.
 func (p *Decoder) unmarshalInterface(val Unmarshaler, start *StartElement) error {
 	// Record that decoder must stop at end tag corresponding to start.
@@ -200,6 +199,31 @@ func (p *Decoder) unmarshalInterface(val Unmarshaler, start *StartElement) error
 	return nil
 }
 
+// unmarshalTextInterface unmarshals a single XML element into val.
+// The chardata contained in the element (but not its children)
+// is passed to the text unmarshaler.
+func (p *Decoder) unmarshalTextInterface(val encoding.TextUnmarshaler, start *StartElement) error {
+	var buf []byte
+	depth := 1
+	for depth > 0 {
+		t, err := p.Token()
+		if err != nil {
+			return err
+		}
+		switch t := t.(type) {
+		case CharData:
+			if depth == 1 {
+				buf = append(buf, t...)
+			}
+		case StartElement:
+			depth++
+		case EndElement:
+			depth--
+		}
+	}
+	return val.UnmarshalText(buf)
+}
+
 // unmarshalAttr unmarshals a single XML attribute into val.
 func (p *Decoder) unmarshalAttr(val reflect.Value, attr Attr) error {
 	if val.Kind() == reflect.Ptr {
@@ -221,7 +245,18 @@ func (p *Decoder) unmarshalAttr(val reflect.Value, attr Attr) error {
 		}
 	}\n 
-\t// TODO: Check for and use encoding.TextUnmarshaler.
+\t// Not an UnmarshalerAttr; try encoding.TextUnmarshaler.
+\tif val.CanInterface() && val.Type().Implements(textUnmarshalerType) {
+\t\t// This is an unmarshaler with a non-pointer receiver,
+\t\t// so it\'s likely to be incorrect, but we do what we\'re told.
+\t\treturn val.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(attr.Value))\n+\t}\n+\tif val.CanAddr() {
+\t\tpv := val.Addr()\n+\t\tif pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) {
+\t\t\treturn pv.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(attr.Value))\n+\t\t}\n+\t}\n \n \tcopyValue(val, []byte(attr.Value))\n \treturn nil
@@ -230,6 +265,7 @@ func (p *Decoder) unmarshalAttr(val reflect.Value, attr Attr) error {\n var (\n \tunmarshalerType     = reflect.TypeOf((*Unmarshaler)(nil)).Elem()\n \tunmarshalerAttrType = reflect.TypeOf((*UnmarshalerAttr)(nil)).Elem()\n+\ttextUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()\n )\n \n // Unmarshal a single XML element into val.\n@@ -268,7 +304,16 @@ func (p *Decoder) unmarshal(val reflect.Value, start *StartElement) error {\n \t\t}\n \t}\n \n-\t// TODO: Check for and use encoding.TextUnmarshaler.
+\tif val.CanInterface() && val.Type().Implements(textUnmarshalerType) {
+\t\treturn p.unmarshalTextInterface(val.Interface().(encoding.TextUnmarshaler), start)\n+\t}\n+\n+\tif val.CanAddr() {
+\t\tpv := val.Addr()\n+\t\tif pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) {
+\t\t\treturn p.unmarshalTextInterface(pv.Interface().(encoding.TextUnmarshaler), start)\n+\t\t}\n+\t}\n \n \tvar (\n \t\tdata         []byte
@@ -332,10 +377,6 @@ func (p *Decoder) unmarshal(val reflect.Value, start *StartElement) error {\n \t\t\tv.Set(reflect.ValueOf(start.Name))\n \t\t\tbreak\n \t\t}\n-\t\tif typ == timeType {\n-\t\t\tsaveData = v\n-\t\t\tbreak\n-\t\t}\n \n \t\tsv = v\n \t\ttinfo, err = getTypeInfo(typ)\n@@ -464,6 +505,23 @@ Loop:\n \t\t}\n \t}\n \n+\tif saveData.IsValid() && saveData.CanInterface() && saveData.Type().Implements(textUnmarshalerType) {\n+\t\tif err := saveData.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t\tsaveData = reflect.Value{}\n+\t}\n+\n+\tif saveData.IsValid() && saveData.CanAddr() {\n+\t\tpv := saveData.Addr()\n+\t\tif pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) {\n+\t\t\tif err := pv.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil {\n+\t\t\t\treturn err\n+\t\t\t}\n+\t\t\tsaveData = reflect.Value{}\n+\t\t}\n+\t}\n+\n \tif err := copyValue(saveData, data); err != nil {\n \t\treturn err\n \t}\n@@ -486,6 +544,8 @@ Loop:\n }\n \n func copyValue(dst reflect.Value, src []byte) (err error) {\n+\tdst0 := dst\n+\n \tif dst.Kind() == reflect.Ptr {\n \t\tif dst.IsNil() {\n \t\t\tdst.Set(reflect.New(dst.Type().Elem()))\n@@ -496,9 +556,9 @@ func copyValue(dst reflect.Value, src []byte) (err error) {\n \t// Save accumulated data.\n \tswitch dst.Kind() {\n \tcase reflect.Invalid:\n-\t\t// Probably a commendst.\n+\t\t// Probably a comment.\n \tdefault:\n-\t\treturn errors.New(\"cannot happen: unknown type \" + dst.Type().String())\n+\t\treturn errors.New(\"cannot unmarshal into \" + dst0.Type().String())\n \tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:\n \t\titmp, err := strconv.ParseInt(string(src), 10, dst.Type().Bits())\n \t\tif err != nil {\n@@ -531,14 +591,6 @@ func copyValue(dst reflect.Value, src []byte) (err error) {\n \t\t\tsrc = []byte{}\n \t\t}\n \t\tdst.SetBytes(src)\n-\tcase reflect.Struct:\n-\t\tif dst.Type() == timeType {\n-\t\t\ttv, err := time.Parse(time.RFC3339, string(src))\n-\t\t\tif err != nil {\n-\t\t\t\treturn err\n-\t\t\t}\n-\t\t\tdst.Set(reflect.ValueOf(tv))\n-\t\t}\n \t}\n \treturn nil\n }\n```

### `src/pkg/encoding/xml/xml.go`

```diff
--- a/src/pkg/encoding/xml/xml.go
+++ b/src/pkg/encoding/xml/xml.go
@@ -67,6 +67,11 @@ func (e StartElement) Copy() StartElement {
 	return e
 }
 
+// End returns the corresponding XML end element.
+func (e StartElement) End() EndElement {
+	return EndElement{e.Name}
+}
+
 // An EndElement represents an XML end element.
 type EndElement struct {
 	Name Name

src/pkg/go/build/deps_test.go

--- a/src/pkg/go/build/deps_test.go
+++ b/src/pkg/go/build/deps_test.go
@@ -200,7 +200,7 @@ var pkgDeps = map[string][]string{
 	"encoding/hex":        {"L4"},
 	"encoding/json":       {"L4", "encoding"},
 	"encoding/pem":        {"L4"},
-	"encoding/xml":        {"L4"},
+	"encoding/xml":        {"L4", "encoding"},
 	"flag":                {"L4", "OS"},
 	"go/build":            {"L4", "OS", "GOPARSER"},
 	"html":                {"L4"},

コアとなるコードの解説

src/pkg/encoding/xml/marshal.go の変更点

  1. time パッケージの削除と encoding パッケージの追加: import "time" が削除され、代わりに import "encoding" が追加されました。これにより、encoding/xmltime.Time に特化した処理から、encoding パッケージで定義された汎用インターフェースを利用する方針に転換したことが明確になります。

  2. textMarshalerType の導入: var textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() が追加されました。これは、リフレクションを使って encoding.TextMarshaler インターフェースの型情報を取得するためのものです。これにより、実行時に任意のGoの型がこのインターフェースを実装しているかどうかを効率的にチェックできるようになります。

  3. marshalValue 関数のロジック変更: marshalValue は、Goの値をXML要素としてマーシャリングする主要な関数です。

    • 以前は xml.Marshaler のチェックの後に time.Time の特殊処理が暗黙的に行われていました。
    • 変更後、xml.Marshaler のチェックに加えて、encoding.TextMarshaler の実装がチェックされるようになりました。val.CanInterface() && typ.Implements(textMarshalerType) または val.CanAddr() && pv.Type().Implements(textMarshalerType) の条件で encoding.TextMarshaler が検出されると、新しく導入された p.marshalTextInterface 関数が呼び出されます。これにより、time.Time を含む encoding.TextMarshaler を実装するすべての型が、一貫した方法でXMLテキストとしてマーシャリングされるようになります。
  4. 属性マーシャリングの改善: 構造体のフィールドがXML属性としてマーシャリングされる際にも、encoding.TextMarshaler の実装が考慮されるようになりました。フィールドが encoding.TextMarshaler を実装している場合、その MarshalText() メソッドが呼び出され、返されたバイト列が属性値として使用されます。これにより、カスタム型をXML属性として柔軟に表現できるようになります。

  5. fCharData フラグを持つフィールドの処理: fCharData フラグは、構造体のフィールドがXML要素の文字データ(テキストコンテンツ)として扱われることを示します。この処理においても、encoding.TextMarshaler の実装が優先的に利用されるようになりました。これにより、フィールドが自身をテキストとして表現する方法を定義できるため、より複雑なデータ型もXMLテキストとして適切にマーシャリングできます。

  6. marshalInterface 関数のシグネチャ変更と defaultStart の導入: marshalInterface のシグネチャが簡素化され、typ reflect.Type, finfo *fieldInfo, startTemplate *StartElement の代わりに start StartElement を直接受け取るようになりました。これは、defaultStart という新しいヘルパー関数が導入されたためです。defaultStart は、与えられた型、フィールド情報、およびテンプレートに基づいて、デフォルトの StartElement を生成する役割を担います。これにより、marshalInterface はより単一責任の原則に沿った形になり、コードの可読性と保守性が向上しました。

  7. marshalTextInterface 関数の追加: この新しい関数は、encoding.TextMarshaler を実装する値をXML要素としてマーシャリングするための専用ロジックをカプセル化します。開始タグを書き込み、MarshalText() を呼び出してテキストコンテンツを取得し、それをエスケープして書き込み、最後に終了タグを書き込みます。

  8. timeType 関連のコードの削除: timeType 変数、および marshalSimplemarshalStruct 関数内の timeType に特化した条件分岐や処理がすべて削除されました。これは、time.Time がもはや特別な扱いを受けず、汎用的な encoding.TextMarshaler のパスで処理されるようになったことを意味します。

src/pkg/encoding/xml/read.go の変更点

  1. time パッケージの削除と encoding パッケージの追加: marshal.go と同様に、time パッケージのインポートが削除され、encoding パッケージが追加されました。

  2. textUnmarshalerType の導入: var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() が追加されました。これは、encoding.TextUnmarshaler インターフェースの型情報をリフレクションで取得するためのものです。

  3. unmarshal 関数のロジック変更: unmarshal は、XML要素をGoの値にアンマーシャリングする主要な関数です。

    • 以前は xml.Unmarshaler のチェックの後に time.Time の特殊処理が暗黙的に行われていました。
    • 変更後、xml.Unmarshaler のチェックに加えて、encoding.TextUnmarshaler の実装がチェックされるようになりました。val.CanInterface() && val.Type().Implements(textUnmarshalerType) または val.CanAddr() && pv.Type().Implements(textUnmarshalerType) の条件で encoding.TextUnmarshaler が検出されると、新しく導入された p.unmarshalTextInterface 関数が呼び出されます。これにより、time.Time を含む encoding.TextUnmarshaler を実装するすべての型が、一貫した方法でXMLテキストからアンマーシャリングされるようになります。
  4. 属性アンマーシャリングの改善: XML属性をGoの構造体フィールドにアンマーシャリングする際にも、encoding.TextUnmarshaler の実装が考慮されるようになりました。フィールドが encoding.TextUnmarshaler を実装している場合、属性値がその UnmarshalText() メソッドに渡され、値が再構築されます。

  5. unmarshalTextInterface 関数の追加: この新しい関数は、encoding.TextUnmarshaler を実装する値をXML要素からアンマーシャリングするための専用ロジックをカプセル化します。XMLストリームを読み進め、現在の要素内の文字データ(CharData)を収集し、そのデータを UnmarshalText() メソッドに渡して値を再構築します。この関数は、子要素をスキップし、現在の要素のテキストコンテンツのみを対象とします。

  6. copyValue 関数の変更: copyValue 関数は、XMLから読み取ったバイト列をGoのターゲット値にコピーする汎用的なヘルパー関数です。この関数も、encoding.TextUnmarshaler の実装を優先的にチェックし、もし実装されていればその UnmarshalText メソッドを呼び出すように変更されました。また、エラーメッセージがより具体的になり、"cannot happen: unknown type " から "cannot unmarshal into " に変更されました。time.Time に特化したアンマーシャリングロジックもここから削除されました。

src/pkg/encoding/xml/xml.go の変更点

  • StartElement.End() メソッドの追加: StartElement 型に End() EndElement メソッドが追加されました。これは、対応する EndElement を簡単に生成するためのユーティリティ関数です。例えば、StartElement <s> があれば、s.End()</s> を返します。これはXMLの構造をプログラムで扱う際の利便性を高めますが、encoding.TextMarshaler / TextUnmarshaler のサポートとは直接的な機能的な関連はありません。

src/pkg/go/build/deps_test.go の変更点

  • encoding/xml の依存関係の更新: pkgDeps マップにおいて、"encoding/xml" の依存関係に "encoding" が追加されました。これは、encoding/xml パッケージが encoding パッケージで定義されている TextMarshaler および TextUnmarshaler インターフェースに直接依存するようになったため、Goのビルドシステムがこの新しい依存関係を正しく解決できるようにするための変更です。

これらの変更全体として、encoding/xml パッケージは、特定の型(time.Time)に依存する特殊な処理を排除し、Goのインターフェースの強力な機能を利用して、より汎用的で、拡張性が高く、保守しやすいXMLエンコーディング・デコーディングメカニズムを実現しました。これにより、開発者はカスタム型をXMLとやり取りする際に、標準的な encoding.TextMarshaler / TextUnmarshaler インターフェースを実装するだけでよくなり、よりクリーンで予測可能なコードを書くことができるようになります。

関連リンク

参考にした情報源リンク

  • Goの公式ドキュメント (上記「関連リンク」に記載)
  • Goのコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/12751045 (コミットメッセージに記載されているCLリンク)
  • Goのソースコード (このコミットの差分)