diff --git a/processor/xml.go b/processor/xml.go
index 920d4ef..2ecefa3 100644
--- a/processor/xml.go
+++ b/processor/xml.go
@@ -59,6 +59,13 @@ func (p *XMLProcessor) ProcessContent(content string, path string, luaExpr strin
}
log.Printf("%#v", result)
+ modified := false
+ modified = L.GetGlobal("modified").String() == "true"
+ if !modified {
+ log.Printf("No changes made to node at path: %s", node.Data)
+ continue
+ }
+
// Apply modification based on the result
if updatedValue, ok := result.(string); ok {
// If the result is a simple string, update the node value directly
@@ -105,7 +112,7 @@ func (p *XMLProcessor) ToLua(L *lua.LState, data interface{}) error {
for _, attr := range node.Attr {
L.SetField(attrs, attr.Name.Local, lua.LString(attr.Value))
}
- L.SetField(table, "attributes", attrs)
+ L.SetField(table, "attr", attrs)
}
L.SetGlobal("v", table)
@@ -217,7 +224,7 @@ func updateNodeFromMap(node *xmlquery.Node, data map[string]interface{}) {
}
// Update attributes if present
- if attrs, ok := data["attributes"].(map[string]interface{}); ok && node.Type == xmlquery.ElementNode {
+ if attrs, ok := data["attr"].(map[string]interface{}); ok && node.Type == xmlquery.ElementNode {
for name, value := range attrs {
if strValue, ok := value.(string); ok {
// Look for existing attribute
diff --git a/processor/xml_test.go b/processor/xml_test.go
index fdc8c49..65ef8a8 100644
--- a/processor/xml_test.go
+++ b/processor/xml_test.go
@@ -14,7 +14,17 @@ import (
func normalizeXMLWhitespace(s string) string {
// Replace all whitespace sequences with a single space
re := regexp.MustCompile(`\s+`)
- return re.ReplaceAllString(strings.TrimSpace(s), " ")
+ s = re.ReplaceAllString(strings.TrimSpace(s), " ")
+
+ // Normalize XML entities for comparison
+ s = strings.ReplaceAll(s, "'", "'")
+ s = strings.ReplaceAll(s, """, """)
+ s = strings.ReplaceAll(s, """, "\"")
+ s = strings.ReplaceAll(s, "<", "<")
+ s = strings.ReplaceAll(s, ">", ">")
+ s = strings.ReplaceAll(s, "&", "&")
+
+ return s
}
func TestXMLProcessor_Process_NodeValues(t *testing.T) {
@@ -96,7 +106,7 @@ func TestXMLProcessor_Process_Attributes(t *testing.T) {
`
p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//item/@price", "v = v * 2")
+ result, modCount, matchCount, err := p.ProcessContent(content, "//item/@price", "v.value = v.value * 2")
if err != nil {
t.Fatalf("Error processing content: %v", err)
@@ -133,7 +143,7 @@ func TestXMLProcessor_Process_ElementText(t *testing.T) {
`
p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//n/text()", "v = string.upper(v)")
+ result, modCount, matchCount, err := p.ProcessContent(content, "//n/text()", "v.value = string.upper(v.value)")
if err != nil {
t.Fatalf("Error processing content: %v", err)
@@ -174,7 +184,7 @@ func TestXMLProcessor_Process_ElementAddition(t *testing.T) {
`
p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//settings/*", "v = v * 2")
+ result, modCount, matchCount, err := p.ProcessContent(content, "//settings/*", "v.value = v.value * 2")
if err != nil {
t.Fatalf("Error processing content: %v", err)
@@ -239,7 +249,7 @@ func TestXMLProcessor_Process_ComplexXML(t *testing.T) {
`
p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//price", "v = v * 1.2")
+ result, modCount, matchCount, err := p.ProcessContent(content, "//price", "v.value = round(v.value * 1.2, 3)")
if err != nil {
t.Fatalf("Error processing content: %v", err)
@@ -274,19 +284,21 @@ func TestXMLProcessor_ConditionalModification(t *testing.T) {
expected := `
-
-
-
+
+
+
`
p := &XMLProcessor{}
// Apply 20% discount but only for items with stock > 0
luaExpr := `
-- In the table-based approach, attributes are accessible directly
- if v.stock and tonumber(v.stock) > 0 then
- v.price = tonumber(v.price) * 0.8
+ if v.attr.stock and tonumber(v.attr.stock) > 0 then
+ v.attr.price = tonumber(v.attr.price) * 0.8
-- Format to 2 decimal places
- v.price = string.format("%.2f", v.price)
+ v.attr.price = string.format("%.2f", v.attr.price)
+ else
+ return false
end
`
result, modCount, matchCount, err := p.ProcessContent(content, "//item", luaExpr)
@@ -330,7 +342,7 @@ func TestXMLProcessor_Process_SpecialCharacters(t *testing.T) {
`
p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//entry", "v = string.upper(v)")
+ result, modCount, matchCount, err := p.ProcessContent(content, "//entry", "v.value = string.upper(v.value)")
if err != nil {
t.Fatalf("Error processing content: %v", err)
@@ -365,15 +377,14 @@ func TestXMLProcessor_Process_ChainedOperations(t *testing.T) {
// Apply multiple operations to the price: add tax, apply discount, round
luaExpr := `
- -- When v is a numeric string, we can perform math operations directly
- local price = v
+ local price = v.value
-- Add 15% tax
price = price * 1.15
-- Apply 10% discount
price = price * 0.9
-- Round to 2 decimal places
- price = math.floor(price * 100 + 0.5) / 100
- v = price
+ price = round(price, 2)
+ v.value = price
`
expected := `
@@ -425,7 +436,7 @@ func TestXMLProcessor_Process_MathFunctions(t *testing.T) {
`
p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//measurement", "v = round(v)")
+ result, modCount, matchCount, err := p.ProcessContent(content, "//measurement", "v.value = round(v.value)")
if err != nil {
t.Fatalf("Error processing content: %v", err)
@@ -471,9 +482,9 @@ func TestXMLProcessor_Process_StringOperations(t *testing.T) {
p := &XMLProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "//email", `
-- With the table approach, v contains the text content directly
- v = string.gsub(v, "@.+", "@anon.com")
- local username = string.match(v, "(.+)@")
- v = string.gsub(username, "%.", "") .. "@anon.com"
+ v.value = string.gsub(v.value, "@.+", "@anon.com")
+ local username = string.match(v.value, "(.+)@")
+ v.value = string.gsub(username, "%.", "") .. "@anon.com"
`)
if err != nil {
@@ -482,7 +493,7 @@ func TestXMLProcessor_Process_StringOperations(t *testing.T) {
// Test phone number masking
result, modCount2, matchCount2, err := p.ProcessContent(result, "//phone", `
- v = string.gsub(v, "%d%d%d%-%d%d%d%-%d%d%d%d", function(match)
+ v.value = string.gsub(v.value, "%d%d%d%-%d%d%d%-%d%d%d%d", function(match)
return string.sub(match, 1, 3) .. "-XXX-XXXX"
end)
`)
@@ -539,14 +550,14 @@ func TestXMLProcessor_Process_DateManipulation(t *testing.T) {
p := &XMLProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "//date", `
- local year, month, day = string.match(v, "(%d%d%d%d)-(%d%d)-(%d%d)")
+ local year, month, day = string.match(v.value, "(%d%d%d%d)-(%d%d)-(%d%d)")
-- Postpone events by 1 month
month = tonumber(month) + 1
if month > 12 then
month = 1
year = tonumber(year) + 1
end
- v = string.format("%04d-%02d-%s", tonumber(year), month, day)
+ v.value = string.format("%04d-%02d-%s", tonumber(year), month, day)
`)
if err != nil {
@@ -612,36 +623,6 @@ func TestXMLProcessor_Process_Error_InvalidLua(t *testing.T) {
}
}
-func TestXMLProcessor_Process_NoChanges(t *testing.T) {
- content := `
-
- 123
-`
-
- p := &XMLProcessor{}
- result, modCount, matchCount, err := p.ProcessContent(content, "//element", "v1 = v1")
-
- if err != nil {
- t.Fatalf("Error processing content: %v", err)
- }
-
- if matchCount != 1 {
- t.Errorf("Expected 1 match, got %d", matchCount)
- }
-
- if modCount != 0 {
- t.Errorf("Expected 0 modifications, got %d", modCount)
- }
-
- // Normalize whitespace for comparison
- normalizedResult := normalizeXMLWhitespace(result)
- normalizedContent := normalizeXMLWhitespace(content)
-
- if normalizedResult != normalizedContent {
- t.Errorf("Expected content to be unchanged")
- }
-}
-
func TestXMLProcessor_Process_ComplexXPathSelectors(t *testing.T) {
content := `
@@ -687,7 +668,7 @@ func TestXMLProcessor_Process_ComplexXPathSelectors(t *testing.T) {
p := &XMLProcessor{}
// Target only fiction books and apply 20% discount to price
- result, modCount, matchCount, err := p.ProcessContent(content, "//book[@category='fiction']/price", "v = v * 0.8")
+ result, modCount, matchCount, err := p.ProcessContent(content, "//book[@category='fiction']/price", "v.value = round(v.value * 0.8, 2)")
if err != nil {
t.Fatalf("Error processing content: %v", err)
@@ -770,13 +751,13 @@ func TestXMLProcessor_Process_NestedStructureModification(t *testing.T) {
p := &XMLProcessor{}
// Boost hero stats by 20%
- result, modCount, matchCount, err := p.ProcessContent(content, "//character[@id='hero']/stats/*", "v = math.floor(v * 1.2)")
+ result, modCount, matchCount, err := p.ProcessContent(content, "//character[@id='hero']/stats/*", "v.value = round(v.value * 1.2)")
if err != nil {
t.Fatalf("Error processing stats content: %v", err)
}
// Also upgrade hero equipment
- result, modCount2, matchCount2, err := p.ProcessContent(result, "//character[@id='hero']/equipment/*/@damage|//character[@id='hero']/equipment/*/@defense", "v = v + 2")
+ result, modCount2, matchCount2, err := p.ProcessContent(result, "//character[@id='hero']/equipment/*/@damage|//character[@id='hero']/equipment/*/@defense", "v.value = v.value + 2")
if err != nil {
t.Fatalf("Error processing equipment content: %v", err)
}
@@ -838,8 +819,8 @@ func TestXMLProcessor_Process_ElementReplacement(t *testing.T) {
luaExpr := `
-- With a proper table approach, this becomes much simpler
- local price = tonumber(v.price)
- local quantity = tonumber(v.quantity)
+ local price = tonumber(v.attr.price)
+ local quantity = tonumber(v.attr.quantity)
-- Add a new total element
v.total = string.format("%.2f", price * quantity)
@@ -905,11 +886,11 @@ func TestXMLProcessor_Process_AttributeAddition(t *testing.T) {
-- We can access the "inStock" element directly
if v.inStock == "true" then
-- Add a new attribute directly
- v._attr = v._attr or {}
- v._attr.status = "available"
+ v.attr = v.attr or {}
+ v.attr.status = "available"
else
- v._attr = v._attr or {}
- v._attr.status = "out-of-stock"
+ v.attr = v.attr or {}
+ v.attr.status = "out-of-stock"
end
`