atom_syndication-0.12.6/.cargo_vcs_info.json0000644000000001360000000000100144660ustar { "git": { "sha1": "f9fefda1a31c2340569314b2d0d77448a0a7f42b" }, "path_in_vcs": "" }atom_syndication-0.12.6/Cargo.toml0000644000000033050000000000100124650ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "atom_syndication" version = "0.12.6" authors = ["James Hurst "] build = false include = [ "src/*", "Cargo.toml", "LICENSE-MIT", "LICENSE-APACHE", "README.md", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Library for serializing the Atom web content syndication format" documentation = "https://docs.rs/atom_syndication/" readme = "README.md" keywords = [ "atom", "feed", "parser", "parsing", ] categories = ["parser-implementations"] license = "MIT/Apache-2.0" repository = "https://github.com/rust-syndication/atom" [lib] name = "atom_syndication" path = "src/lib.rs" [dependencies.chrono] version = "0.4" features = ["alloc"] default-features = false [dependencies.derive_builder] version = "0.20" optional = true [dependencies.diligent-date-parser] version = "0.1.3" [dependencies.never] version = "0.1" optional = true [dependencies.quick-xml] version = "0.37" features = ["encoding"] [dependencies.serde] version = "1.0" features = ["derive"] optional = true [features] builders = [ "derive_builder", "never", ] default = ["builders"] with-serde = [ "serde", "chrono/serde", ] atom_syndication-0.12.6/Cargo.toml.orig000064400000000000000000000017401046102023000161470ustar 00000000000000[package] name = "atom_syndication" version = "0.12.6" authors = ["James Hurst "] edition = "2021" description = "Library for serializing the Atom web content syndication format" repository = "https://github.com/rust-syndication/atom" documentation = "https://docs.rs/atom_syndication/" license = "MIT/Apache-2.0" readme = "README.md" keywords = ["atom", "feed", "parser", "parsing"] categories = ["parser-implementations"] include = ["src/*", "Cargo.toml", "LICENSE-MIT", "LICENSE-APACHE", "README.md"] [dependencies] diligent-date-parser = "0.1.3" quick-xml = { version = "0.37", features = ["encoding"] } chrono = { version = "0.4", default-features = false, features = ["alloc"] } derive_builder = { version = "0.20", optional = true } never = { version = "0.1", optional = true } serde = { version = "1.0", optional = true, features = ["derive"] } [features] default = ["builders"] builders = ["derive_builder", "never"] with-serde = ["serde", "chrono/serde"] atom_syndication-0.12.6/LICENSE-APACHE000064400000000000000000000261411046102023000152060ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2017 The rust-syndication Developers Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. atom_syndication-0.12.6/LICENSE-MIT000064400000000000000000000020771046102023000147200ustar 00000000000000MIT License Copyright © 2017 The rust-syndication Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. atom_syndication-0.12.6/README.md000064400000000000000000000043161046102023000145410ustar 00000000000000# atom [![Build status](https://github.com/rust-syndication/atom/workflows/Build/badge.svg)](https://github.com/rust-syndication/atom/actions) [![Crates.io Status](https://img.shields.io/crates/v/atom_syndication.svg)](https://crates.io/crates/atom_syndication) [![Coverage](https://codecov.io/gh/rust-syndication/atom/branch/master/graph/badge.svg)](https://codecov.io/gh/rust-syndication/atom/) Library for serializing the Atom web content syndication format. [Documentation](https://docs.rs/atom_syndication/) This crate requires *Rustc version 1.57.0 or greater*. ## Usage Add the dependency to your `Cargo.toml`. ```toml [dependencies] atom_syndication = "0.12" ``` Or, if you want [Serde](https://github.com/serde-rs/serde) include the feature like this: ```toml [dependencies] atom_syndication = { version = "0.12", features = ["with-serde"] } ``` The package includes a single crate named `atom_syndication`. ```rust extern crate atom_syndication; ``` ## Reading A feed can be read from any object that implements the `BufRead` trait or using the `FromStr` trait. ```rust use std::fs::File; use std::io::BufReader; use atom_syndication::Feed; let file = File::open("example.xml").unwrap(); let feed = Feed::read_from(BufReader::new(file)).unwrap(); let string = ""; let feed = string.parse::().unwrap(); ``` ## Writing A feed can be written to any object that implements the `Write` trait or converted to an XML string using the `ToString` trait. ### Example ```rust use std::fs::File; use std::io::{BufReader, sink}; use atom_syndication::Feed; let file = File::open("example.xml").unwrap(); let feed = Feed::read_from(BufReader::new(file)).unwrap(); // write to the feed to a writer feed.write_to(sink()).unwrap(); // convert the feed to a string let string = feed.to_string(); ``` ## Invalid Feeds As a best effort to parse invalid feeds `atom_syndication` will default elements declared as "required" by the Atom specification to an empty string. ## License Licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. atom_syndication-0.12.6/src/category.rs000064400000000000000000000113151046102023000162310ustar 00000000000000use std::borrow::Cow; use std::io::{BufRead, Write}; use quick_xml::events::{BytesStart, Event}; use quick_xml::Reader; use quick_xml::Writer; use crate::error::{Error, XmlError}; use crate::toxml::ToXml; use crate::util::{attr_value, decode}; /// Represents a category in an Atom feed #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[derive(Debug, Default, Clone, PartialEq)] #[cfg_attr(feature = "builders", derive(Builder))] #[cfg_attr( feature = "builders", builder( setter(into), default, build_fn(name = "build_impl", private, error = "never::Never") ) )] pub struct Category { /// Identifies the category. pub term: String, /// Identifies the categorization scheme via a URI. pub scheme: Option, /// A human-readable label for display. pub label: Option, } impl Category { /// Return the term that identifies this category. /// /// # Examples /// /// ``` /// use atom_syndication::Category; /// /// let mut category = Category::default(); /// category.set_term("technology"); /// assert_eq!(category.term(), "technology"); /// ``` pub fn term(&self) -> &str { self.term.as_str() } /// Set the term that identifies this category. /// /// # Examples /// /// ``` /// use atom_syndication::Category; /// /// let mut category = Category::default(); /// category.set_term("technology"); /// ``` pub fn set_term(&mut self, term: V) where V: Into, { self.term = term.into(); } /// Return the categorization scheme URI. /// /// # Examples /// /// ``` /// use atom_syndication::Category; /// /// let mut category = Category::default(); /// category.set_scheme("http://example.com/scheme".to_string()); /// assert_eq!(category.scheme(), Some("http://example.com/scheme")); /// ``` pub fn scheme(&self) -> Option<&str> { self.scheme.as_deref() } /// Set the categorization scheme URI. /// /// # Examples /// /// ``` /// use atom_syndication::Category; /// /// let mut category = Category::default(); /// category.set_scheme("http://example.com/scheme".to_string()); /// ``` pub fn set_scheme(&mut self, scheme: V) where V: Into>, { self.scheme = scheme.into(); } /// Return the label for this category. /// /// # Examples /// /// ``` /// use atom_syndication::Category; /// /// let mut category = Category::default(); /// category.set_scheme("Technology".to_string()); /// assert_eq!(category.scheme(), Some("Technology")); /// ``` pub fn label(&self) -> Option<&str> { self.label.as_deref() } /// Set the label for this category. /// /// # Examples /// /// ``` /// use atom_syndication::Category; /// /// let mut category = Category::default(); /// category.set_scheme("Technology".to_string()); /// ``` pub fn set_label(&mut self, label: V) where V: Into>, { self.label = label.into(); } } impl Category { pub(crate) fn from_xml<'s, B: BufRead>( reader: &mut Reader, element: &'s BytesStart<'s>, ) -> Result { let mut category = Category::default(); for att in element.attributes().with_checks(false).flatten() { match decode(att.key.as_ref(), reader)? { Cow::Borrowed("term") => { category.term = attr_value(&att, reader)?.to_string(); } Cow::Borrowed("scheme") => { category.scheme = Some(attr_value(&att, reader)?.to_string()); } Cow::Borrowed("label") => { category.label = Some(attr_value(&att, reader)?.to_string()); } _ => {} } } Ok(category) } } impl ToXml for Category { fn to_xml(&self, writer: &mut Writer) -> Result<(), XmlError> { let mut element = BytesStart::new("category"); element.push_attribute(("term", &*self.term)); if let Some(ref scheme) = self.scheme { element.push_attribute(("scheme", &**scheme)); } if let Some(ref label) = self.label { element.push_attribute(("label", &**label)); } writer .write_event(Event::Empty(element)) .map_err(XmlError::new)?; Ok(()) } } #[cfg(feature = "builders")] impl CategoryBuilder { /// Builds a new `Category`. pub fn build(&self) -> Category { self.build_impl().unwrap() } } atom_syndication-0.12.6/src/content.rs000064400000000000000000000253741046102023000161000ustar 00000000000000use std::borrow::Cow; use std::io::{BufRead, Write}; use quick_xml::events::attributes::Attributes; use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; use quick_xml::Reader; use quick_xml::Writer; use crate::error::{Error, XmlError}; use crate::fromxml::FromXml; use crate::toxml::ToXml; use crate::util::{atom_text, atom_xhtml, attr_value, decode}; /// Represents the content of an Atom entry // /// ## Attention /// /// Atom format specification [RFC4287](https://datatracker.ietf.org/doc/html/rfc4287#section-4.1.3.2) /// states that `src` and `value` (content) fields are mutually exclusive: /// /// > atom:content MAY have a "src" attribute, whose value MUST be an IRI reference. /// > If the "src" attribute is present, atom:content MUST be empty. /// /// Setting of both fields when authoring an Atom feed is still technically possible, /// but it will lead to a non-compliant result. #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[derive(Debug, Default, Clone, PartialEq)] #[cfg_attr(feature = "builders", derive(Builder))] #[cfg_attr( feature = "builders", builder( setter(into), default, build_fn(name = "build_impl", private, error = "never::Never") ) )] pub struct Content { /// Base URL for resolving any relative references found in the element. pub base: Option, /// Indicates the natural language for the element. pub lang: Option, /// The text value of the content. pub value: Option, /// The URI of where the content can be found. pub src: Option, /// Either "text", "html", "xhtml", or the MIME type of the content. pub content_type: Option, } impl Content { /// Return base URL of the content. pub fn base(&self) -> Option<&str> { self.base.as_deref() } /// Set base URL of the content. pub fn set_base(&mut self, base: V) where V: Into>, { self.base = base.into(); } /// Return natural language of the content. pub fn lang(&self) -> Option<&str> { self.lang.as_deref() } /// Set the base URL of the content. pub fn set_lang(&mut self, lang: V) where V: Into>, { self.lang = lang.into(); } /// Return the text value of the content. /// /// If the `content_type` is neither `"text"`, `"html"`, or `"xhtml"` then the value should /// be a base64 encoded document of the indicated MIME type. /// /// # Examples /// /// ``` /// use atom_syndication::Content; /// /// let mut content = Content::default(); /// content.set_value("Example content".to_string()); /// assert_eq!(content.value(), Some("Example content")); /// ``` pub fn value(&self) -> Option<&str> { self.value.as_deref() } /// Set the text value of the content. /// /// # Examples /// /// ``` /// use atom_syndication::Content; /// /// let mut content = Content::default(); /// content.set_value("Example content".to_string()); /// ``` pub fn set_value(&mut self, value: V) where V: Into>, { self.value = value.into(); } /// Return the URI where the content can be found. /// /// # Examples /// /// ``` /// use atom_syndication::Content; /// /// let mut content = Content::default(); /// content.set_src("http://example.com/content.html".to_string()); /// assert_eq!(content.src(), Some("http://example.com/content.html")); /// ``` pub fn src(&self) -> Option<&str> { self.src.as_deref() } /// Set the URI where the content can be found. /// /// # Examples /// /// ``` /// use atom_syndication::Content; /// /// let mut content = Content::default(); /// content.set_src("http://example.com/content.html".to_string()); /// ``` pub fn set_src(&mut self, src: V) where V: Into>, { self.src = src.into(); } /// Return the type of the content. /// /// The type is either `"text"`, `"html"`, `"xhtml"`, or the MIME type of the content. /// /// # Examples /// /// ``` /// use atom_syndication::Content; /// /// let mut content = Content::default(); /// content.set_content_type("image/png".to_string()); /// assert_eq!(content.content_type(), Some("image/png")); /// ``` pub fn content_type(&self) -> Option<&str> { self.content_type.as_deref() } /// Set the type of the content. /// /// # Examples /// /// ``` /// use atom_syndication::Content; /// /// let mut content = Content::default(); /// content.set_content_type("image/png".to_string()); /// assert_eq!(content.content_type(), Some("image/png")); /// ``` pub fn set_content_type(&mut self, content_type: V) where V: Into>, { self.content_type = content_type.into(); } } impl FromXml for Content { fn from_xml( reader: &mut Reader, mut atts: Attributes<'_>, ) -> Result { let mut content = Content::default(); for att in atts.with_checks(false).flatten() { match decode(att.key.as_ref(), reader)? { Cow::Borrowed("xml:base") => { content.base = Some(attr_value(&att, reader)?.to_string()); } Cow::Borrowed("xml:lang") => { content.lang = Some(attr_value(&att, reader)?.to_string()); } Cow::Borrowed("type") => { content.content_type = Some(attr_value(&att, reader)?.to_string()); } Cow::Borrowed("src") => { content.src = Some(attr_value(&att, reader)?.to_string()); } _ => {} } } content.value = match content.content_type { Some(ref t) if t == "xhtml" => atom_xhtml(reader)?, _ => atom_text(reader)?, }; Ok(content) } } impl ToXml for Content { fn to_xml(&self, writer: &mut Writer) -> Result<(), XmlError> { let name = "content"; let mut element = BytesStart::new(name); if let Some(ref base) = self.base { element.push_attribute(("xml:base", base.as_str())); } if let Some(ref lang) = self.lang { element.push_attribute(("xml:lang", lang.as_str())); } if let Some(ref content_type) = self.content_type { if content_type == "xhtml" { element.push_attribute(("type", "xhtml")); } else { element.push_attribute(("type", &**content_type)); } } if let Some(ref src) = self.src { element.push_attribute(("src", &**src)); } writer .write_event(Event::Start(element)) .map_err(XmlError::new)?; if let Some(ref value) = self.value { writer .write_event(Event::Text( if self.content_type.as_deref() == Some("xhtml") { BytesText::from_escaped(value) } else { BytesText::new(value) }, )) .map_err(XmlError::new)?; } writer .write_event(Event::End(BytesEnd::new(name))) .map_err(XmlError::new)?; Ok(()) } } #[cfg(feature = "builders")] impl ContentBuilder { /// Builds a new `Content`. pub fn build(&self) -> Content { self.build_impl().unwrap() } } #[cfg(test)] mod test { use super::*; use crate::error::Error; use crate::util::decode; fn lines(text: &str) -> Vec<&str> { text.lines() .map(|line| line.trim()) .filter(|line| !line.is_empty()) .collect::>() } fn to_xml(content: &Content) -> String { let mut buffer = Vec::new(); let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); content.to_xml(&mut writer).unwrap(); String::from_utf8(buffer).unwrap() } fn from_xml(xml: &str) -> Result { let mut reader = Reader::from_reader(xml.as_bytes()); reader.config_mut().expand_empty_elements = true; loop { let mut buf = Vec::new(); match reader.read_event_into(&mut buf).map_err(XmlError::new)? { Event::Start(element) => { if decode(element.name().as_ref(), &reader)? == "content" { let content = Content::from_xml(&mut reader, element.attributes())?; return Ok(content); } else { return Err(Error::InvalidStartTag); } } Event::Eof => return Err(Error::Eof), _ => {} } } } #[test] fn test_plain_text() { let content = Content { value: Some("Text with ampersand & .".into()), ..Default::default() }; let xml_fragment = r#"Text with ampersand & <tag>."#; assert_eq!(to_xml(&content), xml_fragment); assert_eq!(from_xml(xml_fragment).unwrap(), content); } #[test] fn test_html() { let content = Content { content_type: Some("html".into()), value: Some("Markup with ampersand, , & .".into()), ..Default::default() }; let xml_fragment = r#"Markup with ampersand, <tag>, & </closing-tag>."#; assert_eq!(to_xml(&content), xml_fragment); assert_eq!(from_xml(xml_fragment).unwrap(), content); } #[test] fn test_xhtml() { let content = Content { content_type: Some("xhtml".into()), value: Some(r#"
a line
& one more
"#.into()), ..Default::default() }; let xml_fragment = r#"
a line
& one more
"#; assert_eq!(to_xml(&content), xml_fragment); assert_eq!(from_xml(xml_fragment).unwrap(), content); } #[test] fn test_write_image() { let content = Content { content_type: Some("image/png".into()), src: Some("http://example.com/image.png".into()), ..Default::default() }; assert_eq!( lines(&to_xml(&content)), lines( r#" "# ) ); } } atom_syndication-0.12.6/src/entry.rs000064400000000000000000000456361046102023000155720ustar 00000000000000use std::borrow::Cow; use std::io::{BufRead, Write}; use quick_xml::events::attributes::Attributes; use quick_xml::events::{BytesEnd, BytesStart, Event}; use quick_xml::Reader; use quick_xml::Writer; use crate::category::Category; use crate::content::Content; use crate::error::{Error, XmlError}; use crate::extension::util::{extension_name, parse_extension}; use crate::extension::ExtensionMap; use crate::fromxml::FromXml; use crate::link::Link; use crate::person::Person; use crate::source::Source; use crate::text::Text; use crate::toxml::{ToXml, WriterExt}; use crate::util::{atom_datetime, atom_text, decode, default_fixed_datetime, skip, FixedDateTime}; /// Represents an entry in an Atom feed #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "builders", derive(Builder))] #[cfg_attr( feature = "builders", builder( setter(into), default, build_fn(name = "build_impl", private, error = "never::Never") ) )] pub struct Entry { /// A human-readable title for the entry. pub title: Text, /// A universally unique and permanent URI. pub id: String, /// The last time the entry was modified. pub updated: FixedDateTime, /// The authors of the feed. #[cfg_attr(feature = "builders", builder(setter(each = "author")))] pub authors: Vec, /// The categories that the entry belongs to. #[cfg_attr(feature = "builders", builder(setter(each = "category")))] pub categories: Vec, /// The contributors to the entry. #[cfg_attr(feature = "builders", builder(setter(each = "contributor")))] pub contributors: Vec, /// The Web pages related to the entry. #[cfg_attr(feature = "builders", builder(setter(each = "link")))] pub links: Vec, /// The time of the initial creation or first availability of the entry. pub published: Option, /// Information about rights held in and over the entry. pub rights: Option, /// The source information if an entry is copied from one feed into another feed. pub source: Option, /// A short summary, abstract, or excerpt of the entry. pub summary: Option, /// Contains or links to the complete content of the entry. pub content: Option, /// The extensions for this entry. #[cfg_attr(feature = "builders", builder(setter(each = "extension")))] pub extensions: ExtensionMap, } impl Entry { /// Return the title of this entry. /// /// # Examples /// /// ``` /// use atom_syndication::Entry; /// /// let mut entry = Entry::default(); /// entry.set_title("Entry Title"); /// assert_eq!(entry.title(), "Entry Title"); /// ``` pub fn title(&self) -> &Text { &self.title } /// Set the title of this entry. /// /// # Examples /// /// ``` /// use atom_syndication::Entry; /// /// let mut entry = Entry::default(); /// entry.set_title("Entry Title"); /// ``` pub fn set_title(&mut self, title: V) where V: Into, { self.title = title.into(); } /// Return the unique URI of this entry. /// /// # Examples /// /// ``` /// use atom_syndication::Entry; /// /// let mut entry = Entry::default(); /// entry.set_id("urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6"); /// assert_eq!(entry.id(), "urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6"); /// ``` pub fn id(&self) -> &str { self.id.as_str() } /// Set the unique URI of this entry. /// /// # Examples /// /// ``` /// use atom_syndication::Entry; /// /// let mut entry = Entry::default(); /// entry.set_id("urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6"); /// ``` pub fn set_id(&mut self, id: V) where V: Into, { self.id = id.into(); } /// Return the last time that this entry was modified. /// /// # Examples /// /// ``` /// use atom_syndication::Entry; /// use atom_syndication::FixedDateTime; /// use std::str::FromStr; /// /// let mut entry = Entry::default(); /// entry.set_updated(FixedDateTime::from_str("2017-06-03T15:15:44-05:00").unwrap()); /// assert_eq!(entry.updated().to_rfc3339(), "2017-06-03T15:15:44-05:00"); /// ``` pub fn updated(&self) -> &FixedDateTime { &self.updated } /// Set the last time that this entry was modified. /// /// # Examples /// /// ``` /// use atom_syndication::Entry; /// use atom_syndication::FixedDateTime; /// use std::str::FromStr; /// /// let mut entry = Entry::default(); /// entry.set_updated(FixedDateTime::from_str("2017-06-03T15:15:44-05:00").unwrap()); /// ``` pub fn set_updated(&mut self, updated: V) where V: Into, { self.updated = updated.into(); } /// Return the authors of this entry. /// /// # Examples /// /// ``` /// use atom_syndication::{Entry, Person}; /// /// let mut entry = Entry::default(); /// entry.set_authors(vec![Person::default()]); /// assert_eq!(entry.authors().len(), 1); /// ``` pub fn authors(&self) -> &[Person] { self.authors.as_slice() } /// Set the authors of this entry. /// /// # Examples /// /// ``` /// use atom_syndication::{Entry, Person}; /// /// let mut entry = Entry::default(); /// entry.set_authors(vec![Person::default()]); /// ``` pub fn set_authors(&mut self, authors: V) where V: Into>, { self.authors = authors.into(); } /// Return the categories this entry belongs to. /// /// # Examples /// /// ``` /// use atom_syndication::{Entry, Category}; /// /// let mut entry = Entry::default(); /// entry.set_categories(vec![Category::default()]); /// assert_eq!(entry.categories().len(), 1); /// ``` pub fn categories(&self) -> &[Category] { self.categories.as_slice() } /// Set the categories this entry belongs to. /// /// # Examples /// /// ``` /// use atom_syndication::{Entry, Category}; /// /// let mut entry = Entry::default(); /// entry.set_categories(vec![Category::default()]); /// ``` pub fn set_categories(&mut self, categories: V) where V: Into>, { self.categories = categories.into(); } /// Return the contributors to this entry. /// /// # Examples /// /// ``` /// use atom_syndication::{Entry, Person}; /// /// let mut entry = Entry::default(); /// entry.set_contributors(vec![Person::default()]); /// assert_eq!(entry.contributors().len(), 1); /// ``` pub fn contributors(&self) -> &[Person] { self.contributors.as_slice() } /// Set the contributors to this entry. /// /// # Examples /// /// ``` /// use atom_syndication::{Entry, Person}; /// /// let mut entry = Entry::default(); /// entry.set_contributors(vec![Person::default()]); /// ``` pub fn set_contributors(&mut self, contributors: V) where V: Into>, { self.contributors = contributors.into(); } /// Return the links for this entry. /// /// # Examples /// /// ``` /// use atom_syndication::{Entry, Link}; /// /// let mut entry = Entry::default(); /// entry.set_links(vec![Link::default()]); /// assert_eq!(entry.links().len(), 1); /// ``` pub fn links(&self) -> &[Link] { self.links.as_slice() } /// Set the links for this entry. /// /// # Examples /// /// ``` /// use atom_syndication::{Entry, Link}; /// /// let mut entry = Entry::default(); /// entry.set_links(vec![Link::default()]); /// ``` pub fn set_links(&mut self, links: V) where V: Into>, { self.links = links.into(); } /// Return the time that this entry was initially created or first made available. /// /// # Examples /// /// ``` /// use atom_syndication::Entry; /// use atom_syndication::FixedDateTime; /// use std::str::FromStr; /// /// let mut entry = Entry::default(); /// entry.set_published(FixedDateTime::from_str("2017-06-01T15:15:44-05:00").unwrap()); /// assert_eq!(entry.published().map(|x|x.to_rfc3339()), Some("2017-06-01T15:15:44-05:00".to_string())); /// ``` pub fn published(&self) -> Option<&FixedDateTime> { self.published.as_ref() } /// Set the time that this entry was initially created or first made available. /// /// # Examples /// /// ``` /// use atom_syndication::Entry; /// use atom_syndication::FixedDateTime; /// use std::str::FromStr; /// /// let mut entry = Entry::default(); /// entry.set_published(FixedDateTime::from_str("2017-06-01T15:15:44-05:00").unwrap()); /// ``` pub fn set_published(&mut self, published: V) where V: Into>, { self.published = published.into(); } /// Return the information about the rights held in and over this entry. /// /// # Examples /// /// ``` /// use atom_syndication::{Entry, Text}; /// /// let mut entry = Entry::default(); /// entry.set_rights(Text::from("© 2017 John Doe")); /// assert_eq!(entry.rights().map(Text::as_str), Some("© 2017 John Doe")); /// ``` pub fn rights(&self) -> Option<&Text> { self.rights.as_ref() } /// Set the information about the rights held in and over this entry. /// /// # Examples /// /// ``` /// use atom_syndication::{Entry, Text}; /// /// let mut entry = Entry::default(); /// entry.set_rights(Text::from("© 2017 John Doe")); /// ``` pub fn set_rights(&mut self, rights: V) where V: Into>, { self.rights = rights.into(); } /// Return the source of this entry if it was copied from another feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Entry, Source}; /// /// let mut entry = Entry::default(); /// entry.set_source(Source::default()); /// assert!(entry.source().is_some()); /// ``` pub fn source(&self) -> Option<&Source> { self.source.as_ref() } /// Set the source of this entry if it was copied from another feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Entry, Source}; /// /// let mut entry = Entry::default(); /// entry.set_source(Source::default()); /// ``` pub fn set_source(&mut self, source: V) where V: Into>, { self.source = source.into() } /// Return the summary of this entry. /// /// # Examples /// /// ``` /// use atom_syndication::{Entry, Text}; /// /// let mut entry = Entry::default(); /// entry.set_summary(Text::from("Entry summary.")); /// assert_eq!(entry.summary().map(Text::as_str), Some("Entry summary.")); /// ``` pub fn summary(&self) -> Option<&Text> { self.summary.as_ref() } /// Set the summary of this entry. /// /// # Examples /// /// ``` /// use atom_syndication::{Entry, Text}; /// /// let mut entry = Entry::default(); /// entry.set_summary(Text::from("Entry summary.")); /// ``` pub fn set_summary(&mut self, summary: V) where V: Into>, { self.summary = summary.into(); } /// Return the content of this entry. /// /// # Examples /// /// ``` /// use atom_syndication::{Entry, Content}; /// /// let mut entry = Entry::default(); /// entry.set_content(Content::default()); /// assert!(entry.content().is_some()); /// ``` pub fn content(&self) -> Option<&Content> { self.content.as_ref() } /// Set the content of this entry. /// /// # Examples /// /// ``` /// use atom_syndication::{Entry, Content}; /// /// let mut entry = Entry::default(); /// entry.set_content(Content::default()); /// assert!(entry.content().is_some()); /// ``` pub fn set_content(&mut self, content: V) where V: Into>, { self.content = content.into(); } /// Return the extensions for this entry. /// /// # Examples /// /// ``` /// use std::collections::BTreeMap; /// use atom_syndication::Entry; /// use atom_syndication::extension::{ExtensionMap, Extension}; /// /// let extension = Extension::default(); /// /// let mut item_map = BTreeMap::>::new(); /// item_map.insert("ext:name".to_string(), vec![extension]); /// /// let mut extension_map = ExtensionMap::default(); /// extension_map.insert("ext".to_string(), item_map); /// /// let mut entry = Entry::default(); /// entry.set_extensions(extension_map); /// assert_eq!(entry.extensions() /// .get("ext") /// .and_then(|m| m.get("ext:name")) /// .map(|v| v.len()), /// Some(1)); /// ``` pub fn extensions(&self) -> &ExtensionMap { &self.extensions } /// Set the extensions for this entry. /// /// # Examples /// /// ``` /// use atom_syndication::Entry; /// use atom_syndication::extension::ExtensionMap; /// /// let mut entry = Entry::default(); /// entry.set_extensions(ExtensionMap::default()); /// ``` pub fn set_extensions(&mut self, extensions: V) where V: Into, { self.extensions = extensions.into() } } impl FromXml for Entry { fn from_xml(reader: &mut Reader, _: Attributes<'_>) -> Result { let mut entry = Entry::default(); let mut buf = Vec::new(); loop { match reader.read_event_into(&mut buf).map_err(XmlError::new)? { Event::Start(element) => match decode(element.name().as_ref(), reader)? { Cow::Borrowed("id") => entry.id = atom_text(reader)?.unwrap_or_default(), Cow::Borrowed("title") => { entry.title = Text::from_xml(reader, element.attributes())? } Cow::Borrowed("updated") => { entry.updated = atom_datetime(reader)?.unwrap_or_else(default_fixed_datetime) } Cow::Borrowed("author") => entry .authors .push(Person::from_xml(reader, element.attributes())?), Cow::Borrowed("category") => { entry.categories.push(Category::from_xml(reader, &element)?); skip(element.name(), reader)?; } Cow::Borrowed("contributor") => entry .contributors .push(Person::from_xml(reader, element.attributes())?), Cow::Borrowed("link") => { entry.links.push(Link::from_xml(reader, &element)?); skip(element.name(), reader)?; } Cow::Borrowed("published") => entry.published = atom_datetime(reader)?, Cow::Borrowed("rights") => { entry.rights = Some(Text::from_xml(reader, element.attributes())?) } Cow::Borrowed("source") => { entry.source = Some(Source::from_xml(reader, element.attributes())?) } Cow::Borrowed("summary") => { entry.summary = Some(Text::from_xml(reader, element.attributes())?) } Cow::Borrowed("content") => { entry.content = Some(Content::from_xml(reader, element.attributes())?) } n => { if let Some((ns, name)) = extension_name(n.as_ref()) { parse_extension( reader, element.attributes(), ns, name, &mut entry.extensions, )?; } else { skip(element.name(), reader)?; } } }, Event::End(_) => break, Event::Eof => return Err(Error::Eof), _ => {} } buf.clear(); } Ok(entry) } } impl ToXml for Entry { fn to_xml(&self, writer: &mut Writer) -> Result<(), XmlError> { let name = "entry"; writer .write_event(Event::Start(BytesStart::new(name))) .map_err(XmlError::new)?; writer.write_object_named(&self.title, "title")?; writer.write_text_element("id", &self.id)?; writer.write_text_element("updated", &self.updated.to_rfc3339())?; writer.write_objects_named(&self.authors, "author")?; writer.write_objects(&self.categories)?; writer.write_objects_named(&self.contributors, "contributor")?; writer.write_objects(&self.links)?; if let Some(ref published) = self.published { writer.write_text_element("published", &published.to_rfc3339())?; } if let Some(ref rights) = self.rights { writer.write_object_named(rights, "rights")?; } if let Some(ref source) = self.source { writer.write_object(source)?; } if let Some(ref summary) = self.summary { writer.write_object_named(summary, "summary")?; } if let Some(ref content) = self.content { writer.write_object(content)?; } for map in self.extensions.values() { for extensions in map.values() { writer.write_objects(extensions)?; } } writer .write_event(Event::End(BytesEnd::new(name))) .map_err(XmlError::new)?; Ok(()) } } impl Default for Entry { fn default() -> Self { Entry { title: Text::default(), id: String::new(), updated: default_fixed_datetime(), authors: Vec::new(), categories: Vec::new(), contributors: Vec::new(), links: Vec::new(), published: None, rights: None, source: None, summary: None, content: None, extensions: ExtensionMap::default(), } } } #[cfg(feature = "builders")] impl EntryBuilder { /// Builds a new `Entry`. pub fn build(&self) -> Entry { self.build_impl().unwrap() } } atom_syndication-0.12.6/src/error.rs000064400000000000000000000047341046102023000155540ustar 00000000000000use std::error::Error as StdError; use std::fmt; #[derive(Debug)] /// An error that occurred while performing an Atom operation. #[non_exhaustive] pub enum Error { /// Unable to parse XML. Xml(XmlError), /// Input did not begin with an opening feed tag. InvalidStartTag, /// Unexpected end of input. Eof, /// The format of the timestamp is wrong. WrongDatetime(String), /// The value of an attribute is wrong. WrongAttribute { /// The name of the attribute. attribute: &'static str, /// Invalid value. value: String, }, } impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { match *self { Error::Xml(ref err) => Some(err), Error::InvalidStartTag => None, Error::Eof => None, Error::WrongDatetime(_) => None, Error::WrongAttribute { .. } => None, } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Error::Xml(ref err) => fmt::Display::fmt(err, f), Error::InvalidStartTag => write!(f, "input did not begin with an opening feed tag"), Error::Eof => write!(f, "unexpected end of input"), Error::WrongDatetime(ref datetime) => write!( f, "timestamps must be formatted by RFC3339, rather than {}", datetime ), Error::WrongAttribute { attribute, ref value, } => write!( f, "Unsupported value of attribute {}: '{}'.", attribute, value ), } } } impl From for Error { fn from(err: XmlError) -> Error { Error::Xml(err) } } #[derive(Debug)] pub struct XmlError(Box); impl XmlError { pub(crate) fn new(err: impl StdError + Send + Sync + 'static) -> Self { Self(Box::new(err)) } } impl StdError for XmlError { fn source(&self) -> Option<&(dyn StdError + 'static)> { self.0.source() } } impl fmt::Display for XmlError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } #[cfg(test)] mod test { use super::*; #[test] fn error_send_and_sync() { fn assert_send_sync() {} assert_send_sync::(); assert_send_sync::(); } } atom_syndication-0.12.6/src/extension/mod.rs000064400000000000000000000136611046102023000172150ustar 00000000000000use std::collections::BTreeMap; use std::io::Write; use std::str; use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; use quick_xml::Writer; use crate::error::XmlError; use crate::toxml::ToXml; pub(crate) mod util; /// A map of extension namespace prefixes to local names to elements. pub type ExtensionMap = BTreeMap>>; /// A namespaced extension. #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[derive(Debug, Default, Clone, PartialEq)] #[cfg_attr(feature = "builders", derive(Builder))] #[cfg_attr( feature = "builders", builder( setter(into), default, build_fn(name = "build_impl", private, error = "never::Never") ) )] pub struct Extension { /// The qualified name of the extension element. pub name: String, /// The content of the extension element. pub value: Option, /// The attributes for the extension element. #[cfg_attr(feature = "builders", builder(setter(each = "attr")))] pub attrs: BTreeMap, /// The children of the extension element. A map of local names to child elements. #[cfg_attr(feature = "builders", builder(setter(each = "child")))] pub children: BTreeMap>, } impl Extension { /// Return the qualified name of this extension. /// /// # Examples /// /// ``` /// use atom_syndication::extension::Extension; /// /// let mut extension = Extension::default(); /// extension.set_name("ext:name"); /// assert_eq!(extension.name(), "ext:name"); /// ``` pub fn name(&self) -> &str { self.name.as_str() } /// Set the qualified name of this extension. /// /// # Examples /// /// ``` /// use atom_syndication::extension::Extension; /// /// let mut extension = Extension::default(); /// extension.set_name("ext:name"); /// ``` pub fn set_name(&mut self, name: V) where V: Into, { self.name = name.into(); } /// Return the text content of this extension. /// /// # Examples /// /// ``` /// use atom_syndication::extension::Extension; /// /// let mut extension = Extension::default(); /// extension.set_value("John Doe".to_string()); /// assert_eq!(extension.value(), Some("John Doe")); /// ``` pub fn value(&self) -> Option<&str> { self.value.as_deref() } /// Set the text content of this extension. /// /// # Examples /// /// ``` /// use atom_syndication::extension::Extension; /// /// let mut extension = Extension::default(); /// extension.set_value("John Doe".to_string()); /// ``` pub fn set_value(&mut self, value: V) where V: Into>, { self.value = value.into(); } /// Return the attributes for the extension element. /// /// # Examples /// /// ``` /// use std::collections::BTreeMap; /// use atom_syndication::extension::Extension; /// /// let mut extension = Extension::default(); /// let mut attrs = BTreeMap::::new(); /// attrs.insert("email".to_string(), "johndoe@example.com".to_string()); /// extension.set_attrs(attrs.clone()); /// assert_eq!(*extension.attrs(), attrs); /// ``` pub fn attrs(&self) -> &BTreeMap { &self.attrs } /// Set the attributes for the extension element. /// /// # Examples /// /// ``` /// use std::collections::BTreeMap; /// use atom_syndication::extension::Extension; /// /// let mut extension = Extension::default(); /// extension.set_attrs(BTreeMap::new()); /// ``` pub fn set_attrs(&mut self, attrs: V) where V: Into>, { self.attrs = attrs.into(); } /// Return the children of the extension element. /// /// A map of local names to child elements. /// /// # Examples /// /// ``` /// use std::collections::BTreeMap; /// use atom_syndication::extension::Extension; /// /// let mut extension = Extension::default(); /// let mut children = BTreeMap::>::new(); /// children.insert("ext:child".to_string(), Vec::new()); /// extension.set_children(children); /// assert!(extension.children().contains_key("ext:child")); /// ``` pub fn children(&self) -> &BTreeMap> { &self.children } /// Set the children of the extension element. /// /// A map of local names to child elements. /// /// # Examples /// /// ``` /// use std::collections::BTreeMap; /// use atom_syndication::extension::Extension; /// /// let mut extension = Extension::default(); /// extension.set_children(BTreeMap::new()); /// ``` pub fn set_children(&mut self, children: V) where V: Into>>, { self.children = children.into(); } } impl ToXml for Extension { fn to_xml(&self, writer: &mut Writer) -> Result<(), XmlError> { let mut element = BytesStart::new(&self.name); element.extend_attributes(self.attrs.iter().map(|a| (a.0.as_bytes(), a.1.as_bytes()))); writer .write_event(Event::Start(element)) .map_err(XmlError::new)?; if let Some(value) = self.value.as_ref() { writer .write_event(Event::Text(BytesText::new(value))) .map_err(XmlError::new)?; } for extension in self.children.values().flatten() { extension.to_xml(writer)?; } writer .write_event(Event::End(BytesEnd::new(&self.name))) .map_err(XmlError::new)?; Ok(()) } } #[cfg(feature = "builders")] impl ExtensionBuilder { /// Builds a new `Extension`. pub fn build(&self) -> Extension { self.build_impl().unwrap() } } atom_syndication-0.12.6/src/extension/util.rs000064400000000000000000000057751046102023000174220ustar 00000000000000use std::collections::BTreeMap; use std::io::BufRead; use quick_xml::events::attributes::Attributes; use quick_xml::events::Event; use quick_xml::Reader; use crate::error::{Error, XmlError}; use crate::extension::{Extension, ExtensionMap}; use crate::util::{attr_value, decode}; pub fn extension_name(element_name: &str) -> Option<(&str, &str)> { let mut split = element_name.splitn(2, ':'); let ns = split.next().filter(|ns| !ns.is_empty())?; let name = split.next()?; Some((ns, name)) } pub fn parse_extension( reader: &mut Reader, atts: Attributes<'_>, ns: &str, name: &str, extensions: &mut ExtensionMap, ) -> Result<(), Error> where R: BufRead, { let ext = parse_extension_element(reader, atts)?; if !extensions.contains_key(ns) { extensions.insert(ns.to_string(), BTreeMap::new()); } let map = match extensions.get_mut(ns) { Some(map) => map, None => unreachable!(), }; if !map.contains_key(name) { map.insert(name.to_string(), Vec::new()); } let items = match map.get_mut(name) { Some(items) => items, None => unreachable!(), }; items.push(ext); Ok(()) } fn parse_extension_element( reader: &mut Reader, mut atts: Attributes<'_>, ) -> Result { let mut extension = Extension::default(); let mut buf = Vec::new(); for attr in atts.with_checks(false).flatten() { let key = decode(attr.key.local_name().as_ref(), reader)?.to_string(); let value = attr_value(&attr, reader)?.to_string(); extension.attrs.insert(key, value); } let mut text = String::new(); loop { match reader.read_event_into(&mut buf).map_err(XmlError::new)? { Event::Start(element) => { let ext = parse_extension_element(reader, element.attributes())?; let element_local_name = element.local_name(); let name = decode(element_local_name.as_ref(), reader)?; if !extension.children.contains_key(&*name) { extension.children.insert(name.to_string(), Vec::new()); } let items = match extension.children.get_mut(&*name) { Some(items) => items, None => unreachable!(), }; items.push(ext); } Event::CData(element) => { text.push_str(decode(&element, reader)?.as_ref()); } Event::Text(element) => { text.push_str(element.unescape().map_err(XmlError::new)?.as_ref()); } Event::End(element) => { extension.name = decode(element.name().as_ref(), reader)?.into(); break; } Event::Eof => return Err(Error::Eof), _ => {} } buf.clear(); } extension.value = Some(text.trim()) .filter(|t| !t.is_empty()) .map(ToString::to_string); Ok(extension) } atom_syndication-0.12.6/src/feed.rs000064400000000000000000000740401046102023000153230ustar 00000000000000use std::borrow::Cow; use std::collections::BTreeMap; use std::io::{BufRead, Write}; use std::str::{self, FromStr}; use quick_xml::events::attributes::Attributes; use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event}; use quick_xml::{Reader, Writer}; use crate::category::Category; use crate::entry::Entry; use crate::error::{Error, XmlError}; use crate::extension::util::{extension_name, parse_extension}; use crate::extension::ExtensionMap; use crate::fromxml::FromXml; use crate::generator::Generator; use crate::link::Link; use crate::person::Person; use crate::text::Text; use crate::toxml::{ToXml, WriterExt}; use crate::util::{ atom_datetime, atom_text, attr_value, decode, default_fixed_datetime, skip, FixedDateTime, }; /// Various options which control XML writer #[derive(Clone, Copy)] pub struct WriteConfig { /// Write XML document declaration at the beginning of a document. Default is `true`. pub write_document_declaration: bool, /// Indent XML tags. Default is `None`. pub indent_size: Option, } impl Default for WriteConfig { fn default() -> Self { Self { write_document_declaration: true, indent_size: None, } } } /// Represents an Atom feed #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "builders", derive(Builder))] #[cfg_attr( feature = "builders", builder( setter(into), default, build_fn(name = "build_impl", private, error = "never::Never") ) )] pub struct Feed { /// A human-readable title for the feed. pub title: Text, /// A universally unique and permanent URI. pub id: String, /// The last time the feed was modified in a significant way. pub updated: FixedDateTime, /// The authors of the feed. #[cfg_attr(feature = "builders", builder(setter(each = "author")))] pub authors: Vec, /// The categories that the feed belongs to. #[cfg_attr(feature = "builders", builder(setter(each = "category")))] pub categories: Vec, /// The contributors to the feed. #[cfg_attr(feature = "builders", builder(setter(each = "contributor")))] pub contributors: Vec, /// The software used to generate the feed. pub generator: Option, /// A small image which provides visual identification for the feed. pub icon: Option, /// The Web pages related to the feed. #[cfg_attr(feature = "builders", builder(setter(each = "link")))] pub links: Vec, /// A larger image which provides visual identification for the feed. pub logo: Option, /// Information about rights held in and over the feed. pub rights: Option, /// A human-readable description or subtitle for the feed. pub subtitle: Option, /// The entries contained in the feed. #[cfg_attr(feature = "builders", builder(setter(each = "entry")))] pub entries: Vec, /// The extensions for the feed. #[cfg_attr(feature = "builders", builder(setter(each = "extension")))] pub extensions: ExtensionMap, /// The namespaces present in the feed tag. #[cfg_attr(feature = "builders", builder(setter(each = "namespace")))] pub namespaces: BTreeMap, /// Base URL for resolving any relative references found in the element. pub base: Option, /// Indicates the natural language for the element. pub lang: Option, } impl Feed { /// Attempt to read an Atom feed from the reader. /// /// # Examples /// /// ```no_run /// use std::io::BufReader; /// use std::fs::File; /// use atom_syndication::Feed; /// /// let file = File::open("example.xml").unwrap(); /// let feed = Feed::read_from(BufReader::new(file)).unwrap(); /// ``` pub fn read_from(reader: B) -> Result { let mut reader = Reader::from_reader(reader); reader.config_mut().expand_empty_elements = true; let mut buf = Vec::new(); loop { match reader.read_event_into(&mut buf).map_err(XmlError::new)? { Event::Start(element) => { if decode(element.name().as_ref(), &reader)? == "feed" { return Feed::from_xml(&mut reader, element.attributes()); } else { return Err(Error::InvalidStartTag); } } Event::Eof => break, _ => {} } buf.clear(); } Err(Error::Eof) } /// Attempt to write this Atom feed to a writer using default `WriteConfig`. /// /// # Examples /// /// ``` /// use std::io::BufReader; /// use std::fs::File; /// use atom_syndication::Feed; /// /// # fn main() -> Result<(), Box> { /// let feed = Feed { /// title: "Feed Title".into(), /// id: "Feed ID".into(), /// ..Default::default() /// }; /// /// let out = feed.write_to(Vec::new())?; /// assert_eq!(&out, br#" /// Feed TitleFeed ID1970-01-01T00:00:00+00:00"#); /// # Ok(()) } /// ``` pub fn write_to(&self, writer: W) -> Result { self.write_with_config(writer, WriteConfig::default()) } /// Attempt to write this Atom feed to a writer. /// /// # Examples /// /// ``` /// use std::io::BufReader; /// use std::fs::File; /// use atom_syndication::{Feed, WriteConfig}; /// /// # fn main() -> Result<(), Box> { /// let feed = Feed { /// title: "Feed Title".into(), /// id: "Feed ID".into(), /// ..Default::default() /// }; /// /// let mut out = Vec::new(); /// let config = WriteConfig { /// write_document_declaration: false, /// indent_size: Some(2), /// }; /// feed.write_with_config(&mut out, config)?; /// assert_eq!(&out, br#" /// Feed Title /// Feed ID /// 1970-01-01T00:00:00+00:00 /// "#); /// # Ok(()) } /// ``` pub fn write_with_config( &self, writer: W, write_config: WriteConfig, ) -> Result { let mut writer = match write_config.indent_size { Some(indent_size) => Writer::new_with_indent(writer, b' ', indent_size), None => Writer::new(writer), }; if write_config.write_document_declaration { writer .write_event(Event::Decl(BytesDecl::new("1.0", None, None))) .map_err(XmlError::new)?; writer .write_event(Event::Text(BytesText::from_escaped("\n"))) .map_err(XmlError::new)?; } self.to_xml(&mut writer)?; Ok(writer.into_inner()) } /// Return the title of this feed. /// /// # Examples /// /// ``` /// use atom_syndication::Feed; /// /// let mut feed = Feed::default(); /// feed.set_title("Feed Title"); /// assert_eq!(feed.title(), "Feed Title"); /// ``` pub fn title(&self) -> &Text { &self.title } /// Set the title of this feed. /// /// # Examples /// /// ``` /// use atom_syndication::Feed; /// /// let mut feed = Feed::default(); /// feed.set_title("Feed Title"); /// ``` pub fn set_title(&mut self, title: V) where V: Into, { self.title = title.into(); } /// Return the unique URI of this feed. /// /// # Examples /// /// ``` /// use atom_syndication::Feed; /// /// let mut feed = Feed::default(); /// feed.set_id("urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6"); /// assert_eq!(feed.id(), "urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6"); /// ``` pub fn id(&self) -> &str { self.id.as_str() } /// Set the unique URI of this feed. /// /// # Examples /// /// ``` /// use atom_syndication::Feed; /// /// let mut feed = Feed::default(); /// feed.set_id("urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6"); /// ``` pub fn set_id(&mut self, id: V) where V: Into, { self.id = id.into(); } /// Return the last time that this feed was modified. /// /// # Examples /// /// ``` /// use atom_syndication::Feed; /// use atom_syndication::FixedDateTime; /// use std::str::FromStr; /// /// let mut feed = Feed::default(); /// feed.set_updated(FixedDateTime::from_str("2017-06-03T15:15:44-05:00").unwrap()); /// assert_eq!(feed.updated().to_rfc3339(), "2017-06-03T15:15:44-05:00"); /// ``` pub fn updated(&self) -> &FixedDateTime { &self.updated } /// Set the last time that this feed was modified. /// /// # Examples /// /// ``` /// use atom_syndication::Feed; /// use atom_syndication::FixedDateTime; /// use std::str::FromStr; /// /// let mut feed = Feed::default(); /// feed.set_updated(FixedDateTime::from_str("2017-06-03T15:15:44-05:00").unwrap()); /// ``` pub fn set_updated(&mut self, updated: V) where V: Into, { self.updated = updated.into(); } /// Return the authors of this feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Feed, Person}; /// /// let mut feed = Feed::default(); /// feed.set_authors(vec![Person::default()]); /// assert_eq!(feed.authors().len(), 1); /// ``` pub fn authors(&self) -> &[Person] { self.authors.as_slice() } /// Set the authors of this feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Feed, Person}; /// /// let mut feed = Feed::default(); /// feed.set_authors(vec![Person::default()]); /// ``` pub fn set_authors(&mut self, authors: V) where V: Into>, { self.authors = authors.into(); } /// Return the categories this feed belongs to. /// /// # Examples /// /// ``` /// use atom_syndication::{Feed, Category}; /// /// let mut feed = Feed::default(); /// feed.set_categories(vec![Category::default()]); /// assert_eq!(feed.categories().len(), 1); /// ``` pub fn categories(&self) -> &[Category] { self.categories.as_slice() } /// Set the categories this feed belongs to. /// /// # Examples /// /// ``` /// use atom_syndication::{Feed, Category}; /// /// let mut feed = Feed::default(); /// feed.set_categories(vec![Category::default()]); /// ``` pub fn set_categories(&mut self, categories: V) where V: Into>, { self.categories = categories.into(); } /// Return the contributors to this feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Feed, Person}; /// /// let mut feed = Feed::default(); /// feed.set_contributors(vec![Person::default()]); /// assert_eq!(feed.contributors().len(), 1); /// ``` pub fn contributors(&self) -> &[Person] { self.contributors.as_slice() } /// Set the contributors to this feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Feed, Person}; /// /// let mut feed = Feed::default(); /// feed.set_contributors(vec![Person::default()]); /// ``` pub fn set_contributors(&mut self, contributors: V) where V: Into>, { self.contributors = contributors.into(); } /// Return the name of the software used to generate this feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Feed, Generator}; /// /// let mut feed = Feed::default(); /// feed.set_generator(Generator::default()); /// assert!(feed.generator().is_some()); /// ``` pub fn generator(&self) -> Option<&Generator> { self.generator.as_ref() } /// Set the name of the software used to generate this feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Feed, Generator}; /// /// let mut feed = Feed::default(); /// feed.set_generator(Generator::default()); /// ``` pub fn set_generator(&mut self, generator: V) where V: Into>, { self.generator = generator.into() } /// Return the icon for this feed. /// /// # Examples /// /// ``` /// use atom_syndication::Feed; /// /// let mut feed = Feed::default(); /// feed.set_icon("http://example.com/icon.png".to_string()); /// assert_eq!(feed.icon(), Some("http://example.com/icon.png")); /// ``` pub fn icon(&self) -> Option<&str> { self.icon.as_deref() } /// Set the icon for this feed. /// /// # Examples /// /// ``` /// use atom_syndication::Feed; /// /// let mut feed = Feed::default(); /// feed.set_icon("http://example.com/icon.png".to_string()); /// ``` pub fn set_icon(&mut self, icon: V) where V: Into>, { self.icon = icon.into() } /// Return the Web pages related to this feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Feed, Link}; /// /// let mut feed = Feed::default(); /// feed.set_links(vec![Link::default()]); /// assert_eq!(feed.links().len(), 1); /// ``` pub fn links(&self) -> &[Link] { self.links.as_slice() } /// Set the Web pages related to this feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Feed, Link}; /// /// let mut feed = Feed::default(); /// feed.set_links(vec![Link::default()]); /// ``` pub fn set_links(&mut self, links: V) where V: Into>, { self.links = links.into(); } /// Return the logo for this feed. /// /// # Examples /// /// ``` /// use atom_syndication::Feed; /// /// let mut feed = Feed::default(); /// feed.set_logo("http://example.com/logo.png".to_string()); /// assert_eq!(feed.logo(), Some("http://example.com/logo.png")); /// ``` pub fn logo(&self) -> Option<&str> { self.logo.as_deref() } /// Set the logo for this feed. /// /// # Examples /// /// ``` /// use atom_syndication::Feed; /// /// let mut feed = Feed::default(); /// feed.set_logo("http://example.com/logo.png".to_string()); /// ``` pub fn set_logo(&mut self, logo: V) where V: Into>, { self.logo = logo.into() } /// Return the information about the rights held in and over this feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Feed, Text}; /// /// let mut feed = Feed::default(); /// feed.set_rights(Text::from("© 2017 John Doe")); /// assert_eq!(feed.rights().map(Text::as_str), Some("© 2017 John Doe")); /// ``` pub fn rights(&self) -> Option<&Text> { self.rights.as_ref() } /// Set the information about the rights held in and over this feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Feed, Text}; /// /// let mut feed = Feed::default(); /// feed.set_rights(Text::from("© 2017 John Doe")); /// ``` pub fn set_rights(&mut self, rights: V) where V: Into>, { self.rights = rights.into() } /// Return the description or subtitle of this feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Feed, Text}; /// /// let mut feed = Feed::default(); /// feed.set_subtitle(Text::from("Feed subtitle")); /// assert_eq!(feed.subtitle().map(Text::as_str), Some("Feed subtitle")); /// ``` pub fn subtitle(&self) -> Option<&Text> { self.subtitle.as_ref() } /// Set the description or subtitle of this feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Feed, Text}; /// /// let mut feed = Feed::default(); /// feed.set_subtitle(Text::from("Feed subtitle")); /// ``` pub fn set_subtitle(&mut self, subtitle: V) where V: Into>, { self.subtitle = subtitle.into() } /// Return the entries in this feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Feed, Entry}; /// /// let mut feed = Feed::default(); /// feed.set_entries(vec![Entry::default()]); /// assert_eq!(feed.entries().len(), 1); /// ``` pub fn entries(&self) -> &[Entry] { self.entries.as_slice() } /// Set the entries in this feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Feed, Entry}; /// /// let mut feed = Feed::default(); /// feed.set_entries(vec![Entry::default()]); /// ``` pub fn set_entries(&mut self, entries: V) where V: Into>, { self.entries = entries.into(); } /// Return the extensions for this feed. /// /// # Examples /// /// ``` /// use std::collections::BTreeMap; /// use atom_syndication::Feed; /// use atom_syndication::extension::{ExtensionMap, Extension}; /// /// let extension = Extension::default(); /// /// let mut item_map = BTreeMap::>::new(); /// item_map.insert("ext:name".to_string(), vec![extension]); /// /// let mut extension_map = ExtensionMap::default(); /// extension_map.insert("ext".to_string(), item_map); /// /// let mut feed = Feed::default(); /// feed.set_extensions(extension_map); /// assert_eq!(feed.extensions() /// .get("ext") /// .and_then(|m| m.get("ext:name")) /// .map(|v| v.len()), /// Some(1)); /// ``` pub fn extensions(&self) -> &ExtensionMap { &self.extensions } /// Set the extensions for this feed. /// /// # Examples /// /// ``` /// use atom_syndication::Feed; /// use atom_syndication::extension::ExtensionMap; /// /// let mut feed = Feed::default(); /// feed.set_extensions(ExtensionMap::default()); /// ``` pub fn set_extensions(&mut self, extensions: V) where V: Into, { self.extensions = extensions.into() } /// Return the namespaces for this feed. /// /// # Examples /// /// ``` /// use std::collections::BTreeMap; /// use atom_syndication::Feed; /// /// let mut namespaces = BTreeMap::new(); /// namespaces.insert("ext".to_string(), "http://example.com".to_string()); /// /// let mut feed = Feed::default(); /// feed.set_namespaces(namespaces); /// assert_eq!(feed.namespaces().get("ext").map(|s| s.as_str()), Some("http://example.com")); /// ``` pub fn namespaces(&self) -> &BTreeMap { &self.namespaces } /// Set the namespaces for this feed. /// /// # Examples /// /// ``` /// use std::collections::BTreeMap; /// use atom_syndication::Feed; /// /// let mut feed = Feed::default(); /// feed.set_namespaces(BTreeMap::new()); /// ``` pub fn set_namespaces(&mut self, namespaces: V) where V: Into>, { self.namespaces = namespaces.into() } /// Return base URL of the feed. pub fn base(&self) -> Option<&str> { self.base.as_deref() } /// Set base URL of the feed. pub fn set_base(&mut self, base: V) where V: Into>, { self.base = base.into(); } /// Return natural language of the feed. pub fn lang(&self) -> Option<&str> { self.lang.as_deref() } /// Set the base URL of the feed. pub fn set_lang(&mut self, lang: V) where V: Into>, { self.lang = lang.into(); } } impl FromXml for Feed { fn from_xml( reader: &mut Reader, mut atts: Attributes<'_>, ) -> Result { let mut feed = Feed::default(); let mut buf = Vec::new(); for att in atts.with_checks(false).flatten() { match decode(att.key.as_ref(), reader)? { Cow::Borrowed("xml:base") => { feed.base = Some(attr_value(&att, reader)?.to_string()) } Cow::Borrowed("xml:lang") => { feed.lang = Some(attr_value(&att, reader)?.to_string()) } Cow::Borrowed("xmlns:dc") => {} key => { if let Some(ns) = key.strip_prefix("xmlns:") { feed.namespaces .insert(ns.to_string(), attr_value(&att, reader)?.to_string()); } } } } loop { match reader.read_event_into(&mut buf).map_err(XmlError::new)? { Event::Start(element) => match decode(element.name().as_ref(), reader)? { Cow::Borrowed("title") => { feed.title = Text::from_xml(reader, element.attributes())? } Cow::Borrowed("id") => feed.id = atom_text(reader)?.unwrap_or_default(), Cow::Borrowed("updated") => { feed.updated = atom_datetime(reader)?.unwrap_or_else(default_fixed_datetime) } Cow::Borrowed("author") => feed .authors .push(Person::from_xml(reader, element.attributes())?), Cow::Borrowed("category") => { feed.categories.push(Category::from_xml(reader, &element)?); skip(element.name(), reader)?; } Cow::Borrowed("contributor") => feed .contributors .push(Person::from_xml(reader, element.attributes())?), Cow::Borrowed("generator") => { feed.generator = Some(Generator::from_xml(reader, element.attributes())?) } Cow::Borrowed("icon") => feed.icon = atom_text(reader)?, Cow::Borrowed("link") => { feed.links.push(Link::from_xml(reader, &element)?); skip(element.name(), reader)?; } Cow::Borrowed("logo") => feed.logo = atom_text(reader)?, Cow::Borrowed("rights") => { feed.rights = Some(Text::from_xml(reader, element.attributes())?) } Cow::Borrowed("subtitle") => { feed.subtitle = Some(Text::from_xml(reader, element.attributes())?) } Cow::Borrowed("entry") => feed .entries .push(Entry::from_xml(reader, element.attributes())?), n => { if let Some((ns, name)) = extension_name(n.as_ref()) { parse_extension( reader, element.attributes(), ns, name, &mut feed.extensions, )?; } else { skip(element.name(), reader)?; } } }, Event::End(_) => break, Event::Eof => return Err(Error::Eof), _ => {} } buf.clear(); } Ok(feed) } } impl ToXml for Feed { fn to_xml(&self, writer: &mut Writer) -> Result<(), XmlError> { let name = "feed"; let mut element = BytesStart::new(name); element.push_attribute(("xmlns", "http://www.w3.org/2005/Atom")); for (ns, uri) in &self.namespaces { element.push_attribute((format!("xmlns:{}", ns).as_bytes(), uri.as_bytes())); } if let Some(ref base) = self.base { element.push_attribute(("xml:base", base.as_str())); } if let Some(ref lang) = self.lang { element.push_attribute(("xml:lang", lang.as_str())); } writer .write_event(Event::Start(element)) .map_err(XmlError::new)?; writer.write_object_named(&self.title, "title")?; writer.write_text_element("id", &self.id)?; writer.write_text_element("updated", &self.updated.to_rfc3339())?; writer.write_objects_named(&self.authors, "author")?; writer.write_objects(&self.categories)?; writer.write_objects_named(&self.contributors, "contributor")?; if let Some(ref generator) = self.generator { writer.write_object(generator)?; } if let Some(ref icon) = self.icon { writer.write_text_element("icon", icon)?; } writer.write_objects(&self.links)?; if let Some(ref logo) = self.logo { writer.write_text_element("logo", logo)?; } if let Some(ref rights) = self.rights { writer.write_object_named(rights, "rights")?; } if let Some(ref subtitle) = self.subtitle { writer.write_object_named(subtitle, "subtitle")?; } writer.write_objects(&self.entries)?; for map in self.extensions.values() { for extensions in map.values() { writer.write_objects(extensions)?; } } writer .write_event(Event::End(BytesEnd::new(name))) .map_err(XmlError::new)?; Ok(()) } } impl FromStr for Feed { type Err = Error; fn from_str(s: &str) -> Result { Feed::read_from(s.as_bytes()) } } impl ToString for Feed { fn to_string(&self) -> String { let buf = self.write_to(Vec::new()).unwrap_or_default(); // this unwrap should be safe since the bytes written from the Feed are all valid utf8 String::from_utf8(buf).unwrap() } } impl Default for Feed { fn default() -> Self { Feed { title: Text::default(), id: String::new(), updated: default_fixed_datetime(), authors: Vec::new(), categories: Vec::new(), contributors: Vec::new(), generator: None, icon: None, links: Vec::new(), logo: None, rights: None, subtitle: None, entries: Vec::new(), extensions: ExtensionMap::default(), namespaces: BTreeMap::default(), base: None, lang: None, } } } #[cfg(feature = "builders")] impl FeedBuilder { /// Builds a new `Feed`. pub fn build(&self) -> Feed { self.build_impl().unwrap() } } #[cfg(test)] mod test { use super::*; #[test] fn test_default() { let feed = Feed::default(); let xml_fragment = r#" 1970-01-01T00:00:00+00:00"#; assert_eq!(feed.to_string(), xml_fragment); let loaded_feed = Feed::read_from(xml_fragment.as_bytes()).unwrap(); assert_eq!(loaded_feed, feed); assert_eq!(loaded_feed.base(), None); assert_eq!(loaded_feed.lang(), None); } #[test] fn test_base_and_lang() { let mut feed = Feed::default(); feed.set_base(Some("http://example.com/blog/".into())); feed.set_lang(Some("fr_FR".into())); let xml_fragment = r#" 1970-01-01T00:00:00+00:00"#; assert_eq!(feed.to_string(), xml_fragment); let loaded_feed = Feed::read_from(xml_fragment.as_bytes()).unwrap(); assert_eq!(loaded_feed, feed); assert_eq!(loaded_feed.base(), Some("http://example.com/blog/")); assert_eq!(loaded_feed.lang(), Some("fr_FR")); } #[test] fn test_write_no_decl() { let feed = Feed::default(); let xml = feed .write_with_config( Vec::new(), WriteConfig { write_document_declaration: false, indent_size: None, }, ) .unwrap(); assert_eq!( String::from_utf8_lossy(&xml), r#"1970-01-01T00:00:00+00:00"# ); } #[test] fn test_write_indented() { let feed = Feed::default(); let xml = feed .write_with_config( Vec::new(), WriteConfig { write_document_declaration: true, indent_size: Some(4), }, ) .unwrap(); assert_eq!( String::from_utf8_lossy(&xml), r#" 1970-01-01T00:00:00+00:00 "# ); } #[test] fn test_write_no_decl_indented() { let feed = Feed::default(); let xml = feed .write_with_config( Vec::new(), WriteConfig { write_document_declaration: false, indent_size: Some(4), }, ) .unwrap(); assert_eq!( String::from_utf8_lossy(&xml), r#" 1970-01-01T00:00:00+00:00 "# ); } } atom_syndication-0.12.6/src/fromxml.rs000064400000000000000000000003761046102023000161050ustar 00000000000000use std::io::BufRead; use quick_xml::events::attributes::Attributes; use quick_xml::Reader; use crate::error::Error; pub(crate) trait FromXml: Sized { fn from_xml(reader: &mut Reader, atts: Attributes<'_>) -> Result; } atom_syndication-0.12.6/src/generator.rs000064400000000000000000000116671046102023000164140ustar 00000000000000use std::borrow::Cow; use std::io::{BufRead, Write}; use quick_xml::events::attributes::Attributes; use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; use quick_xml::Reader; use quick_xml::Writer; use crate::error::{Error, XmlError}; use crate::fromxml::FromXml; use crate::toxml::ToXml; use crate::util::{atom_text, attr_value, decode}; /// Represents the generator of an Atom feed #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[derive(Debug, Default, Clone, PartialEq)] #[cfg_attr(feature = "builders", derive(Builder))] #[cfg_attr( feature = "builders", builder( setter(into), default, build_fn(name = "build_impl", private, error = "never::Never") ) )] pub struct Generator { /// The name of the generator. pub value: String, /// The generator URI. pub uri: Option, /// The generator version. pub version: Option, } impl Generator { /// Return the name of the generator. /// /// # Examples /// /// ``` /// use atom_syndication::Generator; /// /// let mut generator = Generator::default(); /// generator.set_value("Feed Generator"); /// assert_eq!(generator.value(), "Feed Generator"); /// ``` pub fn value(&self) -> &str { self.value.as_str() } /// Set the name of the generator. /// /// # Examples /// /// ``` /// use atom_syndication::Generator; /// /// let mut generator = Generator::default(); /// generator.set_value("Feed Generator"); /// assert_eq!(generator.value(), "Feed Generator"); /// ``` pub fn set_value(&mut self, value: V) where V: Into, { self.value = value.into() } /// Return the URI for the generator. /// /// # Examples /// /// ``` /// use atom_syndication::Generator; /// /// let mut generator = Generator::default(); /// generator.set_uri("http://example.com/generator".to_string()); /// assert_eq!(generator.uri(), Some("http://example.com/generator")); /// ``` pub fn uri(&self) -> Option<&str> { self.uri.as_deref() } /// Set the URI for the generator. /// /// # Examples /// /// ``` /// use atom_syndication::Generator; /// /// let mut generator = Generator::default(); /// generator.set_uri("http://example.com/generator".to_string()); /// ``` pub fn set_uri(&mut self, uri: V) where V: Into>, { self.uri = uri.into() } /// Return the version of the generator. /// /// # Examples /// /// ``` /// use atom_syndication::Generator; /// /// let mut generator = Generator::default(); /// generator.set_version("1.0".to_string()); /// assert_eq!(generator.version(), Some("1.0")); /// ``` pub fn version(&self) -> Option<&str> { self.version.as_deref() } /// Set the version of the generator. /// /// # Examples /// /// ``` /// use atom_syndication::Generator; /// /// let mut generator = Generator::default(); /// generator.set_version("1.0".to_string()); /// ``` pub fn set_version(&mut self, version: V) where V: Into>, { self.version = version.into() } } impl FromXml for Generator { fn from_xml( reader: &mut Reader, mut atts: Attributes<'_>, ) -> Result { let mut generator = Generator::default(); for att in atts.with_checks(false).flatten() { match decode(att.key.as_ref(), reader)? { Cow::Borrowed("uri") => { generator.uri = Some(attr_value(&att, reader)?.to_string()); } Cow::Borrowed("version") => { generator.version = Some(attr_value(&att, reader)?.to_string()); } _ => {} } } generator.value = atom_text(reader)?.unwrap_or_default(); Ok(generator) } } impl ToXml for Generator { fn to_xml(&self, writer: &mut Writer) -> Result<(), XmlError> { let name = "generator"; let mut element = BytesStart::new(name); if let Some(ref uri) = self.uri { element.push_attribute(("uri", &**uri)); } if let Some(ref version) = self.version { element.push_attribute(("version", &**version)); } writer .write_event(Event::Start(element)) .map_err(XmlError::new)?; writer .write_event(Event::Text(BytesText::new(&self.value))) .map_err(XmlError::new)?; writer .write_event(Event::End(BytesEnd::new(name))) .map_err(XmlError::new)?; Ok(()) } } #[cfg(feature = "builders")] impl GeneratorBuilder { /// Builds a new `Generator`. pub fn build(&self) -> Generator { self.build_impl().unwrap() } } atom_syndication-0.12.6/src/lib.rs000064400000000000000000000047371046102023000151740ustar 00000000000000#![warn(missing_docs, rust_2018_idioms)] #![doc(html_root_url = "https://docs.rs/atom_syndication/")] //! Library for serializing the Atom web content syndication format. //! //! # Reading //! //! A feed can be read from any object that implements the `BufRead` trait or using //! the `FromStr` trait. //! //! ```no_run //! use std::fs::File; //! use std::io::BufReader; //! use atom_syndication::Feed; //! //! let file = File::open("example.xml").unwrap(); //! let feed = Feed::read_from(BufReader::new(file)).unwrap(); //! //! let string = ""; //! let feed = string.parse::().unwrap(); //! ``` //! //! # Writing //! //! A feed can be written to any object that implements the `Write` trait or converted to an XML //! string using the `ToString` trait. //! //! ## Example //! //! ```no_run //! use std::fs::File; //! use std::io::{BufReader, sink}; //! use atom_syndication::Feed; //! //! let file = File::open("example.xml").unwrap(); //! let feed = Feed::read_from(BufReader::new(file)).unwrap(); //! //! // write to the feed to a writer //! feed.write_to(sink()).unwrap(); //! //! // convert the feed to a string //! let string = feed.to_string(); //! ``` #[cfg(feature = "serde")] #[macro_use] extern crate serde; #[cfg(feature = "builders")] #[macro_use] extern crate derive_builder; mod category; mod content; mod entry; mod feed; mod generator; mod link; mod person; mod source; mod text; mod error; mod fromxml; mod toxml; mod util; /// Types and functions for namespaced extensions. pub mod extension; pub use crate::category::Category; #[cfg(feature = "builders")] pub use crate::category::CategoryBuilder; pub use crate::content::Content; #[cfg(feature = "builders")] pub use crate::content::ContentBuilder; pub use crate::entry::Entry; #[cfg(feature = "builders")] pub use crate::entry::EntryBuilder; pub use crate::error::Error; pub use crate::feed::Feed; #[cfg(feature = "builders")] pub use crate::feed::FeedBuilder; pub use crate::feed::WriteConfig; pub use crate::generator::Generator; #[cfg(feature = "builders")] pub use crate::generator::GeneratorBuilder; pub use crate::link::Link; #[cfg(feature = "builders")] pub use crate::link::LinkBuilder; pub use crate::person::Person; #[cfg(feature = "builders")] pub use crate::person::PersonBuilder; pub use crate::source::Source; #[cfg(feature = "builders")] pub use crate::source::SourceBuilder; #[cfg(feature = "builders")] pub use crate::text::TextBuilder; pub use crate::text::{Text, TextType}; pub use crate::util::FixedDateTime; atom_syndication-0.12.6/src/link.rs000064400000000000000000000175401046102023000153570ustar 00000000000000use std::borrow::Cow; use std::io::{BufRead, Write}; use quick_xml::events::{BytesStart, Event}; use quick_xml::Reader; use quick_xml::Writer; use crate::error::{Error, XmlError}; use crate::toxml::ToXml; use crate::util::{attr_value, decode}; /// Represents a link in an Atom feed #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "builders", derive(Builder))] #[cfg_attr( feature = "builders", builder( setter(into), default, build_fn(name = "build_impl", private, error = "never::Never") ) )] pub struct Link { /// The URI of the referenced resource. pub href: String, /// The link relationship type. pub rel: String, /// The language of the resource. pub hreflang: Option, /// The MIME type of the resource. pub mime_type: Option, /// Human-readable information about the link. pub title: Option, /// The length of the resource, in bytes. pub length: Option, } impl Default for Link { fn default() -> Self { Link { href: Default::default(), rel: "alternate".into(), hreflang: Default::default(), mime_type: Default::default(), title: Default::default(), length: Default::default(), } } } impl Link { /// Return the URI the referenced resource. /// /// # Examples /// /// ``` /// use atom_syndication::Link; /// /// let mut link = Link::default(); /// link.set_href("http://example.com"); /// assert_eq!(link.href(), "http://example.com"); /// ``` pub fn href(&self) -> &str { self.href.as_str() } /// Set the URI of the referenced resource. /// /// # Examples /// /// ``` /// use atom_syndication::Link; /// /// let mut link = Link::default(); /// link.set_href("http://example.com"); /// ``` pub fn set_href(&mut self, href: V) where V: Into, { self.href = href.into() } /// Return the relation type of this link. /// /// # Examples /// /// ``` /// use atom_syndication::Link; /// /// let mut link = Link::default(); /// link.set_rel("alternate"); /// assert_eq!(link.rel(), "alternate"); /// ``` pub fn rel(&self) -> &str { self.rel.as_str() } /// Set the relation type of this link. /// /// # Examples /// /// ``` /// use atom_syndication::Link; /// /// let mut link = Link::default(); /// link.set_rel("alternate"); /// ``` pub fn set_rel(&mut self, rel: V) where V: Into, { self.rel = rel.into() } /// Return the language of the referenced resource. /// /// # Examples /// /// ``` /// use atom_syndication::Link; /// /// let mut link = Link::default(); /// link.set_hreflang("en".to_string()); /// assert_eq!(link.hreflang(), Some("en")); /// ``` pub fn hreflang(&self) -> Option<&str> { self.hreflang.as_deref() } /// Set the language of the referenced resource. /// /// # Examples /// /// ``` /// use atom_syndication::Link; /// /// let mut link = Link::default(); /// link.set_hreflang("en".to_string()); /// ``` pub fn set_hreflang(&mut self, hreflang: V) where V: Into>, { self.hreflang = hreflang.into() } /// Return the MIME type of the referenced resource. /// /// # Examples /// /// ``` /// use atom_syndication::Link; /// /// let mut link = Link::default(); /// link.set_mime_type("text/html".to_string()); /// assert_eq!(link.mime_type(), Some("text/html")); /// ``` pub fn mime_type(&self) -> Option<&str> { self.mime_type.as_deref() } /// Set the MIME type of the referenced resource. /// /// # Examples /// /// ``` /// use atom_syndication::Link; /// /// let mut link = Link::default(); /// link.set_mime_type("text/html".to_string()); /// ``` pub fn set_mime_type(&mut self, mime_type: V) where V: Into>, { self.mime_type = mime_type.into() } /// Return the title of the referenced resource. /// /// # Examples /// /// ``` /// use atom_syndication::Link; /// /// let mut link = Link::default(); /// link.set_title("Article Title".to_string()); /// assert_eq!(link.title(), Some("Article Title")); /// ``` pub fn title(&self) -> Option<&str> { self.title.as_deref() } /// Set the title of the referenced resource. /// /// # Examples /// /// ``` /// use atom_syndication::Link; /// /// let mut link = Link::default(); /// link.set_title("Article Title".to_string()); /// ``` pub fn set_title(&mut self, title: V) where V: Into>, { self.title = title.into() } /// Return the content length of the referenced resource in bytes. /// /// # Examples /// /// ``` /// use atom_syndication::Link; /// /// let mut link = Link::default(); /// link.set_length("1000".to_string()); /// assert_eq!(link.length(), Some("1000")); /// ``` pub fn length(&self) -> Option<&str> { self.length.as_deref() } /// Set the content length of the referenced resource in bytes. /// /// # Examples /// /// ``` /// use atom_syndication::Link; /// /// let mut link = Link::default(); /// link.set_length("1000".to_string()); /// ``` pub fn set_length(&mut self, length: V) where V: Into>, { self.length = length.into() } } impl Link { pub(crate) fn from_xml<'s, B: BufRead>( reader: &mut Reader, element: &'s BytesStart<'s>, ) -> Result { let mut link = Link::default(); for att in element.attributes().with_checks(false).flatten() { match decode(att.key.as_ref(), reader)? { Cow::Borrowed("href") => link.href = attr_value(&att, reader)?.to_string(), Cow::Borrowed("rel") => link.rel = attr_value(&att, reader)?.to_string(), Cow::Borrowed("hreflang") => { link.hreflang = Some(attr_value(&att, reader)?.to_string()) } Cow::Borrowed("type") => { link.mime_type = Some(attr_value(&att, reader)?.to_string()) } Cow::Borrowed("title") => link.title = Some(attr_value(&att, reader)?.to_string()), Cow::Borrowed("length") => { link.length = Some(attr_value(&att, reader)?.to_string()) } _ => {} } } Ok(link) } } impl ToXml for Link { fn to_xml(&self, writer: &mut Writer) -> Result<(), XmlError> { let mut element = BytesStart::new("link"); element.push_attribute(("href", &*self.href)); element.push_attribute(("rel", &*self.rel)); if let Some(ref hreflang) = self.hreflang { element.push_attribute(("hreflang", &**hreflang)); } if let Some(ref mime_type) = self.mime_type { element.push_attribute(("type", &**mime_type)); } if let Some(ref title) = self.title { element.push_attribute(("title", &**title)); } if let Some(ref length) = self.length { element.push_attribute(("length", &**length)); } writer .write_event(Event::Empty(element)) .map_err(XmlError::new)?; Ok(()) } } #[cfg(feature = "builders")] impl LinkBuilder { /// Builds a new `Link`. pub fn build(&self) -> Link { self.build_impl().unwrap() } } atom_syndication-0.12.6/src/person.rs000064400000000000000000000115501046102023000157230ustar 00000000000000use std::borrow::Cow; use std::io::{BufRead, Write}; use quick_xml::events::attributes::Attributes; use quick_xml::events::{BytesEnd, BytesStart, Event}; use quick_xml::Reader; use quick_xml::Writer; use crate::error::{Error, XmlError}; use crate::fromxml::FromXml; use crate::toxml::{ToXmlNamed, WriterExt}; use crate::util::{atom_text, decode, skip}; /// Represents a person in an Atom feed #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[derive(Debug, Default, Clone, PartialEq)] #[cfg_attr(feature = "builders", derive(Builder))] #[cfg_attr( feature = "builders", builder( setter(into), default, build_fn(name = "build_impl", private, error = "never::Never") ) )] pub struct Person { /// A human-readable name for the person. pub name: String, /// An email address for the person. pub email: Option, /// A Web page for the person. pub uri: Option, } impl Person { /// Return the name of this person. /// /// # Examples /// /// ``` /// use atom_syndication::Person; /// /// let mut person = Person::default(); /// person.set_name("John Doe"); /// assert_eq!(person.name(), "John Doe"); /// ``` pub fn name(&self) -> &str { self.name.as_str() } /// Return the name of this person. /// /// # Examples /// /// ``` /// use atom_syndication::Person; /// /// let mut person = Person::default(); /// person.set_name("John Doe"); /// ``` pub fn set_name(&mut self, name: V) where V: Into, { self.name = name.into() } /// Return the email address for this person. /// /// # Examples /// /// ``` /// use atom_syndication::Person; /// /// let mut person = Person::default(); /// person.set_email("johndoe@example.com".to_string()); /// assert_eq!(person.email(), Some("johndoe@example.com")); /// ``` pub fn email(&self) -> Option<&str> { self.email.as_deref() } /// Set the email address for this person. /// /// # Examples /// /// ``` /// use atom_syndication::Person; /// /// let mut person = Person::default(); /// person.set_email("johndoe@example.com".to_string()); /// ``` pub fn set_email(&mut self, email: V) where V: Into>, { self.email = email.into() } /// Return the Web page for this person. /// /// # Examples /// /// ``` /// use atom_syndication::Person; /// /// let mut person = Person::default(); /// person.set_uri("http://example.com".to_string()); /// assert_eq!(person.uri(), Some("http://example.com")); /// ``` pub fn uri(&self) -> Option<&str> { self.uri.as_deref() } /// Set the Web page for this person. /// /// # Examples /// /// ``` /// use atom_syndication::Person; /// /// let mut person = Person::default(); /// person.set_uri("http://example.com".to_string()); /// ``` pub fn set_uri(&mut self, uri: V) where V: Into>, { self.uri = uri.into() } } impl FromXml for Person { fn from_xml(reader: &mut Reader, _: Attributes<'_>) -> Result { let mut person = Person::default(); let mut buf = Vec::new(); loop { match reader.read_event_into(&mut buf).map_err(XmlError::new)? { Event::Start(element) => match decode(element.name().as_ref(), reader)? { Cow::Borrowed("name") => person.name = atom_text(reader)?.unwrap_or_default(), Cow::Borrowed("email") => person.email = atom_text(reader)?, Cow::Borrowed("uri") => person.uri = atom_text(reader)?, _ => skip(element.name(), reader)?, }, Event::End(_) => break, Event::Eof => return Err(Error::Eof), _ => {} } buf.clear(); } Ok(person) } } impl ToXmlNamed for Person { fn to_xml_named(&self, writer: &mut Writer, name: &str) -> Result<(), XmlError> where W: Write, { writer .write_event(Event::Start(BytesStart::new(name))) .map_err(XmlError::new)?; writer.write_text_element("name", &self.name)?; if let Some(ref email) = self.email { writer.write_text_element("email", email)?; } if let Some(ref uri) = self.uri { writer.write_text_element("uri", uri)?; } writer .write_event(Event::End(BytesEnd::new(name))) .map_err(XmlError::new)?; Ok(()) } } #[cfg(feature = "builders")] impl PersonBuilder { /// Builds a new `Person`. pub fn build(&self) -> Person { self.build_impl().unwrap() } } atom_syndication-0.12.6/src/source.rs000064400000000000000000000407111046102023000157160ustar 00000000000000use std::borrow::Cow; use std::io::{BufRead, Write}; use quick_xml::events::attributes::Attributes; use quick_xml::events::{BytesEnd, BytesStart, Event}; use quick_xml::Reader; use quick_xml::Writer; use crate::category::Category; use crate::error::{Error, XmlError}; use crate::fromxml::FromXml; use crate::generator::Generator; use crate::link::Link; use crate::person::Person; use crate::text::Text; use crate::toxml::{ToXml, WriterExt}; use crate::util::{atom_datetime, atom_text, decode, default_fixed_datetime, skip, FixedDateTime}; /// Represents the source of an Atom entry #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "builders", derive(Builder))] #[cfg_attr( feature = "builders", builder( setter(into), default, build_fn(name = "build_impl", private, error = "never::Never") ) )] pub struct Source { /// A human-readable title for the feed. pub title: Text, /// A universally unique and permanent URI. pub id: String, /// The last time the feed was modified in a significant way. pub updated: FixedDateTime, /// The authors of the feed. #[cfg_attr(feature = "builders", builder(setter(each = "author")))] pub authors: Vec, /// The categories that the feed belongs to. #[cfg_attr(feature = "builders", builder(setter(each = "category")))] pub categories: Vec, /// The contributors to the feed. #[cfg_attr(feature = "builders", builder(setter(each = "contributor")))] pub contributors: Vec, /// The software used to generate the feed. pub generator: Option, /// A small image which provides visual identification for the feed. pub icon: Option, /// The Web pages related to the feed. #[cfg_attr(feature = "builders", builder(setter(each = "link")))] pub links: Vec, /// A larger image which provides visual identification for the feed. pub logo: Option, /// Information about rights held in and over the feed. pub rights: Option, /// A human-readable description or subtitle for the feed. pub subtitle: Option, } impl Source { /// Return the title of the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::Source; /// /// let mut source = Source::default(); /// source.set_title("Feed Title"); /// assert_eq!(source.title(), "Feed Title"); /// ``` pub fn title(&self) -> &Text { &self.title } /// Set the title of the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::Source; /// /// let mut source = Source::default(); /// source.set_title("Feed Title"); /// ``` pub fn set_title(&mut self, title: V) where V: Into, { self.title = title.into(); } /// Return the unique URI of the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::Source; /// /// let mut source = Source::default(); /// source.set_id("urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6"); /// assert_eq!(source.id(), "urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6"); /// ``` pub fn id(&self) -> &str { self.id.as_str() } /// Set the unique URI of the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::Source; /// /// let mut source = Source::default(); /// source.set_id("urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6"); /// ``` pub fn set_id(&mut self, id: V) where V: Into, { self.id = id.into(); } /// Return the last time that the source feed was modified. /// /// # Examples /// /// ``` /// use atom_syndication::Source; /// use atom_syndication::FixedDateTime; /// use std::str::FromStr; /// /// let mut source = Source::default(); /// source.set_updated(FixedDateTime::from_str("2017-06-03T15:15:44-05:00").unwrap()); /// assert_eq!(source.updated().to_rfc3339(), "2017-06-03T15:15:44-05:00"); /// ``` pub fn updated(&self) -> &FixedDateTime { &self.updated } /// Set the last time that the source feed was modified. /// /// # Examples /// /// ``` /// use atom_syndication::Source; /// use atom_syndication::FixedDateTime; /// use std::str::FromStr; /// /// let mut source = Source::default(); /// source.set_updated(FixedDateTime::from_str("2017-06-03T15:15:44-05:00").unwrap()); /// ``` pub fn set_updated(&mut self, updated: V) where V: Into, { self.updated = updated.into(); } /// Return the authors of the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Source, Person}; /// /// let mut source = Source::default(); /// source.set_authors(vec![Person::default()]); /// assert_eq!(source.authors().len(), 1); /// ``` pub fn authors(&self) -> &[Person] { self.authors.as_slice() } /// Set the authors of the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Source, Person}; /// /// let mut source = Source::default(); /// source.set_authors(vec![Person::default()]); /// ``` pub fn set_authors(&mut self, authors: V) where V: Into>, { self.authors = authors.into(); } /// Return the categories the source feed belongs to. /// /// # Examples /// /// ``` /// use atom_syndication::{Source, Category}; /// /// let mut source = Source::default(); /// source.set_categories(vec![Category::default()]); /// assert_eq!(source.categories().len(), 1); /// ``` pub fn categories(&self) -> &[Category] { self.categories.as_slice() } /// Set the categories the source feed belongs to. /// /// # Examples /// /// ``` /// use atom_syndication::{Source, Category}; /// /// let mut source = Source::default(); /// source.set_categories(vec![Category::default()]); /// ``` pub fn set_categories(&mut self, categories: V) where V: Into>, { self.categories = categories.into(); } /// Return the contributors to the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Source, Person}; /// /// let mut source = Source::default(); /// source.set_contributors(vec![Person::default()]); /// assert_eq!(source.contributors().len(), 1); /// ``` pub fn contributors(&self) -> &[Person] { self.contributors.as_slice() } /// Set the contributors to the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Source, Person}; /// /// let mut source = Source::default(); /// source.set_contributors(vec![Person::default()]); /// ``` pub fn set_contributors(&mut self, contributors: V) where V: Into>, { self.contributors = contributors.into(); } /// Return the name of the software used to generate the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Source, Generator}; /// /// let mut source = Source::default(); /// source.set_generator(Generator::default()); /// assert!(source.generator().is_some()); /// ``` pub fn generator(&self) -> Option<&Generator> { self.generator.as_ref() } /// Set the name of the software used to generate the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Source, Generator}; /// /// let mut source = Source::default(); /// source.set_generator(Generator::default()); /// ``` pub fn set_generator(&mut self, generator: V) where V: Into>, { self.generator = generator.into() } /// Return the icon for the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::Source; /// /// let mut source = Source::default(); /// source.set_icon("http://example.com/icon.png".to_string()); /// assert_eq!(source.icon(), Some("http://example.com/icon.png")); /// ``` pub fn icon(&self) -> Option<&str> { self.icon.as_deref() } /// Set the icon for the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::Source; /// /// let mut source = Source::default(); /// source.set_icon("http://example.com/icon.png".to_string()); /// ``` pub fn set_icon(&mut self, icon: V) where V: Into>, { self.icon = icon.into() } /// Return the Web pages related to the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Source, Link}; /// /// let mut source = Source::default(); /// source.set_links(vec![Link::default()]); /// assert_eq!(source.links().len(), 1); /// ``` pub fn links(&self) -> &[Link] { self.links.as_slice() } /// Set the Web pages related to the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Source, Link}; /// /// let mut source = Source::default(); /// source.set_links(vec![Link::default()]); /// ``` pub fn set_links(&mut self, links: V) where V: Into>, { self.links = links.into(); } /// Return the logo for the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::Source; /// /// let mut source = Source::default(); /// source.set_logo("http://example.com/logo.png".to_string()); /// assert_eq!(source.logo(), Some("http://example.com/logo.png")); /// ``` pub fn logo(&self) -> Option<&str> { self.logo.as_deref() } /// Set the logo for the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::Source; /// /// let mut source = Source::default(); /// source.set_logo("http://example.com/logo.png".to_string()); /// ``` pub fn set_logo(&mut self, logo: V) where V: Into>, { self.logo = logo.into() } /// Return the information about the rights held in and over the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Source, Text}; /// /// let mut source = Source::default(); /// source.set_rights(Text::from("© 2017 John Doe")); /// assert_eq!(source.rights().map(Text::as_str), Some("© 2017 John Doe")); /// ``` pub fn rights(&self) -> Option<&Text> { self.rights.as_ref() } /// Set the information about the rights held in and over the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Source, Text}; /// /// let mut source = Source::default(); /// source.set_rights(Text::from("© 2017 John Doe")); /// ``` pub fn set_rights(&mut self, rights: V) where V: Into>, { self.rights = rights.into() } /// Return the description or subtitle of the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Source, Text}; /// /// let mut source = Source::default(); /// source.set_subtitle(Text::from("Feed subtitle")); /// assert_eq!(source.subtitle().map(Text::as_str), Some("Feed subtitle")); /// ``` pub fn subtitle(&self) -> Option<&Text> { self.subtitle.as_ref() } /// Set the description or subtitle of the source feed. /// /// # Examples /// /// ``` /// use atom_syndication::{Source, Text}; /// /// let mut source = Source::default(); /// source.set_subtitle(Text::from("Feed subtitle")); /// ``` pub fn set_subtitle(&mut self, subtitle: V) where V: Into>, { self.subtitle = subtitle.into() } } impl FromXml for Source { fn from_xml(reader: &mut Reader, _: Attributes<'_>) -> Result { let mut source = Source::default(); let mut buf = Vec::new(); loop { match reader.read_event_into(&mut buf).map_err(XmlError::new)? { Event::Start(element) => match decode(element.name().as_ref(), reader)? { Cow::Borrowed("id") => source.id = atom_text(reader)?.unwrap_or_default(), Cow::Borrowed("title") => { source.title = Text::from_xml(reader, element.attributes())? } Cow::Borrowed("updated") => { source.updated = atom_datetime(reader)?.unwrap_or_else(default_fixed_datetime) } Cow::Borrowed("author") => source .authors .push(Person::from_xml(reader, element.attributes())?), Cow::Borrowed("category") => { source .categories .push(Category::from_xml(reader, &element)?); skip(element.name(), reader)?; } Cow::Borrowed("contributor") => source .contributors .push(Person::from_xml(reader, element.attributes())?), Cow::Borrowed("generator") => { source.generator = Some(Generator::from_xml(reader, element.attributes())?) } Cow::Borrowed("icon") => source.icon = atom_text(reader)?, Cow::Borrowed("link") => { source.links.push(Link::from_xml(reader, &element)?); skip(element.name(), reader)?; } Cow::Borrowed("logo") => source.logo = atom_text(reader)?, Cow::Borrowed("rights") => { source.rights = Some(Text::from_xml(reader, element.attributes())?) } Cow::Borrowed("subtitle") => { source.subtitle = Some(Text::from_xml(reader, element.attributes())?) } _ => skip(element.name(), reader)?, }, Event::End(_) => break, Event::Eof => return Err(Error::Eof), _ => {} } buf.clear(); } Ok(source) } } impl ToXml for Source { fn to_xml(&self, writer: &mut Writer) -> Result<(), XmlError> { let name = "source"; writer .write_event(Event::Start(BytesStart::new(name))) .map_err(XmlError::new)?; writer.write_object_named(&self.title, "title")?; writer.write_text_element("id", &self.id)?; writer.write_text_element("updated", &self.updated.to_rfc3339())?; writer.write_objects_named(&self.authors, "author")?; writer.write_objects(&self.categories)?; writer.write_objects_named(&self.contributors, "contributor")?; if let Some(ref generator) = self.generator { writer.write_object(generator)?; } if let Some(ref icon) = self.icon { writer.write_text_element("icon", icon)?; } writer.write_objects(&self.links)?; if let Some(ref logo) = self.logo { writer.write_text_element("logo", logo)?; } if let Some(ref rights) = self.rights { writer.write_object_named(rights, "rights")?; } if let Some(ref subtitle) = self.subtitle { writer.write_object_named(subtitle, "subtitle")?; } writer .write_event(Event::End(BytesEnd::new(name))) .map_err(XmlError::new)?; Ok(()) } } impl Default for Source { fn default() -> Self { Source { title: Text::default(), id: String::new(), updated: default_fixed_datetime(), authors: Vec::new(), categories: Vec::new(), contributors: Vec::new(), generator: None, icon: None, links: Vec::new(), logo: None, rights: None, subtitle: None, } } } #[cfg(feature = "builders")] impl SourceBuilder { /// Builds a new `Source`. pub fn build(&self) -> Source { self.build_impl().unwrap() } } atom_syndication-0.12.6/src/text.rs000064400000000000000000000136511046102023000154050ustar 00000000000000use std::borrow::Cow; use std::cmp::PartialEq; use std::convert::{AsRef, From}; use std::io::{BufRead, Write}; use std::ops::Deref; use std::str::FromStr; use quick_xml::events::attributes::Attributes; use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; use quick_xml::Reader; use quick_xml::Writer; use crate::error::{Error, XmlError}; use crate::fromxml::FromXml; use crate::toxml::ToXmlNamed; use crate::util::{atom_text, atom_xhtml, attr_value, decode}; #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] /// Represents the value of the [`type` attribute of a text construct](https://tools.ietf.org/html/rfc4287#section-3.1.1) /// in an Atom feed, e.g. the type of the content stored in the element. pub enum TextType { /// Plain text Text, /// HTML Html, /// XHTML Xhtml, } impl Default for TextType { fn default() -> Self { TextType::Text } } impl TextType { fn as_str(&self) -> &'static str { match self { Self::Text => "text", Self::Html => "html", Self::Xhtml => "xhtml", } } } impl FromStr for TextType { type Err = Error; fn from_str(value: &str) -> Result { match value { "text" => Ok(Self::Text), "html" => Ok(Self::Html), "xhtml" => Ok(Self::Xhtml), _ => Err(Error::WrongAttribute { attribute: "type", value: value.to_owned(), }), } } } #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[derive(Debug, Clone, PartialEq, Default)] #[cfg_attr(feature = "builders", derive(Builder))] #[cfg_attr( feature = "builders", builder( setter(into), default, build_fn(name = "build_impl", private, error = "never::Never") ) )] /// Represents a [text construct](https://tools.ietf.org/html/rfc4287#section-3.1) in an Atom feed. pub struct Text { /// Content of the text construct pub value: String, /// Base URL for resolving any relative references found in the element. pub base: Option, /// Indicates the natural language for the element. pub lang: Option, /// Type of content stored in the element. pub r#type: TextType, } impl Text { /// Creates a plain text construct (type = "text"). pub fn plain(value: impl Into) -> Self { Self { value: value.into(), r#type: TextType::Text, ..Self::default() } } /// Creates an html text construct (type = "html"). pub fn html(value: impl Into) -> Self { Self { value: value.into(), r#type: TextType::Html, ..Self::default() } } /// Creates an html text construct (type = "html"). pub fn xhtml(value: impl Into) -> Self { Self { value: value.into(), r#type: TextType::Xhtml, ..Self::default() } } /// Returns a content as a `str` pub fn as_str(&self) -> &str { &self.value } } impl From for Text { fn from(value: String) -> Self { Self::plain(value) } } impl<'t> From<&'t str> for Text { fn from(value: &'t str) -> Self { Self::plain(value) } } impl AsRef for Text { fn as_ref(&self) -> &str { &self.value } } impl Deref for Text { type Target = str; fn deref(&self) -> &Self::Target { &self.value } } impl PartialEq for Text { fn eq(&self, other: &str) -> bool { self.as_str() == other } } impl PartialEq for str { fn eq(&self, other: &Text) -> bool { self == other.as_str() } } impl FromXml for Text { fn from_xml( reader: &mut Reader, mut atts: Attributes<'_>, ) -> Result { let mut text = Text::default(); for att in atts.with_checks(false).flatten() { match decode(att.key.as_ref(), reader)? { Cow::Borrowed("xml:base") => { text.base = Some(attr_value(&att, reader)?.to_string()) } Cow::Borrowed("xml:lang") => { text.lang = Some(attr_value(&att, reader)?.to_string()) } Cow::Borrowed("type") => text.r#type = attr_value(&att, reader)?.parse()?, _ => {} } } let content = if text.r#type == TextType::Xhtml { atom_xhtml(reader)? } else { atom_text(reader)? }; text.value = content.unwrap_or_default(); Ok(text) } } impl ToXmlNamed for Text { fn to_xml_named(&self, writer: &mut Writer, name: &str) -> Result<(), XmlError> where W: Write, { let mut element = BytesStart::new(name); if let Some(ref base) = self.base { element.push_attribute(("xml:base", base.as_str())); } if let Some(ref lang) = self.lang { element.push_attribute(("xml:lang", lang.as_str())); } if self.r#type != TextType::default() { element.push_attribute(("type", self.r#type.as_str())); } writer .write_event(Event::Start(element)) .map_err(XmlError::new)?; if self.r#type == TextType::Xhtml { writer .write_event(Event::Text(BytesText::from_escaped(&self.value))) .map_err(XmlError::new)?; } else { writer .write_event(Event::Text(BytesText::new(&self.value))) .map_err(XmlError::new)?; } writer .write_event(Event::End(BytesEnd::new(name))) .map_err(XmlError::new)?; Ok(()) } } #[cfg(feature = "builders")] impl TextBuilder { /// Builds a new `Text`. pub fn build(&self) -> Text { self.build_impl().unwrap() } } atom_syndication-0.12.6/src/toxml.rs000064400000000000000000000052021046102023000155550ustar 00000000000000use std::io::Write; use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; use quick_xml::Writer; use crate::error::XmlError; pub(crate) trait ToXml { fn to_xml(&self, writer: &mut Writer) -> Result<(), XmlError>; } impl<'a, T: ToXml> ToXml for &'a T { fn to_xml(&self, writer: &mut Writer) -> Result<(), XmlError> { (*self).to_xml(writer) } } pub(crate) trait ToXmlNamed { fn to_xml_named(&self, writer: &mut Writer, name: &str) -> Result<(), XmlError> where W: Write; } impl<'a, T: ToXmlNamed> ToXmlNamed for &'a T { fn to_xml_named(&self, writer: &mut Writer, name: &str) -> Result<(), XmlError> where W: Write, { (*self).to_xml_named(writer, name) } } pub(crate) trait WriterExt { fn write_text_element(&mut self, name: &str, text: &str) -> Result<(), XmlError>; fn write_object(&mut self, object: T) -> Result<(), XmlError> where T: ToXml; fn write_object_named(&mut self, object: T, name: &str) -> Result<(), XmlError> where T: ToXmlNamed; fn write_objects(&mut self, objects: I) -> Result<(), XmlError> where T: ToXml, I: IntoIterator; fn write_objects_named(&mut self, objects: I, name: &str) -> Result<(), XmlError> where T: ToXmlNamed, I: IntoIterator; } impl WriterExt for Writer { fn write_text_element(&mut self, name: &str, text: &str) -> Result<(), XmlError> { self.write_event(Event::Start(BytesStart::new(name))) .map_err(XmlError::new)?; self.write_event(Event::Text(BytesText::new(text))) .map_err(XmlError::new)?; self.write_event(Event::End(BytesEnd::new(name))) .map_err(XmlError::new)?; Ok(()) } fn write_object(&mut self, object: T) -> Result<(), XmlError> where T: ToXml, { object.to_xml(self) } fn write_object_named(&mut self, object: T, name: &str) -> Result<(), XmlError> where T: ToXmlNamed, { object.to_xml_named(self, name) } fn write_objects(&mut self, objects: I) -> Result<(), XmlError> where T: ToXml, I: IntoIterator, { for object in objects { object.to_xml(self)?; } Ok(()) } fn write_objects_named(&mut self, objects: I, name: &str) -> Result<(), XmlError> where T: ToXmlNamed, I: IntoIterator, { for object in objects { object.to_xml_named(self, name)?; } Ok(()) } } atom_syndication-0.12.6/src/util.rs000064400000000000000000000154541046102023000154010ustar 00000000000000use quick_xml::{ escape::escape, events::{attributes::Attribute, Event}, name::QName, Reader, }; use crate::error::{Error, XmlError}; use std::borrow::Cow; use std::io::BufRead; use std::str::FromStr; /// Alias of `::chrono::DateTime<::chrono::FixedOffset>` pub type FixedDateTime = ::chrono::DateTime<::chrono::FixedOffset>; pub fn default_fixed_datetime() -> FixedDateTime { FixedDateTime::from_str("1970-01-01T00:00:00Z").unwrap() } pub(crate) fn decode<'s, 'r, B: BufRead>( bytes: &'s [u8], reader: &'r Reader, ) -> Result, Error> { let text = reader.decoder().decode(bytes).map_err(XmlError::new)?; Ok(text) } pub(crate) fn attr_value<'s, 'r, B: BufRead>( attr: &'s Attribute<'s>, reader: &'r Reader, ) -> Result, Error> { let value = attr .decode_and_unescape_value(reader.decoder()) .map_err(XmlError::new)?; Ok(value) } pub(crate) fn skip(end: QName<'_>, reader: &mut Reader) -> Result<(), Error> { reader .read_to_end_into(end, &mut Vec::new()) .map_err(XmlError::new)?; Ok(()) } fn non_empty(string: String) -> Option { if !string.is_empty() { Some(string) } else { None } } pub fn atom_text(reader: &mut Reader) -> Result, Error> { reader.config_mut().expand_empty_elements = false; let mut innerbuf = Vec::new(); let mut depth = 0; let mut result = String::new(); loop { match reader .read_event_into(&mut innerbuf) .map_err(XmlError::new)? { Event::Start(start) => { depth += 1; result.push('<'); result.push_str(decode(&start, reader)?.as_ref()); result.push('>'); } Event::End(end) => { if depth <= 0 { break; } depth -= 1; result.push_str("'); } Event::Empty(start) => { result.push('<'); result.push_str(decode(&start, reader)?.as_ref()); result.push_str("/>"); } Event::CData(text) => { result.push_str(decode(&text, reader)?.as_ref()); } Event::Text(text) => { let decoded = text.unescape().map_err(XmlError::new)?; result.push_str(&decoded); } Event::Comment(text) => { let decoded = text.unescape().map_err(XmlError::new)?; result.push_str(""); } Event::Decl(_decl) => {} Event::PI(_text) => {} Event::DocType(_text) => {} Event::Eof => return Err(Error::Eof), } innerbuf.clear(); } reader.config_mut().expand_empty_elements = true; Ok(non_empty(result)) } pub fn atom_xhtml(reader: &mut Reader) -> Result, Error> { reader.config_mut().expand_empty_elements = false; let mut innerbuf = Vec::new(); let mut depth = 0; let mut result = String::new(); loop { match reader .read_event_into(&mut innerbuf) .map_err(XmlError::new)? { Event::Start(start) => { depth += 1; result.push('<'); result.push_str(decode(&start, reader)?.as_ref()); result.push('>'); } Event::End(end) => { if depth <= 0 { break; } depth -= 1; result.push_str("'); } Event::Empty(start) => { result.push('<'); result.push_str(decode(&start, reader)?.as_ref()); result.push_str("/>"); } Event::CData(text) => { result.push_str(escape(decode(&text, reader)?.as_ref()).as_ref()); } Event::Text(text) => { let decoded = text.unescape().map_err(XmlError::new)?; result.push_str(escape(decoded.as_ref()).as_ref()); } Event::Comment(text) => { let decoded = text.unescape().map_err(XmlError::new)?; result.push_str(""); } Event::Decl(_decl) => {} Event::PI(_text) => {} Event::DocType(_text) => {} Event::Eof => return Err(Error::Eof), } innerbuf.clear(); } reader.config_mut().expand_empty_elements = true; Ok(non_empty(result)) } pub fn atom_datetime(reader: &mut Reader) -> Result, Error> { if let Some(datetime_text) = atom_text(reader)? { match diligent_date_parser::parse_date(&datetime_text) { None => Err(Error::WrongDatetime(datetime_text)), Some(datetime) => Ok(Some(datetime)), } } else { Ok(None) } } #[cfg(test)] mod test { use super::*; use crate::error::Error; fn read_x(xml: &str) -> Result, Error> { let mut reader = Reader::from_reader(xml.as_bytes()); reader.config_mut().expand_empty_elements = true; loop { let mut buf = Vec::new(); match reader.read_event_into(&mut buf).map_err(XmlError::new)? { Event::Start(element) => { return match decode(element.name().as_ref(), &reader)? { Cow::Borrowed("text") => atom_text(&mut reader), Cow::Borrowed("raw") => atom_xhtml(&mut reader), _ => Err(Error::InvalidStartTag), } } Event::Eof => return Err(Error::Eof), _ => {} } } } #[test] fn test_read_text() { let xml_fragment = r#" Text with ampersand & <tag> and . "#; assert_eq!( read_x(xml_fragment).unwrap().unwrap().trim(), "Text with ampersand & and ." ); } #[test] fn test_read_xhtml() { let xml_fragment = r#"
a line
& one more
"#; assert_eq!( read_x(xml_fragment).unwrap().unwrap().trim(), r#"
a line
& one more
"# ); } }