/*************************************************************************** * * HPJ.C * * Copyright (C) Microsoft Corporation 1990 - 1994. * All Rights reserved. * **************************************************************************** * * Module Intent * * This module contains routines for parsing the .HPJ file. * ****************************************************************************/ #include "stdafx.h" #include "cfontmap.h" #include "..\common\coutput.h" #include // ERANGE #include #include #include #include "..\common\resource.h" #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /***************************************************************************** * * * Defines * * * *****************************************************************************/ // Parser states #define CCOM ((STATE) 1) // inside a C-style comment #define LEADING ((STATE) 2) // still eating leading white space #define COM_EOL ((STATE) 3) // inside a comment to EOL (either ; or //) #define SLASH ((STATE) 4) // seen a / #define CCOMSTAR ((STATE) 5) // maybe about to end a C-style comment #define MEAT ((STATE) 6) // something on this line isn't a comment const char SPACE = ' '; const char CHAR_TAB = '\t'; // Constants for Window Smag stuff // three possible outcomes of trying to read something out of the buffer: // bad syntax, read ok, or value wasn't there before the comma const int WINDOW_READ_INVALID = 0; const int WINDOW_READ_OK = 1; const int WINDOW_READ_MISSING = 2; /***************************************************************************** * * * Typedefs * * * *****************************************************************************/ typedef RC_TYPE (STDCALL NEAR* PFPARSE)(PSTR); /* File Entry structure (for file stack) REVIEW - currently the full path spec is stored for each file. */ typedef struct { CInput* pinput; char rgchFile[_MAX_PATH]; int iLine; // line number } FE, *PFE; /* File Stack This is used to store the file handles, names, and current line numbers of the open .HPJ file and files included therein. */ typedef struct { int ifeTop; // index to current file (-1 for empty) int ifeComment; // file in which C-style comment started int iCommentLine; // line on which C-style comment started FE rgfe[MAX_INCLUDE]; } FILESTACK; typedef FILESTACK* PFILESTACK; /***************************************************************************** * * * Static Variables * * * *****************************************************************************/ static PFILESTACK pfs; const char txtSecondary[] = "secondary"; static const char txtDefine[] = "#define"; static const char txtIfDef[] = "#ifdef"; static const char txtIfnDef[] = "#ifndef"; static int curConfig = 0; // section table // The section parsing functions RC_TYPE STDCALL RcParseAliasSz(PSTR); RC_TYPE STDCALL RcParseBaggageSz(PSTR); RC_TYPE STDCALL RcParseBitmapsSz(PSTR); RC_TYPE STDCALL RcParseBogusSz(PSTR); RC_TYPE STDCALL RcParseBuildTagsSz(PSTR); RC_TYPE STDCALL RcParseCharSet(PSTR); RC_TYPE STDCALL RcParseConfigSz(PSTR); RC_TYPE STDCALL RcParseFilesSz(PSTR); RC_TYPE STDCALL RcParseFonts(PSTR); RC_TYPE STDCALL RcParseMacros(PSTR); RC_TYPE STDCALL RcParseMapSz(PSTR); RC_TYPE STDCALL RcParseOptionsSz(PSTR); RC_TYPE STDCALL RcParseSecondaryConfigSz(PSTR); RC_TYPE STDCALL RcParseWindowsSz(PSTR); // Associates a section name with a section parsing function. typedef struct { PSTR szName; PFPARSE pfparse; } SECTION; SECTION rgSection[] = { { "ALIAS", RcParseAliasSz }, { "BAGGAGE", RcParseBaggageSz }, { "BITMAPS", RcParseBitmapsSz }, { "BUILDTAGS", RcParseBuildTagsSz }, { "CONFIG", RcParseConfigSz }, { "FILES", RcParseFilesSz }, { "MAP", RcParseMapSz }, { "OPTIONS", RcParseOptionsSz }, { "WINDOWS", RcParseWindowsSz }, // New to 4.0 { "CHARSET", RcParseCharSet }, { "MACROS", RcParseMacros }, { "FONTS", RcParseFonts }, { NULL, RcParseBogusSz }, }; // Strings used for COMPRESS option in addition to yes/no strings const char txtLow[] = "low"; const char txtMedium[] = "medium"; const char txtHigh[] = "high"; const char txtNone[] = "none"; const char txtJohn[] = "JOHN"; // Language stuff char *rgszSortOrder[] = { "ENGLISH", "SCANDINAVIAN", "JAPANESE", "KOREAN", "CHINESE", "CZECH", "POLISH", "HUNGARIAN", "RUSSIAN", "EASTERN EUROPE", "NLS", }; #define MAX_SORT ELEMENTS(rgszSortOrder) COutput* pLogFile; static CTable* ptblDefine; /***************************************************************************** * * * Prototypes * * * *****************************************************************************/ static void STDCALL DispSignOn(void); static BOOL STDCALL FIsIconPf(FILE* pf); static BOOL STDCALL FIsRtf(PSTR); static BOOL STDCALL FPushFilePfs(PSTR szFile); static UINT STDCALL FReadInt16(PSTR *psz, WORD* pi, WORD *pgrf, UINT f); static UINT STDCALL FReadRgb(PSTR *psz, LONG *pl, UINT *pgrf, UINT f); static void STDCALL ParseMapFontSizeOption(PSTR, PCSTR); static PFPARSE STDCALL PfparseFromSz(PSTR); static RC_TYPE STDCALL RcGetLogicalLine(CStr*); static RC_TYPE STDCALL RcLoadBitmapFm(FM fmSource, FM FAR* pfmDest, UINT FAR* pwObjrg, BOOL fsCompress); static void STDCALL ReportDuplicateOption(PSTR* pszOption, PSTR pszName); static void STDCALL ReportBadOption(PSTR pszOptionValue, PSTR pszOption); static RC_TYPE STDCALL ParseMacros(CStr* pcszLine); __inline CInput* STDCALL PfTopPfs(void); __inline BOOL STDCALL FVerifyBuildTag(PSTR psz); __inline BOOL STDCALL FPopPfs(void); /*************************************************************************** FUNCTION: SzGetDriveAndDir PURPOSE: Return the drive letter and directory from a file spec PARAMETERS: pszFile - a filespec that may or may not have drive and/or directory. It isn't NULL or "". pszDst - buffer to receive the info. If NULL, a buffer is allocated. RETURNS: pointer to buffer where the drive and dir are placed. This may be allocated (see pszDst above). COMMENTS: MODIFICATION DATES: 03-Jul-1993 [ralphw] ***************************************************************************/ // REVIEW: allocating memory is risky if caller forgets to free it PSTR STDCALL SzGetDriveAndDir(PCSTR pszFile, PSTR pszDst) { if (!pszFile) return lcStrDup("."); ASSERT(pszFile != NULL); char szFileCopy[_MAX_FNAME]; strcpy(szFileCopy, pszFile); PSTR psz; for (psz = szFileCopy + strlen(szFileCopy) - 1; psz > szFileCopy && ((*psz != ':' && *psz != '\\' && *psz != '/') || IsFirstByte(*(psz - 1))); --psz); int cb = psz - szFileCopy + 1; if (pszDst == NULL) pszDst = (PSTR) lcMalloc(cb + 1); if (cb > 1) memcpy(pszDst, szFileCopy, cb); else cb = 0; pszDst[cb] = '\0'; return pszDst; } /***************************************************************************\ * - Function: FIsRtf( szFile ) - * Purpose: Determine whether szFile is an RTF file (or looks like one). * * ASSUMES * * args IN: szFile - filename * * PROMISES * * returns: TRUE if file contains a normal looking header * FALSE if there is some i/o error or it doesn't * contain rtf header info. * * Side Effects: File is opened and closed. * \***************************************************************************/ static BOOL STDCALL FIsRtf(PSTR pszFile) { char szBuf[_MAX_PATH]; // easy way to check for devices (aux, prn, com1) if (HceResolveFileNameSz(pszFile, NULL, szBuf) != HCE_OK) return FALSE; CRead cr(szBuf); if (cr.hf == HFILE_ERROR) return FALSE; cr.read(szBuf, 5); if (_stricmp(szBuf, "{\\rtf") == 0) // is this an RTF file? return TRUE; else return FALSE; } /***************************************************************************\ * - Function: FIsIconPf( pf ) - * Purpose: Determine whether pf is an icon file (or looks like one). * * ASSUMES * * args IN: pf - file pointer * * PROMISES * * returns: TRUE if file contains a normal looking header * FALSE if there is some i/o error or it doesn't * contain icon header info. * * Method: Read the first four bytes and check that they match * what an .ICO file has. * * Note: This takes a FILE * rather than a filename because * I have one handle. * \***************************************************************************/ static BOOL STDCALL FIsIconPf(FILE* pf) { char szBuf[4]; return (fread(szBuf, 1, 4, pf) == 4 && szBuf[0] == 0 && szBuf[1] == 0 && szBuf[2] == 1 && szBuf[3] == 0); } /***************************************************************************\ * - Function: HceResolveFileNameSz( szFile, szRoot, szBuffer, ppf ) - * Purpose: Resolve a filename relative to a given root directory. * If the filename is absolute (either drive letter or * leading '\' given), the root directory is ignored. * * ASSUMES * * args IN: szFile - the file name (non-null) * szRoot - the home directory name; full path with drive * (NULL OK) * ppf - pointer to FILE* for open file (NULL to not open) * * PROMISES * * returns: HCE_OK * hceFileNameTooLong * hcePathNameTooLong * hceBadlyFormed * hceFileNotFound * hceFileIsDirectory * hceFileIsDevice * * args OUT: szBuffer - resolved file name goes here if successful * ppf - open FILE * placed here * * * Notes: Doesn't check for badly formed file names, such as * filenames containing spaces, multiple colons, etc. * Such bogus names will just come back as an error * opening the file. * * stat() doesn't tell me if the file is a device. * I use isatty(). * \***************************************************************************/ HCE STDCALL HceResolveFileNameSz(PSTR szFile, PSTR pszRoot, PSTR pszBuffer, FILE** ppf, CInput** ppinput) { ASSERT(pszBuffer); int cbRoot = (pszRoot == NULL) ? 0 : strlen(pszRoot); if (strlen(szFile) + cbRoot >= _MAX_PATH) return HCE_NAME_TOO_LONG; if (*szFile == '\\' || szFile[1] == ':' || pszRoot == NULL || *pszRoot == '\0') pszBuffer[0] = '\0'; else { // last char of pszRoot if any strcpy(pszBuffer, pszRoot); // [olympus 306 - chauv] // changed code to check for trailing backslash int i = strlen(pszBuffer); if ( ((i > 1) && (pszBuffer[i-1] == '\\') && (IsDBCSLeadByte(pszBuffer[i-2]))) || ( (i > 0) && (pszBuffer[i-1] != '\\') && (pszBuffer[i-1] != ':')) ) strcat(pszBuffer, "\\"); } strcat(pszBuffer, szFile); /* * If we have a replacement string, and we have a path specification, * and the first part of that path matches our replace string, then * perform a substitution. Its up to the help author to specify a valid * replacement string. */ if (options.pszReplace && (StrChr(pszBuffer, CH_BACKSLASH, fDBCSSystem) || StrChr(pszBuffer, CH_COLON, fDBCSSystem))) { if (nstrisubcmp(pszBuffer, options.pszReplace)) { CStr szTmp(pszBuffer); strcpy(pszBuffer, options.pszReplaceWith); strcat(pszBuffer, (PSTR) szTmp + strlen(options.pszReplace)); } } if (ppinput) { *ppinput = new CInput(pszBuffer); if (!(*ppinput)->fInitialized) { delete *ppinput; *ppinput = NULL; DWORD attribute = GetFileAttributes(pszBuffer); if (attribute == HFILE_ERROR) return HCE_FILE_NOT_FOUND; else if (attribute == FILE_ATTRIBUTE_DIRECTORY) return HCE_FILE_IS_DIRECTORY; else return HCE_CANNOT_OPEN; } return HCE_OK; } FILE *pf; if (!iflags.fTrusted || ppf) { /* * REVIEW: 12-Apr-1994 [ralphw] if ppf == NULL, the only reason * for opening this is to see if the file is a device (isatty). For * very small RTF files, this is a significant time hit (believe it * or not!) Might be worthwhile to simply remove it and simply check * for known device names. */ if (!ppf) { if (GetFileAttributes(pszBuffer) != HFILE_ERROR) { return HCE_OK; } else { return HCE_FILE_NOT_FOUND; } } pf = fopen(pszBuffer, "r"); if (pf == NULL) { DWORD attribute = GetFileAttributes(pszBuffer); if (attribute == HFILE_ERROR) return HCE_FILE_NOT_FOUND; else if (attribute == FILE_ATTRIBUTE_DIRECTORY) return HCE_FILE_IS_DIRECTORY; else return HCE_NO_PERMISSION; // REVIEW - can this be anything else? } if (_isatty(_fileno(pf))) { fclose(pf); return HCE_FILE_IS_DEVICE; } if (ppf == NULL) fclose(pf); else *ppf = pf; } return HCE_OK; } HCE STDCALL HceResolveTableDir(PSTR pszFile, CTable *ptblDir, PSTR pszBuffer, FILE** ppf) { HCE hce; if (!ptblDir) return HceResolveFileNameSz(pszFile, NULL, pszBuffer, ppf); if (ptblDir->CountStrings() > 0) { for (int pos = 1; pos <= ptblDir->CountStrings(); pos++) { hce = HceResolveFileNameSz(pszFile, ptblDir->GetPointer(pos), pszBuffer, ppf); if (hce != HCE_FILE_NOT_FOUND) return hce; } } return HCE_FILE_NOT_FOUND; } /***************************************************************************\ * - Function: RcParseBogusSz( sz) - * Status: stub * * Purpose: Parse an invalid line. (is this function really needed? maybe, if don't want to spew endless error messages.... * * ASSUMES * * args IN: sz - string * * PROMISES * * returns: success code (seems meaningless in this instance) * \***************************************************************************/ RC_TYPE STDCALL RcParseBogusSz(PSTR sz) { Unreferenced(sz); return RC_Success; } /***************************************************************************\ * - Function: ParseMapFontSizeOption( pszRHS, poptions, perr ) - * Purpose: Parse the string into the FONTRANGE struct. * * "mapfontsize" "=" "-" ":" | * "mapfontsize" "=" ":" * refers to size in points. * * ASSUMES * * args IN: pszRHS - * poptions - * perr - * * PROMISES * * args OUT: poptions - iFontRangeMac, rgFontRange updated. * * Side Effects: May emit one of the following errors: * * hceInvalidFontRangeFormat * hceTooManyFontRanges * hceFontRangeOverlap * * Notes: Doesn't recognize fractional points. * \***************************************************************************/ static void STDCALL ParseMapFontSizeOption(PSTR pszRHS, PCSTR pszLine) { HALFPT halfptInMin, halfptInMost, halfptOut; UINT u; int i; FONTRANGE* qfrT; // clear flag because this option can occur > 1 time ClearUlFlag(options.lOptionInitFlags, OPT_MAPFONTSIZE); if (MAX_FONTRANGE <= options.iFontRangeMac) { VReportError(HCERR_TOO_MANY_FONT_RANGES, &errHpj); return; } if (!FGetUnsigned(pszRHS, &pszRHS, &u)) { VReportError(HCERR_INVALID_FONT_RANGE, &errHpj, pszLine); return; } halfptInMin = (HALFPT) (u * 2); // REVIEW - more range checking? if (*pszRHS == '-') { pszRHS = FirstNonSpace(pszRHS + 1, fDBCSSystem); if (!FGetUnsigned(pszRHS, &pszRHS, &u)) { VReportError(HCERR_INVALID_FONT_RANGE, &errHpj, pszLine); return; } halfptInMost = (HALFPT) (u * 2); // REVIEW - more range checking? if (halfptInMost < halfptInMin) { HALFPT halfptT = halfptInMost; halfptInMost = halfptInMin; halfptInMin = halfptT; } if (':' != *pszRHS) { VReportError(HCERR_INVALID_FONT_RANGE, &errHpj, pszLine); return; } pszRHS = FirstNonSpace(pszRHS, fDBCSSystem); } else if (*pszRHS == ':') halfptInMost = halfptInMin; else { VReportError(HCERR_INVALID_FONT_RANGE, &errHpj, pszLine); return; } pszRHS = FirstNonSpace(pszRHS + 1, fDBCSSystem); if (!FGetUnsigned(pszRHS, &pszRHS, &u)) { VReportError(HCERR_INVALID_FONT_RANGE, &errHpj, pszLine); return; } halfptOut = (HALFPT) (2 * u); // REVIEW - more range checking? if (*FirstNonSpace(pszRHS, fDBCSSystem)) { VReportError(HCERR_INVALID_FONT_RANGE, &errHpj, pszLine); return; } // Check for font range overlap. for (i = options.iFontRangeMac - 1, qfrT = options.rgFontRange; i >= 0; i--, qfrT++) { if (! (halfptInMin > qfrT->halfptInMost || halfptInMost < qfrT->halfptInMin)) { VReportError(HCERR_FONT_OVERLAP, &errHpj, pszLine); return; } } options.rgFontRange[options.iFontRangeMac].halfptInMin = halfptInMin; options.rgFontRange[options.iFontRangeMac].halfptInMost = halfptInMost; options.rgFontRange[options.iFontRangeMac++].halfptOut = halfptOut; } // ParsePath - Adds filenames in szPath to the specified table. // ptbl - table to add filenames to // szPath - string containing zero or more filenames, separated // by commas or tabs // opt - option we're parsing the path for (used for error reporting) // // Modified: // 2/28/95 (niklasb): removed space from pchDelimeter because // long filenames may contain spaces (fixes bug 9605). // Also added this nifty comment. void STDCALL ParsePath(CTable* ptbl, PSTR szPath, OPT opt) { PSTR psz; char szNewPath[_MAX_PATH]; HCE hce; BOOL fBadPath = FALSE; PSTR pszDir = SzGetDriveAndDir(errHpj.lpszFile, NULL); // REVIEW (niklasb): this is broken if we every have multiple // filenames separated by spaces, but we just have to make // sure that never occurs because LFNs can contain spaces. static PSTR pchDelimeter = ",\t"; for (psz = StrToken(szPath, pchDelimeter); psz != NULL; psz = StrToken(NULL, pchDelimeter)) { if (strlen(psz) >= _MAX_PATH) { fBadPath = TRUE; VReportError(HCERR_PATH_TOO_LONG, &errHpj, psz); continue; } // If the current .HPJ file isn't in the current directory, // we need to resolve sz to the dir containing it. hce = HceResolveFileNameSz(psz, pszDir, szNewPath); if (hce == HCE_OK) { DWORD attribute = GetFileAttributes(szNewPath); if (attribute == HFILE_ERROR) hce = HCE_FILE_NOT_FOUND; else if (attribute & FILE_ATTRIBUTE_DIRECTORY) hce = HCE_FILE_IS_DIRECTORY; } if (hce == HCE_FILE_IS_DIRECTORY) { if (!ptbl->AddString(szNewPath)) { OOM(); break; } } else { fBadPath = TRUE; if (hce == HCE_OK || hce == HCE_FILE_NOT_FOUND) hce = HCE_INVALID_PATH; VReportError(HCERR_INVALID_PATH, &errHpj, psz, ppszOptions[opt]); break; } } lcFree(pszDir); } /***************************************************************************\ * - Function: RcParseOptionsSz( sz) - * Purpose: Parse a line in the [options] section. (see grammar) * * ASSUMES * * args IN: sz - line to parse, with comments stripped * * PROMISES * * returns: success code. Meaning? * * +++ * Method: All options are of the form = [smag]. * We strip out the keyword and look it up in a table. * Then we parse the smag as appropriate. * \***************************************************************************/ RC_TYPE STDCALL RcParseOptionsSz(PSTR pszLine) { HCE hce; PSTR psz; PSTR pszEq = StrChr(pszLine, '=', fDBCSSystem); if (pszEq == NULL) { VReportError(HCERR_MISSING_VALUE, &errHpj, pszLine); return RC_Success; } *pszEq = '\0'; SzTrimSz(pszLine); PSTR pszOptionValue = FirstNonSpace(pszEq + 1, fDBCSSystem); if (!*pszOptionValue) { VReportError(HCERR_MISSING_VALUE, &errHpj, pszLine); return RC_Success; } ASSERT(MAX_OPT >= OPT_INDEX); int opt; for (opt = 0; opt < MAX_OPT; opt++) { if (_stricmp(pszLine, ppszOptions[opt]) == 0) break; } if (opt == MAX_OPT) { VReportError(HCERR_UNKNOWN_OPTION, &errHpj, pszLine); return RC_Success; } // Nag if the we've seen the option before, but otherwise continue if (FTestUlFlag(options.lOptionInitFlags, opt)) VReportError(HCERR_DUPLICATE_OPTION, &errHpj, pszLine); if (opt != OPT_BMROOT && opt != OPT_ROOT) SetUlFlag(options.lOptionInitFlags, opt); switch (opt) { case OPT_BMROOT: if (!options.ptblBmpRoot) options.ptblBmpRoot = new CTable; ParsePath(options.ptblBmpRoot, pszOptionValue, (OPT) opt); break; case OPT_BUILD: /* * don't use strdup() because we need space for the extra ")" * tacked on in parsebld.c. */ if (options.szBuildExp) lcFree(options.szBuildExp); options.szBuildExp = (PSTR) lcMalloc(strlen(pszOptionValue) + 2); strcpy(options.szBuildExp, pszOptionValue); break; case OPT_COMPRESS: if (isdigit(*pszOptionValue)) { if (!FGetUnsigned(pszOptionValue, &pszOptionValue, &options.fsCompress)) { ReportBadOption(pszOptionValue, ppszOptions[opt]); options.fsCompress = COMPRESS_NONE; } } else { switch (YesNo(pszOptionValue)) { case IDYES: options.fsCompress = COMPRESS_MAXIMUM; break; case IDNO: options.fsCompress = COMPRESS_NONE; break; default: // Check for special compress options if (!_stricmp(pszOptionValue, txtLow)) options.fsCompress = COMPRESS_TEXT_ZECK; else if (!_stricmp(pszOptionValue, txtHigh)) options.fsCompress = COMPRESS_MAXIMUM; else if (!_stricmp(pszOptionValue, txtNone)) options.fsCompress = COMPRESS_NONE; // Medium compression is the same as low for text, but // with maximum compression for bitmaps else if (!_stricmp(pszOptionValue, txtMedium)) options.fsCompress = COMPRESS_TEXT_ZECK | (COMPRESS_BMP_RLE | COMPRESS_BMP_ZECK); else ReportBadOption(pszOptionValue, ppszOptions[opt]); break; } } /* * Phrase and Hall compression cannot be used simultaneously, so * if both are specified we default to Hall compression. */ if (options.fsCompress & COMPRESS_TEXT_PHRASE && options.fsCompress & COMPRESS_TEXT_HALL) options.fsCompress &= ~COMPRESS_TEXT_PHRASE; // Maximum text compression also means maximum bitmap compression if (options.fsCompress & (COMPRESS_TEXT_PHRASE | COMPRESS_TEXT_HALL | COMPRESS_TEXT_ZECK)) options.fsCompress |= (COMPRESS_BMP_RLE | COMPRESS_BMP_ZECK); else options.fsCompress |= COMPRESS_BMP_RLE; break; case OPT_CDROM: // OPTCDROM = -> optimize |topic alignment switch (YesNo(pszOptionValue)) { case IDYES: options.fOptCdRom = TRUE; break; case IDNO: options.fOptCdRom = FALSE; break; default: ReportBadOption(pszOptionValue, ppszOptions[opt]); break; } break; case OPT_ERRORLOG: { // REVIEW: If we can't open the error log, should we terminate? char szFile[_MAX_PATH]; HCE hce = HceResolveFileNameSz(pszOptionValue, NULL, szFile); if (hce == HCE_OK || hce == HCE_FILE_NOT_FOUND) { if (pLogFile) delete pLogFile; pLogFile = new COutput(szFile, 2 * 1024); if (!pLogFile->fInitialized) VReportError(HCERR_CANNOT_WRITE, &errHpj, szFile); else { DispSignOn(); pLogFile->outstring_eol(pfs->rgfe[0].rgchFile); } } else VReportError(HCERR_CANNOT_WRITE, &errHpj, szFile); break; } case OPT_FORCEFONT: if (strlen(pszOptionValue) >= MAX4_FONTNAME - 2) VReportError(HCERR_FONTNAME_TOO_LONG, &errHpj, pszOptionValue); else { if (options.pszForceFont) lcFree(options.pszForceFont); options.pszForceFont = lcStrDup(pszOptionValue); SetForcedFont(options.pszForceFont); } break; case OPT_ICON: { if (options.pszIcon) ReportDuplicateOption(&options.pszIcon, ppszOptions[opt]); FILE *pf; char szBuf[_MAX_PATH]; if (options.ptblFileRoot) hce = HceResolveTableDir(pszOptionValue, options.ptblFileRoot, szBuf, (FILE **) &pf); else { PSTR szDir = SzGetDriveAndDir(errHpj.lpszFile, NULL); hce = HceResolveFileNameSz(pszOptionValue, szDir, szBuf, &pf); lcFree(szDir); } if (hce != HCE_OK) { VReportError(HCERR_CANNOT_OPEN, &errHpj, pszOptionValue); } else { if (!FIsIconPf(pf)) { VReportError(HCERR_INVALID_ICON, &errHpj, pszOptionValue); } else { options.pszIcon = lcStrDup(szBuf); } fclose(pf); } break; } case OPT_HLP: strcpy(szHlpFile, pszOptionValue); break; case OPT_HCW: break; // ignore it -- it's used by HCW case OPT_FTS: if (!FGetNum(pszOptionValue, NULL, &options.fsFTS)) { ReportBadOption(pszOptionValue, ppszOptions[opt]); } break; case OPT_CONTENTS: // REVIEW - unfinished: check syntax if (options.pszContentsTopic) ReportDuplicateOption(&options.pszContentsTopic, ppszOptions[opt]); if (pszOptionValue) options.pszContentsTopic = lcStrDup(pszOptionValue); break; case OPT_LANGUAGE: { VReportError(HCERR_OLD_SORT, &errHpj); for (int i = 0; i < MAX_SORT; i++) { if (!_stricmp(pszOptionValue, rgszSortOrder[i])) break; } if (MAX_SORT == i) VReportError(HCERR_UNKNOWN_LANGUAGE, &errHpj, pszOptionValue); else options.sortorder = (SORTORDER) i; switch (options.sortorder) { /* * Note that the only case-sensitive key types are for * standard and NLS comparisons. The rest of the sorts * are case-insensitive only. They are also here only * for backwards compatibility. All 4.0 help files should * be using either standard or NLS sorting. */ case SORT_ENGLISH: ktKeywordi = KT_SZI; ktKeyword = KT_SZ; break; case SORT_SCANDINAVIAN: // REVIEW: is this Swedish? ktKeywordi = KT_SZISCAND; ktKeyword = KT_SZISCAND; break; // REVIEW: the following only work with WinHelp 4.0. case SORT_CHINESE: kwlcid.langid = 0x0404; options.fDBCS = TRUE; goto NLS; case SORT_JAPANESE: kwlcid.langid = 0x0411; options.fDBCS = TRUE; goto NLS; case SORT_KOREAN: kwlcid.langid = 0x0412; options.fDBCS = TRUE; goto NLS; case SORT_POLISH: kwlcid.langid = 0x0415; goto NLS; case SORT_HUNGARIAN: kwlcid.langid = 0x040E; goto NLS; case SORT_RUSSIAN: kwlcid.langid = 0x0419; goto NLS; case SORT_CZECH: kwlcid.langid = 0x0405; goto NLS; case SORT_NLS: /* * This is the prefered language flag -- it uses * whatever the locale id is for the current * environment. This ensures that the sorting order will * remain unchanged no matter what language windows is * running when the help file is displayed. */ NLS: ktKeywordi = KT_NLSI; ktKeyword = KT_NLS; if (!kwlcid.langid) kwlcid.langid = GetUserDefaultLangID(); lcid = MAKELCID(kwlcid.langid, SORT_DEFAULT); break; default: ASSERT(FALSE); break; } } break; case OPT_MAPFONTSIZE: ParseMapFontSizeOption(pszOptionValue, pszLine); break; case OPT_MULTIKEY: while (*pszOptionValue) HceAddPkwiCh(*pszOptionValue++); // clear flag because this option can occur > 1 time ClearUlFlag(options.lOptionInitFlags, OPT_MULTIKEY); break; case OPT_REPORT: switch (YesNo(pszOptionValue)) { case IDYES: options.fReport = TRUE; break; case IDNO: options.fReport = FALSE; break; default: ReportBadOption(pszOptionValue, ppszOptions[opt]); break; } break; case OPT_ROOT: if (!options.ptblFileRoot) options.ptblFileRoot = new CTable; ParsePath(options.ptblFileRoot, pszOptionValue, (OPT) opt); break; case OPT_TITLE: if (options.pszTitle) ReportDuplicateOption(&options.pszTitle, ppszOptions[opt]); if (strlen(pszOptionValue) > CBMAXTITLE) { pszOptionValue[CBMAXTITLE + 1] = '\0'; VReportError(HCERR_TITLE_TOO_BIG, &errHpj, pszOptionValue); } options.pszTitle = lcStrDup(pszOptionValue); break; case OPT_PHRASE: switch (YesNo(pszOptionValue)) { case IDYES: options.fUsePhrase = TRUE; break; case IDNO: options.fUsePhrase = FALSE; break; default: ReportBadOption(pszOptionValue, ppszOptions[opt]); break; } break; case OPT_WARNING: // We simply ignore this break; case OPT_COPYRIGHT: if (options.pszCopyright) ReportDuplicateOption(&options.pszCopyright, ppszOptions[opt]); if (strlen(pszOptionValue) > CBMAXCOPYRIGHT) { pszOptionValue[CBMAXCOPYRIGHT + 1] = '\0'; VReportError(HCERR_CPRIGHT_TOO_BIG, &errHpj, pszOptionValue); } { PSTR psz; if ((psz = strstr(pszOptionValue, "%date"))) { time_t ltime; time(<ime); *psz = '\0'; CStr csz(pszOptionValue); if (pszOptionValue != psz) csz += "\r\n"; csz += ctime(<ime); PSTR pszCr = strrchr(csz.psz, '\n'); if (pszCr) *pszCr = '\0'; psz = FirstNonSpace(psz + 5); if ((psz = strstr(psz, "%ver"))) { csz += "\r\n"; csz += GetStringResource(IDS_VERSION); psz = FirstNonSpace(psz + 4); } csz += psz; options.pszCopyright = lcStrDup(csz.psz); } else options.pszCopyright = lcStrDup(pszOptionValue); if (!options.pszCopyright) { OOM(); } } break; case OPT_CNT: if (options.pszCntFile) ReportDuplicateOption(&options.pszCntFile, ppszOptions[opt]); // REVIEW: we should nag if the .CNT file doesn't exist options.pszCntFile = lcStrDup(pszOptionValue); if (!options.pszCntFile) OOM(); break; case OPT_CITATION: if (options.pszCitation) ReportDuplicateOption(&options.pszCitation, ppszOptions[opt]); options.pszCitation = lcStrDup(pszOptionValue); break; case OPT_VERSION: if (*pszOptionValue != '4' || pszOptionValue[2] != '0') VReportError(HCERR_INVALID_VERSION, &errHpj, pszOptionValue); break; case OPT_NOTE: switch (YesNo(pszOptionValue)) { case IDYES: options.fSupressNotes = FALSE; break; case IDNO: options.fSupressNotes = TRUE; break; default: ReportBadOption(pszOptionValue, ppszOptions[opt]); break; } break; case OPT_LCID: // new to 4.0 // LCID = lcid [case-sensitive flags] [case-insensitive flags] // This option automatically uses KT_NLS for keyword and browse // sorts if (kwlcid.langid) VReportError(HCERR_DUPLICATE_OPTION, &errHpj, ppszOptions[opt]); if (!isdigit((BYTE) *pszOptionValue)) { ReportBadOption(pszOptionValue, ppszOptions[opt]); break; } if (!FGetUnsigned(pszOptionValue, &pszOptionValue, &kwlcid.langid)) { ReportBadOption(pszOptionValue, ppszOptions[opt]); break; } if (!IsEmptyString(pszOptionValue)) { if (!FGetUnsigned(pszOptionValue, &pszOptionValue, &kwlcid.fsCompareI)) { ReportBadOption(pszOptionValue, ppszOptions[opt]); break; } if (!IsEmptyString(pszOptionValue)) { if (!FGetUnsigned(pszOptionValue, &pszOptionValue, &kwlcid.fsCompare)) { ReportBadOption(pszOptionValue, ppszOptions[opt]); break; } } } if (kwlcid.langid) { lcid = MAKELCID(kwlcid.langid, SORT_DEFAULT); ktKeywordi = KT_NLSI; ktKeyword = KT_NLS; fValidLcid = FALSE; EnumSystemLocales(EnumLocalesProc, LCID_SUPPORTED); if (!fValidLcid) { char szLcid[20]; wsprintf(szLcid, "0x%x", kwlcid.langid); VReportError(HCERR_INVALID_LCID, &errHpj, szLcid); lcid = 0; // shouldn't get here, but just in case... } } else lcid = 0; break; case OPT_DBCS: switch (YesNo(pszOptionValue)) { case IDYES: options.fDBCS = TRUE; break; case IDNO: options.fDBCS = FALSE; break; default: ReportBadOption(pszOptionValue, ppszOptions[opt]); break; } break; case OPT_TMPDIR: if (options.pszTmpDir) ReportDuplicateOption(&options.pszTmpDir, ppszOptions[opt]); if (_access(options.pszTmpDir, 0) != 0) { VReportError(HCERR_INVALID_TMP_DIR, &errHpj, pszOptionValue); break; } AddTrailingBackslash(pszOptionValue); options.pszTmpDir = lcStrDup(pszOptionValue); if (hwndParent) { CreateSharedMemory(); strcpy(pszMap, options.pszTmpDir); SendMessage(hwndParent, WMP_SET_TMPDIR, 0, 0); } break; case OPT_REPLACE: { if (options.pszReplace) { ReportDuplicateOption(&options.pszReplace, ppszOptions[opt]); lcClearFree(&options.pszReplaceWith); } psz = StrRChr(pszOptionValue, '=', fDBCSSystem); if (!psz) { VReportError(HCERR_INVALID_REPLACE, &errHpj, pszOptionValue); break; } *psz++ = '\0'; SzTrimSz(psz); options.pszReplaceWith = lcStrDup(psz); SzTrimSz(pszOptionValue); options.pszReplace = lcStrDup(pszOptionValue); } break; case OPT_CHARSET: { DWORD val; if (!FGetNum(pszOptionValue, NULL, &val)) ReportBadOption(pszOptionValue, ppszOptions[opt]); else defCharSet = (BYTE) val; } break; case OPT_DEFFONT: if (options.pszDefFont) ReportDuplicateOption(&options.pszIndexSeparators, ppszOptions[opt]); options.pszDefFont = (PSTR) lcMalloc(strlen(pszOptionValue)); psz = StrChr(pszOptionValue, ',', fDBCSSystem); if (!psz || !isdigit((BYTE) psz[1])) { ReportBadOption(pszOptionValue, ppszOptions[opt]); lcClearFree(&options.pszDefFont); break; } /* * The first byte is the point size, the second byte is the * charset, and the font name comes after that. */ options.pszDefFont[0] = (BYTE) atoi(psz + 1); *psz = '\0'; psz = StrChr(psz + 2, ',', fDBCSSystem); if (!psz || !isdigit((BYTE) psz[1])) { ReportBadOption(pszOptionValue, ppszOptions[opt]); lcClearFree(&options.pszDefFont); break; } options.pszDefFont[1] = (BYTE) atoi(psz + 1); strcpy(options.pszDefFont + 2, SzTrimSz(pszOptionValue)); break; case OPT_PREFIX: if (!ptblCtxPrefixes) ptblCtxPrefixes = new CTable; psz = StrToken(pszOptionValue, ", "); while (psz) { psz = SzTrimSz(psz); if (*psz) ptblCtxPrefixes->AddString(psz); psz = StrToken(NULL, ", "); } break; case OPT_REVISIONS: switch (YesNo(pszOptionValue)) { case IDYES: options.fAcceptRevions = TRUE; break; case IDNO: options.fAcceptRevions = FALSE; break; default: ReportBadOption(pszOptionValue, ppszOptions[opt]); break; } break; case OPT_INDEX: if (options.pszIndexSeparators) ReportDuplicateOption(&options.pszIndexSeparators, ppszOptions[opt]); if (IsQuote(*pszOptionValue)) { psz = pszOptionValue + 1; while (!IsQuote(*psz) && *psz) psz = CharNext(psz); *psz = '\0'; SzTrimSz(pszOptionValue + 1); options.pszIndexSeparators = lcStrDup(pszOptionValue + 1); } else options.pszIndexSeparators = lcStrDup(pszOptionValue); break; default: return RC_Failure; break; } return RC_Success; } __inline BOOL STDCALL FVerifyBuildTag(PSTR psz) { for (; *psz; ++psz) { if (!IsCharAlphaNumeric(*psz) && *psz != '_') return FALSE; } return TRUE; } /***************************************************************************\ * - Function: RcParseBuildTagsSz( sz) - * Purpose: Parse a line in the [buildtags] section. (see grammar) * * ASSUMES * * args IN: sz - line to parse, with comments stripped. * Valid build tags consist of the characters * [A-Za-z0-9_.], with lower mapped to upper case. * * PROMISES * * returns: success code. REVIEW * * +++ * Method: * \***************************************************************************/ RC_TYPE STDCALL RcParseBuildTagsSz(PSTR pszBuildTag) { if (lcid) { int cbSrc = strlen(pszBuildTag); int cbDst; CMem mem(cbSrc + 16); if ((cbDst = LCMapString(lcid, LCMAP_UPPERCASE, pszBuildTag, cbSrc, mem.psz, cbSrc + 16)) > 0) { strncpy(pszBuildTag, mem.psz, cbDst); pszBuildTag[cbDst + 1] ='\0'; } } else { CharUpper(pszBuildTag); } if (!FVerifyBuildTag(pszBuildTag)) VReportError(HCERR_INVALID_BUILD_TAG, &errHpj, pszBuildTag); else if (ptblBuildtags && ptblBuildtags->IsStringInTable(pszBuildTag)) VReportError(HCERR_DUP_BUILD_TAG, &errHpj, pszBuildTag); else { if (!ptblBuildtags) ptblBuildtags = new CTable; if (!ptblBuildtags->AddString(pszBuildTag)) OOM(); } return RC_Success; } /***************************************************************************\ * - Function: RcParseFilesSz( sz ) - * Purpose: Parse a line in the [files] section (filename, see grammar). * Verify that the filename is properly formed, resolve it * relative to root, verify that the file exists and isn't * a directory or device. Store the file name. * ASSUMES * * args IN: sz - line to parse, with comments stripped. * * * PROMISES * * returns: success code. REVIEW * * +++ * Method: * \***************************************************************************/ RC_TYPE STDCALL RcParseFilesSz(PSTR sz) { HCE hce; char szFile[_MAX_PATH]; if (options.ptblFileRoot) hce = HceResolveTableDir(sz, options.ptblFileRoot, szFile, NULL); else { PSTR pszRoot = SzGetDriveAndDir(errHpj.lpszFile, NULL); hce = HceResolveFileNameSz(sz, pszRoot, szFile); lcFree(pszRoot); } if (hce != HCE_OK) VReportError(HCERR_CANNOT_OPEN, &errHpj, sz); else ptblRtfFiles->AddString(szFile); return RC_Success; } /***************************************************************************\ * - Function: RcParseBaggageSz( sz ) - * Purpose: Parse a line in the [baggage] section (filename, see grammar). * Verify that the filename is properly formed, resolve it * relative to root, verify that the file exists and isn't * a directory or device. Store the file name. * ASSUMES * * args IN: sz - line to parse, with comments stripped. * * * PROMISES * * returns: success code. REVIEW * * +++ * Method: * \***************************************************************************/ RC_TYPE STDCALL RcParseBaggageSz(PSTR psz) { HCE hce = HCE_FILE_NOT_FOUND; char szFile[_MAX_PATH]; if (options.ptblFileRoot) hce = HceResolveTableDir(psz, options.ptblFileRoot, szFile, NULL); if (hce != HCE_OK) { PSTR pszRoot = SzGetDriveAndDir(errHpj.lpszFile, NULL); hce = HceResolveFileNameSz(psz, pszRoot, szFile); lcFree(pszRoot); } if (hce != HCE_OK) VReportError(HCERR_CANNOT_OPEN, &errHpj, psz); else { if (!ptblBaggage) ptblBaggage = new CTable; CharUpper(szFile); if (ptblBaggage->IsCSStringInTable(szFile)) return RC_Success; // we already have it if (!ptblBaggage->AddString(szFile)) OOM(); // this won't return } return RC_Success; } /***************************************************************************\ * - Function: RcParseBitmapsSz( sz ) - * Purpose: Parse a line in the [bitmaps] section (filename, see grammar). * Verify that the filename is properly formed, resolve it * relative to BMroot, verify that the file exists and isn't * a directory or device. Store the file name. * ASSUMES * * args IN: sz - line to parse, with comments stripped. * * * PROMISES * * returns: success code. REVIEW * * +++ * Method: * \***************************************************************************/ RC_TYPE STDCALL RcParseBitmapsSz(PSTR psz) { AddBitmap(psz, NULL, FALSE); return RC_Success; } /***************************************************************************\ * - Function: RcParseMapSz( sz ) - * Purpose: Parse a line in the [map] section. (see grammar) * * :== | * "#define" * * ASSUMES * * args IN: sz - line to parse, with comments stripped. * * * PROMISES * * returns: success code. REVIEW * * +++ * Method: * \***************************************************************************/ RC_TYPE STDCALL RcParseMapSz(PSTR pszLine) { MAP map; PSTR pszTmp; DWORD imap, cmap; QMAP qmap; // If this #define was used for a #ifdef or #ifndef then ignore it if (nstrisubcmp(pszLine, txtDefine) && ptblDefine->IsStringInTable( FirstNonSpace(pszLine + strlen(txtDefine) + 1, fDBCSSystem))) return RC_Success; if (!pdrgMap) pdrgMap = new CDrg(sizeof(MAP), 5, 5); if (!ptblMap) { ptblMap = new CTable; ptblMap->SetSorting(lcid, kwlcid.fsCompareI, kwlcid.fsCompare); } // Assume RcGetLogicalLine() has removed all comments within a line, // and never returns a blank line. ASSERT(*pszLine); /* * New for 4.0 is to look for an '='. This is the only way you can * map a context string containing a space. */ if (!(pszTmp = StrChr(pszLine, CH_EQUAL, fDBCSSystem))) pszTmp = SkipToEndOfWord(pszLine); if (!pszTmp || !*pszTmp) { VReportError(HCERR_NO_MAP_VALUE, &errHpj, pszLine); return RC_Success; } if (nstrisubcmp(pszLine, txtDefine)) { pszLine = FirstNonSpace(pszLine + strlen(txtDefine), fDBCSSystem); if (*pszTmp != CH_EQUAL) pszTmp = SkipToEndOfWord(pszLine); if (!*pszTmp) { VReportError(HCERR_NO_MAP_VALUE, &errHpj, pszLine); return RC_Success; } } *pszTmp = '\0'; SzTrimSz(pszLine); if (!FValidContextSz(pszLine)) { VReportError(HCERR_INVALID_CTX, &errHpj, pszLine); return RC_Success; } // Special case no-help constant if (nstrsubcmp(FirstNonSpace(pszTmp + 1, fDBCSSystem), "((DWORD) -1)")) return RC_Success; map.hash = HashFromSz(pszLine); if (ptblMap->IsHashInTable(map.hash)) { VReportError(HCERR_MAP_USED, &errHpj, pszLine); return RC_Success; } PSTR pszSave = pszLine; pszLine = SzTrimSz(pszTmp + 1); if (!FGetUnsigned(pszLine, &pszTmp, &map.ctx)) { VReportError(HCERR_INVALID_MAP_NUMBER, &errHpj, pszLine, pszSave); return RC_Success; } if (*pszTmp != '\0') // Nag, but otherwise continue VReportError(HCERR_MAP_TEXT_UNEXPECT, &errHpj, pszSave, pszTmp); // make sure this CTX isn't already mapped to another context string if ((cmap = pdrgMap->Count()) != 0) { for (imap = 0, qmap = (QMAP) pdrgMap->GetBasePtr(); imap < cmap; imap++, qmap++) { if (qmap->ctx == map.ctx) { // REVIEW: we should nag, but supposedly, it's okay to do this VReportError(HCERR_MAP_VALUE_DUP, &errHpj, pszSave, (PCSTR) ptblMap->GetPointer(qmap->pos) + sizeof(HASH)); return RC_Success; } } } ASSERT(map.hash == HashFromSz(pszSave)); map.pos = ptblMap->AddString(map.hash, pszSave); pdrgMap->Add(&map); return RC_Success; } /***************************************************************************\ * - Function: RcParseAliasSz( sz) - * Purpose: Parse a line in the [alias] section. (see grammar) * * :== "=" * * :== [A-Za-z0-9_.]{[A-Za-z0-9_.!]} * * ASSUMES * * args IN: sz - line to parse, with comments stripped. * * * PROMISES * * returns: success code. REVIEW * * +++ * Method: * \***************************************************************************/ RC_TYPE STDCALL RcParseAliasSz(PSTR pszLine) { PSTR pszAlias; ALIAS alias; DWORD ialias, calias; QALIAS qalias; ASSERT(!StrChr(pszLine, CH_SEMICOLON, fDBCSSystem)); if (!(pszAlias = StrChr(pszLine, CH_EQUAL, fDBCSSystem))) { VReportError(HCERR_MISS_ALIAS_EQ, &errHpj, pszLine); return RC_Success; } CStr cszOrg(SzTrimSz(pszLine)); *pszAlias++ = '\0'; SzTrimSz(pszLine); if (!FValidContextSz(pszLine)) { VReportError(HCERR_INVALID_CTX, &errHpj, pszLine); return RC_Success; } alias.hashAlias = HashFromSz(pszLine); SzTrimSz(pszAlias); if (!FValidContextSz(pszAlias)) { VReportError(HCERR_INVALID_CTX, &errHpj, pszAlias); return RC_Success; } alias.hashCtx = HashFromSz(pszAlias); if (alias.hashAlias == alias.hashCtx) { VReportError(HCERR_ALIAS_EQ_CTX, &errHpj, cszOrg.psz); return RC_Success; } if (!pdrgAlias) pdrgAlias = new CDrg(sizeof(ALIAS), 5, 5); if ((calias = pdrgAlias->Count()) > 0) { for (ialias = 0, qalias = (QALIAS) pdrgAlias->GetBasePtr(); ialias < calias; ialias++, qalias++) { if (alias.hashAlias == qalias->hashAlias) { VReportError(HCERR_DEFINED_ALIAS, &errHpj, pszLine, cszOrg.psz); return RC_Success; } else if (alias.hashAlias == qalias->hashCtx) { VReportError(HCERR_DUP_ALIAS, &errHpj, pszLine, cszOrg.psz); return RC_Success; } else if (alias.hashCtx == qalias->hashAlias) // REVIEW: 27-Mar-1994 [ralphw] Does this work? alias.hashCtx = qalias->hashCtx; } } alias.szCtx = lcStrDup(pszAlias); pdrgAlias->Add(&alias); return RC_Success; } /***************************************************************************\ * - Function: RcParseConfigSz( sz ) - * Purpose: Parse a line in the [config] section. * The config section has structure unknown to this * parser. All we do is strip comments. Currently * we don't recognize quoted strings (to allow ;). * * ASSUMES * * args IN: sz - line to parse, with comments stripped. * * * PROMISES * * returns: success code. REVIEW * * +++ * Method: Each macro is added as a null terminated string * to ptblConfig. * \***************************************************************************/ RC_TYPE STDCALL RcParseConfigSz(PSTR psz) { if (!ptblConfig) ptblConfig = new CTable; if (Execute(psz) == wMACRO_EXPANSION) { ASSERT((size_t) lcSize(psz) > strlen(GetMacroExpansion())); strcpy(psz, GetMacroExpansion()); } // Add the macro even if it generated an error if (!ptblConfig->AddString(psz)) OOM(); return RC_Success; } /*************************************************************************** FUNCTION: RcParseSecondaryConfigSz PURPOSE: Adds configuration information to a secondary window PARAMETERS: psz RETURNS: COMMENTS: Assumes curConfig points to the current config number in the array of configuration tables MODIFICATION DATES: 10-Jul-1994 [ralphw] ***************************************************************************/ RC_TYPE STDCALL RcParseSecondaryConfigSz(PSTR psz) { if (!pptblConfig[curConfig]) pptblConfig[curConfig] = new CTable; if (Execute(psz) == wMACRO_EXPANSION) { ASSERT((size_t) lcSize(psz) > strlen(GetMacroExpansion())); strcpy(psz, GetMacroExpansion()); } // Add the macro even if it generated an error if (!pptblConfig[curConfig]->AddString(psz)) OOM(); return RC_Success; } /***************************************************************************\ * - Function: FReadInt16() - * Purpose: Read an int from a buffer. * Legal syntax is an int followed by a comma or by nothing. * On success, advance the pointer past the following comma. * A missing number isn't an error. * If a number is actually read, set a flag. * * ASSUMES * args IN: psz - pointer to sz containing number * pi - pointer to int * pgrf - pointer to flag word * f - flag to set in pgrf * * PROMISES * returns: WINDOW_READ_INVALID - syntax error * WINDOW_READ_MISSING - no number, but syntax OK * WINDOW_READ_OK - a valid number was read * * args OUT: psz - sz advanced past following comma * pi - value of int copied here on success * pgrf - *pgrf != flag if number is present * \***************************************************************************/ static UINT STDCALL FReadInt16(PSTR *psz, WORD* pi, WORD *pgrf, UINT f) { PSTR pszTmp; pszTmp = FirstNonSpace(*psz, fDBCSSystem); if (*pszTmp == 'f') pszTmp++; if (*pszTmp == '\0') goto UseDefault; if (*pszTmp == ',') { *psz = pszTmp + 1; UseDefault: if (f == FWSMAG_X || f == FWSMAG_Y || f == FWSMAG_DX || f == FWSMAG_DY) { *pi = (WORD) -1; return WINDOW_READ_OK; } else return WINDOW_READ_MISSING; } LONG l; if (!FGetNum(pszTmp, &pszTmp, &l)) return WINDOW_READ_INVALID; if (l == -1 && (f == FWSMAG_X || f == FWSMAG_Y || f == FWSMAG_DX || f == FWSMAG_DY)) { *pi = (WORD) -1; return WINDOW_READ_OK; } *pi = (WORD) l; // hack to include new possible flags for wsmag.wMax if (f == FWSMAG_MAXIMIZE) { if (*pi & FWSMAG_WMAX_MAXIMIZE) *pgrf |= f; } else *pgrf |= f; pszTmp = FirstNonSpace(pszTmp, fDBCSSystem); if (*pszTmp == ',') { *psz = pszTmp + 1; return WINDOW_READ_OK; } *psz = pszTmp; return (*pszTmp == '\0') ? WINDOW_READ_OK : WINDOW_READ_INVALID; } /***************************************************************************\ * - Function: FReadRgb( psz, pl, pgrf, f ) - * Purpose: Read an RGB triple from a buffer. * Legal syntax is nothing, or a parenthesized list * of three ints (all must be there). * On success, advance the pointer past the following comma. * A missing triple isn't an error. * If a triple is actually read, set a flag. * * ASSUMES * args IN: psz - pointer to sz containing (r,g,b) * pl - pointer to long * pgrf - pointer to flag word * f - flag to set in pgrf * * PROMISES * returns: WINDOW_READ_INVALID - syntax error * WINDOW_READ_MISSING - no number, but syntax OK * WINDOW_READ_OK - a valid number was read * * args OUT: psz - sz advanced past following comma * pl - value of rgb copied here on success. * We use the RGB() macro above (REVIEW!!) * pgrf - *pgrf != flag if number is present * \***************************************************************************/ static UINT STDCALL FReadRgb(PSTR *ppsz, LONG *pl, UINT *pgrf, UINT f) { UINT grf = 0; WORD r, g, b; PSTR psz = FirstNonSpace(*ppsz, fDBCSSystem); PSTR pszTmp; if (*psz == '\0') return WINDOW_READ_MISSING; if (*psz == ',') { *ppsz = psz + 1; return WINDOW_READ_MISSING; } if (*psz == '(') { psz = FirstNonSpace(psz + 1, fDBCSSystem); if (!(pszTmp = StrChr(psz, CH_CLOSE_PAREN, fDBCSSystem))) return WINDOW_READ_INVALID; *pszTmp = '\0'; pszTmp = FirstNonSpace(pszTmp + 1, fDBCSSystem); if (*pszTmp == ',') ++pszTmp; *ppsz = pszTmp; if (*psz == 'r') { // short format produced by HCW *pl = atol(psz + 1); *pgrf |= f; return WINDOW_READ_OK; } if (WINDOW_READ_OK != FReadInt16(&psz, &r, (WORD *) pgrf, f) || WINDOW_READ_OK != FReadInt16(&psz, &g, (WORD *) pgrf, f) || WINDOW_READ_OK != FReadInt16(&psz, &b, (WORD *) pgrf, f)) { *pgrf &= ~f; return WINDOW_READ_INVALID; } if (r < 0 || r >= 256 || g < 0 || g >= 256 || b < 0 || b >= 256) return WINDOW_READ_INVALID; // REVIEW - different error code? *pl = RGB(r, g, b); return WINDOW_READ_OK; } else return WINDOW_READ_INVALID; } /***************************************************************************\ * - Function: RcParseWindowsSz( sz ) - * Purpose: Parse a line in the [windows] section. Syntax: * * = ["caption"],(X,Y,dX,dy),[fMax],[(r,g,b)],[(r,g,b)] * * If member is "main", class is also "main" and position * is not required. * Otherwise class is "secondary" and position is mandatory. * ASSUMES * * args IN: sz - line to parse, with comments stripped * * PROMISES * returns: success code. * * Notes: The WSMAG structure has provisions for multiple * secondary window classes and multiple members of * each class. This parser doesn't allow it, though. * +++ * * Method: * \***************************************************************************/ RC_TYPE STDCALL RcParseWindowsSz(PSTR pszLine) { PSTR pszTmp, pszRHS, pszMember, pszCaption; WSMAG wsmag; CStr szOriginal(pszLine); memset(&wsmag, 0, sizeof(wsmag)); // clear out all values pszRHS = StrChr(pszLine, '=', fDBCSSystem); if (pszRHS == NULL) { VReportError(HCERR_NOEQ_IN_WIN, &errHpj, pszLine); return RC_Success; } else { *pszRHS++ = '\0'; SzTrimSz(pszRHS); } // Deal with "member=" case for the whiners. if (*pszRHS == '\0') { VReportError(HCERR_NOTHING_AFTER_EQ, &errHpj, pszLine); return RC_Success; } pszMember = SzTrimSz(pszLine); // REVIEW: don't allow a window name to begin with an '@' -- WinHelp // special-cases this character. PCSTR pszClass = (PCSTR) ((_stricmp(pszMember, txtMainWindow) == 0) ? txtMainWindow : txtSecondary); if (strlen(pszMember) >= MAX_WINDOW_NAME) { // If the window name is too long, truncate it and complain CStr cstr(pszMember); pszMember[MAX_WINDOW_NAME] = '\0'; VReportError(HCERR_WIN_NAME_TOO_LONG, &errHpj, (PCSTR) cstr, pszMember); } else if (!*pszMember) { VReportError(HCERR_NOTHING_AFTER_EQ, &errHpj, szOriginal); return RC_Success; } strcpy(wsmag.rgchClass, pszClass); strcpy(wsmag.rgchMember, pszMember); CharLower(wsmag.rgchMember); // for consistency if (!ptblWindowNames) ptblWindowNames = new CTable; if (ptblWindowNames->IsCSStringInTable(wsmag.rgchMember)) { VReportError(HCERR_DUPLICATE_NAME, &errHpj, wsmag.rgchMember); return RC_Success; } ptblWindowNames->AddString(wsmag.rgchMember); wsmag.grf = FWSMAG_CLASS | FWSMAG_MEMBER; if (*pszRHS == CH_QUOTE) { // Caption: "string" pszCaption = pszRHS + 1; pszTmp = StrChr(pszCaption, CH_QUOTE, fDBCSSystem); if (pszTmp == NULL) { VReportError(HCERR_MISSING_CAPT_QUOTE, &errHpj, szOriginal); return RC_Success; } else { *pszTmp = '\0'; if (strlen(pszCaption) >= MAX_WINDOWCAPTION) VReportError(HCERR_CAPTION_TOO_LONG, &errHpj, szOriginal); wsmag.grf |= FWSMAG_CAPTION; strncpy(wsmag.rgchCaption, pszCaption, MAX_WINDOWCAPTION); wsmag.rgchCaption[MAX_WINDOWCAPTION] = '\0'; } pszRHS = FirstNonSpace(pszTmp + 1, fDBCSSystem); } /* * New to HCW, we no longer require the leading comma if a window * title isn't specified. */ if (*pszRHS != ',') { if (*pszRHS == '\0') { pszLine = pszRHS; goto normal_return; } else { pszRHS--; // so that next check will skip over this } } pszLine = FirstNonSpace(pszRHS + 1, fDBCSSystem); // Position: (int, int, int, int) or (0) for default if (*pszLine == '(') { PSTR pszSaveLine = pszLine; // save the position for error reporting ++pszLine; pszTmp = StrChr(pszLine, CH_CLOSE_PAREN, fDBCSSystem); if (!pszTmp) goto error_return; *pszTmp = '\0'; // REVIEW: will this handle missing items? if (FReadInt16(&pszLine, &wsmag.x, &wsmag.grf, FWSMAG_X) != WINDOW_READ_OK || FReadInt16(&pszLine, &wsmag.y, &wsmag.grf, FWSMAG_Y) != WINDOW_READ_OK || FReadInt16(&pszLine, &wsmag.dx, &wsmag.grf, FWSMAG_DX) != WINDOW_READ_OK || FReadInt16(&pszLine, &wsmag.dy, &wsmag.grf, FWSMAG_DY) != WINDOW_READ_OK) { *pszTmp++ = CH_CLOSE_PAREN; *pszTmp = '\0'; VReportError(HCERR_INVALID_WIN_POS, &errHpj, pszSaveLine); return RC_Success; } pszLine = FirstNonSpace(pszTmp + 1, fDBCSSystem); } if (*pszLine == ',') pszLine = FirstNonSpace(pszLine + 1, fDBCSSystem); else if (*pszLine == '\0') goto normal_return; else { VReportError(HCERR_INVALID_WIN_SYNTAX, &errHpj, szOriginal); return RC_Success; } // Max, rgbMain, rgbNsr: [int], [(int,int,int)], [(int,int,int)] if (!FReadInt16(&pszLine, &wsmag.wMax, &wsmag.grf, FWSMAG_MAXIMIZE)) { VReportError(HCERR_INVALID_WIN_SYNTAX, &errHpj, szOriginal); return RC_Success; } // version 3 files only understand maximize flag if (version < 4) wsmag.wMax &= 1; if (!FReadRgb(&pszLine, &wsmag.rgbMain, (UINT*) &wsmag.grf, FWSMAG_RGBMAIN) || !FReadRgb(&pszLine, &wsmag.rgbNSR, (UINT*) &wsmag.grf, FWSMAG_RGBNSR)) { VReportError(HCERR_INVALID_RGB, &errHpj, szOriginal); return RC_Success; } { WORD fsWin = 0; if (!FReadInt16(&pszLine, &fsWin, &wsmag.grf, 0)) { VReportError(HCERR_INVALID_WIN_SYNTAX, &errHpj, szOriginal); return RC_Success; } if (fsWin & AUTHOR_WINDOW_ON_TOP) wsmag.grf |= FWSMAG_ON_TOP; if (fsWin & AUTHOR_AUTO_SIZE) wsmag.grf |= FWSMAG_AUTO_SIZE; if (fsWin & AUTHOR_ABSOLUTE) wsmag.grf |= FWSMAG_ABSOLUTE; } normal_return: ASSERT(wsmag.grf & FWSMAG_CLASS); // Check for trailing garbage if (*FirstNonSpace(pszLine, fDBCSSystem) != '\0') { VReportError(HCERR_INVALID_WIN_SYNTAX, &errHpj, szOriginal); return RC_Success; } if (wsmag.wMax & 1) wsmag.grf |= FWSMAG_MAXIMIZE; // range check position if (!(wsmag.grf & AUTHOR_ABSOLUTE) && (wsmag.x >= dxVirtScreen || wsmag.y >= dyVirtScreen || wsmag.x + wsmag.dx >= dxVirtScreen || wsmag.y + wsmag.dy >= dyVirtScreen)) { VReportError(HCERR_INVALID_WIN_RANGE, &errHpj, szOriginal); return RC_Success; } if (!pdrgWsmag) pdrgWsmag = new CDrg(sizeof(WSMAG), 1); if ((cwsmag = pdrgWsmag->Count()) > 0) { if (cwsmag > MAX_WINDOWS) { VReportError(HCERR_256_WINDOWS, &errHpj); return RC_Success; } } pdrgWsmag->Add(&wsmag); if (wsmag.wMax & FWSMAG_WMAX_BROWSE) fBrowseButtonSet = TRUE; // Keep track of the number of windows added, so that we know how many // configuration sections to write out. cwsmag = pdrgWsmag->Count(); return RC_Success; error_return: return RC_Success; } /***************************************************************************\ * - Function: FPushFilePfs( pfs, szFile, perr ) - * Purpose: Open and push an #included file onto the file stack. * Emit an error message if it doesn't work. * * ASSUMES * * args IN: pfs - file stack * szFile - file name * * PROMISES * * returns: TRUE on success; FALSE on failure * * args OUT: pfs - file is pushed if no stack overflow and file can be * opened * * Side Effects: Possible errors: * * hceFileStackOverflow - #includes nested too deeply * hceFileNameTooLong - file name too long * etc. (REVIEW) * +++ * Method: pfs->ifeTop == -1 if the stack is empty. * Otherwise it is the index of the currently open file entry. * * Unless szFile is absolute (drive or rooted), it is taken * to be relative to the path of the previous file on the * stack. For the first file pushed, it is relative to the * current directory. * \***************************************************************************/ static BOOL STDCALL FPushFilePfs(PSTR szFile) { ASSERT(pfs->ifeTop >= -1); ASSERT(pfs->ifeTop <= MAX_INCLUDE); if (++pfs->ifeTop == MAX_INCLUDE) { VReportError(HCERR_TOO_MANY_INCLUDES, &errHpj, szFile); --pfs->ifeTop; return FALSE; } else { PFE pfe = &pfs->rgfe[pfs->ifeTop]; PSTR pszRoot; char szDir[_MAX_DIR]; if (pfs->ifeTop == 0) pszRoot = NULL; else pszRoot = SzGetDriveAndDir(pfe[-1].rgchFile, szDir); HCE hce = HceResolveFileNameSz(szFile, pszRoot, pfe->rgchFile, NULL, &pfe->pinput); if (hce != HCE_OK) { pfs->ifeTop--; VReportError(HCERR_CANNOT_OPEN, &errHpj, szFile); return FALSE; } pfe->iLine = 0; errHpj.lpszFile = pfe->rgchFile; errHpj.iLine = pfe->iLine; } return TRUE; } /***************************************************************************\ * - Function: FPopPfs( pfs, perr ) - * Purpose: Remove the top file entry from the file stack. * * ASSUMES * * args IN: pfs - file stack * perr - error info struct * * PROMISES * * returns: TRUE on success; FALSE on failure * * args OUT: pfs->rgfe[ pfs->ifeTop ].pf gets closed. * pfs->ifeTop is decremented if stack nonempty * perr - pchFile and iLine set equal to top of stack * +++ * * Method: This can be a macro. * \***************************************************************************/ __inline BOOL STDCALL FPopPfs(void) { if (pfs->ifeTop >= 0) { ASSERT(PfTopPfs() != NULL); delete PfTopPfs(); --pfs->ifeTop; PFE pfe = &pfs->rgfe[pfs->ifeTop]; if (pfs->ifeTop >= 0) { errHpj.lpszFile = pfe->rgchFile; errHpj.iLine = pfe->iLine; } return TRUE; } return FALSE; } /***************************************************************************\ * - Function: PfTopPfs( pfs ) - * Purpose: Return pointer to FILE * on top of stack. * * ASSUMES * * args IN: pfs * * PROMISES * * returns: success: valid FILE * from top of stack * failure: NULL (when stack is empty) * * +++ * * Method: This can be a macro. * \***************************************************************************/ __inline CInput* STDCALL PfTopPfs(void) { return (pfs->ifeTop >= 0) ? pfs->rgfe[pfs->ifeTop].pinput : NULL; } /*************************************************************************** FUNCTION: RcGetLogicalLine PURPOSE: Read a line, stripping comments, blank lines, and handling nested include files. PARAMETERS: pcszDst -- CStr object which will expand as necessary to fit the longest line. RETURNS: COMMENTS: MODIFICATION DATES: 05-Sep-1994 [ralphw] ***************************************************************************/ static char txtInclude[] = "#include"; static RC_TYPE STDCALL RcGetLogicalLine(CStr* pcszDst) { CInput* pin = PfTopPfs(); if (!pin) return RC_EOF; for (;;) { if (!pin->getline(pcszDst)) { // Close current file, continue with nested file if there is one FPopPfs(); if (!(pin = PfTopPfs())) return RC_EOF; continue; } PSTR pszDst = pcszDst->psz; // purely for our notational convenience if (*pszDst == CH_SPACE || *pszDst == CH_TAB) strcpy(pszDst, FirstNonSpace(pszDst, fDBCSSystem)); PSTR psz = pszDst; switch (*psz) { case 0: case ';': continue; case '#': if (nstrisubcmp(psz, txtInclude)) { // process #include psz = FirstNonSpace(pszDst + strlen(txtInclude), fDBCSSystem); if (!psz) { VReportError(HCERR_NOINCLUDE_FILE, &errHpj); continue; } PSTR pszEnd; if (*psz == CH_QUOTE || *psz == '<') { char ch = (*psz == CH_QUOTE) ? CH_QUOTE : '>'; psz++; pszEnd = StrChrDBCS(psz, ch); if (*pszEnd) *pszEnd = '\0'; } if (pszEnd = StrChrDBCS(psz, ';')) *pszEnd = '\0'; if (pszEnd = strstr(psz, "//")) *pszEnd = '\0'; if (pszEnd = strstr(psz, "/*")) *pszEnd = '\0'; SzTrimSz(psz); FPushFilePfs(psz); if (!(pin = PfTopPfs())) return RC_EOF; continue; } else if (nstrisubcmp(psz, txtDefine)) goto ValidString; else if (nstrisubcmp(psz, txtIfDef)) ptblDefine->AddString( FirstNonSpace(psz + strlen(txtIfDef) + 1, fDBCSSystem)); else if (nstrisubcmp(psz, txtIfnDef)) ptblDefine->AddString( FirstNonSpace(psz + strlen(txtIfnDef) + 1, fDBCSSystem)); // REVIEW: process #ifdef, #ifndef, #else, #endif continue; default: // We have a valid string. if (psz != pszDst) strcpy(pszDst, psz); // Remove any comments ValidString: if (options.fDBCS) { for (psz = pszDst; *psz; psz = CharNext(psz)) { if (IsQuote(*psz)) { psz++; while (!IsQuote(*psz) && *psz) psz = CharNext(psz); } if (*psz == ';') { *psz = '\0'; break; } } for (psz = pszDst; *psz; psz = CharNext(psz)) { if (IsQuote(*psz)) { psz++; while (!IsQuote(*psz) && *psz) psz = CharNext(psz); } if (*psz == '/' && psz[1] == '/') { *psz = '\0'; break; } } } else { for (psz = pszDst; *psz; psz++) { if (IsQuote(*psz)) { psz++; while (!IsQuote(*psz) && *psz) psz++; } if (*psz == ';') { *psz = '\0'; break; } } for (psz = pszDst; *psz; psz++) { if (IsQuote(*psz)) { psz++; while (!IsQuote(*psz) && *psz) psz++; } if (*psz == '/' && psz[1] == '/') { *psz = '\0'; break; } } } while ((psz = strstr(pszDst, "/*"))) { PSTR pszTmp = strstr(psz, "*/"); if (pszTmp) strcpy(psz, FirstNonSpace(pszTmp + 2, fDBCSSystem)); else { char szBuf[512]; do { if (!pin->getline(szBuf)) { /* * Close current file, continue with nested * file if there is one */ FPopPfs(); if (!(pin = PfTopPfs())) return RC_EOF; continue; } } while (!(pszTmp = strstr(szBuf, "*/"))); strcpy(psz, FirstNonSpace(pszTmp + 2, fDBCSSystem)); // New line could have comments, so start all over goto ValidString; } } SzTrimSz(pszDst); if (!*pszDst) continue; return RC_Success; } } } /***************************************************************************\ * - Function: FInitializeHpj() - * Purpose: Take an HPJ and set to uninitialized state. * Assume fields are garbage (i.e. don't try to free stuff). * * ASSUMES * * * PROMISES * * returns: TRUE if successful, FALSE if OOM. * * * globals OUT: * * state OUT: * * Side Effects: * * Notes: Should there be different defaults for normal and * IsRTF() case? * * Bugs: Doesn't free memory * * +++ * * Method: * * Notes: * \***************************************************************************/ BOOL STDCALL FInitializeHpj(void) { // Error Info errHpj.iWarningLevel = 3; errHpj.ep = epNoFile; // File Stack pfs = (PFILESTACK) lcMalloc(sizeof(FILESTACK)); pfs->ifeTop = -1; pfs->ifeComment = -1; pfs->iCommentLine = 0; // Options options.sortorder = SORT_ENGLISH; options.pszContentsTopic = NULL; options.iWarningLevel = 3; options.fUsePhrase = TRUE; // REVIEW options.fAcceptRevions = TRUE; Ensure(HceAddPkwiCh('K'), HCE_OK); Ensure(HceAddPkwiCh('A'), HCE_OK); // for ALinks ptblRtfFiles = new CTable; // Compile time variables nsr = nsrNone; return TRUE; } /***************************************************************************\ * - Function: FParseHpj( szFile ) - * Purpose: Parse the named project file, filling in the HPJ structure. * * ASSUMES * * args IN: szFile - name of project file (default .HPJ extension) * * PROMISES * * returns: TRUE - parsed OK * FALSE - HPJ file unusable, bad file extension * - ran out of memory * * * Side Effects: emits error messages * - Notes: - - Bugs: Doesn't free discarded and unlock retained smag. REVIEW! - * +++ - - Method: - - Notes: - \***************************************************************************/ BOOL STDCALL FParseHpj(PSTR pszFile) { PFPARSE pfparse = NULL; RC_TYPE rc = RC_Success; PSTR psz; if (iflags.fRtfInput) { // This is an RTF file, not an HPJ file ptblRtfFiles->AddString(pszFile); return TRUE; } for(;;) { // not a real for loop, just gives us something to break out of /* * If a path was specified, then try to change to that drive and * directory. If we can, then remove the path, and just leave the * filename. Makes for cleaner error output by removing the path * name of the .HPJ file, and allows other files to be relative to * the current directory. */ if ((psz = StrRChr(pszFile, CH_BACKSLASH, fDBCSSystem))) { *psz = '\0'; if (_chdir(pszFile) != 0) { *psz = CH_BACKSLASH; break; } if (pszFile[1] == ':') { if (_chdrive(tolower(pszFile[0]) - ('a' - 1)) != 0) { *psz = CH_BACKSLASH; break; } } strcpy(pszFile, psz + 1); } break; } errHpj.lpszFile = pszFile; // extension defaults to szInputExt (.HPJ) // REVIEW: why strip off the path? 03-Jan-1994 [ralphw] // [olympus 306 - chauv] for (psz = pszFile + strlen(pszFile) - 1; psz > pszFile; psz--) { // if it's a backslash, check to see if it's a trailing backslash if (*psz == '\\') { if ( ((psz-1) >= pszFile) && IsDBCSLeadByte(*(psz-1)) ) psz--; else break; } if ( (*psz == '/') || (*psz == ':') || (*psz == '.') ) break; } char szHpjName[_MAX_PATH]; if (*psz != '.') { strcpy(szHpjName, pszFile); strcat(szHpjName, GetStringResource(IDS_HPJ_EXTENSION)); pszFile = szHpjName; } if (FIsRtf(pszFile)) { // REVIEW: 23-Jul-1993 [ralphw] why // are we allowing them to open an RTF file? return FALSE; } if (!FPushFilePfs(pszFile)) { lcFree(pfs); return FALSE; } errHpj.ep = epLine; CStr cszDst; /* * We want automatic deletion of the table, but we want it to be * available to other functions while we're processing the .HPJ file, so * we simply set a global pointer to the address of our local table. * Tacky, but it works. */ CTable tbl; ptblDefine = &tbl; while (rc == RC_Success) { rc = RcGetLogicalLine(&cszDst); SpecialParsing: if (RC_Success != rc) break; if (*cszDst.psz == CH_LEFT_BRACKET) { psz = StrChr(cszDst, CH_RIGHT_BRACKET, fDBCSSystem); if (!psz) { VReportError(HCERR_MISSING_SECTION_BRACKET, &errHpj, cszDst); } else { *psz = '\0'; psz = SzTrimSz(cszDst.psz + 1); pfparse = PfparseFromSz(psz); /* * We don't parse the [MACROS] section normally. This * section cannot contain a #include line, and cannot contain * any comments. So the usual line cleanup don by * RcGetLogicalLine() would destroy the information we need. * Instead, we let RcParseMacros read its entire section. */ if (pfparse == RcParseMacros) { rc = ParseMacros(&cszDst); if (rc == RC_Success) goto SpecialParsing; else { // might have been end of a nested include rc = RcGetLogicalLine(&cszDst); goto SpecialParsing; } } } } else { if (pfparse == NULL) { VReportError(HCERR_MISSING_SECTION, &errHpj, cszDst); pfparse = RcParseBogusSz; } else { rc = pfparse(cszDst.psz); // REVIEW - under what conditions term? lcHeapCheck(); if (rc == RC_SkipSection) { pfparse = RcParseBogusSz; rc = RC_Success; } } } } if (rc == RC_EOF) { rc = RC_Success; /* * If no compression, we still use RLE compression for bitmaps. * This is an insignificant speed hit with a significant size * reduction. */ if (options.fsCompress == COMPRESS_NONE) options.fsCompress = COMPRESS_BMP_RLE; if (kwlcid.langid) SetDbcsFlag(kwlcid.langid); if (FTestUlFlag(options.lOptionInitFlags, OPT_BUILD)) { if (!FTestUFlag(wSectionInitFlags, SECT_BUILDTAGS)) { fBldChk = -1; VReportError(HCERR_BUILD_TAG_MISSING, &errHpj); } else if (!FBuildPolishExpFromSz(options.szBuildExp)) { fBldChk = -1; // REVIEW - ugly global VReportError(HCERR_INVALID_BUILD_EXP, &errHpj); } else fBldChk = 1; // REVIEW - ugly global } if (!FTestUFlag(wSectionInitFlags, SECT_FILES) || ptblRtfFiles->CountStrings() < 1) { VReportError(HCERR_NOFILES_DEFINED, &errHpj); rc = RC_Failure; goto error_return; } // Reset error phase. errHpj.ep = epNoFile; lcFree(pfs); return (rc == RC_Success); } error_return: lcFree(pfs); return (rc == RC_Success); } const char txtCopyright[] = "Copyright (c) Microsoft Corp 1990 - 1994. All rights reserved."; static void STDCALL DispSignOn(void) { pLogFile->outstring_eol(GetStringResource(IDS_TITLE)); pLogFile->outstring_eol(GetStringResource(IDS_VERSION)); pLogFile->outstring_eol((PSTR) txtCopyright); } PSTR STDCALL SkipToEndOfWord(PSTR psz) { while (*psz != SPACE && *psz != CHAR_TAB && *psz) psz = CharNext(psz); return psz; } /***************************************************************************\ * - Function: SzLoseDriveAndDir( szFile, rgch ) - * Purpose: Return the base name + ext from a file name. * * ASSUMES * * args IN: szFile - a filespec that may or may not have drive and/or * directory. It isn't NULL or "". * rgch - buffer to receive the info. If NULL, a buffer * is allocated. * PROMISES * * returns: pointer to buffer where the base + ext are placed. * This may be allocated (see rgch above). * * args OUT: rgch - data put here, except as stated above * * Side Effects: may allocate memory * \***************************************************************************/ void STDCALL SzLoseDriveAndDir(PSTR szFile, PSTR pszDst) { ASSERT(szFile != NULL); // [olympus 306 - chauv] // use _splitpath() to do the work char fname[_MAX_FNAME], ext[_MAX_EXT]; _splitpath(szFile, NULL, NULL, fname, ext); strcpy(pszDst, fname); strcat(pszDst, ext); } /***************************************************************************\ * - Function: PfparseFromSz( szSection) - * Purpose: Return a section parsing function based on section name. * Emit message on error. * * ASSUMES * * args IN: szSection - the section name to look up * - if ->wSectionInitFlags section flag already * set, emit message and return bogus function * * globals IN: rgSection - array of section names * * PROMISES * * returns: Pointer to a section parsing function. If szSection * wasn't a valid section name, return a default function. * * args OUT: wSectionInitFlags - section flag set * * +++ * * Method: Linear search. Could use binary search. * \***************************************************************************/ static PFPARSE STDCALL PfparseFromSz(PSTR pszSection) { int sect; ASSERT(SECT_MAX < ELEMENTS(rgSection)); for (sect = 0; sect < SECT_MAX; sect++) { if (_stricmp(rgSection[sect].szName, pszSection) == 0) break; } if (sect == SECT_MAX) { if (nstrisubcmp(pszSection, "CONFIG")) { // Named config section if (pszSection[6] == '-') { if (pdrgWsmag) { for (int i = 0; i < pdrgWsmag->Count(); i++) { WSMAG *pwsmag = ((WSMAG *) pdrgWsmag->GetBasePtr()) + i; if (!_stricmp(pszSection + 7, pwsmag->rgchMember)) { curConfig = i; return RcParseSecondaryConfigSz; } } } } // Numbered config section else if (pszSection[6] == ':') { if (FGetNum(pszSection + 7, NULL, &curConfig)) { return RcParseSecondaryConfigSz; } } } VReportError(HCERR_UNKNOWN_SECTION, &errHpj, pszSection); } if (sect == SECT_OPTIONS && !FTestUFlag(wSectionInitFlags, SECT_OPTIONS) && (FTestUFlag(wSectionInitFlags, SECT_BITMAPS) || FTestUFlag(wSectionInitFlags, SECT_FILES))) VReportError(HCERR_SECTION_TOO_SOON, &errHpj, pszSection); SetUFlag(wSectionInitFlags, sect); // set even if bogus section return rgSection[sect].pfparse; } RC_TYPE STDCALL RcParseCharSet(PSTR pszLine) { PSTR pszCharSet; if (!(pszCharSet = StrChr(pszLine, CH_EQUAL, fDBCSSystem))) { VReportError(HCERR_MISS_CHARSET_EQ, &errHpj, pszLine); return RC_Success; } *pszCharSet = '\0'; pszCharSet = FirstNonSpace(pszCharSet + 1, fDBCSSystem); if (!isdigit((BYTE) *pszCharSet)) { VReportError(HCERR_INVALID_CHARSET, &errHpj, pszLine); return RC_Success; } SzTrimSz(pszLine); if (!ptblCharSet) ptblCharSet = new CTable; SzTrimSz(pszCharSet); if (atoi(pszCharSet) > 255 || atoi(pszCharSet) < 0) { VReportError(HCERR_INVALID_CHARSET, &errHpj, pszLine); return RC_Success; } ptblCharSet->AddString(pszLine, pszCharSet); return RC_Success; } RC_TYPE STDCALL RcParseFonts(PSTR pszLine) { CFontMap* pMap = new CFontMap(pszLine); if (!pMap->IsInitialized()) delete pMap; return RC_Success; } static void STDCALL ReportDuplicateOption(PSTR* ppszOption, PSTR pszName) { VReportError(HCERR_DUPLICATE_OPTION, &errHpj, pszName); if (ppszOption) lcClearFree(ppszOption); } static void STDCALL ReportBadOption(PSTR pszOptionValue, PSTR pszOption) { VReportError(HCERR_INVALID_OPTION, &errHpj, pszOption, pszOptionValue); } /*************************************************************************** FUNCTION: CreateSharedMemory PURPOSE: Create shared memory for communicating with our parent PARAMETERS: void RETURNS: COMMENTS: MODIFICATION DATES: 02-Jul-1994 [ralphw] ***************************************************************************/ void STDCALL CreateSharedMemory(void) { if (!hfShare) { hfShare = CreateFileMapping((HANDLE) -1, NULL, PAGE_READWRITE, 0, 4096, txtSharedMem); ConfirmOrDie(hfShare); pszMap = (PSTR) MapViewOfFile(hfShare, FILE_MAP_WRITE, 0, 0, 0); ASSERT(pszMap); } } // This function only exists for match purposes. It is never called RC_TYPE STDCALL RcParseMacros(PSTR pszLine) { ASSERT(!"This function should never be called!"); return RC_Success; } /*************************************************************************** FUNCTION: ParseMacros PURPOSE: This creates two tables. The is a double-string table containing each macro and title string pair. There is a keyword table -- with each keyword having its own entry including a position into the associated macro/title table. PARAMETERS: pcszLine RETURNS: COMMENTS: MODIFICATION DATES: 05-Sep-1994 [ralphw] ***************************************************************************/ static RC_TYPE STDCALL ParseMacros(CStr* pcszLine) { if (!ptblMacKeywords) { ptblMacKeywords = new CTable; ptblMacroTitles = new CTable; } CStr cszMacro; CStr cszTitle; int pos; CInput* pin = PfTopPfs(); if (!pin) return RC_Success; for (;;) { if (!pin->getline(pcszLine)) { return RC_EOF; } if (*pcszLine->psz == CH_LEFT_BRACKET) return RC_Success; else if (!*pcszLine->psz) continue; // blank line -- shouldn't happen, but we'll allow it PSTR pszKey = SzParseList(pcszLine->psz); if (pszKey == NULL) { VReportError(HCERR_NULL_KEYWORD, &errHpj); return RC_EOF; } if (!pin->getline(&cszMacro)) { VReportError(HCERR_NULL_KEYWORD, &errHpj); return RC_EOF; } if (!pin->getline(&cszTitle)) { VReportError(HCERR_NULL_KEYWORD, &errHpj); return RC_EOF; } pos = ptblMacroTitles->AddString(cszMacro); ptblMacroTitles->AddString(cszTitle); while (pszKey) { ptblMacKeywords->AddIntAndString(pos, pszKey); pszKey = SzParseList(NULL); } } } static char szLcid[20]; BOOL CALLBACK EnumLocalesProc(LPSTR pszLocale) { if (!szLcid[0]) wsprintf(szLcid, "%08x", kwlcid.langid); if (stricmp(pszLocale, szLcid) == 0) { fValidLcid = TRUE; return FALSE; } return TRUE; } /*************************************************************************** FUNCTION: SetDbcsFlag PURPOSE: Try to force DBCS flag based on language ID PARAMETERS: langid RETURNS: COMMENTS: MODIFICATION DATES: 11-Jan-1995 [ralphw] ***************************************************************************/ void STDCALL SetDbcsFlag(LANGID langid) { switch (langid) { case 0x0411: // Japanese case 0x0404: // Taiwan case 0x1004: // Singapore case 0x0C04: // Hong Kong options.fDBCS = TRUE; break; case 0x0409: // American case 0x0C09: // Australian case 0x0C07: // Austrian case 0x042D: // Basque case 0x080C: // Belgian case 0x0809: // British case 0x0402: // Bulgaria case 0x1009: // Canadian case 0x041A: // Croatian case 0x0405: // Czech case 0x0406: // Danish case 0x0413: // Dutch (Standard) case 0x0C01: // Egypt case 0x040B: // Finnish case 0x040C: // French (Standard) case 0x0C0C: // French Canadian case 0x0407: // German (Standard) case 0x042E: // Germany case 0x0408: // Greek case 0x040E: // Hungarian case 0x040F: // Icelandic case 0x0801: // Iraq case 0x1809: // Ireland case 0x040D: // Israel case 0x0410: // Italian (Standard) case 0x2C01: // Jordan case 0x3401: // Kuwait case 0x0426: // Latvia case 0x3001: // Lebanon case 0x1001: // Libya case 0x1407: // Liechtenstein case 0x0427: // Lithuania case 0x140C: // Luxembourg (French) case 0x1007: // Luxembourg (German) case 0x042f: // Macedonia case 0x080A: // Mexican case 0x0819: // Moldavia case 0x0818: // Moldavia case 0x1801: // Morocco case 0x1409: // New Zealand case 0x0414: // Norwegian (Bokmal) case 0x0814: // Norwegian (Nynorsk) case 0x2001: // Oman case 0x0415: // Polish case 0x0416: // Portuguese (Brazilian) case 0x0816: // Portuguese (Standard) case 0x0418: // Romania case 0x0419: // Russian case 0x0401: // Saudi Arabia case 0x081A: // Serbian case 0x041B: // Slovak case 0x0424: // Slovenia case 0x0C0A: // Spanish (Modern Sort) case 0x040A: // Spanish (Traditional Sort) case 0x0430: // Sutu case 0x041D: // Swedish case 0x100C: // Swiss (French) case 0x0807: // Swiss (German) case 0x0810: // Swiss (Italian) case 0x2801: // Syria case 0x041E: // Thailand case 0x0431: // Tsonga case 0x041f: // Turkish case 0x3801: // U.A.E. case 0x0422: // Ukraine case 0x0420: // Urdu case 0x0436: // Zulu options.fDBCS = FALSE; break; } }