使用分隔符进行拆分,除非分隔符被转义。

6

我正在使用以下代码读取来自Excel的剪贴板数据:

var stream = (System.IO.Stream)(Forms.Clipboard.GetDataObject()).GetData(Forms.DataFormats.CommaSeparatedValue);

但不幸的是,Excel传递的是单元格文本而不是单元格值。当单元格使用特殊格式(例如千位分隔符)时,列中一系列单元格的剪贴板数据如下所示:

 1,234,123.00    2,345.00    342.00      12,345.00

存储为以下内容:

\" 1,234,123.00 \",\" 2,345.00 \", 342.00 ,\" 12,345.00 \"

当我真正想要的是这个时候:
 1234123.00, 2345.00, 342.00, 12345.00

我之前一直使用clipData.Split(new string[] { "," }, StringSllitOptions.None))函数将我的CSV剪贴板数据转换为一系列单元格,但是当有包含逗号的转义格式文本时,这种方法会失败。


我想知道是否有人能想到一种方法来将此字符串拆分为一组单元格,并忽略在\"中转义的逗号,因为这就是Excel选择转义包含逗号的单元格的方式。

简而言之,我如何将包含以下内容的单个字符串转换为一组单元格:

\" 1,234,123.00 \",\" 2,345.00 \", 342.00 ,\" 12,345.00 \"

将其转换为包含以下内容的字符串数组:
{ "1,234,123.00", "2,345.00", "342.00", "12,345.00" }

不要破坏我的解析简单逗号分隔字符串的能力。

*****编辑***

跟进问题(作为DFA构成)在这里:Split a string based on each time a Deterministic Finite Automata reaches a final state?


你是否真的看到了一个反斜杠后面跟着一个双引号,还是只是使用“\”来表示双引号而不是字符串开头的标记。 - juharr
你不能以其他格式获取数据吗?使用IDataObject.GetFormats来检索可用格式列表,看看是否可以找到更好的格式。 - erikkallen
好的,我会花时间评估和测试这些解决方案,并回复给你们。 - Alain
许多解决方案失败的原因是 Excel 并不总是在单元格值周围放置引号,只有当它们包含逗号时才会这样做。任何情况下使用 "split" 来避免在引号内意外拆分逗号的方法都无法正确地在没有使用引号时拆分逗号。 - Alain
5个回答

4

首先,我之前处理过来自Excel的数据。你通常会看到逗号分隔的值,如果该值被视为字符串,则会在其周围有双引号(并且可以包含逗号和双引号)。如果它被视为数字,则没有双引号。另外,如果数据包含双引号,则将被双引号限定,如""。因此,在假设所有这些情况下,以下是我以前处理这个问题的方法:

public static IEnumerable<string> SplitExcelRow(this string value)
{
    value = value.Replace("\"\"", "&quot;");
    bool quoted = false;
    int currStartIndex = 0;
    for (int i = 0; i < value.Length; i++)
    {
        char currChar = value[i];
        if (currChar == '"')
        {
            quoted = !quoted;       
        }
        else if (currChar == ',')
        {
            if (!quoted)
            {
                yield return value.Substring(currStartIndex, i - currStartIndex)
                    .Trim()
                    .Replace("\"","")
                    .Replace("&quot;","\"");
                currStartIndex = i + 1;
            }
        }
    }
    yield return value.Substring(currStartIndex, value.Length - currStartIndex)
        .Trim()
        .Replace("\"", "")
        .Replace("&quot;", "\"");
}

当然,这假设输入的数据是有效的,所以如果你有像"fo,o"b,ar","bar""foo"这样的内容,这将不起作用。另外,如果你的数据包含&quot;,它将被转换为一个",这可能是可取的也可能不是。

如果你正在处理一个来自Excel的csv转储文件,情况会更糟,因为行是由换行符分隔的,但是单元格可能包含换行符,你必须查看该换行符是否被“引用”以确定它是数据的一部分还是新行的开始。 - juharr
这里提供的是最佳方法,因为当需要通过逗号拆分字符串时,除非它们被引号包围,否则真正需要的是对应于此问题的确定性有限状态自动机的迭代实现。虽然正则表达式可以验证字符串是否满足DFA,但我不知道它能够基于每个满足最终状态的实例来拆分字符串。因此,需要通过迭代手动评估DFA。干杯。 - Alain
这是一个后续问题:http://stackoverflow.com/questions/4462168/split-a-string-based-on-each-time-a-deterministic-finite-automata-reaches-a-final - Alain

1

我同意Kyle关于你的字符串可能不一致的观点。

你可以使用以下替代Kyle的第一步:

string[] vals = Regex.Split(value, @"\s*\"",\s*");

4
虽然现在你有两个问题 :) - Nat
1
@Nat,那个笑话不合适,而且也是错误的。首先,人们可能不知道你在重复一个笑话,即如果你考虑用正则表达式来解决问题,那么你就有了两个问题。也就是说,你并没有说他的解决方案有任何问题。因此,在这里,你的笑话是不合适的,因为它可能会被那些不熟悉它的人误解。其次,一旦你从正则表达式中得到了解决方案,你就不再有两个问题了,如果它起作用,你就没有问题了,所以你的笑话在这种情况下或任何你已经有解决方案的情况下都是错误的。 - barlop

1

有很多方法可以做到这一点。一个不太优雅但可行的方法是:

  1. 将 \",\" 转换为制表符或其他分隔符(我假设您在示例中省略了一些 \",否则字符串不一致)
  2. 删除所有剩余的逗号
  3. 删除所有剩余的 \"
  4. 将您的分隔符(例如制表符)转换回逗号

现在你得到了最初想要的结果。


为什么要用其他东西替换",",当你可以直接在它上面分割呢?此外,我从经验中知道Excel并不总是在数据周围加上双引号,所以可能会出现类似于\"1,234\",123,\"2,345\"的情况。 - juharr
谢谢这个想法。我一开始是按 " 进行分割,但不想在 \" 上进行分割,所以我用一些绝对不存在的疯狂字符替换了所有的 \",然后再按 " 进行分割,最后再把那些疯狂字符替换回 \"。效果非常好! - Hanna

0
从你的输入例子中,我们可以看到有三个“不需要”的字符序列。
\"
\",
,\"

因此,将所有这些序列添加到输入数组中以供Split方法使用:

string[] result = clipData.Split(new[] { @",\""", @"\"",", @"\""" }, 
    StringSplitOptions.None);

这将给你一个包含几个空元素的数组。如果这是一个问题,使用StringSplitOptions.RemoveEmptyEntries代替StringSplitOptions.None

string[] result = clipData.Split(new[] { @",\""", @"\"",", @"\""" }, 
    StringSplitOptions.RemoveEmptyEntries);

那么就会在数字内部按千位分隔符进行拆分。 - Tim Jarvis
我非常确定输入可能是 123,456,789,因为Excel只会在被认为是字符串的数据周围放置双引号(在这种情况下,当数据包含逗号时)。在这种情况下,您的解决方案将无法工作。 - juharr
@juharr:你说得很对。有时候我感觉只要数据被Excel碰过,就什么都有可能发生,你需要为任何情况做好准备。 - Fredrik Mörk
是的,正如juharr指出的那样,由于转义字符仅在单元格包含逗号时使用,否则一切都是正常逗号分隔的,因此这很复杂。 - Alain

0

你可以尝试使用一点LINQ:

string excelData = "\\\" 1,234,123.00 \\\",\\\" 2,345.00 \\\", 342.00 ,\\\" 12,345.00 \\\"";

IEnumerable<string> cells = from x in excelData.Split(new string[] { "\\\"" }, StringSplitOptions.RemoveEmptyEntries)
                            let y = x.Trim(',').Trim()
                            where !string.IsNullOrWhiteSpace(y)
                            select y;

或者,如果您不喜欢这个建议,可以尝试使用正则表达式实现类似的模式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接