/*++ Copyright (c) 1989 Microsoft Corporation Module Name: mapmsg.c Abstract: This utility will create an input file for MC from specially formatted include files. This is used to create DLL's which can be used by the message utilities to get message text to display. The format of the header files is: : : #define : : #define + /* text of message */ /* Example: #define NETBASE 1000 #define NerrFOO NETBASE+1 /* A FOO has been encountered at %1 * / /* The mapping tries to be generous about whitespace and parenthesis. It will also handle comments across several lines. Some important points: - all continuations must begin with [WS]'*' any whitespace at the beginning of a message is removed unless the -p command line option is specified. - #define ..... /* * FOO */ /* is handled correctly. The command line to MAPMSG is: mapmsg [-p] [-a appendfile] Example: mapmsg NET NERRBASE neterr.h > neterr.mc The is the 3 character name required by the mkmsg input. The output is written to stdout. If the append file is given, the output is appropriately appended to an existing mkmsgf source file. An optional @X, X: {E, W, I, P} can be the 1st non-WS chars of the comment field. The letter (E, W, I, or P) will be the message type. See MKMSGF documentation for an explaination of the message types. The default type is E. The @X must appear on the same line as the #define. Examples: #define NerrFOO NETBASE+1 /* @I A FOO has been encountered * / /* #define NERR_Foo NETBASE + 2 /* @P The prompt text: %0 */ /* The resulting entry in the message file input file will be NETnnnnI: A FOO has been encountered Use the DOS message file source convention of XXXnnnn?: for placeholder messages. Author: This was ported from the Lanman utility that was used to create input files for mkmsgf by: Dan Hinsley (danhi) 29-Jul-1991 Revision History: Ronald Meijer (ronaldm) 17-Mar-1993 Added -p option to preserve leading white space characters --*/ #include #include #include #include #include #include "mapmsg.h" #define USAGE "syntax: mapmsg [-p] [-a appendfile] \n" int Append = FALSE; /* was the -a switch specified */ int Preserve = FALSE; /* TRUE if the -p switch is set */ int __cdecl main( int argc, PCHAR * argv ) { int Base; // Check for -p[reserve whitespace] option if (argc > 1) { if (_stricmp(argv[1], "-p") == 0) { ++argv; --argc; Preserve = TRUE; } } if (argc == 6) { if (_stricmp(argv[1], "-a") != 0) { fprintf(stderr, USAGE); return(1); } if (freopen(argv[2], "r+", stdout) == NULL) { fprintf(stderr, "Cannot open '%s'\n", argv[2]); return(1); } argv += 2; argc -= 2; Append = TRUE; } /* check for valid command line */ if (argc != 4) { fprintf(stderr, USAGE); return(1); } if (freopen(argv[3], "r", stdin) == NULL) { fprintf(stderr, "Cannot open '%s'\n", argv[3]); return(1); } if (GetBase(argv[2], &Base)) { fprintf(stderr, "Cannot locate definition of in '%s'\n", argv[3]); return(1); } /* now process the rest of the file and map it */ MapMessage(Base, argv[2]); return(0); } int GetBase( PCHAR String, int * pBase ) /*++ Routine Description: GetBase - find the line defining the value of the base number. Arguments: String is the string to match. pBase is a pointer of where to put the value. Return Value: Return 0 if string found, 1 if not. Notes: The global variable, chBuff is used w/in this routine. The pattern to look for is: [WS] #define [WS] [WS | '('] ..... --*/ { PCHAR p; size_t len; len = strlen(String); while(gets(chBuff)) { p = chBuff; SKIPWHITE(p); if (strncmp(p, "#define", 7) == 0) { p += 7; SKIPWHITE(p); if (strncmp(String, p, len) == 0 && strcspn(p, " \t") == len) { /* found the definition ... skip to number */ p += len; SKIP_W_P(p); if ( !isdigit(*p)) { ReportError(chBuff, "Bad definition"); } *pBase = atoi(p); return(0); } } } return(1); } VOID MapMessage( int Base, PCHAR BaseName ) /*++ Routine Description: MapMessage - map the definition lines. Arguments: Base is the base number BaseName is the text form of base Return Value: None Notes: The global variable, chBuff is used w/in this routine. Make sure that the numbers are strictly increasing. --*/ { CHAR auxbuff[BUFSIZ]; int num; int first = TRUE; int next; PCHAR text; CHAR define[41]; PCHAR p; CHAR type; /* Make certain the buffer is always null-terminated */ define[sizeof(define)-1] = '\0'; /* print the header */ if (!Append) { printf(";//\n"); printf(";// Net error file for basename %s = %d\n", BaseName, Base); printf(";//\n"); } else { /* get last number and position to end of file */ first = FALSE; next = 0; if (fseek(stdout, 0L, SEEK_END) == -1) { return; } } /* for each line of the proper format */ while (GetNextLine(BaseName, chBuff, define, &num, &text, &type)) { num += Base; if (first) { first = FALSE; next = num; } /* make sure that the numbers are monotonically increasing */ if (num > next) { if (next == num - 1) { fprintf(stderr, "(warning) Missing error number %d\n", next); } else { fprintf(stderr, "(warning) Missing error numbers %d - %d\n", next, num-1); } next = num; } else if (num < next) { ReportError(chBuff, "Error numbers not strictly increasing"); } /* rule out comment start alone on def line */ if (text && *text == 0) { ReportError(chBuff, "Bad comment format"); } /* * catch the cases where there is no open comment * or the open comment just contains a @X */ if (text == NULL) { text = gets(auxbuff); SKIPWHITE(text); if ((type == '\0') && (strncmp(text, "/*", 2) == 0)) { if (text[2] == 0) { gets(auxbuff); } else { text += 1; } strcpy(chBuff, text); text = chBuff; SKIPWHITE(text); if (*text++ != '*') { ReportError(chBuff, "Comment continuation requires '*'"); } } else if ((type) && (*text == '*')) { if (text[1] == 0) { gets(auxbuff); } strcpy(chBuff, text); text = chBuff; SKIPWHITE(text); if (*text++ != '*') { ReportError(chBuff, "Comment continuation requires '*'"); } } else { ReportError(chBuff, "Bad comment format"); } } /* Strip off trailing trailing close comment */ while (strstr(text, "*/") == NULL) { /* multi-line message ... comment MUST * be continued with '*' */ p = gets(auxbuff); SKIPWHITE(p); if (*p != '*') { ReportError(auxbuff, "Comment continuation requires '*'"); } if (*++p == '/') { break; } // abort if the current text length + add text + "\n" is > the max if (strlen(text) + strlen(p) + 1 > MAXMSGTEXTLEN) { ReportError(text, "\nMessage text length too long"); } strcat(text, "\n"); // // Get rid of leading spaces on continuation line, // unless -p specified // if (!Preserve) { SKIPWHITE(p); } strcat(text, p); } if ((p=strstr(text, "*/")) != NULL) { *p = 0; } TrimTrailingSpaces(text); // // Get rid of leading spaces on first line, unless -p specified // p = text; if (!Preserve) { SKIPWHITE(p); if (!p) { p = text; } } printf("MessageId=%04d SymbolicName=%s\nLanguage=English\n" "%s\n.\n", num, define, p); ++next; } } int GetNextLine( PCHAR BaseName, PCHAR pInputBuffer, PCHAR pDefineName, int * pNumber, PCHAR * pText, PCHAR pType ) /*++ Routine Description: GetNextLine - get the next line of the proper format, and parse out the error number. The format is assumed to be: [WS] #define [WS] [WS | '('] [WS | ')'] \ '+' [WS | '('] [WS | ')'] '/*' [WS] [@X] [WS] Arguments: BaseName is the basename. pInputBuffer is a pointer to an input buffer pDefineName is a pointer to where the manifest constant name pointer goes pNumber is a pointer to where the goes. pText is a pointer to where the text pointer goes. pType is a pointer to the message type (set to 0 if no @X on line). Return Value: Returns 0 at end of file, non-zero otherwise. --*/ { size_t len = strlen(BaseName); PCHAR savep = pInputBuffer; PCHAR startdefine; while (gets(savep)) { pInputBuffer = savep; SKIPWHITE(pInputBuffer); if (strncmp(pInputBuffer, "#define", 7) == 0) { pInputBuffer += 7; SKIPWHITE(pInputBuffer); /* get manifest constant name */ startdefine = pInputBuffer; pInputBuffer += strcspn(pInputBuffer, " \t"); *pInputBuffer = '\0'; pInputBuffer++; strncpy(pDefineName, startdefine, 40); SKIP_W_P(pInputBuffer); /* match */ if (strncmp(BaseName, pInputBuffer, len) == 0 && strcspn(pInputBuffer, " \t)+") == len) { pInputBuffer += len; SKIP_W_P(pInputBuffer); if (*pInputBuffer == '+') { ++pInputBuffer; SKIP_W_P(pInputBuffer); /* the number !! */ if (!isdigit(*pInputBuffer)) { ReportError(savep, "Bad error file format"); } *pNumber = atoi(pInputBuffer); SKIP_NOT_W_P(pInputBuffer); SKIP_W_P(pInputBuffer); if (strncmp(pInputBuffer, "/*", 2)) { *pText = NULL; *pType = '\0'; return(1); } pInputBuffer += 2; SKIPWHITE(pInputBuffer); if (*pInputBuffer == '@') { *pType = *(pInputBuffer+1); pInputBuffer += 2; SKIPWHITE(pInputBuffer); } else { *pType = '\0'; } if (*pInputBuffer) { *pText = pInputBuffer; } else { *pText = NULL; } return(1); } } } } return(0); } void ReportError( PCHAR pLineNumber, PCHAR Message ) /*++ Routine Description: ReportError - report a fatal error. Arguments: pLineNumber is the offending input line. Message is a description of what is wrong. Return Value: None --*/ { fprintf(stderr, "\a%s:%s\n", Message, pLineNumber); exit(1); } void TrimTrailingSpaces( PCHAR Text ) /*++ Routine Description: TrimTrailingSpaces - strip off the end spaces. Arguments: Text - the text to remove spaces from Return Value: None --*/ { PCHAR p; /* strip off trailing space */ while (((p=strrchr(Text, ' ')) && p[1] == 0) || ((p=strrchr(Text, '\t')) && p[1] == 0)) { *p = 0; } }