Commit 57e76417 authored by Raif S. Naffah's avatar Raif S. Naffah Committed by Russ Cox

reflect: add FieldByNameFunc

xml: add support for XML marshalling embedded structs.

R=rsc
CC=golang-dev
https://golang.org/cl/837042
parent 13f81feb
...@@ -507,7 +507,7 @@ func (t *StructType) FieldByIndex(index []int) (f StructField) { ...@@ -507,7 +507,7 @@ func (t *StructType) FieldByIndex(index []int) (f StructField) {
const inf = 1 << 30 // infinity - no struct has that many nesting levels const inf = 1 << 30 // infinity - no struct has that many nesting levels
func (t *StructType) fieldByName(name string, mark map[*StructType]bool, depth int) (ff StructField, fd int) { func (t *StructType) fieldByNameFunc(match func(string) bool, mark map[*StructType]bool, depth int) (ff StructField, fd int) {
fd = inf // field depth fd = inf // field depth
if mark[t] { if mark[t] {
...@@ -522,7 +522,7 @@ L: for i, _ := range t.fields { ...@@ -522,7 +522,7 @@ L: for i, _ := range t.fields {
f := t.Field(i) f := t.Field(i)
d := inf d := inf
switch { switch {
case f.Name == name: case match(f.Name):
// Matching top-level field. // Matching top-level field.
d = depth d = depth
case f.Anonymous: case f.Anonymous:
...@@ -531,13 +531,13 @@ L: for i, _ := range t.fields { ...@@ -531,13 +531,13 @@ L: for i, _ := range t.fields {
ft = pt.Elem() ft = pt.Elem()
} }
switch { switch {
case ft.Name() == name: case match(ft.Name()):
// Matching anonymous top-level field. // Matching anonymous top-level field.
d = depth d = depth
case fd > depth: case fd > depth:
// No top-level field yet; look inside nested structs. // No top-level field yet; look inside nested structs.
if st, ok := ft.(*StructType); ok { if st, ok := ft.(*StructType); ok {
f, d = st.fieldByName(name, mark, depth+1) f, d = st.fieldByNameFunc(match, mark, depth+1)
} }
} }
} }
...@@ -576,7 +576,13 @@ L: for i, _ := range t.fields { ...@@ -576,7 +576,13 @@ L: for i, _ := range t.fields {
// FieldByName returns the struct field with the given name // FieldByName returns the struct field with the given name
// and a boolean to indicate if the field was found. // and a boolean to indicate if the field was found.
func (t *StructType) FieldByName(name string) (f StructField, present bool) { func (t *StructType) FieldByName(name string) (f StructField, present bool) {
if ff, fd := t.fieldByName(name, make(map[*StructType]bool), 0); fd < inf { return t.FieldByNameFunc(func(s string) bool { return s == name })
}
// FieldByNameFunc returns the struct field with a name that satisfies the
// match function and a boolean to indicate if the field was found.
func (t *StructType) FieldByNameFunc(match func(string) bool) (f StructField, present bool) {
if ff, fd := t.fieldByNameFunc(match, make(map[*StructType]bool), 0); fd < inf {
ff.Index = ff.Index[0 : fd+1] ff.Index = ff.Index[0 : fd+1]
f, present = ff, true f, present = ff, true
} }
......
...@@ -1251,6 +1251,16 @@ func (t *StructValue) FieldByName(name string) Value { ...@@ -1251,6 +1251,16 @@ func (t *StructValue) FieldByName(name string) Value {
return nil return nil
} }
// FieldByNameFunc returns the struct field with a name that satisfies the
// match function.
// The result is nil if no field was found.
func (t *StructValue) FieldByNameFunc(match func(string) bool) Value {
if f, ok := t.Type().(*StructType).FieldByNameFunc(match); ok {
return t.FieldByIndex(f.Index)
}
return nil
}
// NumField returns the number of fields in the struct. // NumField returns the number of fields in the struct.
func (v *StructValue) NumField() int { return v.typ.(*StructType).NumField() } func (v *StructValue) NumField() int { return v.typ.(*StructType).NumField() }
......
// Copyright 2010 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 "testing"
type C struct {
Name string
Open bool
}
type A struct {
XMLName Name "http://domain a"
C
B B
FieldA string
}
type B struct {
XMLName Name "b"
C
FieldB string
}
const _1a = `
<?xml version="1.0" encoding="UTF-8"?>
<a xmlns="http://domain">
<name>KmlFile</name>
<open>1</open>
<b>
<name>Absolute</name>
<open>0</open>
<fieldb>bar</fieldb>
</b>
<fielda>foo</fielda>
</a>
`
// Tests that embedded structs are marshalled.
func TestEmbedded1(t *testing.T) {
var a A
if e := Unmarshal(StringReader(_1a), &a); e != nil {
t.Fatalf("Unmarshal: %s", e)
}
if a.FieldA != "foo" {
t.Fatalf("Unmarshal: expected 'foo' but found '%s'", a.FieldA)
}
if a.Name != "KmlFile" {
t.Fatalf("Unmarshal: expected 'KmlFile' but found '%s'", a.Name)
}
if !a.Open {
t.Fatal("Unmarshal: expected 'true' but found otherwise")
}
if a.B.FieldB != "bar" {
t.Fatalf("Unmarshal: expected 'bar' but found '%s'", a.B.FieldB)
}
if a.B.Name != "Absolute" {
t.Fatalf("Unmarshal: expected 'Absolute' but found '%s'", a.B.Name)
}
if a.B.Open {
t.Fatal("Unmarshal: expected 'false' but found otherwise")
}
}
type A2 struct {
XMLName Name "http://domain a"
XY string
Xy string
}
const _2a = `
<?xml version="1.0" encoding="UTF-8"?>
<a xmlns="http://domain">
<xy>foo</xy>
</a>
`
// Tests that conflicting field names get excluded.
func TestEmbedded2(t *testing.T) {
var a A2
if e := Unmarshal(StringReader(_2a), &a); e != nil {
t.Fatalf("Unmarshal: %s", e)
}
if a.XY != "" {
t.Fatalf("Unmarshal: expected empty string but found '%s'", a.XY)
}
if a.Xy != "" {
t.Fatalf("Unmarshal: expected empty string but found '%s'", a.Xy)
}
}
type A3 struct {
XMLName Name "http://domain a"
xy string
}
// Tests that private fields are not set.
func TestEmbedded3(t *testing.T) {
var a A3
if e := Unmarshal(StringReader(_2a), &a); e != nil {
t.Fatalf("Unmarshal: %s", e)
}
if a.xy != "" {
t.Fatalf("Unmarshal: expected empty string but found '%s'", a.xy)
}
}
type A4 struct {
XMLName Name "http://domain a"
Any string
}
// Tests that private fields are not set.
func TestEmbedded4(t *testing.T) {
var a A4
if e := Unmarshal(StringReader(_2a), &a); e != nil {
t.Fatalf("Unmarshal: %s", e)
}
if a.Any != "foo" {
t.Fatalf("Unmarshal: expected 'foo' but found '%s'", a.Any)
}
}
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"unicode" "unicode"
"utf8"
) )
// BUG(rsc): Mapping between XML elements and data structures is inherently flawed: // BUG(rsc): Mapping between XML elements and data structures is inherently flawed:
...@@ -331,24 +332,24 @@ Loop: ...@@ -331,24 +332,24 @@ Loop:
case StartElement: case StartElement:
// Sub-element. // Sub-element.
// Look up by tag name. // Look up by tag name.
// If that fails, fall back to mop-up field named "Any".
if sv != nil { if sv != nil {
k := fieldName(t.Name.Local) k := fieldName(t.Name.Local)
any := -1 match := func(s string) bool {
for i, n := 0, styp.NumField(); i < n; i++ { // check if the name matches ignoring case
f := styp.Field(i) if strings.ToLower(s) != strings.ToLower(k) {
if strings.ToLower(f.Name) == k { return false
if err := p.unmarshal(sv.FieldByIndex(f.Index), &t); err != nil {
return err
}
continue Loop
}
if any < 0 && f.Name == "Any" {
any = i
} }
// now check that it's public
c, _ := utf8.DecodeRuneInString(s)
return unicode.IsUpper(c)
}
f, found := styp.FieldByNameFunc(match)
if !found { // fall back to mop-up field named "Any"
f, found = styp.FieldByName("Any")
} }
if any >= 0 { if found {
if err := p.unmarshal(sv.FieldByIndex(styp.Field(any).Index), &t); err != nil { if err := p.unmarshal(sv.FieldByIndex(f.Index), &t); err != nil {
return err return err
} }
continue Loop continue Loop
......
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