| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- /*
- * 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
- }
|