Commit 3426b80d authored by Gustavo Niemeyer's avatar Gustavo Niemeyer Committed by Russ Cox

xml: support for > in tags

This introduces support for selecting which subelement
to unmarshal into a given struct field by providing a
nesting path separated by the > character.

R=rsc
CC=golang-dev
https://golang.org/cl/4066041
parent 12307008
...@@ -39,6 +39,7 @@ import ( ...@@ -39,6 +39,7 @@ import (
// Name string // Name string
// Phone string // Phone string
// Email []Email // Email []Email
// Groups []string "group>value"
// } // }
// //
// result := Result{Name: "name", Phone: "phone", Email: nil} // result := Result{Name: "name", Phone: "phone", Email: nil}
...@@ -53,6 +54,10 @@ import ( ...@@ -53,6 +54,10 @@ import (
// <addr>gre@work.com</addr> // <addr>gre@work.com</addr>
// </email> // </email>
// <name>Grace R. Emlin</name> // <name>Grace R. Emlin</name>
// <group>
// <value>Friends</value>
// <value>Squash</value>
// </group>
// <address>123 Main Street</address> // <address>123 Main Street</address>
// </result> // </result>
// //
...@@ -65,10 +70,13 @@ import ( ...@@ -65,10 +70,13 @@ import (
// Email{"home", "gre@example.com"}, // Email{"home", "gre@example.com"},
// Email{"work", "gre@work.com"}, // Email{"work", "gre@work.com"},
// }, // },
// []string{"Friends", "Squash"},
// } // }
// //
// Note that the field r.Phone has not been modified and // Note that the field r.Phone has not been modified and
// that the XML <address> element was discarded. // that the XML <address> element was discarded. Also, the field
// Groups was assigned considering the element path provided in the
// field tag.
// //
// Because Unmarshal uses the reflect package, it can only // Because Unmarshal uses the reflect package, it can only
// assign to upper case fields. Unmarshal uses a case-insensitive // assign to upper case fields. Unmarshal uses a case-insensitive
...@@ -97,6 +105,13 @@ import ( ...@@ -97,6 +105,13 @@ import (
// The struct field may have type []byte or string. // The struct field may have type []byte or string.
// If there is no such field, the character data is discarded. // If there is no such field, the character data is discarded.
// //
// * If the XML element contains a sub-element whose name matches
// the prefix of a struct field tag formatted as "a>b>c", unmarshal
// will descend into the XML structure looking for elements with the
// given names, and will map the innermost elements to that struct field.
// A struct field tag starting with ">" is equivalent to one starting
// with the field name followed by ">".
//
// * If the XML element contains a sub-element whose name // * If the XML element contains a sub-element whose name
// matches a struct field whose tag is neither "attr" nor "chardata", // matches a struct field whose tag is neither "attr" nor "chardata",
// Unmarshal maps the sub-element to that struct field. // Unmarshal maps the sub-element to that struct field.
...@@ -104,7 +119,7 @@ import ( ...@@ -104,7 +119,7 @@ import (
// maps the sub-element to that struct field. // maps the sub-element to that struct field.
// //
// Unmarshal maps an XML element to a string or []byte by saving the // Unmarshal maps an XML element to a string or []byte by saving the
// concatenation of that elements character data in the string or []byte. // concatenation of that element's character data in the string or []byte.
// //
// Unmarshal maps an XML element to a slice by extending the length // Unmarshal maps an XML element to a slice by extending the length
// of the slice and mapping the element to the newly created value. // of the slice and mapping the element to the newly created value.
...@@ -211,7 +226,9 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error { ...@@ -211,7 +226,9 @@ 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
) )
switch v := val.(type) { switch v := val.(type) {
default: default:
return os.ErrorString("unknown type " + v.Type().String()) return os.ErrorString("unknown type " + v.Type().String())
...@@ -330,6 +347,23 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error { ...@@ -330,6 +347,23 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error {
saveXMLIndex = p.savedOffset() saveXMLIndex = p.savedOffset()
} }
} }
default:
i := strings.Index(f.Tag, ">")
if i != -1 {
if fieldPaths == nil {
fieldPaths = make(map[string]fieldPath)
}
path := strings.ToLower(f.Tag)
if i == 0 {
path = strings.ToLower(f.Name) + path
}
if path[len(path)-1] == '>' {
path = path[:len(path)-1]
}
s := strings.Split(path, ">", -1)
fieldPaths[s[0]] = fieldPath{s[1:], f.Index}
}
} }
} }
} }
...@@ -351,10 +385,21 @@ Loop: ...@@ -351,10 +385,21 @@ Loop:
// Sub-element. // Sub-element.
// Look up by tag name. // Look up by tag name.
if sv != nil { if sv != nil {
k := fieldName(t.Name.Local) k := strings.ToLower(fieldName(t.Name.Local))
if fieldPaths != nil {
if fp, ok := fieldPaths[k]; ok {
val := sv.FieldByIndex(fp.Index)
if err := p.unmarshalPath(val, &t, fp.Path); err != nil {
return err
}
continue Loop
}
}
match := func(s string) bool { match := func(s string) bool {
// check if the name matches ignoring case // check if the name matches ignoring case
if strings.ToLower(s) != strings.ToLower(k) { if strings.ToLower(s) != k {
return false return false
} }
// now check that it's public // now check that it's public
...@@ -470,6 +515,41 @@ Loop: ...@@ -470,6 +515,41 @@ Loop:
return nil return nil
} }
type fieldPath struct {
Path []string
Index []int
}
// unmarshalPath finds the nested elements matching the
// provided path and calls unmarshal on the tip elements.
func (p *Parser) unmarshalPath(val reflect.Value, start *StartElement, path []string) os.Error {
if len(path) == 0 {
return p.unmarshal(val, start)
}
for {
tok, err := p.Token()
if err != nil {
return err
}
switch t := tok.(type) {
case StartElement:
k := fieldName(t.Name.Local)
if k == path[0] {
if err := p.unmarshalPath(val, &t, path[1:]); err != nil {
return err
}
continue
}
if err := p.Skip(); err != nil {
return err
}
case EndElement:
return nil
}
}
panic("unreachable")
}
// Have already read a start element. // Have already read a start element.
// Read tokens until we find the end element. // Read tokens until we find the end element.
// Token is taking care of making sure the // Token is taking care of making sure the
......
...@@ -230,3 +230,71 @@ func TestFieldName(t *testing.T) { ...@@ -230,3 +230,71 @@ func TestFieldName(t *testing.T) {
} }
} }
} }
const pathTestString = `
<result>
<before>1</before>
<items>
<item>
<value>A</value>
</item>
<skip>
<value>B</value>
</skip>
<Item>
<Value>C</Value>
<Value>D</Value>
</Item>
</items>
<after>2</after>
</result>
`
type PathTestItem struct {
Value string
}
type PathTestA struct {
Items []PathTestItem ">item"
Before, After string
}
type PathTestB struct {
Other []PathTestItem "items>Item"
Before, After string
}
type PathTestC struct {
Values []string "items>item>value"
Before, After string
}
type PathTestSet struct {
Item []PathTestItem
}
type PathTestD struct {
Other PathTestSet "items>"
Before, After string
}
var pathTests = []interface{}{
&PathTestA{Items: []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"},
&PathTestD{Other: PathTestSet{Item: []PathTestItem{{"A"}, {"D"}}}, Before: "1", After: "2"},
}
func TestUnmarshalPaths(t *testing.T) {
for _, pt := range pathTests {
p := reflect.MakeZero(reflect.NewValue(pt).Type()).(*reflect.PtrValue)
p.PointTo(reflect.MakeZero(p.Type().(*reflect.PtrType).Elem()))
v := p.Interface()
if err := Unmarshal(StringReader(pathTestString), v); err != nil {
t.Fatalf("Unmarshal: %s", err)
}
if !reflect.DeepEqual(v, pt) {
t.Fatalf("have %#v\nwant %#v", v, pt)
}
}
}
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