2012-02-13 46 views
4

作爲最近項目的一部分,我必須讀取和寫入CSV文件並將其放入c#中的網格視圖中。最後決定使用現成的解析器爲我完成這項工作。寫入和拋光CSV解析器

因爲我喜歡做那種東西,我想知道如何去寫我自己的東西。

到目前爲止,所有我能夠做到的是:

//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); 
      } 

我的問題:地進行着這樣一個明智的想法,如果是(或者不是),我將如何對其進行測試正常嗎?

+0

你必須改變分裂一行到細胞中,如果你想支持包含用逗號字符串值文件的方法。 – phoog 2012-02-13 19:37:07

+4

即使在C#中,您也可以使用可推薦的VB.NET [TextFieldParserClass](http://msdn.microsoft.com/zh-cn/library/cakac7e6.aspx)。 – 2012-02-13 19:37:12

+0

那麼,你不處理包裹在引號中的值。您不會在包含在引號中的值中處理新的行或逗號,也不會處理任何行比第一行具有更多列的情況。除非有一些令人信服的理由不這樣做,否則我會堅持使用庫工具,因此您不必擔心這些邊緣情況。 – Servy 2012-02-13 19:37:13

回答

7

作爲一個編程練習(學習和獲得經驗),這可能是一個非常合理的事情。對於生產代碼,主要因爲工作已經完成而使用現有的庫可能會更好。用CSV解析器解決的問題有很多。例如(隨機把我的頭頂部):

  • 引用的值(字符串)
  • 嵌入式引號引用的字符串
  • 空值(NULL ......或者甚至NULL與空)。
  • 沒有正確數量的條目的行
  • 標題與無標題。
  • 識別不同的數據類型(例如不同的日期格式)。

如果你在一個非常受控制的環境中有一個非常具體的輸入格式,你可能不需要處理所有這些。

+0

謝謝!這只是我自己的一個練習 - 我肯定會使用現有的庫生產代碼! – 2012-02-13 20:11:21

+0

在這種情況下,我認爲這是一個很棒的項目。它有很多東西需要學習,但不是絕對大的。 – 2012-02-13 20:40:37

2

獲取(或製作)一些CSV數據並使用NUnitVisual Studio Testing Tools編寫Unit Tests

一定要測試的邊緣情況下,像

"csv","Data","with","a","trailing","comma", 

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

...地進行着這樣一個明智的想法...?

既然你這樣做是作爲一個學習鍛鍊,你可能要更深入地lexingparsing理論。您目前的方法將很快顯示其缺點,如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在Writing a parser for CSV data中描述了該過程。查看Test-Driven Development with ANTLR,查看使用解析器生成器的相同進程的示例。

請記住,沒有人接受CSV定義。在野外的CSV數據並不能保證實現所有的RFC 4180建議。

+0

這正是我所期待的!謝謝。我不想用很多String.Split()調用一個笨拙的switch語句。這就是爲什麼我停下來的地方! – 2012-02-15 13:22:00

0

解析CSV文件並不困難,但它涉及的不僅僅是簡單地調用String.Split()

您在每個逗號處打破了分界線。但是字段可能包含嵌入的逗號。在這些情況下,CSV會將該字段用雙引號括起來。所以你還必須查找雙引號,並忽略引號內的逗號。另外,字段甚至可以包含嵌入的雙引號。雙引號必須出現在雙引號內,並「加倍」以表示引用是文字字符。

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

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); 
      } 
     } 
    } 
} 
+0

小心,這不適用於嵌入換行符的字段,包含分隔符作爲文字的字段以及引用字段。 – 2012-10-17 14:00:33