【问题标题】:Using FileHelper library to parse CSV strings, but need to ignore newlines使用 FileHelper 库解析 CSV 字符串,但需要忽略换行符
【发布时间】:2012-08-15 17:45:05
【问题描述】:

我已经逐行读取了一些 CSV 值,格式如下:

30: "NY", 41: "JOHN S.", 36: "HAMPTON", 42: "123 Road Street, NY", 68: "Y"

为了进一步解析这些项​​目,我需要将其分解为以下内容:

30: "NY"

41: "JOHN S."

36: "HAMPTON"

42: "123 Road Street, NY"(注意逗号)

...

我正在使用FileHelper 库,但它似乎喜欢逐行阅读内容,尽管我希望它被分隔的, 逗号分隔。

我有记录类:

[DelimitedRecord(",")]
class BoxRecord
{
    public String record;
}

我通过以下方法检索了我希望的数组中的几个对象,但它只返回了原始行:

DelimitedFileEngine engine = new DelimitedFileEngine(typeof(BoxRecord));
BoxRecord[] boxes = (BoxRecord[])engine.ReadString(boxLine);

我希望boxes[].record 包含的内容:

30: "NY"

41: "JOHN S."

36: "HAMPTON"

42: "123 Road Street, NY"

...

它实际包含的内容:

30: "NY", 41: "JOHN S.", 36: "HAMPTON", 42: "123 Road Street, NY", 68: "Y"

【问题讨论】:

    标签: c# parsing csv filehelpers


    【解决方案1】:

    得到线后,可以根据下面的linq断线,得到你想要的:

    string input = "30: \"NY\", 41: \"JOHN S.\", " +
       "36: \"HAMPTON\", 42: \"123 Road Street, NY\", 68: \"Y\"";
    
    var tempList = input.Split('\"').ToList();
    
    var result = Enumerable.Range(0, tempList.Count/2)
        .Select(i => string.Join(": "
            , tempList[2*i].Split(new[] { ',', ':' })
               .Single(ss => !string.IsNullOrWhiteSpace(ss))
    
            , tempList[2*i + 1]));
    

    更新:对我来说似乎很有趣,这段代码是为了处理这个案子作为你的评论:

    var tempList1 = input.Split(':').ToList();
    
    var tempList2 = tempList1.SelectMany((s, index) =>
     {
         if (index == 0 || index == tempList1.Count - 1)
             return new List<string>() { s };
    
         var subList = s.Split(',');
         return new List<string>
               { 
                    string.Concat(subList.Take(subList.Length - 1)),
                    subList.Last()
               };
     }).ToList();
    
    var result = Enumerable.Range(0, tempList2.Count / 2)
             .Select(i => string.Join(": ", tempList2[2 * i], tempList2[2 * i + 1]));
    

    【讨论】:

    • 这太棒了,谢谢。有没有办法编辑它,使其包含如下项目:40: 12345。目前这仅适用于带有引号的项目,例如,40: "12345"
    • 非常感谢。我讨厌成为坏消息的承担者,不适用于该行(截断AVE, 逗号后的其余值):String test = "20: \"STEVE\", 12: \"JONES\", 96: \"1600 PENNSYLVANIA AVE, NW\"";
    【解决方案2】:

    从技术上讲,您正在查看的示例不是有效的 CSV 格式文件。基本上,提供文件的人都以非标准方式使用了文本限定符符号 - 双引号“。使用它的传统方式是这样的:

    123,"Sue said, ""Hi, this is a test!""",2012-08-15
    

    这个语句应该这样解析:

    Assert.AreEqual(line.Length, 3);
    Assert.AreEqual(line[0], @"123");
    Assert.AreEqual(line[1], @"Sue said, ""Hi, this is a test!""");
    Assert.AreEqual(line[2], @"2012-08-15");
    

    根据我看到的标准,从您的问题中提供的示例 CSV 来看,正确的处理基本上应该将引号视为字符串中的常规字符,而不是文本限定符。以下是我对你的台词的解释——不过,如果我错了,请告诉我!

    Assert.AreEqual(line.Length, 6);
    Assert.AreEqual(line[0], @"30: ""NY""");
    Assert.AreEqual(line[1], @" 41: ""JOHN S.""");
    Assert.AreEqual(line[2], @" 36: ""HAMPTON""");
    Assert.AreEqual(line[3], @" 42: ""123 Road Street");
    Assert.AreEqual(line[4], @" NY""");
    Assert.AreEqual(line[5], @" 68: ""Y""");
    

    我想 FileHelper 会中断,因为它无法确定文本是文本限定的还是正确分隔的。你最好使用自定义代码来处理这个; Cuong Le 提供的解决方案似乎适合您的解决方案。

    作为参考,我的 C# CSV 库在这里:https://code.google.com/p/csharp-csv-reader/

    编辑:为了好玩,我想知道是否可以使用正则表达式对此进行解码。您的数据的格式是一致的,即使它不是严格的 CSV,所以也许这对您的工具箱来说是别的东西:

    String mystring = @"30: ""NY"", 41: ""JOHN S."", 36: ""HAMPTON"", 42: ""123 Road Street, NY"", 68: ""Y""
        20: ""STEVE"", 12: ""JONES"", 96: ""1600 PENNSYLVANIA AVE, NW""
        30: ""NY"", 41: ""JOHN S."", 36: ""HAMPTON"", 42: ""123 Road Street, NY"", 68: ""Y"", 40: 12345";
    Regex r = new Regex(@"(?<id>\d*): (""(?<field>[^""]*)""|(?<field>[\d]*))");
    MatchCollection mc = r.Matches(mystring);
    foreach (Match m in mc) {
        Console.WriteLine("{0}: {1}", m.Groups["id"], m.Groups["field"]);
    }
    

    基本上,正则表达式的工作原理是查找两个十进制数字,后跟一个冒号 - 空格 - 双引号。然后它会查找所有文本,直到它到达另一个双引号。根据我的测试,这也会为您在问题中描述的两条测试线产生正确的匹配。

    如果我的正则表达式不太正确,这里有一个漂亮的在线正则表达式测试器:http://gskinner.com/RegExr/ - 尝试将您的数据复制并粘贴到搜索区域,然后使用这个正则表达式字符串作为起点:

    (?<id>\d*): ("(?<field>[^"]*)"|(?<field>[\d]*))
    

    EDIT2:我修复了正则表达式,还考虑了您在下面的评论中引用的“40:12345”值。它现在可以正确检测所有示例中的所有字段。

    EDIT3:根据另一个请求,此正则表达式现在支持冒号前的无限长度数字。以下是正则表达式如何工作的简要说明:

    • (?&lt;id&gt;\d*) - 第一个块称为捕获组 - 捕获组用括号括起来。它尝试捕获十进制数字 (\d) 的重复字符串 (*) 并将其命名为“id”(?&lt;id&gt;)。
    • : - 匹配记录之间的冒号空格。
    • "(?&lt;field&gt;[^"]*)" - 找到一个开始的引号,然后是引号以外的大量字符 ([^"]),以另一个引号结尾。将结果保存在“字段”中。
    • (?&lt;field&gt;[\d]*) - 查找任意数量的十进制数字并将结果保存在“字段”中。请注意,某些正则表达式引擎不支持拥有两个具有相同名称的捕获组;您可能需要调用一个“field1”和另一个“field2”。

    【讨论】:

    • 谢谢特德。你是完全正确的——这个项目的重点是看看我们是否可以用这些完全非标准的数据做任何有用的事情。我编写了自己的解析器,它可以工作,但我会回到 Cuong Le 的答案,因为他比我更棒。
    • 很高兴听到您解决了这个问题!我一直在寻找奇怪的 CSV 边缘案例,所以感谢您分享这个。
    • 我为了好玩而尝试了你的正则表达式。这很棒。第一个上班! ...几乎 - 它只需要 2 个字符的行号。例如,100: "SPENCE" 输出为 00: SPENCE
    • 哦 - 这很容易解决。这是一个处理无限字符的修改后的 REGEX。 (?\d*): ("(?[^"]*)"|(?[\d]*))
    • 他提供的样本是有效的,因为它没有嵌入双引号。
    【解决方案3】:

    尽管我遇到了所有“不要重新发明轮子”的帖子(所有这些帖子),但这对我来说是最好的解决方案,也是我发现唯一可行的解​​决方案。

    我尝试过使用 FileHelper 框架、Cuong Le 的答案和 VB TextFieldParser。每个都以不同的方式工作和不工作。

    我需要能够解析这个(“非标准”CSV 格式)。这些行是来自文件的输入,但不是 CSV 文件。它们是更大结构的一部分:

    30: "NY", 41: "JOHN S.", 36: "HAMPTON", 42: "123 Road Street, NY", 68: "Y", 40: 12345


    FileHelper 会在引号中的逗号分隔,例如:

    123 Road Street, NY

    变成

    213 Road Street

    NY

    Cuong Le的回答没有处理这种情况:40: 12345(不带引号的数据值)

    TextFieldParser 也可以用逗号分隔引号,例如 FileHelper。


    我的快速-n-dirty、自己滚动的解决方案(并且有效!):

        private List<KeyValuePair<string, string>> SplitBoxLine(String input)
        {
            //SAMPLE input:
            //30: "NY", 41: "JOHN S.", 36: "HAMPTON", 42: "123 Road Street, NY", 68: "Y", 40: 12345
    
            List<KeyValuePair<string, string>> boxes = new List<KeyValuePair<string, string>>();
    
            int quoteCount = 0;
            String buffer = "";
            String boxNum = "";
            String boxValue = "";
    
            for (int i = 0; i < input.Length; i++)
            {
                if (i == input.Length - 1)
                {
                    //if the input character at the end ISN'T a quote or comma, add it to the buffer
                    //supports the case where the last item is 40: 12345
                    if (input[i] != ',' && input[i] != '\"')
                    {
                        buffer += input[i];
                    }
                    boxValue = String.Copy(buffer.Trim());
    
                    //once we have the value, we can create the pair
                    KeyValuePair<string, string> pair = new KeyValuePair<string, string>(boxNum, boxValue);
                    boxes.Add(pair);
    
                    Console.WriteLine("BOX VALUE [LAST ITEM]: " + boxValue);
                }
    
                if (input[i] == ':')
                {
                    boxNum = String.Copy(buffer.Trim());
                    buffer = "";
                    Console.WriteLine("BOX NUM: " + boxNum);
                }
                else if (input[i] == '\"')
                {
                    quoteCount++;
                }
                else if (input[i] == ',')
                {
                    if (quoteCount % 2 == 0) //comma occurs outside of quotes
                    {
                        boxValue = String.Copy(buffer.Trim());
                        buffer = "";
    
                        //once we have the value, we can create the pair
                        KeyValuePair<string, string> pair = new KeyValuePair<string, string>(boxNum, boxValue);
                        boxes.Add(pair);
    
                        Console.WriteLine("BOX VALUE: " + boxValue);
                    }
                    else //the comma occurs in some quotes
                    {
                        buffer += input[i]; //add the comma, it's just part of the boxValue
                    }
                }
                //nothing special about this chacter, add it to the buffer and continue
                else
                {
                    buffer += input[i];
                }
            }
    
            return boxes;
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-10-10
      • 2019-05-26
      • 2011-03-11
      • 2015-09-05
      • 2014-12-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多