Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3309 lines
78 KiB

/***************************************************************************
*
* 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 <errno.h> // ERANGE
#include <io.h>
#include <direct.h>
#include <time.h>
#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" "=" <number> "-" <number> ":" <number> |
* "mapfontsize" "=" <number> ":" <number>
* <number> 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 <keyword> = [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(&ltime);
*psz = '\0';
CStr csz(pszOptionValue);
if (pszOptionValue != psz)
csz += "\r\n";
csz += ctime(&ltime);
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)
*
* <map statement> :== <context string> <number> |
* "#define" <context string> <number>
*
* 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)
*
* <alias statement> :== <context string> "=" <context string>
*
* <context string> :== [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:
*
* <member> = ["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;
}
}