...

Source file src/github.com/aws/smithy-go/testing/xml/xmlToStruct.go

Documentation: github.com/aws/smithy-go/testing/xml

     1  package xml
     2  
     3  import (
     4  	"encoding/xml"
     5  	"fmt"
     6  	"io"
     7  	"sort"
     8  	"strings"
     9  )
    10  
    11  // A XMLNode contains the values to be encoded or decoded.
    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  // NewXMLElement returns a pointer to a new XMLNode initialized to default values.
    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  // AddChild adds child to the XMLNode.
    32  func (n *XMLNode) AddChild(child *XMLNode) {
    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] = []*XMLNode{}
    37  	}
    38  	n.Children[child.Name.Local] = append(n.Children[child.Name.Local], child)
    39  }
    40  
    41  // XMLToStruct converts a xml.Decoder stream to XMLNode with nested values.
    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  			// 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 = &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  // StructToXML writes an XMLNode to a xml.Encoder as tokens.
   135  func StructToXML(e *xml.Encoder, node *XMLNode, 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  // XMLNode 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 []*XMLNode) ([]*XMLNode, error) {
   210  	var sortedNodes []*XMLNode
   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][]*XMLNode, 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][]*XMLNode, 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][]*XMLNode, 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] = []*XMLNode{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] = []*XMLNode{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 := []*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  					// 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] = []*XMLNode{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  }
   340  

View as plain text