486 lines
11 KiB
C++
486 lines
11 KiB
C++
|
/*
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @module RTFLOG.CPP - RichEdit RTF log
|
||
|
*
|
||
|
* Contains the code for the RTFLog class which can be used
|
||
|
* to log the number of times RTF tags are read by the RTF reader
|
||
|
* for use in coverage testing. TODO: Implement RTF tag logging for the Mac
|
||
|
*
|
||
|
* Authors:<nl>
|
||
|
* Created for RichEdit 2.0: Brad Olenick
|
||
|
*
|
||
|
* Copyright (c) 1995-2000, Microsoft Corporation. All rights reserved.
|
||
|
*/
|
||
|
|
||
|
#include "_common.h"
|
||
|
#include "_rtflog.h"
|
||
|
#include "tokens.h"
|
||
|
|
||
|
extern INT cKeywords;
|
||
|
extern const KEYWORD rgKeyword[];
|
||
|
|
||
|
|
||
|
#if defined(DEBUG) && !defined(NOFULLDEBUG)
|
||
|
/*
|
||
|
* CRTFRead::TestParserCoverage()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* A debug routine used to test the coverage of HandleToken. The routine
|
||
|
* puts the routine into a debug mode and then determines:
|
||
|
*
|
||
|
* 1. Dead tokens - (T & !S & !P)
|
||
|
* Here, token:
|
||
|
* a) is defined in token.h (T)
|
||
|
* b) does not have a corresponding keyword (not scanned) (!S)
|
||
|
* c) is not processed by HandleToken (!P)
|
||
|
* 2. Tokens that are parsed but not scanned - (T & !S & P)
|
||
|
* Here, token:
|
||
|
* a) is defined in token.h (T)
|
||
|
* b) does not have a corresponding keyword (not scanned) (!S}
|
||
|
* c) is processed by HandleToken (P)
|
||
|
* 3. Tokens that are scanned but not parsed - (T & S & !P)
|
||
|
* Here, token:
|
||
|
* a) is defined in token.h (T)
|
||
|
* b) does have a corresponding keyword (is scanned) (S)
|
||
|
* c) is not processed by HandleToken (!P)
|
||
|
*/
|
||
|
void CRTFRead::TestParserCoverage()
|
||
|
{
|
||
|
int i;
|
||
|
char *rgpszKeyword[tokenMax - tokenMin];
|
||
|
BOOL rgfParsed[tokenMax - tokenMin];
|
||
|
char szBuf[256];
|
||
|
|
||
|
// Put HandleToken in debug mode
|
||
|
_fTestingParserCoverage = TRUE;
|
||
|
|
||
|
// Gather info about tokens/keywords
|
||
|
for(i = 0; i < tokenMax - tokenMin; i++)
|
||
|
{
|
||
|
_token = (TOKEN)(i + tokenMin);
|
||
|
rgpszKeyword[i] = PszKeywordFromToken(_token);
|
||
|
rgfParsed[i] = HandleToken() == ecNoError;
|
||
|
}
|
||
|
|
||
|
// Reset HandleToken to non-debug mode
|
||
|
_fTestingParserCoverage = FALSE;
|
||
|
|
||
|
// Should coverage check include those we know will fail test, but
|
||
|
// which we've examined and know why they fail?
|
||
|
BOOL fExcuseCheckedToks = TRUE;
|
||
|
|
||
|
if(GetProfileIntA("RICHEDIT DEBUG", "RTFCOVERAGESTRICT", 0))
|
||
|
fExcuseCheckedToks = FALSE;
|
||
|
|
||
|
// (T & !S & !P) (1. above)
|
||
|
for(i = 0; i < tokenMax - tokenMin; i++)
|
||
|
{
|
||
|
if(rgpszKeyword[i] || rgfParsed[i])
|
||
|
continue;
|
||
|
|
||
|
TOKEN tok = (TOKEN)(i + tokenMin);
|
||
|
|
||
|
// Token does not correspond to a keyword, but still may be scanned
|
||
|
// check list of individual symbols which are scanned
|
||
|
if(FTokIsSymbol(tok))
|
||
|
continue;
|
||
|
|
||
|
// Check list of tokens which have been checked and fail
|
||
|
// the sanity check for some known reason (see FTokFailsCoverageTest def'n)
|
||
|
if(fExcuseCheckedToks && FTokFailsCoverageTest(tok))
|
||
|
continue;
|
||
|
|
||
|
sprintf(szBuf, "CRTFRead::TestParserCoverage(): Token neither scanned nor parsed - token = %d", tok);
|
||
|
AssertSz(0, szBuf);
|
||
|
}
|
||
|
|
||
|
// (T & !S & P) (2. above)
|
||
|
for(i = 0; i < tokenMax - tokenMin; i++)
|
||
|
{
|
||
|
if(rgpszKeyword[i] || !rgfParsed[i])
|
||
|
continue;
|
||
|
|
||
|
TOKEN tok = (TOKEN)(i + tokenMin);
|
||
|
|
||
|
// Token does not correspond to a keyword, but still may be scanned
|
||
|
// check list of individual symbols which are scanned
|
||
|
if(FTokIsSymbol(tok))
|
||
|
continue;
|
||
|
|
||
|
// Check list of tokens which have been checked and fail
|
||
|
// the sanity check for some known reason (see FTokFailsCoverageTest def'n)
|
||
|
if(fExcuseCheckedToks && FTokFailsCoverageTest(tok))
|
||
|
continue;
|
||
|
|
||
|
sprintf(szBuf, "CRTFRead::TestParserCoverage(): Token parsed but not scanned - token = %d", tok);
|
||
|
AssertSz(0, szBuf);
|
||
|
}
|
||
|
|
||
|
// (T & S & !P) (3. above)
|
||
|
for(i = 0; i < tokenMax - tokenMin; i++)
|
||
|
{
|
||
|
if(!rgpszKeyword[i] || rgfParsed[i])
|
||
|
continue;
|
||
|
|
||
|
TOKEN tok = (TOKEN)(i + tokenMin);
|
||
|
|
||
|
// Check list of tokens which have been checked and fail
|
||
|
// the sanity check for some known reason (see FTokFailsCoverageTest def'n)
|
||
|
if(fExcuseCheckedToks && FTokFailsCoverageTest(tok))
|
||
|
continue;
|
||
|
|
||
|
sprintf(szBuf, "CRTFRead::TestParserCoverage(): Token scanned but not parsed - token = %d, tag = \\%s", tok, rgpszKeyword[i]);
|
||
|
AssertSz(0, szBuf);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CRTFRead::PszKeywordFromToken()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Searches the array of keywords and returns the keyword
|
||
|
* string corresponding to the token supplied
|
||
|
*
|
||
|
* @rdesc
|
||
|
* returns a pointer to the keyword string if one exists
|
||
|
* and NULL otherwise
|
||
|
*/
|
||
|
CHAR *CRTFRead::PszKeywordFromToken(TOKEN token)
|
||
|
{
|
||
|
for(int i = 0; i < cKeywords; i++)
|
||
|
{
|
||
|
if(rgKeyword[i].token == token)
|
||
|
return rgKeyword[i].szKeyword;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CRTFRead::FTokIsSymbol(TOKEN tok)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Returns a BOOL indicating whether the token, tok, corresponds to an RTF symbol
|
||
|
* (that is, one of a list of single characters that are scanned in the
|
||
|
* RTF reader)
|
||
|
*
|
||
|
* @rdesc
|
||
|
* BOOL - indicates whether the token corresponds to an RTF symbol
|
||
|
*/
|
||
|
BOOL CRTFRead::FTokIsSymbol(TOKEN tok)
|
||
|
{
|
||
|
const BYTE *pbSymbol = NULL;
|
||
|
|
||
|
extern const BYTE szSymbolKeywords[];
|
||
|
extern const TOKEN tokenSymbol[];
|
||
|
|
||
|
// check list of individual symbols which are scanned
|
||
|
for(pbSymbol = szSymbolKeywords; *pbSymbol; pbSymbol++)
|
||
|
{
|
||
|
if(tokenSymbol[pbSymbol - szSymbolKeywords] == tok)
|
||
|
return TRUE;
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CRTFRead::FTokFailsCoverageTest(TOKEN tok)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Returns a BOOL indicating whether the token, tok, is known to fail the
|
||
|
* RTF parser coverage test. These tokens are those that have been checked
|
||
|
* and either:
|
||
|
* 1) have been implemented correctly, but just elude the coverage test
|
||
|
* 2) have yet to be implemented, and have been recognized as such
|
||
|
*
|
||
|
* @rdesc
|
||
|
* BOOL - indicates whether the token has been checked and fails the
|
||
|
* the parser coverage test for some known reason
|
||
|
*/
|
||
|
BOOL CRTFRead::FTokFailsCoverageTest(TOKEN tok)
|
||
|
{
|
||
|
switch(tok)
|
||
|
{
|
||
|
// (T & !S & !P) (1. in TestParserCoverage)
|
||
|
// these really aren't tokens per se, but signal ending conditions for the parse
|
||
|
case tokenError:
|
||
|
case tokenEOF:
|
||
|
|
||
|
// (T & !S & P) (2. in TestParserCoverage)
|
||
|
// emitted by scanner, but don't correspond to recognized RTF keyword
|
||
|
case tokenUnknownKeyword:
|
||
|
case tokenText:
|
||
|
case tokenASCIIText:
|
||
|
|
||
|
// recognized directly (before the scanner is called)
|
||
|
case tokenStartGroup:
|
||
|
case tokenEndGroup:
|
||
|
|
||
|
// recognized using context information (before the scanner is called)
|
||
|
case tokenObjectDataValue:
|
||
|
case tokenPictureDataValue:
|
||
|
|
||
|
// (T & S & !P) (3. in TestParserCoverage)
|
||
|
// None
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
#endif // DEBUG
|
||
|
|
||
|
/*
|
||
|
* CRTFLog::CRTFLog()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Constructor -
|
||
|
* 1. Opens a file mapping to log hit counts, creating
|
||
|
* the backing file if neccessary
|
||
|
* 2. Map a view of the file mapping into memory
|
||
|
* 3. Register a windows message for change notifications
|
||
|
*
|
||
|
*/
|
||
|
CRTFLog::CRTFLog() : _rgdwHits(NULL), _hfm(NULL), _hfile(NULL)
|
||
|
{
|
||
|
#ifndef NOFULLDEBUG
|
||
|
const char cstrMappingName[] = "RTFLOG";
|
||
|
const char cstrWM[] = "RTFLOGWM";
|
||
|
const int cbMappingSize = sizeof(ELEMENT) * ISize();
|
||
|
|
||
|
BOOL fNewFile = FALSE;
|
||
|
|
||
|
// Check for existing file mapping
|
||
|
if(!(_hfm = OpenFileMappingA(FILE_MAP_ALL_ACCESS,
|
||
|
TRUE,
|
||
|
cstrMappingName)))
|
||
|
{
|
||
|
// No existing file mapping
|
||
|
// Get the file with which to create the file mapping
|
||
|
// first, attempt to open an existing file
|
||
|
if(!(_hfile = CreateFileA(LpcstrLogFilename(),
|
||
|
GENERIC_READ | GENERIC_WRITE,
|
||
|
0,
|
||
|
NULL,
|
||
|
OPEN_EXISTING,
|
||
|
FILE_ATTRIBUTE_NORMAL,
|
||
|
NULL)))
|
||
|
{
|
||
|
// No existing file, attempt to create new
|
||
|
if(!(_hfile = CreateFileA(LpcstrLogFilename(),
|
||
|
GENERIC_READ | GENERIC_WRITE,
|
||
|
0,
|
||
|
NULL,
|
||
|
OPEN_ALWAYS,
|
||
|
FILE_ATTRIBUTE_NORMAL,
|
||
|
NULL)))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
fNewFile = TRUE;
|
||
|
}
|
||
|
|
||
|
_hfm = CreateFileMappingA(_hfile, NULL, PAGE_READWRITE, 0,
|
||
|
cbMappingSize, cstrMappingName);
|
||
|
if(!_hfm)
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
LPVOID lpv = MapViewOfFile(_hfm, FILE_MAP_ALL_ACCESS, 0, 0, cbMappingSize);
|
||
|
if(!lpv)
|
||
|
return;
|
||
|
|
||
|
// Register windows message for change notifications
|
||
|
SideAssert(_uMsg = RegisterWindowMessageA(cstrWM));
|
||
|
|
||
|
// Memory-mapped file is now mapped to _rgdwHits
|
||
|
_rgdwHits = (PELEMENT)lpv;
|
||
|
|
||
|
// Zero the memory-mapped file if we created it new
|
||
|
// (Win95 gives us a new file w/ garbage in it for some reason)
|
||
|
if(fNewFile)
|
||
|
Reset();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* CRTFLog::Reset()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Resets the hitcount of each element in the log to 0
|
||
|
*
|
||
|
*/
|
||
|
void CRTFLog::Reset()
|
||
|
{
|
||
|
if(!FInit())
|
||
|
return;
|
||
|
|
||
|
for(INDEX i = 0; i < ISize(); i++)
|
||
|
(*this)[i] = 0;
|
||
|
|
||
|
// notify clients of change
|
||
|
ChangeNotifyAll();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CRTFLog::UGetWindowMsg
|
||
|
*
|
||
|
* @mdesc
|
||
|
* Returns the window message id used for change notifications
|
||
|
*
|
||
|
* @rdesc
|
||
|
* UINT window message id
|
||
|
*
|
||
|
* @devnote
|
||
|
* This should be inline, but the AssertSz macro doesn't compile
|
||
|
* properly on the Mac if its placed in a header file
|
||
|
*
|
||
|
*/
|
||
|
UINT CRTFLog::UGetWindowMsg() const
|
||
|
{
|
||
|
AssertSz(FInit(), "CRTFLog::UGetWindowMsg(): CRTFLog not initialized properly");
|
||
|
|
||
|
return _uMsg;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CRTFLog::operator[]
|
||
|
*
|
||
|
* @mdesc
|
||
|
* Returns reference to element i of RTF log (l-value)
|
||
|
*
|
||
|
* @rdesc
|
||
|
* ELEMENT & reference to element i of log
|
||
|
*
|
||
|
* @devnote
|
||
|
* This should be inline, but the AssertSz macro doesn't compile
|
||
|
* properly on the Mac if its placed in a header file
|
||
|
*
|
||
|
*/
|
||
|
CRTFLog::ELEMENT &CRTFLog::operator[](INDEX i)
|
||
|
{
|
||
|
AssertSz(i < ISize(), "CRTFLog::operator[]: index out of range");
|
||
|
AssertSz(FInit(), "CRTFLog::operator[]: CRTFLog not initialized properly");
|
||
|
|
||
|
return _rgdwHits[i];
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CRTFLog::operator[]
|
||
|
*
|
||
|
* @mdesc
|
||
|
* Returns reference to element i of RTF log (r-value)
|
||
|
*
|
||
|
* @rdesc
|
||
|
* const ELEMENT & reference to element i of log
|
||
|
*
|
||
|
* @devnote
|
||
|
* This should be inline, but the AssertSz macro doesn't compile
|
||
|
* properly on the Mac if its placed in a header file
|
||
|
*
|
||
|
*/
|
||
|
const CRTFLog::ELEMENT &CRTFLog::operator[](INDEX i) const
|
||
|
{
|
||
|
AssertSz(i < ISize(), "CRTFLog::operator[]: index out of range");
|
||
|
AssertSz(FInit(), "CRTFLog::operator[]: CRTFLog not initialized properly");
|
||
|
|
||
|
return _rgdwHits[i];
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* CRTFLog::LpcstrLogFilename()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Returns name of file to be used for log
|
||
|
*
|
||
|
* @rdesc
|
||
|
* LPCSTR pointer to static buffer containing file name
|
||
|
*/
|
||
|
LPCSTR CRTFLog::LpcstrLogFilename() const
|
||
|
{
|
||
|
static char szBuf[MAX_PATH] = "";
|
||
|
#ifndef NOFULLDEBUG
|
||
|
const char cstrLogFilename[] = "RTFLOG";
|
||
|
if(!szBuf[0])
|
||
|
{
|
||
|
DWORD cchLength;
|
||
|
char szBuf2[MAX_PATH];
|
||
|
|
||
|
SideAssert(cchLength = GetTempPathA(MAX_PATH, szBuf2));
|
||
|
|
||
|
// append trailing backslash if neccessary
|
||
|
if(szBuf2[cchLength - 1] != '\\')
|
||
|
{
|
||
|
szBuf2[cchLength] = '\\';
|
||
|
szBuf2[cchLength + 1] = 0;
|
||
|
}
|
||
|
|
||
|
wsprintfA(szBuf, "%s%s", szBuf2, cstrLogFilename);
|
||
|
}
|
||
|
#endif
|
||
|
return szBuf;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* CRTFLog::IIndexOfKeyword(LPCSTR lpcstrKeyword, PINDEX piIndex)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Returns the index of the log element which corresponds to
|
||
|
* the RTF keyword, lpcstrKeyword
|
||
|
*
|
||
|
* @rdesc
|
||
|
* BOOL flag indicating whether index was found
|
||
|
*/
|
||
|
BOOL CRTFLog::IIndexOfKeyword(LPCSTR lpcstrKeyword, PINDEX piIndex) const
|
||
|
{
|
||
|
INDEX i;
|
||
|
|
||
|
for(i = 0; i < ISize(); i++)
|
||
|
{
|
||
|
if(strcmp(lpcstrKeyword, rgKeyword[i].szKeyword) == 0)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if(i == ISize())
|
||
|
return FALSE;
|
||
|
|
||
|
if(piIndex)
|
||
|
*piIndex = i;
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* CRTFLog::IIndexOfToken(TOKEN token, PINDEX piIndex)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Returns the index of the log element which corresponds to
|
||
|
* the RTF token, token
|
||
|
*
|
||
|
* @rdesc
|
||
|
* BOOL flag indicating whether index was found
|
||
|
*/
|
||
|
BOOL CRTFLog::IIndexOfToken(TOKEN token, PINDEX piIndex) const
|
||
|
{
|
||
|
INDEX i;
|
||
|
|
||
|
for(i = 0; i < ISize(); i++)
|
||
|
{
|
||
|
if(token == rgKeyword[i].token)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if(i == ISize())
|
||
|
return FALSE;
|
||
|
|
||
|
if(piIndex)
|
||
|
*piIndex = i;
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|