【问题标题】:Combined positive lookbehind and lookahead结合积极的后视和前瞻
【发布时间】:2015-02-03 02:32:23
【问题描述】:

我想从自定义键值协议中解析一个数组。看起来是这样的

RESPONSE GAMEINFO OK
NAME: "gamelobby"
PLAYERS: "alice", "bob", "hodor"
FLAGS: 1, 2, 3

在 Java 中,字符串看起来像这样(它使用 CRLF 作为换行符):

RESPONSE GAMEINFO OK\\r\\nNAME: \"gamelobby\"\\r\\nPLAYERS: \"alice\", \"bob\", \"hodor\"FLAGS: 1, 2, 3\\r\\n

我想按原样捕获"alice", "bob", "hodor"。所以我使用了这个正则表达式,它在 Sublime Text 和 regex101.com 上进行了测试(键不区分大小写)

(?<=(?i:PLAYERS): )([A-Za-z0-9\s\.,:;\?!\n"_-]*)(?=\r\n)

这是来自 Sublime Text 的截图(注意:我在这里省略了 \r):

当我尝试捕获该组时,我也得到了下一行:

Pattern p = Pattern.compile("(?<=(?i:"+key+"): )([A-Za-z0-9\\s\\.,:;\\?!\\n\"_-]*)(?=\\r\\n)");
Matcher matcher = p.matcher(message);
matcher.find();
String value = new String();
try {
    value = matcher.group(); // = "\"alice\", \"bob\", \"hodor\"\\r\\nFLAGS: 1, 2, 3"
} ...

注意:\"\\\" 似乎没有区别。

为什么FLAGS: 1, 2, 3 被捕获到\\r\\n,而不是在上面的行中?积极的后视和前瞻可能吗?首先评估哪个前瞻/后视?

编辑:字符串数组的定义是

values        = string*("," WSP string)
string        = DQUOTE *(ALPHA / DIGIT / WSP / punctuation / "\n") DQUOTE
punctuation   = "." / ":" / "," / ";" / "?" / "!" / "-" / "_"

【问题讨论】:

  • 它在 SublimeText 中有效,因为最后一行没有新行,而您在 Java 中的输入有。如果您可以假设新行永远不会出现在PLAYERS 行中,那么您应该从您的正则表达式中删除\s\n,如果您使用的是Java 8,则可能替换为\h
  • 我不能假设,我捕获下一行的核心问题仍然存在
  • 所以你不能假设 PLAYER 字段永远不能跨越 2 行或更多?那么,解析它将是一项艰巨的任务。有了这个假设,您可以只拆分输入并逐行解析。
  • 对于昵称限制存在(字母数字),我可以在这个例子中假设它。尽管如此,该协议允许使用\n 进行换行的字符串,并且也允许使用字符串数组。
  • 也许我误解了一些东西,但你不能在括号表达式上使用非贪婪乘数吗?见regex101.com/r/hN6nB5/1

标签: java regex lookahead lookbehind


【解决方案1】:

只需根据您的语法编写代码即可。语法对我来说似乎并不模棱两可,所以如果你只是按照它并逐个编写你的正则表达式,你会没事的:

String WHITESPACE_RE = "[ ]"; // Modify this according to your grammar
String PUNCTUATION_RE = "[.:,;?!_-]";
String STRING_RE = "\"(?:[A-Za-z0-9" + WHITESPACE_RE + PUNCTUATION_RE + "\n])*\"";
String VALUES_RE = STRING_RE + "(?:," + WHITESPACE_RE + STRING_RE + ")*";
String PLAYERS_RE = "PLAYERS:" +  WHITESPACE_RE + "(" + VALUES_RE + ")(?=\r\n)";

目前,\r\n 用于检查 PLAYERS 条目末尾的行分隔符。将其更改为您的规范中指定的任何内容。

警告

此解决方案仅适用于解析有效输入。解析无效输入取决于您的恢复算法和行分隔符。

如果行分隔符允许\n\r\n,则很难从错误中恢复。例如,如果有一个名为ABC\nFLAGS: 1, 2, 3的用户(根据语法允许),但缺少右双引号,则玩家列表将被破坏,您将无法判断FLAGS:是否是一部分上一行或不同的标题。

RESPONSE GAMEINFO OK
NAME: "gamelobby"
PLAYERS: "alice", "bob", "hodor", "ABC
FLAGS: 1, 2, 3
FLAGS: 1, 2, 3

完整示例

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SO28290386 {
    public static void main(String[] args) {
        String WHITESPACE_RE = "[ ]"; // Modify this according to your grammar
        String PUNCTUATION_RE = "[.:,;?!_-]";
        String STRING_RE = "\"(?:[A-Za-z0-9" + WHITESPACE_RE + PUNCTUATION_RE + "\n])*\"";
        String VALUES_RE = STRING_RE + "(?:," + WHITESPACE_RE + STRING_RE + ")*";
        String PLAYERS_RE = "PLAYERS:" +  WHITESPACE_RE + "(" + VALUES_RE + ")(?=\r\n)";
        System.out.println(PLAYERS_RE);

        String input = "RESPONSE GAMEINFO OK\r\nNAME: \"gamelobby\"\r\nPLAYERS: \"alice\", \"bob\", \"hodor\", \"new\nline\"\r\nFLAGS: 1, 2, 3\r\n";
        System.out.println("INPUT");
        System.out.println(input);

        Pattern p = Pattern.compile(PLAYERS_RE);
        Matcher m = p.matcher(input);
        while (m.find()) {
            System.out.println(m.group(0));
            System.out.println(m.group(1));
        }
    }
}

【讨论】:

  • 你为什么用(?=\r\n|[\r\n]);有区别吗?
  • @joschuck:使用规范中指定的任何内容。你没有指定这部分。如果是\r\n,则只使用\r\n
【解决方案2】:

您可以在括号表达式上使用非贪婪乘数:

(?<=(?i:PLAYERS): )([A-Za-z0-9\s\.,:;\?!\n"_-]*?)(?=\r\n)

当您使用贪婪乘法器* 时,匹配不会在\r\n 处停止的原因是因为括号表达式包含\s\s 的定义(根据 Pattern 类的文档)是 [ \t\n\x0B\f\r],因此括号表达式实际上是通过 CRLF 行终止符和其路径中的所有其他内容,直到它到达整个字符串的末尾.

我想如果您可以明确地阻止单独的 CR 出现在引用的单词列表中,那么另一个可行的解决方案是将 \s 替换为明确的 [\n\t\f ],但我会留到你。

非贪婪乘数 *? 解决方案有效,因为当正则表达式引擎命中第一个 CRLF 以满足最终的前瞻断言时,它会停止匹配,即使括号表达式可能会吞噬它。

test code on regex101 在字符串包含新行的情况下失败,因为该站点似乎不支持 CR,因此我们无法真正在那里进行完整测试。但在 Java 代码中真正的正则表达式中,前瞻断言需要 CRLF 来终止搜索,因此它最终会匹配整个引用词列表。

【讨论】:

  • 您当前的建议有效,但您指的是我在问题中关于您损坏的正则表达式的评论。请保持评论讨论到评论。
猜你喜欢
  • 2012-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多