1 package xml
2
3 import (
4 "encoding/xml"
5 "fmt"
6 "io"
7 "sort"
8 "strings"
9 )
10
11
12 type XMLNode struct {
13 Name xml.Name `json:",omitempty"`
14 Children map[string][]*XMLNode `json:",omitempty"`
15 Text string `json:",omitempty"`
16 Attr []xml.Attr `json:",omitempty"`
17
18 namespaces map[string]string
19 parent *XMLNode
20 }
21
22
23 func NewXMLElement(name xml.Name) *XMLNode {
24 return &XMLNode{
25 Name: name,
26 Children: map[string][]*XMLNode{},
27 Attr: []xml.Attr{},
28 }
29 }
30
31
32 func (n *XMLNode) AddChild(child *XMLNode) {
33 child.parent = n
34 if _, ok := n.Children[child.Name.Local]; !ok {
35
36 n.Children[child.Name.Local] = []*XMLNode{}
37 }
38 n.Children[child.Name.Local] = append(n.Children[child.Name.Local], child)
39 }
40
41
42 func XMLToStruct(d *xml.Decoder, s *xml.StartElement, ignoreIndentation bool) (*XMLNode, error) {
43 out := &XMLNode{}
44
45 for {
46 tok, err := d.Token()
47 if err != nil {
48 if err == io.EOF {
49 break
50 } else {
51 return out, err
52 }
53 }
54
55 if tok == nil {
56 break
57 }
58
59 switch typed := tok.(type) {
60 case xml.CharData:
61 text := string(typed.Copy())
62 if ignoreIndentation {
63 text = strings.TrimSpace(text)
64 }
65 if len(text) != 0 {
66 out.Text = text
67 }
68 case xml.StartElement:
69 el := typed.Copy()
70 out.Attr = el.Attr
71 if out.Children == nil {
72 out.Children = map[string][]*XMLNode{}
73 }
74
75 name := typed.Name.Local
76 slice := out.Children[name]
77 if slice == nil {
78 slice = []*XMLNode{}
79 }
80 node, e := XMLToStruct(d, &el, ignoreIndentation)
81 out.findNamespaces()
82 if e != nil {
83 return out, e
84 }
85
86 node.Name = typed.Name
87 node.findNamespaces()
88
89
90 node.Attr = el.Attr
91
92 tempOut := *out
93
94
95 node.parent = &tempOut
96 slice = append(slice, node)
97 out.Children[name] = slice
98 case xml.EndElement:
99 if s != nil && s.Name.Local == typed.Name.Local {
100 return out, nil
101 }
102 out = &XMLNode{}
103 }
104 }
105 return out, nil
106 }
107
108 func (n *XMLNode) findNamespaces() {
109 ns := map[string]string{}
110 for _, a := range n.Attr {
111 if a.Name.Space == "xmlns" {
112 ns[a.Value] = a.Name.Local
113 }
114 }
115
116 n.namespaces = ns
117 }
118
119 func (n *XMLNode) findElem(name string) (string, bool) {
120 for node := n; node != nil; node = node.parent {
121 for _, a := range node.Attr {
122 namespace := a.Name.Space
123 if v, ok := node.namespaces[namespace]; ok {
124 namespace = v
125 }
126 if name == fmt.Sprintf("%s:%s", namespace, a.Name.Local) {
127 return a.Value, true
128 }
129 }
130 }
131 return "", false
132 }
133
134
135 func StructToXML(e *xml.Encoder, node *XMLNode, sorted bool) error {
136 var err error
137
138 attrs := node.Attr
139 if sorted {
140 sortedAttrs := make([]xml.Attr, len(attrs))
141 for _, k := range node.Attr {
142 sortedAttrs = append(sortedAttrs, k)
143 }
144 sort.Sort(xmlAttrSlice(sortedAttrs))
145 attrs = sortedAttrs
146 }
147
148 st := xml.StartElement{Name: node.Name, Attr: attrs}
149 e.EncodeToken(st)
150
151
152 if node.Text != "" {
153 e.EncodeToken(xml.CharData([]byte(node.Text)))
154 } else if sorted {
155 sortedNames := []string{}
156 for k := range node.Children {
157 sortedNames = append(sortedNames, k)
158 }
159 sort.Strings(sortedNames)
160
161 for _, k := range sortedNames {
162
163 flattenedNodes := node.Children[k]
164
165 if len(flattenedNodes) > 1 {
166
167 flattenedNodes, err = sortFlattenedNodes(flattenedNodes)
168 if err != nil {
169 return err
170 }
171 }
172
173 for _, v := range flattenedNodes {
174 err = StructToXML(e, v, sorted)
175 if err != nil {
176 return err
177 }
178 }
179 }
180 } else {
181 for _, c := range node.Children {
182 for _, v := range c {
183 err = StructToXML(e, v, sorted)
184 if err != nil {
185 return err
186 }
187 }
188 }
189 }
190
191 e.EncodeToken(xml.EndElement{Name: node.Name})
192 return e.Flush()
193 }
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209 func sortFlattenedNodes(nodes []*XMLNode) ([]*XMLNode, error) {
210 var sortedNodes []*XMLNode
211
212
213
214 concreteNodeMap := make(map[string][]*XMLNode, 0)
215
216
217
218 flatListNodeMap := make(map[string][]*XMLNode, 0)
219
220
221
222
223 flatMapNodeMap := make(map[string][]*XMLNode, 0)
224
225
226 sortedNodesWithConcreteValue := []string{}
227
228
229 sortedNodesWithListValue := []string{}
230
231
232 sortedNodesWithMapValue := []string{}
233
234 for _, node := range nodes {
235
236 if len(node.Children) == 0 {
237 sortedNodesWithConcreteValue = append(sortedNodesWithConcreteValue, node.Text)
238 if v, ok := concreteNodeMap[node.Text]; ok {
239 concreteNodeMap[node.Text] = append(v, node)
240 } else {
241 concreteNodeMap[node.Text] = []*XMLNode{node}
242 }
243 }
244
245
246 if len(node.Children) == 1 {
247 for _, nestedNodes := range node.Children {
248 nestedNodeName := nestedNodes[0].Name.Local
249
250
251 sortedNodesWithListValue = append(sortedNodesWithListValue, nestedNodeName)
252
253 if v, ok := flatListNodeMap[nestedNodeName]; ok {
254 flatListNodeMap[nestedNodeName] = append(v, nestedNodes[0])
255 } else {
256 flatListNodeMap[nestedNodeName] = []*XMLNode{nestedNodes[0]}
257 }
258 }
259 }
260
261
262 if len(node.Children) == 2 {
263 nestedPair := []*XMLNode{}
264 for _, k := range node.Children {
265 nestedPair = append(nestedPair, k[0])
266 }
267
268 comparableValues := []string{nestedPair[0].Name.Local, nestedPair[1].Name.Local}
269 sort.Strings(comparableValues)
270
271 comparableValue := comparableValues[0]
272 for _, nestedNode := range nestedPair {
273 if comparableValue == nestedNode.Name.Local && len(nestedNode.Children) != 0 {
274
275 comparableValue = comparableValues[1]
276 continue
277 }
278
279
280 if comparableValue == nestedNode.Name.Local {
281
282 comparableValue = nestedNode.Text
283 sortedNodesWithMapValue = append(sortedNodesWithMapValue, comparableValue)
284
285 if v, ok := flatMapNodeMap[comparableValue]; ok {
286 flatMapNodeMap[comparableValue] = append(v, node)
287 } else {
288 flatMapNodeMap[comparableValue] = []*XMLNode{node}
289 }
290 break
291 }
292 }
293 }
294
295
296 if len(node.Children) > 2 {
297 return nodes, fmt.Errorf("malformed xml: multiple nodes with same key name exist, " +
298 "but are not associated with flattened maps (2 children) or list (0 or 1 child)")
299 }
300 }
301
302
303
304 sort.Strings(sortedNodesWithConcreteValue)
305 for _, name := range sortedNodesWithConcreteValue {
306 for _, node := range concreteNodeMap[name] {
307 sortedNodes = append(sortedNodes, node)
308 }
309 }
310
311
312
313 sort.Strings(sortedNodesWithListValue)
314 for _, name := range sortedNodesWithListValue {
315
316 if len(flatListNodeMap[name]) > 1 {
317
318 nestedFlattenedList, err := sortFlattenedNodes(flatListNodeMap[name])
319 if err != nil {
320 return nodes, err
321 }
322
323 for _, nestedNode := range nestedFlattenedList {
324 sortedNodes = append(sortedNodes, nestedNode)
325 }
326 } else {
327
328 sortedNodes = append(sortedNodes, flatListNodeMap[name][0])
329 }
330 }
331
332
333 sort.Strings(sortedNodesWithMapValue)
334 for _, name := range sortedNodesWithMapValue {
335 sortedNodes = append(sortedNodes, flatMapNodeMap[name][0])
336 }
337
338 return sortedNodes, nil
339 }
340
View as plain text