github.com/aavshr/aws-sdk-go@v1.41.3/internal/smithytesting/xml/xmlToStruct.go (about) 1 package xml 2 3 import ( 4 "encoding/xml" 5 "fmt" 6 "io" 7 "sort" 8 "strings" 9 ) 10 11 // A Node contains the values to be encoded or decoded. 12 type Node struct { 13 Name xml.Name `json:",omitempty"` 14 Children map[string][]*Node `json:",omitempty"` 15 Text string `json:",omitempty"` 16 Attr []xml.Attr `json:",omitempty"` 17 18 namespaces map[string]string 19 parent *Node 20 } 21 22 // NewXMLElement returns a pointer to a new Node initialized to default values. 23 func NewXMLElement(name xml.Name) *Node { 24 return &Node{ 25 Name: name, 26 Children: map[string][]*Node{}, 27 Attr: []xml.Attr{}, 28 } 29 } 30 31 // AddChild adds child to the Node. 32 func (n *Node) AddChild(child *Node) { 33 child.parent = n 34 if _, ok := n.Children[child.Name.Local]; !ok { 35 // flattened will have multiple children with same tag name 36 n.Children[child.Name.Local] = []*Node{} 37 } 38 n.Children[child.Name.Local] = append(n.Children[child.Name.Local], child) 39 } 40 41 // ToStruct converts a xml.Decoder stream to Node with nested values. 42 func ToStruct(d *xml.Decoder, s *xml.StartElement, ignoreIndentation bool) (*Node, error) { 43 out := &Node{} 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][]*Node{} 73 } 74 75 name := typed.Name.Local 76 slice := out.Children[name] 77 if slice == nil { 78 slice = []*Node{} 79 } 80 node, e := ToStruct(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 // Add attributes onto the node 90 node.Attr = el.Attr 91 92 tempOut := *out 93 // Save into a temp variable, simply because out gets squashed during 94 // loop iterations 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 { // matching end token 100 return out, nil 101 } 102 out = &Node{} 103 } 104 } 105 return out, nil 106 } 107 108 func (n *Node) 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 *Node) 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 // StructToXML writes an Node to a xml.Encoder as tokens. 135 func StructToXML(e *xml.Encoder, node *Node, sorted bool) error { 136 var err error 137 // Sort Attributes 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 // return fmt.Errorf("encoder string : %s, %s, %s", node.Name.Local, node.Name.Space, st.Attr) 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 // we should sort the []*xml.Node for each key if len >1 163 flattenedNodes := node.Children[k] 164 // Meaning this has multiple nodes 165 if len(flattenedNodes) > 1 { 166 // sort flattened nodes 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 // sortFlattenedNodes sorts nodes with nodes having same element tag 196 // but overall different values. The function will return list of pointer to 197 // Node and an error. 198 // 199 // Overall sort order is followed is: 200 // Nodes with concrete value (no nested node as value) are given precedence 201 // and are added to list after sorting them 202 // 203 // Next nested nodes within a flattened list are given precedence. 204 // 205 // Next nodes within a flattened map are sorted based on either key or value 206 // which ever has lower value and then added to the global sorted list. 207 // If value was initially chosen, but has nested nodes; key will be chosen as comparable 208 // as it is unique and will always have concrete data ie. string. 209 func sortFlattenedNodes(nodes []*Node) ([]*Node, error) { 210 var sortedNodes []*Node 211 212 // concreteNodeMap stores concrete value associated with a list of nodes 213 // This is possible in case multiple members of a flatList has same values. 214 concreteNodeMap := make(map[string][]*Node, 0) 215 216 // flatListNodeMap stores flat list or wrapped list members associated with a list of nodes 217 // This will have only flattened list with members that are Nodes and not concrete values. 218 flatListNodeMap := make(map[string][]*Node, 0) 219 220 // flatMapNodeMap stores flat map or map entry members associated with a list of nodes 221 // This will have only flattened map concrete value members. It is possible to limit this 222 // to concrete value as map key is expected to be concrete. 223 flatMapNodeMap := make(map[string][]*Node, 0) 224 225 // nodes with concrete value are prioritized and appended based on sorting order 226 sortedNodesWithConcreteValue := []string{} 227 228 // list with nested nodes are second in priority and appended based on sorting order 229 sortedNodesWithListValue := []string{} 230 231 // map are last in priority and appended based on sorting order 232 sortedNodesWithMapValue := []string{} 233 234 for _, node := range nodes { 235 // node has no children element, then we consider it as having concrete value 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] = []*Node{node} 242 } 243 } 244 245 // if node has a single child, then it is a flattened list node 246 if len(node.Children) == 1 { 247 for _, nestedNodes := range node.Children { 248 nestedNodeName := nestedNodes[0].Name.Local 249 250 // append to sorted node name for list value 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] = []*Node{nestedNodes[0]} 257 } 258 } 259 } 260 261 // if node has two children, then it is a flattened map node 262 if len(node.Children) == 2 { 263 nestedPair := []*Node{} 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 // if value was selected and is nested node, skip it and use key instead 275 comparableValue = comparableValues[1] 276 continue 277 } 278 279 // now we are certain there is no nested node 280 if comparableValue == nestedNode.Name.Local { 281 // get chardata for comparison 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] = []*Node{node} 289 } 290 break 291 } 292 } 293 } 294 295 // we don't support multiple same name nodes in an xml doc except for in flattened maps, list. 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 // sort concrete value node name list and append corresponding nodes 303 // to sortedNodes 304 sort.Strings(sortedNodesWithConcreteValue) 305 for _, name := range sortedNodesWithConcreteValue { 306 for _, node := range concreteNodeMap[name] { 307 sortedNodes = append(sortedNodes, node) 308 } 309 } 310 311 // sort nested nodes with a list and append corresponding nodes 312 // to sortedNodes 313 sort.Strings(sortedNodesWithListValue) 314 for _, name := range sortedNodesWithListValue { 315 // if two nested nodes have same name, then sort them separately. 316 if len(flatListNodeMap[name]) > 1 { 317 // return nodes, fmt.Errorf("flat list node name are %s %v", flatListNodeMap[name][0].Name.Local, len(flatListNodeMap[name])) 318 nestedFlattenedList, err := sortFlattenedNodes(flatListNodeMap[name]) 319 if err != nil { 320 return nodes, err 321 } 322 // append the identical but sorted nodes 323 for _, nestedNode := range nestedFlattenedList { 324 sortedNodes = append(sortedNodes, nestedNode) 325 } 326 } else { 327 // append the sorted nodes 328 sortedNodes = append(sortedNodes, flatListNodeMap[name][0]) 329 } 330 } 331 332 // sorted nodes with a map and append corresponding nodes to sortedNodes 333 sort.Strings(sortedNodesWithMapValue) 334 for _, name := range sortedNodesWithMapValue { 335 sortedNodes = append(sortedNodes, flatMapNodeMap[name][0]) 336 } 337 338 return sortedNodes, nil 339 }