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

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

このコミットは、Go言語の encoding/xml パッケージにおいて、XMLマーシャリング時に ,chardata タグが付けられたフィールドが []byte 型でない場合でも正しく処理されるようにするものです。具体的には、数値型(int, uint, float)、boolstring、そして time.Time 型のフィールドが chardata としてXMLに出力される際のサポートを追加しています。これにより、Goの構造体からXMLへの変換の柔軟性が向上し、より多様なデータ型をXMLのテキストコンテンツとして扱うことが可能になりました。

コミット

commit 14bd52db3fb56670270aafc79302316afe1ed07c
Author: Vega Garcia Luis Alfonso <vegacom@gmail.com>
Date:   Tue Jan 22 22:13:40 2013 -0500

    xml: Support fields not of type []byte when marshaling ",chardata"
    
    Fixes #4506.
    
    R=rsc, remyoudompheng
    CC=golang-dev
    https://golang.org/cl/7106045
---
 src/pkg/encoding/xml/marshal.go      | 13 +++++++++++++
 src/pkg/encoding/xml/marshal_test.go | 36 ++++++++++++++++++++++++++++++++++++
 2 files changed, 49 insertions(+)

diff --git a/src/pkg/encoding/xml/marshal.go b/src/pkg/encoding/xml/marshal.go
index 8b2f4173f3..383fb26b04 100644
--- a/src/pkg/encoding/xml/marshal.go
+++ b/src/pkg/encoding/xml/marshal.go
@@ -279,13 +279,26 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
 	t\tvf := finfo.value(val)
 	t\tswitch finfo.flags & fMode {
 	t\tcase fCharData:
+\t\t\tvar scratch [64]byte
 \t\t\tswitch vf.Kind() {\
+\t\t\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+\t\t\t\tEscape(p, strconv.AppendInt(scratch[:0], vf.Int(), 10))\
+\t\t\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+\t\t\t\tEscape(p, strconv.AppendUint(scratch[:0], vf.Uint(), 10))\
+\t\t\tcase reflect.Float32, reflect.Float64:
+\t\t\t\tEscape(p, strconv.AppendFloat(scratch[:0], vf.Float(), 'g', -1, vf.Type().Bits()))\
+\t\t\tcase reflect.Bool:
+\t\t\t\tEscape(p, strconv.AppendBool(scratch[:0], vf.Bool()))
 \t\t\tcase reflect.String:
 \t\t\t\tEscape(p, []byte(vf.String()))
 \t\t\tcase reflect.Slice:
 \t\t\t\tif elem, ok := vf.Interface().([]byte); ok {
 \t\t\t\t\tEscape(p, elem)
 \t\t\t\t}\
+\t\t\tcase reflect.Struct:
+\t\t\t\tif vf.Type() == timeType {
+\t\t\t\t\tEscape(p, []byte(vf.Interface().(time.Time).Format(time.RFC3339Nano)))\
+\t\t\t\t}\
 \t\t\t}\
 \t\t\tcontinue
 \n
diff --git a/src/pkg/encoding/xml/marshal_test.go b/src/pkg/encoding/xml/marshal_test.go
index 2ce7721abd..67fcfd9ed5 100644
--- a/src/pkg/encoding/xml/marshal_test.go
+++ b/src/pkg/encoding/xml/marshal_test.go
@@ -59,6 +59,36 @@ type Book struct {
 	Title   string   `xml:",chardata"`
 }
 
+type Event struct {
+\tXMLName struct{} `xml:"event"`
+\tYear    int      `xml:",chardata"`
+}
+
+type Movie struct {
+\tXMLName struct{} `xml:"movie"`
+\tLength  uint     `xml:",chardata"`
+}
+
+type Pi struct {
+\tXMLName       struct{} `xml:"pi"`
+\tApproximation float32  `xml:",chardata"`
+}
+
+type Universe struct {
+\tXMLName struct{} `xml:"universe"`
+\tVisible float64  `xml:",chardata"`
+}
+
+type Particle struct {
+\tXMLName struct{} `xml:"particle"`
+\tHasMass bool     `xml:",chardata"`
+}
+
+type Departure struct {
+\tXMLName struct{}  `xml:"departure"`
+\tWhen    time.Time `xml:",chardata"`
+}
+
 type SecretAgent struct {
 \tXMLName   struct{} `xml:"agent"`
 \tHandle    string   `xml:"handle,attr"`
@@ -345,6 +375,12 @@ var marshalTests = []struct {
 	{Value: &Domain{Name: []byte("google.com&friends")}, ExpectXML: `<domain>google.com&amp;friends</domain>`},
 	{Value: &Domain{Name: []byte("google.com"), Comment: []byte(" &friends ")}, ExpectXML: `<domain>google.com<!-- &friends --></domain>`},
 	{Value: &Book{Title: "Pride & Prejudice"}, ExpectXML: `<book>Pride &amp; Prejudice</book>`},
+\t{Value: &Event{Year: -3114}, ExpectXML: `<event>-3114</event>`},
+\t{Value: &Movie{Length: 13440}, ExpectXML: `<movie>13440</movie>`},
+\t{Value: &Pi{Approximation: 3.14159265}, ExpectXML: `<pi>3.1415927</pi>`},
+\t{Value: &Universe{Visible: 9.3e13}, ExpectXML: `<universe>9.3e+13</universe>`},
+\t{Value: &Particle{HasMass: true}, ExpectXML: `<particle>true</particle>`},
+\t{Value: &Departure{When: ParseTime("2013-01-09T00:15:00-09:00")}, ExpectXML: `<departure>2013-01-09T00:15:00-09:00</departure>`},
 	{Value: atomValue, ExpectXML: atomXml},
 	{
 	\tValue: &Ship{

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

https://github.com/golang/go/commit/14bd52db3fb56670270aafc79302316afe1ed07c

元コミット内容

xml: Support fields not of type []byte when marshaling ",chardata"

Fixes #4506.

R=rsc, remyoudompheng
CC=golang-dev
https://golang.org/cl/7106045

変更の背景

Go言語の encoding/xml パッケージは、Goの構造体とXMLの間でデータを変換(マーシャリングとアンマーシャリング)するための標準ライブラリです。XML要素のテキストコンテンツ(子要素や属性ではない部分)は「文字データ(character data)」と呼ばれ、Goの構造体では通常、xml:",chardata" タグを使用して対応するフィールドにマッピングされます。

このコミットが導入される前は、xml:",chardata" タグが付けられた構造体フィールドは、XMLにマーシャリングされる際に []byte 型である必要がありました。しかし、Goのプログラムでは、XMLの文字データとして数値(整数、浮動小数点数)、真偽値、文字列、日付/時刻など、さまざまな型のデータを扱うことが一般的です。これらのデータをXMLにマーシャリングする際に、いちいち []byte 型に変換する手間が発生したり、あるいは []byte 以外の型を chardata として扱えないという制限がありました。

この制限は、Goの encoding/xml パッケージの柔軟性を損ない、開発者がXMLを扱う際の不便さや冗長なコードの原因となっていました。コミットメッセージにある "Fixes #4506" は、この問題がGoのIssueトラッカーで報告され、修正が求められていたことを示唆しています。このコミットは、この既存の制約を取り除き、より自然なGoのデータ型をXMLの文字データとして直接マーシャリングできるようにすることで、開発体験を向上させることを目的としています。

前提知識の解説

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

encoding/xml パッケージは、Goの構造体とXMLドキュメントの間でデータをエンコード(マーシャリング)およびデコード(アンマーシャリング)するための機能を提供します。

  • マーシャリング (Marshaling): Goの構造体のデータをXML形式のバイト列に変換するプロセスです。xml.Marshal 関数がこれを行います。
  • アンマーシャリング (Unmarshaling): XML形式のバイト列をGoの構造体のデータに変換するプロセスです。xml.Unmarshal 関数がこれを行います。

xml:",chardata" タグ

Goの構造体フィールドに xml:",chardata" タグを付けると、そのフィールドがXML要素のテキストコンテンツ(文字データ)として扱われることを encoding/xml パッケージに指示します。

例:

type Book struct {
    XMLName xml.Name `xml:"book"`
    Title   string   `xml:",chardata"` // このフィールドが <book>要素のテキストコンテンツになる
}

// Book{Title: "Go Programming"} をマーシャリングすると
// <book>Go Programming</book> のようになる

reflect パッケージ

Goの reflect パッケージは、実行時にプログラムの構造(型、フィールド、メソッドなど)を検査および操作するための機能を提供します。encoding/xml のような汎用的なエンコーディング/デコーディングライブラリでは、構造体のフィールドの型を動的に判断し、それに応じて異なる処理を行うために reflect パッケージが広く利用されます。

  • reflect.Value: 任意のGoの値の実行時データ表現です。
  • reflect.Kind(): reflect.Value が表す値の具体的な種類(例: reflect.Int, reflect.String, reflect.Slice, reflect.Struct など)を返します。
  • reflect.Int(), reflect.Uint(), reflect.Float(), reflect.Bool(), reflect.String(): それぞれの reflect.Value を対応するGoの基本型に変換します。
  • reflect.Type(): reflect.Value が表す値の型情報を返します。
  • vf.Interface().([]byte): reflect.Value を元のインターフェース型に戻し、型アサーション .([]byte) を使って []byte 型に変換を試みます。

strconv パッケージ

strconv パッケージは、基本データ型(数値、真偽値など)と文字列の間で変換を行うための機能を提供します。このコミットでは、数値や真偽値をXMLの文字データとして出力するために、これらの値を文字列に変換する際に strconv パッケージの関数(例: AppendInt, AppendUint, AppendFloat, AppendBool)が使用されています。これらの Append 系関数は、既存のバイトスライスに変換結果を追記する形式で、効率的な文字列変換を可能にします。

time パッケージ

Goの time パッケージは、時刻の表現と操作のための機能を提供します。time.Time 型は特定の日時を表し、Format メソッドを使用して指定されたレイアウトで文字列にフォーマットできます。XMLでは、日付/時刻は通常ISO 8601形式(例: 2013-01-09T00:15:00-09:00)で表現されます。

技術的詳細

このコミットの核心は、src/pkg/encoding/xml/marshal.go 内の printer 型の marshalStruct メソッドの変更にあります。このメソッドは、Goの構造体をXMLにマーシャリングする際の主要なロジックを含んでいます。

変更前は、fCharData フラグ(xml:",chardata" タグに対応)が設定されたフィールドに対しては、その値が reflect.String 型か []byte 型であることのみを想定していました。[]byte 型の場合は直接 Escape 関数でXMLエスケープして出力し、string 型の場合は []byte に変換してから Escape していました。

このコミットでは、fCharDatacase ブロック内に新しい switch vf.Kind() ステートメントが追加され、chardata フィールドが取りうる様々なGoの基本データ型を明示的に処理するように拡張されました。

  1. reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:

    • vf.Int()int64 型の値を取得します。
    • strconv.AppendInt(scratch[:0], vf.Int(), 10) を使用して、この整数値を10進数文字列として scratch バイトスライスに追記します。scratch[:0] は、scratch の容量を再利用しつつ、長さを0にリセットするイディオムです。
    • 結果のバイトスライスを Escape 関数に渡し、XMLエスケープして出力します。
  2. reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:

    • vf.Uint()uint64 型の値を取得します。
    • strconv.AppendUint(scratch[:0], vf.Uint(), 10) を使用して、この符号なし整数値を10進数文字列として scratch バイトスライスに追記します。
    • 結果のバイトスライスを Escape 関数に渡し、XMLエスケープして出力します。
  3. reflect.Float32, reflect.Float64:

    • vf.Float()float64 型の値を取得します。
    • strconv.AppendFloat(scratch[:0], vf.Float(), 'g', -1, vf.Type().Bits()) を使用して、浮動小数点数値を文字列に変換します。
      • 'g' は、指数表記または通常の表記のいずれか短い方を選択するフォーマットです。
      • -1 は、精度を自動的に決定することを意味します。
      • vf.Type().Bits() は、元の浮動小数点数が float32 (32ビット) か float64 (64ビット) かに応じて、適切なビット数を指定します。
    • 結果のバイトスライスを Escape 関数に渡し、XMLエスケープして出力します。
  4. reflect.Bool:

    • vf.Bool()bool 型の値を取得します。
    • strconv.AppendBool(scratch[:0], vf.Bool()) を使用して、真偽値を "true" または "false" の文字列として scratch バイトスライスに追記します。
    • 結果のバイトスライスを Escape 関数に渡し、XMLエスケープして出力します。
  5. reflect.Struct (特に time.Time 型):

    • vf.Type() == timeType で、フィールドの型が time.Time であるかをチェックします。timeTypereflect.TypeOf((*time.Time)(nil)).Elem() で事前に取得された time.Time 型の reflect.Type です。
    • vf.Interface().(time.Time)reflect.Valuetime.Time 型に型アサーションします。
    • Format(time.RFC3339Nano) を使用して、time.Time の値をRFC3339Nano形式の文字列にフォーマットします。これはXMLで日付/時刻を表現する際の一般的な形式です。
    • 結果の文字列を []byte に変換し、Escape 関数に渡し、XMLエスケープして出力します。

これらの変更により、encoding/xml パッケージは、Goの多様な基本データ型を chardata としてXMLにシームレスにマーシャリングできるようになり、開発者はXML構造とGoのデータモデルをより自然にマッピングできるようになりました。

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

diff --git a/src/pkg/encoding/xml/marshal.go b/src/pkg/encoding/xml/marshal.go
index 8b2f4173f3..383fb26b04 100644
--- a/src/pkg/encoding/xml/marshal.go
+++ b/src/pkg/encoding/xml/marshal.go
@@ -279,13 +279,26 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
 	t\tvf := finfo.value(val)
 	t\tswitch finfo.flags & fMode {
 	t\tcase fCharData:
+\t\t\tvar scratch [64]byte
 \t\t\tswitch vf.Kind() {\
+\t\t\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+\t\t\t\tEscape(p, strconv.AppendInt(scratch[:0], vf.Int(), 10))\
+\t\t\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+\t\t\t\tEscape(p, strconv.AppendUint(scratch[:0], vf.Uint(), 10))\
+\t\t\tcase reflect.Float32, reflect.Float64:
+\t\t\t\tEscape(p, strconv.AppendFloat(scratch[:0], vf.Float(), 'g', -1, vf.Type().Bits()))\
+\t\t\tcase reflect.Bool:
+\t\t\t\tEscape(p, strconv.AppendBool(scratch[:0], vf.Bool()))
 \t\t\tcase reflect.String:
 \t\t\t\tEscape(p, []byte(vf.String()))
 \t\t\tcase reflect.Slice:
 \t\t\t\tif elem, ok := vf.Interface().([]byte); ok {
 \t\t\t\t\tEscape(p, elem)
 \t\t\t\t}\
+\t\t\tcase reflect.Struct:
+\t\t\t\tif vf.Type() == timeType {
+\t\t\t\t\tEscape(p, []byte(vf.Interface().(time.Time).Format(time.RFC3339Nano)))\
+\t\t\t\t}\
 \t\t\t}\
 \t\t\tcontinue
 \n
diff --git a/src/pkg/encoding/xml/marshal_test.go b/src/pkg/encoding/xml/marshal_test.go
index 2ce7721abd..67fcfd9ed5 100644
--- a/src/pkg/encoding/xml/marshal_test.go
+++ b/src/pkg/encoding/xml/marshal_test.go
@@ -59,6 +59,36 @@ type Book struct {
 	Title   string   `xml:",chardata"`
 }
 
+type Event struct {
+\tXMLName struct{} `xml:"event"`
+\tYear    int      `xml:",chardata"`
+}
+
+type Movie struct {
+\tXMLName struct{} `xml:"movie"`
+\tLength  uint     `xml:",chardata"`
+}
+
+type Pi struct {
+\tXMLName       struct{} `xml:"pi"`
+\tApproximation float32  `xml:",chardata"`
+}
+
+type Universe struct {
+\tXMLName struct{} `xml:"universe"`
+\tVisible float64  `xml:",chardata"`
+}
+
+type Particle struct {
+\tXMLName struct{} `xml:"particle"`
+\tHasMass bool     `xml:",chardata"`
+}
+
+type Departure struct {
+\tXMLName struct{}  `xml:"departure"`
+\tWhen    time.Time `xml:",chardata"`
+}
+
 type SecretAgent struct {
 \tXMLName   struct{} `xml:"agent"`
 \tHandle    string   `xml:"handle,attr"`
@@ -345,6 +375,12 @@ var marshalTests = []struct {
 	{Value: &Domain{Name: []byte("google.com&friends")}, ExpectXML: `<domain>google.com&amp;friends</domain>`},
 	{Value: &Domain{Name: []byte("google.com"), Comment: []byte(" &friends ")}, ExpectXML: `<domain>google.com<!-- &friends --></domain>`},
 	{Value: &Book{Title: "Pride & Prejudice"}, ExpectXML: `<book>Pride &amp; Prejudice</book>`},
+\t{Value: &Event{Year: -3114}, ExpectXML: `<event>-3114</event>`},
+\t{Value: &Movie{Length: 13440}, ExpectXML: `<movie>13440</movie>`},
+\t{Value: &Pi{Approximation: 3.14159265}, ExpectXML: `<pi>3.1415927</pi>`},
+\t{Value: &Universe{Visible: 9.3e13}, ExpectXML: `<universe>9.3e+13</universe>`},
+\t{Value: &Particle{HasMass: true}, ExpectXML: `<particle>true</particle>`},
+\t{Value: &Departure{When: ParseTime("2013-01-09T00:15:00-09:00")}, ExpectXML: `<departure>2013-01-09T00:15:00-09:00</departure>`},
 	{Value: atomValue, ExpectXML: atomXml},
 	{
 	\tValue: &Ship{

コアとなるコードの解説

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

marshal.go の変更は、printer 型の marshalStruct メソッド内の fCharData の処理ロジックを拡張しています。

  1. var scratch [64]byte の追加:

    • これは、strconv.Append... 関数が文字列変換結果を書き込むための一時的なバイトスライスです。64バイトは、一般的な数値や真偽値の文字列表現を格納するのに十分なサイズです。これにより、毎回新しいスライスをアロケートするオーバーヘッドを避けることができます。
  2. switch vf.Kind() の追加と各型のハンドリング:

    • vf.Kind() は、現在処理している構造体フィールドの値 vf の具体的な種類(reflect.Int, reflect.String など)を返します。
    • case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64::
      • vf.Int() でフィールドの整数値を取得し、strconv.AppendInt で10進数文字列に変換して scratch に書き込みます。
      • Escape(p, ...) でXMLエスケープ処理を行い、出力ストリーム p に書き出します。
    • case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr::
      • vf.Uint() でフィールドの符号なし整数値を取得し、strconv.AppendUint で10進数文字列に変換して scratch に書き込みます。
      • 同様に Escape して出力します。
    • case reflect.Float32, reflect.Float64::
      • vf.Float() でフィールドの浮動小数点数値を取得し、strconv.AppendFloat で文字列に変換します。'g' フォーマットは、数値の大きさに応じて最適な表現(通常の表記または指数表記)を選択します。-1 は最小限の桁数で表現することを意味し、vf.Type().Bits() は元の型のビット数(32または64)を渡します。
      • 同様に Escape して出力します。
    • case reflect.Bool::
      • vf.Bool() でフィールドの真偽値を取得し、strconv.AppendBool で "true" または "false" の文字列に変換して scratch に書き込みます。
      • 同様に Escape して出力します。
    • case reflect.String::
      • 既存の string 型の処理はそのまま残ります。[]byte(vf.String()) でバイトスライスに変換し、Escape して出力します。
    • case reflect.Slice::
      • 既存の []byte 型の処理もそのまま残ります。vf.Interface().([]byte)[]byte 型への型アサーションを試み、成功すれば直接 Escape して出力します。
    • case reflect.Struct::
      • 構造体型の場合、特に time.Time 型を特別扱いします。
      • if vf.Type() == timeType で、フィールドの型が time.Time であるかをチェックします。
      • vf.Interface().(time.Time)time.Time 型に変換し、Format(time.RFC3339Nano) でRFC3339Nano形式の文字列にフォーマットします。
      • 結果の文字列を []byte に変換し、Escape して出力します。

これらの変更により、marshalStructchardata フィールドの型に応じて適切な文字列変換を行い、XMLとして出力できるようになりました。

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

marshal_test.go の変更は、新しい chardata のマーシャリング機能が正しく動作することを検証するためのテストケースの追加です。

  1. 新しい構造体の定義:

    • Event (int), Movie (uint), Pi (float32), Universe (float64), Particle (bool), Departure (time.Time) といった、様々なデータ型を xml:",chardata" フィールドとして持つ新しい構造体が定義されています。これらの構造体は、それぞれの型の chardata マーシャリングをテストするために使用されます。
  2. marshalTests スライスへのテストケースの追加:

    • marshalTests は、Goの構造体をXMLにマーシャリングした結果が期待通りになるかを検証するためのテストケースの集合です。
    • 新しく定義された構造体(Event, Movie, Pi, Universe, Particle, Departure)のインスタンスと、それらをマーシャリングした際に期待されるXML文字列が追加されています。
    • 例えば、{Value: &Event{Year: -3114}, ExpectXML: -3114} は、int 型の Year フィールドが chardata として正しくXMLに出力されることをテストします。
    • time.Time 型のテストケースでは、ParseTime ヘルパー関数を使用して特定の時刻を生成し、それがRFC3339Nano形式でXMLに出力されることを確認しています。

これらのテストケースの追加により、encoding/xml パッケージの chardata マーシャリング機能が、[]byte 以外の様々なデータ型に対しても期待通りに動作することが保証されます。

関連リンク

参考にした情報源リンク