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+`)
s = re.ReplaceAllString(strings.TrimSpace(s), " ")
// Normalize XML entities for comparison
s = ConvertToNamedEntities(s)
return 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.value = v.value * 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.value = v.value * 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.value = string.upper(v.value)")
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.value = v.value * 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.value = round(v.value * 1.2, 3)")
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.attr.stock and tonumber(v.attr.stock) > 0 then
v.attr.price = tonumber(v.attr.price) * 0.8
-- Format to 2 decimal places
v.attr.price = string.format("%.2f", v.attr.price)
else
return false
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.value = string.upper(v.value)")
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 := `
local price = v.value
-- Add 15% tax
price = price * 1.15
-- Apply 10% discount
price = price * 0.9
-- Round to 2 decimal places
price = round(price, 2)
v.value = 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.value = round(v.value)")
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.value = string.gsub(v.value, "@.+", "@anon.com")
local username = string.match(v.value, "(.+)@")
v.value = 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.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)
`)
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.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.value = 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_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.value = round(v.value * 0.8, 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_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.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.value = v.value + 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.attr.price)
// local quantity = tonumber(v.attr.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.attr.artist
// local title = v.attr.title
// local year = v.attr.year
//
// -- Clear the table
// for k in pairs(v) do
// v[k] = nil
// end
//
// -- Add elements in the desired order
// v.attr.title = title
// v.attr.artist = artist
// v.attr.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.value = v.value * 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
// local settings = v.children[1]
// local options = settings.children
// -- if settings and options then
// -- 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.children[2] = {
// -- 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
// 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")
// }
// })
// }