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