/*++
Copyright (c) 1995 Microsoft Corporation
Module Name:
prodfilt.c
Abstract:
This module implements a program that filters text files
to produce a product-specific output file.
See below for more information.
Author:
Ted Miller (tedm) 20-May-1995
Revision History:
--*/
/*
The input file to this program consists of a series of lines.
Each line may be prefixed with one or more directives that
indicate which product the line is a part of. Lines that are
not prefixed are part of all products.
The command line is as follows:
prodfilt +tag
For example,
[Files]
@w:wksprog1.exe
@w:wksprog2.exe
@s:srvprog1.exe
@s:srvprog2.exe
comprog1.exe
@@:comprog2.exe
The files wksprog1.exe and wksprog2.exe are part of product w
and the files srvprog1.exe and srvprog2.exe are part of product s.
Comprpg1.exe and comprog2.exe are part of both products.
Specifying +w on the command line produces
[Files]
wksprog1.exe
wksprog2.exe
comprog1.ee
comprog2.exe
in the output.
*/
#include
#include
#include
//
// Define program result codes (returned from main()).
//
#define SUCCESS 0
#define FAILURE 1
//
// Tag definitions.
//
LPCTSTR TagPrefixStr = TEXT("@");
TCHAR TagPrefix = TEXT('@');
TCHAR EndTag = TEXT(':');
TCHAR ExcludeTag = TEXT('!');
TCHAR IncAllTag = TEXT('@');
TCHAR NoIncTag = TEXT('*');
#define TAG_PREFIX_LENGTH 1
#define MIN_TAG_LEN 3
#define EXCLUDE_PREFIX_LENGTH 2 // ! Prefix Length (!n)
//
// Here is the translation for the character symbols used in IncludeTag
// and SynonymTag.
//
// products:
// @w -> wks
// @s -> srv
// @p -> personal (NOT professional)
// @t -> tablet
// @b -> blade server
// @l -> small business server
// @e -> enterprise
// @d -> datacenter
//
// architectures:
// @i -> intel (i386)
// @n -> intel (nec98)
// @m -> intel (ia64)
// @a -> AMD64 (amd64)
//
// Note that @n is a subset of @i. If @i is specified then you also
// get the file include for @n unless you explicitly exclude them
// with a token like @i!n
//
// @3 -> 32 bit (i386+?)
// @6 -> 64 bit (ia64+amd64)
//
TCHAR IncludeTag[][3] = {
{ TEXT('i'), TEXT('n'), 0}, // @i -> i or n, @i!n -> only i
{ TEXT('s'), TEXT('e'), 0}, // @s -> s or e, @s!e -> only s
{ TEXT('s'), TEXT('b'), 0}, // @s -> s or b, @s!b -> only s
{ TEXT('s'), TEXT('d'), 0},
{ TEXT('s'), TEXT('l'), 0},
{ TEXT('e'), TEXT('d'), 0},
{ TEXT('w'), TEXT('p'), 0},
{ TEXT('w'), TEXT('t'), 0},
{ 0 , 0 , 0}
};
TCHAR SynonymTag[][2] = {
{ TEXT('3'), TEXT('i')},
{ TEXT('6'), TEXT('a')},
{ TEXT('6'), TEXT('m')},
{ 0, 0}
};
LPCTSTR InName,OutName;
TCHAR Filter;
BOOL
FileExists(
IN PCTSTR FileName,
OUT PWIN32_FIND_DATA FindData OPTIONAL
)
/*++
Routine Description:
Determine if a file exists and is accessible.
Errormode is set (and then restored) so the user will not see
any pop-ups.
Arguments:
FileName - supplies full path of file to check for existance.
FindData - if specified, receives find data for the file.
Return Value:
TRUE if the file exists and is accessible.
FALSE if not. GetLastError() returns extended error info.
--*/
{
WIN32_FIND_DATA findData;
HANDLE FindHandle;
UINT OldMode;
DWORD Error;
OldMode = SetErrorMode(SEM_FAILCRITICALERRORS);
FindHandle = FindFirstFile(FileName,&findData);
if (FindHandle == INVALID_HANDLE_VALUE) {
Error = GetLastError();
} else {
FindClose(FindHandle);
if (FindData) {
*FindData = findData;
}
Error = NO_ERROR;
}
SetErrorMode(OldMode);
SetLastError(Error);
return (Error == NO_ERROR);
}
void
StripCommentsFromLine(
TCHAR *Line
)
/*
Routine Description:
Strips comments (; Comment) from a line respecting ; inside quotes
Arguments:
Line - POinter to Line to process
*/
{
PWSTR p;
BOOL Done = FALSE, InQuotes = FALSE;
//
// we need to insert a NULL at the first ';' we find,
// thereby stripping the comments
//
p = Line;
if ( !p )
return;
while (*p && !Done) {
switch (*p) {
case TEXT(';'):
if ( !InQuotes ) {
*p=L'\r';
*(p+1)=L'\n';
*(p+2)=L'\0';
Done = TRUE;
}
break;
case TEXT('\"'):
if ( *(p+1) && (*(p+1) == TEXT('\"'))) // Ignore double-quoting as inside quotes
p++;
else
InQuotes = !InQuotes;
default:
;
}
p++;
}// while
return;
}
DWORD
MakeSurePathExists(
IN PCTSTR FullFilespec
)
/*++
Routine Description:
This routine ensures that a multi-level path exists by creating individual
levels one at a time. It is assumed that the caller will pass in a *filename*
whose path needs to exist. Some examples:
c:\x - C:\ is assumes to always exist.
c:\x\y\z - Ensure that c:\x\y exists.
\x\y\z - \x\y on current drive
x\y - x in current directory
d:x\y - d:x
\\server\share\p\file - \\server\share\p
Arguments:
FullFilespec - supplies the *filename* of a file that the caller wants to
create. This routine creates the *path* to that file, in other words,
the final component is assumed to be a filename, and is not a
directory name. (This routine doesn't actually create this file.)
If this is invalid, then the results are undefined (for example,
passing \\server\share, C:\, or C:).
Return Value:
Win32 error code indicating outcome. If FullFilespec is invalid,
*may* return ERROR_INVALID_NAME.
--*/
{
TCHAR Buffer[MAX_PATH];
PTCHAR p,q;
BOOL Done;
DWORD d;
WIN32_FIND_DATA FindData;
//
// The first thing we do is locate and strip off the final component,
// which is assumed to be the filename. If there are no path separator
// chars then assume we have a filename in the current directory and
// that we have nothing to do.
//
// Note that if the caller passed an invalid FullFilespec then this might
// hose us up. For example, \\x\y will result in \\x. We rely on logic
// in the rest of the routine to catch this.
//
lstrcpyn(Buffer,FullFilespec,MAX_PATH);
if (Buffer[0] && (p = _tcsrchr(Buffer,TEXT('\\'))) && (p != Buffer)) {
*p = 0;
} else {
return (NO_ERROR);
}
if (Buffer[0] == TEXT('\\')) {
if (Buffer[1] == TEXT('\\')) {
//
// UNC. Locate the second component, which is the share name.
// If there's no share name then the original FullFilespec
// was invalid. Finally locate the first path-sep char in the
// drive-relative part of the name. Note that there might not
// be such a char (when the file is in the root). Then skip
// the path-sep char.
//
if (!Buffer[2] || (Buffer[2] == TEXT('\\'))) {
return (ERROR_INVALID_NAME);
}
p = _tcschr(&Buffer[3],TEXT('\\'));
if (!p || (p[1] == 0) || (p[1] == TEXT('\\'))) {
return (ERROR_INVALID_NAME);
}
if (q = _tcschr(p+2,TEXT('\\'))) {
q++;
} else {
return (NO_ERROR);
}
} else {
//
// Assume it's a local root-based local path like \x\y.
//
q = Buffer+1;
}
} else {
if (Buffer[1] == TEXT(':')) {
//
// Assume c:x\y or maybe c:\x\y
//
q = (Buffer[2] == TEXT('\\')) ? &Buffer[3] : &Buffer[2];
} else {
//
// Assume path like x\y\z
//
q = Buffer;
}
}
//
// Ignore drive roots.
//
if (*q == 0) {
return (NO_ERROR);
}
//
// If it already exists do nothing.
//
if (FileExists(Buffer,&FindData)) {
return ((FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? NO_ERROR : ERROR_DIRECTORY);
}
Done = FALSE;
do {
//
// Locate the next path sep char. If there is none then
// this is the deepest level of the path.
//
if (p = _tcschr(q,TEXT('\\'))) {
*p = 0;
} else {
Done = TRUE;
}
//
// Create this portion of the path.
//
if (CreateDirectory(Buffer,NULL)) {
d = NO_ERROR;
} else {
d = GetLastError();
if (d == ERROR_ALREADY_EXISTS) {
d = NO_ERROR;
}
}
if (d == NO_ERROR) {
//
// Put back the path sep and move to the next component.
//
if (!Done) {
*p = TEXT('\\');
q = p+1;
}
} else {
Done = TRUE;
}
} while (!Done);
return (d);
}
BOOL
ParseArgs(
IN int argc,
IN TCHAR *argv[],
OUT BOOL *Unicode,
OUT BOOL *StripComments
)
{
int argoffset = 0;
int loopcount;
*Unicode = FALSE;
*StripComments = FALSE;
if (argc == 5) {
//
// 1 switch possible
//
argoffset = 1;
loopcount = 1;
} else if (argc == 6) {
//
// 2 switches possible
//
argoffset = 2;
loopcount = 2;
} else if (argc != 4) {
return (FALSE);
} else {
argoffset = 0;
}
if ((argc == 5) || (argc == 6)) {
int i;
for (i=0; i< loopcount; i++) {
if (!_tcsicmp(argv[i+1],TEXT("/u")) || !_tcsicmp(argv[i+1],TEXT("-u"))) {
*Unicode = TRUE;
}
if (!_tcsicmp(argv[i+1],TEXT("/s")) || !_tcsicmp(argv[i+1],TEXT("-s"))) {
*StripComments = TRUE;
}
}
}
InName = argv[1+argoffset];
OutName = argv[2+argoffset];
Filter = argv[3+argoffset][1];
if (argv[3+argoffset][0] != TEXT('+')) {
return (FALSE);
}
return (TRUE);
}
BOOL
DoTagsMatch(
IN TCHAR LineChar,
IN TCHAR Tag
)
{
int i, j;
BOOL ReturnValue = FALSE;
BOOL TagIsInList = FALSE;
BOOL LineCharIsInList = FALSE;
//
// If they match, we're done.
//
if ( LineChar == Tag ) {
ReturnValue = TRUE;
} else {
//
// Nope. See if we can match a synonym tag.
//
i = 0;
while ( SynonymTag[i][0] ) {
TagIsInList = FALSE;
LineCharIsInList = FALSE;
for ( j = 0; j < 2; j++ ) {
if ( Tag == SynonymTag[i][j] ) {
TagIsInList = TRUE;
}
if ( LineChar == SynonymTag[i][j] ) {
LineCharIsInList = TRUE;
}
}
if ( TagIsInList && LineCharIsInList ) {
ReturnValue = TRUE;
}
i++;
}
}
return ReturnValue;
}
BOOL
DoFilter(
IN FILE *InputFile,
IN FILE *OutputFile,
IN TCHAR Tag,
IN BOOL UnicodeFileIO,
IN BOOL StripComments
)
{
TCHAR Line[1024];
TCHAR *OutputLine;
BOOL FirstLine=TRUE;
BOOL WriteUnicodeHeader = TRUE;
while (!feof(InputFile)) {
//
// read a line of data. if we're doing unicode IO, we just read the
// data and use it otherwise we need to read the data and
//
if (UnicodeFileIO) {
if (!fgetws(Line,sizeof(Line)/sizeof(Line[0]),InputFile)) {
if (ferror(InputFile)) {
_ftprintf(stderr,TEXT("Error reading from input file\n"));
return (FALSE);
} else {
return (TRUE);
}
}
//
// Skip byte order mark if present
//
if (FirstLine) {
if (Line[0] == 0xfeff) {
MoveMemory(Line,Line+1,sizeof(Line)-sizeof(TCHAR));
}
}
} else {
char LineA[1024];
if (!fgets(LineA,sizeof(LineA),InputFile)) {
if (ferror(InputFile)) {
_ftprintf(stderr,TEXT("Error reading from input file\n"));
return (FALSE);
} else {
return (TRUE);
}
}
if (!MultiByteToWideChar(
CP_ACP,
MB_PRECOMPOSED,
LineA,
-1,
Line,
sizeof(Line)/sizeof(WCHAR)
)) {
_ftprintf(stderr,TEXT("Error reading input file\n"));
return (FALSE);
}
}
//
// ok, we've retreived the line. now let's see if we want to output
// the line.
//
OutputLine = Line;
//
// if the line is too short, then we just want to include it no matter
// what.
//
if (_tcslen(Line) >= MIN_TAG_LEN) {
int i;
//
// if the line starts with our tag, then we need to look further
// to see if it should be filtered.
//
if (!_tcsncmp(Line,TagPrefixStr,TAG_PREFIX_LENGTH)) {
//
// is the symbol string an @: combination?
//
if (Line[TAG_PREFIX_LENGTH+1] == EndTag) {
OutputLine = NULL;
//
// Do we have @@: or @:, where matches the tag
// prodfilt was invoked with?
//
if ( (Line[TAG_PREFIX_LENGTH] == IncAllTag) ||
DoTagsMatch(Line[TAG_PREFIX_LENGTH],Tag) ||
(Tag == IncAllTag &&
(Line[TAG_PREFIX_LENGTH] != NoIncTag)) ) {
//
// Yes. Include this line.
//
OutputLine = Line+MIN_TAG_LEN;
} else {
//
// we don't have an explicit match, so let's look to
// see if we have a match by virtue of another tag
// including our tag
//
// To do this, we look at the include tag list to see
// if the line matches the head of an include tag
// entry. If we have a match, then we check if we have
// a match in the specified inclusion entry for our tag
// (or one if it's synonyms)
//
//
int j;
i = 0;
while (IncludeTag[i][0] && !OutputLine) {
j = 1;
if (DoTagsMatch(Line[TAG_PREFIX_LENGTH],
IncludeTag[i][0])) {
//
// we found a match at the start of an include
// entry.
//
while (IncludeTag[i][j]) {
if (DoTagsMatch(
IncludeTag[i][j],
Tag)) {
//
// We found an included match for our
// tag. Include this line.
//
OutputLine = Line+MIN_TAG_LEN;
break;
}
j++;
}
}
i++;
}
}
//
// is the line long enough to have an @! sequence?
//
} else if (_tcslen(Line) >=
(MIN_TAG_LEN+EXCLUDE_PREFIX_LENGTH)) {
//
// Does the line have @! syntax?
//
if (Line[TAG_PREFIX_LENGTH+1] == ExcludeTag) {
TCHAR * tmpPtr = &Line[TAG_PREFIX_LENGTH+1];
OutputLine = NULL;
//
// We have @! syntax. We first need to
// see if the line is included by virtue of .
//
// If that succeeds, then we proceed onto reading the
// !! block, looking for another hit.
//
//
// do we have an explicit match?
//
if ( Line[TAG_PREFIX_LENGTH] == IncAllTag ||
DoTagsMatch(Line[TAG_PREFIX_LENGTH],Tag) ||
(Tag == IncAllTag &&
Line[TAG_PREFIX_LENGTH] != NoIncTag) ) {
//
// Yes, we have an explicit match. Remember this
// so we can parse the ! sequence.
//
OutputLine = Line+MIN_TAG_LEN;
} else {
//
// we don't have an explicit match, so let's look to
// see if we have a match by virtue of another tag
// including our tag
//
// To do this, we look at the include tag list to
// see if the line matches the head of an include
// tag entry. If we have a match, then we check if
// we have a match in the specified inclusion entry
// for our tag (or one if it's synonyms)
//
//
int j;
i = 0;
while (IncludeTag[i][0] && !OutputLine) {
j = 1;
if (DoTagsMatch(Line[TAG_PREFIX_LENGTH],
IncludeTag[i][0])) {
//
// we found a match at the start of an
// include entry.
//
while (IncludeTag[i][j]) {
if (DoTagsMatch(
IncludeTag[i][j],
Tag)) {
//
// We found an included match for
// our tag. Include this line.
//
OutputLine = Line+MIN_TAG_LEN;
break;
}
j++;
}
}
i++;
}
}
if (!OutputLine) {
//
// We didn't match teh initial @ sequence, so
// there is no need to check further. goto the
// next line.
//
goto ProcessNextLine;
}
//
// The line has ![!] combination
// Loop through the chain of exclude chars and see if
// we have any hits. if we do, then we go onto the
// next line.
//
while (tmpPtr[0] == ExcludeTag) {
//
// do we have an explicit match?
//
if ( (tmpPtr[TAG_PREFIX_LENGTH] == IncAllTag) ||
DoTagsMatch(tmpPtr[TAG_PREFIX_LENGTH],Tag) ) {
//
// We have an explicit match, so we know we
// do not want to include this line.
//
//
OutputLine = NULL;
goto ProcessNextLine;
} else {
//
// we don't have an explicit match, so let's
// look to see if we have a match by virtue
// of another tag including our tag
//
// To do this, we look at the include tag list
// to see if the line matches the head of an
// include tag entry. If we have a match, then
// we check if we have a match in the specified
// inclusion entry for our tag (or one if it's
// synonyms)
//
//
int j;
i = 0;
while (IncludeTag[i][0]) {
j = 1;
if (DoTagsMatch(
tmpPtr[TAG_PREFIX_LENGTH],
IncludeTag[i][0])) {
//
// we found a match at the start of an
// include entry.
//
while (IncludeTag[i][j]) {
if (DoTagsMatch(
IncludeTag[i][j],
Tag)) {
//
// We found an included match
// for our tag, so we know we
// do not want to include this
// line.
//
OutputLine = NULL;
goto ProcessNextLine;
}
j++;
}
}
i++;
}
}
tmpPtr += 2;
}
//
// Done parsing the @!!!... tokens.
// Look for the terminator
//
if (tmpPtr[0] != EndTag) {
//
// Malformed tokens. Let's error on the
// conservative side and include the whole line.
//
OutputLine = Line;
} else {
//
// Didn't find any exclusions, so include the tag.
//
OutputLine = &tmpPtr[1];
}
}
}
}
}
ProcessNextLine:
//
// write out the line if we're supposed to. for unicode io we just
// write the file. for ansi i/o we have to convert back to ansi
//
if (OutputLine) {
if (StripComments) {
StripCommentsFromLine( OutputLine );
}
if (UnicodeFileIO) {
if (WriteUnicodeHeader) {
fputwc(0xfeff,OutputFile);
WriteUnicodeHeader = FALSE;
}
if (fputws(OutputLine,OutputFile) == EOF) {
_ftprintf(stderr,TEXT("Error writing to output file\n"));
return (FALSE);
}
} else {
CHAR OutputLineA[1024];
if (!WideCharToMultiByte(
CP_ACP,
0,
OutputLine,
-1,
OutputLineA,
sizeof(OutputLineA)/sizeof(CHAR),
NULL,
NULL)) {
_ftprintf(
stderr,
TEXT("Error translating string for output file\n") );
return (FALSE);
}
if (!fputs(OutputLineA,OutputFile) == EOF) {
_ftprintf(stderr,TEXT("Error writing to output file\n"));
return (FALSE);
}
}
}
}
if (ferror(InputFile)) {
_ftprintf(stderr,TEXT("Error reading from input file\n"));
return (FALSE);
}
return (TRUE);
}
int
__cdecl
_tmain(
IN int argc,
IN TCHAR *argv[]
)
{
FILE *InputFile;
FILE *OutputFile;
BOOL b;
BOOL Unicode,StripComments;
//
// Assume failure.
//
b = FALSE;
if (ParseArgs(argc,argv,&Unicode,&StripComments)) {
//
// Open the files. Have to open in binary mode or else
// CRT converts unicode text to mbcs on way in and out.
//
if (InputFile = _tfopen(InName,TEXT("rb"))) {
if (MakeSurePathExists(OutName) == NO_ERROR) {
if (OutputFile = _tfopen(OutName,TEXT("wb"))) {
//
// Do the filtering operation.
//
_ftprintf(stdout,
TEXT("%s: filtering %s to %s\n"),
argv[0],
InName,
OutName);
b = DoFilter(InputFile,
OutputFile,
Filter,
Unicode,
StripComments);
fclose(OutputFile);
} else {
_ftprintf(stderr,
TEXT("%s: Unable to create output file %s\n"),
argv[0],
OutName);
}
} else {
_ftprintf(stderr,
TEXT("%s: Unable to create output path %s\n"),
argv[0],
OutName);
}
fclose(InputFile);
} else {
_ftprintf(stderr,
TEXT("%s: Unable to open input file %s\n"),
argv[0],
InName);
}
} else {
_ftprintf(stderr,
TEXT("Usage: %s [-u (unicode IO)] [-s (strip comments)] +\n"),
argv[0]);
}
return (b ? SUCCESS : FAILURE);
}