From 648f324981208acc6db795b5d537f587d994664d Mon Sep 17 00:00:00 2001 From: Wito Chandra Date: Sun, 25 Apr 2021 23:51:43 +0700 Subject: [PATCH] refactor null safety migration --- example/main.dart | 12 +- lib/domain/atom_feed.dart | 25 +-- lib/domain/atom_generator.dart | 3 +- lib/domain/atom_item.dart | 21 +-- lib/domain/atom_person.dart | 8 +- lib/domain/atom_source.dart | 11 +- lib/domain/dublin_core/dublin_core.dart | 43 +++-- lib/domain/itunes/itunes.dart | 63 +++++--- lib/domain/itunes/itunes_category.dart | 7 +- lib/domain/itunes/itunes_episode_type.dart | 4 +- lib/domain/itunes/itunes_image.dart | 8 +- lib/domain/itunes/itunes_owner.dart | 8 +- lib/domain/itunes/itunes_type.dart | 4 +- lib/domain/media/category.dart | 3 +- lib/domain/media/community.dart | 26 +-- lib/domain/media/copyright.dart | 3 +- lib/domain/media/description.dart | 3 +- lib/domain/media/embed.dart | 12 +- lib/domain/media/group.dart | 33 ++-- lib/domain/media/hash.dart | 3 +- lib/domain/media/license.dart | 3 +- lib/domain/media/media.dart | 180 ++++++++++++--------- lib/domain/media/param.dart | 3 +- lib/domain/media/peer_link.dart | 3 +- lib/domain/media/player.dart | 3 +- lib/domain/media/rating.dart | 3 +- lib/domain/media/restriction.dart | 3 +- lib/domain/media/rights.dart | 3 +- lib/domain/media/scene.dart | 13 +- lib/domain/media/status.dart | 3 +- lib/domain/media/tags.dart | 3 +- lib/domain/media/text.dart | 3 +- lib/domain/media/title.dart | 3 +- lib/domain/rss_category.dart | 3 +- lib/domain/rss_cloud.dart | 3 +- lib/domain/rss_content.dart | 11 +- lib/domain/rss_enclosure.dart | 3 +- lib/domain/rss_feed.dart | 64 +++++--- lib/domain/rss_image.dart | 11 +- lib/domain/rss_item.dart | 33 ++-- lib/domain/rss_source.dart | 3 +- lib/domain/syndication/syndication.dart | 13 +- lib/util/function.dart | 0 lib/util/iterable.dart | 3 + lib/util/xml.dart | 17 +- test/atom_test.dart | 18 +-- test/rss_test.dart | 36 ++--- 47 files changed, 387 insertions(+), 357 deletions(-) create mode 100644 lib/util/function.dart create mode 100644 lib/util/iterable.dart diff --git a/example/main.dart b/example/main.dart index c03da46..5ec2b7f 100644 --- a/example/main.dart +++ b/example/main.dart @@ -1,18 +1,22 @@ -import 'package:http/http.dart' as http; +import 'dart:io'; + +import 'package:http/io_client.dart'; import 'package:webfeed/webfeed.dart'; void main() async { - var client = http.Client(); + final client = IOClient(HttpClient() + ..badCertificateCallback = + ((X509Certificate cert, String host, int port) => true)); // RSS feed var response = await client.get( - Uri(path: 'https://developer.apple.com/news/releases/rss/releases.rss')); + Uri.parse('https://developer.apple.com/news/releases/rss/releases.rss')); var channel = RssFeed.parse(response.body); print(channel); // Atom feed response = - await client.get(Uri(path: 'https://www.theverge.com/rss/index.xml')); + await client.get(Uri.parse('https://www.theverge.com/rss/index.xml')); var feed = AtomFeed.parse(response.body); print(feed); diff --git a/lib/domain/atom_feed.dart b/lib/domain/atom_feed.dart index cf01443..f48a3c0 100644 --- a/lib/domain/atom_feed.dart +++ b/lib/domain/atom_feed.dart @@ -4,7 +4,7 @@ import 'package:webfeed/domain/atom_item.dart'; import 'package:webfeed/domain/atom_link.dart'; import 'package:webfeed/domain/atom_person.dart'; import 'package:webfeed/util/datetime.dart'; -import 'package:webfeed/util/xml.dart'; +import 'package:webfeed/util/iterable.dart'; import 'package:xml/xml.dart'; class AtomFeed { @@ -41,15 +41,16 @@ class AtomFeed { factory AtomFeed.parse(String xmlString) { var document = XmlDocument.parse(xmlString); - var feedElement = findFirstElement(document, 'feed'); + var feedElement = document.findElements('feed').firstOrNull; if (feedElement == null) { throw ArgumentError('feed not found'); } return AtomFeed( - id: findFirstElement(feedElement, 'id')?.text, - title: findFirstElement(feedElement, 'title')?.text, - updated: parseDateTime(findFirstElement(feedElement, 'updated')?.text), + id: feedElement.findElements('id').firstOrNull?.text, + title: feedElement.findElements('title').firstOrNull?.text, + updated: + parseDateTime(feedElement.findElements('updated').firstOrNull?.text), items: feedElement .findElements('entry') .map((e) => AtomItem.parse(e)) @@ -70,12 +71,14 @@ class AtomFeed { .findElements('category') .map((e) => AtomCategory.parse(e)) .toList(), - generator: - AtomGenerator.parse(findFirstElement(feedElement, 'generator')), - icon: findFirstElement(feedElement, 'icon')?.text, - logo: findFirstElement(feedElement, 'logo')?.text, - rights: findFirstElement(feedElement, 'rights')?.text, - subtitle: findFirstElement(feedElement, 'subtitle')?.text, + generator: feedElement + .findElements('generator') + .map((e) => AtomGenerator.parse(e)) + .firstOrNull, + icon: feedElement.findElements('icon').firstOrNull?.text, + logo: feedElement.findElements('logo').firstOrNull?.text, + rights: feedElement.findElements('rights').firstOrNull?.text, + subtitle: feedElement.findElements('subtitle').firstOrNull?.text, ); } } diff --git a/lib/domain/atom_generator.dart b/lib/domain/atom_generator.dart index 97c866f..63377c0 100644 --- a/lib/domain/atom_generator.dart +++ b/lib/domain/atom_generator.dart @@ -7,8 +7,7 @@ class AtomGenerator { AtomGenerator(this.uri, this.version, this.value); - static parse(XmlElement? element) { - if (element == null) return null; + factory AtomGenerator.parse(XmlElement element) { var uri = element.getAttribute('uri'); var version = element.getAttribute('version'); var value = element.text; diff --git a/lib/domain/atom_item.dart b/lib/domain/atom_item.dart index 316027c..ff9681e 100644 --- a/lib/domain/atom_item.dart +++ b/lib/domain/atom_item.dart @@ -4,7 +4,7 @@ import 'package:webfeed/domain/atom_person.dart'; import 'package:webfeed/domain/atom_source.dart'; import 'package:webfeed/domain/media/media.dart'; import 'package:webfeed/util/datetime.dart'; -import 'package:webfeed/util/xml.dart'; +import 'package:webfeed/util/iterable.dart'; import 'package:xml/xml.dart'; class AtomItem { @@ -41,9 +41,9 @@ class AtomItem { factory AtomItem.parse(XmlElement element) { return AtomItem( - id: findFirstElement(element, 'id')?.text, - title: findFirstElement(element, 'title')?.text, - updated: parseDateTime(findFirstElement(element, 'updated')?.text), + id: element.findElements('id').firstOrNull?.text, + title: element.findElements('title').firstOrNull?.text, + updated: parseDateTime(element.findElements('updated').firstOrNull?.text), authors: element .findElements('author') .map((e) => AtomPerson.parse(e)) @@ -58,11 +58,14 @@ class AtomItem { .findElements('contributor') .map((e) => AtomPerson.parse(e)) .toList(), - source: AtomSource.parse(findFirstElement(element, 'source')), - published: findFirstElement(element, 'published')?.text, - content: findFirstElement(element, 'content')?.text, - summary: findFirstElement(element, 'summary')?.text, - rights: findFirstElement(element, 'rights')?.text, + source: element + .findElements('source') + .map((e) => AtomSource.parse(e)) + .firstOrNull, + published: element.findElements('published').firstOrNull?.text, + content: element.findElements('content').firstOrNull?.text, + summary: element.findElements('summary').firstOrNull?.text, + rights: element.findElements('rights').firstOrNull?.text, media: Media.parse(element), ); } diff --git a/lib/domain/atom_person.dart b/lib/domain/atom_person.dart index 7312eba..cac9457 100644 --- a/lib/domain/atom_person.dart +++ b/lib/domain/atom_person.dart @@ -1,4 +1,4 @@ -import 'package:webfeed/util/xml.dart'; +import 'package:webfeed/util/iterable.dart'; import 'package:xml/xml.dart'; class AtomPerson { @@ -10,9 +10,9 @@ class AtomPerson { factory AtomPerson.parse(XmlElement element) { return AtomPerson( - name: findFirstElement(element, 'name')?.text, - uri: findFirstElement(element, 'uri')?.text, - email: findFirstElement(element, 'email')?.text, + name: element.findElements('name').firstOrNull?.text, + uri: element.findElements('uri').firstOrNull?.text, + email: element.findElements('email').firstOrNull?.text, ); } } diff --git a/lib/domain/atom_source.dart b/lib/domain/atom_source.dart index caf0a15..f5f5f56 100644 --- a/lib/domain/atom_source.dart +++ b/lib/domain/atom_source.dart @@ -1,4 +1,4 @@ -import 'package:webfeed/util/xml.dart'; +import 'package:webfeed/util/iterable.dart'; import 'package:xml/xml.dart'; class AtomSource { @@ -12,12 +12,11 @@ class AtomSource { this.updated, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory AtomSource.parse(XmlElement element) { return AtomSource( - id: findFirstElement(element, 'id')?.text, - title: findFirstElement(element, 'title')?.text, - updated: findFirstElement(element, 'updated')?.text, + id: element.findElements('id').firstOrNull?.text, + title: element.findElements('title').firstOrNull?.text, + updated: element.findElements('updated').firstOrNull?.text, ); } } diff --git a/lib/domain/dublin_core/dublin_core.dart b/lib/domain/dublin_core/dublin_core.dart index ac12701..9cdfaa2 100644 --- a/lib/domain/dublin_core/dublin_core.dart +++ b/lib/domain/dublin_core/dublin_core.dart @@ -1,5 +1,5 @@ import 'package:webfeed/util/datetime.dart'; -import 'package:webfeed/util/xml.dart'; +import 'package:webfeed/util/iterable.dart'; import 'package:xml/xml.dart'; class DublinCore { @@ -41,28 +41,27 @@ class DublinCore { this.rights, }); - static parse(XmlElement? element) { - if (element == null) { - return null; - } + factory DublinCore.parse(XmlElement element) { return DublinCore( - title: findFirstElement(element, 'dc:title')?.text, - description: findFirstElement(element, 'dc:description')?.text, - creator: findFirstElement(element, 'dc:creator')?.text, - subject: findFirstElement(element, 'dc:subject')?.text, - publisher: findFirstElement(element, 'dc:publisher')?.text, - contributor: findFirstElement(element, 'dc:contributor')?.text, - date: parseDateTime(findFirstElement(element, 'dc:date')?.text), - created: parseDateTime(findFirstElement(element, 'dc:created')?.text), - modified: parseDateTime(findFirstElement(element, 'dc:modified')?.text), - type: findFirstElement(element, 'dc:type')?.text, - format: findFirstElement(element, 'dc:format')?.text, - identifier: findFirstElement(element, 'dc:identifier')?.text, - source: findFirstElement(element, 'dc:source')?.text, - language: findFirstElement(element, 'dc:language')?.text, - relation: findFirstElement(element, 'dc:relation')?.text, - coverage: findFirstElement(element, 'dc:coverage')?.text, - rights: findFirstElement(element, 'dc:rights')?.text, + title: element.findElements('dc:title').firstOrNull?.text, + description: element.findElements('dc:description').firstOrNull?.text, + creator: element.findElements('dc:creator').firstOrNull?.text, + subject: element.findElements('dc:subject').firstOrNull?.text, + publisher: element.findElements('dc:publisher').firstOrNull?.text, + contributor: element.findElements('dc:contributor').firstOrNull?.text, + date: parseDateTime(element.findElements('dc:date').firstOrNull?.text), + created: + parseDateTime(element.findElements('dc:created').firstOrNull?.text), + modified: + parseDateTime(element.findElements('dc:modified').firstOrNull?.text), + type: element.findElements('dc:type').firstOrNull?.text, + format: element.findElements('dc:format').firstOrNull?.text, + identifier: element.findElements('dc:identifier').firstOrNull?.text, + source: element.findElements('dc:source').firstOrNull?.text, + language: element.findElements('dc:language').firstOrNull?.text, + relation: element.findElements('dc:relation').firstOrNull?.text, + coverage: element.findElements('dc:coverage').firstOrNull?.text, + rights: element.findElements('dc:rights').firstOrNull?.text, ); } } diff --git a/lib/domain/itunes/itunes.dart b/lib/domain/itunes/itunes.dart index fd8a2be..965ec20 100644 --- a/lib/domain/itunes/itunes.dart +++ b/lib/domain/itunes/itunes.dart @@ -3,6 +3,7 @@ import 'package:webfeed/domain/itunes/itunes_episode_type.dart'; import 'package:webfeed/domain/itunes/itunes_image.dart'; import 'package:webfeed/domain/itunes/itunes_owner.dart'; import 'package:webfeed/domain/itunes/itunes_type.dart'; +import 'package:webfeed/util/iterable.dart'; import 'package:webfeed/util/xml.dart'; import 'package:xml/xml.dart'; @@ -15,7 +16,7 @@ class Itunes { final ItunesOwner? owner; final List? keywords; final ItunesImage? image; - final List? categories; + final List? categories; final ItunesType? type; final String? newFeedUrl; final bool? block; @@ -45,39 +46,51 @@ class Itunes { this.episodeType, }); - static parse(XmlElement? element) { - if (element == null) { - return null; - } - var episodeStr = findFirstElement(element, 'itunes:episode')?.text; - var seasonStr = findFirstElement(element, 'itunes:season')?.text; - var durationStr = findFirstElement(element, 'itunes:duration')?.text; + factory Itunes.parse(XmlElement element) { + final episodeStr = element.findElements('itunes:episode').firstOrNull?.text; + final seasonStr = element.findElements('itunes:season').firstOrNull?.text; + final durationStr = + element.findElements('itunes:duration').firstOrNull?.text; return Itunes( - author: findFirstElement(element, 'itunes:author')?.text, - summary: findFirstElement(element, 'itunes:summary')?.text, + author: element.findElements('itunes:author').firstOrNull?.text, + summary: element.findElements('itunes:summary').firstOrNull?.text, explicit: parseBoolLiteral(element, 'itunes:explicit'), - title: findFirstElement(element, 'itunes:title')?.text, - subtitle: findFirstElement(element, 'itunes:subtitle')?.text, - owner: ItunesOwner.parse(findFirstElement(element, 'itunes:owner')), - keywords: findFirstElement(element, 'itunes:keywords') + title: element.findElements('itunes:title').firstOrNull?.text, + subtitle: element.findElements('itunes:subtitle').firstOrNull?.text, + owner: element + .findElements('itunes:owner') + .map((e) => ItunesOwner.parse(e)) + .firstOrNull, + keywords: element + .findElements('itunes:keywords') + .firstOrNull ?.text .split(',') .map((keyword) => keyword.trim()) .toList() ?? [], - image: ItunesImage.parse(findFirstElement(element, 'itunes:image')), - categories: findElements(element, 'itunes:category')! + image: element + .findElements('itunes:image') + .map((e) => ItunesImage.parse(e)) + .firstOrNull, + categories: element + .findElements('itunes:category') .map((e) => ItunesCategory.parse(e)) .toList(), - type: newItunesType(findFirstElement(element, 'itunes:type')), - newFeedUrl: findFirstElement(element, 'itunes:new-feed-url')?.text, + type: element + .findElements('itunes:type') + .map((e) => newItunesType(e)) + .firstOrNull, + newFeedUrl: element.findElements('itunes:new-feed-url').firstOrNull?.text, block: parseBoolLiteral(element, 'itunes:block'), complete: parseBoolLiteral(element, 'itunes:complete'), - episode: episodeStr == null ? null : int.parse(episodeStr), - season: seasonStr == null ? null : int.parse(seasonStr), + episode: episodeStr == null ? null : int.tryParse(episodeStr), + season: seasonStr == null ? null : int.tryParse(seasonStr), duration: durationStr == null ? null : _parseDuration(durationStr), - episodeType: - newItunesEpisodeType(findFirstElement(element, 'itunes:episodeType')), + episodeType: element + .findElements('itunes:episodeType') + .map((e) => newItunesEpisodeType(e)) + .firstOrNull, ); } @@ -87,12 +100,12 @@ class Itunes { var seconds = 0; var parts = s.split(':'); if (parts.length > 2) { - hours = int.parse(parts[parts.length - 3]); + hours = int.tryParse(parts[parts.length - 3]) ?? 0; } if (parts.length > 1) { - minutes = int.parse(parts[parts.length - 2]); + minutes = int.tryParse(parts[parts.length - 2]) ?? 0; } - seconds = int.parse(parts[parts.length - 1]); + seconds = int.tryParse(parts[parts.length - 1]) ?? 0; return Duration( hours: hours, minutes: minutes, diff --git a/lib/domain/itunes/itunes_category.dart b/lib/domain/itunes/itunes_category.dart index a3228c3..8b7b4be 100644 --- a/lib/domain/itunes/itunes_category.dart +++ b/lib/domain/itunes/itunes_category.dart @@ -2,17 +2,16 @@ import 'package:xml/xml.dart'; class ItunesCategory { final String? category; - final List? subCategories; + final List? subCategories; ItunesCategory({this.category, this.subCategories}); - static ItunesCategory? parse(XmlElement? element) { - if (element == null) return null; + factory ItunesCategory.parse(XmlElement element) { return ItunesCategory( category: element.getAttribute('text')?.trim(), subCategories: element .findElements('itunes:category') - .map((e) => e.getAttribute('text')?.trim()) + .map((e) => e.getAttribute('text')?.trim() ?? '') .toList()); } } diff --git a/lib/domain/itunes/itunes_episode_type.dart b/lib/domain/itunes/itunes_episode_type.dart index 4144662..31e2ad9 100644 --- a/lib/domain/itunes/itunes_episode_type.dart +++ b/lib/domain/itunes/itunes_episode_type.dart @@ -2,8 +2,8 @@ import 'package:xml/xml.dart'; enum ItunesEpisodeType { full, trailer, bonus, unknown } -ItunesEpisodeType newItunesEpisodeType(XmlElement? element) { - switch (element?.text) { +ItunesEpisodeType newItunesEpisodeType(XmlElement element) { + switch (element.text) { case 'full': return ItunesEpisodeType.full; case 'trailer': diff --git a/lib/domain/itunes/itunes_image.dart b/lib/domain/itunes/itunes_image.dart index e4d9d74..065c098 100644 --- a/lib/domain/itunes/itunes_image.dart +++ b/lib/domain/itunes/itunes_image.dart @@ -5,13 +5,9 @@ class ItunesImage { ItunesImage({this.href}); - static parse(XmlElement? element) { - ItunesImage? result; - if (element == null) return null; - result = ItunesImage( + factory ItunesImage.parse(XmlElement element) { + return ItunesImage( href: element.getAttribute('href')?.trim(), ); - - return result; } } diff --git a/lib/domain/itunes/itunes_owner.dart b/lib/domain/itunes/itunes_owner.dart index 982925a..0a40578 100644 --- a/lib/domain/itunes/itunes_owner.dart +++ b/lib/domain/itunes/itunes_owner.dart @@ -1,4 +1,4 @@ -import 'package:webfeed/util/xml.dart'; +import 'package:webfeed/util/iterable.dart'; import 'package:xml/xml.dart'; class ItunesOwner { @@ -7,10 +7,10 @@ class ItunesOwner { ItunesOwner({this.name, this.email}); - factory ItunesOwner.parse(XmlElement? element) { + factory ItunesOwner.parse(XmlElement element) { return ItunesOwner( - name: findFirstElement(element, 'itunes:name')?.text.trim(), - email: findFirstElement(element, 'itunes:email')?.text.trim(), + name: element.findElements('itunes:name').firstOrNull?.text.trim(), + email: element.findElements('itunes:email').firstOrNull?.text.trim(), ); } } diff --git a/lib/domain/itunes/itunes_type.dart b/lib/domain/itunes/itunes_type.dart index df6a320..741b3e6 100644 --- a/lib/domain/itunes/itunes_type.dart +++ b/lib/domain/itunes/itunes_type.dart @@ -2,8 +2,8 @@ import 'package:xml/xml.dart'; enum ItunesType { episodic, serial, unknown } -ItunesType newItunesType(XmlElement? element) { - switch (element?.text) { +ItunesType newItunesType(XmlElement element) { + switch (element.text) { case 'episodic': return ItunesType.episodic; case 'serial': diff --git a/lib/domain/media/category.dart b/lib/domain/media/category.dart index f4bf79b..70b3d9a 100644 --- a/lib/domain/media/category.dart +++ b/lib/domain/media/category.dart @@ -11,8 +11,7 @@ class Category { this.value, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory Category.parse(XmlElement element) { return Category( scheme: element.getAttribute('scheme'), label: element.getAttribute('label'), diff --git a/lib/domain/media/community.dart b/lib/domain/media/community.dart index c3b1186..9c49c22 100644 --- a/lib/domain/media/community.dart +++ b/lib/domain/media/community.dart @@ -1,7 +1,7 @@ import 'package:webfeed/domain/media/star_rating.dart'; import 'package:webfeed/domain/media/statistics.dart'; import 'package:webfeed/domain/media/tags.dart'; -import 'package:webfeed/util/xml.dart'; +import 'package:webfeed/util/iterable.dart'; import 'package:xml/xml.dart'; class Community { @@ -15,18 +15,20 @@ class Community { this.tags, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory Community.parse(XmlElement element) { return Community( - starRating: StarRating.parse( - findFirstElement(element, 'media:starRating'), - ), - statistics: Statistics.parse( - findFirstElement(element, 'media:statistics'), - ), - tags: Tags.parse( - findFirstElement(element, 'media:tags'), - ), + starRating: element + .findElements('media:starRating') + .map((e) => StarRating.parse(e)) + .firstOrNull, + statistics: element + .findElements('media:statistics') + .map((e) => Statistics.parse(e)) + .firstOrNull, + tags: element + .findElements('media:tags') + .map((e) => Tags.parse(e)) + .firstOrNull, ); } } diff --git a/lib/domain/media/copyright.dart b/lib/domain/media/copyright.dart index 8857ee7..e1ec675 100644 --- a/lib/domain/media/copyright.dart +++ b/lib/domain/media/copyright.dart @@ -9,8 +9,7 @@ class Copyright { this.value, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory Copyright.parse(XmlElement element) { return Copyright( url: element.getAttribute('url'), value: element.text, diff --git a/lib/domain/media/description.dart b/lib/domain/media/description.dart index d572329..2e0aa82 100644 --- a/lib/domain/media/description.dart +++ b/lib/domain/media/description.dart @@ -9,8 +9,7 @@ class Description { this.value, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory Description.parse(XmlElement element) { return Description( type: element.getAttribute('type'), value: element.text, diff --git a/lib/domain/media/embed.dart b/lib/domain/media/embed.dart index 140c766..60d0b23 100644 --- a/lib/domain/media/embed.dart +++ b/lib/domain/media/embed.dart @@ -5,7 +5,7 @@ class Embed { final String? url; final int? width; final int? height; - final List? params; + final List? params; Embed({ this.url, @@ -14,15 +14,15 @@ class Embed { this.params, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory Embed.parse(XmlElement element) { 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 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 ba9e0e2..fddecef 100644 --- a/lib/domain/media/group.dart +++ b/lib/domain/media/group.dart @@ -2,7 +2,7 @@ import 'package:webfeed/domain/media/category.dart'; import 'package:webfeed/domain/media/content.dart'; import 'package:webfeed/domain/media/credit.dart'; import 'package:webfeed/domain/media/rating.dart'; -import 'package:webfeed/util/xml.dart'; +import 'package:webfeed/util/iterable.dart'; import 'package:xml/xml.dart'; class Group { @@ -18,21 +18,24 @@ class Group { this.rating, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory Group.parse(XmlElement element) { return Group( - contents: element.findElements('media:content').map((e) { - return Content.parse(e); - }).toList(), - credits: element.findElements('media:credit').map((e) { - return Credit.parse(e); - }).toList(), - category: Category.parse( - findFirstElement(element, 'media:category'), - ), - rating: Rating.parse( - findFirstElement(element, 'media:rating'), - ), + contents: element + .findElements('media:content') + .map((e) => Content.parse(e)) + .toList(), + credits: element + .findElements('media:credit') + .map((e) => Credit.parse(e)) + .toList(), + category: element + .findElements('media:category') + .map((e) => Category.parse(e)) + .firstOrNull, + rating: element + .findElements('media:rating') + .map((e) => Rating.parse(e)) + .firstOrNull, ); } } diff --git a/lib/domain/media/hash.dart b/lib/domain/media/hash.dart index f605045..7387e22 100644 --- a/lib/domain/media/hash.dart +++ b/lib/domain/media/hash.dart @@ -9,8 +9,7 @@ class Hash { this.value, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory Hash.parse(XmlElement element) { return Hash( algo: element.getAttribute('algo'), value: element.text, diff --git a/lib/domain/media/license.dart b/lib/domain/media/license.dart index ba8bfd6..18f50f8 100644 --- a/lib/domain/media/license.dart +++ b/lib/domain/media/license.dart @@ -11,8 +11,7 @@ class License { this.value, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory License.parse(XmlElement element) { return License( type: element.getAttribute('type'), href: element.getAttribute('href'), diff --git a/lib/domain/media/media.dart b/lib/domain/media/media.dart index 9511ebd..e95cb48 100644 --- a/lib/domain/media/media.dart +++ b/lib/domain/media/media.dart @@ -20,6 +20,7 @@ import 'package:webfeed/domain/media/text.dart'; import 'package:webfeed/domain/media/thumbnail.dart'; import 'package:webfeed/domain/media/title.dart'; import 'package:webfeed/util/xml.dart'; +import 'package:webfeed/util/iterable.dart'; import 'package:xml/xml.dart'; class Media { @@ -47,7 +48,7 @@ class Media { final License? license; final PeerLink? peerLink; final Rights? rights; - final List? scenes; + final List? scenes; Media({ this.group, @@ -79,90 +80,113 @@ class Media { factory Media.parse(XmlElement element) { return Media( - group: Group.parse( - findFirstElement(element, 'media:group'), - ), - contents: element.findElements('media:content').map((e) { - return Content.parse(e); - }).toList(), - credits: element.findElements('media:credit').map((e) { - return Credit.parse(e); - }).toList(), - category: Category.parse( - findFirstElement(element, 'media:category'), - ), - rating: Rating.parse( - findFirstElement(element, 'media:rating'), - ), - title: Title.parse( - findFirstElement(element, 'media:title'), - ), - description: Description.parse( - findFirstElement(element, 'media:description'), - ), - keywords: findFirstElement(element, 'media:keywords')?.text, - thumbnails: element.findElements('media:thumbnail').map((e) { - return Thumbnail.parse(e); - }).toList(), - hash: Hash.parse( - findFirstElement(element, 'media:hash'), - ), - player: Player.parse( - findFirstElement(element, 'media:player'), - ), - copyright: Copyright.parse( - findFirstElement(element, 'media:copyright'), - ), - text: Text.parse( - findFirstElement(element, 'media:text'), - ), - restriction: Restriction.parse( - findFirstElement(element, 'media:restriction'), - ), - community: Community.parse( - findFirstElement(element, 'media:community'), - ), - comments: findFirstElement(element, 'media:comments') + group: element + .findElements('media:group') + .map((e) => Group.parse(e)) + .firstOrNull, + contents: element + .findElements('media:content') + .map((e) => Content.parse(e)) + .toList(), + credits: element + .findElements('media:credit') + .map((e) => Credit.parse(e)) + .toList(), + category: element + .findElements('media:category') + .map((e) => Category.parse(e)) + .firstOrNull, + rating: element + .findElements('media:rating') + .map((e) => Rating.parse(e)) + .firstOrNull, + title: findElements(element, 'media:title') + ?.map((e) => Title.parse(e)) + .firstOrNull, + description: element + .findElements('media:description') + .map((e) => Description.parse(e)) + .firstOrNull, + keywords: element.findElements('media:keywords').firstOrNull?.text, + thumbnails: element + .findElements('media:thumbnail') + .map((e) => Thumbnail.parse(e)) + .toList(), + hash: element + .findElements('media:hash') + .map((e) => Hash.parse(e)) + .firstOrNull, + player: element + .findElements('media:player') + .map((e) => Player.parse(e)) + .firstOrNull, + copyright: element + .findElements('media:copyright') + .map((e) => Copyright.parse(e)) + .firstOrNull, + text: element + .findElements('media:text') + .map((e) => Text.parse(e)) + .firstOrNull, + restriction: element + .findElements('media:restriction') + .map((e) => Restriction.parse(e)) + .firstOrNull, + community: element + .findElements('media:community') + .map((e) => Community.parse(e)) + .firstOrNull, + comments: element + .findElements('media:comments') + .firstOrNull ?.findElements('media:comment') - .map((e) { - return e.text; - }).toList() ?? + .map((e) => e.text) + .toList() ?? [], - embed: Embed.parse( - findFirstElement(element, 'media:embed'), - ), - responses: findFirstElement(element, 'media:responses') + embed: element + .findElements('media:embed') + .map((e) => Embed.parse(e)) + .firstOrNull, + responses: element + .findElements('media:responses') + .firstOrNull ?.findElements('media:response') - .map((e) { - return e.text; - }).toList() ?? + .map((e) => e.text) + .toList() ?? [], - backLinks: findFirstElement(element, 'media:backLinks') + backLinks: element + .findElements('media:backLinks') + .firstOrNull ?.findElements('media:backLink') - .map((e) { - return e.text; - }).toList() ?? + .map((e) => e.text) + .toList() ?? [], - status: Status.parse( - findFirstElement(element, 'media:status'), - ), - prices: element.findElements('media:price').map((e) { - return Price.parse(e); - }).toList(), - license: License.parse( - findFirstElement(element, 'media:license'), - ), - peerLink: PeerLink.parse( - findFirstElement(element, 'media:peerLink'), - ), - rights: Rights.parse( - findFirstElement(element, 'media:rights'), - ), - scenes: findFirstElement(element, 'media:scenes') + status: element + .findElements('media:status') + .map((e) => Status.parse(e)) + .firstOrNull, + prices: element + .findElements('media:price') + .map((e) => Price.parse(e)) + .toList(), + license: element + .findElements('media:license') + .map((e) => License.parse(e)) + .firstOrNull, + peerLink: element + .findElements('media:peerLink') + .map((e) => PeerLink.parse(e)) + .firstOrNull, + rights: element + .findElements('media:rights') + .map((e) => Rights.parse(e)) + .firstOrNull, + scenes: element + .findElements('media:scenes') + .firstOrNull ?.findElements('media:scene') - .map((e) { - return Scene.parse(e); - }).toList() ?? + .map((e) => Scene.parse(e)) + .toList() ?? [], ); } diff --git a/lib/domain/media/param.dart b/lib/domain/media/param.dart index cb71d38..0e8b969 100644 --- a/lib/domain/media/param.dart +++ b/lib/domain/media/param.dart @@ -9,8 +9,7 @@ class Param { this.value, }); - static Param? parse(XmlElement? element) { - if (element == null) return null; + factory Param.parse(XmlElement element) { 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 0484d07..d8bec62 100644 --- a/lib/domain/media/peer_link.dart +++ b/lib/domain/media/peer_link.dart @@ -11,8 +11,7 @@ class PeerLink { this.value, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory PeerLink.parse(XmlElement element) { return PeerLink( type: element.getAttribute('type'), href: element.getAttribute('href'), diff --git a/lib/domain/media/player.dart b/lib/domain/media/player.dart index 5d18267..102c081 100644 --- a/lib/domain/media/player.dart +++ b/lib/domain/media/player.dart @@ -13,8 +13,7 @@ class Player { this.value, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory Player.parse(XmlElement element) { return Player( url: element.getAttribute('url'), width: int.tryParse(element.getAttribute('width') ?? '0'), diff --git a/lib/domain/media/rating.dart b/lib/domain/media/rating.dart index 30bd493..a9212c1 100644 --- a/lib/domain/media/rating.dart +++ b/lib/domain/media/rating.dart @@ -9,8 +9,7 @@ class Rating { this.value, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory Rating.parse(XmlElement element) { return Rating( scheme: element.getAttribute('scheme'), value: element.text, diff --git a/lib/domain/media/restriction.dart b/lib/domain/media/restriction.dart index 3532ffb..85f7dbf 100644 --- a/lib/domain/media/restriction.dart +++ b/lib/domain/media/restriction.dart @@ -11,8 +11,7 @@ class Restriction { this.value, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory Restriction.parse(XmlElement element) { return Restriction( relationship: element.getAttribute('relationship'), type: element.getAttribute('type'), diff --git a/lib/domain/media/rights.dart b/lib/domain/media/rights.dart index 90c6a6b..50b41d5 100644 --- a/lib/domain/media/rights.dart +++ b/lib/domain/media/rights.dart @@ -7,8 +7,7 @@ class Rights { this.status, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory Rights.parse(XmlElement element) { return Rights( status: element.getAttribute('status'), ); diff --git a/lib/domain/media/scene.dart b/lib/domain/media/scene.dart index 17222fe..32a9df4 100644 --- a/lib/domain/media/scene.dart +++ b/lib/domain/media/scene.dart @@ -1,4 +1,4 @@ -import 'package:webfeed/util/xml.dart'; +import 'package:webfeed/util/iterable.dart'; import 'package:xml/xml.dart'; class Scene { @@ -14,13 +14,12 @@ class Scene { this.endTime, }); - static Scene? parse(XmlElement? element) { - if (element == null) return null; + factory Scene.parse(XmlElement element) { return Scene( - title: findFirstElement(element, 'sceneTitle')?.text, - description: findFirstElement(element, 'sceneDescription')?.text, - startTime: findFirstElement(element, 'sceneStartTime')?.text, - endTime: findFirstElement(element, 'sceneEndTime')?.text, + title: element.findElements('sceneTitle').firstOrNull?.text, + description: element.findElements('sceneDescription').firstOrNull?.text, + startTime: element.findElements('sceneStartTime').firstOrNull?.text, + endTime: element.findElements('sceneEndTime').firstOrNull?.text, ); } } diff --git a/lib/domain/media/status.dart b/lib/domain/media/status.dart index c2ce9cb..1875d2e 100644 --- a/lib/domain/media/status.dart +++ b/lib/domain/media/status.dart @@ -9,8 +9,7 @@ class Status { this.reason, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory Status.parse(XmlElement element) { 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 fc74357..d52c316 100644 --- a/lib/domain/media/tags.dart +++ b/lib/domain/media/tags.dart @@ -9,8 +9,7 @@ class Tags { this.weight, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory Tags.parse(XmlElement element) { 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 8ce6689..b91ba67 100644 --- a/lib/domain/media/text.dart +++ b/lib/domain/media/text.dart @@ -15,8 +15,7 @@ class Text { this.value, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory Text.parse(XmlElement element) { return Text( type: element.getAttribute('type'), lang: element.getAttribute('lang'), diff --git a/lib/domain/media/title.dart b/lib/domain/media/title.dart index a14ca06..4b523bf 100644 --- a/lib/domain/media/title.dart +++ b/lib/domain/media/title.dart @@ -9,8 +9,7 @@ class Title { this.value, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory Title.parse(XmlElement element) { return Title( type: element.getAttribute('type'), value: element.text, diff --git a/lib/domain/rss_category.dart b/lib/domain/rss_category.dart index bb2c21e..6196776 100644 --- a/lib/domain/rss_category.dart +++ b/lib/domain/rss_category.dart @@ -6,8 +6,7 @@ class RssCategory { RssCategory(this.domain, this.value); - static RssCategory? parse(XmlElement? element) { - if (element == null) return null; + factory RssCategory.parse(XmlElement element) { var domain = element.getAttribute('domain'); var value = element.text; diff --git a/lib/domain/rss_cloud.dart b/lib/domain/rss_cloud.dart index d86f302..a51ee79 100644 --- a/lib/domain/rss_cloud.dart +++ b/lib/domain/rss_cloud.dart @@ -15,8 +15,7 @@ class RssCloud { this.protocol, ); - static parse(XmlElement? node) { - if (node == null) return null; + factory RssCloud.parse(XmlElement node) { var domain = node.getAttribute('domain'); var port = node.getAttribute('port'); var path = node.getAttribute('path'); diff --git a/lib/domain/rss_content.dart b/lib/domain/rss_content.dart index eecf0c0..531c9ed 100644 --- a/lib/domain/rss_content.dart +++ b/lib/domain/rss_content.dart @@ -12,18 +12,15 @@ final _imagesRegExp = RegExp( /// class RssContent { String value; - Iterable images; + Iterable images; RssContent(this.value, this.images); - static parse(XmlElement? element) { - if(element == null) { - return null; - } + factory RssContent.parse(XmlElement element) { final dynamic? content = element.text; - final images = []; + final images = []; _imagesRegExp.allMatches(content).forEach((match) { - images.add(match.group(1)); + images.add(match.group(1)!); }); return RssContent(content, images); } diff --git a/lib/domain/rss_enclosure.dart b/lib/domain/rss_enclosure.dart index 8f8f33d..b1fbee6 100644 --- a/lib/domain/rss_enclosure.dart +++ b/lib/domain/rss_enclosure.dart @@ -7,8 +7,7 @@ class RssEnclosure { RssEnclosure(this.url, this.type, this.length); - static parse(XmlElement? element) { - if (element == null) return null; + factory RssEnclosure.parse(XmlElement element) { var url = element.getAttribute('url'); var type = element.getAttribute('type'); var length = int.tryParse(element.getAttribute('length') ?? '0'); diff --git a/lib/domain/rss_feed.dart b/lib/domain/rss_feed.dart index f6f727a..4408f37 100644 --- a/lib/domain/rss_feed.dart +++ b/lib/domain/rss_feed.dart @@ -7,7 +7,7 @@ import 'package:webfeed/domain/rss_cloud.dart'; import 'package:webfeed/domain/rss_image.dart'; import 'package:webfeed/domain/rss_item.dart'; import 'package:webfeed/domain/syndication/syndication.dart'; -import 'package:webfeed/util/xml.dart'; +import 'package:webfeed/util/iterable.dart'; import 'package:xml/xml.dart'; class RssFeed { @@ -19,9 +19,9 @@ class RssFeed { final RssImage? image; final RssCloud? cloud; - final List? categories; + final List? categories; final List? skipDays; - final List? skipHours; + final List? skipHours; final String? lastBuildDate; final String? language; final String? generator; @@ -62,50 +62,62 @@ class RssFeed { factory RssFeed.parse(String xmlString) { var document = XmlDocument.parse(xmlString); - var rss = findFirstElement(document, 'rss'); - var rdf = findFirstElement(document, 'rdf:RDF'); + var rss = document.findElements('rss').firstOrNull; + var rdf = document.findElements('rdf:RDF').firstOrNull; if (rss == null && rdf == null) { throw ArgumentError('not a rss feed'); } - var channelElement = findFirstElement(rss ?? rdf, 'channel'); + var channelElement = (rss ?? rdf)!.findElements('channel').firstOrNull; if (channelElement == null) { throw ArgumentError('channel not found'); } return RssFeed( - title: findFirstElement(channelElement, 'title')?.text, - author: findFirstElement(channelElement, 'author')?.text, - description: findFirstElement(channelElement, 'description')?.text, - link: findFirstElement(channelElement, 'link')?.text, - items: (rss != null ? channelElement : rdf)! + title: channelElement.findElements('title').firstOrNull?.text, + author: channelElement.findElements('author').firstOrNull?.text, + description: channelElement.findElements('description').firstOrNull?.text, + link: channelElement.findElements('link').firstOrNull?.text, + items: (rdf ?? channelElement) .findElements('item') .map((e) => RssItem.parse(e)) .toList(), - image: RssImage.parse( - findFirstElement(rss != null ? channelElement : rdf, 'image')), - cloud: RssCloud.parse(findFirstElement(channelElement, 'cloud')), + image: (rdf ?? channelElement) + .findElements('image') + .map((e) => RssImage.parse(e)) + .firstOrNull, + cloud: channelElement + .findElements('cloud') + .map((e) => RssCloud.parse(e)) + .firstOrNull, categories: channelElement .findElements('category') .map((e) => RssCategory.parse(e)) .toList(), - skipDays: findFirstElement(channelElement, 'skipDays') + skipDays: channelElement + .findElements('skipDays') + .firstOrNull ?.findAllElements('day') .map((e) => e.text) .toList() ?? [], - skipHours: findFirstElement(channelElement, 'skipHours') + skipHours: channelElement + .findElements('skipHours') + .firstOrNull ?.findAllElements('hour') - .map((e) => int.tryParse(e.text)) + .map((e) => int.tryParse(e.text) ?? 0) .toList() ?? [], - lastBuildDate: findFirstElement(channelElement, 'lastBuildDate')?.text, - language: findFirstElement(channelElement, 'language')?.text, - generator: findFirstElement(channelElement, 'generator')?.text, - copyright: findFirstElement(channelElement, 'copyright')?.text, - docs: findFirstElement(channelElement, 'docs')?.text, - managingEditor: findFirstElement(channelElement, 'managingEditor')?.text, - rating: findFirstElement(channelElement, 'rating')?.text, - webMaster: findFirstElement(channelElement, 'webMaster')?.text, - ttl: int.tryParse(findFirstElement(channelElement, 'ttl')?.text ?? '0'), + lastBuildDate: + channelElement.findElements('lastBuildDate').firstOrNull?.text, + language: channelElement.findElements('language').firstOrNull?.text, + generator: channelElement.findElements('generator').firstOrNull?.text, + copyright: channelElement.findElements('copyright').firstOrNull?.text, + docs: channelElement.findElements('docs').firstOrNull?.text, + managingEditor: + channelElement.findElements('managingEditor').firstOrNull?.text, + rating: channelElement.findElements('rating').firstOrNull?.text, + webMaster: channelElement.findElements('webMaster').firstOrNull?.text, + ttl: int.tryParse( + channelElement.findElements('ttl').firstOrNull?.text ?? '0'), dc: DublinCore.parse(channelElement), itunes: Itunes.parse(channelElement), syndication: Syndication.parse(channelElement), diff --git a/lib/domain/rss_image.dart b/lib/domain/rss_image.dart index 7c43eee..6e3b1f0 100644 --- a/lib/domain/rss_image.dart +++ b/lib/domain/rss_image.dart @@ -1,4 +1,4 @@ -import 'package:webfeed/util/xml.dart'; +import 'package:webfeed/util/iterable.dart'; import 'package:xml/xml.dart'; class RssImage { @@ -8,12 +8,11 @@ class RssImage { RssImage({this.title, this.url, this.link}); - static parse(XmlElement? element) { - if (element == null) return null; + factory RssImage.parse(XmlElement element) { return RssImage( - title: findFirstElement(element, 'title')?.text, - url: findFirstElement(element, 'url')?.text, - link: findFirstElement(element, 'link')?.text, + title: element.findElements('title').firstOrNull?.text, + url: element.findElements('url').firstOrNull?.text, + link: element.findElements('link').firstOrNull?.text, ); } } diff --git a/lib/domain/rss_item.dart b/lib/domain/rss_item.dart index c31ad91..ef9ff20 100644 --- a/lib/domain/rss_item.dart +++ b/lib/domain/rss_item.dart @@ -6,7 +6,7 @@ import 'package:webfeed/domain/rss_content.dart'; import 'package:webfeed/domain/rss_enclosure.dart'; import 'package:webfeed/domain/rss_source.dart'; import 'package:webfeed/util/datetime.dart'; -import 'package:webfeed/util/xml.dart'; +import 'package:webfeed/util/iterable.dart'; import 'package:xml/xml.dart'; class RssItem { @@ -14,7 +14,7 @@ class RssItem { final String? description; final String? link; - final List? categories; + final List? categories; final String? guid; final DateTime? pubDate; final String? author; @@ -45,21 +45,30 @@ class RssItem { factory RssItem.parse(XmlElement element) { return RssItem( - title: findFirstElement(element, 'title')?.text, - description: findFirstElement(element, 'description')?.text, - link: findFirstElement(element, 'link')?.text, + title: element.findElements('title').firstOrNull?.text, + description: element.findElements('description').firstOrNull?.text, + link: element.findElements('link').firstOrNull?.text, categories: element .findElements('category') .map((e) => RssCategory.parse(e)) .toList(), - guid: findFirstElement(element, 'guid')?.text, - pubDate: parseDateTime(findFirstElement(element, 'pubDate')?.text), - author: findFirstElement(element, 'author')?.text, - comments: findFirstElement(element, 'comments')?.text, - source: RssSource.parse(findFirstElement(element, 'source')), - content: RssContent.parse(findFirstElement(element, 'content:encoded')), + guid: element.findElements('guid').firstOrNull?.text, + pubDate: parseDateTime(element.findElements('pubDate').firstOrNull?.text), + author: element.findElements('author').firstOrNull?.text, + comments: element.findElements('comments').firstOrNull?.text, + source: element + .findElements('source') + .map((e) => RssSource.parse(e)) + .firstOrNull, + content: element + .findElements('content:encoded') + .map((e) => RssContent.parse(e)) + .firstOrNull, media: Media.parse(element), - enclosure: RssEnclosure.parse(findFirstElement(element, 'enclosure')), + enclosure: element + .findElements('enclosure') + .map((e) => RssEnclosure.parse(e)) + .firstOrNull, dc: DublinCore.parse(element), itunes: Itunes.parse(element), ); diff --git a/lib/domain/rss_source.dart b/lib/domain/rss_source.dart index 998d70e..3b94b8a 100644 --- a/lib/domain/rss_source.dart +++ b/lib/domain/rss_source.dart @@ -6,8 +6,7 @@ class RssSource { RssSource(this.url, this.value); - static parse(XmlElement? element) { - if (element == null) return null; + factory RssSource.parse(XmlElement element) { var url = element.getAttribute('url'); var value = element.text; diff --git a/lib/domain/syndication/syndication.dart b/lib/domain/syndication/syndication.dart index 0f0f3ac..8c42f3d 100644 --- a/lib/domain/syndication/syndication.dart +++ b/lib/domain/syndication/syndication.dart @@ -1,5 +1,5 @@ import 'package:webfeed/util/datetime.dart'; -import 'package:webfeed/util/xml.dart'; +import 'package:webfeed/util/iterable.dart'; import 'package:xml/xml.dart'; enum SyndicationUpdatePeriod { hourly, daily, weekly, monthly, yearly } @@ -15,10 +15,9 @@ class Syndication { this.updateBase, }); - static parse(XmlElement? element) { - if (element == null) return null; + factory Syndication.parse(XmlElement element) { SyndicationUpdatePeriod updatePeriod; - switch (findFirstElement(element, 'sy:updatePeriod')?.text) { + switch (element.findElements('sy:updatePeriod').firstOrNull?.text) { case 'hourly': updatePeriod = SyndicationUpdatePeriod.hourly; break; @@ -41,9 +40,9 @@ class Syndication { return Syndication( updatePeriod: updatePeriod, updateFrequency: int.tryParse( - findFirstElement(element, 'sy:updateFrequency')?.text ?? '1'), - updateBase: - parseDateTime(findFirstElement(element, 'sy:updateBase')?.text), + element.findElements('sy:updateFrequency').firstOrNull?.text ?? '1'), + updateBase: parseDateTime( + element.findElements('sy:updateBase').firstOrNull?.text), ); } } diff --git a/lib/util/function.dart b/lib/util/function.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/util/iterable.dart b/lib/util/iterable.dart new file mode 100644 index 0000000..6f98f19 --- /dev/null +++ b/lib/util/iterable.dart @@ -0,0 +1,3 @@ +extension WebFeedIterable on Iterable { + T? get firstOrNull => isEmpty ? null : first; +} diff --git a/lib/util/xml.dart b/lib/util/xml.dart index dcfa60a..4f1ad78 100644 --- a/lib/util/xml.dart +++ b/lib/util/xml.dart @@ -1,21 +1,8 @@ import 'dart:core'; +import 'package:webfeed/util/iterable.dart'; import 'package:xml/xml.dart'; -XmlElement? findFirstElement( - XmlNode? node, - String name, { - bool recursive = false, - String? namespace, -}) { - try { - return findElements(node, name, recursive: recursive, namespace: namespace) - ?.first; - } on StateError { - return null; - } -} - Iterable? findElements( XmlNode? node, String name, { @@ -34,7 +21,7 @@ Iterable? findElements( } bool parseBoolLiteral(XmlElement element, String tagName) { - var v = findFirstElement(element, tagName)?.text.toLowerCase().trim(); + var v = element.findElements(tagName).firstOrNull?.text.toLowerCase().trim(); if (v == null) return false; return ['yes', 'true'].contains(v); } diff --git a/test/atom_test.dart b/test/atom_test.dart index eb4759a..5814efc 100644 --- a/test/atom_test.dart +++ b/test/atom_test.dart @@ -185,9 +185,9 @@ void main() { expect(item.media!.embed!.width, 512); 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.name, 'type'); + 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'); @@ -203,8 +203,8 @@ 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'); @@ -218,10 +218,10 @@ void main() { expect(item.media!.rights!.status, 'official'); expect(item.media!.scenes!.length, 2); - expect(item.media!.scenes!.first!.title, 'sceneTitle1'); - expect(item.media!.scenes!.first!.description, 'sceneDesc1'); - expect(item.media!.scenes!.first!.startTime, '00:15'); - expect(item.media!.scenes!.first!.endTime, '00:45'); + expect(item.media!.scenes!.first.title, 'sceneTitle1'); + expect(item.media!.scenes!.first.description, 'sceneDesc1'); + expect(item.media!.scenes!.first.startTime, '00:15'); + expect(item.media!.scenes!.first.endTime, '00:45'); }); test('parse Atom-Empty.xml', () { diff --git a/test/rss_test.dart b/test/rss_test.dart index d01d540..f8295a0 100644 --- a/test/rss_test.dart +++ b/test/rss_test.dart @@ -47,10 +47,10 @@ void main() { expect(feed.cloud!.protocol, 'xml-rpc'); 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(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(feed.skipDays!.length, 3); expect(feed.skipDays!.contains('Monday'), true); @@ -74,8 +74,8 @@ void main() { expect(feed.items!.first.guid, 'https://foo.bar.news/1?guid'); expect(feed.items!.first.pubDate, DateTime(2018, 03, 26, 14)); //Mon, 26 Mar 2018 14:00:00 PDT - expect(feed.items!.first.categories!.first!.domain, 'news'); - expect(feed.items!.first.categories!.first!.value, 'Lorem'); + expect(feed.items!.first.categories!.first.domain, 'news'); + expect(feed.items!.first.categories!.first.value, 'Lorem'); expect(feed.items!.first.author, 'alice@foo.bar.news'); expect(feed.items!.first.source!.url, 'https://foo.bar.news/1?source'); expect(feed.items!.first.source!.value, 'Foo Bar'); @@ -192,9 +192,9 @@ void main() { expect(item.media!.embed!.width, 512); 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.name, 'type'); + 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'); @@ -210,8 +210,8 @@ 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'); @@ -225,10 +225,10 @@ void main() { expect(item.media!.rights!.status, 'official'); expect(item.media!.scenes!.length, 2); - expect(item.media!.scenes!.first!.title, 'sceneTitle1'); - expect(item.media!.scenes!.first!.description, 'sceneDesc1'); - expect(item.media!.scenes!.first!.startTime, '00:15'); - expect(item.media!.scenes!.first!.endTime, '00:45'); + expect(item.media!.scenes!.first.title, 'sceneTitle1'); + expect(item.media!.scenes!.first.description, 'sceneDesc1'); + expect(item.media!.scenes!.first.startTime, '00:15'); + expect(item.media!.scenes!.first.endTime, '00:45'); }); test('parse RSS-DC.xml', () { var xmlString = File('test/xml/RSS-DC.xml').readAsStringSync(); @@ -332,12 +332,12 @@ void main() { expect(feed.itunes!.owner!.email, 'editors@changelog.com'); expect( Set.from([ - feed.itunes!.categories![0]!.category, - feed.itunes!.categories![1]!.category + feed.itunes!.categories![0].category, + feed.itunes!.categories![1].category ]), ['Technology', 'Foo']); for (var category in feed.itunes!.categories!) { - switch (category!.category) { + switch (category.category) { case 'Foo': expect(category.subCategories, ['Bar', 'Baz']); break;