复杂正则表达式从字符串中获取值

时间:2009-11-10 13:16:25

标签: java regex

以下是一些输入样本:

1,2,3 'a','b','c'
'A', 'B', 'C'
1,'a','b'

字符串在它们周围有单引号,数字没有。在字符串中,双引号''(即两次')是单引号的转义字符'。以下也有效输入。

'这''是''字符串',1,2 '''这个''很奇怪',1,2 ''''''','1,

在玩了一段时间之后,我最终得到了类似的东西:

^(\\d*|(?:(?:')([a-zA-Z0-9]*)(?:')))(?:(?:, *)(\\d*|(?:(?:')([a-zA-Z0-9]*)(?:'))))*$

完全不起作用且不完整:)

使用Java matcher / group的例子如下:
输入:'''la''la',1,3
匹配组:

  • 'la'la
  • 1
  • 2

请注意,输出字符串周围没有单引号,只是输入中的转义引号。

那里有任何正则表达的大师吗?感谢
PS:如果我自己弄清楚,仍然会尝试

,我会告诉你的

4 个答案:

答案 0 :(得分:2)

所有示例字符串都满足以下正则表达式:

('(''|[^'])*'|\d+)(\s*,\s*('(''|[^'])*'|\d+))*

含义:

(               # open group 1
  '             #   match a single quote
  (''|[^'])*    #   match two single quotes OR a single character other than a single quote, zero or more times
  '             #   match a single quote
  |             #   OR
  \d+           #   match one or more digits
)               # close group 1
(               # open group 3
  \s*,\s*       #   match a comma possibly surrounded my white space characters
  (             #   open group 4
    '           #     match a single quote
    (''|[^'])*  #     match two single quotes OR a single character other than a single quote, zero or more times
    '           #     match a single quote
    |           #     OR
    \d+         #     match one or more digits
  )             #   close group 4
)*              # close group 3 and repeat it zero or more times

一个小型演示:

import java.util.*;
import java.util.regex.*;

public class Main { 

    public static List<String> tokens(String line) {
        if(!line.matches("('(''|[^'])*'|\\d+)(\\s*,\\s*('(''|[^'])*'|\\d+))*")) {
            return null;
        }
        Matcher m = Pattern.compile("'(''|[^'])*+'|\\d++").matcher(line);
        List<String> tok = new ArrayList<String>();
        while(m.find()) tok.add(m.group());
        return tok;
    }

    public static void main(String[] args) {
        String[] tests = {
                "1, 2, 3",
                "'a', 'b',    'c'",
                "'a','b','c'",
                "1, 'a', 'b'",
                "'this''is''one string', 1, 2",
                "'''this'' is a weird one', 1, 2",
                "'''''''', 1, 2",
                /* and some invalid ones */
                "''', 1, 2",
                "1 2, 3, 4, 'aaa'",
                "'a', 'b', 'c"
        };
        for(String t : tests) {
            System.out.println(t+" --tokens()--> "+tokens(t));
        }
    }
}

输出:

1, 2, 3 --tokens()--> [1, 2, 3]
'a', 'b',    'c' --tokens()--> ['a', 'b', 'c']
'a','b','c' --tokens()--> ['a', 'b', 'c']
1, 'a', 'b' --tokens()--> [1, 'a', 'b']
'this''is''one string', 1, 2 --tokens()--> ['this''is''one string', 1, 2]
'''this'' is a weird one', 1, 2 --tokens()--> ['''this'' is a weird one', 1, 2]
'''''''', 1, 2 --tokens()--> ['''''''', 1, 2]
''', 1, 2 --tokens()--> null
1 2, 3, 4, 'aaa' --tokens()--> null
'a', 'b', 'c --tokens()--> null

但是,您不能简单地使用现有(并经过验证的)CSV解析器吗?想到Ostermiller's CSV parser

答案 1 :(得分:1)

您的问题是您的输入列表是否保证采用您在此处显示的格式,您只需要将其拆分为单个项目?为此,您可能根本不需要正则表达式。

如果字符串不能包含逗号,只需在逗号上拆分以获取您的个人令牌。然后,对于非数字的令牌,删除开始/结束报价。然后将''替换为'。问题解决了,不需要正则表达式。

答案 2 :(得分:1)

作为一个两步过程,你最好这样做;首先将其分解为字段,然后对每个字段的内容进行后处理。

\s*('(?:''|[^'])*'|\d+)\s*(?:,|$)

应匹配单个字段。然后只需遍历每个匹配(通过交替.find()然后.group(1))来按顺序抓取每个字段。拉出字段值后,您可以将双撇号转换为单个撇号;只需为''做一个简单的字符串替换 - &gt; '

答案 3 :(得分:0)

使用RegExp匹配引用的字符串是一个困难的命题。您的分隔符文本不仅仅是单引号,对您有帮助,实际上它是单引号加上以下之一:逗号,行首,行尾。这意味着背靠背单引号出现在合法条目中的唯一时间将作为字符串转义的一部分。

编写正则表达式来匹配这个对于成功案例来说并不难,但对于失败案例,它可能变得非常具有挑战性。

在匹配之前清理文本可能符合您的最佳利益。将所有\个实例替换为文字\u005c,然后将所有''个实例替换为文字\u0027(按此顺序)。你在这里提供一个转义级别,它留下一个没有特殊字符的字符串。

现在您可以使用简单的模式,例如(?:(?:^\s*|\s*,\s*)(?:'([^']*)'|[^,]*?)))*\s*$

以下是该模式的细分(为清楚起见,我使用术语'set'表示非捕获分组,'group'表示捕获分组):

(?:               Open a non-capturing / alternation set 1
  (?:             Open a non-capturing / alternation set 2
    ^\s*          Match the start of the line and any amount of white space.
    |             alternation (or) for alternation set 2
    \s*,\s*       A comma surrounded by optional whitespace
  )               Close non-capturing group 2 (we don't care about the commas once we've used them to split our data)
  (?:             Open non-capturing set 3
    '([^']*)'     Capturing group #1 matching the quoted string value option.
    |             alternation for set 3.
    ([^,]*?)      Capturing group #2 matching non-quoted entries but not including a comma (you might refine this part of the expression if for example you only want to allow numbers to be non-quoted).  This is a non-greedy match so that it'll stop at the first comma rather than the last comma.
  )               Close non-capturing set 3
)                 Close non-capturing set 1
*                 Repeat the whole set as many times as it takes (the first match will trigger the ^ start of line, the subsequent matches will trigger the ,comma delimiters)
\s*$              Consume trailing spaces until the end of line.

您引用的参数将在捕获组1中,您的非引用参数将在捕获组2中。其他所有参数都将被丢弃。

然后循环匹配的条目并反转编码(将\u0027替换为',将\u005c替换为\,然后您就完成了。

这应该是相当容错的,并且正确地解析一些技术上不正确但可恢复的方案,例如1, a''b, 2,但仍然在诸如1, a'b, 2之类的不可恢复的值上失败,同时在技术上正确成功(但可能是无意的) )条目1, 'ab, 2'