Clean up after claude

This commit is contained in:
2025-03-24 16:23:24 +01:00
parent 4f70eaa329
commit 1d39b5287f
10 changed files with 2947 additions and 2160 deletions

View File

@@ -4,30 +4,17 @@ 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,
}
}
// 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, originalExpr string) (int, int, error) {
// Use pattern as XPath expression
xpathExpr := pattern
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)
@@ -36,12 +23,9 @@ func (p *XMLProcessor) Process(filename string, pattern string, luaExpr string,
}
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)
modifiedContent, modCount, matchCount, err := p.ProcessContent(fileContent, pattern, luaExpr)
if err != nil {
return 0, 0, err
}
@@ -52,403 +36,182 @@ func (p *XMLProcessor) Process(filename string, pattern string, luaExpr string,
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
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 "", 0, 0, fmt.Errorf("error parsing XML: %v", err)
return content, 0, 0, fmt.Errorf("error parsing XML: %v", err)
}
// Find nodes matching XPath expression
// Find nodes matching the XPath pattern
nodes, err := xmlquery.QueryAll(doc, pattern)
if err != nil {
return "", 0, 0, fmt.Errorf("invalid XPath expression: %v", err)
return content, 0, 0, fmt.Errorf("error executing XPath: %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)
}
matchCount := len(nodes)
if matchCount == 0 {
return content, 0, 0, nil
}
// Initialize Lua state
// Initialize Lua
L := lua.NewState()
defer L.Close()
// Setup Lua helper functions
if err := InitLuaHelpers(L); err != nil {
return "", 0, 0, err
// 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)
}
// Register XML-specific helper functions
p.SetupXMLHelpers(L)
// Load helper functions
if err := InitLuaHelpers(L); err != nil {
return content, 0, 0, err
}
// Track modifications
matchCount := len(nodes)
modificationCount := 0
modifiedContent := content
modifications := []ModificationRecord{}
// 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)
// 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))
// 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()
}
// 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 to Lua variables
err = p.ToLua(L, originalValue)
if err != nil {
return content, modCount, matchCount, fmt.Errorf("error converting to Lua: %v", err)
}
// 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
// Execute 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
return content, modCount, matchCount, fmt.Errorf("error executing Lua: %v", err)
}
// 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)
}
// 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
}
// 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)
// 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
}
}
if p.Logger != nil && modificationCount > 0 {
p.Logger.Printf("Made %d XML node modifications", modificationCount)
// 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
}
}
return modifiedContent, modificationCount, matchCount, nil
// Default return empty string
return "", nil
}