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 | |