稍微复杂的正则表达式,以匹配后面的否定表情,然后是一个确切的短语


Slightly complex regex to match a negative look behind followed by an exact phrase

所以我有以下正则表达式:

(?<!'.)'b(['w'@'-]+) *'b(IN|NOT IN|LIKE|NOT LIKE|BETWEEN|REGEXP|NOT|IS|XOR)+'b *

我正在寻找帮助我匹配一些SQL代码。

然而,看起来我在第二个括号里的短语会有问题。'NOT IN' and 'NOT LIKE'

我需要一个匹配或不匹配的正则表达式(不像我当前的正则表达式那样部分匹配)。

  1. customers.id NOT IN (SELECT MAX(customers_service.customer_id))不匹配
  2. customers.id NOT LIKE (SELECT MAX(customers_service.customer_id))不匹配
  3. id NOT IN (SELECT MAX(customers_service.customer_id))应匹配
  4. id IN (SELECT MAX(customers_service.customer_id))应该匹配

我使用RegexBuddy来检查,我使用我的regex得到1号和2号的匹配。

,

  1. id NOT IN (SELECT MAX(customers_service.customer_id))只匹配id NOT,而不是id NOT IN
  2. id NOT LIKE (SELECT MAX(customers_service.customer_id))只匹配id NOT,而不是id NOT LIKE

我想修改这个正则表达式,以捕获后面的负look的条件,以及第二个括号中的确切短语,或者根本不匹配(没有部分)。

我该怎么做呢?

首先,'b不匹配单词的开头或结尾。人们总是这么说,但这是个谎言。'b匹配的位置是后面有一个单词字符,但前面没有一个——(?='w)(?<!'w)——或者前面有一个单词字符,而后面没有一个——(?<='w)(?!'w)。如果这些条件不是您想要匹配的,那么最好不要使用'b

你想要匹配的名字显然可以包含@-以及标准的"单词"字符(字母,数字和下划线),所以单词边界是无用的。一般来说,为了确保匹配一个完整的单词,你会使用否定后看和否定前看:

(?<!['w@-])['w@-]+(?!['w@-])

在您的情况下,您还希望确保前面的字符不是.,并且您知道后面的字符必须是空白,因此您的正则表达式的一部分将是:

(?<![.'w@-])['w@-]+'s+

更大的问题是,这也可以匹配你不希望它匹配的东西——例如:, NOTIN等关键字。我建议采取两种补救措施。首先,收紧关键字的正则表达式,以便将NOT INNOT LIKE等复合关键字视为原子单元:

(?:NOT(?:'s+(?:IN|LIKE))?|IN|LIKE|BETWEEN|REGEXP|IS(?:'s+NOT)?|XOR)'b

第二,在forward中使用它来确保你匹配的第一个单词不是关键字(部分)。下面是完整的正则表达式,为了可读性分成两行:

(?<![.'w@-])(?!(?:NOT(?:'s+(?:IN|LIKE))?|IN|LIKE|BETWEEN|REGEXP|IS|XOR)'b)['w@-]+'s+
(?:NOT(?:'s+(?:IN|LIKE))?|IN|LIKE|BETWEEN|REGEXP|IS|XOR)'b's*

您可以通过为关键字定义子例程组来使其更易于维护。下面是PHP字符串文字的样子:

'~
(?(DEFINE)(?<KEYWORD>
  (?:NOT(?:'s+(?:IN|LIKE))?|IN|LIKE|BETWEEN|REGEXP|IS(?:'s+NOT)?|XOR)'b
))
(?<![.'w@-])(?!(?&KEYWORD))['w@-]+'s+(?&KEYWORD)'s*
~ix'

…这里有一个演示

你的措辞有点令人困惑,但据我所知,消极的目光正如你所期望的那样起作用了。

对于"部分匹配"问题,您只需按长度递减顺序排列关键字:

(?<!'.)'b(['w'@'-]+) *'b(NOT LIKE|BETWEEN|REGEXP|NOT IN|LIKE|NOT|IN|IS|XOR)+'b *

这样,它尝试在满足于较短的关键字之前捕获"更完整"的关键字。

编辑

我现在明白是怎么回事了。对于

customers.id NOT IN (SELECT MAX(customers_service.customer_id))

存在匹配的原因是NOT(?<!'.)'b(['w'@'-]+)匹配,并且IN作为运算符被匹配。换句话说,它认为NOT是一个列名。

解决这个问题的唯一方法是添加约束。例如,如果您知道字符串总是以表/列标识符开始,那么这样做:

^'s+(['w'@'-]+) *'b(NOT LIKE|BETWEEN|REGEXP|NOT IN|LIKE|NOT|IN|IS|XOR)+'b *
****

这样就不需要向后看,也不需要字边界了。

如果你不能做这个约束,那么它是棘手的,如果不是完全不切实际的(因为你基本上必须建立一个SQL解析器的正则表达式)。关键是给你的正则表达式一些区分标识符和操作符的方法;否则它无法判断。如果您知道您的所有标识符都是小写的,那么这可能符合您的目的,尽管这很脆弱。

那好吧。因此,经过多次"正则化",这里的正则表达式为我做到了:

(?<='s)(?!(?:not|is)(?='s))(['w'@'-]+)(?='s) (?<='s)(NOT LIKE|NOT IN|IS NOT|BETWEEN|REGEXP|LIKE|XOR|NOT|IN|IS)(?='s)

当然,在我的preg函数中,我会使用不区分大小写的模式修饰符。

我不得不从我在StackOverflow上发布的其他问题中找到其他部分。

欢呼。