QueryParser.php 25 KB

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