use std::borrow::Cow;
#[cfg(test)]
mod tests;
pub struct CsvRow<'a> {
pub line: &'a str,
pub delimiter: char,
pub literal: bool,
char_pos: usize,
byte_pos: usize,
prev_char: Option<char>,
}
impl<'a> CsvRow<'a> {
pub fn new(line: &str, delimiter: char, literal: bool) -> CsvRow {
CsvRow {
line,
delimiter,
literal,
byte_pos: 0,
char_pos: 0,
prev_char: None,
}
}
}
impl<'a> Iterator for CsvRow<'a> {
type Item = Cow<'a, str>;
fn next(&mut self) -> Option<Self::Item> {
if self.byte_pos > self.line.len() || self.line.len() == 0 {
return None;
}
let charenum = self.line.char_indices().into_iter().skip(self.char_pos);
let mut byte_length: usize = 0;
let mut quoted = false;
for (_, c) in charenum {
if byte_length == 0 && c == '"' {
quoted = true;
}
if c == self.delimiter {
if !quoted || (quoted && byte_length > 1 && self.prev_char == Some('"')) {
break;
}
}
byte_length += c.len_utf8();
self.prev_char = Some(c);
}
let mut result = match byte_length {
0 => "",
_ => &self.line[self.byte_pos..self.byte_pos + byte_length],
};
quoted = quoted && result.len() > 1 && result.ends_with('"');
self.char_pos += result.chars().count() + 1;
self.byte_pos += result.len() + self.delimiter.len_utf8();
if self.literal {
return Some(Cow::Borrowed(result));
} else {
if quoted {
result = &result[1..result.len() - 1];
}
let result = match result.contains("\"\"") {
true => Some(Cow::Owned(result.replace("\"\"", "\""))),
false => Some(Cow::Borrowed(result)),
};
return result;
};
}
}
pub fn escape(expression: &str, delimiter: char) -> Cow<str> {
match expression.contains(delimiter) || expression.contains("\"") {
true => Cow::Owned (format!("\"{}\"", expression.replace("\"", "\"\""))),
false => Cow::Borrowed(expression),
}
}