Home | History | Annotate | Download | only in fts2
      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 FTS2 module is being built as an extension
     20 **       (in which case SQLITE_CORE is not defined), or
     21 **
     22 **     * The FTS2 module is being built into the core of
     23 **       SQLite (in which case SQLITE_ENABLE_FTS2 is defined).
     24 */
     25 #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS2)
     26 
     27 
     28 #include <assert.h>
     29 #include <stdlib.h>
     30 #include <stdio.h>
     31 #include <string.h>
     32 
     33 #include "fts2_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 sqlite3Fts2PorterTokenizerModule(
    636   sqlite3_tokenizer_module const**ppModule
    637 ){
    638   *ppModule = &porterTokenizerModule;
    639 }
    640 
    641 #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS2) */
    642