package processor import ( "encoding/json" "fmt" "log" "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) { // Parse JSON document var jsonData interface{} err := json.Unmarshal([]byte(content), &jsonData) if err != nil { return content, 0, 0, fmt.Errorf("error parsing JSON: %v", err) } // Find nodes matching the JSONPath pattern nodes, err := jsonpath.Get(jsonData, pattern) if err != nil { return content, 0, 0, fmt.Errorf("error getting nodes: %v", err) } matchCount := len(nodes) if matchCount == 0 { return content, 0, 0, nil } modCount := 0 for _, node := range nodes { log.Printf("Processing node at path: %s with value: %v", node.Path, node.Value) // Initialize Lua L, err := NewLuaState() if err != nil { return content, len(nodes), 0, fmt.Errorf("error creating Lua state: %v", err) } defer L.Close() log.Println("Lua state initialized successfully.") err = p.ToLua(L, node.Value) if err != nil { return content, len(nodes), 0, fmt.Errorf("error converting to Lua: %v", err) } log.Printf("Converted node value to Lua: %v", node.Value) originalScript := luaExpr fullScript := BuildLuaScript(luaExpr) log.Printf("Original script: %q, Full script: %q", originalScript, fullScript) // Execute Lua script log.Printf("Executing Lua script: %q", fullScript) if err := L.DoString(fullScript); err != nil { return content, len(nodes), 0, fmt.Errorf("error executing Lua %q: %v", fullScript, err) } log.Println("Lua script executed successfully.") // Get modified value result, err := p.FromLua(L) if err != nil { return content, len(nodes), 0, fmt.Errorf("error getting result from Lua: %v", err) } log.Printf("Retrieved modified value from Lua: %v", result) modified := false modified = L.GetGlobal("modified").String() == "true" if !modified { log.Printf("No changes made to node at path: %s", node.Path) continue } // Apply the modification to the JSON data err = p.updateJSONValue(jsonData, node.Path, result) if err != nil { return content, len(nodes), 0, fmt.Errorf("error updating JSON: %v", err) } log.Printf("Updated JSON at path: %s with new value: %v", node.Path, result) modCount++ } // Convert the modified JSON back to a string with same formatting var jsonBytes []byte jsonBytes, err = json.MarshalIndent(jsonData, "", " ") if err != nil { return content, modCount, matchCount, fmt.Errorf("error marshalling JSON: %v", err) } return string(jsonBytes), modCount, matchCount, nil } // / Selects from the root node // // Selects nodes in the document from the current node that match the selection no matter where they are // . Selects the current node // @ Selects attributes // /bookstore/* Selects all the child element nodes of the bookstore element // //* Selects all elements in the document // /bookstore/book[1] Selects the first book element that is the child of the bookstore element. // /bookstore/book[last()] Selects the last book element that is the child of the bookstore element // /bookstore/book[last()-1] Selects the last but one book element that is the child of the bookstore element // /bookstore/book[position()<3] Selects the first two book elements that are children of the bookstore element // //title[@lang] Selects all the title elements that have an attribute named lang // //title[@lang='en'] Selects all the title elements that have a "lang" attribute with a value of "en" // /bookstore/book[price>35.00] Selects all the book elements of the bookstore element that have a price element with a value greater than 35.00 // /bookstore/book[price>35.00]/title Selects all the title elements of the book elements of the bookstore element that have a price element with a value greater than 35.00 // updateJSONValue updates a value in the JSON structure based on its JSONPath func (p *JSONProcessor) updateJSONValue(jsonData interface{}, path string, newValue interface{}) error { // Special handling for root node if path == "$" { // 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 return jsonpath.Set(jsonData, path, newValue) } // Clear the original map for k := range rootMap { delete(rootMap, k) } // Copy all keys from the new 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 return jsonpath.Set(jsonData, path, newValue) } // Clear and recreate the array *&rootArray = rootValue return nil default: // For other types, use jsonpath.Set return jsonpath.Set(jsonData, path, newValue) } } // For non-root paths, use the regular Set method err := jsonpath.Set(jsonData, path, newValue) if err != nil { 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) }