结合积极的后视和前视
Combined positive lookbehind and lookahead
我想从自定义键值协议解析数组。看起来像这样
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 = "." / ":" / "," / ";" / "?" / "!" / "-" / "_"
您可以在括号表达式上使用非贪婪乘法器:
(?<=(?i:PLAYERS): )([A-Za-z0-9\s\.,:;\?!\n"_-]*?)(?=\r\n)
使用贪心乘数*
时匹配不会在\r\n
处停止的原因是括号表达式包含\s
。 \s
的定义(根据 Pattern
class 的文档)是 [ \t\n\x0B\f\r]
,因此括号表达式实际上是通过 CRLF 行终止符及其路径中的所有其他内容,直到它到达整个字符串的末尾。
我想如果您可以明确阻止单独的 CR 出现在引用词列表中,那么另一个可行的解决方案是用明确的 [\n\t\f ]
替换 \s
,但我就交给你了。
非贪婪乘法器 *?
解决方案之所以有效,是因为当正则表达式引擎命中第一个 CRLF 以满足最终的先行断言时,它会停止匹配,即使括号表达式可能会吞噬它。
对于字符串包含新行的情况,test code on regex101 失败,因为该站点似乎不支持 CR,因此我们无法真正在那里进行完整测试。但在 Java 代码中的真实正则表达式中,前瞻断言需要 CRLF 来终止搜索,因此它最终会匹配整个引用词列表。
按照你的语法编写代码即可。语法对我来说似乎没有歧义,所以如果你只是遵循它并一块一块地编写你的正则表达式,你会没事的:
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:
是上一行的一部分或不同的 header.
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));
}
}
}
我想从自定义键值协议解析数组。看起来像这样
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 = "." / ":" / "," / ";" / "?" / "!" / "-" / "_"
您可以在括号表达式上使用非贪婪乘法器:
(?<=(?i:PLAYERS): )([A-Za-z0-9\s\.,:;\?!\n"_-]*?)(?=\r\n)
使用贪心乘数*
时匹配不会在\r\n
处停止的原因是括号表达式包含\s
。 \s
的定义(根据 Pattern
class 的文档)是 [ \t\n\x0B\f\r]
,因此括号表达式实际上是通过 CRLF 行终止符及其路径中的所有其他内容,直到它到达整个字符串的末尾。
我想如果您可以明确阻止单独的 CR 出现在引用词列表中,那么另一个可行的解决方案是用明确的 [\n\t\f ]
替换 \s
,但我就交给你了。
非贪婪乘法器 *?
解决方案之所以有效,是因为当正则表达式引擎命中第一个 CRLF 以满足最终的先行断言时,它会停止匹配,即使括号表达式可能会吞噬它。
对于字符串包含新行的情况,test code on regex101 失败,因为该站点似乎不支持 CR,因此我们无法真正在那里进行完整测试。但在 Java 代码中的真实正则表达式中,前瞻断言需要 CRLF 来终止搜索,因此它最终会匹配整个引用词列表。
按照你的语法编写代码即可。语法对我来说似乎没有歧义,所以如果你只是遵循它并一块一块地编写你的正则表达式,你会没事的:
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:
是上一行的一部分或不同的 header.
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));
}
}
}