use std::fmt::Display;
pub(crate) fn serialize(strings: &Vec<Option<String>>) -> String {
let mut result = String::new();
for (i, str) in strings.iter().enumerate() {
if let Some(s) = str {
if s.contains("\r")
|| s.contains(" ")
|| s.contains("\n")
|| s.contains(",")
|| s.contains("\"")
{
result.push('"');
if s.contains("\"") {
let replaced = s.replace("\"", "\"\"");
result.push_str(&replaced);
} else {
result.push_str(&s);
}
result.push('"');
} else {
result.push_str(&s);
}
}
if i != strings.len() - 1 {
result.push(',');
}
}
result
}
pub(crate) fn deserialize(str: &str) -> Result<Vec<Option<String>>, ParserError> {
validate_string(str)?;
let strings = divide(str);
let strings = replace_double_quotes(strings);
Ok(strings)
}
fn validate_string(str: &str) -> Result<(), ParserError> {
let mut quotes_are_open = false;
let chars = str.chars();
let chars = chars.collect::<Vec<char>>();
let mut count_of_quotes_in_raw = 0;
let mut last_index = 0;
for (i, c) in chars.iter().enumerate() {
if *c == '"' {
if count_of_quotes_in_raw == 0 && !quotes_are_open && i != 0 {
let previous_char = chars.get(i - 1).unwrap();
if *previous_char != ',' {
return Err(ParserError::ValidationError("quotes_error1".to_string()));
}
}
count_of_quotes_in_raw += 1;
} else {
if (*c == ' ' || *c == '\r' || *c == '\n') && !quotes_are_open {
return Err(ParserError::ValidationError("quotes_error2".to_string()));
}
if quotes_are_open {
if count_of_quotes_in_raw % 2 != 0 {
if *c != ',' {
return Err(ParserError::ValidationError("quotes_error3".to_string()));
}
quotes_are_open = false;
}
count_of_quotes_in_raw = 0;
}
if !quotes_are_open {
if count_of_quotes_in_raw != 0 {
if count_of_quotes_in_raw % 2 == 0 {
return Err(ParserError::ValidationError("quotes_error4".to_string()));
}
if *chars.get(last_index).unwrap() != ',' && last_index != 0 {
return Err(ParserError::ValidationError("quotes_error5".to_string()));
}
count_of_quotes_in_raw = 0;
quotes_are_open = true;
}
}
last_index = i;
}
}
Ok(())
}
fn replace_double_quotes(vec: Vec<Option<String>>) -> Vec<Option<String>> {
vec.iter()
.map(|s| match s {
Some(str) => {
if str.chars().next().unwrap() == '"' {
let str = &str[1..str.len() - 1];
return Some(str.replace("\"\"", "\""));
}
Some(str.replace("\"\"", "\""))
}
None => None,
})
.collect()
}
fn divide(str: &str) -> Vec<Option<String>> {
let mut quotes_are_open = false;
let mut strings = Vec::new();
let mut begin = 0;
let chars = str.chars();
let chars = chars.collect::<Vec<char>>();
for (i, c) in str.chars().enumerate() {
if c == '"' {
quotes_are_open = !quotes_are_open;
}
if !quotes_are_open {
if c == ',' {
if begin == i {
strings.push(None);
} else {
let mut times = String::new();
for (s, t) in str.chars().enumerate().skip(begin) {
if s >= i {
break;
}
times.push(t);
}
strings.push(Some(times));
}
begin = i + 1;
}
}
if chars.get(i + 1).is_none() {
if begin == i + 1 {
strings.push(None);
} else {
let mut times = String::new();
for (_, t) in str.chars().enumerate().skip(begin) {
times.push(t);
}
strings.push(Some(times));
}
}
}
strings
}
#[derive(Debug)]
pub(crate) enum ParserError {
ValidationError(String),
}
impl Display for ParserError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ValidationError(desc) => {
write!(f, "ParseError: {}.", desc)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deserialize_string_with_comma() {
let input = "text,\"text with ,comma\"";
let res = deserialize(input).unwrap();
dbg!(&res);
assert_eq!("text", res.get(0).unwrap().as_ref().unwrap());
assert_eq!("text with ,comma", res.get(1).unwrap().as_ref().unwrap());
}
#[test]
fn deserialize_string_with_comma_3_words() {
let input = "text,\"text with ,comma\",text";
let res = deserialize(input).unwrap();
dbg!(&res);
assert_eq!("text", res.get(0).unwrap().as_ref().unwrap());
assert_eq!("text with ,comma", res.get(1).unwrap().as_ref().unwrap());
assert_eq!("text", res.get(2).unwrap().as_ref().unwrap());
}
#[test]
fn deserialize_simple() {
let input = "text,text1,text2";
let res = deserialize(input).unwrap();
dbg!(&res);
assert_eq!("text", res.get(0).unwrap().as_ref().unwrap());
assert_eq!("text1", res.get(1).unwrap().as_ref().unwrap());
assert_eq!("text2", res.get(2).unwrap().as_ref().unwrap());
}
#[test]
fn validate_err_space_after_or_before_comma() {
let input = "text,text1 ,text2";
let res = validate_string(input);
assert_eq!(true, res.is_err());
let input = "text,text1, text2";
let res = validate_string(input);
assert_eq!(true, res.is_err());
match res.err().unwrap() {
ParserError::ValidationError(desc) => {
assert_eq!("quotes_error2".to_string(), desc)
}
}
}
#[test]
fn validate_err_quotes() {
let input = "text,\"\"text1\",text2";
let res = validate_string(input);
assert_eq!(true, res.is_err());
}
#[test]
fn unknown() {
let input = "text,\"\"text1\"\",text2";
let res = validate_string(input);
dbg!(&res);
assert_eq!(true, res.is_err());
match res.err().unwrap() {
ParserError::ValidationError(desc) => assert_eq!("quotes_error4".to_string(), desc),
}
}
#[test]
fn unknown2() {
let input = "text,tex\"t1,text2";
let res = validate_string(input);
dbg!(&res);
assert_eq!(true, res.is_err());
}
#[test]
fn unknown3() {
let input = "text,\"tex\"t1\",text2";
let res = validate_string(input);
dbg!(&res);
assert_eq!(true, res.is_err());
}
#[test]
fn unknown4() {
let input = "text,\"tex\"\"t1\",text2";
let res = validate_string(input);
dbg!(&res);
assert_eq!(true, res.is_ok());
}
#[test]
fn unknown5() {
let input = "text,\"tex\"\"\"t1\",text2";
let res = validate_string(input);
assert_eq!(true, res.is_err());
}
#[test]
fn unknown6() {
let input = "text,\"\"\"text1\"\"\",text2";
let res = validate_string(input);
dbg!(&res);
assert_eq!(true, res.is_ok());
}
#[test]
fn divide1() {
let input = "text,\"text, text\",1";
let result = divide(input);
dbg!(&result);
assert_eq!(3, result.len());
assert_eq!("text", result[0].as_ref().unwrap());
assert_eq!("\"text, text\"", result[1].as_ref().unwrap());
assert_eq!("1", result[2].as_ref().unwrap());
}
#[test]
fn divide2() {
let input = ",\"text, text\",";
let result = divide(input);
dbg!(&result);
assert_eq!(3, result.len());
assert_eq!(true, result.get(0).unwrap().is_none());
assert_eq!("\"text, text\"", result[1].as_ref().unwrap());
assert_eq!(true, result.get(2).unwrap().is_none());
}
#[test]
fn validate7() {
let input = "text,\"text with ,comma\",text\r\n";
let res = validate_string(input);
assert_eq!(true, res.is_err());
}
#[test]
fn validate8() {
let input = "text,\"text with \r\n,comma\",text";
let res = validate_string(input);
assert_eq!(true, res.is_ok());
}
#[test]
fn serialize_string_with_quotes() {
let strs = vec![None, Some("te\"xt".to_string()), None, None];
let str = serialize(&strs);
assert_eq!(",\"te\"\"xt\",,", str);
}
}