373 lines
9.8 KiB
Go
373 lines
9.8 KiB
Go
// 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 goxml
|
|
|
|
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
|
|
fCDATA
|
|
fCharData
|
|
fInnerXML
|
|
fComment
|
|
fAny
|
|
|
|
fOmitEmpty
|
|
|
|
fMode = fElement | fAttr | fCDATA | fCharData | fInnerXML | fComment | fAny
|
|
|
|
xmlName = "XMLName"
|
|
)
|
|
|
|
var tinfoMap sync.Map // map[reflect.Type]*typeInfo
|
|
|
|
var nameType = reflect.TypeFor[Name]()
|
|
|
|
// getTypeInfo returns the typeInfo structure with details necessary
|
|
// for marshaling and unmarshaling typ.
|
|
func getTypeInfo(typ reflect.Type, inDecode bool) (*typeInfo, error) {
|
|
if ti, ok := tinfoMap.Load(typ); ok {
|
|
return ti.(*typeInfo), 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.IsExported() && !f.Anonymous) || f.Tag.Get("xml") == "-" {
|
|
continue // Private field
|
|
}
|
|
|
|
// For embedded structs, embed its fields.
|
|
if f.Anonymous {
|
|
t := f.Type
|
|
if t.Kind() == reflect.Pointer {
|
|
t = t.Elem()
|
|
}
|
|
if t.Kind() == reflect.Struct {
|
|
inner, err := getTypeInfo(t, inDecode)
|
|
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, inDecode)
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
ti, _ := tinfoMap.LoadOrStore(typ, tinfo)
|
|
return ti.(*typeInfo), nil
|
|
}
|
|
|
|
// structFieldInfo builds and returns a fieldInfo for f.
|
|
func structFieldInfo(typ reflect.Type, f *reflect.StructField, inDecode bool) (*fieldInfo, error) {
|
|
finfo := &fieldInfo{idx: f.Index}
|
|
|
|
// Split the tag from the xml namespace if necessary.
|
|
tag := f.Tag.Get("xml")
|
|
if ns, t, ok := strings.Cut(tag, " "); ok {
|
|
finfo.xmlns, tag = ns, t
|
|
}
|
|
if inDecode {
|
|
if ns, t, ok := strings.Cut(tag, ":"); ok {
|
|
finfo.xmlns, tag = ns, t
|
|
}
|
|
}
|
|
|
|
// 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 "cdata":
|
|
finfo.flags |= fCDATA
|
|
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, fCDATA, fCharData, fInnerXML, fComment, fAny, fAny | fAttr:
|
|
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, inDecode); xmlname != nil {
|
|
finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name
|
|
} else {
|
|
finfo.name = f.Name
|
|
}
|
|
return finfo, nil
|
|
}
|
|
|
|
// 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 marshaling and unmarshaling
|
|
// is straightforward and unambiguous.
|
|
if finfo.flags&fElement != 0 {
|
|
ftyp := f.Type
|
|
xmlname := lookupXMLName(ftyp, inDecode)
|
|
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, inDecode bool) (xmlname *fieldInfo) {
|
|
for typ.Kind() == reflect.Pointer {
|
|
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, inDecode)
|
|
if err == nil && finfo.name != "" {
|
|
return finfo
|
|
}
|
|
// Also consider errors as a non-existent field tag
|
|
// and let getTypeInfo itself report the error.
|
|
break
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 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 && newf.xmlns == oldf.xmlns {
|
|
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 unmarshaling 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)
|
|
}
|
|
|
|
const (
|
|
initNilPointers = true
|
|
dontInitNilPointers = false
|
|
)
|
|
|
|
// value returns v's field value corresponding to finfo.
|
|
// It's equivalent to v.FieldByIndex(finfo.idx), but when passed
|
|
// initNilPointers, it initializes and dereferences pointers as necessary.
|
|
// When passed dontInitNilPointers and a nil pointer is reached, the function
|
|
// returns a zero reflect.Value.
|
|
func (finfo *fieldInfo) value(v reflect.Value, shouldInitNilPointers bool) reflect.Value {
|
|
for i, x := range finfo.idx {
|
|
if i > 0 {
|
|
t := v.Type()
|
|
if t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Struct {
|
|
if v.IsNil() {
|
|
if !shouldInitNilPointers {
|
|
return reflect.Value{}
|
|
}
|
|
v.Set(reflect.New(v.Type().Elem()))
|
|
}
|
|
v = v.Elem()
|
|
}
|
|
}
|
|
v = v.Field(x)
|
|
}
|
|
return v
|
|
}
|