Skip to content

Commit

Permalink
Add support for liveItem parsing within the podcast extension (Resolves
Browse files Browse the repository at this point in the history
#39) (#45)

Co-authored-by: maxdreherwalsworth <[email protected]>
  • Loading branch information
matthew-carroll and maxdreherwalsworth authored Jan 14, 2024
1 parent 9216498 commit fa0aba6
Show file tree
Hide file tree
Showing 13 changed files with 503 additions and 6 deletions.
5 changes: 4 additions & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ linter:
rules:
- always_use_package_imports
- avoid_print
- curly_braces_in_flow_control_structures
- curly_braces_in_flow_control_structures
- prefer_const_constructors
- prefer_const_constructors_in_immutables
- prefer_const_declarations
14 changes: 14 additions & 0 deletions lib/dart_rss.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,17 @@ export 'domain/rss_feed.dart';
export 'domain/rss_image.dart';
export 'domain/rss_item.dart';
export 'domain/rss_source.dart';

// Podcasts
export 'domain/podcast_index/rss_podcast_index.dart';
export 'domain/podcast_index/rss_podcast_index_alternate_enclosure.dart';
export 'domain/podcast_index/rss_podcast_index_chapters.dart';
export 'domain/podcast_index/rss_podcast_index_content_link.dart';
export 'domain/podcast_index/rss_podcast_index_funding.dart';
export 'domain/podcast_index/rss_podcast_index_guid.dart';
export 'domain/podcast_index/rss_podcast_index_live_item.dart';
export 'domain/podcast_index/rss_podcast_index_locked.dart';
export 'domain/podcast_index/rss_podcast_index_person.dart';
export 'domain/podcast_index/rss_podcast_index_soudbite.dart';
export 'domain/podcast_index/rss_podcast_index_transcript.dart';
export 'domain/podcast_index/rss_podcast_live_item_images.dart';
7 changes: 7 additions & 0 deletions lib/domain/podcast_index/rss_podcast_index.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:dart_rss/domain/podcast_index/rss_podcast_index_funding.dart';
import 'package:dart_rss/domain/podcast_index/rss_podcast_index_live_item.dart';
import 'package:dart_rss/domain/podcast_index/rss_podcast_index_locked.dart';
import 'package:dart_rss/domain/podcast_index/rss_podcast_index_person.dart';
import 'package:dart_rss/util/helpers.dart';
Expand All @@ -21,13 +22,17 @@ class RssPodcastIndex {
locked: RssPodcastIndexLocked.parse(
findElementOrNull(element, 'podcast:locked'),
),
liveItems: element.findElements('podcast:liveItem').map((e) {
return RssPodcastIndexLiveItem.parse(e);
}).toList(),
);
}

const RssPodcastIndex({
this.funding,
this.persons,
this.locked,
this.liveItems,
});

/// List of funding tags.
Expand All @@ -38,4 +43,6 @@ class RssPodcastIndex {

/// The purpose is to tell other podcast hosting platforms whether they are allowed to import this feed.
final RssPodcastIndexLocked? locked;

final List<RssPodcastIndexLiveItem?>? liveItems;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:dart_rss/util/helpers.dart';
import 'package:xml/xml.dart';

// TODO: Complete the definition for the "alternativeEnclosure" tag (
/// The `alternativeEnclosure` XML element.
///
/// https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#alternate-enclosure
class RssPodcastIndexAlternateEnclosure {
static RssPodcastIndexAlternateEnclosure? parse(XmlElement? element) {
if (element == null) {
return null;
}

return RssPodcastIndexAlternateEnclosure(
type: element.getAttribute("type"),
length: parseInt(element.getAttribute("length")),
isDefault: parseBool(element.getAttribute("default")),
);
}

const RssPodcastIndexAlternateEnclosure({
required this.type,
required this.length,
required this.isDefault,
});

final String? type;
final int? length;
final bool? isDefault;

@override
String toString() {
return 'RssPodcastIndexAlternateEnclosure{type: $type, length: $length, isDefault: $isDefault}';
}
}
23 changes: 23 additions & 0 deletions lib/domain/podcast_index/rss_podcast_index_content_link.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:xml/xml.dart';

class RssPodcastIndexContentLink {
static RssPodcastIndexContentLink parse(XmlElement element) {
return RssPodcastIndexContentLink(
value: element.innerText,
href: element.getAttribute("href"),
);
}

const RssPodcastIndexContentLink({
this.href,
this.value,
});

final String? href;
final String? value;

@override
String toString() {
return 'RssPodcastIndexContentLink{href: $href, value: $value}';
}
}
28 changes: 28 additions & 0 deletions lib/domain/podcast_index/rss_podcast_index_guid.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:dart_rss/util/helpers.dart';
import 'package:xml/xml.dart';

class RssPodcastIndexGuid {
static RssPodcastIndexGuid? parse(XmlElement? element) {
if (element == null) {
return null;
}

return RssPodcastIndexGuid(
isPermalink: parseBool(element.getAttribute("isPermaLink")),
value: element.innerText,
);
}

const RssPodcastIndexGuid({
this.isPermalink,
this.value,
});

final bool? isPermalink;
final String? value;

@override
String toString() {
return 'RssPodcastIndexGuid{isPermalink: $isPermalink, value: $value}';
}
}
87 changes: 87 additions & 0 deletions lib/domain/podcast_index/rss_podcast_index_live_item.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import 'package:dart_rss/domain/podcast_index/rss_podcast_index_alternate_enclosure.dart';
import 'package:dart_rss/domain/podcast_index/rss_podcast_index_content_link.dart';
import 'package:dart_rss/domain/podcast_index/rss_podcast_index_guid.dart';
import 'package:dart_rss/domain/podcast_index/rss_podcast_index_person.dart';
import 'package:dart_rss/domain/podcast_index/rss_podcast_live_item_images.dart';
import 'package:dart_rss/domain/rss_enclosure.dart';
import 'package:dart_rss/domain/rss_itunes_image.dart';
import 'package:dart_rss/util/helpers.dart';
import 'package:xml/xml.dart';

/// The `liveItem` XML element.
///
/// https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#live-item
class RssPodcastIndexLiveItem {
static RssPodcastIndexLiveItem? parse(XmlElement? element) {
if (element == null) {
return null;
}

return RssPodcastIndexLiveItem(
//elements
title: findElementOrNull(element, "title")?.innerText,
description: findElementOrNull(element, "description")?.innerText,
link: findElementOrNull(element, "link")?.innerText,
author: findElementOrNull(element, "author")?.innerText,

// attributes
status: element.getAttribute("status"),
start: element.getAttribute("start"),
end: element.getAttribute("end"),

// classes
guid: RssPodcastIndexGuid.parse(findElementOrNull(element, "guid")),

images: RssPodcastIndexLiveItemImages.parse(findElementOrNull(element, "podcast:images")),

itunesImage: RssItunesImage.parse(findElementOrNull(element, "itunes:image")),
alternateEnclosure:
RssPodcastIndexAlternateEnclosure.parse(findElementOrNull(element, "podcast:alternateEnclosure")),

persons: element.findElements('podcast:person').map((e) => RssPodcastIndexPerson.parse(e)).toList(),
enclosure: RssEnclosure.parse(findElementOrNull(element, "enclosure")),

contentLinks:
element.findElements('podcast:contentLink').map((e) => RssPodcastIndexContentLink.parse(e)).toList(),
);
}

const RssPodcastIndexLiveItem({
this.title,
this.description,
this.link,
this.author,
this.status,
this.start,
this.end,
this.guid,
this.images,
this.itunesImage,
this.alternateEnclosure,
this.persons,
this.enclosure,
this.contentLinks,
});

final String? title;
final String? description;
final String? link;
final String? author;

final String? status;
final String? start;
final String? end;

final RssPodcastIndexGuid? guid;
final RssPodcastIndexLiveItemImages? images;
final RssItunesImage? itunesImage;
final RssPodcastIndexAlternateEnclosure? alternateEnclosure;
final List<RssPodcastIndexPerson>? persons;
final RssEnclosure? enclosure;
final List<RssPodcastIndexContentLink>? contentLinks;

@override
String toString() {
return 'RssPodcastIndexLiveItem{title: $title, description: $description, link: $link, author: $author, status: $status, start: $start, end: $end, guid: $guid, images: $images, alternateEnclosure: $alternateEnclosure, persons: $persons, enclosure: $enclosure, contentLinks: $contentLinks}';
}
}
25 changes: 25 additions & 0 deletions lib/domain/podcast_index/rss_podcast_live_item_images.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:xml/xml.dart';

class RssPodcastIndexLiveItemImages {
static RssPodcastIndexLiveItemImages? parse(XmlElement? element) {
if (element == null) {
return null;
}

final imagesElement = element.getAttribute("srcset");
if (imagesElement == null) {
return const RssPodcastIndexLiveItemImages(urls: null);
}
final urls = imagesElement.split(",").map((e) => RegExp(r"^[^s]+").firstMatch(e).toString()).toList();
return RssPodcastIndexLiveItemImages(urls: urls);
}

const RssPodcastIndexLiveItemImages({required this.urls});

final List<String>? urls;

@override
String toString() {
return 'RssPodcastLiveItemImages{urls: $urls}';
}
}
4 changes: 4 additions & 0 deletions lib/domain/rss_feed.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,9 @@ class RssFeed {
final int ttl;
final DublinCore? dc;
final RssItunes? itunes;

// TODO: Rework the model for RssPodcastIndex. This looks like an artificial construct that was
// introduced to hold podcast extensions, even though some/all of those extensions are
// supposed to be able to apply directly to a channel.
final RssPodcastIndex? podcastIndex;
}
19 changes: 16 additions & 3 deletions lib/util/helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,16 @@ List<XmlElement>? findAllDirectElementsOrNull(XmlElement element, String name, {

bool? parseBoolLiteral(XmlElement element, String tagName) {
final v = findElementOrNull(element, tagName)?.innerText.toLowerCase().trim();
if (v == null) return null;
if (v == null) {
return null;
}
return ['yes', 'true'].contains(v);
}

bool? parseBool(String? v) {
if (v == null) {
return null;
}
return ['yes', 'true'].contains(v);
}

Expand Down Expand Up @@ -53,11 +62,15 @@ extension SafeParseDateTime on DateTime {
}

DateTime? parseDateTime(String? dateTimeString) {
if (dateTimeString == null) return null;
if (dateTimeString == null) {
return null;
}
return DateTime.tryParse(dateTimeString);
}

int? parseInt(String? intString) {
if (intString == null) return null;
if (intString == null) {
return null;
}
return int.tryParse(intString);
}
2 changes: 1 addition & 1 deletion test/rss1_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ void main() {
);
expect(
firstItem.content!.images,
Iterable.empty(),
const Iterable.empty(),
);
});

Expand Down
Loading

0 comments on commit fa0aba6

Please sign in to comment.