| @@ -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); | |||
| @@ -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, | |||
| ); | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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), | |||
| ); | |||
| } | |||
| @@ -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, | |||
| ); | |||
| } | |||
| } | |||
| @@ -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, | |||
| ); | |||
| } | |||
| } | |||
| @@ -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, | |||
| ); | |||
| } | |||
| } | |||
| @@ -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<String>? keywords; | |||
| final ItunesImage? image; | |||
| final List<ItunesCategory?>? categories; | |||
| final List<ItunesCategory>? 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, | |||
| @@ -2,17 +2,16 @@ import 'package:xml/xml.dart'; | |||
| class ItunesCategory { | |||
| final String? category; | |||
| final List<String?>? subCategories; | |||
| final List<String>? 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()); | |||
| } | |||
| } | |||
| @@ -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': | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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(), | |||
| ); | |||
| } | |||
| } | |||
| @@ -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': | |||
| @@ -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'), | |||
| @@ -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, | |||
| ); | |||
| } | |||
| } | |||
| @@ -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, | |||
| @@ -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, | |||
| @@ -5,7 +5,7 @@ class Embed { | |||
| final String? url; | |||
| final int? width; | |||
| final int? height; | |||
| final List<Param?>? params; | |||
| final List<Param>? 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(), | |||
| ); | |||
| } | |||
| } | |||
| @@ -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, | |||
| ); | |||
| } | |||
| } | |||
| @@ -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, | |||
| @@ -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'), | |||
| @@ -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<Scene?>? scenes; | |||
| final List<Scene>? 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() ?? | |||
| [], | |||
| ); | |||
| } | |||
| @@ -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, | |||
| @@ -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'), | |||
| @@ -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'), | |||
| @@ -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, | |||
| @@ -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'), | |||
| @@ -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'), | |||
| ); | |||
| @@ -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, | |||
| ); | |||
| } | |||
| } | |||
| @@ -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'), | |||
| @@ -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'), | |||
| @@ -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'), | |||
| @@ -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, | |||
| @@ -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; | |||
| @@ -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'); | |||
| @@ -12,18 +12,15 @@ final _imagesRegExp = RegExp( | |||
| /// | |||
| class RssContent { | |||
| String value; | |||
| Iterable<String?> images; | |||
| Iterable<String> 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 = <String?>[]; | |||
| final images = <String>[]; | |||
| _imagesRegExp.allMatches(content).forEach((match) { | |||
| images.add(match.group(1)); | |||
| images.add(match.group(1)!); | |||
| }); | |||
| return RssContent(content, images); | |||
| } | |||
| @@ -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'); | |||
| @@ -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<RssCategory?>? categories; | |||
| final List<RssCategory>? categories; | |||
| final List<String>? skipDays; | |||
| final List<int?>? skipHours; | |||
| final List<int>? 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), | |||
| @@ -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, | |||
| ); | |||
| } | |||
| } | |||
| @@ -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<RssCategory?>? categories; | |||
| final List<RssCategory>? 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), | |||
| ); | |||
| @@ -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; | |||
| @@ -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), | |||
| ); | |||
| } | |||
| } | |||
| @@ -0,0 +1,3 @@ | |||
| extension WebFeedIterable<T> on Iterable<T> { | |||
| T? get firstOrNull => isEmpty ? null : first; | |||
| } | |||
| @@ -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<XmlElement>? findElements( | |||
| XmlNode? node, | |||
| String name, { | |||
| @@ -34,7 +21,7 @@ Iterable<XmlElement>? 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); | |||
| } | |||
| @@ -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', () { | |||
| @@ -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; | |||