繁体   English   中英

如何格式化和读取CSV文件?

[英]How to format and read CSV file?

这只是我需要格式化的数据的示例。

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

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

重要*:第二列需要包含多个值,例如下面的示例

Name       Details

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

CSV可能应如下所示:

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

每个单元格应与相邻单元格之间用一个逗号分隔。

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

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

您的格式如下所示:Alex,年龄:25 |高度:6 |头发:棕色|眼睛:淡褐色

在解析中,您将首先解析逗号分隔的字段(该字段将返回两个值),然后将第二个字段解析为管道分隔。

这是一个有趣的问题-解析特定格式的文件可能非常困难,这就是为什么人们经常编写特定的类来处理它们的原因。 更传统的文件格式(如CSV)或其他定界格式更易于阅读,因为它们的格式类似。

可以通过以下方式解决上述问题:

1)输出应该是什么样?

就您而言,这只是一个猜测,但我相信您的目标是:

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

在这种情况下,您必须根据上面的结构解析此信息。 如果是上面的文本重复块,那么我们可以说以下内容:

一种。 每个人都以姓名详细信息开头

b。 名称值是“ Details”之后的第一个文本,其他列以Column:Value格式定界

但是,您可能还具有带有附加属性的节,或者如果原始输入是可选的,则这些属性会丢失,因此跟踪列和序数也将很有用。

因此,一种方法可能如下所示:

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)此函数假定实际文本项本身中没有空格。 这是一个很大的假设,如果出错,将需要采用不同的方法来解析线段。 但是,这只需要在一个地方更改-一次阅读一行,您可以应用正则表达式,或者只是读入字符并假定第一个“ column:”部分之后的所有内容都是一个值,例如。

2)没有异常处理

3)文本输出未引用。 您可以测试每个值以查看它是日期还是数字-如果不是,请用引号引起来,因为其他程序(例如Excel)将尝试更有效地保留基础数据类型。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM