From 70d9d43ec2ea0e4248f46a31f79a579e6d152b4b Mon Sep 17 00:00:00 2001 From: joshuaflores Date: Tue, 23 Feb 2021 20:26:51 -0600 Subject: [PATCH] - Add basic support for JSON v1.1 (#169) - Refactor json tests to support multiple versions - Add support for multiple authors - Modified all tests to support multiple authors with single author fallback --- README.md | 61 ++++++------ detector_test.go | 2 +- feed.go | 8 +- json/feed.go | 37 ++++--- json/parser_test.go | 50 +++++++++- parser_test.go | 5 +- rss/feed.go | 2 +- .../parser/json/{ => invalid}/invalid.json | 0 .../{sample.json => version_json_10.json} | 0 .../version_json_10_expected.json} | 0 testdata/parser/json/version_json_11.json | 49 ++++++++++ .../version_json_11_deprecated_author.json | 47 +++++++++ ...on_json_11_deprecated_author_expected.json | 47 +++++++++ .../parser/json/version_json_11_expected.json | 49 ++++++++++ .../universal/json10_feed.json} | 0 testdata/parser/universal/json11_feed.json | 49 ++++++++++ ...thor_email_-_atom03_feed_author_email.json | 19 ++-- ...thor_email_-_atom10_feed_author_email.json | 19 ++-- ...author_name_-_atom03_feed_author_name.json | 19 ++-- ...author_name_-_atom10_feed_author_name.json | 19 ++-- ...mail_-_atom03_feed_entry_author_email.json | 21 ++-- ...mail_-_atom10_feed_entry_author_email.json | 21 ++-- ..._name_-_atom03_feed_entry_author_name.json | 21 ++-- ..._name_-_atom10_feed_entry_author_name.json | 21 ++-- testdata/translator/json/json10_full.json | 47 +++++++++ .../translator/json/json10_full_expected.json | 63 ++++++++++++ .../json/{sample2.json => json10_simple.json} | 0 .../json/json10_simple_expected.json | 12 +++ testdata/translator/json/json11_full.json | 52 ++++++++++ .../translator/json/json11_full_expected.json | 54 +++++++++++ ...or_-_rss_channel_managingEditor_email.json | 19 ++-- ...s_channel_managingEditor_emailAndName.json | 22 +++-- ...hor_-_rss_channel_managingEditor_name.json | 19 ++-- ...s_channel_managingEditor_nameAndEmail.json | 22 +++-- ..._author_-_rss_channel_webMaster_email.json | 19 ++-- ..._-_rss_channel_webMaster_emailAndName.json | 22 +++-- ...d_author_-_rss_channel_webMaster_name.json | 19 ++-- ..._-_rss_channel_webMaster_nameAndEmail.json | 22 +++-- .../feed_category_-_rss_channel_category.json | 16 ++-- ...eed_copyright_-_rss_channel_copyright.json | 10 +- ...description_-_rdf_channel_description.json | 10 +- ...description_-_rss_channel_description.json | 10 +- ...eed_generator_-_rss_channel_generator.json | 10 +- .../rss/feed_image_-_rdf_image.json | 12 +-- .../rss/feed_image_-_rss_channel_image.json | 16 ++-- ...uthor_-_rss_channel_item_author_email.json | 21 ++-- ..._rss_channel_item_author_emailAndName.json | 24 +++-- ...author_-_rss_channel_item_author_name.json | 21 ++-- ..._rss_channel_item_author_nameAndEmail.json | 24 +++-- ..._category_-_rss_channel_item_category.json | 22 ++--- ...em_description_-_rdf_item_description.json | 16 ++-- ...iption_-_rss_channel_item_description.json | 16 ++-- ...nclosure_-_rss_channel_item_enclosure.json | 24 ++--- ...eed_item_guid_-_rss_channel_item_guid.json | 16 ++-- .../rss/feed_item_link_-_rdf_item_link.json | 16 ++-- ...eed_item_link_-_rss_channel_item_link.json | 16 ++-- ..._published_-_rss_channel_item_pubDate.json | 18 ++-- .../rss/feed_item_title_-_rdf_item_title.json | 16 ++-- ...d_item_title_-_rss_channel_item_title.json | 16 ++-- .../feed_language_-_rss_channel_language.json | 10 +- .../rss/feed_link_-_rdf_channel_link.json | 10 +- .../feed_link_-_rss_channel_atom_link.json | 38 ++++---- .../rss/feed_link_-_rss_channel_link.json | 10 +- .../feed_published_-_rss_channel_pubDate.json | 12 +-- .../rss/feed_title_-_rdf_channel_title.json | 10 +- .../rss/feed_title_-_rss_channel_title.json | 10 +- ...d_updated_-_rss_channel_lastBuildDate.json | 12 +-- translator.go | 96 +++++++++++++++++++ translator_test.go | 49 +++++++++- 69 files changed, 1166 insertions(+), 399 deletions(-) rename testdata/parser/json/{ => invalid}/invalid.json (100%) rename testdata/parser/json/{sample.json => version_json_10.json} (100%) rename testdata/parser/{universal/json_feed.json => json/version_json_10_expected.json} (100%) create mode 100644 testdata/parser/json/version_json_11.json create mode 100644 testdata/parser/json/version_json_11_deprecated_author.json create mode 100644 testdata/parser/json/version_json_11_deprecated_author_expected.json create mode 100644 testdata/parser/json/version_json_11_expected.json rename testdata/{translator/json/sample.json => parser/universal/json10_feed.json} (100%) create mode 100644 testdata/parser/universal/json11_feed.json create mode 100644 testdata/translator/json/json10_full.json create mode 100644 testdata/translator/json/json10_full_expected.json rename testdata/translator/json/{sample2.json => json10_simple.json} (100%) create mode 100644 testdata/translator/json/json10_simple_expected.json create mode 100644 testdata/translator/json/json11_full.json create mode 100644 testdata/translator/json/json11_full_expected.json diff --git a/README.md b/README.md index a3473b71..898c7559 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ The `gofeed` library is a robust feed parser that supports parsing both [RSS](ht - Atom 0.3 - Atom 1.0 - JSON 1.0 +- JSON 1.1 #### Extension Support @@ -228,36 +229,36 @@ In addition to the generic handling of extensions, `gofeed` also has built in su The `DefaultRSSTranslator`, the `DefaultAtomTranslator` and the `DefaultJSONTranslator` map the following `rss.Feed`, `atom.Feed` and `json.Feed` fields to their respective `gofeed.Feed` fields. They are listed in order of precedence (highest to lowest): -`gofeed.Feed` | RSS | Atom | JSON ---- | --- | --- | -- -Title | /rss/channel/title
/rdf:RDF/channel/title
/rss/channel/dc:title
/rdf:RDF/channel/dc:title | /feed/title | /title -Description | /rss/channel/description
/rdf:RDF/channel/description
/rss/channel/itunes:subtitle | /feed/subtitle
/feed/tagline | /description -Link | /rss/channel/link
/rdf:RDF/channel/link | /feed/link[@rel=”alternate”]/@href
/feed/link[not(@rel)]/@href | /home_page_url -FeedLink | /rss/channel/atom:link[@rel="self"]/@href
/rdf:RDF/channel/atom:link[@rel="self"]/@href | /feed/link[@rel="self"]/@href | /feed_url -Updated | /rss/channel/lastBuildDate
/rss/channel/dc:date
/rdf:RDF/channel/dc:date | /feed/updated
/feed/modified | /items[0]/date_modified -Published | /rss/channel/pubDate | | /items[0]/date_published -Author | /rss/channel/managingEditor
/rss/channel/webMaster
/rss/channel/dc:author
/rdf:RDF/channel/dc:author
/rss/channel/dc:creator
/rdf:RDF/channel/dc:creator
/rss/channel/itunes:author | /feed/author | /author/name -Language | /rss/channel/language
/rss/channel/dc:language
/rdf:RDF/channel/dc:language | /feed/@xml:lang | -Image | /rss/channel/image
/rdf:RDF/image
/rss/channel/itunes:image | /feed/logo | /icon -Copyright | /rss/channel/copyright
/rss/channel/dc:rights
/rdf:RDF/channel/dc:rights | /feed/rights
/feed/copyright | -Generator | /rss/channel/generator | /feed/generator | -Categories | /rss/channel/category
/rss/channel/itunes:category
/rss/channel/itunes:keywords
/rss/channel/dc:subject
/rdf:RDF/channel/dc:subject | /feed/category | - - -`gofeed.Item` | RSS | Atom | JSON ---- | --- | --- | --- -Title | /rss/channel/item/title
/rdf:RDF/item/title
/rdf:RDF/item/dc:title
/rss/channel/item/dc:title | /feed/entry/title | /items/title -Description | /rss/channel/item/description
/rdf:RDF/item/description
/rss/channel/item/dc:description
/rdf:RDF/item/dc:description | /feed/entry/summary | /items/summary -Content | /rss/channel/item/content:encoded | /feed/entry/content | /items/content_html -Link | /rss/channel/item/link
/rdf:RDF/item/link | /feed/entry/link[@rel=”alternate”]/@href
/feed/entry/link[not(@rel)]/@href | /items/url -Updated | /rss/channel/item/dc:date
/rdf:RDF/rdf:item/dc:date | /feed/entry/modified
/feed/entry/updated | /items/date_modified -Published | /rss/channel/item/pubDate
/rss/channel/item/dc:date | /feed/entry/published
/feed/entry/issued | /items/date_published -Author | /rss/channel/item/author
/rss/channel/item/dc:author
/rdf:RDF/item/dc:author
/rss/channel/item/dc:creator
/rdf:RDF/item/dc:creator
/rss/channel/item/itunes:author | /feed/entry/author | /items/author/name -GUID | /rss/channel/item/guid | /feed/entry/id | /items/id -Image | /rss/channel/item/itunes:image
/rss/channel/item/media:image | | /items/image
/items/banner_image -Categories | /rss/channel/item/category
/rss/channel/item/dc:subject
/rss/channel/item/itunes:keywords
/rdf:RDF/channel/item/dc:subject | /feed/entry/category | /items/tags -Enclosures | /rss/channel/item/enclosure | /feed/entry/link[@rel=”enclosure”] | /items/attachments - +| `gofeed.Feed` | RSS | Atom | JSON | +| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | ------------------------ | +| Title | /rss/channel/title
/rdf:RDF/channel/title
/rss/channel/dc:title
/rdf:RDF/channel/dc:title | /feed/title | /title | +| Description | /rss/channel/description
/rdf:RDF/channel/description
/rss/channel/itunes:subtitle | /feed/subtitle
/feed/tagline | /description | +| Link | /rss/channel/link
/rdf:RDF/channel/link | /feed/link[@rel=”alternate”]/@href
/feed/link[not(@rel)]/@href | /home_page_url | +| FeedLink | /rss/channel/atom:link[@rel="self"]/@href
/rdf:RDF/channel/atom:link[@rel="self"]/@href | /feed/link[@rel="self"]/@href | /feed_url | +| Updated | /rss/channel/lastBuildDate
/rss/channel/dc:date
/rdf:RDF/channel/dc:date | /feed/updated
/feed/modified | /items[0]/date_modified | +| Published | /rss/channel/pubDate | | /items[0]/date_published | +| Author | /rss/channel/managingEditor
/rss/channel/webMaster
/rss/channel/dc:author
/rdf:RDF/channel/dc:author
/rss/channel/dc:creator
/rdf:RDF/channel/dc:creator
/rss/channel/itunes:author | /feed/authors[0] | /author | +| Authors | /rss/channel/managingEditor
/rss/channel/webMaster
/rss/channel/dc:author
/rdf:RDF/channel/dc:author
/rss/channel/dc:creator
/rdf:RDF/channel/dc:creator
/rss/channel/itunes:author | /feed/authors | /authors
/author | +| Language | /rss/channel/language
/rss/channel/dc:language
/rdf:RDF/channel/dc:language | /feed/@xml:lang | /language | +| Image | /rss/channel/image
/rdf:RDF/image
/rss/channel/itunes:image | /feed/logo | /icon | +| Copyright | /rss/channel/copyright
/rss/channel/dc:rights
/rdf:RDF/channel/dc:rights | /feed/rights
/feed/copyright | +| Generator | /rss/channel/generator | /feed/generator | +| Categories | /rss/channel/category
/rss/channel/itunes:category
/rss/channel/itunes:keywords
/rss/channel/dc:subject
/rdf:RDF/channel/dc:subject | /feed/category | + +| `gofeed.Item` | RSS | Atom | JSON | +| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ----------------------------------- | +| Title | /rss/channel/item/title
/rdf:RDF/item/title
/rdf:RDF/item/dc:title
/rss/channel/item/dc:title | /feed/entry/title | /items/title | +| Description | /rss/channel/item/description
/rdf:RDF/item/description
/rss/channel/item/dc:description
/rdf:RDF/item/dc:description | /feed/entry/summary | /items/summary | +| Content | /rss/channel/item/content:encoded | /feed/entry/content | /items/content_html | +| Link | /rss/channel/item/link
/rdf:RDF/item/link | /feed/entry/link[@rel=”alternate”]/@href
/feed/entry/link[not(@rel)]/@href | /items/url | +| Updated | /rss/channel/item/dc:date
/rdf:RDF/rdf:item/dc:date | /feed/entry/modified
/feed/entry/updated | /items/date_modified | +| Published | /rss/channel/item/pubDate
/rss/channel/item/dc:date | /feed/entry/published
/feed/entry/issued | /items/date_published | +| Author | /rss/channel/item/author
/rss/channel/item/dc:author
/rdf:RDF/item/dc:author
/rss/channel/item/dc:creator
/rdf:RDF/item/dc:creator
/rss/channel/item/itunes:author | /feed/entry/author | /items/author/name | +| Authors | /rss/channel/item/author
/rss/channel/item/dc:author
/rdf:RDF/item/dc:author
/rss/channel/item/dc:creator
/rdf:RDF/item/dc:creator
/rss/channel/item/itunes:author | /feed/entry/authors[0] | /items/authors
/items/author/name | +| GUID | /rss/channel/item/guid | /feed/entry/id | /items/id | +| Image | /rss/channel/item/itunes:image
/rss/channel/item/media:image | | /items/image
/items/banner_image | +| Categories | /rss/channel/item/category
/rss/channel/item/dc:subject
/rss/channel/item/itunes:keywords
/rdf:RDF/channel/item/dc:subject | /feed/entry/category | /items/tags | +| Enclosures | /rss/channel/item/enclosure | /feed/entry/link[@rel=”enclosure”] | /items/attachments | ## Dependencies diff --git a/detector_test.go b/detector_test.go index d438bf7a..b5243d46 100644 --- a/detector_test.go +++ b/detector_test.go @@ -24,7 +24,7 @@ func TestDetectFeedType(t *testing.T) { {"rdf_feed.xml", gofeed.FeedTypeRSS}, {"unknown_feed.xml", gofeed.FeedTypeUnknown}, {"empty_feed.xml", gofeed.FeedTypeUnknown}, - {"json_feed.json", gofeed.FeedTypeJSON}, + {"json10_feed.json", gofeed.FeedTypeJSON}, } for _, test := range feedTypeTests { diff --git a/feed.go b/feed.go index 8d95c04f..266856fd 100644 --- a/feed.go +++ b/feed.go @@ -4,7 +4,7 @@ import ( "encoding/json" "time" - "github.com/mmcdole/gofeed/extensions" + ext "github.com/mmcdole/gofeed/extensions" ) // Feed is the universal Feed type that atom.Feed @@ -21,7 +21,8 @@ type Feed struct { UpdatedParsed *time.Time `json:"updatedParsed,omitempty"` Published string `json:"published,omitempty"` PublishedParsed *time.Time `json:"publishedParsed,omitempty"` - Author *Person `json:"author,omitempty"` + Author *Person `json:"author,omitempty"` // Deprecated: Use feed.Authors instead + Authors []*Person `json:"authors,omitempty"` Language string `json:"language,omitempty"` Image *Image `json:"image,omitempty"` Copyright string `json:"copyright,omitempty"` @@ -53,7 +54,8 @@ type Item struct { UpdatedParsed *time.Time `json:"updatedParsed,omitempty"` Published string `json:"published,omitempty"` PublishedParsed *time.Time `json:"publishedParsed,omitempty"` - Author *Person `json:"author,omitempty"` + Author *Person `json:"author,omitempty"` // Deprecated: Use item.Authors instead + Authors []*Person `json:"authors,omitempty"` GUID string `json:"guid,omitempty"` Image *Image `json:"image,omitempty"` Categories []string `json:"categories,omitempty"` diff --git a/json/feed.go b/json/feed.go index 8539fced..8e4434b6 100644 --- a/json/feed.go +++ b/json/feed.go @@ -19,6 +19,10 @@ type Feed struct { Items []*Item `json:"items"` // items is an array, and is required // TODO Hubs // hubs (very optional, array of objects) describes endpoints that can be used to subscribe to real-time notifications from the publisher of this feed. Each object has a type and url, both of which are required. See the section “Subscribing to Real-time Notifications” below for details. // TODO Extensions + + // Version 1.1 + Authors []*Author `json:"authors,omitempty"` + Language string `json:"language,omitempty"` } func (f Feed) String() string { @@ -28,21 +32,26 @@ func (f Feed) String() string { // Item defines an item in the feed type Item struct { - ID string `json:"id,omitempty"` // id (required, string) is unique for that item for that feed over time. If an id is presented as a number or other type, a JSON Feed reader must coerce it to a string. Ideally, the id is the full URL of the resource described by the item, since URLs make great unique identifiers. - URL string `json:"url,omitempty"` // url (optional, string) is the URL of the resource described by the item. It’s the permalink - ExternalURL string `json:"external_url,omitempty"` // external_url (very optional, string) is the URL of a page elsewhere. This is especially useful for linkblogs - Title string `json:"title,omitempty"` // title (optional, string) is plain text. Microblog items in particular may omit titles. - ContentHTML string `json:"content_html,omitempty"` // content_html and content_text are each optional strings — but one or both must be present. This is the HTML or plain text of the item. Important: the only place HTML is allowed in this format is in content_html. A Twitter-like service might use content_text, while a blog might use content_html. Use whichever makes sense for your resource. (It doesn’t even have to be the same for each item in a feed.) - ContentText string `json:"content_text,omitempty"` // Same as above - Summary string `json:"summary,omitempty"` // summary (optional, string) is a plain text sentence or two describing the item. - Image string `json:"image,omitempty"` // image (optional, string) is the URL of the main image for the item. This image may also appear in the content_html - BannerImage string `json:"banner_image,omitempty"` // banner_image (optional, string) is the URL of an image to use as a banner. - DatePublished string `json:"date_published,omitempty"` // date_published (optional, string) specifies the date in RFC 3339 format. (Example: 2010-02-07T14:04:00-05:00.) - DateModified string `json:"date_modified,omitempty"` // date_modified (optional, string) specifies the modification date in RFC 3339 format. - Author *Author `json:"author,omitempty"` // author (optional, object) has the same structure as the top-level author. If not specified in an item, then the top-level author, if present, is the author of the item. - Tags []string `json:"tags,omitempty"` // tags (optional, array of strings) can have any plain text values you want. Tags tend to be just one word, but they may be anything. - Attachments *[]Attachments `json:"attachments,omitempty"` // attachments (optional, array) lists related resources. Podcasts, for instance, would include an attachment that’s an audio or video file. An individual item may have one or more attachments. + ID string `json:"id,omitempty"` // id (required, string) is unique for that item for that feed over time. If an id is presented as a number or other type, a JSON Feed reader must coerce it to a string. Ideally, the id is the full URL of the resource described by the item, since URLs make great unique identifiers. + URL string `json:"url,omitempty"` // url (optional, string) is the URL of the resource described by the item. It’s the permalink + ExternalURL string `json:"external_url,omitempty"` // external_url (very optional, string) is the URL of a page elsewhere. This is especially useful for linkblogs + Title string `json:"title,omitempty"` // title (optional, string) is plain text. Microblog items in particular may omit titles. + ContentHTML string `json:"content_html,omitempty"` // content_html and content_text are each optional strings — but one or both must be present. This is the HTML or plain text of the item. Important: the only place HTML is allowed in this format is in content_html. A Twitter-like service might use content_text, while a blog might use content_html. Use whichever makes sense for your resource. (It doesn’t even have to be the same for each item in a feed.) + ContentText string `json:"content_text,omitempty"` // Same as above + Summary string `json:"summary,omitempty"` // summary (optional, string) is a plain text sentence or two describing the item. + Image string `json:"image,omitempty"` // image (optional, string) is the URL of the main image for the item. This image may also appear in the content_html + BannerImage string `json:"banner_image,omitempty"` // banner_image (optional, string) is the URL of an image to use as a banner. + DatePublished string `json:"date_published,omitempty"` // date_published (optional, string) specifies the date in RFC 3339 format. (Example: 2010-02-07T14:04:00-05:00.) + DateModified string `json:"date_modified,omitempty"` // date_modified (optional, string) specifies the modification date in RFC 3339 format. + Author *Author `json:"author,omitempty"` // author (optional, object) has the same structure as the top-level author. If not specified in an item, then the top-level author, if present, is the author of the item. + + Tags []string `json:"tags,omitempty"` // tags (optional, array of strings) can have any plain text values you want. Tags tend to be just one word, but they may be anything. + Attachments *[]Attachments `json:"attachments,omitempty"` // attachments (optional, array) lists related resources. Podcasts, for instance, would include an attachment that’s an audio or video file. An individual item may have one or more attachments. // TODO Extensions + + // Version 1.1 + Authors []*Author `json:"authors,omitempty"` + Language string `json:"language,omitempty"` } // Author defines the feed author structure. The author object has several members. These are all optional — but if you provide an author object, then at least one is required: diff --git a/json/parser_test.go b/json/parser_test.go index f36acbbd..1cad073d 100644 --- a/json/parser_test.go +++ b/json/parser_test.go @@ -2,31 +2,73 @@ package json_test import ( "bytes" + "encoding/json" "fmt" "io/ioutil" + "path/filepath" + "strings" "testing" - "github.com/mmcdole/gofeed/json" + jsonParser "github.com/mmcdole/gofeed/json" "github.com/stretchr/testify/assert" ) // Tests +// TODO: add tests for invalid + func TestParser_Parse(t *testing.T) { + files, _ := filepath.Glob("../testdata/parser/json/*.json") + for _, f := range files { + base := filepath.Base(f) + name := strings.TrimSuffix(base, filepath.Ext(base)) + + if strings.HasSuffix(name, "expected") { + continue + } + + fmt.Printf("Testing %s... ", name) + + // Get actual source feed + ff := fmt.Sprintf("../testdata/parser/json/%s.json", name) + f, _ := ioutil.ReadFile(ff) + + // Parse actual feed + fp := &jsonParser.Parser{} + actual, _ := fp.Parse(bytes.NewReader(f)) + + // Get json encoded expected feed result + ef := fmt.Sprintf("../testdata/parser/json/%s_expected.json", name) + e, _ := ioutil.ReadFile(ef) + + // Unmarshal expected feed + expected := &jsonParser.Feed{} + json.Unmarshal(e, &expected) + + if assert.Equal(t, expected, actual, "Feed file %s.json did not match expected output %s.json", name, name) { + fmt.Printf("OK\n") + } else { + fmt.Printf("Failed\n") + } + } +} + +// TODO: Remove redundant tests +func TestParser_ParseInvalidAndStruct(t *testing.T) { name := "invalid" fmt.Printf("Testing %s... ", name) // Get actual source feed - ff := fmt.Sprintf("../testdata/parser/json/%s.json", name) + ff := fmt.Sprintf("../testdata/parser/json/invalid/%s.json", name) fmt.Println(ff) f, _ := ioutil.ReadFile(ff) // Parse actual feed - fp := &json.Parser{} + fp := &jsonParser.Parser{} _, err := fp.Parse(bytes.NewReader(f)) assert.Contains(t, err.Error(), "expect }") - name = "sample" + name = "version_json_10" fmt.Printf("Testing %s... ", name) // Get actual source feed diff --git a/parser_test.go b/parser_test.go index 287b82d1..3b58a3ad 100644 --- a/parser_test.go +++ b/parser_test.go @@ -31,6 +31,8 @@ func TestParser_Parse(t *testing.T) { {"rss_feed_leading_spaces.xml", "rss", "Feed Title", false}, {"rdf_feed.xml", "rss", "Feed Title", false}, {"sample.json", "json", "title", false}, + {"json10_feed.json", "json", "title", false}, + {"json11_feed.json", "json", "title", false}, {"unknown_feed.xml", "", "", true}, {"empty_feed.xml", "", "", true}, {"invalid.json", "", "", true}, @@ -114,7 +116,8 @@ func TestParser_ParseURL_Success(t *testing.T) { {"rss_feed_bom.xml", "rss", "Feed Title", false}, {"rss_feed_leading_spaces.xml", "rss", "Feed Title", false}, {"rdf_feed.xml", "rss", "Feed Title", false}, - {"sample.json", "json", "title", false}, + {"json10_feed.json", "json", "title", false}, + {"json11_feed.json", "json", "title", false}, {"unknown_feed.xml", "", "", true}, {"invalid.json", "", "", true}, } diff --git a/rss/feed.go b/rss/feed.go index 5366a4ef..fc8041de 100644 --- a/rss/feed.go +++ b/rss/feed.go @@ -4,7 +4,7 @@ import ( "encoding/json" "time" - "github.com/mmcdole/gofeed/extensions" + ext "github.com/mmcdole/gofeed/extensions" ) // Feed is an RSS Feed diff --git a/testdata/parser/json/invalid.json b/testdata/parser/json/invalid/invalid.json similarity index 100% rename from testdata/parser/json/invalid.json rename to testdata/parser/json/invalid/invalid.json diff --git a/testdata/parser/json/sample.json b/testdata/parser/json/version_json_10.json similarity index 100% rename from testdata/parser/json/sample.json rename to testdata/parser/json/version_json_10.json diff --git a/testdata/parser/universal/json_feed.json b/testdata/parser/json/version_json_10_expected.json similarity index 100% rename from testdata/parser/universal/json_feed.json rename to testdata/parser/json/version_json_10_expected.json diff --git a/testdata/parser/json/version_json_11.json b/testdata/parser/json/version_json_11.json new file mode 100644 index 00000000..8504e796 --- /dev/null +++ b/testdata/parser/json/version_json_11.json @@ -0,0 +1,49 @@ +{ + "version": "1.1", + "title": "title", + "home_page_url": "https://sample-json-feed.com", + "feed_url": "https://sample-json-feed.com/feed.json", + "description": "description", + "user_comment": "user_comment", + "next_url": "https://sample-json-feed.com/feed.json?next=500", + "icon": "https://sample-json-feed.com/icon.png", + "favicon": "https://sample-json-feed.com/favicon.png", + "authors": [ + { + "name": "author_name", + "url": "https://sample-feed-author.com", + "avatar": "https://sample-feed-author.com/me.png" + } + ], + "expired": false, + "items": [ + { + "id": "id", + "url": "https://sample-json-feed.com/id", + "external_url": "https://sample-json-feed.com/external", + "title": "title", + "content_html": "

content_html

", + "content_text": "content_text", + "summary": "summary", + "image": "https://sample-json-feed.com/image.png", + "banner_image": "https://sample-json-feed.com/banner_image.png", + "date_published": "2019-10-12T07:20:50.52Z", + "date_modified": "2019-10-12T07:20:50.52Z", + "author": { + "name": "author_name", + "url": "https://sample-feed-author.com", + "avatar": "https://sample-feed-author.com/me.png" + }, + "tags": ["tag1", "tag2"], + "attachments": [ + { + "url": "https://sample-json-feed.com/attachment", + "mime_type": "audio/mpeg", + "title": "title", + "size_in_bytes": 100, + "duration_in_seconds": 100 + } + ] + } + ] +} diff --git a/testdata/parser/json/version_json_11_deprecated_author.json b/testdata/parser/json/version_json_11_deprecated_author.json new file mode 100644 index 00000000..d2ac04cc --- /dev/null +++ b/testdata/parser/json/version_json_11_deprecated_author.json @@ -0,0 +1,47 @@ +{ + "version": "1.1", + "title": "title", + "home_page_url": "https://sample-json-feed.com", + "feed_url": "https://sample-json-feed.com/feed.json", + "description": "description", + "user_comment": "user_comment", + "next_url": "https://sample-json-feed.com/feed.json?next=500", + "icon": "https://sample-json-feed.com/icon.png", + "favicon": "https://sample-json-feed.com/favicon.png", + "author": { + "name": "author_name", + "url": "https://sample-feed-author.com", + "avatar": "https://sample-feed-author.com/me.png" + }, + "expired": false, + "items": [ + { + "id": "id", + "url": "https://sample-json-feed.com/id", + "external_url": "https://sample-json-feed.com/external", + "title": "title", + "content_html": "

content_html

", + "content_text": "content_text", + "summary": "summary", + "image": "https://sample-json-feed.com/image.png", + "banner_image": "https://sample-json-feed.com/banner_image.png", + "date_published": "2019-10-12T07:20:50.52Z", + "date_modified": "2019-10-12T07:20:50.52Z", + "author": { + "name": "author_name", + "url": "https://sample-feed-author.com", + "avatar": "https://sample-feed-author.com/me.png" + }, + "tags": ["tag1", "tag2"], + "attachments": [ + { + "url": "https://sample-json-feed.com/attachment", + "mime_type": "audio/mpeg", + "title": "title", + "size_in_bytes": 100, + "duration_in_seconds": 100 + } + ] + } + ] +} diff --git a/testdata/parser/json/version_json_11_deprecated_author_expected.json b/testdata/parser/json/version_json_11_deprecated_author_expected.json new file mode 100644 index 00000000..d2ac04cc --- /dev/null +++ b/testdata/parser/json/version_json_11_deprecated_author_expected.json @@ -0,0 +1,47 @@ +{ + "version": "1.1", + "title": "title", + "home_page_url": "https://sample-json-feed.com", + "feed_url": "https://sample-json-feed.com/feed.json", + "description": "description", + "user_comment": "user_comment", + "next_url": "https://sample-json-feed.com/feed.json?next=500", + "icon": "https://sample-json-feed.com/icon.png", + "favicon": "https://sample-json-feed.com/favicon.png", + "author": { + "name": "author_name", + "url": "https://sample-feed-author.com", + "avatar": "https://sample-feed-author.com/me.png" + }, + "expired": false, + "items": [ + { + "id": "id", + "url": "https://sample-json-feed.com/id", + "external_url": "https://sample-json-feed.com/external", + "title": "title", + "content_html": "

content_html

", + "content_text": "content_text", + "summary": "summary", + "image": "https://sample-json-feed.com/image.png", + "banner_image": "https://sample-json-feed.com/banner_image.png", + "date_published": "2019-10-12T07:20:50.52Z", + "date_modified": "2019-10-12T07:20:50.52Z", + "author": { + "name": "author_name", + "url": "https://sample-feed-author.com", + "avatar": "https://sample-feed-author.com/me.png" + }, + "tags": ["tag1", "tag2"], + "attachments": [ + { + "url": "https://sample-json-feed.com/attachment", + "mime_type": "audio/mpeg", + "title": "title", + "size_in_bytes": 100, + "duration_in_seconds": 100 + } + ] + } + ] +} diff --git a/testdata/parser/json/version_json_11_expected.json b/testdata/parser/json/version_json_11_expected.json new file mode 100644 index 00000000..8504e796 --- /dev/null +++ b/testdata/parser/json/version_json_11_expected.json @@ -0,0 +1,49 @@ +{ + "version": "1.1", + "title": "title", + "home_page_url": "https://sample-json-feed.com", + "feed_url": "https://sample-json-feed.com/feed.json", + "description": "description", + "user_comment": "user_comment", + "next_url": "https://sample-json-feed.com/feed.json?next=500", + "icon": "https://sample-json-feed.com/icon.png", + "favicon": "https://sample-json-feed.com/favicon.png", + "authors": [ + { + "name": "author_name", + "url": "https://sample-feed-author.com", + "avatar": "https://sample-feed-author.com/me.png" + } + ], + "expired": false, + "items": [ + { + "id": "id", + "url": "https://sample-json-feed.com/id", + "external_url": "https://sample-json-feed.com/external", + "title": "title", + "content_html": "

content_html

", + "content_text": "content_text", + "summary": "summary", + "image": "https://sample-json-feed.com/image.png", + "banner_image": "https://sample-json-feed.com/banner_image.png", + "date_published": "2019-10-12T07:20:50.52Z", + "date_modified": "2019-10-12T07:20:50.52Z", + "author": { + "name": "author_name", + "url": "https://sample-feed-author.com", + "avatar": "https://sample-feed-author.com/me.png" + }, + "tags": ["tag1", "tag2"], + "attachments": [ + { + "url": "https://sample-json-feed.com/attachment", + "mime_type": "audio/mpeg", + "title": "title", + "size_in_bytes": 100, + "duration_in_seconds": 100 + } + ] + } + ] +} diff --git a/testdata/translator/json/sample.json b/testdata/parser/universal/json10_feed.json similarity index 100% rename from testdata/translator/json/sample.json rename to testdata/parser/universal/json10_feed.json diff --git a/testdata/parser/universal/json11_feed.json b/testdata/parser/universal/json11_feed.json new file mode 100644 index 00000000..8504e796 --- /dev/null +++ b/testdata/parser/universal/json11_feed.json @@ -0,0 +1,49 @@ +{ + "version": "1.1", + "title": "title", + "home_page_url": "https://sample-json-feed.com", + "feed_url": "https://sample-json-feed.com/feed.json", + "description": "description", + "user_comment": "user_comment", + "next_url": "https://sample-json-feed.com/feed.json?next=500", + "icon": "https://sample-json-feed.com/icon.png", + "favicon": "https://sample-json-feed.com/favicon.png", + "authors": [ + { + "name": "author_name", + "url": "https://sample-feed-author.com", + "avatar": "https://sample-feed-author.com/me.png" + } + ], + "expired": false, + "items": [ + { + "id": "id", + "url": "https://sample-json-feed.com/id", + "external_url": "https://sample-json-feed.com/external", + "title": "title", + "content_html": "

content_html

", + "content_text": "content_text", + "summary": "summary", + "image": "https://sample-json-feed.com/image.png", + "banner_image": "https://sample-json-feed.com/banner_image.png", + "date_published": "2019-10-12T07:20:50.52Z", + "date_modified": "2019-10-12T07:20:50.52Z", + "author": { + "name": "author_name", + "url": "https://sample-feed-author.com", + "avatar": "https://sample-feed-author.com/me.png" + }, + "tags": ["tag1", "tag2"], + "attachments": [ + { + "url": "https://sample-json-feed.com/attachment", + "mime_type": "audio/mpeg", + "title": "title", + "size_in_bytes": 100, + "duration_in_seconds": 100 + } + ] + } + ] +} diff --git a/testdata/translator/atom/feed_author_email_-_atom03_feed_author_email.json b/testdata/translator/atom/feed_author_email_-_atom03_feed_author_email.json index c872d8c9..4165c706 100644 --- a/testdata/translator/atom/feed_author_email_-_atom03_feed_author_email.json +++ b/testdata/translator/atom/feed_author_email_-_atom03_feed_author_email.json @@ -1,8 +1,13 @@ { - "author": { - "email": "email@example.org" - }, - "items": [], - "feedType": "atom", - "feedVersion": "0.3" -} + "author": { + "email": "email@example.org" + }, + "authors": [ + { + "email": "email@example.org" + } + ], + "feedType": "atom", + "feedVersion": "0.3", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/atom/feed_author_email_-_atom10_feed_author_email.json b/testdata/translator/atom/feed_author_email_-_atom10_feed_author_email.json index 89842994..d647d36d 100644 --- a/testdata/translator/atom/feed_author_email_-_atom10_feed_author_email.json +++ b/testdata/translator/atom/feed_author_email_-_atom10_feed_author_email.json @@ -1,8 +1,13 @@ { - "author": { - "email": "email@example.org" - }, - "items": [], - "feedType": "atom", - "feedVersion": "1.0" -} + "author": { + "email": "email@example.org" + }, + "authors": [ + { + "email": "email@example.org" + } + ], + "feedType": "atom", + "feedVersion": "1.0", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/atom/feed_author_name_-_atom03_feed_author_name.json b/testdata/translator/atom/feed_author_name_-_atom03_feed_author_name.json index 032cb4f0..eeb11746 100644 --- a/testdata/translator/atom/feed_author_name_-_atom03_feed_author_name.json +++ b/testdata/translator/atom/feed_author_name_-_atom03_feed_author_name.json @@ -1,8 +1,13 @@ { - "author": { - "name": "Author Name" - }, - "items": [], - "feedType": "atom", - "feedVersion": "0.3" -} + "author": { + "name": "Author Name" + }, + "authors": [ + { + "name": "Author Name" + } + ], + "feedType": "atom", + "feedVersion": "0.3", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/atom/feed_author_name_-_atom10_feed_author_name.json b/testdata/translator/atom/feed_author_name_-_atom10_feed_author_name.json index ada4b79b..c67e6c5c 100644 --- a/testdata/translator/atom/feed_author_name_-_atom10_feed_author_name.json +++ b/testdata/translator/atom/feed_author_name_-_atom10_feed_author_name.json @@ -1,8 +1,13 @@ { - "author": { - "name": "Author Name" - }, - "items": [], - "feedType": "atom", - "feedVersion": "1.0" -} + "author": { + "name": "Author Name" + }, + "authors": [ + { + "name": "Author Name" + } + ], + "feedType": "atom", + "feedVersion": "1.0", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/atom/feed_item_author_email_-_atom03_feed_entry_author_email.json b/testdata/translator/atom/feed_item_author_email_-_atom03_feed_entry_author_email.json index 4a05b2bb..5eed1332 100644 --- a/testdata/translator/atom/feed_item_author_email_-_atom03_feed_entry_author_email.json +++ b/testdata/translator/atom/feed_item_author_email_-_atom03_feed_entry_author_email.json @@ -1,11 +1,16 @@ { - "items": [ + "feedType": "atom", + "feedVersion": "0.3", + "items": [ + { + "author": { + "email": "email@example.org" + }, + "authors": [ { - "author": { - "email": "email@example.org" - } + "email": "email@example.org" } - ], - "feedType": "atom", - "feedVersion": "0.3" -} + ] + } + ] +} \ No newline at end of file diff --git a/testdata/translator/atom/feed_item_author_email_-_atom10_feed_entry_author_email.json b/testdata/translator/atom/feed_item_author_email_-_atom10_feed_entry_author_email.json index 989df040..6e076d22 100644 --- a/testdata/translator/atom/feed_item_author_email_-_atom10_feed_entry_author_email.json +++ b/testdata/translator/atom/feed_item_author_email_-_atom10_feed_entry_author_email.json @@ -1,11 +1,16 @@ { - "items": [ + "feedType": "atom", + "feedVersion": "1.0", + "items": [ + { + "author": { + "email": "email@example.org" + }, + "authors": [ { - "author": { - "email": "email@example.org" - } + "email": "email@example.org" } - ], - "feedType": "atom", - "feedVersion": "1.0" -} + ] + } + ] +} \ No newline at end of file diff --git a/testdata/translator/atom/feed_item_author_name_-_atom03_feed_entry_author_name.json b/testdata/translator/atom/feed_item_author_name_-_atom03_feed_entry_author_name.json index 33919525..a9bdd57f 100644 --- a/testdata/translator/atom/feed_item_author_name_-_atom03_feed_entry_author_name.json +++ b/testdata/translator/atom/feed_item_author_name_-_atom03_feed_entry_author_name.json @@ -1,11 +1,16 @@ { - "items": [ + "feedType": "atom", + "feedVersion": "0.3", + "items": [ + { + "author": { + "name": "Author Name" + }, + "authors": [ { - "author": { - "name": "Author Name" - } + "name": "Author Name" } - ], - "feedType": "atom", - "feedVersion": "0.3" -} + ] + } + ] +} \ No newline at end of file diff --git a/testdata/translator/atom/feed_item_author_name_-_atom10_feed_entry_author_name.json b/testdata/translator/atom/feed_item_author_name_-_atom10_feed_entry_author_name.json index 958c5200..10e74356 100644 --- a/testdata/translator/atom/feed_item_author_name_-_atom10_feed_entry_author_name.json +++ b/testdata/translator/atom/feed_item_author_name_-_atom10_feed_entry_author_name.json @@ -1,11 +1,16 @@ { - "items": [ + "feedType": "atom", + "feedVersion": "1.0", + "items": [ + { + "author": { + "name": "Author Name" + }, + "authors": [ { - "author": { - "name": "Author Name" - } + "name": "Author Name" } - ], - "feedType": "atom", - "feedVersion": "1.0" -} + ] + } + ] +} \ No newline at end of file diff --git a/testdata/translator/json/json10_full.json b/testdata/translator/json/json10_full.json new file mode 100644 index 00000000..5fc92df0 --- /dev/null +++ b/testdata/translator/json/json10_full.json @@ -0,0 +1,47 @@ +{ + "version": "1.0", + "title": "title", + "author": { + "avatar": "https://sample-feed-author.com/me.png", + "name": "author_name", + "url": "https://sample-feed-author.com" + }, + "description": "description", + "expired": false, + "favicon": "https://sample-json-feed.com/favicon.png", + "feed_url": "https://sample-json-feed.com/feed.json", + "home_page_url": "https://sample-json-feed.com", + "icon": "https://sample-json-feed.com/icon.png", + "next_url": "https://sample-json-feed.com/feed.json?next=500", + "user_comment": "user_comment", + "items": [ + { + "attachments": [ + { + "duration_in_seconds": 100, + "mime_type": "audio/mpeg", + "size_in_bytes": 100, + "title": "title", + "url": "https://sample-json-feed.com/attachment" + } + ], + "author": { + "avatar": "https://sample-feed-author.com/me.png", + "name": "author_name", + "url": "https://sample-feed-author.com" + }, + "banner_image": "https://sample-json-feed.com/banner_image.png", + "content_html": "

content_html

", + "content_text": "content_text", + "date_modified": "2019-10-12T07:20:50.52Z", + "date_published": "2019-10-12T07:20:50.52Z", + "external_url": "https://sample-json-feed.com/external", + "id": "id", + "image": "https://sample-json-feed.com/image.png", + "summary": "summary", + "tags": ["tag1", "tag2"], + "title": "title", + "url": "https://sample-json-feed.com/id" + } + ] +} diff --git a/testdata/translator/json/json10_full_expected.json b/testdata/translator/json/json10_full_expected.json new file mode 100644 index 00000000..5f889e33 --- /dev/null +++ b/testdata/translator/json/json10_full_expected.json @@ -0,0 +1,63 @@ +{ + "feedVersion": "1.0", + "feedType": "json", + "feedLink": "https://sample-json-feed.com/feed.json", + "title": "title", + "author": { + "avatar": "https://sample-feed-author.com/me.png", + "name": "author_name", + "url": "https://sample-feed-author.com" + }, + "authors": [ + { + "avatar": "https://sample-feed-author.com/me.png", + "name": "author_name", + "url": "https://sample-feed-author.com" + } + ], + "description": "description", + "link": "https://sample-json-feed.com", + "image": { + "url": "https://sample-json-feed.com/icon.png" + }, + "updated": "2019-10-12T07:20:50.52Z", + "updatedParsed": "2019-10-12T07:20:50.52Z", + "published": "2019-10-12T07:20:50.52Z", + "publishedParsed": "2019-10-12T07:20:50.52Z", + "items": [ + { + "guid": "id", + "title": "title", + "link": "https://sample-json-feed.com/id", + "content": "

content_html

", + "updated": "2019-10-12T07:20:50.52Z", + "updatedParsed": "2019-10-12T07:20:50.52Z", + "published": "2019-10-12T07:20:50.52Z", + "publishedParsed": "2019-10-12T07:20:50.52Z", + "description": "summary", + "categories": ["tag1", "tag2"], + "enclosures": [ + { + "length": "100", + "type": "audio/mpeg", + "url": "https://sample-json-feed.com/attachment" + } + ], + "author": { + "avatar": "https://sample-feed-author.com/me.png", + "name": "author_name", + "url": "https://sample-feed-author.com" + }, + "authors": [ + { + "avatar": "https://sample-feed-author.com/me.png", + "name": "author_name", + "url": "https://sample-feed-author.com" + } + ], + "image": { + "url": "https://sample-json-feed.com/image.png" + } + } + ] +} diff --git a/testdata/translator/json/sample2.json b/testdata/translator/json/json10_simple.json similarity index 100% rename from testdata/translator/json/sample2.json rename to testdata/translator/json/json10_simple.json diff --git a/testdata/translator/json/json10_simple_expected.json b/testdata/translator/json/json10_simple_expected.json new file mode 100644 index 00000000..8555829f --- /dev/null +++ b/testdata/translator/json/json10_simple_expected.json @@ -0,0 +1,12 @@ +{ + "feedVersion": "1.0", + "feedType": "json", + "items": [ + { + "content": "content_text", + "image": { + "url": "https://sample-json-feed.com/banner_image.png" + } + } + ] +} diff --git a/testdata/translator/json/json11_full.json b/testdata/translator/json/json11_full.json new file mode 100644 index 00000000..b441af54 --- /dev/null +++ b/testdata/translator/json/json11_full.json @@ -0,0 +1,52 @@ +{ + "version": "1.1", + "title": "title", + "user_comment": "user_comment", + "authors": [ + { + "avatar": "https://sample-feed-author.com/me.png", + "name": "author_name", + "url": "https://sample-feed-author.com" + } + ], + "next_url": "https://sample-json-feed.com/feed.json?next=500", + "description": "description", + "expired": false, + "favicon": "https://sample-json-feed.com/favicon.png", + "feed_url": "https://sample-json-feed.com/feed.json", + "home_page_url": "https://sample-json-feed.com", + "icon": "https://sample-json-feed.com/icon.png", + "language": "en", + "items": [ + { + "attachments": [ + { + "duration_in_seconds": 100, + "mime_type": "audio/mpeg", + "size_in_bytes": 100, + "title": "title", + "url": "https://sample-json-feed.com/attachment" + } + ], + "authors": [ + { + "avatar": "https://sample-feed-author.com/me.png", + "name": "author_name", + "url": "https://sample-feed-author.com" + } + ], + "banner_image": "https://sample-json-feed.com/banner_image.png", + "content_html": "

content_html

", + "content_text": "content_text", + "date_modified": "2019-10-12T07:20:50.52Z", + "date_published": "2019-10-12T07:20:50.52Z", + "external_url": "https://sample-json-feed.com/external", + "id": "id", + "image": "https://sample-json-feed.com/image.png", + "summary": "summary", + "tags": ["tag1", "tag2"], + "title": "title", + "url": "https://sample-json-feed.com/id" + } + ] +} diff --git a/testdata/translator/json/json11_full_expected.json b/testdata/translator/json/json11_full_expected.json new file mode 100644 index 00000000..75adbb26 --- /dev/null +++ b/testdata/translator/json/json11_full_expected.json @@ -0,0 +1,54 @@ +{ + "feedVersion": "1.1", + "feedType": "json", + "feedLink": "https://sample-json-feed.com/feed.json", + "title": "title", + "language": "en", + "authors": [ + { + "avatar": "https://sample-feed-author.com/me.png", + "name": "author_name", + "url": "https://sample-feed-author.com" + } + ], + "description": "description", + "link": "https://sample-json-feed.com", + "image": { + "url": "https://sample-json-feed.com/icon.png" + }, + "updated": "2019-10-12T07:20:50.52Z", + "updatedParsed": "2019-10-12T07:20:50.52Z", + "published": "2019-10-12T07:20:50.52Z", + "publishedParsed": "2019-10-12T07:20:50.52Z", + "items": [ + { + "guid": "id", + "title": "title", + "link": "https://sample-json-feed.com/id", + "content": "

content_html

", + "updated": "2019-10-12T07:20:50.52Z", + "updatedParsed": "2019-10-12T07:20:50.52Z", + "published": "2019-10-12T07:20:50.52Z", + "publishedParsed": "2019-10-12T07:20:50.52Z", + "description": "summary", + "categories": ["tag1", "tag2"], + "enclosures": [ + { + "length": "100", + "type": "audio/mpeg", + "url": "https://sample-json-feed.com/attachment" + } + ], + "authors": [ + { + "avatar": "https://sample-feed-author.com/me.png", + "name": "author_name", + "url": "https://sample-feed-author.com" + } + ], + "image": { + "url": "https://sample-json-feed.com/image.png" + } + } + ] +} diff --git a/testdata/translator/rss/feed_author_-_rss_channel_managingEditor_email.json b/testdata/translator/rss/feed_author_-_rss_channel_managingEditor_email.json index 0839fe3b..efa8d855 100644 --- a/testdata/translator/rss/feed_author_-_rss_channel_managingEditor_email.json +++ b/testdata/translator/rss/feed_author_-_rss_channel_managingEditor_email.json @@ -1,8 +1,13 @@ { - "author": { - "email": "email@example.org" - }, - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "author": { + "email": "email@example.org" + }, + "authors": [ + { + "email": "email@example.org" + } + ], + "feedType": "rss", + "feedVersion": "2.0", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_author_-_rss_channel_managingEditor_emailAndName.json b/testdata/translator/rss/feed_author_-_rss_channel_managingEditor_emailAndName.json index 4186aa69..33ba3758 100644 --- a/testdata/translator/rss/feed_author_-_rss_channel_managingEditor_emailAndName.json +++ b/testdata/translator/rss/feed_author_-_rss_channel_managingEditor_emailAndName.json @@ -1,9 +1,15 @@ { - "author": { - "name": "Author Name", - "email": "email@example.org" - }, - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "author": { + "email": "email@example.org", + "name": "Author Name" + }, + "authors": [ + { + "email": "email@example.org", + "name": "Author Name" + } + ], + "feedType": "rss", + "feedVersion": "2.0", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_author_-_rss_channel_managingEditor_name.json b/testdata/translator/rss/feed_author_-_rss_channel_managingEditor_name.json index 58ac0124..a64d63a2 100644 --- a/testdata/translator/rss/feed_author_-_rss_channel_managingEditor_name.json +++ b/testdata/translator/rss/feed_author_-_rss_channel_managingEditor_name.json @@ -1,8 +1,13 @@ { - "author": { - "name": "Feed Editor" - }, - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "author": { + "name": "Feed Editor" + }, + "authors": [ + { + "name": "Feed Editor" + } + ], + "feedType": "rss", + "feedVersion": "2.0", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_author_-_rss_channel_managingEditor_nameAndEmail.json b/testdata/translator/rss/feed_author_-_rss_channel_managingEditor_nameAndEmail.json index b83da699..09a26f81 100644 --- a/testdata/translator/rss/feed_author_-_rss_channel_managingEditor_nameAndEmail.json +++ b/testdata/translator/rss/feed_author_-_rss_channel_managingEditor_nameAndEmail.json @@ -1,9 +1,15 @@ { - "author": { - "name": "Feed Editor", - "email": "email@example.org" - }, - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "author": { + "email": "email@example.org", + "name": "Feed Editor" + }, + "authors": [ + { + "email": "email@example.org", + "name": "Feed Editor" + } + ], + "feedType": "rss", + "feedVersion": "2.0", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_author_-_rss_channel_webMaster_email.json b/testdata/translator/rss/feed_author_-_rss_channel_webMaster_email.json index 0839fe3b..efa8d855 100644 --- a/testdata/translator/rss/feed_author_-_rss_channel_webMaster_email.json +++ b/testdata/translator/rss/feed_author_-_rss_channel_webMaster_email.json @@ -1,8 +1,13 @@ { - "author": { - "email": "email@example.org" - }, - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "author": { + "email": "email@example.org" + }, + "authors": [ + { + "email": "email@example.org" + } + ], + "feedType": "rss", + "feedVersion": "2.0", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_author_-_rss_channel_webMaster_emailAndName.json b/testdata/translator/rss/feed_author_-_rss_channel_webMaster_emailAndName.json index ae8c3b40..007b3bad 100644 --- a/testdata/translator/rss/feed_author_-_rss_channel_webMaster_emailAndName.json +++ b/testdata/translator/rss/feed_author_-_rss_channel_webMaster_emailAndName.json @@ -1,9 +1,15 @@ { - "author": { - "name": "Feed WebMaster", - "email": "email@example.org" - }, - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "author": { + "email": "email@example.org", + "name": "Feed WebMaster" + }, + "authors": [ + { + "email": "email@example.org", + "name": "Feed WebMaster" + } + ], + "feedType": "rss", + "feedVersion": "2.0", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_author_-_rss_channel_webMaster_name.json b/testdata/translator/rss/feed_author_-_rss_channel_webMaster_name.json index d5ebf0cf..61319298 100644 --- a/testdata/translator/rss/feed_author_-_rss_channel_webMaster_name.json +++ b/testdata/translator/rss/feed_author_-_rss_channel_webMaster_name.json @@ -1,8 +1,13 @@ { - "author": { - "name": "Feed WebMaster" - }, - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "author": { + "name": "Feed WebMaster" + }, + "authors": [ + { + "name": "Feed WebMaster" + } + ], + "feedType": "rss", + "feedVersion": "2.0", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_author_-_rss_channel_webMaster_nameAndEmail.json b/testdata/translator/rss/feed_author_-_rss_channel_webMaster_nameAndEmail.json index ae8c3b40..007b3bad 100644 --- a/testdata/translator/rss/feed_author_-_rss_channel_webMaster_nameAndEmail.json +++ b/testdata/translator/rss/feed_author_-_rss_channel_webMaster_nameAndEmail.json @@ -1,9 +1,15 @@ { - "author": { - "name": "Feed WebMaster", - "email": "email@example.org" - }, - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "author": { + "email": "email@example.org", + "name": "Feed WebMaster" + }, + "authors": [ + { + "email": "email@example.org", + "name": "Feed WebMaster" + } + ], + "feedType": "rss", + "feedVersion": "2.0", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_category_-_rss_channel_category.json b/testdata/translator/rss/feed_category_-_rss_channel_category.json index 4f467171..2c8b9ef4 100644 --- a/testdata/translator/rss/feed_category_-_rss_channel_category.json +++ b/testdata/translator/rss/feed_category_-_rss_channel_category.json @@ -1,9 +1,9 @@ { - "categories": [ - "Feed Category 1", - "Feed Category 2" - ], - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "categories": [ + "Feed Category 1", + "Feed Category 2" + ], + "feedType": "rss", + "feedVersion": "2.0", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_copyright_-_rss_channel_copyright.json b/testdata/translator/rss/feed_copyright_-_rss_channel_copyright.json index 8e8ea060..fff69b26 100644 --- a/testdata/translator/rss/feed_copyright_-_rss_channel_copyright.json +++ b/testdata/translator/rss/feed_copyright_-_rss_channel_copyright.json @@ -1,6 +1,6 @@ { - "copyright": "Feed Copyright", - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "copyright": "Feed Copyright", + "feedType": "rss", + "feedVersion": "2.0", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_description_-_rdf_channel_description.json b/testdata/translator/rss/feed_description_-_rdf_channel_description.json index 5d0e9d7b..819863c9 100644 --- a/testdata/translator/rss/feed_description_-_rdf_channel_description.json +++ b/testdata/translator/rss/feed_description_-_rdf_channel_description.json @@ -1,6 +1,6 @@ { - "description": "Feed Description", - "items": [], - "feedType": "rss", - "feedVersion": "1.0" -} + "description": "Feed Description", + "feedType": "rss", + "feedVersion": "1.0", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_description_-_rss_channel_description.json b/testdata/translator/rss/feed_description_-_rss_channel_description.json index 65ef2d65..8e85cbe8 100644 --- a/testdata/translator/rss/feed_description_-_rss_channel_description.json +++ b/testdata/translator/rss/feed_description_-_rss_channel_description.json @@ -1,6 +1,6 @@ { - "description": "Feed Description", - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "description": "Feed Description", + "feedType": "rss", + "feedVersion": "2.0", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_generator_-_rss_channel_generator.json b/testdata/translator/rss/feed_generator_-_rss_channel_generator.json index 9ab2ce83..cbe5cc1c 100644 --- a/testdata/translator/rss/feed_generator_-_rss_channel_generator.json +++ b/testdata/translator/rss/feed_generator_-_rss_channel_generator.json @@ -1,6 +1,6 @@ { - "generator": "Feed Generator", - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "feedType": "rss", + "feedVersion": "2.0", + "generator": "Feed Generator", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_image_-_rdf_image.json b/testdata/translator/rss/feed_image_-_rdf_image.json index 2316ca89..4d00e8e5 100644 --- a/testdata/translator/rss/feed_image_-_rdf_image.json +++ b/testdata/translator/rss/feed_image_-_rdf_image.json @@ -1,9 +1,9 @@ { + "feedType": "rss", + "feedVersion": "1.0", "image": { - "url": "http://xml.com/universal/images/xml_tiny.gif", - "title": "XML.com" + "title": "XML.com", + "url": "http://xml.com/universal/images/xml_tiny.gif" }, - "items": [], - "feedType": "rss", - "feedVersion": "1.0" -} + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_image_-_rss_channel_image.json b/testdata/translator/rss/feed_image_-_rss_channel_image.json index 62386a39..bb1561fc 100644 --- a/testdata/translator/rss/feed_image_-_rss_channel_image.json +++ b/testdata/translator/rss/feed_image_-_rss_channel_image.json @@ -1,9 +1,9 @@ { - "image": { - "url": "http://example.org/url", - "title": "Sample image" - }, - "items": [], - "feedType": "rss", - "feedVersion": "0.91" -} + "feedType": "rss", + "feedVersion": "0.91", + "image": { + "title": "Sample image", + "url": "http://example.org/url" + }, + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_item_author_-_rss_channel_item_author_email.json b/testdata/translator/rss/feed_item_author_-_rss_channel_item_author_email.json index 44b21bff..53d3865a 100644 --- a/testdata/translator/rss/feed_item_author_-_rss_channel_item_author_email.json +++ b/testdata/translator/rss/feed_item_author_-_rss_channel_item_author_email.json @@ -1,11 +1,16 @@ { - "items": [ + "feedType": "rss", + "feedVersion": "2.0", + "items": [ + { + "author": { + "email": "email@example.org" + }, + "authors": [ { - "author": { - "email": "email@example.org" - } + "email": "email@example.org" } - ], - "feedType": "rss", - "feedVersion": "2.0" -} + ] + } + ] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_item_author_-_rss_channel_item_author_emailAndName.json b/testdata/translator/rss/feed_item_author_-_rss_channel_item_author_emailAndName.json index 9ee44c16..1c399c6c 100644 --- a/testdata/translator/rss/feed_item_author_-_rss_channel_item_author_emailAndName.json +++ b/testdata/translator/rss/feed_item_author_-_rss_channel_item_author_emailAndName.json @@ -1,12 +1,18 @@ { - "items": [ + "feedType": "rss", + "feedVersion": "2.0", + "items": [ + { + "author": { + "email": "email@example.org", + "name": "Author Name" + }, + "authors": [ { - "author": { - "name": "Author Name", - "email": "email@example.org" - } + "email": "email@example.org", + "name": "Author Name" } - ], - "feedType": "rss", - "feedVersion": "2.0" -} + ] + } + ] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_item_author_-_rss_channel_item_author_name.json b/testdata/translator/rss/feed_item_author_-_rss_channel_item_author_name.json index 8acb3ed8..75957973 100644 --- a/testdata/translator/rss/feed_item_author_-_rss_channel_item_author_name.json +++ b/testdata/translator/rss/feed_item_author_-_rss_channel_item_author_name.json @@ -1,11 +1,16 @@ { - "items": [ + "feedType": "rss", + "feedVersion": "2.0", + "items": [ + { + "author": { + "name": "Author Name" + }, + "authors": [ { - "author": { - "name": "Author Name" - } + "name": "Author Name" } - ], - "feedType": "rss", - "feedVersion": "2.0" -} + ] + } + ] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_item_author_-_rss_channel_item_author_nameAndEmail.json b/testdata/translator/rss/feed_item_author_-_rss_channel_item_author_nameAndEmail.json index 9ee44c16..1c399c6c 100644 --- a/testdata/translator/rss/feed_item_author_-_rss_channel_item_author_nameAndEmail.json +++ b/testdata/translator/rss/feed_item_author_-_rss_channel_item_author_nameAndEmail.json @@ -1,12 +1,18 @@ { - "items": [ + "feedType": "rss", + "feedVersion": "2.0", + "items": [ + { + "author": { + "email": "email@example.org", + "name": "Author Name" + }, + "authors": [ { - "author": { - "name": "Author Name", - "email": "email@example.org" - } + "email": "email@example.org", + "name": "Author Name" } - ], - "feedType": "rss", - "feedVersion": "2.0" -} + ] + } + ] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_item_category_-_rss_channel_item_category.json b/testdata/translator/rss/feed_item_category_-_rss_channel_item_category.json index f7bd46ee..726cb880 100644 --- a/testdata/translator/rss/feed_item_category_-_rss_channel_item_category.json +++ b/testdata/translator/rss/feed_item_category_-_rss_channel_item_category.json @@ -1,12 +1,12 @@ { - "items": [ - { - "categories": [ - "Item Category 1", - "Item Category 2" - ] - } - ], - "feedType": "rss", - "feedVersion": "2.0" -} + "feedType": "rss", + "feedVersion": "2.0", + "items": [ + { + "categories": [ + "Item Category 1", + "Item Category 2" + ] + } + ] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_item_description_-_rdf_item_description.json b/testdata/translator/rss/feed_item_description_-_rdf_item_description.json index 2306236a..a58bea63 100644 --- a/testdata/translator/rss/feed_item_description_-_rdf_item_description.json +++ b/testdata/translator/rss/feed_item_description_-_rdf_item_description.json @@ -1,9 +1,9 @@ { - "items": [ - { - "description": "Item Description" - } - ], - "feedType": "rss", - "feedVersion": "1.0" -} + "feedType": "rss", + "feedVersion": "1.0", + "items": [ + { + "description": "Item Description" + } + ] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_item_description_-_rss_channel_item_description.json b/testdata/translator/rss/feed_item_description_-_rss_channel_item_description.json index 820a5026..00ca1dc8 100644 --- a/testdata/translator/rss/feed_item_description_-_rss_channel_item_description.json +++ b/testdata/translator/rss/feed_item_description_-_rss_channel_item_description.json @@ -1,9 +1,9 @@ { - "items": [ - { - "description": "Item Description" - } - ], - "feedType": "rss", - "feedVersion": "2.0" -} + "feedType": "rss", + "feedVersion": "2.0", + "items": [ + { + "description": "Item Description" + } + ] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_item_enclosure_-_rss_channel_item_enclosure.json b/testdata/translator/rss/feed_item_enclosure_-_rss_channel_item_enclosure.json index 58650141..bfb0af36 100644 --- a/testdata/translator/rss/feed_item_enclosure_-_rss_channel_item_enclosure.json +++ b/testdata/translator/rss/feed_item_enclosure_-_rss_channel_item_enclosure.json @@ -1,15 +1,15 @@ { - "items": [ + "feedType": "rss", + "feedVersion": "2.0", + "items": [ + { + "enclosures": [ { - "enclosures": [ - { - "url": "http://example.org/podcast.mp3", - "length": "123456", - "type": "audio/mpeg" - } - ] + "length": "123456", + "type": "audio/mpeg", + "url": "http://example.org/podcast.mp3" } - ], - "feedType": "rss", - "feedVersion": "2.0" -} + ] + } + ] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_item_guid_-_rss_channel_item_guid.json b/testdata/translator/rss/feed_item_guid_-_rss_channel_item_guid.json index 4a3895eb..20ca1e85 100644 --- a/testdata/translator/rss/feed_item_guid_-_rss_channel_item_guid.json +++ b/testdata/translator/rss/feed_item_guid_-_rss_channel_item_guid.json @@ -1,9 +1,9 @@ { - "items": [ - { - "guid": "abc123" - } - ], - "feedType": "rss", - "feedVersion": "2.0" -} + "feedType": "rss", + "feedVersion": "2.0", + "items": [ + { + "guid": "abc123" + } + ] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_item_link_-_rdf_item_link.json b/testdata/translator/rss/feed_item_link_-_rdf_item_link.json index 4bfe9295..ee5e963e 100644 --- a/testdata/translator/rss/feed_item_link_-_rdf_item_link.json +++ b/testdata/translator/rss/feed_item_link_-_rdf_item_link.json @@ -1,9 +1,9 @@ { - "items": [ - { - "link": "http://example.org" - } - ], - "feedType": "rss", - "feedVersion": "1.0" -} + "feedType": "rss", + "feedVersion": "1.0", + "items": [ + { + "link": "http://example.org" + } + ] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_item_link_-_rss_channel_item_link.json b/testdata/translator/rss/feed_item_link_-_rss_channel_item_link.json index 05ea65b9..2ea96d08 100644 --- a/testdata/translator/rss/feed_item_link_-_rss_channel_item_link.json +++ b/testdata/translator/rss/feed_item_link_-_rss_channel_item_link.json @@ -1,9 +1,9 @@ { - "items": [ - { - "link": "http://example.org" - } - ], - "feedType": "rss", - "feedVersion": "2.0" -} + "feedType": "rss", + "feedVersion": "2.0", + "items": [ + { + "link": "http://example.org" + } + ] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_item_published_-_rss_channel_item_pubDate.json b/testdata/translator/rss/feed_item_published_-_rss_channel_item_pubDate.json index 65ba5614..bc011294 100644 --- a/testdata/translator/rss/feed_item_published_-_rss_channel_item_pubDate.json +++ b/testdata/translator/rss/feed_item_published_-_rss_channel_item_pubDate.json @@ -1,10 +1,10 @@ { - "items": [ - { - "published": "Thu, 01 Jan 2004 19:48:21 GMT", - "publishedParsed": "2004-01-01T19:48:21Z" - } - ], - "feedType": "rss", - "feedVersion": "2.0" -} + "feedType": "rss", + "feedVersion": "2.0", + "items": [ + { + "published": "Thu, 01 Jan 2004 19:48:21 GMT", + "publishedParsed": "2004-01-01T19:48:21Z" + } + ] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_item_title_-_rdf_item_title.json b/testdata/translator/rss/feed_item_title_-_rdf_item_title.json index d4b83178..10594024 100644 --- a/testdata/translator/rss/feed_item_title_-_rdf_item_title.json +++ b/testdata/translator/rss/feed_item_title_-_rdf_item_title.json @@ -1,9 +1,9 @@ { - "items": [ - { - "title": "Item Title" - } - ], - "feedType": "rss", - "feedVersion": "1.0" -} + "feedType": "rss", + "feedVersion": "1.0", + "items": [ + { + "title": "Item Title" + } + ] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_item_title_-_rss_channel_item_title.json b/testdata/translator/rss/feed_item_title_-_rss_channel_item_title.json index 71cd0b0a..ba1e6dfd 100644 --- a/testdata/translator/rss/feed_item_title_-_rss_channel_item_title.json +++ b/testdata/translator/rss/feed_item_title_-_rss_channel_item_title.json @@ -1,9 +1,9 @@ { - "items": [ - { - "title": "Item 1 title" - } - ], - "feedType": "rss", - "feedVersion": "2.0" -} + "feedType": "rss", + "feedVersion": "2.0", + "items": [ + { + "title": "Item 1 title" + } + ] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_language_-_rss_channel_language.json b/testdata/translator/rss/feed_language_-_rss_channel_language.json index 490f1034..847002cb 100644 --- a/testdata/translator/rss/feed_language_-_rss_channel_language.json +++ b/testdata/translator/rss/feed_language_-_rss_channel_language.json @@ -1,6 +1,6 @@ { - "language": "en-us", - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "feedType": "rss", + "feedVersion": "2.0", + "items": [], + "language": "en-us" +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_link_-_rdf_channel_link.json b/testdata/translator/rss/feed_link_-_rdf_channel_link.json index 5c382b2d..eb7c4e47 100644 --- a/testdata/translator/rss/feed_link_-_rdf_channel_link.json +++ b/testdata/translator/rss/feed_link_-_rdf_channel_link.json @@ -1,6 +1,6 @@ { - "link": "http://example.org", - "items": [], - "feedType": "rss", - "feedVersion": "1.0" -} + "feedType": "rss", + "feedVersion": "1.0", + "items": [], + "link": "http://example.org" +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_link_-_rss_channel_atom_link.json b/testdata/translator/rss/feed_link_-_rss_channel_atom_link.json index 904d3ad9..8c4f7fb5 100644 --- a/testdata/translator/rss/feed_link_-_rss_channel_atom_link.json +++ b/testdata/translator/rss/feed_link_-_rss_channel_atom_link.json @@ -1,22 +1,22 @@ -{ - "items": [], - "feedType": "rss", - "feedVersion": "2.0", - "feedLink": "http://example.org", +{ "extensions": { - "atom": { - "link": [ - { - "name": "link", - "value": "", - "attrs": { - "href": "http://example.org", - "rel": "self", - "type": "application/rss+xml" - }, - "children": {} - } - ] + "atom": { + "link": [ + { + "attrs": { + "href": "http://example.org", + "rel": "self", + "type": "application/rss+xml" + }, + "children": {}, + "name": "link", + "value": "" } + ] } -} + }, + "feedLink": "http://example.org", + "feedType": "rss", + "feedVersion": "2.0", + "items": [] +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_link_-_rss_channel_link.json b/testdata/translator/rss/feed_link_-_rss_channel_link.json index 4f5bacce..a6d89343 100644 --- a/testdata/translator/rss/feed_link_-_rss_channel_link.json +++ b/testdata/translator/rss/feed_link_-_rss_channel_link.json @@ -1,6 +1,6 @@ { - "link": "http://example.org", - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "feedType": "rss", + "feedVersion": "2.0", + "items": [], + "link": "http://example.org" +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_published_-_rss_channel_pubDate.json b/testdata/translator/rss/feed_published_-_rss_channel_pubDate.json index 903b2e96..548da204 100644 --- a/testdata/translator/rss/feed_published_-_rss_channel_pubDate.json +++ b/testdata/translator/rss/feed_published_-_rss_channel_pubDate.json @@ -1,7 +1,7 @@ { - "published": "Thu, 01 Jan 2004 19:48:21 GMT", - "publishedParsed": "2004-01-01T19:48:21Z", - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "feedType": "rss", + "feedVersion": "2.0", + "items": [], + "published": "Thu, 01 Jan 2004 19:48:21 GMT", + "publishedParsed": "2004-01-01T19:48:21Z" +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_title_-_rdf_channel_title.json b/testdata/translator/rss/feed_title_-_rdf_channel_title.json index 9fab9973..655a3430 100644 --- a/testdata/translator/rss/feed_title_-_rdf_channel_title.json +++ b/testdata/translator/rss/feed_title_-_rdf_channel_title.json @@ -1,6 +1,6 @@ { - "title": "Feed Title", - "items": [], - "feedType": "rss", - "feedVersion": "1.0" -} + "feedType": "rss", + "feedVersion": "1.0", + "items": [], + "title": "Feed Title" +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_title_-_rss_channel_title.json b/testdata/translator/rss/feed_title_-_rss_channel_title.json index 7348f26a..27529800 100644 --- a/testdata/translator/rss/feed_title_-_rss_channel_title.json +++ b/testdata/translator/rss/feed_title_-_rss_channel_title.json @@ -1,6 +1,6 @@ { - "title": "Feed Title", - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "feedType": "rss", + "feedVersion": "2.0", + "items": [], + "title": "Feed Title" +} \ No newline at end of file diff --git a/testdata/translator/rss/feed_updated_-_rss_channel_lastBuildDate.json b/testdata/translator/rss/feed_updated_-_rss_channel_lastBuildDate.json index 900052e1..fc92ff3f 100644 --- a/testdata/translator/rss/feed_updated_-_rss_channel_lastBuildDate.json +++ b/testdata/translator/rss/feed_updated_-_rss_channel_lastBuildDate.json @@ -1,7 +1,7 @@ { - "updated": "Sat, 07 Sep 2002 00:00:01 GMT", - "updatedParsed": "2002-09-07T00:00:01Z", - "items": [], - "feedType": "rss", - "feedVersion": "2.0" -} + "feedType": "rss", + "feedVersion": "2.0", + "items": [], + "updated": "Sat, 07 Sep 2002 00:00:01 GMT", + "updatedParsed": "2002-09-07T00:00:01Z" +} \ No newline at end of file diff --git a/translator.go b/translator.go index 72942a22..3b80790c 100644 --- a/translator.go +++ b/translator.go @@ -44,6 +44,7 @@ func (t *DefaultRSSTranslator) Translate(feed interface{}) (*Feed, error) { result.Published = t.translateFeedPublished(rss) result.PublishedParsed = t.translateFeedPublishedParsed(rss) result.Author = t.translateFeedAuthor(rss) + result.Authors = t.translateFeedAuthors(rss) result.Language = t.translateFeedLanguage(rss) result.Image = t.translateFeedImage(rss) result.Copyright = t.translateFeedCopyright(rss) @@ -67,6 +68,7 @@ func (t *DefaultRSSTranslator) translateFeedItem(rssItem *rss.Item) (item *Item) item.Published = t.translateItemPublished(rssItem) item.PublishedParsed = t.translateItemPublishedParsed(rssItem) item.Author = t.translateItemAuthor(rssItem) + item.Authors = t.translateItemAuthors(rssItem) item.GUID = t.translateItemGUID(rssItem) item.Image = t.translateItemImage(rssItem) item.Categories = t.translateItemCategories(rssItem) @@ -175,6 +177,13 @@ func (t *DefaultRSSTranslator) translateFeedAuthor(rss *rss.Feed) (author *Perso return } +func (t *DefaultRSSTranslator) translateFeedAuthors(rss *rss.Feed) (authors []*Person) { + if author := t.translateFeedAuthor(rss); author != nil { + authors = []*Person{author} + } + return +} + func (t *DefaultRSSTranslator) translateFeedLanguage(rss *rss.Feed) (language string) { if rss.Language != "" { language = rss.Language @@ -347,6 +356,14 @@ func (t *DefaultRSSTranslator) translateItemAuthor(rssItem *rss.Item) (author *P return } +func (t *DefaultRSSTranslator) translateItemAuthors(rssItem *rss.Item) (authors []*Person) { + + if author := t.translateItemAuthor(rssItem); author != nil { + authors = []*Person{author} + } + return +} + func (t *DefaultRSSTranslator) translateItemGUID(rssItem *rss.Item) (guid string) { if rssItem.GUID != nil { guid = rssItem.GUID.Value @@ -452,6 +469,7 @@ func (t *DefaultAtomTranslator) Translate(feed interface{}) (*Feed, error) { result.Updated = t.translateFeedUpdated(atom) result.UpdatedParsed = t.translateFeedUpdatedParsed(atom) result.Author = t.translateFeedAuthor(atom) + result.Authors = t.translateFeedAuthors(atom) result.Language = t.translateFeedLanguage(atom) result.Image = t.translateFeedImage(atom) result.Copyright = t.translateFeedCopyright(atom) @@ -475,6 +493,7 @@ func (t *DefaultAtomTranslator) translateFeedItem(entry *atom.Entry) (item *Item item.Published = t.translateItemPublished(entry) item.PublishedParsed = t.translateItemPublishedParsed(entry) item.Author = t.translateItemAuthor(entry) + item.Authors = t.translateItemAuthors(entry) item.GUID = t.translateItemGUID(entry) item.Image = t.translateItemImage(entry) item.Categories = t.translateItemCategories(entry) @@ -526,6 +545,22 @@ func (t *DefaultAtomTranslator) translateFeedAuthor(atom *atom.Feed) (author *Pe return } +func (t *DefaultAtomTranslator) translateFeedAuthors(atom *atom.Feed) (authors []*Person) { + + if atom.Authors != nil { + authors = []*Person{} + + for _, a := range atom.Authors { + authors = append(authors, &Person{ + Name: a.Name, + Email: a.Email, + }) + } + } + + return +} + func (t *DefaultAtomTranslator) translateFeedLanguage(atom *atom.Feed) (language string) { return atom.Language } @@ -626,6 +661,19 @@ func (t *DefaultAtomTranslator) translateItemAuthor(entry *atom.Entry) (author * return } +func (t *DefaultAtomTranslator) translateItemAuthors(entry *atom.Entry) (authors []*Person) { + if entry.Authors != nil { + authors = []*Person{} + for _, a := range entry.Authors { + authors = append(authors, &Person{ + Name: a.Name, + Email: a.Email, + }) + } + } + return +} + func (t *DefaultAtomTranslator) translateItemGUID(entry *atom.Entry) (guid string) { return entry.ID } @@ -710,6 +758,8 @@ func (t *DefaultJSONTranslator) Translate(feed interface{}) (*Feed, error) { result.Description = t.translateFeedDescription(json) result.Image = t.translateFeedImage(json) result.Author = t.translateFeedAuthor(json) + result.Authors = t.translateFeedAuthors(json) + result.Language = t.translateFeedLanguage(json) result.Items = t.translateFeedItems(json) result.Updated = t.translateFeedUpdated(json) result.UpdatedParsed = t.translateFeedUpdatedParsed(json) @@ -738,6 +788,7 @@ func (t *DefaultJSONTranslator) translateFeedItem(jsonItem *json.Item) (item *It item.Updated = t.translateItemUpdated(jsonItem) item.UpdatedParsed = t.translateItemUpdatedParsed(jsonItem) item.Author = t.translateItemAuthor(jsonItem) + item.Authors = t.translateItemAuthors(jsonItem) item.Categories = t.translateItemCategories(jsonItem) item.Enclosures = t.translateItemEnclosures(jsonItem) // TODO ExternalURL is missing in global Feed @@ -816,6 +867,31 @@ func (t *DefaultJSONTranslator) translateFeedAuthor(json *json.Feed) (author *Pe return } +func (t *DefaultJSONTranslator) translateFeedAuthors(json *json.Feed) (authors []*Person) { + + if json.Authors != nil { + authors = []*Person{} + for _, a := range json.Authors { + name, address := shared.ParseNameAddress(a.Name) + author := &Person{} + author.Name = name + author.Email = address + + authors = append(authors, author) + } + } else if author := t.translateFeedAuthor(json); author != nil { + authors = []*Person{author} + } + // Author.URL is missing in global feed + // Author.Avatar is missing in global feed + return +} + +func (t *DefaultJSONTranslator) translateFeedLanguage(json *json.Feed) (language string) { + language = json.Language + return +} + func (t *DefaultJSONTranslator) translateFeedImage(json *json.Feed) (image *Image) { // Using the Icon rather than the image // icon (optional, string) is the URL of an image for the feed suitable to be used in a timeline. It should be square and relatively large — such as 512 x 512 @@ -907,6 +983,26 @@ func (t *DefaultJSONTranslator) translateItemAuthor(jsonItem *json.Item) (author return } +func (t *DefaultJSONTranslator) translateItemAuthors(jsonItem *json.Item) (authors []*Person) { + + if jsonItem.Authors != nil { + authors = []*Person{} + for _, a := range jsonItem.Authors { + name, address := shared.ParseNameAddress(a.Name) + author := &Person{} + author.Name = name + author.Email = address + + authors = append(authors, author) + } + } else if author := t.translateItemAuthor(jsonItem); author != nil { + authors = []*Person{author} + } + // Author.URL is missing in global feed + // Author.Avatar is missing in global feed + return +} + func (t *DefaultJSONTranslator) translateItemGUID(jsonItem *json.Item) (guid string) { if jsonItem.ID != "" { guid = jsonItem.ID diff --git a/translator_test.go b/translator_test.go index 2cbbb8f5..bbd76d69 100644 --- a/translator_test.go +++ b/translator_test.go @@ -1,7 +1,6 @@ package gofeed_test import ( - "bytes" jsonEncoding "encoding/json" "fmt" "io/ioutil" @@ -9,11 +8,9 @@ import ( "path/filepath" "strings" "testing" - "time" "github.com/mmcdole/gofeed" "github.com/mmcdole/gofeed/atom" - ext "github.com/mmcdole/gofeed/extensions" "github.com/mmcdole/gofeed/json" "github.com/mmcdole/gofeed/rss" "github.com/stretchr/testify/assert" @@ -46,7 +43,7 @@ func TestDefaultRSSTranslator_Translate(t *testing.T) { expected := &gofeed.Feed{} jsonEncoding.Unmarshal(e, &expected) - if assert.Equal(t, actual, expected, "Feed file %s.xml did not match expected output %s.json", name, name) { + if assert.Equal(t, expected, actual, "Feed file %s.xml did not match expected output %s.json", name, name) { fmt.Printf("OK\n") } else { fmt.Printf("Failed\n") @@ -88,7 +85,7 @@ func TestDefaultAtomTranslator_Translate(t *testing.T) { expected := &gofeed.Feed{} jsonEncoding.Unmarshal(e, &expected) - if assert.Equal(t, actual, expected, "Feed file %s.xml did not match expected output %s.json", name, name) { + if assert.Equal(t, expected, actual, "Feed file %s.xml did not match expected output %s.json", name, name) { fmt.Printf("OK\n") } else { fmt.Printf("Failed\n") @@ -103,6 +100,47 @@ func TestDefaultAtomTranslator_Translate_WrongType(t *testing.T) { assert.NotNil(t, err) } +func TestDefaultJSONTranslator_Translate(t *testing.T) { + files, _ := filepath.Glob("testdata/translator/json/*.json") + for _, f := range files { + base := filepath.Base(f) + name := strings.TrimSuffix(base, filepath.Ext(base)) + + if strings.HasSuffix(name, "expected") { + continue + } + + fmt.Printf("Testing %s... ", name) + + // Get actual source feed + ff := fmt.Sprintf("testdata/translator/json/%s.json", name) + f, _ := os.Open(ff) + defer f.Close() + + // Parse actual feed + translator := &gofeed.DefaultJSONTranslator{} + fp := json.Parser{} + jsonFeed, _ := fp.Parse(f) + actual, _ := translator.Translate(jsonFeed) + + // Get json encoded expected feed result + ef := fmt.Sprintf("testdata/translator/json/%s_expected.json", name) + e, _ := ioutil.ReadFile(ef) + + // Unmarshal expected feed + expected := &gofeed.Feed{} + jsonEncoding.Unmarshal(e, &expected) + + if assert.Equal(t, expected, actual, "Feed file %s.json did not match expected output %s_expected.json", name, name) { + fmt.Printf("OK\n") + } else { + fmt.Printf("Failed\n") + } + } +} + +/* + func TestDefaultJSONTranslator_Translate(t *testing.T) { name := "sample" fmt.Printf("Testing %s... ", name) @@ -179,6 +217,7 @@ func TestDefaultJSONTranslator_Translate(t *testing.T) { assert.Equal(t, "https://sample-json-feed.com/banner_image.png", actual.Items[0].Image.URL) } +*/ func TestDefaultJSONTranslator_Translate_WrongType(t *testing.T) { translator := &gofeed.DefaultJSONTranslator{}