Skip to content

Commit

Permalink
implement Atom extension
Browse files Browse the repository at this point in the history
  • Loading branch information
andy128k committed Nov 7, 2020
1 parent 549f219 commit 6b6eac1
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 1 deletion.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ all-features = false

[features]
default = ["builders"]
builders = ["derive_builder"]
builders = ["derive_builder", "atom_syndication/builders"]
validation = ["chrono", "url", "mime"]
with-serde = ["serde", "atom_syndication/with-serde"]
atom = ["atom_syndication"]

[dependencies]
quick-xml = { version = "0.20", features = ["encoding"] }
Expand All @@ -26,6 +28,7 @@ chrono = {version = "0.4", optional = true }
url = { version = "2.1", optional = true }
mime = { version = "0.3", optional = true }
serde = { version = "1.0", optional = true, features = ["derive"] }
atom_syndication = { version = "0.9", optional = true }

[dev-dependencies]
bencher = "0.1"
Expand Down
51 changes: 51 additions & 0 deletions src/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ use quick_xml::Writer;
use crate::category::Category;
use crate::cloud::Cloud;
use crate::error::Error;
#[cfg(feature = "atom")]
use crate::extension::atom;
use crate::extension::dublincore;
use crate::extension::itunes;
use crate::extension::syndication;
Expand Down Expand Up @@ -77,6 +79,9 @@ pub struct Channel {
pub items: Vec<Item>,
/// The extensions for the channel.
pub extensions: ExtensionMap,
/// The Atom extension for the channel.
#[cfg(feature = "atom")]
pub atom_ext: Option<atom::AtomExtension>,
/// The iTunes extension for the channel.
pub itunes_ext: Option<itunes::ITunesChannelExtension>,
/// The Dublin Core extension for the channel.
Expand Down Expand Up @@ -779,6 +784,42 @@ impl Channel {
self.items = items.into();
}

/// Return the Atom extension for this channel.
///
/// # Examples
///
/// ```
/// use rss::Channel;
/// use rss::extension::atom::AtomExtension;
///
/// let mut channel = Channel::default();
/// channel.set_atom_ext(AtomExtension::default());
/// assert!(channel.atom_ext().is_some());
/// ```
#[cfg(feature = "atom")]
pub fn atom_ext(&self) -> Option<&atom::AtomExtension> {
self.atom_ext.as_ref()
}

/// Set the Atom extension for this channel.
///
/// # Examples
///
/// ```
/// use rss::Channel;
/// use rss::extension::atom::AtomExtension;
///
/// let mut channel = Channel::default();
/// channel.set_atom_ext(AtomExtension::default());
/// ```
#[cfg(feature = "atom")]
pub fn set_atom_ext<V>(&mut self, atom_ext: V)
where
V: Into<Option<atom::AtomExtension>>,
{
self.atom_ext = atom_ext.into();
}

/// Return the iTunes extension for this channel.
///
/// # Examples
Expand Down Expand Up @@ -1255,6 +1296,11 @@ impl Channel {
// Process each of the namespaces we know (note that the values are not removed prior and reused to support pass-through of unknown extensions)
for (prefix, namespace) in namespaces {
match namespace.as_ref() {
#[cfg(feature = "atom")]
atom::NAMESPACE => channel
.extensions
.remove(prefix)
.map(|v| channel.atom_ext = Some(atom::AtomExtension::from_map(v))),
itunes::NAMESPACE => channel.extensions.remove(prefix).map(|v| {
channel.itunes_ext = Some(itunes::ITunesChannelExtension::from_map(v))
}),
Expand Down Expand Up @@ -1364,6 +1410,11 @@ impl ToXml for Channel {
}
}

#[cfg(feature = "atom")]
if let Some(ext) = &self.atom_ext {
ext.to_xml(writer)?;
}

if let Some(ext) = &self.itunes_ext {
ext.to_xml(writer)?;
}
Expand Down
104 changes: 104 additions & 0 deletions src/extension/atom.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// This file is part of rss.
//
// Copyright © 2015-2020 The rust-syndication Developers
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the MIT License and/or Apache 2.0 License.

use std::collections::HashMap;
use std::io::Write;

pub use atom_syndication::Link;
use quick_xml::events::{BytesStart, Event};
use quick_xml::Error as XmlError;
use quick_xml::Writer;

use crate::extension::Extension;
use crate::toxml::ToXml;

/// The Atom XML namespace.
pub const NAMESPACE: &str = "http://www.w3.org/2005/Atom";

/// An Atom element extension.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Default, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "builders", derive(Builder))]
#[cfg_attr(feature = "builders", builder(setter(into), default))]
pub struct AtomExtension {
/// Links
pub links: Vec<Link>,
}

impl AtomExtension {
/// Retrieve links
pub fn links(&self) -> &[Link] {
&self.links
}

/// Set links
pub fn set_links<V>(&mut self, links: V)
where
V: Into<Vec<Link>>,
{
self.links = links.into();
}
}

impl AtomExtension {
/// Creates an `AtomExtension` using the specified `HashMap`.
pub fn from_map(mut map: HashMap<String, Vec<Extension>>) -> Self {
let mut ext = Self::default();

ext.links = map
.remove("link")
.unwrap_or_default()
.into_iter()
.filter_map(|mut link_ext| {
let href = link_ext.attrs.remove("href")?;

let mut link = Link::default();
link.href = href;
if let Some(rel) = link_ext.attrs.remove("rel") {
link.rel = rel;
}
link.hreflang = link_ext.attrs.remove("hreflang");
link.mime_type = link_ext.attrs.remove("type");
link.title = link_ext.attrs.remove("title");
link.length = link_ext.attrs.remove("length");
Some(link)
})
.collect();

ext
}
}

impl ToXml for AtomExtension {
fn to_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), XmlError> {
for link in &self.links {
let name = b"link";
let mut element = BytesStart::borrowed(name, name.len());
element.push_attribute(("href", &*link.href));
element.push_attribute(("rel", &*link.rel));

if let Some(ref hreflang) = link.hreflang {
element.push_attribute(("hreflang", &**hreflang));
}

if let Some(ref mime_type) = link.mime_type {
element.push_attribute(("type", &**mime_type));
}

if let Some(ref title) = link.title {
element.push_attribute(("title", &**title));
}

if let Some(ref length) = link.length {
element.push_attribute(("length", &**length));
}

writer.write_event(Event::Empty(element))?;
}
Ok(())
}
}
4 changes: 4 additions & 0 deletions src/extension/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ use quick_xml::Writer;

use crate::toxml::ToXml;

/// Types and methods for [Atom](https://www.rssboard.org/rss-profile#namespace-elements-atom) extensions.
#[cfg(feature = "atom")]
pub mod atom;

/// Types and methods for
/// [iTunes](https://help.apple.com/itc/podcasts_connect/#/itcb54353390) extensions.
pub mod itunes;
Expand Down
51 changes: 51 additions & 0 deletions src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use quick_xml::Writer;
use crate::category::Category;
use crate::enclosure::Enclosure;
use crate::error::Error;
#[cfg(feature = "atom")]
use crate::extension::atom;
use crate::extension::dublincore;
use crate::extension::itunes;
use crate::extension::util::{extension_name, parse_extension};
Expand Down Expand Up @@ -56,6 +58,9 @@ pub struct Item {
pub content: Option<String>,
/// The extensions for the item.
pub extensions: ExtensionMap,
/// The Atom extension for the channel.
#[cfg(feature = "atom")]
pub atom_ext: Option<atom::AtomExtension>,
/// The iTunes extension for the item.
pub itunes_ext: Option<itunes::ITunesItemExtension>,
/// The Dublin Core extension for the item.
Expand Down Expand Up @@ -438,6 +443,42 @@ impl Item {
self.content = content.into();
}

/// Return the Atom extension for this item.
///
/// # Examples
///
/// ```
/// use rss::Item;
/// use rss::extension::atom::AtomExtension;
///
/// let mut item = Item::default();
/// item.set_atom_ext(AtomExtension::default());
/// assert!(item.atom_ext().is_some());
/// ```
#[cfg(feature = "atom")]
pub fn atom_ext(&self) -> Option<&atom::AtomExtension> {
self.atom_ext.as_ref()
}

/// Set the Atom extension for this item.
///
/// # Examples
///
/// ```
/// use rss::Item;
/// use rss::extension::atom::AtomExtension;
///
/// let mut item = Item::default();
/// item.set_atom_ext(AtomExtension::default());
/// ```
#[cfg(feature = "atom")]
pub fn set_atom_ext<V>(&mut self, atom_ext: V)
where
V: Into<Option<atom::AtomExtension>>,
{
self.atom_ext = atom_ext.into();
}

/// Return the iTunes extension for this item.
///
/// # Examples
Expand Down Expand Up @@ -615,6 +656,11 @@ impl Item {
// Process each of the namespaces we know (note that the values are not removed prior and reused to support pass-through of unknown extensions)
for (prefix, namespace) in namespaces {
match namespace.as_ref() {
#[cfg(feature = "atom")]
atom::NAMESPACE => item
.extensions
.remove(prefix)
.map(|v| item.atom_ext = Some(atom::AtomExtension::from_map(v))),
itunes::NAMESPACE => item
.extensions
.remove(prefix)
Expand Down Expand Up @@ -687,6 +733,11 @@ impl ToXml for Item {
}
}

#[cfg(feature = "atom")]
if let Some(ext) = self.atom_ext.as_ref() {
ext.to_xml(writer)?;
}

if let Some(ext) = self.itunes_ext.as_ref() {
ext.to_xml(writer)?;
}
Expand Down
38 changes: 38 additions & 0 deletions tests/data/rss2_with_atom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Liftoff News</title>
<link>http://liftoff.msfc.nasa.gov/</link>
<description>Liftoff to Space Exploration.</description>
<language>en-us</language>
<pubDate>Tue, 10 Jun 2003 04:00:00 GMT</pubDate>
<lastBuildDate>Tue, 10 Jun 2003 09:41:01 GMT</lastBuildDate>
<docs>http://blogs.law.harvard.edu/tech/rss</docs>
<generator>Weblog Editor 2.0</generator>
<managingEditor>editor@example.com</managingEditor>
<webMaster>webmaster@example.com</webMaster>
<atom:link href="http://liftoff.msfc.nasa.gov/rss" rel="self" type="application/rss+xml" />
<item>
<title>Star City</title>
<link>http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp</link>
<atom:link href="http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp" />
<description>How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's &lt;a href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm"&gt;Star City&lt;/a&gt;.</description>
<pubDate>Tue, 03 Jun 2003 09:39:21 GMT</pubDate>
<guid>http://liftoff.msfc.nasa.gov/2003/06/03.html#item573</guid>
<atom:link href="http://liftoff.msfc.nasa.gov/2003/05/30.html#item572" rel="related" />
</item>
<item>
<description>Sky watchers in Europe, Asia, and parts of Alaska and Canada will experience a &lt;a href="http://science.nasa.gov/headlines/y2003/30may_solareclipse.htm"&gt;partial eclipse of the Sun&lt;/a&gt; on Saturday, May 31st.</description>
<pubDate>Fri, 30 May 2003 11:06:42 GMT</pubDate>
<guid>http://liftoff.msfc.nasa.gov/2003/05/30.html#item572</guid>
</item>
<item>
<title>The Engine That Does More</title>
<link>http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp</link>
<atom:link href="http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp" />
<description>Before man travels to Mars, NASA hopes to design new engines that will let us fly through the Solar System more quickly. The proposed VASIMR engine would do that.</description>
<pubDate>Tue, 27 May 2003 08:37:32 GMT</pubDate>
<guid>http://liftoff.msfc.nasa.gov/2003/05/27.html#item571</guid>
</item>
</channel>
</rss>
42 changes: 42 additions & 0 deletions tests/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,48 @@ fn read_extension() {
);
}

#[cfg(feature = "atom")]
#[test]
fn read_atom() {
let input = include_str!("data/rss2_with_atom.xml");
let channel = input.parse::<Channel>().expect("failed to parse xml");

assert_eq!(
channel.atom_ext().unwrap().links(),
&[rss::extension::atom::Link {
href: "http://liftoff.msfc.nasa.gov/rss".into(),
rel: "self".into(),
mime_type: Some("application/rss+xml".into()),
..Default::default()
},]
);

assert_eq!(
channel.items[0].atom_ext().unwrap().links(),
&[
rss::extension::atom::Link {
href: "http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp".into(),
..Default::default()
},
rss::extension::atom::Link {
href: "http://liftoff.msfc.nasa.gov/2003/05/30.html#item572".into(),
rel: "related".into(),
..Default::default()
},
]
);

assert!(channel.items[1].atom_ext().is_none());

assert_eq!(
channel.items[2].atom_ext().unwrap().links(),
&[rss::extension::atom::Link {
href: "http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp".into(),
..Default::default()
}]
);
}

#[test]
fn read_itunes() {
let input = include_str!("data/itunes.xml");
Expand Down

0 comments on commit 6b6eac1

Please sign in to comment.