~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/xml/typeinfo.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
                                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.
 
114
func 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
        // Prepare field name and parents.
 
198
        parents := strings.Split(tag, ">")
 
199
        if parents[0] == "" {
 
200
                parents[0] = f.Name
 
201
        }
 
202
        if parents[len(parents)-1] == "" {
 
203
                return nil, fmt.Errorf("xml: trailing '>' in field %s of type %s", f.Name, typ)
 
204
        }
 
205
        finfo.name = parents[len(parents)-1]
 
206
        if len(parents) > 1 {
 
207
                if (finfo.flags & fElement) == 0 {
 
208
                        return nil, fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ","))
 
209
                }
 
210
                finfo.parents = parents[:len(parents)-1]
 
211
        }
 
212
 
 
213
        // If the field type has an XMLName field, the names must match
 
214
        // so that the behavior of both marshalling and unmarshalling
 
215
        // is straightforward and unambiguous.
 
216
        if finfo.flags&fElement != 0 {
 
217
                ftyp := f.Type
 
218
                xmlname := lookupXMLName(ftyp)
 
219
                if xmlname != nil && xmlname.name != finfo.name {
 
220
                        return nil, fmt.Errorf("xml: name %q in tag of %s.%s conflicts with name %q in %s.XMLName",
 
221
                                finfo.name, typ, f.Name, xmlname.name, ftyp)
 
222
                }
 
223
        }
 
224
        return finfo, nil
 
225
}
 
226
 
 
227
// lookupXMLName returns the fieldInfo for typ's XMLName field
 
228
// in case it exists and has a valid xml field tag, otherwise
 
229
// it returns nil.
 
230
func lookupXMLName(typ reflect.Type) (xmlname *fieldInfo) {
 
231
        for typ.Kind() == reflect.Ptr {
 
232
                typ = typ.Elem()
 
233
        }
 
234
        if typ.Kind() != reflect.Struct {
 
235
                return nil
 
236
        }
 
237
        for i, n := 0, typ.NumField(); i < n; i++ {
 
238
                f := typ.Field(i)
 
239
                if f.Name != "XMLName" {
 
240
                        continue
 
241
                }
 
242
                finfo, err := structFieldInfo(typ, &f)
 
243
                if finfo.name != "" && err == nil {
 
244
                        return finfo
 
245
                }
 
246
                // Also consider errors as a non-existent field tag
 
247
                // and let getTypeInfo itself report the error.
 
248
                break
 
249
        }
 
250
        return nil
 
251
}
 
252
 
 
253
func min(a, b int) int {
 
254
        if a <= b {
 
255
                return a
 
256
        }
 
257
        return b
 
258
}
 
259
 
 
260
// addFieldInfo adds finfo to tinfo.fields if there are no
 
261
// conflicts, or if conflicts arise from previous fields that were
 
262
// obtained from deeper embedded structures than finfo. In the latter
 
263
// case, the conflicting entries are dropped.
 
264
// A conflict occurs when the path (parent + name) to a field is
 
265
// itself a prefix of another path, or when two paths match exactly.
 
266
// It is okay for field paths to share a common, shorter prefix.
 
267
func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error {
 
268
        var conflicts []int
 
269
Loop:
 
270
        // First, figure all conflicts. Most working code will have none.
 
271
        for i := range tinfo.fields {
 
272
                oldf := &tinfo.fields[i]
 
273
                if oldf.flags&fMode != newf.flags&fMode {
 
274
                        continue
 
275
                }
 
276
                if oldf.xmlns != "" && newf.xmlns != "" && oldf.xmlns != newf.xmlns {
 
277
                        continue
 
278
                }
 
279
                minl := min(len(newf.parents), len(oldf.parents))
 
280
                for p := 0; p < minl; p++ {
 
281
                        if oldf.parents[p] != newf.parents[p] {
 
282
                                continue Loop
 
283
                        }
 
284
                }
 
285
                if len(oldf.parents) > len(newf.parents) {
 
286
                        if oldf.parents[len(newf.parents)] == newf.name {
 
287
                                conflicts = append(conflicts, i)
 
288
                        }
 
289
                } else if len(oldf.parents) < len(newf.parents) {
 
290
                        if newf.parents[len(oldf.parents)] == oldf.name {
 
291
                                conflicts = append(conflicts, i)
 
292
                        }
 
293
                } else {
 
294
                        if newf.name == oldf.name {
 
295
                                conflicts = append(conflicts, i)
 
296
                        }
 
297
                }
 
298
        }
 
299
        // Without conflicts, add the new field and return.
 
300
        if conflicts == nil {
 
301
                tinfo.fields = append(tinfo.fields, *newf)
 
302
                return nil
 
303
        }
 
304
 
 
305
        // If any conflict is shallower, ignore the new field.
 
306
        // This matches the Go field resolution on embedding.
 
307
        for _, i := range conflicts {
 
308
                if len(tinfo.fields[i].idx) < len(newf.idx) {
 
309
                        return nil
 
310
                }
 
311
        }
 
312
 
 
313
        // Otherwise, if any of them is at the same depth level, it's an error.
 
314
        for _, i := range conflicts {
 
315
                oldf := &tinfo.fields[i]
 
316
                if len(oldf.idx) == len(newf.idx) {
 
317
                        f1 := typ.FieldByIndex(oldf.idx)
 
318
                        f2 := typ.FieldByIndex(newf.idx)
 
319
                        return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")}
 
320
                }
 
321
        }
 
322
 
 
323
        // Otherwise, the new field is shallower, and thus takes precedence,
 
324
        // so drop the conflicting fields from tinfo and append the new one.
 
325
        for c := len(conflicts) - 1; c >= 0; c-- {
 
326
                i := conflicts[c]
 
327
                copy(tinfo.fields[i:], tinfo.fields[i+1:])
 
328
                tinfo.fields = tinfo.fields[:len(tinfo.fields)-1]
 
329
        }
 
330
        tinfo.fields = append(tinfo.fields, *newf)
 
331
        return nil
 
332
}
 
333
 
 
334
// A TagPathError represents an error in the unmarshalling process
 
335
// caused by the use of field tags with conflicting paths.
 
336
type TagPathError struct {
 
337
        Struct       reflect.Type
 
338
        Field1, Tag1 string
 
339
        Field2, Tag2 string
 
340
}
 
341
 
 
342
func (e *TagPathError) Error() string {
 
343
        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)
 
344
}
 
345
 
 
346
// value returns v's field value corresponding to finfo.
 
347
// It's equivalent to v.FieldByIndex(finfo.idx), but initializes
 
348
// and dereferences pointers as necessary.
 
349
func (finfo *fieldInfo) value(v reflect.Value) reflect.Value {
 
350
        for i, x := range finfo.idx {
 
351
                if i > 0 {
 
352
                        t := v.Type()
 
353
                        if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
 
354
                                if v.IsNil() {
 
355
                                        v.Set(reflect.New(v.Type().Elem()))
 
356
                                }
 
357
                                v = v.Elem()
 
358
                        }
 
359
                }
 
360
                v = v.Field(x)
 
361
        }
 
362
        return v
 
363
}