/* 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) */ /* int get_idmissing (idmissing_optfile,missings_by_var, */ /* missings_for_all) */ /* Logical get_latlonparams(latlon_optfile,outlat,outlon,inlat,outlat) */ /* Logical get_timedateparams(timedate_optfile,outtime_structarray, */ /* intime_structarray,fragbuflist) */ /* char get_comment_sources(datacomments_optfile, */ /* comment_filearray, */ /* varlist_optfile, */ /* level0_datafile) */ /* void init_outinfo (outinfo_struct) */ /* void init_file_structs (files,indirect_file) */ /* void init_latlon_structs */ /* (outlonformat,inlonformat,outlatformat,inlatformat) */ /* void init_time_structs (in,out,buflist,cumtime,fractime) */ /* 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 3.1 5 Jun 2013" /* 5 Jun 13 v 3.1 WJS */ /* coordinatedmissingdata /* 4 Jun 13 v 3.1 WJS */ /* identifymissingdata */ /* Noted general problem of comparisons of first part of strings */ /* being treated as representative of whole string. Did some work */ /* on those. Seems to be a 15 yr-old problem */ /* Noted problem of exact comparisons being done by plunking a */ /* null into a string that is reused. Should fix that (15 yrs old) */ /* Hmm, most of the places I looked at unplunk the null w/the char */ /* displaced by original plunking. Maybe I wasn't so dumb after all */ /* Rewrote some wjstbl routines. Here for now - need to go to */ /* utils for real */ /* [Needs defgb.h 5.6] */ /* [Begin v 3.1] */ /* 8 Oct 12 v 3.0e WJS */ /* ISO 8601 time output */ /* Bug fix: yearmonthday output did not diagnose situation where */ /* input did not provide year or month or day */ /* Some commented-out sections relating to possible future */ /* generated data capability */ /* [Needs defgb.h 5.5b] */ /* [Begin v 3.0e] */ /* 7 Feb 11 v 3.0d WJS */ /* Bug fix: test for non-level0-file, non-varlist datacomments */ /* source did not check for null pointer */ /* [Begin v 3.0d] */ /* 30 May 09 v 3.0c WJS */ /* Change "closer to level 0" diagnostics to reflect situation */ /* where problem is actually a missing datum rather than a */ /* "farther" datum */ /* [Begin v 3.0c] */ /* 6 Nov 08 v 3.0b WJS */ /* strip_*_blanks stuff */ /* [Begin v 3.0b] */ /* 22 May 08 v 3.0a WJS */ /* Have get_comment_sources return source format if known */ /* [Needs defgb.h 5.3] */ /* [Begin v 3.0a] */ /* 29 Feb 08 v 3.0 WJS */ /* Use is_a_remote_object instead of just initial // */ /* [Begin v 3.0] */ /* */ /* [1.0 -> 2.9 comments in defgb_revision.doc. 5 Oct 12] */ #include "defgb.h" #ifdef VMS /* For some reason, next statement started causing trouble */ /* Defn should be in sys/types.h on a unix box. Some google */ /* article says it's in unistd.h on VMS. core.h includes the */ /* latter, and defgb.h includes core.h (eventually). We never */ /* use the value returned by waitpid, so see if we can fake things */ /* pid_t waitpid(); ? in wait.h if vms version > 7 ? */ int waitpid(); /* ? in wait.h if vms version > 7 ? */ #endif struct split_wjstbl { int n; /* Number of tables after split */ int *tag; /* User-supplied tag for each table (might */ /* want to go char string some time) */ int *count; /* Number items in each table */ char **wjstbls; /* Result - wjstbl[n][max_table_size] */ }; /* Uses no global variables; defines only these... */ char *version_ioopen_routines = IOOPEN_ROUTINES_VERSION; /* Next array global to IOO for convenience with respect to err msgs */ /* Also, several routines use a buffer of this size for this purpose */ /* so this saves alloc/dealloc */ static char varname[MAXVARNAMESIZE+1]; /* Requires following routines: */ void add_to_comment_stream(); void analy_source(); char *buildstring(); /* Dynamic string concatenator */ char *copy_into_fixed_length_buffer(); void err(); void errn(); /* Logical extract_wjstbl(); */ char *get_int_from_keyword(); int get_int_from_string(); Logical get_logical_from_string(); void ioname__(); int iovarlevel__(); int is_a_remote_object(); Logical legal_varnum(); Logical legal_level(); char *lengthen_str(); int lookup_lev0varlist(); /* char *lookup_wjstbl(); */ int getrec_proccomment(); char *nxttok(); int strcasecmp_wjs(); char *strdupl(); /* Gets mem for copy of string & copies it */ int transvar(); #if READ_COMMANDS char *startchild(); #endif /******************* *****************/ /* Next 2 routines belong in utils */ void err_non_null_terminated_arg2 (arg1,arg2,len_arg2) /* Assumes we can write into arg2 */ char *arg1,*arg2; int len_arg2; { *(arg2+len_arg2) = '\0'; err (arg1,arg2); } Logical match_non_null_terminated_arg2 (arg1,arg2,len_arg2) char *arg1,*arg2; int len_arg2; { return ( strlen(arg1) == len_arg2 ) && ( strncmp(arg1,arg2,len_arg2) == 0 ); } /* The next rewrite of the wjstbl functions belong in utils.c. */ /* It is here, static, to */ /* decouple the utils and defgb releases. When utils is updated, */ /* remove function here and add function name to the doc above */ static char *lookup_wjstbl_w_len (s,wjstbl,value_len) char s[],wjstbl[]; int *value_len; /* Looks up string s in a "wjs table". If found, return a pointer */ /* to the "value" corresponding to the key; else return NULL */ /* If found, return the length of the value as well */ /* A "wjs table" is a string consisting of a number of objects. */ /* Each object is of the form */ /* separator_character, */ /* key_string, */ /* separator_character, */ /* value_string, */ /* white_space_character(s) */ /* The 2 separator chars must be the same, and must not be white */ /* space characters or characters found in any key or value. */ /* There may be multiple blanks */ { char *found,*ptr,*start_value,*end_ptr; int key_len; if (wjstbl == NULL) err("lookup_wjstbl received null wjstbl",""); if (wjstbl[0] == '\0') return NULL; key_len = strlen(s); ptr = (char *)malloc(key_len + 3); if (ptr == NULL) errn ("Could not allocate mem for copy of key in lookup_wjstbl. Nbytes=", key_len+3); /* Build string consisting of input string surrounded by */ /* separator characters */ ptr[0] = WJSTBL_SEPARATOR; strcpy(ptr+1,s); ptr[key_len+1] = WJSTBL_SEPARATOR; ptr[key_len+2] = '\0'; found=strstr(wjstbl,ptr); free(ptr); *value_len = 0; if (found == NULL) return NULL; ptr = found+key_len+2; end_ptr = strchr(ptr,WJSTBL_WHITE_SPACE); /* Next is really an error in the wjstbl construction, but there */ /* are no semantics to return this. The returned length of 0 can */ /* be used ... just have to change all the code everywhere */ if (end_ptr == NULL) return NULL; *value_len = end_ptr - ptr; return ptr; } static char *lookup_wjstbl (s,wjstbl) char s[],wjstbl[]; { int len; return lookup_wjstbl_w_len (s,wjstbl,&len); } static Logical extract_wjstbl_w_len (synonym,max_len_synonym,variable,wjstbl,actual_len_synonym,dyn_synonym) char **dyn_synonym; char *variable,*synonym,*wjstbl; int max_len_synonym; int *actual_len_synonym; /* Function returns NOT_VALID if string-to-be-returned will not fit */ /* into available buffer space. Otherwise returns TRUE/FALSE based */ /* on whether synonym found/not found */ /* max_len_synonym is 2 switches as well as potentially a size */ /* It determines if the return information should be to the user- */ /* supplied buffer or to a dynamically allocated buffer. */ /* It also determines if anything should be returned if "the lookup" */ /* fails */ /* max_len_synonym */ /* 0 dynamically allocate synonym buffer and return it to */ /* *dyn_synonym argument. Do not alter synonym buffer */ /* If there is no synonym, put copy of variable in buffer */ /* In case of allocation failure, return attempted allo- */ /* cation size in *actual_len_synonym */ /* !0 Size of synonym buffer is abs val of max_len_synonym */ /* >0 If there is no synonym, put copy of variable in synonym */ /* <0 If there is no synonym, do not alter synonym */ { char *trans_loc,*synonym_ptr,white_space; int i,trans_len,len; char *return_buffer; trans_loc=lookup_wjstbl_w_len (variable,wjstbl,&trans_len); /* Next if block: lookup failed - variable not in wjstbl */ if (trans_loc == NULL) { /* Returned synonym length will be length of variable, since */ /* we will generally make a copy of that. It will also be the */ /* "can't fit" size. In the dynamic case, increment it to get */ /* "tried to allocate" size */ *actual_len_synonym = len = strlen(variable); if (max_len_synonym == 0) { return_buffer = (char *)malloc(len+1); if (return_buffer == NULL) { (*actual_len_synonym)++; return NOT_VALID; } strcpy (return_buffer,variable); *dyn_synonym = return_buffer; } else { /* Next takes into account use of sign of max_len_synonym as a */ /* "copy the key if lookup fails" flag */ if (len > max_len_synonym) return NOT_VALID; /* Next test is just for efficiency - no need to copy if */ /* intent was to replace key w/value anyway */ if (variable != synonym) strcpy (synonym,variable); } return FALSE; } /* lookup succeeded. Validate buffer size */ /* Returned synonym length will be the val from lookup_wjstbl, */ /* since that's the string we intend to return. It will also */ /* be the "can't fit" size. In the dynamic case, increment */ /* it to get "tried to allocate" size */ *actual_len_synonym = trans_len; if (max_len_synonym == 0) { return_buffer = (char *)malloc(trans_len+1); if (return_buffer == NULL) { (*actual_len_synonym)++; return NOT_VALID; } } else { len = (max_len_synonym > 0) ? max_len_synonym : -max_len_synonym; /* trans_len is length of string; max_len* length of buffer - */ /* hence the foolery to account for the terminating \0 */ if (trans_len >= len) return NOT_VALID; return_buffer = synonym; } /* lookup succeeded & buffer size good. Transfer info back to */ /* caller */ strncpy (return_buffer,trans_loc,trans_len); *(return_buffer + trans_len) = '\0'; if (max_len_synonym == 0) *dyn_synonym = return_buffer; return TRUE; } static Logical extract_wjstbl(synonym,max_len_synonym,variable,wjstbl) char *variable,*synonym,*wjstbl; int max_len_synonym; { int actual_len; char *dyn_buffer; return extract_wjstbl_w_len (synonym,max_len_synonym,variable,wjstbl,&actual_len,&dyn_buffer); } static Logical extract_wjstbl_dyn(synonym,len_synonym,variable,wjstbl) char *variable,*wjstbl; char **synonym; int *len_synonym; { return extract_wjstbl_w_len(NULL,0,variable,wjstbl,len_synonym,synonym); } static Logical match_wjstbl_value_n(match_value,wjstbl_value,wjstbl_value_len) char *match_value,*wjstbl_value; int wjstbl_value_len; /* wjstbl_value is pointer into wjstbl returned by lookup_wjstbl */ /* wjstbl_value_len is # chars to the wjstbl whitespace char which */ /* terminates wjstbl_value. This is returned by lookup_wjstbl_w_len */ /* If you don't have this, you can use match_wjstbl_value (less */ /* efficient, and depends on "wjstbl internals" */ /* Routine also works if *value is normal null-terminated string and */ /* *len is strlen of that string */ /* Returns TRUE if match_value matches wjstbl_value */ /* FALSE if no match */ { return match_non_null_terminated_arg2 (match_value,wjstbl_value,wjstbl_value_len); } static Logical match_wjstbl_value(match_value,wjstbl_value,wjstbl_whitespace) char *match_value,*wjstbl_value; char wjstbl_whitespace; /* THIS ROUTINE MAY NOT BE USED ANYPLACE & hence it's untested */ /* wjstbl_value is pointer into wjstbl returned by lookup_wjstbl */ /* wjstbl_whitespace is the value-terminating character in the wjstbl */ /* being used. The last char of a wjstbl is always this whitespace */ /* if you know the # chars to the wjstbl whitespace char which */ /* terminates wjstbl_value (returned by lookup_wjstbl_w_len), */ /* can use match_wjstbl_value_n (cleaner & faster) */ /* Returns TRUE if match_value matches wjstbl_value */ /* FALSE if no match */ { char *match_ptr,*wjstbl_ptr; match_ptr = match_value; wjstbl_ptr = wjstbl_value; while (*match_ptr != '\0') { if (*wjstbl_ptr == wjstbl_whitespace) return FALSE; if (*wjstbl_ptr != *match_ptr) return FALSE; match_ptr++; wjstbl_ptr++; } return (*wjstbl_ptr == wjstbl_whitespace); } /*************** end wjstbl routine rewrite *****************/ 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 */ /* (although I don't think it will return NULL - it seems to err() ) */ { char *errtmp1,*errtmp2; char *outstr,*inptr,*outptr; Logical backslash_zero = FALSE; 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': backslash_zero = TRUE; *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') && (! backslash_zero)) { 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); } if (backslash_zero && (outptr != outstr + 1)) { errtmp1 = buildstring("\\0 mixed into 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; } void check_levs(frag_lev,frag_desc,component_lev,component_desc,plural) int frag_lev,component_lev; char *frag_desc,*component_desc,*plural; /* Inputs needed to generate an output time must occur on the same */ /* level as the output time or on earlier levels. If this is not the */ /* case, generate an error. Of course, said inputs must actually */ /* be in the data set - diagnose that, too */ /* frag is what we're generating; = output */ /* component is what goes into it; = input */ { char *s,*errmsg_pt1,*errmsg_pt2; if (frag_lev >= component_lev) return; /* Things are OK */ /* A level of MAXLEVELS means that var isn't in object */ s = (component_lev == MAXLEVELS) ? " " : "closer-to-level-0 "; errmsg_pt1 = buildstring(frag_desc," output require",plural); errmsg_pt2 = buildstring(s,component_desc," information"); if ((errmsg_pt1 == NULL) || (errmsg_pt2 == NULL)) err("Problem w/timedate output and its input", "Further problem getting memory to describe issue better. Sorry"); err (errmsg_pt1,errmsg_pt2); return; /* Not that we should get here */ } 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; int i; 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'; } errno = 0; /* Be sure errno refers to opens we do here... */ switch (file->source_type) { case INDIRECT_FILE_LINE: msg=buildstring(file->descrip," data: contained in indirect input file", NULL, " have_opt_ msg"); file->open=FALSE; break; case COMMAND_FILE: msg=buildstring(file->descrip, " data: comes from script ", source, " have_opt_ msg"); #if READ_COMMANDS ptr = startchild(file->source, NULL, &file->stream); /* Not sure if bad startchild result would always give */ /* bad return status */ if ( (file->stream == NULL) && (ptr == NULL) ) ptr = "Reason unknown"; if (ptr != NULL) { msg=lengthen_str("Error opening ", file->descrip, " script ", 300, " have_opt_ msg"); msg=lengthen_str(msg, source, ": \n ", 300, " have_opt_ msg"); err (msg,ptr); } file->open=TRUE; #else err(msg,"\n\tbut method not compiled with script-reading capability"); #endif break; case DATA_FILE: file->stream=fopen(source,"r"); if (file->stream == NULL) { msg = lengthen_str("Error opening ",file->descrip," file ",300, " have_opt_ msg"); msg = lengthen_str(msg,source,"\nProblem: ",300, " have_opt_ msg"); err (msg,strerror(errno)); } msg=buildstring(file->descrip, " file: ", source, " have_opt_ msg"); file->open=TRUE; break; default: msg=buildstring( "Illegal source type for ", file->descrip, " data\n Bad source = ", NULL, " have_opt_ msg" ); err (msg,source); break; } file->eof=FALSE; file->eod=FALSE; file->nrecs=0; add_to_comment_stream(brack_method_name,msg); free (msg); free (brack_method_name); return TRUE; } int key_N(key,string,n) char *key,*string; int *n; /* This routine determines if string is of the form key[_N]. N, if */ /* specified, must be > 0. */ /* Returns -1 if key not spec'd */ /* 0 if exactly key spec'd */ /* 1 if exactly key_N spec'd (w/legal N) */ /* 2 if key_N w/legal N spec'd & something else follows */ /* If returning 1 or 2, N is returned in arg n */ { char underscore,whitespace; char dummy[1+1] = {'\0','\0'}; int i; i = strlen(key); if (strncmp(string,key,i) != 0) return -1; /* Check for _N string. Using sscanf not only does */ /* decode for us, but also handles some whitespace issues */ i = sscanf(string+i,"%c%d%c%1s",&underscore,n,&whitespace,dummy); if (i == 0) return 0; if (i == 1) err ("In key_N format, N must be present. Key_N = ",string); if (i > 4) err ("Internal coding error in key_N. scanf returned ",i); if (underscore != '_') /* This stops use of key as legal prefix for other keywords */ /* Might be overly restrictive, but probably not... */ err ("In key_N format, _ must be present. Key_N = ",string); if (i > 2) /* Check that thing after _N is a space. */ if ( ! isspace(whitespace)) err ("In key_N format, bad char follows N. Key_N = ",string); if (*n <= 0) err ("In key_N format, N must be positive. Key_N = ",string); /* Passed all the checks. Treat "key_N " like "key_N" for return */ /* value purposes */ return (i == 4) ? 2 : 1; } char get_comment_sources(file,outfiles,varlist_file,level0_file) struct fileinfo *file,outfiles[],*varlist_file,*level0_file; /* If datacomments were spec'd, returns UNKNOWN_FORMAT or DEF_FORMATTED */ /* Returns '\0' if datacomments were not spec'd */ /* Input file contains a list of "files" (a la defgb - files, scripts */ /* objects or immediate strings). list length limited by size of */ /* outfiles (presumed to be MAX_DATACOMMENTS_SOURCES). For each, */ /* determine its order (from source_NN keyword or its "line number") */ /* and init the appropriate outfile structure w/ the "file" */ { int position,out_index; char *seps,*msg,*source,*descrip,*end_token1; char *dirstring,*ptr; char format; Logical is_varlist_file,is_level0_file; if (! open_and_log_opt(file)) return '\0'; if (getrec_proccomment(file,file->comment_postfix) == 0) err ("EOF/error before datacomments file data records",""); strdupl(&dirstring,file->source, " saving datacomments file's directory string"); /* If there is a directory, split it from file spec */ /* after its trailing / */ if ( (ptr = strrchr(dirstring,DIRSEP)) == NULL ) { free (dirstring); dirstring = NULL; } else *(++ptr) = '\0'; /* Truncate after found / */ seps = file->item_separators; position = 1; /* Line number (= "start-from-1" array position) */ while (TRUE) { descrip = file->buf + strspn(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 a string of the form source_N, where N gives the */ /* order in which source is to be processed (missing N */ /* defaults to 1) If descrip not present, N is */ /* calculated from position within file. There may be at */ /* most one entry (explicit or calculated) for each N */ /* source is an indirect file source (see get_files) except */ /* that the allowable pointers are .varlist & .level_0, */ /* pointing to the varlist and level 0 data file, re- */ /* spectively, for this object */ source = end_token1 = strpbrk(descrip,seps); if (end_token1 != NULL) source += 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) || (is_a_remote_object(descrip) != 0) || (strcmp(descrip,VARLIST_PTR) == 0) || (strcmp(descrip,LEVEL0_PTR) == 0) || (source == NULL) || (*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; out_index = position; } else { /* Have both source and descrip. Terminate descrip, then */ /* process it to find file type and translation prefix */ *end_token1 = '\0'; switch (key_N(DATACOMMENTS_KEYWORD,descrip,&out_index)) { case -1: out_index = 1; break; case 0: err ("Illegal keyword in datacomments opt file. Keyword = ", descrip); break; case 1: case 2: case 3: break; default: err ("Internal coding error - illegal return from key_N\n",""); } } if (out_index > MAX_DATACOMMENTS_SOURCES) errn ("Too many datacomments sources requested\n" "Requested at least ", out_index ); /* Note that source is not trimmed of trailing seps at this */ /* point. Cannot process left-to-right because could be a */ /* multi-token string (eg, command w/params) */ if (*source == '\0') err ("0 length indirect file entry for ",msg); if (outfiles[out_index-1].source != NULL) errn ("2nd entry for datacomments source number ",out_index); msg=buildstring(" saving ",outfiles[out_index-1].descrip, " source info",""); if (strcmp(source,VARLIST_PTR) == 0) { if (varlist_file->source == NULL ) err ("varlist opt file listed as datacomment source", " but no varlist opt file specified" ); strdupl(&outfiles[out_index-1].source,varlist_file->source,msg); outfiles[out_index-1].source_type = varlist_file->source_type; format = DATACOMMENTS_DEF_FORMATTED; } else if (strcmp(source,LEVEL0_PTR) == 0) { if (level0_file->source == NULL) err ("level 0 data file listed as datacomment source", " but for some reason its name string is NULL" ); strdupl(&outfiles[out_index-1].source,level0_file->source,msg); outfiles[out_index-1].source_type = level0_file->source_type; format = DATACOMMENTS_DEF_FORMATTED; } else { strdupl(&outfiles[out_index-1].source,source,msg); analy_source(&outfiles[out_index-1],seps,dirstring); /* Take a shot at trying to see "files are the same" */ /* This test can easily fail (take, for example, original */ /* code which did not do null pointer checks - not that this */ /* parenthetical note has anything to do w/the logical */ /* difficulty of the testing). */ /* Better would be to look at inode #, etc */ is_varlist_file = (varlist_file->source != NULL) && (strcmp(source,varlist_file->source) == 0); /* Not sure null level0_file->source is legit here, but that */ /* issue is certainly dealt w/elsewhere */ is_level0_file = (level0_file->source != NULL) && (strcmp(source,level0_file->source) == 0); format = (is_varlist_file || is_level0_file) ? DATACOMMENTS_DEF_FORMATTED : DATACOMMENTS_UNKNOWN_FORMAT; } free (msg); } if (getrec_proccomment(file,NULL) == 0) break; position++; } if (dirstring != NULL) free (dirstring); return format; } 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 *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 ("\n 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_CALL(varname,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) { if (fclose(file->stream) != 0) err ("Failure to close opt file. errno msg is ",strerror(errno)); file->open = FALSE; /* See comments at waitpid in dataeod (in defgb.c) */ waitpid((pid_t)-1,NULL,WNOHANG); errno = 0; } 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 */ /* 4 Mar 98. No error if removals list variable not in lev0 list */ /* 25 Mar 96. Check that we have vars before & after removals */ { int i; char *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_CALL(varname,tok) == -1) err ("Translated variable name too long\n Name (pre-trans) = ",tok); tok=varname; } /* If variable name found, mark it as removed */ if ( (i=lookup_lev0varlist(tok)) >= 0 ) list_after_removals[i] = -1; tok=strtok(NULL,removals_file->item_separators); } if (getrec_proccomment(removals_file,NULL) == 0) break; } if (removals_file->eof) { if (fclose(removals_file->stream) != 0) err ("Failure to close removals file. errno msg is ",strerror(errno)); removals_file->open=FALSE; } *ntotal=0; for (i=0; idescrip, " calculation too long\n Param = ", " get_var_from_keyword"), keyword); else if (i == FALSE) return -1; if (file_assoc_w_wjstbl->pre_trans) if (TRANSVAR_CALL(varname,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+1]; /* 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]=WJSTBL_WHITE_SPACE; wjstbl_chars[1]=WJSTBL_SEPARATOR; wjstbl_chars[2]='\0'; now = time(NULL); if (strftime(time_buf, sizeof time_buf, "_%y%b%d-%H%M-%S", localtime(&now) ) == 0 ) err ("Cannot make time string for unique- file name. Buffer too short",""); 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. Does NOT accept addl- prefix */ if (ptr == keyword) output_info->maxscriptdiags = get_int_from_string(keyval,0,INT_MAX,wjstbl_chars[0], " diag optfile maxscriptdiags keyword"); else err ("Invalid prefix in diag optfile keyword ",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) ) { if (fclose(*errout->stream_ptr) != 0) err ("Failure to close compile-time err sink. errno msg is ", strerror(errno)); *(errout->open_ptr) = FALSE; *(errout->mode_ptr) = NULL; errout->mode_ptr = output_info->file[i].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_DEVICE," creating null output sink name"); if ( *(errout->sink) == '\0' ) strdupl(&(errout->sink),NULL_DEVICE," creating null output sink name"); } return TRUE; } Logical get_varlist_options(varlistopts_optfile,varlists_end, varname_for_lev1_specs,coll_from_objobj_obj) struct fileinfo *varlistopts_optfile; int *varlists_end; char **varname_for_lev1_specs,**coll_from_objobj_obj; /* Get the switches from varlistopts optional file */ { char *keyword,*keyval,*end_keyval,*ptr; char varlistopts_wjstbl[VARLISTOPTS_WJSTBL]; char wjstbl_white_space,dummy; int i; int size_v_lev1_specs; int nitems_supplied; int nitems_processed = 0; /* Default to "not specified" */ *varlists_end = NO_VARLISTS_END_SPECIFIED; *varname_for_lev1_specs = '\0'; nitems_supplied = create_wjstbl(varlistopts_optfile,varlistopts_wjstbl,VARLISTOPTS_WJSTBL); if (nitems_supplied == 0) return FALSE; wjstbl_white_space = WJSTBL_WHITE_SPACE; dummy = '\0'; /* Anything != wjstbl_white_space */ keyword = "varlists_end"; if ( (keyval = lookup_wjstbl(keyword,varlistopts_wjstbl)) != NULL ) { i = sscanf(keyval,"%d%c",varlists_end,&dummy); if ( (i != 2) || (dummy != wjstbl_white_space) ) err ("Badly formatted varlists_end field (or defgb err). Field = ", keyval); if (*varlists_end < MIN_VARLISTS_END) err ("Illegal varlists_end value. Field = ",keyval); nitems_processed++; } keyword = FLAG_FOR_COLL_FROM_OBJOBJ; if ( (keyval = lookup_wjstbl(keyword,varlistopts_wjstbl)) != NULL ) { end_keyval = strchr(keyval,wjstbl_white_space); *end_keyval = '\0'; size_v_lev1_specs = sizeof(varname); *varname_for_lev1_specs = (char *)malloc(size_v_lev1_specs); if (*varname_for_lev1_specs == NULL ) err("No memory for varname_for_lev1_specs",""); if (varlistopts_optfile->pre_trans) if (transvar(*varname_for_lev1_specs,size_v_lev1_specs,keyval) == -1) { err ("Translation for varname_for_level1_specs variable too" " long\n Name (pre-trans) = ",keyval ); } else /* Could probably strdupl here and malloc in the other half */ /* of this if, but not sure if some other logic doesn't assume */ /* all varname buffers are same size */ copy_into_buffer (*varname_for_lev1_specs,size_v_lev1_specs,keyval, "copying into varname_for_lev1_specs" ); *end_keyval = wjstbl_white_space; nitems_processed++; } /* Supply default if not spec'd. */ keyword = "coll_from_objobj_obj"; if ( (keyval = lookup_wjstbl(keyword,varlistopts_wjstbl)) == NULL ) { keyval = COLL_FROM_OBJOBJ_OBJ; end_keyval = NULL; } else { end_keyval = strchr(keyval,wjstbl_white_space); *end_keyval = '\0'; } strdupl(coll_from_objobj_obj,keyval,"No memory for coll_from_objobj_obj"); if (end_keyval != NULL) { *end_keyval = wjstbl_white_space; nitems_processed++; } if (nitems_supplied > nitems_processed) errn ("Unprocessed varlistopts parameters. Number skipped = ", nitems_supplied-nitems_processed); return TRUE; } Logical get_datafield_options( datafield_optfile, consec_seps, embed_seps, sepstring_ptr, alt_sepstring, strip_leading_blanks, strip_trailing_blanks, all_blank_treatment) struct fileinfo *datafield_optfile; Logical *consec_seps,*embed_seps; char **sepstring_ptr; char *alt_sepstring; Logical *strip_leading_blanks,*strip_trailing_blanks; int *all_blank_treatment; /* Get the switches from the datafieldopts optional file */ { char *keyword,*keyval,*end_keyval,*ptr; char datafield_wjstbl[DATAFIELDOPTS_WJSTBL+1]; char wjstbl_white_space; Logical leading_blanks,trailing_blanks,tmp_blanks; int nitems_supplied; int nitems_processed = 0; nitems_supplied = create_wjstbl(datafield_optfile,datafield_wjstbl,DATAFIELDOPTS_WJSTBL); if (nitems_supplied == 0) return FALSE; wjstbl_white_space = WJSTBL_WHITE_SPACE; 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_separators_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'; } leading_blanks = trailing_blanks = NOT_VALID; keyword = "strip_blanks"; if ( (keyval = lookup_wjstbl(keyword,datafield_wjstbl)) != NULL ) { end_keyval = strchr(keyval,wjstbl_white_space); *end_keyval = '\0'; leading_blanks = trailing_blanks = get_logical_from_string (keyval," datafieldopts optfile keyword ",keyword); *end_keyval = wjstbl_white_space; nitems_processed++; } keyword = "strip_leading_blanks"; if ( (keyval = lookup_wjstbl(keyword,datafield_wjstbl)) != NULL ) { end_keyval = strchr(keyval,wjstbl_white_space); *end_keyval = '\0'; tmp_blanks = get_logical_from_string (keyval," datafieldopts optfile keyword ",keyword); *end_keyval = wjstbl_white_space; nitems_processed++; if (leading_blanks == NOT_VALID) { leading_blanks = tmp_blanks; } else { if (leading_blanks != tmp_blanks) err ("Conflicting values for strip_blanks and strip_leading_blanks"); } } keyword = "strip_trailing_blanks"; if ( (keyval = lookup_wjstbl(keyword,datafield_wjstbl)) != NULL ) { end_keyval = strchr(keyval,wjstbl_white_space); *end_keyval = '\0'; tmp_blanks = get_logical_from_string (keyval," datafieldopts optfile keyword ",keyword); *end_keyval = wjstbl_white_space; nitems_processed++; if (trailing_blanks == NOT_VALID) { trailing_blanks = tmp_blanks; } else { if (trailing_blanks != tmp_blanks) err ("Conflicting values for strip_blanks and strip_trailing_blanks"); } } if (leading_blanks != NOT_VALID) *strip_leading_blanks = leading_blanks; if (trailing_blanks != NOT_VALID) *strip_trailing_blanks = trailing_blanks; /* We really want a character string, so the fooling w/integers */ /* seems counter-productive. However, it substantially eases the */ /* task of specifying a compile-time value */ keyword = "treat_all_blank_strings_as"; if ( (keyval = lookup_wjstbl(keyword,datafield_wjstbl)) != NULL ) { end_keyval = strchr(keyval,wjstbl_white_space); *end_keyval = '\0'; nitems_processed++; if (strcmp(keyval,"MISSING") == 0) *all_blank_treatment = TREAT_ALL_BLANK_STRINGS_AS_MISSING; else if (strcmp(keyval,"SINGLE_BLANK") == 0) *all_blank_treatment = TREAT_ALL_BLANK_STRINGS_AS_SINGLE_BLANK; else err ("Bad value for treat_all_blank_strings_as keyword. Bad value is ", keyval); } 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; } struct split_wjstbl *split_wjstbls(intbl,max_outtbls,size_outtbl,prefix) char *intbl,*prefix; int max_outtbls,size_outtbl; { int num_out,lenprefix; int i,k; char sep_str[2]; char *ptr,*ptr2; int *tags,*num_items_per_table; struct split_wjstbl *split_tbls; char **outtbls; i = max_outtbls * sizeof(int); tags = (int *) malloc (i); if (tags == NULL) errn ("Could not not get memory for tags in split_wjstbls. Bytes = ,",i); num_items_per_table = (int *) malloc (i); if (num_items_per_table == NULL) errn ("Could not not get memory for counts in split_wjstbls. Bytes = ,",i); i = max_outtbls * sizeof(char *); outtbls = (char **) malloc (i); if (outtbls == NULL) errn ("Could not not get memory for wjstbl ptrs in split_wjstbls. Bytes = ,",i); num_out = 0; sep_str[0] = *intbl; sep_str[1] = '\0'; ptr = strtok(intbl+1,sep_str); lenprefix = strlen(prefix); while (ptr != NULL) { /* ptr pointing to keyword, possibly leading off w/ prefix_tag_ */ /* (underscores optional) */ if (strncmp(ptr,prefix,lenprefix) == 0) { ptr += lenprefix; if (*ptr == '_') ptr++; i = strtol(ptr,&ptr2,10); if (ptr2 == ptr) err( buildstring(prefix, " keyword prefix does not include a tag. Keyword is ", ptr - lenprefix, "no tag err msg in split_wjstbls"), ""); ptr = (*ptr2 == '_') ? ptr2+1 : ptr2; } else { i = 1; } /* See if we've already encountered this output time. */ for (k=0; k max_outtbls) { ptr = buildstring("Too many ",prefix,"s requested. Max = ", "building too many err msg in split_wjstbls"); errn(ptr,max_outtbls); } outtbls[k] = (char *)malloc(size_outtbl); if (outtbls[k] == NULL) errn ("Could not not get memory for wjstbl in split_wjstbls. Bytes = ", size_outtbl); *outtbls[k] = '\0'; tags[k] = i; num_items_per_table[k] = 0; } /* Add this entry to the wjstbl for this output time */ strcat(outtbls[k],sep_str); /* Separator char */ strcat(outtbls[k],ptr); /* Keyword */ strcat(outtbls[k],sep_str); /* Separator char */ ptr = strtok(NULL,sep_str); /* Value for */ strcat(outtbls[k],ptr); /* keyword */ num_items_per_table[k]++; ptr = strtok(NULL,sep_str); } split_tbls = (struct split_wjstbl *)malloc(sizeof(struct split_wjstbl)); if (split_tbls == NULL) errn ("Could not not get memory for split_tbls in split_wjstbls. Bytes = ", sizeof(struct split_wjstbl)); split_tbls->n = num_out; split_tbls->tag = tags; split_tbls->count = num_items_per_table; split_tbls->wjstbls = outtbls; return split_tbls; } void free_split_wjstbl(split_tbl) struct split_wjstbl *split_tbl; { int i; for (i = 0; i < split_tbl->n; i++) free(split_tbl->wjstbls[i]); free(split_tbl->tag); free(split_tbl->count); free(split_tbl->wjstbls); return; } Logical get_latlonformat(buffer,key,wjstbl) char *key,*buffer,*wjstbl; { char *ptr; int len; ptr = lookup_wjstbl_w_len(key,wjstbl,&len); if (ptr == NULL) return FALSE; if (len > MAXLATLONFORMATSIZE) err ("Lat/lon format string too long. String = ",ptr); sscanf(ptr,"%s",buffer); if (strcmp(buffer,"decdeg") == 0) return TRUE; if (strcmp(buffer,"degdecmin") == 0) return TRUE; err ("Illegal lat/lon format. Format = ",ptr); } Logical get_latlonchar(ptr_to_sepchar,key,wjstbl) char *key,*ptr_to_sepchar,*wjstbl; { char *sepstring_ptr,*keyval,*end_keyval; if ( (keyval = lookup_wjstbl(key,wjstbl)) == NULL ) { return FALSE; } else { end_keyval = strchr(keyval,WJSTBL_WHITE_SPACE); *end_keyval = '\0'; sepstring_ptr = get_string_from_escaped_string (keyval," latlonparams optfile separator or convention string",keyval); *end_keyval = WJSTBL_WHITE_SPACE; /* sepstring_ptr cannot be NULL - get_string_from_escaped_string */ /* would err () */ if (*(sepstring_ptr + 1) != '\0') err ("Lat/lon separator or convention string > 1 char. String = ", sepstring_ptr); *ptr_to_sepchar = *sepstring_ptr; free (sepstring_ptr); return TRUE; } } void do_latlon(latlon_wjstbl, outlat,outlon,inlat,inlon, nitems_supplied,latlon_optfile) char *latlon_wjstbl; struct latlonformat *outlat,*outlon; struct latlonformat *inlat,*inlon; int nitems_supplied; struct fileinfo *latlon_optfile; { int nitems_processed = 0; inlat->var = get_var_from_keyword("inlatname",latlon_wjstbl,latlon_optfile); outlat->var = get_var_from_keyword ("outlatname",latlon_wjstbl,latlon_optfile); if ( (inlat->var == INPUT_NOT_SUPPLIED) && (outlat->var != OUTPUT_NOT_REQUESTED) ) err ("Latitude work requested but latitude input variable name not", "supplied"); inlon->var = get_var_from_keyword("inlonname",latlon_wjstbl,latlon_optfile); outlon->var = get_var_from_keyword ("outlonname",latlon_wjstbl,latlon_optfile); if ( (inlon->var == INPUT_NOT_SUPPLIED) && (outlon->var != OUTPUT_NOT_REQUESTED) ) err ("Longitude work requested but longitude input variable name not", "supplied"); if (inlat->var != INPUT_NOT_SUPPLIED) { nitems_processed++; if (outlat->var == OUTPUT_NOT_REQUESTED) outlat->var = inlat->var; else nitems_processed++; } if (inlon->var != INPUT_NOT_SUPPLIED) { nitems_processed++; if (outlon->var == OUTPUT_NOT_REQUESTED) outlon->var = inlon->var; else nitems_processed++; } if (get_latlonchar(&inlat->convention,"inlatconvention",latlon_wjstbl)) { nitems_processed++; if (inlat->convention != '\0') if (strchr(inlat->legal_cardinal,inlat->convention) == NULL) err ("Illegal input latitude convention. Convention = ", inlat->convention); inlat->convention = toupper(inlat->convention); } if (get_latlonchar(&inlon->convention,"inlonconvention",latlon_wjstbl)) { nitems_processed++; if (inlon->convention != '\0') if (strchr(inlon->legal_cardinal,inlon->convention) == NULL) err ("Illegal input longitude convention. Convention = ", inlon->convention); inlon->convention = toupper(inlon->convention); } if (get_latlonchar(&outlat->convention,"outlatconvention",latlon_wjstbl)) { nitems_processed++; if (outlat->convention != '\0') if (strchr(outlat->legal_cardinal,outlat->convention) == NULL) err ("Illegal output latitude convention. Convention = ", outlat->convention); outlat->convention = toupper(outlat->convention); } if (get_latlonchar(&outlon->convention,"outlonconvention",latlon_wjstbl)) { nitems_processed++; if (outlon->convention != '\0') if (strchr(outlon->legal_cardinal,outlon->convention) == NULL) err ("Illegal output longitude convention. Convention = ", outlon->convention); outlon->convention = toupper(outlon->convention); } if (get_latlonchar(&inlat->sep[0],"inlatseparator",latlon_wjstbl)) nitems_processed++; if (get_latlonchar(&inlon->sep[0],"inlonseparator",latlon_wjstbl)) nitems_processed++; 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 == INPUT_NOT_SUPPLIED) ) err ("Latitude work requested ", "but latitude input variable name not supplied"); if ( ( (strcmp(inlon->format,outlon->format) != 0) || (inlon->convention != outlon->convention) ) && (inlon->var == INPUT_NOT_SUPPLIED) ) err ("Longitude work requested ", "but longitude input variable name not supplied"); return; } int get_latlonparams(latlon_optfile,outlat,outlon,inlat,inlon) struct fileinfo *latlon_optfile; struct latlonformat outlat[NUM_LATLON],outlon[NUM_LATLON]; struct latlonformat inlat[NUM_LATLON],inlon[NUM_LATLON]; /* Returns number of lat/lons requested */ /* */ /* 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 (including which characters separate minutes from de- */ /* grees & degrees from NSEW), 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 all_latlons[NUM_LATLON*LATLON_WJSTBL+1]; struct split_wjstbl *per_latlon_tbls; char **latlon_wjstbl; int *nitems_supplied; int num_latlon; int i; if (create_wjstbl(latlon_optfile, all_latlons, NUM_LATLON*LATLON_WJSTBL) == 0 ) return 0; /* Make 1 wjstbl for each output latlon requested */ per_latlon_tbls = split_wjstbls (all_latlons, NUM_LATLON, LATLON_WJSTBL, LATLON_SEQUENCE_PREFIX); num_latlon = per_latlon_tbls->n; nitems_supplied = per_latlon_tbls->count; latlon_wjstbl = per_latlon_tbls->wjstbls; for (i = 0; i < num_latlon; i++) do_latlon(latlon_wjstbl[i], &outlat[i],&outlon[i],&inlat[i],&inlon[i], nitems_supplied[i],latlon_optfile); free_split_wjstbl(per_latlon_tbls); return num_latlon; } 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); free(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); free(errtmp); } out_struct->intime_index = informat_index; /* Save level if variable wasn't asked for. Level set */ /* elsewhere for wanted variables. */ if (out_struct->var == OUTPUT_NOT_REQUESTED) out_struct->lev = inlev; return; } void do_outtime_frags(template_type,fmt_type,level,ntemplates, out_td,buflist,index,calc_j,calc_hhmm) struct outtime out_td[]; char *buflist[]; char template_type; char **fmt_type; int level,ntemplates; int *index,*calc_j,*calc_hhmm; { char template_string[1+1] = {'\0', '\0'}; *fmt_type = NULL; /* Represents default numeric input */ template_string[0] = template_type; /* Check all output fragments affected by this field. */ switch (template_type) { 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],level,template_string,ntemplates); buflist[(*index)++] = out_td[MONTH_FRAG].fragbuf; break; case 'd': /* Day of month */ affects_frag(&out_td[DAY_FRAG],level,template_string,ntemplates); buflist[(*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],level,template_string,ntemplates); buflist[(*index)++] = out_td[DST_FRAG].fragbuf; break; case 'H': case 'I': /* Hour (24 or 12 hour clock) */ affects_frag(&out_td[HOUR_FRAG],level,template_string,ntemplates); buflist[(*index)++] = out_td[HOUR_FRAG].fragbuf; break; case 'j': case 'v': /* Julian w leapyear (1 or 0 = 1 Jan) */ case 'J': case 'V': /* Julian w/o leapyear (1 or 0 = 1 Jan) */ affects_frag(&out_td[MONTH_FRAG],level,template_string,ntemplates); affects_frag(&out_td[DAY_FRAG],level,template_string,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) { buflist[(*index)++] = out_td[MONTH_FRAG].fragbuf; *calc_j = MONTH_FRAG; } else { buflist[(*index)++] = out_td[DAY_FRAG].fragbuf; *calc_j = DAY_FRAG; } break; case 'M': /* Minute of hour */ affects_frag(&out_td[MINUTE_FRAG],level,template_string,ntemplates); buflist[(*index)++] = out_td[MINUTE_FRAG].fragbuf; break; case 'N': /* 24 hour time */ /* See Julian comments - same stuff applies */ affects_frag(&out_td[HOUR_FRAG],level,template_string,ntemplates); affects_frag(&out_td[MINUTE_FRAG],level,template_string,ntemplates); if (out_td[HOUR_FRAG].lev <= out_td[MINUTE_FRAG].lev) { buflist[(*index)++] = out_td[HOUR_FRAG].fragbuf; *calc_hhmm = HOUR_FRAG; } else { buflist[(*index)++] = out_td[MINUTE_FRAG].fragbuf; *calc_hhmm = MINUTE_FRAG; } break; case 'p': /* AM/PM flag */ *fmt_type = "[A-Z,a-z]"; affects_frag(&out_td[AMPM_FRAG],level,template_string,ntemplates); buflist[(*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],level,template_string,ntemplates); buflist[(*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],level,template_string,ntemplates); buflist[(*index)++] = out_td[TIME_OFFSET_FRAG].fragbuf; break; case 'S': /* Second of minute */ /* Specification of this also affects FRACTION_FRAG, since that */ /* eventually represents fractions of minutes. Couldn't figure */ /* out how to do it here, so as of 2.6a (& ioreadrec_ 1.5a) */ /* FRACTION_FRAG.source_type_field gets set in ioreadrec_ when */ /* appropriate (I hope!) (12 May 00) */ affects_frag(&out_td[SECOND_FRAG],level,template_string,ntemplates); buflist[(*index)++] = out_td[SECOND_FRAG].fragbuf; break; case 'y': case 'Y': /* Year (without or with century) */ affects_frag(&out_td[YEAR_FRAG],level,template_string,ntemplates); buflist[(*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 */ /* group of cases and the order of the cases is significant */ /* All values will be extracted from FRACTION buffer */ case 'g': case 'K': /* Fraction of year w leapyear (1 or 0 = 1 Jan) */ case 'G': case 'P': /* Fraction of year w/o leapyear (1 or 0 = 1 Jan) */ affects_frag(&out_td[MONTH_FRAG],level,template_string,ntemplates); affects_frag(&out_td[DAY_FRAG],level,template_string,ntemplates); /* See Julian comments - same stuff applies */ *calc_j = (out_td[MONTH_FRAG].lev <= out_td[DAY_FRAG].lev) ? MONTH_FRAG : DAY_FRAG; case 'o': /* Fraction of day */ affects_frag(&out_td[HOUR_FRAG],level,template_string,ntemplates); case 'O': /* Fraction of hour */ affects_frag(&out_td[MINUTE_FRAG],level,template_string,ntemplates); case 'q': /* Fraction of minute */ affects_frag(&out_td[SECOND_FRAG],level,template_string,ntemplates); case 'Q': /* Fraction of second */ affects_frag(&out_td[FRACTION_FRAG],level,template_string,ntemplates); buflist[(*index)++] = out_td[FRACTION_FRAG].fragbuf; break; case TIMEDATE_SKIPFIELD_CHAR: /* No affected frags, */ *fmt_type = "c"; /* but must */ break; /* accept character(s) */ default: err (buildstring( "Unrecognized template specifier ", template_string, " for input time/date variable ", " bad_template_spec" ), varname); break; } return; } void err_sing_field_templ_mismatch(time_piece,template) char *time_piece,*template; { char *ptr; if ( (ptr = strchr(template,WJSTBL_WHITE_SPACE)) != NULL ) *ptr = '\0'; ptr = lengthen_str("\ Time template problem\n\ Mismatch between integer and decimal parts of single-field template \n\ specifier. Decimal part specifies a fraction of a ", time_piece, ";\n integer part does not specify a ", 512, "single-field template1" ); ptr = lengthen_str(ptr,time_piece,"\n Template = ",512, "single-field template2"); ptr = lengthen_str(ptr, template, "\n Template is for input time/date variable ", 512, "single-field template3" ); err (ptr,varname); return; } Logical widthless_template(template) char **template; /* Templates can either specify the widths of subfields of input or */ /* not. If not, the input can still supply more than one output */ /* field if the input is numeric and has a fraction attached. */ /* Widthless input is represented by templates of the form */ /* X or XY, where X represents what the integer part of the */ /* field represents, and Y represents both that there is a */ /* fractional part, and the format of that fractional part. */ /* X., X.X & X.Y templates are also accepted and turned into XY. */ /* This routine returns a widthless/widths flag. It also changes */ /* the input template pointer to a pointer to the appropriate XY */ /* template if necessary. */ { static char XY_template[2+1] = { '\0', '\0', WJSTBL_WHITE_SPACE }; char X,Y; char *ptr; ptr = *template; X = *(ptr++); Y = *(ptr++); /* If first char of template is repeated, we have "standard" */ /* template, so no multifragment situation */ if (X == Y) return FALSE; /* If format is just X, we have multifragments, but don't have to */ /* fiddle with buffer */ if (Y == WJSTBL_WHITE_SPACE) return TRUE; /* Change X., X.X, & X.Y templates to XY. Check X/Y combos for */ /* validity. Move template to its own buffer */ if (Y == '.') Y = *(ptr++); if (Y == WJSTBL_WHITE_SPACE) Y = X; else if (*ptr != WJSTBL_WHITE_SPACE) { if ( (ptr = strchr(*template,WJSTBL_WHITE_SPACE)) != NULL ) *ptr = '\0'; err (buildstring( "Single-field template specifier must be of form X, X.,\ X.X, or X.Y\n Template = ", *template, " for input time/date variable ", " bad_single_template_fmt" ), varname); } if (X == Y) /* User has given us X. or X.X. Supply Y */ switch (X) { case 'd': case 'j': case 'J': case 'v': case 'V': /* days */ Y = 'o'; /* frac day */ break; case 'H': case 'I': /* hours */ Y = 'O'; /* frac hours */ break; case 'M': /* minutes */ Y = 'q'; /* frac minutes */ break; case 'S': /* seconds */ Y = 'Q'; /* frac seconds */ break; case 'y': case 'Y': /* years */ Y = 'P'; /* A guess - jul day 0-364; no leaps */ break; default: if ( (ptr = strchr(*template,WJSTBL_WHITE_SPACE)) != NULL ) *ptr = '\0'; err (buildstring( "Unrecognized single-field template specifier ", *template, " for input time/date variable ", " bad_single_template_spec" ), varname); } else /* User has given us XY or X.Y. Check validity */ switch (Y) { /* One of the year fractions must go with one of the years */ case 'g': case 'G': case 'K': case 'P': if ( (X == 'Y') || (X == 'y') ) break; err_sing_field_templ_mismatch("year",*template); /* day frac must go with day or one of the Julian days */ case 'o': switch (X) { case 'd': case 'j': case 'J': case 'v': case 'V': break; default: err_sing_field_templ_mismatch("Julian day",*template); } break; case 'O': /* hour frac must go with 24 or 12 hour hour */ if ( (X == 'H') || (X == 'I') ) break; err_sing_field_templ_mismatch("hour",*template); case 'q': /* minute frac must go with minute */ if (X == 'M') break; err_sing_field_templ_mismatch("minute",*template); case 'Q': /* second frac must go with second */ if (X == 'S') break; err_sing_field_templ_mismatch("second",*template); default: if ( (ptr = strchr(*template,WJSTBL_WHITE_SPACE)) != NULL ) *ptr = '\0'; err (buildstring( "Unrecognized decimal part of single-field template ", *template, " for input time/date variable ", " bad_dec_single_template_spec" ), varname); } XY_template[0] = X; XY_template[1] = Y; *template = XY_template; return TRUE; } void force_use_of_frag (out_td,outvar_level) struct outtime *out_td; int outvar_level; { /* If user did not request fragment, request it "for ourselves" */ if (out_td->var == OUTPUT_NOT_REQUESTED) { out_td->var = TIME_INTERNAL_USE; out_td->lev = outvar_level; } return; } int get_timedateparams(timedate_optfile,out_td,in_td,fragbuflist) struct fileinfo *timedate_optfile; struct outtime out_td[NUM_OUTTIMES][NUM_TIMEDATE_FRAGS]; struct intime in_td[NUM_OUTTIMES][NUM_TIMEDATE_FRAGS]; char *fragbuflist[NUM_OUTTIMES][2 * NUM_TIMEDATE_FRAGS]; /* Returns number of output times requested */ /* 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. */ /* We special-case the common situation where an input field */ /* contains only 1 "kind" of time. Such fields are either that */ /* kind of time, or that kind and all smaller kinds. For example, a */ /* day field w/o a decimal pt is just a day, while a day field w/ a */ /* decimal point specifies day, hour, min, & sec. To an extent, this */ /* means many fields can be described both in this special case, and */ /* in full "template" style. The special case is easier to input, */ /* since widths are irrelevant, but the real reason for it is that */ /* we decode special fields w/floating point, allowing input of E */ /* format, which we can't do any other (easy) way */ /* 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 */ /* E, f, F, g, G, K, N, o, O, P, q, Q, R, s, v, V & z. Good luck! */ { char all_timedates[NUM_OUTTIMES*TIMEDATE_WJSTBL + 1]; struct split_wjstbl *per_outtime_tbls; char **timedate_wjstbl; int *timedate_tags; int num_outtimes; int *nitems_supplied; int ntemplates,lenprefix,len_inpfield; int nitems_processed; int fragbuflist_index; /* Could probably calculate N_ALTERNATE_INVAR_FMTS by use of */ /* sizeof, etc, but didn't... */ #define N_ALTERNATE_INVAR_FMTS 4 char *invar_fmts[N_ALTERNATE_INVAR_FMTS] = {"%d%s","_%d%s","%d_%s","_%d_%s"}; int i,j,k,level; char *ptr,*ptr2,*tmp,*fmt_ptr,*fmt_type,*keyword; char template_string[1+1] = {'\0', '\0'}; char sep_str[1+1]; struct intime *in_ptr; div_t digs; int calculate_julian; int calculate_hhmm; Logical var_used[NVAR]; Logical got_yrmoday_input,got_hourmin_input; if (create_wjstbl(timedate_optfile, all_timedates, NUM_OUTTIMES*TIMEDATE_WJSTBL) == 0 ) return 0; if (isalnum(TIMEDATE_SKIPFIELD_CHAR)) { /* Use template_string as buffer for error message */ *template_string = TIMEDATE_SKIPFIELD_CHAR; err ("Bad time/date skipfield character. Char is ",template_string); } /* Make 1 wjstbl for each output time requested */ per_outtime_tbls = split_wjstbls (all_timedates, NUM_OUTTIMES, TIMEDATE_WJSTBL, TIMEDATE_SEQUENCE_PREFIX); num_outtimes = per_outtime_tbls->n; nitems_supplied = per_outtime_tbls->count; timedate_tags = per_outtime_tbls->tag; timedate_wjstbl = per_outtime_tbls->wjstbls; for (k=0; klocal conversion option */ ptr = lookup_wjstbl("intimeconvention",timedate_wjstbl[k]); if (ptr == 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++; } ptr2 = lookup_wjstbl("outtimeconvention",timedate_wjstbl[k]); if (ptr2 == 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++; } if (strcmp(ptr,ptr2) == 0) { out_td[k][HOUR_FRAG].conv = NO_TIME_ZONE_CONVERSION; out_td[k][ISO8601_FRAG].conv = (*ptr == 'l') ? OUTPUT_IS_LOCAL : OUTPUT_IS_UTC ; } else { out_td[k][HOUR_FRAG].conv = (*ptr == 'l') ? CONVERT_LOCAL_TO_UTC : CONVERT_UTC_TO_LOCAL ; out_td[k][ISO8601_FRAG].conv = (*ptr == 'l') ? OUTPUT_IS_UTC : OUTPUT_IS_LOCAL ; } /* keyword is prefix + # (assumed <= 99) + longer of postfixes */ /* ("name" & "template"). Underscores are optional */ lenprefix = strlen(TIMEDATE_INPUT_KEYWORD_PREFIX); keyword = buildstring(TIMEDATE_INPUT_KEYWORD_PREFIX, "_99_template", NULL, " building time/date keyword"); /* Get any output variables. */ i = get_var_from_keyword ("outjulianname",timedate_wjstbl[k],timedate_optfile); if (i >= 0) { ptr = lookup_wjstbl("outjulianformat",timedate_wjstbl[k]); if (ptr == NULL) err ("No format for output julian date variable ",varname); if ( *(ptr+1) != WJSTBL_WHITE_SPACE ) err ( "Julian output format longer than 1 char for output time variable ", varname ); /* outjulianformat is an "odd" item, like the time conventions */ /* so count it here. The "regular" items (inname, intemplate, */ /* and outname) are counted while doing some checking */ nitems_processed++; switch (*ptr) { case 'j': case 'J': case 'v': case 'V': case 'g': case 'G': case 'P': case 'K': break; case 's': case 'E': if (get_int_from_keyword(&out_td[k][JULIAN_FRAG].baseyear, "outjulianbaseyear", timedate_wjstbl[k], 0,9999,"decoding julian base year") == NULL ) { out_td[k][JULIAN_FRAG].baseyear = DEFAULT_JULIAN_BASE_YEAR; } else { nitems_processed++; } if (out_td[k][JULIAN_FRAG].baseyear < MINIMUM_JULIAN_BASE_YEAR) errn ("Base year for time-since-base-year computation is too small\ . Offending value is ",out_td[k][JULIAN_FRAG]); break; default: /* Put # in next message as marker for bad format char */ tmp = "Julian output format # not one of gGPKjJvVsE for \ output time variable "; if ( (ptr2=strchr(tmp,'#')) != NULL ) *ptr2 = *ptr; err (tmp,varname); break; } out_td[k][JULIAN_FRAG].conv = *ptr; out_td[k][JULIAN_FRAG].var = i; out_td[k][JULIAN_FRAG].lev = iovarlevel__(i); } i = get_var_from_keyword ("outyearname",timedate_wjstbl[k],timedate_optfile); if (i >= 0) { out_td[k][YEAR_FRAG].var = i; out_td[k][YEAR_FRAG].lev = iovarlevel__(i); } i=get_var_from_keyword ("outmonthname",timedate_wjstbl[k],timedate_optfile); if (i >= 0) { out_td[k][MONTH_FRAG].var = i; out_td[k][MONTH_FRAG].lev = iovarlevel__(i); } i = get_var_from_keyword ("outdayname",timedate_wjstbl[k],timedate_optfile); if (i >= 0) { out_td[k][DAY_FRAG].var = i; out_td[k][DAY_FRAG].lev = iovarlevel__(i); } i = get_var_from_keyword ("outyearmonthdayname",timedate_wjstbl[k],timedate_optfile); if (i >= 0) { out_td[k][YEARMONTHDAY_FRAG].var = i; i = iovarlevel__(i); out_td[k][YEARMONTHDAY_FRAG].lev = i; /* We do YEARMONTHDAY by setting up to be sure that YEAR, */ /* MONTH, and DAY are done, whether by us or by user */ /* We honor user levels if user requested components of */ /* YEARMONTHDAY, but will check that YEARMONTHDAY level is, */ /* ultimately, >= all components */ /* No input fragments will contribute to YEARMONTHDAY_FRAG */ force_use_of_frag(&out_td[k][YEAR_FRAG],i); force_use_of_frag(&out_td[k][MONTH_FRAG],i); force_use_of_frag(&out_td[k][DAY_FRAG],i); } /* 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[k],timedate_optfile); out_td[k][HOUR_FRAG].var = i; out_td[k][MINUTE_FRAG].var = i; out_td[k][SECOND_FRAG].var = i; out_td[k][FRACTION_FRAG].var = i; if (i >= 0) { i = iovarlevel__(i); out_td[k][HOUR_FRAG].lev = i; out_td[k][MINUTE_FRAG].lev = i; out_td[k][SECOND_FRAG].lev = i; out_td[k][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[k],timedate_optfile); out_td[k][TIME_OFFSET_FRAG].var = i; out_td[k][DST_FRAG].var = out_td[k][TIME_OFFSET_FRAG].var; if (i >= 0) { i = iovarlevel__(i); out_td[k][TIME_OFFSET_FRAG].lev = i; out_td[k][DST_FRAG].lev = i; } i = get_var_from_keyword ("outISO8601name",timedate_wjstbl[k],timedate_optfile); if (i >= 0) { /* See comments in YEARMONTHDAY section. This section "down here" */ /* since it must be after output times (if output times spec'd, */ /* must honor those levels) */ out_td[k][ISO8601_FRAG].var = i; i = iovarlevel__(i); out_td[k][ISO8601_FRAG].lev = i; force_use_of_frag(&out_td[k][YEAR_FRAG],i); force_use_of_frag(&out_td[k][MONTH_FRAG],i); force_use_of_frag(&out_td[k][DAY_FRAG],i); force_use_of_frag(&out_td[k][HOUR_FRAG],i); force_use_of_frag(&out_td[k][MINUTE_FRAG],i); force_use_of_frag(&out_td[k][SECOND_FRAG],i); } /* Input variables, templates, etc */ for (ntemplates=0; ntemplatesvar = get_var_from_keyword(keyword,timedate_wjstbl[k],timedate_optfile)) >= 0 ) break; } if (in_ptr->var < 0) break; for (i = 0; i < N_ALTERNATE_INVAR_FMTS; i++) { sprintf(keyword+lenprefix,invar_fmts[i],ntemplates+1,"template"); if ( (ptr = lookup_wjstbl(keyword,timedate_wjstbl[k])) != NULL ) break; } if (ptr == NULL) err ("No template for time/date input variable ",varname); /* Get all output fragments from this input variable, and */ /* build format that will extract substrings for these frag- */ /* ments. */ in_ptr->fragbuf_index = fragbuflist_index; level = iovarlevel__(in_ptr->var); fmt_ptr = in_ptr->format; in_ptr->nfrags_in_substring = 0; if (widthless_template(&ptr)) { /* "Widthless" input - input is decoded as one string, re- */ /* gardless of number of fragments it contains */ do_outtime_frags(*ptr, &fmt_type, level, ntemplates, out_td[k], fragbuflist[k], &fragbuflist_index, &calculate_julian, &calculate_hhmm ); /* Widthless field numeric input is always read in float- */ /* ing point. Be sure fields & frac fields do not change */ /* fmt type. Note that decodes from bufs w/these chars will */ /* have to check anyway, since chars accept non-legit */ /* numbers */ if (fmt_type == NULL) fmt_type = "[0-9,.,+,-,E,e]"; in_ptr->nfrags_in_substring++; /* If there is a second fragment from this variable, process it */ if ( *(ptr+1) != WJSTBL_WHITE_SPACE ) { in_ptr->nfrags_in_substring++; do_outtime_frags(*(ptr+1), &ptr2, level, ntemplates, out_td[k], fragbuflist[k], &fragbuflist_index, &calculate_julian, &calculate_hhmm ); if (ptr2 != NULL) err ("defgb internal error: do_outtime_frags changed fmt_type",""); } /* Set up format for this widthless input. By defn, number */ /* of strings to be decoded for widthless input is 1 */ *(fmt_ptr++) = '%'; strcpy(fmt_ptr,fmt_type); in_ptr->nstrings = 1; } else { /* Template says that input contains fixed-field substrings */ /* (well, maximum length substrings anyway) */ in_ptr->nstrings = 0; while (*ptr != WJSTBL_WHITE_SPACE) { *template_string = *ptr; if ( ispunct(*ptr) && (*ptr != TIMEDATE_SKIPFIELD_CHAR) ) *(fmt_ptr++) = *(ptr++); else { len_inpfield=strspn(ptr,template_string); do_outtime_frags(*ptr, &fmt_type, level, ntemplates, out_td[k], fragbuflist[k], &fragbuflist_index, &calculate_julian, &calculate_hhmm ); /* Default numerics to unsigned integer input. Be sure */ /* that integer data uses this format since code does */ /* not check when decoding. */ if (fmt_type == NULL) fmt_type = "[0-9]"; /* Make %nns format string for this piece of the field. */ /* Make nn string from length of field, a la */ /* sprintf(fmt_ptr,"%%ds",len_inpfield) [I hope], assum- */ /* ing field widths of < 100 characters (and assuming */ /* char representation of digit n+1 is one greater than */ /* representation of digit n). Did it this way... for */ /* fun, I guess */ /* Instead of s, use an set of legal characters */ /* appropriate to the template type. */ *(fmt_ptr++) = '%'; /* If field is to be skipped, indicate in format. Else */ /* increment # fragments this template */ if (*template_string == TIMEDATE_SKIPFIELD_CHAR) *(fmt_ptr++) = SSCANF_SKIPFIELD_CHAR; else in_ptr->nstrings++; 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 nitems_processed) { char tmp[20]; sprintf (tmp,"time%d_",timedate_tags[k]); errn (buildstring("Unprocessed ",tmp," parameters. Number skipped = ", " unproc param msg"), nitems_supplied[k] - nitems_processed); } /* Output variables that requires multiple sources of input must */ /* "have" that info by their output time. (Variables that need */ /* single sources have that problem, too, but they are checked */ /* in affects_frag and these aren't */ if ( legal_varnum(out_td[k][YEARMONTHDAY_FRAG].var) ) { check_levs (out_td[k][YEARMONTHDAY_FRAG].lev,"yearmonthday", out_td[k][YEAR_FRAG].lev,"year","s "); check_levs (out_td[k][YEARMONTHDAY_FRAG].lev,"yearmonthday", out_td[k][MONTH_FRAG].lev,"month","s "); check_levs (out_td[k][YEARMONTHDAY_FRAG].lev,"yearmonthday", out_td[k][DAY_FRAG].lev,"day","s "); } if ( legal_varnum(out_td[k][ISO8601_FRAG].var) ) { check_levs (out_td[k][ISO8601_FRAG].lev,"ISO8601", out_td[k][YEAR_FRAG].lev,"year","s "); check_levs (out_td[k][ISO8601_FRAG].lev,"ISO8601", out_td[k][MONTH_FRAG].lev,"month","s "); check_levs (out_td[k][ISO8601_FRAG].lev,"ISO8601", out_td[k][DAY_FRAG].lev,"day","s "); check_levs (out_td[k][ISO8601_FRAG].lev,"ISO8601", out_td[k][HOUR_FRAG].lev,"day","s "); check_levs (out_td[k][ISO8601_FRAG].lev,"ISO8601", out_td[k][MINUTE_FRAG].lev,"day","s "); check_levs (out_td[k][ISO8601_FRAG].lev,"ISO8601", out_td[k][SECOND_FRAG].lev,"day","s "); } if ( legal_varnum(out_td[k][JULIAN_FRAG].var) ) { /* If leap years significant, need year input. Check year */ /* output, since input can come from variety of sources */ switch (out_td[k][JULIAN_FRAG].conv) { case 'K': case 'v': case 'j': case 'g': case 's': case 'E': check_levs (out_td[k][JULIAN_FRAG].lev, "Time/date Julian-day/fractional-year/time-since-epoch", out_td[k][YEAR_FRAG].lev,"year"," "); } check_levs (out_td[k][JULIAN_FRAG].lev, "Time/date Julian-day/fractional-year/time-since-epoch", out_td[k][MONTH_FRAG].lev,"month"," "); check_levs (out_td[k][JULIAN_FRAG].lev, "Time/date Julian-day/fractional-year/time-since-epoch", out_td[k][DAY_FRAG].lev,"day"," "); /* Finally, because we're lazy and want to use the other */ /* output variables to go into output julian, insist that */ /* output julian occurs last. No logical necessity for this */ if ( (out_td[k][JULIAN_FRAG].lev < out_td[k][HOUR_FRAG].lev) && (*out_td[k][HOUR_FRAG].source_field_type != '\0') ) err ("Available time information must occur closer to level 0 than ", "the Julian-day/fractional-year/time-since-epoch output level"); } if (*out_td[k][HOUR_FRAG].source_field_type == 'I') if (*out_td[k][AMPM_FRAG].source_field_type == '\0') err ("Time/date I format requires AM/PM information",""); if (out_td[k][HOUR_FRAG].conv != NO_TIME_ZONE_CONVERSION) { check_levs (out_td[k][HOUR_FRAG].lev,"hour", out_td[k][TIME_OFFSET_FRAG].lev,"time displacement","s "); check_levs (out_td[k][MINUTE_FRAG].lev,"minute", out_td[k][TIME_OFFSET_FRAG].lev,"time displacement","s "); } /* 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[k][calculate_julian].source_field_type) { case 'v': case 'j': case 'g': case 'K': if (out_td[k][calculate_julian].lev < out_td[k][YEAR_FRAG].lev) err ("Time/date Julian day/fractional year input ", "requires year information"); /* Deliberately fall through */ case 'V': case 'J': case 'G': case 'P': *out_td[k] [MONTH_FRAG + DAY_FRAG - calculate_julian] .source_field_type = JULIAN_SOURCES; break; default: break; } /* See calculate_julian comments above */ if (*out_td[k][calculate_hhmm].source_field_type == 'N') *out_td[k] [HOUR_FRAG + MINUTE_FRAG - calculate_hhmm] .source_field_type = JULIAN_SOURCES; free (keyword); } /* end of (k=0; k MAXLEN_NOTDECODABLE_SPECIAL_STRING) errn ("String associated with not-decodable_special_word keyword too long. Max len = ", MAXLEN_NOTDECODABLE_SPECIAL_STRING); nitems_processed++; } ok = extract_wjstbl_dyn(&value,&len,"value",wjstbl); if (ok == NOT_VALID) errn( "Allocation failure for string associated with value keyword. Failed # bytes=", len); if ( ! ok ) err ("identifymissingdata: no value supplied in a set of pairs associated \ w/varlist ",varlist); nitems_processed++; value_is_all_notdecodables = (strcasecmp_wjs(value,special_word) == 0); missing_value_struct->do_comparison_test = ( ! value_is_all_notdecodables ); temp = lookup_wjstbl_w_len("datatype",wjstbl,&len); if (temp == NULL) err ("identifymissingdata: no datatype supplied for value ",value); nitems_processed++; if (match_wjstbl_value_n("integer",temp,len)) { if ( ! value_is_all_notdecodables) { GET_INTEGER_FROM_STRING(missing_value_struct->val_as_long,value,ok); if ( ! ok ) err ("identifymissingdata: could not decode as integer. value: ",value); } missing_value_struct->datatype = IDMISSING_DATATYPE_LONG; } else if (match_wjstbl_value_n("floatingpoint",temp,len)) { if ( ! value_is_all_notdecodables) { GET_NUMBER_FROM_STRING(missing_value_struct->val_as_double,value,ok); if ( ! ok ) err ("identifymissingdata: could not decode as floatingpoint. value: ", value); } missing_value_struct->datatype = IDMISSING_DATATYPE_FLOAT; } else if (match_wjstbl_value_n("character",temp,len)) { if (value_is_all_notdecodables) err ("identifymissingdata: cannot specify datatype=character with value=", special_word); missing_value_struct->val_as_string = value; missing_value_struct->datatype = IDMISSING_DATATYPE_CHAR; } else { err ("identifymissingdata: illegal option for datatype keyword. option: ", temp); } temp = lookup_wjstbl_w_len("nonnumeric",wjstbl,&len); if (temp == NULL) { chr = (value_is_all_notdecodables) ? IDMISSING_NOTDECODABLE_MISSING : IDMISSING_NOTDECODABLE_DEFAULT; } else { nitems_processed++; if (match_wjstbl_value_n("legal",temp,len)) chr = IDMISSING_NOTDECODABLE_ALLOW; else if (match_wjstbl_value_n("illegal",temp,len)) chr = IDMISSING_NOTDECODABLE_ABORT; else if (match_wjstbl_value_n("missing",temp,len)) chr = IDMISSING_NOTDECODABLE_MISSING; else err ("identifymissingdata: illegal option for nonnumeric keyword. option: ", temp); if (value_is_all_notdecodables && (chr != IDMISSING_NOTDECODABLE_MISSING)) err ("identifymissingdata: must specify nonnumeric=missing with value=", special_word); } missing_value_struct->notdecodable_action = chr; ok = extract_wjstbl_dyn(&temp,&len,"tolerance",wjstbl); if (ok == NOT_VALID) errn( "Allocation failure for string associated with tolerance keyword. Failed # bytes=", len); if (ok) { nitems_processed++; GET_NUMBER_FROM_STRING(missing_value_struct->tolerance,temp,ok); if ( ! ok ) err ("identifymissingdata: could not decode as floatingpoint. tolerance: ", temp); } else { missing_value_struct->tolerance = 0.; } temp = lookup_wjstbl_w_len("comparison",wjstbl,&len); if (temp == NULL) { missing_value_struct->comparison = IDMISSING_DATUM_EQ_VAL; } else { nitems_processed++; /* Syntax: key1_XX or key1_XX_key2, where keys are either */ /* 'datum' or 'value', and XX are comparison ops. Accept */ /* key1_XX_ since it's easier than making the doc clearer */ /* Case of XX significant if character compares; ignore else */ ok = (len == 8) || (len == 9) || (len == 14); if ( ! ok ) err_non_null_terminated_arg2 ("identifymissingdata: bad syntax in comparison spec: ",temp,len); ok = ( *(temp+5) == '_' ); if ( ! ok ) err_non_null_terminated_arg2 ("identifymissingdata: bad syntax in comparison spec: ",temp,len); ok = (len > 8) && ( *(temp+8) == '_' ); if ( ! ok ) err_non_null_terminated_arg2 ("identifymissingdata: bad syntax in comparison spec: ",temp,len); word1_is_datum = (strncmp("datum",temp,5) == 0); ok = word1_is_datum || (strncmp("value",temp,5) == 0); if ( ! ok ) err_non_null_terminated_arg2 ("identifymissingdata: bad syntax in comparison spec: ",temp,len); if (len >=14) { ok = (word1_is_datum) ? (strncmp("value",temp+9,5) == 0) : (strncmp("datum",temp+9,5) == 0); if ( ! ok ) err_non_null_terminated_arg2 ("identifymissingdata: bad syntax in comparison spec: ",temp,len); } chr = IDMISSING_BAD_COMPARISON_OPERATOR; comp1 = toupper (*(temp+6)); comp2 = toupper (*(temp+7)); if (missing_value_struct->datatype == IDMISSING_DATATYPE_CHAR) { mixed_case = ( (comp1 == *(temp+6)) && (comp2 != *(temp+7)) ) || ( (comp2 == *(temp+7)) && (comp1 != *(temp+6)) ); if (mixed_case) err ("identifymissingdata: mixed case in comparison for char data in",temp); missing_value_struct->comparison = (comp1 == *(temp+6)) ? IDMISSING_CASEBLIND : IDMISSING_CASESENSITIVE; } if ((comp1 == 'E') && (comp2 == 'Q')) chr = IDMISSING_DATUM_EQ_VAL; else if ((comp1 == 'L') && (comp2 == 'T')) chr = (word1_is_datum) ? IDMISSING_DATUM_LT_VAL : IDMISSING_DATUM_GT_VAL; else if ((comp1 == 'L') && (comp2 == 'E')) chr = (word1_is_datum) ? IDMISSING_DATUM_LE_VAL : IDMISSING_DATUM_GE_VAL; else if ((comp1 == 'G') && (comp2 == 'T')) chr = (word1_is_datum) ? IDMISSING_DATUM_GT_VAL : IDMISSING_DATUM_LT_VAL; else if ((comp1 == 'G') && (comp2 == 'E')) chr = (word1_is_datum) ? IDMISSING_DATUM_GE_VAL : IDMISSING_DATUM_LE_VAL; if (chr == IDMISSING_BAD_COMPARISON_OPERATOR) err_non_null_terminated_arg2 ("identifymissingdata: bad comparison operator in",temp,len); missing_value_struct->comparison = chr; } if (nitems_supplied > nitems_processed) errn ("Unprocessed identifymissingdata parameters. Number skipped = ", nitems_supplied-nitems_processed); *return_struct = missing_value_struct; return varlist; } void add_into_idmissing_list(list_start,ptr_to_add) struct idmissing ***list_start,*ptr_to_add; { struct idmissing **ptr; ptr = *list_start; while (*ptr++ != NULL) ; /* ptr now points to location after NULL; = new end of list */ *ptr-- = NULL; *ptr = ptr_to_add; return; } int get_idmissing (idmissing_optfile,missings_by_var,missings_for_all) struct fileinfo *idmissing_optfile; struct idmissing **missings_by_var[NVAR],***missings_for_all; /* Returns number of vars that have "alternate" missing vals defined */ /* For each var w/an alternate, create an entry in */ /* missings_by_var, an array indexed by varnum. Each entry */ /* points to a list of structures. Each structure defines an */ /* alternate value test. Each list is NULL terminated. */ /* If there are alternates that apply to all values, make a list of */ /* those, too, and return it in missings_for_all */ /* The order of the */ /* lists is important in that string alternates should be processed */ /* before numeric alternates (else there is a problem when a string */ /* alternate cannot be decoded and the user has said that non- */ /* decodables should abort) */ /* The structs that define an alternate are dynamically allocated */ { char all_idmissings[NUM_IDMISSING*IDMISSING_WJSTBL+1]; struct split_wjstbl *per_idmissing_tbls; char **idmissing_wjstbl; int *nitems_supplied; int num_idmissing; struct idmissing *missing_struct; struct idmissing **per_var_missing_list,**all_var_missing_list; char *varlist; char *nxt,*tok; int i,list_len,ntoks,varnum; if (create_wjstbl(idmissing_optfile, all_idmissings, NUM_IDMISSING*IDMISSING_WJSTBL) == 0) return 0; list_len = (NUM_IDMISSING+1) * (sizeof(struct idmissing *)); all_var_missing_list = (struct idmissing **)malloc(list_len); if (all_var_missing_list == NULL) errn ("Could not not get memory for all_var_missing_list in get_idmissing Bytes = ", list_len); *all_var_missing_list = NULL; /* Make 1 wjstbl for each missing value requested */ per_idmissing_tbls = split_wjstbls (all_idmissings, NUM_IDMISSING, IDMISSING_WJSTBL, IDMISSING_SEQUENCE_PREFIX); num_idmissing = per_idmissing_tbls->n; nitems_supplied = per_idmissing_tbls->count; idmissing_wjstbl = per_idmissing_tbls->wjstbls; /* this is a per-value loop. We want to return a per-var struct */ for (i = 0; i < num_idmissing; i++) { varlist = do_idmissing(idmissing_wjstbl[i],&missing_struct, nitems_supplied[i]); tok = nxttok(varlist,IDMISSING_VARLIST_SEP_STRING,&nxt,NULL,FALSE,FALSE); ntoks = 0; while (tok != NULL) { ntoks++; if ( strcasecmp_wjs(tok,IDMISSING_FOR_ALL_VARIABLES) == 0 ) { if (ntoks != 1) err ("identifymissingdata: ALL string specified along with other variables in",tok); add_into_idmissing_list(&all_var_missing_list,missing_struct); } else { varnum = lookup_lev0varlist(tok); if (varnum == ILLEGAL_VARNUM) err ("identifymissingdata: cannot find varname in object. Bad varname: ",tok); per_var_missing_list = missings_by_var[varnum]; if (per_var_missing_list == NULL) { per_var_missing_list = (struct idmissing **)malloc(list_len); if (per_var_missing_list == NULL) errn ("Could not not get memory for per_var_missing_list in get_idmissing Bytes = ", list_len); *per_var_missing_list = NULL; } add_into_idmissing_list(&per_var_missing_list,missing_struct); missings_by_var[varnum] = per_var_missing_list; } tok = nxttok(nxt,IDMISSING_VARLIST_SEP_STRING,&nxt,NULL,FALSE,FALSE); } } free_split_wjstbl(per_idmissing_tbls); if (all_var_missing_list != NULL) *missings_for_all = all_var_missing_list; return num_idmissing; } int get_coordmissing(file,coordmissings) int *coordmissings[NVAR]; struct fileinfo *file; /* This code essentially copied from create_wjstbl in order to get */ /* the record/entry/item logic. Unfortunately, that kind of dwarfs */ /* what this routine does; esp because of the generality of the */ /* error msgs. Oh well ... */ /* Data comes in as independent vars followed by a list of their */ /* dependent vars. We output an array indexed by dependent varnums. */ /* Each array element points to a list of independent vars, the */ /* missing status of any of which means the dependent var is missing */ { int paircnt; 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; int len_dependent_varnum_list; char *ptr,*end_ptr; int independent_varnum,dependent_varnum; int level_of_independent; int *dependent_varnum_list; int i; Logical independents[NVAR]; if (! open_and_log_opt(file)) return 0; opt_file_id = buildstring ("\n Problem in ",file->descrip," optional file", " creating opt_file_id string"); if ( (strchr(file->entry_separators,COORDMISSING_VARLIST_SEP_CHAR) != 0) || (strchr(file->item_separators,COORDMISSING_VARLIST_SEP_CHAR) != 0) || (strchr(file->item_alt_separators,COORDMISSING_VARLIST_SEP_CHAR) != 0) ) err ("Internal error: coordinated_missing list separator \ char conflicts with its entry/item/alt separator strings",""); /* See comments about this list size in ioreadrec_routines ... */ /* esp if you're about to change it */ len_dependent_varnum_list = (NVAR+1)*sizeof(int *); for (i=0; icomment_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; paircnt++; /* Got first of pair (typically a variable name or keyword) */ if (file->pre_trans) { if (TRANSVAR_CALL(varname,tok) == -1) { msg = buildstring ("Translated variable name too long\n Name (pre-trans) = ", tok, rec_id, "creating err msg coord2"); err (msg,opt_file_id); } else { tok=varname; } } independent_varnum = lookup_lev0varlist(tok); level_of_independent = iovarlevel__(independent_varnum); if (independent_varnum == ILLEGAL_VARNUM) { msg = buildstring ("Cannot find variable name in object\n Name (pre-trans) = ", tok, rec_id, "creating err msg coord3"); err (msg,opt_file_id); } if (independents[independent_varnum]) { msg = buildstring("More than one entry for independent var ",tok,rec_id, " creating err msg coord8"); err (msg,opt_file_id); } independents[independent_varnum] = TRUE; /* 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 coord6"); 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 coord7"); err (msg,opt_file_id); } ptr = tok; /* Interesting issue about what happens if tok is at address */ /* 1, leading to question about something being legitimately */ /* located at address 0 ... */ end_ptr = ptr - 1; while (end_ptr != NULL) { ptr = end_ptr + 1; end_ptr = strchr(ptr,COORDMISSING_VARLIST_SEP_CHAR); if (end_ptr != NULL) *end_ptr = '\0'; if (file->pre_trans) { if (TRANSVAR_CALL(varname,ptr) == -1) { msg = buildstring ("Translated variable name too long\n Name (pre-trans) = ", ptr, rec_id, "creating err msg coord10"); err (msg,opt_file_id); } else { ptr=varname; } } dependent_varnum = lookup_lev0varlist(ptr); if (dependent_varnum == ILLEGAL_VARNUM) { msg = buildstring ("Cannot find variable name in object\n Name (pre-trans) = ", tok, rec_id, "creating err msg coord11"); err (msg,opt_file_id); } if (level_of_independent > iovarlevel__(dependent_varnum)) { msg = buildstring ("Dependent variable occurs at lower level \ than independent variable. Dependent variable = ", ptr, rec_id, "creating err msg coord12"); err (msg,opt_file_id); } if (coordmissings[dependent_varnum] == NULL) { dependent_varnum_list = (int *)malloc(len_dependent_varnum_list); if (dependent_varnum_list == NULL) { msg = buildstring ("Could not get memory for dependent_varnum_list", opt_file_id, "Bytes = ", "creating err msg coord13"); errn (msg,len_dependent_varnum_list); } coordmissings[dependent_varnum] = dependent_varnum_list; i = 0; } else { dependent_varnum_list = coordmissings[dependent_varnum]; for (i=0; ieof) { if (fclose(file->stream) != 0) err ("Failure to close opt file. errno msg is ",strerror(errno)); file->open = FALSE; /* See comments at waitpid in dataeod (in defgb.c) */ waitpid((pid_t)-1,NULL,WNOHANG); errno = 0; } free(opt_file_id); if (rec_id != NULL) free(rec_id); if (msg != NULL) free (msg); return paircnt; } /*********** for generated data void init_generateddata_structs (gendata_structs) struct gendata gendata_structs[NUM_GENDATA]; { int i; for (i=0; itag[i] = ILLEGAL_GENDATA_TAG; gendata_structs->var[i] = ILLEGAL_VARNUM; gendata_structs->lev[i] = ILLEGAL_LEVEL; gendata_structs->formula_type[i] = ILLEGAL_GENDATA_FORMULA_TYPE; gendata_structs->missing_value[i] = MISSING_VALUE_STRING; gendata_structs->formula[i] = NULL; gendata_structs->first_index[i] = 1; gendata_structs->missing_input_means_missing_output[i] = TRUE; gendata_structs->kill_if_extract_is_nd[i] = TRUE; gendata_structs->kill_if_extract_string_too_short[i] = TRUE; gendata_structs->action_if_string_to_be_padded_too_long[i] = "abort"; } return; } ************* for generated data */ void init_outinfo (ptr) struct outinfo *ptr; { int i; static char empty[1] = {'\0'}; 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; ptr->file[i].fileinfo.st_dev = NO_SUCH_DEVICE; } /* 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[PRIMARY_ERROR_SINK].descrip = "primary_error";/* User cannot */ ptr->file[PRIMARY_DIAG_SINK].descrip = "primary_diag"; /* spec these */ ptr->file[DATA_COMMENT_SINK].sink = ADD_TO_BUFFER_FILE; ptr->file[NON_DATA_COMMENT_SINK].sink = DEFAULT_OTHER_COMMENT_FILE; ptr->file[ERROR_SINK].sink = DEFAULT_ERROR_FILE; ptr->file[DIAG_SINK].sink = DEFAULT_DEBUG_FILE; ptr->file[PRIMARY_ERROR_SINK].sink = PRIMARY_ERROR_FILE; ptr->file[PRIMARY_DIAG_SINK].sink = PRIMARY_DEBUG_FILE; 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; return; } void init_file_structs (files,indirect_file,comment_sources) struct fileinfo files[],*indirect_file,comment_sources[]; /* 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; /* +1 for \n; +1 to detect record overflow */ if ( (indirect_file->buf=(char *)malloc(MAXREC+1+1)) == NULL ) errn ("Could not allocate space for indirect file buffer. # bytes = ", MAXREC+1+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"; files[VARLISTOPTS].descrip="varlistopts"; files[DATACOMMENTS].descrip="datacomments"; files[COMMENTSOURCE].descrip="commentsource"; files[IDMISSING].descrip="identifymissingdata"; files[COORDMISSING].descrip="coordinatedmissingdata"; files[GENERATEDDATA].descrip="generateddata"; for (i=0;iitem_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) { msg = buildstring("Error opening indirect file ",indirect_name, "\nProblem: ", " open_indir_ msg"); err(msg,strerror(errno)); } 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); strdupl(&dirstring,indirect_name," saving .ind file's directory string"); /* If there is a directory, split it from file spec */ /* after its trailing / */ if ( (ptr = strrchr(dirstring,DIRSEP)) == NULL ) { free (dirstring); dirstring = NULL; } else *(++ptr) = '\0'; /* Truncate after found / */ 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 */ source = end_token1 = strpbrk(descrip,seps); if (end_token1 != NULL) source += 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) || (is_a_remote_object(descrip) != 0) || (source == NULL) || (*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) || (file_type == VARLIST) || (file_type == VARLISTOPTS)); 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 */ /* Legal type might follow prefix, but has no postfix; eg, */ /* "found" strings must end at same byte as input string */ 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, VARLIST, & */ /* VARLISTOPTS defaults to FALSE */ files[file_type].pre_trans = ( (file_type == DATAFILE) || (file_type == VARLIST) || (file_type == VARLISTOPTS) ); 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) */ /* See if it's a .objects_parameter key */ switch (key_N(OBJ_KEY,source,&i)) { case -1: /* Wasn't a .objects_parameter_N key. We're done */ break; case 0: /* .objects_parameter. Get N from line number, then fall */ /* through to check validity of N */ i = position + 1; case 1: 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]; /* Next test OK, but implies that under some circumstances */ /* .objects_parameter_N IS legal in an argument from a */ /* .objects file entry. Technically OK, but would */ /* probably be confusing... */ if (strncmp(OBJ_KEY,source,strlen(OBJ_KEY)) == 0) errn (".objects param may not request another .objects param\n" " Bad param = ",i); break; case 2: err ("Nothing may follow .objects_parameter_N keyword\n" " Illegal string: ",source); break; default: errn ("Internal coding error - illegal return from key_N\n" " illegal return value = ",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,dirstring); /* 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) { if (fclose(indirect_file->stream) != 0) err ("Failure to close indirect file. errno msg is ",strerror(errno)); indirect_file->open = FALSE; } if (dirstring != NULL) free (dirstring); return; }