Skip to content

Commit d6f4906

Browse files
committed
RSS feed add element with "itunes:" prefix (for podcast)
1 parent ef04c18 commit d6f4906

File tree

9 files changed

+262
-2
lines changed

9 files changed

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

lib/domain/rss_itunes.dart

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
8+
class RssItunes {
9+
final String author;
10+
final String summary;
11+
final bool explicit;
12+
final String subtitle;
13+
final RssItunesOwner owner;
14+
final List<String> keywords;
15+
final RssItunesImage image;
16+
final RssItunesCategory category;
17+
18+
RssItunes({
19+
this.author,
20+
this.summary,
21+
this.explicit,
22+
this.subtitle,
23+
this.owner,
24+
this.keywords,
25+
this.image,
26+
this.category,
27+
});
28+
29+
factory RssItunes.parse(XmlElement element) {
30+
if (element == null) {
31+
return null;
32+
}
33+
var explicitStr =
34+
findElementOrNull(element, "itunes:explicit")?.text?.toLowerCase()?.trim();
35+
36+
return RssItunes(
37+
author: findElementOrNull(element, "itunes:author")?.text?.trim(),
38+
summary: findElementOrNull(element, "itunes:summary")?.text?.trim(),
39+
explicit: explicitStr == null
40+
? null
41+
: explicitStr == "yes" || explicitStr == "true",
42+
subtitle: findElementOrNull(element, "itunes:subtitle")?.text?.trim(),
43+
owner: RssItunesOwner.parse(findElementOrNull(element, "itunes:owner")),
44+
keywords: findElementOrNull(element, "itunes:keywords")?.text?.split(",")?.map((keyword) => keyword.trim())?.toList(),
45+
image: RssItunesImage.parse(findElementOrNull(element, "itunes:image")),
46+
category: RssItunesCategory.parse(
47+
findElementOrNull(element, "itunes:category")),
48+
);
49+
}
50+
}
51+
52+
class RssItunesOwner {
53+
final String name;
54+
final String email;
55+
56+
RssItunesOwner({this.name, this.email});
57+
58+
factory RssItunesOwner.parse(XmlElement element) {
59+
if (element == null) return null;
60+
return RssItunesOwner(
61+
name: findElementOrNull(element, "itunes:name")?.text?.trim(),
62+
email: findElementOrNull(element, "itunes:email")?.text?.trim(),
63+
);
64+
}
65+
}
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.findAllElements("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+
}

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/util/helpers.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ 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, {String namespace}) {
66
try {
7-
return element.findAllElements(name).first;
7+
return element.findAllElements(name, namespace: namespace).first;
88
} on StateError {
99
return null;
1010
}

test/rss_test.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,4 +295,31 @@ void main() {
295295

296296
expect(feed.items.first.content, null);
297297
});
298+
299+
test("parse RSS-Itunes.xml", () {
300+
var xmlString = File("test/xml/RSS-Itunes.xml").readAsStringSync();
301+
302+
var feed = RssFeed.parse(xmlString);
303+
304+
expect(feed.itunes.author, "Changelog Media");
305+
expect(feed.itunes.summary, "Foo");
306+
expect(feed.itunes.explicit, false);
307+
expect(feed.itunes.image.href, "https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357");
308+
expect(feed.itunes.keywords, "go,golang,open source,software,development".split(","));
309+
expect(feed.itunes.owner.name, "Changelog Media");
310+
expect(feed.itunes.owner.email, "[email protected]");
311+
expect(feed.itunes.category.category, "Technology");
312+
expect(feed.itunes.category.subCategories, ["Software How-To", "Tech News"]);
313+
314+
var item = feed.items[0];
315+
expect(item.itunes.episodeType, "full");
316+
expect(item.itunes.episode, 1);
317+
expect(item.itunes.image.href, "https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357");
318+
expect(item.itunes.duration, Duration(minutes: 32, seconds: 30));
319+
expect(item.itunes.explicit, false);
320+
expect(item.itunes.keywords, "go,golang,open source,software,development".split(","));
321+
expect(item.itunes.subtitle, "with Erik, Carlisia, and Brian");
322+
expect(item.itunes.summary, "Foo");
323+
expect(item.itunes.author, "Erik St. Martin, Carlisia Pinto, and Brian Ketelsen");
324+
});
298325
}

test/xml/RSS-Itunes.xml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0" nighteye="disabled">
2+
<channel>
3+
<title>Go Time</title>
4+
<copyright>All rights reserved</copyright>
5+
<link>https://changelog.com/gotime</link>
6+
<atom:link href="https://changelog.com/gotime/feed" rel="self" type="application/rss+xml"/>
7+
<atom:link href="https://changelog.com/gotime/feed" rel="first" type="application/rss+xml"/>
8+
<atom:link href="https://changelog.com/gotime/feed?page=1" rel="last" type="application/rss+xml"/>
9+
<atom:link href="https://changelog.com/gotime" rel="alternate" type="text/html"/>
10+
<language>en-us</language>
11+
<description>
12+
A diverse panel and special guests discuss cloud infrastructure, distributed systems, microservices, Kubernetes, Docker…oh and also Go! This show records LIVE every Thursday at 3pm US Eastern. A diverse panel and special guests discuss cloud infrastructure, distributed systems, microservices, Kubernetes, Docker…oh and also Go!
13+
</description>
14+
<itunes:author>Changelog Media</itunes:author>
15+
<itunes:summary>Foo</itunes:summary>
16+
<itunes:explicit>no</itunes:explicit>
17+
<itunes:image href="https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357"/>
18+
<itunes:keywords>go, golang, open source, software, development</itunes:keywords>
19+
<itunes:owner>
20+
<itunes:name>Changelog Media</itunes:name>
21+
<itunes:email>[email protected]</itunes:email>
22+
</itunes:owner>
23+
<itunes:category text="Technology">
24+
<itunes:category text="Software How-To"/>
25+
<itunes:category text="Tech News"/>
26+
</itunes:category>
27+
<item>
28+
<itunes:episodeType>full</itunes:episodeType>
29+
<itunes:episode>1</itunes:episode>
30+
<itunes:image href="https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357"/>
31+
<itunes:duration>32:30</itunes:duration>
32+
<itunes:explicit>no</itunes:explicit>
33+
<itunes:keywords>go, golang, open source, software, development</itunes:keywords>
34+
<itunes:subtitle>with Erik, Carlisia, and Brian</itunes:subtitle>
35+
<itunes:summary>Foo</itunes:summary>
36+
<dc:creator>
37+
Erik St. Martin, Carlisia Pinto, and Brian Ketelsen
38+
</dc:creator>
39+
<itunes:author>
40+
Erik St. Martin, Carlisia Pinto, and Brian Ketelsen
41+
</itunes:author>
42+
</item>
43+
</channel>
44+
</rss>

0 commit comments

Comments
 (0)