github.com/libvirt/libvirt-go-xml@v7.4.0+incompatible/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 }