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