Implement xpath (by calling library)
This commit is contained in:
189
processor/xml.go
189
processor/xml.go
@@ -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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user