xml/
name.rs

1//! Contains XML qualified names manipulation types and functions.
2//!
3
4use std::fmt;
5use std::str::FromStr;
6
7use 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)]
48pub 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
59impl<'a> From<&'a str> for Name<'a> {
60    fn from(s: &'a str) -> Name<'a> {
61        let mut parts = s.splitn(2, ":").fuse();
62        match (parts.next(), parts.next()) {
63            (Some(name), None) => Name::local(name),
64            (Some(prefix), Some(name)) => Name::prefixed(name, prefix),
65            _ => unreachable!()
66        }
67    }
68}
69
70impl<'a> From<(&'a str, &'a str)> for Name<'a> {
71    fn from((prefix, name): (&'a str, &'a str)) -> Name<'a> {
72        Name::prefixed(name, prefix)
73    }
74}
75
76impl<'a> fmt::Display for Name<'a> {
77    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78        if let Some(namespace) = self.namespace {
79            write!(f, "{{{}}}", namespace)?;
80        }
81
82        if let Some(prefix) = self.prefix {
83            write!(f, "{}:", prefix)?;
84        }
85
86        write!(f, "{}", self.local_name)
87    }
88}
89
90impl<'a> Name<'a> {
91    /// Returns an owned variant of the qualified name.
92    pub fn to_owned(&self) -> OwnedName {
93        OwnedName {
94            local_name: self.local_name.into(),
95            namespace: self.namespace.map(|s| s.into()),
96            prefix: self.prefix.map(|s| s.into())
97        }
98    }
99
100    /// Returns a new `Name` instance representing plain local name.
101    #[inline]
102    pub 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    pub fn prefixed(local_name: &'a str, prefix: &'a str) -> Name<'a> {
113        Name {
114            local_name,
115            namespace: None,
116            prefix: Some(prefix)
117        }
118    }
119
120    /// Returns a new `Name` instance representing a qualified name with or without a prefix and
121    /// with a namespace URI.
122    #[inline]
123    pub fn qualified(local_name: &'a str, namespace: &'a str, prefix: Option<&'a str>) -> Name<'a> {
124        Name {
125            local_name,
126            namespace: Some(namespace),
127            prefix,
128        }
129    }
130
131    /// Returns a correct XML representation of this local name and prefix.
132    ///
133    /// This method is different from the autoimplemented `to_string()` because it does not
134    /// include namespace URI in the result.
135    pub fn to_repr(&self) -> String {
136        self.repr_display().to_string()
137    }
138
139    /// Returns a structure which can be displayed with `std::fmt` machinery to obtain this
140    /// local name and prefix.
141    ///
142    /// This method is needed for efficiency purposes in order not to create unnecessary
143    /// allocations.
144    #[inline]
145    pub fn repr_display(&self) -> ReprDisplay {
146        ReprDisplay(self)
147    }
148
149    /// Returns either a prefix of this name or `namespace::NS_NO_PREFIX` constant.
150    #[inline]
151    pub fn prefix_repr(&self) -> &str {
152        self.prefix.unwrap_or(NS_NO_PREFIX)
153    }
154}
155
156/// A wrapper around `Name` whose `Display` implementation prints the wrapped name as it is
157/// displayed in an XML document.
158pub struct ReprDisplay<'a, 'b:'a>(&'a Name<'b>);
159
160impl<'a, 'b:'a> fmt::Display for ReprDisplay<'a, 'b> {
161    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
162        match self.0.prefix {
163            Some(prefix) => write!(f, "{}:{}", prefix, self.0.local_name),
164            None => write!(f, "{}", self.0.local_name)
165        }
166    }
167}
168
169/// An owned variant of `Name`.
170///
171/// Everything about `Name` applies to this structure as well.
172#[derive(Clone, PartialEq, Eq, Hash, Debug)]
173pub struct OwnedName {
174    /// A local name, e.g. `string` in `xsi:string`.
175    pub local_name: String,
176
177    /// A namespace URI, e.g. `http://www.w3.org/2000/xmlns/`.
178    pub namespace: Option<String>,
179
180    /// A name prefix, e.g. `xsi` in `xsi:string`.
181    pub prefix: Option<String>,
182}
183
184impl fmt::Display for OwnedName {
185    #[inline]
186    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
187        fmt::Display::fmt(&self.borrow(), f)
188    }
189}
190
191impl OwnedName {
192    /// Constructs a borrowed `Name` based on this owned name.
193    pub fn borrow(&self) -> Name {
194        Name {
195            local_name: &*self.local_name,
196            namespace: self.namespace.as_ref().map(|s| &**s),
197            prefix: self.prefix.as_ref().map(|s| &**s),
198        }
199    }
200
201    /// Returns a new `OwnedName` instance representing a plain local name.
202    #[inline]
203    pub fn local<S>(local_name: S) -> OwnedName where S: Into<String> {
204        OwnedName {
205            local_name: local_name.into(),
206            namespace: None,
207            prefix: None,
208        }
209    }
210
211    /// Returns a new `OwnedName` instance representing a qualified name with or without
212    /// a prefix and with a namespace URI.
213    #[inline]
214    pub fn qualified<S1, S2, S3>(local_name: S1, namespace: S2, prefix: Option<S3>) -> OwnedName
215        where S1: Into<String>, S2: Into<String>, S3: Into<String>
216    {
217        OwnedName {
218            local_name: local_name.into(),
219            namespace: Some(namespace.into()),
220            prefix: prefix.map(|v| v.into())
221        }
222    }
223
224    /// Returns an optional prefix by reference, equivalent to `self.borrow().prefix`
225    /// but avoids extra work.
226    #[inline]
227    pub fn prefix_ref(&self) -> Option<&str> {
228        self.prefix.as_ref().map(|s| &**s)
229    }
230
231    /// Returns an optional namespace by reference, equivalen to `self.borrow().namespace`
232    /// but avoids extra work.
233    #[inline]
234    pub fn namespace_ref(&self) -> Option<&str> {
235        self.namespace.as_ref().map(|s| &**s)
236    }
237}
238
239impl<'a> From<Name<'a>> for OwnedName {
240    #[inline]
241    fn from(n: Name<'a>) -> OwnedName {
242        n.to_owned()
243    }
244}
245
246impl FromStr for OwnedName {
247    type Err = ();
248
249    /// Parses the given string slice into a qualified name.
250    ///
251    /// This function, when finishes sucessfully, always return a qualified
252    /// name without a namespace (`name.namespace == None`). It should be filled later
253    /// using proper `NamespaceStack`.
254    ///
255    /// It is supposed that all characters in the argument string are correct
256    /// as defined by the XML specification. No additional checks except a check
257    /// for emptiness are done.
258    fn from_str(s: &str) -> Result<OwnedName, ()> {
259        let mut it = s.split(':');
260
261        let r = match (it.next(), it.next(), it.next()) {
262            (Some(prefix), Some(local_name), None) if !prefix.is_empty() &&
263                                                      !local_name.is_empty() =>
264                Some((local_name.into(), Some(prefix.into()))),
265            (Some(local_name), None, None) if !local_name.is_empty() =>
266                Some((local_name.into(), None)),
267            (_, _, _) => None
268        };
269        r.map(|(local_name, prefix)| OwnedName {
270            local_name,
271            namespace: None,
272            prefix
273        }).ok_or(())
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use super::OwnedName;
280
281    #[test]
282    fn test_owned_name_from_str() {
283        assert_eq!("prefix:name".parse(), Ok(OwnedName {
284            local_name: "name".into(),
285            namespace: None,
286            prefix: Some("prefix".into())
287        }));
288
289        assert_eq!("name".parse(), Ok(OwnedName {
290            local_name: "name".into(),
291            namespace: None,
292            prefix: None
293        }));
294
295        assert_eq!("".parse(), Err::<OwnedName, ()>(()));
296        assert_eq!(":".parse(), Err::<OwnedName, ()>(()));
297        assert_eq!(":a".parse(), Err::<OwnedName, ()>(()));
298        assert_eq!("a:".parse(), Err::<OwnedName, ()>(()));
299        assert_eq!("a:b:c".parse(), Err::<OwnedName, ()>(()));
300    }
301}