blob: 487994ac6e53872e87cde7ca908d9c0977d77699 [file] [log] [blame]
// Copyright 2009 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 (
"bytes"
"fmt"
"io"
"os"
"reflect"
"strconv"
"strings"
"unicode"
"utf8"
)
// BUG(rsc): Mapping between XML elements and data structures is inherently flawed:
// an XML element is an order-dependent collection of anonymous
// values, while a data structure is an order-independent collection
// of named values.
// See package json for a textual representation more suitable
// to data structures.
// Unmarshal parses an XML element from r and uses the
// reflect library to fill in an arbitrary struct, slice, or string
// pointed at by val. Well-formed data that does not fit
// into val is discarded.
//
// For example, given these definitions:
//
// type Email struct {
// Where string "attr"
// Addr string
// }
//
// type Result struct {
// XMLName xml.Name "result"
// Name string
// Phone string
// Email []Email
// Groups []string "group>value"
// }
//
// result := Result{Name: "name", Phone: "phone", Email: nil}
//
// unmarshalling the XML input
//
// <result>
// <email where="home">
// <addr>gre@example.com</addr>
// </email>
// <email where='work'>
// <addr>gre@work.com</addr>
// </email>
// <name>Grace R. Emlin</name>
// <group>
// <value>Friends</value>
// <value>Squash</value>
// </group>
// <address>123 Main Street</address>
// </result>
//
// via Unmarshal(r, &result) is equivalent to assigning
//
// r = Result{xml.Name{"", "result"},
// "Grace R. Emlin", // name
// "phone", // no phone given
// []Email{
// Email{"home", "gre@example.com"},
// Email{"work", "gre@work.com"},
// },
// []string{"Friends", "Squash"},
// }
//
// Note that the field r.Phone has not been modified and
// 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
// assign to upper case fields. Unmarshal uses a case-insensitive
// comparison to match XML element names to struct field names.
//
// Unmarshal maps an XML element to a struct using the following rules:
//
// * If the struct has a field of type []byte or string with tag "innerxml",
// Unmarshal accumulates the raw XML nested inside the element
// in that field. The rest of the rules still apply.
//
// * If the struct has a field named XMLName of type xml.Name,
// Unmarshal records the element name in that field.
//
// * If the XMLName field has an associated tag string of the form
// "tag" or "namespace-URL tag", the XML element must have
// the given tag (and, optionally, name space) or else Unmarshal
// returns an error.
//
// * If the XML element has an attribute whose name matches a
// struct field of type string with tag "attr", Unmarshal records
// the attribute value in that field.
//
// * If the XML element contains character data, that data is
// accumulated in the first struct field that has tag "chardata".
// The struct field may have type []byte or string.
// 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
// matches a struct field whose tag is neither "attr" nor "chardata",
// Unmarshal maps the sub-element to that struct field.
// Otherwise, if the struct has a field named Any, unmarshal
// maps the sub-element to that struct field.
//
// Unmarshal maps an XML element to a string or []byte by saving the
// concatenation of that element's character data in the string or []byte.
//
// Unmarshal maps an XML element to a slice by extending the length
// of the slice and mapping the element to the newly created value.
//
// Unmarshal maps an XML element to a bool by setting it to the boolean
// value represented by the string.
//
// Unmarshal maps an XML element to an integer or floating-point
// field by setting the field to the result of interpreting the string
// value in decimal. There is no check for overflow.
//
// Unmarshal maps an XML element to an xml.Name by recording the
// element name.
//
// Unmarshal maps an XML element to a pointer by setting the pointer
// to a freshly allocated value and then mapping the element to that value.
//
func Unmarshal(r io.Reader, val interface{}) os.Error {
v, ok := reflect.NewValue(val).(*reflect.PtrValue)
if !ok {
return os.NewError("non-pointer passed to Unmarshal")
}
p := NewParser(r)
elem := v.Elem()
err := p.unmarshal(elem, nil)
if err != nil {
return err
}
return nil
}
// An UnmarshalError represents an error in the unmarshalling process.
type UnmarshalError string
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
// except that it can be passed a pointer to the initial start element,
// useful when a client reads some raw XML tokens itself
// but also defers to Unmarshal for some elements.
// Passing a nil start element indicates that Unmarshal should
// read the token stream to find the start element.
func (p *Parser) Unmarshal(val interface{}, start *StartElement) os.Error {
v, ok := reflect.NewValue(val).(*reflect.PtrValue)
if !ok {
return os.NewError("non-pointer passed to Unmarshal")
}
return p.unmarshal(v.Elem(), start)
}
// fieldName strips invalid characters from an XML name
// to create a valid Go struct name. It also converts the
// name to lower case letters.
func fieldName(original string) string {
var i int
//remove leading underscores
for i = 0; i < len(original) && original[i] == '_'; i++ {
}
return strings.Map(
func(x int) int {
if x == '_' || unicode.IsDigit(x) || unicode.IsLetter(x) {
return unicode.ToLower(x)
}
return -1
},
original[i:])
}
// Unmarshal a single XML element into val.
func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error {
// Find start element if we need it.
if start == nil {
for {
tok, err := p.Token()
if err != nil {
return err
}
if t, ok := tok.(StartElement); ok {
start = &t
break
}
}
}
if pv, ok := val.(*reflect.PtrValue); ok {
if pv.Get() == 0 {
zv := reflect.MakeZero(pv.Type().(*reflect.PtrType).Elem())
pv.PointTo(zv)
val = zv
} else {
val = pv.Elem()
}
}
var (
data []byte
saveData reflect.Value
comment []byte
saveComment reflect.Value
saveXML reflect.Value
saveXMLIndex int
saveXMLData []byte
sv *reflect.StructValue
styp *reflect.StructType
fieldPaths map[string]pathInfo
)
switch v := val.(type) {
default:
return os.NewError("unknown type " + v.Type().String())
case *reflect.SliceValue:
typ := v.Type().(*reflect.SliceType)
if typ.Elem().Kind() == reflect.Uint8 {
// []byte
saveData = v
break
}
// Slice of element values.
// Grow slice.
n := v.Len()
if n >= v.Cap() {
ncap := 2 * n
if ncap < 4 {
ncap = 4
}
new := reflect.MakeSlice(typ, n, ncap)
reflect.Copy(new, v)
v.Set(new)
}
v.SetLen(n + 1)
// Recur to read element into slice.
if err := p.unmarshal(v.Elem(n), start); err != nil {
v.SetLen(n)
return err
}
return nil
case *reflect.BoolValue, *reflect.FloatValue, *reflect.IntValue, *reflect.UintValue, *reflect.StringValue:
saveData = v
case *reflect.StructValue:
if _, ok := v.Interface().(Name); ok {
v.Set(reflect.NewValue(start.Name).(*reflect.StructValue))
break
}
sv = v
typ := sv.Type().(*reflect.StructType)
styp = typ
// Assign name.
if f, ok := typ.FieldByName("XMLName"); ok {
// Validate element name.
if f.Tag != "" {
tag := f.Tag
ns := ""
i := strings.LastIndex(tag, " ")
if i >= 0 {
ns, tag = tag[0:i], tag[i+1:]
}
if tag != start.Name.Local {
return UnmarshalError("expected element type <" + tag + "> but have <" + start.Name.Local + ">")
}
if ns != "" && ns != start.Name.Space {
e := "expected element <" + tag + "> in name space " + ns + " but have "
if start.Name.Space == "" {
e += "no name space"
} else {
e += start.Name.Space
}
return UnmarshalError(e)
}
}
// Save
v := sv.FieldByIndex(f.Index)
if _, ok := v.Interface().(Name); !ok {
return UnmarshalError(sv.Type().String() + " field XMLName does not have type xml.Name")
}
v.(*reflect.StructValue).Set(reflect.NewValue(start.Name).(*reflect.StructValue))
}
// Assign attributes.
// Also, determine whether we need to save character data or comments.
for i, n := 0, typ.NumField(); i < n; i++ {
f := typ.Field(i)
switch f.Tag {
case "attr":
strv, ok := sv.FieldByIndex(f.Index).(*reflect.StringValue)
if !ok {
return UnmarshalError(sv.Type().String() + " field " + f.Name + " has attr tag but is not type string")
}
// Look for attribute.
val := ""
k := strings.ToLower(f.Name)
for _, a := range start.Attr {
if fieldName(a.Name.Local) == k {
val = a.Value
break
}
}
strv.Set(val)
case "comment":
if saveComment == nil {
saveComment = sv.FieldByIndex(f.Index)
}
case "chardata":
if saveData == nil {
saveData = sv.FieldByIndex(f.Index)
}
case "innerxml":
if saveXML == nil {
saveXML = sv.FieldByIndex(f.Index)
if p.saved == nil {
saveXMLIndex = 0
p.saved = new(bytes.Buffer)
} else {
saveXMLIndex = p.savedOffset()
}
}
default:
if strings.Contains(f.Tag, ">") {
if fieldPaths == nil {
fieldPaths = make(map[string]pathInfo)
}
path := strings.ToLower(f.Tag)
if strings.HasPrefix(f.Tag, ">") {
path = strings.ToLower(f.Name) + path
}
if strings.HasSuffix(f.Tag, ">") {
path = path[:len(path)-1]
}
err := addFieldPath(sv, fieldPaths, path, f.Index)
if err != nil {
return err
}
}
}
}
}
// Find end element.
// Process sub-elements along the way.
Loop:
for {
var savedOffset int
if saveXML != nil {
savedOffset = p.savedOffset()
}
tok, err := p.Token()
if err != nil {
return err
}
switch t := tok.(type) {
case StartElement:
// Sub-element.
// Look up by tag name.
if sv != nil {
k := fieldName(t.Name.Local)
if fieldPaths != nil {
if _, found := fieldPaths[k]; found {
if err := p.unmarshalPaths(sv, fieldPaths, k, &t); err != nil {
return err
}
continue Loop
}
}
match := func(s string) bool {
// check if the name matches ignoring case
if strings.ToLower(s) != k {
return false
}
// 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 found {
if err := p.unmarshal(sv.FieldByIndex(f.Index), &t); err != nil {
return err
}
continue Loop
}
}
// Not saving sub-element but still have to skip over it.
if err := p.Skip(); err != nil {
return err
}
case EndElement:
if saveXML != nil {
saveXMLData = p.saved.Bytes()[saveXMLIndex:savedOffset]
if saveXMLIndex == 0 {
p.saved = nil
}
}
break Loop
case CharData:
if saveData != nil {
data = append(data, t...)
}
case Comment:
if saveComment != nil {
comment = append(comment, t...)
}
}
}
var err os.Error
// Helper functions for integer and unsigned integer conversions
var itmp int64
getInt64 := func() bool {
itmp, err = strconv.Atoi64(string(data))
// TODO: should check sizes
return err == nil
}
var utmp uint64
getUint64 := func() bool {
utmp, err = strconv.Atoui64(string(data))
// TODO: check for overflow?
return err == nil
}
var ftmp float64
getFloat64 := func() bool {
ftmp, err = strconv.Atof64(string(data))
// TODO: check for overflow?
return err == nil
}
// Save accumulated data and comments
switch t := saveData.(type) {
case nil:
// Probably a comment, handled below
default:
return os.NewError("cannot happen: unknown type " + t.Type().String())
case *reflect.IntValue:
if !getInt64() {
return err
}
t.Set(itmp)
case *reflect.UintValue:
if !getUint64() {
return err
}
t.Set(utmp)
case *reflect.FloatValue:
if !getFloat64() {
return err
}
t.Set(ftmp)
case *reflect.BoolValue:
value, err := strconv.Atob(strings.TrimSpace(string(data)))
if err != nil {
return err
}
t.Set(value)
case *reflect.StringValue:
t.Set(string(data))
case *reflect.SliceValue:
t.Set(reflect.NewValue(data).(*reflect.SliceValue))
}
switch t := saveComment.(type) {
case *reflect.StringValue:
t.Set(string(comment))
case *reflect.SliceValue:
t.Set(reflect.NewValue(comment).(*reflect.SliceValue))
}
switch t := saveXML.(type) {
case *reflect.StringValue:
t.Set(string(saveXMLData))
case *reflect.SliceValue:
t.Set(reflect.NewValue(saveXMLData).(*reflect.SliceValue))
}
return nil
}
type pathInfo struct {
fieldIdx []int
complete bool
}
// addFieldPath takes an element path such as "a>b>c" and fills the
// paths map with all paths leading to it ("a", "a>b", and "a>b>c").
// It is okay for paths to share a common, shorter prefix but not ok
// for one path to itself be a prefix of another.
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 {
tok, err := p.Token()
if err != nil {
return err
}
switch t := tok.(type) {
case StartElement:
k := path + ">" + fieldName(t.Name.Local)
if _, found := paths[k]; found {
if err := p.unmarshalPaths(sv, paths, k, &t); 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.
// Read tokens until we find the end element.
// Token is taking care of making sure the
// end element matches the start element we saw.
func (p *Parser) Skip() os.Error {
for {
tok, err := p.Token()
if err != nil {
return err
}
switch t := tok.(type) {
case StartElement:
if err := p.Skip(); err != nil {
return err
}
case EndElement:
return nil
}
}
panic("unreachable")
}