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