diff --git a/processor/json.go b/processor/json.go
deleted file mode 100644
index aff788b..0000000
--- a/processor/json.go
+++ /dev/null
@@ -1,194 +0,0 @@
-package processor
-
-import (
- "encoding/json"
- "fmt"
- "modify/logger"
- "modify/processor/jsonpath"
-
- lua "github.com/yuin/gopher-lua"
-)
-
-// JSONProcessor implements the Processor interface for JSON documents
-type JSONProcessor struct{}
-
-// ProcessContent implements the Processor interface for JSONProcessor
-func (p *JSONProcessor) ProcessContent(content string, pattern string, luaExpr string) (string, int, int, error) {
- logger.Debug("Processing JSON content with JSONPath: %s", pattern)
-
- // Parse JSON document
- logger.Trace("Parsing JSON document")
- var jsonData interface{}
- err := json.Unmarshal([]byte(content), &jsonData)
- if err != nil {
- logger.Error("Failed to parse JSON: %v", err)
- return content, 0, 0, fmt.Errorf("error parsing JSON: %v", err)
- }
-
- // Find nodes matching the JSONPath pattern
- logger.Debug("Executing JSONPath query: %s", pattern)
- nodes, err := jsonpath.Get(jsonData, pattern)
- if err != nil {
- logger.Error("Failed to execute JSONPath: %v", err)
- return content, 0, 0, fmt.Errorf("error getting nodes: %v", err)
- }
-
- matchCount := len(nodes)
- logger.Debug("Found %d nodes matching JSONPath", matchCount)
- if matchCount == 0 {
- logger.Warning("No nodes matched the JSONPath pattern: %s", pattern)
- return content, 0, 0, nil
- }
-
- modCount := 0
- for i, node := range nodes {
- logger.Trace("Processing node #%d at path: %s with value: %v", i+1, node.Path, node.Value)
-
- // Initialize Lua
- L, err := NewLuaState()
- if err != nil {
- logger.Error("Failed to create Lua state: %v", err)
- return content, len(nodes), 0, fmt.Errorf("error creating Lua state: %v", err)
- }
- defer L.Close()
- logger.Trace("Lua state initialized successfully")
-
- err = p.ToLua(L, node.Value)
- if err != nil {
- logger.Error("Failed to convert value to Lua: %v", err)
- return content, len(nodes), 0, fmt.Errorf("error converting to Lua: %v", err)
- }
- logger.Trace("Converted node value to Lua: %v", node.Value)
-
- originalScript := luaExpr
- fullScript := BuildLuaScript(luaExpr)
- logger.Debug("Original script: %q, Full script: %q", originalScript, fullScript)
-
- // Execute Lua script
- logger.Trace("Executing Lua script: %q", fullScript)
- if err := L.DoString(fullScript); err != nil {
- logger.Error("Failed to execute Lua script: %v", err)
- return content, len(nodes), 0, fmt.Errorf("error executing Lua %q: %v", fullScript, err)
- }
- logger.Trace("Lua script executed successfully")
-
- // Get modified value
- result, err := p.FromLua(L)
- if err != nil {
- logger.Error("Failed to get result from Lua: %v", err)
- return content, len(nodes), 0, fmt.Errorf("error getting result from Lua: %v", err)
- }
- logger.Trace("Retrieved modified value from Lua: %v", result)
-
- modified := false
- modified = L.GetGlobal("modified").String() == "true"
- if !modified {
- logger.Debug("No changes made to node at path: %s", node.Path)
- continue
- }
-
- // Apply the modification to the JSON data
- logger.Debug("Updating JSON at path: %s with new value: %v", node.Path, result)
- err = p.updateJSONValue(jsonData, node.Path, result)
- if err != nil {
- logger.Error("Failed to update JSON at path %s: %v", node.Path, err)
- return content, len(nodes), 0, fmt.Errorf("error updating JSON: %v", err)
- }
- logger.Debug("Updated JSON at path: %s successfully", node.Path)
- modCount++
- }
-
- logger.Info("JSON processing complete: %d modifications from %d matches", modCount, matchCount)
-
- // Convert the modified JSON back to a string with same formatting
- logger.Trace("Marshalling JSON data back to string")
- var jsonBytes []byte
- jsonBytes, err = json.MarshalIndent(jsonData, "", " ")
- if err != nil {
- logger.Error("Failed to marshal JSON: %v", err)
- return content, modCount, matchCount, fmt.Errorf("error marshalling JSON: %v", err)
- }
- return string(jsonBytes), modCount, matchCount, nil
-}
-
-// updateJSONValue updates a value in the JSON structure based on its JSONPath
-func (p *JSONProcessor) updateJSONValue(jsonData interface{}, path string, newValue interface{}) error {
- logger.Trace("Updating JSON value at path: %s", path)
-
- // Special handling for root node
- if path == "$" {
- logger.Debug("Handling special case for root node update")
- // For the root node, we'll copy the value to the jsonData reference
- // This is a special case since we can't directly replace the interface{} variable
-
- // We need to handle different types of root elements
- switch rootValue := newValue.(type) {
- case map[string]interface{}:
- // For objects, we need to copy over all keys
- rootMap, ok := jsonData.(map[string]interface{})
- if !ok {
- // If the original wasn't a map, completely replace it with the new map
- // This is handled by the jsonpath.Set function
- logger.Debug("Root was not a map, replacing entire root")
- return jsonpath.Set(jsonData, path, newValue)
- }
-
- // Clear the original map
- logger.Trace("Clearing original root map")
- for k := range rootMap {
- delete(rootMap, k)
- }
-
- // Copy all keys from the new map
- logger.Trace("Copying keys to root map")
- for k, v := range rootValue {
- rootMap[k] = v
- }
- return nil
-
- case []interface{}:
- // For arrays, we need to handle similarly
- rootArray, ok := jsonData.([]interface{})
- if !ok {
- // If the original wasn't an array, use jsonpath.Set
- logger.Debug("Root was not an array, replacing entire root")
- return jsonpath.Set(jsonData, path, newValue)
- }
-
- // Clear and recreate the array
- logger.Trace("Replacing root array")
- *&rootArray = rootValue
- return nil
-
- default:
- // For other types, use jsonpath.Set
- logger.Debug("Replacing root with primitive value")
- return jsonpath.Set(jsonData, path, newValue)
- }
- }
-
- // For non-root paths, use the regular Set method
- logger.Trace("Using regular Set method for non-root path")
- err := jsonpath.Set(jsonData, path, newValue)
- if err != nil {
- logger.Error("Failed to set JSON value at path %s: %v", path, err)
- return fmt.Errorf("failed to update JSON value at path '%s': %w", path, err)
- }
- return nil
-}
-
-// ToLua converts JSON values to Lua variables
-func (p *JSONProcessor) ToLua(L *lua.LState, data interface{}) error {
- table, err := ToLua(L, data)
- if err != nil {
- return err
- }
- L.SetGlobal("v", table)
- return nil
-}
-
-// FromLua retrieves values from Lua
-func (p *JSONProcessor) FromLua(L *lua.LState) (interface{}, error) {
- luaValue := L.GetGlobal("v")
- return FromLua(L, luaValue)
-}
diff --git a/processor/json_test.go b/processor/json_test.go
deleted file mode 100644
index 7070348..0000000
--- a/processor/json_test.go
+++ /dev/null
@@ -1,1771 +0,0 @@
-package processor
-
-import (
- "encoding/json"
- "strings"
- "testing"
-
- "github.com/PaesslerAG/jsonpath"
-)
-
-// fi ndMatchingPaths finds nodes in a JSON document that match the given JSONPath
-func findMatchingPaths(jsonDoc interface{}, path string) ([]interface{}, error) {
- // Use the existing jsonpath library to extract values
- result, err := jsonpath.Get(path, jsonDoc)
- if err != nil {
- return nil, err
- }
-
- // Convert the result to a slice
- var values []interface{}
- switch v := result.(type) {
- case []interface{}:
- values = v
- default:
- values = []interface{}{v}
- }
-
- return values, nil
-}
-
-// TestJSONProcessor_Process_NumericValues tests processing numeric JSON values
-func TestJSONProcessor_Process_NumericValues(t *testing.T) {
- content := `{
- "books": [
- {
- "title": "The Go Programming Language",
- "price": 44.95
- },
- {
- "title": "Go in Action",
- "price": 5.95
- }
- ]
- }`
-
- expected := `{
- "books": [
- {
- "title": "The Go Programming Language",
- "price": 89.9
- },
- {
- "title": "Go in Action",
- "price": 11.9
- }
- ]
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.books[*]", "v.price=v.price*2")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 2 {
- t.Errorf("Expected 2 matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 modifications, got %d", modCount)
- }
-
- // Compare parsed JSON objects instead of formatted strings
- var resultObj map[string]interface{}
- if err := json.Unmarshal([]byte(result), &resultObj); err != nil {
- t.Fatalf("Failed to parse result JSON: %v", err)
- }
-
- var expectedObj map[string]interface{}
- if err := json.Unmarshal([]byte(expected), &expectedObj); err != nil {
- t.Fatalf("Failed to parse expected JSON: %v", err)
- }
-
- // Compare the first book's price
- resultBooks, ok := resultObj["books"].([]interface{})
- if !ok || len(resultBooks) < 1 {
- t.Fatalf("Expected books array in result")
- }
- resultBook1, ok := resultBooks[0].(map[string]interface{})
- if !ok {
- t.Fatalf("Expected first book to be an object")
- }
- resultPrice1, ok := resultBook1["price"].(float64)
- if !ok {
- t.Fatalf("Expected numeric price in first book")
- }
- if resultPrice1 != 89.9 {
- t.Errorf("Expected first book price to be 89.9, got %v", resultPrice1)
- }
-
- // Compare the second book's price
- resultBook2, ok := resultBooks[1].(map[string]interface{})
- if !ok {
- t.Fatalf("Expected second book to be an object")
- }
- resultPrice2, ok := resultBook2["price"].(float64)
- if !ok {
- t.Fatalf("Expected numeric price in second book")
- }
- if resultPrice2 != 11.9 {
- t.Errorf("Expected second book price to be 11.9, got %v", resultPrice2)
- }
-}
-
-// TestJSONProcessor_Process_StringValues tests processing string JSON values
-func TestJSONProcessor_Process_StringValues(t *testing.T) {
- content := `{
- "config": {
- "maxItems": "100",
- "itemTimeoutSecs": "30",
- "retryCount": "5"
- }
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.config", "for k,vi in pairs(v) do v[k]=vi*2 end")
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- // Debug info
- t.Logf("Result: %s", result)
- t.Logf("Match count: %d, Mod count: %d", matchCount, modCount)
-
- if matchCount != 1 {
- t.Errorf("Expected 1 matches, got %d", matchCount)
- }
-
- if modCount != 1 {
- t.Errorf("Expected 1 modifications, got %d", modCount)
- }
-
- // Check that all expected values are in the result
- if !strings.Contains(result, `"maxItems": 200`) {
- t.Errorf("Result missing expected value: maxItems=200")
- }
-
- if !strings.Contains(result, `"itemTimeoutSecs": 60`) {
- t.Errorf("Result missing expected value: itemTimeoutSecs=60")
- }
-
- if !strings.Contains(result, `"retryCount": 10`) {
- t.Errorf("Result missing expected value: retryCount=10")
- }
-}
-
-// TestJSONProcessor_FindNodes tests the JSONPath implementation
-func TestJSONProcessor_FindNodes(t *testing.T) {
- // Test cases for JSONPath implementation
- testCases := []struct {
- name string
- jsonData string
- path string
- expectLen int
- expectErr bool
- }{
- {
- name: "Root element",
- jsonData: `{"name": "root", "value": 100}`,
- path: "$",
- expectLen: 1,
- expectErr: false,
- },
- {
- name: "Direct property",
- jsonData: `{"name": "test", "value": 100}`,
- path: "$.value",
- expectLen: 1,
- expectErr: false,
- },
- {
- name: "Array access",
- jsonData: `{"items": [10, 20, 30]}`,
- path: "$.items[1]",
- expectLen: 1,
- expectErr: false,
- },
- {
- name: "All array elements",
- jsonData: `{"items": [10, 20, 30]}`,
- path: "$.items[*]",
- expectLen: 3,
- expectErr: false,
- },
- {
- name: "Nested property",
- jsonData: `{"user": {"name": "John", "age": 30}}`,
- path: "$.user.age",
- expectLen: 1,
- expectErr: false,
- },
- {
- name: "Array of objects",
- jsonData: `{"users": [{"name": "John"}, {"name": "Jane"}]}`,
- path: "$.users[*].name",
- expectLen: 2,
- expectErr: false,
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- // Parse the JSON data
- var jsonDoc interface{}
- if err := json.Unmarshal([]byte(tc.jsonData), &jsonDoc); err != nil {
- t.Fatalf("Failed to parse test JSON: %v", err)
- }
-
- // Find nodes with the given path
- nodes, err := findMatchingPaths(jsonDoc, tc.path)
-
- // Check error expectation
- if tc.expectErr && err == nil {
- t.Errorf("Expected error but got none")
- }
- if !tc.expectErr && err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
-
- // Skip further checks if we expected an error
- if tc.expectErr {
- return
- }
-
- // Check the number of nodes found
- if len(nodes) != tc.expectLen {
- t.Errorf("Expected %d nodes, got %d", tc.expectLen, len(nodes))
- }
- })
- }
-}
-
-// TestJSONProcessor_NestedModifications tests modifying nested JSON objects
-func TestJSONProcessor_NestedModifications(t *testing.T) {
- content := `{
- "store": {
- "book": [
- {
- "category": "reference",
- "title": "Learn Go in 24 Hours",
- "price": 10.99
- },
- {
- "category": "fiction",
- "title": "The Go Developer",
- "price": 8.99
- }
- ]
- }
- }`
-
- expected := `{
- "store": {
- "book": [
- {
- "category": "reference",
- "price": 13.188,
- "title": "Learn Go in 24 Hours"
- },
- {
- "category": "fiction",
- "price": 10.788,
- "title": "The Go Developer"
- }
- ]
- }
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.store.book[*].price", "v=v*1.2")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 2 {
- t.Errorf("Expected 2 matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_StringManipulation tests string manipulation
-func TestJSONProcessor_StringManipulation(t *testing.T) {
- content := `{
- "users": [
- {
- "name": "john",
- "role": "admin"
- },
- {
- "name": "alice",
- "role": "user"
- }
- ]
- }`
-
- expected := `{
- "users": [
- {
- "name": "JOHN",
- "role": "admin"
- },
- {
- "name": "ALICE",
- "role": "user"
- }
- ]
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.users[*].name", "v = string.upper(v)")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 2 {
- t.Errorf("Expected 2 matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_ComplexScript tests using more complex Lua scripts
-func TestJSONProcessor_ComplexScript(t *testing.T) {
- content := `{
- "products": [
- {
- "name": "Basic Widget",
- "price": 9.99,
- "discount": 0.1
- },
- {
- "name": "Premium Widget",
- "price": 19.99,
- "discount": 0.05
- }
- ]
- }`
-
- expected := `{
- "products": [
- {
- "discount": 0.1,
- "name": "Basic Widget",
- "price": 8.991
- },
- {
- "discount": 0.05,
- "name": "Premium Widget",
- "price": 18.9905
- }
- ]
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.products[*]", "v.price = round(v.price * (1 - v.discount), 4)")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 2 {
- t.Errorf("Expected 2 matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_SpecificItemUpdate tests updating a specific item in an array
-func TestJSONProcessor_SpecificItemUpdate(t *testing.T) {
- content := `{
- "items": [
- {"id": 1, "name": "Item 1", "stock": 10},
- {"id": 2, "name": "Item 2", "stock": 5},
- {"id": 3, "name": "Item 3", "stock": 0}
- ]
- }`
-
- expected := `
- {
- "items": [
- {
- "id": 1,
- "name": "Item 1",
- "stock": 10
- },
- {
- "id": 2,
- "name": "Item 2",
- "stock": 15
- },
- {
- "id": 3,
- "name": "Item 3",
- "stock": 0
- }
- ]
- } `
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.items[1].stock", "v=v+10")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 1 {
- t.Errorf("Expected 1 match, got %d", matchCount)
- }
-
- if modCount != 1 {
- t.Errorf("Expected 1 modification, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_RootElementUpdate tests updating the root element
-func TestJSONProcessor_RootElementUpdate(t *testing.T) {
- content := `{"value": 100}`
- expected := `{
- "value": 200
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.value", "v=v*2")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 1 {
- t.Errorf("Expected 1 match, got %d", matchCount)
- }
-
- if modCount != 1 {
- t.Errorf("Expected 1 modification, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_AddNewField tests adding a new field to a JSON object
-func TestJSONProcessor_AddNewField(t *testing.T) {
- content := `{
- "user": {
- "name": "John",
- "age": 30
- }
- }`
-
- expected := `{
- "user": {
- "age": 30,
- "email": "john@example.com",
- "name": "John"
- }
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.user", "v.email = 'john@example.com'")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 1 {
- t.Errorf("Expected 1 match, got %d", matchCount)
- }
-
- if modCount != 1 {
- t.Errorf("Expected 1 modification, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_RemoveField tests removing a field from a JSON object
-func TestJSONProcessor_RemoveField(t *testing.T) {
- content := `{
- "user": {
- "name": "John",
- "age": 30,
- "email": "john@example.com"
- }
- }`
-
- expected := `{
- "user": {
- "email": "john@example.com",
- "name": "John"
- }
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.user", "v.age = nil")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 1 {
- t.Errorf("Expected 1 match, got %d", matchCount)
- }
-
- if modCount != 1 {
- t.Errorf("Expected 1 modification, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_ArrayManipulation tests adding and manipulating array elements
-func TestJSONProcessor_ArrayManipulation(t *testing.T) {
- content := `{
- "tags": ["go", "json", "lua"]
- }`
-
- expected := ` {
- "tags": [
- "GO",
- "JSON",
- "LUA",
- "testing"
- ]
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.tags", `
- -- Convert existing tags to uppercase
- for i=1, #v do
- v[i] = string.upper(v[i])
- end
- -- Add a new tag
- v[#v+1] = "testing"
- `)
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 1 {
- t.Errorf("Expected 1 match, got %d", matchCount)
- }
-
- if modCount != 1 {
- t.Errorf("Expected 1 modification, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_ConditionalModification tests conditionally modifying values
-func TestJSONProcessor_ConditionalModification(t *testing.T) {
- content := `{
- "products": [
- {
- "name": "Product A",
- "price": 10.99,
- "inStock": true
- },
- {
- "name": "Product B",
- "price": 5.99,
- "inStock": false
- },
- {
- "name": "Product C",
- "price": 15.99,
- "inStock": true
- }
- ]
- }`
-
- expected := `{
- "products": [
- {
- "inStock": true,
- "name": "Product A",
- "price": 9.891
- },
- {
- "inStock": false,
- "name": "Product B",
- "price": 5.99
- },
- {
- "inStock": true,
- "name": "Product C",
- "price": 14.391
- }
- ]
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.products[*]", `
- if not v.inStock then
- return false
- end
- v.price = v.price * 0.9
- `)
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 3 {
- t.Errorf("Expected 3 matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_DeepNesting tests manipulating deeply nested JSON structures
-func TestJSONProcessor_DeepNesting(t *testing.T) {
- content := `{
- "company": {
- "departments": {
- "engineering": {
- "teams": {
- "frontend": {
- "members": 12,
- "projects": 5
- },
- "backend": {
- "members": 8,
- "projects": 3
- }
- }
- }
- }
- }
- }`
-
- expected := `{
- "company": {
- "departments": {
- "engineering": {
- "teams": {
- "backend": {
- "members": 8,
- "projects": 3,
- "status": "active"
- },
- "frontend": {
- "members": 12,
- "projects": 5,
- "status": "active"
- }
- }
- }
- }
- }
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.company.departments.engineering.teams.*", `
- v.status = "active"
- `)
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 2 {
- t.Errorf("Expected 2 matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_ComplexTransformation tests a complex transformation involving
-// multiple fields and calculations
-func TestJSONProcessor_ComplexTransformation(t *testing.T) {
- content := `{
- "order": {
- "items": [
- {
- "product": "Widget A",
- "quantity": 5,
- "price": 10.0
- },
- {
- "product": "Widget B",
- "quantity": 3,
- "price": 15.0
- }
- ],
- "customer": {
- "name": "John Smith",
- "tier": "gold"
- }
- }
- }`
-
- expected := `{
- "order": {
- "customer": {
- "name": "John Smith",
- "tier": "gold"
- },
- "items": [
- {
- "discounted_total": 45,
- "price": 10,
- "product": "Widget A",
- "quantity": 5,
- "total": 50
- },
- {
- "discounted_total": 40.5,
- "price": 15,
- "product": "Widget B",
- "quantity": 3,
- "total": 45
- }
- ],
- "summary": {
- "discount": 9.5,
- "subtotal": 95,
- "total": 85.5,
- "total_items": 8
- }
- }
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.order", `
- -- Calculate item totals and apply discounts
- local discount_rate = 0.1 -- 10% discount for gold tier
- local subtotal = 0
- local total_items = 0
-
- for i, item in ipairs(v.items) do
- -- Calculate item total
- item.total = item.quantity * item.price
-
- -- Apply discount
- item.discounted_total = item.total * (1 - discount_rate)
-
- -- Add to running totals
- subtotal = subtotal + item.total
- total_items = total_items + item.quantity
- end
-
- -- Add order summary
- v.summary = {
- total_items = total_items,
- subtotal = subtotal,
- discount = subtotal * discount_rate,
- total = subtotal * (1 - discount_rate)
- }
- `)
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 1 {
- t.Errorf("Expected 1 match, got %d", matchCount)
- }
-
- if modCount != 1 {
- t.Errorf("Expected 1 modification, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_HandlingNullValues tests handling of null values in JSON
-func TestJSONProcessor_HandlingNullValues(t *testing.T) {
- content := `{
- "data": {
- "value1": null,
- "value2": 42,
- "value3": "hello"
- }
- }`
-
- expected := `{
- "data": {
- "value1": 0,
- "value2": 42,
- "value3": "hello"
- }
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.data.value1", `
- v = 0
- `)
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 1 {
- t.Errorf("Expected 1 match, got %d", matchCount)
- }
-
- if modCount != 1 {
- t.Errorf("Expected 1 modification, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_RestructuringData tests completely restructuring JSON data
-func TestJSONProcessor_RestructuringData(t *testing.T) {
- content := `{
- "people": [
- {
- "id": 1,
- "name": "Alice",
- "attributes": {
- "age": 25,
- "role": "developer"
- }
- },
- {
- "id": 2,
- "name": "Bob",
- "attributes": {
- "age": 30,
- "role": "manager"
- }
- }
- ]
- }`
-
- expected := `{
- "people": {
- "developers": [
- {
- "age": 25,
- "id": 1,
- "name": "Alice"
- }
- ],
- "managers": [
- {
- "age": 30,
- "id": 2,
- "name": "Bob"
- }
- ]
- }
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$", `
- -- Restructure the data
- local old_people = v.people
- local new_structure = {
- developers = {},
- managers = {}
- }
-
- for _, person in ipairs(old_people) do
- local role = person.attributes.role
- local new_person = {
- id = person.id,
- name = person.name,
- age = person.attributes.age
- }
-
- if role == "developer" then
- table.insert(new_structure.developers, new_person)
- elseif role == "manager" then
- table.insert(new_structure.managers, new_person)
- end
- end
-
- v.people = new_structure
- `)
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 1 {
- t.Errorf("Expected 1 match, got %d", matchCount)
- }
-
- if modCount != 1 {
- t.Errorf("Expected 1 modification, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_FilteringArrayElements tests filtering elements from an array
-func TestJSONProcessor_FilteringArrayElements(t *testing.T) {
- content := `{
- "numbers": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
- }`
-
- expected := `{
- "numbers": [
- 2,
- 4,
- 6,
- 8,
- 10
- ]
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.numbers", `
- -- Filter to keep only even numbers
- local filtered = {}
- for _, num in ipairs(v) do
- if num % 2 == 0 then
- table.insert(filtered, num)
- end
- end
- v = filtered
- `)
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 1 {
- t.Errorf("Expected 1 match, got %d", matchCount)
- }
-
- if modCount != 1 {
- t.Errorf("Expected 1 modification, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_RootNodeModification tests modifying the root node directly
-func TestJSONProcessor_RootNodeModification(t *testing.T) {
- content := `{
- "name": "original",
- "value": 100
- }`
-
- expected := `{
- "description": "This is a completely modified root",
- "name": "modified",
- "values": [
- 1,
- 2,
- 3
- ]
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$", `
- -- Completely replace the root node
- v = {
- name = "modified",
- description = "This is a completely modified root",
- values = {1, 2, 3}
- }
- `)
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 1 {
- t.Errorf("Expected 1 match, got %d", matchCount)
- }
-
- if modCount != 1 {
- t.Errorf("Expected 1 modification, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_DateManipulation tests manipulating date strings in a JSON document
-func TestJSONProcessor_DateManipulation(t *testing.T) {
- content := `{
- "events": [
- {
- "name": "Conference",
- "date": "2023-06-15"
- },
- {
- "name": "Workshop",
- "date": "2023-06-20"
- }
- ]
- }`
-
- expected := `{
- "events": [
- {
- "name": "Conference",
- "date": "2023-07-15"
- },
- {
- "name": "Workshop",
- "date": "2023-07-20"
- }
- ]
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.events[*].date", `
- local year, month, day = string.match(v, "(%d%d%d%d)-(%d%d)-(%d%d)")
- -- Postpone events by 1 month
- month = tonumber(month) + 1
- if month > 12 then
- month = 1
- year = tonumber(year) + 1
- end
- v = string.format("%04d-%02d-%s", tonumber(year), month, day)
- `)
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 2 {
- t.Errorf("Expected 2 matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 modifications, got %d", modCount)
- }
-
- // Parse results as JSON objects for deep comparison rather than string comparison
- var resultObj map[string]interface{}
- var expectedObj map[string]interface{}
-
- if err := json.Unmarshal([]byte(result), &resultObj); err != nil {
- t.Fatalf("Failed to parse result JSON: %v", err)
- }
-
- if err := json.Unmarshal([]byte(expected), &expectedObj); err != nil {
- t.Fatalf("Failed to parse expected JSON: %v", err)
- }
-
- // Get the events arrays
- resultEvents, ok := resultObj["events"].([]interface{})
- if !ok || len(resultEvents) != 2 {
- t.Fatalf("Expected events array with 2 items in result")
- }
-
- expectedEvents, ok := expectedObj["events"].([]interface{})
- if !ok || len(expectedEvents) != 2 {
- t.Fatalf("Expected events array with 2 items in expected")
- }
-
- // Check each event's date value
- for i := 0; i < 2; i++ {
- resultEvent, ok := resultEvents[i].(map[string]interface{})
- if !ok {
- t.Fatalf("Expected event %d to be an object", i)
- }
-
- expectedEvent, ok := expectedEvents[i].(map[string]interface{})
- if !ok {
- t.Fatalf("Expected expected event %d to be an object", i)
- }
-
- resultDate, ok := resultEvent["date"].(string)
- if !ok {
- t.Fatalf("Expected date in result event %d to be a string", i)
- }
-
- expectedDate, ok := expectedEvent["date"].(string)
- if !ok {
- t.Fatalf("Expected date in expected event %d to be a string", i)
- }
-
- if resultDate != expectedDate {
- t.Errorf("Event %d: expected date %s, got %s", i, expectedDate, resultDate)
- }
- }
-}
-
-// TestJSONProcessor_MathFunctions tests using math functions in JSON processing
-func TestJSONProcessor_MathFunctions(t *testing.T) {
- content := `{
- "measurements": [
- 3.14159,
- 2.71828,
- 1.41421
- ]
- }`
-
- expected := `{
- "measurements": [
- 3,
- 3,
- 1
- ]
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.measurements[*]", "v = round(v)")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 3 {
- t.Errorf("Expected 3 matches, got %d", matchCount)
- }
-
- if modCount != 3 {
- t.Errorf("Expected 3 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_Error_InvalidJSON tests error handling for invalid JSON
-func TestJSONProcessor_Error_InvalidJSON(t *testing.T) {
- content := `{
- "unclosed": "value"
- `
-
- p := &JSONProcessor{}
- _, _, _, err := p.ProcessContent(content, "$.unclosed", "v='modified'")
-
- if err == nil {
- t.Errorf("Expected an error for invalid JSON, but got none")
- }
-}
-
-// TestJSONProcessor_Error_InvalidJSONPath tests error handling for invalid JSONPath
-func TestJSONProcessor_Error_InvalidJSONPath(t *testing.T) {
- content := `{
- "element": "value"
- }`
-
- p := &JSONProcessor{}
- _, _, _, err := p.ProcessContent(content, "[invalid path]", "v='modified'")
-
- if err == nil {
- t.Errorf("Expected an error for invalid JSONPath, but got none")
- }
-}
-
-// TestJSONProcessor_Error_InvalidLua tests error handling for invalid Lua
-func TestJSONProcessor_Error_InvalidLua(t *testing.T) {
- content := `{
- "element": 123
- }`
-
- p := &JSONProcessor{}
- _, _, _, err := p.ProcessContent(content, "$.element", "v = invalid_function()")
-
- if err == nil {
- t.Errorf("Expected an error for invalid Lua, but got none")
- }
-}
-
-// TestJSONProcessor_Process_SpecialCharacters tests handling of special characters in JSON
-func TestJSONProcessor_Process_SpecialCharacters(t *testing.T) {
- content := `{
- "data": [
- "This & that",
- "a < b",
- "c > d",
- "Quote: \"Hello\""
- ]
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.data[*]", "v = string.upper(v)")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 4 {
- t.Errorf("Expected 4 matches, got %d", matchCount)
- }
-
- if modCount != 4 {
- t.Errorf("Expected 4 modifications, got %d", modCount)
- }
-
- // Parse the result to verify the content
- var resultObj map[string]interface{}
- if err := json.Unmarshal([]byte(result), &resultObj); err != nil {
- t.Fatalf("Failed to parse result JSON: %v", err)
- }
-
- data, ok := resultObj["data"].([]interface{})
- if !ok || len(data) != 4 {
- t.Fatalf("Expected data array with 4 items")
- }
-
- expectedValues := []string{
- "THIS & THAT",
- "A < B",
- "C > D",
- "QUOTE: \"HELLO\"",
- }
-
- for i, val := range data {
- strVal, ok := val.(string)
- if !ok {
- t.Errorf("Expected item %d to be a string", i)
- continue
- }
-
- if strVal != expectedValues[i] {
- t.Errorf("Item %d: expected %q, got %q", i, expectedValues[i], strVal)
- }
- }
-}
-
-// TestJSONProcessor_AggregateCalculation tests calculating aggregated values from multiple fields
-func TestJSONProcessor_AggregateCalculation(t *testing.T) {
- content := `{
- "items": [
- {
- "name": "Apple",
- "price": 1.99,
- "quantity": 10
- },
- {
- "name": "Carrot",
- "price": 0.99,
- "quantity": 5
- }
- ]
- }`
-
- expected := `{
- "items": [
- {
- "name": "Apple",
- "price": 1.99,
- "quantity": 10,
- "total": 19.9
- },
- {
- "name": "Carrot",
- "price": 0.99,
- "quantity": 5,
- "total": 4.95
- }
- ]
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.items[*]", `
- -- Calculate total from price and quantity
- local price = v.price
- local quantity = v.quantity
-
- -- Add new total field
- v.total = price * quantity
- `)
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 2 {
- t.Errorf("Expected 2 matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_DataAnonymization tests anonymizing sensitive data
-func TestJSONProcessor_DataAnonymization(t *testing.T) {
- content := `{
- "contacts": [
- {
- "name": "John Doe",
- "email": "john.doe@example.com",
- "phone": "123-456-7890"
- },
- {
- "name": "Jane Smith",
- "email": "jane.smith@example.com",
- "phone": "456-789-0123"
- }
- ]
- }`
-
- p := &JSONProcessor{}
-
- // First pass: anonymize email addresses
- result, modCount1, matchCount1, err := p.ProcessContent(content, "$.contacts[*].email", `
- -- Anonymize email
- v = string.gsub(v, "@.+", "@anon.com")
- local username = string.match(v, "(.+)@")
- v = string.gsub(username, "%.", "") .. "@anon.com"
- `)
-
- if err != nil {
- t.Fatalf("Error processing email content: %v", err)
- }
-
- // Second pass: anonymize phone numbers
- result, modCount2, matchCount2, err := p.ProcessContent(result, "$.contacts[*].phone", `
- -- Mask phone numbers
- v = string.gsub(v, "%d%d%d%-%d%d%d%-%d%d%d%d", function(match)
- return string.sub(match, 1, 3) .. "-XXX-XXXX"
- end)
- `)
-
- if err != nil {
- t.Fatalf("Error processing phone content: %v", err)
- }
-
- // Total counts from both operations
- matchCount := matchCount1 + matchCount2
- modCount := modCount1 + modCount2
-
- if matchCount != 4 {
- t.Errorf("Expected 4 total matches, got %d", matchCount)
- }
-
- if modCount != 4 {
- t.Errorf("Expected 4 total modifications, got %d", modCount)
- }
-
- // Parse the resulting JSON for validating content
- var resultObj map[string]interface{}
- if err := json.Unmarshal([]byte(result), &resultObj); err != nil {
- t.Fatalf("Failed to parse result JSON: %v", err)
- }
-
- contacts, ok := resultObj["contacts"].([]interface{})
- if !ok || len(contacts) != 2 {
- t.Fatalf("Expected contacts array with 2 items")
- }
-
- // Validate first contact
- contact1, ok := contacts[0].(map[string]interface{})
- if !ok {
- t.Fatalf("Expected first contact to be an object")
- }
-
- if email1, ok := contact1["email"].(string); !ok || email1 != "johndoe@anon.com" {
- t.Errorf("First contact email should be johndoe@anon.com, got %v", contact1["email"])
- }
-
- if phone1, ok := contact1["phone"].(string); !ok || phone1 != "123-XXX-XXXX" {
- t.Errorf("First contact phone should be 123-XXX-XXXX, got %v", contact1["phone"])
- }
-
- // Validate second contact
- contact2, ok := contacts[1].(map[string]interface{})
- if !ok {
- t.Fatalf("Expected second contact to be an object")
- }
-
- if email2, ok := contact2["email"].(string); !ok || email2 != "janesmith@anon.com" {
- t.Errorf("Second contact email should be janesmith@anon.com, got %v", contact2["email"])
- }
-
- if phone2, ok := contact2["phone"].(string); !ok || phone2 != "456-XXX-XXXX" {
- t.Errorf("Second contact phone should be 456-XXX-XXXX, got %v", contact2["phone"])
- }
-}
-
-// TestJSONProcessor_ChainedOperations tests sequential operations on the same data
-func TestJSONProcessor_ChainedOperations(t *testing.T) {
- content := `{
- "product": {
- "name": "Widget",
- "price": 100
- }
- }`
-
- expected := `{
- "product": {
- "name": "Widget",
- "price": 103.5
- }
- }`
-
- p := &JSONProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "$.product.price", `
- -- When v is a numeric value, we can perform math operations directly
- local price = v
- -- Add 15% tax
- price = price * 1.15
- -- Apply 10% discount
- price = price * 0.9
- -- Round to 2 decimal places
- price = math.floor(price * 100 + 0.5) / 100
- v = price
- `)
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 1 {
- t.Errorf("Expected 1 match, got %d", matchCount)
- }
-
- if modCount != 1 {
- t.Errorf("Expected 1 modification, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeWhitespace(result)
- normalizedExpected := normalizeWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// TestJSONProcessor_ComplexDataTransformation tests advanced JSON transformation
-func TestJSONProcessor_ComplexDataTransformation(t *testing.T) {
- content := `{
- "store": {
- "name": "My Store",
- "inventory": [
- {
- "id": 1,
- "name": "Laptop",
- "category": "electronics",
- "price": 999.99,
- "stock": 15,
- "features": ["16GB RAM", "512GB SSD", "15-inch display"]
- },
- {
- "id": 2,
- "name": "Smartphone",
- "category": "electronics",
- "price": 499.99,
- "stock": 25,
- "features": ["6GB RAM", "128GB storage", "5G"]
- },
- {
- "id": 3,
- "name": "T-Shirt",
- "category": "clothing",
- "price": 19.99,
- "stock": 100,
- "features": ["100% cotton", "M, L, XL sizes", "Red color"]
- },
- {
- "id": 4,
- "name": "Headphones",
- "category": "electronics",
- "price": 149.99,
- "stock": 8,
- "features": ["Noise cancelling", "Bluetooth", "20hr battery"]
- }
- ]
- }
- }`
-
- expected := `{
- "store": {
- "name": "My Store",
- "inventory_summary": {
- "electronics": {
- "count": 3,
- "total_value": 30924.77,
- "low_stock_items": [
- {
- "id": 4,
- "name": "Headphones",
- "stock": 8
- }
- ]
- },
- "clothing": {
- "count": 1,
- "total_value": 1999.00,
- "low_stock_items": []
- }
- },
- "transformed_items": [
- {
- "name": "Laptop",
- "price_with_tax": 1199.99,
- "in_stock": true
- },
- {
- "name": "Smartphone",
- "price_with_tax": 599.99,
- "in_stock": true
- },
- {
- "name": "T-Shirt",
- "price_with_tax": 23.99,
- "in_stock": true
- },
- {
- "name": "Headphones",
- "price_with_tax": 179.99,
- "in_stock": true
- }
- ]
- }
- }`
-
- p := &JSONProcessor{}
-
- // First, create a complex transformation that:
- // 1. Summarizes inventory by category (count, total value, low stock alerts)
- // 2. Creates a simplified view of items with tax added
- result, modCount, matchCount, err := p.ProcessContent(content, "$", `
- -- Get store data
- local store = v.store
- local inventory = store.inventory
-
- -- Remove the original inventory array, we'll replace it with our summaries
- store.inventory = nil
-
- -- Create summary by category
- local summary = {}
- local transformed = {}
-
- -- Group and analyze items by category
- for _, item in ipairs(inventory) do
- -- Prepare category data if not exists
- local category = item.category
- if not summary[category] then
- summary[category] = {
- count = 0,
- total_value = 0,
- low_stock_items = {}
- }
- end
-
- -- Update category counts
- summary[category].count = summary[category].count + 1
-
- -- Calculate total value (price * stock) and add to category
- local item_value = item.price * item.stock
- summary[category].total_value = summary[category].total_value + item_value
-
- -- Check for low stock (less than 10)
- if item.stock < 10 then
- table.insert(summary[category].low_stock_items, {
- id = item.id,
- name = item.name,
- stock = item.stock
- })
- end
-
- -- Create transformed view of the item with added tax
- table.insert(transformed, {
- name = item.name,
- price_with_tax = math.floor((item.price * 1.2) * 100 + 0.5) / 100, -- 20% tax, rounded to 2 decimals
- in_stock = item.stock > 0
- })
- end
-
- -- Format the total_value with two decimal places
- for category, data in pairs(summary) do
- data.total_value = math.floor(data.total_value * 100 + 0.5) / 100
- end
-
- -- Add our new data structures to the store
- store.inventory_summary = summary
- store.transformed_items = transformed
- `)
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 1 {
- t.Errorf("Expected 1 match, got %d", matchCount)
- }
-
- if modCount != 1 {
- t.Errorf("Expected 1 modification, got %d", modCount)
- }
-
- // Parse both results as JSON objects for deep comparison
- var resultObj map[string]interface{}
- var expectedObj map[string]interface{}
-
- if err := json.Unmarshal([]byte(result), &resultObj); err != nil {
- t.Fatalf("Failed to parse result JSON: %v", err)
- }
-
- if err := json.Unmarshal([]byte(expected), &expectedObj); err != nil {
- t.Fatalf("Failed to parse expected JSON: %v", err)
- }
-
- // Verify the structure and key counts
- resultStore, ok := resultObj["store"].(map[string]interface{})
- if !ok {
- t.Fatalf("Expected 'store' object in result")
- }
-
- // Check that inventory is gone and replaced with our new structures
- if resultStore["inventory"] != nil {
- t.Errorf("Expected 'inventory' to be removed")
- }
-
- if resultStore["inventory_summary"] == nil {
- t.Errorf("Expected 'inventory_summary' to be added")
- }
-
- if resultStore["transformed_items"] == nil {
- t.Errorf("Expected 'transformed_items' to be added")
- }
-
- // Check that the transformed_items array has the correct length
- transformedItems, ok := resultStore["transformed_items"].([]interface{})
- if !ok {
- t.Fatalf("Expected 'transformed_items' to be an array")
- }
-
- if len(transformedItems) != 4 {
- t.Errorf("Expected 'transformed_items' to have 4 items, got %d", len(transformedItems))
- }
-
- // Check that the summary has entries for both electronics and clothing
- summary, ok := resultStore["inventory_summary"].(map[string]interface{})
- if !ok {
- t.Fatalf("Expected 'inventory_summary' to be an object")
- }
-
- if summary["electronics"] == nil {
- t.Errorf("Expected 'electronics' category in summary")
- }
-
- if summary["clothing"] == nil {
- t.Errorf("Expected 'clothing' category in summary")
- }
-}
diff --git a/processor/xml.go b/processor/xml.go
deleted file mode 100644
index 4943312..0000000
--- a/processor/xml.go
+++ /dev/null
@@ -1,434 +0,0 @@
-package processor
-
-import (
- "fmt"
- "modify/logger"
- "modify/processor/xpath"
- "strings"
-
- "github.com/antchfx/xmlquery"
- lua "github.com/yuin/gopher-lua"
-)
-
-// XMLProcessor implements the Processor interface for XML documents
-type XMLProcessor struct{}
-
-// ProcessContent implements the Processor interface for XMLProcessor
-func (p *XMLProcessor) ProcessContent(content string, path string, luaExpr string) (string, int, int, error) {
- logger.Debug("Processing XML content with XPath: %s", path)
-
- // Parse XML document
- // We can't really use encoding/xml here because it requires a pre defined struct
- // And we HAVE TO parse dynamic unknown XML
- logger.Trace("Parsing XML document")
- doc, err := xmlquery.Parse(strings.NewReader(content))
- if err != nil {
- logger.Error("Failed to parse XML: %v", err)
- return content, 0, 0, fmt.Errorf("error parsing XML: %v", err)
- }
-
- // Find nodes matching the XPath pattern
- logger.Debug("Executing XPath query: %s", path)
- nodes, err := xpath.Get(doc, path)
- if err != nil {
- logger.Error("Failed to execute XPath: %v", err)
- return content, 0, 0, fmt.Errorf("error executing XPath: %v", err)
- }
-
- matchCount := len(nodes)
- logger.Debug("Found %d nodes matching XPath", matchCount)
- if matchCount == 0 {
- logger.Warning("No nodes matched the XPath pattern: %s", path)
- return content, 0, 0, nil
- }
-
- // Apply modifications to each node
- modCount := 0
- for i, node := range nodes {
- logger.Trace("Processing node #%d: %s", i+1, node.Data)
-
- L, err := NewLuaState()
- if err != nil {
- logger.Error("Failed to create Lua state: %v", err)
- return content, 0, 0, fmt.Errorf("error creating Lua state: %v", err)
- }
- defer L.Close()
-
- logger.Trace("Converting XML node to Lua")
- err = p.ToLua(L, node)
- if err != nil {
- logger.Error("Failed to convert XML node to Lua: %v", err)
- return content, modCount, matchCount, fmt.Errorf("error converting to Lua: %v", err)
- }
-
- luaScript := BuildLuaScript(luaExpr)
- logger.Trace("Executing Lua script: %s", luaScript)
- err = L.DoString(luaScript)
- if err != nil {
- logger.Error("Failed to execute Lua script: %v", err)
- return content, modCount, matchCount, fmt.Errorf("error executing Lua: %v", err)
- }
-
- result, err := p.FromLua(L)
- if err != nil {
- logger.Error("Failed to get result from Lua: %v", err)
- return content, modCount, matchCount, fmt.Errorf("error getting result from Lua: %v", err)
- }
- logger.Trace("Lua returned result: %#v", result)
-
- modified := false
- modified = L.GetGlobal("modified").String() == "true"
- if !modified {
- logger.Debug("No changes made to node at path: %s", node.Data)
- continue
- }
-
- // Apply modification based on the result
- if updatedValue, ok := result.(string); ok {
- // If the result is a simple string, update the node value directly
- logger.Debug("Updating node with string value: %s", updatedValue)
- xpath.Set(doc, path, updatedValue)
- } else if nodeData, ok := result.(map[string]interface{}); ok {
- // If the result is a map, apply more complex updates
- logger.Debug("Updating node with complex data structure")
- updateNodeFromMap(node, nodeData)
- }
-
- modCount++
- logger.Debug("Successfully modified node #%d", i+1)
- }
-
- logger.Info("XML processing complete: %d modifications from %d matches", modCount, matchCount)
-
- // Serialize the modified XML document to string
- if doc.FirstChild != nil && doc.FirstChild.Type == xmlquery.DeclarationNode {
- // If we have an XML declaration, start with it
- declaration := doc.FirstChild.OutputXML(true)
- // Remove the firstChild (declaration) before serializing the rest of the document
- doc.FirstChild = doc.FirstChild.NextSibling
- return ConvertToNamedEntities(declaration + doc.OutputXML(true)), modCount, matchCount, nil
- }
-
- // Convert numeric entities to named entities for better readability
- return ConvertToNamedEntities(doc.OutputXML(true)), modCount, matchCount, nil
-}
-
-func (p *XMLProcessor) ToLua(L *lua.LState, data interface{}) error {
- table, err := p.ToLuaTable(L, data)
- if err != nil {
- return err
- }
- L.SetGlobal("v", table)
- return nil
-}
-
-// ToLua converts XML node values to Lua variables
-func (p *XMLProcessor) ToLuaTable(L *lua.LState, data interface{}) (lua.LValue, error) {
- // Check if data is an xmlquery.Node
- node, ok := data.(*xmlquery.Node)
- if !ok {
- return nil, fmt.Errorf("expected xmlquery.Node, got %T", data)
- }
-
- // Create a simple table with essential data
- table := L.NewTable()
-
- // For element nodes, just provide basic info
- L.SetField(table, "type", lua.LString(nodeTypeToString(node.Type)))
- L.SetField(table, "name", lua.LString(node.Data))
- L.SetField(table, "value", lua.LString(node.InnerText()))
-
- // Add children if any
- children := L.NewTable()
- for child := node.FirstChild; child != nil; child = child.NextSibling {
- childTable, err := p.ToLuaTable(L, child)
- if err == nil {
- children.Append(childTable)
- }
- }
- L.SetField(table, "children", children)
-
- attrs := L.NewTable()
- if len(node.Attr) > 0 {
- for _, attr := range node.Attr {
- L.SetField(attrs, attr.Name.Local, lua.LString(attr.Value))
- }
- }
- L.SetField(table, "attr", attrs)
-
- return table, nil
-}
-
-// FromLua gets modified values from Lua
-func (p *XMLProcessor) FromLua(L *lua.LState) (interface{}, error) {
- luaValue := L.GetGlobal("v")
-
- // Handle string values directly
- if luaValue.Type() == lua.LTString {
- return luaValue.String(), nil
- }
-
- // Handle tables (for attributes and more complex updates)
- if luaValue.Type() == lua.LTTable {
- return luaTableToMap(L, luaValue.(*lua.LTable)), nil
- }
-
- return luaValue.String(), nil
-}
-
-// Simple helper to convert a Lua table to a Go map
-func luaTableToMap(L *lua.LState, table *lua.LTable) map[string]interface{} {
- result := make(map[string]interface{})
-
- table.ForEach(func(k, v lua.LValue) {
- if k.Type() == lua.LTString {
- key := k.String()
-
- if v.Type() == lua.LTTable {
- result[key] = luaTableToMap(L, v.(*lua.LTable))
- } else {
- result[key] = v.String()
- }
- }
- })
-
- return result
-}
-
-// Simple helper to convert node type to string
-func nodeTypeToString(nodeType xmlquery.NodeType) string {
- switch nodeType {
- case xmlquery.ElementNode:
- return "element"
- case xmlquery.TextNode:
- return "text"
- case xmlquery.AttributeNode:
- return "attribute"
- default:
- return "other"
- }
-}
-
-// Helper function to update an XML node from a map
-func updateNodeFromMap(node *xmlquery.Node, data map[string]interface{}) {
- // Update node value if present
- if value, ok := data["value"]; ok {
- if strValue, ok := value.(string); ok {
- // For element nodes, replace text content
- if node.Type == xmlquery.ElementNode {
- // Find the first text child if it exists
- var textNode *xmlquery.Node
- for child := node.FirstChild; child != nil; child = child.NextSibling {
- if child.Type == xmlquery.TextNode {
- textNode = child
- break
- }
- }
-
- if textNode != nil {
- // Update existing text node
- textNode.Data = strValue
- } else {
- // Create new text node
- newText := &xmlquery.Node{
- Type: xmlquery.TextNode,
- Data: strValue,
- Parent: node,
- }
-
- // Insert at beginning of children
- if node.FirstChild != nil {
- newText.NextSibling = node.FirstChild
- node.FirstChild.PrevSibling = newText
- node.FirstChild = newText
- } else {
- node.FirstChild = newText
- node.LastChild = newText
- }
- }
- } else if node.Type == xmlquery.TextNode {
- // Directly update text node
- node.Data = strValue
- } else if node.Type == xmlquery.AttributeNode {
- // Update attribute value
- if node.Parent != nil {
- for i, attr := range node.Parent.Attr {
- if attr.Name.Local == node.Data {
- node.Parent.Attr[i].Value = strValue
- break
- }
- }
- }
- }
- }
- }
-
- // Update attributes if present
- if attrs, ok := data["attr"].(map[string]interface{}); ok && node.Type == xmlquery.ElementNode {
- for name, value := range attrs {
- if strValue, ok := value.(string); ok {
- // Look for existing attribute
- found := false
- for i, attr := range node.Attr {
- if attr.Name.Local == name {
- node.Attr[i].Value = strValue
- found = true
- break
- }
- }
-
- // Add new attribute if not found
- if !found {
- node.Attr = append(node.Attr, xmlquery.Attr{
- Name: struct {
- Space, Local string
- }{Local: name},
- Value: strValue,
- })
- }
- }
- }
- }
-}
-
-// Helper function to get a string representation of node type
-func nodeTypeName(nodeType xmlquery.NodeType) string {
- switch nodeType {
- case xmlquery.ElementNode:
- return "element"
- case xmlquery.TextNode:
- return "text"
- case xmlquery.AttributeNode:
- return "attribute"
- case xmlquery.CommentNode:
- return "comment"
- case xmlquery.DeclarationNode:
- return "declaration"
- default:
- return "unknown"
- }
-}
-
-// ConvertToNamedEntities replaces numeric XML entities with their named counterparts
-func ConvertToNamedEntities(xml string) string {
- // Basic XML entities
- replacements := map[string]string{
- // Basic XML entities
- """: """, // double quote
- "'": "'", // single quote
- "<": "<", // less than
- ">": ">", // greater than
- "&": "&", // ampersand
-
- // Common symbols
- " ": " ", // non-breaking space
- "©": "©", // copyright
- "®": "®", // registered trademark
- "€": "€", // euro
- "£": "£", // pound
- "¥": "¥", // yen
- "¢": "¢", // cent
- "§": "§", // section
- "™": "™", // trademark
- "♠": "♠", // spade
- "♣": "♣", // club
- "♥": "♥", // heart
- "♦": "♦", // diamond
-
- // Special characters
- "¡": "¡", // inverted exclamation
- "¿": "¿", // inverted question
- "«": "«", // left angle quotes
- "»": "»", // right angle quotes
- "·": "·", // middle dot
- "•": "•", // bullet
- "…": "…", // horizontal ellipsis
- "′": "′", // prime
- "″": "″", // double prime
- "‾": "‾", // overline
- "⁄": "⁄", // fraction slash
-
- // Math symbols
- "±": "±", // plus-minus
- "×": "×", // multiplication
- "÷": "÷", // division
- "∞": "∞", // infinity
- "≈": "≈", // almost equal
- "≠": "≠", // not equal
- "≤": "≤", // less than or equal
- "≥": "≥", // greater than or equal
- "∑": "∑", // summation
- "√": "√", // square root
- "∫": "∫", // integral
-
- // Accented characters
- "À": "À", // A grave
- "Á": "Á", // A acute
- "Â": "Â", // A circumflex
- "Ã": "Ã", // A tilde
- "Ä": "Ä", // A umlaut
- "Å": "Å", // A ring
- "Æ": "Æ", // AE ligature
- "Ç": "Ç", // C cedilla
- "È": "È", // E grave
- "É": "É", // E acute
- "Ê": "Ê", // E circumflex
- "Ë": "Ë", // E umlaut
- "Ì": "Ì", // I grave
- "Í": "Í", // I acute
- "Î": "Î", // I circumflex
- "Ï": "Ï", // I umlaut
- "Ð": "Ð", // Eth
- "Ñ": "Ñ", // N tilde
- "Ò": "Ò", // O grave
- "Ó": "Ó", // O acute
- "Ô": "Ô", // O circumflex
- "Õ": "Õ", // O tilde
- "Ö": "Ö", // O umlaut
- "Ø": "Ø", // O slash
- "Ù": "Ù", // U grave
- "Ú": "Ú", // U acute
- "Û": "Û", // U circumflex
- "Ü": "Ü", // U umlaut
- "Ý": "Ý", // Y acute
- "Þ": "Þ", // Thorn
- "ß": "ß", // Sharp s
- "à": "à", // a grave
- "á": "á", // a acute
- "â": "â", // a circumflex
- "ã": "ã", // a tilde
- "ä": "ä", // a umlaut
- "å": "å", // a ring
- "æ": "æ", // ae ligature
- "ç": "ç", // c cedilla
- "è": "è", // e grave
- "é": "é", // e acute
- "ê": "ê", // e circumflex
- "ë": "ë", // e umlaut
- "ì": "ì", // i grave
- "í": "í", // i acute
- "î": "î", // i circumflex
- "ï": "ï", // i umlaut
- "ð": "ð", // eth
- "ñ": "ñ", // n tilde
- "ò": "ò", // o grave
- "ó": "ó", // o acute
- "ô": "ô", // o circumflex
- "õ": "õ", // o tilde
- "ö": "ö", // o umlaut
- "ø": "ø", // o slash
- "ù": "ù", // u grave
- "ú": "ú", // u acute
- "û": "û", // u circumflex
- "ü": "ü", // u umlaut
- "ý": "ý", // y acute
- "þ": "þ", // thorn
- "ÿ": "ÿ", // y umlaut
- }
-
- result := xml
- for numeric, named := range replacements {
- result = strings.ReplaceAll(result, numeric, named)
- }
- return result
-}
diff --git a/processor/xml_test.go b/processor/xml_test.go
deleted file mode 100644
index cda1ce5..0000000
--- a/processor/xml_test.go
+++ /dev/null
@@ -1,1772 +0,0 @@
-package processor
-
-import (
- "strings"
- "testing"
-
- "regexp"
- )
-
-// Helper function to normalize whitespace for comparison
-func normalizeXMLWhitespace(s string) string {
- // Replace all whitespace sequences with a single space
- re := regexp.MustCompile(`\s+`)
- s = re.ReplaceAllString(strings.TrimSpace(s), " ")
-
- // Normalize XML entities for comparison
- s = ConvertToNamedEntities(s)
-
- return s
-}
-
-func TestXMLProcessor_Process_NodeValues(t *testing.T) {
- content := `
-
-
- Gambardella, Matthew
- XML Developer's Guide
- Computer
- 44.95
- 2000-10-01
- An in-depth look at creating applications with XML.
-
-
- Ralls, Kim
- Midnight Rain
- Fantasy
- 5.95
- 2000-12-16
- A former architect battles corporate zombies.
-
-`
-
- expected := `
-
-
- Gambardella, Matthew
- XML Developer's Guide
- Computer
- 89.9
- 2000-10-01
- An in-depth look at creating applications with XML.
-
-
- Ralls, Kim
- Midnight Rain
- Fantasy
- 11.9
- 2000-12-16
- A former architect battles corporate zombies.
-
-`
-
- p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//price", "v.value = v.value * 2")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 2 {
- t.Errorf("Expected 2 matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeXMLWhitespace(result)
- normalizedExpected := normalizeXMLWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-func TestXMLProcessor_Process_Attributes(t *testing.T) {
- content := `
-
- - Widget A
- - Widget B
-`
-
- expected := `
-
- - Widget A
- - Widget B
-`
-
- p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//item/@price", "v.value = v.value * 2")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 2 {
- t.Errorf("Expected 2 matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeXMLWhitespace(result)
- normalizedExpected := normalizeXMLWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-func TestXMLProcessor_Process_ElementText(t *testing.T) {
- content := `
-
- john
- mary
-`
-
- expected := `
-
- JOHN
- MARY
-`
-
- p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//n/text()", "v.value = string.upper(v.value)")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 2 {
- t.Errorf("Expected 2 matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeXMLWhitespace(result)
- normalizedExpected := normalizeXMLWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-func TestXMLProcessor_Process_ElementAddition(t *testing.T) {
- content := `
-
-
- 30
- 100
-
-`
-
- expected := `
-
-
- 60
- 200
-
-`
-
- p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//settings/*", "v.value = v.value * 2")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 2 {
- t.Errorf("Expected 2 matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeXMLWhitespace(result)
- normalizedExpected := normalizeXMLWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-func TestXMLProcessor_Process_ComplexXML(t *testing.T) {
- content := `
-
-
-
- Laptop
- 999.99
-
-
- Smartphone
- 499.99
-
-
-
-
- T-Shirt
- 19.99
-
-
-`
-
- expected := `
-
-
-
- Laptop
- 1199.988
-
-
- Smartphone
- 599.988
-
-
-
-
- T-Shirt
- 23.988
-
-
-`
-
- p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//price", "v.value = round(v.value * 1.2, 3)")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 3 {
- t.Errorf("Expected 3 matches, got %d", matchCount)
- }
-
- if modCount != 3 {
- t.Errorf("Expected 3 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeXMLWhitespace(result)
- normalizedExpected := normalizeXMLWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// New tests added below
-
-func TestXMLProcessor_ConditionalModification(t *testing.T) {
- content := `
-
-
-
-
-`
-
- expected := `
-
-
-
-
-`
-
- p := &XMLProcessor{}
- // Apply 20% discount but only for items with stock > 0
- luaExpr := `
- -- In the table-based approach, attributes are accessible directly
- if v.attr.stock and tonumber(v.attr.stock) > 0 then
- v.attr.price = tonumber(v.attr.price) * 0.8
- -- Format to 2 decimal places
- v.attr.price = string.format("%.2f", v.attr.price)
- else
- return false
- end
- `
- result, modCount, matchCount, err := p.ProcessContent(content, "//item", luaExpr)
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 3 {
- t.Errorf("Expected 3 matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeXMLWhitespace(result)
- normalizedExpected := normalizeXMLWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-func TestXMLProcessor_Process_SpecialCharacters(t *testing.T) {
- content := `
-
- This & that
- a < b
- c > d
- Quote: "Hello"
-`
-
- expected := `
-
- THIS & THAT
- A < B
- C > D
- QUOTE: "HELLO"
-`
-
- p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//entry", "v.value = string.upper(v.value)")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 4 {
- t.Errorf("Expected 4 matches, got %d", matchCount)
- }
-
- if modCount != 4 {
- t.Errorf("Expected 4 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeXMLWhitespace(result)
- normalizedExpected := normalizeXMLWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-func TestXMLProcessor_Process_ChainedOperations(t *testing.T) {
- content := `
-
-
- Widget
- 100
- 20
-
-`
-
- // Apply multiple operations to the price: add tax, apply discount, round
- luaExpr := `
- local price = v.value
- -- Add 15% tax
- price = price * 1.15
- -- Apply 10% discount
- price = price * 0.9
- -- Round to 2 decimal places
- price = round(price, 2)
- v.value = price
- `
-
- expected := `
-
-
- Widget
- 103.5
- 20
-
-`
-
- p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//price", luaExpr)
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 1 {
- t.Errorf("Expected 1 match, got %d", matchCount)
- }
-
- if modCount != 1 {
- t.Errorf("Expected 1 modification, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeXMLWhitespace(result)
- normalizedExpected := normalizeXMLWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-func TestXMLProcessor_Process_MathFunctions(t *testing.T) {
- content := `
-
- 3.14159
- 2.71828
- 1.41421
-`
-
- expected := `
-
- 3
- 3
- 1
-`
-
- p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//measurement", "v.value = round(v.value)")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 3 {
- t.Errorf("Expected 3 matches, got %d", matchCount)
- }
-
- if modCount != 3 {
- t.Errorf("Expected 3 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeXMLWhitespace(result)
- normalizedExpected := normalizeXMLWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-func TestXMLProcessor_Process_StringOperations(t *testing.T) {
- content := `
-
-
- John Doe
- john.doe@example.com
- 123-456-7890
-
-`
-
- expected := `
-
-
- John Doe
- johndoe@anon.com
- 123-XXX-XXXX
-
-`
-
- // Test email anonymization
- p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//email", `
- -- With the table approach, v contains the text content directly
- v.value = string.gsub(v.value, "@.+", "@anon.com")
- local username = string.match(v.value, "(.+)@")
- v.value = string.gsub(username, "%.", "") .. "@anon.com"
- `)
-
- if err != nil {
- t.Fatalf("Error processing email content: %v", err)
- }
-
- // Test phone number masking
- result, modCount2, matchCount2, err := p.ProcessContent(result, "//phone", `
- v.value = string.gsub(v.value, "%d%d%d%-%d%d%d%-%d%d%d%d", function(match)
- return string.sub(match, 1, 3) .. "-XXX-XXXX"
- end)
- `)
-
- if err != nil {
- t.Fatalf("Error processing phone content: %v", err)
- }
-
- // Total counts from both operations
- matchCount += matchCount2
- modCount += modCount2
-
- if matchCount != 2 {
- t.Errorf("Expected 2 total matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 total modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeXMLWhitespace(result)
- normalizedExpected := normalizeXMLWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-func TestXMLProcessor_Process_DateManipulation(t *testing.T) {
- content := `
-
-
- Conference
- 2023-06-15
-
-
- Workshop
- 2023-06-20
-
-`
-
- expected := `
-
-
- Conference
- 2023-07-15
-
-
- Workshop
- 2023-07-20
-
-`
-
- p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//date", `
- local year, month, day = string.match(v.value, "(%d%d%d%d)-(%d%d)-(%d%d)")
- -- Postpone events by 1 month
- month = tonumber(month) + 1
- if month > 12 then
- month = 1
- year = tonumber(year) + 1
- end
- v.value = string.format("%04d-%02d-%s", tonumber(year), month, day)
- `)
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 2 {
- t.Errorf("Expected 2 matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeXMLWhitespace(result)
- normalizedExpected := normalizeXMLWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-func TestXMLProcessor_Process_Error_InvalidXML(t *testing.T) {
- content := `
-
-
-`
-
- p := &XMLProcessor{}
- _, _, _, err := p.ProcessContent(content, "//unclosed", "v1=v1")
-
- if err == nil {
- t.Errorf("Expected an error for invalid XML, but got none")
- }
-}
-
-func TestXMLProcessor_Process_Error_InvalidXPath(t *testing.T) {
- content := `
-
- value
-`
-
- p := &XMLProcessor{}
- _, _, _, err := p.ProcessContent(content, "[invalid xpath]", "v1=v1")
-
- if err == nil {
- t.Errorf("Expected an error for invalid XPath, but got none")
- }
-}
-
-func TestXMLProcessor_Process_Error_InvalidLua(t *testing.T) {
- content := `
-
- 123
-`
-
- p := &XMLProcessor{}
- _, _, _, err := p.ProcessContent(content, "//element", "v1 = invalid_function()")
-
- if err == nil {
- t.Errorf("Expected an error for invalid Lua, but got none")
- }
-}
-
-func TestXMLProcessor_Process_ComplexXPathSelectors(t *testing.T) {
- content := `
-
-
-
- The Imaginary World
- Alice Johnson
- 19.99
-
-
- History of Science
- Bob Smith
- 29.99
-
-
- Future Tales
- Charlie Adams
- 24.99
-
-
-`
-
- expected := `
-
-
-
- The Imaginary World
- Alice Johnson
- 15.99
-
-
- History of Science
- Bob Smith
- 29.99
-
-
- Future Tales
- Charlie Adams
- 19.99
-
-
-`
-
- p := &XMLProcessor{}
- // Target only fiction books and apply 20% discount to price
- result, modCount, matchCount, err := p.ProcessContent(content, "//book[@category='fiction']/price", "v.value = round(v.value * 0.8, 2)")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 2 {
- t.Errorf("Expected 2 matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeXMLWhitespace(result)
- normalizedExpected := normalizeXMLWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-func TestXMLProcessor_Process_NestedStructureModification(t *testing.T) {
- content := `
-
-
-
-
- 10
- 15
- 12
-
-
- Sword
- Leather
-
-
-
-
- 14
- 10
- 16
-
-
- Axe
- Chain Mail
-
-
-
-`
-
- expected := `
-
-
-
-
- 12
- 18
- 14
-
-
- Sword
- Leather
-
-
-
-
- 14
- 10
- 16
-
-
- Axe
- Chain Mail
-
-
-
-`
-
- p := &XMLProcessor{}
-
- // Boost hero stats by 20%
- result, modCount, matchCount, err := p.ProcessContent(content, "//character[@id='hero']/stats/*", "v.value = round(v.value * 1.2)")
- if err != nil {
- t.Fatalf("Error processing stats content: %v", err)
- }
-
- // Also upgrade hero equipment
- result, modCount2, matchCount2, err := p.ProcessContent(result, "//character[@id='hero']/equipment/*/@damage|//character[@id='hero']/equipment/*/@defense", "v.value = v.value + 2")
- if err != nil {
- t.Fatalf("Error processing equipment content: %v", err)
- }
-
- totalMatches := matchCount + matchCount2
- totalMods := modCount + modCount2
-
- if totalMatches != 5 { // 3 stats + 2 equipment attributes
- t.Errorf("Expected 5 total matches, got %d", totalMatches)
- }
-
- if totalMods != 5 { // 3 stats + 2 equipment attributes
- t.Errorf("Expected 5 total modifications, got %d", totalMods)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeXMLWhitespace(result)
- normalizedExpected := normalizeXMLWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// func TestXMLProcessor_Process_ElementReplacement(t *testing.T) {
-// content := `
-//
-// -
-// Apple
-// 1.99
-// 10
-//
-// -
-// Carrot
-// 0.99
-// 5
-//
-// `
-//
-// expected := `
-//
-// -
-// Apple
-// 1.99
-// 10
-// 19.90
-//
-// -
-// Carrot
-// 0.99
-// 5
-// 4.95
-//
-// `
-//
-// // This test demonstrates using variables from multiple elements to calculate a new value
-// // With the table approach, we can directly access child elements
-// p := &XMLProcessor{}
-//
-// luaExpr := `
-// -- With a proper table approach, this becomes much simpler
-// local price = tonumber(v.attr.price)
-// local quantity = tonumber(v.attr.quantity)
-//
-// -- Add a new total element
-// v.total = string.format("%.2f", price * quantity)
-// `
-//
-// result, modCount, matchCount, err := p.ProcessContent(content, "//item", luaExpr)
-//
-// if err != nil {
-// t.Fatalf("Error processing content: %v", err)
-// }
-//
-// if matchCount != 2 {
-// t.Errorf("Expected 2 matches, got %d", matchCount)
-// }
-//
-// if modCount != 2 {
-// t.Errorf("Expected 2 modifications, got %d", modCount)
-// }
-//
-// // Normalize whitespace for comparison
-// normalizedResult := normalizeXMLWhitespace(result)
-// normalizedExpected := normalizeXMLWhitespace(expected)
-//
-// if normalizedResult != normalizedExpected {
-// t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
-// }
-// }
-
-// func TestXMLProcessor_Process_AttributeAddition(t *testing.T) {
-// content := `
-//
-//
-// Laptop
-// 999.99
-// true
-//
-//
-// Phone
-// 499.99
-// false
-//
-// `
-//
-// expected := `
-//
-//
-// Laptop
-// 999.99
-// true
-//
-//
-// Phone
-// 499.99
-// false
-//
-// `
-//
-// // This test demonstrates adding a new attribute based on element content
-// p := &XMLProcessor{}
-//
-// luaExpr := `
-// -- With table approach, this becomes much cleaner
-// -- We can access the "inStock" element directly
-// if v.inStock == "true" then
-// -- Add a new attribute directly
-// v.attr = v.attr or {}
-// v.attr.status = "available"
-// else
-// v.attr = v.attr or {}
-// v.attr.status = "out-of-stock"
-// end
-// `
-//
-// result, modCount, matchCount, err := p.ProcessContent(content, "//product", luaExpr)
-//
-// if err != nil {
-// t.Fatalf("Error processing content: %v", err)
-// }
-//
-// if matchCount != 2 {
-// t.Errorf("Expected 2 matches, got %d", matchCount)
-// }
-//
-// if modCount != 2 {
-// t.Errorf("Expected 2 modifications, got %d", modCount)
-// }
-//
-// // Normalize whitespace for comparison
-// normalizedResult := normalizeXMLWhitespace(result)
-// normalizedExpected := normalizeXMLWhitespace(expected)
-//
-// if normalizedResult != normalizedExpected {
-// t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
-// }
-// }
-
-// func TestXMLProcessor_Process_ElementRemoval(t *testing.T) {
-// content := `
-//
-//
-// John Smith
-// john@example.com
-// secret123
-// admin
-//
-//
-// Jane Doe
-// jane@example.com
-// pass456
-// user
-//
-// `
-//
-// expected := `
-//
-//
-// John Smith
-// john@example.com
-// admin
-//
-//
-// Jane Doe
-// jane@example.com
-// user
-//
-// `
-//
-// // This test demonstrates removing sensitive data elements
-// p := &XMLProcessor{}
-//
-// luaExpr := `
-// -- With table approach, element removal is trivial
-// -- Just set the element to nil to remove it
-// v.password = nil
-// `
-//
-// result, modCount, matchCount, err := p.ProcessContent(content, "//user", luaExpr)
-//
-// if err != nil {
-// t.Fatalf("Error processing content: %v", err)
-// }
-//
-// if matchCount != 2 {
-// t.Errorf("Expected 2 matches, got %d", matchCount)
-// }
-//
-// if modCount != 2 {
-// t.Errorf("Expected 2 modifications, got %d", modCount)
-// }
-//
-// // Normalize whitespace for comparison
-// normalizedResult := normalizeXMLWhitespace(result)
-// normalizedExpected := normalizeXMLWhitespace(expected)
-//
-// if normalizedResult != normalizedExpected {
-// t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
-// }
-// }
-
-// func TestXMLProcessor_Process_ElementReordering(t *testing.T) {
-// content := `
-//
-//
-// Bob Dylan
-// Blowin' in the Wind
-// 1963
-//
-//
-// The Beatles
-// Hey Jude
-// 1968
-//
-// `
-//
-// expected := `
-//
-//
-// Blowin' in the Wind
-// Bob Dylan
-// 1963
-//
-//
-// Hey Jude
-// The Beatles
-// 1968
-//
-// `
-//
-// // This test demonstrates reordering elements
-// p := &XMLProcessor{}
-//
-// luaExpr := `
-// -- With table approach, we can reorder elements by redefining the table
-// -- Store the values
-// local artist = v.attr.artist
-// local title = v.attr.title
-// local year = v.attr.year
-//
-// -- Clear the table
-// for k in pairs(v) do
-// v[k] = nil
-// end
-//
-// -- Add elements in the desired order
-// v.attr.title = title
-// v.attr.artist = artist
-// v.attr.year = year
-// `
-//
-// result, modCount, matchCount, err := p.ProcessContent(content, "//song", luaExpr)
-//
-// if err != nil {
-// t.Fatalf("Error processing content: %v", err)
-// }
-//
-// if matchCount != 2 {
-// t.Errorf("Expected 2 matches, got %d", matchCount)
-// }
-//
-// if modCount != 2 {
-// t.Errorf("Expected 2 modifications, got %d", modCount)
-// }
-//
-// // Normalize whitespace for comparison
-// normalizedResult := normalizeXMLWhitespace(result)
-// normalizedExpected := normalizeXMLWhitespace(expected)
-//
-// if normalizedResult != normalizedExpected {
-// t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
-// }
-// }
-
-// func TestXMLProcessor_Process_ComplexStructuralChange(t *testing.T) {
-// content := `
-//
-//
-// The Great Gatsby
-// F. Scott Fitzgerald
-// 1925
-// 10.99
-//
-//
-// A Brief History of Time
-// Stephen Hawking
-// 1988
-// 15.99
-//
-// `
-//
-// expected := `
-//
-//
-//
-// The Great Gatsby
-// F. Scott Fitzgerald
-// 1925
-//
-//
-// 10.99
-// 0
-//
-//
-// fiction
-//
-//
-//
-//
-// A Brief History of Time
-// Stephen Hawking
-// 1988
-//
-//
-// 15.99
-// 0
-//
-//
-// non-fiction
-//
-//
-// `
-//
-// // This test demonstrates a complete restructuring of the XML using table approach
-// p := &XMLProcessor{}
-//
-// luaExpr := `
-// -- Store the original values
-// local category = v._attr and v._attr.category
-// local title = v.title
-// local author = v.author
-// local year = v.year
-// local price = v.price
-//
-// -- Clear the original structure
-// for k in pairs(v) do
-// v[k] = nil
-// end
-//
-// -- Create a new nested structure
-// v.details = {
-// title = title,
-// author = author,
-// year = year
-// }
-//
-// v.pricing = {
-// price = {
-// _attr = { currency = "USD" },
-// _text = price
-// },
-// discount = "0"
-// }
-//
-// v.metadata = {
-// category = category
-// }
-// `
-//
-// result, modCount, matchCount, err := p.ProcessContent(content, "//book", luaExpr)
-//
-// if err != nil {
-// t.Fatalf("Error processing content: %v", err)
-// }
-//
-// if matchCount != 2 {
-// t.Errorf("Expected 2 matches, got %d", matchCount)
-// }
-//
-// if modCount != 2 {
-// t.Errorf("Expected 2 modifications, got %d", modCount)
-// }
-//
-// // Normalize whitespace for comparison
-// normalizedResult := normalizeXMLWhitespace(result)
-// normalizedExpected := normalizeXMLWhitespace(expected)
-//
-// if normalizedResult != normalizedExpected {
-// t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
-// }
-// }
-
-func TestXMLProcessor_Process_DynamicXPath(t *testing.T) {
- content := `
-
-
-
-
-
-
-
-
-
-
-`
-
- expected := `
-
-
-
-
-
-
-
-
-
-
-`
-
- // This test demonstrates using specific XPath queries to select precise nodes
- p := &XMLProcessor{}
-
- // Double all timeout values in the configuration
- result, modCount, matchCount, err := p.ProcessContent(content, "//setting[@name='timeout']/@value", "v.value = v.value * 2")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 2 {
- t.Errorf("Expected 2 matches, got %d", matchCount)
- }
-
- if modCount != 2 {
- t.Errorf("Expected 2 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeXMLWhitespace(result)
- normalizedExpected := normalizeXMLWhitespace(expected)
-
- if normalizedResult != normalizedExpected {
- t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
- }
-}
-
-// func TestXMLProcessor_Process_TableBasedStructureCreation(t *testing.T) {
-// content := `
-//
-//
-//
-//
-//
-// `
-//
-// expected := `
-//
-//
-//
-//
-//
-//
-//
-// 2
-// Debug: OFF, Logging: info
-//
-//
-// `
-//
-// // This test demonstrates adding a completely new section with nested structure
-// p := &XMLProcessor{}
-//
-// luaExpr := `
-// -- Count all options
-// local count = 0
-// local summary = ""
-//
-// -- Process each child option
-// local settings = v.children[1]
-// local options = settings.children
-// -- if settings and options then
-// -- if options.attr then
-// -- options = {options}
-// -- end
-// --
-// -- for i, opt in ipairs(options) do
-// -- count = count + 1
-// -- if opt.attr.name == "debug" then
-// -- summary = summary .. "Debug: " .. (opt.attr.value == "true" and "ON" or "OFF")
-// -- elseif opt.attr.name == "log_level" then
-// -- summary = summary .. "Logging: " .. opt.attr.value
-// -- end
-// --
-// -- if i < #options then
-// -- summary = summary .. ", "
-// -- end
-// -- end
-// -- end
-//
-// -- Create a new calculated section
-// -- v.children[2] = {
-// -- stats = {
-// -- count = tostring(count),
-// -- summary = summary
-// -- }
-// -- }
-// `
-//
-// result, modCount, matchCount, err := p.ProcessContent(content, "/data", luaExpr)
-//
-// if err != nil {
-// t.Fatalf("Error processing content: %v", err)
-// }
-//
-// if matchCount != 1 {
-// t.Errorf("Expected 1 match, got %d", matchCount)
-// }
-//
-// if modCount != 1 {
-// t.Errorf("Expected 1 modification, got %d", modCount)
-// }
-//
-// // Normalize whitespace for comparison
-// normalizedResult := normalizeXMLWhitespace(result)
-// normalizedExpected := normalizeXMLWhitespace(expected)
-//
-// if normalizedResult != normalizedExpected {
-// t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
-// }
-// }
-
-// func TestXMLProcessor_Process_ArrayManipulation(t *testing.T) {
-// content := `
-//
-//
-//
-// Book 1
-// 200
-//
-//
-// Book 2
-// 150
-//
-//
-// Book 3
-// 300
-//
-//
-// `
-//
-// expected := `
-//
-//
-//
-// Book 3
-// 300
-//
-//
-// Book 1
-// 200
-//
-//
-//
-// 2
-// 500
-// 250
-//
-// `
-//
-// // This test demonstrates advanced manipulation including:
-// // 1. Sorting and filtering arrays of elements
-// // 2. Calculating aggregates
-// // 3. Generating summaries
-// p := &XMLProcessor{}
-//
-// luaExpr := `
-// -- Get the books array
-// local books = v.books.book
-//
-// -- If only one book, wrap it in a table
-// if books and not books[1] then
-// books = {books}
-// end
-//
-// -- Filter and sort books
-// local filtered_books = {}
-// local total_pages = 0
-//
-// for _, book in ipairs(books) do
-// local pages = tonumber(book.pages) or 0
-//
-// -- Filter: only keep books with pages >= 200
-// if pages >= 200 then
-// total_pages = total_pages + pages
-// table.insert(filtered_books, book)
-// end
-// end
-//
-// -- Sort books by number of pages (descending)
-// table.sort(filtered_books, function(a, b)
-// return tonumber(a.pages) > tonumber(b.pages)
-// end)
-//
-// -- Replace the books array with our filtered and sorted one
-// v.books.book = filtered_books
-//
-// -- Add summary information
-// local count = #filtered_books
-// local average_pages = count > 0 and math.floor(total_pages / count) or 0
-//
-// v.summary = {
-// count = tostring(count),
-// total_pages = tostring(total_pages),
-// average_pages = tostring(average_pages)
-// }
-// `
-//
-// result, modCount, matchCount, err := p.ProcessContent(content, "/library", luaExpr)
-//
-// if err != nil {
-// t.Fatalf("Error processing content: %v", err)
-// }
-//
-// if matchCount != 1 {
-// t.Errorf("Expected 1 match, got %d", matchCount)
-// }
-//
-// if modCount != 1 {
-// t.Errorf("Expected 1 modification, got %d", modCount)
-// }
-//
-// // Normalize whitespace for comparison
-// normalizedResult := normalizeXMLWhitespace(result)
-// normalizedExpected := normalizeXMLWhitespace(expected)
-//
-// if normalizedResult != normalizedExpected {
-// t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
-// }
-// }
-
-// func TestXMLProcessor_Process_DeepPathNavigation(t *testing.T) {
-// content := `
-//
-//
-//
-//
-// localhost
-// 3306
-//
-// admin
-// secret
-//
-//
-//
-// 10
-// 30
-//
-//
-//
-// info
-// /var/log/app.log
-//
-//
-// `
-//
-// expected := `
-//
-//
-//
-//
-// db.example.com
-// 5432
-//
-// admin
-// REDACTED
-//
-//
-//
-// 20
-// 60
-//
-//
-//
-// debug
-// /var/log/app.log
-//
-//
-//
-// production
-// true
-// true
-//
-// `
-//
-// // This test demonstrates navigating deeply nested elements in a complex XML structure
-// p := &XMLProcessor{}
-//
-// luaExpr := `
-// -- Update database connection settings
-// v.config.database.connection.host = "db.example.com"
-// v.config.database.connection.port = "5432"
-//
-// -- Redact sensitive information
-// v.config.database.connection.credentials.password = "REDACTED"
-//
-// -- Double pool size and timeout
-// v.config.database.pool.size = tostring(tonumber(v.config.database.pool.size) * 2)
-// v.config.database.pool.timeout = tostring(tonumber(v.config.database.pool.timeout) * 2)
-//
-// -- Change logging level
-// v.config.logging.level = "debug"
-//
-// -- Add a new status section
-// v.status = {
-// environment = "production",
-// updated = "true",
-// secure = tostring(v.config.database.connection.credentials.password == "REDACTED")
-// }
-// `
-//
-// result, modCount, matchCount, err := p.ProcessContent(content, "/application", luaExpr)
-//
-// if err != nil {
-// t.Fatalf("Error processing content: %v", err)
-// }
-//
-// if matchCount != 1 {
-// t.Errorf("Expected 1 match, got %d", matchCount)
-// }
-//
-// if modCount != 1 {
-// t.Errorf("Expected 1 modification, got %d", modCount)
-// }
-//
-// // Normalize whitespace for comparison
-// normalizedResult := normalizeXMLWhitespace(result)
-// normalizedExpected := normalizeXMLWhitespace(expected)
-//
-// if normalizedResult != normalizedExpected {
-// t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
-// }
-// }
-
-// Add more test cases for specific XML manipulation scenarios below
-// These tests would cover additional functionality as the implementation progresses
-
-// func TestXMLToLua(t *testing.T) {
-// // Sample XML to test with
-// xmlStr := `
-//
-//
-//
-// 123 Main St
-// Anytown
-// 12345
-//
-// john@example.com
-//
-//
-//
-// 456 Business Ave
-// Worktown
-// 54321
-//
-// 555-1234
-//
-//
-// `
-//
-// // Parse the XML
-// doc, err := xmlquery.Parse(strings.NewReader(xmlStr))
-// if err != nil {
-// t.Fatalf("Failed to parse XML: %v", err)
-// }
-//
-// // Create a new Lua state
-// L := lua.NewState()
-// defer L.Close()
-//
-// // Create an XML processor
-// processor := &XMLProcessor{}
-//
-// // Test converting the root element to Lua
-// t.Run("RootElement", func(t *testing.T) {
-// // Find the root element
-// root := doc.SelectElement("root")
-// if root == nil {
-// t.Fatal("Failed to find root element")
-// }
-//
-// // Convert to Lua
-// err := processor.ToLua(L, root)
-// if err != nil {
-// t.Fatalf("Failed to convert to Lua: %v", err)
-// }
-//
-// // Verify the result
-// luaTable := L.GetGlobal("v")
-// if luaTable.Type() != lua.LTTable {
-// t.Fatalf("Expected table, got %s", luaTable.Type().String())
-// }
-//
-// // Check element type
-// typeVal := L.GetField(luaTable, "type")
-// if typeVal.String() != "element" {
-// t.Errorf("Expected type 'element', got '%s'", typeVal.String())
-// }
-//
-// // Check name
-// nameVal := L.GetField(luaTable, "name")
-// if nameVal.String() != "root" {
-// t.Errorf("Expected name 'root', got '%s'", nameVal.String())
-// }
-//
-// // Check attributes
-// attrsTable := L.GetField(luaTable, "attributes")
-// if attrsTable.Type() != lua.LTTable {
-// t.Fatalf("Expected attributes table, got %s", attrsTable.Type().String())
-// }
-//
-// idVal := L.GetField(attrsTable, "id")
-// if idVal.String() != "1" {
-// t.Errorf("Expected id '1', got '%s'", idVal.String())
-// }
-//
-// // Check that we have children
-// childrenTable := L.GetField(luaTable, "children")
-// if childrenTable.Type() != lua.LTTable {
-// t.Fatalf("Expected children table, got %s", childrenTable.Type().String())
-// }
-// })
-//
-// // Test converting a nested element to Lua
-// t.Run("NestedElement", func(t *testing.T) {
-// // Find a nested element
-// street := doc.SelectElement("//street")
-// if street == nil {
-// t.Fatal("Failed to find street element")
-// }
-//
-// // Convert to Lua
-// err := processor.ToLua(L, street)
-// if err != nil {
-// t.Fatalf("Failed to convert to Lua: %v", err)
-// }
-//
-// // Verify the result
-// luaTable := L.GetGlobal("v")
-// if luaTable.Type() != lua.LTTable {
-// t.Fatalf("Expected table, got %s", luaTable.Type().String())
-// }
-//
-// // Check element type
-// typeVal := L.GetField(luaTable, "type")
-// if typeVal.String() != "element" {
-// t.Errorf("Expected type 'element', got '%s'", typeVal.String())
-// }
-//
-// // Check name
-// nameVal := L.GetField(luaTable, "name")
-// if nameVal.String() != "street" {
-// t.Errorf("Expected name 'street', got '%s'", nameVal.String())
-// }
-//
-// // Check value
-// valueVal := L.GetField(luaTable, "value")
-// if valueVal.String() != "123 Main St" {
-// t.Errorf("Expected value '123 Main St', got '%s'", valueVal.String())
-// }
-// })
-//
-// // Test FromLua with a simple string update
-// t.Run("FromLuaString", func(t *testing.T) {
-// // Set up a Lua state with a string value
-// L := lua.NewState()
-// defer L.Close()
-// L.SetGlobal("v", lua.LString("New Value"))
-//
-// // Convert from Lua
-// result, err := processor.FromLua(L)
-// if err != nil {
-// t.Fatalf("Failed to convert from Lua: %v", err)
-// }
-//
-// // Verify the result
-// strResult, ok := result.(string)
-// if !ok {
-// t.Fatalf("Expected string result, got %T", result)
-// }
-//
-// if strResult != "New Value" {
-// t.Errorf("Expected 'New Value', got '%s'", strResult)
-// }
-// })
-//
-// // Test FromLua with a complex table update
-// t.Run("FromLuaTable", func(t *testing.T) {
-// // Set up a Lua state with a table value
-// L := lua.NewState()
-// defer L.Close()
-//
-// table := L.NewTable()
-// L.SetField(table, "value", lua.LString("Updated Text"))
-//
-// attrTable := L.NewTable()
-// L.SetField(attrTable, "id", lua.LString("new-id"))
-// L.SetField(attrTable, "class", lua.LString("highlight"))
-//
-// L.SetField(table, "attributes", attrTable)
-// L.SetGlobal("v", table)
-//
-// // Convert from Lua
-// result, err := processor.FromLua(L)
-// if err != nil {
-// t.Fatalf("Failed to convert from Lua: %v", err)
-// }
-//
-// // Verify the result
-// mapResult, ok := result.(map[string]interface{})
-// if !ok {
-// t.Fatalf("Expected map result, got %T", result)
-// }
-//
-// // Check value
-// if value, ok := mapResult["value"]; !ok || value != "Updated Text" {
-// t.Errorf("Expected value 'Updated Text', got '%v'", value)
-// }
-//
-// // Check attributes
-// attrs, ok := mapResult["attributes"].(map[string]interface{})
-// if !ok {
-// t.Fatalf("Expected attributes map, got %T", mapResult["attributes"])
-// }
-//
-// if id, ok := attrs["id"]; !ok || id != "new-id" {
-// t.Errorf("Expected id 'new-id', got '%v'", id)
-// }
-//
-// if class, ok := attrs["class"]; !ok || class != "highlight" {
-// t.Errorf("Expected class 'highlight', got '%v'", class)
-// }
-// })
-//
-// // Test updateNodeFromMap with a simple value update
-// t.Run("UpdateNodeValue", func(t *testing.T) {
-// // Create a simple element to update
-// xmlStr := `Original Text`
-// doc, _ := xmlquery.Parse(strings.NewReader(xmlStr))
-// node := doc.SelectElement("test")
-//
-// // Create update data
-// updateData := map[string]interface{}{
-// "value": "Updated Text",
-// }
-//
-// // Update the node
-// updateNodeFromMap(node, updateData)
-//
-// // Verify the update
-// if node.InnerText() != "Updated Text" {
-// t.Errorf("Expected value 'Updated Text', got '%s'", node.InnerText())
-// }
-// })
-//
-// // Test updateNodeFromMap with attribute updates
-// t.Run("UpdateNodeAttributes", func(t *testing.T) {
-// // Create an element with attributes
-// xmlStr := `Text`
-// doc, _ := xmlquery.Parse(strings.NewReader(xmlStr))
-// node := doc.SelectElement("test")
-//
-// // Create update data
-// updateData := map[string]interface{}{
-// "attributes": map[string]interface{}{
-// "id": "new",
-// "class": "added",
-// },
-// }
-//
-// // Update the node
-// updateNodeFromMap(node, updateData)
-//
-// // Verify the id attribute was updated
-// idFound := false
-// classFound := false
-// for _, attr := range node.Attr {
-// if attr.Name.Local == "id" {
-// idFound = true
-// if attr.Value != "new" {
-// t.Errorf("Expected id 'new', got '%s'", attr.Value)
-// }
-// }
-// if attr.Name.Local == "class" {
-// classFound = true
-// if attr.Value != "added" {
-// t.Errorf("Expected class 'added', got '%s'", attr.Value)
-// }
-// }
-// }
-//
-// if !idFound {
-// t.Error("Expected to find 'id' attribute but didn't")
-// }
-//
-// if !classFound {
-// t.Error("Expected to find 'class' attribute but didn't")
-// }
-// })
-// }