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}