func sortFlattenedNodes()

in testing/xml/xmlToStruct.go [209:339]


func sortFlattenedNodes(nodes []*XMLNode) ([]*XMLNode, error) {
	var sortedNodes []*XMLNode

	// concreteNodeMap stores concrete value associated with a list of nodes
	// This is possible in case multiple members of a flatList has same values.
	concreteNodeMap := make(map[string][]*XMLNode, 0)

	// flatListNodeMap stores flat list or wrapped list members associated with a list of nodes
	// This will have only flattened list with members that are Nodes and not concrete values.
	flatListNodeMap := make(map[string][]*XMLNode, 0)

	// flatMapNodeMap stores flat map or map entry members associated with a list of nodes
	// This will have only flattened map concrete value members. It is possible to limit this
	// to concrete value as map key is expected to be concrete.
	flatMapNodeMap := make(map[string][]*XMLNode, 0)

	// nodes with concrete value are prioritized and appended based on sorting order
	sortedNodesWithConcreteValue := []string{}

	// list with nested nodes are second in priority and appended based on sorting order
	sortedNodesWithListValue := []string{}

	// map are last in priority and appended based on sorting order
	sortedNodesWithMapValue := []string{}

	for _, node := range nodes {
		// node has no children element, then we consider it as having concrete value
		if len(node.Children) == 0 {
			sortedNodesWithConcreteValue = append(sortedNodesWithConcreteValue, node.Text)
			if v, ok := concreteNodeMap[node.Text]; ok {
				concreteNodeMap[node.Text] = append(v, node)
			} else {
				concreteNodeMap[node.Text] = []*XMLNode{node}
			}
		}

		// if node has a single child, then it is a flattened list node
		if len(node.Children) == 1 {
			for _, nestedNodes := range node.Children {
				nestedNodeName := nestedNodes[0].Name.Local

				// append to sorted node name for list value
				sortedNodesWithListValue = append(sortedNodesWithListValue, nestedNodeName)

				if v, ok := flatListNodeMap[nestedNodeName]; ok {
					flatListNodeMap[nestedNodeName] = append(v, nestedNodes[0])
				} else {
					flatListNodeMap[nestedNodeName] = []*XMLNode{nestedNodes[0]}
				}
			}
		}

		// if node has two children, then it is a flattened map node
		if len(node.Children) == 2 {
			nestedPair := []*XMLNode{}
			for _, k := range node.Children {
				nestedPair = append(nestedPair, k[0])
			}

			comparableValues := []string{nestedPair[0].Name.Local, nestedPair[1].Name.Local}
			sort.Strings(comparableValues)

			comparableValue := comparableValues[0]
			for _, nestedNode := range nestedPair {
				if comparableValue == nestedNode.Name.Local && len(nestedNode.Children) != 0 {
					// if value was selected and is nested node, skip it and use key instead
					comparableValue = comparableValues[1]
					continue
				}

				// now we are certain there is no nested node
				if comparableValue == nestedNode.Name.Local {
					// get chardata for comparison
					comparableValue = nestedNode.Text
					sortedNodesWithMapValue = append(sortedNodesWithMapValue, comparableValue)

					if v, ok := flatMapNodeMap[comparableValue]; ok {
						flatMapNodeMap[comparableValue] = append(v, node)
					} else {
						flatMapNodeMap[comparableValue] = []*XMLNode{node}
					}
					break
				}
			}
		}

		// we don't support multiple same name nodes in an xml doc except for in flattened maps, list.
		if len(node.Children) > 2 {
			return nodes, fmt.Errorf("malformed xml: multiple nodes with same key name exist, " +
				"but are not associated with flattened maps (2 children) or list (0 or 1 child)")
		}
	}

	// sort concrete value node name list and append corresponding nodes
	// to sortedNodes
	sort.Strings(sortedNodesWithConcreteValue)
	for _, name := range sortedNodesWithConcreteValue {
		for _, node := range concreteNodeMap[name] {
			sortedNodes = append(sortedNodes, node)
		}
	}

	// sort nested nodes with a list and append corresponding nodes
	// to sortedNodes
	sort.Strings(sortedNodesWithListValue)
	for _, name := range sortedNodesWithListValue {
		// if two nested nodes have same name, then sort them separately.
		if len(flatListNodeMap[name]) > 1 {
			// return nodes, fmt.Errorf("flat list node name are %s %v", flatListNodeMap[name][0].Name.Local, len(flatListNodeMap[name]))
			nestedFlattenedList, err := sortFlattenedNodes(flatListNodeMap[name])
			if err != nil {
				return nodes, err
			}
			// append the identical but sorted nodes
			for _, nestedNode := range nestedFlattenedList {
				sortedNodes = append(sortedNodes, nestedNode)
			}
		} else {
			// append the sorted nodes
			sortedNodes = append(sortedNodes, flatListNodeMap[name][0])
		}
	}

	// sorted nodes with a map and append corresponding nodes to sortedNodes
	sort.Strings(sortedNodesWithMapValue)
	for _, name := range sortedNodesWithMapValue {
		sortedNodes = append(sortedNodes, flatMapNodeMap[name][0])
	}

	return sortedNodes, nil
}