root/lang/php/ZendFramework_ext/library/Twk/Search/Lucene/Analysis/Analyzer/Common/Utf8MbcsUnigram.php

Revision 14121, 9.0 kB (checked in by twk, 6 months ago)

Zend_Search_Lucene Japanese uni-gram analyzer

Line 
1<?php
2/**
3 * Multi Byte Character uni-gram Analyzer
4 * like Java CJKAnalyzer
5 *
6 * @version 20080616
7 * @author twk (http://nonn-et-twk.net/twk/)
8 */
9
10// Requires analyzer.php
11// @see http://framework.zend.com/issues/browse/ZF-991
12require_once 'Zend/Search/Lucene/Analysis/Analyzer.php';
13
14/** Zend_Search_Lucene_Analysis_Analyzer_Common */
15//require_once 'Zend/Search/Lucene/Analysis/Analyzer/Common.php';
16
17
18/**
19 * @category   Twk
20 * @package    Twk_Search_Lucene
21 * @subpackage Analysis
22 * @license    http://framework.zend.com/license/new-bsd     New BSD License
23 */
24
25class Twk_Search_Lucene_Analysis_Analyzer_Common_Utf8MbcsUnigram extends Zend_Search_Lucene_Analysis_Analyzer_Common
26{
27    /**
28     * Current char position in an UTF-8 stream
29     *
30     * @var integer
31     */
32    private $_position;
33
34    /**
35     * Current binary position in an UTF-8 stream
36     *
37     * @var integer
38     */
39    private $_bytePosition;
40
41    private $_lastPosition;
42    private $_lastBytePosition;
43   
44    /**
45     * Stream length
46     *
47     * @var integer
48     */
49    private $_streamLength;
50
51    const CATEGORY_ALNUM = 1;
52    const CATEGORY_PUNCT = 2;
53    const CATEGORY_CNTRL = 3;
54    const CATEGORY_SPACE = 4;
55    const CATEGORY_SBCS_OTHER = 5;
56    const CATEGORY_MBCS = 0x10;
57    const CATEGORY_HIRAGANA = 0x11;
58    const CATEGORY_KATAKANA = 0x12;
59    const CATEGORY_JAPANESE_KANJI = 0x13;
60    const CATEGORY_FULLWIDTH_ALNUM = 0x14;
61    const CATEGORY_JAPANESE_PUNCT = 0x14;
62    const CATEGORY_MBCS_OTHER = 0x15;
63
64    /**
65     * Reset token stream
66     */
67    public function reset()
68    {
69        $this->_position     = 0;
70        $this->_bytePosition = 0;
71       
72        $this->_lastPosition = 0;
73        $this->_lastBytePosition = 0;
74
75        // convert input into UTF-8
76        if (strcasecmp($this->_encoding, 'utf8' ) != &&
77            strcasecmp($this->_encoding, 'utf-8') != 0 ) {
78                $this->_input = iconv($this->_encoding, 'UTF-8', $this->_input);
79                $this->_encoding = 'UTF-8';
80        }
81
82        // Get UTF-8 string length.
83        // It also checks if it's a correct utf-8 string
84        $this->_streamLength = iconv_strlen($this->_input, 'UTF-8');
85    }
86
87    /**
88     * @return CATEGORY_xxx
89     */
90    private static function _getCharCategory($char)
91    {
92        if (!self::_isMbcs($char))
93        {
94            if (ctype_alnum($char))
95                $category = self::CATEGORY_ALNUM;
96            else if (ctype_punct($char))
97                $category = self::CATEGORY_PUNCT;
98            else if (ctype_cntrl($char))
99                $category = self::CATEGORY_CNTRL;
100            else if (ctype_space($char))
101                $category = self::CATEGORY_SPACE;
102               else
103                $category = self::CATEGORY_SBCS_OTHER;
104        }
105        else
106        {
107            if (extension_loaded('mbstring'))
108            {
109                // @see http://ablog.seesaa.net/article/20969848.html
110                // 記号は代表的な物のみ指定している。pregと/u使っても書けそう
111                if (mb_ereg_match('^[ぁ-ん]+$', $char))
112                    $category = self::CATEGORY_HIRAGANA;
113                else if (mb_ereg_match('^[ァ-ヴー]+$', $char))
114                    $category = self::CATEGORY_KATAKANA;
115                else if (mb_ereg_match('^[一-龠々〆ヵヶ]+$', $char))
116                    $category = self::CATEGORY_JAPANESE_KANJI;
117                else if (mb_ereg_match('^[、。!?()「」『』【】]+$', $char))
118                    $category = self::CATEGORY_JAPANESE_PUNCT;
119                else if (mb_ereg_match('^[a-zA-Z0-9]+$', $char))
120                    $category = self::CATEGORY_FULLWIDTH_ALNUM;
121                else
122                    $category = self::CATEGORY_MBCS_OTHER;
123            }
124            else
125            {
126                $category = self::CATEGORY_MBCS_OTHER;
127            }
128        }
129       
130        return $category;
131    }
132   
133    /**
134     * Check, how that character is
135     *
136     * @param string $char
137     * @return boolean
138     */
139    private static function _isWhiteSpaceCategory($char)
140    {
141        static $whiteSpaceCategories = array(self::CATEGORY_PUNCT, self::CATEGORY_CNTRL, self::CATEGORY_SPACE, self::CATEGORY_JAPANESE_PUNCT);
142       
143        $category = self::_getCharCategory($char);
144        //echo $category, "\n";
145       
146        return in_array($category, $whiteSpaceCategories);
147    }
148   
149    /**
150     * Get next UTF-8 char
151     * _position and _bytePosition are modified
152     * last positions are stored to _lastPosition and _lastBytePosition
153     *
154     * @param string $char
155     * @return boolean
156     */
157    private function _nextChar()
158    {
159        $this->_lastBytePosition = $this->_bytePosition;
160        $this->_lastPosition = $this->_position;
161       
162        $char = $this->_input[$this->_bytePosition++];
163       
164        $ordChar = ord($char);
165        if (( $ordChar & 0xC0 ) == 0xC0) {
166            $addBytes = 1;
167            if ($ordChar & 0x20 ) {
168                $addBytes++;
169                if ($ordChar & 0x10 ) {
170                    $addBytes++;
171                }
172            }
173            $char .= substr($this->_input, $this->_bytePosition, $addBytes);
174            $this->_bytePosition += $addBytes;
175        }
176
177        $this->_position++;
178
179        return $char;
180    }
181
182    private function _undoLastChar()
183    {
184        $this->_bytePosition = $this->_lastBytePosition;
185        $this->_position = $this->_lastPosition;
186    }
187
188    /**
189     * @return boolean
190     */
191    private static function _isMbcs($char)
192    {
193        return strlen($char) > 1;
194    }
195   
196    /**
197     * Tokenization stream API
198     * Get next token
199     * Returns null at the end of stream
200     *
201     * @return Zend_Search_Lucene_Analysis_Token|null
202     */
203    public function nextToken()
204    {
205        if ($this->_input === null) {
206            return null;
207        }
208
209        while ($this->_position < $this->_streamLength) {
210            // skip white spaces
211            while ($this->_position < $this->_streamLength &&
212                   self::_isWhiteSpaceCategory($char = $this->_nextChar())) {
213                $char = '';
214            }
215            if ($char === '') {
216                return null;
217            }
218           
219            $wasMbcs = $this->_isMbcs($char);
220               $this->_undoLastChar();
221            // echo $this->_position, '=', $char;
222           
223            // read token
224            $termStartPosition = $this->_position;
225            $termText = '';
226            $isTokenDivided = false;
227            while ($this->_position < $this->_streamLength) {
228                $char = $this->_nextChar();
229                if (self::_isWhiteSpaceCategory($char))
230                {
231                    $isTokenDivided = true;
232                    break;
233                }
234                else if (!$wasMbcs)
235                {
236                    if ($this->_isMbcs($char))
237                    {
238                        $isTokenDivided = true;
239                        break;
240                    }
241                }
242                else
243                {
244                    if (!$this->_isMbcs($char) || iconv_strlen($termText, 'UTF-8') >= 1)
245                    {
246                        $isTokenDivided = true;
247                        break;
248                    }
249                }
250                $termText .= $char;
251            }
252           
253            // Empty token, end of stream.
254            if ($termText == '') {
255                return null;
256            }
257           
258            // if not End, undo the last char
259            // echo $this->_position ,"<=", $this->_streamLength;
260            if ($isTokenDivided) {
261                $this->_undoLastChar();
262            }
263           
264            $termEndPosition = $this->_position;
265            $token = new Zend_Search_Lucene_Analysis_Token(
266                                      $termText,
267                                      $termStartPosition,
268                                      $termEndPosition);
269            $token = $this->normalize($token);
270            if ($token !== null) {
271                // echo "$termText $termStartPosition $termEndPosition"
272                return $token;
273            }
274           
275            // Continue if token is skipped
276        }
277
278        return null;
279    }
280}
281
282
283// ここから先は直接実行の時のみ動きます。チェックは甘め
284if (!count(debug_backtrace()))
285{
286    $fp = fopen(__FILE__, 'r');
287    fseek($fp, __COMPILER_HALT_OFFSET__ + 2); // 2 for ? and >
288    echo stream_get_contents($fp);
289   
290    $text = '日本でWindows 95が発売されたのはいつでしょう?';
291    $analyzer = new Twk_Search_Lucene_Analysis_Analyzer_Common_Utf8MbcsUnigram();
292    $a = $analyzer->tokenize($text, 'UTF-8');
293    print_r($a);
294}
295
296__halt_compiler();?>
297<html>
298<head>
299<title>Twk_Search_Lucene_Analysis_Analyzer_Common_Utf8MbcsUnigram</title>
300</head>
301<body>
302<h1>Twk_Search_Lucene_Analysis_Analyzer_Common_Utf8MbcsUnigram</h1>
303<pre>
304    Zend_Search_Lucene用日本語アナライザー
305    作者 twk
306
307    <a href="http://coderepos.org/share/browser/lang/php/ZendFramework_ext/library/Twk/Search/Lucene/Analysis/Analyzer/Common/">配布場所</a>
308   
309    コード例:
310    <code>
311        $text = '日本でWindows 95が発売されたのはいつでしょう?';
312        $analyzer = new Twk_Search_Lucene_Analysis_Analyzer_Common_Utf8MbcsUnigram();
313        $a = $analyzer-&gt;tokenize($text, 'UTF-8');
314        print_r($a);   
315    </code>
316</pre>
317</body>
318</html>
Note: See TracBrowser for help on using the browser.