Pergunta

Como posso converter uma consulta de pesquisa Google para algo que pode alimentar to_tsquery do PostgreSQL ()?

Se não há nenhuma biblioteca existente lá fora, como eu devo ir sobre como analisar uma consulta de pesquisa Google em uma linguagem como PHP?

Por exemplo, eu gostaria de ter o seguinte Google ish-consulta de pesquisa:

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

E transformá-lo em um to_tsquery () - seqüência friendly:

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

Eu posso falsificar isso com expressões regulares, mas isso é o melhor que posso fazer. Existe algum método de análise lexical robusta de ir sobre isso? Eu gostaria de ser capaz de suportar operadores de pesquisa extensos demais (como o site do Google:. E intitle :) que será aplicado a diferentes campos de banco de dados e, portanto, teriam de ser separados da cadeia de tsquery

UPDATE: Eu percebo que com operadores especiais isso se torna um Google para SQL conversão cláusula WHERE, em vez de um Google para tsquery conversão. Mas a cláusula WHERE pode conter um ou mais tsqueries.

Por exemplo, a consulta de estilo Google:

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

Se produzir um SQL WHERE cláusula como esta:

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'
  );

Eu não tenho certeza se o acima é possível com regex?

Foi útil?

Solução

honesto, acho que as expressões regulares são o caminho a percorrer com algo parecido com isso. Assim mesmo, este foi um exercício divertido. O código abaixo é muito prototypal - na verdade, você vai ver que eu nem sequer implementar o próprio léxico - Eu só falsificou a saída. Eu gostaria de continuar, mas eu simplesmente não tenho mais tempo livre hoje.

Além disso, há definitivamente muito mais trabalho a ser feito aqui em termos de apoiar outros tipos de operadores de pesquisa e similares.

Basicamente, a idéia é que um certo tipo de consulta é lexed então analisado em um formato comum (neste caso, uma instância QueryExpression) que é então processado de volta como outro tipo de consulta.

<?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();
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top