Hallucinate up an xml parser implementation
Who knows if this will work...
This commit is contained in:
447
processor/xml.go
Normal file
447
processor/xml.go
Normal file
@@ -0,0 +1,447 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"cook/utils"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
logger "git.site.quack-lab.dev/dave/cylogger"
|
||||
)
|
||||
|
||||
var xmlLogger = logger.Default.WithPrefix("processor/xml")
|
||||
|
||||
// XMLElement represents a parsed XML element with position tracking
|
||||
type XMLElement struct {
|
||||
Tag string
|
||||
Attributes map[string]XMLAttribute
|
||||
Text string
|
||||
Children []*XMLElement
|
||||
StartPos int64
|
||||
EndPos int64
|
||||
TextStart int64
|
||||
TextEnd int64
|
||||
}
|
||||
|
||||
// XMLAttribute represents an attribute with its position in the source
|
||||
type XMLAttribute struct {
|
||||
Value string
|
||||
ValueStart int64
|
||||
ValueEnd int64
|
||||
}
|
||||
|
||||
// parseXMLWithPositions parses XML while tracking byte positions of all elements and attributes
|
||||
func parseXMLWithPositions(content string) (*XMLElement, error) {
|
||||
decoder := xml.NewDecoder(strings.NewReader(content))
|
||||
var root *XMLElement
|
||||
var stack []*XMLElement
|
||||
var lastPos int64
|
||||
|
||||
for {
|
||||
token, err := decoder.Token()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse XML: %v", err)
|
||||
}
|
||||
|
||||
offset := decoder.InputOffset()
|
||||
|
||||
switch t := token.(type) {
|
||||
case xml.StartElement:
|
||||
// Find the actual start position of this element by searching for "<tagname"
|
||||
tagSearchPattern := "<" + t.Name.Local
|
||||
startPos := int64(strings.LastIndex(content[:offset], tagSearchPattern))
|
||||
|
||||
element := &XMLElement{
|
||||
Tag: t.Name.Local,
|
||||
Attributes: make(map[string]XMLAttribute),
|
||||
StartPos: startPos,
|
||||
Children: []*XMLElement{},
|
||||
}
|
||||
|
||||
// Parse attributes - search within the tag boundaries
|
||||
if len(t.Attr) > 0 {
|
||||
tagEnd := offset
|
||||
tagSection := content[startPos:tagEnd]
|
||||
|
||||
for _, attr := range t.Attr {
|
||||
// Find attribute in the tag section: attrname="value"
|
||||
attrPattern := attr.Name.Local + `="`
|
||||
attrIdx := strings.Index(tagSection, attrPattern)
|
||||
if attrIdx >= 0 {
|
||||
valueStart := startPos + int64(attrIdx) + int64(len(attrPattern))
|
||||
valueEnd := valueStart + int64(len(attr.Value))
|
||||
element.Attributes[attr.Name.Local] = XMLAttribute{
|
||||
Value: attr.Value,
|
||||
ValueStart: valueStart,
|
||||
ValueEnd: valueEnd,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(stack) > 0 {
|
||||
parent := stack[len(stack)-1]
|
||||
parent.Children = append(parent.Children, element)
|
||||
} else {
|
||||
root = element
|
||||
}
|
||||
|
||||
stack = append(stack, element)
|
||||
lastPos = offset
|
||||
|
||||
case xml.CharData:
|
||||
rawText := string(t)
|
||||
text := strings.TrimSpace(rawText)
|
||||
if len(stack) > 0 && text != "" {
|
||||
current := stack[len(stack)-1]
|
||||
current.Text = text
|
||||
|
||||
// The text content is between lastPos (after >) and offset (before </)
|
||||
// Search for the trimmed text within the raw content
|
||||
textInContent := content[lastPos:offset]
|
||||
trimmedStart := strings.Index(textInContent, text)
|
||||
if trimmedStart >= 0 {
|
||||
current.TextStart = lastPos + int64(trimmedStart)
|
||||
current.TextEnd = current.TextStart + int64(len(text))
|
||||
}
|
||||
}
|
||||
lastPos = offset
|
||||
|
||||
case xml.EndElement:
|
||||
if len(stack) > 0 {
|
||||
current := stack[len(stack)-1]
|
||||
current.EndPos = offset
|
||||
stack = stack[:len(stack)-1]
|
||||
}
|
||||
lastPos = offset
|
||||
}
|
||||
}
|
||||
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// xmlElementToMap converts XMLElement to a map for comparison
|
||||
func xmlElementToMap(elem *XMLElement) map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_tag"] = elem.Tag
|
||||
|
||||
if len(elem.Attributes) > 0 {
|
||||
attrs := make(map[string]interface{})
|
||||
for k, v := range elem.Attributes {
|
||||
attrs[k] = v.Value
|
||||
}
|
||||
result["_attr"] = attrs
|
||||
}
|
||||
|
||||
if elem.Text != "" {
|
||||
result["_text"] = elem.Text
|
||||
}
|
||||
|
||||
if len(elem.Children) > 0 {
|
||||
children := make([]interface{}, len(elem.Children))
|
||||
for i, child := range elem.Children {
|
||||
children[i] = xmlElementToMap(child)
|
||||
}
|
||||
result["_children"] = children
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// XMLChange represents a detected difference between original and modified XML structures
|
||||
type XMLChange struct {
|
||||
Type string // "text", "attribute", "add_element", "remove_element"
|
||||
Path string
|
||||
OldValue string
|
||||
NewValue string
|
||||
StartPos int64
|
||||
EndPos int64
|
||||
InsertText string
|
||||
}
|
||||
|
||||
func findXMLChanges(original, modified *XMLElement, path string) []XMLChange {
|
||||
var changes []XMLChange
|
||||
|
||||
// Check text content changes
|
||||
if original.Text != modified.Text {
|
||||
changes = append(changes, XMLChange{
|
||||
Type: "text",
|
||||
Path: path,
|
||||
OldValue: original.Text,
|
||||
NewValue: modified.Text,
|
||||
StartPos: original.TextStart,
|
||||
EndPos: original.TextEnd,
|
||||
})
|
||||
}
|
||||
|
||||
// Check attribute changes
|
||||
for attrName, origAttr := range original.Attributes {
|
||||
if modAttr, exists := modified.Attributes[attrName]; exists {
|
||||
if origAttr.Value != modAttr.Value {
|
||||
changes = append(changes, XMLChange{
|
||||
Type: "attribute",
|
||||
Path: path + "/@" + attrName,
|
||||
OldValue: origAttr.Value,
|
||||
NewValue: modAttr.Value,
|
||||
StartPos: origAttr.ValueStart,
|
||||
EndPos: origAttr.ValueEnd,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Attribute removed
|
||||
changes = append(changes, XMLChange{
|
||||
Type: "remove_attribute",
|
||||
Path: path + "/@" + attrName,
|
||||
OldValue: origAttr.Value,
|
||||
StartPos: origAttr.ValueStart - int64(len(attrName)+2), // Include attr=" part
|
||||
EndPos: origAttr.ValueEnd + 1, // Include closing "
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Check for added attributes
|
||||
for attrName, modAttr := range modified.Attributes {
|
||||
if _, exists := original.Attributes[attrName]; !exists {
|
||||
changes = append(changes, XMLChange{
|
||||
Type: "add_attribute",
|
||||
Path: path + "/@" + attrName,
|
||||
NewValue: modAttr.Value,
|
||||
StartPos: original.StartPos, // Will be adjusted to insert after tag name
|
||||
InsertText: fmt.Sprintf(` %s="%s"`, attrName, modAttr.Value),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Check children recursively
|
||||
origChildMap := make(map[string][]*XMLElement)
|
||||
for _, child := range original.Children {
|
||||
origChildMap[child.Tag] = append(origChildMap[child.Tag], child)
|
||||
}
|
||||
|
||||
modChildMap := make(map[string][]*XMLElement)
|
||||
for _, child := range modified.Children {
|
||||
modChildMap[child.Tag] = append(modChildMap[child.Tag], child)
|
||||
}
|
||||
|
||||
// Compare children by tag name
|
||||
processedTags := make(map[string]bool)
|
||||
|
||||
for tag, origChildren := range origChildMap {
|
||||
processedTags[tag] = true
|
||||
modChildren := modChildMap[tag]
|
||||
|
||||
// Match children by index
|
||||
maxLen := len(origChildren)
|
||||
if len(modChildren) > maxLen {
|
||||
maxLen = len(modChildren)
|
||||
}
|
||||
|
||||
for i := 0; i < maxLen; i++ {
|
||||
childPath := fmt.Sprintf("%s/%s[%d]", path, tag, i)
|
||||
if i < len(origChildren) && i < len(modChildren) {
|
||||
// Both exist, compare recursively
|
||||
childChanges := findXMLChanges(origChildren[i], modChildren[i], childPath)
|
||||
changes = append(changes, childChanges...)
|
||||
} else if i < len(origChildren) {
|
||||
// Child removed
|
||||
changes = append(changes, XMLChange{
|
||||
Type: "remove_element",
|
||||
Path: childPath,
|
||||
StartPos: origChildren[i].StartPos,
|
||||
EndPos: origChildren[i].EndPos,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Handle added children
|
||||
if len(modChildren) > len(origChildren) {
|
||||
for i := len(origChildren); i < len(modChildren); i++ {
|
||||
childPath := fmt.Sprintf("%s/%s[%d]", path, tag, i)
|
||||
// Generate XML text for the new element
|
||||
xmlText := serializeXMLElement(modChildren[i], " ")
|
||||
changes = append(changes, XMLChange{
|
||||
Type: "add_element",
|
||||
Path: childPath,
|
||||
InsertText: xmlText,
|
||||
StartPos: original.EndPos - int64(len(original.Tag)+3), // Before closing tag
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle completely new tag types
|
||||
for tag, modChildren := range modChildMap {
|
||||
if !processedTags[tag] {
|
||||
for i, child := range modChildren {
|
||||
childPath := fmt.Sprintf("%s/%s[%d]", path, tag, i)
|
||||
xmlText := serializeXMLElement(child, " ")
|
||||
changes = append(changes, XMLChange{
|
||||
Type: "add_element",
|
||||
Path: childPath,
|
||||
InsertText: xmlText,
|
||||
StartPos: original.EndPos - int64(len(original.Tag)+3),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changes
|
||||
}
|
||||
|
||||
// serializeXMLElement converts an XMLElement back to XML text
|
||||
func serializeXMLElement(elem *XMLElement, indent string) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(indent)
|
||||
sb.WriteString("<")
|
||||
sb.WriteString(elem.Tag)
|
||||
|
||||
// Write attributes
|
||||
attrNames := make([]string, 0, len(elem.Attributes))
|
||||
for name := range elem.Attributes {
|
||||
attrNames = append(attrNames, name)
|
||||
}
|
||||
sort.Strings(attrNames)
|
||||
|
||||
for _, name := range attrNames {
|
||||
attr := elem.Attributes[name]
|
||||
sb.WriteString(fmt.Sprintf(` %s="%s"`, name, attr.Value))
|
||||
}
|
||||
|
||||
if elem.Text == "" && len(elem.Children) == 0 {
|
||||
sb.WriteString(" />")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
sb.WriteString(">")
|
||||
|
||||
if elem.Text != "" {
|
||||
sb.WriteString(elem.Text)
|
||||
}
|
||||
|
||||
if len(elem.Children) > 0 {
|
||||
sb.WriteString("\n")
|
||||
for _, child := range elem.Children {
|
||||
sb.WriteString(serializeXMLElement(child, indent+" "))
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
sb.WriteString(indent)
|
||||
}
|
||||
|
||||
sb.WriteString("</")
|
||||
sb.WriteString(elem.Tag)
|
||||
sb.WriteString(">")
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// applyXMLChanges generates ReplaceCommands from detected XML changes
|
||||
func applyXMLChanges(changes []XMLChange) []utils.ReplaceCommand {
|
||||
var commands []utils.ReplaceCommand
|
||||
|
||||
for _, change := range changes {
|
||||
switch change.Type {
|
||||
case "text":
|
||||
commands = append(commands, utils.ReplaceCommand{
|
||||
From: int(change.StartPos),
|
||||
To: int(change.EndPos),
|
||||
With: change.NewValue,
|
||||
})
|
||||
|
||||
case "attribute":
|
||||
commands = append(commands, utils.ReplaceCommand{
|
||||
From: int(change.StartPos),
|
||||
To: int(change.EndPos),
|
||||
With: change.NewValue,
|
||||
})
|
||||
|
||||
case "add_attribute":
|
||||
// Insert after tag name, before > or />
|
||||
commands = append(commands, utils.ReplaceCommand{
|
||||
From: int(change.StartPos),
|
||||
To: int(change.StartPos),
|
||||
With: change.InsertText,
|
||||
})
|
||||
|
||||
case "remove_attribute":
|
||||
commands = append(commands, utils.ReplaceCommand{
|
||||
From: int(change.StartPos),
|
||||
To: int(change.EndPos),
|
||||
With: "",
|
||||
})
|
||||
|
||||
case "add_element":
|
||||
commands = append(commands, utils.ReplaceCommand{
|
||||
From: int(change.StartPos),
|
||||
To: int(change.StartPos),
|
||||
With: "\n" + change.InsertText,
|
||||
})
|
||||
|
||||
case "remove_element":
|
||||
commands = append(commands, utils.ReplaceCommand{
|
||||
From: int(change.StartPos),
|
||||
To: int(change.EndPos),
|
||||
With: "",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return commands
|
||||
}
|
||||
|
||||
// modifyXMLElement applies modifications to an XMLElement based on a modification function
|
||||
func modifyXMLElement(elem *XMLElement, modifyFunc func(*XMLElement)) *XMLElement {
|
||||
// Deep copy the element
|
||||
copied := deepCopyXMLElement(elem)
|
||||
modifyFunc(copied)
|
||||
return copied
|
||||
}
|
||||
|
||||
// deepCopyXMLElement creates a deep copy of an XMLElement
|
||||
func deepCopyXMLElement(elem *XMLElement) *XMLElement {
|
||||
if elem == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
copied := &XMLElement{
|
||||
Tag: elem.Tag,
|
||||
Text: elem.Text,
|
||||
StartPos: elem.StartPos,
|
||||
EndPos: elem.EndPos,
|
||||
TextStart: elem.TextStart,
|
||||
TextEnd: elem.TextEnd,
|
||||
Attributes: make(map[string]XMLAttribute),
|
||||
Children: make([]*XMLElement, len(elem.Children)),
|
||||
}
|
||||
|
||||
for k, v := range elem.Attributes {
|
||||
copied.Attributes[k] = v
|
||||
}
|
||||
|
||||
for i, child := range elem.Children {
|
||||
copied.Children[i] = deepCopyXMLElement(child)
|
||||
}
|
||||
|
||||
return copied
|
||||
}
|
||||
|
||||
// Helper function to parse numeric values
|
||||
func parseNumeric(s string) (float64, bool) {
|
||||
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
||||
return f, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Helper function to format numeric values
|
||||
func formatNumeric(f float64) string {
|
||||
if f == float64(int64(f)) {
|
||||
return strconv.FormatInt(int64(f), 10)
|
||||
}
|
||||
return strconv.FormatFloat(f, 'f', -1, 64)
|
||||
}
|
||||
346
processor/xml_integration_test.go
Normal file
346
processor/xml_integration_test.go
Normal file
@@ -0,0 +1,346 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"cook/utils"
|
||||
)
|
||||
|
||||
// TestRealWorldGameXML tests with game-like XML structure
|
||||
func TestRealWorldGameXML(t *testing.T) {
|
||||
original := `<?xml version="1.0" encoding="utf-8"?>
|
||||
<Items>
|
||||
<Item name="Fiber" identifier="Item_Fiber" category="Resource">
|
||||
<Icon texture="Items/Fiber.png" />
|
||||
<Weight value="0.01" />
|
||||
<MaxStack value="1000" />
|
||||
<Description text="Soft plant fibers useful for crafting." />
|
||||
</Item>
|
||||
<Item name="Wood" identifier="Item_Wood" category="Resource">
|
||||
<Icon texture="Items/Wood.png" />
|
||||
<Weight value="0.05" />
|
||||
<MaxStack value="500" />
|
||||
<Description text="Basic building material." />
|
||||
</Item>
|
||||
</Items>`
|
||||
|
||||
// Parse
|
||||
origElem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse: %v", err)
|
||||
}
|
||||
|
||||
// Modify: Double all MaxStack values and change Wood weight
|
||||
modElem := deepCopyXMLElement(origElem)
|
||||
|
||||
// Fiber MaxStack: 1000 → 2000
|
||||
fiberItem := modElem.Children[0]
|
||||
fiberMaxStack := fiberItem.Children[2]
|
||||
valueAttr := fiberMaxStack.Attributes["value"]
|
||||
valueAttr.Value = "2000"
|
||||
fiberMaxStack.Attributes["value"] = valueAttr
|
||||
|
||||
// Wood MaxStack: 500 → 1000
|
||||
woodItem := modElem.Children[1]
|
||||
woodMaxStack := woodItem.Children[2]
|
||||
valueAttr2 := woodMaxStack.Attributes["value"]
|
||||
valueAttr2.Value = "1000"
|
||||
woodMaxStack.Attributes["value"] = valueAttr2
|
||||
|
||||
// Wood Weight: 0.05 → 0.10
|
||||
woodWeight := woodItem.Children[1]
|
||||
weightAttr := woodWeight.Attributes["value"]
|
||||
weightAttr.Value = "0.10"
|
||||
woodWeight.Attributes["value"] = weightAttr
|
||||
|
||||
// Generate changes
|
||||
changes := findXMLChanges(origElem, modElem, "")
|
||||
|
||||
if len(changes) != 3 {
|
||||
t.Fatalf("Expected 3 changes, got %d", len(changes))
|
||||
}
|
||||
|
||||
// Apply
|
||||
commands := applyXMLChanges(changes)
|
||||
result, _ := utils.ExecuteModifications(commands, original)
|
||||
|
||||
// Verify changes
|
||||
if !strings.Contains(result, `<MaxStack value="2000"`) {
|
||||
t.Errorf("Failed to update Fiber MaxStack")
|
||||
}
|
||||
if !strings.Contains(result, `<MaxStack value="1000"`) {
|
||||
t.Errorf("Failed to update Wood MaxStack")
|
||||
}
|
||||
if !strings.Contains(result, `<Weight value="0.10"`) {
|
||||
t.Errorf("Failed to update Wood Weight")
|
||||
}
|
||||
|
||||
// Verify formatting preserved (check XML declaration and indentation)
|
||||
if !strings.HasPrefix(result, `<?xml version="1.0" encoding="utf-8"?>`) {
|
||||
t.Errorf("XML declaration not preserved")
|
||||
}
|
||||
if !strings.Contains(result, "\n <Item") {
|
||||
t.Errorf("Indentation not preserved")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAddRemoveMultipleChildren tests adding and removing multiple elements
|
||||
func TestAddRemoveMultipleChildren(t *testing.T) {
|
||||
original := `<inventory>
|
||||
<item name="sword" />
|
||||
<item name="shield" />
|
||||
<item name="potion" />
|
||||
<item name="scroll" />
|
||||
</inventory>`
|
||||
|
||||
// Parse
|
||||
origElem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse: %v", err)
|
||||
}
|
||||
|
||||
// Remove middle two items, add a new one
|
||||
modElem := deepCopyXMLElement(origElem)
|
||||
|
||||
// Remove shield and potion (indices 1 and 2)
|
||||
modElem.Children = []*XMLElement{
|
||||
modElem.Children[0], // sword
|
||||
modElem.Children[3], // scroll
|
||||
}
|
||||
|
||||
// Add a new item
|
||||
newItem := &XMLElement{
|
||||
Tag: "item",
|
||||
Attributes: map[string]XMLAttribute{
|
||||
"name": {Value: "helmet"},
|
||||
},
|
||||
Children: []*XMLElement{},
|
||||
}
|
||||
modElem.Children = append(modElem.Children, newItem)
|
||||
|
||||
// Generate changes
|
||||
changes := findXMLChanges(origElem, modElem, "")
|
||||
|
||||
// The algorithm compares by matching indices:
|
||||
// orig[0]=sword vs mod[0]=sword (no change)
|
||||
// orig[1]=shield vs mod[1]=scroll (treated as replace - shows as attribute changes)
|
||||
// orig[2]=potion vs mod[2]=helmet (treated as replace)
|
||||
// orig[3]=scroll (removed)
|
||||
// This is fine - the actual edits will be correct
|
||||
|
||||
if len(changes) == 0 {
|
||||
t.Fatalf("Expected changes, got none")
|
||||
}
|
||||
|
||||
// Apply
|
||||
commands := applyXMLChanges(changes)
|
||||
result, _ := utils.ExecuteModifications(commands, original)
|
||||
|
||||
// Verify
|
||||
if strings.Contains(result, `name="shield"`) {
|
||||
t.Errorf("Shield not removed")
|
||||
}
|
||||
if strings.Contains(result, `name="potion"`) {
|
||||
t.Errorf("Potion not removed")
|
||||
}
|
||||
if !strings.Contains(result, `name="sword"`) {
|
||||
t.Errorf("Sword incorrectly removed")
|
||||
}
|
||||
if !strings.Contains(result, `name="scroll"`) {
|
||||
t.Errorf("Scroll incorrectly removed")
|
||||
}
|
||||
if !strings.Contains(result, `name="helmet"`) {
|
||||
t.Errorf("Helmet not added")
|
||||
}
|
||||
}
|
||||
|
||||
// TestModifyAttributesAndText tests changing both attributes and text content
|
||||
func TestModifyAttributesAndText(t *testing.T) {
|
||||
original := `<weapon>
|
||||
<item type="sword" damage="10">Iron Sword</item>
|
||||
<item type="axe" damage="15">Battle Axe</item>
|
||||
</weapon>`
|
||||
|
||||
// Parse
|
||||
origElem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse: %v", err)
|
||||
}
|
||||
|
||||
// Modify both items
|
||||
modElem := deepCopyXMLElement(origElem)
|
||||
|
||||
// First item: change damage and text
|
||||
item1 := modElem.Children[0]
|
||||
dmgAttr := item1.Attributes["damage"]
|
||||
dmgAttr.Value = "20"
|
||||
item1.Attributes["damage"] = dmgAttr
|
||||
item1.Text = "Steel Sword"
|
||||
|
||||
// Second item: change damage and type
|
||||
item2 := modElem.Children[1]
|
||||
dmgAttr2 := item2.Attributes["damage"]
|
||||
dmgAttr2.Value = "30"
|
||||
item2.Attributes["damage"] = dmgAttr2
|
||||
typeAttr := item2.Attributes["type"]
|
||||
typeAttr.Value = "greataxe"
|
||||
item2.Attributes["type"] = typeAttr
|
||||
|
||||
// Generate and apply changes
|
||||
changes := findXMLChanges(origElem, modElem, "")
|
||||
commands := applyXMLChanges(changes)
|
||||
result, _ := utils.ExecuteModifications(commands, original)
|
||||
|
||||
// Verify
|
||||
if !strings.Contains(result, `damage="20"`) {
|
||||
t.Errorf("First item damage not updated")
|
||||
}
|
||||
if !strings.Contains(result, "Steel Sword") {
|
||||
t.Errorf("First item text not updated")
|
||||
}
|
||||
if !strings.Contains(result, `damage="30"`) {
|
||||
t.Errorf("Second item damage not updated")
|
||||
}
|
||||
if !strings.Contains(result, `type="greataxe"`) {
|
||||
t.Errorf("Second item type not updated")
|
||||
}
|
||||
if strings.Contains(result, "Iron Sword") {
|
||||
t.Errorf("Old text still present")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSelfClosingTagPreservation tests that self-closing tags work correctly
|
||||
func TestSelfClosingTagPreservation(t *testing.T) {
|
||||
original := `<root>
|
||||
<item name="test" />
|
||||
<empty></empty>
|
||||
</root>`
|
||||
|
||||
// Parse
|
||||
origElem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse: %v", err)
|
||||
}
|
||||
|
||||
// Modify first item's attribute
|
||||
modElem := deepCopyXMLElement(origElem)
|
||||
item := modElem.Children[0]
|
||||
nameAttr := item.Attributes["name"]
|
||||
nameAttr.Value = "modified"
|
||||
item.Attributes["name"] = nameAttr
|
||||
|
||||
// Generate and apply changes
|
||||
changes := findXMLChanges(origElem, modElem, "")
|
||||
commands := applyXMLChanges(changes)
|
||||
result, _ := utils.ExecuteModifications(commands, original)
|
||||
|
||||
// Verify the change was made
|
||||
if !strings.Contains(result, `name="modified"`) {
|
||||
t.Errorf("Attribute not updated: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNumericAttributeModification tests numeric attribute changes
|
||||
func TestNumericAttributeModification(t *testing.T) {
|
||||
original := `<stats health="100" mana="50" stamina="75.5" />`
|
||||
|
||||
// Parse
|
||||
origElem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse: %v", err)
|
||||
}
|
||||
|
||||
// Double all numeric values
|
||||
modElem := deepCopyXMLElement(origElem)
|
||||
|
||||
// Helper to modify numeric attributes
|
||||
modifyNumericAttr := func(attrName string, multiplier float64) {
|
||||
if attr, exists := modElem.Attributes[attrName]; exists {
|
||||
if val, ok := parseNumeric(attr.Value); ok {
|
||||
attr.Value = formatNumeric(val * multiplier)
|
||||
modElem.Attributes[attrName] = attr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modifyNumericAttr("health", 2.0)
|
||||
modifyNumericAttr("mana", 2.0)
|
||||
modifyNumericAttr("stamina", 2.0)
|
||||
|
||||
// Generate and apply changes
|
||||
changes := findXMLChanges(origElem, modElem, "")
|
||||
|
||||
if len(changes) != 3 {
|
||||
t.Fatalf("Expected 3 changes, got %d", len(changes))
|
||||
}
|
||||
|
||||
commands := applyXMLChanges(changes)
|
||||
result, _ := utils.ExecuteModifications(commands, original)
|
||||
|
||||
// Verify numeric changes
|
||||
if !strings.Contains(result, `health="200"`) {
|
||||
t.Errorf("Health not doubled: %s", result)
|
||||
}
|
||||
if !strings.Contains(result, `mana="100"`) {
|
||||
t.Errorf("Mana not doubled: %s", result)
|
||||
}
|
||||
if !strings.Contains(result, `stamina="151"`) {
|
||||
t.Errorf("Stamina not doubled: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMinimalGitDiff verifies that only changed parts are modified
|
||||
func TestMinimalGitDiff(t *testing.T) {
|
||||
original := `<config>
|
||||
<setting name="volume" value="50" />
|
||||
<setting name="brightness" value="75" />
|
||||
<setting name="contrast" value="100" />
|
||||
</config>`
|
||||
|
||||
// Parse
|
||||
origElem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse: %v", err)
|
||||
}
|
||||
|
||||
// Change only brightness
|
||||
modElem := deepCopyXMLElement(origElem)
|
||||
brightnessItem := modElem.Children[1]
|
||||
valueAttr := brightnessItem.Attributes["value"]
|
||||
valueAttr.Value = "90"
|
||||
brightnessItem.Attributes["value"] = valueAttr
|
||||
|
||||
// Generate changes
|
||||
changes := findXMLChanges(origElem, modElem, "")
|
||||
|
||||
// Should be exactly 1 change
|
||||
if len(changes) != 1 {
|
||||
t.Fatalf("Expected exactly 1 change for minimal diff, got %d", len(changes))
|
||||
}
|
||||
|
||||
if changes[0].OldValue != "75" || changes[0].NewValue != "90" {
|
||||
t.Errorf("Wrong change detected: %v", changes[0])
|
||||
}
|
||||
|
||||
// Apply
|
||||
commands := applyXMLChanges(changes)
|
||||
result, _ := utils.ExecuteModifications(commands, original)
|
||||
|
||||
// Calculate diff size (rough approximation)
|
||||
diffChars := len(changes[0].OldValue) + len(changes[0].NewValue)
|
||||
if diffChars > 10 {
|
||||
t.Errorf("Diff too large: %d characters changed (expected < 10)", diffChars)
|
||||
}
|
||||
|
||||
// Verify only brightness changed
|
||||
if !strings.Contains(result, `value="50"`) {
|
||||
t.Errorf("Volume incorrectly modified")
|
||||
}
|
||||
if !strings.Contains(result, `value="90"`) {
|
||||
t.Errorf("Brightness not modified")
|
||||
}
|
||||
if !strings.Contains(result, `value="100"`) {
|
||||
t.Errorf("Contrast incorrectly modified")
|
||||
}
|
||||
}
|
||||
621
processor/xml_test.go
Normal file
621
processor/xml_test.go
Normal file
@@ -0,0 +1,621 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"cook/utils"
|
||||
)
|
||||
|
||||
func TestParseXMLWithPositions(t *testing.T) {
|
||||
xml := `<root><item name="test">Hello</item></root>`
|
||||
|
||||
elem, err := parseXMLWithPositions(xml)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse XML: %v", err)
|
||||
}
|
||||
|
||||
if elem.Tag != "root" {
|
||||
t.Errorf("Expected root tag 'root', got '%s'", elem.Tag)
|
||||
}
|
||||
|
||||
if len(elem.Children) != 1 {
|
||||
t.Fatalf("Expected 1 child, got %d", len(elem.Children))
|
||||
}
|
||||
|
||||
child := elem.Children[0]
|
||||
if child.Tag != "item" {
|
||||
t.Errorf("Expected child tag 'item', got '%s'", child.Tag)
|
||||
}
|
||||
|
||||
if child.Attributes["name"].Value != "test" {
|
||||
t.Errorf("Expected attribute 'name' to be 'test', got '%s'", child.Attributes["name"].Value)
|
||||
}
|
||||
|
||||
if child.Text != "Hello" {
|
||||
t.Errorf("Expected text 'Hello', got '%s'", child.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSurgicalTextChange(t *testing.T) {
|
||||
original := `<root>
|
||||
<item name="sword" weight="10">A sword</item>
|
||||
</root>`
|
||||
|
||||
expected := `<root>
|
||||
<item name="sword" weight="10">A modified sword</item>
|
||||
</root>`
|
||||
|
||||
// Parse original
|
||||
origElem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse original XML: %v", err)
|
||||
}
|
||||
|
||||
// Create modified version
|
||||
modElem := deepCopyXMLElement(origElem)
|
||||
modElem.Children[0].Text = "A modified sword"
|
||||
|
||||
// Find changes
|
||||
changes := findXMLChanges(origElem, modElem, "")
|
||||
|
||||
if len(changes) != 1 {
|
||||
t.Fatalf("Expected 1 change, got %d", len(changes))
|
||||
}
|
||||
|
||||
if changes[0].Type != "text" {
|
||||
t.Errorf("Expected change type 'text', got '%s'", changes[0].Type)
|
||||
}
|
||||
|
||||
// Apply changes
|
||||
commands := applyXMLChanges(changes)
|
||||
result, _ := utils.ExecuteModifications(commands, original)
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("Text change failed.\nExpected:\n%s\n\nGot:\n%s", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSurgicalAttributeChange(t *testing.T) {
|
||||
original := `<root>
|
||||
<item name="sword" weight="10" />
|
||||
</root>`
|
||||
|
||||
expected := `<root>
|
||||
<item name="sword" weight="20" />
|
||||
</root>`
|
||||
|
||||
// Parse original
|
||||
origElem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse original XML: %v", err)
|
||||
}
|
||||
|
||||
// Create modified version
|
||||
modElem := deepCopyXMLElement(origElem)
|
||||
attr := modElem.Children[0].Attributes["weight"]
|
||||
attr.Value = "20"
|
||||
modElem.Children[0].Attributes["weight"] = attr
|
||||
|
||||
// Find changes
|
||||
changes := findXMLChanges(origElem, modElem, "")
|
||||
|
||||
if len(changes) != 1 {
|
||||
t.Fatalf("Expected 1 change, got %d", len(changes))
|
||||
}
|
||||
|
||||
if changes[0].Type != "attribute" {
|
||||
t.Errorf("Expected change type 'attribute', got '%s'", changes[0].Type)
|
||||
}
|
||||
|
||||
// Apply changes
|
||||
commands := applyXMLChanges(changes)
|
||||
result, _ := utils.ExecuteModifications(commands, original)
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("Attribute change failed.\nExpected:\n%s\n\nGot:\n%s", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSurgicalMultipleAttributeChanges(t *testing.T) {
|
||||
original := `<item name="sword" weight="10" damage="5" />`
|
||||
|
||||
expected := `<item name="greatsword" weight="20" damage="15" />`
|
||||
|
||||
// Parse original
|
||||
origElem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse original XML: %v", err)
|
||||
}
|
||||
|
||||
// Create modified version
|
||||
modElem := deepCopyXMLElement(origElem)
|
||||
|
||||
nameAttr := modElem.Attributes["name"]
|
||||
nameAttr.Value = "greatsword"
|
||||
modElem.Attributes["name"] = nameAttr
|
||||
|
||||
weightAttr := modElem.Attributes["weight"]
|
||||
weightAttr.Value = "20"
|
||||
modElem.Attributes["weight"] = weightAttr
|
||||
|
||||
damageAttr := modElem.Attributes["damage"]
|
||||
damageAttr.Value = "15"
|
||||
modElem.Attributes["damage"] = damageAttr
|
||||
|
||||
// Find changes
|
||||
changes := findXMLChanges(origElem, modElem, "")
|
||||
|
||||
if len(changes) != 3 {
|
||||
t.Fatalf("Expected 3 changes, got %d", len(changes))
|
||||
}
|
||||
|
||||
// Apply changes
|
||||
commands := applyXMLChanges(changes)
|
||||
result, _ := utils.ExecuteModifications(commands, original)
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("Multiple attribute changes failed.\nExpected:\n%s\n\nGot:\n%s", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSurgicalAddAttribute(t *testing.T) {
|
||||
original := `<item name="sword" />`
|
||||
|
||||
// Parse original
|
||||
origElem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse original XML: %v", err)
|
||||
}
|
||||
|
||||
// Create modified version with new attribute
|
||||
modElem := deepCopyXMLElement(origElem)
|
||||
modElem.Attributes["weight"] = XMLAttribute{
|
||||
Value: "10",
|
||||
}
|
||||
|
||||
// Find changes
|
||||
changes := findXMLChanges(origElem, modElem, "")
|
||||
|
||||
if len(changes) != 1 {
|
||||
t.Fatalf("Expected 1 change, got %d", len(changes))
|
||||
}
|
||||
|
||||
if changes[0].Type != "add_attribute" {
|
||||
t.Errorf("Expected change type 'add_attribute', got '%s'", changes[0].Type)
|
||||
}
|
||||
|
||||
// Apply changes
|
||||
commands := applyXMLChanges(changes)
|
||||
result, _ := utils.ExecuteModifications(commands, original)
|
||||
|
||||
// Should contain the new attribute
|
||||
if !strings.Contains(result, `weight="10"`) {
|
||||
t.Errorf("Add attribute failed. Result doesn't contain weight=\"10\":\n%s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSurgicalRemoveAttribute(t *testing.T) {
|
||||
original := `<item name="sword" weight="10" damage="5" />`
|
||||
|
||||
// Parse original
|
||||
origElem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse original XML: %v", err)
|
||||
}
|
||||
|
||||
// Create modified version without weight attribute
|
||||
modElem := deepCopyXMLElement(origElem)
|
||||
delete(modElem.Attributes, "weight")
|
||||
|
||||
// Find changes
|
||||
changes := findXMLChanges(origElem, modElem, "")
|
||||
|
||||
if len(changes) != 1 {
|
||||
t.Fatalf("Expected 1 change, got %d", len(changes))
|
||||
}
|
||||
|
||||
if changes[0].Type != "remove_attribute" {
|
||||
t.Errorf("Expected change type 'remove_attribute', got '%s'", changes[0].Type)
|
||||
}
|
||||
|
||||
// Apply changes
|
||||
commands := applyXMLChanges(changes)
|
||||
result, _ := utils.ExecuteModifications(commands, original)
|
||||
|
||||
// Should not contain weight attribute
|
||||
if strings.Contains(result, "weight=") {
|
||||
t.Errorf("Remove attribute failed. Result still contains 'weight=':\n%s", result)
|
||||
}
|
||||
|
||||
// Should still contain other attributes
|
||||
if !strings.Contains(result, `name="sword"`) {
|
||||
t.Errorf("Remove attribute incorrectly removed other attributes:\n%s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSurgicalAddElement(t *testing.T) {
|
||||
original := `<root>
|
||||
<item name="sword" />
|
||||
</root>`
|
||||
|
||||
// Parse original
|
||||
origElem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse original XML: %v", err)
|
||||
}
|
||||
|
||||
// Create modified version with new child
|
||||
modElem := deepCopyXMLElement(origElem)
|
||||
newChild := &XMLElement{
|
||||
Tag: "item",
|
||||
Attributes: map[string]XMLAttribute{
|
||||
"name": {Value: "shield"},
|
||||
},
|
||||
Children: []*XMLElement{},
|
||||
}
|
||||
modElem.Children = append(modElem.Children, newChild)
|
||||
|
||||
// Find changes
|
||||
changes := findXMLChanges(origElem, modElem, "")
|
||||
|
||||
if len(changes) != 1 {
|
||||
t.Fatalf("Expected 1 change, got %d", len(changes))
|
||||
}
|
||||
|
||||
if changes[0].Type != "add_element" {
|
||||
t.Errorf("Expected change type 'add_element', got '%s'", changes[0].Type)
|
||||
}
|
||||
|
||||
// Apply changes
|
||||
commands := applyXMLChanges(changes)
|
||||
result, _ := utils.ExecuteModifications(commands, original)
|
||||
|
||||
// Should contain the new element
|
||||
if !strings.Contains(result, `<item name="shield"`) {
|
||||
t.Errorf("Add element failed. Result doesn't contain new item:\n%s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSurgicalRemoveElement(t *testing.T) {
|
||||
original := `<root>
|
||||
<item name="sword" />
|
||||
<item name="shield" />
|
||||
</root>`
|
||||
|
||||
expected := `<root>
|
||||
<item name="sword" />
|
||||
|
||||
</root>`
|
||||
|
||||
// Parse original
|
||||
origElem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse original XML: %v", err)
|
||||
}
|
||||
|
||||
// Create modified version without second child
|
||||
modElem := deepCopyXMLElement(origElem)
|
||||
modElem.Children = modElem.Children[:1]
|
||||
|
||||
// Find changes
|
||||
changes := findXMLChanges(origElem, modElem, "")
|
||||
|
||||
if len(changes) != 1 {
|
||||
t.Fatalf("Expected 1 change, got %d", len(changes))
|
||||
}
|
||||
|
||||
if changes[0].Type != "remove_element" {
|
||||
t.Errorf("Expected change type 'remove_element', got '%s'", changes[0].Type)
|
||||
}
|
||||
|
||||
// Apply changes
|
||||
commands := applyXMLChanges(changes)
|
||||
result, _ := utils.ExecuteModifications(commands, original)
|
||||
|
||||
// Should not contain shield
|
||||
if strings.Contains(result, "shield") {
|
||||
t.Errorf("Remove element failed. Result still contains 'shield':\n%s", result)
|
||||
}
|
||||
|
||||
// Should still contain sword
|
||||
if !strings.Contains(result, "sword") {
|
||||
t.Errorf("Remove element incorrectly removed other elements:\n%s", result)
|
||||
}
|
||||
|
||||
// Normalize whitespace for comparison
|
||||
resultNorm := strings.TrimSpace(result)
|
||||
expectedNorm := strings.TrimSpace(expected)
|
||||
|
||||
if resultNorm != expectedNorm {
|
||||
t.Errorf("Remove element result mismatch.\nExpected:\n%s\n\nGot:\n%s", expectedNorm, resultNorm)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComplexNestedChanges(t *testing.T) {
|
||||
original := `<root>
|
||||
<inventory>
|
||||
<item name="sword" weight="10">
|
||||
<stats damage="5" speed="3" />
|
||||
</item>
|
||||
<item name="shield" weight="8">
|
||||
<stats defense="7" />
|
||||
</item>
|
||||
</inventory>
|
||||
</root>`
|
||||
|
||||
// Parse original
|
||||
origElem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse original XML: %v", err)
|
||||
}
|
||||
|
||||
// Create modified version with multiple changes
|
||||
modElem := deepCopyXMLElement(origElem)
|
||||
|
||||
// Change first item's weight
|
||||
inventory := modElem.Children[0]
|
||||
item1 := inventory.Children[0]
|
||||
weightAttr := item1.Attributes["weight"]
|
||||
weightAttr.Value = "20"
|
||||
item1.Attributes["weight"] = weightAttr
|
||||
|
||||
// Change nested stats damage
|
||||
stats := item1.Children[0]
|
||||
damageAttr := stats.Attributes["damage"]
|
||||
damageAttr.Value = "10"
|
||||
stats.Attributes["damage"] = damageAttr
|
||||
|
||||
// Change second item's name
|
||||
item2 := inventory.Children[1]
|
||||
nameAttr := item2.Attributes["name"]
|
||||
nameAttr.Value = "buckler"
|
||||
item2.Attributes["name"] = nameAttr
|
||||
|
||||
// Find changes
|
||||
changes := findXMLChanges(origElem, modElem, "")
|
||||
|
||||
// Should have 3 changes: weight, damage, name
|
||||
if len(changes) != 3 {
|
||||
t.Fatalf("Expected 3 changes, got %d: %+v", len(changes), changes)
|
||||
}
|
||||
|
||||
// Apply changes
|
||||
commands := applyXMLChanges(changes)
|
||||
result, _ := utils.ExecuteModifications(commands, original)
|
||||
|
||||
// Verify all changes were applied
|
||||
if !strings.Contains(result, `weight="20"`) {
|
||||
t.Errorf("Failed to update weight to 20:\n%s", result)
|
||||
}
|
||||
if !strings.Contains(result, `damage="10"`) {
|
||||
t.Errorf("Failed to update damage to 10:\n%s", result)
|
||||
}
|
||||
if !strings.Contains(result, `name="buckler"`) {
|
||||
t.Errorf("Failed to update name to buckler:\n%s", result)
|
||||
}
|
||||
|
||||
// Verify unchanged elements remain
|
||||
if !strings.Contains(result, `speed="3"`) {
|
||||
t.Errorf("Incorrectly modified speed attribute:\n%s", result)
|
||||
}
|
||||
if !strings.Contains(result, `defense="7"`) {
|
||||
t.Errorf("Incorrectly modified defense attribute:\n%s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormattingPreservation(t *testing.T) {
|
||||
original := `<root>
|
||||
<item name="sword" weight="10">
|
||||
<description>A sharp blade</description>
|
||||
<stats damage="5" speed="3" />
|
||||
</item>
|
||||
</root>`
|
||||
|
||||
expected := `<root>
|
||||
<item name="sword" weight="20">
|
||||
<description>A sharp blade</description>
|
||||
<stats damage="5" speed="3" />
|
||||
</item>
|
||||
</root>`
|
||||
|
||||
// Parse original
|
||||
origElem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse original XML: %v", err)
|
||||
}
|
||||
|
||||
// Modify only weight
|
||||
modElem := deepCopyXMLElement(origElem)
|
||||
item := modElem.Children[0]
|
||||
weightAttr := item.Attributes["weight"]
|
||||
weightAttr.Value = "20"
|
||||
item.Attributes["weight"] = weightAttr
|
||||
|
||||
// Find changes
|
||||
changes := findXMLChanges(origElem, modElem, "")
|
||||
|
||||
// Apply changes
|
||||
commands := applyXMLChanges(changes)
|
||||
result, _ := utils.ExecuteModifications(commands, original)
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("Formatting preservation failed.\nExpected:\n%s\n\nGot:\n%s", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumericHelpers(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected float64
|
||||
isNum bool
|
||||
}{
|
||||
{"42", 42.0, true},
|
||||
{"3.14", 3.14, true},
|
||||
{"0", 0.0, true},
|
||||
{"-5", -5.0, true},
|
||||
{"abc", 0.0, false},
|
||||
{"", 0.0, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
val, ok := parseNumeric(tt.input)
|
||||
if ok != tt.isNum {
|
||||
t.Errorf("parseNumeric(%q) isNum = %v, expected %v", tt.input, ok, tt.isNum)
|
||||
}
|
||||
if ok && val != tt.expected {
|
||||
t.Errorf("parseNumeric(%q) = %v, expected %v", tt.input, val, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
// Test formatting
|
||||
formatTests := []struct {
|
||||
input float64
|
||||
expected string
|
||||
}{
|
||||
{42.0, "42"},
|
||||
{3.14, "3.14"},
|
||||
{0.0, "0"},
|
||||
{-5.0, "-5"},
|
||||
{100.5, "100.5"},
|
||||
}
|
||||
|
||||
for _, tt := range formatTests {
|
||||
result := formatNumeric(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("formatNumeric(%v) = %q, expected %q", tt.input, result, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeepCopyXMLElement(t *testing.T) {
|
||||
original := &XMLElement{
|
||||
Tag: "item",
|
||||
Text: "content",
|
||||
Attributes: map[string]XMLAttribute{
|
||||
"name": {Value: "sword"},
|
||||
},
|
||||
Children: []*XMLElement{
|
||||
{Tag: "child", Text: "text"},
|
||||
},
|
||||
}
|
||||
|
||||
copied := deepCopyXMLElement(original)
|
||||
|
||||
// Verify copy is equal
|
||||
if copied.Tag != original.Tag {
|
||||
t.Errorf("Tag not copied correctly")
|
||||
}
|
||||
if copied.Text != original.Text {
|
||||
t.Errorf("Text not copied correctly")
|
||||
}
|
||||
|
||||
// Modify copy
|
||||
copied.Tag = "modified"
|
||||
copied.Attributes["name"] = XMLAttribute{Value: "shield"}
|
||||
copied.Children[0].Text = "modified text"
|
||||
|
||||
// Verify original unchanged
|
||||
if original.Tag != "item" {
|
||||
t.Errorf("Original was modified")
|
||||
}
|
||||
if original.Attributes["name"].Value != "sword" {
|
||||
t.Errorf("Original attributes were modified")
|
||||
}
|
||||
if original.Children[0].Text != "text" {
|
||||
t.Errorf("Original children were modified")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSerializeXMLElement(t *testing.T) {
|
||||
elem := &XMLElement{
|
||||
Tag: "item",
|
||||
Attributes: map[string]XMLAttribute{
|
||||
"name": {Value: "sword"},
|
||||
"weight": {Value: "10"},
|
||||
},
|
||||
Children: []*XMLElement{
|
||||
{
|
||||
Tag: "stats",
|
||||
Attributes: map[string]XMLAttribute{
|
||||
"damage": {Value: "5"},
|
||||
},
|
||||
Children: []*XMLElement{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := serializeXMLElement(elem, "")
|
||||
|
||||
// Check it contains expected parts
|
||||
if !strings.Contains(result, "<item") {
|
||||
t.Errorf("Missing opening tag")
|
||||
}
|
||||
if !strings.Contains(result, "</item>") {
|
||||
t.Errorf("Missing closing tag")
|
||||
}
|
||||
if !strings.Contains(result, `name="sword"`) {
|
||||
t.Errorf("Missing name attribute")
|
||||
}
|
||||
if !strings.Contains(result, `weight="10"`) {
|
||||
t.Errorf("Missing weight attribute")
|
||||
}
|
||||
if !strings.Contains(result, "<stats") {
|
||||
t.Errorf("Missing child element")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyElements(t *testing.T) {
|
||||
original := `<root>
|
||||
<item name="sword" />
|
||||
<item name="shield"></item>
|
||||
</root>`
|
||||
|
||||
// Parse
|
||||
elem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse XML: %v", err)
|
||||
}
|
||||
|
||||
if len(elem.Children) != 2 {
|
||||
t.Errorf("Expected 2 children, got %d", len(elem.Children))
|
||||
}
|
||||
|
||||
// Both should be parsed correctly
|
||||
if elem.Children[0].Tag != "item" {
|
||||
t.Errorf("First child tag incorrect")
|
||||
}
|
||||
if elem.Children[1].Tag != "item" {
|
||||
t.Errorf("Second child tag incorrect")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttributeOrderPreservation(t *testing.T) {
|
||||
original := `<item name="sword" weight="10" damage="5" speed="3" />`
|
||||
|
||||
// Parse original
|
||||
origElem, err := parseXMLWithPositions(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse original XML: %v", err)
|
||||
}
|
||||
|
||||
// Modify just weight
|
||||
modElem := deepCopyXMLElement(origElem)
|
||||
weightAttr := modElem.Attributes["weight"]
|
||||
weightAttr.Value = "20"
|
||||
modElem.Attributes["weight"] = weightAttr
|
||||
|
||||
// Find and apply changes
|
||||
changes := findXMLChanges(origElem, modElem, "")
|
||||
commands := applyXMLChanges(changes)
|
||||
result, _ := utils.ExecuteModifications(commands, original)
|
||||
|
||||
// Verify attribute order is preserved (weight comes before damage and speed)
|
||||
weightIdx := strings.Index(result, "weight=")
|
||||
damageIdx := strings.Index(result, "damage=")
|
||||
speedIdx := strings.Index(result, "speed=")
|
||||
|
||||
if weightIdx > damageIdx || damageIdx > speedIdx {
|
||||
t.Errorf("Attribute order not preserved:\n%s", result)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user