Remove the -rs suffix of jid, minidom and xmpp

We know those are Rust libraries, no need to add it to the path.  This
synchronises their directory with the crate name, hopefully reducing
confusion.
This commit is contained in:
Emmanuel Gil Peyrot 2020-06-22 02:17:32 +02:00
commit 714d850e69
28 changed files with 6 additions and 6 deletions

94
minidom/src/convert.rs Normal file
View file

@ -0,0 +1,94 @@
// Copyright (c) 2020 lumi <lumi@pew.im>
// Copyright (c) 2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
//
// 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/.
//! A module which exports a few traits for converting types to elements and attributes.
/// A trait for types which can be converted to an attribute value.
pub trait IntoAttributeValue {
/// Turns this into an attribute string, or None if it shouldn't be added.
fn into_attribute_value(self) -> Option<String>;
}
macro_rules! impl_into_attribute_value {
($t:ty) => {
impl IntoAttributeValue for $t {
fn into_attribute_value(self) -> Option<String> {
Some(format!("{}", self))
}
}
};
}
macro_rules! impl_into_attribute_values {
($($t:ty),*) => {
$(impl_into_attribute_value!($t);)*
}
}
impl_into_attribute_values!(
usize,
u64,
u32,
u16,
u8,
isize,
i64,
i32,
i16,
i8,
::std::net::IpAddr
);
impl IntoAttributeValue for String {
fn into_attribute_value(self) -> Option<String> {
Some(self)
}
}
impl<'a> IntoAttributeValue for &'a String {
fn into_attribute_value(self) -> Option<String> {
Some(self.to_owned())
}
}
impl<'a> IntoAttributeValue for &'a str {
fn into_attribute_value(self) -> Option<String> {
Some(self.to_owned())
}
}
impl<T: IntoAttributeValue> IntoAttributeValue for Option<T> {
fn into_attribute_value(self) -> Option<String> {
self.and_then(IntoAttributeValue::into_attribute_value)
}
}
#[cfg(test)]
mod tests {
use super::IntoAttributeValue;
use std::net::IpAddr;
use std::str::FromStr;
#[test]
fn test_into_attribute_value_on_ints() {
assert_eq!(16u8.into_attribute_value().unwrap(), "16");
assert_eq!(17u16.into_attribute_value().unwrap(), "17");
assert_eq!(18u32.into_attribute_value().unwrap(), "18");
assert_eq!(19u64.into_attribute_value().unwrap(), "19");
assert_eq!(16i8.into_attribute_value().unwrap(), "16");
assert_eq!((-17i16).into_attribute_value().unwrap(), "-17");
assert_eq!(18i32.into_attribute_value().unwrap(), "18");
assert_eq!((-19i64).into_attribute_value().unwrap(), "-19");
assert_eq!(
IpAddr::from_str("127.000.0.1")
.unwrap()
.into_attribute_value()
.unwrap(),
"127.0.0.1"
);
}
}

1163
minidom/src/element.rs Normal file

File diff suppressed because it is too large Load diff

112
minidom/src/error.rs Normal file
View file

@ -0,0 +1,112 @@
// Copyright (c) 2020 lumi <lumi@pew.im>
// Copyright (c) 2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
// Copyright (c) 2020 Bastien Orivel <eijebong+minidom@bananium.fr>
// Copyright (c) 2020 Astro <astro@spaceboyz.net>
// Copyright (c) 2020 Maxime “pep” Buquet <pep@bouah.net>
// Copyright (c) 2020 Matt Bilker <me@mbilker.us>
//
// 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 an error type for this crate.
use std::convert::From;
use std::error::Error as StdError;
/// Our main error type.
#[derive(Debug)]
pub enum Error {
/// An error from quick_xml.
XmlError(::quick_xml::Error),
/// An UTF-8 conversion error.
Utf8Error(::std::str::Utf8Error),
/// An I/O error, from std::io.
IoError(::std::io::Error),
/// An error which is returned when the end of the document was reached prematurely.
EndOfDocument,
/// An error which is returned when an element is closed when it shouldn't be
InvalidElementClosed,
/// An error which is returned when an elemet's name contains more colons than permitted
InvalidElement,
/// An error which is returned when an element being serialized doesn't contain a prefix
/// (be it None or Some(_)).
InvalidPrefix,
/// 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,
/// An error which is returned when a prefixed is defined twice
DuplicatePrefix,
}
impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> {
match self {
Error::XmlError(e) => Some(e),
Error::Utf8Error(e) => Some(e),
Error::IoError(e) => Some(e),
Error::EndOfDocument => None,
Error::InvalidElementClosed => None,
Error::InvalidElement => None,
Error::InvalidPrefix => None,
Error::MissingNamespace => None,
Error::NoComments => None,
Error::DuplicatePrefix => None,
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Error::XmlError(e) => write!(fmt, "XML error: {}", e),
Error::Utf8Error(e) => write!(fmt, "UTF-8 error: {}", e),
Error::IoError(e) => write!(fmt, "IO error: {}", e),
Error::EndOfDocument => {
write!(fmt, "the end of the document has been reached prematurely")
}
Error::InvalidElementClosed => {
write!(fmt, "the XML is invalid, an element was wrongly closed")
}
Error::InvalidElement => write!(fmt, "the XML element is invalid"),
Error::InvalidPrefix => write!(fmt, "the prefix 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"
),
Error::DuplicatePrefix => write!(fmt, "the prefix is already defined"),
}
}
}
impl From<::quick_xml::Error> for Error {
fn from(err: ::quick_xml::Error) -> Error {
Error::XmlError(err)
}
}
impl From<::std::str::Utf8Error> for Error {
fn from(err: ::std::str::Utf8Error) -> Error {
Error::Utf8Error(err)
}
}
impl From<::std::io::Error> for Error {
fn from(err: ::std::io::Error) -> Error {
Error::IoError(err)
}
}
/// Our simplified Result type.
pub type Result<T> = ::std::result::Result<T, Error>;

93
minidom/src/lib.rs Normal file
View file

@ -0,0 +1,93 @@
// Copyright (c) 2020 lumi <lumi@pew.im>
// Copyright (c) 2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
// Copyright (c) 2020 Bastien Orivel <eijebong+minidom@bananium.fr>
// Copyright (c) 2020 Astro <astro@spaceboyz.net>
// Copyright (c) 2020 Maxime “pep” Buquet <pep@bouah.net>
//
// 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/.
#![deny(missing_docs)]
//! A minimal DOM crate built on top of quick-xml.
//!
//! This library exports an `Element` struct which represents a DOM tree.
//!
//! # Example
//!
//! Run with `cargo run --example articles`. Located in `examples/articles.rs`.
//!
//! ```rust,ignore
//! extern crate minidom;
//!
//! use minidom::Element;
//!
//! const DATA: &'static str = r#"<articles xmlns="article">
//! <article>
//! <title>10 Terrible Bugs You Would NEVER Believe Happened</title>
//! <body>
//! Rust fixed them all. &lt;3
//! </body>
//! </article>
//! <article>
//! <title>BREAKING NEWS: Physical Bug Jumps Out Of Programmer's Screen</title>
//! <body>
//! Just kidding!
//! </body>
//! </article>
//! </articles>"#;
//!
//! const ARTICLE_NS: &'static str = "article";
//!
//! #[derive(Debug)]
//! pub struct Article {
//! title: String,
//! body: String,
//! }
//!
//! fn main() {
//! let root: Element = DATA.parse().unwrap();
//!
//! let mut articles: Vec<Article> = Vec::new();
//!
//! for child in root.children() {
//! if child.is("article", ARTICLE_NS) {
//! let title = child.get_child("title", ARTICLE_NS).unwrap().text();
//! let body = child.get_child("body", ARTICLE_NS).unwrap().text();
//! articles.push(Article {
//! title: title,
//! body: body.trim().to_owned(),
//! });
//! }
//! }
//!
//! println!("{:?}", articles);
//! }
//! ```
//!
//! # Usage
//!
//! To use `minidom`, add this to your `Cargo.toml` under `dependencies`:
//!
//! ```toml,ignore
//! minidom = "*"
//! ```
pub use quick_xml;
pub mod convert;
pub mod element;
pub mod error;
mod namespaces;
pub mod node;
mod prefixes;
#[cfg(test)]
mod tests;
pub use convert::IntoAttributeValue;
pub use element::{Children, ChildrenMut, Element, ElementBuilder};
pub use error::{Error, Result};
pub use namespaces::NSChoice;
pub use node::Node;

38
minidom/src/namespaces.rs Normal file
View file

@ -0,0 +1,38 @@
// Copyright (c) 2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
// Copyright (c) 2020 Astro <astro@spaceboyz.net>
// Copyright (c) 2020 Maxime “pep” Buquet <pep@bouah.net>
// Copyright (c) 2020 Xidorn Quan <me@upsuper.org>
//
// 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),
}
}
}

214
minidom/src/node.rs Normal file
View file

@ -0,0 +1,214 @@
// Copyright (c) 2020 lumi <lumi@pew.im>
// Copyright (c) 2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
// Copyright (c) 2020 Maxime “pep” Buquet <pep@bouah.net>
//
// 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 the `Node` struct, which represents a node in the DOM.
use crate::element::{Element, ElementBuilder};
use crate::error::Result;
use std::collections::BTreeMap;
use std::io::Write;
use quick_xml::events::{BytesText, Event};
use quick_xml::Writer as EventWriter;
/// A node in an element tree.
#[derive(Clone, Debug, Eq)]
pub enum Node {
/// An `Element`.
Element(Element),
/// A text node.
Text(String),
}
impl Node {
/// Turns this into a reference to an `Element` if this is an element node.
/// Else this returns `None`.
///
/// # Examples
///
/// ```rust
/// use minidom::Node;
///
/// let elm = Node::Element("<meow xmlns=\"ns1\"/>".parse().unwrap());
/// let txt = Node::Text("meow".to_owned());
///
/// assert_eq!(elm.as_element().unwrap().name(), "meow");
/// assert_eq!(txt.as_element(), None);
/// ```
pub fn as_element(&self) -> Option<&Element> {
match *self {
Node::Element(ref e) => Some(e),
Node::Text(_) => None,
}
}
/// Turns this into a mutable reference of an `Element` if this is an element node.
/// Else this returns `None`.
///
/// # Examples
///
/// ```rust
/// use minidom::Node;
///
/// let mut elm = Node::Element("<meow xmlns=\"ns1\"/>".parse().unwrap());
/// let mut txt = Node::Text("meow".to_owned());
///
/// assert_eq!(elm.as_element_mut().unwrap().name(), "meow");
/// assert_eq!(txt.as_element_mut(), None);
/// ```
pub fn as_element_mut(&mut self) -> Option<&mut Element> {
match *self {
Node::Element(ref mut e) => Some(e),
Node::Text(_) => None,
}
}
/// Turns this into an `Element`, consuming self, if this is an element node.
/// Else this returns `None`.
///
/// # Examples
///
/// ```rust
/// use minidom::Node;
///
/// let elm = Node::Element("<meow xmlns=\"ns1\"/>".parse().unwrap());
/// let txt = Node::Text("meow".to_owned());
///
/// assert_eq!(elm.into_element().unwrap().name(), "meow");
/// assert_eq!(txt.into_element(), None);
/// ```
pub fn into_element(self) -> Option<Element> {
match self {
Node::Element(e) => Some(e),
Node::Text(_) => None,
}
}
/// Turns this into an `&str` if this is a text node.
/// Else this returns `None`.
///
/// # Examples
///
/// ```rust
/// use minidom::Node;
///
/// let elm = Node::Element("<meow xmlns=\"ns1\"/>".parse().unwrap());
/// let txt = Node::Text("meow".to_owned());
///
/// assert_eq!(elm.as_text(), None);
/// assert_eq!(txt.as_text().unwrap(), "meow");
/// ```
pub fn as_text(&self) -> Option<&str> {
match *self {
Node::Element(_) => None,
Node::Text(ref s) => Some(s),
}
}
/// Turns this into an `&mut String` if this is a text node.
/// Else this returns `None`.
///
/// # Examples
///
/// ```rust
/// use minidom::Node;
///
/// let mut elm = Node::Element("<meow xmlns=\"ns1\"/>".parse().unwrap());
/// let mut txt = Node::Text("meow".to_owned());
///
/// assert_eq!(elm.as_text_mut(), None);
/// {
/// let text_mut = txt.as_text_mut().unwrap();
/// assert_eq!(text_mut, "meow");
/// text_mut.push_str("zies");
/// assert_eq!(text_mut, "meowzies");
/// }
/// assert_eq!(txt.as_text().unwrap(), "meowzies");
/// ```
pub fn as_text_mut(&mut self) -> Option<&mut String> {
match *self {
Node::Element(_) => None,
Node::Text(ref mut s) => Some(s),
}
}
/// Turns this into an `String`, consuming self, if this is a text node.
/// Else this returns `None`.
///
/// # Examples
///
/// ```rust
/// use minidom::Node;
///
/// let elm = Node::Element("<meow xmlns=\"ns1\"/>".parse().unwrap());
/// let txt = Node::Text("meow".to_owned());
///
/// assert_eq!(elm.into_text(), None);
/// assert_eq!(txt.into_text().unwrap(), "meow");
/// ```
pub fn into_text(self) -> Option<String> {
match self {
Node::Element(_) => None,
Node::Text(s) => Some(s),
}
}
#[doc(hidden)]
pub(crate) fn write_to_inner<W: Write>(
&self,
writer: &mut EventWriter<W>,
prefixes: &mut BTreeMap<Option<String>, String>,
) -> Result<()> {
match *self {
Node::Element(ref elmt) => elmt.write_to_inner(writer, prefixes)?,
Node::Text(ref s) => {
writer.write_event(Event::Text(BytesText::from_plain_str(s)))?;
}
}
Ok(())
}
}
impl<I> From<I> for Node
where
I: Into<Element>,
{
fn from(elm: I) -> Node {
Node::Element(elm.into())
}
}
impl From<String> for Node {
fn from(s: String) -> Node {
Node::Text(s)
}
}
impl<'a> From<&'a str> for Node {
fn from(s: &'a str) -> Node {
Node::Text(s.to_owned())
}
}
impl From<ElementBuilder> for Node {
fn from(builder: ElementBuilder) -> Node {
Node::Element(builder.build())
}
}
impl PartialEq for Node {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(&Node::Element(ref elem1), &Node::Element(ref elem2)) => elem1 == elem2,
(&Node::Text(ref text1), &Node::Text(ref text2)) => text1 == text2,
_ => false,
}
}
}

184
minidom/src/parser.rs Normal file
View file

@ -0,0 +1,184 @@
// Copyright (c) 2020 Maxime “pep” Buquet <pep@bouah.net>
// Copyright (c) 2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
//
// 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<BytesMut>,
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<Element>,
/// 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 <stream:stream> 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("<?xml") {
let mut stuff = string.splitn(2, '>');
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<Option<ParserResult>> {
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"<?xml version='1.0'?>"[..]);
buf.put(&b"<stream:stream xmlns='jabber:client' xml:lang='en' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='foo.bar'>"[..]);
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!(),
}
}
}

101
minidom/src/prefixes.rs Normal file
View file

@ -0,0 +1,101 @@
// Copyright (c) 2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
// Copyright (c) 2020 Astro <astro@spaceboyz.net>
// Copyright (c) 2020 Maxime “pep” Buquet <pep@bouah.net>
// Copyright (c) 2020 Xidorn Quan <me@upsuper.org>
//
// 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;
pub type Prefix = Option<String>;
pub type Namespace = String;
#[derive(Clone, PartialEq, Eq)]
pub struct Prefixes {
prefixes: BTreeMap<Prefix, Namespace>,
}
impl Default for Prefixes {
fn default() -> Self {
Prefixes {
prefixes: BTreeMap::new(),
}
}
}
impl fmt::Debug for Prefixes {
// TODO: Fix end character
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Prefixes(")?;
for (prefix, namespace) 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<Prefix, Namespace> {
&self.prefixes
}
pub fn get(&self, prefix: &Prefix) -> Option<&Namespace> {
self.prefixes.get(prefix)
}
pub(crate) fn insert<S: Into<Namespace>>(&mut self, prefix: Prefix, namespace: S) {
self.prefixes.insert(prefix, namespace.into());
}
}
impl From<BTreeMap<Prefix, Namespace>> for Prefixes {
fn from(prefixes: BTreeMap<Prefix, Namespace>) -> Self {
Prefixes { prefixes }
}
}
impl From<Option<String>> for Prefixes {
fn from(namespace: Option<String>) -> Self {
match namespace {
None => Self::default(),
Some(namespace) => Self::from(namespace),
}
}
}
impl From<Namespace> for Prefixes {
fn from(namespace: Namespace) -> Self {
let mut prefixes = BTreeMap::new();
prefixes.insert(None, namespace);
Prefixes { prefixes }
}
}
impl From<(Prefix, Namespace)> for Prefixes {
fn from(prefix_namespace: (Prefix, Namespace)) -> Self {
let (prefix, namespace) = prefix_namespace;
let mut prefixes = BTreeMap::new();
prefixes.insert(prefix, namespace);
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))
}
}

459
minidom/src/tests.rs Normal file
View file

@ -0,0 +1,459 @@
// Copyright (c) 2020 lumi <lumi@pew.im>
// Copyright (c) 2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
// Copyright (c) 2020 Bastien Orivel <eijebong+minidom@bananium.fr>
// Copyright (c) 2020 Astro <astro@spaceboyz.net>
// Copyright (c) 2020 Maxime “pep” Buquet <pep@bouah.net>
// Copyright (c) 2020 Yue Liu <amznyue@amazon.com>
// Copyright (c) 2020 Matt Bilker <me@mbilker.us>
//
// 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 crate::element::Element;
use crate::error::Error;
use quick_xml::Reader;
const TEST_STRING: &'static str = r#"<root xmlns="root_ns" a="b" xml:lang="en">meow<child c="d"/><child xmlns="child_ns" d="e" xml:lang="fr"/>nya</root>"#;
fn build_test_tree() -> Element {
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", "root_ns").attr("c", "d").build();
root.append_child(child);
let other_child = Element::builder("child", "child_ns")
.attr("d", "e")
.attr("xml:lang", "fr")
.build();
root.append_child(other_child);
root.append_text_node("nya");
root
}
#[test]
fn reader_works() {
let mut reader = Reader::from_str(TEST_STRING);
assert_eq!(
Element::from_reader(&mut reader).unwrap(),
build_test_tree()
);
}
#[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#"<root xmlns="ns1"><child/></root>"#.parse().unwrap();
let _: Element = r#"<p1:root xmlns:p1="ns1"><p1:child/></p1:root>"#.parse().unwrap();
let _: Element = r#"<root xmlns="ns1"><child xmlns:p1="ns2"><p1:grandchild/></child></root>"#
.parse()
.unwrap();
match r#"<p1:root xmlns:p1="ns1"><child/></p1:root>"#.parse::<Element>() {
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#"<root xmlns="ns1"><p1:child1 xmlns:p1="ns2"/><p1:child2/></root>"#.parse::<Element>() {
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",
)
.unwrap()
.prefix(None, "jabber:client")
.unwrap()
.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",
)
.unwrap()
.prefix(None, "jabber:client")
.unwrap()
.append(iq)
.build();
println!("{}", String::from(&stream));
}
#[test]
fn writer_works() {
let root = build_test_tree();
let mut writer = Vec::new();
{
root.write_to(&mut writer).unwrap();
}
assert_eq!(String::from_utf8(writer).unwrap(), TEST_STRING);
}
#[test]
fn writer_with_decl_works() {
let root = build_test_tree();
let mut writer = Vec::new();
{
root.write_to_decl(&mut writer).unwrap();
}
let result = format!(r#"<?xml version="1.0" encoding="utf-8"?>{}"#, TEST_STRING);
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")
.unwrap()
.prefix(None, "ns2")
.unwrap()
.build();
assert_eq!(
String::from(&root),
r#"<p1:root xmlns="ns2" xmlns:p1="ns1"/>"#,
);
}
#[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#"<root xmlns="ns1"/>"#);
}
#[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#"<root xmlns="ns1"><child/></root>"#);
let child = Element::builder("child", "ns2")
.prefix(None, "ns3")
.unwrap()
.build();
let root = Element::builder("root", "ns1").append(child).build();
// TODO: Same remark as `writer_no_prefix_namespace`.
assert_eq!(
String::from(&root),
r#"<root xmlns="ns1"><ns0:child xmlns:ns0="ns2" xmlns="ns3"/></root>"#
);
}
#[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")
.unwrap()
.append(child)
.build();
assert_eq!(
String::from(&root),
r#"<p1:root xmlns:p1="ns1"><p1:child/></p1:root>"#
);
}
#[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")
.unwrap()
.prefix(None, "ns2")
.unwrap()
.append(child)
.build();
assert_eq!(
String::from(&root),
r#"<p1:root xmlns="ns2" xmlns:p1="ns1"><p1:child/></p1:root>"#,
);
// 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#"<root xmlns="ns1"><child xmlns="ns2"><grandchild xmlns="ns1"/></child></root>"#,
);
}
#[test]
fn writer_escapes_attributes() {
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#"<root xmlns="ns1" a="&quot;Air&quot; quotes"/>"#
);
}
#[test]
fn writer_escapes_text() {
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#"<root xmlns="ns1">&lt;3</root>"#
);
}
#[test]
fn builder_works() {
let elem = Element::builder("a", "b")
.attr("c", "d")
.append(Element::builder("child", "b"))
.append("e")
.build();
assert_eq!(elem.name(), "a");
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");
assert!(elem.has_child("child", "b"));
assert!(elem.is("a", "b"));
}
#[test]
fn children_iter_works() {
let root = build_test_tree();
let mut iter = root.children();
assert!(iter.next().unwrap().is("child", "root_ns"));
assert!(iter.next().unwrap().is("child", "child_ns"));
assert_eq!(iter.next(), None);
}
#[test]
fn get_child_works() {
let root = build_test_tree();
assert_eq!(root.get_child("child", "inexistent_ns"), None);
assert_eq!(root.get_child("not_a_child", "root_ns"), None);
assert!(root
.get_child("child", "root_ns")
.unwrap()
.is("child", "root_ns"));
assert!(root
.get_child("child", "child_ns")
.unwrap()
.is("child", "child_ns"));
assert_eq!(
root.get_child("child", "root_ns").unwrap().attr("c"),
Some("d")
);
assert_eq!(
root.get_child("child", "child_ns").unwrap().attr("d"),
Some("e")
);
}
#[test]
fn namespace_propagation_works() {
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);
assert_eq!(root.get_child("child", "root_ns").unwrap().ns(), root.ns());
assert_eq!(
root.get_child("child", "root_ns")
.unwrap()
.get_child("grandchild", "root_ns")
.unwrap()
.ns(),
root.ns()
);
}
#[test]
fn two_elements_with_same_arguments_different_order_are_equal() {
let elem1: Element = "<a b='a' c='' xmlns='ns1'/>".parse().unwrap();
let elem2: Element = "<a c='' b='a' xmlns='ns1'/>".parse().unwrap();
assert_eq!(elem1, elem2);
let elem1: Element = "<a b='a' c='' xmlns='ns1'/>".parse().unwrap();
let elem2: Element = "<a c='d' b='a' xmlns='ns1'/>".parse().unwrap();
assert_ne!(elem1, elem2);
}
#[test]
fn namespace_attributes_works() {
let mut reader = Reader::from_str(TEST_STRING);
let root = Element::from_reader(&mut reader).unwrap();
assert_eq!("en", root.attr("xml:lang").unwrap());
assert_eq!(
"fr",
root.get_child("child", "child_ns")
.unwrap()
.attr("xml:lang")
.unwrap()
);
}
#[test]
fn wrongly_closed_elements_error() {
let elem1 = "<a xmlns='ns1'></b>".parse::<Element>();
assert!(elem1.is_err());
let elem1 = "<a xmlns='ns1'></c></a>".parse::<Element>();
assert!(elem1.is_err());
let elem1 = "<a xmlns='ns1'><c xmlns='ns1'><d xmlns='ns1'/></c></a>".parse::<Element>();
assert!(elem1.is_ok());
}
#[test]
fn namespace_simple() {
let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
assert_eq!(elem.name(), "message");
assert_eq!(elem.ns(), "jabber:client".to_owned());
}
#[test]
fn namespace_prefixed() {
let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'/>"
.parse()
.unwrap();
assert_eq!(elem.name(), "features");
assert_eq!(elem.ns(), "http://etherx.jabber.org/streams".to_owned(),);
}
#[test]
fn namespace_inherited_simple() {
let elem: Element = "<stream xmlns='jabber:client'><message xmlns='jabber:client' /></stream>"
.parse()
.unwrap();
assert_eq!(elem.name(), "stream");
assert_eq!(elem.ns(), "jabber:client".to_owned());
let child = elem.children().next().unwrap();
assert_eq!(child.name(), "message");
assert_eq!(child.ns(), "jabber:client".to_owned());
}
#[test]
fn namespace_inherited_prefixed1() {
let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client'><message xmlns='jabber:client' /></stream:features>"
.parse().unwrap();
assert_eq!(elem.name(), "features");
assert_eq!(elem.ns(), "http://etherx.jabber.org/streams".to_owned(),);
let child = elem.children().next().unwrap();
assert_eq!(child.name(), "message");
assert_eq!(child.ns(), "jabber:client".to_owned());
}
#[test]
fn namespace_inherited_prefixed2() {
let elem: Element = "<stream xmlns='http://etherx.jabber.org/streams' xmlns:jabber='jabber:client'><jabber:message xmlns:jabber='jabber:client' /></stream>"
.parse().unwrap();
assert_eq!(elem.name(), "stream");
assert_eq!(elem.ns(), "http://etherx.jabber.org/streams".to_owned(),);
let child = elem.children().next().unwrap();
assert_eq!(child.name(), "message");
assert_eq!(child.ns(), "jabber:client".to_owned());
}
#[test]
fn fail_comments() {
let elem: Result<Element, Error> = "<foo xmlns='ns1'><!-- bar --></foo>".parse();
match elem {
Err(Error::NoComments) => (),
_ => panic!(),
};
}
#[test]
fn xml_error() {
match "<a xmlns='ns1'></b>".parse::<Element>() {
Err(crate::error::Error::XmlError(_)) => (),
err => panic!("No or wrong error: {:?}", err),
}
match "<a xmlns='ns1'></".parse::<Element>() {
Err(crate::error::Error::XmlError(_)) => (),
err => panic!("No or wrong error: {:?}", err),
}
}
#[test]
fn invalid_element_error() {
match "<a:b:c>".parse::<Element>() {
Err(crate::error::Error::InvalidElement) => (),
err => panic!("No or wrong error: {:?}", err),
}
}
#[test]
fn missing_namespace_error() {
match "<a/>".parse::<Element>() {
Err(crate::error::Error::MissingNamespace) => (),
err => panic!("No or wrong error: {:?}", err),
}
}