plugins/contacts/symbian/plugin/src/filtering/cntfilterdetail.cpp
changeset 0 876b1a06bc25
child 5 603d3f8b6302
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/contacts/symbian/plugin/src/filtering/cntfilterdetail.cpp	Wed Aug 25 15:49:42 2010 +0300
@@ -0,0 +1,810 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <centralrepository.h>
+#include <cntfldst.h>
+
+#include "cntfilterdetail.h"
+#include "cntfilterdetaildisplaylabel.h" //todo rename class to follow naming pattern CntFilterDetailDisplayLabel
+#include "cntsqlsearch.h"
+#include "cntsymbianengine.h"
+#include "cnttransformphonenumber.h"
+
+// Telephony Configuration API
+// Keys under this category are used in defining telephony configuration.
+const TUid KCRUidTelConfiguration = {0x102828B8};
+// Amount of digits to be used in contact matching.
+// This allows a customer to variate the amount of digits to be matched.
+const TUint32 KTelMatchDigits                               = 0x00000001;
+// Default match length
+const TInt KDefaultMatchLength(7);
+
+CntFilterDetail::CntFilterDetail(CContactDatabase& contactDatabase,CntSymbianSrvConnection &cntServer,CntDbInfo& dbInfo) 
+                                        : m_contactdatabase(contactDatabase),
+                                          m_srvConnection(cntServer),
+                                          m_dbInfo(dbInfo),
+                                          m_emulateBestMatching(false)
+{
+}
+
+CntFilterDetail::~CntFilterDetail()
+{
+}
+
+
+QList<QContactLocalId> CntFilterDetail::contacts(
+        const QContactFilter &filter,
+        const QList<QContactSortOrder> &sortOrders,
+        bool &filterSupportedflag,
+        QContactManager::Error* error)
+{
+    Q_UNUSED(filterSupportedflag);
+    //Check if any invalid filter is passed 
+    if (!filterSupported(filter) ) {
+        *error =  QContactManager::NotSupportedError;
+        return QList<QContactLocalId>();
+    }
+    QList<QContactLocalId> idList;
+    QContactDetailFilter detailFilter(filter);
+    QString sqlQuery;
+    //Check for phonenumber. Special handling needed
+    if ( (detailFilter.detailDefinitionName() == QContactPhoneNumber::DefinitionName ) &&
+            (detailFilter.detailFieldName() != QContactPhoneNumber::FieldSubTypes)) {
+        //Handle phonenumber ...
+        createMatchPhoneNumberQuery(filter,sqlQuery,error);
+        if (*error == QContactManager::NoError) {
+            //fetch the contacts
+            idList =  m_srvConnection.searchContacts(sqlQuery,error);
+        }
+    }
+
+    else if (detailFilter.matchFlags() == QContactFilter::MatchKeypadCollation) {
+        //predictive search filter
+        idList = HandlePredictiveSearchFilter(filter,error);
+    }
+            
+    // handle other cases
+    else {
+        createSelectQuery(filter,sqlQuery,error);
+        QString sortQuery = m_dbInfo.getSortQuery(sortOrders, sqlQuery, error);
+        
+        if (*error == QContactManager::NoError) {
+            //fetch the contacts
+            idList =  m_srvConnection.searchContacts(sortQuery, error);
+        }
+    }
+    return idList;
+}
+
+bool CntFilterDetail::filterSupported(const QContactFilter& filter) 
+{
+    bool result = false;
+    if (QContactFilter::ContactDetailFilter == filter.type()) {
+        result = true;
+    }
+    return result;
+}
+
+void CntFilterDetail::createSelectQuery(const QContactFilter& filter,
+                                        QString& sqlQuery,
+                                        QContactManager::Error* error)
+
+{
+    if (!filterSupported(filter)) {
+      *error = QContactManager::NotSupportedError;
+      return;
+    }
+    QContactDetailFilter detailFilter(filter);
+    //display label
+    if (detailFilter.detailDefinitionName() == QContactDisplayLabel::DefinitionName) {
+      CntFilterDetailDisplayLabel displayLabelFilter;
+      displayLabelFilter.createSelectQuery(filter, sqlQuery, error);
+    }
+    //type
+    else if (detailFilter.detailDefinitionName() == QContactType::DefinitionName) {
+       if (detailFilter.value().toString() == QContactType::TypeContact)
+           sqlQuery = "SELECT contact_id FROM contact WHERE (type_flags>>24)=0";
+       else if (detailFilter.value().toString() == QContactType::TypeGroup)
+           sqlQuery = "SELECT contact_id FROM contact WHERE (type_flags>>24)=3";
+    }
+    else if (detailFilter.detailDefinitionName() == QContactGuid::DefinitionName) {
+       if (detailFilter.detailFieldName() == QContactGuid::FieldGuid) {
+           QStringList fullGuidValue = detailFilter.value().toString().split('-');
+           if (fullGuidValue.count() == 3) {
+               QString localGuidValue = fullGuidValue.at(1);
+               sqlQuery = "SELECT contact_id FROM contact WHERE guid_string = '" + localGuidValue + '\'';
+           }
+       }
+    }
+    //everything else
+    else {   
+       QString tableName;
+       QString sqlWhereClause;
+       getTableNameWhereClause(detailFilter,tableName,sqlWhereClause,error);
+       //Create the sql query
+       sqlQuery += "SELECT DISTINCT contact_id FROM " + tableName + " WHERE " + sqlWhereClause;
+    }
+}
+
+/*!
+ * Updates match flags for columns.
+ */
+void CntFilterDetail::updateForMatchFlag(const QContactDetailFilter& filter,
+                                         QString& fieldToUpdate ,
+                                         QContactManager::Error* error) const
+{
+    // Modify the filed depending on the query
+    switch (filter.matchFlags()) {
+        case QContactFilter::MatchExactly: {
+            // Pattern for MatchExactly:
+            // " ='xyz'"
+            fieldToUpdate = " ='"
+                           + filter.value().toString() + '\'';
+            *error = QContactManager::NoError;
+            break;
+        }
+        case QContactFilter::MatchContains: {
+            // Pattern for MatchContains:
+            // " LIKE '%xyz%'"
+            fieldToUpdate = " LIKE '%" + filter.value().toString() + "%'" ;
+            *error = QContactManager::NoError;
+            break;
+        }
+        case QContactFilter::MatchStartsWith: {
+            // Pattern for MatchStartsWith:
+            // " LIKE 'xyz%'"
+            fieldToUpdate = " LIKE '" +  filter.value().toString() + "%'"  ;
+            *error = QContactManager::NoError;
+            break;
+        }
+        case QContactFilter::MatchEndsWith: {
+            // Pattern for MatchEndsWith:
+            // " LIKE '%xyz'"
+            fieldToUpdate = " LIKE '%" + filter.value().toString() + '\'' ;
+            *error = QContactManager::NoError;
+            break;
+        }
+        case QContactFilter::MatchFixedString: {
+            *error = QContactManager::NotSupportedError;
+            break;
+        }
+        case QContactFilter::MatchCaseSensitive: {
+            *error = QContactManager::NotSupportedError;
+            break;
+        }
+        default: {
+            *error = QContactManager::NotSupportedError;
+            break;
+        }
+    }
+}
+
+void CntFilterDetail::getTableNameWhereClause(const QContactDetailFilter& detailfilter,
+                                              QString& tableName,
+                                              QString& sqlWhereClause ,
+                                              QContactManager::Error* error) const
+{
+    //Get the table name and the column name
+    QString columnName;
+    bool isSubType;
+
+    m_dbInfo.getDbTableAndColumnName(detailfilter.detailDefinitionName(), detailfilter.detailFieldName(), tableName, columnName, isSubType);
+
+    // return if tableName is empty
+    if (tableName.isEmpty()) {
+        *error = QContactManager::NotSupportedError;
+        return;
+    }
+
+    //check columnName
+    if (columnName.isEmpty()) {
+        *error = QContactManager::NotSupportedError;
+        return;
+    }
+    else if (isSubType) {
+        sqlWhereClause += columnName;
+        sqlWhereClause += " NOT NULL ";
+    }
+    else {
+        sqlWhereClause += ' ' + columnName + ' ';
+        QString fieldToUpdate;
+        //Update the value depending on the match flag
+        updateForMatchFlag(detailfilter,fieldToUpdate,error);
+        sqlWhereClause +=  fieldToUpdate;
+    }
+}
+
+QList<QContactLocalId>  CntFilterDetail::HandlePredictiveSearchFilter(const QContactFilter& filter,
+                                                                      QContactManager::Error* error)
+{
+    QString sqlQuery;
+    
+    if (filter.type() == QContactFilter::ContactDetailFilter) {
+       const QContactDetailFilter detailFilter(filter);
+       if (detailFilter.matchFlags() == QContactFilter::MatchKeypadCollation) {
+           CntSqlSearch sqlSearch;
+           //convert string to numeric format
+            QString pattern = detailFilter.value().toString();
+            sqlQuery = sqlSearch.CreatePredictiveSearch(pattern);
+            return  m_srvConnection.searchContacts(sqlQuery, error);  
+       }
+       else {
+           return QList<QContactLocalId>();
+       }
+    }
+    else {
+        return QList<QContactLocalId>();
+    }
+}
+
+/*
+ * Creates an sql query to fetch contact item IDs for all the contact items
+ * which may contain the specified telephone number in a telephone, fax
+ * or SMS type field.
+ *
+ * The comparison method used is not exact.  The number is compared starting from
+ * the right side of the number and the method returns an array of candidate
+ * matches.  Punctuation (e.g. spaces) and other alphabetic characters are ignored
+ * when comparing.
+ * 
+ * Note that due to the way numbers are stored in the database, it is recommended
+ * that at least 7 match digits are specified even when matching a number
+ * containing fewer digits.  Failure to follow this guideline may (depending on the
+ * database contents) mean that the function will not return the expected Contact
+ * IDs.
+ */
+void CntFilterDetail::createMatchPhoneNumberQuery(
+                                      const QContactFilter& filter,
+                                      QString& sqlQuery,
+                                      QContactManager::Error* error)
+
+{
+    if (!filterSupported(filter) ) {
+      *error = QContactManager::NotSupportedError;
+      return;
+    }
+          
+    QContactDetailFilter detailFilter(filter);
+    QString number((detailFilter.value()).toString());
+    TPtrC numberPtr(reinterpret_cast<const TUint16*>(number.utf16()));
+    
+    TInt matchLengthFromRight(KDefaultMatchLength);
+    // no need to propagate error, we can use the default match length
+    TRAP_IGNORE(getMatchLengthL(matchLengthFromRight));
+    
+    TInt numLowerDigits = matchLengthFromRight;
+    TInt numUpperDigits = 0;
+    
+    if (numLowerDigits > KLowerSevenDigits) {
+        numLowerDigits = KLowerSevenDigits;
+        numUpperDigits = matchLengthFromRight - KLowerSevenDigits;
+    }
+    else if (numLowerDigits == 0) {
+        // best match phonenumbers
+        numLowerDigits = KLowerSevenDigits;
+    }
+    
+    TMatch phoneDigits = createPaddedPhoneDigits(
+                          numberPtr, numLowerDigits, numUpperDigits, error);
+    
+    if (*error == QContactManager::NoError) {
+        // select fields for contacts that match phone lookup
+        //  SELECT contact_id FROM comm_addr
+        //      WHERE value = [value string] AND type = [type value];
+        //
+        QString type =  QString(" type = %1").arg(CntDbInfo::EPhoneNumber);
+        QString value =  QString(" value = %1").arg(phoneDigits.iLowerSevenDigits);
+        QString whereClause = " WHERE" + value + " AND" + type;
+        if (matchLengthFromRight <= KLowerSevenDigits) {
+            // Matching 7 or less digits...
+            sqlQuery = "SELECT contact_id FROM comm_addr" + whereClause;
+        }
+        else {
+            // Checking the upper digits...
+            TMatch phoneNumber = createPhoneMatchNumber(
+                                  numberPtr, numLowerDigits, numUpperDigits, error);
+            QString fieldToMatch = QString(" LIKE '%1").arg(phoneNumber.iUpperDigits) + "%'"  ;
+            whereClause += " AND extra_value" + fieldToMatch;
+            sqlQuery = "SELECT contact_id FROM comm_addr" + whereClause;
+        }
+      
+        // refine search
+        if (bestMatchingEnabled()) {
+            QList<QContactLocalId> list =  m_srvConnection.searchContacts(sqlQuery,error);
+            QList<QContactLocalId> bestMatchingIds;
+            if (*error == QContactManager::NoError) {
+                TRAP_IGNORE(
+                        bestMatchingIds = getBestMatchPhoneNumbersL(number, list, error);
+                )
+                if (bestMatchingIds.count()>0) {
+                    // recreate query
+                    QString selectQuery = " SELECT contact_id FROM comm_addr WHERE contact_id in (";
+                    QString ids = QString("%1").arg(bestMatchingIds.at(0));
+                    for(int i=1; i<bestMatchingIds.count(); ++i) {
+                        ids += QString(" ,%1").arg(bestMatchingIds.at(i));
+                    }
+                    selectQuery += ids + ')';
+                    sqlQuery = selectQuery;
+                }
+                else {
+                    // empty list
+                    QString selectQuery = " SELECT contact_id FROM comm_addr WHERE contact_id in (null)";
+                    sqlQuery = selectQuery;
+                }
+            }
+        }
+    }
+}
+
+#ifdef PBK_UNIT_TEST
+void CntFilterDetail::emulateBestMatching()
+{
+    m_emulateBestMatching = true;
+}
+#endif
+
+/*
+ * Best matching number if matchLengthFromRight set to 0
+ */
+bool CntFilterDetail::bestMatchingEnabled() 
+{
+#ifdef PBK_UNIT_TEST
+    if (m_emulateBestMatching) {
+        return true;
+    }
+#endif
+    bool result = false;
+    TInt matchLengthFromRight(KDefaultMatchLength);
+    TRAP_IGNORE(getMatchLengthL(matchLengthFromRight));
+    if (matchLengthFromRight == 0) {
+        result = true;
+    }
+    return result;
+}
+
+/*
+ * Get the match length setting. Digits to be used in matching (counted from
+ * right).
+ */
+bool CntFilterDetail::getMatchLengthL(TInt& matchLength)
+{
+#ifdef PBK_UNIT_TEST
+    if (m_emulateBestMatching) {
+        matchLength = 0;
+        return true;
+    }
+#endif
+    //Get number of digits used to match
+    bool result = false;
+    CRepository* repository = CRepository::NewL(KCRUidTelConfiguration);
+    TInt err = repository->Get(KTelMatchDigits, matchLength);
+    delete repository;
+    result = (err == KErrNone);
+    return result;
+}
+
+/*
+ * Convert the supplied string to a matchable phone number.
+ *
+ * \param text Descriptor containing phone number.
+ * \param lowerMatchlength Number of least significant phone digits to use.
+ * \param upperMatchLength Number of most significant phone digits to use.
+ * \param error Qt error code.
+ * \return The hash code(s) to use when matching the supplied phone number.
+ */
+CntFilterDetail::TMatch CntFilterDetail::createPaddedPhoneDigits(
+                                            const TDesC& number, 
+                                            const TInt numLowerDigits, 
+                                            const TInt numUpperDigits,
+                                            QContactManager::Error* error)
+{
+    TMatch phoneNumber = createPhoneMatchNumber(
+                                            number, numLowerDigits, numUpperDigits, error);
+    if (*error == QContactManager::NoError) {
+        if (phoneNumber.iNumLowerDigits + phoneNumber.iUpperDigits == 0) {
+            // No digits, do nothing
+        }
+        else if (phoneNumber.iNumLowerDigits < KLowerSevenDigits) {
+            // Only the lower-digits hash is used, pad out the number to
+            // KLowerSevenDigits.
+            TInt pad = KLowerSevenDigits - phoneNumber.iNumLowerDigits;
+            phoneNumber.iLowerSevenDigits = TMatch::padOutPhoneMatchNumber(phoneNumber.iLowerSevenDigits,pad);
+        }
+        else if (phoneNumber.iNumUpperDigits < (KMaxPhoneMatchLength - KLowerSevenDigits) ) {
+            // The lower-digits hash is full, pad out the upper hash if less than 15
+            // digits total.
+            TInt pad = KMaxPhoneMatchLength - KLowerSevenDigits - phoneNumber.iNumUpperDigits;
+            phoneNumber.iUpperDigits = TMatch::padOutPhoneMatchNumber(phoneNumber.iUpperDigits,pad);
+        }
+    }
+    return phoneNumber;
+}
+
+/*
+ * Returns the hash code(s) to use when matching the supplied phone number.  If the
+ * number supplied has more actual phone digits (i.e. not including spaces) than
+ * KLowerSevenDigits, a second hash is generated to hold the remaining most
+ * significant phone digits. Removes the non-digit characters.
+
+ * \param text Descriptor containing contacts phone number field.
+ * \param lowerMatchlength Number of least significant phone digits to use.
+ * \param upperMatchLength Number of most significant phone digits to use.
+ * \param error Qt error code.
+ * \return The hash code(s) to use when matching the supplied phone number.
+ */
+CntFilterDetail::TMatch CntFilterDetail::createPhoneMatchNumber(
+                                            const TDesC& text, 
+                                            TInt lowerMatchLength, 
+                                            TInt upperMatchLength,
+                                            QContactManager::Error* error)
+{
+    const TInt KBufLength = KCntMaxTextFieldLength+1;
+    TBuf<KBufLength> buf;
+    
+    if (text.Length() <= KBufLength) {
+        buf = text;
+    }
+    else {
+        buf = text.Right(KBufLength);
+    }
+    TMatch::stripOutNonDigitChars(buf);
+    
+    TMatch phoneNumber;
+    if (buf.Length() == 0) {
+        *error = QContactManager::BadArgumentError;
+        return phoneNumber;
+    }
+    
+    // Generate a hash for the upper digits only if the phone number string is
+    // large enough and more than 7 digits are to be matched.
+    TInt phoneNumberlength = buf.Length();
+    if ((phoneNumberlength > KLowerSevenDigits) && (upperMatchLength > 0)) {
+        TPtrC upperPart = buf.Left(phoneNumberlength - KLowerSevenDigits);
+        phoneNumber.iUpperDigits = TMatch::createHash(upperPart,
+            upperMatchLength, phoneNumber.iNumUpperDigits);
+    }
+    // Generate a hash of the lower digits.
+    phoneNumber.iLowerSevenDigits = TMatch::createHash(buf, 
+            lowerMatchLength, phoneNumber.iNumLowerDigits);
+    
+    return phoneNumber;
+}
+
+QList<QContactLocalId> CntFilterDetail::getBestMatchPhoneNumbersL(
+                                      const QString number,
+                                      const QList<QContactLocalId>& idList,
+                                      QContactManager::Error* error)
+
+{
+    TPtrC numberPtr(reinterpret_cast<const TUint16*>(number.utf16()));
+    RBuf matchNumber;
+    matchNumber.CleanupClosePushL();
+    matchNumber.CreateL(numberPtr);
+  
+    QList<QContactLocalId> bestMatchingIds;
+    for (int i=0; i<idList.count(); i++) {
+        QContact contact = m_dbInfo.engine()->contact(idList.at(i), QContactFetchHint(), error);
+        QList<QContactPhoneNumber> details = contact.details<QContactPhoneNumber>();
+        CntTransformContactData* transformPhoneNumber = new CntTransformPhoneNumber();
+        
+        bool matchFound(false);
+        for (int j = 0;j < details.count(); j++) {
+            QList<CContactItemField *> fields = transformPhoneNumber->transformDetailL(details.at(j));
+            for (int k = 0;k < details.count() && !matchFound; k++) {
+                CContactTextField* storage = fields.at(k)->TextStorage();
+                RBuf phoneNumber;
+                phoneNumber.CleanupClosePushL();
+                phoneNumber.CreateL(storage->Text());
+                if (TMatch::validateBestMatchingRulesL(phoneNumber,matchNumber)) {
+                    matchFound = true;
+                }
+                // phoneNumber
+                CleanupStack::PopAndDestroy();
+            }
+            if (matchFound) {
+                bestMatchingIds.append(idList.at(i));
+                break;
+            }
+        }
+        delete transformPhoneNumber;
+    }
+    // matchNumber
+    CleanupStack::PopAndDestroy();
+    return bestMatchingIds;
+}
+
+//CntFilterDetail::TMatch constructor.
+CntFilterDetail::TMatch::TMatch()
+    :
+    iLowerSevenDigits(0),
+    iUpperDigits(0),
+    iNumLowerDigits(0),
+    iNumUpperDigits(0)
+{
+}
+
+/*
+ * Generates a hash value by reversing the matchLength least significant digits,
+ * ignoring non-digits and zeroes at the end of the number.  Returns error if no phone
+ * digits are supplied.
+
+ * \param phoneNumberString A descriptor containing a phone number.
+ * \param matchLength The number of digits from the right of the phone number to use.
+ * \param numPhoneDigits The number of digits found in the phone number string.
+ * \param error Qt error code.*
+ * \return An integer representation of the phone number string in reverse.
+ */
+TInt32 CntFilterDetail::TMatch::createHash(
+                                    const TDesC& phoneNumberString, 
+                                    TInt matchLength, 
+                                    TInt& numPhoneDigits)
+{
+    TInt phoneNumberLength = phoneNumberString.Length();
+    TInt startIndex = 0;
+    if (phoneNumberLength > matchLength) {
+        startIndex = phoneNumberLength - matchLength;
+    }
+    
+    numPhoneDigits = 0;
+    TUint reversedDigits = 0;
+    TInt mult = 1;
+    
+    for (TInt chrIndex = startIndex; (numPhoneDigits < matchLength) && (chrIndex < phoneNumberLength); chrIndex++) {
+        TChar chr = phoneNumberString[chrIndex];
+        if (chr.IsDigit()) {
+            reversedDigits += (chr.GetNumericValue()) * mult;
+            mult = mult * 10;
+            ++numPhoneDigits;
+        }
+    }
+    return reversedDigits ;
+}
+
+void CntFilterDetail::TMatch::stripOutNonDigitChars(TDes& text)
+{
+    for (TInt chrPos = 0; chrPos < text.Length(); ++chrPos) {
+        TChar chr = text[chrPos];
+        if (!chr.IsDigit()) {
+            text.Delete(chrPos, 1);
+            --chrPos;
+        }
+    }
+}
+
+TInt32 CntFilterDetail::TMatch::padOutPhoneMatchNumber(TInt32& phoneNumber,
+                                                       TInt padOutLength)
+{
+    TInt32 result(phoneNumber);
+    const TInt KMult(10);
+    for (TInt i = 0; i < padOutLength; ++i) {
+        result *= KMult;
+    }
+    phoneNumber = result;
+    return result;
+}
+
+// Removes non-digit chars except plus form the beginning
+// Checks if number matches to one of defined types
+//
+TInt CntFilterDetail::TMatch::formatAndCheckNumberType(TDes& number)
+    {
+    _LIT( KOneZeroPattern, "0*" );
+    _LIT( KTwoZerosPattern, "00*" );
+    _LIT( KPlusPattern, "+*" );
+    const TChar KPlus = TChar('+');
+    const TChar KZero = TChar('0');
+    const TChar KAsterisk = TChar('*');
+    const TChar KHash = TChar('#');
+    
+    for( TInt pos = 0; pos < number.Length(); ++pos ) {
+        TChar chr = number[pos];
+        if ( !chr.IsDigit() && !( pos == 0 && chr == KPlus  )
+                && !( chr == KAsterisk ) && !( chr == KHash ) ) {
+            number.Delete( pos, 1 );
+            --pos;
+        }
+    }
+    
+    TInt format;
+    
+    if (!number.Match(KTwoZerosPattern) && number.Length() > 2 && number[2] != KZero) {
+        format = ETwoZeros;
+    }
+    else if (!number.Match(KOneZeroPattern)&& number.Length() > 1 && number[1] != KZero) {
+        format = EOneZero;
+    }
+    else if (!number.Match(KPlusPattern) && number.Length() > 1 && number[1] != KZero) {
+        format = EPlus;
+    }
+    else if (number.Length() > 0 && number[0] != KZero && ( ( TChar ) number[0] ).IsDigit()) {
+        format = EDigit;
+    }
+    else {
+        format = EUnknown;
+    }
+
+    return format;
+    }
+
+TBool CntFilterDetail::TMatch::validateBestMatchingRulesL(const TDesC& phoneNumber, const TDesC& matchNumber)
+    {
+    RBuf numberA;
+    numberA.CleanupClosePushL();
+    numberA.CreateL(matchNumber);
+    TNumberType numberAType = (TNumberType) TMatch::formatAndCheckNumberType(numberA);
+    
+    RBuf numberB;
+    numberB.CleanupClosePushL();
+    numberB.CreateL(phoneNumber);
+    TNumberType numberBType = (TNumberType) TMatch::formatAndCheckNumberType(numberB);
+
+    TBool match = (!numberB.Compare(numberA) ||
+                    TMatch::checkBestMatchingRules(numberA, numberAType, numberB, numberBType) ||
+                    TMatch::checkBestMatchingRules(numberB, numberBType, numberA, numberAType));
+    
+    // cleanup
+    CleanupStack::PopAndDestroy(2);
+    return match;
+    }
+
+TBool CntFilterDetail::TMatch::checkBestMatchingRules(
+        const TDesC& numberA, TNumberType numberAType,
+        const TDesC& numberB, TNumberType numberBType  )
+    {
+    TBool result = EFalse;
+    
+    // Rules for matching not identical numbers
+    // Rules details are presented in Best_Number_Matching_Algorithm_Description.doc
+    
+    // rule International-International 1
+    if (!result && numberAType == EPlus && numberBType == ETwoZeros) {
+        TPtrC numberAPtr = numberA.Right(numberA.Length() - 1);
+        TPtrC numberBPtr = numberB.Right(numberB.Length() - 2);
+        result = !(numberAPtr.Compare(numberBPtr));
+        if (result) {
+            return result;
+        }
+    }
+
+    // rule International-International 2
+    if (numberAType == EPlus && numberBType == EDigit) {
+        TPtrC numberAPtr = numberA.Right( numberA.Length() - 1 );
+        if (numberAPtr.Length() < numberB.Length()) {
+            TPtrC numberBPtr = numberB.Right( numberAPtr.Length() );
+            result = !(numberAPtr.Compare(numberBPtr));
+            if (result) {
+                return result;
+            }
+        }
+    }
+
+    // rule International-International 3
+    if (numberAType == ETwoZeros && numberBType == EDigit) {
+        TPtrC numberAPtr = numberA.Right(numberA.Length() - 2);
+        if (numberAPtr.Length() < numberB.Length()) {
+            TPtrC numberBPtr = numberB.Right(numberAPtr.Length());
+            result = !(numberAPtr.Compare(numberBPtr));
+            if (result) {
+                return result;
+            }
+        }
+    }
+
+    // rule International-Operator 1
+    if (numberAType == EOneZero && numberBType == EPlus
+            || numberAType == EDigit && numberBType == EPlus) {
+        TPtrC numberAPtr;
+        if (numberAType == EOneZero) {
+            numberAPtr.Set(numberA.Right(numberA.Length() - 1));
+        }
+        else {
+            numberAPtr.Set(numberA);
+        }
+        if (numberAPtr.Length() < numberB.Length() - 1) {
+            TPtrC numberBPtr = numberB.Right(numberAPtr.Length());
+            result = !(numberAPtr.Compare(numberBPtr));
+            if (result) {
+                return result;
+            }
+        }
+    }
+
+    // rule International-Operator 2
+    if (numberAType == EOneZero && numberBType == ETwoZeros
+            || numberAType == EDigit && numberBType == ETwoZeros) {
+        TPtrC numberAPtr;
+        if (numberAType == EOneZero) {
+            numberAPtr.Set(numberA.Right(numberA.Length() - 1));
+        }
+        else {
+            numberAPtr.Set(numberA);
+        }
+        if (numberAPtr.Length() < numberB.Length() - 2) {
+            TPtrC numberBPtr = numberB.Right(numberAPtr.Length());
+            result = !(numberAPtr.Compare(numberBPtr));
+            if (result) {
+                return result;
+            }
+        }
+    }
+
+    // rule International-Operator 3
+    if (numberAType == EOneZero && numberBType == EDigit
+            || numberAType == EDigit && numberBType == EDigit) {
+        TPtrC numberAPtr;
+        if (numberAType == EOneZero) {
+            numberAPtr.Set(numberA.Right( numberA.Length() - 1));
+        }
+        else {
+            numberAPtr.Set(numberA);
+        }
+        if (numberAPtr.Length() < numberB.Length()) {
+            TPtrC numberBPtr = numberB.Right(numberAPtr.Length());
+            result = !(numberAPtr.Compare(numberBPtr));
+            if (result) {
+                return result;
+            }
+        }
+    }
+
+    // rule Operator-Operator 1
+    if (numberAType == EOneZero && numberBType == EDigit) {
+        TPtrC numberAPtr = numberA.Right(numberA.Length() - 1);
+        result = !(numberAPtr.Compare(numberB));
+        if (result) {
+            return result;
+        }
+    }
+    
+    // rule North America Numbering Plan 1
+    if (numberAType == EDigit && numberBType == EPlus) {
+        TPtrC numberBPtr = numberB.Right(numberB.Length() - 1);
+        result = !(numberA.Compare(numberBPtr));
+        if (result) {
+            return result;
+        }
+    }
+
+    // More exceptional acceptance rules can be added here
+    // Keep rules updated in the document Best_Number_Matching_Algorithm_Description.doc
+
+    return result;
+    }