diff --git a/lib/domain/dart_rss.dart b/lib/domain/dart_rss.dart index 6ace93f..ddfb60a 100644 --- a/lib/domain/dart_rss.dart +++ b/lib/domain/dart_rss.dart @@ -4,14 +4,25 @@ import 'package:dart_rss/domain/atom_feed.dart'; import 'package:dart_rss/domain/rss1_feed.dart'; import 'package:dart_rss/domain/rss_feed.dart'; import 'package:xml/xml.dart' as xml; +import 'package:intl/intl.dart'; extension SafeParseDateTime on DateTime { static DateTime safeParse(String str) { + const dateFormatPatterns = [ + 'EEE, d MMM yyyy HH:mm:ss Z', + ]; + try { return DateTime.parse(str); } catch (_) { - return null; + for (final pattern in dateFormatPatterns) { + try { + final format = DateFormat(pattern); + return format.parse(str); + } catch (_) {} + } } + return null; } } @@ -40,56 +51,15 @@ class WebFeed { switch (rssVersion) { case RssVersion.RSS1: final rss1Feed = Rss1Feed.parse(xmlString); - return WebFeed( - title: rss1Feed.title, - description: rss1Feed.description, - links: [rss1Feed.link], - items: rss1Feed.items - .map( - (item) => WebFeedItem( - title: item.title, - body: item.description ?? item.dc?.description, - updated: SafeParseDateTime.safeParse(item.dc?.date), - links: [item.link], - ), - ) - .toList(), - ); + return WebFeed.fromRss1(rss1Feed); break; case RssVersion.RSS2: final rss2Feed = RssFeed.parse(xmlString); - return WebFeed( - title: rss2Feed.title, - description: rss2Feed.description, - links: [rss2Feed.link], - items: rss2Feed.items - .map( - (item) => WebFeedItem( - title: item.title, - body: item.description ?? item.dc?.description, - updated: SafeParseDateTime.safeParse(item.dc?.date), - ), - ) - .toList(), - ); + return WebFeed.fromRss2(rss2Feed); break; case RssVersion.Atom: final atomFeed = AtomFeed.parse(xmlString); - return WebFeed( - title: atomFeed.title, - description: atomFeed.subtitle, - links: atomFeed.links.map((atomLink) => atomLink.href).toList(), - items: atomFeed.items - .map( - (item) => WebFeedItem( - title: item.title, - body: item.summary, - updated: SafeParseDateTime.safeParse(item.updated), - links: item.links.map((atomLink) => atomLink.href).toList(), - ), - ) - .toList(), - ); + return WebFeed.fromAtom(atomFeed); break; case RssVersion.Unknown: throw Error.safeToString( @@ -100,6 +70,62 @@ class WebFeed { } } + static WebFeed fromRss1(Rss1Feed rss1feed) { + return WebFeed( + title: rss1feed.title ?? rss1feed.dc.title ?? '', + description: rss1feed.description ?? rss1feed.dc?.description ?? '', + links: [rss1feed.link], + items: rss1feed.items + .map( + (item) => WebFeedItem( + title: item.title ?? item.dc?.title ?? '', + body: item.description ?? item.dc?.description ?? '', + updated: SafeParseDateTime.safeParse(item.dc?.date), + links: [item.link], + ), + ) + .toList(), + ); + } + + static WebFeed fromRss2(RssFeed rssFeed) { + return WebFeed( + title: rssFeed.title ?? rssFeed.dc.title ?? '', + description: rssFeed.description ?? rssFeed.dc.description ?? '', + links: [rssFeed.link], + items: rssFeed.items + .map( + (item) => WebFeedItem( + title: item.title ?? item.dc.title ?? '', + body: item.description ?? item.dc.description ?? '', + updated: SafeParseDateTime.safeParse(item.pubDate) ?? + SafeParseDateTime.safeParse(item.dc.date), + links: [item.link], + ), + ) + .toList(), + ); + } + + static WebFeed fromAtom(AtomFeed atomFeed) { + return WebFeed( + title: atomFeed.title, + description: atomFeed.subtitle, + links: atomFeed.links.map((atomLink) => atomLink.href).toList(), + items: atomFeed.items + .map( + (item) => WebFeedItem( + title: item.title, + body: item.summary ?? item.content, + updated: SafeParseDateTime.safeParse(item.updated) ?? + SafeParseDateTime.safeParse(item.published), + links: item.links.map((atomLink) => atomLink.href).toList(), + ), + ) + .toList(), + ); + } + static Future fromUrl(String url) async { final response = await http.get(url); return fromXmlString(response.body); diff --git a/pubspec.yaml b/pubspec.yaml index 7c8a427..76aaca0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,5 +8,7 @@ dependencies: xml: ^3.0.0 http: ^0.11.0 meta: ^1.0.0 + intl: any + dev_dependencies: test: ^1.0.0 diff --git a/test/real_rss_test.dart b/test/real_rss_test.dart new file mode 100644 index 0000000..0c68a00 --- /dev/null +++ b/test/real_rss_test.dart @@ -0,0 +1,27 @@ +import 'package:dart_rss/dart_rss.dart'; +import 'package:test/test.dart'; + +void main() { + group('Can this library use for real rss xml?', () { + const testFeeds = { + 'pub.dev feed': 'https://pub.dev/feed.atom', + 'Flutter Weekly': + 'https://us17.campaign-archive.com/feed?u=c8d8d18b6e2c6316ddc1d48a0&id=47548a283b', + 'jQuery blog': 'http://blog.jquery.com/feed/', // <- not use ISO8601 + 'hatena sample': 'https://b.hatena.ne.jp/sample/bookmark.rss', // <- RSS1 + }; + + for (final title in testFeeds.keys) { + final url = testFeeds[title]; + test(title, () async { + // given + final feed = await WebFeed.fromUrl(url); + + // then + expect(feed.title.isNotEmpty, true); + expect(feed.items.first.updated is DateTime, true); + expect(feed.items.first.links.isNotEmpty, true); + }); + } + }); +} diff --git a/test/safe_parse_date_time_test.dart b/test/safe_parse_date_time_test.dart new file mode 100644 index 0000000..72bf796 --- /dev/null +++ b/test/safe_parse_date_time_test.dart @@ -0,0 +1,23 @@ +import 'package:dart_rss/dart_rss.dart'; +import 'package:test/test.dart'; + +void main() { + group('about SafeParseDateTime, ', () { + test('it can parse ISO-8601', () { + final date = SafeParseDateTime.safeParse('2018-04-06T13:02:47Z'); + + expect(date.year, 2018); + expect(date.month, 4); + expect(date.day, 6); + }); + + test('it can parse American-English-Format', () { + final date = + SafeParseDateTime.safeParse('Tue, 02 Jul 2019 16:47:24 +0000'); + + expect(date.year, 2019); + expect(date.month, 7); + expect(date.day, 2); + }); + }); +} diff --git a/test/webfeed_test.dart b/test/webfeed_test.dart index 538d012..5cae9ff 100644 --- a/test/webfeed_test.dart +++ b/test/webfeed_test.dart @@ -91,7 +91,8 @@ void main() { rss2Feed.items.first.body, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', ); - expect(rss2Feed.items.first.updated, null); + expect(rss2Feed.items.first.updated, + DateTime.parse('2018-03-26 14:00:00.000')); }); }); }