atom_syndication/
feed.rs

1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::io::{BufRead, Write};
4use std::str::{self, FromStr};
5
6use quick_xml::events::attributes::Attributes;
7use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
8use quick_xml::{Reader, Writer};
9
10use crate::category::Category;
11use crate::entry::Entry;
12use crate::error::{Error, XmlError};
13use crate::extension::util::{extension_name, parse_extension};
14use crate::extension::ExtensionMap;
15use crate::fromxml::FromXml;
16use crate::generator::Generator;
17use crate::link::Link;
18use crate::person::Person;
19use crate::text::Text;
20use crate::toxml::{ToXml, WriterExt};
21use crate::util::{
22    atom_datetime, atom_text, attr_value, decode, default_fixed_datetime, skip, FixedDateTime,
23};
24
25/// Various options which control XML writer
26#[derive(Clone, Copy)]
27pub struct WriteConfig {
28    /// Write XML document declaration at the beginning of a document. Default is `true`.
29    pub write_document_declaration: bool,
30    /// Indent XML tags. Default is `None`.
31    pub indent_size: Option<usize>,
32}
33
34impl Default for WriteConfig {
35    fn default() -> Self {
36        Self {
37            write_document_declaration: true,
38            indent_size: None,
39        }
40    }
41}
42
43/// Represents an Atom feed
44#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
45#[derive(Debug, Clone, PartialEq)]
46#[cfg_attr(feature = "builders", derive(Builder))]
47#[cfg_attr(
48    feature = "builders",
49    builder(
50        setter(into),
51        default,
52        build_fn(name = "build_impl", private, error = "never::Never")
53    )
54)]
55pub struct Feed {
56    /// A human-readable title for the feed.
57    pub title: Text,
58    /// A universally unique and permanent URI.
59    pub id: String,
60    /// The last time the feed was modified in a significant way.
61    pub updated: FixedDateTime,
62    /// The authors of the feed.
63    #[cfg_attr(feature = "builders", builder(setter(each = "author")))]
64    pub authors: Vec<Person>,
65    /// The categories that the feed belongs to.
66    #[cfg_attr(feature = "builders", builder(setter(each = "category")))]
67    pub categories: Vec<Category>,
68    /// The contributors to the feed.
69    #[cfg_attr(feature = "builders", builder(setter(each = "contributor")))]
70    pub contributors: Vec<Person>,
71    /// The software used to generate the feed.
72    pub generator: Option<Generator>,
73    /// A small image which provides visual identification for the feed.
74    pub icon: Option<String>,
75    /// The Web pages related to the feed.
76    #[cfg_attr(feature = "builders", builder(setter(each = "link")))]
77    pub links: Vec<Link>,
78    /// A larger image which provides visual identification for the feed.
79    pub logo: Option<String>,
80    /// Information about rights held in and over the feed.
81    pub rights: Option<Text>,
82    /// A human-readable description or subtitle for the feed.
83    pub subtitle: Option<Text>,
84    /// The entries contained in the feed.
85    #[cfg_attr(feature = "builders", builder(setter(each = "entry")))]
86    pub entries: Vec<Entry>,
87    /// The extensions for the feed.
88    #[cfg_attr(feature = "builders", builder(setter(each = "extension")))]
89    pub extensions: ExtensionMap,
90    /// The namespaces present in the feed tag.
91    #[cfg_attr(feature = "builders", builder(setter(each = "namespace")))]
92    pub namespaces: BTreeMap<String, String>,
93    /// Base URL for resolving any relative references found in the element.
94    pub base: Option<String>,
95    /// Indicates the natural language for the element.
96    pub lang: Option<String>,
97}
98
99impl Feed {
100    /// Attempt to read an Atom feed from the reader.
101    ///
102    /// # Examples
103    ///
104    /// ```no_run
105    /// use std::io::BufReader;
106    /// use std::fs::File;
107    /// use atom_syndication::Feed;
108    ///
109    /// let file = File::open("example.xml").unwrap();
110    /// let feed = Feed::read_from(BufReader::new(file)).unwrap();
111    /// ```
112    pub fn read_from<B: BufRead>(reader: B) -> Result<Feed, Error> {
113        let mut reader = Reader::from_reader(reader);
114        reader.config_mut().expand_empty_elements = true;
115
116        let mut buf = Vec::new();
117
118        loop {
119            match reader.read_event_into(&mut buf).map_err(XmlError::new)? {
120                Event::Start(element) => {
121                    if decode(element.name().as_ref(), &reader)? == "feed" {
122                        return Feed::from_xml(&mut reader, element.attributes());
123                    } else {
124                        return Err(Error::InvalidStartTag);
125                    }
126                }
127                Event::Eof => break,
128                _ => {}
129            }
130
131            buf.clear();
132        }
133
134        Err(Error::Eof)
135    }
136
137    /// Attempt to write this Atom feed to a writer using default `WriteConfig`.
138    ///
139    /// # Examples
140    ///
141    /// ```
142    /// use std::io::BufReader;
143    /// use std::fs::File;
144    /// use atom_syndication::Feed;
145    ///
146    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
147    /// let feed = Feed {
148    ///     title: "Feed Title".into(),
149    ///     id: "Feed ID".into(),
150    ///     ..Default::default()
151    /// };
152    ///
153    /// let out = feed.write_to(Vec::new())?;
154    /// assert_eq!(&out, br#"<?xml version="1.0"?>
155    /// <feed xmlns="http://www.w3.org/2005/Atom"><title>Feed Title</title><id>Feed ID</id><updated>1970-01-01T00:00:00+00:00</updated></feed>"#);
156    /// # Ok(()) }
157    /// ```
158    pub fn write_to<W: Write>(&self, writer: W) -> Result<W, Error> {
159        self.write_with_config(writer, WriteConfig::default())
160    }
161
162    /// Attempt to write this Atom feed to a writer.
163    ///
164    /// # Examples
165    ///
166    /// ```
167    /// use std::io::BufReader;
168    /// use std::fs::File;
169    /// use atom_syndication::{Feed, WriteConfig};
170    ///
171    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
172    /// let feed = Feed {
173    ///     title: "Feed Title".into(),
174    ///     id: "Feed ID".into(),
175    ///     ..Default::default()
176    /// };
177    ///
178    /// let mut out = Vec::new();
179    /// let config = WriteConfig {
180    ///     write_document_declaration: false,
181    ///     indent_size: Some(2),
182    /// };
183    /// feed.write_with_config(&mut out, config)?;
184    /// assert_eq!(&out, br#"<feed xmlns="http://www.w3.org/2005/Atom">
185    ///   <title>Feed Title</title>
186    ///   <id>Feed ID</id>
187    ///   <updated>1970-01-01T00:00:00+00:00</updated>
188    /// </feed>"#);
189    /// # Ok(()) }
190    /// ```
191    pub fn write_with_config<W: Write>(
192        &self,
193        writer: W,
194        write_config: WriteConfig,
195    ) -> Result<W, Error> {
196        let mut writer = match write_config.indent_size {
197            Some(indent_size) => Writer::new_with_indent(writer, b' ', indent_size),
198            None => Writer::new(writer),
199        };
200        if write_config.write_document_declaration {
201            writer
202                .write_event(Event::Decl(BytesDecl::new("1.0", None, None)))
203                .map_err(XmlError::new)?;
204            writer
205                .write_event(Event::Text(BytesText::from_escaped("\n")))
206                .map_err(XmlError::new)?;
207        }
208        self.to_xml(&mut writer)?;
209        Ok(writer.into_inner())
210    }
211
212    /// Return the title of this feed.
213    ///
214    /// # Examples
215    ///
216    /// ```
217    /// use atom_syndication::Feed;
218    ///
219    /// let mut feed = Feed::default();
220    /// feed.set_title("Feed Title");
221    /// assert_eq!(feed.title(), "Feed Title");
222    /// ```
223    pub fn title(&self) -> &Text {
224        &self.title
225    }
226
227    /// Set the title of this feed.
228    ///
229    /// # Examples
230    ///
231    /// ```
232    /// use atom_syndication::Feed;
233    ///
234    /// let mut feed = Feed::default();
235    /// feed.set_title("Feed Title");
236    /// ```
237    pub fn set_title<V>(&mut self, title: V)
238    where
239        V: Into<Text>,
240    {
241        self.title = title.into();
242    }
243
244    /// Return the unique URI of this feed.
245    ///
246    /// # Examples
247    ///
248    /// ```
249    /// use atom_syndication::Feed;
250    ///
251    /// let mut feed = Feed::default();
252    /// feed.set_id("urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6");
253    /// assert_eq!(feed.id(), "urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6");
254    /// ```
255    pub fn id(&self) -> &str {
256        self.id.as_str()
257    }
258
259    /// Set the unique URI of this feed.
260    ///
261    /// # Examples
262    ///
263    /// ```
264    /// use atom_syndication::Feed;
265    ///
266    /// let mut feed = Feed::default();
267    /// feed.set_id("urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6");
268    /// ```
269    pub fn set_id<V>(&mut self, id: V)
270    where
271        V: Into<String>,
272    {
273        self.id = id.into();
274    }
275
276    /// Return the last time that this feed was modified.
277    ///
278    /// # Examples
279    ///
280    /// ```
281    /// use atom_syndication::Feed;
282    /// use atom_syndication::FixedDateTime;
283    /// use std::str::FromStr;
284    ///
285    /// let mut feed = Feed::default();
286    /// feed.set_updated(FixedDateTime::from_str("2017-06-03T15:15:44-05:00").unwrap());
287    /// assert_eq!(feed.updated().to_rfc3339(), "2017-06-03T15:15:44-05:00");
288    /// ```
289    pub fn updated(&self) -> &FixedDateTime {
290        &self.updated
291    }
292
293    /// Set the last time that this feed was modified.
294    ///
295    /// # Examples
296    ///
297    /// ```
298    /// use atom_syndication::Feed;
299    /// use atom_syndication::FixedDateTime;
300    /// use std::str::FromStr;
301    ///
302    /// let mut feed = Feed::default();
303    /// feed.set_updated(FixedDateTime::from_str("2017-06-03T15:15:44-05:00").unwrap());
304    /// ```
305    pub fn set_updated<V>(&mut self, updated: V)
306    where
307        V: Into<FixedDateTime>,
308    {
309        self.updated = updated.into();
310    }
311
312    /// Return the authors of this feed.
313    ///
314    /// # Examples
315    ///
316    /// ```
317    /// use atom_syndication::{Feed, Person};
318    ///
319    /// let mut feed = Feed::default();
320    /// feed.set_authors(vec![Person::default()]);
321    /// assert_eq!(feed.authors().len(), 1);
322    /// ```
323    pub fn authors(&self) -> &[Person] {
324        self.authors.as_slice()
325    }
326
327    /// Set the authors of this feed.
328    ///
329    /// # Examples
330    ///
331    /// ```
332    /// use atom_syndication::{Feed, Person};
333    ///
334    /// let mut feed = Feed::default();
335    /// feed.set_authors(vec![Person::default()]);
336    /// ```
337    pub fn set_authors<V>(&mut self, authors: V)
338    where
339        V: Into<Vec<Person>>,
340    {
341        self.authors = authors.into();
342    }
343
344    /// Return the categories this feed belongs to.
345    ///
346    /// # Examples
347    ///
348    /// ```
349    /// use atom_syndication::{Feed, Category};
350    ///
351    /// let mut feed = Feed::default();
352    /// feed.set_categories(vec![Category::default()]);
353    /// assert_eq!(feed.categories().len(), 1);
354    /// ```
355    pub fn categories(&self) -> &[Category] {
356        self.categories.as_slice()
357    }
358
359    /// Set the categories this feed belongs to.
360    ///
361    /// # Examples
362    ///
363    /// ```
364    /// use atom_syndication::{Feed, Category};
365    ///
366    /// let mut feed = Feed::default();
367    /// feed.set_categories(vec![Category::default()]);
368    /// ```
369    pub fn set_categories<V>(&mut self, categories: V)
370    where
371        V: Into<Vec<Category>>,
372    {
373        self.categories = categories.into();
374    }
375
376    /// Return the contributors to this feed.
377    ///
378    /// # Examples
379    ///
380    /// ```
381    /// use atom_syndication::{Feed, Person};
382    ///
383    /// let mut feed = Feed::default();
384    /// feed.set_contributors(vec![Person::default()]);
385    /// assert_eq!(feed.contributors().len(), 1);
386    /// ```
387    pub fn contributors(&self) -> &[Person] {
388        self.contributors.as_slice()
389    }
390
391    /// Set the contributors to this feed.
392    ///
393    /// # Examples
394    ///
395    /// ```
396    /// use atom_syndication::{Feed, Person};
397    ///
398    /// let mut feed = Feed::default();
399    /// feed.set_contributors(vec![Person::default()]);
400    /// ```
401    pub fn set_contributors<V>(&mut self, contributors: V)
402    where
403        V: Into<Vec<Person>>,
404    {
405        self.contributors = contributors.into();
406    }
407
408    /// Return the name of the software used to generate this feed.
409    ///
410    /// # Examples
411    ///
412    /// ```
413    /// use atom_syndication::{Feed, Generator};
414    ///
415    /// let mut feed = Feed::default();
416    /// feed.set_generator(Generator::default());
417    /// assert!(feed.generator().is_some());
418    /// ```
419    pub fn generator(&self) -> Option<&Generator> {
420        self.generator.as_ref()
421    }
422
423    /// Set the name of the software used to generate this feed.
424    ///
425    /// # Examples
426    ///
427    /// ```
428    /// use atom_syndication::{Feed, Generator};
429    ///
430    /// let mut feed = Feed::default();
431    /// feed.set_generator(Generator::default());
432    /// ```
433    pub fn set_generator<V>(&mut self, generator: V)
434    where
435        V: Into<Option<Generator>>,
436    {
437        self.generator = generator.into()
438    }
439
440    /// Return the icon for this feed.
441    ///
442    /// # Examples
443    ///
444    /// ```
445    /// use atom_syndication::Feed;
446    ///
447    /// let mut feed = Feed::default();
448    /// feed.set_icon("http://example.com/icon.png".to_string());
449    /// assert_eq!(feed.icon(), Some("http://example.com/icon.png"));
450    /// ```
451    pub fn icon(&self) -> Option<&str> {
452        self.icon.as_deref()
453    }
454
455    /// Set the icon for this feed.
456    ///
457    /// # Examples
458    ///
459    /// ```
460    /// use atom_syndication::Feed;
461    ///
462    /// let mut feed = Feed::default();
463    /// feed.set_icon("http://example.com/icon.png".to_string());
464    /// ```
465    pub fn set_icon<V>(&mut self, icon: V)
466    where
467        V: Into<Option<String>>,
468    {
469        self.icon = icon.into()
470    }
471
472    /// Return the Web pages related to this feed.
473    ///
474    /// # Examples
475    ///
476    /// ```
477    /// use atom_syndication::{Feed, Link};
478    ///
479    /// let mut feed = Feed::default();
480    /// feed.set_links(vec![Link::default()]);
481    /// assert_eq!(feed.links().len(), 1);
482    /// ```
483    pub fn links(&self) -> &[Link] {
484        self.links.as_slice()
485    }
486
487    /// Set the Web pages related to this feed.
488    ///
489    /// # Examples
490    ///
491    /// ```
492    /// use atom_syndication::{Feed, Link};
493    ///
494    /// let mut feed = Feed::default();
495    /// feed.set_links(vec![Link::default()]);
496    /// ```
497    pub fn set_links<V>(&mut self, links: V)
498    where
499        V: Into<Vec<Link>>,
500    {
501        self.links = links.into();
502    }
503
504    /// Return the logo for this feed.
505    ///
506    /// # Examples
507    ///
508    /// ```
509    /// use atom_syndication::Feed;
510    ///
511    /// let mut feed = Feed::default();
512    /// feed.set_logo("http://example.com/logo.png".to_string());
513    /// assert_eq!(feed.logo(), Some("http://example.com/logo.png"));
514    /// ```
515    pub fn logo(&self) -> Option<&str> {
516        self.logo.as_deref()
517    }
518
519    /// Set the logo for this feed.
520    ///
521    /// # Examples
522    ///
523    /// ```
524    /// use atom_syndication::Feed;
525    ///
526    /// let mut feed = Feed::default();
527    /// feed.set_logo("http://example.com/logo.png".to_string());
528    /// ```
529    pub fn set_logo<V>(&mut self, logo: V)
530    where
531        V: Into<Option<String>>,
532    {
533        self.logo = logo.into()
534    }
535
536    /// Return the information about the rights held in and over this feed.
537    ///
538    /// # Examples
539    ///
540    /// ```
541    /// use atom_syndication::{Feed, Text};
542    ///
543    /// let mut feed = Feed::default();
544    /// feed.set_rights(Text::from("© 2017 John Doe"));
545    /// assert_eq!(feed.rights().map(Text::as_str), Some("© 2017 John Doe"));
546    /// ```
547    pub fn rights(&self) -> Option<&Text> {
548        self.rights.as_ref()
549    }
550
551    /// Set the information about the rights held in and over this feed.
552    ///
553    /// # Examples
554    ///
555    /// ```
556    /// use atom_syndication::{Feed, Text};
557    ///
558    /// let mut feed = Feed::default();
559    /// feed.set_rights(Text::from("© 2017 John Doe"));
560    /// ```
561    pub fn set_rights<V>(&mut self, rights: V)
562    where
563        V: Into<Option<Text>>,
564    {
565        self.rights = rights.into()
566    }
567
568    /// Return the description or subtitle of this feed.
569    ///
570    /// # Examples
571    ///
572    /// ```
573    /// use atom_syndication::{Feed, Text};
574    ///
575    /// let mut feed = Feed::default();
576    /// feed.set_subtitle(Text::from("Feed subtitle"));
577    /// assert_eq!(feed.subtitle().map(Text::as_str), Some("Feed subtitle"));
578    /// ```
579    pub fn subtitle(&self) -> Option<&Text> {
580        self.subtitle.as_ref()
581    }
582
583    /// Set the description or subtitle of this feed.
584    ///
585    /// # Examples
586    ///
587    /// ```
588    /// use atom_syndication::{Feed, Text};
589    ///
590    /// let mut feed = Feed::default();
591    /// feed.set_subtitle(Text::from("Feed subtitle"));
592    /// ```
593    pub fn set_subtitle<V>(&mut self, subtitle: V)
594    where
595        V: Into<Option<Text>>,
596    {
597        self.subtitle = subtitle.into()
598    }
599
600    /// Return the entries in this feed.
601    ///
602    /// # Examples
603    ///
604    /// ```
605    /// use atom_syndication::{Feed, Entry};
606    ///
607    /// let mut feed = Feed::default();
608    /// feed.set_entries(vec![Entry::default()]);
609    /// assert_eq!(feed.entries().len(), 1);
610    /// ```
611    pub fn entries(&self) -> &[Entry] {
612        self.entries.as_slice()
613    }
614
615    /// Set the entries in this feed.
616    ///
617    /// # Examples
618    ///
619    /// ```
620    /// use atom_syndication::{Feed, Entry};
621    ///
622    /// let mut feed = Feed::default();
623    /// feed.set_entries(vec![Entry::default()]);
624    /// ```
625    pub fn set_entries<V>(&mut self, entries: V)
626    where
627        V: Into<Vec<Entry>>,
628    {
629        self.entries = entries.into();
630    }
631
632    /// Return the extensions for this feed.
633    ///
634    /// # Examples
635    ///
636    /// ```
637    /// use std::collections::BTreeMap;
638    /// use atom_syndication::Feed;
639    /// use atom_syndication::extension::{ExtensionMap, Extension};
640    ///
641    /// let extension = Extension::default();
642    ///
643    /// let mut item_map = BTreeMap::<String, Vec<Extension>>::new();
644    /// item_map.insert("ext:name".to_string(), vec![extension]);
645    ///
646    /// let mut extension_map = ExtensionMap::default();
647    /// extension_map.insert("ext".to_string(), item_map);
648    ///
649    /// let mut feed = Feed::default();
650    /// feed.set_extensions(extension_map);
651    /// assert_eq!(feed.extensions()
652    ///                .get("ext")
653    ///                .and_then(|m| m.get("ext:name"))
654    ///                .map(|v| v.len()),
655    ///            Some(1));
656    /// ```
657    pub fn extensions(&self) -> &ExtensionMap {
658        &self.extensions
659    }
660
661    /// Set the extensions for this feed.
662    ///
663    /// # Examples
664    ///
665    /// ```
666    /// use atom_syndication::Feed;
667    /// use atom_syndication::extension::ExtensionMap;
668    ///
669    /// let mut feed = Feed::default();
670    /// feed.set_extensions(ExtensionMap::default());
671    /// ```
672    pub fn set_extensions<V>(&mut self, extensions: V)
673    where
674        V: Into<ExtensionMap>,
675    {
676        self.extensions = extensions.into()
677    }
678
679    /// Return the namespaces for this feed.
680    ///
681    /// # Examples
682    ///
683    /// ```
684    /// use std::collections::BTreeMap;
685    /// use atom_syndication::Feed;
686    ///
687    /// let mut namespaces = BTreeMap::new();
688    /// namespaces.insert("ext".to_string(), "http://example.com".to_string());
689    ///
690    /// let mut feed = Feed::default();
691    /// feed.set_namespaces(namespaces);
692    /// assert_eq!(feed.namespaces().get("ext").map(|s| s.as_str()), Some("http://example.com"));
693    /// ```
694    pub fn namespaces(&self) -> &BTreeMap<String, String> {
695        &self.namespaces
696    }
697
698    /// Set the namespaces for this feed.
699    ///
700    /// # Examples
701    ///
702    /// ```
703    /// use std::collections::BTreeMap;
704    /// use atom_syndication::Feed;
705    ///
706    /// let mut feed = Feed::default();
707    /// feed.set_namespaces(BTreeMap::new());
708    /// ```
709    pub fn set_namespaces<V>(&mut self, namespaces: V)
710    where
711        V: Into<BTreeMap<String, String>>,
712    {
713        self.namespaces = namespaces.into()
714    }
715
716    /// Return base URL of the feed.
717    pub fn base(&self) -> Option<&str> {
718        self.base.as_deref()
719    }
720
721    /// Set base URL of the feed.
722    pub fn set_base<V>(&mut self, base: V)
723    where
724        V: Into<Option<String>>,
725    {
726        self.base = base.into();
727    }
728
729    /// Return natural language of the feed.
730    pub fn lang(&self) -> Option<&str> {
731        self.lang.as_deref()
732    }
733
734    /// Set the base URL of the feed.
735    pub fn set_lang<V>(&mut self, lang: V)
736    where
737        V: Into<Option<String>>,
738    {
739        self.lang = lang.into();
740    }
741}
742
743impl FromXml for Feed {
744    fn from_xml<B: BufRead>(
745        reader: &mut Reader<B>,
746        mut atts: Attributes<'_>,
747    ) -> Result<Self, Error> {
748        let mut feed = Feed::default();
749        let mut buf = Vec::new();
750
751        for att in atts.with_checks(false).flatten() {
752            match decode(att.key.as_ref(), reader)? {
753                Cow::Borrowed("xml:base") => {
754                    feed.base = Some(attr_value(&att, reader)?.to_string())
755                }
756                Cow::Borrowed("xml:lang") => {
757                    feed.lang = Some(attr_value(&att, reader)?.to_string())
758                }
759                Cow::Borrowed("xmlns:dc") => {}
760                key => {
761                    if let Some(ns) = key.strip_prefix("xmlns:") {
762                        feed.namespaces
763                            .insert(ns.to_string(), attr_value(&att, reader)?.to_string());
764                    }
765                }
766            }
767        }
768
769        loop {
770            match reader.read_event_into(&mut buf).map_err(XmlError::new)? {
771                Event::Start(element) => match decode(element.name().as_ref(), reader)? {
772                    Cow::Borrowed("title") => {
773                        feed.title = Text::from_xml(reader, element.attributes())?
774                    }
775                    Cow::Borrowed("id") => feed.id = atom_text(reader)?.unwrap_or_default(),
776                    Cow::Borrowed("updated") => {
777                        feed.updated = atom_datetime(reader)?.unwrap_or_else(default_fixed_datetime)
778                    }
779                    Cow::Borrowed("author") => feed
780                        .authors
781                        .push(Person::from_xml(reader, element.attributes())?),
782                    Cow::Borrowed("category") => {
783                        feed.categories.push(Category::from_xml(reader, &element)?);
784                        skip(element.name(), reader)?;
785                    }
786                    Cow::Borrowed("contributor") => feed
787                        .contributors
788                        .push(Person::from_xml(reader, element.attributes())?),
789                    Cow::Borrowed("generator") => {
790                        feed.generator = Some(Generator::from_xml(reader, element.attributes())?)
791                    }
792                    Cow::Borrowed("icon") => feed.icon = atom_text(reader)?,
793                    Cow::Borrowed("link") => {
794                        feed.links.push(Link::from_xml(reader, &element)?);
795                        skip(element.name(), reader)?;
796                    }
797                    Cow::Borrowed("logo") => feed.logo = atom_text(reader)?,
798                    Cow::Borrowed("rights") => {
799                        feed.rights = Some(Text::from_xml(reader, element.attributes())?)
800                    }
801                    Cow::Borrowed("subtitle") => {
802                        feed.subtitle = Some(Text::from_xml(reader, element.attributes())?)
803                    }
804                    Cow::Borrowed("entry") => feed
805                        .entries
806                        .push(Entry::from_xml(reader, element.attributes())?),
807                    n => {
808                        if let Some((ns, name)) = extension_name(n.as_ref()) {
809                            parse_extension(
810                                reader,
811                                element.attributes(),
812                                ns,
813                                name,
814                                &mut feed.extensions,
815                            )?;
816                        } else {
817                            skip(element.name(), reader)?;
818                        }
819                    }
820                },
821                Event::End(_) => break,
822                Event::Eof => return Err(Error::Eof),
823                _ => {}
824            }
825
826            buf.clear();
827        }
828
829        Ok(feed)
830    }
831}
832
833impl ToXml for Feed {
834    fn to_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), XmlError> {
835        let name = "feed";
836        let mut element = BytesStart::new(name);
837        element.push_attribute(("xmlns", "http://www.w3.org/2005/Atom"));
838
839        for (ns, uri) in &self.namespaces {
840            element.push_attribute((format!("xmlns:{}", ns).as_bytes(), uri.as_bytes()));
841        }
842
843        if let Some(ref base) = self.base {
844            element.push_attribute(("xml:base", base.as_str()));
845        }
846
847        if let Some(ref lang) = self.lang {
848            element.push_attribute(("xml:lang", lang.as_str()));
849        }
850
851        writer
852            .write_event(Event::Start(element))
853            .map_err(XmlError::new)?;
854        writer.write_object_named(&self.title, "title")?;
855        writer.write_text_element("id", &self.id)?;
856        writer.write_text_element("updated", &self.updated.to_rfc3339())?;
857        writer.write_objects_named(&self.authors, "author")?;
858        writer.write_objects(&self.categories)?;
859        writer.write_objects_named(&self.contributors, "contributor")?;
860
861        if let Some(ref generator) = self.generator {
862            writer.write_object(generator)?;
863        }
864
865        if let Some(ref icon) = self.icon {
866            writer.write_text_element("icon", icon)?;
867        }
868
869        writer.write_objects(&self.links)?;
870
871        if let Some(ref logo) = self.logo {
872            writer.write_text_element("logo", logo)?;
873        }
874
875        if let Some(ref rights) = self.rights {
876            writer.write_object_named(rights, "rights")?;
877        }
878
879        if let Some(ref subtitle) = self.subtitle {
880            writer.write_object_named(subtitle, "subtitle")?;
881        }
882
883        writer.write_objects(&self.entries)?;
884
885        for map in self.extensions.values() {
886            for extensions in map.values() {
887                writer.write_objects(extensions)?;
888            }
889        }
890
891        writer
892            .write_event(Event::End(BytesEnd::new(name)))
893            .map_err(XmlError::new)?;
894
895        Ok(())
896    }
897}
898
899impl FromStr for Feed {
900    type Err = Error;
901
902    fn from_str(s: &str) -> Result<Self, Error> {
903        Feed::read_from(s.as_bytes())
904    }
905}
906
907impl ToString for Feed {
908    fn to_string(&self) -> String {
909        let buf = self.write_to(Vec::new()).unwrap_or_default();
910        // this unwrap should be safe since the bytes written from the Feed are all valid utf8
911        String::from_utf8(buf).unwrap()
912    }
913}
914
915impl Default for Feed {
916    fn default() -> Self {
917        Feed {
918            title: Text::default(),
919            id: String::new(),
920            updated: default_fixed_datetime(),
921            authors: Vec::new(),
922            categories: Vec::new(),
923            contributors: Vec::new(),
924            generator: None,
925            icon: None,
926            links: Vec::new(),
927            logo: None,
928            rights: None,
929            subtitle: None,
930            entries: Vec::new(),
931            extensions: ExtensionMap::default(),
932            namespaces: BTreeMap::default(),
933            base: None,
934            lang: None,
935        }
936    }
937}
938
939#[cfg(feature = "builders")]
940impl FeedBuilder {
941    /// Builds a new `Feed`.
942    pub fn build(&self) -> Feed {
943        self.build_impl().unwrap()
944    }
945}
946
947#[cfg(test)]
948mod test {
949    use super::*;
950
951    #[test]
952    fn test_default() {
953        let feed = Feed::default();
954        let xml_fragment = r#"<?xml version="1.0"?>
955<feed xmlns="http://www.w3.org/2005/Atom"><title></title><id></id><updated>1970-01-01T00:00:00+00:00</updated></feed>"#;
956        assert_eq!(feed.to_string(), xml_fragment);
957        let loaded_feed = Feed::read_from(xml_fragment.as_bytes()).unwrap();
958        assert_eq!(loaded_feed, feed);
959        assert_eq!(loaded_feed.base(), None);
960        assert_eq!(loaded_feed.lang(), None);
961    }
962
963    #[test]
964    fn test_base_and_lang() {
965        let mut feed = Feed::default();
966        feed.set_base(Some("http://example.com/blog/".into()));
967        feed.set_lang(Some("fr_FR".into()));
968        let xml_fragment = r#"<?xml version="1.0"?>
969<feed xmlns="http://www.w3.org/2005/Atom" xml:base="http://example.com/blog/" xml:lang="fr_FR"><title></title><id></id><updated>1970-01-01T00:00:00+00:00</updated></feed>"#;
970        assert_eq!(feed.to_string(), xml_fragment);
971        let loaded_feed = Feed::read_from(xml_fragment.as_bytes()).unwrap();
972        assert_eq!(loaded_feed, feed);
973        assert_eq!(loaded_feed.base(), Some("http://example.com/blog/"));
974        assert_eq!(loaded_feed.lang(), Some("fr_FR"));
975    }
976
977    #[test]
978    fn test_write_no_decl() {
979        let feed = Feed::default();
980        let xml = feed
981            .write_with_config(
982                Vec::new(),
983                WriteConfig {
984                    write_document_declaration: false,
985                    indent_size: None,
986                },
987            )
988            .unwrap();
989        assert_eq!(
990            String::from_utf8_lossy(&xml),
991            r#"<feed xmlns="http://www.w3.org/2005/Atom"><title></title><id></id><updated>1970-01-01T00:00:00+00:00</updated></feed>"#
992        );
993    }
994
995    #[test]
996    fn test_write_indented() {
997        let feed = Feed::default();
998        let xml = feed
999            .write_with_config(
1000                Vec::new(),
1001                WriteConfig {
1002                    write_document_declaration: true,
1003                    indent_size: Some(4),
1004                },
1005            )
1006            .unwrap();
1007        assert_eq!(
1008            String::from_utf8_lossy(&xml),
1009            r#"<?xml version="1.0"?>
1010<feed xmlns="http://www.w3.org/2005/Atom">
1011    <title></title>
1012    <id></id>
1013    <updated>1970-01-01T00:00:00+00:00</updated>
1014</feed>"#
1015        );
1016    }
1017
1018    #[test]
1019    fn test_write_no_decl_indented() {
1020        let feed = Feed::default();
1021        let xml = feed
1022            .write_with_config(
1023                Vec::new(),
1024                WriteConfig {
1025                    write_document_declaration: false,
1026                    indent_size: Some(4),
1027                },
1028            )
1029            .unwrap();
1030        assert_eq!(
1031            String::from_utf8_lossy(&xml),
1032            r#"<feed xmlns="http://www.w3.org/2005/Atom">
1033    <title></title>
1034    <id></id>
1035    <updated>1970-01-01T00:00:00+00:00</updated>
1036</feed>"#
1037        );
1038    }
1039}