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