///|
/// The iTunes podcast namespace.
pub const ITUNES_NAMESPACE : String = "http://www.itunes.com/dtds/podcast-1.0.dtd"
///|
fn eq_ignore_ascii_case(a : String, b : String) -> Bool {
if a.length() != b.length() {
return false
}
for i in 0..= 97 && ca <= 122 { ca - 32 } else { ca }
let lb = if cb >= 97 && cb <= 122 { cb - 32 } else { cb }
if la != lb {
return false
}
}
true
}
///|
fn is_itunes_namespace(ns : String) -> Bool {
eq_ignore_ascii_case(ns, ITUNES_NAMESPACE)
}
///|
/// An iTunes category with an optional nested subcategory.
pub(all) struct ITunesCategory {
/// The name of the category.
text : String
/// An optional subcategory.
subcategory : ITunesCategory?
} derive(Debug, Eq)
///|
/// Contact information for the owner of a podcast.
pub(all) struct ITunesOwner {
/// The name of the owner.
name : String?
/// The email of the owner.
email : String?
} derive(Debug, Eq)
///|
/// iTunes metadata extracted from a channel.
pub(all) struct ITunesChannelExtension {
/// The author of the podcast.
author : String?
/// `"Yes"` hides the podcast from the iTunes Store.
block : String?
/// The iTunes categories of the podcast.
categories : Array[ITunesCategory]
/// The artwork URL of the podcast.
image : String?
/// Whether the podcast contains explicit content.
explicit : String?
/// Whether the podcast is complete.
complete : String?
/// The new URL where the podcast is located.
new_feed_url : String?
/// The contact information of the podcast owner.
owner : ITunesOwner?
/// A short description of the podcast.
subtitle : String?
/// A long description of the podcast.
summary : String?
/// Comma-separated keywords.
keywords : String?
/// The type of podcast, e.g. `"episodic"` or `"serial"`.
itunes_type : String?
} derive(Debug, Eq)
///|
/// Build a channel extension from parsed generic extensions,
/// mirroring `ITunesChannelExtension::from_map`.
pub fn ITunesChannelExtension::from_map(
map : Map[String, Array[Extension]],
) -> ITunesChannelExtension {
fn value(key : String) -> String? {
match map.get(key) {
Some(list) => if list.length() > 0 { list[0].value } else { None }
None => None
}
}
let categories : Array[ITunesCategory] = []
if map.get("category") is Some(list) {
for ext in list {
categories.push(itunes_category_from(ext))
}
}
let owner : ITunesOwner? = match map.get("owner") {
Some(list) =>
if list.length() > 0 {
let ext = list[0]
Some({
name: child_value(ext, "name"),
email: child_value(ext, "email"),
})
} else {
None
}
None => None
}
{
author: value("author"),
block: value("block"),
categories,
image: attr_of(map, "image", "href"),
explicit: value("explicit"),
complete: value("complete"),
new_feed_url: value("new-feed-url"),
owner,
subtitle: value("subtitle"),
summary: value("summary"),
keywords: value("keywords"),
itunes_type: value("type"),
}
}
///|
/// iTunes metadata extracted from an item.
pub(all) struct ITunesItemExtension {
/// The author of the episode.
author : String?
/// Whether the episode is blocked from the iTunes Store.
block : String?
/// The artwork URL of the episode.
image : String?
/// The duration in HH:MM:SS, MM:SS or M:SS form.
duration : String?
/// Whether the episode contains explicit content.
explicit : String?
/// Whether the episode has closed captioning.
closed_captioned : String?
/// Overrides default sorting order.
order : String?
/// A short description.
subtitle : String?
/// A long description.
summary : String?
/// Comma-separated keywords.
keywords : String?
/// The episode number.
episode : String?
/// The season number.
season : String?
/// Whether the episode is a full or trailer, e.g. `"full"`.
episode_type : String?
} derive(Debug, Eq)
///|
/// Build an item extension from parsed generic extensions,
/// mirroring `ITunesItemExtension::from_map`.
pub fn ITunesItemExtension::from_map(
map : Map[String, Array[Extension]],
) -> ITunesItemExtension {
fn value(key : String) -> String? {
match map.get(key) {
Some(list) => if list.length() > 0 { list[0].value } else { None }
None => None
}
}
{
author: value("author"),
block: value("block"),
image: attr_of(map, "image", "href"),
duration: value("duration"),
explicit: value("explicit"),
closed_captioned: value("isClosedCaptioned"),
order: value("order"),
subtitle: value("subtitle"),
summary: value("summary"),
keywords: value("keywords"),
episode: value("episode"),
season: value("season"),
episode_type: value("episodeType"),
}
}
///|
fn attr_of(
map : Map[String, Array[Extension]],
key : String,
attr : String,
) -> String? {
match map.get(key) {
Some(list) => if list.length() > 0 { list[0].attrs.get(attr) } else { None }
None => None
}
}
///|
fn child_value(ext : Extension, name : String) -> String? {
match ext.children.get(name) {
Some(list) => if list.length() > 0 { list[0].value } else { None }
None => None
}
}
///|
fn itunes_category_from(ext : Extension) -> ITunesCategory {
// Mirrors the crate: the name comes from the `text` attribute and the
// nested category from a child element named `category`.
let text = ext.attrs.get("text").unwrap_or("")
let subcategory : ITunesCategory? = match ext.children.get("category") {
Some(list) =>
if list.length() > 0 {
let child = list[0]
Some({ text: child.attrs.get("text").unwrap_or(""), subcategory: None })
} else {
None
}
None => None
}
{ text, subcategory }
}