Commit c52ad234 authored by Gustavo Niemeyer's avatar Gustavo Niemeyer Committed by Russ Cox

xml: handle tag paths through the same element

With the current implementation, xml unmarshalling
will silently fail to unmarshal any paths passing
through the same element, such as:

type T struct {
	A string "dummy>a"
	B string "dummy>b"
}

This change tweaks the algorithm so that this works
correctly.

Also, using paths that would cause the same element to
unmarshal twice will error out ahead of time explaining
the problem, rather than silently misbehaving.

R=rsc
CC=golang-dev
https://golang.org/cl/4082041
parent b99a6d46
...@@ -6,6 +6,7 @@ package xml ...@@ -6,6 +6,7 @@ package xml
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"os" "os"
"reflect" "reflect"
...@@ -156,6 +157,18 @@ type UnmarshalError string ...@@ -156,6 +157,18 @@ type UnmarshalError string
func (e UnmarshalError) String() string { return string(e) } func (e UnmarshalError) String() string { return string(e) }
// A TagPathError represents an error in the unmarshalling process
// caused by the use of field tags with conflicting paths.
type TagPathError struct {
Struct reflect.Type
Field1, Tag1 string
Field2, Tag2 string
}
func (e *TagPathError) String() string {
return fmt.Sprintf("%s field %q with tag %q conflicts with field %q with tag %q", e.Struct, e.Field1, e.Tag1, e.Field2, e.Tag2)
}
// The Parser's Unmarshal method is like xml.Unmarshal // The Parser's Unmarshal method is like xml.Unmarshal
// except that it can be passed a pointer to the initial start element, // except that it can be passed a pointer to the initial start element,
// useful when a client reads some raw XML tokens itself // useful when a client reads some raw XML tokens itself
...@@ -226,7 +239,7 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error { ...@@ -226,7 +239,7 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error {
saveXMLData []byte saveXMLData []byte
sv *reflect.StructValue sv *reflect.StructValue
styp *reflect.StructType styp *reflect.StructType
fieldPaths map[string]fieldPath fieldPaths map[string]pathInfo
) )
switch v := val.(type) { switch v := val.(type) {
...@@ -349,20 +362,21 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error { ...@@ -349,20 +362,21 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error {
} }
default: default:
i := strings.Index(f.Tag, ">") if strings.Contains(f.Tag, ">") {
if i != -1 {
if fieldPaths == nil { if fieldPaths == nil {
fieldPaths = make(map[string]fieldPath) fieldPaths = make(map[string]pathInfo)
} }
path := strings.ToLower(f.Tag) path := strings.ToLower(f.Tag)
if i == 0 { if strings.HasPrefix(f.Tag, ">") {
path = strings.ToLower(f.Name) + path path = strings.ToLower(f.Name) + path
} }
if path[len(path)-1] == '>' { if strings.HasSuffix(f.Tag, ">") {
path = path[:len(path)-1] path = path[:len(path)-1]
} }
s := strings.Split(path, ">", -1) err := addFieldPath(sv, fieldPaths, path, f.Index)
fieldPaths[s[0]] = fieldPath{s[1:], f.Index} if err != nil {
return err
}
} }
} }
} }
...@@ -385,12 +399,11 @@ Loop: ...@@ -385,12 +399,11 @@ Loop:
// Sub-element. // Sub-element.
// Look up by tag name. // Look up by tag name.
if sv != nil { if sv != nil {
k := strings.ToLower(fieldName(t.Name.Local)) k := fieldName(t.Name.Local)
if fieldPaths != nil { if fieldPaths != nil {
if fp, ok := fieldPaths[k]; ok { if _, found := fieldPaths[k]; found {
val := sv.FieldByIndex(fp.Index) if err := p.unmarshalPaths(sv, fieldPaths, k, &t); err != nil {
if err := p.unmarshalPath(val, &t, fp.Path); err != nil {
return err return err
} }
continue Loop continue Loop
...@@ -515,16 +528,50 @@ Loop: ...@@ -515,16 +528,50 @@ Loop:
return nil return nil
} }
type fieldPath struct { type pathInfo struct {
Path []string fieldIdx []int
Index []int complete bool
} }
// unmarshalPath finds the nested elements matching the // addFieldPath takes an element path such as "a>b>c" and fills the
// provided path and calls unmarshal on the tip elements. // paths map with all paths leading to it ("a", "a>b", and "a>b>c").
func (p *Parser) unmarshalPath(val reflect.Value, start *StartElement, path []string) os.Error { // It is okay for paths to share a common, shorter prefix but not ok
if len(path) == 0 { // for one path to itself be a prefix of another.
return p.unmarshal(val, start) func addFieldPath(sv *reflect.StructValue, paths map[string]pathInfo, path string, fieldIdx []int) os.Error {
if info, found := paths[path]; found {
return tagError(sv, info.fieldIdx, fieldIdx)
}
paths[path] = pathInfo{fieldIdx, true}
for {
i := strings.LastIndex(path, ">")
if i < 0 {
break
}
path = path[:i]
if info, found := paths[path]; found {
if info.complete {
return tagError(sv, info.fieldIdx, fieldIdx)
}
} else {
paths[path] = pathInfo{fieldIdx, false}
}
}
return nil
}
func tagError(sv *reflect.StructValue, idx1 []int, idx2 []int) os.Error {
t := sv.Type().(*reflect.StructType)
f1 := t.FieldByIndex(idx1)
f2 := t.FieldByIndex(idx2)
return &TagPathError{t, f1.Name, f1.Tag, f2.Name, f2.Tag}
}
// unmarshalPaths walks down an XML structure looking for
// wanted paths, and calls unmarshal on them.
func (p *Parser) unmarshalPaths(sv *reflect.StructValue, paths map[string]pathInfo, path string, start *StartElement) os.Error {
if info, _ := paths[path]; info.complete {
return p.unmarshal(sv.FieldByIndex(info.fieldIdx), start)
} }
for { for {
tok, err := p.Token() tok, err := p.Token()
...@@ -533,9 +580,9 @@ func (p *Parser) unmarshalPath(val reflect.Value, start *StartElement, path []st ...@@ -533,9 +580,9 @@ func (p *Parser) unmarshalPath(val reflect.Value, start *StartElement, path []st
} }
switch t := tok.(type) { switch t := tok.(type) {
case StartElement: case StartElement:
k := fieldName(t.Name.Local) k := path + ">" + fieldName(t.Name.Local)
if k == path[0] { if _, found := paths[k]; found {
if err := p.unmarshalPath(val, &t, path[1:]); err != nil { if err := p.unmarshalPaths(sv, paths, k, &t); err != nil {
return err return err
} }
continue continue
......
...@@ -235,16 +235,16 @@ const pathTestString = ` ...@@ -235,16 +235,16 @@ const pathTestString = `
<result> <result>
<before>1</before> <before>1</before>
<items> <items>
<item> <item1>
<value>A</value> <value>A</value>
</item> </item1>
<skip> <item2>
<value>B</value> <value>B</value>
</skip> </item2>
<Item> <Item1>
<Value>C</Value> <Value>C</Value>
<Value>D</Value> <Value>D</Value>
</Item> </Item1>
</items> </items>
<after>2</after> <after>2</after>
</result> </result>
...@@ -255,22 +255,23 @@ type PathTestItem struct { ...@@ -255,22 +255,23 @@ type PathTestItem struct {
} }
type PathTestA struct { type PathTestA struct {
Items []PathTestItem ">item" Items []PathTestItem ">item1"
Before, After string Before, After string
} }
type PathTestB struct { type PathTestB struct {
Other []PathTestItem "items>Item" Other []PathTestItem "items>Item1"
Before, After string Before, After string
} }
type PathTestC struct { type PathTestC struct {
Values []string "items>item>value" Values1 []string "items>item1>value"
Values2 []string "items>item2>value"
Before, After string Before, After string
} }
type PathTestSet struct { type PathTestSet struct {
Item []PathTestItem Item1 []PathTestItem
} }
type PathTestD struct { type PathTestD struct {
...@@ -281,8 +282,8 @@ type PathTestD struct { ...@@ -281,8 +282,8 @@ type PathTestD struct {
var pathTests = []interface{}{ var pathTests = []interface{}{
&PathTestA{Items: []PathTestItem{{"A"}, {"D"}}, Before: "1", After: "2"}, &PathTestA{Items: []PathTestItem{{"A"}, {"D"}}, Before: "1", After: "2"},
&PathTestB{Other: []PathTestItem{{"A"}, {"D"}}, Before: "1", After: "2"}, &PathTestB{Other: []PathTestItem{{"A"}, {"D"}}, Before: "1", After: "2"},
&PathTestC{Values: []string{"A", "C", "D"}, Before: "1", After: "2"}, &PathTestC{Values1: []string{"A", "C", "D"}, Values2: []string{"B"}, Before: "1", After: "2"},
&PathTestD{Other: PathTestSet{Item: []PathTestItem{{"A"}, {"D"}}}, Before: "1", After: "2"}, &PathTestD{Other: PathTestSet{Item1: []PathTestItem{{"A"}, {"D"}}}, Before: "1", After: "2"},
} }
func TestUnmarshalPaths(t *testing.T) { func TestUnmarshalPaths(t *testing.T) {
...@@ -298,3 +299,31 @@ func TestUnmarshalPaths(t *testing.T) { ...@@ -298,3 +299,31 @@ func TestUnmarshalPaths(t *testing.T) {
} }
} }
} }
type BadPathTestA struct {
First string "items>item1"
Other string "items>item2"
Second string "items>"
}
type BadPathTestB struct {
Other string "items>item2>value"
First string "items>item1"
Second string "items>item1>value"
}
var badPathTests = []struct {
v, e interface{}
}{
{&BadPathTestA{}, &TagPathError{reflect.Typeof(BadPathTestA{}), "First", "items>item1", "Second", "items>"}},
{&BadPathTestB{}, &TagPathError{reflect.Typeof(BadPathTestB{}), "First", "items>item1", "Second", "items>item1>value"}},
}
func TestUnmarshalBadPaths(t *testing.T) {
for _, tt := range badPathTests {
err := Unmarshal(StringReader(pathTestString), tt.v)
if !reflect.DeepEqual(err, tt.e) {
t.Fatalf("Unmarshal with %#v didn't fail properly: %#v", tt.v, err)
}
}
}
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