diff --git a/src/feed/atom.go b/src/feed/atom.go new file mode 100644 index 0000000..3e966f7 --- /dev/null +++ b/src/feed/atom.go @@ -0,0 +1,87 @@ +package feed + +import ( + "encoding/xml" + "html" + "io" + "strings" +) + +type atomFeed struct { + XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"` + ID string `xml:"id"` + Title atomText `xml:"title"` + Links atomLinks `xml:"link"` + Entries []atomEntry `xml:"entry"` +} + +type atomEntry struct { + ID string `xml:"id"` + Title atomText `xml:"title"` + Summary atomText `xml:"summary"` + Published string `xml:"published"` + Updated string `xml:"updated"` + Links atomLinks `xml:"link"` + Content atomText `xml:"content"` +} + +type atomText struct { + Type string `xml:"type,attr"` + Data string `xml:",chardata"` + XML string `xml:",innerxml"` +} + +type atomLink struct { + Href string `xml:"href,attr"` + Rel string `xml:"rel,attr"` +} + +type atomLinks []atomLink + +func (a *atomText) String() string { + data := a.Data + if a.Type == "xhtml" { + data = a.XML + } + return html.UnescapeString(strings.TrimSpace(data)) +} + +func (links atomLinks) First(rel string) string { + for _, l := range links { + if l.Rel == rel { + return l.Href + } + } + return "" +} + +func ParseAtom(r io.Reader) (*Feed, error) { + f := atomFeed{} + + decoder := xml.NewDecoder(r) + if err := decoder.Decode(&f); err != nil { + return nil, err + } + + feed := &Feed{ + Title: f.Title.String(), + SiteURL: first(f.Links.First("alternate"), f.Links.First("")), + FeedURL: f.Links.First("self"), + } + for _, e := range f.Entries { + date, _ := dateParse(first(e.Published, e.Updated)) + imageUrl := "" + podcastUrl := "" + + feed.Items = append(feed.Items, Item{ + GUID: first(e.ID), + Date: date, + URL: first(e.Links.First("alternate"), f.Links.First("")), + Title: e.Title.String(), + Content: e.Content.String(), + ImageURL: imageUrl, + PodcastURL: podcastUrl, + }) + } + return feed, nil +} diff --git a/src/feed/atom_test.go b/src/feed/atom_test.go new file mode 100644 index 0000000..49e5b14 --- /dev/null +++ b/src/feed/atom_test.go @@ -0,0 +1,59 @@ +package feed + +import ( + "reflect" + "strings" + "testing" + "time" +) + +func TestAtom(t *testing.T) { + have, _ := ParseAtom(strings.NewReader(` + + + Example Feed + A subtitle. + + + urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6 + 2003-12-13T18:30:02Z + + Atom-Powered Robots Run Amok + + + + urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a + 2003-12-13T18:30:02Z + Some text. + +

This is the entry content.

+
+ + John Doe + johndoe@example.com + +
+
+ `)) + want := &Feed{ + Title: "Example Feed", + SiteURL: "http://example.org/", + FeedURL: "http://example.org/feed/", + Items: []Item{ + { + GUID: "urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a", + Date: time.Unix(1071340202, 0).UTC(), + URL: "http://example.org/2003/12/13/atom03.html", + Title: "Atom-Powered Robots Run Amok", + Content: `

This is the entry content.

`, + ImageURL: "", + PodcastURL: "", + }, + }, + } + if !reflect.DeepEqual(want, have) { + t.Logf("want: %#v", want) + t.Logf("have: %#v", have) + t.Fatal("invalid atom") + } +}