diff --git a/processor/xml.go b/processor/xml.go
index 9d02435..920d4ef 100644
--- a/processor/xml.go
+++ b/processor/xml.go
@@ -59,68 +59,55 @@ func (p *XMLProcessor) ProcessContent(content string, path string, luaExpr strin
}
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
- // }
- // }
- // }
- // }
+ // Apply modification based on the result
+ if updatedValue, ok := result.(string); ok {
+ // If the result is a simple string, update the node value directly
+ xpath.Set(doc, path, updatedValue)
+ } else if nodeData, ok := result.(map[string]interface{}); ok {
+ // If the result is a map, apply more complex updates
+ updateNodeFromMap(node, nodeData)
+ }
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 "", 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 {
- table, err := ToLua(L, data)
- if err != nil {
- return err
+ // Check if data is an xmlquery.Node
+ node, ok := data.(*xmlquery.Node)
+ if !ok {
+ return fmt.Errorf("expected xmlquery.Node, got %T", data)
}
+
+ // Create a simple table with essential data
+ table := L.NewTable()
+
+ // For element nodes, just provide basic info
+ L.SetField(table, "type", lua.LString(nodeTypeToString(node.Type)))
+ L.SetField(table, "name", lua.LString(node.Data))
+ L.SetField(table, "value", lua.LString(node.InnerText()))
+
+ // Add attributes if any
+ if len(node.Attr) > 0 {
+ attrs := L.NewTable()
+ for _, attr := range node.Attr {
+ L.SetField(attrs, attr.Name.Local, lua.LString(attr.Value))
+ }
+ L.SetField(table, "attributes", attrs)
+ }
+
L.SetGlobal("v", table)
return nil
}
@@ -128,5 +115,149 @@ func (p *XMLProcessor) ToLua(L *lua.LState, data interface{}) error {
// FromLua gets modified values from Lua
func (p *XMLProcessor) FromLua(L *lua.LState) (interface{}, error) {
luaValue := L.GetGlobal("v")
- return FromLua(L, luaValue)
+
+ // Handle string values directly
+ if luaValue.Type() == lua.LTString {
+ return luaValue.String(), nil
+ }
+
+ // Handle tables (for attributes and more complex updates)
+ if luaValue.Type() == lua.LTTable {
+ return luaTableToMap(L, luaValue.(*lua.LTable)), nil
+ }
+
+ return luaValue.String(), nil
+}
+
+// Simple helper to convert a Lua table to a Go map
+func luaTableToMap(L *lua.LState, table *lua.LTable) map[string]interface{} {
+ result := make(map[string]interface{})
+
+ table.ForEach(func(k, v lua.LValue) {
+ if k.Type() == lua.LTString {
+ key := k.String()
+
+ if v.Type() == lua.LTTable {
+ result[key] = luaTableToMap(L, v.(*lua.LTable))
+ } else {
+ result[key] = v.String()
+ }
+ }
+ })
+
+ return result
+}
+
+// Simple helper to convert node type to string
+func nodeTypeToString(nodeType xmlquery.NodeType) string {
+ switch nodeType {
+ case xmlquery.ElementNode:
+ return "element"
+ case xmlquery.TextNode:
+ return "text"
+ case xmlquery.AttributeNode:
+ return "attribute"
+ default:
+ return "other"
+ }
+}
+
+// Helper function to update an XML node from a map
+func updateNodeFromMap(node *xmlquery.Node, data map[string]interface{}) {
+ // Update node value if present
+ if value, ok := data["value"]; ok {
+ if strValue, ok := value.(string); ok {
+ // For element nodes, replace text content
+ if node.Type == xmlquery.ElementNode {
+ // Find the first text child if it exists
+ var textNode *xmlquery.Node
+ for child := node.FirstChild; child != nil; child = child.NextSibling {
+ if child.Type == xmlquery.TextNode {
+ textNode = child
+ break
+ }
+ }
+
+ if textNode != nil {
+ // Update existing text node
+ textNode.Data = strValue
+ } else {
+ // Create new text node
+ newText := &xmlquery.Node{
+ Type: xmlquery.TextNode,
+ Data: strValue,
+ Parent: node,
+ }
+
+ // Insert at beginning of children
+ if node.FirstChild != nil {
+ newText.NextSibling = node.FirstChild
+ node.FirstChild.PrevSibling = newText
+ node.FirstChild = newText
+ } else {
+ node.FirstChild = newText
+ node.LastChild = newText
+ }
+ }
+ } else if node.Type == xmlquery.TextNode {
+ // Directly update text node
+ node.Data = strValue
+ } else if node.Type == xmlquery.AttributeNode {
+ // Update attribute value
+ if node.Parent != nil {
+ for i, attr := range node.Parent.Attr {
+ if attr.Name.Local == node.Data {
+ node.Parent.Attr[i].Value = strValue
+ break
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Update attributes if present
+ if attrs, ok := data["attributes"].(map[string]interface{}); ok && node.Type == xmlquery.ElementNode {
+ for name, value := range attrs {
+ if strValue, ok := value.(string); ok {
+ // Look for existing attribute
+ found := false
+ for i, attr := range node.Attr {
+ if attr.Name.Local == name {
+ node.Attr[i].Value = strValue
+ found = true
+ break
+ }
+ }
+
+ // Add new attribute if not found
+ if !found {
+ node.Attr = append(node.Attr, xmlquery.Attr{
+ Name: struct {
+ Space, Local string
+ }{Local: name},
+ Value: strValue,
+ })
+ }
+ }
+ }
+ }
+}
+
+// Helper function to get a string representation of node type
+func nodeTypeName(nodeType xmlquery.NodeType) string {
+ switch nodeType {
+ case xmlquery.ElementNode:
+ return "element"
+ case xmlquery.TextNode:
+ return "text"
+ case xmlquery.AttributeNode:
+ return "attribute"
+ case xmlquery.CommentNode:
+ return "comment"
+ case xmlquery.DeclarationNode:
+ return "declaration"
+ default:
+ return "unknown"
+ }
}
diff --git a/processor/xml_test.go b/processor/xml_test.go
index 484a77e..fdc8c49 100644
--- a/processor/xml_test.go
+++ b/processor/xml_test.go
@@ -5,6 +5,9 @@ import (
"testing"
"regexp"
+
+ "github.com/antchfx/xmlquery"
+ lua "github.com/yuin/gopher-lua"
)
// Helper function to normalize whitespace for comparison
@@ -56,7 +59,7 @@ func TestXMLProcessor_Process_NodeValues(t *testing.T) {
`
p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//price", "v = v * 2")
+ result, modCount, matchCount, err := p.ProcessContent(content, "//price", "v.value = v.value * 2")
if err != nil {
t.Fatalf("Error processing content: %v", err)
@@ -1530,3 +1533,267 @@ func TestXMLProcessor_Process_DeepPathNavigation(t *testing.T) {
// Add more test cases for specific XML manipulation scenarios below
// These tests would cover additional functionality as the implementation progresses
+
+func TestXMLToLua(t *testing.T) {
+ // Sample XML to test with
+ xmlStr := `
+
+
+
+ 123 Main St
+ Anytown
+ 12345
+
+ john@example.com
+
+
+
+ 456 Business Ave
+ Worktown
+ 54321
+
+ 555-1234
+
+
+ `
+
+ // Parse the XML
+ doc, err := xmlquery.Parse(strings.NewReader(xmlStr))
+ if err != nil {
+ t.Fatalf("Failed to parse XML: %v", err)
+ }
+
+ // Create a new Lua state
+ L := lua.NewState()
+ defer L.Close()
+
+ // Create an XML processor
+ processor := &XMLProcessor{}
+
+ // Test converting the root element to Lua
+ t.Run("RootElement", func(t *testing.T) {
+ // Find the root element
+ root := doc.SelectElement("root")
+ if root == nil {
+ t.Fatal("Failed to find root element")
+ }
+
+ // Convert to Lua
+ err := processor.ToLua(L, root)
+ if err != nil {
+ t.Fatalf("Failed to convert to Lua: %v", err)
+ }
+
+ // Verify the result
+ luaTable := L.GetGlobal("v")
+ if luaTable.Type() != lua.LTTable {
+ t.Fatalf("Expected table, got %s", luaTable.Type().String())
+ }
+
+ // Check element type
+ typeVal := L.GetField(luaTable, "type")
+ if typeVal.String() != "element" {
+ t.Errorf("Expected type 'element', got '%s'", typeVal.String())
+ }
+
+ // Check name
+ nameVal := L.GetField(luaTable, "name")
+ if nameVal.String() != "root" {
+ t.Errorf("Expected name 'root', got '%s'", nameVal.String())
+ }
+
+ // Check attributes
+ attrsTable := L.GetField(luaTable, "attributes")
+ if attrsTable.Type() != lua.LTTable {
+ t.Fatalf("Expected attributes table, got %s", attrsTable.Type().String())
+ }
+
+ idVal := L.GetField(attrsTable, "id")
+ if idVal.String() != "1" {
+ t.Errorf("Expected id '1', got '%s'", idVal.String())
+ }
+
+ // Check that we have children
+ childrenTable := L.GetField(luaTable, "children")
+ if childrenTable.Type() != lua.LTTable {
+ t.Fatalf("Expected children table, got %s", childrenTable.Type().String())
+ }
+ })
+
+ // Test converting a nested element to Lua
+ t.Run("NestedElement", func(t *testing.T) {
+ // Find a nested element
+ street := doc.SelectElement("//street")
+ if street == nil {
+ t.Fatal("Failed to find street element")
+ }
+
+ // Convert to Lua
+ err := processor.ToLua(L, street)
+ if err != nil {
+ t.Fatalf("Failed to convert to Lua: %v", err)
+ }
+
+ // Verify the result
+ luaTable := L.GetGlobal("v")
+ if luaTable.Type() != lua.LTTable {
+ t.Fatalf("Expected table, got %s", luaTable.Type().String())
+ }
+
+ // Check element type
+ typeVal := L.GetField(luaTable, "type")
+ if typeVal.String() != "element" {
+ t.Errorf("Expected type 'element', got '%s'", typeVal.String())
+ }
+
+ // Check name
+ nameVal := L.GetField(luaTable, "name")
+ if nameVal.String() != "street" {
+ t.Errorf("Expected name 'street', got '%s'", nameVal.String())
+ }
+
+ // Check value
+ valueVal := L.GetField(luaTable, "value")
+ if valueVal.String() != "123 Main St" {
+ t.Errorf("Expected value '123 Main St', got '%s'", valueVal.String())
+ }
+ })
+
+ // Test FromLua with a simple string update
+ t.Run("FromLuaString", func(t *testing.T) {
+ // Set up a Lua state with a string value
+ L := lua.NewState()
+ defer L.Close()
+ L.SetGlobal("v", lua.LString("New Value"))
+
+ // Convert from Lua
+ result, err := processor.FromLua(L)
+ if err != nil {
+ t.Fatalf("Failed to convert from Lua: %v", err)
+ }
+
+ // Verify the result
+ strResult, ok := result.(string)
+ if !ok {
+ t.Fatalf("Expected string result, got %T", result)
+ }
+
+ if strResult != "New Value" {
+ t.Errorf("Expected 'New Value', got '%s'", strResult)
+ }
+ })
+
+ // Test FromLua with a complex table update
+ t.Run("FromLuaTable", func(t *testing.T) {
+ // Set up a Lua state with a table value
+ L := lua.NewState()
+ defer L.Close()
+
+ table := L.NewTable()
+ L.SetField(table, "value", lua.LString("Updated Text"))
+
+ attrTable := L.NewTable()
+ L.SetField(attrTable, "id", lua.LString("new-id"))
+ L.SetField(attrTable, "class", lua.LString("highlight"))
+
+ L.SetField(table, "attributes", attrTable)
+ L.SetGlobal("v", table)
+
+ // Convert from Lua
+ result, err := processor.FromLua(L)
+ if err != nil {
+ t.Fatalf("Failed to convert from Lua: %v", err)
+ }
+
+ // Verify the result
+ mapResult, ok := result.(map[string]interface{})
+ if !ok {
+ t.Fatalf("Expected map result, got %T", result)
+ }
+
+ // Check value
+ if value, ok := mapResult["value"]; !ok || value != "Updated Text" {
+ t.Errorf("Expected value 'Updated Text', got '%v'", value)
+ }
+
+ // Check attributes
+ attrs, ok := mapResult["attributes"].(map[string]interface{})
+ if !ok {
+ t.Fatalf("Expected attributes map, got %T", mapResult["attributes"])
+ }
+
+ if id, ok := attrs["id"]; !ok || id != "new-id" {
+ t.Errorf("Expected id 'new-id', got '%v'", id)
+ }
+
+ if class, ok := attrs["class"]; !ok || class != "highlight" {
+ t.Errorf("Expected class 'highlight', got '%v'", class)
+ }
+ })
+
+ // Test updateNodeFromMap with a simple value update
+ t.Run("UpdateNodeValue", func(t *testing.T) {
+ // Create a simple element to update
+ xmlStr := `Original Text`
+ doc, _ := xmlquery.Parse(strings.NewReader(xmlStr))
+ node := doc.SelectElement("test")
+
+ // Create update data
+ updateData := map[string]interface{}{
+ "value": "Updated Text",
+ }
+
+ // Update the node
+ updateNodeFromMap(node, updateData)
+
+ // Verify the update
+ if node.InnerText() != "Updated Text" {
+ t.Errorf("Expected value 'Updated Text', got '%s'", node.InnerText())
+ }
+ })
+
+ // Test updateNodeFromMap with attribute updates
+ t.Run("UpdateNodeAttributes", func(t *testing.T) {
+ // Create an element with attributes
+ xmlStr := `Text`
+ doc, _ := xmlquery.Parse(strings.NewReader(xmlStr))
+ node := doc.SelectElement("test")
+
+ // Create update data
+ updateData := map[string]interface{}{
+ "attributes": map[string]interface{}{
+ "id": "new",
+ "class": "added",
+ },
+ }
+
+ // Update the node
+ updateNodeFromMap(node, updateData)
+
+ // Verify the id attribute was updated
+ idFound := false
+ classFound := false
+ for _, attr := range node.Attr {
+ if attr.Name.Local == "id" {
+ idFound = true
+ if attr.Value != "new" {
+ t.Errorf("Expected id 'new', got '%s'", attr.Value)
+ }
+ }
+ if attr.Name.Local == "class" {
+ classFound = true
+ if attr.Value != "added" {
+ t.Errorf("Expected class 'added', got '%s'", attr.Value)
+ }
+ }
+ }
+
+ if !idFound {
+ t.Error("Expected to find 'id' attribute but didn't")
+ }
+
+ if !classFound {
+ t.Error("Expected to find 'class' attribute but didn't")
+ }
+ })
+}