1

我已经逐行读取了一些具有以下格式的 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"

4

3 回答 3

2

得到线路后,你可以根据下面的 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]));
于 2012-08-15T18:35:18.200 回答
1

从技术上讲,您正在查看的示例不是有效的 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:从另一个请求,这个正则表达式现在支持冒号前的无限长度数字。以下是正则表达式如何工作的快速解释:

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

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

我尝试使用 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。


我的快速、肮脏、自己动手的解决方案(它有效!):

    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;
    }
于 2012-08-15T22:09:15.613 回答