mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
xml: add Marshal and MarshalIndent
I have written up a Marshal and MarshalIndent pair that should closely reflect the way that Unmarshal works. I would love feedback on making this code more accessible and efficient... I haven't used reflecton on this scale before, so there is probably a lot of work that can be done on that. Some potentially controversial things: - All tag names are lower-cased by default. - Zero-valued struct values are skipped. - No namespace prefix (o:tag, etc) mechanism is supplied. - You are allowed to marshal non-struct values (even though unmarshal cannot handle them). - A tag for a non-XMLName struct field that isn't "attr", "chardata", or "innerxml" is used as the name of the tag. This could wreak havoc if you try to marshal a protobuf struct. - The "innerxml" and "chardata" are inserted verbatim. If you try to marshal something straight from unmarshal, the results could be unexpected (remove "innerxml" support from Marshal would be one possible solution). R=rsc CC=golang-dev https://golang.org/cl/4539082
This commit is contained in:
parent
9ded2b3451
commit
abd50de296
4 changed files with 578 additions and 0 deletions
299
src/pkg/xml/marshal_test.go
Normal file
299
src/pkg/xml/marshal_test.go
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package xml
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"os"
|
||||
"bytes"
|
||||
"strings"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type DriveType int
|
||||
|
||||
const (
|
||||
HyperDrive DriveType = iota
|
||||
ImprobabilityDrive
|
||||
)
|
||||
|
||||
type Passenger struct {
|
||||
Name []string "name"
|
||||
Weight float32 "weight"
|
||||
}
|
||||
|
||||
type Ship struct {
|
||||
XMLName Name "spaceship"
|
||||
|
||||
Name string "attr"
|
||||
Pilot string "attr"
|
||||
Drive DriveType "drive"
|
||||
Age uint "age"
|
||||
Passenger []*Passenger "passenger"
|
||||
secret string
|
||||
}
|
||||
|
||||
type RawXML string
|
||||
|
||||
func (rx RawXML) MarshalXML() ([]byte, os.Error) {
|
||||
return []byte(rx), nil
|
||||
}
|
||||
|
||||
type NamedType string
|
||||
|
||||
type Port struct {
|
||||
XMLName Name "port"
|
||||
Type string "attr"
|
||||
Number string "chardata"
|
||||
}
|
||||
|
||||
type Domain struct {
|
||||
XMLName Name "domain"
|
||||
Country string "attr"
|
||||
Name []byte "chardata"
|
||||
}
|
||||
|
||||
type SecretAgent struct {
|
||||
XMLName Name "agent"
|
||||
Handle string "attr"
|
||||
Identity string
|
||||
Obfuscate string "innerxml"
|
||||
}
|
||||
|
||||
var nilStruct *Ship
|
||||
|
||||
var marshalTests = []struct {
|
||||
Value interface{}
|
||||
ExpectXML string
|
||||
}{
|
||||
// Test nil marshals to nothing
|
||||
{Value: nil, ExpectXML: ``},
|
||||
{Value: nilStruct, ExpectXML: ``},
|
||||
|
||||
// Test value types (no tag name, so ???)
|
||||
{Value: true, ExpectXML: `<???>true</???>`},
|
||||
{Value: int(42), ExpectXML: `<???>42</???>`},
|
||||
{Value: int8(42), ExpectXML: `<???>42</???>`},
|
||||
{Value: int16(42), ExpectXML: `<???>42</???>`},
|
||||
{Value: int32(42), ExpectXML: `<???>42</???>`},
|
||||
{Value: uint(42), ExpectXML: `<???>42</???>`},
|
||||
{Value: uint8(42), ExpectXML: `<???>42</???>`},
|
||||
{Value: uint16(42), ExpectXML: `<???>42</???>`},
|
||||
{Value: uint32(42), ExpectXML: `<???>42</???>`},
|
||||
{Value: float32(1.25), ExpectXML: `<???>1.25</???>`},
|
||||
{Value: float64(1.25), ExpectXML: `<???>1.25</???>`},
|
||||
{Value: uintptr(0xFFDD), ExpectXML: `<???>65501</???>`},
|
||||
{Value: "gopher", ExpectXML: `<???>gopher</???>`},
|
||||
{Value: []byte("gopher"), ExpectXML: `<???>gopher</???>`},
|
||||
{Value: "</>", ExpectXML: `<???></></???>`},
|
||||
{Value: []byte("</>"), ExpectXML: `<???></></???>`},
|
||||
{Value: [3]byte{'<', '/', '>'}, ExpectXML: `<???></></???>`},
|
||||
{Value: NamedType("potato"), ExpectXML: `<???>potato</???>`},
|
||||
{Value: []int{1, 2, 3}, ExpectXML: `<???>1</???><???>2</???><???>3</???>`},
|
||||
{Value: [3]int{1, 2, 3}, ExpectXML: `<???>1</???><???>2</???><???>3</???>`},
|
||||
|
||||
// Test innerxml
|
||||
{Value: RawXML("</>"), ExpectXML: `</>`},
|
||||
{
|
||||
Value: &SecretAgent{
|
||||
Handle: "007",
|
||||
Identity: "James Bond",
|
||||
Obfuscate: "<redacted/>",
|
||||
},
|
||||
//ExpectXML: `<agent handle="007"><redacted/></agent>`,
|
||||
ExpectXML: `<agent handle="007"><Identity>James Bond</Identity><redacted/></agent>`,
|
||||
},
|
||||
|
||||
// Test structs
|
||||
{Value: &Port{Type: "ssl", Number: "443"}, ExpectXML: `<port type="ssl">443</port>`},
|
||||
{Value: &Port{Number: "443"}, ExpectXML: `<port>443</port>`},
|
||||
{Value: &Port{Type: "<unix>"}, ExpectXML: `<port type="<unix>"></port>`},
|
||||
{Value: &Domain{Name: []byte("google.com&friends")}, ExpectXML: `<domain>google.com&friends</domain>`},
|
||||
{Value: atomValue, ExpectXML: atomXml},
|
||||
{
|
||||
Value: &Ship{
|
||||
Name: "Heart of Gold",
|
||||
Pilot: "Computer",
|
||||
Age: 1,
|
||||
Drive: ImprobabilityDrive,
|
||||
Passenger: []*Passenger{
|
||||
&Passenger{
|
||||
Name: []string{"Zaphod", "Beeblebrox"},
|
||||
Weight: 7.25,
|
||||
},
|
||||
&Passenger{
|
||||
Name: []string{"Trisha", "McMillen"},
|
||||
Weight: 5.5,
|
||||
},
|
||||
&Passenger{
|
||||
Name: []string{"Ford", "Prefect"},
|
||||
Weight: 7,
|
||||
},
|
||||
&Passenger{
|
||||
Name: []string{"Arthur", "Dent"},
|
||||
Weight: 6.75,
|
||||
},
|
||||
},
|
||||
},
|
||||
ExpectXML: `<spaceship name="Heart of Gold" pilot="Computer">` +
|
||||
`<drive>` + strconv.Itoa(int(ImprobabilityDrive)) + `</drive>` +
|
||||
`<age>1</age>` +
|
||||
`<passenger>` +
|
||||
`<name>Zaphod</name>` +
|
||||
`<name>Beeblebrox</name>` +
|
||||
`<weight>7.25</weight>` +
|
||||
`</passenger>` +
|
||||
`<passenger>` +
|
||||
`<name>Trisha</name>` +
|
||||
`<name>McMillen</name>` +
|
||||
`<weight>5.5</weight>` +
|
||||
`</passenger>` +
|
||||
`<passenger>` +
|
||||
`<name>Ford</name>` +
|
||||
`<name>Prefect</name>` +
|
||||
`<weight>7</weight>` +
|
||||
`</passenger>` +
|
||||
`<passenger>` +
|
||||
`<name>Arthur</name>` +
|
||||
`<name>Dent</name>` +
|
||||
`<weight>6.75</weight>` +
|
||||
`</passenger>` +
|
||||
`</spaceship>`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
for idx, test := range marshalTests {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := Marshal(buf, test.Value)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: Error: %s", idx, err)
|
||||
continue
|
||||
}
|
||||
if got, want := buf.String(), test.ExpectXML; got != want {
|
||||
if strings.Contains(want, "\n") {
|
||||
t.Errorf("#%d: marshal(%#v) - GOT:\n%s\nWANT:\n%s", idx, test.Value, got, want)
|
||||
} else {
|
||||
t.Errorf("#%d: marshal(%#v) = %#q want %#q", idx, test.Value, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var marshalErrorTests = []struct {
|
||||
Value interface{}
|
||||
ExpectErr string
|
||||
ExpectKind reflect.Kind
|
||||
}{
|
||||
{
|
||||
Value: make(chan bool),
|
||||
ExpectErr: "xml: unsupported type: chan bool",
|
||||
ExpectKind: reflect.Chan,
|
||||
},
|
||||
{
|
||||
Value: map[string]string{
|
||||
"question": "What do you get when you multiply six by nine?",
|
||||
"answer": "42",
|
||||
},
|
||||
ExpectErr: "xml: unsupported type: map[string] string",
|
||||
ExpectKind: reflect.Map,
|
||||
},
|
||||
{
|
||||
Value: map[*Ship]bool{nil: false},
|
||||
ExpectErr: "xml: unsupported type: map[*xml.Ship] bool",
|
||||
ExpectKind: reflect.Map,
|
||||
},
|
||||
}
|
||||
|
||||
func TestMarshalErrors(t *testing.T) {
|
||||
for idx, test := range marshalErrorTests {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := Marshal(buf, test.Value)
|
||||
if got, want := err, test.ExpectErr; got == nil {
|
||||
t.Errorf("#%d: want error %s", idx, want)
|
||||
continue
|
||||
} else if got.String() != want {
|
||||
t.Errorf("#%d: marshal(%#v) = [error] %q, want %q", idx, test.Value, got, want)
|
||||
}
|
||||
if got, want := err.(*UnsupportedTypeError).Type.Kind(), test.ExpectKind; got != want {
|
||||
t.Errorf("#%d: marshal(%#v) = [error kind] %s, want %s", idx, test.Value, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do invertibility testing on the various structures that we test
|
||||
func TestUnmarshal(t *testing.T) {
|
||||
for i, test := range marshalTests {
|
||||
// Skip the nil pointers
|
||||
if i <= 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
var dest interface{}
|
||||
|
||||
switch test.Value.(type) {
|
||||
case *Ship, Ship:
|
||||
dest = &Ship{}
|
||||
case *Port, Port:
|
||||
dest = &Port{}
|
||||
case *Domain, Domain:
|
||||
dest = &Domain{}
|
||||
case *Feed, Feed:
|
||||
dest = &Feed{}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
buffer := bytes.NewBufferString(test.ExpectXML)
|
||||
err := Unmarshal(buffer, dest)
|
||||
|
||||
// Don't compare XMLNames
|
||||
switch fix := dest.(type) {
|
||||
case *Ship:
|
||||
fix.XMLName = Name{}
|
||||
case *Port:
|
||||
fix.XMLName = Name{}
|
||||
case *Domain:
|
||||
fix.XMLName = Name{}
|
||||
case *Feed:
|
||||
fix.XMLName = Name{}
|
||||
fix.Author.InnerXML = ""
|
||||
for i := range fix.Entry {
|
||||
fix.Entry[i].Author.InnerXML = ""
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("#%d: unexpected error: %#v", i, err)
|
||||
} else if got, want := dest, test.Value; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("#%d: unmarshal(%#s) = %#v, want %#v", i, test.ExpectXML, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshal(b *testing.B) {
|
||||
idx := len(marshalTests) - 1
|
||||
test := marshalTests[idx]
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for i := 0; i < b.N; i++ {
|
||||
Marshal(buf, test.Value)
|
||||
buf.Truncate(0)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshal(b *testing.B) {
|
||||
idx := len(marshalTests) - 1
|
||||
test := marshalTests[idx]
|
||||
sm := &Ship{}
|
||||
xml := []byte(test.ExpectXML)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
buffer := bytes.NewBuffer(xml)
|
||||
Unmarshal(buffer, sm)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue