这几天工作要读写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();
        }

        /// <summary>
        /// Init DataTable's colomns
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        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到这篇。

posted on 2011-10-28 12:51  小城故事  阅读(2008)  评论(3编辑  收藏  举报