从通配符到正则表达式

我想允许两个主要的通配符?*来过滤我的数据。

以下是我现在正在做的事情(正如我在许多网站上看到的):

 public boolean contains(String data, String filter) { if(data == null || data.isEmpty()) { return false; } String regex = filter.replace(".", "[.]") .replace("?", ".") .replace("*", ".*"); return Pattern.matches(regex, data); } 

但是我们不应该逃避所有其他的正则表达式特殊字符,比如|(等等?还有,也许我们可以保留?*如果它们前面有一个\ ?例如,类似于:

 filter.replaceAll("([$|\\[\\]{}(),.+^-])", "\\\\$1") // 1. escape regex special chars, but ?, * and \ .replaceAll("([^\\\\]|^)\\?", "$1.") // 2. replace any ? that isn't preceded by a \ by . .replaceAll("([^\\\\]|^)\\*", "$1.*") // 3. replace any * that isn't preceded by a \ by .* .replaceAll("\\\\([^?*]|$)", "\\\\\\\\$1"); // 4. replace any \ that isn't followed by a ? or a * (possibly due to step 2 and 3) by \\ 

你怎么看待这件事? 如果您同意,我是否缺少任何其他正则表达式特殊字符?


编辑#1 (在考虑了dan1111和m.buettner的建议之后):

 // replace any even number of backslashes by a * regex = regex.replaceAll("(?<!\\\\)(\\\\\\\\)+(?!\\\\)", "*"); // reduce redundant wildcards that aren't preceded by a \ regex = regex.replaceAll("(?<!\\\\)[?]*[*][*?]+", "*"); // escape regexps special chars, but \, ? and * regex = regex.replaceAll("([|\\[\\]{}(),.^$+-])", "\\\\$1"); // replace ? that aren't preceded by a \ by . regex = regex.replaceAll("(?<!\\\\)[?]", "."); // replace * that aren't preceded by a \ by .* regex = regex.replaceAll("(?<!\\\\)[*]", ".*"); 

这个如何?


编辑#2 (考虑到dan1111的建议后):

 // replace any even number of backslashes by a * regex = regex.replaceAll("(?<!\\\\)(\\\\\\\\)+(?!\\\\)", "*"); // reduce redundant wildcards that aren't preceded by a \ regex = regex.replaceAll("(?<!\\\\)[?]*[*][*?]+", "*"); // escape regexps special chars (if not already escaped by user), but \, ? and * regex = regex.replaceAll("(?<!\\\\)([|\\[\\]{}(),.^$+-])", "\\\\$1"); // replace ? that aren't preceded by a \ by . regex = regex.replaceAll("(?<!\\\\)[?]", "."); // replace * that aren't preceded by a \ by .* regex = regex.replaceAll("(?<!\\\\)[*]", ".*"); 

目标在眼前?

替换字符串中不需要4个反斜杠来写出一个反斜杠。 两个反斜杠就足够了。

并且您可以通过使用负向lookbehind来避免替换字符串中的([^\\\\]|^)$1

 filter.replaceAll("([$|\\[\\]{}(),.+^-])", "\\$1") // 1. escape regex special chars, but ?, * and \ .replaceAll("(? 

我真的没有看到你需要的最后一步。 不会逃脱逃避元字符的反斜杠(反过来,实际上不会逃避它们)。 我忽略了这样一个事实,你的替换呼叫会写出4个反斜杠而不是只有两个。 但是说你的原始输入是th|is 。 然后你的第一个替换将使那个th\|is 。 然后最后的替换将使得th\\|is匹配th -backslash 或者 is

您需要区分字符串在代码中的编写方式(未编译,反斜杠的两倍)以及编译后的内容(仅包含一半反斜杠)。

您可能还想考虑限制可能*的数量。 一个正则表达式,如.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*! (在输入中找不到! )可能需要很长时间才能运行。 这个问题被称为灾难性的回溯 。

最后我采用的解决方案(使用Apache Commons Lang库):

 public static boolean isFiltered(String data, String filter) { // no filter: return true if (StringUtils.isBlank(filter)) { return true; } // a filter but no data: return false else if (StringUtils.isBlank(data)) { return false; } // a filter and a data: else { // case insensitive data = data.toLowerCase(); filter = filter.toLowerCase(); // .matches() auto-anchors, so add [*] (ie "containing") String regex = "*" + filter + "*"; // replace any pair of backslashes by [*] regex = regex.replaceAll("(? 

非常感谢@ dan1111和@ m.buettner的宝贵帮助;)

试试这个更简单的版本:

 String regex = Pattern.quote(filter).replace("*", "\\E.*\\Q").replace("?", "\\E.\\Q"); 

这引用了整个filter的\Q\E ,然后在*?上停止引用 ,用等效的模式替换它们( .*.

我测试了它

 String simplePattern = "ab*g\\Ei\\.lmn?p"; String data = "abcdefg\\Ei\\.lmnop"; String quotedPattern = Pattern.quote(simplePattern); System.out.println(quotedPattern); String regex = quotedPattern.replace("*", "\\E.*\\Q").replace("?", "\\E.\\Q"); System.out.println(regex); System.out.println(data.matches(regex)); 

输出:

 \Qab*g\E\\E\Qi\.lmn?p\E \Qab\E.*\Qg\E\\E\Qi\.lmn\E.\Qp\E true 

请注意,这是基于Oracle的Pattern.quote实现,我不知道是否还有其他有效的实现。