try update Zend directory to 1.9 equivalent

This commit is contained in:
diml
2007-12-19 21:59:41 +00:00
parent 03c03e08cc
commit 54b9c5feb0
67 changed files with 10344 additions and 1280 deletions
+3 -3
View File
@@ -14,7 +14,7 @@
*
* @category Zend
* @package Zend_Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
@@ -22,13 +22,13 @@
/**
* Framework base exception
*/
require_once 'Zend/Exception.php';
require_once $CFG->dirroot.'/search/Zend/Exception.php';
/**
* @category Zend
* @package Zend_Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Exception extends Zend_Exception
+599 -129
View File
@@ -14,53 +14,78 @@
*
* @category Zend
* @package Zend_Search_Lucene
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Exception */
require_once 'Zend/Search/Lucene/Exception.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/** Zend_Search_Lucene_Document */
require_once 'Zend/Search/Lucene/Document.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Document.php';
/** Zend_Search_Lucene_Document_Html */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Document/Html.php';
/** Zend_Search_Lucene_Storage_Directory */
require_once 'Zend/Search/Lucene/Storage/Directory/Filesystem.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Storage/Directory/Filesystem.php';
/** Zend_Search_Lucene_Storage_File_Memory */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Storage/File/Memory.php';
/** Zend_Search_Lucene_Index_Term */
require_once 'Zend/Search/Lucene/Index/Term.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/Term.php';
/** Zend_Search_Lucene_Index_TermInfo */
require_once 'Zend/Search/Lucene/Index/TermInfo.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/TermInfo.php';
/** Zend_Search_Lucene_Index_SegmentInfo */
require_once 'Zend/Search/Lucene/Index/SegmentInfo.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/SegmentInfo.php';
/** Zend_Search_Lucene_Index_FieldInfo */
require_once 'Zend/Search/Lucene/Index/FieldInfo.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/FieldInfo.php';
/** Zend_Search_Lucene_Index_Writer */
require_once 'Zend/Search/Lucene/Index/Writer.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/Writer.php';
/** Zend_Search_Lucene_Search_QueryParser */
require_once 'Zend/Search/Lucene/Search/QueryParser.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryParser.php';
/** Zend_Search_Lucene_Search_QueryHit */
require_once 'Zend/Search/Lucene/Search/QueryHit.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryHit.php';
/** Zend_Search_Lucene_Search_Similarity */
require_once 'Zend/Search/Lucene/Search/Similarity.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Similarity.php';
/** Zend_Search_Lucene_Index_SegmentInfoPriorityQueue */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/SegmentInfoPriorityQueue.php';
/** Zend_Search_Lucene_Interface */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Interface.php';
/** Zend_Search_Lucene_Proxy */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Proxy.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene
class Zend_Search_Lucene implements Zend_Search_Lucene_Interface
{
/**
* Default field name for search
*
* Null means search through all fields
*
* @var string
*/
private static $_defaultSearchField = null;
/**
* File system adapter.
*
@@ -103,6 +128,51 @@ class Zend_Search_Lucene
*/
private $_hasChanges = false;
/**
* Index lock object
*
* @var Zend_Search_Lucene_Storage_File
*/
private $_lock;
/**
* Signal, that index is already closed, changes are fixed and resources are cleaned up
*
* @var boolean
*/
private $_closed = false;
/**
* Number of references to the index object
*
* @var integer
*/
private $_refCount = 0;
/**
* Create index
*
* @param mixed $directory
* @return Zend_Search_Lucene_Interface
*/
public static function create($directory)
{
return new Zend_Search_Lucene_Proxy(new Zend_Search_Lucene($directory, true));
}
/**
* Open index
*
* @param mixed $directory
* @return Zend_Search_Lucene_Interface
*/
public static function open($directory)
{
return new Zend_Search_Lucene_Proxy(new Zend_Search_Lucene($directory, false));
}
/**
* Opens the index.
*
@@ -126,13 +196,32 @@ class Zend_Search_Lucene
$this->_closeDirOnExit = true;
}
// Get a shared lock to the index
$this->_lock = $this->_directory->createFile('index.lock');
$this->_segmentInfos = array();
if ($create) {
$this->_writer = new Zend_Search_Lucene_Index_Writer($this->_directory, true);
// Throw an exception if index is under processing now
if (!$this->_lock->lock(LOCK_EX, true)) {
throw new Zend_Search_Lucene_Exception('Can\'t create index. It\'s under processing now');
}
// Writer will create segments file for empty segments list
$this->_writer = new Zend_Search_Lucene_Index_Writer($this->_directory, $this->_segmentInfos, true);
if (!$this->_lock->lock(LOCK_SH)) {
throw new Zend_Search_Lucene_Exception('Can\'t reduce lock level from Exclusive to Shared');
}
} else {
// Wait if index is under switching from one set of segments to another (Index_Writer::_updateSegments())
if (!$this->_lock->lock(LOCK_SH)) {
throw new Zend_Search_Lucene_Exception('Can\'t obtain shared index lock');
}
$this->_writer = null;
}
$this->_segmentInfos = array();
$segmentsFile = $this->_directory->getFileObject('segments');
@@ -143,9 +232,10 @@ class Zend_Search_Lucene
}
// read version
$segmentsFile->readLong();
// $segmentsFile->readLong();
$segmentsFile->readInt(); $segmentsFile->readInt();
// read counter
// read segment name counter
$segmentsFile->readInt();
$segments = $segmentsFile->readInt();
@@ -158,35 +248,83 @@ class Zend_Search_Lucene
$segSize = $segmentsFile->readInt();
$this->_docCount += $segSize;
$this->_segmentInfos[$count] =
$this->_segmentInfos[] =
new Zend_Search_Lucene_Index_SegmentInfo($segName,
$segSize,
$this->_directory);
}
}
/**
* Close current index and free resources
*/
private function _close()
{
if ($this->_closed) {
// index is already closed and resources are cleaned up
return;
}
$this->commit();
// Free shared lock
$this->_lock->unlock();
if ($this->_closeDirOnExit) {
$this->_directory->close();
}
$this->_directory = null;
$this->_writer = null;
$this->_segmentInfos = null;
$this->_closed = true;
}
/**
* Add reference to the index object
*
* @internal
*/
public function addReference()
{
$this->_refCount++;
}
/**
* Remove reference from the index object
*
* When reference count becomes zero, index is closed and resources are cleaned up
*
* @internal
*/
public function removeReference()
{
$this->_refCount--;
if ($this->_refCount == 0) {
$this->_close();
}
}
/**
* Object destructor
*/
public function __destruct()
{
$this->commit();
if ($this->_closeDirOnExit) {
$this->_directory->close();
}
$this->_close();
}
/**
* Returns an instance of Zend_Search_Lucene_Index_Writer for the index
*
* @internal
* @return Zend_Search_Lucene_Index_Writer
*/
public function getIndexWriter()
{
if (!$this->_writer instanceof Zend_Search_Lucene_Index_Writer) {
$this->_writer = new Zend_Search_Lucene_Index_Writer($this->_directory);
$this->_writer = new Zend_Search_Lucene_Index_Writer($this->_directory, $this->_segmentInfos);
}
return $this->_writer;
@@ -205,7 +343,7 @@ class Zend_Search_Lucene
/**
* Returns the total number of documents in this index.
* Returns the total number of documents in this index (including deleted documents).
*
* @return integer
*/
@@ -214,6 +352,192 @@ class Zend_Search_Lucene
return $this->_docCount;
}
/**
* Returns one greater than the largest possible document number.
* This may be used to, e.g., determine how big to allocate a structure which will have
* an element for every document number in an index.
*
* @return integer
*/
public function maxDoc()
{
return $this->count();
}
/**
* Returns the total number of non-deleted documents in this index.
*
* @return integer
*/
public function numDocs()
{
$numDocs = 0;
foreach ($this->_segmentInfos as $segmentInfo) {
$numDocs += $segmentInfo->numDocs();
}
return $numDocs;
}
/**
* Checks, that document is deleted
*
* @param integer $id
* @return boolean
* @throws Zend_Search_Lucene_Exception Exception is thrown if $id is out of the range
*/
public function isDeleted($id)
{
if ($id >= $this->_docCount) {
throw new Zend_Search_Lucene_Exception('Document id is out of the range.');
}
$segmentStartId = 0;
foreach ($this->_segmentInfos as $segmentInfo) {
if ($segmentStartId + $segmentInfo->count() > $id) {
break;
}
$segmentStartId += $segmentInfo->count();
}
return $segmentInfo->isDeleted($id - $segmentStartId);
}
/**
* Set default search field.
*
* Null means, that search is performed through all fields by default
*
* Default value is null
*
* @param string $fieldName
*/
public static function setDefaultSearchField($fieldName)
{
self::$_defaultSearchField = $fieldName;
}
/**
* Get default search field.
*
* Null means, that search is performed through all fields by default
*
* @return string
*/
public static function getDefaultSearchField()
{
return self::$_defaultSearchField;
}
/**
* Retrieve index maxBufferedDocs option
*
* maxBufferedDocs is a minimal number of documents required before
* the buffered in-memory documents are written into a new Segment
*
* Default value is 10
*
* @return integer
*/
public function getMaxBufferedDocs()
{
return $this->getIndexWriter()->maxBufferedDocs;
}
/**
* Set index maxBufferedDocs option
*
* maxBufferedDocs is a minimal number of documents required before
* the buffered in-memory documents are written into a new Segment
*
* Default value is 10
*
* @param integer $maxBufferedDocs
*/
public function setMaxBufferedDocs($maxBufferedDocs)
{
$this->getIndexWriter()->maxBufferedDocs = $maxBufferedDocs;
}
/**
* Retrieve index maxMergeDocs option
*
* maxMergeDocs is a largest number of documents ever merged by addDocument().
* Small values (e.g., less than 10,000) are best for interactive indexing,
* as this limits the length of pauses while indexing to a few seconds.
* Larger values are best for batched indexing and speedier searches.
*
* Default value is PHP_INT_MAX
*
* @return integer
*/
public function getMaxMergeDocs()
{
return $this->getIndexWriter()->maxMergeDocs;
}
/**
* Set index maxMergeDocs option
*
* maxMergeDocs is a largest number of documents ever merged by addDocument().
* Small values (e.g., less than 10,000) are best for interactive indexing,
* as this limits the length of pauses while indexing to a few seconds.
* Larger values are best for batched indexing and speedier searches.
*
* Default value is PHP_INT_MAX
*
* @param integer $maxMergeDocs
*/
public function setMaxMergeDocs($maxMergeDocs)
{
$this->getIndexWriter()->maxMergeDocs = $maxMergeDocs;
}
/**
* Retrieve index mergeFactor option
*
* mergeFactor determines how often segment indices are merged by addDocument().
* With smaller values, less RAM is used while indexing,
* and searches on unoptimized indices are faster,
* but indexing speed is slower.
* With larger values, more RAM is used during indexing,
* and while searches on unoptimized indices are slower,
* indexing is faster.
* Thus larger values (> 10) are best for batch index creation,
* and smaller values (< 10) for indices that are interactively maintained.
*
* Default value is 10
*
* @return integer
*/
public function getMergeFactor()
{
return $this->getIndexWriter()->mergeFactor;
}
/**
* Set index mergeFactor option
*
* mergeFactor determines how often segment indices are merged by addDocument().
* With smaller values, less RAM is used while indexing,
* and searches on unoptimized indices are faster,
* but indexing speed is slower.
* With larger values, more RAM is used during indexing,
* and while searches on unoptimized indices are slower,
* indexing is faster.
* Thus larger values (> 10) are best for batch index creation,
* and smaller values (< 10) for indices that are interactively maintained.
*
* Default value is 10
*
* @param integer $maxMergeDocs
*/
public function setMergeFactor($mergeFactor)
{
$this->getIndexWriter()->mergeFactor = $mergeFactor;
}
/**
* Performs a query against the index and returns an array
@@ -221,7 +545,8 @@ class Zend_Search_Lucene
* Input is a string or Zend_Search_Lucene_Search_Query.
*
* @param mixed $query
* @return array ZSearchHit
* @return array Zend_Search_Lucene_Search_QueryHit
* @throws Zend_Search_Lucene_Exception
*/
public function find($query)
{
@@ -235,22 +560,115 @@ class Zend_Search_Lucene
$this->commit();
$hits = array();
$hits = array();
$scores = array();
$ids = array();
$docNum = $this->count();
for( $count=0; $count < $docNum; $count++ ) {
$docScore = $query->score( $count, $this);
$query = $query->rewrite($this)->optimize($this);
$query->execute($this);
$topScore = 0;
foreach ($query->matchedDocs() as $id => $num) {
$docScore = $query->score($id, $this);
if( $docScore != 0 ) {
$hit = new Zend_Search_Lucene_Search_QueryHit($this);
$hit->id = $count;
$hit->id = $id;
$hit->score = $docScore;
$hits[] = $hit;
$hits[] = $hit;
$ids[] = $id;
$scores[] = $docScore;
if ($docScore > $topScore) {
$topScore = $docScore;
}
}
}
array_multisort($scores, SORT_DESC, SORT_REGULAR, $hits);
if (count($hits) == 0) {
// skip sorting, which may cause a error on empty index
return array();
}
if ($topScore > 1) {
foreach ($hits as $hit) {
$hit->score /= $topScore;
}
}
if (func_num_args() == 1) {
// sort by scores
array_multisort($scores, SORT_DESC, SORT_NUMERIC,
$ids, SORT_ASC, SORT_NUMERIC,
$hits);
} else {
// sort by given field names
$argList = func_get_args();
$fieldNames = $this->getFieldNames();
$sortArgs = array();
for ($count = 1; $count < count($argList); $count++) {
$fieldName = $argList[$count];
if (!is_string($fieldName)) {
throw new Zend_Search_Lucene_Exception('Field name must be a string.');
}
if (!in_array($fieldName, $fieldNames)) {
throw new Zend_Search_Lucene_Exception('Wrong field name.');
}
$valuesArray = array();
foreach ($hits as $hit) {
try {
$value = $hit->getDocument()->getFieldValue($fieldName);
} catch (Zend_Search_Lucene_Exception $e) {
if (strpos($e->getMessage(), 'not found') === false) {
throw $e;
} else {
$value = null;
}
}
$valuesArray[] = $value;
}
$sortArgs[] = $valuesArray;
if ($count + 1 < count($argList) && is_integer($argList[$count+1])) {
$count++;
$sortArgs[] = $argList[$count];
if ($count + 1 < count($argList) && is_integer($argList[$count+1])) {
$count++;
$sortArgs[] = $argList[$count];
} else {
if ($argList[$count] == SORT_ASC || $argList[$count] == SORT_DESC) {
$sortArgs[] = SORT_REGULAR;
} else {
$sortArgs[] = SORT_ASC;
}
}
} else {
$sortArgs[] = SORT_ASC;
$sortArgs[] = SORT_REGULAR;
}
}
// Sort by id's if values are equal
$sortArgs[] = $ids;
$sortArgs[] = SORT_ASC;
$sortArgs[] = SORT_NUMERIC;
// Array to be sorted
$sortArgs[] = &$hits;
// Do sort
call_user_func_array('array_multisort', $sortArgs);
}
return $hits;
}
@@ -290,41 +708,45 @@ class Zend_Search_Lucene
throw new Zend_Search_Lucene_Exception('Document id is out of the range.');
}
$segCount = 0;
$nextSegmentStartId = $this->_segmentInfos[ 0 ]->count();
while( $nextSegmentStartId <= $id ) {
$segCount++;
$nextSegmentStartId += $this->_segmentInfos[ $segCount ]->count();
}
$segmentStartId = $nextSegmentStartId - $this->_segmentInfos[ $segCount ]->count();
$segmentStartId = 0;
foreach ($this->_segmentInfos as $segmentInfo) {
if ($segmentStartId + $segmentInfo->count() > $id) {
break;
}
$fdxFile = $this->_segmentInfos[ $segCount ]->openCompoundFile('.fdx');
$segmentStartId += $segmentInfo->count();
}
$fdxFile = $segmentInfo->openCompoundFile('.fdx');
$fdxFile->seek( ($id-$segmentStartId)*8, SEEK_CUR );
$fieldValuesPosition = $fdxFile->readLong();
$fdtFile = $this->_segmentInfos[ $segCount ]->openCompoundFile('.fdt');
$fdtFile->seek( $fieldValuesPosition, SEEK_CUR );
$fdtFile = $segmentInfo->openCompoundFile('.fdt');
$fdtFile->seek($fieldValuesPosition, SEEK_CUR);
$fieldCount = $fdtFile->readVInt();
$doc = new Zend_Search_Lucene_Document();
for( $count = 0; $count < $fieldCount; $count++ ) {
for ($count = 0; $count < $fieldCount; $count++) {
$fieldNum = $fdtFile->readVInt();
$bits = $fdtFile->readByte();
$fieldInfo = $this->_segmentInfos[ $segCount ]->getField($fieldNum);
$fieldInfo = $segmentInfo->getField($fieldNum);
if( !($bits & 2) ) { // Text data
if (!($bits & 2)) { // Text data
$field = new Zend_Search_Lucene_Field($fieldInfo->name,
$fdtFile->readString(),
'UTF-8',
true,
$fieldInfo->isIndexed,
$bits & 1 );
} else {
} else { // Binary data
$field = new Zend_Search_Lucene_Field($fieldInfo->name,
$fdtFile->readBinary(),
'',
true,
$fieldInfo->isIndexed,
$bits & 1 );
$bits & 1,
true );
}
$doc->addField($field);
@@ -335,7 +757,26 @@ class Zend_Search_Lucene
/**
* Returns an array of all the documents which contain term.
* Returns true if index contain documents with specified term.
*
* Is used for query optimization.
*
* @param Zend_Search_Lucene_Index_Term $term
* @return boolean
*/
public function hasTerm(Zend_Search_Lucene_Index_Term $term)
{
foreach ($this->_segmentInfos as $segInfo) {
if ($segInfo->getTermInfo($term) instanceof Zend_Search_Lucene_Index_TermInfo) {
return true;
}
}
return false;
}
/**
* Returns IDs of all the documents containing term.
*
* @param Zend_Search_Lucene_Index_Term $term
* @return array
@@ -376,9 +817,29 @@ class Zend_Search_Lucene
}
/**
* Returns an array of all term freqs.
* Result array structure: array(docId => freq, ...)
*
* @param Zend_Search_Lucene_Index_Term $term
* @return integer
*/
public function termFreqs(Zend_Search_Lucene_Index_Term $term)
{
$result = array();
$segmentStartDocId = 0;
foreach ($this->_segmentInfos as $segmentInfo) {
$result += $segmentInfo->termFreqs($term, $segmentStartDocId);
$segmentStartDocId += $segmentInfo->count();
}
return $result;
}
/**
* Returns an array of all term positions in the documents.
* Return array structure: array( docId => array( pos1, pos2, ...), ...)
* Result array structure: array(docId => array(pos1, pos2, ...), ...)
*
* @param Zend_Search_Lucene_Index_Term $term
* @return array
@@ -387,45 +848,10 @@ class Zend_Search_Lucene
{
$result = array();
$segmentStartDocId = 0;
foreach( $this->_segmentInfos as $segInfo ) {
$termInfo = $segInfo->getTermInfo($term);
foreach ($this->_segmentInfos as $segmentInfo) {
$result += $segmentInfo->termPositions($term, $segmentStartDocId);
if (!$termInfo instanceof Zend_Search_Lucene_Index_TermInfo) {
$segmentStartDocId += $segInfo->count();
continue;
}
$frqFile = $segInfo->openCompoundFile('.frq');
$frqFile->seek($termInfo->freqPointer,SEEK_CUR);
$freqs = array();
$docId = 0;
for( $count = 0; $count < $termInfo->docFreq; $count++ ) {
$docDelta = $frqFile->readVInt();
if( $docDelta % 2 == 1 ) {
$docId += ($docDelta-1)/2;
$freqs[ $docId ] = 1;
} else {
$docId += $docDelta/2;
$freqs[ $docId ] = $frqFile->readVInt();
}
}
$prxFile = $segInfo->openCompoundFile('.prx');
$prxFile->seek($termInfo->proxPointer,SEEK_CUR);
foreach ($freqs as $docId => $freq) {
$termPosition = 0;
$positions = array();
for ($count = 0; $count < $freq; $count++ ) {
$termPosition += $prxFile->readVInt();
$positions[] = $termPosition;
}
$result[ $segmentStartDocId + $docId ] = $positions;
}
$segmentStartDocId += $segInfo->count();
$segmentStartDocId += $segmentInfo->count();
}
return $result;
@@ -468,9 +894,9 @@ class Zend_Search_Lucene
*
* @param integer $id
* @param string $fieldName
* @return Zend_Search_Lucene_Document
* @return float
*/
public function norm( $id, $fieldName )
public function norm($id, $fieldName)
{
if ($id >= $this->_docCount) {
return null;
@@ -527,16 +953,17 @@ class Zend_Search_Lucene
throw new Zend_Search_Lucene_Exception('Document id is out of the range.');
}
$segCount = 0;
$nextSegmentStartId = $this->_segmentInfos[ 0 ]->count();
while( $nextSegmentStartId <= $id ) {
$segCount++;
$nextSegmentStartId += $this->_segmentInfos[ $segCount ]->count();
$segmentStartId = 0;
foreach ($this->_segmentInfos as $segmentInfo) {
if ($segmentStartId + $segmentInfo->count() > $id) {
break;
}
$segmentStartId += $segmentInfo->count();
}
$segmentInfo->delete($id - $segmentStartId);
$this->_hasChanges = true;
$segmentStartId = $nextSegmentStartId - $this->_segmentInfos[ $segCount ]->count();
$this->_segmentInfos[ $segCount ]->delete($id - $segmentStartId);
}
@@ -548,18 +975,26 @@ class Zend_Search_Lucene
*/
public function addDocument(Zend_Search_Lucene_Document $document)
{
if (!$this->_writer instanceof Zend_Search_Lucene_Index_Writer) {
$this->_writer = new Zend_Search_Lucene_Index_Writer($this->_directory);
}
$this->_writer->addDocument($document);
$this->getIndexWriter()->addDocument($document);
$this->_docCount++;
}
/**
* Update document counter
*/
private function _updateDocCount()
{
$this->_docCount = 0;
foreach ($this->_segmentInfos as $segInfo) {
$this->_docCount += $segInfo->count();
}
}
/**
* Commit changes resulting from delete() or undeleteAll() operations.
*
* @todo delete() and undeleteAll processing.
* @todo undeleteAll processing.
*/
public function commit()
{
@@ -572,38 +1007,73 @@ class Zend_Search_Lucene
}
if ($this->_writer !== null) {
foreach ($this->_writer->commit() as $segmentName => $segmentInfo) {
if ($segmentInfo !== null) {
$this->_segmentInfos[] = $segmentInfo;
$this->_docCount += $segmentInfo->count();
} else {
foreach ($this->_segmentInfos as $segId => $segInfo) {
if ($segInfo->getName() == $segmentName) {
unset($this->_segmentInfos[$segId]);
}
}
}
$this->_writer->commit();
$this->_updateDocCount();
}
}
/**
* Optimize index.
*
* Merges all segments into one
*/
public function optimize()
{
// Commit changes if any changes have been made
$this->commit();
if (count($this->_segmentInfos) > 1 || $this->hasDeletions()) {
$this->getIndexWriter()->optimize();
$this->_updateDocCount();
}
}
/**
* Returns an array of all terms in this index.
*
* @return array
*/
public function terms()
{
$result = array();
$segmentInfoQueue = new Zend_Search_Lucene_Index_SegmentInfoPriorityQueue();
foreach ($this->_segmentInfos as $segmentInfo) {
$segmentInfo->reset();
// Skip "empty" segments
if ($segmentInfo->currentTerm() !== null) {
$segmentInfoQueue->put($segmentInfo);
}
}
while (($segmentInfo = $segmentInfoQueue->pop()) !== null) {
if ($segmentInfoQueue->top() === null ||
$segmentInfoQueue->top()->currentTerm()->key() !=
$segmentInfo->currentTerm()->key()) {
// We got new term
$result[] = $segmentInfo->currentTerm();
}
$segmentInfo->nextTerm();
// check, if segment dictionary is finished
if ($segmentInfo->currentTerm() !== null) {
// Put segment back into the priority queue
$segmentInfoQueue->put($segmentInfo);
}
}
return $result;
}
/*************************************************************************
@todo UNIMPLEMENTED
*************************************************************************/
/**
* Returns an array of all terms in this index.
*
* @todo Implementation
* @return array
*/
public function terms()
{
return array();
}
/**
* Undeletes all documents currently marked as deleted in this index.
*
@@ -611,4 +1081,4 @@ class Zend_Search_Lucene
*/
public function undeleteAll()
{}
}
}
+84 -11
View File
@@ -15,20 +15,37 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Analysis_Token */
require_once 'Zend/Search/Lucene/Analysis/Token.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Token.php';
/** Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8 */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Analyzer/Common/Utf8.php';
/** Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Analyzer/Common/Utf8Num.php';
/** Zend_Search_Lucene_Analysis_Analyzer_Common_Text */
require_once 'Zend/Search/Lucene/Analysis/Analyzer/Common/Text.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Analyzer/Common/Text.php';
/** Zend_Search_Lucene_Analysis_Analyzer_Common_Text_CaseInsensitive */
require_once 'Zend/Search/Lucene/Analysis/Analyzer/Common/Text/CaseInsensitive.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Analyzer/Common/Text/CaseInsensitive.php';
/** Zend_Search_Lucene_Analysis_Analyzer_Common_TextNum */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Analyzer/Common/TextNum.php';
/** Zend_Search_Lucene_Analysis_Analyzer_Common_TextNum_CaseInsensitive */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Analyzer/Common/TextNum/CaseInsensitive.php';
/** Zend_Search_Lucene_Analysis_TokenFilter_StopWords */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/TokenFilter/StopWords.php';
/** Zend_Search_Lucene_Analysis_TokenFilter_ShortWords */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/TokenFilter/ShortWords.php';
/**
@@ -44,7 +61,7 @@ require_once 'Zend/Search/Lucene/Analysis/Analyzer/Common/Text/CaseInsensitive.p
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
@@ -55,16 +72,73 @@ abstract class Zend_Search_Lucene_Analysis_Analyzer
*
* @var Zend_Search_Lucene_Analysis_Analyzer
*/
static private $_defaultImpl;
private static $_defaultImpl;
/**
* Input string
*
* @var string
*/
protected $_input = null;
/**
* Input string encoding
*
* @var string
*/
protected $_encoding = '';
/**
* Tokenize text to a terms
* Returns array of Zend_Search_Lucene_Analysis_Token objects
*
* Tokens are returned in UTF-8 (internal Zend_Search_Lucene encoding)
*
* @param string $data
* @return array
*/
abstract public function tokenize($data);
public function tokenize($data, $encoding = 'UTF-8')
{
$this->setInput($data, $encoding);
$tokenList = array();
while (($nextToken = $this->nextToken()) !== null) {
$tokenList[] = $nextToken;
}
return $tokenList;
}
/**
* Tokenization stream API
* Set input
*
* @param string $data
*/
public function setInput($data, $encoding = '')
{
$this->_input = $data;
$this->_encoding = $encoding;
$this->reset();
}
/**
* Reset token stream
*/
abstract public function reset();
/**
* Tokenization stream API
* Get next token
* Returns null at the end of stream
*
* Tokens are returned in UTF-8 (internal Zend_Search_Lucene encoding)
*
* @return Zend_Search_Lucene_Analysis_Token|null
*/
abstract public function nextToken();
/**
@@ -72,7 +146,7 @@ abstract class Zend_Search_Lucene_Analysis_Analyzer
*
* @param Zend_Search_Lucene_Analysis_Analyzer $similarity
*/
static public function setDefault(Zend_Search_Lucene_Analysis_Analyzer $analyzer)
public static function setDefault(Zend_Search_Lucene_Analysis_Analyzer $analyzer)
{
self::$_defaultImpl = $analyzer;
}
@@ -83,14 +157,13 @@ abstract class Zend_Search_Lucene_Analysis_Analyzer
*
* @return Zend_Search_Lucene_Analysis_Analyzer
*/
static public function getDefault()
public static function getDefault()
{
if (!self::$_defaultImpl instanceof Zend_Search_Lucene_Analysis_Analyzer) {
self::$_defaultImpl = new Zend_Search_Lucene_Analysis_Analyzer_Common_Text_CaseInsensitive();
self::$_defaultImpl = new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8();
}
return self::$_defaultImpl;
}
}
@@ -15,13 +15,13 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Analysis_Analyzer */
require_once 'Zend/Search/Lucene/Analysis/Analyzer.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Analyzer.php';
/**
@@ -34,7 +34,7 @@ require_once 'Zend/Search/Lucene/Analysis/Analyzer.php';
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
abstract class Zend_Search_Lucene_Analysis_Analyzer_Common extends Zend_Search_Lucene_Analysis_Analyzer
@@ -58,7 +58,7 @@ abstract class Zend_Search_Lucene_Analysis_Analyzer_Common extends Zend_Search_L
}
/**
* Apply filters to the token.
* Apply filters to the token. Can return null when the token was removed.
*
* @param Zend_Search_Lucene_Analysis_Token $token
* @return Zend_Search_Lucene_Analysis_Token
@@ -67,6 +67,11 @@ abstract class Zend_Search_Lucene_Analysis_Analyzer_Common extends Zend_Search_L
{
foreach ($this->_filters as $filter) {
$token = $filter->normalize($token);
// resulting token can be null if the filter removed it
if (is_null($token)) {
return null;
}
}
return $token;
@@ -15,64 +15,79 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Analysis_Analyzer_Common */
require_once 'Zend/Search/Lucene/Analysis/Analyzer/Common.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Analyzer/Common.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Analysis_Analyzer_Common_Text extends Zend_Search_Lucene_Analysis_Analyzer_Common
{
/**
* Tokenize text to a terms
* Returns array of Zend_Search_Lucene_Analysis_Token objects
* Current position in a stream
*
* @param string $data
* @return array
* @var integer
*/
public function tokenize($data)
private $_position;
/**
* Reset token stream
*/
public function reset()
{
$tokenStream = array();
$this->_position = 0;
$position = 0;
while ($position < strlen($data)) {
// skip white space
while ($position < strlen($data) && !ctype_alpha( $data{$position} )) {
$position++;
}
$termStartPosition = $position;
// read token
while ($position < strlen($data) && ctype_alpha( $data{$position} )) {
$position++;
}
// Empty token, end of stream.
if ($position == $termStartPosition) {
break;
}
$token = new Zend_Search_Lucene_Analysis_Token(substr($data,
$termStartPosition,
$position-$termStartPosition),
$termStartPosition,
$position);
$tokenStream[] = $this->normalize($token);
if ($this->_input === null) {
return;
}
return $tokenStream;
// convert input into ascii
$this->_input = iconv($this->_encoding, 'ASCII//TRANSLIT', $this->_input);
$this->_encoding = 'ASCII';
}
/**
* Tokenization stream API
* Get next token
* Returns null at the end of stream
*
* @return Zend_Search_Lucene_Analysis_Token|null
*/
public function nextToken()
{
if ($this->_input === null) {
return null;
}
do {
if (! preg_match('/[a-zA-Z]+/', $this->_input, $match, PREG_OFFSET_CAPTURE, $this->_position)) {
// It covers both cases a) there are no matches (preg_match(...) === 0)
// b) error occured (preg_match(...) === FALSE)
return null;
}
$str = $match[0][0];
$pos = $match[0][1];
$endpos = $pos + strlen($str);
$this->_position = $endpos;
$token = $this->normalize(new Zend_Search_Lucene_Analysis_Token($str, $pos, $endpos));
} while ($token === null); // try again if token is skipped
return $token;
}
}
@@ -15,23 +15,23 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Analysis_Analyzer_Common_Text */
require_once 'Zend/Search/Lucene/Analysis/Analyzer/Common/Text.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Analyzer/Common/Text.php';
/** Zend_Search_Lucene_Analysis_TokenFilter_LowerCase */
require_once 'Zend/Search/Lucene/Analysis/TokenFilter/LowerCase.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/TokenFilter/LowerCase.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
@@ -0,0 +1,92 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Analysis_Analyzer_Common */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Analyzer/Common.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Analysis_Analyzer_Common_TextNum extends Zend_Search_Lucene_Analysis_Analyzer_Common
{
/**
* Current position in a stream
*
* @var integer
*/
private $_position;
/**
* Reset token stream
*/
public function reset()
{
$this->_position = 0;
if ($this->_input === null) {
return;
}
// convert input into ascii
$this->_input = iconv($this->_encoding, 'ASCII//TRANSLIT', $this->_input);
$this->_encoding = 'ASCII';
}
/**
* Tokenization stream API
* Get next token
* Returns null at the end of stream
*
* @return Zend_Search_Lucene_Analysis_Token|null
*/
public function nextToken()
{
if ($this->_input === null) {
return null;
}
do {
if (! preg_match('/[a-zA-Z0-9]+/', $this->_input, $match, PREG_OFFSET_CAPTURE, $this->_position)) {
// It covers both cases a) there are no matches (preg_match(...) === 0)
// b) error occured (preg_match(...) === FALSE)
return null;
}
$str = $match[0][0];
$pos = $match[0][1];
$endpos = $pos + strlen($str);
$this->_position = $endpos;
$token = $this->normalize(new Zend_Search_Lucene_Analysis_Token($str, $pos, $endpos));
} while ($token === null); // try again if token is skipped
return $token;
}
}
@@ -0,0 +1,46 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Analysis_Analyzer_Common_TextNum */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Analyzer/Common/TextNum.php';
/** Zend_Search_Lucene_Analysis_TokenFilter_LowerCase */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/TokenFilter/LowerCase.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Analysis_Analyzer_Common_TextNum_CaseInsensitive extends Zend_Search_Lucene_Analysis_Analyzer_Common_TextNum
{
public function __construct()
{
$this->addFilter(new Zend_Search_Lucene_Analysis_TokenFilter_LowerCase());
}
}
@@ -0,0 +1,169 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Analysis_Analyzer_Common */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Analyzer/Common.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8 extends Zend_Search_Lucene_Analysis_Analyzer_Common
{
/**
* Current char position in an UTF-8 stream
*
* @var integer
*/
private $_position;
/**
* Current binary position in an UTF-8 stream
*
* @var integer
*/
private $_bytePosition;
/**
* Stream length
*
* @var integer
*/
private $_streamLength;
/**
* Reset token stream
*/
public function reset()
{
$this->_position = 0;
$this->_bytePosition = 0;
// convert input into UTF-8
if (strcasecmp($this->_encoding, 'utf8' ) != 0 &&
strcasecmp($this->_encoding, 'utf-8') != 0 ) {
$this->_input = iconv($this->_encoding, 'UTF-8', $this->_input);
$this->_encoding = 'UTF-8';
}
// Get UTF-8 string length.
// It also checks if it's a correct utf-8 string
$this->_streamLength = iconv_strlen($this->_input, 'UTF-8');
}
/**
* Check, that character is a letter
*
* @param string $char
* @return boolean
*/
private static function _isAlpha($char)
{
if (strlen($char) > 1) {
// It's an UTF-8 character
return true;
}
return ctype_alpha($char);
}
/**
* Get next UTF-8 char
*
* @param string $char
* @return boolean
*/
private function _nextChar()
{
$char = $this->_input[$this->_bytePosition++];
if (( ord($char) & 0xC0 ) == 0xC0) {
$addBytes = 1;
if (ord($char) & 0x20 ) {
$addBytes++;
if (ord($char) & 0x10 ) {
$addBytes++;
}
}
$char .= substr($this->_input, $this->_bytePosition, $addBytes);
$this->_bytePosition += $addBytes;
}
$this->_position++;
return $char;
}
/**
* Tokenization stream API
* Get next token
* Returns null at the end of stream
*
* @return Zend_Search_Lucene_Analysis_Token|null
*/
public function nextToken()
{
if ($this->_input === null) {
return null;
}
while ($this->_position < $this->_streamLength) {
// skip white space
while ($this->_position < $this->_streamLength &&
!self::_isAlpha($char = $this->_nextChar())) {
$char = '';
}
$termStartPosition = $this->_position - 1;
$termText = $char;
// read token
while ($this->_position < $this->_streamLength &&
self::_isAlpha($char = $this->_nextChar())) {
$termText .= $char;
}
// Empty token, end of stream.
if ($termText == '') {
return null;
}
$token = new Zend_Search_Lucene_Analysis_Token(
$termText,
$termStartPosition,
$this->_position - 1);
$token = $this->normalize($token);
if ($token !== null) {
return $token;
}
// Continue if token is skipped
}
return null;
}
}
@@ -0,0 +1,169 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Analysis_Analyzer_Common */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Analyzer/Common.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num extends Zend_Search_Lucene_Analysis_Analyzer_Common
{
/**
* Current char position in an UTF-8 stream
*
* @var integer
*/
private $_position;
/**
* Current binary position in an UTF-8 stream
*
* @var integer
*/
private $_bytePosition;
/**
* Stream length
*
* @var integer
*/
private $_streamLength;
/**
* Reset token stream
*/
public function reset()
{
$this->_position = 0;
$this->_bytePosition = 0;
// convert input into UTF-8
if (strcasecmp($this->_encoding, 'utf8' ) != 0 &&
strcasecmp($this->_encoding, 'utf-8') != 0 ) {
$this->_input = iconv($this->_encoding, 'UTF-8', $this->_input);
$this->_encoding = 'UTF-8';
}
// Get UTF-8 string length.
// It also checks if it's a correct utf-8 string
$this->_streamLength = iconv_strlen($this->_input, 'UTF-8');
}
/**
* Check, that character is a letter
*
* @param string $char
* @return boolean
*/
private static function _isAlNum($char)
{
if (strlen($char) > 1) {
// It's an UTF-8 character
return true;
}
return ctype_alnum($char);
}
/**
* Get next UTF-8 char
*
* @param string $char
* @return boolean
*/
private function _nextChar()
{
$char = $this->_input[$this->_bytePosition++];
if (( ord($char) & 0xC0 ) == 0xC0) {
$addBytes = 1;
if (ord($char) & 0x20 ) {
$addBytes++;
if (ord($char) & 0x10 ) {
$addBytes++;
}
}
$char .= substr($this->_input, $this->_bytePosition, $addBytes);
$this->_bytePosition += $addBytes;
}
$this->_position++;
return $char;
}
/**
* Tokenization stream API
* Get next token
* Returns null at the end of stream
*
* @return Zend_Search_Lucene_Analysis_Token|null
*/
public function nextToken()
{
if ($this->_input === null) {
return null;
}
while ($this->_position < $this->_streamLength) {
// skip white space
while ($this->_position < $this->_streamLength &&
!self::_isAlNum($char = $this->_nextChar())) {
$char = '';
}
$termStartPosition = $this->_position - 1;
$termText = $char;
// read token
while ($this->_position < $this->_streamLength &&
self::_isAlNum($char = $this->_nextChar())) {
$termText .= $char;
}
// Empty token, end of stream.
if ($termText == '') {
return null;
}
$token = new Zend_Search_Lucene_Analysis_Token(
$termText,
$termStartPosition,
$this->_position - 1);
$token = $this->normalize($token);
if ($token !== null) {
return $token;
}
// Continue if token is skipped
}
return null;
}
}
+3 -21
View File
@@ -15,7 +15,7 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
@@ -24,7 +24,7 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Analysis_Token
@@ -50,13 +50,6 @@ class Zend_Search_Lucene_Analysis_Token
*/
private $_endOffset;
/**
* Lexical type.
*
* @var string
*/
private $_type;
/**
* The position of this token relative to the previous Token.
*
@@ -90,12 +83,11 @@ class Zend_Search_Lucene_Analysis_Token
* @param integer $end
* @param string $type
*/
public function __construct($text, $start, $end, $type = 'word' )
public function __construct($text, $start, $end)
{
$this->_termText = $text;
$this->_startOffset = $start;
$this->_endOffset = $end;
$this->_type = $type;
$this->_positionIncrement = 1;
}
@@ -157,15 +149,5 @@ class Zend_Search_Lucene_Analysis_Token
{
return $this->_endOffset;
}
/**
* Returns this Token's lexical type. Defaults to 'word'.
*
* @return string
*/
public function getType()
{
return $this->_type;
}
}
@@ -15,13 +15,13 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Analysis_Token */
require_once 'Zend/Search/Lucene/Analysis/Token.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Token.php';
/**
@@ -30,7 +30,7 @@ require_once 'Zend/Search/Lucene/Analysis/Token.php';
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
@@ -15,13 +15,13 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Analysis_TokenFilter */
require_once 'Zend/Search/Lucene/Analysis/TokenFilter.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/TokenFilter.php';
/**
@@ -30,7 +30,7 @@ require_once 'Zend/Search/Lucene/Analysis/TokenFilter.php';
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
@@ -44,10 +44,10 @@ class Zend_Search_Lucene_Analysis_TokenFilter_LowerCase extends Zend_Search_Luce
*/
public function normalize(Zend_Search_Lucene_Analysis_Token $srcToken)
{
$newToken = new Zend_Search_Lucene_Analysis_Token(strtolower( $srcToken->getTermText() ),
$newToken = new Zend_Search_Lucene_Analysis_Token(
strtolower( $srcToken->getTermText() ),
$srcToken->getStartOffset(),
$srcToken->getEndOffset(),
$srcToken->getType());
$srcToken->getEndOffset());
$newToken->setPositionIncrement($srcToken->getPositionIncrement());
@@ -0,0 +1,68 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Analysis_TokenFilter */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/TokenFilter.php';
/**
* Token filter that removes short words. What is short word can be configured with constructor.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Analysis_TokenFilter_ShortWords extends Zend_Search_Lucene_Analysis_TokenFilter
{
/**
* Minimum allowed term length
* @var integer
*/
private $length;
/**
* Constructs new instance of this filter.
*
* @param integer $short minimum allowed length of term which passes this filter (default 2)
*/
public function __construct($length = 2) {
$this->length = $length;
}
/**
* Normalize Token or remove it (if null is returned)
*
* @param Zend_Search_Lucene_Analysis_Token $srcToken
* @return Zend_Search_Lucene_Analysis_Token
*/
public function normalize(Zend_Search_Lucene_Analysis_Token $srcToken) {
if (strlen($srcToken->getTermText()) < $this->length) {
return null;
} else {
return $srcToken;
}
}
}
@@ -0,0 +1,101 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Analysis_TokenFilter */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/TokenFilter.php';
require_once $CFG->dirroot.'/search/Zend/Search/Exception.php';
/**
* Token filter that removes stop words. These words must be provided as array (set), example:
* $stopwords = array('the' => 1, 'an' => '1');
*
* We do recommend to provide all words in lowercase and concatenate this class after the lowercase filter.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Analysis
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Analysis_TokenFilter_StopWords extends Zend_Search_Lucene_Analysis_TokenFilter
{
/**
* Minimum allowed term length
* @var array
*/
private $_stopSet;
/**
* Constructs new instance of this filter.
*
* @param array $stopwords array (set) of words that will be filtered out
*/
public function __construct($stopwords = array()) {
$this->_stopSet = array_flip($stopwords);
}
/**
* Normalize Token or remove it (if null is returned)
*
* @param Zend_Search_Lucene_Analysis_Token $srcToken
* @return Zend_Search_Lucene_Analysis_Token
*/
public function normalize(Zend_Search_Lucene_Analysis_Token $srcToken) {
if (array_key_exists($srcToken->getTermText(), $this->_stopSet)) {
$t = $srcToken->getTermText();
return null;
} else {
return $srcToken;
}
}
/**
* Fills stopwords set from a text file. Each line contains one stopword, lines with '#' in the first
* column are ignored (as comments).
*
* You can call this method one or more times. New stopwords are always added to current set.
*
* @param string $filepath full path for text file with stopwords
* @throws Zend_Search_Exception When the file doesn`t exists or is not readable.
*/
public function loadFromFile($filepath = null) {
if (! $filepath || ! file_exists($filepath)) {
throw new Zend_Search_Exception('You have to provide valid file path');
}
$fd = fopen($filepath, "r");
if (! $fd) {
throw new Zend_Search_Exception('Cannot open file ' . $filepath);
}
while (!feof ($fd)) {
$buffer = trim(fgets($fd));
if (strlen($buffer) > 0 && $buffer[0] != '#') {
$this->_stopSet[$buffer] = 1;
}
}
if (!fclose($fd)) {
throw new Zend_Search_Exception('Cannot close file ' . $filepath);
}
}
}
+17 -7
View File
@@ -15,13 +15,13 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Document
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Field */
require_once 'Zend/Search/Lucene/Field.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Field.php';
/**
@@ -30,7 +30,7 @@ require_once 'Zend/Search/Lucene/Field.php';
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Document
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Document
@@ -90,9 +90,9 @@ class Zend_Search_Lucene_Document
*/
public function getField($fieldName)
{
if (!array_key_exists($fieldName, $this->_fields)) {
throw new Zend_Search_Lucene_Exception("Field name \"$fieldName\" not found in document.");
}
if (!array_key_exists($fieldName, $this->_fields)) {
throw new Zend_Search_Lucene_Exception("Field name \"$fieldName\" not found in document.");
}
return $this->_fields[$fieldName];
}
@@ -105,7 +105,17 @@ class Zend_Search_Lucene_Document
*/
public function getFieldValue($fieldName)
{
return $this->getField($fieldName)->stringValue;
return $this->getField($fieldName)->value;
}
/**
* Returns the string value of a named field in UTF-8 encoding.
*
* @see __get()
* @return string
*/
public function getFieldUtf8Value($fieldName)
{
return $this->getField($fieldName)->getUtf8Value();
}
}
+310
View File
@@ -0,0 +1,310 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Document
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Document */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Document.php';
/**
* HTML document.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Document
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Document_Html extends Zend_Search_Lucene_Document
{
/**
* List of document links
*
* @var array
*/
private $_links = array();
/**
* List of document header links
*
* @var array
*/
private $_headerLinks = array();
/**
* Stored DOM representation
*
* @var DOMDocument
*/
private $_doc;
/**
* Object constructor
*
* @param string $data
* @param boolean $isFile
* @param boolean $storeContent
*/
private function __construct($data, $isFile, $storeContent)
{
$this->_doc = new DOMDocument();
$this->_doc->substituteEntities = true;
if ($isFile) {
@$this->_doc->loadHTMLFile($data);
} else{
@$this->_doc->loadHTML($data);
}
$xpath = new DOMXPath($this->_doc);
$docTitle = '';
$titleNodes = $xpath->query('/html/head/title');
foreach ($titleNodes as $titleNode) {
// title should always have only one entry, but we process all nodeset entries
$docTitle .= $titleNode->nodeValue . ' ';
}
$this->addField(Zend_Search_Lucene_Field::Text('title', $docTitle, $this->_doc->actualEncoding));
$metaNodes = $xpath->query('/html/head/meta[@name]');
foreach ($metaNodes as $metaNode) {
$this->addField(Zend_Search_Lucene_Field::Text($metaNode->getAttribute('name'),
$metaNode->getAttribute('content'),
$this->_doc->actualEncoding));
}
$docBody = '';
$bodyNodes = $xpath->query('/html/body');
foreach ($bodyNodes as $bodyNode) {
// body should always have only one entry, but we process all nodeset entries
$this->_retrieveNodeText($bodyNode, $docBody);
}
if ($storeContent) {
$this->addField(Zend_Search_Lucene_Field::Text('body', $docBody, $this->_doc->actualEncoding));
} else {
$this->addField(Zend_Search_Lucene_Field::UnStored('body', $docBody, $this->_doc->actualEncoding));
}
$linkNodes = $this->_doc->getElementsByTagName('a');
foreach ($linkNodes as $linkNode) {
if (($href = $linkNode->getAttribute('href')) != '') {
$this->_links[] = $href;
}
}
$this->_links = array_unique($this->_links);
$linkNodes = $xpath->query('/html/head/link');
foreach ($linkNodes as $linkNode) {
if (($href = $linkNode->getAttribute('href')) != '') {
$this->_headerLinks[] = $href;
}
}
$this->_headerLinks = array_unique($this->_headerLinks);
}
/**
* Get node text
*
* We should exclude scripts, which may be not included into comment tags, CDATA sections,
*
* @param DOMNode $node
* @param string &$text
*/
private function _retrieveNodeText(DOMNode $node, &$text)
{
if ($node->nodeType == XML_TEXT_NODE) {
$text .= $node->nodeValue ;
$text .= ' ';
} else if ($node->nodeType == XML_ELEMENT_NODE && $node->nodeName != 'script') {
foreach ($node->childNodes as $childNode) {
$this->_retrieveNodeText($childNode, $text);
}
}
}
/**
* Get document HREF links
*
* @return array
*/
public function getLinks()
{
return $this->_links;
}
/**
* Get document header links
*
* @return array
*/
public function getHeaderLinks()
{
return $this->_headerLinks;
}
/**
* Load HTML document from a string
*
* @param string $data
* @param boolean $storeContent
* @return Zend_Search_Lucene_Document_Html
*/
public static function loadHTML($data, $storeContent = false)
{
return new Zend_Search_Lucene_Document_Html($data, false, $storeContent);
}
/**
* Load HTML document from a file
*
* @param string $file
* @param boolean $storeContent
* @return Zend_Search_Lucene_Document_Html
*/
public static function loadHTMLFile($file, $storeContent = false)
{
return new Zend_Search_Lucene_Document_Html($file, true, $storeContent);
}
/**
* Highlight text in text node
*
* @param DOMText $node
* @param array $wordsToHighlight
* @param string $color
*/
public function _highlightTextNode(DOMText $node, $wordsToHighlight, $color)
{
$analyzer = Zend_Search_Lucene_Analysis_Analyzer::getDefault();
$analyzer->setInput($node->nodeValue, $this->_doc->encoding);
$matchedTokens = array();
while (($token = $analyzer->nextToken()) !== null) {
if (isset($wordsToHighlight[$token->getTermText()])) {
$matchedTokens[] = $token;
}
}
if (count($matchedTokens) == 0) {
return;
}
$matchedTokens = array_reverse($matchedTokens);
foreach ($matchedTokens as $token) {
// Cut text after matched token
$node->splitText($token->getEndOffset());
// Cut matched node
$matchedWordNode = $node->splitText($token->getStartOffset());
$highlightedNode = $this->_doc->createElement('b', $matchedWordNode->nodeValue);
$highlightedNode->setAttribute('style', 'color:black;background-color:' . $color);
$node->parentNode->replaceChild($highlightedNode, $matchedWordNode);
}
}
/**
* highlight words in content of the specified node
*
* @param DOMNode $contextNode
* @param array $wordsToHighlight
* @param string $color
*/
public function _highlightNode(DOMNode $contextNode, $wordsToHighlight, $color)
{
$textNodes = array();
if (!$contextNode->hasChildNodes()) {
return;
}
foreach ($contextNode->childNodes as $childNode) {
if ($childNode->nodeType == XML_TEXT_NODE) {
// process node later to leave childNodes structure untouched
$textNodes[] = $childNode;
} else {
// Skip script nodes
if ($childNode->nodeName != 'script') {
$this->_highlightNode($childNode, $wordsToHighlight, $color);
}
}
}
foreach ($textNodes as $textNode) {
$this->_highlightTextNode($textNode, $wordsToHighlight, $color);
}
}
/**
* Highlight text with specified color
*
* @param string|array $words
* @param string $color
* @return string
*/
public function highlight($words, $color = '#66ffff')
{
if (!is_array($words)) {
$words = array($words);
}
$wordsToHighlight = array();
$analyzer = Zend_Search_Lucene_Analysis_Analyzer::getDefault();
foreach ($words as $wordString) {
$wordsToHighlight = array_merge($wordsToHighlight, $analyzer->tokenize($wordString));
}
if (count($wordsToHighlight) == 0) {
return $this->_doc->saveHTML();
}
$wordsToHighlightFlipped = array();
foreach ($wordsToHighlight as $id => $token) {
$wordsToHighlightFlipped[$token->getTermText()] = $id;
}
$xpath = new DOMXPath($this->_doc);
$matchedNodes = $xpath->query("/html/body/*");
foreach ($matchedNodes as $matchedNode) {
$this->_highlightNode($matchedNode, $wordsToHighlightFlipped, $color);
}
}
/**
* Get HTML
*
* @return string
*/
public function getHTML()
{
return $this->_doc->saveHTML();
}
}
+3 -3
View File
@@ -14,7 +14,7 @@
*
* @category Zend
* @package Zend_Search_Lucene
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
@@ -22,13 +22,13 @@
/**
* Framework base exception
*/
require_once 'Zend/Search/Exception.php';
require_once $CFG->dirroot.'/search/Zend/Search/Exception.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Exception extends Zend_Search_Exception
+433
View File
@@ -0,0 +1,433 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_FSMAction */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/FSMAction.php';
/** Zend_Search_Exception */
require_once $CFG->dirroot.'/search/Zend/Search/Exception.php';
/**
* Abstract Finite State Machine
*
* Take a look on Wikipedia state machine description: http://en.wikipedia.org/wiki/Finite_state_machine
*
* Any type of Transducers (Moore machine or Mealy machine) also may be implemented by using this abstract FSM.
* process() methods invokes a specified actions which may construct FSM output.
* Actions may be also used to signal, that we have reached Accept State
*
* @category Zend
* @package Zend_Search_Lucene
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
abstract class Zend_Search_Lucene_FSM
{
/**
* Machine States alphabet
*
* @var array
*/
private $_states = array();
/**
* Current state
*
* @var integer|string
*/
private $_currentState = null;
/**
* Input alphabet
*
* @var array
*/
private $_inputAphabet = array();
/**
* State transition table
*
* [sourceState][input] => targetState
*
* @var array
*/
private $_rules = array();
/**
* List of entry actions
* Each action executes when entering the state
*
* [state] => action
*
* @var array
*/
private $_entryActions = array();
/**
* List of exit actions
* Each action executes when exiting the state
*
* [state] => action
*
* @var array
*/
private $_exitActions = array();
/**
* List of input actions
* Each action executes when entering the state
*
* [state][input] => action
*
* @var array
*/
private $_inputActions = array();
/**
* List of input actions
* Each action executes when entering the state
*
* [state1][state2] => action
*
* @var array
*/
private $_transitionActions = array();
/**
* Finite State machine constructor
*
* $states is an array of integers or strings with a list of possible machine states
* constructor treats fist list element as a sturt state (assignes it to $_current state).
* It may be reassigned by setState() call.
* States list may be empty and can be extended later by addState() or addStates() calls.
*
* $inputAphabet is the same as $states, but represents input alphabet
* it also may be extended later by addInputSymbols() or addInputSymbol() calls.
*
* $rules parameter describes FSM transitions and has a structure:
* array( array(sourseState, input, targetState[, inputAction]),
* array(sourseState, input, targetState[, inputAction]),
* array(sourseState, input, targetState[, inputAction]),
* ...
* )
* Rules also can be added later by addRules() and addRule() calls.
*
* FSM actions are very flexible and may be defined by addEntryAction(), addExitAction(),
* addInputAction() and addTransitionAction() calls.
*
* @param array $states
* @param array $inputAphabet
* @param array $rules
*/
public function __construct($states = array(), $inputAphabet = array(), $rules = array())
{
$this->addStates($states);
$this->addInputSymbols($inputAphabet);
$this->addRules($rules);
}
/**
* Add states to the state machine
*
* @param array $states
*/
public function addStates($states)
{
foreach ($states as $state) {
$this->addState($state);
}
}
/**
* Add state to the state machine
*
* @param integer|string $state
*/
public function addState($state)
{
$this->_states[$state] = $state;
if ($this->_currentState === null) {
$this->_currentState = $state;
}
}
/**
* Set FSM state.
* No any action is invoked
*
* @param integer|string $state
* @throws Zend_Search_Exception
*/
public function setState($state)
{
if (!isset($this->_states[$state])) {
throw new Zend_Search_Exception('State \'' . $state . '\' is not on of the possible FSM states.');
}
$this->_currentState = $state;
}
/**
* Get FSM state.
*
* @return integer|string $state|null
*/
public function getState()
{
return $this->_currentState;
}
/**
* Add symbols to the input alphabet
*
* @param array $inputAphabet
*/
public function addInputSymbols($inputAphabet)
{
foreach ($inputAphabet as $inputSymbol) {
$this->addInputSymbol($inputSymbol);
}
}
/**
* Add symbol to the input alphabet
*
* @param integer|string $inputSymbol
*/
public function addInputSymbol($inputSymbol)
{
$this->_inputAphabet[$inputSymbol] = $inputSymbol;
}
/**
* Add transition rules
*
* array structure:
* array( array(sourseState, input, targetState[, inputAction]),
* array(sourseState, input, targetState[, inputAction]),
* array(sourseState, input, targetState[, inputAction]),
* ...
* )
*
* @param array $rules
*/
public function addRules($rules)
{
foreach ($rules as $rule) {
$this->addrule($rule[0], $rule[1], $rule[2], isset($rule[3])?$rule[3]:null);
}
}
/**
* Add symbol to the input alphabet
*
* @param integer|string $sourceState
* @param integer|string $input
* @param integer|string $targetState
* @param Zend_Search_Lucene_FSMAction|null $inputAction
* @throws Zend_Search_Exception
*/
public function addRule($sourceState, $input, $targetState, $inputAction = null)
{
if (!isset($this->_states[$sourceState])) {
throw new Zend_Search_Exception('Undefined source state (' . $sourceState . ').');
}
if (!isset($this->_states[$targetState])) {
throw new Zend_Search_Exception('Undefined target state (' . $targetState . ').');
}
if (!isset($this->_inputAphabet[$input])) {
throw new Zend_Search_Exception('Undefined input symbol (' . $input . ').');
}
if (!isset($this->_rules[$sourceState])) {
$this->_rules[$sourceState] = array();
}
if (isset($this->_rules[$sourceState][$input])) {
throw new Zend_Search_Exception('Rule for {state,input} pair (' . $sourceState . ', '. $input . ') is already defined.');
}
$this->_rules[$sourceState][$input] = $targetState;
if ($inputAction !== null) {
$this->addInputAction($sourceState, $input, $inputAction);
}
}
/**
* Add state entry action.
* Several entry actions are allowed.
* Action execution order is defined by addEntryAction() calls
*
* @param integer|string $state
* @param Zend_Search_Lucene_FSMAction $action
*/
public function addEntryAction($state, Zend_Search_Lucene_FSMAction $action)
{
if (!isset($this->_states[$state])) {
throw new Zend_Search_Exception('Undefined state (' . $state. ').');
}
if (!isset($this->_entryActions[$state])) {
$this->_entryActions[$state] = array();
}
$this->_entryActions[$state][] = $action;
}
/**
* Add state exit action.
* Several exit actions are allowed.
* Action execution order is defined by addEntryAction() calls
*
* @param integer|string $state
* @param Zend_Search_Lucene_FSMAction $action
*/
public function addExitAction($state, Zend_Search_Lucene_FSMAction $action)
{
if (!isset($this->_states[$state])) {
throw new Zend_Search_Exception('Undefined state (' . $state. ').');
}
if (!isset($this->_exitActions[$state])) {
$this->_exitActions[$state] = array();
}
$this->_exitActions[$state][] = $action;
}
/**
* Add input action (defined by {state, input} pair).
* Several input actions are allowed.
* Action execution order is defined by addInputAction() calls
*
* @param integer|string $state
* @param integer|string $input
* @param Zend_Search_Lucene_FSMAction $action
*/
public function addInputAction($state, $inputSymbol, Zend_Search_Lucene_FSMAction $action)
{
if (!isset($this->_states[$state])) {
throw new Zend_Search_Exception('Undefined state (' . $state. ').');
}
if (!isset($this->_inputAphabet[$inputSymbol])) {
throw new Zend_Search_Exception('Undefined input symbol (' . $inputSymbol. ').');
}
if (!isset($this->_inputActions[$state])) {
$this->_inputActions[$state] = array();
}
if (!isset($this->_inputActions[$state][$inputSymbol])) {
$this->_inputActions[$state][$inputSymbol] = array();
}
$this->_inputActions[$state][$inputSymbol][] = $action;
}
/**
* Add transition action (defined by {state, input} pair).
* Several transition actions are allowed.
* Action execution order is defined by addTransitionAction() calls
*
* @param integer|string $sourceState
* @param integer|string $targetState
* @param Zend_Search_Lucene_FSMAction $action
*/
public function addTransitionAction($sourceState, $targetState, Zend_Search_Lucene_FSMAction $action)
{
if (!isset($this->_states[$sourceState])) {
throw new Zend_Search_Exception('Undefined source state (' . $sourceState. ').');
}
if (!isset($this->_states[$targetState])) {
throw new Zend_Search_Exception('Undefined source state (' . $targetState. ').');
}
if (!isset($this->_transitionActions[$sourceState])) {
$this->_transitionActions[$sourceState] = array();
}
if (!isset($this->_transitionActions[$sourceState][$targetState])) {
$this->_transitionActions[$sourceState][$targetState] = array();
}
$this->_transitionActions[$sourceState][$targetState][] = $action;
}
/**
* Process an input
*
* @param mixed $input
* @throws Zend_Search_Exception
*/
public function process($input)
{
if (!isset($this->_rules[$this->_currentState])) {
throw new Zend_Search_Exception('There is no any rule for current state (' . $this->_currentState . ').');
}
if (!isset($this->_rules[$this->_currentState][$input])) {
throw new Zend_Search_Exception('There is no any rule for {current state, input} pair (' . $this->_currentState . ', ' . $input . ').');
}
$sourceState = $this->_currentState;
$targetState = $this->_rules[$this->_currentState][$input];
if ($sourceState != $targetState && isset($this->_exitActions[$sourceState])) {
foreach ($this->_exitActions[$sourceState] as $action) {
$action->doAction();
}
}
if (isset($this->_inputActions[$sourceState]) &&
isset($this->_inputActions[$sourceState][$input])) {
foreach ($this->_inputActions[$sourceState][$input] as $action) {
$action->doAction();
}
}
$this->_currentState = $targetState;
if (isset($this->_transitionActions[$sourceState]) &&
isset($this->_transitionActions[$sourceState][$targetState])) {
foreach ($this->_transitionActions[$sourceState][$targetState] as $action) {
$action->doAction();
}
}
if ($sourceState != $targetState && isset($this->_entryActions[$targetState])) {
foreach ($this->_entryActions[$targetState] as $action) {
$action->doAction();
}
}
}
public function reset()
{
if (count($this->_states) == 0) {
throw new Zend_Search_Exception('There is no any state defined for FSM.');
}
$this->_currentState = $this->_states[0];
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/**
* Abstract Finite State Machine
*
*
* @category Zend
* @package Zend_Search_Lucene
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_FSMAction
{
/**
* Object reference
*
* @var object
*/
private $_object;
/**
* Method name
*
* @var string
*/
private $_method;
/**
* Object constructor
*
* @param object $object
* @param string $method
*/
public function __construct($object, $method)
{
$this->_object = $object;
$this->_method = $method;
}
public function doAction()
{
$methodName = $this->_method;
$this->_object->$methodName();
}
}
+74 -28
View File
@@ -15,7 +15,7 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Document
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
@@ -31,15 +31,20 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Document
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Field
{
public $kind;
/**
* Field name
*
* @var string
*/
public $name;
public $name = 'body';
public $stringValue = null;
public $value;
public $isStored = false;
public $isIndexed = true;
public $isTokenized = true;
@@ -47,26 +52,48 @@ class Zend_Search_Lucene_Field
public $storeTermVector = false;
/**
* Field boos factor
* It's not stored directly in the index, but affects on normalizetion factor
*
* @var float
*/
public $boost = 1.0;
public function __construct($name, $stringValue, $isStored, $isIndexed, $isTokenized, $isBinary = false)
/**
* Field value encoding.
*
* @var string
*/
public $encoding;
/**
* Object constructor
*
* @param string $name
* @param string $value
* @param string $encoding
* @param boolean $isStored
* @param boolean $isIndexed
* @param boolean $isTokenized
* @param boolean $isBinary
*/
public function __construct($name, $value, $encoding, $isStored, $isIndexed, $isTokenized, $isBinary = false)
{
$this->name = $name;
$this->name = $name;
$this->value = $value;
if (!$isBinary) {
/**
* @todo Correct UTF-8 string should be required in future
* Until full UTF-8 support is not completed, string should be normalized to ANSII encoding
*/
$this->stringValue = iconv(mb_detect_encoding($stringValue), 'ASCII//TRANSLIT', $stringValue);
//$this->stringValue = iconv('', 'ASCII//TRANSLIT', $stringValue);
$this->encoding = $encoding;
$this->isTokenized = $isTokenized;
} else {
$this->stringValue = $stringValue;
$this->encoding = '';
$this->isTokenized = false;
}
$this->isStored = $isStored;
$this->isIndexed = $isIndexed;
$this->isTokenized = $isTokenized;
$this->isBinary = $isBinary;
$this->isStored = $isStored;
$this->isIndexed = $isIndexed;
$this->isBinary = $isBinary;
$this->storeTermVector = false;
$this->boost = 1.0;
@@ -79,11 +106,12 @@ class Zend_Search_Lucene_Field
*
* @param string $name
* @param string $value
* @param string $encoding
* @return Zend_Search_Lucene_Field
*/
static public function Keyword($name, $value)
public static function Keyword($name, $value, $encoding = '')
{
return new self($name, $value, true, true, false);
return new self($name, $value, $encoding, true, true, false);
}
@@ -93,11 +121,12 @@ class Zend_Search_Lucene_Field
*
* @param string $name
* @param string $value
* @param string $encoding
* @return Zend_Search_Lucene_Field
*/
static public function UnIndexed($name, $value)
public static function UnIndexed($name, $value, $encoding = '')
{
return new self($name, $value, true, false, false);
return new self($name, $value, $encoding, true, false, false);
}
@@ -107,11 +136,12 @@ class Zend_Search_Lucene_Field
*
* @param string $name
* @param string $value
* @param string $encoding
* @return Zend_Search_Lucene_Field
*/
static public function Binary($name, $value)
public static function Binary($name, $value)
{
return new self($name, $value, true, false, false, true);
return new self($name, $value, '', true, false, false, true);
}
/**
@@ -121,11 +151,12 @@ class Zend_Search_Lucene_Field
*
* @param string $name
* @param string $value
* @param string $encoding
* @return Zend_Search_Lucene_Field
*/
static public function Text($name, $value)
public static function Text($name, $value, $encoding = '')
{
return new self($name, $value, true, true, true);
return new self($name, $value, $encoding, true, true, true);
}
@@ -135,12 +166,27 @@ class Zend_Search_Lucene_Field
*
* @param string $name
* @param string $value
* @param string $encoding
* @return Zend_Search_Lucene_Field
*/
static public function UnStored($name, $value)
public static function UnStored($name, $value, $encoding = '')
{
return new self($name, $value, false, true, true);
return new self($name, $value, $encoding, false, true, true);
}
/**
* Get field value in UTF-8 encoding
*
* @return string
*/
public function getUtf8Value()
{
if (strcasecmp($this->encoding, 'utf8' ) == 0 ||
strcasecmp($this->encoding, 'utf-8') == 0 ) {
return $this->value;
} else {
return iconv($this->encoding, 'UTF-8', $this->value);
}
}
}
@@ -0,0 +1,254 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Exception */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/**
* Dictionary loader
*
* It's a dummy class which is created to encapsulate non-good structured code.
* Manual "method inlining" is performed to increase dictionary index loading operation
* which is major bottelneck for search performance.
*
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Index_DictionaryLoader
{
/**
* Dictionary index loader.
*
* It takes a string which is actually <segment_name>.tii index file data and
* returns two arrays - term and tremInfo lists.
*
* See Zend_Search_Lucene_Index_SegmintInfo class for details
*
* @param string $data
* @return array
* @throws Zend_Search_Lucene_Exception
*/
public static function load($data)
{
$termDictionary = array();
$termInfos = array();
$pos = 0;
// $tiVersion = $tiiFile->readInt();
$tiVersion = ord($data[0]) << 24 | ord($data[1]) << 16 | ord($data[2]) << 8 | ord($data[3]);
$pos += 4;
if ($tiVersion != (int)0xFFFFFFFE) {
throw new Zend_Search_Lucene_Exception('Wrong TermInfoIndexFile file format');
}
// $indexTermCount = = $tiiFile->readLong();
if (PHP_INT_SIZE > 4) {
$indexTermCount = ord($data[$pos]) << 56 |
ord($data[$pos+1]) << 48 |
ord($data[$pos+2]) << 40 |
ord($data[$pos+3]) << 32 |
ord($data[$pos+4]) << 24 |
ord($data[$pos+5]) << 16 |
ord($data[$pos+6]) << 8 |
ord($data[$pos+7]);
} else {
if ((ord($data[$pos]) != 0) ||
(ord($data[$pos+1]) != 0) ||
(ord($data[$pos+2]) != 0) ||
(ord($data[$pos+3]) != 0) ||
((ord($data[$pos+4]) & 0x80) != 0)) {
throw new Zend_Search_Lucene_Exception('Largest supported segment size (for 32-bit mode) is 2Gb');
}
$indexTermCount = ord($data[$pos+4]) << 24 |
ord($data[$pos+5]) << 16 |
ord($data[$pos+6]) << 8 |
ord($data[$pos+7]);
}
$pos += 8;
// $tiiFile->readInt(); // IndexInterval
$pos += 4;
// $skipInterval = $tiiFile->readInt();
$skipInterval = ord($data[$pos]) << 24 | ord($data[$pos+1]) << 16 | ord($data[$pos+2]) << 8 | ord($data[$pos+3]);
$pos += 4;
if ($indexTermCount < 1) {
throw new Zend_Search_Lucene_Exception('Wrong number of terms in a term dictionary index');
}
$prevTerm = '';
$freqPointer = 0;
$proxPointer = 0;
$indexPointer = 0;
for ($count = 0; $count < $indexTermCount; $count++) {
//$termPrefixLength = $tiiFile->readVInt();
$nbyte = ord($data[$pos++]);
$termPrefixLength = $nbyte & 0x7F;
for ($shift=7; ($nbyte & 0x80) != 0; $shift += 7) {
$nbyte = ord($data[$pos++]);
$termPrefixLength |= ($nbyte & 0x7F) << $shift;
}
// $termSuffix = $tiiFile->readString();
$nbyte = ord($data[$pos++]);
$len = $nbyte & 0x7F;
for ($shift=7; ($nbyte & 0x80) != 0; $shift += 7) {
$nbyte = ord($data[$pos++]);
$len |= ($nbyte & 0x7F) << $shift;
}
if ($len == 0) {
$termSuffix = '';
} else {
$termSuffix = substr($data, $pos, $len);
$pos += $len;
for ($count1 = 0; $count1 < $len; $count1++ ) {
if (( ord($termSuffix[$count1]) & 0xC0 ) == 0xC0) {
$addBytes = 1;
if (ord($termSuffix[$count1]) & 0x20 ) {
$addBytes++;
}
$termSuffix .= substr($data, $pos, $addBytes);
$pos += $addBytes;
$len += $addBytes;
// Check for null character. Java2 encodes null character
// in two bytes.
if (ord($termSuffix[$count1]) == 0xC0 &&
ord($termSuffix[$count1+1]) == 0x80 ) {
$termSuffix[$count1] = 0;
$termSuffix = substr($termSuffix,0,$count1+1)
. substr($termSuffix,$count1+2);
}
$count1 += $addBytes;
}
}
}
// $termValue = Zend_Search_Lucene_Index_Term::getPrefix($prevTerm, $termPrefixLength) . $termSuffix;
$pb = 0; $pc = 0;
while ($pb < strlen($prevTerm) && $pc < $termPrefixLength) {
$charBytes = 1;
if ((ord($prevTerm[$pb]) & 0xC0) == 0xC0) {
$charBytes++;
if (ord($prevTerm[$pb]) & 0x20 ) {
$charBytes++;
if (ord($prevTerm[$pb]) & 0x10 ) {
$charBytes++;
}
}
}
if ($pb + $charBytes > strlen($data)) {
// wrong character
break;
}
$pc++;
$pb += $charBytes;
}
$termValue = substr($prevTerm, 0, $pb) . $termSuffix;
// $termFieldNum = $tiiFile->readVInt();
$nbyte = ord($data[$pos++]);
$termFieldNum = $nbyte & 0x7F;
for ($shift=7; ($nbyte & 0x80) != 0; $shift += 7) {
$nbyte = ord($data[$pos++]);
$termFieldNum |= ($nbyte & 0x7F) << $shift;
}
// $docFreq = $tiiFile->readVInt();
$nbyte = ord($data[$pos++]);
$docFreq = $nbyte & 0x7F;
for ($shift=7; ($nbyte & 0x80) != 0; $shift += 7) {
$nbyte = ord($data[$pos++]);
$docFreq |= ($nbyte & 0x7F) << $shift;
}
// $freqPointer += $tiiFile->readVInt();
$nbyte = ord($data[$pos++]);
$vint = $nbyte & 0x7F;
for ($shift=7; ($nbyte & 0x80) != 0; $shift += 7) {
$nbyte = ord($data[$pos++]);
$vint |= ($nbyte & 0x7F) << $shift;
}
$freqPointer += $vint;
// $proxPointer += $tiiFile->readVInt();
$nbyte = ord($data[$pos++]);
$vint = $nbyte & 0x7F;
for ($shift=7; ($nbyte & 0x80) != 0; $shift += 7) {
$nbyte = ord($data[$pos++]);
$vint |= ($nbyte & 0x7F) << $shift;
}
$proxPointer += $vint;
if( $docFreq >= $skipInterval ) {
// $skipDelta = $tiiFile->readVInt();
$nbyte = ord($data[$pos++]);
$vint = $nbyte & 0x7F;
for ($shift=7; ($nbyte & 0x80) != 0; $shift += 7) {
$nbyte = ord($data[$pos++]);
$vint |= ($nbyte & 0x7F) << $shift;
}
$skipDelta = $vint;
} else {
$skipDelta = 0;
}
// $indexPointer += $tiiFile->readVInt();
$nbyte = ord($data[$pos++]);
$vint = $nbyte & 0x7F;
for ($shift=7; ($nbyte & 0x80) != 0; $shift += 7) {
$nbyte = ord($data[$pos++]);
$vint |= ($nbyte & 0x7F) << $shift;
}
$indexPointer += $vint;
// $this->_termDictionary[] = new Zend_Search_Lucene_Index_Term($termValue, $termFieldNum);
$termDictionary[] = array($termFieldNum, $termValue);
$termInfos[] =
// new Zend_Search_Lucene_Index_TermInfo($docFreq, $freqPointer, $proxPointer, $skipDelta, $indexPointer);
array($docFreq, $freqPointer, $proxPointer, $skipDelta, $indexPointer);
$prevTerm = $termValue;
}
// Check special index entry mark
if ($termDictionary[0][0] != (int)0xFFFFFFFF) {
throw new Zend_Search_Lucene_Exception('Wrong TermInfoIndexFile file format');
} else if (PHP_INT_SIZE > 4){
// Treat 64-bit 0xFFFFFFFF as -1
$termDictionary[0][0] = -1;
}
return array(&$termDictionary, &$termInfos);
}
}
@@ -15,7 +15,7 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
@@ -24,7 +24,7 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Index_FieldInfo
+561 -80
View File
@@ -15,20 +15,23 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Index_DictionaryLoader */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/DictionaryLoader.php';
/** Zend_Search_Lucene_Exception */
require_once 'Zend/Search/Lucene/Exception.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Index_SegmentInfo
@@ -49,7 +52,12 @@ class Zend_Search_Lucene_Index_SegmentInfo
/**
* Term Dictionary Index
* Array of the Zend_Search_Lucene_Index_Term objects
*
* Array of arrays (Zend_Search_Lucene_Index_Term objects are represented as arrays because
* of performance considerations)
* [0] -> $termValue
* [1] -> $termFieldNum
*
* Corresponding Zend_Search_Lucene_Index_TermInfo object stored in the $_termDictionaryInfos
*
* @var array
@@ -58,7 +66,14 @@ class Zend_Search_Lucene_Index_SegmentInfo
/**
* Term Dictionary Index TermInfos
* Array of the Zend_Search_Lucene_Index_TermInfo objects
*
* Array of arrays (Zend_Search_Lucene_Index_TermInfo objects are represented as arrays because
* of performance considerations)
* [0] -> $docFreq
* [1] -> $freqPointer
* [2] -> $proxPointer
* [3] -> $skipOffset
* [4] -> $indexPointer
*
* @var array
*/
@@ -88,6 +103,14 @@ class Zend_Search_Lucene_Index_SegmentInfo
*/
private $_segFiles;
/**
* Associative array where the key is the file name and the value is file size (.csf).
*
* @var array
*/
private $_segFileSizes;
/**
* File system adapter.
*
@@ -122,6 +145,7 @@ class Zend_Search_Lucene_Index_SegmentInfo
*/
private $_deletedDirty = false;
/**
* Zend_Search_Lucene_Index_SegmentInfo constructor needs Segmentname,
* Documents count and Directory as a parameter.
@@ -144,9 +168,15 @@ class Zend_Search_Lucene_Index_SegmentInfo
for ($count = 0; $count < $segFilesCount; $count++) {
$dataOffset = $cfsFile->readLong();
if ($count != 0) {
$this->_segFileSizes[$fileName] = $dataOffset - end($this->_segFiles);
}
$fileName = $cfsFile->readString();
$this->_segFiles[$fileName] = $dataOffset;
}
if ($count != 0) {
$this->_segFileSizes[$fileName] = $this->_directory->fileLength($name . '.cfs') - $dataOffset;
}
}
$fnmFile = $this->openCompoundFile('.fnm');
@@ -197,7 +227,6 @@ class Zend_Search_Lucene_Index_SegmentInfo
}
}
}
}
} catch(Zend_Search_Exception $e) {
if (strpos($e->getMessage(), 'compound file doesn\'t contain') !== false ) {
@@ -212,16 +241,17 @@ class Zend_Search_Lucene_Index_SegmentInfo
* Opens index file stoted within compound index file
*
* @param string $extension
* @param boolean $shareHandler
* @throws Zend_Search_Lucene_Exception
* @return Zend_Search_Lucene_Storage_File
*/
public function openCompoundFile($extension)
public function openCompoundFile($extension, $shareHandler = true)
{
$filename = $this->_name . $extension;
// Try to open common file first
if ($this->_directory->fileExists($filename)) {
return $this->_directory->getFileObject($filename);
return $this->_directory->getFileObject($filename, $shareHandler);
}
if( !isset($this->_segFiles[$filename]) ) {
@@ -229,11 +259,34 @@ class Zend_Search_Lucene_Index_SegmentInfo
. $filename . ' file.' );
}
$file = $this->_directory->getFileObject( $this->_name.".cfs" );
$file = $this->_directory->getFileObject($this->_name . '.cfs', $shareHandler);
$file->seek($this->_segFiles[$filename]);
return $file;
}
/**
* Get compound file length
*
* @param string $extension
* @return integer
*/
public function compoundFileLength($extension)
{
$filename = $this->_name . $extension;
// Try to get common file first
if ($this->_directory->fileExists($filename)) {
return $this->_directory->fileLength($filename);
}
if( !isset($this->_segFileSizes[$filename]) ) {
throw new Zend_Search_Lucene_Exception('Index compound file doesn\'t contain '
. $filename . ' file.' );
}
return $this->_segFileSizes[$filename];
}
/**
* Returns field index or -1 if field is not found
*
@@ -255,7 +308,7 @@ class Zend_Search_Lucene_Index_SegmentInfo
* Returns field info for specified field
*
* @param integer $fieldNum
* @return ZSearchFieldInfo
* @return Zend_Search_Lucene_Index_FieldInfo
*/
public function getField($fieldNum)
{
@@ -281,7 +334,17 @@ class Zend_Search_Lucene_Index_SegmentInfo
}
/**
* Returns the total number of documents in this segment.
* Returns array of FieldInfo objects.
*
* @return array
*/
public function getFieldInfos()
{
return $this->_fields;
}
/**
* Returns the total number of documents in this segment (including deleted documents).
*
* @return integer
*/
@@ -290,6 +353,38 @@ class Zend_Search_Lucene_Index_SegmentInfo
return $this->_docCount;
}
/**
* Returns number of deleted documents.
*
* @return integer
*/
private function _deletedCount()
{
if ($this->_deleted === null) {
return 0;
}
if (extension_loaded('bitset')) {
return count(bitset_to_array($this->_deleted));
} else {
return count($this->_deleted);
}
}
/**
* Returns the total number of non-deleted documents in this segment.
*
* @return integer
*/
public function numDocs()
{
if ($this->hasDeletions()) {
return $this->_docCount - $this->_deletedCount();
} else {
return $this->_docCount;
}
}
/**
* Get field position in a fields dictionary
*
@@ -302,57 +397,6 @@ class Zend_Search_Lucene_Index_SegmentInfo
$this->_fieldsDicPositions[$fieldNum] : $fieldNum;
}
/**
* Loads Term dictionary from TermInfoIndex file
*/
protected function _loadDictionary()
{
if ($this->_termDictionary !== null) {
return;
}
$this->_termDictionary = array();
$this->_termDictionaryInfos = array();
$tiiFile = $this->openCompoundFile('.tii');
$tiVersion = $tiiFile->readInt();
if ($tiVersion != (int)0xFFFFFFFE) {
throw new Zend_Search_Lucene_Exception('Wrong TermInfoIndexFile file format');
}
$indexTermCount = $tiiFile->readLong();
$tiiFile->readInt(); // IndexInterval
$skipInterval = $tiiFile->readInt();
$prevTerm = '';
$freqPointer = 0;
$proxPointer = 0;
$indexPointer = 0;
for ($count = 0; $count < $indexTermCount; $count++) {
$termPrefixLength = $tiiFile->readVInt();
$termSuffix = $tiiFile->readString();
$termValue = substr( $prevTerm, 0, $termPrefixLength ) . $termSuffix;
$termFieldNum = $tiiFile->readVInt();
$docFreq = $tiiFile->readVInt();
$freqPointer += $tiiFile->readVInt();
$proxPointer += $tiiFile->readVInt();
if( $docFreq >= $skipInterval ) {
$skipDelta = $tiiFile->readVInt();
} else {
$skipDelta = 0;
}
$indexPointer += $tiiFile->readVInt();
$this->_termDictionary[] = new Zend_Search_Lucene_Index_Term($termValue,$termFieldNum);
$this->_termDictionaryInfos[] =
new Zend_Search_Lucene_Index_TermInfo($docFreq, $freqPointer, $proxPointer, $skipDelta, $indexPointer);
$prevTerm = $termValue;
}
}
/**
* Return segment name
*
@@ -364,15 +408,75 @@ class Zend_Search_Lucene_Index_SegmentInfo
}
/**
* TermInfo cache
*
* Size is 1024.
* Numbers are used instead of class constants because of performance considerations
*
* @var array
*/
private $_termInfoCache = array();
private function _cleanUpTermInfoCache()
{
// Clean 256 term infos
foreach ($this->_termInfoCache as $key => $termInfo) {
unset($this->_termInfoCache[$key]);
// leave 768 last used term infos
if (count($this->_termInfoCache) == 768) {
break;
}
}
}
/**
* Scans terms dictionary and returns term info
*
* @param Zend_Search_Lucene_Index_Term $term
* @return Zend_Search_Lucene_Index_TermInfo
*/
public function getTermInfo($term)
public function getTermInfo(Zend_Search_Lucene_Index_Term $term)
{
$this->_loadDictionary();
$termKey = $term->key();
if (isset($this->_termInfoCache[$termKey])) {
$termInfo = $this->_termInfoCache[$termKey];
// Move termInfo to the end of cache
unset($this->_termInfoCache[$termKey]);
$this->_termInfoCache[$termKey] = $termInfo;
return $termInfo;
}
if ($this->_termDictionary === null) {
// Check, if index is already serialized
if ($this->_directory->fileExists($this->_name . '.sti')) {
// Prefetch dictionary index data
$stiFile = $this->_directory->getFileObject($this->_name . '.sti');
$stiFileData = $stiFile->readBytes($this->_directory->fileLength($this->_name . '.sti'));
// Load dictionary index data
list($this->_termDictionary, $this->_termDictionaryInfos) = unserialize($stiFileData);
} else {
// Prefetch dictionary index data
$tiiFile = $this->openCompoundFile('.tii');
$tiiFileData = $tiiFile->readBytes($this->compoundFileLength('.tii'));
// Load dictionary index data
list($this->_termDictionary, $this->_termDictionaryInfos) =
Zend_Search_Lucene_Index_DictionaryLoader::load($tiiFileData);
$stiFileData = serialize(array($this->_termDictionary, $this->_termDictionaryInfos));
$stiFile = $this->_directory->createFile($this->_name . '.sti');
$stiFile->writeBytes($stiFileData);
}
}
$searchField = $this->getFieldNum($term->field);
@@ -389,10 +493,10 @@ class Zend_Search_Lucene_Index_SegmentInfo
$mid = ($highIndex + $lowIndex) >> 1;
$midTerm = $this->_termDictionary[$mid];
$fieldNum = $this->_getFieldPosition($midTerm->field);
$fieldNum = $this->_getFieldPosition($midTerm[0] /* field */);
$delta = $searchDicField - $fieldNum;
if ($delta == 0) {
$delta = strcmp($term->text, $midTerm->text);
$delta = strcmp($term->text, $midTerm[1] /* text */);
}
if ($delta < 0) {
@@ -400,7 +504,14 @@ class Zend_Search_Lucene_Index_SegmentInfo
} elseif ($delta > 0) {
$lowIndex = $mid+1;
} else {
return $this->_termDictionaryInfos[$mid]; // We got it!
// return $this->_termDictionaryInfos[$mid]; // We got it!
$a = $this->_termDictionaryInfos[$mid];
$termInfo = new Zend_Search_Lucene_Index_TermInfo($a[0], $a[1], $a[2], $a[3], $a[4]);
// Put loaded termInfo into cache
$this->_termInfoCache[$termKey] = $termInfo;
return $termInfo;
}
}
@@ -411,7 +522,7 @@ class Zend_Search_Lucene_Index_SegmentInfo
$prevPosition = $highIndex;
$prevTerm = $this->_termDictionary[$prevPosition];
$prevTermInfo = $this->_termDictionaryInfos[ $prevPosition ];
$prevTermInfo = $this->_termDictionaryInfos[$prevPosition];
$tisFile = $this->openCompoundFile('.tis');
$tiVersion = $tisFile->readInt();
@@ -423,12 +534,12 @@ class Zend_Search_Lucene_Index_SegmentInfo
$indexInterval = $tisFile->readInt();
$skipInterval = $tisFile->readInt();
$tisFile->seek($prevTermInfo->indexPointer - 20 /* header size*/, SEEK_CUR);
$tisFile->seek($prevTermInfo[4] /* indexPointer */ - 20 /* header size*/, SEEK_CUR);
$termValue = $prevTerm->text;
$termFieldNum = $prevTerm->field;
$freqPointer = $prevTermInfo->freqPointer;
$proxPointer = $prevTermInfo->proxPointer;
$termValue = $prevTerm[1] /* text */;
$termFieldNum = $prevTerm[0] /* field */;
$freqPointer = $prevTermInfo[1] /* freqPointer */;
$proxPointer = $prevTermInfo[2] /* proxPointer */;
for ($count = $prevPosition*$indexInterval + 1;
$count <= $termCount &&
( $this->_getFieldPosition($termFieldNum) < $searchDicField ||
@@ -438,7 +549,7 @@ class Zend_Search_Lucene_Index_SegmentInfo
$termPrefixLength = $tisFile->readVInt();
$termSuffix = $tisFile->readString();
$termFieldNum = $tisFile->readVInt();
$termValue = substr( $termValue, 0, $termPrefixLength ) . $termSuffix;
$termValue = Zend_Search_Lucene_Index_Term::getPrefix($termValue, $termPrefixLength) . $termSuffix;
$docFreq = $tisFile->readVInt();
$freqPointer += $tisFile->readVInt();
@@ -451,10 +562,115 @@ class Zend_Search_Lucene_Index_SegmentInfo
}
if ($termFieldNum == $searchField && $termValue == $term->text) {
return new Zend_Search_Lucene_Index_TermInfo($docFreq, $freqPointer, $proxPointer, $skipOffset);
$termInfo = new Zend_Search_Lucene_Index_TermInfo($docFreq, $freqPointer, $proxPointer, $skipOffset);
} else {
return null;
$termInfo = null;
}
// Put loaded termInfo into cache
$this->_termInfoCache[$termKey] = $termInfo;
if (count($this->_termInfoCache) == 1024) {
$this->_cleanUpTermInfoCache();
}
return $termInfo;
}
/**
* Returns term freqs array.
* Result array structure: array(docId => freq, ...)
*
* @param Zend_Search_Lucene_Index_Term $term
* @param integer $shift
* @return Zend_Search_Lucene_Index_TermInfo
*/
public function termFreqs(Zend_Search_Lucene_Index_Term $term, $shift = 0)
{
$termInfo = $this->getTermInfo($term);
if (!$termInfo instanceof Zend_Search_Lucene_Index_TermInfo) {
return array();
}
$frqFile = $this->openCompoundFile('.frq');
$frqFile->seek($termInfo->freqPointer,SEEK_CUR);
$result = array();
$docId = 0;
for ($count = 0; $count < $termInfo->docFreq; $count++) {
$docDelta = $frqFile->readVInt();
if ($docDelta % 2 == 1) {
$docId += ($docDelta-1)/2;
$result[$shift + $docId] = 1;
} else {
$docId += $docDelta/2;
$result[$shift + $docId] = $frqFile->readVInt();
}
}
return $result;
}
/**
* Returns term positions array.
* Result array structure: array(docId => array(pos1, pos2, ...), ...)
*
* @param Zend_Search_Lucene_Index_Term $term
* @param integer $shift
* @return Zend_Search_Lucene_Index_TermInfo
*/
public function termPositions(Zend_Search_Lucene_Index_Term $term, $shift = 0)
{
$termInfo = $this->getTermInfo($term);
if (!$termInfo instanceof Zend_Search_Lucene_Index_TermInfo) {
return array();
}
$frqFile = $this->openCompoundFile('.frq');
$frqFile->seek($termInfo->freqPointer,SEEK_CUR);
$freqs = array();
$docId = 0;
for ($count = 0; $count < $termInfo->docFreq; $count++) {
$docDelta = $frqFile->readVInt();
if ($docDelta % 2 == 1) {
$docId += ($docDelta-1)/2;
$freqs[$docId] = 1;
} else {
$docId += $docDelta/2;
$freqs[$docId] = $frqFile->readVInt();
}
}
$result = array();
$prxFile = $this->openCompoundFile('.prx');
$prxFile->seek($termInfo->proxPointer, SEEK_CUR);
foreach ($freqs as $docId => $freq) {
$termPosition = 0;
$positions = array();
for ($count = 0; $count < $freq; $count++ ) {
$termPosition += $prxFile->readVInt();
$positions[] = $termPosition;
}
$result[$shift + $docId] = $positions;
}
return $result;
}
/**
* Load normalizatin factors from an index file
*
* @param integer $fieldNum
*/
private function _loadNorm($fieldNum)
{
$fFile = $this->openCompoundFile('.f' . $fieldNum);
$this->_norms[$fieldNum] = $fFile->readBytes($this->_docCount);
}
/**
@@ -462,7 +678,7 @@ class Zend_Search_Lucene_Index_SegmentInfo
*
* @param integer $id
* @param string $fieldName
* @return string
* @return float
*/
public function norm($id, $fieldName)
{
@@ -472,14 +688,37 @@ class Zend_Search_Lucene_Index_SegmentInfo
return null;
}
if ( !isset( $this->_norms[$fieldNum] )) {
$fFile = $this->openCompoundFile('.f' . $fieldNum);
$this->_norms[$fieldNum] = $fFile->readBytes($this->_docCount);
if (!isset($this->_norms[$fieldNum])) {
$this->_loadNorm($fieldNum);
}
return Zend_Search_Lucene_Search_Similarity::decodeNorm( ord($this->_norms[$fieldNum]{$id}) );
}
/**
* Returns norm vector, encoded in a byte string
*
* @param string $fieldName
* @return string
*/
public function normVector($fieldName)
{
$fieldNum = $this->getFieldNum($fieldName);
if ($fieldNum == -1 || !($this->_fields[$fieldNum]->isIndexed)) {
$similarity = Zend_Search_Lucene_Search_Similarity::getDefault();
return str_repeat(chr($similarity->encodeNorm( $similarity->lengthNorm($fieldName, 0) )),
$this->_docCount);
}
if (!isset($this->_norms[$fieldNum])) {
$this->_loadNorm($fieldNum);
}
return $this->_norms[$fieldNum];
}
/**
* Returns true if any documents have been deleted from this index segment.
@@ -571,5 +810,247 @@ class Zend_Search_Lucene_Index_SegmentInfo
$this->_deletedDirty = false;
}
/**
* Term Dictionary File object for stream like terms reading
*
* @var Zend_Search_Lucene_Storage_File
*/
private $_tisFile = null;
/**
* Frequencies File object for stream like terms reading
*
* @var Zend_Search_Lucene_Storage_File
*/
private $_frqFile = null;
/**
* Offset of the .frq file in the compound file
*
* @var integer
*/
private $_frqFileOffset;
/**
* Positions File object for stream like terms reading
*
* @var Zend_Search_Lucene_Storage_File
*/
private $_prxFile = null;
/**
* Offset of the .prx file in the compound file
*
* @var integer
*/
private $_prxFileOffset;
/**
* Number of terms in term stream
*
* @var integer
*/
private $_termCount = 0;
/**
* Segment skip interval
*
* @var integer
*/
private $_skipInterval;
/**
* Last TermInfo in a terms stream
*
* @var Zend_Search_Lucene_Index_TermInfo
*/
private $_lastTermInfo = null;
/**
* Last Term in a terms stream
*
* @var Zend_Search_Lucene_Index_Term
*/
private $_lastTerm = null;
/**
* Map of the document IDs
* Used to get new docID after removing deleted documents.
* It's not very effective from memory usage point of view,
* but much more faster, then other methods
*
* @var array|null
*/
private $_docMap = null;
/**
* An array of all term positions in the documents.
* Array structure: array( docId => array( pos1, pos2, ...), ...)
*
* @var array
*/
private $_lastTermPositions;
/**
* Reset terms stream
*
* $startId - id for the fist document
* $compact - remove deleted documents
*
* Returns start document id for the next segment
*
* @param integer $startId
* @param boolean $compact
* @throws Zend_Search_Lucene_Exception
* @return integer
*/
public function reset($startId = 0, $compact = false)
{
if ($this->_tisFile !== null) {
$this->_tisFile = null;
}
$this->_tisFile = $this->openCompoundFile('.tis', false);
$tiVersion = $this->_tisFile->readInt();
if ($tiVersion != (int)0xFFFFFFFE) {
throw new Zend_Search_Lucene_Exception('Wrong TermInfoFile file format');
}
$this->_termCount = $this->_tisFile->readLong();
$this->_tisFile->readInt(); // Read Index interval
$this->_skipInterval = $this->_tisFile->readInt(); // Read skip interval
if ($this->_frqFile !== null) {
$this->_frqFile = null;
}
$this->_frqFile = $this->openCompoundFile('.frq', false);
$this->_frqFileOffset = $this->_frqFile->tell();
if ($this->_prxFile !== null) {
$this->_prxFile = null;
}
$this->_prxFile = $this->openCompoundFile('.prx', false);
$this->_prxFileOffset = $this->_prxFile->tell();
$this->_lastTerm = new Zend_Search_Lucene_Index_Term('', -1);
$this->_lastTermInfo = new Zend_Search_Lucene_Index_TermInfo(0, 0, 0, 0);
$this->_docMap = array();
for ($count = 0; $count < $this->_docCount; $count++) {
if (!$this->isDeleted($count)) {
$this->_docMap[$count] = $startId + ($compact ? count($this->_docMap) : $count);
}
}
$this->nextTerm();
return $startId + ($compact ? count($this->_docMap) : $this->_docCount);
}
/**
* Scans terms dictionary and returns next term
*
* @return Zend_Search_Lucene_Index_Term|null
*/
public function nextTerm()
{
if ($this->_tisFile === null || $this->_termCount == 0) {
$this->_lastTerm = null;
$this->_lastTermInfo = null;
// may be necessary for "empty" segment
$this->_tisFile = null;
$this->_frqFile = null;
$this->_prxFile = null;
return null;
}
$termPrefixLength = $this->_tisFile->readVInt();
$termSuffix = $this->_tisFile->readString();
$termFieldNum = $this->_tisFile->readVInt();
$termValue = Zend_Search_Lucene_Index_Term::getPrefix($this->_lastTerm->text, $termPrefixLength) . $termSuffix;
$this->_lastTerm = new Zend_Search_Lucene_Index_Term($termValue, $this->_fields[$termFieldNum]->name);
$docFreq = $this->_tisFile->readVInt();
$freqPointer = $this->_lastTermInfo->freqPointer + $this->_tisFile->readVInt();
$proxPointer = $this->_lastTermInfo->proxPointer + $this->_tisFile->readVInt();
if ($docFreq >= $this->_skipInterval) {
$skipOffset = $this->_tisFile->readVInt();
} else {
$skipOffset = 0;
}
$this->_lastTermInfo = new Zend_Search_Lucene_Index_TermInfo($docFreq, $freqPointer, $proxPointer, $skipOffset);
$this->_lastTermPositions = array();
$this->_frqFile->seek($this->_lastTermInfo->freqPointer + $this->_frqFileOffset, SEEK_SET);
$freqs = array(); $docId = 0;
for( $count = 0; $count < $this->_lastTermInfo->docFreq; $count++ ) {
$docDelta = $this->_frqFile->readVInt();
if( $docDelta % 2 == 1 ) {
$docId += ($docDelta-1)/2;
$freqs[ $docId ] = 1;
} else {
$docId += $docDelta/2;
$freqs[ $docId ] = $this->_frqFile->readVInt();
}
}
$this->_prxFile->seek($this->_lastTermInfo->proxPointer + $this->_prxFileOffset, SEEK_SET);
foreach ($freqs as $docId => $freq) {
$termPosition = 0; $positions = array();
for ($count = 0; $count < $freq; $count++ ) {
$termPosition += $this->_prxFile->readVInt();
$positions[] = $termPosition;
}
if (isset($this->_docMap[$docId])) {
$this->_lastTermPositions[$this->_docMap[$docId]] = $positions;
}
}
$this->_termCount--;
if ($this->_termCount == 0) {
$this->_tisFile = null;
$this->_frqFile = null;
$this->_prxFile = null;
}
return $this->_lastTerm;
}
/**
* Returns term in current position
*
* @param Zend_Search_Lucene_Index_Term $term
* @return Zend_Search_Lucene_Index_Term|null
*/
public function currentTerm()
{
return $this->_lastTerm;
}
/**
* Returns an array of all term positions in the documents.
* Return array structure: array( docId => array( pos1, pos2, ...), ...)
*
* @return array
*/
public function currentTermPositions()
{
return $this->_lastTermPositions;
}
}
@@ -0,0 +1,53 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Exception */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/** Zend_Search_Lucene */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/PriorityQueue.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Index_SegmentInfoPriorityQueue extends Zend_Search_Lucene_PriorityQueue
{
/**
* Compare elements
*
* Returns true, if $el1 is less than $el2; else otherwise
*
* @param mixed $segmentInfo1
* @param mixed $segmentInfo2
* @return boolean
*/
protected function _less($segmentInfo1, $segmentInfo2)
{
return strcmp($segmentInfo1->currentTerm()->key(), $segmentInfo2->currentTerm()->key()) < 0;
}
}
@@ -0,0 +1,273 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Exception */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/** Zend_Search_Lucene_Index_SegmentInfo */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/SegmentInfo.php';
/** Zend_Search_Lucene_Index_SegmentWriter_StreamWriter */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/SegmentWriter/StreamWriter.php';
/** Zend_Search_Lucene_Index_SegmentInfoPriorityQueue */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/SegmentInfoPriorityQueue.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Index_SegmentMerger
{
/**
* Target segment writer
*
* @var Zend_Search_Lucene_Index_SegmentWriter_StreamWriter
*/
private $_writer;
/**
* Number of docs in a new segment
*
* @var integer
*/
private $_docCount;
/**
* A set of segments to be merged
*
* @var array Zend_Search_Lucene_Index_SegmentInfo
*/
private $_segmentInfos = array();
/**
* Flag to signal, that merge is already done
*
* @var boolean
*/
private $_mergeDone = false;
/**
* Field map
* [<segment_name>][<field_number>] => <target_field_number>
*
* @var array
*/
private $_fieldsMap = array();
/**
* Object constructor.
*
* Creates new segment merger with $directory as target to merge segments into
* and $name as a name of new segment
*
* @param Zend_Search_Lucene_Storage_Directory $directory
* @param string $name
*/
public function __construct($directory, $name)
{
$this->_writer = new Zend_Search_Lucene_Index_SegmentWriter_StreamWriter($directory, $name);
}
/**
* Add segmnet to a collection of segments to be merged
*
* @param Zend_Search_Lucene_Index_SegmentInfo $segment
*/
public function addSource(Zend_Search_Lucene_Index_SegmentInfo $segmentInfo)
{
$this->_segmentInfos[$segmentInfo->getName()] = $segmentInfo;
}
/**
* Do merge.
*
* Returns number of documents in newly created segment
*
* @return Zend_Search_Lucene_Index_SegmentInfo
* @throws Zend_Search_Lucene_Exception
*/
public function merge()
{
if ($this->_mergeDone) {
throw new Zend_Search_Lucene_Exception('Merge is already done.');
}
if (count($this->_segmentInfos) < 1) {
throw new Zend_Search_Lucene_Exception('Wrong number of segments to be merged ('
. count($this->_segmentInfos)
. ').');
}
$this->_mergeFields();
$this->_mergeNorms();
$this->_mergeStoredFields();
$this->_mergeTerms();
$this->_mergeDone = true;
return $this->_writer->close();
}
/**
* Merge fields information
*/
private function _mergeFields()
{
foreach ($this->_segmentInfos as $segName => $segmentInfo) {
foreach ($segmentInfo->getFieldInfos() as $fieldInfo) {
$this->_fieldsMap[$segName][$fieldInfo->number] = $this->_writer->addFieldInfo($fieldInfo);
}
}
}
/**
* Merge field's normalization factors
*/
private function _mergeNorms()
{
foreach ($this->_writer->getFieldInfos() as $fieldInfo) {
if ($fieldInfo->isIndexed) {
foreach ($this->_segmentInfos as $segName => $segmentInfo) {
if ($segmentInfo->hasDeletions()) {
$srcNorm = $segmentInfo->normVector($fieldInfo->name);
$norm = '';
$docs = $segmentInfo->count();
for ($count = 0; $count < $docs; $count++) {
if (!$segmentInfo->isDeleted($count)) {
$norm .= $srcNorm[$count];
}
}
$this->_writer->addNorm($fieldInfo->name, $norm);
} else {
$this->_writer->addNorm($fieldInfo->name, $segmentInfo->normVector($fieldInfo->name));
}
}
}
}
}
/**
* Merge fields information
*/
private function _mergeStoredFields()
{
$this->_docCount = 0;
foreach ($this->_segmentInfos as $segName => $segmentInfo) {
$fdtFile = $segmentInfo->openCompoundFile('.fdt');
for ($count = 0; $count < $segmentInfo->count(); $count++) {
$fieldCount = $fdtFile->readVInt();
$storedFields = array();
for ($count2 = 0; $count2 < $fieldCount; $count2++) {
$fieldNum = $fdtFile->readVInt();
$bits = $fdtFile->readByte();
$fieldInfo = $segmentInfo->getField($fieldNum);
if (!($bits & 2)) { // Text data
$storedFields[] =
new Zend_Search_Lucene_Field($fieldInfo->name,
$fdtFile->readString(),
'UTF-8',
true,
$fieldInfo->isIndexed,
$bits & 1 );
} else { // Binary data
$storedFields[] =
new Zend_Search_Lucene_Field($fieldInfo->name,
$fdtFile->readBinary(),
'',
true,
$fieldInfo->isIndexed,
$bits & 1,
true);
}
}
if (!$segmentInfo->isDeleted($count)) {
$this->_docCount++;
$this->_writer->addStoredFields($storedFields);
}
}
}
}
/**
* Merge fields information
*/
private function _mergeTerms()
{
$segmentInfoQueue = new Zend_Search_Lucene_Index_SegmentInfoPriorityQueue();
$segmentStartId = 0;
foreach ($this->_segmentInfos as $segName => $segmentInfo) {
$segmentStartId = $segmentInfo->reset($segmentStartId, true);
// Skip "empty" segments
if ($segmentInfo->currentTerm() !== null) {
$segmentInfoQueue->put($segmentInfo);
}
}
$this->_writer->initializeDictionaryFiles();
$termDocs = array();
while (($segmentInfo = $segmentInfoQueue->pop()) !== null) {
// Merge positions array
$termDocs += $segmentInfo->currentTermPositions();
if ($segmentInfoQueue->top() === null ||
$segmentInfoQueue->top()->currentTerm()->key() !=
$segmentInfo->currentTerm()->key()) {
// We got new term
ksort($termDocs, SORT_NUMERIC);
// Add term if it's contained in any document
if (count($termDocs) > 0) {
$this->_writer->addTerm($segmentInfo->currentTerm(), $termDocs);
}
$termDocs = array();
}
$segmentInfo->nextTerm();
// check, if segment dictionary is finished
if ($segmentInfo->currentTerm() !== null) {
// Put segment back into the priority queue
$segmentInfoQueue->put($segmentInfo);
}
}
$this->_writer->closeDictionaryFiles();
}
}
+332 -257
View File
@@ -15,29 +15,26 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Exception */
require_once 'Zend/Search/Lucene/Exception.php';
/** Zend_Search_Lucene_Analysis_Analyzer */
require_once 'Zend/Search/Lucene/Analysis/Analyzer.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/** Zend_Search_Lucene_Index_SegmentInfo */
require_once 'Zend/Search/Lucene/Index/SegmentInfo.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/SegmentInfo.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Index_SegmentWriter
abstract class Zend_Search_Lucene_Index_SegmentWriter
{
/**
* Expert: The fraction of terms in the "dictionary" which should be stored
@@ -48,7 +45,7 @@ class Zend_Search_Lucene_Index_SegmentWriter
*
* @var integer
*/
static public $indexInterval = 128;
public static $indexInterval = 128;
/** Expert: The fraction of TermDocs entries stored in skip tables.
* Larger values result in smaller indexes, greater acceleration, but fewer
@@ -61,28 +58,28 @@ class Zend_Search_Lucene_Index_SegmentWriter
*
* @var integer
*/
static public $skipInterval = 0x7FFFFFFF;
public static $skipInterval = 0x7FFFFFFF;
/**
* Number of docs in a segment
*
* @var integer
*/
private $_docCount;
protected $_docCount = 0;
/**
* Segment name
*
* @var string
*/
private $_name;
protected $_name;
/**
* File system adapter.
*
* @var Zend_Search_Lucene_Storage_Directory
*/
private $_directory;
protected $_directory;
/**
* List of the index files.
@@ -90,52 +87,41 @@ class Zend_Search_Lucene_Index_SegmentWriter
*
* @var unknown_type
*/
private $_files;
/**
* Term Dictionary
* Array of the Zend_Search_Lucene_Index_Term objects
* Corresponding Zend_Search_Lucene_Index_TermInfo object stored in the $_termDictionaryInfos
*
* @var array
*/
private $_termDictionary;
/**
* Documents, which contain the term
*
* @var array
*/
private $_termDocs;
protected $_files = array();
/**
* Segment fields. Array of Zend_Search_Lucene_Index_FieldInfo objects for this segment
*
* @var array
*/
private $_fields;
protected $_fields = array();
/**
* Sizes of the indexed fields.
* Used for normalization factors calculation.
* Normalization factors.
* An array fieldName => normVector
* normVector is a binary string.
* Each byte corresponds to an indexed document in a segment and
* encodes normalization factor (float value, encoded by
* Zend_Search_Lucene_Search_Similarity::encodeNorm())
*
* @var array
*/
private $_fieldLengths;
protected $_norms = array();
/**
* '.fdx' file - Stored Fields, the field index.
*
* @var Zend_Search_Lucene_Storage_File
*/
private $_fdxFile;
protected $_fdxFile = null;
/**
* '.fdt' file - Stored Fields, the field data.
*
* @var Zend_Search_Lucene_Storage_File
*/
private $_fdtFile;
protected $_fdtFile = null;
/**
@@ -144,132 +130,125 @@ class Zend_Search_Lucene_Index_SegmentWriter
* @param Zend_Search_Lucene_Storage_Directory $directory
* @param string $name
*/
public function __construct($directory, $name)
public function __construct(Zend_Search_Lucene_Storage_Directory $directory, $name)
{
$this->_directory = $directory;
$this->_name = $name;
$this->_docCount = 0;
$this->_fields = array();
$this->_termDocs = array();
$this->_files = array();
$this->_norms = array();
$this->_fieldLengths = array();
$this->_termDictionary = array();
$this->_fdxFile = null;
$this->_fdtFile = null;
}
/**
* Add field to the segment
*
* Returns actual field number
*
* @param Zend_Search_Lucene_Field $field
* @return integer
*/
private function _addFieldInfo(Zend_Search_Lucene_Field $field)
public function addField(Zend_Search_Lucene_Field $field)
{
if (!isset($this->_fields[$field->name])) {
$fieldNumber = count($this->_fields);
$this->_fields[$field->name] =
new Zend_Search_Lucene_Index_FieldInfo($field->name,
$field->isIndexed,
count($this->_fields),
$fieldNumber,
$field->storeTermVector);
return $fieldNumber;
} else {
$this->_fields[$field->name]->isIndexed |= $field->isIndexed;
$this->_fields[$field->name]->storeTermVector |= $field->storeTermVector;
return $this->_fields[$field->name]->number;
}
}
/**
* Add fieldInfo to the segment
*
* Returns actual field number
*
* @param Zend_Search_Lucene_Index_FieldInfo $fieldInfo
* @return integer
*/
public function addFieldInfo(Zend_Search_Lucene_Index_FieldInfo $fieldInfo)
{
if (!isset($this->_fields[$fieldInfo->name])) {
$fieldNumber = count($this->_fields);
$this->_fields[$fieldInfo->name] =
new Zend_Search_Lucene_Index_FieldInfo($fieldInfo->name,
$fieldInfo->isIndexed,
$fieldNumber,
$fieldInfo->storeTermVector);
return $fieldNumber;
} else {
$this->_fields[$fieldInfo->name]->isIndexed |= $fieldInfo->isIndexed;
$this->_fields[$fieldInfo->name]->storeTermVector |= $fieldInfo->storeTermVector;
return $this->_fields[$fieldInfo->name]->number;
}
}
/**
* Adds a document to this segment.
* Returns array of FieldInfo objects.
*
* @param Zend_Search_Lucene_Document $document
* @throws Zend_Search_Lucene_Exception
* @return array
*/
public function addDocument(Zend_Search_Lucene_Document $document)
public function getFieldInfos()
{
$storedFields = array();
return $this->_fields;
}
foreach ($document->getFieldNames() as $fieldName) {
$field = $document->getField($fieldName);
$this->_addFieldInfo($field);
/**
* Add stored fields information
*
* @param array $storedFields array of Zend_Search_Lucene_Field objects
*/
public function addStoredFields($storedFields)
{
if (!isset($this->_fdxFile)) {
$this->_fdxFile = $this->_directory->createFile($this->_name . '.fdx');
$this->_fdtFile = $this->_directory->createFile($this->_name . '.fdt');
if ($field->storeTermVector) {
/**
* @todo term vector storing support
*/
throw new Zend_Search_Lucene_Exception('Store term vector functionality is not supported yet.');
}
if ($field->isIndexed) {
if ($field->isTokenized) {
$tokenList = Zend_Search_Lucene_Analysis_Analyzer::getDefault()->tokenize($field->stringValue);
} else {
$tokenList = array();
$tokenList[] = new Zend_Search_Lucene_Analysis_Token($field->stringValue, 0, strlen($field->stringValue));
}
$this->_fieldLengths[$field->name][$this->_docCount] = count($tokenList);
$position = 0;
foreach ($tokenList as $token) {
$term = new Zend_Search_Lucene_Index_Term($token->getTermText(), $field->name);
$termKey = $term->key();
if (!isset($this->_termDictionary[$termKey])) {
// New term
$this->_termDictionary[$termKey] = $term;
$this->_termDocs[$termKey] = array();
$this->_termDocs[$termKey][$this->_docCount] = array();
} else if (!isset($this->_termDocs[$termKey][$this->_docCount])) {
// Existing term, but new term entry
$this->_termDocs[$termKey][$this->_docCount] = array();
}
$position += $token->getPositionIncrement();
$this->_termDocs[$termKey][$this->_docCount][] = $position;
}
}
if ($field->isStored) {
$storedFields[] = $field;
}
$this->_files[] = $this->_name . '.fdx';
$this->_files[] = $this->_name . '.fdt';
}
if (count($storedFields) != 0) {
if (!isset($this->_fdxFile)) {
$this->_fdxFile = $this->_directory->createFile($this->_name . '.fdx');
$this->_fdtFile = $this->_directory->createFile($this->_name . '.fdt');
$this->_files[] = $this->_name . '.fdx';
$this->_files[] = $this->_name . '.fdt';
}
$this->_fdxFile->writeLong($this->_fdtFile->tell());
$this->_fdtFile->writeVInt(count($storedFields));
foreach ($storedFields as $field) {
$this->_fdtFile->writeVInt($this->_fields[$field->name]->number);
$fieldBits = ($field->isTokenized ? 0x01 : 0x00) |
($field->isBinary ? 0x02 : 0x00) |
0x00; /* 0x04 - third bit, compressed (ZLIB) */
$this->_fdtFile->writeByte($fieldBits);
if ($field->isBinary) {
$this->_fdtFile->writeVInt(strlen($field->stringValue));
$this->_fdtFile->writeBytes($field->stringValue);
} else {
$this->_fdtFile->writeString($field->stringValue);
}
$this->_fdxFile->writeLong($this->_fdtFile->tell());
$this->_fdtFile->writeVInt(count($storedFields));
foreach ($storedFields as $field) {
$this->_fdtFile->writeVInt($this->_fields[$field->name]->number);
$fieldBits = ($field->isTokenized ? 0x01 : 0x00) |
($field->isBinary ? 0x02 : 0x00) |
0x00; /* 0x04 - third bit, compressed (ZLIB) */
$this->_fdtFile->writeByte($fieldBits);
if ($field->isBinary) {
$this->_fdtFile->writeVInt(strlen($field->value));
$this->_fdtFile->writeBytes($field->value);
} else {
$this->_fdtFile->writeString($field->getUtf8Value());
}
}
$this->_docCount++;
}
/**
* Returns the total number of documents in this segment.
*
* @return integer
*/
public function count()
{
return $this->_docCount;
}
/**
* Dump Field Info (.fnm) segment file
*/
private function _dumpFNM()
protected function _dumpFNM()
{
$fnmFile = $this->_directory->createFile($this->_name . '.fnm');
$fnmFile->writeVInt(count($this->_fields));
@@ -283,20 +262,9 @@ class Zend_Search_Lucene_Index_SegmentWriter
);
if ($field->isIndexed) {
$fieldNum = $this->_fields[$field->name]->number;
$fieldName = $field->name;
$similarity = Zend_Search_Lucene_Search_Similarity::getDefault();
$norm = '';
for ($count = 0; $count < $this->_docCount; $count++) {
$numTokens = isset($this->_fieldLengths[$fieldName][$count]) ?
$this->_fieldLengths[$fieldName][$count] : 0;
$norm .= chr($similarity->encodeNorm($similarity->lengthNorm($fieldName, $numTokens)));
}
$normFileName = $this->_name . '.f' . $fieldNum;
$normFileName = $this->_name . '.f' . $field->number;
$fFile = $this->_directory->createFile($normFileName);
$fFile->writeBytes($norm);
$fFile->writeBytes($this->_norms[$field->name]);
$this->_files[] = $normFileName;
}
}
@@ -305,6 +273,194 @@ class Zend_Search_Lucene_Index_SegmentWriter
}
/**
* Term Dictionary file
*
* @var Zend_Search_Lucene_Storage_File
*/
private $_tisFile = null;
/**
* Term Dictionary index file
*
* @var Zend_Search_Lucene_Storage_File
*/
private $_tiiFile = null;
/**
* Frequencies file
*
* @var Zend_Search_Lucene_Storage_File
*/
private $_frqFile = null;
/**
* Positions file
*
* @var Zend_Search_Lucene_Storage_File
*/
private $_prxFile = null;
/**
* Number of written terms
*
* @var integer
*/
private $_termCount;
/**
* Last saved term
*
* @var Zend_Search_Lucene_Index_Term
*/
private $_prevTerm;
/**
* Last saved term info
*
* @var Zend_Search_Lucene_Index_TermInfo
*/
private $_prevTermInfo;
/**
* Last saved index term
*
* @var Zend_Search_Lucene_Index_Term
*/
private $_prevIndexTerm;
/**
* Last saved index term info
*
* @var Zend_Search_Lucene_Index_TermInfo
*/
private $_prevIndexTermInfo;
/**
* Last term dictionary file position
*
* @var integer
*/
private $_lastIndexPosition;
/**
* Create dicrionary, frequency and positions files and write necessary headers
*/
public function initializeDictionaryFiles()
{
$this->_tisFile = $this->_directory->createFile($this->_name . '.tis');
$this->_tisFile->writeInt((int)0xFFFFFFFE);
$this->_tisFile->writeLong(0 /* dummy data for terms count */);
$this->_tisFile->writeInt(self::$indexInterval);
$this->_tisFile->writeInt(self::$skipInterval);
$this->_tiiFile = $this->_directory->createFile($this->_name . '.tii');
$this->_tiiFile->writeInt((int)0xFFFFFFFE);
$this->_tiiFile->writeLong(0 /* dummy data for terms count */);
$this->_tiiFile->writeInt(self::$indexInterval);
$this->_tiiFile->writeInt(self::$skipInterval);
/** Dump dictionary header */
$this->_tiiFile->writeVInt(0); // preffix length
$this->_tiiFile->writeString(''); // suffix
$this->_tiiFile->writeInt((int)0xFFFFFFFF); // field number
$this->_tiiFile->writeByte((int)0x0F);
$this->_tiiFile->writeVInt(0); // DocFreq
$this->_tiiFile->writeVInt(0); // FreqDelta
$this->_tiiFile->writeVInt(0); // ProxDelta
$this->_tiiFile->writeVInt(20); // IndexDelta
$this->_frqFile = $this->_directory->createFile($this->_name . '.frq');
$this->_prxFile = $this->_directory->createFile($this->_name . '.prx');
$this->_files[] = $this->_name . '.tis';
$this->_files[] = $this->_name . '.tii';
$this->_files[] = $this->_name . '.frq';
$this->_files[] = $this->_name . '.prx';
$this->_prevTerm = null;
$this->_prevTermInfo = null;
$this->_prevIndexTerm = null;
$this->_prevIndexTermInfo = null;
$this->_lastIndexPosition = 20;
$this->_termCount = 0;
}
/**
* Add term
*
* Term positions is an array( docId => array(pos1, pos2, pos3, ...), ... )
*
* @param Zend_Search_Lucene_Index_Term $termEntry
* @param array $termDocs
*/
public function addTerm($termEntry, $termDocs)
{
$freqPointer = $this->_frqFile->tell();
$proxPointer = $this->_prxFile->tell();
$prevDoc = 0;
foreach ($termDocs as $docId => $termPositions) {
$docDelta = ($docId - $prevDoc)*2;
$prevDoc = $docId;
if (count($termPositions) > 1) {
$this->_frqFile->writeVInt($docDelta);
$this->_frqFile->writeVInt(count($termPositions));
} else {
$this->_frqFile->writeVInt($docDelta + 1);
}
$prevPosition = 0;
foreach ($termPositions as $position) {
$this->_prxFile->writeVInt($position - $prevPosition);
$prevPosition = $position;
}
}
if (count($termDocs) >= self::$skipInterval) {
/**
* @todo Write Skip Data to a freq file.
* It's not used now, but make index more optimal
*/
$skipOffset = $this->_frqFile->tell() - $freqPointer;
} else {
$skipOffset = 0;
}
$term = new Zend_Search_Lucene_Index_Term($termEntry->text,
$this->_fields[$termEntry->field]->number);
$termInfo = new Zend_Search_Lucene_Index_TermInfo(count($termDocs),
$freqPointer, $proxPointer, $skipOffset);
$this->_dumpTermDictEntry($this->_tisFile, $this->_prevTerm, $term, $this->_prevTermInfo, $termInfo);
if (($this->_termCount + 1) % self::$indexInterval == 0) {
$this->_dumpTermDictEntry($this->_tiiFile, $this->_prevIndexTerm, $term, $this->_prevIndexTermInfo, $termInfo);
$indexPosition = $this->_tisFile->tell();
$this->_tiiFile->writeVInt($indexPosition - $this->_lastIndexPosition);
$this->_lastIndexPosition = $indexPosition;
}
$this->_termCount++;
}
/**
* Close dictionary
*/
public function closeDictionaryFiles()
{
$this->_tisFile->seek(4);
$this->_tisFile->writeLong($this->_termCount);
$this->_tiiFile->seek(4);
$this->_tiiFile->writeLong(ceil(($this->_termCount + 2)/self::$indexInterval));
}
/**
* Dump Term Dictionary segment file entry.
* Used to write entry to .tis or .tii files
@@ -315,22 +471,47 @@ class Zend_Search_Lucene_Index_SegmentWriter
* @param Zend_Search_Lucene_Index_TermInfo $prevTermInfo
* @param Zend_Search_Lucene_Index_TermInfo $termInfo
*/
private function _dumpTermDictEntry(Zend_Search_Lucene_Storage_File $dicFile,
protected function _dumpTermDictEntry(Zend_Search_Lucene_Storage_File $dicFile,
&$prevTerm, Zend_Search_Lucene_Index_Term $term,
&$prevTermInfo, Zend_Search_Lucene_Index_TermInfo $termInfo)
{
if (isset($prevTerm) && $prevTerm->field == $term->field) {
$prefixLength = 0;
while ($prefixLength < strlen($prevTerm->text) &&
$prefixLength < strlen($term->text) &&
$prevTerm->text{$prefixLength} == $term->text{$prefixLength}
) {
$prefixLength++;
$matchedBytes = 0;
$maxBytes = min(strlen($prevTerm->text), strlen($term->text));
while ($matchedBytes < $maxBytes &&
$prevTerm->text[$matchedBytes] == $term->text[$matchedBytes]) {
$matchedBytes++;
}
// Calculate actual matched UTF-8 pattern
$prefixBytes = 0;
$prefixChars = 0;
while ($prefixBytes < $matchedBytes) {
$charBytes = 1;
if ((ord($term->text[$prefixBytes]) & 0xC0) == 0xC0) {
$charBytes++;
if (ord($term->text[$prefixBytes]) & 0x20 ) {
$charBytes++;
if (ord($term->text[$prefixBytes]) & 0x10 ) {
$charBytes++;
}
}
}
if ($prefixBytes + $charBytes > $matchedBytes) {
// char crosses matched bytes boundary
// skip char
break;
}
$prefixChars++;
$prefixBytes += $charBytes;
}
// Write preffix length
$dicFile->writeVInt($prefixLength);
$dicFile->writeVInt($prefixChars);
// Write suffix
$dicFile->writeString( substr($term->text, $prefixLength) );
$dicFile->writeString(substr($term->text, $prefixBytes));
} else {
// Write preffix length
$dicFile->writeVInt(0);
@@ -363,107 +544,11 @@ class Zend_Search_Lucene_Index_SegmentWriter
$prevTermInfo = $termInfo;
}
/**
* Dump Term Dictionary (.tis) and Term Dictionary Index (.tii) segment files
*/
private function _dumpDictionary()
{
$termKeys = array_keys($this->_termDictionary);
sort($termKeys, SORT_STRING);
$tisFile = $this->_directory->createFile($this->_name . '.tis');
$tisFile->writeInt((int)0xFFFFFFFE);
$tisFile->writeLong(count($termKeys));
$tisFile->writeInt(self::$indexInterval);
$tisFile->writeInt(self::$skipInterval);
$tiiFile = $this->_directory->createFile($this->_name . '.tii');
$tiiFile->writeInt((int)0xFFFFFFFE);
$tiiFile->writeLong(ceil((count($termKeys) + 2)/self::$indexInterval));
$tiiFile->writeInt(self::$indexInterval);
$tiiFile->writeInt(self::$skipInterval);
/** Dump dictionary header */
$tiiFile->writeVInt(0); // preffix length
$tiiFile->writeString(''); // suffix
$tiiFile->writeInt((int)0xFFFFFFFF); // field number
$tiiFile->writeByte((int)0x0F);
$tiiFile->writeVInt(0); // DocFreq
$tiiFile->writeVInt(0); // FreqDelta
$tiiFile->writeVInt(0); // ProxDelta
$tiiFile->writeVInt(20); // IndexDelta
$frqFile = $this->_directory->createFile($this->_name . '.frq');
$prxFile = $this->_directory->createFile($this->_name . '.prx');
$termCount = 1;
$prevTerm = null;
$prevTermInfo = null;
$prevIndexTerm = null;
$prevIndexTermInfo = null;
$prevIndexPosition = 20;
foreach ($termKeys as $termId) {
$freqPointer = $frqFile->tell();
$proxPointer = $prxFile->tell();
$prevDoc = 0;
foreach ($this->_termDocs[$termId] as $docId => $termPositions) {
$docDelta = ($docId - $prevDoc)*2;
$prevDoc = $docId;
if (count($termPositions) > 1) {
$frqFile->writeVInt($docDelta);
$frqFile->writeVInt(count($termPositions));
} else {
$frqFile->writeVInt($docDelta + 1);
}
$prevPosition = 0;
foreach ($termPositions as $position) {
$prxFile->writeVInt($position - $prevPosition);
$prevPosition = $position;
}
}
if (count($this->_termDocs[$termId]) >= self::$skipInterval) {
/**
* @todo Write Skip Data to a freq file.
* It's not used now, but make index more optimal
*/
$skipOffset = $frqFile->tell() - $freqPointer;
} else {
$skipOffset = 0;
}
$term = new Zend_Search_Lucene_Index_Term($this->_termDictionary[$termId]->text,
$this->_fields[$this->_termDictionary[$termId]->field]->number);
$termInfo = new Zend_Search_Lucene_Index_TermInfo(count($this->_termDocs[$termId]),
$freqPointer, $proxPointer, $skipOffset);
$this->_dumpTermDictEntry($tisFile, $prevTerm, $term, $prevTermInfo, $termInfo);
if ($termCount % self::$indexInterval == 0) {
$this->_dumpTermDictEntry($tiiFile, $prevIndexTerm, $term, $prevIndexTermInfo, $termInfo);
$indexPosition = $tisFile->tell();
$tiiFile->writeVInt($indexPosition - $prevIndexPosition);
$prevIndexPosition = $indexPosition;
}
$termCount++;
}
$this->_files[] = $this->_name . '.tis';
$this->_files[] = $this->_name . '.tii';
$this->_files[] = $this->_name . '.frq';
$this->_files[] = $this->_name . '.prx';
}
/**
* Generate compound index file
*/
private function _generateCFS()
protected function _generateCFS()
{
$cfsFile = $this->_directory->createFile($this->_name . '.cfs');
$cfsFile->writeVInt(count($this->_files));
@@ -486,8 +571,13 @@ class Zend_Search_Lucene_Index_SegmentWriter
$cfsFile->seek($dataOffset);
$dataFile = $this->_directory->getFileObject($fileName);
$data = $dataFile->readBytes($this->_directory->fileLength($fileName));
$cfsFile->writeBytes($data);
$byteCount = $this->_directory->fileLength($fileName);
while ($byteCount > 0) {
$data = $dataFile->readBytes(min($byteCount, 131072 /*128Kb*/));
$byteCount -= strlen($data);
$cfsFile->writeBytes($data);
}
$this->_directory->deleteFile($fileName);
}
@@ -499,21 +589,6 @@ class Zend_Search_Lucene_Index_SegmentWriter
*
* @return Zend_Search_Lucene_Index_SegmentInfo
*/
public function close()
{
if ($this->_docCount == 0) {
return null;
}
$this->_dumpFNM();
$this->_dumpDictionary();
$this->_generateCFS();
return new Zend_Search_Lucene_Index_SegmentInfo($this->_name,
$this->_docCount,
$this->_directory);
}
abstract public function close();
}
@@ -0,0 +1,213 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Exception */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/** Zend_Search_Lucene_Analysis_Analyzer */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Analyzer.php';
/** Zend_Search_Lucene_Index_SegmentWriter */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/SegmentWriter.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Index_SegmentWriter_DocumentWriter extends Zend_Search_Lucene_Index_SegmentWriter
{
/**
* Term Dictionary
* Array of the Zend_Search_Lucene_Index_Term objects
* Corresponding Zend_Search_Lucene_Index_TermInfo object stored in the $_termDictionaryInfos
*
* @var array
*/
protected $_termDictionary;
/**
* Documents, which contain the term
*
* @var array
*/
protected $_termDocs;
/**
* Object constructor.
*
* @param Zend_Search_Lucene_Storage_Directory $directory
* @param string $name
*/
public function __construct(Zend_Search_Lucene_Storage_Directory $directory, $name)
{
parent::__construct($directory, $name);
$this->_termDocs = array();
$this->_termDictionary = array();
}
/**
* Adds a document to this segment.
*
* @param Zend_Search_Lucene_Document $document
* @throws Zend_Search_Lucene_Exception
*/
public function addDocument(Zend_Search_Lucene_Document $document)
{
$storedFields = array();
$docNorms = array();
$similarity = Zend_Search_Lucene_Search_Similarity::getDefault();
foreach ($document->getFieldNames() as $fieldName) {
$field = $document->getField($fieldName);
$this->addField($field);
if ($field->storeTermVector) {
/**
* @todo term vector storing support
*/
throw new Zend_Search_Lucene_Exception('Store term vector functionality is not supported yet.');
}
if ($field->isIndexed) {
if ($field->isTokenized) {
$analyzer = Zend_Search_Lucene_Analysis_Analyzer::getDefault();
$analyzer->setInput($field->value, $field->encoding);
$position = 0;
$tokenCounter = 0;
while (($token = $analyzer->nextToken()) !== null) {
$tokenCounter++;
$term = new Zend_Search_Lucene_Index_Term($token->getTermText(), $field->name);
$termKey = $term->key();
if (!isset($this->_termDictionary[$termKey])) {
// New term
$this->_termDictionary[$termKey] = $term;
$this->_termDocs[$termKey] = array();
$this->_termDocs[$termKey][$this->_docCount] = array();
} else if (!isset($this->_termDocs[$termKey][$this->_docCount])) {
// Existing term, but new term entry
$this->_termDocs[$termKey][$this->_docCount] = array();
}
$position += $token->getPositionIncrement();
$this->_termDocs[$termKey][$this->_docCount][] = $position;
}
$docNorms[$field->name] = chr($similarity->encodeNorm( $similarity->lengthNorm($field->name,
$tokenCounter)*
$document->boost*
$field->boost ));
} else {
$term = new Zend_Search_Lucene_Index_Term($field->getUtf8Value(), $field->name);
$termKey = $term->key();
if (!isset($this->_termDictionary[$termKey])) {
// New term
$this->_termDictionary[$termKey] = $term;
$this->_termDocs[$termKey] = array();
$this->_termDocs[$termKey][$this->_docCount] = array();
} else if (!isset($this->_termDocs[$termKey][$this->_docCount])) {
// Existing term, but new term entry
$this->_termDocs[$termKey][$this->_docCount] = array();
}
$this->_termDocs[$termKey][$this->_docCount][] = 0; // position
$docNorms[$field->name] = chr($similarity->encodeNorm( $similarity->lengthNorm($field->name, 1)*
$document->boost*
$field->boost ));
}
}
if ($field->isStored) {
$storedFields[] = $field;
}
}
foreach ($this->_fields as $fieldName => $field) {
if (!$field->isIndexed) {
continue;
}
if (!isset($this->_norms[$fieldName])) {
$this->_norms[$fieldName] = str_repeat(chr($similarity->encodeNorm( $similarity->lengthNorm($fieldName, 0) )),
$this->_docCount);
}
if (isset($docNorms[$fieldName])){
$this->_norms[$fieldName] .= $docNorms[$fieldName];
} else {
$this->_norms[$fieldName] .= chr($similarity->encodeNorm( $similarity->lengthNorm($fieldName, 0) ));
}
}
$this->addStoredFields($storedFields);
}
/**
* Dump Term Dictionary (.tis) and Term Dictionary Index (.tii) segment files
*/
protected function _dumpDictionary()
{
ksort($this->_termDictionary, SORT_STRING);
$this->initializeDictionaryFiles();
foreach ($this->_termDictionary as $termId => $term) {
$this->addTerm($term, $this->_termDocs[$termId]);
}
$this->closeDictionaryFiles();
}
/**
* Close segment, write it to disk and return segment info
*
* @return Zend_Search_Lucene_Index_SegmentInfo
*/
public function close()
{
if ($this->_docCount == 0) {
return null;
}
$this->_dumpFNM();
$this->_dumpDictionary();
$this->_generateCFS();
return new Zend_Search_Lucene_Index_SegmentInfo($this->_name,
$this->_docCount,
$this->_directory);
}
}
@@ -0,0 +1,94 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Exception */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/** Zend_Search_Lucene_Index_SegmentInfo */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/SegmentInfo.php';
/** Zend_Search_Lucene_Index_SegmentWriter */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/SegmentWriter.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Index_SegmentWriter_StreamWriter extends Zend_Search_Lucene_Index_SegmentWriter
{
/**
* Object constructor.
*
* @param Zend_Search_Lucene_Storage_Directory $directory
* @param string $name
*/
public function __construct(Zend_Search_Lucene_Storage_Directory $directory, $name)
{
parent::__construct($directory, $name);
}
/**
* Create stored fields files and open them for write
*/
public function createStoredFieldsFiles()
{
$this->_fdxFile = $this->_directory->createFile($this->_name . '.fdx');
$this->_fdtFile = $this->_directory->createFile($this->_name . '.fdt');
$this->_files[] = $this->_name . '.fdx';
$this->_files[] = $this->_name . '.fdt';
}
public function addNorm($fieldName, $normVector)
{
if (isset($this->_norms[$fieldName])) {
$this->_norms[$fieldName] .= $normVector;
} else {
$this->_norms[$fieldName] = $normVector;
}
}
/**
* Close segment, write it to disk and return segment info
*
* @return Zend_Search_Lucene_Index_SegmentInfo
*/
public function close()
{
if ($this->_docCount == 0) {
return null;
}
$this->_dumpFNM();
$this->_generateCFS();
return new Zend_Search_Lucene_Index_SegmentInfo($this->_name,
$this->_docCount,
$this->_directory);
}
}
+43 -7
View File
@@ -15,7 +15,7 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
@@ -31,7 +31,7 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Index_Term
@@ -52,21 +52,57 @@ class Zend_Search_Lucene_Index_Term
/**
* @todo docblock
* Object constructor
*/
public function __construct( $text, $field = 'contents' )
public function __construct($text, $field = null)
{
$this->field = $field;
$this->text = $text;
$this->field = ($field === null)? Zend_Search_Lucene::getDefaultSearchField() : $field;
$this->text = $text;
}
/**
* @todo docblock
* Returns term key
*
* @return string
*/
public function key()
{
return $this->field . chr(0) . $this->text;
}
/**
* Get term prefix
*
* @param integer $length
* @return string
*/
public static function getPrefix($str, $length)
{
$prefixBytes = 0;
$prefixChars = 0;
while ($prefixBytes < strlen($str) && $prefixChars < $length) {
$charBytes = 1;
if ((ord($str[$prefixBytes]) & 0xC0) == 0xC0) {
$charBytes++;
if (ord($str[$prefixBytes]) & 0x20 ) {
$charBytes++;
if (ord($str[$prefixBytes]) & 0x10 ) {
$charBytes++;
}
}
}
if ($prefixBytes + $charBytes > strlen($str)) {
// wrong character
break;
}
$prefixChars++;
$prefixBytes += $charBytes;
}
return substr($str, 0, $prefixBytes);
}
}
+2 -2
View File
@@ -15,7 +15,7 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
@@ -26,7 +26,7 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Index_TermInfo
+305 -130
View File
@@ -15,36 +15,80 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Index_SegmentWriter */
require_once 'Zend/Search/Lucene/Index/SegmentWriter.php';
/** Zend_Search_Lucene_Index_SegmentWriter_ */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/SegmentWriter/DocumentWriter.php';
/** Zend_Search_Lucene_Index_SegmentInfo */
require_once 'Zend/Search/Lucene/Index/SegmentInfo.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/SegmentInfo.php';
/** Zend_Search_Lucene_Index_SegmentMerger */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/SegmentMerger.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Index
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Index_Writer
{
/**
* @todo Implement segment merger
* @todo Implement mergeFactor, minMergeDocs, maxMergeDocs usage.
* @todo Implement Analyzer substitution
* @todo Implement Zend_Search_Lucene_Storage_DirectoryRAM and Zend_Search_Lucene_Storage_FileRAM to use it for
* temporary index files
* @todo Directory lock processing
*/
/**
* Number of documents required before the buffered in-memory
* documents are written into a new Segment
*
* Default value is 10
*
* @var integer
*/
public $maxBufferedDocs = 10;
/**
* Largest number of documents ever merged by addDocument().
* Small values (e.g., less than 10,000) are best for interactive indexing,
* as this limits the length of pauses while indexing to a few seconds.
* Larger values are best for batched indexing and speedier searches.
*
* Default value is PHP_INT_MAX
*
* @var integer
*/
public $maxMergeDocs = PHP_INT_MAX;
/**
* Determines how often segment indices are merged by addDocument().
*
* With smaller values, less RAM is used while indexing,
* and searches on unoptimized indices are faster,
* but indexing speed is slower.
*
* With larger values, more RAM is used during indexing,
* and while searches on unoptimized indices are slower,
* indexing is faster.
*
* Thus larger values (> 10) are best for batch index creation,
* and smaller values (< 10) for indices that are interactively maintained.
*
* Default value is 10
*
* @var integer
*/
public $mergeFactor = 10;
/**
* File system adapter.
*
@@ -54,51 +98,11 @@ class Zend_Search_Lucene_Index_Writer
/**
* Index version
* Counts how often the index has been changed by adding or deleting docs
* Changes counter.
*
* @var integer
*/
private $_version;
/**
* Segment name counter.
* Used to name new segments .
*
* @var integer
*/
private $_segmentNameCounter;
/**
* Number of the segments in the index
*
* @var inteher
*/
private $_segments;
/**
* Determines how often segment indices
* are merged by addDocument().
*
* @var integer
*/
public $mergeFactor;
/**
* Determines the minimal number of documents required before
* the buffered in-memory documents are merging and a new Segment
* is created.
*
* @var integer
*/
public $minMergeDocs;
/**
* Determines the largest number of documents ever merged by addDocument().
*
* @var integer
*/
public $maxMergeDocs;
private $_versionUpdate = 0;
/**
* List of the segments, created by index writer
@@ -106,14 +110,30 @@ class Zend_Search_Lucene_Index_Writer
*
* @var array
*/
private $_newSegments;
private $_newSegments = array();
/**
* List of segments to be deleted on commit
*
* @var array
*/
private $_segmentsToDelete = array();
/**
* Current segment to add documents
*
* @var Zend_Search_Lucene_Index_SegmentWriter
* @var Zend_Search_Lucene_Index_SegmentWriter_DocumentWriter
*/
private $_currentSegment;
private $_currentSegment = null;
/**
* Array of Zend_Search_Lucene_Index_SegmentInfo objects for this index.
*
* It's a reference to the corresponding Zend_Search_Lucene::$_segmentInfos array
*
* @var array Zend_Search_Lucene_Index_SegmentInfo
*/
private $_segmentInfos;
/**
* List of indexfiles extensions
@@ -131,7 +151,8 @@ class Zend_Search_Lucene_Index_Writer
'.tvx' => '.tvx',
'.tvd' => '.tvd',
'.tvf' => '.tvf',
'.del' => '.del' );
'.del' => '.del',
'.sti' => '.sti' );
/**
* Opens the index for writing
@@ -142,11 +163,13 @@ class Zend_Search_Lucene_Index_Writer
* index or overwrite the existing one.
*
* @param Zend_Search_Lucene_Storage_Directory $directory
* @param array $segmentInfos
* @param boolean $create
*/
public function __construct(Zend_Search_Lucene_Storage_Directory $directory, $create = false)
public function __construct(Zend_Search_Lucene_Storage_Directory $directory, &$segmentInfos, $create = false)
{
$this->_directory = $directory;
$this->_directory = $directory;
$this->_segmentInfos = &$segmentInfos;
if ($create) {
foreach ($this->_directory->fileList() as $file) {
@@ -159,8 +182,13 @@ class Zend_Search_Lucene_Index_Writer
}
$segmentsFile = $this->_directory->createFile('segments');
$segmentsFile->writeInt((int)0xFFFFFFFF);
// write version
$segmentsFile->writeLong(0);
// write version (is initialized by current time
// $segmentsFile->writeLong((int)microtime(true));
$version = microtime(true);
$segmentsFile->writeInt((int)($version/((double)0xFFFFFFFF + 1)));
$segmentsFile->writeInt((int)($version & 0xFFFFFFFF));
// write name counter
$segmentsFile->writeInt(0);
// write segment counter
@@ -169,27 +197,13 @@ class Zend_Search_Lucene_Index_Writer
$deletableFile = $this->_directory->createFile('deletable');
// write counter
$deletableFile->writeInt(0);
$this->_version = 0;
$this->_segmentNameCounter = 0;
$this->_segments = 0;
} else {
$segmentsFile = $this->_directory->getFileObject('segments');
$format = $segmentsFile->readInt();
if ($format != (int)0xFFFFFFFF) {
throw new Zend_Search_Lucene_Exception('Wrong segments file format');
}
// read version
$this->_version = $segmentsFile->readLong();
// read counter
$this->_segmentNameCounter = $segmentsFile->readInt();
// read segment counter
$this->_segments = $segmentsFile->readInt();
}
$this->_newSegments = array();
$this->_currentSegment = null;
}
/**
@@ -201,49 +215,218 @@ class Zend_Search_Lucene_Index_Writer
{
if ($this->_currentSegment === null) {
$this->_currentSegment =
new Zend_Search_Lucene_Index_SegmentWriter($this->_directory, $this->_newSegmentName());
new Zend_Search_Lucene_Index_SegmentWriter_DocumentWriter($this->_directory, $this->_newSegmentName());
}
$this->_currentSegment->addDocument($document);
$this->_version++;
if ($this->_currentSegment->count() >= $this->maxBufferedDocs) {
$this->commit();
}
$this->_versionUpdate++;
$this->_maybeMergeSegments();
}
/**
* Merge segments if necessary
*/
private function _maybeMergeSegments()
{
$segmentSizes = array();
foreach ($this->_segmentInfos as $segId => $segmentInfo) {
$segmentSizes[$segId] = $segmentInfo->count();
}
$mergePool = array();
$poolSize = 0;
$sizeToMerge = $this->maxBufferedDocs;
asort($segmentSizes, SORT_NUMERIC);
foreach ($segmentSizes as $segId => $size) {
// Check, if segment comes into a new merging block
while ($size >= $sizeToMerge) {
// Merge previous block if it's large enough
if ($poolSize >= $sizeToMerge) {
$this->_mergeSegments($mergePool);
}
$mergePool = array();
$poolSize = 0;
$sizeToMerge *= $this->mergeFactor;
if ($sizeToMerge > $this->maxMergeDocs) {
return;
}
}
$mergePool[] = $this->_segmentInfos[$segId];
$poolSize += $size;
}
if ($poolSize >= $sizeToMerge) {
$this->_mergeSegments($mergePool);
}
}
/**
* Merge specified segments
*
* $segments is an array of SegmentInfo objects
*
* @param array $segments
*/
private function _mergeSegments($segments)
{
// Try to get exclusive non-blocking lock to the 'index.optimization.lock'
// Skip optimization if it's performed by other process right now
$optimizationLock = $this->_directory->createFile('index.optimization.lock');
if (!$optimizationLock->lock(LOCK_EX,true)) {
return;
}
$newName = $this->_newSegmentName();
$merger = new Zend_Search_Lucene_Index_SegmentMerger($this->_directory,
$newName);
foreach ($segments as $segmentInfo) {
$merger->addSource($segmentInfo);
$this->_segmentsToDelete[$segmentInfo->getName()] = $segmentInfo->getName();
}
$newSegment = $merger->merge();
if ($newSegment !== null) {
$this->_newSegments[$newSegment->getName()] = $newSegment;
}
$this->commit();
// optimization is finished
$optimizationLock->unlock();
}
/**
* Update segments file by adding current segment to a list
* @todo !!!!!Finish the implementation
*
* @throws Zend_Search_Lucene_Exception
*/
private function _updateSegments()
{
$segmentsFile = $this->_directory->getFileObject('segments');
$newSegmentFile = $this->_directory->createFile('segments.new');
$newSegmentFile->writeInt((int)0xFFFFFFFF);
$newSegmentFile->writeLong($this->_version);
$newSegmentFile->writeInt($this->_segmentNameCounter);
$this->_segments += count($this->_newSegments);
$newSegmentFile->writeInt($this->_segments);
$segmentsFile->seek(20);
$newSegmentFile->writeBytes($segmentsFile->readBytes($this->_directory->fileLength('segments') - 20));
foreach ($this->_newSegments as $segmentName => $segmentInfo) {
$newSegmentFile->writeString($segmentName);
$newSegmentFile->writeInt($segmentInfo->count());
// Get an exclusive index lock
// Wait, until all parallel searchers or indexers won't stop
// and stop all next searchers, while we are updating segments file
$lock = $this->_directory->getFileObject('index.lock');
if (!$lock->lock(LOCK_EX)) {
throw new Zend_Search_Lucene_Exception('Can\'t obtain exclusive index lock');
}
// Do not share file handlers to get file updates from other sessions.
$segmentsFile = $this->_directory->getFileObject('segments', false);
$newSegmentFile = $this->_directory->createFile('segments.new', false);
// Write format marker
$newSegmentFile->writeInt((int)0xFFFFFFFF);
// Write index version
$segmentsFile->seek(4, SEEK_CUR);
// $version = $segmentsFile->readLong() + $this->_versionUpdate;
// Process version on 32-bit platforms
$versionHigh = $segmentsFile->readInt();
$versionLow = $segmentsFile->readInt();
$version = $versionHigh * ((double)0xFFFFFFFF + 1) +
(($versionLow < 0)? (double)0xFFFFFFFF - (-1 - $versionLow) : $versionLow);
$version += $this->_versionUpdate;
$this->_versionUpdate = 0;
$newSegmentFile->writeInt((int)($version/((double)0xFFFFFFFF + 1)));
$newSegmentFile->writeInt((int)($version & 0xFFFFFFFF));
// Write segment name counter
$newSegmentFile->writeInt($segmentsFile->readInt());
// Get number of segments offset
$numOfSegmentsOffset = $newSegmentFile->tell();
// Write number of segemnts
$segmentsCount = $segmentsFile->readInt();
$newSegmentFile->writeInt(0); // Write dummy data (segment counter)
$segments = array();
for ($count = 0; $count < $segmentsCount; $count++) {
$segName = $segmentsFile->readString();
$segSize = $segmentsFile->readInt();
if (!in_array($segName, $this->_segmentsToDelete)) {
$newSegmentFile->writeString($segName);
$newSegmentFile->writeInt($segSize);
$segments[$segName] = $segSize;
}
}
$segmentsFile->close();
$segmentsCount = count($segments) + count($this->_newSegments);
// Remove segments, not listed in $segments (deleted)
// Load segments, not listed in $this->_segmentInfos
foreach ($this->_segmentInfos as $segId => $segInfo) {
if (isset($segments[$segInfo->getName()])) {
// Segment is already included into $this->_segmentInfos
unset($segments[$segInfo->getName()]);
} else {
// remove deleted segment from a list
unset($this->_segmentInfos[$segId]);
}
}
// $segments contains a list of segments to load
// do it later
foreach ($this->_newSegments as $segName => $segmentInfo) {
$newSegmentFile->writeString($segName);
$newSegmentFile->writeInt($segmentInfo->count());
$this->_segmentInfos[] = $segmentInfo;
}
$this->_newSegments = array();
$newSegmentFile->seek($numOfSegmentsOffset);
$newSegmentFile->writeInt($segmentsCount); // Update segments count
$newSegmentFile->close();
$this->_directory->renameFile('segments.new', 'segments');
// Segments file update is finished
// Switch back to shared lock mode
$lock->lock(LOCK_SH);
$fileList = $this->_directory->fileList();
foreach ($this->_segmentsToDelete as $nameToDelete) {
foreach (self::$_indexExtensions as $ext) {
if ($this->_directory->fileExists($nameToDelete . $ext)) {
$this->_directory->deleteFile($nameToDelete . $ext);
}
}
foreach ($fileList as $file) {
if (substr($file, 0, strlen($nameToDelete) + 2) == ($nameToDelete . '.f') &&
ctype_digit( substr($file, strlen($nameToDelete) + 2) )) {
$this->_directory->deleteFile($file);
}
}
}
$this->_segmentsToDelete = array();
// Load segments, created by other process
foreach ($segments as $segName => $segSize) {
// Load new segments
$this->_segmentInfos[] = new Zend_Search_Lucene_Index_SegmentInfo($segName,
$segSize,
$this->_directory);
}
}
/**
* Commit current changes
* returns array of new segments
*
* @return array
*/
public function commit()
{
@@ -255,14 +438,10 @@ class Zend_Search_Lucene_Index_Writer
$this->_currentSegment = null;
}
if (count($this->_newSegments) != 0) {
if (count($this->_newSegments) != 0 ||
count($this->_segmentsToDelete) != 0) {
$this->_updateSegments();
}
$result = $this->_newSegments;
$this->_newSegments = array();
return $result;
}
@@ -279,43 +458,16 @@ class Zend_Search_Lucene_Index_Writer
*/
}
/**
* Returns the number of documents currently in this index.
*
* @return integer
*/
public function docCount($readers)
{
/**
* @todo implementation
*/
}
/**
* Flushes all changes to an index and closes all associated files.
*
*/
public function close()
{
/**
* @todo implementation
*/
}
/**
* Merges all segments together into a single segment, optimizing
* an index for search.
* Input is an array of Zend_Search_Lucene_Index_SegmentInfo objects
*
* return void
* @throws Zend_Search_Lucene_Exception
*/
public function optimize()
{
/**
* @todo implementation
*/
$this->_mergeSegments($this->_segmentInfos);
}
/**
@@ -325,7 +477,30 @@ class Zend_Search_Lucene_Index_Writer
*/
private function _newSegmentName()
{
return '_' . base_convert($this->_segmentNameCounter++, 10, 36);
// Do not share file handler to get file updates from other sessions.
$segmentsFile = $this->_directory->getFileObject('segments', false);
// Get exclusive segments file lock
// We have guarantee, that we will not intersect with _updateSegments() call
// of other process, because it needs exclusive index lock and waits
// until all other searchers won't stop
if (!$segmentsFile->lock(LOCK_EX)) {
throw new Zend_Search_Lucene_Exception('Can\'t obtain exclusive index lock');
}
$segmentsFile->seek(12); // 12 = 4 (int, file format marker) + 8 (long, index version)
$segmentNameCounter = $segmentsFile->readInt();
$segmentsFile->seek(12); // 12 = 4 (int, file format marker) + 8 (long, index version)
$segmentsFile->writeInt($segmentNameCounter + 1);
// Flash output to guarantee that wrong value will not be loaded between unlock and
// return (which calls $segmentsFile destructor)
$segmentsFile->flush();
$segmentsFile->unlock();
return '_' . base_convert($segmentNameCounter, 10, 36);
}
}
+330
View File
@@ -0,0 +1,330 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/**
* @category Zend
* @package Zend_Search_Lucene
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
interface Zend_Search_Lucene_Interface
{
/**
* Returns the Zend_Search_Lucene_Storage_Directory instance for this index.
*
* @return Zend_Search_Lucene_Storage_Directory
*/
public function getDirectory();
/**
* Returns the total number of documents in this index (including deleted documents).
*
* @return integer
*/
public function count();
/**
* Returns one greater than the largest possible document number.
* This may be used to, e.g., determine how big to allocate a structure which will have
* an element for every document number in an index.
*
* @return integer
*/
public function maxDoc();
/**
* Returns the total number of non-deleted documents in this index.
*
* @return integer
*/
public function numDocs();
/**
* Checks, that document is deleted
*
* @param integer $id
* @return boolean
* @throws Zend_Search_Lucene_Exception Exception is thrown if $id is out of the range
*/
public function isDeleted($id);
/**
* Set default search field.
*
* Null means, that search is performed through all fields by default
*
* Default value is null
*
* @param string $fieldName
*/
public static function setDefaultSearchField($fieldName);
/**
* Get default search field.
*
* Null means, that search is performed through all fields by default
*
* @return string
*/
public static function getDefaultSearchField();
/**
* Retrieve index maxBufferedDocs option
*
* maxBufferedDocs is a minimal number of documents required before
* the buffered in-memory documents are written into a new Segment
*
* Default value is 10
*
* @return integer
*/
public function getMaxBufferedDocs();
/**
* Set index maxBufferedDocs option
*
* maxBufferedDocs is a minimal number of documents required before
* the buffered in-memory documents are written into a new Segment
*
* Default value is 10
*
* @param integer $maxBufferedDocs
*/
public function setMaxBufferedDocs($maxBufferedDocs);
/**
* Retrieve index maxMergeDocs option
*
* maxMergeDocs is a largest number of documents ever merged by addDocument().
* Small values (e.g., less than 10,000) are best for interactive indexing,
* as this limits the length of pauses while indexing to a few seconds.
* Larger values are best for batched indexing and speedier searches.
*
* Default value is PHP_INT_MAX
*
* @return integer
*/
public function getMaxMergeDocs();
/**
* Set index maxMergeDocs option
*
* maxMergeDocs is a largest number of documents ever merged by addDocument().
* Small values (e.g., less than 10,000) are best for interactive indexing,
* as this limits the length of pauses while indexing to a few seconds.
* Larger values are best for batched indexing and speedier searches.
*
* Default value is PHP_INT_MAX
*
* @param integer $maxMergeDocs
*/
public function setMaxMergeDocs($maxMergeDocs);
/**
* Retrieve index mergeFactor option
*
* mergeFactor determines how often segment indices are merged by addDocument().
* With smaller values, less RAM is used while indexing,
* and searches on unoptimized indices are faster,
* but indexing speed is slower.
* With larger values, more RAM is used during indexing,
* and while searches on unoptimized indices are slower,
* indexing is faster.
* Thus larger values (> 10) are best for batch index creation,
* and smaller values (< 10) for indices that are interactively maintained.
*
* Default value is 10
*
* @return integer
*/
public function getMergeFactor();
/**
* Set index mergeFactor option
*
* mergeFactor determines how often segment indices are merged by addDocument().
* With smaller values, less RAM is used while indexing,
* and searches on unoptimized indices are faster,
* but indexing speed is slower.
* With larger values, more RAM is used during indexing,
* and while searches on unoptimized indices are slower,
* indexing is faster.
* Thus larger values (> 10) are best for batch index creation,
* and smaller values (< 10) for indices that are interactively maintained.
*
* Default value is 10
*
* @param integer $maxMergeDocs
*/
public function setMergeFactor($mergeFactor);
/**
* Performs a query against the index and returns an array
* of Zend_Search_Lucene_Search_QueryHit objects.
* Input is a string or Zend_Search_Lucene_Search_Query.
*
* @param mixed $query
* @return array Zend_Search_Lucene_Search_QueryHit
* @throws Zend_Search_Lucene_Exception
*/
public function find($query);
/**
* Returns a list of all unique field names that exist in this index.
*
* @param boolean $indexed
* @return array
*/
public function getFieldNames($indexed = false);
/**
* Returns a Zend_Search_Lucene_Document object for the document
* number $id in this index.
*
* @param integer|Zend_Search_Lucene_Search_QueryHit $id
* @return Zend_Search_Lucene_Document
*/
public function getDocument($id);
/**
* Returns true if index contain documents with specified term.
*
* Is used for query optimization.
*
* @param Zend_Search_Lucene_Index_Term $term
* @return boolean
*/
public function hasTerm(Zend_Search_Lucene_Index_Term $term);
/**
* Returns IDs of all the documents containing term.
*
* @param Zend_Search_Lucene_Index_Term $term
* @return array
*/
public function termDocs(Zend_Search_Lucene_Index_Term $term);
/**
* Returns an array of all term freqs.
* Return array structure: array( docId => freq, ...)
*
* @param Zend_Search_Lucene_Index_Term $term
* @return integer
*/
public function termFreqs(Zend_Search_Lucene_Index_Term $term);
/**
* Returns an array of all term positions in the documents.
* Return array structure: array( docId => array( pos1, pos2, ...), ...)
*
* @param Zend_Search_Lucene_Index_Term $term
* @return array
*/
public function termPositions(Zend_Search_Lucene_Index_Term $term);
/**
* Returns the number of documents in this index containing the $term.
*
* @param Zend_Search_Lucene_Index_Term $term
* @return integer
*/
public function docFreq(Zend_Search_Lucene_Index_Term $term);
/**
* Retrive similarity used by index reader
*
* @return Zend_Search_Lucene_Search_Similarity
*/
public function getSimilarity();
/**
* Returns a normalization factor for "field, document" pair.
*
* @param integer $id
* @param string $fieldName
* @return float
*/
public function norm($id, $fieldName);
/**
* Returns true if any documents have been deleted from this index.
*
* @return boolean
*/
public function hasDeletions();
/**
* Deletes a document from the index.
* $id is an internal document id
*
* @param integer|Zend_Search_Lucene_Search_QueryHit $id
* @throws Zend_Search_Lucene_Exception
*/
public function delete($id);
/**
* Adds a document to this index.
*
* @param Zend_Search_Lucene_Document $document
*/
public function addDocument(Zend_Search_Lucene_Document $document);
/**
* Commit changes resulting from delete() or undeleteAll() operations.
*/
public function commit();
/**
* Optimize index.
*
* Merges all segments into one
*/
public function optimize();
/**
* Returns an array of all terms in this index.
*
* @return array
*/
public function terms();
/**
* Undeletes all documents currently marked as deleted in this index.
*/
public function undeleteAll();
/**
* Add reference to the index object
*
* @internal
*/
public function addReference();
/**
* Remove reference from the index object
*
* When reference count becomes zero, index is closed and resources are cleaned up
*
* @internal
*/
public function removeReference();
}
+170
View File
@@ -0,0 +1,170 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/**
* Abstract Priority Queue
*
* It implements a priority queue.
* Please go to "Data Structures and Algorithms",
* Aho, Hopcroft, and Ullman, Addison-Wesley, 1983 (corrected 1987 edition),
* for implementation details.
*
* It provides O(log(N)) time of put/pop operations, where N is a size of queue
*
* @category Zend
* @package Zend_Search_Lucene
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
abstract class Zend_Search_Lucene_PriorityQueue
{
/**
* Queue heap
*
* Heap contains balanced partial ordered binary tree represented in array
* [0] - top of the tree
* [1] - first child of [0]
* [2] - second child of [0]
* ...
* [2*n + 1] - first child of [n]
* [2*n + 2] - second child of [n]
*
* @var array
*/
private $_heap = array();
/**
* Add element to the queue
*
* O(log(N)) time
*
* @param mixed $element
*/
public function put($element)
{
$nodeId = count($this->_heap);
$parentId = ($nodeId-1) >> 1; // floor( ($nodeId-1)/2 )
while ($nodeId != 0 && $this->_less($element, $this->_heap[$parentId])) {
// Move parent node down
$this->_heap[$nodeId] = $this->_heap[$parentId];
// Move pointer to the next level of tree
$nodeId = $parentId;
$parentId = ($nodeId-1) >> 1; // floor( ($nodeId-1)/2 )
}
// Put new node into the tree
$this->_heap[$nodeId] = $element;
}
/**
* Return least element of the queue
*
* Constant time
*
* @return mixed
*/
public function top()
{
if (count($this->_heap) == 0) {
return null;
}
return $this->_heap[0];
}
/**
* Removes and return least element of the queue
*
* O(log(N)) time
*
* @return mixed
*/
public function pop()
{
if (count($this->_heap) == 0) {
return null;
}
$top = $this->_heap[0];
$lastId = count($this->_heap) - 1;
/**
* Find appropriate position for last node
*/
$nodeId = 0; // Start from a top
$childId = 1; // First child
// Choose smaller child
if ($lastId > 2 && $this->_less($this->_heap[2], $this->_heap[1])) {
$childId = 2;
}
while ($childId < $lastId &&
$this->_less($this->_heap[$childId], $this->_heap[$lastId])
) {
// Move child node up
$this->_heap[$nodeId] = $this->_heap[$childId];
$nodeId = $childId; // Go down
$childId = ($nodeId << 1) + 1; // First child
// Choose smaller child
if (($childId+1) < $lastId &&
$this->_less($this->_heap[$childId+1], $this->_heap[$childId])
) {
$childId++;
}
}
// Move last element to the new position
$this->_heap[$nodeId] = $this->_heap[$lastId];
unset($this->_heap[$lastId]);
return $top;
}
/**
* Clear queue
*/
public function clear()
{
$this->_heap = array();
}
/**
* Compare elements
*
* Returns true, if $el1 is less than $el2; else otherwise
*
* @param mixed $el1
* @param mixed $el2
* @return boolean
*/
abstract protected function _less($el1, $el2);
}
+468
View File
@@ -0,0 +1,468 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Interface */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Interface.php';
/**
* Proxy class intended to be used in userland.
*
* It tracks, when index object goes out of scope and forces ndex closing
*
* @category Zend
* @package Zend_Search_Lucene
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Proxy implements Zend_Search_Lucene_Interface
{
/**
* Index object
*
* @var Zend_Search_Lucene_Interface
*/
private $_index;
/**
* Object constructor
*
* @param Zend_Search_Lucene_Interface $index
*/
public function __construct(Zend_Search_Lucene_Interface $index)
{
$this->_index = $index;
$this->_index->addReference();
}
/**
* Object destructor
*/
public function __destruct()
{
if ($this->_index !== null) {
// This code is invoked if Zend_Search_Lucene_Interface object constructor throws an exception
$this->_index->removeReference();
}
$this->_index = null;
}
/**
* Returns the Zend_Search_Lucene_Storage_Directory instance for this index.
*
* @return Zend_Search_Lucene_Storage_Directory
*/
public function getDirectory()
{
return $this->_index->getDirectory();
}
/**
* Returns the total number of documents in this index (including deleted documents).
*
* @return integer
*/
public function count()
{
return $this->_index->count();
}
/**
* Returns one greater than the largest possible document number.
* This may be used to, e.g., determine how big to allocate a structure which will have
* an element for every document number in an index.
*
* @return integer
*/
public function maxDoc()
{
return $this->_index->maxDoc();
}
/**
* Returns the total number of non-deleted documents in this index.
*
* @return integer
*/
public function numDocs()
{
return $this->_index->numDocs();
}
/**
* Checks, that document is deleted
*
* @param integer $id
* @return boolean
* @throws Zend_Search_Lucene_Exception Exception is thrown if $id is out of the range
*/
public function isDeleted($id)
{
return $this->_index->isDeleted($id);
}
/**
* Set default search field.
*
* Null means, that search is performed through all fields by default
*
* Default value is null
*
* @param string $fieldName
*/
public static function setDefaultSearchField($fieldName)
{
Zend_Search_Lucene::setDefaultSearchField($fieldName);
}
/**
* Get default search field.
*
* Null means, that search is performed through all fields by default
*
* @return string
*/
public static function getDefaultSearchField()
{
return Zend_Search_Lucene::getDefaultSearchField();
}
/**
* Retrieve index maxBufferedDocs option
*
* maxBufferedDocs is a minimal number of documents required before
* the buffered in-memory documents are written into a new Segment
*
* Default value is 10
*
* @return integer
*/
public function getMaxBufferedDocs()
{
return $this->_index->getMaxBufferedDocs();
}
/**
* Set index maxBufferedDocs option
*
* maxBufferedDocs is a minimal number of documents required before
* the buffered in-memory documents are written into a new Segment
*
* Default value is 10
*
* @param integer $maxBufferedDocs
*/
public function setMaxBufferedDocs($maxBufferedDocs)
{
$this->_index->setMaxBufferedDocs($maxBufferedDocs);
}
/**
* Retrieve index maxMergeDocs option
*
* maxMergeDocs is a largest number of documents ever merged by addDocument().
* Small values (e.g., less than 10,000) are best for interactive indexing,
* as this limits the length of pauses while indexing to a few seconds.
* Larger values are best for batched indexing and speedier searches.
*
* Default value is PHP_INT_MAX
*
* @return integer
*/
public function getMaxMergeDocs()
{
return $this->_index->getMaxMergeDocs();
}
/**
* Set index maxMergeDocs option
*
* maxMergeDocs is a largest number of documents ever merged by addDocument().
* Small values (e.g., less than 10,000) are best for interactive indexing,
* as this limits the length of pauses while indexing to a few seconds.
* Larger values are best for batched indexing and speedier searches.
*
* Default value is PHP_INT_MAX
*
* @param integer $maxMergeDocs
*/
public function setMaxMergeDocs($maxMergeDocs)
{
$this->_index->setMaxMergeDocs($maxMergeDocs);
}
/**
* Retrieve index mergeFactor option
*
* mergeFactor determines how often segment indices are merged by addDocument().
* With smaller values, less RAM is used while indexing,
* and searches on unoptimized indices are faster,
* but indexing speed is slower.
* With larger values, more RAM is used during indexing,
* and while searches on unoptimized indices are slower,
* indexing is faster.
* Thus larger values (> 10) are best for batch index creation,
* and smaller values (< 10) for indices that are interactively maintained.
*
* Default value is 10
*
* @return integer
*/
public function getMergeFactor()
{
return $this->_index->getMergeFactor();
}
/**
* Set index mergeFactor option
*
* mergeFactor determines how often segment indices are merged by addDocument().
* With smaller values, less RAM is used while indexing,
* and searches on unoptimized indices are faster,
* but indexing speed is slower.
* With larger values, more RAM is used during indexing,
* and while searches on unoptimized indices are slower,
* indexing is faster.
* Thus larger values (> 10) are best for batch index creation,
* and smaller values (< 10) for indices that are interactively maintained.
*
* Default value is 10
*
* @param integer $maxMergeDocs
*/
public function setMergeFactor($mergeFactor)
{
$this->_index->setMergeFactor($mergeFactor);
}
/**
* Performs a query against the index and returns an array
* of Zend_Search_Lucene_Search_QueryHit objects.
* Input is a string or Zend_Search_Lucene_Search_Query.
*
* @param mixed $query
* @return array Zend_Search_Lucene_Search_QueryHit
* @throws Zend_Search_Lucene_Exception
*/
public function find($query)
{
// actual parameter list
$parameters = func_get_args();
// invoke $this->_index->find() method with specified parameters
return call_user_func_array(array(&$this->_index, 'find'), $parameters);
}
/**
* Returns a list of all unique field names that exist in this index.
*
* @param boolean $indexed
* @return array
*/
public function getFieldNames($indexed = false)
{
return $this->_index->getFieldNames($indexed);
}
/**
* Returns a Zend_Search_Lucene_Document object for the document
* number $id in this index.
*
* @param integer|Zend_Search_Lucene_Search_QueryHit $id
* @return Zend_Search_Lucene_Document
*/
public function getDocument($id)
{
return $this->_index->getDocument($id);
}
/**
* Returns true if index contain documents with specified term.
*
* Is used for query optimization.
*
* @param Zend_Search_Lucene_Index_Term $term
* @return boolean
*/
public function hasTerm(Zend_Search_Lucene_Index_Term $term)
{
return $this->_index->hasTerm($term);
}
/**
* Returns IDs of all the documents containing term.
*
* @param Zend_Search_Lucene_Index_Term $term
* @return array
*/
public function termDocs(Zend_Search_Lucene_Index_Term $term)
{
return $this->_index->termDocs($term);
}
/**
* Returns an array of all term freqs.
* Return array structure: array( docId => freq, ...)
*
* @param Zend_Search_Lucene_Index_Term $term
* @return integer
*/
public function termFreqs(Zend_Search_Lucene_Index_Term $term)
{
return $this->_index->termFreqs($term);
}
/**
* Returns an array of all term positions in the documents.
* Return array structure: array( docId => array( pos1, pos2, ...), ...)
*
* @param Zend_Search_Lucene_Index_Term $term
* @return array
*/
public function termPositions(Zend_Search_Lucene_Index_Term $term)
{
return $this->_index->termPositions($term);
}
/**
* Returns the number of documents in this index containing the $term.
*
* @param Zend_Search_Lucene_Index_Term $term
* @return integer
*/
public function docFreq(Zend_Search_Lucene_Index_Term $term)
{
return $this->_index->docFreq($term);
}
/**
* Retrive similarity used by index reader
*
* @return Zend_Search_Lucene_Search_Similarity
*/
public function getSimilarity()
{
return $this->_index->getSimilarity();
}
/**
* Returns a normalization factor for "field, document" pair.
*
* @param integer $id
* @param string $fieldName
* @return float
*/
public function norm($id, $fieldName)
{
return $this->_index->norm($id, $fieldName);
}
/**
* Returns true if any documents have been deleted from this index.
*
* @return boolean
*/
public function hasDeletions()
{
return $this->_index->hasDeletions();
}
/**
* Deletes a document from the index.
* $id is an internal document id
*
* @param integer|Zend_Search_Lucene_Search_QueryHit $id
* @throws Zend_Search_Lucene_Exception
*/
public function delete($id)
{
return $this->_index->delete($id);
}
/**
* Adds a document to this index.
*
* @param Zend_Search_Lucene_Document $document
*/
public function addDocument(Zend_Search_Lucene_Document $document)
{
$this->_index->addDocument($document);
}
/**
* Commit changes resulting from delete() or undeleteAll() operations.
*/
public function commit()
{
$this->_index->commit();
}
/**
* Optimize index.
*
* Merges all segments into one
*/
public function optimize()
{
$this->_index->optimize();
}
/**
* Returns an array of all terms in this index.
*
* @return array
*/
public function terms()
{
return $this->_index->terms();
}
/**
* Undeletes all documents currently marked as deleted in this index.
*/
public function undeleteAll()
{
return $this->_index->undeleteAll();
}
/**
* Add reference to the index object
*
* @internal
*/
public function addReference()
{
return $this->_index->addReference();
}
/**
* Remove reference from the index object
*
* When reference count becomes zero, index is closed and resources are cleaned up
*
* @internal
*/
public function removeReference()
{
return $this->_index->removeReference();
}
}
@@ -0,0 +1,280 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_FSM */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/FSM.php';
/** Zend_Search_Lucene_Search_QueryToken */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryToken.php';
/** Zend_Search_Lucene_Search_QueryParser */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryParser.php';
/** Zend_Search_Lucene_Exception */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_BooleanExpressionRecognizer extends Zend_Search_Lucene_FSM
{
/** State Machine states */
const ST_START = 0;
const ST_LITERAL = 1;
const ST_NOT_OPERATOR = 2;
const ST_AND_OPERATOR = 3;
const ST_OR_OPERATOR = 4;
/** Input symbols */
const IN_LITERAL = 0;
const IN_NOT_OPERATOR = 1;
const IN_AND_OPERATOR = 2;
const IN_OR_OPERATOR = 3;
/**
* NOT operator signal
*
* @var boolean
*/
private $_negativeLiteral = false;
/**
* Current literal
*
* @var mixed
*/
private $_literal;
/**
* Set of boolean query conjunctions
*
* Each conjunction is an array of conjunction elements
* Each conjunction element is presented with two-elements array:
* array(<literal>, <is_negative>)
*
* So, it has a structure:
* array( array( array(<literal>, <is_negative>), // first literal of first conjuction
* array(<literal>, <is_negative>), // second literal of first conjuction
* ...
* array(<literal>, <is_negative>)
* ), // end of first conjuction
* array( array(<literal>, <is_negative>), // first literal of second conjuction
* array(<literal>, <is_negative>), // second literal of second conjuction
* ...
* array(<literal>, <is_negative>)
* ), // end of second conjuction
* ...
* ) // end of structure
*
* @var array
*/
private $_conjunctions = array();
/**
* Current conjuction
*
* @var array
*/
private $_currentConjunction = array();
/**
* Object constructor
*/
public function __construct()
{
parent::__construct( array(self::ST_START,
self::ST_LITERAL,
self::ST_NOT_OPERATOR,
self::ST_AND_OPERATOR,
self::ST_OR_OPERATOR),
array(self::IN_LITERAL,
self::IN_NOT_OPERATOR,
self::IN_AND_OPERATOR,
self::IN_OR_OPERATOR));
$emptyOperatorAction = new Zend_Search_Lucene_FSMAction($this, 'emptyOperatorAction');
$emptyNotOperatorAction = new Zend_Search_Lucene_FSMAction($this, 'emptyNotOperatorAction');
$this->addRules(array( array(self::ST_START, self::IN_LITERAL, self::ST_LITERAL),
array(self::ST_START, self::IN_NOT_OPERATOR, self::ST_NOT_OPERATOR),
array(self::ST_LITERAL, self::IN_AND_OPERATOR, self::ST_AND_OPERATOR),
array(self::ST_LITERAL, self::IN_OR_OPERATOR, self::ST_OR_OPERATOR),
array(self::ST_LITERAL, self::IN_LITERAL, self::ST_LITERAL, $emptyOperatorAction),
array(self::ST_LITERAL, self::IN_NOT_OPERATOR, self::ST_NOT_OPERATOR, $emptyNotOperatorAction),
array(self::ST_NOT_OPERATOR, self::IN_LITERAL, self::ST_LITERAL),
array(self::ST_AND_OPERATOR, self::IN_LITERAL, self::ST_LITERAL),
array(self::ST_AND_OPERATOR, self::IN_NOT_OPERATOR, self::ST_NOT_OPERATOR),
array(self::ST_OR_OPERATOR, self::IN_LITERAL, self::ST_LITERAL),
array(self::ST_OR_OPERATOR, self::IN_NOT_OPERATOR, self::ST_NOT_OPERATOR),
));
$notOperatorAction = new Zend_Search_Lucene_FSMAction($this, 'notOperatorAction');
$orOperatorAction = new Zend_Search_Lucene_FSMAction($this, 'orOperatorAction');
$literalAction = new Zend_Search_Lucene_FSMAction($this, 'literalAction');
$this->addEntryAction(self::ST_NOT_OPERATOR, $notOperatorAction);
$this->addEntryAction(self::ST_OR_OPERATOR, $orOperatorAction);
$this->addEntryAction(self::ST_LITERAL, $literalAction);
}
/**
* Process next operator.
*
* Operators are defined by class constants: IN_AND_OPERATOR, IN_OR_OPERATOR and IN_NOT_OPERATOR
*
* @param integer $operator
*/
public function processOperator($operator)
{
$this->process($operator);
}
/**
* Process expression literal.
*
* @param integer $operator
*/
public function processLiteral($literal)
{
$this->_literal = $literal;
$this->process(self::IN_LITERAL);
}
/**
* Finish an expression and return result
*
* Result is a set of boolean query conjunctions
*
* Each conjunction is an array of conjunction elements
* Each conjunction element is presented with two-elements array:
* array(<literal>, <is_negative>)
*
* So, it has a structure:
* array( array( array(<literal>, <is_negative>), // first literal of first conjuction
* array(<literal>, <is_negative>), // second literal of first conjuction
* ...
* array(<literal>, <is_negative>)
* ), // end of first conjuction
* array( array(<literal>, <is_negative>), // first literal of second conjuction
* array(<literal>, <is_negative>), // second literal of second conjuction
* ...
* array(<literal>, <is_negative>)
* ), // end of second conjuction
* ...
* ) // end of structure
*
* @return array
* @throws Zend_Search_Lucene_Exception
*/
public function finishExpression()
{
if ($this->getState() != self::ST_LITERAL) {
throw new Zend_Search_Lucene_Exception('Literal expected.');
}
$this->_conjunctions[] = $this->_currentConjunction;
return $this->_conjunctions;
}
/*********************************************************************
* Actions implementation
*********************************************************************/
/**
* default (omitted) operator processing
*/
public function emptyOperatorAction()
{
if (Zend_Search_Lucene_Search_QueryParser::getDefaultOperator() == Zend_Search_Lucene_Search_QueryParser::B_AND) {
// Do nothing
} else {
$this->orOperatorAction();
}
// Process literal
$this->literalAction();
}
/**
* default (omitted) + NOT operator processing
*/
public function emptyNotOperatorAction()
{
if (Zend_Search_Lucene_Search_QueryParser::getDefaultOperator() == Zend_Search_Lucene_Search_QueryParser::B_AND) {
// Do nothing
} else {
$this->orOperatorAction();
}
// Process NOT operator
$this->notOperatorAction();
}
/**
* NOT operator processing
*/
public function notOperatorAction()
{
$this->_negativeLiteral = true;
}
/**
* OR operator processing
* Close current conjunction
*/
public function orOperatorAction()
{
$this->_conjunctions[] = $this->_currentConjunction;
$this->_currentConjunction = array();
}
/**
* Literal processing
*/
public function literalAction()
{
// Add literal to the current conjunction
$this->_currentConjunction[] = array($this->_literal, !$this->_negativeLiteral);
// Switch off negative signal
$this->_negativeLiteral = false;
}
}
+136 -13
View File
@@ -15,16 +15,19 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Document_Html */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Document/Html.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
abstract class Zend_Search_Lucene_Search_Query
@@ -35,14 +38,31 @@ abstract class Zend_Search_Lucene_Search_Query
*
* @var float
*/
private $_boost = 1.0;
private $_boost = 1;
/**
* Query weight
*
* @var Zend_Search_Lucene_Search_Weight
*/
protected $_weight;
protected $_weight = null;
/**
* Current highlight color
*
* @var integer
*/
private $_currentColorIndex = 0;
/**
* List of colors for text highlighting
*
* @var array
*/
private $_highlightColors = array('#66ffff', '#ff66ff', '#ffff66',
'#ff8888', '#88ff88', '#8888ff',
'#88dddd', '#dd88dd', '#dddd88',
'#aaddff', '#aaffdd', '#ddaaff', '#ddffaa', '#ffaadd', '#ffddaa');
/**
@@ -71,30 +91,133 @@ abstract class Zend_Search_Lucene_Search_Query
* Score specified document
*
* @param integer $docId
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Interface $reader
* @return float
*/
abstract public function score($docId, $reader);
abstract public function score($docId, Zend_Search_Lucene_Interface $reader);
/**
* Get document ids likely matching the query
*
* It's an array with document ids as keys (performance considerations)
*
* @return array
*/
abstract public function matchedDocs();
/**
* Execute query in context of index reader
* It also initializes necessary internal structures
*
* Query specific implementation
*
* @param Zend_Search_Lucene_Interface $reader
*/
abstract public function execute(Zend_Search_Lucene_Interface $reader);
/**
* Constructs an appropriate Weight implementation for this query.
*
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Interface $reader
* @return Zend_Search_Lucene_Search_Weight
*/
abstract protected function _createWeight($reader);
abstract public function createWeight(Zend_Search_Lucene_Interface $reader);
/**
* Constructs an initializes a Weight for a query.
* Constructs an initializes a Weight for a _top-level_query_.
*
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Interface $reader
*/
protected function _initWeight($reader)
protected function _initWeight(Zend_Search_Lucene_Interface $reader)
{
$this->_weight = $this->_createWeight($reader);
// Check, that it's a top-level query and query weight is not initialized yet.
if ($this->_weight !== null) {
return $this->_weight;
}
$this->createWeight($reader);
$sum = $this->_weight->sumOfSquaredWeights();
$queryNorm = $reader->getSimilarity()->queryNorm($sum);
$this->_weight->normalize($queryNorm);
}
}
/**
* Re-write query into primitive queries in the context of specified index
*
* @param Zend_Search_Lucene_Interface $index
* @return Zend_Search_Lucene_Search_Query
*/
abstract public function rewrite(Zend_Search_Lucene_Interface $index);
/**
* Optimize query in the context of specified index
*
* @param Zend_Search_Lucene_Interface $index
* @return Zend_Search_Lucene_Search_Query
*/
abstract public function optimize(Zend_Search_Lucene_Interface $index);
/**
* Reset query, so it can be reused within other queries or
* with other indeces
*/
public function reset()
{
$this->_weight = null;
}
/**
* Print a query
*
* @return string
*/
abstract public function __toString();
/**
* Return query terms
*
* @return array
*/
abstract public function getQueryTerms();
/**
* Get highlight color and shift to next
*
* @param integer &$colorIndex
* @return string
*/
protected function _getHighlightColor(&$colorIndex)
{
$color = $this->_highlightColors[$colorIndex++];
$colorIndex %= count($this->_highlightColors);
return $color;
}
/**
* Highlight query terms
*
* @param integer &$colorIndex
* @param Zend_Search_Lucene_Document_Html $doc
*/
abstract public function highlightMatchesDOM(Zend_Search_Lucene_Document_Html $doc, &$colorIndex);
/**
* Highlight matches in $inputHTML
*
* @param string $inputHTML
* @return string
*/
public function highlightMatches($inputHTML)
{
$doc = Zend_Search_Lucene_Document_Html::loadHTML($inputHTML);
$colorIndex = 0;
$this->highlightMatchesDOM($doc, $colorIndex);
return $doc->getHTML();
}
}
@@ -0,0 +1,715 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Search_Query */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Query.php';
/** Zend_Search_Lucene_Search_Weight_Boolean */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Weight/Boolean.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_Query_Boolean extends Zend_Search_Lucene_Search_Query
{
/**
* Subqueries
* Array of Zend_Search_Lucene_Query
*
* @var array
*/
private $_subqueries = array();
/**
* Subqueries signs.
* If true then subquery is required.
* If false then subquery is prohibited.
* If null then subquery is neither prohibited, nor required
*
* If array is null then all subqueries are required
*
* @var array
*/
private $_signs = array();
/**
* Result vector.
*
* @var array
*/
private $_resVector = null;
/**
* A score factor based on the fraction of all query subqueries
* that a document contains.
* float for conjunction queries
* array of float for non conjunction queries
*
* @var mixed
*/
private $_coord = null;
/**
* Class constructor. Create a new Boolean query object.
*
* if $signs array is omitted then all subqueries are required
* it differs from addSubquery() behavior, but should never be used
*
* @param array $subqueries Array of Zend_Search_Search_Query objects
* @param array $signs Array of signs. Sign is boolean|null.
* @return void
*/
public function __construct($subqueries = null, $signs = null)
{
if (is_array($subqueries)) {
$this->_subqueries = $subqueries;
$this->_signs = null;
// Check if all subqueries are required
if (is_array($signs)) {
foreach ($signs as $sign ) {
if ($sign !== true) {
$this->_signs = $signs;
break;
}
}
}
}
}
/**
* Add a $subquery (Zend_Search_Lucene_Query) to this query.
*
* The sign is specified as:
* TRUE - subquery is required
* FALSE - subquery is prohibited
* NULL - subquery is neither prohibited, nor required
*
* @param Zend_Search_Lucene_Search_Query $subquery
* @param boolean|null $sign
* @return void
*/
public function addSubquery(Zend_Search_Lucene_Search_Query $subquery, $sign=null) {
if ($sign !== true || $this->_signs !== null) { // Skip, if all subqueries are required
if ($this->_signs === null) { // Check, If all previous subqueries are required
foreach ($this->_subqueries as $prevSubquery) {
$this->_signs[] = true;
}
}
$this->_signs[] = $sign;
}
$this->_subqueries[] = $subquery;
}
/**
* Re-write queries into primitive queries
*
* @param Zend_Search_Lucene_Interface $index
* @return Zend_Search_Lucene_Search_Query
*/
public function rewrite(Zend_Search_Lucene_Interface $index)
{
$query = new Zend_Search_Lucene_Search_Query_Boolean();
$query->setBoost($this->getBoost());
foreach ($this->_subqueries as $subqueryId => $subquery) {
$query->addSubquery($subquery->rewrite($index),
($this->_signs === null)? true : $this->_signs[$subqueryId]);
}
return $query;
}
/**
* Optimize query in the context of specified index
*
* @param Zend_Search_Lucene_Interface $index
* @return Zend_Search_Lucene_Search_Query
*/
public function optimize(Zend_Search_Lucene_Interface $index)
{
$subqueries = array();
$signs = array();
// Optimize all subqueries
foreach ($this->_subqueries as $id => $subquery) {
$subqueries[] = $subquery->optimize($index);
$signs[] = ($this->_signs === null)? true : $this->_signs[$id];
}
// Check for empty subqueries
foreach ($subqueries as $id => $subquery) {
if ($subquery instanceof Zend_Search_Lucene_Search_Query_Empty) {
if ($signs[$id] === true) {
// Matching is required, but is actually empty
return new Zend_Search_Lucene_Search_Query_Empty();
} else {
// Matching is optional or prohibited, but is empty
// Remove it from subqueries and signs list
unset($subqueries[$id]);
unset($signs[$id]);
}
}
}
// Check if all non-empty subqueries are prohibited
$allProhibited = true;
foreach ($signs as $sign) {
if ($sign !== false) {
$allProhibited = false;
break;
}
}
if ($allProhibited) {
return new Zend_Search_Lucene_Search_Query_Empty();
}
// Check, if reduced subqueries list has only one entry
if (count($subqueries) == 1) {
// It's a query with only one required or optional clause
// (it's already checked, that it's not a prohibited clause)
if ($this->getBoost() == 1) {
return reset($subqueries);
}
$optimizedQuery = clone reset($subqueries);
$optimizedQuery->setBoost($optimizedQuery->getBoost()*$this->getBoost());
return $optimizedQuery;
}
// Check, if reduced subqueries list is empty
if (count($subqueries) == 0) {
return new Zend_Search_Lucene_Search_Query_Empty();
}
// Prepare first candidate for optimized query
$optimizedQuery = new Zend_Search_Lucene_Search_Query_Boolean($subqueries, $signs);
$optimizedQuery->setBoost($this->getBoost());
$terms = array();
$tsigns = array();
$boostFactors = array();
// Try to decompose term and multi-term subqueries
foreach ($subqueries as $id => $subquery) {
if ($subquery instanceof Zend_Search_Lucene_Search_Query_Term) {
$terms[] = $subquery->getTerm();
$tsigns[] = $signs[$id];
$boostFactors[] = $subquery->getBoost();
// remove subquery from a subqueries list
unset($subqueries[$id]);
unset($signs[$id]);
} else if ($subquery instanceof Zend_Search_Lucene_Search_Query_MultiTerm) {
$subTerms = $subquery->getTerms();
$subSigns = $subquery->getSigns();
if ($signs[$id] === true) {
// It's a required multi-term subquery.
// Something like '... +(+term1 -term2 term3 ...) ...'
// Multi-term required subquery can be decomposed only if it contains
// required terms and doesn't contain prohibited terms:
// ... +(+term1 term2 ...) ... => ... +term1 term2 ...
//
// Check this
$hasRequired = false;
$hasProhibited = false;
if ($subSigns === null) {
// All subterms are required
$hasRequired = true;
} else {
foreach ($subSigns as $sign) {
if ($sign === true) {
$hasRequired = true;
} else if ($sign === false) {
$hasProhibited = true;
break;
}
}
}
// Continue if subquery has prohibited terms or doesn't have required terms
if ($hasProhibited || !$hasRequired) {
continue;
}
foreach ($subTerms as $termId => $term) {
$terms[] = $term;
$tsigns[] = ($subSigns === null)? true : $subSigns[$termId];
$boostFactors[] = $subquery->getBoost();
}
// remove subquery from a subqueries list
unset($subqueries[$id]);
unset($signs[$id]);
} else { // $signs[$id] === null || $signs[$id] === false
// It's an optional or prohibited multi-term subquery.
// Something like '... (+term1 -term2 term3 ...) ...'
// or
// something like '... -(+term1 -term2 term3 ...) ...'
// Multi-term optional and required subqueries can be decomposed
// only if all terms are optional.
//
// Check if all terms are optional.
$onlyOptional = true;
if ($subSigns === null) {
// All subterms are required
$onlyOptional = false;
} else {
foreach ($subSigns as $sign) {
if ($sign !== null) {
$onlyOptional = false;
break;
}
}
}
// Continue if non-optional terms are presented in this multi-term subquery
if (!$onlyOptional) {
continue;
}
foreach ($subTerms as $termId => $term) {
$terms[] = $term;
$tsigns[] = ($signs[$id] === null)? null /* optional */ :
false /* prohibited */;
$boostFactors[] = $subquery->getBoost();
}
// remove subquery from a subqueries list
unset($subqueries[$id]);
unset($signs[$id]);
}
}
}
// Check, if there are no decomposed subqueries
if (count($terms) == 0 ) {
// return prepared candidate
return $optimizedQuery;
}
// Check, if all subqueries have been decomposed and all terms has the same boost factor
if (count($subqueries) == 0 && count(array_unique($boostFactors)) == 1) {
$optimizedQuery = new Zend_Search_Lucene_Search_Query_MultiTerm($terms, $tsigns);
$optimizedQuery->setBoost(reset($boostFactors)*$this->getBoost());
return $optimizedQuery;
}
// This boolean query can't be transformed to Term/MultiTerm query and still contains
// several subqueries
// Separate prohibited terms
$prohibitedTerms = array();
foreach ($terms as $id => $term) {
if ($tsigns[$id] === false) {
$prohibitedTerms[] = $term;
unset($terms[$id]);
unset($tsigns[$id]);
unset($boostFactors[$id]);
}
}
if (count($terms) == 1) {
$clause = new Zend_Search_Lucene_Search_Query_Term(reset($terms));
$clause->setBoost(reset($boostFactors));
$subqueries[] = $clause;
$signs[] = reset($tsigns);
// Clear terms list
$terms = array();
} else if (count($terms) > 1 && count(array_unique($boostFactors)) == 1) {
$clause = new Zend_Search_Lucene_Search_Query_MultiTerm($terms, $tsigns);
$clause->setBoost(reset($boostFactors));
$subqueries[] = $clause;
// Clause sign is 'required' if clause contains required terms. 'Optional' otherwise.
$signs[] = (in_array(true, $tsigns))? true : null;
// Clear terms list
$terms = array();
}
if (count($prohibitedTerms) == 1) {
// (boost factors are not significant for prohibited clauses)
$subqueries[] = new Zend_Search_Lucene_Search_Query_Term(reset($prohibitedTerms));
$signs[] = false;
// Clear prohibited terms list
$prohibitedTerms = array();
} else if (count($prohibitedTerms) > 1) {
// prepare signs array
$prohibitedSigns = array();
foreach ($prohibitedTerms as $id => $term) {
// all prohibited term are grouped as optional into multi-term query
$prohibitedSigns[$id] = null;
}
// (boost factors are not significant for prohibited clauses)
$subqueries[] = new Zend_Search_Lucene_Search_Query_MultiTerm($prohibitedTerms, $prohibitedSigns);
// Clause sign is 'prohibited'
$signs[] = false;
// Clear terms list
$prohibitedTerms = array();
}
/** @todo Group terms with the same boost factors together */
// Check, that all terms are processed
// Replace candidate for optimized query
if (count($terms) == 0 && count($prohibitedTerms) == 0) {
$optimizedQuery = new Zend_Search_Lucene_Search_Query_Boolean($subqueries, $signs);
$optimizedQuery->setBoost($this->getBoost());
}
return $optimizedQuery;
}
/**
* Returns subqueries
*
* @return array
*/
public function getSubqueries()
{
return $this->_subqueries;
}
/**
* Return subqueries signs
*
* @return array
*/
public function getSigns()
{
return $this->_signs;
}
/**
* Constructs an appropriate Weight implementation for this query.
*
* @param Zend_Search_Lucene_Interface $reader
* @return Zend_Search_Lucene_Search_Weight
*/
public function createWeight(Zend_Search_Lucene_Interface $reader)
{
$this->_weight = new Zend_Search_Lucene_Search_Weight_Boolean($this, $reader);
return $this->_weight;
}
/**
* Calculate result vector for Conjunction query
* (like '<subquery1> AND <subquery2> AND <subquery3>')
*/
private function _calculateConjunctionResult()
{
$this->_resVector = null;
if (count($this->_subqueries) == 0) {
$this->_resVector = array();
}
foreach ($this->_subqueries as $subquery) {
if($this->_resVector === null) {
$this->_resVector = $subquery->matchedDocs();
} else {
$this->_resVector = array_intersect_key($this->_resVector, $subquery->matchedDocs());
}
if (count($this->_resVector) == 0) {
// Empty result set, we don't need to check other terms
break;
}
}
ksort($this->_resVector, SORT_NUMERIC);
}
/**
* Calculate result vector for non Conjunction query
* (like '<subquery1> AND <subquery2> AND NOT <subquery3> OR <subquery4>')
*/
private function _calculateNonConjunctionResult()
{
$required = null;
$optional = array();
foreach ($this->_subqueries as $subqueryId => $subquery) {
$docs = $subquery->matchedDocs();
if ($this->_signs[$subqueryId] === true) {
// required
if ($required !== null) {
// array intersection
$required = array_intersect_key($required, $docs);
} else {
$required = $docs;
}
} elseif ($this->_signs[$subqueryId] === false) {
// prohibited
// Do nothing. matchedDocs() may include non-matching id's
} else {
// neither required, nor prohibited
// array union
$optional += $docs;
}
}
if ($required !== null) {
$this->_resVector = &$required;
} else {
$this->_resVector = &$optional;
}
ksort($this->_resVector, SORT_NUMERIC);
}
/**
* Score calculator for conjunction queries (all subqueries are required)
*
* @param integer $docId
* @param Zend_Search_Lucene_Interface $reader
* @return float
*/
public function _conjunctionScore($docId, Zend_Search_Lucene_Interface $reader)
{
if ($this->_coord === null) {
$this->_coord = $reader->getSimilarity()->coord(count($this->_subqueries),
count($this->_subqueries) );
}
$score = 0;
foreach ($this->_subqueries as $subquery) {
$subscore = $subquery->score($docId, $reader);
if ($subscore == 0) {
return 0;
}
$score += $subquery->score($docId, $reader) * $this->_coord;
}
return $score * $this->_coord * $this->getBoost();
}
/**
* Score calculator for non conjunction queries (not all subqueries are required)
*
* @param integer $docId
* @param Zend_Search_Lucene_Interface $reader
* @return float
*/
public function _nonConjunctionScore($docId, Zend_Search_Lucene_Interface $reader)
{
if ($this->_coord === null) {
$this->_coord = array();
$maxCoord = 0;
foreach ($this->_signs as $sign) {
if ($sign !== false /* not prohibited */) {
$maxCoord++;
}
}
for ($count = 0; $count <= $maxCoord; $count++) {
$this->_coord[$count] = $reader->getSimilarity()->coord($count, $maxCoord);
}
}
$score = 0;
$matchedSubqueries = 0;
foreach ($this->_subqueries as $subqueryId => $subquery) {
$subscore = $subquery->score($docId, $reader);
// Prohibited
if ($this->_signs[$subqueryId] === false && $subscore != 0) {
return 0;
}
// is required, but doen't match
if ($this->_signs[$subqueryId] === true && $subscore == 0) {
return 0;
}
if ($subscore != 0) {
$matchedSubqueries++;
$score += $subscore;
}
}
return $score * $this->_coord[$matchedSubqueries] * $this->getBoost();
}
/**
* Execute query in context of index reader
* It also initializes necessary internal structures
*
* @param Zend_Search_Lucene_Interface $reader
*/
public function execute(Zend_Search_Lucene_Interface $reader)
{
// Initialize weight if it's not done yet
$this->_initWeight($reader);
foreach ($this->_subqueries as $subquery) {
$subquery->execute($reader);
}
if ($this->_signs === null) {
$this->_calculateConjunctionResult();
} else {
$this->_calculateNonConjunctionResult();
}
}
/**
* Get document ids likely matching the query
*
* It's an array with document ids as keys (performance considerations)
*
* @return array
*/
public function matchedDocs()
{
return $this->_resVector;
}
/**
* Score specified document
*
* @param integer $docId
* @param Zend_Search_Lucene_Interface $reader
* @return float
*/
public function score($docId, Zend_Search_Lucene_Interface $reader)
{
if (isset($this->_resVector[$docId])) {
if ($this->_signs === null) {
return $this->_conjunctionScore($docId, $reader);
} else {
return $this->_nonConjunctionScore($docId, $reader);
}
} else {
return 0;
}
}
/**
* Return query terms
*
* @return array
*/
public function getQueryTerms()
{
$terms = array();
foreach ($this->_subqueries as $id => $subquery) {
if ($this->_signs === null || $this->_signs[$id] !== false) {
$terms = array_merge($terms, $subquery->getQueryTerms());
}
}
return $terms;
}
/**
* Highlight query terms
*
* @param integer &$colorIndex
* @param Zend_Search_Lucene_Document_Html $doc
*/
public function highlightMatchesDOM(Zend_Search_Lucene_Document_Html $doc, &$colorIndex)
{
foreach ($this->_subqueries as $id => $subquery) {
if ($this->_signs === null || $this->_signs[$id] !== false) {
$subquery->highlightMatchesDOM($doc, $colorIndex);
}
}
}
/**
* Print a query
*
* @return string
*/
public function __toString()
{
// It's used only for query visualisation, so we don't care about characters escaping
$query = '';
foreach ($this->_subqueries as $id => $subquery) {
if ($id != 0) {
$query .= ' ';
}
if ($this->_signs === null || $this->_signs[$id] === true) {
$query .= '+';
} else if ($this->_signs[$id] === false) {
$query .= '-';
}
$query .= '(' . $subquery->__toString() . ')';
if ($subquery->getBoost() != 1) {
$query .= '^' . $subquery->getBoost();
}
}
return $query;
}
}
@@ -0,0 +1,139 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Search_Query */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Query.php';
/** Zend_Search_Lucene_Search_Weight_Empty */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Weight/Empty.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_Query_Empty extends Zend_Search_Lucene_Search_Query
{
/**
* Re-write query into primitive queries in the context of specified index
*
* @param Zend_Search_Lucene_Interface $index
* @return Zend_Search_Lucene_Search_Query
*/
public function rewrite(Zend_Search_Lucene_Interface $index)
{
return $this;
}
/**
* Optimize query in the context of specified index
*
* @param Zend_Search_Lucene_Interface $index
* @return Zend_Search_Lucene_Search_Query
*/
public function optimize(Zend_Search_Lucene_Interface $index)
{
// "Empty" query is a primitive query and don't need to be optimized
return $this;
}
/**
* Constructs an appropriate Weight implementation for this query.
*
* @param Zend_Search_Lucene_Interface $reader
* @return Zend_Search_Lucene_Search_Weight
*/
public function createWeight(Zend_Search_Lucene_Interface $reader)
{
return new Zend_Search_Lucene_Search_Weight_Empty();
}
/**
* Execute query in context of index reader
* It also initializes necessary internal structures
*
* @param Zend_Search_Lucene_Interface $reader
*/
public function execute(Zend_Search_Lucene_Interface $reader)
{
// Do nothing
}
/**
* Get document ids likely matching the query
*
* It's an array with document ids as keys (performance considerations)
*
* @return array
*/
public function matchedDocs()
{
return array();
}
/**
* Score specified document
*
* @param integer $docId
* @param Zend_Search_Lucene_Interface $reader
* @return float
*/
public function score($docId, Zend_Search_Lucene_Interface $reader)
{
return 0;
}
/**
* Return query terms
*
* @return array
*/
public function getQueryTerms()
{
return array();
}
/**
* Highlight query terms
*
* @param integer &$colorIndex
* @param Zend_Search_Lucene_Document_Html $doc
*/
public function highlightMatchesDOM(Zend_Search_Lucene_Document_Html $doc, &$colorIndex)
{
// Do nothing
}
/**
* Print a query
*
* @return string
*/
public function __toString()
{
return '<EmptyQuery>';
}
}
@@ -15,23 +15,23 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Search_Query */
require_once 'Zend/Search/Lucene/Search/Query.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Query.php';
/** Zend_Search_Lucene_Search_Weight_MultiTerm */
require_once 'Zend/Search/Lucene/Search/Weight/MultiTerm.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Weight/MultiTerm.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_Query_MultiTerm extends Zend_Search_Lucene_Search_Query
@@ -55,27 +55,24 @@ class Zend_Search_Lucene_Search_Query_MultiTerm extends Zend_Search_Lucene_Searc
*
* @var array
*/
private $_signs = array();
private $_signs;
/**
* Result vector.
* Bitset or array of document IDs
* (depending from Bitset extension availability).
*
* @var mixed
* @var array
*/
private $_resVector = null;
/**
* Terms positions vectors.
* Array of Arrays:
* term1Id => (docId => array( pos1, pos2, ... ), ...)
* term2Id => (docId => array( pos1, pos2, ... ), ...)
* term1Id => (docId => freq, ...)
* term2Id => (docId => freq, ...)
*
* @var array
*/
private $_termsPositions = array();
private $_termsFreqs = array();
/**
@@ -101,15 +98,15 @@ class Zend_Search_Lucene_Search_Query_MultiTerm extends Zend_Search_Lucene_Searc
/**
* Class constructor. Create a new multi-term query object.
*
* if $signs array is omitted then all terms are required
* it differs from addTerm() behavior, but should never be used
*
* @param array $terms Array of Zend_Search_Lucene_Index_Term objects
* @param array $signs Array of signs. Sign is boolean|null.
* @return void
*/
public function __construct($terms = null, $signs = null)
{
/**
* @todo Check contents of $terms and $signs before adding them.
*/
if (is_array($terms)) {
$this->_terms = $terms;
@@ -119,7 +116,7 @@ class Zend_Search_Lucene_Search_Query_MultiTerm extends Zend_Search_Lucene_Searc
foreach ($signs as $sign ) {
if ($sign !== true) {
$this->_signs = $signs;
continue;
break;
}
}
}
@@ -139,25 +136,122 @@ class Zend_Search_Lucene_Search_Query_MultiTerm extends Zend_Search_Lucene_Searc
* @param boolean|null $sign
* @return void
*/
public function addTerm(Zend_Search_Lucene_Index_Term $term, $sign=null) {
$this->_terms[] = $term;
/**
* @todo This is not good. Sometimes $this->_signs is an array, sometimes
* it is null, even when there are terms. It will be changed so that
* it is always an array.
*/
if ($this->_signs === null) {
if ($sign !== null) {
$this->_signs = array();
foreach ($this->_terms as $term) {
$this->_signs[] = null;
public function addTerm(Zend_Search_Lucene_Index_Term $term, $sign = null) {
if ($sign !== true || $this->_signs !== null) { // Skip, if all terms are required
if ($this->_signs === null) { // Check, If all previous terms are required
foreach ($this->_terms as $prevTerm) {
$this->_signs[] = true;
}
$this->_signs[] = $sign;
}
} else {
$this->_signs[] = $sign;
}
$this->_terms[] = $term;
}
/**
* Re-write query into primitive queries in the context of specified index
*
* @param Zend_Search_Lucene_Interface $index
* @return Zend_Search_Lucene_Search_Query
*/
public function rewrite(Zend_Search_Lucene_Interface $index)
{
if (count($this->_terms) == 0) {
return new Zend_Search_Lucene_Search_Query_Empty();
}
// Check, that all fields are qualified
$allQualified = true;
foreach ($this->_terms as $term) {
if ($term->field === null) {
$allQualified = false;
break;
}
}
if ($allQualified) {
return $this;
} else {
/** transform multiterm query to boolean and apply rewrite() method to subqueries. */
$query = new Zend_Search_Lucene_Search_Query_Boolean();
$query->setBoost($this->getBoost());
foreach ($this->_terms as $termId => $term) {
$subquery = new Zend_Search_Lucene_Search_Query_Term($term);
$query->addSubquery($subquery->rewrite($index),
($this->_signs === null)? true : $this->_signs[$termId]);
}
return $query;
}
}
/**
* Optimize query in the context of specified index
*
* @param Zend_Search_Lucene_Interface $index
* @return Zend_Search_Lucene_Search_Query
*/
public function optimize(Zend_Search_Lucene_Interface $index)
{
$terms = $this->_terms;
$signs = $this->_signs;
foreach ($terms as $id => $term) {
if (!$index->hasTerm($term)) {
if ($signs === null || $signs[$id] === true) {
// Term is required
return new Zend_Search_Lucene_Search_Query_Empty();
} else {
// Term is optional or prohibited
// Remove it from terms and signs list
unset($terms[$id]);
unset($signs[$id]);
}
}
}
// Check if all presented terms are prohibited
$allProhibited = true;
if ($signs === null) {
$allProhibited = false;
} else {
foreach ($signs as $sign) {
if ($sign !== false) {
$allProhibited = false;
break;
}
}
}
if ($allProhibited) {
return new Zend_Search_Lucene_Search_Query_Empty();
}
/**
* @todo make an optimization for repeated terms
* (they may have different signs)
*/
if (count($terms) == 1) {
// It's already checked, that it's not a prohibited term
// It's one term query with one required or optional element
$optimizedQuery = new Zend_Search_Lucene_Search_Query_Term(reset($terms));
$optimizedQuery->setBoost($this->getBoost());
return $optimizedQuery;
}
if (count($terms) == 0) {
return new Zend_Search_Lucene_Search_Query_Empty();
}
$optimizedQuery = new Zend_Search_Lucene_Search_Query_MultiTerm($terms, $signs);
$optimizedQuery->setBoost($this->getBoost());
return $optimizedQuery;
}
@@ -198,12 +292,13 @@ class Zend_Search_Lucene_Search_Query_MultiTerm extends Zend_Search_Lucene_Searc
/**
* Constructs an appropriate Weight implementation for this query.
*
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Interface $reader
* @return Zend_Search_Lucene_Search_Weight
*/
protected function _createWeight($reader)
public function createWeight(Zend_Search_Lucene_Interface $reader)
{
return new Zend_Search_Lucene_Search_Weight_MultiTerm($this, $reader);
$this->_weight = new Zend_Search_Lucene_Search_Weight_MultiTerm($this, $reader);
return $this->_weight;
}
@@ -211,38 +306,32 @@ class Zend_Search_Lucene_Search_Query_MultiTerm extends Zend_Search_Lucene_Searc
* Calculate result vector for Conjunction query
* (like '+something +another')
*
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Interface $reader
*/
private function _calculateConjunctionResult($reader)
private function _calculateConjunctionResult(Zend_Search_Lucene_Interface $reader)
{
if (extension_loaded('bitset')) {
foreach( $this->_terms as $termId=>$term ) {
if($this->_resVector === null) {
$this->_resVector = bitset_from_array($reader->termDocs($term));
} else {
$this->_resVector = bitset_intersection(
$this->_resVector,
bitset_from_array($reader->termDocs($term)) );
}
$this->_resVector = null;
$this->_termsPositions[$termId] = $reader->termPositions($term);
}
} else {
foreach( $this->_terms as $termId=>$term ) {
if($this->_resVector === null) {
$this->_resVector = array_flip($reader->termDocs($term));
} else {
$termDocs = array_flip($reader->termDocs($term));
foreach($this->_resVector as $key=>$value) {
if (!isset( $termDocs[$key] )) {
unset( $this->_resVector[$key] );
}
}
}
$this->_termsPositions[$termId] = $reader->termPositions($term);
}
if (count($this->_terms) == 0) {
$this->_resVector = array();
}
foreach( $this->_terms as $termId=>$term ) {
if($this->_resVector === null) {
$this->_resVector = array_flip($reader->termDocs($term));
} else {
$this->_resVector = array_intersect_key($this->_resVector, array_flip($reader->termDocs($term)));
}
if (count($this->_resVector) == 0) {
// Empty result set, we don't need to check other terms
break;
}
$this->_termsFreqs[$termId] = $reader->termFreqs($term);
}
ksort($this->_resVector, SORT_NUMERIC);
}
@@ -250,89 +339,49 @@ class Zend_Search_Lucene_Search_Query_MultiTerm extends Zend_Search_Lucene_Searc
* Calculate result vector for non Conjunction query
* (like '+something -another')
*
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Interface $reader
*/
private function _calculateNonConjunctionResult($reader)
private function _calculateNonConjunctionResult(Zend_Search_Lucene_Interface $reader)
{
if (extension_loaded('bitset')) {
$required = null;
$neither = bitset_empty();
$prohibited = bitset_empty();
$required = null;
$optional = array();
$prohibited = array();
foreach ($this->_terms as $termId => $term) {
$termDocs = bitset_from_array($reader->termDocs($term));
foreach ($this->_terms as $termId => $term) {
$termDocs = array_flip($reader->termDocs($term));
if ($this->_signs[$termId] === true) {
// required
if ($required !== null) {
$required = bitset_intersection($required, $termDocs);
} else {
$required = $termDocs;
}
} elseif ($this->_signs[$termId] === false) {
// prohibited
$prohibited = bitset_union($prohibited, $termDocs);
if ($this->_signs[$termId] === true) {
// required
if ($required !== null) {
// array intersection
$required = array_intersect_key($required, $termDocs);
} else {
// neither required, nor prohibited
$neither = bitset_union($neither, $termDocs);
$required = $termDocs;
}
$this->_termsPositions[$termId] = $reader->termPositions($term);
} elseif ($this->_signs[$termId] === false) {
// prohibited
// array union
$prohibited += $termDocs;
} else {
// neither required, nor prohibited
// array union
$optional += $termDocs;
}
if ($required === null) {
$required = $neither;
}
$this->_resVector = bitset_intersection( $required,
bitset_invert($prohibited, $reader->count()) );
} else {
$required = null;
$neither = array();
$prohibited = array();
foreach ($this->_terms as $termId => $term) {
$termDocs = array_flip($reader->termDocs($term));
if ($this->_signs[$termId] === true) {
// required
if ($required !== null) {
// substitute for bitset_intersection
foreach ($required as $key => $value) {
if (!isset( $termDocs[$key] )) {
unset($required[$key]);
}
}
} else {
$required = $termDocs;
}
} elseif ($this->_signs[$termId] === false) {
// prohibited
// substitute for bitset_union
foreach ($termDocs as $key => $value) {
$prohibited[$key] = $value;
}
} else {
// neither required, nor prohibited
// substitute for bitset_union
foreach ($termDocs as $key => $value) {
$neither[$key] = $value;
}
}
$this->_termsPositions[$termId] = $reader->termPositions($term);
}
if ($required === null) {
$required = $neither;
}
foreach ($required as $key=>$value) {
if (isset( $prohibited[$key] )) {
unset($required[$key]);
}
}
$this->_resVector = $required;
$this->_termsFreqs[$termId] = $reader->termFreqs($term);
}
if ($required !== null) {
$this->_resVector = (count($prohibited) > 0) ?
array_diff_key($required, $prohibited) :
$required;
} else {
$this->_resVector = (count($prohibited) > 0) ?
array_diff_key($optional, $prohibited) :
$optional;
}
ksort($this->_resVector, SORT_NUMERIC);
}
@@ -340,10 +389,10 @@ class Zend_Search_Lucene_Search_Query_MultiTerm extends Zend_Search_Lucene_Searc
* Score calculator for conjunction queries (all terms are required)
*
* @param integer $docId
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Interface $reader
* @return float
*/
public function _conjunctionScore($docId, $reader)
public function _conjunctionScore($docId, Zend_Search_Lucene_Interface $reader)
{
if ($this->_coord === null) {
$this->_coord = $reader->getSimilarity()->coord(count($this->_terms),
@@ -353,12 +402,16 @@ class Zend_Search_Lucene_Search_Query_MultiTerm extends Zend_Search_Lucene_Searc
$score = 0.0;
foreach ($this->_terms as $termId=>$term) {
$score += $reader->getSimilarity()->tf(count($this->_termsPositions[$termId][$docId]) ) *
/**
* We don't need to check that term freq is not 0
* Score calculation is performed only for matched docs
*/
$score += $reader->getSimilarity()->tf($this->_termsFreqs[$termId][$docId]) *
$this->_weights[$termId]->getValue() *
$reader->norm($docId, $term->field);
}
return $score * $this->_coord;
return $score * $this->_coord * $this->getBoost();
}
@@ -366,7 +419,7 @@ class Zend_Search_Lucene_Search_Query_MultiTerm extends Zend_Search_Lucene_Searc
* Score calculator for non conjunction queries (not all terms are required)
*
* @param integer $docId
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Interface $reader
* @return float
*/
public function _nonConjunctionScore($docId, $reader)
@@ -390,42 +443,65 @@ class Zend_Search_Lucene_Search_Query_MultiTerm extends Zend_Search_Lucene_Searc
$matchedTerms = 0;
foreach ($this->_terms as $termId=>$term) {
// Check if term is
if ($this->_signs[$termId] !== false && // not prohibited
isset($this->_termsPositions[$termId][$docId]) // matched
if ($this->_signs[$termId] !== false && // not prohibited
isset($this->_termsFreqs[$termId][$docId]) // matched
) {
$matchedTerms++;
/**
* We don't need to check that term freq is not 0
* Score calculation is performed only for matched docs
*/
$score +=
$reader->getSimilarity()->tf(count($this->_termsPositions[$termId][$docId]) ) *
$reader->getSimilarity()->tf($this->_termsFreqs[$termId][$docId]) *
$this->_weights[$termId]->getValue() *
$reader->norm($docId, $term->field);
}
}
return $score * $this->_coord[$matchedTerms];
return $score * $this->_coord[$matchedTerms] * $this->getBoost();
}
/**
* Execute query in context of index reader
* It also initializes necessary internal structures
*
* @param Zend_Search_Lucene_Interface $reader
*/
public function execute(Zend_Search_Lucene_Interface $reader)
{
if ($this->_signs === null) {
$this->_calculateConjunctionResult($reader);
} else {
$this->_calculateNonConjunctionResult($reader);
}
// Initialize weight if it's not done yet
$this->_initWeight($reader);
}
/**
* Get document ids likely matching the query
*
* It's an array with document ids as keys (performance considerations)
*
* @return array
*/
public function matchedDocs()
{
return $this->_resVector;
}
/**
* Score specified document
*
* @param integer $docId
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Interface $reader
* @return float
*/
public function score($docId, $reader)
public function score($docId, Zend_Search_Lucene_Interface $reader)
{
if($this->_resVector === null) {
if ($this->_signs === null) {
$this->_calculateConjunctionResult($reader);
} else {
$this->_calculateNonConjunctionResult($reader);
}
$this->_initWeight($reader);
}
if ( (extension_loaded('bitset')) ?
bitset_in($this->_resVector, $docId) :
isset($this->_resVector[$docId]) ) {
if (isset($this->_resVector[$docId])) {
if ($this->_signs === null) {
return $this->_conjunctionScore($docId, $reader);
} else {
@@ -435,5 +511,87 @@ class Zend_Search_Lucene_Search_Query_MultiTerm extends Zend_Search_Lucene_Searc
return 0;
}
}
/**
* Return query terms
*
* @return array
*/
public function getQueryTerms()
{
if ($this->_signs === null) {
return $this->_terms;
}
$terms = array();
foreach ($this->_signs as $id => $sign) {
if ($sign !== false) {
$terms[] = $this->_terms[$id];
}
}
return $terms;
}
/**
* Highlight query terms
*
* @param integer &$colorIndex
* @param Zend_Search_Lucene_Document_Html $doc
*/
public function highlightMatchesDOM(Zend_Search_Lucene_Document_Html $doc, &$colorIndex)
{
$words = array();
if ($this->_signs === null) {
foreach ($this->_terms as $term) {
$words[] = $term->text;
}
} else {
foreach ($this->_signs as $id => $sign) {
if ($sign !== false) {
$words[] = $this->_terms[$id]->text;
}
}
}
$doc->highlight($words, $this->_getHighlightColor($colorIndex));
}
/**
* Print a query
*
* @return string
*/
public function __toString()
{
// It's used only for query visualisation, so we don't care about characters escaping
$query = '';
foreach ($this->_terms as $id => $term) {
if ($id != 0) {
$query .= ' ';
}
if ($this->_signs === null || $this->_signs[$id] === true) {
$query .= '+';
} else if ($this->_signs[$id] === false) {
$query .= '-';
}
if ($term->field !== null) {
$query .= $term->field . ':';
}
$query .= $term->text;
}
if ($this->getBoost() != 1) {
$query = '(' . $query . ')^' . $this->getBoost();
}
return $query;
}
}
+188 -72
View File
@@ -15,7 +15,7 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
@@ -23,12 +23,12 @@
/**
* Zend_Search_Lucene_Search_Query
*/
require_once 'Zend/Search/Lucene/Search/Query.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Query.php';
/**
* Zend_Search_Lucene_Search_Weight_MultiTerm
*/
require_once 'Zend/Search/Lucene/Search/Weight/Phrase.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Weight/Phrase.php';
/**
@@ -37,7 +37,7 @@ require_once 'Zend/Search/Lucene/Search/Weight/Phrase.php';
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_Query_Phrase extends Zend_Search_Lucene_Search_Query
@@ -73,16 +73,14 @@ class Zend_Search_Lucene_Search_Query_Phrase extends Zend_Search_Lucene_Search_Q
*
* The slop is zero by default, requiring exact matches.
*
* @var unknown_type
* @var integer
*/
private $_slop;
/**
* Result vector.
* Bitset or array of document IDs
* (depending from Bitset extension availability).
*
* @var mixed
* @var array
*/
private $_resVector = null;
@@ -183,6 +181,70 @@ class Zend_Search_Lucene_Search_Query_Phrase extends Zend_Search_Lucene_Search_Q
}
/**
* Re-write query into primitive queries in the context of specified index
*
* @param Zend_Search_Lucene_Interface $index
* @return Zend_Search_Lucene_Search_Query
*/
public function rewrite(Zend_Search_Lucene_Interface $index)
{
if (count($this->_terms) == 0) {
return new Zend_Search_Lucene_Search_Query_Empty();
} else if ($this->_terms[0]->field !== null) {
return $this;
} else {
$query = new Zend_Search_Lucene_Search_Query_Boolean();
$query->setBoost($this->getBoost());
foreach ($index->getFieldNames(true) as $fieldName) {
$subquery = new Zend_Search_Lucene_Search_Query_Phrase();
$subquery->setSlop($this->getSlop());
foreach ($this->_terms as $termId => $term) {
$qualifiedTerm = new Zend_Search_Lucene_Index_Term($term->text, $fieldName);
$subquery->addTerm($qualifiedTerm, $this->_offsets[$termId]);
}
$query->addSubquery($subquery);
}
return $query;
}
}
/**
* Optimize query in the context of specified index
*
* @param Zend_Search_Lucene_Interface $index
* @return Zend_Search_Lucene_Search_Query
*/
public function optimize(Zend_Search_Lucene_Interface $index)
{
// Check, that index contains all phrase terms
foreach ($this->_terms as $term) {
if (!$index->hasTerm($term)) {
return new Zend_Search_Lucene_Search_Query_Empty();
}
}
if (count($this->_terms) == 1) {
// It's one term query
$optimizedQuery = new Zend_Search_Lucene_Search_Query_Term(reset($this->_terms));
$optimizedQuery->setBoost($this->getBoost());
return $optimizedQuery;
}
if (count($this->_terms) == 0) {
return new Zend_Search_Lucene_Search_Query_Empty();
}
return $this;
}
/**
* Returns query term
*
@@ -209,50 +271,13 @@ class Zend_Search_Lucene_Search_Query_Phrase extends Zend_Search_Lucene_Search_Q
/**
* Constructs an appropriate Weight implementation for this query.
*
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Interface $reader
* @return Zend_Search_Lucene_Search_Weight
*/
protected function _createWeight($reader)
public function createWeight(Zend_Search_Lucene_Interface $reader)
{
return new Zend_Search_Lucene_Search_Weight_Phrase($this, $reader);
}
/**
* Calculate result vector
*
* @param Zend_Search_Lucene $reader
*/
private function _calculateResult($reader)
{
if (extension_loaded('bitset')) {
foreach( $this->_terms as $termId=>$term ) {
if($this->_resVector === null) {
$this->_resVector = bitset_from_array($reader->termDocs($term));
} else {
$this->_resVector = bitset_intersection(
$this->_resVector,
bitset_from_array($reader->termDocs($term)) );
}
$this->_termsPositions[$termId] = $reader->termPositions($term);
}
} else {
foreach( $this->_terms as $termId=>$term ) {
if($this->_resVector === null) {
$this->_resVector = array_flip($reader->termDocs($term));
} else {
$termDocs = array_flip($reader->termDocs($term));
foreach($this->_resVector as $key=>$value) {
if (!isset( $termDocs[$key] )) {
unset( $this->_resVector[$key] );
}
}
}
$this->_termsPositions[$termId] = $reader->termPositions($term);
}
}
$this->_weight = new Zend_Search_Lucene_Search_Weight_Phrase($this, $reader);
return $this->_weight;
}
@@ -305,10 +330,10 @@ class Zend_Search_Lucene_Search_Query_Phrase extends Zend_Search_Lucene_Search_Q
* Score calculator for sloppy phrase queries (terms sequence is fixed)
*
* @param integer $docId
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Interface $reader
* @return float
*/
public function _sloppyPhraseFreq($docId, Zend_Search_Lucene $reader)
public function _sloppyPhraseFreq($docId, Zend_Search_Lucene_Interface $reader)
{
$freq = 0;
@@ -377,50 +402,141 @@ class Zend_Search_Lucene_Search_Query_Phrase extends Zend_Search_Lucene_Search_Q
return $freq;
}
/**
* Execute query in context of index reader
* It also initializes necessary internal structures
*
* @param Zend_Search_Lucene_Interface $reader
*/
public function execute(Zend_Search_Lucene_Interface $reader)
{
$this->_resVector = null;
if (count($this->_terms) == 0) {
$this->_resVector = array();
}
foreach( $this->_terms as $termId=>$term ) {
if($this->_resVector === null) {
$this->_resVector = array_flip($reader->termDocs($term));
} else {
$this->_resVector = array_intersect_key($this->_resVector, array_flip($reader->termDocs($term)));
}
if (count($this->_resVector) == 0) {
// Empty result set, we don't need to check other terms
break;
}
$this->_termsPositions[$termId] = $reader->termPositions($term);
}
ksort($this->_resVector, SORT_NUMERIC);
// Initialize weight if it's not done yet
$this->_initWeight($reader);
}
/**
* Get document ids likely matching the query
*
* It's an array with document ids as keys (performance considerations)
*
* @return array
*/
public function matchedDocs()
{
return $this->_resVector;
}
/**
* Score specified document
*
* @param integer $docId
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Interface $reader
* @return float
*/
public function score($docId, $reader)
public function score($docId, Zend_Search_Lucene_Interface $reader)
{
// optimize zero-term case
if (count($this->_terms) == 0) {
return 0;
}
if($this->_resVector === null) {
$this->_calculateResult($reader);
$this->_initWeight($reader);
}
if ( (extension_loaded('bitset')) ?
bitset_in($this->_resVector, $docId) :
isset($this->_resVector[$docId]) ) {
if (isset($this->_resVector[$docId])) {
if ($this->_slop == 0) {
$freq = $this->_exactPhraseFreq($docId);
} else {
$freq = $this->_sloppyPhraseFreq($docId, $reader);
}
/*
return $reader->getSimilarity()->tf($freq) *
$this->_weight->getValue() *
$reader->norm($docId, reset($this->_terms)->field);
*/
if ($freq != 0) {
$tf = $reader->getSimilarity()->tf($freq);
$weight = $this->_weight->getValue();
$norm = $reader->norm($docId, reset($this->_terms)->field);
return $tf*$weight*$norm;
return $tf * $weight * $norm * $this->getBoost();
}
// Included in result, but culculated freq is zero
return 0;
} else {
return 0;
}
}
/**
* Return query terms
*
* @return array
*/
public function getQueryTerms()
{
return $this->_terms;
}
/**
* Highlight query terms
*
* @param integer &$colorIndex
* @param Zend_Search_Lucene_Document_Html $doc
*/
public function highlightMatchesDOM(Zend_Search_Lucene_Document_Html $doc, &$colorIndex)
{
$words = array();
foreach ($this->_terms as $term) {
$words[] = $term->text;
}
$doc->highlight($words, $this->_getHighlightColor($colorIndex));
}
/**
* Print a query
*
* @return string
*/
public function __toString()
{
// It's used only for query visualisation, so we don't care about characters escaping
$query = '';
if (isset($this->_terms[0]) && $this->_terms[0]->field !== null) {
$query .= $this->_terms[0]->field . ':';
}
$query .= '"';
foreach ($this->_terms as $id => $term) {
if ($id != 0) {
$query .= ' ';
}
$query .= $term->text;
}
$query .= '"';
if ($this->_slop != 0) {
$query .= '~' . $this->_slop;
}
return $query;
}
}
+138 -42
View File
@@ -15,23 +15,23 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Search_Query */
require_once 'Zend/Search/Lucene/Search/Query.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Query.php';
/** Zend_Search_Lucene_Search_Weight_Term */
require_once 'Zend/Search/Lucene/Search/Weight/Term.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Weight/Term.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_Query_Term extends Zend_Search_Lucene_Search_Query
@@ -43,31 +43,20 @@ class Zend_Search_Lucene_Search_Query_Term extends Zend_Search_Lucene_Search_Que
*/
private $_term;
/**
* Term sign.
* If true then term is required
* If false then term is prohibited.
*
* @var bool
*/
private $_sign;
/**
* Documents vector.
* Bitset or array of document IDs
* (depending from Bitset extension availability).
*
* @var mixed
* @var array
*/
private $_docVector = null;
/**
* Term positions vector.
* Array: docId => array( pos1, pos2, ... )
* Term freqs vector.
* array(docId => freq, ...)
*
* @var array
*/
private $_termPositions;
private $_termFreqs;
/**
@@ -76,53 +65,160 @@ class Zend_Search_Lucene_Search_Query_Term extends Zend_Search_Lucene_Search_Que
* @param Zend_Search_Lucene_Index_Term $term
* @param boolean $sign
*/
public function __construct( $term, $sign = true )
public function __construct($term)
{
$this->_term = $term;
$this->_sign = $sign;
}
/**
* Re-write query into primitive queries in the context of specified index
*
* @param Zend_Search_Lucene_Interface $index
* @return Zend_Search_Lucene_Search_Query
*/
public function rewrite(Zend_Search_Lucene_Interface $index)
{
if ($this->_term->field != null) {
return $this;
} else {
$query = new Zend_Search_Lucene_Search_Query_MultiTerm();
$query->setBoost($this->getBoost());
foreach ($index->getFieldNames(true) as $fieldName) {
$term = new Zend_Search_Lucene_Index_Term($this->_term->text, $fieldName);
$query->addTerm($term);
}
return $query->rewrite($index);
}
}
/**
* Optimize query in the context of specified index
*
* @param Zend_Search_Lucene_Interface $index
* @return Zend_Search_Lucene_Search_Query
*/
public function optimize(Zend_Search_Lucene_Interface $index)
{
// Check, that index contains specified term
if (!$index->hasTerm($this->_term)) {
return new Zend_Search_Lucene_Search_Query_Empty();
}
return $this;
}
/**
* Constructs an appropriate Weight implementation for this query.
*
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Interface $reader
* @return Zend_Search_Lucene_Search_Weight
*/
protected function _createWeight($reader)
public function createWeight(Zend_Search_Lucene_Interface $reader)
{
return new Zend_Search_Lucene_Search_Weight_Term($this->_term, $this, $reader);
$this->_weight = new Zend_Search_Lucene_Search_Weight_Term($this->_term, $this, $reader);
return $this->_weight;
}
/**
* Execute query in context of index reader
* It also initializes necessary internal structures
*
* @param Zend_Search_Lucene_Interface $reader
*/
public function execute(Zend_Search_Lucene_Interface $reader)
{
$this->_docVector = array_flip($reader->termDocs($this->_term));
$this->_termFreqs = $reader->termFreqs($this->_term);
// Initialize weight if it's not done yet
$this->_initWeight($reader);
}
/**
* Get document ids likely matching the query
*
* It's an array with document ids as keys (performance considerations)
*
* @return array
*/
public function matchedDocs()
{
return $this->_docVector;
}
/**
* Score specified document
*
* @param integer $docId
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Interface $reader
* @return float
*/
public function score( $docId, $reader )
public function score($docId, Zend_Search_Lucene_Interface $reader)
{
if($this->_docVector===null) {
if (extension_loaded('bitset')) {
$this->_docVector = bitset_from_array( $reader->termDocs($this->_term) );
} else {
$this->_docVector = array_flip($reader->termDocs($this->_term));
}
$this->_termPositions = $reader->termPositions($this->_term);
$this->_initWeight($reader);
}
$match = extension_loaded('bitset') ? bitset_in($this->_docVector, $docId) :
isset($this->_docVector[$docId]);
if ($this->_sign && $match) {
return $reader->getSimilarity()->tf(count($this->_termPositions[$docId]) ) *
if (isset($this->_docVector[$docId])) {
return $reader->getSimilarity()->tf($this->_termFreqs[$docId]) *
$this->_weight->getValue() *
$reader->norm($docId, $this->_term->field);
$reader->norm($docId, $this->_term->field) *
$this->getBoost();
} else {
return 0;
}
}
/**
* Return query terms
*
* @return array
*/
public function getQueryTerms()
{
return array($this->_term);
}
/**
* Return query term
*
* @return Zend_Search_Lucene_Index_Term
*/
public function getTerm()
{
return $this->_term;
}
/**
* Returns query term
*
* @return array
*/
public function getTerms()
{
return $this->_terms;
}
/**
* Highlight query terms
*
* @param integer &$colorIndex
* @param Zend_Search_Lucene_Document_Html $doc
*/
public function highlightMatchesDOM(Zend_Search_Lucene_Document_Html $doc, &$colorIndex)
{
$doc->highlight($this->_term->text, $this->_getHighlightColor($colorIndex));
}
/**
* Print a query
*
* @return string
*/
public function __toString()
{
// It's used only for query visualisation, so we don't care about characters escaping
return (($this->_term->field === null)? '':$this->_term->field . ':') . $this->_term->text;
}
}
@@ -0,0 +1,87 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Index_Term */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/Term.php';
/** Zend_Search_Lucene_Exception */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/** Zend_Search_Lucene_Search_QueryEntry_Term */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryEntry/Term.php';
/** Zend_Search_Lucene_Search_QueryEntry_Phrase */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryEntry/Phrase.php';
/** Zend_Search_Lucene_Search_QueryEntry_Subquery */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryEntry/Subquery.php';
/** Zend_Search_Lucene_Search_QueryParserException */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryParserException.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
abstract class Zend_Search_Lucene_Search_QueryEntry
{
/**
* Query entry boost factor
*
* @var float
*/
protected $_boost = 1.0;
/**
* Process modifier ('~')
*
* @param mixed $parameter
*/
abstract public function processFuzzyProximityModifier($parameter = null);
/**
* Transform entry to a subquery
*
* @param string $encoding
* @return Zend_Search_Lucene_Search_Query
*/
abstract public function getQuery($encoding);
/**
* Boost query entry
*
* @param float $boostFactor
*/
public function boost($boostFactor)
{
$this->_boost *= $boostFactor;
}
}
@@ -0,0 +1,147 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Index_Term */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/Term.php';
/** Zend_Search_Lucene_Exception */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/** Zend_Search_Lucene_Search_QueryEntry */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryEntry.php';
/** Zend_Search_Lucene_Search_QueryParserException */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryParserException.php';
/** Zend_Search_Lucene_Analysis_Analyzer */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Analyzer.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_QueryEntry_Phrase extends Zend_Search_Lucene_Search_QueryEntry
{
/**
* Phrase value
*
* @var string
*/
private $_phrase;
/**
* Field
*
* @var string|null
*/
private $_field;
/**
* Proximity phrase query
*
* @var boolean
*/
private $_proximityQuery = false;
/**
* Words distance, used for proximiti queries
*
* @var integer
*/
private $_wordsDistance = 0;
/**
* Object constractor
*
* @param string $phrase
* @param string $field
*/
public function __construct($phrase, $field)
{
$this->_phrase = $phrase;
$this->_field = $field;
}
/**
* Process modifier ('~')
*
* @param mixed $parameter
*/
public function processFuzzyProximityModifier($parameter = null)
{
$this->_proximityQuery = true;
if ($parameter !== null) {
$this->_wordsDistance = $parameter;
}
}
/**
* Transform entry to a subquery
*
* @param string $encoding
* @return Zend_Search_Lucene_Search_Query
* @throws Zend_Search_Lucene_Search_QueryParserException
*/
public function getQuery($encoding)
{
if (strpos($this->_phrase, '?') !== false || strpos($this->_phrase, '*') !== false) {
throw new Zend_Search_Lucene_Search_QueryParserException('Wildcards are only allowed in a single terms.');
}
$tokens = Zend_Search_Lucene_Analysis_Analyzer::getDefault()->tokenize($this->_phrase, $encoding);
if (count($tokens) == 0) {
return new Zend_Search_Lucene_Search_Query_Empty();
}
if (count($tokens) == 1) {
$term = new Zend_Search_Lucene_Index_Term($tokens[0]->getTermText(), $this->_field);
$query = new Zend_Search_Lucene_Search_Query_Term($term);
$query->setBoost($this->_boost);
return $query;
}
//It's not empty or one term query
$query = new Zend_Search_Lucene_Search_Query_Phrase();
foreach ($tokens as $token) {
$term = new Zend_Search_Lucene_Index_Term($token->getTermText(), $this->_field);
$query->addTerm($term);
}
if ($this->_proximityQuery) {
$query->setSlop($this->_wordsDistance);
}
$query->setBoost($this->_boost);
return $query;
}
}
@@ -0,0 +1,86 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Index_Term */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/Term.php';
/** Zend_Search_Lucene_Exception */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/** Zend_Search_Lucene_Search_QueryEntry */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryEntry.php';
/** Zend_Search_Lucene_Search_QueryParserException */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryParserException.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_QueryEntry_Subquery extends Zend_Search_Lucene_Search_QueryEntry
{
/**
* Query
*
* @var Zend_Search_Lucene_Search_Query
*/
private $_query;
/**
* Object constractor
*
* @param Zend_Search_Lucene_Search_Query $query
*/
public function __construct(Zend_Search_Lucene_Search_Query $query)
{
$this->_query = $query;
}
/**
* Process modifier ('~')
*
* @param mixed $parameter
* @throws Zend_Search_Lucene_Search_QueryParserException
*/
public function processFuzzyProximityModifier($parameter = null)
{
throw new Zend_Search_Lucene_Search_QueryParserException('\'~\' sign must follow term or phrase');
}
/**
* Transform entry to a subquery
*
* @param string $encoding
* @return Zend_Search_Lucene_Search_Query
*/
public function getQuery($encoding)
{
$this->_query->setBoost($this->_boost);
return $this->_query;
}
}
@@ -0,0 +1,154 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Index_Term */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/Term.php';
/** Zend_Search_Lucene_Exception */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/** Zend_Search_Lucene_Search_QueryEntry */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryEntry.php';
/** Zend_Search_Lucene_Search_QueryParserException */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryParserException.php';
/** Zend_Search_Lucene_Analysis_Analyzer */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Analysis/Analyzer.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_QueryEntry_Term extends Zend_Search_Lucene_Search_QueryEntry
{
/**
* Term value
*
* @var string
*/
private $_term;
/**
* Field
*
* @var string|null
*/
private $_field;
/**
* Fuzzy search query
*
* @var boolean
*/
private $_fuzzyQuery = false;
/**
* Similarity
*
* @var float
*/
private $_similarity = 1.;
/**
* Object constractor
*
* @param string $term
* @param string $field
*/
public function __construct($term, $field)
{
$this->_term = $term;
$this->_field = $field;
}
/**
* Process modifier ('~')
*
* @param mixed $parameter
*/
public function processFuzzyProximityModifier($parameter = null)
{
$this->_fuzzyQuery = true;
if ($parameter !== null) {
$this->_similarity = $parameter;
} else {
$this->_similarity = 0.5;
}
}
/**
* Transform entry to a subquery
*
* @param string $encoding
* @return Zend_Search_Lucene_Search_Query
* @throws Zend_Search_Lucene_Search_QueryParserException
*/
public function getQuery($encoding)
{
if ($this->_fuzzyQuery) {
throw new Zend_Search_Lucene_Search_QueryParserException('Fuzzy search is not supported yet.');
}
if (strpos($this->_term, '?') !== false || strpos($this->_term, '*') !== false) {
throw new Zend_Search_Lucene_Search_QueryParserException('Wildcard queries are not supported yet.');
}
$tokens = Zend_Search_Lucene_Analysis_Analyzer::getDefault()->tokenize($this->_term, $encoding);
if (count($tokens) == 0) {
return new Zend_Search_Lucene_Search_Query_Empty();
}
if (count($tokens) == 1) {
$term = new Zend_Search_Lucene_Index_Term($tokens[0]->getTermText(), $this->_field);
$query = new Zend_Search_Lucene_Search_Query_Term($term);
$query->setBoost($this->_boost);
return $query;
}
//It's not empty or one term query
$query = new Zend_Search_Lucene_Search_Query_MultiTerm();
/**
* @todo Process $token->getPositionIncrement() to support stemming, synonyms and other
* analizer design features
*/
foreach ($tokens as $token) {
$term = new Zend_Search_Lucene_Index_Term($token->getTermText(), $this->_field);
$query->addTerm($term, true); // all subterms are required
}
$query->setBoost($this->_boost);
return $query;
}
}
@@ -15,7 +15,7 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
@@ -24,14 +24,14 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_QueryHit
{
/**
* Object handle of the index
* @var Zend_Search_Lucene
* @var Zend_Search_Lucene_Interface
*/
protected $_index = null;
@@ -55,15 +55,15 @@ class Zend_Search_Lucene_Search_QueryHit
/**
* Constructor - pass object handle of Zend_Search_Lucene index that produced
* Constructor - pass object handle of Zend_Search_Lucene_Interface index that produced
* the hit so the document can be retrieved easily from the hit.
*
* @param Zend_Search_Lucene $index
* @param Zend_Search_Lucene_Interface $index
*/
public function __construct(Zend_Search_Lucene $index)
public function __construct(Zend_Search_Lucene_Interface $index)
{
$this->_index = $index;
$this->_index = new Zend_Search_Lucene_Proxy($index);
}
@@ -98,7 +98,7 @@ class Zend_Search_Lucene_Search_QueryHit
/**
* Return the index object for this hit
*
* @return Zend_Search_Lucene
* @return Zend_Search_Lucene_Interface
*/
public function getIndex()
{
@@ -0,0 +1,508 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_FSM */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/FSM.php';
/** Zend_Search_Lucene_Search_QueryParser */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryToken.php';
/** Zend_Search_Lucene_Exception */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/** Zend_Search_Lucene_Search_QueryParserException */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryParserException.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_QueryLexer extends Zend_Search_Lucene_FSM
{
/** State Machine states */
const ST_WHITE_SPACE = 0;
const ST_SYNT_LEXEME = 1;
const ST_LEXEME = 2;
const ST_QUOTED_LEXEME = 3;
const ST_ESCAPED_CHAR = 4;
const ST_ESCAPED_QCHAR = 5;
const ST_LEXEME_MODIFIER = 6;
const ST_NUMBER = 7;
const ST_MANTISSA = 8;
const ST_ERROR = 9;
/** Input symbols */
const IN_WHITE_SPACE = 0;
const IN_SYNT_CHAR = 1;
const IN_LEXEME_MODIFIER = 2;
const IN_ESCAPE_CHAR = 3;
const IN_QUOTE = 4;
const IN_DECIMAL_POINT = 5;
const IN_ASCII_DIGIT = 6;
const IN_CHAR = 7;
const IN_MUTABLE_CHAR = 8;
const QUERY_WHITE_SPACE_CHARS = " \n\r\t";
const QUERY_SYNT_CHARS = ':()[]{}!|&';
const QUERY_MUTABLE_CHARS = '+-';
const QUERY_DOUBLECHARLEXEME_CHARS = '|&';
const QUERY_LEXEMEMODIFIER_CHARS = '~^';
const QUERY_ASCIIDIGITS_CHARS = '0123456789';
/**
* List of recognized lexemes
*
* @var array
*/
private $_lexemes;
/**
* Query string (array of single- or non single-byte characters)
*
* @var array
*/
private $_queryString;
/**
* Current position within a query string
* Used to create appropriate error messages
*
* @var integer
*/
private $_queryStringPosition;
/**
* Recognized part of current lexeme
*
* @var string
*/
private $_currentLexeme;
public function __construct()
{
parent::__construct( array(self::ST_WHITE_SPACE,
self::ST_SYNT_LEXEME,
self::ST_LEXEME,
self::ST_QUOTED_LEXEME,
self::ST_ESCAPED_CHAR,
self::ST_ESCAPED_QCHAR,
self::ST_LEXEME_MODIFIER,
self::ST_NUMBER,
self::ST_MANTISSA,
self::ST_ERROR),
array(self::IN_WHITE_SPACE,
self::IN_SYNT_CHAR,
self::IN_MUTABLE_CHAR,
self::IN_LEXEME_MODIFIER,
self::IN_ESCAPE_CHAR,
self::IN_QUOTE,
self::IN_DECIMAL_POINT,
self::IN_ASCII_DIGIT,
self::IN_CHAR));
$lexemeModifierErrorAction = new Zend_Search_Lucene_FSMAction($this, 'lexModifierErrException');
$quoteWithinLexemeErrorAction = new Zend_Search_Lucene_FSMAction($this, 'quoteWithinLexemeErrException');
$wrongNumberErrorAction = new Zend_Search_Lucene_FSMAction($this, 'wrongNumberErrException');
$this->addRules(array( array(self::ST_WHITE_SPACE, self::IN_WHITE_SPACE, self::ST_WHITE_SPACE),
array(self::ST_WHITE_SPACE, self::IN_SYNT_CHAR, self::ST_SYNT_LEXEME),
array(self::ST_WHITE_SPACE, self::IN_MUTABLE_CHAR, self::ST_SYNT_LEXEME),
array(self::ST_WHITE_SPACE, self::IN_LEXEME_MODIFIER, self::ST_LEXEME_MODIFIER),
array(self::ST_WHITE_SPACE, self::IN_ESCAPE_CHAR, self::ST_ESCAPED_CHAR),
array(self::ST_WHITE_SPACE, self::IN_QUOTE, self::ST_QUOTED_LEXEME),
array(self::ST_WHITE_SPACE, self::IN_DECIMAL_POINT, self::ST_LEXEME),
array(self::ST_WHITE_SPACE, self::IN_ASCII_DIGIT, self::ST_LEXEME),
array(self::ST_WHITE_SPACE, self::IN_CHAR, self::ST_LEXEME)
));
$this->addRules(array( array(self::ST_SYNT_LEXEME, self::IN_WHITE_SPACE, self::ST_WHITE_SPACE),
array(self::ST_SYNT_LEXEME, self::IN_SYNT_CHAR, self::ST_SYNT_LEXEME),
array(self::ST_SYNT_LEXEME, self::IN_MUTABLE_CHAR, self::ST_SYNT_LEXEME),
array(self::ST_SYNT_LEXEME, self::IN_LEXEME_MODIFIER, self::ST_LEXEME_MODIFIER),
array(self::ST_SYNT_LEXEME, self::IN_ESCAPE_CHAR, self::ST_ESCAPED_CHAR),
array(self::ST_SYNT_LEXEME, self::IN_QUOTE, self::ST_QUOTED_LEXEME),
array(self::ST_SYNT_LEXEME, self::IN_DECIMAL_POINT, self::ST_LEXEME),
array(self::ST_SYNT_LEXEME, self::IN_ASCII_DIGIT, self::ST_LEXEME),
array(self::ST_SYNT_LEXEME, self::IN_CHAR, self::ST_LEXEME)
));
$this->addRules(array( array(self::ST_LEXEME, self::IN_WHITE_SPACE, self::ST_WHITE_SPACE),
array(self::ST_LEXEME, self::IN_SYNT_CHAR, self::ST_SYNT_LEXEME),
array(self::ST_LEXEME, self::IN_MUTABLE_CHAR, self::ST_LEXEME),
array(self::ST_LEXEME, self::IN_LEXEME_MODIFIER, self::ST_LEXEME_MODIFIER),
array(self::ST_LEXEME, self::IN_ESCAPE_CHAR, self::ST_ESCAPED_CHAR),
// IN_QUOTE not allowed
array(self::ST_LEXEME, self::IN_QUOTE, self::ST_ERROR, $quoteWithinLexemeErrorAction),
array(self::ST_LEXEME, self::IN_DECIMAL_POINT, self::ST_LEXEME),
array(self::ST_LEXEME, self::IN_ASCII_DIGIT, self::ST_LEXEME),
array(self::ST_LEXEME, self::IN_CHAR, self::ST_LEXEME)
));
$this->addRules(array( array(self::ST_QUOTED_LEXEME, self::IN_WHITE_SPACE, self::ST_QUOTED_LEXEME),
array(self::ST_QUOTED_LEXEME, self::IN_SYNT_CHAR, self::ST_QUOTED_LEXEME),
array(self::ST_QUOTED_LEXEME, self::IN_MUTABLE_CHAR, self::ST_QUOTED_LEXEME),
array(self::ST_QUOTED_LEXEME, self::IN_LEXEME_MODIFIER, self::ST_QUOTED_LEXEME),
array(self::ST_QUOTED_LEXEME, self::IN_ESCAPE_CHAR, self::ST_ESCAPED_QCHAR),
array(self::ST_QUOTED_LEXEME, self::IN_QUOTE, self::ST_WHITE_SPACE),
array(self::ST_QUOTED_LEXEME, self::IN_DECIMAL_POINT, self::ST_QUOTED_LEXEME),
array(self::ST_QUOTED_LEXEME, self::IN_ASCII_DIGIT, self::ST_QUOTED_LEXEME),
array(self::ST_QUOTED_LEXEME, self::IN_CHAR, self::ST_QUOTED_LEXEME)
));
$this->addRules(array( array(self::ST_ESCAPED_CHAR, self::IN_WHITE_SPACE, self::ST_LEXEME),
array(self::ST_ESCAPED_CHAR, self::IN_SYNT_CHAR, self::ST_LEXEME),
array(self::ST_ESCAPED_CHAR, self::IN_MUTABLE_CHAR, self::ST_LEXEME),
array(self::ST_ESCAPED_CHAR, self::IN_LEXEME_MODIFIER, self::ST_LEXEME),
array(self::ST_ESCAPED_CHAR, self::IN_ESCAPE_CHAR, self::ST_LEXEME),
array(self::ST_ESCAPED_CHAR, self::IN_QUOTE, self::ST_LEXEME),
array(self::ST_ESCAPED_CHAR, self::IN_DECIMAL_POINT, self::ST_LEXEME),
array(self::ST_ESCAPED_CHAR, self::IN_ASCII_DIGIT, self::ST_LEXEME),
array(self::ST_ESCAPED_CHAR, self::IN_CHAR, self::ST_LEXEME)
));
$this->addRules(array( array(self::ST_ESCAPED_QCHAR, self::IN_WHITE_SPACE, self::ST_QUOTED_LEXEME),
array(self::ST_ESCAPED_QCHAR, self::IN_SYNT_CHAR, self::ST_QUOTED_LEXEME),
array(self::ST_ESCAPED_QCHAR, self::IN_MUTABLE_CHAR, self::ST_QUOTED_LEXEME),
array(self::ST_ESCAPED_QCHAR, self::IN_LEXEME_MODIFIER, self::ST_QUOTED_LEXEME),
array(self::ST_ESCAPED_QCHAR, self::IN_ESCAPE_CHAR, self::ST_QUOTED_LEXEME),
array(self::ST_ESCAPED_QCHAR, self::IN_QUOTE, self::ST_QUOTED_LEXEME),
array(self::ST_ESCAPED_QCHAR, self::IN_DECIMAL_POINT, self::ST_QUOTED_LEXEME),
array(self::ST_ESCAPED_QCHAR, self::IN_ASCII_DIGIT, self::ST_QUOTED_LEXEME),
array(self::ST_ESCAPED_QCHAR, self::IN_CHAR, self::ST_QUOTED_LEXEME)
));
$this->addRules(array( array(self::ST_LEXEME_MODIFIER, self::IN_WHITE_SPACE, self::ST_WHITE_SPACE),
array(self::ST_LEXEME_MODIFIER, self::IN_SYNT_CHAR, self::ST_SYNT_LEXEME),
array(self::ST_LEXEME_MODIFIER, self::IN_MUTABLE_CHAR, self::ST_SYNT_LEXEME),
array(self::ST_LEXEME_MODIFIER, self::IN_LEXEME_MODIFIER, self::ST_LEXEME_MODIFIER),
// IN_ESCAPE_CHAR not allowed
array(self::ST_LEXEME_MODIFIER, self::IN_ESCAPE_CHAR, self::ST_ERROR, $lexemeModifierErrorAction),
// IN_QUOTE not allowed
array(self::ST_LEXEME_MODIFIER, self::IN_QUOTE, self::ST_ERROR, $lexemeModifierErrorAction),
array(self::ST_LEXEME_MODIFIER, self::IN_DECIMAL_POINT, self::ST_MANTISSA),
array(self::ST_LEXEME_MODIFIER, self::IN_ASCII_DIGIT, self::ST_NUMBER),
// IN_CHAR not allowed
array(self::ST_LEXEME_MODIFIER, self::IN_CHAR, self::ST_ERROR, $lexemeModifierErrorAction),
));
$this->addRules(array( array(self::ST_NUMBER, self::IN_WHITE_SPACE, self::ST_WHITE_SPACE),
array(self::ST_NUMBER, self::IN_SYNT_CHAR, self::ST_SYNT_LEXEME),
array(self::ST_NUMBER, self::IN_MUTABLE_CHAR, self::ST_SYNT_LEXEME),
array(self::ST_NUMBER, self::IN_LEXEME_MODIFIER, self::ST_LEXEME_MODIFIER),
// IN_ESCAPE_CHAR not allowed
array(self::ST_NUMBER, self::IN_ESCAPE_CHAR, self::ST_ERROR, $wrongNumberErrorAction),
// IN_QUOTE not allowed
array(self::ST_NUMBER, self::IN_QUOTE, self::ST_ERROR, $wrongNumberErrorAction),
array(self::ST_NUMBER, self::IN_DECIMAL_POINT, self::ST_MANTISSA),
array(self::ST_NUMBER, self::IN_ASCII_DIGIT, self::ST_NUMBER),
// IN_CHAR not allowed
array(self::ST_NUMBER, self::IN_CHAR, self::ST_ERROR, $wrongNumberErrorAction),
));
$this->addRules(array( array(self::ST_MANTISSA, self::IN_WHITE_SPACE, self::ST_WHITE_SPACE),
array(self::ST_MANTISSA, self::IN_SYNT_CHAR, self::ST_SYNT_LEXEME),
array(self::ST_MANTISSA, self::IN_MUTABLE_CHAR, self::ST_SYNT_LEXEME),
array(self::ST_MANTISSA, self::IN_LEXEME_MODIFIER, self::ST_LEXEME_MODIFIER),
// IN_ESCAPE_CHAR not allowed
array(self::ST_MANTISSA, self::IN_ESCAPE_CHAR, self::ST_ERROR, $wrongNumberErrorAction),
// IN_QUOTE not allowed
array(self::ST_MANTISSA, self::IN_QUOTE, self::ST_ERROR, $wrongNumberErrorAction),
// IN_DECIMAL_POINT not allowed
array(self::ST_MANTISSA, self::IN_DECIMAL_POINT, self::ST_ERROR, $wrongNumberErrorAction),
array(self::ST_MANTISSA, self::IN_ASCII_DIGIT, self::ST_MANTISSA),
// IN_CHAR not allowed
array(self::ST_MANTISSA, self::IN_CHAR, self::ST_ERROR, $wrongNumberErrorAction),
));
/** Actions */
$syntaxLexemeAction = new Zend_Search_Lucene_FSMAction($this, 'addQuerySyntaxLexeme');
$lexemeModifierAction = new Zend_Search_Lucene_FSMAction($this, 'addLexemeModifier');
$addLexemeAction = new Zend_Search_Lucene_FSMAction($this, 'addLexeme');
$addQuotedLexemeAction = new Zend_Search_Lucene_FSMAction($this, 'addQuotedLexeme');
$addNumberLexemeAction = new Zend_Search_Lucene_FSMAction($this, 'addNumberLexeme');
$addLexemeCharAction = new Zend_Search_Lucene_FSMAction($this, 'addLexemeChar');
/** Syntax lexeme */
$this->addEntryAction(self::ST_SYNT_LEXEME, $syntaxLexemeAction);
// Two lexemes in succession
$this->addTransitionAction(self::ST_SYNT_LEXEME, self::ST_SYNT_LEXEME, $syntaxLexemeAction);
/** Lexeme */
$this->addEntryAction(self::ST_LEXEME, $addLexemeCharAction);
$this->addTransitionAction(self::ST_LEXEME, self::ST_LEXEME, $addLexemeCharAction);
// ST_ESCAPED_CHAR => ST_LEXEME transition is covered by ST_LEXEME entry action
$this->addTransitionAction(self::ST_LEXEME, self::ST_WHITE_SPACE, $addLexemeAction);
$this->addTransitionAction(self::ST_LEXEME, self::ST_SYNT_LEXEME, $addLexemeAction);
$this->addTransitionAction(self::ST_LEXEME, self::ST_QUOTED_LEXEME, $addLexemeAction);
$this->addTransitionAction(self::ST_LEXEME, self::ST_LEXEME_MODIFIER, $addLexemeAction);
$this->addTransitionAction(self::ST_LEXEME, self::ST_NUMBER, $addLexemeAction);
$this->addTransitionAction(self::ST_LEXEME, self::ST_MANTISSA, $addLexemeAction);
/** Quoted lexeme */
// We don't need entry action (skeep quote)
$this->addTransitionAction(self::ST_QUOTED_LEXEME, self::ST_QUOTED_LEXEME, $addLexemeCharAction);
$this->addTransitionAction(self::ST_ESCAPED_QCHAR, self::ST_QUOTED_LEXEME, $addLexemeCharAction);
// Closing quote changes state to the ST_WHITE_SPACE other states are not used
$this->addTransitionAction(self::ST_QUOTED_LEXEME, self::ST_WHITE_SPACE, $addQuotedLexemeAction);
/** Lexeme modifier */
$this->addEntryAction(self::ST_LEXEME_MODIFIER, $lexemeModifierAction);
/** Number */
$this->addEntryAction(self::ST_NUMBER, $addLexemeCharAction);
$this->addEntryAction(self::ST_MANTISSA, $addLexemeCharAction);
$this->addTransitionAction(self::ST_NUMBER, self::ST_NUMBER, $addLexemeCharAction);
// ST_NUMBER => ST_MANTISSA transition is covered by ST_MANTISSA entry action
$this->addTransitionAction(self::ST_MANTISSA, self::ST_MANTISSA, $addLexemeCharAction);
$this->addTransitionAction(self::ST_NUMBER, self::ST_WHITE_SPACE, $addNumberLexemeAction);
$this->addTransitionAction(self::ST_NUMBER, self::ST_SYNT_LEXEME, $addNumberLexemeAction);
$this->addTransitionAction(self::ST_NUMBER, self::ST_LEXEME_MODIFIER, $addNumberLexemeAction);
$this->addTransitionAction(self::ST_MANTISSA, self::ST_WHITE_SPACE, $addNumberLexemeAction);
$this->addTransitionAction(self::ST_MANTISSA, self::ST_SYNT_LEXEME, $addNumberLexemeAction);
$this->addTransitionAction(self::ST_MANTISSA, self::ST_LEXEME_MODIFIER, $addNumberLexemeAction);
}
/**
* Translate input char to an input symbol of state machine
*
* @param string $char
* @return integer
*/
private function _translateInput($char)
{
if (strpos(self::QUERY_WHITE_SPACE_CHARS, $char) !== false) { return self::IN_WHITE_SPACE;
} else if (strpos(self::QUERY_SYNT_CHARS, $char) !== false) { return self::IN_SYNT_CHAR;
} else if (strpos(self::QUERY_MUTABLE_CHARS, $char) !== false) { return self::IN_MUTABLE_CHAR;
} else if (strpos(self::QUERY_LEXEMEMODIFIER_CHARS, $char) !== false) { return self::IN_LEXEME_MODIFIER;
} else if (strpos(self::QUERY_ASCIIDIGITS_CHARS, $char) !== false) { return self::IN_ASCII_DIGIT;
} else if ($char === '"' ) { return self::IN_QUOTE;
} else if ($char === '.' ) { return self::IN_DECIMAL_POINT;
} else if ($char === '\\') { return self::IN_ESCAPE_CHAR;
} else { return self::IN_CHAR;
}
}
/**
* This method is used to tokenize query string into lexemes
*
* @param string $inputString
* @param string $encoding
* @return array
* @throws Zend_Search_Lucene_Search_QueryParserException
*/
public function tokenize($inputString, $encoding)
{
$this->reset();
$this->_lexemes = array();
$this->_queryString = array();
$strLength = iconv_strlen($inputString, $encoding);
// Workaround for iconv_substr bug
$inputString .= ' ';
for ($count = 0; $count < $strLength; $count++) {
$this->_queryString[$count] = iconv_substr($inputString, $count, 1, $encoding);
}
for ($this->_queryStringPosition = 0;
$this->_queryStringPosition < count($this->_queryString);
$this->_queryStringPosition++) {
$this->process($this->_translateInput($this->_queryString[$this->_queryStringPosition]));
}
$this->process(self::IN_WHITE_SPACE);
if ($this->getState() != self::ST_WHITE_SPACE) {
throw new Zend_Search_Lucene_Search_QueryParserException('Unexpected end of query');
}
$this->_queryString = null;
return $this->_lexemes;
}
/*********************************************************************
* Actions implementation
*
* Actions affect on recognized lexemes list
*********************************************************************/
/**
* Add query syntax lexeme
*
* @throws Zend_Search_Lucene_Search_QueryParserException
*/
public function addQuerySyntaxLexeme()
{
$lexeme = $this->_queryString[$this->_queryStringPosition];
// Process two char lexemes
if (strpos(self::QUERY_DOUBLECHARLEXEME_CHARS, $lexeme) !== false) {
// increase current position in a query string
$this->_queryStringPosition++;
// check,
if ($this->_queryStringPosition == count($this->_queryString) ||
$this->_queryString[$this->_queryStringPosition] != $lexeme) {
throw new Zend_Search_Lucene_Search_QueryParserException('Two chars lexeme expected. ' . $this->_positionMsg());
}
// duplicate character
$lexeme .= $lexeme;
}
$token = new Zend_Search_Lucene_Search_QueryToken(
Zend_Search_Lucene_Search_QueryToken::TC_SYNTAX_ELEMENT,
$lexeme,
$this->_queryStringPosition);
// Skip this lexeme if it's a field indicator ':' and treat previous as 'field' instead of 'word'
if ($token->type == Zend_Search_Lucene_Search_QueryToken::TT_FIELD_INDICATOR) {
$token = array_pop($this->_lexemes);
if ($token === null || $token->type != Zend_Search_Lucene_Search_QueryToken::TT_WORD) {
throw new Zend_Search_Lucene_Search_QueryParserException('Field mark \':\' must follow field name. ' . $this->_positionMsg());
}
$token->type = Zend_Search_Lucene_Search_QueryToken::TT_FIELD;
}
$this->_lexemes[] = $token;
}
/**
* Add lexeme modifier
*/
public function addLexemeModifier()
{
$this->_lexemes[] = new Zend_Search_Lucene_Search_QueryToken(
Zend_Search_Lucene_Search_QueryToken::TC_SYNTAX_ELEMENT,
$this->_queryString[$this->_queryStringPosition],
$this->_queryStringPosition);
}
/**
* Add lexeme
*/
public function addLexeme()
{
$this->_lexemes[] = new Zend_Search_Lucene_Search_QueryToken(
Zend_Search_Lucene_Search_QueryToken::TC_WORD,
$this->_currentLexeme,
$this->_queryStringPosition - 1);
$this->_currentLexeme = '';
}
/**
* Add quoted lexeme
*/
public function addQuotedLexeme()
{
$this->_lexemes[] = new Zend_Search_Lucene_Search_QueryToken(
Zend_Search_Lucene_Search_QueryToken::TC_PHRASE,
$this->_currentLexeme,
$this->_queryStringPosition);
$this->_currentLexeme = '';
}
/**
* Add number lexeme
*/
public function addNumberLexeme()
{
$this->_lexemes[] = new Zend_Search_Lucene_Search_QueryToken(
Zend_Search_Lucene_Search_QueryToken::TC_NUMBER,
$this->_currentLexeme,
$this->_queryStringPosition - 1);
$this->_currentLexeme = '';
}
/**
* Extend lexeme by one char
*/
public function addLexemeChar()
{
$this->_currentLexeme .= $this->_queryString[$this->_queryStringPosition];
}
/**
* Position message
*
* @return string
*/
private function _positionMsg()
{
return 'Position is ' . $this->_queryStringPosition . '.';
}
/*********************************************************************
* Syntax errors actions
*********************************************************************/
public function lexModifierErrException()
{
throw new Zend_Search_Lucene_Search_QueryParserException('Lexeme modifier character can be followed only by number, white space or query syntax element. ' . $this->_positionMsg());
}
public function quoteWithinLexemeErrException()
{
throw new Zend_Search_Lucene_Search_QueryParserException('Quote within lexeme must be escaped by \'\\\' char. ' . $this->_positionMsg());
}
public function wrongNumberErrException()
{
throw new Zend_Search_Lucene_Search_QueryParserException('Wrong number syntax.' . $this->_positionMsg());
}
}
+456 -77
View File
@@ -15,128 +15,507 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Search_QueryTokenizer */
require_once 'Zend/Search/Lucene/Search/QueryTokenizer.php';
/** Zend_Search_Lucene_Index_Term */
require_once 'Zend/Search/Lucene/Index/Term.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/Term.php';
/** Zend_Search_Lucene_Search_Query_Term */
require_once 'Zend/Search/Lucene/Search/Query/Term.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Query/Term.php';
/** Zend_Search_Lucene_Search_Query_MultiTerm */
require_once 'Zend/Search/Lucene/Search/Query/MultiTerm.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Query/MultiTerm.php';
/** Zend_Search_Lucene_Search_Query_Boolean */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Query/Boolean.php';
/** Zend_Search_Lucene_Search_Query_Phrase */
require_once 'Zend/Search/Lucene/Search/Query/Phrase.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Query/Phrase.php';
/** Zend_Search_Lucene_Search_Query_Empty */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Query/Empty.php';
/** Zend_Search_Lucene_Search_QueryLexer */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryLexer.php';
/** Zend_Search_Lucene_Search_QueryParserContext */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryParserContext.php';
/** Zend_Search_Lucene_FSM */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/FSM.php';
/** Zend_Search_Lucene_Exception */
require_once 'Zend/Search/Lucene/Exception.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/** Zend_Search_Lucene_Search_QueryParserException */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryParserException.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_QueryParser
class Zend_Search_Lucene_Search_QueryParser extends Zend_Search_Lucene_FSM
{
/**
* Parser instance
*
* @var Zend_Search_Lucene_Search_QueryParser
*/
private static $_instance = null;
/**
* Parses a query string, returning a Zend_Search_Lucene_Search_Query
* Query lexer
*
* @var Zend_Search_Lucene_Search_QueryLexer
*/
private $_lexer;
/**
* Tokens list
* Array of Zend_Search_Lucene_Search_QueryToken objects
*
* @var array
*/
private $_tokens;
/**
* Current token
*
* @var integer|string
*/
private $_currentToken;
/**
* Last token
*
* It can be processed within FSM states, but this addirional state simplifies FSM
*
* @var Zend_Search_Lucene_Search_QueryToken
*/
private $_lastToken = null;
/**
* Range query first term
*
* @var string
*/
private $_rqFirstTerm = null;
/**
* Current query parser context
*
* @var Zend_Search_Lucene_Search_QueryParserContext
*/
private $_context;
/**
* Context stack
*
* @var array
*/
private $_contextStack;
/**
* Query string encoding
*
* @var string
*/
private $_encoding;
/**
* Query string default encoding
*
* @var string
*/
private $_defaultEncoding = '';
/**
* Boolean operators constants
*/
const B_OR = 0;
const B_AND = 1;
/**
* Default boolean queries operator
*
* @var integer
*/
private $_defaultOperator = self::B_OR;
/** Query parser State Machine states */
const ST_COMMON_QUERY_ELEMENT = 0; // Terms, phrases, operators
const ST_CLOSEDINT_RQ_START = 1; // Range query start (closed interval) - '['
const ST_CLOSEDINT_RQ_FIRST_TERM = 2; // First term in '[term1 to term2]' construction
const ST_CLOSEDINT_RQ_TO_TERM = 3; // 'TO' lexeme in '[term1 to term2]' construction
const ST_CLOSEDINT_RQ_LAST_TERM = 4; // Second term in '[term1 to term2]' construction
const ST_CLOSEDINT_RQ_END = 5; // Range query end (closed interval) - ']'
const ST_OPENEDINT_RQ_START = 6; // Range query start (opened interval) - '{'
const ST_OPENEDINT_RQ_FIRST_TERM = 7; // First term in '{term1 to term2}' construction
const ST_OPENEDINT_RQ_TO_TERM = 8; // 'TO' lexeme in '{term1 to term2}' construction
const ST_OPENEDINT_RQ_LAST_TERM = 9; // Second term in '{term1 to term2}' construction
const ST_OPENEDINT_RQ_END = 10; // Range query end (opened interval) - '}'
/**
* Parser constructor
*/
public function __construct()
{
parent::__construct(array(self::ST_COMMON_QUERY_ELEMENT,
self::ST_CLOSEDINT_RQ_START,
self::ST_CLOSEDINT_RQ_FIRST_TERM,
self::ST_CLOSEDINT_RQ_TO_TERM,
self::ST_CLOSEDINT_RQ_LAST_TERM,
self::ST_CLOSEDINT_RQ_END,
self::ST_OPENEDINT_RQ_START,
self::ST_OPENEDINT_RQ_FIRST_TERM,
self::ST_OPENEDINT_RQ_TO_TERM,
self::ST_OPENEDINT_RQ_LAST_TERM,
self::ST_OPENEDINT_RQ_END
),
Zend_Search_Lucene_Search_QueryToken::getTypes());
$this->addRules(
array(array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_WORD, self::ST_COMMON_QUERY_ELEMENT),
array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_PHRASE, self::ST_COMMON_QUERY_ELEMENT),
array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_FIELD, self::ST_COMMON_QUERY_ELEMENT),
array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_REQUIRED, self::ST_COMMON_QUERY_ELEMENT),
array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_PROHIBITED, self::ST_COMMON_QUERY_ELEMENT),
array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_FUZZY_PROX_MARK, self::ST_COMMON_QUERY_ELEMENT),
array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_BOOSTING_MARK, self::ST_COMMON_QUERY_ELEMENT),
array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_RANGE_INCL_START, self::ST_CLOSEDINT_RQ_START),
array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_RANGE_EXCL_START, self::ST_OPENEDINT_RQ_START),
array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_SUBQUERY_START, self::ST_COMMON_QUERY_ELEMENT),
array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_SUBQUERY_END, self::ST_COMMON_QUERY_ELEMENT),
array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_AND_LEXEME, self::ST_COMMON_QUERY_ELEMENT),
array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_OR_LEXEME, self::ST_COMMON_QUERY_ELEMENT),
array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_NOT_LEXEME, self::ST_COMMON_QUERY_ELEMENT),
array(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_NUMBER, self::ST_COMMON_QUERY_ELEMENT)
));
$this->addRules(
array(array(self::ST_CLOSEDINT_RQ_START, Zend_Search_Lucene_Search_QueryToken::TT_WORD, self::ST_CLOSEDINT_RQ_FIRST_TERM),
array(self::ST_CLOSEDINT_RQ_FIRST_TERM, Zend_Search_Lucene_Search_QueryToken::TT_TO_LEXEME, self::ST_CLOSEDINT_RQ_TO_TERM),
array(self::ST_CLOSEDINT_RQ_TO_TERM, Zend_Search_Lucene_Search_QueryToken::TT_WORD, self::ST_CLOSEDINT_RQ_LAST_TERM),
array(self::ST_CLOSEDINT_RQ_LAST_TERM, Zend_Search_Lucene_Search_QueryToken::TT_RANGE_INCL_END, self::ST_COMMON_QUERY_ELEMENT)
));
$this->addRules(
array(array(self::ST_OPENEDINT_RQ_START, Zend_Search_Lucene_Search_QueryToken::TT_WORD, self::ST_OPENEDINT_RQ_FIRST_TERM),
array(self::ST_OPENEDINT_RQ_FIRST_TERM, Zend_Search_Lucene_Search_QueryToken::TT_TO_LEXEME, self::ST_OPENEDINT_RQ_TO_TERM),
array(self::ST_OPENEDINT_RQ_TO_TERM, Zend_Search_Lucene_Search_QueryToken::TT_WORD, self::ST_OPENEDINT_RQ_LAST_TERM),
array(self::ST_OPENEDINT_RQ_LAST_TERM, Zend_Search_Lucene_Search_QueryToken::TT_RANGE_EXCL_END, self::ST_COMMON_QUERY_ELEMENT)
));
$addTermEntryAction = new Zend_Search_Lucene_FSMAction($this, 'addTermEntry');
$addPhraseEntryAction = new Zend_Search_Lucene_FSMAction($this, 'addPhraseEntry');
$setFieldAction = new Zend_Search_Lucene_FSMAction($this, 'setField');
$setSignAction = new Zend_Search_Lucene_FSMAction($this, 'setSign');
$setFuzzyProxAction = new Zend_Search_Lucene_FSMAction($this, 'processFuzzyProximityModifier');
$processModifierParameterAction = new Zend_Search_Lucene_FSMAction($this, 'processModifierParameter');
$subqueryStartAction = new Zend_Search_Lucene_FSMAction($this, 'subqueryStart');
$subqueryEndAction = new Zend_Search_Lucene_FSMAction($this, 'subqueryEnd');
$logicalOperatorAction = new Zend_Search_Lucene_FSMAction($this, 'logicalOperator');
$openedRQFirstTermAction = new Zend_Search_Lucene_FSMAction($this, 'openedRQFirstTerm');
$openedRQLastTermAction = new Zend_Search_Lucene_FSMAction($this, 'openedRQLastTerm');
$closedRQFirstTermAction = new Zend_Search_Lucene_FSMAction($this, 'closedRQFirstTerm');
$closedRQLastTermAction = new Zend_Search_Lucene_FSMAction($this, 'closedRQLastTerm');
$this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_WORD, $addTermEntryAction);
$this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_PHRASE, $addPhraseEntryAction);
$this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_FIELD, $setFieldAction);
$this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_REQUIRED, $setSignAction);
$this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_PROHIBITED, $setSignAction);
$this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_FUZZY_PROX_MARK, $setFuzzyProxAction);
$this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_NUMBER, $processModifierParameterAction);
$this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_SUBQUERY_START, $subqueryStartAction);
$this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_SUBQUERY_END, $subqueryEndAction);
$this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_AND_LEXEME, $logicalOperatorAction);
$this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_OR_LEXEME, $logicalOperatorAction);
$this->addInputAction(self::ST_COMMON_QUERY_ELEMENT, Zend_Search_Lucene_Search_QueryToken::TT_NOT_LEXEME, $logicalOperatorAction);
$this->addEntryAction(self::ST_OPENEDINT_RQ_FIRST_TERM, $openedRQFirstTermAction);
$this->addEntryAction(self::ST_OPENEDINT_RQ_LAST_TERM, $openedRQLastTermAction);
$this->addEntryAction(self::ST_CLOSEDINT_RQ_FIRST_TERM, $closedRQFirstTermAction);
$this->addEntryAction(self::ST_CLOSEDINT_RQ_LAST_TERM, $closedRQLastTermAction);
$this->_lexer = new Zend_Search_Lucene_Search_QueryLexer();
}
/**
* Set query string default encoding
*
* @param string $encoding
*/
public static function setDefaultEncoding($encoding)
{
if (self::$_instance === null) {
self::$_instance = new Zend_Search_Lucene_Search_QueryParser();
}
self::$_instance->_defaultEncoding = $encoding;
}
/**
* Get query string default encoding
*
* @return string
*/
public static function getDefaultEncoding()
{
if (self::$_instance === null) {
self::$_instance = new Zend_Search_Lucene_Search_QueryParser();
}
return self::$_instance->_defaultEncoding;
}
/**
* Set default boolean operator
*
* @param integer $operator
*/
public static function setDefaultOperator($operator)
{
if (self::$_instance === null) {
self::$_instance = new Zend_Search_Lucene_Search_QueryParser();
}
self::$_instance->_defaultOperator = $operator;
}
/**
* Get default boolean operator
*
* @return integer
*/
public static function getDefaultOperator()
{
if (self::$_instance === null) {
self::$_instance = new Zend_Search_Lucene_Search_QueryParser();
}
return self::$_instance->_defaultOperator;
}
/**
* Parses a query string
*
* @param string $strQuery
* @param string $encoding
* @return Zend_Search_Lucene_Search_Query
* @throws Zend_Search_Lucene_Search_QueryParserException
*/
static public function parse($strQuery)
public static function parse($strQuery, $encoding = null)
{
$tokens = new Zend_Search_Lucene_Search_QueryTokenizer($strQuery);
if (self::$_instance === null) {
self::$_instance = new Zend_Search_Lucene_Search_QueryParser();
}
self::$_instance->_encoding = ($encoding !== null) ? $encoding : self::$_instance->_defaultEncoding;
self::$_instance->_lastToken = null;
self::$_instance->_context = new Zend_Search_Lucene_Search_QueryParserContext(self::$_instance->_encoding);
self::$_instance->_contextStack = array();
self::$_instance->_tokens = self::$_instance->_lexer->tokenize($strQuery, self::$_instance->_encoding);
// Empty query
if (!$tokens->count()) {
throw new Zend_Search_Lucene_Exception('Syntax error: query string cannot be empty.');
if (count(self::$_instance->_tokens) == 0) {
return new Zend_Search_Lucene_Search_Query_Empty();
}
// Term query
if ($tokens->count() == 1) {
if ($tokens->current()->type == Zend_Search_Lucene_Search_QueryToken::TOKTYPE_WORD) {
return new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term($tokens->current()->text, 'contents'));
} else {
throw new Zend_Search_Lucene_Exception('Syntax error: query string must contain at least one word.');
foreach (self::$_instance->_tokens as $token) {
try {
self::$_instance->_currentToken = $token;
self::$_instance->process($token->type);
self::$_instance->_lastToken = $token;
} catch (Exception $e) {
if (strpos($e->getMessage(), 'There is no any rule for') !== false) {
throw new Zend_Search_Lucene_Search_QueryParserException( 'Syntax error at char position ' . $token->position . '.' );
}
throw $e;
}
}
/**
* MultiTerm Query
*
* Process each token that was returned by the tokenizer.
*/
$terms = array();
$signs = array();
$prevToken = null;
$openBrackets = 0;
$field = 'contents';
foreach ($tokens as $token) {
switch ($token->type) {
case Zend_Search_Lucene_Search_QueryToken::TOKTYPE_WORD:
$terms[] = new Zend_Search_Lucene_Index_Term($token->text, $field);
$field = 'contents';
if ($prevToken !== null &&
$prevToken->type == Zend_Search_Lucene_Search_QueryToken::TOKTYPE_SIGN) {
if ($prevToken->text == "+") {
$signs[] = true;
} else {
$signs[] = false;
}
} else {
$signs[] = null;
}
break;
case Zend_Search_Lucene_Search_QueryToken::TOKTYPE_SIGN:
if ($prevToken !== null &&
$prevToken->type == Zend_Search_Lucene_Search_QueryToken::TOKTYPE_SIGN) {
throw new Zend_Search_Lucene_Exception('Syntax error: sign operator must be followed by a word.');
}
break;
case Zend_Search_Lucene_Search_QueryToken::TOKTYPE_FIELD:
$field = $token->text;
// let previous token to be signed as next $prevToken
$token = $prevToken;
break;
case Zend_Search_Lucene_Search_QueryToken::TOKTYPE_BRACKET:
$token->text=='(' ? $openBrackets++ : $openBrackets--;
}
$prevToken = $token;
if (count(self::$_instance->_contextStack) != 0) {
throw new Zend_Search_Lucene_Search_QueryParserException('Syntax Error: mismatched parentheses, every opening must have closing.' );
}
// Finish up parsing: check the last token in the query for an opening sign or parenthesis.
if ($prevToken->type == Zend_Search_Lucene_Search_QueryToken::TOKTYPE_SIGN) {
throw new Zend_Search_Lucene_Exception('Syntax Error: sign operator must be followed by a word.');
return self::$_instance->_context->getQuery();
}
/*********************************************************************
* Actions implementation
*
* Actions affect on recognized lexemes list
*********************************************************************/
/**
* Add term to a query
*/
public function addTermEntry()
{
$entry = new Zend_Search_Lucene_Search_QueryEntry_Term($this->_currentToken->text, $this->_context->getField());
$this->_context->addEntry($entry);
}
/**
* Add phrase to a query
*/
public function addPhraseEntry()
{
$entry = new Zend_Search_Lucene_Search_QueryEntry_Phrase($this->_currentToken->text, $this->_context->getField());
$this->_context->addEntry($entry);
}
/**
* Set entry field
*/
public function setField()
{
$this->_context->setNextEntryField($this->_currentToken->text);
}
/**
* Set entry sign
*/
public function setSign()
{
$this->_context->setNextEntrySign($this->_currentToken->type);
}
/**
* Process fuzzy search/proximity modifier - '~'
*/
public function processFuzzyProximityModifier()
{
$this->_context->processFuzzyProximityModifier();
}
/**
* Process modifier parameter
*
* @throws Zend_Search_Lucene_Exception
*/
public function processModifierParameter()
{
if ($this->_lastToken === null) {
throw new Zend_Search_Lucene_Search_QueryParserException('Lexeme modifier parameter must follow lexeme modifier. Char position 0.' );
}
// Finish up parsing: check that every opening bracket has a matching closing bracket.
if ($openBrackets != 0) {
throw new Zend_Search_Lucene_Exception('Syntax Error: mismatched parentheses, every opening must have closing.');
}
switch ($this->_lastToken->type) {
case Zend_Search_Lucene_Search_QueryToken::TT_FUZZY_PROX_MARK:
$this->_context->processFuzzyProximityModifier($this->_currentToken->text);
break;
case Zend_Search_Lucene_Search_QueryToken::TT_BOOSTING_MARK:
$this->_context->boost($this->_currentToken->text);
break;
switch (count($terms)) {
case 0:
throw new Zend_Search_Lucene_Exception('Syntax error: bad term count.');
case 1:
return new Zend_Search_Lucene_Search_Query_Term($terms[0],$signs[0] !== false);
default:
return new Zend_Search_Lucene_Search_Query_MultiTerm($terms,$signs);
// It's not a user input exception
throw new Zend_Search_Lucene_Exception('Lexeme modifier parameter must follow lexeme modifier. Char position .' );
}
}
/**
* Start subquery
*/
public function subqueryStart()
{
$this->_contextStack[] = $this->_context;
$this->_context = new Zend_Search_Lucene_Search_QueryParserContext($this->_encoding, $this->_context->getField());
}
/**
* End subquery
*/
public function subqueryEnd()
{
if (count($this->_contextStack) == 0) {
throw new Zend_Search_Lucene_Search_QueryParserException('Syntax Error: mismatched parentheses, every opening must have closing. Char position ' . $this->_currentToken->position . '.' );
}
$query = $this->_context->getQuery();
$this->_context = array_pop($this->_contextStack);
$this->_context->addEntry(new Zend_Search_Lucene_Search_QueryEntry_Subquery($query));
}
/**
* Process logical operator
*/
public function logicalOperator()
{
$this->_context->addLogicalOperator($this->_currentToken->type);
}
/**
* Process first range query term (opened interval)
*/
public function openedRQFirstTerm()
{
$this->_rqFirstTerm = $this->_currentToken->text;
}
/**
* Process last range query term (opened interval)
*
* @throws Zend_Search_Lucene_Search_QueryParserException
*/
public function openedRQLastTerm()
{
throw new Zend_Search_Lucene_Search_QueryParserException('Range queries are not supported yet.');
// $firstTerm = new Zend_Search_Lucene_Index_Term($this->_rqFirstTerm, $this->_context->getField());
// $lastTerm = new Zend_Search_Lucene_Index_Term($this->_currentToken->text, $this->_context->getField());
// $query = new Zend_Search_Lucene_Search_Query_Range($firstTerm, $lastTerm, false);
// $this->_context->addentry($query);
}
/**
* Process first range query term (closed interval)
*/
public function closedRQFirstTerm()
{
$this->_rqFirstTerm = $this->_currentToken->text;
}
/**
* Process last range query term (closed interval)
*
* @throws Zend_Search_Lucene_Search_QueryParserException
*/
public function closedRQLastTerm()
{
throw new Zend_Search_Lucene_Search_QueryParserException('Range queries are not supported yet.');
// $firstTerm = new Zend_Search_Lucene_Index_Term($this->_rqFirstTerm, $this->_context->getField());
// $lastTerm = new Zend_Search_Lucene_Index_Term($this->_currentToken->text, $this->_context->getField());
// $query = new Zend_Search_Lucene_Search_Query_Range($firstTerm, $lastTerm, true);
// $this->_context->addentry($query);
}
}
@@ -0,0 +1,416 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_FSM */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/FSM.php';
/** Zend_Search_Lucene_Index_Term */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Index/Term.php';
/** Zend_Search_Lucene_Search_QueryToken */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryToken.php';
/** Zend_Search_Lucene_Search_Query_Term */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Query/Term.php';
/** Zend_Search_Lucene_Search_Query_MultiTerm */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Query/MultiTerm.php';
/** Zend_Search_Lucene_Search_Query_Boolean */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Query/Boolean.php';
/** Zend_Search_Lucene_Search_Query_Phrase */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Query/Phrase.php';
/** Zend_Search_Lucene_Exception */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/** Zend_Search_Lucene_Search_QueryParserException */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryParserException.php';
/** Zend_Search_Lucene_Search_BooleanExpressionRecognizer */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/BooleanExpressionRecognizer.php';
/** Zend_Search_Lucene_Search_QueryEntry */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryEntry.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_QueryParserContext
{
/**
* Default field for the context.
*
* null means, that term should be searched through all fields
* Zend_Search_Lucene_Search_Query::rewriteQuery($index) transletes such queries to several
*
* @var string|null
*/
private $_defaultField;
/**
* Field specified for next entry
*
* @var string
*/
private $_nextEntryField = null;
/**
* True means, that term is required.
* False means, that term is prohibited.
* null means, that term is neither prohibited, nor required
*
* @var boolean
*/
private $_nextEntrySign = null;
/**
* Entries grouping mode
*/
const GM_SIGNS = 0; // Signs mode: '+term1 term2 -term3 +(subquery1) -(subquery2)'
const GM_BOOLEAN = 1; // Boolean operators mode: 'term1 and term2 or (subquery1) and not (subquery2)'
/**
* Grouping mode
*
* @var integer
*/
private $_mode = null;
/**
* Entries signs.
* Used in GM_SIGNS grouping mode
*
* @var arrays
*/
private $_signs = array();
/**
* Query entries
* Each entry is a Zend_Search_Lucene_Search_QueryEntry object or
* boolean operator (Zend_Search_Lucene_Search_QueryToken class constant)
*
* @var array
*/
private $_entries = array();
/**
* Query string encoding
*
* @var string
*/
private $_encoding;
/**
* Context object constructor
*
* @param string $encoding
* @param string|null $defaultField
*/
public function __construct($encoding, $defaultField = null)
{
$this->_encoding = $encoding;
$this->_defaultField = $defaultField;
}
/**
* Get context default field
*
* @return string|null
*/
public function getField()
{
return ($this->_nextEntryField !== null) ? $this->_nextEntryField : $this->_defaultField;
}
/**
* Set field for next entry
*
* @param string $field
*/
public function setNextEntryField($field)
{
$this->_nextEntryField = $field;
}
/**
* Set sign for next entry
*
* @param integer $sign
* @throws Zend_Search_Lucene_Exception
*/
public function setNextEntrySign($sign)
{
if ($this->_mode === self::GM_BOOLEAN) {
throw new Zend_Search_Lucene_Search_QueryParserException('It\'s not allowed to mix boolean and signs styles in the same subquery.');
}
$this->_mode = self::GM_SIGNS;
if ($sign == Zend_Search_Lucene_Search_QueryToken::TT_REQUIRED) {
$this->_nextEntrySign = true;
} else if ($sign == Zend_Search_Lucene_Search_QueryToken::TT_PROHIBITED) {
$this->_nextEntrySign = false;
} else {
throw new Zend_Search_Lucene_Exception('Unrecognized sign type.');
}
}
/**
* Add entry to a query
*
* @param Zend_Search_Lucene_Search_QueryEntry $entry
*/
public function addEntry(Zend_Search_Lucene_Search_QueryEntry $entry)
{
if ($this->_mode !== self::GM_BOOLEAN) {
$this->_signs[] = $this->_nextEntrySign;
}
$this->_entries[] = $entry;
$this->_nextEntryField = null;
$this->_nextEntrySign = null;
}
/**
* Process fuzzy search or proximity search modifier
*
* @throws Zend_Search_Lucene_Search_QueryParserException
*/
public function processFuzzyProximityModifier($parameter = null)
{
// Check, that modifier has came just after word or phrase
if ($this->_nextEntryField !== null || $this->_nextEntrySign !== null) {
throw new Zend_Search_Lucene_Search_QueryParserException('\'~\' modifier must follow word or phrase.');
}
$lastEntry = array_pop($this->_entries);
if (!$lastEntry instanceof Zend_Search_Lucene_Search_QueryEntry) {
// there are no entries or last entry is boolean operator
throw new Zend_Search_Lucene_Search_QueryParserException('\'~\' modifier must follow word or phrase.');
}
$lastEntry->processFuzzyProximityModifier($parameter);
$this->_entries[] = $lastEntry;
}
/**
* Set boost factor to the entry
*
* @param float $boostFactor
*/
public function boost($boostFactor)
{
// Check, that modifier has came just after word or phrase
if ($this->_nextEntryField !== null || $this->_nextEntrySign !== null) {
throw new Zend_Search_Lucene_Search_QueryParserException('\'^\' modifier must follow word, phrase or subquery.');
}
$lastEntry = array_pop($this->_entries);
if (!$lastEntry instanceof Zend_Search_Lucene_Search_QueryEntry) {
// there are no entries or last entry is boolean operator
throw new Zend_Search_Lucene_Search_QueryParserException('\'^\' modifier must follow word, phrase or subquery.');
}
$lastEntry->boost($boostFactor);
$this->_entries[] = $lastEntry;
}
/**
* Process logical operator
*
* @param integer $operator
*/
public function addLogicalOperator($operator)
{
if ($this->_mode === self::GM_SIGNS) {
throw new Zend_Search_Lucene_Search_QueryParserException('It\'s not allowed to mix boolean and signs styles in the same subquery.');
}
$this->_mode = self::GM_BOOLEAN;
$this->_entries[] = $operator;
}
/**
* Generate 'signs style' query from the context
* '+term1 term2 -term3 +(<subquery1>) ...'
*
* @return Zend_Search_Lucene_Search_Query
*/
public function _signStyleExpressionQuery()
{
$query = new Zend_Search_Lucene_Search_Query_Boolean();
if (Zend_Search_Lucene_Search_QueryParser::getDefaultOperator() == Zend_Search_Lucene_Search_QueryParser::B_AND) {
$defaultSign = true; // required
} else {
// Zend_Search_Lucene_Search_QueryParser::B_OR
$defaultSign = null; // optional
}
foreach ($this->_entries as $entryId => $entry) {
$sign = ($this->_signs[$entryId] !== null) ? $this->_signs[$entryId] : $defaultSign;
$query->addSubquery($entry->getQuery($this->_encoding), $sign);
}
return $query;
}
/**
* Generate 'boolean style' query from the context
* 'term1 and term2 or term3 and (<subquery1>) and not (<subquery2>)'
*
* @return Zend_Search_Lucene_Search_Query
* @throws Zend_Search_Lucene
*/
private function _booleanExpressionQuery()
{
/**
* We treat each level of an expression as a boolean expression in
* a Disjunctive Normal Form
*
* AND operator has higher precedence than OR
*
* Thus logical query is a disjunction of one or more conjunctions of
* one or more query entries
*/
$expressionRecognizer = new Zend_Search_Lucene_Search_BooleanExpressionRecognizer();
try {
foreach ($this->_entries as $entry) {
if ($entry instanceof Zend_Search_Lucene_Search_QueryEntry) {
$expressionRecognizer->processLiteral($entry);
} else {
switch ($entry) {
case Zend_Search_Lucene_Search_QueryToken::TT_AND_LEXEME:
$expressionRecognizer->processOperator(Zend_Search_Lucene_Search_BooleanExpressionRecognizer::IN_AND_OPERATOR);
break;
case Zend_Search_Lucene_Search_QueryToken::TT_OR_LEXEME:
$expressionRecognizer->processOperator(Zend_Search_Lucene_Search_BooleanExpressionRecognizer::IN_OR_OPERATOR);
break;
case Zend_Search_Lucene_Search_QueryToken::TT_NOT_LEXEME:
$expressionRecognizer->processOperator(Zend_Search_Lucene_Search_BooleanExpressionRecognizer::IN_NOT_OPERATOR);
break;
default:
throw new Zend_Search_Lucene('Boolean expression error. Unknown operator type.');
}
}
}
$conjuctions = $expressionRecognizer->finishExpression();
} catch (Zend_Search_Exception $e) {
// throw new Zend_Search_Lucene_Search_QueryParserException('Boolean expression error. Error message: \'' .
// $e->getMessage() . '\'.' );
// It's query syntax error message and it should be user friendly. So FSM message is omitted
throw new Zend_Search_Lucene_Search_QueryParserException('Boolean expression error.');
}
// Remove 'only negative' conjunctions
foreach ($conjuctions as $conjuctionId => $conjuction) {
$nonNegativeEntryFound = false;
foreach ($conjuction as $conjuctionEntry) {
if ($conjuctionEntry[1]) {
$nonNegativeEntryFound = true;
break;
}
}
if (!$nonNegativeEntryFound) {
unset($conjuctions[$conjuctionId]);
}
}
$subqueries = array();
foreach ($conjuctions as $conjuction) {
// Check, if it's a one term conjuction
if (count($conjuction) == 1) {
$subqueries[] = $conjuction[0][0]->getQuery($this->_encoding);
} else {
$subquery = new Zend_Search_Lucene_Search_Query_Boolean();
foreach ($conjuction as $conjuctionEntry) {
$subquery->addSubquery($conjuctionEntry[0]->getQuery($this->_encoding), $conjuctionEntry[1]);
}
$subqueries[] = $subquery;
}
}
if (count($subqueries) == 0) {
return new Zend_Search_Lucene_Search_Query_Empty();
}
if (count($subqueries) == 1) {
return $subqueries[0];
}
$query = new Zend_Search_Lucene_Search_Query_Boolean();
foreach ($subqueries as $subquery) {
// Non-requirered entry/subquery
$query->addSubquery($subquery);
}
return $query;
}
/**
* Generate query from current context
*
* @return Zend_Search_Lucene_Search_Query
*/
public function getQuery()
{
if ($this->_mode === self::GM_BOOLEAN) {
return $this->_booleanExpressionQuery();
} else {
return $this->_signStyleExpressionQuery();
}
}
}
@@ -0,0 +1,40 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/**
* Zend_Search_Lucene base exception
*/
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*
* Special exception type, which may be used to intercept wrong user input
*/
class Zend_Search_Lucene_Search_QueryParserException extends Zend_Search_Lucene_Exception
{}
+160 -36
View File
@@ -15,46 +15,86 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Exception */
require_once 'Zend/Search/Lucene/Exception.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_QueryToken
{
/**
* Token type Word.
* Token types.
*/
const TOKTYPE_WORD = 0;
const TT_WORD = 0; // Word
const TT_PHRASE = 1; // Phrase (one or several quoted words)
const TT_FIELD = 2; // Field name in 'field:word', field:<phrase> or field:(<subquery>) pairs
const TT_FIELD_INDICATOR = 3; // ':'
const TT_REQUIRED = 4; // '+'
const TT_PROHIBITED = 5; // '-'
const TT_FUZZY_PROX_MARK = 6; // '~'
const TT_BOOSTING_MARK = 7; // '^'
const TT_RANGE_INCL_START = 8; // '['
const TT_RANGE_INCL_END = 9; // ']'
const TT_RANGE_EXCL_START = 10; // '{'
const TT_RANGE_EXCL_END = 11; // '}'
const TT_SUBQUERY_START = 12; // '('
const TT_SUBQUERY_END = 13; // ')'
const TT_AND_LEXEME = 14; // 'AND' or 'and'
const TT_OR_LEXEME = 15; // 'OR' or 'or'
const TT_NOT_LEXEME = 16; // 'NOT' or 'not'
const TT_TO_LEXEME = 17; // 'TO' or 'to'
const TT_NUMBER = 18; // Number, like: 10, 0.8, .64, ....
/**
* Token type Field.
* Field indicator in 'field:word' pair
* Returns all possible lexeme types.
* It's used for syntax analyzer state machine initialization
*
* @return array
*/
const TOKTYPE_FIELD = 1;
public static function getTypes()
{
return array( self::TT_WORD,
self::TT_PHRASE,
self::TT_FIELD,
self::TT_FIELD_INDICATOR,
self::TT_REQUIRED,
self::TT_PROHIBITED,
self::TT_FUZZY_PROX_MARK,
self::TT_BOOSTING_MARK,
self::TT_RANGE_INCL_START,
self::TT_RANGE_INCL_END,
self::TT_RANGE_EXCL_START,
self::TT_RANGE_EXCL_END,
self::TT_SUBQUERY_START,
self::TT_SUBQUERY_END,
self::TT_AND_LEXEME,
self::TT_OR_LEXEME,
self::TT_NOT_LEXEME,
self::TT_TO_LEXEME,
self::TT_NUMBER
);
}
/**
* Token type Sign.
* '+' (required) or '-' (absentee) sign
* TokenCategories
*/
const TOKTYPE_SIGN = 2;
/**
* Token type Bracket.
* '(' or ')'
*/
const TOKTYPE_BRACKET = 3;
const TC_WORD = 0; // Word
const TC_PHRASE = 1; // Phrase (one or several quoted words)
const TC_NUMBER = 2; // Nubers, which are used with syntax elements. Ex. roam~0.8
const TC_SYNTAX_ELEMENT = 3; // + - ( ) [ ] { } ! || && ~ ^
/**
@@ -71,34 +111,118 @@ class Zend_Search_Lucene_Search_QueryToken
*/
public $text;
/**
* Token position within query.
*
* @var integer
*/
public $position;
/**
* IndexReader constructor needs token type and token text as a parameters.
*
* @param $tokType integer
* @param $tokText string
* @param integer $tokenCategory
* @param string $tokText
* @param integer $position
*/
public function __construct($tokType, $tokText)
public function __construct($tokenCategory, $tokenText, $position)
{
switch ($tokType) {
case self::TOKTYPE_BRACKET:
// fall through to the next case
case self::TOKTYPE_FIELD:
// fall through to the next case
case self::TOKTYPE_SIGN:
// fall through to the next case
case self::TOKTYPE_WORD:
$this->text = $tokenText;
$this->position = $position + 1; // Start from 1
switch ($tokenCategory) {
case self::TC_WORD:
if ( strtolower($tokenText) == 'and') {
$this->type = self::TT_AND_LEXEME;
} else if (strtolower($tokenText) == 'or') {
$this->type = self::TT_OR_LEXEME;
} else if (strtolower($tokenText) == 'not') {
$this->type = self::TT_NOT_LEXEME;
} else if (strtolower($tokenText) == 'to') {
$this->type = self::TT_TO_LEXEME;
} else {
$this->type = self::TT_WORD;
}
break;
case self::TC_PHRASE:
$this->type = self::TT_PHRASE;
break;
case self::TC_NUMBER:
$this->type = self::TT_NUMBER;
break;
case self::TC_SYNTAX_ELEMENT:
switch ($tokenText) {
case ':':
$this->type = self::TT_FIELD_INDICATOR;
break;
case '+':
$this->type = self::TT_REQUIRED;
break;
case '-':
$this->type = self::TT_PROHIBITED;
break;
case '~':
$this->type = self::TT_FUZZY_PROX_MARK;
break;
case '^':
$this->type = self::TT_BOOSTING_MARK;
break;
case '[':
$this->type = self::TT_RANGE_INCL_START;
break;
case ']':
$this->type = self::TT_RANGE_INCL_END;
break;
case '{':
$this->type = self::TT_RANGE_EXCL_START;
break;
case '}':
$this->type = self::TT_RANGE_EXCL_END;
break;
case '(':
$this->type = self::TT_SUBQUERY_START;
break;
case ')':
$this->type = self::TT_SUBQUERY_END;
break;
case '!':
$this->type = self::TT_NOT_LEXEME;
break;
case '&&':
$this->type = self::TT_AND_LEXEME;
break;
case '||':
$this->type = self::TT_OR_LEXEME;
break;
default:
throw new Zend_Search_Lucene_Exception('Unrecognized query syntax lexeme: \'' . $tokenText . '\'');
}
break;
case self::TC_NUMBER:
$this->type = self::TT_NUMBER;
default:
throw new Zend_Search_Lucene_Exception("Unrecognized token type \"$tokType\".");
throw new Zend_Search_Lucene_Exception('Unrecognized lexeme type: \'' . $tokenCategory . '\'');
}
if (!strlen($tokText)) {
throw new Zend_Search_Lucene_Exception('Token text must be supplied.');
}
$this->type = $tokType;
$this->text = $tokText;
}
}
@@ -21,10 +21,10 @@
/** Zend_Search_Lucene_Search_QueryToken */
require_once 'Zend/Search/Lucene/Search/QueryToken.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/QueryToken.php';
/** Zend_Search_Lucene_Exception */
require_once 'Zend/Search/Lucene/Exception.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/**
+11 -11
View File
@@ -15,20 +15,20 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Search_Similarity_Default */
require_once 'Zend/Search/Lucene/Search/Similarity/Default.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Similarity/Default.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
abstract class Zend_Search_Lucene_Search_Similarity
@@ -38,7 +38,7 @@ abstract class Zend_Search_Lucene_Search_Similarity
*
* @var Zend_Search_Lucene_Search_Similarity
*/
static private $_defaultImpl;
private static $_defaultImpl;
/**
* Cache of decoded bytes.
@@ -46,7 +46,7 @@ abstract class Zend_Search_Lucene_Search_Similarity
*
* @var array
*/
static private $_normTable = array( 0 => 0.0,
private static $_normTable = array( 0 => 0.0,
1 => 5.820766E-10,
2 => 6.9849193E-10,
3 => 8.1490725E-10,
@@ -310,7 +310,7 @@ abstract class Zend_Search_Lucene_Search_Similarity
*
* @param Zend_Search_Lucene_Search_Similarity $similarity
*/
static public function setDefault(Zend_Search_Lucene_Search_Similarity $similarity)
public static function setDefault(Zend_Search_Lucene_Search_Similarity $similarity)
{
self::$_defaultImpl = $similarity;
}
@@ -322,7 +322,7 @@ abstract class Zend_Search_Lucene_Search_Similarity
*
* @return Zend_Search_Lucene_Search_Similarity
*/
static public function getDefault()
public static function getDefault()
{
if (!self::$_defaultImpl instanceof Zend_Search_Lucene_Search_Similarity) {
self::$_defaultImpl = new Zend_Search_Lucene_Search_Similarity_Default();
@@ -381,7 +381,7 @@ abstract class Zend_Search_Lucene_Search_Similarity
* @param integer $byte
* @return float
*/
static public function decodeNorm($byte)
public static function decodeNorm($byte)
{
return self::$_normTable[$byte & 0xFF];
}
@@ -412,7 +412,7 @@ abstract class Zend_Search_Lucene_Search_Similarity
* @param integer $b
* @return float
*/
static private function _floatToByte($f)
private static function _floatToByte($f)
{
// round negatives up to zero
if ($f <= 0.0) {
@@ -495,10 +495,10 @@ abstract class Zend_Search_Lucene_Search_Similarity
* Returns a score factor for the term
*
* @param mixed $input
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Interface $reader
* @return a score factor for the term
*/
public function idf($input, $reader)
public function idf($input, Zend_Search_Lucene_Interface $reader)
{
if (!is_array($input)) {
return $this->idfFreq($reader->docFreq($input), $reader->count());
@@ -15,16 +15,20 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Search_Similarity */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Similarity.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_Similarity_Default extends Zend_Search_Lucene_Search_Similarity
+26 -3
View File
@@ -15,7 +15,7 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
@@ -32,17 +32,40 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
abstract class Zend_Search_Lucene_Search_Weight
{
/**
* Normalization factor.
* This value is stored only for query expanation purpose and not used in any other place
*
* @var float
*/
protected $_queryNorm;
/**
* Weight value
*
* Weight value may be initialized in sumOfSquaredWeights() or normalize()
* because they both are invoked either in Query::_initWeight (for top-level query) or
* in corresponding methods of parent query's weights
*
* @var float
*/
protected $_value;
/**
* The weight for this query.
*
* @return float
*/
abstract public function getValue();
public function getValue()
{
return $this->_value;
}
/**
* The sum of squared weights of contained query clauses.
@@ -0,0 +1,136 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Search_Weight */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Weight.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_Weight_Boolean extends Zend_Search_Lucene_Search_Weight
{
/**
* IndexReader.
*
* @var Zend_Search_Lucene_Interface
*/
private $_reader;
/**
* The query that this concerns.
*
* @var Zend_Search_Lucene_Search_Query
*/
private $_query;
/**
* Queries weights
* Array of Zend_Search_Lucene_Search_Weight
*
* @var array
*/
private $_weights;
/**
* Zend_Search_Lucene_Search_Weight_Boolean constructor
* query - the query that this concerns.
* reader - index reader
*
* @param Zend_Search_Lucene_Search_Query $query
* @param Zend_Search_Lucene_Interface $reader
*/
public function __construct(Zend_Search_Lucene_Search_Query $query,
Zend_Search_Lucene_Interface $reader)
{
$this->_query = $query;
$this->_reader = $reader;
$this->_weights = array();
$signs = $query->getSigns();
foreach ($query->getSubqueries() as $num => $subquery) {
if ($signs === null || $signs[$num] === null || $signs[$num]) {
$this->_weights[$num] = $subquery->createWeight($reader);
}
}
}
/**
* The weight for this query
* Standard Weight::$_value is not used for boolean queries
*
* @return float
*/
public function getValue()
{
return $this->_query->getBoost();
}
/**
* The sum of squared weights of contained query clauses.
*
* @return float
*/
public function sumOfSquaredWeights()
{
$sum = 0;
foreach ($this->_weights as $weight) {
// sum sub weights
$sum += $weight->sumOfSquaredWeights();
}
// boost each sub-weight
$sum *= $this->_query->getBoost() * $this->_query->getBoost();
// check for empty query (like '-something -another')
if ($sum == 0) {
$sum = 1.0;
}
return $sum;
}
/**
* Assigns the query normalization factor to this.
*
* @param float $queryNorm
*/
public function normalize($queryNorm)
{
// incorporate boost
$queryNorm *= $this->_query->getBoost();
foreach ($this->_weights as $weight) {
$weight->normalize($queryNorm);
}
}
}
@@ -0,0 +1,56 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Search_Weight */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Weight.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_Weight_Empty extends Zend_Search_Lucene_Search_Weight
{
/**
* The sum of squared weights of contained query clauses.
*
* @return float
*/
public function sumOfSquaredWeights()
{
return 1;
}
/**
* Assigns the query normalization factor to this.
*
* @param float $queryNorm
*/
public function normalize($queryNorm)
{
}
}
@@ -15,20 +15,20 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Search_Weight */
require_once 'Zend/Search/Lucene/Search/Weight.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Weight.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_Weight_MultiTerm extends Zend_Search_Lucene_Search_Weight
@@ -36,14 +36,14 @@ class Zend_Search_Lucene_Search_Weight_MultiTerm extends Zend_Search_Lucene_Sear
/**
* IndexReader.
*
* @var Zend_Search_Lucene
* @var Zend_Search_Lucene_Interface
*/
private $_reader;
/**
* The query that this concerns.
*
* @var Zend_Search_Lucene_Search_Query_MultiTerm
* @var Zend_Search_Lucene_Search_Query
*/
private $_query;
@@ -61,10 +61,11 @@ class Zend_Search_Lucene_Search_Weight_MultiTerm extends Zend_Search_Lucene_Sear
* query - the query that this concerns.
* reader - index reader
*
* @param Zend_Search_Lucene_Search_Query_MultiTerm $query
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Search_Query $query
* @param Zend_Search_Lucene_Interface $reader
*/
public function __construct($query, $reader)
public function __construct(Zend_Search_Lucene_Search_Query $query,
Zend_Search_Lucene_Interface $reader)
{
$this->_query = $query;
$this->_reader = $reader;
@@ -72,10 +73,10 @@ class Zend_Search_Lucene_Search_Weight_MultiTerm extends Zend_Search_Lucene_Sear
$signs = $query->getSigns();
foreach ($query->getTerms() as $num => $term) {
if ($signs === null || $signs[$num] === null || $signs[$num]) {
$this->_weights[$num] = new Zend_Search_Lucene_Search_Weight_Term($term, $query, $reader);
$query->setWeight($num, $this->_weights[$num]);
foreach ($query->getTerms() as $id => $term) {
if ($signs === null || $signs[$id] === null || $signs[$id]) {
$this->_weights[$id] = new Zend_Search_Lucene_Search_Weight_Term($term, $query, $reader);
$query->setWeight($id, $this->_weights[$id]);
}
}
}
@@ -83,6 +84,7 @@ class Zend_Search_Lucene_Search_Weight_MultiTerm extends Zend_Search_Lucene_Sear
/**
* The weight for this query
* Standard Weight::$_value is not used for boolean queries
*
* @return float
*/
@@ -15,7 +15,7 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
@@ -23,14 +23,14 @@
/**
* Zend_Search_Lucene_Search_Weight
*/
require_once 'Zend/Search/Lucene/Search/Weight.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Weight.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_Weight_Phrase extends Zend_Search_Lucene_Search_Weight
@@ -38,7 +38,7 @@ class Zend_Search_Lucene_Search_Weight_Phrase extends Zend_Search_Lucene_Search_
/**
* IndexReader.
*
* @var Zend_Search_Lucene
* @var Zend_Search_Lucene_Interface
*/
private $_reader;
@@ -49,13 +49,6 @@ class Zend_Search_Lucene_Search_Weight_Phrase extends Zend_Search_Lucene_Search_
*/
private $_query;
/**
* Weight value
*
* @var float
*/
private $_value;
/**
* Score factor
*
@@ -63,46 +56,19 @@ class Zend_Search_Lucene_Search_Weight_Phrase extends Zend_Search_Lucene_Search_
*/
private $_idf;
/**
* Normalization factor
*
* @var float
*/
private $_queryNorm;
/**
* Query weight
*
* @var float
*/
private $_queryWeight;
/**
* Zend_Search_Lucene_Search_Weight_Phrase constructor
*
* @param Zend_Search_Lucene_Search_Query_Phrase $query
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Interface $reader
*/
public function __construct(Zend_Search_Lucene_Search_Query_Phrase $query, Zend_Search_Lucene $reader)
public function __construct(Zend_Search_Lucene_Search_Query_Phrase $query,
Zend_Search_Lucene_Interface $reader)
{
$this->_query = $query;
$this->_reader = $reader;
}
/**
* The weight for this query
*
* @return float
*/
public function getValue()
{
return $this->_value;
}
/**
* The sum of squared weights of contained query clauses.
*
@@ -15,20 +15,20 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Search_Weight */
require_once 'Zend/Search/Lucene/Search/Weight.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Search/Weight.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Search
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Search_Weight_Term extends Zend_Search_Lucene_Search_Weight
@@ -36,7 +36,7 @@ class Zend_Search_Lucene_Search_Weight_Term extends Zend_Search_Lucene_Search_We
/**
* IndexReader.
*
* @var Zend_Search_Lucene
* @var Zend_Search_Lucene_Interface
*/
private $_reader;
@@ -54,13 +54,6 @@ class Zend_Search_Lucene_Search_Weight_Term extends Zend_Search_Lucene_Search_We
*/
private $_query;
/**
* Weight value
*
* @var float
*/
private $_value;
/**
* Score factor
*
@@ -68,14 +61,6 @@ class Zend_Search_Lucene_Search_Weight_Term extends Zend_Search_Lucene_Search_We
*/
private $_idf;
/**
* Normalization factor
*
* @var float
*/
private $_queryNorm;
/**
* Query weight
*
@@ -88,9 +73,13 @@ class Zend_Search_Lucene_Search_Weight_Term extends Zend_Search_Lucene_Search_We
* Zend_Search_Lucene_Search_Weight_Term constructor
* reader - index reader
*
* @param Zend_Search_Lucene $reader
* @param Zend_Search_Lucene_Index_Term $term
* @param Zend_Search_Lucene_Search_Query $query
* @param Zend_Search_Lucene_Interface $reader
*/
public function __construct($term, $query, $reader)
public function __construct(Zend_Search_Lucene_Index_Term $term,
Zend_Search_Lucene_Search_Query $query,
Zend_Search_Lucene_Interface $reader)
{
$this->_term = $term;
$this->_query = $query;
@@ -98,17 +87,6 @@ class Zend_Search_Lucene_Search_Weight_Term extends Zend_Search_Lucene_Search_We
}
/**
* The weight for this query
*
* @return float
*/
public function getValue()
{
return $this->_value;
}
/**
* The sum of squared weights of contained query clauses.
*
@@ -15,7 +15,7 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Storage
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
@@ -24,7 +24,7 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Storage
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
abstract class Zend_Search_Lucene_Storage_Directory
@@ -111,10 +111,16 @@ abstract class Zend_Search_Lucene_Storage_Directory
/**
* Returns a Zend_Search_Lucene_Storage_File object for a given $filename in the directory.
*
* If $shareHandler option is true, then file handler can be shared between File Object
* requests. It speed-ups performance, but makes problems with file position.
* Shared handler are good for short atomic requests.
* Non-shared handlers are useful for stream file reading (especial for compound files).
*
* @param string $filename
* @param boolean $shareHandler
* @return Zend_Search_Lucene_Storage_File
*/
abstract public function getFileObject($filename);
abstract public function getFileObject($filename, $shareHandler = true);
}
@@ -15,16 +15,16 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Storage
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Storage_Directory */
require_once 'Zend/Search/Lucene/Storage/Directory.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Storage/Directory.php';
/** Zend_Search_Lucene_Storage_File_Filesystem */
require_once 'Zend/Search/Lucene/Storage/File/Filesystem.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Storage/File/Filesystem.php';
/**
@@ -33,7 +33,7 @@ require_once 'Zend/Search/Lucene/Storage/File/Filesystem.php';
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Storage
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Storage_Directory_Filesystem extends Zend_Search_Lucene_Storage_Directory
@@ -64,7 +64,7 @@ class Zend_Search_Lucene_Storage_Directory_Filesystem extends Zend_Search_Lucene
* @return boolean
*/
static public function mkdirs($dir, $mode = 0777, $recursive = true)
public static function mkdirs($dir, $mode = 0777, $recursive = true)
{
if (is_null($dir) || $dir === '') {
return false;
@@ -113,7 +113,7 @@ class Zend_Search_Lucene_Storage_Directory_Filesystem extends Zend_Search_Lucene
$fileObject->close();
}
unset($this->_fileHandlers);
$this->_fileHandlers = array();
}
@@ -127,15 +127,14 @@ class Zend_Search_Lucene_Storage_Directory_Filesystem extends Zend_Search_Lucene
$result = array();
$dirContent = opendir( $this->_dirPath );
while ($file = readdir($dirContent)) {
while (($file = readdir($dirContent)) !== false) {
if (($file == '..')||($file == '.')) continue;
$fullName = $this->_dirPath . '/' . $file;
if( !is_dir($this->_dirPath . '/' . $file) ) {
$result[] = $file;
}
}
closedir($dirContent);
return $result;
}
@@ -165,11 +164,17 @@ class Zend_Search_Lucene_Storage_Directory_Filesystem extends Zend_Search_Lucene
*/
public function deleteFile($filename)
{
/**
* @todo add support of "deletable" file
* "deletable" is used on Windows systems if file can't be deleted
* (while it is still open).
*/
if (isset($this->_fileHandlers[$filename])) {
$this->_fileHandlers[$filename]->close();
}
unset($this->_fileHandlers[$filename]);
unlink($this->_dirPath .'/'. $filename);
unlink($this->_dirPath . '/' . $filename);
}
@@ -219,24 +224,40 @@ class Zend_Search_Lucene_Storage_Directory_Filesystem extends Zend_Search_Lucene
* @param string $from
* @param string $to
* @return void
* @throws Zend_Search_Lucene_Exception
*/
public function renameFile($from, $to)
{
if ($this->_fileHandlers[$from] !== null) {
global $php_errormsg;
if (isset($this->_fileHandlers[$from])) {
$this->_fileHandlers[$from]->close();
}
unset($this->_fileHandlers[$from]);
if ($this->_fileHandlers[$to] !== null) {
if (isset($this->_fileHandlers[$to])) {
$this->_fileHandlers[$to]->close();
}
unset($this->_fileHandlers[$to]);
if (file_exists($this->_dirPath . '/' . $to)) {
unlink($this->_dirPath . '/' . $to);
if (!unlink($this->_dirPath . '/' . $to)) {
throw new Zend_Search_Lucene_Exception('Delete operation failed');
}
}
return @rename($this->_dirPath . '/' . $from, $this->_dirPath . '/' . $to);
$trackErrors = ini_get('track_errors');
ini_set('track_errors', '1');
$success = @rename($this->_dirPath . '/' . $from, $this->_dirPath . '/' . $to);
if (!$success) {
ini_set('track_errors', $trackErrors);
throw new Zend_Search_Lucene_Exception($php_errormsg);
}
ini_set('track_errors', $trackErrors);
return $success;
}
@@ -255,17 +276,29 @@ class Zend_Search_Lucene_Storage_Directory_Filesystem extends Zend_Search_Lucene
/**
* Returns a Zend_Search_Lucene_Storage_File object for a given $filename in the directory.
*
* If $shareHandler option is true, then file handler can be shared between File Object
* requests. It speed-ups performance, but makes problems with file position.
* Shared handler are good for short atomic requests.
* Non-shared handlers are useful for stream file reading (especial for compound files).
*
* @param string $filename
* @param boolean $shareHandler
* @return Zend_Search_Lucene_Storage_File
*/
public function getFileObject($filename)
public function getFileObject($filename, $shareHandler = true)
{
$fullFilename = $this->_dirPath . '/' . $filename;
if (!$shareHandler) {
return new Zend_Search_Lucene_Storage_File_Filesystem($fullFilename);
}
if (isset( $this->_fileHandlers[$filename] )) {
$this->_fileHandlers[$filename]->seek(0);
return $this->_fileHandlers[$filename];
}
$this->_fileHandlers[$filename] = new Zend_Search_Lucene_Storage_File_Filesystem($this->_dirPath . '/' . $filename);
$this->_fileHandlers[$filename] = new Zend_Search_Lucene_Storage_File_Filesystem($fullFilename);
return $this->_fileHandlers[$filename];
}
}
+27 -4
View File
@@ -15,21 +15,21 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Storage
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Exception */
require_once 'Zend/Search/Lucene/Exception.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Storage
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
abstract class Zend_Search_Lucene_Storage_File
@@ -69,6 +69,15 @@ abstract class Zend_Search_Lucene_Storage_File
*/
abstract public function tell();
/**
* Flush output.
*
* Returns true on success or false on failure.
*
* @return boolean
*/
abstract public function flush();
/**
* Writes $length number of bytes (all, if $length===null) to the end
* of the file.
@@ -78,6 +87,20 @@ abstract class Zend_Search_Lucene_Storage_File
*/
abstract protected function _fwrite($data, $length=null);
/**
* Lock file
*
* Lock type may be a LOCK_SH (shared lock) or a LOCK_EX (exclusive lock)
*
* @param integer $lockType
* @return boolean
*/
abstract public function lock($lockType, $nonBlockinLock = false);
/**
* Unlock file
*/
abstract public function unlock();
/**
* Reads a byte from the current position in the file
@@ -401,4 +424,4 @@ abstract class Zend_Search_Lucene_Storage_File
{
return $this->_fread($this->readVInt());
}
}
}
@@ -15,23 +15,23 @@
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Storage
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Storage_File */
require_once 'Zend/Search/Lucene/Storage/File.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Storage/File.php';
/** Zend_Search_Lucene_Exception */
require_once 'Zend/Search/Lucene/Exception.php';
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Storage
* @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Storage_File_Filesystem extends Zend_Search_Lucene_Storage_File
@@ -53,12 +53,12 @@ class Zend_Search_Lucene_Storage_File_Filesystem extends Zend_Search_Lucene_Stor
{
global $php_errormsg;
$trackErrors = ini_get( "track_errors");
$trackErrors = ini_get('track_errors');
ini_set('track_errors', '1');
$this->_fileHandle = @fopen($filename, $mode);
if ($this->_fileHandle===false) {
if ($this->_fileHandle === false) {
ini_set('track_errors', $trackErrors);
throw new Zend_Search_Lucene_Exception($php_errormsg);
}
@@ -100,6 +100,17 @@ class Zend_Search_Lucene_Storage_File_Filesystem extends Zend_Search_Lucene_Stor
return ftell($this->_fileHandle);
}
/**
* Flush output.
*
* Returns true on success or false on failure.
*
* @return boolean
*/
public function flush()
{
return fflush($this->_fileHandle);
}
/**
* Close File object
@@ -167,5 +178,39 @@ class Zend_Search_Lucene_Storage_File_Filesystem extends Zend_Search_Lucene_Stor
fwrite($this->_fileHandle, $data, $length);
}
}
/**
* Lock file
*
* Lock type may be a LOCK_SH (shared lock) or a LOCK_EX (exclusive lock)
*
* @param integer $lockType
* @param boolean $nonBlockinLock
* @return boolean
*/
public function lock($lockType, $nonBlockinLock = false)
{
if ($nonBlockinLock) {
return flock($this->_fileHandle, $lockType | LOCK_NB);
} else {
return flock($this->_fileHandle, $lockType);
}
}
/**
* Unlock file
*
* Returns true on success
*
* @return boolean
*/
public function unlock()
{
if ($this->_fileHandle !== null ) {
return flock($this->_fileHandle, LOCK_UN);
} else {
return true;
}
}
}
@@ -0,0 +1,555 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Storage
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/** Zend_Search_Lucene_Storage_File */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Storage/File.php';
/** Zend_Search_Lucene_Exception */
require_once $CFG->dirroot.'/search/Zend/Search/Lucene/Exception.php';
/**
* @category Zend
* @package Zend_Search_Lucene
* @subpackage Storage
* @copyright Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Search_Lucene_Storage_File_Memory extends Zend_Search_Lucene_Storage_File
{
/**
* FileData
*
* @var string
*/
private $_data;
/**
* File Position
*
* @var integer
*/
private $_position = 0;
/**
* Object constractor
*
* @param string $data
*/
public function __construct($data)
{
$this->_data = $data;
}
/**
* Reads $length number of bytes at the current position in the
* file and advances the file pointer.
*
* @param integer $length
* @return string
*/
protected function _fread($length = 1)
{
$returnValue = substr($this->_data, $this->_position, $length);
$this->_position += $length;
return $returnValue;
}
/**
* Sets the file position indicator and advances the file pointer.
* The new position, measured in bytes from the beginning of the file,
* is obtained by adding offset to the position specified by whence,
* whose values are defined as follows:
* SEEK_SET - Set position equal to offset bytes.
* SEEK_CUR - Set position to current location plus offset.
* SEEK_END - Set position to end-of-file plus offset. (To move to
* a position before the end-of-file, you need to pass a negative value
* in offset.)
* Upon success, returns 0; otherwise, returns -1
*
* @param integer $offset
* @param integer $whence
* @return integer
*/
public function seek($offset, $whence=SEEK_SET)
{
switch ($whence) {
case SEEK_SET:
$this->_position = $offset;
break;
case SEEK_CUR:
$this->_position += $offset;
break;
case SEEK_END:
$this->_position = strlen($this->_data);
$this->_position += $offset;
break;
default:
break;
}
}
/**
* Get file position.
*
* @return integer
*/
public function tell()
{
return $this->_position;
}
/**
* Flush output.
*
* Returns true on success or false on failure.
*
* @return boolean
*/
public function flush()
{
// Do nothing
return true;
}
/**
* Writes $length number of bytes (all, if $length===null) to the end
* of the file.
*
* @param string $data
* @param integer $length
*/
protected function _fwrite($data, $length=null)
{
// We do not need to check if file position points to the end of "file".
// Only append operation is supported now
if ($length !== null) {
$this->_data .= substr($data, 0, $length);
} else {
$this->_data .= $data;
}
$this->_position = strlen($this->_data);
}
/**
* Lock file
*
* Lock type may be a LOCK_SH (shared lock) or a LOCK_EX (exclusive lock)
*
* @param integer $lockType
* @return boolean
*/
public function lock($lockType, $nonBlockinLock = false)
{
// Memory files can't be shared
// do nothing
return true;
}
/**
* Unlock file
*/
public function unlock()
{
// Memory files can't be shared
// do nothing
}
/**
* Reads a byte from the current position in the file
* and advances the file pointer.
*
* @return integer
*/
public function readByte()
{
return ord($this->_data[$this->_position++]);
}
/**
* Writes a byte to the end of the file.
*
* @param integer $byte
*/
public function writeByte($byte)
{
// We do not need to check if file position points to the end of "file".
// Only append operation is supported now
$this->_data .= chr($byte);
$this->_position = strlen($this->_data);
return 1;
}
/**
* Read num bytes from the current position in the file
* and advances the file pointer.
*
* @param integer $num
* @return string
*/
public function readBytes($num)
{
$returnValue = substr($this->_data, $this->_position, $num);
$this->_position += $num;
return $returnValue;
}
/**
* Writes num bytes of data (all, if $num===null) to the end
* of the string.
*
* @param string $data
* @param integer $num
*/
public function writeBytes($data, $num=null)
{
// We do not need to check if file position points to the end of "file".
// Only append operation is supported now
if ($num !== null) {
$this->_data .= substr($data, 0, $num);
} else {
$this->_data .= $data;
}
$this->_position = strlen($this->_data);
}
/**
* Reads an integer from the current position in the file
* and advances the file pointer.
*
* @return integer
*/
public function readInt()
{
$str = substr($this->_data, $this->_position, 4);
$this->_position += 4;
return ord($str{0}) << 24 |
ord($str{1}) << 16 |
ord($str{2}) << 8 |
ord($str{3});
}
/**
* Writes an integer to the end of file.
*
* @param integer $value
*/
public function writeInt($value)
{
// We do not need to check if file position points to the end of "file".
// Only append operation is supported now
settype($value, 'integer');
$this->_data .= chr($value>>24 & 0xFF) .
chr($value>>16 & 0xFF) .
chr($value>>8 & 0xFF) .
chr($value & 0xFF);
$this->_position = strlen($this->_data);
}
/**
* Returns a long integer from the current position in the file
* and advances the file pointer.
*
* @return integer
* @throws Zend_Search_Lucene_Exception
*/
public function readLong()
{
$str = substr($this->_data, $this->_position, 8);
$this->_position += 8;
/**
* Check, that we work in 64-bit mode.
* fseek() uses long for offset. Thus, largest index segment file size in 32bit mode is 2Gb
*/
if (PHP_INT_SIZE > 4) {
return ord($str{0}) << 56 |
ord($str{1}) << 48 |
ord($str{2}) << 40 |
ord($str{3}) << 32 |
ord($str{4}) << 24 |
ord($str{5}) << 16 |
ord($str{6}) << 8 |
ord($str{7});
} else {
if ((ord($str{0}) != 0) ||
(ord($str{1}) != 0) ||
(ord($str{2}) != 0) ||
(ord($str{3}) != 0) ||
((ord($str{0}) & 0x80) != 0)) {
throw new Zend_Search_Lucene_Exception('Largest supported segment size (for 32-bit mode) is 2Gb');
}
return ord($str{4}) << 24 |
ord($str{5}) << 16 |
ord($str{6}) << 8 |
ord($str{7});
}
}
/**
* Writes long integer to the end of file
*
* @param integer $value
* @throws Zend_Search_Lucene_Exception
*/
public function writeLong($value)
{
// We do not need to check if file position points to the end of "file".
// Only append operation is supported now
/**
* Check, that we work in 64-bit mode.
* fseek() and ftell() use long for offset. Thus, largest index segment file size in 32bit mode is 2Gb
*/
if (PHP_INT_SIZE > 4) {
settype($value, 'integer');
$this->_data .= chr($value>>56 & 0xFF) .
chr($value>>48 & 0xFF) .
chr($value>>40 & 0xFF) .
chr($value>>32 & 0xFF) .
chr($value>>24 & 0xFF) .
chr($value>>16 & 0xFF) .
chr($value>>8 & 0xFF) .
chr($value & 0xFF);
} else {
if ($value > 0x7FFFFFFF) {
throw new Zend_Search_Lucene_Exception('Largest supported segment size (for 32-bit mode) is 2Gb');
}
$this->_data .= chr(0) . chr(0) . chr(0) . chr(0) .
chr($value>>24 & 0xFF) .
chr($value>>16 & 0xFF) .
chr($value>>8 & 0xFF) .
chr($value & 0xFF);
}
$this->_position = strlen($this->_data);
}
/**
* Returns a variable-length integer from the current
* position in the file and advances the file pointer.
*
* @return integer
*/
public function readVInt()
{
$nextByte = ord($this->_data[$this->_position++]);
$val = $nextByte & 0x7F;
for ($shift=7; ($nextByte & 0x80) != 0; $shift += 7) {
$nextByte = ord($this->_data[$this->_position++]);
$val |= ($nextByte & 0x7F) << $shift;
}
return $val;
}
/**
* Writes a variable-length integer to the end of file.
*
* @param integer $value
*/
public function writeVInt($value)
{
// We do not need to check if file position points to the end of "file".
// Only append operation is supported now
settype($value, 'integer');
while ($value > 0x7F) {
$this->_data .= chr( ($value & 0x7F)|0x80 );
$value >>= 7;
}
$this->_data .= chr($value);
$this->_position = strlen($this->_data);
}
/**
* Reads a string from the current position in the file
* and advances the file pointer.
*
* @return string
*/
public function readString()
{
$strlen = $this->readVInt();
if ($strlen == 0) {
return '';
} else {
/**
* This implementation supports only Basic Multilingual Plane
* (BMP) characters (from 0x0000 to 0xFFFF) and doesn't support
* "supplementary characters" (characters whose code points are
* greater than 0xFFFF)
* Java 2 represents these characters as a pair of char (16-bit)
* values, the first from the high-surrogates range (0xD800-0xDBFF),
* the second from the low-surrogates range (0xDC00-0xDFFF). Then
* they are encoded as usual UTF-8 characters in six bytes.
* Standard UTF-8 representation uses four bytes for supplementary
* characters.
*/
$str_val = substr($this->_data, $this->_position, $strlen);
$this->_position += $strlen;
for ($count = 0; $count < $strlen; $count++ ) {
if (( ord($str_val{$count}) & 0xC0 ) == 0xC0) {
$addBytes = 1;
if (ord($str_val{$count}) & 0x20 ) {
$addBytes++;
// Never used. Java2 doesn't encode strings in four bytes
if (ord($str_val{$count}) & 0x10 ) {
$addBytes++;
}
}
$str_val .= substr($this->_data, $this->_position, $addBytes);
$this->_position += $addBytes;
$strlen += $addBytes;
// Check for null character. Java2 encodes null character
// in two bytes.
if (ord($str_val{$count}) == 0xC0 &&
ord($str_val{$count+1}) == 0x80 ) {
$str_val{$count} = 0;
$str_val = substr($str_val,0,$count+1)
. substr($str_val,$count+2);
}
$count += $addBytes;
}
}
return $str_val;
}
}
/**
* Writes a string to the end of file.
*
* @param string $str
* @throws Zend_Search_Lucene_Exception
*/
public function writeString($str)
{
/**
* This implementation supports only Basic Multilingual Plane
* (BMP) characters (from 0x0000 to 0xFFFF) and doesn't support
* "supplementary characters" (characters whose code points are
* greater than 0xFFFF)
* Java 2 represents these characters as a pair of char (16-bit)
* values, the first from the high-surrogates range (0xD800-0xDBFF),
* the second from the low-surrogates range (0xDC00-0xDFFF). Then
* they are encoded as usual UTF-8 characters in six bytes.
* Standard UTF-8 representation uses four bytes for supplementary
* characters.
*/
// We do not need to check if file position points to the end of "file".
// Only append operation is supported now
// convert input to a string before iterating string characters
settype($str, 'string');
$chars = $strlen = strlen($str);
$containNullChars = false;
for ($count = 0; $count < $strlen; $count++ ) {
/**
* String is already in Java 2 representation.
* We should only calculate actual string length and replace
* \x00 by \xC0\x80
*/
if ((ord($str{$count}) & 0xC0) == 0xC0) {
$addBytes = 1;
if (ord($str{$count}) & 0x20 ) {
$addBytes++;
// Never used. Java2 doesn't encode strings in four bytes
// and we dont't support non-BMP characters
if (ord($str{$count}) & 0x10 ) {
$addBytes++;
}
}
$chars -= $addBytes;
if (ord($str{$count}) == 0 ) {
$containNullChars = true;
}
$count += $addBytes;
}
}
if ($chars < 0) {
throw new Zend_Search_Lucene_Exception('Invalid UTF-8 string');
}
$this->writeVInt($chars);
if ($containNullChars) {
$this->_data .= str_replace($str, "\x00", "\xC0\x80");
} else {
$this->_data .= $str;
}
$this->_position = strlen($this->_data);
}
/**
* Reads binary data from the current position in the file
* and advances the file pointer.
*
* @return string
*/
public function readBinary()
{
$length = $this->readVInt();
$returnValue = substr($this->_data, $this->_position, $length);
$this->_position += $length;
return $returnValue;
}
}
+1 -8
View File
@@ -1,14 +1,7 @@
@todo
- Improve API: fix ZSearchMultiTermQuery($terms, $signs);
- Analysis and indexing engine
- Additional queries: phrase, wildcard, proximity, and range
- Additional queries: wildcard, proximity, and range
- Better class-level docblocks (most functions okay)
- Some Windows issues(?) during indexing
- Finish renaming classes to PEAR-like conventions