1package gofeed 2 3import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "net/http" 9 "strings" 10 11 "github.com/mmcdole/gofeed/atom" 12 "github.com/mmcdole/gofeed/rss" 13) 14 15// ErrFeedTypeNotDetected is returned when the detection system can not figure 16// out the Feed format 17var ErrFeedTypeNotDetected = errors.New("Failed to detect feed type") 18 19// HTTPError represents an HTTP error returned by a server. 20type HTTPError struct { 21 StatusCode int 22 Status string 23} 24 25func (err HTTPError) Error() string { 26 return fmt.Sprintf("http error: %s", err.Status) 27} 28 29// Parser is a universal feed parser that detects 30// a given feed type, parsers it, and translates it 31// to the universal feed type. 32type Parser struct { 33 AtomTranslator Translator 34 RSSTranslator Translator 35 Client *http.Client 36 rp *rss.Parser 37 ap *atom.Parser 38} 39 40// NewParser creates a universal feed parser. 41func NewParser() *Parser { 42 fp := Parser{ 43 rp: &rss.Parser{}, 44 ap: &atom.Parser{}, 45 } 46 return &fp 47} 48 49// Parse parses a RSS or Atom feed into 50// the universal gofeed.Feed. It takes an 51// io.Reader which should return the xml content. 52func (f *Parser) Parse(feed io.Reader) (*Feed, error) { 53 // Wrap the feed io.Reader in a io.TeeReader 54 // so we can capture all the bytes read by the 55 // DetectFeedType function and construct a new 56 // reader with those bytes intact for when we 57 // attempt to parse the feeds. 58 var buf bytes.Buffer 59 tee := io.TeeReader(feed, &buf) 60 feedType := DetectFeedType(tee) 61 62 // Glue the read bytes from the detect function 63 // back into a new reader 64 r := io.MultiReader(&buf, feed) 65 66 switch feedType { 67 case FeedTypeAtom: 68 return f.parseAtomFeed(r) 69 case FeedTypeRSS: 70 return f.parseRSSFeed(r) 71 } 72 73 return nil, ErrFeedTypeNotDetected 74} 75 76// ParseURL fetches the contents of a given url and 77// attempts to parse the response into the universal feed type. 78func (f *Parser) ParseURL(feedURL string) (feed *Feed, err error) { 79 client := f.httpClient() 80 81 req, err := http.NewRequest("GET", feedURL, nil) 82 if err != nil { 83 return nil, err 84 } 85 req.Header.Set("User-Agent", "Gofeed/1.0") 86 resp, err := client.Do(req) 87 88 if err != nil { 89 return nil, err 90 } 91 92 if resp != nil { 93 defer func() { 94 ce := resp.Body.Close() 95 if ce != nil { 96 err = ce 97 } 98 }() 99 } 100 101 if resp.StatusCode < 200 || resp.StatusCode >= 300 { 102 return nil, HTTPError{ 103 StatusCode: resp.StatusCode, 104 Status: resp.Status, 105 } 106 } 107 108 return f.Parse(resp.Body) 109} 110 111// ParseString parses a feed XML string and into the 112// universal feed type. 113func (f *Parser) ParseString(feed string) (*Feed, error) { 114 return f.Parse(strings.NewReader(feed)) 115} 116 117func (f *Parser) parseAtomFeed(feed io.Reader) (*Feed, error) { 118 af, err := f.ap.Parse(feed) 119 if err != nil { 120 return nil, err 121 } 122 return f.atomTrans().Translate(af) 123} 124 125func (f *Parser) parseRSSFeed(feed io.Reader) (*Feed, error) { 126 rf, err := f.rp.Parse(feed) 127 if err != nil { 128 return nil, err 129 } 130 131 return f.rssTrans().Translate(rf) 132} 133 134func (f *Parser) atomTrans() Translator { 135 if f.AtomTranslator != nil { 136 return f.AtomTranslator 137 } 138 f.AtomTranslator = &DefaultAtomTranslator{} 139 return f.AtomTranslator 140} 141 142func (f *Parser) rssTrans() Translator { 143 if f.RSSTranslator != nil { 144 return f.RSSTranslator 145 } 146 f.RSSTranslator = &DefaultRSSTranslator{} 147 return f.RSSTranslator 148} 149 150func (f *Parser) httpClient() *http.Client { 151 if f.Client != nil { 152 return f.Client 153 } 154 f.Client = &http.Client{} 155 return f.Client 156} 157