| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 ** 2006 September 30 | |
| 3 ** | |
| 4 ** The author disclaims copyright to this source code. In place of | |
| 5 ** a legal notice, here is a blessing: | |
| 6 ** | |
| 7 ** May you do good and not evil. | |
| 8 ** May you find forgiveness for yourself and forgive others. | |
| 9 ** May you share freely, never taking more than you give. | |
| 10 ** | |
| 11 ************************************************************************* | |
| 12 ** Implementation of the full-text-search tokenizer that implements | |
| 13 ** a Porter stemmer. | |
| 14 */ | |
| 15 | |
| 16 /* | |
| 17 ** The code in this file is only compiled if: | |
| 18 ** | |
| 19 ** * The FTS3 module is being built as an extension | |
| 20 ** (in which case SQLITE_CORE is not defined), or | |
| 21 ** | |
| 22 ** * The FTS3 module is being built into the core of | |
| 23 ** SQLite (in which case SQLITE_ENABLE_FTS3 is defined). | |
| 24 */ | |
| 25 #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) | |
| 26 | |
| 27 | |
| 28 #include <assert.h> | |
| 29 #include <stdlib.h> | |
| 30 #include <stdio.h> | |
| 31 #include <string.h> | |
| 32 | |
| 33 #include "fts3_tokenizer.h" | |
| 34 | |
| 35 /* | |
| 36 ** Class derived from sqlite3_tokenizer | |
| 37 */ | |
| 38 typedef struct porter_tokenizer { | |
| 39 sqlite3_tokenizer base; /* Base class */ | |
| 40 } porter_tokenizer; | |
| 41 | |
| 42 /* | |
| 43 ** Class derived from sqlit3_tokenizer_cursor | |
| 44 */ | |
| 45 typedef struct porter_tokenizer_cursor { | |
| 46 sqlite3_tokenizer_cursor base; | |
| 47 const char *zInput; /* input we are tokenizing */ | |
| 48 int nInput; /* size of the input */ | |
| 49 int iOffset; /* current position in zInput */ | |
| 50 int iToken; /* index of next token to be returned */ | |
| 51 char *zToken; /* storage for current token */ | |
| 52 int nAllocated; /* space allocated to zToken buffer */ | |
| 53 } porter_tokenizer_cursor; | |
| 54 | |
| 55 | |
| 56 /* Forward declaration */ | |
| 57 static const sqlite3_tokenizer_module porterTokenizerModule; | |
| 58 | |
| 59 | |
| 60 /* | |
| 61 ** Create a new tokenizer instance. | |
| 62 */ | |
| 63 static int porterCreate( | |
| 64 int argc, const char * const *argv, | |
| 65 sqlite3_tokenizer **ppTokenizer | |
| 66 ){ | |
| 67 porter_tokenizer *t; | |
| 68 t = (porter_tokenizer *) sqlite3_malloc(sizeof(*t)); | |
| 69 if( t==NULL ) return SQLITE_NOMEM; | |
| 70 memset(t, 0, sizeof(*t)); | |
| 71 *ppTokenizer = &t->base; | |
| 72 return SQLITE_OK; | |
| 73 } | |
| 74 | |
| 75 /* | |
| 76 ** Destroy a tokenizer | |
| 77 */ | |
| 78 static int porterDestroy(sqlite3_tokenizer *pTokenizer){ | |
| 79 sqlite3_free(pTokenizer); | |
| 80 return SQLITE_OK; | |
| 81 } | |
| 82 | |
| 83 /* | |
| 84 ** Prepare to begin tokenizing a particular string. The input | |
| 85 ** string to be tokenized is zInput[0..nInput-1]. A cursor | |
| 86 ** used to incrementally tokenize this string is returned in | |
| 87 ** *ppCursor. | |
| 88 */ | |
| 89 static int porterOpen( | |
| 90 sqlite3_tokenizer *pTokenizer, /* The tokenizer */ | |
| 91 const char *zInput, int nInput, /* String to be tokenized */ | |
| 92 sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */ | |
| 93 ){ | |
| 94 porter_tokenizer_cursor *c; | |
| 95 | |
| 96 c = (porter_tokenizer_cursor *) sqlite3_malloc(sizeof(*c)); | |
| 97 if( c==NULL ) return SQLITE_NOMEM; | |
| 98 | |
| 99 c->zInput = zInput; | |
| 100 if( zInput==0 ){ | |
| 101 c->nInput = 0; | |
| 102 }else if( nInput<0 ){ | |
| 103 c->nInput = (int)strlen(zInput); | |
| 104 }else{ | |
| 105 c->nInput = nInput; | |
| 106 } | |
| 107 c->iOffset = 0; /* start tokenizing at the beginning */ | |
| 108 c->iToken = 0; | |
| 109 c->zToken = NULL; /* no space allocated, yet. */ | |
| 110 c->nAllocated = 0; | |
| 111 | |
| 112 *ppCursor = &c->base; | |
| 113 return SQLITE_OK; | |
| 114 } | |
| 115 | |
| 116 /* | |
| 117 ** Close a tokenization cursor previously opened by a call to | |
| 118 ** porterOpen() above. | |
| 119 */ | |
| 120 static int porterClose(sqlite3_tokenizer_cursor *pCursor){ | |
| 121 porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor; | |
| 122 sqlite3_free(c->zToken); | |
| 123 sqlite3_free(c); | |
| 124 return SQLITE_OK; | |
| 125 } | |
| 126 /* | |
| 127 ** Vowel or consonant | |
| 128 */ | |
| 129 static const char cType[] = { | |
| 130 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, | |
| 131 1, 1, 1, 2, 1 | |
| 132 }; | |
| 133 | |
| 134 /* | |
| 135 ** isConsonant() and isVowel() determine if their first character in | |
| 136 ** the string they point to is a consonant or a vowel, according | |
| 137 ** to Porter ruls. | |
| 138 ** | |
| 139 ** A consonate is any letter other than 'a', 'e', 'i', 'o', or 'u'. | |
| 140 ** 'Y' is a consonant unless it follows another consonant, | |
| 141 ** in which case it is a vowel. | |
| 142 ** | |
| 143 ** In these routine, the letters are in reverse order. So the 'y' rule | |
| 144 ** is that 'y' is a consonant unless it is followed by another | |
| 145 ** consonent. | |
| 146 */ | |
| 147 static int isVowel(const char*); | |
| 148 static int isConsonant(const char *z){ | |
| 149 int j; | |
| 150 char x = *z; | |
| 151 if( x==0 ) return 0; | |
| 152 assert( x>='a' && x<='z' ); | |
| 153 j = cType[x-'a']; | |
| 154 if( j<2 ) return j; | |
| 155 return z[1]==0 || isVowel(z + 1); | |
| 156 } | |
| 157 static int isVowel(const char *z){ | |
| 158 int j; | |
| 159 char x = *z; | |
| 160 if( x==0 ) return 0; | |
| 161 assert( x>='a' && x<='z' ); | |
| 162 j = cType[x-'a']; | |
| 163 if( j<2 ) return 1-j; | |
| 164 return isConsonant(z + 1); | |
| 165 } | |
| 166 | |
| 167 /* | |
| 168 ** Let any sequence of one or more vowels be represented by V and let | |
| 169 ** C be sequence of one or more consonants. Then every word can be | |
| 170 ** represented as: | |
| 171 ** | |
| 172 ** [C] (VC){m} [V] | |
| 173 ** | |
| 174 ** In prose: A word is an optional consonant followed by zero or | |
| 175 ** vowel-consonant pairs followed by an optional vowel. "m" is the | |
| 176 ** number of vowel consonant pairs. This routine computes the value | |
| 177 ** of m for the first i bytes of a word. | |
| 178 ** | |
| 179 ** Return true if the m-value for z is 1 or more. In other words, | |
| 180 ** return true if z contains at least one vowel that is followed | |
| 181 ** by a consonant. | |
| 182 ** | |
| 183 ** In this routine z[] is in reverse order. So we are really looking | |
| 184 ** for an instance of of a consonant followed by a vowel. | |
| 185 */ | |
| 186 static int m_gt_0(const char *z){ | |
| 187 while( isVowel(z) ){ z++; } | |
| 188 if( *z==0 ) return 0; | |
| 189 while( isConsonant(z) ){ z++; } | |
| 190 return *z!=0; | |
| 191 } | |
| 192 | |
| 193 /* Like mgt0 above except we are looking for a value of m which is | |
| 194 ** exactly 1 | |
| 195 */ | |
| 196 static int m_eq_1(const char *z){ | |
| 197 while( isVowel(z) ){ z++; } | |
| 198 if( *z==0 ) return 0; | |
| 199 while( isConsonant(z) ){ z++; } | |
| 200 if( *z==0 ) return 0; | |
| 201 while( isVowel(z) ){ z++; } | |
| 202 if( *z==0 ) return 1; | |
| 203 while( isConsonant(z) ){ z++; } | |
| 204 return *z==0; | |
| 205 } | |
| 206 | |
| 207 /* Like mgt0 above except we are looking for a value of m>1 instead | |
| 208 ** or m>0 | |
| 209 */ | |
| 210 static int m_gt_1(const char *z){ | |
| 211 while( isVowel(z) ){ z++; } | |
| 212 if( *z==0 ) return 0; | |
| 213 while( isConsonant(z) ){ z++; } | |
| 214 if( *z==0 ) return 0; | |
| 215 while( isVowel(z) ){ z++; } | |
| 216 if( *z==0 ) return 0; | |
| 217 while( isConsonant(z) ){ z++; } | |
| 218 return *z!=0; | |
| 219 } | |
| 220 | |
| 221 /* | |
| 222 ** Return TRUE if there is a vowel anywhere within z[0..n-1] | |
| 223 */ | |
| 224 static int hasVowel(const char *z){ | |
| 225 while( isConsonant(z) ){ z++; } | |
| 226 return *z!=0; | |
| 227 } | |
| 228 | |
| 229 /* | |
| 230 ** Return TRUE if the word ends in a double consonant. | |
| 231 ** | |
| 232 ** The text is reversed here. So we are really looking at | |
| 233 ** the first two characters of z[]. | |
| 234 */ | |
| 235 static int doubleConsonant(const char *z){ | |
| 236 return isConsonant(z) && z[0]==z[1] && isConsonant(z+1); | |
| 237 } | |
| 238 | |
| 239 /* | |
| 240 ** Return TRUE if the word ends with three letters which | |
| 241 ** are consonant-vowel-consonent and where the final consonant | |
| 242 ** is not 'w', 'x', or 'y'. | |
| 243 ** | |
| 244 ** The word is reversed here. So we are really checking the | |
| 245 ** first three letters and the first one cannot be in [wxy]. | |
| 246 */ | |
| 247 static int star_oh(const char *z){ | |
| 248 return | |
| 249 z[0]!=0 && isConsonant(z) && | |
| 250 z[0]!='w' && z[0]!='x' && z[0]!='y' && | |
| 251 z[1]!=0 && isVowel(z+1) && | |
| 252 z[2]!=0 && isConsonant(z+2); | |
| 253 } | |
| 254 | |
| 255 /* | |
| 256 ** If the word ends with zFrom and xCond() is true for the stem | |
| 257 ** of the word that preceeds the zFrom ending, then change the | |
| 258 ** ending to zTo. | |
| 259 ** | |
| 260 ** The input word *pz and zFrom are both in reverse order. zTo | |
| 261 ** is in normal order. | |
| 262 ** | |
| 263 ** Return TRUE if zFrom matches. Return FALSE if zFrom does not | |
| 264 ** match. Not that TRUE is returned even if xCond() fails and | |
| 265 ** no substitution occurs. | |
| 266 */ | |
| 267 static int stem( | |
| 268 char **pz, /* The word being stemmed (Reversed) */ | |
| 269 const char *zFrom, /* If the ending matches this... (Reversed) */ | |
| 270 const char *zTo, /* ... change the ending to this (not reversed) */ | |
| 271 int (*xCond)(const char*) /* Condition that must be true */ | |
| 272 ){ | |
| 273 char *z = *pz; | |
| 274 while( *zFrom && *zFrom==*z ){ z++; zFrom++; } | |
| 275 if( *zFrom!=0 ) return 0; | |
| 276 if( xCond && !xCond(z) ) return 1; | |
| 277 while( *zTo ){ | |
| 278 *(--z) = *(zTo++); | |
| 279 } | |
| 280 *pz = z; | |
| 281 return 1; | |
| 282 } | |
| 283 | |
| 284 /* | |
| 285 ** This is the fallback stemmer used when the porter stemmer is | |
| 286 ** inappropriate. The input word is copied into the output with | |
| 287 ** US-ASCII case folding. If the input word is too long (more | |
| 288 ** than 20 bytes if it contains no digits or more than 6 bytes if | |
| 289 ** it contains digits) then word is truncated to 20 or 6 bytes | |
| 290 ** by taking 10 or 3 bytes from the beginning and end. | |
| 291 */ | |
| 292 static void copy_stemmer(const char *zIn, int nIn, char *zOut, int *pnOut){ | |
| 293 int i, mx, j; | |
| 294 int hasDigit = 0; | |
| 295 for(i=0; i<nIn; i++){ | |
| 296 int c = zIn[i]; | |
| 297 if( c>='A' && c<='Z' ){ | |
| 298 zOut[i] = c - 'A' + 'a'; | |
| 299 }else{ | |
| 300 if( c>='0' && c<='9' ) hasDigit = 1; | |
| 301 zOut[i] = c; | |
| 302 } | |
| 303 } | |
| 304 mx = hasDigit ? 3 : 10; | |
| 305 if( nIn>mx*2 ){ | |
| 306 for(j=mx, i=nIn-mx; i<nIn; i++, j++){ | |
| 307 zOut[j] = zOut[i]; | |
| 308 } | |
| 309 i = j; | |
| 310 } | |
| 311 zOut[i] = 0; | |
| 312 *pnOut = i; | |
| 313 } | |
| 314 | |
| 315 | |
| 316 /* | |
| 317 ** Stem the input word zIn[0..nIn-1]. Store the output in zOut. | |
| 318 ** zOut is at least big enough to hold nIn bytes. Write the actual | |
| 319 ** size of the output word (exclusive of the '\0' terminator) into *pnOut. | |
| 320 ** | |
| 321 ** Any upper-case characters in the US-ASCII character set ([A-Z]) | |
| 322 ** are converted to lower case. Upper-case UTF characters are | |
| 323 ** unchanged. | |
| 324 ** | |
| 325 ** Words that are longer than about 20 bytes are stemmed by retaining | |
| 326 ** a few bytes from the beginning and the end of the word. If the | |
| 327 ** word contains digits, 3 bytes are taken from the beginning and | |
| 328 ** 3 bytes from the end. For long words without digits, 10 bytes | |
| 329 ** are taken from each end. US-ASCII case folding still applies. | |
| 330 ** | |
| 331 ** If the input word contains not digits but does characters not | |
| 332 ** in [a-zA-Z] then no stemming is attempted and this routine just | |
| 333 ** copies the input into the input into the output with US-ASCII | |
| 334 ** case folding. | |
| 335 ** | |
| 336 ** Stemming never increases the length of the word. So there is | |
| 337 ** no chance of overflowing the zOut buffer. | |
| 338 */ | |
| 339 static void porter_stemmer(const char *zIn, int nIn, char *zOut, int *pnOut){ | |
| 340 int i, j, c; | |
| 341 char zReverse[28]; | |
| 342 char *z, *z2; | |
| 343 if( nIn<3 || nIn>=sizeof(zReverse)-7 ){ | |
| 344 /* The word is too big or too small for the porter stemmer. | |
| 345 ** Fallback to the copy stemmer */ | |
| 346 copy_stemmer(zIn, nIn, zOut, pnOut); | |
| 347 return; | |
| 348 } | |
| 349 for(i=0, j=sizeof(zReverse)-6; i<nIn; i++, j--){ | |
| 350 c = zIn[i]; | |
| 351 if( c>='A' && c<='Z' ){ | |
| 352 zReverse[j] = c + 'a' - 'A'; | |
| 353 }else if( c>='a' && c<='z' ){ | |
| 354 zReverse[j] = c; | |
| 355 }else{ | |
| 356 /* The use of a character not in [a-zA-Z] means that we fallback | |
| 357 ** to the copy stemmer */ | |
| 358 copy_stemmer(zIn, nIn, zOut, pnOut); | |
| 359 return; | |
| 360 } | |
| 361 } | |
| 362 memset(&zReverse[sizeof(zReverse)-5], 0, 5); | |
| 363 z = &zReverse[j+1]; | |
| 364 | |
| 365 | |
| 366 /* Step 1a */ | |
| 367 if( z[0]=='s' ){ | |
| 368 if( | |
| 369 !stem(&z, "sess", "ss", 0) && | |
| 370 !stem(&z, "sei", "i", 0) && | |
| 371 !stem(&z, "ss", "ss", 0) | |
| 372 ){ | |
| 373 z++; | |
| 374 } | |
| 375 } | |
| 376 | |
| 377 /* Step 1b */ | |
| 378 z2 = z; | |
| 379 if( stem(&z, "dee", "ee", m_gt_0) ){ | |
| 380 /* Do nothing. The work was all in the test */ | |
| 381 }else if( | |
| 382 (stem(&z, "gni", "", hasVowel) || stem(&z, "de", "", hasVowel)) | |
| 383 && z!=z2 | |
| 384 ){ | |
| 385 if( stem(&z, "ta", "ate", 0) || | |
| 386 stem(&z, "lb", "ble", 0) || | |
| 387 stem(&z, "zi", "ize", 0) ){ | |
| 388 /* Do nothing. The work was all in the test */ | |
| 389 }else if( doubleConsonant(z) && (*z!='l' && *z!='s' && *z!='z') ){ | |
| 390 z++; | |
| 391 }else if( m_eq_1(z) && star_oh(z) ){ | |
| 392 *(--z) = 'e'; | |
| 393 } | |
| 394 } | |
| 395 | |
| 396 /* Step 1c */ | |
| 397 if( z[0]=='y' && hasVowel(z+1) ){ | |
| 398 z[0] = 'i'; | |
| 399 } | |
| 400 | |
| 401 /* Step 2 */ | |
| 402 switch( z[1] ){ | |
| 403 case 'a': | |
| 404 stem(&z, "lanoita", "ate", m_gt_0) || | |
| 405 stem(&z, "lanoit", "tion", m_gt_0); | |
| 406 break; | |
| 407 case 'c': | |
| 408 stem(&z, "icne", "ence", m_gt_0) || | |
| 409 stem(&z, "icna", "ance", m_gt_0); | |
| 410 break; | |
| 411 case 'e': | |
| 412 stem(&z, "rezi", "ize", m_gt_0); | |
| 413 break; | |
| 414 case 'g': | |
| 415 stem(&z, "igol", "log", m_gt_0); | |
| 416 break; | |
| 417 case 'l': | |
| 418 stem(&z, "ilb", "ble", m_gt_0) || | |
| 419 stem(&z, "illa", "al", m_gt_0) || | |
| 420 stem(&z, "iltne", "ent", m_gt_0) || | |
| 421 stem(&z, "ile", "e", m_gt_0) || | |
| 422 stem(&z, "ilsuo", "ous", m_gt_0); | |
| 423 break; | |
| 424 case 'o': | |
| 425 stem(&z, "noitazi", "ize", m_gt_0) || | |
| 426 stem(&z, "noita", "ate", m_gt_0) || | |
| 427 stem(&z, "rota", "ate", m_gt_0); | |
| 428 break; | |
| 429 case 's': | |
| 430 stem(&z, "msila", "al", m_gt_0) || | |
| 431 stem(&z, "ssenevi", "ive", m_gt_0) || | |
| 432 stem(&z, "ssenluf", "ful", m_gt_0) || | |
| 433 stem(&z, "ssensuo", "ous", m_gt_0); | |
| 434 break; | |
| 435 case 't': | |
| 436 stem(&z, "itila", "al", m_gt_0) || | |
| 437 stem(&z, "itivi", "ive", m_gt_0) || | |
| 438 stem(&z, "itilib", "ble", m_gt_0); | |
| 439 break; | |
| 440 } | |
| 441 | |
| 442 /* Step 3 */ | |
| 443 switch( z[0] ){ | |
| 444 case 'e': | |
| 445 stem(&z, "etaci", "ic", m_gt_0) || | |
| 446 stem(&z, "evita", "", m_gt_0) || | |
| 447 stem(&z, "ezila", "al", m_gt_0); | |
| 448 break; | |
| 449 case 'i': | |
| 450 stem(&z, "itici", "ic", m_gt_0); | |
| 451 break; | |
| 452 case 'l': | |
| 453 stem(&z, "laci", "ic", m_gt_0) || | |
| 454 stem(&z, "luf", "", m_gt_0); | |
| 455 break; | |
| 456 case 's': | |
| 457 stem(&z, "ssen", "", m_gt_0); | |
| 458 break; | |
| 459 } | |
| 460 | |
| 461 /* Step 4 */ | |
| 462 switch( z[1] ){ | |
| 463 case 'a': | |
| 464 if( z[0]=='l' && m_gt_1(z+2) ){ | |
| 465 z += 2; | |
| 466 } | |
| 467 break; | |
| 468 case 'c': | |
| 469 if( z[0]=='e' && z[2]=='n' && (z[3]=='a' || z[3]=='e') && m_gt_1(z+4) ){ | |
| 470 z += 4; | |
| 471 } | |
| 472 break; | |
| 473 case 'e': | |
| 474 if( z[0]=='r' && m_gt_1(z+2) ){ | |
| 475 z += 2; | |
| 476 } | |
| 477 break; | |
| 478 case 'i': | |
| 479 if( z[0]=='c' && m_gt_1(z+2) ){ | |
| 480 z += 2; | |
| 481 } | |
| 482 break; | |
| 483 case 'l': | |
| 484 if( z[0]=='e' && z[2]=='b' && (z[3]=='a' || z[3]=='i') && m_gt_1(z+4) ){ | |
| 485 z += 4; | |
| 486 } | |
| 487 break; | |
| 488 case 'n': | |
| 489 if( z[0]=='t' ){ | |
| 490 if( z[2]=='a' ){ | |
| 491 if( m_gt_1(z+3) ){ | |
| 492 z += 3; | |
| 493 } | |
| 494 }else if( z[2]=='e' ){ | |
| 495 stem(&z, "tneme", "", m_gt_1) || | |
| 496 stem(&z, "tnem", "", m_gt_1) || | |
| 497 stem(&z, "tne", "", m_gt_1); | |
| 498 } | |
| 499 } | |
| 500 break; | |
| 501 case 'o': | |
| 502 if( z[0]=='u' ){ | |
| 503 if( m_gt_1(z+2) ){ | |
| 504 z += 2; | |
| 505 } | |
| 506 }else if( z[3]=='s' || z[3]=='t' ){ | |
| 507 stem(&z, "noi", "", m_gt_1); | |
| 508 } | |
| 509 break; | |
| 510 case 's': | |
| 511 if( z[0]=='m' && z[2]=='i' && m_gt_1(z+3) ){ | |
| 512 z += 3; | |
| 513 } | |
| 514 break; | |
| 515 case 't': | |
| 516 stem(&z, "eta", "", m_gt_1) || | |
| 517 stem(&z, "iti", "", m_gt_1); | |
| 518 break; | |
| 519 case 'u': | |
| 520 if( z[0]=='s' && z[2]=='o' && m_gt_1(z+3) ){ | |
| 521 z += 3; | |
| 522 } | |
| 523 break; | |
| 524 case 'v': | |
| 525 case 'z': | |
| 526 if( z[0]=='e' && z[2]=='i' && m_gt_1(z+3) ){ | |
| 527 z += 3; | |
| 528 } | |
| 529 break; | |
| 530 } | |
| 531 | |
| 532 /* Step 5a */ | |
| 533 if( z[0]=='e' ){ | |
| 534 if( m_gt_1(z+1) ){ | |
| 535 z++; | |
| 536 }else if( m_eq_1(z+1) && !star_oh(z+1) ){ | |
| 537 z++; | |
| 538 } | |
| 539 } | |
| 540 | |
| 541 /* Step 5b */ | |
| 542 if( m_gt_1(z) && z[0]=='l' && z[1]=='l' ){ | |
| 543 z++; | |
| 544 } | |
| 545 | |
| 546 /* z[] is now the stemmed word in reverse order. Flip it back | |
| 547 ** around into forward order and return. | |
| 548 */ | |
| 549 *pnOut = i = strlen(z); | |
| 550 zOut[i] = 0; | |
| 551 while( *z ){ | |
| 552 zOut[--i] = *(z++); | |
| 553 } | |
| 554 } | |
| 555 | |
| 556 /* | |
| 557 ** Characters that can be part of a token. We assume any character | |
| 558 ** whose value is greater than 0x80 (any UTF character) can be | |
| 559 ** part of a token. In other words, delimiters all must have | |
| 560 ** values of 0x7f or lower. | |
| 561 */ | |
| 562 static const char porterIdChar[] = { | |
| 563 /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */ | |
| 564 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */ | |
| 565 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */ | |
| 566 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */ | |
| 567 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */ | |
| 568 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */ | |
| 569 }; | |
| 570 #define isDelim(C) (((ch=C)&0x80)==0 && (ch<0x30 || !porterIdChar[ch-0x30])) | |
| 571 | |
| 572 /* | |
| 573 ** Extract the next token from a tokenization cursor. The cursor must | |
| 574 ** have been opened by a prior call to porterOpen(). | |
| 575 */ | |
| 576 static int porterNext( | |
| 577 sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by porterOpen */ | |
| 578 const char **pzToken, /* OUT: *pzToken is the token text */ | |
| 579 int *pnBytes, /* OUT: Number of bytes in token */ | |
| 580 int *piStartOffset, /* OUT: Starting offset of token */ | |
| 581 int *piEndOffset, /* OUT: Ending offset of token */ | |
| 582 int *piPosition /* OUT: Position integer of token */ | |
| 583 ){ | |
| 584 porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor; | |
| 585 const char *z = c->zInput; | |
| 586 | |
| 587 while( c->iOffset<c->nInput ){ | |
| 588 int iStartOffset, ch; | |
| 589 | |
| 590 /* Scan past delimiter characters */ | |
| 591 while( c->iOffset<c->nInput && isDelim(z[c->iOffset]) ){ | |
| 592 c->iOffset++; | |
| 593 } | |
| 594 | |
| 595 /* Count non-delimiter characters. */ | |
| 596 iStartOffset = c->iOffset; | |
| 597 while( c->iOffset<c->nInput && !isDelim(z[c->iOffset]) ){ | |
| 598 c->iOffset++; | |
| 599 } | |
| 600 | |
| 601 if( c->iOffset>iStartOffset ){ | |
| 602 int n = c->iOffset-iStartOffset; | |
| 603 if( n>c->nAllocated ){ | |
| 604 c->nAllocated = n+20; | |
| 605 c->zToken = sqlite3_realloc(c->zToken, c->nAllocated); | |
| 606 if( c->zToken==NULL ) return SQLITE_NOMEM; | |
| 607 } | |
| 608 porter_stemmer(&z[iStartOffset], n, c->zToken, pnBytes); | |
| 609 *pzToken = c->zToken; | |
| 610 *piStartOffset = iStartOffset; | |
| 611 *piEndOffset = c->iOffset; | |
| 612 *piPosition = c->iToken++; | |
| 613 return SQLITE_OK; | |
| 614 } | |
| 615 } | |
| 616 return SQLITE_DONE; | |
| 617 } | |
| 618 | |
| 619 /* | |
| 620 ** The set of routines that implement the porter-stemmer tokenizer | |
| 621 */ | |
| 622 static const sqlite3_tokenizer_module porterTokenizerModule = { | |
| 623 0, | |
| 624 porterCreate, | |
| 625 porterDestroy, | |
| 626 porterOpen, | |
| 627 porterClose, | |
| 628 porterNext, | |
| 629 }; | |
| 630 | |
| 631 /* | |
| 632 ** Allocate a new porter tokenizer. Return a pointer to the new | |
| 633 ** tokenizer in *ppModule | |
| 634 */ | |
| 635 void sqlite3Fts3PorterTokenizerModule( | |
| 636 sqlite3_tokenizer_module const**ppModule | |
| 637 ){ | |
| 638 *ppModule = &porterTokenizerModule; | |
| 639 } | |
| 640 | |
| 641 #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ | |
| OLD | NEW |