src/pkg/encoding/xml/typeinfo.go - The Go Programming Language

Golang

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