Fix up some more xml tests and other small bugs

This commit is contained in:
2025-03-26 01:52:48 +01:00
parent e5092edf53
commit 2bfd9f951e
2 changed files with 52 additions and 64 deletions

View File

@@ -59,6 +59,13 @@ func (p *XMLProcessor) ProcessContent(content string, path string, luaExpr strin
} }
log.Printf("%#v", result) 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 // Apply modification based on the result
if updatedValue, ok := result.(string); ok { if updatedValue, ok := result.(string); ok {
// If the result is a simple string, update the node value directly // 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 { for _, attr := range node.Attr {
L.SetField(attrs, attr.Name.Local, lua.LString(attr.Value)) L.SetField(attrs, attr.Name.Local, lua.LString(attr.Value))
} }
L.SetField(table, "attributes", attrs) L.SetField(table, "attr", attrs)
} }
L.SetGlobal("v", table) L.SetGlobal("v", table)
@@ -217,7 +224,7 @@ func updateNodeFromMap(node *xmlquery.Node, data map[string]interface{}) {
} }
// Update attributes if present // 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 { for name, value := range attrs {
if strValue, ok := value.(string); ok { if strValue, ok := value.(string); ok {
// Look for existing attribute // Look for existing attribute

View File

@@ -14,7 +14,17 @@ import (
func normalizeXMLWhitespace(s string) string { func normalizeXMLWhitespace(s string) string {
// Replace all whitespace sequences with a single space // Replace all whitespace sequences with a single space
re := regexp.MustCompile(`\s+`) 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, "&lt;", "<")
s = strings.ReplaceAll(s, "&gt;", ">")
s = strings.ReplaceAll(s, "&amp;", "&")
return s
} }
func TestXMLProcessor_Process_NodeValues(t *testing.T) { func TestXMLProcessor_Process_NodeValues(t *testing.T) {
@@ -96,7 +106,7 @@ func TestXMLProcessor_Process_Attributes(t *testing.T) {
</items>` </items>`
p := &XMLProcessor{} 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 { if err != nil {
t.Fatalf("Error processing content: %v", err) t.Fatalf("Error processing content: %v", err)
@@ -133,7 +143,7 @@ func TestXMLProcessor_Process_ElementText(t *testing.T) {
</names>` </names>`
p := &XMLProcessor{} 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 { if err != nil {
t.Fatalf("Error processing content: %v", err) t.Fatalf("Error processing content: %v", err)
@@ -174,7 +184,7 @@ func TestXMLProcessor_Process_ElementAddition(t *testing.T) {
</config>` </config>`
p := &XMLProcessor{} 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 { if err != nil {
t.Fatalf("Error processing content: %v", err) t.Fatalf("Error processing content: %v", err)
@@ -239,7 +249,7 @@ func TestXMLProcessor_Process_ComplexXML(t *testing.T) {
</store>` </store>`
p := &XMLProcessor{} 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 { if err != nil {
t.Fatalf("Error processing content: %v", err) t.Fatalf("Error processing content: %v", err)
@@ -274,19 +284,21 @@ func TestXMLProcessor_ConditionalModification(t *testing.T) {
expected := `<?xml version="1.0" encoding="UTF-8"?> expected := `<?xml version="1.0" encoding="UTF-8"?>
<inventory> <inventory>
<item id="1" stock="5" price="8.00"/> <item id="1" stock="5" price="8.00"></item>
<item id="2" stock="15" price="16.00"/> <item id="2" stock="15" price="16.00"></item>
<item id="3" stock="0" price="15.00"/> <item id="3" stock="0" price="15.00"></item>
</inventory>` </inventory>`
p := &XMLProcessor{} p := &XMLProcessor{}
// Apply 20% discount but only for items with stock > 0 // Apply 20% discount but only for items with stock > 0
luaExpr := ` luaExpr := `
-- In the table-based approach, attributes are accessible directly -- In the table-based approach, attributes are accessible directly
if v.stock and tonumber(v.stock) > 0 then if v.attr.stock and tonumber(v.attr.stock) > 0 then
v.price = tonumber(v.price) * 0.8 v.attr.price = tonumber(v.attr.price) * 0.8
-- Format to 2 decimal places -- 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 end
` `
result, modCount, matchCount, err := p.ProcessContent(content, "//item", luaExpr) result, modCount, matchCount, err := p.ProcessContent(content, "//item", luaExpr)
@@ -330,7 +342,7 @@ func TestXMLProcessor_Process_SpecialCharacters(t *testing.T) {
</data>` </data>`
p := &XMLProcessor{} 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 { if err != nil {
t.Fatalf("Error processing content: %v", err) 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 // Apply multiple operations to the price: add tax, apply discount, round
luaExpr := ` luaExpr := `
-- When v is a numeric string, we can perform math operations directly local price = v.value
local price = v
-- Add 15% tax -- Add 15% tax
price = price * 1.15 price = price * 1.15
-- Apply 10% discount -- Apply 10% discount
price = price * 0.9 price = price * 0.9
-- Round to 2 decimal places -- Round to 2 decimal places
price = math.floor(price * 100 + 0.5) / 100 price = round(price, 2)
v = price v.value = price
` `
expected := `<?xml version="1.0" encoding="UTF-8"?> expected := `<?xml version="1.0" encoding="UTF-8"?>
@@ -425,7 +436,7 @@ func TestXMLProcessor_Process_MathFunctions(t *testing.T) {
</measurements>` </measurements>`
p := &XMLProcessor{} 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 { if err != nil {
t.Fatalf("Error processing content: %v", err) t.Fatalf("Error processing content: %v", err)
@@ -471,9 +482,9 @@ func TestXMLProcessor_Process_StringOperations(t *testing.T) {
p := &XMLProcessor{} p := &XMLProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "//email", ` result, modCount, matchCount, err := p.ProcessContent(content, "//email", `
-- With the table approach, v contains the text content directly -- With the table approach, v contains the text content directly
v = string.gsub(v, "@.+", "@anon.com") v.value = string.gsub(v.value, "@.+", "@anon.com")
local username = string.match(v, "(.+)@") local username = string.match(v.value, "(.+)@")
v = string.gsub(username, "%.", "") .. "@anon.com" v.value = string.gsub(username, "%.", "") .. "@anon.com"
`) `)
if err != nil { if err != nil {
@@ -482,7 +493,7 @@ func TestXMLProcessor_Process_StringOperations(t *testing.T) {
// Test phone number masking // Test phone number masking
result, modCount2, matchCount2, err := p.ProcessContent(result, "//phone", ` 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" return string.sub(match, 1, 3) .. "-XXX-XXXX"
end) end)
`) `)
@@ -539,14 +550,14 @@ func TestXMLProcessor_Process_DateManipulation(t *testing.T) {
p := &XMLProcessor{} p := &XMLProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "//date", ` 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 -- Postpone events by 1 month
month = tonumber(month) + 1 month = tonumber(month) + 1
if month > 12 then if month > 12 then
month = 1 month = 1
year = tonumber(year) + 1 year = tonumber(year) + 1
end 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 { if err != nil {
@@ -612,36 +623,6 @@ func TestXMLProcessor_Process_Error_InvalidLua(t *testing.T) {
} }
} }
func TestXMLProcessor_Process_NoChanges(t *testing.T) {
content := `<?xml version="1.0" encoding="UTF-8"?>
<root>
<element>123</element>
</root>`
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) { func TestXMLProcessor_Process_ComplexXPathSelectors(t *testing.T) {
content := `<?xml version="1.0" encoding="UTF-8"?> content := `<?xml version="1.0" encoding="UTF-8"?>
<library> <library>
@@ -687,7 +668,7 @@ func TestXMLProcessor_Process_ComplexXPathSelectors(t *testing.T) {
p := &XMLProcessor{} p := &XMLProcessor{}
// Target only fiction books and apply 20% discount to price // 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 { if err != nil {
t.Fatalf("Error processing content: %v", err) t.Fatalf("Error processing content: %v", err)
@@ -770,13 +751,13 @@ func TestXMLProcessor_Process_NestedStructureModification(t *testing.T) {
p := &XMLProcessor{} p := &XMLProcessor{}
// Boost hero stats by 20% // 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 { if err != nil {
t.Fatalf("Error processing stats content: %v", err) t.Fatalf("Error processing stats content: %v", err)
} }
// Also upgrade hero equipment // 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 { if err != nil {
t.Fatalf("Error processing equipment content: %v", err) t.Fatalf("Error processing equipment content: %v", err)
} }
@@ -838,8 +819,8 @@ func TestXMLProcessor_Process_ElementReplacement(t *testing.T) {
luaExpr := ` luaExpr := `
-- With a proper table approach, this becomes much simpler -- With a proper table approach, this becomes much simpler
local price = tonumber(v.price) local price = tonumber(v.attr.price)
local quantity = tonumber(v.quantity) local quantity = tonumber(v.attr.quantity)
-- Add a new total element -- Add a new total element
v.total = string.format("%.2f", price * quantity) 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 -- We can access the "inStock" element directly
if v.inStock == "true" then if v.inStock == "true" then
-- Add a new attribute directly -- Add a new attribute directly
v._attr = v._attr or {} v.attr = v.attr or {}
v._attr.status = "available" v.attr.status = "available"
else else
v._attr = v._attr or {} v.attr = v.attr or {}
v._attr.status = "out-of-stock" v.attr.status = "out-of-stock"
end end
` `