//! Contains XML qualified names manipulation types and functions. //! use std::fmt; use std::str::FromStr; use namespace::NS_NO_PREFIX; /// Represents a qualified XML name. /// /// A qualified name always consists at least of a local name. It can optionally contain /// a prefix; when reading an XML document, if it contains a prefix, it must also contain a /// namespace URI, but this is not enforced statically; see below. The name can contain a /// namespace without a prefix; in that case a default, empty prefix is assumed. /// /// When writing XML documents, it is possible to omit the namespace URI, leaving only /// the prefix. In this case the writer will check that the specifed prefix is bound to some /// URI in the current namespace context. If both prefix and namespace URI are specified, /// it is checked that the current namespace context contains this exact correspondence /// between prefix and namespace URI. /// /// # Prefixes and URIs /// /// A qualified name with a prefix must always contain a proper namespace URI --- names with /// a prefix but without a namespace associated with that prefix are meaningless. However, /// it is impossible to obtain proper namespace URI by a prefix without a context, and such /// context is only available when parsing a document (or it can be constructed manually /// when writing a document). Tying a name to a context statically seems impractical. This /// may change in future, though. /// /// # Conversions /// /// `Name` implements some `From` instances for conversion from strings and tuples. For example: /// /// ```rust /// # use xml::name::Name; /// let n1: Name = "p:some-name".into(); /// let n2: Name = ("p", "some-name").into(); /// /// assert_eq!(n1, n2); /// assert_eq!(n1.local_name, "some-name"); /// assert_eq!(n1.prefix, Some("p")); /// assert!(n1.namespace.is_none()); /// ``` /// /// This is added to support easy specification of XML elements when writing XML documents. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct Name<'a> { /// A local name, e.g. `string` in `xsi:string`. pub local_name: &'a str, /// A namespace URI, e.g. `http://www.w3.org/2000/xmlns/`. pub namespace: Option<&'a str>, /// A name prefix, e.g. `xsi` in `xsi:string`. pub prefix: Option<&'a str> } impl<'a> From<&'a str> for Name<'a> { fn from(s: &'a str) -> Name<'a> { let mut parts = s.splitn(2, ":").fuse(); match (parts.next(), parts.next()) { (Some(name), None) => Name::local(name), (Some(prefix), Some(name)) => Name::prefixed(name, prefix), _ => unreachable!() } } } impl<'a> From<(&'a str, &'a str)> for Name<'a> { fn from((prefix, name): (&'a str, &'a str)) -> Name<'a> { Name::prefixed(name, prefix) } } impl<'a> fmt::Display for Name<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(namespace) = self.namespace { write!(f, "{{{}}}", namespace)?; } if let Some(prefix) = self.prefix { write!(f, "{}:", prefix)?; } write!(f, "{}", self.local_name) } } impl<'a> Name<'a> { /// Returns an owned variant of the qualified name. pub fn to_owned(&self) -> OwnedName { OwnedName { local_name: self.local_name.into(), namespace: self.namespace.map(|s| s.into()), prefix: self.prefix.map(|s| s.into()) } } /// Returns a new `Name` instance representing plain local name. #[inline] pub fn local(local_name: &str) -> Name { Name { local_name, prefix: None, namespace: None } } /// Returns a new `Name` instance with the given local name and prefix. #[inline] pub fn prefixed(local_name: &'a str, prefix: &'a str) -> Name<'a> { Name { local_name, namespace: None, prefix: Some(prefix) } } /// Returns a new `Name` instance representing a qualified name with or without a prefix and /// with a namespace URI. #[inline] pub fn qualified(local_name: &'a str, namespace: &'a str, prefix: Option<&'a str>) -> Name<'a> { Name { local_name, namespace: Some(namespace), prefix, } } /// Returns a correct XML representation of this local name and prefix. /// /// This method is different from the autoimplemented `to_string()` because it does not /// include namespace URI in the result. pub fn to_repr(&self) -> String { self.repr_display().to_string() } /// Returns a structure which can be displayed with `std::fmt` machinery to obtain this /// local name and prefix. /// /// This method is needed for efficiency purposes in order not to create unnecessary /// allocations. #[inline] pub fn repr_display(&self) -> ReprDisplay { ReprDisplay(self) } /// Returns either a prefix of this name or `namespace::NS_NO_PREFIX` constant. #[inline] pub fn prefix_repr(&self) -> &str { self.prefix.unwrap_or(NS_NO_PREFIX) } } /// A wrapper around `Name` whose `Display` implementation prints the wrapped name as it is /// displayed in an XML document. pub struct ReprDisplay<'a, 'b:'a>(&'a Name<'b>); impl<'a, 'b:'a> fmt::Display for ReprDisplay<'a, 'b> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.0.prefix { Some(prefix) => write!(f, "{}:{}", prefix, self.0.local_name), None => write!(f, "{}", self.0.local_name) } } } /// An owned variant of `Name`. /// /// Everything about `Name` applies to this structure as well. #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub struct OwnedName { /// A local name, e.g. `string` in `xsi:string`. pub local_name: String, /// A namespace URI, e.g. `http://www.w3.org/2000/xmlns/`. pub namespace: Option, /// A name prefix, e.g. `xsi` in `xsi:string`. pub prefix: Option, } impl fmt::Display for OwnedName { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.borrow(), f) } } impl OwnedName { /// Constructs a borrowed `Name` based on this owned name. pub fn borrow(&self) -> Name { Name { local_name: &*self.local_name, namespace: self.namespace.as_ref().map(|s| &**s), prefix: self.prefix.as_ref().map(|s| &**s), } } /// Returns a new `OwnedName` instance representing a plain local name. #[inline] pub fn local(local_name: S) -> OwnedName where S: Into { OwnedName { local_name: local_name.into(), namespace: None, prefix: None, } } /// Returns a new `OwnedName` instance representing a qualified name with or without /// a prefix and with a namespace URI. #[inline] pub fn qualified(local_name: S1, namespace: S2, prefix: Option) -> OwnedName where S1: Into, S2: Into, S3: Into { OwnedName { local_name: local_name.into(), namespace: Some(namespace.into()), prefix: prefix.map(|v| v.into()) } } /// Returns an optional prefix by reference, equivalent to `self.borrow().prefix` /// but avoids extra work. #[inline] pub fn prefix_ref(&self) -> Option<&str> { self.prefix.as_ref().map(|s| &**s) } /// Returns an optional namespace by reference, equivalen to `self.borrow().namespace` /// but avoids extra work. #[inline] pub fn namespace_ref(&self) -> Option<&str> { self.namespace.as_ref().map(|s| &**s) } } impl<'a> From> for OwnedName { #[inline] fn from(n: Name<'a>) -> OwnedName { n.to_owned() } } impl FromStr for OwnedName { type Err = (); /// Parses the given string slice into a qualified name. /// /// This function, when finishes sucessfully, always return a qualified /// name without a namespace (`name.namespace == None`). It should be filled later /// using proper `NamespaceStack`. /// /// It is supposed that all characters in the argument string are correct /// as defined by the XML specification. No additional checks except a check /// for emptiness are done. fn from_str(s: &str) -> Result { let mut it = s.split(':'); let r = match (it.next(), it.next(), it.next()) { (Some(prefix), Some(local_name), None) if !prefix.is_empty() && !local_name.is_empty() => Some((local_name.into(), Some(prefix.into()))), (Some(local_name), None, None) if !local_name.is_empty() => Some((local_name.into(), None)), (_, _, _) => None }; r.map(|(local_name, prefix)| OwnedName { local_name, namespace: None, prefix }).ok_or(()) } } #[cfg(test)] mod tests { use super::OwnedName; #[test] fn test_owned_name_from_str() { assert_eq!("prefix:name".parse(), Ok(OwnedName { local_name: "name".into(), namespace: None, prefix: Some("prefix".into()) })); assert_eq!("name".parse(), Ok(OwnedName { local_name: "name".into(), namespace: None, prefix: None })); assert_eq!("".parse(), Err::(())); assert_eq!(":".parse(), Err::(())); assert_eq!(":a".parse(), Err::(())); assert_eq!("a:".parse(), Err::(())); assert_eq!("a:b:c".parse(), Err::(())); } }