编写和完善CSV解析器
本文关键字:CSV | 更新日期: 2023-09-27 18:24:15
作为最近一个项目的一部分,我不得不从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);
}
我的问题是:这样做是一个明智的想法吗?如果是(或不是),我该如何正确测试它?
作为一项编程练习(为了学习和获得经验),这可能是一件非常合理的事情。对于生产代码,最好使用现有的库,主要是因为工作已经完成。使用CSV解析器可以解决很多问题。例如(随机从我的头顶):
- 带引号的值(字符串)
- 在带引号的字符串中嵌入引号
- 空值(NULL…甚至可能是NULL对空)
- 没有正确条目数的行
- 有标题与无标题
- 识别不同的数据类型(例如,不同的日期格式)
但是,如果您在一个非常可控的环境中有一个非常特定的输入格式,您可能不需要处理所有这些。
这样做是明智的想法吗。。。?
既然你这样做是作为一种学习练习,你可能想更深入地研究词汇和解析理论。您当前的方法将很快显示其缺点,如Stop Rolling Your Own CSV Parser!中所述!。解析CSV数据并不困难。(事实并非如此。)只是大多数CSV解析器项目将此问题视为文本拆分问题,而不是解析问题。如果你花时间定义CSV";语言";,解析器几乎是自己写的。
RFC 4180定义了ABNF形式的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在为CSV数据编写解析器中描述了这个过程。请看一下使用ANTLR的测试驱动开发,以获得使用解析器生成器的相同过程的示例。
请记住,没有一个可接受的CSV定义。野生的CSV数据不能保证实现所有的RFC 4180建议。
使用NUnit或Visual Studio测试工具获取(或生成)一些CSV数据并编写单元测试。
一定要测试像这样的边缘案例
"csv","Data","with","a","trailing","comma",
和
"csv","Data","with,","commas","and","""quotes""","in","it"
这来自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);
}
}
}
}
解析CSV文件并不困难,但它涉及的不仅仅是调用String.Split()
。
你正在打断每个逗号处的行。但是字段中可能包含嵌入的逗号。在这些情况下,CSV将字段用双引号括起来。因此,您还必须查找双引号,并忽略这些引号中的逗号。此外,字段甚至可以包含嵌入的双引号。双引号必须出现在双引号中,并且必须是"双引号",以表示引号是文字字符。
如果你想看看我是怎么做到的,你可以看看这篇文章。