【问题标题】:How to do a complex negative lookbehind to split tokens in Java?如何在 Java 中对标记进行复杂的负面回顾?
【发布时间】:2014-12-20 20:01:04
【问题描述】:

我有许多 EDIFACT 格式的行需要在 + 上进行标记。但是,根据 EDIFACT 规范,可以使用 ? 转义字符。例如:?? 代表 ?,?+ 代表 +,?: 代表 :。 ?+ 是字段的一部分,因此不应被视为分隔符。

我使用否定的lookbehind 来处理+ 后跟?:

delimiter = "\\+";
String[] tokens = data.split("(?<!\\?)" + delimiter);

这会分裂

a+b+c 转换为 a、b 和 c

a?+b+c 转换为 a?+b 和 c

但是,当涉及?? 转义序列时,它会失败:

a??+b+c 产生 2 个令牌:a??+b、c

而实际上应该是 3 个令牌:a?、b 和 c

另一方面:a???+b+c 应该产生两个令牌:a???+b 和 c

有没有办法使用消极的后视来实现这一点?

如果你愿意,这里有一个可运行的测试。

import java.util.Arrays;

public class Main {
   public static void main(String[] args) {
      assertTokens("a+b+c", "a", "b", "c");
      assertTokens("a?+b+c", "a?+b", "c");
      assertTokens("a??+b+c", "a??", "b", "c");
      assertTokens("a???+b+c", "a???+b", "c");
   }

   private static void assertTokens(String data, String... expectedTokens) {
      String delimiter = "\\+";
      String[] tokens = data.split("(?<!\\?)" + delimiter);

      if(!Arrays.deepEquals(tokens, expectedTokens)) {
         throw new IllegalStateException("Not equals for " + data);
      }
   }

}

【问题讨论】:

    标签: java regex tokenize regex-negation


    【解决方案1】:

    与拆分相比,使用匹配更容易标记化。在您的情况下,要使 split 工作,您必须使用 java 不支持的可变长度的lookbehind。

    试试下面的正则表达式:

    (?:[^+:?]++|\?.)+
    

    DEMO

    (我使用所有格量词 (++) 纯粹是为了避免无用的回溯)


    如果你想匹配空标记(a++b yielding、a、空字符串和b),正则表达式会变得更复杂:

    (?:[^+:?\r\n]++|\?.)+|(?<=[+:]|^)(?=[+:]|$)
    

    DEMO

    这意味着

    • 两者都与上述相同(我刚刚将\r\n 添加到组中,因此换行符不匹配)
    • 或一个空字符串,即:
      • 前面有记号分隔符或行首
      • 后跟标记分隔符或行尾

    我已经添加了m 选项,这意味着^ 和$ 匹配每行的开头和结尾。

    【讨论】:

    • 嗨卢卡斯,我确实误会了你,对不起。上面的模式匹配效果很好:) 谢谢。
    • 卢卡斯,当我们在做的时候。 EDIFACT 还允许使用冒号 (:) 进行分隔。在这种情况下,正则表达式会是什么样子?
    • @javacoder 只需将[^+?] 部分替换为[^+:?] - 基本上匹配除语言特殊字符之外的所有内容
    • 对不起,也错误地改变了所有格量词
    • 有没有办法返回空令牌?例如。 a++b 返回 a、空字符串和 b ?
    【解决方案2】:

    供参考:

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class Main {
        public static void main(String[] args) {
            assertTokens("+", "a+b+c", "a", "b", "c");
            assertTokens("+", "a?+b+c", "a?+b", "c");
            assertTokens("+", "a??+b+c", "a??", "b", "c");
            assertTokens("+", "a???+b+c", "a???+b", "c");
            assertTokens("+", "a?'??+b+c", "a?'??", "b", "c");
    
            assertTokens("\\:", "a???:b:c", "a???:b", "c");
            assertTokens("\\:", "a????:b:c", "a????", "b", "c");
        }
    
        private static void assertTokens(String delim, String data, String... expectedTokens) {
            Pattern pattern = Pattern.compile("(?:[^" + delim + ":?]++|\\?.)+");
            Matcher matcher = pattern.matcher(data);
    
            List<String> tokens = new ArrayList<>();
            while (matcher.find()) {
                tokens.add(matcher.group());
            }
    
            if(!Arrays.deepEquals(tokens.toArray(), expectedTokens)) {
                for (String token: tokens) {
                    System.out.println(token);
                }
                throw new IllegalStateException("Not equals for " + data);
            }
        }
    }
    

    【讨论】:

    • 你真的应该使用所有格量词(查看this 和相关的explanation)。
    • 我注意到,改变了答案。感谢您的链接,我会阅读它们。在你的脑海中,你能想出上面例子中的一个模式,如果没有那个肯定量词,它会表现得很糟糕吗?
    • 我想我对这件事的警告太多了...我想不出一个会导致失败的输入,主要是因为真正危险的模式就像(X+)+Y,在你的情况下,没有Y。
    • 除非输入始终有效,否则当您使用正则表达式进行标记时,应使用\G 来确保您不会跳过字符串中的无效语法。
    猜你喜欢
    • 2016-05-19
    • 2013-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多