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 `