/* * main.c * * This program takes a texinfo file without node and menu commands, * build those commands and inserts them. * * It works by reading the input file into a linked list of lines * and then performing sweeps on that list until all formatting is * complete. After the program is run, there is still a little * clean up to be performed by hand. The following have to be fixed * by hand: * + previous of the first node * + next of the last node * * COPYRIGHT (c) 1988-2002. * On-Line Applications Research Corporation (OAR). * All rights reserved. * * $Id$ */ #include #include #include #include #include #include /* XXX -- just for testing -- these should be set by options */ char TopString[] = "Top"; char EmptyString[] = ""; char *DocsNextNode; char *DocsPreviousNode; char *DocsUpNode; int NodeNameIncludesChapter = 1; extern int optind; /* Why is this not in ? */ extern char *optarg; /* Why is this not in ? */ #ifndef NAME_MAX #define NAME_MAX 14 /* Why is the one in limits.h not showing up? */ #endif #define INIT_DATA #define EXTERN #include "base.h" FILE *OutFile; static void ProcessFile2( FILE *infile, FILE *outfile ); static void PrintFile2( FILE *outfile ); static void ReadFileIntoChain2( FILE *InFile ); /************************************************************************* ************************************************************************* ***** DATA TYPES AND CONSTANT TABLES ***** ************************************************************************* *************************************************************************/ /* * Usage Information */ char *Usage_Strings[] = { "\n", "usage: cmd [-cv] [-p prev] [-n next] [-u up] \n", "\n", "EOF" }; /* * The page separator is not really a keyword and will be purged before * it is seen elsewhere. */ #define PAGE_SEPARATOR "#PAGE" /* * Section Delimiter Keywords */ #define MAXIMUM_KEYWORD_LENGTH 32 /* * Level indicates where in the format the delimiter is allowed to occur. * 1 indicates a major section divider (e.g. "ATTRIBUTE DESCRIPTIONS:"). * 2 indicates a subsection (e.g. "ATTRIBUTE:"). * 3 indicates a heading (e.g. "DESCRIPTION:"). */ #define TEXT 0 #define SECTION 1 #define SUBSECTION 2 #define SUBSUBSECTION 3 #define HEADING 4 typedef enum { UNUSED, /* dummy 0 slot */ KEYWORD_CHAPTER, KEYWORD_APPENDIX, KEYWORD_PREFACE, KEYWORD_CHAPHEADING, KEYWORD_SECTION, KEYWORD_SUBSECTION, KEYWORD_SUBSUBSECTION, KEYWORD_RAISE, KEYWORD_LOWER, KEYWORD_OTHER, KEYWORD_END } Keyword_indices_t; #define KEYWORD_FIRST KEYOWRD_CHAPTER #define KEYWORD_LAST KEYWORD_END /* * Line Management Structure */ typedef enum { NO_EXTRA_FORMATTING_INFO, RAW_OUTPUT, PARAGRAPH_OUTPUT } ExtraFormat_info_t; typedef struct { Chain_Node Node; Keyword_indices_t keyword; /* unused is unknown/undecided */ ExtraFormat_info_t format; int number; int level; char Contents[ PARAGRAPH_SIZE ]; } Line_Control; typedef enum { RT_FORBIDDEN, /* no text to right allowed */ RT_OPTIONAL, /* text to right optional -- none below */ RT_NONE, /* text to right is "none" or nothing -- none below */ RT_REQUIRED, /* text to right required -- none below */ RT_BELOW, /* text to right forbidden -- text below required */ RT_NONE_OR_BELOW, /* text to right is "none" OR there is text below */ RT_EITHER, /* text to right OR below */ RT_BOTH /* text to right AND below */ } Keywords_text_mode_t; typedef enum { BL_FORBIDDEN, /* text below forbidden */ BL_FORMATTED, /* text below is to be formatted as paragraphs */ BL_RAW, /* text below is to be unprocessed by this program */ } Keywords_text_below_t; typedef (*Keyword_validater_t)( Line_Control * ); typedef struct { char Name[ MAXIMUM_KEYWORD_LENGTH ]; int level; Keywords_text_mode_t text_mode; Keywords_text_below_t text_below_mode; Keyword_validater_t keyword_validation_routine; } Keyword_info_t; Keyword_info_t Keywords[] = { { "unused", 0, 0, 0, NULL }, /* so 0 can be invalid */ { "@chapter", SECTION, RT_FORBIDDEN, BL_FORBIDDEN, NULL }, { "@appendix", SECTION, RT_FORBIDDEN, BL_FORBIDDEN, NULL }, { "@preface", SECTION, RT_FORBIDDEN, BL_FORBIDDEN, NULL }, { "@chapheading", SECTION, RT_FORBIDDEN, BL_FORBIDDEN, NULL }, { "@section", SECTION, RT_FORBIDDEN, BL_FORBIDDEN, NULL }, { "@subsection", SUBSECTION, RT_FORBIDDEN, BL_FORBIDDEN, NULL }, { "@subsubsection", SUBSUBSECTION, RT_FORBIDDEN, BL_FORBIDDEN, NULL }, { "@raise", SUBSECTION, RT_FORBIDDEN, BL_FORBIDDEN, NULL }, { "@lower", SUBSECTION, RT_FORBIDDEN, BL_FORBIDDEN, NULL }, { "", HEADING, RT_FORBIDDEN, BL_FORBIDDEN, NULL }, { "END OF FILE", SECTION, RT_FORBIDDEN, BL_FORBIDDEN, NULL } }; #define NUMBER_OF_KEYWORDS \ ( sizeof( Keywords ) / sizeof( Keyword_info_t ) ) - 2 /* * exit_application */ void exit_application( int status ) { fprintf( stderr, "*** Error encountered ***\n" ); /* fprintf( stderr, "*** Error encountered on line %d ***\n", CurrentLine ); */ fclose( OutFile ); exit( status ); } /************************************************************************* ************************************************************************* ***** LINE MANIPULATION ROUTINES ***** ************************************************************************* *************************************************************************/ /* * PrintLine */ void PrintLine( Line_Control *line ) { assert( line ); if ( line->number == -1 ) fprintf( stderr, " " ); else fprintf( stderr, "%5d", line->number ); #if 0 fprintf( stderr, "%s\n", line->Contents ); #else /* * Include some debugging information */ fprintf( stderr, "<%d,%d,%d>:%s\n", line->keyword, line->format, line->level, line->Contents ); #endif } Chain_Control Line_Pool; /* * FillLinePool */ void FillLinePool( void ) { void *pool; #define LINE_POOL_FILL_COUNT 100 pool = malloc( sizeof( Line_Control ) * LINE_POOL_FILL_COUNT ); assert( pool ); _Chain_Initialize( &Line_Pool, pool, LINE_POOL_FILL_COUNT, sizeof( Line_Control ) ); } /* * AllocateLine */ Line_Control *AllocateLine( void ) { Line_Control *new_line; new_line = (Line_Control *) _Chain_Get( &Line_Pool ); if ( !new_line ) { FillLinePool(); new_line = (Line_Control *) _Chain_Get( &Line_Pool ); assert( new_line ); } /* * This is commented out because although it is helpful during debug, * it consumes a significant percentage of the program's execution time. memset( new_line->Contents, '\0', sizeof( new_line->Contents ) ); */ new_line->number = -1; new_line->level = -1; new_line->keyword = UNUSED; new_line->format = NO_EXTRA_FORMATTING_INFO; new_line->Node.next = NULL; new_line->Node.previous = NULL; return new_line; } /* * FreeLine */ void FreeLine( Line_Control *line ) { fflush( stdout ); _Chain_Append( &Line_Pool, &line->Node ); } /* * DeleteLine */ Line_Control *DeleteLine( Line_Control *line ) { Line_Control *next; next = (Line_Control *)line->Node.next; _Chain_Extract( &line->Node ); FreeLine( line ); return next; } /* * PrintSurroundingLines */ void PrintSurroundingLines( Line_Control *line, int backward, int forward ) { int i; int real_backward; Line_Control *local; for ( local=line, real_backward=0, i=1 ; i<=backward ; i++, real_backward++ ) { if ( &local->Node == Lines.first ) break; local = (Line_Control *) local->Node.previous; } for ( i=1 ; i<=real_backward ; i++ ) { PrintLine( local ); local = (Line_Control *) local->Node.next; } PrintLine( local ); for ( i=1 ; i<=forward ; i++ ) { local = (Line_Control *) local->Node.next; if ( _Chain_Is_last( &local->Node ) ) break; PrintLine( local ); } } /* * SetLineFormat */ void SetLineFormat( Line_Control *line, ExtraFormat_info_t format ) { if ( line->format != NO_EXTRA_FORMATTING_INFO ) { fprintf( stderr, "Line %d is already formatted\n", line->number ); PrintLine( line ); assert( FALSE ); } line->format = format; } /* * LineCopyFromRight */ void LineCopyFromRight( Line_Control *line, char *dest ) { char *p; for ( p=line->Contents ; *p != ' ' ; p++ ) ; p++; /* skip the ' ' */ for ( ; isspace( *p ) ; p++ ) ; strcpy( dest, p ); } /* * LineCopySectionName */ void LineCopySectionName( Line_Control *line, char *dest ) { char *p; char *d; p = line->Contents; d = dest; if ( *p == '@' ) { /* skip texinfo command */ while ( !isspace( *p++ ) ) ; } for ( ; *p ; ) *d++ = *p++; *d = '\0'; } /************************************************************************* ************************************************************************* ***** END OF LINE MANIPULATION ROUTINES ***** ************************************************************************* *************************************************************************/ /* * main */ int main( int argc, char **argv ) { int c; int index; boolean single_file_mode; OutFile = stdout; Verbose = FALSE; DocsNextNode = EmptyString; DocsPreviousNode = TopString; DocsUpNode = TopString; while ((c = getopt(argc, argv, "vcp:n:u:")) != EOF) { switch (c) { case 'v': Verbose = TRUE; break; case 'c': NodeNameIncludesChapter = 0; break; case 'p': DocsPreviousNode = strdup(optarg); break; case 'n': DocsNextNode = strdup(optarg); break; case 'u': DocsUpNode = strdup(optarg); break; case '?': usage(); return 0; } } if ( optind != argc ) { usage(); return 0 ; } if ( Verbose ) fprintf( stderr, "Arguments successfully parsed\n" ); FillLinePool(); ProcessFile2( stdin, stdout ); if ( Verbose ) fprintf( stderr, "Exitting\n" ); return 0; } /* * ProcessFile */ void ProcessFile2( FILE *infile, FILE *outfile ) { int index; /* * Read the file into our internal data structure */ if ( Verbose ) printf( "Processing (%s) -> (%s)\n", "stdin", "stdout" ); ReadFileIntoChain2( infile ); if ( Verbose ) fprintf( stderr, "-------->FILE READ IN\n" ); /* * Remove any spaces before the keyword and mark each keyword line as * such. Also remove extra white space at the end of lines. */ StripBlanks(); if ( Verbose ) fprintf( stderr, "-------->BLANKS BEFORE KEYWORDS STRIPPED\n" ); FormatToTexinfo(); if ( Verbose ) fprintf( stderr, "-------->FILE FORMATTED TO TEXINFO\n" ); /* * Print the file */ PrintFile2( outfile ); if ( Verbose ) fprintf( stderr, "-------->FILE PRINTED\n" ); /* * Clean Up */ ReleaseFile(); if ( Verbose ) fprintf( stderr, "-------->FILE RELEASED\n" ); } /* * usage */ void usage( void ) { int index; for ( index=0 ; strcmp( Usage_Strings[ index ], "EOF" ) ; index++ ) fprintf( stderr, Usage_Strings[ index ] ); } /* * ReadFileIntoChain */ void ReadFileIntoChain2( FILE *InFile ) { int line_count; int max_length; char *line; char Buffer[ BUFFER_SIZE ]; Line_Control *new_line; if ( !InFile ) { fprintf( stderr, "Unable to open (%s)\n", "stdin" ); exit( 1 ); } assert( InFile ); max_length = 0; line_count = 0; _Chain_Initialize_empty( &Lines ); for ( ;; ) { line = fgets( Buffer, BUFFER_SIZE, InFile ); if ( !line ) break; Buffer[ strlen( Buffer ) - 1 ] = '\0'; new_line = AllocateLine(); strcpy( new_line->Contents, Buffer ); new_line->number = ++line_count; _Chain_Append( &Lines, &new_line->Node ); } fclose( InFile ); } /* * StripBlanks */ void StripBlanks( void ) { Line_Control *line; Keyword_indices_t index; int indentation; int length; for ( line = (Line_Control *) Lines.first ; !_Chain_Is_last( &line->Node ) ; line = (Line_Control *) line->Node.next ) { /* * Strip white space from the end of each line */ length = strlen( line->Contents ); while ( isspace( line->Contents[ --length ] ) ) line->Contents[ length ] = '\0'; if ( strstr( line->Contents, "@chapter" ) ) line->keyword = KEYWORD_CHAPTER; else if ( strstr( line->Contents, "@appendix" ) ) line->keyword = KEYWORD_APPENDIX; else if ( strstr( line->Contents, "@preface" ) ) line->keyword = KEYWORD_PREFACE; else if ( strstr( line->Contents, "@chapheading" ) ) line->keyword = KEYWORD_CHAPHEADING; else if ( strstr( line->Contents, "@section" ) ) line->keyword = KEYWORD_SECTION; else if ( strstr( line->Contents, "@subsection" ) ) line->keyword = KEYWORD_SUBSECTION; else if ( strstr( line->Contents, "@subsubsection" ) ) line->keyword = KEYWORD_SUBSUBSECTION; else if ( strstr( line->Contents, "@raise" ) ) line->keyword = KEYWORD_RAISE; else if ( strstr( line->Contents, "@lower" ) ) line->keyword = KEYWORD_LOWER; else line->keyword = KEYWORD_OTHER; } line = AllocateLine(); line->keyword = KEYWORD_END; _Chain_Append( &Lines, &line->Node ); } /* * strIsAllSpace */ boolean strIsAllSpace( char *s ) { char *p; for ( p = s ; *p ; p++ ) if ( !isspace( *p ) ) return FALSE; return TRUE; } /* * BuildTexinfoNodes */ void BuildTexinfoNodes( void ) { char Buffer[ BUFFER_SIZE ]; Line_Control *line; Line_Control *next_node; Line_Control *up_node; Line_Control *new_line; Line_Control *menu_insert_point; Line_Control *node_line; int next_found; int menu_items; Keyword_indices_t index; char ChapterName[ BUFFER_SIZE ]; char NodeName[ BUFFER_SIZE ]; char UpNodeName[ BUFFER_SIZE ]; char NextNodeName[ BUFFER_SIZE ]; char PreviousNodeName[ BUFFER_SIZE ]; /* * Set Initial Previous Node Name */ strcpy( PreviousNodeName, DocsPreviousNode ); for ( line = (Line_Control *) Lines.first ; !_Chain_Is_last( &line->Node ) ; line = (Line_Control *) line->Node.next ) { if ( line->level == -1 ) continue; LineCopyFromRight( line, NodeName ); if ( line->keyword == KEYWORD_CHAPTER || line->keyword == KEYWORD_APPENDIX || line->keyword == KEYWORD_PREFACE || line->keyword == KEYWORD_CHAPHEADING ) { strcpy( ChapterName, NodeName ); } else if ( NodeNameIncludesChapter ) { sprintf( Buffer, "%s %s", ChapterName, NodeName ); strcpy( NodeName, Buffer ); } /* * Set Default Next Node Name */ next_found = FALSE; strcpy( NextNodeName, DocsNextNode ); /* * Go ahead and put it on the chain in the right order (ahead of * the menu) and we can fill it in later (after the menu is built). */ new_line = AllocateLine(); strcpy( new_line->Contents, "" ); /*"@ifinfo" ); */ _Chain_Insert( line->Node.previous, &new_line->Node ); node_line = AllocateLine(); _Chain_Insert( line->Node.previous, &node_line->Node ); new_line = AllocateLine(); strcpy( new_line->Contents, "" ); /* "@end ifinfo" ); */ _Chain_Insert( line->Node.previous, &new_line->Node ); next_node = (Line_Control *) line->Node.next; menu_insert_point = next_node; menu_items = 0; for ( ; ; ) { if ( next_node->keyword == KEYWORD_END ) break; if ( next_node->level == -1 ) goto continue_menu_loop; LineCopySectionName( next_node, Buffer ); if ( !next_found ) { next_found = TRUE; if (NodeNameIncludesChapter) sprintf( NextNodeName, "%s %s", ChapterName, Buffer ); else sprintf( NextNodeName, "%s", Buffer ); } if ( next_node->level <= line->level ) break; if ( next_node->level != (line->level + 1) ) goto continue_menu_loop; if ( menu_items == 0 ) { new_line = AllocateLine(); strcpy( new_line->Contents, "@ifinfo" ); _Chain_Insert( menu_insert_point->Node.previous, &new_line->Node ); new_line = AllocateLine(); strcpy( new_line->Contents, "@menu" ); _Chain_Insert( menu_insert_point->Node.previous, &new_line->Node ); } menu_items++; new_line = AllocateLine(); if (NodeNameIncludesChapter) sprintf( new_line->Contents, "* %s %s::", ChapterName, Buffer ); else sprintf( new_line->Contents, "* %s::", Buffer ); _Chain_Insert( menu_insert_point->Node.previous, &new_line->Node ); continue_menu_loop: next_node = (Line_Control *) next_node->Node.next; } /* * If menu items were generated, then insert the end of menu stuff. */ if ( menu_items ) { new_line = AllocateLine(); strcpy( new_line->Contents, "@end menu" ); _Chain_Insert( menu_insert_point->Node.previous, &new_line->Node ); new_line = AllocateLine(); strcpy( new_line->Contents, "@end ifinfo" ); _Chain_Insert( menu_insert_point->Node.previous, &new_line->Node ); } /* * Find the UpNodeName */ /* DumpList( &Lines ); */ if ( line->level == 0 ) { strcpy( UpNodeName, DocsUpNode ); } else { for ( up_node = line; up_node && !_Chain_Is_first((Chain_Node *)up_node) ; up_node = (Line_Control *) up_node->Node.previous ) { if ( (up_node->level == -1) ) continue; if ( up_node->level == (line->level - 1) ) { LineCopySectionName( up_node, Buffer ); if (NodeNameIncludesChapter) { if (!strcmp(ChapterName, Buffer)) sprintf( UpNodeName, "%s", Buffer ); else sprintf( UpNodeName, "%s %s", ChapterName, Buffer ); } else sprintf( UpNodeName, "%s", Buffer ); break; } } } /* * Update the node information */ #if 0 fprintf( stderr, "@node %s, %s, %s, %s\n", NodeName, NextNodeName, PreviousNodeName, UpNodeName ); #endif /* node_line was previously inserted */ if (!NodeNameIncludesChapter) { sprintf( node_line->Contents, "@node %s, %s, %s, %s", NodeName, NextNodeName, PreviousNodeName, UpNodeName ); } else { sprintf( node_line->Contents, "@node %s, %s, %s, %s", NodeName, NextNodeName, PreviousNodeName, UpNodeName ); } strcpy( PreviousNodeName, NodeName ); /* PrintLine( line ); */ } } /* * FormatToTexinfo */ void FormatToTexinfo( void ) { Line_Control *line; int baselevel = 0; int currentlevel; if ( Verbose ) fprintf( stderr, "-------->INSERTING TEXINFO MENUS\n" ); for ( line = (Line_Control *) Lines.first ; !_Chain_Is_last( &line->Node ) ; line = (Line_Control *) line->Node.next ) { switch (line->keyword) { case UNUSED: case KEYWORD_OTHER: case KEYWORD_END: line->level = -1; break; case KEYWORD_CHAPTER: case KEYWORD_APPENDIX: case KEYWORD_PREFACE: case KEYWORD_CHAPHEADING: currentlevel = 0; line->level = baselevel + currentlevel; break; case KEYWORD_SECTION: currentlevel = 1; line->level = baselevel + currentlevel; break; case KEYWORD_SUBSECTION: currentlevel = 2; line->level = baselevel + currentlevel; break; case KEYWORD_SUBSUBSECTION: currentlevel = 3; line->level = baselevel + currentlevel; break; case KEYWORD_RAISE: assert( baselevel ); baselevel--; line->level = -1; break; case KEYWORD_LOWER: baselevel++; line->level = -1; break; } } BuildTexinfoNodes(); } /* * PrintFile */ void PrintFile2( FILE *OutFile ) { Line_Control *line; if ( !OutFile ) { fprintf( stderr, "Unable to open (%s) for output\n", "stdout" ); exit_application( 1 ); } assert( OutFile ); for ( line = (Line_Control *) Lines.first ; !_Chain_Is_last( &line->Node ) ; line = (Line_Control *) line->Node.next ) { fprintf( OutFile, "%s\n", line->Contents ); /* fprintf( OutFile, "(%d,%d)%s\n", line->keyword, line->format, line->Contents ); */ } } /* * DumpList */ void DumpList( Chain_Control *the_list ) { Line_Control *line; fprintf( stderr, "---> Dumping list (%p)\n", the_list ); for ( line = (Line_Control *) the_list->first ; !_Chain_Is_last( &line->Node ) ; line = (Line_Control *) line->Node.next ) { /* if (line->level != -1) */ PrintLine( line ); /* fprintf( stderr, "%s\n", line->Contents ); */ } } /* * ReleaseFile */ void ReleaseFile() { Line_Control *line; Line_Control *next; for ( line = (Line_Control *) Lines.first ; !_Chain_Is_last( &line->Node ) ; ) { next = (Line_Control *) line->Node.next; line = next; } } /* * strtoInitialCaps */ void strtoInitialCaps( char *dest, char *src ) { char *source = src; char *destination = dest; source = src; destination = (dest) ? dest : src; while ( *source ) { while ( isspace( *source ) ) *destination++ = *source++; if ( !*source ) break; *destination++ = toupper( *source++ ); for ( ; *source && !isspace( *source ) ; source++ ) *destination++ = tolower( *source ); if ( !*source ) break; } *destination = '\0'; }