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}