package xmlparser import ( "fmt" "strings" ) // ValidationError represents a validation error with details type ValidationError struct { Field string `json:"field"` Message string `json:"message"` Value string `json:"value,omitempty"` } func (ve ValidationError) Error() string { if ve.Value != "" { return fmt.Sprintf("validation error in field '%s': %s (value: %s)", ve.Field, ve.Message, ve.Value) } return fmt.Sprintf("validation error in field '%s': %s", ve.Field, ve.Message) } // ValidationErrors represents multiple validation errors type ValidationErrors []ValidationError func (ve ValidationErrors) Error() string { if len(ve) == 0 { return "no validation errors" } var messages []string for _, err := range ve { messages = append(messages, err.Error()) } return fmt.Sprintf("validation failed with %d error(s): %s", len(ve), strings.Join(messages, "; ")) } // IsEmpty returns true if there are no validation errors func (ve ValidationErrors) IsEmpty() bool { return len(ve) == 0 } // Add adds a validation error func (ve *ValidationErrors) Add(field, message, value string) { *ve = append(*ve, ValidationError{ Field: field, Message: message, Value: value, }) } // XMLParseError represents an XML parsing error with context type XMLParseError struct { Line int `json:"line,omitempty"` Column int `json:"column,omitempty"` Element string `json:"element,omitempty"` Message string `json:"message"` Original error `json:"-"` } func (xpe XMLParseError) Error() string { var parts []string if xpe.Element != "" { parts = append(parts, fmt.Sprintf("element '%s'", xpe.Element)) } if xpe.Line > 0 { if xpe.Column > 0 { parts = append(parts, fmt.Sprintf("line %d, column %d", xpe.Line, xpe.Column)) } else { parts = append(parts, fmt.Sprintf("line %d", xpe.Line)) } } context := "" if len(parts) > 0 { context = " (" + strings.Join(parts, ", ") + ")" } return fmt.Sprintf("XML parse error%s: %s", context, xpe.Message) } // Unwrap returns the original error func (xpe XMLParseError) Unwrap() error { return xpe.Original } // SanitizeXMLString removes invalid XML characters and cleans the string func SanitizeXMLString(s string) string { // Remove control characters except tab, newline, and carriage return var result strings.Builder for _, r := range s { if (r >= 0x20 && r <= 0xD7FF) || (r >= 0xE000 && r <= 0xFFFD) || (r >= 0x10000 && r <= 0x10FFFF) || r == 0x09 || r == 0x0A || r == 0x0D { result.WriteRune(r) } } return result.String() } // ValidateXMLName checks if a string is a valid XML name func ValidateXMLName(name string) bool { if name == "" { return false } // XML names must start with a letter or underscore first := []rune(name)[0] if !((first >= 'A' && first <= 'Z') || (first >= 'a' && first <= 'z') || first == '_') { return false } // Subsequent characters can be letters, digits, hyphens, periods, or underscores for _, r := range []rune(name)[1:] { if !((r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' || r == '.' || r == '_') { return false } } return true } // IsValidNamespace checks if a string is a valid XML namespace URI func IsValidNamespace(namespace string) bool { if namespace == "" { return true // Empty namespace is valid } // Basic URI validation - should start with http:// or https:// or urn: return strings.HasPrefix(namespace, "http://") || strings.HasPrefix(namespace, "https://") || strings.HasPrefix(namespace, "urn:") } // NormalizeWhitespace normalizes whitespace in XML text content func NormalizeWhitespace(s string) string { // Replace all whitespace sequences with single spaces fields := strings.Fields(s) return strings.Join(fields, " ") } // TruncateString truncates a string to maxLength and adds "..." if needed func TruncateString(s string, maxLength int) string { if len(s) <= maxLength { return s } if maxLength <= 3 { return s[:maxLength] } return s[:maxLength-3] + "..." }