Implement xpath (by calling library)

This commit is contained in:
2025-03-25 23:47:14 +01:00
parent 73d93367a0
commit e31c0e4e8f
7 changed files with 449 additions and 538 deletions

View File

@@ -2,6 +2,8 @@ package processor
import (
"fmt"
"log"
"modify/processor/xpath"
"strings"
"github.com/antchfx/xmlquery"
@@ -12,15 +14,17 @@ import (
type XMLProcessor struct{}
// ProcessContent implements the Processor interface for XMLProcessor
func (p *XMLProcessor) ProcessContent(content string, pattern string, luaExpr string) (string, int, int, error) {
func (p *XMLProcessor) ProcessContent(content string, path string, luaExpr string) (string, int, int, error) {
// Parse XML document
// We can't really use encoding/xml here because it requires a pre defined struct
// And we HAVE TO parse dynamic unknown XML
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)
nodes, err := xpath.Get(doc, path)
if err != nil {
return content, 0, 0, fmt.Errorf("error executing XPath: %v", err)
}
@@ -30,158 +34,99 @@ func (p *XMLProcessor) ProcessContent(content string, pattern string, luaExpr st
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()
L, err := NewLuaState()
if err != nil {
return content, 0, 0, fmt.Errorf("error creating Lua state: %v", err)
}
defer L.Close()
// Convert to Lua variables
err = p.ToLua(L, originalValue)
err = p.ToLua(L, node)
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 {
err = L.DoString(BuildLuaScript(luaExpr))
if 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
}
log.Printf("%#v", result)
// 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
}
}
}
}
// 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
}
// 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
// return doc.OutputXML(true), modCount, matchCount, nil
return "", 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)
table, err := ToLua(L, data)
if err != nil {
return err
}
// 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)
}
L.SetGlobal("v", table)
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
luaValue := L.GetGlobal("v")
return FromLua(L, luaValue)
}