Files
tm_back/pkg/xmlparser/parser.go
T
n.nakhostin 7dc695752b Remove github.com/google/uuid dependency and update module files
- 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.
2025-08-12 12:15:14 +03:30

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
}