From 3989cde89c83fec5ab58ccaea6ebe114ebe71200 Mon Sep 17 00:00:00 2001 From: Nika Jones Date: Thu, 14 Jun 2018 00:36:24 -0700 Subject: [PATCH] Fixes issue GH-25 for RSS feeds. Adds the ability to add custom fields with a namespace to the RSS feed. --- feed.go | 53 ++++++++++++++++++++++++++++++++++++ rss.go | 40 +++++++++++++++------------- rss_test.go | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 18 deletions(-) create mode 100644 rss_test.go diff --git a/feed.go b/feed.go index 93d6449..368e29c 100644 --- a/feed.go +++ b/feed.go @@ -35,9 +35,11 @@ type Item struct { Created time.Time Enclosure *Enclosure Content string + Extension []interface{} } type Feed struct { + Attrs []xml.Attr Title string Link *Link Description string @@ -56,6 +58,57 @@ func (f *Feed) Add(item *Item) { f.Items = append(f.Items, item) } +func (f *Feed) AddAttribute(name, nsURI string) { + f.Attrs = append(f.Attrs, xml.Attr{ + Name: xml.Name{Local: name}, + Value: nsURI, + }) +} + +func (i *Item) AddExtension(extend interface{}) { + i.Extension = append(i.Extension, extend) +} + +func (i *Item) AddExtensionString(name string, nsURI string, value string) { + i.Extension = append(i.Extension, struct { + XMLName xml.Name + Text string `xml:",chardata"` + }{ + XMLName: xml.Name{Local: name, Space: nsURI}, + Text: value, + }) +} + +func (i *Item) AddExtensionInt(name string, nsURI string, value int) { + i.Extension = append(i.Extension, struct { + XMLName xml.Name + Number int `xml:",chardata"` + }{ + XMLName: xml.Name{Local: name, Space: nsURI}, + Number: value, + }) +} + +func (i *Item) AddExtensionUint(name string, nsURI string, value uint) { + i.Extension = append(i.Extension, struct { + XMLName xml.Name + Number uint `xml:",chardata"` + }{ + XMLName: xml.Name{Local: name, Space: nsURI}, + Number: value, + }) +} + +func (i *Item) AddExtensionFloat64(name string, nsURI string, value float64) { + i.Extension = append(i.Extension, struct { + XMLName xml.Name + Number float64 `xml:",chardata"` + }{ + XMLName: xml.Name{Local: name, Space: nsURI}, + Number: value, + }) +} + // returns the first non-zero time formatted as a string or "" func anyTimeFormat(format string, times ...time.Time) string { for _, t := range times { diff --git a/rss.go b/rss.go index 39fe84b..07b4020 100644 --- a/rss.go +++ b/rss.go @@ -41,24 +41,25 @@ type RssTextInput struct { } type RssFeed struct { - XMLName xml.Name `xml:"channel"` - Title string `xml:"title"` // required - Link string `xml:"link"` // required - Description string `xml:"description"` // required - Language string `xml:"language,omitempty"` - Copyright string `xml:"copyright,omitempty"` - ManagingEditor string `xml:"managingEditor,omitempty"` // Author used - WebMaster string `xml:"webMaster,omitempty"` - PubDate string `xml:"pubDate,omitempty"` // created or updated - LastBuildDate string `xml:"lastBuildDate,omitempty"` // updated used - Category string `xml:"category,omitempty"` - Generator string `xml:"generator,omitempty"` - Docs string `xml:"docs,omitempty"` - Cloud string `xml:"cloud,omitempty"` - Ttl int `xml:"ttl,omitempty"` - Rating string `xml:"rating,omitempty"` - SkipHours string `xml:"skipHours,omitempty"` - SkipDays string `xml:"skipDays,omitempty"` + XMLName xml.Name `xml:"channel"` + Attrs []xml.Attr `xml:",attr"` + Title string `xml:"title"` // required + Link string `xml:"link"` // required + Description string `xml:"description"` // required + Language string `xml:"language,omitempty"` + Copyright string `xml:"copyright,omitempty"` + ManagingEditor string `xml:"managingEditor,omitempty"` // Author used + WebMaster string `xml:"webMaster,omitempty"` + PubDate string `xml:"pubDate,omitempty"` // created or updated + LastBuildDate string `xml:"lastBuildDate,omitempty"` // updated used + Category string `xml:"category,omitempty"` + Generator string `xml:"generator,omitempty"` + Docs string `xml:"docs,omitempty"` + Cloud string `xml:"cloud,omitempty"` + Ttl int `xml:"ttl,omitempty"` + Rating string `xml:"rating,omitempty"` + SkipHours string `xml:"skipHours,omitempty"` + SkipDays string `xml:"skipDays,omitempty"` Image *RssImage TextInput *RssTextInput Items []*RssItem @@ -77,6 +78,7 @@ type RssItem struct { Guid string `xml:"guid,omitempty"` // Id used PubDate string `xml:"pubDate,omitempty"` // created or updated Source string `xml:"source,omitempty"` + Extensions interface{} } type RssEnclosure struct { @@ -99,6 +101,7 @@ func newRssItem(i *Item) *RssItem { Description: i.Description, Guid: i.Id, PubDate: anyTimeFormat(time.RFC1123Z, i.Created, i.Updated), + Extensions: i.Extension, } if len(i.Content) > 0 { item.Content = &RssContent{Content: i.Content} @@ -136,6 +139,7 @@ func (r *Rss) RssFeed() *RssFeed { } channel := &RssFeed{ + Attrs: r.Attrs, Title: r.Title, Link: r.Link.Href, Description: r.Description, diff --git a/rss_test.go b/rss_test.go new file mode 100644 index 0000000..9ebd192 --- /dev/null +++ b/rss_test.go @@ -0,0 +1,77 @@ +package feeds + +import ( + "bytes" + "testing" + "time" +) + +func TestRssFeedExtensions(t *testing.T) { + var rssOutput = ` + + Example + https://rss.example.com + Example Example + copyright © Example + Example (Example) + Wed, 16 Jan 2013 21:52:35 -0500 + + Example example + https://example.com/link + example + + Jason Moiron + Wed, 16 Jan 2013 21:52:35 -0500 + v1 + 1234567890 + + +` + + now, err := time.Parse(time.RFC3339, "2013-01-16T21:52:35-05:00") + if err != nil { + t.Error(err) + } + tz := time.FixedZone("EST", -5*60*60) + now = now.In(tz) + + feed := &Feed{ + Title: "Example", + Link: &Link{Href: "https://rss.example.com"}, + Description: "Example Example", + Author: &Author{Name: "Example", Email: "Example"}, + Created: now, + Copyright: "copyright © Example", + } + + feed.Items = []*Item{ + { + Title: "Example example", + Link: &Link{Href: "https://example.com/link"}, + Description: "example", + Author: &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"}, + Created: now, + Content: "Example example example, example example...", + }, + } + + feed.AddAttribute("test", "https://example.com/test/uri") + feed.Items[0].AddExtensionString("test:version", "", "v1") + feed.Items[0].AddExtensionInt("test:example", "", 1234567890) + + rss, err := feed.ToRss() + if err != nil { + t.Errorf("unexpected error encoding RSS: %v", err) + } + if rss != rssOutput { + t.Errorf("Rss not what was expected. Got:\n%s\n\nExpected:\n%s\n", rss, rssOutput) + } + + var buf = new(bytes.Buffer) + if err := feed.WriteRss(buf); err != nil { + t.Errorf("unexpected error writing RSS: %v", err) + } + if got := buf.String(); got != rssOutput { + t.Errorf("Rss not what was expected. Got:\n%s\n\nExpected:\n%s\n", got, rssOutput) + } +}