[インデックス 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
)に依存したコードパスを生み出し、以下のような問題を引き起こす可能性がありました。
- 拡張性の欠如:
time.Time
以外のカスタム型や、同様に文字列としてエンコード・デコードしたい他の型に対して、同様の特殊処理を実装する必要が生じた場合、コードの重複や複雑さが増します。 - 一貫性の欠如:
encoding/xml
パッケージが提供するMarshaler
やUnmarshaler
といったインターフェースによる汎用的なエンコーディングメカニズムと、time.Time
の特殊処理との間に一貫性がありませんでした。 - 保守性の低下: 特定の型に特化したロジックは、その型が変更された場合や、新しいエンコーディング要件が生じた場合に、コードの変更が広範囲に及ぶ可能性があります。
このコミットでは、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.TextMarshaler
や encoding.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
型を識別するためのリフレクション変数が削除されました。marshalSimple
、marshalStruct
、copyValue
といった関数内で、val.Type() == timeType
のようなtime.Time
型に特化した条件分岐や処理がすべて削除されました。これにより、encoding/xml
はtime.Time
を他のGoの組み込み型や構造体と同様に、そのインターフェース実装に基づいて処理するようになります。
encoding.TextMarshaler
および encoding.TextUnmarshaler
の統合
-
インターフェース型の定義:
src/pkg/encoding/xml/marshal.go
にtextMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
が追加され、encoding.TextMarshaler
インターフェースのreflect.Type
が取得できるようになりました。 同様に、src/pkg/encoding/xml/read.go
にtextUnmarshalerType = 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/xml
がencoding.TextMarshaler
やencoding.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
の変更点
-
time
パッケージの削除とencoding
パッケージの追加:import "time"
が削除され、代わりにimport "encoding"
が追加されました。これにより、encoding/xml
はtime.Time
に特化した処理から、encoding
パッケージで定義された汎用インターフェースを利用する方針に転換したことが明確になります。 -
textMarshalerType
の導入:var textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
が追加されました。これは、リフレクションを使ってencoding.TextMarshaler
インターフェースの型情報を取得するためのものです。これにより、実行時に任意のGoの型がこのインターフェースを実装しているかどうかを効率的にチェックできるようになります。 -
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テキストとしてマーシャリングされるようになります。
- 以前は
-
属性マーシャリングの改善: 構造体のフィールドがXML属性としてマーシャリングされる際にも、
encoding.TextMarshaler
の実装が考慮されるようになりました。フィールドがencoding.TextMarshaler
を実装している場合、そのMarshalText()
メソッドが呼び出され、返されたバイト列が属性値として使用されます。これにより、カスタム型をXML属性として柔軟に表現できるようになります。 -
fCharData
フラグを持つフィールドの処理:fCharData
フラグは、構造体のフィールドがXML要素の文字データ(テキストコンテンツ)として扱われることを示します。この処理においても、encoding.TextMarshaler
の実装が優先的に利用されるようになりました。これにより、フィールドが自身をテキストとして表現する方法を定義できるため、より複雑なデータ型もXMLテキストとして適切にマーシャリングできます。 -
marshalInterface
関数のシグネチャ変更とdefaultStart
の導入:marshalInterface
のシグネチャが簡素化され、typ reflect.Type, finfo *fieldInfo, startTemplate *StartElement
の代わりにstart StartElement
を直接受け取るようになりました。これは、defaultStart
という新しいヘルパー関数が導入されたためです。defaultStart
は、与えられた型、フィールド情報、およびテンプレートに基づいて、デフォルトのStartElement
を生成する役割を担います。これにより、marshalInterface
はより単一責任の原則に沿った形になり、コードの可読性と保守性が向上しました。 -
marshalTextInterface
関数の追加: この新しい関数は、encoding.TextMarshaler
を実装する値をXML要素としてマーシャリングするための専用ロジックをカプセル化します。開始タグを書き込み、MarshalText()
を呼び出してテキストコンテンツを取得し、それをエスケープして書き込み、最後に終了タグを書き込みます。 -
timeType
関連のコードの削除:timeType
変数、およびmarshalSimple
、marshalStruct
関数内のtimeType
に特化した条件分岐や処理がすべて削除されました。これは、time.Time
がもはや特別な扱いを受けず、汎用的なencoding.TextMarshaler
のパスで処理されるようになったことを意味します。
src/pkg/encoding/xml/read.go
の変更点
-
time
パッケージの削除とencoding
パッケージの追加:marshal.go
と同様に、time
パッケージのインポートが削除され、encoding
パッケージが追加されました。 -
textUnmarshalerType
の導入:var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
が追加されました。これは、encoding.TextUnmarshaler
インターフェースの型情報をリフレクションで取得するためのものです。 -
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テキストからアンマーシャリングされるようになります。
- 以前は
-
属性アンマーシャリングの改善: XML属性をGoの構造体フィールドにアンマーシャリングする際にも、
encoding.TextUnmarshaler
の実装が考慮されるようになりました。フィールドがencoding.TextUnmarshaler
を実装している場合、属性値がそのUnmarshalText()
メソッドに渡され、値が再構築されます。 -
unmarshalTextInterface
関数の追加: この新しい関数は、encoding.TextUnmarshaler
を実装する値をXML要素からアンマーシャリングするための専用ロジックをカプセル化します。XMLストリームを読み進め、現在の要素内の文字データ(CharData)を収集し、そのデータをUnmarshalText()
メソッドに渡して値を再構築します。この関数は、子要素をスキップし、現在の要素のテキストコンテンツのみを対象とします。 -
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言語
encoding
パッケージのドキュメント: https://pkg.go.dev/encoding - Go言語
encoding/xml
パッケージのドキュメント: https://pkg.go.dev/encoding/xml - Go言語
reflect
パッケージのドキュメント: https://pkg.go.dev/reflect - Go言語
time
パッケージのドキュメント: https://pkg.go.dev/time
参考にした情報源リンク
- Goの公式ドキュメント (上記「関連リンク」に記載)
- Goのコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/12751045 (コミットメッセージに記載されているCLリンク)
- Goのソースコード (このコミットの差分)