Files
BigChef/processor/regex_test.go

1305 lines
31 KiB
Go

package processor
import (
"modify/utils"
"regexp"
"strings"
"testing"
)
// TestLogger implements the Logger interface for testing
type TestLogger struct {
T *testing.T // Reference to the test's *testing.T
}
func (l *TestLogger) Printf(format string, v ...interface{}) {
if l.T != nil {
l.T.Logf(format, v...)
}
}
// Helper function to normalize whitespace for comparison
func normalizeWhitespace(s string) string {
// Replace all whitespace with a single space
re := regexp.MustCompile(`\s+`)
return re.ReplaceAllString(strings.TrimSpace(s), " ")
}
func ApiAdaptor(content string, regex string, lua string) (string, int, int, error) {
command := utils.ModifyCommand{
Pattern: regex,
Lua: lua,
LogLevel: "TRACE",
}
commands, err := ProcessRegex(content, command)
if err != nil {
return "", 0, 0, err
}
result, modifications := utils.ExecuteModifications(commands, content)
return result, modifications, len(commands), nil
}
func TestBuildLuaScript(t *testing.T) {
cases := []struct {
input string
expected string
}{
{"s1 .. '_suffix'", "v1 = s1 .. '_suffix'"},
{"v1 * 1.5", "v1 = v1 * 1.5"},
{"v1 + 10", "v1 = v1 + 10"},
{"v1 * 2", "v1 = v1 * 2"},
{"v1 * v2", "v1 = v1 * v2"},
{"v1 / v2", "v1 = v1 / v2"},
{"12", "v1 = 12"},
}
for _, c := range cases {
result := PrependLuaAssignment(c.input)
if result != c.expected {
t.Errorf("BuildLuaScript(%q): expected %q, got %q", c.input, c.expected, result)
}
}
}
func TestSimpleValueMultiplication(t *testing.T) {
content := `<config>
<item>
<value>100</value>
</item>
</config>`
expected := `<config>
<item>
<value>150</value>
</item>
</config>`
result, mods, matches, err := ApiAdaptor(content, `(?s)<value>(\d+)</value>`, "v1 = v1*1.5")
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 1 {
t.Errorf("Expected 1 match, got %d", matches)
}
if mods != 1 {
t.Errorf("Expected 1 modification, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestShorthandNotation(t *testing.T) {
content := `<config>
<item>
<value>100</value>
</item>
</config>`
expected := `<config>
<item>
<value>150</value>
</item>
</config>`
result, mods, matches, err := ApiAdaptor(content, `(?s)<value>(\d+)</value>`, "v1*1.5")
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 1 {
t.Errorf("Expected 1 match, got %d", matches)
}
if mods != 1 {
t.Errorf("Expected 1 modification, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestShorthandNotationFloats(t *testing.T) {
content := `<config>
<item>
<value>10.5</value>
</item>
</config>`
expected := `<config>
<item>
<value>15.75</value>
</item>
</config>`
result, mods, matches, err := ApiAdaptor(content, `(?s)<value>(\d+\.\d+)</value>`, "v1*1.5")
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 1 {
t.Errorf("Expected 1 match, got %d", matches)
}
if mods != 1 {
t.Errorf("Expected 1 modification, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestArrayNotation(t *testing.T) {
content := `<config>
<prices>
<price>10</price>
<price>20</price>
<price>30</price>
</prices>
</config>`
expected := `<config>
<prices>
<price>20</price>
<price>40</price>
<price>60</price>
</prices>
</config>`
result, mods, matches, err := ApiAdaptor(content, `(?s)<price>(\d+)</price>`, "v1*2")
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 3 {
t.Errorf("Expected 3 matches, got %d", matches)
}
if mods != 3 {
t.Errorf("Expected 3 modifications, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestMultipleNumericMatches(t *testing.T) {
content := `<data>
<entry>50</entry>
<entry>100</entry>
<entry>200</entry>
</data>`
expected := `<data>
<entry>100</entry>
<entry>200</entry>
<entry>400</entry>
</data>`
result, mods, matches, err := ApiAdaptor(content, `<entry>(\d+)</entry>`, "v1*2")
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 3 {
t.Errorf("Expected 3 matches, got %d", matches)
}
if mods != 3 {
t.Errorf("Expected 3 modifications, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestMultipleStringMatches(t *testing.T) {
content := `<data>
<name>John</name>
<name>Mary</name>
</data>`
expected := `<data>
<name>John_modified</name>
<name>Mary_modified</name>
</data>`
result, mods, matches, err := ApiAdaptor(content, `<name>([A-Za-z]+)</name>`, `s1 = s1 .. "_modified"`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 2 {
t.Errorf("Expected 2 matches, got %d", matches)
}
if mods != 2 {
t.Errorf("Expected 2 modifications, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestStringUpperCase(t *testing.T) {
content := `<users>
<user>John</user>
<user>Mary</user>
</users>`
expected := `<users>
<user>JOHN</user>
<user>MARY</user>
</users>`
result, mods, matches, err := ApiAdaptor(content, `<user>([A-Za-z]+)</user>`, `s1 = string.upper(s1)`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 2 {
t.Errorf("Expected 2 matches, got %d", matches)
}
if mods != 2 {
t.Errorf("Expected 2 modifications, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestStringConcatenation(t *testing.T) {
content := `<products>
<product>Apple</product>
<product>Banana</product>
</products>`
expected := `<products>
<product>Apple_fruit</product>
<product>Banana_fruit</product>
</products>`
result, mods, matches, err := ApiAdaptor(content, `<product>([A-Za-z]+)</product>`, `s1 = s1 .. "_fruit"`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 2 {
t.Errorf("Expected 2 matches, got %d", matches)
}
if mods != 2 {
t.Errorf("Expected 2 modifications, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
// Added from main_test.go
func TestDecimalValues(t *testing.T) {
content := `
<config>
<item>
<value>10.5</value>
<multiplier>2.5</multiplier>
</item>
</config>
`
expected := `
<config>
<item>
<value>26.25</value>
<multiplier>2.5</multiplier>
</item>
</config>
`
regex := regexp.MustCompile(`(?s)<value>([0-9.]+)</value>.*?<multiplier>([0-9.]+)</multiplier>`)
luaExpr := BuildLuaScript("v1 = v1 * v2")
result, _, _, err := ApiAdaptor(content, regex.String(), luaExpr)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
normalizedModified := normalizeWhitespace(result)
normalizedExpected := normalizeWhitespace(expected)
if normalizedModified != normalizedExpected {
t.Fatalf("Expected modified content to be %q, but got %q", normalizedExpected, normalizedModified)
}
}
// Added from main_test.go
func TestLuaMathFunctions(t *testing.T) {
content := `
<config>
<item>
<value>16</value>
</item>
</config>
`
expected := `
<config>
<item>
<value>4</value>
</item>
</config>
`
regex := regexp.MustCompile(`(?s)<value>(\d+)</value>`)
luaExpr := BuildLuaScript("v1 = math.sqrt(v1)")
modifiedContent, _, _, err := ApiAdaptor(content, regex.String(), luaExpr)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
normalizedModified := normalizeWhitespace(modifiedContent)
normalizedExpected := normalizeWhitespace(expected)
if normalizedModified != normalizedExpected {
t.Fatalf("Expected modified content to be %q, but got %q", normalizedExpected, normalizedModified)
}
}
// Added from main_test.go
func TestDirectAssignment(t *testing.T) {
content := `
<config>
<item>
<value>100</value>
</item>
</config>
`
expected := `
<config>
<item>
<value>0</value>
</item>
</config>
`
regex := regexp.MustCompile(`(?s)<value>(\d+)</value>`)
luaExpr := BuildLuaScript("=0")
modifiedContent, _, _, err := ApiAdaptor(content, regex.String(), luaExpr)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
normalizedModified := normalizeWhitespace(modifiedContent)
normalizedExpected := normalizeWhitespace(expected)
if normalizedModified != normalizedExpected {
t.Fatalf("Expected modified content to be %q, but got %q", normalizedExpected, normalizedModified)
}
}
// Added from main_test.go
func TestStringAndNumericOperations(t *testing.T) {
tests := []struct {
name string
input string
regexPattern string
luaExpression string
expectedOutput string
expectedMods int
}{
{
name: "Basic numeric multiplication",
input: "<value>42</value>",
regexPattern: "<value>(\\d+)</value>",
luaExpression: "v1 = v1 * 2",
expectedOutput: "<value>84</value>",
expectedMods: 1,
},
{
name: "Basic string manipulation",
input: "<name>test</name>",
regexPattern: "<name>(.*?)</name>",
luaExpression: "s1 = string.upper(s1)",
expectedOutput: "<name>TEST</name>",
expectedMods: 1,
},
{
name: "String concatenation",
input: "<id>abc123</id>",
regexPattern: "<id>(.*?)</id>",
luaExpression: "s1 = s1 .. '_modified'",
expectedOutput: "<id>abc123_modified</id>",
expectedMods: 1,
},
{
name: "Numeric value from string using num()",
input: "<price>19.99</price>",
regexPattern: "<price>(.*?)</price>",
luaExpression: "v1 = num(s1) * 1.2",
expectedOutput: "<price>23.987999999999996</price>",
expectedMods: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Compile the regex pattern with multiline support
pattern := "(?s)" + tt.regexPattern
luaExpr := BuildLuaScript(tt.luaExpression)
// Process with our function
result, modCount, _, err := ApiAdaptor(tt.input, pattern, luaExpr)
if err != nil {
t.Fatalf("Process function failed: %v", err)
}
// Check results
if result != tt.expectedOutput {
t.Errorf("Expected output: %s, got: %s", tt.expectedOutput, result)
}
if modCount != tt.expectedMods {
t.Errorf("Expected %d modifications, got %d", tt.expectedMods, modCount)
}
})
}
}
// Added from main_test.go
func TestEdgeCases(t *testing.T) {
tests := []struct {
name string
input string
regexPattern string
luaExpression string
expectedOutput string
expectedMods int
}{
{
name: "Empty capture group",
input: "<value></value>",
regexPattern: "<value>(.*?)</value>",
luaExpression: "s1 = 'filled'",
expectedOutput: "<value>filled</value>",
expectedMods: 1,
},
{
name: "Non-numeric string with numeric operation",
input: "<value>abc</value>",
regexPattern: "<value>(.*?)</value>",
luaExpression: "v1 = v1 * 2", // This would fail if we didn't handle strings properly
expectedOutput: "<value>abc</value>", // Should remain unchanged
expectedMods: 0, // No modifications
},
{
name: "Invalid number conversion",
input: "<value>abc</value>",
regexPattern: "<value>(.*?)</value>",
luaExpression: "v1 = num(s1) + 10", // num(s1) should return 0
expectedOutput: "<value>10</value>",
expectedMods: 1,
},
{
name: "Multiline string",
input: "<text>Line 1\nLine 2</text>",
regexPattern: "<text>(.*?)</text>",
luaExpression: "s1 = string.gsub(s1, '\\n', ' - ')",
expectedOutput: "<text>Line 1 - Line 2</text>",
expectedMods: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Make sure the regex can match across multiple lines
pattern := "(?s)" + tt.regexPattern
luaExpr := BuildLuaScript(tt.luaExpression)
// Process with our function
result, modCount, _, err := ApiAdaptor(tt.input, pattern, luaExpr)
if err != nil {
t.Fatalf("Process function failed: %v", err)
}
// Check results
if result != tt.expectedOutput {
t.Errorf("Expected output: %s, got: %s", tt.expectedOutput, result)
}
if modCount != tt.expectedMods {
t.Errorf("Expected %d modifications, got %d", tt.expectedMods, modCount)
}
})
}
}
func TestNamedCaptureGroups(t *testing.T) {
content := `<config>
<item>
<value>100</value>
</item>
</config>`
expected := `<config>
<item>
<value>200</value>
</item>
</config>`
result, mods, matches, err := ApiAdaptor(content, `(?s)<value>(?<amount>\d+)</value>`, "amount = amount * 2")
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 1 {
t.Errorf("Expected 1 match, got %d", matches)
}
if mods != 1 {
t.Errorf("Expected 1 modification, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestNamedCaptureGroupsNum(t *testing.T) {
content := `<config>
<item>
<value>100</value>
</item>
</config>`
expected := `<config>
<item>
<value>200</value>
</item>
</config>`
result, mods, matches, err := ApiAdaptor(content, `(?s)<value>(?<amount>!num)</value>`, "amount = amount * 2")
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 1 {
t.Errorf("Expected 1 match, got %d", matches)
}
if mods != 1 {
t.Errorf("Expected 1 modification, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestMultipleNamedCaptureGroups(t *testing.T) {
content := `<product>
<name>Widget</name>
<price>15.99</price>
<quantity>10</quantity>
</product>`
expected := `<product>
<name>WIDGET</name>
<price>23.99</price>
<quantity>15</quantity>
</product>`
result, mods, matches, err := ApiAdaptor(content,
`(?s)<name>(?<prodName>[^<]+)</name>.*?<price>(?<prodPrice>\d+\.\d+)</price>.*?<quantity>(?<prodQty>\d+)</quantity>`,
`prodName = string.upper(prodName)
prodPrice = round(prodPrice + 8, 2)
prodQty = prodQty + 5`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 3 {
t.Errorf("Expected 3 matches, got %d", matches)
}
if mods != 3 {
t.Errorf("Expected 3 modifications, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestMixedIndexedAndNamedCaptures(t *testing.T) {
content := `<entry>
<id>12345</id>
<data>value</data>
</entry>`
expected := `<entry>
<id>24690</id>
<data>VALUE</data>
</entry>`
result, mods, matches, err := ApiAdaptor(content,
`(?s)<id>(\d+)</id>.*?<data>(?<dataField>[^<]+)</data>`,
`v1 = v1 * 2
dataField = string.upper(dataField)`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 2 {
t.Errorf("Expected 2 matches, got %d", matches)
}
if mods != 2 {
t.Errorf("Expected 2 modifications, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestComplexNestedNamedCaptures(t *testing.T) {
content := `<person>
<details>
<name>John Smith</name>
<age>32</age>
</details>
<contact>
<email>john@example.com</email>
</contact>
</person>`
expected := `<person>
<details>
<name>JOHN SMITH (32)</name>
<age>32</age>
</details>
<contact>
<email>john@example.com</email>
</contact>
</person>`
result, mods, matches, err := ApiAdaptor(content,
`(?s)<details>.*?<name>(?<fullName>[^<]+)</name>.*?<age>(?<age>\d+)</age>`,
`fullName = string.upper(fullName) .. " (" .. age .. ")"`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 1 {
t.Errorf("Expected 1 match, got %d", matches)
}
if mods != 1 {
t.Errorf("Expected 1 modification, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestNamedCaptureWithVariableReadback(t *testing.T) {
content := `<stats>
<health>100</health>
<mana>200</mana>
</stats>`
expected := `<stats>
<health>150</health>
<mana>300</mana>
</stats>`
result, mods, matches, err := ApiAdaptor(content,
`(?s)<health>(?<hp>\d+)</health>.*?<mana>(?<mp>\d+)</mana>`,
`hp = hp * 1.5
mp = mp * 1.5`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 2 {
t.Errorf("Expected 2 matches, got %d", matches)
}
if mods != 2 {
t.Errorf("Expected 2 modifications, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestNamedCaptureWithSpecialCharsInName(t *testing.T) {
content := `<data value="42" min="10" max="100" />`
expected := `<data value="84" min="10" max="100" />`
result, mods, matches, err := ApiAdaptor(content,
`<data value="(?<val_1>\d+)"`,
`val_1 = val_1 * 2`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 1 {
t.Errorf("Expected 1 match, got %d", matches)
}
if mods != 1 {
t.Errorf("Expected 1 modification, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestEmptyNamedCapture(t *testing.T) {
content := `<tag attr="" />`
expected := `<tag attr="default" />`
result, mods, matches, err := ApiAdaptor(content,
`attr="(?<value>.*?)"`,
`value = value == "" and "default" or value`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 1 {
t.Errorf("Expected 1 match, got %d", matches)
}
if mods != 1 {
t.Errorf("Expected 1 modification, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestMultipleNamedCapturesInSameLine(t *testing.T) {
content := `<rect x="10" y="20" width="100" height="50" />`
expected := `<rect x="20" y="40" width="200" height="100" />`
result, mods, matches, err := ApiAdaptor(content,
`x="(?<x>\d+)" y="(?<y>\d+)" width="(?<w>\d+)" height="(?<h>\d+)"`,
`x = x * 2
y = y * 2
w = w * 2
h = h * 2`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 4 {
t.Errorf("Expected 4 matches, got %d", matches)
}
if mods != 4 {
t.Errorf("Expected 4 modifications, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestConditionalNamedCapture(t *testing.T) {
content := `
<item status="active" count="5" />
<item status="inactive" count="10" />
`
expected := `
<item status="active" count="10" />
<item status="inactive" count="10" />
`
result, mods, matches, err := ApiAdaptor(content,
`<item status="(?<status>[^"]+)" count="(?<count>\d+)"`,
`count = status == "active" and count * 2 or count`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 1 {
t.Errorf("Expected 1 matches, got %d", matches)
}
if mods != 1 {
t.Errorf("Expected 1 modification, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestLuaFunctionsOnNamedCaptures(t *testing.T) {
content := `
<user name="john doe" role="user" />
<user name="jane smith" role="admin" />
`
expected := `
<user name="John Doe" role="user" />
<user name="JANE SMITH" role="admin" />
`
result, mods, matches, err := ApiAdaptor(content,
`<user name="(?<name>[^"]+)" role="(?<role>[^"]+)"`,
`-- Capitalize first letters for regular users
if role == "user" then
name = name:gsub("(%w)(%w*)", function(first, rest) return first:upper()..rest end):gsub(" (%w)(%w*)", " %1%2")
else
-- Uppercase for admins
name = string.upper(name)
end`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 2 {
t.Errorf("Expected 2 matches, got %d", matches)
}
if mods != 2 {
t.Errorf("Expected 2 modifications, got %d", mods)
}
// For simpler tests, we can use this. More complex string modifications
// might need additional transformations before comparison
normalizedResult := normalizeWhitespace(result)
normalizedExpected := normalizeWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestNamedCaptureWithMath(t *testing.T) {
content := `
<item price="19.99" quantity="3" />
`
expected := `
<item price="19.99" quantity="3" total="59.97" />
`
result, mods, matches, err := ApiAdaptor(content,
`<item price="(?<price>\d+\.\d+)" quantity="(?<qty>\d+)"!any$`,
`-- Calculate and add total
replacement = string.format('<item price="%s" quantity="%s" total="%.2f" />',
price, qty, price * qty)`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 1 {
t.Errorf("Expected 1 match, got %d", matches)
}
if mods != 1 {
t.Errorf("Expected 1 modification, got %d", mods)
}
result = normalizeWhitespace(result)
expected = normalizeWhitespace(expected)
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestNamedCaptureWithGlobals(t *testing.T) {
content := `<temp unit="C">25</temp>`
expected := `<temp unit="F">77</temp>`
result, mods, matches, err := ApiAdaptor(content,
`<temp unit="(?<unit>[CF]?)">(?<value>\d+)</temp>`,
`if unit == "C" then
value = value * 9/5 + 32
unit = "F"
elseif unit == "F" then
value = (value - 32) * 5/9
unit = "C"
end`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 2 {
t.Errorf("Expected 2 matches, got %d", matches)
}
if mods != 2 {
t.Errorf("Expected 2 modifications, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestMixedDynamicAndNamedCaptures(t *testing.T) {
content := `
<color rgb="255,0,0" name="red" />
<color rgb="0,255,0" name="green" />
`
expected := `
<color rgb="255,0,0" name="RED" hex="#FF0000" />
<color rgb="0,255,0" name="GREEN" hex="#00FF00" />
`
result, mods, matches, err := ApiAdaptor(content,
`<color rgb="(?<r>\d+),(?<g>\d+),(?<b>\d+)" name="(?<colorName>[^"]+)" />`,
`-- Uppercase the name
colorName = string.upper(colorName)
-- Create hex color
local hex = string.format("#%02X%02X%02X", tonumber(r), tonumber(g), tonumber(b))
-- Replace the entire match
replacement = string.format('<color rgb="%s,%s,%s" name="%s" hex="%s" />',
r, g, b, colorName, hex)`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 2 {
t.Errorf("Expected 2 matches, got %d", matches)
}
if mods != 2 {
t.Errorf("Expected 2 modifications, got %d", mods)
}
result = normalizeWhitespace(result)
expected = normalizeWhitespace(expected)
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestNamedCapturesWithMultipleReferences(t *testing.T) {
content := `<text>Hello world</text>`
expected := `<text format="uppercase" length="11">HELLO WORLD</text>`
result, mods, matches, err := ApiAdaptor(content,
`<text>(?<content>[^<]+)</text>`,
`local uppercaseContent = string.upper(content)
local contentLength = string.len(content)
replacement = string.format('<text format="uppercase" length="%d">%s</text>',
contentLength, uppercaseContent)`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 1 {
t.Errorf("Expected 1 match, got %d", matches)
}
if mods != 1 {
t.Errorf("Expected 1 modification, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestNamedCaptureWithJsonData(t *testing.T) {
content := `<data>{"name":"John","age":30}</data>`
expected := `<data>{"name":"JOHN","age":30}</data>`
result, mods, matches, err := ApiAdaptor(content,
`<data>(?<json>\{.*?\})</data>`,
`-- Parse JSON (simplified, assumes valid JSON)
local name = json:match('"name":"([^"]+)"')
local upperName = string.upper(name)
json = json:gsub('"name":"([^"]+)"', '"name":"' .. upperName .. '"')`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 1 {
t.Errorf("Expected 1 match, got %d", matches)
}
if mods != 1 {
t.Errorf("Expected 1 modification, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestNamedCaptureInXML(t *testing.T) {
content := `
<product>
<sku>ABC-123</sku>
<price currency="USD">19.99</price>
<stock>25</stock>
</product>
`
expected := `
<product>
<sku>ABC-123</sku>
<price currency="USD">23.99</price>
<stock>20</stock>
</product>
`
result, mods, matches, err := ApiAdaptor(content,
`(?s)<price currency="(?<currency>[^"]+)">(?<price>\d+\.\d+)</price>.*?<stock>(?<stock>\d+)</stock>`,
`-- Add 20% to price if USD
if currency == "USD" then
price = round(price * 1.20, 2)
end
-- Reduce stock by 5
stock = stock - 5`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 2 {
t.Errorf("Expected 2 matches, got %d", matches)
}
if mods != 2 {
t.Errorf("Expected 2 modifications, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestComprehensiveNamedCaptures(t *testing.T) {
content := `
<products>
<product sku="AB-123" status="in-stock">
<name>Widget A</name>
<price currency="USD">19.99</price>
<quantity>15</quantity>
</product>
<product sku="CD-456" status="out-of-stock">
<name>Widget B</name>
<price currency="EUR">29.99</price>
<quantity>0</quantity>
</product>
<product sku="EF-789" status="in-stock">
<name>Widget C</name>
<price currency="GBP">39.99</price>
<quantity>5</quantity>
</product>
</products>
`
expected := `
<products>
<product sku="AB-123" status="in-stock" discounted="true">
<name>WIDGET A</name>
<price currency="USD">15.99</price>
<quantity>15</quantity>
</product>
<product sku="CD-456" status="out-of-stock" discounted="false">
<name>Widget B</name>
<price currency="EUR">29.99</price>
<quantity>0</quantity>
</product>
<product sku="EF-789" status="in-stock" discounted="true">
<name>WIDGET C</name>
<price currency="GBP">39.99</price>
<quantity>5</quantity>
</product>
</products>
`
result, mods, matches, err := ApiAdaptor(content,
`(?s)<product sku="(?<sku>[^"]+)" status="(?<status>[^"]+)"[^>]*>\s*<name>(?<product_name>[^<]+)</name>\s*<price currency="(?<currency>[^"]+)">(?<price>\d+\.\d+)</price>\s*<quantity>(?<qty>\d+)</quantity>`,
`-- Only process in-stock items
if status == "in-stock" then
-- Transform name to uppercase
product_name = string.upper(product_name)
-- Apply discount based on currency
local discounted = true
if currency == "USD" then
price = round(price * 0.8, 2) -- 20% discount for USD
elseif currency == "GBP" then
price = round(price * 0.8, 2) -- 20% discount for GBP
price = price + 8 -- Add shipping cost for GBP
else
discounted = false
end
-- Add discounted attribute
replacement = string.format('<product sku="%s" status="%s" discounted="%s">\n\t\t\t<name>%s</name>\n\t\t\t<price currency="%s">%.2f</price>\n\t\t\t<quantity>%s</quantity>',
sku, status, tostring(discounted), product_name, currency, price, qty)
else
-- Add discounted attribute for out-of-stock items (always false)
replacement = string.format('<product sku="%s" status="%s" discounted="false">\n\t\t\t<name>%s</name>\n\t\t\t<price currency="%s">%s</price>\n\t\t\t<quantity>%s</quantity>',
sku, status, product_name, currency, price, qty)
end`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 3 {
t.Errorf("Expected 3 matches, got %d", matches)
}
if mods != 3 {
t.Errorf("Expected 3 modifications, got %d", mods)
}
// Normalize whitespace for comparison
normalizedResult := normalizeWhitespace(result)
normalizedExpected := normalizeWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestVariousNamedCaptureFormats(t *testing.T) {
content := `
<data>
<entry id="1" value="100" />
<entry id="2" value="200" status="active" />
<entry id="3" value="300" status="inactive" />
</data>
`
expected := `
<data>
<entry id="ID-1" value="200" />
<entry id="ID-2" value="400" status="ACTIVE" />
<entry id="ID-3" value="300" status="inactive" />
</data>
`
result, mods, matches, err := ApiAdaptor(content,
`<entry id="(?<id_num>\d+)" value="(?<val>\d+)"(?: status="(?<status>[^"]*)")? />`,
`-- Prefix the ID with "ID-"
id_num = "ID-" .. id_num
print(id_num)
print(val)
print(status)
-- Double the value except for inactive status
if not status or status ~= "inactive" then
val = val * 2
end
-- Convert status to uppercase if present and active
if status and status == "active" then
status = string.upper(status)
end
-- Build the replacement based on whether status exists
if status then
replacement = string.format('<entry id="%s" value="%s" status="%s" />', id_num, val, status)
else
replacement = string.format('<entry id="%s" value="%s" />', id_num, val)
end`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 3 {
t.Errorf("Expected 3 matches, got %d", matches)
}
if mods != 3 {
t.Errorf("Expected 3 modifications, got %d", mods)
}
normalizedResult := normalizeWhitespace(result)
normalizedExpected := normalizeWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}
func TestSimpleNamedCapture(t *testing.T) {
content := `<product name="Widget" price="19.99"/>`
expected := `<product name="WIDGET" price="19.99"/>`
result, mods, matches, err := ApiAdaptor(content,
`name="(?<product_name>[^"]+)"`,
`product_name = string.upper(product_name)`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matches != 1 {
t.Errorf("Expected 1 match, got %d", matches)
}
if mods != 1 {
t.Errorf("Expected 1 modification, got %d", mods)
}
if result != expected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}