1package xmlstruct
2
3import (
4	"encoding/xml"
5	"fmt"
6	"reflect"
7	"strings"
8
9	"github.com/ChrisTrenkamp/goxpath/tree"
10)
11
12type XMLEle struct {
13	Val     reflect.Value
14	pos     int
15	prnt    tree.Elem
16	prntTag string
17}
18
19func (x *XMLEle) ResValue() string {
20	if x.Val.Kind() != reflect.Struct {
21		return fmt.Sprintf("%v", x.Val.Interface())
22	}
23
24	ret := ""
25	for _, i := range x.GetChildren() {
26		switch i.GetNodeType() {
27		case tree.NtChd, tree.NtElem, tree.NtRoot:
28			ret += i.ResValue()
29		}
30	}
31	return ret
32}
33
34func (x *XMLEle) Pos() int {
35	return x.pos
36}
37
38func (x *XMLEle) GetToken() xml.Token {
39	ret := xml.StartElement{}
40
41	if x.prntTag != "" {
42		ret.Name = getTagInfo(x.prntTag).name
43	} else {
44		ret.Name = getXMLName(x.Val)
45	}
46
47	return ret
48}
49
50func (x *XMLEle) GetParent() tree.Elem {
51	return x.prnt
52}
53
54func (x *XMLEle) GetNodeType() tree.NodeType {
55	return tree.NtElem
56}
57
58func (x *XMLEle) GetChildren() []tree.Node {
59	n, _ := getChildren(x, x.Val, x.pos, false)
60	return n
61}
62
63func (x *XMLEle) GetAttrs() []tree.Node {
64	n, _ := getChildren(x, x.Val, x.pos, true)
65	return n
66}
67
68type tagInfo struct {
69	name    xml.Name
70	attr    bool
71	cdata   bool
72	comment bool
73}
74
75func getTagInfo(tag string) (ret tagInfo) {
76	spl := strings.Split(tag, ",")
77	name := strings.Split(spl[0], " ")
78	if len(spl) >= 2 {
79		for i := 1; i < len(spl); i++ {
80			if spl[i] == "chardata" || spl[i] == "cdata" {
81				ret.cdata = true
82			} else if spl[i] == "attr" {
83				ret.attr = true
84			} else if spl[i] == "comment" {
85				ret.comment = true
86			}
87		}
88	}
89
90	if len(name) == 2 {
91		ret.name.Space = name[1]
92	}
93
94	ret.name.Local = name[0]
95	return
96}
97
98func getXMLName(val reflect.Value) xml.Name {
99	n := val.FieldByName("XMLName")
100	zero := reflect.Value{}
101
102	if zero != n {
103		field, _ := val.Type().FieldByName("XMLName")
104		tagInfo := getTagInfo(field.Tag.Get("xml"))
105		if tagInfo.name.Local != "" {
106			return tagInfo.name
107		}
108
109		if name, ok := n.Interface().(xml.Name); ok {
110			return name
111		}
112	}
113
114	return xml.Name{Local: val.Type().Name()}
115}
116
117func getChildren(x *XMLEle, val reflect.Value, pos int, getAttrs bool) ([]tree.Node, int) {
118	if val.Kind() == reflect.Ptr {
119		val = val.Elem()
120	}
121
122	if val.Kind() == reflect.Interface {
123		val = reflect.ValueOf(val.Interface())
124	}
125
126	if val.Kind() != reflect.Struct {
127		if getAttrs {
128			return []tree.Node{}, x.pos + 1
129		}
130
131		return []tree.Node{&XMLNode{
132			Val:      x.Val,
133			pos:      x.pos + 1,
134			prnt:     x,
135			nodeType: tree.NtChd,
136			prntTag:  "",
137		}}, x.pos + 2
138	}
139
140	ret := make([]tree.Node, 0, val.NumField())
141
142	for i := 0; i < val.NumField(); i++ {
143		field := val.Field(i)
144		name := val.Type().Field(i).Name
145
146		if val.Type().Field(i).Anonymous {
147			nodes, newPos := getChildren(x, field, pos, getAttrs)
148			ret = append(ret, nodes...)
149			pos = newPos
150			continue
151		}
152
153		tag := val.Type().Field(i).Tag.Get("xml")
154
155		if tag == "-" || name == "XMLName" {
156			continue
157		}
158
159		tagInfo := getTagInfo(tag)
160
161		pos++
162
163		if tagInfo.attr || tagInfo.cdata || tagInfo.comment {
164			if !getAttrs && tagInfo.attr || getAttrs && !tagInfo.attr {
165				continue
166			}
167
168			child := &XMLNode{
169				Val:     field,
170				pos:     pos,
171				prnt:    x,
172				prntTag: tag,
173			}
174
175			if tagInfo.attr {
176				child.nodeType = tree.NtAttr
177			} else if tagInfo.cdata {
178				child.nodeType = tree.NtChd
179			} else {
180				child.nodeType = tree.NtComm
181			}
182
183			if tagInfo.name.Local == "" {
184				child.prntTag = name
185			}
186
187			ret = append(ret, child)
188
189			continue
190		}
191
192		if getAttrs {
193			continue
194		}
195
196		child := &XMLEle{Val: field, pos: pos}
197
198		if tag == "" {
199			tag = name
200		}
201
202		child.prntTag = tag
203		ret = append(ret, child)
204	}
205
206	return ret, pos + 1
207}
208