windows-nt/Source/XPSP1/NT/enduser/troubleshoot/msinfo/tmplfile.cpp
2020-09-26 16:20:57 +08:00

1230 lines
36 KiB
C++

//=============================================================================
// File: tmplfile.cpp
// Author: a-jammar
// Covers: CTemplateFileFunctions
//
// Copyright (c) 1998-1999 Microsoft Corporation
//
// This file contains the functions necessary to read in the MSInfo template
// file. The template file is a text file with an NFT extension. Reading
// multiple template files is allowed - the contents are merged together into
// the category tree maintained by the snap-in.
//=============================================================================
#include "stdafx.h"
#include "gather.h"
#include "gathint.h"
//-----------------------------------------------------------------------------
// This function is the main entry point for reading the template file into
// the category structure used by the CDataGatherer object. If there is no
// tree when this function is called, the tree is created from the contents
// of the file. If there is a tree already in place, then the contents of the
// file are loaded under the existing root node.
//-----------------------------------------------------------------------------
BOOL CTemplateFileFunctions::ReadTemplateFile(CFile *pFile, CDataGatherer *pGatherer)
{
ASSERT(pFile && pGatherer);
if (!VerifyUNICODEFile(pFile))
{
TRACE0("-- CTemplateFileFunctions::ReadTemplateFile() passed non-UNICODE file\n");
return FALSE;
}
if (!ReadHeaderInfo(pFile, pGatherer))
{
TRACE0("-- CTemplateFileFunctions::ReadTemplateFile() failed from ReadHeaderInfo\n");
return FALSE;
}
if (pGatherer->m_dwRootID)
{
// There is already a tree present. Insert the contents of the file under
// the root node, after the last first level node. Walk through the first
// level of the internal category tree. TBD: add a way to extend a specified node
INTERNAL_CATEGORY *pInternal = pGatherer->GetInternalRep(pGatherer->m_dwRootID);
ASSERT(pInternal);
if (pInternal)
{
pInternal = pGatherer->GetInternalRep(pInternal->m_dwChildID);
while (pInternal && pInternal->m_dwNextID)
pInternal = pGatherer->GetInternalRep(pInternal->m_dwNextID);
}
DWORD dwPrevID = (pInternal) ? pInternal->m_dwID : 0;
if (ReadNodeRecursive(pFile, pGatherer, pGatherer->m_dwRootID, dwPrevID) == 0)
return FALSE;
return TRUE;
}
else
{
pGatherer->m_dwRootID = ReadNodeRecursive(pFile, pGatherer, 0, 0);
if (pGatherer->m_dwRootID == 0)
return FALSE;
return TRUE;
}
}
//-----------------------------------------------------------------------------
// This method reads the header information from the file before the recursive
// category descriptions. Note: since this is the first and only version of
// the template file, we take the easy way out and just make sure that the
// identifier and version are there.
//-----------------------------------------------------------------------------
BOOL CTemplateFileFunctions::ReadHeaderInfo(CFile *pFile, CDataGatherer * /* pGatherer */)
{
return VerifyAndAdvanceFile(pFile, CString(_T(TEMPLATE_FILE_TAG)));
}
//-----------------------------------------------------------------------------
// This method verifies that the passed file is a UNICODE file, by reading the
// value 0xFEFF from the file. It also leaves the file pointer past this word.
//-----------------------------------------------------------------------------
BOOL CTemplateFileFunctions::VerifyUNICODEFile(CFile *pFile)
{
WORD verify;
if (pFile->Read((void *) &verify, sizeof(WORD)) != sizeof(WORD))
{
TRACE0("-- CTemplateFileFunctions::VerifyUNICODEFile() couldn't read WORD\n");
return FALSE;
}
return (verify == 0xFEFF);
}
//-----------------------------------------------------------------------------
// This is the recursive function to read a node. It reads the information
// from the node parameters, creates the node, and processes the contents of
// the block following the node (contained within "{}"'s). It's called
// recursively if there are any nodes in that block.
//-----------------------------------------------------------------------------
DWORD CTemplateFileFunctions::ReadNodeRecursive(CFile *pFile, CDataGatherer *pGatherer, DWORD dwParentID, DWORD dwPrevID)
{
// Determine if we need to create a new category for this node. Search through the
// other sibling nodes to see if there is one which matches this category's name.
// If there is, just use that category, and don't create a new one. Read the
// information from the file to determine the identifier for the new category.
CString strEnumerateClass, strIdentifier;
if (!VerifyAndAdvanceFile(pFile, CString(NODE_KEYWORD) + CString("(")))
{
TRACE0("-- CTemplateFileFunctions::ReadNodeRecursive() Verify.. failed on node keyword\n");
return 0;
}
if (!ReadArgument(pFile, strEnumerateClass))
{
TRACE0("-- CTemplateFileFunctions::ReadNodeRecursive() ReadArgument failed on enumerate class\n");
return 0;
}
if (!ReadArgument(pFile, strIdentifier))
{
TRACE0("-- CTemplateFileFunctions::ReadNodeRecursive() ReadArgument failed on identifier\n");
return 0;
}
// Look for a node among the siblings which has a matching strIdentifier.
INTERNAL_CATEGORY * pInternal;
DWORD dwSearchID = dwPrevID, dwMatchingID = 0;
while (dwMatchingID == 0 && dwSearchID != 0)
{
pInternal = pGatherer->GetInternalRep(dwSearchID);
if (pInternal)
{
if (pInternal->m_strIdentifier.CompareNoCase(strIdentifier) == 0)
dwMatchingID = dwSearchID;
dwSearchID = pInternal->m_dwPrevID;
}
else
{
ASSERT(pInternal);
break;
}
}
INTERNAL_CATEGORY * pCategory = ((dwMatchingID) ? pInternal : NULL);
DWORD dwID = dwMatchingID;
if (pCategory == NULL)
{
// Create the category for the node.
dwID = CreateCategory(pGatherer, dwParentID, dwPrevID);
pCategory = pGatherer->GetInternalRep(dwID);
if (!pCategory)
return 0;
// Read the contents of the node argument list ("node(enum, identifier, field(source, formatstr, arg...))")
// We've already read up to and including the identifier.
pCategory->m_strEnumerateClass = strEnumerateClass;
pCategory->m_strIdentifier = strIdentifier;
if (!ReadField(pFile, pCategory->m_fieldName))
return 0;
// Copy the field name to the name of the category (they are two different
// member variables to allow for dynamically refreshed names, which turns
// out to be unnecessary in this version).
pCategory->m_categoryName.m_strText = pCategory->m_fieldName.m_strFormat;
if (!ReadArgument(pFile, pCategory->m_strNoInstances))
{
TRACE0("-- CTemplateFileFunctions::ReadNodeRecursive() ReadArgument failed on no instance message\n");
return 0;
}
}
else
{
// This node already existed, and we just want to read past the rest of
// it's description without changing the existing node.
GATH_FIELD fieldTemp;
CString strTemp;
if (!ReadField(pFile, fieldTemp))
return 0;
if (!ReadArgument(pFile, strTemp))
{
TRACE0("-- CTemplateFileFunctions::ReadNodeRecursive() ReadArgument failed on no instance message\n");
return 0;
}
}
if (!VerifyAndAdvanceFile(pFile, CString("){")))
{
TRACE1("-- CTemplateFileFunctions::ReadNodeRecursive() Verify.. failed on node \"){\" (%s)\n", pCategory->m_strIdentifier);
return 0;
}
// Process the contents of the block (enclosed in "{}") for this node.
DWORD dwSubNodePrev = 0, dwNewNode = 0;
CString strKeyword;
// If this new category isn't actually new (i.e. it is being read from a
// template and overlaps an existing category) see if there are any
// existing children.
if (pCategory->m_dwChildID)
{
pInternal = pGatherer->GetInternalRep(pCategory->m_dwChildID); ASSERT(pInternal);
while (pInternal && pInternal->m_dwNextID)
pInternal = pGatherer->GetInternalRep(pInternal->m_dwNextID);
if (pInternal)
dwSubNodePrev = pInternal->m_dwID;
}
while (GetKeyword(pFile, strKeyword))
{
if (strKeyword.CompareNoCase(CString(NODE_KEYWORD)) == 0)
{
dwNewNode = ReadNodeRecursive(pFile, pGatherer, dwID, dwSubNodePrev);
if (dwNewNode == 0)
return 0;
// If this is the first child node we've read, save its ID.
if (pCategory->m_dwChildID == 0)
pCategory->m_dwChildID = dwNewNode;
// If we've read another child node, set its next field appropriately.
if (dwSubNodePrev)
{
INTERNAL_CATEGORY * pPrevCategory = pGatherer->GetInternalRep(dwSubNodePrev);
if (pPrevCategory)
pPrevCategory->m_dwNextID = dwNewNode;
}
dwSubNodePrev = dwNewNode;
}
else if (strKeyword.CompareNoCase(CString(COLUMN_KEYWORD)) == 0)
{
if (!ReadColumnInfo(pFile, pGatherer, dwID))
{
TRACE0("-- CTemplateFileFunctions::ReadNodeRecursive() failed on ReadColumnInfo\n");
return FALSE;
}
}
else if (strKeyword.CompareNoCase(CString(LINE_KEYWORD)) == 0)
{
GATH_LINESPEC * pNewLineSpec = ReadLineInfo(pFile, pGatherer);
if (pNewLineSpec == NULL)
{
TRACE0("-- CTemplateFileFunctions::ReadNodeRecursive() failed on ReadLineInfo\n");
return FALSE;
}
// Add the line we just read in to the end of the list of line specs for this
// internal category.
if (pCategory->m_pLineSpec == NULL)
pCategory->m_pLineSpec = pNewLineSpec;
else
{
GATH_LINESPEC * pLineSpec = pCategory->m_pLineSpec;
while (pLineSpec->m_pNext)
pLineSpec = pLineSpec->m_pNext;
pLineSpec->m_pNext = pNewLineSpec;
}
}
else if (strKeyword.CompareNoCase(CString(ENUMLINE_KEYWORD)) == 0)
{
GATH_LINESPEC * pNewLineSpec = ReadLineEnumRecursive(pFile, pGatherer);
if (pNewLineSpec == NULL)
{
TRACE0("-- CTemplateFileFunctions::ReadNodeRecursive() failed on ReadLineEnumRecursive\n");
return FALSE;
}
// Add the line we just read in to the end of the list of line specs for this
// internal category.
if (pCategory->m_pLineSpec == NULL)
pCategory->m_pLineSpec = pNewLineSpec;
else
{
GATH_LINESPEC * pLineSpec = pCategory->m_pLineSpec;
while (pLineSpec->m_pNext)
pLineSpec = pLineSpec->m_pNext;
pLineSpec->m_pNext = pNewLineSpec;
}
//if (!ReadLineEnumRecursive(pFile, pGatherer, dwID))
//{
// TRACE0("CTemplateFileFunctions::ReadNodeRecursive() failed on ReadLineEnumRecursive\n");
// return FALSE;
//}
}
else
{
ASSERT(FALSE);
VerifyAndAdvanceFile(pFile, strKeyword);
}
}
if (!VerifyAndAdvanceFile(pFile, CString("}")))
{
TRACE0("CTemplateFileFunctions::ReadNodeRecursive() Verify.. failed on \"}\"\n");
return 0;
}
return dwID;
}
//-----------------------------------------------------------------------------
// This method verifies that the text in strVerify comes next in the file (not
// including case or whitespace differences) and advances the file past that
// text. If the text was the next content in the file, TRUE is returned,
// otherwise FALSE. If FALSE is returned, the file is backed up to where it
// was when this method was called.
//-----------------------------------------------------------------------------
BOOL CTemplateFileFunctions::VerifyAndAdvanceFile(CFile * pFile, const CString &strVerify)
{
DWORD dwPosition = pFile->GetPosition();
TCHAR cLastChar, cCurrentChar = _T('\0');
BOOL fInComment = FALSE;
int iCharIndex = 0, iStringLen = strVerify.GetLength();
while (iCharIndex < iStringLen)
{
// Save the last character read, since the comment token ("//") is
// two characters long.
cLastChar = cCurrentChar;
// Read the next character in the file.
if (pFile->Read((void *) &cCurrentChar, sizeof(TCHAR)) != sizeof(TCHAR))
{
TRACE0("-- CTemplateFileFunctions::VerifyAndAdvanceFile() couldn't read character\n");
return FALSE;
}
// If we're in a comment, and the character we just read isn't a new line,
// we want to ignore it.
if (fInComment)
{
if (cCurrentChar == _T('\n'))
fInComment = FALSE;
continue;
}
// Check to see if we've started into a comment. Note that we ignore
// the first '/' also by continuing.
if (cCurrentChar == _T('/'))
{
if (cLastChar == _T('/'))
fInComment = TRUE;
continue;
}
// Skip whitespace, and also leading commas.
if (_istspace(cCurrentChar) || (cCurrentChar == _T(',') && iCharIndex == 0))
continue;
if (cCurrentChar != strVerify[iCharIndex])
{
pFile->Seek((LONG)dwPosition, CFile::begin);
return FALSE;
}
iCharIndex++;
}
return TRUE;
}
//-----------------------------------------------------------------------------
// Create a new category, and return the ID for the category.
//-----------------------------------------------------------------------------
DWORD CTemplateFileFunctions::CreateCategory(CDataGatherer * pGatherer, DWORD dwParentID, DWORD dwPrevID)
{
DWORD dwID = pGatherer->m_dwNextFreeID;
INTERNAL_CATEGORY * pInternalCat;
INTERNAL_CATEGORY * pPreviousCat;
CString strName;
pInternalCat = new INTERNAL_CATEGORY;
if (!pInternalCat)
return 0;
pInternalCat->m_fListView = TRUE;
pInternalCat->m_dwID = dwID;
pInternalCat->m_dwParentID = dwParentID;
pInternalCat->m_dwPrevID = dwPrevID;
if (dwPrevID)
{
pPreviousCat = pGatherer->GetInternalRep(dwPrevID);
if (pPreviousCat)
pPreviousCat->m_dwNextID = dwID;
}
pGatherer->m_mapCategories.SetAt((WORD)dwID, (void *) pInternalCat);
pGatherer->m_dwNextFreeID++;
return dwID;
}
//-----------------------------------------------------------------------------
// This method returns the next keyword in the file. Any whitespace or
// punctuation is skipped until an alphanumeric character is read. The keyword
// returned is the string starting with this character until whitespace or
// punctuation is encountered. Note: it is very important that this function
// returns the file to the state it was in when the function started, with
// the current position restored.
//
// TBD: inefficient
//-----------------------------------------------------------------------------
BOOL CTemplateFileFunctions::GetKeyword(CFile * pFile, CString & strKeyword)
{
CString strTemp = CString("");
DWORD dwPosition = pFile->GetPosition();
TCHAR cLastChar, cCurrentChar = _T('\0');
BOOL fInComment = FALSE;
// Skip over whitespace characters until we reach an alphanumeric char.
do
{
// Save the last character read, since the comment token ("//") is
// two characters long.
cLastChar = cCurrentChar;
// Read the next character in the file.
if (pFile->Read((void *) &cCurrentChar, sizeof(TCHAR)) != sizeof(TCHAR))
return FALSE;
// If we're in a comment, and the character we just read isn't a new line,
// we want to ignore it.
if (fInComment)
{
if (cCurrentChar == _T('\n'))
fInComment = FALSE;
continue;
}
// Check to see if we've started into a comment.
if (cCurrentChar == _T('/'))
{
if (cLastChar == _T('/'))
fInComment = TRUE;
continue;
}
} while (_istspace(cCurrentChar) || cCurrentChar == _T('/') || fInComment);
// Read the keyword while it's alphanumeric.
if (_istalnum(cCurrentChar))
do
{
strTemp += CString(cCurrentChar);
if (pFile->Read((void *) &cCurrentChar, sizeof(TCHAR)) != sizeof(TCHAR))
return FALSE;
} while (_istalnum(cCurrentChar));
// Reset the file, set the keyword and return.
pFile->Seek((LONG)dwPosition, CFile::begin);
strKeyword = strTemp;
return !strTemp.IsEmpty();
}
//-----------------------------------------------------------------------------
// This method reads in a "column" line from the file, adding the appropriate
// entries for the columns into the category referenced by dwID. The column
// line contains a bunch of fields in a list.
//-----------------------------------------------------------------------------
BOOL CTemplateFileFunctions::ReadColumnInfo(CFile * pFile, CDataGatherer * pGatherer, DWORD dwID)
{
CString strTemp;
if (!VerifyAndAdvanceFile(pFile, CString(COLUMN_KEYWORD) + CString("(")))
{
TRACE0("CTemplateFileFunctions::ReadColumnInfo() Verify.. failed on column keyword\n");
return FALSE;
}
// Get the internal category referenced by dwID.
INTERNAL_CATEGORY * pCategory = pGatherer->GetInternalRep(dwID);
if (!pCategory)
return FALSE;
// We only allow one column specifier list per node.
if (pCategory->m_pColSpec)
{
TRACE0("CTemplateFileFunctions::ReadColumnInfo() already column information present\n");
return FALSE;
}
// While we are still reading fields from the file, keep adding to the column list.
GATH_FIELD * pNewField = new GATH_FIELD;
if (pNewField == NULL)
return FALSE;
while (ReadField(pFile, *pNewField))
{
if (pCategory->m_pColSpec == NULL)
pCategory->m_pColSpec = pNewField;
else
{
// Scan to the last field in the linespec.m_pFields list, and insert the new field.
GATH_FIELD * pFieldScan = pCategory->m_pColSpec;
while (pFieldScan->m_pNext)
pFieldScan = pFieldScan->m_pNext;
pFieldScan->m_pNext = pNewField;
}
// Parse the width out of the column caption.
if (pNewField->m_strFormat.ReverseFind(_T(',')) != -1)
{
strTemp = pNewField->m_strFormat.Right(pNewField->m_strFormat.GetLength() - pNewField->m_strFormat.ReverseFind(_T(',')) - 1);
pNewField->m_usWidth = (unsigned short) atoi(strTemp);
pNewField->m_strFormat = pNewField->m_strFormat.Left(pNewField->m_strFormat.GetLength() - strTemp.GetLength() - 1);
}
else
{
ASSERT(FALSE);
pNewField->m_usWidth = (unsigned short) 80;
}
// Parse off any remaining information in the column label (the label ends
// with [name, n], when n is the width, and name is the ID for the column
// which should not be displayed).
if (pNewField->m_strFormat.ReverseFind(_T('[')) != -1)
pNewField->m_strFormat = pNewField->m_strFormat.Left(pNewField->m_strFormat.ReverseFind(_T('[')) - 1);
// Read the sorting type from the file.
if (ReadArgument(pFile, strTemp))
{
if (strTemp.CompareNoCase(CString(_T(SORT_LEXICAL))) == 0)
pNewField->m_sort = LEXICAL;
else if (strTemp.CompareNoCase(CString(_T(SORT_VALUE))) == 0)
pNewField->m_sort = BYVALUE;
else
pNewField->m_sort = NOSORT;
}
else
{
TRACE0("CTemplateFileFunctions::ReadColumnInfo() couldn't read column sorting\n");
return FALSE;
}
// Read the complexity (BASIC or ADVANCED) from the file.
if (ReadArgument(pFile, strTemp))
{
if (strTemp.CompareNoCase(CString(_T(COMPLEXITY_ADVANCED))) == 0)
pNewField->m_datacomplexity = ADVANCED;
else
pNewField->m_datacomplexity = BASIC;
}
else
{
TRACE0("CTemplateFileFunctions::ReadColumnInfo() couldn't read data complexity\n");
return FALSE;
}
pNewField = new GATH_FIELD;
if (pNewField == NULL)
return FALSE;
}
delete pNewField;
if (!VerifyAndAdvanceFile(pFile, CString(")")))
{
TRACE0("CTemplateFileFunctions::ReadColumnInfo() Verify.. failed on \")\"\n");
return FALSE;
}
return TRUE;
}
//-----------------------------------------------------------------------------
// Read in the information for a single line. Add the line to the internal
// representation of the category. TBD: inefficient, since this will be
// called multiple times and the line list will need to be scanned to the
// end each time.
//-----------------------------------------------------------------------------
GATH_LINESPEC * CTemplateFileFunctions::ReadLineInfo(CFile * pFile, CDataGatherer * /* pGatherer */)
{
if (!VerifyAndAdvanceFile(pFile, CString(LINE_KEYWORD) + CString("(")))
{
TRACE0("CTemplateFileFunctions::ReadLineInfo() Verify.. failed on line keyword\n");
return NULL;
}
// Declare a line specification variable to store the line info.
GATH_LINESPEC * pNewLineSpec = new GATH_LINESPEC;
if (pNewLineSpec == NULL)
return NULL;
// While we are still reading fields from the file, keep adding to the column list.
// TBD: inefficient, repeated scans through linespec.m_pFields list.
GATH_FIELD * pNewField = new GATH_FIELD;
if (pNewField == NULL)
{
delete pNewLineSpec;
return NULL;
}
// Read in the complexity (BASIC or ADVANCED) for this line.
CString strTemp;
if (ReadArgument(pFile, strTemp))
{
if (strTemp.CompareNoCase(CString(_T(COMPLEXITY_ADVANCED))) == 0)
pNewLineSpec->m_datacomplexity = ADVANCED;
else
pNewLineSpec->m_datacomplexity = BASIC;
}
else
{
TRACE0("CTemplateFileFunctions::ReadLineInfo() couldn't read complexity\n");
return FALSE;
}
while (ReadField(pFile, *pNewField))
{
if (pNewLineSpec->m_pFields == NULL)
pNewLineSpec->m_pFields = pNewField;
else
{
// Scan to the last field in the linespec.m_pFields list, and insert the new field.
GATH_FIELD * pFieldScan = pNewLineSpec->m_pFields;
while (pFieldScan->m_pNext)
pFieldScan = pFieldScan->m_pNext;
pFieldScan->m_pNext = pNewField;
}
pNewField = new GATH_FIELD;
if (pNewField == NULL)
{
delete pNewLineSpec;
return NULL;
}
}
delete pNewField;
if (!VerifyAndAdvanceFile(pFile, CString(")")))
{
TRACE0("CTemplateFileFunctions::ReadLineInfo() Verify.. failed on \")\"\n");
delete pNewLineSpec;
return NULL;
}
return pNewLineSpec;
}
//-----------------------------------------------------------------------------
// This method simply reads an argument (as string) from the file, until a
// punctuation or whitespace character is found. If a quote mark is found,
// all characters are included in the string until another quote is found.
// TBD: currently no way to have a quote mark in the string.
//-----------------------------------------------------------------------------
BOOL CTemplateFileFunctions::ReadArgument(CFile * pFile, CString & strSource)
{
BOOL fInQuote = FALSE, fInComment = FALSE;
CString strTemp;
TCHAR cLastChar, cCurrentChar = _T('\0');
// Skip over characters until we reach an alphanumeric char. If we find
// a close paren, then we've reached the end of the argument list and
// should return FALSE.
do
{
// Save the last character read, since the comment token ("//") is
// two characters long.
cLastChar = cCurrentChar;
// Read the next character in the file.
if (pFile->Read((void *) &cCurrentChar, sizeof(TCHAR)) != sizeof(TCHAR))
{
TRACE0("CTemplateFileFunctions::ReadArgument() couldn't read character\n");
return FALSE;
}
// If we're in a comment, and the character we just read isn't a new line,
// we want to ignore it.
if (fInComment)
{
if (cCurrentChar == _T('\n'))
fInComment = FALSE;
continue;
}
// Check to see if we've started into a comment.
if (cCurrentChar == _T('/'))
{
if (cLastChar == _T('/'))
fInComment = TRUE;
continue;
}
if (cCurrentChar == _T(')'))
return FALSE;
} while (!_istalnum(cCurrentChar) && cCurrentChar != _T('"'));
// Read characters into the string until we find whitespace or punctuation.
do
{
if (cCurrentChar == _T('"'))
{
fInQuote = !fInQuote;
continue;
}
if (_istalnum(cCurrentChar) || fInQuote)
strTemp += CString(cCurrentChar);
else
break;
} while (pFile->Read((void *) &cCurrentChar, sizeof(TCHAR)) == sizeof(TCHAR));
// If the last character read (the one which terminated this argument) was
// not a comma, then back the file up so that the character can be re-read
// and interpreted.
if (cCurrentChar != _T(','))
pFile->Seek(-(LONG)sizeof(TCHAR), CFile::current);
strSource = strTemp;
return TRUE;
}
//-----------------------------------------------------------------------------
// A field consists of a source string, followed by a format string, followed
// by a list of zero or more arguments.
//-----------------------------------------------------------------------------
BOOL CTemplateFileFunctions::ReadField(CFile * pFile, GATH_FIELD & field)
{
// Advance past the field keyword and read the two source and format strings.
if (!VerifyAndAdvanceFile(pFile, CString(FIELD_KEYWORD) + CString("(")))
return FALSE;
if (!ReadArgument(pFile, field.m_strSource))
{
TRACE0("CTemplateFileFunctions::ReadField() ReadArgument failed on source\n");
return FALSE;
}
if (!ReadArgument(pFile, field.m_strFormat))
{
TRACE0("CTemplateFileFunctions::ReadField() ReadArgument failed on format\n");
return FALSE;
}
// Read arguments until there are no more, building them into a list of
// arguments stored by the FIELD struct.
GATH_VALUE arg;
GATH_VALUE * pArg = NULL;
while (ReadArgument(pFile, arg.m_strText))
{
if (pArg == NULL)
{
field.m_pArgs = new GATH_VALUE;
if (field.m_pArgs == NULL)
{
TRACE0("CTemplateFileFunctions::ReadField() field.m_pArgs allocation failed\n");
return FALSE;
}
*field.m_pArgs = arg;
pArg = field.m_pArgs;
}
else
{
pArg->m_pNext = new GATH_VALUE;
if (pArg->m_pNext == NULL)
{
TRACE0("CTemplateFileFunctions::ReadField() pArg->m_pNext allocation failed\n");
return FALSE;
}
*pArg->m_pNext = arg;
pArg = pArg->m_pNext;
}
}
return TRUE;
}
//-----------------------------------------------------------------------------
// Read an enumline(){} block. This construct is used to group lines together
// which are enumerated for each instance of a class. A line is added to
// the parent node's list of lines with a m_strEnumerateClass equal to the
// class to be enumerated. The added line structure will have children lines
// (the lines to be enumerated) referenced by m_pEnumeratedGroup.
//-----------------------------------------------------------------------------
GATH_LINESPEC * CTemplateFileFunctions::ReadLineEnumRecursive(CFile * pFile, CDataGatherer * pGatherer)
{
if (!VerifyAndAdvanceFile(pFile, CString(ENUMLINE_KEYWORD) + CString("(")))
{
TRACE0("CTemplateFileFunctions::ReadLineEnumRecursive() Verify.. failed on enum line keyword\n");
return NULL;
}
// Declare a line specification variable to store the line info.
GATH_LINESPEC * pNewLineSpec = new GATH_LINESPEC;
if (pNewLineSpec == NULL)
return NULL;
// Read in the enumerated class variable.
if (!ReadArgument(pFile, pNewLineSpec->m_strEnumerateClass))
{
delete pNewLineSpec;
TRACE0("CTemplateFileFunctions::ReadLineEnumRecursive() ReadArgument failed on enumerate class\n");
return NULL;
}
// Read in the variable (zero or more) number of fields for the constraints.
GATH_FIELD * pNewField = new GATH_FIELD;
if (pNewField == NULL)
return NULL;
while (ReadField(pFile, *pNewField))
{
if (pNewLineSpec->m_pConstraintFields == NULL)
pNewLineSpec->m_pConstraintFields = pNewField;
else
{
// Add the newly read field to the end of the field list. Note,
// this is inefficient, and should be fixed. (TBD)
GATH_FIELD * pFieldScan = pNewLineSpec->m_pConstraintFields;
while (pFieldScan->m_pNext)
pFieldScan = pFieldScan->m_pNext;
pFieldScan->m_pNext = pNewField;
}
pNewField = new GATH_FIELD;
if (pNewField == NULL)
return NULL;
}
delete pNewField;
// Advance past the close paren and the (necessary) open bracket.
if (!VerifyAndAdvanceFile(pFile, CString("){")))
{
TRACE0("CTemplateFileFunctions::ReadLineEnumRecursive() Verify.. failed on \"){\"\n");
delete pNewLineSpec;
return NULL;
}
// Read the contents of the block (should be all lines or enumlines).
CString strKeyword;
while (GetKeyword(pFile, strKeyword))
{
if (strKeyword.CompareNoCase(CString(LINE_KEYWORD)) == 0)
{
GATH_LINESPEC * pNewSubLine = ReadLineInfo(pFile, pGatherer);
if (pNewSubLine == NULL)
{
delete pNewLineSpec;
return NULL;
}
if (pNewLineSpec->m_pEnumeratedGroup == NULL)
pNewLineSpec->m_pEnumeratedGroup = pNewSubLine;
else
{
GATH_LINESPEC * pLineSpec = pNewLineSpec->m_pEnumeratedGroup;
while (pLineSpec->m_pNext)
pLineSpec = pLineSpec->m_pNext;
pLineSpec->m_pNext = pNewSubLine;
}
}
else if (strKeyword.CompareNoCase(CString(ENUMLINE_KEYWORD)) == 0)
{
GATH_LINESPEC * pNewSubLine = ReadLineEnumRecursive(pFile, pGatherer);
if (pNewSubLine == NULL)
{
delete pNewLineSpec;
return NULL;
}
if (pNewLineSpec->m_pEnumeratedGroup == NULL)
pNewLineSpec->m_pEnumeratedGroup = pNewSubLine;
else
{
GATH_LINESPEC * pLineSpec = pNewLineSpec->m_pEnumeratedGroup;
while (pLineSpec->m_pNext)
pLineSpec = pLineSpec->m_pNext;
pLineSpec->m_pNext = pNewSubLine;
}
}
else
{
TRACE0("CTemplateFileFunctions::ReadLineEnumRecursive(), bad keyword in enumlines block\n");
delete pNewLineSpec;
return NULL;
}
}
if (!VerifyAndAdvanceFile(pFile, CString("}")))
{
TRACE0("CTemplateFileFunctions::ReadLineEnumRecursive() Verify.. failed on \"}\"\n");
delete pNewLineSpec;
return NULL;
}
return pNewLineSpec;
}
//-----------------------------------------------------------------------------
// This function is used to adjust the tree of loaded categories based on
// a string (which indicates what categories should be included). The
// following rules are applied:
//
// 1. By default, no categories are included.
// 2. If "+all" is in the string, all categories are included.
// 3. If "+cat" is in the string, the cat, all its children and ancestors
// are included.
// 4. If "-cat" is in the string, the cat and all its children are excluded.
//
// First this function must recurse through the tree, marking each node
// with whether it should be deleted or not. Then the nodes are actually
// removed from the tree. Yippee skip.
//-----------------------------------------------------------------------------
BOOL CTemplateFileFunctions::ApplyCategories(const CString & strCategories, CDataGatherer * pGatherer)
{
CString strLoweredCats(strCategories);
strLoweredCats.MakeLower();
BOOL fDefaultAdd = (strLoweredCats.Find(CString(_T("+all"))) > -1);
RecurseTreeCategories(fDefaultAdd, pGatherer->m_dwRootID, strLoweredCats, pGatherer);
RemoveExtraCategories(pGatherer->m_dwRootID, pGatherer);
return TRUE;
}
//-----------------------------------------------------------------------------
// Remove all the categories from the tree which aren't marked as "include".
//-----------------------------------------------------------------------------
void CTemplateFileFunctions::RemoveExtraCategories(DWORD dwID, CDataGatherer * pGatherer)
{
if (dwID == 0)
return;
INTERNAL_CATEGORY *pInternal = pGatherer->GetInternalRep(dwID);
if (pInternal == NULL)
return;
// If this category is not marked as included, delete it and
// all the children.
if (!pInternal->m_fIncluded)
{
DWORD dwChildID = pInternal->m_dwChildID;
DWORD dwNextChild = 0;
while (dwChildID)
{
INTERNAL_CATEGORY *pChild = pGatherer->GetInternalRep(dwChildID);
if (pChild)
dwNextChild = pChild->m_dwNextID;
else
dwNextChild = 0;
RemoveExtraCategories(dwChildID, pGatherer);
dwChildID = dwNextChild;
}
pGatherer->m_mapCategories.SetAt((WORD)pInternal->m_dwID, (void *) NULL);
delete pInternal;
return;
}
// Otherwise, if we are to save this category, scan through all the
// children, recursively calling this function on each one, and
// constructing a new list of children which are included.
INTERNAL_CATEGORY * pLastGood = NULL;
DWORD dwChildID = pInternal->m_dwChildID;
DWORD dwNextChild = 0;
while (dwChildID)
{
INTERNAL_CATEGORY *pChild = pGatherer->GetInternalRep(dwChildID);
if (pChild)
{
dwNextChild = pChild->m_dwNextID;
if (!pChild->m_fIncluded)
{
// We're removing this child. If this is the first child,
// set the pInternal field, otherwise, remove it from
// the list of children.
if (dwChildID == pInternal->m_dwChildID)
pInternal->m_dwChildID = dwNextChild;
else if (pLastGood) // this better be true
pLastGood->m_dwNextID = dwNextChild;
}
else
pLastGood = pChild;
RemoveExtraCategories(dwChildID, pGatherer);
}
else
dwNextChild = 0;
dwChildID = dwNextChild;
}
}
//-----------------------------------------------------------------------------
// This function recursively processes the categories to determine which
// ones should be included.
//-----------------------------------------------------------------------------
BOOL CTemplateFileFunctions::RecurseTreeCategories(BOOL fParentOK, DWORD dwID, const CString & strCategories, CDataGatherer * pGatherer)
{
if (dwID == 0)
return FALSE;
INTERNAL_CATEGORY *pInternal = pGatherer->GetInternalRep(dwID);
if (pInternal == NULL)
return FALSE;
// Default to using the same status as the parent category.
pInternal->m_fIncluded = fParentOK;
// If we are added or removed by the category string, change our status.
CString strCategoryID(pInternal->m_strIdentifier);
strCategoryID.MakeLower();
int iIndex = strCategories.Find(strCategoryID);
if (iIndex > 0)
{
// Make sure that we aren't matching part of a longer string,
// by making sure this is either the last string, or a + or -
// immediately follows.
if ((iIndex + strCategoryID.GetLength()) >= strCategories.GetLength() ||
strCategories[iIndex + strCategoryID.GetLength()] == _T('+') ||
strCategories[iIndex + strCategoryID.GetLength()] == _T('-'))
{
if (strCategories[iIndex - 1] == _T('+'))
pInternal->m_fIncluded = TRUE;
else if (strCategories[iIndex - 1] == _T('-'))
pInternal->m_fIncluded = FALSE;
}
}
// Now, for each child of this node, recurse using this node's status.
// If any of the children return TRUE for an included status, we must
// modify this node to TRUE.
DWORD dwChildID = pInternal->m_dwChildID;
BOOL fChildIncluded = FALSE;
while (dwChildID)
{
fChildIncluded |= RecurseTreeCategories(pInternal->m_fIncluded, dwChildID, strCategories, pGatherer);
INTERNAL_CATEGORY *pChild = pGatherer->GetInternalRep(dwChildID);
if (pChild)
dwChildID = pChild->m_dwNextID;
else
dwChildID = 0;
}
pInternal->m_fIncluded |= fChildIncluded;
return pInternal->m_fIncluded;
}
//-----------------------------------------------------------------------------
// This function is used to load template information from DLLs (the new
// method, to allow resources to be selected on the fly). The HKEY passed in
// is the base key for the entries which describe the DLLs containing template
// information. It's enumerated for subkeys, each of which is used to load a
// DLL. A standard entry point for the DLL is used, and the template
// information retrieved and passed into the file parsing functions.
//-----------------------------------------------------------------------------
typedef DWORD (__cdecl *pfuncGetTemplate)(void ** ppBuffer);
extern "C" DWORD __cdecl GetTemplate(void ** ppBuffer);
BOOL CTemplateFileFunctions::LoadTemplateDLLs(HKEY hkeyBase, CDataGatherer * pGatherer)
{
CStringList strlistTemplates;
// Add a keyword to the list of DLLs which indicates that we should add
// information from ourselves (we don't want to just add ourselves normally,
// since we would do a LoadLibrary on ourselves, which opens up can of
// unnecessary initializion worms). So, we'll just add "this" to the string list.
strlistTemplates.AddTail(_T("this"));
// Enumerate the registry key, adding each subkey to a list of DLL names to
// process (the DLL path is in the default value of the subkey).
if (hkeyBase)
{
TCHAR szName[64], szValue[MAX_PATH];
DWORD dwIndex = 0;
DWORD dwLength = sizeof(szName) / sizeof(TCHAR);
while (ERROR_SUCCESS == RegEnumKeyEx(hkeyBase, dwIndex++, szName, &dwLength, NULL, NULL, NULL, NULL))
{
dwLength = sizeof(szValue) / sizeof(TCHAR);
if (ERROR_SUCCESS == RegQueryValue(hkeyBase, szName, szValue, (long *)&dwLength))
if (*szValue)
strlistTemplates.AddTail(szValue);
dwLength = sizeof(szName) / sizeof(TCHAR);
}
}
// For each DLL in the list of templates, we'll attempt to get the template info.
CString strFileName;
HINSTANCE hinst;
DWORD dwBufferSize;
pfuncGetTemplate pfunc;
unsigned char * pBuffer;
CMemFile memfile;
while (!strlistTemplates.IsEmpty())
{
strFileName = strlistTemplates.RemoveHead();
// Try to load the library, and get a pointer to the entry point.
if (strFileName.Compare(_T("this")) == 0)
{
hinst = NULL;
pfunc = &GetTemplate;
}
else
{
hinst = LoadLibrary(strFileName);
if (hinst == NULL)
continue;
pfunc = (pfuncGetTemplate) GetProcAddress(hinst, "GetTemplate");
if (pfunc == NULL)
{
FreeLibrary(hinst);
continue;
}
}
// Call the DLL function with a NULL parameter to get the size of the buffer.
dwBufferSize = (*pfunc)((void **)&pBuffer);
if (dwBufferSize && pBuffer)
{
memfile.Attach((BYTE *)pBuffer, dwBufferSize, 0);
CTemplateFileFunctions::ReadTemplateFile(&memfile, pGatherer);
memfile.Detach();
(void)(*pfunc)(NULL); // calling the exported DLL function with NULL frees its buffers
}
if (hinst != NULL)
{
FreeLibrary(hinst);
hinst = NULL;
}
}
return TRUE;
}