Skip to main content

feedparser_rs/namespace/
content.rs

1/// Content Module for RSS 1.0
2///
3/// Namespace: <http://purl.org/rss/1.0/modules/content/>
4/// Prefix: content
5///
6/// This module provides parsing support for the Content namespace,
7/// commonly used in `WordPress` and other RSS feeds to provide full
8/// HTML content separate from the summary.
9///
10/// Elements:
11/// - `content:encoded` → adds to entry.content with type "text/html"
12use crate::types::{Content, Entry};
13
14/// Content namespace URI
15pub const CONTENT_NAMESPACE: &str = "http://purl.org/rss/1.0/modules/content/";
16
17/// Handle Content namespace element at entry level
18///
19/// # Arguments
20///
21/// * `element` - Local name of the element (without namespace prefix)
22/// * `text` - Text content of the element
23/// * `entry` - Entry to update
24pub fn handle_entry_element(
25    element: &str,
26    text: &str,
27    entry: &mut Entry,
28    lang: Option<&str>,
29    base: Option<&str>,
30) {
31    if element == "encoded" {
32        // content:encoded → add to entry.content as HTML
33        entry.content.push(Content {
34            value: text.to_string(),
35            content_type: Some("text/html".into()),
36            language: lang.filter(|s| !s.is_empty()).map(Into::into),
37            base: base.filter(|s| !s.is_empty()).map(ToString::to_string),
38            src: None,
39        });
40    }
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn test_content_encoded() {
49        let mut entry = Entry::default();
50        let html = r"<p>Full HTML content with <strong>formatting</strong>.</p>";
51
52        handle_entry_element("encoded", html, &mut entry, None, None);
53
54        assert_eq!(entry.content.len(), 1);
55        assert_eq!(entry.content[0].value, html);
56        assert_eq!(entry.content[0].content_type.as_deref(), Some("text/html"));
57    }
58
59    #[test]
60    fn test_multiple_content_encoded() {
61        let mut entry = Entry::default();
62
63        handle_entry_element("encoded", "<p>First content</p>", &mut entry, None, None);
64        handle_entry_element("encoded", "<p>Second content</p>", &mut entry, None, None);
65
66        assert_eq!(entry.content.len(), 2);
67    }
68
69    #[test]
70    fn test_content_with_cdata() {
71        let mut entry = Entry::default();
72        // CDATA markers are typically stripped by XML parser before we see it
73        let html = r"<p>Content from <![CDATA[...]]></p>";
74
75        handle_entry_element("encoded", html, &mut entry, None, None);
76
77        assert!(!entry.content.is_empty());
78    }
79
80    #[test]
81    fn test_ignore_unknown_elements() {
82        let mut entry = Entry::default();
83
84        handle_entry_element("unknown", "test", &mut entry, None, None);
85
86        assert!(entry.content.is_empty());
87    }
88
89    #[test]
90    fn test_content_encoded_with_lang_and_base() {
91        let mut entry = Entry::default();
92        let html = "<p>Content</p>";
93
94        handle_entry_element(
95            "encoded",
96            html,
97            &mut entry,
98            Some("en"),
99            Some("http://example.com/"),
100        );
101
102        assert_eq!(entry.content.len(), 1);
103        assert_eq!(entry.content[0].language.as_deref(), Some("en"));
104        assert_eq!(
105            entry.content[0].base.as_deref(),
106            Some("http://example.com/")
107        );
108    }
109
110    #[test]
111    fn test_content_encoded_empty_lang_treated_as_none() {
112        let mut entry = Entry::default();
113
114        handle_entry_element("encoded", "<p>Test</p>", &mut entry, Some(""), None);
115
116        assert_eq!(entry.content.len(), 1);
117        assert!(entry.content[0].language.is_none());
118    }
119}