| @@ -10,3 +10,6 @@ pubspec.lock | |||||
| # Directory created by dartdoc | # Directory created by dartdoc | ||||
| # If you don't generate documentation locally you can remove this line. | # If you don't generate documentation locally you can remove this line. | ||||
| doc/api/ | 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"); | |||||
| }); | |||||
| } | |||||