diff options
Diffstat (limited to '')
-rw-r--r-- | third_party/rust/xml-rs/src/name.rs | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/third_party/rust/xml-rs/src/name.rs b/third_party/rust/xml-rs/src/name.rs new file mode 100644 index 0000000000..a20eae2f10 --- /dev/null +++ b/third_party/rust/xml-rs/src/name.rs @@ -0,0 +1,301 @@ +//! 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<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. + 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<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(|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<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, ()>(())); + } +} |