mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-05-30 04:20:55 +08:00
292 lines
7.7 KiB
Go
292 lines
7.7 KiB
Go
/*
|
|
* This file is part of the libvirt-go-xml project
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*
|
|
* Copyright (C) 2017 Red Hat, Inc.
|
|
*
|
|
*/
|
|
|
|
package libvirtxml
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type element struct {
|
|
XMLNS string
|
|
Name string
|
|
Attrs map[string]string
|
|
Content string
|
|
Children []*element
|
|
}
|
|
|
|
type elementstack []*element
|
|
|
|
func (s *elementstack) push(v *element) {
|
|
*s = append(*s, v)
|
|
}
|
|
|
|
func (s *elementstack) pop() *element {
|
|
res := (*s)[len(*s)-1]
|
|
*s = (*s)[:len(*s)-1]
|
|
return res
|
|
}
|
|
|
|
func getNamespaceURI(xmlnsMap map[string]string, xmlns string, name xml.Name) string {
|
|
if name.Space != "" {
|
|
uri, ok := xmlnsMap[name.Space]
|
|
if !ok {
|
|
return "undefined://" + name.Space
|
|
} else {
|
|
return uri
|
|
}
|
|
} else {
|
|
return xmlns
|
|
}
|
|
}
|
|
|
|
func xmlName(xmlns string, name xml.Name) string {
|
|
if xmlns == "" {
|
|
return name.Local
|
|
}
|
|
return name.Local + "(" + xmlns + ")"
|
|
}
|
|
|
|
func loadXML(xmlstr string, ignoreNSDecl bool) (*element, error) {
|
|
xmlnsMap := make(map[string]string)
|
|
xmlr := strings.NewReader(xmlstr)
|
|
|
|
d := xml.NewDecoder(xmlr)
|
|
var root *element
|
|
stack := elementstack{}
|
|
for {
|
|
t, err := d.RawToken()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var parent *element
|
|
if root != nil {
|
|
if len(stack) == 0 {
|
|
return nil, fmt.Errorf("Unexpectedly empty stack")
|
|
}
|
|
parent = stack[len(stack)-1]
|
|
}
|
|
|
|
switch t := t.(type) {
|
|
case xml.StartElement:
|
|
xmlns := ""
|
|
if parent != nil {
|
|
xmlns = parent.XMLNS
|
|
}
|
|
for _, a := range t.Attr {
|
|
if a.Name.Space == "xmlns" {
|
|
xmlnsMap[a.Name.Local] = a.Value
|
|
} else if a.Name.Space == "" && a.Name.Local == "xmlns" {
|
|
xmlns = a.Value
|
|
}
|
|
}
|
|
xmlns = getNamespaceURI(xmlnsMap, xmlns, t.Name)
|
|
child := &element{
|
|
XMLNS: xmlns,
|
|
Name: xmlName(xmlns, t.Name),
|
|
Attrs: make(map[string]string),
|
|
}
|
|
|
|
for _, a := range t.Attr {
|
|
if a.Name.Space == "xmlns" {
|
|
continue
|
|
}
|
|
if a.Name.Space == "" && a.Name.Local == "xmlns" {
|
|
continue
|
|
}
|
|
attrNS := getNamespaceURI(xmlnsMap, "", a.Name)
|
|
child.Attrs[xmlName(attrNS, a.Name)] = a.Value
|
|
}
|
|
stack.push(child)
|
|
if root == nil {
|
|
root = child
|
|
} else {
|
|
parent.Children = append(parent.Children, child)
|
|
parent.Content = ""
|
|
}
|
|
case xml.EndElement:
|
|
stack.pop()
|
|
case xml.CharData:
|
|
if parent != nil && len(parent.Children) == 0 {
|
|
val := string(t)
|
|
if strings.TrimSpace(val) != "" {
|
|
parent.Content = val
|
|
}
|
|
}
|
|
}
|
|
|
|
if root != nil && len(stack) == 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
return root, nil
|
|
}
|
|
|
|
func testCompareValue(filename, path, key, expected, actual string) error {
|
|
if expected == actual {
|
|
return nil
|
|
}
|
|
|
|
i1, err1 := strconv.ParseInt(expected, 0, 64)
|
|
i2, err2 := strconv.ParseInt(actual, 0, 64)
|
|
if err1 == nil && err2 == nil && i1 == i2 {
|
|
return nil
|
|
}
|
|
path = path + "/@" + key
|
|
return fmt.Errorf("%s: %s: attribute actual value '%s' does not match expected value '%s'",
|
|
filename, path, actual, expected)
|
|
}
|
|
|
|
func testCompareElement(filename, expectPath, actualPath string, expect, actual *element, extraExpectNodes, extraActualNodes map[string]bool) error {
|
|
if expect.Name != actual.Name {
|
|
return fmt.Errorf("%s: name '%s' doesn't match '%s'",
|
|
expectPath, expect.Name, actual.Name)
|
|
}
|
|
|
|
expectAttr := expect.Attrs
|
|
for key, val := range actual.Attrs {
|
|
expectval, ok := expectAttr[key]
|
|
if !ok {
|
|
attrPath := actualPath + "/@" + key
|
|
if _, ok := extraActualNodes[attrPath]; ok {
|
|
continue
|
|
}
|
|
return fmt.Errorf("%s: %s: attribute in actual XML missing in expected XML",
|
|
filename, attrPath)
|
|
}
|
|
err := testCompareValue(filename, actualPath, key, expectval, val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
delete(expectAttr, key)
|
|
}
|
|
for key, _ := range expectAttr {
|
|
attrPath := expectPath + "/@" + key
|
|
if _, ok := extraExpectNodes[attrPath]; ok {
|
|
continue
|
|
}
|
|
return fmt.Errorf("%s: %s: attribute '%s' in expected XML missing in actual XML",
|
|
filename, attrPath, expectAttr[key])
|
|
}
|
|
|
|
if expect.Content != actual.Content {
|
|
return fmt.Errorf("%s: %s: actual content '%s' does not match expected '%s'",
|
|
filename, actualPath, actual.Content, expect.Content)
|
|
}
|
|
|
|
used := make([]bool, len(actual.Children))
|
|
expectChildIndexes := make(map[string]uint)
|
|
actualChildIndexes := make(map[string]uint)
|
|
for _, expectChild := range expect.Children {
|
|
expectIndex, _ := expectChildIndexes[expectChild.Name]
|
|
expectChildIndexes[expectChild.Name] = expectIndex + 1
|
|
subExpectPath := fmt.Sprintf("%s/%s[%d]", expectPath, expectChild.Name, expectIndex)
|
|
|
|
var actualChild *element = nil
|
|
for i := 0; i < len(used); i++ {
|
|
if !used[i] && actual.Children[i].Name == expectChild.Name {
|
|
actualChild = actual.Children[i]
|
|
used[i] = true
|
|
break
|
|
}
|
|
}
|
|
if actualChild == nil {
|
|
if _, ok := extraExpectNodes[subExpectPath]; ok {
|
|
continue
|
|
}
|
|
return fmt.Errorf("%s: %s: element in expected XML missing in actual XML",
|
|
filename, subExpectPath)
|
|
}
|
|
|
|
actualIndex, _ := actualChildIndexes[actualChild.Name]
|
|
actualChildIndexes[actualChild.Name] = actualIndex + 1
|
|
subActualPath := fmt.Sprintf("%s/%s[%d]", actualPath, actualChild.Name, actualIndex)
|
|
|
|
err := testCompareElement(filename, subExpectPath, subActualPath, expectChild, actualChild, extraExpectNodes, extraActualNodes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
actualChildIndexes = make(map[string]uint)
|
|
for i, actualChild := range actual.Children {
|
|
actualIndex, _ := actualChildIndexes[actualChild.Name]
|
|
actualChildIndexes[actualChild.Name] = actualIndex + 1
|
|
if used[i] {
|
|
continue
|
|
}
|
|
subActualPath := fmt.Sprintf("%s/%s[%d]", actualPath, actualChild.Name, actualIndex)
|
|
|
|
if _, ok := extraActualNodes[subActualPath]; ok {
|
|
continue
|
|
}
|
|
return fmt.Errorf("%s: %s: element in actual XML missing in expected XML",
|
|
filename, subActualPath)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func makeExtraNodeMap(nodes []string) map[string]bool {
|
|
ret := make(map[string]bool)
|
|
for _, node := range nodes {
|
|
ret[node] = true
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func testCompareXML(filename, expectStr, actualStr string, extraExpectNodes, extraActualNodes []string) error {
|
|
extraExpectNodeMap := makeExtraNodeMap(extraExpectNodes)
|
|
extraActualNodeMap := makeExtraNodeMap(extraActualNodes)
|
|
|
|
//fmt.Printf("%s\n", expectedstr)
|
|
expectRoot, err := loadXML(expectStr, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
//fmt.Printf("%s\n", actualstr)
|
|
actualRoot, err := loadXML(actualStr, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if expectRoot.Name != actualRoot.Name {
|
|
return fmt.Errorf("%s: /: expected root element '%s' does not match actual '%s'",
|
|
filename, expectRoot.Name, actualRoot.Name)
|
|
}
|
|
|
|
err = testCompareElement(filename, "/"+expectRoot.Name+"[0]", "/"+actualRoot.Name+"[0]", expectRoot, actualRoot, extraExpectNodeMap, extraActualNodeMap)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|