| //! Contains XML qualified names manipulation types and functions. |
| //! |
| |
| use std::fmt; |
| use std::str::FromStr; |
| |
| use crate::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}:")?; |
| } |
| |
| f.write_str(self.local_name) |
| } |
| } |
| |
| impl<'a> Name<'a> { |
| /// Returns an owned variant of the qualified name. |
| #[must_use] |
| pub fn to_owned(&self) -> OwnedName { |
| OwnedName { |
| local_name: self.local_name.into(), |
| namespace: self.namespace.map(std::convert::Into::into), |
| prefix: self.prefix.map(std::convert::Into::into), |
| } |
| } |
| |
| /// Returns a new `Name` instance representing plain local name. |
| #[inline] |
| #[must_use] |
| 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] |
| #[must_use] |
| 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] |
| #[must_use] |
| 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. |
| #[must_use] |
| 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] |
| #[must_use] |
| pub fn repr_display(&self) -> ReprDisplay<'_, '_> { |
| ReprDisplay(self) |
| } |
| |
| /// Returns either a prefix of this name or `namespace::NS_NO_PREFIX` constant. |
| #[inline] |
| #[must_use] |
| 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 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 => self.0.local_name.fmt(f), |
| } |
| } |
| } |
| |
| /// 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<String>, |
| |
| /// A name prefix, e.g. `xsi` in `xsi:string`. |
| pub prefix: Option<String>, |
| } |
| |
| 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. |
| #[must_use] |
| #[inline] |
| pub fn borrow(&self) -> Name<'_> { |
| Name { |
| local_name: &self.local_name, |
| namespace: self.namespace.as_deref(), |
| prefix: self.prefix.as_deref(), |
| } |
| } |
| |
| /// Returns a new `OwnedName` instance representing a plain local name. |
| #[inline] |
| pub fn local<S>(local_name: S) -> OwnedName where S: Into<String> { |
| 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<S1, S2, S3>(local_name: S1, namespace: S2, prefix: Option<S3>) -> OwnedName |
| where S1: Into<String>, S2: Into<String>, S3: Into<String> |
| { |
| OwnedName { |
| local_name: local_name.into(), |
| namespace: Some(namespace.into()), |
| prefix: prefix.map(std::convert::Into::into), |
| } |
| } |
| |
| /// Returns an optional prefix by reference, equivalent to `self.borrow().prefix` |
| /// but avoids extra work. |
| #[inline] |
| #[must_use] |
| pub fn prefix_ref(&self) -> Option<&str> { |
| self.prefix.as_deref() |
| } |
| |
| /// Returns an optional namespace by reference, equivalen to `self.borrow().namespace` |
| /// but avoids extra work. |
| #[inline] |
| #[must_use] |
| pub fn namespace_ref(&self) -> Option<&str> { |
| self.namespace.as_deref() |
| } |
| } |
| |
| impl<'a> From<Name<'a>> 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<OwnedName, ()> { |
| 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::<OwnedName, ()>(())); |
| assert_eq!(":".parse(), Err::<OwnedName, ()>(())); |
| assert_eq!(":a".parse(), Err::<OwnedName, ()>(())); |
| assert_eq!("a:".parse(), Err::<OwnedName, ()>(())); |
| assert_eq!("a:b:c".parse(), Err::<OwnedName, ()>(())); |
| } |
| } |