...

Source file src/encoding/xml/typeinfo.go

     1	// Copyright 2011 The Go Authors. All rights reserved.
     2	// Use of this source code is governed by a BSD-style
     3	// license that can be found in the LICENSE file.
     4	
     5	package xml
     6	
     7	import (
     8		"fmt"
     9		"reflect"
    10		"strings"
    11		"sync"
    12	)
    13	
    14	// typeInfo holds details for the xml representation of a type.
    15	type typeInfo struct {
    16		xmlname *fieldInfo
    17		fields  []fieldInfo
    18	}
    19	
    20	// fieldInfo holds details for the xml representation of a single field.
    21	type fieldInfo struct {
    22		idx     []int
    23		name    string
    24		xmlns   string
    25		flags   fieldFlags
    26		parents []string
    27	}
    28	
    29	type fieldFlags int
    30	
    31	const (
    32		fElement fieldFlags = 1 << iota
    33		fAttr
    34		fCDATA
    35		fCharData
    36		fInnerXml
    37		fComment
    38		fAny
    39	
    40		fOmitEmpty
    41	
    42		fMode = fElement | fAttr | fCDATA | fCharData | fInnerXml | fComment | fAny
    43	
    44		xmlName = "XMLName"
    45	)
    46	
    47	var tinfoMap sync.Map // map[reflect.Type]*typeInfo
    48	
    49	var nameType = reflect.TypeOf(Name{})
    50	
    51	// getTypeInfo returns the typeInfo structure with details necessary
    52	// for marshaling and unmarshaling typ.
    53	func getTypeInfo(typ reflect.Type) (*typeInfo, error) {
    54		if ti, ok := tinfoMap.Load(typ); ok {
    55			return ti.(*typeInfo), nil
    56		}
    57	
    58		tinfo := &typeInfo{}
    59		if typ.Kind() == reflect.Struct && typ != nameType {
    60			n := typ.NumField()
    61			for i := 0; i < n; i++ {
    62				f := typ.Field(i)
    63				if (f.PkgPath != "" && !f.Anonymous) || f.Tag.Get("xml") == "-" {
    64					continue // Private field
    65				}
    66	
    67				// For embedded structs, embed its fields.
    68				if f.Anonymous {
    69					t := f.Type
    70					if t.Kind() == reflect.Ptr {
    71						t = t.Elem()
    72					}
    73					if t.Kind() == reflect.Struct {
    74						inner, err := getTypeInfo(t)
    75						if err != nil {
    76							return nil, err
    77						}
    78						if tinfo.xmlname == nil {
    79							tinfo.xmlname = inner.xmlname
    80						}
    81						for _, finfo := range inner.fields {
    82							finfo.idx = append([]int{i}, finfo.idx...)
    83							if err := addFieldInfo(typ, tinfo, &finfo); err != nil {
    84								return nil, err
    85							}
    86						}
    87						continue
    88					}
    89				}
    90	
    91				finfo, err := structFieldInfo(typ, &f)
    92				if err != nil {
    93					return nil, err
    94				}
    95	
    96				if f.Name == xmlName {
    97					tinfo.xmlname = finfo
    98					continue
    99				}
   100	
   101				// Add the field if it doesn't conflict with other fields.
   102				if err := addFieldInfo(typ, tinfo, finfo); err != nil {
   103					return nil, err
   104				}
   105			}
   106		}
   107	
   108		ti, _ := tinfoMap.LoadOrStore(typ, tinfo)
   109		return ti.(*typeInfo), nil
   110	}
   111	
   112	// structFieldInfo builds and returns a fieldInfo for f.
   113	func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) {
   114		finfo := &fieldInfo{idx: f.Index}
   115	
   116		// Split the tag from the xml namespace if necessary.
   117		tag := f.Tag.Get("xml")
   118		if i := strings.Index(tag, " "); i >= 0 {
   119			finfo.xmlns, tag = tag[:i], tag[i+1:]
   120		}
   121	
   122		// Parse flags.
   123		tokens := strings.Split(tag, ",")
   124		if len(tokens) == 1 {
   125			finfo.flags = fElement
   126		} else {
   127			tag = tokens[0]
   128			for _, flag := range tokens[1:] {
   129				switch flag {
   130				case "attr":
   131					finfo.flags |= fAttr
   132				case "cdata":
   133					finfo.flags |= fCDATA
   134				case "chardata":
   135					finfo.flags |= fCharData
   136				case "innerxml":
   137					finfo.flags |= fInnerXml
   138				case "comment":
   139					finfo.flags |= fComment
   140				case "any":
   141					finfo.flags |= fAny
   142				case "omitempty":
   143					finfo.flags |= fOmitEmpty
   144				}
   145			}
   146	
   147			// Validate the flags used.
   148			valid := true
   149			switch mode := finfo.flags & fMode; mode {
   150			case 0:
   151				finfo.flags |= fElement
   152			case fAttr, fCDATA, fCharData, fInnerXml, fComment, fAny, fAny | fAttr:
   153				if f.Name == xmlName || tag != "" && mode != fAttr {
   154					valid = false
   155				}
   156			default:
   157				// This will also catch multiple modes in a single field.
   158				valid = false
   159			}
   160			if finfo.flags&fMode == fAny {
   161				finfo.flags |= fElement
   162			}
   163			if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 {
   164				valid = false
   165			}
   166			if !valid {
   167				return nil, fmt.Errorf("xml: invalid tag in field %s of type %s: %q",
   168					f.Name, typ, f.Tag.Get("xml"))
   169			}
   170		}
   171	
   172		// Use of xmlns without a name is not allowed.
   173		if finfo.xmlns != "" && tag == "" {
   174			return nil, fmt.Errorf("xml: namespace without name in field %s of type %s: %q",
   175				f.Name, typ, f.Tag.Get("xml"))
   176		}
   177	
   178		if f.Name == xmlName {
   179			// The XMLName field records the XML element name. Don't
   180			// process it as usual because its name should default to
   181			// empty rather than to the field name.
   182			finfo.name = tag
   183			return finfo, nil
   184		}
   185	
   186		if tag == "" {
   187			// If the name part of the tag is completely empty, get
   188			// default from XMLName of underlying struct if feasible,
   189			// or field name otherwise.
   190			if xmlname := lookupXMLName(f.Type); xmlname != nil {
   191				finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name
   192			} else {
   193				finfo.name = f.Name
   194			}
   195			return finfo, nil
   196		}
   197	
   198		// Prepare field name and parents.
   199		parents := strings.Split(tag, ">")
   200		if parents[0] == "" {
   201			parents[0] = f.Name
   202		}
   203		if parents[len(parents)-1] == "" {
   204			return nil, fmt.Errorf("xml: trailing '>' in field %s of type %s", f.Name, typ)
   205		}
   206		finfo.name = parents[len(parents)-1]
   207		if len(parents) > 1 {
   208			if (finfo.flags & fElement) == 0 {
   209				return nil, fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ","))
   210			}
   211			finfo.parents = parents[:len(parents)-1]
   212		}
   213	
   214		// If the field type has an XMLName field, the names must match
   215		// so that the behavior of both marshaling and unmarshaling
   216		// is straightforward and unambiguous.
   217		if finfo.flags&fElement != 0 {
   218			ftyp := f.Type
   219			xmlname := lookupXMLName(ftyp)
   220			if xmlname != nil && xmlname.name != finfo.name {
   221				return nil, fmt.Errorf("xml: name %q in tag of %s.%s conflicts with name %q in %s.XMLName",
   222					finfo.name, typ, f.Name, xmlname.name, ftyp)
   223			}
   224		}
   225		return finfo, nil
   226	}
   227	
   228	// lookupXMLName returns the fieldInfo for typ's XMLName field
   229	// in case it exists and has a valid xml field tag, otherwise
   230	// it returns nil.
   231	func lookupXMLName(typ reflect.Type) (xmlname *fieldInfo) {
   232		for typ.Kind() == reflect.Ptr {
   233			typ = typ.Elem()
   234		}
   235		if typ.Kind() != reflect.Struct {
   236			return nil
   237		}
   238		for i, n := 0, typ.NumField(); i < n; i++ {
   239			f := typ.Field(i)
   240			if f.Name != xmlName {
   241				continue
   242			}
   243			finfo, err := structFieldInfo(typ, &f)
   244			if err == nil && finfo.name != "" {
   245				return finfo
   246			}
   247			// Also consider errors as a non-existent field tag
   248			// and let getTypeInfo itself report the error.
   249			break
   250		}
   251		return nil
   252	}
   253	
   254	func min(a, b int) int {
   255		if a <= b {
   256			return a
   257		}
   258		return b
   259	}
   260	
   261	// addFieldInfo adds finfo to tinfo.fields if there are no
   262	// conflicts, or if conflicts arise from previous fields that were
   263	// obtained from deeper embedded structures than finfo. In the latter
   264	// case, the conflicting entries are dropped.
   265	// A conflict occurs when the path (parent + name) to a field is
   266	// itself a prefix of another path, or when two paths match exactly.
   267	// It is okay for field paths to share a common, shorter prefix.
   268	func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error {
   269		var conflicts []int
   270	Loop:
   271		// First, figure all conflicts. Most working code will have none.
   272		for i := range tinfo.fields {
   273			oldf := &tinfo.fields[i]
   274			if oldf.flags&fMode != newf.flags&fMode {
   275				continue
   276			}
   277			if oldf.xmlns != "" && newf.xmlns != "" && oldf.xmlns != newf.xmlns {
   278				continue
   279			}
   280			minl := min(len(newf.parents), len(oldf.parents))
   281			for p := 0; p < minl; p++ {
   282				if oldf.parents[p] != newf.parents[p] {
   283					continue Loop
   284				}
   285			}
   286			if len(oldf.parents) > len(newf.parents) {
   287				if oldf.parents[len(newf.parents)] == newf.name {
   288					conflicts = append(conflicts, i)
   289				}
   290			} else if len(oldf.parents) < len(newf.parents) {
   291				if newf.parents[len(oldf.parents)] == oldf.name {
   292					conflicts = append(conflicts, i)
   293				}
   294			} else {
   295				if newf.name == oldf.name {
   296					conflicts = append(conflicts, i)
   297				}
   298			}
   299		}
   300		// Without conflicts, add the new field and return.
   301		if conflicts == nil {
   302			tinfo.fields = append(tinfo.fields, *newf)
   303			return nil
   304		}
   305	
   306		// If any conflict is shallower, ignore the new field.
   307		// This matches the Go field resolution on embedding.
   308		for _, i := range conflicts {
   309			if len(tinfo.fields[i].idx) < len(newf.idx) {
   310				return nil
   311			}
   312		}
   313	
   314		// Otherwise, if any of them is at the same depth level, it's an error.
   315		for _, i := range conflicts {
   316			oldf := &tinfo.fields[i]
   317			if len(oldf.idx) == len(newf.idx) {
   318				f1 := typ.FieldByIndex(oldf.idx)
   319				f2 := typ.FieldByIndex(newf.idx)
   320				return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")}
   321			}
   322		}
   323	
   324		// Otherwise, the new field is shallower, and thus takes precedence,
   325		// so drop the conflicting fields from tinfo and append the new one.
   326		for c := len(conflicts) - 1; c >= 0; c-- {
   327			i := conflicts[c]
   328			copy(tinfo.fields[i:], tinfo.fields[i+1:])
   329			tinfo.fields = tinfo.fields[:len(tinfo.fields)-1]
   330		}
   331		tinfo.fields = append(tinfo.fields, *newf)
   332		return nil
   333	}
   334	
   335	// A TagPathError represents an error in the unmarshaling process
   336	// caused by the use of field tags with conflicting paths.
   337	type TagPathError struct {
   338		Struct       reflect.Type
   339		Field1, Tag1 string
   340		Field2, Tag2 string
   341	}
   342	
   343	func (e *TagPathError) Error() string {
   344		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)
   345	}
   346	
   347	// value returns v's field value corresponding to finfo.
   348	// It's equivalent to v.FieldByIndex(finfo.idx), but initializes
   349	// and dereferences pointers as necessary.
   350	func (finfo *fieldInfo) value(v reflect.Value) reflect.Value {
   351		for i, x := range finfo.idx {
   352			if i > 0 {
   353				t := v.Type()
   354				if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
   355					if v.IsNil() {
   356						v.Set(reflect.New(v.Type().Elem()))
   357					}
   358					v = v.Elem()
   359				}
   360			}
   361			v = v.Field(x)
   362		}
   363		return v
   364	}
   365	

View as plain text