| @@ -0,0 +1,10 @@ | |||
| import 'package:webfeedclient/client.dart'; | |||
| void main() { | |||
| var client = new WebFeedClient(); | |||
| client.fetch("https://developer.apple.com/news/releases/rss/releases.rss").then((channel) { | |||
| print(channel); | |||
| }).catchError((error) { | |||
| print(error); | |||
| }); | |||
| } | |||
| @@ -1,7 +1,7 @@ | |||
| import 'dart:async'; | |||
| import 'package:http/http.dart' as http; | |||
| import 'package:webfeedclient/domain/channel.dart'; | |||
| import 'package:webfeedclient/domain/rss_feed.dart'; | |||
| import 'package:xml/xml.dart' as xml; | |||
| class WebFeedClient { | |||
| @@ -11,14 +11,14 @@ class WebFeedClient { | |||
| client = new http.Client(); | |||
| } | |||
| Future<Channel> fetch(String url) { | |||
| Future<RssFeed> fetch(String url) { | |||
| return client.get(url).then((response) { | |||
| print("Response Status Code: ${response.statusCode}"); | |||
| print("Response Body: ${response.body}"); | |||
| return response.body; | |||
| }).then((bodyString) { | |||
| var document = xml.parse(bodyString); | |||
| var channel = new Channel.parse(document); | |||
| var channel = new RssFeed.parse(document); | |||
| return channel; | |||
| }); | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| import 'package:xml/xml.dart'; | |||
| class RssCategory { | |||
| String domain; | |||
| String value; | |||
| RssCategory(this.domain, this.value); | |||
| factory RssCategory.parse(XmlElement node) { | |||
| String domain = node.getAttribute("domain"); | |||
| String value = node.text; | |||
| return new RssCategory(domain, value); | |||
| } | |||
| } | |||
| @@ -1,32 +1,32 @@ | |||
| import 'dart:core'; | |||
| import 'package:webfeedclient/domain/item.dart'; | |||
| import 'package:webfeedclient/domain/rss_category.dart'; | |||
| import 'package:webfeedclient/domain/rss_item.dart'; | |||
| import 'package:webfeedclient/util/helpers.dart'; | |||
| import 'package:xml/xml.dart'; | |||
| import 'image.dart'; | |||
| import 'package:webfeedclient/domain/rss_image.dart'; | |||
| class Channel { | |||
| class RssFeed { | |||
| final String title; | |||
| final String description; | |||
| final String link; | |||
| final List<Item> items; | |||
| final List<RssItem> items; | |||
| final Image image; | |||
| final RssImage image; | |||
| final List<RssCategory> categories; | |||
| final String lastBuildDate; | |||
| final String language; | |||
| final String generator; | |||
| final String copyright; | |||
| Channel(this.title, this.description, this.link, this.items, | |||
| {this.image, this.lastBuildDate, this.language, this.generator, this.copyright}); | |||
| RssFeed(this.title, this.description, this.link, this.items, | |||
| {this.image, this.categories, this.lastBuildDate, this.language, this.generator, this.copyright}); | |||
| factory Channel.parse(XmlDocument document) { | |||
| factory RssFeed.parse(XmlDocument document) { | |||
| XmlElement channelElement; | |||
| try { | |||
| channelElement = document | |||
| .findAllElements("channel") | |||
| .first; | |||
| channelElement = document.findAllElements("channel").first; | |||
| } on StateError { | |||
| throw new ArgumentError("channel not found"); | |||
| } | |||
| @@ -34,23 +34,27 @@ class Channel { | |||
| var description = xmlGetString(channelElement, "description"); | |||
| var link = xmlGetString(channelElement, "link"); | |||
| var feeds = channelElement.findAllElements("item").map((XmlElement element) { | |||
| return new Item.parse(element); | |||
| List<RssItem> feeds = channelElement.findElements("item").map((element) { | |||
| return new RssItem.parse(element); | |||
| }).toList(); | |||
| Image image; | |||
| RssImage image; | |||
| try { | |||
| image = new Image.parse(channelElement | |||
| .findElements("image") | |||
| .first); | |||
| image = new RssImage.parse(channelElement.findElements("image").first); | |||
| } on StateError {} | |||
| List<RssCategory> categories = channelElement.findElements("category").map((element) { | |||
| return new RssCategory.parse(element); | |||
| }).toList(); | |||
| var lastBuildDate = xmlGetString(channelElement, "lastBuildDate", strict: false); | |||
| var language = xmlGetString(channelElement, "language", strict: false); | |||
| var generator = xmlGetString(channelElement, "generator", strict: false); | |||
| var copyright = xmlGetString(channelElement, "copyright", strict: false); | |||
| return new Channel(title, description, link, feeds, image: image, | |||
| return new RssFeed(title, description, link, feeds, | |||
| image: image, | |||
| categories: categories, | |||
| lastBuildDate: lastBuildDate, | |||
| language: language, | |||
| generator: generator, | |||
| @@ -1,19 +1,19 @@ | |||
| import 'package:webfeedclient/util/helpers.dart'; | |||
| import 'package:xml/xml.dart'; | |||
| class Image { | |||
| class RssImage { | |||
| final String title; | |||
| final String url; | |||
| final String link; | |||
| Image(this.title, this.url, this.link); | |||
| RssImage(this.title, this.url, this.link); | |||
| factory Image.parse(XmlElement element) { | |||
| factory RssImage.parse(XmlElement element) { | |||
| var title = xmlGetString(element, "title", strict: false); | |||
| var url = xmlGetString(element, "url", strict: false); | |||
| var link = xmlGetString(element, "link", strict: false); | |||
| return new Image(title, url, link); | |||
| return new RssImage(title, url, link); | |||
| } | |||
| @override | |||
| @@ -1,24 +1,31 @@ | |||
| import 'package:webfeedclient/domain/rss_category.dart'; | |||
| import 'package:webfeedclient/util/helpers.dart'; | |||
| import 'package:xml/xml.dart'; | |||
| class Item { | |||
| class RssItem { | |||
| final String title; | |||
| final String description; | |||
| final String link; | |||
| final List<RssCategory> categories; | |||
| final String guid; | |||
| final String pubDate; | |||
| Item(this.title, this.description, this.link, {this.guid, this.pubDate}); | |||
| RssItem(this.title, this.description, this.link, {this.categories, this.guid, this.pubDate}); | |||
| factory Item.parse(XmlElement element) { | |||
| factory RssItem.parse(XmlElement element) { | |||
| var title = xmlGetString(element, "title"); | |||
| var description = xmlGetString(element, "description"); | |||
| var link = xmlGetString(element, "link"); | |||
| List<RssCategory> categories = element.findElements("category").map((element) { | |||
| return new RssCategory.parse(element); | |||
| }).toList(); | |||
| var guid = xmlGetString(element, "guid", strict: false); | |||
| var pubDate = xmlGetString(element, "pubDate", strict: false); | |||
| return new Item(title, description, link, guid: guid, pubDate: pubDate); | |||
| return new RssItem(title, description, link, categories: categories, guid: guid, pubDate: pubDate); | |||
| } | |||
| @override | |||
| @@ -1,43 +1,51 @@ | |||
| import 'dart:io'; | |||
| import 'dart:core'; | |||
| import 'package:test/test.dart'; | |||
| import 'package:webfeedclient/domain/channel.dart'; | |||
| import 'package:webfeedclient/domain/rss_feed.dart'; | |||
| import 'package:xml/xml.dart' as xml; | |||
| void main() { | |||
| test("parsing Invalid.xml", () { | |||
| var xmlString = new File("test/examples/Invalid.xml").readAsStringSync(); | |||
| var xmlString = new File("test/xml/Invalid.xml").readAsStringSync(); | |||
| var doc = xml.parse(xmlString); | |||
| try { | |||
| new Channel.parse(doc); | |||
| new RssFeed.parse(doc); | |||
| fail("Should throw Argument Error"); | |||
| } on ArgumentError {} | |||
| }); | |||
| test("parsing RSS.xml", () { | |||
| var xmlString = new File("test/examples/RSS.xml").readAsStringSync(); | |||
| var xmlString = new File("test/xml/RSS.xml").readAsStringSync(); | |||
| var doc = xml.parse(xmlString); | |||
| var channel = new Channel.parse(doc); | |||
| var feed = new RssFeed.parse(doc); | |||
| expect(channel.title, "News - Foo bar News"); | |||
| expect(channel.description, "Foo bar News and Updates feed provided by Foo bar, Inc."); | |||
| expect(channel.link, "https://foo.bar.news/"); | |||
| expect(channel.language, "en-US"); | |||
| expect(channel.lastBuildDate, "Mon, 26 Mar 2018 14:00:00 PDT"); | |||
| expect(channel.generator, "Custom"); | |||
| expect(channel.copyright, "Copyright 2018, Foo bar Inc."); | |||
| expect(feed.title, "News - Foo bar News"); | |||
| expect(feed.description, "Foo bar News and Updates feed provided by Foo bar, Inc."); | |||
| expect(feed.link, "https://foo.bar.news/"); | |||
| expect(feed.language, "en-US"); | |||
| expect(feed.lastBuildDate, "Mon, 26 Mar 2018 14:00:00 PDT"); | |||
| expect(feed.generator, "Custom"); | |||
| expect(feed.copyright, "Copyright 2018, Foo bar Inc."); | |||
| expect(channel.image.title, "Foo bar News"); | |||
| expect(channel.image.url, "https://foo.bar.news/logo.gif"); | |||
| expect(channel.image.link, "https://foo.bar.news/"); | |||
| expect(feed.image.title, "Foo bar News"); | |||
| expect(feed.image.url, "https://foo.bar.news/logo.gif"); | |||
| expect(feed.image.link, "https://foo.bar.news/"); | |||
| expect(channel.items.length, 2); | |||
| expect(feed.categories.length, 2); | |||
| expect(feed.categories[0].domain, null); | |||
| expect(feed.categories[0].value, "Ipsum"); | |||
| expect(feed.categories[1].domain, "news"); | |||
| expect(feed.categories[1].value, "Lorem Ipsum"); | |||
| expect(channel.items.first.title, "The standard Lorem Ipsum passage, used since the 1500s"); | |||
| expect(channel.items.first.description, "Lorem ipsum dolor sit amet, consectetur adipiscing elit"); | |||
| expect(channel.items.first.link, "https://foo.bar.news/1"); | |||
| expect(channel.items.first.guid, "https://foo.bar.news/1?guid"); | |||
| expect(channel.items.first.pubDate, "Mon, 26 Mar 2018 14:00:00 PDT"); | |||
| expect(feed.items.length, 2); | |||
| expect(feed.items.first.title, "The standard Lorem Ipsum passage, used since the 1500s"); | |||
| expect(feed.items.first.description, "Lorem ipsum dolor sit amet, consectetur adipiscing elit"); | |||
| expect(feed.items.first.link, "https://foo.bar.news/1"); | |||
| expect(feed.items.first.guid, "https://foo.bar.news/1?guid"); | |||
| expect(feed.items.first.pubDate, "Mon, 26 Mar 2018 14:00:00 PDT"); | |||
| expect(feed.items.first.categories.first.domain, "news"); | |||
| expect(feed.items.first.categories.first.value, "Lorem"); | |||
| }); | |||
| } | |||
| @@ -9,6 +9,8 @@ | |||
| <title>Foo bar News</title> | |||
| <link>https://foo.bar.news/</link> | |||
| </image> | |||
| <category>Ipsum</category> | |||
| <category domain="news">Lorem Ipsum</category> | |||
| <language>en-US</language> | |||
| <lastBuildDate>Mon, 26 Mar 2018 14:00:00 PDT</lastBuildDate> | |||
| <generator>Custom</generator> | |||
| @@ -18,6 +20,7 @@ | |||
| <link>https://foo.bar.news/1</link> | |||
| <guid>https://foo.bar.news/1?guid</guid> | |||
| <description>Lorem ipsum dolor sit amet, consectetur adipiscing elit</description> | |||
| <category domain="news">Lorem</category> | |||
| <pubDate>Mon, 26 Mar 2018 14:00:00 PDT</pubDate> | |||
| </item> | |||
| <item> | |||
| @@ -25,6 +28,7 @@ | |||
| <link>https://foo.bar.news/2</link> | |||
| <guid>https://foo.bar.news/2?guid</guid> | |||
| <description>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium</description> | |||
| <category domain="auto">Ipsum</category> | |||
| <pubDate>Tue, 20 Mar 2018 10:00:00 PDT</pubDate> | |||
| </item> | |||
| </channel> | |||