diff -r 000000000000 -r 8e480a14352b messagingfw/biomsgfw/CBCPSRC/CBCP.CPP --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/messagingfw/biomsgfw/CBCPSRC/CBCP.CPP Mon Jan 18 20:36:02 2010 +0200 @@ -0,0 +1,679 @@ +// Copyright (c) 1999-2009 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: +// Compact Business Card Parser for the BIO Messaging project. +// Still in a rudimentary shape, pending specifications and design from Symbian. +// When ParseL is called, the parser parses the data into an array. This array is saved to a store. +// The message is converted to vCard format and then saved as an attachment file. +// Finally, the message body is converted to an informative message for the user. +// Possibles: May have to implement a ProcessL fn to either save the file in a different directory +// or launch an application after the user presses the accept button in the viewer. +// N.B. The Compact Business Card specification defines the delimiter to be . +// However, it will often be the case that the is preceded by a . +// The characters must be stripped out. +// +// + +#include +#include +#include + +#include +#include "CBCP.H" +#include "CBCPDEF.H" +#ifdef SYMBIAN_ENABLE_SPLIT_HEADERS +#include "tmsvbioinfo.h" +#endif + +// Factory methods +EXPORT_C CBusinessCardParser* CBusinessCardParser::NewL(CRegisteredParserDll& aRegisteredParserDll, CMsvEntry& aEntry, RFs& aFs) + { + CBusinessCardParser* self = new (ELeave) CBusinessCardParser(aRegisteredParserDll, aEntry, aFs); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(); + return self; + } +// end CBusinessCardParser::NewL() + + +void CBusinessCardParser::ConstructL() + { + iParsedFieldArray = new (ELeave) CArrayPtrSeg(16); + CActiveScheduler::Add(this); + } +// end CBusinessCardParser::ConstructL() + + +void CBusinessCardParser::ParseL(TRequestStatus& aStatus, const TDesC& aSms) +// Doesn't actually do any parsing (see ParseMessageL) but kicks off the parsing process. + { + TMsvEntry entry = iEntry.Entry(); // Get the generic stuff. + iEntryId = entry.Id(); // Store the TMsvId. + + __ASSERT_DEBUG((entry.MtmData3() == 0 || entry.MtmData3() == 1), + User::Panic(_L("CBCP-DLL"),KErrGeneral)); + // Already parsed.... + if(entry.MtmData3() == 1) + { + iReport = &aStatus; + User::RequestComplete(iReport, KErrNone); + } + // Not parsed.... + else if(entry.MtmData3() == 0) + { + if (iSmsBuf != NULL) + { + delete iSmsBuf; + iSmsBuf = NULL; + } + iSmsBuf = aSms.AllocL(); // Allocate new HBufC object. + ChangeStateL(EUnfoldMessage); // Unfold message and set thread active. + aStatus = KRequestPending; + iReport = &aStatus; + } + else + { + User::Leave(KErrNotSupported); + } + } +// end CBusinessCardParser::ParseL(TRequestStatus&, const TDesC&) + + +// ProcessL() -- Business Card parser doesn't use this function at present. +void CBusinessCardParser::ProcessL(TRequestStatus& aStatus) + { + iReport = &aStatus; + User::RequestComplete(iReport, KErrNotSupported); + } +// end CBusinessCardParser::CommitL(TRequestStatus&) + + +void CBusinessCardParser::DoCancel() // Must be provided. + { + User::RequestComplete(iReport,KErrCancel); + } +// end CBusinessCardParser::DoCancel() + + +void CBusinessCardParser::RunL() // Must be provided since this is derived from a CActive object. + { + iCompleted = iStatus.Int(); + if (iCompleted != KErrNone) + { + User::RequestComplete(iReport,iCompleted); + return; + } + + switch (iState) + { + case EUnfoldMessage: // Message unfolded so parse it. + TRAPD(err, ChangeStateL(EParseMessage)); + if (err != KErrNone) + User::RequestComplete(iReport, err); + break; + + case EParseMessage: // Message parsed so update message. + TRAP(err, ChangeStateL(ECompleteMessage)); + if(err != KErrNone) + { + User::RequestComplete(iReport, err); + } + break; + + case ECompleteMessage: // CompleteMessage done. We're finished here. + User::RequestComplete(iReport, KErrNone); + break; + + default: + break; + } + } +// end CBusinessCardParser::RunL() + + +void CBusinessCardParser::ChangeStateL(TParseSession aState) + { + iState = aState; // Increments iState. + switch (iState) + { + case EUnfoldMessage: + UnfoldMessageL(); // Perform MIME unfolding of SMS message. + break; + + case EParseMessage: + ParseMessageL(); // Parse message in Tlex object into array. + break; + + case ECompleteMessage: + CompleteMessageL(); // Copy message data to child and write new message body. + break; + + default: + break; + } + RequestComplete(iStatus, KErrNone); // Go back to ready state. + SetActive(); + } +// end CBusinessCardParser::ChangeStateL(TParseSession) + + +void CBusinessCardParser::ParseMessageL() +// Parses Tlex object iSms into array. + { + StripCarriageReturnsL(); // Strip carriage returns from message. + TPtrC fieldName; + TPtrC fieldValue; + TInt i; + + // First line of a CBC is "Business Card". This keyword may not exist if the card is + // received over NBS (port 5501 decimal, 157D hex). + // Need to hunt for keyword. If it does not exist then we assume that it is a valid + // CBC received over NBS. + + // reset parsedfield array + + if (iParsedFieldArray->Count() > 0) + iParsedFieldArray->ResetAndDestroy(); + + TLexMark startMark; + iSms.Mark(startMark); + iSms.SkipSpaceAndMark(); + TBool isCompactBusinessCard = EFalse; + do + { + while ( (iSms.Peek() != 'B') && (!iSms.Eos()) ) iSms.Inc(); + if (iSms.Peek() == 'B') // If the next character is a B then... + { + iSms.Mark(); + iSms.SkipCharacters(); // Skip over non-white space (hopefully "Business"). + if (!iSms.Eos()) iSms.Inc(); // Skip over a single white space. + if (!iSms.Eos()) iSms.SkipCharacters(); // Skip over non-white space (hopefully "Card"). + // N.B. '/n' is a white space character. + + // Extract text from between mark and current position and check that it is as we hope. + if (iSms.MarkedToken() != KBusinessCardHeader) // Not keyword so keep looking. + { + iSms.UnGetToMark(); + iSms.Inc(); + iSms.Mark(); + } + else + isCompactBusinessCard = ETrue; + } + else // No keyword so received over NBS. + { + isCompactBusinessCard = ETrue; + iSms.UnGetToMark(startMark); + } + } + while (!isCompactBusinessCard); + // Move on to next line: + while (iSms.Peek() != KCharLineFeed && !iSms.Eos()) iSms.Inc(); + if (iSms.Eos()) + User::Leave(KBspInvalidMessage); // Leave. + else + iSms.SkipAndMark(1); + + // Parse the first three compulsory fields: + AddRequiredFieldL(KHeaderName); + AddRequiredFieldL(KHeaderCompany); + AddRequiredFieldL(KHeaderTitle); + + for (i=0; i<3; i++) + { + while (iSms.Peek() != KCharLineFeed && !iSms.Eos()) // Skip to next delimiter. + iSms.Inc(); + fieldName.Set(iParsedFieldArray->At(i)->FieldName()); // Store (pointer to) field name. + fieldValue.Set(iSms.MarkedToken()); // Store (pointer to) field value. + AddParsedFieldL(fieldName, fieldValue, ETrue); // All fields are mandatory. + if (!iSms.Eos()) + iSms.SkipAndMark(1); // Move on to next line. + else + User::Leave(KBspInvalidMessage); // Leave - not long enough for valid CBC. + } + iNumberOfCbcFields = i; + + // Now parse the fax and telephone fields: + // The first telephone field must be the fourth line of the compact-card-body. It must + // either be empty or start with "tel". Subsequent telephone lines must also start with + // "tel", otherwise we move on to the fax line(s). + iNumberOfTel = 0; + AddRequiredFieldL(KHeaderPhone); + if (iSms.Peek() != KCharLineFeed) // We have a potential phone number. + { + if ( iSms.Peek() != 't' || iSms.Remainder().Length() < 4 ) + User::Leave(KBspInvalidMessage); // No valid phone number line. + do { + iSms.Inc(3); + if (iSms.MarkedToken().Compare(_L("tel")) != 0 && iNumberOfTel == 0) + User::Leave(KBspInvalidMessage); // No valid phone number line. + else if (iSms.MarkedToken().Compare(_L("tel")) != 0) + { // Not a valid phone number line. + iSms.UnGetToMark(); // Go back to start of line. + break; // Exit; go on to fax number lines + } + else if (iSms.MarkedToken().Compare(_L("tel")) == 0 && iNumberOfTel > 0) + { // Additional phone number. + TBuf<8> aPhoneHeader; + aPhoneHeader.Append(KHeaderPhone); + TBuf<3> aNumber; + aNumber.NumFixedWidth(++iNumberOfTel,EDecimal,3); + aPhoneHeader.Append(aNumber); + AddRequiredFieldL(aPhoneHeader); + } + else + iNumberOfTel = 1; // First phone number + for (;;) + { + if (IsValidTelephoneCharacter()) + { + iSms.Mark(); + while (iSms.Peek() != KCharLineFeed && !iSms.Eos()) iSms.Inc(); + i = iNumberOfCbcFields + iNumberOfTel - 1; + fieldName.Set(iParsedFieldArray->At(i)->FieldName());// Store (pointer to) field name. + fieldValue.Set(iSms.MarkedToken()); // Store (pointer to) field value. + AddParsedFieldL(fieldName, fieldValue, ETrue); // All fields are mandatory. + iSms.SkipAndMark(1); // Move on to next line. + break; + } + else if (iSms.Peek() == KCharLineFeed) + { + iSms.SkipAndMark(1); // Move on to next line. + break; + } + else if (iSms.Eos()) + User::Leave(KBspInvalidMessage);// Leave - not long enough for valid CBC. + else + iSms.Inc(); // Skip to next digit. + } + } + while (iSms.Peek() == 't' && iSms.Remainder().Length() >= 3); + } + else + { + iSms.SkipAndMark(1); // No phone number - move on to next line. + iNumberOfTel = 1; // One (empty) phone number field. + } + + // The first fax field must be the next line of the compact-card-body. It must either + // be empty or start with "fax". Subsequent fax number lines must also start with "fax". + AddRequiredFieldL(KHeaderFax); + iNumberOfFax = 0; + if (iSms.Peek() != KCharLineFeed) // We have a potential fax number. + { + if ( iSms.Peek() != 'f' || iSms.Remainder().Length() < 4 ) + User::Leave(KBspInvalidMessage); // No valid fax number line. + do { + iSms.Inc(3); + if (iSms.MarkedToken().Compare(_L("fax")) != 0 && iNumberOfFax == 0) + User::Leave(KBspInvalidMessage); // No valid fax number line. + else if (iSms.MarkedToken().Compare(_L("fax")) != 0) + { // Not a valid fax number line. + iSms.UnGetToMark(); // Go back to start of line. + break; // Exit; go on to email line + } + else if (iSms.MarkedToken().Compare(_L("fax")) == 0 && iNumberOfFax > 0) + { // Additional fax number. + TBuf<6> aFaxHeader; + aFaxHeader.Append(KHeaderFax); + TBuf<3> aNumber; + aNumber.NumFixedWidth(++iNumberOfFax,EDecimal,3); + aFaxHeader.Append(aNumber); + AddRequiredFieldL(aFaxHeader); + } + else + iNumberOfFax = 1; // First fax number + for (;;) // Could use a switch. + { + if (IsValidTelephoneCharacter()) + { + iSms.Mark(); + while (iSms.Peek() != KCharLineFeed && !iSms.Eos()) iSms.Inc(); + i = iNumberOfCbcFields + iNumberOfTel + iNumberOfFax - 1; + fieldName.Set(iParsedFieldArray->At(i)->FieldName());// Store (pointer to) field name. + fieldValue.Set(iSms.MarkedToken()); // Store (pointer to) field value. + AddParsedFieldL(fieldName, fieldValue, ETrue); // All fields are mandatory. + iSms.SkipAndMark(1); // Move on to next line. + break; + } + else if (iSms.Peek() == KCharLineFeed) + { + iSms.SkipAndMark(1); // Move on to next line. + break; + } + else if (iSms.Eos()) + User::Leave(KBspInvalidMessage);// Leave - not long enough for valid CBC. + else + iSms.Inc(); // Skip to next digit. + } + } + while (iSms.Peek() == 'f' && iSms.Remainder().Length() >= 3); + } + else + { + iSms.SkipAndMark(1); // No fax number - move on to next line. + iNumberOfFax = 1; // One (empty) fax number field. + } + + // Now parse the last six compulsory fields: + iNumberOfCbcFields += (iNumberOfFax + iNumberOfTel); + AddRequiredFieldL(KHeaderEmail); + AddRequiredFieldL(KHeaderStreet1); + AddRequiredFieldL(KHeaderStreet2); + AddRequiredFieldL(KHeaderStreet3); + AddRequiredFieldL(KHeaderStreet4); + AddRequiredFieldL(KHeaderStreet5); + for (i=iNumberOfCbcFields; iAt(i)->FieldName()); // Store (pointer to) field name. + fieldValue.Set(iSms.MarkedToken()); // Store (pointer to) field value. + AddParsedFieldL(fieldName, fieldValue, ETrue); // All fields are mandatory. + if (!iSms.Eos()) + { + iSms.SkipAndMark(1); // Move on to next line. + } + else + i=iNumberOfCbcFields+6; // Exit FOR loop. + } + iNumberOfCbcFields = i; + + // Throw any extra lines into the note field: + if (!iSms.Eos()) + { + AddRequiredFieldL(KHeaderExtra); + iNumberOfCbcFields++; + while (!iSms.Eos()) iSms.Inc(); // Include remainder of text. + TInt notesLength = iSms.TokenLength(); + iSms.UnGetToMark(); + delete iNotesBuffer; + iNotesBuffer = NULL; + iNotesBuffer=HBufC::NewLC(notesLength); // Buffer large enough for remainder of text. + TPtr notesPtr = iNotesBuffer->Des(); + + iSms.SkipSpaceAndMark(); + // Need to strip out line feeds: + while (!iSms.Eos()) + { + while (!iSms.Eos() && iSms.Peek() != KCharLineFeed) iSms.Inc(); + notesPtr.Append(iSms.MarkedToken()); + notesPtr.Append(KCharComma); // Delimit data with commas. + if (!iSms.Eos()) iSms.SkipSpaceAndMark(); + } + + notesLength = notesPtr.Length(); // Reduce size of buffer to minimum. + iNotesBuffer = iNotesBuffer->ReAllocL(notesLength); + + fieldValue.Set(iNotesBuffer->Des()); // Store (pointer to) field value + fieldName.Set(KHeaderExtra); // Store (pointer to) field name + AddParsedFieldL(fieldName, fieldValue, EFalse); // This field is optional. + CleanupStack::Pop(); // iNotesBuffer (deleted in destructor). + } + } +// end CBusinessCardParser::ParseMessageL + + +void CBusinessCardParser::StripCarriageReturnsL() + { + // Strips out the carriage returns; they shouldn't be there anyway! + TPtr smsPtr = iSmsBuf->Des(); + for (TInt pos = 0; pos < (smsPtr.Length() - 1); pos++) + if (smsPtr[pos] == KCharCarriageReturn) + smsPtr.Delete(pos, 1); + iSmsBuf = iSmsBuf->ReAllocL(smsPtr.Length()); // Reallocate iSmsBuf with new size. + iSms = iSmsBuf->Des(); // Initialise Tlex object. + } +// end CBusinessCardParser::StripCarriageReturnsL + + +void CBusinessCardParser::CompleteMessageL() +// Streams the contents of iParsedFieldsArray as a child entry of the original message. +// Converts data to vCard format and saves to a file. +// If this returns OK sets the iMtmData3 flag to 1 (parsed) and writes a new message body into the SmartMessage. + { + // Create new stream (containing parsed data) within store associated with message entry: + iEntry.SetEntryL(iEntryId); // Changes the entry used as the Message Server context. + CMsvStore* store=iEntry.EditStoreL(); // Returns the message store for the current context with write access. + // 'store' is the 'active object' responsible for the request. + CleanupStack::PushL(store); + StoreL(*store); // Store the parsed fields into a message stream. + CleanupStack::PopAndDestroy(); + + // Change Entry Body text to vCard and save to a file: + iEntry.SetEntryL(iEntryId); // Changes the entry used as the Message Server context. + + CreateDataL(); + + TBuf<64> tempBuf; + tempBuf.Format(_L("%x"), iEntry.Entry().Id()); + tempBuf.Append(KVCardExt); + StoreL(TFileName(tempBuf)); // Pass the filename to base class method - creates a new folder + // linked to message entry and stores body as an 8 bit attachment file. + + TMsvEntry entry= iEntry.Entry(); // 'entry' is the 'active object' responsible for the request. + + entry.SetMtmData3(1); // Indicates that we've parsed it. + // + iEntry.ChangeL(entry); // Changes the current context. + } +// end CBusinessCardParser::CompleteMessageL() + +void CBusinessCardParser::AddRequiredFieldL(const TDesC& aFieldName) + { + // Create new CParsedField object on heap: + CParsedField* parsedField = new (ELeave) CParsedField; + CleanupStack::PushL(parsedField); + + // Initialise field name: + parsedField->SetFieldNameL(aFieldName); + parsedField->SetFieldValueL(KInitialFieldValue); + parsedField->SetMandatoryField(EFalse); + + // Add pointer to array: + iParsedFieldArray->AppendL(parsedField); + CleanupStack::Pop(parsedField); // No need to delete the CParsedField object, since it's now owned by the array. + } +// end CBusinessCardParser::AddRequiredFieldL(const TDesC&) + + +void CBusinessCardParser::CreateDataL() + { + delete iSettings; + iSettings = HBufC::NewL(512); + + // Write vCard to iSettings: + AppendItemL( KVCardHeader); // Header. + AppendItemL( KVCardVersion); // Version of vCard spec. + AppendItemL( KVCardFormattedName); // Formatted name. + AppendItemL( iParsedFieldArray->At(0)->FieldValue()); + + // Parse out name into vCard semicolon delimited fields: + // The Nokia Smart Messaging Specification, Revision 2.0.0, Section 3.3.1 suggests that the name + // should be in the format "last-name first-name". The following code assumes that this convention + // is used. A single name is assumed to be a last name. If there are more than two words then the + // remaining text is assumed to be additional names. + AppendItemL( KVCardName); // Name, semicolon delimited. + TLex iField = iParsedFieldArray->At(0)->FieldValue(); // Put name into a TLex object + iField.SkipSpace(); // Last name. + if (!iField.Eos()) + { + iField.Mark(); + iField.SkipCharacters(); + AppendItemL( iField.MarkedToken()); + } + AppendItemL( KCharSemiColon ); + iField.SkipSpace(); // First name. + if (!iField.Eos()) + { + iField.Mark(); + iField.SkipCharacters(); + AppendItemL( iField.MarkedToken()); + } + AppendItemL( KCharSemiColon); + iField.SkipSpace(); + AppendItemL( iField.Remainder()); // Additional names. + AppendItemL( KCharSemiColon); // Honourific prefices. + AppendItemL( KCharSemiColon); // Honourific suffices. + + AppendItemL( KVCardAddress); // Address, semicolon delimited. + TInt addressOffset = iNumberOfFax + iNumberOfTel + KTelOffset; // Offset of address lines from start of cbc. + TInt i; + for (i=0; iAt(addressOffset+i)->FieldValue()); + } + + // note info is optional - so append if it exists + + if (addressOffset+KNumberOfAddressLines < iParsedFieldArray->Count()) + { + // Extra gubbins tacked to end of notes field, after address: + AppendItemL( KVCardNote); + AppendItemL( + iParsedFieldArray->At(addressOffset+KNumberOfAddressLines)->FieldValue()); + } + + // Telephone numbers: + for (i=KTelOffset-1; iAt(i)->FieldValue(), KVCardTel, KVCardTelCell); + } + + // Fax numbers: + for (; iAt(i)->FieldValue(), KVCardFax, KVCardFaxCell); + } + + // Other stuff: + AppendItemL( KVCardEmail); // Email address. + AppendItemL( + iParsedFieldArray->At(iNumberOfFax+iNumberOfTel+KTelOffset-1)->FieldValue()); + AppendItemL( KVCardTitle); // Title (job description). + AppendItemL( iParsedFieldArray->At(2)->FieldValue()); + AppendItemL( KVCardOrg); // Organisation: company. + AppendItemL( iParsedFieldArray->At(1)->FieldValue()); + AppendItemL( KVCardFooter); // End of vCard. + } + +void CBusinessCardParser::AppendTelItemL(const TDesC& aTel, const TDesC& aDefaultLabel, const TDesC& aCellLabel) + { + // We must check for the prefix (GSM) to see if the number should + // be stored in the cellphone field instead + + // Store the field for the first number and check for the (GSM) prefix + + TLex telLex(aTel); + const TPtrC prefixCell(KVPrefixCell); + const TInt prefixCellLen = prefixCell.Length(); + + TPtrC label(aDefaultLabel); + TPtrC value(aTel); + + if (aTel.Length() > prefixCellLen && telLex.Peek() == TChar(*prefixCell.Ptr())) + { + telLex.Inc(prefixCellLen); + if(telLex.MarkedToken().CompareF(prefixCell) == 0) // If there is a GSM prefix create as cellphone number + { + telLex.SkipSpaceAndMark(); + label.Set(aCellLabel); + value.Set(telLex.RemainderFromMark()); + } + } + + AppendItemL(label); + AppendItemL(value); + } + +void CBusinessCardParser::AppendItemL(const TDesC& aItem) + { + TInt newLength = iSettings->Des().Length() + aItem.Length(); + if(iSettings->Des().MaxLength() < newLength) + { + iSettings = iSettings->ReAllocL(newLength); + } + TPtr des = iSettings->Des(); + des.Append(aItem); + } + +void CBusinessCardParser::AddParsedFieldL(const TDesC& aFieldName, + const TDesC& aFieldValue, + TBool aMandatory) + { + // This method is responsible for adding field values found by the parser to the array of parsed fields. + // It enforces the "each field once only" rule (see Nokia Smart Messaging spec, 2.0.0pre, 3-34). + for (TInt i = 0; i < iParsedFieldArray->Count(); i++) + { + // Use CompareF() to perform case-insensitive match: + if (aFieldName.CompareF(iParsedFieldArray->At(i)->FieldName()) == 0) + { + // Field name exists, so insert a value: + iParsedFieldArray->At(i)->SetFieldValueL(aFieldValue); + iParsedFieldArray->At(i)->SetMandatoryField(aMandatory); + return; + } + } + } +// end CBusinessCardParser::AddParsedField(const TDesC&, const TDesC&, TBool) + + +void CBusinessCardParser::RequestComplete(TRequestStatus& aStatus, TInt aError) + { + TRequestStatus* p = &aStatus; + User::RequestComplete(p, aError); + } +// end CBusinessCardParser::RequestComplete(TRequestStatus&, TInt) + +TBool CBusinessCardParser::IsValidTelephoneCharacter() const + { + const TChar ch = iSms.Peek(); + + if (ch.IsDigit() || + ch == '(' || + ch == '-' || + ch == '*' || + ch == '#' || + ch == 'w' || + ch == 'W' || + ch == 'p' || + ch == 'P' || + ch =='+') + return ETrue; + else + return EFalse; + } + +// Constructor +CBusinessCardParser::CBusinessCardParser(CRegisteredParserDll& aRegisteredParserDll, CMsvEntry& aEntry, RFs& aFs) +: CBaseScriptParser2(aRegisteredParserDll, aEntry, aFs) + { + } +// end CBusinessCardParser::CBusinessCardParser() + + +CBusinessCardParser::~CBusinessCardParser() + { + if (iSmsBuf != NULL) + { + delete iSmsBuf; + } + if (iParsedFieldArray != NULL) + { + iParsedFieldArray->ResetAndDestroy(); + delete iParsedFieldArray; + delete iNotesBuffer; + } + } +// end CBusinessCardParser::~CBusinessCardParser()