From df212b7fcc25a7e791ed6ae39da6348a0780c1c2 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Thu, 27 Mar 2025 21:27:47 +0100 Subject: [PATCH] Remove jsonpath and xpath --- processor/jsonpath/jsonpath.go | 490 ----------------- processor/jsonpath/jsonpath_get_set_test.go | 577 -------------------- processor/jsonpath/jsonpath_test.go | 318 ----------- processor/xpath/parser_manual_test.go | 4 - processor/xpath/parser_test.go | 4 - processor/xpath/xpath.go | 133 ----- processor/xpath/xpath_test.go | 474 ---------------- 7 files changed, 2000 deletions(-) delete mode 100644 processor/jsonpath/jsonpath.go delete mode 100644 processor/jsonpath/jsonpath_get_set_test.go delete mode 100644 processor/jsonpath/jsonpath_test.go delete mode 100644 processor/xpath/parser_manual_test.go delete mode 100644 processor/xpath/parser_test.go delete mode 100644 processor/xpath/xpath.go delete mode 100644 processor/xpath/xpath_test.go diff --git a/processor/jsonpath/jsonpath.go b/processor/jsonpath/jsonpath.go deleted file mode 100644 index 79498db..0000000 --- a/processor/jsonpath/jsonpath.go +++ /dev/null @@ -1,490 +0,0 @@ -package jsonpath - -import ( - "fmt" - "strconv" -) - -// JSONStep represents a single step in a JSONPath query -type JSONStep struct { - Type StepType - Key string // For Child/RecursiveDescent - Index int // For Index (use -1 for wildcard "*") -} - -// JSONNode represents a value in the JSON data with its path -type JSONNode struct { - Value interface{} // The value found at the path - Path string // The exact JSONPath where the value was found -} - -// StepType defines the types of steps in a JSONPath -type StepType int - -const ( - RootStep StepType = iota // $ - The root element - ChildStep // .key - Direct child access - RecursiveDescentStep // ..key - Recursive search for key - WildcardStep // .* - All children of an object - IndexStep // [n] - Array index access (or [*] for all elements) -) - -// TraversalMode determines how the traversal behaves -type TraversalMode int - -const ( - CollectMode TraversalMode = iota // Just collect matched nodes - ModifyFirstMode // Modify first matching node - ModifyAllMode // Modify all matching nodes -) - -// ParseJSONPath parses a JSONPath string into a sequence of steps -func ParseJSONPath(path string) ([]JSONStep, error) { - if len(path) == 0 || path[0] != '$' { - return nil, fmt.Errorf("path must start with $; received: %q", path) - } - - steps := []JSONStep{} - i := 0 - - for i < len(path) { - switch path[i] { - case '$': - steps = append(steps, JSONStep{Type: RootStep}) - i++ - case '.': - i++ - if i < len(path) && path[i] == '.' { - // Recursive descent - i++ - key, nextPos := readKey(path, i) - steps = append(steps, JSONStep{Type: RecursiveDescentStep, Key: key}) - i = nextPos - } else { - // Child step or wildcard - key, nextPos := readKey(path, i) - if key == "*" { - steps = append(steps, JSONStep{Type: WildcardStep}) - } else { - steps = append(steps, JSONStep{Type: ChildStep, Key: key}) - } - i = nextPos - } - case '[': - // Index step - i++ - indexStr, nextPos := readIndex(path, i) - if indexStr == "*" { - steps = append(steps, JSONStep{Type: IndexStep, Index: -1}) - } else { - index, err := strconv.Atoi(indexStr) - if err != nil { - return nil, fmt.Errorf("invalid index: %s; error: %w", indexStr, err) - } - steps = append(steps, JSONStep{Type: IndexStep, Index: index}) - } - i = nextPos + 1 // Skip closing ] - default: - return nil, fmt.Errorf("unexpected character: %c at position %d; path: %q", path[i], i, path) - } - } - - return steps, nil -} - -// readKey extracts a key name from the path -func readKey(path string, start int) (string, int) { - i := start - for ; i < len(path); i++ { - if path[i] == '.' || path[i] == '[' { - break - } - } - return path[start:i], i -} - -// readIndex extracts an array index or wildcard from the path -func readIndex(path string, start int) (string, int) { - i := start - for ; i < len(path); i++ { - if path[i] == ']' { - break - } - } - return path[start:i], i -} - -// Get retrieves values with their paths from data at the specified JSONPath -// Each returned JSONNode contains both the value and its exact path in the data structure -func Get(data interface{}, path string) ([]JSONNode, error) { - steps, err := ParseJSONPath(path) - if err != nil { - return nil, fmt.Errorf("failed to parse JSONPath %q: %w", path, err) - } - - results := []JSONNode{} - err = traverseWithPaths(data, steps, &results, "$") - if err != nil { - return nil, fmt.Errorf("failed to traverse JSONPath %q: %w", path, err) - } - return results, nil -} - -// Set updates the value at the specified JSONPath in the original data structure. -// It only modifies the first matching node. -func Set(data interface{}, path string, value interface{}) error { - steps, err := ParseJSONPath(path) - if err != nil { - return fmt.Errorf("failed to parse JSONPath %q: %w", path, err) - } - - success := false - err = setWithPath(data, steps, &success, value, "$", ModifyFirstMode) - if err != nil { - return fmt.Errorf("failed to set value at JSONPath %q: %w", path, err) - } - return nil -} - -// SetAll updates all matching values at the specified JSONPath. -func SetAll(data interface{}, path string, value interface{}) error { - steps, err := ParseJSONPath(path) - if err != nil { - return fmt.Errorf("failed to parse JSONPath %q: %w", path, err) - } - - success := false - err = setWithPath(data, steps, &success, value, "$", ModifyAllMode) - if err != nil { - return fmt.Errorf("failed to set value at JSONPath %q: %w", path, err) - } - return nil -} - -// setWithPath modifies values while tracking paths -func setWithPath(node interface{}, steps []JSONStep, success *bool, value interface{}, currentPath string, mode TraversalMode) error { - if node == nil || *success && mode == ModifyFirstMode { - return nil - } - - // Skip root step - actualSteps := steps - if len(steps) > 0 && steps[0].Type == RootStep { - actualSteps = steps[1:] - } - - // If we have no steps left, we're setting the root value - if len(actualSteps) == 0 { - // For the root node, we need to handle it differently depending on what's passed in - // since we can't directly replace the interface{} variable - - // We'll signal success and let the JSONProcessor handle updating the root - *success = true - return nil - } - - // Process the first step - step := actualSteps[0] - remainingSteps := actualSteps[1:] - isLastStep := len(remainingSteps) == 0 - - switch step.Type { - case ChildStep: - m, ok := node.(map[string]interface{}) - if !ok { - return fmt.Errorf("node at path %q is not a map; actual type: %T", currentPath, node) - } - - childPath := currentPath + "." + step.Key - - if isLastStep { - // We've reached the target, set the value - m[step.Key] = value - *success = true - return nil - } - - // Create intermediate nodes if necessary - child, exists := m[step.Key] - if !exists { - // Create missing intermediate node - if len(remainingSteps) > 0 && remainingSteps[0].Type == IndexStep { - child = []interface{}{} - } else { - child = map[string]interface{}{} - } - m[step.Key] = child - } - - err := setWithPath(child, remainingSteps, success, value, childPath, mode) - if err != nil { - return fmt.Errorf("failed to set value at JSONPath %q: %w", childPath, err) - } - - case IndexStep: - arr, ok := node.([]interface{}) - if !ok { - return fmt.Errorf("node at path %q is not an array; actual type: %T", currentPath, node) - } - - // Handle wildcard index - if step.Index == -1 { - for i, item := range arr { - itemPath := fmt.Sprintf("%s[%d]", currentPath, i) - if isLastStep { - arr[i] = value - *success = true - if mode == ModifyFirstMode { - return nil - } - } else { - err := setWithPath(item, remainingSteps, success, value, itemPath, mode) - if err != nil { - return fmt.Errorf("failed to set value at JSONPath %q: %w", itemPath, err) - } - if *success && mode == ModifyFirstMode { - return nil - } - } - } - return nil - } - - // Handle specific index - if step.Index >= 0 && step.Index < len(arr) { - item := arr[step.Index] - itemPath := fmt.Sprintf("%s[%d]", currentPath, step.Index) - if isLastStep { - arr[step.Index] = value - *success = true - } else { - err := setWithPath(item, remainingSteps, success, value, itemPath, mode) - if err != nil { - return fmt.Errorf("failed to set value at JSONPath %q: %w", itemPath, err) - } - } - } - - case RecursiveDescentStep: - // For recursive descent, first check direct match at this level - if m, ok := node.(map[string]interface{}); ok && step.Key != "*" { - if val, exists := m[step.Key]; exists { - directPath := currentPath + "." + step.Key - if isLastStep { - m[step.Key] = value - *success = true - if mode == ModifyFirstMode { - return nil - } - } else { - err := setWithPath(val, remainingSteps, success, value, directPath, mode) - if err != nil { - return fmt.Errorf("failed to set value at JSONPath %q: %w", directPath, err) - } - if *success && mode == ModifyFirstMode { - return nil - } - } - } - } - - // Then continue recursion to all children - switch n := node.(type) { - case map[string]interface{}: - for k, v := range n { - childPath := currentPath + "." + k - // Skip keys we've already processed directly - if step.Key != "*" && k == step.Key { - continue - } - err := setWithPath(v, steps, success, value, childPath, mode) - if err != nil { - return fmt.Errorf("failed to set value at JSONPath %q: %w", childPath, err) - } - if *success && mode == ModifyFirstMode { - return nil - } - } - case []interface{}: - for i, v := range n { - childPath := fmt.Sprintf("%s[%d]", currentPath, i) - err := setWithPath(v, steps, success, value, childPath, mode) - if err != nil { - return fmt.Errorf("failed to set value at JSONPath %q: %w", childPath, err) - } - if *success && mode == ModifyFirstMode { - return nil - } - } - } - - case WildcardStep: - m, ok := node.(map[string]interface{}) - if !ok { - return fmt.Errorf("node at path %q is not a map; actual type: %T", currentPath, node) - } - - for k, v := range m { - childPath := currentPath + "." + k - if isLastStep { - m[k] = value - *success = true - if mode == ModifyFirstMode { - return nil - } - } else { - err := setWithPath(v, remainingSteps, success, value, childPath, mode) - if err != nil { - return fmt.Errorf("failed to set value at JSONPath %q: %w", childPath, err) - } - if *success && mode == ModifyFirstMode { - return nil - } - } - } - } - return nil -} - -// traverseWithPaths tracks both nodes and their paths during traversal -func traverseWithPaths(node interface{}, steps []JSONStep, results *[]JSONNode, currentPath string) error { - if len(steps) == 0 || node == nil { - return fmt.Errorf("cannot traverse with empty steps or nil node; steps length: %d, node: %v", len(steps), node) - } - - // Skip root step - actualSteps := steps - if steps[0].Type == RootStep { - if len(steps) == 1 { - *results = append(*results, JSONNode{Value: node, Path: currentPath}) - return nil - } - actualSteps = steps[1:] - } - - // Process the first step - step := actualSteps[0] - remainingSteps := actualSteps[1:] - isLastStep := len(remainingSteps) == 0 - - switch step.Type { - case ChildStep: - m, ok := node.(map[string]interface{}) - if !ok { - return fmt.Errorf("node is not a map; actual type: %T", node) - } - - child, exists := m[step.Key] - if !exists { - return fmt.Errorf("key not found: %s in node at path: %s", step.Key, currentPath) - } - - childPath := currentPath + "." + step.Key - if isLastStep { - *results = append(*results, JSONNode{Value: child, Path: childPath}) - } else { - err := traverseWithPaths(child, remainingSteps, results, childPath) - if err != nil { - return fmt.Errorf("failed to traverse JSONPath %q: %w", childPath, err) - } - } - - case IndexStep: - arr, ok := node.([]interface{}) - if !ok { - return fmt.Errorf("node is not an array; actual type: %T", node) - } - - // Handle wildcard index - if step.Index == -1 { - for i, item := range arr { - itemPath := fmt.Sprintf("%s[%d]", currentPath, i) - if isLastStep { - *results = append(*results, JSONNode{Value: item, Path: itemPath}) - } else { - err := traverseWithPaths(item, remainingSteps, results, itemPath) - if err != nil { - return fmt.Errorf("failed to traverse JSONPath %q: %w", itemPath, err) - } - } - } - return nil - } - - // Handle specific index - if step.Index >= 0 && step.Index < len(arr) { - item := arr[step.Index] - itemPath := fmt.Sprintf("%s[%d]", currentPath, step.Index) - if isLastStep { - *results = append(*results, JSONNode{Value: item, Path: itemPath}) - } else { - err := traverseWithPaths(item, remainingSteps, results, itemPath) - if err != nil { - return fmt.Errorf("failed to traverse JSONPath %q: %w", itemPath, err) - } - } - } else { - return fmt.Errorf("index %d out of bounds for array at path: %s", step.Index, currentPath) - } - - case RecursiveDescentStep: - // For recursive descent, first check direct match at this level - if m, ok := node.(map[string]interface{}); ok && step.Key != "*" { - if val, exists := m[step.Key]; exists { - directPath := currentPath + "." + step.Key - if isLastStep { - *results = append(*results, JSONNode{Value: val, Path: directPath}) - } else { - err := traverseWithPaths(val, remainingSteps, results, directPath) - if err != nil { - return fmt.Errorf("failed to traverse JSONPath %q: %w", directPath, err) - } - } - } - } - - // For wildcard, collect this node - if step.Key == "*" && isLastStep { - *results = append(*results, JSONNode{Value: node, Path: currentPath}) - } - - // Then continue recursion to all children - switch n := node.(type) { - case map[string]interface{}: - for k, v := range n { - childPath := currentPath + "." + k - err := traverseWithPaths(v, steps, results, childPath) // Use the same steps - if err != nil { - return fmt.Errorf("failed to traverse JSONPath %q: %w", childPath, err) - } - } - case []interface{}: - for i, v := range n { - childPath := fmt.Sprintf("%s[%d]", currentPath, i) - err := traverseWithPaths(v, steps, results, childPath) // Use the same steps - if err != nil { - return fmt.Errorf("failed to traverse JSONPath %q: %w", childPath, err) - } - } - } - - case WildcardStep: - m, ok := node.(map[string]interface{}) - if !ok { - return fmt.Errorf("node is not a map; actual type: %T", node) - } - - for k, v := range m { - childPath := currentPath + "." + k - if isLastStep { - *results = append(*results, JSONNode{Value: v, Path: childPath}) - } else { - err := traverseWithPaths(v, remainingSteps, results, childPath) - if err != nil { - return fmt.Errorf("failed to traverse JSONPath %q: %w", childPath, err) - } - } - } - } - return nil -} diff --git a/processor/jsonpath/jsonpath_get_set_test.go b/processor/jsonpath/jsonpath_get_set_test.go deleted file mode 100644 index 94fa707..0000000 --- a/processor/jsonpath/jsonpath_get_set_test.go +++ /dev/null @@ -1,577 +0,0 @@ -package jsonpath - -import ( - "reflect" - "testing" -) - -func TestGetWithPathsBasic(t *testing.T) { - tests := []struct { - name string - data map[string]interface{} - path string - expected []JSONNode - error bool - }{ - { - name: "simple property", - data: map[string]interface{}{ - "name": "John", - "age": 30, - }, - path: "$.name", - expected: []JSONNode{ - {Value: "John", Path: "$.name"}, - }, - }, - { - name: "nested property", - data: map[string]interface{}{ - "user": map[string]interface{}{ - "name": "John", - "age": 30, - }, - }, - path: "$.user.name", - expected: []JSONNode{ - {Value: "John", Path: "$.user.name"}, - }, - }, - { - name: "array access", - data: map[string]interface{}{ - "users": []interface{}{ - map[string]interface{}{"name": "John", "age": 30}, - map[string]interface{}{"name": "Jane", "age": 25}, - }, - }, - path: "$.users[1].name", - expected: []JSONNode{ - {Value: "Jane", Path: "$.users[1].name"}, - }, - }, - { - name: "wildcard", - data: map[string]interface{}{ - "users": []interface{}{ - map[string]interface{}{"name": "John", "age": 30}, - map[string]interface{}{"name": "Jane", "age": 25}, - }, - }, - path: "$.users[*].name", - expected: []JSONNode{ - {Value: "John", Path: "$.users[0].name"}, - {Value: "Jane", Path: "$.users[1].name"}, - }, - }, - { - name: "recursive descent", - data: map[string]interface{}{ - "user": map[string]interface{}{ - "name": "John", - "profile": map[string]interface{}{ - "email": "john@example.com", - }, - }, - "admin": map[string]interface{}{ - "email": "admin@example.com", - }, - }, - path: "$..email", - expected: []JSONNode{ - {Value: "john@example.com", Path: "$.user.profile.email"}, - {Value: "admin@example.com", Path: "$.admin.email"}, - }, - }, - { - name: "nonexistent path", - data: map[string]interface{}{ - "user": map[string]interface{}{ - "name": "John", - }, - }, - path: "$.user.email", - expected: []JSONNode{}, - error: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := Get(tt.data, tt.path) - if err != nil { - if !tt.error { - t.Errorf("GetWithPaths() returned error: %v", err) - } - return - } - - // For nonexistent path, we expect empty slice - if tt.name == "nonexistent path" { - if len(result) > 0 { - t.Errorf("GetWithPaths() returned %v, expected empty result", result) - } - return - } - - // Check if lengths match - if len(result) != len(tt.expected) { - t.Errorf("GetWithPaths() returned %d items, expected %d", len(result), len(tt.expected)) - return - } - - // For wildcard results, we need to check containment rather than exact order - if tt.name == "wildcard" || tt.name == "recursive descent" { - // For each expected item, check if it exists in the results by both value and path - for _, expected := range tt.expected { - found := false - for _, r := range result { - if reflect.DeepEqual(r.Value, expected.Value) && r.Path == expected.Path { - found = true - break - } - } - if !found { - t.Errorf("GetWithPaths() missing expected value: %v with path: %s", expected.Value, expected.Path) - } - } - } else { - // Otherwise check exact equality of both values and paths - for i, expected := range tt.expected { - if !reflect.DeepEqual(result[i].Value, expected.Value) { - t.Errorf("GetWithPaths() value at [%d] = %v, expected %v", i, result[i].Value, expected.Value) - } - if result[i].Path != expected.Path { - t.Errorf("GetWithPaths() path at [%d] = %s, expected %s", i, result[i].Path, expected.Path) - } - } - } - }) - } -} - -func TestSet(t *testing.T) { - t.Run("simple property", func(t *testing.T) { - data := map[string]interface{}{ - "name": "John", - "age": 30, - } - err := Set(data, "$.name", "Jane") - if err != nil { - t.Errorf("Set() returned error: %v", err) - return - } - - if data["name"] != "Jane" { - t.Errorf("Set() failed: expected name to be 'Jane', got %v", data["name"]) - } - }) - - t.Run("nested property", func(t *testing.T) { - data := map[string]interface{}{ - "user": map[string]interface{}{ - "name": "John", - "age": 30, - }, - } - err := Set(data, "$.user.name", "Jane") - if err != nil { - t.Errorf("Set() returned error: %v", err) - return - } - - user, ok := data["user"].(map[string]interface{}) - if !ok { - t.Fatalf("User is not a map") - } - if user["name"] != "Jane" { - t.Errorf("Set() failed: expected user.name to be 'Jane', got %v", user["name"]) - } - }) - - t.Run("array element", func(t *testing.T) { - data := map[string]interface{}{ - "users": []interface{}{ - map[string]interface{}{"name": "John", "age": 30}, - map[string]interface{}{"name": "Jane", "age": 25}, - }, - } - err := Set(data, "$.users[0].name", "Bob") - if err != nil { - t.Errorf("Set() returned error: %v", err) - return - } - users, ok := data["users"].([]interface{}) - if !ok { - t.Fatalf("Users is not a slice") - } - user0, ok := users[0].(map[string]interface{}) - if !ok { - t.Fatalf("User is not a map") - } - if user0["name"] != "Bob" { - t.Errorf("Set() failed: expected users[0].name to be 'Bob', got %v", user0["name"]) - } - }) - - t.Run("complex value", func(t *testing.T) { - data := map[string]interface{}{ - "user": map[string]interface{}{ - "name": "John", - "profile": map[string]interface{}{ - "email": "john@example.com", - }, - }, - } - - newProfile := map[string]interface{}{ - "email": "john.doe@example.com", - "phone": "123-456-7890", - } - - err := Set(data, "$.user.profile", newProfile) - if err != nil { - t.Errorf("Set() returned error: %v", err) - return - } - - userMap, ok := data["user"].(map[string]interface{}) - if !ok { - t.Fatalf("User is not a map") - } - - profile, ok := userMap["profile"].(map[string]interface{}) - if !ok { - t.Fatalf("Profile is not a map") - } - - if profile["email"] != "john.doe@example.com" || profile["phone"] != "123-456-7890" { - t.Errorf("Set() failed: expected profile to be updated with new values") - } - }) - - t.Run("create new property", func(t *testing.T) { - data := map[string]interface{}{ - "user": map[string]interface{}{ - "name": "John", - }, - } - - err := Set(data, "$.user.email", "john@example.com") - if err != nil { - t.Errorf("Set() returned error: %v", err) - return - } - userMap, ok := data["user"].(map[string]interface{}) - if !ok { - t.Fatalf("User is not a map") - } - - if email, exists := userMap["email"]; !exists || email != "john@example.com" { - t.Errorf("Set() failed: expected user.email to be 'john@example.com', got %v", userMap["email"]) - } - }) - - t.Run("create nested properties", func(t *testing.T) { - data := map[string]interface{}{ - "user": map[string]interface{}{ - "name": "John", - }, - } - - err := Set(data, "$.user.contact.email", "john@example.com") - if err != nil { - t.Errorf("Set() returned error: %v", err) - return - } - userMap, ok := data["user"].(map[string]interface{}) - if !ok { - t.Fatalf("User is not a map") - } - - contact, ok := userMap["contact"].(map[string]interface{}) - if !ok { - t.Fatalf("Contact is not a map") - } - - if email, exists := contact["email"]; !exists || email != "john@example.com" { - t.Errorf("Set() failed: expected user.contact.email to be 'john@example.com', got %v", contact["email"]) - } - }) - - t.Run("create array and element", func(t *testing.T) { - data := map[string]interface{}{ - "user": map[string]interface{}{ - "name": "John", - }, - } - - // This should create an empty addresses array, but won't be able to set index 0 - // since the array is empty - err := Set(data, "$.user.addresses[0].street", "123 Main St") - if err != nil { - t.Errorf("Set() returned error: %v", err) - return - } - }) - - t.Run("multiple targets (should only update first)", func(t *testing.T) { - data := map[string]interface{}{ - "users": []interface{}{ - map[string]interface{}{"active": true}, - map[string]interface{}{"active": true}, - }, - } - - err := Set(data, "$.users[*].active", false) - if err != nil { - t.Errorf("Set() returned error: %v", err) - return - } - - users, ok := data["users"].([]interface{}) - if !ok { - t.Fatalf("Users is not a slice") - } - - user0, ok := users[0].(map[string]interface{}) - if !ok { - t.Fatalf("User0 is not a map") - } - - user1, ok := users[1].(map[string]interface{}) - if !ok { - t.Fatalf("User1 is not a map") - } - - // Only the first one should be changed - if active, exists := user0["active"]; !exists || active != false { - t.Errorf("Set() failed: expected users[0].active to be false, got %v", user0["active"]) - } - - // The second one should remain unchanged - if active, exists := user1["active"]; !exists || active != true { - t.Errorf("Set() incorrectly modified users[1].active: expected true, got %v", user1["active"]) - } - }) - - t.Run("setting on root should not fail (anymore)", func(t *testing.T) { - data := map[string]interface{}{ - "name": "John", - } - - err := Set(data, "$", "Jane") - if err != nil { - t.Errorf("Set() returned error: %v", err) - return - } - - // Data should be unchanged - if data["name"] != "John" { - t.Errorf("Data was modified when setting on root") - } - }) -} - -func TestSetAll(t *testing.T) { - t.Run("simple property", func(t *testing.T) { - data := map[string]interface{}{ - "name": "John", - "age": 30, - } - err := SetAll(data, "$.name", "Jane") - if err != nil { - t.Errorf("SetAll() returned error: %v", err) - return - } - if data["name"] != "Jane" { - t.Errorf("SetAll() failed: expected name to be 'Jane', got %v", data["name"]) - } - }) - - t.Run("all array elements", func(t *testing.T) { - data := map[string]interface{}{ - "users": []interface{}{ - map[string]interface{}{"active": true}, - map[string]interface{}{"active": true}, - }, - } - - err := SetAll(data, "$.users[*].active", false) - if err != nil { - t.Errorf("SetAll() returned error: %v", err) - return - } - - users, ok := data["users"].([]interface{}) - if !ok { - t.Fatalf("Users is not a slice") - } - - // Both elements should be updated - for i, user := range users { - userMap, ok := user.(map[string]interface{}) - if !ok { - t.Fatalf("User%d is not a map", i) - } - - if active, exists := userMap["active"]; !exists || active != false { - t.Errorf("SetAll() failed: expected users[%d].active to be false, got %v", i, userMap["active"]) - } - } - }) - - t.Run("recursive descent", func(t *testing.T) { - data := map[string]interface{}{ - "user": map[string]interface{}{ - "profile": map[string]interface{}{ - "active": true, - }, - }, - "admin": map[string]interface{}{ - "profile": map[string]interface{}{ - "active": true, - }, - }, - } - - err := SetAll(data, "$..active", false) - if err != nil { - t.Errorf("SetAll() returned error: %v", err) - return - } - - // Check user profile - userProfile, ok := data["user"].(map[string]interface{})["profile"].(map[string]interface{}) - if !ok { - t.Fatalf("Failed to access user.profile") - } - if active, exists := userProfile["active"]; !exists || active != false { - t.Errorf("SetAll() didn't update user.profile.active, got: %v", active) - } - - // Check admin profile - adminProfile, ok := data["admin"].(map[string]interface{})["profile"].(map[string]interface{}) - if !ok { - t.Fatalf("Failed to access admin.profile") - } - if active, exists := adminProfile["active"]; !exists || active != false { - t.Errorf("SetAll() didn't update admin.profile.active, got: %v", active) - } - }) -} - -func TestGetWithPathsExtended(t *testing.T) { - tests := []struct { - name string - data map[string]interface{} - path string - expected []JSONNode - }{ - { - name: "simple property", - data: map[string]interface{}{ - "name": "John", - "age": 30, - }, - path: "$.name", - expected: []JSONNode{ - {Value: "John", Path: "$.name"}, - }, - }, - { - name: "nested property", - data: map[string]interface{}{ - "user": map[string]interface{}{ - "name": "John", - "age": 30, - }, - }, - path: "$.user.name", - expected: []JSONNode{ - {Value: "John", Path: "$.user.name"}, - }, - }, - { - name: "array access", - data: map[string]interface{}{ - "users": []interface{}{ - map[string]interface{}{"name": "John", "age": 30}, - map[string]interface{}{"name": "Jane", "age": 25}, - }, - }, - path: "$.users[1].name", - expected: []JSONNode{ - {Value: "Jane", Path: "$.users[1].name"}, - }, - }, - { - name: "wildcard", - data: map[string]interface{}{ - "users": []interface{}{ - map[string]interface{}{"name": "John", "age": 30}, - map[string]interface{}{"name": "Jane", "age": 25}, - }, - }, - path: "$.users[*].name", - expected: []JSONNode{ - {Value: "John", Path: "$.users[0].name"}, - {Value: "Jane", Path: "$.users[1].name"}, - }, - }, - { - name: "recursive descent", - data: map[string]interface{}{ - "user": map[string]interface{}{ - "name": "John", - "profile": map[string]interface{}{ - "email": "john@example.com", - }, - }, - "admin": map[string]interface{}{ - "email": "admin@example.com", - }, - }, - path: "$..email", - expected: []JSONNode{ - {Value: "john@example.com", Path: "$.user.profile.email"}, - {Value: "admin@example.com", Path: "$.admin.email"}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := Get(tt.data, tt.path) - if err != nil { - t.Errorf("GetWithPaths() returned error: %v", err) - return - } - - // Check if lengths match - if len(result) != len(tt.expected) { - t.Errorf("GetWithPaths() 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 { - // Check if value matches - if reflect.DeepEqual(r.Value, expected.Value) { - found = true - // Check if 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) - } - } - }) - } -} diff --git a/processor/jsonpath/jsonpath_test.go b/processor/jsonpath/jsonpath_test.go deleted file mode 100644 index 69a7175..0000000 --- a/processor/jsonpath/jsonpath_test.go +++ /dev/null @@ -1,318 +0,0 @@ -package jsonpath - -import ( - "reflect" - "testing" -) - -var testData = map[string]interface{}{ - "store": map[string]interface{}{ - "book": []interface{}{ - map[string]interface{}{ - "title": "The Fellowship of the Ring", - "price": 22.99, - }, - map[string]interface{}{ - "title": "The Two Towers", - "price": 23.45, - }, - }, - "bicycle": map[string]interface{}{ - "color": "red", - "price": 199.95, - }, - }, -} - -func TestParser(t *testing.T) { - tests := []struct { - path string - steps []JSONStep - wantErr bool - }{ - { - path: "$.store.bicycle.color", - steps: []JSONStep{ - {Type: RootStep}, - {Type: ChildStep, Key: "store"}, - {Type: ChildStep, Key: "bicycle"}, - {Type: ChildStep, Key: "color"}, - }, - }, - { - path: "$..price", - steps: []JSONStep{ - {Type: RootStep}, - {Type: RecursiveDescentStep, Key: "price"}, - }, - }, - { - path: "$.store.book[*].title", - steps: []JSONStep{ - {Type: RootStep}, - {Type: ChildStep, Key: "store"}, - {Type: ChildStep, Key: "book"}, - {Type: IndexStep, Index: -1}, // Wildcard - {Type: ChildStep, Key: "title"}, - }, - }, - { - path: "$.store.book[0]", - steps: []JSONStep{ - {Type: RootStep}, - {Type: ChildStep, Key: "store"}, - {Type: ChildStep, Key: "book"}, - {Type: IndexStep, Index: 0}, - }, - }, - { - path: "invalid.path", - wantErr: true, - }, - { - path: "$.store.book[abc]", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.path, func(t *testing.T) { - steps, err := ParseJSONPath(tt.path) - if (err != nil) != tt.wantErr { - t.Fatalf("ParseJSONPath() error = %v, wantErr %v", err, tt.wantErr) - } - if !tt.wantErr && !reflect.DeepEqual(steps, tt.steps) { - t.Errorf("ParseJSONPath() steps = %+v, want %+v", steps, tt.steps) - } - }) - } -} - -func TestEvaluator(t *testing.T) { - tests := []struct { - name string - path string - expected []JSONNode - error bool - }{ - { - name: "simple_property_access", - path: "$.store.bicycle.color", - expected: []JSONNode{ - {Value: "red", Path: "$.store.bicycle.color"}, - }, - }, - { - name: "array_index_access", - path: "$.store.book[0].title", - expected: []JSONNode{ - {Value: "The Fellowship of the Ring", Path: "$.store.book[0].title"}, - }, - }, - { - name: "wildcard_array_access", - path: "$.store.book[*].title", - expected: []JSONNode{ - {Value: "The Fellowship of the Ring", Path: "$.store.book[0].title"}, - {Value: "The Two Towers", Path: "$.store.book[1].title"}, - }, - }, - { - name: "recursive_price_search", - path: "$..price", - expected: []JSONNode{ - {Value: 22.99, Path: "$.store.book[0].price"}, - {Value: 23.45, Path: "$.store.book[1].price"}, - {Value: 199.95, Path: "$.store.bicycle.price"}, - }, - }, - { - name: "wildcard_recursive", - path: "$..*", - expected: []JSONNode{ - // These will be compared by value only, paths will be validated separately - {Value: testData["store"].(map[string]interface{})["book"]}, - {Value: testData["store"].(map[string]interface{})["bicycle"]}, - {Value: testData["store"].(map[string]interface{})["book"].([]interface{})[0]}, - {Value: testData["store"].(map[string]interface{})["book"].([]interface{})[1]}, - {Value: "The Fellowship of the Ring"}, - {Value: 22.99}, - {Value: "The Two Towers"}, - {Value: 23.45}, - {Value: "red"}, - {Value: 199.95}, - }, - }, - { - name: "invalid_index", - path: "$.store.book[5]", - expected: []JSONNode{}, - error: true, - }, - { - name: "nonexistent_property", - path: "$.store.nonexistent", - expected: []JSONNode{}, - error: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Use GetWithPaths directly - result, err := Get(testData, tt.path) - if err != nil { - if !tt.error { - t.Errorf("Get() returned error: %v", err) - } - return - } - - // Special handling for wildcard recursive test - if tt.name == "wildcard_recursive" { - // Skip length check for wildcard recursive since it might vary - // Just verify that each expected item is in the results - - // Validate values match and paths are filled in - for _, e := range tt.expected { - found := false - for _, r := range result { - if reflect.DeepEqual(r.Value, e.Value) { - found = true - break - } - } - if !found { - t.Errorf("Expected value %v not found in results", e.Value) - } - } - return - } - - if len(result) != len(tt.expected) { - t.Errorf("Expected %d items, got %d", len(tt.expected), len(result)) - } - - // 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(nil, "$.a.b") - 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 := ParseJSONPath("") - if err == nil { - t.Error("Expected error for empty path") - } - }) - - t.Run("numeric_keys", func(t *testing.T) { - data := map[string]interface{}{ - "42": "answer", - } - result, err := Get(data, "$.42") - if err != nil { - t.Errorf("Get() returned error: %v", err) - return - } - if len(result) == 0 || result[0].Value != "answer" { - t.Errorf("Expected 'answer', got %v", result) - } - }) -} - -func TestGetWithPaths(t *testing.T) { - tests := []struct { - name string - path string - expected []JSONNode - }{ - { - name: "simple_property_access", - path: "$.store.bicycle.color", - expected: []JSONNode{ - {Value: "red", Path: "$.store.bicycle.color"}, - }, - }, - { - name: "array_index_access", - path: "$.store.book[0].title", - expected: []JSONNode{ - {Value: "The Fellowship of the Ring", Path: "$.store.book[0].title"}, - }, - }, - { - name: "wildcard_array_access", - path: "$.store.book[*].title", - expected: []JSONNode{ - {Value: "The Fellowship of the Ring", Path: "$.store.book[0].title"}, - {Value: "The Two Towers", Path: "$.store.book[1].title"}, - }, - }, - { - name: "recursive_price_search", - path: "$..price", - expected: []JSONNode{ - {Value: 22.99, Path: "$.store.book[0].price"}, - {Value: 23.45, Path: "$.store.book[1].price"}, - {Value: 199.95, Path: "$.store.bicycle.price"}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := Get(testData, 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("GetWithPaths() 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) - } - } - }) - } -} diff --git a/processor/xpath/parser_manual_test.go b/processor/xpath/parser_manual_test.go deleted file mode 100644 index d852c95..0000000 --- a/processor/xpath/parser_manual_test.go +++ /dev/null @@ -1,4 +0,0 @@ -// The package is now using github.com/antchfx/xmlquery for XPath parsing. -// The parsing functionality tests have been removed since we're now -// delegating XPath parsing to the xmlquery library. -package xpath diff --git a/processor/xpath/parser_test.go b/processor/xpath/parser_test.go deleted file mode 100644 index d852c95..0000000 --- a/processor/xpath/parser_test.go +++ /dev/null @@ -1,4 +0,0 @@ -// The package is now using github.com/antchfx/xmlquery for XPath parsing. -// The parsing functionality tests have been removed since we're now -// delegating XPath parsing to the xmlquery library. -package xpath diff --git a/processor/xpath/xpath.go b/processor/xpath/xpath.go deleted file mode 100644 index d05bb1d..0000000 --- a/processor/xpath/xpath.go +++ /dev/null @@ -1,133 +0,0 @@ -package xpath - -import ( - "errors" - "fmt" - - "github.com/antchfx/xmlquery" -) - -// Get retrieves nodes from XML data using an XPath expression -func Get(node *xmlquery.Node, path string) ([]*xmlquery.Node, error) { - if node == nil { - return nil, errors.New("nil node provided") - } - - // Execute xpath query directly - nodes, err := xmlquery.QueryAll(node, path) - if err != nil { - return nil, fmt.Errorf("failed to execute XPath query: %v", err) - } - - return nodes, nil -} - -// Set updates a single node in the XML data using an XPath expression -func Set(node *xmlquery.Node, path string, value interface{}) error { - if node == nil { - return errors.New("nil node provided") - } - - // Find the node to update - nodes, err := xmlquery.QueryAll(node, path) - if err != nil { - return fmt.Errorf("failed to execute XPath query: %v", err) - } - - if len(nodes) == 0 { - return fmt.Errorf("no nodes found for path: %s", path) - } - - // Update the first matching node - updateNodeValue(nodes[0], value) - - return nil -} - -// SetAll updates all nodes that match the XPath expression -func SetAll(node *xmlquery.Node, path string, value interface{}) error { - if node == nil { - return errors.New("nil node provided") - } - - // Find all nodes to update - nodes, err := xmlquery.QueryAll(node, path) - if err != nil { - return fmt.Errorf("failed to execute XPath query: %v", err) - } - - if len(nodes) == 0 { - return fmt.Errorf("no nodes found for path: %s", path) - } - - // Update all matching nodes - for _, matchNode := range nodes { - updateNodeValue(matchNode, value) - } - - return nil -} - -// Helper function to update a node's value -func updateNodeValue(node *xmlquery.Node, value interface{}) { - strValue := fmt.Sprintf("%v", value) - - // Handle different node types - switch node.Type { - case xmlquery.AttributeNode: - // For attribute nodes, update the attribute value - parent := node.Parent - if parent != nil { - for i, attr := range parent.Attr { - if attr.Name.Local == node.Data { - parent.Attr[i].Value = strValue - break - } - } - } - case xmlquery.TextNode: - // For text nodes, update the text content - node.Data = strValue - case xmlquery.ElementNode: - // For element nodes, clear existing text children and add a new text node - // First, remove all existing text children - var nonTextChildren []*xmlquery.Node - for child := node.FirstChild; child != nil; child = child.NextSibling { - if child.Type != xmlquery.TextNode { - nonTextChildren = append(nonTextChildren, child) - } - } - - // Clear all children - node.FirstChild = nil - node.LastChild = nil - - // Add a new text node - textNode := &xmlquery.Node{ - Type: xmlquery.TextNode, - Data: strValue, - Parent: node, - } - - // Set the text node as the first child - node.FirstChild = textNode - node.LastChild = textNode - - // Add back non-text children - for _, child := range nonTextChildren { - child.Parent = node - - // If this is the first child being added back - if node.FirstChild == textNode && node.LastChild == textNode { - node.FirstChild.NextSibling = child - child.PrevSibling = node.FirstChild - node.LastChild = child - } else { - // Add to the end of the chain - node.LastChild.NextSibling = child - child.PrevSibling = node.LastChild - node.LastChild = child - } - } - } -} diff --git a/processor/xpath/xpath_test.go b/processor/xpath/xpath_test.go deleted file mode 100644 index 95ab90e..0000000 --- a/processor/xpath/xpath_test.go +++ /dev/null @@ -1,474 +0,0 @@ -package xpath - -import ( - "strings" - "testing" - - "github.com/antchfx/xmlquery" -) - -// Parse test XML data once at the beginning for use in multiple tests -func parseTestXML(t *testing.T, xmlData string) *xmlquery.Node { - doc, err := xmlquery.Parse(strings.NewReader(xmlData)) - if err != nil { - t.Fatalf("Failed to parse test XML: %v", err) - } - return doc -} - -// XML test data as a string for our tests -var testXML = ` - - - The Fellowship of the Ring - J.R.R. Tolkien - 1954 - 22.99 - - - The Two Towers - J.R.R. Tolkien - 1954 - 23.45 - - - Learning XML - Erik T. Ray - 2003 - 39.95 - - - red - 199.95 - - -` - -func TestEvaluator(t *testing.T) { - // Parse the test XML data once for all test cases - doc := parseTestXML(t, testXML) - - tests := []struct { - name string - path string - error bool - }{ - { - name: "simple_element_access", - path: "/store/bicycle/color", - }, - { - name: "recursive_element_access", - path: "//price", - }, - { - name: "wildcard_element_access", - path: "/store/book/*", - }, - { - name: "attribute_exists_predicate", - path: "//title[@lang]", - }, - { - name: "attribute_equals_predicate", - path: "//title[@lang='en']", - }, - { - name: "value_comparison_predicate", - path: "/store/book[price>35.00]/title", - error: true, - }, - { - name: "last_predicate", - path: "/store/book[last()]/title", - error: true, - }, - { - name: "last_minus_predicate", - path: "/store/book[last()-1]/title", - error: true, - }, - { - name: "position_predicate", - path: "/store/book[position()<3]/title", - error: true, - }, - { - name: "invalid_index", - path: "/store/book[10]/title", - error: true, - }, - { - name: "nonexistent_element", - path: "/store/nonexistent", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := Get(doc, tt.path) - - // Handle expected errors - if tt.error { - if err == nil && len(result) == 0 { - // If we expected an error but got empty results instead, that's okay - return - } - if err != nil { - // If we got an error as expected, that's okay - return - } - } else if err != nil { - // If we didn't expect an error but got one, that's a test failure - t.Errorf("Get(%q) returned unexpected error: %v", tt.path, err) - return - } - - // Special cases where we don't care about exact matches - switch tt.name { - case "wildcard_element_access": - // Just check that we got some elements - if len(result) == 0 { - t.Errorf("Expected multiple elements for wildcard, got none") - } - return - case "attribute_exists_predicate", "attribute_equals_predicate": - // Just check that we got some titles - if len(result) == 0 { - t.Errorf("Expected titles with lang attribute, got none") - } - // Ensure all are title elements - for _, node := range result { - if node.Data != "title" { - t.Errorf("Expected title elements, got: %s", node.Data) - } - } - return - case "nonexistent_element": - // Just check that we got empty results - if len(result) != 0 { - t.Errorf("Expected empty results for nonexistent element, got %d items", len(result)) - } - return - } - - // For other cases, just verify we got results - if len(result) == 0 { - t.Errorf("Expected results for path %s, got none", tt.path) - } - }) - } -} - -func TestEdgeCases(t *testing.T) { - t.Run("nil_node", func(t *testing.T) { - result, err := Get(nil, "/store/book") - if err == nil { - t.Errorf("Expected error for nil node") - return - } - if len(result) > 0 { - t.Errorf("Expected empty result, got %v", result) - } - }) - - t.Run("invalid_xml", func(t *testing.T) { - invalidXML, err := xmlquery.Parse(strings.NewReader("xml")) - if err != nil { - // If parsing fails, that's expected - return - } - - _, err = Get(invalidXML, "/store") - if err == nil { - t.Error("Expected error for invalid XML structure") - } - }) - - // For these tests with the simple XML, we expect just one result - simpleXML := `Test` - doc := parseTestXML(t, simpleXML) - - t.Run("current_node", func(t *testing.T) { - result, err := Get(doc, "/root/book/.") - if err != nil { - t.Errorf("Get() returned error: %v", err) - return - } - if len(result) > 1 { - t.Errorf("Expected at most 1 result, got %d", len(result)) - } - if len(result) > 0 { - // Verify it's the book node - if result[0].Data != "book" { - t.Errorf("Expected book node, got %v", result[0].Data) - } - } - }) - - t.Run("attributes", func(t *testing.T) { - result, err := Get(doc, "/root/book/title/@lang") - if err != nil { - t.Errorf("Get() returned error: %v", err) - return - } - if len(result) != 1 || result[0].InnerText() != "en" { - t.Errorf("Expected 'en', got %v", result[0].InnerText()) - } - }) -} - -func TestGetWithPaths(t *testing.T) { - // Use a simplified, well-formed XML document - simpleXML := ` - - The Book Title - Author Name - 19.99 - - - red - 199.95 - -` - - // Parse the XML for testing - doc := parseTestXML(t, simpleXML) - - // Debug: Print the test XML - t.Logf("Test XML:\n%s", simpleXML) - - tests := []struct { - name string - path string - expectedValue string - }{ - { - name: "simple_element_access", - path: "/store/bicycle/color", - expectedValue: "red", - }, - { - name: "attribute_access", - path: "/store/book/title/@lang", - expectedValue: "en", - }, - { - name: "recursive_with_attribute", - path: "//title[@lang='en']", - expectedValue: "The Book Title", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Debug: Print the path we're looking for - t.Logf("Looking for path: %s", tt.path) - - result, err := Get(doc, tt.path) - if err != nil { - t.Errorf("Get(%q) returned error: %v", tt.path, err) - return - } - - // Debug: Print the results - t.Logf("Got %d results", len(result)) - for i, r := range result { - t.Logf("Result %d: Node=%s, Value=%v", i, r.Data, r.InnerText()) - } - - // Check that we got results - if len(result) == 0 { - t.Errorf("Get(%q) returned no results", tt.path) - return - } - - // For attribute access test, do more specific checks - if tt.name == "attribute_access" { - // Check the first result's value matches expected - if result[0].InnerText() != tt.expectedValue { - t.Errorf("Attribute value: got %v, expected %s", result[0].InnerText(), tt.expectedValue) - } - } - - // For simple element access, check the text content - if tt.name == "simple_element_access" { - if text := result[0].InnerText(); text != tt.expectedValue { - t.Errorf("Element text: got %s, expected %s", text, tt.expectedValue) - } - } - - // For recursive with attribute test, check title elements with lang="en" - if tt.name == "recursive_with_attribute" { - for _, node := range result { - // Check the node is a title - if node.Data != "title" { - t.Errorf("Expected title element, got %s", node.Data) - } - - // Check text content - if text := node.InnerText(); text != tt.expectedValue { - t.Errorf("Text content: got %s, expected %s", text, tt.expectedValue) - } - - // Check attributes - find the lang attribute - hasLang := false - for _, attr := range node.Attr { - if attr.Name.Local == "lang" && attr.Value == "en" { - hasLang = true - break - } - } - if !hasLang { - t.Errorf("Expected lang=\"en\" attribute, but it was not found") - } - } - } - }) - } -} - -func TestSet(t *testing.T) { - t.Run("simple element", func(t *testing.T) { - xmlData := `John` - doc := parseTestXML(t, xmlData) - - err := Set(doc, "/root/name", "Jane") - if err != nil { - t.Errorf("Set() returned error: %v", err) - return - } - - // Verify the change - result, err := Get(doc, "/root/name") - if err != nil { - t.Errorf("Get() returned error: %v", err) - return - } - if len(result) != 1 { - t.Errorf("Expected 1 result, got %d", len(result)) - return - } - - // Check text content - if text := result[0].InnerText(); text != "Jane" { - t.Errorf("Expected text 'Jane', got '%s'", text) - } - }) - - t.Run("attribute", func(t *testing.T) { - xmlData := `` - doc := parseTestXML(t, xmlData) - - err := Set(doc, "/root/element/@id", "456") - if err != nil { - t.Errorf("Set() returned error: %v", err) - return - } - - // Verify the change - result, err := Get(doc, "/root/element/@id") - if err != nil { - t.Errorf("Get() returned error: %v", err) - return - } - if len(result) != 1 { - t.Errorf("Expected 1 result, got %d", len(result)) - return - } - - // For attributes, check the inner text - if text := result[0].InnerText(); text != "456" { - t.Errorf("Expected attribute value '456', got '%s'", text) - } - }) - - t.Run("indexed element", func(t *testing.T) { - xmlData := `firstsecond` - doc := parseTestXML(t, xmlData) - - err := Set(doc, "/root/items/item[1]", "changed") - if err != nil { - t.Errorf("Set() returned error: %v", err) - return - } - - // Verify the change using XPath that specifically targets the first item - result, err := Get(doc, "/root/items/item[1]") - if err != nil { - t.Errorf("Get() returned error: %v", err) - return - } - - // Check if we have results - if len(result) == 0 { - t.Errorf("Expected at least one result for /root/items/item[1]") - return - } - - // Check text content - if text := result[0].InnerText(); text != "changed" { - t.Errorf("Expected text 'changed', got '%s'", text) - } - }) -} - -func TestSetAll(t *testing.T) { - t.Run("multiple elements", func(t *testing.T) { - xmlData := `firstsecond` - doc := parseTestXML(t, xmlData) - - err := SetAll(doc, "//item", "changed") - if err != nil { - t.Errorf("SetAll() returned error: %v", err) - return - } - - // Verify all items are changed - result, err := Get(doc, "//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 - } - - // Check each node - for i, node := range result { - if text := node.InnerText(); text != "changed" { - t.Errorf("Item %d: expected text 'changed', got '%s'", i, text) - } - } - }) - - t.Run("attributes", func(t *testing.T) { - xmlData := `` - doc := parseTestXML(t, xmlData) - - err := SetAll(doc, "//item/@id", "new") - if err != nil { - t.Errorf("SetAll() returned error: %v", err) - return - } - - // Verify all attributes are changed - result, err := Get(doc, "//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 attributes, check inner text - for i, node := range result { - if text := node.InnerText(); text != "new" { - t.Errorf("Attribute %d: expected value 'new', got '%s'", i, text) - } - } - }) -}