218 lines
6.0 KiB
Go
218 lines
6.0 KiB
Go
package processor
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/antchfx/xmlquery"
|
|
lua "github.com/yuin/gopher-lua"
|
|
)
|
|
|
|
// XMLProcessor implements the Processor interface for XML documents
|
|
type XMLProcessor struct{}
|
|
|
|
// Process implements the Processor interface for XMLProcessor
|
|
func (p *XMLProcessor) Process(filename string, pattern string, luaExpr string) (int, int, error) {
|
|
// Read file content
|
|
fullPath := filepath.Join(".", filename)
|
|
content, err := os.ReadFile(fullPath)
|
|
if err != nil {
|
|
return 0, 0, fmt.Errorf("error reading file: %v", err)
|
|
}
|
|
|
|
fileContent := string(content)
|
|
|
|
// Process the content
|
|
modifiedContent, modCount, matchCount, err := p.ProcessContent(fileContent, pattern, luaExpr)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
// If we made modifications, save the file
|
|
if modCount > 0 {
|
|
err = os.WriteFile(fullPath, []byte(modifiedContent), 0644)
|
|
if err != nil {
|
|
return 0, 0, fmt.Errorf("error writing file: %v", err)
|
|
}
|
|
}
|
|
|
|
return modCount, matchCount, nil
|
|
}
|
|
|
|
// ProcessContent implements the Processor interface for XMLProcessor
|
|
func (p *XMLProcessor) ProcessContent(content string, pattern string, luaExpr string) (string, int, int, error) {
|
|
// Parse XML document
|
|
doc, err := xmlquery.Parse(strings.NewReader(content))
|
|
if err != nil {
|
|
return content, 0, 0, fmt.Errorf("error parsing XML: %v", err)
|
|
}
|
|
|
|
// Find nodes matching the XPath pattern
|
|
nodes, err := xmlquery.QueryAll(doc, pattern)
|
|
if err != nil {
|
|
return content, 0, 0, fmt.Errorf("error executing XPath: %v", err)
|
|
}
|
|
|
|
matchCount := len(nodes)
|
|
if matchCount == 0 {
|
|
return content, 0, 0, nil
|
|
}
|
|
|
|
// Initialize Lua
|
|
L := lua.NewState()
|
|
defer L.Close()
|
|
|
|
// Load math library
|
|
L.Push(L.GetGlobal("require"))
|
|
L.Push(lua.LString("math"))
|
|
if err := L.PCall(1, 1, nil); err != nil {
|
|
return content, 0, 0, fmt.Errorf("error loading Lua math library: %v", err)
|
|
}
|
|
|
|
// Load helper functions
|
|
if err := InitLuaHelpers(L); err != nil {
|
|
return content, 0, 0, err
|
|
}
|
|
|
|
// Apply modifications to each node
|
|
modCount := 0
|
|
for _, node := range nodes {
|
|
// Reset Lua state for each node
|
|
L.SetGlobal("v1", lua.LNil)
|
|
L.SetGlobal("s1", lua.LNil)
|
|
|
|
// Get the node value
|
|
var originalValue string
|
|
if node.Type == xmlquery.AttributeNode {
|
|
originalValue = node.InnerText()
|
|
} else if node.Type == xmlquery.TextNode {
|
|
originalValue = node.Data
|
|
} else {
|
|
originalValue = node.InnerText()
|
|
}
|
|
|
|
// Convert to Lua variables
|
|
err = p.ToLua(L, originalValue)
|
|
if err != nil {
|
|
return content, modCount, matchCount, fmt.Errorf("error converting to Lua: %v", err)
|
|
}
|
|
|
|
// Execute Lua script
|
|
if err := L.DoString(luaExpr); err != nil {
|
|
return content, modCount, matchCount, fmt.Errorf("error executing Lua: %v", err)
|
|
}
|
|
|
|
// Get modified value
|
|
result, err := p.FromLua(L)
|
|
if err != nil {
|
|
return content, modCount, matchCount, fmt.Errorf("error getting result from Lua: %v", err)
|
|
}
|
|
|
|
newValue, ok := result.(string)
|
|
if !ok {
|
|
return content, modCount, matchCount, fmt.Errorf("expected string result from Lua, got %T", result)
|
|
}
|
|
|
|
// Skip if no change
|
|
if newValue == originalValue {
|
|
continue
|
|
}
|
|
|
|
// Apply modification
|
|
if node.Type == xmlquery.AttributeNode {
|
|
// For attribute nodes, update the attribute value
|
|
node.Parent.Attr = append([]xmlquery.Attr{}, node.Parent.Attr...)
|
|
for i, attr := range node.Parent.Attr {
|
|
if attr.Name.Local == node.Data {
|
|
node.Parent.Attr[i].Value = newValue
|
|
break
|
|
}
|
|
}
|
|
} else if node.Type == xmlquery.TextNode {
|
|
// For text nodes, update the text content
|
|
node.Data = newValue
|
|
} else {
|
|
// For element nodes, replace inner text
|
|
// Simple approach: set the InnerText directly if there are no child elements
|
|
if node.FirstChild == nil || (node.FirstChild != nil && node.FirstChild.Type == xmlquery.TextNode && node.FirstChild.NextSibling == nil) {
|
|
if node.FirstChild != nil {
|
|
node.FirstChild.Data = newValue
|
|
} else {
|
|
// Create a new text node and add it as the first child
|
|
textNode := &xmlquery.Node{
|
|
Type: xmlquery.TextNode,
|
|
Data: newValue,
|
|
}
|
|
node.FirstChild = textNode
|
|
}
|
|
} else {
|
|
// Complex case: node has mixed content or child elements
|
|
// Replace just the text content while preserving child elements
|
|
// This is a simplified approach - more complex XML may need more robust handling
|
|
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
|
if child.Type == xmlquery.TextNode {
|
|
child.Data = newValue
|
|
break // Update only the first text node
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
modCount++
|
|
}
|
|
|
|
// 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 declaration + doc.OutputXML(true), modCount, matchCount, nil
|
|
}
|
|
|
|
return doc.OutputXML(true), modCount, matchCount, nil
|
|
}
|
|
|
|
// ToLua converts XML node values to Lua variables
|
|
func (p *XMLProcessor) ToLua(L *lua.LState, data interface{}) error {
|
|
value, ok := data.(string)
|
|
if !ok {
|
|
return fmt.Errorf("expected string value, got %T", data)
|
|
}
|
|
|
|
// Set as string variable
|
|
L.SetGlobal("s1", lua.LString(value))
|
|
|
|
// Try to convert to number if possible
|
|
L.SetGlobal("v1", lua.LNumber(0)) // Default to 0
|
|
if err := L.DoString(fmt.Sprintf("v1 = tonumber(%q) or 0", value)); err != nil {
|
|
return fmt.Errorf("error converting value to number: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// FromLua gets modified values from Lua
|
|
func (p *XMLProcessor) FromLua(L *lua.LState) (interface{}, error) {
|
|
// Check if string variable was modified
|
|
s1 := L.GetGlobal("s1")
|
|
if s1 != lua.LNil {
|
|
if s1Str, ok := s1.(lua.LString); ok {
|
|
return string(s1Str), nil
|
|
}
|
|
}
|
|
|
|
// Check if numeric variable was modified
|
|
v1 := L.GetGlobal("v1")
|
|
if v1 != lua.LNil {
|
|
if v1Num, ok := v1.(lua.LNumber); ok {
|
|
return fmt.Sprintf("%v", v1Num), nil
|
|
}
|
|
}
|
|
|
|
// Default return empty string
|
|
return "", nil
|
|
}
|