τau / encoding/csv /

csv.tau

source
/Users/niconex/Documents/tau/stdlib/encoding/csv/csv.tau
1# csv - comma separated values, the way RFC 4180 writes them.2#3# Parse turns the text into a list of records, each one a list of fields, and4# Format writes that back out:5#6#	csv = import("encoding/csv")7#	os = import("os")8#9#	rows = csv.Parse(os.ReadFileString("cities.csv"), null)10#	println(rows[0])11#	os.WriteFile("out.csv", csv.Format(rows, null))12#13# A field may be quoted with ", and a quote inside a quoted field is written14# twice. Quoted fields may hold the separator and newlines. Both \n and \r\n15# end a record. The separator is a single character, "," when it is null.16#17# ponytail: the whole text at once, no reader over a stream. A file that18# doesn't fit in memory needs one, and that is when to write it.1920# sep returns the separator to use, and complains when it isn't one character.21sepOf = fn(sep) {22	if sep == null {23		return ","24	}2526	sep = string(sep)27	if len(sep) != 1 {28		return error("csv: the separator is one character, got \"{sep}\"")29	}30	if sep == "\"" || sep == "\r" || sep == "\n" {31		return error("csv: {sep} cannot be a separator")32	}33	return sep34}3536# Parse reads text and returns its records. A record is a list of strings, and37# every one of them is returned as it was written, without a guess at what it38# means. An unterminated quoted field is an error.39Parse = fn(text, sep) {40	if failed(sep = sepOf(sep)) {41		return sep42	}4344	text = string(text)45	rows = []46	fields = []47	field = ""48	quoted = false # inside a "..." field49	started = false # anything at all seen in this record5051	i = 052	for i < len(text) {53		c = text[i]5455		if quoted {56			if c != "\"" {57				field = field + c58				++i59				continue60			}6162			# A doubled quote is one quote, a single one ends the field.63			if i + 1 < len(text) && text[i + 1] == "\"" {64				field = field + "\""65				i = i + 266				continue67			}68			quoted = false69			++i70			continue71		}7273		if c == "\"" && field == "" {74			quoted = true75			started = true76			++i77			continue78		}7980		if c == sep {81			fields = append(fields, field)82			field = ""83			started = true84			++i85			continue86		}8788		if c == "\n" || c == "\r" {89			if c == "\r" && i + 1 < len(text) && text[i + 1] == "\n" {90				++i91			}92			++i9394			# A blank line between records is skipped, the way Go does it.95			if started || len(fields) > 0 || field != "" {96				rows = append(rows, append(fields, field))97			}98			fields = []99			field = ""100			started = false101			continue102		}103104		field = field + c105		started = true106		++i107	}108109	if quoted {110		return error("csv: the file ends inside a quoted field")111	}112	if started || len(fields) > 0 || field != "" {113		rows = append(rows, append(fields, field))114	}115	return rows116}117118# needsQuotes reports whether a field has to be written quoted.119needsQuotes = fn(field, sep) {120	if field == "" {121		return false122	}123	if field[0] == " " || field[len(field) - 1] == " " {124		return true125	}126127	for i = 0; i < len(field); ++i {128		c = field[i]129		if c == sep || c == "\"" || c == "\n" || c == "\r" {130			return true131		}132	}133	return false134}135136# FormatField returns one field written the way a CSV file wants it, quoted137# only when it has to be.138FormatField = fn(field, sep) {139	if failed(sep = sepOf(sep)) {140		return sep141	}142143	field = string(field)144	if !needsQuotes(field, sep) {145		return field146	}147148	out = "\""149	for i = 0; i < len(field); ++i {150		if field[i] == "\"" {151			out = out + "\"\""152			continue153		}154		out = out + field[i]155	}156	return out + "\""157}158159# Format writes the records as CSV text, one line each, the last one ended by160# a newline as well.161Format = fn(rows, sep) {162	if failed(sep = sepOf(sep)) {163		return sep164	}165166	out = ""167	for r = 0; r < len(rows); ++r {168		row = rows[r]169		for f = 0; f < len(row); ++f {170			if f > 0 {171				out = out + sep172			}173			out = out + FormatField(row[f], sep)174		}175		out = out + "\n"176	}177	return out178}