|
|
/*****************************************************************************
* * * CTX.C * * * * Copyright (C) Microsoft Corporation 1990. * * All Rights reserved. * * * ****************************************************************************** * * * Module Intent * * This module handles context strings, including footnote processing * * and cross reference error checking. * * *****************************************************************************/
#include "stdafx.h"
#include "..\common\coutput.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__; #endif
const char CTX_DEFINED = '0'; const char CTX_REFERENCED = '1'; const char CTX_SEPARATOR = '@'; const char txtSameCtx[] = ">";
extern COutput* pLogFile;
INLINE static BOOL STDCALL IsCtxPrefix(PCSTR pszContext);
/*
* Module Algorithms * * This module has three entry points: one when context strings * are defined, one when they are referenced, and then one for error * checking all the links at the end. When context strings are * defined, the hash value and address pair is added to the context * btree. When context strings are defined or referenced, the * hash value, string, filename, and page number are all added to * a file, as well as whether it is a definition or a reference. When * error checking occurs, this file is sorted, and then entries are * read in. An error is reported whenever a context string is * referenced without being defined. * * Revision History * This functionality is completely different from what this * source file used to do. * */
/*****************************************************************************
* * * Defines * * * *****************************************************************************/
static char const txtCTXBtree[] = "|CONTEXT";
/*****************************************************************************
* * * Prototypes * * * *****************************************************************************/
BOOL STDCALL FCreateContextFiles(void) { BTREE_PARAMS bp;
// Create hash btree file
InitBtreeStruct(&bp, "L4", CB_CTX_BLOCK); // KT_LONG
fmsg.qbthrCtx = HbtCreateBtreeSz(txtCTXBtree, &bp);
// Create context info file. This may already have been done.
if (!ptblCtx) ptblCtx = new CTable; return TRUE; }
void STDCALL CloseContextBtree(void) { if (fmsg.qbthrCtx != NULL) { if (RcCloseBtreeHbt(fmsg.qbthrCtx) != RC_Success) { fmsg.qbthrCtx = NULL; } else fmsg.qbthrCtx = NULL; } }
/***************************************************************************
* - Name FProcContextSz - * Purpose * This function processes a context footnote. If it is given a * valid context string, it puts the address into the context btree, * indexed by a hash of the context string. It also enters a * definition entry into the context string cross reference file. * * Arguments * PSTR szContext: String found in context footnote * ADDR addr: Address for context string * PERR perr: Pointer to error information. * * Returns * TRUE if context string was valid. * * +++ * * Notes * Currently uses global fmsg. * ***************************************************************************/
// BUGBUG: nobody uses this
// HASH hPrev; // REVIEW: who uses this?
BOOL STDCALL FProcContextSz(PSTR pszContext, IDFCP idfcp, UINT wObjrg, PERR perr) { HASH hash; PSTR szMaster;
SzTrimSz(pszContext); if (*pszContext == '\0') { VReportError(HCERR_MISSING_CTX, &errHpj); return FALSE; } else if (!FValidContextSz(pszContext)) { VReportError(HCERR_INVALID_CTX, &errHpj, pszContext); return FALSE; }
hash = HashFromSz(pszContext);
// Remap if the hash value is in the [ALIAS] section. Note that
// apparently, viewer 2.0 doesn't do this anymore. 14-Oct-1993 [ralphw]
szMaster = SzTranslateHash(&hash);
// Add hash value into btree
ASSERT(fmsg.qbthrCtx != NULL); FDelayExecutionContext(hash, idfcp, wObjrg);
// Add context info to context info file
FRecordContext(hash, pszContext, szMaster, TRUE, perr); curHash = hash; // save for possible use in FTS processing
fContextSeen = TRUE;
doGrind(); // update grinder bitmap
return TRUE; }
/***************************************************************************
* - Name: FRecordContext - * Purpose: * Records a context string definition or reference, so that we * may later check to see if it was undefined. * * Strings are written out as: * hash szMaster fDefine pszContext pchFile iTopic * If szMaster is nil, pszContext is used instead. * * Arguments: * HASH hash: Translated hash value of context string. * PSTR pszContext: Context string as it appears in the text. * PSTR szMaster: Context string corresponding to given hash value, * if different from pszContext. * BOOL fDefine: TRUE for definition, FALSE for reference. * PERR perr: Pointer to error information. * * Returns: * * Globals Used: * Writes out info to fmsg. * ***************************************************************************/
BOOL STDCALL FRecordContext(HASH hash, PCSTR pszContext, PSTR pszMaster, BOOL fDefine, PERR perr) { if (!ptblCtx) ptblCtx = new CTable;
ASSERT(pszContext != NULL && pszContext[0] != '\0'); if (pszMaster == NULL) pszMaster = (PSTR) pszContext; ASSERT(pszMaster[0] != '\0'); ASSERT(perr->lpszFile != NULL && perr->lpszFile[0] != '\0');
char szBuf[1024]; ASSERT(strlen(pszMaster) < MAX_PATH);
// The CTX_SEPARATOR value must be used to separate strings. You can't
// use space because that would prevent using space for a Topic ID.
int iFile = ptblRtfFiles->IsStringInTable(perr->lpszFile);
if (iFile) wsprintf(szBuf, "%8lX@%s@%c@%s@%u@%u", hash, pszMaster, (char) (fDefine ? CTX_DEFINED : CTX_REFERENCED), strcmp(pszMaster, pszContext) == 0 ? txtSameCtx : pszContext, iFile, perr->iTopic); else wsprintf(szBuf, "%8lX@%s@%c@%s@%s@%u", hash, pszMaster, (char) (fDefine ? CTX_DEFINED : CTX_REFERENCED), strcmp(pszMaster, pszContext) == 0 ? txtSameCtx : pszContext, perr->lpszFile, perr->iTopic);
ASSERT(strchr(szBuf, CTX_SEPARATOR)); // Make sure you used CTX_SEPARATOR!
if (!ptblCtx->AddString(hash, szBuf)) OOM();
if (fDefine) hlpStats.cTopics++; else hlpStats.cJumps++; return TRUE; }
/***************************************************************************
* - Name: FResolveContextErrors - * Purpose: * This function goes through the list of context string definitions * and references, looking for multiple definitions, hash value conflicts, * and unresolved references. * * Arguments: * * Returns: * * Globals Used: * * +++ * * Notes: * ***************************************************************************/
const char txtSpaceEol[] = " \n";
BOOL STDCALL FResolveContextErrors(void) { PSTR pszMasterLast; PSTR pszHash, pszMaster, pszReference, pszContext, pszFile, pszTopic; CTable* ptblErrors = NULL; int pos;
ASSERT(ptblCtx != NULL);
if (!iflags.fTrusted) { CMem mem(CB_SCRATCH); // create a scratch buffer
CMem master(MAX_FOOTNOTE);
ptblCtx->SetTableSortColumn(sizeof(HASH) + 1); ptblCtx->SortTablei(); ptblCtx->RemoveDuplicateHashStrings();
#if 0 // Debugging code
{ COutput out("ctx.log");
int i = 1; while (i < ptblCtx->CountStrings()) out.outstring_eol(ptblCtx->GetPointer(i++) + sizeof(HASH) + 1); ptblCtx->SetPosition(); } #endif
pszMasterLast = master.psz; *pszMasterLast = '\0';
/*
* Note that we don't check for overflow of pszHash. This shouldn't * happen. If it does, it will corrupt the heap which will be caught * when the function terminates. */
// REVIEW: we now save the has number in the table...
HASH hash, hashLast = 0; while(ptblCtx->GetHashAndString(&hash, mem.psz)) {
/*
* REVIEW: We still save the hash string in order to sort the * hash strings. To avoid this we'd have to have a special sort * table that sorted both the DWORD hash value and the ensuing * string. */
pszHash = StrToken(mem.psz, CTX_SEPARATOR); pszMaster = StrToken(NULL, CTX_SEPARATOR); pszReference = StrToken(NULL, CTX_SEPARATOR); pszContext = StrToken(NULL, CTX_SEPARATOR); if (*pszContext == txtSameCtx[0]) pszContext = pszMaster; pszFile = StrToken(NULL, CTX_SEPARATOR); pszTopic = StrToken(NULL, CTX_SEPARATOR);
ASSERT(pszTopic != NULL); errHpj.lpszFile = isdigit(*pszFile) ? ptblRtfFiles->GetPointer(atoi(pszFile)) : pszFile; errHpj.iTopic = atoi(pszTopic);
if (hash == hashLast) {
// Case insensitive comparison of aliased context strings:
CStr cszMaster(pszMaster); CStr cszLast(pszMasterLast); StrUpper(cszMaster); StrUpper(cszLast);
if (strcmp(cszMaster, cszLast) != 0) { VReportError(HCERR_HASH_CONFLICT, &errHpj, pszMaster, pszMasterLast); if (*pszReference == CTX_REFERENCED) VReportError(HCERR_BAD_JUMP, &errHpj, pszContext); } else if (*pszReference == CTX_DEFINED) { UINT curpos = ptblCtx->GetPosition(); pos = curpos - 2; CMem memTmp(CB_SCRATCH);
// Find and whine about every duplicate
while (pos > 0) { HASH hashTmp; ptblCtx->GetHashAndString(&hashTmp, memTmp.psz, pos--); PSTR pszTmpHash = StrToken(memTmp.psz, CTX_SEPARATOR);
// We're done when we hit a different hash number
if (hash != hashTmp) break;
PSTR pszTmpMaster = StrToken(NULL, CTX_SEPARATOR); PSTR pszTmpReference = StrToken(NULL, CTX_SEPARATOR);
// Only complain if its a definition, not if its a jump
if (*pszTmpReference == CTX_DEFINED && hash == hashTmp && strcmp(pszMaster, pszTmpMaster) == 0) { PSTR pszTmpContext = StrToken(NULL, CTX_SEPARATOR); PSTR pszTmpFile = StrToken(NULL, CTX_SEPARATOR); PSTR pszTmpTopic = StrToken(NULL, CTX_SEPARATOR); VReportError(HCERR_DUPLICATE_CTX, &errHpj, pszContext, atoi(pszTmpTopic), pszTmpFile); break; } } ptblCtx->SetPosition(curpos); } } else { if (*pszReference == CTX_REFERENCED) VReportError(HCERR_BAD_JUMP, &errHpj, pszContext); else if (ptblMap && *pszContext == 'I' && *pszReference == CTX_DEFINED && strcmp(pszContext, pszMaster) == 0 && IsCtxPrefix(pszContext)) { if (!ptblMap->IsHashInTable(HashFromSz(pszContext))) { int ialias; QALIAS qalias; HASH hashMap = HashFromSz(pszContext); if (pdrgAlias && pdrgAlias->Count() > 0) { for (ialias = 0, qalias = (QALIAS) pdrgAlias->GetBasePtr(); ialias < pdrgAlias->Count(); ialias++, qalias++) {
// Look up address for alias in context btree
if (qalias->hashCtx == hashMap) goto AliasedCtx; } if (ialias < pdrgAlias->Count()) continue; // aliased, so not an error
}
if (!ptblErrors) ptblErrors = new CTable(); wsprintf(szParentString, "\t%s\tTopic %s of %s\r\n", pszContext, pszTopic, ptblRtfFiles->GetPointer(atoi(pszFile))); ptblErrors->AddString(szParentString); } } } AliasedCtx: hashLast = hash; strcpy(pszMasterLast, pszMaster); } }
delete ptblCtx; ptblCtx = NULL;
if (ptblErrors) { errHpj.ep = epNoFile; VReportError(HCERR_NOT_IN_MAP, &errHpj); ptblErrors->SortTable();
// Remove warning count from VReportError() and add real count
errcount.cWarnings--; errcount.cWarnings += ptblErrors->CountStrings();
for (pos = 1; pos <= ptblErrors->CountStrings(); pos++) { ptblErrors->GetString(szParentString, pos); SendStringToParent(szParentString); if (pLogFile) pLogFile->outstring(szParentString); if (pos % 10 == 0) doGrind(); } delete ptblErrors; }
return TRUE; }
/***************************************************************************
* - Name AddrGetContents - * Purpose * Gets the address of the contents topic. * * Arguments * HASH hash: Hash value of index topic. * * Returns * Address of contents topic. * * Globals Used: * This function uses the fmsg.qbthrCtx global to look up the * contents topic. * ***************************************************************************/
ADDR STDCALL AddrGetContents(PSTR pszContents) { ADDR addr = 0; // Actually, default is sizeof(MBHD)
// Get address of contents
if (pszContents) { ASSERT(fmsg.qbthrCtx != NULL); HASH hash = HashFromSz(pszContents); RC_TYPE rc = RcLookupByKey(fmsg.qbthrCtx, (KEY) &hash, NULL, &addr); if (rc == RC_NoExists) { VReportError(HCERR_CONTENTS_CTX_MISSING, &errHpj, pszContents); addr = 0; } #ifdef _DEBUG
else ASSERT(rc == RC_Success); // REVIEW: Is this true?
#endif
}
return addr; }
/***************************************************************************
* - Name: FOutAliasToCtxBtree - * Purpose: * This function takes each entry in the alias table, looks up the * address of the aliased topic, and enters this into the context * btree. * * Returns: * TRUE if successful, FALSE otherwise. * * Globals: * This function reads and writes values to fmsg.qbthrCtx. * ***************************************************************************/
BOOL STDCALL FOutAliasToCtxBtree(void) { RC_TYPE rc; ADDR addr; int ialias; QALIAS qalias;
ASSERT(fmsg.qbthrCtx != NULL);
if (pdrgAlias && pdrgAlias->Count() > 0) { for (ialias = 0, qalias = (QALIAS) pdrgAlias->GetBasePtr(); ialias < pdrgAlias->Count(); ialias++, qalias++) {
// Look up address for alias in context btree
rc = RcLookupByKey(fmsg.qbthrCtx, (KEY) &qalias->hashCtx, NULL, &addr); switch (rc) { case RC_Success:
// Put context string and address of alias into context btree
// REVIEW: Error check?
ASSERT(fmsg.qbthrCtx != NULL); RcInsertHbt(fmsg.qbthrCtx, (KEY) &qalias->hashAlias, &addr); break;
case RC_NoExists:
// Context string was not defined -- not an error here?
break;
default:
// REVIEW: Error message?
ASSERT(FALSE); } } }
return TRUE; }
/*-----------------------------------------------------------------------------
* VOID VOutCtxOffsetTable() * * Description: * This function outputs the Context-Offset table into the FS. It goes * through every context string defined in the map section of the project * file and finds the offset looking into the Topic-Offset Table. It * outputs the offset against the hashed context string. * * Table layout: * integer Count of Context strings present * Context ID -- Address * ..................... * ..................... * Context ID -- Address * * Returns; * NOTHING *-----------------------------------------------------------------------------*/
const char txtCTXOMAP[] = "|CTXOMAP"; // context map file name
void STDCALL VOutCtxOffsetTable(void) { QMAP qmap; ADDR addr; RC_TYPE rc; BOOL fNagged = FALSE; int count; #ifdef _DEBUG
int actual = 0; int cActualMap; #endif
// create the context map file
fmsg.hfCtxOMap = HfCreateFileHfs(hfsOut, txtCTXOMAP, 0);
// Write out size of map table.
int cmap = pdrgMap ? pdrgMap->Count() : 0; int imap;
/*
* If we're compressing, then we run the map entries first to get a * count of valid entries. It sometimes happens that people will drop * in .H files that contain not only map entries but a bunch of other * #defines for their code -- such as dropping in ApStudio's resource.h * file. Rather then put all of the non-valid entries into the help file, * we make a pass here to calculate the number of real entries. Then when * we're in the for() loop that writes the entry into the help file, we * simply ignore non-valid entries. */
if (cmap && (options.fsCompress & (COMPRESS_TEXT_PHRASE | COMPRESS_TEXT_HALL | COMPRESS_TEXT_ZECK))) { int cRealMap = cmap; #ifdef _DEBUG
cActualMap = cmap; #endif
for (imap = 0, qmap = (QMAP) pdrgMap->GetBasePtr(); imap < cmap; imap++, qmap++) {
// Get address of given hash value
rc = RcLookupByKey(fmsg.qbthrCtx, (KEY) &qmap->hash, NULL, (LPVOID) &addr); if (rc == RC_NoExists) cRealMap--; } LcbWriteIntAsShort(fmsg.hfCtxOMap, cRealMap); } else { LcbWriteIntAsShort(fmsg.hfCtxOMap, cmap); #ifdef _DEBUG
cActualMap = cmap; #endif
} ASSERT(fmsg.qbthrCtx != NULL);
if (cmap > 0) { ASSERT(ptblMap); for (imap = 0, qmap = (QMAP) pdrgMap->GetBasePtr(); imap < cmap; imap++, qmap++) {
// Get address of given hash value
rc = RcLookupByKey(fmsg.qbthrCtx, (KEY) &qmap->hash, NULL, (LPVOID) &addr); if (rc == RC_NoExists) {
int ialias; QALIAS qalias; if (pdrgAlias && pdrgAlias->Count() > 0) { for (ialias = 0, qalias = (QALIAS) pdrgAlias->GetBasePtr(); ialias < pdrgAlias->Count(); ialias++, qalias++) {
// Look up address for alias in context btree
if (qalias->hashAlias == qmap->hash) break; } if (ialias < pdrgAlias->Count()) { if (options.fsCompress & (COMPRESS_TEXT_PHRASE | COMPRESS_TEXT_HALL | COMPRESS_TEXT_ZECK)) continue; // aliased, so not an error
else goto BadCtx; // non-compressed, have to add it anyway
} }
if (!fNagged) { errHpj.ep = epNoFile; VReportError(HCERR_MAP_UNUSED, &errHpj); fNagged = TRUE; count = 0; } ASSERT(HCERR_MAP_UNUSED < HCERR_WARNINGS); if (!options.fSupressNotes) { strcpy(szParentString, "\t"); strcat(szParentString, ptblMap->GetPointer(qmap->pos) + sizeof(HASH)); strcat(szParentString, txtEol); SendStringToParent(szParentString); if (pLogFile) pLogFile->outstring(szParentString);
if (++count == 10) { count = 0; doGrind(); } } BadCtx: addr = addrNil;
/*
* When we are compressing the help file, we already reduced * the total map count to the number of entries actually * used, so we can just ignore this not-found entry. */
if (options.fsCompress & (COMPRESS_TEXT_PHRASE | COMPRESS_TEXT_HALL | COMPRESS_TEXT_ZECK)) continue; }
// Write out CTX
LcbWriteHf(fmsg.hfCtxOMap, &qmap->ctx, sizeof(CTX));
// Write out address
LcbWriteHf(fmsg.hfCtxOMap, &addr, sizeof(ADDR)); #ifdef _DEBUG
actual += sizeof(CTX) + sizeof(ADDR); #endif
} } #ifdef _DEBUG
{ int expected = cActualMap * (sizeof(CTX) + sizeof(ADDR)); ASSERT(expected == actual); } #endif
}
INLINE static BOOL STDCALL IsCtxPrefix(PCSTR pszContext) { // BUGBUG: strnicmp won't be correct for DBCS
if (ptblCtxPrefixes) { for (int pos = 1; pos <= ptblCtxPrefixes->CountStrings(); pos++) { if (strnicmp(pszContext, ptblCtxPrefixes->GetPointer(pos), strlen(ptblCtxPrefixes->GetPointer(pos))) == 0) return TRUE; } } else if (strnicmp(pszContext, "IDH_", 4) == 0) return TRUE;
return FALSE; }
|