From f151306fbe6ee9e76145f1bf625484e738690926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Fri, 27 Mar 2020 13:45:22 +0100 Subject: [PATCH] minidom: forcing a namespace on Element. Stop requiring prefixes. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Below is what I think I did. A few changes: - Change prefixes to be something less important in the API. - Rework the Element struct to force a namespace. In XMPP everything is namespaced. - Remove parent ref on what was previously NamespaceSet and is now Prefixes. More specifically this means `Element::new` has changed to require `Element`'s new new properties as parameters. `Element::builder` and `Element::bare` now require a namespace unconditionally. `Element::prefix` has been removed. This new API is based on the fact that prefixes are non-essential (really just an implementation detail) and shouldn't be visible to the user. It is possible nonetheless to set custom prefixes for compatibility reasons with `ElementBuilder::prefix`. **A prefix is firstly mapped to a namespace, and then attached to an element**, there cannot be a prefix without a namespace. Prefix inheritance is used if possible but for the case with no prefix ("xmlns") to be reused, we only check custom prefixes declared on the tag itself and not ascendants. If it's already used then we generate prefixes (ns0, ns1, ..) checking on what has been declared on all ascendants (plus of course those already set on the current tag). Example API: ```rust let mut elem = ElementBuilder("stream", "http://etherx.jabber.org/streams") .prefix(Some(String::from("stream")), "http://etherx.jabber.org/streams) .prefix(None, "jabber:client") .attr(..) .build(); assert_eq!(elem.ns(), String::from("http://etherx.jabber.org/streams")); ``` See also the few tests added in src/tests. TODO: Fix inconsistencies wrt. "prefix:name" format provided as a name when creating an Element with `Element::new` or `Element::bare`. `Element::builder` already handles this as it should, splitting name and prefix. TODO: Change `Element::name` method to `Element::local_name` to make it more explicit. Signed-off-by: Maxime “pep” Buquet --- minidom-rs/src/element.rs | 293 +++++++++++++++++++------------- minidom-rs/src/error.rs | 8 + minidom-rs/src/lib.rs | 5 +- minidom-rs/src/namespace_set.rs | 217 ----------------------- minidom-rs/src/namespaces.rs | 38 +++++ minidom-rs/src/node.rs | 17 +- minidom-rs/src/parser.rs | 184 ++++++++++++++++++++ minidom-rs/src/prefixes.rs | 96 +++++++++++ minidom-rs/src/tests.rs | 230 +++++++++++++++++++++---- 9 files changed, 709 insertions(+), 379 deletions(-) delete mode 100644 minidom-rs/src/namespace_set.rs create mode 100644 minidom-rs/src/namespaces.rs create mode 100644 minidom-rs/src/parser.rs create mode 100644 minidom-rs/src/prefixes.rs diff --git a/minidom-rs/src/element.rs b/minidom-rs/src/element.rs index a663c737f4dbd21e8760d5b4734fc17f7f0c3a12..19ff0b6dcfa61524adaa7abbe84eb018e69fced6 100644 --- a/minidom-rs/src/element.rs +++ b/minidom-rs/src/element.rs @@ -14,7 +14,8 @@ use crate::convert::IntoAttributeValue; use crate::error::{Error, Result}; -use crate::namespace_set::{NSChoice, NamespaceSet}; +use crate::namespaces::NSChoice; +use crate::prefixes::Prefixes; use crate::node::Node; use std::collections::{btree_map, BTreeMap}; @@ -83,9 +84,9 @@ pub fn escape(raw: &[u8]) -> Cow<[u8]> { #[derive(Clone, Eq, Debug)] /// A struct representing a DOM Element. pub struct Element { - prefix: Option, name: String, - namespaces: Rc, + namespace: String, + prefixes: Rc, attributes: BTreeMap, children: Vec, } @@ -121,17 +122,18 @@ impl PartialEq for Element { } impl Element { - fn new>( + fn new>( name: String, - prefix: Option, - namespaces: NS, + namespace: String, + prefixes: P, attributes: BTreeMap, children: Vec, ) -> Element { + // TODO split name and possible prefix. Element { - prefix, name, - namespaces: Rc::new(namespaces.into()), + namespace, + prefixes: Rc::new(prefixes.into()), attributes, children, } @@ -144,23 +146,31 @@ impl Element { /// ```rust /// use minidom::Element; /// - /// let elem = Element::builder("name") - /// .ns("namespace") + /// let elem = Element::builder("name", "namespace") /// .attr("name", "value") /// .append("inner") /// .build(); /// /// assert_eq!(elem.name(), "name"); - /// assert_eq!(elem.ns(), Some("namespace".to_owned())); + /// assert_eq!(elem.ns(), "namespace".to_owned()); /// assert_eq!(elem.attr("name"), Some("value")); /// assert_eq!(elem.attr("inexistent"), None); /// assert_eq!(elem.text(), "inner"); /// ``` - pub fn builder>(name: S) -> ElementBuilder { + pub fn builder, NS: Into>(name: S, namespace: NS) -> ElementBuilder { let (prefix, name) = split_element_name(name).unwrap(); + let namespace: String = namespace.into(); + let prefixes: BTreeMap> = match prefix { + None => Default::default(), + Some(_) => { + let mut prefixes: BTreeMap> = BTreeMap::new(); + prefixes.insert(namespace.clone(), prefix); + prefixes + }, + }; ElementBuilder { - root: Element::new(name, prefix, None, BTreeMap::new(), Vec::new()), - namespaces: Default::default(), + root: Element::new(name, namespace, None, BTreeMap::new(), Vec::new()), + prefixes, } } @@ -171,47 +181,33 @@ impl Element { /// ```rust /// use minidom::Element; /// - /// let bare = Element::bare("name"); + /// let bare = Element::bare("name", "namespace"); /// /// assert_eq!(bare.name(), "name"); - /// assert_eq!(bare.ns(), None); + /// assert_eq!(bare.ns(), "namespace"); /// assert_eq!(bare.attr("name"), None); /// assert_eq!(bare.text(), ""); /// ``` - pub fn bare>(name: S) -> Element { + pub fn bare, NS: Into>(name: S, namespace: NS) -> Element { + // TODO split name and possible prefix. Element { - prefix: None, name: name.into(), - namespaces: Rc::new(NamespaceSet::default()), + namespace: namespace.into(), + prefixes: Rc::new(Prefixes::default()), attributes: BTreeMap::new(), children: Vec::new(), } } /// Returns a reference to the name of this element. + // TODO: rename local_name pub fn name(&self) -> &str { &self.name } - /// Returns a reference to the prefix of this element. - /// - /// # Examples - /// ```rust - /// use minidom::Element; - /// - /// let elem = Element::builder("prefix:name") - /// .build(); - /// - /// assert_eq!(elem.name(), "name"); - /// assert_eq!(elem.prefix(), Some("prefix")); - /// ``` - pub fn prefix(&self) -> Option<&str> { - self.prefix.as_ref().map(String::as_ref) - } - /// Returns a reference to the namespace of this element, if it has one, else `None`. - pub fn ns(&self) -> Option { - self.namespaces.get(&self.prefix) + pub fn ns(&self) -> String { + self.namespace.clone() } /// Returns a reference to the value of the given attribute, if it exists, else `None`. @@ -229,7 +225,7 @@ impl Element { /// ```rust /// use minidom::Element; /// - /// let elm: Element = "".parse().unwrap(); + /// let elm: Element = "".parse().unwrap(); /// /// let mut iter = elm.attrs(); /// @@ -273,7 +269,7 @@ impl Element { /// ```rust /// use minidom::{Element, NSChoice}; /// - /// let elem = Element::builder("name").ns("namespace").build(); + /// let elem = Element::builder("name", "namespace").build(); /// /// assert_eq!(elem.is("name", "namespace"), true); /// assert_eq!(elem.is("name", "wrong"), false); @@ -284,13 +280,9 @@ impl Element { /// assert_eq!(elem.is("name", NSChoice::OneOf("foo")), false); /// assert_eq!(elem.is("name", NSChoice::AnyOf(&["foo", "namespace"])), true); /// assert_eq!(elem.is("name", NSChoice::Any), true); - /// - /// let elem2 = Element::builder("name").build(); - /// - /// assert_eq!(elem2.is("name", NSChoice::Any), true); /// ``` pub fn is<'a, N: AsRef, NS: Into>>(&self, name: N, namespace: NS) -> bool { - self.name == name.as_ref() && self.has_ns(namespace) + self.name == name.as_ref() && namespace.into().compare(self.namespace.as_ref()) } /// Returns whether the element has the given namespace. @@ -300,7 +292,7 @@ impl Element { /// ```rust /// use minidom::{Element, NSChoice}; /// - /// let elem = Element::builder("name").ns("namespace").build(); + /// let elem = Element::builder("name", "namespace").build(); /// /// assert_eq!(elem.has_ns("namespace"), true); /// assert_eq!(elem.has_ns("wrong"), false); @@ -309,24 +301,21 @@ impl Element { /// assert_eq!(elem.has_ns(NSChoice::OneOf("foo")), false); /// assert_eq!(elem.has_ns(NSChoice::AnyOf(&["foo", "namespace"])), true); /// assert_eq!(elem.has_ns(NSChoice::Any), true); - /// - /// let elem2 = Element::builder("name").build(); - /// - /// assert_eq!(elem2.has_ns(NSChoice::Any), true); /// ``` pub fn has_ns<'a, NS: Into>>(&self, namespace: NS) -> bool { - self.namespaces.has(&self.prefix, namespace) + namespace.into().compare(self.namespace.as_ref()) } /// Parse a document from an `EventReader`. pub fn from_reader(reader: &mut EventReader) -> Result { let mut buf = Vec::new(); + let mut prefixes = BTreeMap::new(); let root: Element = loop { let e = reader.read_event(&mut buf)?; match e { Event::Empty(ref e) | Event::Start(ref e) => { - break build_element(reader, e)?; + break build_element(reader, e, &mut prefixes)?; } Event::Eof => { return Err(Error::EndOfDocument); @@ -344,22 +333,27 @@ impl Element { }; let mut stack = vec![root]; + let mut prefix_stack = vec![prefixes]; loop { match reader.read_event(&mut buf)? { Event::Empty(ref e) => { - let elem = build_element(reader, e)?; + let mut prefixes = prefix_stack.last().unwrap().clone(); + let elem = build_element(reader, e, &mut prefixes)?; // Since there is no Event::End after, directly append it to the current node stack.last_mut().unwrap().append_child(elem); } Event::Start(ref e) => { - let elem = build_element(reader, e)?; + let mut prefixes = prefix_stack.last().unwrap().clone(); + let elem = build_element(reader, e, &mut prefixes)?; stack.push(elem); + prefix_stack.push(prefixes); } Event::End(ref e) => { if stack.len() <= 1 { break; } + prefix_stack.pop().unwrap(); let elem = stack.pop().unwrap(); if let Some(to) = stack.last_mut() { // TODO: check whether this is correct, we are comparing &[u8]s, not &strs @@ -368,13 +362,13 @@ impl Element { let possible_prefix = split_iter.next().unwrap(); // Can't be empty. match split_iter.next() { Some(name) => { - match elem.prefix() { - Some(prefix) => { + match elem.prefixes.get(&elem.namespace) { + Some(Some(prefix)) => { if possible_prefix != prefix.as_bytes() { return Err(Error::InvalidElementClosed); } } - None => { + _ => { return Err(Error::InvalidElementClosed); } } @@ -383,8 +377,9 @@ impl Element { } } None => { - if elem.prefix().is_some() { - return Err(Error::InvalidElementClosed); + match elem.prefixes.get(&elem.namespace) { + Some(Some(_)) => return Err(Error::InvalidElementClosed), + _ => (), } if possible_prefix != elem.name().as_bytes() { return Err(Error::InvalidElementClosed); @@ -430,32 +425,93 @@ impl Element { /// Output the document to quick-xml `Writer` pub fn to_writer(&self, writer: &mut EventWriter) -> Result<()> { - self.write_to_inner(writer) + self.write_to_inner(writer, &mut BTreeMap::new()) } /// Output the document to quick-xml `Writer` pub fn to_writer_decl(&self, writer: &mut EventWriter) -> Result<()> { writer.write_event(Event::Decl(BytesDecl::new(b"1.0", Some(b"utf-8"), None)))?; - self.write_to_inner(writer) + self.write_to_inner(writer, &mut BTreeMap::new()) } /// Like `write_to()` but without the `` prelude - pub fn write_to_inner(&self, writer: &mut EventWriter) -> Result<()> { - let name = match self.prefix { - None => Cow::Borrowed(&self.name), - Some(ref prefix) => Cow::Owned(format!("{}:{}", prefix, self.name)), + pub fn write_to_inner(&self, writer: &mut EventWriter, all_prefixes: &mut BTreeMap, String>) -> Result<()> { + let local_prefixes = self.prefixes.declared_prefixes(); + + // Element namespace + // If the element prefix hasn't been set yet via a custom prefix, add it. + let mut existing_self_prefix: Option> = None; + if let Some(prefix) = local_prefixes.get(&self.namespace) { + existing_self_prefix = Some(prefix.clone()); + } else { + for (prefix, ns) in all_prefixes.iter() { + if ns == &self.namespace { + existing_self_prefix = Some(prefix.clone()); + } + } + } + + let mut all_keys = all_prefixes.keys().cloned(); + let mut local_keys = local_prefixes.values().cloned(); + let self_prefix: (Option, bool) = match existing_self_prefix { + // No prefix exists already for our namespace + None => { + if local_keys.find(|p| p == &None).is_none() { + // Use the None prefix if available + (None, true) + } else { + // Otherwise generate one. Check if it isn't already used, if so increase the + // number until we find a suitable one. + let mut prefix_n = 0u8; + while let Some(_) = all_keys.find(|p| p == &Some(format!("ns{}", prefix_n))) { + prefix_n += 1; + } + (Some(format!("ns{}", prefix_n)), true) + } + }, + // Some prefix has already been declared (or is going to be) for our namespace. We + // don't need to declare a new one. We do however need to remember which one to use in + // the tag name. + Some(prefix) => (prefix, false), }; + let name = match self_prefix { + (Some(ref prefix), _) => Cow::Owned(format!("{}:{}", prefix, self.name)), + _ => Cow::Borrowed(&self.name), + }; let mut start = BytesStart::borrowed(name.as_bytes(), name.len()); - for (prefix, ns) in self.namespaces.declared_ns() { - match *prefix { - None => start.push_attribute(("xmlns", ns.as_ref())), - Some(ref prefix) => { - let key = format!("xmlns:{}", prefix); - start.push_attribute((key.as_bytes(), ns.as_bytes())) - } + + // Write self prefix if necessary + match self_prefix { + (Some(ref p), true) => { + let key = format!("xmlns:{}", p); + start.push_attribute((key.as_bytes(), self.namespace.as_bytes())); + all_prefixes.insert(self_prefix.0, self.namespace.clone()); + } + (None, true) => { + let key = format!("xmlns"); + start.push_attribute((key.as_bytes(), self.namespace.as_bytes())); + all_prefixes.insert(self_prefix.0, self.namespace.clone()); + }, + _ => (), + }; + + // Custom prefixes/namespace sets + for (ns, prefix) in local_prefixes { + match all_prefixes.get(prefix) { + p @ Some(_) if p == prefix.as_ref() => (), + _ => { + let key = match prefix { + None => String::from("xmlns"), + Some(p) => format!("xmlns:{}", p), + }; + + start.push_attribute((key.as_bytes(), ns.as_ref())); + all_prefixes.insert(prefix.clone(), ns.clone()); + }, } } + for (key, value) in &self.attributes { start.push_attribute((key.as_bytes(), escape(value.as_bytes()).as_ref())); } @@ -468,7 +524,7 @@ impl Element { writer.write_event(Event::Start(start))?; for child in &self.children { - child.write_to_inner(writer)?; + child.write_to_inner(writer, all_prefixes)?; } writer.write_event(Event::End(BytesEnd::borrowed(name.as_bytes())))?; @@ -482,7 +538,7 @@ impl Element { /// ```rust /// use minidom::Element; /// - /// let elem: Element = "abc".parse().unwrap(); + /// let elem: Element = "abc".parse().unwrap(); /// /// let mut iter = elem.nodes(); /// @@ -511,7 +567,7 @@ impl Element { /// ```rust /// use minidom::Element; /// - /// let elem: Element = "hellothisisignored".parse().unwrap(); + /// let elem: Element = "hellothisisignored".parse().unwrap(); /// /// let mut iter = elem.children(); /// assert_eq!(iter.next().unwrap().name(), "child1"); @@ -541,7 +597,7 @@ impl Element { /// ```rust /// use minidom::Element; /// - /// let elem: Element = "hello world!".parse().unwrap(); + /// let elem: Element = "hello world!".parse().unwrap(); /// /// let mut iter = elem.texts(); /// assert_eq!(iter.next().unwrap(), "hello"); @@ -570,11 +626,11 @@ impl Element { /// ```rust /// use minidom::Element; /// - /// let mut elem = Element::bare("root"); + /// let mut elem = Element::bare("root", "ns1"); /// /// assert_eq!(elem.children().count(), 0); /// - /// elem.append_child(Element::bare("child")); + /// elem.append_child(Element::bare("child", "ns1")); /// /// { /// let mut iter = elem.children(); @@ -582,13 +638,11 @@ impl Element { /// assert_eq!(iter.next(), None); /// } /// - /// let child = elem.append_child(Element::bare("new")); + /// let child = elem.append_child(Element::bare("new", "ns1")); /// /// assert_eq!(child.name(), "new"); /// ``` pub fn append_child(&mut self, child: Element) -> &mut Element { - child.namespaces.set_parent(Rc::clone(&self.namespaces)); - self.children.push(Node::Element(child)); if let Node::Element(ref mut cld) = *self.children.last_mut().unwrap() { cld @@ -604,7 +658,7 @@ impl Element { /// ```rust /// use minidom::Element; /// - /// let mut elem = Element::bare("node"); + /// let mut elem = Element::bare("node", "ns1"); /// /// assert_eq!(elem.text(), ""); /// @@ -623,7 +677,7 @@ impl Element { /// ```rust /// use minidom::{Element, Node}; /// - /// let mut elem = Element::bare("node"); + /// let mut elem = Element::bare("node", "ns1"); /// /// elem.append_node(Node::Text("hello".to_owned())); /// @@ -640,7 +694,7 @@ impl Element { /// ```rust /// use minidom::Element; /// - /// let elem: Element = "hello, world!".parse().unwrap(); + /// let elem: Element = "hello, world!".parse().unwrap(); /// /// assert_eq!(elem.text(), "hello, world!"); /// ``` @@ -656,7 +710,7 @@ impl Element { /// ```rust /// use minidom::{Element, NSChoice}; /// - /// let elem: Element = r#""#.parse().unwrap(); + /// let elem: Element = r#""#.parse().unwrap(); /// assert!(elem.get_child("a", "ns").unwrap().is("a", "ns")); /// assert!(elem.get_child("a", "other_ns").unwrap().is("a", "other_ns")); /// assert!(elem.get_child("b", "ns").unwrap().is("b", "ns")); @@ -763,8 +817,10 @@ fn split_element_name>(s: S) -> Result<(Option, String)> { } } -fn build_element(reader: &EventReader, event: &BytesStart) -> Result { - let mut namespaces = BTreeMap::new(); +fn build_element(reader: &EventReader, event: &BytesStart, prefixes: &mut BTreeMap>) -> Result { + let (prefix, name) = split_element_name(str::from_utf8(event.name())?)?; + let mut local_prefixes = BTreeMap::new(); + let attributes = event .attributes() .map(|o| { @@ -775,19 +831,33 @@ fn build_element(reader: &EventReader, event: &BytesStart) -> Res }) .filter(|o| match *o { Ok((ref key, ref value)) if key == "xmlns" => { - namespaces.insert(None, value.to_owned()); + local_prefixes.insert(value.clone(), None); + prefixes.insert(value.clone(), None); false } Ok((ref key, ref value)) if key.starts_with("xmlns:") => { - namespaces.insert(Some(key[6..].to_owned()), value.to_owned()); + local_prefixes.insert(value.to_owned(), Some(key[6..].to_owned())); + prefixes.insert(value.to_owned(), Some(key[6..].to_owned())); false } _ => true, }) .collect::>>()?; - let (prefix, name) = split_element_name(str::from_utf8(event.name())?)?; - let element = Element::new(name, prefix, namespaces, attributes, Vec::new()); + let namespace: String = { + let mut tmp: Option = None; + + for (k, v) in local_prefixes.iter().chain(prefixes.iter()) { + if v == &prefix { + tmp = Some(k.clone()); + break; + } + } + + tmp.ok_or(Error::MissingNamespace)? + }; + + let element = Element::new(name, namespace, local_prefixes, attributes, Vec::new()); Ok(element) } @@ -898,14 +968,13 @@ impl<'a> Iterator for AttrsMut<'a> { /// A builder for `Element`s. pub struct ElementBuilder { root: Element, - namespaces: BTreeMap, String>, + prefixes: BTreeMap>, } impl ElementBuilder { - /// Sets the namespace. - pub fn ns>(mut self, namespace: S) -> ElementBuilder { - self.namespaces - .insert(self.root.prefix.clone(), namespace.into()); + /// Sets a custom prefix. + pub fn prefix>(mut self, prefix: Option, namespace: S) -> ElementBuilder { + self.prefixes.insert(namespace.into(), prefix); self } @@ -940,13 +1009,7 @@ impl ElementBuilder { pub fn build(self) -> Element { let mut element = self.root; // Set namespaces - element.namespaces = Rc::new(NamespaceSet::from(self.namespaces)); - // Propagate namespaces - for node in &element.children { - if let Node::Element(ref e) = *node { - e.namespaces.set_parent(Rc::clone(&element.namespaces)); - } - } + element.prefixes = Rc::new(Prefixes::from(self.prefixes)); element } } @@ -961,49 +1024,49 @@ mod tests { let elem = Element::new( "name".to_owned(), - None, - Some("namespace".to_owned()), + "namespace".to_owned(), + (None, "namespace".to_owned()), BTreeMap::from_iter(vec![("name".to_string(), "value".to_string())].into_iter()), Vec::new(), ); assert_eq!(elem.name(), "name"); - assert_eq!(elem.ns(), Some("namespace".to_owned())); + assert_eq!(elem.ns(), "namespace".to_owned()); assert_eq!(elem.attr("name"), Some("value")); assert_eq!(elem.attr("inexistent"), None); } #[test] fn test_from_reader_simple() { - let xml = ""; + let xml = ""; let mut reader = EventReader::from_str(xml); let elem = Element::from_reader(&mut reader); - let elem2 = Element::builder("foo").build(); + let elem2 = Element::builder("foo", "ns1").build(); assert_eq!(elem.unwrap(), elem2); } #[test] fn test_from_reader_nested() { - let xml = ""; + let xml = ""; let mut reader = EventReader::from_str(xml); let elem = Element::from_reader(&mut reader); - let nested = Element::builder("bar").attr("baz", "qxx").build(); - let elem2 = Element::builder("foo").append(nested).build(); + let nested = Element::builder("bar", "ns1").attr("baz", "qxx").build(); + let elem2 = Element::builder("foo", "ns1").append(nested).build(); assert_eq!(elem.unwrap(), elem2); } #[test] fn test_from_reader_with_prefix() { - let xml = ""; + let xml = ""; let mut reader = EventReader::from_str(xml); let elem = Element::from_reader(&mut reader); - let nested = Element::builder("prefix:bar").attr("baz", "qxx").build(); - let elem2 = Element::builder("foo").append(nested).build(); + let nested = Element::builder("prefix:bar", "ns1").attr("baz", "qxx").build(); + let elem2 = Element::builder("foo", "ns1").append(nested).build(); assert_eq!(elem.unwrap(), elem2); } @@ -1022,7 +1085,7 @@ mod tests { #[test] fn does_not_unescape_cdata() { - let xml = "]]>"; + let xml = "]]>"; let mut reader = EventReader::from_str(xml); let elem = Element::from_reader(&mut reader).unwrap(); assert_eq!(elem.text(), "'>blah"); @@ -1030,7 +1093,7 @@ mod tests { #[test] fn test_compare_all_ns() { - let xml = ""; + let xml = ""; let mut reader = EventReader::from_str(xml); let elem = Element::from_reader(&mut reader).unwrap(); diff --git a/minidom-rs/src/error.rs b/minidom-rs/src/error.rs index 666e3aa70f3cd9e2dcdc59bf2b5ae7b546291068..acffc5f0874113d30edfef8d8ff5fe8bcfb210be 100644 --- a/minidom-rs/src/error.rs +++ b/minidom-rs/src/error.rs @@ -35,6 +35,9 @@ pub enum Error { /// An error which is returned when an elemet's name contains more than one colon InvalidElement, + /// An error which is returned when an element doesn't contain a namespace + MissingNamespace, + /// An error which is returned when a comment is to be parsed by minidom NoComments, } @@ -48,6 +51,7 @@ impl StdError for Error { Error::EndOfDocument => None, Error::InvalidElementClosed => None, Error::InvalidElement => None, + Error::MissingNamespace => None, Error::NoComments => None, } } @@ -66,6 +70,10 @@ impl std::fmt::Display for Error { write!(fmt, "the XML is invalid, an element was wrongly closed") } Error::InvalidElement => write!(fmt, "the XML element is invalid"), + Error::MissingNamespace => write!( + fmt, + "the XML element is missing a namespace", + ), Error::NoComments => write!( fmt, "a comment has been found even though comments are forbidden" diff --git a/minidom-rs/src/lib.rs b/minidom-rs/src/lib.rs index 1d996b01fc5e30811fe0ea7c84ea2099afb35904..bc47a2f6bf4e7b07eb2a1e1ab6e0e1265f2be506 100644 --- a/minidom-rs/src/lib.rs +++ b/minidom-rs/src/lib.rs @@ -79,7 +79,8 @@ pub use quick_xml; pub mod convert; pub mod element; pub mod error; -mod namespace_set; +mod namespaces; +mod prefixes; pub mod node; #[cfg(test)] @@ -88,5 +89,5 @@ mod tests; pub use convert::IntoAttributeValue; pub use element::{Children, ChildrenMut, Element, ElementBuilder}; pub use error::{Error, Result}; -pub use namespace_set::NSChoice; +pub use namespaces::NSChoice; pub use node::Node; diff --git a/minidom-rs/src/namespace_set.rs b/minidom-rs/src/namespace_set.rs deleted file mode 100644 index 986a92d03748bde83f874ab8484d4047290bc7b7..0000000000000000000000000000000000000000 --- a/minidom-rs/src/namespace_set.rs +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright (c) 2020 Emmanuel Gil Peyrot -// Copyright (c) 2020 Astro -// Copyright (c) 2020 Maxime “pep” Buquet -// Copyright (c) 2020 Xidorn Quan -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::cell::RefCell; -use std::collections::BTreeMap; -use std::fmt; -use std::rc::Rc; - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -/// Use to compare namespaces -pub enum NSChoice<'a> { - /// The element's namespace must match the specified namespace - OneOf(&'a str), - /// The element's namespace must be in the specified vector - AnyOf(&'a [&'a str]), - /// The element can have any namespace, or no namespace - Any, -} - -impl<'a> From<&'a str> for NSChoice<'a> { - fn from(ns: &'a str) -> NSChoice<'a> { - NSChoice::OneOf(ns) - } -} - -impl<'a> NSChoice<'a> { - fn compare(&self, ns: Option<&str>) -> bool { - match (ns, &self) { - (None, NSChoice::Any) => true, - (None, NSChoice::OneOf(_)) | (None, NSChoice::AnyOf(_)) => false, - (Some(_), NSChoice::Any) => true, - (Some(ns), NSChoice::OneOf(wanted_ns)) => &ns == wanted_ns, - (Some(ns), NSChoice::AnyOf(wanted_nss)) => wanted_nss.iter().any(|w| &ns == w), - } - } -} - -#[derive(Clone, PartialEq, Eq)] -pub struct NamespaceSet { - parent: RefCell>>, - namespaces: BTreeMap, String>, -} - -impl Default for NamespaceSet { - fn default() -> Self { - NamespaceSet { - parent: RefCell::new(None), - namespaces: BTreeMap::new(), - } - } -} - -impl fmt::Debug for NamespaceSet { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "NamespaceSet(")?; - for (prefix, namespace) in &self.namespaces { - write!( - f, - "xmlns{}={:?}, ", - match prefix { - None => String::new(), - Some(prefix) => format!(":{}", prefix), - }, - namespace - )?; - } - write!(f, "parent: {:?})", *self.parent.borrow()) - } -} - -impl NamespaceSet { - pub fn declared_ns(&self) -> &BTreeMap, String> { - &self.namespaces - } - - pub fn get(&self, prefix: &Option) -> Option { - match self.namespaces.get(prefix) { - Some(ns) => Some(ns.clone()), - None => match *self.parent.borrow() { - None => None, - Some(ref parent) => parent.get(prefix), - }, - } - } - - pub fn has<'a, NS: Into>>(&self, prefix: &Option, wanted_ns: NS) -> bool { - match self.namespaces.get(prefix) { - Some(ns) => wanted_ns.into().compare(Some(ns)), - None => match *self.parent.borrow() { - None => wanted_ns.into().compare(None), - Some(ref parent) => parent.has(prefix, wanted_ns), - }, - } - } - - pub fn set_parent(&self, parent: Rc) { - let mut parent_ns = self.parent.borrow_mut(); - let new_set = parent; - *parent_ns = Some(new_set); - } -} - -impl From, String>> for NamespaceSet { - fn from(namespaces: BTreeMap, String>) -> Self { - NamespaceSet { - parent: RefCell::new(None), - namespaces, - } - } -} - -impl From> for NamespaceSet { - fn from(namespace: Option) -> Self { - match namespace { - None => Self::default(), - Some(namespace) => Self::from(namespace), - } - } -} - -impl From for NamespaceSet { - fn from(namespace: String) -> Self { - let mut namespaces = BTreeMap::new(); - namespaces.insert(None, namespace); - - NamespaceSet { - parent: RefCell::new(None), - namespaces, - } - } -} - -impl From<(Option, String)> for NamespaceSet { - fn from(prefix_namespace: (Option, String)) -> Self { - let (prefix, namespace) = prefix_namespace; - let mut namespaces = BTreeMap::new(); - namespaces.insert(prefix, namespace); - - NamespaceSet { - parent: RefCell::new(None), - namespaces, - } - } -} - -impl From<(String, String)> for NamespaceSet { - fn from(prefix_namespace: (String, String)) -> Self { - let (prefix, namespace) = prefix_namespace; - Self::from((Some(prefix), namespace)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn get_has() { - let namespaces = NamespaceSet::from("foo".to_owned()); - assert_eq!(namespaces.get(&None), Some("foo".to_owned())); - assert!(namespaces.has(&None, "foo")); - } - - #[test] - fn get_has_prefixed() { - let namespaces = NamespaceSet::from(("x".to_owned(), "bar".to_owned())); - assert_eq!( - namespaces.get(&Some("x".to_owned())), - Some("bar".to_owned()) - ); - assert!(namespaces.has(&Some("x".to_owned()), "bar")); - } - - #[test] - fn get_has_recursive() { - let mut parent = NamespaceSet::from("foo".to_owned()); - for _ in 0..1000 { - let namespaces = NamespaceSet::default(); - namespaces.set_parent(Rc::new(parent)); - assert_eq!(namespaces.get(&None), Some("foo".to_owned())); - assert!(namespaces.has(&None, "foo")); - parent = namespaces; - } - } - - #[test] - fn get_has_prefixed_recursive() { - let mut parent = NamespaceSet::from(("x".to_owned(), "bar".to_owned())); - for _ in 0..1000 { - let namespaces = NamespaceSet::default(); - namespaces.set_parent(Rc::new(parent)); - assert_eq!( - namespaces.get(&Some("x".to_owned())), - Some("bar".to_owned()) - ); - assert!(namespaces.has(&Some("x".to_owned()), "bar")); - parent = namespaces; - } - } - - #[test] - fn debug_looks_correct() { - let parent = NamespaceSet::from("http://www.w3.org/2000/svg".to_owned()); - let namespaces = NamespaceSet::from(( - "xhtml".to_owned(), - "http://www.w3.org/1999/xhtml".to_owned(), - )); - namespaces.set_parent(Rc::new(parent)); - assert_eq!(format!("{:?}", namespaces), "NamespaceSet(xmlns:xhtml=\"http://www.w3.org/1999/xhtml\", parent: Some(NamespaceSet(xmlns=\"http://www.w3.org/2000/svg\", parent: None)))"); - } -} diff --git a/minidom-rs/src/namespaces.rs b/minidom-rs/src/namespaces.rs new file mode 100644 index 0000000000000000000000000000000000000000..7d81744065b8e5b7af19779e94c9b1c18486c6b9 --- /dev/null +++ b/minidom-rs/src/namespaces.rs @@ -0,0 +1,38 @@ +// Copyright (c) 2020 Emmanuel Gil Peyrot +// Copyright (c) 2020 Astro +// Copyright (c) 2020 Maxime “pep” Buquet +// Copyright (c) 2020 Xidorn Quan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +/// Use to compare namespaces +pub enum NSChoice<'a> { + /// The element must have no namespace + None, + /// The element's namespace must match the specified namespace + OneOf(&'a str), + /// The element's namespace must be in the specified vector + AnyOf(&'a [&'a str]), + /// The element can have any namespace, or no namespace + Any, +} + +impl<'a> From<&'a str> for NSChoice<'a> { + fn from(ns: &'a str) -> NSChoice<'a> { + NSChoice::OneOf(ns) + } +} + +impl<'a> NSChoice<'a> { + pub(crate) fn compare(&self, ns: &str) -> bool { + match (ns, &self) { + (_, NSChoice::None) => false, + (_, NSChoice::Any) => true, + (ns, NSChoice::OneOf(wanted_ns)) => &ns == wanted_ns, + (ns, NSChoice::AnyOf(wanted_nss)) => wanted_nss.iter().any(|w| &ns == w), + } + } +} diff --git a/minidom-rs/src/node.rs b/minidom-rs/src/node.rs index 501b22b5c2dd938cabbc428783dbc878d234e03f..0a0615ca803df4db431b1e28158d9e954d7b6c19 100644 --- a/minidom-rs/src/node.rs +++ b/minidom-rs/src/node.rs @@ -12,6 +12,7 @@ use crate::element::{Element, ElementBuilder}; use crate::error::Result; use std::io::Write; +use std::collections::BTreeMap; use quick_xml::events::{BytesText, Event}; use quick_xml::Writer as EventWriter; @@ -34,7 +35,7 @@ impl Node { /// ```rust /// use minidom::Node; /// - /// let elm = Node::Element("".parse().unwrap()); + /// let elm = Node::Element("".parse().unwrap()); /// let txt = Node::Text("meow".to_owned()); /// /// assert_eq!(elm.as_element().unwrap().name(), "meow"); @@ -55,7 +56,7 @@ impl Node { /// ```rust /// use minidom::Node; /// - /// let mut elm = Node::Element("".parse().unwrap()); + /// let mut elm = Node::Element("".parse().unwrap()); /// let mut txt = Node::Text("meow".to_owned()); /// /// assert_eq!(elm.as_element_mut().unwrap().name(), "meow"); @@ -76,7 +77,7 @@ impl Node { /// ```rust /// use minidom::Node; /// - /// let elm = Node::Element("".parse().unwrap()); + /// let elm = Node::Element("".parse().unwrap()); /// let txt = Node::Text("meow".to_owned()); /// /// assert_eq!(elm.into_element().unwrap().name(), "meow"); @@ -97,7 +98,7 @@ impl Node { /// ```rust /// use minidom::Node; /// - /// let elm = Node::Element("".parse().unwrap()); + /// let elm = Node::Element("".parse().unwrap()); /// let txt = Node::Text("meow".to_owned()); /// /// assert_eq!(elm.as_text(), None); @@ -118,7 +119,7 @@ impl Node { /// ```rust /// use minidom::Node; /// - /// let mut elm = Node::Element("".parse().unwrap()); + /// let mut elm = Node::Element("".parse().unwrap()); /// let mut txt = Node::Text("meow".to_owned()); /// /// assert_eq!(elm.as_text_mut(), None); @@ -145,7 +146,7 @@ impl Node { /// ```rust /// use minidom::Node; /// - /// let elm = Node::Element("".parse().unwrap()); + /// let elm = Node::Element("".parse().unwrap()); /// let txt = Node::Text("meow".to_owned()); /// /// assert_eq!(elm.into_text(), None); @@ -159,9 +160,9 @@ impl Node { } #[doc(hidden)] - pub(crate) fn write_to_inner(&self, writer: &mut EventWriter) -> Result<()> { + pub(crate) fn write_to_inner(&self, writer: &mut EventWriter, prefixes: &mut BTreeMap, String>) -> Result<()> { match *self { - Node::Element(ref elmt) => elmt.write_to_inner(writer)?, + Node::Element(ref elmt) => elmt.write_to_inner(writer, prefixes)?, Node::Text(ref s) => { writer.write_event(Event::Text(BytesText::from_plain_str(s)))?; } diff --git a/minidom-rs/src/parser.rs b/minidom-rs/src/parser.rs new file mode 100644 index 0000000000000000000000000000000000000000..49896c3ade75562144ac1e3639a4aa7b80aa7144 --- /dev/null +++ b/minidom-rs/src/parser.rs @@ -0,0 +1,184 @@ +// Copyright (c) 2020 Maxime “pep” Buquet +// Copyright (c) 2020 Emmanuel Gil Peyrot +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +//! Provides a `Parser` type, which takes bytes and returns Elements. It also keeps a hold of +//! ascendant elements to be able to handle namespaces properly. + +use crate::element::Element; +use crate::error::{Error, ParserError, Result}; + +use bytes::BytesMut; +use quick_xml::Reader as EventReader; +use std::cell::RefCell; +use std::str; + +/// Parser +#[derive(Debug)] +pub struct Parser { + buffer: RefCell, + state: ParserState, +} + +/// Describes the state of the parser. +/// +/// This parser will only accept one-level documents. The root element is kept for convenience, to +/// be able to pass namespaces down to children who are themselves children. +#[derive(Debug)] +pub enum ParserState { + /// Not enough data has been processed to find the first element. + Empty, + + /// The normal state. the root element has been identified and children are processed. + Root { + /// Root element. Kept for future reference + root: Element, + + /// Child element + child: Option, + + /// XXX: Weird flag to say if we've already sent what we could send or if there's more to + /// send. This Variant needs to be changed. + sent: bool, + }, + + /// Something was passed in the buffer that made the parser get into an error state. + Error, + + /// The root element has been closed. No feed-ing can happen past this point. + Closed, +} + +/// Result of polling the parser +#[derive(Debug)] +pub enum ParserResult { + /// Buffer is not empty but needs more data + Partial, + + /// An Element has been generated from the buffer. + Single(Element), +} + +/* +/// Split and parse it. +fn split_stream_stream_stream_features(string: String) -> (Element, Element) { + let mut stuff = string.splitn(2, '>'); + let stream_opening_str = stuff.next().unwrap().to_string() + "/>"; + let rest = stuff.next().unwrap().to_string(); + let stream_opening: Element = stream_opening_str.parse().unwrap(); + let rest: Element = rest.parse().unwrap(); + println!("opening: {}", String::from(&stream_opening)); + println!("features: {}", String::from(&rest)); + (stream_opening, rest) +} +*/ + +fn maybe_split_prolog(string: &str) -> &str { + if string.starts_with("'); + stuff.next(); + stuff.next().unwrap() + } else { + string + } +} + +impl Parser { + /// Creates a new Parser + pub fn new() -> Parser { + Parser { + buffer: RefCell::new(BytesMut::new()), + state: ParserState::Empty, + } + } + + /// Feed bytes to the parser. + pub fn feed(&mut self, bytes: BytesMut) -> Result<()> { + self.buffer.borrow_mut().unsplit(bytes); + let state = match self.state { + ParserState::Empty => { + // TODO: Try splitting xml prolog and stream header + let foo = self.buffer.borrow(); + let header = maybe_split_prolog(str::from_utf8(foo.as_ref())?); + println!("FOO: header: {:?}", header); + let mut reader = EventReader::from_str(header); + let root = Element::from_reader(&mut reader); + match root { + Ok(root) => { + println!("FOO: elem: {:?}", root); + ParserState::Root { + root, + child: None, + sent: false, + } + } + Err(e) => { + println!("FOO: err: {:?}", e); + ParserState::Empty + } + } + } + ParserState::Closed => return Err(Error::ParserError(ParserError::Closed)), + _ => ParserState::Empty, + }; + + self.state = state; + Ok(()) + } + + /// Returns Elements to the application. + pub fn poll(&mut self) -> Result> { + match &self.state { + ParserState::Empty if self.buffer.borrow().len() != 0 => { + Ok(Some(ParserResult::Partial)) + } + ParserState::Empty | ParserState::Closed | ParserState::Error => Ok(None), + ParserState::Root { + root, child: None, .. + } => Ok(Some(ParserResult::Single(root.clone()))), + ParserState::Root { + child: Some(child), .. + } => Ok(Some(ParserResult::Single(child.clone()))), + } + } + + /// Resets the parser + pub fn reset(&mut self) { + *self = Parser::new(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bytes::{BufMut, BytesMut}; + + #[test] + fn test_prolog() { + let mut parser = Parser::new(); + let mut buf = BytesMut::new(); + buf.put(&b""[..]); + buf.put(&b""[..]); + match parser.feed(buf) { + Ok(_) => (), + _ => panic!(), + } + + let elem = Element::builder("stream:stream", "http://etherx.jabber.org/streams") + .prefix_ns(None, "jabber:client") + .attr("xml:lang", "en") + .attr("version", "1.0") + .attr("to", "foo.bar") + .build(); + + println!("BAR: elem: {:?}", elem); + + match parser.poll() { + Ok(Some(ParserResult::Single(e))) => assert_eq!(e, elem), + _ => panic!(), + } + } +} diff --git a/minidom-rs/src/prefixes.rs b/minidom-rs/src/prefixes.rs new file mode 100644 index 0000000000000000000000000000000000000000..c0508b9eb0edf4de2be8985d6bd2eb2cb26448ac --- /dev/null +++ b/minidom-rs/src/prefixes.rs @@ -0,0 +1,96 @@ +// Copyright (c) 2020 Emmanuel Gil Peyrot +// Copyright (c) 2020 Astro +// Copyright (c) 2020 Maxime “pep” Buquet +// Copyright (c) 2020 Xidorn Quan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::collections::BTreeMap; +use std::fmt; + +#[derive(Clone, PartialEq, Eq)] +pub struct Prefixes { + prefixes: BTreeMap>, +} + +impl Default for Prefixes { + fn default() -> Self { + Prefixes { + prefixes: BTreeMap::new(), + } + } +} + +impl fmt::Debug for Prefixes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Prefixes(")?; + for (namespace, prefix) in &self.prefixes { + write!( + f, + "xmlns{}={:?} ", + match prefix { + None => String::new(), + Some(prefix) => format!(":{}", prefix), + }, + namespace + )?; + } + write!(f, ")") + } +} + +impl Prefixes { + pub fn declared_prefixes(&self) -> &BTreeMap> { + &self.prefixes + } + + pub fn get(&self, namespace: &String) -> Option> { + match self.prefixes.get(namespace) { + Some(ns) => Some(ns.clone()), + None => None, + } + } +} + +impl From>> for Prefixes { + fn from(prefixes: BTreeMap>) -> Self { + Prefixes { prefixes } + } +} + +impl From> for Prefixes { + fn from(namespace: Option) -> Self { + match namespace { + None => Self::default(), + Some(namespace) => Self::from(namespace), + } + } +} + +impl From for Prefixes { + fn from(namespace: String) -> Self { + let mut prefixes = BTreeMap::new(); + prefixes.insert(namespace, None); + + Prefixes { prefixes } + } +} + +impl From<(Option, String)> for Prefixes { + fn from(prefix_namespace: (Option, String)) -> Self { + let (prefix, namespace) = prefix_namespace; + let mut prefixes = BTreeMap::new(); + prefixes.insert(namespace, prefix); + + Prefixes { prefixes } + } +} + +impl From<(String, String)> for Prefixes { + fn from(prefix_namespace: (String, String)) -> Self { + let (prefix, namespace) = prefix_namespace; + Self::from((Some(prefix), namespace)) + } +} diff --git a/minidom-rs/src/tests.rs b/minidom-rs/src/tests.rs index 79cdff937536d5ca07b2f3eef565413b524c7037..3d34fe3d0b48a24aab3c2c6ef48ee6863bbb659a 100644 --- a/minidom-rs/src/tests.rs +++ b/minidom-rs/src/tests.rs @@ -18,16 +18,14 @@ use quick_xml::Reader; const TEST_STRING: &'static str = r#"meownya"#; fn build_test_tree() -> Element { - let mut root = Element::builder("root") - .ns("root_ns") + let mut root = Element::builder("root", "root_ns") .attr("xml:lang", "en") .attr("a", "b") .build(); root.append_text_node("meow"); - let child = Element::builder("child").attr("c", "d").build(); + let child = Element::builder("child", "root_ns").attr("c", "d").build(); root.append_child(child); - let other_child = Element::builder("child") - .ns("child_ns") + let other_child = Element::builder("child", "child_ns") .attr("d", "e") .attr("xml:lang", "fr") .build(); @@ -45,6 +43,80 @@ fn reader_works() { ); } +#[test] +fn reader_deduplicate_prefixes() { + // The reader shouldn't complain that "child" doesn't have a namespace. It should reuse the + // parent ns with the same prefix. + let _: Element = r#""#.parse().unwrap(); + let _: Element = r#""#.parse().unwrap(); + let _: Element = r#""#.parse().unwrap(); + + match r#""#.parse::() { + Err(Error::MissingNamespace) => (), + Err(err) => panic!("No or wrong error: {:?}", err), + Ok(elem) => panic!("Got Element: {}; was expecting Error::MissingNamespace", String::from(&elem)), + } +} + +#[test] +fn reader_no_deduplicate_sibling_prefixes() { + // The reader shouldn't reuse the sibling's prefixes + match r#""#.parse::() { + Err(Error::MissingNamespace) => (), + Err(err) => panic!("No or wrong error: {:?}", err), + Ok(elem) => panic!("Got Element:\n{:?}\n{}\n; was expecting Error::MissingNamespace", elem, String::from(&elem)), + } +} + +#[test] +fn test_real_data() { + let correction = Element::builder("replace", "urn:xmpp:message-correct:0").build(); + let body = Element::builder("body", "jabber:client").build(); + let message = Element::builder("message", "jabber:client") + .append(body) + .append(correction) + .build(); + let stream = Element::builder("stream", "http://etherx.jabber.org/streams") + .prefix(Some(String::from("stream")), "http://etherx.jabber.org/streams") + .prefix(None, "jabber:client") + .append(message) + .build(); + println!("{}", String::from(&stream)); + + let jid = Element::builder("jid", "urn:xmpp:presence:0").build(); + let nick = Element::builder("nick", "urn:xmpp:presence:0").build(); + let mix = Element::builder("mix", "urn:xmpp:presence:0") + .append(jid) + .append(nick) + .build(); + let show = Element::builder("show", "jabber:client").build(); + let status = Element::builder("status", "jabber:client").build(); + let presence = Element::builder("presence", "jabber:client") + .append(show) + .append(status) + .append(mix) + .build(); + let item = Element::builder("item", "http://jabber.org/protocol/pubsub") + .append(presence) + .build(); + let items = Element::builder("items", "http://jabber.org/protocol/pubsub") + .append(item) + .build(); + let pubsub = Element::builder("pubsub", "http://jabber.org/protocol/pubsub") + .append(items) + .build(); + let iq = Element::builder("iq", "jabber:client") + .append(pubsub) + .build(); + let stream = Element::builder("stream", "http://etherx.jabber.org/streams") + .prefix(Some(String::from("stream")), "http://etherx.jabber.org/streams") + .prefix(None, "jabber:client") + .append(iq) + .build(); + + println!("{}", String::from(&stream)); +} + #[test] fn writer_works() { let root = build_test_tree(); @@ -66,39 +138,115 @@ fn writer_with_decl_works() { assert_eq!(String::from_utf8(writer).unwrap(), result); } +#[test] +fn writer_with_prefix() { + let root = Element::builder("root", "ns1") + .prefix(Some(String::from("p1")), "ns1") + .prefix(None, "ns2") + .build(); + assert_eq!(String::from(&root), + r#""#, + ); +} + +#[test] +fn writer_no_prefix_namespace() { + let root = Element::builder("root", "ns1").build(); + // TODO: Note that this isn't exactly equal to a None prefix. it's just that the None prefix is + // the most obvious when it's not already used. Maybe fix tests so that it only checks that the + // prefix used equals the one declared for the namespace. + assert_eq!(String::from(&root), r#""#); +} + +#[test] +fn writer_no_prefix_namespace_child() { + let child = Element::builder("child", "ns1").build(); + let root = Element::builder("root", "ns1") + .append(child) + .build(); + // TODO: Same remark as `writer_no_prefix_namespace`. + assert_eq!(String::from(&root), r#""#); + + let child = Element::builder("child", "ns2") + .prefix(None, "ns3") + .build(); + let root = Element::builder("root", "ns1") + .append(child) + .build(); + // TODO: Same remark as `writer_no_prefix_namespace`. + assert_eq!(String::from(&root), r#""#); +} + +#[test] +fn writer_prefix_namespace_child() { + let child = Element::builder("child", "ns1").build(); + let root = Element::builder("root", "ns1") + .prefix(Some(String::from("p1")), "ns1") + .append(child) + .build(); + assert_eq!(String::from(&root), r#""#); +} + +#[test] +fn writer_with_prefix_deduplicate() { + let child = Element::builder("child", "ns1") + // .prefix(Some(String::from("p1")), "ns1") + .build(); + let root = Element::builder("root", "ns1") + .prefix(Some(String::from("p1")), "ns1") + .prefix(None, "ns2") + .append(child) + .build(); + assert_eq!(String::from(&root), + r#""#, + ); + + // Ensure descendants don't just reuse ancestors' prefixes that have been shadowed in between + let grandchild = Element::builder("grandchild", "ns1") + .build(); + let child = Element::builder("child", "ns2") + .append(grandchild) + .build(); + let root = Element::builder("root", "ns1") + .append(child) + .build(); + assert_eq!(String::from(&root), + r#""#, + ); +} + #[test] fn writer_escapes_attributes() { - let root = Element::builder("root").attr("a", "\"Air\" quotes").build(); + let root = Element::builder("root", "ns1").attr("a", "\"Air\" quotes").build(); let mut writer = Vec::new(); { root.write_to(&mut writer).unwrap(); } assert_eq!( String::from_utf8(writer).unwrap(), - r#""# + r#""# ); } #[test] fn writer_escapes_text() { - let root = Element::builder("root").append("<3").build(); + let root = Element::builder("root", "ns1").append("<3").build(); let mut writer = Vec::new(); { root.write_to(&mut writer).unwrap(); } - assert_eq!(String::from_utf8(writer).unwrap(), r#"<3"#); + assert_eq!(String::from_utf8(writer).unwrap(), r#"<3"#); } #[test] fn builder_works() { - let elem = Element::builder("a") - .ns("b") + let elem = Element::builder("a", "b") .attr("c", "d") - .append(Element::builder("child")) + .append(Element::builder("child", "b")) .append("e") .build(); assert_eq!(elem.name(), "a"); - assert_eq!(elem.ns(), Some("b".to_owned())); + assert_eq!(elem.ns(), "b".to_owned()); assert_eq!(elem.attr("c"), Some("d")); assert_eq!(elem.attr("x"), None); assert_eq!(elem.text(), "e"); @@ -140,9 +288,9 @@ fn get_child_works() { #[test] fn namespace_propagation_works() { - let mut root = Element::builder("root").ns("root_ns").build(); - let mut child = Element::bare("child"); - let grandchild = Element::bare("grandchild"); + let mut root = Element::builder("root", "root_ns").build(); + let mut child = Element::bare("child", "root_ns"); + let grandchild = Element::bare("grandchild", "root_ns"); child.append_child(grandchild); root.append_child(child); @@ -159,12 +307,12 @@ fn namespace_propagation_works() { #[test] fn two_elements_with_same_arguments_different_order_are_equal() { - let elem1: Element = "".parse().unwrap(); - let elem2: Element = "".parse().unwrap(); + let elem1: Element = "".parse().unwrap(); + let elem2: Element = "".parse().unwrap(); assert_eq!(elem1, elem2); - let elem1: Element = "".parse().unwrap(); - let elem2: Element = "".parse().unwrap(); + let elem1: Element = "".parse().unwrap(); + let elem2: Element = "".parse().unwrap(); assert_ne!(elem1, elem2); } @@ -184,11 +332,11 @@ fn namespace_attributes_works() { #[test] fn wrongly_closed_elements_error() { - let elem1 = "".parse::(); + let elem1 = "".parse::(); assert!(elem1.is_err()); - let elem1 = "".parse::(); + let elem1 = "".parse::(); assert!(elem1.is_err()); - let elem1 = "".parse::(); + let elem1 = "".parse::(); assert!(elem1.is_ok()); } @@ -196,7 +344,7 @@ fn wrongly_closed_elements_error() { fn namespace_simple() { let elem: Element = "".parse().unwrap(); assert_eq!(elem.name(), "message"); - assert_eq!(elem.ns(), Some("jabber:client".to_owned())); + assert_eq!(elem.ns(), "jabber:client".to_owned()); } #[test] @@ -207,53 +355,53 @@ fn namespace_prefixed() { assert_eq!(elem.name(), "features"); assert_eq!( elem.ns(), - Some("http://etherx.jabber.org/streams".to_owned()) + "http://etherx.jabber.org/streams".to_owned(), ); } #[test] fn namespace_inherited_simple() { - let elem: Element = "" + let elem: Element = "" .parse() .unwrap(); assert_eq!(elem.name(), "stream"); - assert_eq!(elem.ns(), Some("jabber:client".to_owned())); + assert_eq!(elem.ns(), "jabber:client".to_owned()); let child = elem.children().next().unwrap(); assert_eq!(child.name(), "message"); - assert_eq!(child.ns(), Some("jabber:client".to_owned())); + assert_eq!(child.ns(), "jabber:client".to_owned()); } #[test] fn namespace_inherited_prefixed1() { - let elem: Element = "" + let elem: Element = "" .parse().unwrap(); assert_eq!(elem.name(), "features"); assert_eq!( elem.ns(), - Some("http://etherx.jabber.org/streams".to_owned()) + "http://etherx.jabber.org/streams".to_owned(), ); let child = elem.children().next().unwrap(); assert_eq!(child.name(), "message"); - assert_eq!(child.ns(), Some("jabber:client".to_owned())); + assert_eq!(child.ns(), "jabber:client".to_owned()); } #[test] fn namespace_inherited_prefixed2() { - let elem: Element = "" + let elem: Element = "" .parse().unwrap(); assert_eq!(elem.name(), "stream"); assert_eq!( elem.ns(), - Some("http://etherx.jabber.org/streams".to_owned()) + "http://etherx.jabber.org/streams".to_owned(), ); let child = elem.children().next().unwrap(); assert_eq!(child.name(), "message"); - assert_eq!(child.ns(), Some("jabber:client".to_owned())); + assert_eq!(child.ns(), "jabber:client".to_owned()); } #[test] fn fail_comments() { - let elem: Result = "".parse(); + let elem: Result = "".parse(); match elem { Err(Error::NoComments) => (), _ => panic!(), @@ -262,12 +410,12 @@ fn fail_comments() { #[test] fn xml_error() { - match "".parse::() { + match "".parse::() { Err(crate::error::Error::XmlError(_)) => (), err => panic!("No or wrong error: {:?}", err), } - match "() { + match "() { Err(crate::error::Error::XmlError(_)) => (), err => panic!("No or wrong error: {:?}", err), } @@ -280,3 +428,11 @@ fn invalid_element_error() { err => panic!("No or wrong error: {:?}", err), } } + +#[test] +fn missing_namespace_error() { + match "".parse::() { + Err(crate::error::Error::MissingNamespace) => (), + err => panic!("No or wrong error: {:?}", err), + } +}