textandlocutils/numbergrouping/src/NumberGrouping.cpp
changeset 40 91ef7621b7fc
child 41 ea44a32a96bc
equal deleted inserted replaced
21:f2f7b3284356 40:91ef7621b7fc
       
     1 /*
       
     2 * Copyright (c) 2002-2008 Nokia Corporation and/or its subsidiary(-ies).
       
     3 * All rights reserved.
       
     4 * This component and the accompanying materials are made available
       
     5 * under the terms of the License "Eclipse Public License v1.0"
       
     6 * which accompanies this distribution, and is available
       
     7 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8 *
       
     9 * Initial Contributors:
       
    10 * Nokia Corporation - initial contribution.
       
    11 *
       
    12 * Contributors:
       
    13 *
       
    14 * Description: 
       
    15 *
       
    16 */
       
    17 
       
    18 #include <f32file.h>
       
    19 #include <barsc.h> 
       
    20 #include "NumberGrouping.h"
       
    21 #include "RegularExpression.h"
       
    22 #include "cleanuputil.h"
       
    23 
       
    24 #include <barsread.h>
       
    25 //#include <eikenv.h>
       
    26 #include <centralrepository.h>
       
    27 #include <NumberGroupingCRKeys.h>
       
    28 
       
    29 const TText KNumberGroupingWildcard('n');
       
    30 const TText KNumberGroupingOneOrMoreCharactersToken('~');
       
    31 
       
    32 // This constant represents all the valid whitespace that we can handle. Several
       
    33 // APIs give whitespace special significance as a formatting character, especially
       
    34 // those methods used to obtain text for formatting into fixed-width UI elements
       
    35 // where spaces are not to be rendered at the margins.
       
    36 const TText KNumberGroupingSpace(' ');
       
    37 
       
    38 const TInt KMinimumLengthToGroup = 1; // No grouping occurs if fewer than this in unformatted buffer
       
    39 
       
    40 #include <NumberGrouping.rsg>
       
    41 
       
    42 
       
    43 GLDEF_C void Panic(TNumberGroupingPanic aPanic)
       
    44     {
       
    45     _LIT(KPanicCat,"Number Grouping");
       
    46     User::Panic(KPanicCat, aPanic);
       
    47     }
       
    48 
       
    49 // Valid phone number characters apart from IsDigit() characters are listed here
       
    50 const TText KAdditionalPhoneNumberCharacters[] = {'+','#','*','p','w'};
       
    51 
       
    52 
       
    53 
       
    54 
       
    55 NONSHARABLE_CLASS(CPNGNumberGroupingExtension): public CBase
       
    56 	{
       
    57 public:
       
    58 	CPNGNumberGroupingExtension();
       
    59 	~CPNGNumberGroupingExtension();
       
    60 public:
       
    61     TInt        iMaxExtraCharacters; 
       
    62     TInt        iNumberGroupingCRValue;
       
    63     };
       
    64 
       
    65 CPNGNumberGroupingExtension::CPNGNumberGroupingExtension()
       
    66 	{
       
    67 	}
       
    68 
       
    69 CPNGNumberGroupingExtension::~CPNGNumberGroupingExtension()
       
    70 	{
       
    71 	}
       
    72 
       
    73 
       
    74 
       
    75 CPNGNumberGrouping::TPNGSeparator::TPNGSeparator()
       
    76     : iPosition(-1), iSeparatorCharacter(KNumberGroupingSpace)
       
    77     {
       
    78     }
       
    79 
       
    80 CPNGNumberGrouping::TPNGSeparator::TPNGSeparator( TInt aPosition, TText aSeparatorCharacter )
       
    81     : iPosition(aPosition), iSeparatorCharacter(aSeparatorCharacter)
       
    82     {
       
    83     }
       
    84 
       
    85 CPNGNumberGrouping::TPNGGroupingInfo::TPNGGroupingInfo()
       
    86     {
       
    87     }
       
    88 
       
    89 // CPNGNumberGrouping - grouping engine class
       
    90 CPNGNumberGrouping::CPNGNumberGrouping(TInt aMaxLength /* = 0 */, TBool aReversed /* = EFalse */) :
       
    91                                             iForceLanguage(ELangTest),
       
    92                                             iUnformattedNumberPtr(KNullDesC),
       
    93                                             iFormattedNumberPtr(KNullDesC),
       
    94                                             iReverseFormattedNumberPtr(KNullDesC),
       
    95                                             iSelectionPtr(KNullDesC),
       
    96                                             iLanguage(ELangTest),
       
    97                                             iMaxUnformattedLength(aMaxLength),
       
    98                                             iReversed(aReversed),
       
    99                                             iMatchedPatternIndex(ENoMatchedPattern)
       
   100     {
       
   101     }
       
   102 
       
   103 EXPORT_C CPNGNumberGrouping* CPNGNumberGrouping::NewL( TInt aMaxLength, TBool aReserved)
       
   104     {
       
   105     CPNGNumberGrouping* s = NewLC(aMaxLength, aReserved);
       
   106     CleanupStack::Pop();
       
   107     return s;
       
   108     }
       
   109 
       
   110 EXPORT_C CPNGNumberGrouping* CPNGNumberGrouping::NewLC( TInt aMaxLength, TBool aReserved)
       
   111     {
       
   112     CPNGNumberGrouping* s = new(ELeave)CPNGNumberGrouping( aMaxLength, aReserved);
       
   113     CleanupStack::PushL(s);
       
   114     s->ConstructL();
       
   115     return s;
       
   116     }
       
   117 
       
   118 void CPNGNumberGrouping::ConstructL()
       
   119     {
       
   120     iExtension = new (ELeave) CPNGNumberGroupingExtension(); 
       
   121     CRepository* repository = NULL;
       
   122     iExtension->iNumberGroupingCRValue = 0;
       
   123     TRAPD(ret, repository = CRepository::NewL(KCRUidNumberGrouping));
       
   124     if (ret == KErrNone)
       
   125         {
       
   126         ret = repository->Get(KNumberGrouping, iExtension->iNumberGroupingCRValue);
       
   127         }
       
   128     delete repository;
       
   129     
       
   130     // Read from resource first in order to obtain a value for iExtension->iMaxExtraCharacters
       
   131     iLanguage = doReadLanguageFromSharedData();
       
   132     doReadFormatInfoFromResourceFileL(); // This sets iExtension->iMaxExtraCharacters
       
   133 
       
   134     // Allocation of buffers.  Note that iMaxUnformattedLength must be retained as member
       
   135     // data because HBufCs may come back with more storage available than asked for.
       
   136     // The uncertainty in the actual length provided by the HBufCs means that although
       
   137     // they are asked to be different by iExtension->iMaxExtraCharacters, the difference between the
       
   138     // actual MaxLengths may be less than iMaxExtraCharcaters.
       
   139     // The approach decided upon is to retain iMaxUnformattedLength as member data and
       
   140     // use IT everywhere throughout this class's implementation, NEVER using
       
   141     // iUnformattedNumber->Des().MaxLength()
       
   142     iUnformattedNumber = HBufC::NewL(iMaxUnformattedLength);
       
   143     iFormattedNumber = HBufC::NewL(iMaxUnformattedLength + iExtension->iMaxExtraCharacters);
       
   144 
       
   145     // Create revesed buffer only if requested
       
   146     if ( iReversed )
       
   147         iReverseFormattedNumber = HBufC::NewL(iMaxUnformattedLength + iExtension->iMaxExtraCharacters);        
       
   148     }
       
   149 
       
   150 EXPORT_C CPNGNumberGrouping::~CPNGNumberGrouping()
       
   151     {
       
   152     doClearGroupingItemsList();
       
   153 
       
   154     delete iUnformattedNumber;
       
   155     delete iFormattedNumber;
       
   156     delete iReverseFormattedNumber;
       
   157     delete iRegExp;
       
   158     delete iExtension;
       
   159     }
       
   160 
       
   161 EXPORT_C TInt CPNGNumberGrouping::Insert(TInt aIndex, TText aChar)
       
   162     {
       
   163 
       
   164     if( aIndex >= 0 && aIndex <= iUnformattedNumber->Length())
       
   165         {
       
   166 
       
   167         if(iUnformattedNumber->Length() >= iMaxUnformattedLength)
       
   168             return KErrOverflow;
       
   169 
       
   170         TBuf<1> bufChar(1);
       
   171         bufChar[0] = aChar;
       
   172         TPtr ptrModifyable(iUnformattedNumber->Des());
       
   173         ptrModifyable.Insert(aIndex, bufChar);
       
   174 
       
   175         doClearFormattedNumbers();
       
   176 
       
   177         return KErrNone;
       
   178         }
       
   179 
       
   180     return KErrIndexOutOfRange;
       
   181     }
       
   182 
       
   183 EXPORT_C TInt CPNGNumberGrouping::Delete(TInt aIndex)
       
   184     {
       
   185     if(aIndex >= 0 && aIndex < iUnformattedNumber->Length())
       
   186         {
       
   187         TPtr ptrModifyable(iUnformattedNumber->Des());
       
   188         ptrModifyable.Delete(aIndex, KSingleCharacter);
       
   189 
       
   190         doClearFormattedNumbers();
       
   191 
       
   192         return KErrNone;
       
   193         }
       
   194 
       
   195     return KErrIndexOutOfRange;
       
   196     }
       
   197 
       
   198 EXPORT_C TInt CPNGNumberGrouping::Append(TText aChar)
       
   199     {
       
   200     if(iUnformattedNumber->Length() >= iMaxUnformattedLength)
       
   201         return KErrOverflow;
       
   202 
       
   203     TBuf<1> bufChar(1);
       
   204     bufChar[0] = aChar;
       
   205     TPtr ptrModifyable(iUnformattedNumber->Des());
       
   206     ptrModifyable.Append(bufChar);
       
   207 
       
   208     doClearFormattedNumbers();
       
   209 
       
   210     return KErrNone;
       
   211     }
       
   212 
       
   213 EXPORT_C TInt CPNGNumberGrouping::Set(const TDesC& aNumber)
       
   214     {
       
   215     if( aNumber.Length() > iMaxUnformattedLength )
       
   216         return KErrOverflow;
       
   217 
       
   218     TPtr ptrModifyable( iUnformattedNumber->Des() );
       
   219     ptrModifyable.Copy( aNumber );
       
   220 
       
   221     doClearFormattedNumbers();
       
   222 
       
   223     return KErrNone;
       
   224     }
       
   225 
       
   226 EXPORT_C TInt CPNGNumberGrouping::Length() const
       
   227     {
       
   228     if(!iFormattedNumber->Length()) // This test is used as the trigger to reformat
       
   229         FormattedNumber();
       
   230 
       
   231     return iFormattedNumber->Length();
       
   232     }
       
   233 
       
   234 EXPORT_C TInt CPNGNumberGrouping::UnFormattedLength() const
       
   235     {
       
   236     return iUnformattedNumber->Length();
       
   237     }
       
   238 
       
   239 EXPORT_C TInt CPNGNumberGrouping::MaxDisplayLength() const
       
   240     {
       
   241     // Despite its name, this method returns the max length of the UNFORMATTED buffer
       
   242     // This must not be implemented to return the actual length available in
       
   243     // iUnformattedBuffer.
       
   244     return iMaxUnformattedLength;
       
   245     }
       
   246 
       
   247 EXPORT_C TBool CPNGNumberGrouping::IsSpace(TInt aPos) const
       
   248     {
       
   249     // Very tricky semantics for this.  Must be a space inserted by the formatting
       
   250     if ( iFormattedNumber->Length() > aPos &&
       
   251          iFormattedNumber->operator[](aPos) == KNumberGroupingSpace &&
       
   252          // Check also that is is less than the length to group + inserted characters
       
   253          aPos < ( LengthToGroup() + (iFormattedNumber->Length() - iUnformattedNumber->Length()) ) )
       
   254         return ETrue;
       
   255     else
       
   256         return EFalse;
       
   257     }
       
   258 
       
   259 EXPORT_C const TDesC& CPNGNumberGrouping::FormattedNumber(TInt aFrom, TInt aTo) const
       
   260     {
       
   261     if(iUnformattedNumber->Length() != 0 &&
       
   262         aFrom >= 0 &&
       
   263         aFrom <= aTo )
       
   264         {
       
   265         FormattedNumber();
       
   266         iFormattedNumberPtr.Set(KNullDesC);
       
   267 
       
   268         TInt length = iFormattedNumber->Length();
       
   269         if(aTo < length + 1)
       
   270             {
       
   271             TInt length = iFormattedNumber->Length();
       
   272             if ( iExtension->iNumberGroupingCRValue )
       
   273             	{
       
   274 				// Advance to the next non-space
       
   275 				while( (aFrom < length ) && (*iFormattedNumber)[aFrom] == KNumberGroupingSpace )
       
   276 					{
       
   277 					++aFrom;
       
   278 					}
       
   279 	
       
   280 				// Retreat to the last non-space
       
   281 				while( (aTo > 0) && (*iFormattedNumber)[aTo] == KNumberGroupingSpace )
       
   282 					{
       
   283 					--aTo;
       
   284 					}
       
   285             	}
       
   286 
       
   287             // Does fetching the descriptor still make sense?
       
   288             if ( (0 <= aFrom) && (aFrom <= aTo) && (aTo < length) )
       
   289                 iFormattedNumberPtr.Set( iFormattedNumber->Mid( aFrom, aTo-aFrom+1 ) );
       
   290             }
       
   291         }
       
   292     else
       
   293         {
       
   294         if(iFormattedNumber->Length())
       
   295             {
       
   296             CPNGNumberGrouping* pThis = const_cast<CPNGNumberGrouping*>(this);
       
   297             pThis->doClearFormattedNumbers();
       
   298             }
       
   299 
       
   300         iFormattedNumberPtr.Set(KNullDesC);
       
   301         }
       
   302 
       
   303     return iFormattedNumberPtr;
       
   304     }
       
   305 
       
   306 EXPORT_C const TDesC& CPNGNumberGrouping::FormattedNumber() const
       
   307     {
       
   308     if( !iFormattedNumber->Length() )
       
   309         {
       
   310         TInt err = KErrNone;
       
   311 
       
   312         CPNGNumberGrouping* pThis = const_cast<CPNGNumberGrouping*>(this);
       
   313 
       
   314         if( LengthToGroup() < KMinimumLengthToGroup || !iExtension->iNumberGroupingCRValue )
       
   315             {
       
   316             // This is now just a short cut, as doNumberGroupingL handles premature truncation of
       
   317             // formatting. But this avoids all the language checking
       
   318             doNumberSquashing();  // copies the unformatted number straight into the formatted number
       
   319             }
       
   320         else
       
   321             {
       
   322             TLanguage eLanguage;
       
   323             if(iForceLanguage != ELangTest)
       
   324                 eLanguage = iForceLanguage;
       
   325             else
       
   326                 eLanguage = doReadLanguageFromSharedData();
       
   327 
       
   328             if(eLanguage != iLanguage)
       
   329                 {
       
   330                 iLanguage = eLanguage;
       
   331 
       
   332                 TRAP(err, pThis->doReadFormatInfoFromResourceFileL());
       
   333                 if(err != KErrNone)
       
   334                     {
       
   335                     iFormattedNumberPtr.Set(KNullDesC);
       
   336                     return iFormattedNumberPtr;
       
   337                     }
       
   338                 }
       
   339 
       
   340             TRAP(err, doNumberGroupingL());
       
   341             }
       
   342 
       
   343         if(err != KErrNone)
       
   344             pThis->doClearFormattedNumbers();
       
   345         else
       
   346             iFormattedNumberPtr.Set(iFormattedNumber->Ptr(), iFormattedNumber->Length());
       
   347         }
       
   348 
       
   349     return iFormattedNumberPtr;
       
   350     }
       
   351 
       
   352 EXPORT_C const TDesC& CPNGNumberGrouping::ReverseFormattedNumber(TInt aFrom, TInt aTo) const
       
   353     {
       
   354     if ( iReversed )
       
   355         {
       
   356         if(iUnformattedNumber->Length() != 0 &&
       
   357             aFrom >= 0 &&
       
   358             aFrom <= aTo)
       
   359             {
       
   360             ReverseFormattedNumber();
       
   361 
       
   362             iReverseFormattedNumberPtr.Set(KNullDesC);
       
   363 
       
   364             TInt length = iReverseFormattedNumber->Length();
       
   365             if( aTo < length + 1 )
       
   366                 {
       
   367                 // Advance to the next non-space
       
   368                 if( iExtension->iNumberGroupingCRValue )
       
   369                 	{
       
   370 					while( (aFrom < length ) && (*iReverseFormattedNumber)[aFrom] == KNumberGroupingSpace )
       
   371 						{
       
   372 						++aFrom;
       
   373 						}
       
   374 	
       
   375 					// Retreat to the last non-space
       
   376 					while( (aTo > 0) && (*iReverseFormattedNumber)[aTo] == KNumberGroupingSpace )
       
   377 						{
       
   378 						--aTo;
       
   379 						}
       
   380                 	}
       
   381 
       
   382                 // Does fetching the descriptor still make sense?
       
   383                 if ( (0 <= aFrom) && (aFrom <= aTo) && (aTo < length) )
       
   384                     iReverseFormattedNumberPtr.Set(
       
   385                         iReverseFormattedNumber->Mid( aFrom, aTo-aFrom+1) );
       
   386                 }
       
   387             }
       
   388         else
       
   389             iReverseFormattedNumberPtr.Set(KNullDesC);
       
   390         }
       
   391 
       
   392     return iReverseFormattedNumberPtr; // Zero initialized at construction
       
   393     }
       
   394 
       
   395 EXPORT_C const TDesC& CPNGNumberGrouping::ReverseFormattedNumber() const
       
   396     {
       
   397     if( iReverseFormattedNumber && !iReverseFormattedNumber->Length())
       
   398         {
       
   399         if(!iFormattedNumber->Length())
       
   400             FormattedNumber();
       
   401 
       
   402         TInt nLength = iFormattedNumber->Length();
       
   403 
       
   404         TPtr ptrModifyable(iReverseFormattedNumber->Des());
       
   405         TBuf<1> bufChar(1);
       
   406 
       
   407         for(TInt i = nLength; i > 0; --i)
       
   408             {
       
   409             TText cChar = (*iFormattedNumber)[i-1];
       
   410             bufChar[0] = cChar;
       
   411             ptrModifyable.Insert(nLength - i, bufChar);
       
   412             }
       
   413 
       
   414         iReverseFormattedNumberPtr.Set(iReverseFormattedNumber->Ptr(), nLength);
       
   415         }
       
   416 
       
   417     return iReverseFormattedNumberPtr;
       
   418     }
       
   419 
       
   420 EXPORT_C const TDesC& CPNGNumberGrouping::Selection(TInt aFrom, TInt aTo) const
       
   421     {
       
   422     if(aFrom < iUnformattedNumber->Length())
       
   423         {
       
   424         TPtr ptrUnformatted = iUnformattedNumber->Des();
       
   425         iSelectionPtr.Set(&(ptrUnformatted[aFrom]), aTo - aFrom);
       
   426         }
       
   427     else
       
   428         iSelectionPtr.Set(KNullDesC);
       
   429 
       
   430     return iSelectionPtr;
       
   431     }
       
   432 
       
   433 EXPORT_C const TDesC&   CPNGNumberGrouping::UnFormattedNumber(TInt aFrom, TInt aTo) const
       
   434     {
       
   435     if (iUnformattedNumber && aFrom >= 0 && aFrom <= aTo && aTo < iUnformattedNumber->Length())
       
   436         {
       
   437         iUnformattedNumberPtr.Set(&((*iUnformattedNumber)[aFrom]), aTo - aFrom + 1);
       
   438         }
       
   439     else
       
   440         {
       
   441         iUnformattedNumberPtr.Set(KNullDesC);
       
   442         }
       
   443     return iUnformattedNumberPtr;
       
   444     }
       
   445 
       
   446 EXPORT_C const TDesC& CPNGNumberGrouping::UnFormattedNumber() const
       
   447     {
       
   448     return UnFormattedNumber(0, iUnformattedNumber->Length() - 1);
       
   449     }
       
   450 
       
   451 TLanguage CPNGNumberGrouping::doReadLanguageFromSharedData() const
       
   452     {    
       
   453     if (iExtension->iNumberGroupingCRValue)
       
   454         {
       
   455         return ELangAmerican;
       
   456         }
       
   457     else
       
   458         {
       
   459         return ELangTest;
       
   460         }
       
   461     }
       
   462 
       
   463 void CPNGNumberGrouping::doClearFormattedNumbers()
       
   464     {
       
   465     TPtr ptrModifyable( iUnformattedNumber->Des() );
       
   466 
       
   467     for (TInt index = 0; index < ptrModifyable.Length(); index++)
       
   468         {
       
   469         TChar ch = TChar(ptrModifyable[index]);
       
   470         ch.Fold( TChar::EFoldDigits | TChar::EFoldSpaces);
       
   471         }
       
   472     
       
   473     iFormattedNumber->Des().Zero();
       
   474     iFormattedNumberPtr.Set(KNullDesC);
       
   475 
       
   476     if ( iReverseFormattedNumber )
       
   477         iReverseFormattedNumber->Des().Zero();
       
   478 
       
   479     iReverseFormattedNumberPtr.Set(KNullDesC);
       
   480     iMatchedPatternIndex = ENoMatchedPattern;
       
   481     }
       
   482 
       
   483 void CPNGNumberGrouping::doReadFormatInfoFromResourceFileL()
       
   484     {
       
   485     doClearGroupingItemsList();
       
   486     delete iRegExp;
       
   487     iRegExp = NULL;
       
   488 
       
   489     RPointerArray<TDesC> parrGroupingPatternsList;
       
   490     CleanupResetAndDestroyPushL(parrGroupingPatternsList);
       
   491 
       
   492     TInt maxExtraCharacters(0);
       
   493 
       
   494     RFs fs;
       
   495     CleanupClosePushL(fs);
       
   496     if(fs.Connect() == KErrNone)
       
   497         {
       
   498         RResourceFile resourceFile;
       
   499         CleanupClosePushL(resourceFile);
       
   500 
       
   501         resourceFile.OpenL(fs, _L("z:\\resource\\numbergrouping.rsc"));
       
   502         HBufC8* bufResource = resourceFile.AllocReadL(R_GROUPING_MAPPING);
       
   503 
       
   504         TResourceReader resourceReader;
       
   505         resourceReader.SetBuffer(bufResource);
       
   506 
       
   507         TInt    nLanguageCount = resourceReader.ReadInt8();
       
   508         TBool   bLanguageMatches = EFalse;
       
   509 
       
   510         while(nLanguageCount-- || !bLanguageMatches)
       
   511             {
       
   512             TBool bLanguageMatches = (resourceReader.ReadInt8() == iLanguage);
       
   513 
       
   514             if(bLanguageMatches || ((nLanguageCount == -1) && !bLanguageMatches))
       
   515                 {
       
   516                 TInt nGroupingSchemeCount = resourceReader.ReadInt8();
       
   517 
       
   518                 while(nGroupingSchemeCount--)
       
   519                     {
       
   520                     TInt thisMaxExtraCharacters(0);
       
   521                     ReadGroupingSchemeL(
       
   522                         resourceReader, parrGroupingPatternsList, thisMaxExtraCharacters );
       
   523                     // take this new max extra characters if bigger
       
   524                     maxExtraCharacters = Max( maxExtraCharacters, thisMaxExtraCharacters );
       
   525                     }
       
   526 
       
   527                 break; // This breaks out because we take the first language that matches
       
   528 
       
   529                 } // End of if on language/locale test
       
   530             else  // skip other locales
       
   531                 {
       
   532                 TInt nGroupingSchemeCount = resourceReader.ReadInt8();
       
   533                 while(nGroupingSchemeCount--)
       
   534                     {
       
   535                     SkipGroupingSchemeL( resourceReader );
       
   536                     }
       
   537                 }
       
   538             }
       
   539 
       
   540         delete bufResource;
       
   541 
       
   542         resourceFile.Close();
       
   543         CleanupStack::Pop();  // resource file
       
   544         }
       
   545 
       
   546     fs.Close();
       
   547     CleanupStack::Pop();  // file system
       
   548 
       
   549     iExtension->iMaxExtraCharacters = maxExtraCharacters; // Latch the high water mark of extra characters
       
   550 
       
   551     iRegExp = CRegularExpression::NewL(&parrGroupingPatternsList);
       
   552 
       
   553     CleanupStack::PopAndDestroy(&parrGroupingPatternsList);  // patterns list
       
   554     }
       
   555 
       
   556 void CPNGNumberGrouping::doNumberGroupingL() const
       
   557     {
       
   558     TInt lengthToGroup = LengthToGroup();
       
   559 
       
   560     if ( lengthToGroup >= KMinimumLengthToGroup )
       
   561         {
       
   562 
       
   563         TInt matchedPattern = KErrNotFound;
       
   564         TInt newMatchedPattern = KErrNotFound;
       
   565 
       
   566         // Search for matches in the RegExp object. It returns the next matching pattern
       
   567         // However, even if there is a match, lengthToGroup may not be in the deployment
       
   568         // length range between minDigits and MaxDigits, inclusive
       
   569         do  {
       
   570             // Check for another matching pattern
       
   571             newMatchedPattern = iRegExp->SearchFrom( newMatchedPattern+1, *iUnformattedNumber);
       
   572 
       
   573             if( newMatchedPattern != KErrNotFound) // Found a match, but it is OK?
       
   574                 {
       
   575 
       
   576                 TInt minDigits = iGroupingItemsList[newMatchedPattern]->iMinNumberOfDigits;
       
   577                 TInt maxDigits = iGroupingItemsList[newMatchedPattern]->iMaxNumberOfDigits;
       
   578 
       
   579                 // Fill in sensible values for min and max if not present
       
   580                 if(minDigits == -1)
       
   581                     minDigits = 0;
       
   582                 if(maxDigits == -1)
       
   583                     maxDigits = lengthToGroup;
       
   584 
       
   585                 if ( minDigits <= lengthToGroup && lengthToGroup <= maxDigits )
       
   586                     {
       
   587                     matchedPattern = newMatchedPattern; // accept this new pattern
       
   588                     break;
       
   589                     }
       
   590                 }
       
   591 
       
   592             } while ( newMatchedPattern != KErrNotFound  );
       
   593 
       
   594         // Actually go and do the grouping
       
   595         if ( matchedPattern != KErrNotFound )
       
   596             {
       
   597             doNumberGroupingForPatternL( matchedPattern, lengthToGroup );
       
   598             return;
       
   599             }
       
   600 
       
   601         }
       
   602 
       
   603     // if we get to here, either the string was not matched to any of the patterns or the
       
   604     // unformatted string is exactly the display length.  In either case we call
       
   605     // doNumberSquashing() which simply leaves the string as it is...
       
   606     doNumberSquashing();
       
   607 
       
   608     }
       
   609 
       
   610 
       
   611 void CPNGNumberGrouping::doNumberGroupingForPatternL( TInt aMatchingPattern, TInt aLengthToGroup ) const
       
   612     {
       
   613     iMatchedPatternIndex = aMatchingPattern;
       
   614 
       
   615     TInt nLowPos = 0;
       
   616     TInt nHighPos = 0;
       
   617 
       
   618     TPtr desUnformattedNumber = iUnformattedNumber->Des();
       
   619     TInt unformattedLength = iUnformattedNumber->Length();
       
   620 
       
   621     __ASSERT_ALWAYS( aLengthToGroup <= unformattedLength , Panic(ENumberGroupingBadLengthToGroup) );
       
   622 
       
   623     TPNGGroupingInfo* matchedPattern = iGroupingItemsList[iMatchedPatternIndex];
       
   624     TInt nAfterCount = matchedPattern->iAfterPositions.Count();
       
   625     TBool bBeforePosition = (matchedPattern->iBeforePosition.iPosition == -1)?0:1;
       
   626 
       
   627     // Test to see if the beforePosition can be used with the current text length.
       
   628     // The following does not allow the before position to be used if it would result in an
       
   629     // insertion right next to one from the AfterPositions.
       
   630     // That is, tildas in the formatting string represent 1 or more characters.
       
   631     // e.g. if the last afterPosition is 4 and the before position is 3, then a 7 digit
       
   632     // number will not be able to have the before position used.
       
   633     if( nAfterCount &&
       
   634         (unformattedLength - matchedPattern->iBeforePosition.iPosition) <=
       
   635         matchedPattern->iAfterPositions[nAfterCount - 1].iPosition)
       
   636         {
       
   637         bBeforePosition = EFalse;
       
   638         }
       
   639 
       
   640     TPtr ptrModifyable(iFormattedNumber->Des());
       
   641 
       
   642     for(TInt i  = 0; i < nAfterCount && nHighPos < aLengthToGroup ; ++i)
       
   643         {
       
   644         nHighPos = matchedPattern->iAfterPositions[i].iPosition;
       
   645         if ( nHighPos >= aLengthToGroup )
       
   646             break;
       
   647 
       
   648         if(nHighPos < unformattedLength)
       
   649             {
       
   650             ptrModifyable.Append( desUnformattedNumber.Mid( nLowPos, nHighPos - nLowPos) );
       
   651             ptrModifyable.Append(matchedPattern->iAfterPositions[i].iSeparatorCharacter);
       
   652             nLowPos = nHighPos;
       
   653             }
       
   654         }
       
   655 
       
   656     // Do not do "before end" formatting at all if there is any truncation
       
   657     if ( aLengthToGroup < unformattedLength )
       
   658         {
       
   659         TInt nBeforePosition = matchedPattern->iBeforePosition.iPosition;
       
   660 
       
   661         if(bBeforePosition && nBeforePosition < unformattedLength)
       
   662             {
       
   663             nHighPos = unformattedLength - nBeforePosition;
       
   664             ptrModifyable.Append( desUnformattedNumber.Mid( nLowPos, nHighPos - nLowPos) );
       
   665             ptrModifyable.Append( matchedPattern->iBeforePosition.iSeparatorCharacter );
       
   666             nLowPos = nHighPos;
       
   667             }
       
   668         }
       
   669 
       
   670     nHighPos = unformattedLength;
       
   671     ptrModifyable.Append( desUnformattedNumber.Mid( nLowPos, nHighPos - nLowPos) );
       
   672 
       
   673     }
       
   674 
       
   675 void CPNGNumberGrouping::doNumberSquashing() const
       
   676     {
       
   677     __ASSERT_ALWAYS( !iFormattedNumber->Length(), Panic(ENumberGroupingFormattedNumberAlreadyExists) );
       
   678 
       
   679     // just copy from one t'other...
       
   680     TPtr ptrModifyable(iFormattedNumber->Des());
       
   681     ptrModifyable.Copy(*iUnformattedNumber);
       
   682     iMatchedPatternIndex = ENoMatchedPattern;
       
   683     }
       
   684 
       
   685 void CPNGNumberGrouping::doClearGroupingItemsList()
       
   686     {
       
   687     TInt nCount = iGroupingItemsList.Count();
       
   688 
       
   689     for(TInt i = 0; i < nCount; ++i)
       
   690         {
       
   691         iGroupingItemsList[i]->iAfterPositions.Close();
       
   692         delete iGroupingItemsList[i];
       
   693         iGroupingItemsList[i] = NULL;
       
   694         }
       
   695     iGroupingItemsList.Close();
       
   696     }
       
   697 
       
   698 void CPNGNumberGrouping::ReadGroupingSchemeL(
       
   699     TResourceReader& aResourceReader,
       
   700     RPointerArray<TDesC>& aGroupingPatternsList,
       
   701     TInt& aMaxExtraCharacters )
       
   702     {
       
   703     CleanupResetAndDestroyPushL(aGroupingPatternsList);
       
   704     TPNGGroupingInfo* groupingInfo = new (ELeave) TPNGGroupingInfo;
       
   705     CleanupStack::PushL( groupingInfo );
       
   706 
       
   707     // Read in all resource for this grouping scheme, perform checking and then analyze it
       
   708     HBufC* initialDigits = aResourceReader.ReadHBufCL();
       
   709     __ASSERT_ALWAYS( initialDigits, Panic( ENumberGroupingNoInitialDigitsInResource ) );
       
   710     CleanupStack::PushL( initialDigits );
       
   711 
       
   712     groupingInfo->iMinNumberOfDigits = aResourceReader.ReadInt8();
       
   713     groupingInfo->iMaxNumberOfDigits = aResourceReader.ReadInt8();
       
   714     __ASSERT_DEBUG(
       
   715         ( groupingInfo->iMaxNumberOfDigits == -1) ||
       
   716         ( groupingInfo->iMinNumberOfDigits <= groupingInfo->iMaxNumberOfDigits ),
       
   717         Panic( ENumberGroupingBadMinMaxDigitRangeInResource ) );
       
   718 
       
   719     // Read in formatting Pattern
       
   720     HBufC* formatPattern = aResourceReader.ReadHBufCL();
       
   721 
       
   722     if ( formatPattern ) // Does not have to be there
       
   723         {
       
   724         CleanupStack::PushL( formatPattern );
       
   725         TInt formatLength = formatPattern->Length();
       
   726         if ( formatLength > 0 )
       
   727             {
       
   728             // Obtain a wildcard version of the matching pattern in initialDigits.
       
   729             // This is used to check the supplied formatPattern for comformance to initialDigits
       
   730             HBufC* wildcardedMatchBuf = HBufC::NewLC( formatLength ); // Will not be longer than the search pattern
       
   731 
       
   732             TPtr wildcardedMatchPtr( wildcardedMatchBuf->Des() );
       
   733             // Get the example number using the latest search pattern only
       
   734             GetWildcardVersionOfMatchStringL( *initialDigits, KNumberGroupingWildcard, wildcardedMatchPtr );
       
   735 
       
   736             // Now parse the descriptor
       
   737             TBool trailingPossible(EFalse);
       
   738             ParseForAfterPositions(
       
   739                 *formatPattern, groupingInfo, wildcardedMatchPtr, aMaxExtraCharacters, trailingPossible );
       
   740 
       
   741             // Now parse the descriptor from the end if needed
       
   742             if ( trailingPossible )
       
   743                 ParseForBeforePosition( *formatPattern, groupingInfo, aMaxExtraCharacters );
       
   744 
       
   745             CleanupStack::PopAndDestroy( wildcardedMatchBuf );
       
   746             }
       
   747         CleanupStack::PopAndDestroy( formatPattern );
       
   748         } // End of if on formatPattern.Length
       
   749 
       
   750     User::LeaveIfError( aGroupingPatternsList.Append( initialDigits ) );
       
   751     CleanupStack::Pop( initialDigits );
       
   752 
       
   753     // Do not leave if the next one fails, but remove the last from the patterns list and then leave
       
   754     // This is done in case someone TRAPs. Otherwise neither of these lists would be used and their
       
   755     // mismatch would not be a problem
       
   756     if ( TInt err = iGroupingItemsList.Append(groupingInfo) != KErrNone )
       
   757         {
       
   758         // return value of Count will be at least 1, because we have just successfully gone through an Append
       
   759         aGroupingPatternsList.Remove( aGroupingPatternsList.Count() - 1 );
       
   760         // ownership is now mine again...
       
   761         delete initialDigits;
       
   762         // Need to delete groupingInfo, and make sure it is no longer on the cleanupstack
       
   763         CleanupStack::PopAndDestroy( groupingInfo );
       
   764         User::Leave(err);
       
   765         }
       
   766     else
       
   767         {
       
   768         CleanupStack::Pop( groupingInfo ); // Success. This object now not owned by the cleanupstack
       
   769         }
       
   770     
       
   771     CleanupStack::Pop(&aGroupingPatternsList);
       
   772     }
       
   773 
       
   774 void CPNGNumberGrouping::ParseForAfterPositions(
       
   775     const TDesC& aFormatPattern,
       
   776     TPNGGroupingInfo* aGroupingInfo,
       
   777     const TDesC& aWildcardedMatchingPattern,
       
   778     TInt& aMaxExtraCharacters,
       
   779     TBool& trailingPossible ) const
       
   780     {
       
   781     TInt pos(0); // Keeps track of the position with which the next separator will be stored
       
   782     TInt formatLength = aFormatPattern.Length();
       
   783     for (TInt index = 0; index < formatLength; index++ )
       
   784         {
       
   785         // The format pattern is compared with the matching pattern.  The matching pattern may be
       
   786         // shorter than the format pattern, so by default a wildcard character is used.
       
   787         TText ch = aFormatPattern[index];
       
   788         TText matchingChar(KNumberGroupingWildcard); // default to expect is the wildcard character
       
   789         if ( pos < aWildcardedMatchingPattern.Length() ) // if still within the matching pattern
       
   790             matchingChar = aWildcardedMatchingPattern[pos];
       
   791         if ( ch == matchingChar )
       
   792             pos++; // not a separator. index where the next "after" marker goes
       
   793         else if ( ch == KNumberGroupingOneOrMoreCharactersToken )
       
   794             {
       
   795             // finish looking for "afterPositions". But there may be a "before" position in the
       
   796             // remainder, so set the flag
       
   797             trailingPossible = ETrue;
       
   798             break;
       
   799             }
       
   800         else
       
   801             {
       
   802             // Explicit prevention of any separator characters being valid phone numbers
       
   803 #ifdef _DEBUG
       
   804             if ( IsValidPhoneNumberCharacter( ch ) || ch == KNumberGroupingWildcard )
       
   805                 {
       
   806                 RDebug::Print(
       
   807                     _L("NumberGrouping: Illegal character or format mismatch in resource: initialDigits pattern= <%S> formatPattern=<%S>"),
       
   808                     &aWildcardedMatchingPattern, &aFormatPattern );
       
   809                 }
       
   810 #endif
       
   811             __ASSERT_DEBUG( !IsValidPhoneNumberCharacter( ch ), Panic( ENumberGroupingInvalidSeparatorCharacterInFormat ) );
       
   812             __ASSERT_DEBUG( ch != KNumberGroupingWildcard, Panic( ENumberGroupingMatchingPatternVersusFormatPatternMismatch ) );
       
   813             TPNGSeparator separator( pos, aFormatPattern[index]);
       
   814             aGroupingInfo->iAfterPositions.Append(separator);
       
   815             aMaxExtraCharacters++;
       
   816             }
       
   817         }
       
   818     }
       
   819 
       
   820 void CPNGNumberGrouping::ParseForBeforePosition(
       
   821     const TDesC& aFormatPattern,
       
   822     TPNGGroupingInfo* aGroupingInfo,
       
   823     TInt& aMaxExtraCharacters ) const
       
   824     {
       
   825     TInt pos=0;
       
   826     TInt formatLength = aFormatPattern.Length();
       
   827 
       
   828     for (TInt index = formatLength-1; index >=0; index-- )
       
   829         {
       
   830         TText ch = aFormatPattern[index];
       
   831         if ( ch == KNumberGroupingWildcard )
       
   832             pos++;
       
   833         else if ( ch == KNumberGroupingOneOrMoreCharactersToken )
       
   834             break;
       
   835         else
       
   836             {
       
   837             // Explicit prevention of any separator characters being valid phone numbers
       
   838 #ifdef _DEBUG
       
   839             if ( IsValidPhoneNumberCharacter( ch ) )
       
   840                 {
       
   841                 RDebug::Print(
       
   842                     _L("NumberGrouping: Illegal character in trailing part of format string in resource: formatPattern=<%S>"),
       
   843                     &aFormatPattern );
       
   844                 }
       
   845 #endif
       
   846             __ASSERT_DEBUG( !IsValidPhoneNumberCharacter( ch ),
       
   847                 Panic( ENumberGroupingInvalidSeparatorCharacterInFormat ) );
       
   848             TPNGSeparator separator( pos, ch );
       
   849             aGroupingInfo->iBeforePosition = separator;
       
   850             aMaxExtraCharacters++;
       
   851             break;
       
   852             }
       
   853         }
       
   854     }
       
   855 
       
   856 
       
   857 void CPNGNumberGrouping::SkipGroupingSchemeL( TResourceReader& aResourceReader ) const
       
   858     {
       
   859     HBufC* tempBuf;
       
   860     tempBuf = aResourceReader.ReadHBufCL();
       
   861     delete tempBuf;
       
   862     aResourceReader.Advance(2); // min and max characters
       
   863     tempBuf = aResourceReader.ReadHBufCL();
       
   864     delete tempBuf;
       
   865     }
       
   866 
       
   867 void CPNGNumberGrouping::GetWildcardVersionOfMatchStringL(
       
   868     const TDesC& aMatchString,
       
   869     TText aWildcard,
       
   870     TDes& aWildcardMatchString ) const
       
   871     {
       
   872     RPointerArray<TDesC> patternList;
       
   873     CleanupClosePushL(patternList);
       
   874 
       
   875     // Make a copy of the input string
       
   876     HBufC* matchString = aMatchString.AllocLC();
       
   877 
       
   878     User::LeaveIfError( patternList.Append(matchString) );// takes ownership
       
   879     CleanupStack::Pop( matchString );
       
   880 
       
   881     CRegularExpression* regExp = CRegularExpression::NewLC(&patternList);
       
   882 
       
   883     // Only 1 pattern fed in.  Access that pattern at index 0
       
   884     regExp->GetWildcardVersionOfPattern( 0 , aWildcard, aWildcardMatchString );
       
   885 
       
   886     CleanupStack::PopAndDestroy(regExp);
       
   887 
       
   888     // Delete the patterns list
       
   889     delete patternList[0];
       
   890     CleanupStack::PopAndDestroy();
       
   891     }
       
   892 
       
   893 
       
   894 EXPORT_C TBool CPNGNumberGrouping::IsCharacterInsertedByNumberGrouping(TInt aPos) const
       
   895     {
       
   896     TInt insertedCharacters = Length() - UnFormattedLength();
       
   897 
       
   898     if( insertedCharacters == 0 ) // no formatting was done
       
   899         return EFalse;
       
   900     else if ( aPos < ( insertedCharacters + LengthToGroup() ) )
       
   901         {
       
   902         return !IsValidPhoneNumberCharacter( (*iFormattedNumber)[aPos] );
       
   903         }
       
   904     else // aPos is pointing at or beyond index= LengthToGroup() + <chars inserted>; no formatting there
       
   905         return EFalse;
       
   906     }
       
   907 
       
   908 
       
   909 TBool CPNGNumberGrouping::IsValidPhoneNumberCharacter( TText aCharacter ) const
       
   910     {
       
   911     if ( ((TChar)aCharacter).IsDigit() )
       
   912         return ETrue;
       
   913 
       
   914     // Check through the list of additional valid phone number characters
       
   915     TInt numAdditionalChars = sizeof( KAdditionalPhoneNumberCharacters )/sizeof(TText);
       
   916 
       
   917     for (TInt index = 0; index < numAdditionalChars; index++)
       
   918         {
       
   919         if ( aCharacter == KAdditionalPhoneNumberCharacters[index] )
       
   920             return ETrue;
       
   921         }
       
   922 
       
   923     return EFalse;
       
   924     }
       
   925 
       
   926 EXPORT_C TBool CPNGNumberGrouping::IsChangedByGrouping() const
       
   927     {
       
   928     // The only way that grouping is effectively different is by making things longer
       
   929     return ( Length() > UnFormattedLength() );
       
   930     }
       
   931 
       
   932 TInt CPNGNumberGrouping::LengthToGroup() const
       
   933     {
       
   934 
       
   935     TPtrC ptr = iUnformattedNumber->Des();
       
   936     TInt lengthToGroup = ptr.Length();
       
   937 
       
   938     // Find the first non-digit
       
   939     for (TInt index = 0; index < ptr.Length(); index++)
       
   940         {
       
   941         TChar ch = TChar(ptr[index]);
       
   942         ch.Fold(TChar::EFoldDigits);
       
   943         if ( !( ch.IsDigit() ) )
       
   944             {
       
   945             lengthToGroup = index; // only characters BEFORE the character at index are grouped
       
   946             break;
       
   947             }
       
   948         }
       
   949 
       
   950     return lengthToGroup;
       
   951     }
       
   952 
       
   953 // End of File