1 use std::io::{BufRead, Write};
2 
3 use quick_xml::events::attributes::Attributes;
4 use quick_xml::events::{BytesEnd, BytesStart, Event};
5 use quick_xml::Error as XmlError;
6 use quick_xml::Reader;
7 use quick_xml::Writer;
8 
9 use crate::category::Category;
10 use crate::content::Content;
11 use crate::error::Error;
12 use crate::extension::util::{extension_name, parse_extension};
13 use crate::extension::ExtensionMap;
14 use crate::fromxml::FromXml;
15 use crate::link::Link;
16 use crate::person::Person;
17 use crate::source::Source;
18 use crate::text::Text;
19 use crate::toxml::{ToXml, WriterExt};
20 use crate::util::{atom_datetime, atom_text, default_fixed_datetime, FixedDateTime};
21 
22 /// Represents an entry in an Atom feed
23 #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
24 #[derive(Debug, Clone, PartialEq)]
25 #[cfg_attr(feature = "builders", derive(Builder))]
26 #[cfg_attr(
27     feature = "builders",
28     builder(
29         setter(into),
30         default,
31         build_fn(name = "build_impl", private, error = "never::Never")
32     )
33 )]
34 pub struct Entry {
35     /// A human-readable title for the entry.
36     pub title: Text,
37     /// A universally unique and permanent URI.
38     pub id: String,
39     /// The last time the entry was modified.
40     pub updated: FixedDateTime,
41     /// The authors of the feed.
42     #[cfg_attr(feature = "builders", builder(setter(each = "author")))]
43     pub authors: Vec<Person>,
44     /// The categories that the entry belongs to.
45     #[cfg_attr(feature = "builders", builder(setter(each = "category")))]
46     pub categories: Vec<Category>,
47     /// The contributors to the entry.
48     #[cfg_attr(feature = "builders", builder(setter(each = "contributor")))]
49     pub contributors: Vec<Person>,
50     /// The Web pages related to the entry.
51     #[cfg_attr(feature = "builders", builder(setter(each = "link")))]
52     pub links: Vec<Link>,
53     /// The time of the initial creation or first availability of the entry.
54     pub published: Option<FixedDateTime>,
55     /// Information about rights held in and over the entry.
56     pub rights: Option<Text>,
57     /// The source information if an entry is copied from one feed into another feed.
58     pub source: Option<Source>,
59     /// A short summary, abstract, or excerpt of the entry.
60     pub summary: Option<Text>,
61     /// Contains or links to the complete content of the entry.
62     pub content: Option<Content>,
63     /// The extensions for this entry.
64     #[cfg_attr(feature = "builders", builder(setter(each = "extension")))]
65     pub extensions: ExtensionMap,
66 }
67 
68 impl Entry {
69     /// Return the title of this entry.
70     ///
71     /// # Examples
72     ///
73     /// ```
74     /// use atom_syndication::Entry;
75     ///
76     /// let mut entry = Entry::default();
77     /// entry.set_title("Entry Title");
78     /// assert_eq!(entry.title(), "Entry Title");
79     /// ```
title(&self) -> &Text80     pub fn title(&self) -> &Text {
81         &self.title
82     }
83 
84     /// Set the title of this entry.
85     ///
86     /// # Examples
87     ///
88     /// ```
89     /// use atom_syndication::Entry;
90     ///
91     /// let mut entry = Entry::default();
92     /// entry.set_title("Entry Title");
93     /// ```
set_title<V>(&mut self, title: V) where V: Into<Text>,94     pub fn set_title<V>(&mut self, title: V)
95     where
96         V: Into<Text>,
97     {
98         self.title = title.into();
99     }
100 
101     /// Return the unique URI of this entry.
102     ///
103     /// # Examples
104     ///
105     /// ```
106     /// use atom_syndication::Entry;
107     ///
108     /// let mut entry = Entry::default();
109     /// entry.set_id("urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6");
110     /// assert_eq!(entry.id(), "urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6");
111     /// ```
id(&self) -> &str112     pub fn id(&self) -> &str {
113         self.id.as_str()
114     }
115 
116     /// Set the unique URI of this entry.
117     ///
118     /// # Examples
119     ///
120     /// ```
121     /// use atom_syndication::Entry;
122     ///
123     /// let mut entry = Entry::default();
124     /// entry.set_id("urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6");
125     /// ```
set_id<V>(&mut self, id: V) where V: Into<String>,126     pub fn set_id<V>(&mut self, id: V)
127     where
128         V: Into<String>,
129     {
130         self.id = id.into();
131     }
132 
133     /// Return the last time that this entry was modified.
134     ///
135     /// # Examples
136     ///
137     /// ```
138     /// use atom_syndication::Entry;
139     /// use atom_syndication::FixedDateTime;
140     /// use std::str::FromStr;
141     ///
142     /// let mut entry = Entry::default();
143     /// entry.set_updated(FixedDateTime::from_str("2017-06-03T15:15:44-05:00").unwrap());
144     /// assert_eq!(entry.updated().to_rfc3339(), "2017-06-03T15:15:44-05:00");
145     /// ```
updated(&self) -> &FixedDateTime146     pub fn updated(&self) -> &FixedDateTime {
147         &self.updated
148     }
149 
150     /// Set the last time that this entry was modified.
151     ///
152     /// # Examples
153     ///
154     /// ```
155     /// use atom_syndication::Entry;
156     /// use atom_syndication::FixedDateTime;
157     /// use std::str::FromStr;
158     ///
159     /// let mut entry = Entry::default();
160     /// entry.set_updated(FixedDateTime::from_str("2017-06-03T15:15:44-05:00").unwrap());
161     /// ```
set_updated<V>(&mut self, updated: V) where V: Into<FixedDateTime>,162     pub fn set_updated<V>(&mut self, updated: V)
163     where
164         V: Into<FixedDateTime>,
165     {
166         self.updated = updated.into();
167     }
168 
169     /// Return the authors of this entry.
170     ///
171     /// # Examples
172     ///
173     /// ```
174     /// use atom_syndication::{Entry, Person};
175     ///
176     /// let mut entry = Entry::default();
177     /// entry.set_authors(vec![Person::default()]);
178     /// assert_eq!(entry.authors().len(), 1);
179     /// ```
authors(&self) -> &[Person]180     pub fn authors(&self) -> &[Person] {
181         self.authors.as_slice()
182     }
183 
184     /// Set the authors of this entry.
185     ///
186     /// # Examples
187     ///
188     /// ```
189     /// use atom_syndication::{Entry, Person};
190     ///
191     /// let mut entry = Entry::default();
192     /// entry.set_authors(vec![Person::default()]);
193     /// ```
set_authors<V>(&mut self, authors: V) where V: Into<Vec<Person>>,194     pub fn set_authors<V>(&mut self, authors: V)
195     where
196         V: Into<Vec<Person>>,
197     {
198         self.authors = authors.into();
199     }
200 
201     /// Return the categories this entry belongs to.
202     ///
203     /// # Examples
204     ///
205     /// ```
206     /// use atom_syndication::{Entry, Category};
207     ///
208     /// let mut entry = Entry::default();
209     /// entry.set_categories(vec![Category::default()]);
210     /// assert_eq!(entry.categories().len(), 1);
211     /// ```
categories(&self) -> &[Category]212     pub fn categories(&self) -> &[Category] {
213         self.categories.as_slice()
214     }
215 
216     /// Set the categories this entry belongs to.
217     ///
218     /// # Examples
219     ///
220     /// ```
221     /// use atom_syndication::{Entry, Category};
222     ///
223     /// let mut entry = Entry::default();
224     /// entry.set_categories(vec![Category::default()]);
225     /// ```
set_categories<V>(&mut self, categories: V) where V: Into<Vec<Category>>,226     pub fn set_categories<V>(&mut self, categories: V)
227     where
228         V: Into<Vec<Category>>,
229     {
230         self.categories = categories.into();
231     }
232 
233     /// Return the contributors to this entry.
234     ///
235     /// # Examples
236     ///
237     /// ```
238     /// use atom_syndication::{Entry, Person};
239     ///
240     /// let mut entry = Entry::default();
241     /// entry.set_contributors(vec![Person::default()]);
242     /// assert_eq!(entry.contributors().len(), 1);
243     /// ```
contributors(&self) -> &[Person]244     pub fn contributors(&self) -> &[Person] {
245         self.contributors.as_slice()
246     }
247 
248     /// Set the contributors to this entry.
249     ///
250     /// # Examples
251     ///
252     /// ```
253     /// use atom_syndication::{Entry, Person};
254     ///
255     /// let mut entry = Entry::default();
256     /// entry.set_contributors(vec![Person::default()]);
257     /// ```
set_contributors<V>(&mut self, contributors: V) where V: Into<Vec<Person>>,258     pub fn set_contributors<V>(&mut self, contributors: V)
259     where
260         V: Into<Vec<Person>>,
261     {
262         self.contributors = contributors.into();
263     }
264 
265     /// Return the links for this entry.
266     ///
267     /// # Examples
268     ///
269     /// ```
270     /// use atom_syndication::{Entry, Link};
271     ///
272     /// let mut entry = Entry::default();
273     /// entry.set_links(vec![Link::default()]);
274     /// assert_eq!(entry.links().len(), 1);
275     /// ```
links(&self) -> &[Link]276     pub fn links(&self) -> &[Link] {
277         self.links.as_slice()
278     }
279 
280     /// Set the links for this entry.
281     ///
282     /// # Examples
283     ///
284     /// ```
285     /// use atom_syndication::{Entry, Link};
286     ///
287     /// let mut entry = Entry::default();
288     /// entry.set_links(vec![Link::default()]);
289     /// ```
set_links<V>(&mut self, links: V) where V: Into<Vec<Link>>,290     pub fn set_links<V>(&mut self, links: V)
291     where
292         V: Into<Vec<Link>>,
293     {
294         self.links = links.into();
295     }
296 
297     /// Return the time that this entry was initially created or first made available.
298     ///
299     /// # Examples
300     ///
301     /// ```
302     /// use atom_syndication::Entry;
303     /// use atom_syndication::FixedDateTime;
304     /// use std::str::FromStr;
305     ///
306     /// let mut entry = Entry::default();
307     /// entry.set_published(FixedDateTime::from_str("2017-06-01T15:15:44-05:00").unwrap());
308     /// assert_eq!(entry.published().map(|x|x.to_rfc3339()), Some("2017-06-01T15:15:44-05:00".to_string()));
309     /// ```
published(&self) -> Option<&FixedDateTime>310     pub fn published(&self) -> Option<&FixedDateTime> {
311         self.published.as_ref()
312     }
313 
314     /// Set the time that this entry was initially created or first made available.
315     ///
316     /// # Examples
317     ///
318     /// ```
319     /// use atom_syndication::Entry;
320     /// use atom_syndication::FixedDateTime;
321     /// use std::str::FromStr;
322     ///
323     /// let mut entry = Entry::default();
324     /// entry.set_published(FixedDateTime::from_str("2017-06-01T15:15:44-05:00").unwrap());
325     /// ```
set_published<V>(&mut self, published: V) where V: Into<Option<FixedDateTime>>,326     pub fn set_published<V>(&mut self, published: V)
327     where
328         V: Into<Option<FixedDateTime>>,
329     {
330         self.published = published.into();
331     }
332 
333     /// Return the information about the rights held in and over this entry.
334     ///
335     /// # Examples
336     ///
337     /// ```
338     /// use atom_syndication::{Entry, Text};
339     ///
340     /// let mut entry = Entry::default();
341     /// entry.set_rights(Text::from("© 2017 John Doe"));
342     /// assert_eq!(entry.rights().map(Text::as_str), Some("© 2017 John Doe"));
343     /// ```
rights(&self) -> Option<&Text>344     pub fn rights(&self) -> Option<&Text> {
345         self.rights.as_ref()
346     }
347 
348     /// Set the information about the rights held in and over this entry.
349     ///
350     /// # Examples
351     ///
352     /// ```
353     /// use atom_syndication::{Entry, Text};
354     ///
355     /// let mut entry = Entry::default();
356     /// entry.set_rights(Text::from("© 2017 John Doe"));
357     /// ```
set_rights<V>(&mut self, rights: V) where V: Into<Option<Text>>,358     pub fn set_rights<V>(&mut self, rights: V)
359     where
360         V: Into<Option<Text>>,
361     {
362         self.rights = rights.into();
363     }
364 
365     /// Return the source of this entry if it was copied from another feed.
366     ///
367     /// # Examples
368     ///
369     /// ```
370     /// use atom_syndication::{Entry, Source};
371     ///
372     /// let mut entry = Entry::default();
373     /// entry.set_source(Source::default());
374     /// assert!(entry.source().is_some());
375     /// ```
source(&self) -> Option<&Source>376     pub fn source(&self) -> Option<&Source> {
377         self.source.as_ref()
378     }
379 
380     /// Set the source of this entry if it was copied from another feed.
381     ///
382     /// # Examples
383     ///
384     /// ```
385     /// use atom_syndication::{Entry, Source};
386     ///
387     /// let mut entry = Entry::default();
388     /// entry.set_source(Source::default());
389     /// ```
set_source<V>(&mut self, source: V) where V: Into<Option<Source>>,390     pub fn set_source<V>(&mut self, source: V)
391     where
392         V: Into<Option<Source>>,
393     {
394         self.source = source.into()
395     }
396 
397     /// Return the summary of this entry.
398     ///
399     /// # Examples
400     ///
401     /// ```
402     /// use atom_syndication::{Entry, Text};
403     ///
404     /// let mut entry = Entry::default();
405     /// entry.set_summary(Text::from("Entry summary."));
406     /// assert_eq!(entry.summary().map(Text::as_str), Some("Entry summary."));
407     /// ```
summary(&self) -> Option<&Text>408     pub fn summary(&self) -> Option<&Text> {
409         self.summary.as_ref()
410     }
411 
412     /// Set the summary of this entry.
413     ///
414     /// # Examples
415     ///
416     /// ```
417     /// use atom_syndication::{Entry, Text};
418     ///
419     /// let mut entry = Entry::default();
420     /// entry.set_summary(Text::from("Entry summary."));
421     /// ```
set_summary<V>(&mut self, summary: V) where V: Into<Option<Text>>,422     pub fn set_summary<V>(&mut self, summary: V)
423     where
424         V: Into<Option<Text>>,
425     {
426         self.summary = summary.into();
427     }
428 
429     /// Return the content of this entry.
430     ///
431     /// # Examples
432     ///
433     /// ```
434     /// use atom_syndication::{Entry, Content};
435     ///
436     /// let mut entry = Entry::default();
437     /// entry.set_content(Content::default());
438     /// assert!(entry.content().is_some());
439     /// ```
content(&self) -> Option<&Content>440     pub fn content(&self) -> Option<&Content> {
441         self.content.as_ref()
442     }
443 
444     /// Set the content of this entry.
445     ///
446     /// # Examples
447     ///
448     /// ```
449     /// use atom_syndication::{Entry, Content};
450     ///
451     /// let mut entry = Entry::default();
452     /// entry.set_content(Content::default());
453     /// assert!(entry.content().is_some());
454     /// ```
set_content<V>(&mut self, content: V) where V: Into<Option<Content>>,455     pub fn set_content<V>(&mut self, content: V)
456     where
457         V: Into<Option<Content>>,
458     {
459         self.content = content.into();
460     }
461 
462     /// Return the extensions for this entry.
463     ///
464     /// # Examples
465     ///
466     /// ```
467     /// use std::collections::BTreeMap;
468     /// use atom_syndication::Entry;
469     /// use atom_syndication::extension::{ExtensionMap, Extension};
470     ///
471     /// let extension = Extension::default();
472     ///
473     /// let mut item_map = BTreeMap::<String, Vec<Extension>>::new();
474     /// item_map.insert("ext:name".to_string(), vec![extension]);
475     ///
476     /// let mut extension_map = ExtensionMap::default();
477     /// extension_map.insert("ext".to_string(), item_map);
478     ///
479     /// let mut entry = Entry::default();
480     /// entry.set_extensions(extension_map);
481     /// assert_eq!(entry.extensions()
482     ///                 .get("ext")
483     ///                 .and_then(|m| m.get("ext:name"))
484     ///                 .map(|v| v.len()),
485     ///            Some(1));
486     /// ```
extensions(&self) -> &ExtensionMap487     pub fn extensions(&self) -> &ExtensionMap {
488         &self.extensions
489     }
490 
491     /// Set the extensions for this entry.
492     ///
493     /// # Examples
494     ///
495     /// ```
496     /// use atom_syndication::Entry;
497     /// use atom_syndication::extension::ExtensionMap;
498     ///
499     /// let mut entry = Entry::default();
500     /// entry.set_extensions(ExtensionMap::default());
501     /// ```
set_extensions<V>(&mut self, extensions: V) where V: Into<ExtensionMap>,502     pub fn set_extensions<V>(&mut self, extensions: V)
503     where
504         V: Into<ExtensionMap>,
505     {
506         self.extensions = extensions.into()
507     }
508 }
509 
510 impl FromXml for Entry {
from_xml<B: BufRead>(reader: &mut Reader<B>, _: Attributes<'_>) -> Result<Self, Error>511     fn from_xml<B: BufRead>(reader: &mut Reader<B>, _: Attributes<'_>) -> Result<Self, Error> {
512         let mut entry = Entry::default();
513         let mut buf = Vec::new();
514 
515         loop {
516             match reader.read_event(&mut buf)? {
517                 Event::Start(element) => match element.name() {
518                     b"id" => entry.id = atom_text(reader)?.unwrap_or_default(),
519                     b"title" => entry.title = Text::from_xml(reader, element.attributes())?,
520                     b"updated" => {
521                         entry.updated =
522                             atom_datetime(reader)?.unwrap_or_else(default_fixed_datetime)
523                     }
524                     b"author" => entry
525                         .authors
526                         .push(Person::from_xml(reader, element.attributes())?),
527                     b"category" => entry
528                         .categories
529                         .push(Category::from_xml(reader, element.attributes())?),
530                     b"contributor" => entry
531                         .contributors
532                         .push(Person::from_xml(reader, element.attributes())?),
533                     b"link" => entry
534                         .links
535                         .push(Link::from_xml(reader, element.attributes())?),
536                     b"published" => entry.published = atom_datetime(reader)?,
537                     b"rights" => entry.rights = Some(Text::from_xml(reader, element.attributes())?),
538                     b"source" => {
539                         entry.source = Some(Source::from_xml(reader, element.attributes())?)
540                     }
541                     b"summary" => {
542                         entry.summary = Some(Text::from_xml(reader, element.attributes())?)
543                     }
544                     b"content" => {
545                         entry.content = Some(Content::from_xml(reader, element.attributes())?)
546                     }
547                     n => {
548                         if let Some((ns, name)) = extension_name(element.name()) {
549                             parse_extension(
550                                 reader,
551                                 element.attributes(),
552                                 ns,
553                                 name,
554                                 &mut entry.extensions,
555                             )?;
556                         } else {
557                             reader.read_to_end(n, &mut Vec::new())?;
558                         }
559                     }
560                 },
561                 Event::End(_) => break,
562                 Event::Eof => return Err(Error::Eof),
563                 _ => {}
564             }
565 
566             buf.clear();
567         }
568 
569         Ok(entry)
570     }
571 }
572 
573 impl ToXml for Entry {
to_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), XmlError>574     fn to_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), XmlError> {
575         let name = b"entry";
576         writer.write_event(Event::Start(BytesStart::borrowed(name, name.len())))?;
577         writer.write_object_named(&self.title, b"title")?;
578         writer.write_text_element(b"id", &*self.id)?;
579         writer.write_text_element(b"updated", &*self.updated.to_rfc3339())?;
580         writer.write_objects_named(&self.authors, "author")?;
581         writer.write_objects(&self.categories)?;
582         writer.write_objects_named(&self.contributors, "contributor")?;
583         writer.write_objects(&self.links)?;
584 
585         if let Some(ref published) = self.published {
586             writer.write_text_element(b"published", &published.to_rfc3339())?;
587         }
588 
589         if let Some(ref rights) = self.rights {
590             writer.write_object_named(rights, b"rights")?;
591         }
592 
593         if let Some(ref source) = self.source {
594             writer.write_object(source)?;
595         }
596 
597         if let Some(ref summary) = self.summary {
598             writer.write_object_named(summary, b"summary")?;
599         }
600 
601         if let Some(ref content) = self.content {
602             writer.write_object(content)?;
603         }
604 
605         for map in self.extensions.values() {
606             for extensions in map.values() {
607                 writer.write_objects(extensions)?;
608             }
609         }
610 
611         writer.write_event(Event::End(BytesEnd::borrowed(name)))?;
612 
613         Ok(())
614     }
615 }
616 
617 impl Default for Entry {
default() -> Self618     fn default() -> Self {
619         Entry {
620             title: Text::default(),
621             id: String::new(),
622             updated: default_fixed_datetime(),
623             authors: Vec::new(),
624             categories: Vec::new(),
625             contributors: Vec::new(),
626             links: Vec::new(),
627             published: None,
628             rights: None,
629             source: None,
630             summary: None,
631             content: None,
632             extensions: ExtensionMap::default(),
633         }
634     }
635 }
636 
637 #[cfg(feature = "builders")]
638 impl EntryBuilder {
639     /// Builds a new `Entry`.
build(&self) -> Entry640     pub fn build(&self) -> Entry {
641         self.build_impl().unwrap()
642     }
643 }
644