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") // } // }) // }