aboutsummaryrefslogtreecommitdiff
path: root/src/encoding/xml
diff options
context:
space:
mode:
authorSarah Adams <shadams@google.com>2017-03-20 16:11:46 -0700
committerIan Lance Taylor <iant@golang.org>2017-03-22 23:33:36 +0000
commit0a0186fb7832928c9b5b1966854a8abc31678ea8 (patch)
treefe23a6bf8f67191dc3cb01e93cc2fc9e54fc3307 /src/encoding/xml
parent1295b745d13fe1402d5b645c9c20cc3adf85d563 (diff)
downloadgo-0a0186fb7832928c9b5b1966854a8abc31678ea8.tar.gz
go-0a0186fb7832928c9b5b1966854a8abc31678ea8.zip
encoding/xml: unmarshal allow empty, non-string values
When unmarshaling, if an element is empty, eg. '<tag></tag>', and destination type is int, uint, float or bool, do not attempt to parse value (""). Set to its zero value instead. Fixes #13417 Change-Id: I2d79f6d8f39192bb277b1a9129727d5abbb2dd1f Reviewed-on: https://go-review.googlesource.com/38386 Reviewed-by: Ian Lance Taylor <iant@golang.org> Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
Diffstat (limited to 'src/encoding/xml')
-rw-r--r--src/encoding/xml/read.go19
-rw-r--r--src/encoding/xml/read_test.go156
2 files changed, 175 insertions, 0 deletions
diff --git a/src/encoding/xml/read.go b/src/encoding/xml/read.go
index b90271fed3..000d9fbd0e 100644
--- a/src/encoding/xml/read.go
+++ b/src/encoding/xml/read.go
@@ -120,6 +120,9 @@ import (
// Unmarshal maps an XML element to a pointer by setting the pointer
// to a freshly allocated value and then mapping the element to that value.
//
+// A missing element or empty attribute value will be unmarshaled as a zero value.
+// If the field is a slice, a zero value will be appended to the field. Otherwise, the
+// field will be set to its zero value.
func Unmarshal(data []byte, v interface{}) error {
return NewDecoder(bytes.NewReader(data)).Decode(v)
}
@@ -607,24 +610,40 @@ func copyValue(dst reflect.Value, src []byte) (err error) {
default:
return errors.New("cannot unmarshal into " + dst0.Type().String())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ if len(src) == 0 {
+ dst.SetInt(0)
+ return nil
+ }
itmp, err := strconv.ParseInt(string(src), 10, dst.Type().Bits())
if err != nil {
return err
}
dst.SetInt(itmp)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ if len(src) == 0 {
+ dst.SetUint(0)
+ return nil
+ }
utmp, err := strconv.ParseUint(string(src), 10, dst.Type().Bits())
if err != nil {
return err
}
dst.SetUint(utmp)
case reflect.Float32, reflect.Float64:
+ if len(src) == 0 {
+ dst.SetFloat(0)
+ return nil
+ }
ftmp, err := strconv.ParseFloat(string(src), dst.Type().Bits())
if err != nil {
return err
}
dst.SetFloat(ftmp)
case reflect.Bool:
+ if len(src) == 0 {
+ dst.SetBool(false)
+ return nil
+ }
value, err := strconv.ParseBool(strings.TrimSpace(string(src)))
if err != nil {
return err
diff --git a/src/encoding/xml/read_test.go b/src/encoding/xml/read_test.go
index 273c303d16..a1eb516187 100644
--- a/src/encoding/xml/read_test.go
+++ b/src/encoding/xml/read_test.go
@@ -752,3 +752,159 @@ func TestInvalidInnerXMLType(t *testing.T) {
t.Errorf("NotInnerXML = %v, want nil", v.NotInnerXML)
}
}
+
+type Child struct {
+ G struct {
+ I int
+ }
+}
+
+type ChildToEmbed struct {
+ X bool
+}
+
+type Parent struct {
+ I int
+ IPtr *int
+ Is []int
+ IPtrs []*int
+ F float32
+ FPtr *float32
+ Fs []float32
+ FPtrs []*float32
+ B bool
+ BPtr *bool
+ Bs []bool
+ BPtrs []*bool
+ Bytes []byte
+ BytesPtr *[]byte
+ S string
+ SPtr *string
+ Ss []string
+ SPtrs []*string
+ MyI MyInt
+ Child Child
+ Children []Child
+ ChildPtr *Child
+ ChildToEmbed
+}
+
+const (
+ emptyXML = `
+<Parent>
+ <I></I>
+ <IPtr></IPtr>
+ <Is></Is>
+ <IPtrs></IPtrs>
+ <F></F>
+ <FPtr></FPtr>
+ <Fs></Fs>
+ <FPtrs></FPtrs>
+ <B></B>
+ <BPtr></BPtr>
+ <Bs></Bs>
+ <BPtrs></BPtrs>
+ <Bytes></Bytes>
+ <BytesPtr></BytesPtr>
+ <S></S>
+ <SPtr></SPtr>
+ <Ss></Ss>
+ <SPtrs></SPtrs>
+ <MyI></MyI>
+ <Child></Child>
+ <Children></Children>
+ <ChildPtr></ChildPtr>
+ <X></X>
+</Parent>
+`
+)
+
+// github.com/golang/go/issues/13417
+func TestUnmarshalEmptyValues(t *testing.T) {
+ // Test first with a zero-valued dst.
+ v := new(Parent)
+ if err := Unmarshal([]byte(emptyXML), v); err != nil {
+ t.Fatalf("zero: Unmarshal failed: got %v", err)
+ }
+
+ zBytes, zInt, zStr, zFloat, zBool := []byte{}, 0, "", float32(0), false
+ want := &Parent{
+ IPtr: &zInt,
+ Is: []int{zInt},
+ IPtrs: []*int{&zInt},
+ FPtr: &zFloat,
+ Fs: []float32{zFloat},
+ FPtrs: []*float32{&zFloat},
+ BPtr: &zBool,
+ Bs: []bool{zBool},
+ BPtrs: []*bool{&zBool},
+ Bytes: []byte{},
+ BytesPtr: &zBytes,
+ SPtr: &zStr,
+ Ss: []string{zStr},
+ SPtrs: []*string{&zStr},
+ Children: []Child{{}},
+ ChildPtr: new(Child),
+ ChildToEmbed: ChildToEmbed{},
+ }
+ if !reflect.DeepEqual(v, want) {
+ t.Fatalf("zero: Unmarshal:\nhave: %#+v\nwant: %#+v", v, want)
+ }
+
+ // Test with a pre-populated dst.
+ // Multiple addressable copies, as pointer-to fields will replace value during unmarshal.
+ vBytes0, vInt0, vStr0, vFloat0, vBool0 := []byte("x"), 1, "x", float32(1), true
+ vBytes1, vInt1, vStr1, vFloat1, vBool1 := []byte("x"), 1, "x", float32(1), true
+ vInt2, vStr2, vFloat2, vBool2 := 1, "x", float32(1), true
+ v = &Parent{
+ I: vInt0,
+ IPtr: &vInt1,
+ Is: []int{vInt0},
+ IPtrs: []*int{&vInt2},
+ F: vFloat0,
+ FPtr: &vFloat1,
+ Fs: []float32{vFloat0},
+ FPtrs: []*float32{&vFloat2},
+ B: vBool0,
+ BPtr: &vBool1,
+ Bs: []bool{vBool0},
+ BPtrs: []*bool{&vBool2},
+ Bytes: vBytes0,
+ BytesPtr: &vBytes1,
+ S: vStr0,
+ SPtr: &vStr1,
+ Ss: []string{vStr0},
+ SPtrs: []*string{&vStr2},
+ MyI: MyInt(vInt0),
+ Child: Child{G: struct{ I int }{I: vInt0}},
+ Children: []Child{{G: struct{ I int }{I: vInt0}}},
+ ChildPtr: &Child{G: struct{ I int }{I: vInt0}},
+ ChildToEmbed: ChildToEmbed{X: vBool0},
+ }
+ if err := Unmarshal([]byte(emptyXML), v); err != nil {
+ t.Fatalf("populated: Unmarshal failed: got %v", err)
+ }
+
+ want = &Parent{
+ IPtr: &zInt,
+ Is: []int{vInt0, zInt},
+ IPtrs: []*int{&vInt0, &zInt},
+ FPtr: &zFloat,
+ Fs: []float32{vFloat0, zFloat},
+ FPtrs: []*float32{&vFloat0, &zFloat},
+ BPtr: &zBool,
+ Bs: []bool{vBool0, zBool},
+ BPtrs: []*bool{&vBool0, &zBool},
+ Bytes: []byte{},
+ BytesPtr: &zBytes,
+ SPtr: &zStr,
+ Ss: []string{vStr0, zStr},
+ SPtrs: []*string{&vStr0, &zStr},
+ Child: Child{G: struct{ I int }{I: vInt0}}, // I should == zInt0? (zero value)
+ Children: []Child{{G: struct{ I int }{I: vInt0}}, {}},
+ ChildPtr: &Child{G: struct{ I int }{I: vInt0}}, // I should == zInt0? (zero value)
+ }
+ if !reflect.DeepEqual(v, want) {
+ t.Fatalf("populated: Unmarshal:\nhave: %#+v\nwant: %#+v", v, want)
+ }
+}