编写和抛光CSV解析器

时间:2012-02-13 19:33:12

标签: c# parsing

作为最近项目的一部分,我必须从CSV文件中读取和写入并放入c#中的网格视图中。最后决定使用一个现成的解析器为我做的工作。

因为我喜欢做那种东西,所以我想知道如何写自己的东西。

到目前为止,我所做的就是:

//Read the header
            StreamReader reader = new StreamReader(dialog.FileName);
            string row = reader.ReadLine();
            string[] cells = row.Split(',');

            //Create the columns of the dataGridView
            for (int i = 0; i < cells.Count() - 1; i++)
            {
                DataGridViewTextBoxColumn column = new DataGridViewTextBoxColumn();
                column.Name = cells[i];
                column.HeaderText = cells[i];
                dataGridView1.Columns.Add(column);
            }

            //Display the contents of the file
            while (reader.Peek() != -1)
            {
                row = reader.ReadLine();
                cells = row.Split(',');
                dataGridView1.Rows.Add(cells);
            }

我的问题:继续这样一个明智的想法,如果是(或不是),我将如何正确地测试它?

5 个答案:

答案 0 :(得分:7)

作为一项编程练习(用于学习和获得经验),这可能是一件非常合理的事情。对于生产代码,使用现有库可能更好,主要是因为工作已经完成。使用CSV解析器可以解决很多问题。例如(随机离开我的头顶):

  • 引用值(字符串)
  • 引用字符串中的嵌入式引号
  • 空值(NULL ...或者甚至是NULL与空)。
  • 没有正确条目数的行
  • 标题与无标题。
  • 识别不同的数据类型(例如,不同的日期格式)。

如果您在非常受控的环境中拥有非常特定的输入格式,则可能不需要处理所有这些。

答案 1 :(得分:2)

获取(或制作)一些CSV数据,并使用Unit TestsNUnit撰写Visual Studio Testing Tools

请务必测试像

这样的边缘情况
"csv","Data","with","a","trailing","comma",

"csv","Data","with,","commas","and","""quotes""","in","it"

答案 2 :(得分:1)

  

......正在继续这样一个明智的想法......?

由于您将此作为学习练习,因此您可能需要深入了解lexingparsing理论。如Stop Rolling Your Own CSV Parser!中所述,您当前的方法将很快显示其缺点。并不是解析CSV数据很困难。 (事实并非如此。)只是大多数CSV解析器项目将问题视为文本拆分问题而不是解析问题。如果您花时间定义CSV“语言”,解析器几乎会自行编写。

RFC 4180ABNF形式定义CSV数据的语法:

file = [header CRLF] record *(CRLF record) [CRLF]
header = name *(COMMA name)
record = field *(COMMA field)
name = field
field = (escaped / non-escaped)
escaped = DQUOTE *(TEXTDATA / COMMA / CR / LF / 2DQUOTE) DQUOTE
non-escaped = *TEXTDATA
COMMA = %x2C
CR = %x0D ;as per section 6.1 of RFC 2234
DQUOTE =  %x22 ;as per section 6.1 of RFC 2234
LF = %x0A ;as per section 6.1 of RFC 2234
CRLF = CR LF ;as per section 6.1 of RFC 2234
TEXTDATA =  %x20-21 / %x23-2B / %x2D-7E

此语法显示如何构建单个字符以创建越来越复杂的语言元素。 (正如所写,定义从复杂到简单的方向相反。)

如果从语法开始,可以编写镜像非终端语法元素(小写项)的解析函数。 Julian M Bucknall在Writing a parser for CSV data中描述了这个过程。使用解析器生成器查看Test-Driven Development with ANTLR以获取相同进程的示例。

请注意,没有人接受CSV定义。野外的CSV数据无法保证实施所有RFC 4180建议。

答案 3 :(得分:0)

解析CSV文件并不困难,但它不仅仅是调用String.Split()

你打破了每个逗号的界限。但是字段可能包含嵌入的逗号。在这些情况下,CSV将字段用双引号括起来。因此,您还必须查找双引号并忽略这些引号中的逗号。此外,字段甚至可以包含嵌入的双引号。双引号必须出现在双引号内并且“加倍”以表示引号是文字字符。

如果您想了解我是如何做到的,可以查看this article

答案 4 :(得分:0)

这来自 http://www.gigawebsolution.com/Posts/Details/61/Building-a-Simple-CSV-Parser-in-C#

public interface ICsvReaderWriter
{
    List<string[]> Read(string filePath, char delimiter);
    void Write(string filePath, List<string[]> lines, char delimiter);
}

public class CsvReaderWriter : ICsvReaderWriter
{
    public List<string[]> Read(string filePath, char delimiter)
    {
        var fileContent = new List<string[]>();
        using (var reader = new StreamReader(filePath, Encoding.Unicode))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                if (!string.IsNullOrEmpty(line))
                {
                    fileContent.Add(line.Split(delimiter));
                }
            }
        }
        return fileContent;
    }

    public void Write(string filePath, List<string[]> lines, char delimiter)
    {
        using (var writer = new StreamWriter(filePath, true, Encoding.Unicode))
        {
            foreach (var line in lines)
            {
                var data = line.Aggregate(string.Empty,
                                         (current, column) => current +
                                          string.Format("{0}{1}", column,delimiter))
                    .TrimEnd(delimiter);
                writer.WriteLine(data);
            }
        }
    }
}