/*
* 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 <barsread.h>
#include <eikenv.h>
#include <centralrepository.h>
#include <NumberGroupingCRKeys.h>
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 <NumberGrouping.rsg>
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<CPNGNumberGrouping*>(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<CPNGNumberGrouping*>(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<TDesC> parrGroupingPatternsList;
CleanupClosePushL(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);
TInt nCount = parrGroupingPatternsList.Count();
for(TInt j = 0; j < nCount; ++j)
delete parrGroupingPatternsList[j];
parrGroupingPatternsList.Close();
CleanupStack::Pop(); // 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<TDesC>& aGroupingPatternsList,
TInt& aMaxExtraCharacters )
{
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
}
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<TDesC> 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() + <chars inserted>; 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