| // Copyright 2011 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 ( | |
| "fmt" | |
| "reflect" | |
| "strings" | |
| "sync" | |
| ) | |
| // typeInfo holds details for the xml representation of a type. | |
| type typeInfo struct { | |
| xmlname *fieldInfo | |
| fields []fieldInfo | |
| } | |
| // fieldInfo holds details for the xml representation of a single field. | |
| type fieldInfo struct { | |
| idx []int | |
| name string | |
| xmlns string | |
| flags fieldFlags | |
| parents []string | |
| } | |
| type fieldFlags int | |
| const ( | |
| fElement fieldFlags = 1 << iota | |
| fAttr | |
| fCharData | |
| fInnerXml | |
| fComment | |
| fAny | |
| fOmitEmpty | |
| fMode = fElement | fAttr | fCharData | fInnerXml | fComment | fAny | |
| ) | |
| var tinfoMap = make(map[reflect.Type]*typeInfo) | |
| var tinfoLock sync.RWMutex | |
| var nameType = reflect.TypeOf(Name{}) | |
| // getTypeInfo returns the typeInfo structure with details necessary | |
| // for marshalling and unmarshalling typ. | |
| func getTypeInfo(typ reflect.Type) (*typeInfo, error) { | |
| tinfoLock.RLock() | |
| tinfo, ok := tinfoMap[typ] | |
| tinfoLock.RUnlock() | |
| if ok { | |
| return tinfo, nil | |
| } | |
| tinfo = &typeInfo{} | |
| if typ.Kind() == reflect.Struct && typ != nameType { | |
| n := typ.NumField() | |
| for i := 0; i < n; i++ { | |
| f := typ.Field(i) | |
| if f.PkgPath != "" || f.Tag.Get("xml") == "-" { | |
| continue // Private field | |
| } | |
| // For embedded structs, embed its fields. | |
| if f.Anonymous { | |
| t := f.Type | |
| if t.Kind() == reflect.Ptr { | |
| t = t.Elem() | |
| } | |
| if t.Kind() == reflect.Struct { | |
| inner, err := getTypeInfo(t) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if tinfo.xmlname == nil { | |
| tinfo.xmlname = inner.xmlname | |
| } | |
| for _, finfo := range inner.fields { | |
| finfo.idx = append([]int{i}, finfo.idx...) | |
| if err := addFieldInfo(typ, tinfo, &finfo); err != nil { | |
| return nil, err | |
| } | |
| } | |
| continue | |
| } | |
| } | |
| finfo, err := structFieldInfo(typ, &f) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if f.Name == "XMLName" { | |
| tinfo.xmlname = finfo | |
| continue | |
| } | |
| // Add the field if it doesn't conflict with other fields. | |
| if err := addFieldInfo(typ, tinfo, finfo); err != nil { | |
| return nil, err | |
| } | |
| } | |
| } | |
| tinfoLock.Lock() | |
| tinfoMap[typ] = tinfo | |
| tinfoLock.Unlock() | |
| return tinfo, nil | |
| } | |
| // structFieldInfo builds and returns a fieldInfo for f. | |
| func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) { | |
| finfo := &fieldInfo{idx: f.Index} | |
| // Split the tag from the xml namespace if necessary. | |
| tag := f.Tag.Get("xml") | |
| if i := strings.Index(tag, " "); i >= 0 { | |
| finfo.xmlns, tag = tag[:i], tag[i+1:] | |
| } | |
| // Parse flags. | |
| tokens := strings.Split(tag, ",") | |
| if len(tokens) == 1 { | |
| finfo.flags = fElement | |
| } else { | |
| tag = tokens[0] | |
| for _, flag := range tokens[1:] { | |
| switch flag { | |
| case "attr": | |
| finfo.flags |= fAttr | |
| case "chardata": | |
| finfo.flags |= fCharData | |
| case "innerxml": | |
| finfo.flags |= fInnerXml | |
| case "comment": | |
| finfo.flags |= fComment | |
| case "any": | |
| finfo.flags |= fAny | |
| case "omitempty": | |
| finfo.flags |= fOmitEmpty | |
| } | |
| } | |
| // Validate the flags used. | |
| valid := true | |
| switch mode := finfo.flags & fMode; mode { | |
| case 0: | |
| finfo.flags |= fElement | |
| case fAttr, fCharData, fInnerXml, fComment, fAny: | |
| if f.Name == "XMLName" || tag != "" && mode != fAttr { | |
| valid = false | |
| } | |
| default: | |
| // This will also catch multiple modes in a single field. | |
| valid = false | |
| } | |
| if finfo.flags&fMode == fAny { | |
| finfo.flags |= fElement | |
| } | |
| if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 { | |
| valid = false | |
| } | |
| if !valid { | |
| return nil, fmt.Errorf("xml: invalid tag in field %s of type %s: %q", | |
| f.Name, typ, f.Tag.Get("xml")) | |
| } | |
| } | |
| // Use of xmlns without a name is not allowed. | |
| if finfo.xmlns != "" && tag == "" { | |
| return nil, fmt.Errorf("xml: namespace without name in field %s of type %s: %q", | |
| f.Name, typ, f.Tag.Get("xml")) | |
| } | |
| if f.Name == "XMLName" { | |
| // The XMLName field records the XML element name. Don't | |
| // process it as usual because its name should default to | |
| // empty rather than to the field name. | |
| finfo.name = tag | |
| return finfo, nil | |
| } | |
| if tag == "" { | |
| // If the name part of the tag is completely empty, get | |
| // default from XMLName of underlying struct if feasible, | |
| // or field name otherwise. | |
| if xmlname := lookupXMLName(f.Type); xmlname != nil { | |
| finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name | |
| } else { | |
| finfo.name = f.Name | |
| } | |
| return finfo, nil | |
| } | |
| if finfo.xmlns == "" && finfo.flags&fAttr == 0 { | |
| // If it's an element no namespace specified, get the default | |
| // from the XMLName of enclosing struct if possible. | |
| if xmlname := lookupXMLName(typ); xmlname != nil { | |
| finfo.xmlns = xmlname.xmlns | |
| } | |
| } | |
| // Prepare field name and parents. | |
| parents := strings.Split(tag, ">") | |
| if parents[0] == "" { | |
| parents[0] = f.Name | |
| } | |
| if parents[len(parents)-1] == "" { | |
| return nil, fmt.Errorf("xml: trailing '>' in field %s of type %s", f.Name, typ) | |
| } | |
| finfo.name = parents[len(parents)-1] | |
| if len(parents) > 1 { | |
| if (finfo.flags & fElement) == 0 { | |
| return nil, fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ",")) | |
| } | |
| finfo.parents = parents[:len(parents)-1] | |
| } | |
| // If the field type has an XMLName field, the names must match | |
| // so that the behavior of both marshalling and unmarshalling | |
| // is straightforward and unambiguous. | |
| if finfo.flags&fElement != 0 { | |
| ftyp := f.Type | |
| xmlname := lookupXMLName(ftyp) | |
| if xmlname != nil && xmlname.name != finfo.name { | |
| return nil, fmt.Errorf("xml: name %q in tag of %s.%s conflicts with name %q in %s.XMLName", | |
| finfo.name, typ, f.Name, xmlname.name, ftyp) | |
| } | |
| } | |
| return finfo, nil | |
| } | |
| // lookupXMLName returns the fieldInfo for typ's XMLName field | |
| // in case it exists and has a valid xml field tag, otherwise | |
| // it returns nil. | |
| func lookupXMLName(typ reflect.Type) (xmlname *fieldInfo) { | |
| for typ.Kind() == reflect.Ptr { | |
| typ = typ.Elem() | |
| } | |
| if typ.Kind() != reflect.Struct { | |
| return nil | |
| } | |
| for i, n := 0, typ.NumField(); i < n; i++ { | |
| f := typ.Field(i) | |
| if f.Name != "XMLName" { | |
| continue | |
| } | |
| finfo, err := structFieldInfo(typ, &f) | |
| if finfo.name != "" && err == nil { | |
| return finfo | |
| } | |
| // Also consider errors as a non-existent field tag | |
| // and let getTypeInfo itself report the error. | |
| break | |
| } | |
| return nil | |
| } | |
| func min(a, b int) int { | |
| if a <= b { | |
| return a | |
| } | |
| return b | |
| } | |
| // addFieldInfo adds finfo to tinfo.fields if there are no | |
| // conflicts, or if conflicts arise from previous fields that were | |
| // obtained from deeper embedded structures than finfo. In the latter | |
| // case, the conflicting entries are dropped. | |
| // A conflict occurs when the path (parent + name) to a field is | |
| // itself a prefix of another path, or when two paths match exactly. | |
| // It is okay for field paths to share a common, shorter prefix. | |
| func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error { | |
| var conflicts []int | |
| Loop: | |
| // First, figure all conflicts. Most working code will have none. | |
| for i := range tinfo.fields { | |
| oldf := &tinfo.fields[i] | |
| if oldf.flags&fMode != newf.flags&fMode { | |
| continue | |
| } | |
| if oldf.xmlns != "" && newf.xmlns != "" && oldf.xmlns != newf.xmlns { | |
| continue | |
| } | |
| minl := min(len(newf.parents), len(oldf.parents)) | |
| for p := 0; p < minl; p++ { | |
| if oldf.parents[p] != newf.parents[p] { | |
| continue Loop | |
| } | |
| } | |
| if len(oldf.parents) > len(newf.parents) { | |
| if oldf.parents[len(newf.parents)] == newf.name { | |
| conflicts = append(conflicts, i) | |
| } | |
| } else if len(oldf.parents) < len(newf.parents) { | |
| if newf.parents[len(oldf.parents)] == oldf.name { | |
| conflicts = append(conflicts, i) | |
| } | |
| } else { | |
| if newf.name == oldf.name { | |
| conflicts = append(conflicts, i) | |
| } | |
| } | |
| } | |
| // Without conflicts, add the new field and return. | |
| if conflicts == nil { | |
| tinfo.fields = append(tinfo.fields, *newf) | |
| return nil | |
| } | |
| // If any conflict is shallower, ignore the new field. | |
| // This matches the Go field resolution on embedding. | |
| for _, i := range conflicts { | |
| if len(tinfo.fields[i].idx) < len(newf.idx) { | |
| return nil | |
| } | |
| } | |
| // Otherwise, if any of them is at the same depth level, it's an error. | |
| for _, i := range conflicts { | |
| oldf := &tinfo.fields[i] | |
| if len(oldf.idx) == len(newf.idx) { | |
| f1 := typ.FieldByIndex(oldf.idx) | |
| f2 := typ.FieldByIndex(newf.idx) | |
| return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")} | |
| } | |
| } | |
| // Otherwise, the new field is shallower, and thus takes precedence, | |
| // so drop the conflicting fields from tinfo and append the new one. | |
| for c := len(conflicts) - 1; c >= 0; c-- { | |
| i := conflicts[c] | |
| copy(tinfo.fields[i:], tinfo.fields[i+1:]) | |
| tinfo.fields = tinfo.fields[:len(tinfo.fields)-1] | |
| } | |
| tinfo.fields = append(tinfo.fields, *newf) | |
| return nil | |
| } | |
| // 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) Error() 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) | |
| } | |
| // value returns v's field value corresponding to finfo. | |
| // It's equivalent to v.FieldByIndex(finfo.idx), but initializes | |
| // and dereferences pointers as necessary. | |
| func (finfo *fieldInfo) value(v reflect.Value) reflect.Value { | |
| for i, x := range finfo.idx { | |
| if i > 0 { | |
| t := v.Type() | |
| if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { | |
| if v.IsNil() { | |
| v.Set(reflect.New(v.Type().Elem())) | |
| } | |
| v = v.Elem() | |
| } | |
| } | |
| v = v.Field(x) | |
| } | |
| return v | |
| } |