Domanda

Come posso convertire una query di ricerca di Google in qualcosa che posso alimentare a_tsquery () di PostgreSQL?

Se non esiste una libreria esistente, come devo fare per analizzare una query di ricerca di Google in una lingua come PHP?

Ad esempio, vorrei prendere la seguente query di ricerca di Google ish:

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

E trasformalo in una stringa amichevole to_tsquery ():

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

Posso confonderlo con le regex, ma è il massimo che posso fare. Esiste un solido metodo di analisi lessicale per farlo? Vorrei poter supportare anche gli operatori di ricerca estesa (come il sito di Google: e intitle :) che si applicheranno a diversi campi del database e quindi dovrebbero essere separati dalla stringa tsquery.

AGGIORNAMENTO: mi rendo conto che con operatori speciali questa diventa una conversione da clausola WHERE a Google, piuttosto che una conversione da Google a tsquery. Ma la clausola WHERE può contenere uno o più tsqueries.

Ad esempio, la query in stile Google:

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

Dovrebbe produrre una clausola WHERE di SQL come questa:

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

Non sono sicuro che quanto sopra sia possibile con regex?

È stato utile?

Soluzione

Onesto, penso che le espressioni regolari siano la strada da percorrere con qualcosa del genere. Allo stesso modo, questo è stato un esercizio divertente. Il codice qui sotto è molto prototipo - infatti, vedrai che non ho nemmeno implementato il lexer stesso - ho solo falsificato l'output. Vorrei continuare, ma oggi non ho più tempo libero.

Inoltre, c'è sicuramente molto più lavoro da fare qui in termini di supporto di altri tipi di operatori di ricerca e simili.

Fondamentalmente, l'idea è che un determinato tipo di query sia sottoposto a less, quindi analizzato in un formato comune (in questo caso, un'istanza di QueryExpression) che viene quindi restituito come un altro tipo di query.

<?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();
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top