Skip to content

Commit cf3bb97

Browse files
authored
Merge pull request #19 from magodo/itunes_namespace
RSS feed add element with "itunes:" prefix (for podcast)
2 parents ef04c18 + 67f38f8 commit cf3bb97

12 files changed

+415
-19
lines changed

lib/domain/rss_feed.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import 'package:webfeed/domain/rss_item.dart';
88
import 'package:webfeed/util/helpers.dart';
99
import 'package:xml/xml.dart';
1010

11+
import 'rss_itunes.dart';
12+
1113
class RssFeed {
1214
final String title;
1315
final String author;
@@ -30,6 +32,7 @@ class RssFeed {
3032
final String webMaster;
3133
final int ttl;
3234
final DublinCore dc;
35+
final RssItunes itunes;
3336

3437
RssFeed({
3538
this.title,
@@ -52,6 +55,7 @@ class RssFeed {
5255
this.webMaster,
5356
this.ttl,
5457
this.dc,
58+
this.itunes,
5559
});
5660

5761
factory RssFeed.parse(String xmlString) {
@@ -98,6 +102,7 @@ class RssFeed {
98102
webMaster: findElementOrNull(channelElement, "webMaster")?.text,
99103
ttl: int.tryParse(findElementOrNull(channelElement, "ttl")?.text ?? "0"),
100104
dc: DublinCore.parse(channelElement),
105+
itunes: RssItunes.parse(channelElement),
101106
);
102107
}
103108
}

lib/domain/rss_item.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import 'package:webfeed/domain/rss_source.dart';
77
import 'package:webfeed/util/helpers.dart';
88
import 'package:xml/xml.dart';
99

10+
import 'rss_item_itunes.dart';
11+
1012
class RssItem {
1113
final String title;
1214
final String description;
@@ -22,6 +24,7 @@ class RssItem {
2224
final Media media;
2325
final RssEnclosure enclosure;
2426
final DublinCore dc;
27+
final RssItemItunes itunes;
2528

2629
RssItem({
2730
this.title,
@@ -37,6 +40,7 @@ class RssItem {
3740
this.media,
3841
this.enclosure,
3942
this.dc,
43+
this.itunes,
4044
});
4145

4246
factory RssItem.parse(XmlElement element) {
@@ -56,6 +60,7 @@ class RssItem {
5660
media: Media.parse(element),
5761
enclosure: RssEnclosure.parse(findElementOrNull(element, "enclosure")),
5862
dc: DublinCore.parse(element),
63+
itunes: RssItemItunes.parse(element),
5964
);
6065
}
6166
}

lib/domain/rss_item_itunes.dart

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import 'package:webfeed/util/helpers.dart';
2+
import 'package:xml/xml.dart';
3+
4+
import 'rss_itunes_category.dart';
5+
import 'rss_itunes_episode_type.dart';
6+
import 'rss_itunes_image.dart';
7+
8+
class RssItemItunes {
9+
final String title;
10+
final int episode;
11+
final int season;
12+
final Duration duration;
13+
final RssItunesEpisodeType episodeType;
14+
final String author;
15+
final String summary;
16+
final bool explicit;
17+
final String subtitle;
18+
final List<String> keywords;
19+
final RssItunesImage image;
20+
final RssItunesCategory category;
21+
final bool block;
22+
23+
RssItemItunes({
24+
this.title,
25+
this.episode,
26+
this.season,
27+
this.duration,
28+
this.episodeType,
29+
this.author,
30+
this.summary,
31+
this.explicit,
32+
this.subtitle,
33+
this.keywords,
34+
this.image,
35+
this.category,
36+
this.block,
37+
});
38+
39+
factory RssItemItunes.parse(XmlElement element) {
40+
if (element == null) {
41+
return null;
42+
}
43+
var episodeStr = findElementOrNull(element, "itunes:episode")?.text?.trim();
44+
var seasonStr = findElementOrNull(element, "itunes:season")?.text?.trim();
45+
var durationStr = findElementOrNull(element, "itunes:duration")?.text?.trim();
46+
47+
return RssItemItunes(
48+
title: findElementOrNull(element, "itunes:title")?.text?.trim(),
49+
episode: episodeStr == null ? null : int.parse(episodeStr),
50+
season: seasonStr == null ? null : int.parse(seasonStr),
51+
duration: durationStr == null ? null : parseDuration(durationStr),
52+
episodeType: newRssItunesEpisodeType(findElementOrNull(element, "itunes:episodeType")),
53+
author: findElementOrNull(element, "itunes:author")?.text?.trim(),
54+
summary: findElementOrNull(element, "itunes:summary")?.text?.trim(),
55+
explicit: parseBoolLiteral(element, "itunes:explicit"),
56+
subtitle: findElementOrNull(element, "itunes:subtitle")?.text?.trim(),
57+
keywords: findElementOrNull(element, "itunes:keywords")?.text?.split(",")?.map((keyword) => keyword.trim())?.toList(),
58+
image: RssItunesImage.parse(findElementOrNull(element, "itunes:image")),
59+
category: RssItunesCategory.parse(
60+
findElementOrNull(element, "itunes:category")),
61+
block: parseBoolLiteral(element, "itunes:block"),
62+
);
63+
}
64+
}
65+
66+
Duration parseDuration(String s) {
67+
var hours = 0;
68+
var minutes = 0;
69+
var seconds = 0;
70+
var parts = s.split(':');
71+
if (parts.length > 2) {
72+
hours = int.parse(parts[parts.length - 3]);
73+
}
74+
if (parts.length > 1) {
75+
minutes = int.parse(parts[parts.length - 2]);
76+
}
77+
seconds = int.parse(parts[parts.length - 1]);
78+
return Duration(
79+
hours: hours,
80+
minutes: minutes,
81+
seconds: seconds,
82+
);
83+
}

lib/domain/rss_itunes.dart

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import 'package:xml/xml.dart';
2+
3+
import 'package:webfeed/util/helpers.dart';
4+
5+
import 'rss_itunes_category.dart';
6+
import 'rss_itunes_image.dart';
7+
import 'rss_itunes_owner.dart';
8+
import 'rss_itunes_type.dart';
9+
10+
class RssItunes {
11+
final String author;
12+
final String summary;
13+
final bool explicit;
14+
final String title;
15+
final String subtitle;
16+
final RssItunesOwner owner;
17+
final List<String> keywords;
18+
final RssItunesImage image;
19+
final List<RssItunesCategory> categories;
20+
final RssItunesType type;
21+
final String newFeedUrl;
22+
final bool block;
23+
final bool complete;
24+
25+
RssItunes({
26+
this.author,
27+
this.summary,
28+
this.explicit,
29+
this.title,
30+
this.subtitle,
31+
this.owner,
32+
this.keywords,
33+
this.image,
34+
this.categories,
35+
this.type,
36+
this.newFeedUrl,
37+
this.block,
38+
this.complete,
39+
});
40+
41+
factory RssItunes.parse(XmlElement element) {
42+
if (element == null) {
43+
return null;
44+
}
45+
return RssItunes(
46+
author: findElementOrNull(element, "itunes:author")?.text?.trim(),
47+
summary: findElementOrNull(element, "itunes:summary")?.text?.trim(),
48+
explicit: parseBoolLiteral(element, "itunes:explicit"),
49+
title: findElementOrNull(element, "itunes:title")?.text?.trim(),
50+
subtitle: findElementOrNull(element, "itunes:subtitle")?.text?.trim(),
51+
owner: RssItunesOwner.parse(findElementOrNull(element, "itunes:owner")),
52+
keywords: findElementOrNull(element, "itunes:keywords")
53+
?.text
54+
?.split(",")
55+
?.map((keyword) => keyword.trim())
56+
?.toList(),
57+
image: RssItunesImage.parse(findElementOrNull(element, "itunes:image")),
58+
categories: findAllDirectElementsOrNull(element, "itunes:category")
59+
.map((ele) => RssItunesCategory.parse(ele))
60+
.toList(),
61+
type: newRssItunesType(findElementOrNull(element, "itunes:type")),
62+
newFeedUrl:
63+
findElementOrNull(element, "itunes:new-feed-url")?.text?.trim(),
64+
block: parseBoolLiteral(element, "itunes:block"),
65+
complete: parseBoolLiteral(element, "itunes:complete"),
66+
);
67+
}
68+
}
69+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import 'package:xml/xml.dart';
2+
3+
class RssItunesCategory {
4+
final String category;
5+
final List<String> subCategories;
6+
7+
RssItunesCategory({this.category, this.subCategories});
8+
9+
factory RssItunesCategory.parse(XmlElement element) {
10+
if (element == null) return null;
11+
12+
Iterable<XmlElement> subCategories;
13+
try {
14+
subCategories = element.findElements("itunes:category");
15+
} on StateError {
16+
subCategories = null;
17+
}
18+
return RssItunesCategory(
19+
category: element.getAttribute("text")?.trim(),
20+
subCategories:
21+
subCategories?.map((ele) => ele.getAttribute("text")?.trim())?.toList(),
22+
);
23+
}
24+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import 'package:xml/xml.dart';
2+
3+
enum RssItunesEpisodeType {full, trailer, bonus}
4+
5+
RssItunesEpisodeType newRssItunesEpisodeType(XmlElement element) {
6+
// "full" is default type
7+
if (element == null) return RssItunesEpisodeType.full;
8+
9+
switch (element.text) {
10+
case "full":
11+
return RssItunesEpisodeType.full;
12+
case "trailer":
13+
return RssItunesEpisodeType.trailer;
14+
case "bonus":
15+
return RssItunesEpisodeType.bonus;
16+
default:
17+
return null;
18+
}
19+
}

lib/domain/rss_itunes_image.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import 'package:xml/xml.dart';
2+
3+
class RssItunesImage {
4+
final String href;
5+
6+
RssItunesImage({this.href});
7+
8+
factory RssItunesImage.parse(XmlElement element) {
9+
if (element == null) return null;
10+
return RssItunesImage(
11+
href: element.getAttribute("href")?.trim(),
12+
);
13+
}
14+
}

lib/domain/rss_itunes_owner.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import 'package:xml/xml.dart';
2+
3+
import '../util/helpers.dart';
4+
5+
class RssItunesOwner {
6+
final String name;
7+
final String email;
8+
9+
RssItunesOwner({this.name, this.email});
10+
11+
factory RssItunesOwner.parse(XmlElement element) {
12+
if (element == null) return null;
13+
return RssItunesOwner(
14+
name: findElementOrNull(element, "itunes:name")?.text?.trim(),
15+
email: findElementOrNull(element, "itunes:email")?.text?.trim(),
16+
);
17+
}
18+
}

lib/domain/rss_itunes_type.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'package:xml/xml.dart';
2+
3+
enum RssItunesType { episodic, serial }
4+
5+
RssItunesType newRssItunesType(XmlElement element) {
6+
// "episodic" is default type
7+
if (element == null) return RssItunesType.episodic;
8+
9+
switch (element.text) {
10+
case "episodic":
11+
return RssItunesType.episodic;
12+
case "serial":
13+
return RssItunesType.serial;
14+
default:
15+
return null;
16+
}
17+
}

lib/util/helpers.dart

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,27 @@ import 'dart:core';
22

33
import 'package:xml/xml.dart';
44

5-
XmlElement findElementOrNull(XmlElement element, String name) {
5+
XmlElement findElementOrNull(XmlElement element, String name,
6+
{String namespace}) {
67
try {
7-
return element.findAllElements(name).first;
8+
return element.findAllElements(name, namespace: namespace).first;
89
} on StateError {
910
return null;
1011
}
1112
}
13+
14+
List<XmlElement> findAllDirectElementsOrNull(XmlElement element, String name,
15+
{String namespace}) {
16+
try {
17+
return element.findElements(name, namespace: namespace).toList();
18+
} on StateError {
19+
return null;
20+
}
21+
}
22+
23+
bool parseBoolLiteral(XmlElement element, String tagName) {
24+
var v = findElementOrNull(element, tagName)?.text?.toLowerCase()?.trim();
25+
if (v == null) return null;
26+
return ["yes", "true"].contains(v);
27+
}
28+

0 commit comments

Comments
 (0)