1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use xml::{Xml, Element};

use {NS, XHTML_NS};
use error::Error;
use utils::{FromXml, ToXml};

/// [The Atom Syndication Format § The "atom:content" Element]
/// (https://tools.ietf.org/html/rfc4287#section-4.1.3)
#[derive(Clone, Debug, PartialEq)]
pub enum Content {
    /// Plain text only, no markup
    Text(String),
    /// String containing escaped HTML markup
    Html(String),
    /// XHTML div element embedded in the feed
    Xhtml(Element),
}

impl Content {
    fn type_string(&self) -> String {
        match *self {
            Content::Text(_) => "text".to_string(),
            Content::Html(_) => "html".to_string(),
            Content::Xhtml(_) => "xhtml".to_string(),
        }
    }
}

impl ToXml for Content {
    fn to_xml(&self) -> Element {
        let type_attr = ("type".to_string(), None, self.type_string());
        let mut content = Element::new("content".to_string(), Some(NS.to_string()), vec![type_attr]);
        match *self {
            Content::Text(ref text) | Content::Html(ref text) => { content.text(text.clone()); },
            Content::Xhtml(ref element) => { content.children.push(Xml::ElementNode(element.clone())); },
        };

        content
    }
}

impl FromXml for Content {
    fn from_xml(elem: &Element) -> Result<Self, Error> {
        let text = elem.content_str();
        match elem.get_attribute("type", None) {
            Some("text") | None => Ok(Content::Text(text)),
            Some("html") => Ok(Content::Html(text)),
            Some("xhtml") => {
                // https://tools.ietf.org/html/rfc4287#section-4.1.3.3
                // 4.1.3.3.3 If the value of "type" is "xhtml", the content of atom:content MUST be
                // a single XHTML div element
                if let Some(div) = elem.get_child("div", Some(XHTML_NS)) {
                    return Ok(Content::Xhtml(div.clone()));
                }

                Err(Error::Html("expected to find child element <div> of <content> but found none"))
            },
            Some(_) => Err(Error::ContentType("<content> has unknown type"))
        }
    }
}

#[cfg(test)]
mod tests {
    use xml::{Xml, Element};

    use {Content, Feed, XHTML_NS};
    use error::Error;
    use utils::{FromXml, ToXml};

    #[test]
    fn to_xml_with_text() {
        let content = Content::Text("Content of the first post.".to_string());
        let xml = format!("{}", content.to_xml());
        assert_eq!(xml, "<content xmlns='http://www.w3.org/2005/Atom' type='text'>Content of the first post.</content>");
    }

    #[test]
    fn to_xml_with_html() {
        let content = Content::Html("<p>Content of the first post.</p>".to_string());
        let xml = format!("{}", content.to_xml());
        assert_eq!(xml, "<content xmlns='http://www.w3.org/2005/Atom' type='html'>&lt;p&gt;Content of the first post.&lt;/p&gt;</content>");
    }

    #[test]
    fn to_xml_with_xhtml() {
        let mut div = Element::new("div".to_string(), Some(XHTML_NS.to_string()), vec![]);
        let mut p = Element::new("p".to_string(), Some(XHTML_NS.to_string()), vec![]);
        p.text("Content of the first post.".to_string());
        div.children.push(Xml::ElementNode(p));

        let content = Content::Xhtml(div);
        let xml = format!("{}", content.to_xml());
        assert_eq!(xml, "<content xmlns='http://www.w3.org/2005/Atom' type='xhtml'><div xmlns='http://www.w3.org/1999/xhtml'><p>Content of the first post.</p></div></content>");
    }

    #[test]
    fn from_xml_with_text() {
        let content = Content::from_xml(&str::parse("<content xmlns='http://www.w3.org/2005/Atom'>Content of the first post.</content>").unwrap());
        assert_eq!(content, Ok(Content::Text("Content of the first post.".to_string())));
    }

    #[test]
    fn from_xml_with_html() {
        let content = Content::from_xml(&str::parse("<content xmlns='http://www.w3.org/2005/Atom' type='html'>&lt;p&gt;Content of the first post.&lt;/p&gt;</content>").unwrap());
        assert_eq!(content, Ok(Content::Html("<p>Content of the first post.</p>".to_string())));
    }

    #[test]
    fn from_xml_with_xhtml() {
        let content = Content::from_xml(&str::parse("<content xmlns='http://www.w3.org/2005/Atom' type='xhtml'><div xmlns='http://www.w3.org/1999/xhtml'><p>Content of the first post.</p></div></content>").unwrap());

        let namespace_attr = ("xmlns".to_string(), None, "http://www.w3.org/1999/xhtml".to_string());
        let mut div = Element::new("div".to_string(), Some(XHTML_NS.to_string()), vec![namespace_attr]);
        let mut p = Element::new("p".to_string(), Some(XHTML_NS.to_string()), vec![]);
        p.text("Content of the first post.".to_string());
        div.children.push(Xml::ElementNode(p));

        assert_eq!(content, Ok(Content::Xhtml(div)));
    }

    #[test]
    fn from_xml_with_xhtml_content_no_div() {
        let content = Content::from_xml(&str::parse("<content xmlns='http://www.w3.org/2005/Atom' type='xhtml'><p>Content of the first post.</p></content>").unwrap());
        assert_eq!(content, Err(Error::Html("expected to find child element <div> of <content> but found none")));
    }

    #[test]
    fn from_xml_with_invalid_content_type() {
        let content = Content::from_xml(&str::parse("<content xmlns='http://www.w3.org/2005/Atom' type='invalid'>Content of the first post.</content>").unwrap());
        assert_eq!(content, Err(Error::ContentType("<content> has unknown type")));
    }

    #[test]
    fn from_xml_with_top_level_xhtml_namespace() {
        let feed = Feed::from_xml(&str::parse("<feed xmlns='http://www.w3.org/2005/Atom' xmlns:xhtml='http://www.w3.org/1999/xhtml'>
            <id>http://example.com/feed.atom</id>
            <title>Examplar Feed</title>
            <updated>2016-09-18T18:53:16Z</updated>
            <entry>
                <id>http://example.com/1</id>
                <title>First!</title>
                <updated>2016-09-17T19:18:32Z</updated>
                <content type='xhtml'>
                    <xhtml:div>Content of the first post.</xhtml:div>
                </content>
            </entry>
        </feed>").unwrap()).unwrap();

        match feed.entries[0].content {
            Some(Content::Xhtml(ref div)) => { assert_eq!(div.ns, Some(XHTML_NS.to_string())); },
            _ => panic!("expected <content> type to be xhtml"),
        }
    }
}