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

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

このコミットは、Go言語の標準ライブラリ encoding/xml パッケージに、(*Encoder).Indent メソッドを追加するものです。これにより、XMLエンコーダがストリーム処理中にインデントされたXML出力を生成できるようになります。これまでは、xml.MarshalIndent 関数を使用した場合にのみインデントが可能でしたが、この変更により、xml.NewEncoder で作成された Encoder インスタンスに対しても、明示的にインデント設定を適用できるようになりました。

コミット

commit ee908748265debed97592f63a40f41a17e9c9d2a
Author: Russ Cox <rsc@golang.org>
Date:   Wed Jan 30 07:57:20 2013 -0800

    encoding/xml: add (*Encoder).Indent
    
    Exposing this on the Encoder allows streaming generation of indented XML.
    
    R=golang-dev, rogpeppe
    CC=golang-dev
    https://golang.org/cl/7221075

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

https://github.com/golang/go/commit/ee908748265debed97592f63a40f41a17e9c9d2a

元コミット内容

encoding/xml パッケージに (*Encoder).Indent メソッドを追加します。このメソッドを Encoder に公開することで、インデントされたXMLをストリーミングで生成することが可能になります。

変更の背景

Go言語の encoding/xml パッケージには、Goの構造体をXML形式にエンコードするための機能が提供されています。従来、XML出力をインデントして整形するには、xml.MarshalIndent 関数を使用する必要がありました。この関数は、Goのオブジェクトを一度メモリ上でXMLバイト列に変換し、その際にインデントを適用します。

しかし、大規模なデータや、リアルタイムでXMLを生成して出力ストリームに書き込むようなシナリオ(例えば、HTTPレスポンスとしてXMLを返すWebサーバーなど)では、xml.MarshalIndent のように一度全体をメモリにロードしてから処理する方法は効率的ではありません。このようなストリーミングのユースケースでは、xml.NewEncoder を使用して io.Writer に直接書き込む Encoder インスタンスが利用されます。

このコミット以前は、Encoder インスタンスには直接インデントを設定する公開されたメソッドがありませんでした。そのため、ストリーミングでインデントされたXMLを生成するには、カスタムのラッパーや複雑なロジックが必要でした。この変更は、EncoderIndent メソッドを追加することで、ストリーミング処理においても簡単に整形されたXMLを出力できるようにし、開発者の利便性と効率性を向上させることを目的としています。

前提知識の解説

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

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

  • xml.Marshal(v interface{}) ([]byte, error): Goの値をXMLバイト列にマーシャリングします。インデントは適用されません。
  • xml.MarshalIndent(v interface{}, prefix, indent string) ([]byte, error): Goの値をXMLバイト列にマーシャリングし、指定された prefixindent 文字列を使用してインデントを適用します。
  • xml.NewEncoder(w io.Writer) *Encoder: 指定された io.Writer にXMLを書き込む新しい Encoder を作成します。この Encoder を使用すると、Encode メソッドを呼び出すたびに、Goの値をストリームに直接書き込むことができます。これは、メモリ効率が良く、大規模なデータやストリーミング処理に適しています。
  • xml.Encoder: NewEncoder によって返される型で、XMLエンコーディングのストリーム処理を管理します。

XMLのインデント

XMLのインデントは、XMLドキュメントの可読性を向上させるために、要素のネストレベルに応じて空白(スペースやタブ)を追加する整形方法です。例えば、以下のようにインデントされます。

<root>
    <element1>
        <subelement/>
    </element1>
    <element2/>
</root>

prefix は各行の先頭に追加される文字列(例: ドキュメント全体の先頭に付く文字列)、indent はネストレベルごとに繰り返されるインデント文字列(例: スペース2つ、タブ1つ)を指します。

技術的詳細

このコミットの核心は、xml.Encoder 型に Indent メソッドを追加し、その内部で prefixindent という非公開フィールドを設定できるようにした点です。

encoding/xml パッケージの内部では、XMLの書き込み処理を行う printer という構造体が存在し、この printer が実際にインデントのロジックを管理しています。Encoder はこの printer を内包しており、prefixindent フィールドは printer の設定に直接影響を与えます。

変更前は、xml.MarshalIndent 関数が内部的に NewEncoder を呼び出した後、直接 enc.prefix = prefixenc.indent = indent のようにフィールドに値を設定していました。これは、MarshalIndent が一度限りの処理であり、その場でインデント設定を完結させるためでした。

しかし、ストリーミングでXMLを生成する Encoder の場合、NewEncoder でインスタンスを作成した後に、任意のタイミングでインデント設定を変更できるメカニズムが必要でした。(*Encoder).Indent メソッドは、このニーズに応えるために導入されました。

このメソッドが追加されたことで、開発者は以下のように Encoder を利用できるようになります。

  1. xml.NewEncoder(w io.Writer)Encoder インスタンスを作成する。
  2. enc.Indent(prefix, indent) を呼び出して、インデントの形式を設定する。
  3. enc.Encode(v) を繰り返し呼び出して、Goの値をストリームに書き込む。この際、設定されたインデントが自動的に適用される。

これにより、MarshalIndent のように一度に全てのXMLをメモリに保持することなく、効率的に整形されたXMLを生成し続けることが可能になります。

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

このコミットによる主要なコード変更は以下の2つのファイルにあります。

  1. src/pkg/encoding/xml/example_test.go: ExampleEncoder() という新しいテスト関数が追加され、(*Encoder).Indent の使用例が示されています。
  2. src/pkg/encoding/xml/marshal.go:
    • MarshalIndent 関数内で、直接フィールドに値を設定していた部分が enc.Indent(prefix, indent) の呼び出しに置き換えられました。
    • (*Encoder).Indent メソッドが新しく追加されました。

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

--- a/src/pkg/encoding/xml/marshal.go
+++ b/src/pkg/encoding/xml/marshal.go
@@ -81,8 +81,7 @@ func Marshal(v interface{}) ([]byte, error) {
 func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
 	var b bytes.Buffer
 	enc := NewEncoder(&b)
-	enc.prefix = prefix
-	enc.indent = indent
+	enc.Indent(prefix, indent)
 	if err := enc.Encode(v); err != nil {
 		return nil, err
 	}
@@ -99,6 +98,14 @@ func NewEncoder(w io.Writer) *Encoder {
 	return &Encoder{printer{Writer: bufio.NewWriter(w)}}
 }
 
+// Indent sets the encoder to generate XML in which each 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 (enc *Encoder) Indent(prefix, indent string) {
+	enc.prefix = prefix
+	enc.indent = indent
+}
+
 // Encode writes the XML encoding of v to the stream.
 //
 // See the documentation for Marshal for details about the conversion

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

--- a/src/pkg/encoding/xml/example_test.go
+++ b/src/pkg/encoding/xml/example_test.go
@@ -50,6 +50,46 @@ func ExampleMarshalIndent() {
 	//   </person>
 }
 
+func ExampleEncoder() {
+	type Address struct {
+		City, State string
+	}
+	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
+		Address
+		Comment string `xml:",comment"`
+	}
+
+	v := &Person{Id: 13, FirstName: "John", LastName: "Doe", Age: 42}
+	v.Comment = " Need more details. "
+	v.Address = Address{"Hanga Roa", "Easter Island"}
+
+	enc := xml.NewEncoder(os.Stdout)
+	enc.Indent("  ", "    ")
+	if err := enc.Encode(v); err != nil {
+		fmt.Printf("error: %v\n", err)
+	}
+
+	// Output:
+	//   <person id="13">
+	//       <name>
+	//           <first>John</first>
+	//           <last>Doe</last>
+	//       </name>
+	//       <age>42</age>
+	//       <Married>false</Married>
+	//       <City>Hanga Roa</City>
+	//       <State>Easter Island</State>
+	//       <!-- Need more details. -->
+	//   </person>
+}
+
 // This example demonstrates unmarshaling an XML excerpt into a value with
 // some preset fields. Note that the Phone field isn\'t modified and that
 // the XML <Company> element is ignored. Also, the Groups field is assigned

コアとなるコードの解説

func (enc *Encoder) Indent(prefix, indent string)

このメソッドは Encoder 型のレシーバを持ち、prefixindent という2つの文字列引数を受け取ります。

  • prefix: 各行の先頭に付加される文字列です。例えば、XMLドキュメント全体を特定の文字列で囲みたい場合などに使用できます。
  • indent: 各ネストレベルごとに繰り返されるインデント文字列です。通常はスペース2つ (" ") やタブ ("\t") などが指定されます。

メソッドの内部では、単にレシーバ enc の非公開フィールドである prefixindent に引数の値を代入しています。

func (enc *Encoder) Indent(prefix, indent string) {
	enc.prefix = prefix
	enc.indent = indent
}

このシンプルな代入により、Encoder がXMLを書き出す際に使用するインデント設定が更新されます。Encoder の内部ロジック(具体的には printer 構造体)は、これらのフィールドの値を参照して、XML要素の開始タグを新しい行に書き込み、適切な数のインデント文字列を付加します。

MarshalIndent 関数の変更

MarshalIndent 関数は、これまで直接 enc.prefix = prefixenc.indent = indent を行っていましたが、この変更により新しく追加された enc.Indent(prefix, indent) メソッドを呼び出すように修正されました。

-	enc.prefix = prefix
-	enc.indent = indent
+	enc.Indent(prefix, indent)

これは、コードの重複を避け、Indent メソッドがインデント設定の唯一の責任を持つようにするためのリファクタリングです。これにより、MarshalIndent の実装がより簡潔になり、Encoder のインデント設定ロジックが一元化されました。

ExampleEncoder() の追加

example_test.go に追加された ExampleEncoder() は、(*Encoder).Indent メソッドの具体的な使用方法を示しています。

	enc := xml.NewEncoder(os.Stdout)
	enc.Indent("  ", "    ") // ここでインデントを設定
	if err := enc.Encode(v); err != nil {
		fmt.Printf("error: %v\n", err)
	}

この例では、os.Stdout に直接XMLを書き出す Encoder を作成し、enc.Indent(" ", " ") を呼び出して、各行の先頭にスペース2つ、ネストレベルごとにスペース4つのインデントを設定しています。その後の enc.Encode(v) の呼び出しにより、整形されたXMLが出力されることが // Output: コメントで示されています。

この新しい例は、開発者が Encoder を使用してストリーミングでインデントされたXMLを生成する方法を理解する上で非常に役立ちます。

関連リンク

参考にした情報源リンク

  • Go言語 encoding/xml パッケージ公式ドキュメント: https://pkg.go.dev/encoding/xml
  • Go言語のソースコード (GitHub): https://github.com/golang/go
  • XML (Extensible Markup Language) の基本概念
  • Go言語のストリーム処理と io.Writer インターフェースに関する一般的な知識