| @@ -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'; | import 'package:webfeed/webfeed.dart'; | ||||
| void main() async { | void main() async { | ||||
| var client = http.Client(); | |||||
| final client = IOClient(HttpClient() | |||||
| ..badCertificateCallback = | |||||
| ((X509Certificate cert, String host, int port) => true)); | |||||
| // RSS feed | // RSS feed | ||||
| var response = await client.get( | 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); | var channel = RssFeed.parse(response.body); | ||||
| print(channel); | print(channel); | ||||
| // Atom feed | // Atom feed | ||||
| response = | 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); | var feed = AtomFeed.parse(response.body); | ||||
| print(feed); | 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_link.dart'; | ||||
| import 'package:webfeed/domain/atom_person.dart'; | import 'package:webfeed/domain/atom_person.dart'; | ||||
| import 'package:webfeed/util/datetime.dart'; | import 'package:webfeed/util/datetime.dart'; | ||||
| import 'package:webfeed/util/xml.dart'; | |||||
| import 'package:webfeed/util/iterable.dart'; | |||||
| import 'package:xml/xml.dart'; | import 'package:xml/xml.dart'; | ||||
| class AtomFeed { | class AtomFeed { | ||||
| @@ -41,15 +41,16 @@ class AtomFeed { | |||||
| factory AtomFeed.parse(String xmlString) { | factory AtomFeed.parse(String xmlString) { | ||||
| var document = XmlDocument.parse(xmlString); | var document = XmlDocument.parse(xmlString); | ||||
| var feedElement = findFirstElement(document, 'feed'); | |||||
| var feedElement = document.findElements('feed').firstOrNull; | |||||
| if (feedElement == null) { | if (feedElement == null) { | ||||
| throw ArgumentError('feed not found'); | throw ArgumentError('feed not found'); | ||||
| } | } | ||||
| return AtomFeed( | 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 | items: feedElement | ||||
| .findElements('entry') | .findElements('entry') | ||||
| .map((e) => AtomItem.parse(e)) | .map((e) => AtomItem.parse(e)) | ||||
| @@ -70,12 +71,14 @@ class AtomFeed { | |||||
| .findElements('category') | .findElements('category') | ||||
| .map((e) => AtomCategory.parse(e)) | .map((e) => AtomCategory.parse(e)) | ||||
| .toList(), | .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); | 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 uri = element.getAttribute('uri'); | ||||
| var version = element.getAttribute('version'); | var version = element.getAttribute('version'); | ||||
| var value = element.text; | 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/atom_source.dart'; | ||||
| import 'package:webfeed/domain/media/media.dart'; | import 'package:webfeed/domain/media/media.dart'; | ||||
| import 'package:webfeed/util/datetime.dart'; | import 'package:webfeed/util/datetime.dart'; | ||||
| import 'package:webfeed/util/xml.dart'; | |||||
| import 'package:webfeed/util/iterable.dart'; | |||||
| import 'package:xml/xml.dart'; | import 'package:xml/xml.dart'; | ||||
| class AtomItem { | class AtomItem { | ||||
| @@ -41,9 +41,9 @@ class AtomItem { | |||||
| factory AtomItem.parse(XmlElement element) { | factory AtomItem.parse(XmlElement element) { | ||||
| return AtomItem( | 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 | authors: element | ||||
| .findElements('author') | .findElements('author') | ||||
| .map((e) => AtomPerson.parse(e)) | .map((e) => AtomPerson.parse(e)) | ||||
| @@ -58,11 +58,14 @@ class AtomItem { | |||||
| .findElements('contributor') | .findElements('contributor') | ||||
| .map((e) => AtomPerson.parse(e)) | .map((e) => AtomPerson.parse(e)) | ||||
| .toList(), | .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), | 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'; | import 'package:xml/xml.dart'; | ||||
| class AtomPerson { | class AtomPerson { | ||||
| @@ -10,9 +10,9 @@ class AtomPerson { | |||||
| factory AtomPerson.parse(XmlElement element) { | factory AtomPerson.parse(XmlElement element) { | ||||
| return AtomPerson( | 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'; | import 'package:xml/xml.dart'; | ||||
| class AtomSource { | class AtomSource { | ||||
| @@ -12,12 +12,11 @@ class AtomSource { | |||||
| this.updated, | this.updated, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory AtomSource.parse(XmlElement element) { | |||||
| return AtomSource( | 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/datetime.dart'; | ||||
| import 'package:webfeed/util/xml.dart'; | |||||
| import 'package:webfeed/util/iterable.dart'; | |||||
| import 'package:xml/xml.dart'; | import 'package:xml/xml.dart'; | ||||
| class DublinCore { | class DublinCore { | ||||
| @@ -41,28 +41,27 @@ class DublinCore { | |||||
| this.rights, | this.rights, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) { | |||||
| return null; | |||||
| } | |||||
| factory DublinCore.parse(XmlElement element) { | |||||
| return DublinCore( | 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_image.dart'; | ||||
| import 'package:webfeed/domain/itunes/itunes_owner.dart'; | import 'package:webfeed/domain/itunes/itunes_owner.dart'; | ||||
| import 'package:webfeed/domain/itunes/itunes_type.dart'; | import 'package:webfeed/domain/itunes/itunes_type.dart'; | ||||
| import 'package:webfeed/util/iterable.dart'; | |||||
| import 'package:webfeed/util/xml.dart'; | import 'package:webfeed/util/xml.dart'; | ||||
| import 'package:xml/xml.dart'; | import 'package:xml/xml.dart'; | ||||
| @@ -15,7 +16,7 @@ class Itunes { | |||||
| final ItunesOwner? owner; | final ItunesOwner? owner; | ||||
| final List<String>? keywords; | final List<String>? keywords; | ||||
| final ItunesImage? image; | final ItunesImage? image; | ||||
| final List<ItunesCategory?>? categories; | |||||
| final List<ItunesCategory>? categories; | |||||
| final ItunesType? type; | final ItunesType? type; | ||||
| final String? newFeedUrl; | final String? newFeedUrl; | ||||
| final bool? block; | final bool? block; | ||||
| @@ -45,39 +46,51 @@ class Itunes { | |||||
| this.episodeType, | 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( | 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'), | 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 | ?.text | ||||
| .split(',') | .split(',') | ||||
| .map((keyword) => keyword.trim()) | .map((keyword) => keyword.trim()) | ||||
| .toList() ?? | .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)) | .map((e) => ItunesCategory.parse(e)) | ||||
| .toList(), | .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'), | block: parseBoolLiteral(element, 'itunes:block'), | ||||
| complete: parseBoolLiteral(element, 'itunes:complete'), | 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), | 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 seconds = 0; | ||||
| var parts = s.split(':'); | var parts = s.split(':'); | ||||
| if (parts.length > 2) { | if (parts.length > 2) { | ||||
| hours = int.parse(parts[parts.length - 3]); | |||||
| hours = int.tryParse(parts[parts.length - 3]) ?? 0; | |||||
| } | } | ||||
| if (parts.length > 1) { | 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( | return Duration( | ||||
| hours: hours, | hours: hours, | ||||
| minutes: minutes, | minutes: minutes, | ||||
| @@ -2,17 +2,16 @@ import 'package:xml/xml.dart'; | |||||
| class ItunesCategory { | class ItunesCategory { | ||||
| final String? category; | final String? category; | ||||
| final List<String?>? subCategories; | |||||
| final List<String>? subCategories; | |||||
| ItunesCategory({this.category, this.subCategories}); | ItunesCategory({this.category, this.subCategories}); | ||||
| static ItunesCategory? parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory ItunesCategory.parse(XmlElement element) { | |||||
| return ItunesCategory( | return ItunesCategory( | ||||
| category: element.getAttribute('text')?.trim(), | category: element.getAttribute('text')?.trim(), | ||||
| subCategories: element | subCategories: element | ||||
| .findElements('itunes:category') | .findElements('itunes:category') | ||||
| .map((e) => e.getAttribute('text')?.trim()) | |||||
| .map((e) => e.getAttribute('text')?.trim() ?? '') | |||||
| .toList()); | .toList()); | ||||
| } | } | ||||
| } | } | ||||
| @@ -2,8 +2,8 @@ import 'package:xml/xml.dart'; | |||||
| enum ItunesEpisodeType { full, trailer, bonus, unknown } | enum ItunesEpisodeType { full, trailer, bonus, unknown } | ||||
| ItunesEpisodeType newItunesEpisodeType(XmlElement? element) { | |||||
| switch (element?.text) { | |||||
| ItunesEpisodeType newItunesEpisodeType(XmlElement element) { | |||||
| switch (element.text) { | |||||
| case 'full': | case 'full': | ||||
| return ItunesEpisodeType.full; | return ItunesEpisodeType.full; | ||||
| case 'trailer': | case 'trailer': | ||||
| @@ -5,13 +5,9 @@ class ItunesImage { | |||||
| ItunesImage({this.href}); | 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(), | 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'; | import 'package:xml/xml.dart'; | ||||
| class ItunesOwner { | class ItunesOwner { | ||||
| @@ -7,10 +7,10 @@ class ItunesOwner { | |||||
| ItunesOwner({this.name, this.email}); | ItunesOwner({this.name, this.email}); | ||||
| factory ItunesOwner.parse(XmlElement? element) { | |||||
| factory ItunesOwner.parse(XmlElement element) { | |||||
| return ItunesOwner( | 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 } | enum ItunesType { episodic, serial, unknown } | ||||
| ItunesType newItunesType(XmlElement? element) { | |||||
| switch (element?.text) { | |||||
| ItunesType newItunesType(XmlElement element) { | |||||
| switch (element.text) { | |||||
| case 'episodic': | case 'episodic': | ||||
| return ItunesType.episodic; | return ItunesType.episodic; | ||||
| case 'serial': | case 'serial': | ||||
| @@ -11,8 +11,7 @@ class Category { | |||||
| this.value, | this.value, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Category.parse(XmlElement element) { | |||||
| return Category( | return Category( | ||||
| scheme: element.getAttribute('scheme'), | scheme: element.getAttribute('scheme'), | ||||
| label: element.getAttribute('label'), | label: element.getAttribute('label'), | ||||
| @@ -1,7 +1,7 @@ | |||||
| import 'package:webfeed/domain/media/star_rating.dart'; | import 'package:webfeed/domain/media/star_rating.dart'; | ||||
| import 'package:webfeed/domain/media/statistics.dart'; | import 'package:webfeed/domain/media/statistics.dart'; | ||||
| import 'package:webfeed/domain/media/tags.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'; | import 'package:xml/xml.dart'; | ||||
| class Community { | class Community { | ||||
| @@ -15,18 +15,20 @@ class Community { | |||||
| this.tags, | this.tags, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Community.parse(XmlElement element) { | |||||
| return Community( | 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, | this.value, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Copyright.parse(XmlElement element) { | |||||
| return Copyright( | return Copyright( | ||||
| url: element.getAttribute('url'), | url: element.getAttribute('url'), | ||||
| value: element.text, | value: element.text, | ||||
| @@ -9,8 +9,7 @@ class Description { | |||||
| this.value, | this.value, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Description.parse(XmlElement element) { | |||||
| return Description( | return Description( | ||||
| type: element.getAttribute('type'), | type: element.getAttribute('type'), | ||||
| value: element.text, | value: element.text, | ||||
| @@ -5,7 +5,7 @@ class Embed { | |||||
| final String? url; | final String? url; | ||||
| final int? width; | final int? width; | ||||
| final int? height; | final int? height; | ||||
| final List<Param?>? params; | |||||
| final List<Param>? params; | |||||
| Embed({ | Embed({ | ||||
| this.url, | this.url, | ||||
| @@ -14,15 +14,15 @@ class Embed { | |||||
| this.params, | this.params, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Embed.parse(XmlElement element) { | |||||
| return Embed( | return Embed( | ||||
| url: element.getAttribute('url'), | url: element.getAttribute('url'), | ||||
| width: int.tryParse(element.getAttribute('width') ?? '0'), | width: int.tryParse(element.getAttribute('width') ?? '0'), | ||||
| height: int.tryParse(element.getAttribute('height') ?? '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/content.dart'; | ||||
| import 'package:webfeed/domain/media/credit.dart'; | import 'package:webfeed/domain/media/credit.dart'; | ||||
| import 'package:webfeed/domain/media/rating.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'; | import 'package:xml/xml.dart'; | ||||
| class Group { | class Group { | ||||
| @@ -18,21 +18,24 @@ class Group { | |||||
| this.rating, | this.rating, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Group.parse(XmlElement element) { | |||||
| return Group( | 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, | this.value, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Hash.parse(XmlElement element) { | |||||
| return Hash( | return Hash( | ||||
| algo: element.getAttribute('algo'), | algo: element.getAttribute('algo'), | ||||
| value: element.text, | value: element.text, | ||||
| @@ -11,8 +11,7 @@ class License { | |||||
| this.value, | this.value, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory License.parse(XmlElement element) { | |||||
| return License( | return License( | ||||
| type: element.getAttribute('type'), | type: element.getAttribute('type'), | ||||
| href: element.getAttribute('href'), | 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/thumbnail.dart'; | ||||
| import 'package:webfeed/domain/media/title.dart'; | import 'package:webfeed/domain/media/title.dart'; | ||||
| import 'package:webfeed/util/xml.dart'; | import 'package:webfeed/util/xml.dart'; | ||||
| import 'package:webfeed/util/iterable.dart'; | |||||
| import 'package:xml/xml.dart'; | import 'package:xml/xml.dart'; | ||||
| class Media { | class Media { | ||||
| @@ -47,7 +48,7 @@ class Media { | |||||
| final License? license; | final License? license; | ||||
| final PeerLink? peerLink; | final PeerLink? peerLink; | ||||
| final Rights? rights; | final Rights? rights; | ||||
| final List<Scene?>? scenes; | |||||
| final List<Scene>? scenes; | |||||
| Media({ | Media({ | ||||
| this.group, | this.group, | ||||
| @@ -79,90 +80,113 @@ class Media { | |||||
| factory Media.parse(XmlElement element) { | factory Media.parse(XmlElement element) { | ||||
| return Media( | 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') | ?.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') | ?.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') | ?.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') | ?.findElements('media:scene') | ||||
| .map((e) { | |||||
| return Scene.parse(e); | |||||
| }).toList() ?? | |||||
| .map((e) => Scene.parse(e)) | |||||
| .toList() ?? | |||||
| [], | [], | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -9,8 +9,7 @@ class Param { | |||||
| this.value, | this.value, | ||||
| }); | }); | ||||
| static Param? parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Param.parse(XmlElement element) { | |||||
| return Param( | return Param( | ||||
| name: element.getAttribute('name'), | name: element.getAttribute('name'), | ||||
| value: element.text, | value: element.text, | ||||
| @@ -11,8 +11,7 @@ class PeerLink { | |||||
| this.value, | this.value, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory PeerLink.parse(XmlElement element) { | |||||
| return PeerLink( | return PeerLink( | ||||
| type: element.getAttribute('type'), | type: element.getAttribute('type'), | ||||
| href: element.getAttribute('href'), | href: element.getAttribute('href'), | ||||
| @@ -13,8 +13,7 @@ class Player { | |||||
| this.value, | this.value, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Player.parse(XmlElement element) { | |||||
| return Player( | return Player( | ||||
| url: element.getAttribute('url'), | url: element.getAttribute('url'), | ||||
| width: int.tryParse(element.getAttribute('width') ?? '0'), | width: int.tryParse(element.getAttribute('width') ?? '0'), | ||||
| @@ -9,8 +9,7 @@ class Rating { | |||||
| this.value, | this.value, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Rating.parse(XmlElement element) { | |||||
| return Rating( | return Rating( | ||||
| scheme: element.getAttribute('scheme'), | scheme: element.getAttribute('scheme'), | ||||
| value: element.text, | value: element.text, | ||||
| @@ -11,8 +11,7 @@ class Restriction { | |||||
| this.value, | this.value, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Restriction.parse(XmlElement element) { | |||||
| return Restriction( | return Restriction( | ||||
| relationship: element.getAttribute('relationship'), | relationship: element.getAttribute('relationship'), | ||||
| type: element.getAttribute('type'), | type: element.getAttribute('type'), | ||||
| @@ -7,8 +7,7 @@ class Rights { | |||||
| this.status, | this.status, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Rights.parse(XmlElement element) { | |||||
| return Rights( | return Rights( | ||||
| status: element.getAttribute('status'), | 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'; | import 'package:xml/xml.dart'; | ||||
| class Scene { | class Scene { | ||||
| @@ -14,13 +14,12 @@ class Scene { | |||||
| this.endTime, | this.endTime, | ||||
| }); | }); | ||||
| static Scene? parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Scene.parse(XmlElement element) { | |||||
| return Scene( | 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, | this.reason, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Status.parse(XmlElement element) { | |||||
| return Status( | return Status( | ||||
| state: element.getAttribute('state'), | state: element.getAttribute('state'), | ||||
| reason: element.getAttribute('reason'), | reason: element.getAttribute('reason'), | ||||
| @@ -9,8 +9,7 @@ class Tags { | |||||
| this.weight, | this.weight, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Tags.parse(XmlElement element) { | |||||
| return Tags( | return Tags( | ||||
| tags: element.text, | tags: element.text, | ||||
| weight: int.tryParse(element.getAttribute('weight') ?? '1'), | weight: int.tryParse(element.getAttribute('weight') ?? '1'), | ||||
| @@ -15,8 +15,7 @@ class Text { | |||||
| this.value, | this.value, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Text.parse(XmlElement element) { | |||||
| return Text( | return Text( | ||||
| type: element.getAttribute('type'), | type: element.getAttribute('type'), | ||||
| lang: element.getAttribute('lang'), | lang: element.getAttribute('lang'), | ||||
| @@ -9,8 +9,7 @@ class Title { | |||||
| this.value, | this.value, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Title.parse(XmlElement element) { | |||||
| return Title( | return Title( | ||||
| type: element.getAttribute('type'), | type: element.getAttribute('type'), | ||||
| value: element.text, | value: element.text, | ||||
| @@ -6,8 +6,7 @@ class RssCategory { | |||||
| RssCategory(this.domain, this.value); | 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 domain = element.getAttribute('domain'); | ||||
| var value = element.text; | var value = element.text; | ||||
| @@ -15,8 +15,7 @@ class RssCloud { | |||||
| this.protocol, | this.protocol, | ||||
| ); | ); | ||||
| static parse(XmlElement? node) { | |||||
| if (node == null) return null; | |||||
| factory RssCloud.parse(XmlElement node) { | |||||
| var domain = node.getAttribute('domain'); | var domain = node.getAttribute('domain'); | ||||
| var port = node.getAttribute('port'); | var port = node.getAttribute('port'); | ||||
| var path = node.getAttribute('path'); | var path = node.getAttribute('path'); | ||||
| @@ -12,18 +12,15 @@ final _imagesRegExp = RegExp( | |||||
| /// | /// | ||||
| class RssContent { | class RssContent { | ||||
| String value; | String value; | ||||
| Iterable<String?> images; | |||||
| Iterable<String> images; | |||||
| RssContent(this.value, this.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 dynamic? content = element.text; | ||||
| final images = <String?>[]; | |||||
| final images = <String>[]; | |||||
| _imagesRegExp.allMatches(content).forEach((match) { | _imagesRegExp.allMatches(content).forEach((match) { | ||||
| images.add(match.group(1)); | |||||
| images.add(match.group(1)!); | |||||
| }); | }); | ||||
| return RssContent(content, images); | return RssContent(content, images); | ||||
| } | } | ||||
| @@ -7,8 +7,7 @@ class RssEnclosure { | |||||
| RssEnclosure(this.url, this.type, this.length); | 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 url = element.getAttribute('url'); | ||||
| var type = element.getAttribute('type'); | var type = element.getAttribute('type'); | ||||
| var length = int.tryParse(element.getAttribute('length') ?? '0'); | 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_image.dart'; | ||||
| import 'package:webfeed/domain/rss_item.dart'; | import 'package:webfeed/domain/rss_item.dart'; | ||||
| import 'package:webfeed/domain/syndication/syndication.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'; | import 'package:xml/xml.dart'; | ||||
| class RssFeed { | class RssFeed { | ||||
| @@ -19,9 +19,9 @@ class RssFeed { | |||||
| final RssImage? image; | final RssImage? image; | ||||
| final RssCloud? cloud; | final RssCloud? cloud; | ||||
| final List<RssCategory?>? categories; | |||||
| final List<RssCategory>? categories; | |||||
| final List<String>? skipDays; | final List<String>? skipDays; | ||||
| final List<int?>? skipHours; | |||||
| final List<int>? skipHours; | |||||
| final String? lastBuildDate; | final String? lastBuildDate; | ||||
| final String? language; | final String? language; | ||||
| final String? generator; | final String? generator; | ||||
| @@ -62,50 +62,62 @@ class RssFeed { | |||||
| factory RssFeed.parse(String xmlString) { | factory RssFeed.parse(String xmlString) { | ||||
| var document = XmlDocument.parse(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) { | if (rss == null && rdf == null) { | ||||
| throw ArgumentError('not a rss feed'); | throw ArgumentError('not a rss feed'); | ||||
| } | } | ||||
| var channelElement = findFirstElement(rss ?? rdf, 'channel'); | |||||
| var channelElement = (rss ?? rdf)!.findElements('channel').firstOrNull; | |||||
| if (channelElement == null) { | if (channelElement == null) { | ||||
| throw ArgumentError('channel not found'); | throw ArgumentError('channel not found'); | ||||
| } | } | ||||
| return RssFeed( | 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') | .findElements('item') | ||||
| .map((e) => RssItem.parse(e)) | .map((e) => RssItem.parse(e)) | ||||
| .toList(), | .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 | categories: channelElement | ||||
| .findElements('category') | .findElements('category') | ||||
| .map((e) => RssCategory.parse(e)) | .map((e) => RssCategory.parse(e)) | ||||
| .toList(), | .toList(), | ||||
| skipDays: findFirstElement(channelElement, 'skipDays') | |||||
| skipDays: channelElement | |||||
| .findElements('skipDays') | |||||
| .firstOrNull | |||||
| ?.findAllElements('day') | ?.findAllElements('day') | ||||
| .map((e) => e.text) | .map((e) => e.text) | ||||
| .toList() ?? | .toList() ?? | ||||
| [], | [], | ||||
| skipHours: findFirstElement(channelElement, 'skipHours') | |||||
| skipHours: channelElement | |||||
| .findElements('skipHours') | |||||
| .firstOrNull | |||||
| ?.findAllElements('hour') | ?.findAllElements('hour') | ||||
| .map((e) => int.tryParse(e.text)) | |||||
| .map((e) => int.tryParse(e.text) ?? 0) | |||||
| .toList() ?? | .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), | dc: DublinCore.parse(channelElement), | ||||
| itunes: Itunes.parse(channelElement), | itunes: Itunes.parse(channelElement), | ||||
| syndication: Syndication.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'; | import 'package:xml/xml.dart'; | ||||
| class RssImage { | class RssImage { | ||||
| @@ -8,12 +8,11 @@ class RssImage { | |||||
| RssImage({this.title, this.url, this.link}); | RssImage({this.title, this.url, this.link}); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory RssImage.parse(XmlElement element) { | |||||
| return RssImage( | 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_enclosure.dart'; | ||||
| import 'package:webfeed/domain/rss_source.dart'; | import 'package:webfeed/domain/rss_source.dart'; | ||||
| import 'package:webfeed/util/datetime.dart'; | import 'package:webfeed/util/datetime.dart'; | ||||
| import 'package:webfeed/util/xml.dart'; | |||||
| import 'package:webfeed/util/iterable.dart'; | |||||
| import 'package:xml/xml.dart'; | import 'package:xml/xml.dart'; | ||||
| class RssItem { | class RssItem { | ||||
| @@ -14,7 +14,7 @@ class RssItem { | |||||
| final String? description; | final String? description; | ||||
| final String? link; | final String? link; | ||||
| final List<RssCategory?>? categories; | |||||
| final List<RssCategory>? categories; | |||||
| final String? guid; | final String? guid; | ||||
| final DateTime? pubDate; | final DateTime? pubDate; | ||||
| final String? author; | final String? author; | ||||
| @@ -45,21 +45,30 @@ class RssItem { | |||||
| factory RssItem.parse(XmlElement element) { | factory RssItem.parse(XmlElement element) { | ||||
| return RssItem( | 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 | categories: element | ||||
| .findElements('category') | .findElements('category') | ||||
| .map((e) => RssCategory.parse(e)) | .map((e) => RssCategory.parse(e)) | ||||
| .toList(), | .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), | media: Media.parse(element), | ||||
| enclosure: RssEnclosure.parse(findFirstElement(element, 'enclosure')), | |||||
| enclosure: element | |||||
| .findElements('enclosure') | |||||
| .map((e) => RssEnclosure.parse(e)) | |||||
| .firstOrNull, | |||||
| dc: DublinCore.parse(element), | dc: DublinCore.parse(element), | ||||
| itunes: Itunes.parse(element), | itunes: Itunes.parse(element), | ||||
| ); | ); | ||||
| @@ -6,8 +6,7 @@ class RssSource { | |||||
| RssSource(this.url, this.value); | 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 url = element.getAttribute('url'); | ||||
| var value = element.text; | var value = element.text; | ||||
| @@ -1,5 +1,5 @@ | |||||
| import 'package:webfeed/util/datetime.dart'; | import 'package:webfeed/util/datetime.dart'; | ||||
| import 'package:webfeed/util/xml.dart'; | |||||
| import 'package:webfeed/util/iterable.dart'; | |||||
| import 'package:xml/xml.dart'; | import 'package:xml/xml.dart'; | ||||
| enum SyndicationUpdatePeriod { hourly, daily, weekly, monthly, yearly } | enum SyndicationUpdatePeriod { hourly, daily, weekly, monthly, yearly } | ||||
| @@ -15,10 +15,9 @@ class Syndication { | |||||
| this.updateBase, | this.updateBase, | ||||
| }); | }); | ||||
| static parse(XmlElement? element) { | |||||
| if (element == null) return null; | |||||
| factory Syndication.parse(XmlElement element) { | |||||
| SyndicationUpdatePeriod updatePeriod; | SyndicationUpdatePeriod updatePeriod; | ||||
| switch (findFirstElement(element, 'sy:updatePeriod')?.text) { | |||||
| switch (element.findElements('sy:updatePeriod').firstOrNull?.text) { | |||||
| case 'hourly': | case 'hourly': | ||||
| updatePeriod = SyndicationUpdatePeriod.hourly; | updatePeriod = SyndicationUpdatePeriod.hourly; | ||||
| break; | break; | ||||
| @@ -41,9 +40,9 @@ class Syndication { | |||||
| return Syndication( | return Syndication( | ||||
| updatePeriod: updatePeriod, | updatePeriod: updatePeriod, | ||||
| updateFrequency: int.tryParse( | 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 'dart:core'; | ||||
| import 'package:webfeed/util/iterable.dart'; | |||||
| import 'package:xml/xml.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( | Iterable<XmlElement>? findElements( | ||||
| XmlNode? node, | XmlNode? node, | ||||
| String name, { | String name, { | ||||
| @@ -34,7 +21,7 @@ Iterable<XmlElement>? findElements( | |||||
| } | } | ||||
| bool parseBoolLiteral(XmlElement element, String tagName) { | 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; | if (v == null) return false; | ||||
| return ['yes', 'true'].contains(v); | return ['yes', 'true'].contains(v); | ||||
| } | } | ||||
| @@ -185,9 +185,9 @@ void main() { | |||||
| expect(item.media!.embed!.width, 512); | expect(item.media!.embed!.width, 512); | ||||
| expect(item.media!.embed!.height, 323); | expect(item.media!.embed!.height, 323); | ||||
| expect(item.media!.embed!.params!.length, 5); | 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!.length, 2); | ||||
| expect(item.media!.responses!.first, 'http://www.response1.com'); | 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!.length, 2); | ||||
| expect(item.media!.prices!.first.price, 19.99); | expect(item.media!.prices!.first.price, 19.99); | ||||
| expect(item.media!.prices!.first.type, 'rent'); | 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!.prices!.first.currency, 'EUR'); | ||||
| expect(item.media!.license!.type, 'text/html'); | expect(item.media!.license!.type, 'text/html'); | ||||
| @@ -218,10 +218,10 @@ void main() { | |||||
| expect(item.media!.rights!.status, 'official'); | expect(item.media!.rights!.status, 'official'); | ||||
| expect(item.media!.scenes!.length, 2); | 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', () { | test('parse Atom-Empty.xml', () { | ||||
| @@ -47,10 +47,10 @@ void main() { | |||||
| expect(feed.cloud!.protocol, 'xml-rpc'); | expect(feed.cloud!.protocol, 'xml-rpc'); | ||||
| expect(feed.categories!.length, 2); | 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!.length, 3); | ||||
| expect(feed.skipDays!.contains('Monday'), true); | 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.guid, 'https://foo.bar.news/1?guid'); | ||||
| expect(feed.items!.first.pubDate, | expect(feed.items!.first.pubDate, | ||||
| DateTime(2018, 03, 26, 14)); //Mon, 26 Mar 2018 14:00:00 PDT | 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.author, 'alice@foo.bar.news'); | ||||
| expect(feed.items!.first.source!.url, 'https://foo.bar.news/1?source'); | expect(feed.items!.first.source!.url, 'https://foo.bar.news/1?source'); | ||||
| expect(feed.items!.first.source!.value, 'Foo Bar'); | expect(feed.items!.first.source!.value, 'Foo Bar'); | ||||
| @@ -192,9 +192,9 @@ void main() { | |||||
| expect(item.media!.embed!.width, 512); | expect(item.media!.embed!.width, 512); | ||||
| expect(item.media!.embed!.height, 323); | expect(item.media!.embed!.height, 323); | ||||
| expect(item.media!.embed!.params!.length, 5); | 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!.length, 2); | ||||
| expect(item.media!.responses!.first, 'http://www.response1.com'); | 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!.length, 2); | ||||
| expect(item.media!.prices!.first.price, 19.99); | expect(item.media!.prices!.first.price, 19.99); | ||||
| expect(item.media!.prices!.first.type, 'rent'); | 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!.prices!.first.currency, 'EUR'); | ||||
| expect(item.media!.license!.type, 'text/html'); | expect(item.media!.license!.type, 'text/html'); | ||||
| @@ -225,10 +225,10 @@ void main() { | |||||
| expect(item.media!.rights!.status, 'official'); | expect(item.media!.rights!.status, 'official'); | ||||
| expect(item.media!.scenes!.length, 2); | 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', () { | test('parse RSS-DC.xml', () { | ||||
| var xmlString = File('test/xml/RSS-DC.xml').readAsStringSync(); | var xmlString = File('test/xml/RSS-DC.xml').readAsStringSync(); | ||||
| @@ -332,12 +332,12 @@ void main() { | |||||
| expect(feed.itunes!.owner!.email, 'editors@changelog.com'); | expect(feed.itunes!.owner!.email, 'editors@changelog.com'); | ||||
| expect( | expect( | ||||
| Set.from([ | Set.from([ | ||||
| feed.itunes!.categories![0]!.category, | |||||
| feed.itunes!.categories![1]!.category | |||||
| feed.itunes!.categories![0].category, | |||||
| feed.itunes!.categories![1].category | |||||
| ]), | ]), | ||||
| ['Technology', 'Foo']); | ['Technology', 'Foo']); | ||||
| for (var category in feed.itunes!.categories!) { | for (var category in feed.itunes!.categories!) { | ||||
| switch (category!.category) { | |||||
| switch (category.category) { | |||||
| case 'Foo': | case 'Foo': | ||||
| expect(category.subCategories, ['Bar', 'Baz']); | expect(category.subCategories, ['Bar', 'Baz']); | ||||
| break; | break; | ||||