在引号外的逗号上拆分

Posted

技术标签:

【中文标题】在引号外的逗号上拆分【英文标题】:Splitting on comma outside quotes 【发布时间】:2013-09-24 11:04:57 【问题描述】:

我的程序从文件中读取一行。此行包含逗号分隔的文本,例如:

123,test,444,"don't split, this",more test,1

我希望拆分的结果是这样的:

123
test
444
"don't split, this"
more test
1

如果我使用String.split(","),我会得到这个:

123
test
444
"don't split
 this"
more test
1

换句话说:子字符串"don't split, this" 中的逗号不是分隔符。如何处理?

【问题讨论】:

为什么有这个要求?您能否提供更多有关您要解决的问题的信息? 我不相信这个问题与前面提到的问题重复,因为这里双引号的字符串用逗号分隔;前面的问题没有这个要求。它希望(给出的示例)foo,bar,c;qual="baz,blurb",d;junk="quux,syzygy" 被拆分为 foobarc;qual="baz,blurb"d;junk="quux,syzygy"。这不是一个微不足道的区别,因为匹配@LAFKsaysReinstateMonica 的正则表达式"\"[^\"]*\"|[^,]+ 在这里有效,但在那里无效。我已投票决定重新开放。 【参考方案1】:

你可以试试这个正则表达式:

str.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");

这将分割, 上的字符串,后跟偶数个双引号。换句话说,它在双引号之外的逗号上拆分。如果您的字符串中有平衡的引号,这将起作用。

解释:

,           // Split on comma
(?=         // Followed by
   (?:      // Start a non-capture group
     [^"]*  // 0 or more non-quote characters
     "      // 1 quote
     [^"]*  // 0 or more non-quote characters
     "      // 1 quote
   )*       // 0 or more repetition of non-capture group (multiple of 2 quotes will be even)
   [^"]*    // Finally 0 or more non-quotes
   $        // Till the end  (This is necessary, else every comma will satisfy the condition)
)

您甚至可以在您的代码中输入这样的内容,在您的正则表达式中使用(?x) 修饰符。修饰符会忽略正则表达式中的任何空格,因此更容易阅读分成多行的正则表达式,如下所示:

String[] arr = str.split("(?x)   " + 
                     ",          " +   // Split on comma
                     "(?=        " +   // Followed by
                     "  (?:      " +   // Start a non-capture group
                     "    [^\"]* " +   // 0 or more non-quote characters
                     "    \"     " +   // 1 quote
                     "    [^\"]* " +   // 0 or more non-quote characters
                     "    \"     " +   // 1 quote
                     "  )*       " +   // 0 or more repetition of non-capture group (multiple of 2 quotes will be even)
                     "  [^\"]*   " +   // Finally 0 or more non-quotes
                     "  $        " +   // Till the end  (This is necessary, else every comma will satisfy the condition)
                     ")          "     // End look-ahead
                         );

【讨论】:

这个答案这么多年还是有价值的! 让我的程序与您的解释一起工作。谢谢!现在,有没有办法也可以为此添加换行符? \n 和 \r? 嗨,我的字符串是这样的:\“不要拆分,这个\”(它在“前面有那些反斜杠。如何修改正则表达式? 嗨 Rohit,我遵循了您的解决方案,其中我有两个分隔符,和/或。并使用以下正则表达式: (\s+and\s+|\s+or\s+)(?=(?:[^\"]*"[^\"]*\")*[^\"]*$ ). 它适用于大多数用例,但输入失败:'brand == "Kellogg\'\'s" 或 country == \'UnitedStates and "India\''。请你帮助我好吗?我对正则表达式很陌生。 这不适用于非平凡的情况,例如如果文本本身包含引号,则需要转义,例如 \"【参考方案2】:

既然可以匹配,为什么要拆分?

重新提出这个问题,因为出于某种原因,没有提到简单的解决方案。这是我们精美紧凑的正则表达式:

"[^"]*"|[^,]+

这将匹配所有需要的片段 (see demo)。

说明

"[^"]*",我们匹配完整的"double-quoted strings"| 我们匹配[^,]+ 任何不是逗号的字符。

一种可能的改进是改进交替的字符串端,以允许引用的字符串包含转义的引号。

【讨论】:

因为我喜欢这个而不是拆分,所以我将它与 Matcher 中的 Java 9 改进相结合,允许流式传输。我的答案包含演示它的 jshell 会话。 如果您还需要获取空字符串,此解决方案将不起作用,但我喜欢它。 @zx81 你知道如何使用转义引号\" 吗?【参考方案3】:

你可以很容易地做到这一点,无需复杂的正则表达式:

    拆分字符"。你得到一个字符串列表 处理列表中的每个字符串:将列表中偶数位置上的每个字符串(从零开始索引)拆分为“,”(您会在列表中获得一个列表),单独保留每个奇数位置的字符串(直接将其放入列表中的列表中)。 加入列表列表,因此您只得到一个列表。

如果你想处理 '"' 的引用,你必须稍微调整一下算法(加入一些部分,你有不正确的拆分,或者将拆分更改为简单的正则表达式),但基本结构保持不变。

所以基本上是这样的:

public class SplitTest 
    public static void main(String[] args) 
        final String splitMe="123,test,444,\"don't split, this\",more test,1";
        final String[] splitByQuote=splitMe.split("\"");
        final String[][] splitByComma=new String[splitByQuote.length][];
        for(int i=0;i<splitByQuote.length;i++) 
            String part=splitByQuote[i];
            if (i % 2 == 0)
               splitByComma[i]=part.split(",");
            else
                splitByComma[i]=new String[1];
                splitByComma[i][0]=part;
            
        
        for (String parts[] : splitByComma) 
            for (String part : parts) 
                System.out.println(part);
            
        
    

承诺使用 lambda 会更简洁!

【讨论】:

【参考方案4】:

基于 @zx81's 的回答,因为匹配的想法非常好,我添加了 Java 9 results 调用,它返回一个 Stream。由于 OP 想使用split,我已经收集到String[],就像split 一样。

如果逗号分隔符 (a, b, "c,d") 后面有空格,请注意。然后你需要改变模式。

Jshell 演示

$ jshell
-> String so = "123,test,444,\"don't split, this\",more test,1";
|  Added variable so of type String with initial value "123,test,444,"don't split, this",more test,1"

-> Pattern.compile("\"[^\"]*\"|[^,]+").matcher(so).results();
|  Expression value is: java.util.stream.ReferencePipeline$Head@2038ae61
|    assigned to temporary variable $68 of type java.util.stream.Stream<MatchResult>

-> $68.map(MatchResult::group).toArray(String[]::new);
|  Expression value is: [Ljava.lang.String;@6b09bb57
|    assigned to temporary variable $69 of type String[]

-> Arrays.stream($69).forEach(System.out::println);
123
test
444
"don't split, this"
more test
1

代码

String so = "123,test,444,\"don't split, this\",more test,1";
Pattern.compile("\"[^\"]*\"|[^,]+")
    .matcher(so)
    .results()
    .map(MatchResult::group)
    .toArray(String[]::new);

说明

    正则表达式 [^"] 匹配:引号,除引号外的任何内容,引号。 正则表达式 [^"]* 匹配:一个引号,除了 0(或更多)次引号之外的任何内容,一个引号。 该正则表达式需要首先“获胜”,否则匹配除了逗号 1 次或更多次 - 即:[^,]+ - 将“获胜”。 results() 需要 Java 9 或更高版本。 它返回Stream&lt;MatchResult&gt;,我使用group() 调用将其映射并收集到字符串数组。无参数的toArray() 调用将返回Object[]

【讨论】:

【参考方案5】:

请看下面的代码sn-p。此代码仅考虑快乐流。根据您的要求更改

public static String[] splitWithEscape(final String str, char split,
        char escapeCharacter) 
    final List<String> list = new LinkedList<String>();

    char[] cArr = str.toCharArray();

    boolean isEscape = false;
    StringBuilder sb = new StringBuilder();

    for (char c : cArr) 
        if (isEscape && c != escapeCharacter) 
            sb.append(c);
         else if (c != split && c != escapeCharacter) 
            sb.append(c);
         else if (c == escapeCharacter) 
            if (!isEscape) 
                isEscape = true;
                if (sb.length() > 0) 
                    list.add(sb.toString());
                    sb = new StringBuilder();
                
             else 
                isEscape = false;
            

         else if (c == split) 
            list.add(sb.toString());
            sb = new StringBuilder();
        
    

    if (sb.length() > 0) 
        list.add(sb.toString());
    

    String[] strArr = new String[list.size()];

    return list.toArray(strArr);

【讨论】:

以上是关于在引号外的逗号上拆分的主要内容,如果未能解决你的问题,请参考以下文章

在逗号上拆分字符串并忽略双引号中的逗号[重复]

如何在逗号和引号上拆分此字符串? [复制]

正则表达式匹配引号外的逗号 - XML 模式变体

Java:拆分逗号分隔的字符串但忽略引号中的逗号

使用逗号拆分字符串,但忽略双引号内的逗号 - javascript

C# 正则表达式拆分引号和逗号语法错误 [重复]