Path: blob/main/vendor/github.com/onsi/gomega/matchers/match_xml_matcher.go
2880 views
package matchers12import (3"bytes"4"encoding/xml"5"errors"6"fmt"7"io"8"reflect"9"sort"10"strings"1112"github.com/onsi/gomega/format"13"golang.org/x/net/html/charset"14)1516type MatchXMLMatcher struct {17XMLToMatch any18}1920func (matcher *MatchXMLMatcher) Match(actual any) (success bool, err error) {21actualString, expectedString, err := matcher.formattedPrint(actual)22if err != nil {23return false, err24}2526aval, err := parseXmlContent(actualString)27if err != nil {28return false, fmt.Errorf("Actual '%s' should be valid XML, but it is not.\nUnderlying error:%s", actualString, err)29}3031eval, err := parseXmlContent(expectedString)32if err != nil {33return false, fmt.Errorf("Expected '%s' should be valid XML, but it is not.\nUnderlying error:%s", expectedString, err)34}3536return reflect.DeepEqual(aval, eval), nil37}3839func (matcher *MatchXMLMatcher) FailureMessage(actual any) (message string) {40actualString, expectedString, _ := matcher.formattedPrint(actual)41return fmt.Sprintf("Expected\n%s\nto match XML of\n%s", actualString, expectedString)42}4344func (matcher *MatchXMLMatcher) NegatedFailureMessage(actual any) (message string) {45actualString, expectedString, _ := matcher.formattedPrint(actual)46return fmt.Sprintf("Expected\n%s\nnot to match XML of\n%s", actualString, expectedString)47}4849func (matcher *MatchXMLMatcher) formattedPrint(actual any) (actualString, expectedString string, err error) {50var ok bool51actualString, ok = toString(actual)52if !ok {53return "", "", fmt.Errorf("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got actual:\n%s", format.Object(actual, 1))54}55expectedString, ok = toString(matcher.XMLToMatch)56if !ok {57return "", "", fmt.Errorf("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got expected:\n%s", format.Object(matcher.XMLToMatch, 1))58}59return actualString, expectedString, nil60}6162func parseXmlContent(content string) (*xmlNode, error) {63allNodes := []*xmlNode{}6465dec := newXmlDecoder(strings.NewReader(content))66for {67tok, err := dec.Token()68if err != nil {69if err == io.EOF {70break71}72return nil, fmt.Errorf("failed to decode next token: %v", err) // untested section73}7475lastNodeIndex := len(allNodes) - 176var lastNode *xmlNode77if len(allNodes) > 0 {78lastNode = allNodes[lastNodeIndex]79} else {80lastNode = &xmlNode{}81}8283switch tok := tok.(type) {84case xml.StartElement:85attrs := attributesSlice(tok.Attr)86sort.Sort(attrs)87allNodes = append(allNodes, &xmlNode{XMLName: tok.Name, XMLAttr: tok.Attr})88case xml.EndElement:89if len(allNodes) > 1 {90allNodes[lastNodeIndex-1].Nodes = append(allNodes[lastNodeIndex-1].Nodes, lastNode)91allNodes = allNodes[:lastNodeIndex]92}93case xml.CharData:94lastNode.Content = append(lastNode.Content, tok.Copy()...)95case xml.Comment:96lastNode.Comments = append(lastNode.Comments, tok.Copy()) // untested section97case xml.ProcInst:98lastNode.ProcInsts = append(lastNode.ProcInsts, tok.Copy())99}100}101102if len(allNodes) == 0 {103return nil, errors.New("found no nodes")104}105firstNode := allNodes[0]106trimParentNodesContentSpaces(firstNode)107108return firstNode, nil109}110111func newXmlDecoder(reader io.Reader) *xml.Decoder {112dec := xml.NewDecoder(reader)113dec.CharsetReader = charset.NewReaderLabel114return dec115}116117func trimParentNodesContentSpaces(node *xmlNode) {118if len(node.Nodes) > 0 {119node.Content = bytes.TrimSpace(node.Content)120for _, childNode := range node.Nodes {121trimParentNodesContentSpaces(childNode)122}123}124}125126type xmlNode struct {127XMLName xml.Name128Comments []xml.Comment129ProcInsts []xml.ProcInst130XMLAttr []xml.Attr131Content []byte132Nodes []*xmlNode133}134135136