/*
Copyright (C) 1994-2007 Frans Slothouber, Jacco van Weert, Petteri Kettunen,
Bernd Koesling, Thomas Aglassinger, Anthon Pang, Stefan Kost, David Druffner,
Sasha Vasko, Kai Hofmann, Thierry Pierron, Friedrich Haase, and Gergely Budai.
This file is part of ROBODoc
ROBODoc is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see
* aaa * aaaa * * */ /****f* Analyser/Analyse_Preformatted * FUNCTION * Analyse preformatted text ... (TODO) * SYNPOPSIS */ static void Analyse_Preformatted( struct RB_Item *arg_item, int indent ) /* * INPUTS * * arg_item -- the item to be analysed. * * indent -- * SOURCE */ { int i; int in_list = FALSE; int new_indent = -1; int preformatted = FALSE; char *line = NULL; /* preformatted blocks */ if ( arg_item->no_lines > 0 ) { i = 0; /* Skip any pipe stuff */ for ( ; ( i < arg_item->no_lines ) && ( arg_item->lines[i]->kind == ITEM_LINE_PIPE ); ++i ) { /* Empty */ } line = arg_item->lines[i]->line; if ( ( !in_list ) && ( arg_item->lines[i]->format & RBILA_BEGIN_LIST ) ) { in_list = TRUE; } if ( ( in_list ) && ( arg_item->lines[i]->format & RBILA_END_LIST ) ) { in_list = FALSE; } for ( ++i; i < arg_item->no_lines; i++ ) { if ( arg_item->lines[i]->kind == ITEM_LINE_PIPE ) { if ( preformatted ) { arg_item->lines[i]->format |= RBILA_END_PRE; } for ( ; ( i < arg_item->no_lines ) && ( arg_item->lines[i]->kind == ITEM_LINE_PIPE ); ++i ) { /* Empty */ }; /* Every item ends with an ITEM_LINE_END, so: */ assert( i < arg_item->no_lines ); if ( preformatted ) { arg_item->lines[i]->format |= RBILA_BEGIN_PRE; } } line = arg_item->lines[i]->line; new_indent = Get_Indent( line ); if ( ( !in_list ) && ( arg_item->lines[i]->format & RBILA_BEGIN_LIST ) ) { in_list = TRUE; } if ( ( in_list ) && ( arg_item->lines[i]->format & RBILA_END_LIST ) ) { in_list = FALSE; } if ( !in_list ) { if ( ( new_indent > indent ) && !preformatted ) { preformatted = TRUE; arg_item->lines[i]->format |= RBILA_BEGIN_PRE; } else if ( ( new_indent <= indent ) && preformatted ) { preformatted = FALSE; arg_item->lines[i]->format |= RBILA_END_PRE; } else { /* An empty line */ } } else { /* We are in a list, do nothing */ } } } } /******/ /****f* Analyser/Analyse_Paragraphs * FUNCTION * Analyse paragraphs... (TODO) * SYNPOPSIS */ static void Analyse_Paragraphs( struct RB_Item *arg_item ) /* * INPUTS * * arg_item -- the item to be analysed. * SOURCE */ { int i; int in_par = FALSE; int in_list = FALSE; int in_pre = FALSE; int is_empty = FALSE; int prev_is_empty = FALSE; for ( i = 0; ( i < arg_item->no_lines ) && ( arg_item->lines[i]->kind == ITEM_LINE_PIPE ); ++i ) { /* Empty */ } assert( i < arg_item->no_lines ); if ( ( arg_item->lines[i]->format == 0 ) ) { arg_item->lines[i]->format |= RBILA_BEGIN_PARAGRAPH; in_par = TRUE; } for ( ; i < arg_item->no_lines; i++ ) { char *line = arg_item->lines[i]->line; prev_is_empty = is_empty; is_empty = Is_Empty_Line( line ); if ( arg_item->lines[i]->format & RBILA_BEGIN_LIST ) { in_list = TRUE; } if ( arg_item->lines[i]->format & RBILA_BEGIN_PRE ) { in_pre = TRUE; } if ( arg_item->lines[i]->format & RBILA_END_LIST ) { in_list = FALSE; } if ( arg_item->lines[i]->format & RBILA_END_PRE ) { in_pre = FALSE; } if ( in_par ) { if ( ( arg_item->lines[i]->format & RBILA_BEGIN_LIST ) || ( arg_item->lines[i]->format & RBILA_BEGIN_PRE ) || is_empty ) { in_par = FALSE; arg_item->lines[i]->format |= RBILA_END_PARAGRAPH; } } else { if ( ( arg_item->lines[i]->format & RBILA_END_LIST ) || ( arg_item->lines[i]->format & RBILA_END_PRE ) || ( !is_empty && prev_is_empty && !in_list && !in_pre ) ) { in_par = TRUE; arg_item->lines[i]->format |= RBILA_BEGIN_PARAGRAPH; } } } if ( in_par ) { arg_item->lines[arg_item->no_lines - 1]->format |= RBILA_END_PARAGRAPH; } } /******/ /****f* Analyser/Analyse_Indentation * FUNCTION * Figure out how text is indented. * SYNPOPSIS */ static int Analyse_Indentation( struct RB_Item *arg_item ) /* * INPUTS * * arg_item -- the item to be analysed. * SOURCE */ { int i; int indent = -1; assert( arg_item->no_lines > 0 ); for ( i = 0; i < arg_item->no_lines; ++i ) { if ( arg_item->lines[i]->kind == ITEM_LINE_PLAIN ) { char *line = arg_item->lines[i]->line; if ( Is_Empty_Line( line ) ) { /* Empty */ indent = 0; } else { indent = Get_Indent( line ); break; } } } assert( indent >= 0 ); return indent; } /******/ /****f* Analyser/Analyse_Item_Format * FUNCTION * Try to determine the formatting of an item. * An empty line generates a new paragraph * Things that are indented more that the rest of the text * are preformatted. * Things that start with a '*' or '-' create list items * unless they are indented more that the rest of the text. * SYNPOPSIS */ static void Analyse_Item_Format( struct RB_Item *arg_item ) /* * INPUTS * * arg_item -- the item to be analysed. * SOURCE */ { // If item is not empty if ( arg_item->no_lines ) { // If it is a SOURCE item if ( Works_Like_SourceItem( arg_item->type ) ) { // Preformat it like a SOURCE item Preformat_All( arg_item, TRUE ); } // Check if we have to analyse this item else if ( ( course_of_action.do_nopre || Is_Format_Item( arg_item->type ) ) && !Is_Preformatted_Item( arg_item->type ) ) { // analyse item int indent = Analyse_Indentation( arg_item ); Analyse_List( arg_item, indent ); Analyse_Preformatted( arg_item, indent ); Analyse_Paragraphs( arg_item ); } // If none of above, preformat item else { // Preformat it Preformat_All( arg_item, FALSE ); } } // Item is empty else { // Do nothing } } /*****/ static int Trim_Empty_Item_Begin_Lines( struct RB_header *arg_header, struct RB_Item *arg_item, int current_index ) { char *c; if ( Works_Like_SourceItem( arg_item->type ) ) { /* We skip the first line after the source item, if * it an remark end marker -- such as '*)' */ c = arg_header->lines[current_index]; if ( RB_Is_Remark_End_Marker( c ) ) { c = RB_Skip_Remark_End_Marker( c ); c = RB_Skip_Whitespace( c ); if ( *c != '\0' ) { c = arg_header->lines[current_index]; RB_Warning( "text following a remark end marker:\n%s\n", c ); } ++current_index; } } if ( current_index > arg_item->end_index ) { /* item is empty */ } else { do { c = arg_header->lines[current_index]; c = RB_Skip_Whitespace( c ); if ( RB_Has_Remark_Marker( c ) ) { c = RB_Skip_Remark_Marker( c ); } c = RB_Skip_Whitespace( c ); if ( *c == '\0' ) { ++current_index; } } while ( ( *c == '\0' ) && ( current_index < arg_item->end_index ) ); } return current_index; } static int Trim_Empty_Item_End_Lines( struct RB_header *arg_header, struct RB_Item *arg_item, int current_index ) { char *c; if ( Works_Like_SourceItem( arg_item->type ) ) { c = arg_header->lines[current_index]; if ( RB_Is_Remark_Begin_Marker( c ) ) { c = RB_Skip_Remark_Begin_Marker( c ); c = RB_Skip_Whitespace( c ); if ( *c != '\0' ) { c = arg_header->lines[current_index]; RB_Warning( "text following a remark begin marker:\n%s\n", c ); } --current_index; } } do { c = arg_header->lines[current_index]; c = RB_Skip_Whitespace( c ); if ( RB_Has_Remark_Marker( c ) ) { c = RB_Skip_Remark_Marker( c ); } c = RB_Skip_Whitespace( c ); if ( *c == '\0' ) { --current_index; } } while ( ( *c == '\0' ) && ( current_index > arg_item->begin_index ) ); return current_index; } static void Trim_Empty_Item_Lines( struct RB_header *arg_header, struct RB_Item *arg_item ) { arg_item->no_lines = arg_item->end_index - arg_item->begin_index + 1; if ( arg_item->no_lines <= 1 ) { /* item is empty */ arg_item->no_lines = 0; } else { int current_index; /* trim all empty lines at the begin of an item */ /* we skip the first line because that contains the item name. */ current_index = arg_item->begin_index + 1; current_index = Trim_Empty_Item_Begin_Lines( arg_header, arg_item, current_index ); /* Is there anything left? */ if ( current_index <= arg_item->end_index ) { arg_item->begin_index = current_index; /* trim all the empty lines at the end of an item */ current_index = arg_item->end_index; current_index = Trim_Empty_Item_End_Lines( arg_header, arg_item, current_index ); if ( current_index >= arg_item->begin_index ) { arg_item->end_index = current_index; arg_item->no_lines = arg_item->end_index - arg_item->begin_index + 1; } else { /* item is empty */ arg_item->no_lines = 0; } } else { /* item is empty */ arg_item->no_lines = 0; } } } /* TODO This routine is way too long */ static void Copy_Lines_To_Item( struct RB_header *arg_header, struct RB_Item *arg_item ) { #if 0 printf( "%d\n%d\n%s\n%s\n", arg_item->begin_index, arg_item->end_index, arg_header->lines[arg_item->begin_index], arg_header->lines[arg_item->end_index] ); #endif Trim_Empty_Item_Lines( arg_header, arg_item ); if ( arg_item->no_lines > 0 ) { int i = 0; int j = 0; struct RB_Item_Line *itemline = NULL; int tool_active = 0; // Shows wether we are inside a tool body /* Allocate enough memory for all the lines, plus one * extra line */ ++arg_item->no_lines; arg_item->lines = calloc( arg_item->no_lines, sizeof( struct RB_Item_Line * ) ); if ( !arg_item->lines ) { RB_Panic( "Out of memory! %s\n", "Copy_Lines_To_Item" ); } /* And create an RB_Item_Line for each of them, and add * those to the RB_Item */ for ( i = 0; i < arg_item->no_lines - 1; ++i ) { char *c = arg_header->lines[arg_item->begin_index + i]; /* TODO should be a Create_ItemLine() */ itemline = malloc( sizeof( struct RB_Item_Line ) ); if ( !itemline ) { RB_Panic( "Out of memory! %s (2)\n", "Copy_Lines_To_Item" ); } c = ExpandTab( c ); c = RB_Skip_Whitespace( c ); // Lines with remark marker if ( RB_Has_Remark_Marker( c ) && !Works_Like_SourceItem( arg_item->type ) ) { char *c2, *c3; int pipe_mode; enum ItemLineKind item_kind; c = RB_Skip_Remark_Marker( c ); c2 = RB_Skip_Whitespace( c ); if ( *c2 ) { // Check wether a tool starts or ends if ( ( c3 = Is_Tool( c2, &item_kind, &tool_active ) ) ) { itemline->kind = item_kind; c = c3; } // If we have an active tool, copy the body lines else if ( tool_active ) { itemline->kind = ITEM_LINE_TOOL_BODY; c++; // Skip space after the remark marker } // Check for pipes else if ( ( c3 = Is_Pipe_Marker( c2, &pipe_mode ) ) ) { itemline->kind = ITEM_LINE_PIPE; itemline->pipe_mode = pipe_mode; c = c3; } // Plain Items ... else { itemline->kind = ITEM_LINE_PLAIN; } } // Empty lines with remark markers and active tool else if ( tool_active ) { itemline->kind = ITEM_LINE_TOOL_BODY; } // Plain empty lines with remark markers... else { itemline->kind = ITEM_LINE_PLAIN; } } else { itemline->kind = ITEM_LINE_RAW; /* The is raw code, so we do not want to have the * whitespace stripped of */ c = arg_header->lines[arg_item->begin_index + i]; c = ExpandTab( c ); } if ( ( !Works_Like_SourceItem( arg_item->type ) && ( itemline->kind != ITEM_LINE_RAW ) ) || Works_Like_SourceItem( arg_item->type ) ) { itemline->line = RB_StrDup( c ); itemline->format = 0; arg_item->lines[j] = itemline; ++j; } else { /* We dump the RAW item lines if we are not in a * source item. */ free( itemline ); } } if ( j > 0 ) { /* And one empty line to mark the end of an item and * to be able to store some additional formatting actions */ itemline = malloc( sizeof( struct RB_Item_Line ) ); if ( !itemline ) { RB_Panic( "Out of memory! %s (3)\n", "Copy_Lines_To_Item" ); } itemline->kind = ITEM_LINE_END; itemline->line = RB_StrDup( "" ); itemline->format = 0; arg_item->lines[j] = itemline; /* Store the real number of lines we copied */ assert( arg_item->no_lines >= ( j + 1 ) ); arg_item->no_lines = j + 1; } else { arg_item->no_lines = 0; free( arg_item->lines ); arg_item->lines = NULL; } } else { arg_item->no_lines = 0; arg_item->lines = NULL; } } /****f* Analyser/RB_Analyse_Items * FUNCTION * Locate the items in the header and create RB_Item structures for * them. * SYNPOPSIS */ static int Analyse_Items( struct RB_header *arg_header ) /* * SOURCE */ { int line_nr; enum ItemType item_type = NO_ITEM; struct RB_Item *new_item; struct RB_Item *cur_item; RB_Item_Lock_Reset( ); /* find the first item */ for ( line_nr = 0; line_nr < arg_header->no_lines; ++line_nr ) { item_type = RB_Is_ItemName( arg_header->lines[line_nr] ); if ( item_type != NO_ITEM ) { break; } } /* and all the others */ while ( ( item_type != NO_ITEM ) && ( line_nr < arg_header->no_lines ) ) { new_item = RB_Create_Item( item_type ); new_item->begin_index = line_nr; /* Add the item to the end of the list of items. */ if ( arg_header->items ) { for ( cur_item = arg_header->items; cur_item->next; cur_item = cur_item->next ) { /* Empty */ } cur_item->next = new_item; } else { arg_header->items = new_item; } /* Find the next item */ for ( ++line_nr; line_nr < arg_header->no_lines; ++line_nr ) { item_type = RB_Is_ItemName( arg_header->lines[line_nr] ); if ( item_type != NO_ITEM ) { break; } } /* This points to the last line in the item */ new_item->end_index = line_nr - 1; assert( new_item->end_index >= new_item->begin_index ); /* Now analyse and copy the lines */ Copy_Lines_To_Item( arg_header, new_item ); Analyse_Item_Format( new_item ); /* Handy for debugging wiki formatting * Dump_Item( new_item ); */ } return 0; } /******/ /****f* Analyser/ToBeAdded * FUNCTION * Test whether or not a header needs to be added to the * list of headers. This implements the options * --internal * and * --internalonly * SYNPOPSIS */ static int ToBeAdded( struct RB_Document *document, struct RB_header *header ) /* * INPUTS * o document -- a document (to determine the options) * o header -- a header * RESULT * TRUE -- Add header * FALSE -- Don't add header * SOURCE */ { int add = FALSE; if ( header->is_internal ) { if ( ( document->actions.do_include_internal ) || ( document->actions.do_internal_only ) ) { add = TRUE; } else { add = FALSE; } } else { if ( document->actions.do_internal_only ) { add = FALSE; } else { add = TRUE; } } return add; } /******/ /****f* Analyser/Grab_Header * FUNCTION * Grab a header from a source file, that is scan a source file * until the start of a header is found. Then search for the end * of a header and store all the lines in between. * SYNPOPSIS */ static struct RB_header *Grab_Header( FILE *sourcehandle, struct RB_Document *arg_document ) /* * INPUTS * o sourcehandle -- an opened source file. * OUTPUT * o sourcehandle -- will point to the line following the end marker. * RESULT * 0 if no header was found, or a pointer to a new header otherwise. * SOURCE */ { struct RB_header *new_header = NULL; int is_internal = 0; struct RB_HeaderType *header_type = NULL; int good_header = FALSE; int reuse = FALSE; do { good_header = FALSE; header_type = RB_Find_Marker( sourcehandle, &is_internal, reuse ); reuse = FALSE; if ( header_type ) { struct RB_header *duplicate_header = NULL; long previous_line = 0; new_header = RB_Alloc_Header( ); new_header->htype = header_type; new_header->is_internal = is_internal; if ( Find_Header_Name( sourcehandle, new_header ) ) { new_header->line_number = line_number; RB_Say( "found header [line %5d]: \"%s\"\n", SAY_DEBUG, line_number, new_header->name ); duplicate_header = RB_Document_Check_For_Duplicate( arg_document, new_header ); if ( duplicate_header ) { /* Duplicate headers do not crash the program so * we accept them. But we do warn the user. */ RB_Warning ( "A header with the name \"%s\" already exists.\n See %s(%d)\n", new_header->name, Get_Fullname( duplicate_header->owner->filename ), duplicate_header->line_number ); } if ( ( new_header->function_name = Function_Name( new_header->name ) ) == NULL ) { RB_Warning( "Can't determine the \"function\" name.\n" ); RB_Free_Header( new_header ); new_header = NULL; } else { if ( ( new_header->module_name = Module_Name( new_header->name ) ) == NULL ) { RB_Warning ( "Can't determine the \"module\" name.\n" ); RB_Free_Header( new_header ); new_header = NULL; } else { previous_line = line_number; if ( Find_End_Marker( sourcehandle, new_header ) == 0 ) { RB_Warning ( "found header on line %d with name \"%s\"\n" " but I can't find the end marker\n", previous_line, new_header->name ); /* Reuse the current line while finding the next * Marking using RB_Find_Marker() */ reuse = TRUE; RB_Free_Header( new_header ); new_header = NULL; } else { RB_Say( "found end header [line %5d]:\n", SAY_DEBUG, line_number ); /* Good header found, we can stop */ good_header = TRUE; } } } } else { RB_Warning( "found header marker but no name\n" ); RB_Free_Header( new_header ); new_header = NULL; } } else { /* end of the file */ good_header = TRUE; } } while ( !good_header ); return new_header; } /*******/ /****f* Analyser/Module_Name * FUNCTION * Get the module name from the header name. The header name will be * something like * * module/functionname. * * SYNPOPSIS */ static char *Module_Name( char *header_name ) /* * INPUTS * o header_name -- a pointer to a nul terminated string. * RESULT * Pointer to the modulename. You're responsible for freeing it. * SEE ALSO * Function_Name() * SOURCE */ { char *cur_char; char c; char *name = NULL; assert( header_name ); for ( cur_char = header_name; *cur_char && *cur_char != '/'; ++cur_char ); if ( *cur_char ) { c = *cur_char; *cur_char = '\0'; name = RB_StrDup( header_name ); *cur_char = c; } return name; } /******/ /****f* Analyser/Function_Name * FUNCTION * A header name is consists of two parts. The module name and * the function name. This returns a pointer to the function name. * The name "function name" is a bit obsolete. It is really the name * of any of objects that can be documented; classes, methods, * variables, functions, projects, etc. * SYNOPSIS */ static char *Function_Name( char *header_name ) /* * SOURCE */ { char *cur_char; char *name; name = NULL; if ( ( cur_char = header_name ) != NULL ) { for ( ; *cur_char != '\0'; ++cur_char ) { if ( '/' == *cur_char ) { ++cur_char; if ( *cur_char ) { name = cur_char; break; } } } } if ( name ) { return RB_StrDup( name ); } else { return ( name ); } } /*** Function_Name ***/ /****f* Analyser/RB_Find_Marker * NAME * RB_Find_Marker -- Search for header marker in document. * FUNCTION * Read document file line by line, and search each line for * any of the headers defined in the array header_markers (OR * if using the -rh switch, robo_head) * SYNOPSIS */ static struct RB_HeaderType *RB_Find_Marker( FILE *document, int *is_internal, int reuse_previous_line ) /* * INPUTS * document - pointer to the file to be searched. * the gobal buffer line_buffer. * OUTPUT * o document will point to the line after the line with * the header marker. * o is_internal will be TRUE if the header is an internal * header. * RESULT * o header type * BUGS * Bad use of feof(), fgets(). * SEE ALSO * Find_End_Marker * SOURCE */ { int found; char *cur_char; struct RB_HeaderType *header_type = 0; cur_char = NULL; found = FALSE; while ( !feof( document ) && !found ) { if ( reuse_previous_line ) { /* reuse line in the line_buffer */ reuse_previous_line = FALSE; } else { RB_FreeLineBuffer( ); myLine = RB_ReadWholeLine( document, line_buffer, &readChars ); } if ( !feof( document ) ) { line_number++; found = RB_Is_Begin_Marker( myLine, &cur_char ); if ( found ) { header_type = AnalyseHeaderType( &cur_char, is_internal ); RB_Say( "found header marker of type %s\n", SAY_DEBUG, header_type->indexName ); } } } return header_type; } /******** END RB_Find_Marker ******/ /****f* Analyser/AnalyseHeaderType * FUNCTION * Determine the type of the header. * SYNOPSIS */ struct RB_HeaderType *AnalyseHeaderType( char **cur_char, int *is_internal ) /* * INPUTS * o cur_char -- pointer to the header type character * OUTPUT * o is_internal -- indicates if it is an internal header or not.* * o cur_char -- points to the header type character * RESULT * o pointer to a RB_HeaderType * SOURCE */ { struct RB_HeaderType *headertype = 0; *is_internal = RB_IsInternalHeader( **cur_char ); if ( *is_internal ) { /* Skip the character */ ++( *cur_char ); } headertype = RB_FindHeaderType( **cur_char ); if ( !headertype ) { RB_Panic( "Undefined headertype (%c)\n", **cur_char ); } return headertype; } /*******/ /****f* Analyser/Find_End_Marker * FUNCTION * Scan and store all lines from a source file until * an end marker is found. * SYNOPSIS */ static int Find_End_Marker( FILE *document, struct RB_header *new_header ) /* * INPUTS * o document -- a pointer to an opened source file. * OUTPUT * o new_header -- the lines of source code will be added * here. * RESULT * o TRUE -- an end marker was found. * o FALSE -- no end marker was found while scanning the * source file. * SOURCE */ { int found = FALSE; unsigned int no_lines = 0; unsigned int max_no_lines = 10; char **lines = NULL; char **new_lines = NULL; char *dummy; lines = ( char ** ) calloc( max_no_lines, sizeof( char * ) ); if ( !lines ) { RB_Panic( "Out of memory! %s()\n", "Find_End_Marker" ); } while ( !feof( document ) ) { RB_FreeLineBuffer( ); myLine = RB_ReadWholeLine( document, line_buffer, &readChars ); ++line_number; /* global linecounter, koessi */ if ( RB_Is_Begin_Marker( myLine, &dummy ) ) { /* Bad... found a begin marker but was expecting to find an end marker. Panic... */ found = FALSE; return found; } else if ( RB_Is_End_Marker( myLine ) ) { RB_Say( "Found end marker \"%s\"", SAY_DEBUG, myLine ); found = TRUE; break; } else { unsigned int n; char *line; line = RB_StrDup( myLine ); n = strlen( line ); assert( n > 0 ); assert( line[n - 1] == '\n' ); /* Strip CR */ line[n - 1] = '\0'; lines[no_lines] = line; ++no_lines; if ( no_lines == max_no_lines ) { max_no_lines *= 2; new_lines = realloc( lines, max_no_lines * sizeof( char * ) ); if ( !new_lines ) { RB_Panic( "Out of memory! %s()\n", "Find_End_Marker" ); } lines = new_lines; } } } new_header->no_lines = no_lines; new_header->lines = lines; return found; } /******/ /* TODO Documentation */ static void Remove_Trailing_Asterics( char *line ) { int i = strlen( line ) - 1; for ( ; ( i > 0 ) && utf8_isspace( line[i] ); i-- ) { /* Empty */ } for ( ; ( i > 0 ) && ( line[i] == '*' ); i-- ) { line[i] = ' '; } } /****if* Utilities/RB_WordWithSpacesLen * SYNOPSIS */ static int RB_WordWithSpacesLen( char *str ) /* * FUNCTION * get the amount of bytes until next separator character or ignore character * or end of line * INPUTS * char *str -- the line * RESULT * int -- length of the next word or 0 * SEE ALSO * RB_Find_Header_Name() * SOURCE */ { int len; char c; for ( len = 0; ( ( c = *str ) != '\0' ) && ( c != '\n' ); ++str, ++len ) { // Look for header truncating characters if ( Find_Parameter_Char( &( configuration.header_separate_chars ), c ) != NULL || Find_Parameter_Char( &( configuration.header_ignore_chars ), c ) != NULL ) { // and exit loop if any found break; } } /* Don't count any of the trailing spaces. */ if ( len ) { --str; for ( ; utf8_isspace( *str ); --len, --str ) { /* empty */ } } return ( len ); } /*** RB_WordWithSpacesLen ***/ /* TODO Documentation */ static int Find_Header_Name( FILE *fh, struct RB_header *hdr ) { char *cur_char = myLine; char **names = NULL; int num = 0; Remove_Trailing_Asterics( cur_char ); skip_while( *cur_char != '*' ); skip_while( !utf8_isspace( *cur_char ) ); skip_while( utf8_isspace( *cur_char ) ); while ( *cur_char ) { int length = RB_WordWithSpacesLen( cur_char ); if ( length == 0 ) break; names = realloc( names, ( ++num ) * sizeof *names ); if ( !names ) { RB_Panic( "Out of memory! %s()\n", "Find_Header_Name" ); } names[num - 1] = RB_StrDupLen( cur_char, length ); /* printf("%c adding name = %s\n", num > 1 ? ' ' : '*', names[ num - 1 ] ); */ cur_char += length; if ( Find_Parameter_Char( &( configuration.header_separate_chars ), *cur_char ) ) { for ( cur_char++; utf8_isspace( *cur_char ); cur_char++ ); /* End of line reach, but comma encountered, more headernames follow on next line */ if ( *cur_char == 0 ) { /* Skip comment */ RB_FreeLineBuffer( ); myLine = RB_ReadWholeLine( fh, line_buffer, &readChars ); line_number++; for ( cur_char = myLine; *cur_char && !utf8_isalpha( *cur_char ); cur_char++ ); } } } hdr->names = names; hdr->no_names = num; hdr->name = num ? names[0] : NULL; return num; } /***** Find_Header_Name *****/