diff -r 748ec5531811 -r 336bee5c2d35 charconvfw/numbergrouping/Src/NumberGrouping.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/charconvfw/numbergrouping/Src/NumberGrouping.cpp Wed Sep 01 12:39:40 2010 +0100 @@ -0,0 +1,951 @@ +/* +* Copyright (c) 2002-2008 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* This component and the accompanying materials are made available +* under the terms of "Eclipse Public License v1.0" +* which accompanies this distribution, and is available +* at the URL "http://www.eclipse.org/legal/epl-v10.html". +* +* Initial Contributors: +* Nokia Corporation - initial contribution. +* +* Contributors: +* +* Description: +* +*/ + +#include "NumberGrouping.h" +#include "RegularExpression.h" +#include "cleanuputil.h" + +#include +#include +#include +#include + +const TText KNumberGroupingWildcard('n'); +const TText KNumberGroupingOneOrMoreCharactersToken('~'); + +// This constant represents all the valid whitespace that we can handle. Several +// APIs give whitespace special significance as a formatting character, especially +// those methods used to obtain text for formatting into fixed-width UI elements +// where spaces are not to be rendered at the margins. +const TText KNumberGroupingSpace(' '); + +const TInt KMinimumLengthToGroup = 1; // No grouping occurs if fewer than this in unformatted buffer + +#include + + +GLDEF_C void Panic(TNumberGroupingPanic aPanic) + { + _LIT(KPanicCat,"Number Grouping"); + User::Panic(KPanicCat, aPanic); + } + +// Valid phone number characters apart from IsDigit() characters are listed here +const TText KAdditionalPhoneNumberCharacters[] = {'+','#','*','p','w'}; + + + + +NONSHARABLE_CLASS(CPNGNumberGroupingExtension): public CBase + { +public: + CPNGNumberGroupingExtension(); + ~CPNGNumberGroupingExtension(); +public: + TInt iMaxExtraCharacters; + TInt iNumberGroupingCRValue; + }; + +CPNGNumberGroupingExtension::CPNGNumberGroupingExtension() + { + } + +CPNGNumberGroupingExtension::~CPNGNumberGroupingExtension() + { + } + + + +CPNGNumberGrouping::TPNGSeparator::TPNGSeparator() + : iPosition(-1), iSeparatorCharacter(KNumberGroupingSpace) + { + } + +CPNGNumberGrouping::TPNGSeparator::TPNGSeparator( TInt aPosition, TText aSeparatorCharacter ) + : iPosition(aPosition), iSeparatorCharacter(aSeparatorCharacter) + { + } + +CPNGNumberGrouping::TPNGGroupingInfo::TPNGGroupingInfo() + { + } + +// CPNGNumberGrouping - grouping engine class +CPNGNumberGrouping::CPNGNumberGrouping(TInt aMaxLength /* = 0 */, TBool aReversed /* = EFalse */) : + iForceLanguage(ELangTest), + iUnformattedNumberPtr(KNullDesC), + iFormattedNumberPtr(KNullDesC), + iReverseFormattedNumberPtr(KNullDesC), + iSelectionPtr(KNullDesC), + iLanguage(ELangTest), + iMaxUnformattedLength(aMaxLength), + iReversed(aReversed), + iMatchedPatternIndex(ENoMatchedPattern) + { + } + +EXPORT_C CPNGNumberGrouping* CPNGNumberGrouping::NewL( TInt aMaxLength, TBool aReserved) + { + CPNGNumberGrouping* s = NewLC(aMaxLength, aReserved); + CleanupStack::Pop(); + return s; + } + +EXPORT_C CPNGNumberGrouping* CPNGNumberGrouping::NewLC( TInt aMaxLength, TBool aReserved) + { + CPNGNumberGrouping* s = new(ELeave)CPNGNumberGrouping( aMaxLength, aReserved); + CleanupStack::PushL(s); + s->ConstructL(); + return s; + } + +void CPNGNumberGrouping::ConstructL() + { + iExtension = new (ELeave) CPNGNumberGroupingExtension(); + CRepository* repository = NULL; + iExtension->iNumberGroupingCRValue = 0; + TRAPD(ret, repository = CRepository::NewL(KCRUidNumberGrouping)); + if (ret == KErrNone) + { + ret = repository->Get(KNumberGrouping, iExtension->iNumberGroupingCRValue); + } + delete repository; + + // Read from resource first in order to obtain a value for iExtension->iMaxExtraCharacters + iLanguage = doReadLanguageFromSharedData(); + doReadFormatInfoFromResourceFileL(); // This sets iExtension->iMaxExtraCharacters + + // Allocation of buffers. Note that iMaxUnformattedLength must be retained as member + // data because HBufCs may come back with more storage available than asked for. + // The uncertainty in the actual length provided by the HBufCs means that although + // they are asked to be different by iExtension->iMaxExtraCharacters, the difference between the + // actual MaxLengths may be less than iMaxExtraCharcaters. + // The approach decided upon is to retain iMaxUnformattedLength as member data and + // use IT everywhere throughout this class's implementation, NEVER using + // iUnformattedNumber->Des().MaxLength() + iUnformattedNumber = HBufC::NewL(iMaxUnformattedLength); + iFormattedNumber = HBufC::NewL(iMaxUnformattedLength + iExtension->iMaxExtraCharacters); + + // Create revesed buffer only if requested + if ( iReversed ) + iReverseFormattedNumber = HBufC::NewL(iMaxUnformattedLength + iExtension->iMaxExtraCharacters); + } + +EXPORT_C CPNGNumberGrouping::~CPNGNumberGrouping() + { + doClearGroupingItemsList(); + + delete iUnformattedNumber; + delete iFormattedNumber; + delete iReverseFormattedNumber; + delete iRegExp; + delete iExtension; + } + +EXPORT_C TInt CPNGNumberGrouping::Insert(TInt aIndex, TText aChar) + { + + if( aIndex >= 0 && aIndex <= iUnformattedNumber->Length()) + { + + if(iUnformattedNumber->Length() >= iMaxUnformattedLength) + return KErrOverflow; + + TBuf<1> bufChar(1); + bufChar[0] = aChar; + TPtr ptrModifyable(iUnformattedNumber->Des()); + ptrModifyable.Insert(aIndex, bufChar); + + doClearFormattedNumbers(); + + return KErrNone; + } + + return KErrIndexOutOfRange; + } + +EXPORT_C TInt CPNGNumberGrouping::Delete(TInt aIndex) + { + if(aIndex >= 0 && aIndex < iUnformattedNumber->Length()) + { + TPtr ptrModifyable(iUnformattedNumber->Des()); + ptrModifyable.Delete(aIndex, KSingleCharacter); + + doClearFormattedNumbers(); + + return KErrNone; + } + + return KErrIndexOutOfRange; + } + +EXPORT_C TInt CPNGNumberGrouping::Append(TText aChar) + { + if(iUnformattedNumber->Length() >= iMaxUnformattedLength) + return KErrOverflow; + + TBuf<1> bufChar(1); + bufChar[0] = aChar; + TPtr ptrModifyable(iUnformattedNumber->Des()); + ptrModifyable.Append(bufChar); + + doClearFormattedNumbers(); + + return KErrNone; + } + +EXPORT_C TInt CPNGNumberGrouping::Set(const TDesC& aNumber) + { + if( aNumber.Length() > iMaxUnformattedLength ) + return KErrOverflow; + + TPtr ptrModifyable( iUnformattedNumber->Des() ); + ptrModifyable.Copy( aNumber ); + + doClearFormattedNumbers(); + + return KErrNone; + } + +EXPORT_C TInt CPNGNumberGrouping::Length() const + { + if(!iFormattedNumber->Length()) // This test is used as the trigger to reformat + FormattedNumber(); + + return iFormattedNumber->Length(); + } + +EXPORT_C TInt CPNGNumberGrouping::UnFormattedLength() const + { + return iUnformattedNumber->Length(); + } + +EXPORT_C TInt CPNGNumberGrouping::MaxDisplayLength() const + { + // Despite its name, this method returns the max length of the UNFORMATTED buffer + // This must not be implemented to return the actual length available in + // iUnformattedBuffer. + return iMaxUnformattedLength; + } + +EXPORT_C TBool CPNGNumberGrouping::IsSpace(TInt aPos) const + { + // Very tricky semantics for this. Must be a space inserted by the formatting + if ( iFormattedNumber->Length() > aPos && + iFormattedNumber->operator[](aPos) == KNumberGroupingSpace && + // Check also that is is less than the length to group + inserted characters + aPos < ( LengthToGroup() + (iFormattedNumber->Length() - iUnformattedNumber->Length()) ) ) + return ETrue; + else + return EFalse; + } + +EXPORT_C const TDesC& CPNGNumberGrouping::FormattedNumber(TInt aFrom, TInt aTo) const + { + if(iUnformattedNumber->Length() != 0 && + aFrom >= 0 && + aFrom <= aTo ) + { + FormattedNumber(); + iFormattedNumberPtr.Set(KNullDesC); + + TInt length = iFormattedNumber->Length(); + if(aTo < length + 1) + { + TInt length = iFormattedNumber->Length(); + if ( iExtension->iNumberGroupingCRValue ) + { + // Advance to the next non-space + while( (aFrom < length ) && (*iFormattedNumber)[aFrom] == KNumberGroupingSpace ) + { + ++aFrom; + } + + // Retreat to the last non-space + while( (aTo > 0) && (*iFormattedNumber)[aTo] == KNumberGroupingSpace ) + { + --aTo; + } + } + + // Does fetching the descriptor still make sense? + if ( (0 <= aFrom) && (aFrom <= aTo) && (aTo < length) ) + iFormattedNumberPtr.Set( iFormattedNumber->Mid( aFrom, aTo-aFrom+1 ) ); + } + } + else + { + if(iFormattedNumber->Length()) + { + CPNGNumberGrouping* pThis = const_cast(this); + pThis->doClearFormattedNumbers(); + } + + iFormattedNumberPtr.Set(KNullDesC); + } + + return iFormattedNumberPtr; + } + +EXPORT_C const TDesC& CPNGNumberGrouping::FormattedNumber() const + { + if( !iFormattedNumber->Length() ) + { + TInt err = KErrNone; + + CPNGNumberGrouping* pThis = const_cast(this); + + if( LengthToGroup() < KMinimumLengthToGroup || !iExtension->iNumberGroupingCRValue ) + { + // This is now just a short cut, as doNumberGroupingL handles premature truncation of + // formatting. But this avoids all the language checking + doNumberSquashing(); // copies the unformatted number straight into the formatted number + } + else + { + TLanguage eLanguage; + if(iForceLanguage != ELangTest) + eLanguage = iForceLanguage; + else + eLanguage = doReadLanguageFromSharedData(); + + if(eLanguage != iLanguage) + { + iLanguage = eLanguage; + + TRAP(err, pThis->doReadFormatInfoFromResourceFileL()); + if(err != KErrNone) + { + iFormattedNumberPtr.Set(KNullDesC); + return iFormattedNumberPtr; + } + } + + TRAP(err, doNumberGroupingL()); + } + + if(err != KErrNone) + pThis->doClearFormattedNumbers(); + else + iFormattedNumberPtr.Set(iFormattedNumber->Ptr(), iFormattedNumber->Length()); + } + + return iFormattedNumberPtr; + } + +EXPORT_C const TDesC& CPNGNumberGrouping::ReverseFormattedNumber(TInt aFrom, TInt aTo) const + { + if ( iReversed ) + { + if(iUnformattedNumber->Length() != 0 && + aFrom >= 0 && + aFrom <= aTo) + { + ReverseFormattedNumber(); + + iReverseFormattedNumberPtr.Set(KNullDesC); + + TInt length = iReverseFormattedNumber->Length(); + if( aTo < length + 1 ) + { + // Advance to the next non-space + if( iExtension->iNumberGroupingCRValue ) + { + while( (aFrom < length ) && (*iReverseFormattedNumber)[aFrom] == KNumberGroupingSpace ) + { + ++aFrom; + } + + // Retreat to the last non-space + while( (aTo > 0) && (*iReverseFormattedNumber)[aTo] == KNumberGroupingSpace ) + { + --aTo; + } + } + + // Does fetching the descriptor still make sense? + if ( (0 <= aFrom) && (aFrom <= aTo) && (aTo < length) ) + iReverseFormattedNumberPtr.Set( + iReverseFormattedNumber->Mid( aFrom, aTo-aFrom+1) ); + } + } + else + iReverseFormattedNumberPtr.Set(KNullDesC); + } + + return iReverseFormattedNumberPtr; // Zero initialized at construction + } + +EXPORT_C const TDesC& CPNGNumberGrouping::ReverseFormattedNumber() const + { + if( iReverseFormattedNumber && !iReverseFormattedNumber->Length()) + { + if(!iFormattedNumber->Length()) + FormattedNumber(); + + TInt nLength = iFormattedNumber->Length(); + + TPtr ptrModifyable(iReverseFormattedNumber->Des()); + TBuf<1> bufChar(1); + + for(TInt i = nLength; i > 0; --i) + { + TText cChar = (*iFormattedNumber)[i-1]; + bufChar[0] = cChar; + ptrModifyable.Insert(nLength - i, bufChar); + } + + iReverseFormattedNumberPtr.Set(iReverseFormattedNumber->Ptr(), nLength); + } + + return iReverseFormattedNumberPtr; + } + +EXPORT_C const TDesC& CPNGNumberGrouping::Selection(TInt aFrom, TInt aTo) const + { + if(aFrom < iUnformattedNumber->Length()) + { + TPtr ptrUnformatted = iUnformattedNumber->Des(); + iSelectionPtr.Set(&(ptrUnformatted[aFrom]), aTo - aFrom); + } + else + iSelectionPtr.Set(KNullDesC); + + return iSelectionPtr; + } + +EXPORT_C const TDesC& CPNGNumberGrouping::UnFormattedNumber(TInt aFrom, TInt aTo) const + { + if (iUnformattedNumber && aFrom >= 0 && aFrom <= aTo && aTo < iUnformattedNumber->Length()) + { + iUnformattedNumberPtr.Set(&((*iUnformattedNumber)[aFrom]), aTo - aFrom + 1); + } + else + { + iUnformattedNumberPtr.Set(KNullDesC); + } + return iUnformattedNumberPtr; + } + +EXPORT_C const TDesC& CPNGNumberGrouping::UnFormattedNumber() const + { + return UnFormattedNumber(0, iUnformattedNumber->Length() - 1); + } + +TLanguage CPNGNumberGrouping::doReadLanguageFromSharedData() const + { + if (iExtension->iNumberGroupingCRValue) + { + return ELangAmerican; + } + else + { + return ELangTest; + } + } + +void CPNGNumberGrouping::doClearFormattedNumbers() + { + TPtr ptrModifyable( iUnformattedNumber->Des() ); + + for (TInt index = 0; index < ptrModifyable.Length(); index++) + { + TChar ch = TChar(ptrModifyable[index]); + ch.Fold( TChar::EFoldDigits | TChar::EFoldSpaces); + } + + iFormattedNumber->Des().Zero(); + iFormattedNumberPtr.Set(KNullDesC); + + if ( iReverseFormattedNumber ) + iReverseFormattedNumber->Des().Zero(); + + iReverseFormattedNumberPtr.Set(KNullDesC); + iMatchedPatternIndex = ENoMatchedPattern; + } + +void CPNGNumberGrouping::doReadFormatInfoFromResourceFileL() + { + doClearGroupingItemsList(); + delete iRegExp; + iRegExp = NULL; + + RPointerArray parrGroupingPatternsList; + CleanupResetAndDestroyPushL(parrGroupingPatternsList); + + TInt maxExtraCharacters(0); + + RFs fs; + CleanupClosePushL(fs); + if(fs.Connect() == KErrNone) + { + RResourceFile resourceFile; + CleanupClosePushL(resourceFile); + + resourceFile.OpenL(fs, _L("z:\\resource\\numbergrouping.rsc")); + HBufC8* bufResource = resourceFile.AllocReadL(R_GROUPING_MAPPING); + + TResourceReader resourceReader; + resourceReader.SetBuffer(bufResource); + + TInt nLanguageCount = resourceReader.ReadInt8(); + TBool bLanguageMatches = EFalse; + + while(nLanguageCount-- || !bLanguageMatches) + { + TBool bLanguageMatches = (resourceReader.ReadInt8() == iLanguage); + + if(bLanguageMatches || ((nLanguageCount == -1) && !bLanguageMatches)) + { + TInt nGroupingSchemeCount = resourceReader.ReadInt8(); + + while(nGroupingSchemeCount--) + { + TInt thisMaxExtraCharacters(0); + ReadGroupingSchemeL( + resourceReader, parrGroupingPatternsList, thisMaxExtraCharacters ); + // take this new max extra characters if bigger + maxExtraCharacters = Max( maxExtraCharacters, thisMaxExtraCharacters ); + } + + break; // This breaks out because we take the first language that matches + + } // End of if on language/locale test + else // skip other locales + { + TInt nGroupingSchemeCount = resourceReader.ReadInt8(); + while(nGroupingSchemeCount--) + { + SkipGroupingSchemeL( resourceReader ); + } + } + } + + delete bufResource; + + resourceFile.Close(); + CleanupStack::Pop(); // resource file + } + + fs.Close(); + CleanupStack::Pop(); // file system + + iExtension->iMaxExtraCharacters = maxExtraCharacters; // Latch the high water mark of extra characters + + iRegExp = CRegularExpression::NewL(&parrGroupingPatternsList); + + CleanupStack::PopAndDestroy(&parrGroupingPatternsList); // patterns list + } + +void CPNGNumberGrouping::doNumberGroupingL() const + { + TInt lengthToGroup = LengthToGroup(); + + if ( lengthToGroup >= KMinimumLengthToGroup ) + { + + TInt matchedPattern = KErrNotFound; + TInt newMatchedPattern = KErrNotFound; + + // Search for matches in the RegExp object. It returns the next matching pattern + // However, even if there is a match, lengthToGroup may not be in the deployment + // length range between minDigits and MaxDigits, inclusive + do { + // Check for another matching pattern + newMatchedPattern = iRegExp->SearchFrom( newMatchedPattern+1, *iUnformattedNumber); + + if( newMatchedPattern != KErrNotFound) // Found a match, but it is OK? + { + + TInt minDigits = iGroupingItemsList[newMatchedPattern]->iMinNumberOfDigits; + TInt maxDigits = iGroupingItemsList[newMatchedPattern]->iMaxNumberOfDigits; + + // Fill in sensible values for min and max if not present + if(minDigits == -1) + minDigits = 0; + if(maxDigits == -1) + maxDigits = lengthToGroup; + + if ( minDigits <= lengthToGroup && lengthToGroup <= maxDigits ) + { + matchedPattern = newMatchedPattern; // accept this new pattern + break; + } + } + + } while ( newMatchedPattern != KErrNotFound ); + + // Actually go and do the grouping + if ( matchedPattern != KErrNotFound ) + { + doNumberGroupingForPatternL( matchedPattern, lengthToGroup ); + return; + } + + } + + // if we get to here, either the string was not matched to any of the patterns or the + // unformatted string is exactly the display length. In either case we call + // doNumberSquashing() which simply leaves the string as it is... + doNumberSquashing(); + + } + + +void CPNGNumberGrouping::doNumberGroupingForPatternL( TInt aMatchingPattern, TInt aLengthToGroup ) const + { + iMatchedPatternIndex = aMatchingPattern; + + TInt nLowPos = 0; + TInt nHighPos = 0; + + TPtr desUnformattedNumber = iUnformattedNumber->Des(); + TInt unformattedLength = iUnformattedNumber->Length(); + + __ASSERT_ALWAYS( aLengthToGroup <= unformattedLength , Panic(ENumberGroupingBadLengthToGroup) ); + + TPNGGroupingInfo* matchedPattern = iGroupingItemsList[iMatchedPatternIndex]; + TInt nAfterCount = matchedPattern->iAfterPositions.Count(); + TBool bBeforePosition = (matchedPattern->iBeforePosition.iPosition == -1)?0:1; + + // Test to see if the beforePosition can be used with the current text length. + // The following does not allow the before position to be used if it would result in an + // insertion right next to one from the AfterPositions. + // That is, tildas in the formatting string represent 1 or more characters. + // e.g. if the last afterPosition is 4 and the before position is 3, then a 7 digit + // number will not be able to have the before position used. + if( nAfterCount && + (unformattedLength - matchedPattern->iBeforePosition.iPosition) <= + matchedPattern->iAfterPositions[nAfterCount - 1].iPosition) + { + bBeforePosition = EFalse; + } + + TPtr ptrModifyable(iFormattedNumber->Des()); + + for(TInt i = 0; i < nAfterCount && nHighPos < aLengthToGroup ; ++i) + { + nHighPos = matchedPattern->iAfterPositions[i].iPosition; + if ( nHighPos >= aLengthToGroup ) + break; + + if(nHighPos < unformattedLength) + { + ptrModifyable.Append( desUnformattedNumber.Mid( nLowPos, nHighPos - nLowPos) ); + ptrModifyable.Append(matchedPattern->iAfterPositions[i].iSeparatorCharacter); + nLowPos = nHighPos; + } + } + + // Do not do "before end" formatting at all if there is any truncation + if ( aLengthToGroup < unformattedLength ) + { + TInt nBeforePosition = matchedPattern->iBeforePosition.iPosition; + + if(bBeforePosition && nBeforePosition < unformattedLength) + { + nHighPos = unformattedLength - nBeforePosition; + ptrModifyable.Append( desUnformattedNumber.Mid( nLowPos, nHighPos - nLowPos) ); + ptrModifyable.Append( matchedPattern->iBeforePosition.iSeparatorCharacter ); + nLowPos = nHighPos; + } + } + + nHighPos = unformattedLength; + ptrModifyable.Append( desUnformattedNumber.Mid( nLowPos, nHighPos - nLowPos) ); + + } + +void CPNGNumberGrouping::doNumberSquashing() const + { + __ASSERT_ALWAYS( !iFormattedNumber->Length(), Panic(ENumberGroupingFormattedNumberAlreadyExists) ); + + // just copy from one t'other... + TPtr ptrModifyable(iFormattedNumber->Des()); + ptrModifyable.Copy(*iUnformattedNumber); + iMatchedPatternIndex = ENoMatchedPattern; + } + +void CPNGNumberGrouping::doClearGroupingItemsList() + { + TInt nCount = iGroupingItemsList.Count(); + + for(TInt i = 0; i < nCount; ++i) + { + iGroupingItemsList[i]->iAfterPositions.Close(); + delete iGroupingItemsList[i]; + iGroupingItemsList[i] = NULL; + } + iGroupingItemsList.Close(); + } + +void CPNGNumberGrouping::ReadGroupingSchemeL( + TResourceReader& aResourceReader, + RPointerArray& aGroupingPatternsList, + TInt& aMaxExtraCharacters ) + { + CleanupResetAndDestroyPushL(aGroupingPatternsList); + TPNGGroupingInfo* groupingInfo = new (ELeave) TPNGGroupingInfo; + CleanupStack::PushL( groupingInfo ); + + // Read in all resource for this grouping scheme, perform checking and then analyze it + HBufC* initialDigits = aResourceReader.ReadHBufCL(); + __ASSERT_ALWAYS( initialDigits, Panic( ENumberGroupingNoInitialDigitsInResource ) ); + CleanupStack::PushL( initialDigits ); + + groupingInfo->iMinNumberOfDigits = aResourceReader.ReadInt8(); + groupingInfo->iMaxNumberOfDigits = aResourceReader.ReadInt8(); + __ASSERT_DEBUG( + ( groupingInfo->iMaxNumberOfDigits == -1) || + ( groupingInfo->iMinNumberOfDigits <= groupingInfo->iMaxNumberOfDigits ), + Panic( ENumberGroupingBadMinMaxDigitRangeInResource ) ); + + // Read in formatting Pattern + HBufC* formatPattern = aResourceReader.ReadHBufCL(); + + if ( formatPattern ) // Does not have to be there + { + CleanupStack::PushL( formatPattern ); + TInt formatLength = formatPattern->Length(); + if ( formatLength > 0 ) + { + // Obtain a wildcard version of the matching pattern in initialDigits. + // This is used to check the supplied formatPattern for comformance to initialDigits + HBufC* wildcardedMatchBuf = HBufC::NewLC( formatLength ); // Will not be longer than the search pattern + + TPtr wildcardedMatchPtr( wildcardedMatchBuf->Des() ); + // Get the example number using the latest search pattern only + GetWildcardVersionOfMatchStringL( *initialDigits, KNumberGroupingWildcard, wildcardedMatchPtr ); + + // Now parse the descriptor + TBool trailingPossible(EFalse); + ParseForAfterPositions( + *formatPattern, groupingInfo, wildcardedMatchPtr, aMaxExtraCharacters, trailingPossible ); + + // Now parse the descriptor from the end if needed + if ( trailingPossible ) + ParseForBeforePosition( *formatPattern, groupingInfo, aMaxExtraCharacters ); + + CleanupStack::PopAndDestroy( wildcardedMatchBuf ); + } + CleanupStack::PopAndDestroy( formatPattern ); + } // End of if on formatPattern.Length + + User::LeaveIfError( aGroupingPatternsList.Append( initialDigits ) ); + CleanupStack::Pop( initialDigits ); + + // Do not leave if the next one fails, but remove the last from the patterns list and then leave + // This is done in case someone TRAPs. Otherwise neither of these lists would be used and their + // mismatch would not be a problem + if ( TInt err = iGroupingItemsList.Append(groupingInfo) != KErrNone ) + { + // return value of Count will be at least 1, because we have just successfully gone through an Append + aGroupingPatternsList.Remove( aGroupingPatternsList.Count() - 1 ); + // ownership is now mine again... + delete initialDigits; + // Need to delete groupingInfo, and make sure it is no longer on the cleanupstack + CleanupStack::PopAndDestroy( groupingInfo ); + User::Leave(err); + } + else + { + CleanupStack::Pop( groupingInfo ); // Success. This object now not owned by the cleanupstack + } + + CleanupStack::Pop(&aGroupingPatternsList); + } + +void CPNGNumberGrouping::ParseForAfterPositions( + const TDesC& aFormatPattern, + TPNGGroupingInfo* aGroupingInfo, + const TDesC& aWildcardedMatchingPattern, + TInt& aMaxExtraCharacters, + TBool& trailingPossible ) const + { + TInt pos(0); // Keeps track of the position with which the next separator will be stored + TInt formatLength = aFormatPattern.Length(); + for (TInt index = 0; index < formatLength; index++ ) + { + // The format pattern is compared with the matching pattern. The matching pattern may be + // shorter than the format pattern, so by default a wildcard character is used. + TText ch = aFormatPattern[index]; + TText matchingChar(KNumberGroupingWildcard); // default to expect is the wildcard character + if ( pos < aWildcardedMatchingPattern.Length() ) // if still within the matching pattern + matchingChar = aWildcardedMatchingPattern[pos]; + if ( ch == matchingChar ) + pos++; // not a separator. index where the next "after" marker goes + else if ( ch == KNumberGroupingOneOrMoreCharactersToken ) + { + // finish looking for "afterPositions". But there may be a "before" position in the + // remainder, so set the flag + trailingPossible = ETrue; + break; + } + else + { + // Explicit prevention of any separator characters being valid phone numbers +#ifdef _DEBUG + if ( IsValidPhoneNumberCharacter( ch ) || ch == KNumberGroupingWildcard ) + { + RDebug::Print( + _L("NumberGrouping: Illegal character or format mismatch in resource: initialDigits pattern= <%S> formatPattern=<%S>"), + &aWildcardedMatchingPattern, &aFormatPattern ); + } +#endif + __ASSERT_DEBUG( !IsValidPhoneNumberCharacter( ch ), Panic( ENumberGroupingInvalidSeparatorCharacterInFormat ) ); + __ASSERT_DEBUG( ch != KNumberGroupingWildcard, Panic( ENumberGroupingMatchingPatternVersusFormatPatternMismatch ) ); + TPNGSeparator separator( pos, aFormatPattern[index]); + aGroupingInfo->iAfterPositions.Append(separator); + aMaxExtraCharacters++; + } + } + } + +void CPNGNumberGrouping::ParseForBeforePosition( + const TDesC& aFormatPattern, + TPNGGroupingInfo* aGroupingInfo, + TInt& aMaxExtraCharacters ) const + { + TInt pos=0; + TInt formatLength = aFormatPattern.Length(); + + for (TInt index = formatLength-1; index >=0; index-- ) + { + TText ch = aFormatPattern[index]; + if ( ch == KNumberGroupingWildcard ) + pos++; + else if ( ch == KNumberGroupingOneOrMoreCharactersToken ) + break; + else + { + // Explicit prevention of any separator characters being valid phone numbers +#ifdef _DEBUG + if ( IsValidPhoneNumberCharacter( ch ) ) + { + RDebug::Print( + _L("NumberGrouping: Illegal character in trailing part of format string in resource: formatPattern=<%S>"), + &aFormatPattern ); + } +#endif + __ASSERT_DEBUG( !IsValidPhoneNumberCharacter( ch ), + Panic( ENumberGroupingInvalidSeparatorCharacterInFormat ) ); + TPNGSeparator separator( pos, ch ); + aGroupingInfo->iBeforePosition = separator; + aMaxExtraCharacters++; + break; + } + } + } + + +void CPNGNumberGrouping::SkipGroupingSchemeL( TResourceReader& aResourceReader ) const + { + HBufC* tempBuf; + tempBuf = aResourceReader.ReadHBufCL(); + delete tempBuf; + aResourceReader.Advance(2); // min and max characters + tempBuf = aResourceReader.ReadHBufCL(); + delete tempBuf; + } + +void CPNGNumberGrouping::GetWildcardVersionOfMatchStringL( + const TDesC& aMatchString, + TText aWildcard, + TDes& aWildcardMatchString ) const + { + RPointerArray patternList; + CleanupClosePushL(patternList); + + // Make a copy of the input string + HBufC* matchString = aMatchString.AllocLC(); + + User::LeaveIfError( patternList.Append(matchString) );// takes ownership + CleanupStack::Pop( matchString ); + + CRegularExpression* regExp = CRegularExpression::NewLC(&patternList); + + // Only 1 pattern fed in. Access that pattern at index 0 + regExp->GetWildcardVersionOfPattern( 0 , aWildcard, aWildcardMatchString ); + + CleanupStack::PopAndDestroy(regExp); + + // Delete the patterns list + delete patternList[0]; + CleanupStack::PopAndDestroy(); + } + + +EXPORT_C TBool CPNGNumberGrouping::IsCharacterInsertedByNumberGrouping(TInt aPos) const + { + TInt insertedCharacters = Length() - UnFormattedLength(); + + if( insertedCharacters == 0 ) // no formatting was done + return EFalse; + else if ( aPos < ( insertedCharacters + LengthToGroup() ) ) + { + return !IsValidPhoneNumberCharacter( (*iFormattedNumber)[aPos] ); + } + else // aPos is pointing at or beyond index= LengthToGroup() + ; no formatting there + return EFalse; + } + + +TBool CPNGNumberGrouping::IsValidPhoneNumberCharacter( TText aCharacter ) const + { + if ( ((TChar)aCharacter).IsDigit() ) + return ETrue; + + // Check through the list of additional valid phone number characters + TInt numAdditionalChars = sizeof( KAdditionalPhoneNumberCharacters )/sizeof(TText); + + for (TInt index = 0; index < numAdditionalChars; index++) + { + if ( aCharacter == KAdditionalPhoneNumberCharacters[index] ) + return ETrue; + } + + return EFalse; + } + +EXPORT_C TBool CPNGNumberGrouping::IsChangedByGrouping() const + { + // The only way that grouping is effectively different is by making things longer + return ( Length() > UnFormattedLength() ); + } + +TInt CPNGNumberGrouping::LengthToGroup() const + { + + TPtrC ptr = iUnformattedNumber->Des(); + TInt lengthToGroup = ptr.Length(); + + // Find the first non-digit + for (TInt index = 0; index < ptr.Length(); index++) + { + TChar ch = TChar(ptr[index]); + ch.Fold(TChar::EFoldDigits); + if ( !( ch.IsDigit() ) ) + { + lengthToGroup = index; // only characters BEFORE the character at index are grouped + break; + } + } + + return lengthToGroup; + } + +// End of File