PageRenderTime 34ms CodeModel.GetById 13ms app.highlight 17ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Search/Lucene/Search/QueryParser.php

https://bitbucket.org/hamidrezas/melobit
PHP | 635 lines | 333 code | 85 blank | 217 comment | 37 complexity | 3d2aa29cb89ecfffcf14777334d80be0 MD5 | raw file
Possible License(s): AGPL-1.0
  1<?php
  2/**
  3 * Zend Framework
  4 *
  5 * LICENSE
  6 *
  7 * This source file is subject to the new BSD license that is bundled
  8 * with this package in the file LICENSE.txt.
  9 * It is also available through the world-wide-web at this URL:
 10 * http://framework.zend.com/license/new-bsd
 11 * If you did not receive a copy of the license and are unable to
 12 * obtain it through the world-wide-web, please send an email
 13 * to license@zend.com so we can send you a copy immediately.
 14 *
 15 * @category   Zend
 16 * @package    Zend_Search_Lucene
 17 * @subpackage Search
 18 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
 19 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 20 * @version    $Id: QueryParser.php 24594 2012-01-05 21:27:01Z matthew $
 21 */
 22
 23
 24/** Internally used classes */
 25
 26/** Zend_Search_Lucene_Analysis_Analyzer */
 27require_once 'Zend/Search/Lucene/Analysis/Analyzer.php';
 28
 29/** Zend_Search_Lucene_Search_QueryToken */
 30require_once 'Zend/Search/Lucene/Search/QueryToken.php';
 31
 32
 33
 34/** Zend_Search_Lucene_FSM */
 35require_once 'Zend/Search/Lucene/FSM.php';
 36
 37/**
 38 * @category   Zend
 39 * @package    Zend_Search_Lucene
 40 * @subpackage Search
 41 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
 42 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 43 */
 44class Zend_Search_Lucene_Search_QueryParser extends Zend_Search_Lucene_FSM
 45{
 46    /**
 47     * Parser instance
 48     *
 49     * @var Zend_Search_Lucene_Search_QueryParser
 50     */
 51    private static $_instance = null;
 52
 53
 54    /**
 55     * Query lexer
 56     *
 57     * @var Zend_Search_Lucene_Search_QueryLexer
 58     */
 59    private $_lexer;
 60
 61    /**
 62     * Tokens list
 63     * Array of Zend_Search_Lucene_Search_QueryToken objects
 64     *
 65     * @var array
 66     */
 67    private $_tokens;
 68
 69    /**
 70     * Current token
 71     *
 72     * @var integer|string
 73     */
 74    private $_currentToken;
 75
 76    /**
 77     * Last token
 78     *
 79     * It can be processed within FSM states, but this addirional state simplifies FSM
 80     *
 81     * @var Zend_Search_Lucene_Search_QueryToken
 82     */
 83    private $_lastToken = null;
 84
 85    /**
 86     * Range query first term
 87     *
 88     * @var string
 89     */
 90    private $_rqFirstTerm = null;
 91
 92    /**
 93     * Current query parser context
 94     *
 95     * @var Zend_Search_Lucene_Search_QueryParserContext
 96     */
 97    private $_context;
 98
 99    /**
100     * Context stack
101     *
102     * @var array
103     */
104    private $_contextStack;
105
106    /**
107     * Query string encoding
108     *
109     * @var string
110     */
111    private $_encoding;
112
113    /**
114     * Query string default encoding
115     *
116     * @var string
117     */
118    private $_defaultEncoding = '';
119
120    /**
121     * Defines query parsing mode.
122     *
123     * If this option is turned on, then query parser suppress query parser exceptions
124     * and constructs multi-term query using all words from a query.
125     *
126     * That helps to avoid exceptions caused by queries, which don't conform to query language,
127     * but limits possibilities to check, that query entered by user has some inconsistencies.
128     *
129     *
130     * Default is true.
131     *
132     * Use {@link Zend_Search_Lucene::suppressQueryParsingExceptions()},
133     * {@link Zend_Search_Lucene::dontSuppressQueryParsingExceptions()} and
134     * {@link Zend_Search_Lucene::checkQueryParsingExceptionsSuppressMode()} to operate
135     * with this setting.
136     *
137     * @var boolean
138     */
139    private $_suppressQueryParsingExceptions = true;
140
141    /**
142     * Boolean operators constants
143     */
144    const B_OR  = 0;
145    const B_AND = 1;
146
147    /**
148     * Default boolean queries operator
149     *
150     * @var integer
151     */
152    private $_defaultOperator = self::B_OR;
153
154
155    /** Query parser State Machine states */
156    const ST_COMMON_QUERY_ELEMENT       = 0;   // Terms, phrases, operators
157    const ST_CLOSEDINT_RQ_START         = 1;   // Range query start (closed interval) - '['
158    const ST_CLOSEDINT_RQ_FIRST_TERM    = 2;   // First term in '[term1 to term2]' construction
159    const ST_CLOSEDINT_RQ_TO_TERM       = 3;   // 'TO' lexeme in '[term1 to term2]' construction
160    const ST_CLOSEDINT_RQ_LAST_TERM     = 4;   // Second term in '[term1 to term2]' construction
161    const ST_CLOSEDINT_RQ_END           = 5;   // Range query end (closed interval) - ']'
162    const ST_OPENEDINT_RQ_START         = 6;   // Range query start (opened interval) - '{'
163    const ST_OPENEDINT_RQ_FIRST_TERM    = 7;   // First term in '{term1 to term2}' construction
164    const ST_OPENEDINT_RQ_TO_TERM       = 8;   // 'TO' lexeme in '{term1 to term2}' construction
165    const ST_OPENEDINT_RQ_LAST_TERM     = 9;   // Second term in '{term1 to term2}' construction
166    const ST_OPENEDINT_RQ_END           = 10;  // Range query end (opened interval) - '}'
167
168    /**
169     * Parser constructor
170     */
171    public function __construct()
172    {
173        parent::__construct(array(self::ST_COMMON_QUERY_ELEMENT,
174                                  self::ST_CLOSEDINT_RQ_START,
175                                  self::ST_CLOSEDINT_RQ_FIRST_TERM,
176                                  self::ST_CLOSEDINT_RQ_TO_TERM,
177                                  self::ST_CLOSEDINT_RQ_LAST_TERM,
178                                  self::ST_CLOSEDINT_RQ_END,
179                                  self::ST_OPENEDINT_RQ_START,
180                                  self::ST_OPENEDINT_RQ_FIRST_TERM,
181                                  self::ST_OPENEDINT_RQ_TO_TERM,
182                                  self::ST_OPENEDINT_RQ_LAST_TERM,
183                                  self::ST_OPENEDINT_RQ_END
184                                 ),
185                            Zend_Search_Lucene_Search_QueryToken::getTypes());
186
187        $this->addRules(
188             array(array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_WORD,             self::ST_COMMON_QUERY_ELEMENT),
189                   array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_PHRASE,           self::ST_COMMON_QUERY_ELEMENT),
190                   array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_FIELD,            self::ST_COMMON_QUERY_ELEMENT),
191                   array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_REQUIRED,         self::ST_COMMON_QUERY_ELEMENT),
192                   array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_PROHIBITED,       self::ST_COMMON_QUERY_ELEMENT),
193                   array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_FUZZY_PROX_MARK,  self::ST_COMMON_QUERY_ELEMENT),
194                   array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_BOOSTING_MARK,    self::ST_COMMON_QUERY_ELEMENT),
195                   array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_RANGE_INCL_START, self::ST_CLOSEDINT_RQ_START),
196                   array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_RANGE_EXCL_START, self::ST_OPENEDINT_RQ_START),
197                   array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_SUBQUERY_START,   self::ST_COMMON_QUERY_ELEMENT),
198                   array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_SUBQUERY_END,     self::ST_COMMON_QUERY_ELEMENT),
199                   array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_AND_LEXEME,       self::ST_COMMON_QUERY_ELEMENT),
200                   array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_OR_LEXEME,        self::ST_COMMON_QUERY_ELEMENT),
201                   array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_NOT_LEXEME,       self::ST_COMMON_QUERY_ELEMENT),
202                   array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_NUMBER,           self::ST_COMMON_QUERY_ELEMENT)
203                  ));
204        $this->addRules(
205             array(array(self::ST_CLOSEDINT_RQ_START,      Zend_Search_Lucene_Search_QueryToken::TT_WORD,           self::ST_CLOSEDINT_RQ_FIRST_TERM),
206                   array(self::ST_CLOSEDINT_RQ_FIRST_TERM, Zend_Search_Lucene_Search_QueryToken::TT_TO_LEXEME,      self::ST_CLOSEDINT_RQ_TO_TERM),
207                   array(self::ST_CLOSEDINT_RQ_TO_TERM,    Zend_Search_Lucene_Search_QueryToken::TT_WORD,           self::ST_CLOSEDINT_RQ_LAST_TERM),
208                   array(self::ST_CLOSEDINT_RQ_LAST_TERM,  Zend_Search_Lucene_Search_QueryToken::TT_RANGE_INCL_END, self::ST_COMMON_QUERY_ELEMENT)
209                  ));
210        $this->addRules(
211             array(array(self::ST_OPENEDINT_RQ_START,      Zend_Search_Lucene_Search_QueryToken::TT_WORD,           self::ST_OPENEDINT_RQ_FIRST_TERM),
212                   array(self::ST_OPENEDINT_RQ_FIRST_TERM, Zend_Search_Lucene_Search_QueryToken::TT_TO_LEXEME,      self::ST_OPENEDINT_RQ_TO_TERM),
213                   array(self::ST_OPENEDINT_RQ_TO_TERM,    Zend_Search_Lucene_Search_QueryToken::TT_WORD,           self::ST_OPENEDINT_RQ_LAST_TERM),
214                   array(self::ST_OPENEDINT_RQ_LAST_TERM,  Zend_Search_Lucene_Search_QueryToken::TT_RANGE_EXCL_END, self::ST_COMMON_QUERY_ELEMENT)
215                  ));
216
217
218
219        $addTermEntryAction             = new Zend_Search_Lucene_FSMAction($this, 'addTermEntry');
220        $addPhraseEntryAction           = new Zend_Search_Lucene_FSMAction($this, 'addPhraseEntry');
221        $setFieldAction                 = new Zend_Search_Lucene_FSMAction($this, 'setField');
222        $setSignAction                  = new Zend_Search_Lucene_FSMAction($this, 'setSign');
223        $setFuzzyProxAction             = new Zend_Search_Lucene_FSMAction($this, 'processFuzzyProximityModifier');
224        $processModifierParameterAction = new Zend_Search_Lucene_FSMAction($this, 'processModifierParameter');
225        $subqueryStartAction            = new Zend_Search_Lucene_FSMAction($this, 'subqueryStart');
226        $subqueryEndAction              = new Zend_Search_Lucene_FSMAction($this, 'subqueryEnd');
227        $logicalOperatorAction          = new Zend_Search_Lucene_FSMAction($this, 'logicalOperator');
228        $openedRQFirstTermAction        = new Zend_Search_Lucene_FSMAction($this, 'openedRQFirstTerm');
229        $openedRQLastTermAction         = new Zend_Search_Lucene_FSMAction($this, 'openedRQLastTerm');
230        $closedRQFirstTermAction        = new Zend_Search_Lucene_FSMAction($this, 'closedRQFirstTerm');
231        $closedRQLastTermAction         = new Zend_Search_Lucene_FSMAction($this, 'closedRQLastTerm');
232
233
234        $this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_WORD,            $addTermEntryAction);
235        $this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_PHRASE,          $addPhraseEntryAction);
236        $this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_FIELD,           $setFieldAction);
237        $this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_REQUIRED,        $setSignAction);
238        $this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_PROHIBITED,      $setSignAction);
239        $this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_FUZZY_PROX_MARK, $setFuzzyProxAction);
240        $this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_NUMBER,          $processModifierParameterAction);
241        $this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_SUBQUERY_START,  $subqueryStartAction);
242        $this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_SUBQUERY_END,    $subqueryEndAction);
243        $this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_AND_LEXEME,      $logicalOperatorAction);
244        $this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_OR_LEXEME,       $logicalOperatorAction);
245        $this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_NOT_LEXEME,      $logicalOperatorAction);
246
247        $this->addEntryAction(self::ST_OPENEDINT_RQ_FIRST_TERM, $openedRQFirstTermAction);
248        $this->addEntryAction(self::ST_OPENEDINT_RQ_LAST_TERM,  $openedRQLastTermAction);
249        $this->addEntryAction(self::ST_CLOSEDINT_RQ_FIRST_TERM, $closedRQFirstTermAction);
250        $this->addEntryAction(self::ST_CLOSEDINT_RQ_LAST_TERM,  $closedRQLastTermAction);
251
252
253        require_once 'Zend/Search/Lucene/Search/QueryLexer.php';
254        $this->_lexer = new Zend_Search_Lucene_Search_QueryLexer();
255    }
256
257    /**
258     * Get query parser instance
259     *
260     * @return Zend_Search_Lucene_Search_QueryParser
261     */
262    private static function _getInstance()
263    {
264        if (self::$_instance === null) {
265            self::$_instance = new self();
266        }
267        return self::$_instance;
268    }
269
270    /**
271     * Set query string default encoding
272     *
273     * @param string $encoding
274     */
275    public static function setDefaultEncoding($encoding)
276    {
277        self::_getInstance()->_defaultEncoding = $encoding;
278    }
279
280    /**
281     * Get query string default encoding
282     *
283     * @return string
284     */
285    public static function getDefaultEncoding()
286    {
287       return self::_getInstance()->_defaultEncoding;
288    }
289
290    /**
291     * Set default boolean operator
292     *
293     * @param integer $operator
294     */
295    public static function setDefaultOperator($operator)
296    {
297        self::_getInstance()->_defaultOperator = $operator;
298    }
299
300    /**
301     * Get default boolean operator
302     *
303     * @return integer
304     */
305    public static function getDefaultOperator()
306    {
307        return self::_getInstance()->_defaultOperator;
308    }
309
310    /**
311     * Turn on 'suppress query parser exceptions' mode.
312     */
313    public static function suppressQueryParsingExceptions()
314    {
315        self::_getInstance()->_suppressQueryParsingExceptions = true;
316    }
317    /**
318     * Turn off 'suppress query parser exceptions' mode.
319     */
320    public static function dontSuppressQueryParsingExceptions()
321    {
322        self::_getInstance()->_suppressQueryParsingExceptions = false;
323    }
324    /**
325     * Check 'suppress query parser exceptions' mode.
326     * @return boolean
327     */
328    public static function queryParsingExceptionsSuppressed()
329    {
330        return self::_getInstance()->_suppressQueryParsingExceptions;
331    }
332
333
334    /**
335     * Escape keyword to force it to be parsed as one term
336     *
337     * @param string $keyword
338     * @return string
339     */
340    public static function escape($keyword)
341    {
342        return '\\' . implode('\\', str_split($keyword));
343    }
344
345    /**
346     * Parses a query string
347     *
348     * @param string $strQuery
349     * @param string $encoding
350     * @return Zend_Search_Lucene_Search_Query
351     * @throws Zend_Search_Lucene_Search_QueryParserException
352     */
353    public static function parse($strQuery, $encoding = null)
354    {
355        self::_getInstance();
356
357        // Reset FSM if previous parse operation didn't return it into a correct state
358        self::$_instance->reset();
359
360        require_once 'Zend/Search/Lucene/Search/QueryParserException.php';
361        try {
362            require_once 'Zend/Search/Lucene/Search/QueryParserContext.php';
363
364            self::$_instance->_encoding     = ($encoding !== null) ? $encoding : self::$_instance->_defaultEncoding;
365            self::$_instance->_lastToken    = null;
366            self::$_instance->_context      = new Zend_Search_Lucene_Search_QueryParserContext(self::$_instance->_encoding);
367            self::$_instance->_contextStack = array();
368            self::$_instance->_tokens       = self::$_instance->_lexer->tokenize($strQuery, self::$_instance->_encoding);
369
370            // Empty query
371            if (count(self::$_instance->_tokens) == 0) {
372                require_once 'Zend/Search/Lucene/Search/Query/Insignificant.php';
373                return new Zend_Search_Lucene_Search_Query_Insignificant();
374            }
375
376
377            foreach (self::$_instance->_tokens as $token) {
378                try {
379                    self::$_instance->_currentToken = $token;
380                    self::$_instance->process($token->type);
381
382                    self::$_instance->_lastToken = $token;
383                } catch (Exception $e) {
384                    if (strpos($e->getMessage(), 'There is no any rule for') !== false) {
385                        throw new Zend_Search_Lucene_Search_QueryParserException( 'Syntax error at char position ' . $token->position . '.', 0, $e);
386                    }
387
388                    require_once 'Zend/Search/Lucene/Exception.php';
389                    throw new Zend_Search_Lucene_Exception($e->getMessage(), $e->getCode(), $e);
390                }
391            }
392
393            if (count(self::$_instance->_contextStack) != 0) {
394                throw new Zend_Search_Lucene_Search_QueryParserException('Syntax Error: mismatched parentheses, every opening must have closing.' );
395            }
396
397            return self::$_instance->_context->getQuery();
398        } catch (Zend_Search_Lucene_Search_QueryParserException $e) {
399            if (self::$_instance->_suppressQueryParsingExceptions) {
400                $queryTokens = Zend_Search_Lucene_Analysis_Analyzer::getDefault()->tokenize($strQuery, self::$_instance->_encoding);
401
402                require_once 'Zend/Search/Lucene/Search/Query/MultiTerm.php';
403                $query = new Zend_Search_Lucene_Search_Query_MultiTerm();
404                $termsSign = (self::$_instance->_defaultOperator == self::B_AND) ? true /* required term */ :
405                                                                                   null /* optional term */;
406
407                require_once 'Zend/Search/Lucene/Index/Term.php';
408                foreach ($queryTokens as $token) {
409                    $query->addTerm(new Zend_Search_Lucene_Index_Term($token->getTermText()), $termsSign);
410                }
411
412
413                return $query;
414            } else {
415                require_once 'Zend/Search/Lucene/Exception.php';
416                throw new Zend_Search_Lucene_Exception($e->getMessage(), $e->getCode(), $e);
417            }
418        }
419    }
420
421    /*********************************************************************
422     * Actions implementation
423     *
424     * Actions affect on recognized lexemes list
425     *********************************************************************/
426
427    /**
428     * Add term to a query
429     */
430    public function addTermEntry()
431    {
432        require_once 'Zend/Search/Lucene/Search/QueryEntry/Term.php';
433        $entry = new Zend_Search_Lucene_Search_QueryEntry_Term($this->_currentToken->text, $this->_context->getField());
434        $this->_context->addEntry($entry);
435    }
436
437    /**
438     * Add phrase to a query
439     */
440    public function addPhraseEntry()
441    {
442        require_once 'Zend/Search/Lucene/Search/QueryEntry/Phrase.php';
443        $entry = new Zend_Search_Lucene_Search_QueryEntry_Phrase($this->_currentToken->text, $this->_context->getField());
444        $this->_context->addEntry($entry);
445    }
446
447    /**
448     * Set entry field
449     */
450    public function setField()
451    {
452        $this->_context->setNextEntryField($this->_currentToken->text);
453    }
454
455    /**
456     * Set entry sign
457     */
458    public function setSign()
459    {
460        $this->_context->setNextEntrySign($this->_currentToken->type);
461    }
462
463
464    /**
465     * Process fuzzy search/proximity modifier - '~'
466     */
467    public function processFuzzyProximityModifier()
468    {
469        $this->_context->processFuzzyProximityModifier();
470    }
471
472    /**
473     * Process modifier parameter
474     *
475     * @throws Zend_Search_Lucene_Exception
476     */
477    public function processModifierParameter()
478    {
479        if ($this->_lastToken === null) {
480            require_once 'Zend/Search/Lucene/Search/QueryParserException.php';
481            throw new Zend_Search_Lucene_Search_QueryParserException('Lexeme modifier parameter must follow lexeme modifier. Char position 0.' );
482        }
483
484        switch ($this->_lastToken->type) {
485            case Zend_Search_Lucene_Search_QueryToken::TT_FUZZY_PROX_MARK:
486                $this->_context->processFuzzyProximityModifier($this->_currentToken->text);
487                break;
488
489            case Zend_Search_Lucene_Search_QueryToken::TT_BOOSTING_MARK:
490                $this->_context->boost($this->_currentToken->text);
491                break;
492
493            default:
494                // It's not a user input exception
495                require_once 'Zend/Search/Lucene/Exception.php';
496                throw new Zend_Search_Lucene_Exception('Lexeme modifier parameter must follow lexeme modifier. Char position 0.' );
497        }
498    }
499
500
501    /**
502     * Start subquery
503     */
504    public function subqueryStart()
505    {
506        require_once 'Zend/Search/Lucene/Search/QueryParserContext.php';
507
508        $this->_contextStack[] = $this->_context;
509        $this->_context        = new Zend_Search_Lucene_Search_QueryParserContext($this->_encoding, $this->_context->getField());
510    }
511
512    /**
513     * End subquery
514     */
515    public function subqueryEnd()
516    {
517        if (count($this->_contextStack) == 0) {
518            require_once 'Zend/Search/Lucene/Search/QueryParserException.php';
519            throw new Zend_Search_Lucene_Search_QueryParserException('Syntax Error: mismatched parentheses, every opening must have closing. Char position ' . $this->_currentToken->position . '.' );
520        }
521
522        $query          = $this->_context->getQuery();
523        $this->_context = array_pop($this->_contextStack);
524
525        require_once 'Zend/Search/Lucene/Search/QueryEntry/Subquery.php';
526        $this->_context->addEntry(new Zend_Search_Lucene_Search_QueryEntry_Subquery($query));
527    }
528
529    /**
530     * Process logical operator
531     */
532    public function logicalOperator()
533    {
534        $this->_context->addLogicalOperator($this->_currentToken->type);
535    }
536
537    /**
538     * Process first range query term (opened interval)
539     */
540    public function openedRQFirstTerm()
541    {
542        $this->_rqFirstTerm = $this->_currentToken->text;
543    }
544
545    /**
546     * Process last range query term (opened interval)
547     *
548     * @throws Zend_Search_Lucene_Search_QueryParserException
549     */
550    public function openedRQLastTerm()
551    {
552        $tokens = Zend_Search_Lucene_Analysis_Analyzer::getDefault()->tokenize($this->_rqFirstTerm, $this->_encoding);
553        if (count($tokens) > 1) {
554            require_once 'Zend/Search/Lucene/Search/QueryParserException.php';
555            throw new Zend_Search_Lucene_Search_QueryParserException('Range query boundary terms must be non-multiple word terms');
556        } else if (count($tokens) == 1) {
557            require_once 'Zend/Search/Lucene/Index/Term.php';
558            $from = new Zend_Search_Lucene_Index_Term(reset($tokens)->getTermText(), $this->_context->getField());
559        } else {
560            $from = null;
561        }
562
563        $tokens = Zend_Search_Lucene_Analysis_Analyzer::getDefault()->tokenize($this->_currentToken->text, $this->_encoding);
564        if (count($tokens) > 1) {
565            require_once 'Zend/Search/Lucene/Search/QueryParserException.php';
566            throw new Zend_Search_Lucene_Search_QueryParserException('Range query boundary terms must be non-multiple word terms');
567        } else if (count($tokens) == 1) {
568            require_once 'Zend/Search/Lucene/Index/Term.php';
569            $to = new Zend_Search_Lucene_Index_Term(reset($tokens)->getTermText(), $this->_context->getField());
570        } else {
571            $to = null;
572        }
573
574        if ($from === null  &&  $to === null) {
575            require_once 'Zend/Search/Lucene/Search/QueryParserException.php';
576            throw new Zend_Search_Lucene_Search_QueryParserException('At least one range query boundary term must be non-empty term');
577        }
578
579        require_once 'Zend/Search/Lucene/Search/Query/Range.php';
580        $rangeQuery = new Zend_Search_Lucene_Search_Query_Range($from, $to, false);
581        require_once 'Zend/Search/Lucene/Search/QueryEntry/Subquery.php';
582        $entry      = new Zend_Search_Lucene_Search_QueryEntry_Subquery($rangeQuery);
583        $this->_context->addEntry($entry);
584    }
585
586    /**
587     * Process first range query term (closed interval)
588     */
589    public function closedRQFirstTerm()
590    {
591        $this->_rqFirstTerm = $this->_currentToken->text;
592    }
593
594    /**
595     * Process last range query term (closed interval)
596     *
597     * @throws Zend_Search_Lucene_Search_QueryParserException
598     */
599    public function closedRQLastTerm()
600    {
601        $tokens = Zend_Search_Lucene_Analysis_Analyzer::getDefault()->tokenize($this->_rqFirstTerm, $this->_encoding);
602        if (count($tokens) > 1) {
603            require_once 'Zend/Search/Lucene/Search/QueryParserException.php';
604            throw new Zend_Search_Lucene_Search_QueryParserException('Range query boundary terms must be non-multiple word terms');
605        } else if (count($tokens) == 1) {
606            require_once 'Zend/Search/Lucene/Index/Term.php';
607            $from = new Zend_Search_Lucene_Index_Term(reset($tokens)->getTermText(), $this->_context->getField());
608        } else {
609            $from = null;
610        }
611
612        $tokens = Zend_Search_Lucene_Analysis_Analyzer::getDefault()->tokenize($this->_currentToken->text, $this->_encoding);
613        if (count($tokens) > 1) {
614            require_once 'Zend/Search/Lucene/Search/QueryParserException.php';
615            throw new Zend_Search_Lucene_Search_QueryParserException('Range query boundary terms must be non-multiple word terms');
616        } else if (count($tokens) == 1) {
617            require_once 'Zend/Search/Lucene/Index/Term.php';
618            $to = new Zend_Search_Lucene_Index_Term(reset($tokens)->getTermText(), $this->_context->getField());
619        } else {
620            $to = null;
621        }
622
623        if ($from === null  &&  $to === null) {
624            require_once 'Zend/Search/Lucene/Search/QueryParserException.php';
625            throw new Zend_Search_Lucene_Search_QueryParserException('At least one range query boundary term must be non-empty term');
626        }
627
628        require_once 'Zend/Search/Lucene/Search/Query/Range.php';
629        $rangeQuery = new Zend_Search_Lucene_Search_Query_Range($from, $to, true);
630        require_once 'Zend/Search/Lucene/Search/QueryEntry/Subquery.php';
631        $entry      = new Zend_Search_Lucene_Search_QueryEntry_Subquery($rangeQuery);
632        $this->_context->addEntry($entry);
633    }
634}
635