package processor
import (
"strings"
"testing"
"regexp"
)
// Helper function to normalize whitespace for comparison
func normalizeXMLWhitespace(s string) string {
// Replace all whitespace sequences with a single space
re := regexp.MustCompile(`\s+`)
return re.ReplaceAllString(strings.TrimSpace(s), " ")
}
func TestXMLProcessor_Process_NodeValues(t *testing.T) {
content := `
Gambardella, Matthew
XML Developer's Guide
Computer
44.95
2000-10-01
An in-depth look at creating applications with XML.
Ralls, Kim
Midnight Rain
Fantasy
5.95
2000-12-16
A former architect battles corporate zombies.
`
expected := `
Gambardella, Matthew
XML Developer's Guide
Computer
89.9
2000-10-01
An in-depth look at creating applications with XML.
Ralls, Kim
Midnight Rain
Fantasy
11.9
2000-12-16
A former architect battles corporate zombies.
`
p := &XMLProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "//price", "v = v * 2")
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 2 {
t.Errorf("Expected 2 matches, got %d", matchCount)
}
if modCount != 2 {
t.Errorf("Expected 2 modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_Attributes(t *testing.T) {
content := `
- Widget A
- Widget B
`
expected := `
- Widget A
- Widget B
`
p := &XMLProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "//item/@price", "v = v * 2")
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 2 {
t.Errorf("Expected 2 matches, got %d", matchCount)
}
if modCount != 2 {
t.Errorf("Expected 2 modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_ElementText(t *testing.T) {
content := `
john
mary
`
expected := `
JOHN
MARY
`
p := &XMLProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "//n/text()", "v = string.upper(v)")
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 2 {
t.Errorf("Expected 2 matches, got %d", matchCount)
}
if modCount != 2 {
t.Errorf("Expected 2 modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_ElementAddition(t *testing.T) {
content := `
30
100
`
expected := `
60
200
`
p := &XMLProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "//settings/*", "v = v * 2")
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 2 {
t.Errorf("Expected 2 matches, got %d", matchCount)
}
if modCount != 2 {
t.Errorf("Expected 2 modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_ComplexXML(t *testing.T) {
content := `
Laptop
999.99
Smartphone
499.99
T-Shirt
19.99
`
expected := `
Laptop
1199.988
Smartphone
599.988
T-Shirt
23.988
`
p := &XMLProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "//price", "v = v * 1.2")
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 3 {
t.Errorf("Expected 3 matches, got %d", matchCount)
}
if modCount != 3 {
t.Errorf("Expected 3 modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
// New tests added below
func TestXMLProcessor_ConditionalModification(t *testing.T) {
content := `
`
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
-- Format to 2 decimal places
v.price = string.format("%.2f", v.price)
end
`
result, modCount, matchCount, err := p.ProcessContent(content, "//item", luaExpr)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 3 {
t.Errorf("Expected 3 matches, got %d", matchCount)
}
if modCount != 2 {
t.Errorf("Expected 2 modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_SpecialCharacters(t *testing.T) {
content := `
This & that
a < b
c > d
Quote: "Hello"
`
expected := `
THIS & THAT
A < B
C > D
QUOTE: "HELLO"
`
p := &XMLProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "//entry", "v = string.upper(v)")
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 4 {
t.Errorf("Expected 4 matches, got %d", matchCount)
}
if modCount != 4 {
t.Errorf("Expected 4 modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_ChainedOperations(t *testing.T) {
content := `
Widget
100
20
`
// 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
-- 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
`
expected := `
Widget
103.5
20
`
p := &XMLProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "//price", luaExpr)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 1 {
t.Errorf("Expected 1 match, got %d", matchCount)
}
if modCount != 1 {
t.Errorf("Expected 1 modification, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_MathFunctions(t *testing.T) {
content := `
3.14159
2.71828
1.41421
`
expected := `
3
3
1
`
p := &XMLProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "//measurement", "v = round(v)")
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 3 {
t.Errorf("Expected 3 matches, got %d", matchCount)
}
if modCount != 3 {
t.Errorf("Expected 3 modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_StringOperations(t *testing.T) {
content := `
John Doe
john.doe@example.com
123-456-7890
`
expected := `
John Doe
johndoe@anon.com
123-XXX-XXXX
`
// Test email anonymization
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"
`)
if err != nil {
t.Fatalf("Error processing email content: %v", err)
}
// 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)
return string.sub(match, 1, 3) .. "-XXX-XXXX"
end)
`)
if err != nil {
t.Fatalf("Error processing phone content: %v", err)
}
// Total counts from both operations
matchCount += matchCount2
modCount += modCount2
if matchCount != 2 {
t.Errorf("Expected 2 total matches, got %d", matchCount)
}
if modCount != 2 {
t.Errorf("Expected 2 total modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_DateManipulation(t *testing.T) {
content := `
Conference
2023-06-15
Workshop
2023-06-20
`
expected := `
Conference
2023-07-15
Workshop
2023-07-20
`
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)")
-- 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)
`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 2 {
t.Errorf("Expected 2 matches, got %d", matchCount)
}
if modCount != 2 {
t.Errorf("Expected 2 modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_Error_InvalidXML(t *testing.T) {
content := `
`
p := &XMLProcessor{}
_, _, _, err := p.ProcessContent(content, "//unclosed", "v1=v1")
if err == nil {
t.Errorf("Expected an error for invalid XML, but got none")
}
}
func TestXMLProcessor_Process_Error_InvalidXPath(t *testing.T) {
content := `
value
`
p := &XMLProcessor{}
_, _, _, err := p.ProcessContent(content, "[invalid xpath]", "v1=v1")
if err == nil {
t.Errorf("Expected an error for invalid XPath, but got none")
}
}
func TestXMLProcessor_Process_Error_InvalidLua(t *testing.T) {
content := `
123
`
p := &XMLProcessor{}
_, _, _, err := p.ProcessContent(content, "//element", "v1 = invalid_function()")
if err == nil {
t.Errorf("Expected an error for invalid Lua, but got none")
}
}
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 := `
The Imaginary World
Alice Johnson
19.99
History of Science
Bob Smith
29.99
Future Tales
Charlie Adams
24.99
`
expected := `
The Imaginary World
Alice Johnson
15.99
History of Science
Bob Smith
29.99
Future Tales
Charlie Adams
19.99
`
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")
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 2 {
t.Errorf("Expected 2 matches, got %d", matchCount)
}
if modCount != 2 {
t.Errorf("Expected 2 modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_NestedStructureModification(t *testing.T) {
content := `
10
15
12
Sword
Leather
14
10
16
Axe
Chain Mail
`
expected := `
12
18
14
Sword
Leather
14
10
16
Axe
Chain Mail
`
p := &XMLProcessor{}
// Boost hero stats by 20%
result, modCount, matchCount, err := p.ProcessContent(content, "//character[@id='hero']/stats/*", "v = math.floor(v * 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")
if err != nil {
t.Fatalf("Error processing equipment content: %v", err)
}
totalMatches := matchCount + matchCount2
totalMods := modCount + modCount2
if totalMatches != 5 { // 3 stats + 2 equipment attributes
t.Errorf("Expected 5 total matches, got %d", totalMatches)
}
if totalMods != 5 { // 3 stats + 2 equipment attributes
t.Errorf("Expected 5 total modifications, got %d", totalMods)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_ElementReplacement(t *testing.T) {
content := `
-
Apple
1.99
10
-
Carrot
0.99
5
`
expected := `
-
Apple
1.99
10
19.90
-
Carrot
0.99
5
4.95
`
// This test demonstrates using variables from multiple elements to calculate a new value
// With the table approach, we can directly access child elements
p := &XMLProcessor{}
luaExpr := `
-- With a proper table approach, this becomes much simpler
local price = tonumber(v.price)
local quantity = tonumber(v.quantity)
-- Add a new total element
v.total = string.format("%.2f", price * quantity)
`
result, modCount, matchCount, err := p.ProcessContent(content, "//item", luaExpr)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 2 {
t.Errorf("Expected 2 matches, got %d", matchCount)
}
if modCount != 2 {
t.Errorf("Expected 2 modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_AttributeAddition(t *testing.T) {
content := `
Laptop
999.99
true
Phone
499.99
false
`
expected := `
Laptop
999.99
true
Phone
499.99
false
`
// This test demonstrates adding a new attribute based on element content
p := &XMLProcessor{}
luaExpr := `
-- With table approach, this becomes much cleaner
-- 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"
else
v._attr = v._attr or {}
v._attr.status = "out-of-stock"
end
`
result, modCount, matchCount, err := p.ProcessContent(content, "//product", luaExpr)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 2 {
t.Errorf("Expected 2 matches, got %d", matchCount)
}
if modCount != 2 {
t.Errorf("Expected 2 modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_ElementRemoval(t *testing.T) {
content := `
John Smith
john@example.com
secret123
admin
Jane Doe
jane@example.com
pass456
user
`
expected := `
John Smith
john@example.com
admin
Jane Doe
jane@example.com
user
`
// This test demonstrates removing sensitive data elements
p := &XMLProcessor{}
luaExpr := `
-- With table approach, element removal is trivial
-- Just set the element to nil to remove it
v.password = nil
`
result, modCount, matchCount, err := p.ProcessContent(content, "//user", luaExpr)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 2 {
t.Errorf("Expected 2 matches, got %d", matchCount)
}
if modCount != 2 {
t.Errorf("Expected 2 modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_ElementReordering(t *testing.T) {
content := `
Bob Dylan
Blowin' in the Wind
1963
The Beatles
Hey Jude
1968
`
expected := `
Blowin' in the Wind
Bob Dylan
1963
Hey Jude
The Beatles
1968
`
// This test demonstrates reordering elements
p := &XMLProcessor{}
luaExpr := `
-- With table approach, we can reorder elements by redefining the table
-- Store the values
local artist = v.artist
local title = v.title
local year = v.year
-- Clear the table
for k in pairs(v) do
v[k] = nil
end
-- Add elements in the desired order
v.title = title
v.artist = artist
v.year = year
`
result, modCount, matchCount, err := p.ProcessContent(content, "//song", luaExpr)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 2 {
t.Errorf("Expected 2 matches, got %d", matchCount)
}
if modCount != 2 {
t.Errorf("Expected 2 modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_ComplexStructuralChange(t *testing.T) {
content := `
The Great Gatsby
F. Scott Fitzgerald
1925
10.99
A Brief History of Time
Stephen Hawking
1988
15.99
`
expected := `
The Great Gatsby
F. Scott Fitzgerald
1925
10.99
0
fiction
A Brief History of Time
Stephen Hawking
1988
15.99
0
non-fiction
`
// This test demonstrates a complete restructuring of the XML using table approach
p := &XMLProcessor{}
luaExpr := `
-- Store the original values
local category = v._attr and v._attr.category
local title = v.title
local author = v.author
local year = v.year
local price = v.price
-- Clear the original structure
for k in pairs(v) do
v[k] = nil
end
-- Create a new nested structure
v.details = {
title = title,
author = author,
year = year
}
v.pricing = {
price = {
_attr = { currency = "USD" },
_text = price
},
discount = "0"
}
v.metadata = {
category = category
}
`
result, modCount, matchCount, err := p.ProcessContent(content, "//book", luaExpr)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 2 {
t.Errorf("Expected 2 matches, got %d", matchCount)
}
if modCount != 2 {
t.Errorf("Expected 2 modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_DynamicXPath(t *testing.T) {
content := `
`
expected := `
`
// This test demonstrates using specific XPath queries to select precise nodes
p := &XMLProcessor{}
// Double all timeout values in the configuration
result, modCount, matchCount, err := p.ProcessContent(content, "//setting[@name='timeout']/@value", "v = v * 2")
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 2 {
t.Errorf("Expected 2 matches, got %d", matchCount)
}
if modCount != 2 {
t.Errorf("Expected 2 modifications, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_TableBasedStructureCreation(t *testing.T) {
content := `
`
expected := `
2
Debug: OFF, Logging: info
`
// This test demonstrates adding a completely new section with nested structure
p := &XMLProcessor{}
luaExpr := `
-- Count all options
local count = 0
local summary = ""
-- Process each child option
if v.settings and v.settings.option then
local options = v.settings.option
-- If there's just one option, wrap it in a table
if options._attr then
options = {options}
end
for i, opt in ipairs(options) do
count = count + 1
if opt._attr.name == "debug" then
summary = summary .. "Debug: " .. (opt._attr.value == "true" and "ON" or "OFF")
elseif opt._attr.name == "log_level" then
summary = summary .. "Logging: " .. opt._attr.value
end
if i < #options then
summary = summary .. ", "
end
end
end
-- Create a new calculated section
v.calculated = {
stats = {
count = tostring(count),
summary = summary
}
}
`
result, modCount, matchCount, err := p.ProcessContent(content, "/data", luaExpr)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 1 {
t.Errorf("Expected 1 match, got %d", matchCount)
}
if modCount != 1 {
t.Errorf("Expected 1 modification, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_ArrayManipulation(t *testing.T) {
content := `
Book 1
200
Book 2
150
Book 3
300
`
expected := `
Book 3
300
Book 1
200
2
500
250
`
// This test demonstrates advanced manipulation including:
// 1. Sorting and filtering arrays of elements
// 2. Calculating aggregates
// 3. Generating summaries
p := &XMLProcessor{}
luaExpr := `
-- Get the books array
local books = v.books.book
-- If only one book, wrap it in a table
if books and not books[1] then
books = {books}
end
-- Filter and sort books
local filtered_books = {}
local total_pages = 0
for _, book in ipairs(books) do
local pages = tonumber(book.pages) or 0
-- Filter: only keep books with pages >= 200
if pages >= 200 then
total_pages = total_pages + pages
table.insert(filtered_books, book)
end
end
-- Sort books by number of pages (descending)
table.sort(filtered_books, function(a, b)
return tonumber(a.pages) > tonumber(b.pages)
end)
-- Replace the books array with our filtered and sorted one
v.books.book = filtered_books
-- Add summary information
local count = #filtered_books
local average_pages = count > 0 and math.floor(total_pages / count) or 0
v.summary = {
count = tostring(count),
total_pages = tostring(total_pages),
average_pages = tostring(average_pages)
}
`
result, modCount, matchCount, err := p.ProcessContent(content, "/library", luaExpr)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 1 {
t.Errorf("Expected 1 match, got %d", matchCount)
}
if modCount != 1 {
t.Errorf("Expected 1 modification, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestXMLProcessor_Process_DeepPathNavigation(t *testing.T) {
content := `
localhost
3306
admin
secret
10
30
info
/var/log/app.log
`
expected := `
db.example.com
5432
admin
REDACTED
20
60
debug
/var/log/app.log
production
true
true
`
// This test demonstrates navigating deeply nested elements in a complex XML structure
p := &XMLProcessor{}
luaExpr := `
-- Update database connection settings
v.config.database.connection.host = "db.example.com"
v.config.database.connection.port = "5432"
-- Redact sensitive information
v.config.database.connection.credentials.password = "REDACTED"
-- Double pool size and timeout
v.config.database.pool.size = tostring(tonumber(v.config.database.pool.size) * 2)
v.config.database.pool.timeout = tostring(tonumber(v.config.database.pool.timeout) * 2)
-- Change logging level
v.config.logging.level = "debug"
-- Add a new status section
v.status = {
environment = "production",
updated = "true",
secure = tostring(v.config.database.connection.credentials.password == "REDACTED")
}
`
result, modCount, matchCount, err := p.ProcessContent(content, "/application", luaExpr)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 1 {
t.Errorf("Expected 1 match, got %d", matchCount)
}
if modCount != 1 {
t.Errorf("Expected 1 modification, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeXMLWhitespace(result)
normalizedExpected := normalizeXMLWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
// Add more test cases for specific XML manipulation scenarios below
// These tests would cover additional functionality as the implementation progresses