mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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
This commit is contained in:
parent
12307008e9
commit
3426b80dd9
2 changed files with 152 additions and 4 deletions
|
|
@ -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 (
|
||||||
// <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 (
|
||||||
// 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 (
|
||||||
// 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 (
|
||||||
// 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 {
|
||||||
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 {
|
||||||
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:
|
||||||
// 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:
|
||||||
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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue