//============================================================================= // 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; }