diff --git a/README.md b/README.md index 0e83d16..c90ef0b 100644 --- a/README.md +++ b/README.md @@ -3,21 +3,28 @@ [![Build Status](https://travis-ci.org/witochandra/webfeed.svg?branch=master)](https://travis-ci.org/witochandra/webfeed) [![Pub](https://img.shields.io/pub/v/webfeed.svg)](https://pub.dartlang.org/packages/webfeed) -A dart package for parsing RSS and Atom feed. +A dart package for parsing and generating RSS and Atom feeds. ### Features -- [x] RSS -- [x] Atom -- [x] Namespaces - - [x] Media RSS - - [x] Dublin Core +- [x] Parsing + - [x] RSS + - [x] Atom + - [x] Namespaces + - [x] Media RSS + - [x] Dublin Core +- [ ] Generating + - [ ] RSS + - [x] Atom + - [ ] Namespaces + - [ ] Media RSS + - [ ] Dublin Core ### Installing Add this line into your `pubspec.yaml` ``` -webfeed: ^0.4.2 +webfeed: ^0.5.0 ``` Import the package into your dart code using: @@ -27,10 +34,16 @@ import 'package:webfeed/webfeed.dart'; ### Example -To parse string into `RssFeed` object use: +To parse string into an object use: +```dart +var rssFeed = RssFeed.parse(xmlString); // for parsing RSS feed +var atomFeed = AtomFeed.parse(xmlString); // for parsing Atom feed ``` -var rssFeed = new RssFeed.parse(xmlString); // for parsing RSS feed -var atomFeed = new AtomFeed.parse(xmlString); // for parsing Atom feed + +To generate string from an object use: +```dart +var atomFeed = AtomFeed(id: Uri.parse('urn:42'), title: 'a title', ...); // for creating Atom feed +var xmlString = atomFeed.toXml().toXmlString(); // for creating XML string for Atom feed ``` ### Preview @@ -105,6 +118,10 @@ item.rights item.media ``` +## Contributors +- Wito Chandra (author) +- Chris Sells (provided XML generation from Atom OM) + ## License WebFeed is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details diff --git a/analysis_options.yaml b/analysis_options.yaml index 0323d68..3d2d6cd 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,7 +1,4 @@ analyzer: - language: - enablePreviewDart2: true - strong-mode: true errors: unused_import: error unused_local_variable: error diff --git a/example/main.dart b/example/main.dart index 81a0888..3ae2492 100644 --- a/example/main.dart +++ b/example/main.dart @@ -1,24 +1,16 @@ import 'package:http/http.dart' as http; import 'package:webfeed/webfeed.dart'; -void main() { - var client = new http.Client(); +void main() async { + var client = http.Client(); // RSS feed - client.get("https://developer.apple.com/news/releases/rss/releases.rss").then((response) { - return response.body; - }).then((bodyString) { - var channel = new RssFeed.parse(bodyString); - print(channel); - return channel; - }); + var rssresp = await client.get("https://developer.apple.com/news/releases/rss/releases.rss"); + var channel = RssFeed.parse(rssresp.body); + print(channel); // Atom feed - client.get("https://www.theverge.com/rss/index.xml").then((response) { - return response.body; - }).then((bodyString) { - var feed = new AtomFeed.parse(bodyString); - print(feed); - return feed; - }); + var atomresp = await client.get("https://www.theverge.com/rss/index.xml"); + var feed = AtomFeed.parse(atomresp.body); + print(feed.toXml().toXmlString(pretty: true, indent: ' ')); } diff --git a/lib/domain/atom_category.dart b/lib/domain/atom_category.dart index 22c6a97..9ffb589 100644 --- a/lib/domain/atom_category.dart +++ b/lib/domain/atom_category.dart @@ -5,12 +5,23 @@ class AtomCategory { final String scheme; final String label; - AtomCategory(this.term, this.scheme, this.label); + AtomCategory({ + this.term, + this.scheme, + this.label, + }); - factory AtomCategory.parse(XmlElement element) { - var term = element.getAttribute("term"); - var scheme = element.getAttribute("scheme"); - var label = element.getAttribute("label"); - return AtomCategory(term, scheme, label); + factory AtomCategory.parse(XmlElement element) => AtomCategory( + term: element.getAttribute("term"), + scheme: element.getAttribute("scheme"), + label: element.getAttribute("label"), + ); + + void build(XmlBuilder b) { + b.element('category', nest: () { + if (term != null) b.attribute('term', term); + if (scheme != null) b.attribute('scheme', scheme); + if (label != null) b.attribute('label', label); + }); } } diff --git a/lib/domain/atom_content.dart b/lib/domain/atom_content.dart new file mode 100644 index 0000000..5860b84 --- /dev/null +++ b/lib/domain/atom_content.dart @@ -0,0 +1,24 @@ +import 'package:xml/xml.dart'; + +class AtomContent { + String type; + String text; + + AtomContent({ + this.type, + this.text, + }); + + factory AtomContent.parse(XmlElement element) { + if (element == null) return null; + return AtomContent( + type: element.getAttribute('type'), + text: element.text, + ); + } + + void build(XmlBuilder b, String kind) => b.element(kind, nest: () { + if (type != null) b.attribute('type', type); + if (text != null) b.text(text); + }); +} diff --git a/lib/domain/atom_feed.dart b/lib/domain/atom_feed.dart index 92f2cd6..ab39fff 100644 --- a/lib/domain/atom_feed.dart +++ b/lib/domain/atom_feed.dart @@ -7,25 +7,24 @@ import 'package:webfeed/util/helpers.dart'; import 'package:xml/xml.dart'; class AtomFeed { - final String id; + final Uri id; final String title; - final String updated; + final DateTime updated; final List items; - final List links; final List authors; final List contributors; final List categories; final AtomGenerator generator; - final String icon; - final String logo; + final Uri icon; + final Uri logo; final String rights; final String subtitle; AtomFeed({ this.id, this.title, - this.updated, + updated, this.items, this.links, this.authors, @@ -36,42 +35,61 @@ class AtomFeed { this.logo, this.rights, this.subtitle, - }); + }) : this.updated = updated ?? DateTime.now(); // default value factory AtomFeed.parse(String xmlString) { - var document = parse(xmlString); XmlElement feedElement; + try { + var document = parse(xmlString); feedElement = document.findElements("feed").first; } on StateError { - throw new ArgumentError("feed not found"); + throw ArgumentError("feed not found"); } return AtomFeed( - id: findElementOrNull(feedElement, "id")?.text, - title: findElementOrNull(feedElement, "title")?.text, - updated: findElementOrNull(feedElement, "updated")?.text, - items: feedElement.findElements("entry").map((element) { - return AtomItem.parse(element); - }).toList(), - links: feedElement.findElements("link").map((element) { - return AtomLink.parse(element); - }).toList(), - authors: feedElement.findElements("author").map((element) { - return AtomPerson.parse(element); - }).toList(), - contributors: feedElement.findElements("contributor").map((element) { - return AtomPerson.parse(element); - }).toList(), - categories: feedElement.findElements("category").map((element) { - return AtomCategory.parse(element); - }).toList(), - generator: - AtomGenerator.parse(findElementOrNull(feedElement, "generator")), - icon: findElementOrNull(feedElement, "icon")?.text, - logo: findElementOrNull(feedElement, "logo")?.text, - rights: findElementOrNull(feedElement, "rights")?.text, - subtitle: findElementOrNull(feedElement, "subtitle")?.text, + id: parseUriLiteral(feedElement, "id"), + title: parseTextLiteral(feedElement, "title"), + updated: parseDateTimeLiteral(feedElement, "updated"), + items: feedElement.findElements("entry").map((e) => AtomItem.parse(e)).toList(), + links: feedElement.findElements("link").map((e) => AtomLink.parse(e)).toList(), + authors: feedElement.findElements("author").map((e) => AtomPerson.parse(e)).toList(), + contributors: feedElement.findElements("contributor").map((e) => AtomPerson.parse(e)).toList(), + categories: feedElement.findElements("category").map((e) => AtomCategory.parse(e)).toList(), + generator: AtomGenerator.parse(findElementOrNull(feedElement, "generator")), + icon: parseUriLiteral(feedElement, "icon"), + logo: parseUriLiteral(feedElement, "logo"), + rights: parseTextLiteral(feedElement, "rights"), + subtitle: parseTextLiteral(feedElement, "subtitle"), ); } + + XmlDocument toXml() { + var doc = parse(''); + var b = XmlBuilder(); + build(b); + var feed = doc.findAllElements('feed').first; + b.build().children.forEach((c) => feed.children.add(c.copy())); + return doc; + } + + void build(XmlBuilder b) { + if (id == null) throw Exception('must have an id'); + + b.element('id', nest: () => b.text(id)); + b.element('title', nest: () => title == null ? '' : b.text(title)); + b.element('updated', nest: () => b.text(updated.toUtc().toIso8601String())); + + if (links != null) links.forEach((l) => l.build(b)); + if (authors != null) authors.forEach((a) => a.build(b, 'author')); + if (contributors != null) contributors.forEach((c) => c.build(b, 'contributor')); + if (categories != null) categories.forEach((c) => c.build(b)); + if (generator != null) generator.build(b); + + if (icon != null) b.element('icon', nest: () => b.text(icon)); + if (logo != null) b.element('logo', nest: () => b.text(logo)); + if (subtitle != null) b.element('subtitle', nest: () => b.text(subtitle)); + + if (items != null) items.forEach((i) => i.build(b)); + } } diff --git a/lib/domain/atom_generator.dart b/lib/domain/atom_generator.dart index 7fd8579..254b29e 100644 --- a/lib/domain/atom_generator.dart +++ b/lib/domain/atom_generator.dart @@ -1,19 +1,32 @@ import 'package:xml/xml.dart'; class AtomGenerator { - final String uri; + final Uri uri; final String version; final String value; - AtomGenerator(this.uri, this.version, this.value); + AtomGenerator({ + this.uri, + this.version, + this.value, + }); factory AtomGenerator.parse(XmlElement element) { - if (element == null) { - return null; - } + if (element == null) return null; var uri = element.getAttribute("uri"); - var version = element.getAttribute("version"); - var value = element.text; - return new AtomGenerator(uri, version, value); + return AtomGenerator( + uri: uri == null ? null : Uri.parse(uri), + version: element.getAttribute("version"), + value: element.text, + ); + } + + void build(XmlBuilder b) { + // Foo bar generator + b.element('generator', nest: () { + if (uri != null) b.attribute('uri', uri); + if (version != null) b.attribute('version', version); + if (value != null) b.text(value); + }); } } diff --git a/lib/domain/atom_item.dart b/lib/domain/atom_item.dart index 618015b..1fba17a 100644 --- a/lib/domain/atom_item.dart +++ b/lib/domain/atom_item.dart @@ -1,4 +1,5 @@ import 'package:webfeed/domain/atom_category.dart'; +import 'package:webfeed/domain/atom_content.dart'; import 'package:webfeed/domain/atom_link.dart'; import 'package:webfeed/domain/atom_person.dart'; import 'package:webfeed/domain/atom_source.dart'; @@ -7,25 +8,24 @@ import 'package:webfeed/util/helpers.dart'; import 'package:xml/xml.dart'; class AtomItem { - final String id; + final Uri id; final String title; - final String updated; - + final DateTime updated; final List authors; final List links; final List categories; final List contributors; final AtomSource source; - final String published; - final String content; - final String summary; + final DateTime published; + final AtomContent content; + final AtomContent summary; final String rights; final Media media; AtomItem({ this.id, this.title, - this.updated, + updated, this.authors, this.links, this.categories, @@ -36,31 +36,40 @@ class AtomItem { this.summary, this.rights, this.media, - }); + }) : this.updated = updated ?? DateTime.now(); + + factory AtomItem.parse(XmlElement element) => AtomItem( + id: parseUriLiteral(element, "id"), + title: parseTextLiteral(element, "title"), + updated: parseDateTimeLiteral(element, "updated"), + authors: element.findElements("author").map((e) => AtomPerson.parse(e)).toList(), + links: element.findElements("link").map((e) => AtomLink.parse(e)).toList(), + categories: element.findElements("category").map((e) => AtomCategory.parse(e)).toList(), + contributors: element.findElements("contributor").map((e) => AtomPerson.parse(e)).toList(), + source: AtomSource.parse(findElementOrNull(element, "source")), + published: parseDateTimeLiteral(element, "published"), + content: AtomContent.parse(findElementOrNull(element, "content")), + summary: AtomContent.parse(findElementOrNull(element, "summary")), + rights: parseTextLiteral(element, "rights"), + media: Media.parse(element), + ); - factory AtomItem.parse(XmlElement element) { - return AtomItem( - id: findElementOrNull(element, "id")?.text, - title: findElementOrNull(element, "title")?.text, - updated: findElementOrNull(element, "updated")?.text, - authors: element.findElements("author").map((element) { - return AtomPerson.parse(element); - }).toList(), - links: element.findElements("link").map((element) { - return AtomLink.parse(element); - }).toList(), - categories: element.findElements("category").map((element) { - return AtomCategory.parse(element); - }).toList(), - contributors: element.findElements("contributor").map((element) { - return AtomPerson.parse(element); - }).toList(), - source: AtomSource.parse(findElementOrNull(element, "source")), - published: findElementOrNull(element, "published")?.text, - content: findElementOrNull(element, "content")?.text, - summary: findElementOrNull(element, "summary")?.text, - rights: findElementOrNull(element, "rights")?.text, - media: Media.parse(element), - ); + void build(XmlBuilder b) { + if (id == null) throw Exception('must have an id'); + b.element('entry', nest: () { + b.element('id', nest: () => b.text(id)); + if (title != null) b.element('title', nest: () => b.text(title)); + if (updated != null) b.element('updated', nest: () => b.text(updated.toUtc().toIso8601String())); + if (authors != null) authors.forEach((a) => a.build(b, 'author')); + if (links != null) links.forEach((l) => l.build(b)); + if (categories != null) categories.forEach((c) => c.build(b)); + if (contributors != null) contributors.forEach((c) => c.build(b, 'contributor')); + if (source != null) source.build(b); + if (published != null) b.element('published', nest: () => b.text(published.toUtc().toIso8601String())); + if (summary != null) summary.build(b, 'summary'); + if (content != null) content.build(b, 'content'); + if (rights != null) b.element('rights', nest: () => b.text(rights)); + //if (media != null) media.build(b); + }); } } diff --git a/lib/domain/atom_link.dart b/lib/domain/atom_link.dart index 3b570f2..7bdd213 100644 --- a/lib/domain/atom_link.dart +++ b/lib/domain/atom_link.dart @@ -1,32 +1,42 @@ import 'package:xml/xml.dart'; class AtomLink { - final String href; + final Uri href; final String rel; final String type; final String hreflang; final String title; final int length; - AtomLink( + AtomLink({ this.href, this.rel, this.type, this.hreflang, this.title, this.length, - ); + }); factory AtomLink.parse(XmlElement element) { var href = element.getAttribute("href"); - var rel = element.getAttribute("rel"); - var type = element.getAttribute("type"); - var title = element.getAttribute("title"); - var hreflang = element.getAttribute("hreflang"); - var length = 0; - if (element.getAttribute("length") != null) { - length = int.parse(element.getAttribute("length")); - } - return AtomLink(href, rel, type, hreflang, title, length); + return AtomLink( + href: href == null ? null : Uri.parse(href), + rel: element.getAttribute("rel"), + type: element.getAttribute("type"), + hreflang: element.getAttribute("hreflang"), + title: element.getAttribute("title"), + length: int.parse(element.getAttribute("length") ?? "0"), + ); + } + + void build(XmlBuilder b) { + b.element('link', nest: () { + if (rel != null) b.attribute('rel', rel); + if (type != null) b.attribute('type', type); + if (hreflang != null) b.attribute('hreflang', hreflang); + if (href != null) b.attribute('href', href); + if (title != null) b.attribute('title', title); + if (length != null) b.attribute('length', length); + }); } } diff --git a/lib/domain/atom_person.dart b/lib/domain/atom_person.dart index 3eb1e4a..7b15cce 100644 --- a/lib/domain/atom_person.dart +++ b/lib/domain/atom_person.dart @@ -3,15 +3,25 @@ import 'package:xml/xml.dart'; class AtomPerson { final String name; - final String uri; + final Uri uri; final String email; - AtomPerson(this.name, this.uri, this.email); + AtomPerson({this.name, this.uri, this.email}); factory AtomPerson.parse(XmlElement element) { - var name = findElementOrNull(element, "name")?.text; - var uri = findElementOrNull(element, "uri")?.text; - var email = findElementOrNull(element, "email")?.text; - return AtomPerson(name, uri, email); + var uri = parseTextLiteral(element, "uri"); + return AtomPerson( + name: parseTextLiteral(element, "name"), + uri: uri == null ? null : Uri.parse(uri), + email: parseTextLiteral(element, "email"), + ); + } + + void build(XmlBuilder b, String kind) { + b.element(kind, nest: () { + if (name != null) b.element('name', nest: () => b.text(name)); + if (uri != null) b.element('uri', nest: () => b.text(uri)); + if (email != null) b.element('email', nest: () => b.text(email)); + }); } } diff --git a/lib/domain/atom_source.dart b/lib/domain/atom_source.dart index 37fb619..228afe0 100644 --- a/lib/domain/atom_source.dart +++ b/lib/domain/atom_source.dart @@ -2,20 +2,32 @@ import 'package:webfeed/util/helpers.dart'; import 'package:xml/xml.dart'; class AtomSource { - final String id; + final Uri id; final String title; - final String updated; + final DateTime updated; - AtomSource(this.id, this.title, this.updated); + AtomSource({ + this.id, + this.title, + updated, + }) : this.updated = updated ?? DateTime.now(); factory AtomSource.parse(XmlElement element) { - if (element == null) { - return null; - } - var id = findElementOrNull(element, "id")?.text; - var title = findElementOrNull(element, "title")?.text; - var updated = findElementOrNull(element, "updated")?.text; + if (element == null) return null; + var id = parseTextLiteral(element, 'id'); + return AtomSource( + id: id == null ? null : Uri.parse(id), + title: parseTextLiteral(element, 'title'), + updated: parseDateTimeLiteral(element, 'updated'), + ); + } - return AtomSource(id, title, updated); + void build(XmlBuilder b) { + if (id == null) throw Exception('must have id'); + b.element('source', nest: () { + b.element('id', nest: () => b.text(id)); + if (title != null) b.element('title', nest: () => b.text(title)); + if (updated != null) b.element('updated', nest: () => b.text(updated.toUtc().toIso8601String())); + }); } } diff --git a/lib/domain/media/category.dart b/lib/domain/media/category.dart index 4796cd7..22bcc6a 100644 --- a/lib/domain/media/category.dart +++ b/lib/domain/media/category.dart @@ -12,10 +12,8 @@ class Category { }); factory Category.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Category( + if (element == null) return null; + return Category( scheme: element.getAttribute("scheme"), label: element.getAttribute("label"), value: element.text, diff --git a/lib/domain/media/community.dart b/lib/domain/media/community.dart index b301050..7076447 100644 --- a/lib/domain/media/community.dart +++ b/lib/domain/media/community.dart @@ -16,19 +16,11 @@ class Community { }); factory Community.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Community( - starRating: new StarRating.parse( - findElementOrNull(element, "media:starRating"), - ), - statistics: new Statistics.parse( - findElementOrNull(element, "media:statistics"), - ), - tags: new Tags.parse( - findElementOrNull(element, "media:tags"), - ), + if (element == null) return null; + return Community( + starRating: StarRating.parse(findElementOrNull(element, "media:starRating")), + statistics: Statistics.parse(findElementOrNull(element, "media:statistics")), + tags: Tags.parse(findElementOrNull(element, "media:tags")), ); } } diff --git a/lib/domain/media/content.dart b/lib/domain/media/content.dart index 3a4ebaa..b44d493 100644 --- a/lib/domain/media/content.dart +++ b/lib/domain/media/content.dart @@ -33,24 +33,20 @@ class Content { this.lang, }); - factory Content.parse(XmlElement element) { - return new Content( - url: element.getAttribute("url"), - type: element.getAttribute("type"), - fileSize: int.tryParse(element.getAttribute("fileSize") ?? "0"), - medium: element.getAttribute("medium"), - isDefault: element.getAttribute("isDefault") == "true", - expression: element.getAttribute("expression"), - bitrate: int.tryParse(element.getAttribute("bitrate") ?? "0"), - framerate: double.tryParse(element.getAttribute("framerate") ?? "0"), - samplingrate: double.tryParse( - element.getAttribute("samplingrate") ?? "0", - ), - channels: int.tryParse(element.getAttribute("channels") ?? "0"), - duration: int.tryParse(element.getAttribute("duration") ?? "0"), - height: int.tryParse(element.getAttribute("height") ?? "0"), - width: int.tryParse(element.getAttribute("width") ?? "0"), - lang: element.getAttribute("lang"), - ); - } + factory Content.parse(XmlElement element) => Content( + url: element.getAttribute("url"), + type: element.getAttribute("type"), + fileSize: int.tryParse(element.getAttribute("fileSize") ?? "0"), + medium: element.getAttribute("medium"), + isDefault: element.getAttribute("isDefault") == "true", + expression: element.getAttribute("expression"), + bitrate: int.tryParse(element.getAttribute("bitrate") ?? "0"), + framerate: double.tryParse(element.getAttribute("framerate") ?? "0"), + samplingrate: double.tryParse(element.getAttribute("samplingrate") ?? "0"), + channels: int.tryParse(element.getAttribute("channels") ?? "0"), + duration: int.tryParse(element.getAttribute("duration") ?? "0"), + height: int.tryParse(element.getAttribute("height") ?? "0"), + width: int.tryParse(element.getAttribute("width") ?? "0"), + lang: element.getAttribute("lang"), + ); } diff --git a/lib/domain/media/copyright.dart b/lib/domain/media/copyright.dart index 1cad059..7c5493a 100644 --- a/lib/domain/media/copyright.dart +++ b/lib/domain/media/copyright.dart @@ -10,10 +10,8 @@ class Copyright { }); factory Copyright.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Copyright( + if (element == null) return null; + return Copyright( url: element.getAttribute("url"), value: element.text, ); diff --git a/lib/domain/media/credit.dart b/lib/domain/media/credit.dart index 70a5e50..ccd3534 100644 --- a/lib/domain/media/credit.dart +++ b/lib/domain/media/credit.dart @@ -11,11 +11,9 @@ class Credit { this.value, }); - factory Credit.parse(XmlElement element) { - return new Credit( - role: element.getAttribute("role"), - scheme: element.getAttribute("scheme"), - value: element.text, - ); - } + factory Credit.parse(XmlElement element) => Credit( + role: element.getAttribute("role"), + scheme: element.getAttribute("scheme"), + value: element.text, + ); } diff --git a/lib/domain/media/description.dart b/lib/domain/media/description.dart index ba6f0e0..9e04ae1 100644 --- a/lib/domain/media/description.dart +++ b/lib/domain/media/description.dart @@ -10,10 +10,8 @@ class Description { }); factory Description.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Description( + if (element == null) return null; + return Description( type: element.getAttribute("type"), value: element.text, ); diff --git a/lib/domain/media/embed.dart b/lib/domain/media/embed.dart index 1cca476..8f4932f 100644 --- a/lib/domain/media/embed.dart +++ b/lib/domain/media/embed.dart @@ -15,16 +15,12 @@ class Embed { }); factory Embed.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Embed( + if (element == null) return null; + return Embed( url: element.getAttribute("url"), width: int.tryParse(element.getAttribute("width") ?? "0"), height: int.tryParse(element.getAttribute("height") ?? "0"), - params: element.findElements("media:param").map((e) { - return new Param.parse(e); - }).toList(), + params: element.findElements("media:param").map((e) => Param.parse(e)).toList(), ); } } diff --git a/lib/domain/media/group.dart b/lib/domain/media/group.dart index 1a9a1cb..b0ae70f 100644 --- a/lib/domain/media/group.dart +++ b/lib/domain/media/group.dart @@ -19,22 +19,13 @@ class Group { }); factory Group.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Group( - contents: element.findElements("media:content").map((e) { - return new Content.parse(e); - }).toList(), - credits: element.findElements("media:credit").map((e) { - return new Credit.parse(e); - }).toList(), - category: new Category.parse( - findElementOrNull(element, "media:category"), - ), - rating: new Rating.parse( - findElementOrNull(element, "media:rating"), - ), + if (element == null) return null; + + return Group( + contents: element.findElements("media:content").map((e) => Content.parse(e)).toList(), + credits: element.findElements("media:credit").map((e) => Credit.parse(e)).toList(), + category: Category.parse(findElementOrNull(element, "media:category")), + rating: Rating.parse(findElementOrNull(element, "media:rating")), ); } } diff --git a/lib/domain/media/hash.dart b/lib/domain/media/hash.dart index 72ce859..adc5ac9 100644 --- a/lib/domain/media/hash.dart +++ b/lib/domain/media/hash.dart @@ -10,10 +10,8 @@ class Hash { }); factory Hash.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Hash( + if (element == null) return null; + return Hash( algo: element.getAttribute("algo"), value: element.text, ); diff --git a/lib/domain/media/license.dart b/lib/domain/media/license.dart index c52a8c6..d4882f3 100644 --- a/lib/domain/media/license.dart +++ b/lib/domain/media/license.dart @@ -12,10 +12,8 @@ class License { }); factory License.parse(XmlElement element) { - if (element == null) { - return null; - } - return new License( + if (element == null) return null; + return License( type: element.getAttribute("type"), href: element.getAttribute("href"), value: element.text, diff --git a/lib/domain/media/media.dart b/lib/domain/media/media.dart index 2f685c1..6c5c63f 100644 --- a/lib/domain/media/media.dart +++ b/lib/domain/media/media.dart @@ -78,92 +78,32 @@ class Media { }); factory Media.parse(XmlElement element) { - return new Media( - group: new Group.parse( - findElementOrNull(element, "media:group"), - ), - contents: element.findElements("media:content").map((e) { - return new Content.parse(e); - }).toList(), - credits: element.findElements("media:credit").map((e) { - return new Credit.parse(e); - }).toList(), - category: new Category.parse( - findElementOrNull(element, "media:category"), - ), - rating: new Rating.parse( - findElementOrNull(element, "media:rating"), - ), - title: new Title.parse( - findElementOrNull(element, "media:title"), - ), - description: new Description.parse( - findElementOrNull(element, "media:description"), - ), - keywords: findElementOrNull(element, "media:keywords")?.text, - thumbnails: element.findElements("media:thumbnail").map((e) { - return new Thumbnail.parse(e); - }).toList(), - hash: new Hash.parse( - findElementOrNull(element, "media:hash"), - ), - player: new Player.parse( - findElementOrNull(element, "media:player"), - ), - copyright: new Copyright.parse( - findElementOrNull(element, "media:copyright"), - ), - text: new Text.parse( - findElementOrNull(element, "media:text"), - ), - restriction: new Restriction.parse( - findElementOrNull(element, "media:restriction"), - ), - community: new Community.parse( - findElementOrNull(element, "media:community"), - ), - comments: findElementOrNull(element, "media:comments") - ?.findElements("media:comment") - ?.map((e) { - return e.text; - })?.toList() ?? - [], - embed: new Embed.parse( - findElementOrNull(element, "media:embed"), - ), - responses: findElementOrNull(element, "media:responses") - ?.findElements("media:response") - ?.map((e) { - return e.text; - })?.toList() ?? - [], - backLinks: findElementOrNull(element, "media:backLinks") - ?.findElements("media:backLink") - ?.map((e) { - return e.text; - })?.toList() ?? - [], - status: new Status.parse( - findElementOrNull(element, "media:status"), - ), - prices: element.findElements("media:price").map((e) { - return new Price.parse(e); - }).toList(), - license: new License.parse( - findElementOrNull(element, "media:license"), - ), - peerLink: new PeerLink.parse( - findElementOrNull(element, "media:peerLink"), - ), - rights: new Rights.parse( - findElementOrNull(element, "media:rights"), - ), - scenes: findElementOrNull(element, "media:scenes") - ?.findElements("media:scene") - ?.map((e) { - return new Scene.parse(e); - })?.toList() ?? - [], + return Media( + group: Group.parse(findElementOrNull(element, "media:group")), + contents: element.findElements("media:content").map((e) => Content.parse(e)).toList(), + credits: element.findElements("media:credit").map((e) => Credit.parse(e)).toList(), + category: Category.parse(findElementOrNull(element, "media:category")), + rating: Rating.parse(findElementOrNull(element, "media:rating")), + title: Title.parse(findElementOrNull(element, "media:title")), + description: Description.parse(findElementOrNull(element, "media:description")), + keywords: parseTextLiteral(element, "media:keywords"), + thumbnails: element.findElements("media:thumbnail").map((e) => Thumbnail.parse(e)).toList(), + hash: Hash.parse(findElementOrNull(element, "media:hash")), + player: Player.parse(findElementOrNull(element, "media:player")), + copyright: Copyright.parse(findElementOrNull(element, "media:copyright")), + text: Text.parse(findElementOrNull(element, "media:text")), + restriction: Restriction.parse(findElementOrNull(element, "media:restriction")), + community: Community.parse(findElementOrNull(element, "media:community")), + comments: findElementOrNull(element, "media:comments")?.findElements("media:comment")?.map((e) => e.text)?.toList() ?? [], + embed: Embed.parse(findElementOrNull(element, "media:embed")), + responses: findElementOrNull(element, "media:responses")?.findElements("media:response")?.map((e) => e.text)?.toList() ?? [], + backLinks: findElementOrNull(element, "media:backLinks")?.findElements("media:backLink")?.map((e) => e.text)?.toList() ?? [], + status: Status.parse(findElementOrNull(element, "media:status")), + prices: element.findElements("media:price").map((e) => Price.parse(e)).toList(), + license: License.parse(findElementOrNull(element, "media:license")), + peerLink: PeerLink.parse(findElementOrNull(element, "media:peerLink")), + rights: Rights.parse(findElementOrNull(element, "media:rights")), + scenes: findElementOrNull(element, "media:scenes")?.findElements("media:scene")?.map((e) => Scene.parse(e))?.toList() ?? [], ); } } diff --git a/lib/domain/media/param.dart b/lib/domain/media/param.dart index 175e6b8..93e0952 100644 --- a/lib/domain/media/param.dart +++ b/lib/domain/media/param.dart @@ -10,10 +10,8 @@ class Param { }); factory Param.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Param( + if (element == null) return null; + return Param( name: element.getAttribute("name"), value: element.text, ); diff --git a/lib/domain/media/peer_link.dart b/lib/domain/media/peer_link.dart index 6c51a17..cba2cf7 100644 --- a/lib/domain/media/peer_link.dart +++ b/lib/domain/media/peer_link.dart @@ -12,10 +12,8 @@ class PeerLink { }); factory PeerLink.parse(XmlElement element) { - if (element == null) { - return null; - } - return new PeerLink( + if (element == null) return null; + return PeerLink( type: element.getAttribute("type"), href: element.getAttribute("href"), value: element.text, diff --git a/lib/domain/media/player.dart b/lib/domain/media/player.dart index 23d5e7a..c7464bf 100644 --- a/lib/domain/media/player.dart +++ b/lib/domain/media/player.dart @@ -14,10 +14,8 @@ class Player { }); factory Player.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Player( + if (element == null) return null; + return Player( url: element.getAttribute("url"), width: int.tryParse(element.getAttribute("width") ?? "0"), height: int.tryParse(element.getAttribute("height") ?? "0"), diff --git a/lib/domain/media/price.dart b/lib/domain/media/price.dart index 6da075b..6b4313a 100644 --- a/lib/domain/media/price.dart +++ b/lib/domain/media/price.dart @@ -13,12 +13,10 @@ class Price { this.currency, }); - factory Price.parse(XmlElement element) { - return new Price( - price: double.tryParse(element.getAttribute("price") ?? "0"), - type: element.getAttribute("type"), - info: element.getAttribute("info"), - currency: element.getAttribute("currency"), - ); - } + factory Price.parse(XmlElement element) => Price( + price: double.tryParse(element.getAttribute("price") ?? "0"), + type: element.getAttribute("type"), + info: element.getAttribute("info"), + currency: element.getAttribute("currency"), + ); } diff --git a/lib/domain/media/rating.dart b/lib/domain/media/rating.dart index 77c2b12..cab9641 100644 --- a/lib/domain/media/rating.dart +++ b/lib/domain/media/rating.dart @@ -10,10 +10,8 @@ class Rating { }); factory Rating.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Rating( + if (element == null) return null; + return Rating( scheme: element.getAttribute("scheme"), value: element.text, ); diff --git a/lib/domain/media/restriction.dart b/lib/domain/media/restriction.dart index 4aa56bd..d527120 100644 --- a/lib/domain/media/restriction.dart +++ b/lib/domain/media/restriction.dart @@ -12,10 +12,8 @@ class Restriction { }); factory Restriction.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Restriction( + if (element == null) return null; + return Restriction( relationship: element.getAttribute("relationship"), type: element.getAttribute("type"), value: element.text, diff --git a/lib/domain/media/rights.dart b/lib/domain/media/rights.dart index eb00d4d..9831cde 100644 --- a/lib/domain/media/rights.dart +++ b/lib/domain/media/rights.dart @@ -8,10 +8,8 @@ class Rights { }); factory Rights.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Rights( + if (element == null) return null; + return Rights( status: element.getAttribute("status"), ); } diff --git a/lib/domain/media/scene.dart b/lib/domain/media/scene.dart index 9eab8d0..29e15dd 100644 --- a/lib/domain/media/scene.dart +++ b/lib/domain/media/scene.dart @@ -15,14 +15,12 @@ class Scene { }); factory Scene.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Scene( - title: findElementOrNull(element, "sceneTitle")?.text, - description: findElementOrNull(element, "sceneDescription")?.text, - startTime: findElementOrNull(element, "sceneStartTime")?.text, - endTime: findElementOrNull(element, "sceneEndTime")?.text, + if (element == null) return null; + return Scene( + title: parseTextLiteral(element, "sceneTitle"), + description: parseTextLiteral(element, "sceneDescription"), + startTime: parseTextLiteral(element, "sceneStartTime"), + endTime: parseTextLiteral(element, "sceneEndTime"), ); } } diff --git a/lib/domain/media/star_rating.dart b/lib/domain/media/star_rating.dart index ae4c400..7a68ee5 100644 --- a/lib/domain/media/star_rating.dart +++ b/lib/domain/media/star_rating.dart @@ -13,12 +13,10 @@ class StarRating { this.max, }); - factory StarRating.parse(XmlElement element) { - return new StarRating( - average: double.tryParse(element.getAttribute("average") ?? "0"), - count: int.tryParse(element.getAttribute("count") ?? "0"), - min: int.tryParse(element.getAttribute("min") ?? "0"), - max: int.tryParse(element.getAttribute("max") ?? "0"), - ); - } + factory StarRating.parse(XmlElement element) => StarRating( + average: double.tryParse(element.getAttribute("average") ?? "0"), + count: int.tryParse(element.getAttribute("count") ?? "0"), + min: int.tryParse(element.getAttribute("min") ?? "0"), + max: int.tryParse(element.getAttribute("max") ?? "0"), + ); } diff --git a/lib/domain/media/statistics.dart b/lib/domain/media/statistics.dart index d93461f..5756f20 100644 --- a/lib/domain/media/statistics.dart +++ b/lib/domain/media/statistics.dart @@ -9,10 +9,8 @@ class Statistics { this.favorites, }); - factory Statistics.parse(XmlElement element) { - return new Statistics( - views: int.tryParse(element.getAttribute("views") ?? "0"), - favorites: int.tryParse(element.getAttribute("favorites") ?? "0"), - ); - } + factory Statistics.parse(XmlElement element) => Statistics( + views: int.tryParse(element.getAttribute("views") ?? "0"), + favorites: int.tryParse(element.getAttribute("favorites") ?? "0"), + ); } diff --git a/lib/domain/media/status.dart b/lib/domain/media/status.dart index 3071a09..aef25c2 100644 --- a/lib/domain/media/status.dart +++ b/lib/domain/media/status.dart @@ -10,10 +10,8 @@ class Status { }); factory Status.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Status( + if (element == null) return null; + return Status( state: element.getAttribute("state"), reason: element.getAttribute("reason"), ); diff --git a/lib/domain/media/tags.dart b/lib/domain/media/tags.dart index c7001a7..2e1b4d2 100644 --- a/lib/domain/media/tags.dart +++ b/lib/domain/media/tags.dart @@ -10,10 +10,8 @@ class Tags { }); factory Tags.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Tags( + if (element == null) return null; + return Tags( tags: element.text, weight: int.tryParse(element.getAttribute("weight") ?? "1"), ); diff --git a/lib/domain/media/text.dart b/lib/domain/media/text.dart index 1ff886b..452abf8 100644 --- a/lib/domain/media/text.dart +++ b/lib/domain/media/text.dart @@ -16,10 +16,8 @@ class Text { }); factory Text.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Text( + if (element == null) return null; + return Text( type: element.getAttribute("type"), lang: element.getAttribute("lang"), start: element.getAttribute("start"), diff --git a/lib/domain/media/thumbnail.dart b/lib/domain/media/thumbnail.dart index 8adce3b..9da9322 100644 --- a/lib/domain/media/thumbnail.dart +++ b/lib/domain/media/thumbnail.dart @@ -13,12 +13,10 @@ class Thumbnail { this.time, }); - factory Thumbnail.parse(XmlElement element) { - return new Thumbnail( - url: element.getAttribute("url"), - width: element.getAttribute("width"), - height: element.getAttribute("height"), - time: element.getAttribute("time"), - ); - } + factory Thumbnail.parse(XmlElement element) => Thumbnail( + url: element.getAttribute("url"), + width: element.getAttribute("width"), + height: element.getAttribute("height"), + time: element.getAttribute("time"), + ); } diff --git a/lib/domain/media/title.dart b/lib/domain/media/title.dart index 8c875a6..982cf98 100644 --- a/lib/domain/media/title.dart +++ b/lib/domain/media/title.dart @@ -10,10 +10,8 @@ class Title { }); factory Title.parse(XmlElement element) { - if (element == null) { - return null; - } - return new Title( + if (element == null) return null; + return Title( type: element.getAttribute("type"), value: element.text, ); diff --git a/lib/util/helpers.dart b/lib/util/helpers.dart index 179926c..5169ed0 100644 --- a/lib/util/helpers.dart +++ b/lib/util/helpers.dart @@ -2,8 +2,7 @@ import 'dart:core'; import 'package:xml/xml.dart'; -XmlElement findElementOrNull(XmlElement element, String name, - {String namespace}) { +XmlElement findElementOrNull(XmlElement element, String name, {String namespace}) { try { return element.findAllElements(name, namespace: namespace).first; } on StateError { @@ -11,8 +10,7 @@ XmlElement findElementOrNull(XmlElement element, String name, } } -List findAllDirectElementsOrNull(XmlElement element, String name, - {String namespace}) { +List findAllDirectElementsOrNull(XmlElement element, String name, {String namespace}) { try { return element.findElements(name, namespace: namespace).toList(); } on StateError { @@ -20,9 +18,27 @@ List findAllDirectElementsOrNull(XmlElement element, String name, } } -bool parseBoolLiteral(XmlElement element, String tagName) { - var v = findElementOrNull(element, tagName)?.text?.toLowerCase()?.trim(); - if (v == null) return null; - return ["yes", "true"].contains(v); +String parseTextLiteral(XmlElement element, String name, {String namespace}) { + var s = findElementOrNull(element, name, namespace: namespace)?.text; + return s == null || s.isEmpty ? null : s; } +bool parseBoolLiteral(XmlElement element, String name, {String namespace}) { + var s = parseTextLiteral(element, name, namespace: namespace)?.toLowerCase()?.trim(); + return s == null + ? null + : [ + "yes", + "true" + ].contains(s); +} + +Uri parseUriLiteral(XmlElement element, String name, {String namespace}) { + var s = parseTextLiteral(element, name, namespace: namespace); + return s == null ? null : Uri.parse(s); +} + +DateTime parseDateTimeLiteral(XmlElement element, String name, {String namespace}) { + var s = parseTextLiteral(element, name, namespace: namespace); + return s == null ? null : DateTime.parse(s); +} diff --git a/lib/webfeed.dart b/lib/webfeed.dart index 94c2632..a2f693e 100644 --- a/lib/webfeed.dart +++ b/lib/webfeed.dart @@ -1,4 +1,5 @@ export 'domain/atom_category.dart'; +export 'domain/atom_content.dart'; export 'domain/atom_feed.dart'; export 'domain/atom_generator.dart'; export 'domain/atom_item.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index b7bb403..1015ad0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: webfeed -version: 0.4.2 -description: webfeed is a dart package for parsing RSS and Atom feeds. Media & DublinCore namespaces are also supported. +version: 0.5.2 +description: webfeed is a dart package for parsing and generating RSS and Atom feeds. Media & DublinCore namespaces are also supported. author: Wito Chandra homepage: https://github.com/witochandra/webfeed environment: diff --git a/test/atom_test.dart b/test/atom_test.dart index e7ba377..fa99211 100644 --- a/test/atom_test.dart +++ b/test/atom_test.dart @@ -6,98 +6,98 @@ import 'package:webfeed/webfeed.dart'; void main() { test("parse Invalid.xml", () { - var xmlString = new File("test/xml/Invalid.xml").readAsStringSync(); + var xmlString = File("test/xml/Invalid.xml").readAsStringSync(); try { - new AtomFeed.parse(xmlString); + AtomFeed.parse(xmlString); fail("Should throw Argument Error"); } on ArgumentError {} }); test("parse Atom.xml", () { - var xmlString = new File("test/xml/Atom.xml").readAsStringSync(); + var xmlString = File("test/xml/Atom.xml").readAsStringSync(); - var feed = new AtomFeed.parse(xmlString); - - expect(feed.id, "foo-bar-id"); + var feed = AtomFeed.parse(xmlString); + expect(feed.id, Uri.parse("urn:foo:foo-bar-id")); expect(feed.title, "Foo bar news"); - expect(feed.updated, "2018-04-06T13:02:46Z"); + expect(feed.updated, DateTime.parse("2018-04-06T13:02:46Z")); expect(feed.links.length, 2); - expect(feed.links.first.rel, "foo"); + expect(feed.links.first.rel, "related"); expect(feed.links.first.type, "text/html"); expect(feed.links.first.hreflang, "en"); - expect(feed.links.first.href, "http://foo.bar.news/"); + expect(feed.links.first.href, Uri.parse("http://foo.bar.news/")); expect(feed.links.first.title, "Foo bar news html"); expect(feed.links.first.length, 1000); expect(feed.authors.length, 2); expect(feed.authors.first.name, "Alice"); - expect(feed.authors.first.uri, "http://foo.bar.news/people/alice"); + expect(feed.authors.first.uri, Uri.parse("http://foo.bar.news/people/alice")); expect(feed.authors.first.email, "alice@foo.bar.news"); expect(feed.contributors.length, 2); expect(feed.contributors.first.name, "Charlie"); - expect(feed.contributors.first.uri, "http://foo.bar.news/people/charlie"); + expect(feed.contributors.first.uri, Uri.parse("http://foo.bar.news/people/charlie")); expect(feed.contributors.first.email, "charlie@foo.bar.news"); expect(feed.categories.length, 2); expect(feed.categories.first.term, "foo category"); - expect(feed.categories.first.scheme, "this-is-foo-scheme"); + expect(feed.categories.first.scheme, "https://foo.com/this-is-foo-scheme"); expect(feed.categories.first.label, "this is foo label"); - expect(feed.generator.uri, "http://foo.bar.news/generator"); + expect(feed.generator.uri, Uri.parse("http://foo.bar.news/generator")); expect(feed.generator.version, "1.0"); expect(feed.generator.value, "Foo bar generator"); - expect(feed.icon, "http://foo.bar.news/icon.png"); - expect(feed.logo, "http://foo.bar.news/logo.png"); + expect(feed.icon, Uri.parse("http://foo.bar.news/icon.png")); + expect(feed.logo, Uri.parse("http://foo.bar.news/logo.png")); expect(feed.subtitle, "This is subtitle"); expect(feed.items.length, 2); var item = feed.items.first; - expect(item.id, "foo-bar-entry-id-1"); + expect(item.id, Uri.parse("urn:foo:foo-bar-entry-id-1")); expect(item.title, "Foo bar item 1"); - expect(item.updated, "2018-04-06T13:02:47Z"); + expect(item.updated, DateTime.parse("2018-04-06T13:02:47Z")); expect(item.authors.length, 2); expect(item.authors.first.name, "Ellie"); - expect(item.authors.first.uri, "http://foo.bar.news/people/ellie"); + expect(item.authors.first.uri, Uri.parse("http://foo.bar.news/people/ellie")); expect(item.authors.first.email, "ellie@foo.bar.news"); expect(item.links.length, 2); - expect(item.links.first.rel, "foo entry"); + expect(item.links.first.rel, "related"); expect(item.links.first.type, "text/html"); expect(item.links.first.hreflang, "en"); - expect(item.links.first.href, "http://foo.bar.news/entry"); + expect(item.links.first.href, Uri.parse("http://foo.bar.news/entry")); expect(item.links.first.title, "Foo bar news html"); expect(item.links.first.length, 1000); expect(item.categories.length, 2); expect(item.categories.first.term, "foo entry category"); - expect(item.categories.first.scheme, "this-is-foo-entry-scheme"); + expect(item.categories.first.scheme, "https://foo.com/this-is-foo-entry-scheme"); expect(item.categories.first.label, "this is foo entry label"); expect(item.contributors.length, 2); expect(item.contributors.first.name, "Gin"); - expect(item.contributors.first.uri, "http://foo.bar.news/people/gin"); + expect(item.contributors.first.uri, Uri.parse("http://foo.bar.news/people/gin")); expect(item.contributors.first.email, "gin@foo.bar.news"); - expect(item.published, "2018-04-06T13:02:49Z"); - expect(item.summary, "This is summary 1"); - expect(item.content, "This is content 1"); + expect(item.published, DateTime.parse("2018-04-06T13:02:49Z")); + expect(item.summary.text, "This is summary 1"); + expect(item.content.text, "This is content 1"); expect(item.rights, "This is rights 1"); }); - test("parse Atom-Media.xml", (){ - var xmlString = new File("test/xml/Atom-Media.xml").readAsStringSync(); - var feed = new AtomFeed.parse(xmlString); - expect(feed.id, "foo-bar-id"); + test("parse Atom-Media.xml", () { + var xmlString = File("test/xml/Atom-Media.xml").readAsStringSync(); + + var feed = AtomFeed.parse(xmlString); + expect(feed.id, Uri.parse("foo-bar-id")); expect(feed.title, "Foo bar news"); - expect(feed.updated, "2018-04-06T13:02:46Z"); + expect(feed.updated, DateTime.parse("2018-04-06T13:02:46Z")); expect(feed.items.length, 1); - + var item = feed.items.first; expect(item.media.group.contents.length, 5); expect(item.media.group.credits.length, 2); @@ -135,9 +135,9 @@ void main() { expect(item.media.description.type, "plain"); expect(item.media.description.value, "This was some really bizarre band I listened to as a young lad."); - + expect(item.media.keywords, "kitty, cat, big dog, yarn, fluffy"); - + expect(item.media.thumbnails.length, 2); var mediaThumbnail = item.media.thumbnails.first; expect(mediaThumbnail.url, "http://www.foo.com/keyframe1.jpg"); @@ -225,9 +225,9 @@ void main() { var feed = AtomFeed.parse(xmlString); - expect(feed.id, null); + expect(feed.id, Uri.parse('https://example.com')); expect(feed.title, null); - expect(feed.updated, null); + expect(feed.updated, DateTime.parse('1970-01-01T00:00:00-00:00')); expect(feed.links.length, 0); expect(feed.authors.length, 0); expect(feed.contributors.length, 0); @@ -237,20 +237,215 @@ void main() { expect(feed.logo, null); expect(feed.subtitle, null); - expect(feed.items.length, 1); - var item = feed.items.first; + expect(feed.items.length, 0); + }); - expect(item.authors.length, 0); + // RFC 5005: Feed Paging and Archiving + test("parse Atom-Page1.xml", () { + var xmlString = File("test/xml/Atom-Page1.xml").readAsStringSync(); - expect(item.links.length, 0); + var feed = AtomFeed.parse(xmlString); + var firstPage = feed.links.firstWhere((l) => l.rel == 'first', orElse: () => null); + var previousPage = feed.links.firstWhere((l) => l.rel == 'previous', orElse: () => null); + var nextPage = feed.links.firstWhere((l) => l.rel == 'next', orElse: () => null); + var lastPage = feed.links.firstWhere((l) => l.rel == 'last', orElse: () => null); + + expect(firstPage.href, Uri.parse('http://example.org/index.atom')); + expect(previousPage, null); + expect(nextPage.href, Uri.parse('http://example.org/index.atom?page=2')); + expect(lastPage.href, Uri.parse('http://example.org/index.atom?page=2')); + }); + + // RFC 5005: Feed Paging and Archiving + test("parse Atom-Page2.xml", () { + var xmlString = File("test/xml/Atom-Page2.xml").readAsStringSync(); + + var feed = AtomFeed.parse(xmlString); + var firstPage = feed.links.firstWhere((l) => l.rel == 'first', orElse: () => null); + var previousPage = feed.links.firstWhere((l) => l.rel == 'previous', orElse: () => null); + var nextPage = feed.links.firstWhere((l) => l.rel == 'next', orElse: () => null); + var lastPage = feed.links.firstWhere((l) => l.rel == 'last', orElse: () => null); + + expect(firstPage.href, Uri.parse('http://example.org/index.atom')); + expect(previousPage.href, Uri.parse('http://example.org/index.atom')); + expect(nextPage, null); + expect(lastPage.href, Uri.parse('http://example.org/index.atom?page=2')); + }); - expect(item.categories.length, 0); + test("generate Atom-Empty.xml", () { + var xmlString = File("test/xml/Atom-Empty.xml").readAsStringSync(); + var feed = AtomFeed(id: Uri.parse('https://example.com'), updated: DateTime.parse('1970-01-01T00:00:00-00:00')); + var xmlString2 = feed.toXml().toXmlString(pretty: true, indent: ' '); + expect(xmlString2, xmlString); + }); - expect(item.contributors.length, 0); + test("generate Atom.xml", () { + var xmlString = File("test/xml/Atom.xml").readAsStringSync(); + var feed = AtomFeed( + id: Uri.parse('urn:foo:foo-bar-id'), + title: 'Foo bar news', + updated: DateTime.parse('2018-04-06T13:02:46Z'), + icon: Uri.parse('http://foo.bar.news/icon.png'), + logo: Uri.parse('http://foo.bar.news/logo.png'), + subtitle: 'This is subtitle', + links: [ + AtomLink(rel: 'related', type: 'text/html', hreflang: 'en', href: Uri.parse('http://foo.bar.news/'), title: 'Foo bar news html', length: 1000), + AtomLink(rel: 'related', type: 'application/atom+xml', hreflang: 'pt', href: Uri.parse('http://foo.bar.news/feed.atom'), title: 'Foo bar news atom', length: 100), + ], + authors: [ + AtomPerson(name: 'Alice', uri: Uri.parse('http://foo.bar.news/people/alice'), email: 'alice@foo.bar.news'), + AtomPerson(name: 'Bob', uri: Uri.parse('http://foo.bar.news/people/bob'), email: 'bob@foo.bar.news'), + ], + contributors: [ + AtomPerson(name: 'Charlie', uri: Uri.parse('http://foo.bar.news/people/charlie'), email: 'charlie@foo.bar.news'), + AtomPerson(name: 'David', uri: Uri.parse('http://foo.bar.news/people/david'), email: 'david@foo.bar.news'), + ], + categories: [ + AtomCategory(term: 'foo category', scheme: 'https://foo.com/this-is-foo-scheme', label: 'this is foo label'), + AtomCategory(term: 'bar category', scheme: 'https://foo.com/this-is-bar-scheme', label: 'this is bar label'), + ], + generator: AtomGenerator(uri: Uri.parse('http://foo.bar.news/generator'), version: '1.0', value: 'Foo bar generator'), + items: [ + AtomItem( + id: Uri.parse('urn:foo:foo-bar-entry-id-1'), + title: 'Foo bar item 1', + updated: DateTime.parse('2018-04-06T13:02:47Z'), + published: DateTime.parse('2018-04-06T13:02:49Z'), + summary: AtomContent(text: 'This is summary 1'), + content: AtomContent(text: 'This is content 1'), + rights: 'This is rights 1', + authors: [ + AtomPerson(name: 'Ellie', uri: Uri.parse('http://foo.bar.news/people/ellie'), email: 'ellie@foo.bar.news'), + AtomPerson(name: 'Franz', uri: Uri.parse('http://foo.bar.news/people/franz'), email: 'franz@foo.bar.news'), + ], + links: [ + AtomLink(rel: 'related', type: 'text/html', hreflang: 'en', href: Uri.parse('http://foo.bar.news/entry'), title: 'Foo bar news html', length: 1000), + AtomLink(rel: 'related', type: 'application/atom+xml', hreflang: 'pt', href: Uri.parse('http://foo.bar.news/entry/feed.atom'), title: 'Foo bar entry atom', length: 100), + ], + categories: [ + AtomCategory(term: 'foo entry category', scheme: 'https://foo.com/this-is-foo-entry-scheme', label: 'this is foo entry label'), + AtomCategory(term: 'bar entry category', scheme: 'https://foo.com/this-is-bar-entry-scheme', label: 'this is bar entry label'), + ], + contributors: [ + AtomPerson(name: 'Gin', uri: Uri.parse('http://foo.bar.news/people/gin'), email: 'gin@foo.bar.news'), + AtomPerson(name: 'Hanz', uri: Uri.parse('http://foo.bar.news/people/hanz'), email: 'hanz@foo.bar.news'), + ], + source: AtomSource( + id: Uri.parse('http://foo.bar.news/source'), + title: 'Foo bar source', + updated: DateTime.parse('2018-04-06T13:02:48Z'), + ), + ), + AtomItem( + id: Uri.parse('urn:foo:foo-bar-entry-id-2'), + title: 'Foo bar item 2', + updated: DateTime.parse('2018-04-06T13:02:50Z'), + published: DateTime.parse('2018-04-06T13:02:52Z'), + summary: AtomContent(text: 'This is summary 2'), + content: AtomContent(text: 'This is content 2'), + rights: 'This is rights 2', + authors: [ + AtomPerson( + name: 'Iris', + uri: Uri.parse('http://foo.bar.news/people/iris'), + email: 'iris@foo.bar.news', + ), + AtomPerson( + name: 'Jhon', + uri: Uri.parse('http://foo.bar.news/people/jhon'), + email: 'jhon@foo.bar.news', + ), + ], + links: [ + AtomLink(rel: 'related', type: 'text/html', hreflang: 'en', href: Uri.parse('http://foo.bar.news/entry'), title: 'Foo bar news html', length: 1000), + AtomLink(rel: 'related', type: 'application/atom+xml', hreflang: 'pt', href: Uri.parse('http://foo.bar.news/entry/feed.atom'), title: 'Foo bar entry atom', length: 100), + ], + categories: [ + AtomCategory(term: 'foo entry category'), + AtomCategory(term: 'bar entry category'), + ], + contributors: [ + AtomPerson(name: 'Kevin', uri: Uri.parse('http://foo.bar.news/people/kevin'), email: 'kevin@foo.bar.news'), + AtomPerson(name: 'Lucy', uri: Uri.parse('http://foo.bar.news/people/lucy'), email: 'lucy@foo.bar.news'), + ], + source: AtomSource( + id: Uri.parse('http://foo.bar.news/source'), + title: 'Foo bar source', + updated: DateTime.parse('2018-04-06T13:02:51Z'), + ), + ), + ], + ); + + var xmlString2 = feed.toXml().toXmlString(pretty: true, indent: ' '); + expect(xmlString2, xmlString); + }); + + // RFC 5005: Feed Paging and Archiving + test("generate Atom-Page1.xml", () { + var xmlString = File("test/xml/Atom-Page1.xml").readAsStringSync(); + + var feed = AtomFeed( + title: 'Example Feed', + links: [ + AtomLink(href: Uri.parse('http://example.org/')), + AtomLink(rel: 'first', href: Uri.parse('http://example.org/index.atom')), + AtomLink(rel: 'next', href: Uri.parse('http://example.org/index.atom?page=2')), + AtomLink(rel: 'last', href: Uri.parse('http://example.org/index.atom?page=2')), + ], + updated: DateTime.parse('2003-12-13T18:30:02Z'), + authors: [ + AtomPerson(name: 'John Doe'), + ], + id: Uri.parse('urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6'), + items: [ + AtomItem( + title: 'Atom-Powered Robots Run Amok', + links: [ + AtomLink(href: Uri.parse('http://example.org/2003/12/13/atom03')), + ], + id: Uri.parse('urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a'), + updated: DateTime.parse('2003-12-13T18:30:02Z'), + summary: AtomContent(text: 'Some text.'), + ), + ], + ); + + var xmlString2 = feed.toXml().toXmlString(pretty: true, indent: ' '); + expect(xmlString2, xmlString); + }); - expect(item.published, null); - expect(item.summary, null); - expect(item.content, null); - expect(item.rights, null); + // RFC 5005: Feed Paging and Archiving + test("generate Atom-Page2.xml", () { + var xmlString = File("test/xml/Atom-Page2.xml").readAsStringSync(); + + var feed = AtomFeed( + title: 'Example Feed', + links: [ + AtomLink(href: Uri.parse('http://example.org/')), + AtomLink(rel: 'first', href: Uri.parse('http://example.org/index.atom')), + AtomLink(rel: 'previous', href: Uri.parse('http://example.org/index.atom')), + AtomLink(rel: 'last', href: Uri.parse('http://example.org/index.atom?page=2')), + ], + updated: DateTime.parse('2003-12-13T18:30:02Z'), + authors: [ + AtomPerson(name: 'John Doe'), + ], + id: Uri.parse('urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6'), + items: [ + AtomItem( + title: 'Atom-Powered Robots Run Amok', + links: [ + AtomLink(href: Uri.parse('http://example.org/2003/12/13/atom03')), + ], + id: Uri.parse('urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a'), + updated: DateTime.parse('2003-12-13T18:30:02Z'), + summary: AtomContent(text: 'Some text.'), + ), + ], + ); + + var xmlString2 = feed.toXml().toXmlString(pretty: true, indent: ' '); + expect(xmlString2, xmlString); }); } diff --git a/test/rss_test.dart b/test/rss_test.dart index 5b14dfc..4c9ac13 100644 --- a/test/rss_test.dart +++ b/test/rss_test.dart @@ -22,8 +22,7 @@ void main() { var feed = new RssFeed.parse(xmlString); expect(feed.title, "News - Foo bar News"); - expect(feed.description, - "Foo bar News and Updates feed provided by Foo bar, Inc."); + expect(feed.description, "Foo bar News and Updates feed provided by Foo bar, Inc."); expect(feed.link, "https://foo.bar.news/"); expect(feed.author, "hello@world.net"); expect(feed.language, "en-US"); @@ -66,10 +65,8 @@ void main() { 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.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"); @@ -79,23 +76,19 @@ void main() { expect(feed.items.first.source.url, "https://foo.bar.news/1?source"); expect(feed.items.first.source.value, "Foo Bar"); expect(feed.items.first.comments, "https://foo.bar.news/1/comments"); - expect(feed.items.first.enclosure.url, - "http://www.scripting.com/mp3s/weatherReportSuite.mp3"); + expect(feed.items.first.enclosure.url, "http://www.scripting.com/mp3s/weatherReportSuite.mp3"); expect(feed.items.first.enclosure.length, 12216320); expect(feed.items.first.enclosure.type, "audio/mpeg"); - expect(feed.items.first.content.value, - " Test content
"); - expect( - feed.items.first.content.images.first, "https://test.com/image_link"); + expect(feed.items.first.content.value, " Test content
"); + expect(feed.items.first.content.images.first, "https://test.com/image_link"); }); test("parse RSS-Media.xml", () { var xmlString = new File("test/xml/RSS-Media.xml").readAsStringSync(); var feed = new RssFeed.parse(xmlString); expect(feed.title, "Song Site"); - expect( - feed.description, "Media RSS example with new fields added in v1.5.0"); + expect(feed.description, "Media RSS example with new fields added in v1.5.0"); expect(feed.items.length, 1); @@ -128,8 +121,7 @@ void main() { expect(mediaCredit.scheme, "urn:yvs"); expect(mediaCredit.value, "copyright holder of the entity"); - expect(item.media.category.scheme, - "http://search.yahoo.com/mrss/category_ schema"); + expect(item.media.category.scheme, "http://search.yahoo.com/mrss/category_ schema"); expect(item.media.category.label, "Music"); expect(item.media.category.value, "music/artist/album/song"); @@ -140,8 +132,7 @@ void main() { expect(item.media.title.value, "The Judy's -- The Moo Song"); expect(item.media.description.type, "plain"); - expect(item.media.description.value, - "This was some really bizarre band I listened to as a young lad."); + expect(item.media.description.value, "This was some really bizarre band I listened to as a young lad."); expect(item.media.keywords, "kitty, cat, big dog, yarn, fluffy"); @@ -191,8 +182,7 @@ void main() { expect(item.media.embed.height, 323); expect(item.media.embed.params.length, 5); expect(item.media.embed.params.first.name, "type"); - expect( - item.media.embed.params.first.value, "application/x-shockwave-flash"); + expect(item.media.embed.params.first.value, "application/x-shockwave-flash"); expect(item.media.responses.length, 2); expect(item.media.responses.first, "http://www.response1.com"); @@ -208,8 +198,7 @@ void main() { expect(item.media.prices.length, 2); expect(item.media.prices.first.price, 19.99); expect(item.media.prices.first.type, "rent"); - expect( - item.media.prices.first.info, "http://www.dummy.jp/package_info.html"); + expect(item.media.prices.first.info, "http://www.dummy.jp/package_info.html"); expect(item.media.prices.first.currency, "EUR"); expect(item.media.license.type, "text/html"); @@ -318,10 +307,8 @@ void main() { expect(feed.itunes.author, "Changelog Media"); expect(feed.itunes.summary, "Foo"); expect(feed.itunes.explicit, false); - expect(feed.itunes.image.href, - "https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357"); - expect(feed.itunes.keywords, - "go,golang,open source,software,development".split(",")); + expect(feed.itunes.image.href, "https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357"); + expect(feed.itunes.keywords, "go,golang,open source,software,development".split(",")); expect(feed.itunes.owner.name, "Changelog Media"); expect(feed.itunes.owner.email, "editors@changelog.com"); expect( @@ -329,14 +316,23 @@ void main() { feed.itunes.categories[0].category, feed.itunes.categories[1].category ]), - ["Technology", "Foo"]); + [ + "Technology", + "Foo" + ]); for (var category in feed.itunes.categories) { switch (category.category) { case "Foo": - expect(category.subCategories, ["Bar", "Baz"]); + expect(category.subCategories, [ + "Bar", + "Baz" + ]); break; case "Technology": - expect(category.subCategories, ["Software How-To", "Tech News"]); + expect(category.subCategories, [ + "Software How-To", + "Tech News" + ]); break; } } @@ -350,16 +346,13 @@ void main() { expect(item.itunes.episodeType, RssItunesEpisodeType.full); expect(item.itunes.episode, 1); expect(item.itunes.season, 1); - expect(item.itunes.image.href, - "https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357"); + expect(item.itunes.image.href, "https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357"); expect(item.itunes.duration, Duration(minutes: 32, seconds: 30)); expect(item.itunes.explicit, false); - expect(item.itunes.keywords, - "go,golang,open source,software,development".split(",")); + expect(item.itunes.keywords, "go,golang,open source,software,development".split(",")); expect(item.itunes.subtitle, "with Erik, Carlisia, and Brian"); expect(item.itunes.summary, "Foo"); - expect(item.itunes.author, - "Erik St. Martin, Carlisia Pinto, and Brian Ketelsen"); + expect(item.itunes.author, "Erik St. Martin, Carlisia Pinto, and Brian Ketelsen"); expect(item.itunes.explicit, false); expect(item.itunes.title, "awesome title"); expect(item.itunes.block, false); diff --git a/test/xml/Atom-Empty.xml b/test/xml/Atom-Empty.xml index 638fb04..9008a38 100644 --- a/test/xml/Atom-Empty.xml +++ b/test/xml/Atom-Empty.xml @@ -1,5 +1,6 @@ - - + https://example.com + + <updated>1970-01-01T00:00:00.000Z</updated> </feed> \ No newline at end of file diff --git a/test/xml/Atom-Page1.xml b/test/xml/Atom-Page1.xml new file mode 100644 index 0000000..cc817f4 --- /dev/null +++ b/test/xml/Atom-Page1.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<feed xmlns="http://www.w3.org/2005/Atom"> + <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id> + <title>Example Feed + 2003-12-13T18:30:02.000Z + + + + + + John Doe + + + urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a + Atom-Powered Robots Run Amok + 2003-12-13T18:30:02.000Z + + Some text. + + \ No newline at end of file diff --git a/test/xml/Atom-Page2.xml b/test/xml/Atom-Page2.xml new file mode 100644 index 0000000..3b56a3a --- /dev/null +++ b/test/xml/Atom-Page2.xml @@ -0,0 +1,20 @@ + + + urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 + Example Feed + 2003-12-13T18:30:02.000Z + + + + + + John Doe + + + urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a + Atom-Powered Robots Run Amok + 2003-12-13T18:30:02.000Z + + Some text. + + \ No newline at end of file diff --git a/test/xml/Atom.xml b/test/xml/Atom.xml index e88eb12..989517c 100644 --- a/test/xml/Atom.xml +++ b/test/xml/Atom.xml @@ -1,11 +1,10 @@ - foo-bar-id - Foo bar news - 2018-04-06T13:02:46Z - - + urn:foo:foo-bar-id + Foo bar news + 2018-04-06T13:02:46.000Z + + Alice http://foo.bar.news/people/alice @@ -26,16 +25,16 @@ http://foo.bar.news/people/david david@foo.bar.news - - + + Foo bar generator http://foo.bar.news/icon.png http://foo.bar.news/logo.png This is subtitle - foo-bar-entry-id-1 + urn:foo:foo-bar-entry-id-1 Foo bar item 1 - 2018-04-06T13:02:47Z + 2018-04-06T13:02:47.000Z Ellie http://foo.bar.news/people/ellie @@ -46,12 +45,10 @@ http://foo.bar.news/people/franz franz@foo.bar.news - - - - + + + + Gin http://foo.bar.news/people/gin @@ -65,18 +62,17 @@ http://foo.bar.news/source Foo bar source - 2018-04-06T13:02:48Z + 2018-04-06T13:02:48.000Z - - 2018-04-06T13:02:49Z - This is summary 1 + 2018-04-06T13:02:49.000Z + This is summary 1 This is content 1 This is rights 1 - foo-bar-entry-id-2 + urn:foo:foo-bar-entry-id-2 Foo bar item 2 - 2018-04-06T13:02:50Z + 2018-04-06T13:02:50.000Z Iris http://foo.bar.news/people/iris @@ -87,10 +83,8 @@ http://foo.bar.news/people/jhon jhon@foo.bar.news - - + + @@ -106,11 +100,10 @@ http://foo.bar.news/source Foo bar source - 2018-04-06T13:02:51Z + 2018-04-06T13:02:51.000Z - - 2018-04-06T13:02:52Z - This is summary 2 + 2018-04-06T13:02:52.000Z + This is summary 2 This is content 2 This is rights 2