/*
* Copyright (c) 2005 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "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: Parser "base" class.
*
*/
#include <TInternetDate.h>
#include "FeedParser.h"
#include "FeedParserObserver.h"
#include <leaktracker.h>
#include <xmlutils.h>
_LIT8(KUrlStr, "url");
// -----------------------------------------------------------------------------
// CFeedParser::CFeedParser
// C++ default constructor can NOT contain any code, that
// might leave.
// -----------------------------------------------------------------------------
//
CFeedParser::CFeedParser(CXmlUtils& aXmlUtils):
iFeedMappings(3), iItemMappings(3), iXmlUtils(aXmlUtils)
{
}
// -----------------------------------------------------------------------------
// CFeedParser::~CFeedParser
// Deconstructor.
// -----------------------------------------------------------------------------
//
CFeedParser::~CFeedParser()
{
iFeedMappings.Close();
iItemMappings.Close();
}
// -----------------------------------------------------------------------------
// CFeedParser::AddFeedMappingL
//
// Add a feed ElementHandler mapping. See AddMappingL.
// -----------------------------------------------------------------------------
//
void CFeedParser::AddFeedMappingL(const TDesC8& aNamespace, const TDesC8& aElementName,
TInt aValueId, ElementHandlerFunctionL aHandler)
{
AddMappingL(iFeedMappings, aNamespace, aElementName, aValueId, aHandler);
}
// -----------------------------------------------------------------------------
// CFeedParser::HandleFeedChildL
//
// Process a child of a feed element. See HandleChildL
// -----------------------------------------------------------------------------
//
void CFeedParser::HandleFeedChildL(TXmlEngElement aNode,
MFeedParserObserver& aObserver) const
{
HandleChildL(iFeedMappings, aNode, aObserver);
}
// -----------------------------------------------------------------------------
// CFeedParser::AddItemMappingL
//
// Add an item ElementHandler mapping. See AddMappingL.
// -----------------------------------------------------------------------------
//
void CFeedParser::AddItemMappingL(const TDesC8& aNamespace, const TDesC8& aElementName,
TInt aValueId, ElementHandlerFunctionL aHandler)
{
AddMappingL(iItemMappings, aNamespace, aElementName, aValueId, aHandler);
}
// -----------------------------------------------------------------------------
// CFeedParser::HandleItemChildL
//
// Process a child of a item element. See HandleChildL
// -----------------------------------------------------------------------------
//
void CFeedParser::HandleItemChildL(TXmlEngElement aNode,
MFeedParserObserver& aObserver) const
{
HandleChildL(iItemMappings, aNode, aObserver);
}
// -----------------------------------------------------------------------------
// CFeedParser::ElementHandlerCDataL
//
// An ElementHandler function that extracts the value from the
// child text nodes.
// -----------------------------------------------------------------------------
//
void CFeedParser::ElementHandlerCDataL(const CFeedParser& /*aParser*/, CXmlUtils& aXmlUtils,
TXmlEngElement aNode, TInt aValueId, MFeedParserObserver& aObserver)
{
TDesC* ucs2Des = NULL;
// Get the text.
ucs2Des = aXmlUtils.ExtractTextL(aNode);
if ((ucs2Des == NULL) || (ucs2Des->Length() == 0))
{
delete ucs2Des;
return;
}
// Set the value
CleanupStack::PushL(ucs2Des);
aObserver.AddAttributeL(aValueId, *ucs2Des);
CleanupStack::PopAndDestroy(ucs2Des);
}
// -----------------------------------------------------------------------------
// CFeedParser::ElementHandlerTextL
//
// An ElementHandler function that extracts the value from the
// child text nodes then resolves any html entities and removes any markup.
// -----------------------------------------------------------------------------
//
void CFeedParser::ElementHandlerTextL(const CFeedParser& /*aParser*/, CXmlUtils& aXmlUtils,
TXmlEngElement aNode, TInt aValueId, MFeedParserObserver& aObserver)
{
HBufC* ucs2Des = NULL;
// Get the text.
ucs2Des = aXmlUtils.ExtractTextL(aNode);
if ((ucs2Des == NULL) || (ucs2Des->Length() == 0))
{
delete ucs2Des;
return;
}
// Clean it up.
TPtr ptr(ucs2Des->Des());
CleanupStack::PushL(ucs2Des);
(void) aXmlUtils.CleanupMarkupL(ptr, 0);
// Set the value
if (ucs2Des->Length() > 0)
{
aObserver.AddAttributeL(aValueId, *ucs2Des);
}
CleanupStack::PopAndDestroy(ucs2Des);
}
// -----------------------------------------------------------------------------
// CFeedParser::ElementHandlerUrlChildL
//
// An ElementHandler function that extracts the value from a child
// url element. If the element doesn't contain any elements and it contains
// text it is extracted instead.
// -----------------------------------------------------------------------------
//
void CFeedParser::ElementHandlerUrlChildL(const CFeedParser& aParser, CXmlUtils& aXmlUtils,
TXmlEngElement aNode, TInt aValueId, MFeedParserObserver& aObserver)
{
TXmlEngElement urlNode = NULL;
// Get the url element.
urlNode = aXmlUtils.GetFirstNamedChild(aNode, KUrlStr());
// If it doesn't have a url child and it has no children at all just
// extract the url from the node's text.
if ((urlNode.IsNull()) && (!urlNode.HasChildNodes()))
{
urlNode = aNode;
}
// Extracts the value url.
ElementHandlerUrlL(aParser, aXmlUtils, urlNode, aValueId, aObserver);
}
// -----------------------------------------------------------------------------
// CFeedParser::ElementHandlerUrlL
//
// An ElementHandler function that extracts the value from the
// child text nodes. It further performs url related clean up.
// -----------------------------------------------------------------------------
//
void CFeedParser::ElementHandlerUrlL(const CFeedParser& /*aParser*/, CXmlUtils& aXmlUtils,
TXmlEngElement aNode, TInt aValueId, MFeedParserObserver& aObserver)
{
TDesC* ucs2Des = NULL;
// Get the text.
ucs2Des = aXmlUtils.ExtractTextL(aNode);
if ((ucs2Des == NULL) || (ucs2Des->Length() == 0))
{
delete ucs2Des;
return;
}
CleanupStack::PushL(ucs2Des);
// Clean up the url.
TPtr ptr(const_cast<TUint16*>(ucs2Des->Ptr()), ucs2Des->Length());
aXmlUtils.CleanupUrlL(ptr);
// TODO: Resolve the href using feed url (the feed's url can be stored in CFeedParser).
// Set the value
aObserver.AddAttributeL(aValueId, *ucs2Des);
CleanupStack::PopAndDestroy(ucs2Des);
}
// -----------------------------------------------------------------------------
// CFeedParser::ElementHandlerDateL
//
// An ElementHandler function that extracts the date from the
// child text nodes. It can handle date formats defined in RFC 3339, RFC 822,
// RFC 1123, RFC 850, and RFC 1036
// -----------------------------------------------------------------------------
//
void CFeedParser::ElementHandlerDateL(const CFeedParser& /*aParser*/,
CXmlUtils& /*aXmlUtils*/, TXmlEngElement aNode, TInt aValueId, MFeedParserObserver& aObserver)
{
TTime date;
TBool dateSet = EFalse;
RBuf8 rbuf;
// Get the text.
aNode.WholeTextContentsCopyL( rbuf );
if ( rbuf.Length() == 0 )
{
rbuf.Close();
return;
}
rbuf.CleanupClosePushL();
// Try to handle the date with TInternetDate first.
TInternetDate internetData;
TRAPD(err, internetData.SetDateL( rbuf ));
if (err == KErrNone)
{
date = internetData.DateTime();
dateSet = ETrue;
}
// Otherwise if that didn't work then try to interpret it as a RFC 3339 date.
else if (err == KErrCorrupt)
{
TRAP(err, ParseRFC3339L( rbuf, date ));
if (err == KErrNone)
{
dateSet = ETrue;
}
}
// Set the value
if (dateSet)
{
TBuf16<25> str;
str.Format(_L("%Ld"), date.Int64());
aObserver.AddAttributeL(aValueId, str);
}
CleanupStack::PopAndDestroy( /*rbuf*/ );
}
// -----------------------------------------------------------------------------
// CFeedParser::AddMappingL
//
// Makes a mapping between an element's namespance and name with the normalized
// value-id (i.e. title or description) and a function which extracts out
// relevant data and applies it to a provided ValueHolder using the value-id
// (see HandleChildL).
// -----------------------------------------------------------------------------
//
void CFeedParser::AddMappingL(RArray<ElementHandlerMapEntry>& aMappings,
const TDesC8& aNamespace, const TDesC8& aElementName, TInt aValueId,
ElementHandlerFunctionL aHandler)
{
ElementHandlerMapEntry entry;
// Init the entry.
entry.iElementNamespace.Set(aNamespace);
entry.iElementName.Set(aElementName);
entry.iValueId = aValueId;
entry.iElementHandlerL = aHandler;
// Append the entry.
User::LeaveIfError(aMappings.Append(entry));
}
// -----------------------------------------------------------------------------
// CFeedParser::HandleChildL
//
// Using the mapping defined by calls to AddMappingL it passes aNode,
// aValueHolder and associated value-id to the associated function. The method
// does what is needed to extract the relevant values from the node and applies
// them on aValueHolder (using the normalized value-id).
// -----------------------------------------------------------------------------
//
void CFeedParser::HandleChildL(const RArray<ElementHandlerMapEntry>& aMappings,
TXmlEngElement aNode, MFeedParserObserver& aObserver) const
{
// Find the corresponding entry in the map.
for (TInt i = 0; i < aMappings.Count(); i++)
{
// Call the ElementHandler.
if (iXmlUtils.IsNamed(aNode, aMappings[i].iElementNamespace,
aMappings[i].iElementName))
{
(aMappings[i].iElementHandlerL)(*this, iXmlUtils, aNode,
aMappings[i].iValueId, aObserver);
break;
}
}
}
// -----------------------------------------------------------------------------
// CFeedParser::RFC3339DateL
//
// Converts the given RFC 3339 date into a TTime.
// RFC 3339 format (examples): YYYY-MM-DDTHH:MM:SS.FFZ,
// YYYY-MM-DDTHH:MM:SS.FF+HH:MM or YYYY-MM-DDTHH:MM:SS.FF-HH:MM
// -----------------------------------------------------------------------------
//
void CFeedParser::ParseRFC3339L(const TDesC8& aDateStr, TTime& aDate)
{
enum TPart {EYear, EMonth, EDay, EHour, EMin, ESec, EMicro, EZoneHour, EZoneMin, ENone};
TDateTime dateTime;
TInt num;
TBuf<30> timeStr;
TBuf<30> zoneHour;
TBuf<30> zoneMin;
TPart part = EYear;
TInt zone = 0;
timeStr.Zero();
if (aDateStr.Length() > 30)
{
User::Leave(KErrCorrupt);
}
for (TInt i = 0; i < aDateStr.Length(); i++)
{
TChar c(aDateStr[i]);
// Extract the year.
if (part == EYear)
{
if (c.IsDigit())
{
timeStr.Append(c);
}
else if (c == '-')
{
// If timeStr.Length() equals 2 then get the current
// year and use the first two digits to set the four digit year.
if (timeStr.Length() == 2)
{
TTime now;
TInt year;
TBuf<4> yearStr;
now.UniversalTime();
year = now.DateTime().Year();
yearStr.AppendNum(year);
timeStr.Insert(0, yearStr.Left(1));
timeStr.Insert(1, yearStr.Mid(1, 1));
}
TLex16 lex(timeStr);
lex.Val(num);
User::LeaveIfError(dateTime.SetYear(num));
timeStr.Zero();
part = EMonth;
}
else
{
User::Leave(KErrCorrupt);
}
}
// Extract the month.
else if (part == EMonth)
{
if (c.IsDigit())
{
timeStr.Append(c);
}
else if (c == '-')
{
TLex16 lex(timeStr);
TMonth month = EJanuary;
lex.Val(num);
// Convert num to the month enum.
switch (num)
{
case 1:
month = EJanuary;
break;
case 2:
month = EFebruary;
break;
case 3:
month = EMarch;
break;
case 4:
month = EApril;
break;
case 5:
month = EMay;
break;
case 6:
month = EJune;
break;
case 7:
month = EJuly;
break;
case 8:
month = EAugust;
break;
case 9:
month = ESeptember;
break;
case 10:
month = EOctober;
break;
case 11:
month = ENovember;
break;
case 12:
month = EDecember;
break;
default:
User::Leave(KErrCorrupt);
break;
}
User::LeaveIfError(dateTime.SetMonth(month));
timeStr.Zero();
part = EDay;
}
else
{
User::Leave(KErrCorrupt);
}
}
// Extract the day.
else if (part == EDay)
{
if (c.IsDigit())
{
timeStr.Append(c);
}
else if ((c == 'T') || (c == 't'))
{
TLex16 lex(timeStr);
lex.Val(num);
// Day is zero based for some odd reason...
User::LeaveIfError(dateTime.SetDay(num - 1));
timeStr.Zero();
part = EHour;
}
else
{
User::Leave(KErrCorrupt);
}
}
// Extract the hour.
else if (part == EHour)
{
if (c.IsDigit())
{
timeStr.Append(c);
}
else if (c == ':')
{
TLex16 lex(timeStr);
lex.Val(num);
User::LeaveIfError(dateTime.SetHour(num));
timeStr.Zero();
part = EMin;
}
else
{
User::Leave(KErrCorrupt);
}
}
// Extract the min.
else if (part == EMin)
{
if (c.IsDigit())
{
timeStr.Append(c);
}
else if (c == ':')
{
TLex16 lex(timeStr);
lex.Val(num);
User::LeaveIfError(dateTime.SetMinute(num));
timeStr.Zero();
part = ESec;
}
else
{
User::Leave(KErrCorrupt);
}
}
// Extract the sec.
else if (part == ESec)
{
if (c.IsDigit())
{
timeStr.Append(c);
}
else if (c == '.')
{
TLex16 lex(timeStr);
lex.Val(num);
User::LeaveIfError(dateTime.SetSecond(num));
timeStr.Zero();
part = EMicro;
}
else if ((c == 'Z') || (c == 'z'))
{
TLex16 lex(timeStr);
lex.Val(num);
User::LeaveIfError(dateTime.SetSecond(num));
timeStr.Zero();
zone = 0;
part = ENone;
}
else if (c == '+')
{
TLex16 lex(timeStr);
lex.Val(num);
User::LeaveIfError(dateTime.SetSecond(num));
timeStr.Zero();
zone = 1;
part = EZoneHour;
}
else if (c == '-')
{
TLex16 lex(timeStr);
lex.Val(num);
User::LeaveIfError(dateTime.SetSecond(num));
timeStr.Zero();
zone = -1;
part = EZoneHour;
}
else
{
User::Leave(KErrCorrupt);
}
}
// Ignore the micro-sec.
else if (part == EMicro)
{
if (c.IsDigit())
{
// Ignore microseconds.
}
else if ((c == 'Z') || (c == 'z'))
{
zone = 0;
part = ENone;
}
else if (c == '+')
{
zone = 1;
part = EZoneHour;
}
else if (c == '-')
{
zone = -1;
part = EZoneHour;
}
else
{
User::Leave(KErrCorrupt);
}
}
// Extract the hour.
else if (part == EZoneHour)
{
if (c.IsDigit())
{
zoneHour.Append(c);
}
else if (c == ':')
{
part = EZoneMin;
}
else
{
User::Leave(KErrCorrupt);
}
}
// Extract the min.
else if (part == EZoneMin)
{
if (c.IsDigit())
{
zoneMin.Append(c);
}
else
{
User::Leave(KErrCorrupt);
}
}
else
{
User::Leave(KErrCorrupt);
}
}
// Handle the common date only format (i.e. YYYY-MM-DD).
if ((part == EDay) && ((aDateStr.Length() == 10) || (aDateStr.Length() == 8)))
{
TLex16 lex(timeStr);
lex.Val(num);
// Day is zero based for some odd reason...
User::LeaveIfError(dateTime.SetDay(num - 1));
}
// Convert timeStr into a TTime.
aDate = dateTime;
// Adjust the aDate to UTC.
if (zone != 0)
{
TLex16 zHoursLex(zoneHour);
TLex16 zMinLex(zoneMin);
TInt zHours;
TInt zMins;
// Get the offset.
zHoursLex.Val(zHours);
zMinLex.Val(zMins);
// Adjust it back to UTC.
if (zone > 0)
{
aDate -= TTimeIntervalHours(zHours);
aDate -= TTimeIntervalMinutes(zMins);
}
// Otherwise adjust it forward to UTC.
else
{
aDate += TTimeIntervalHours(zHours);
aDate += TTimeIntervalMinutes(zMins);
}
}
}