Ver Fonte

Improve Podcast support (iTunes and Google Play feeds)

- Add support for Google Play XML namespace
- Improve existing iTunes namespace implementation
Frédéric Guillot há 6 anos atrás
pai
commit
1b33bb3d1c
5 ficheiros alterados com 580 adições e 127 exclusões
  1. 12 0
      reader/rss/dublincore.go
  2. 11 0
      reader/rss/feedburner.go
  3. 339 11
      reader/rss/parser_test.go
  4. 70 0
      reader/rss/podcast.go
  5. 148 116
      reader/rss/rss.go

+ 12 - 0
reader/rss/dublincore.go

@@ -0,0 +1,12 @@
+// Copyright 2019 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package rss // import "miniflux.app/reader/rss"
+
+// DublinCoreElement represents Dublin Core XML elements.
+type DublinCoreElement struct {
+	DublinCoreDate    string `xml:"http://purl.org/dc/elements/1.1/ date"`
+	DublinCoreCreator string `xml:"http://purl.org/dc/elements/1.1/ creator"`
+	DublinCoreContent string `xml:"http://purl.org/rss/1.0/modules/content/ encoded"`
+}

+ 11 - 0
reader/rss/feedburner.go

@@ -0,0 +1,11 @@
+// Copyright 2019 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package rss // import "miniflux.app/reader/rss"
+
+// FeedBurnerElement represents FeedBurner XML elements.
+type FeedBurnerElement struct {
+	FeedBurnerLink          string `xml:"http://rssnamespace.org/feedburner/ext/1.0 origLink"`
+	FeedBurnerEnclosureLink string `xml:"http://rssnamespace.org/feedburner/ext/1.0 origEnclosureLink"`
+}

+ 339 - 11
reader/rss/parser_test.go

@@ -230,6 +230,59 @@ func TestParseFeedURLWithAtomLink(t *testing.T) {
 	}
 }
 
+func TestParseFeedWithWebmaster(t *testing.T) {
+	data := `<?xml version="1.0" encoding="utf-8"?>
+		<rss version="2.0">
+		<channel>
+			<title>Example</title>
+			<link>https://example.org/</link>
+			<webMaster>webmaster@example.com</webMaster>
+			<item>
+				<title>Test</title>
+				<link>https://example.org/item</link>
+			</item>
+		</channel>
+		</rss>`
+
+	feed, err := Parse(bytes.NewBufferString(data))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expected := "webmaster@example.com"
+	result := feed.Entries[0].Author
+	if result != expected {
+		t.Errorf("Incorrect entry author, got %q instead of %q", result, expected)
+	}
+}
+
+func TestParseFeedWithManagingEditor(t *testing.T) {
+	data := `<?xml version="1.0" encoding="utf-8"?>
+		<rss version="2.0">
+		<channel>
+			<title>Example</title>
+			<link>https://example.org/</link>
+			<webMaster>webmaster@example.com</webMaster>
+			<managingEditor>editor@example.com</managingEditor>
+			<item>
+				<title>Test</title>
+				<link>https://example.org/item</link>
+			</item>
+		</channel>
+		</rss>`
+
+	feed, err := Parse(bytes.NewBufferString(data))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expected := "editor@example.com"
+	result := feed.Entries[0].Author
+	if result != expected {
+		t.Errorf("Incorrect entry author, got %q instead of %q", result, expected)
+	}
+}
+
 func TestParseEntryWithAuthorAndInnerHTML(t *testing.T) {
 	data := `<?xml version="1.0" encoding="utf-8"?>
 		<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
@@ -250,12 +303,14 @@ func TestParseEntryWithAuthorAndInnerHTML(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if feed.Entries[0].Author != "by Foo Bar" {
-		t.Errorf("Incorrect entry author, got: %s", feed.Entries[0].Author)
+	expected := "by Foo Bar"
+	result := feed.Entries[0].Author
+	if result != expected {
+		t.Errorf("Incorrect entry author, got %q instead of %q", result, expected)
 	}
 }
 
-func TestParseEntryWithAtomAuthor(t *testing.T) {
+func TestParseEntryWithNonStandardAtomAuthor(t *testing.T) {
 	data := `<?xml version="1.0" encoding="utf-8"?>
 		<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
 		<channel>
@@ -280,8 +335,68 @@ func TestParseEntryWithAtomAuthor(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if feed.Entries[0].Author != "Foo Bar" {
-		t.Errorf("Incorrect entry author, got: %s", feed.Entries[0].Author)
+	expected := "Foo Bar"
+	result := feed.Entries[0].Author
+	if result != expected {
+		t.Errorf("Incorrect entry author, got %q instead of %q", result, expected)
+	}
+}
+
+func TestParseEntryWithAtomAuthorEmail(t *testing.T) {
+	data := `<?xml version="1.0" encoding="utf-8"?>
+		<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
+		<channel>
+			<title>Example</title>
+			<link>https://example.org/</link>
+			<atom:link href="https://example.org/rss" type="application/rss+xml" rel="self"></atom:link>
+			<item>
+				<title>Test</title>
+				<link>https://example.org/item</link>
+				<atom:author>
+					<email>author@example.org</email>
+				</atom:author>
+			</item>
+		</channel>
+		</rss>`
+
+	feed, err := Parse(bytes.NewBufferString(data))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expected := "author@example.org"
+	result := feed.Entries[0].Author
+	if result != expected {
+		t.Errorf("Incorrect entry author, got %q instead of %q", result, expected)
+	}
+}
+
+func TestParseEntryWithAtomAuthor(t *testing.T) {
+	data := `<?xml version="1.0" encoding="utf-8"?>
+		<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
+		<channel>
+			<title>Example</title>
+			<link>https://example.org/</link>
+			<atom:link href="https://example.org/rss" type="application/rss+xml" rel="self"></atom:link>
+			<item>
+				<title>Test</title>
+				<link>https://example.org/item</link>
+				<atom:author>
+					<name>Foo Bar</name>
+				</atom:author>
+			</item>
+		</channel>
+		</rss>`
+
+	feed, err := Parse(bytes.NewBufferString(data))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expected := "Foo Bar"
+	result := feed.Entries[0].Author
+	if result != expected {
+		t.Errorf("Incorrect entry author, got: %q instead of %q", result, expected)
 	}
 }
 
@@ -304,8 +419,10 @@ func TestParseEntryWithDublinCoreAuthor(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if feed.Entries[0].Author != "Me (me@example.com)" {
-		t.Errorf("Incorrect entry author, got: %s", feed.Entries[0].Author)
+	expected := "Me (me@example.com)"
+	result := feed.Entries[0].Author
+	if result != expected {
+		t.Errorf("Incorrect entry author, got %q instead of %q", result, expected)
 	}
 }
 
@@ -328,8 +445,10 @@ func TestParseEntryWithItunesAuthor(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if feed.Entries[0].Author != "Someone" {
-		t.Errorf("Incorrect entry author, got: %s", feed.Entries[0].Author)
+	expected := "Someone"
+	result := feed.Entries[0].Author
+	if result != expected {
+		t.Errorf("Incorrect entry author, got %q instead of %q", result, expected)
 	}
 }
 
@@ -352,8 +471,119 @@ func TestParseFeedWithItunesAuthor(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if feed.Entries[0].Author != "Someone" {
-		t.Errorf("Incorrect entry author, got: %s", feed.Entries[0].Author)
+	expected := "Someone"
+	result := feed.Entries[0].Author
+	if result != expected {
+		t.Errorf("Incorrect entry author, got %q instead of %q", result, expected)
+	}
+}
+
+func TestParseFeedWithItunesOwner(t *testing.T) {
+	data := `<?xml version="1.0" encoding="utf-8"?>
+		<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
+		<channel>
+			<title>Example</title>
+			<link>https://example.org/</link>
+			<itunes:owner>
+				<itunes:name>John Doe</itunes:name>
+				<itunes:email>john.doe@example.com</itunes:email>
+			</itunes:owner>
+			<item>
+				<title>Test</title>
+				<link>https://example.org/item</link>
+			</item>
+		</channel>
+		</rss>`
+
+	feed, err := Parse(bytes.NewBufferString(data))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expected := "John Doe"
+	result := feed.Entries[0].Author
+	if result != expected {
+		t.Errorf("Incorrect entry author, got %q instead of %q", result, expected)
+	}
+}
+
+func TestParseFeedWithItunesOwnerEmail(t *testing.T) {
+	data := `<?xml version="1.0" encoding="utf-8"?>
+		<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
+		<channel>
+			<title>Example</title>
+			<link>https://example.org/</link>
+			<itunes:owner>
+				<itunes:email>john.doe@example.com</itunes:email>
+			</itunes:owner>
+			<item>
+				<title>Test</title>
+				<link>https://example.org/item</link>
+			</item>
+		</channel>
+		</rss>`
+
+	feed, err := Parse(bytes.NewBufferString(data))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expected := "john.doe@example.com"
+	result := feed.Entries[0].Author
+	if result != expected {
+		t.Errorf("Incorrect entry author, got %q instead of %q", result, expected)
+	}
+}
+
+func TestParseEntryWithGooglePlayAuthor(t *testing.T) {
+	data := `<?xml version="1.0" encoding="utf-8"?>
+		<rss version="2.0" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0">
+		<channel>
+			<title>Example</title>
+			<link>https://example.org/</link>
+			<item>
+				<title>Test</title>
+				<link>https://example.org/item</link>
+				<googleplay:author>Someone</googleplay:author>
+			</item>
+		</channel>
+		</rss>`
+
+	feed, err := Parse(bytes.NewBufferString(data))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expected := "Someone"
+	result := feed.Entries[0].Author
+	if result != expected {
+		t.Errorf("Incorrect entry author, got %q instead of %q", result, expected)
+	}
+}
+
+func TestParseFeedWithGooglePlayAuthor(t *testing.T) {
+	data := `<?xml version="1.0" encoding="utf-8"?>
+		<rss version="2.0" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0">
+		<channel>
+			<title>Example</title>
+			<link>https://example.org/</link>
+			<googleplay:author>Someone</googleplay:author>
+			<item>
+				<title>Test</title>
+				<link>https://example.org/item</link>
+			</item>
+		</channel>
+		</rss>`
+
+	feed, err := Parse(bytes.NewBufferString(data))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expected := "Someone"
+	result := feed.Entries[0].Author
+	if result != expected {
+		t.Errorf("Incorrect entry author, got %q instead of %q", result, expected)
 	}
 }
 
@@ -794,6 +1024,7 @@ func TestParseEntryWithMediaPeerLink(t *testing.T) {
 	if len(feed.Entries) != 1 {
 		t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
 	}
+
 	if len(feed.Entries[0].Enclosures) != 1 {
 		t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures))
 	}
@@ -820,3 +1051,100 @@ func TestParseEntryWithMediaPeerLink(t *testing.T) {
 		}
 	}
 }
+
+func TestEntryDescriptionFromItunesSummary(t *testing.T) {
+	data := `<?xml version="1.0" encoding="UTF-8"?>
+	<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
+		<channel>
+			<title>Podcast Example</title>
+			<link>http://www.example.com/index.html</link>
+			<item>
+				<title>Podcast Episode</title>
+				<guid>http://example.com/episode.m4a</guid>
+				<pubDate>Tue, 08 Mar 2016 12:00:00 GMT</pubDate>
+				<itunes:subtitle>Episode Subtitle</itunes:subtitle>
+				<itunes:summary>Episode Summary</itunes:summary>
+			</item>
+		</channel>
+	</rss>`
+
+	feed, err := Parse(bytes.NewBufferString(data))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if len(feed.Entries) != 1 {
+		t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
+	}
+
+	expected := "Episode Summary"
+	result := feed.Entries[0].Content
+	if expected != result {
+		t.Errorf(`Unexpected podcast content, got %q instead of %q`, result, expected)
+	}
+}
+
+func TestEntryDescriptionFromItunesSubtitle(t *testing.T) {
+	data := `<?xml version="1.0" encoding="UTF-8"?>
+	<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
+		<channel>
+			<title>Podcast Example</title>
+			<link>http://www.example.com/index.html</link>
+			<item>
+				<title>Podcast Episode</title>
+				<guid>http://example.com/episode.m4a</guid>
+				<pubDate>Tue, 08 Mar 2016 12:00:00 GMT</pubDate>
+				<itunes:subtitle>Episode Subtitle</itunes:subtitle>
+			</item>
+		</channel>
+	</rss>`
+
+	feed, err := Parse(bytes.NewBufferString(data))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if len(feed.Entries) != 1 {
+		t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
+	}
+
+	expected := "Episode Subtitle"
+	result := feed.Entries[0].Content
+	if expected != result {
+		t.Errorf(`Unexpected podcast content, got %q instead of %q`, result, expected)
+	}
+}
+
+func TestEntryDescriptionFromGooglePlayDescription(t *testing.T) {
+	data := `<?xml version="1.0" encoding="UTF-8"?>
+	<rss version="2.0"
+		xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"
+		xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
+		<channel>
+			<title>Podcast Example</title>
+			<link>http://www.example.com/index.html</link>
+			<item>
+				<title>Podcast Episode</title>
+				<guid>http://example.com/episode.m4a</guid>
+				<pubDate>Tue, 08 Mar 2016 12:00:00 GMT</pubDate>
+				<itunes:subtitle>Episode Subtitle</itunes:subtitle>
+				<googleplay:description>Episode Description</googleplay:description>
+			</item>
+		</channel>
+	</rss>`
+
+	feed, err := Parse(bytes.NewBufferString(data))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if len(feed.Entries) != 1 {
+		t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
+	}
+
+	expected := "Episode Description"
+	result := feed.Entries[0].Content
+	if expected != result {
+		t.Errorf(`Unexpected podcast content, got %q instead of %q`, result, expected)
+	}
+}

+ 70 - 0
reader/rss/podcast.go

@@ -0,0 +1,70 @@
+// Copyright 2019 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package rss // import "miniflux.app/reader/rss"
+
+import "strings"
+
+// PodcastFeedElement represents iTunes and GooglePlay feed XML elements.
+// Specs:
+// - https://github.com/simplepie/simplepie-ng/wiki/Spec:-iTunes-Podcast-RSS
+// - https://developers.google.com/search/reference/podcast/rss-feed
+type PodcastFeedElement struct {
+	ItunesAuthor     string       `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd channel>author"`
+	Subtitle         string       `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd channel>subtitle"`
+	Summary          string       `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd channel>summary"`
+	PodcastOwner     PodcastOwner `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd channel>owner"`
+	GooglePlayAuthor string       `xml:"http://www.google.com/schemas/play-podcasts/1.0 channel>author"`
+}
+
+// PodcastEntryElement represents iTunes and GooglePlay entry XML elements.
+type PodcastEntryElement struct {
+	Subtitle              string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd subtitle"`
+	Summary               string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd summary"`
+	GooglePlayDescription string `xml:"http://www.google.com/schemas/play-podcasts/1.0 description"`
+}
+
+// PodcastOwner represents contact information for the podcast owner.
+type PodcastOwner struct {
+	Name  string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd name"`
+	Email string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd email"`
+}
+
+// Image represents podcast artwork.
+type Image struct {
+	URL string `xml:"href,attr"`
+}
+
+// PodcastAuthor returns the author of the podcast.
+func (e *PodcastFeedElement) PodcastAuthor() string {
+	author := ""
+
+	switch {
+	case e.ItunesAuthor != "":
+		author = e.ItunesAuthor
+	case e.GooglePlayAuthor != "":
+		author = e.GooglePlayAuthor
+	case e.PodcastOwner.Name != "":
+		author = e.PodcastOwner.Name
+	case e.PodcastOwner.Email != "":
+		author = e.PodcastOwner.Email
+	}
+
+	return strings.TrimSpace(author)
+}
+
+// PodcastDescription returns the description of the podcast.
+func (e *PodcastEntryElement) PodcastDescription() string {
+	description := ""
+
+	switch {
+	case e.GooglePlayDescription != "":
+		description = e.GooglePlayDescription
+	case e.Summary != "":
+		description = e.Summary
+	case e.Subtitle != "":
+		description = e.Subtitle
+	}
+	return strings.TrimSpace(description)
+}

+ 148 - 116
reader/rss/rss.go

@@ -20,16 +20,86 @@ import (
 	"miniflux.app/url"
 )
 
+// Specs: https://cyber.harvard.edu/rss/rss.html
 type rssFeed struct {
-	XMLName      xml.Name  `xml:"rss"`
-	Version      string    `xml:"version,attr"`
-	Title        string    `xml:"channel>title"`
-	Links        []rssLink `xml:"channel>link"`
-	Language     string    `xml:"channel>language"`
-	Description  string    `xml:"channel>description"`
-	PubDate      string    `xml:"channel>pubDate"`
-	ItunesAuthor string    `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd channel>author"`
-	Items        []rssItem `xml:"channel>item"`
+	XMLName        xml.Name  `xml:"rss"`
+	Version        string    `xml:"version,attr"`
+	Title          string    `xml:"channel>title"`
+	Links          []rssLink `xml:"channel>link"`
+	Language       string    `xml:"channel>language"`
+	Description    string    `xml:"channel>description"`
+	PubDate        string    `xml:"channel>pubDate"`
+	ManagingEditor string    `xml:"channel>managingEditor"`
+	Webmaster      string    `xml:"channel>webMaster"`
+	Items          []rssItem `xml:"channel>item"`
+	PodcastFeedElement
+}
+
+func (r *rssFeed) Transform() *model.Feed {
+	feed := new(model.Feed)
+	feed.SiteURL = r.siteURL()
+	feed.FeedURL = r.feedURL()
+	feed.Title = strings.TrimSpace(r.Title)
+
+	if feed.Title == "" {
+		feed.Title = feed.SiteURL
+	}
+
+	for _, item := range r.Items {
+		entry := item.Transform()
+		if entry.Author == "" {
+			entry.Author = r.feedAuthor()
+		}
+		entry.Author = sanitizer.StripTags(entry.Author)
+
+		if entry.URL == "" {
+			entry.URL = feed.SiteURL
+		} else {
+			entryURL, err := url.AbsoluteURL(feed.SiteURL, entry.URL)
+			if err == nil {
+				entry.URL = entryURL
+			}
+		}
+
+		if entry.Title == "" {
+			entry.Title = entry.URL
+		}
+
+		feed.Entries = append(feed.Entries, entry)
+	}
+
+	return feed
+}
+
+func (r *rssFeed) siteURL() string {
+	for _, element := range r.Links {
+		if element.XMLName.Space == "" {
+			return strings.TrimSpace(element.Data)
+		}
+	}
+
+	return ""
+}
+
+func (r *rssFeed) feedURL() string {
+	for _, element := range r.Links {
+		if element.XMLName.Space == "http://www.w3.org/2005/Atom" {
+			return strings.TrimSpace(element.Href)
+		}
+	}
+
+	return ""
+}
+
+func (r rssFeed) feedAuthor() string {
+	author := r.PodcastAuthor()
+	switch {
+	case r.ManagingEditor != "":
+		author = r.ManagingEditor
+	case r.Webmaster != "":
+		author = r.Webmaster
+	}
+	return strings.TrimSpace(author)
 }
 
 type rssLink struct {
@@ -48,6 +118,7 @@ type rssAuthor struct {
 	XMLName xml.Name
 	Data    string `xml:",chardata"`
 	Name    string `xml:"name"`
+	Email   string `xml:"email"`
 	Inner   string `xml:",innerxml"`
 }
 
@@ -66,83 +137,37 @@ func (enclosure *rssEnclosure) Size() int64 {
 }
 
 type rssItem struct {
-	GUID              string           `xml:"guid"`
-	Title             string           `xml:"title"`
-	Links             []rssLink        `xml:"link"`
-	OriginalLink      string           `xml:"http://rssnamespace.org/feedburner/ext/1.0 origLink"`
-	CommentLinks      []rssCommentLink `xml:"comments"`
-	Description       string           `xml:"description"`
-	EncodedContent    string           `xml:"http://purl.org/rss/1.0/modules/content/ encoded"`
-	PubDate           string           `xml:"pubDate"`
-	Date              string           `xml:"http://purl.org/dc/elements/1.1/ date"`
-	Authors           []rssAuthor      `xml:"author"`
-	Creator           string           `xml:"http://purl.org/dc/elements/1.1/ creator"`
-	EnclosureLinks    []rssEnclosure   `xml:"enclosure"`
-	OrigEnclosureLink string           `xml:"http://rssnamespace.org/feedburner/ext/1.0 origEnclosureLink"`
+	GUID           string           `xml:"guid"`
+	Title          string           `xml:"title"`
+	Links          []rssLink        `xml:"link"`
+	Description    string           `xml:"description"`
+	PubDate        string           `xml:"pubDate"`
+	Authors        []rssAuthor      `xml:"author"`
+	CommentLinks   []rssCommentLink `xml:"comments"`
+	EnclosureLinks []rssEnclosure   `xml:"enclosure"`
+	DublinCoreElement
+	FeedBurnerElement
+	PodcastEntryElement
 	media.Element
 }
 
-func (r *rssFeed) SiteURL() string {
-	for _, element := range r.Links {
-		if element.XMLName.Space == "" {
-			return strings.TrimSpace(element.Data)
-		}
-	}
-
-	return ""
-}
-
-func (r *rssFeed) FeedURL() string {
-	for _, element := range r.Links {
-		if element.XMLName.Space == "http://www.w3.org/2005/Atom" {
-			return strings.TrimSpace(element.Href)
-		}
-	}
-
-	return ""
-}
-
-func (r *rssFeed) Transform() *model.Feed {
-	feed := new(model.Feed)
-	feed.SiteURL = r.SiteURL()
-	feed.FeedURL = r.FeedURL()
-	feed.Title = strings.TrimSpace(r.Title)
-
-	if feed.Title == "" {
-		feed.Title = feed.SiteURL
-	}
-
-	for _, item := range r.Items {
-		entry := item.Transform()
-
-		if entry.Author == "" && r.ItunesAuthor != "" {
-			entry.Author = r.ItunesAuthor
-		}
-		entry.Author = strings.TrimSpace(sanitizer.StripTags(entry.Author))
-
-		if entry.URL == "" {
-			entry.URL = feed.SiteURL
-		} else {
-			entryURL, err := url.AbsoluteURL(feed.SiteURL, entry.URL)
-			if err == nil {
-				entry.URL = entryURL
-			}
-		}
-
-		if entry.Title == "" {
-			entry.Title = entry.URL
-		}
-
-		feed.Entries = append(feed.Entries, entry)
-	}
-
-	return feed
+func (r *rssItem) Transform() *model.Entry {
+	entry := new(model.Entry)
+	entry.URL = r.entryURL()
+	entry.CommentsURL = r.entryCommentsURL()
+	entry.Date = r.entryDate()
+	entry.Author = r.entryAuthor()
+	entry.Hash = r.entryHash()
+	entry.Content = r.entryContent()
+	entry.Title = r.entryTitle()
+	entry.Enclosures = r.entryEnclosures()
+	return entry
 }
 
-func (r *rssItem) PublishedDate() time.Time {
+func (r *rssItem) entryDate() time.Time {
 	value := r.PubDate
-	if r.Date != "" {
-		value = r.Date
+	if r.DublinCoreDate != "" {
+		value = r.DublinCoreDate
 	}
 
 	if value != "" {
@@ -158,22 +183,37 @@ func (r *rssItem) PublishedDate() time.Time {
 	return time.Now()
 }
 
-func (r *rssItem) Author() string {
-	for _, element := range r.Authors {
-		if element.Name != "" {
-			return element.Name
+func (r *rssItem) entryAuthor() string {
+	author := ""
+
+	for _, rssAuthor := range r.Authors {
+		switch rssAuthor.XMLName.Space {
+		case "http://www.itunes.com/dtds/podcast-1.0.dtd", "http://www.google.com/schemas/play-podcasts/1.0":
+			author = rssAuthor.Data
+		case "http://www.w3.org/2005/Atom":
+			if rssAuthor.Name != "" {
+				author = rssAuthor.Name
+			} else if rssAuthor.Email != "" {
+				author = rssAuthor.Email
+			}
+		default:
+			if rssAuthor.Name != "" {
+				author = rssAuthor.Name
+			} else {
+				author = rssAuthor.Inner
+			}
 		}
+	}
 
-		if element.Inner != "" {
-			return element.Inner
-		}
+	if author == "" {
+		author = r.DublinCoreCreator
 	}
 
-	return r.Creator
+	return strings.TrimSpace(author)
 }
 
-func (r *rssItem) Hash() string {
-	for _, value := range []string{r.GUID, r.URL()} {
+func (r *rssItem) entryHash() string {
+	for _, value := range []string{r.GUID, r.entryURL()} {
 		if value != "" {
 			return crypto.Hash(value)
 		}
@@ -182,17 +222,22 @@ func (r *rssItem) Hash() string {
 	return ""
 }
 
-func (r *rssItem) Content() string {
-	if r.EncodedContent != "" {
-		return r.EncodedContent
-	}
+func (r *rssItem) entryTitle() string {
+	return strings.TrimSpace(sanitizer.StripTags(r.Title))
+}
 
-	return r.Description
+func (r *rssItem) entryContent() string {
+	for _, value := range []string{r.DublinCoreContent, r.Description, r.PodcastDescription()} {
+		if value != "" {
+			return value
+		}
+	}
+	return ""
 }
 
-func (r *rssItem) URL() string {
-	if r.OriginalLink != "" {
-		return r.OriginalLink
+func (r *rssItem) entryURL() string {
+	if r.FeedBurnerLink != "" {
+		return r.FeedBurnerLink
 	}
 
 	for _, link := range r.Links {
@@ -208,7 +253,7 @@ func (r *rssItem) URL() string {
 	return ""
 }
 
-func (r *rssItem) Enclosures() model.EnclosureList {
+func (r *rssItem) entryEnclosures() model.EnclosureList {
 	enclosures := make(model.EnclosureList, 0)
 	duplicates := make(map[string]bool, 0)
 
@@ -226,10 +271,10 @@ func (r *rssItem) Enclosures() model.EnclosureList {
 	for _, enclosure := range r.EnclosureLinks {
 		enclosureURL := enclosure.URL
 
-		if r.OrigEnclosureLink != "" {
-			filename := path.Base(r.OrigEnclosureLink)
+		if r.FeedBurnerEnclosureLink != "" {
+			filename := path.Base(r.FeedBurnerEnclosureLink)
 			if strings.Contains(enclosureURL, filename) {
-				enclosureURL = r.OrigEnclosureLink
+				enclosureURL = r.FeedBurnerEnclosureLink
 			}
 		}
 
@@ -269,7 +314,7 @@ func (r *rssItem) Enclosures() model.EnclosureList {
 	return enclosures
 }
 
-func (r *rssItem) CommentsURL() string {
+func (r *rssItem) entryCommentsURL() string {
 	for _, commentLink := range r.CommentLinks {
 		if commentLink.XMLName.Space == "" {
 			return strings.TrimSpace(commentLink.Data)
@@ -279,19 +324,6 @@ func (r *rssItem) CommentsURL() string {
 	return ""
 }
 
-func (r *rssItem) Transform() *model.Entry {
-	entry := new(model.Entry)
-	entry.URL = r.URL()
-	entry.CommentsURL = r.CommentsURL()
-	entry.Date = r.PublishedDate()
-	entry.Author = r.Author()
-	entry.Hash = r.Hash()
-	entry.Content = r.Content()
-	entry.Title = strings.TrimSpace(r.Title)
-	entry.Enclosures = r.Enclosures()
-	return entry
-}
-
 func isValidLinkRelation(rel string) bool {
 	switch rel {
 	case "", "alternate", "enclosure", "related", "self", "via":