diff --git a/.travis.yml b/.travis.yml
index 06b45ced20..01fdba5906 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,9 @@ rust:
- nightly
- beta
- stable
+script:
+ - cargo test --verbose
+ - cargo test --verbose --features rss_loose
after_success: |
cargo doc && \
echo '' > target/doc/index.html && \
diff --git a/Cargo.toml b/Cargo.toml
index a1757adf44..d0e3f7ad1e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,4 @@
[package]
-
name = "rss"
version = "0.3.1"
authors = ["Corey Farwell "]
@@ -10,6 +9,9 @@ description = "Library for serializing the RSS web content syndication format"
keywords = ["rss", "feed", "blog", "web", "news"]
exclude = ["test-data/"]
-
[dependencies]
RustyXML = "0.1"
+
+[features]
+rss_loose = []
+
diff --git a/README.md b/README.md
index 622d427e05..680737ed79 100644
--- a/README.md
+++ b/README.md
@@ -57,3 +57,24 @@ let rss_str = r#"
let rss = rss_str.parse::().unwrap();
```
+
+### Partial Feeds
+
+In some cases, the RSS source may not return a standards-compliant RSS such as a missing description tag. The library
+is designed to return an error in such cases, however this behaviour can be loosened by using the feature
+flag `rss_loose`.
+
+Using this flag changes what would normally be a `String` type to a `Option`, just like other fields.
+
+In your `Cargo.toml`, add the following:
+```toml
+[dependencies.rss]
+features = ["rss_loose"]
+```
+
+## Contributors & License
+
+- Michael Yoo [GitHub](https://github.com/sekjun9878) [Web](https://www.michael.yoo.id.au/)
+
+Released under The Apache License 2.0
+
diff --git a/src/category.rs b/src/category.rs
index 35242de96c..895bd62e63 100644
--- a/src/category.rs
+++ b/src/category.rs
@@ -45,3 +45,4 @@ impl ViaXml for Category {
})
}
}
+
diff --git a/src/channel.rs b/src/channel.rs
index 83960be907..0ebcf1a52a 100644
--- a/src/channel.rs
+++ b/src/channel.rs
@@ -23,8 +23,10 @@ use ::{Category, ElementUtils, Item, Image, ReadError, TextInput, ViaXml};
/// # Examples
///
/// ```
+///# #![feature(stmt_expr_attributes)]
/// use rss::Channel;
///
+///# #[cfg(not(feature = "rss_loose"))]
/// let channel = Channel {
/// title: String::from("My Blog"),
/// link: String::from("http://myblog.com"),
@@ -32,12 +34,31 @@ use ::{Category, ElementUtils, Item, Image, ReadError, TextInput, ViaXml};
/// items: vec![],
/// ..Default::default()
/// };
+///# #[cfg(feature = "rss_loose")]
+///# let channel = Channel {
+///# title: Some(String::from("My Blog")),
+///# link: Some(String::from("http://myblog.com")),
+///# description: Some(String::from("My thoughts on life, the universe, and everything")),
+///# items: vec![],
+///# ..Default::default()
+///# };
/// ```
#[derive(Default, Debug, Clone)]
pub struct Channel {
+ #[cfg(not(feature = "rss_loose"))]
pub title: String,
+ #[cfg(not(feature = "rss_loose"))]
pub link: String,
+ #[cfg(not(feature = "rss_loose"))]
pub description: String,
+
+ #[cfg(feature = "rss_loose")]
+ pub title: Option,
+ #[cfg(feature = "rss_loose")]
+ pub link: Option,
+ #[cfg(feature = "rss_loose")]
+ pub description: Option,
+
pub items: Vec- ,
pub language: Option,
pub copyright: Option,
@@ -61,10 +82,20 @@ impl ViaXml for Channel {
fn to_xml(&self) -> Element {
let mut channel = Element::new("channel".to_owned(), None, vec![]);
+ #[cfg(not(feature = "rss_loose"))]
channel.tag_with_text("title", self.title.clone());
+ #[cfg(not(feature = "rss_loose"))]
channel.tag_with_text("link", self.link.clone());
+ #[cfg(not(feature = "rss_loose"))]
channel.tag_with_text("description", self.description.clone());
+ #[cfg(feature = "rss_loose")]
+ channel.tag_with_optional_text("title", self.title.clone());
+ #[cfg(feature = "rss_loose")]
+ channel.tag_with_optional_text("link", self.link.clone());
+ #[cfg(feature = "rss_loose")]
+ channel.tag_with_optional_text("description", self.description.clone());
+
for item in &self.items {
channel.tag(item.to_xml());
}
@@ -95,21 +126,29 @@ impl ViaXml for Channel {
}
fn from_xml(elem: Element) -> Result {
+ #[cfg(not(feature = "rss_loose"))]
let title = match elem.get_child("title", None) {
Some(elem) => elem.content_str(),
None => return Err(ReadError::ChannelMissingTitle),
};
-
+ #[cfg(not(feature = "rss_loose"))]
let link = match elem.get_child("link", None) {
Some(elem) => elem.content_str(),
None => return Err(ReadError::ChannelMissingLink),
};
-
+ #[cfg(not(feature = "rss_loose"))]
let description = match elem.get_child("description", None) {
Some(elem) => elem.content_str(),
None => return Err(ReadError::ChannelMissingDescription),
};
+ #[cfg(feature = "rss_loose")]
+ let title = elem.get_child("title", None).map(Element::content_str);
+ #[cfg(feature = "rss_loose")]
+ let link = elem.get_child("link", None).map(Element::content_str);
+ #[cfg(feature = "rss_loose")]
+ let description = elem.get_child("description", None).map(Element::content_str);
+
let items = match elem.get_children("item", None)
.map(|e| ViaXml::from_xml(e.clone()))
.collect::, _>>()
diff --git a/src/image.rs b/src/image.rs
index f784b59e63..1eedbfbd39 100644
--- a/src/image.rs
+++ b/src/image.rs
@@ -8,9 +8,20 @@ use ::{ElementUtils, ReadError, ViaXml};
/// (http://cyber.law.harvard.edu/rss/rss.html#ltimagegtSubelementOfLtchannelgt)
#[derive(Default, Debug, Clone)]
pub struct Image {
+ #[cfg(not(feature = "rss_loose"))]
pub url: String,
+ #[cfg(not(feature = "rss_loose"))]
pub title: String,
+ #[cfg(not(feature = "rss_loose"))]
pub link: String,
+
+ #[cfg(feature = "rss_loose")]
+ pub url: Option,
+ #[cfg(feature = "rss_loose")]
+ pub title: Option,
+ #[cfg(feature = "rss_loose")]
+ pub link: Option,
+
pub width: Option,
pub height: Option,
}
@@ -18,9 +29,21 @@ pub struct Image {
impl ViaXml for Image {
fn to_xml(&self) -> Element {
let mut elem = Element::new("image".to_owned(), None, vec![]);
+
+ #[cfg(not(feature = "rss_loose"))]
elem.tag_with_text("url", self.url.clone());
+ #[cfg(not(feature = "rss_loose"))]
elem.tag_with_text("title", self.title.clone());
+ #[cfg(not(feature = "rss_loose"))]
elem.tag_with_text("link", self.link.clone());
+
+ #[cfg(feature = "rss_loose")]
+ elem.tag_with_optional_text("url", self.url.clone());
+ #[cfg(feature = "rss_loose")]
+ elem.tag_with_optional_text("title", self.title.clone());
+ #[cfg(feature = "rss_loose")]
+ elem.tag_with_optional_text("link", self.link.clone());
+
if let Some(ref n) = self.width {
elem.tag_with_text("width", n.to_string());
}
@@ -31,21 +54,31 @@ impl ViaXml for Image {
}
fn from_xml(elem: Element) -> Result {
+ #[cfg(not(feature = "rss_loose"))]
let url = match elem.get_child("url", None) {
Some(elem) => elem.content_str(),
None => return Err(ReadError::ImageMissingUrl),
};
+ #[cfg(not(feature = "rss_loose"))]
let title = match elem.get_child("title", None) {
Some(elem) => elem.content_str(),
None => return Err(ReadError::ImageMissingTitle),
};
+ #[cfg(not(feature = "rss_loose"))]
let link = match elem.get_child("link", None) {
Some(elem) => elem.content_str(),
None => return Err(ReadError::ImageMissingLink),
};
+ #[cfg(feature = "rss_loose")]
+ let url = elem.get_child("url", None).map(Element::content_str);
+ #[cfg(feature = "rss_loose")]
+ let title = elem.get_child("title", None).map(Element::content_str);
+ #[cfg(feature = "rss_loose")]
+ let link = elem.get_child("link", None).map(Element::content_str);
+
let height = match elem.get_child("height", None)
.map(|h| u32::from_str(&h.content_str()))
{
diff --git a/src/lib.rs b/src/lib.rs
index 9b2f83af23..cd6db83258 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -19,6 +19,7 @@
//! ## Writing
//!
//! ```
+//!# #![feature(stmt_expr_attributes)]
//! use rss::{Channel, Item, Rss};
//!
//! let item = Item {
@@ -28,6 +29,7 @@
//! ..Default::default()
//! };
//!
+//!# #[cfg(not(feature = "rss_loose"))]
//! let channel = Channel {
//! title: String::from("TechCrunch"),
//! link: String::from("http://techcrunch.com"),
@@ -35,6 +37,15 @@
//! items: vec![item],
//! ..Default::default()
//! };
+//!#
+//!# #[cfg(feature = "rss_loose")]
+//!# let channel = Channel {
+//!# title: Some(String::from("TechCrunch")),
+//!# link: Some(String::from("http://techcrunch.com")),
+//!# description: Some(String::from("The latest technology news and information on startups")),
+//!# items: vec![item],
+//!# ..Default::default()
+//!# };
//!
//! let rss = Rss(channel);
//!
@@ -64,6 +75,22 @@
//!
//! let rss = rss_str.parse::().unwrap();
//! ```
+//!
+//! ### Partial Feeds
+//!
+//! In some cases, the RSS source may not return a standards-compliant RSS such as a missing description tag. The library
+//! is designed to return an error in such cases, however this behaviour can be loosened by using the feature
+//! flag `rss_loose`.
+//!
+//! Using this flag changes what would normally be a `String` type to a `Option`, just like other fields.
+//!
+//! In your `Cargo.toml`, add the following:
+//! ```toml
+//! [dependencies.rss]
+//! features = ["rss_loose"]
+//! ```
+
+#![feature(stmt_expr_attributes)]
mod category;
mod guid;
@@ -246,6 +273,7 @@ mod test {
..Default::default()
};
+ #[cfg(not(feature = "rss_loose"))]
let channel = Channel {
title: "My Blog".to_owned(),
link: "http://myblog.com".to_owned(),
@@ -254,6 +282,15 @@ mod test {
..Default::default()
};
+ #[cfg(feature = "rss_loose")]
+ let channel = Channel {
+ title: Some("My Blog".to_owned()),
+ link: Some("http://myblog.com".to_owned()),
+ description: Some("Where I write stuff".to_owned()),
+ items: vec![item],
+ ..Default::default()
+ };
+
let rss = Rss(channel);
assert_eq!(rss.to_string(), "My Bloghttp://myblog.comWhere I write stuff
- My first post!http://myblog.com/post1This is my first post
");
}
@@ -274,6 +311,7 @@ mod test {
}
#[test]
+ #[cfg_attr(feature = "rss_loose", ignore)]
fn test_read_one_channel_no_properties() {
let rss_str = "\
\
@@ -294,7 +332,11 @@ mod test {
\
";
let Rss(channel) = Rss::from_str(rss_str).unwrap();
+
+ #[cfg(not(feature = "rss_loose"))]
assert_eq!("Hello world!", channel.title);
+ #[cfg(feature = "rss_loose")]
+ assert_eq!(Some("Hello world!".to_owned()), channel.title); // How come &str is dereferenced but Some(&str) is not?
}
#[test]
@@ -340,7 +382,11 @@ mod test {
\
";
let Rss(channel) = Rss::from_str(rss_str).unwrap();
+
+ #[cfg(not(feature = "rss_loose"))]
assert_eq!("Foobar", channel.text_input.unwrap().title);
+ #[cfg(feature = "rss_loose")]
+ assert_eq!(Some("Foobar".to_owned()), channel.text_input.unwrap().title);
}
// Ensure reader ignores the PI XML node and continues to parse the RSS
@@ -356,7 +402,11 @@ mod test {
\
";
let Rss(channel) = Rss::from_str(rss_str).unwrap();
+
+ #[cfg(not(feature = "rss_loose"))]
assert_eq!("Title", channel.title);
+ #[cfg(feature = "rss_loose")]
+ assert_eq!(Some("Title".to_owned()), channel.title);
}
#[test]
@@ -379,14 +429,27 @@ mod test {
";
let rss = Rss::from_str(rss_str).unwrap();
let image = rss.0.image.unwrap();
- assert_eq!(image.url, "a url");
- assert_eq!(image.title, "a title");
- assert_eq!(image.link, "a link");
+
+ #[cfg(not(feature = "rss_loose"))]
+ assert_eq!(image.url, "a url".to_owned());
+ #[cfg(not(feature = "rss_loose"))]
+ assert_eq!(image.title, "a title".to_owned());
+ #[cfg(not(feature = "rss_loose"))]
+ assert_eq!(image.link, "a link".to_owned());
+
+ #[cfg(feature = "rss_loose")]
+ assert_eq!(image.url, Some("a url".to_owned()));
+ #[cfg(feature = "rss_loose")]
+ assert_eq!(image.title, Some("a title".to_owned()));
+ #[cfg(feature = "rss_loose")]
+ assert_eq!(image.link, Some("a link".to_owned()));
+
assert_eq!(image.height, Some(140));
assert_eq!(image.width, Some(280));
}
#[test]
+ #[cfg_attr(feature = "rss_loose", ignore)]
fn test_read_image_no_url() {
let rss_str = "\
\
@@ -405,6 +468,7 @@ mod test {
}
#[test]
+ #[cfg_attr(feature = "rss_loose", ignore)]
fn test_read_image_no_title() {
let rss_str = "\
\
@@ -423,6 +487,7 @@ mod test {
}
#[test]
+ #[cfg_attr(feature = "rss_loose", ignore)]
fn test_read_image_no_link() {
let rss_str = "\
\
diff --git a/src/text_input.rs b/src/text_input.rs
index 69a6884a3f..d582dd4be0 100644
--- a/src/text_input.rs
+++ b/src/text_input.rs
@@ -21,43 +21,84 @@ use ::{ElementUtils, ReadError, ViaXml};
/// (http://cyber.law.harvard.edu/rss/rss.html#lttextinputgtSubelementOfLtchannelgt)
#[derive(Debug, Clone)]
pub struct TextInput {
+ #[cfg(not(feature = "rss_loose"))]
pub title: String,
+ #[cfg(not(feature = "rss_loose"))]
pub description: String,
+ #[cfg(not(feature = "rss_loose"))]
pub name: String,
+ #[cfg(not(feature = "rss_loose"))]
pub link: String,
+
+ #[cfg(feature = "rss_loose")]
+ pub title: Option,
+ #[cfg(feature = "rss_loose")]
+ pub description: Option,
+ #[cfg(feature = "rss_loose")]
+ pub name: Option,
+ #[cfg(feature = "rss_loose")]
+ pub link: Option,
}
impl ViaXml for TextInput {
fn to_xml(&self) -> Element {
let mut elem = Element::new("textInput".to_owned(), None, vec![]);
+
+ #[cfg(not(feature = "rss_loose"))]
elem.tag_with_text("title", self.title.clone());
+ #[cfg(not(feature = "rss_loose"))]
elem.tag_with_text("description", self.description.clone());
+ #[cfg(not(feature = "rss_loose"))]
elem.tag_with_text("name", self.name.clone());
+ #[cfg(not(feature = "rss_loose"))]
elem.tag_with_text("link", self.link.clone());
+
+ #[cfg(feature = "rss_loose")]
+ elem.tag_with_optional_text("title", self.title.clone());
+ #[cfg(feature = "rss_loose")]
+ elem.tag_with_optional_text("description", self.description.clone());
+ #[cfg(feature = "rss_loose")]
+ elem.tag_with_optional_text("name", self.name.clone());
+ #[cfg(feature = "rss_loose")]
+ elem.tag_with_optional_text("link", self.link.clone());
+
elem
}
fn from_xml(elem: Element) -> Result {
+ #[cfg(not(feature = "rss_loose"))]
let title = match elem.get_child("title", None) {
Some(elem) => elem.content_str(),
None => return Err(ReadError::TextInputMissingTitle),
};
+ #[cfg(not(feature = "rss_loose"))]
let description = match elem.get_child("description", None) {
Some(elem) => elem.content_str(),
None => return Err(ReadError::TextInputMissingDescription),
};
+ #[cfg(not(feature = "rss_loose"))]
let name = match elem.get_child("name", None) {
Some(elem) => elem.content_str(),
None => return Err(ReadError::TextInputMissingName),
};
+ #[cfg(not(feature = "rss_loose"))]
let link = match elem.get_child("link", None) {
Some(elem) => elem.content_str(),
None => return Err(ReadError::TextInputMissingLink),
};
+ #[cfg(feature = "rss_loose")]
+ let title = elem.get_child("title", None).map(Element::content_str);
+ #[cfg(feature = "rss_loose")]
+ let description = elem.get_child("description", None).map(Element::content_str);
+ #[cfg(feature = "rss_loose")]
+ let name = elem.get_child("name", None).map(Element::content_str);
+ #[cfg(feature = "rss_loose")]
+ let link = elem.get_child("link", None).map(Element::content_str);
+
Ok(TextInput {
title: title,
description: description,