/* * 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-1997. * 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 DocsNextNode[] = ""; char DocsPreviousNode[] = "Top"; char DocsUpNode[] = "Top"; 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 = stdout; /************************************************************************* ************************************************************************* ***** DATA TYPES AND CONSTANT TABLES ***** ************************************************************************* *************************************************************************/ /* * Usage Information */ char *Usage_Strings[] = { "\n", "usage: cmd [-bv] files ...\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 HEADING 3 typedef enum { UNUSED, /* dummy 0 slot */ KEYWORD_CHAPTER, KEYWORD_CHAPHEADING, KEYWORD_SECTION, KEYWORD_SUBSECTION, 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; 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 }, { "@chapheading",SECTION, RT_FORBIDDEN, BL_FORBIDDEN, NULL }, { "@section", SECTION, RT_FORBIDDEN, BL_FORBIDDEN, NULL }, { "@subsection", 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>:%s\n", line->keyword, line->format, 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->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; Verbose = FALSE; while ((c = getopt(argc, argv, "bv")) != EOF) { switch (c) { case 'v': Verbose = TRUE; break; case '?': usage(); return 0; } } if ( Verbose ) fprintf( stderr, "Arguments successfully parsed\n" ); FillLinePool(); for ( index=optind ; index < argc ; index++ ) { ProcessFile( argv[ index ], NULL ); } if ( Verbose ) fprintf( stderr, "Exitting\n" ); return 0; } /* * ProcessFile */ void ProcessFile( char *inname, char *outname ) { char out[ 256 ]; int index; /* * Automatically generate the output file name. */ if ( outname == NULL ) { for( index=0 ; inname[index] && inname[index] != '.' ; index++ ) { out[ index ] = inname[ index ]; } out[ index++ ] = '.'; out[ index++ ] = 't'; out[ index++ ] = 'x'; out[ index++ ] = 't'; out[ index ] = '\0'; } /* * Read the file into our internal data structure */ if ( Verbose ) printf( "Processing (%s) -> (%s)\n", inname, out ); ReadFileIntoChain( inname ); 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 */ PrintFile( out ); 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 ReadFileIntoChain( char *inname ) { FILE *InFile; int line_count; int max_length; char *line; char Buffer[ BUFFER_SIZE ]; Line_Control *new_line; InFile = fopen( inname, "r" ); if ( !InFile ) { fprintf( stderr, "Unable to open (%s)\n", inname ); 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, "@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 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 ) { Line_Control *line; Line_Control *new_line; Line_Control *next_node; char Buffer[ BUFFER_SIZE ]; char ChapterName[ BUFFER_SIZE ]; char NodeName[ BUFFER_SIZE ]; char NextNode[ BUFFER_SIZE ]; char NextNodeName[ BUFFER_SIZE ]; char PreviousNodeName[ BUFFER_SIZE ]; char UpNodeName[ BUFFER_SIZE ]; char SectionName[ BUFFER_SIZE ]; char MenuBuffer[ BUFFER_SIZE ]; Line_Control *node_insert_point; Line_Control *menu_insert_point; Line_Control *node_line; boolean next_found; int menu_items; strcpy( PreviousNodeName, DocsPreviousNode ); for ( line = (Line_Control *) Lines.first ; !_Chain_Is_last( &line->Node ) ; line = (Line_Control *) line->Node.next ) { menu_insert_point = (Line_Control *) line->Node.next; switch ( Keywords[ line->keyword ].level ) { case TEXT: case HEADING: break; case SECTION: if ( line->keyword == KEYWORD_END ) goto bottom; if ( line->keyword == KEYWORD_CHAPTER || line->keyword == KEYWORD_CHAPHEADING ) { LineCopyFromRight( line, ChapterName ); strcpy( UpNodeName, DocsUpNode ); strcpy( NodeName, ChapterName ); } else { LineCopySectionName( line, Buffer ); sprintf( NodeName, "%s %s", ChapterName, Buffer ); strcpy( UpNodeName, ChapterName ); } strcpy( SectionName, NodeName ); /* * 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 ); menu_items = 0; if ( line->keyword == KEYWORD_CHAPTER || line->keyword == KEYWORD_CHAPHEADING ) { next_node = (Line_Control *) line->Node.next; next_found = FALSE; for ( ; ; ) { if ( next_node->keyword == KEYWORD_END ) break; if ( Keywords[ next_node->keyword ].level == SECTION ) { LineCopySectionName( next_node, Buffer ); if ( !next_found ) { next_found = TRUE; sprintf( NextNodeName, "%s %s", ChapterName, Buffer ); } 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(); sprintf( new_line->Contents, "* %s %s::", ChapterName, Buffer ); _Chain_Insert( menu_insert_point->Node.previous, &new_line->Node ); } next_node = (Line_Control *) next_node->Node.next; } } else { next_node = (Line_Control *) line->Node.next; next_found = FALSE; for ( ; ; ) { if ( Keywords[ next_node->keyword ].level == SECTION ) { if ( !next_found ) { if ( next_node->keyword == KEYWORD_END ) { strcpy( NextNodeName, DocsNextNode ); } else { LineCopySectionName( next_node, Buffer ); sprintf( NextNodeName, "%s %s", ChapterName, Buffer ); } next_found = TRUE; } break; } else if ( Keywords[ next_node->keyword ].level == SUBSECTION ) { LineCopySectionName( next_node, MenuBuffer ); /* has next node */ 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(); sprintf( new_line->Contents, "* %s::", MenuBuffer ); _Chain_Insert( menu_insert_point->Node.previous, &new_line->Node ); if ( !next_found ) { next_found = TRUE; strcpy( NextNodeName, MenuBuffer ); } } next_node = (Line_Control *) next_node->Node.next; } } 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 ); } #if 0 fprintf( stderr, "@node %s, %s, %s, %s\n", NodeName, NextNodeName, PreviousNodeName, UpNodeName ); #endif /* node_line was previously inserted */ sprintf( node_line->Contents, "@node %s, %s, %s, %s", NodeName, NextNodeName, PreviousNodeName, UpNodeName ); strcpy( PreviousNodeName, NodeName ); break; case SUBSECTION: strcpy( UpNodeName, SectionName ); LineCopyFromRight( line, NodeName ); new_line = AllocateLine(); strcpy( new_line->Contents, "@ifinfo" ); _Chain_Insert( line->Node.previous, &new_line->Node ); next_node = (Line_Control *) line->Node.next; for ( ; ; ) { if ( Keywords[ next_node->keyword ].level == SECTION ) { if ( next_node->keyword == KEYWORD_END ) { strcpy( NextNodeName, DocsNextNode ); } else { LineCopySectionName( next_node, Buffer ); sprintf( NextNodeName, "%s %s", ChapterName, Buffer ); } break; } else if ( Keywords[ next_node->keyword ].level == SUBSECTION ) { LineCopyFromRight( next_node, NextNodeName ); break; } next_node = (Line_Control *) next_node->Node.next; } #if 0 fprintf( stderr, "@node %s, %s, %s, %s\n", NodeName, NextNodeName, PreviousNodeName, UpNodeName ); #endif new_line = AllocateLine(); sprintf( new_line->Contents, "@node %s, %s, %s, %s", NodeName, NextNodeName, PreviousNodeName, UpNodeName ); _Chain_Insert( line->Node.previous, &new_line->Node ); new_line = AllocateLine(); strcpy( new_line->Contents, "@end ifinfo" ); _Chain_Insert( line->Node.previous, &new_line->Node ); strcpy( PreviousNodeName, NodeName ); break; } } bottom: } /* * FormatToTexinfo */ void FormatToTexinfo( void ) { if ( Verbose ) fprintf( stderr, "-------->INSERTING TEXINFO MENUS\n" ); BuildTexinfoNodes(); } /* * PrintFile */ void PrintFile( char *out ) { Line_Control *line; OutFile = fopen( out, "w+" ); if ( !OutFile ) { fprintf( stderr, "Unable to open (%s) for output\n", out ); 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 ) { 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'; }