Mongo正则表达式不能通过前缀匹配字符串中的单词

时间:2014-01-14 20:16:38

标签: php regex mongodb

我的mongo数据库中有书籍集合

{
    "title": "Some cool title",
    "authors": [ "Author1", "Author2", ... ],
    ...
}

我想为这些书创建足够智能的搜索引擎。如果用户在搜索输入中键入内容,则会发生以下情况:

  1. 将输入字符串转换为关键字数组
  2. 搜索至少一个关键字与任何作者的标题或名称相匹配的所有文档
  3. 然后我用它做了一些魔术,但我需要帮助的是这个 - 当我说关键字匹配标题/作者时,我的意思是它匹配标题/作者中的某些单词或它的前缀。例如,do会匹配其中包含dodoingdouble但不包含adobadoo的任何字符串。

    我用谷歌搜索它,这应该是正确的方法:

    public function searchBooksByKeywords($keywords) {
    
        array_walk($keywords, function(&$keyword) {
            $keyword = preg_quote($keyword, "/");
        });
    
        $filter = array(
            '$or'      => [
                [ "title"    => new \MongoRegex("/\\b(" . implode('|', $keywords) . ")/i") ],
                [ "authors"   => new \MongoRegex("/\\b(" . implode('|', $keywords) . ")/i") ],
            ]
        );
    
        $books = $this->database->Books->find($filter);
        return \iterator_to_array($books);
    }
    

    它不起作用。对于steal,我仍然得到tea之类的结果,即它甚至匹配单词中的子字符串,而不仅仅是前缀。我迷失在这里......

    BTW,我使用PHP。

    编辑:我发现了问题的可能原因。如果在单词内部匹配,搜索后的单词会在某些非ASCII字符(但可能不是全部)之后立即出现,例如我搜索sto并获得Město & město的结果,用于{{ 1}}它找到了steKroatien Dalmatinische Küste等等。

3 个答案:

答案 0 :(得分:2)

我终于找到了解决方案。我只是将u标志添加到正则表达式。

new \MongoRegex("/\\b(" . implode('|', $keywords) . ")/iu"

PHP文档说

  

此修饰符打开与Perl不兼容的PCRE的其他功能。模式字符串被视为UTF-8。此修饰符可从Unix上的PHP 4.1.0或更高版本以及win32上的PHP 4.2.3获得。从PHP 4.3.5开始检查模式的UTF-8有效性。

可以找到here

答案 1 :(得分:1)

试试这个:

new \MongoRegex("/\\b(" . implode('|', $keywords) . ").*\\b/i")

编辑:

正如OP在他的编辑中提到的,上述正则表达式对于包含非ASCII字符的关键字失败,例如关键字sto匹配Město& městoste匹配Küste,等等。

因此,在这种情况下,我修改了正则表达式如下:

new \MongoRegex("/(?:^|\\s)(" . implode('|', $keywords) . ")/i")

正则表达式示例:http://regex101.com/r/nR9lH6

答案 2 :(得分:0)

在查看您的编辑后,很明显您需要增强限制字的边界 它仅限ASCII字符。有很多方法可以做到这一点。

如果搜索字符串/关键字中的第一个字符可能位于\ x80 - \ xff之间,则需要采用完全不同的方法。希望不是这样的。

 new \MongoRegex("/(?:^|(?<=[\\x00-\\x7f]))(?=[\\x00-\\x7f])\\b(" . implode('|', $keywords) . ")/i")

 # --------------------------------------------
 # Using hex 
 (?:                           # Group start
      ^                             # Beginning of string
   |  (?<= [\x00-\x7f] )            # or, ASCII character behind us
 )                             # Group end
 (?= [\x00-\x7f] )             # ASCII character in front of us
 \b                            # word boundry

 # --------------------------------------------
 # Using Posix 
 (?:                           # Group start
      ^                             # Beginning of string
   |  (?<= [[:ascii:]] )            # or, ASCII character behind us
 )                             # Group end
 (?= [[:ascii:]] )             # ASCII character in front of us
 \b                            # word boundry