xml/name.rs
1//! Contains XML qualified names manipulation types and functions.
2
3use std::fmt;
4use std::str::FromStr;
5
6use 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)]
47pub 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
58impl<'a> From<&'a str> for Name<'a> {
59 fn from(s: &'a str) -> Self {
60 if let Some((prefix, name)) = s.split_once(':') {
61 Name::prefixed(name, prefix)
62 } else {
63 Name::local(s)
64 }
65 }
66}
67
68impl<'a> From<(&'a str, &'a str)> for Name<'a> {
69 fn from((prefix, name): (&'a str, &'a str)) -> Self {
70 Name::prefixed(name, prefix)
71 }
72}
73
74impl fmt::Display for Name<'_> {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 if let Some(namespace) = self.namespace {
77 write!(f, "{{{namespace}}}")?;
78 }
79
80 if let Some(prefix) = self.prefix {
81 write!(f, "{prefix}:")?;
82 }
83
84 f.write_str(self.local_name)
85 }
86}
87
88impl<'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(Into::into),
95 prefix: self.prefix.map(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.
163pub struct ReprDisplay<'a, 'b>(&'a Name<'b>);
164
165impl<'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) => 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)]
178pub struct OwnedName {
179 /// A local name, e.g. `string` in `xsi:string`.
180 ///
181 /// Local name is ambiguous, because multiple namespaces can share the same name.
182 /// Always check `namespace` too.
183 pub local_name: String,
184
185 /// A namespace URI, e.g. `http://www.w3.org/2000/xmlns/`.
186 ///
187 /// `None` for default namespace.
188 ///
189 /// Note that in XML attributes don't inherit element's namespace
190 /// and are in the default namespace unless they have a prefix.
191 pub namespace: Option<String>,
192
193 /// Semantically meaningless name prefix, e.g. `xsi` in `xsi:string`.
194 ///
195 /// Prefixes are just a syntactic detail used for decoration or to avoid repetition.
196 /// Only `namespace` matters for identity of the element.
197 pub prefix: Option<String>,
198}
199
200impl PartialEq<(&str, &str)> for OwnedName {
201 /// Compare `(namespaceURI, localName)`. Default namespace is `""`.
202 fn eq(&self, other: &(&str, &str)) -> bool {
203 other.1.eq(&self.local_name) && other.0.eq(self.namespace.as_deref().unwrap_or_default())
204 }
205}
206impl PartialEq<OwnedName> for (&str, &str) {
207 /// Compare `(namespaceURI, localName)`. Default namespace is `""`.
208 fn eq(&self, other: &OwnedName) -> bool {
209 other.eq(self)
210 }
211}
212
213impl fmt::Display for OwnedName {
214 #[inline]
215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216 fmt::Display::fmt(&self.borrow(), f)
217 }
218}
219
220impl OwnedName {
221 /// Constructs a borrowed `Name` based on this owned name.
222 #[must_use]
223 #[inline]
224 pub fn borrow(&self) -> Name<'_> {
225 Name {
226 local_name: &self.local_name,
227 namespace: self.namespace.as_deref(),
228 prefix: self.prefix.as_deref(),
229 }
230 }
231
232 /// Returns a new `OwnedName` instance representing a plain local name.
233 #[inline]
234 pub fn local<S>(local_name: S) -> Self where S: Into<String> {
235 Self {
236 local_name: local_name.into(),
237 namespace: None,
238 prefix: None,
239 }
240 }
241
242 /// Returns a new `OwnedName` instance representing a qualified name with or without
243 /// a prefix and with a namespace URI.
244 #[inline]
245 pub fn qualified<S1, S2, S3>(local_name: S1, namespace: S2, prefix: Option<S3>) -> Self
246 where S1: Into<String>, S2: Into<String>, S3: Into<String>
247 {
248 Self {
249 local_name: local_name.into(),
250 namespace: Some(namespace.into()),
251 prefix: prefix.map(Into::into),
252 }
253 }
254
255 /// Returns an optional prefix by reference, equivalent to `self.borrow().prefix`
256 /// but avoids extra work.
257 #[inline]
258 #[must_use]
259 pub fn prefix_ref(&self) -> Option<&str> {
260 self.prefix.as_deref()
261 }
262
263 /// Returns an optional namespace by reference, equivalen to `self.borrow().namespace`
264 /// but avoids extra work.
265 #[inline]
266 #[must_use]
267 pub fn namespace_ref(&self) -> Option<&str> {
268 self.namespace.as_deref()
269 }
270}
271
272impl<'a> From<Name<'a>> for OwnedName {
273 #[inline]
274 fn from(n: Name<'a>) -> Self {
275 n.to_owned()
276 }
277}
278
279impl FromStr for OwnedName {
280 type Err = ();
281
282 /// Parses the given string slice into a qualified name.
283 ///
284 /// This function, when finishes sucessfully, always return a qualified
285 /// name without a namespace (`name.namespace == None`). It should be filled later
286 /// using proper `NamespaceStack`.
287 ///
288 /// It is supposed that all characters in the argument string are correct
289 /// as defined by the XML specification. No additional checks except a check
290 /// for emptiness are done.
291 fn from_str(s: &str) -> Result<Self, ()> {
292 let mut it = s.split(':');
293
294 let r = match (it.next(), it.next(), it.next()) {
295 (Some(prefix), Some(local_name), None) if !prefix.is_empty() &&
296 !local_name.is_empty() =>
297 Some((local_name.into(), Some(prefix.into()))),
298 (Some(local_name), None, None) if !local_name.is_empty() =>
299 Some((local_name.into(), None)),
300 (_, _, _) => None
301 };
302 r.map(|(local_name, prefix)| Self {
303 local_name,
304 namespace: None,
305 prefix
306 }).ok_or(())
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::OwnedName;
313
314 #[test]
315 fn test_owned_name_from_str() {
316 assert_eq!("prefix:name".parse(), Ok(OwnedName {
317 local_name: "name".into(),
318 namespace: None,
319 prefix: Some("prefix".into())
320 }));
321
322 assert_eq!("name".parse(), Ok(OwnedName {
323 local_name: "name".into(),
324 namespace: None,
325 prefix: None
326 }));
327
328 assert_eq!("".parse(), Err::<OwnedName, ()>(()));
329 assert_eq!(":".parse(), Err::<OwnedName, ()>(()));
330 assert_eq!(":a".parse(), Err::<OwnedName, ()>(()));
331 assert_eq!("a:".parse(), Err::<OwnedName, ()>(()));
332 assert_eq!("a:b:c".parse(), Err::<OwnedName, ()>(()));
333 }
334}