/*-----------------------------------------------------------------------*/ /* */ /* Turbo Vision 1.0 */ /* Turbo Vision Help Compiler Source File */ /* Copyright (c) 1991 by Borland International */ /* */ /*-----------------------------------------------------------------------*/ /* * Modified by Sergey Clushin <serg@lamport.ru>, <Clushin@deol.ru> * Modified by Sergio Sigala <sergio@sigala.it> * Modified by Max Okumoto <okumoto@ucsd.edu> */ /*===== TVHC ============================================================*/ /* Turbo Vision help file compiler documentation. */ /*=======================================================================*/ /* */ /* Refer to DEMOHELP.TXT for an example of a help source file. */ /* */ /* This program takes a help script and produces a help file (.HLP) */ /* and a help context file (.H). The format for the help file is */ /* very simple. Each context is given a symbolic name (i.e FileOpen) */ /* which is then put in the context file (i.e. hcFileOpen). The text */ /* following the topic line is put into the help file. Since the */ /* help file can be resized, some of the text will need to be wrapped */ /* to fit into the window. If a line of text is flush left with */ /* no preceeding white space, the line will be wrapped. All adjacent */ /* wrappable lines are wrapped as a paragraph. If a line begins with */ /* a space it will not be wrapped. For example, the following is a */ /* help topic for a File|Open menu item. */ /* */ /* |.topic FileOpen */ /* | File|Open */ /* | --------- */ /* |This menu item will bring up a dialog... */ /* */ /* The "File|Open" will not be wrapped with the "----" line since */ /* they both begin with a space, but the "This menu..." line will */ /* be wrapped. */ /* The syntax for a ".topic" line is: */ /* */ /* .topic symbol[=number][, symbol[=number][...]] */ /* */ /* Note a topic can have multiple symbols that define it so that one */ /* topic can be used by multiple contexts. The number is optional */ /* and will be the value of the hcXXX context in the context file */ /* Once a number is assigned all following topic symbols will be */ /* assigned numbers in sequence. For example, */ /* */ /* .topic FileOpen=3, OpenFile, FFileOpen */ /* */ /* will produce the follwing help context number definitions, */ /* */ /* hcFileOpen = 3; */ /* hcOpenFile = 4; */ /* hcFFileOpen = 5; */ /* */ /* Cross references can be imbedded in the text of a help topic which */ /* allows the user to quickly access related topics. The format for */ /* a cross reference is as follows, */ /* */ /* {text[:alias]} */ /* */ /* The text in the brackets is highlighted by the help viewer. This */ /* text can be selected by the user and will take the user to the */ /* topic by the name of the text. Sometimes the text will not be */ /* the same as a topic symbol. In this case you can use the optional */ /* alias syntax. The symbol you wish to use is placed after the text */ /* after a ':'. The following is a paragraph of text using cross */ /* references, */ /* */ /* |The {file open dialog:FileOpen} allows you specify which */ /* |file you wish to view. If it also allow you to navigate */ /* |directories. To change to a given directory use the */ /* |{change directory dialog:ChDir}. */ /* */ /* The user can tab or use the mouse to select more information about */ /* the "file open dialog" or the "change directory dialog". The help */ /* compiler handles forward references so a topic need not be defined */ /* before it is referenced. If a topic is referenced but not */ /* defined, the compiler will give a warning but will still create a */ /* useable help file. If the undefined reference is used, a message */ /* ("No help available...") will appear in the help window. */ /*=======================================================================*/ #define Uses_fpstream #define Uses_TSortedCollection #include <tv.h> #include "tvhc.h" #include <string.h> #include <limits.h> #include <unistd.h> #include <ctype.h> #include <stdlib.h> #include <errno.h> #include <fstream> #include <sstream> #include <string> using std::cerr; using std::cin; using std::cout; using std::ends; using std::fstream; using std::ios; using std::ostringstream; using std::string; static const int MAXSTRSIZE=256; static const char commandChar[] = "."; static const int bufferSize = 4096; typedef enum State { undefined, wrapping, notWrapping }; static char *helpName; static uchar buffer[bufferSize]; static int ofs; static TRefTable *refTable = 0; static TCrossRefNode *xRefs; static char line[MAXSTRSIZE] = ""; static Boolean lineInBuffer = False; static int lineCount = 0; //======================= File Management ===============================// TProtectedStream::TProtectedStream( char *aFileName, ios::openmode aMode) : fstream( aFileName, aMode ) { strcpy(fileName, aFileName); mode = aMode; } //----- replaceExt(fileName, nExt, force) -------------------------------// // Replace the extension of the given file with the given extension. // // If an extension already exists Force indicates if it should be // // replaced anyway. // //-----------------------------------------------------------------------// #if 0 char *replaceExt( char *fileName, char *nExt, Boolean force ) { char dir[MAXPATH]; char name[MAXFILE]; char ext[MAXEXT]; char drive[MAXDRIVE]; string buffer; ostringstream os(buffer); fnsplit(fileName, drive, dir, name, ext); if (force || (strlen(ext) == 0)) { os << dir << name << nExt << ends; return os.str(); } else return fileName; } #endif //----- fExist(fileName) ------------------------------------------------/ // Returns true if the file exists false otherwise. / //-----------------------------------------------------------------------/ static bool fExists(const string &fileName) { return (access(fileName.c_str(), R_OK) == 0) ? true : false; } //======================== Line Management ==============================// //----- getLine(s) ------------------------------------------------------// // Returns the next line out of the stream. // //-----------------------------------------------------------------------// char *getLine( fstream& s) { if (s.eof()) { strcpy(line, "\x1A"); return line; } if (!lineInBuffer) { s.getline(line, MAXSTRSIZE, '\n'); //SS: remove carriage return, if present. After this simple fix the //program will both handle unix and dos files. int l; if ((l = strlen(line)) >= 1 && line[l - 1] == '\r') { line[l - 1] = '\0'; } } lineInBuffer = False; ++lineCount; return line; } //----- unGetLine(s) ----------------------------------------------------// // Return given line into the stream. // //-----------------------------------------------------------------------// void unGetLine( char *s ) { strcpy(line,s); lineInBuffer = True; --lineCount; } //========================= Error routines ==============================// //----- prntMsg(text) ---------------------------------------------------// // Used by Error and Warning to print the message. // //-----------------------------------------------------------------------// void prntMsg(const string &pref, char *text) { if (lineCount > 0) cout << pref << ": " << helpName << "(" << lineCount << "): " << text << "\n"; else cout << pref << ": " << helpName << " " << text << "\n"; } //----- error(text) -----------------------------------------------------// // Used to indicate an error. Terminates the program // //-----------------------------------------------------------------------// void error(char *text) { prntMsg("Error", text); exit(1); } //----- warning(text) ---------------------------------------------------// // Used to indicate an warning. // //-----------------------------------------------------------------------// void warning( char *text ) { prntMsg("Warning", text); } //====================== Topic Reference Management =====================// void disposeFixUps( TFixUp *&p ) { TFixUp *q; while (p != 0) { q = p->next; delete p; p = q; } } //----- TRefTable -------------------------------------------------------// // TRefTable is a collection of TReference pointers used as a symbol // // table. If the topic has not been seen, a forward reference is // // inserted and a fix-up list is started. When the topic is seen all // // forward references are resolved. If the topic has been seen already // // the value it has is used. // //-----------------------------------------------------------------------// TRefTable::TRefTable( ccIndex aLimit, ccIndex aDelta ) : TSortedCollection( aLimit, aDelta ) { } int TRefTable::compare( void *key1, void *key2 ) { int compValue; compValue = strcmp( (char *)key1, (char *)key2 ); if (compValue > 0) return 1; else if (compValue < 0) return (-1); else return(0); } void TRefTable::freeItem( void *item ) { TReference *ref; ref = (TReference *) item; if (ref->resolved == False) disposeFixUps(ref->val.fixUpList); delete ref->topic; delete ref; } TReference *TRefTable::getReference( char *topic ) { TReference *ref; int i; if (search(topic, i)) ref = (TReference *) at(i); else { ref = new TReference; ref->topic = newStr(topic); ref->resolved = False; ref->val.fixUpList = 0; insert(ref); } return(ref); } void* TRefTable::keyOf( void *item ) { return(((TReference *)item)->topic); } //----- initRefTable ---------------------------------------------------// // Make sure the reference table is initialized. // //----------------------------------------------------------------------// void initRefTable() { if (refTable == 0) refTable = new TRefTable(5,5); } //---- RecordReference -------------------------------------------------// // Record a reference to a topic to the given stream. This routine // // handles forward references. // //----------------------------------------------------------------------// void recordReference( char *topic, opstream& s ) { int i; TReference *ref; TFixUp *fixUp; initRefTable(); ref = refTable->getReference(topic); if (ref->resolved == True) s << ref->val.value; else { fixUp = new TFixUp; fixUp->pos = s.tellp(); i = -1; // s << i; s << (typeof(ref->val.value))i; // SC fixUp->next = ref->val.fixUpList; ref->val.fixUpList = fixUp; } } void doFixUps( TFixUp *p, ushort value, fpstream& s ) { long pos; // for(pos = s.tellg(); (p != 0); p = p->next) for(pos = s.tellp(); (p != 0); p = p->next) // SC { s.seekp(p->pos); // s << value; TReference *ref; // SC s << (typeof(ref->val.value))value; // } s.seekp(pos); } //----- resolveReference -----------------------------------------------// // Resolve a reference to a topic to the given stream. This function // // handles forward references. // //----------------------------------------------------------------------// void resolveReference( char *topic, ushort value, fpstream& s ) { TReference *ref; char bufStr[MAXSIZE]; initRefTable(); ref = refTable->getReference(topic); if (ref->resolved ) { strcpy(bufStr,"Redefinition of "); strcat(bufStr,ref->topic); error(bufStr); } else { doFixUps(ref->val.fixUpList,value,s); disposeFixUps(ref->val.fixUpList); ref->resolved = True; ref->val.value = value; } } //======================= Help file parser =============================// void skipWhite( char *line,int& i ) { while (i <= (int)strlen(line) && (line[i] == ' ') || (line[i] == 8)) ++i; } int checkForValidChar( char ch ) { if (isalnum(ch) || (ch == '_')) return(0); return(-1); } void skipToNonWord( char *line, int& i ) { while (i <= (int)strlen(line) && (!checkForValidChar(line[i]))) ++i; } //----- getWord --------------------------------------------------------// // Extract the next word from the given line at offset i. // //----------------------------------------------------------------------// char *getWord( char *line, int &i ) { int j; char *strptr; static char getword[MAXSIZE]; // SS skipWhite(line,i); j = i; if (j > (int)strlen(line)) strcpy(getword,""); else { ++i; if (!checkForValidChar(line[j])) skipToNonWord(line, i); strptr = line + j; strncpy(getword,strptr,i - j); getword[i-j] = '\0'; } return getword; } //---- topicDefinition -------------------------------------------------// // Extracts the next topic definition from the given line at i. // //----------------------------------------------------------------------// TTopicDefinition::TTopicDefinition( char *aTopic, ushort aValue ) { topic = newStr(aTopic); value = aValue; next = 0; } TTopicDefinition::~TTopicDefinition() { delete topic; if (next != 0) delete next; } int is_numeric(char *str) { int i; for(i = 0; i < (int)strlen(str); ++i) if (!isdigit(str[i])) return 0; return 1; } TTopicDefinition *topicDefinition( char *line, int& i ) { int j; char topic[MAXSTRSIZE], w[MAXSTRSIZE], *endptr; static int helpCounter = 2; //1 is hcDragging strcpy(topic,getWord(line, i)); if (strlen(topic) == 0) { error("Expected topic definition"); return(0); } else { j = i; strcpy(w,getWord(line, j)); if (strcmp(w,"=") == 0) { i = j; strcpy(w,getWord(line, i)); if (!is_numeric(w)) error("Expected numeric"); else helpCounter = (int) strtol(w, &endptr,10); } else ++helpCounter; return(new TTopicDefinition(topic, helpCounter)); } } //---- topicDefinitionList----------------------------------------------// // Extracts a list of topic definitions from the given line at i. // //----------------------------------------------------------------------// TTopicDefinition *topicDefinitionList( char *line, int &i ) { int j; char w[MAXSTRSIZE]; TTopicDefinition *topicList, *p; j = i; topicList = 0; do { i = j; p = topicDefinition(line, i); if (p == 0 ) { if (topicList != 0) delete topicList; return(0); } p->next = topicList; topicList = p; j = i; strcpy(w,getWord(line, j)); } while ( strcmp(w,",") == 0); return(topicList); } //---- topicHeader -----------------------------------------------------// // Parse a Topic header // //----------------------------------------------------------------------// TTopicDefinition *topicHeader( char *line ) { int i; char w[75]; i = 0; strcpy(w, getWord(line, i)); if (strcmp(w, commandChar) != 0) return(0); strcpy(w, getWord(line, i)); if (strcasecmp(w, "TOPIC") == 0) return topicDefinitionList(line, i); else { error("TOPIC expected"); return(0); } } void addToBuffer( char *line, Boolean wrapping ) { uchar *bufptr; bufptr = &buffer[ofs]; ofs += strlen(line); if( ofs > bufferSize ) error("Text too long"); strcpy((char *)bufptr, line); bufptr += (strlen(line)); if (wrapping == False) *bufptr = '\n'; else *bufptr = ' '; ofs++; } void addXRef( char *xRef, int offset, uchar length, TCrossRefNode *&xRefs ) { TCrossRefNode *p, *pp, *prev; p = new TCrossRefNode; p->topic = newStr(xRef); p->offset = offset; p->length = length; p->next = 0; if (xRefs == 0) xRefs = p; else { pp = xRefs; prev = pp; while (pp != 0) { prev = pp; pp = pp->next; } prev->next = p; } } void replaceSpacesWithFF( char *line, int start, uchar length ) { int i; for(i = start; i <= (start + length); ++i) if (line[i] == ' ') line[i] = 0xFF; } void strdel(char *string, int pos, int len) { char tempstr[MAXSTRSIZE]; char *strptr; strncpy(tempstr, string, pos); tempstr[pos] = 0; strptr = string + pos + len; strcat(tempstr, strptr); strcpy(string, tempstr); } void scanForCrossRefs( char *line, int& offset, TCrossRefNode *&xRefs ) { int i; char begXRef = '{'; char endXRef = '}'; char aliasCh = ':'; char *begPtr, *endPtr, *aliasPtr, *tempPtr; int begPos, endPos, aliasPos; char xRef[75]; i = 0; do { if ((begPtr = strchr(line+i,begXRef)) == 0) i = 0; else { begPos = (int)(begPtr - (line+i)); i += begPos + 1; // if (line[i + 1] == begXRef) if (line[i] == begXRef) // S.I. Clushin { strdel(line, i, 1); ++i; } else { if ((endPtr = strchr(line+i,endXRef)) == 0) { error("Unterminated topic reference."); ++i; } else { endPos = (int)(endPtr - (line + i)); aliasPtr = strchr(line+i, aliasCh); if ((aliasPtr == 0) || (aliasPtr > endPtr)) { tempPtr = line + i; strncpy(xRef, tempPtr, endPos); xRef[endPos] = 0; addXRef(xRef, (offset + ofs + i), endPos, xRefs); } else { aliasPos = (int)(aliasPtr - (line + i)); tempPtr = line ; tempPtr += aliasPos+i+1; strncpy(xRef, tempPtr, (endPos - aliasPos -1)); xRef[endPos - aliasPos -1] = 0; addXRef(xRef, (offset + ofs + i), (aliasPos), xRefs); strdel(line, (i + aliasPos), (endPos - aliasPos)); endPtr = aliasPtr; endPos = aliasPos; } replaceSpacesWithFF(line, i, endPos -1); strdel(line, i + endPos, 1); strdel(line, i-1, 1); i += (endPos - 2); } } } } while (i != 0); } Boolean isEndParagraph( State state ) { int flag; int wrapping = 1; int notWrapping = 2; flag = ((line[0] == 0) || (line[0] == commandChar[0]) || (line[0] == 26) || ((line[0] == ' ') && (state == wrapping)) || ((line[0] != ' ') && (state == notWrapping))); if (flag) return(True); else return(False); } //---- readParagraph ----------------------------------------------------// // Read a paragraph of the screen. Returns the paragraph or 0 if the // // paragraph was not found in the given stream. Searches for cross // // references and updates the xRefs variable. // //-----------------------------------------------------------------------// TParagraph *readParagraph( fstream& textFile, int& offset, TCrossRefNode *&xRefs ) { State state; Boolean flag; char line[MAXSTRSIZE]; TParagraph *p; ofs = 0; state = undefined; strcpy(line, getLine(textFile)); while (strlen(line) == 0) { flag = (state == wrapping)? True: False; addToBuffer(line, flag); strcpy(line, getLine(textFile)); } if (isEndParagraph(state) == True) { unGetLine(line); return(0); } while (isEndParagraph(state) == False) { if (state == undefined ) if (line[0] == ' ') state = notWrapping; else state = wrapping; scanForCrossRefs(line, offset, xRefs); flag = (state == wrapping)? True: False; addToBuffer(line, flag); strcpy(line, getLine(textFile)); } unGetLine(line); p = new TParagraph; p->size = ofs; p->wrap = (state == wrapping)? True: False; p->text = new char[ofs]; memmove(p->text, buffer, ofs); p->next = 0; offset += ofs; return(p); } void handleCrossRefs( opstream& s, int xRefValue ) { TCrossRefNode *p; for(p = xRefs; (xRefValue > 0) ; --xRefValue) { if (p != 0) p = p->next; } if (p != 0) recordReference((p->topic), s); } void skipBlankLines( fstream& s ) { char line[256]; line[0] = 0; while (line[0] == 0) strcpy(line,getLine(s)); unGetLine(line); } int xRefCount() { int i; TCrossRefNode *p; i = 0; for (p=xRefs; (p != 0); p=p->next) ++i; return(i); } void disposeXRefs( TCrossRefNode *p ) { TCrossRefNode *q; while (p != 0) { q = p; p = p->next; delete q->topic; delete q; } } void recordTopicDefinitions( TTopicDefinition *p, THelpFile& helpFile ) { while (p != 0) { resolveReference(p->topic, p->value, *(helpFile.stream)); helpFile.recordPositionInIndex(p->value); p = p->next; } } //---- readTopic -------------------------------------------------------// // Read a topic from the source file and write it to the help file // //----------------------------------------------------------------------// void readTopic( fstream& textFile, THelpFile& helpFile ) { TParagraph *p; THelpTopic *topic; TTopicDefinition *topicDef; int i, j, offset; TCrossRef ref; TCrossRefNode *refNode; // Get screen command skipBlankLines(textFile); strcpy(line, getLine(textFile)); topicDef = topicHeader(line); topic = new THelpTopic; // read paragraphs xRefs = 0; offset = 0; p = readParagraph(textFile, offset, xRefs); while (p != 0) { topic->addParagraph(p); p = readParagraph(textFile, offset, xRefs); } i = xRefCount(); topic->setNumCrossRefs(i); refNode = xRefs; for( j = 0; j < i; ++j) { ref.offset = refNode->offset; ref.length = refNode->length; ref.ref = j; topic->setCrossRef(j, ref); refNode = refNode->next; } recordTopicDefinitions(topicDef, helpFile); crossRefHandler = handleCrossRefs; helpFile.putTopic(topic); if (topic != 0) delete topic; if (topicDef != 0) delete topicDef; disposeXRefs(xRefs); skipBlankLines(textFile); } void doWriteSymbol(void *p, void *p1) { int numBlanks, i; ostringstream os(line); TProtectedStream *symbFile = (TProtectedStream *)p1; if (((TReference *)p)->resolved ) { os << " hc" << (char *)((TReference *)p)->topic; numBlanks = 20 - strlen((char *)((TReference *)p)->topic); for (i = 0; i < numBlanks; ++i) os << ' '; os << " = " << ((TReference *)p)->val.value << ","<< ends; *symbFile << os.str() << "\n"; } else { os << "Unresolved forward reference \"" << ((TReference *)p)->topic << "\"" << ends; warning(const_cast<char *>(os.str().c_str())); } } //---- writeSymbFile ---------------------------------------------------// // Write the .H file containing all screen titles as constants. // //----------------------------------------------------------------------// void writeSymbFile( TProtectedStream *symbFile ) { char header1[] = "const\n"; *symbFile << header1; refTable->forEach(doWriteSymbol, symbFile); symbFile->seekp(-2L, ios::end); *symbFile << ";\n"; } //---- processtext -----------------------------------------------------// // Compile the given stream, and output a help file. // //----------------------------------------------------------------------// void processText( TProtectedStream& textFile, fpstream& helpFile, TProtectedStream& symbFile ) { THelpFile *helpRez; helpRez = new THelpFile(helpFile); while (!textFile.eof()) readTopic(textFile, *helpRez); writeSymbFile(&symbFile); delete helpRez; } //---- checkOverwrite --------------------------------------------------// // Check whether the output file name exists. If it does, ask whether // // it's ok to overwrite it. // //----------------------------------------------------------------------// static void checkOverwrite(const string &fName) { if (fExists(fName)) { cerr << "File already exists: " << fName << ". Overwrite? (y/n) "; char ch; cin >> ch; if (toupper(ch) != 'Y') { exit(1); } } } //========================== Program Block ==========================// int main(int argc, char **argv) { // Banner messages char initialText[] = "Help Compiler Version 1.0 Copyright (c) 1991" " Borland International.\n"; char helpText[] = "\n Syntax TVHC <Help text> <Help file> <Symbol file>\n" "\n" " Help text = Help file source\n" " Help file = Compiled help file\n" " Symbol file = An include file containing all the screen names as const's\n"; char bufStr[MAXSTRSIZE]; cout << initialText; if (argc != 4) { cout << helpText; exit(1); } // Calculate file names char *textName = argv[1]; if (!fExists(textName)) { strcpy(bufStr,"File "); strcat(bufStr, textName); strcat(bufStr, " not found."); error(bufStr); } helpName = argv[2]; checkOverwrite(helpName); char *symbName = argv[3]; checkOverwrite(symbName); TProtectedStream textStrm(textName, ios::in); TProtectedStream symbStrm(symbName, ios::out | ios::trunc); fpstream helpStrm(helpName, ios::out | ios::trunc); processText(textStrm, helpStrm, symbStrm); return 0; }