/* ioopen_routines WJS Mar 96 */ /* Contains externally referenced routines: */ /* Logical configure_output(diag_optfile,output_info,files) */ /* int create_wjstbl(optfile,wjstbl,maxlen_tbl) */ /* Logical get_datafield_options(datafield_optfile, */ /* consec_seps,embed_seps,trim) */ /* void get_files(files,indirect_name,indirect_file,inner_param_list) */ /* Logical get_latlonparams(latlon_optfile,outlat,outlon,inlat,outlat) */ /* Logical get_timedateparams(timedate_optfile,outtime_structarray, */ /* intime_structarray,fragbuflist) */ /* void init_outinfo (outinfo_struct) */ /* void init_file_structs (files,indirect_file) */ /* void init_time_structs (in,out,buflist) */ /* Logical open_and_log_opt (file) */ /* Logical remove_vars(removals_file,ntotal,list_after_removals,ncnt) */ /* all called by ioopen_ (& only by ioopen_) */ /* */ /* Some notes on parsing the various keyword strings: the check- */ /* ing is not foolproof, and not consistently applied, especially */ /* in cases of potentially overlapping strings (eg, suppose there */ /* is some future "translation" keyword-how does this interact with */ /* "pre-translation-" prefix. The main unrequired rule that keeps */ /* things working is that the separator characters like - and _ do */ /* not appear in the middle of significant strings... except for */ /* the hyphen in pre-translation-! Sigh... */ #define IOOPEN_ROUTINES_VERSION "ioopen_routines version 1.8 18 Apr 97" /* 18 Apr 97. intimeconvention; outtimeconvention */ /* Pull outtime[].max per-field initializations */ /* 3 Apr 97. Remove unneeded line that had typo. */ /* [Begin v 1.8] */ /* 17 Mar 97. Change default destination of the other_comment */ /* sink from the screen to its own sink */ /* Remove test for uninitialized mode_ptrs. Now done */ /* before opening file */ /* Bug fix: must save most time info even if output */ /* of that piece of time not wanted. Symptom: Julian calc */ /* falls apart if yr/mo/da not all requested on output */ /* 8 Mar 97. Init .reread, .scanheader bits (unused as of 2.8b) */ /* [Begin v 1.7a] */ /* 27 Feb 97. .nvar -> .lastvar field name change */ /* .maxlenrec init */ /* 25 Feb 97. Init outtime[].max, .fmt fields */ /* Allow output time frags w/o corresponding variables */ /* 17 Feb 97. eof, eod bits; buf item. EOD logic beginning */ /* Bug fix: wjstbl not initialized if no opt file. */ /* Remove make_wjstbl_from_optfile by moving its */ /* sole "value-added" (opening & closing opt file) into */ /* create_wjstbl */ /* 10 Feb 97. Revamp of 17 Dec no good-fix. (17 Dec was "real" */ /* beginning of 1.7, hence no version increment) Enhance */ /* err msgs while at it */ /* 5 Feb 97. VARLIST stuff */ /* 28 Jan 97. Init files[i].ndatarecs */ /* 3 Jan 97. analy_source. Make objects_param_for_source */ /* do only that (rest of logic to analy_source; */ /* routine name now objects_param_number) */ /* [Begin v 1.7] */ /* 17 Dec 96. Init files[i].item_alt_separators */ /* Allow wjstbl data to consist of escaped strings */ /* (Involves revamp of wjstbl loop logic) */ /* 6 Dec 96. source_is... -> source_type = */ /* Move (unused) get_int_from_keyword and */ /* get_int_from_string to defgb */ /* make_wjstbl_from_optfile; replaces some code as */ /* well as get_input_widths & get_translation_table */ /* routines. */ /* Bug fix: non-numeric checks via sscanf wrong. */ /* Switch to strtol (should be faster, too) */ /* Bug fix-did not honor embedded separators in */ /* script string */ /* [Begin 1.6] */ /* [1.0 -> 1.5 comments in defgb_revision.doc. 1 Jan 97] */ #include "defgb.h" /* Uses no global variables; defines only version */ char *version_ioopen_routines = IOOPEN_ROUTINES_VERSION; /* Requires following routines: */ void add_to_comment_stream(); void analy_source(); char *buildstring(); /* Dynamic string concatenator */ void err(); void errn(); int extract_wjstbl(); int get_int_from_string(); Logical get_logical_from_string(); int iovarlevel__(); int lookup_lev0varlist(); char *lookup_wjstbl(); int getrec_proccomment(); char *nxttok(); char *strdupl(); /* Gets mem for copy of string & copies it */ int transvar(); /******************* *****************/ char *get_string_from_escaped_string (string,errmsg1,errmsg2) char *string,*errmsg1,*errmsg2; /* Turns string of form ab\t,\n to equivalent 5 character string */ /* a, b, tab, comma, newline */ /* Creates output string and returns pointer to it; NULL if trouble */ { char *errtmp1,*errtmp2; char *outstr,*inptr,*outptr; strdupl(&outstr,string,"get_string_from_escaped_string"); inptr = outstr; outptr = outstr; while ( (*outptr = *(inptr++)) != '\0' ) { if (*outptr == '\\') switch (*(inptr++)) { case 't': *outptr = '\t'; break; case 'r': *outptr = '\r'; break; case 'v': *outptr = '\v'; break; case 'n': *outptr = '\n'; break; case 'b': *outptr = '\b'; break; case 'f': *outptr = '\f'; break; case '0': *outptr = '\0'; break; case '\\': *outptr = '\\'; break; case '\'': *outptr = '\''; break; case '\"': *outptr = '\"'; break; case '\0': inptr--; /* Make sure we don't go beyond end. Fall thru to err */ default: *inptr = '\0'; /* Set up to terminate loop */ *outstr = '\0'; /* Set error indicator */ break; } if (*outstr == '\0') { free (outstr); outstr == NULL; break; } else outptr++; } if (outstr == NULL) { /* Unterminated or illegal \ sequence */ errtmp1 = buildstring("Illegal escaped string ->",string,"<-\n ", " get_string_from_escaped_string"); errtmp2 = buildstring(errmsg1,errmsg2,NULL," get_string_from_escaped_string"); err (errtmp1,errtmp2); free (errtmp1); free (errtmp2); } return outstr; } Logical open_and_log_opt (file) /* Checks validity of optional input data. Input is a pointer to */ /* a file_info structure whose validity is to be checked. Logging */ /* is done. The file is opened, if appropriate. Return is TRUE or */ /* FALSE depending on whether valid data exists */ struct fileinfo *file; { char *source; char *msg,*ptr; char *brack_method_name; source=file->source; if (file->force_flag > 0 && source == NULL) err (file->descrip," required option missing"); if (file->force_flag < 0 && source != NULL) err (file->descrip," option provided and it must not be"); if (source == NULL) return FALSE; /* buildstring used in next line to get space & properly position */ /* METHODNAME. Then we need to put blanks & delims in place */ brack_method_name = buildstring(TAG_DELIM,METHOD_NAME," ", " bracketing method name"); *brack_method_name = ' '; *(brack_method_name+1) = *TAG_DELIM; *(brack_method_name+strlen(brack_method_name)-1) = *(TAG_DELIM+1); ptr=strchr(brack_method_name,OMIT_FROM_TAG_CHAR); if (ptr != NULL) { /* Replace last blank preceding OMIT_FROM_TAG_CHAR with },null */ /* Find last nonblank */ while (*(--ptr) == ' ') ; /* Advance beyond it and put in tag termination */ *(++ptr) == '}'; *(++ptr) == '\0'; } if (file->source_type == INDIRECT_FILE_LINE) { /* Add comment that data embedded in indirect file is being used */ msg=buildstring(file->descrip," data: contained in indirect input file", NULL, " have_opt_ msg"); add_to_comment_stream (brack_method_name,msg); } else if (file->source_type == DATA_FILE) { file->stream=fopen(source,"r"); if (file->stream == NULL) { msg=buildstring("Error opening ",file->descrip," file "," have_opt_ msg"); err (msg,source); } file->open=TRUE; file->eof=FALSE; file->eod=FALSE; msg=buildstring(file->descrip, " file: ",source, " have_opt_ msg"); add_to_comment_stream(brack_method_name,msg); } else { msg=buildstring( file->descrip, " data must come from optional or indirect file\n Bad source = ", NULL, " have_opt_ msg" ); err (msg,source); } free (msg); free (brack_method_name); return TRUE; } int create_wjstbl (file,output_list,output_list_size) struct fileinfo *file; char *output_list; int output_list_size; /* To associate strings with variables, create string */ /* w/ format SVSAsB per pair, where */ /* S = is any non-whitespace character not in either */ /* pair ("Separator") */ /* V = variable (will be translated, if appropriate) */ /* As= associated string */ /* B = at least one whitespace character */ /* Variables can appear in any order in input file, but may */ /* appear only once. A string may be both a variable name */ /* and an associated string, but this asks for trouble... */ /* */ /* Returns count of # elements in table */ /* Returns list in provided argument ('\0' if no input file) */ { int paircnt; char varname[MAXVARNAMESIZE+1]; char *tok,*output_list_ptr,*this_var; char *entry_ptr,*next_entry_ptr; char *next_item_ptr; char *rec_id = NULL,*msg = NULL,*opt_file_id; char *comment_postfix; /* Consistency of item separator list and entry separator list */ /* checked in init_file_structs, but really should be checked here */ /* in case something changes, or if we ever have individual */ /* lists per opt file. */ if (! open_and_log_opt(file)) { *output_list = '\0'; return 0; } opt_file_id = buildstring (" Problem in ",file->descrip," optional file", " creating opt_file_id string"); output_list_ptr=output_list; paircnt=0; comment_postfix = file->comment_postfix; /* Loop per record in file */ while (getrec_proccomment(file,comment_postfix) != 0) { /* Throw out comments from now on (after first non-comment) */ comment_postfix = NULL; rec_id = buildstring ("\n Opt file record = ",file->buf,NULL, " creating rec_id string"); next_entry_ptr = file->buf; /* Loop per entry (pair) in record */ /* Allow item alt separators to protect entry separators */ /* that happen to occur within items */ while ( (entry_ptr = nxttok (next_entry_ptr, file->entry_separators, &next_entry_ptr, file->item_alt_separators, FALSE, FALSE) ) != NULL ) { /* Ignore empty pairs. Should take care of empty lines, too */ if ( (tok = nxttok(entry_ptr, file->item_separators, &next_item_ptr, NULL, FALSE, FALSE) ) == NULL ) break; /* Got first of pair (typically a variable name or keyword) */ if (file->pre_trans) { if (transvar(varname,MAXVARNAMESIZE,tok) == -1) { msg = buildstring ("Translated variable name too long\n Name (pre-trans) = ", tok, rec_id, "creating err msg 2"); err (msg,opt_file_id); } else tok=varname; } if (lookup_wjstbl(tok,output_list) != NULL) { msg =buildstring ("Entry appears twice in list\n Entry = ",tok,rec_id, " creating err msg 3"); err (msg,opt_file_id); } if (output_list_ptr + strlen(tok) + 2 > output_list + output_list_size) { msg = buildstring ("List overflow attempting to add item ",tok,rec_id, " creating err msg 4"); err (msg,opt_file_id); } if (strchr(tok,WJSTBL_SEPARATOR) != NULL) { msg = buildstring("Table separator appears in item ",tok,rec_id, " creating err msg 5"); err (msg,opt_file_id); } *output_list_ptr++ = WJSTBL_SEPARATOR; this_var = output_list_ptr; while (*tok != '\0') *output_list_ptr++ = *tok++; /* Get its associated string. Might be enclosed in */ /* apostrophes (depending on which opt file this is) */ if ( (tok = nxttok (next_item_ptr, file->item_separators, &next_item_ptr, file->item_alt_separators, FALSE, FALSE) ) == NULL ) { /* Error if no associated string */ msg = buildstring("Incomplete entry\n Variable/keyword = ", this_var, rec_id, " creating err msg 6"); err (msg,opt_file_id); } if (nxttok (next_item_ptr,file->item_separators,NULL,NULL,FALSE,FALSE) != NULL) { msg = buildstring("Third item in entry ",tok,rec_id, " creating err msg 7"); err (msg,opt_file_id); } if (output_list_ptr + strlen(tok) + 2 > output_list + output_list_size) { msg = buildstring ("List overflow attempting to add variable/keyword ", this_var,rec_id, " creating err msg 8"); err (msg,opt_file_id); } if (strchr(tok,WJSTBL_SEPARATOR) != NULL) { msg = buildstring("Table separator appears in item ",tok,rec_id, " creating err msg 9"); err (msg,opt_file_id); } *output_list_ptr++ = WJSTBL_SEPARATOR; while (*tok != '\0') *output_list_ptr++ = *tok++; *output_list_ptr++ = WJSTBL_WHITE_SPACE; paircnt++; } } *output_list_ptr = '\0'; if (file->eof) { fclose(file->stream); file->open = FALSE; } free(opt_file_id); if (rec_id != NULL) free(rec_id); if (msg != NULL) free (msg); return paircnt; } Logical remove_vars (removals_file,ntotal,list_after_removals,ncnt) struct fileinfo *removals_file; int *ntotal,list_after_removals[],ncnt; /* Makes ntotal, the number of variables presented to outer, from */ /* ncnt, the number of variables in the level 0 file, and a list */ /* of variables to remove. Also provides the mapping array, */ /* list_after_removals to implement the removal. Note that */ /* outer may implement variable selection, further reducing the */ /* list of variables shipped out of the method */ /* Returns TRUE or FALSE depending on whether variable removals were */ /* spec'd */ /* List of variables to be removed comes in via removals optional */ /* file. Order of variables is irrelevant */ /* pointers array & its size, ncnt, are base data for this function. */ /* At this point, however, pointers[i]=i, so that simplifies things*/ /* Idea is to prepare list_after_removals, a version of pointers */ /* w/o the indices of the variables in the removals list */ /* list_after_removals starts as a copy of pointers. To remove a */ /* variable, find its position in the master variable list, which */ /* is its position in pointers. Mark this position in */ /* list_after_removals. After all are marked, compress out the */ /* marked elements */ /* Below the index of a removed variable, the list_after_removals */ /* list is the same as the pointers list. At and above, */ /* list_after_removals points to the next higher pointers element */ /* *ntotal is the size of list_after_removals and (presumably) the */ /* number of variables returned to outer */ /* 25 Mar 96. Check that we have vars before & after removals */ { int i; char varname[MAXVARNAMESIZE+1],*tok; /* Create list without any removed variables */ for (i=0; icomment_postfix) == 0) err ("EOF/error before removals file data records",""); /* At start of loop, we have record in buf. Thereafter, read */ /* until EOF */ while (TRUE) { tok=strtok(removals_file->buf,removals_file->item_separators); while (tok != NULL) { if (removals_file->pre_trans) { if (transvar(varname,MAXVARNAMESIZE,tok) == -1) err ("Translated variable name too long\n Name (pre-trans) = ",tok); tok=varname; } /* Find variable name */ i=lookup_lev0varlist(tok); if (i < 0) err ("Removal variable doesn't exist\n Name (after trans) = ",tok); /* Mark it as removed */ list_after_removals[i] = -1; tok=strtok(NULL,removals_file->item_separators); } if (getrec_proccomment(removals_file,NULL) == 0) break; } if (removals_file->eof) { fclose(removals_file->stream); removals_file->open=FALSE; } *ntotal=0; for (i=0; idescrip, " calculation too long\n Param = ", " get_var_from_keyword"), keyword); else if (i == 0) return -1; if (file_assoc_w_wjstbl->pre_trans) if (transvar(varname,MAXVARNAMESIZE,varname) == -1) err ( buildstring( "Translated variable in ", file_assoc_w_wjstbl->descrip, " calculation too long\n Truncated, post-trans variable = ", " get_var_from_keyword"), varname); i = lookup_lev0varlist(varname); if (i < 0) err ( buildstring( "Variable provided for ", file_assoc_w_wjstbl->descrip, " calculations not in dataset\n Variable = ", " get_var_from_keyword"), varname); return i; } Logical has_prefix (possible_prefix,prefix,rest_of_string) char *prefix,*possible_prefix,*rest_of_string; /* Check that prefix is the same as possible_prefix, and that next */ /* character is, in fact, beginning of rest_of_string */ /* (Note that test for "beginning of rest_of_string" takes care of */ /* situation where prefix is longer than possible_prefix and a match */ /* is made on a substring of prefix) */ { int len; len=strlen(prefix); return ( (strncmp(possible_prefix,prefix,len) == 0) && (possible_prefix+len == rest_of_string) ); } char *found_at_end(string,suffix) char *string,*suffix; /* Returns pointer to suffix if suffix is found at end of string; */ /* NULL else. Note that this is NOT logically the same as has_prefix, */ /* which checks for overlapping strings. This routine doesn't. */ { char *ptr; if ( (ptr = strstr(string,suffix)) != NULL ) if ( ptr+strlen(ptr) == string+strlen(string) ) return ptr; return NULL; } int *select_lev(keyword,output_info) char *keyword; struct outinfo *output_info; { char *ptr; int *retval; /* Logically a loop over all possible levels. However, this */ /* requires some way to associate keywords with the pointers to */ /* the levels, as well as a way to tie the "addl-" prefix to */ /* "the dup_ version of a particular level pointer" */ if ( (ptr = found_at_end(keyword,"trace")) != NULL ) if (ptr == keyword) retval = &output_info->trace_level; else if (has_prefix(keyword,"addl-",ptr)) retval = &output_info->dup_trace_level; else if ( (ptr = found_at_end(keyword,"error")) != NULL ) if (ptr == keyword) retval = &output_info->error_level; else if (has_prefix(keyword,"addl-",ptr)) retval = &output_info->dup_error_level; else retval = NULL; return retval; } int *select_debug1_type(keyword,output_info) char *keyword; struct outinfo *output_info; { char *ptr; int *retval; /* See select_lev, if overwhelming similarity hasn't hit already */ /* Slight difference because the "print_" key is at beginning in- */ /* stead of end, and prefix will precede the print_, not the ioval */ if ( (ptr = found_at_end(keyword,"print_iovalstr")) != NULL ) if (ptr == keyword) retval = &output_info->iovalstr; else if (has_prefix(keyword,"addl-",ptr)) retval = &output_info->dup_iovalstr; else if ( (ptr = found_at_end(keyword,"print_iovalreal")) != NULL ) if (ptr == keyword) retval = &output_info->iovalreal; else if (has_prefix(keyword,"addl-",ptr)) retval = &output_info->dup_iovalreal; else retval = NULL; return retval; } Logical *select_logical_type(keyword,output_info) char *keyword; struct outinfo *output_info; { char *ptr; Logical *retval; if (strcmp(keyword,"inserted_msgs") == 0) retval = &output_info->inserted_comments; else if (strcmp(keyword,"inserted_msg_tag") == 0) retval = &output_info->inserted_comment_id; else if (strcmp(keyword,"comment_source_tag") == 0) retval = &output_info->opt_file_comment_id; else retval = NULL; return retval; } Logical configure_output(diag_optfile,output_info,files) struct fileinfo *diag_optfile,files[]; struct outinfo *output_info; /* Returns TRUE or FALSE depending on whether output info was provided */ /* */ { int i,j,max; int *intptr; Logical *logptr; char *ptr,*ptr2; char *keyword,*keyval,*old_sink_name,*mode; char diags_wjstbl[DIAGS_WJSTBL+1]; char time_buf[1+2+3+2+1+2+2+1+2]; /* See strftime call */ char wjstbl_chars[2+1]; /* Whitespace char; separator char */ /* See wjstbl doc */ struct outfile *errout; time_t now; if (create_wjstbl(diag_optfile,diags_wjstbl,DIAGS_WJSTBL) == 0) return FALSE; wjstbl_chars[0]=diags_wjstbl[strlen(diags_wjstbl)-1]; wjstbl_chars[1]=diags_wjstbl[0]; wjstbl_chars[2]='\0'; now = time(NULL); strftime(time_buf,strlen(time_buf),"_%y%b%d-%H%M-%S",localtime(&now)); keyword = strtok(diags_wjstbl,wjstbl_chars); while (keyword != NULL) { if (keyword == NULL) err ("Bad internal table format",""); keyval = strtok(NULL,wjstbl_chars); if (keyval == NULL) err ("Bad internal table format",""); /* Scheme: since we do the various types w/ an if/else-if */ /* structure, order is slightly significant (a keyword could */ /* conceivably fit more than one pattern). Do "independent" */ /* keywords first-those considered in their entirety-no prefixes */ /* suffixes, etc. Then do keywords that have just prefixes and/ */ /* or suffixes. Then do keywords with "embedded" key strings. */ /* Right now, we use the strings _sink, _level & _lines as */ /* significant suffixes. Almost all keywords take the addl- */ /* prefix. This makes the print_ prefix "embedded", as well as */ /* the new- and unique- prefixes. (Prefixes ending in - are */ /* user-supplied; strings involving _ are parts of keywords that */ /* we use to group types of parameters) */ /* Sigh. */ if ( (logptr = select_logical_type(keyword,output_info)) != NULL ) { /* Process a logical - happens to be only "simple" keyword */ *logptr = get_logical_from_string(keyval," diag optfile keyword ",keyword); } else if ( (ptr = found_at_end(keyword,"_level")) != NULL ) { /* Process a level */ *ptr = '\0'; /* Lop off _level */ if ( (intptr = select_lev(keyword,output_info)) == NULL ) { *ptr = '_'; /* Restore _level */ err ("Invalid diag optfile _level keyword ",keyword); } *intptr = get_int_from_string(keyval,0,99,wjstbl_chars[0], " diag optfile _level keyword"); } else if ( (ptr = found_at_end(keyword,"maxscriptdiags")) != NULL ) { /* Process maxscriptdiags */ if (ptr = keyword) intptr = &output_info->maxscriptdiags; else if (has_prefix(keyword,"addl-",ptr)) intptr = &output_info->dup_maxscriptdiags; else err ("Invalid prefix in diag optfile keyword ",keyword); *intptr = get_int_from_string(keyval,0,LONG_MAX,wjstbl_chars[0], " diag optfile maxscriptdiags keyword"); } else if ( (ptr = found_at_end(keyword,"_sink")) != NULL ) { /* Process a sink */ *ptr = '\0'; /* Lop off _sink */ for (i=0; ifile[i]; if ( (ptr = found_at_end(keyword,errout->descrip)) != NULL ) { /* Found a legit sinkname keyword */ /* Put _sink back on for possible use in error msgs */ *(ptr+strlen(ptr)) = '_'; if (errout->processed) err ("Diag sink specified more than once. Sink = ",keyword); errout->processed = TRUE; /* Test for addl- prefix */ *ptr = '\0'; /* Only look at prefixes */ /* Test for addl- prefix and logically make it part of */ /* keyword body (instead of prefix) if found */ if ( (ptr2 = found_at_end(keyword,"addl-")) != NULL ) { errout->dup = TRUE; ptr = ptr2; } /* Get output mode and save sink name, "uniquing" if needed */ old_sink_name = errout->sink; if (keyword == ptr) { mode = "a"; strdupl(&(errout->sink),keyval, " saving output sink name"); } else /* Still more text in keyword */ if (has_prefix(keyword,"new-",ptr)) { mode = "w"; strdupl(&(errout->sink),keyval, " saving output sink name"); } else if (has_prefix(keyword,"unique-",ptr)) { mode = "a"; errout->sink = buildstring(keyval,time_buf,NULL, " building unique sink name"); } else { /* Restore keyword for message */ if (ptr2 != NULL) ptr += strlen("addl-"); *ptr = *(errout->descrip); err ("Invalid prefix in diag optfile keyword ",keyword); } /* Handle situation where a) compile-time sink different */ /* from runtime sink & b) compile-time sink open. */ if ( *(errout->open_ptr) && (strcmp(errout->sink,old_sink_name) != 0) ) { fclose (*errout->stream_ptr); *(errout->open_ptr) = FALSE; *(errout->mode_ptr) = NULL; errout->mode_ptr = output_info->file[i].mode_ptr; } /* See if this sink matches any others. If so, */ /* make its stream and open pointers point to those of */ /* that earlier one. This lets us send multiple streams */ /* to same sink w/o worrying about file sharing */ /* (multiple opens of same file, etc) */ for (j=0; jsink,output_info->file[j].sink) == 0) { errout->stream_ptr = output_info->file[j].stream_ptr; errout->open_ptr = output_info->file[j].open_ptr; errout->mode_ptr = output_info->file[j].mode_ptr; } /* Save mode, checking for consistency */ if ( *(errout->mode_ptr) == NULL) { strdupl(&output_info->outsink_modes[i],mode); errout->mode_ptr = &output_info->outsink_modes[i]; } else if (strcmp(*(errout->mode_ptr),mode) != 0) err ("> 1 sink to one file, but different modes spec'd\n File = ", errout->sink); break; } } } else if ( (ptr = strstr(keyword,"print_")) != NULL ) { /* Process trace request. Should be last to protect against */ /* an embedded print_ string not being a keyword */ /* 2 flavors; optfilename_lines and debug1-compatible */ if ( (ptr2 = found_at_end(ptr,"_lines")) == NULL ) { /* Type added to be compatible w/old DEBUG1 switch */ if ( (intptr = select_debug1_type(keyword,output_info)) == NULL ) err ("Invalid diag optfile print_ keyword ",keyword); } else { /* optfilename_lines type */ *ptr2 = '\0'; /* Lop off _lines */ for (i=0; ifile[i]; if (errout->sink == NULL) strdupl(&(errout->sink),NULL_SINK," creating null output sink name"); if ( *(errout->sink) == '\0' ) strdupl(&(errout->sink),NULL_SINK," creating null output sink name"); } return TRUE; } Logical get_datafield_options(datafield_optfile,trim,consec_seps,embed_seps, sepstring_ptr, alt_sepstring) Logical *consec_seps,*embed_seps,*trim; char **sepstring_ptr,*alt_sepstring; struct fileinfo *datafield_optfile; /* Get several switches from datafieldopts optional file */ { char *keyword,*keyval,*end_keyval,*ptr; char datafield_wjstbl[DATAFIELDOPTS_WJSTBL+1]; char wjstbl_white_space; int nitems_supplied; int nitems_processed = 0; nitems_supplied = create_wjstbl(datafield_optfile,datafield_wjstbl,DIAGS_WJSTBL); if (nitems_supplied == 0) return FALSE; wjstbl_white_space = datafield_wjstbl[strlen(datafield_wjstbl)-1]; keyword = "significant_consecutive_separators_in_free_field"; if ( (keyval = lookup_wjstbl(keyword,datafield_wjstbl)) != NULL ) { end_keyval = strchr(keyval,wjstbl_white_space); *end_keyval = '\0'; *consec_seps = get_logical_from_string (keyval," datafieldopts optfile keyword ",keyword); *end_keyval = wjstbl_white_space; nitems_processed++; } keyword = "significant_embedded_separators_in_fixed_field"; if ( (keyval = lookup_wjstbl(keyword,datafield_wjstbl)) != NULL ) { end_keyval = strchr(keyval,wjstbl_white_space); *end_keyval = '\0'; *embed_seps = get_logical_from_string (keyval," datafieldopts optfile keyword ",keyword); *end_keyval = wjstbl_white_space; nitems_processed++; } keyword = "separators"; if ( (keyval = lookup_wjstbl(keyword,datafield_wjstbl)) != NULL ) { end_keyval = strchr(keyval,wjstbl_white_space); *end_keyval = '\0'; *sepstring_ptr = get_string_from_escaped_string (keyval," datafieldopts optfile keyword ",keyword); *end_keyval = wjstbl_white_space; nitems_processed++; } keyword = "alt_separator_in_free_field"; if ( (keyval = lookup_wjstbl(keyword,datafield_wjstbl)) != NULL ) { end_keyval = strchr(keyval,wjstbl_white_space); *end_keyval = '\0'; ptr = get_string_from_escaped_string (keyval," datafieldopts optfile keyword ",keyword); *end_keyval = wjstbl_white_space; nitems_processed++; if (strlen(ptr) < 3) strcpy(alt_sepstring,ptr); else err ("alt_separator string too long. String = ",ptr); if (alt_sepstring[1] == '\0') alt_sepstring[1] = alt_sepstring[0]; alt_sepstring[2] = '\0'; } /****** NO data_field_trim FOR NOW keyword = "data_field_trim"; if ( (keyval = lookup_wjstbl(keyword,datafield_wjstbl)) != NULL ) { end_keyval = strchr(keyval,wjstbl_white_space); *end_keyval = '\0'; *trim = get_logical_from_string (keyval," datafieldopts optfile keyword ",keyword); *end_keyval = wjstbl_white_space; nitems_processed++; } ******/ if (nitems_supplied > nitems_processed) errn ("Unprocessed datafieldopts parameters. Number skipped = ", nitems_supplied-nitems_processed); ptr = REQUIRED_SEPARATORS; while (*ptr != '\0') if (strchr(*sepstring_ptr,*(ptr++)) == NULL) err ("Separator string missing one or more required separators\n", "Characters not printed here since they may be unprintable-sorry"); if ( (strpbrk(alt_sepstring,*sepstring_ptr) != NULL) || (strpbrk(alt_sepstring,EXEC_DELIM) != NULL) || (strpbrk(alt_sepstring,OBJECT_DELIM) != NULL) ) err("Alt_separators character may not be separator character\n", "Characters not printed here since they may be unprintable-sorry"); return TRUE; } Logical get_latlonformat(buffer,key,wjstbl) char *key,*buffer,*wjstbl; { char *ptr; char *legal_format_wjstbl; /* Lazy way to search list of legal strings. q's are irrelevant */ legal_format_wjstbl = "/decdeg/q /degdecmin/q "; if ( (ptr = lookup_wjstbl(key,wjstbl)) == NULL ) return FALSE; else { /* Assume *ptr is not white space-else some wjstbl error */ if ( strchr(ptr,WJSTBL_WHITE_SPACE) > (ptr + MAXLATLONFORMATSIZE) ) err ("Lat/lon format string too long. String = ",ptr); sscanf(ptr,"%s",buffer); if (lookup_wjstbl(buffer,legal_format_wjstbl) == NULL) err ("Illegal lat/lon format. Format = ",ptr); } return TRUE; } Logical get_latlonparams(latlon_optfile,outlat,outlon,inlat,inlon) struct fileinfo *latlon_optfile; struct latlonformat *outlat,*outlon,*inlat,*inlon; /* Returns TRUE or FALSE depending on whether latlon params were spec'd */ /* */ /* defgb will do 2 different types of work on latitudes and longitudes */ /* To do anything, it needs an inlatname (or inlonname) variable name */ /* from the latlonparams optional input file. It will put its output */ /* in the same variable unless the corresponding out*name is spec'd */ /* See timedateparams notes for variable name "rules" */ /* One calculation is field negation, and the other is format */ /* To support the work, get_latlonparams is responsible for getting */ /* the format, which-direction-positive convention, and variable names */ /* for input and output latitude and longitude from the optional input */ /* file. If input and output formats and/or conventions differ, */ /* ioreadrec will call routines to do the work. */ { char latlon_wjstbl[LATLON_WJSTBL+1]; char *ptr; int nitems_supplied; int nitems_processed = 0; nitems_supplied = create_wjstbl(latlon_optfile,latlon_wjstbl,LATLON_WJSTBL); if (nitems_supplied == 0) return FALSE; inlat->var = get_var_from_keyword("inlatname",latlon_wjstbl,latlon_optfile); outlat->var = get_var_from_keyword("outlatname",latlon_wjstbl,latlon_optfile); inlon->var = get_var_from_keyword("inlonname",latlon_wjstbl,latlon_optfile); outlon->var = get_var_from_keyword("outlonname",latlon_wjstbl,latlon_optfile); if ( (inlat->var < 0 && outlat->var >= 0) || (inlon->var < 0 && outlon->var >= 0) ) err ("Output lat/lon variable name provided without corresponding input", " name"); if (inlat->var >= 0) { nitems_processed++; if (outlat->var < 0) outlat->var = inlat->var; else nitems_processed++; } if (inlon->var >= 0) { nitems_processed++; if (outlon->var < 0) outlon->var = inlon->var; else nitems_processed++; } if ( (ptr = lookup_wjstbl("inlatconvention",latlon_wjstbl)) != NULL ) { if (strchr("NnSs",*ptr) == NULL) err ("Input latitude convention not N or S. Convention = ",ptr); nitems_processed++; inlat->convention = *ptr; } if ( (ptr = lookup_wjstbl("outlatconvention",latlon_wjstbl)) != NULL ) { if (strchr("NnSs",*ptr) == NULL) err ("Output latitude convention not N or S. Convention = ",ptr); nitems_processed++; outlat->convention = *ptr; } if ( (ptr = lookup_wjstbl("inlonconvention",latlon_wjstbl)) != NULL ) { if (strchr("WwEe",*ptr) == NULL) err ("Input longitude convention not E or W. Convention = ",ptr); nitems_processed++; inlon->convention = *ptr; } if ( (ptr = lookup_wjstbl("outlonconvention",latlon_wjstbl)) != NULL ) { if (strchr("WwEe",*ptr) == NULL) err ("Output longitude convention not E or W. Convention = ",ptr); nitems_processed++; outlon->convention = *ptr; } if (get_latlonformat(inlat->format,"inlatformat",latlon_wjstbl)) nitems_processed++; if (get_latlonformat(outlat->format,"outlatformat",latlon_wjstbl)) nitems_processed++; if (get_latlonformat(inlon->format,"inlonformat",latlon_wjstbl)) nitems_processed++; if (get_latlonformat(outlon->format,"outlonformat",latlon_wjstbl)) nitems_processed++; if (nitems_supplied > nitems_processed) errn ("Unprocessed latlon parameters. Number skipped = ", nitems_supplied-nitems_processed); if ( ( (strcmp(inlat->format,outlat->format) != 0) || (inlat->convention != outlat->convention) ) && (inlat->var < 0) ) err ("Latitude work requested ", "but latitude input variable name not supplied"); if ( ( (strcmp(inlon->format,outlon->format) != 0) || (inlon->convention != outlon->convention) ) && (inlon->var < 0) ) err ("Longitude work requested ", "but longitude input variable name not supplied"); return TRUE; } char *valid_time_conv(ptr) char *ptr; { if ( (strncmp(ptr,"gmt",3) == 0) || (strncmp(ptr,"GMT",3) == 0) || (strncmp(ptr,"utc",3) == 0) || (strncmp(ptr,"UTC",3) == 0) ) return "utc"; if ( (strncmp(ptr,"LOCAL",5) == 0) || (strncmp(ptr,"Local",5) == 0) || (strncmp(ptr,"local",5) == 0) ) return "local"; return NULL; } void affects_frag(out_struct,inlev,field_type,informat_index) int inlev,informat_index; char *field_type; struct outtime *out_struct; /* A piece of an input variable will affect the output time/date */ /* fragment sent to this func. Check */ /* that it hasn't already being supplied from an earlier input */ /* format field; then that the input data "arrives with or before" */ /* it. Then, if OK, link that particular output fragment to the index */ /* the input structure that will provide its information */ /* Input for unrequested output not an error in anticipation of full */ /* input description followed by later decision to omit some output. */ { char *errtmp; /* Save lots of info even if output variable was not wanted */ /* Allows us to use input to affect "other" output; eg, yr/mo/da */ /* calcs must be done in Julian case even if only time is wanted */ /* on output. Similarly, "small" time pieces can affect rounding */ /* of large ones even if small ones aren't output */ if (*out_struct->source_field_type == '\0') *out_struct->source_field_type = *field_type; else { errtmp = buildstring (field_type, " and ", out_struct->source_field_type, " building time/date error string"); err ("Duplicate time/date input fields, types b and ",errtmp); } if (out_struct->lev < inlev) { errtmp = buildstring ("Output time/date ", out_struct->var_type, "\n variable occurs before its input variable ", " building time/date error string"); errn (buildstring(errtmp, TIMEDATE_INPUT_KEYWORD_PREFIX, NULL, " building time/date error string"), informat_index+1); } out_struct->intime_index = informat_index; /* Save level if variable wasn't asked for. Level set */ /* elsewhere for wanted variables. */ if (out_struct->var < 0) out_struct->lev = inlev; return; } Logical get_timedateparams(timedate_optfile,out_td,in_td,fragbuflist) struct fileinfo *timedate_optfile; struct outtime out_td[]; struct intime in_td[]; char *fragbuflist[]; /* Returns TRUE or FALSE depending on whether time/date params were */ /* spec'd */ /* General idea is that there are fragments of time/date info */ /* scattered among the input variables and we want to reformat and/or */ /* reassemble some of them into standard formats. */ /* We will identify all the fragments supplied and tie them to */ /* output structures as well as the input variables they come from. */ /* At the level an input variable is read, it is "mined" for its frag- */ /* ments and those fragments are reformatted if necessary. The */ /* fragments are stored in the output time structures they belong to. */ /* At the level the output value is to appear, it is assembled from */ /* the saved fragments. */ /* This routine handles the creation of the links between input */ /* and output structures (and the necessary validity checks, etc) */ /* There are logically 10 fragments-year, month, day, hour, am/pm */ /* flag, min, sec, fraction of a second, time zone and daylight */ /* savings time flag. (Other input fragments decompose into these; eg, */ /* fraction of a minute is seconds and fractions of seconds). These */ /* could all appear in 1 input variable, or each could appear in its */ /* own, or any combination. The input structures consist of a 10 */ /* element structure, each element corresponding to 1 input template, */ /* and a 10 element vector of pointers to buffers to contain decoded */ /* reformatted fragments. There is one output structure per fragment */ /* containing, among other things, the buffers mentioned. There is */ /* logically one structure per output agglomeration, too, for those */ /* output variables that consist of more than one fragment. */ /* Exactly one output structure contains a pointer to the template it */ /* comes from. The template structure, however, contains pointers to */ /* each fragment it will supply. The idea is to decode only once, no */ /* matter how many fragments there are, and no matter if fragments are */ /* to be used at lower levels. It works as long as at least one frag- */ /* ment is used at the same level as the input variable. */ /* Note about adding new template types: */ /* K&R defines "ANSI standard" stuff for strftime. The "world" has */ /* added more. As of 25 Jul 96, I found use of a, A, b, B, c, C, d, */ /* D, e, h, H, I, j, J, k, l, m, M, n, p, r, S, t, T, U, w, W, x, X, */ /* y, Y, & Z. Not all are formats, but all are used. I've added */ /* f, F, N, o, O, q, Q, R, v, V & z. Good luck! */ { char timedate_wjstbl[TIMEDATE_WJSTBL+1]; int nitems_supplied,ntemplates,lenprefix,len_inpfield; int nitems_processed = 0; int fragbuflist_index = 0; int i,j; char *ptr,*ptr2,*tmp,*fmt_ptr,*fmt_type,*keyword; char template_type[1+1] = {'\0', '\0'}; struct intime *in_ptr; div_t digs; /* Logically, these are set if needed. However, it's convenient */ /* to use them whether needed or not. Any valid fragment ID */ /* (including 0) should do here, but chose these "relevant" ones.. */ int calculate_julian = MONTH_FRAG; int calculate_hhmm = HOUR_FRAG; nitems_supplied = create_wjstbl(timedate_optfile,timedate_wjstbl, TIMEDATE_WJSTBL); if (nitems_supplied == 0) return FALSE; if (isalnum(TIMEDATE_SKIPFIELD_CHAR)) { /* Use template_type as buffer for error message */ *template_type = TIMEDATE_SKIPFIELD_CHAR; err ("Bad time/date skipfield character. Char is ",template_type); } /* Set up UTC<->local conversion option */ if ( (ptr = lookup_wjstbl("intimeconvention",timedate_wjstbl)) == NULL ) ptr = "local"; else if ( (tmp = valid_time_conv(ptr)) == NULL ) err ("Input time convention not UTC, GMT, or local. Convention = ",ptr); else { ptr = tmp; nitems_processed++; } if ( (ptr2 = lookup_wjstbl("outtimeconvention",timedate_wjstbl)) == NULL ) ptr2 = "local"; else if ( (tmp = valid_time_conv(ptr2)) == NULL ) err ("Output time convention not UTC, GMT, or local. Convention = ",ptr2); else { ptr2 = tmp; nitems_processed++; } /* conv = 0 means no conversion; 1, local->UTC; -1, UTC-> local */ if (strcmp(ptr,ptr2) == 0) out_td[HOUR_FRAG].conv = 0; else out_td[HOUR_FRAG].conv = (*ptr == 'l') ? 1 : -1; /* keyword is prefix + # (assumed <= 99) + longer of postfixes */ /* ("name" & "template") */ lenprefix = strlen(TIMEDATE_INPUT_KEYWORD_PREFIX); keyword = buildstring(TIMEDATE_INPUT_KEYWORD_PREFIX, "99template", NULL, " building time/date keyword"); /* Get any output variables. */ if ( (out_td[YEAR_FRAG].var = get_var_from_keyword("outyearname",timedate_wjstbl,timedate_optfile)) >= 0 ) out_td[YEAR_FRAG].lev = iovarlevel__(out_td[YEAR_FRAG].var); if ( (out_td[MONTH_FRAG].var = get_var_from_keyword("outmonthname",timedate_wjstbl,timedate_optfile)) >= 0 ) out_td[MONTH_FRAG].lev = iovarlevel__(out_td[MONTH_FRAG].var); if ( (out_td[DAY_FRAG].var = get_var_from_keyword("outdayname",timedate_wjstbl,timedate_optfile)) >= 0 ) out_td[DAY_FRAG].lev = iovarlevel__(out_td[DAY_FRAG].var); /* Use HOUR to indicate that output time has been requested, since */ /* can't have output time without it. Asking for time is equiv to */ /* asking for all its fragments. Better: any specified input time */ /* fragments "belong" to same output variable. In any case, do */ /* not need variables or levels for MINUTE_FRAG or other possible */ /* components of time. However, for convenience of checking, set */ /* up the components */ i = get_var_from_keyword("outtimename",timedate_wjstbl,timedate_optfile); out_td[HOUR_FRAG].var = i; out_td[MINUTE_FRAG].var = i; out_td[SECOND_FRAG].var = i; out_td[FRACTION_FRAG].var = i; if (i >= 0) { i = iovarlevel__(i); out_td[HOUR_FRAG].lev = i; out_td[MINUTE_FRAG].lev = i; out_td[SECOND_FRAG].lev = i; out_td[FRACTION_FRAG].lev = i; } /* Similarly, time offset is required if output time displacement */ /* is to be calculated and do not need vars/levels for other */ /* offset frags */ i = get_var_from_keyword("outdisplacementname",timedate_wjstbl,timedate_optfile); out_td[TIME_OFFSET_FRAG].var = i; out_td[DST_FRAG].var = out_td[TIME_OFFSET_FRAG].var; if (i >= 0) { i = iovarlevel__(i); out_td[TIME_OFFSET_FRAG].lev = i; out_td[DST_FRAG].lev = i; } /* Input variables, templates, etc */ for (ntemplates=0; ntemplatesvar = get_var_from_keyword(keyword,timedate_wjstbl,timedate_optfile)) < 0 ) break; /* ntemplates starts from 0; user keywords start from 1 */ (ntemplates < 9) ? (i = 1) : (i = 2); strcpy(keyword+lenprefix+i,"template"); if ( (ptr = lookup_wjstbl(keyword,timedate_wjstbl)) == NULL ) errn (buildstring("No template for time/date input variable ", TIMEDATE_INPUT_KEYWORD_PREFIX, NULL, " building calc_timedate err msg"), ntemplates+1); in_ptr->nfrags = 0; in_ptr->fragbuf_index = fragbuflist_index; i = iovarlevel__(in_ptr->var); fmt_ptr = in_ptr->format; while (*ptr != WJSTBL_WHITE_SPACE) { if ( ispunct(*ptr) && (*ptr != TIMEDATE_SKIPFIELD_CHAR) ) *(fmt_ptr++) = *(ptr++); else { *template_type = *ptr; len_inpfield=strspn(ptr,template_type); /* Default to numeric input. Be sure that integer data uses */ /* this format since code does not check when decoding. */ fmt_type = "[0-9]"; /* Check all output fragments affected by this field. */ switch (*ptr) { case 'b': case 'B': /* Month name (abbr or full) */ fmt_type = "[A-Z,a-z]"; case 'm': /* Month number */ affects_frag(&out_td[MONTH_FRAG],i,template_type,ntemplates); fragbuflist[fragbuflist_index++] = out_td[MONTH_FRAG].fragbuf; break; case 'd': /* Day of month */ affects_frag(&out_td[DAY_FRAG],i,template_type,ntemplates); fragbuflist[fragbuflist_index++] = out_td[DAY_FRAG].fragbuf; break; case 'F': /* DST flag */ fmt_type = "[A-Z,a-z,0-9]"; case 'f': /* DST offset */ affects_frag(&out_td[DST_FRAG],i,template_type,ntemplates); fragbuflist[fragbuflist_index++] = out_td[DST_FRAG].fragbuf; break; case 'H': case 'I': /* Hour (24 or 12 hour clock) */ affects_frag(&out_td[HOUR_FRAG],i,template_type,ntemplates); fragbuflist[fragbuflist_index++] = out_td[HOUR_FRAG].fragbuf; break; case 'j': case 'v': /* Julian w/leapyear (1 or 0 = 1 Jan) */ case 'J': case 'V': /* Julian wo/leapyear (1 or 0 = 1 Jan) */ affects_frag(&out_td[MONTH_FRAG],i,template_type,ntemplates); affects_frag(&out_td[DAY_FRAG],i,template_type,ntemplates); /* No point in doing Julian calculations once each for */ /* month & day. Set up so that "first" output (variable */ /* closer to level 0-month if a tie) does calculation. */ /* Eventually set other variable's template type to */ /* something which means "get your value from saved */ /* info " - see calc_timedateparams. Don't do it here */ /* or changed template type could appear in diagnostic */ /* and be non-meaningful */ /* Note that if output was not requested, .lev is large */ if (out_td[MONTH_FRAG].lev <= out_td[DAY_FRAG].lev) { fragbuflist[fragbuflist_index++] = out_td[MONTH_FRAG].fragbuf; calculate_julian = MONTH_FRAG; } else { fragbuflist[fragbuflist_index++] = out_td[DAY_FRAG].fragbuf; calculate_julian = DAY_FRAG; } break; case 'M': /* Minute of hour */ affects_frag(&out_td[MINUTE_FRAG],i,template_type,ntemplates); fragbuflist[fragbuflist_index++] = out_td[MINUTE_FRAG].fragbuf; break; case 'N': /* 24 hour time */ /* See Julian comments - same stuff applies */ affects_frag(&out_td[HOUR_FRAG],i,template_type,ntemplates); affects_frag(&out_td[MINUTE_FRAG],i,template_type,ntemplates); if (out_td[HOUR_FRAG].lev <= out_td[MINUTE_FRAG].lev) { fragbuflist[fragbuflist_index++] = out_td[HOUR_FRAG].fragbuf; calculate_hhmm = HOUR_FRAG; } else { fragbuflist[fragbuflist_index++] = out_td[MINUTE_FRAG].fragbuf; calculate_hhmm = MINUTE_FRAG; } break; case 'p': /* AM/PM flag */ fmt_type = "[A-Z,a-z]"; affects_frag(&out_td[AMPM_FRAG],i,template_type,ntemplates); fragbuflist[fragbuflist_index++] = out_td[AMPM_FRAG].fragbuf; break; case 'Z': /* time zone name */ fmt_type = "[A-Z,a-z,0-9,+,-]"; /* ? maybe GMT+7 is a name */ affects_frag(&out_td[TIME_OFFSET_FRAG],i,template_type,ntemplates); fragbuflist[fragbuflist_index++] = out_td[TIME_OFFSET_FRAG].fragbuf; break; case 'R': /* displ. from prime (hrs) */ case 'z': /* time zone -12 to 12; W pos. */ fmt_type = "[0-9,+,-]"; affects_frag(&out_td[TIME_OFFSET_FRAG],i,template_type,ntemplates); fragbuflist[fragbuflist_index++] = out_td[TIME_OFFSET_FRAG].fragbuf; break; case 'S': /* Second of minute */ affects_frag(&out_td[SECOND_FRAG],i,template_type,ntemplates); fragbuflist[fragbuflist_index++] = out_td[SECOND_FRAG].fragbuf; break; case 'y': case 'Y': /* Year (without or with century) */ affects_frag(&out_td[YEAR_FRAG],i,template_type,ntemplates); fragbuflist[fragbuflist_index++] = out_td[YEAR_FRAG].fragbuf; break; /* Each fraction affects the fraction fragment, and all */ /* time fragments shorter than it is. For example, an */ /* hour fraction affects minutes and seconds (but not */ /* hours, which has its own template specifier). */ /* Therefore, there are no break statements in the next */ /* 3 cases and the order of the cases is significant */ /* All values will be extracted from FRACTION buffer */ case 'o': /* Fraction of day */ affects_frag(&out_td[HOUR_FRAG],i,template_type,ntemplates); case 'O': /* Fraction of hour */ affects_frag(&out_td[MINUTE_FRAG],i,template_type,ntemplates); case 'q': /* Fraction of minute */ affects_frag(&out_td[SECOND_FRAG],i,template_type,ntemplates); case 'Q': /* Fraction of second */ affects_frag(&out_td[FRACTION_FRAG],i,template_type,ntemplates); fragbuflist[fragbuflist_index++] = out_td[FRACTION_FRAG].fragbuf; break; case TIMEDATE_SKIPFIELD_CHAR: /* No affected frags, */ fmt_type = "s"; /* but must */ break; /* accept character(s) */ default: err ("Unrecognized time/date template specifier ",template_type); break; } /* Make %nns format string from length of field. Equiv of */ /* sprintf(fmt_ptr,"%%ds",len_inpfield) [I hope], assuming */ /* field widths of < 100 characters (and assuming character */ /* representation of digit n+1 is one greater than represen- */ /* tation of digit n). Did it this way... for fun, I guess */ /* Instead of s, use an set of legal characters appropriate */ /* appropriate to the template type. */ *(fmt_ptr++) = '%'; /* If field is to be skipped, indicate in format. Else */ /* increment # fragments this template */ if (*template_type == TIMEDATE_SKIPFIELD_CHAR) *(fmt_ptr++) = SSCANF_SKIPFIELD_CHAR; else in_ptr->nfrags++; if (len_inpfield < 10) *(fmt_ptr++) = '0' + len_inpfield; else { digs = div(len_inpfield,10); *(fmt_ptr++) = '0' + digs.quot; *(fmt_ptr++) = '0' + digs.rem; } strcpy(fmt_ptr,fmt_type); fmt_ptr += strlen(fmt_type); ptr += len_inpfield; } } if (in_ptr->fragbuf_index == fragbuflist_index) err (keyword," time/date keyword did not specify any input fields"); /* Finally, add a check on the real data by trying to get one */ /* more character than format said is there. If successful, */ /* we'll know there is a data irregularity */ strcat(fmt_ptr,"%1s"); } nitems_processed += 2 * ntemplates; for (i=0; i= 0) { if (*out_td[i].source_field_type == '\0') err ("No input time/date variables provide info for output ", out_td[i].var_type); nitems_processed++; } if (nitems_supplied > nitems_processed) errn ("Unprocessed time/date parameters. Number skipped = ", nitems_supplied - nitems_processed); if (*out_td[HOUR_FRAG].source_field_type == 'I') if (*out_td[AMPM_FRAG].source_field_type == '\0') err ("Time/date I format requires AM/PM information",""); if (out_td[HOUR_FRAG].conv != 0) if ( (out_td[HOUR_FRAG].lev < out_td[TIME_OFFSET_FRAG].lev) || (out_td[MINUTE_FRAG].lev < out_td[TIME_OFFSET_FRAG].lev) ) err ("Time zone conversion requires time displacement input",""); /* Converting Julian day to day of month may require leap year info */ /* calculate_julian is whichever of MONTH_FRAG or DAY_FRAG */ /* that occurs "first" (or at all, if month or day output not */ /* requested). This is the output frag which determines when */ /* Julian input is used; hence year must be there "by that time" */ /* If things OK, change the "other" fragment's source type so */ /* that it will use saved date information */ switch (*out_td[calculate_julian].source_field_type) { case 'v': case 'j': if (out_td[calculate_julian].lev < out_td[YEAR_FRAG].lev) err ("Time/date Julian day input requires year information",""); case 'V': case 'J': *out_td[MONTH_FRAG + DAY_FRAG - calculate_julian].source_field_type = '*'; break; default: break; } /* See calculate_julian comments above */ if (*out_td[calculate_hhmm].source_field_type == 'N') *out_td[HOUR_FRAG + MINUTE_FRAG - calculate_hhmm].source_field_type = '*'; free (keyword); return TRUE; } void init_time_structs (in,out,buflist) struct intime in[]; struct outtime out[]; char *buflist[]; { int i; char *scratch; scratch = out[0].fragbuf; for (i=0;ioutsink_streams[i] = NULL; ptr->outsink_open_flags[i] = FALSE; ptr->outsink_modes[i] = NULL; ptr->file[i].stream_ptr = &(ptr->outsink_streams[i]); ptr->file[i].open_ptr = &(ptr->outsink_open_flags[i]); ptr->file[i].mode_ptr = &(ptr->outsink_modes[i]); ptr->file[i].processed = FALSE; ptr->file[i].dup = FALSE; } /* Descrips match opt file keywords less the _sink */ ptr->file[DATA_COMMENT_SINK].descrip = "data_comment"; ptr->file[NON_DATA_COMMENT_SINK].descrip = "other_comment"; ptr->file[ERROR_SINK].descrip = "error"; ptr->file[DIAG_SINK].descrip = "debug"; ptr->file[DATA_COMMENT_SINK].sink = ADD_TO_BUFFER_SINK; ptr->file[NON_DATA_COMMENT_SINK].sink = DEFAULT_OTHER_COMMENT_SINK; ptr->file[ERROR_SINK].sink = DEFAULT_ERROR_SINK; ptr->file[DIAG_SINK].sink = DEFAULT_DEBUG_SINK; ptr->inserted_comments = DEFAULT_INSERTED_MSGS; ptr->inserted_comment_id = DEFAULT_INSERTED_MSG_TAG; ptr->opt_file_comment_id = DEFAULT_COMMENT_SOURCE_TAG; ptr->trace_level = DEFAULT_TRACE_LEVEL; ptr->dup_trace_level = DEFAULT_TRACE_LEVEL; ptr->iovalstr = DEFAULT_DATA_TRACE; ptr->n_iovalstrs = 0; ptr->n_iovalreals = 0; ptr->error_level = DEFAULT_ERROR_LEVEL; ptr->dup_error_level = DEFAULT_ERROR_LEVEL; ptr->maxscriptdiags = DEFAULT_MAXSCRIPTDIAGS; ptr->dup_maxscriptdiags = DEFAULT_MAXSCRIPTDIAGS; return; } void init_file_structs (files,indirect_file) struct fileinfo files[],*indirect_file; /* Fill in those fields of the fileinfo structures relevant to the */ /* various types of files. Fill in some for logical purposes, even if */ /* they aren't used at present. What's the bet I miss lots? Well, */ /* that's why I put all this in one place... */ /* datafiles[*] all start out as a copy of files[DATAFILE], so they */ /* are not initialized here */ /* Field definitions are with structure defn in defgb.h */ { int i; if (strpbrk(DEFAULT_ITEM_SEPARATORS,DEFAULT_ENTRY_SEPARATORS) != NULL) err ("Compile-time entry separator appears in compile-time item ", "separator list"); indirect_file->source_type=DATA_FILE; indirect_file->descrip="indirect"; indirect_file->open=FALSE; indirect_file->eof=FALSE; indirect_file->eod=FALSE; indirect_file->reread=FALSE; indirect_file->scanheader=FALSE; indirect_file->buf=(char *)malloc(MAXREC+1); indirect_file->nrecs=0; indirect_file->comment_postfix= buildstring(" {",indirect_file->descrip,"}", " building postfix"); indirect_file->entry_separators=NULL; strdupl(&indirect_file->item_separators,DEFAULT_ITEM_SEPARATORS, " copying item separator list"); files[DATAFILE].descrip="datafile"; files[TRANSVAR].descrip="transvar"; files[INPWIDTHS].descrip="inpwidths"; files[REMOVALS].descrip="removals"; files[TIMEDATEPARAMS].descrip="timedateparams"; files[DIAGNOSTICS].descrip="diagnostics"; files[LATLONPARAMS].descrip="latlonparams"; files[DATAFIELDOPTS].descrip="datafieldopts"; files[DISPWIDTHS].descrip="dispwidths"; files[VARLIST].descrip="varlist"; for (i=0;i 0. */ /* If key specified, returns -1. If key_N specified, returns N if */ /* N > 0, errors if N <= 0. If neither key nor key_N specified, */ /* returns 0 */ /* Key is hardcoded into the routine. */ { #define OBJ_KEY ".objects_parameter" int n; char dummy[1+1]; if (strncmp(string,OBJ_KEY,strlen(OBJ_KEY)) == 0) /* Check for [_N] string. Using sscanf not only does */ /* decode for us, but also handles some whitespace issues */ switch (sscanf(string+strlen(OBJ_KEY),"%1s%d %1s",dummy,&n,dummy)) { case EOF: /* Get here if nothing but key was on line. */ return -1; case 2: /* Get here if there was exactly 1 char after key, and */ /* what followed that was an integer, and nothing */ /* followed that. Check that the 1 character was _ */ if (*dummy != '_') break; /* Check value. */ if (n <= 0) errn ("Requested .objects param must be positive\n Bad param = ",n); return n; default: /* 0 means the single char after key wasn't a valid 1 char */ /* string, & so should be impossible. 1 means that what */ /* followed the _ was not a legal integer. 3 means that */ /* something else followed a .objects_param_N string */ /* Given that we only got here if we had a .objects_param */ /* string, all of these are probably illegal, but we'll */ /* leave that to the rest of the code to detect */ break; } return 0; } void get_files (files,indirect_name,indirect_file, inner_param_list,len_inner_param_list) char *indirect_name,*inner_param_list[]; int len_inner_param_list; struct fileinfo files[],*indirect_file; /* get_files fills in the files array of file information from */ /* data found in file *indirect_name */ { char *source,*descrip,*prefix; char *msg,*end_token1,*seps; char *brack_method_name; int i,file_type,position,lentmp; seps = indirect_file->item_separators; if (strchr(seps,DIRSEP) != NULL) err ("Directory separator cannot be used as indirect file separator",""); indirect_file->stream=fopen(indirect_name,"r"); if (indirect_file->stream == NULL) err("Error opening indirect file ",indirect_name); indirect_file->open=TRUE; indirect_file->eod=FALSE; indirect_file->eof=FALSE; /* Following line assumes that indirect name won't change in */ /* calling program. Since we don't really care about this info at */ /* the moment (we're saving for completeness more than anything */ /* else), not making an effort to check, etc */ indirect_file->source=indirect_name; brack_method_name = buildstring(" {",METHOD_NAME,"}", " bracketing method name"); msg = buildstring("Indirect file: ",indirect_name,NULL," indirect file msg"); add_to_comment_stream (brack_method_name,msg); free (msg); free (brack_method_name); if (getrec_proccomment(indirect_file,indirect_file->comment_postfix) == 0) err ("EOF/error before indirect file data records",""); position = 0; while (TRUE) { descrip = indirect_file->buf + strspn(indirect_file->buf,seps); /* Skip lines consisting entirely of separators */ if (*descrip != '\0') { /* File logically consists of entries of the form */ /* source */ /* or */ /* descrip = source */ /* descrip is one of the .descrip strings, above, optionally */ /* preceded by a prefix. If descrip not present, it's */ /* calculated from position within file. There may be at */ /* most one entry (explicit or calculated) for a descrip */ /* source is either filespec, or text string (hopefully for- */ /* matted the way a record in filespec would be), or a */ /* pointer to a filespec or text string. If filespec, */ /* nothing else is allowed on line. Text string is, by */ /* defn, rest of line. Only pointer presently allowed is */ /* keyword of form .objects_parameter_N, which means that */ /* the filespec or text string comes from parameter N of */ /* the .objects file. If _N is not part of the string */ /* assume N is the position within this file (plus 1). */ /* Example: .objects_parameter on the datafile line means */ /* that the datafile will be found in .objects parameter 1 */ /* (0 is the datafile position within the indirect file, */ /* + 1). Not coincidentally at all, we happen */ /* to have a list of pointers to those parameters... */ /* The source could also be a command (possibly with params) */ /* which when executed produces data formatted the way a */ /* record in filespec would be. At present, only DATAFILE */ /* may have this kind of source, but logically... */ /* DATAFILEs may also be from JGOFS objects. */ /* Since command w/params may have embedded separators, */ /* treat commands like text strings. Not sure if JGOFS */ /* object spec's can have embedded separators, but assume */ /* yes, since that will work if not true, and we have to do */ /* the work anyway */ /* Get start of 2nd field w/o using strtok (which would */ /* destroy embedded separators, if present). This code */ /* should work even if no separators after 1st field, or */ /* nothing but separators after 1st field */ end_token1 = strpbrk(descrip,seps); source = end_token1 + strspn(end_token1,seps); /* Single entry if first thing we encounter is "special" */ /* string delimiter, or if 2nd field empty */ if ( (*descrip == SOURCE_IS_DATA) || (*descrip == *EXEC_DELIM) || (*descrip == *OBJECT_DELIM) || (*source == '\0') ) { /* No descrip field. Source is string; file type comes from */ /* where we are in file; vars in file are post-translation */ /* unless we are talking about the DATAFILE */ source=descrip; file_type=position; files[file_type].pre_trans = (file_type == DATAFILE); msg=buildstring(files[file_type].descrip," optional file",""); } else { /* Have both source and descrip. Terminate descrip, then */ /* process it to find file type and translation prefix */ *end_token1 = '\0'; prefix=descrip; /* Search descrip for any of the legal types */ for (file_type=0;file_type= NFILETYPES) err ("Unknown file descriptor in indirect file\n Descriptor = ", prefix); /* Handle translation prefix. */ if (prefix == descrip) /* No prefix. Everything but DATAFILE and VARLIST */ /* defaults to FALSE */ files[file_type].pre_trans = ( (file_type == DATAFILE) || (file_type == VARLIST) ); else { /* Prefix found. "pre-translation-" and */ /* "post-translation-" are the only legal ones. */ if (has_prefix(prefix,"pre-translation-",descrip)) files[file_type].pre_trans = TRUE; else if (has_prefix(prefix,"post-translation-",descrip)) files[file_type].pre_trans = FALSE; else err ("Illegal file descriptor prefix in indirect file\n \ Prefix precedes descriptor ",descrip); /* Makes no sense to spec trans opt for some files */ if ( ! files[file_type].accepts_trans_prefix ) err ("Cannot supply prefix for descriptor ",descrip); } msg=buildstring("file descriptor ",descrip,""); } /* We now know what kind of file is being described. Deal */ /* with its name (or save its data) */ i = objects_param_number(source); /* i = 0 means we're done. Otherwise, must try again */ /* i > 0 means that source pointed to a .objects file */ /* param. Try to fill files[file_type] from there */ if (i < 0) i = position+1; if (i > 0) { if (i >= len_inner_param_list) errn ("Requested .objects param beyond end of param list\n Bad param = ", i); if (inner_param_list[i]==NULL) errn ("Requested .objects param already processed\n Bad param = ",i); /* Process argument as source for file[file_type] */ source = inner_param_list[i]; if (objects_param_number(source) != 0) errn (".objects param may not request another .objects param\n \ Bad param = ",i); } if (*source == '\0') err ("0 length indirect file entry for ",msg); if (files[file_type].source != NULL) err ("2nd entry for optional file\n Attempted entry = ",source); /* Save source string and set up source type */ msg=buildstring(" saving ",files[file_type].descrip," source info",""); strdupl(&files[file_type].source,source,msg); free (msg); analy_source(&files[file_type],indirect_file->item_separators,NULL); /* If source came from a .objects parameter, mark it processed */ /* as far as outer is concerned. */ if (i != 0) *source='\0'; } if (getrec_proccomment(indirect_file,NULL) == 0) break; position++; } if (indirect_file->eof) { fclose(indirect_file->stream); indirect_file->open = FALSE; } return; }