windows-nt/Source/XPSP1/NT/shell/ext/ratings/msrating/parserat.cpp
2020-09-26 16:20:57 +08:00

1333 lines
39 KiB
C++

/****************************************************************************\
*
* PARSERAT.C -- Code to parse .RAT files
*
* Created: Greg Jones
*
\****************************************************************************/
/*Includes------------------------------------------------------------------*/
#include "msrating.h"
#include "mslubase.h"
#include "parselbl.h" /* we use a couple of this guy's subroutines */
#include "msluglob.h"
#include "debug.h"
// Sundown: pointer to boolean conversion
#pragma warning (disable: 4800)
/****************************************************************************
Some design notes on how this parser works:
A ParenThing is:
'(' identifier [stuff] ')'
where [stuff] could be:
a quoted string
a number
a boolean
a series of ParenThings
in the case of extensions:
a quoted string, followed by
one or more quoted strings and/or ParenThings
The entire .RAT file is a ParenThing, except that it has no identifier, just
a list of ParenThings inside it.
**********************************************************************
We pass the parser a schema for what things it expects -- we have
a big array listing identifiers for each different possible keyword, and
each parser call receives a smaller array containing only those indices
that are valid to occur within that object.
We make PicsRatingSystem, PicsCategory, and PicsEnum derive from a common
base class which supports a virtual function AddItem(ID,data). So at the
top level, we construct an (empty) PicsRatingSystem. We call the parser,
giving it a pointer to that guy, and a structure describing what to parse --
the ParenObject's token is a null string (since the global structure is the
one that doesn't start with a token before its first embedded ParenThing),
and we give a list saying the allowable things in a PicsRatingSystem are
PICS-version, rating-system, rating-service, default, description, extension,
icon, name, category. There is a global table indicating a handler function
for every type of ParenThing, which knows how to create a data structure
completely describing that ParenThing. (That data structure could be as
simple as a number or as complex as allocating and parsing a complete
PicsCategory object.)
The parser walks along, and for each ParenThing he finds, he identifies it
by looking up its token in the list provided by the caller. Each entry in
that list should include a field which indicates whether multiple things
of that identity are allowed (e.g., 'category') or not (e.g., rating-system).
If only one is allowed, then when the parser finds one he marks it as having
been found.
When the parser identifies the ParenThing, he calls its handler function to
completely parse the data in the ParenThing and return that object into an
LPVOID provided by the parser. If that is successful, the parser then calls
its object's AddItem(ID,data) virtual function to add the specified item to
the object, relying on the object itself to know what type "data" points to --
a number, a pointer to a heap string which can be given to ETS::SetTo, a
pointer to a PicsCategory object which can be appended to an array, etc.
The RatFileParser class exists solely to provide a line number shared by
all the parsing routines. This line number is updated as the parser walks
through the file, and is frozen as soon as an error is found. This line
number can later be reported to the user to help localize errors in RAT files.
*****************************************************************************/
class RatFileParser
{
public:
UINT m_nLine;
RatFileParser() { m_nLine = 1; }
LPSTR EatQuotedString(LPSTR pIn);
HRESULT ParseToOpening(LPSTR *ppIn, AllowableOption *paoExpected,
AllowableOption **ppFound);
HRESULT ParseParenthesizedObject(
LPSTR *ppIn, /* where we are in the text stream */
AllowableOption aao[], /* allowable things inside this object */
PicsObjectBase *pObject /* object to set parameters into */
);
char* FindNonWhite(char *pc);
};
/* White returns a pointer to the first whitespace character starting at pc.
*/
char* White(char *pc)
{
ASSERT(pc);
while (1)
{
if (*pc == '\0' ||
*pc ==' ' ||
*pc == '\t' ||
*pc == '\r' ||
*pc == '\n')
{
return pc;
}
pc++;
}
}
/* NonWhite returns a pointer to the first non-whitespace character starting
* at pc.
*/
char* NonWhite(char *pc)
{
ASSERT(pc);
while (1)
{
if (*pc != ' ' &&
*pc != '\t' &&
*pc != '\r' &&
*pc != '\n') /* includes null terminator */
{
return pc;
}
pc++;
}
}
/* FindNonWhite returns a pointer to the first non-whitespace character starting
* at pc.
*/
char* RatFileParser::FindNonWhite(char *pc)
{
ASSERT(pc);
while (1)
{
if (*pc != ' ' &&
*pc != '\t' &&
*pc != '\r' &&
*pc != '\n') /* includes null terminator */
{
return pc;
}
if (*pc == '\n')
m_nLine++;
pc++;
}
}
/* Returns a pointer to the closing doublequote of a quoted string, counting
* linefeeds as we go. Returns NULL if no closing doublequote found.
*/
LPSTR RatFileParser::EatQuotedString(LPSTR pIn)
{
LPSTR pszQuote = strchrf(pIn, '\"');
if (pszQuote == NULL)
{
TraceMsg( TF_WARNING, "RatFileParser::EatQuotedString() - No closing doublequote found!" );
return NULL;
}
pIn = strchrf(pIn, '\n');
while (pIn != NULL && pIn < pszQuote)
{
m_nLine++;
pIn = strchrf(pIn+1, '\n');
}
return pszQuote;
}
/***************************************************************************
Worker functions for inheriting category properties and other
miscellaneous category stuff.
***************************************************************************/
HRESULT PicsCategory::InitializeMyDefaults(PicsCategory *pCategory)
{
if (!pCategory->etnMin.fIsInit() && etnMin.fIsInit())
pCategory->etnMin.Set(etnMin.Get());
if (!pCategory->etnMax.fIsInit() && etnMax.fIsInit())
pCategory->etnMax.Set(etnMax.Get());
if (!pCategory->etfMulti.fIsInit() && etfMulti.fIsInit())
pCategory->etfMulti.Set(etfMulti.Get());
if (!pCategory->etfInteger.fIsInit() && etfInteger.fIsInit())
pCategory->etfInteger.Set(etfInteger.Get());
if (!pCategory->etfLabelled.fIsInit() && etfLabelled.fIsInit())
pCategory->etfLabelled.Set(etfLabelled.Get());
if (!pCategory->etfUnordered.fIsInit() && etfUnordered.fIsInit())
pCategory->etfUnordered.Set(etfUnordered.Get());
return NOERROR;
}
HRESULT PicsRatingSystem::InitializeMyDefaults(PicsCategory *pCategory)
{
if (m_pDefaultOptions != NULL)
return m_pDefaultOptions->InitializeMyDefaults(pCategory);
return NOERROR; /* no defaults to initialize */
}
HRESULT PicsDefault::InitializeMyDefaults(PicsCategory *pCategory)
{
if (!pCategory->etnMin.fIsInit() && etnMin.fIsInit())
pCategory->etnMin.Set(etnMin.Get());
if (!pCategory->etnMax.fIsInit() && etnMax.fIsInit())
pCategory->etnMax.Set(etnMax.Get());
if (!pCategory->etfMulti.fIsInit() && etfMulti.fIsInit())
pCategory->etfMulti.Set(etfMulti.Get());
if (!pCategory->etfInteger.fIsInit() && etfInteger.fIsInit())
pCategory->etfInteger.Set(etfInteger.Get());
if (!pCategory->etfLabelled.fIsInit() && etfLabelled.fIsInit())
pCategory->etfLabelled.Set(etfLabelled.Get());
if (!pCategory->etfUnordered.fIsInit() && etfUnordered.fIsInit())
pCategory->etfUnordered.Set(etfUnordered.Get());
return NOERROR;
}
HRESULT PicsEnum::InitializeMyDefaults(PicsCategory *pCategory)
{
return E_NOTIMPL; /* should never have a category inherit from an enum */
}
PicsExtension::PicsExtension()
: m_pszRatingBureau(NULL)
{
/* nothing else */
}
PicsExtension::~PicsExtension()
{
delete m_pszRatingBureau;
m_pszRatingBureau = NULL;
}
HRESULT PicsExtension::InitializeMyDefaults(PicsCategory *pCategory)
{
return E_NOTIMPL; /* should never have a category inherit from an extension */
}
void PicsCategory::FixupLimits()
{
BOOL fLabelled = (etfLabelled.fIsInit() && etfLabelled.Get());
/*fix up max and min values*/
if (fLabelled ||
(arrpPE.Length()>0 && (!etnMax.fIsInit() || !etnMax.fIsInit())))
{
if (arrpPE.Length() > 0)
{
if (!etnMax.fIsInit())
etnMax.Set(N_INFINITY);
if (!etnMin.fIsInit())
etnMin.Set(P_INFINITY);
for (int z=0;z<arrpPE.Length();++z)
{
if (arrpPE[z]->etnValue.Get() > etnMax.Get()) etnMax.Set(arrpPE[z]->etnValue.Get());
if (arrpPE[z]->etnValue.Get() < etnMin.Get()) etnMin.Set(arrpPE[z]->etnValue.Get());
}
}
else
{
etfLabelled.Set(FALSE); /* no enum labels? better not have labelled flag then */
fLabelled = FALSE;
}
}
/*sort labels by value*/
if (fLabelled)
{
int x,y;
PicsEnum *pPE;
for (x=0;x<arrpPE.Length()-1;++x)
{
for (y=x+1;y<arrpPE.Length();++y)
{
if (arrpPE[y]->etnValue.Get() < arrpPE[x]->etnValue.Get())
{
pPE = arrpPE[x];
arrpPE[x] = arrpPE[y];
arrpPE[y] = pPE;
}
}
}
}
}
void PicsCategory::SetParents(PicsRatingSystem *pOwner)
{
pPRS = pOwner;
UINT cSubCategories = arrpPC.Length();
for (UINT i = 0; i < cSubCategories; i++)
{
InitializeMyDefaults(arrpPC[i]); /* subcategory inherits our defaults */
arrpPC[i]->SetParents(pOwner); /* process all subcategories */
}
FixupLimits(); /* inheritance is done, make sure limits make sense */
}
/***************************************************************************
Handler functions which know how to parse the various kinds of content
which can occur within a parenthesized object.
***************************************************************************/
HRESULT RatParseString(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
{
*ppOut = NULL;
LPSTR pszCurrent = *ppszIn;
if (*pszCurrent != '\"')
{
TraceMsg( TF_WARNING, "RatParseString() - Start string expected!" );
return RAT_E_EXPECTEDSTRING;
}
pszCurrent++;
LPSTR pszEnd = pParser->EatQuotedString(pszCurrent);
if (pszEnd == NULL)
{
TraceMsg( TF_WARNING, "RatParseString() - End string expected!" );
return RAT_E_EXPECTEDSTRING;
}
UINT cbString = (unsigned int) (pszEnd-pszCurrent);
LPSTR pszNew = new char[cbString + 1];
if (pszNew == NULL)
{
TraceMsg( TF_WARNING, "RatParseString() - pszNew is NULL!" );
return E_OUTOFMEMORY;
}
memcpyf(pszNew, pszCurrent, cbString);
pszNew[cbString] = '\0';
*ppOut = (LPVOID)pszNew;
*ppszIn = pParser->FindNonWhite(pszEnd + 1);
return NOERROR;
}
HRESULT RatParseNumber(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
{
int n;
LPSTR pszCurrent = *ppszIn;
HRESULT hres = ::ParseNumber(&pszCurrent, &n);
if (FAILED(hres))
{
TraceMsg( TF_WARNING, "RatParseNumber() - Number Expected!" );
return RAT_E_EXPECTEDNUMBER;
}
*(int *)ppOut = n;
LPSTR pszNewline = strchrf(*ppszIn, '\n');
while (pszNewline != NULL && pszNewline < pszCurrent)
{
pParser->m_nLine++;
pszNewline = strchrf(pszNewline+1, '\n');
}
*ppszIn = pszCurrent;
return NOERROR;
}
HRESULT RatParseBool(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
{
BOOL b;
/* PICS spec allows a terse way of specifying a TRUE boolean -- leaving
* out the value entirely. In a .RAT file, the result looks like
*
* (unordered)
* (multivalue)
*
* and so on. Called has pointed us at non-whitespace, so if we see
* a closing paren, we know the .RAT file author used this syntax.
*/
if (**ppszIn == ')')
{
b = TRUE;
}
else
{
LPSTR pszCurrent = *ppszIn;
HRESULT hres = ::GetBool(&pszCurrent, &b);
if (FAILED(hres))
{
TraceMsg( TF_WARNING, "RatParseBool() - Boolean Expected!" );
return RAT_E_EXPECTEDBOOL;
}
LPSTR pszNewline = strchrf(*ppszIn, '\n');
while (pszNewline != NULL && pszNewline < pszCurrent)
{
pParser->m_nLine++;
pszNewline = strchrf(pszNewline+1, '\n');
}
*ppszIn = pszCurrent;
}
*(LPBOOL)ppOut = b;
return NOERROR;
}
AllowableOption aaoPicsCategory[] = {
{ ROID_TRANSMITAS, AO_SINGLE | AO_MANDATORY },
{ ROID_NAME, AO_SINGLE },
{ ROID_DESCRIPTION, AO_SINGLE },
{ ROID_ICON, AO_SINGLE },
{ ROID_EXTENSION, 0 },
{ ROID_INTEGER, AO_SINGLE },
{ ROID_LABELONLY, AO_SINGLE },
{ ROID_MIN, AO_SINGLE },
{ ROID_MAX, AO_SINGLE },
{ ROID_MULTIVALUE, AO_SINGLE },
{ ROID_UNORDERED, AO_SINGLE },
{ ROID_LABEL, 0 },
{ ROID_CATEGORY, 0 },
{ ROID_INVALID, 0 }
};
const UINT caoPicsCategory = sizeof(aaoPicsCategory) / sizeof(aaoPicsCategory[0]);
HRESULT RatParseCategory(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
{
/* We must make a copy of the allowable options array because the
* parser will fiddle with the flags in the entries -- specifically,
* setting AO_SEEN. It wouldn't be thread-safe to do this to a
* static array.
*/
AllowableOption aao[caoPicsCategory];
::memcpyf(aao, ::aaoPicsCategory, sizeof(aao));
PicsCategory *pCategory = new PicsCategory;
if (pCategory == NULL)
{
TraceMsg( TF_WARNING, "RatParseCategory() - pCategory is NULL!" );
return E_OUTOFMEMORY;
}
HRESULT hres = pParser->ParseParenthesizedObject(
ppszIn, /* var containing current ptr */
aao, /* what's legal in this object */
pCategory); /* object to add items back to */
if (FAILED(hres))
{
TraceMsg( TF_WARNING, "RatParseCategory() - ParseParenthesizedObject() Failed with hres=0x%x!", hres );
delete pCategory;
pCategory = NULL;
return hres;
}
*ppOut = (LPVOID)pCategory;
return NOERROR;
}
AllowableOption aaoPicsEnum[] = {
{ ROID_NAME, AO_SINGLE },
{ ROID_DESCRIPTION, AO_SINGLE },
{ ROID_VALUE, AO_SINGLE | AO_MANDATORY },
{ ROID_ICON, AO_SINGLE },
{ ROID_INVALID, 0 }
};
const UINT caoPicsEnum = sizeof(aaoPicsEnum) / sizeof(aaoPicsEnum[0]);
HRESULT RatParseLabel(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
{
/* We must make a copy of the allowable options array because the
* parser will fiddle with the flags in the entries -- specifically,
* setting AO_SEEN. It wouldn't be thread-safe to do this to a
* static array.
*/
AllowableOption aao[caoPicsEnum];
::memcpyf(aao, ::aaoPicsEnum, sizeof(aao));
PicsEnum *pEnum = new PicsEnum;
if (pEnum == NULL)
{
TraceMsg( TF_WARNING, "RatParseCategory() - pEnum is NULL!" );
return E_OUTOFMEMORY;
}
HRESULT hres = pParser->ParseParenthesizedObject(
ppszIn, /* var containing current ptr */
aao, /* what's legal in this object */
pEnum); /* object to add items back to */
if (FAILED(hres))
{
TraceMsg( TF_WARNING, "RatParseLabel() - ParseParenthesizedObject() Failed with hres=0x%x!", hres );
delete pEnum;
pEnum = NULL;
return hres;
}
*ppOut = (LPVOID)pEnum;
return NOERROR;
}
AllowableOption aaoPicsDefault[] = {
{ ROID_EXTENSION, 0 },
{ ROID_INTEGER, AO_SINGLE },
{ ROID_LABELONLY, AO_SINGLE },
{ ROID_MAX, AO_SINGLE },
{ ROID_MIN, AO_SINGLE },
{ ROID_MULTIVALUE, AO_SINGLE },
{ ROID_UNORDERED, AO_SINGLE },
{ ROID_INVALID, 0 }
};
const UINT caoPicsDefault = sizeof(aaoPicsDefault) / sizeof(aaoPicsDefault[0]);
HRESULT RatParseDefault(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
{
/* We must make a copy of the allowable options array because the
* parser will fiddle with the flags in the entries -- specifically,
* setting AO_SEEN. It wouldn't be thread-safe to do this to a
* static array.
*/
AllowableOption aao[caoPicsDefault];
::memcpyf(aao, ::aaoPicsDefault, sizeof(aao));
PicsDefault *pDefault = new PicsDefault;
if (pDefault == NULL)
{
TraceMsg( TF_WARNING, "RatParseDefault() - pDefault is NULL!" );
return E_OUTOFMEMORY;
}
HRESULT hres = pParser->ParseParenthesizedObject(
ppszIn, /* var containing current ptr */
aao, /* what's legal in this object */
pDefault); /* object to add items back to */
if (FAILED(hres))
{
TraceMsg( TF_WARNING, "RatParseDefault() - ParseParenthesizedObject() Failed with hres=0x%x!", hres );
delete pDefault;
pDefault = NULL;
return hres;
}
*ppOut = (LPVOID)pDefault;
return NOERROR;
}
AllowableOption aaoPicsExtension[] = {
{ ROID_MANDATORY, AO_SINGLE },
{ ROID_OPTIONAL, AO_SINGLE },
{ ROID_INVALID, 0 }
};
const UINT caoPicsExtension = sizeof(aaoPicsExtension) / sizeof(aaoPicsExtension[0]);
HRESULT RatParseExtension(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
{
/* We must make a copy of the allowable options array because the
* parser will fiddle with the flags in the entries -- specifically,
* setting AO_SEEN. It wouldn't be thread-safe to do this to a
* static array.
*/
AllowableOption aao[caoPicsExtension];
::memcpyf(aao, ::aaoPicsExtension, sizeof(aao));
PicsExtension *pExtension = new PicsExtension;
if (pExtension == NULL)
{
TraceMsg( TF_WARNING, "RatParseExtension() - pExtension is NULL!" );
return E_OUTOFMEMORY;
}
HRESULT hres = pParser->ParseParenthesizedObject(
ppszIn, /* var containing current ptr */
aao, /* what's legal in this object */
pExtension); /* object to add items back to */
if (FAILED(hres))
{
TraceMsg( TF_WARNING, "RatParseExtension() - ParseParenthesizedObject() Failed with hres=0x%x!", hres );
delete pExtension;
pExtension = NULL;
return hres;
}
*ppOut = (LPVOID)pExtension;
return NOERROR;
}
/* Since the only extension we support right now is the one for a label
* bureau, we just return the first quoted string we find if the caller
* wants it. If ppOut is NULL, then it's some other extension and the
* caller doesn't care about the data, he just wants it eaten.
*/
HRESULT ParseRatExtensionData(LPSTR *ppszIn, RatFileParser *pParser, LPSTR *ppOut)
{
HRESULT hres = NOERROR;
LPSTR pszCurrent = *ppszIn;
/* Must look for closing ')' ourselves to terminate */
while (*pszCurrent != ')')
{
if (*pszCurrent == '(')
{
pszCurrent = pParser->FindNonWhite(pszCurrent+1); /* skip paren and whitespace */
hres = ParseRatExtensionData(&pszCurrent, pParser, ppOut); /* parentheses contain data */
if (FAILED(hres))
{
TraceMsg( TF_WARNING, "ParseRatExtensionData() - ParseRatExtensionData() Failed with hres=0x%x!", hres );
return hres;
}
if (*pszCurrent != ')')
{
TraceMsg( TF_WARNING, "ParseRatExtensionData() - Right Parenthesis Expected!" );
return RAT_E_EXPECTEDRIGHT;
}
pszCurrent = pParser->FindNonWhite(pszCurrent+1); /* skip close ) and whitespace */
}
else if (*pszCurrent == '\"')
{ /* should be just a quoted string */
if (ppOut != NULL && *ppOut == NULL)
{
hres = RatParseString(&pszCurrent, (LPVOID *)ppOut, pParser);
// $REVIEW - Should we return for FAILED(hres)?
}
else
{
++pszCurrent;
LPSTR pszEndQuote = pParser->EatQuotedString(pszCurrent);
if (pszEndQuote == NULL)
{
TraceMsg( TF_WARNING, "ParseRatExtensionData() - String Expected!" );
return RAT_E_EXPECTEDSTRING;
}
pszCurrent = pParser->FindNonWhite(pszEndQuote+1); /* skip close " and whitespace */
}
}
else
{
TraceMsg( TF_WARNING, "ParseRatExtensionData() - General Bad Syntax!" );
return RAT_E_UNKNOWNITEM; /* general bad syntax */
}
}
/* Caller will skip over final ')' for us. */
*ppszIn = pszCurrent;
return NOERROR;
}
HRESULT RatParseMandatory(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
{
LPSTR pszCurrent = *ppszIn;
/* First thing better be a quoted URL identifying the extension. */
if (*pszCurrent != '\"')
{
TraceMsg( TF_WARNING, "RatParseMandatory() - Start String Expected!" );
return RAT_E_EXPECTEDSTRING;
}
pszCurrent++;
LPSTR pszEnd = pParser->EatQuotedString(pszCurrent);
if (pszCurrent == NULL)
{
TraceMsg( TF_WARNING, "RatParseMandatory() - End String Expected!" );
return RAT_E_EXPECTEDSTRING; /* missing closing " */
}
/* See if it's the extension for a label bureau. */
LPSTR pszBureau = NULL;
LPSTR *ppData = NULL;
if (IsEqualToken(pszCurrent, pszEnd, ::szRatingBureauExtension))
{
ppData = &pszBureau;
}
pszCurrent = pParser->FindNonWhite(pszEnd+1); /* skip closing " and whitespace */
HRESULT hres = ParseRatExtensionData(&pszCurrent, pParser, ppData);
if (FAILED(hres))
{
TraceMsg( TF_WARNING, "RatParseMandatory() - ParseRatExtensionData() Failed with hres=0x%x!", hres );
return hres;
}
*ppOut = pszBureau; /* return label bureau string if that's what we found */
*ppszIn = pszCurrent;
if (ppData == NULL)
return RAT_E_UNKNOWNMANDATORY; /* we didn't recognize it */
else
return NOERROR;
}
/* RatParseOptional uses the code in RatParseMandatory to parse the extension
* data, in case an extension that should be optional comes in as mandatory.
* We then detect RatParseMandatory rejecting the thing as unrecognized and
* allow it through, since here it's optional.
*/
HRESULT RatParseOptional(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
{
HRESULT hres = RatParseMandatory(ppszIn, ppOut, pParser);
if (hres == RAT_E_UNKNOWNMANDATORY)
hres = S_OK;
return hres;
}
/***************************************************************************
Code to identify the opening keyword of a parenthesized object and
associate it with content.
***************************************************************************/
/* The following array is indexed by RatObjectID values. */
struct {
LPCSTR pszToken; /* token by which we identify it */
RatObjectHandler pHandler; /* function which parses the object's contents */
} aObjectDescriptions[] = {
{ szNULL, NULL },
{ NULL, NULL }, /* placeholder for comparing against no token */
{ szPicsVersion, RatParseNumber },
{ szRatingSystem, RatParseString },
{ szRatingService, RatParseString },
{ szRatingBureau, RatParseString },
{ szBureauRequired, RatParseBool },
{ szCategory, RatParseCategory },
{ szTransmitAs, RatParseString },
{ szLabel, RatParseLabel },
{ szValue, RatParseNumber },
{ szDefault, RatParseDefault },
{ szDescription, RatParseString },
{ szExtensionOption, RatParseExtension },
{ szMandatory, RatParseMandatory },
{ szOptional, RatParseOptional },
{ szIcon, RatParseString },
{ szInteger, RatParseBool },
{ szLabelOnly, RatParseBool },
{ szMax, RatParseNumber },
{ szMin, RatParseNumber },
{ szMultiValue, RatParseBool },
{ szName, RatParseString },
{ szUnordered, RatParseBool }
};
/* ParseToOpening eats the opening '(' of a parenthesized object, and
* verifies that the token just inside it is one of the expected ones.
* If so, *ppIn is advanced past that token to the next non-whitespace
* character; otherwise, an error is returned.
*
* For example, if *ppIn is pointing at "(PICS-version 1.1)", and
* ROID_PICSVERSION is in the allowable option table supplied, then
* NOERROR is returned and *ppIn will point at "1.1)".
*
* If the function is successful, *ppFound is set to point to the element
* in the allowable-options table which matches the type of thing this
* object actually is.
*/
HRESULT RatFileParser::ParseToOpening(LPSTR *ppIn, AllowableOption *paoExpected,
AllowableOption **ppFound)
{
LPSTR pszCurrent = *ppIn;
pszCurrent = FindNonWhite(pszCurrent);
if (*pszCurrent != '(')
{
TraceMsg( TF_WARNING, "RatFileParser::ParseToOpening() - Left Parenthesis Expected!" );
return RAT_E_EXPECTEDLEFT;
}
pszCurrent = FindNonWhite(pszCurrent+1); /* skip '(' and whitespace */
LPSTR pszTokenEnd = FindTokenEnd(pszCurrent);
for (; paoExpected->roid != ROID_INVALID; paoExpected++)
{
LPCSTR pszThisToken = aObjectDescriptions[paoExpected->roid].pszToken;
/* Special case for beginning of RAT file structure: no token at all. */
if (pszThisToken == NULL)
{
if (*pszCurrent == '(')
{
*ppIn = pszCurrent;
*ppFound = paoExpected;
return NOERROR;
}
else
{
TraceMsg( TF_WARNING, "RatFileParser::ParseToOpening() - Token Left Parenthesis Expected!" );
return RAT_E_EXPECTEDLEFT;
}
}
else if (IsEqualToken(pszCurrent, pszTokenEnd, pszThisToken))
break;
}
if (paoExpected->roid != ROID_INVALID)
{
*ppIn = FindNonWhite(pszTokenEnd); /* skip token and whitespace */
*ppFound = paoExpected;
return NOERROR;
}
else
{
TraceMsg( TF_WARNING, "RatFileParser::ParseToOpening() - Unknown Rat Object!" );
return RAT_E_UNKNOWNITEM;
}
}
/***************************************************************************
The top-level entrypoint for parsing out a whole rating system.
***************************************************************************/
AllowableOption aaoPicsRatingSystem[] = {
{ ROID_PICSVERSION, AO_SINGLE | AO_MANDATORY },
{ ROID_RATINGSYSTEM, AO_SINGLE | AO_MANDATORY },
{ ROID_RATINGSERVICE, AO_SINGLE | AO_MANDATORY },
{ ROID_RATINGBUREAU, AO_SINGLE },
{ ROID_BUREAUREQUIRED, AO_SINGLE },
{ ROID_DEFAULT, 0 },
{ ROID_DESCRIPTION, AO_SINGLE },
{ ROID_EXTENSION, 0 },
{ ROID_ICON, AO_SINGLE },
{ ROID_NAME, AO_SINGLE },
{ ROID_CATEGORY, AO_MANDATORY },
{ ROID_INVALID, 0 }
};
const UINT caoPicsRatingSystem = sizeof(aaoPicsRatingSystem) / sizeof(aaoPicsRatingSystem[0]);
HRESULT PicsRatingSystem::Parse(LPCSTR pszFilename, LPSTR pIn)
{
/* This guy is small enough to just init directly on the stack */
AllowableOption aaoRoot[] = { { ROID_PICSDOCUMENT, 0 }, { ROID_INVALID, 0 } };
AllowableOption aao[caoPicsRatingSystem];
::memcpyf(aao, ::aaoPicsRatingSystem, sizeof(aao));
AllowableOption *pFound;
RatFileParser parser;
HRESULT hres = parser.ParseToOpening(&pIn, aaoRoot, &pFound);
if (FAILED(hres))
{
TraceMsg( TF_WARNING, "PicsRatingSystem::Parse() - Failed ParseToOpening() with hres=0x%x!", hres );
return hres; /* some error early on */
}
hres = parser.ParseParenthesizedObject(
&pIn, /* var containing current ptr */
aao, /* what's legal in this object */
this); /* object to add items back to */
if(SUCCEEDED(hres))
{
if(*pIn!=')') //check for a closing parenthesis
{
hres=RAT_E_EXPECTEDRIGHT;
}
else
{
LPTSTR lpszEnd=NonWhite(pIn+1);
if(*lpszEnd!='\0') // make sure we're at the end of the file
{
hres=RAT_E_EXPECTEDEND;
}
}
}
if(FAILED(hres))
{
nErrLine=parser.m_nLine;
TraceMsg( TF_WARNING, "PicsRatingSystem::Parse() - Failed ParseParenthesizedObject() at nErrLine=%d with hres=0x%x!", nErrLine, hres );
}
return hres;
}
/***************************************************************************
Callbacks into the various class objects to add parsed properties.
***************************************************************************/
HRESULT PicsRatingSystem::AddItem(RatObjectID roid, LPVOID pData)
{
HRESULT hres = S_OK;
switch (roid) {
case ROID_PICSVERSION:
etnPicsVersion.Set(PtrToLong(pData));
break;
case ROID_RATINGSYSTEM:
etstrRatingSystem.SetTo((LPSTR)pData);
break;
case ROID_RATINGSERVICE:
etstrRatingService.SetTo((LPSTR)pData);
break;
case ROID_RATINGBUREAU:
etstrRatingBureau.SetTo((LPSTR)pData);
break;
case ROID_BUREAUREQUIRED:
etbBureauRequired.Set((bool)pData);
break;
case ROID_DEFAULT:
m_pDefaultOptions = (PicsDefault *)pData;
break;
case ROID_DESCRIPTION:
etstrDesc.SetTo((LPSTR)pData);
break;
case ROID_EXTENSION:
{
/* just eat extensions for now */
PicsExtension *pExtension = (PicsExtension *)pData;
if (pExtension != NULL)
{
/* If this is a rating bureau extension, take his bureau
* string and store it in this PicsRatingSystem. We now
* own the memory, so NULL out the extension's pointer to
* it so he won't delete it.
*/
if (pExtension->m_pszRatingBureau != NULL)
{
etstrRatingBureau.SetTo(pExtension->m_pszRatingBureau);
pExtension->m_pszRatingBureau = NULL;
}
delete pExtension;
pExtension = NULL;
}
}
break;
case ROID_ICON:
etstrIcon.SetTo((LPSTR)pData);
break;
case ROID_NAME:
etstrName.SetTo((LPSTR)pData);
break;
case ROID_CATEGORY:
{
PicsCategory *pCategory = (PicsCategory *)pData;
hres = arrpPC.Append(pCategory) ? S_OK : E_OUTOFMEMORY;
if (FAILED(hres))
{
delete pCategory;
pCategory = NULL;
}
else
{
InitializeMyDefaults(pCategory); /* category inherits default settings */
pCategory->SetParents(this); /* set pPRS fields in whole tree */
}
}
break;
default:
ASSERT(FALSE); /* shouldn't have been given a ROID that wasn't in
* the table we passed to the parser! */
hres = E_UNEXPECTED;
break;
}
return hres;
}
HRESULT PicsCategory::AddItem(RatObjectID roid, LPVOID pData)
{
HRESULT hres = S_OK;
switch (roid)
{
case ROID_TRANSMITAS:
etstrTransmitAs.SetTo((LPSTR)pData);
break;
case ROID_NAME:
etstrName.SetTo((LPSTR)pData);
break;
case ROID_DESCRIPTION:
etstrDesc.SetTo((LPSTR)pData);
break;
case ROID_ICON:
etstrIcon.SetTo((LPSTR)pData);
break;
case ROID_EXTENSION:
{ /* we support no extensions below the rating system level */
PicsExtension *pExtension = (PicsExtension *)pData;
if (pExtension != NULL)
{
delete pExtension;
pExtension = NULL;
}
}
break;
case ROID_INTEGER:
etfInteger.Set((bool) pData);
break;
case ROID_LABELONLY:
etfLabelled.Set((bool) pData);
break;
case ROID_MULTIVALUE:
etfMulti.Set((bool)pData);
break;
case ROID_UNORDERED:
etfUnordered.Set((bool)pData);
break;
case ROID_MIN:
etnMin.Set(PtrToLong(pData));
break;
case ROID_MAX:
etnMax.Set(PtrToLong(pData));
break;
case ROID_LABEL:
{
PicsEnum *pEnum = (PicsEnum *)pData;
hres = arrpPE.Append(pEnum) ? S_OK : E_OUTOFMEMORY;
if (FAILED(hres))
{
delete pEnum;
pEnum = NULL;
}
}
break;
case ROID_CATEGORY:
{
PicsCategory *pCategory = (PicsCategory *)pData;
/* For a nested category, synthesize the transmit-name from
* ours and the child's (e.g., parent category 'color' plus
* child category 'hue' becomes 'color/hue'.
*
* Note that the memory we allocate for the new name will be
* owned by pCategory->etstrTransmitAs. There is no memory
* leak there.
*/
UINT cbCombined = strlenf(etstrTransmitAs.Get()) +
strlenf(pCategory->etstrTransmitAs.Get()) +
2; /* for PicsDelimChar + null */
LPSTR pszTemp = new char[cbCombined];
if (pszTemp == NULL)
hres = E_OUTOFMEMORY;
else {
wsprintf(pszTemp, "%s%c%s", etstrTransmitAs.Get(),
PicsDelimChar, pCategory->etstrTransmitAs.Get());
pCategory->etstrTransmitAs.SetTo(pszTemp);
hres = arrpPC.Append(pCategory) ? S_OK : E_OUTOFMEMORY;
}
if (FAILED(hres))
{
delete pCategory;
pCategory = NULL;
}
}
break;
default:
ASSERT(FALSE); /* shouldn't have been given a ROID that wasn't in
* the table we passed to the parser! */
hres = E_UNEXPECTED;
break;
}
return hres;
}
HRESULT PicsEnum::AddItem(RatObjectID roid, LPVOID pData)
{
HRESULT hres = S_OK;
switch (roid)
{
case ROID_NAME:
etstrName.SetTo((LPSTR)pData);
break;
case ROID_DESCRIPTION:
etstrDesc.SetTo((LPSTR)pData);
break;
case ROID_ICON:
etstrIcon.SetTo((LPSTR)pData);
break;
case ROID_VALUE:
etnValue.Set(PtrToLong(pData));
break;
default:
ASSERT(FALSE); /* shouldn't have been given a ROID that wasn't in
* the table we passed to the parser! */
hres = E_UNEXPECTED;
break;
}
return hres;
}
HRESULT PicsDefault::AddItem(RatObjectID roid, LPVOID pData)
{
HRESULT hres = S_OK;
switch (roid)
{
case ROID_EXTENSION:
{ /* we support no extensions below the rating system level */
PicsExtension *pExtension = (PicsExtension *)pData;
if (pExtension != NULL)
{
delete pExtension;
pExtension = NULL;
}
}
break;
case ROID_INTEGER:
etfInteger.Set((bool)pData);
break;
case ROID_LABELONLY:
etfLabelled.Set((bool)pData);
break;
case ROID_MULTIVALUE:
etfMulti.Set((bool)pData);
break;
case ROID_UNORDERED:
etfUnordered.Set((bool)pData);
break;
case ROID_MIN:
etnMin.Set(PtrToLong(pData));
break;
case ROID_MAX:
etnMax.Set(PtrToLong(pData));
break;
default:
ASSERT(FALSE); /* shouldn't have been given a ROID that wasn't in
* the table we passed to the parser! */
hres = E_UNEXPECTED;
break;
}
return hres;
}
HRESULT PicsExtension::AddItem(RatObjectID roid, LPVOID pData)
{
HRESULT hres = S_OK;
switch (roid) {
case ROID_OPTIONAL:
case ROID_MANDATORY:
/* Only data we should get is a label bureau string. */
if (pData != NULL)
m_pszRatingBureau = (LPSTR)pData;
break;
default:
ASSERT(FALSE); /* shouldn't have been given a ROID that wasn't in
* the table we passed to the parser! */
hres = E_UNEXPECTED;
break;
}
return hres;
}
/***************************************************************************
The main loop of the parser.
***************************************************************************/
/* ParseParenthesizedObjectContents is called with a text pointer pointing at
* the first non-whitespace thing following the token identifying the type of
* object. It parses the rest of the contents of the object, up to and
* including the ')' which closes it. The array of AllowableOption structures
* specifies which understood options are allowed to occur within this object.
*/
HRESULT RatFileParser::ParseParenthesizedObject(
LPSTR *ppIn, /* where we are in the text stream */
AllowableOption aao[], /* allowable things inside this object */
PicsObjectBase *pObject /* object to set parameters into */
)
{
HRESULT hres = S_OK;
LPSTR pszCurrent = *ppIn;
AllowableOption *pFound;
for (pFound = aao; pFound->roid != ROID_INVALID; pFound++)
{
pFound->fdwOptions &= ~AO_SEEN;
}
pFound = NULL;
while (*pszCurrent != ')' && *pszCurrent != '\0' && SUCCEEDED(hres))
{
hres = ParseToOpening(&pszCurrent, aao, &pFound);
if (SUCCEEDED(hres))
{
LPVOID pData;
hres = (*(aObjectDescriptions[pFound->roid].pHandler))(&pszCurrent, &pData, this);
if (SUCCEEDED(hres))
{
if ((pFound->fdwOptions & (AO_SINGLE | AO_SEEN)) == (AO_SINGLE | AO_SEEN))
{
hres = RAT_E_DUPLICATEITEM;
}
else
{
pFound->fdwOptions |= AO_SEEN;
hres = pObject->AddItem(pFound->roid, pData);
if (SUCCEEDED(hres))
{
if (*pszCurrent != ')')
hres = RAT_E_EXPECTEDRIGHT;
else
pszCurrent = FindNonWhite(pszCurrent+1);
}
}
}
}
}
if (FAILED(hres))
{
return hres;
}
for (pFound = aao; pFound->roid != ROID_INVALID; pFound++)
{
if ((pFound->fdwOptions & (AO_MANDATORY | AO_SEEN)) == AO_MANDATORY)
{
return RAT_E_MISSINGITEM; /* mandatory item not found */
}
}
*ppIn = pszCurrent;
return hres;
}