...

Source file src/github.com/onsi/gomega/matchers/match_xml_matcher.go

Documentation: github.com/onsi/gomega/matchers

     1  package matchers
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/xml"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"reflect"
    10  	"sort"
    11  	"strings"
    12  
    13  	"github.com/onsi/gomega/format"
    14  	"golang.org/x/net/html/charset"
    15  )
    16  
    17  type MatchXMLMatcher struct {
    18  	XMLToMatch interface{}
    19  }
    20  
    21  func (matcher *MatchXMLMatcher) Match(actual interface{}) (success bool, err error) {
    22  	actualString, expectedString, err := matcher.formattedPrint(actual)
    23  	if err != nil {
    24  		return false, err
    25  	}
    26  
    27  	aval, err := parseXmlContent(actualString)
    28  	if err != nil {
    29  		return false, fmt.Errorf("Actual '%s' should be valid XML, but it is not.\nUnderlying error:%s", actualString, err)
    30  	}
    31  
    32  	eval, err := parseXmlContent(expectedString)
    33  	if err != nil {
    34  		return false, fmt.Errorf("Expected '%s' should be valid XML, but it is not.\nUnderlying error:%s", expectedString, err)
    35  	}
    36  
    37  	return reflect.DeepEqual(aval, eval), nil
    38  }
    39  
    40  func (matcher *MatchXMLMatcher) FailureMessage(actual interface{}) (message string) {
    41  	actualString, expectedString, _ := matcher.formattedPrint(actual)
    42  	return fmt.Sprintf("Expected\n%s\nto match XML of\n%s", actualString, expectedString)
    43  }
    44  
    45  func (matcher *MatchXMLMatcher) NegatedFailureMessage(actual interface{}) (message string) {
    46  	actualString, expectedString, _ := matcher.formattedPrint(actual)
    47  	return fmt.Sprintf("Expected\n%s\nnot to match XML of\n%s", actualString, expectedString)
    48  }
    49  
    50  func (matcher *MatchXMLMatcher) formattedPrint(actual interface{}) (actualString, expectedString string, err error) {
    51  	var ok bool
    52  	actualString, ok = toString(actual)
    53  	if !ok {
    54  		return "", "", fmt.Errorf("MatchXMLMatcher matcher requires a string, stringer, or []byte.  Got actual:\n%s", format.Object(actual, 1))
    55  	}
    56  	expectedString, ok = toString(matcher.XMLToMatch)
    57  	if !ok {
    58  		return "", "", fmt.Errorf("MatchXMLMatcher matcher requires a string, stringer, or []byte.  Got expected:\n%s", format.Object(matcher.XMLToMatch, 1))
    59  	}
    60  	return actualString, expectedString, nil
    61  }
    62  
    63  func parseXmlContent(content string) (*xmlNode, error) {
    64  	allNodes := []*xmlNode{}
    65  
    66  	dec := newXmlDecoder(strings.NewReader(content))
    67  	for {
    68  		tok, err := dec.Token()
    69  		if err != nil {
    70  			if err == io.EOF {
    71  				break
    72  			}
    73  			return nil, fmt.Errorf("failed to decode next token: %v", err) // untested section
    74  		}
    75  
    76  		lastNodeIndex := len(allNodes) - 1
    77  		var lastNode *xmlNode
    78  		if len(allNodes) > 0 {
    79  			lastNode = allNodes[lastNodeIndex]
    80  		} else {
    81  			lastNode = &xmlNode{}
    82  		}
    83  
    84  		switch tok := tok.(type) {
    85  		case xml.StartElement:
    86  			attrs := attributesSlice(tok.Attr)
    87  			sort.Sort(attrs)
    88  			allNodes = append(allNodes, &xmlNode{XMLName: tok.Name, XMLAttr: tok.Attr})
    89  		case xml.EndElement:
    90  			if len(allNodes) > 1 {
    91  				allNodes[lastNodeIndex-1].Nodes = append(allNodes[lastNodeIndex-1].Nodes, lastNode)
    92  				allNodes = allNodes[:lastNodeIndex]
    93  			}
    94  		case xml.CharData:
    95  			lastNode.Content = append(lastNode.Content, tok.Copy()...)
    96  		case xml.Comment:
    97  			lastNode.Comments = append(lastNode.Comments, tok.Copy()) // untested section
    98  		case xml.ProcInst:
    99  			lastNode.ProcInsts = append(lastNode.ProcInsts, tok.Copy())
   100  		}
   101  	}
   102  
   103  	if len(allNodes) == 0 {
   104  		return nil, errors.New("found no nodes")
   105  	}
   106  	firstNode := allNodes[0]
   107  	trimParentNodesContentSpaces(firstNode)
   108  
   109  	return firstNode, nil
   110  }
   111  
   112  func newXmlDecoder(reader io.Reader) *xml.Decoder {
   113  	dec := xml.NewDecoder(reader)
   114  	dec.CharsetReader = charset.NewReaderLabel
   115  	return dec
   116  }
   117  
   118  func trimParentNodesContentSpaces(node *xmlNode) {
   119  	if len(node.Nodes) > 0 {
   120  		node.Content = bytes.TrimSpace(node.Content)
   121  		for _, childNode := range node.Nodes {
   122  			trimParentNodesContentSpaces(childNode)
   123  		}
   124  	}
   125  }
   126  
   127  type xmlNode struct {
   128  	XMLName   xml.Name
   129  	Comments  []xml.Comment
   130  	ProcInsts []xml.ProcInst
   131  	XMLAttr   []xml.Attr
   132  	Content   []byte
   133  	Nodes     []*xmlNode
   134  }
   135  

View as plain text