草庐IT

php - 将 Google 搜索查询转换为 PostgreSQL "tsquery"

coder 2024-04-18 原文

如何将 Google 搜索查询转换为可以提供给 PostgreSQL 的 to_tsquery() 的内容?

如果没有现成的库,我应该如何使用 PHP 等语言解析 Google 搜索查询?

例如,我想采用以下类似 Google 的搜索查询:

("used cars" OR "new cars") -ford -mistubishi

并将其转换为对 to_tsquery() 友好的字符串:

('used cars' | 'new cars') & !ford & !mistubishi

我可以用正则表达式捏造这个,但这是我能做的最好的。是否有一些强大的词法分析方法可以解决这个问题?我也希望能够支持扩展搜索运算符(如 Google 的 site: 和 intitle:),它们将应用于不同的数据库字段,因此需要与 tsquery 字符串分开。

更新:我意识到使用特殊运算符,这变成了 Google 到 SQL WHERE 子句的转换,而不是 Google 到 tsquery 的转换。但是 WHERE 子句可能包含一个或多个 tsqueries。

例如,Google 风格的查询:

((color:blue OR "4x4") OR style:coupe) -color:red used

应该产生这样的 SQL WHERE 子句:

WHERE to_tsvector(description) MATCH to_tsquery('used')
  AND color <> 'red'
  AND ( (color = 'blue' OR to_tsvector(description) MATCH to_tsquery('4x4') )
    OR style = 'coupe'
  );

我不确定以上是否可以用正则表达式实现?

最佳答案

老实说,我认为正则表达式是解决此类问题的方法。同样,这是一个有趣的练习。下面的代码非常原型(prototype)——事实上,你会看到我什至没有实现词法分析器本身——我只是伪造了输出。我想继续,但我今天没有更多的空闲时间。

此外,在支持其他类型的搜索运算符等方面肯定还有很多工作要做。

基本上,这个想法是对某种类型的查询进行词法分析,然后将其解析为一种通用格式(在本例中为 QueryExpression 实例),然后将其呈现为另一种类型的查询。

<?php

ini_set( "display_errors", "on" );
error_reporting( E_ALL );

interface ILexer
{
    public function execute( $str );
    public function getTokens();
}

interface IParser
{
    public function __construct( iLexer $lexer );
    public function parse( $input );
    public function addToken( $token );
}

class GoogleQueryLexer implements ILexer
{
    private $tokenStack = array();

    public function execute( $str )
    {
        $chars = str_split( $str );
        foreach ( $chars as $char )
        {
            //  add to self::$tokenStack per your rules
        }

        //'("used cars" OR "new cars") -ford -mistubishi'
        $this->tokenStack = array(
                '('
            ,   'used cars'
            ,   'or new cars'
            ,   ')'
            ,   '-ford'
            ,   '-mitsubishi'
        );
    }

    public function getTokens()
    {
        return $this->tokenStack;
    }
}

class GoogleQueryParser implements IParser
{
    protected $lexer;

    public function __construct( iLexer $lexer )
    {
        $this->lexer = $lexer;
    }

    public function addToken( $token )
    {
        $this->tokenStack[] = $token;
    }

    public function parse( $input )
    {
        $this->lexer->execute( $input );
        $tokens = $this->lexer->getTokens();

        $expression = new QueryExpression();

        foreach ( $tokens as $token )
        {
            $expression = $this->processToken( $token, $expression );
        }

        return $expression;
    }

    protected function processToken( $token, QueryExpression $expression )
    {
        switch ( $token )
        {
            case '(':
                return $expression->initiateSubExpression();
                break;
            case ')':
                return $expression->getParentExpression();
                break;
            default:
                $modifier   = $token[0];
                $phrase     = substr( $token, 1 );
                switch ( $modifier )
                {
                    case '-':
                        $expression->addExclusionPhrase( $phrase );
                        break;
                    case '+':
                        $expression->addPhrase( $phrase );
                        break;
                    default:
                        $operator   = trim( substr( $token, 0, strpos( $token, ' ' ) ) );
                        $phrase     = trim( substr( $token, strpos( $token, ' ' ) ) );
                        switch ( strtolower( $operator ) )
                        {
                            case 'and':
                                $expression->addAndPhrase( $phrase );
                                break;
                            case 'or':
                                $expression->addOrPhrase( $phrase );
                                break;
                            default:
                                $expression->addPhrase( $token );
                        }
                }
        }
        return $expression;
    }
}

class QueryExpression
{
    protected $phrases = array();
    protected $subExpressions = array();
    protected $parent;

    public function __construct( $parent=null )
    {
        $this->parent = $parent;
    }

    public function initiateSubExpression()
    {
        $expression = new self( $this );
        $this->subExpressions[] = $expression;
        return $expression;
    }

    public function getPhrases()
    {
        return $this->phrases;
    }

    public function getSubExpressions()
    {
        return $this->subExpressions;
    }

    public function getParentExpression()
    {
        return $this->parent;
    }

    protected function addQueryPhrase( QueryPhrase $phrase )
    {
        $this->phrases[] = $phrase;
    }

    public function addPhrase( $input )
    {
        $this->addQueryPhrase( new QueryPhrase( $input ) );
    }

    public function addOrPhrase( $input )
    {
        $this->addQueryPhrase( new QueryPhrase( $input, QueryPhrase::MODE_OR ) );
    }

    public function addAndPhrase( $input )
    {
        $this->addQueryPhrase( new QueryPhrase( $input, QueryPhrase::MODE_AND ) );
    }

    public function addExclusionPhrase( $input )
    {
        $this->addQueryPhrase( new QueryPhrase( $input, QueryPhrase::MODE_EXCLUDE ) );
    }
}

class QueryPhrase
{
    const MODE_DEFAULT = 1;
    const MODE_OR = 2;
    const MODE_AND = 3;
    const MODE_EXCLUDE = 4;

    protected $phrase;
    protected $mode;

    public function __construct( $input, $mode=self::MODE_DEFAULT )
    {
        $this->phrase = $input;
        $this->mode = $mode;
    }

    public function getMode()
    {
        return $this->mode;
    }

    public function __toString()
    {
        return $this->phrase;
    }
}

class TsqueryBuilder
{
    protected $expression;
    protected $query;

    public function __construct( QueryExpression $expression )
    {
        $this->query = trim( $this->processExpression( $expression ), ' &|' );
    }

    public function getResult()
    {
        return $this->query;
    }

    protected function processExpression( QueryExpression $expression )
    {
        $query = '';
        $phrases = $expression->getPhrases();
        $subExpressions = $expression->getSubExpressions();

        foreach ( $phrases as $phrase )
        {
            $format = "'%s' ";
            switch ( $phrase->getMode() )
            {
                case QueryPhrase::MODE_AND :
                    $format = "& '%s' ";
                    break;
                case QueryPhrase::MODE_OR :
                    $format = "| '%s' ";
                    break;
                case QueryPhrase::MODE_EXCLUDE :
                    $format = "& !'%s' ";
                    break;
            }
            $query .= sprintf( $format, str_replace( "'", "\\'", $phrase ) );
        }

        foreach ( $subExpressions as $subExpression )
        {
            $query .= "& (" . $this->processExpression( $subExpression ) . ")";
        }
        return $query;
    }
}

$parser = new GoogleQueryParser( new GoogleQueryLexer() );

$queryBuilder = new TsqueryBuilder( $parser->parse( '("used cars" OR "new cars") -ford -mistubishi' ) );

echo $queryBuilder->getResult();

关于php - 将 Google 搜索查询转换为 PostgreSQL "tsquery",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/207817/

有关php - 将 Google 搜索查询转换为 PostgreSQL "tsquery"的更多相关文章

  1. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  2. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  3. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  4. ruby - ECONNRESET (Whois::ConnectionError) - 尝试在 Ruby 中查询 Whois 时出错 - 2

    我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.

  5. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  6. ruby - 将数组的内容转换为 int - 2

    我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

  7. ruby - 将散列转换为嵌套散列 - 2

    这道题是thisquestion的逆题.给定一个散列,每个键都有一个数组,例如{[:a,:b,:c]=>1,[:a,:b,:d]=>2,[:a,:e]=>3,[:f]=>4,}将其转换为嵌套哈希的最佳方法是什么{:a=>{:b=>{:c=>1,:d=>2},:e=>3,},:f=>4,} 最佳答案 这是一个迭代的解决方案,递归的解决方案留给读者作为练习:defconvert(h={})ret={}h.eachdo|k,v|node=retk[0..-2].each{|x|node[x]||={};node=node[x]}node[

  8. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  9. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

  10. ruby-on-rails - 在 Rails 和 ActiveRecord 中查询时忽略某些字段 - 2

    我知道我可以指定某些字段来使用pluck查询数据库。ids=Item.where('due_at但是我想知道,是否有一种方法可以指定我想避免从数据库查询的某些字段。某种反拔?posts=Post.where(published:true).do_not_lookup(:enormous_field) 最佳答案 Model#attribute_names应该返回列/属性数组。您可以排除其中一些并传递给pluck或select方法。像这样:posts=Post.where(published:true).select(Post.attr

随机推荐