github.com/v21neolink/libvirt-go-xml@v1.0.5/xmlutil.go (about)

     1  /*
     2   * This file is part of the libvirt-go-xml project
     3   *
     4   * Permission is hereby granted, free of charge, to any person obtaining a copy
     5   * of this software and associated documentation files (the "Software"), to deal
     6   * in the Software without restriction, including without limitation the rights
     7   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     8   * copies of the Software, and to permit persons to whom the Software is
     9   * furnished to do so, subject to the following conditions:
    10   *
    11   * The above copyright notice and this permission notice shall be included in
    12   * all copies or substantial portions of the Software.
    13   *
    14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    20   * THE SOFTWARE.
    21   *
    22   * Copyright (C) 2017 Red Hat, Inc.
    23   *
    24   */
    25  
    26  package libvirtxml
    27  
    28  import (
    29  	"encoding/xml"
    30  	"fmt"
    31  	"strconv"
    32  	"strings"
    33  )
    34  
    35  type element struct {
    36  	XMLNS    string
    37  	Name     string
    38  	Attrs    map[string]string
    39  	Content  string
    40  	Children []*element
    41  }
    42  
    43  type elementstack []*element
    44  
    45  func (s *elementstack) push(v *element) {
    46  	*s = append(*s, v)
    47  }
    48  
    49  func (s *elementstack) pop() *element {
    50  	res := (*s)[len(*s)-1]
    51  	*s = (*s)[:len(*s)-1]
    52  	return res
    53  }
    54  
    55  func getNamespaceURI(xmlnsMap map[string]string, xmlns string, name xml.Name) string {
    56  	if name.Space != "" {
    57  		uri, ok := xmlnsMap[name.Space]
    58  		if !ok {
    59  			return "undefined://" + name.Space
    60  		} else {
    61  			return uri
    62  		}
    63  	} else {
    64  		return xmlns
    65  	}
    66  }
    67  
    68  func xmlName(xmlns string, name xml.Name) string {
    69  	if xmlns == "" {
    70  		return name.Local
    71  	}
    72  	return name.Local + "(" + xmlns + ")"
    73  }
    74  
    75  func loadXML(xmlstr string, ignoreNSDecl bool) (*element, error) {
    76  	xmlnsMap := make(map[string]string)
    77  	xmlr := strings.NewReader(xmlstr)
    78  
    79  	d := xml.NewDecoder(xmlr)
    80  	var root *element
    81  	stack := elementstack{}
    82  	for {
    83  		t, err := d.RawToken()
    84  		if err != nil {
    85  			return nil, err
    86  		}
    87  
    88  		var parent *element
    89  		if root != nil {
    90  			if len(stack) == 0 {
    91  				return nil, fmt.Errorf("Unexpectedly empty stack")
    92  			}
    93  			parent = stack[len(stack)-1]
    94  		}
    95  
    96  		switch t := t.(type) {
    97  		case xml.StartElement:
    98  			xmlns := ""
    99  			if parent != nil {
   100  				xmlns = parent.XMLNS
   101  			}
   102  			for _, a := range t.Attr {
   103  				if a.Name.Space == "xmlns" {
   104  					xmlnsMap[a.Name.Local] = a.Value
   105  				} else if a.Name.Space == "" && a.Name.Local == "xmlns" {
   106  					xmlns = a.Value
   107  				}
   108  			}
   109  			xmlns = getNamespaceURI(xmlnsMap, xmlns, t.Name)
   110  			child := &element{
   111  				XMLNS: xmlns,
   112  				Name:  xmlName(xmlns, t.Name),
   113  				Attrs: make(map[string]string),
   114  			}
   115  
   116  			for _, a := range t.Attr {
   117  				if a.Name.Space == "xmlns" {
   118  					continue
   119  				}
   120  				if a.Name.Space == "" && a.Name.Local == "xmlns" {
   121  					continue
   122  				}
   123  				attrNS := getNamespaceURI(xmlnsMap, "", a.Name)
   124  				child.Attrs[xmlName(attrNS, a.Name)] = a.Value
   125  			}
   126  			stack.push(child)
   127  			if root == nil {
   128  				root = child
   129  			} else {
   130  				parent.Children = append(parent.Children, child)
   131  				parent.Content = ""
   132  			}
   133  		case xml.EndElement:
   134  			stack.pop()
   135  		case xml.CharData:
   136  			if parent != nil && len(parent.Children) == 0 {
   137  				val := string(t)
   138  				if strings.TrimSpace(val) != "" {
   139  					parent.Content = val
   140  				}
   141  			}
   142  		}
   143  
   144  		if root != nil && len(stack) == 0 {
   145  			break
   146  		}
   147  	}
   148  
   149  	return root, nil
   150  }
   151  
   152  func testCompareValue(filename, path, key, expected, actual string) error {
   153  	if expected == actual {
   154  		return nil
   155  	}
   156  
   157  	i1, err1 := strconv.ParseInt(expected, 0, 64)
   158  	i2, err2 := strconv.ParseInt(actual, 0, 64)
   159  	if err1 == nil && err2 == nil && i1 == i2 {
   160  		return nil
   161  	}
   162  	path = path + "/@" + key
   163  	return fmt.Errorf("%s: %s: attribute actual value '%s' does not match expected value '%s'",
   164  		filename, path, actual, expected)
   165  }
   166  
   167  func testCompareElement(filename, expectPath, actualPath string, expect, actual *element, extraExpectNodes, extraActualNodes map[string]bool) error {
   168  	if expect.Name != actual.Name {
   169  		return fmt.Errorf("%s: name '%s' doesn't match '%s'",
   170  			expectPath, expect.Name, actual.Name)
   171  	}
   172  
   173  	expectAttr := expect.Attrs
   174  	for key, val := range actual.Attrs {
   175  		expectval, ok := expectAttr[key]
   176  		if !ok {
   177  			attrPath := actualPath + "/@" + key
   178  			if _, ok := extraActualNodes[attrPath]; ok {
   179  				continue
   180  			}
   181  			return fmt.Errorf("%s: %s: attribute in actual XML missing in expected XML",
   182  				filename, attrPath)
   183  		}
   184  		err := testCompareValue(filename, actualPath, key, expectval, val)
   185  		if err != nil {
   186  			return err
   187  		}
   188  		delete(expectAttr, key)
   189  	}
   190  	for key, _ := range expectAttr {
   191  		attrPath := expectPath + "/@" + key
   192  		if _, ok := extraExpectNodes[attrPath]; ok {
   193  			continue
   194  		}
   195  		return fmt.Errorf("%s: %s: attribute '%s'  in expected XML missing in actual XML",
   196  			filename, attrPath, expectAttr[key])
   197  	}
   198  
   199  	if expect.Content != actual.Content {
   200  		return fmt.Errorf("%s: %s: actual content '%s' does not match expected '%s'",
   201  			filename, actualPath, actual.Content, expect.Content)
   202  	}
   203  
   204  	used := make([]bool, len(actual.Children))
   205  	expectChildIndexes := make(map[string]uint)
   206  	actualChildIndexes := make(map[string]uint)
   207  	for _, expectChild := range expect.Children {
   208  		expectIndex, _ := expectChildIndexes[expectChild.Name]
   209  		expectChildIndexes[expectChild.Name] = expectIndex + 1
   210  		subExpectPath := fmt.Sprintf("%s/%s[%d]", expectPath, expectChild.Name, expectIndex)
   211  
   212  		var actualChild *element = nil
   213  		for i := 0; i < len(used); i++ {
   214  			if !used[i] && actual.Children[i].Name == expectChild.Name {
   215  				actualChild = actual.Children[i]
   216  				used[i] = true
   217  				break
   218  			}
   219  		}
   220  		if actualChild == nil {
   221  			if _, ok := extraExpectNodes[subExpectPath]; ok {
   222  				continue
   223  			}
   224  			return fmt.Errorf("%s: %s: element in expected XML missing in actual XML",
   225  				filename, subExpectPath)
   226  		}
   227  
   228  		actualIndex, _ := actualChildIndexes[actualChild.Name]
   229  		actualChildIndexes[actualChild.Name] = actualIndex + 1
   230  		subActualPath := fmt.Sprintf("%s/%s[%d]", actualPath, actualChild.Name, actualIndex)
   231  
   232  		err := testCompareElement(filename, subExpectPath, subActualPath, expectChild, actualChild, extraExpectNodes, extraActualNodes)
   233  		if err != nil {
   234  			return err
   235  		}
   236  	}
   237  
   238  	actualChildIndexes = make(map[string]uint)
   239  	for i, actualChild := range actual.Children {
   240  		actualIndex, _ := actualChildIndexes[actualChild.Name]
   241  		actualChildIndexes[actualChild.Name] = actualIndex + 1
   242  		if used[i] {
   243  			continue
   244  		}
   245  		subActualPath := fmt.Sprintf("%s/%s[%d]", actualPath, actualChild.Name, actualIndex)
   246  
   247  		if _, ok := extraActualNodes[subActualPath]; ok {
   248  			continue
   249  		}
   250  		return fmt.Errorf("%s: %s: element in actual XML missing in expected XML",
   251  			filename, subActualPath)
   252  	}
   253  
   254  	return nil
   255  }
   256  
   257  func makeExtraNodeMap(nodes []string) map[string]bool {
   258  	ret := make(map[string]bool)
   259  	for _, node := range nodes {
   260  		ret[node] = true
   261  	}
   262  	return ret
   263  }
   264  
   265  func testCompareXML(filename, expectStr, actualStr string, extraExpectNodes, extraActualNodes []string) error {
   266  	extraExpectNodeMap := makeExtraNodeMap(extraExpectNodes)
   267  	extraActualNodeMap := makeExtraNodeMap(extraActualNodes)
   268  
   269  	//fmt.Printf("%s\n", expectedstr)
   270  	expectRoot, err := loadXML(expectStr, true)
   271  	if err != nil {
   272  		return err
   273  	}
   274  	//fmt.Printf("%s\n", actualstr)
   275  	actualRoot, err := loadXML(actualStr, true)
   276  	if err != nil {
   277  		return err
   278  	}
   279  
   280  	if expectRoot.Name != actualRoot.Name {
   281  		return fmt.Errorf("%s: /: expected root element '%s' does not match actual '%s'",
   282  			filename, expectRoot.Name, actualRoot.Name)
   283  	}
   284  
   285  	err = testCompareElement(filename, "/"+expectRoot.Name+"[0]", "/"+actualRoot.Name+"[0]", expectRoot, actualRoot, extraExpectNodeMap, extraActualNodeMap)
   286  	if err != nil {
   287  		return err
   288  	}
   289  
   290  	return nil
   291  }