正则表达式找到令牌 - Java Scanner或其他替代品

时间:2013-06-01 08:39:55

标签: java regex parsing tokenize

您好我正在尝试编写一个将一些文本转换为定义明确的标记的类。

字符串有点类似于以下代码:(brown) "fox" 'c';。我想得到的是(扫描仪中的一个令牌或切割后的数组我认为两者都可以正常工作)( , brown , ) , "fox" , 'c' , ;分开(因为它们是潜在的令牌),其中包括:

  • 引用'"
  • 的文字
  • 带或不带小数点的数字
  • 括号,大括号,分号,等号,锐利,||,< =,&&

目前我正在使用扫描仪,我遇到了一些问题,分隔符无法单独给我()等,所以我现在使用了以下分隔符\s+|(?=[;\{\}\(\)]|\b)"'作为单独的令牌以及我真的想避免它,我已经尝试为"的变体添加一些负面的前瞻但没有运气。

我尝试过使用StreamTokenizer,但它没有保留不同的引号..

P.S。 我确实在网站上搜索并试图谷歌它,但即使有很多与扫描仪相关/正则表达式相关的问题,我找不到能解决我问题的东西。

编辑1: 到目前为止,我想出了\s+|^|(?=[;{}()])|(?<![.\-/'"])(?=\b)(?![.\-/'"]) 我可能不够清楚但是什么时候 我有一些事情:

"foo";'bar')(;{

gray fox=-56565.4546;

foo boo="hello"{

我想得到:

"foo";'bar')(;{

grayfox=-56565.4546;

fooboo="hello"{

但我有:

"foo";'bar')(;{

grayfox=-56565.4546;

fooboo="hello"{

请注意,当=与其他人之间有空格时,例如:gray fox = -56565.4546;会导致:

grayfox=-56565.4546;

我正在对上面提到的正则表达式做的是:

Scanner scanner = new Scanner(line);
    scanner.useDelimiter(MY_MENTIONED_REGEX_HERE);
    while (scanner.hasNext()) {
       System.out.println("Got: `" + scanner.next() +"`");
       //Some work here

}

4 个答案:

答案 0 :(得分:3)

描述

由于您正在查找可能包含小数点的所有字母数字文本,为什么不“忽略”分隔符?以下正则表达式将从输入字符串中提取带小数点块的所有字母数字。这是有效的,因为您的示例文本是:

"foo";'bar')(;{
gray fox=-56565.4546;
foo boo="hello"{

正则表达式:(?:(["']?)[-]?[a-z0-9-.]*\1|(?<=[^a-z0-9])[^a-z0-9](?=(?:[^a-z0-9]|$))|(?<=[a-z0-9"'])[^a-z0-9"'](?=(?:[^a-z0-9]|['"]|$)))

enter image description here

摘要

正则表达式有三条路径:

  1. (["']?)[-]?[a-z0-9-.]*\1捕获一个开放的引号,然后是一个减号(如果它存在),后跟一些文本或数字,这一直持续到达到收盘价。这会捕获带小数点的任何文本或数字。这些数字未经过验证,因此12.32.1会匹配。如果您的输入文字还包含前缀为加号的数字,请将[-]更改为[+-]
  2. (?<=[^a-z0-9])[^a-z0-9](?=(?:[^a-z0-9]|$))如果前一个字符是符号,则查找非字母数字,并且此字符是符号,下一个字符也是字符串的符号或结尾,然后获取当前符号。这会捕获任何不是引号的自由浮动符号,或者像)(;{这样的行中的多个符号。
  3. (?<=[a-z0-9"'])[^a-z0-9"'](?=(?:[^a-z0-9]|['"]|$)))如果当前字符不是字母数字或引号,则查找字母数字或引号,并查看非字母数字,非引号或行尾。这会捕获引用之后的任何符号,这些符号不会被之前的表达式捕获,例如{之后的"Hello"
  4. 完整解释

    • (?:启动非组捕获语句。在此组中,每个替代项由一个或|字符分隔
      1. 第一种选择:(["']?)[-]?[a-z0-9-.]*\1
        • 第一个捕获小组(["']?)
        • Char class ["'] 1到0次匹配以下字符之一:"'
        • Char class [-] 1到0次匹配以下字符之一:-
        • Char class [a-z0-9-.]无限到0次匹配以下字符之一:a-z0-9-.
        • \1匹配BackRef 1
        • 中保存的文字
      2. 第二种选择:(?<=[^a-z0-9])[^a-z0-9](?=(?:[^a-z0-9]|$))
        • (?<=[^a-z0-9])正面LookBehind
        • 否定字符组[^a-z0-9]匹配除a-z0-9
        • 之外的所有字符
        • 否定字符组[^a-z0-9]匹配除a-z0-9
        • 之外的所有字符
        • (?=(?:[^a-z0-9]|$))正面LookAhead,每个子替代品都由一个或|字符分隔
        • 群组(?:[^a-z0-9]|$)
        • 第一种选择:[^a-z0-9]
        • 否定字符组[^a-z0-9]匹配除a-z0-9
        • 之外的所有字符
        • 第二种选择:$字符串结尾
      3. 第三种选择:(?<=[a-z0-9"'])[^a-z0-9"'](?=(?:[^a-z0-9]|['"]|$))
        • (?<=[a-z0-9"'])正面LookBehind
        • Char class [a-z0-9"']匹配以下字符之一:a-z0-9"'
        • 否定字符组[^a-z0-9"']匹配除a-z0-9"'
        • 之外的所有字符
        • (?=(?:[^a-z0-9]|['"]|$))正面LookAhead,每个子替代品都由一个或|字符分隔
        • 群组(?:[^a-z0-9]|['"]|$)
        • 第一种选择:[^a-z0-9]
        • 否定字符组[^a-z0-9]匹配除a-z0-9
        • 之外的所有字符
        • 第二种选择:['"]
        • Char class ['"]匹配以下字符之一:'"
        • 第三种选择:$字符串结尾
    • )结束非群组捕获声明

    组0获取整个匹配的字符串,而组1获取引用分隔符(如果存在)以确保它与匹配的引号匹配。

    Java代码示例:

    注意数组中的一些空值来自新行字符,有些是从表达式中引入的。您可以应用表达式和一些基本逻辑来确保输出数组只有非空值。

    import java.util.regex.Pattern;
    import java.util.regex.Matcher;
    class Module1{
      public static void main(String[] asd){
      String sourcestring = "\"foo\";'bar')(;{
    gray fox=-56565.4546;
    foo boo=\"hello\"{";
      Pattern re = Pattern.compile("(?:(["']?)[-]?[a-z0-9-.]*\1|(?<=[^a-z0-9])[^a-z0-9](?=(?:[^a-z0-9]|$))|(?<=[a-z0-9"'])[^a-z0-9"'](?=(?:[^a-z0-9]|['"]|$)))",Pattern.CASE_INSENSITIVE);
      Matcher m = re.matcher(sourcestring);
      int mIdx = 0;
        while (m.find()){
          for( int groupIdx = 0; groupIdx < m.groupCount()+1; groupIdx++ ){
            System.out.println( "[" + mIdx + "][" + groupIdx + "] = " + m.group(groupIdx));
          }
          mIdx++;
        }
      }
    }
    
     $matches Array:
    (
        [0] => Array
            (
                [0] => "foo"
                [1] => 
                [2] => ;
                [3] => 'bar'
                [4] => 
                [5] => )
                [6] => 
                [7] => (
                [8] => 
                [9] => ;
                [10] => 
                [11] => {
                [12] => 
                [13] => 
                [14] => 
                [15] => gray
                [16] => 
                [17] => fox
                [18] => 
                [19] => =
                [20] => -56565.4546
                [21] => 
                [22] => ;
                [23] => 
                [24] => 
                [25] => 
                [26] => foo
                [27] => 
                [28] => boo
                [29] => 
                [30] => =
                [31] => "hello"
                [32] => 
                [33] => {
                [34] => 
            )
    
        [1] => Array
            (
                [0] => "
                [1] => 
                [2] => 
                [3] => '
                [4] => 
                [5] => 
                [6] => 
                [7] => 
                [8] => 
                [9] => 
                [10] => 
                [11] => 
                [12] => 
                [13] => 
                [14] => 
                [15] => 
                [16] => 
                [17] => 
                [18] => 
                [19] => 
                [20] => 
                [21] => 
                [22] => 
                [23] => 
                [24] => 
                [25] => 
                [26] => 
                [27] => 
                [28] => 
                [29] => 
                [30] => 
                [31] => "
                [32] => 
                [33] => 
                [34] => 
            )
    
    )
    

答案 1 :(得分:1)

这个想法是从特定情况开始到一般情况。试试这个表达式:

Java string:
"([\"'])(?:[^\"']+|(?!\\1)[\"'])*\\1|\\|\\||<=|&&|[()\\[\\]{};=#]|[\\w.-]+"

Raw pattern:
(["'])(?:[^"']+|(?!\1)["'])*\1|\|\||<=|&&|[()\[\]{};=#]|[\w.-]+

此处的目标不是使用hypotetic分隔符进行拆分,而是逐个实体匹配。请注意,替代顺序定义了优先级(您不能在=之前放置=>

新规范的示例(需要导入Pattern&amp; Matcher):

String s = "(brown) \"fox\" 'c';foo bar || 55.555;\"foo\";'bar')(;{ gray fox=-56565.4546; foo boo=\"hello\"{";
Pattern p = Pattern.compile("([\"'])(?:[^\"']+|(?!\\1)[\"'])*\\1|\\|\\||<=|&&|[()\\[\\]{};=#]|[\\w.-]+");
Matcher m = p.matcher(s) ;  

 while (m.find()) {
    System.out.println("item = `" + m.group() + "`");
}

答案 2 :(得分:0)

你的问题很大程度上是你试图用一个正则表达式做太多,因此无法理解该部分的相互作用。作为人类,我们都有这个麻烦。

您正在做的事情在编译器业务中有一个标准处理,称为“lexing”。词法分析器生成器接受您感兴趣的每个单个标记的正则表达式,并构建一组复杂的状态,如果它们是可区分的,将挑选出单个词位。每个令牌单独的词汇定义使得它们易于单独编写并且不会令人困惑。词法分析器使识别所有成员变得“简单”和高效。 (如果要定义包含特定引号的词法,则很容易做到这一点。)

查看广泛使用的任何解析器生成器;它们都包括lexing引擎,例如JCup,ANTLR,JavaCC,......

答案 3 :(得分:0)

也许使用像JFLex这样的扫描仪生成器,实现目标比使用正则表达式更容易。

即使您更喜欢手动编写代码,我认为将其结构化得更好一些。一个简单的解决方案是创建单独的方法,尝试从文本中“消耗”您想要识别的不同类型的标记。每种这样的方法都可以判断它是否成功。这样你就有了几个较小的代码块,可以用于不同的令牌,而不仅仅是一段难以理解和编写的大代码。