//+--------------------------------------------------------------------------- // // // CThaiBreakTree - class CThaiBreakTree // // History: // created 7/99 aarayas // // ©1999 Microsoft Corporation //---------------------------------------------------------------------------- #include "CThaiBreakTree.hpp" //+--------------------------------------------------------------------------- // // Function: ExtractPOS // // Synopsis: The functions takes a tag and return Part Of Speech Tags. // // Arguments: // // Modifies: // // History: created 7/99 aarayas // // Notes: // //---------------------------------------------------------------------------- inline WCHAR ExtractPOS(DWORD dwTag) { return (WCHAR) ( (dwTag & iPosMask) >> iPosShift); } //+--------------------------------------------------------------------------- // // Function: ExtractFrq // // Synopsis: The functions takes a tag and return Frquency of words. // // Arguments: // // Modifies: // // History: created 7/99 aarayas // // Notes: // //---------------------------------------------------------------------------- inline BYTE ExtractFrq(DWORD dwTag) { return (BYTE) ( (dwTag & 0x300) >> iFrqShift); } //+--------------------------------------------------------------------------- // // Function: DetermineFrequencyWeight // // Synopsis: The functions returns the frequency weight of a words. // // Arguments: // // Modifies: // // History: created 7/99 aarayas // // Notes: // //---------------------------------------------------------------------------- inline void DetermineFrequencyWeight(BYTE frq, unsigned int* uiWeight) { switch (frq) { case frqpenInfrequent: (*uiWeight) -= 2; break; case frqpenSomewhat: (*uiWeight)--; break; case frqpenVery: (*uiWeight) += 2; break; case frqpenNormal: default: (*uiWeight)++; break; } } //+--------------------------------------------------------------------------- // // Function: DetermineFrequencyWeight // // Synopsis: The functions returns the frequency weight of a words. // // Arguments: // // Modifies: // // History: created 7/99 aarayas // // Notes: // //---------------------------------------------------------------------------- inline void DetermineFrequencyWeight(BYTE frq, DWORD* uiWeight) { switch (frq) { case frqpenInfrequent: (*uiWeight) -= 2; break; case frqpenSomewhat: (*uiWeight)--; break; case frqpenVery: (*uiWeight) += 2; break; case frqpenNormal: default: (*uiWeight)++; break; } } //+--------------------------------------------------------------------------- // // Class: CThaiTrieIter // // Synopsis: Constructor - initialize local variables // // Arguments: // // Modifies: // // History: created 7/99 aarayas // // Notes: // //---------------------------------------------------------------------------- CThaiBreakTree::CThaiBreakTree() : iNodeIndex(0), iNumNode(0), pszBegin(NULL), pszEnd(NULL), breakTree(NULL), breakArray(NULL), tagArray(NULL), maximalMatchingBreakArray(NULL), maximalMatchingTAGArray(NULL), POSArray(NULL), maximalMatchingPOSArray(NULL) { // Allocate memory need for CThaiBreakTree. #if defined (NGRAM_ENABLE) breakTree = new ThaiBreakNode[MAXTHAIBREAKNODE]; #endif breakArray = new BYTE[MAXBREAK]; tagArray = new DWORD[MAXBREAK]; POSArray = new WCHAR[MAXBREAK]; } //+--------------------------------------------------------------------------- // // Class: CThaiTrieIter // // Synopsis: Destructor - clean up code // // Arguments: // // Modifies: // // History: created 7/99 aarayas // // Notes: // //---------------------------------------------------------------------------- CThaiBreakTree::~CThaiBreakTree() { // Clean up all memory used. #if defined (NGRAM_ENABLE) if (breakTree) delete breakTree; if (maximalMatchingBreakArray) delete maximalMatchingBreakArray; if (maximalMatchingTAGArray) delete maximalMatchingTAGArray; if (maximalMatchingPOSArray) delete maximalMatchingPOSArray; #endif if (breakArray) delete breakArray; if (tagArray) delete tagArray; if (POSArray) delete POSArray; } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: Associate the class to the string. // // Arguments: // // Modifies: // // History: created 7/99 aarayas // // Notes: // //---------------------------------------------------------------------------- #if defined (NGRAM_ENABLE) void CThaiBreakTree::Init(CTrie* pTrie, CTrie* pSentTrie, CTrie* pTrigramTrie) #else void CThaiBreakTree::Init(CTrie* pTrie, CTrie* pTrigramTrie) #endif { assert(pTrie != NULL); thaiTrieIter.Init(pTrie); thaiTrieIter1.Init(pTrie); #if defined (NGRAM_ENABLE) assert(pSentTrie != NULL); thaiSentIter.Init(pSentTrie); #endif assert(pTrigramTrie != NULL); thaiTrigramIter.Init(pTrigramTrie); } #if defined (NGRAM_ENABLE) //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: reset iterator to top of the tree // // Arguments: // // Modifies: // // History: created 7/99 aarayas // // Notes: // //---------------------------------------------------------------------------- inline void CThaiBreakTree::Reset() { iNodeIndex = 0; } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: Move to the next break. // // Arguments: // // Modifies: // // History: created 7/99 aarayas // // Notes: // //---------------------------------------------------------------------------- inline bool CThaiBreakTree::MoveNext() { iNodeIndex = breakTree[iNodeIndex].NextBreak; return (iNodeIndex != 0); } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: Move down to next level. // // Arguments: // // Modifies: // // History: created 7/99 aarayas // // Notes: // //---------------------------------------------------------------------------- inline bool CThaiBreakTree::MoveDown() { iNodeIndex = breakTree[iNodeIndex].Down; return (iNodeIndex != 0); } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: create new node to position, and return index to the node. // // * return Unable to Create Node. // // Arguments: // // Modifies: // // History: created 7/99 aarayas // // Notes: // //---------------------------------------------------------------------------- inline unsigned int CThaiBreakTree::CreateNode(int iPos, BYTE iBreakLen, DWORD dwTAG) { assert(iNumNode < MAXTHAIBREAKNODE); breakTree[iNumNode].iPos = iPos; breakTree[iNumNode].iBreakLen = iBreakLen; breakTree[iNumNode].dwTAG = dwTAG; breakTree[iNumNode].NextBreak = 0; breakTree[iNumNode].Down = 0; if (iNumNode >= MAXTHAIBREAKNODE) { return UNABLETOCREATENODE; } iNumNode++; return (iNumNode - 1); } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: Generate a Tree of possible break from the given string. // // * Note - false if there aren't enough memory to create node. // // Arguments: // // Modifies: // // History: created 7/99 aarayas // // Notes: // //---------------------------------------------------------------------------- enum thai_parse_state { END_SENTENCE, // Reached the end of sentence. LONGEST_MATCH, // Longest possible matched. NOMATCH_FOUND, // Unable to find word. ERROR_OUTMEMORY, // Out of Memory. }; bool CThaiBreakTree::GenerateTree(WCHAR* pszBegin, WCHAR* pszEnd1) { // Declare and initialize local variables. unsigned int iIndexBreakTree = 0; unsigned int iPrevIndexBreakTree = 0; unsigned int iParentNode = 0; WCHAR* pszBeginWord = pszBegin; WCHAR* pszIndex = pszBegin; unsigned int iNumCluster = 1; unsigned int iNumLastCluster; unsigned int iWordLen = 0; unsigned int iNodeAnalyze = 0; thai_parse_state parseState = END_SENTENCE; bool fFoundMatch = false; bool fAddToNodeAnalyze = false; bool fDoneGenerateTree = false; pszEnd = pszEnd1; #if defined (_DEBUG) memset(breakTree,0,sizeof(ThaiBreakNode)*MAXTHAIBREAKNODE); #endif iNodeIndex = 0; iNumNode = 0; while (true) { // Reset Iterator for generating break for new word. fFoundMatch = false; thaiTrieIter.Reset(); if (iIndexBreakTree != 0) { while (true) { // If this is not the first node than set pszBeginWord after the last break. pszBeginWord = pszBegin + breakTree[iNodeAnalyze].iPos + breakTree[iNodeAnalyze].iBreakLen; fAddToNodeAnalyze = true; // Are we at the end of the sentence. if ( (pszBeginWord == pszEnd) || (breakTree[iNodeAnalyze].dwTAG == TAGPOS_PURGE) ) { iNodeAnalyze++; // Move to next node. if (iNodeAnalyze >= iNumNode) { fDoneGenerateTree = true; break; } } else break; } } pszIndex = pszBeginWord; iParentNode = iNodeAnalyze; if (fDoneGenerateTree) break; // Get next level of tree. while (TRUE) { iNumLastCluster = iNumCluster; iNumCluster = GetCluster(pszIndex); if (thaiTrieIter.MoveCluster(pszIndex, iNumCluster)) { pszIndex += iNumCluster; if (thaiTrieIter.fWordEnd) { fFoundMatch = true; // if first node add first node if (iIndexBreakTree == 0) { CreateNode(pszBeginWord - pszBegin, pszIndex - pszBeginWord, thaiTrieIter.dwTag); iIndexBreakTree++; } else { if (fAddToNodeAnalyze) { fAddToNodeAnalyze = false; breakTree[iNodeAnalyze].NextBreak = CreateNode(pszBeginWord - pszBegin, pszIndex - pszBeginWord, thaiTrieIter.dwTag); // Determine if an error has occur. if (breakTree[iNodeAnalyze].NextBreak == UNABLETOCREATENODE) { breakTree[iNodeAnalyze].NextBreak = 0; parseState = ERROR_OUTMEMORY; break; } iPrevIndexBreakTree = breakTree[iNodeAnalyze].NextBreak; iNodeAnalyze++; } else { breakTree[iPrevIndexBreakTree].Down = CreateNode(pszBeginWord - pszBegin, pszIndex - pszBeginWord, thaiTrieIter.dwTag); // Determine if an error has occur. if (breakTree[iPrevIndexBreakTree].Down == UNABLETOCREATENODE) { breakTree[iPrevIndexBreakTree].Down = 0; parseState = ERROR_OUTMEMORY; break; } iPrevIndexBreakTree = iIndexBreakTree; } iIndexBreakTree++; } } if (pszIndex >= pszEnd) { assert(pszIndex <= pszEnd); // assert should never come up - if it appear likely bug in GetCluster funciton. parseState = END_SENTENCE; break; } } else { if (fFoundMatch) parseState = LONGEST_MATCH; else parseState = NOMATCH_FOUND; break; } } if (parseState == LONGEST_MATCH) { // We found a matched. assert(breakTree[iPrevIndexBreakTree].Down == 0); // at this point breakTree[iPreveIndexBreakTree].Down should equal null.(optimization note) if (breakTree[iParentNode].NextBreak != iPrevIndexBreakTree) { assert(breakTree[iPrevIndexBreakTree].dwTAG != TAGPOS_UNKNOWN); // shouldn't assert because the end node should ever be unknown. DeterminePurgeEndingSentence(pszBeginWord, breakTree[iParentNode].NextBreak); } } else if (parseState == NOMATCH_FOUND) { // Should mark node as unknown. if (fAddToNodeAnalyze) { fAddToNodeAnalyze = false; iWordLen = pszIndex - pszBeginWord; // Make sure we don't only have a cluster of text before making a node. if (iWordLen == 0) { // If we have an UNKNOWN word of one character only current node mark it as unknown. assert(iNodeAnalyze == iParentNode); // Since we have a no match iNodeAnalyze better equal iParentNode breakTree[iNodeAnalyze].iBreakLen += iNumCluster; breakTree[iNodeAnalyze].dwTAG = DeterminePurgeOrUnknown(iNodeAnalyze,breakTree[iNodeAnalyze].iBreakLen); } else { if (breakTree[iNodeAnalyze].iBreakLen + iWordLen < 8) // The reason we are using 8 is because from corpora analysis // the average Thai word is about 7.732 characters. // TODO: We should add orthographic analysis here to get a better on boundary // of unknown word. { assert(iNodeAnalyze == iParentNode); // Since we have a no match iNodeAnalyze better equal iParentNode breakTree[iNodeAnalyze].iBreakLen += iWordLen; breakTree[iNodeAnalyze].dwTAG = DeterminePurgeOrUnknown(iNodeAnalyze,breakTree[iNodeAnalyze].iBreakLen); } else { if (GetWeight(pszIndex - iNumLastCluster)) breakTree[iNodeAnalyze].NextBreak = CreateNode(pszBeginWord - pszBegin, iWordLen - iNumLastCluster, TAGPOS_UNKNOWN); else breakTree[iNodeAnalyze].NextBreak = CreateNode(pszBeginWord - pszBegin, iWordLen, TAGPOS_UNKNOWN); // Determine if an error has occur. if (breakTree[iNodeAnalyze].NextBreak == UNABLETOCREATENODE) { breakTree[iNodeAnalyze].NextBreak = 0; parseState = ERROR_OUTMEMORY; break; } iNodeAnalyze++; iIndexBreakTree++; } } } else { breakTree[iPrevIndexBreakTree].Down = CreateNode(pszBeginWord - pszBegin, pszIndex - pszBeginWord, TAGPOS_UNKNOWN); // Determine if an error has occur. if (breakTree[iPrevIndexBreakTree].Down == UNABLETOCREATENODE) { breakTree[iPrevIndexBreakTree].Down = 0; parseState = ERROR_OUTMEMORY; break; } iIndexBreakTree++; } } else if (parseState == END_SENTENCE) { // If we find ourself at the end of a sentence and no match. if (!fFoundMatch) { if (fAddToNodeAnalyze) { fAddToNodeAnalyze = false; iWordLen = pszIndex - pszBeginWord; // Make sure we don't only have a cluster of text before making a node. if (iWordLen == 0) { // If we have an UNKNOWN word of one character only current node mark it as unknown. assert(iNodeAnalyze == iParentNode); // Since we have a no match iNodeAnalyze better equal iParentNode breakTree[iNodeAnalyze].iBreakLen += iNumCluster; breakTree[iNodeAnalyze].dwTAG = DeterminePurgeOrUnknown(iNodeAnalyze,breakTree[iNodeAnalyze].iBreakLen); } else { if (breakTree[iNodeAnalyze].iBreakLen + iWordLen < 8) // The reason we are using 8 is because from corpora analysis // the average Thai word is about 7.732 characters. // TODO: We should add orthographic analysis here to get a better on boundary // of unknown word. { assert(iNodeAnalyze == iParentNode); // Since we have a no match iNodeAnalyze better equal iParentNode breakTree[iNodeAnalyze].iBreakLen += iWordLen; breakTree[iNodeAnalyze].dwTAG = DeterminePurgeOrUnknown(iNodeAnalyze,breakTree[iNodeAnalyze].iBreakLen); } else { if (GetWeight(pszIndex - iNumLastCluster)) breakTree[iNodeAnalyze].NextBreak = CreateNode(pszBeginWord - pszBegin, iWordLen - iNumLastCluster, TAGPOS_UNKNOWN); else breakTree[iNodeAnalyze].NextBreak = CreateNode(pszBeginWord - pszBegin, iWordLen, TAGPOS_UNKNOWN); // Determine if an error has occur. if (breakTree[iNodeAnalyze].NextBreak == UNABLETOCREATENODE) { breakTree[iNodeAnalyze].NextBreak = 0; parseState = ERROR_OUTMEMORY; break; } iNodeAnalyze++; iIndexBreakTree++; } } } else { breakTree[iPrevIndexBreakTree].Down = CreateNode(pszBeginWord - pszBegin, pszIndex - pszBeginWord, TAGPOS_UNKNOWN); // Determine if an error has occur. if (breakTree[iPrevIndexBreakTree].Down == UNABLETOCREATENODE) { breakTree[iPrevIndexBreakTree].Down = 0; parseState = ERROR_OUTMEMORY; break; } } iIndexBreakTree++; } // If the beginning of node the branch isn't equal to leaf node perphase it is possible to // do some ending optimization. else if (breakTree[iParentNode].NextBreak != iPrevIndexBreakTree) { assert(breakTree[iPrevIndexBreakTree].dwTAG != TAGPOS_UNKNOWN); // shouldn't assert because the end node should ever be unknown. DeterminePurgeEndingSentence(pszBeginWord, breakTree[iParentNode].NextBreak); } } else if ( (breakTree[iNodeAnalyze].iBreakLen == 0) || (parseState == ERROR_OUTMEMORY) ) break; } return (parseState != ERROR_OUTMEMORY); } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: Traverse all the tree and look for the least number of token. // // Arguments: // // Modifies: // // History: created 7/99 aarayas // // Notes: // //---------------------------------------------------------------------------- bool CThaiBreakTree::MaximalMatching() { // If maximal matching break array has not been allocate, than allocate it. if (!maximalMatchingBreakArray) maximalMatchingBreakArray = new BYTE[MAXBREAK]; if (!maximalMatchingTAGArray) maximalMatchingTAGArray = new DWORD[MAXBREAK]; if (!maximalMatchingPOSArray) maximalMatchingPOSArray = new WCHAR[MAXBREAK]; maxLevel = MAXUNSIGNEDINT; maxToken = 0; iNumUnknownMaximalPOSArray = MAXBREAK; Traverse(0,0,0); return true; } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: The function determine if the node if the node should, // be tag as unknown or purge. // // Arguments: // // Modifies: // // History: created 8/99 aarayas // // Notes: // //---------------------------------------------------------------------------- inline DWORD CThaiBreakTree::DeterminePurgeOrUnknown(unsigned int iCurrentNode, unsigned int iBreakLen) { // Declare and initialize local variables. unsigned int iNode = breakTree[iCurrentNode].Down; while (iNode != 0) { if ( (breakTree[iNode].iBreakLen == iBreakLen) || (breakTree[iNode].iBreakLen < iBreakLen) && ( (breakTree[iNode].dwTAG != TAGPOS_UNKNOWN) || (breakTree[iNode].dwTAG != TAGPOS_PURGE) )) { // Since we are purging this break just make sure the NextBreak is Null. assert(breakTree[iCurrentNode].NextBreak == 0); return TAGPOS_PURGE; } iNode = breakTree[iNode].Down; } return TAGPOS_UNKNOWN; } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: Ending optimization - if we have found the end of a sentence, // and possible break. Purge the branch for unnecessary break. // // Arguments: // // Modifies: // // History: created 8/99 aarayas // // Notes: // //---------------------------------------------------------------------------- inline void CThaiBreakTree::DeterminePurgeEndingSentence(WCHAR* pszBeginWord, unsigned int iNode) { while (breakTree[iNode].Down != 0) { // Determine if the next string has a possiblity to become a word. // TODO: We may need to change this once the GetWeight add soundex // functionality. if (GetWeight(pszBeginWord + breakTree[iNode].iBreakLen) == 0) { // Since we are purging this break just make sure the NextBreak is Null. assert(breakTree[iNode].NextBreak == 0); breakTree[iNode].dwTAG = TAGPOS_PURGE; } iNode = breakTree[iNode].Down; } } #endif //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: // // Arguments: // // Modifies: // // History: created 8/99 aarayas // // Notes: // //---------------------------------------------------------------------------- unsigned int CThaiBreakTree::GetWeight(WCHAR* pszBegin) { // Declare and initialize local variables. unsigned int iNumCluster = 1; unsigned int Weight = 0; bool fBeginNewWord; WCHAR* pszIndex = pszBegin; // Short circuit the length is less of string is less than 1. if ((pszEnd - pszBegin) == 1) return Weight; else if (pszEnd == pszBegin) return 1000; // Reset Iterator for generating break for new word. fBeginNewWord = true; // Get next level of tree. while (true) { iNumCluster = GetCluster(pszIndex); if (thaiTrieIter.MoveCluster(pszIndex, iNumCluster, fBeginNewWord)) { fBeginNewWord = false; pszIndex += iNumCluster; if (thaiTrieIter.fWordEnd) Weight = (unsigned int) (pszIndex - pszBegin); } else break; } return Weight; } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: // // Arguments: // // Modifies: // // History: created 8/99 aarayas // // Notes: // //---------------------------------------------------------------------------- unsigned int CThaiBreakTree::GetWeight(WCHAR* pszBegin, DWORD* pdwTag) { // Declare and initialize local variables. unsigned int iNumCluster = 1; unsigned int Weight = 0; bool fBeginNewWord; WCHAR* pszIndex = pszBegin; // Short circuit the length is less of string is less than 1. if ((pszEnd - pszBegin) == 1) return Weight; else if (pszEnd == pszBegin) return 1000; // Reset Iterator for generating break for new word. fBeginNewWord = true; // Get next level of tree. while (true) { iNumCluster = GetCluster(pszIndex); if (thaiTrieIter.MoveCluster(pszIndex, iNumCluster, fBeginNewWord)) { fBeginNewWord = false; pszIndex += iNumCluster; if (thaiTrieIter.fWordEnd) { Weight = (unsigned int) (pszIndex - pszBegin); *pdwTag = thaiTrieIter.dwTag; } } else break; } return Weight; } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: Traverse the tree. // // Arguments: // // Modifies: // // History: created 7/99 aarayas // // Notes: // //---------------------------------------------------------------------------- bool CThaiBreakTree::Traverse(unsigned int iLevel, unsigned int iCurrentNode, unsigned int iNumUnknown) { assert (iLevel < MAXBREAK); // Process node. breakArray[iLevel] = breakTree[iCurrentNode].iBreakLen; tagArray[iLevel] = breakTree[iCurrentNode].dwTAG; if (tagArray[iLevel] == TAGPOS_UNKNOWN) iNumUnknown++; // Have we found the end of the sentence. if (breakTree[iCurrentNode].NextBreak == 0) { if (breakTree[iCurrentNode].dwTAG != TAGPOS_PURGE) AddBreakToList(iLevel + 1, iNumUnknown); if (breakTree[iCurrentNode].Down != 0) { if (tagArray[iLevel] == TAGPOS_UNKNOWN) iNumUnknown--; return Traverse(iLevel,breakTree[iCurrentNode].Down, iNumUnknown); } else return true; } else Traverse(iLevel + 1, breakTree[iCurrentNode].NextBreak, iNumUnknown); if (breakTree[iCurrentNode].Down != 0) { if (tagArray[iLevel] == TAGPOS_UNKNOWN) iNumUnknown--; Traverse(iLevel,breakTree[iCurrentNode].Down, iNumUnknown); } return true; } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: // // Arguments: // // Modifies: // // History: created 8/99 aarayas // // Notes: // //---------------------------------------------------------------------------- unsigned int CThaiBreakTree::SoundexSearch(WCHAR* pszBegin) { // Declare and initialize local variables. unsigned int iNumCluster = 1; unsigned int iNumNextCluster = 1; unsigned int iLongestWord = 0; unsigned int iPenalty = 0; WCHAR* pszIndex = pszBegin; // Short circuit the length is less of string is less than 1. if ( (pszBegin+1) >= pszEnd ) return iLongestWord; // Reset Iterator for generating break for new word. thaiTrieIter1.Reset(); // Get next level of tree. while (true) { iNumCluster = GetCluster(pszIndex); // Determine iNumNextCluster let iNumNextCluster = 0, if we reached the end of string. if (pszIndex + iNumCluster >= pszEnd) iNumNextCluster = 0; else iNumNextCluster = GetCluster(pszIndex+iNumCluster); // Determine penalty switch (thaiTrieIter1.MoveSoundexByCluster(pszIndex, iNumCluster, iNumNextCluster)) { case SUBSTITUTE_SOUNDLIKECHAR: iPenalty += 2; break; case SUBSTITUTE_DIACRITIC: iPenalty++; break; case UNABLE_TO_MOVE: iPenalty += 2; break; default: case NOSUBSTITUTE: break; } // Update Index. if (iPenalty <= 2) { pszIndex += iNumCluster; if (thaiTrieIter1.fWordEnd) iLongestWord = (unsigned int) (pszIndex - pszBegin); } else break; } return iLongestWord; } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: The information used here is a reference to the orthographic // analysis work done on the Thai languages. (see paper: Natural // Language Processing in Thailand 1993 Chulalongkorn. p 361). // // Arguments: pszBoundaryChar - Contain pointer to at least two thai character // character next to each other which we will // use to calculate wheather we should or // should not merge the two word. // // iPrevWordLen - // // Modifies: // // History: created 8/99 aarayas // // Notes: // //---------------------------------------------------------------------------- inline bool CThaiBreakTree::ShouldMerge(WCHAR* pwszPrevWord, unsigned int iPrevWordLen, unsigned int iMergeWordLen, DWORD dwPrevTag) { WCHAR* pwszBoundary = pwszPrevWord + iPrevWordLen - 1; assert(iMergeWordLen != 0); assert(iPrevWordLen != 0); // There are very few words in Thai that are 4 character or less, therefore we should // found a pair that less than 4 character we should merge. // Or if merge word length is one than also merge. // Of if last cluster of the word is a Thanthakhat(Karan) we should always merge. if (iPrevWordLen + iMergeWordLen <= 4 || iMergeWordLen == 1 || (iMergeWordLen == 2 && *(pwszBoundary + iMergeWordLen) == THAI_Thanthakhat)) return true; if (iPrevWordLen >=2) { WCHAR* pwszPrevCharBoundary = pwszBoundary - 1; // TO IMPROVE: It better to check the last character of Previous word, it can give us a // much better guess if ((*pwszPrevCharBoundary == THAI_Vowel_Sign_Mai_HanAkat || *pwszBoundary == THAI_Vowel_Sign_Mai_HanAkat) || (*pwszPrevCharBoundary == THAI_Tone_Mai_Tri || *pwszBoundary == THAI_Tone_Mai_Tri) || (*pwszPrevCharBoundary == THAI_Sara_Ue || *pwszBoundary == THAI_Sara_Ue) ) return true; } // If the first character of the next word is mostly likly the beginning // character and last character of the previous word is not sara-A than // we have a high probability that we found a begin of word boundary, // therefore we shouldn't merge. if ( (IsThaiMostlyBeginCharacter(pwszBoundary[1]) && *pwszBoundary != THAI_Vowel_Sara_A) ) return false; // If the last character of the previous word is mostly likely an ending // character than, than there is a high probability that the found a boundary. // There are very few words in Thai that are 4 character or less, therefore we should // found a pair that less than 4 character we should merge. if (IsThaiMostlyLastCharacter(*pwszBoundary)) return false; // The reason we are using 8 is because from corpora analysis // the average Thai word is about 7.732 characters. Or, if previous word is already // an unknown, to keep the amount of unknown low the unknown to previous words. if ( (iPrevWordLen + iMergeWordLen < 8) || (dwPrevTag == TAGPOS_UNKNOWN) ) return true; return false; } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: // // Arguments: // // Modifies: // // History: created 7/99 aarayas // 8/17/99 optimize some code. // // Notes: // //---------------------------------------------------------------------------- inline void CThaiBreakTree::AddBreakToList(unsigned int iNumBreak, unsigned int iNumUnknown) { #if defined (_DEBUG) breakArray[iNumBreak] = 0; #endif if (CompareSentenceStructure(iNumBreak, iNumUnknown)) { maxToken = maxLevel = iNumBreak; // This is ugly but it save 5 clock cycle. memcpy(maximalMatchingBreakArray,breakArray,maxToken); memcpy(maximalMatchingTAGArray,tagArray,sizeof(unsigned int)*maxToken); maximalMatchingBreakArray[maxToken] = 0; maximalMatchingTAGArray[maxToken] = 0; } } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: The function compares sentence structure of // maximalMatchingPOSArray with posArray. // // Arguments: // // Modifies: // // History: created 7/99 aarayas // // Notes: // //---------------------------------------------------------------------------- inline bool CThaiBreakTree::CompareSentenceStructure(unsigned int iNumBreak, unsigned int iNumUnknownPOSArray) { if ( (iNumBreak < maxLevel) && (iNumUnknownMaximalPOSArray >= iNumUnknownPOSArray) ) { iNumUnknownMaximalPOSArray = iNumUnknownPOSArray; return true; } else if (iNumBreak == maxLevel) { // true - maximal matching has a larger unknown. if (iNumUnknownMaximalPOSArray > iNumUnknownPOSArray) { iNumUnknownMaximalPOSArray = iNumUnknownPOSArray; return true; } for(unsigned int i = 0; i <= iNumBreak; i++) { maximalMatchingPOSArray[i] = ExtractPOS(maximalMatchingTAGArray[i]); POSArray[i] = ExtractPOS(tagArray[i]); } // Determine if the sentence structure is like any one of the sentence // sentence structure in our corpora. if ( (IsSentenceStruct(POSArray, iNumBreak)) && (!IsSentenceStruct(maximalMatchingPOSArray, iNumBreak)) ) { iNumUnknownMaximalPOSArray = iNumUnknownPOSArray; return true; } else if (iNumUnknownMaximalPOSArray == iNumUnknownPOSArray) { // Determine the frequency of word used in the sentence. unsigned int iFrequencyArray = 500; unsigned int iFrequencyMaximalArray = 500; for(unsigned int i = 0; i <= iNumBreak; i++) { DetermineFrequencyWeight(ExtractFrq(maximalMatchingTAGArray[i]),&iFrequencyMaximalArray); DetermineFrequencyWeight(ExtractFrq(tagArray[i]),&iFrequencyArray); } return (iFrequencyArray > iFrequencyMaximalArray); } } return false; } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: // // Arguments: // // Modifies: // // History: created 8/99 aarayas // // Notes: // //---------------------------------------------------------------------------- bool CThaiBreakTree::IsSentenceStruct(WCHAR* pos, unsigned int iPosLen) { // Declare and initialize all local variables. unsigned int i = 0; thaiSentIter.Reset(); if (!thaiSentIter.Down()) return FALSE; while (TRUE) { thaiSentIter.GetNode(); if (thaiSentIter.pos == pos[i]) { i++; if (thaiSentIter.fWordEnd && i == iPosLen) { return TRUE; } else if (i == iPosLen) break; // Move down the Trie Branch. else if (!thaiSentIter.Down()) break; } // Move right of the Trie Branch else if (!thaiSentIter.Right()) break; } return FALSE; } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: // // Arguments: // // Modifies: // // History: created 8/99 aarayas // // Notes: // //---------------------------------------------------------------------------- float CThaiBreakTree::BigramProbablity(DWORD dwTag1,DWORD dwTag2) { unsigned int iWeight = 4; // TODO : Use the distribution of word category to determine optimial search - exmaple // NOUN VERB ADVERB CLASSIFIER CONJETURE PREP et.... // TODO : Once we got trigram use it to create bigram probability as well. if ( (dwTag1 != TAGPOS_UNKNOWN) && (dwTag2 != TAGPOS_UNKNOWN) ) { WCHAR pos1 = ExtractPOS(dwTag1); WCHAR pos2 = ExtractPOS(dwTag2); // case NCMN VATT /// a common noun is often followed by attributive verb(adjective) // Example: (In Thai) book good, people nice if (pos1 == 5 && pos2 == 13) iWeight += 10; // case NTTL NPRP // a title noun is often followed by proper noun // Example: Dr. Athapan, Mr. Sam else if (pos1 == 6 && pos2 == 1) iWeight += 5; // case JSBR (XVAM || VSTA) // a subordinating conjunction is often followed by preverb auxillary or Active verb // Example: (In Thai) Because of , Because see else if (pos1 == 39 && (pos2 == 15 || pos2 == 12)) iWeight += 10; // case ADVN NCMN // a Adverb normal form is often followed by Common noun (Bug 55057). // Example: (In Thai) under table. else if (pos1 == 28 && pos2 == 5) iWeight += 5; // case VACT XVAE else if (pos1 == 11 && pos2 == 18) iWeight += 5; // case VACT DDBQ // Active verb follow by Definite determiner. // Example: (In Thai) working for, singing again. else if (pos1 == 11 && pos2 == 21) iWeight += 10; // case XVAE VACT // a post verb auxilliary are often followed by an active verb. // Example: (In Thai) come singing, go work. else if (pos1 == 18 && pos2 == 11) iWeight += 10; // case CLTV NCMN // a Collective classfier are often followed by Common Noun // Example: (In Thai) group people, flock bird else if (pos1 == 33 && pos2 == 5) iWeight += 5; // case NEG (VACT || VSTA || VATT || XVAM || XVAE) // a negator (ie. not) is often followed by some kind of VERB. // Example: He is not going. else if (pos1 == 46 && (pos2 == 11 || pos2 == 12 || pos2 == 13 || pos2 == 15 || pos2 == 16)) iWeight += 8; // case EAFF or EITT // Ending for affirmative, and interrogative are more often ending of the pair // Example: (In Thai) Krub, Ka, else if (pos2 == 44 || pos2 == 45) iWeight += 3; // case VATT and VATT // Attributive Verb and Attributive Verb occur when often in spoken laguages. // Example: she is reall really cute. else if (pos1 == 13 && pos2 == 13) iWeight += 2; // case NCMN and DDAC // Common Noun and Definitive determiner classifier. // Example: Food here (Thai) else if (pos1 == 5 && pos2 == 20) iWeight += 3; // case CMTR and JCMP // Measurement classifier and Comparative conjunction, are likly to appear in Thai. // Example: year about (Thai) -> English about a year. else if (pos1 == 34 && pos2 == 38) iWeight += 5; } DetermineFrequencyWeight(ExtractFrq(dwTag1), &iWeight); DetermineFrequencyWeight(ExtractFrq(dwTag2), &iWeight); return (float) iWeight; } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: // // Arguments: // // Modifies: // // History: created 8/99 aarayas // // Notes: // //---------------------------------------------------------------------------- DWORD CThaiBreakTree::TrigramProbablity(DWORD dwTag1,DWORD dwTag2,DWORD dwTag3) { DWORD iWeight = 6; if ( (dwTag1 != TAGPOS_UNKNOWN) && (dwTag2 != TAGPOS_UNKNOWN) && (dwTag3 != TAGPOS_UNKNOWN) ) { WCHAR pos1 = ExtractPOS(dwTag1); WCHAR pos2 = ExtractPOS(dwTag2); WCHAR pos3 = ExtractPOS(dwTag3); WCHAR posArray[4]; posArray[0] = pos1; posArray[1] = pos2; posArray[2] = pos3; posArray[3] = 0; iWeight += thaiTrigramIter.GetProb(posArray); /* // TODO: We are hard coding this part until we get finish Trigram probablity analysis // than we simply return stright look up. if ( (pos1 == 18) && // Post verb auxiliary - XVAE (pos2 == 5 ) && // Common Noun - NCMN (pos3 == 18) ) // Post verb auxiliary - XVAE { return 70; // Return 70% probablity } else if ( (pos1 == 18) && // Post verb auxiliary - XVAE (pos2 == 5 ) && // Common Noun - NCMN (pos3 == 5 ) ) // Common Noun - NCMN { return 30; // Return 30%. } */ // iWeight = thaiTrigramIter.GetProb(pos1,pos2,pos3); } DetermineFrequencyWeight(ExtractFrq(dwTag1), &iWeight); DetermineFrequencyWeight(ExtractFrq(dwTag2), &iWeight); DetermineFrequencyWeight(ExtractFrq(dwTag3), &iWeight); // We reached zero probablity. return (DWORD)iWeight; } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: // // Arguments: // // Modifies: // // History: created 8/99 aarayas // // Notes: // //---------------------------------------------------------------------------- unsigned int CThaiBreakTree::TrigramBreak(WCHAR* pwchBegin, WCHAR* pwchEnd1) { // Declare and initialize local variables. WCHAR* pwchBeginWord = pwchBegin; WCHAR* pwchIndex = pwchBegin; unsigned int iWordLen; unsigned int iNumCluster = 1; unsigned int iNumLastCluster; unsigned int iBreakIndex = 0; BYTE nextBreakArray[MAXBREAK]; DWORD nextTagArray[MAXBREAK]; unsigned int iNextBreakIndex; // index for array nextBreakArray and nextTagArray. bool fFoundMatch; unsigned int iWeight; unsigned int iSumWeight; unsigned int iPrevWeight; BYTE iSoundexWordLen; DWORD iPrevProbability; DWORD iCurrentProbability; DWORD dwTagTemp; DWORD dwLastTag; int i; // temporary int for use as need. bool fBeginNewWord; bool fEndWord = false; pszEnd = pwchEnd1; breakArray[0] = 0; POSArray[0] = 0; tagArray[0] = 0; nextBreakArray[0] = 0; nextTagArray[0] = 0; while (true) { // Reset Iterator for generating break for new word. fFoundMatch = false; fBeginNewWord = true; // Get begin word string for next round of word break. pwchIndex = pwchBeginWord; iNextBreakIndex = 0; if (pwchIndex == pszEnd) break; while(true) { iNumLastCluster = iNumCluster; iNumCluster = GetCluster(pwchIndex); if (!thaiTrieIter.MoveCluster(pwchIndex, iNumCluster, fBeginNewWord)) { if ((iNumCluster == 0) && (pwchIndex == pszEnd)) fEndWord = true; else break; } fBeginNewWord = false; pwchIndex += iNumCluster; if (thaiTrieIter.fWordEnd) { if (thaiTrieIter.m_fThaiNumber) { // If we have Thai number accumulate it as one break. assert(iNumCluster == 1); fFoundMatch = true; nextBreakArray[0]= (BYTE)(pwchIndex - pwchBeginWord); nextTagArray[0] = TAGPOS_NCNM; iNextBreakIndex = 1; } else { fFoundMatch = true; nextBreakArray[iNextBreakIndex] = (BYTE)(pwchIndex - pwchBeginWord); nextTagArray[iNextBreakIndex] = thaiTrieIter.dwTag; iNextBreakIndex++; } if (pwchIndex >= pszEnd) { assert(pwchIndex <= pszEnd); // assert should never come up - if it appear likely bug in GetCluster funciton. assert(iNextBreakIndex != 0); breakArray[iBreakIndex] = nextBreakArray[iNextBreakIndex - 1]; tagArray[iBreakIndex] = nextTagArray[iNextBreakIndex - 1]; return (++iBreakIndex); } } else if ((pwchIndex >= pszEnd && iNextBreakIndex == 0) || fEndWord) { assert(pwchIndex <= pszEnd); // assert should never come up - if it appear likely bug in GetCluster funciton. iWordLen = (unsigned int) (pwchIndex - pwchBeginWord); switch (iWordLen) { case 0: if (iBreakIndex > 0) { // if We have a length of one character add it to previous node. breakArray[iBreakIndex - 1] += (BYTE) iNumCluster; tagArray[iBreakIndex - 1] = TAGPOS_UNKNOWN; } else { // if this is the first break create a new break. breakArray[iBreakIndex] = (BYTE) iNumCluster; tagArray[iBreakIndex] = TAGPOS_UNKNOWN; iBreakIndex++; } break; case 1: if (iBreakIndex > 0) { // if We have a length of one character add it to previous node. breakArray[iBreakIndex - 1] += (BYTE) iWordLen; tagArray[iBreakIndex - 1] = TAGPOS_UNKNOWN; } else { // if this is the first break create a new break. breakArray[iBreakIndex] = (BYTE) iWordLen; tagArray[iBreakIndex] = TAGPOS_UNKNOWN; iBreakIndex++; } break; default: if ( iBreakIndex > 0 && ShouldMerge(pwchBeginWord - breakArray[iBreakIndex - 1], breakArray[iBreakIndex - 1], iWordLen , tagArray[iBreakIndex - 1]) ) { breakArray[iBreakIndex - 1] += (BYTE) iWordLen; tagArray[iBreakIndex - 1] = TAGPOS_UNKNOWN; } else { breakArray[iBreakIndex] = (BYTE) iWordLen; tagArray[iBreakIndex] = TAGPOS_UNKNOWN; iBreakIndex++; } } return iBreakIndex; } } if (fFoundMatch) // Longest Matching. { // If we only found one break, than say it the maximum. if (1 == iNextBreakIndex) { breakArray[iBreakIndex] = nextBreakArray[0]; tagArray[iBreakIndex] = nextTagArray[0]; } else { iSumWeight = 0; iPrevWeight = 0; iPrevProbability = 0; iCurrentProbability = 0; dwLastTag = TAGPOS_UNKNOWN; tagArray[iBreakIndex] = TAGPOS_UNKNOWN; for (i = (iNextBreakIndex - 1); i >= 0 ; i--) { if ( iBreakIndex == 0) { iWeight = GetWeight(pwchBeginWord + nextBreakArray[i], &dwTagTemp); if (iWeight != 0) // Bigram Probability iCurrentProbability = (DWORD)BigramProbablity(nextTagArray[i], dwTagTemp); } else { iWeight = GetWeight(pwchBeginWord + nextBreakArray[i], &dwTagTemp); if (iBreakIndex == 1) // Get Trigram Probability. iCurrentProbability = TrigramProbablity(tagArray[iBreakIndex - 1], nextTagArray[i], dwTagTemp); else if (iBreakIndex >= 2) { // Get Trigram Probability. iCurrentProbability = TrigramProbablity(tagArray[iBreakIndex - 2], tagArray[iBreakIndex - 1], nextTagArray[i]); if (iWeight != 0) iCurrentProbability += (DWORD)BigramProbablity(nextTagArray[i],dwTagTemp); } } // Store the string the best maximum weight, if the pair is equal // store the string with maxim if ( (iWeight + nextBreakArray[i] > iSumWeight) || ( (iWeight + nextBreakArray[i] == iSumWeight) && ( (Maximum(iWeight,nextBreakArray[i]) <= iPrevWeight) || (iCurrentProbability > iPrevProbability) ) )) { if (iCurrentProbability >= iPrevProbability || iSumWeight < iWeight + nextBreakArray[i]) { iSumWeight = Maximum(iWeight,1) + nextBreakArray[i]; iPrevWeight = Maximum(iWeight,nextBreakArray[i]); breakArray[iBreakIndex] = nextBreakArray[i]; tagArray[iBreakIndex] = nextTagArray[i]; iPrevProbability = iCurrentProbability; dwLastTag = dwTagTemp; } } } } pwchBeginWord += breakArray[iBreakIndex]; // update begin word for next round. iBreakIndex++; } else { // NOMATCH_FOUND iWordLen = (unsigned int)(pwchIndex - pwchBeginWord); if (iBreakIndex > 0) { i = iBreakIndex - 1; // set i to previous break if (iWordLen == 0) { if (iNumCluster == 1 && *pwchBeginWord == L',' && IsThaiChar(*(pwchBeginWord-breakArray[i])) ) { // We should not merge comma into the word, only merge comma to // Number. // TODO: Should add TAGPOS_PUNCT. breakArray[iBreakIndex] = (BYTE) iNumCluster; tagArray[iBreakIndex] = TAGPOS_UNKNOWN; pwchBeginWord += (BYTE) iNumCluster; // update begin word for next round. iBreakIndex++; } else if (ShouldMerge(pwchBeginWord - breakArray[i], breakArray[i], iNumCluster, tagArray[i])) { // If word length is null use the cluster add to previous node. breakArray[i] += (BYTE) iNumCluster; tagArray[i] = TAGPOS_UNKNOWN; pwchBeginWord += iNumCluster; // update begin word for next round. } else { // Add the unknown word to list. breakArray[iBreakIndex] = (BYTE) iNumCluster; tagArray[iBreakIndex] = TAGPOS_UNKNOWN; pwchBeginWord += (BYTE) iNumCluster; // update begin word for next round. iBreakIndex++; } } else { // Perphase Misspelled word try use sounding to spell the words. if (iWordLen == 1 && iNumCluster == 2 && pwchIndex[1] == L'.') { // The word is an abbrivated words. // TODO: #1. Add TAGPOS_ABBRV. // TODO: #2. May need to add rules code abbrivated word with 3 letters. breakArray[iBreakIndex] = iWordLen + iNumCluster; tagArray[iBreakIndex] = TAGPOS_UNKNOWN; pwchBeginWord += breakArray[iBreakIndex]; iBreakIndex++; } // Try soundex two word back. else if ( (iBreakIndex >= 2) && ( (iSoundexWordLen = (BYTE) SoundexSearch(pwchBeginWord - breakArray[i] - breakArray[i - 1])) > (BYTE) (breakArray[i] + breakArray[i - 1]) ) && GetWeight(pwchBeginWord - breakArray[i] - breakArray[i - 1] + iSoundexWordLen) ) { // Resize the word. pwchBeginWord = (pwchBeginWord - breakArray[i] - breakArray[i - 1]) + iSoundexWordLen; // update begin word for next round. breakArray[i - 1] = iSoundexWordLen; tagArray[i - 1] = thaiTrieIter.dwTag; iBreakIndex--; // Decrement iBreakIndex. } // Try soundex one words back. else if (((iSoundexWordLen = (BYTE) SoundexSearch(pwchBeginWord - breakArray[i])) > (BYTE) breakArray[i]) && GetWeight(pwchBeginWord - breakArray[i] + iSoundexWordLen) ) { // Resize the word pwchBeginWord = (pwchBeginWord - breakArray[i]) + iSoundexWordLen; // update begin word for next round. breakArray[i] = iSoundexWordLen; tagArray[i] = thaiTrieIter.dwTag; } // Try soundex on this word. else if (((iSoundexWordLen = (BYTE) SoundexSearch(pwchBeginWord)) > (BYTE) iWordLen) && GetWeight(pwchBeginWord + iSoundexWordLen) ) { // Resize the word. breakArray[iBreakIndex] = iSoundexWordLen; tagArray[iBreakIndex] = thaiTrieIter.dwTag; pwchBeginWord += iSoundexWordLen; // update begin word for next round. iBreakIndex++; } else if ( ShouldMerge(pwchBeginWord - breakArray[i], breakArray[i], iWordLen , tagArray[i]) ) { // Merge the words. breakArray[i] += (BYTE) iWordLen; tagArray[i] = TAGPOS_UNKNOWN; pwchBeginWord += iWordLen; // update begin word for next round. } else { // Add the unknown word to list. breakArray[iBreakIndex] = (BYTE) iWordLen; tagArray[iBreakIndex] = TAGPOS_UNKNOWN; pwchBeginWord += iWordLen; // update begin word for next round. iBreakIndex++; } } } else { // Add unknown word to list and mark it. if (iWordLen == 0) { // If word length is null use the cluster add to previous node. breakArray[iBreakIndex] = (BYTE) iNumCluster; tagArray[iBreakIndex] = TAGPOS_UNKNOWN; pwchBeginWord += iNumCluster; // update begin word for next round. } else { // We we are here there are 2 case that can happen: // 1. We take too little into our unknown. // 2. We take too much into our unknown word. // Have we taken too little check if this unknown word is an abbrivated words. if (iWordLen == 1 && iNumCluster == 2 && pwchIndex[1] == L'.') breakArray[iBreakIndex] = iWordLen + iNumCluster; // Try to see if we are taking to much, see if we can get a Weight from last cluster. else if ( (iWordLen - iNumLastCluster > 0) && GetWeight(pwchIndex - iNumLastCluster) ) breakArray[iBreakIndex] = iWordLen - iNumLastCluster; else breakArray[iBreakIndex] = (BYTE) iWordLen; tagArray[iBreakIndex] = TAGPOS_UNKNOWN; pwchBeginWord += breakArray[iBreakIndex]; // update begin word for next round. } iBreakIndex++; } } } return iBreakIndex; } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: // // Arguments: // // Modifies: // // History: created 8/99 aarayas // // Notes: // //---------------------------------------------------------------------------- int CThaiBreakTree::Soundex(WCHAR* word) { return thaiTrieIter.Soundex(word); } //+--------------------------------------------------------------------------- // // Function: GetCluster // // Synopsis: The function return the next number of character which represent // a cluster of Thai text. // // ie. Kor Kai, Kor Kai -> 1 // Kor Kai, Sara Um -> 2 // // * Note this function will not return no more than 3 character, // for cluster as this would represent invalid sequence of character. // // Arguments: // // Modifies: // // History: created 7/99 aarayas // // Notes: // //---------------------------------------------------------------------------- unsigned int CThaiBreakTree::GetCluster(WCHAR* pszIndex) { bool fHasSaraE; int iRetValue = 0; bool fNeedEndingCluster = false; if (pszIndex == pszEnd) return 0; while (true) { fHasSaraE= false; // Take all begin cluster character. while (IsThaiBeginClusterCharacter(*pszIndex)) { if (*pszIndex == THAI_Vowel_Sara_E) fHasSaraE = true; pszIndex++; iRetValue++; } if (IsThaiConsonant(*pszIndex)) { pszIndex++; iRetValue++; while (IsThaiUpperAndLowerClusterCharacter(*pszIndex)) { // Mai Han Akat is a special type of cluster that will need at lease // one ending cluster. if (*pszIndex == THAI_Vowel_Sign_Mai_HanAkat) fNeedEndingCluster = true; // In Thai it isn't possible to make a sound if we have the SaraE // following by vowel below vowel. else if ( fHasSaraE && ( (*pszIndex == THAI_Vowel_Sara_II) || (*pszIndex == THAI_Tone_MaiTaiKhu) || (*pszIndex == THAI_Vowel_Sara_I) || (*pszIndex == THAI_Sara_Uee) )) fNeedEndingCluster = true; pszIndex++; iRetValue++; } while (IsThaiEndingClusterCharacter(*pszIndex)) { pszIndex++; iRetValue++; fNeedEndingCluster = false; } // Include period as part of a cluster. Bug#57106 if (*pszIndex == 0x002e) { pszIndex++; iRetValue++; fNeedEndingCluster = false; } } if (fNeedEndingCluster) fNeedEndingCluster = false; else break; } if (iRetValue == 0) iRetValue++; // The character is probably a punctuation. if (pszIndex > pszEnd) { // We need to do this as we have gone over end buff boundary. iRetValue -= (int) (pszIndex - pszEnd); pszIndex = pszEnd; } return iRetValue; } //+--------------------------------------------------------------------------- // // Class: CThaiBreakTree // // Synopsis: // // Arguments: // // wzWord - input string. (in) // iWordLen - input string length. (in) // Alt - find close alternate word (in) // pBreakPos - array of break position allways 5 byte. (out) // // Modifies: // // History: created 3/00 aarayas // // Notes: // //---------------------------------------------------------------------------- int CThaiBreakTree::FindAltWord(WCHAR* pwchBegin,unsigned int iWordLen, BYTE Alt, BYTE* pBreakPos) { // Declare and initialize local variables. unsigned int iNumCluster = 1; WCHAR* pwchBeginWord = pwchBegin; WCHAR* pwchIndex = pwchBegin; bool fBeginNewWord = true; unsigned int iBreakIndex = 0; unsigned int iBreakTemp = 0; unsigned int iBreakTemp1 = 0; unsigned int iBreakTemp2 = 0; pszEnd = pwchBegin + iWordLen; // TODO: Need to clean this code up. switch(Alt) { case 3: while (true) { iNumCluster = GetCluster(pwchIndex); if (!thaiTrieIter1.MoveCluster(pwchIndex, iNumCluster, fBeginNewWord)) return iBreakIndex; fBeginNewWord = false; pwchIndex += iNumCluster; if (thaiTrieIter1.fWordEnd) { iBreakTemp = (unsigned int)(pwchIndex - pwchBeginWord); iBreakTemp1 = GetWeight(pwchIndex); iBreakTemp2 = GetWeight(pwchIndex+iBreakTemp1); if (iBreakTemp + iBreakTemp1 + iBreakTemp2 == iWordLen) { pBreakPos[0] = (BYTE)iBreakTemp; pBreakPos[1] = (BYTE)iBreakTemp1; pBreakPos[2] = (BYTE)iBreakTemp2; return 3; } } if (pwchIndex >= pszEnd) return iBreakIndex; } break; case 2: while (true) { iNumCluster = GetCluster(pwchIndex); if (!thaiTrieIter1.MoveCluster(pwchIndex, iNumCluster, fBeginNewWord)) return iBreakIndex; fBeginNewWord = false; pwchIndex += iNumCluster; if (thaiTrieIter1.fWordEnd) { iBreakTemp = (unsigned int)(pwchIndex - pwchBeginWord); iBreakTemp1 = GetWeight(pwchIndex); if (iBreakTemp + iBreakTemp1 == iWordLen) { pBreakPos[0] = (BYTE)iBreakTemp; pBreakPos[1] = (BYTE)iBreakTemp1; return 2; } } if (pwchIndex >= pszEnd) return iBreakIndex; } break; default: case 1: while (iBreakIndex < Alt) { iNumCluster = GetCluster(pwchIndex); if (!thaiTrieIter1.MoveCluster(pwchIndex, iNumCluster, fBeginNewWord)) return iBreakIndex; fBeginNewWord = false; pwchIndex += iNumCluster; if (thaiTrieIter1.fWordEnd) { fBeginNewWord = true; pBreakPos[iBreakIndex] = (BYTE)(pwchIndex - pwchBeginWord); iBreakIndex++; pwchBeginWord = pwchIndex; } if (pwchIndex >= pszEnd) return iBreakIndex; } break; } return iBreakIndex; }