这几天工作要读写CSV文件,CSV格式虽然很简单,为省事想先从网上找一个现成的,但找了半天只有一个看上去可以,至少读写都支持,在此基础上,按照标准修正了一下,标准才是王道。
贴一下CSV格式标准规则,来自百度百科 http://baike.baidu.com/view/468993.htm:
1 开头是不留空,以行为单位。
2 可含或不含列名,含列名则居文件第一行。
3 一行数据不垮行,无空行。
4 以半角逗号(即,)作分隔符,列为空也要表达其存在。
5 列内容如存在半角逗号(即,)则用半角引号(即"")将该字段值包含起来。
6 列内容如存在半角引号(即")则应替换成半角双引号("")转义。
7 文件读写时引号,逗号操作规则互逆。
8 内码格式不限,可为ASCII、Unicode或者其他。
9 不支持特殊字符和中文。
网上看到的,都满足前4条,5和6基本都没考虑,应该只是为处理自己手头工作需要而用。其实只继续努力一下,让它变成通用的,再共享出来就有价值的多。
此类可以读文件或流的数据,返回DataTable。也可以将DataTable处理后保存成csv文件。
public class CsvHelper { public CsvHelper() { } public CsvHelper(string csvFile) { this.CsvFile = csvFile; } public string CsvFile { get; set; } public DataTable Read(Stream stream) { if (stream.CanSeek) stream.Seek(0, SeekOrigin.Begin); return Read(new StreamReader(stream)); } public DataTable Read() { if (String.IsNullOrEmpty(CsvFile)) throw new InvalidOperationException("File name can't be null or empty."); StreamReader reader = new StreamReader(this.CsvFile); return Read(reader); } private DataTable Read(TextReader reader) { string line = string.Empty; int lineNumber = 0; DataTable dt = null; try { while ((line = reader.ReadLine()) != null) { if (lineNumber == 0) {//Create Tole dt = CreateDataTable(line); if (dt.Columns.Count == 0) return null; } else { bool added = CreateDataRow(dt, line); if (!added) break; //Meet the empty rows. } lineNumber++; } } finally { reader.Close(); } return dt; } public void Write(DataTable dt) { if (String.IsNullOrEmpty(CsvFile)) throw new InvalidOperationException("File name can't be null or empty."); Write(dt, this.CsvFile); } public void Write(DataTable dt, string filename) { var sb = WriteString(dt); using (StreamWriter writer = new StreamWriter(filename)) { writer.Write(sb); writer.Flush(); } } public string WriteString(DataTable dt) { if (dt == null) throw new ArgumentNullException("dt"); var sb = new System.Text.StringBuilder(9999); for (int i = 0; i < dt.Columns.Count; i++) { if (i > 0) sb.Append(','); sb.Append(dt.Columns[i].ColumnName); } sb.Append(Environment.NewLine); foreach (DataRow dr in dt.Rows) { for (int i = 0; i < dt.Columns.Count; i++) { var text = dr[i] as string; if (text.IndexOf('"') >= 0) text = text.Replace("\"", "\"\""); //Replace quote to double quotes. if (text.IndexOf(',') >= 0) text = '"' + text + '"'; if (i > 0) sb.Append(','); sb.Append(text); } sb.Append(Environment.NewLine); } return sb.ToString(); } ////// Init DataTable's colomns /// /// ///private DataTable CreateDataTable(string line) { DataTable dt = new DataTable(); foreach (string field in line.Split(',')) { dt.Columns.Add(field); } return dt; } private bool CreateDataRow(DataTable dt, string line) { DataRow dr = dt.NewRow(); string[] fields = new string[dt.Columns.Count]; bool add = true; int index = 0; for (int i = 0; i < dt.Columns.Count; i++) { if (index >= line.Length) break; var text = ReadField(ref index, line); if (i == 0 && text.Length == 0) { add = false; break; } dr[i] = text; } if (add) dt.Rows.Add(dr); return add; } private string ReadField(ref int startIndex, string line) { int endIndex; string text; if (line[startIndex] == '"') { startIndex++; endIndex = line.IndexOf("\",", startIndex); //The normal case, except the end of line. if (endIndex == -1) { endIndex = line.IndexOf(',', startIndex); //Unnormal case, a comma required. if (endIndex == -1) endIndex = line.Length; //Meet string's end. if (line[endIndex - 1] == '"') endIndex--; //The normal case. } text = line.Substring(startIndex, endIndex - startIndex); startIndex = endIndex + 2; } else { endIndex = line.IndexOf(',', startIndex); if (endIndex == -1) endIndex = line.Length; //Meet string's end. text = line.Substring(startIndex, endIndex - startIndex); startIndex = endIndex + 1; } if (text.Contains("\"\"")) text = text.Replace("\"\"", "\""); return text; } }
处理边界条件,如行尾时,要特别小心。既要保证功能正确,又要确保一定的健壮性和容错性。
这是个不依赖状态的类,也就是其中大部分方法都可以转换为静态方法,看你心情了。希望下次有人像我一样偷懒时,能幸运地google到这篇。