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.
14 // typeInfo holds details for the xml representation of a type.
15 type typeInfo struct {
20 // fieldInfo holds details for the xml representation of a single field.
21 type fieldInfo struct {
32 fElement fieldFlags = 1 << iota
43 fMode = fElement | fAttr | fCharData | fInnerXml | fComment | fAny
46 var tinfoMap = make(map[reflect.Type]*typeInfo)
47 var tinfoLock sync.RWMutex
49 // getTypeInfo returns the typeInfo structure with details necessary
50 // for marshalling and unmarshalling typ.
51 func getTypeInfo(typ reflect.Type) (*typeInfo, error) {
53 tinfo, ok := tinfoMap[typ]
59 if typ.Kind() == reflect.Struct {
61 for i := 0; i < n; i++ {
64 continue // Private field
67 // For embedded structs, embed its fields.
69 if f.Type.Kind() != reflect.Struct {
72 inner, err := getTypeInfo(f.Type)
76 for _, finfo := range inner.fields {
77 finfo.idx = append([]int{i}, finfo.idx...)
78 if err := addFieldInfo(typ, tinfo, &finfo); err != nil {
85 finfo, err := structFieldInfo(typ, &f)
90 if f.Name == "XMLName" {
95 // Add the field if it doesn't conflict with other fields.
96 if err := addFieldInfo(typ, tinfo, finfo); err != nil {
102 tinfoMap[typ] = tinfo
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}
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:]
118 tokens := strings.Split(tag, ",")
119 if len(tokens) == 1 {
120 finfo.flags = fElement
123 for _, flag := range tokens[1:] {
128 finfo.flags |= fCharData
130 finfo.flags |= fInnerXml
132 finfo.flags |= fComment
138 // Validate the flags used.
139 switch mode := finfo.flags & fMode; mode {
141 finfo.flags |= fElement
142 case fAttr, fCharData, fInnerXml, fComment, fAny:
143 if f.Name != "XMLName" && (tag == "" || mode == fAttr) {
148 // This will also catch multiple modes in a single field.
149 return nil, fmt.Errorf("xml: invalid tag in field %s of type %s: %q",
150 f.Name, typ, f.Tag.Get("xml"))
154 // Use of xmlns without a name is not allowed.
155 if finfo.xmlns != "" && tag == "" {
156 return nil, fmt.Errorf("xml: namespace without name in field %s of type %s: %q",
157 f.Name, typ, f.Tag.Get("xml"))
160 if f.Name == "XMLName" {
161 // The XMLName field records the XML element name. Don't
162 // process it as usual because its name should default to
163 // empty rather than to the field name.
169 // If the name part of the tag is completely empty, get
170 // default from XMLName of underlying struct if feasible,
171 // or field name otherwise.
172 if xmlname := lookupXMLName(f.Type); xmlname != nil {
173 finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name
180 // Prepare field name and parents.
181 tokens = strings.Split(tag, ">")
185 if tokens[len(tokens)-1] == "" {
186 return nil, fmt.Errorf("xml: trailing '>' in field %s of type %s", f.Name, typ)
188 finfo.name = tokens[len(tokens)-1]
190 finfo.parents = tokens[:len(tokens)-1]
193 // If the field type has an XMLName field, the names must match
194 // so that the behavior of both marshalling and unmarshalling
195 // is straighforward and unambiguous.
196 if finfo.flags&fElement != 0 {
198 xmlname := lookupXMLName(ftyp)
199 if xmlname != nil && xmlname.name != finfo.name {
200 return nil, fmt.Errorf("xml: name %q in tag of %s.%s conflicts with name %q in %s.XMLName",
201 finfo.name, typ, f.Name, xmlname.name, ftyp)
207 // lookupXMLName returns the fieldInfo for typ's XMLName field
208 // in case it exists and has a valid xml field tag, otherwise
210 func lookupXMLName(typ reflect.Type) (xmlname *fieldInfo) {
211 for typ.Kind() == reflect.Ptr {
214 if typ.Kind() != reflect.Struct {
217 for i, n := 0, typ.NumField(); i < n; i++ {
219 if f.Name != "XMLName" {
222 finfo, err := structFieldInfo(typ, &f)
223 if finfo.name != "" && err == nil {
226 // Also consider errors as a non-existent field tag
227 // and let getTypeInfo itself report the error.
233 func min(a, b int) int {
240 // addFieldInfo adds finfo to tinfo.fields if there are no
241 // conflicts, or if conflicts arise from previous fields that were
242 // obtained from deeper embedded structures than finfo. In the latter
243 // case, the conflicting entries are dropped.
244 // A conflict occurs when the path (parent + name) to a field is
245 // itself a prefix of another path, or when two paths match exactly.
246 // It is okay for field paths to share a common, shorter prefix.
247 func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error {
250 // First, figure all conflicts. Most working code will have none.
251 for i := range tinfo.fields {
252 oldf := &tinfo.fields[i]
253 if oldf.flags&fMode != newf.flags&fMode {
256 minl := min(len(newf.parents), len(oldf.parents))
257 for p := 0; p < minl; p++ {
258 if oldf.parents[p] != newf.parents[p] {
262 if len(oldf.parents) > len(newf.parents) {
263 if oldf.parents[len(newf.parents)] == newf.name {
264 conflicts = append(conflicts, i)
266 } else if len(oldf.parents) < len(newf.parents) {
267 if newf.parents[len(oldf.parents)] == oldf.name {
268 conflicts = append(conflicts, i)
271 if newf.name == oldf.name {
272 conflicts = append(conflicts, i)
276 // Without conflicts, add the new field and return.
277 if conflicts == nil {
278 tinfo.fields = append(tinfo.fields, *newf)
282 // If any conflict is shallower, ignore the new field.
283 // This matches the Go field resolution on embedding.
284 for _, i := range conflicts {
285 if len(tinfo.fields[i].idx) < len(newf.idx) {
290 // Otherwise, if any of them is at the same depth level, it's an error.
291 for _, i := range conflicts {
292 oldf := &tinfo.fields[i]
293 if len(oldf.idx) == len(newf.idx) {
294 f1 := typ.FieldByIndex(oldf.idx)
295 f2 := typ.FieldByIndex(newf.idx)
296 return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")}
300 // Otherwise, the new field is shallower, and thus takes precedence,
301 // so drop the conflicting fields from tinfo and append the new one.
302 for c := len(conflicts) - 1; c >= 0; c-- {
304 copy(tinfo.fields[i:], tinfo.fields[i+1:])
305 tinfo.fields = tinfo.fields[:len(tinfo.fields)-1]
307 tinfo.fields = append(tinfo.fields, *newf)
311 // A TagPathError represents an error in the unmarshalling process
312 // caused by the use of field tags with conflicting paths.
313 type TagPathError struct {
319 func (e *TagPathError) Error() string {
320 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)