package processor import ( "fmt" "strings" "github.com/antchfx/xmlquery" lua "github.com/yuin/gopher-lua" ) // XMLProcessor implements the Processor interface for XML documents type XMLProcessor struct{} // 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 }