quoted_string/
iter.rs

1use std::str::Chars;
2use std::iter::Iterator;
3use std::cmp::{ PartialEq };
4
5use error::CoreError;
6use spec::{GeneralQSSpec, ScanAutomaton, PartialCodePoint};
7// this import will become unused in future rust versions
8// but won't be removed for now for supporting current
9// rust versions
10#[allow(warnings)]
11use std::ascii::AsciiExt;
12
13/// Analogous to PartialEq, but with _ascii_ case insensitive equality
14pub trait AsciiCaseInsensitiveEq<Rhs: ?Sized> {
15
16    /// compares this instance with other with a ascii case insensitive comparsion algorithm
17    ///
18    /// Note that this is _ascii_ case insensitivity. Which means this will not work
19    /// well/as expected if the content contain non ascii characters.
20    /// E.g. the upercase of `"ß"` was `"SS"` but by know there is also a
21    /// "ẞ" used in all caps writing.
22    fn eq_ignore_ascii_case(&self, other: &Rhs) -> bool;
23}
24
25/// A iterator over chars of the content represented by the quoted strings (PartialEq<&str>)
26///
27/// It will on the fly (without extra allocation)
28/// remove the surrounding quotes and unquote quoted-pairs
29///
30/// It implements Eq, and PartialEq with str, &str and itself. This
31/// enables to compare a quoted string with a string representing the
32/// content of a quoted string.
33///
34/// While it does not explicity checks for validity of the quoted-string it iterates over
35/// it still might notice that it is invalid as such it returns results (i.e. let the
36/// consumer decide how to handle the error). If you are fine with a panic if the
37/// `ContentChars` iterator was created from a invalid quoted-string, then just
38/// use `.map(|r| r.expect("[BUG] ContentChars was created from malformed quoted-string")`.
39/// (Note that the the only 2two thinks the current implementation does not check are
40///  if a char in a quoted-pair is valid (= is quotable) and it does not call `end_validation`
41///  on the internally used `QuotedValidator`, but thats a implementation detail for now)
42///
43/// # Example
44///
45/// ```
46/// // use your own Spec instead
47/// use quoted_string::test_utils::TestSpec;
48/// use quoted_string::ContentChars;
49/// use quoted_string::AsciiCaseInsensitiveEq;
50///
51/// let quoted_string = r#""ab\"\ c""#;
52/// let cc = ContentChars::<TestSpec>::from_str(quoted_string);
53/// assert_eq!(cc, "ab\" c");
54/// assert!(cc.eq_ignore_ascii_case("AB\" c"));
55/// assert_eq!(cc.collect::<Result<Vec<_>,_>>().unwrap().as_slice(), &[ 'a', 'b', '"', ' ', 'c' ] );
56///
57/// ```
58#[derive(Debug, Clone)]
59pub struct ContentChars<'a, Impl: GeneralQSSpec> {
60    inner: Chars<'a>,
61    automaton: ScanAutomaton<Impl::Parsing>
62}
63
64impl<'s, Impl> ContentChars<'s, Impl>
65    where Impl: GeneralQSSpec
66{
67
68    /// creates a char iterator over the content of a quoted string
69    ///
70    /// the quoted string is _assumed_ to be valid and not explicitely checked for validity
71    /// but because of the way unquoting works a number of error can be detected
72    pub fn from_str(quoted: &'s str) -> Self {
73        ContentChars {
74            inner: quoted.chars(),
75            automaton: ScanAutomaton::<Impl::Parsing>::new()
76        }
77    }
78
79    /// creates a ContentChars iterator from a str and a QuotedValidator
80    ///
81    /// The `partial_quoted_content` is assumed to be a valid quoted string
82    /// without the surrounding `'"'`. It might not be a the complete content
83    /// of a quoted string but if it isn't the q_validator is expected to have
84    /// been used on a chars stripped on the left side (and no more than that).
85    /// Note that it can work with using it on less then all chars but this depends
86    /// on the Spec used. E.g. if any decison of the spec only depends on the current char
87    /// (QuotedValidator is zero-sized) then no char had to be used with it.
88    pub fn from_parts_unchecked(
89        partial_quoted_content: &'s str,
90        automaton: ScanAutomaton<Impl::Parsing>
91    ) -> Self
92    {
93        let inner = partial_quoted_content.chars();
94        ContentChars{ inner, automaton }
95    }
96}
97
98
99impl<'a, Impl> Iterator for ContentChars<'a, Impl>
100    where Impl: GeneralQSSpec
101{
102    type Item = Result<char, CoreError>;
103
104    fn next(&mut self) -> Option<Self::Item> {
105        loop {
106            if let Some(ch) = self.inner.next() {
107                let res = self.automaton.advance(PartialCodePoint::from_code_point(ch as u32));
108                match res {
109                    Err(e) => return Some(Err(e.into())),
110                    Ok(true)  => return Some(Ok(ch)),
111                    Ok(false) => {},
112                }
113            } else {
114                match self.automaton.end() {
115                    Err(e) => return Some(Err(e.into())),
116                    Ok(()) => return None
117                }
118            }
119        }
120    }
121
122    #[inline]
123    fn size_hint(&self) -> (usize, Option<usize>) {
124        self.inner.size_hint()
125    }
126}
127
128
129impl<'a, Spec> PartialEq<str> for ContentChars<'a, Spec>
130    where Spec: GeneralQSSpec
131{
132
133    #[inline]
134    fn eq(&self, other: &str) -> bool {
135        iter_eq(self.clone(), other.chars().map(|ch|Ok(ch)), |l,r|l==r)
136    }
137}
138
139impl<'a, 'b, Spec> PartialEq<ContentChars<'b, Spec>> for &'a str
140    where Spec: GeneralQSSpec
141{
142    #[inline]
143    fn eq(&self, other: &ContentChars<'b, Spec>) -> bool {
144        *other == **self
145    }
146}
147
148impl<'a, 'b, Spec> PartialEq<&'b str> for ContentChars<'a, Spec>
149    where Spec: GeneralQSSpec
150{
151    #[inline]
152    fn eq(&self, other: &&'b str) -> bool {
153        self == *other
154    }
155}
156
157impl<'a, 'b, Spec> PartialEq<ContentChars<'b, Spec>> for ContentChars<'a, Spec>
158    where Spec: GeneralQSSpec
159{
160    #[inline]
161    fn eq(&self, other: &ContentChars<'b, Spec>) -> bool {
162        iter_eq(self.clone(), other.clone(), |l,r|l==r)
163    }
164}
165
166
167
168impl<'a, Spec> AsciiCaseInsensitiveEq<str> for ContentChars<'a, Spec>
169    where Spec: GeneralQSSpec
170{
171    #[inline]
172    fn eq_ignore_ascii_case(&self, other: &str) -> bool {
173        iter_eq(self.clone(), other.chars().map(|ch|Ok(ch)), |l,r| l.eq_ignore_ascii_case(&r))
174    }
175}
176
177impl<'a, 'b, Spec> AsciiCaseInsensitiveEq<ContentChars<'b, Spec>> for ContentChars<'a, Spec>
178    where Spec: GeneralQSSpec
179{
180    #[inline]
181    fn eq_ignore_ascii_case(&self, other: &ContentChars<'b, Spec>) -> bool {
182        iter_eq(self.clone(), other.clone(), |l,r|l.eq_ignore_ascii_case(&r))
183    }
184}
185
186impl<'a, 'b, Spec> AsciiCaseInsensitiveEq<ContentChars<'b, Spec>> for &'a str
187    where Spec: GeneralQSSpec
188{
189    #[inline]
190    fn eq_ignore_ascii_case(&self, other: &ContentChars<'b, Spec>) -> bool {
191        other == *self
192    }
193}
194
195
196
197impl<'a, 'b, Spec> AsciiCaseInsensitiveEq<&'b str> for ContentChars<'a, Spec>
198    where Spec: GeneralQSSpec
199{
200    #[inline]
201    fn eq_ignore_ascii_case(&self, other: &&'b str) -> bool {
202        self == *other
203    }
204}
205
206fn iter_eq<I1, I2, E, FN>(mut left: I1, mut right: I2, cmp: FN) -> bool
207    where I1: Iterator<Item=Result<char, E>>,
208          I2: Iterator<Item=Result<char, E>>, FN: Fn(char, char) -> bool
209{
210    loop {
211        match (left.next(), right.next()) {
212            (None, None) => return true,
213            (Some(Ok(x)), Some(Ok(y))) if cmp(x, y) => (),
214            _ => return false
215        }
216    }
217}
218
219
220
221#[cfg(test)]
222mod test {
223    use test_utils::*;
224    use error::CoreError;
225    use super::{ContentChars, AsciiCaseInsensitiveEq};
226
227    #[test]
228    fn missing_double_quoted() {
229        let mut chars = ContentChars::<TestSpec>::from_str("abcdef");
230        assert_eq!(chars.next().expect("is some").unwrap_err(), CoreError::DoesNotStartWithDQuotes);
231    }
232
233    #[test]
234    fn unnecessary_quoted() {
235        let res = ContentChars::<TestSpec>::from_str("\"abcdef\"");
236        assert_eq!(res.collect::<Result<Vec<_>, _>>().unwrap().as_slice(), &[
237            'a', 'b', 'c' ,'d', 'e', 'f'
238        ])
239    }
240
241    #[test]
242    fn quoted() {
243        let res = ContentChars::<TestSpec>::from_str("\"abc def\"");
244        assert_eq!(res.collect::<Result<Vec<_>, _>>().unwrap().as_slice(), &[
245            'a', 'b', 'c', ' ', 'd', 'e', 'f'
246        ])
247    }
248
249    #[test]
250    fn with_quoted_pair() {
251        let res = ContentChars::<TestSpec>::from_str(r#""abc\" \def""#);
252        assert_eq!(res.collect::<Result<Vec<_>, _>>().unwrap().as_slice(), &[
253            'a', 'b', 'c', '"', ' ', 'd', 'e', 'f'
254        ])
255    }
256
257    #[test]
258    fn strip_non_semantic_ws() {
259        let res = ContentChars::<TestSpec>::from_str("\"abc\n\ndef\"");
260        assert_eq!(res.collect::<Result<Vec<_>, _>>().unwrap().as_slice(), &[
261            'a', 'b', 'c', 'd', 'e', 'f'
262        ])
263    }
264
265    #[test]
266    fn ascii_case_insensitive_eq() {
267        let left = ContentChars::<TestSpec>::from_str(r#""abc""#);
268        let right = ContentChars::<TestSpec>::from_str(r#""aBc""#);
269        assert!(left.eq_ignore_ascii_case(&right))
270    }
271}