2012-05-08 32 views
0

这里只是我需要格式化的数据的一个例子。如何格式化和读取CSV文件?

第一列很简单,问题第二列。

  1. 什么是在一列中格式化多个数据字段的最佳方法?
  2. 如何解析这些数据?

重要*:第二列需要一个例子以包含多个值,像下面

Name  Details 

Alex  Age:25 
      Height:6 
      Hair:Brown 
      Eyes:Hazel 
+0

这种格式不是CSV - 你想格式化为CSV?或者你只是对阅读方式感兴趣? – dash

+0

是的,我想将它格式化为CSV格式,然后阅读它 – user1294187

+0

是否总会有相同数量的Column:Value变量?而且,至关重要的是,他们会一直处于同一个秩序吗? – dash

回答

0

通常使用逗号作为字段分隔符和CR为一排分离器定义的CSV文件。您在第二列中使用了CR,这会导致问题。您需要重新格式化第二列以在多个值之间使用其他形式的分隔符。一个常见的备用分隔符是| (管道)字符。

那么你的格式看起来像: 亚历克斯,年龄:25 |身高:6 |头发:棕色|眼睛:淡褐色

在你的分析,你会先解析逗号分隔的字段(这将返回两个值),然后解析第二个字段为管道分离。

1

一个CSV或许应该是这样的:

Name,Age,Height,Hair,Eyes 
Alex,25,6,Brown,Hazel 

每个单元应该从它的邻居恰好一个逗号隔开。

你可以通过使用一个简单的正则表达式来重新格式化它,它用逗号代替某些换行符和非换行符空格(因为它在两列中都有值,所以可以轻松找到每个块)。

0

这很有趣 - 解析特定格式文件非常困难,这就是人们为什么经常编写特定类来处理它们的原因。更传统的文件格式,如CSV或其他分隔格式更容易阅读,因为它们的格式相似。

类似的问题上面可以通过以下方式解决:

1)什么应该输出什么样子?

在您的实例,这只是一个猜测,但我相信你的目标是为以下几点:

Name, Age, Height, Hair, Eyes 
Alex, 25, 6, Brown, Hazel 

在这种情况下,你必须分析出基于上面的结构信息。如果它是像上面那样重复的文本块,那么我们可以这样说:

a。每个人都在一个以名称详细说明的区块

b。名称值是详细信息之后的第一个文本,其他列以格式列分隔:值

但是,如果原始输入是可选的,您可能也会有附加属性的部分或缺少的属性,所以跟踪列和序号也很有用。

所以一个方法可能如下所示:

public void ParseFile(){ 

     String currentLine; 

     bool newSection = false; 

     //Store the column names and ordinal position here. 
     List<String> nameOrdinals = new List<String>(); 
     nameOrdinals.Add("Name"); //IndexOf == 0 

     Dictionary<Int32, List<String>> nameValues = new Dictionary<Int32 ,List<string>>(); //Use this to store each person's details 

     Int32 rowNumber = 0; 

     using (TextReader reader = File.OpenText("D:\\temp\\test.txt")) 
     { 

      while ((currentLine = reader.ReadLine()) != null) //This will read the file one row at a time until there are no more rows to read 
      { 

       string[] lineSegments = currentLine.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries); 

       if (lineSegments.Length == 2 && String.Compare(lineSegments[0], "Name", StringComparison.InvariantCultureIgnoreCase) == 0 
        && String.Compare(lineSegments[1], "Details", StringComparison.InvariantCultureIgnoreCase) == 0) //Looking for a Name Details Line - Start of a new section 
       { 
        rowNumber++; 
        newSection = true; 
        continue; 
       } 

       if (newSection && lineSegments.Length > 1) //We can start adding a new person's details - we know that 
       { 
        nameValues.Add(rowNumber, new List<String>()); 
        nameValues[rowNumber].Insert(nameOrdinals.IndexOf("Name"), lineSegments[0]); 

        //Get the first column:value item 
        ParseColonSeparatedItem(lineSegments[1], nameOrdinals, nameValues, rowNumber); 

        newSection = false; 
        continue; 
       } 

       if (lineSegments.Length > 0 && lineSegments[0] != String.Empty) //Ignore empty lines 
       { 
        ParseColonSeparatedItem(lineSegments[0], nameOrdinals, nameValues, rowNumber); 
       } 

      } 
     } 


     //At this point we should have collected a big list of items. We can then write out the CSV. We can use a StringBuilder for now, although your requirements will 
     //be dependent upon how big the source files are. 

     //Write out the columns 

     StringBuilder builder = new StringBuilder(); 

     for (int i = 0; i < nameOrdinals.Count; i++) 
     { 
      if(i == nameOrdinals.Count - 1) 
      { 
       builder.Append(nameOrdinals[i]); 
      } 
      else 
      { 
       builder.AppendFormat("{0},", nameOrdinals[i]); 
      } 
     } 

     builder.Append(Environment.NewLine); 


     foreach (int key in nameValues.Keys) 
     { 
      List<String> values = nameValues[key]; 

      for (int i = 0; i < values.Count; i++) 
      { 
       if (i == values.Count - 1) 
       { 
        builder.Append(values[i]); 
       } 
       else 
       { 
        builder.AppendFormat("{0},", values[i]); 
       } 
      } 

      builder.Append(Environment.NewLine); 

     } 

     //At this point you now have a StringBuilder containing the CSV data you can write to a file or similar 




    } 


    private void ParseColonSeparatedItem(string textToSeparate, List<String> columns, Dictionary<Int32, List<String>> outputStorage, int outputKey) 
    { 

     if (String.IsNullOrWhiteSpace(textToSeparate)) { return; } 

     string[] colVals = textToSeparate.Split(new[] { ":" }, StringSplitOptions.RemoveEmptyEntries); 

     List<String> outputValues = outputStorage[outputKey]; 

     if (!columns.Contains(colVals[0])) 
     { 
      //Add the column to the list of expected columns. The index of the column determines it's index in the output 
      columns.Add(colVals[0]); 

     } 

     if (outputValues.Count < columns.Count) 
     { 
      outputValues.Add(colVals[1]); 
     } 
     else 
     { 
      outputStorage[outputKey].Insert(columns.IndexOf(colVals[0]), colVals[1]); //We append the value to the list at the place where the column index expects it to be. That way we can miss values in certain sections yet still have the expected output 
     } 
    } 

运行此对您的文件后,该字符串生成器包含:

"Name,Age,Height,Hair,Eyes\r\nAlex,25,6,Brown,Hazel\r\n" 

相匹配上述(\ r \ n是有效Windows新行标记)

此方法演示了自定义分析程序如何工作 - 它有目的地超过了冗长,因为有很多重构可以发生在这里,只是一个例子。

的改进将包括:

1)此函数假定有实际文本项目本身没有空格。这是一个相当大的假设,如果错误的话,需要采用不同的方法来解析出线段。然而,这只需要在一个地方进行更改 - 当您一次读取一行时,您可以应用一个reg ex,或者只读取字符,并假定例如第一个“column:”部分之后的所有内容都是一个值,例如。

2)无异常处理

3)文本输出没有加引号。您可以测试每个值以查看它是日期还是数字 - 如果不是,请将其包含在引号中,然后其他程序(如Excel)将尝试更有效地保留基础数据类型。

4)假定没有列名重复。如果是,则必须检查是否已添加列项目,然后在解析部分创建一个ColName2列。