Subversion-Projekte lars-tiefland.prado

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/**
3
 * Zend Framework
4
 *
5
 * LICENSE
6
 *
7
 * This source file is subject to version 1.0 of the Zend Framework
8
 * license, that is bundled with this package in the file LICENSE, and
9
 * is available through the world-wide-web at the following URL:
10
 * http://www.zend.com/license/framework/1_0.txt. If you did not receive
11
 * a copy of the Zend Framework license and are unable to obtain it
12
 * through the world-wide-web, please send a note to license@zend.com
13
 * so we can mail you a copy immediately.
14
 *
15
 * @package    Zend_Search_Lucene
16
 * @copyright  Copyright (c) 2005-2006 Zend Technologies USA Inc. (http://www.zend.com)
17
 * @license    http://www.zend.com/license/framework/1_0.txt Zend Framework License version 1.0
18
 */
19
 
20
 
21
/** Zend_Search_Lucene_Exception */
22
require_once 'Zend/Search/Lucene/Exception.php';
23
 
24
/** Zend_Search_Lucene_Document */
25
require_once 'Zend/Search/Lucene/Document.php';
26
 
27
/** Zend_Search_Lucene_Storage_Directory */
28
require_once 'Zend/Search/Lucene/Storage/Directory/Filesystem.php';
29
 
30
/** Zend_Search_Lucene_Index_Term */
31
require_once 'Zend/Search/Lucene/Index/Term.php';
32
 
33
/** Zend_Search_Lucene_Index_TermInfo */
34
require_once 'Zend/Search/Lucene/Index/TermInfo.php';
35
 
36
/** Zend_Search_Lucene_Index_SegmentInfo */
37
require_once 'Zend/Search/Lucene/Index/SegmentInfo.php';
38
 
39
/** Zend_Search_Lucene_Index_FieldInfo */
40
require_once 'Zend/Search/Lucene/Index/FieldInfo.php';
41
 
42
/** Zend_Search_Lucene_Index_Writer */
43
require_once 'Zend/Search/Lucene/Index/Writer.php';
44
 
45
/** Zend_Search_Lucene_Search_QueryParser */
46
require_once 'Zend/Search/Lucene/Search/QueryParser.php';
47
 
48
/** Zend_Search_Lucene_Search_QueryHit */
49
require_once 'Zend/Search/Lucene/Search/QueryHit.php';
50
 
51
/** Zend_Search_Lucene_Search_Similarity */
52
require_once 'Zend/Search/Lucene/Search/Similarity.php';
53
 
54
 
55
/**
56
 * @package    Zend_Search_Lucene
57
 * @copyright  Copyright (c) 2005-2006 Zend Technologies USA Inc. (http://www.zend.com)
58
 * @license    http://www.zend.com/license/framework/1_0.txt Zend Framework License version 1.0
59
 */
60
class Zend_Search_Lucene
61
{
62
    /**
63
     * File system adapter.
64
     *
65
     * @var Zend_Search_Lucene_Storage_Directory
66
     */
67
    private $_directory = null;
68
 
69
    /**
70
     * File system adapter closing option
71
     *
72
     * @var boolean
73
     */
74
    private $_closeDirOnExit = true;
75
 
76
    /**
77
     * Writer for this index, not instantiated unless required.
78
     *
79
     * @var Zend_Search_Lucene_Index_Writer
80
     */
81
    private $_writer = null;
82
 
83
    /**
84
     * Array of Zend_Search_Lucene_Index_SegmentInfo objects for this index.
85
     *
86
     * @var array Zend_Search_Lucene_Index_SegmentInfo
87
     */
88
    private $_segmentInfos = array();
89
 
90
    /**
91
     * Number of documents in this index.
92
     *
93
     * @var integer
94
     */
95
    private $_docCount = 0;
96
 
97
 
98
    /**
99
     * Opens the index.
100
     *
101
     * IndexReader constructor needs Directory as a parameter. It should be
102
     * a string with a path to the index folder or a Directory object.
103
     *
104
     * @param mixed $directory
105
     * @throws Zend_Search_Lucene_Exception
106
     */
107
    public function __construct($directory = null, $create = false)
108
    {
109
        if ($directory === null) {
110
            throw new Zend_Search_Exception('No index directory specified');
111
        }
112
 
113
        if ($directory instanceof Zend_Search_Lucene_Storage_Directory_Filesystem) {
114
            $this->_directory      = $directory;
115
            $this->_closeDirOnExit = false;
116
        } else {
117
            $this->_directory      = new Zend_Search_Lucene_Storage_Directory_Filesystem($directory);
118
            $this->_closeDirOnExit = true;
119
        }
120
 
121
        if ($create) {
122
            $this->_writer = new Zend_Search_Lucene_Index_Writer($this->_directory, true);
123
        } else {
124
            $this->_writer = null;
125
        }
126
 
127
        $this->_segmentInfos = array();
128
 
129
        $segmentsFile = $this->_directory->getFileObject('segments');
130
 
131
        $format = $segmentsFile->readInt();
132
 
133
        if ($format != (int)0xFFFFFFFF) {
134
            throw new Zend_Search_Lucene_Exception('Wrong segments file format');
135
        }
136
 
137
        // read version
138
        $segmentsFile->readLong();
139
 
140
        // read counter
141
        $segmentsFile->readInt();
142
 
143
        $segments = $segmentsFile->readInt();
144
 
145
        $this->_docCount = 0;
146
 
147
        // read segmentInfos
148
        for ($count = 0; $count < $segments; $count++) {
149
            $segName = $segmentsFile->readString();
150
            $segSize = $segmentsFile->readInt();
151
            $this->_docCount += $segSize;
152
 
153
            $this->_segmentInfos[$count] =
154
                                new Zend_Search_Lucene_Index_SegmentInfo($segName,
155
                                                                         $segSize,
156
                                                                         $this->_directory);
157
        }
158
    }
159
 
160
 
161
    /**
162
     * Object destructor
163
     */
164
    public function __destruct()
165
    {
166
        $this->commit();
167
 
168
        if ($this->_closeDirOnExit) {
169
            $this->_directory->close();
170
        }
171
    }
172
 
173
    /**
174
     * Returns an instance of Zend_Search_Lucene_Index_Writer for the index
175
     *
176
     * @return Zend_Search_Lucene_Index_Writer
177
     */
178
    public function getIndexWriter()
179
    {
180
        if (!$this->_writer instanceof Zend_Search_Lucene_Index_Writer) {
181
            $this->_writer = new Zend_Search_Lucene_Index_Writer($this->_directory);
182
        }
183
 
184
        return $this->_writer;
185
    }
186
 
187
 
188
    /**
189
     * Returns the Zend_Search_Lucene_Storage_Directory instance for this index.
190
     *
191
     * @return Zend_Search_Lucene_Storage_Directory
192
     */
193
    public function getDirectory()
194
    {
195
        return $this->_directory;
196
    }
197
 
198
 
199
    /**
200
     * Returns the total number of documents in this index.
201
     *
202
     * @return integer
203
     */
204
    public function count()
205
    {
206
        return $this->_docCount;
207
    }
208
 
209
 
210
    /**
211
     * Performs a query against the index and returns an array
212
     * of Zend_Search_Lucene_Search_QueryHit objects.
213
     * Input is a string or Zend_Search_Lucene_Search_Query.
214
     *
215
     * @param mixed $query
216
     * @return array ZSearchHit
217
     */
218
    public function find($query)
219
    {
220
        if (is_string($query)) {
221
            $query = Zend_Search_Lucene_Search_QueryParser::parse($query);
222
        }
223
 
224
        if (!$query instanceof Zend_Search_Lucene_Search_Query) {
225
            throw new Zend_Search_Lucene_Exception('Query must be a string or Zend_Search_Lucene_Search_Query object');
226
        }
227
 
228
        $this->commit();
229
 
230
        $hits = array();
231
        $scores = array();
232
 
233
        $docNum = $this->count();
234
        for( $count=0; $count < $docNum; $count++ ) {
235
            $docScore = $query->score( $count, $this);
236
            if( $docScore != 0 ) {
237
                $hit = new Zend_Search_Lucene_Search_QueryHit($this);
238
                $hit->id = $count;
239
                $hit->score = $docScore;
240
 
241
                $hits[] = $hit;
242
                $scores[] = $docScore;
243
            }
244
        }
245
        array_multisort($scores, SORT_DESC, SORT_REGULAR, $hits);
246
 
247
        return $hits;
248
    }
249
 
250
 
251
    /**
252
     * Returns a list of all unique field names that exist in this index.
253
     *
254
     * @param boolean $indexed
255
     * @return array
256
     */
257
    public function getFieldNames($indexed = false)
258
    {
259
        $result = array();
260
        foreach( $this->_segmentInfos as $segmentInfo ) {
261
            $result = array_merge($result, $segmentInfo->getFields($indexed));
262
        }
263
        return $result;
264
    }
265
 
266
 
267
    /**
268
     * Returns a Zend_Search_Lucene_Document object for the document
269
     * number $id in this index.
270
     *
271
     * @param integer|Zend_Search_Lucene_Search_QueryHit $id
272
     * @return Zend_Search_Lucene_Document
273
     */
274
    public function getDocument($id)
275
    {
276
        if ($id instanceof Zend_Search_Lucene_Search_QueryHit) {
277
            /* @var $id Zend_Search_Lucene_Search_QueryHit */
278
            $id = $id->id;
279
        }
280
 
281
        if ($id >= $this->_docCount) {
282
            /**
283
             * @todo exception here?
284
             */
285
            return null;
286
        }
287
 
288
        $segCount = 0;
289
        $nextSegmentStartId = $this->_segmentInfos[ 0 ]->count();
290
        while( $nextSegmentStartId <= $id ) {
291
               $segCount++;
292
               $nextSegmentStartId += $this->_segmentInfos[ $segCount ]->count();
293
        }
294
        $segmentStartId = $nextSegmentStartId - $this->_segmentInfos[ $segCount ]->count();
295
 
296
        $fdxFile = $this->_segmentInfos[ $segCount ]->openCompoundFile('.fdx');
297
        $fdxFile->seek( ($id-$segmentStartId)*8, SEEK_CUR );
298
        $fieldValuesPosition = $fdxFile->readLong();
299
 
300
        $fdtFile = $this->_segmentInfos[ $segCount ]->openCompoundFile('.fdt');
301
        $fdtFile->seek( $fieldValuesPosition, SEEK_CUR );
302
        $fieldCount = $fdtFile->readVInt();
303
 
304
        $doc = new Zend_Search_Lucene_Document();
305
        for( $count = 0; $count < $fieldCount; $count++ ) {
306
            $fieldNum = $fdtFile->readVInt();
307
            $bits = $fdtFile->readByte();
308
 
309
            $fieldInfo = $this->_segmentInfos[ $segCount ]->getField($fieldNum);
310
 
311
            if( !($bits & 2) ) { // Text data
312
                $field = new Zend_Search_Lucene_Field($fieldInfo->name,
313
                                                      $fdtFile->readString(),
314
                                                      true,
315
                                                      $fieldInfo->isIndexed,
316
                                                      $bits & 1 );
317
            } else {
318
                $field = new Zend_Search_Lucene_Field($fieldInfo->name,
319
                                                      $fdtFile->readBinary(),
320
                                                      true,
321
                                                      $fieldInfo->isIndexed,
322
                                                      $bits & 1 );
323
            }
324
 
325
            $doc->addField($field);
326
        }
327
 
328
        return $doc;
329
    }
330
 
331
 
332
    /**
333
     * Returns an array of all the documents which contain term.
334
     *
335
     * @param Zend_Search_Lucene_Index_Term $term
336
     * @return array
337
     */
338
    public function termDocs(Zend_Search_Lucene_Index_Term $term)
339
    {
340
        $result = array();
341
        $segmentStartDocId = 0;
342
 
343
        foreach ($this->_segmentInfos as $segInfo) {
344
            $termInfo = $segInfo->getTermInfo($term);
345
 
346
            if (!$termInfo instanceof Zend_Search_Lucene_Index_TermInfo) {
347
                $segmentStartDocId += $segInfo->count();
348
                continue;
349
            }
350
 
351
            $frqFile = $segInfo->openCompoundFile('.frq');
352
            $frqFile->seek($termInfo->freqPointer,SEEK_CUR);
353
            $docId = 0;
354
            for( $count=0; $count < $termInfo->docFreq; $count++ ) {
355
                $docDelta = $frqFile->readVInt();
356
                if( $docDelta % 2 == 1 ) {
357
                    $docId += ($docDelta-1)/2;
358
                } else {
359
                    $docId += $docDelta/2;
360
                    // read freq
361
                    $frqFile->readVInt();
362
                }
363
                $result[] = $segmentStartDocId + $docId;
364
            }
365
 
366
            $segmentStartDocId += $segInfo->count();
367
        }
368
 
369
        return $result;
370
    }
371
 
372
 
373
    /**
374
     * Returns an array of all term positions in the documents.
375
     * Return array structure: array( docId => array( pos1, pos2, ...), ...)
376
     *
377
     * @param Zend_Search_Lucene_Index_Term $term
378
     * @return array
379
     */
380
    public function termPositions(Zend_Search_Lucene_Index_Term $term)
381
    {
382
        $result = array();
383
        $segmentStartDocId = 0;
384
        foreach( $this->_segmentInfos as $segInfo ) {
385
            $termInfo = $segInfo->getTermInfo($term);
386
 
387
            if (!$termInfo instanceof Zend_Search_Lucene_Index_TermInfo) {
388
                $segmentStartDocId += $segInfo->count();
389
                continue;
390
            }
391
 
392
            $frqFile = $segInfo->openCompoundFile('.frq');
393
            $frqFile->seek($termInfo->freqPointer,SEEK_CUR);
394
            $freqs = array();
395
            $docId = 0;
396
 
397
            for( $count = 0; $count < $termInfo->docFreq; $count++ ) {
398
                $docDelta = $frqFile->readVInt();
399
                if( $docDelta % 2 == 1 ) {
400
                    $docId += ($docDelta-1)/2;
401
                    $freqs[ $docId ] = 1;
402
                } else {
403
                    $docId += $docDelta/2;
404
                    $freqs[ $docId ] = $frqFile->readVInt();
405
                }
406
            }
407
 
408
            $prxFile = $segInfo->openCompoundFile('.prx');
409
            $prxFile->seek($termInfo->proxPointer,SEEK_CUR);
410
            foreach ($freqs as $docId => $freq) {
411
                $termPosition = 0;
412
                $positions = array();
413
 
414
                for ($count = 0; $count < $freq; $count++ ) {
415
                    $termPosition += $prxFile->readVInt();
416
                    $positions[] = $termPosition;
417
                }
418
                $result[ $segmentStartDocId + $docId ] = $positions;
419
            }
420
 
421
            $segmentStartDocId += $segInfo->count();
422
        }
423
 
424
        return $result;
425
    }
426
 
427
 
428
    /**
429
     * Returns the number of documents in this index containing the $term.
430
     *
431
     * @param Zend_Search_Lucene_Index_Term $term
432
     * @return integer
433
     */
434
    public function docFreq(Zend_Search_Lucene_Index_Term $term)
435
    {
436
        $result = 0;
437
        foreach ($this->_segmentInfos as $segInfo) {
438
            $termInfo = $segInfo->getTermInfo($term);
439
            if ($termInfo !== null) {
440
                $result += $termInfo->docFreq;
441
            }
442
        }
443
 
444
        return $result;
445
    }
446
 
447
 
448
    /**
449
     * Retrive similarity used by index reader
450
     *
451
     * @return Zend_Search_Lucene_Search_Similarity
452
     */
453
    public function getSimilarity()
454
    {
455
        return Zend_Search_Lucene_Search_Similarity::getDefault();
456
    }
457
 
458
 
459
    /**
460
     * Returns a normalization factor for "field, document" pair.
461
     *
462
     * @param integer $id
463
     * @param string $fieldName
464
     * @return Zend_Search_Lucene_Document
465
     */
466
    public function norm( $id, $fieldName )
467
    {
468
        if( $id >= $this->_docCount )
469
            return null;
470
 
471
        $segCount = 0;
472
        $nextSegmentStartId = $this->_segmentInfos[ 0 ]->count();
473
        while( $nextSegmentStartId <= $id ) {
474
               $segCount++;
475
               $nextSegmentStartId += $this->_segmentInfos[ $segCount ]->count();
476
        }
477
 
478
        $segmentStartId = $nextSegmentStartId - $this->_segmentInfos[ $segCount ]->count();
479
 
480
        return $this->_segmentInfos[ $segCount ]->norm($id - $segmentStartId, $fieldName);
481
    }
482
 
483
 
484
    /**
485
     * Adds a document to this index.
486
     *
487
     * @param Zend_Search_Lucene_Document $document
488
     */
489
    public function addDocument(Zend_Search_Lucene_Document $document)
490
    {
491
        if (!$this->_writer instanceof Zend_Search_Lucene_Index_Writer) {
492
            $this->_writer = new Zend_Search_Lucene_Index_Writer($this->_directory);
493
        }
494
 
495
        $this->_writer->addDocument($document);
496
    }
497
 
498
 
499
    /**
500
     * Commit changes resulting from delete() or undeleteAll() operations.
501
     *
502
     * @todo delete() and undeleteAll processing.
503
     */
504
    public function commit()
505
    {
506
        if ($this->_writer !== null) {
507
            foreach ($this->_writer->commit() as $segmentName => $segmentInfo) {
508
                if ($segmentInfo !== null) {
509
                    $this->_segmentInfos[] = $segmentInfo;
510
                    $this->_docCount += $segmentInfo->count();
511
                } else {
512
                    foreach ($this->_segmentInfos as $segId => $segInfo) {
513
                        if ($segInfo->getName() == $segmentName) {
514
                            unset($this->_segmentInfos[$segId]);
515
                        }
516
                    }
517
                }
518
            }
519
        }
520
    }
521
 
522
 
523
    /*************************************************************************
524
    @todo UNIMPLEMENTED
525
    *************************************************************************/
526
 
527
    /**
528
     * Returns an array of all terms in this index.
529
     *
530
     * @todo Implementation
531
     * @return array
532
     */
533
    public function terms()
534
    {
535
        return array();
536
    }
537
 
538
 
539
    /**
540
     * Returns true if any documents have been deleted from this index.
541
     *
542
     * @todo Implementation
543
     * @return boolean
544
     */
545
    public function hasDeletions()
546
    {
547
        return false;
548
    }
549
 
550
 
551
    /**
552
     * Deletes a document from the index.  $doc may contain a Zend_Search_Lucene_Document
553
     * or the number of the document to delete.
554
     *
555
     * @todo Implementation
556
     * @param mixed $item_to_del
557
     */
558
    public function delete($doc)
559
    {}
560
 
561
 
562
    /**
563
     * Undeletes all documents currently marked as deleted in this index.
564
     *
565
     * @todo Implementation
566
     */
567
    public function undeleteAll()
568
    {}
569
}