| 1 | //! Contains XML qualified names manipulation types and functions. |
| 2 | |
| 3 | use std::fmt; |
| 4 | use std::str::FromStr; |
| 5 | |
| 6 | use crate::namespace::NS_NO_PREFIX; |
| 7 | |
| 8 | /// Represents a qualified XML name. |
| 9 | /// |
| 10 | /// A qualified name always consists at least of a local name. It can optionally contain |
| 11 | /// a prefix; when reading an XML document, if it contains a prefix, it must also contain a |
| 12 | /// namespace URI, but this is not enforced statically; see below. The name can contain a |
| 13 | /// namespace without a prefix; in that case a default, empty prefix is assumed. |
| 14 | /// |
| 15 | /// When writing XML documents, it is possible to omit the namespace URI, leaving only |
| 16 | /// the prefix. In this case the writer will check that the specifed prefix is bound to some |
| 17 | /// URI in the current namespace context. If both prefix and namespace URI are specified, |
| 18 | /// it is checked that the current namespace context contains this exact correspondence |
| 19 | /// between prefix and namespace URI. |
| 20 | /// |
| 21 | /// # Prefixes and URIs |
| 22 | /// |
| 23 | /// A qualified name with a prefix must always contain a proper namespace URI --- names with |
| 24 | /// a prefix but without a namespace associated with that prefix are meaningless. However, |
| 25 | /// it is impossible to obtain proper namespace URI by a prefix without a context, and such |
| 26 | /// context is only available when parsing a document (or it can be constructed manually |
| 27 | /// when writing a document). Tying a name to a context statically seems impractical. This |
| 28 | /// may change in future, though. |
| 29 | /// |
| 30 | /// # Conversions |
| 31 | /// |
| 32 | /// `Name` implements some `From` instances for conversion from strings and tuples. For example: |
| 33 | /// |
| 34 | /// ```rust |
| 35 | /// # use xml::name::Name; |
| 36 | /// let n1: Name = "p:some-name" .into(); |
| 37 | /// let n2: Name = ("p" , "some-name" ).into(); |
| 38 | /// |
| 39 | /// assert_eq!(n1, n2); |
| 40 | /// assert_eq!(n1.local_name, "some-name" ); |
| 41 | /// assert_eq!(n1.prefix, Some("p" )); |
| 42 | /// assert!(n1.namespace.is_none()); |
| 43 | /// ``` |
| 44 | /// |
| 45 | /// This is added to support easy specification of XML elements when writing XML documents. |
| 46 | #[derive (Copy, Clone, PartialEq, Eq, Hash, Debug)] |
| 47 | pub struct Name<'a> { |
| 48 | /// A local name, e.g. `string` in `xsi:string`. |
| 49 | pub local_name: &'a str, |
| 50 | |
| 51 | /// A namespace URI, e.g. `http://www.w3.org/2000/xmlns/`. |
| 52 | pub namespace: Option<&'a str>, |
| 53 | |
| 54 | /// A name prefix, e.g. `xsi` in `xsi:string`. |
| 55 | pub prefix: Option<&'a str>, |
| 56 | } |
| 57 | |
| 58 | impl<'a> From<&'a str> for Name<'a> { |
| 59 | fn from(s: &'a str) -> Self { |
| 60 | if let Some((prefix: &str, name: &str)) = s.split_once(delimiter:':' ) { |
| 61 | Name::prefixed(name, prefix) |
| 62 | } else { |
| 63 | Name::local(local_name:s) |
| 64 | } |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | impl<'a> From<(&'a str, &'a str)> for Name<'a> { |
| 69 | fn from((prefix: &'a str, name: &'a str): (&'a str, &'a str)) -> Self { |
| 70 | Name::prefixed(name, prefix) |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | impl fmt::Display for Name<'_> { |
| 75 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 76 | if let Some(namespace: &str) = self.namespace { |
| 77 | write!(f, " {{{namespace}}}" )?; |
| 78 | } |
| 79 | |
| 80 | if let Some(prefix: &str) = self.prefix { |
| 81 | write!(f, " {prefix}:" )?; |
| 82 | } |
| 83 | |
| 84 | f.write_str(self.local_name) |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | impl<'a> Name<'a> { |
| 89 | /// Returns an owned variant of the qualified name. |
| 90 | #[must_use ] |
| 91 | pub fn to_owned(&self) -> OwnedName { |
| 92 | OwnedName { |
| 93 | local_name: self.local_name.into(), |
| 94 | namespace: self.namespace.map(std::convert::Into::into), |
| 95 | prefix: self.prefix.map(std::convert::Into::into), |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | /// Returns a new `Name` instance representing plain local name. |
| 100 | #[inline ] |
| 101 | #[must_use ] |
| 102 | pub const fn local(local_name: &str) -> Name<'_> { |
| 103 | Name { |
| 104 | local_name, |
| 105 | prefix: None, |
| 106 | namespace: None, |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | /// Returns a new `Name` instance with the given local name and prefix. |
| 111 | #[inline ] |
| 112 | #[must_use ] |
| 113 | pub const fn prefixed(local_name: &'a str, prefix: &'a str) -> Self { |
| 114 | Name { |
| 115 | local_name, |
| 116 | namespace: None, |
| 117 | prefix: Some(prefix), |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | /// Returns a new `Name` instance representing a qualified name with or without a prefix and |
| 122 | /// with a namespace URI. |
| 123 | #[inline ] |
| 124 | #[must_use ] |
| 125 | pub const fn qualified(local_name: &'a str, namespace: &'a str, prefix: Option<&'a str>) -> Self { |
| 126 | Name { |
| 127 | local_name, |
| 128 | namespace: Some(namespace), |
| 129 | prefix, |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | /// Returns a correct XML representation of this local name and prefix. |
| 134 | /// |
| 135 | /// This method is different from the autoimplemented `to_string()` because it does not |
| 136 | /// include namespace URI in the result. |
| 137 | #[must_use ] |
| 138 | pub fn to_repr(&self) -> String { |
| 139 | self.repr_display().to_string() |
| 140 | } |
| 141 | |
| 142 | /// Returns a structure which can be displayed with `std::fmt` machinery to obtain this |
| 143 | /// local name and prefix. |
| 144 | /// |
| 145 | /// This method is needed for efficiency purposes in order not to create unnecessary |
| 146 | /// allocations. |
| 147 | #[inline ] |
| 148 | #[must_use ] |
| 149 | pub const fn repr_display(&self) -> ReprDisplay<'_, '_> { |
| 150 | ReprDisplay(self) |
| 151 | } |
| 152 | |
| 153 | /// Returns either a prefix of this name or `namespace::NS_NO_PREFIX` constant. |
| 154 | #[inline ] |
| 155 | #[must_use ] |
| 156 | pub fn prefix_repr(&self) -> &str { |
| 157 | self.prefix.unwrap_or(NS_NO_PREFIX) |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | /// A wrapper around `Name` whose `Display` implementation prints the wrapped name as it is |
| 162 | /// displayed in an XML document. |
| 163 | pub struct ReprDisplay<'a, 'b>(&'a Name<'b>); |
| 164 | |
| 165 | impl<'a, 'b: 'a> fmt::Display for ReprDisplay<'a, 'b> { |
| 166 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 167 | match self.0.prefix { |
| 168 | Some(prefix: &'b str) => write!(f, " {}: {}" , prefix, self.0.local_name), |
| 169 | None => self.0.local_name.fmt(f), |
| 170 | } |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | /// An owned variant of `Name`. |
| 175 | /// |
| 176 | /// Everything about `Name` applies to this structure as well. |
| 177 | #[derive (Clone, PartialEq, Eq, Hash, Debug)] |
| 178 | pub struct OwnedName { |
| 179 | /// A local name, e.g. `string` in `xsi:string`. |
| 180 | pub local_name: String, |
| 181 | |
| 182 | /// A namespace URI, e.g. `http://www.w3.org/2000/xmlns/`. |
| 183 | pub namespace: Option<String>, |
| 184 | |
| 185 | /// A name prefix, e.g. `xsi` in `xsi:string`. |
| 186 | pub prefix: Option<String>, |
| 187 | } |
| 188 | |
| 189 | impl fmt::Display for OwnedName { |
| 190 | #[inline ] |
| 191 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 192 | fmt::Display::fmt(&self.borrow(), f) |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | impl OwnedName { |
| 197 | /// Constructs a borrowed `Name` based on this owned name. |
| 198 | #[must_use ] |
| 199 | #[inline ] |
| 200 | pub fn borrow(&self) -> Name<'_> { |
| 201 | Name { |
| 202 | local_name: &self.local_name, |
| 203 | namespace: self.namespace.as_deref(), |
| 204 | prefix: self.prefix.as_deref(), |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | /// Returns a new `OwnedName` instance representing a plain local name. |
| 209 | #[inline ] |
| 210 | pub fn local<S>(local_name: S) -> Self where S: Into<String> { |
| 211 | Self { |
| 212 | local_name: local_name.into(), |
| 213 | namespace: None, |
| 214 | prefix: None, |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | /// Returns a new `OwnedName` instance representing a qualified name with or without |
| 219 | /// a prefix and with a namespace URI. |
| 220 | #[inline ] |
| 221 | pub fn qualified<S1, S2, S3>(local_name: S1, namespace: S2, prefix: Option<S3>) -> Self |
| 222 | where S1: Into<String>, S2: Into<String>, S3: Into<String> |
| 223 | { |
| 224 | Self { |
| 225 | local_name: local_name.into(), |
| 226 | namespace: Some(namespace.into()), |
| 227 | prefix: prefix.map(std::convert::Into::into), |
| 228 | } |
| 229 | } |
| 230 | |
| 231 | /// Returns an optional prefix by reference, equivalent to `self.borrow().prefix` |
| 232 | /// but avoids extra work. |
| 233 | #[inline ] |
| 234 | #[must_use ] |
| 235 | pub fn prefix_ref(&self) -> Option<&str> { |
| 236 | self.prefix.as_deref() |
| 237 | } |
| 238 | |
| 239 | /// Returns an optional namespace by reference, equivalen to `self.borrow().namespace` |
| 240 | /// but avoids extra work. |
| 241 | #[inline ] |
| 242 | #[must_use ] |
| 243 | pub fn namespace_ref(&self) -> Option<&str> { |
| 244 | self.namespace.as_deref() |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | impl<'a> From<Name<'a>> for OwnedName { |
| 249 | #[inline ] |
| 250 | fn from(n: Name<'a>) -> Self { |
| 251 | n.to_owned() |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | impl FromStr for OwnedName { |
| 256 | type Err = (); |
| 257 | |
| 258 | /// Parses the given string slice into a qualified name. |
| 259 | /// |
| 260 | /// This function, when finishes sucessfully, always return a qualified |
| 261 | /// name without a namespace (`name.namespace == None`). It should be filled later |
| 262 | /// using proper `NamespaceStack`. |
| 263 | /// |
| 264 | /// It is supposed that all characters in the argument string are correct |
| 265 | /// as defined by the XML specification. No additional checks except a check |
| 266 | /// for emptiness are done. |
| 267 | fn from_str(s: &str) -> Result<Self, ()> { |
| 268 | let mut it = s.split(':' ); |
| 269 | |
| 270 | let r = match (it.next(), it.next(), it.next()) { |
| 271 | (Some(prefix), Some(local_name), None) if !prefix.is_empty() && |
| 272 | !local_name.is_empty() => |
| 273 | Some((local_name.into(), Some(prefix.into()))), |
| 274 | (Some(local_name), None, None) if !local_name.is_empty() => |
| 275 | Some((local_name.into(), None)), |
| 276 | (_, _, _) => None |
| 277 | }; |
| 278 | r.map(|(local_name, prefix)| Self { |
| 279 | local_name, |
| 280 | namespace: None, |
| 281 | prefix |
| 282 | }).ok_or(()) |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | #[cfg (test)] |
| 287 | mod tests { |
| 288 | use super::OwnedName; |
| 289 | |
| 290 | #[test ] |
| 291 | fn test_owned_name_from_str() { |
| 292 | assert_eq!("prefix:name" .parse(), Ok(OwnedName { |
| 293 | local_name: "name" .into(), |
| 294 | namespace: None, |
| 295 | prefix: Some("prefix" .into()) |
| 296 | })); |
| 297 | |
| 298 | assert_eq!("name" .parse(), Ok(OwnedName { |
| 299 | local_name: "name" .into(), |
| 300 | namespace: None, |
| 301 | prefix: None |
| 302 | })); |
| 303 | |
| 304 | assert_eq!("" .parse(), Err::<OwnedName, ()>(())); |
| 305 | assert_eq!(":" .parse(), Err::<OwnedName, ()>(())); |
| 306 | assert_eq!(":a" .parse(), Err::<OwnedName, ()>(())); |
| 307 | assert_eq!("a:" .parse(), Err::<OwnedName, ()>(())); |
| 308 | assert_eq!("a:b:c" .parse(), Err::<OwnedName, ()>(())); |
| 309 | } |
| 310 | } |
| 311 | |