Commit 0a0186fb authored by Sarah Adams's avatar Sarah Adams Committed by Ian Lance Taylor

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/38386Reviewed-by: 's avatarIan Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent 1295b745
...@@ -120,6 +120,9 @@ import ( ...@@ -120,6 +120,9 @@ import (
// Unmarshal maps an XML element to a pointer by setting the pointer // 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. // 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 { func Unmarshal(data []byte, v interface{}) error {
return NewDecoder(bytes.NewReader(data)).Decode(v) return NewDecoder(bytes.NewReader(data)).Decode(v)
} }
...@@ -607,24 +610,40 @@ func copyValue(dst reflect.Value, src []byte) (err error) { ...@@ -607,24 +610,40 @@ func copyValue(dst reflect.Value, src []byte) (err error) {
default: default:
return errors.New("cannot unmarshal into " + dst0.Type().String()) return errors.New("cannot unmarshal into " + dst0.Type().String())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 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()) itmp, err := strconv.ParseInt(string(src), 10, dst.Type().Bits())
if err != nil { if err != nil {
return err return err
} }
dst.SetInt(itmp) dst.SetInt(itmp)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 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()) utmp, err := strconv.ParseUint(string(src), 10, dst.Type().Bits())
if err != nil { if err != nil {
return err return err
} }
dst.SetUint(utmp) dst.SetUint(utmp)
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
if len(src) == 0 {
dst.SetFloat(0)
return nil
}
ftmp, err := strconv.ParseFloat(string(src), dst.Type().Bits()) ftmp, err := strconv.ParseFloat(string(src), dst.Type().Bits())
if err != nil { if err != nil {
return err return err
} }
dst.SetFloat(ftmp) dst.SetFloat(ftmp)
case reflect.Bool: case reflect.Bool:
if len(src) == 0 {
dst.SetBool(false)
return nil
}
value, err := strconv.ParseBool(strings.TrimSpace(string(src))) value, err := strconv.ParseBool(strings.TrimSpace(string(src)))
if err != nil { if err != nil {
return err return err
......
...@@ -752,3 +752,159 @@ func TestInvalidInnerXMLType(t *testing.T) { ...@@ -752,3 +752,159 @@ func TestInvalidInnerXMLType(t *testing.T) {
t.Errorf("NotInnerXML = %v, want nil", v.NotInnerXML) 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)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment