| @@ -10,3 +10,6 @@ pubspec.lock | |||
| # Directory created by dartdoc | |||
| # If you don't generate documentation locally you can remove this line. | |||
| doc/api/ | |||
| .idea | |||
| .dart_tool | |||
| @@ -0,0 +1,25 @@ | |||
| import 'dart:async'; | |||
| import 'package:http/http.dart' as http; | |||
| import 'package:webfeedclient/domain/channel.dart'; | |||
| import 'package:xml/xml.dart' as xml; | |||
| class WebFeedClient { | |||
| http.Client client; | |||
| WebFeedClient() { | |||
| client = new http.Client(); | |||
| } | |||
| Future<Channel> 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); | |||
| return channel; | |||
| }); | |||
| } | |||
| } | |||
| @@ -0,0 +1,70 @@ | |||
| import 'dart:core'; | |||
| import 'package:webfeedclient/domain/item.dart'; | |||
| import 'package:webfeedclient/util/helpers.dart'; | |||
| import 'package:xml/xml.dart'; | |||
| import 'image.dart'; | |||
| class Channel { | |||
| final String title; | |||
| final String description; | |||
| final String link; | |||
| final List<Item> items; | |||
| final Image image; | |||
| 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}); | |||
| factory Channel.parse(XmlDocument document) { | |||
| XmlElement channelElement; | |||
| try { | |||
| channelElement = document | |||
| .findAllElements("channel") | |||
| .first; | |||
| } on StateError { | |||
| throw new ArgumentError("channel not found"); | |||
| } | |||
| var title = xmlGetString(channelElement, "title"); | |||
| var description = xmlGetString(channelElement, "description"); | |||
| var link = xmlGetString(channelElement, "link"); | |||
| var feeds = channelElement.findAllElements("item").map((XmlElement element) { | |||
| return new Item.parse(element); | |||
| }).toList(); | |||
| Image image; | |||
| try { | |||
| image = new Image.parse(channelElement | |||
| .findElements("image") | |||
| .first); | |||
| } on StateError {} | |||
| 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, | |||
| lastBuildDate: lastBuildDate, | |||
| language: language, | |||
| generator: generator, | |||
| copyright: copyright); | |||
| } | |||
| @override | |||
| String toString() { | |||
| return ''' | |||
| title: $title | |||
| description: $description | |||
| link: $link | |||
| lastBuildDate: $lastBuildDate | |||
| items: $items | |||
| '''; | |||
| } | |||
| } | |||
| @@ -0,0 +1,27 @@ | |||
| import 'package:webfeedclient/util/helpers.dart'; | |||
| import 'package:xml/xml.dart'; | |||
| class Image { | |||
| final String title; | |||
| final String url; | |||
| final String link; | |||
| Image(this.title, this.url, this.link); | |||
| factory Image.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); | |||
| } | |||
| @override | |||
| String toString() { | |||
| return ''' | |||
| title: $title | |||
| url: $url | |||
| link: $link | |||
| '''; | |||
| } | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| import 'package:webfeedclient/util/helpers.dart'; | |||
| import 'package:xml/xml.dart'; | |||
| class Item { | |||
| final String title; | |||
| final String description; | |||
| final String link; | |||
| final String guid; | |||
| final String pubDate; | |||
| Item(this.title, this.description, this.link, {this.guid, this.pubDate}); | |||
| factory Item.parse(XmlElement element) { | |||
| var title = xmlGetString(element, "title"); | |||
| var description = xmlGetString(element, "description"); | |||
| var link = xmlGetString(element, "link"); | |||
| var guid = xmlGetString(element, "guid", strict: false); | |||
| var pubDate = xmlGetString(element, "pubDate", strict: false); | |||
| return new Item(title, description, link, guid: guid, pubDate: pubDate); | |||
| } | |||
| @override | |||
| String toString() { | |||
| return ''' | |||
| title: $title | |||
| description: $description | |||
| link: $link | |||
| pubDate: $pubDate | |||
| '''; | |||
| } | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| import 'package:webfeedclient/client.dart'; | |||
| void main() { | |||
| var client = new WebFeedClient(); | |||
| client.fetch("https://developer.apple.com/news/releases/rss/releases.rss"); | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| import 'dart:core'; | |||
| import 'package:xml/xml.dart'; | |||
| String xmlGetString(XmlElement element, String name, {strict: true}) { | |||
| try { | |||
| return element | |||
| .findElements(name) | |||
| .first | |||
| .text; | |||
| } on StateError { | |||
| if (strict) { | |||
| throw new ArgumentError("$name not found"); | |||
| } | |||
| } | |||
| return null; | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| name: webfeedclient | |||
| dependencies: | |||
| xml: "^3.0.0" | |||
| http: "^0.11.3+16" | |||
| dev_dependencies: | |||
| test: ^0.12.0 | |||
| @@ -0,0 +1,2 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <foo></foo> | |||
| @@ -0,0 +1,31 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <rss version="2.0"> | |||
| <channel> | |||
| <title>News - Foo bar News</title> | |||
| <link>https://foo.bar.news/</link> | |||
| <description>Foo bar News and Updates feed provided by Foo bar, Inc.</description> | |||
| <image> | |||
| <url>https://foo.bar.news/logo.gif</url> | |||
| <title>Foo bar News</title> | |||
| <link>https://foo.bar.news/</link> | |||
| </image> | |||
| <language>en-US</language> | |||
| <lastBuildDate>Mon, 26 Mar 2018 14:00:00 PDT</lastBuildDate> | |||
| <generator>Custom</generator> | |||
| <copyright>Copyright 2018, Foo bar Inc.</copyright> | |||
| <item> | |||
| <title>The standard Lorem Ipsum passage, used since the 1500s</title> | |||
| <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> | |||
| <pubDate>Mon, 26 Mar 2018 14:00:00 PDT</pubDate> | |||
| </item> | |||
| <item> | |||
| <title>Section 1.10.32 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC</title> | |||
| <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> | |||
| <pubDate>Tue, 20 Mar 2018 10:00:00 PDT</pubDate> | |||
| </item> | |||
| </channel> | |||
| </rss> | |||
| @@ -0,0 +1,43 @@ | |||
| import 'dart:io'; | |||
| import 'dart:core'; | |||
| import 'package:test/test.dart'; | |||
| import 'package:webfeedclient/domain/channel.dart'; | |||
| import 'package:xml/xml.dart' as xml; | |||
| void main() { | |||
| test("parsing Invalid.xml", () { | |||
| var xmlString = new File("test/examples/Invalid.xml").readAsStringSync(); | |||
| var doc = xml.parse(xmlString); | |||
| try { | |||
| new Channel.parse(doc); | |||
| fail("Should throw Argument Error"); | |||
| } on ArgumentError {} | |||
| }); | |||
| test("parsing RSS.xml", () { | |||
| var xmlString = new File("test/examples/RSS.xml").readAsStringSync(); | |||
| var doc = xml.parse(xmlString); | |||
| var channel = new Channel.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(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(channel.items.length, 2); | |||
| 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"); | |||
| }); | |||
| } | |||