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