// Copyright 2011 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package xmlimport ()// typeInfo holds details for the xml representation of a type.type typeInfo struct { xmlname *fieldInfo fields []fieldInfo}// fieldInfo holds details for the xml representation of a single field.type fieldInfo struct { idx []int name string xmlns string flags fieldFlags parents []string}type fieldFlags intconst ( fElement fieldFlags = 1 << iota fAttr fCDATA fCharData fInnerXML fComment fAny fOmitEmpty fMode = fElement | fAttr | fCDATA | fCharData | fInnerXML | fComment | fAny xmlName = "XMLName")var tinfoMap sync.Map// map[reflect.Type]*typeInfovar nameType = reflect.TypeFor[Name]()// getTypeInfo returns the typeInfo structure with details necessary// for marshaling and unmarshaling typ.func getTypeInfo( reflect.Type) (*typeInfo, error) {if , := tinfoMap.Load(); {return .(*typeInfo), nil } := &typeInfo{}if .Kind() == reflect.Struct && != nameType { := .NumField()for := 0; < ; ++ { := .Field()if (!.IsExported() && !.Anonymous) || .Tag.Get("xml") == "-" {continue// Private field }// For embedded structs, embed its fields.if .Anonymous { := .Typeif .Kind() == reflect.Pointer { = .Elem() }if .Kind() == reflect.Struct { , := ()if != nil {returnnil, }if .xmlname == nil { .xmlname = .xmlname }for , := range .fields { .idx = append([]int{}, .idx...)if := addFieldInfo(, , &); != nil {returnnil, } }continue } } , := structFieldInfo(, &)if != nil {returnnil, }if .Name == xmlName { .xmlname = continue }// Add the field if it doesn't conflict with other fields.if := addFieldInfo(, , ); != nil {returnnil, } } } , := tinfoMap.LoadOrStore(, )return .(*typeInfo), nil}// structFieldInfo builds and returns a fieldInfo for f.func structFieldInfo( reflect.Type, *reflect.StructField) (*fieldInfo, error) { := &fieldInfo{idx: .Index}// Split the tag from the xml namespace if necessary. := .Tag.Get("xml")if , , := strings.Cut(, " "); { .xmlns, = , }// Parse flags. := strings.Split(, ",")iflen() == 1 { .flags = fElement } else { = [0]for , := range [1:] {switch {case"attr": .flags |= fAttrcase"cdata": .flags |= fCDATAcase"chardata": .flags |= fCharDatacase"innerxml": .flags |= fInnerXMLcase"comment": .flags |= fCommentcase"any": .flags |= fAnycase"omitempty": .flags |= fOmitEmpty } }// Validate the flags used. := trueswitch := .flags & fMode; {case0: .flags |= fElementcasefAttr, fCDATA, fCharData, fInnerXML, fComment, fAny, fAny | fAttr:if .Name == xmlName || != "" && != fAttr { = false }default:// This will also catch multiple modes in a single field. = false }if .flags&fMode == fAny { .flags |= fElement }if .flags&fOmitEmpty != 0 && .flags&(fElement|fAttr) == 0 { = false }if ! {returnnil, fmt.Errorf("xml: invalid tag in field %s of type %s: %q", .Name, , .Tag.Get("xml")) } }// Use of xmlns without a name is not allowed.if .xmlns != "" && == "" {returnnil, fmt.Errorf("xml: namespace without name in field %s of type %s: %q", .Name, , .Tag.Get("xml")) }if .Name == xmlName {// The XMLName field records the XML element name. Don't // process it as usual because its name should default to // empty rather than to the field name. .name = return , nil }if == "" {// If the name part of the tag is completely empty, get // default from XMLName of underlying struct if feasible, // or field name otherwise.if := lookupXMLName(.Type); != nil { .xmlns, .name = .xmlns, .name } else { .name = .Name }return , nil }// Prepare field name and parents. := strings.Split(, ">")if [0] == "" { [0] = .Name }if [len()-1] == "" {returnnil, fmt.Errorf("xml: trailing '>' in field %s of type %s", .Name, ) } .name = [len()-1]iflen() > 1 {if (.flags & fElement) == 0 {returnnil, fmt.Errorf("xml: %s chain not valid with %s flag", , strings.Join([1:], ",")) } .parents = [:len()-1] }// If the field type has an XMLName field, the names must match // so that the behavior of both marshaling and unmarshaling // is straightforward and unambiguous.if .flags&fElement != 0 { := .Type := lookupXMLName()if != nil && .name != .name {returnnil, fmt.Errorf("xml: name %q in tag of %s.%s conflicts with name %q in %s.XMLName", .name, , .Name, .name, ) } }return , nil}// lookupXMLName returns the fieldInfo for typ's XMLName field// in case it exists and has a valid xml field tag, otherwise// it returns nil.func lookupXMLName( reflect.Type) ( *fieldInfo) {for .Kind() == reflect.Pointer { = .Elem() }if .Kind() != reflect.Struct {returnnil }for , := 0, .NumField(); < ; ++ { := .Field()if .Name != xmlName {continue } , := structFieldInfo(, &)if == nil && .name != "" {return }// Also consider errors as a non-existent field tag // and let getTypeInfo itself report the error.break }returnnil}// addFieldInfo adds finfo to tinfo.fields if there are no// conflicts, or if conflicts arise from previous fields that were// obtained from deeper embedded structures than finfo. In the latter// case, the conflicting entries are dropped.// A conflict occurs when the path (parent + name) to a field is// itself a prefix of another path, or when two paths match exactly.// It is okay for field paths to share a common, shorter prefix.func addFieldInfo( reflect.Type, *typeInfo, *fieldInfo) error {var []int:// First, figure all conflicts. Most working code will have none.for := range .fields { := &.fields[]if .flags&fMode != .flags&fMode {continue }if .xmlns != "" && .xmlns != "" && .xmlns != .xmlns {continue } := min(len(.parents), len(.parents))for := 0; < ; ++ {if .parents[] != .parents[] {continue } }iflen(.parents) > len(.parents) {if .parents[len(.parents)] == .name { = append(, ) } } elseiflen(.parents) < len(.parents) {if .parents[len(.parents)] == .name { = append(, ) } } else {if .name == .name && .xmlns == .xmlns { = append(, ) } } }// Without conflicts, add the new field and return.if == nil { .fields = append(.fields, *)returnnil }// If any conflict is shallower, ignore the new field. // This matches the Go field resolution on embedding.for , := range {iflen(.fields[].idx) < len(.idx) {returnnil } }// Otherwise, if any of them is at the same depth level, it's an error.for , := range { := &.fields[]iflen(.idx) == len(.idx) { := .FieldByIndex(.idx) := .FieldByIndex(.idx)return &TagPathError{, .Name, .Tag.Get("xml"), .Name, .Tag.Get("xml")} } }// Otherwise, the new field is shallower, and thus takes precedence, // so drop the conflicting fields from tinfo and append the new one.for := len() - 1; >= 0; -- { := []copy(.fields[:], .fields[+1:]) .fields = .fields[:len(.fields)-1] } .fields = append(.fields, *)returnnil}// A TagPathError represents an error in the unmarshaling process// caused by the use of field tags with conflicting paths.typeTagPathErrorstruct { Struct reflect.Type Field1, Tag1 string Field2, Tag2 string}func ( *TagPathError) () string {returnfmt.Sprintf("%s field %q with tag %q conflicts with field %q with tag %q", .Struct, .Field1, .Tag1, .Field2, .Tag2)}const ( initNilPointers = true dontInitNilPointers = false)// value returns v's field value corresponding to finfo.// It's equivalent to v.FieldByIndex(finfo.idx), but when passed// initNilPointers, it initializes and dereferences pointers as necessary.// When passed dontInitNilPointers and a nil pointer is reached, the function// returns a zero reflect.Value.func ( *fieldInfo) ( reflect.Value, bool) reflect.Value {for , := range .idx {if > 0 { := .Type()if .Kind() == reflect.Pointer && .Elem().Kind() == reflect.Struct {if .IsNil() {if ! {returnreflect.Value{} } .Set(reflect.New(.Type().Elem())) } = .Elem() } } = .Field() }return}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.