/************************************************************************ * * * CNTTEST.CPP * * * * Copyright (C) Microsoft Corporation 1994 * * All Rights reserved. * * * * Miscellanious routins for HCW * * * ************************************************************************/ #include "stdafx.h" #include #include #include static const char txtInclude[] = ":include"; static const char txtBase[] = ":base"; static const char txtTitle[] = ":title"; static const char txtIndex[] = ":index"; static const char txtTab[] = ":tab"; static const char txtLink[] = ":link"; static const char txtFind[] = ":nofind"; // limits and colon commands should match mastkey.cpp in WinHelp source tree const int MAX_NEST_INPUT = 2; const int CNT_ANIMATECOUNT = 100; // Lines to process before animation frame const int MAX_LEVELS = 9; // maximum nested folders static int STDCALL CompareSz(PCSTR psz, PCSTR pszSub); static PSTR STDCALL SzGetDir(DIR dir, PSTR sz); static void STDCALL GetFmParts(FM fm, PSTR pszDest, int iPart); #define MSG_JUMP_TOPIC (WM_USER + 40) // taken from ..\winhlp32\inc\genmsg.h void STDCALL doCntTest(PSTR pszCntFile) { BOOL fSeenTitle = FALSE; static BOOL fBaseWarned; int curLevel = 0; int cContainers = 0; int cTopics = 0; FM fmBase = 0; PSTR pszBase = NULL; PSTR psz; PSTR pszFile; // We use szLine to make it easier to match code with mastkey CStr cszLine; int curInput = 0; CInput* ainput[MAX_NEST_INPUT + 1]; if (*pszCntFile == ' ') strcpy(pszCntFile, FirstNonSpace(pszCntFile, _fDBCSSystem)); ainput[curInput] = new CInput(pszCntFile); if (!ainput[curInput]->fInitialized) { OutSz(HCERR_CANNOT_OPEN, pszCntFile); return; } OutSz(IDCNT_TESTING_CONTENTS, pszCntFile); ChangeDirectory(pszCntFile); errHpj.lpszFile = pszCntFile; errHpj.ep = epCnt; { char szFile[256]; SzPartsFm(pszCntFile, szFile, PARTBASE); wsprintf(szParentString, GetStringResource(IDS_TESTING_CNT), szFile); InitGrind(szParentString); } cGrind = 0; version = 4; // force version 4.0 help file processing for (;;) { if (!ainput[curInput]->getline(&cszLine)) { delete ainput[curInput]; if (curInput == 0) break; // we're all done. else { curInput--; continue; } } // Check for string length overflow PSTR pszLine = cszLine.psz; // purely for notational convenience if (strlen(pszLine) > _MAX_FNAME) { SendStringToParent(IDCNT_LINE_TOO_LONG); SendStringToParent(pszLine); SendStringToParent(IDCNT_TEST_TERMINATED); do { delete ainput[curInput--]; } while (curInput > 0); RemoveGrind(); return; } if (++cGrind >= CNT_ANIMATECOUNT) { doGrind(); cGrind = 0; } psz = StrChr(pszLine, ';', fDBCSSystem); if (psz) *psz = '\0'; SzTrimSz(pszLine); // remove leading and trailing spaces if (!pszLine[0]) continue; // blank line // Is this a container (head level)? if (isdigit((BYTE) pszLine[0]) && !StrChr(pszLine, '=', fDBCSSystem)) { int level = atoi(pszLine); if (level > MAX_LEVELS || level == 0) { OutSz(IDCNT_INVALID_LEVEL, pszLine); continue; // context string not specified } if (level > 1 && curLevel == 0) { wsprintf(szParentString, GetStringResource(IDCNT_MISSING_LEVEL_1), level, pszLine); SendStringToParent(szParentString); } else if (level > curLevel + 1) { wsprintf(szParentString, GetStringResource(IDCNT_SKIPPED_LEVEL), level, curLevel, pszLine); SendStringToParent(szParentString); } curLevel = level; cContainers++; // keep count of valid containers continue; } // Is this a topic? if (pszLine[0] != ':') { psz = StrChr(pszLine, '=', fDBCSSystem); if (!psz) { OutSz(IDCNT_MISSING_CTX, pszLine); continue; } *psz++ = '\0'; // split line into text and context string SzTrimSz(psz); // remove leading and trailing spaces pszFile = StrChr(psz, FILESEPARATOR, fDBCSSystem); if (!pszFile) pszFile = StrChr(psz, WINDOWSEPARATOR, fDBCSSystem); cTopics++; if (*psz == CH_MACRO) { if (Execute(psz + 1) == wMACRO_EXPANSION) { if ((size_t) cszLine.SizeAlloc() <= strlen(GetMacroExpansion())) cszLine.ReSize(strlen(GetMacroExpansion() + 10)); strcpy(cszLine.psz, GetMacroExpansion()); } } else { // Get rid of filename specification long enough to check // for a valid context string char chSeparator; if (pszFile) { chSeparator = *pszFile; *pszFile = '\0'; int cbOld = strlen(psz); SzTrimSz(psz); // remove leading and trailing spaces /* * After trimming, we may no longer be able to just * restore the file separator to get the complete * striing. So, if the string ends up shorter, move the * file portion to compensate so that when the file * separator character is resotred, the context string * becomes rejoined with the filename part. */ int cbNew = strlen(psz); if (cbOld != cbNew) { strcpy(psz + cbNew + 2, pszFile); pszFile = psz + cbNew + 1; } } if (!FValidContextSz(psz)) { SendStringToParent(IDCNT_ERROR); OutSz(HCERR_INVALID_CTX, psz); if (pszFile) *pszFile = chSeparator; psz[-1] = '='; wsprintf(szParentString, "\r\n\t%s\r\n", pszLine); SendStringToParent(szParentString); } } continue; } ASSERT(pszLine[0] == ':'); // This is a command line int cb; if ((cb = CompareSz(pszLine, txtInclude))) { if (curInput >= MAX_NEST_INPUT) { SendStringToParent(IDCNT_NEST_TOO_DEEP); wsprintf(szParentString, "\r\n\t%s\r\n", pszLine); SendStringToParent(szParentString); continue; } FM fm = FmNewExistSzDir(FirstNonSpace(pszLine + cb, fDBCSSystem), DIR_INI | DIR_PATH | DIR_CURRENT | DIR_SYSTEM); if (fm) { ainput[++curInput] = new CInput(fm); lcFree(fm); if (!ainput[curInput]->fInitialized) { --curInput; } } else { OutSz(IDCNT_INCLUDE_NOT_FND, pszLine + cb); } } else { if ((cb = CompareSz(pszLine, txtTitle))) { // REVIEW: we should calculate available width in Index // tab and see if this title will fit -- warn if it doesn't. } else if ((cb = CompareSz(pszLine, txtBase))) { if (fmBase) { lcFree(fmBase); } pszFile = StrChr(pszLine + cb, '.', fDBCSSystem); if (pszFile && !nstrisubcmp(psz, ".HLP")) { PSTR psz = pszFile + 1; while (*psz && *psz != WINDOWSEPARATOR && *psz != ' ' && *psz != '\t') psz = CharNext(psz); char ch = *psz; *psz = '\0'; CStr csz(pszLine); csz += GetStringResource(IDCNT_BAD_EXTENSION); SendStringToParent(csz); *psz = ch; } pszFile = StrChr(pszLine + cb, WINDOWSEPARATOR, fDBCSSystem); if (pszFile) *pszFile = '\0'; strcpy(pszLine, SzTrimSz(pszLine + cb)); FM fm = FmNewExistSzDir(pszLine, DIR_INI | DIR_PATH | DIR_CURRENT | DIR_SYSTEM); if (!fm) OutSz(IDCNT_NOT_OPENABLE, pszLine); else if (!pszBase) { pszBase = lcStrDup(fm); } fmBase = fm; // even if its bad, since WinHelp would do this } else if ((cb = CompareSz(pszLine, txtTab))) { psz = StrChr(pszLine, '=', fDBCSSystem); if (!psz) { OutSz(IDCNT_BAD_TAB, pszLine); continue; } // REVIEW: now we'll want to find the dll and make sure it // has the function name the author claims it has. } else if (CompareSz(pszLine, txtIndex) || CompareSz(pszLine, txtLink)) { if (CompareSz(pszLine, txtLink)) { psz = pszLine + strlen(txtLink); } else { psz = StrChr(pszLine, '=', fDBCSSystem); if (!psz) { OutSz(IDCNT_BAD_INDEX, pszLine); continue; } } PSTR pszExt = StrChr(psz, '.', fDBCSSystem); if (!pszExt) ChangeExtension(psz, IDS_HLP_EXTENSION); pszExt = FirstNonSpace(psz + 1, fDBCSSystem); do psz--; while (*psz == ' '); psz[1] = '\0'; FM fm = FmNewExistSzDir(pszExt, DIR_INI | DIR_PATH | DIR_CURRENT | DIR_SYSTEM); if (!fm) { psz[1] = ' '; OutSz(IDCNT_NOT_OPENABLE, pszLine); } else lcFree(fm); } else if ((cb = CompareSz(pszLine, txtFind))) { continue; } else { OutSz(IDCNT_UNKNOWN_CMD, pszLine); } } } wsprintf(szParentString, GetStringResource(IDCNT_VALID_TOPICS), pszCntFile, cContainers, (cContainers == 1) ? "" : "s", cTopics, (cTopics == 1) ? "" : "s"); SendStringToParent(szParentString); RemoveGrind(); if (!pszBase) { pszBase = (PSTR) lcMalloc(strlen(pszCntFile) + 5); strcpy(pszBase, pszCntFile); ChangeExtension(pszBase, "HLP"); } if (MessageBox(NULL, GetStringResource(IDCNT_TEST_JUMPS), "", MB_YESNO) == IDYES) { WinHelp(hwndGrind, pszBase, HELP_FORCEFILE, 0); // At this point, the WinHelp window may well be hidden, so we // try to show it here. HWND hwndWinHelp = FindWindow("MS_WINHELP", NULL); if (hwndWinHelp) SendMessage(hwndWinHelp, MSG_JUMP_TOPIC, 0, 0); if (!WinHelp(hwndGrind, pszBase, HELP_COMMAND, (DWORD) "Test(6)")) SendStringToParent(IDCNT_BAD_HELP); } } /*************************************************************************** FUNCTION: CompareSz PURPOSE: Compare a sub-string from the specified STRINGTABLE resource with the first part of a main string. PARAMETERS: psz id RETURNS: 0 if no match, else the length of the matching string. COMMENTS: MODIFICATION DATES: 17-Aug-1993 [ralphw] ***************************************************************************/ static int STDCALL CompareSz(PCSTR psz, PCSTR pszSub) { int cb; if (_strnicmp(psz, pszSub, cb = strlen(pszSub)) == 0) return cb; else return 0; } /*************************************************************************** * - Name: FmNewExistSzDir - * Purpose: Returns an FM describing a file that exists * * Arguments: sz - see FmNewSzDir dir - DIR * * Returns: the new FM * * Globals Used: rcIOError * * +++ * * Notes: * If sz is a rooted pathname, dir is ignored. Otherwise, all directories * specified by dir are searched in the order of the dir* enum type. * ***************************************************************************/ FM STDCALL FmNewExistSzDir(PCSTR pszFileName, DIR dir) { char szBuf[_MAX_PATH]; FM fm = NULL; int iDrive, iDir, iBase, iExt; int cb; rcIOError = RC_Success; // Clear error flag if (IsEmptyString(pszFileName)) { rcIOError = RC_BadArg; return NULL; } cb = strlen(pszFileName); SnoopPath(pszFileName, &iDrive, &iDir, &iBase, &iExt); if (pszFileName[iBase] == '\0') { // no name rcIOError = RC_BadArg; return fm; } if (pszFileName[iDrive] || pszFileName[iDir] == '\\' || pszFileName[iDir] == '/' ) { // was given a drive or rooted path, so ignore dir parameter fm = FmNew(pszFileName); if (!FExistFm(fm)) { DisposeFm(fm); // If we can't find it in the path specified, then strip off the path // and try the normal directories. lstrcpy(szBuf, pszFileName + iBase); fm = FmNewExistSzDir(szBuf, dir); if (!FExistFm(fm)) { RemoveFM(&fm); rcIOError = RC_NoExists; } } return fm; } else { DIR idir, xdir; for (idir = DIR_FIRST, fm = NULL; idir <= DIR_LAST && fm==NULL; idir <<= 1) { xdir = dir & idir; if (xdir == DIR_CURRENT && dir & DIR_CUR_HELP) { char szCurName[_MAX_PATH]; lstrcpy(szCurName, pszFileName + iBase); fm = FmNew(szCurName); if (FExistFm(fm)) return fm; else RemoveFM(&fm); } else if (xdir == DIR_PATH) { PSTR pszFilePart; /* * search $PATH using the full string which will catch the case * of a relative path and also do the right thing searching $PATH */ ConvertToWindowsHelp(pszFileName, szBuf); if (GetFileAttributes(szBuf) != (DWORD) -1) { fm = FmNew(szBuf); } else if (SearchPath(NULL, pszFileName, NULL, sizeof(szBuf), szBuf, &pszFilePart)) { fm = FmNew(szBuf); } } else if (xdir) { if (SzGetDir(xdir, szBuf) != NULL) { lstrcat(szBuf, pszFileName + iBase); fm = FmNew(szBuf); if (!FValidFm(fm)) { rcIOError = RC_Failure; } else if (!FExistFm(fm)) { RemoveFM(&fm); } } } } // for if ((rcIOError == RC_Success) && (!FValidFm(fm))) rcIOError = RC_NoExists; } return fm; } /*************************************************************************** * - Name: SzGetDir - * Purpose: returns the rooted path of a DIR * * Arguments: dir - DIR (must be one field only, and must be an actual dir - * not DIR_PATH) * sz - buffer for storage (should be at least _MAX_PATH) * * Returns: sz - fine * NULL - OS Error (check rcIOError) * * Globals Used: rcIOError * ***************************************************************************/ static PSTR STDCALL SzGetDir(DIR dir, PSTR sz) { ASSERT(sz); switch (dir) { case DIR_CURRENT: GetCurrentDirectory(MAX_PATH, sz); AddTrailingBackslash(sz); return sz; default: rcIOError = RC_BadArg; return NULL; } } /*************************************************************************** * - Name: FExistFm - * Purpose: Does the file exist? * * Arguments: FM * * Returns: TRUE if it does * FALSE if it doesn't, or if there's an error * (call _ to find out what error it was) * * Globals Used: rcIOError * * +++ * * Notes: * ***************************************************************************/ BOOL STDCALL FExistFm(FM fm) { char nsz[_MAX_PATH]; BOOL fExist; if (!FValidFm(fm)) { rcIOError = RC_BadArg; return FALSE; } strcpy(nsz, fm); // make a copy of the filename // FMs are ANSI critters and access() wants an OEM string AnsiToOem(nsz, nsz); fExist = _access(nsz, 0) == 0; // pass 0 to test for existence if (!fExist) rcIOError = (errno == ENOENT) ? RC_Success : RcGetLastError(); else rcIOError = RC_Success; return fExist; } void STDCALL ConvertToWindowsHelp(PCSTR pszFile, PSTR pszDstPath) { PSTR pszHelpDir; GetWindowsDirectory(pszDstPath, MAX_PATH); AddTrailingBackslash(pszDstPath); pszHelpDir = pszDstPath + strlen(pszDstPath); lstrcat(pszDstPath, "help"); // REVIEW: will this tell us if we have a read-only directory? if (GetFileAttributes(pszDstPath) == (DWORD) -1) *pszHelpDir = '\0'; else AddTrailingBackslash(pszDstPath); GetFmParts((FM) pszFile, pszDstPath + strlen(pszDstPath), PARTBASE | PARTEXT); } static void STDCALL GetFmParts(FM fm, PSTR pszDest, int iPart) { int iDrive, iDir, iBase, iExt; ASSERT(fm && pszDest); if (!fm || pszDest == NULL) { *pszDest = '\0'; rcIOError = RC_BadArg; return; } ASSERT(iPart != PARTALL); SnoopPath(fm, &iDrive, &iDir, &iBase, &iExt); if (iPart & PARTBASE) { strcpy(pszDest, fm + iBase); if (iPart & PARTEXT) return; // remove extension if not specifically requested. else { PSTR psz = StrChrDBCS(pszDest, '.'); if (psz && !StrChrDBCS(psz, '\\')) *psz = '\0'; } return; } else if (iPart & (PARTDRIVE | PARTDIR)) { strcpy(pszDest, fm); pszDest[iBase] = '\0'; return; } ASSERT(iPart & PARTBASE || iPart & (PARTDRIVE | PARTDIR)); }