Refactor everything to processors and implement json and xml processors such as they are
This commit is contained in:
454
processor/xml.go
Normal file
454
processor/xml.go
Normal file
@@ -0,0 +1,454 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/antchfx/xmlquery"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
// XMLProcessor implements the Processor interface using XPath
|
||||
type XMLProcessor struct {
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
// NewXMLProcessor creates a new XMLProcessor
|
||||
func NewXMLProcessor(logger Logger) *XMLProcessor {
|
||||
return &XMLProcessor{
|
||||
Logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Process implements the Processor interface for XMLProcessor
|
||||
func (p *XMLProcessor) Process(filename string, pattern string, luaExpr string, originalExpr string) (int, int, error) {
|
||||
// Use pattern as XPath expression
|
||||
xpathExpr := pattern
|
||||
|
||||
// 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)
|
||||
if p.Logger != nil {
|
||||
p.Logger.Printf("File %s loaded: %d bytes", fullPath, len(content))
|
||||
}
|
||||
|
||||
// Process the content
|
||||
modifiedContent, modCount, matchCount, err := p.ProcessContent(fileContent, xpathExpr, luaExpr, originalExpr)
|
||||
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)
|
||||
}
|
||||
|
||||
if p.Logger != nil {
|
||||
p.Logger.Printf("Made %d XML node modifications to %s and saved (%d bytes)",
|
||||
modCount, fullPath, len(modifiedContent))
|
||||
}
|
||||
}
|
||||
|
||||
return modCount, matchCount, nil
|
||||
}
|
||||
|
||||
// ToLua implements the Processor interface for XMLProcessor
|
||||
func (p *XMLProcessor) ToLua(L *lua.LState, data interface{}) error {
|
||||
// Currently not used directly as this is handled in Process
|
||||
return nil
|
||||
}
|
||||
|
||||
// FromLua implements the Processor interface for XMLProcessor
|
||||
func (p *XMLProcessor) FromLua(L *lua.LState) (interface{}, error) {
|
||||
// Currently not used directly as this is handled in Process
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// XMLNodeToString converts an XML node to a string representation
|
||||
func (p *XMLProcessor) XMLNodeToString(node *xmlquery.Node) string {
|
||||
// Use a simple string representation for now
|
||||
var sb strings.Builder
|
||||
|
||||
// Start tag with attributes
|
||||
if node.Type == xmlquery.ElementNode {
|
||||
sb.WriteString("<")
|
||||
sb.WriteString(node.Data)
|
||||
|
||||
// Add attributes
|
||||
for _, attr := range node.Attr {
|
||||
sb.WriteString(" ")
|
||||
sb.WriteString(attr.Name.Local)
|
||||
sb.WriteString("=\"")
|
||||
sb.WriteString(attr.Value)
|
||||
sb.WriteString("\"")
|
||||
}
|
||||
|
||||
// If self-closing
|
||||
if node.FirstChild == nil {
|
||||
sb.WriteString("/>")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
sb.WriteString(">")
|
||||
} else if node.Type == xmlquery.TextNode {
|
||||
// Just write the text content
|
||||
sb.WriteString(node.Data)
|
||||
return sb.String()
|
||||
} else if node.Type == xmlquery.CommentNode {
|
||||
// Write comment
|
||||
sb.WriteString("<!--")
|
||||
sb.WriteString(node.Data)
|
||||
sb.WriteString("-->")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// Add children
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
sb.WriteString(p.XMLNodeToString(child))
|
||||
}
|
||||
|
||||
// End tag for elements
|
||||
if node.Type == xmlquery.ElementNode {
|
||||
sb.WriteString("</")
|
||||
sb.WriteString(node.Data)
|
||||
sb.WriteString(">")
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// NodeToLuaTable creates a Lua table from an XML node
|
||||
func (p *XMLProcessor) NodeToLuaTable(L *lua.LState, node *xmlquery.Node) lua.LValue {
|
||||
nodeTable := L.NewTable()
|
||||
|
||||
// Add node name
|
||||
L.SetField(nodeTable, "name", lua.LString(node.Data))
|
||||
|
||||
// Add node type
|
||||
switch node.Type {
|
||||
case xmlquery.ElementNode:
|
||||
L.SetField(nodeTable, "type", lua.LString("element"))
|
||||
case xmlquery.TextNode:
|
||||
L.SetField(nodeTable, "type", lua.LString("text"))
|
||||
case xmlquery.AttributeNode:
|
||||
L.SetField(nodeTable, "type", lua.LString("attribute"))
|
||||
case xmlquery.CommentNode:
|
||||
L.SetField(nodeTable, "type", lua.LString("comment"))
|
||||
default:
|
||||
L.SetField(nodeTable, "type", lua.LString("other"))
|
||||
}
|
||||
|
||||
// Add node text content if it's a text node
|
||||
if node.Type == xmlquery.TextNode {
|
||||
L.SetField(nodeTable, "content", lua.LString(node.Data))
|
||||
}
|
||||
|
||||
// Add attributes if it's an element node
|
||||
if node.Type == xmlquery.ElementNode && len(node.Attr) > 0 {
|
||||
attrsTable := L.NewTable()
|
||||
for _, attr := range node.Attr {
|
||||
L.SetField(attrsTable, attr.Name.Local, lua.LString(attr.Value))
|
||||
}
|
||||
L.SetField(nodeTable, "attributes", attrsTable)
|
||||
}
|
||||
|
||||
// Add children if any
|
||||
if node.FirstChild != nil {
|
||||
childrenTable := L.NewTable()
|
||||
i := 1
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
// Skip empty text nodes (whitespace)
|
||||
if child.Type == xmlquery.TextNode && strings.TrimSpace(child.Data) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
childTable := p.NodeToLuaTable(L, child)
|
||||
childrenTable.RawSetInt(i, childTable)
|
||||
i++
|
||||
}
|
||||
L.SetField(nodeTable, "children", childrenTable)
|
||||
}
|
||||
|
||||
return nodeTable
|
||||
}
|
||||
|
||||
// GetModifiedNode retrieves a modified node from Lua
|
||||
func (p *XMLProcessor) GetModifiedNode(L *lua.LState, originalNode *xmlquery.Node) (*xmlquery.Node, bool) {
|
||||
// Check if we have a node global with changes
|
||||
nodeTable := L.GetGlobal("node")
|
||||
if nodeTable == lua.LNil || nodeTable.Type() != lua.LTTable {
|
||||
return originalNode, false
|
||||
}
|
||||
|
||||
// Clone the node since we don't want to modify the original
|
||||
clonedNode := *originalNode
|
||||
|
||||
// For text nodes, check if content was changed
|
||||
if originalNode.Type == xmlquery.TextNode {
|
||||
contentField := L.GetField(nodeTable.(*lua.LTable), "content")
|
||||
if contentField != lua.LNil {
|
||||
if strContent, ok := contentField.(lua.LString); ok {
|
||||
if string(strContent) != originalNode.Data {
|
||||
clonedNode.Data = string(strContent)
|
||||
return &clonedNode, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return originalNode, false
|
||||
}
|
||||
|
||||
// For element nodes, attributes might have been changed
|
||||
if originalNode.Type == xmlquery.ElementNode {
|
||||
attrsField := L.GetField(nodeTable.(*lua.LTable), "attributes")
|
||||
if attrsField != lua.LNil && attrsField.Type() == lua.LTTable {
|
||||
attrsTable := attrsField.(*lua.LTable)
|
||||
|
||||
// Check if any attributes changed
|
||||
changed := false
|
||||
for _, attr := range originalNode.Attr {
|
||||
newValue := L.GetField(attrsTable, attr.Name.Local)
|
||||
if newValue != lua.LNil {
|
||||
if strValue, ok := newValue.(lua.LString); ok {
|
||||
if string(strValue) != attr.Value {
|
||||
// Create a new attribute with the changed value
|
||||
for i, a := range clonedNode.Attr {
|
||||
if a.Name.Local == attr.Name.Local {
|
||||
clonedNode.Attr[i].Value = string(strValue)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
return &clonedNode, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No changes detected
|
||||
return originalNode, false
|
||||
}
|
||||
|
||||
// SetupXMLHelpers adds XML-specific helper functions to Lua
|
||||
func (p *XMLProcessor) SetupXMLHelpers(L *lua.LState) {
|
||||
// Helper function to create a new XML node
|
||||
L.SetGlobal("new_node", L.NewFunction(func(L *lua.LState) int {
|
||||
nodeName := L.CheckString(1)
|
||||
nodeTable := L.NewTable()
|
||||
L.SetField(nodeTable, "name", lua.LString(nodeName))
|
||||
L.SetField(nodeTable, "type", lua.LString("element"))
|
||||
L.SetField(nodeTable, "attributes", L.NewTable())
|
||||
L.SetField(nodeTable, "children", L.NewTable())
|
||||
L.Push(nodeTable)
|
||||
return 1
|
||||
}))
|
||||
|
||||
// Helper function to set an attribute
|
||||
L.SetGlobal("set_attr", L.NewFunction(func(L *lua.LState) int {
|
||||
nodeTable := L.CheckTable(1)
|
||||
attrName := L.CheckString(2)
|
||||
attrValue := L.CheckString(3)
|
||||
|
||||
attrsTable := L.GetField(nodeTable, "attributes")
|
||||
if attrsTable == lua.LNil {
|
||||
attrsTable = L.NewTable()
|
||||
L.SetField(nodeTable, "attributes", attrsTable)
|
||||
}
|
||||
|
||||
L.SetField(attrsTable.(*lua.LTable), attrName, lua.LString(attrValue))
|
||||
return 0
|
||||
}))
|
||||
|
||||
// Helper function to add a child node
|
||||
L.SetGlobal("add_child", L.NewFunction(func(L *lua.LState) int {
|
||||
parentTable := L.CheckTable(1)
|
||||
childTable := L.CheckTable(2)
|
||||
|
||||
childrenTable := L.GetField(parentTable, "children")
|
||||
if childrenTable == lua.LNil {
|
||||
childrenTable = L.NewTable()
|
||||
L.SetField(parentTable, "children", childrenTable)
|
||||
}
|
||||
|
||||
childrenTbl := childrenTable.(*lua.LTable)
|
||||
childrenTbl.RawSetInt(childrenTbl.Len()+1, childTable)
|
||||
return 0
|
||||
}))
|
||||
}
|
||||
|
||||
// ProcessContent implements the Processor interface for XMLProcessor
|
||||
// It processes XML content directly without file I/O
|
||||
func (p *XMLProcessor) ProcessContent(content string, pattern string, luaExpr string, originalExpr string) (string, int, int, error) {
|
||||
// Parse the XML document
|
||||
doc, err := xmlquery.Parse(strings.NewReader(content))
|
||||
if err != nil {
|
||||
return "", 0, 0, fmt.Errorf("error parsing XML: %v", err)
|
||||
}
|
||||
|
||||
// Find nodes matching XPath expression
|
||||
nodes, err := xmlquery.QueryAll(doc, pattern)
|
||||
if err != nil {
|
||||
return "", 0, 0, fmt.Errorf("invalid XPath expression: %v", err)
|
||||
}
|
||||
|
||||
// Log what we found
|
||||
if p.Logger != nil {
|
||||
p.Logger.Printf("XML mode selected with XPath expression: %s (found %d matching nodes)",
|
||||
pattern, len(nodes))
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
if p.Logger != nil {
|
||||
p.Logger.Printf("No XML nodes matched XPath expression: %s", pattern)
|
||||
}
|
||||
return content, 0, 0, nil
|
||||
}
|
||||
|
||||
// Initialize Lua state
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
|
||||
// Setup Lua helper functions
|
||||
if err := InitLuaHelpers(L); err != nil {
|
||||
return "", 0, 0, err
|
||||
}
|
||||
|
||||
// Register XML-specific helper functions
|
||||
p.SetupXMLHelpers(L)
|
||||
|
||||
// Track modifications
|
||||
matchCount := len(nodes)
|
||||
modificationCount := 0
|
||||
modifiedContent := content
|
||||
modifications := []ModificationRecord{}
|
||||
|
||||
// Process each matching node
|
||||
for i, node := range nodes {
|
||||
// Get the original text representation of this node
|
||||
originalNodeText := p.XMLNodeToString(node)
|
||||
if p.Logger != nil {
|
||||
p.Logger.Printf("Found node #%d: %s", i+1, LimitString(originalNodeText, 100))
|
||||
}
|
||||
|
||||
// For text nodes, we'll handle them directly
|
||||
if node.Type == xmlquery.TextNode && node.Parent != nil {
|
||||
// If this is a text node, we'll use its value directly
|
||||
// Get the node's text content
|
||||
textContent := node.Data
|
||||
|
||||
// Set up Lua environment
|
||||
L.SetGlobal("v1", lua.LNumber(0)) // Default to 0 if not numeric
|
||||
L.SetGlobal("s1", lua.LString(textContent))
|
||||
|
||||
// Try to convert to number if possible
|
||||
if floatVal, err := strconv.ParseFloat(textContent, 64); err == nil {
|
||||
L.SetGlobal("v1", lua.LNumber(floatVal))
|
||||
}
|
||||
|
||||
// Execute user's Lua script
|
||||
if err := L.DoString(luaExpr); err != nil {
|
||||
if p.Logger != nil {
|
||||
p.Logger.Printf("Lua execution failed for node #%d: %v", i+1, err)
|
||||
}
|
||||
continue // Skip this node on error
|
||||
}
|
||||
|
||||
// Check for modifications
|
||||
modVal := L.GetGlobal("v1")
|
||||
if v, ok := modVal.(lua.LNumber); ok {
|
||||
// If we have a numeric result, convert it to string
|
||||
newValue := strconv.FormatFloat(float64(v), 'f', -1, 64)
|
||||
if newValue != textContent {
|
||||
// Replace the node content in the document
|
||||
parentStr := p.XMLNodeToString(node.Parent)
|
||||
newParentStr := strings.Replace(parentStr, textContent, newValue, 1)
|
||||
modifiedContent = strings.Replace(modifiedContent, parentStr, newParentStr, 1)
|
||||
modificationCount++
|
||||
|
||||
// Record the modification
|
||||
modifications = append(modifications, ModificationRecord{
|
||||
File: "",
|
||||
OldValue: textContent,
|
||||
NewValue: newValue,
|
||||
Operation: originalExpr,
|
||||
Context: fmt.Sprintf("(XPath: %s)", pattern),
|
||||
})
|
||||
|
||||
if p.Logger != nil {
|
||||
p.Logger.Printf("Modified text node #%d: '%s' -> '%s'",
|
||||
i+1, LimitString(textContent, 30), LimitString(newValue, 30))
|
||||
}
|
||||
}
|
||||
}
|
||||
continue // Move to next node
|
||||
}
|
||||
|
||||
// Convert the node to a Lua table
|
||||
nodeTable := p.NodeToLuaTable(L, node)
|
||||
|
||||
// Set the node in Lua global variable for user script
|
||||
L.SetGlobal("node", nodeTable)
|
||||
|
||||
// Execute user's Lua script
|
||||
if err := L.DoString(luaExpr); err != nil {
|
||||
if p.Logger != nil {
|
||||
p.Logger.Printf("Lua execution failed for node #%d: %v", i+1, err)
|
||||
}
|
||||
continue // Skip this node on error
|
||||
}
|
||||
|
||||
// Get modified node from Lua
|
||||
modifiedNode, changed := p.GetModifiedNode(L, node)
|
||||
if !changed {
|
||||
if p.Logger != nil {
|
||||
p.Logger.Printf("Node #%d was not modified by script", i+1)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Render the modified node back to XML
|
||||
modifiedNodeText := p.XMLNodeToString(modifiedNode)
|
||||
|
||||
// Replace just this node in the document
|
||||
if originalNodeText != modifiedNodeText {
|
||||
modifiedContent = strings.Replace(
|
||||
modifiedContent,
|
||||
originalNodeText,
|
||||
modifiedNodeText,
|
||||
1)
|
||||
modificationCount++
|
||||
|
||||
// Record the modification for reporting
|
||||
modifications = append(modifications, ModificationRecord{
|
||||
File: "",
|
||||
OldValue: LimitString(originalNodeText, 30),
|
||||
NewValue: LimitString(modifiedNodeText, 30),
|
||||
Operation: originalExpr,
|
||||
Context: fmt.Sprintf("(XPath: %s)", pattern),
|
||||
})
|
||||
|
||||
if p.Logger != nil {
|
||||
p.Logger.Printf("Modified node #%d", i+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.Logger != nil && modificationCount > 0 {
|
||||
p.Logger.Printf("Made %d XML node modifications", modificationCount)
|
||||
}
|
||||
|
||||
return modifiedContent, modificationCount, matchCount, nil
|
||||
}
|
Reference in New Issue
Block a user