package xmlparser import ( "bytes" "encoding/xml" "fmt" "io" "reflect" "strings" ) // GenericParser implements the Parser interface using Go generics type GenericParser[T Parsable] struct { options *ParserOptions } // NewParser creates a new generic XML parser func NewParser[T Parsable](options *ParserOptions) Parser[T] { if options == nil { options = DefaultParserOptions() } return &GenericParser[T]{ options: options, } } // Parse parses XML from reader into the target type func (p *GenericParser[T]) Parse(reader io.Reader) (*T, error) { // Read all data from reader data, err := io.ReadAll(reader) if err != nil { return nil, fmt.Errorf("failed to read XML data: %w", err) } return p.ParseBytes(data) } // ParseString parses XML from string into the target type func (p *GenericParser[T]) ParseString(xmlData string) (*T, error) { return p.ParseBytes([]byte(xmlData)) } // ParseBytes parses XML from byte slice into the target type func (p *GenericParser[T]) ParseBytes(xmlData []byte) (*T, error) { // Check size limit if p.options.MaxSize > 0 && int64(len(xmlData)) > p.options.MaxSize { return nil, fmt.Errorf("XML size %d exceeds maximum allowed size %d", len(xmlData), p.options.MaxSize) } // Create a new instance of T var result T resultType := reflect.TypeOf(result) // Create a pointer to the struct if it's not already a pointer var targetPtr interface{} if resultType.Kind() == reflect.Ptr { // T is already a pointer type targetPtr = reflect.New(resultType.Elem()).Interface() } else { // T is a value type, create a pointer to it targetPtr = reflect.New(resultType).Interface() } // Check depth limit before parsing if specified if p.options.MaxDepth > 0 { if err := p.checkDepthLimit(xmlData); err != nil { return nil, err } } // Create XML decoder decoder := xml.NewDecoder(bytes.NewReader(xmlData)) // Configure decoder options if !p.options.PreserveWhitespace { decoder.CharsetReader = nil } if p.options.StrictValidation { decoder.Strict = true } // Parse XML into the pointer err := decoder.Decode(targetPtr) if err != nil { return nil, fmt.Errorf("failed to decode XML: %w", err) } // Convert back to the target type var parsedResult T if resultType.Kind() == reflect.Ptr { // T is a pointer type, so targetPtr is already T parsedResult = targetPtr.(T) } else { // T is a value type, so dereference the pointer parsedResult = reflect.ValueOf(targetPtr).Elem().Interface().(T) } // Validate the parsed result if err := parsedResult.Validate(); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } return &parsedResult, nil } // Validate validates XML against schema (basic validation for now) func (p *GenericParser[T]) Validate(reader io.Reader) error { data, err := io.ReadAll(reader) if err != nil { return fmt.Errorf("failed to read XML data: %w", err) } // Check for empty data if len(data) == 0 { return fmt.Errorf("XML data is empty") } // Basic XML well-formedness validation decoder := xml.NewDecoder(bytes.NewReader(data)) // Count depth for max depth validation depth := 0 maxDepthReached := 0 for { token, err := decoder.Token() if err == io.EOF { break } if err != nil { return fmt.Errorf("XML validation failed: %w", err) } switch token.(type) { case xml.StartElement: depth++ if depth > maxDepthReached { maxDepthReached = depth } if p.options.MaxDepth > 0 && depth > p.options.MaxDepth { return fmt.Errorf("XML depth %d exceeds maximum allowed depth %d", depth, p.options.MaxDepth) } case xml.EndElement: depth-- } } return nil } // checkDepthLimit validates that XML doesn't exceed the maximum depth limit func (p *GenericParser[T]) checkDepthLimit(xmlData []byte) error { decoder := xml.NewDecoder(bytes.NewReader(xmlData)) depth := 0 maxDepthReached := 0 for { token, err := decoder.Token() if err == io.EOF { break } if err != nil { return fmt.Errorf("XML depth validation failed: %w", err) } switch token.(type) { case xml.StartElement: depth++ if depth > maxDepthReached { maxDepthReached = depth } if depth > p.options.MaxDepth { return fmt.Errorf("XML depth %d exceeds maximum allowed depth %d", depth, p.options.MaxDepth) } case xml.EndElement: depth-- } } return nil } // ParseWithResult parses XML and returns detailed result with metadata func (p *GenericParser[T]) ParseWithResult(reader io.Reader) (*ParseResult[T], error) { result := &ParseResult[T]{ Metadata: &XMLMetadata{}, Errors: []string{}, Warnings: []string{}, } // Read all data data, err := io.ReadAll(reader) if err != nil { result.Errors = append(result.Errors, fmt.Sprintf("Failed to read XML data: %v", err)) return result, err } // Extract XML metadata if err := p.extractMetadata(data, result.Metadata); err != nil { result.Warnings = append(result.Warnings, fmt.Sprintf("Failed to extract metadata: %v", err)) } // Parse the XML parsed, err := p.ParseBytes(data) if err != nil { result.Errors = append(result.Errors, err.Error()) return result, err } result.Data = parsed return result, nil } // extractMetadata extracts basic metadata from XML func (p *GenericParser[T]) extractMetadata(xmlData []byte, metadata *XMLMetadata) error { // Basic extraction of XML declaration and root element xmlStr := string(xmlData) // Extract encoding from XML declaration if strings.HasPrefix(xmlStr, "") if endIdx > 0 { declaration := xmlStr[:endIdx+2] // Extract encoding if encodingIdx := strings.Index(declaration, "encoding="); encodingIdx > 0 { start := encodingIdx + 9 if start < len(declaration) { quote := declaration[start] if quote == '"' || quote == '\'' { end := strings.IndexByte(declaration[start+1:], byte(quote)) if end > 0 { metadata.Encoding = declaration[start+1 : start+1+end] } } } } // Extract version if versionIdx := strings.Index(declaration, "version="); versionIdx > 0 { start := versionIdx + 8 if start < len(declaration) { quote := declaration[start] if quote == '"' || quote == '\'' { end := strings.IndexByte(declaration[start+1:], byte(quote)) if end > 0 { metadata.Version = declaration[start+1 : start+1+end] } } } } } } // Extract root element namespace decoder := xml.NewDecoder(bytes.NewReader(xmlData)) for { token, err := decoder.Token() if err != nil { break } if startElement, ok := token.(xml.StartElement); ok { metadata.XMLName = startElement.Name metadata.Namespace = startElement.Name.Space break } } return nil }