【问题标题】:Split comma separated string with quotes and commas within quotes and escaped quotes within quotes拆分逗号分隔的字符串,引号内有引号和逗号,引号内有转义引号
【发布时间】:2013-12-04 16:14:40
【问题描述】:

我什至在谷歌的第 3 页上搜索过这个问题,但似乎没有合适的解决方案。

以下字符串

"zhg,wimö,'astor wohnideen','multistore 2002',yonza,'asdf, saflk','marc o\'polo'"

在 Java 中应该用逗号分隔。引号可以是双引号或单引号。我尝试了以下正则表达式

,(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)

但由于 'marc o\'polo' 的转义引号,它失败了......

有人可以帮帮我吗?

试用代码:

String checkString = "zhg,wimö,'astor wohnideen','multistore 2002',yonza,'asdf, saflk','marc \'opolo'";
Pattern COMMA_PATTERN = Pattern.compile(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
String[] splits = COMMA_PATTERN.split(checkString);
for (String split : splits) {
  System.out.println(split);
}

【问题讨论】:

  • 每个逗号都是分隔符,还是引号中的逗号应该被忽略?
  • 是的,引号内的逗号应该被忽略

标签: java regex


【解决方案1】:

你可以这样做:

List<String> result = new ArrayList<String>();

Pattern p = Pattern.compile("(?>[^,'\"]++|(['\"])(?>[^\"'\\\\]++|\\\\.|(?!\\1)[\"'])*\\1|(?<=,|^)\\s*(?=,|$))+", Pattern.DOTALL);
Matcher m = p.matcher(checkString);

while(m.find()) {
    result.add(m.group());
}

【讨论】:

  • +1 但是你花了多少时间来写这个野兽? :)
  • 谢谢,几次,因为我使用了我已经知道的子模式。最困难的是匹配空项目,避免不在两个逗号之间的空结果:(?&lt;=,|^)\\s*(?=,|$)
  • 我在这个正则表达式中抛出了一些“困难”的东西来拆分,它已经过去了。一个评论是,这不会取消结果,它实际上只是进行了“拆分”。您仍然需要检查结果值并取消引用和取消转义。此外,我不是 100% 有信心这将涵盖所有内容……但找到证据比我想象的要难。仍然,赞成,印象深刻....谢谢。
  • @rolfl:谢谢,我可以确认它将涵盖所有内容。
  • @rolfl:模式不移除引号的原因只是为了支持这种项目:abc'def'ghi
【解决方案2】:

使用正则表达式拆分 CSV 不是正确的解决方案...这可能就是您努力寻找带有 split/csv/regex 搜索词的解决方案的原因。

使用带有状态机的专用库通常是最佳解决方案。有很多:

我能说的是,正则表达式和 CSV 相对来说会变得非常非常复杂(正如您所发现的那样),而且仅出于性能原因,“原始”解析器会更好。

【讨论】:

【解决方案3】:

如果您正在解析 CVS(或非常类似的东西),那么使用其中一个已建立的框架通常是一个好主意,因为它们涵盖了大多数极端情况,并且经过了更广泛的受众在不同项目中的全面使用测试。

如果库不是你可以选择的,例如这个:

public class Curios {

    public static void main(String[] args) {
        String checkString = "zhg,wimö,'astor wohnideen','multistore 2002',yonza,'asdf, saflk','marc o\\'polo'";
        List<String> result = splitValues(checkString);
        System.out.println(result);

        System.out.println(splitValues("zhg\\,wi\\'mö,'astor wohnideen','multistore 2002',\"yo\\\"nza\",'asdf, saflk\\\\','marc o\\'polo',"));
    }

    public static List<String> splitValues(String checkString) {
        List<String> result = new ArrayList<String>();

        // Used for reporting errors and detecting quotes
        int startOfValue = 0;
        // Used to mark the next character as being escaped
        boolean charEscaped = false;
        // Is the current value quoted?
        boolean quoted = false;
        // Quote-character in use (only valid when quoted == true)
        char quote = '\0';
        // All characters read from current value
        final StringBuilder currentValue = new StringBuilder();

        for (int i = 0; i < checkString.length(); i++) {
            final char charAt = checkString.charAt(i);
            if (i == startOfValue && !quoted) {
                // We have not yet decided if this is a quoted value, but we are right at the beginning of the next value
                if (charAt == '\'' || charAt == '"') {
                    // This will be a quoted String
                    quote = charAt;
                    quoted = true;
                    startOfValue++;
                    continue;
                }
            }
            if (!charEscaped) {
                if (charAt == '\\') {
                    charEscaped = true;
                } else if (quoted && charAt == quote) {
                    if (i + 1 == checkString.length()) {
                        // So we don't throw an exception
                        quoted = false;
                        // Last value will be added to result outside loop
                        break;
                    } else if (checkString.charAt(i + 1) == ',') {
                        // Ensure we don't parse , again
                        i++;
                        // Add the value to the result
                        result.add(currentValue.toString());
                        // Prepare for next value
                        currentValue.setLength(0);
                        startOfValue = i + 1;
                        quoted = false;
                    } else {
                        throw new IllegalStateException(String.format(
                                "Value was quoted with %s but prematurely terminated at position %d " +
                                        "maybe a \\ is missing before this %s or a , after? " +
                                        "Value up to this point: \"%s\"",
                                quote, i, quote, checkString.substring(startOfValue, i + 1)));
                    }
                } else if (!quoted && charAt == ',') {
                    // Add the value to the result
                    result.add(currentValue.toString());
                    // Prepare for next value
                    currentValue.setLength(0);
                    startOfValue = i + 1;
                } else {
                    // a boring character
                    currentValue.append(charAt);
                }
            } else {
                // So we don't forget to reset for next char...
                charEscaped = false;
                // Here we can do interpolations
                switch (charAt) {
                    case 'n':
                        currentValue.append('\n');
                        break;
                    case 'r':
                        currentValue.append('\r');
                        break;
                    case 't':
                        currentValue.append('\t');
                        break;
                    default:
                        currentValue.append(charAt);
                }
            }
        }
        if(charEscaped) {
            throw new IllegalStateException("Input ended with a stray \\");
        } else if (quoted) {
            throw new IllegalStateException("Last value was quoted with "+quote+" but there is no terminating quote.");
        }

        // Add the last value to the result
        result.add(currentValue.toString());

        return result;
    }

}

为什么不简单地使用正则表达式?

正则表达式不能很好地理解嵌套。虽然 Casimir 的正则表达式确实做得很好,但引用值和未引用值之间的差异更容易以某种形式的状态机建模。您会看到确保您不会意外匹配转义或引用的, 是多么困难。此外,当您已经对每个字符进行评估时,很容易解释转义序列,例如 \n

要注意什么?

  • 我的函数不是为空白环绕值编写的(可以更改)
  • 我的函数将像大多数 C 风格的语言解释器一样,将转义序列 \n\r\t\\ 解释为\x,同时将\x 解读为x(这很容易改变)
  • 我的函数在不带引号的值中接受引号和转义(这很容易更改)
  • 我只进行了一些测试,并尽我所能展示了良好的内存管理和时序,但您需要查看它是否符合您的需求。

【讨论】:

    猜你喜欢
    • 2017-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-25
    相关资源
    最近更新 更多