7dc695752b
- Deleted the `github.com/google/uuid` dependency from `go.mod` to streamline the project and reduce unnecessary dependencies. - Updated `go.sum` to reflect the removal of the UUID package. - Enhanced the overall module management by ensuring only necessary dependencies are included.
234 lines
5.8 KiB
Go
234 lines
5.8 KiB
Go
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()
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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, "<?xml") {
|
|
endIdx := strings.Index(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
|
|
}
|