代码之家  ›  专栏  ›  技术社区  ›  arturh

如何在忽略转义逗号的同时拆分逗号分隔字符串?

  •  26
  • arturh  · 技术社区  · 17 年前

    我需要编写一个StringUtils.commadelimitedListToStringArray函数的扩展版本,该函数将获得一个附加参数:escape char。

    所以呼唤我:

    commaDelimitedListToStringArray("test,test\\,test\\,test,test", "\\")
    

    应该返回:

    ["test", "test,test,test", "test"]
    



    我当前的尝试是使用string.split()使用正则表达式拆分字符串:

    String[] array = str.split("[^\\\\],");
    

    但返回的数组是:

    ["tes", "test\,test\,tes", "test"]
    

    有什么想法吗?

    4 回复  |  直到 13 年前
        1
  •  32
  •   matt b    17 年前

    正则表达式

    [^\\],
    

    意思是“匹配不是反斜杠后跟逗号的字符”—这就是为什么模式如 t, 是匹配的,因为 t 不是反斜杠的字符。

    我想你需要用一些 negative lookbehind 捕捉一个 , 前面没有 \ 不捕获前面的字符,比如

    (?<!\\),
    

    (顺便说一句,请注意,我特意没有对反斜杠进行双重转义,以使其更具可读性)

        2
  •  30
  •   cletus    17 年前

    尝试:

    String array[] = str.split("(?<!\\\\),");
    

    基本上这是在逗号上说split,除了逗号前面有两个反斜杠。这叫A negative lookbehind zero-width assertion .

        3
  •  6
  •   arturh    17 年前

    以下是我最后得出的完整方法,供以后参考:

    public static String[] commaDelimitedListToStringArray(String str, String escapeChar) {
        // these characters need to be escaped in a regular expression
        String regularExpressionSpecialChars = "/.*+?|()[]{}\\";
    
        String escapedEscapeChar = escapeChar;
    
        // if the escape char for our comma separated list needs to be escaped 
        // for the regular expression, escape it using the \ char
        if(regularExpressionSpecialChars.indexOf(escapeChar) != -1) 
            escapedEscapeChar = "\\" + escapeChar;
    
        // see http://stackoverflow.com/questions/820172/how-to-split-a-comma-separated-string-while-ignoring-escaped-commas
        String[] temp = str.split("(?<!" + escapedEscapeChar + "),", -1);
    
        // remove the escapeChar for the end result
        String[] result = new String[temp.length];
        for(int i=0; i<temp.length; i++) {
            result[i] = temp[i].replaceAll(escapedEscapeChar + ",", ",");
        }
    
        return result;
    }
    
        4
  •  2
  •   boumbh    13 年前

    正如马特B所说, [^\\], 将逗号前面的字符解释为分隔符的一部分。

    "test\\\\\\,test\\\\,test\\,test,test"
      -(split)->
    ["test\\\\\\,test\\\\,test\\,tes" , "test"]
    

    正如Vdijk博士所说, (?<!\\), 会误解转义的反斜杠。

    "test\\\\\\,test\\\\,test\\,test,test"
      -(split)->
    ["test\\\\\\,test\\\\,test\\,test" , "test"]
      -(unescape commas)->
    ["test\\\\,test\\,test,test" , "test"]
    

    我也希望能摆脱反斜杠…

    "test\\\\\\,test\\\\,test\\,test,test"
      -(split)->
    ["test\\\\\\,test\\\\" , "test\\,test" , "test"]
      -(unescape commas and backslashes)->
    ["test\\,test\\" , "test,test" , "test"]
    

    Drvdijk建议 (?<=(?<!\\\\)(\\\\\\\\){0,100}), 它适用于元素以100个反斜杠结尾的列表。这已经够远了…但是为什么有限制呢?有没有更有效的方法(不是贪心的背后看)?无效字符串怎么办?

    我找了一会儿通用的解决方案,然后自己写了这个东西…其思想是按照与列表元素匹配的模式(而不是与分隔符匹配)进行拆分。

    我的答案不以转义符作为参数。

    public static List<String> commaDelimitedListStringToStringList(String list) {
        // Check the validity of the list
        // ex: "te\\st" is not valid, backslash should be escaped
        if (!list.matches("^(([^\\\\,]|\\\\,|\\\\\\\\)*(,|$))+")) {
            // Could also raise an exception
            return null;
        }
        // Matcher for the list elements
        Matcher matcher = Pattern
                .compile("(?<=(^|,))([^\\\\,]|\\\\,|\\\\\\\\)*(?=(,|$))")
                .matcher(list);
        ArrayList<String> result = new ArrayList<String>();
        while (matcher.find()) {
            // Unescape the list element
            result.add(matcher.group().replaceAll("\\\\([\\\\,])", "$1"));
        }
        return result;
    }
    

    模式说明(无范围):

    (?<=(^|,)) forward是字符串或 ,

    ([^\\,]|\\,|\\\\)* 元素由 \, , \\ 或者字符wich都不是 \ 也不 ,

    (?=(,|$)) 后面是绳子的末端或 ,

    模式可以简化。

    即使有三个部分( matches + find + replaceAll ,这种方法似乎比Drvdijk建议的方法更快。它仍然可以通过编写特定的解析器进行优化。

    另外,如果只有一个字符是特殊的,那么有一个转义字符的需要是什么?它可以简单地加倍…

    public static List<String> commaDelimitedListStringToStringList2(String list) {
        if (!list.matches("^(([^,]|,,)*(,|$))+")) {
            return null;
        }
        Matcher matcher = Pattern.compile("(?<=(^|,))([^,]|,,)*(?=(,|$))")
                        .matcher(list);
        ArrayList<String> result = new ArrayList<String>();
        while (matcher.find()) {
            result.add(matcher.group().replaceAll(",,", ","));
        }
        return result;
    }