package rss import ( "bytes" "encoding/xml" "fmt" "io" "strings" ) // Parse parses an RSS 2.0 or Atom 1.0 XML string into a unified Feed struct. // It auto-detects the feed format by inspecting the root XML element. func Parse(data string) (*Feed, error) { trimmed := strings.TrimSpace(data) if trimmed == "" { return nil, fmt.Errorf("empty input") } format, err := detectFormat([]byte(trimmed)) if err != nil { return nil, err } b := []byte(trimmed) switch format { case "rss": return parseRSS(b) case "atom": return parseAtom(b) default: return nil, fmt.Errorf("unsupported feed format: %s", format) } } // Validate checks whether the input string is a valid RSS 2.0 or Atom 1.0 feed. // Returns nil on success, or a descriptive error explaining what is wrong. // // Process convention: // - success → true (bool) // - failure → error description string func Validate(data string) error { trimmed := strings.TrimSpace(data) if trimmed == "" { return fmt.Errorf("empty input: expected an XML document containing an RSS or Atom feed") } // Step 1: check if it is valid XML at all decoder := xml.NewDecoder(bytes.NewReader([]byte(trimmed))) var rootFound bool for { tok, err := decoder.Token() if err == io.EOF { break } if err != nil { return fmt.Errorf("not valid XML: %s", err.Error()) } if _, ok := tok.(xml.StartElement); ok { rootFound = true break } } if !rootFound { return fmt.Errorf("not valid XML: no root element found") } // Step 2: detect format format, err := detectFormat([]byte(trimmed)) if err != nil { return err } // Step 3: attempt a full parse to verify structural integrity b := []byte(trimmed) switch format { case "rss": feed, err := parseRSS(b) if err != nil { return fmt.Errorf("RSS 2.0 parse error: %s", err.Error()) } if feed.Title == "" { return fmt.Errorf("RSS 2.0 feed is missing required