Add xpath tests
This commit is contained in:
98
processor/xpath/xpath.go
Normal file
98
processor/xpath/xpath.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package xpath
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// XPathStep represents a single step in an XPath expression
|
||||||
|
type XPathStep struct {
|
||||||
|
Type StepType
|
||||||
|
Name string
|
||||||
|
Predicate *Predicate
|
||||||
|
}
|
||||||
|
|
||||||
|
// StepType defines the type of XPath step
|
||||||
|
type StepType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RootStep represents the root step (/)
|
||||||
|
RootStep StepType = iota
|
||||||
|
// ChildStep represents a child element step (element)
|
||||||
|
ChildStep
|
||||||
|
// RecursiveDescentStep represents a recursive descent step (//)
|
||||||
|
RecursiveDescentStep
|
||||||
|
// WildcardStep represents a wildcard step (*)
|
||||||
|
WildcardStep
|
||||||
|
// PredicateStep represents a predicate condition step ([...])
|
||||||
|
PredicateStep
|
||||||
|
)
|
||||||
|
|
||||||
|
// PredicateType defines the type of XPath predicate
|
||||||
|
type PredicateType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// IndexPredicate represents an index predicate [n]
|
||||||
|
IndexPredicate PredicateType = iota
|
||||||
|
// LastPredicate represents a last() function predicate
|
||||||
|
LastPredicate
|
||||||
|
// LastMinusPredicate represents a last()-n predicate
|
||||||
|
LastMinusPredicate
|
||||||
|
// PositionPredicate represents position()-based predicates
|
||||||
|
PositionPredicate
|
||||||
|
// AttributeExistsPredicate represents [@attr] predicate
|
||||||
|
AttributeExistsPredicate
|
||||||
|
// AttributeEqualsPredicate represents [@attr='value'] predicate
|
||||||
|
AttributeEqualsPredicate
|
||||||
|
// ComparisonPredicate represents element comparison predicates
|
||||||
|
ComparisonPredicate
|
||||||
|
)
|
||||||
|
|
||||||
|
// Predicate represents a condition in XPath
|
||||||
|
type Predicate struct {
|
||||||
|
Type PredicateType
|
||||||
|
Index int
|
||||||
|
Offset int
|
||||||
|
Attribute string
|
||||||
|
Value string
|
||||||
|
Expression string
|
||||||
|
}
|
||||||
|
|
||||||
|
// XMLNode represents a node in the result set with its value and path
|
||||||
|
type XMLNode struct {
|
||||||
|
Value interface{}
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseXPath parses an XPath expression into a series of steps
|
||||||
|
func ParseXPath(path string) ([]XPathStep, error) {
|
||||||
|
if path == "" {
|
||||||
|
return nil, errors.New("empty path")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is just a placeholder implementation for the tests
|
||||||
|
// The actual implementation would parse the XPath expression
|
||||||
|
return nil, errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves nodes from XML data using an XPath expression
|
||||||
|
func Get(data interface{}, path string) ([]XMLNode, error) {
|
||||||
|
if data == "" {
|
||||||
|
return nil, errors.New("empty XML data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is just a placeholder implementation for the tests
|
||||||
|
// The actual implementation would evaluate the XPath against the XML
|
||||||
|
return nil, errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set updates a node in the XML data using an XPath expression
|
||||||
|
func Set(xmlData string, path string, value interface{}) (string, error) {
|
||||||
|
// This is just a placeholder implementation for the tests
|
||||||
|
// The actual implementation would modify the XML based on the XPath
|
||||||
|
return "", errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAll updates all nodes matching an XPath expression in the XML data
|
||||||
|
func SetAll(xmlData string, path string, value interface{}) (string, error) {
|
||||||
|
// This is just a placeholder implementation for the tests
|
||||||
|
// The actual implementation would modify all matching nodes
|
||||||
|
return "", errors.New("not implemented")
|
||||||
|
}
|
545
processor/xpath/xpath_test.go
Normal file
545
processor/xpath/xpath_test.go
Normal file
@@ -0,0 +1,545 @@
|
|||||||
|
package xpath
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// XML test data as a string for our tests
|
||||||
|
var testXML = `
|
||||||
|
<store>
|
||||||
|
<book category="fiction">
|
||||||
|
<title lang="en">The Fellowship of the Ring</title>
|
||||||
|
<author>J.R.R. Tolkien</author>
|
||||||
|
<year>1954</year>
|
||||||
|
<price>22.99</price>
|
||||||
|
</book>
|
||||||
|
<book category="fiction">
|
||||||
|
<title lang="en">The Two Towers</title>
|
||||||
|
<author>J.R.R. Tolkien</author>
|
||||||
|
<year>1954</year>
|
||||||
|
<price>23.45</price>
|
||||||
|
</book>
|
||||||
|
<book category="technical">
|
||||||
|
<title lang="en">Learning XML</title>
|
||||||
|
<author>Erik T. Ray</author>
|
||||||
|
<year>2003</year>
|
||||||
|
<price>39.95</price>
|
||||||
|
</book>
|
||||||
|
<bicycle>
|
||||||
|
<color>red</color>
|
||||||
|
<price>199.95</price>
|
||||||
|
</bicycle>
|
||||||
|
</store>
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestParser(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
path string
|
||||||
|
steps []XPathStep
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
path: "/store/bicycle/color",
|
||||||
|
steps: []XPathStep{
|
||||||
|
{Type: RootStep},
|
||||||
|
{Type: ChildStep, Name: "store"},
|
||||||
|
{Type: ChildStep, Name: "bicycle"},
|
||||||
|
{Type: ChildStep, Name: "color"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "//price",
|
||||||
|
steps: []XPathStep{
|
||||||
|
{Type: RootStep},
|
||||||
|
{Type: RecursiveDescentStep, Name: "price"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/store/book/*",
|
||||||
|
steps: []XPathStep{
|
||||||
|
{Type: RootStep},
|
||||||
|
{Type: ChildStep, Name: "store"},
|
||||||
|
{Type: ChildStep, Name: "book"},
|
||||||
|
{Type: WildcardStep},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/store/book[1]/title",
|
||||||
|
steps: []XPathStep{
|
||||||
|
{Type: RootStep},
|
||||||
|
{Type: ChildStep, Name: "store"},
|
||||||
|
{Type: ChildStep, Name: "book"},
|
||||||
|
{Type: PredicateStep, Predicate: &Predicate{Type: IndexPredicate, Index: 1}},
|
||||||
|
{Type: ChildStep, Name: "title"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "//title[@lang]",
|
||||||
|
steps: []XPathStep{
|
||||||
|
{Type: RootStep},
|
||||||
|
{Type: RecursiveDescentStep, Name: "title"},
|
||||||
|
{Type: PredicateStep, Predicate: &Predicate{Type: AttributeExistsPredicate, Attribute: "lang"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "//title[@lang='en']",
|
||||||
|
steps: []XPathStep{
|
||||||
|
{Type: RootStep},
|
||||||
|
{Type: RecursiveDescentStep, Name: "title"},
|
||||||
|
{Type: PredicateStep, Predicate: &Predicate{
|
||||||
|
Type: AttributeEqualsPredicate,
|
||||||
|
Attribute: "lang",
|
||||||
|
Value: "en",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/store/book[price>35.00]/title",
|
||||||
|
steps: []XPathStep{
|
||||||
|
{Type: RootStep},
|
||||||
|
{Type: ChildStep, Name: "store"},
|
||||||
|
{Type: ChildStep, Name: "book"},
|
||||||
|
{Type: PredicateStep, Predicate: &Predicate{
|
||||||
|
Type: ComparisonPredicate,
|
||||||
|
Expression: "price>35.00",
|
||||||
|
}},
|
||||||
|
{Type: ChildStep, Name: "title"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/store/book[last()]",
|
||||||
|
steps: []XPathStep{
|
||||||
|
{Type: RootStep},
|
||||||
|
{Type: ChildStep, Name: "store"},
|
||||||
|
{Type: ChildStep, Name: "book"},
|
||||||
|
{Type: PredicateStep, Predicate: &Predicate{Type: LastPredicate}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/store/book[last()-1]",
|
||||||
|
steps: []XPathStep{
|
||||||
|
{Type: RootStep},
|
||||||
|
{Type: ChildStep, Name: "store"},
|
||||||
|
{Type: ChildStep, Name: "book"},
|
||||||
|
{Type: PredicateStep, Predicate: &Predicate{
|
||||||
|
Type: LastMinusPredicate,
|
||||||
|
Offset: 1,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/store/book[position()<3]",
|
||||||
|
steps: []XPathStep{
|
||||||
|
{Type: RootStep},
|
||||||
|
{Type: ChildStep, Name: "store"},
|
||||||
|
{Type: ChildStep, Name: "book"},
|
||||||
|
{Type: PredicateStep, Predicate: &Predicate{
|
||||||
|
Type: PositionPredicate,
|
||||||
|
Expression: "position()<3",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "invalid/path",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.path, func(t *testing.T) {
|
||||||
|
steps, err := ParseXPath(tt.path)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Fatalf("ParseXPath() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
if !tt.wantErr && !reflect.DeepEqual(steps, tt.steps) {
|
||||||
|
t.Errorf("ParseXPath() steps = %+v, want %+v", steps, tt.steps)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEvaluator(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
expected []XMLNode
|
||||||
|
error bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple_element_access",
|
||||||
|
path: "/store/bicycle/color",
|
||||||
|
expected: []XMLNode{
|
||||||
|
{Value: "red", Path: "/store/bicycle/color"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "recursive_element_access",
|
||||||
|
path: "//price",
|
||||||
|
expected: []XMLNode{
|
||||||
|
{Value: "22.99", Path: "/store/book[1]/price"},
|
||||||
|
{Value: "23.45", Path: "/store/book[2]/price"},
|
||||||
|
{Value: "39.95", Path: "/store/book[3]/price"},
|
||||||
|
{Value: "199.95", Path: "/store/bicycle/price"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wildcard_element_access",
|
||||||
|
path: "/store/book[1]/*",
|
||||||
|
expected: []XMLNode{
|
||||||
|
{Value: "The Fellowship of the Ring", Path: "/store/book[1]/title"},
|
||||||
|
{Value: "J.R.R. Tolkien", Path: "/store/book[1]/author"},
|
||||||
|
{Value: "1954", Path: "/store/book[1]/year"},
|
||||||
|
{Value: "22.99", Path: "/store/book[1]/price"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "indexed_element_access",
|
||||||
|
path: "/store/book[1]/title",
|
||||||
|
expected: []XMLNode{
|
||||||
|
{Value: "The Fellowship of the Ring", Path: "/store/book[1]/title"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "attribute_exists_predicate",
|
||||||
|
path: "//title[@lang]",
|
||||||
|
expected: []XMLNode{
|
||||||
|
{Value: "The Fellowship of the Ring", Path: "/store/book[1]/title"},
|
||||||
|
{Value: "The Two Towers", Path: "/store/book[2]/title"},
|
||||||
|
{Value: "Learning XML", Path: "/store/book[3]/title"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "attribute_equals_predicate",
|
||||||
|
path: "//title[@lang='en']",
|
||||||
|
expected: []XMLNode{
|
||||||
|
{Value: "The Fellowship of the Ring", Path: "/store/book[1]/title"},
|
||||||
|
{Value: "The Two Towers", Path: "/store/book[2]/title"},
|
||||||
|
{Value: "Learning XML", Path: "/store/book[3]/title"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "value_comparison_predicate",
|
||||||
|
path: "/store/book[price>35.00]/title",
|
||||||
|
expected: []XMLNode{
|
||||||
|
{Value: "Learning XML", Path: "/store/book[3]/title"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "last_predicate",
|
||||||
|
path: "/store/book[last()]/title",
|
||||||
|
expected: []XMLNode{
|
||||||
|
{Value: "Learning XML", Path: "/store/book[3]/title"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "last_minus_predicate",
|
||||||
|
path: "/store/book[last()-1]/title",
|
||||||
|
expected: []XMLNode{
|
||||||
|
{Value: "The Two Towers", Path: "/store/book[2]/title"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "position_predicate",
|
||||||
|
path: "/store/book[position()<3]/title",
|
||||||
|
expected: []XMLNode{
|
||||||
|
{Value: "The Fellowship of the Ring", Path: "/store/book[1]/title"},
|
||||||
|
{Value: "The Two Towers", Path: "/store/book[2]/title"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all_elements",
|
||||||
|
path: "//*",
|
||||||
|
expected: []XMLNode{
|
||||||
|
// For brevity, we'll just check the count, not all values
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid_index",
|
||||||
|
path: "/store/book[10]/title",
|
||||||
|
expected: []XMLNode{},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nonexistent_element",
|
||||||
|
path: "/store/nonexistent",
|
||||||
|
expected: []XMLNode{},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := Get(testXML, tt.path)
|
||||||
|
if err != nil {
|
||||||
|
if !tt.error {
|
||||||
|
t.Errorf("Get() returned error: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for the "//*" test case
|
||||||
|
if tt.path == "//*" {
|
||||||
|
// Just check that we got multiple elements, not the specific count
|
||||||
|
if len(result) < 10 { // We expect at least 10 elements
|
||||||
|
t.Errorf("Expected multiple elements for '//*', got %d", len(result))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) != len(tt.expected) {
|
||||||
|
t.Errorf("Expected %d items, got %d", len(tt.expected), len(result))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate both values and paths
|
||||||
|
for i, e := range tt.expected {
|
||||||
|
if i < len(result) {
|
||||||
|
if !reflect.DeepEqual(result[i].Value, e.Value) {
|
||||||
|
t.Errorf("Value at [%d]: got %v, expected %v", i, result[i].Value, e.Value)
|
||||||
|
}
|
||||||
|
if result[i].Path != e.Path {
|
||||||
|
t.Errorf("Path at [%d]: got %s, expected %s", i, result[i].Path, e.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEdgeCases(t *testing.T) {
|
||||||
|
t.Run("empty_data", func(t *testing.T) {
|
||||||
|
result, err := Get("", "/store/book")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error for empty data")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(result) > 0 {
|
||||||
|
t.Errorf("Expected empty result, got %v", result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty_path", func(t *testing.T) {
|
||||||
|
_, err := ParseXPath("")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error for empty path")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid_xml", func(t *testing.T) {
|
||||||
|
_, err := Get("<invalid>xml", "/store")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error for invalid XML")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("current_node", func(t *testing.T) {
|
||||||
|
result, err := Get(testXML, "/store/book[1]/.")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Get() returned error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(result) != 1 {
|
||||||
|
t.Errorf("Expected 1 result, got %d", len(result))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("attributes", func(t *testing.T) {
|
||||||
|
result, err := Get(testXML, "/store/book[1]/title/@lang")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Get() returned error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(result) != 1 || result[0].Value != "en" {
|
||||||
|
t.Errorf("Expected 'en', got %v", result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetWithPaths(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
expected []XMLNode
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple_element_access",
|
||||||
|
path: "/store/bicycle/color",
|
||||||
|
expected: []XMLNode{
|
||||||
|
{Value: "red", Path: "/store/bicycle/color"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "indexed_element_access",
|
||||||
|
path: "/store/book[1]/title",
|
||||||
|
expected: []XMLNode{
|
||||||
|
{Value: "The Fellowship of the Ring", Path: "/store/book[1]/title"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "recursive_element_access",
|
||||||
|
path: "//price",
|
||||||
|
expected: []XMLNode{
|
||||||
|
{Value: "22.99", Path: "/store/book[1]/price"},
|
||||||
|
{Value: "23.45", Path: "/store/book[2]/price"},
|
||||||
|
{Value: "39.95", Path: "/store/book[3]/price"},
|
||||||
|
{Value: "199.95", Path: "/store/bicycle/price"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "attribute_access",
|
||||||
|
path: "/store/book[1]/title/@lang",
|
||||||
|
expected: []XMLNode{
|
||||||
|
{Value: "en", Path: "/store/book[1]/title/@lang"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := Get(testXML, tt.path)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Get() returned error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if lengths match
|
||||||
|
if len(result) != len(tt.expected) {
|
||||||
|
t.Errorf("Get() returned %d items, expected %d", len(result), len(tt.expected))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each expected item, find its match in the results and verify both value and path
|
||||||
|
for _, expected := range tt.expected {
|
||||||
|
found := false
|
||||||
|
for _, r := range result {
|
||||||
|
// First verify the value matches
|
||||||
|
if reflect.DeepEqual(r.Value, expected.Value) {
|
||||||
|
found = true
|
||||||
|
// Then verify the path matches
|
||||||
|
if r.Path != expected.Path {
|
||||||
|
t.Errorf("Path mismatch for value %v: got %s, expected %s", r.Value, r.Path, expected.Path)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected node with value %v and path %s not found in results", expected.Value, expected.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSet(t *testing.T) {
|
||||||
|
t.Run("simple element", func(t *testing.T) {
|
||||||
|
xmlData := `<root><name>John</name></root>`
|
||||||
|
newXML, err := Set(xmlData, "/root/name", "Jane")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Set() returned error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the change
|
||||||
|
result, err := Get(newXML, "/root/name")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Get() returned error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(result) != 1 || result[0].Value != "Jane" {
|
||||||
|
t.Errorf("Set() failed: expected name to be 'Jane', got %v", result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("attribute", func(t *testing.T) {
|
||||||
|
xmlData := `<root><element id="123"></element></root>`
|
||||||
|
newXML, err := Set(xmlData, "/root/element/@id", "456")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Set() returned error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the change
|
||||||
|
result, err := Get(newXML, "/root/element/@id")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Get() returned error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(result) != 1 || result[0].Value != "456" {
|
||||||
|
t.Errorf("Set() failed: expected id to be '456', got %v", result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("indexed element", func(t *testing.T) {
|
||||||
|
xmlData := `<root><items><item>first</item><item>second</item></items></root>`
|
||||||
|
newXML, err := Set(xmlData, "/root/items/item[1]", "changed")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Set() returned error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the change
|
||||||
|
result, err := Get(newXML, "/root/items/item[1]")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Get() returned error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(result) != 1 || result[0].Value != "changed" {
|
||||||
|
t.Errorf("Set() failed: expected item to be 'changed', got %v", result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetAll(t *testing.T) {
|
||||||
|
t.Run("multiple elements", func(t *testing.T) {
|
||||||
|
xmlData := `<root><items><item>first</item><item>second</item></items></root>`
|
||||||
|
newXML, err := SetAll(xmlData, "//item", "changed")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("SetAll() returned error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all items are changed
|
||||||
|
result, err := Get(newXML, "//item")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Get() returned error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(result) != 2 {
|
||||||
|
t.Errorf("Expected 2 results, got %d", len(result))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, node := range result {
|
||||||
|
if node.Value != "changed" {
|
||||||
|
t.Errorf("Item %d not changed, got %v", i+1, node.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("attributes", func(t *testing.T) {
|
||||||
|
xmlData := `<root><item id="1"/><item id="2"/></root>`
|
||||||
|
newXML, err := SetAll(xmlData, "//item/@id", "new")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("SetAll() returned error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all attributes are changed
|
||||||
|
result, err := Get(newXML, "//item/@id")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Get() returned error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(result) != 2 {
|
||||||
|
t.Errorf("Expected 2 results, got %d", len(result))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, node := range result {
|
||||||
|
if node.Value != "new" {
|
||||||
|
t.Errorf("Attribute %d not changed, got %v", i+1, node.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
Reference in New Issue
Block a user