func (s *Source) Validate() error {
	for _, author := range s.Authors {
		if err := author.Validate(); err != nil {
			return err
		}
	}
	for _, category := range s.Categories {
		if err := category.Validate(); err != nil {
			return err
		}
	}
	for _, contributor := range s.Contributors {
		if err := contributor.Validate(); err != nil {
			return err
		}
	}
	if s.Generator != nil {
		if err := s.Generator.Validate(); err != nil {
			return err
		}
	}
	if _, err := url.Parse(s.Id); err != nil {
		return errors.Annotate(err, ErrParsing, errorMessages, "feed entry source id")
	}
	if s.Link != nil {
		if err := s.Link.Validate(); err != nil {
			return err
		}
	}
	if err := validateText("feed entry source rights", s.Rights, false); err != nil {
		return err
	}
	if err := validateText("feed entry source subtitle", s.Subtitle, false); err != nil {
		return err
	}
	if err := validateText("feed entry source title", s.Title, false); err != nil {
		return err
	}
	if _, err := ParseTime(s.Updated); err != nil {
		return errors.Annotate(err, ErrParsing, errorMessages, "feed entry source update")
	}
	return nil
}
Validate checks if a feed entry source is valid.
package atom
import (
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
	"github.com/tideland/golib/errors"
	"github.com/tideland/golib/feed/utils"
)
const (
	Version = "1.0"
	XMLNS   = "http://www.w3.org/2005/Atom"
	TextType  = "text"
	HTMLType  = "html"
	XHTMLType = "xhtml"
	AlternateRel = "alternate"
	EnclosureRel = "enclosure"
	RelatedRel   = "related"
	SelfRel      = "self"
	ViaRel       = "via"
)
type Feed struct {
	XMLName      string         `xml:"feed"`
	XMLNS        string         `xml:"xmlns,attr"`
	Id           string         `xml:"id"`
	Title        *Text          `xml:"title"`
	Updated      string         `xml:"updated"`
	Authors      []*Author      `xml:"author,omitempty"`
	Link         *Link          `xml:"link,omitempty"`
	Categories   []*Category    `xml:"category,omitempty"`
	Contributors []*Contributor `xml:"contributor,omitempty"`
	Generator    *Generator     `xml:"generator,omitempty"`
	Icon         string         `xml:"icon,omitempty"`
	Logo         string         `xml:"logo,omitempty"`
	Rights       *Text          `xml:"rights,omitempty"`
	Subtitle     *Text          `xml:"subtitle,omitempty"`
	Entries      []*Entry       `xml:"entry"`
}
func (f *Feed) Validate() error {
	if f.XMLNS != XMLNS {
		return errors.New(ErrValidation, errorMessages, "invalid namespace %q: has to be %q", f.XMLNS, XMLNS)
	}
	if _, err := url.Parse(f.Id); err != nil {
		return errors.Annotate(err, ErrParsing, errorMessages, "feed id")
	}
	if err := validateText("feed title", f.Title, true); err != nil {
		return err
	}
	if _, err := ParseTime(f.Updated); err != nil {
		return errors.Annotate(err, ErrParsing, errorMessages, "feed update")
	}
	for _, author := range f.Authors {
		if err := author.Validate(); err != nil {
			return err
		}
	}
	if f.Link != nil {
		if err := f.Link.Validate(); err != nil {
			return err
		}
	}
	for _, category := range f.Categories {
		if err := category.Validate(); err != nil {
			return err
		}
	}
	for _, contributor := range f.Contributors {
		if err := contributor.Validate(); err != nil {
			return err
		}
	}
	if f.Generator != nil {
		if err := f.Generator.Validate(); err != nil {
			return err
		}
	}
	if err := validateText("feed rights", f.Rights, false); err != nil {
		return err
	}
	if err := validateText("feed subtitle", f.Subtitle, false); err != nil {
		return err
	}
	allEntriesWithAuthor := true
	for _, entry := range f.Entries {
		if err := entry.Validate(); err != nil {
			return err
		}
		allEntriesWithAuthor = allEntriesWithAuthor && len(entry.Authors) > 0
	}
	if !allEntriesWithAuthor && len(f.Authors) == 0 {
		return errors.New(ErrValidation, errorMessages, "author(s) of feed or entries missing")
	}
	return nil
}
type Text struct {
	Text string `xml:",chardata"`
	Src  string `xml:"src,attr,omitempty"`
	Type string `xml:"type,attr,omitempty"`
}
func (t Text) PlainText() (string, error) {
	var raw string
	if t.Src != "" {
		resp, err := http.Get(t.Src)
		if err != nil {
			return "", errors.Annotate(err, ErrNoPlainText, errorMessages, t)
		}
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return "", errors.Annotate(err, ErrNoPlainText, errorMessages, t)
		}
		raw = string(body)
	} else {
		raw = t.Text
	}
	switch t.Type {
	case "", TextType:
		return raw, nil
	case HTMLType:
		return utils.StripTags(raw, false, true)
	case XHTMLType:
		return utils.StripTags(raw, true, true)
	}
	if strings.HasSuffix(t.Type, "xml") {
		return utils.StripTags(raw, true, true)
	}
	err := fmt.Errorf("illegal test type: %s", t.Type)
	return "", errors.Annotate(err, ErrNoPlainText, errorMessages, t)
}
func validateText(description string, t *Text, mandatory bool) error {
	if (t == nil || t.Text == "") && mandatory {
		return errors.New(ErrValidation, errorMessages, "%s must not be missing or empty", description)
	}
	if t != nil {
		if t.Src != "" {
			if _, err := url.Parse(t.Src); err != nil {
				what := fmt.Sprintf("%s src", description)
				return errors.Annotate(err, ErrParsing, errorMessages, what)
			}
		}
		switch t.Type {
		case "", TextType, HTMLType, XHTMLType:
		default:
			return errors.New(ErrValidation, errorMessages, "%s has illegal type %q", description, t.Type)
		}
	}
	return nil
}
type Author struct {
	Name  string `xml:"name"`
	URI   string `xml:"uri,omitempty"`
	EMail string `xml:"email,omitempty"`
}
func (a *Author) Validate() error {
	if a.Name == "" {
		return errors.New(ErrValidation, errorMessages, "feed author name must not be empty")
	}
	if a.URI != "" {
		if _, err := url.Parse(a.URI); err != nil {
			return errors.Annotate(err, ErrValidation, errorMessages, "feed author uri is not parsable")
		}
	}
	return nil
}
type Link struct {
	HRef     string `xml:"href,attr"`
	Rel      string `xml:"rel,attr,omitempty"`
	Type     string `xml:"type,attr,omitempty"`
	HRefLang string `xml:"hreflang,attr,omitempty"`
	Title    string `xml:"title,attr,omitempty"`
	Length   int    `xml:"length,attr,omitempty"`
}
func (l *Link) Validate() error {
	if _, err := url.Parse(l.HRef); err != nil {
		return errors.Annotate(err, ErrValidation, errorMessages, "feed link href is not parsable")
	}
	switch l.Rel {
	case "", AlternateRel, EnclosureRel, RelatedRel, SelfRel, ViaRel:
	default:
		if _, err := url.Parse(l.Rel); err != nil {
			return errors.Annotate(err, ErrValidation, errorMessages, "feed link rel is neither predefined nor parsable")
		}
	}
	return nil
}
type Category struct {
	Term   string `xml:"term,attr"`
	Scheme string `xml:"scheme,attr,omitempty"`
	Label  string `xml:"label,attr,omitempty"`
}
func (c *Category) Validate() error {
	if c.Term == "" {
		return errors.New(ErrValidation, errorMessages, "feed category term must not be empty")
	}
	if c.Scheme != "" {
		if _, err := url.Parse(c.Scheme); err != nil {
			return errors.Annotate(err, ErrValidation, errorMessages, "feed category scheme is not parsable")
		}
	}
	return nil
}
type Contributor struct {
	Name string `xml:"name"`
}
func (c *Contributor) Validate() error {
	if c.Name == "" {
		return errors.New(ErrValidation, errorMessages, "feed contributor name must not be empty")
	}
	return nil
}
type Generator struct {
	Generator string `xml:",chardata"`
	URI       string `xml:"uri,attr,omitempty"`
	Version   string `xml:"version,attr,omitempty"`
}
func (g *Generator) Validate() error {
	if g.Generator == "" {
		return errors.New(ErrValidation, errorMessages, "feed generator must not be empty")
	}
	if g.URI != "" {
		if _, err := url.Parse(g.URI); err != nil {
			return errors.Annotate(err, ErrParsing, errorMessages, "feed generator URI")
		}
	}
	return nil
}
type Entry struct {
	Id           string         `xml:"id"`
	Title        *Text          `xml:"title"`
	Updated      string         `xml:"updated"`
	Authors      []*Author      `xml:"author,omitempty"`
	Content      *Text          `xml:"content,omitempty"`
	Link         *Link          `xml:"link,omitempty"`
	Summary      *Text          `xml:"subtitle,omitempty"`
	Categories   []*Category    `xml:"category,omitempty"`
	Contributors []*Contributor `xml:"contributor,omitempty"`
	Published    string         `xml:"published,omitempty"`
	Source       *Source        `xml:"source,omitempty"`
	Rights       *Text          `xml:"rights,omitempty"`
}
func (e *Entry) Validate() error {
	if _, err := url.Parse(e.Id); err != nil {
		return errors.Annotate(err, ErrParsing, errorMessages, "feed entry id")
	}
	if err := validateText("feed entry title", e.Title, true); err != nil {
		return err
	}
	if _, err := ParseTime(e.Updated); err != nil {
		return errors.Annotate(err, ErrParsing, errorMessages, "feed entry update")
	}
	for _, author := range e.Authors {
		if err := author.Validate(); err != nil {
			return err
		}
	}
	if err := validateText("feed entry content", e.Content, false); err != nil {
		return err
	}
	if e.Link != nil {
		if err := e.Link.Validate(); err != nil {
			return err
		}
	}
	if err := validateText("feed entry summary", e.Summary, false); err != nil {
		return err
	}
	for _, category := range e.Categories {
		if err := category.Validate(); err != nil {
			return err
		}
	}
	for _, contributor := range e.Contributors {
		if err := contributor.Validate(); err != nil {
			return err
		}
	}
	if _, err := ParseTime(e.Published); err != nil {
		return errors.Annotate(err, ErrParsing, errorMessages, "feed entry published")
	}
	if e.Source != nil {
		if err := e.Source.Validate(); err != nil {
			return err
		}
	}
	if err := validateText("feed entry rights", e.Rights, false); err != nil {
		return err
	}
	return nil
}
type Source struct {
	Authors      []*Author      `xml:"author,omitempty"`
	Categories   []*Category    `xml:"category,omitempty"`
	Contributors []*Contributor `xml:"contributor,omitempty"`
	Generator    *Generator     `xml:"generator,omitempty"`
	Icon         string         `xml:"icon,omitempty"`
	Id           string         `xml:"id,omitempty"`
	Link         *Link          `xml:"link,omitempty"`
	Logo         string         `xml:"logo,omitempty"`
	Rights       *Text          `xml:"rights,omitempty"`
	Subtitle     *Text          `xml:"subtitle,omitempty"`
	Title        *Text          `xml:"title,omitempty"`
	Updated      string         `xml:"updated,omitempty"`
}