github.com/vmware/govmomi@v0.43.0/vim25/xml/typeinfo.go (about) 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 fCDATA 35 fCharData 36 fInnerXML 37 fComment 38 fAny 39 40 fOmitEmpty 41 fTypeAttr 42 43 fMode = fElement | fAttr | fCDATA | fCharData | fInnerXML | fComment | fAny 44 45 xmlName = "XMLName" 46 ) 47 48 var tinfoMap sync.Map // map[reflect.Type]*typeInfo 49 50 var nameType = reflect.TypeOf(Name{}) 51 52 // getTypeInfo returns the typeInfo structure with details necessary 53 // for marshaling and unmarshaling typ. 54 func getTypeInfo(typ reflect.Type) (*typeInfo, error) { 55 if ti, ok := tinfoMap.Load(typ); ok { 56 return ti.(*typeInfo), nil 57 } 58 59 tinfo := &typeInfo{} 60 if typ.Kind() == reflect.Struct && typ != nameType { 61 n := typ.NumField() 62 for i := 0; i < n; i++ { 63 f := typ.Field(i) 64 if (f.PkgPath != "" && !f.Anonymous) || f.Tag.Get("xml") == "-" { 65 continue // Private field 66 } 67 68 // For embedded structs, embed its fields. 69 if f.Anonymous { 70 t := f.Type 71 if t.Kind() == reflect.Ptr { 72 t = t.Elem() 73 } 74 if t.Kind() == reflect.Struct { 75 inner, err := getTypeInfo(t) 76 if err != nil { 77 return nil, err 78 } 79 if tinfo.xmlname == nil { 80 tinfo.xmlname = inner.xmlname 81 } 82 for _, finfo := range inner.fields { 83 finfo.idx = append([]int{i}, finfo.idx...) 84 if err := addFieldInfo(typ, tinfo, &finfo); err != nil { 85 return nil, err 86 } 87 } 88 continue 89 } 90 } 91 92 finfo, err := structFieldInfo(typ, &f) 93 if err != nil { 94 return nil, err 95 } 96 97 if f.Name == xmlName { 98 tinfo.xmlname = finfo 99 continue 100 } 101 102 // Add the field if it doesn't conflict with other fields. 103 if err := addFieldInfo(typ, tinfo, finfo); err != nil { 104 return nil, err 105 } 106 } 107 } 108 109 ti, _ := tinfoMap.LoadOrStore(typ, tinfo) 110 return ti.(*typeInfo), 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 "cdata": 134 finfo.flags |= fCDATA 135 case "chardata": 136 finfo.flags |= fCharData 137 case "innerxml": 138 finfo.flags |= fInnerXML 139 case "comment": 140 finfo.flags |= fComment 141 case "any": 142 finfo.flags |= fAny 143 case "omitempty": 144 finfo.flags |= fOmitEmpty 145 case "typeattr": 146 finfo.flags |= fTypeAttr 147 } 148 } 149 150 // Validate the flags used. 151 valid := true 152 switch mode := finfo.flags & fMode; mode { 153 case 0: 154 finfo.flags |= fElement 155 case fAttr, fCDATA, fCharData, fInnerXML, fComment, fAny, fAny | fAttr: 156 if f.Name == xmlName || tag != "" && mode != fAttr { 157 valid = false 158 } 159 default: 160 // This will also catch multiple modes in a single field. 161 valid = false 162 } 163 if finfo.flags&fMode == fAny { 164 finfo.flags |= fElement 165 } 166 if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 { 167 valid = false 168 } 169 if !valid { 170 return nil, fmt.Errorf("xml: invalid tag in field %s of type %s: %q", 171 f.Name, typ, f.Tag.Get("xml")) 172 } 173 } 174 175 // Use of xmlns without a name is not allowed. 176 if finfo.xmlns != "" && tag == "" { 177 return nil, fmt.Errorf("xml: namespace without name in field %s of type %s: %q", 178 f.Name, typ, f.Tag.Get("xml")) 179 } 180 181 if f.Name == xmlName { 182 // The XMLName field records the XML element name. Don't 183 // process it as usual because its name should default to 184 // empty rather than to the field name. 185 finfo.name = tag 186 return finfo, nil 187 } 188 189 if tag == "" { 190 // If the name part of the tag is completely empty, get 191 // default from XMLName of underlying struct if feasible, 192 // or field name otherwise. 193 if xmlname := lookupXMLName(f.Type); xmlname != nil { 194 finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name 195 } else { 196 finfo.name = f.Name 197 } 198 return finfo, nil 199 } 200 201 // Prepare field name and parents. 202 parents := strings.Split(tag, ">") 203 if parents[0] == "" { 204 parents[0] = f.Name 205 } 206 if parents[len(parents)-1] == "" { 207 return nil, fmt.Errorf("xml: trailing '>' in field %s of type %s", f.Name, typ) 208 } 209 finfo.name = parents[len(parents)-1] 210 if len(parents) > 1 { 211 if (finfo.flags & fElement) == 0 { 212 return nil, fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ",")) 213 } 214 finfo.parents = parents[:len(parents)-1] 215 } 216 217 // If the field type has an XMLName field, the names must match 218 // so that the behavior of both marshaling and unmarshaling 219 // is straightforward and unambiguous. 220 if finfo.flags&fElement != 0 { 221 ftyp := f.Type 222 xmlname := lookupXMLName(ftyp) 223 if xmlname != nil && xmlname.name != finfo.name { 224 return nil, fmt.Errorf("xml: name %q in tag of %s.%s conflicts with name %q in %s.XMLName", 225 finfo.name, typ, f.Name, xmlname.name, ftyp) 226 } 227 } 228 return finfo, nil 229 } 230 231 // lookupXMLName returns the fieldInfo for typ's XMLName field 232 // in case it exists and has a valid xml field tag, otherwise 233 // it returns nil. 234 func lookupXMLName(typ reflect.Type) (xmlname *fieldInfo) { 235 for typ.Kind() == reflect.Ptr { 236 typ = typ.Elem() 237 } 238 if typ.Kind() != reflect.Struct { 239 return nil 240 } 241 for i, n := 0, typ.NumField(); i < n; i++ { 242 f := typ.Field(i) 243 if f.Name != xmlName { 244 continue 245 } 246 finfo, err := structFieldInfo(typ, &f) 247 if err == nil && finfo.name != "" { 248 return finfo 249 } 250 // Also consider errors as a non-existent field tag 251 // and let getTypeInfo itself report the error. 252 break 253 } 254 return nil 255 } 256 257 func min(a, b int) int { 258 if a <= b { 259 return a 260 } 261 return b 262 } 263 264 // addFieldInfo adds finfo to tinfo.fields if there are no 265 // conflicts, or if conflicts arise from previous fields that were 266 // obtained from deeper embedded structures than finfo. In the latter 267 // case, the conflicting entries are dropped. 268 // A conflict occurs when the path (parent + name) to a field is 269 // itself a prefix of another path, or when two paths match exactly. 270 // It is okay for field paths to share a common, shorter prefix. 271 func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error { 272 var conflicts []int 273 Loop: 274 // First, figure all conflicts. Most working code will have none. 275 for i := range tinfo.fields { 276 oldf := &tinfo.fields[i] 277 if oldf.flags&fMode != newf.flags&fMode { 278 continue 279 } 280 if oldf.xmlns != "" && newf.xmlns != "" && oldf.xmlns != newf.xmlns { 281 continue 282 } 283 minl := min(len(newf.parents), len(oldf.parents)) 284 for p := 0; p < minl; p++ { 285 if oldf.parents[p] != newf.parents[p] { 286 continue Loop 287 } 288 } 289 if len(oldf.parents) > len(newf.parents) { 290 if oldf.parents[len(newf.parents)] == newf.name { 291 conflicts = append(conflicts, i) 292 } 293 } else if len(oldf.parents) < len(newf.parents) { 294 if newf.parents[len(oldf.parents)] == oldf.name { 295 conflicts = append(conflicts, i) 296 } 297 } else { 298 if newf.name == oldf.name { 299 conflicts = append(conflicts, i) 300 } 301 } 302 } 303 // Without conflicts, add the new field and return. 304 if conflicts == nil { 305 tinfo.fields = append(tinfo.fields, *newf) 306 return nil 307 } 308 309 // If any conflict is shallower, ignore the new field. 310 // This matches the Go field resolution on embedding. 311 for _, i := range conflicts { 312 if len(tinfo.fields[i].idx) < len(newf.idx) { 313 return nil 314 } 315 } 316 317 // Otherwise, if any of them is at the same depth level, it's an error. 318 for _, i := range conflicts { 319 oldf := &tinfo.fields[i] 320 if len(oldf.idx) == len(newf.idx) { 321 f1 := typ.FieldByIndex(oldf.idx) 322 f2 := typ.FieldByIndex(newf.idx) 323 return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")} 324 } 325 } 326 327 // Otherwise, the new field is shallower, and thus takes precedence, 328 // so drop the conflicting fields from tinfo and append the new one. 329 for c := len(conflicts) - 1; c >= 0; c-- { 330 i := conflicts[c] 331 copy(tinfo.fields[i:], tinfo.fields[i+1:]) 332 tinfo.fields = tinfo.fields[:len(tinfo.fields)-1] 333 } 334 tinfo.fields = append(tinfo.fields, *newf) 335 return nil 336 } 337 338 // A TagPathError represents an error in the unmarshaling process 339 // caused by the use of field tags with conflicting paths. 340 type TagPathError struct { 341 Struct reflect.Type 342 Field1, Tag1 string 343 Field2, Tag2 string 344 } 345 346 func (e *TagPathError) Error() string { 347 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) 348 } 349 350 const ( 351 initNilPointers = true 352 dontInitNilPointers = false 353 ) 354 355 // value returns v's field value corresponding to finfo. 356 // It's equivalent to v.FieldByIndex(finfo.idx), but when passed 357 // initNilPointers, it initializes and dereferences pointers as necessary. 358 // When passed dontInitNilPointers and a nil pointer is reached, the function 359 // returns a zero reflect.Value. 360 func (finfo *fieldInfo) value(v reflect.Value, shouldInitNilPointers bool) reflect.Value { 361 for i, x := range finfo.idx { 362 if i > 0 { 363 t := v.Type() 364 if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { 365 if v.IsNil() { 366 if !shouldInitNilPointers { 367 return reflect.Value{} 368 } 369 v.Set(reflect.New(v.Type().Elem())) 370 } 371 v = v.Elem() 372 } 373 } 374 v = v.Field(x) 375 } 376 return v 377 }