Skip to content

Commit 57e7641

Browse files
Raif S. Naffahrsc
authored andcommitted
reflect: add FieldByNameFunc
xml: add support for XML marshalling embedded structs. R=rsc CC=golang-dev https://golang.org/cl/837042
1 parent 13f81fe commit 57e7641

4 files changed

Lines changed: 160 additions & 19 deletions

File tree

src/pkg/reflect/type.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ func (t *StructType) FieldByIndex(index []int) (f StructField) {
507507

508508
const inf = 1 << 30 // infinity - no struct has that many nesting levels
509509

510-
func (t *StructType) fieldByName(name string, mark map[*StructType]bool, depth int) (ff StructField, fd int) {
510+
func (t *StructType) fieldByNameFunc(match func(string) bool, mark map[*StructType]bool, depth int) (ff StructField, fd int) {
511511
fd = inf // field depth
512512

513513
if mark[t] {
@@ -522,7 +522,7 @@ L: for i, _ := range t.fields {
522522
f := t.Field(i)
523523
d := inf
524524
switch {
525-
case f.Name == name:
525+
case match(f.Name):
526526
// Matching top-level field.
527527
d = depth
528528
case f.Anonymous:
@@ -531,13 +531,13 @@ L: for i, _ := range t.fields {
531531
ft = pt.Elem()
532532
}
533533
switch {
534-
case ft.Name() == name:
534+
case match(ft.Name()):
535535
// Matching anonymous top-level field.
536536
d = depth
537537
case fd > depth:
538538
// No top-level field yet; look inside nested structs.
539539
if st, ok := ft.(*StructType); ok {
540-
f, d = st.fieldByName(name, mark, depth+1)
540+
f, d = st.fieldByNameFunc(match, mark, depth+1)
541541
}
542542
}
543543
}
@@ -576,7 +576,13 @@ L: for i, _ := range t.fields {
576576
// FieldByName returns the struct field with the given name
577577
// and a boolean to indicate if the field was found.
578578
func (t *StructType) FieldByName(name string) (f StructField, present bool) {
579-
if ff, fd := t.fieldByName(name, make(map[*StructType]bool), 0); fd < inf {
579+
return t.FieldByNameFunc(func(s string) bool { return s == name })
580+
}
581+
582+
// FieldByNameFunc returns the struct field with a name that satisfies the
583+
// match function and a boolean to indicate if the field was found.
584+
func (t *StructType) FieldByNameFunc(match func(string) bool) (f StructField, present bool) {
585+
if ff, fd := t.fieldByNameFunc(match, make(map[*StructType]bool), 0); fd < inf {
580586
ff.Index = ff.Index[0 : fd+1]
581587
f, present = ff, true
582588
}

src/pkg/reflect/value.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,6 +1251,16 @@ func (t *StructValue) FieldByName(name string) Value {
12511251
return nil
12521252
}
12531253

1254+
// FieldByNameFunc returns the struct field with a name that satisfies the
1255+
// match function.
1256+
// The result is nil if no field was found.
1257+
func (t *StructValue) FieldByNameFunc(match func(string) bool) Value {
1258+
if f, ok := t.Type().(*StructType).FieldByNameFunc(match); ok {
1259+
return t.FieldByIndex(f.Index)
1260+
}
1261+
return nil
1262+
}
1263+
12541264
// NumField returns the number of fields in the struct.
12551265
func (v *StructValue) NumField() int { return v.typ.(*StructType).NumField() }
12561266

src/pkg/xml/embed_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright 2010 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package xml
6+
7+
import "testing"
8+
9+
type C struct {
10+
Name string
11+
Open bool
12+
}
13+
14+
type A struct {
15+
XMLName Name "http://domain a"
16+
C
17+
B B
18+
FieldA string
19+
}
20+
21+
type B struct {
22+
XMLName Name "b"
23+
C
24+
FieldB string
25+
}
26+
27+
const _1a = `
28+
<?xml version="1.0" encoding="UTF-8"?>
29+
<a xmlns="http://domain">
30+
<name>KmlFile</name>
31+
<open>1</open>
32+
<b>
33+
<name>Absolute</name>
34+
<open>0</open>
35+
<fieldb>bar</fieldb>
36+
</b>
37+
<fielda>foo</fielda>
38+
</a>
39+
`
40+
41+
// Tests that embedded structs are marshalled.
42+
func TestEmbedded1(t *testing.T) {
43+
var a A
44+
if e := Unmarshal(StringReader(_1a), &a); e != nil {
45+
t.Fatalf("Unmarshal: %s", e)
46+
}
47+
if a.FieldA != "foo" {
48+
t.Fatalf("Unmarshal: expected 'foo' but found '%s'", a.FieldA)
49+
}
50+
if a.Name != "KmlFile" {
51+
t.Fatalf("Unmarshal: expected 'KmlFile' but found '%s'", a.Name)
52+
}
53+
if !a.Open {
54+
t.Fatal("Unmarshal: expected 'true' but found otherwise")
55+
}
56+
if a.B.FieldB != "bar" {
57+
t.Fatalf("Unmarshal: expected 'bar' but found '%s'", a.B.FieldB)
58+
}
59+
if a.B.Name != "Absolute" {
60+
t.Fatalf("Unmarshal: expected 'Absolute' but found '%s'", a.B.Name)
61+
}
62+
if a.B.Open {
63+
t.Fatal("Unmarshal: expected 'false' but found otherwise")
64+
}
65+
}
66+
67+
type A2 struct {
68+
XMLName Name "http://domain a"
69+
XY string
70+
Xy string
71+
}
72+
73+
const _2a = `
74+
<?xml version="1.0" encoding="UTF-8"?>
75+
<a xmlns="http://domain">
76+
<xy>foo</xy>
77+
</a>
78+
`
79+
80+
// Tests that conflicting field names get excluded.
81+
func TestEmbedded2(t *testing.T) {
82+
var a A2
83+
if e := Unmarshal(StringReader(_2a), &a); e != nil {
84+
t.Fatalf("Unmarshal: %s", e)
85+
}
86+
if a.XY != "" {
87+
t.Fatalf("Unmarshal: expected empty string but found '%s'", a.XY)
88+
}
89+
if a.Xy != "" {
90+
t.Fatalf("Unmarshal: expected empty string but found '%s'", a.Xy)
91+
}
92+
}
93+
94+
type A3 struct {
95+
XMLName Name "http://domain a"
96+
xy string
97+
}
98+
99+
// Tests that private fields are not set.
100+
func TestEmbedded3(t *testing.T) {
101+
var a A3
102+
if e := Unmarshal(StringReader(_2a), &a); e != nil {
103+
t.Fatalf("Unmarshal: %s", e)
104+
}
105+
if a.xy != "" {
106+
t.Fatalf("Unmarshal: expected empty string but found '%s'", a.xy)
107+
}
108+
}
109+
110+
type A4 struct {
111+
XMLName Name "http://domain a"
112+
Any string
113+
}
114+
115+
// Tests that private fields are not set.
116+
func TestEmbedded4(t *testing.T) {
117+
var a A4
118+
if e := Unmarshal(StringReader(_2a), &a); e != nil {
119+
t.Fatalf("Unmarshal: %s", e)
120+
}
121+
if a.Any != "foo" {
122+
t.Fatalf("Unmarshal: expected 'foo' but found '%s'", a.Any)
123+
}
124+
}

src/pkg/xml/read.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strconv"
1313
"strings"
1414
"unicode"
15+
"utf8"
1516
)
1617

1718
// BUG(rsc): Mapping between XML elements and data structures is inherently flawed:
@@ -331,24 +332,24 @@ Loop:
331332
case StartElement:
332333
// Sub-element.
333334
// Look up by tag name.
334-
// If that fails, fall back to mop-up field named "Any".
335335
if sv != nil {
336336
k := fieldName(t.Name.Local)
337-
any := -1
338-
for i, n := 0, styp.NumField(); i < n; i++ {
339-
f := styp.Field(i)
340-
if strings.ToLower(f.Name) == k {
341-
if err := p.unmarshal(sv.FieldByIndex(f.Index), &t); err != nil {
342-
return err
343-
}
344-
continue Loop
345-
}
346-
if any < 0 && f.Name == "Any" {
347-
any = i
337+
match := func(s string) bool {
338+
// check if the name matches ignoring case
339+
if strings.ToLower(s) != strings.ToLower(k) {
340+
return false
348341
}
342+
// now check that it's public
343+
c, _ := utf8.DecodeRuneInString(s)
344+
return unicode.IsUpper(c)
345+
}
346+
347+
f, found := styp.FieldByNameFunc(match)
348+
if !found { // fall back to mop-up field named "Any"
349+
f, found = styp.FieldByName("Any")
349350
}
350-
if any >= 0 {
351-
if err := p.unmarshal(sv.FieldByIndex(styp.Field(any).Index), &t); err != nil {
351+
if found {
352+
if err := p.unmarshal(sv.FieldByIndex(f.Index), &t); err != nil {
352353
return err
353354
}
354355
continue Loop

0 commit comments

Comments
 (0)