| | |
| | |
| | |
| |
|
| | package xml |
| |
|
| | import ( |
| | "fmt" |
| | "reflect" |
| | "strings" |
| | "sync" |
| | ) |
| |
|
| | |
| | type typeInfo struct { |
| | xmlname *fieldInfo |
| | fields []fieldInfo |
| | } |
| |
|
| | |
| | type fieldInfo struct { |
| | idx []int |
| | name string |
| | xmlns string |
| | flags fieldFlags |
| | parents []string |
| | } |
| |
|
| | type fieldFlags int |
| |
|
| | const ( |
| | 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 |
| |
|
| | var nameType = reflect.TypeFor[Name]() |
| |
|
| | |
| | |
| | func getTypeInfo(typ reflect.Type) (*typeInfo, error) { |
| | if ti, ok := tinfoMap.Load(typ); ok { |
| | return ti.(*typeInfo), nil |
| | } |
| |
|
| | tinfo := &typeInfo{} |
| | if typ.Kind() == reflect.Struct && typ != nameType { |
| | n := typ.NumField() |
| | for i := 0; i < n; i++ { |
| | f := typ.Field(i) |
| | if (!f.IsExported() && !f.Anonymous) || f.Tag.Get("xml") == "-" { |
| | continue |
| | } |
| |
|
| | |
| | if f.Anonymous { |
| | t := f.Type |
| | if t.Kind() == reflect.Pointer { |
| | t = t.Elem() |
| | } |
| | if t.Kind() == reflect.Struct { |
| | inner, err := getTypeInfo(t) |
| | if err != nil { |
| | return nil, err |
| | } |
| | if tinfo.xmlname == nil { |
| | tinfo.xmlname = inner.xmlname |
| | } |
| | for _, finfo := range inner.fields { |
| | finfo.idx = append([]int{i}, finfo.idx...) |
| | if err := addFieldInfo(typ, tinfo, &finfo); err != nil { |
| | return nil, err |
| | } |
| | } |
| | continue |
| | } |
| | } |
| |
|
| | finfo, err := structFieldInfo(typ, &f) |
| | if err != nil { |
| | return nil, err |
| | } |
| |
|
| | if f.Name == xmlName { |
| | tinfo.xmlname = finfo |
| | continue |
| | } |
| |
|
| | |
| | if err := addFieldInfo(typ, tinfo, finfo); err != nil { |
| | return nil, err |
| | } |
| | } |
| | } |
| |
|
| | ti, _ := tinfoMap.LoadOrStore(typ, tinfo) |
| | return ti.(*typeInfo), nil |
| | } |
| |
|
| | |
| | func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) { |
| | finfo := &fieldInfo{idx: f.Index} |
| |
|
| | |
| | tag := f.Tag.Get("xml") |
| | if ns, t, ok := strings.Cut(tag, " "); ok { |
| | finfo.xmlns, tag = ns, t |
| | } |
| |
|
| | |
| | tokens := strings.Split(tag, ",") |
| | if len(tokens) == 1 { |
| | finfo.flags = fElement |
| | } else { |
| | tag = tokens[0] |
| | for _, flag := range tokens[1:] { |
| | switch flag { |
| | case "attr": |
| | finfo.flags |= fAttr |
| | case "cdata": |
| | finfo.flags |= fCDATA |
| | case "chardata": |
| | finfo.flags |= fCharData |
| | case "innerxml": |
| | finfo.flags |= fInnerXML |
| | case "comment": |
| | finfo.flags |= fComment |
| | case "any": |
| | finfo.flags |= fAny |
| | case "omitempty": |
| | finfo.flags |= fOmitEmpty |
| | } |
| | } |
| |
|
| | |
| | valid := true |
| | switch mode := finfo.flags & fMode; mode { |
| | case 0: |
| | finfo.flags |= fElement |
| | case fAttr, fCDATA, fCharData, fInnerXML, fComment, fAny, fAny | fAttr: |
| | if f.Name == xmlName || tag != "" && mode != fAttr { |
| | valid = false |
| | } |
| | default: |
| | |
| | valid = false |
| | } |
| | if finfo.flags&fMode == fAny { |
| | finfo.flags |= fElement |
| | } |
| | if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 { |
| | valid = false |
| | } |
| | if !valid { |
| | return nil, fmt.Errorf("xml: invalid tag in field %s of type %s: %q", |
| | f.Name, typ, f.Tag.Get("xml")) |
| | } |
| | } |
| |
|
| | |
| | if finfo.xmlns != "" && tag == "" { |
| | return nil, fmt.Errorf("xml: namespace without name in field %s of type %s: %q", |
| | f.Name, typ, f.Tag.Get("xml")) |
| | } |
| |
|
| | if f.Name == xmlName { |
| | |
| | |
| | |
| | finfo.name = tag |
| | return finfo, nil |
| | } |
| |
|
| | if tag == "" { |
| | |
| | |
| | |
| | if xmlname := lookupXMLName(f.Type); xmlname != nil { |
| | finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name |
| | } else { |
| | finfo.name = f.Name |
| | } |
| | return finfo, nil |
| | } |
| |
|
| | |
| | parents := strings.Split(tag, ">") |
| | if parents[0] == "" { |
| | parents[0] = f.Name |
| | } |
| | if parents[len(parents)-1] == "" { |
| | return nil, fmt.Errorf("xml: trailing '>' in field %s of type %s", f.Name, typ) |
| | } |
| | finfo.name = parents[len(parents)-1] |
| | if len(parents) > 1 { |
| | if (finfo.flags & fElement) == 0 { |
| | return nil, fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ",")) |
| | } |
| | finfo.parents = parents[:len(parents)-1] |
| | } |
| |
|
| | |
| | |
| | |
| | if finfo.flags&fElement != 0 { |
| | ftyp := f.Type |
| | xmlname := lookupXMLName(ftyp) |
| | if xmlname != nil && xmlname.name != finfo.name { |
| | return nil, fmt.Errorf("xml: name %q in tag of %s.%s conflicts with name %q in %s.XMLName", |
| | finfo.name, typ, f.Name, xmlname.name, ftyp) |
| | } |
| | } |
| | return finfo, nil |
| | } |
| |
|
| | |
| | |
| | |
| | func lookupXMLName(typ reflect.Type) (xmlname *fieldInfo) { |
| | for typ.Kind() == reflect.Pointer { |
| | typ = typ.Elem() |
| | } |
| | if typ.Kind() != reflect.Struct { |
| | return nil |
| | } |
| | for i, n := 0, typ.NumField(); i < n; i++ { |
| | f := typ.Field(i) |
| | if f.Name != xmlName { |
| | continue |
| | } |
| | finfo, err := structFieldInfo(typ, &f) |
| | if err == nil && finfo.name != "" { |
| | return finfo |
| | } |
| | |
| | |
| | break |
| | } |
| | return nil |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error { |
| | var conflicts []int |
| | Loop: |
| | |
| | for i := range tinfo.fields { |
| | oldf := &tinfo.fields[i] |
| | if oldf.flags&fMode != newf.flags&fMode { |
| | continue |
| | } |
| | if oldf.xmlns != "" && newf.xmlns != "" && oldf.xmlns != newf.xmlns { |
| | continue |
| | } |
| | minl := min(len(newf.parents), len(oldf.parents)) |
| | for p := 0; p < minl; p++ { |
| | if oldf.parents[p] != newf.parents[p] { |
| | continue Loop |
| | } |
| | } |
| | if len(oldf.parents) > len(newf.parents) { |
| | if oldf.parents[len(newf.parents)] == newf.name { |
| | conflicts = append(conflicts, i) |
| | } |
| | } else if len(oldf.parents) < len(newf.parents) { |
| | if newf.parents[len(oldf.parents)] == oldf.name { |
| | conflicts = append(conflicts, i) |
| | } |
| | } else { |
| | if newf.name == oldf.name && newf.xmlns == oldf.xmlns { |
| | conflicts = append(conflicts, i) |
| | } |
| | } |
| | } |
| | |
| | if conflicts == nil { |
| | tinfo.fields = append(tinfo.fields, *newf) |
| | return nil |
| | } |
| |
|
| | |
| | |
| | for _, i := range conflicts { |
| | if len(tinfo.fields[i].idx) < len(newf.idx) { |
| | return nil |
| | } |
| | } |
| |
|
| | |
| | for _, i := range conflicts { |
| | oldf := &tinfo.fields[i] |
| | if len(oldf.idx) == len(newf.idx) { |
| | f1 := typ.FieldByIndex(oldf.idx) |
| | f2 := typ.FieldByIndex(newf.idx) |
| | return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")} |
| | } |
| | } |
| |
|
| | |
| | |
| | for c := len(conflicts) - 1; c >= 0; c-- { |
| | i := conflicts[c] |
| | copy(tinfo.fields[i:], tinfo.fields[i+1:]) |
| | tinfo.fields = tinfo.fields[:len(tinfo.fields)-1] |
| | } |
| | tinfo.fields = append(tinfo.fields, *newf) |
| | return nil |
| | } |
| |
|
| | |
| | |
| | type TagPathError struct { |
| | Struct reflect.Type |
| | Field1, Tag1 string |
| | Field2, Tag2 string |
| | } |
| |
|
| | func (e *TagPathError) Error() string { |
| | 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) |
| | } |
| |
|
| | const ( |
| | initNilPointers = true |
| | dontInitNilPointers = false |
| | ) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | func (finfo *fieldInfo) value(v reflect.Value, shouldInitNilPointers bool) reflect.Value { |
| | for i, x := range finfo.idx { |
| | if i > 0 { |
| | t := v.Type() |
| | if t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Struct { |
| | if v.IsNil() { |
| | if !shouldInitNilPointers { |
| | return reflect.Value{} |
| | } |
| | v.Set(reflect.New(v.Type().Elem())) |
| | } |
| | v = v.Elem() |
| | } |
| | } |
| | v = v.Field(x) |
| | } |
| | return v |
| | } |
| |
|