2020-01-26 12:11:57 +00:00
|
|
|
use core::iter::FusedIterator;
|
2019-09-14 09:31:16 +00:00
|
|
|
|
2020-02-06 16:02:44 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2020-01-26 12:11:57 +00:00
|
|
|
pub(crate) struct AttributePairs<'a> {
|
|
|
|
string: &'a str,
|
|
|
|
index: usize,
|
2019-09-14 09:31:16 +00:00
|
|
|
}
|
2018-02-11 06:10:52 +00:00
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
impl<'a> AttributePairs<'a> {
|
|
|
|
pub const fn new(string: &'a str) -> Self { Self { string, index: 0 } }
|
2019-09-14 09:31:16 +00:00
|
|
|
}
|
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
impl<'a> Iterator for AttributePairs<'a> {
|
|
|
|
type Item = (&'a str, &'a str);
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
2020-03-16 10:17:52 +00:00
|
|
|
// return `None`, if there are no more bytes
|
2020-01-26 12:11:57 +00:00
|
|
|
self.string.as_bytes().get(self.index + 1)?;
|
|
|
|
|
|
|
|
let key = {
|
|
|
|
// the position in the string:
|
|
|
|
let start = self.index;
|
|
|
|
// the key ends at an `=`:
|
2020-04-18 12:21:51 +00:00
|
|
|
let end = self.string[self.index..]
|
2020-03-16 10:17:52 +00:00
|
|
|
.char_indices()
|
2020-04-18 12:21:51 +00:00
|
|
|
.find_map(|(i, c)| if c == '=' { Some(i) } else { None })?
|
|
|
|
+ self.index;
|
2020-01-26 12:11:57 +00:00
|
|
|
|
2020-03-16 10:17:52 +00:00
|
|
|
// advance the index to the char after the end of the key (to skip the `=`)
|
|
|
|
// NOTE: it is okay to add 1 to the index, because an `=` is exactly 1 byte.
|
2020-01-26 12:11:57 +00:00
|
|
|
self.index = end + 1;
|
|
|
|
|
2020-04-22 07:54:48 +00:00
|
|
|
&self.string[start..end]
|
2020-01-26 12:11:57 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let value = {
|
|
|
|
let start = self.index;
|
|
|
|
|
|
|
|
// find the end of the value by searching for `,`.
|
|
|
|
// it should ignore `,` that are inside double quotes.
|
|
|
|
let mut inside_quotes = false;
|
2020-03-16 10:17:52 +00:00
|
|
|
|
|
|
|
let end = {
|
|
|
|
let mut result = self.string.len();
|
|
|
|
|
2020-04-18 12:21:51 +00:00
|
|
|
for (i, c) in self.string[self.index..].char_indices() {
|
2020-03-16 10:17:52 +00:00
|
|
|
// if a quote is encountered
|
|
|
|
if c == '"' {
|
|
|
|
// update variable
|
|
|
|
inside_quotes = !inside_quotes;
|
|
|
|
// terminate if a comma is encountered, which is not in a
|
|
|
|
// quote
|
|
|
|
} else if c == ',' && !inside_quotes {
|
|
|
|
// move the index past the comma
|
|
|
|
self.index += 1;
|
|
|
|
// the result is the index of the comma (comma is not included in the
|
|
|
|
// resulting string)
|
2020-04-18 12:21:51 +00:00
|
|
|
result = i + self.index - 1;
|
2020-03-16 10:17:52 +00:00
|
|
|
break;
|
|
|
|
}
|
2020-01-26 12:11:57 +00:00
|
|
|
}
|
2020-03-16 10:17:52 +00:00
|
|
|
|
|
|
|
result
|
|
|
|
};
|
2019-09-14 09:31:16 +00:00
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
self.index += end;
|
2020-03-16 10:17:52 +00:00
|
|
|
self.index -= start;
|
2019-09-14 09:31:16 +00:00
|
|
|
|
2020-04-22 07:54:48 +00:00
|
|
|
&self.string[start..end]
|
2020-01-26 12:11:57 +00:00
|
|
|
};
|
2019-09-15 14:45:43 +00:00
|
|
|
|
2020-04-18 12:21:51 +00:00
|
|
|
Some((key, value))
|
2018-02-11 06:10:52 +00:00
|
|
|
}
|
2019-09-13 14:06:52 +00:00
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
|
|
let mut remaining = 0;
|
2018-02-11 06:10:52 +00:00
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
// each `=` in the remaining str is an iteration
|
|
|
|
// this also ignores `=` inside quotes!
|
|
|
|
let mut inside_quotes = false;
|
2020-03-16 10:17:52 +00:00
|
|
|
|
2020-04-18 12:21:51 +00:00
|
|
|
for (_, c) in self.string[self.index..].char_indices() {
|
2020-03-16 10:17:52 +00:00
|
|
|
if c == '=' && !inside_quotes {
|
2020-01-26 12:11:57 +00:00
|
|
|
remaining += 1;
|
2020-03-16 10:17:52 +00:00
|
|
|
} else if c == '"' {
|
2019-10-05 14:08:03 +00:00
|
|
|
inside_quotes = !inside_quotes;
|
2019-09-14 09:31:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
(remaining, Some(remaining))
|
|
|
|
}
|
2018-02-11 06:10:52 +00:00
|
|
|
}
|
2018-02-18 14:36:52 +00:00
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
impl<'a> ExactSizeIterator for AttributePairs<'a> {}
|
|
|
|
impl<'a> FusedIterator for AttributePairs<'a> {}
|
|
|
|
|
2018-02-18 14:36:52 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
2019-10-08 13:42:33 +00:00
|
|
|
use pretty_assertions::assert_eq;
|
2018-02-18 14:36:52 +00:00
|
|
|
|
|
|
|
#[test]
|
2020-01-26 12:11:57 +00:00
|
|
|
fn test_attributes() {
|
|
|
|
let mut attributes = AttributePairs::new("KEY=VALUE,PAIR=YES");
|
2020-03-16 10:17:52 +00:00
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
assert_eq!((2, Some(2)), attributes.size_hint());
|
2020-03-16 10:17:52 +00:00
|
|
|
assert_eq!(attributes.next(), Some(("KEY", "VALUE")));
|
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
assert_eq!((1, Some(1)), attributes.size_hint());
|
2020-03-16 10:17:52 +00:00
|
|
|
assert_eq!(attributes.next(), Some(("PAIR", "YES")));
|
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
assert_eq!((0, Some(0)), attributes.size_hint());
|
2020-03-16 10:17:52 +00:00
|
|
|
assert_eq!(attributes.next(), None);
|
2020-01-26 12:11:57 +00:00
|
|
|
|
|
|
|
let mut attributes = AttributePairs::new("garbage");
|
|
|
|
assert_eq!((0, Some(0)), attributes.size_hint());
|
2020-03-16 10:17:52 +00:00
|
|
|
assert_eq!(attributes.next(), None);
|
2020-01-26 12:11:57 +00:00
|
|
|
|
|
|
|
let mut attributes = AttributePairs::new("KEY=,=VALUE,=,");
|
2020-03-16 10:17:52 +00:00
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
assert_eq!((3, Some(3)), attributes.size_hint());
|
2020-03-16 10:17:52 +00:00
|
|
|
assert_eq!(attributes.next(), Some(("KEY", "")));
|
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
assert_eq!((2, Some(2)), attributes.size_hint());
|
2020-03-16 10:17:52 +00:00
|
|
|
assert_eq!(attributes.next(), Some(("", "VALUE")));
|
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
assert_eq!((1, Some(1)), attributes.size_hint());
|
2020-03-16 10:17:52 +00:00
|
|
|
assert_eq!(attributes.next(), Some(("", "")));
|
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
assert_eq!((0, Some(0)), attributes.size_hint());
|
2020-03-16 10:17:52 +00:00
|
|
|
assert_eq!(attributes.next(), None);
|
2020-01-26 12:11:57 +00:00
|
|
|
|
|
|
|
// test quotes:
|
|
|
|
let mut attributes = AttributePairs::new("KEY=\"VALUE,\",");
|
2020-03-16 10:17:52 +00:00
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
assert_eq!((1, Some(1)), attributes.size_hint());
|
2020-03-16 10:17:52 +00:00
|
|
|
assert_eq!(attributes.next(), Some(("KEY", "\"VALUE,\"")));
|
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
assert_eq!((0, Some(0)), attributes.size_hint());
|
2020-03-16 10:17:52 +00:00
|
|
|
assert_eq!(attributes.next(), None);
|
2020-01-26 12:11:57 +00:00
|
|
|
|
|
|
|
// test with chars, that are larger, than 1 byte
|
2020-03-16 10:17:52 +00:00
|
|
|
let mut attributes = AttributePairs::new(concat!(
|
|
|
|
"LANGUAGE=\"fre\",",
|
|
|
|
"NAME=\"Français\",",
|
|
|
|
"AUTOSELECT=YES"
|
|
|
|
));
|
|
|
|
|
|
|
|
assert_eq!(attributes.next(), Some(("LANGUAGE", "\"fre\"")));
|
|
|
|
assert_eq!(attributes.next(), Some(("NAME", "\"Français\"")));
|
|
|
|
assert_eq!(attributes.next(), Some(("AUTOSELECT", "YES")));
|
2018-02-18 14:36:52 +00:00
|
|
|
}
|
2019-09-22 18:33:40 +00:00
|
|
|
|
|
|
|
#[test]
|
2020-01-26 12:11:57 +00:00
|
|
|
fn test_parser() {
|
|
|
|
let mut pairs = AttributePairs::new("FOO=BAR,BAR=\"baz,qux\",ABC=12.3");
|
2019-09-22 18:33:40 +00:00
|
|
|
|
2020-01-26 12:11:57 +00:00
|
|
|
assert_eq!(pairs.next(), Some(("FOO", "BAR")));
|
|
|
|
assert_eq!(pairs.next(), Some(("BAR", "\"baz,qux\"")));
|
|
|
|
assert_eq!(pairs.next(), Some(("ABC", "12.3")));
|
|
|
|
assert_eq!(pairs.next(), None);
|
2020-03-16 10:17:52 +00:00
|
|
|
|
|
|
|
// stress test with foreign input
|
|
|
|
// got it from https://generator.lorem-ipsum.info/_chinese
|
|
|
|
|
|
|
|
let mut pairs = AttributePairs::new(concat!(
|
|
|
|
"載抗留囲軽来実基供全必式覧領意度振。=著地内方満職控努作期投綱研本模,",
|
|
|
|
"後文図様改表宮能本園半参裁報作神掲索=\"針支年得率新賞現報発援白少動面。矢拉年世掲注索政平定他込\",",
|
|
|
|
"ध्वनि स्थिति और्४५० नीचे =देखने लाभो द्वारा करके(विशेष"
|
|
|
|
));
|
|
|
|
|
|
|
|
assert_eq!((3, Some(3)), pairs.size_hint());
|
|
|
|
assert_eq!(
|
|
|
|
pairs.next(),
|
|
|
|
Some((
|
|
|
|
"載抗留囲軽来実基供全必式覧領意度振。",
|
|
|
|
"著地内方満職控努作期投綱研本模"
|
|
|
|
))
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!((2, Some(2)), pairs.size_hint());
|
|
|
|
assert_eq!(
|
|
|
|
pairs.next(),
|
|
|
|
Some((
|
|
|
|
"後文図様改表宮能本園半参裁報作神掲索",
|
|
|
|
"\"針支年得率新賞現報発援白少動面。矢拉年世掲注索政平定他込\""
|
|
|
|
))
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!((1, Some(1)), pairs.size_hint());
|
|
|
|
assert_eq!(
|
|
|
|
pairs.next(),
|
2020-04-18 12:21:51 +00:00
|
|
|
Some(("ध्वनि स्थिति और्४५० नीचे ", "देखने लाभो द्वारा करके(विशेष"))
|
2020-03-16 10:17:52 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!((0, Some(0)), pairs.size_hint());
|
|
|
|
assert_eq!(pairs.next(), None);
|
2019-09-22 18:33:40 +00:00
|
|
|
}
|
2018-02-18 14:36:52 +00:00
|
|
|
}
|