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

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

このコミットは、Go言語の標準ライブラリ encoding/xml パッケージにおける MarshalIndent 関数の挙動を修正するものです。具体的には、MarshalIndent が生成するXML出力の先頭に不要な改行が含まれる問題を解決し、より期待される整形済みXML出力を提供します。

コミット

commit 848d10f06cf327212d1ce7041c0eeae5fda317f1
Author: Shivakumar GN <shivakumar.gn@gmail.com>
Date:   Sun Feb 3 11:21:07 2013 -0500

    xml: omit newline at beginning of MarshalIndent output
    
    (Still valid XML.)
    
    Fixes #3354.
    
    R=golang-dev, dave
    CC=golang-dev
    https://golang.org/cl/7288047

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

https://github.com/golang/go/commit/848d10f06cf327212d1ce7041c0eeae5fda317f1

元コミット内容

xml: omit newline at beginning of MarshalIndent output

(Still valid XML.)

Fixes #3354.

R=golang-dev, dave
CC=golang-dev
https://golang.org/cl/7288047

変更の背景

この変更は、Go言語の encoding/xml パッケージの MarshalIndent 関数が、XMLデータを整形して出力する際に、結果のXML文字列の先頭に余分な改行文字(\n)を挿入してしまうというバグ(Issue 3354)を修正するために行われました。

MarshalIndent は、XMLデータを人間が読みやすいようにインデントを付けて整形する機能を提供します。しかし、この余分な改行は、特にXML宣言(<?xml version="1.0"?>)がない場合に、出力の見た目を損ねたり、一部のXMLパーサーやツールで予期せぬ挙動を引き起こす可能性がありました。XMLの仕様上、XML宣言の前に空白文字が存在することは許容されますが、XML宣言がない場合に先頭に改行があるのは、一般的に期待される整形出力ではありませんでした。

この問題は、GoのIssueトラッカーで Issue 3354: encoding/xml: MarshalIndent adds newline at beginning of output として報告されており、このコミットはその報告に対する修正として実装されました。

前提知識の解説

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

encoding/xml パッケージは、Go言語でXMLデータをエンコード(Goの構造体からXMLへ変換)およびデコード(XMLからGoの構造体へ変換)するための機能を提供します。このパッケージは、XMLの要素、属性、テキストコンテンツなどをGoのデータ型にマッピングするためのタグ付けメカニズム(構造体フィールドタグ)をサポートしています。

MarshalMarshalIndent 関数

  • xml.Marshal(v interface{}) ([]byte, error): この関数は、Goの任意のデータ構造 v をXML形式にエンコードし、その結果をバイトスライスとして返します。この関数は、XMLを整形せずに、可能な限りコンパクトな形式で出力します。

  • xml.MarshalIndent(v interface{}, prefix, indent string) ([]byte, error): この関数も Marshal と同様にGoのデータ構造をXMLにエンコードしますが、出力されるXMLを人間が読みやすいように整形(インデント)します。

    • prefix: 各行の先頭に付加される文字列(例: XML宣言の前にスペースを入れる場合など)。
    • indent: 各インデントレベルで使用される文字列(例: \t でタブ、 でスペース)。

XMLにおける改行と空白文字の扱い

XMLの仕様では、要素間の空白文字(スペース、タブ、改行)は、そのコンテキストによって「意味のある空白」と「意味のない空白」に区別されます。整形されたXMLでは、通常、要素間の改行やインデントは「意味のない空白」と見なされ、XMLパーサーによって無視されるか、正規化されます。

しかし、XMLドキュメントの先頭、特にXML宣言(<?xml ...?>)の前に空白文字が存在することは、XML 1.0の仕様では許容されています。このコミットで修正された問題は、この「許容される」空白が、MarshalIndent の意図しない挙動によって「余分な」改行として出力されてしまう点にありました。

技術的詳細

この修正は、encoding/xml/marshal.go 内の printer 構造体と writeIndent メソッドのロジックを変更することで実現されています。

printer 構造体

printer 構造体は、XMLのエンコード処理中にXML出力を構築するための内部的なヘルパー構造体です。この構造体には、インデントレベル、プレフィックス、現在のインデント状態などを管理するためのフィールドが含まれています。

今回の変更では、printer 構造体に新たに putNewline bool フィールドが追加されました。

  • putNewline: このフラグは、次に改行を出力すべきかどうかを制御します。初期値は false で、最初のインデント処理時には改行を出力しないようにします。一度改行が出力された後は true に設定され、それ以降のインデント処理では通常通り改行が出力されるようになります。

writeIndent メソッド

writeIndent メソッドは、XML要素の開始タグや終了タグの前にインデントと改行を書き込む役割を担っています。

修正前は、このメソッドが呼び出されるたびに無条件に改行文字(\n)を書き込んでいました。これが、MarshalIndent が生成するXML出力の先頭に余分な改行が含まれる原因でした。

修正後、writeIndent メソッドは p.putNewline フラグの状態をチェックするようになりました。

  • if p.putNewline: putNewlinetrue の場合のみ、改行文字を書き込みます。
  • else: putNewlinefalse の場合(つまり、MarshalIndent が呼び出されてから最初のインデント処理の場合)、改行は書き込まず、p.putNewlinetrue に設定します。これにより、2回目以降のインデント処理では改行が通常通り出力されるようになります。

このロジックにより、MarshalIndent が生成するXML出力の最初のインデント処理では改行が抑制され、結果としてXML文字列の先頭に余分な改行が含まれなくなります。

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

src/pkg/encoding/xml/marshal.go

--- a/src/pkg/encoding/xml/marshal.go
+++ b/src/pkg/encoding/xml/marshal.go
@@ -124,6 +124,7 @@ type printer struct {
 	prefix     string
 	depth      int
 	indentedIn bool
+	putNewline bool // 新規追加
 }
 
 // marshalValue writes one or more XML elements representing val.
@@ -394,7 +395,11 @@ func (p *printer) writeIndent(depthDelta int) {
 		}
 		p.indentedIn = false
 	}
-	p.WriteByte('\n') // 変更前: 無条件に改行を書き込む
+	if p.putNewline { // 変更後: putNewlineがtrueの場合のみ改行
+		p.WriteByte('\n')
+	} else { // putNewlineがfalseの場合(初回)
+		p.putNewline = true // 次回以降は改行を許可
+	}
 	if len(p.prefix) > 0 {
 		p.WriteString(p.prefix)
 	}

src/pkg/encoding/xml/marshal_test.go

--- a/src/pkg/encoding/xml/marshal_test.go
+++ b/src/pkg/encoding/xml/marshal_test.go
@@ -7,6 +7,7 @@ package xml
 import (
 	"bytes"
 	"errors"
+	"fmt" // fmtパッケージのインポートを追加
 	"io"
 	"reflect"
 	"strconv"
@@ -840,6 +841,24 @@ var marshalErrorTests = []struct {
 	},\n}\n \n+var marshalIndentTests = []struct { // MarshalIndentの新しいテストケースを追加
+	Value     interface{}
+	Prefix    string
+	Indent    string
+	ExpectXML string
+}{\n+\t{\n+\t\tValue: &SecretAgent{\n+\t\t\tHandle:    "007",\n+\t\t\tIdentity:  "James Bond",\n+\t\t\tObfuscate: "<redacted/>",\n+\t\t},\n+\t\tPrefix:    "",\n+\t\tIndent:    "\t",
+		ExpectXML: fmt.Sprintf("<agent handle=\"007\">\\n\\t<Identity>James Bond</Identity><redacted/>\\n</agent>"),
+	},\n}\n+\n func TestMarshalErrors(t *testing.T) {\n \tfor idx, test := range marshalErrorTests {\n \t\t_, err := Marshal(test.Value)\n@@ -884,6 +903,19 @@ func TestUnmarshal(t *testing.T) {\n \t}\n }\n \n+func TestMarshalIndent(t *testing.T) { // MarshalIndentのテスト関数を追加
+\tfor i, test := range marshalIndentTests {\n+\t\tdata, err := MarshalIndent(test.Value, test.Prefix, test.Indent)\n+\t\tif err != nil {\n+\t\t\tt.Errorf("#%d: Error: %s", i, err)\n+\t\t\tcontinue\n+\t\t}\n+\t\tif got, want := string(data), test.ExpectXML; got != want {\n+\t\t\tt.Errorf("#%d: MarshalIndent:\\nGot:%s\\nWant:\\n%s", i, got, want)\n+\t\t}\n+\t}\n+}\n+\n type limitedBytesWriter struct {\n \tw      io.Writer\n \tremain int // until writes fail

コアとなるコードの解説

このコミットの核心は、printer 構造体に導入された putNewline フラグと、それを利用した writeIndent メソッドの条件付き改行出力ロジックです。

  1. putNewline フラグの導入: printer 構造体に putNewline bool フィールドが追加されました。このフラグは、MarshalIndent がXMLのエンコードを開始してから、まだ一度も改行を出力していない状態を追跡するために使用されます。初期値は false です。

  2. writeIndent メソッドの変更: writeIndent メソッドは、XML要素のインデントと改行を処理する際に呼び出されます。

    • 初回呼び出し時: putNewlinefalse の場合、これは MarshalIndent がXML出力の最初のインデント処理を行っていることを意味します。このとき、writeIndent は改行を出力せず、代わりに putNewlinetrue に設定します。これにより、XML出力の先頭に余分な改行が挿入されるのを防ぎます。
    • 2回目以降の呼び出し時: putNewlinetrue の場合、これは最初のインデント処理が既に完了していることを意味します。このとき、writeIndent は通常通り改行文字(\n)を書き込みます。これにより、XMLの各要素が適切に改行され、整形された出力が維持されます。

このシンプルなフラグと条件分岐の追加により、MarshalIndent はXMLの構造を損なうことなく、期待される整形済みXML(先頭に余分な改行がない)を生成できるようになりました。

また、この変更を検証するために、marshal_test.gomarshalIndentTests という新しいテストケースと TestMarshalIndent というテスト関数が追加されました。このテストは、MarshalIndent の出力が、先頭に改行を含まない正しい形式であることを確認します。

関連リンク

参考にした情報源リンク