// 959: CSV Writer
// Escape quotes, handle commas/newlines in fields, produce valid CSV output
// Approach 1: Escape a single field
pub fn needs_quoting(s: &str) -> bool {
s.contains(',') || s.contains('"') || s.contains('\n') || s.contains('\r')
}
pub fn escape_field(s: &str) -> String {
if needs_quoting(s) {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for c in s.chars() {
if c == '"' {
out.push('"');
out.push('"');
} else {
out.push(c);
}
}
out.push('"');
out
} else {
s.to_string()
}
}
// Approach 2: Write a single row
pub fn write_row(fields: &[&str]) -> String {
fields
.iter()
.map(|f| escape_field(f))
.collect::<Vec<_>>()
.join(",")
}
pub fn write_row_owned(fields: &[String]) -> String {
fields
.iter()
.map(|f| escape_field(f))
.collect::<Vec<_>>()
.join(",")
}
// Approach 3: Write complete CSV (rows of string slices)
pub fn write_csv(rows: &[Vec<&str>]) -> String {
rows.iter()
.map(|row| write_row(row))
.collect::<Vec<_>>()
.join("\n")
}
fn main() {
println!("simple: {}", escape_field("hello"));
println!("comma: {}", escape_field("one, two"));
println!("quoted: {}", escape_field("say \"hi\""));
println!("newline: {}", escape_field("line1\nline2").replace('\n', "\\n"));
let rows = vec![
vec!["name", "age", "city"],
vec!["Alice, Smith", "30", "Amsterdam"],
vec!["Bob", "25", "say \"hi\""],
];
println!("\nCSV output:");
println!("{}", write_csv(&rows));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_quoting_needed() {
assert_eq!(escape_field("hello"), "hello");
assert_eq!(escape_field("42"), "42");
assert_eq!(escape_field(""), "");
}
#[test]
fn test_comma_quoting() {
assert_eq!(escape_field("one, two"), "\"one, two\"");
}
#[test]
fn test_quote_escaping() {
assert_eq!(escape_field("say \"hi\""), "\"say \"\"hi\"\"\"");
}
#[test]
fn test_newline_quoting() {
assert_eq!(escape_field("line1\nline2"), "\"line1\nline2\"");
}
#[test]
fn test_write_row_plain() {
assert_eq!(write_row(&["name", "age", "city"]), "name,age,city");
}
#[test]
fn test_write_row_with_special() {
assert_eq!(
write_row(&["Alice, Smith", "30", "Amsterdam"]),
"\"Alice, Smith\",30,Amsterdam"
);
}
#[test]
fn test_write_csv() {
let rows = vec![
vec!["name", "age", "city"],
vec!["Alice, Smith", "30", "Amsterdam"],
vec!["Bob", "25", "say \"hi\""],
];
let csv = write_csv(&rows);
let lines: Vec<&str> = csv.lines().collect();
assert_eq!(lines.len(), 3);
assert_eq!(lines[0], "name,age,city");
assert_eq!(lines[1], "\"Alice, Smith\",30,Amsterdam");
assert_eq!(lines[2], "Bob,25,\"say \"\"hi\"\"\"");
}
}
(* 959: CSV Writer *)
(* Escape quotes and handle special characters *)
(* Approach 1: Escape a single field *)
let needs_quoting s =
String.contains s ',' ||
String.contains s '"' ||
String.contains s '\n' ||
String.contains s '\r'
let escape_field s =
if needs_quoting s then begin
(* Replace " with "" and wrap in quotes *)
let buf = Buffer.create (String.length s + 2) in
Buffer.add_char buf '"';
String.iter (fun c ->
if c = '"' then Buffer.add_string buf "\"\""
else Buffer.add_char buf c
) s;
Buffer.add_char buf '"';
Buffer.contents buf
end else
s
(* Approach 2: Write a row *)
let write_row fields =
String.concat "," (List.map escape_field fields)
(* Approach 3: Write a complete CSV *)
let write_csv rows =
String.concat "\n" (List.map write_row rows)
let () =
(* Simple field - no quoting needed *)
assert (escape_field "hello" = "hello");
assert (escape_field "42" = "42");
(* Comma triggers quoting *)
assert (escape_field "one, two" = "\"one, two\"");
(* Quote gets doubled *)
assert (escape_field "say \"hi\"" = "\"say \"\"hi\"\"\"");
(* Newline triggers quoting *)
assert (escape_field "line1\nline2" = "\"line1\nline2\"");
(* Write rows *)
assert (write_row ["name"; "age"; "city"] = "name,age,city");
assert (write_row ["Alice, Smith"; "30"; "Amsterdam"] = "\"Alice, Smith\",30,Amsterdam");
(* Full CSV *)
let rows = [
["name"; "age"; "city"];
["Alice, Smith"; "30"; "Amsterdam"];
["Bob"; "25"; "say \"hi\""];
] in
let csv = write_csv rows in
assert (String.length csv > 0);
let lines = String.split_on_char '\n' csv in
assert (List.length lines = 3);
assert (List.nth lines 0 = "name,age,city");
assert (List.nth lines 1 = "\"Alice, Smith\",30,Amsterdam");
assert (List.nth lines 2 = "Bob,25,\"say \"\"hi\"\"\"");
Printf.printf "โ All tests passed\n"