[インデックス 11957] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/xml
パッケージに MarshalIndent
関数を追加し、XML出力の整形(インデント)を可能にするものです。また、既存のXMLマーシャリングの例を、より読みやすくするために整形された出力を使用するテストファイルに移動しています。これにより、生成されるXMLの可読性が向上し、デバッグや人間による確認が容易になります。
コミット
commit aed20a6951948ef7f6edd1f4160fc8c1d3e8df56
Author: Gustavo Niemeyer <gustavo@niemeyer.net>
Date: Thu Feb 16 02:01:46 2012 -0200
encoding/xml: add MarshalIndent and move the example
An unindented XML example is hard to follow. MarshalIndent
allows moving the example over to a test file (and fixing it).
R=golang-dev, r, gustavo, r, rsc
CC=golang-dev
https://golang.org/cl/5674050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/aed20a6951948ef7f6edd1f4160fc8c1d3e8df56
元コミット内容
このコミットの目的は、Goの encoding/xml
パッケージに新しい関数 MarshalIndent
を導入することです。この関数は、XMLデータをバイトスライスにマーシャリングする際に、指定されたプレフィックスとインデント文字列を使用して整形された出力を生成します。これにより、生成されるXMLが視覚的に構造化され、人間にとって読みやすくなります。
また、コミットメッセージは、既存のXMLマーシャリングの例がインデントされていないために読みにくいという問題意識を表明しています。MarshalIndent
の導入により、この例をテストファイル (example_test.go
) に移動し、整形された出力で表示できるようになります。これにより、例の可読性が向上し、encoding/xml
パッケージの利用者がXMLの構造をより簡単に理解できるようになります。
変更の背景
Go言語の encoding/xml
パッケージは、Goの構造体とXMLデータの間で変換を行うための機能を提供します。これには、Goの構造体をXMLに変換する「マーシャリング」と、XMLをGoの構造体に変換する「アンマーシャリング」が含まれます。
従来の xml.Marshal
関数は、XMLデータを生成しますが、その出力はインデントされず、すべてが1行に連結された形式でした。これは機械処理には適していますが、人間がXMLの構造を理解したり、デバッグを行ったりする際には非常に読みにくいという問題がありました。特に、複雑なXML構造を持つデータの場合、インデントがないと要素の階層関係を視覚的に把握することが困難でした。
このコミットの背景には、以下の具体的なニーズがありました。
- 可読性の向上: 生成されるXMLの可読性を高め、開発者がXMLデータを容易に検査できるようにすること。これは、特にAPIのレスポンスや設定ファイルなど、人間が直接読み書きする可能性のあるXMLデータにおいて重要です。
- デバッグの容易化: XMLの構造が明確になることで、マーシャリングの際に発生する可能性のある問題を特定しやすくなる。
- 例の改善:
encoding/xml
パッケージのドキュメントや例において、整形されたXML出力を用いることで、利用者がXMLの構造とマーシャリングの挙動をより直感的に理解できるようにすること。コミットメッセージにあるように、「インデントされていないXMLの例は理解しにくい」という課題を解決するためです。
これらの背景から、MarshalIndent
のような整形出力機能の追加が求められ、このコミットによって実現されました。
前提知識の解説
このコミットの変更内容を理解するためには、以下の前提知識が必要です。
1. XML (Extensible Markup Language)
XMLは、情報を構造化するためのマークアップ言語です。HTMLがウェブページの表示に特化しているのに対し、XMLはデータの記述と交換に特化しています。
- 要素 (Elements):
<tag>content</tag>
のように、開始タグと終了タグで囲まれた構造。 - 属性 (Attributes): 開始タグ内に
name="value"
の形式で記述される、要素に関する追加情報。 - 階層構造: 要素は他の要素を内包することができ、ツリー状の階層構造を形成します。
- 整形 (Pretty-printing): XMLデータを、インデントや改行を用いて視覚的に読みやすくする処理のこと。
2. Go言語の encoding/xml
パッケージ
Goの標準ライブラリである encoding/xml
パッケージは、Goの構造体とXMLデータの間の変換(マーシャリングとアンマーシャリング)を提供します。
- マーシャリング (Marshalling): Goの構造体のデータをXML形式のバイトスライスに変換するプロセス。
xml.Marshal
関数がこれを行います。 - アンマーシャリング (Unmarshalling): XML形式のバイトスライスをGoの構造体に変換するプロセス。
xml.Unmarshal
関数がこれを行います。 - 構造体タグ (Struct Tags): Goの構造体のフィールドに付与される文字列リテラルで、
encoding/xml
パッケージがXML要素名や属性名を決定するために使用します。例えば、xml:"name,attr"
は、そのフィールドがXMLのname
属性に対応することを示します。xml:"element_name"
: フィールドが対応するXML要素の名前を指定します。xml:"element_name,attr"
: フィールドが対応するXML属性の名前を指定します。xml:"parent>child"
: ネストされた要素のパスを指定します。xml:",omitempty"
: フィールドがGoのゼロ値の場合、XML出力から省略されます。xml:",comment"
: フィールドの内容がXMLコメントとして出力されます。
xml.Name
: XML要素のローカル名と名前空間を表現する構造体です。構造体のフィールドにxml.Name
型のXMLName
フィールドを埋め込むことで、ルート要素の名前を制御できます。
3. bufio.Writer
bufio.Writer
は、Goの io
パッケージの一部であり、バッファリングされたI/O操作を提供します。これにより、小さな書き込み操作が効率的にバッファリングされ、基になる io.Writer
へのシステムコールが削減されます。encoding/xml
パッケージの内部では、XMLデータを効率的に書き出すために bufio.Writer
が使用されています。
4. reflect
パッケージ
Goの reflect
パッケージは、実行時に型情報を検査し、値を操作するための機能を提供します。encoding/xml
パッケージは、Goの構造体のフィールドを動的に検査し、それらをXML要素や属性にマッピングするために reflect
パッケージを広範に利用しています。
技術的詳細
このコミットの技術的詳細は、主に encoding/xml/marshal.go
と src/pkg/encoding/xml/example_test.go
の変更に集約されます。
MarshalIndent
関数の追加
新しい関数 MarshalIndent
が marshal.go
に追加されました。
// MarshalIndent works like Marshal, but each XML element begins on a new
// indented line that starts with prefix and is followed by one or more
// copies of indent according to the nesting depth.
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
var b bytes.Buffer
enc := NewEncoder(&b)
enc.prefix = prefix
enc.indent = indent
err := enc.marshalValue(reflect.ValueOf(v), nil)
enc.Flush()
if err != nil {
return nil, err
}
return b.Bytes(), nil
}
MarshalIndent
はMarshal
と同様にinterface{}
型のv
を受け取りますが、加えてprefix
とindent
という2つの文字列引数を取ります。prefix
は各行の先頭に付加される文字列(例: タブやスペース)です。indent
は各ネストレベルごとに繰り返されるインデント文字列です。- 内部的には
bytes.Buffer
を使用してXMLデータを構築し、NewEncoder
で新しいEncoder
インスタンスを作成します。 - 作成された
Encoder
のprefix
とindent
フィールドに引数で渡された値を設定します。 enc.marshalValue
を呼び出して実際のマーシャリング処理を行い、最後にenc.Flush()
でバッファをフラッシュします。
Encoder
および printer
構造体の変更
XMLのインデントを制御するために、Encoder
が内部的に使用する printer
構造体に新しいフィールドが追加されました。
type printer struct {
*bufio.Writer
indent string // 各ネストレベルのインデント文字列
prefix string // 各行の先頭に付加されるプレフィックス文字列
depth int // 現在のネストの深さ
indentedIn bool // 直前の書き込みがインデントされた行の開始だったかを示すフラグ
}
indent
: 各ネストレベルで繰り返されるインデント文字列(例:"\t"
や" "
)。prefix
: 各行の先頭に一度だけ付加される文字列。depth
: 現在のXML要素のネストの深さを追跡します。indentedIn
: 直前の書き込みがインデントされた行の開始だったかどうかを示すフラグ。これにより、閉じタグの前に余分な改行とインデントが挿入されるのを防ぎます。
また、NewEncoder
関数も変更され、printer
の初期化時に Writer
フィールドを明示的に設定するようになりました。
--- a/src/pkg/encoding/xml/marshal.go
+++ b/src/pkg/encoding/xml/marshal.go
@@ -103,7 +96,7 @@ type Encoder struct {
// NewEncoder returns a new encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
- return &Encoder{printer{bufio.NewWriter(w)}}
+ return &Encoder{printer{Writer: bufio.NewWriter(w)}}
}
writeIndent
関数の追加
printer
構造体に新しいメソッド writeIndent
が追加されました。この関数が実際のインデント処理を行います。
func (p *printer) writeIndent(depthDelta int) {
if len(p.prefix) == 0 && len(p.indent) == 0 {
return // インデントが不要な場合は何もしない
}
if depthDelta < 0 { // 閉じタグの場合
p.depth-- // ネストレベルを減らす
if p.indentedIn {
p.indentedIn = false
return // 直前がインデントされた行の開始だった場合、余分な改行・インデントは不要
}
p.indentedIn = false
}
p.WriteByte('\n') // 改行を書き込む
if len(p.prefix) > 0 {
p.WriteString(p.prefix) // プレフィックスを書き込む
}
if len(p.indent) > 0 {
for i := 0; i < p.depth; i++ {
p.WriteString(p.indent) // インデント文字列をネストレベルの回数だけ書き込む
}
}
if depthDelta > 0 { // 開始タグの場合
p.depth++ // ネストレベルを増やす
p.indentedIn = true // インデントされた行の開始フラグを立てる
}
}
depthDelta
は、インデントを書き込む際にネストの深さをどのように調整するかを示します(+1
は開始タグ、-1
は閉じタグ、0
はコメントなど)。prefix
とindent
が設定されていない場合は、インデント処理はスキップされます。- 常に改行を書き込みます。
prefix
が設定されていれば、それを書き込みます。indent
が設定されていれば、現在のdepth
の回数だけindent
文字列を繰り返して書き込みます。depthDelta
に応じてdepth
を増減させ、indentedIn
フラグを適切に設定します。
marshalValue
および parentStack
の変更
marshalValue
関数と parentStack
構造体(XML要素の階層を管理)の内部で、writeIndent
が適切に呼び出されるように変更されました。
- 開始タグの書き込み前:
p.writeIndent(1)
が呼び出され、新しい要素の開始前に改行とインデントが挿入され、depth
がインクリメントされます。 - 閉じタグの書き込み前:
p.writeIndent(-1)
が呼び出され、要素の閉じタグの前に改行とインデントが挿入され、depth
がデクリメントされます。 - コメントの書き込み前:
p.writeIndent(0)
が呼び出され、コメントの前に改行とインデントが挿入されます。 parentStack
のtrim
およびpush
メソッドでもwriteIndent
が使用されるようになり、ネストされた要素の開始・終了時にも適切なインデントが適用されます。
example_test.go
の追加と例の移動
src/pkg/encoding/xml/example_test.go
という新しいファイルが追加され、ExampleMarshalIndent
という関数が定義されました。
func ExampleMarshalIndent() {
type Person struct {
XMLName xml.Name `xml:"person"`
Id int `xml:"id,attr"`
FirstName string `xml:"name>first"`
LastName string `xml:"name>last"`
Age int `xml:"age"`
Height float32 `xml:"height,omitempty"`
Married bool
Comment string `xml:",comment"`
}
v := &Person{Id: 13, FirstName: "John", LastName: "Doe", Age: 42}
v.Comment = " Need more fields. "
output, err := xml.MarshalIndent(v, "\t", "\t")
if err != nil {
fmt.Printf("error: %v\n", err)
}
os.Stdout.Write(output)
}
この例では、Person
構造体を定義し、MarshalIndent
を使用してXMLにマーシャリングしています。prefix
と indent
にはそれぞれタブ文字 ("\t"
) が指定されており、これにより整形されたXMLが出力されます。このテストは、go test
コマンドで実行される際に、出力が期待されるXMLと一致するかどうかを検証します。
また、marshal.go
内にあった古いコメント形式のXML例は削除され、MarshalIndent
の例を参照するように変更されました。
--- a/src/pkg/encoding/xml/marshal.go
+++ b/src/pkg/encoding/xml/marshal.go
@@ -60,32 +60,9 @@ const (
//
// If a field uses a tag "a>b>c", then the element c will be nested inside
// parent elements a and b. Fields that appear next to each other that name
-// the same parent will be enclosed in one XML element. For example:
+// the same parent will be enclosed in one XML element.
//
-// type Result struct {
-// XMLName xml.Name `xml:"result"`
-// Id int `xml:"id,attr"`
-// FirstName string `xml:"person>name>first"`
-// LastName string `xml:"person>name>last"`
-// Age int `xml:"person>age"`
-// Height float `xml:"person>height,omitempty"`
-// Married bool `xml:"person>person>married"`
-// }\n-//
-// xml.Marshal(&Result{Id: 13, FirstName: "John", LastName: "Doe", Age: 42})\n-//
-// would be marshalled as:\n-//
-// <result>\n-// <person id="13">\n-// <name>\n-// <first>John</first>\n-// <last>Doe</last>\n-// </name>\n-// <age>42</age>\n-// <married>false</married>\n-// </person>\n-// </result>
+// See MarshalIndent for an example.
//
// Marshal will return an error if asked to marshal a channel, function, or map.
func Marshal(v interface{}) ([]byte, error) {
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下の通りです。
-
src/pkg/encoding/xml/marshal.go
:MarshalIndent
関数の新規追加 (L96-L110)printer
構造体へのindent
,prefix
,depth
,indentedIn
フィールドの追加 (L118-L121)NewEncoder
関数でのprinter
初期化の変更 (L114)writeIndent
関数の新規追加 (L354-L377)marshalValue
関数内でのp.writeIndent(1)
(L177) およびp.writeIndent(-1)
(L216) の追加marshalStruct
関数内でのコメント書き込み時のp.writeIndent(0)
の追加 (L294)parentStack
構造体のtrim
メソッド内でのs.writeIndent(-1)
の追加 (L379)parentStack
構造体のpush
メソッド内でのs.writeIndent(1)
およびs.s.WriteByte('<')
の変更 (L388-L389)Marshal
関数のコメントからXML例の削除とMarshalIndent
への参照追加 (L60-L86)
-
src/pkg/encoding/xml/example_test.go
:- 新しいテストファイルとして追加され、
ExampleMarshalIndent
関数が定義されています。このファイルは、MarshalIndent
の使用例と、その出力がどのように整形されるかを示しています。
- 新しいテストファイルとして追加され、
コアとなるコードの解説
MarshalIndent
関数
この関数は、encoding/xml
パッケージの新しい公開APIであり、XMLデータの整形出力機能を提供します。
v interface{}
: マーシャリングするGoの構造体やデータを受け取ります。prefix string
: 生成されるXMLの各行の先頭に付加される文字列です。例えば、XML宣言の前に特定の文字列を挿入したい場合などに使用できます。indent string
: 各ネストレベルごとに繰り返されるインデント文字列です。通常はタブ ("\t"
) やスペース (" "
) が指定されます。
内部的には、Encoder
を作成し、その prefix
と indent
フィールドを設定することで、後続のマーシャリング処理が整形された出力を生成するように構成します。
printer
構造体の拡張
printer
構造体は、XMLデータを実際に書き出すための内部ヘルパーです。このコミットで追加されたフィールドは、インデント処理の状態を管理するために不可欠です。
indent
,prefix
:MarshalIndent
に渡されたインデントとプレフィックスの文字列を保持します。depth
: 現在書き込んでいるXML要素のネストの深さを追跡します。ルート要素の深さは0から始まり、子要素が追加されるたびに増加します。indentedIn
: 直前の書き込みがインデントされた行の開始だったかどうかを示すフラグです。これにより、閉じタグの前に余分な改行とインデントが挿入されるのを防ぎ、XMLの整形をより自然に見せます。
writeIndent
関数
この関数は、XMLの各要素の開始タグ、終了タグ、コメントの前に適切な改行とインデントを挿入する役割を担います。
depthDelta int
: この引数は、writeIndent
が呼び出された時点でのネストの深さの変更を示します。+1
: 新しい要素の開始タグを書き込む前。depth
をインクリメントし、indentedIn
をtrue
に設定します。-1
: 要素の閉じタグを書き込む前。depth
をデクリメントし、indentedIn
をfalse
に設定します。0
: コメントなど、ネストの深さが変わらない場合。
- この関数は、まず改行を書き込み、次に
prefix
を書き込み、最後に現在のdepth
に応じてindent
文字列を繰り返して書き込みます。これにより、XMLの階層構造が視覚的に表現されます。
marshalValue
および parentStack
の変更
これらの関数は、XML要素の開始タグ、終了タグ、およびネストされた要素のパスを処理する際に、writeIndent
を呼び出すように変更されました。これにより、XMLの各部分が書き出されるたびに、適切なインデントが適用されるようになります。特に、parentStack
はXMLの階層を管理し、親要素の開始・終了タグを適切に書き出す役割を担っているため、ここでの writeIndent
の適用は、複雑なネスト構造を持つXMLの整形に不可欠です。
example_test.go
の追加
この新しいテストファイルは、MarshalIndent
の具体的な使用例を提供します。Goの testing
パッケージの Example
関数は、ドキュメントの一部として自動的に生成され、実行可能な例として機能します。これにより、開発者は MarshalIndent
の使い方と、それが生成するXMLの形式を簡単に確認できます。また、この例をテストファイルに移動することで、コードベースの変更によって例が壊れていないかを継続的に検証できるようになります。
これらの変更により、encoding/xml
パッケージは、人間が読みやすい整形されたXML出力を生成する機能を手に入れ、パッケージの使いやすさとデバッグの容易性が大幅に向上しました。
関連リンク
- Go CL 5674050: https://golang.org/cl/5674050
参考にした情報源リンク
- Go言語公式ドキュメント
encoding/xml
パッケージ: https://pkg.go.dev/encoding/xml - Go言語の
reflect
パッケージ: https://pkg.go.dev/reflect - Go言語の
bufio
パッケージ: https://pkg.go.dev/bufio - XMLの基本概念 (W3C): https://www.w3.org/XML/
- Go言語のテストにおけるExample関数: https://go.dev/blog/examples
- Go言語の構造体タグ: https://go.dev/blog/json (JSONの例ですが、XMLタグも同様の概念です)