Home | History | Annotate | Download | only in src
      1 /*---------------------------------------------------------------------------*
      2  *  VocabularyImpl.c                                                         *
      3  *                                                                           *
      4  *  Copyright 2007, 2008 Nuance Communciations, Inc.                         *
      5  *                                                                           *
      6  *  Licensed under the Apache License, Version 2.0 (the 'License');          *
      7  *  you may not use this file except in compliance with the License.         *
      8  *                                                                           *
      9  *  You may obtain a copy of the License at                                  *
     10  *      http://www.apache.org/licenses/LICENSE-2.0                           *
     11  *                                                                           *
     12  *  Unless required by applicable law or agreed to in writing, software      *
     13  *  distributed under the License is distributed on an 'AS IS' BASIS,        *
     14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
     15  *  See the License for the specific language governing permissions and      *
     16  *  limitations under the License.                                           *
     17  *                                                                           *
     18  *---------------------------------------------------------------------------*/
     19 
     20 #include "ESR_Session.h"
     21 #include "SR_Vocabulary.h"
     22 #include "SR_VocabularyImpl.h"
     23 #include "passert.h"
     24 #include "plog.h"
     25 #include "ptypes.h"
     26 #include "pmemory.h"
     27 
     28 //#define DEBUG 1
     29 #define MAX_PRON_LEN 256
     30 #define MAX_WORD_LEN    40
     31 #define MTAG NULL
     32 #define MAX_PHONE_LEN 4
     33 #define DO_DEFER_LOADING_UNTIL_LOOKUPS 1
     34 
     35 static PINLINE LCHAR* get_first_word(LCHAR* curr, LCHAR* end);
     36 static PINLINE LCHAR* get_next_word(LCHAR* curr, LCHAR* end);
     37 static ESR_ReturnCode run_ttt(const LCHAR *input_sentence, LCHAR *output_sentence, int *text_length);
     38 
     39 #define MAX_NUM_PRONS 4
     40 #define LSTRDUP(src) LSTRCPY(CALLOC(LSTRLEN(src)+1, sizeof(LCHAR), "srec.Vocabulary.LSTRDUP"), (src))
     41 #define LSTRFREE(src) FREE(src)
     42 
     43 /**
     44  * Creates a new vocabulary but does not set the locale.
     45  *
     46  * @param self SR_Vocabulary handle
     47  */
     48 #ifdef USE_TTP
     49 ESR_ReturnCode SR_CreateG2P(SR_Vocabulary* self)
     50 {
     51   ESR_ReturnCode      rc = ESR_SUCCESS;
     52   SWIsltsResult       res = SWIsltsSuccess;
     53   SR_VocabularyImpl * impl = (SR_VocabularyImpl*) self;
     54   LCHAR               szG2PDataFile[P_PATH_MAX];
     55   size_t              len = P_PATH_MAX;
     56   ESR_BOOL                bG2P = ESR_TRUE;
     57 
     58      rc = ESR_SessionGetBool ( L("G2P.Available"), &bG2P );
     59      if ( rc != ESR_SUCCESS )
     60        {
     61 	 PLogError(L("ESR_FATAL_ERROR: ESR_SessionGetBool() - G2P.Available fails with return code %d\n"), rc);
     62 	 return rc;
     63        }
     64      if ( bG2P == ESR_FALSE )
     65        {
     66 	 impl->hSlts = NULL;
     67 	 return ESR_SUCCESS;
     68        }
     69 
     70      rc = ESR_SessionGetLCHAR ( L("G2P.Data"), szG2PDataFile, &len );
     71      if ( rc != ESR_SUCCESS )
     72        {
     73 	 PLogError(L("ESR_FATAL_ERROR: ESR_SessionGetLCHAR() - G2P.Data fails with return code %d\n"), rc);
     74 	 return rc;
     75      }
     76      rc = ESR_SessionPrefixWithBaseDirectory(szG2PDataFile, &len);
     77      if ( rc != ESR_SUCCESS )
     78        {
     79 	 PLogError(L("ESR_FATAL_ERROR: ESR_SessionPrefixWithBaseDirectory() - G2P.Data fails with return code %d\n"), rc);
     80 	 return rc;
     81        }
     82 
     83      res = SWIsltsInit();
     84      if (res == SWIsltsSuccess)
     85        {
     86 	 /* data_file: en-US-ttp.data */
     87 	 res = SWIsltsOpen(&(impl->hSlts), szG2PDataFile);
     88 	 if (res != SWIsltsSuccess)
     89 	   {
     90 	     PLogError(L("ESR_FATAL_ERROR: SWIsltsOpen( ) fails with return code %d\n"), res);
     91 	     FREE(impl);
     92 	     return ESR_FATAL_ERROR;
     93 	   }
     94        }
     95      else
     96      {
     97        PLogError(L("ESR_FATAL_ERROR: SWIsltsInit( ) fails with return code %d\n"), res);
     98        FREE(impl);
     99        return ESR_FATAL_ERROR;
    100      }
    101      return rc;
    102 }
    103 
    104 ESR_ReturnCode SR_DestroyG2P(SR_Vocabulary* self)
    105 {
    106   ESR_ReturnCode      rc = ESR_SUCCESS;
    107   SWIsltsResult       res = SWIsltsSuccess;
    108   SR_VocabularyImpl * impl = (SR_VocabularyImpl*) self;
    109   ESR_BOOL                bG2P = ESR_TRUE;
    110 
    111   rc = ESR_SessionGetBool ( L("G2P.Available"), &bG2P );
    112   if ( rc != ESR_SUCCESS )
    113      {
    114        PLogError(L("ESR_FATAL_ERROR: ESR_SessionGetBool() - G2P.Available fails with return code %d\n"), rc);
    115        return rc;
    116      }
    117   if ( bG2P == ESR_FALSE || impl->hSlts == NULL)
    118     {
    119       return ESR_SUCCESS;
    120     }
    121 
    122   res = SWIsltsClose(impl->hSlts);
    123   if (res == SWIsltsSuccess)
    124     {
    125       res = SWIsltsTerm();
    126       if (res != SWIsltsSuccess)
    127 	{
    128 	  PLogError(L("ESR_FATAL_ERROR: SWIsltsTerm( ) fails with return code %d\n"), res);
    129 	  rc = ESR_FATAL_ERROR;
    130           }
    131     }
    132   else
    133     {
    134       PLogError(L("ESR_FATAL_ERROR: SWIsltsClose( ) fails with return code %d\n"), res);
    135       rc = ESR_FATAL_ERROR;
    136     }
    137   return rc;
    138 }
    139 #endif /* USE_TTP */
    140 
    141 /**
    142  * Creates a new vocabulary but does not set the locale.
    143  *
    144  * @param self SR_Vocabulary handle
    145  */
    146 ESR_ReturnCode SR_VocabularyCreateImpl(SR_Vocabulary** self)
    147 {
    148   SR_VocabularyImpl* impl;
    149 
    150   if (self==NULL)
    151     {
    152       PLogError(L("ESR_INVALID_ARGUMENT"));
    153       return ESR_INVALID_ARGUMENT;
    154     }
    155   impl = NEW(SR_VocabularyImpl, MTAG);
    156   if (impl==NULL)
    157     {
    158       PLogError(L("ESR_OUT_OF_MEMORY"));
    159       return ESR_OUT_OF_MEMORY;
    160     }
    161 
    162   impl->Interface.save = &SR_VocabularySaveImpl;
    163   impl->Interface.getPronunciation = &SR_VocabularyGetPronunciationImpl;
    164      impl->Interface.getLanguage = &SR_VocabularyGetLanguageImpl;
    165      impl->Interface.destroy = &SR_VocabularyDestroyImpl;
    166      impl->vocabulary = NULL;
    167 
    168      *self = (SR_Vocabulary*) impl;
    169      impl->hSlts = NULL;
    170      return ESR_SUCCESS;
    171 }
    172 
    173 ESR_ReturnCode SR_VocabularyDestroyImpl(SR_Vocabulary* self)
    174 {
    175   SR_VocabularyImpl* impl = (SR_VocabularyImpl*) self;
    176 
    177 #ifdef USE_TTP
    178   SR_DestroyG2P(self);
    179 #endif
    180 
    181      if (impl->vocabulary!=NULL)
    182        {
    183 	 CA_UnloadDictionary(impl->vocabulary);
    184 	 CA_FreeVocabulary(impl->vocabulary);
    185 	 impl->vocabulary = NULL;
    186        }
    187 	   LSTRFREE(impl->filename);
    188      FREE(impl);
    189      return ESR_SUCCESS;
    190 }
    191 
    192 ESR_ReturnCode sr_vocabularyloadimpl_for_real(SR_VocabularyImpl* impl)
    193 {
    194 	ESR_ReturnCode rc = ESR_SUCCESS;
    195 	ESR_BOOL sessionExists = ESR_FALSE;
    196   LCHAR vocabulary[P_PATH_MAX];
    197   size_t len;
    198 
    199      impl->vocabulary = CA_AllocateVocabulary();
    200      if (impl->vocabulary==NULL)
    201        {
    202 	 rc = ESR_OUT_OF_MEMORY;
    203 	 PLogError(ESR_rc2str(rc));
    204 	 goto CLEANUP;
    205        }
    206 
    207      CHKLOG(rc, ESR_SessionExists(&sessionExists));
    208 
    209      if (sessionExists)
    210        {
    211           LSTRCPY(vocabulary, impl->filename);
    212           len = P_PATH_MAX;
    213           CHKLOG(rc, ESR_SessionPrefixWithBaseDirectory(vocabulary, &len));
    214        }
    215      else
    216        LSTRCPY(vocabulary, impl->filename);
    217 
    218      CA_LoadDictionary(impl->vocabulary, vocabulary, L(""), &impl->locale);
    219      if(impl->vocabulary->is_loaded == False /*(booldata)*/ ) {
    220        CHKLOG(rc, ESR_INVALID_ARGUMENT);
    221      }
    222      impl->ttp_lang = TTP_LANG(impl->locale);
    223 
    224 #ifdef USE_TTP
    225      rc = SR_CreateG2P((SR_Vocabulary*)impl);
    226 	 if (rc != ESR_SUCCESS) {
    227           goto CLEANUP;
    228      }
    229 #endif
    230 
    231 CLEANUP:
    232 	 return rc;
    233 }
    234 
    235 ESR_ReturnCode SR_VocabularyLoadImpl(const LCHAR* filename, SR_Vocabulary** self)
    236 {
    237   SR_Vocabulary* Interface;
    238   SR_VocabularyImpl* impl;
    239   ESR_ReturnCode rc;
    240 
    241      CHK(rc, SR_VocabularyCreateImpl(&Interface));
    242      impl = (SR_VocabularyImpl*) Interface;
    243 #if DO_DEFER_LOADING_UNTIL_LOOKUPS
    244 	 impl->vocabulary = NULL;
    245 	 impl->ttp_lang = NULL;
    246 	 impl->filename = LSTRDUP( filename);
    247 	 impl->locale = ESR_LOCALE_EN_US; // default really
    248 	 impl->hSlts = NULL;
    249 #else
    250 	 impl->filename = LSTRDUP( filename);
    251 	 CHKLOG( rc, sr_vocabularyloadimpl_for_real( impl));
    252 #endif
    253 
    254      *self = Interface;
    255      return ESR_SUCCESS;
    256  CLEANUP:
    257      Interface->destroy(Interface);
    258      return rc;
    259 }
    260 
    261 ESR_ReturnCode SR_VocabularySaveImpl(SR_Vocabulary* self, const LCHAR* filename)
    262 {
    263   /* TODO: complete */
    264   return ESR_SUCCESS;
    265 }
    266 
    267 /* internal util function prototype */
    268 /* we split the string on all non-alphanum and "'" which
    269 is handled below */
    270 #define LSINGLEQUOTE L('\'')
    271 int split_on_nonalphanum(LCHAR* toSplit, LCHAR** end, const ESR_Locale locale)
    272 {
    273   int nsplits = 0;
    274   LCHAR* _next = toSplit;
    275     while(*_next)
    276     {
    277 		do {
    278 			if(*_next == LSINGLEQUOTE && locale == ESR_LOCALE_EN_US) {
    279 				if(_next[1] != 't' && _next[1] != 's') break;
    280 				else if( LISALNUM(_next[2])) break; // LISDIGIT
    281 				else { *_next++; continue; }
    282 			}
    283 			if(!*_next || !LISALNUM(*_next)) break;
    284 			*_next++;
    285 		} while(1);
    286       // FORMERLY:  while(*_next && LISALNUM(*_next))     _next++;
    287 
    288       /* check if I am at the last word or not */
    289       if(*_next)
    290       {
    291         *_next = 0; /* replace split_char with '\0' the word */
    292 		nsplits++;
    293         _next++;    /* point to first char of next word */
    294 		*end = _next; /* we'll be push forward later, if there's content here!*/
    295       }
    296       else
    297         *end = _next;
    298     }
    299 	return nsplits;
    300 }
    301 
    302 void join(LCHAR* toJoin, LCHAR* end, LCHAR join_char)
    303 {
    304   LCHAR* _next;
    305     for(_next = toJoin; _next<end; _next++)
    306 		if(*_next == 0) *_next = join_char;
    307 }
    308 
    309 size_t get_num_prons( const LCHAR* word_prons, const LCHAR** word_pron_ptr, int max_num_prons)
    310 {
    311   int num_prons = 0;
    312   while(word_prons && *word_prons) {
    313     word_pron_ptr[ num_prons++] = word_prons;
    314     if(num_prons >= max_num_prons) break;
    315     while( *word_prons) word_prons++;
    316     word_prons++;
    317   }
    318   return num_prons;
    319 }
    320 
    321 /* This function is used from multi-word phrases, such as "mike smith".  We
    322    build up the pronunication of the phrase, by appending the pronunciation
    323    of each word.  We need to handle the cases of multiple prons for "mike"
    324    and multiple prons for "smith".  For simple cases we try to run faster
    325    code. */
    326 
    327 int append_to_each_with_joiner( LCHAR* phrase_prons, const LCHAR* word_prons, const LCHAR joiner, size_t max_len, size_t* len)
    328 {
    329   LCHAR* word_pron_ptr[MAX_NUM_PRONS];
    330   LCHAR* phrase_pron_ptr[MAX_NUM_PRONS];
    331   LCHAR *dst, *max_dst;
    332   const LCHAR *src;
    333   size_t nphrase_prons = get_num_prons( phrase_prons, (const LCHAR**)phrase_pron_ptr, MAX_NUM_PRONS);
    334   size_t nword_prons = get_num_prons( word_prons, (const LCHAR**)word_pron_ptr, MAX_NUM_PRONS);
    335   max_dst = phrase_prons+max_len-3;
    336 
    337   if( nword_prons == 0)
    338     return 0;
    339   else if(nphrase_prons == 0) {
    340 	for(src=word_prons,dst=phrase_prons; src && *src; ) {
    341 		for( ; *src && dst<max_dst; ) {
    342 			*dst++ = *src++;
    343 		}
    344       *dst++ = *src++; // copy the null
    345     }
    346     *dst = 0; // add a double-null
    347 	*len = dst-phrase_prons;
    348     return 0;
    349   }
    350   else if(nphrase_prons == 1 && nword_prons == 1) {
    351     for(dst=phrase_prons; *dst; ) dst++;
    352     if(joiner!=L('\0')) *dst++ = joiner;
    353     for(src=word_prons; *src && dst<max_dst; ) *dst++ = *src++;
    354     *dst++ = 0;
    355     *dst = 0; // add a double-null
    356 	*len = dst-phrase_prons;
    357     return 0;
    358   }
    359   else  {
    360     size_t i,j;
    361     LCHAR *phrase_pron_dups[MAX_NUM_PRONS];
    362     LCHAR *dst_good_end = phrase_prons+1;
    363     for(i=0;i<nphrase_prons; i++)
    364       phrase_pron_dups[i] = LSTRDUP( phrase_pron_ptr[i]);
    365     dst = phrase_prons;
    366     for(i=0;i<nphrase_prons; i++) {
    367       for(j=0; j<nword_prons; j++) {
    368 	for(src=phrase_pron_dups[i]; *src && dst<max_dst; ) *dst++=*src++;
    369 	if(dst>max_dst) break;
    370 	if(joiner!=L('\0')) *dst++ = joiner;
    371 	for(src=word_pron_ptr[j]; *src && dst<max_dst; ) *dst++=*src++;
    372 	if(dst>max_dst) break;
    373 	*dst++ = 0;
    374 	dst_good_end = dst;
    375       }
    376     }
    377     *dst_good_end++ = 0; // double-null terminator
    378     for(i=0; i<nphrase_prons; i++) LSTRFREE( phrase_pron_dups[i]);
    379     return 0;
    380   }
    381 }
    382 
    383 PINLINE LCHAR* get_first_word(LCHAR* curr, LCHAR* end)
    384 {
    385   while(*curr==L('\0') && curr<end) curr++;
    386   return curr;
    387 }
    388 
    389 PINLINE LCHAR* get_next_word(LCHAR* curr, LCHAR* end)
    390 {
    391   while(*curr) curr++;
    392   if(curr<end)  curr++;
    393   while( !*curr && curr<end) curr++;
    394   return curr;
    395 }
    396 
    397 /*
    398   For each word in a phrase (words separated by spaces)
    399 
    400   if the complete word is in the dictionary
    401   return pron
    402   else
    403   if the word contains '_', split the word into parts
    404   and check if parts are in the dictionary.
    405   if none of the parts are in the dictionary,
    406   reassemble the parts and pass the whole thing to TTP
    407   else
    408   build the pron by concat of TTP pron and dictionary pron for individual parts
    409 */
    410 ESR_ReturnCode SR_VocabularyGetPronunciationImpl(SR_Vocabulary* self, const LCHAR* phrase, LCHAR* pronunciation, size_t* pronunciation_len)
    411 {
    412   SR_VocabularyImpl* impl = (SR_VocabularyImpl*) self;
    413   /* copy of phrase */
    414   LCHAR copy_of_phrase[MAX_PRON_LEN];
    415 
    416   /* pointer to curr phoneme output */
    417   LCHAR* curr_phoneme = pronunciation;
    418   // size_t pronunciation_len = *len;
    419 
    420   ESR_ReturnCode nEsrRes = ESR_SUCCESS;
    421   int text_length;
    422   size_t len;
    423   int nsplits;
    424 
    425 #ifdef USE_TTP
    426   SWIsltsResult      res = SWIsltsSuccess;
    427   SWIsltsTranscription  *pTranscriptions = NULL;
    428   int nNbrOfTranscriptions = 0;
    429 #endif /* USE_TTP */
    430   /* full inf pron after conversion */
    431   LCHAR infpron[MAX_PRON_LEN];
    432   LCHAR* p_infpron;
    433   LCHAR* curr;     /* pointer to current word */
    434   LCHAR* end = 0;   /* pointer to end of phrase */
    435 
    436   if(self == NULL || phrase == NULL)
    437     {
    438       PLogError(L("ESR_INVALID_ARGUMENT"));
    439       return ESR_INVALID_ARGUMENT;
    440     }
    441 
    442   if( LSTRLEN(phrase) >= MAX_PRON_LEN)
    443 	return ESR_ARGUMENT_OUT_OF_BOUNDS;
    444 
    445 #if DO_DEFER_LOADING_UNTIL_LOOKUPS
    446   if( impl->vocabulary == NULL) {
    447     CHKLOG( nEsrRes, sr_vocabularyloadimpl_for_real( impl));
    448   }
    449 #endif
    450 
    451   /* by default, check the whole word entry first (regardless of underscores) */
    452   if( CA_GetEntryInDictionary(impl->vocabulary, phrase, pronunciation, (int*)&len, MAX_PRON_LEN)) {
    453     // len includes the final null, but not the double-null
    454     *pronunciation_len = LSTRLEN(pronunciation)+1;
    455     // look for double-null terminator
    456     while( pronunciation[ (*pronunciation_len)] != L('\0'))
    457       *pronunciation_len += LSTRLEN( pronunciation + (*pronunciation_len)) + 1;
    458 
    459     return ESR_SUCCESS;
    460   }
    461 
    462   /*************************/
    463   /* split digit strings */
    464   text_length = MAX_PRON_LEN;
    465   nEsrRes = run_ttt(phrase, copy_of_phrase, &text_length);
    466   if (nEsrRes != ESR_SUCCESS)
    467     {
    468       PLogError(L("ESR_FATAL_ERROR: run_ttt( ) fails with return code %d\n"), nEsrRes);
    469       return nEsrRes;
    470     }
    471 
    472   len = 0;
    473   *curr_phoneme = L('\0');
    474   if( *pronunciation_len>=12) curr_phoneme[1] = L('\0');
    475   else return ESR_INVALID_ARGUMENT;
    476 
    477   /*************************/
    478   /* split into word parts */
    479   nsplits = split_on_nonalphanum(copy_of_phrase, &end, impl->locale);
    480 
    481   /******************************************************/
    482   /* if none of the words are found in the dictionary, then
    483      reassemble and get the TTP pron for the whole thing */
    484   curr=get_first_word(copy_of_phrase,end);
    485   /* check if there are any valid characters at all */
    486   if(!curr || !*curr)
    487     return ESR_INVALID_ARGUMENT;
    488   /* now loop over all words in the phrase */
    489   for(   ; *curr; curr = get_next_word(curr,end))
    490     {
    491       LCHAR* squote = NULL;
    492       p_infpron = infpron;
    493 
    494       /* by default, check the whole word entry first (regardless of LSINGLEQUOTE) */
    495       if(CA_GetEntryInDictionary(impl->vocabulary, curr, p_infpron, (int*)&len, MAX_PRON_LEN))
    496         {
    497           /* concatenate, and insert join_char between words */
    498           append_to_each_with_joiner( pronunciation, p_infpron, OPTSILENCE_CODE, MAX_PRON_LEN, &len);
    499         }
    500       else {
    501         p_infpron[0] = 0;
    502         /* if this is English AND we're dealing with a quote (possessive or a
    503            contraction), then we use the dictionary for the stuff before the
    504            quote, and use the TTP to find out what single phoneme should
    505            correspond the the thing after the quote ('s' or 't').  This keeps
    506            the code clean (no phoneme codes here), and maps 's' to 's' or 'z'
    507            with the intelligence of the G2P engine */
    508         if( impl->locale == ESR_LOCALE_EN_US) {
    509           if( (squote=LSTRCHR(curr,LSINGLEQUOTE))==NULL) {}
    510           else {
    511             *squote = L('\0');   // temporary
    512             if( CA_GetEntryInDictionary(impl->vocabulary, curr, p_infpron, (int*)&len, MAX_PRON_LEN)) {
    513             } else
    514               p_infpron[0] = 0;
    515             *squote = LSINGLEQUOTE; // undo temporary
    516           }
    517         }
    518 #ifdef USE_TTP
    519         pTranscriptions = NULL;
    520         if (impl->hSlts)
    521           {
    522             res = SWIsltsG2PGetWordTranscriptions(impl->hSlts, curr, &pTranscriptions, &nNbrOfTranscriptions);
    523             if (res != SWIsltsSuccess) {
    524               PLogError(L("ESR_FATAL_ERROR: SWIsltsG2PGetWordTranscriptions( ) fails with return code %d\n"), res);
    525               return ESR_FATAL_ERROR;
    526             }
    527             if( impl->locale == ESR_LOCALE_EN_US && p_infpron[0] && squote!=L('\0')) {
    528               const LCHAR* lastPhoneme = pTranscriptions[0].pBuffer;
    529               while(lastPhoneme && *lastPhoneme && lastPhoneme[1]!=L('\0'))
    530                 lastPhoneme++;
    531               append_to_each_with_joiner( pronunciation, p_infpron, OPTSILENCE_CODE, MAX_PRON_LEN, &len);
    532               append_to_each_with_joiner( pronunciation, lastPhoneme, L('\0'), MAX_PRON_LEN, &len);
    533             } else {
    534               /* only one transcription available from seti */
    535               p_infpron = pTranscriptions[0].pBuffer;
    536               append_to_each_with_joiner( pronunciation, p_infpron, OPTSILENCE_CODE, MAX_PRON_LEN, &len);
    537 #if defined(SREC_ENGINE_VERBOSE_LOGGING)
    538               PLogError("L: used G2P for %s", curr);
    539 #endif
    540 
    541             }
    542             if (pTranscriptions) {
    543               res = SWIsltsG2PFreeWordTranscriptions(impl->hSlts, pTranscriptions);
    544               pTranscriptions = NULL;
    545               if (res != SWIsltsSuccess) {
    546                 PLogError(L("ESR_FATAL_ERROR: SWIsltsG2PFreeWordTranscriptions( ) fails with return code %d\n"), res);
    547                 return ESR_FATAL_ERROR;
    548               }
    549             }
    550           } else {
    551             nEsrRes = ESR_INVALID_ARGUMENT;
    552             PLogError(L("ESR_INVALID_ARGUMENT: impl->hSlts was not configured!"));
    553             return nEsrRes;
    554           }
    555 #else /* USE_TTP */
    556         nEsrRes = ESR_INVALID_ARGUMENT;
    557         PLogError(L("ESR_INVALID_ARGUMENT: need USE_TTP build to guess pronunciations!"));
    558         return nEsrRes;
    559 #endif
    560       } /* multi-word phrase */
    561     } /* loop over words in phrase */
    562   len = LSTRLEN(pronunciation)+1;
    563   // look for double-null terminator
    564   while( pronunciation[ len] != L('\0'))
    565     len += LSTRLEN( pronunciation + len) + 1;
    566   *pronunciation_len = len;
    567   nEsrRes = ESR_SUCCESS;
    568  CLEANUP:
    569   return nEsrRes;
    570 }
    571 
    572 ESR_ReturnCode SR_VocabularyGetLanguageImpl(SR_Vocabulary* self, ESR_Locale* locale)
    573 {
    574   SR_VocabularyImpl* impl = (SR_VocabularyImpl*) self;
    575 
    576   *locale = impl->locale;
    577   return ESR_SUCCESS;
    578 }
    579 
    580 /* simple text normalization rountine for splitting up any digit string */
    581 static ESR_ReturnCode run_ttt(const LCHAR *input_sentence, LCHAR *output_sentence, int *text_length)
    582 {
    583   ESR_ReturnCode         nRes = ESR_SUCCESS;
    584   int                    num_out = 0;
    585   int                    max_text_length = *text_length / sizeof(LCHAR) - 1;
    586   ESR_BOOL                   bDigit = False;
    587 
    588   while (*input_sentence != L('\0')) {
    589     if (num_out + 2 >= max_text_length) {
    590       nRes = ESR_FATAL_ERROR;
    591       goto CLEAN_UP;
    592     }
    593 
    594     if (L('0') <= *input_sentence && *input_sentence <= L('9')) {
    595       if (num_out > 0 && !LISSPACE(output_sentence[num_out-1]) ) {
    596 		  // put 1 space before digits
    597         output_sentence[num_out] = L(' ');
    598         num_out++;
    599 		while( LISSPACE(*input_sentence) ) input_sentence++;
    600       }
    601       output_sentence[num_out] = *input_sentence;
    602       num_out++;
    603       bDigit = True;
    604     }
    605     else {
    606       if (bDigit == True && !LISSPACE(output_sentence[num_out-1])) {
    607 		// put 1 space after digits
    608         output_sentence[num_out] = L(' ');
    609         num_out++;
    610 		while( LISSPACE(*input_sentence)) input_sentence++;
    611       }
    612 		output_sentence[num_out] = *input_sentence;
    613 		num_out++;
    614       bDigit = False;
    615     }
    616     input_sentence++;
    617 	if( LISSPACE(output_sentence[num_out-1]))
    618 		while(LISSPACE(*input_sentence )) input_sentence++; // remove repeated spaces
    619   }
    620 
    621   output_sentence[num_out] = L('\0');
    622   *text_length = num_out * sizeof(LCHAR);
    623   return ESR_SUCCESS;
    624 
    625  CLEAN_UP:
    626 
    627   *output_sentence = L('\0');
    628   *text_length = 0;
    629   return nRes;
    630 }
    631