1
2
3
4
5 package xml
6
7 import (
8 "fmt"
9 "reflect"
10 "strings"
11 "sync"
12 )
13
14
15 type typeInfo struct {
16 xmlname *fieldInfo
17 fields []fieldInfo
18 }
19
20
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
48
49 var nameType = reflect.TypeOf(Name{})
50
51
52
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.IsExported() && !f.Anonymous) || f.Tag.Get("xml") == "-" {
64 continue
65 }
66
67
68 if f.Anonymous {
69 t := f.Type
70 if t.Kind() == reflect.Pointer {
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
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
113 func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) {
114 finfo := &fieldInfo{idx: f.Index}
115
116
117 tag := f.Tag.Get("xml")
118 if ns, t, ok := strings.Cut(tag, " "); ok {
119 finfo.xmlns, tag = ns, t
120 }
121
122
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
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
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
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
180
181
182 finfo.name = tag
183 return finfo, nil
184 }
185
186 if tag == "" {
187
188
189
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
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
215
216
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
229
230
231 func lookupXMLName(typ reflect.Type) (xmlname *fieldInfo) {
232 for typ.Kind() == reflect.Pointer {
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
248
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
262
263
264
265
266
267
268 func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error {
269 var conflicts []int
270 Loop:
271
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
301 if conflicts == nil {
302 tinfo.fields = append(tinfo.fields, *newf)
303 return nil
304 }
305
306
307
308 for _, i := range conflicts {
309 if len(tinfo.fields[i].idx) < len(newf.idx) {
310 return nil
311 }
312 }
313
314
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
325
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
336
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 const (
348 initNilPointers = true
349 dontInitNilPointers = false
350 )
351
352
353
354
355
356
357 func (finfo *fieldInfo) value(v reflect.Value, shouldInitNilPointers bool) reflect.Value {
358 for i, x := range finfo.idx {
359 if i > 0 {
360 t := v.Type()
361 if t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Struct {
362 if v.IsNil() {
363 if !shouldInitNilPointers {
364 return reflect.Value{}
365 }
366 v.Set(reflect.New(v.Type().Elem()))
367 }
368 v = v.Elem()
369 }
370 }
371 v = v.Field(x)
372 }
373 return v
374 }
375
View as plain text