183 lines
6.0 KiB
Go
183 lines
6.0 KiB
Go
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)
|
|
}
|